diff --git a/.editorconfig b/.editorconfig index d065fa46469fc..eadd72e499373 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,8 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 + +[*.rs] max_line_length = 100 [*.md] diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 4c445f635d7d2..8040cb22855bd 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -14,3 +14,7 @@ c34fbfaad38cf5829ef5cfe780dc9d58480adeaa cf2dff2b1e3fa55fa5415d524200070d0d7aacfe # Run rustfmt on bootstrap b39a1d6f1a30ba29f25d7141038b9a5bf0126e36 +# reorder fluent message files +f97fddab91fbf290ea5b691fe355d6f915220b6e +# format let-else +cc907f80b95c6ec530c5ee1b05b044a468f07eca diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 7d4cfaece4481..a2878a0e01208 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,3 +6,6 @@ contact_links: - name: Feature Request url: https://internals.rust-lang.org/ about: Please discuss language feature requests on the internals forum. + - name: Clippy Bug + url: https://github.com/rust-lang/rust-clippy/issues/new/choose + about: Please report Clippy bugs such as false positives in the Clippy repo. diff --git a/.github/ISSUE_TEMPLATE/tracking_issue.md b/.github/ISSUE_TEMPLATE/tracking_issue.md index 0065960507179..5f17f30b3b0b1 100644 --- a/.github/ISSUE_TEMPLATE/tracking_issue.md +++ b/.github/ISSUE_TEMPLATE/tracking_issue.md @@ -39,10 +39,13 @@ for larger features an implementation could be broken up into multiple PRs. - [ ] Implement the RFC (cc @rust-lang/XXX -- can anyone write up mentoring instructions?) - [ ] Adjust documentation ([see instructions on rustc-dev-guide][doc-guide]) +- [ ] Formatting for new syntax has been added to the [Style Guide] ([nightly-style-procedure]) - [ ] Stabilization PR ([see instructions on rustc-dev-guide][stabilization-guide]) [stabilization-guide]: https://rustc-dev-guide.rust-lang.org/stabilization_guide.html#stabilization-pr [doc-guide]: https://rustc-dev-guide.rust-lang.org/stabilization_guide.html#documentation-prs +[nightly-style-procedure]: https://github.com/rust-lang/style-team/blob/master/nightly-style-procedure.md +[Style Guide]: https://github.com/rust-lang/rust/tree/master/src/doc/style-guide ### Unresolved Questions 765000 - // ^^^ - // But we shouldn't make the number look more precise than it is. - exp as usize > width || digits + exp as usize > precision - } else { - // Power of the most significant digit. - let msd = exp + (digits - 1) as ExpInt; - if msd >= 0 { - // 765e-2 == 7.65 - false - } else { - // 765e-5 == 0.00765 - // ^ ^^ - -msd as usize > width - } - }; - - // Scientific formatting is pretty straightforward. - if scientific { - exp += digits as ExpInt - 1; - - f.write_char(buffer[digits - 1] as char)?; - f.write_char('.')?; - let truncate_zero = !alternate; - if digits == 1 && truncate_zero { - f.write_char('0')?; - } else { - for &d in buffer[..digits - 1].iter().rev() { - f.write_char(d as char)?; - } - } - // Fill with zeros up to precision. - if !truncate_zero && precision > digits - 1 { - for _ in 0..=precision - digits { - f.write_char('0')?; - } - } - // For alternate we use lower 'e'. - f.write_char(if alternate { 'e' } else { 'E' })?; - - // Exponent always at least two digits if we do not truncate zeros. - if truncate_zero { - write!(f, "{:+}", exp)?; - } else { - write!(f, "{:+03}", exp)?; - } - - return Ok(()); - } - - // Non-scientific, positive exponents. - if exp >= 0 { - for &d in buffer.iter().rev() { - f.write_char(d as char)?; - } - for _ in 0..exp { - f.write_char('0')?; - } - return Ok(()); - } - - // Non-scientific, negative exponents. - let unit_place = -exp as usize; - if unit_place < digits { - for &d in buffer[unit_place..].iter().rev() { - f.write_char(d as char)?; - } - f.write_char('.')?; - for &d in buffer[..unit_place].iter().rev() { - f.write_char(d as char)?; - } - } else { - f.write_str("0.")?; - for _ in digits..unit_place { - f.write_char('0')?; - } - for &d in buffer.iter().rev() { - f.write_char(d as char)?; - } - } - - Ok(()) - } -} - -impl fmt::Debug for IeeeFloat { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}({:?} | {}{:?} * 2^{})", - self, - self.category, - if self.sign { "-" } else { "+" }, - self.sig, - self.exp - ) - } -} - -impl Float for IeeeFloat { - const BITS: usize = S::BITS; - const PRECISION: usize = S::PRECISION; - const MAX_EXP: ExpInt = S::MAX_EXP; - const MIN_EXP: ExpInt = S::MIN_EXP; - - const ZERO: Self = IeeeFloat { - sig: [0], - exp: S::MIN_EXP - 1, - category: Category::Zero, - sign: false, - marker: PhantomData, - }; - - const INFINITY: Self = IeeeFloat { - sig: [0], - exp: S::MAX_EXP + 1, - category: Category::Infinity, - sign: false, - marker: PhantomData, - }; - - // FIXME(eddyb) remove when qnan becomes const fn. - const NAN: Self = IeeeFloat { - sig: [S::QNAN_SIGNIFICAND], - exp: S::MAX_EXP + 1, - category: Category::NaN, - sign: false, - marker: PhantomData, - }; - - fn qnan(payload: Option) -> Self { - IeeeFloat { - sig: [S::QNAN_SIGNIFICAND - | payload.map_or(0, |payload| { - // Zero out the excess bits of the significand. - payload & ((1 << S::QNAN_BIT) - 1) - })], - exp: S::MAX_EXP + 1, - category: Category::NaN, - sign: false, - marker: PhantomData, - } - } - - fn snan(payload: Option) -> Self { - let mut snan = Self::qnan(payload); - - // We always have to clear the QNaN bit to make it an SNaN. - sig::clear_bit(&mut snan.sig, S::QNAN_BIT); - - // If there are no bits set in the payload, we have to set - // *something* to make it a NaN instead of an infinity; - // conventionally, this is the next bit down from the QNaN bit. - if snan.sig[0] & !S::QNAN_SIGNIFICAND == 0 { - sig::set_bit(&mut snan.sig, S::QNAN_BIT - 1); - } - - snan - } - - fn largest() -> Self { - // We want (in interchange format): - // exponent = 1..10 - // significand = 1..1 - IeeeFloat { - sig: [(1 << S::PRECISION) - 1], - exp: S::MAX_EXP, - category: Category::Normal, - sign: false, - marker: PhantomData, - } - } - - // We want (in interchange format): - // exponent = 0..0 - // significand = 0..01 - const SMALLEST: Self = IeeeFloat { - sig: [1], - exp: S::MIN_EXP, - category: Category::Normal, - sign: false, - marker: PhantomData, - }; - - fn smallest_normalized() -> Self { - // We want (in interchange format): - // exponent = 0..0 - // significand = 10..0 - IeeeFloat { - sig: [1 << (S::PRECISION - 1)], - exp: S::MIN_EXP, - category: Category::Normal, - sign: false, - marker: PhantomData, - } - } - - fn add_r(mut self, rhs: Self, round: Round) -> StatusAnd { - let status = match (self.category, rhs.category) { - (Category::Infinity, Category::Infinity) => { - // Differently signed infinities can only be validly - // subtracted. - if self.sign != rhs.sign { - self = Self::NAN; - Status::INVALID_OP - } else { - Status::OK - } - } - - // Sign may depend on rounding mode; handled below. - (_, Category::Zero) | (Category::NaN, _) | (Category::Infinity, Category::Normal) => { - Status::OK - } - - (Category::Zero, _) | (_, Category::NaN | Category::Infinity) => { - self = rhs; - Status::OK - } - - // This return code means it was not a simple case. - (Category::Normal, Category::Normal) => { - let loss = sig::add_or_sub( - &mut self.sig, - &mut self.exp, - &mut self.sign, - &mut [rhs.sig[0]], - rhs.exp, - rhs.sign, - ); - let status; - self = unpack!(status=, self.normalize(round, loss)); - - // Can only be zero if we lost no fraction. - assert!(self.category != Category::Zero || loss == Loss::ExactlyZero); - - status - } - }; - - // If two numbers add (exactly) to zero, IEEE 754 decrees it is a - // positive zero unless rounding to minus infinity, except that - // adding two like-signed zeroes gives that zero. - if self.category == Category::Zero - && (rhs.category != Category::Zero || self.sign != rhs.sign) - { - self.sign = round == Round::TowardNegative; - } - - status.and(self) - } - - fn mul_r(mut self, rhs: Self, round: Round) -> StatusAnd { - self.sign ^= rhs.sign; - - match (self.category, rhs.category) { - (Category::NaN, _) => { - self.sign = false; - Status::OK.and(self) - } - - (_, Category::NaN) => { - self.sign = false; - self.category = Category::NaN; - self.sig = rhs.sig; - Status::OK.and(self) - } - - (Category::Zero, Category::Infinity) | (Category::Infinity, Category::Zero) => { - Status::INVALID_OP.and(Self::NAN) - } - - (_, Category::Infinity) | (Category::Infinity, _) => { - self.category = Category::Infinity; - Status::OK.and(self) - } - - (Category::Zero, _) | (_, Category::Zero) => { - self.category = Category::Zero; - Status::OK.and(self) - } - - (Category::Normal, Category::Normal) => { - self.exp += rhs.exp; - let mut wide_sig = [0; 2]; - let loss = - sig::mul(&mut wide_sig, &mut self.exp, &self.sig, &rhs.sig, S::PRECISION); - self.sig = [wide_sig[0]]; - let mut status; - self = unpack!(status=, self.normalize(round, loss)); - if loss != Loss::ExactlyZero { - status |= Status::INEXACT; - } - status.and(self) - } - } - } - - fn mul_add_r(mut self, multiplicand: Self, addend: Self, round: Round) -> StatusAnd { - // If and only if all arguments are normal do we need to do an - // extended-precision calculation. - if !self.is_finite_non_zero() || !multiplicand.is_finite_non_zero() || !addend.is_finite() { - let mut status; - self = unpack!(status=, self.mul_r(multiplicand, round)); - - // FS can only be Status::OK or Status::INVALID_OP. There is no more work - // to do in the latter case. The IEEE-754R standard says it is - // implementation-defined in this case whether, if ADDEND is a - // quiet NaN, we raise invalid op; this implementation does so. - // - // If we need to do the addition we can do so with normal - // precision. - if status == Status::OK { - self = unpack!(status=, self.add_r(addend, round)); - } - return status.and(self); - } - - // Post-multiplication sign, before addition. - self.sign ^= multiplicand.sign; - - // Allocate space for twice as many bits as the original significand, plus one - // extra bit for the addition to overflow into. - assert!(limbs_for_bits(S::PRECISION * 2 + 1) <= 2); - let mut wide_sig = sig::widening_mul(self.sig[0], multiplicand.sig[0]); - - let mut loss = Loss::ExactlyZero; - let mut omsb = sig::omsb(&wide_sig); - self.exp += multiplicand.exp; - - // Assume the operands involved in the multiplication are single-precision - // FP, and the two multiplicants are: - // lhs = a23 . a22 ... a0 * 2^e1 - // rhs = b23 . b22 ... b0 * 2^e2 - // the result of multiplication is: - // lhs = c48 c47 c46 . c45 ... c0 * 2^(e1+e2) - // Note that there are three significant bits at the left-hand side of the - // radix point: two for the multiplication, and an overflow bit for the - // addition (that will always be zero at this point). Move the radix point - // toward left by two bits, and adjust exponent accordingly. - self.exp += 2; - - if addend.is_non_zero() { - // Normalize our MSB to one below the top bit to allow for overflow. - let ext_precision = 2 * S::PRECISION + 1; - if omsb != ext_precision - 1 { - assert!(ext_precision > omsb); - sig::shift_left(&mut wide_sig, &mut self.exp, (ext_precision - 1) - omsb); - } - - // The intermediate result of the multiplication has "2 * S::PRECISION" - // significant bit; adjust the addend to be consistent with mul result. - let mut ext_addend_sig = [addend.sig[0], 0]; - - // Extend the addend significand to ext_precision - 1. This guarantees - // that the high bit of the significand is zero (same as wide_sig), - // so the addition will overflow (if it does overflow at all) into the top bit. - sig::shift_left(&mut ext_addend_sig, &mut 0, ext_precision - 1 - S::PRECISION); - loss = sig::add_or_sub( - &mut wide_sig, - &mut self.exp, - &mut self.sign, - &mut ext_addend_sig, - addend.exp + 1, - addend.sign, - ); - - omsb = sig::omsb(&wide_sig); - } - - // Convert the result having "2 * S::PRECISION" significant-bits back to the one - // having "S::PRECISION" significant-bits. First, move the radix point from - // position "2*S::PRECISION - 1" to "S::PRECISION - 1". The exponent need to be - // adjusted by "2*S::PRECISION - 1" - "S::PRECISION - 1" = "S::PRECISION". - self.exp -= S::PRECISION as ExpInt + 1; - - // In case MSB resides at the left-hand side of radix point, shift the - // mantissa right by some amount to make sure the MSB reside right before - // the radix point (i.e., "MSB . rest-significant-bits"). - if omsb > S::PRECISION { - let bits = omsb - S::PRECISION; - loss = sig::shift_right(&mut wide_sig, &mut self.exp, bits).combine(loss); - } - - self.sig[0] = wide_sig[0]; - - let mut status; - self = unpack!(status=, self.normalize(round, loss)); - if loss != Loss::ExactlyZero { - status |= Status::INEXACT; - } - - // If two numbers add (exactly) to zero, IEEE 754 decrees it is a - // positive zero unless rounding to minus infinity, except that - // adding two like-signed zeroes gives that zero. - if self.category == Category::Zero - && !status.intersects(Status::UNDERFLOW) - && self.sign != addend.sign - { - self.sign = round == Round::TowardNegative; - } - - status.and(self) - } - - fn div_r(mut self, rhs: Self, round: Round) -> StatusAnd { - self.sign ^= rhs.sign; - - match (self.category, rhs.category) { - (Category::NaN, _) => { - self.sign = false; - Status::OK.and(self) - } - - (_, Category::NaN) => { - self.category = Category::NaN; - self.sig = rhs.sig; - self.sign = false; - Status::OK.and(self) - } - - (Category::Infinity, Category::Infinity) | (Category::Zero, Category::Zero) => { - Status::INVALID_OP.and(Self::NAN) - } - - (Category::Infinity | Category::Zero, _) => Status::OK.and(self), - - (Category::Normal, Category::Infinity) => { - self.category = Category::Zero; - Status::OK.and(self) - } - - (Category::Normal, Category::Zero) => { - self.category = Category::Infinity; - Status::DIV_BY_ZERO.and(self) - } - - (Category::Normal, Category::Normal) => { - self.exp -= rhs.exp; - let dividend = self.sig[0]; - let loss = sig::div( - &mut self.sig, - &mut self.exp, - &mut [dividend], - &mut [rhs.sig[0]], - S::PRECISION, - ); - let mut status; - self = unpack!(status=, self.normalize(round, loss)); - if loss != Loss::ExactlyZero { - status |= Status::INEXACT; - } - status.and(self) - } - } - } - - fn c_fmod(mut self, rhs: Self) -> StatusAnd { - match (self.category, rhs.category) { - (Category::NaN, _) - | (Category::Zero, Category::Infinity | Category::Normal) - | (Category::Normal, Category::Infinity) => Status::OK.and(self), - - (_, Category::NaN) => { - self.sign = false; - self.category = Category::NaN; - self.sig = rhs.sig; - Status::OK.and(self) - } - - (Category::Infinity, _) | (_, Category::Zero) => Status::INVALID_OP.and(Self::NAN), - - (Category::Normal, Category::Normal) => { - while self.is_finite_non_zero() - && rhs.is_finite_non_zero() - && self.cmp_abs_normal(rhs) != Ordering::Less - { - let mut v = rhs.scalbn(self.ilogb() - rhs.ilogb()); - if self.cmp_abs_normal(v) == Ordering::Less { - v = v.scalbn(-1); - } - v.sign = self.sign; - - let status; - self = unpack!(status=, self - v); - assert_eq!(status, Status::OK); - } - Status::OK.and(self) - } - } - } - - fn round_to_integral(self, round: Round) -> StatusAnd { - // If the exponent is large enough, we know that this value is already - // integral, and the arithmetic below would potentially cause it to saturate - // to +/-Inf. Bail out early instead. - if self.is_finite_non_zero() && self.exp + 1 >= S::PRECISION as ExpInt { - return Status::OK.and(self); - } - - // The algorithm here is quite simple: we add 2^(p-1), where p is the - // precision of our format, and then subtract it back off again. The choice - // of rounding modes for the addition/subtraction determines the rounding mode - // for our integral rounding as well. - // NOTE: When the input value is negative, we do subtraction followed by - // addition instead. - assert!(S::PRECISION <= 128); - let mut status; - let magic_const = unpack!(status=, Self::from_u128(1 << (S::PRECISION - 1))); - let magic_const = magic_const.copy_sign(self); - - if status != Status::OK { - return status.and(self); - } - - let mut r = self; - r = unpack!(status=, r.add_r(magic_const, round)); - if status != Status::OK && status != Status::INEXACT { - return status.and(self); - } - - // Restore the input sign to handle 0.0/-0.0 cases correctly. - r.sub_r(magic_const, round).map(|r| r.copy_sign(self)) - } - - fn next_up(mut self) -> StatusAnd { - // Compute nextUp(x), handling each float category separately. - match self.category { - Category::Infinity => { - if self.sign { - // nextUp(-inf) = -largest - Status::OK.and(-Self::largest()) - } else { - // nextUp(+inf) = +inf - Status::OK.and(self) - } - } - Category::NaN => { - // IEEE-754R 2008 6.2 Par 2: nextUp(sNaN) = qNaN. Set Invalid flag. - // IEEE-754R 2008 6.2: nextUp(qNaN) = qNaN. Must be identity so we do not - // change the payload. - if self.is_signaling() { - // For consistency, propagate the sign of the sNaN to the qNaN. - Status::INVALID_OP.and(Self::NAN.copy_sign(self)) - } else { - Status::OK.and(self) - } - } - Category::Zero => { - // nextUp(pm 0) = +smallest - Status::OK.and(Self::SMALLEST) - } - Category::Normal => { - // nextUp(-smallest) = -0 - if self.is_smallest() && self.sign { - return Status::OK.and(-Self::ZERO); - } - - // nextUp(largest) == INFINITY - if self.is_largest() && !self.sign { - return Status::OK.and(Self::INFINITY); - } - - // Excluding the integral bit. This allows us to test for binade boundaries. - let sig_mask = (1 << (S::PRECISION - 1)) - 1; - - // nextUp(normal) == normal + inc. - if self.sign { - // If we are negative, we need to decrement the significand. - - // We only cross a binade boundary that requires adjusting the exponent - // if: - // 1. exponent != S::MIN_EXP. This implies we are not in the - // smallest binade or are dealing with denormals. - // 2. Our significand excluding the integral bit is all zeros. - let crossing_binade_boundary = - self.exp != S::MIN_EXP && self.sig[0] & sig_mask == 0; - - // Decrement the significand. - // - // We always do this since: - // 1. If we are dealing with a non-binade decrement, by definition we - // just decrement the significand. - // 2. If we are dealing with a normal -> normal binade decrement, since - // we have an explicit integral bit the fact that all bits but the - // integral bit are zero implies that subtracting one will yield a - // significand with 0 integral bit and 1 in all other spots. Thus we - // must just adjust the exponent and set the integral bit to 1. - // 3. If we are dealing with a normal -> denormal binade decrement, - // since we set the integral bit to 0 when we represent denormals, we - // just decrement the significand. - sig::decrement(&mut self.sig); - - if crossing_binade_boundary { - // Our result is a normal number. Do the following: - // 1. Set the integral bit to 1. - // 2. Decrement the exponent. - sig::set_bit(&mut self.sig, S::PRECISION - 1); - self.exp -= 1; - } - } else { - // If we are positive, we need to increment the significand. - - // We only cross a binade boundary that requires adjusting the exponent if - // the input is not a denormal and all of said input's significand bits - // are set. If all of said conditions are true: clear the significand, set - // the integral bit to 1, and increment the exponent. If we have a - // denormal always increment since moving denormals and the numbers in the - // smallest normal binade have the same exponent in our representation. - let crossing_binade_boundary = - !self.is_denormal() && self.sig[0] & sig_mask == sig_mask; - - if crossing_binade_boundary { - self.sig = [0]; - sig::set_bit(&mut self.sig, S::PRECISION - 1); - assert_ne!( - self.exp, - S::MAX_EXP, - "We can not increment an exponent beyond the MAX_EXP \ - allowed by the given floating point semantics." - ); - self.exp += 1; - } else { - sig::increment(&mut self.sig); - } - } - Status::OK.and(self) - } - } - } - - fn from_bits(input: u128) -> Self { - // Dispatch to semantics. - S::from_bits(input) - } - - fn from_u128_r(input: u128, round: Round) -> StatusAnd { - IeeeFloat { - sig: [input], - exp: S::PRECISION as ExpInt - 1, - category: Category::Normal, - sign: false, - marker: PhantomData, - } - .normalize(round, Loss::ExactlyZero) - } - - fn from_str_r(mut s: &str, mut round: Round) -> Result, ParseError> { - if s.is_empty() { - return Err(ParseError("Invalid string length")); - } - - // Handle special cases. - match s { - "inf" | "INFINITY" => return Ok(Status::OK.and(Self::INFINITY)), - "-inf" | "-INFINITY" => return Ok(Status::OK.and(-Self::INFINITY)), - "nan" | "NaN" => return Ok(Status::OK.and(Self::NAN)), - "-nan" | "-NaN" => return Ok(Status::OK.and(-Self::NAN)), - _ => {} - } - - // Handle a leading minus sign. - let minus = s.starts_with('-'); - if minus || s.starts_with('+') { - s = &s[1..]; - if s.is_empty() { - return Err(ParseError("String has no digits")); - } - } - - // Adjust the rounding mode for the absolute value below. - if minus { - round = -round; - } - - let r = if s.starts_with("0x") || s.starts_with("0X") { - s = &s[2..]; - if s.is_empty() { - return Err(ParseError("Invalid string")); - } - Self::from_hexadecimal_string(s, round)? - } else { - Self::from_decimal_string(s, round)? - }; - - Ok(r.map(|r| if minus { -r } else { r })) - } - - fn to_bits(self) -> u128 { - // Dispatch to semantics. - S::to_bits(self) - } - - fn to_u128_r(self, width: usize, round: Round, is_exact: &mut bool) -> StatusAnd { - // The result of trying to convert a number too large. - let overflow = if self.sign { - // Negative numbers cannot be represented as unsigned. - 0 - } else { - // Largest unsigned integer of the given width. - !0 >> (128 - width) - }; - - *is_exact = false; - - match self.category { - Category::NaN => Status::INVALID_OP.and(0), - - Category::Infinity => Status::INVALID_OP.and(overflow), - - Category::Zero => { - // Negative zero can't be represented as an int. - *is_exact = !self.sign; - Status::OK.and(0) - } - - Category::Normal => { - let mut r = 0; - - // Step 1: place our absolute value, with any fraction truncated, in - // the destination. - let truncated_bits = if self.exp < 0 { - // Our absolute value is less than one; truncate everything. - // For exponent -1 the integer bit represents .5, look at that. - // For smaller exponents leftmost truncated bit is 0. - S::PRECISION - 1 + (-self.exp) as usize - } else { - // We want the most significant (exponent + 1) bits; the rest are - // truncated. - let bits = self.exp as usize + 1; - - // Hopelessly large in magnitude? - if bits > width { - return Status::INVALID_OP.and(overflow); - } - - if bits < S::PRECISION { - // We truncate (S::PRECISION - bits) bits. - r = self.sig[0] >> (S::PRECISION - bits); - S::PRECISION - bits - } else { - // We want at least as many bits as are available. - r = self.sig[0] << (bits - S::PRECISION); - 0 - } - }; - - // Step 2: work out any lost fraction, and increment the absolute - // value if we would round away from zero. - let mut loss = Loss::ExactlyZero; - if truncated_bits > 0 { - loss = Loss::through_truncation(&self.sig, truncated_bits); - if loss != Loss::ExactlyZero - && self.round_away_from_zero(round, loss, truncated_bits) - { - r = r.wrapping_add(1); - if r == 0 { - return Status::INVALID_OP.and(overflow); // Overflow. - } - } - } - - // Step 3: check if we fit in the destination. - if r > overflow { - return Status::INVALID_OP.and(overflow); - } - - if loss == Loss::ExactlyZero { - *is_exact = true; - Status::OK.and(r) - } else { - Status::INEXACT.and(r) - } - } - } - } - - fn cmp_abs_normal(self, rhs: Self) -> Ordering { - assert!(self.is_finite_non_zero()); - assert!(rhs.is_finite_non_zero()); - - // If exponents are equal, do an unsigned comparison of the significands. - self.exp.cmp(&rhs.exp).then_with(|| sig::cmp(&self.sig, &rhs.sig)) - } - - fn bitwise_eq(self, rhs: Self) -> bool { - if self.category != rhs.category || self.sign != rhs.sign { - return false; - } - - if self.category == Category::Zero || self.category == Category::Infinity { - return true; - } - - if self.is_finite_non_zero() && self.exp != rhs.exp { - return false; - } - - self.sig == rhs.sig - } - - fn is_negative(self) -> bool { - self.sign - } - - fn is_denormal(self) -> bool { - self.is_finite_non_zero() - && self.exp == S::MIN_EXP - && !sig::get_bit(&self.sig, S::PRECISION - 1) - } - - fn is_signaling(self) -> bool { - // IEEE-754R 2008 6.2.1: A signaling NaN bit string should be encoded with the - // first bit of the trailing significand being 0. - self.is_nan() && !sig::get_bit(&self.sig, S::QNAN_BIT) - } - - fn category(self) -> Category { - self.category - } - - fn get_exact_inverse(self) -> Option { - // Special floats and denormals have no exact inverse. - if !self.is_finite_non_zero() { - return None; - } - - // Check that the number is a power of two by making sure that only the - // integer bit is set in the significand. - if self.sig != [1 << (S::PRECISION - 1)] { - return None; - } - - // Get the inverse. - let mut reciprocal = Self::from_u128(1).value; - let status; - reciprocal = unpack!(status=, reciprocal / self); - if status != Status::OK { - return None; - } - - // Avoid multiplication with a denormal, it is not safe on all platforms and - // may be slower than a normal division. - if reciprocal.is_denormal() { - return None; - } - - assert!(reciprocal.is_finite_non_zero()); - assert_eq!(reciprocal.sig, [1 << (S::PRECISION - 1)]); - - Some(reciprocal) - } - - fn ilogb(mut self) -> ExpInt { - if self.is_nan() { - return IEK_NAN; - } - if self.is_zero() { - return IEK_ZERO; - } - if self.is_infinite() { - return IEK_INF; - } - if !self.is_denormal() { - return self.exp; - } - - let sig_bits = (S::PRECISION - 1) as ExpInt; - self.exp += sig_bits; - self = self.normalize(Round::NearestTiesToEven, Loss::ExactlyZero).value; - self.exp - sig_bits - } - - fn scalbn_r(mut self, exp: ExpInt, round: Round) -> Self { - // If exp is wildly out-of-scale, simply adding it to self.exp will - // overflow; clamp it to a safe range before adding, but ensure that the range - // is large enough that the clamp does not change the result. The range we - // need to support is the difference between the largest possible exponent and - // the normalized exponent of half the smallest denormal. - - let sig_bits = (S::PRECISION - 1) as i32; - let max_change = S::MAX_EXP as i32 - (S::MIN_EXP as i32 - sig_bits) + 1; - - // Clamp to one past the range ends to let normalize handle overflow. - let exp_change = cmp::min(cmp::max(exp as i32, -max_change - 1), max_change); - self.exp = self.exp.saturating_add(exp_change as ExpInt); - self = self.normalize(round, Loss::ExactlyZero).value; - if self.is_nan() { - sig::set_bit(&mut self.sig, S::QNAN_BIT); - } - self - } - - fn frexp_r(mut self, exp: &mut ExpInt, round: Round) -> Self { - *exp = self.ilogb(); - - // Quiet signalling nans. - if *exp == IEK_NAN { - sig::set_bit(&mut self.sig, S::QNAN_BIT); - return self; - } - - if *exp == IEK_INF { - return self; - } - - // 1 is added because frexp is defined to return a normalized fraction in - // +/-[0.5, 1.0), rather than the usual +/-[1.0, 2.0). - if *exp == IEK_ZERO { - *exp = 0; - } else { - *exp += 1; - } - self.scalbn_r(-*exp, round) - } -} - -impl FloatConvert> for IeeeFloat { - fn convert_r(self, round: Round, loses_info: &mut bool) -> StatusAnd> { - let mut r = IeeeFloat { - sig: self.sig, - exp: self.exp, - category: self.category, - sign: self.sign, - marker: PhantomData, - }; - - // x86 has some unusual NaNs which cannot be represented in any other - // format; note them here. - fn is_x87_double_extended() -> bool { - S::QNAN_SIGNIFICAND == X87DoubleExtendedS::QNAN_SIGNIFICAND - } - let x87_special_nan = is_x87_double_extended::() - && !is_x87_double_extended::() - && r.category == Category::NaN - && (r.sig[0] & S::QNAN_SIGNIFICAND) != S::QNAN_SIGNIFICAND; - - // If this is a truncation of a denormal number, and the target semantics - // has larger exponent range than the source semantics (this can happen - // when truncating from PowerPC double-double to double format), the - // right shift could lose result mantissa bits. Adjust exponent instead - // of performing excessive shift. - let mut shift = T::PRECISION as ExpInt - S::PRECISION as ExpInt; - if shift < 0 && r.is_finite_non_zero() { - let mut exp_change = sig::omsb(&r.sig) as ExpInt - S::PRECISION as ExpInt; - if r.exp + exp_change < T::MIN_EXP { - exp_change = T::MIN_EXP - r.exp; - } - if exp_change < shift { - exp_change = shift; - } - if exp_change < 0 { - shift -= exp_change; - r.exp += exp_change; - } - } - - // If this is a truncation, perform the shift. - let loss = if shift < 0 && (r.is_finite_non_zero() || r.category == Category::NaN) { - sig::shift_right(&mut r.sig, &mut 0, -shift as usize) - } else { - Loss::ExactlyZero - }; - - // If this is an extension, perform the shift. - if shift > 0 && (r.is_finite_non_zero() || r.category == Category::NaN) { - sig::shift_left(&mut r.sig, &mut 0, shift as usize); - } - - let status; - if r.is_finite_non_zero() { - r = unpack!(status=, r.normalize(round, loss)); - *loses_info = status != Status::OK; - } else if r.category == Category::NaN { - *loses_info = loss != Loss::ExactlyZero || x87_special_nan; - - // For x87 extended precision, we want to make a NaN, not a special NaN if - // the input wasn't special either. - if !x87_special_nan && is_x87_double_extended::() { - sig::set_bit(&mut r.sig, T::PRECISION - 1); - } - - // Convert of sNaN creates qNaN and raises an exception (invalid op). - // This also guarantees that a sNaN does not become Inf on a truncation - // that loses all payload bits. - if self.is_signaling() { - // Quiet signaling NaN. - sig::set_bit(&mut r.sig, T::QNAN_BIT); - status = Status::INVALID_OP; - } else { - status = Status::OK; - } - } else { - *loses_info = false; - status = Status::OK; - } - - status.and(r) - } -} - -impl IeeeFloat { - /// Handle positive overflow. We either return infinity or - /// the largest finite number. For negative overflow, - /// negate the `round` argument before calling. - fn overflow_result(round: Round) -> StatusAnd { - match round { - // Infinity? - Round::NearestTiesToEven | Round::NearestTiesToAway | Round::TowardPositive => { - (Status::OVERFLOW | Status::INEXACT).and(Self::INFINITY) - } - // Otherwise we become the largest finite number. - Round::TowardNegative | Round::TowardZero => Status::INEXACT.and(Self::largest()), - } - } - - /// Returns `true` if, when truncating the current number, with `bit` the - /// new LSB, with the given lost fraction and rounding mode, the result - /// would need to be rounded away from zero (i.e., by increasing the - /// signficand). This routine must work for `Category::Zero` of both signs, and - /// `Category::Normal` numbers. - fn round_away_from_zero(&self, round: Round, loss: Loss, bit: usize) -> bool { - // NaNs and infinities should not have lost fractions. - assert!(self.is_finite_non_zero() || self.is_zero()); - - // Current callers never pass this so we don't handle it. - assert_ne!(loss, Loss::ExactlyZero); - - match round { - Round::NearestTiesToAway => loss == Loss::ExactlyHalf || loss == Loss::MoreThanHalf, - Round::NearestTiesToEven => { - if loss == Loss::MoreThanHalf { - return true; - } - - // Our zeros don't have a significand to test. - if loss == Loss::ExactlyHalf && self.category != Category::Zero { - return sig::get_bit(&self.sig, bit); - } - - false - } - Round::TowardZero => false, - Round::TowardPositive => !self.sign, - Round::TowardNegative => self.sign, - } - } - - fn normalize(mut self, round: Round, mut loss: Loss) -> StatusAnd { - if !self.is_finite_non_zero() { - return Status::OK.and(self); - } - - // Before rounding normalize the exponent of Category::Normal numbers. - let mut omsb = sig::omsb(&self.sig); - - if omsb > 0 { - // OMSB is numbered from 1. We want to place it in the integer - // bit numbered PRECISION if possible, with a compensating change in - // the exponent. - let mut final_exp = self.exp.saturating_add(omsb as ExpInt - S::PRECISION as ExpInt); - - // If the resulting exponent is too high, overflow according to - // the rounding mode. - if final_exp > S::MAX_EXP { - let round = if self.sign { -round } else { round }; - return Self::overflow_result(round).map(|r| r.copy_sign(self)); - } - - // Subnormal numbers have exponent MIN_EXP, and their MSB - // is forced based on that. - if final_exp < S::MIN_EXP { - final_exp = S::MIN_EXP; - } - - // Shifting left is easy as we don't lose precision. - if final_exp < self.exp { - assert_eq!(loss, Loss::ExactlyZero); - - let exp_change = (self.exp - final_exp) as usize; - sig::shift_left(&mut self.sig, &mut self.exp, exp_change); - - return Status::OK.and(self); - } - - // Shift right and capture any new lost fraction. - if final_exp > self.exp { - let exp_change = (final_exp - self.exp) as usize; - loss = sig::shift_right(&mut self.sig, &mut self.exp, exp_change).combine(loss); - - // Keep OMSB up-to-date. - omsb = omsb.saturating_sub(exp_change); - } - } - - // Now round the number according to round given the lost - // fraction. - - // As specified in IEEE 754, since we do not trap we do not report - // underflow for exact results. - if loss == Loss::ExactlyZero { - // Canonicalize zeros. - if omsb == 0 { - self.category = Category::Zero; - } - - return Status::OK.and(self); - } - - // Increment the significand if we're rounding away from zero. - if self.round_away_from_zero(round, loss, 0) { - if omsb == 0 { - self.exp = S::MIN_EXP; - } - - // We should never overflow. - assert_eq!(sig::increment(&mut self.sig), 0); - omsb = sig::omsb(&self.sig); - - // Did the significand increment overflow? - if omsb == S::PRECISION + 1 { - // Renormalize by incrementing the exponent and shifting our - // significand right one. However if we already have the - // maximum exponent we overflow to infinity. - if self.exp == S::MAX_EXP { - self.category = Category::Infinity; - - return (Status::OVERFLOW | Status::INEXACT).and(self); - } - - let _: Loss = sig::shift_right(&mut self.sig, &mut self.exp, 1); - - return Status::INEXACT.and(self); - } - } - - // The normal case - we were and are not denormal, and any - // significand increment above didn't overflow. - if omsb == S::PRECISION { - return Status::INEXACT.and(self); - } - - // We have a non-zero denormal. - assert!(omsb < S::PRECISION); - - // Canonicalize zeros. - if omsb == 0 { - self.category = Category::Zero; - } - - // The Category::Zero case is a denormal that underflowed to zero. - (Status::UNDERFLOW | Status::INEXACT).and(self) - } - - fn from_hexadecimal_string(s: &str, round: Round) -> Result, ParseError> { - let mut r = IeeeFloat { - sig: [0], - exp: 0, - category: Category::Normal, - sign: false, - marker: PhantomData, - }; - - let mut any_digits = false; - let mut has_exp = false; - let mut bit_pos = LIMB_BITS as isize; - let mut loss = None; - - // Without leading or trailing zeros, irrespective of the dot. - let mut first_sig_digit = None; - let mut dot = s.len(); - - for (p, c) in s.char_indices() { - // Skip leading zeros and any (hexa)decimal point. - if c == '.' { - if dot != s.len() { - return Err(ParseError("String contains multiple dots")); - } - dot = p; - } else if let Some(hex_value) = c.to_digit(16) { - any_digits = true; - - if first_sig_digit.is_none() { - if hex_value == 0 { - continue; - } - first_sig_digit = Some(p); - } - - // Store the number while we have space. - bit_pos -= 4; - if bit_pos >= 0 { - r.sig[0] |= (hex_value as Limb) << bit_pos; - // If zero or one-half (the hexadecimal digit 8) are followed - // by non-zero, they're a little more than zero or one-half. - } else if let Some(ref mut loss) = loss { - if hex_value != 0 { - if *loss == Loss::ExactlyZero { - *loss = Loss::LessThanHalf; - } - if *loss == Loss::ExactlyHalf { - *loss = Loss::MoreThanHalf; - } - } - } else { - loss = Some(match hex_value { - 0 => Loss::ExactlyZero, - 1..=7 => Loss::LessThanHalf, - 8 => Loss::ExactlyHalf, - 9..=15 => Loss::MoreThanHalf, - _ => unreachable!(), - }); - } - } else if c == 'p' || c == 'P' { - if !any_digits { - return Err(ParseError("Significand has no digits")); - } - - if dot == s.len() { - dot = p; - } - - let mut chars = s[p + 1..].chars().peekable(); - - // Adjust for the given exponent. - let exp_minus = chars.peek() == Some(&'-'); - if exp_minus || chars.peek() == Some(&'+') { - chars.next(); - } - - for c in chars { - if let Some(value) = c.to_digit(10) { - has_exp = true; - r.exp = r.exp.saturating_mul(10).saturating_add(value as ExpInt); - } else { - return Err(ParseError("Invalid character in exponent")); - } - } - if !has_exp { - return Err(ParseError("Exponent has no digits")); - } - - if exp_minus { - r.exp = -r.exp; - } - - break; - } else { - return Err(ParseError("Invalid character in significand")); - } - } - if !any_digits { - return Err(ParseError("Significand has no digits")); - } - - // Hex floats require an exponent but not a hexadecimal point. - if !has_exp { - return Err(ParseError("Hex strings require an exponent")); - } - - // Ignore the exponent if we are zero. - let first_sig_digit = match first_sig_digit { - Some(p) => p, - None => return Ok(Status::OK.and(Self::ZERO)), - }; - - // Calculate the exponent adjustment implicit in the number of - // significant digits and adjust for writing the significand starting - // at the most significant nibble. - let exp_adjustment = if dot > first_sig_digit { - ExpInt::try_from(dot - first_sig_digit).unwrap() - } else { - -ExpInt::try_from(first_sig_digit - dot - 1).unwrap() - }; - let exp_adjustment = exp_adjustment - .saturating_mul(4) - .saturating_sub(1) - .saturating_add(S::PRECISION as ExpInt) - .saturating_sub(LIMB_BITS as ExpInt); - r.exp = r.exp.saturating_add(exp_adjustment); - - Ok(r.normalize(round, loss.unwrap_or(Loss::ExactlyZero))) - } - - fn from_decimal_string(s: &str, round: Round) -> Result, ParseError> { - // Given a normal decimal floating point number of the form - // - // dddd.dddd[eE][+-]ddd - // - // where the decimal point and exponent are optional, fill out the - // variables below. Exponent is appropriate if the significand is - // treated as an integer, and normalized_exp if the significand - // is taken to have the decimal point after a single leading - // non-zero digit. - // - // If the value is zero, first_sig_digit is None. - - let mut any_digits = false; - let mut dec_exp = 0i32; - - // Without leading or trailing zeros, irrespective of the dot. - let mut first_sig_digit = None; - let mut last_sig_digit = 0; - let mut dot = s.len(); - - for (p, c) in s.char_indices() { - if c == '.' { - if dot != s.len() { - return Err(ParseError("String contains multiple dots")); - } - dot = p; - } else if let Some(dec_value) = c.to_digit(10) { - any_digits = true; - - if dec_value != 0 { - if first_sig_digit.is_none() { - first_sig_digit = Some(p); - } - last_sig_digit = p; - } - } else if c == 'e' || c == 'E' { - if !any_digits { - return Err(ParseError("Significand has no digits")); - } - - if dot == s.len() { - dot = p; - } - - let mut chars = s[p + 1..].chars().peekable(); - - // Adjust for the given exponent. - let exp_minus = chars.peek() == Some(&'-'); - if exp_minus || chars.peek() == Some(&'+') { - chars.next(); - } - - any_digits = false; - for c in chars { - if let Some(value) = c.to_digit(10) { - any_digits = true; - dec_exp = dec_exp.saturating_mul(10).saturating_add(value as i32); - } else { - return Err(ParseError("Invalid character in exponent")); - } - } - if !any_digits { - return Err(ParseError("Exponent has no digits")); - } - - if exp_minus { - dec_exp = -dec_exp; - } - - break; - } else { - return Err(ParseError("Invalid character in significand")); - } - } - if !any_digits { - return Err(ParseError("Significand has no digits")); - } - - // Test if we have a zero number allowing for non-zero exponents. - let first_sig_digit = match first_sig_digit { - Some(p) => p, - None => return Ok(Status::OK.and(Self::ZERO)), - }; - - // Adjust the exponents for any decimal point. - if dot > last_sig_digit { - dec_exp = dec_exp.saturating_add((dot - last_sig_digit - 1) as i32); - } else { - dec_exp = dec_exp.saturating_sub((last_sig_digit - dot) as i32); - } - let significand_digits = last_sig_digit - first_sig_digit + 1 - - (dot > first_sig_digit && dot < last_sig_digit) as usize; - let normalized_exp = dec_exp.saturating_add(significand_digits as i32 - 1); - - // Handle the cases where exponents are obviously too large or too - // small. Writing L for log 10 / log 2, a number d.ddddd*10^dec_exp - // definitely overflows if - // - // (dec_exp - 1) * L >= MAX_EXP - // - // and definitely underflows to zero where - // - // (dec_exp + 1) * L <= MIN_EXP - PRECISION - // - // With integer arithmetic the tightest bounds for L are - // - // 93/28 < L < 196/59 [ numerator <= 256 ] - // 42039/12655 < L < 28738/8651 [ numerator <= 65536 ] - - // Check for MAX_EXP. - if normalized_exp.saturating_sub(1).saturating_mul(42039) >= 12655 * S::MAX_EXP as i32 { - // Overflow and round. - return Ok(Self::overflow_result(round)); - } - - // Check for MIN_EXP. - if normalized_exp.saturating_add(1).saturating_mul(28738) - <= 8651 * (S::MIN_EXP as i32 - S::PRECISION as i32) - { - // Underflow to zero and round. - let r = - if round == Round::TowardPositive { IeeeFloat::SMALLEST } else { IeeeFloat::ZERO }; - return Ok((Status::UNDERFLOW | Status::INEXACT).and(r)); - } - - // A tight upper bound on number of bits required to hold an - // N-digit decimal integer is N * 196 / 59. Allocate enough space - // to hold the full significand, and an extra limb required by - // tcMultiplyPart. - let max_limbs = limbs_for_bits(1 + 196 * significand_digits / 59); - let mut dec_sig: SmallVec<[Limb; 1]> = SmallVec::with_capacity(max_limbs); - - // Convert to binary efficiently - we do almost all multiplication - // in a Limb. When this would overflow do we do a single - // bignum multiplication, and then revert again to multiplication - // in a Limb. - let mut chars = s[first_sig_digit..=last_sig_digit].chars(); - loop { - let mut val = 0; - let mut multiplier = 1; - - loop { - let dec_value = match chars.next() { - Some('.') => continue, - Some(c) => c.to_digit(10).unwrap(), - None => break, - }; - - multiplier *= 10; - val = val * 10 + dec_value as Limb; - - // The maximum number that can be multiplied by ten with any - // digit added without overflowing a Limb. - if multiplier > (!0 - 9) / 10 { - break; - } - } - - // If we've consumed no digits, we're done. - if multiplier == 1 { - break; - } - - // Multiply out the current limb. - let mut carry = val; - for x in &mut dec_sig { - let [low, mut high] = sig::widening_mul(*x, multiplier); - - // Now add carry. - let (low, overflow) = low.overflowing_add(carry); - high += overflow as Limb; - - *x = low; - carry = high; - } - - // If we had carry, we need another limb (likely but not guaranteed). - if carry > 0 { - dec_sig.push(carry); - } - } - - // Calculate pow(5, abs(dec_exp)) into `pow5_full`. - // The *_calc Vec's are reused scratch space, as an optimization. - let (pow5_full, mut pow5_calc, mut sig_calc, mut sig_scratch_calc) = { - let mut power = dec_exp.abs() as usize; - - const FIRST_EIGHT_POWERS: [Limb; 8] = [1, 5, 25, 125, 625, 3125, 15625, 78125]; - - let mut p5_scratch = smallvec![]; - let mut p5: SmallVec<[Limb; 1]> = smallvec![FIRST_EIGHT_POWERS[4]]; - - let mut r_scratch = smallvec![]; - let mut r: SmallVec<[Limb; 1]> = smallvec![FIRST_EIGHT_POWERS[power & 7]]; - power >>= 3; - - while power > 0 { - // Calculate pow(5,pow(2,n+3)). - p5_scratch.resize(p5.len() * 2, 0); - let _: Loss = sig::mul(&mut p5_scratch, &mut 0, &p5, &p5, p5.len() * 2 * LIMB_BITS); - while p5_scratch.last() == Some(&0) { - p5_scratch.pop(); - } - mem::swap(&mut p5, &mut p5_scratch); - - if power & 1 != 0 { - r_scratch.resize(r.len() + p5.len(), 0); - let _: Loss = - sig::mul(&mut r_scratch, &mut 0, &r, &p5, (r.len() + p5.len()) * LIMB_BITS); - while r_scratch.last() == Some(&0) { - r_scratch.pop(); - } - mem::swap(&mut r, &mut r_scratch); - } - - power >>= 1; - } - - (r, r_scratch, p5, p5_scratch) - }; - - // Attempt dec_sig * 10^dec_exp with increasing precision. - let mut attempt = 0; - loop { - let calc_precision = (LIMB_BITS << attempt) - 1; - attempt += 1; - - let calc_normal_from_limbs = |sig: &mut SmallVec<[Limb; 1]>, - limbs: &[Limb]| - -> StatusAnd { - sig.resize(limbs_for_bits(calc_precision), 0); - let (mut loss, mut exp) = sig::from_limbs(sig, limbs, calc_precision); - - // Before rounding normalize the exponent of Category::Normal numbers. - let mut omsb = sig::omsb(sig); - - assert_ne!(omsb, 0); - - // OMSB is numbered from 1. We want to place it in the integer - // bit numbered PRECISION if possible, with a compensating change in - // the exponent. - let final_exp = exp.saturating_add(omsb as ExpInt - calc_precision as ExpInt); - - // Shifting left is easy as we don't lose precision. - if final_exp < exp { - assert_eq!(loss, Loss::ExactlyZero); - - let exp_change = (exp - final_exp) as usize; - sig::shift_left(sig, &mut exp, exp_change); - - return Status::OK.and(exp); - } - - // Shift right and capture any new lost fraction. - if final_exp > exp { - let exp_change = (final_exp - exp) as usize; - loss = sig::shift_right(sig, &mut exp, exp_change).combine(loss); - - // Keep OMSB up-to-date. - omsb = omsb.saturating_sub(exp_change); - } - - assert_eq!(omsb, calc_precision); - - // Now round the number according to round given the lost - // fraction. - - // As specified in IEEE 754, since we do not trap we do not report - // underflow for exact results. - if loss == Loss::ExactlyZero { - return Status::OK.and(exp); - } - - // Increment the significand if we're rounding away from zero. - if loss == Loss::MoreThanHalf || loss == Loss::ExactlyHalf && sig::get_bit(sig, 0) { - // We should never overflow. - assert_eq!(sig::increment(sig), 0); - omsb = sig::omsb(sig); - - // Did the significand increment overflow? - if omsb == calc_precision + 1 { - let _: Loss = sig::shift_right(sig, &mut exp, 1); - - return Status::INEXACT.and(exp); - } - } - - // The normal case - we were and are not denormal, and any - // significand increment above didn't overflow. - Status::INEXACT.and(exp) - }; - - let status; - let mut exp = unpack!(status=, - calc_normal_from_limbs(&mut sig_calc, &dec_sig)); - let pow5_status; - let pow5_exp = unpack!(pow5_status=, - calc_normal_from_limbs(&mut pow5_calc, &pow5_full)); - - // Add dec_exp, as 10^n = 5^n * 2^n. - exp += dec_exp as ExpInt; - - let mut used_bits = S::PRECISION; - let mut truncated_bits = calc_precision - used_bits; - - let half_ulp_err1 = (status != Status::OK) as Limb; - let (calc_loss, half_ulp_err2); - if dec_exp >= 0 { - exp += pow5_exp; - - sig_scratch_calc.resize(sig_calc.len() + pow5_calc.len(), 0); - calc_loss = sig::mul( - &mut sig_scratch_calc, - &mut exp, - &sig_calc, - &pow5_calc, - calc_precision, - ); - mem::swap(&mut sig_calc, &mut sig_scratch_calc); - - half_ulp_err2 = (pow5_status != Status::OK) as Limb; - } else { - exp -= pow5_exp; - - sig_scratch_calc.resize(sig_calc.len(), 0); - calc_loss = sig::div( - &mut sig_scratch_calc, - &mut exp, - &mut sig_calc, - &mut pow5_calc, - calc_precision, - ); - mem::swap(&mut sig_calc, &mut sig_scratch_calc); - - // Denormal numbers have less precision. - if exp < S::MIN_EXP { - truncated_bits += (S::MIN_EXP - exp) as usize; - used_bits = calc_precision.saturating_sub(truncated_bits); - } - // Extra half-ulp lost in reciprocal of exponent. - half_ulp_err2 = - 2 * (pow5_status != Status::OK || calc_loss != Loss::ExactlyZero) as Limb; - } - - // Both sig::mul and sig::div return the - // result with the integer bit set. - assert!(sig::get_bit(&sig_calc, calc_precision - 1)); - - // The error from the true value, in half-ulps, on multiplying two - // floating point numbers, which differ from the value they - // approximate by at most half_ulp_err1 and half_ulp_err2 half-ulps, is strictly less - // than the returned value. - // - // See "How to Read Floating Point Numbers Accurately" by William D Clinger. - assert!(half_ulp_err1 < 2 || half_ulp_err2 < 2 || (half_ulp_err1 + half_ulp_err2 < 8)); - - let inexact = (calc_loss != Loss::ExactlyZero) as Limb; - let half_ulp_err = if half_ulp_err1 + half_ulp_err2 == 0 { - inexact * 2 // <= inexact half-ulps. - } else { - inexact + 2 * (half_ulp_err1 + half_ulp_err2) - }; - - let ulps_from_boundary = { - let bits = calc_precision - used_bits - 1; - - let i = bits / LIMB_BITS; - let limb = sig_calc[i] & (!0 >> (LIMB_BITS - 1 - bits % LIMB_BITS)); - let boundary = match round { - Round::NearestTiesToEven | Round::NearestTiesToAway => 1 << (bits % LIMB_BITS), - _ => 0, - }; - if i == 0 { - let delta = limb.wrapping_sub(boundary); - cmp::min(delta, delta.wrapping_neg()) - } else if limb == boundary { - if !sig::is_all_zeros(&sig_calc[1..i]) { - !0 // A lot. - } else { - sig_calc[0] - } - } else if limb == boundary.wrapping_sub(1) { - if sig_calc[1..i].iter().any(|&x| x.wrapping_neg() != 1) { - !0 // A lot. - } else { - sig_calc[0].wrapping_neg() - } - } else { - !0 // A lot. - } - }; - - // Are we guaranteed to round correctly if we truncate? - if ulps_from_boundary.saturating_mul(2) >= half_ulp_err { - let mut r = IeeeFloat { - sig: [0], - exp, - category: Category::Normal, - sign: false, - marker: PhantomData, - }; - sig::extract(&mut r.sig, &sig_calc, used_bits, calc_precision - used_bits); - // If we extracted less bits above we must adjust our exponent - // to compensate for the implicit right shift. - r.exp += (S::PRECISION - used_bits) as ExpInt; - let loss = Loss::through_truncation(&sig_calc, truncated_bits); - return Ok(r.normalize(round, loss)); - } - } - } -} - -impl Loss { - /// Combine the effect of two lost fractions. - fn combine(self, less_significant: Loss) -> Loss { - let mut more_significant = self; - if less_significant != Loss::ExactlyZero { - if more_significant == Loss::ExactlyZero { - more_significant = Loss::LessThanHalf; - } else if more_significant == Loss::ExactlyHalf { - more_significant = Loss::MoreThanHalf; - } - } - - more_significant - } - - /// Returns the fraction lost were a bignum truncated losing the least - /// significant `bits` bits. - fn through_truncation(limbs: &[Limb], bits: usize) -> Loss { - if bits == 0 { - return Loss::ExactlyZero; - } - - let half_bit = bits - 1; - let half_limb = half_bit / LIMB_BITS; - let (half_limb, rest) = if half_limb < limbs.len() { - (limbs[half_limb], &limbs[..half_limb]) - } else { - (0, limbs) - }; - let half = 1 << (half_bit % LIMB_BITS); - let has_half = half_limb & half != 0; - let has_rest = half_limb & (half - 1) != 0 || !sig::is_all_zeros(rest); - - match (has_half, has_rest) { - (false, false) => Loss::ExactlyZero, - (false, true) => Loss::LessThanHalf, - (true, false) => Loss::ExactlyHalf, - (true, true) => Loss::MoreThanHalf, - } - } -} - -/// Implementation details of IeeeFloat significands, such as big integer arithmetic. -/// As a rule of thumb, no functions in this module should dynamically allocate. -mod sig { - use super::{limbs_for_bits, ExpInt, Limb, Loss, LIMB_BITS}; - use core::cmp::Ordering; - use core::iter; - use core::mem; - - pub(super) fn is_all_zeros(limbs: &[Limb]) -> bool { - limbs.iter().all(|&l| l == 0) - } - - /// One, not zero, based LSB. That is, returns 0 for a zeroed significand. - pub(super) fn olsb(limbs: &[Limb]) -> usize { - limbs - .iter() - .enumerate() - .find(|(_, &limb)| limb != 0) - .map_or(0, |(i, limb)| i * LIMB_BITS + limb.trailing_zeros() as usize + 1) - } - - /// One, not zero, based MSB. That is, returns 0 for a zeroed significand. - pub(super) fn omsb(limbs: &[Limb]) -> usize { - limbs - .iter() - .enumerate() - .rfind(|(_, &limb)| limb != 0) - .map_or(0, |(i, limb)| (i + 1) * LIMB_BITS - limb.leading_zeros() as usize) - } - - /// Comparison (unsigned) of two significands. - pub(super) fn cmp(a: &[Limb], b: &[Limb]) -> Ordering { - assert_eq!(a.len(), b.len()); - for (a, b) in a.iter().zip(b).rev() { - match a.cmp(b) { - Ordering::Equal => {} - o => return o, - } - } - - Ordering::Equal - } - - /// Extracts the given bit. - pub(super) fn get_bit(limbs: &[Limb], bit: usize) -> bool { - limbs[bit / LIMB_BITS] & (1 << (bit % LIMB_BITS)) != 0 - } - - /// Sets the given bit. - pub(super) fn set_bit(limbs: &mut [Limb], bit: usize) { - limbs[bit / LIMB_BITS] |= 1 << (bit % LIMB_BITS); - } - - /// Clear the given bit. - pub(super) fn clear_bit(limbs: &mut [Limb], bit: usize) { - limbs[bit / LIMB_BITS] &= !(1 << (bit % LIMB_BITS)); - } - - /// Shifts `dst` left `bits` bits, subtract `bits` from its exponent. - pub(super) fn shift_left(dst: &mut [Limb], exp: &mut ExpInt, bits: usize) { - if bits > 0 { - // Our exponent should not underflow. - *exp = exp.checked_sub(bits as ExpInt).unwrap(); - - // Jump is the inter-limb jump; shift is the intra-limb shift. - let jump = bits / LIMB_BITS; - let shift = bits % LIMB_BITS; - - for i in (0..dst.len()).rev() { - let mut limb; - - if i < jump { - limb = 0; - } else { - // dst[i] comes from the two limbs src[i - jump] and, if we have - // an intra-limb shift, src[i - jump - 1]. - limb = dst[i - jump]; - if shift > 0 { - limb <<= shift; - if i > jump { - limb |= dst[i - jump - 1] >> (LIMB_BITS - shift); - } - } - } - - dst[i] = limb; - } - } - } - - /// Shifts `dst` right `bits` bits noting lost fraction. - pub(super) fn shift_right(dst: &mut [Limb], exp: &mut ExpInt, bits: usize) -> Loss { - let loss = Loss::through_truncation(dst, bits); - - if bits > 0 { - // Our exponent should not overflow. - *exp = exp.checked_add(bits as ExpInt).unwrap(); - - // Jump is the inter-limb jump; shift is the intra-limb shift. - let jump = bits / LIMB_BITS; - let shift = bits % LIMB_BITS; - - // Perform the shift. This leaves the most significant `bits` bits - // of the result at zero. - for i in 0..dst.len() { - let mut limb; - - if i + jump >= dst.len() { - limb = 0; - } else { - limb = dst[i + jump]; - if shift > 0 { - limb >>= shift; - if i + jump + 1 < dst.len() { - limb |= dst[i + jump + 1] << (LIMB_BITS - shift); - } - } - } - - dst[i] = limb; - } - } - - loss - } - - /// Copies the bit vector of width `src_bits` from `src`, starting at bit SRC_LSB, - /// to `dst`, such that the bit SRC_LSB becomes the least significant bit of `dst`. - /// All high bits above `src_bits` in `dst` are zero-filled. - pub(super) fn extract(dst: &mut [Limb], src: &[Limb], src_bits: usize, src_lsb: usize) { - if src_bits == 0 { - return; - } - - let dst_limbs = limbs_for_bits(src_bits); - assert!(dst_limbs <= dst.len()); - - let src = &src[src_lsb / LIMB_BITS..]; - dst[..dst_limbs].copy_from_slice(&src[..dst_limbs]); - - let shift = src_lsb % LIMB_BITS; - let _: Loss = shift_right(&mut dst[..dst_limbs], &mut 0, shift); - - // We now have (dst_limbs * LIMB_BITS - shift) bits from `src` - // in `dst`. If this is less that src_bits, append the rest, else - // clear the high bits. - let n = dst_limbs * LIMB_BITS - shift; - if n < src_bits { - let mask = (1 << (src_bits - n)) - 1; - dst[dst_limbs - 1] |= (src[dst_limbs] & mask) << (n % LIMB_BITS); - } else if n > src_bits && src_bits % LIMB_BITS > 0 { - dst[dst_limbs - 1] &= (1 << (src_bits % LIMB_BITS)) - 1; - } - - // Clear high limbs. - for x in &mut dst[dst_limbs..] { - *x = 0; - } - } - - /// We want the most significant PRECISION bits of `src`. There may not - /// be that many; extract what we can. - pub(super) fn from_limbs(dst: &mut [Limb], src: &[Limb], precision: usize) -> (Loss, ExpInt) { - let omsb = omsb(src); - - if precision <= omsb { - extract(dst, src, precision, omsb - precision); - (Loss::through_truncation(src, omsb - precision), omsb as ExpInt - 1) - } else { - extract(dst, src, omsb, 0); - (Loss::ExactlyZero, precision as ExpInt - 1) - } - } - - /// For every consecutive chunk of `bits` bits from `limbs`, - /// going from most significant to the least significant bits, - /// call `f` to transform those bits and store the result back. - pub(super) fn each_chunk Limb>(limbs: &mut [Limb], bits: usize, mut f: F) { - assert_eq!(LIMB_BITS % bits, 0); - for limb in limbs.iter_mut().rev() { - let mut r = 0; - for i in (0..LIMB_BITS / bits).rev() { - r |= f((*limb >> (i * bits)) & ((1 << bits) - 1)) << (i * bits); - } - *limb = r; - } - } - - /// Increment in-place, return the carry flag. - pub(super) fn increment(dst: &mut [Limb]) -> Limb { - for x in dst { - *x = x.wrapping_add(1); - if *x != 0 { - return 0; - } - } - - 1 - } - - /// Decrement in-place, return the borrow flag. - pub(super) fn decrement(dst: &mut [Limb]) -> Limb { - for x in dst { - *x = x.wrapping_sub(1); - if *x != !0 { - return 0; - } - } - - 1 - } - - /// `a += b + c` where `c` is zero or one. Returns the carry flag. - pub(super) fn add(a: &mut [Limb], b: &[Limb], mut c: Limb) -> Limb { - assert!(c <= 1); - - for (a, &b) in iter::zip(a, b) { - let (r, overflow) = a.overflowing_add(b); - let (r, overflow2) = r.overflowing_add(c); - *a = r; - c = (overflow | overflow2) as Limb; - } - - c - } - - /// `a -= b + c` where `c` is zero or one. Returns the borrow flag. - pub(super) fn sub(a: &mut [Limb], b: &[Limb], mut c: Limb) -> Limb { - assert!(c <= 1); - - for (a, &b) in iter::zip(a, b) { - let (r, overflow) = a.overflowing_sub(b); - let (r, overflow2) = r.overflowing_sub(c); - *a = r; - c = (overflow | overflow2) as Limb; - } - - c - } - - /// `a += b` or `a -= b`. Does not preserve `b`. - pub(super) fn add_or_sub( - a_sig: &mut [Limb], - a_exp: &mut ExpInt, - a_sign: &mut bool, - b_sig: &mut [Limb], - b_exp: ExpInt, - b_sign: bool, - ) -> Loss { - // Are we bigger exponent-wise than the RHS? - let bits = *a_exp - b_exp; - - // Determine if the operation on the absolute values is effectively - // an addition or subtraction. - // Subtraction is more subtle than one might naively expect. - if *a_sign ^ b_sign { - let (reverse, loss); - - if bits == 0 { - reverse = cmp(a_sig, b_sig) == Ordering::Less; - loss = Loss::ExactlyZero; - } else if bits > 0 { - loss = shift_right(b_sig, &mut 0, (bits - 1) as usize); - shift_left(a_sig, a_exp, 1); - reverse = false; - } else { - loss = shift_right(a_sig, a_exp, (-bits - 1) as usize); - shift_left(b_sig, &mut 0, 1); - reverse = true; - } - - let borrow = (loss != Loss::ExactlyZero) as Limb; - if reverse { - // The code above is intended to ensure that no borrow is necessary. - assert_eq!(sub(b_sig, a_sig, borrow), 0); - a_sig.copy_from_slice(b_sig); - *a_sign = !*a_sign; - } else { - // The code above is intended to ensure that no borrow is necessary. - assert_eq!(sub(a_sig, b_sig, borrow), 0); - } - - // Invert the lost fraction - it was on the RHS and subtracted. - match loss { - Loss::LessThanHalf => Loss::MoreThanHalf, - Loss::MoreThanHalf => Loss::LessThanHalf, - _ => loss, - } - } else { - let loss = if bits > 0 { - shift_right(b_sig, &mut 0, bits as usize) - } else { - shift_right(a_sig, a_exp, -bits as usize) - }; - // We have a guard bit; generating a carry cannot happen. - assert_eq!(add(a_sig, b_sig, 0), 0); - loss - } - } - - /// `[low, high] = a * b`. - /// - /// This cannot overflow, because - /// - /// `(n - 1) * (n - 1) + 2 * (n - 1) == (n - 1) * (n + 1)` - /// - /// which is less than n2. - pub(super) fn widening_mul(a: Limb, b: Limb) -> [Limb; 2] { - let mut wide = [0, 0]; - - if a == 0 || b == 0 { - return wide; - } - - const HALF_BITS: usize = LIMB_BITS / 2; - - let select = |limb, i| (limb >> (i * HALF_BITS)) & ((1 << HALF_BITS) - 1); - for i in 0..2 { - for j in 0..2 { - let mut x = [select(a, i) * select(b, j), 0]; - shift_left(&mut x, &mut 0, (i + j) * HALF_BITS); - assert_eq!(add(&mut wide, &x, 0), 0); - } - } - - wide - } - - /// `dst = a * b` (for normal `a` and `b`). Returns the lost fraction. - pub(super) fn mul<'a>( - dst: &mut [Limb], - exp: &mut ExpInt, - mut a: &'a [Limb], - mut b: &'a [Limb], - precision: usize, - ) -> Loss { - // Put the narrower number on the `a` for less loops below. - if a.len() > b.len() { - mem::swap(&mut a, &mut b); - } - - for x in &mut dst[..b.len()] { - *x = 0; - } - - for i in 0..a.len() { - let mut carry = 0; - for j in 0..b.len() { - let [low, mut high] = widening_mul(a[i], b[j]); - - // Now add carry. - let (low, overflow) = low.overflowing_add(carry); - high += overflow as Limb; - - // And now `dst[i + j]`, and store the new low part there. - let (low, overflow) = low.overflowing_add(dst[i + j]); - high += overflow as Limb; - - dst[i + j] = low; - carry = high; - } - dst[i + b.len()] = carry; - } - - // Assume the operands involved in the multiplication are single-precision - // FP, and the two multiplicants are: - // a = a23 . a22 ... a0 * 2^e1 - // b = b23 . b22 ... b0 * 2^e2 - // the result of multiplication is: - // dst = c48 c47 c46 . c45 ... c0 * 2^(e1+e2) - // Note that there are three significant bits at the left-hand side of the - // radix point: two for the multiplication, and an overflow bit for the - // addition (that will always be zero at this point). Move the radix point - // toward left by two bits, and adjust exponent accordingly. - *exp += 2; - - // Convert the result having "2 * precision" significant-bits back to the one - // having "precision" significant-bits. First, move the radix point from - // poision "2*precision - 1" to "precision - 1". The exponent need to be - // adjusted by "2*precision - 1" - "precision - 1" = "precision". - *exp -= precision as ExpInt + 1; - - // In case MSB resides at the left-hand side of radix point, shift the - // mantissa right by some amount to make sure the MSB reside right before - // the radix point (i.e., "MSB . rest-significant-bits"). - // - // Note that the result is not normalized when "omsb < precision". So, the - // caller needs to call IeeeFloat::normalize() if normalized value is - // expected. - let omsb = omsb(dst); - if omsb <= precision { Loss::ExactlyZero } else { shift_right(dst, exp, omsb - precision) } - } - - /// `quotient = dividend / divisor`. Returns the lost fraction. - /// Does not preserve `dividend` or `divisor`. - pub(super) fn div( - quotient: &mut [Limb], - exp: &mut ExpInt, - dividend: &mut [Limb], - divisor: &mut [Limb], - precision: usize, - ) -> Loss { - // Normalize the divisor. - let bits = precision - omsb(divisor); - shift_left(divisor, &mut 0, bits); - *exp += bits as ExpInt; - - // Normalize the dividend. - let bits = precision - omsb(dividend); - shift_left(dividend, exp, bits); - - // Division by 1. - let olsb_divisor = olsb(divisor); - if olsb_divisor == precision { - quotient.copy_from_slice(dividend); - return Loss::ExactlyZero; - } - - // Ensure the dividend >= divisor initially for the loop below. - // Incidentally, this means that the division loop below is - // guaranteed to set the integer bit to one. - if cmp(dividend, divisor) == Ordering::Less { - shift_left(dividend, exp, 1); - assert_ne!(cmp(dividend, divisor), Ordering::Less) - } - - // Helper for figuring out the lost fraction. - let lost_fraction = |dividend: &[Limb], divisor: &[Limb]| match cmp(dividend, divisor) { - Ordering::Greater => Loss::MoreThanHalf, - Ordering::Equal => Loss::ExactlyHalf, - Ordering::Less => { - if is_all_zeros(dividend) { - Loss::ExactlyZero - } else { - Loss::LessThanHalf - } - } - }; - - // Try to perform a (much faster) short division for small divisors. - let divisor_bits = precision - (olsb_divisor - 1); - macro_rules! try_short_div { - ($W:ty, $H:ty, $half:expr) => { - if divisor_bits * 2 <= $half { - // Extract the small divisor. - let _: Loss = shift_right(divisor, &mut 0, olsb_divisor - 1); - let divisor = divisor[0] as $H as $W; - - // Shift the dividend to produce a quotient with the unit bit set. - let top_limb = *dividend.last().unwrap(); - let mut rem = (top_limb >> (LIMB_BITS - (divisor_bits - 1))) as $H; - shift_left(dividend, &mut 0, divisor_bits - 1); - - // Apply short division in place on $H (of $half bits) chunks. - each_chunk(dividend, $half, |chunk| { - let chunk = chunk as $H; - let combined = ((rem as $W) << $half) | (chunk as $W); - rem = (combined % divisor) as $H; - (combined / divisor) as $H as Limb - }); - quotient.copy_from_slice(dividend); - - return lost_fraction(&[(rem as Limb) << 1], &[divisor as Limb]); - } - }; - } - - try_short_div!(u32, u16, 16); - try_short_div!(u64, u32, 32); - try_short_div!(u128, u64, 64); - - // Zero the quotient before setting bits in it. - for x in &mut quotient[..limbs_for_bits(precision)] { - *x = 0; - } - - // Long division. - for bit in (0..precision).rev() { - if cmp(dividend, divisor) != Ordering::Less { - sub(dividend, divisor, 0); - set_bit(quotient, bit); - } - shift_left(dividend, &mut 0, 1); - } - - lost_fraction(dividend, divisor) - } -} diff --git a/compiler/rustc_apfloat/src/lib.rs b/compiler/rustc_apfloat/src/lib.rs deleted file mode 100644 index dde368e7b924f..0000000000000 --- a/compiler/rustc_apfloat/src/lib.rs +++ /dev/null @@ -1,695 +0,0 @@ -//! Port of LLVM's APFloat software floating-point implementation from the -//! following C++ sources (please update commit hash when backporting): -//! -//! -//! * `include/llvm/ADT/APFloat.h` -> `Float` and `FloatConvert` traits -//! * `lib/Support/APFloat.cpp` -> `ieee` and `ppc` modules -//! * `unittests/ADT/APFloatTest.cpp` -> `tests` directory -//! -//! The port contains no unsafe code, global state, or side-effects in general, -//! and the only allocations are in the conversion to/from decimal strings. -//! -//! Most of the API and the testcases are intact in some form or another, -//! with some ergonomic changes, such as idiomatic short names, returning -//! new values instead of mutating the receiver, and having separate method -//! variants that take a non-default rounding mode (with the suffix `_r`). -//! Comments have been preserved where possible, only slightly adapted. -//! -//! Instead of keeping a pointer to a configuration struct and inspecting it -//! dynamically on every operation, types (e.g., `ieee::Double`), traits -//! (e.g., `ieee::Semantics`) and associated constants are employed for -//! increased type safety and performance. -//! -//! On-heap bigints are replaced everywhere (except in decimal conversion), -//! with short arrays of `type Limb = u128` elements (instead of `u64`), -//! This allows fitting the largest supported significands in one integer -//! (`ieee::Quad` and `ppc::Fallback` use slightly less than 128 bits). -//! All of the functions in the `ieee::sig` module operate on slices. -//! -//! # Note -//! -//! This API is completely unstable and subject to change. - -#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] -#![no_std] -#![forbid(unsafe_code)] -#![deny(rustc::untranslatable_diagnostic)] -#![deny(rustc::diagnostic_outside_of_impl)] - -#[macro_use] -extern crate alloc; - -use core::cmp::Ordering; -use core::fmt; -use core::ops::{Add, Div, Mul, Neg, Rem, Sub}; -use core::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign}; -use core::str::FromStr; - -bitflags::bitflags! { - /// IEEE-754R 7: Default exception handling. - /// - /// UNDERFLOW or OVERFLOW are always returned or-ed with INEXACT. - #[must_use] - pub struct Status: u8 { - const OK = 0x00; - const INVALID_OP = 0x01; - const DIV_BY_ZERO = 0x02; - const OVERFLOW = 0x04; - const UNDERFLOW = 0x08; - const INEXACT = 0x10; - } -} - -#[must_use] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct StatusAnd { - pub status: Status, - pub value: T, -} - -impl Status { - pub fn and(self, value: T) -> StatusAnd { - StatusAnd { status: self, value } - } -} - -impl StatusAnd { - pub fn map U, U>(self, f: F) -> StatusAnd { - StatusAnd { status: self.status, value: f(self.value) } - } -} - -#[macro_export] -macro_rules! unpack { - ($status:ident|=, $e:expr) => { - match $e { - $crate::StatusAnd { status, value } => { - $status |= status; - value - } - } - }; - ($status:ident=, $e:expr) => { - match $e { - $crate::StatusAnd { status, value } => { - $status = status; - value - } - } - }; -} - -/// Category of internally-represented number. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Category { - Infinity, - NaN, - Normal, - Zero, -} - -/// IEEE-754R 4.3: Rounding-direction attributes. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Round { - NearestTiesToEven, - TowardPositive, - TowardNegative, - TowardZero, - NearestTiesToAway, -} - -impl Neg for Round { - type Output = Round; - fn neg(self) -> Round { - match self { - Round::TowardPositive => Round::TowardNegative, - Round::TowardNegative => Round::TowardPositive, - Round::NearestTiesToEven | Round::TowardZero | Round::NearestTiesToAway => self, - } - } -} - -/// A signed type to represent a floating point number's unbiased exponent. -pub type ExpInt = i16; - -// \c ilogb error results. -pub const IEK_INF: ExpInt = ExpInt::MAX; -pub const IEK_NAN: ExpInt = ExpInt::MIN; -pub const IEK_ZERO: ExpInt = ExpInt::MIN + 1; - -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct ParseError(pub &'static str); - -/// A self-contained host- and target-independent arbitrary-precision -/// floating-point software implementation. -/// -/// `apfloat` uses significand bignum integer arithmetic as provided by functions -/// in the `ieee::sig`. -/// -/// Written for clarity rather than speed, in particular with a view to use in -/// the front-end of a cross compiler so that target arithmetic can be correctly -/// performed on the host. Performance should nonetheless be reasonable, -/// particularly for its intended use. It may be useful as a base -/// implementation for a run-time library during development of a faster -/// target-specific one. -/// -/// All 5 rounding modes in the IEEE-754R draft are handled correctly for all -/// implemented operations. Currently implemented operations are add, subtract, -/// multiply, divide, fused-multiply-add, conversion-to-float, -/// conversion-to-integer and conversion-from-integer. New rounding modes -/// (e.g., away from zero) can be added with three or four lines of code. -/// -/// Four formats are built-in: IEEE single precision, double precision, -/// quadruple precision, and x87 80-bit extended double (when operating with -/// full extended precision). Adding a new format that obeys IEEE semantics -/// only requires adding two lines of code: a declaration and definition of the -/// format. -/// -/// All operations return the status of that operation as an exception bit-mask, -/// so multiple operations can be done consecutively with their results or-ed -/// together. The returned status can be useful for compiler diagnostics; e.g., -/// inexact, underflow and overflow can be easily diagnosed on constant folding, -/// and compiler optimizers can determine what exceptions would be raised by -/// folding operations and optimize, or perhaps not optimize, accordingly. -/// -/// At present, underflow tininess is detected after rounding; it should be -/// straight forward to add support for the before-rounding case too. -/// -/// The library reads hexadecimal floating point numbers as per C99, and -/// correctly rounds if necessary according to the specified rounding mode. -/// Syntax is required to have been validated by the caller. -/// -/// It also reads decimal floating point numbers and correctly rounds according -/// to the specified rounding mode. -/// -/// Non-zero finite numbers are represented internally as a sign bit, a 16-bit -/// signed exponent, and the significand as an array of integer limbs. After -/// normalization of a number of precision P the exponent is within the range of -/// the format, and if the number is not denormal the P-th bit of the -/// significand is set as an explicit integer bit. For denormals the most -/// significant bit is shifted right so that the exponent is maintained at the -/// format's minimum, so that the smallest denormal has just the least -/// significant bit of the significand set. The sign of zeros and infinities -/// is significant; the exponent and significand of such numbers is not stored, -/// but has a known implicit (deterministic) value: 0 for the significands, 0 -/// for zero exponent, all 1 bits for infinity exponent. For NaNs the sign and -/// significand are deterministic, although not really meaningful, and preserved -/// in non-conversion operations. The exponent is implicitly all 1 bits. -/// -/// `apfloat` does not provide any exception handling beyond default exception -/// handling. We represent Signaling NaNs via IEEE-754R 2008 6.2.1 should clause -/// by encoding Signaling NaNs with the first bit of its trailing significand -/// as 0. -/// -/// Future work -/// =========== -/// -/// Some features that may or may not be worth adding: -/// -/// Optional ability to detect underflow tininess before rounding. -/// -/// New formats: x87 in single and double precision mode (IEEE apart from -/// extended exponent range) (hard). -/// -/// New operations: sqrt, nexttoward. -/// -pub trait Float: - Copy - + Default - + FromStr - + PartialOrd - + fmt::Display - + Neg - + AddAssign - + SubAssign - + MulAssign - + DivAssign - + RemAssign - + Add> - + Sub> - + Mul> - + Div> - + Rem> -{ - /// Total number of bits in the in-memory format. - const BITS: usize; - - /// Number of bits in the significand. This includes the integer bit. - const PRECISION: usize; - - /// The largest E such that 2E is representable; this matches the - /// definition of IEEE 754. - const MAX_EXP: ExpInt; - - /// The smallest E such that 2E is a normalized number; this - /// matches the definition of IEEE 754. - const MIN_EXP: ExpInt; - - /// Positive Zero. - const ZERO: Self; - - /// Positive Infinity. - const INFINITY: Self; - - /// NaN (Not a Number). - // FIXME(eddyb) provide a default when qnan becomes const fn. - const NAN: Self; - - /// Factory for QNaN values. - // FIXME(eddyb) should be const fn. - fn qnan(payload: Option) -> Self; - - /// Factory for SNaN values. - // FIXME(eddyb) should be const fn. - fn snan(payload: Option) -> Self; - - /// Largest finite number. - // FIXME(eddyb) should be const (but FloatPair::largest is nontrivial). - fn largest() -> Self; - - /// Smallest (by magnitude) finite number. - /// Might be denormalized, which implies a relative loss of precision. - const SMALLEST: Self; - - /// Smallest (by magnitude) normalized finite number. - // FIXME(eddyb) should be const (but FloatPair::smallest_normalized is nontrivial). - fn smallest_normalized() -> Self; - - // Arithmetic - - fn add_r(self, rhs: Self, round: Round) -> StatusAnd; - fn sub_r(self, rhs: Self, round: Round) -> StatusAnd { - self.add_r(-rhs, round) - } - fn mul_r(self, rhs: Self, round: Round) -> StatusAnd; - fn mul_add_r(self, multiplicand: Self, addend: Self, round: Round) -> StatusAnd; - fn mul_add(self, multiplicand: Self, addend: Self) -> StatusAnd { - self.mul_add_r(multiplicand, addend, Round::NearestTiesToEven) - } - fn div_r(self, rhs: Self, round: Round) -> StatusAnd; - /// IEEE remainder. - // This is not currently correct in all cases. - fn ieee_rem(self, rhs: Self) -> StatusAnd { - let mut v = self; - - let status; - v = unpack!(status=, v / rhs); - if status == Status::DIV_BY_ZERO { - return status.and(self); - } - - assert!(Self::PRECISION < 128); - - let status; - let x = unpack!(status=, v.to_i128_r(128, Round::NearestTiesToEven, &mut false)); - if status == Status::INVALID_OP { - return status.and(self); - } - - let status; - let mut v = unpack!(status=, Self::from_i128(x)); - assert_eq!(status, Status::OK); // should always work - - let status; - v = unpack!(status=, v * rhs); - assert_eq!(status - Status::INEXACT, Status::OK); // should not overflow or underflow - - let status; - v = unpack!(status=, self - v); - assert_eq!(status - Status::INEXACT, Status::OK); // likewise - - if v.is_zero() { - status.and(v.copy_sign(self)) // IEEE754 requires this - } else { - status.and(v) - } - } - /// C fmod, or llvm frem. - fn c_fmod(self, rhs: Self) -> StatusAnd; - fn round_to_integral(self, round: Round) -> StatusAnd; - - /// IEEE-754R 2008 5.3.1: nextUp. - fn next_up(self) -> StatusAnd; - - /// IEEE-754R 2008 5.3.1: nextDown. - /// - /// *NOTE* since nextDown(x) = -nextUp(-x), we only implement nextUp with - /// appropriate sign switching before/after the computation. - fn next_down(self) -> StatusAnd { - (-self).next_up().map(|r| -r) - } - - fn abs(self) -> Self { - if self.is_negative() { -self } else { self } - } - fn copy_sign(self, rhs: Self) -> Self { - if self.is_negative() != rhs.is_negative() { -self } else { self } - } - - // Conversions - fn from_bits(input: u128) -> Self; - fn from_i128_r(input: i128, round: Round) -> StatusAnd { - if input < 0 { - Self::from_u128_r(input.wrapping_neg() as u128, -round).map(|r| -r) - } else { - Self::from_u128_r(input as u128, round) - } - } - fn from_i128(input: i128) -> StatusAnd { - Self::from_i128_r(input, Round::NearestTiesToEven) - } - fn from_u128_r(input: u128, round: Round) -> StatusAnd; - fn from_u128(input: u128) -> StatusAnd { - Self::from_u128_r(input, Round::NearestTiesToEven) - } - fn from_str_r(s: &str, round: Round) -> Result, ParseError>; - fn to_bits(self) -> u128; - - /// Converts a floating point number to an integer according to the - /// rounding mode. In case of an invalid operation exception, - /// deterministic values are returned, namely zero for NaNs and the - /// minimal or maximal value respectively for underflow or overflow. - /// If the rounded value is in range but the floating point number is - /// not the exact integer, the C standard doesn't require an inexact - /// exception to be raised. IEEE-854 does require it so we do that. - /// - /// Note that for conversions to integer type the C standard requires - /// round-to-zero to always be used. - /// - /// The *is_exact output tells whether the result is exact, in the sense - /// that converting it back to the original floating point type produces - /// the original value. This is almost equivalent to `result == Status::OK`, - /// except for negative zeroes. - fn to_i128_r(self, width: usize, round: Round, is_exact: &mut bool) -> StatusAnd { - let status; - if self.is_negative() { - if self.is_zero() { - // Negative zero can't be represented as an int. - *is_exact = false; - } - let r = unpack!(status=, (-self).to_u128_r(width, -round, is_exact)); - - // Check for values that don't fit in the signed integer. - if r > (1 << (width - 1)) { - // Return the most negative integer for the given width. - *is_exact = false; - Status::INVALID_OP.and(-1 << (width - 1)) - } else { - status.and(r.wrapping_neg() as i128) - } - } else { - // Positive case is simpler, can pretend it's a smaller unsigned - // integer, and `to_u128` will take care of all the edge cases. - self.to_u128_r(width - 1, round, is_exact).map(|r| r as i128) - } - } - fn to_i128(self, width: usize) -> StatusAnd { - self.to_i128_r(width, Round::TowardZero, &mut true) - } - fn to_u128_r(self, width: usize, round: Round, is_exact: &mut bool) -> StatusAnd; - fn to_u128(self, width: usize) -> StatusAnd { - self.to_u128_r(width, Round::TowardZero, &mut true) - } - - fn cmp_abs_normal(self, rhs: Self) -> Ordering; - - /// Bitwise comparison for equality (QNaNs compare equal, 0!=-0). - fn bitwise_eq(self, rhs: Self) -> bool; - - // IEEE-754R 5.7.2 General operations. - - /// Implements IEEE minNum semantics. Returns the smaller of the 2 arguments if - /// both are not NaN. If either argument is a NaN, returns the other argument. - fn min(self, other: Self) -> Self { - if self.is_nan() { - other - } else if other.is_nan() { - self - } else if other.partial_cmp(&self) == Some(Ordering::Less) { - other - } else { - self - } - } - - /// Implements IEEE maxNum semantics. Returns the larger of the 2 arguments if - /// both are not NaN. If either argument is a NaN, returns the other argument. - fn max(self, other: Self) -> Self { - if self.is_nan() { - other - } else if other.is_nan() { - self - } else if self.partial_cmp(&other) == Some(Ordering::Less) { - other - } else { - self - } - } - - /// IEEE-754R isSignMinus: Returns whether the current value is - /// negative. - /// - /// This applies to zeros and NaNs as well. - fn is_negative(self) -> bool; - - /// IEEE-754R isNormal: Returns whether the current value is normal. - /// - /// This implies that the current value of the float is not zero, subnormal, - /// infinite, or NaN following the definition of normality from IEEE-754R. - fn is_normal(self) -> bool { - !self.is_denormal() && self.is_finite_non_zero() - } - - /// Returns `true` if the current value is zero, subnormal, or - /// normal. - /// - /// This means that the value is not infinite or NaN. - fn is_finite(self) -> bool { - !self.is_nan() && !self.is_infinite() - } - - /// Returns `true` if the float is plus or minus zero. - fn is_zero(self) -> bool { - self.category() == Category::Zero - } - - /// IEEE-754R isSubnormal(): Returns whether the float is a - /// denormal. - fn is_denormal(self) -> bool; - - /// IEEE-754R isInfinite(): Returns whether the float is infinity. - fn is_infinite(self) -> bool { - self.category() == Category::Infinity - } - - /// Returns `true` if the float is a quiet or signaling NaN. - fn is_nan(self) -> bool { - self.category() == Category::NaN - } - - /// Returns `true` if the float is a signaling NaN. - fn is_signaling(self) -> bool; - - // Simple Queries - - fn category(self) -> Category; - fn is_non_zero(self) -> bool { - !self.is_zero() - } - fn is_finite_non_zero(self) -> bool { - self.is_finite() && !self.is_zero() - } - fn is_pos_zero(self) -> bool { - self.is_zero() && !self.is_negative() - } - fn is_neg_zero(self) -> bool { - self.is_zero() && self.is_negative() - } - - /// Returns `true` if the number has the smallest possible non-zero - /// magnitude in the current semantics. - fn is_smallest(self) -> bool { - Self::SMALLEST.copy_sign(self).bitwise_eq(self) - } - - /// Returns `true` if the number has the largest possible finite - /// magnitude in the current semantics. - fn is_largest(self) -> bool { - Self::largest().copy_sign(self).bitwise_eq(self) - } - - /// Returns `true` if the number is an exact integer. - fn is_integer(self) -> bool { - // This could be made more efficient; I'm going for obviously correct. - if !self.is_finite() { - return false; - } - self.round_to_integral(Round::TowardZero).value.bitwise_eq(self) - } - - /// If this value has an exact multiplicative inverse, return it. - fn get_exact_inverse(self) -> Option; - - /// Returns the exponent of the internal representation of the Float. - /// - /// Because the radix of Float is 2, this is equivalent to floor(log2(x)). - /// For special Float values, this returns special error codes: - /// - /// NaN -> \c IEK_NAN - /// 0 -> \c IEK_ZERO - /// Inf -> \c IEK_INF - /// - fn ilogb(self) -> ExpInt; - - /// Returns: self * 2exp for integral exponents. - /// Equivalent to C standard library function `ldexp`. - fn scalbn_r(self, exp: ExpInt, round: Round) -> Self; - fn scalbn(self, exp: ExpInt) -> Self { - self.scalbn_r(exp, Round::NearestTiesToEven) - } - - /// Equivalent to C standard library function with the same name. - /// - /// While the C standard says exp is an unspecified value for infinity and nan, - /// this returns INT_MAX for infinities, and INT_MIN for NaNs (see `ilogb`). - fn frexp_r(self, exp: &mut ExpInt, round: Round) -> Self; - fn frexp(self, exp: &mut ExpInt) -> Self { - self.frexp_r(exp, Round::NearestTiesToEven) - } -} - -pub trait FloatConvert: Float { - /// Converts a value of one floating point type to another. - /// The return value corresponds to the IEEE754 exceptions. *loses_info - /// records whether the transformation lost information, i.e., whether - /// converting the result back to the original type will produce the - /// original value (this is almost the same as return `value == Status::OK`, - /// but there are edge cases where this is not so). - fn convert_r(self, round: Round, loses_info: &mut bool) -> StatusAnd; - fn convert(self, loses_info: &mut bool) -> StatusAnd { - self.convert_r(Round::NearestTiesToEven, loses_info) - } -} - -macro_rules! float_common_impls { - ($ty:ident<$t:tt>) => { - impl<$t> Default for $ty<$t> - where - Self: Float, - { - fn default() -> Self { - Self::ZERO - } - } - - impl<$t> ::core::str::FromStr for $ty<$t> - where - Self: Float, - { - type Err = ParseError; - fn from_str(s: &str) -> Result { - Self::from_str_r(s, Round::NearestTiesToEven).map(|x| x.value) - } - } - - // Rounding ties to the nearest even, by default. - - impl<$t> ::core::ops::Add for $ty<$t> - where - Self: Float, - { - type Output = StatusAnd; - fn add(self, rhs: Self) -> StatusAnd { - self.add_r(rhs, Round::NearestTiesToEven) - } - } - - impl<$t> ::core::ops::Sub for $ty<$t> - where - Self: Float, - { - type Output = StatusAnd; - fn sub(self, rhs: Self) -> StatusAnd { - self.sub_r(rhs, Round::NearestTiesToEven) - } - } - - impl<$t> ::core::ops::Mul for $ty<$t> - where - Self: Float, - { - type Output = StatusAnd; - fn mul(self, rhs: Self) -> StatusAnd { - self.mul_r(rhs, Round::NearestTiesToEven) - } - } - - impl<$t> ::core::ops::Div for $ty<$t> - where - Self: Float, - { - type Output = StatusAnd; - fn div(self, rhs: Self) -> StatusAnd { - self.div_r(rhs, Round::NearestTiesToEven) - } - } - - impl<$t> ::core::ops::Rem for $ty<$t> - where - Self: Float, - { - type Output = StatusAnd; - fn rem(self, rhs: Self) -> StatusAnd { - self.c_fmod(rhs) - } - } - - impl<$t> ::core::ops::AddAssign for $ty<$t> - where - Self: Float, - { - fn add_assign(&mut self, rhs: Self) { - *self = (*self + rhs).value; - } - } - - impl<$t> ::core::ops::SubAssign for $ty<$t> - where - Self: Float, - { - fn sub_assign(&mut self, rhs: Self) { - *self = (*self - rhs).value; - } - } - - impl<$t> ::core::ops::MulAssign for $ty<$t> - where - Self: Float, - { - fn mul_assign(&mut self, rhs: Self) { - *self = (*self * rhs).value; - } - } - - impl<$t> ::core::ops::DivAssign for $ty<$t> - where - Self: Float, - { - fn div_assign(&mut self, rhs: Self) { - *self = (*self / rhs).value; - } - } - - impl<$t> ::core::ops::RemAssign for $ty<$t> - where - Self: Float, - { - fn rem_assign(&mut self, rhs: Self) { - *self = (*self % rhs).value; - } - } - }; -} - -pub mod ieee; -pub mod ppc; diff --git a/compiler/rustc_apfloat/src/ppc.rs b/compiler/rustc_apfloat/src/ppc.rs deleted file mode 100644 index 65a0f66645bef..0000000000000 --- a/compiler/rustc_apfloat/src/ppc.rs +++ /dev/null @@ -1,434 +0,0 @@ -use crate::ieee; -use crate::{Category, ExpInt, Float, FloatConvert, ParseError, Round, Status, StatusAnd}; - -use core::cmp::Ordering; -use core::fmt; -use core::ops::Neg; - -#[must_use] -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -pub struct DoubleFloat(F, F); -pub type DoubleDouble = DoubleFloat; - -// These are legacy semantics for the Fallback, inaccurate implementation of -// IBM double-double, if the accurate DoubleDouble doesn't handle the -// operation. It's equivalent to having an IEEE number with consecutive 106 -// bits of mantissa and 11 bits of exponent. -// -// It's not equivalent to IBM double-double. For example, a legit IBM -// double-double, 1 + epsilon: -// -// 1 + epsilon = 1 + (1 >> 1076) -// -// is not representable by a consecutive 106 bits of mantissa. -// -// Currently, these semantics are used in the following way: -// -// DoubleDouble -> (Double, Double) -> -// DoubleDouble's Fallback -> IEEE operations -// -// FIXME: Implement all operations in DoubleDouble, and delete these -// semantics. -// FIXME(eddyb) This shouldn't need to be `pub`, it's only used in bounds. -pub struct FallbackS(#[allow(unused)] F); -type Fallback = ieee::IeeeFloat>; -impl ieee::Semantics for FallbackS { - // Forbid any conversion to/from bits. - const BITS: usize = 0; - const PRECISION: usize = F::PRECISION * 2; - const MAX_EXP: ExpInt = F::MAX_EXP as ExpInt; - const MIN_EXP: ExpInt = F::MIN_EXP as ExpInt + F::PRECISION as ExpInt; -} - -// Convert number to F. To avoid spurious underflows, we re- -// normalize against the F exponent range first, and only *then* -// truncate the mantissa. The result of that second conversion -// may be inexact, but should never underflow. -// FIXME(eddyb) This shouldn't need to be `pub`, it's only used in bounds. -pub struct FallbackExtendedS(#[allow(unused)] F); -type FallbackExtended = ieee::IeeeFloat>; -impl ieee::Semantics for FallbackExtendedS { - // Forbid any conversion to/from bits. - const BITS: usize = 0; - const PRECISION: usize = Fallback::::PRECISION; - const MAX_EXP: ExpInt = F::MAX_EXP as ExpInt; -} - -impl From> for DoubleFloat -where - F: FloatConvert>, - FallbackExtended: FloatConvert, -{ - fn from(x: Fallback) -> Self { - let mut status; - let mut loses_info = false; - - let extended: FallbackExtended = unpack!(status=, x.convert(&mut loses_info)); - assert_eq!((status, loses_info), (Status::OK, false)); - - let a = unpack!(status=, extended.convert(&mut loses_info)); - assert_eq!(status - Status::INEXACT, Status::OK); - - // If conversion was exact or resulted in a special case, we're done; - // just set the second double to zero. Otherwise, re-convert back to - // the extended format and compute the difference. This now should - // convert exactly to double. - let b = if a.is_finite_non_zero() && loses_info { - let u: FallbackExtended = unpack!(status=, a.convert(&mut loses_info)); - assert_eq!((status, loses_info), (Status::OK, false)); - let v = unpack!(status=, extended - u); - assert_eq!(status, Status::OK); - let v = unpack!(status=, v.convert(&mut loses_info)); - assert_eq!((status, loses_info), (Status::OK, false)); - v - } else { - F::ZERO - }; - - DoubleFloat(a, b) - } -} - -impl> From> for Fallback { - fn from(DoubleFloat(a, b): DoubleFloat) -> Self { - let mut status; - let mut loses_info = false; - - // Get the first F and convert to our format. - let a = unpack!(status=, a.convert(&mut loses_info)); - assert_eq!((status, loses_info), (Status::OK, false)); - - // Unless we have a special case, add in second F. - if a.is_finite_non_zero() { - let b = unpack!(status=, b.convert(&mut loses_info)); - assert_eq!((status, loses_info), (Status::OK, false)); - - (a + b).value - } else { - a - } - } -} - -float_common_impls!(DoubleFloat); - -impl Neg for DoubleFloat { - type Output = Self; - fn neg(self) -> Self { - if self.1.is_finite_non_zero() { - DoubleFloat(-self.0, -self.1) - } else { - DoubleFloat(-self.0, self.1) - } - } -} - -impl>> fmt::Display for DoubleFloat { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&Fallback::from(*self), f) - } -} - -impl>> Float for DoubleFloat -where - Self: From>, -{ - const BITS: usize = F::BITS * 2; - const PRECISION: usize = Fallback::::PRECISION; - const MAX_EXP: ExpInt = Fallback::::MAX_EXP; - const MIN_EXP: ExpInt = Fallback::::MIN_EXP; - - const ZERO: Self = DoubleFloat(F::ZERO, F::ZERO); - - const INFINITY: Self = DoubleFloat(F::INFINITY, F::ZERO); - - // FIXME(eddyb) remove when qnan becomes const fn. - const NAN: Self = DoubleFloat(F::NAN, F::ZERO); - - fn qnan(payload: Option) -> Self { - DoubleFloat(F::qnan(payload), F::ZERO) - } - - fn snan(payload: Option) -> Self { - DoubleFloat(F::snan(payload), F::ZERO) - } - - fn largest() -> Self { - let status; - let mut r = DoubleFloat(F::largest(), F::largest()); - r.1 = r.1.scalbn(-(F::PRECISION as ExpInt + 1)); - r.1 = unpack!(status=, r.1.next_down()); - assert_eq!(status, Status::OK); - r - } - - const SMALLEST: Self = DoubleFloat(F::SMALLEST, F::ZERO); - - fn smallest_normalized() -> Self { - DoubleFloat(F::smallest_normalized().scalbn(F::PRECISION as ExpInt), F::ZERO) - } - - // Implement addition, subtraction, multiplication and division based on: - // "Software for Doubled-Precision Floating-Point Computations", - // by Seppo Linnainmaa, ACM TOMS vol 7 no 3, September 1981, pages 272-283. - - fn add_r(mut self, rhs: Self, round: Round) -> StatusAnd { - match (self.category(), rhs.category()) { - (Category::Infinity, Category::Infinity) => { - if self.is_negative() != rhs.is_negative() { - Status::INVALID_OP.and(Self::NAN.copy_sign(self)) - } else { - Status::OK.and(self) - } - } - - (_, Category::Zero) | (Category::NaN, _) | (Category::Infinity, Category::Normal) => { - Status::OK.and(self) - } - - (Category::Zero, _) | (_, Category::NaN | Category::Infinity) => Status::OK.and(rhs), - - (Category::Normal, Category::Normal) => { - let mut status = Status::OK; - let (a, aa, c, cc) = (self.0, self.1, rhs.0, rhs.1); - let mut z = a; - z = unpack!(status|=, z.add_r(c, round)); - if !z.is_finite() { - if !z.is_infinite() { - return status.and(DoubleFloat(z, F::ZERO)); - } - status = Status::OK; - let a_cmp_c = a.cmp_abs_normal(c); - z = cc; - z = unpack!(status|=, z.add_r(aa, round)); - if a_cmp_c == Ordering::Greater { - // z = cc + aa + c + a; - z = unpack!(status|=, z.add_r(c, round)); - z = unpack!(status|=, z.add_r(a, round)); - } else { - // z = cc + aa + a + c; - z = unpack!(status|=, z.add_r(a, round)); - z = unpack!(status|=, z.add_r(c, round)); - } - if !z.is_finite() { - return status.and(DoubleFloat(z, F::ZERO)); - } - self.0 = z; - let mut zz = aa; - zz = unpack!(status|=, zz.add_r(cc, round)); - if a_cmp_c == Ordering::Greater { - // self.1 = a - z + c + zz; - self.1 = a; - self.1 = unpack!(status|=, self.1.sub_r(z, round)); - self.1 = unpack!(status|=, self.1.add_r(c, round)); - self.1 = unpack!(status|=, self.1.add_r(zz, round)); - } else { - // self.1 = c - z + a + zz; - self.1 = c; - self.1 = unpack!(status|=, self.1.sub_r(z, round)); - self.1 = unpack!(status|=, self.1.add_r(a, round)); - self.1 = unpack!(status|=, self.1.add_r(zz, round)); - } - } else { - // q = a - z; - let mut q = a; - q = unpack!(status|=, q.sub_r(z, round)); - - // zz = q + c + (a - (q + z)) + aa + cc; - // Compute a - (q + z) as -((q + z) - a) to avoid temporary copies. - let mut zz = q; - zz = unpack!(status|=, zz.add_r(c, round)); - q = unpack!(status|=, q.add_r(z, round)); - q = unpack!(status|=, q.sub_r(a, round)); - q = -q; - zz = unpack!(status|=, zz.add_r(q, round)); - zz = unpack!(status|=, zz.add_r(aa, round)); - zz = unpack!(status|=, zz.add_r(cc, round)); - if zz.is_zero() && !zz.is_negative() { - return Status::OK.and(DoubleFloat(z, F::ZERO)); - } - self.0 = z; - self.0 = unpack!(status|=, self.0.add_r(zz, round)); - if !self.0.is_finite() { - self.1 = F::ZERO; - return status.and(self); - } - self.1 = z; - self.1 = unpack!(status|=, self.1.sub_r(self.0, round)); - self.1 = unpack!(status|=, self.1.add_r(zz, round)); - } - status.and(self) - } - } - } - - fn mul_r(mut self, rhs: Self, round: Round) -> StatusAnd { - // Interesting observation: For special categories, finding the lowest - // common ancestor of the following layered graph gives the correct - // return category: - // - // NaN - // / \ - // Zero Inf - // \ / - // Normal - // - // e.g., NaN * NaN = NaN - // Zero * Inf = NaN - // Normal * Zero = Zero - // Normal * Inf = Inf - match (self.category(), rhs.category()) { - (Category::NaN, _) => Status::OK.and(self), - - (_, Category::NaN) => Status::OK.and(rhs), - - (Category::Zero, Category::Infinity) | (Category::Infinity, Category::Zero) => { - Status::OK.and(Self::NAN) - } - - (Category::Zero | Category::Infinity, _) => Status::OK.and(self), - - (_, Category::Zero | Category::Infinity) => Status::OK.and(rhs), - - (Category::Normal, Category::Normal) => { - let mut status = Status::OK; - let (a, b, c, d) = (self.0, self.1, rhs.0, rhs.1); - // t = a * c - let mut t = a; - t = unpack!(status|=, t.mul_r(c, round)); - if !t.is_finite_non_zero() { - return status.and(DoubleFloat(t, F::ZERO)); - } - - // tau = fmsub(a, c, t), that is -fmadd(-a, c, t). - let mut tau = a; - tau = unpack!(status|=, tau.mul_add_r(c, -t, round)); - // v = a * d - let mut v = a; - v = unpack!(status|=, v.mul_r(d, round)); - // w = b * c - let mut w = b; - w = unpack!(status|=, w.mul_r(c, round)); - v = unpack!(status|=, v.add_r(w, round)); - // tau += v + w - tau = unpack!(status|=, tau.add_r(v, round)); - // u = t + tau - let mut u = t; - u = unpack!(status|=, u.add_r(tau, round)); - - self.0 = u; - if !u.is_finite() { - self.1 = F::ZERO; - } else { - // self.1 = (t - u) + tau - t = unpack!(status|=, t.sub_r(u, round)); - t = unpack!(status|=, t.add_r(tau, round)); - self.1 = t; - } - status.and(self) - } - } - } - - fn mul_add_r(self, multiplicand: Self, addend: Self, round: Round) -> StatusAnd { - Fallback::from(self) - .mul_add_r(Fallback::from(multiplicand), Fallback::from(addend), round) - .map(Self::from) - } - - fn div_r(self, rhs: Self, round: Round) -> StatusAnd { - Fallback::from(self).div_r(Fallback::from(rhs), round).map(Self::from) - } - - fn c_fmod(self, rhs: Self) -> StatusAnd { - Fallback::from(self).c_fmod(Fallback::from(rhs)).map(Self::from) - } - - fn round_to_integral(self, round: Round) -> StatusAnd { - Fallback::from(self).round_to_integral(round).map(Self::from) - } - - fn next_up(self) -> StatusAnd { - Fallback::from(self).next_up().map(Self::from) - } - - fn from_bits(input: u128) -> Self { - let (a, b) = (input, input >> F::BITS); - DoubleFloat(F::from_bits(a & ((1 << F::BITS) - 1)), F::from_bits(b & ((1 << F::BITS) - 1))) - } - - fn from_u128_r(input: u128, round: Round) -> StatusAnd { - Fallback::from_u128_r(input, round).map(Self::from) - } - - fn from_str_r(s: &str, round: Round) -> Result, ParseError> { - Fallback::from_str_r(s, round).map(|r| r.map(Self::from)) - } - - fn to_bits(self) -> u128 { - self.0.to_bits() | (self.1.to_bits() << F::BITS) - } - - fn to_u128_r(self, width: usize, round: Round, is_exact: &mut bool) -> StatusAnd { - Fallback::from(self).to_u128_r(width, round, is_exact) - } - - fn cmp_abs_normal(self, rhs: Self) -> Ordering { - self.0.cmp_abs_normal(rhs.0).then_with(|| { - let result = self.1.cmp_abs_normal(rhs.1); - if result != Ordering::Equal { - let against = self.0.is_negative() ^ self.1.is_negative(); - let rhs_against = rhs.0.is_negative() ^ rhs.1.is_negative(); - (!against) - .cmp(&!rhs_against) - .then_with(|| if against { result.reverse() } else { result }) - } else { - result - } - }) - } - - fn bitwise_eq(self, rhs: Self) -> bool { - self.0.bitwise_eq(rhs.0) && self.1.bitwise_eq(rhs.1) - } - - fn is_negative(self) -> bool { - self.0.is_negative() - } - - fn is_denormal(self) -> bool { - self.category() == Category::Normal - && (self.0.is_denormal() || self.0.is_denormal() || - // (double)(Hi + Lo) == Hi defines a normal number. - !(self.0 + self.1).value.bitwise_eq(self.0)) - } - - fn is_signaling(self) -> bool { - self.0.is_signaling() - } - - fn category(self) -> Category { - self.0.category() - } - - fn get_exact_inverse(self) -> Option { - Fallback::from(self).get_exact_inverse().map(Self::from) - } - - fn ilogb(self) -> ExpInt { - self.0.ilogb() - } - - fn scalbn_r(self, exp: ExpInt, round: Round) -> Self { - DoubleFloat(self.0.scalbn_r(exp, round), self.1.scalbn_r(exp, round)) - } - - fn frexp_r(self, exp: &mut ExpInt, round: Round) -> Self { - let a = self.0.frexp_r(exp, round); - let mut b = self.1; - if self.category() == Category::Normal { - b = b.scalbn_r(-*exp, round); - } - DoubleFloat(a, b) - } -} diff --git a/compiler/rustc_apfloat/tests/ieee.rs b/compiler/rustc_apfloat/tests/ieee.rs deleted file mode 100644 index f8fac0c2358c9..0000000000000 --- a/compiler/rustc_apfloat/tests/ieee.rs +++ /dev/null @@ -1,3301 +0,0 @@ -// ignore-tidy-filelength - -use rustc_apfloat::ieee::{Double, Half, Quad, Single, X87DoubleExtended}; -use rustc_apfloat::unpack; -use rustc_apfloat::{Category, ExpInt, IEK_INF, IEK_NAN, IEK_ZERO}; -use rustc_apfloat::{Float, FloatConvert, ParseError, Round, Status}; - -trait SingleExt { - fn from_f32(input: f32) -> Self; - fn to_f32(self) -> f32; -} - -impl SingleExt for Single { - fn from_f32(input: f32) -> Self { - Self::from_bits(input.to_bits() as u128) - } - - fn to_f32(self) -> f32 { - f32::from_bits(self.to_bits() as u32) - } -} - -trait DoubleExt { - fn from_f64(input: f64) -> Self; - fn to_f64(self) -> f64; -} - -impl DoubleExt for Double { - fn from_f64(input: f64) -> Self { - Self::from_bits(input.to_bits() as u128) - } - - fn to_f64(self) -> f64 { - f64::from_bits(self.to_bits() as u64) - } -} - -#[test] -fn is_signaling() { - // We test qNaN, -qNaN, +sNaN, -sNaN with and without payloads. - let payload = 4; - assert!(!Single::qnan(None).is_signaling()); - assert!(!(-Single::qnan(None)).is_signaling()); - assert!(!Single::qnan(Some(payload)).is_signaling()); - assert!(!(-Single::qnan(Some(payload))).is_signaling()); - assert!(Single::snan(None).is_signaling()); - assert!((-Single::snan(None)).is_signaling()); - assert!(Single::snan(Some(payload)).is_signaling()); - assert!((-Single::snan(Some(payload))).is_signaling()); -} - -#[test] -fn next() { - // 1. Test Special Cases Values. - // - // Test all special values for nextUp and nextDown perscribed by IEEE-754R - // 2008. These are: - // 1. +inf - // 2. -inf - // 3. largest - // 4. -largest - // 5. smallest - // 6. -smallest - // 7. qNaN - // 8. sNaN - // 9. +0 - // 10. -0 - - let mut status; - - // nextUp(+inf) = +inf. - let test = unpack!(status=, Quad::INFINITY.next_up()); - let expected = Quad::INFINITY; - assert_eq!(status, Status::OK); - assert!(test.is_infinite()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(+inf) = -nextUp(-inf) = -(-largest) = largest - let test = unpack!(status=, Quad::INFINITY.next_down()); - let expected = Quad::largest(); - assert_eq!(status, Status::OK); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(-inf) = -largest - let test = unpack!(status=, (-Quad::INFINITY).next_up()); - let expected = -Quad::largest(); - assert_eq!(status, Status::OK); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(-inf) = -nextUp(+inf) = -(+inf) = -inf. - let test = unpack!(status=, (-Quad::INFINITY).next_down()); - let expected = -Quad::INFINITY; - assert_eq!(status, Status::OK); - assert!(test.is_infinite() && test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(largest) = +inf - let test = unpack!(status=, Quad::largest().next_up()); - let expected = Quad::INFINITY; - assert_eq!(status, Status::OK); - assert!(test.is_infinite() && !test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(largest) = -nextUp(-largest) - // = -(-largest + inc) - // = largest - inc. - let test = unpack!(status=, Quad::largest().next_down()); - let expected = "0x1.fffffffffffffffffffffffffffep+16383".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_infinite() && !test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(-largest) = -largest + inc. - let test = unpack!(status=, (-Quad::largest()).next_up()); - let expected = "-0x1.fffffffffffffffffffffffffffep+16383".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(-largest) = -nextUp(largest) = -(inf) = -inf. - let test = unpack!(status=, (-Quad::largest()).next_down()); - let expected = -Quad::INFINITY; - assert_eq!(status, Status::OK); - assert!(test.is_infinite() && test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(smallest) = smallest + inc. - let test = unpack!(status=, "0x0.0000000000000000000000000001p-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "0x0.0000000000000000000000000002p-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(smallest) = -nextUp(-smallest) = -(-0) = +0. - let test = unpack!(status=, "0x0.0000000000000000000000000001p-16382" - .parse::() - .unwrap() - .next_down()); - let expected = Quad::ZERO; - assert_eq!(status, Status::OK); - assert!(test.is_pos_zero()); - assert!(test.bitwise_eq(expected)); - - // nextUp(-smallest) = -0. - let test = unpack!(status=, "-0x0.0000000000000000000000000001p-16382" - .parse::() - .unwrap() - .next_up()); - let expected = -Quad::ZERO; - assert_eq!(status, Status::OK); - assert!(test.is_neg_zero()); - assert!(test.bitwise_eq(expected)); - - // nextDown(-smallest) = -nextUp(smallest) = -smallest - inc. - let test = unpack!(status=, "-0x0.0000000000000000000000000001p-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x0.0000000000000000000000000002p-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextUp(qNaN) = qNaN - let test = unpack!(status=, Quad::qnan(None).next_up()); - let expected = Quad::qnan(None); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(qNaN) = qNaN - let test = unpack!(status=, Quad::qnan(None).next_down()); - let expected = Quad::qnan(None); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextUp(sNaN) = qNaN - let test = unpack!(status=, Quad::snan(None).next_up()); - let expected = Quad::qnan(None); - assert_eq!(status, Status::INVALID_OP); - assert!(test.bitwise_eq(expected)); - - // nextDown(sNaN) = qNaN - let test = unpack!(status=, Quad::snan(None).next_down()); - let expected = Quad::qnan(None); - assert_eq!(status, Status::INVALID_OP); - assert!(test.bitwise_eq(expected)); - - // nextUp(+0) = +smallest - let test = unpack!(status=, Quad::ZERO.next_up()); - let expected = Quad::SMALLEST; - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(+0) = -nextUp(-0) = -smallest - let test = unpack!(status=, Quad::ZERO.next_down()); - let expected = -Quad::SMALLEST; - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextUp(-0) = +smallest - let test = unpack!(status=, (-Quad::ZERO).next_up()); - let expected = Quad::SMALLEST; - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(-0) = -nextUp(0) = -smallest - let test = unpack!(status=, (-Quad::ZERO).next_down()); - let expected = -Quad::SMALLEST; - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // 2. Binade Boundary Tests. - - // 2a. Test denormal <-> normal binade boundaries. - // * nextUp(+Largest Denormal) -> +Smallest Normal. - // * nextDown(-Largest Denormal) -> -Smallest Normal. - // * nextUp(-Smallest Normal) -> -Largest Denormal. - // * nextDown(+Smallest Normal) -> +Largest Denormal. - - // nextUp(+Largest Denormal) -> +Smallest Normal. - let test = unpack!(status=, "0x0.ffffffffffffffffffffffffffffp-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "0x1.0000000000000000000000000000p-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - // nextDown(-Largest Denormal) -> -Smallest Normal. - let test = unpack!(status=, "-0x0.ffffffffffffffffffffffffffffp-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x1.0000000000000000000000000000p-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - // nextUp(-Smallest Normal) -> -Largest Denormal. - let test = unpack!(status=, "-0x1.0000000000000000000000000000p-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "-0x0.ffffffffffffffffffffffffffffp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - // nextDown(+Smallest Normal) -> +Largest Denormal. - let test = unpack!(status=, "+0x1.0000000000000000000000000000p-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "+0x0.ffffffffffffffffffffffffffffp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - // 2b. Test normal <-> normal binade boundaries. - // * nextUp(-Normal Binade Boundary) -> -Normal Binade Boundary + 1. - // * nextDown(+Normal Binade Boundary) -> +Normal Binade Boundary - 1. - // * nextUp(+Normal Binade Boundary - 1) -> +Normal Binade Boundary. - // * nextDown(-Normal Binade Boundary + 1) -> -Normal Binade Boundary. - - // nextUp(-Normal Binade Boundary) -> -Normal Binade Boundary + 1. - let test = unpack!(status=, "-0x1p+1".parse::().unwrap().next_up()); - let expected = "-0x1.ffffffffffffffffffffffffffffp+0".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(+Normal Binade Boundary) -> +Normal Binade Boundary - 1. - let test = unpack!(status=, "0x1p+1".parse::().unwrap().next_down()); - let expected = "0x1.ffffffffffffffffffffffffffffp+0".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextUp(+Normal Binade Boundary - 1) -> +Normal Binade Boundary. - let test = unpack!(status=, "0x1.ffffffffffffffffffffffffffffp+0" - .parse::() - .unwrap() - .next_up()); - let expected = "0x1p+1".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(-Normal Binade Boundary + 1) -> -Normal Binade Boundary. - let test = unpack!(status=, "-0x1.ffffffffffffffffffffffffffffp+0" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x1p+1".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // 2c. Test using next at binade boundaries with a direction away from the - // binade boundary. Away from denormal <-> normal boundaries. - // - // This is to make sure that even though we are at a binade boundary, since - // we are rounding away, we do not trigger the binade boundary code. Thus we - // test: - // * nextUp(-Largest Denormal) -> -Largest Denormal + inc. - // * nextDown(+Largest Denormal) -> +Largest Denormal - inc. - // * nextUp(+Smallest Normal) -> +Smallest Normal + inc. - // * nextDown(-Smallest Normal) -> -Smallest Normal - inc. - - // nextUp(-Largest Denormal) -> -Largest Denormal + inc. - let test = unpack!(status=, "-0x0.ffffffffffffffffffffffffffffp-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "-0x0.fffffffffffffffffffffffffffep-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(+Largest Denormal) -> +Largest Denormal - inc. - let test = unpack!(status=, "0x0.ffffffffffffffffffffffffffffp-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "0x0.fffffffffffffffffffffffffffep-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(+Smallest Normal) -> +Smallest Normal + inc. - let test = unpack!(status=, "0x1.0000000000000000000000000000p-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "0x1.0000000000000000000000000001p-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(-Smallest Normal) -> -Smallest Normal - inc. - let test = unpack!(status=, "-0x1.0000000000000000000000000000p-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x1.0000000000000000000000000001p-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // 2d. Test values which cause our exponent to go to min exponent. This - // is to ensure that guards in the code to check for min exponent - // trigger properly. - // * nextUp(-0x1p-16381) -> -0x1.ffffffffffffffffffffffffffffp-16382 - // * nextDown(-0x1.ffffffffffffffffffffffffffffp-16382) -> - // -0x1p-16381 - // * nextUp(0x1.ffffffffffffffffffffffffffffp-16382) -> 0x1p-16382 - // * nextDown(0x1p-16382) -> 0x1.ffffffffffffffffffffffffffffp-16382 - - // nextUp(-0x1p-16381) -> -0x1.ffffffffffffffffffffffffffffp-16382 - let test = unpack!(status=, "-0x1p-16381".parse::().unwrap().next_up()); - let expected = "-0x1.ffffffffffffffffffffffffffffp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(-0x1.ffffffffffffffffffffffffffffp-16382) -> - // -0x1p-16381 - let test = unpack!(status=, "-0x1.ffffffffffffffffffffffffffffp-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x1p-16381".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextUp(0x1.ffffffffffffffffffffffffffffp-16382) -> 0x1p-16381 - let test = unpack!(status=, "0x1.ffffffffffffffffffffffffffffp-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "0x1p-16381".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // nextDown(0x1p-16381) -> 0x1.ffffffffffffffffffffffffffffp-16382 - let test = unpack!(status=, "0x1p-16381".parse::().unwrap().next_down()); - let expected = "0x1.ffffffffffffffffffffffffffffp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.bitwise_eq(expected)); - - // 3. Now we test both denormal/normal computation which will not cause us - // to go across binade boundaries. Specifically we test: - // * nextUp(+Denormal) -> +Denormal. - // * nextDown(+Denormal) -> +Denormal. - // * nextUp(-Denormal) -> -Denormal. - // * nextDown(-Denormal) -> -Denormal. - // * nextUp(+Normal) -> +Normal. - // * nextDown(+Normal) -> +Normal. - // * nextUp(-Normal) -> -Normal. - // * nextDown(-Normal) -> -Normal. - - // nextUp(+Denormal) -> +Denormal. - let test = unpack!(status=, "0x0.ffffffffffffffffffffffff000cp-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "0x0.ffffffffffffffffffffffff000dp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(+Denormal) -> +Denormal. - let test = unpack!(status=, "0x0.ffffffffffffffffffffffff000cp-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "0x0.ffffffffffffffffffffffff000bp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(-Denormal) -> -Denormal. - let test = unpack!(status=, "-0x0.ffffffffffffffffffffffff000cp-16382" - .parse::() - .unwrap() - .next_up()); - let expected = "-0x0.ffffffffffffffffffffffff000bp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(-Denormal) -> -Denormal - let test = unpack!(status=, "-0x0.ffffffffffffffffffffffff000cp-16382" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x0.ffffffffffffffffffffffff000dp-16382".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(test.is_denormal()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(+Normal) -> +Normal. - let test = unpack!(status=, "0x1.ffffffffffffffffffffffff000cp-16000" - .parse::() - .unwrap() - .next_up()); - let expected = "0x1.ffffffffffffffffffffffff000dp-16000".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(+Normal) -> +Normal. - let test = unpack!(status=, "0x1.ffffffffffffffffffffffff000cp-16000" - .parse::() - .unwrap() - .next_down()); - let expected = "0x1.ffffffffffffffffffffffff000bp-16000".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextUp(-Normal) -> -Normal. - let test = unpack!(status=, "-0x1.ffffffffffffffffffffffff000cp-16000" - .parse::() - .unwrap() - .next_up()); - let expected = "-0x1.ffffffffffffffffffffffff000bp-16000".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - - // nextDown(-Normal) -> -Normal. - let test = unpack!(status=, "-0x1.ffffffffffffffffffffffff000cp-16000" - .parse::() - .unwrap() - .next_down()); - let expected = "-0x1.ffffffffffffffffffffffff000dp-16000".parse::().unwrap(); - assert_eq!(status, Status::OK); - assert!(!test.is_denormal()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); -} - -#[test] -fn fma() { - { - let mut f1 = Single::from_f32(14.5); - let f2 = Single::from_f32(-14.5); - let f3 = Single::from_f32(225.0); - f1 = f1.mul_add(f2, f3).value; - assert_eq!(14.75, f1.to_f32()); - } - - { - let val2 = Single::from_f32(2.0); - let mut f1 = Single::from_f32(1.17549435e-38); - let mut f2 = Single::from_f32(1.17549435e-38); - f1 /= val2; - f2 /= val2; - let f3 = Single::from_f32(12.0); - f1 = f1.mul_add(f2, f3).value; - assert_eq!(12.0, f1.to_f32()); - } - - // Test for correct zero sign when answer is exactly zero. - // fma(1.0, -1.0, 1.0) -> +ve 0. - { - let mut f1 = Double::from_f64(1.0); - let f2 = Double::from_f64(-1.0); - let f3 = Double::from_f64(1.0); - f1 = f1.mul_add(f2, f3).value; - assert!(!f1.is_negative() && f1.is_zero()); - } - - // Test for correct zero sign when answer is exactly zero and rounding towards - // negative. - // fma(1.0, -1.0, 1.0) -> +ve 0. - { - let mut f1 = Double::from_f64(1.0); - let f2 = Double::from_f64(-1.0); - let f3 = Double::from_f64(1.0); - f1 = f1.mul_add_r(f2, f3, Round::TowardNegative).value; - assert!(f1.is_negative() && f1.is_zero()); - } - - // Test for correct (in this case -ve) sign when adding like signed zeros. - // Test fma(0.0, -0.0, -0.0) -> -ve 0. - { - let mut f1 = Double::from_f64(0.0); - let f2 = Double::from_f64(-0.0); - let f3 = Double::from_f64(-0.0); - f1 = f1.mul_add(f2, f3).value; - assert!(f1.is_negative() && f1.is_zero()); - } - - // Test -ve sign preservation when small negative results underflow. - { - let mut f1 = "-0x1p-1074".parse::().unwrap(); - let f2 = "+0x1p-1074".parse::().unwrap(); - let f3 = Double::from_f64(0.0); - f1 = f1.mul_add(f2, f3).value; - assert!(f1.is_negative() && f1.is_zero()); - } - - // Test x87 extended precision case from https://llvm.org/PR20728. - { - let mut m1 = X87DoubleExtended::from_u128(1).value; - let m2 = X87DoubleExtended::from_u128(1).value; - let a = X87DoubleExtended::from_u128(3).value; - - let mut loses_info = false; - m1 = m1.mul_add(m2, a).value; - let r: Single = m1.convert(&mut loses_info).value; - assert!(!loses_info); - assert_eq!(4.0, r.to_f32()); - } -} - -#[test] -fn issue_69532() { - let f = Double::from_bits(0x7FF0_0000_0000_0001u64 as u128); - let mut loses_info = false; - let sta = f.convert(&mut loses_info); - let r: Single = sta.value; - assert!(loses_info); - assert!(r.is_nan()); - assert_eq!(sta.status, Status::INVALID_OP); -} - -#[test] -fn min_num() { - let f1 = Double::from_f64(1.0); - let f2 = Double::from_f64(2.0); - let nan = Double::NAN; - - assert_eq!(1.0, f1.min(f2).to_f64()); - assert_eq!(1.0, f2.min(f1).to_f64()); - assert_eq!(1.0, f1.min(nan).to_f64()); - assert_eq!(1.0, nan.min(f1).to_f64()); -} - -#[test] -fn max_num() { - let f1 = Double::from_f64(1.0); - let f2 = Double::from_f64(2.0); - let nan = Double::NAN; - - assert_eq!(2.0, f1.max(f2).to_f64()); - assert_eq!(2.0, f2.max(f1).to_f64()); - assert_eq!(1.0, f1.max(nan).to_f64()); - assert_eq!(1.0, nan.max(f1).to_f64()); -} - -#[test] -fn denormal() { - // Test single precision - { - assert!(!Single::from_f32(0.0).is_denormal()); - - let mut t = "1.17549435082228750797e-38".parse::().unwrap(); - assert!(!t.is_denormal()); - - let val2 = Single::from_f32(2.0e0); - t /= val2; - assert!(t.is_denormal()); - } - - // Test double precision - { - assert!(!Double::from_f64(0.0).is_denormal()); - - let mut t = "2.22507385850720138309e-308".parse::().unwrap(); - assert!(!t.is_denormal()); - - let val2 = Double::from_f64(2.0e0); - t /= val2; - assert!(t.is_denormal()); - } - - // Test Intel double-ext - { - assert!(!X87DoubleExtended::from_u128(0).value.is_denormal()); - - let mut t = "3.36210314311209350626e-4932".parse::().unwrap(); - assert!(!t.is_denormal()); - - t /= X87DoubleExtended::from_u128(2).value; - assert!(t.is_denormal()); - } - - // Test quadruple precision - { - assert!(!Quad::from_u128(0).value.is_denormal()); - - let mut t = "3.36210314311209350626267781732175260e-4932".parse::().unwrap(); - assert!(!t.is_denormal()); - - t /= Quad::from_u128(2).value; - assert!(t.is_denormal()); - } -} - -#[test] -fn decimal_strings_without_null_terminators() { - // Make sure that we can parse strings without null terminators. - // rdar://14323230. - let val = "0.00"[..3].parse::().unwrap(); - assert_eq!(val.to_f64(), 0.0); - let val = "0.01"[..3].parse::().unwrap(); - assert_eq!(val.to_f64(), 0.0); - let val = "0.09"[..3].parse::().unwrap(); - assert_eq!(val.to_f64(), 0.0); - let val = "0.095"[..4].parse::().unwrap(); - assert_eq!(val.to_f64(), 0.09); - let val = "0.00e+3"[..7].parse::().unwrap(); - assert_eq!(val.to_f64(), 0.00); - let val = "0e+3"[..4].parse::().unwrap(); - assert_eq!(val.to_f64(), 0.00); -} - -#[test] -fn from_zero_decimal_string() { - assert_eq!(0.0, "0".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.".parse::().unwrap().to_f64()); - - assert_eq!(0.0, ".0".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+.0".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-.0".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.0".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.0".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.0".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "00000.".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+00000.".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-00000.".parse::().unwrap().to_f64()); - - assert_eq!(0.0, ".00000".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+.00000".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-.00000".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0000.00000".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0000.00000".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0000.00000".parse::().unwrap().to_f64()); -} - -#[test] -fn from_zero_decimal_single_exponent_string() { - assert_eq!(0.0, "0e1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0e1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0e1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0e+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0e+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0e+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0e-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0e-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0e-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.e1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.e1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.e1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.e+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.e+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.e+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.e-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.e-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.e-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, ".0e1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+.0e1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-.0e1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, ".0e+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+.0e+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-.0e+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, ".0e-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+.0e-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-.0e-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.0e1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.0e1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.0e1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.0e+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.0e+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.0e+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0.0e-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0.0e-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0.0e-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "000.0000e1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+000.0000e+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-000.0000e+1".parse::().unwrap().to_f64()); -} - -#[test] -fn from_zero_decimal_large_exponent_string() { - assert_eq!(0.0, "0e1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0e1234".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0e1234".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0e+1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0e+1234".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0e+1234".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0e-1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0e-1234".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0e-1234".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "000.0000e1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "000.0000e-1234".parse::().unwrap().to_f64()); -} - -#[test] -fn from_zero_hexadecimal_string() { - assert_eq!(0.0, "0x0p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0p1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0p1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0p+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0p+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0p+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0p-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0p-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0p-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0.p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0.p1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0.p1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0.p+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0.p+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0.p+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0.p-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0.p-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0.p-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x.0p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x.0p1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x.0p1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x.0p+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x.0p+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x.0p+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x.0p-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x.0p-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x.0p-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0.0p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0.0p1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0.0p1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0.0p+1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0.0p+1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0.0p+1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x0.0p-1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "+0x0.0p-1".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0.0p-1".parse::().unwrap().to_f64()); - - assert_eq!(0.0, "0x00000.p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x0000.00000p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x.00000p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x0.p1".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x0p1234".parse::().unwrap().to_f64()); - assert_eq!(-0.0, "-0x0p1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x00000.p1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x0000.00000p1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x.00000p1234".parse::().unwrap().to_f64()); - assert_eq!(0.0, "0x0.p1234".parse::().unwrap().to_f64()); -} - -#[test] -fn from_decimal_string() { - assert_eq!(1.0, "1".parse::().unwrap().to_f64()); - assert_eq!(2.0, "2.".parse::().unwrap().to_f64()); - assert_eq!(0.5, ".5".parse::().unwrap().to_f64()); - assert_eq!(1.0, "1.0".parse::().unwrap().to_f64()); - assert_eq!(-2.0, "-2".parse::().unwrap().to_f64()); - assert_eq!(-4.0, "-4.".parse::().unwrap().to_f64()); - assert_eq!(-0.5, "-.5".parse::().unwrap().to_f64()); - assert_eq!(-1.5, "-1.5".parse::().unwrap().to_f64()); - assert_eq!(1.25e12, "1.25e12".parse::().unwrap().to_f64()); - assert_eq!(1.25e+12, "1.25e+12".parse::().unwrap().to_f64()); - assert_eq!(1.25e-12, "1.25e-12".parse::().unwrap().to_f64()); - assert_eq!(1024.0, "1024.".parse::().unwrap().to_f64()); - assert_eq!(1024.05, "1024.05000".parse::().unwrap().to_f64()); - assert_eq!(0.05, ".05000".parse::().unwrap().to_f64()); - assert_eq!(2.0, "2.".parse::().unwrap().to_f64()); - assert_eq!(2.0e2, "2.e2".parse::().unwrap().to_f64()); - assert_eq!(2.0e+2, "2.e+2".parse::().unwrap().to_f64()); - assert_eq!(2.0e-2, "2.e-2".parse::().unwrap().to_f64()); - assert_eq!(2.05e2, "002.05000e2".parse::().unwrap().to_f64()); - assert_eq!(2.05e+2, "002.05000e+2".parse::().unwrap().to_f64()); - assert_eq!(2.05e-2, "002.05000e-2".parse::().unwrap().to_f64()); - assert_eq!(2.05e12, "002.05000e12".parse::().unwrap().to_f64()); - assert_eq!(2.05e+12, "002.05000e+12".parse::().unwrap().to_f64()); - assert_eq!(2.05e-12, "002.05000e-12".parse::().unwrap().to_f64()); - - // These are "carefully selected" to overflow the fast log-base - // calculations in the implementation. - assert!("99e99999".parse::().unwrap().is_infinite()); - assert!("-99e99999".parse::().unwrap().is_infinite()); - assert!("1e-99999".parse::().unwrap().is_pos_zero()); - assert!("-1e-99999".parse::().unwrap().is_neg_zero()); - - assert_eq!(2.71828, "2.71828".parse::().unwrap().to_f64()); -} - -#[test] -fn from_hexadecimal_string() { - assert_eq!(1.0, "0x1p0".parse::().unwrap().to_f64()); - assert_eq!(1.0, "+0x1p0".parse::().unwrap().to_f64()); - assert_eq!(-1.0, "-0x1p0".parse::().unwrap().to_f64()); - - assert_eq!(1.0, "0x1p+0".parse::().unwrap().to_f64()); - assert_eq!(1.0, "+0x1p+0".parse::().unwrap().to_f64()); - assert_eq!(-1.0, "-0x1p+0".parse::().unwrap().to_f64()); - - assert_eq!(1.0, "0x1p-0".parse::().unwrap().to_f64()); - assert_eq!(1.0, "+0x1p-0".parse::().unwrap().to_f64()); - assert_eq!(-1.0, "-0x1p-0".parse::().unwrap().to_f64()); - - assert_eq!(2.0, "0x1p1".parse::().unwrap().to_f64()); - assert_eq!(2.0, "+0x1p1".parse::().unwrap().to_f64()); - assert_eq!(-2.0, "-0x1p1".parse::().unwrap().to_f64()); - - assert_eq!(2.0, "0x1p+1".parse::().unwrap().to_f64()); - assert_eq!(2.0, "+0x1p+1".parse::().unwrap().to_f64()); - assert_eq!(-2.0, "-0x1p+1".parse::().unwrap().to_f64()); - - assert_eq!(0.5, "0x1p-1".parse::().unwrap().to_f64()); - assert_eq!(0.5, "+0x1p-1".parse::().unwrap().to_f64()); - assert_eq!(-0.5, "-0x1p-1".parse::().unwrap().to_f64()); - - assert_eq!(3.0, "0x1.8p1".parse::().unwrap().to_f64()); - assert_eq!(3.0, "+0x1.8p1".parse::().unwrap().to_f64()); - assert_eq!(-3.0, "-0x1.8p1".parse::().unwrap().to_f64()); - - assert_eq!(3.0, "0x1.8p+1".parse::().unwrap().to_f64()); - assert_eq!(3.0, "+0x1.8p+1".parse::().unwrap().to_f64()); - assert_eq!(-3.0, "-0x1.8p+1".parse::().unwrap().to_f64()); - - assert_eq!(0.75, "0x1.8p-1".parse::().unwrap().to_f64()); - assert_eq!(0.75, "+0x1.8p-1".parse::().unwrap().to_f64()); - assert_eq!(-0.75, "-0x1.8p-1".parse::().unwrap().to_f64()); - - assert_eq!(8192.0, "0x1000.000p1".parse::().unwrap().to_f64()); - assert_eq!(8192.0, "+0x1000.000p1".parse::().unwrap().to_f64()); - assert_eq!(-8192.0, "-0x1000.000p1".parse::().unwrap().to_f64()); - - assert_eq!(8192.0, "0x1000.000p+1".parse::().unwrap().to_f64()); - assert_eq!(8192.0, "+0x1000.000p+1".parse::().unwrap().to_f64()); - assert_eq!(-8192.0, "-0x1000.000p+1".parse::().unwrap().to_f64()); - - assert_eq!(2048.0, "0x1000.000p-1".parse::().unwrap().to_f64()); - assert_eq!(2048.0, "+0x1000.000p-1".parse::().unwrap().to_f64()); - assert_eq!(-2048.0, "-0x1000.000p-1".parse::().unwrap().to_f64()); - - assert_eq!(8192.0, "0x1000p1".parse::().unwrap().to_f64()); - assert_eq!(8192.0, "+0x1000p1".parse::().unwrap().to_f64()); - assert_eq!(-8192.0, "-0x1000p1".parse::().unwrap().to_f64()); - - assert_eq!(8192.0, "0x1000p+1".parse::().unwrap().to_f64()); - assert_eq!(8192.0, "+0x1000p+1".parse::().unwrap().to_f64()); - assert_eq!(-8192.0, "-0x1000p+1".parse::().unwrap().to_f64()); - - assert_eq!(2048.0, "0x1000p-1".parse::().unwrap().to_f64()); - assert_eq!(2048.0, "+0x1000p-1".parse::().unwrap().to_f64()); - assert_eq!(-2048.0, "-0x1000p-1".parse::().unwrap().to_f64()); - - assert_eq!(16384.0, "0x10p10".parse::().unwrap().to_f64()); - assert_eq!(16384.0, "+0x10p10".parse::().unwrap().to_f64()); - assert_eq!(-16384.0, "-0x10p10".parse::().unwrap().to_f64()); - - assert_eq!(16384.0, "0x10p+10".parse::().unwrap().to_f64()); - assert_eq!(16384.0, "+0x10p+10".parse::().unwrap().to_f64()); - assert_eq!(-16384.0, "-0x10p+10".parse::().unwrap().to_f64()); - - assert_eq!(0.015625, "0x10p-10".parse::().unwrap().to_f64()); - assert_eq!(0.015625, "+0x10p-10".parse::().unwrap().to_f64()); - assert_eq!(-0.015625, "-0x10p-10".parse::().unwrap().to_f64()); - - assert_eq!(1.0625, "0x1.1p0".parse::().unwrap().to_f64()); - assert_eq!(1.0, "0x1p0".parse::().unwrap().to_f64()); - - assert_eq!( - "0x1p-150".parse::().unwrap().to_f64(), - "+0x800000000000000001.p-221".parse::().unwrap().to_f64() - ); - assert_eq!( - 2251799813685248.5, - "0x80000000000004000000.010p-28".parse::().unwrap().to_f64() - ); -} - -#[test] -fn to_string() { - let to_string = |d: f64, precision: usize, width: usize| { - let x = Double::from_f64(d); - if precision == 0 { - format!("{:1$}", x, width) - } else { - format!("{:2$.1$}", x, precision, width) - } - }; - assert_eq!("10", to_string(10.0, 6, 3)); - assert_eq!("1.0E+1", to_string(10.0, 6, 0)); - assert_eq!("10100", to_string(1.01E+4, 5, 2)); - assert_eq!("1.01E+4", to_string(1.01E+4, 4, 2)); - assert_eq!("1.01E+4", to_string(1.01E+4, 5, 1)); - assert_eq!("0.0101", to_string(1.01E-2, 5, 2)); - assert_eq!("0.0101", to_string(1.01E-2, 4, 2)); - assert_eq!("1.01E-2", to_string(1.01E-2, 5, 1)); - assert_eq!("0.78539816339744828", to_string(0.78539816339744830961, 0, 3)); - assert_eq!("4.9406564584124654E-324", to_string(4.9406564584124654e-324, 0, 3)); - assert_eq!("873.18340000000001", to_string(873.1834, 0, 1)); - assert_eq!("8.7318340000000001E+2", to_string(873.1834, 0, 0)); - assert_eq!("1.7976931348623157E+308", to_string(1.7976931348623157E+308, 0, 0)); - - let to_string = |d: f64, precision: usize, width: usize| { - let x = Double::from_f64(d); - if precision == 0 { - format!("{:#1$}", x, width) - } else { - format!("{:#2$.1$}", x, precision, width) - } - }; - assert_eq!("10", to_string(10.0, 6, 3)); - assert_eq!("1.000000e+01", to_string(10.0, 6, 0)); - assert_eq!("10100", to_string(1.01E+4, 5, 2)); - assert_eq!("1.0100e+04", to_string(1.01E+4, 4, 2)); - assert_eq!("1.01000e+04", to_string(1.01E+4, 5, 1)); - assert_eq!("0.0101", to_string(1.01E-2, 5, 2)); - assert_eq!("0.0101", to_string(1.01E-2, 4, 2)); - assert_eq!("1.01000e-02", to_string(1.01E-2, 5, 1)); - assert_eq!("0.78539816339744828", to_string(0.78539816339744830961, 0, 3)); - assert_eq!("4.94065645841246540e-324", to_string(4.9406564584124654e-324, 0, 3)); - assert_eq!("873.18340000000001", to_string(873.1834, 0, 1)); - assert_eq!("8.73183400000000010e+02", to_string(873.1834, 0, 0)); - assert_eq!("1.79769313486231570e+308", to_string(1.7976931348623157E+308, 0, 0)); -} - -#[test] -fn to_integer() { - let mut is_exact = false; - - assert_eq!( - Status::OK.and(10), - "10".parse::().unwrap().to_u128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(is_exact); - - assert_eq!( - Status::INVALID_OP.and(0), - "-10".parse::().unwrap().to_u128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(!is_exact); - - assert_eq!( - Status::INVALID_OP.and(31), - "32".parse::().unwrap().to_u128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(!is_exact); - - assert_eq!( - Status::INEXACT.and(7), - "7.9".parse::().unwrap().to_u128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(!is_exact); - - assert_eq!( - Status::OK.and(-10), - "-10".parse::().unwrap().to_i128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(is_exact); - - assert_eq!( - Status::INVALID_OP.and(-16), - "-17".parse::().unwrap().to_i128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(!is_exact); - - assert_eq!( - Status::INVALID_OP.and(15), - "16".parse::().unwrap().to_i128_r(5, Round::TowardZero, &mut is_exact,) - ); - assert!(!is_exact); -} - -#[test] -fn nan() { - fn nanbits(signaling: bool, negative: bool, fill: u128) -> u128 { - let x = if signaling { T::snan(Some(fill)) } else { T::qnan(Some(fill)) }; - if negative { (-x).to_bits() } else { x.to_bits() } - } - - assert_eq!(0x7fc00000, nanbits::(false, false, 0)); - assert_eq!(0xffc00000, nanbits::(false, true, 0)); - assert_eq!(0x7fc0ae72, nanbits::(false, false, 0xae72)); - assert_eq!(0x7fffae72, nanbits::(false, false, 0xffffae72)); - assert_eq!(0x7fa00000, nanbits::(true, false, 0)); - assert_eq!(0xffa00000, nanbits::(true, true, 0)); - assert_eq!(0x7f80ae72, nanbits::(true, false, 0xae72)); - assert_eq!(0x7fbfae72, nanbits::(true, false, 0xffffae72)); - - assert_eq!(0x7ff8000000000000, nanbits::(false, false, 0)); - assert_eq!(0xfff8000000000000, nanbits::(false, true, 0)); - assert_eq!(0x7ff800000000ae72, nanbits::(false, false, 0xae72)); - assert_eq!(0x7fffffffffffae72, nanbits::(false, false, 0xffffffffffffae72)); - assert_eq!(0x7ff4000000000000, nanbits::(true, false, 0)); - assert_eq!(0xfff4000000000000, nanbits::(true, true, 0)); - assert_eq!(0x7ff000000000ae72, nanbits::(true, false, 0xae72)); - assert_eq!(0x7ff7ffffffffae72, nanbits::(true, false, 0xffffffffffffae72)); -} - -#[test] -fn string_decimal_death() { - assert_eq!("".parse::(), Err(ParseError("Invalid string length"))); - assert_eq!("+".parse::(), Err(ParseError("String has no digits"))); - assert_eq!("-".parse::(), Err(ParseError("String has no digits"))); - - assert_eq!("\0".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("1\0".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("1\02".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("1\02e1".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("1e\0".parse::(), Err(ParseError("Invalid character in exponent"))); - assert_eq!("1e1\0".parse::(), Err(ParseError("Invalid character in exponent"))); - assert_eq!("1e1\02".parse::(), Err(ParseError("Invalid character in exponent"))); - - assert_eq!("1.0f".parse::(), Err(ParseError("Invalid character in significand"))); - - assert_eq!("..".parse::(), Err(ParseError("String contains multiple dots"))); - assert_eq!("..0".parse::(), Err(ParseError("String contains multiple dots"))); - assert_eq!("1.0.0".parse::(), Err(ParseError("String contains multiple dots"))); -} - -#[test] -fn string_decimal_significand_death() { - assert_eq!(".".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+.".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-.".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("e".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+e".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-e".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("e1".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+e1".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-e1".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!(".e1".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+.e1".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-.e1".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!(".e".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+.e".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-.e".parse::(), Err(ParseError("Significand has no digits"))); -} - -#[test] -fn string_decimal_exponent_death() { - assert_eq!("1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-1e".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("1.e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+1.e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-1.e".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!(".1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+.1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-.1e".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("1.1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+1.1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-1.1e".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("1e+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("1e-".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!(".1e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!(".1e+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!(".1e-".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("1.0e".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("1.0e+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("1.0e-".parse::(), Err(ParseError("Exponent has no digits"))); -} - -#[test] -fn string_hexadecimal_death() { - assert_eq!("0x".parse::(), Err(ParseError("Invalid string"))); - assert_eq!("+0x".parse::(), Err(ParseError("Invalid string"))); - assert_eq!("-0x".parse::(), Err(ParseError("Invalid string"))); - - assert_eq!("0x0".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("+0x0".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("-0x0".parse::(), Err(ParseError("Hex strings require an exponent"))); - - assert_eq!("0x0.".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("+0x0.".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("-0x0.".parse::(), Err(ParseError("Hex strings require an exponent"))); - - assert_eq!("0x.0".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("+0x.0".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("-0x.0".parse::(), Err(ParseError("Hex strings require an exponent"))); - - assert_eq!("0x0.0".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("+0x0.0".parse::(), Err(ParseError("Hex strings require an exponent"))); - assert_eq!("-0x0.0".parse::(), Err(ParseError("Hex strings require an exponent"))); - - assert_eq!("0x\0".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("0x1\0".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("0x1\02".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("0x1\02p1".parse::(), Err(ParseError("Invalid character in significand"))); - assert_eq!("0x1p\0".parse::(), Err(ParseError("Invalid character in exponent"))); - assert_eq!("0x1p1\0".parse::(), Err(ParseError("Invalid character in exponent"))); - assert_eq!("0x1p1\02".parse::(), Err(ParseError("Invalid character in exponent"))); - - assert_eq!("0x1p0f".parse::(), Err(ParseError("Invalid character in exponent"))); - - assert_eq!("0x..p1".parse::(), Err(ParseError("String contains multiple dots"))); - assert_eq!("0x..0p1".parse::(), Err(ParseError("String contains multiple dots"))); - assert_eq!("0x1.0.0p1".parse::(), Err(ParseError("String contains multiple dots"))); -} - -#[test] -fn string_hexadecimal_significand_death() { - assert_eq!("0x.".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0x.".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0x.".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("0xp".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0xp".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0xp".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("0xp+".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0xp+".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0xp+".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("0xp-".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0xp-".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0xp-".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("0x.p".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0x.p".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0x.p".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("0x.p+".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0x.p+".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0x.p+".parse::(), Err(ParseError("Significand has no digits"))); - - assert_eq!("0x.p-".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("+0x.p-".parse::(), Err(ParseError("Significand has no digits"))); - assert_eq!("-0x.p-".parse::(), Err(ParseError("Significand has no digits"))); -} - -#[test] -fn string_hexadecimal_exponent_death() { - assert_eq!("0x1p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1p".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1p+".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1p-".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1.p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1.p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1.p".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1.p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1.p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1.p+".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1.p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1.p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1.p-".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x.1p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x.1p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x.1p".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x.1p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x.1p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x.1p+".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x.1p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x.1p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x.1p-".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1.1p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1.1p".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1.1p".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1.1p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1.1p+".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1.1p+".parse::(), Err(ParseError("Exponent has no digits"))); - - assert_eq!("0x1.1p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("+0x1.1p-".parse::(), Err(ParseError("Exponent has no digits"))); - assert_eq!("-0x1.1p-".parse::(), Err(ParseError("Exponent has no digits"))); -} - -#[test] -fn exact_inverse() { - // Trivial operation. - assert!(Double::from_f64(2.0).get_exact_inverse().unwrap().bitwise_eq(Double::from_f64(0.5))); - assert!(Single::from_f32(2.0).get_exact_inverse().unwrap().bitwise_eq(Single::from_f32(0.5))); - assert!( - "2.0" - .parse::() - .unwrap() - .get_exact_inverse() - .unwrap() - .bitwise_eq("0.5".parse::().unwrap()) - ); - assert!( - "2.0" - .parse::() - .unwrap() - .get_exact_inverse() - .unwrap() - .bitwise_eq("0.5".parse::().unwrap()) - ); - - // FLT_MIN - assert!( - Single::from_f32(1.17549435e-38) - .get_exact_inverse() - .unwrap() - .bitwise_eq(Single::from_f32(8.5070592e+37)) - ); - - // Large float, inverse is a denormal. - assert!(Single::from_f32(1.7014118e38).get_exact_inverse().is_none()); - // Zero - assert!(Double::from_f64(0.0).get_exact_inverse().is_none()); - // Denormalized float - assert!(Single::from_f32(1.40129846e-45).get_exact_inverse().is_none()); -} - -#[test] -fn round_to_integral() { - let t = Double::from_f64(-0.5); - assert_eq!(-0.0, t.round_to_integral(Round::TowardZero).value.to_f64()); - assert_eq!(-1.0, t.round_to_integral(Round::TowardNegative).value.to_f64()); - assert_eq!(-0.0, t.round_to_integral(Round::TowardPositive).value.to_f64()); - assert_eq!(-0.0, t.round_to_integral(Round::NearestTiesToEven).value.to_f64()); - - let s = Double::from_f64(3.14); - assert_eq!(3.0, s.round_to_integral(Round::TowardZero).value.to_f64()); - assert_eq!(3.0, s.round_to_integral(Round::TowardNegative).value.to_f64()); - assert_eq!(4.0, s.round_to_integral(Round::TowardPositive).value.to_f64()); - assert_eq!(3.0, s.round_to_integral(Round::NearestTiesToEven).value.to_f64()); - - let r = Double::largest(); - assert_eq!(r.to_f64(), r.round_to_integral(Round::TowardZero).value.to_f64()); - assert_eq!(r.to_f64(), r.round_to_integral(Round::TowardNegative).value.to_f64()); - assert_eq!(r.to_f64(), r.round_to_integral(Round::TowardPositive).value.to_f64()); - assert_eq!(r.to_f64(), r.round_to_integral(Round::NearestTiesToEven).value.to_f64()); - - let p = Double::ZERO.round_to_integral(Round::TowardZero).value; - assert_eq!(0.0, p.to_f64()); - let p = (-Double::ZERO).round_to_integral(Round::TowardZero).value; - assert_eq!(-0.0, p.to_f64()); - let p = Double::NAN.round_to_integral(Round::TowardZero).value; - assert!(p.to_f64().is_nan()); - let p = Double::INFINITY.round_to_integral(Round::TowardZero).value; - assert!(p.to_f64().is_infinite() && p.to_f64() > 0.0); - let p = (-Double::INFINITY).round_to_integral(Round::TowardZero).value; - assert!(p.to_f64().is_infinite() && p.to_f64() < 0.0); -} - -#[test] -fn is_integer() { - let t = Double::from_f64(-0.0); - assert!(t.is_integer()); - let t = Double::from_f64(3.14159); - assert!(!t.is_integer()); - let t = Double::NAN; - assert!(!t.is_integer()); - let t = Double::INFINITY; - assert!(!t.is_integer()); - let t = -Double::INFINITY; - assert!(!t.is_integer()); - let t = Double::largest(); - assert!(t.is_integer()); -} - -#[test] -fn largest() { - assert_eq!(3.402823466e+38, Single::largest().to_f32()); - assert_eq!(1.7976931348623158e+308, Double::largest().to_f64()); -} - -#[test] -fn smallest() { - let test = Single::SMALLEST; - let expected = "0x0.000002p-126".parse::().unwrap(); - assert!(!test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - let test = -Single::SMALLEST; - let expected = "-0x0.000002p-126".parse::().unwrap(); - assert!(test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - let test = Quad::SMALLEST; - let expected = "0x0.0000000000000000000000000001p-16382".parse::().unwrap(); - assert!(!test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - let test = -Quad::SMALLEST; - let expected = "-0x0.0000000000000000000000000001p-16382".parse::().unwrap(); - assert!(test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(test.is_denormal()); - assert!(test.bitwise_eq(expected)); -} - -#[test] -fn smallest_normalized() { - let test = Single::smallest_normalized(); - let expected = "0x1p-126".parse::().unwrap(); - assert!(!test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(!test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - let test = -Single::smallest_normalized(); - let expected = "-0x1p-126".parse::().unwrap(); - assert!(test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(!test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - let test = Quad::smallest_normalized(); - let expected = "0x1p-16382".parse::().unwrap(); - assert!(!test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(!test.is_denormal()); - assert!(test.bitwise_eq(expected)); - - let test = -Quad::smallest_normalized(); - let expected = "-0x1p-16382".parse::().unwrap(); - assert!(test.is_negative()); - assert!(test.is_finite_non_zero()); - assert!(!test.is_denormal()); - assert!(test.bitwise_eq(expected)); -} - -#[test] -fn zero() { - assert_eq!(0.0, Single::from_f32(0.0).to_f32()); - assert_eq!(-0.0, Single::from_f32(-0.0).to_f32()); - assert!(Single::from_f32(-0.0).is_negative()); - - assert_eq!(0.0, Double::from_f64(0.0).to_f64()); - assert_eq!(-0.0, Double::from_f64(-0.0).to_f64()); - assert!(Double::from_f64(-0.0).is_negative()); - - fn test(sign: bool, bits: u128) { - let test = if sign { -T::ZERO } else { T::ZERO }; - let pattern = if sign { "-0x0p+0" } else { "0x0p+0" }; - let expected = pattern.parse::().unwrap(); - assert!(test.is_zero()); - assert_eq!(sign, test.is_negative()); - assert!(test.bitwise_eq(expected)); - assert_eq!(bits, test.to_bits()); - } - test::(false, 0); - test::(true, 0x8000); - test::(false, 0); - test::(true, 0x80000000); - test::(false, 0); - test::(true, 0x8000000000000000); - test::(false, 0); - test::(true, 0x8000000000000000_0000000000000000); - test::(false, 0); - test::(true, 0x8000_0000000000000000); -} - -#[test] -fn copy_sign() { - assert!( - Double::from_f64(-42.0) - .bitwise_eq(Double::from_f64(42.0).copy_sign(Double::from_f64(-1.0),),) - ); - assert!( - Double::from_f64(42.0) - .bitwise_eq(Double::from_f64(-42.0).copy_sign(Double::from_f64(1.0),),) - ); - assert!( - Double::from_f64(-42.0) - .bitwise_eq(Double::from_f64(-42.0).copy_sign(Double::from_f64(-1.0),),) - ); - assert!( - Double::from_f64(42.0) - .bitwise_eq(Double::from_f64(42.0).copy_sign(Double::from_f64(1.0),),) - ); -} - -#[test] -fn convert() { - let mut loses_info = false; - let test = "1.0".parse::().unwrap(); - let test: Single = test.convert(&mut loses_info).value; - assert_eq!(1.0, test.to_f32()); - assert!(!loses_info); - - let mut test = "0x1p-53".parse::().unwrap(); - let one = "1.0".parse::().unwrap(); - test += one; - let test: Double = test.convert(&mut loses_info).value; - assert_eq!(1.0, test.to_f64()); - assert!(loses_info); - - let mut test = "0x1p-53".parse::().unwrap(); - let one = "1.0".parse::().unwrap(); - test += one; - let test: Double = test.convert(&mut loses_info).value; - assert_eq!(1.0, test.to_f64()); - assert!(loses_info); - - let test = "0xf.fffffffp+28".parse::().unwrap(); - let test: Double = test.convert(&mut loses_info).value; - assert_eq!(4294967295.0, test.to_f64()); - assert!(!loses_info); - - let test = Single::qnan(None); - let x87_qnan = X87DoubleExtended::qnan(None); - let test: X87DoubleExtended = test.convert(&mut loses_info).value; - assert!(test.bitwise_eq(x87_qnan)); - assert!(!loses_info); - - let test = Single::snan(None); - let sta = test.convert(&mut loses_info); - let test: X87DoubleExtended = sta.value; - assert!(test.is_nan()); - assert!(!test.is_signaling()); - assert!(!loses_info); - assert_eq!(sta.status, Status::INVALID_OP); - - let test = X87DoubleExtended::qnan(None); - let test: X87DoubleExtended = test.convert(&mut loses_info).value; - assert!(test.bitwise_eq(x87_qnan)); - assert!(!loses_info); - - let test = X87DoubleExtended::snan(None); - let sta = test.convert(&mut loses_info); - let test: X87DoubleExtended = sta.value; - assert!(test.is_nan()); - assert!(!test.is_signaling()); - assert!(!loses_info); - assert_eq!(sta.status, Status::INVALID_OP); -} - -#[test] -fn is_negative() { - let t = "0x1p+0".parse::().unwrap(); - assert!(!t.is_negative()); - let t = "-0x1p+0".parse::().unwrap(); - assert!(t.is_negative()); - - assert!(!Single::INFINITY.is_negative()); - assert!((-Single::INFINITY).is_negative()); - - assert!(!Single::ZERO.is_negative()); - assert!((-Single::ZERO).is_negative()); - - assert!(!Single::NAN.is_negative()); - assert!((-Single::NAN).is_negative()); - - assert!(!Single::snan(None).is_negative()); - assert!((-Single::snan(None)).is_negative()); -} - -#[test] -fn is_normal() { - let t = "0x1p+0".parse::().unwrap(); - assert!(t.is_normal()); - - assert!(!Single::INFINITY.is_normal()); - assert!(!Single::ZERO.is_normal()); - assert!(!Single::NAN.is_normal()); - assert!(!Single::snan(None).is_normal()); - assert!(!"0x1p-149".parse::().unwrap().is_normal()); -} - -#[test] -fn is_finite() { - let t = "0x1p+0".parse::().unwrap(); - assert!(t.is_finite()); - assert!(!Single::INFINITY.is_finite()); - assert!(Single::ZERO.is_finite()); - assert!(!Single::NAN.is_finite()); - assert!(!Single::snan(None).is_finite()); - assert!("0x1p-149".parse::().unwrap().is_finite()); -} - -#[test] -fn is_infinite() { - let t = "0x1p+0".parse::().unwrap(); - assert!(!t.is_infinite()); - assert!(Single::INFINITY.is_infinite()); - assert!(!Single::ZERO.is_infinite()); - assert!(!Single::NAN.is_infinite()); - assert!(!Single::snan(None).is_infinite()); - assert!(!"0x1p-149".parse::().unwrap().is_infinite()); -} - -#[test] -fn is_nan() { - let t = "0x1p+0".parse::().unwrap(); - assert!(!t.is_nan()); - assert!(!Single::INFINITY.is_nan()); - assert!(!Single::ZERO.is_nan()); - assert!(Single::NAN.is_nan()); - assert!(Single::snan(None).is_nan()); - assert!(!"0x1p-149".parse::().unwrap().is_nan()); -} - -#[test] -fn is_finite_non_zero() { - // Test positive/negative normal value. - assert!("0x1p+0".parse::().unwrap().is_finite_non_zero()); - assert!("-0x1p+0".parse::().unwrap().is_finite_non_zero()); - - // Test positive/negative denormal value. - assert!("0x1p-149".parse::().unwrap().is_finite_non_zero()); - assert!("-0x1p-149".parse::().unwrap().is_finite_non_zero()); - - // Test +/- Infinity. - assert!(!Single::INFINITY.is_finite_non_zero()); - assert!(!(-Single::INFINITY).is_finite_non_zero()); - - // Test +/- Zero. - assert!(!Single::ZERO.is_finite_non_zero()); - assert!(!(-Single::ZERO).is_finite_non_zero()); - - // Test +/- qNaN. +/- don't mean anything with qNaN but paranoia can't hurt in - // this instance. - assert!(!Single::NAN.is_finite_non_zero()); - assert!(!(-Single::NAN).is_finite_non_zero()); - - // Test +/- sNaN. +/- don't mean anything with sNaN but paranoia can't hurt in - // this instance. - assert!(!Single::snan(None).is_finite_non_zero()); - assert!(!(-Single::snan(None)).is_finite_non_zero()); -} - -#[test] -fn add() { - // Test Special Cases against each other and normal values. - - // FIXMES/NOTES: - // 1. Since we perform only default exception handling all operations with - // signaling NaNs should have a result that is a quiet NaN. Currently they - // return sNaN. - - let p_inf = Single::INFINITY; - let m_inf = -Single::INFINITY; - let p_zero = Single::ZERO; - let m_zero = -Single::ZERO; - let qnan = Single::NAN; - let p_normal_value = "0x1p+0".parse::().unwrap(); - let m_normal_value = "-0x1p+0".parse::().unwrap(); - let p_largest_value = Single::largest(); - let m_largest_value = -Single::largest(); - let p_smallest_value = Single::SMALLEST; - let m_smallest_value = -Single::SMALLEST; - let p_smallest_normalized = Single::smallest_normalized(); - let m_smallest_normalized = -Single::smallest_normalized(); - - let overflow_status = Status::OVERFLOW | Status::INEXACT; - - let special_cases = [ - (p_inf, p_inf, "inf", Status::OK, Category::Infinity), - (p_inf, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (p_inf, p_zero, "inf", Status::OK, Category::Infinity), - (p_inf, m_zero, "inf", Status::OK, Category::Infinity), - (p_inf, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_inf, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_inf, p_normal_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_normal_value, "inf", Status::OK, Category::Infinity), - (p_inf, p_largest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_largest_value, "inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_value, "inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_normalized, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_normalized, "inf", Status::OK, Category::Infinity), - (m_inf, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (m_inf, m_inf, "-inf", Status::OK, Category::Infinity), - (m_inf, p_zero, "-inf", Status::OK, Category::Infinity), - (m_inf, m_zero, "-inf", Status::OK, Category::Infinity), - (m_inf, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_inf, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_inf, p_normal_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_normal_value, "-inf", Status::OK, Category::Infinity), - (m_inf, p_largest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_largest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (p_zero, p_inf, "inf", Status::OK, Category::Infinity), - (p_zero, m_inf, "-inf", Status::OK, Category::Infinity), - (p_zero, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_zero, "0x0p+0", Status::OK, Category::Zero), - (p_zero, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_zero, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_zero, p_normal_value, "0x1p+0", Status::OK, Category::Normal), - (p_zero, m_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (p_zero, p_largest_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_zero, m_largest_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (p_zero, p_smallest_value, "0x1p-149", Status::OK, Category::Normal), - (p_zero, m_smallest_value, "-0x1p-149", Status::OK, Category::Normal), - (p_zero, p_smallest_normalized, "0x1p-126", Status::OK, Category::Normal), - (p_zero, m_smallest_normalized, "-0x1p-126", Status::OK, Category::Normal), - (m_zero, p_inf, "inf", Status::OK, Category::Infinity), - (m_zero, m_inf, "-inf", Status::OK, Category::Infinity), - (m_zero, p_zero, "0x0p+0", Status::OK, Category::Zero), - (m_zero, m_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_zero, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_zero, p_normal_value, "0x1p+0", Status::OK, Category::Normal), - (m_zero, m_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (m_zero, p_largest_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (m_zero, m_largest_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_zero, p_smallest_value, "0x1p-149", Status::OK, Category::Normal), - (m_zero, m_smallest_value, "-0x1p-149", Status::OK, Category::Normal), - (m_zero, p_smallest_normalized, "0x1p-126", Status::OK, Category::Normal), - (m_zero, m_smallest_normalized, "-0x1p-126", Status::OK, Category::Normal), - (qnan, p_inf, "nan", Status::OK, Category::NaN), - (qnan, m_inf, "nan", Status::OK, Category::NaN), - (qnan, p_zero, "nan", Status::OK, Category::NaN), - (qnan, m_zero, "nan", Status::OK, Category::NaN), - (qnan, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (qnan, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (qnan, p_normal_value, "nan", Status::OK, Category::NaN), - (qnan, m_normal_value, "nan", Status::OK, Category::NaN), - (qnan, p_largest_value, "nan", Status::OK, Category::NaN), - (qnan, m_largest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_normalized, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_normalized, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (snan, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, qnan, "nan", Status::INVALID_OP, Category::NaN), - (snan, snan, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_inf, "inf", Status::OK, Category::Infinity), - (p_normal_value, m_inf, "-inf", Status::OK, Category::Infinity), - (p_normal_value, p_zero, "0x1p+0", Status::OK, Category::Normal), - (p_normal_value, m_zero, "0x1p+0", Status::OK, Category::Normal), - (p_normal_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_normal_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_normal_value, "0x1p+1", Status::OK, Category::Normal), - (p_normal_value, m_normal_value, "0x0p+0", Status::OK, Category::Zero), - (p_normal_value, p_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_normal_value, m_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_normal_value, p_smallest_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_normal_value, m_smallest_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_normal_value, p_smallest_normalized, "0x1p+0", Status::INEXACT, Category::Normal), - (p_normal_value, m_smallest_normalized, "0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, p_inf, "inf", Status::OK, Category::Infinity), - (m_normal_value, m_inf, "-inf", Status::OK, Category::Infinity), - (m_normal_value, p_zero, "-0x1p+0", Status::OK, Category::Normal), - (m_normal_value, m_zero, "-0x1p+0", Status::OK, Category::Normal), - (m_normal_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_normal_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_normal_value, p_normal_value, "0x0p+0", Status::OK, Category::Zero), - (m_normal_value, m_normal_value, "-0x1p+1", Status::OK, Category::Normal), - (m_normal_value, p_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_normal_value, m_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_normal_value, p_smallest_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, m_smallest_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, p_smallest_normalized, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, m_smallest_normalized, "-0x1p+0", Status::INEXACT, Category::Normal), - (p_largest_value, p_inf, "inf", Status::OK, Category::Infinity), - (p_largest_value, m_inf, "-inf", Status::OK, Category::Infinity), - (p_largest_value, p_zero, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, m_zero, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_largest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_largest_value, p_normal_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_largest_value, m_normal_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_largest_value, p_largest_value, "inf", overflow_status, Category::Infinity), - (p_largest_value, m_largest_value, "0x0p+0", Status::OK, Category::Zero), - (p_largest_value, p_smallest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_largest_value, m_smallest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - ( - p_largest_value, - p_smallest_normalized, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - p_largest_value, - m_smallest_normalized, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (m_largest_value, p_inf, "inf", Status::OK, Category::Infinity), - (m_largest_value, m_inf, "-inf", Status::OK, Category::Infinity), - (m_largest_value, p_zero, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, m_zero, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_largest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_largest_value, p_normal_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_largest_value, m_normal_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_largest_value, p_largest_value, "0x0p+0", Status::OK, Category::Zero), - (m_largest_value, m_largest_value, "-inf", overflow_status, Category::Infinity), - (m_largest_value, p_smallest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_largest_value, m_smallest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - ( - m_largest_value, - p_smallest_normalized, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - m_largest_value, - m_smallest_normalized, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (p_smallest_value, p_inf, "inf", Status::OK, Category::Infinity), - (p_smallest_value, m_inf, "-inf", Status::OK, Category::Infinity), - (p_smallest_value, p_zero, "0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, m_zero, "0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_value, p_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_smallest_value, m_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (p_smallest_value, p_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_smallest_value, m_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_smallest_value, p_smallest_value, "0x1p-148", Status::OK, Category::Normal), - (p_smallest_value, m_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_value, p_smallest_normalized, "0x1.000002p-126", Status::OK, Category::Normal), - (p_smallest_value, m_smallest_normalized, "-0x1.fffffcp-127", Status::OK, Category::Normal), - (m_smallest_value, p_inf, "inf", Status::OK, Category::Infinity), - (m_smallest_value, m_inf, "-inf", Status::OK, Category::Infinity), - (m_smallest_value, p_zero, "-0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, m_zero, "-0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_value, p_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - (m_smallest_value, m_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_smallest_value, p_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_smallest_value, m_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_smallest_value, p_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_value, m_smallest_value, "-0x1p-148", Status::OK, Category::Normal), - (m_smallest_value, p_smallest_normalized, "0x1.fffffcp-127", Status::OK, Category::Normal), - (m_smallest_value, m_smallest_normalized, "-0x1.000002p-126", Status::OK, Category::Normal), - (p_smallest_normalized, p_inf, "inf", Status::OK, Category::Infinity), - (p_smallest_normalized, m_inf, "-inf", Status::OK, Category::Infinity), - (p_smallest_normalized, p_zero, "0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, m_zero, "0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_normalized, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_normalized, p_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_smallest_normalized, m_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - ( - p_smallest_normalized, - p_largest_value, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - p_smallest_normalized, - m_largest_value, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (p_smallest_normalized, p_smallest_value, "0x1.000002p-126", Status::OK, Category::Normal), - (p_smallest_normalized, m_smallest_value, "0x1.fffffcp-127", Status::OK, Category::Normal), - (p_smallest_normalized, p_smallest_normalized, "0x1p-125", Status::OK, Category::Normal), - (p_smallest_normalized, m_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_normalized, p_inf, "inf", Status::OK, Category::Infinity), - (m_smallest_normalized, m_inf, "-inf", Status::OK, Category::Infinity), - (m_smallest_normalized, p_zero, "-0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, m_zero, "-0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_normalized, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_normalized, p_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - (m_smallest_normalized, m_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - ( - m_smallest_normalized, - p_largest_value, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - m_smallest_normalized, - m_largest_value, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (m_smallest_normalized, p_smallest_value, "-0x1.fffffcp-127", Status::OK, Category::Normal), - (m_smallest_normalized, m_smallest_value, "-0x1.000002p-126", Status::OK, Category::Normal), - (m_smallest_normalized, p_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_normalized, m_smallest_normalized, "-0x1p-125", Status::OK, Category::Normal), - ]; - - for (x, y, e_result, e_status, e_category) in special_cases { - let status; - let result = unpack!(status=, x + y); - assert_eq!(status, e_status); - assert_eq!(result.category(), e_category); - assert!(result.bitwise_eq(e_result.parse::().unwrap())); - } -} - -#[test] -fn subtract() { - // Test Special Cases against each other and normal values. - - // FIXMES/NOTES: - // 1. Since we perform only default exception handling all operations with - // signaling NaNs should have a result that is a quiet NaN. Currently they - // return sNaN. - - let p_inf = Single::INFINITY; - let m_inf = -Single::INFINITY; - let p_zero = Single::ZERO; - let m_zero = -Single::ZERO; - let qnan = Single::NAN; - let p_normal_value = "0x1p+0".parse::().unwrap(); - let m_normal_value = "-0x1p+0".parse::().unwrap(); - let p_largest_value = Single::largest(); - let m_largest_value = -Single::largest(); - let p_smallest_value = Single::SMALLEST; - let m_smallest_value = -Single::SMALLEST; - let p_smallest_normalized = Single::smallest_normalized(); - let m_smallest_normalized = -Single::smallest_normalized(); - - let overflow_status = Status::OVERFLOW | Status::INEXACT; - - let special_cases = [ - (p_inf, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (p_inf, m_inf, "inf", Status::OK, Category::Infinity), - (p_inf, p_zero, "inf", Status::OK, Category::Infinity), - (p_inf, m_zero, "inf", Status::OK, Category::Infinity), - (p_inf, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_inf, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (p_inf, p_normal_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_normal_value, "inf", Status::OK, Category::Infinity), - (p_inf, p_largest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_largest_value, "inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_value, "inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_normalized, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_normalized, "inf", Status::OK, Category::Infinity), - (m_inf, p_inf, "-inf", Status::OK, Category::Infinity), - (m_inf, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (m_inf, p_zero, "-inf", Status::OK, Category::Infinity), - (m_inf, m_zero, "-inf", Status::OK, Category::Infinity), - (m_inf, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_inf, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (m_inf, p_normal_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_normal_value, "-inf", Status::OK, Category::Infinity), - (m_inf, p_largest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_largest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (p_zero, p_inf, "-inf", Status::OK, Category::Infinity), - (p_zero, m_inf, "inf", Status::OK, Category::Infinity), - (p_zero, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_zero, "0x0p+0", Status::OK, Category::Zero), - (p_zero, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_zero, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (p_zero, p_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (p_zero, m_normal_value, "0x1p+0", Status::OK, Category::Normal), - (p_zero, p_largest_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (p_zero, m_largest_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_zero, p_smallest_value, "-0x1p-149", Status::OK, Category::Normal), - (p_zero, m_smallest_value, "0x1p-149", Status::OK, Category::Normal), - (p_zero, p_smallest_normalized, "-0x1p-126", Status::OK, Category::Normal), - (p_zero, m_smallest_normalized, "0x1p-126", Status::OK, Category::Normal), - (m_zero, p_inf, "-inf", Status::OK, Category::Infinity), - (m_zero, m_inf, "inf", Status::OK, Category::Infinity), - (m_zero, p_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_zero, "0x0p+0", Status::OK, Category::Zero), - (m_zero, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_zero, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (m_zero, p_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (m_zero, m_normal_value, "0x1p+0", Status::OK, Category::Normal), - (m_zero, p_largest_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_zero, m_largest_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (m_zero, p_smallest_value, "-0x1p-149", Status::OK, Category::Normal), - (m_zero, m_smallest_value, "0x1p-149", Status::OK, Category::Normal), - (m_zero, p_smallest_normalized, "-0x1p-126", Status::OK, Category::Normal), - (m_zero, m_smallest_normalized, "0x1p-126", Status::OK, Category::Normal), - (qnan, p_inf, "nan", Status::OK, Category::NaN), - (qnan, m_inf, "nan", Status::OK, Category::NaN), - (qnan, p_zero, "nan", Status::OK, Category::NaN), - (qnan, m_zero, "nan", Status::OK, Category::NaN), - (qnan, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (qnan, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (qnan, p_normal_value, "nan", Status::OK, Category::NaN), - (qnan, m_normal_value, "nan", Status::OK, Category::NaN), - (qnan, p_largest_value, "nan", Status::OK, Category::NaN), - (qnan, m_largest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_normalized, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_normalized, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (snan, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, qnan, "nan", Status::INVALID_OP, Category::NaN), - (snan, snan, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_inf, "-inf", Status::OK, Category::Infinity), - (p_normal_value, m_inf, "inf", Status::OK, Category::Infinity), - (p_normal_value, p_zero, "0x1p+0", Status::OK, Category::Normal), - (p_normal_value, m_zero, "0x1p+0", Status::OK, Category::Normal), - (p_normal_value, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_normal_value, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_normal_value, "0x0p+0", Status::OK, Category::Zero), - (p_normal_value, m_normal_value, "0x1p+1", Status::OK, Category::Normal), - (p_normal_value, p_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_normal_value, m_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_normal_value, p_smallest_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_normal_value, m_smallest_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_normal_value, p_smallest_normalized, "0x1p+0", Status::INEXACT, Category::Normal), - (p_normal_value, m_smallest_normalized, "0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, p_inf, "-inf", Status::OK, Category::Infinity), - (m_normal_value, m_inf, "inf", Status::OK, Category::Infinity), - (m_normal_value, p_zero, "-0x1p+0", Status::OK, Category::Normal), - (m_normal_value, m_zero, "-0x1p+0", Status::OK, Category::Normal), - (m_normal_value, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_normal_value, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (m_normal_value, p_normal_value, "-0x1p+1", Status::OK, Category::Normal), - (m_normal_value, m_normal_value, "0x0p+0", Status::OK, Category::Zero), - (m_normal_value, p_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_normal_value, m_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_normal_value, p_smallest_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, m_smallest_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, p_smallest_normalized, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_normal_value, m_smallest_normalized, "-0x1p+0", Status::INEXACT, Category::Normal), - (p_largest_value, p_inf, "-inf", Status::OK, Category::Infinity), - (p_largest_value, m_inf, "inf", Status::OK, Category::Infinity), - (p_largest_value, p_zero, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, m_zero, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_largest_value, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (p_largest_value, p_normal_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_largest_value, m_normal_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_largest_value, p_largest_value, "0x0p+0", Status::OK, Category::Zero), - (p_largest_value, m_largest_value, "inf", overflow_status, Category::Infinity), - (p_largest_value, p_smallest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_largest_value, m_smallest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - ( - p_largest_value, - p_smallest_normalized, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - p_largest_value, - m_smallest_normalized, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (m_largest_value, p_inf, "-inf", Status::OK, Category::Infinity), - (m_largest_value, m_inf, "inf", Status::OK, Category::Infinity), - (m_largest_value, p_zero, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, m_zero, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_largest_value, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (m_largest_value, p_normal_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_largest_value, m_normal_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_largest_value, p_largest_value, "-inf", overflow_status, Category::Infinity), - (m_largest_value, m_largest_value, "0x0p+0", Status::OK, Category::Zero), - (m_largest_value, p_smallest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_largest_value, m_smallest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - ( - m_largest_value, - p_smallest_normalized, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - m_largest_value, - m_smallest_normalized, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (p_smallest_value, p_inf, "-inf", Status::OK, Category::Infinity), - (p_smallest_value, m_inf, "inf", Status::OK, Category::Infinity), - (p_smallest_value, p_zero, "0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, m_zero, "0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_value, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_value, p_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (p_smallest_value, m_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - (p_smallest_value, p_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_smallest_value, m_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (p_smallest_value, p_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_value, m_smallest_value, "0x1p-148", Status::OK, Category::Normal), - (p_smallest_value, p_smallest_normalized, "-0x1.fffffcp-127", Status::OK, Category::Normal), - (p_smallest_value, m_smallest_normalized, "0x1.000002p-126", Status::OK, Category::Normal), - (m_smallest_value, p_inf, "-inf", Status::OK, Category::Infinity), - (m_smallest_value, m_inf, "inf", Status::OK, Category::Infinity), - (m_smallest_value, p_zero, "-0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, m_zero, "-0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_value, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_value, p_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_smallest_value, m_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - (m_smallest_value, p_largest_value, "-0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_smallest_value, m_largest_value, "0x1.fffffep+127", Status::INEXACT, Category::Normal), - (m_smallest_value, p_smallest_value, "-0x1p-148", Status::OK, Category::Normal), - (m_smallest_value, m_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_value, p_smallest_normalized, "-0x1.000002p-126", Status::OK, Category::Normal), - (m_smallest_value, m_smallest_normalized, "0x1.fffffcp-127", Status::OK, Category::Normal), - (p_smallest_normalized, p_inf, "-inf", Status::OK, Category::Infinity), - (p_smallest_normalized, m_inf, "inf", Status::OK, Category::Infinity), - (p_smallest_normalized, p_zero, "0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, m_zero, "0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_normalized, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_normalized, p_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (p_smallest_normalized, m_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - ( - p_smallest_normalized, - p_largest_value, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - p_smallest_normalized, - m_largest_value, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (p_smallest_normalized, p_smallest_value, "0x1.fffffcp-127", Status::OK, Category::Normal), - (p_smallest_normalized, m_smallest_value, "0x1.000002p-126", Status::OK, Category::Normal), - (p_smallest_normalized, p_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_normalized, m_smallest_normalized, "0x1p-125", Status::OK, Category::Normal), - (m_smallest_normalized, p_inf, "-inf", Status::OK, Category::Infinity), - (m_smallest_normalized, m_inf, "inf", Status::OK, Category::Infinity), - (m_smallest_normalized, p_zero, "-0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, m_zero, "-0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, qnan, "-nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_normalized, snan, "-nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_normalized, p_normal_value, "-0x1p+0", Status::INEXACT, Category::Normal), - (m_smallest_normalized, m_normal_value, "0x1p+0", Status::INEXACT, Category::Normal), - ( - m_smallest_normalized, - p_largest_value, - "-0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - ( - m_smallest_normalized, - m_largest_value, - "0x1.fffffep+127", - Status::INEXACT, - Category::Normal, - ), - (m_smallest_normalized, p_smallest_value, "-0x1.000002p-126", Status::OK, Category::Normal), - (m_smallest_normalized, m_smallest_value, "-0x1.fffffcp-127", Status::OK, Category::Normal), - (m_smallest_normalized, p_smallest_normalized, "-0x1p-125", Status::OK, Category::Normal), - (m_smallest_normalized, m_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - ]; - - for (x, y, e_result, e_status, e_category) in special_cases { - let status; - let result = unpack!(status=, x - y); - assert_eq!(status, e_status); - assert_eq!(result.category(), e_category); - assert!(result.bitwise_eq(e_result.parse::().unwrap())); - } -} - -#[test] -fn multiply() { - // Test Special Cases against each other and normal values. - - // FIXMES/NOTES: - // 1. Since we perform only default exception handling all operations with - // signaling NaNs should have a result that is a quiet NaN. Currently they - // return sNaN. - - let p_inf = Single::INFINITY; - let m_inf = -Single::INFINITY; - let p_zero = Single::ZERO; - let m_zero = -Single::ZERO; - let qnan = Single::NAN; - let p_normal_value = "0x1p+0".parse::().unwrap(); - let m_normal_value = "-0x1p+0".parse::().unwrap(); - let p_largest_value = Single::largest(); - let m_largest_value = -Single::largest(); - let p_smallest_value = Single::SMALLEST; - let m_smallest_value = -Single::SMALLEST; - let p_smallest_normalized = Single::smallest_normalized(); - let m_smallest_normalized = -Single::smallest_normalized(); - - let overflow_status = Status::OVERFLOW | Status::INEXACT; - let underflow_status = Status::UNDERFLOW | Status::INEXACT; - - let special_cases = [ - (p_inf, p_inf, "inf", Status::OK, Category::Infinity), - (p_inf, m_inf, "-inf", Status::OK, Category::Infinity), - (p_inf, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (p_inf, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (p_inf, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_inf, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_inf, p_normal_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_normal_value, "-inf", Status::OK, Category::Infinity), - (p_inf, p_largest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_largest_value, "-inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_value, "-inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_normalized, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (m_inf, p_inf, "-inf", Status::OK, Category::Infinity), - (m_inf, m_inf, "inf", Status::OK, Category::Infinity), - (m_inf, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (m_inf, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (m_inf, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_inf, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_inf, p_normal_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_normal_value, "inf", Status::OK, Category::Infinity), - (m_inf, p_largest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_largest_value, "inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_value, "inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_normalized, "inf", Status::OK, Category::Infinity), - (p_zero, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (p_zero, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (p_zero, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_zero, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_zero, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_zero, p_normal_value, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_normal_value, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_largest_value, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_largest_value, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_smallest_value, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_smallest_normalized, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (m_zero, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (m_zero, p_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_zero, "0x0p+0", Status::OK, Category::Zero), - (m_zero, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_zero, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_zero, p_normal_value, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_normal_value, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_largest_value, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_largest_value, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_smallest_value, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_smallest_normalized, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (qnan, p_inf, "nan", Status::OK, Category::NaN), - (qnan, m_inf, "nan", Status::OK, Category::NaN), - (qnan, p_zero, "nan", Status::OK, Category::NaN), - (qnan, m_zero, "nan", Status::OK, Category::NaN), - (qnan, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (qnan, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (qnan, p_normal_value, "nan", Status::OK, Category::NaN), - (qnan, m_normal_value, "nan", Status::OK, Category::NaN), - (qnan, p_largest_value, "nan", Status::OK, Category::NaN), - (qnan, m_largest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_normalized, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_normalized, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (snan, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, qnan, "nan", Status::INVALID_OP, Category::NaN), - (snan, snan, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_inf, "inf", Status::OK, Category::Infinity), - (p_normal_value, m_inf, "-inf", Status::OK, Category::Infinity), - (p_normal_value, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_normal_value, m_zero, "-0x0p+0", Status::OK, Category::Zero), - (p_normal_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_normal_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_normal_value, "0x1p+0", Status::OK, Category::Normal), - (p_normal_value, m_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (p_normal_value, p_largest_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_normal_value, m_largest_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (p_normal_value, p_smallest_value, "0x1p-149", Status::OK, Category::Normal), - (p_normal_value, m_smallest_value, "-0x1p-149", Status::OK, Category::Normal), - (p_normal_value, p_smallest_normalized, "0x1p-126", Status::OK, Category::Normal), - (p_normal_value, m_smallest_normalized, "-0x1p-126", Status::OK, Category::Normal), - (m_normal_value, p_inf, "-inf", Status::OK, Category::Infinity), - (m_normal_value, m_inf, "inf", Status::OK, Category::Infinity), - (m_normal_value, p_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_normal_value, m_zero, "0x0p+0", Status::OK, Category::Zero), - (m_normal_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_normal_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_normal_value, p_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (m_normal_value, m_normal_value, "0x1p+0", Status::OK, Category::Normal), - (m_normal_value, p_largest_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_normal_value, m_largest_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (m_normal_value, p_smallest_value, "-0x1p-149", Status::OK, Category::Normal), - (m_normal_value, m_smallest_value, "0x1p-149", Status::OK, Category::Normal), - (m_normal_value, p_smallest_normalized, "-0x1p-126", Status::OK, Category::Normal), - (m_normal_value, m_smallest_normalized, "0x1p-126", Status::OK, Category::Normal), - (p_largest_value, p_inf, "inf", Status::OK, Category::Infinity), - (p_largest_value, m_inf, "-inf", Status::OK, Category::Infinity), - (p_largest_value, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_largest_value, m_zero, "-0x0p+0", Status::OK, Category::Zero), - (p_largest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_largest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_largest_value, p_normal_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, m_normal_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, p_largest_value, "inf", overflow_status, Category::Infinity), - (p_largest_value, m_largest_value, "-inf", overflow_status, Category::Infinity), - (p_largest_value, p_smallest_value, "0x1.fffffep-22", Status::OK, Category::Normal), - (p_largest_value, m_smallest_value, "-0x1.fffffep-22", Status::OK, Category::Normal), - (p_largest_value, p_smallest_normalized, "0x1.fffffep+1", Status::OK, Category::Normal), - (p_largest_value, m_smallest_normalized, "-0x1.fffffep+1", Status::OK, Category::Normal), - (m_largest_value, p_inf, "-inf", Status::OK, Category::Infinity), - (m_largest_value, m_inf, "inf", Status::OK, Category::Infinity), - (m_largest_value, p_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_largest_value, m_zero, "0x0p+0", Status::OK, Category::Zero), - (m_largest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_largest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_largest_value, p_normal_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, m_normal_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, p_largest_value, "-inf", overflow_status, Category::Infinity), - (m_largest_value, m_largest_value, "inf", overflow_status, Category::Infinity), - (m_largest_value, p_smallest_value, "-0x1.fffffep-22", Status::OK, Category::Normal), - (m_largest_value, m_smallest_value, "0x1.fffffep-22", Status::OK, Category::Normal), - (m_largest_value, p_smallest_normalized, "-0x1.fffffep+1", Status::OK, Category::Normal), - (m_largest_value, m_smallest_normalized, "0x1.fffffep+1", Status::OK, Category::Normal), - (p_smallest_value, p_inf, "inf", Status::OK, Category::Infinity), - (p_smallest_value, m_inf, "-inf", Status::OK, Category::Infinity), - (p_smallest_value, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_value, m_zero, "-0x0p+0", Status::OK, Category::Zero), - (p_smallest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_value, p_normal_value, "0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, m_normal_value, "-0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, p_largest_value, "0x1.fffffep-22", Status::OK, Category::Normal), - (p_smallest_value, m_largest_value, "-0x1.fffffep-22", Status::OK, Category::Normal), - (p_smallest_value, p_smallest_value, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_value, m_smallest_value, "-0x0p+0", underflow_status, Category::Zero), - (p_smallest_value, p_smallest_normalized, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_value, m_smallest_normalized, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_value, p_inf, "-inf", Status::OK, Category::Infinity), - (m_smallest_value, m_inf, "inf", Status::OK, Category::Infinity), - (m_smallest_value, p_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_smallest_value, m_zero, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_value, p_normal_value, "-0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, m_normal_value, "0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, p_largest_value, "-0x1.fffffep-22", Status::OK, Category::Normal), - (m_smallest_value, m_largest_value, "0x1.fffffep-22", Status::OK, Category::Normal), - (m_smallest_value, p_smallest_value, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_value, m_smallest_value, "0x0p+0", underflow_status, Category::Zero), - (m_smallest_value, p_smallest_normalized, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_value, m_smallest_normalized, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_normalized, p_inf, "inf", Status::OK, Category::Infinity), - (p_smallest_normalized, m_inf, "-inf", Status::OK, Category::Infinity), - (p_smallest_normalized, p_zero, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_normalized, m_zero, "-0x0p+0", Status::OK, Category::Zero), - (p_smallest_normalized, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_normalized, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_normalized, p_normal_value, "0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, m_normal_value, "-0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, p_largest_value, "0x1.fffffep+1", Status::OK, Category::Normal), - (p_smallest_normalized, m_largest_value, "-0x1.fffffep+1", Status::OK, Category::Normal), - (p_smallest_normalized, p_smallest_value, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_normalized, m_smallest_value, "-0x0p+0", underflow_status, Category::Zero), - (p_smallest_normalized, p_smallest_normalized, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_normalized, m_smallest_normalized, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_normalized, p_inf, "-inf", Status::OK, Category::Infinity), - (m_smallest_normalized, m_inf, "inf", Status::OK, Category::Infinity), - (m_smallest_normalized, p_zero, "-0x0p+0", Status::OK, Category::Zero), - (m_smallest_normalized, m_zero, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_normalized, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_normalized, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_normalized, p_normal_value, "-0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, m_normal_value, "0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, p_largest_value, "-0x1.fffffep+1", Status::OK, Category::Normal), - (m_smallest_normalized, m_largest_value, "0x1.fffffep+1", Status::OK, Category::Normal), - (m_smallest_normalized, p_smallest_value, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_normalized, m_smallest_value, "0x0p+0", underflow_status, Category::Zero), - (m_smallest_normalized, p_smallest_normalized, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_normalized, m_smallest_normalized, "0x0p+0", underflow_status, Category::Zero), - ]; - - for (x, y, e_result, e_status, e_category) in special_cases { - let status; - let result = unpack!(status=, x * y); - assert_eq!(status, e_status); - assert_eq!(result.category(), e_category); - assert!(result.bitwise_eq(e_result.parse::().unwrap())); - } -} - -#[test] -fn divide() { - // Test Special Cases against each other and normal values. - - // FIXMES/NOTES: - // 1. Since we perform only default exception handling all operations with - // signaling NaNs should have a result that is a quiet NaN. Currently they - // return sNaN. - - let p_inf = Single::INFINITY; - let m_inf = -Single::INFINITY; - let p_zero = Single::ZERO; - let m_zero = -Single::ZERO; - let qnan = Single::NAN; - let p_normal_value = "0x1p+0".parse::().unwrap(); - let m_normal_value = "-0x1p+0".parse::().unwrap(); - let p_largest_value = Single::largest(); - let m_largest_value = -Single::largest(); - let p_smallest_value = Single::SMALLEST; - let m_smallest_value = -Single::SMALLEST; - let p_smallest_normalized = Single::smallest_normalized(); - let m_smallest_normalized = -Single::smallest_normalized(); - - let overflow_status = Status::OVERFLOW | Status::INEXACT; - let underflow_status = Status::UNDERFLOW | Status::INEXACT; - - let special_cases = [ - (p_inf, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (p_inf, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (p_inf, p_zero, "inf", Status::OK, Category::Infinity), - (p_inf, m_zero, "-inf", Status::OK, Category::Infinity), - (p_inf, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_inf, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_inf, p_normal_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_normal_value, "-inf", Status::OK, Category::Infinity), - (p_inf, p_largest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_largest_value, "-inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_value, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_value, "-inf", Status::OK, Category::Infinity), - (p_inf, p_smallest_normalized, "inf", Status::OK, Category::Infinity), - (p_inf, m_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (m_inf, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (m_inf, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (m_inf, p_zero, "-inf", Status::OK, Category::Infinity), - (m_inf, m_zero, "inf", Status::OK, Category::Infinity), - (m_inf, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_inf, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_inf, p_normal_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_normal_value, "inf", Status::OK, Category::Infinity), - (m_inf, p_largest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_largest_value, "inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_value, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_value, "inf", Status::OK, Category::Infinity), - (m_inf, p_smallest_normalized, "-inf", Status::OK, Category::Infinity), - (m_inf, m_smallest_normalized, "inf", Status::OK, Category::Infinity), - (p_zero, p_inf, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_inf, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (p_zero, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (p_zero, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_zero, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_zero, p_normal_value, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_normal_value, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_largest_value, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_largest_value, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_smallest_value, "-0x0p+0", Status::OK, Category::Zero), - (p_zero, p_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (p_zero, m_smallest_normalized, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, p_inf, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_inf, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (m_zero, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (m_zero, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_zero, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_zero, p_normal_value, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_normal_value, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_largest_value, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_largest_value, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_smallest_value, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_smallest_value, "0x0p+0", Status::OK, Category::Zero), - (m_zero, p_smallest_normalized, "-0x0p+0", Status::OK, Category::Zero), - (m_zero, m_smallest_normalized, "0x0p+0", Status::OK, Category::Zero), - (qnan, p_inf, "nan", Status::OK, Category::NaN), - (qnan, m_inf, "nan", Status::OK, Category::NaN), - (qnan, p_zero, "nan", Status::OK, Category::NaN), - (qnan, m_zero, "nan", Status::OK, Category::NaN), - (qnan, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (qnan, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (qnan, p_normal_value, "nan", Status::OK, Category::NaN), - (qnan, m_normal_value, "nan", Status::OK, Category::NaN), - (qnan, p_largest_value, "nan", Status::OK, Category::NaN), - (qnan, m_largest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_value, "nan", Status::OK, Category::NaN), - (qnan, p_smallest_normalized, "nan", Status::OK, Category::NaN), - (qnan, m_smallest_normalized, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (snan, p_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_inf, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_zero, "nan", Status::INVALID_OP, Category::NaN), - (snan, qnan, "nan", Status::INVALID_OP, Category::NaN), - (snan, snan, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_normal_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_largest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_value, "nan", Status::INVALID_OP, Category::NaN), - (snan, p_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - (snan, m_smallest_normalized, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_inf, "0x0p+0", Status::OK, Category::Zero), - (p_normal_value, m_inf, "-0x0p+0", Status::OK, Category::Zero), - (p_normal_value, p_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_normal_value, m_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_normal_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_normal_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_normal_value, p_normal_value, "0x1p+0", Status::OK, Category::Normal), - (p_normal_value, m_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (p_normal_value, p_largest_value, "0x1p-128", underflow_status, Category::Normal), - (p_normal_value, m_largest_value, "-0x1p-128", underflow_status, Category::Normal), - (p_normal_value, p_smallest_value, "inf", overflow_status, Category::Infinity), - (p_normal_value, m_smallest_value, "-inf", overflow_status, Category::Infinity), - (p_normal_value, p_smallest_normalized, "0x1p+126", Status::OK, Category::Normal), - (p_normal_value, m_smallest_normalized, "-0x1p+126", Status::OK, Category::Normal), - (m_normal_value, p_inf, "-0x0p+0", Status::OK, Category::Zero), - (m_normal_value, m_inf, "0x0p+0", Status::OK, Category::Zero), - (m_normal_value, p_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_normal_value, m_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_normal_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_normal_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_normal_value, p_normal_value, "-0x1p+0", Status::OK, Category::Normal), - (m_normal_value, m_normal_value, "0x1p+0", Status::OK, Category::Normal), - (m_normal_value, p_largest_value, "-0x1p-128", underflow_status, Category::Normal), - (m_normal_value, m_largest_value, "0x1p-128", underflow_status, Category::Normal), - (m_normal_value, p_smallest_value, "-inf", overflow_status, Category::Infinity), - (m_normal_value, m_smallest_value, "inf", overflow_status, Category::Infinity), - (m_normal_value, p_smallest_normalized, "-0x1p+126", Status::OK, Category::Normal), - (m_normal_value, m_smallest_normalized, "0x1p+126", Status::OK, Category::Normal), - (p_largest_value, p_inf, "0x0p+0", Status::OK, Category::Zero), - (p_largest_value, m_inf, "-0x0p+0", Status::OK, Category::Zero), - (p_largest_value, p_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_largest_value, m_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_largest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_largest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_largest_value, p_normal_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, m_normal_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (p_largest_value, p_largest_value, "0x1p+0", Status::OK, Category::Normal), - (p_largest_value, m_largest_value, "-0x1p+0", Status::OK, Category::Normal), - (p_largest_value, p_smallest_value, "inf", overflow_status, Category::Infinity), - (p_largest_value, m_smallest_value, "-inf", overflow_status, Category::Infinity), - (p_largest_value, p_smallest_normalized, "inf", overflow_status, Category::Infinity), - (p_largest_value, m_smallest_normalized, "-inf", overflow_status, Category::Infinity), - (m_largest_value, p_inf, "-0x0p+0", Status::OK, Category::Zero), - (m_largest_value, m_inf, "0x0p+0", Status::OK, Category::Zero), - (m_largest_value, p_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_largest_value, m_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_largest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_largest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_largest_value, p_normal_value, "-0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, m_normal_value, "0x1.fffffep+127", Status::OK, Category::Normal), - (m_largest_value, p_largest_value, "-0x1p+0", Status::OK, Category::Normal), - (m_largest_value, m_largest_value, "0x1p+0", Status::OK, Category::Normal), - (m_largest_value, p_smallest_value, "-inf", overflow_status, Category::Infinity), - (m_largest_value, m_smallest_value, "inf", overflow_status, Category::Infinity), - (m_largest_value, p_smallest_normalized, "-inf", overflow_status, Category::Infinity), - (m_largest_value, m_smallest_normalized, "inf", overflow_status, Category::Infinity), - (p_smallest_value, p_inf, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_value, m_inf, "-0x0p+0", Status::OK, Category::Zero), - (p_smallest_value, p_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_smallest_value, m_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_smallest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_value, p_normal_value, "0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, m_normal_value, "-0x1p-149", Status::OK, Category::Normal), - (p_smallest_value, p_largest_value, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_value, m_largest_value, "-0x0p+0", underflow_status, Category::Zero), - (p_smallest_value, p_smallest_value, "0x1p+0", Status::OK, Category::Normal), - (p_smallest_value, m_smallest_value, "-0x1p+0", Status::OK, Category::Normal), - (p_smallest_value, p_smallest_normalized, "0x1p-23", Status::OK, Category::Normal), - (p_smallest_value, m_smallest_normalized, "-0x1p-23", Status::OK, Category::Normal), - (m_smallest_value, p_inf, "-0x0p+0", Status::OK, Category::Zero), - (m_smallest_value, m_inf, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_value, p_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_smallest_value, m_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_smallest_value, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_value, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_value, p_normal_value, "-0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, m_normal_value, "0x1p-149", Status::OK, Category::Normal), - (m_smallest_value, p_largest_value, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_value, m_largest_value, "0x0p+0", underflow_status, Category::Zero), - (m_smallest_value, p_smallest_value, "-0x1p+0", Status::OK, Category::Normal), - (m_smallest_value, m_smallest_value, "0x1p+0", Status::OK, Category::Normal), - (m_smallest_value, p_smallest_normalized, "-0x1p-23", Status::OK, Category::Normal), - (m_smallest_value, m_smallest_normalized, "0x1p-23", Status::OK, Category::Normal), - (p_smallest_normalized, p_inf, "0x0p+0", Status::OK, Category::Zero), - (p_smallest_normalized, m_inf, "-0x0p+0", Status::OK, Category::Zero), - (p_smallest_normalized, p_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_smallest_normalized, m_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (p_smallest_normalized, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (p_smallest_normalized, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (p_smallest_normalized, p_normal_value, "0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, m_normal_value, "-0x1p-126", Status::OK, Category::Normal), - (p_smallest_normalized, p_largest_value, "0x0p+0", underflow_status, Category::Zero), - (p_smallest_normalized, m_largest_value, "-0x0p+0", underflow_status, Category::Zero), - (p_smallest_normalized, p_smallest_value, "0x1p+23", Status::OK, Category::Normal), - (p_smallest_normalized, m_smallest_value, "-0x1p+23", Status::OK, Category::Normal), - (p_smallest_normalized, p_smallest_normalized, "0x1p+0", Status::OK, Category::Normal), - (p_smallest_normalized, m_smallest_normalized, "-0x1p+0", Status::OK, Category::Normal), - (m_smallest_normalized, p_inf, "-0x0p+0", Status::OK, Category::Zero), - (m_smallest_normalized, m_inf, "0x0p+0", Status::OK, Category::Zero), - (m_smallest_normalized, p_zero, "-inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_smallest_normalized, m_zero, "inf", Status::DIV_BY_ZERO, Category::Infinity), - (m_smallest_normalized, qnan, "nan", Status::OK, Category::NaN), - /* - // See Note 1. - (m_smallest_normalized, snan, "nan", Status::INVALID_OP, Category::NaN), - */ - (m_smallest_normalized, p_normal_value, "-0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, m_normal_value, "0x1p-126", Status::OK, Category::Normal), - (m_smallest_normalized, p_largest_value, "-0x0p+0", underflow_status, Category::Zero), - (m_smallest_normalized, m_largest_value, "0x0p+0", underflow_status, Category::Zero), - (m_smallest_normalized, p_smallest_value, "-0x1p+23", Status::OK, Category::Normal), - (m_smallest_normalized, m_smallest_value, "0x1p+23", Status::OK, Category::Normal), - (m_smallest_normalized, p_smallest_normalized, "-0x1p+0", Status::OK, Category::Normal), - (m_smallest_normalized, m_smallest_normalized, "0x1p+0", Status::OK, Category::Normal), - ]; - - for (x, y, e_result, e_status, e_category) in special_cases { - let status; - let result = unpack!(status=, x / y); - assert_eq!(status, e_status); - assert_eq!(result.category(), e_category); - assert!(result.bitwise_eq(e_result.parse::().unwrap())); - } -} - -#[test] -fn operator_overloads() { - // This is mostly testing that these operator overloads compile. - let one = "0x1p+0".parse::().unwrap(); - let two = "0x2p+0".parse::().unwrap(); - assert!(two.bitwise_eq((one + one).value)); - assert!(one.bitwise_eq((two - one).value)); - assert!(two.bitwise_eq((one * two).value)); - assert!(one.bitwise_eq((two / two).value)); -} - -#[test] -fn abs() { - let p_inf = Single::INFINITY; - let m_inf = -Single::INFINITY; - let p_zero = Single::ZERO; - let m_zero = -Single::ZERO; - let p_qnan = Single::NAN; - let m_qnan = -Single::NAN; - let p_snan = Single::snan(None); - let m_snan = -Single::snan(None); - let p_normal_value = "0x1p+0".parse::().unwrap(); - let m_normal_value = "-0x1p+0".parse::().unwrap(); - let p_largest_value = Single::largest(); - let m_largest_value = -Single::largest(); - let p_smallest_value = Single::SMALLEST; - let m_smallest_value = -Single::SMALLEST; - let p_smallest_normalized = Single::smallest_normalized(); - let m_smallest_normalized = -Single::smallest_normalized(); - - assert!(p_inf.bitwise_eq(p_inf.abs())); - assert!(p_inf.bitwise_eq(m_inf.abs())); - assert!(p_zero.bitwise_eq(p_zero.abs())); - assert!(p_zero.bitwise_eq(m_zero.abs())); - assert!(p_qnan.bitwise_eq(p_qnan.abs())); - assert!(p_qnan.bitwise_eq(m_qnan.abs())); - assert!(p_snan.bitwise_eq(p_snan.abs())); - assert!(p_snan.bitwise_eq(m_snan.abs())); - assert!(p_normal_value.bitwise_eq(p_normal_value.abs())); - assert!(p_normal_value.bitwise_eq(m_normal_value.abs())); - assert!(p_largest_value.bitwise_eq(p_largest_value.abs())); - assert!(p_largest_value.bitwise_eq(m_largest_value.abs())); - assert!(p_smallest_value.bitwise_eq(p_smallest_value.abs())); - assert!(p_smallest_value.bitwise_eq(m_smallest_value.abs())); - assert!(p_smallest_normalized.bitwise_eq(p_smallest_normalized.abs(),)); - assert!(p_smallest_normalized.bitwise_eq(m_smallest_normalized.abs(),)); -} - -#[test] -fn neg() { - let one = "1.0".parse::().unwrap(); - let neg_one = "-1.0".parse::().unwrap(); - let zero = Single::ZERO; - let neg_zero = -Single::ZERO; - let inf = Single::INFINITY; - let neg_inf = -Single::INFINITY; - let qnan = Single::NAN; - let neg_qnan = -Single::NAN; - - assert!(neg_one.bitwise_eq(-one)); - assert!(one.bitwise_eq(-neg_one)); - assert!(neg_zero.bitwise_eq(-zero)); - assert!(zero.bitwise_eq(-neg_zero)); - assert!(neg_inf.bitwise_eq(-inf)); - assert!(inf.bitwise_eq(-neg_inf)); - assert!(neg_inf.bitwise_eq(-inf)); - assert!(inf.bitwise_eq(-neg_inf)); - assert!(neg_qnan.bitwise_eq(-qnan)); - assert!(qnan.bitwise_eq(-neg_qnan)); -} - -#[test] -fn ilogb() { - assert_eq!(-1074, Double::SMALLEST.ilogb()); - assert_eq!(-1074, (-Double::SMALLEST).ilogb()); - assert_eq!(-1023, "0x1.ffffffffffffep-1024".parse::().unwrap().ilogb()); - assert_eq!(-1023, "0x1.ffffffffffffep-1023".parse::().unwrap().ilogb()); - assert_eq!(-1023, "-0x1.ffffffffffffep-1023".parse::().unwrap().ilogb()); - assert_eq!(-51, "0x1p-51".parse::().unwrap().ilogb()); - assert_eq!(-1023, "0x1.c60f120d9f87cp-1023".parse::().unwrap().ilogb()); - assert_eq!(-2, "0x0.ffffp-1".parse::().unwrap().ilogb()); - assert_eq!(-1023, "0x1.fffep-1023".parse::().unwrap().ilogb()); - assert_eq!(1023, Double::largest().ilogb()); - assert_eq!(1023, (-Double::largest()).ilogb()); - - assert_eq!(0, "0x1p+0".parse::().unwrap().ilogb()); - assert_eq!(0, "-0x1p+0".parse::().unwrap().ilogb()); - assert_eq!(42, "0x1p+42".parse::().unwrap().ilogb()); - assert_eq!(-42, "0x1p-42".parse::().unwrap().ilogb()); - - assert_eq!(IEK_INF, Single::INFINITY.ilogb()); - assert_eq!(IEK_INF, (-Single::INFINITY).ilogb()); - assert_eq!(IEK_ZERO, Single::ZERO.ilogb()); - assert_eq!(IEK_ZERO, (-Single::ZERO).ilogb()); - assert_eq!(IEK_NAN, Single::NAN.ilogb()); - assert_eq!(IEK_NAN, Single::snan(None).ilogb()); - - assert_eq!(127, Single::largest().ilogb()); - assert_eq!(127, (-Single::largest()).ilogb()); - - assert_eq!(-149, Single::SMALLEST.ilogb()); - assert_eq!(-149, (-Single::SMALLEST).ilogb()); - assert_eq!(-126, Single::smallest_normalized().ilogb()); - assert_eq!(-126, (-Single::smallest_normalized()).ilogb()); -} - -#[test] -fn scalbn() { - assert!( - "0x1p+0" - .parse::() - .unwrap() - .bitwise_eq("0x1p+0".parse::().unwrap().scalbn(0),) - ); - assert!( - "0x1p+42" - .parse::() - .unwrap() - .bitwise_eq("0x1p+0".parse::().unwrap().scalbn(42),) - ); - assert!( - "0x1p-42" - .parse::() - .unwrap() - .bitwise_eq("0x1p+0".parse::().unwrap().scalbn(-42),) - ); - - let p_inf = Single::INFINITY; - let m_inf = -Single::INFINITY; - let p_zero = Single::ZERO; - let m_zero = -Single::ZERO; - let p_qnan = Single::NAN; - let m_qnan = -Single::NAN; - let snan = Single::snan(None); - - assert!(p_inf.bitwise_eq(p_inf.scalbn(0))); - assert!(m_inf.bitwise_eq(m_inf.scalbn(0))); - assert!(p_zero.bitwise_eq(p_zero.scalbn(0))); - assert!(m_zero.bitwise_eq(m_zero.scalbn(0))); - assert!(p_qnan.bitwise_eq(p_qnan.scalbn(0))); - assert!(m_qnan.bitwise_eq(m_qnan.scalbn(0))); - assert!(!snan.scalbn(0).is_signaling()); - - let scalbn_snan = snan.scalbn(1); - assert!(scalbn_snan.is_nan() && !scalbn_snan.is_signaling()); - - // Make sure highest bit of payload is preserved. - let payload = (1 << 50) | (1 << 49) | (1234 << 32) | 1; - - let snan_with_payload = Double::snan(Some(payload)); - let quiet_payload = snan_with_payload.scalbn(1); - assert!(quiet_payload.is_nan() && !quiet_payload.is_signaling()); - assert_eq!(payload, quiet_payload.to_bits() & ((1 << 51) - 1)); - - assert!(p_inf.bitwise_eq("0x1p+0".parse::().unwrap().scalbn(128),)); - assert!(m_inf.bitwise_eq("-0x1p+0".parse::().unwrap().scalbn(128),)); - assert!(p_inf.bitwise_eq("0x1p+127".parse::().unwrap().scalbn(1),)); - assert!(p_zero.bitwise_eq("0x1p-127".parse::().unwrap().scalbn(-127),)); - assert!(m_zero.bitwise_eq("-0x1p-127".parse::().unwrap().scalbn(-127),)); - assert!( - "-0x1p-149" - .parse::() - .unwrap() - .bitwise_eq("-0x1p-127".parse::().unwrap().scalbn(-22),) - ); - assert!(p_zero.bitwise_eq("0x1p-126".parse::().unwrap().scalbn(-24),)); - - let smallest_f64 = Double::SMALLEST; - let neg_smallest_f64 = -Double::SMALLEST; - - let largest_f64 = Double::largest(); - let neg_largest_f64 = -Double::largest(); - - let largest_denormal_f64 = "0x1.ffffffffffffep-1023".parse::().unwrap(); - let neg_largest_denormal_f64 = "-0x1.ffffffffffffep-1023".parse::().unwrap(); - - assert!(smallest_f64.bitwise_eq("0x1p-1074".parse::().unwrap().scalbn(0),)); - assert!(neg_smallest_f64.bitwise_eq("-0x1p-1074".parse::().unwrap().scalbn(0),)); - - assert!("0x1p+1023".parse::().unwrap().bitwise_eq(smallest_f64.scalbn(2097,),)); - - assert!(smallest_f64.scalbn(-2097).is_pos_zero()); - assert!(smallest_f64.scalbn(-2098).is_pos_zero()); - assert!(smallest_f64.scalbn(-2099).is_pos_zero()); - assert!("0x1p+1022".parse::().unwrap().bitwise_eq(smallest_f64.scalbn(2096,),)); - assert!("0x1p+1023".parse::().unwrap().bitwise_eq(smallest_f64.scalbn(2097,),)); - assert!(smallest_f64.scalbn(2098).is_infinite()); - assert!(smallest_f64.scalbn(2099).is_infinite()); - - // Test for integer overflows when adding to exponent. - assert!(smallest_f64.scalbn(-ExpInt::MAX).is_pos_zero()); - assert!(largest_f64.scalbn(ExpInt::MAX).is_infinite()); - - assert!(largest_denormal_f64.bitwise_eq(largest_denormal_f64.scalbn(0),)); - assert!(neg_largest_denormal_f64.bitwise_eq(neg_largest_denormal_f64.scalbn(0),)); - - assert!( - "0x1.ffffffffffffep-1022" - .parse::() - .unwrap() - .bitwise_eq(largest_denormal_f64.scalbn(1)) - ); - assert!( - "-0x1.ffffffffffffep-1021" - .parse::() - .unwrap() - .bitwise_eq(neg_largest_denormal_f64.scalbn(2)) - ); - - assert!( - "0x1.ffffffffffffep+1" - .parse::() - .unwrap() - .bitwise_eq(largest_denormal_f64.scalbn(1024)) - ); - assert!(largest_denormal_f64.scalbn(-1023).is_pos_zero()); - assert!(largest_denormal_f64.scalbn(-1024).is_pos_zero()); - assert!(largest_denormal_f64.scalbn(-2048).is_pos_zero()); - assert!(largest_denormal_f64.scalbn(2047).is_infinite()); - assert!(largest_denormal_f64.scalbn(2098).is_infinite()); - assert!(largest_denormal_f64.scalbn(2099).is_infinite()); - - assert!( - "0x1.ffffffffffffep-2" - .parse::() - .unwrap() - .bitwise_eq(largest_denormal_f64.scalbn(1021)) - ); - assert!( - "0x1.ffffffffffffep-1" - .parse::() - .unwrap() - .bitwise_eq(largest_denormal_f64.scalbn(1022)) - ); - assert!( - "0x1.ffffffffffffep+0" - .parse::() - .unwrap() - .bitwise_eq(largest_denormal_f64.scalbn(1023)) - ); - assert!( - "0x1.ffffffffffffep+1023" - .parse::() - .unwrap() - .bitwise_eq(largest_denormal_f64.scalbn(2046)) - ); - assert!("0x1p+974".parse::().unwrap().bitwise_eq(smallest_f64.scalbn(2048,),)); - - let random_denormal_f64 = "0x1.c60f120d9f87cp+51".parse::().unwrap(); - assert!( - "0x1.c60f120d9f87cp-972" - .parse::() - .unwrap() - .bitwise_eq(random_denormal_f64.scalbn(-1023)) - ); - assert!( - "0x1.c60f120d9f87cp-1" - .parse::() - .unwrap() - .bitwise_eq(random_denormal_f64.scalbn(-52)) - ); - assert!( - "0x1.c60f120d9f87cp-2" - .parse::() - .unwrap() - .bitwise_eq(random_denormal_f64.scalbn(-53)) - ); - assert!( - "0x1.c60f120d9f87cp+0" - .parse::() - .unwrap() - .bitwise_eq(random_denormal_f64.scalbn(-51)) - ); - - assert!(random_denormal_f64.scalbn(-2097).is_pos_zero()); - assert!(random_denormal_f64.scalbn(-2090).is_pos_zero()); - - assert!("-0x1p-1073".parse::().unwrap().bitwise_eq(neg_largest_f64.scalbn(-2097),)); - - assert!("-0x1p-1024".parse::().unwrap().bitwise_eq(neg_largest_f64.scalbn(-2048),)); - - assert!("0x1p-1073".parse::().unwrap().bitwise_eq(largest_f64.scalbn(-2097,),)); - - assert!("0x1p-1074".parse::().unwrap().bitwise_eq(largest_f64.scalbn(-2098,),)); - assert!("-0x1p-1074".parse::().unwrap().bitwise_eq(neg_largest_f64.scalbn(-2098),)); - assert!(neg_largest_f64.scalbn(-2099).is_neg_zero()); - assert!(largest_f64.scalbn(1).is_infinite()); - - assert!( - "0x1p+0" - .parse::() - .unwrap() - .bitwise_eq("0x1p+52".parse::().unwrap().scalbn(-52),) - ); - - assert!( - "0x1p-103" - .parse::() - .unwrap() - .bitwise_eq("0x1p-51".parse::().unwrap().scalbn(-52),) - ); -} - -#[test] -fn frexp() { - let p_zero = Double::ZERO; - let m_zero = -Double::ZERO; - let one = Double::from_f64(1.0); - let m_one = Double::from_f64(-1.0); - - let largest_denormal = "0x1.ffffffffffffep-1023".parse::().unwrap(); - let neg_largest_denormal = "-0x1.ffffffffffffep-1023".parse::().unwrap(); - - let smallest = Double::SMALLEST; - let neg_smallest = -Double::SMALLEST; - - let largest = Double::largest(); - let neg_largest = -Double::largest(); - - let p_inf = Double::INFINITY; - let m_inf = -Double::INFINITY; - - let p_qnan = Double::NAN; - let m_qnan = -Double::NAN; - let snan = Double::snan(None); - - // Make sure highest bit of payload is preserved. - let payload = (1 << 50) | (1 << 49) | (1234 << 32) | 1; - - let snan_with_payload = Double::snan(Some(payload)); - - let mut exp = 0; - - let frac = p_zero.frexp(&mut exp); - assert_eq!(0, exp); - assert!(frac.is_pos_zero()); - - let frac = m_zero.frexp(&mut exp); - assert_eq!(0, exp); - assert!(frac.is_neg_zero()); - - let frac = one.frexp(&mut exp); - assert_eq!(1, exp); - assert!("0x1p-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = m_one.frexp(&mut exp); - assert_eq!(1, exp); - assert!("-0x1p-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = largest_denormal.frexp(&mut exp); - assert_eq!(-1022, exp); - assert!("0x1.ffffffffffffep-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = neg_largest_denormal.frexp(&mut exp); - assert_eq!(-1022, exp); - assert!("-0x1.ffffffffffffep-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = smallest.frexp(&mut exp); - assert_eq!(-1073, exp); - assert!("0x1p-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = neg_smallest.frexp(&mut exp); - assert_eq!(-1073, exp); - assert!("-0x1p-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = largest.frexp(&mut exp); - assert_eq!(1024, exp); - assert!("0x1.fffffffffffffp-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = neg_largest.frexp(&mut exp); - assert_eq!(1024, exp); - assert!("-0x1.fffffffffffffp-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = p_inf.frexp(&mut exp); - assert_eq!(IEK_INF, exp); - assert!(frac.is_infinite() && !frac.is_negative()); - - let frac = m_inf.frexp(&mut exp); - assert_eq!(IEK_INF, exp); - assert!(frac.is_infinite() && frac.is_negative()); - - let frac = p_qnan.frexp(&mut exp); - assert_eq!(IEK_NAN, exp); - assert!(frac.is_nan()); - - let frac = m_qnan.frexp(&mut exp); - assert_eq!(IEK_NAN, exp); - assert!(frac.is_nan()); - - let frac = snan.frexp(&mut exp); - assert_eq!(IEK_NAN, exp); - assert!(frac.is_nan() && !frac.is_signaling()); - - let frac = snan_with_payload.frexp(&mut exp); - assert_eq!(IEK_NAN, exp); - assert!(frac.is_nan() && !frac.is_signaling()); - assert_eq!(payload, frac.to_bits() & ((1 << 51) - 1)); - - let frac = "0x0.ffffp-1".parse::().unwrap().frexp(&mut exp); - assert_eq!(-1, exp); - assert!("0x1.fffep-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = "0x1p-51".parse::().unwrap().frexp(&mut exp); - assert_eq!(-50, exp); - assert!("0x1p-1".parse::().unwrap().bitwise_eq(frac)); - - let frac = "0x1.c60f120d9f87cp+51".parse::().unwrap().frexp(&mut exp); - assert_eq!(52, exp); - assert!("0x1.c60f120d9f87cp-1".parse::().unwrap().bitwise_eq(frac)); -} - -#[test] -fn modulo() { - let mut status; - { - let f1 = "1.5".parse::().unwrap(); - let f2 = "1.0".parse::().unwrap(); - let expected = "0.5".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).bitwise_eq(expected)); - assert_eq!(status, Status::OK); - } - { - let f1 = "0.5".parse::().unwrap(); - let f2 = "1.0".parse::().unwrap(); - let expected = "0.5".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).bitwise_eq(expected)); - assert_eq!(status, Status::OK); - } - { - let f1 = "0x1.3333333333333p-2".parse::().unwrap(); // 0.3 - let f2 = "0x1.47ae147ae147bp-7".parse::().unwrap(); // 0.01 - // 0.009999999999999983 - let expected = "0x1.47ae147ae1471p-7".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).bitwise_eq(expected)); - assert_eq!(status, Status::OK); - } - { - let f1 = "0x1p64".parse::().unwrap(); // 1.8446744073709552e19 - let f2 = "1.5".parse::().unwrap(); - let expected = "1.0".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).bitwise_eq(expected)); - assert_eq!(status, Status::OK); - } - { - let f1 = "0x1p1000".parse::().unwrap(); - let f2 = "0x1p-1000".parse::().unwrap(); - let expected = "0.0".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).bitwise_eq(expected)); - assert_eq!(status, Status::OK); - } - { - let f1 = "0.0".parse::().unwrap(); - let f2 = "1.0".parse::().unwrap(); - let expected = "0.0".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).bitwise_eq(expected)); - assert_eq!(status, Status::OK); - } - { - let f1 = "1.0".parse::().unwrap(); - let f2 = "0.0".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).is_nan()); - assert_eq!(status, Status::INVALID_OP); - } - { - let f1 = "0.0".parse::().unwrap(); - let f2 = "0.0".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).is_nan()); - assert_eq!(status, Status::INVALID_OP); - } - { - let f1 = Double::INFINITY; - let f2 = "1.0".parse::().unwrap(); - assert!(unpack!(status=, f1 % f2).is_nan()); - assert_eq!(status, Status::INVALID_OP); - } -} diff --git a/compiler/rustc_apfloat/tests/ppc.rs b/compiler/rustc_apfloat/tests/ppc.rs deleted file mode 100644 index c769d26543786..0000000000000 --- a/compiler/rustc_apfloat/tests/ppc.rs +++ /dev/null @@ -1,530 +0,0 @@ -use rustc_apfloat::ppc::DoubleDouble; -use rustc_apfloat::{Category, Float, Round}; - -use std::cmp::Ordering; - -#[test] -fn ppc_double_double() { - let test = DoubleDouble::ZERO; - let expected = "0x0p+0".parse::().unwrap(); - assert!(test.is_zero()); - assert!(!test.is_negative()); - assert!(test.bitwise_eq(expected)); - assert_eq!(0, test.to_bits()); - - let test = -DoubleDouble::ZERO; - let expected = "-0x0p+0".parse::().unwrap(); - assert!(test.is_zero()); - assert!(test.is_negative()); - assert!(test.bitwise_eq(expected)); - assert_eq!(0x8000000000000000, test.to_bits()); - - let test = "1.0".parse::().unwrap(); - assert_eq!(0x3ff0000000000000, test.to_bits()); - - // LDBL_MAX - let test = "1.79769313486231580793728971405301e+308".parse::().unwrap(); - assert_eq!(0x7c8ffffffffffffe_7fefffffffffffff, test.to_bits()); - - // LDBL_MIN - let test = "2.00416836000897277799610805135016e-292".parse::().unwrap(); - assert_eq!(0x0000000000000000_0360000000000000, test.to_bits()); -} - -#[test] -fn ppc_double_double_add_special() { - let data = [ - // (1 + 0) + (-1 + 0) = Category::Zero - (0x3ff0000000000000, 0xbff0000000000000, Category::Zero, Round::NearestTiesToEven), - // LDBL_MAX + (1.1 >> (1023 - 106) + 0)) = Category::Infinity - ( - 0x7c8ffffffffffffe_7fefffffffffffff, - 0x7948000000000000, - Category::Infinity, - Round::NearestTiesToEven, - ), - // FIXME: change the 4th 0x75effffffffffffe to 0x75efffffffffffff when - // DoubleDouble's fallback is gone. - // LDBL_MAX + (1.011111... >> (1023 - 106) + (1.1111111...0 >> (1023 - - // 160))) = Category::Normal - ( - 0x7c8ffffffffffffe_7fefffffffffffff, - 0x75effffffffffffe_7947ffffffffffff, - Category::Normal, - Round::NearestTiesToEven, - ), - // LDBL_MAX + (1.1 >> (1023 - 106) + 0)) = Category::Infinity - ( - 0x7c8ffffffffffffe_7fefffffffffffff, - 0x7c8ffffffffffffe_7fefffffffffffff, - Category::Infinity, - Round::NearestTiesToEven, - ), - // NaN + (1 + 0) = Category::NaN - (0x7ff8000000000000, 0x3ff0000000000000, Category::NaN, Round::NearestTiesToEven), - ]; - - for (op1, op2, expected, round) in data { - { - let mut a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - a1 = a1.add_r(a2, round).value; - - assert_eq!(expected, a1.category(), "{:#x} + {:#x}", op1, op2); - } - { - let a1 = DoubleDouble::from_bits(op1); - let mut a2 = DoubleDouble::from_bits(op2); - a2 = a2.add_r(a1, round).value; - - assert_eq!(expected, a2.category(), "{:#x} + {:#x}", op2, op1); - } - } -} - -#[test] -fn ppc_double_double_add() { - let data = [ - // (1 + 0) + (1e-105 + 0) = (1 + 1e-105) - ( - 0x3ff0000000000000, - 0x3960000000000000, - 0x3960000000000000_3ff0000000000000, - Round::NearestTiesToEven, - ), - // (1 + 0) + (1e-106 + 0) = (1 + 1e-106) - ( - 0x3ff0000000000000, - 0x3950000000000000, - 0x3950000000000000_3ff0000000000000, - Round::NearestTiesToEven, - ), - // (1 + 1e-106) + (1e-106 + 0) = (1 + 1e-105) - ( - 0x3950000000000000_3ff0000000000000, - 0x3950000000000000, - 0x3960000000000000_3ff0000000000000, - Round::NearestTiesToEven, - ), - // (1 + 0) + (epsilon + 0) = (1 + epsilon) - ( - 0x3ff0000000000000, - 0x0000000000000001, - 0x0000000000000001_3ff0000000000000, - Round::NearestTiesToEven, - ), - // FIXME: change 0xf950000000000000 to 0xf940000000000000, when - // DoubleDouble's fallback is gone. - // (DBL_MAX - 1 << (1023 - 105)) + (1 << (1023 - 53) + 0) = DBL_MAX + - // 1.11111... << (1023 - 52) - ( - 0xf950000000000000_7fefffffffffffff, - 0x7c90000000000000, - 0x7c8ffffffffffffe_7fefffffffffffff, - Round::NearestTiesToEven, - ), - // FIXME: change 0xf950000000000000 to 0xf940000000000000, when - // DoubleDouble's fallback is gone. - // (1 << (1023 - 53) + 0) + (DBL_MAX - 1 << (1023 - 105)) = DBL_MAX + - // 1.11111... << (1023 - 52) - ( - 0x7c90000000000000, - 0xf950000000000000_7fefffffffffffff, - 0x7c8ffffffffffffe_7fefffffffffffff, - Round::NearestTiesToEven, - ), - ]; - - for (op1, op2, expected, round) in data { - { - let mut a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - a1 = a1.add_r(a2, round).value; - - assert_eq!(expected, a1.to_bits(), "{:#x} + {:#x}", op1, op2); - } - { - let a1 = DoubleDouble::from_bits(op1); - let mut a2 = DoubleDouble::from_bits(op2); - a2 = a2.add_r(a1, round).value; - - assert_eq!(expected, a2.to_bits(), "{:#x} + {:#x}", op2, op1); - } - } -} - -#[test] -fn ppc_double_double_subtract() { - let data = [ - // (1 + 0) - (-1e-105 + 0) = (1 + 1e-105) - ( - 0x3ff0000000000000, - 0xb960000000000000, - 0x3960000000000000_3ff0000000000000, - Round::NearestTiesToEven, - ), - // (1 + 0) - (-1e-106 + 0) = (1 + 1e-106) - ( - 0x3ff0000000000000, - 0xb950000000000000, - 0x3950000000000000_3ff0000000000000, - Round::NearestTiesToEven, - ), - ]; - - for (op1, op2, expected, round) in data { - let mut a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - a1 = a1.sub_r(a2, round).value; - - assert_eq!(expected, a1.to_bits(), "{:#x} - {:#x}", op1, op2); - } -} - -#[test] -fn ppc_double_double_multiply_special() { - let data = [ - // Category::NaN * Category::NaN = Category::NaN - (0x7ff8000000000000, 0x7ff8000000000000, Category::NaN, Round::NearestTiesToEven), - // Category::NaN * Category::Zero = Category::NaN - (0x7ff8000000000000, 0, Category::NaN, Round::NearestTiesToEven), - // Category::NaN * Category::Infinity = Category::NaN - (0x7ff8000000000000, 0x7ff0000000000000, Category::NaN, Round::NearestTiesToEven), - // Category::NaN * Category::Normal = Category::NaN - (0x7ff8000000000000, 0x3ff0000000000000, Category::NaN, Round::NearestTiesToEven), - // Category::Infinity * Category::Infinity = Category::Infinity - (0x7ff0000000000000, 0x7ff0000000000000, Category::Infinity, Round::NearestTiesToEven), - // Category::Infinity * Category::Zero = Category::NaN - (0x7ff0000000000000, 0, Category::NaN, Round::NearestTiesToEven), - // Category::Infinity * Category::Normal = Category::Infinity - (0x7ff0000000000000, 0x3ff0000000000000, Category::Infinity, Round::NearestTiesToEven), - // Category::Zero * Category::Zero = Category::Zero - (0, 0, Category::Zero, Round::NearestTiesToEven), - // Category::Zero * Category::Normal = Category::Zero - (0, 0x3ff0000000000000, Category::Zero, Round::NearestTiesToEven), - ]; - - for (op1, op2, expected, round) in data { - { - let mut a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - a1 = a1.mul_r(a2, round).value; - - assert_eq!(expected, a1.category(), "{:#x} * {:#x}", op1, op2); - } - { - let a1 = DoubleDouble::from_bits(op1); - let mut a2 = DoubleDouble::from_bits(op2); - a2 = a2.mul_r(a1, round).value; - - assert_eq!(expected, a2.category(), "{:#x} * {:#x}", op2, op1); - } - } -} - -#[test] -fn ppc_double_double_multiply() { - let data = [ - // 1/3 * 3 = 1.0 - ( - 0x3c75555555555556_3fd5555555555555, - 0x4008000000000000, - 0x3ff0000000000000, - Round::NearestTiesToEven, - ), - // (1 + epsilon) * (1 + 0) = Category::Zero - ( - 0x0000000000000001_3ff0000000000000, - 0x3ff0000000000000, - 0x0000000000000001_3ff0000000000000, - Round::NearestTiesToEven, - ), - // (1 + epsilon) * (1 + epsilon) = 1 + 2 * epsilon - ( - 0x0000000000000001_3ff0000000000000, - 0x0000000000000001_3ff0000000000000, - 0x0000000000000002_3ff0000000000000, - Round::NearestTiesToEven, - ), - // -(1 + epsilon) * (1 + epsilon) = -1 - ( - 0x0000000000000001_bff0000000000000, - 0x0000000000000001_3ff0000000000000, - 0xbff0000000000000, - Round::NearestTiesToEven, - ), - // (0.5 + 0) * (1 + 2 * epsilon) = 0.5 + epsilon - ( - 0x3fe0000000000000, - 0x0000000000000002_3ff0000000000000, - 0x0000000000000001_3fe0000000000000, - Round::NearestTiesToEven, - ), - // (0.5 + 0) * (1 + epsilon) = 0.5 - ( - 0x3fe0000000000000, - 0x0000000000000001_3ff0000000000000, - 0x3fe0000000000000, - Round::NearestTiesToEven, - ), - // __LDBL_MAX__ * (1 + 1 << 106) = inf - ( - 0x7c8ffffffffffffe_7fefffffffffffff, - 0x3950000000000000_3ff0000000000000, - 0x7ff0000000000000, - Round::NearestTiesToEven, - ), - // __LDBL_MAX__ * (1 + 1 << 107) > __LDBL_MAX__, but not inf, yes =_=||| - ( - 0x7c8ffffffffffffe_7fefffffffffffff, - 0x3940000000000000_3ff0000000000000, - 0x7c8fffffffffffff_7fefffffffffffff, - Round::NearestTiesToEven, - ), - // __LDBL_MAX__ * (1 + 1 << 108) = __LDBL_MAX__ - ( - 0x7c8ffffffffffffe_7fefffffffffffff, - 0x3930000000000000_3ff0000000000000, - 0x7c8ffffffffffffe_7fefffffffffffff, - Round::NearestTiesToEven, - ), - ]; - - for (op1, op2, expected, round) in data { - { - let mut a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - a1 = a1.mul_r(a2, round).value; - - assert_eq!(expected, a1.to_bits(), "{:#x} * {:#x}", op1, op2); - } - { - let a1 = DoubleDouble::from_bits(op1); - let mut a2 = DoubleDouble::from_bits(op2); - a2 = a2.mul_r(a1, round).value; - - assert_eq!(expected, a2.to_bits(), "{:#x} * {:#x}", op2, op1); - } - } -} - -#[test] -fn ppc_double_double_divide() { - // FIXME: Only a sanity check for now. Add more edge cases when the - // double-double algorithm is implemented. - let data = [ - // 1 / 3 = 1/3 - ( - 0x3ff0000000000000, - 0x4008000000000000, - 0x3c75555555555556_3fd5555555555555, - Round::NearestTiesToEven, - ), - ]; - - for (op1, op2, expected, round) in data { - let mut a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - a1 = a1.div_r(a2, round).value; - - assert_eq!(expected, a1.to_bits(), "{:#x} / {:#x}", op1, op2); - } -} - -#[test] -fn ppc_double_double_remainder() { - let data = [ - // ieee_rem(3.0 + 3.0 << 53, 1.25 + 1.25 << 53) = (0.5 + 0.5 << 53) - ( - 0x3cb8000000000000_4008000000000000, - 0x3ca4000000000000_3ff4000000000000, - 0x3c90000000000000_3fe0000000000000, - ), - // ieee_rem(3.0 + 3.0 << 53, 1.75 + 1.75 << 53) = (-0.5 - 0.5 << 53) - ( - 0x3cb8000000000000_4008000000000000, - 0x3cac000000000000_3ffc000000000000, - 0xbc90000000000000_bfe0000000000000, - ), - ]; - - for (op1, op2, expected) in data { - let a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - let result = a1.ieee_rem(a2).value; - - assert_eq!(expected, result.to_bits(), "ieee_rem({:#x}, {:#x})", op1, op2); - } -} - -#[test] -fn ppc_double_double_mod() { - let data = [ - // mod(3.0 + 3.0 << 53, 1.25 + 1.25 << 53) = (0.5 + 0.5 << 53) - ( - 0x3cb8000000000000_4008000000000000, - 0x3ca4000000000000_3ff4000000000000, - 0x3c90000000000000_3fe0000000000000, - ), - // mod(3.0 + 3.0 << 53, 1.75 + 1.75 << 53) = (1.25 + 1.25 << 53) - // 0xbc98000000000000 doesn't seem right, but it's what we currently have. - // FIXME: investigate - ( - 0x3cb8000000000000_4008000000000000, - 0x3cac000000000000_3ffc000000000000, - 0xbc98000000000000_3ff4000000000001, - ), - ]; - - for (op1, op2, expected) in data { - let a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - let r = (a1 % a2).value; - - assert_eq!(expected, r.to_bits(), "fmod({:#x}, {:#x})", op1, op2); - } -} - -#[test] -fn ppc_double_double_fma() { - // Sanity check for now. - let mut a = "2".parse::().unwrap(); - a = a.mul_add("3".parse::().unwrap(), "4".parse::().unwrap()).value; - assert_eq!(Some(Ordering::Equal), "10".parse::().unwrap().partial_cmp(&a)); -} - -#[test] -fn ppc_double_double_round_to_integral() { - { - let a = "1.5".parse::().unwrap(); - let a = a.round_to_integral(Round::NearestTiesToEven).value; - assert_eq!(Some(Ordering::Equal), "2".parse::().unwrap().partial_cmp(&a)); - } - { - let a = "2.5".parse::().unwrap(); - let a = a.round_to_integral(Round::NearestTiesToEven).value; - assert_eq!(Some(Ordering::Equal), "2".parse::().unwrap().partial_cmp(&a)); - } -} - -#[test] -fn ppc_double_double_compare() { - let data = [ - // (1 + 0) = (1 + 0) - (0x3ff0000000000000, 0x3ff0000000000000, Some(Ordering::Equal)), - // (1 + 0) < (1.00...1 + 0) - (0x3ff0000000000000, 0x3ff0000000000001, Some(Ordering::Less)), - // (1.00...1 + 0) > (1 + 0) - (0x3ff0000000000001, 0x3ff0000000000000, Some(Ordering::Greater)), - // (1 + 0) < (1 + epsilon) - (0x3ff0000000000000, 0x0000000000000001_3ff0000000000001, Some(Ordering::Less)), - // NaN != NaN - (0x7ff8000000000000, 0x7ff8000000000000, None), - // (1 + 0) != NaN - (0x3ff0000000000000, 0x7ff8000000000000, None), - // Inf = Inf - (0x7ff0000000000000, 0x7ff0000000000000, Some(Ordering::Equal)), - ]; - - for (op1, op2, expected) in data { - let a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - assert_eq!(expected, a1.partial_cmp(&a2), "compare({:#x}, {:#x})", op1, op2,); - } -} - -#[test] -fn ppc_double_double_bitwise_eq() { - let data = [ - // (1 + 0) = (1 + 0) - (0x3ff0000000000000, 0x3ff0000000000000, true), - // (1 + 0) != (1.00...1 + 0) - (0x3ff0000000000000, 0x3ff0000000000001, false), - // NaN = NaN - (0x7ff8000000000000, 0x7ff8000000000000, true), - // NaN != NaN with a different bit pattern - (0x7ff8000000000000, 0x3ff0000000000000_7ff8000000000000, false), - // Inf = Inf - (0x7ff0000000000000, 0x7ff0000000000000, true), - ]; - - for (op1, op2, expected) in data { - let a1 = DoubleDouble::from_bits(op1); - let a2 = DoubleDouble::from_bits(op2); - assert_eq!(expected, a1.bitwise_eq(a2), "{:#x} = {:#x}", op1, op2); - } -} - -#[test] -fn ppc_double_double_change_sign() { - let float = DoubleDouble::from_bits(0xbcb0000000000000_400f000000000000); - { - let actual = float.copy_sign("1".parse::().unwrap()); - assert_eq!(0xbcb0000000000000_400f000000000000, actual.to_bits()); - } - { - let actual = float.copy_sign("-1".parse::().unwrap()); - assert_eq!(0x3cb0000000000000_c00f000000000000, actual.to_bits()); - } -} - -#[test] -fn ppc_double_double_factories() { - assert_eq!(0, DoubleDouble::ZERO.to_bits()); - assert_eq!(0x7c8ffffffffffffe_7fefffffffffffff, DoubleDouble::largest().to_bits()); - assert_eq!(0x0000000000000001, DoubleDouble::SMALLEST.to_bits()); - assert_eq!(0x0360000000000000, DoubleDouble::smallest_normalized().to_bits()); - assert_eq!(0x0000000000000000_8000000000000000, (-DoubleDouble::ZERO).to_bits()); - assert_eq!(0xfc8ffffffffffffe_ffefffffffffffff, (-DoubleDouble::largest()).to_bits()); - assert_eq!(0x0000000000000000_8000000000000001, (-DoubleDouble::SMALLEST).to_bits()); - assert_eq!( - 0x0000000000000000_8360000000000000, - (-DoubleDouble::smallest_normalized()).to_bits() - ); - assert!(DoubleDouble::SMALLEST.is_smallest()); - assert!(DoubleDouble::largest().is_largest()); -} - -#[test] -fn ppc_double_double_is_denormal() { - assert!(DoubleDouble::SMALLEST.is_denormal()); - assert!(!DoubleDouble::largest().is_denormal()); - assert!(!DoubleDouble::smallest_normalized().is_denormal()); - { - // (4 + 3) is not normalized - let data = 0x4008000000000000_4010000000000000; - assert!(DoubleDouble::from_bits(data).is_denormal()); - } -} - -#[test] -fn ppc_double_double_exact_inverse() { - assert!( - "2.0" - .parse::() - .unwrap() - .get_exact_inverse() - .unwrap() - .bitwise_eq("0.5".parse::().unwrap()) - ); -} - -#[test] -fn ppc_double_double_scalbn() { - // 3.0 + 3.0 << 53 - let input = 0x3cb8000000000000_4008000000000000; - let result = DoubleDouble::from_bits(input).scalbn(1); - // 6.0 + 6.0 << 53 - assert_eq!(0x3cc8000000000000_4018000000000000, result.to_bits()); -} - -#[test] -fn ppc_double_double_frexp() { - // 3.0 + 3.0 << 53 - let input = 0x3cb8000000000000_4008000000000000; - let mut exp = 0; - // 0.75 + 0.75 << 53 - let result = DoubleDouble::from_bits(input).frexp(&mut exp); - assert_eq!(2, exp); - assert_eq!(0x3c98000000000000_3fe8000000000000, result.to_bits()); -} diff --git a/compiler/rustc_arena/src/lib.rs b/compiler/rustc_arena/src/lib.rs index 6e15f06a76de0..e45b7c154faf2 100644 --- a/compiler/rustc_arena/src/lib.rs +++ b/compiler/rustc_arena/src/lib.rs @@ -11,6 +11,7 @@ html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/", test(no_crate_inject, attr(deny(warnings))) )] +#![feature(core_intrinsics)] #![feature(dropck_eyepatch)] #![feature(new_uninit)] #![feature(maybe_uninit_slice)] @@ -23,17 +24,18 @@ #![deny(unsafe_op_in_unsafe_fn)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #![allow(clippy::mut_from_ref)] // Arena allocators are one of the places where this pattern is fine. use smallvec::SmallVec; use std::alloc::Layout; use std::cell::{Cell, RefCell}; -use std::cmp; use std::marker::PhantomData; use std::mem::{self, MaybeUninit}; use std::ptr::{self, NonNull}; use std::slice; +use std::{cmp, intrinsics}; #[inline(never)] #[cold] @@ -67,7 +69,7 @@ struct ArenaChunk { unsafe impl<#[may_dangle] T> Drop for ArenaChunk { fn drop(&mut self) { - unsafe { Box::from_raw(self.storage.as_mut()) }; + unsafe { drop(Box::from_raw(self.storage.as_mut())) } } } @@ -362,6 +364,22 @@ unsafe impl<#[may_dangle] T> Drop for TypedArena { unsafe impl Send for TypedArena {} +#[inline(always)] +fn align_down(val: usize, align: usize) -> usize { + debug_assert!(align.is_power_of_two()); + val & !(align - 1) +} + +#[inline(always)] +fn align_up(val: usize, align: usize) -> usize { + debug_assert!(align.is_power_of_two()); + (val + align - 1) & !(align - 1) +} + +// Pointer alignment is common in compiler types, so keep `DroplessArena` aligned to them +// to optimize away alignment code. +const DROPLESS_ALIGNMENT: usize = mem::align_of::(); + /// An arena that can hold objects of multiple different types that impl `Copy` /// and/or satisfy `!mem::needs_drop`. pub struct DroplessArena { @@ -374,6 +392,8 @@ pub struct DroplessArena { /// start. (This is slightly simpler and faster than allocating upwards, /// see .) /// When this pointer crosses the start pointer, a new chunk is allocated. + /// + /// This is kept aligned to DROPLESS_ALIGNMENT. end: Cell<*mut u8>, /// A vector of arena chunks. @@ -394,9 +414,11 @@ impl Default for DroplessArena { } impl DroplessArena { - #[inline(never)] - #[cold] - fn grow(&self, additional: usize) { + fn grow(&self, layout: Layout) { + // Add some padding so we can align `self.end` while + // stilling fitting in a `layout` allocation. + let additional = layout.size() + cmp::max(DROPLESS_ALIGNMENT, layout.align()) - 1; + unsafe { let mut chunks = self.chunks.borrow_mut(); let mut new_cap; @@ -415,13 +437,35 @@ impl DroplessArena { // Also ensure that this chunk can fit `additional`. new_cap = cmp::max(additional, new_cap); - let mut chunk = ArenaChunk::new(new_cap); + let mut chunk = ArenaChunk::new(align_up(new_cap, PAGE)); self.start.set(chunk.start()); - self.end.set(chunk.end()); + + // Align the end to DROPLESS_ALIGNMENT + let end = align_down(chunk.end().addr(), DROPLESS_ALIGNMENT); + + // Make sure we don't go past `start`. This should not happen since the allocation + // should be at least DROPLESS_ALIGNMENT - 1 bytes. + debug_assert!(chunk.start().addr() <= end); + + self.end.set(chunk.end().with_addr(end)); + chunks.push(chunk); } } + #[inline(never)] + #[cold] + fn grow_and_alloc_raw(&self, layout: Layout) -> *mut u8 { + self.grow(layout); + self.alloc_raw_without_grow(layout).unwrap() + } + + #[inline(never)] + #[cold] + fn grow_and_alloc(&self) -> *mut u8 { + self.grow_and_alloc_raw(Layout::new::()) + } + /// Allocates a byte slice with specified layout from the current memory /// chunk. Returns `None` if there is no free space left to satisfy the /// request. @@ -431,12 +475,17 @@ impl DroplessArena { let old_end = self.end.get(); let end = old_end.addr(); - let align = layout.align(); - let bytes = layout.size(); + // Align allocated bytes so that `self.end` stays aligned to DROPLESS_ALIGNMENT + let bytes = align_up(layout.size(), DROPLESS_ALIGNMENT); + + // Tell LLVM that `end` is aligned to DROPLESS_ALIGNMENT + unsafe { intrinsics::assume(end == align_down(end, DROPLESS_ALIGNMENT)) }; - let new_end = end.checked_sub(bytes)? & !(align - 1); + let new_end = align_down(end.checked_sub(bytes)?, layout.align()); if start <= new_end { let new_end = old_end.with_addr(new_end); + // `new_end` is aligned to DROPLESS_ALIGNMENT as `align_down` preserves alignment + // as both `end` and `bytes` are already aligned to DROPLESS_ALIGNMENT. self.end.set(new_end); Some(new_end) } else { @@ -447,21 +496,26 @@ impl DroplessArena { #[inline] pub fn alloc_raw(&self, layout: Layout) -> *mut u8 { assert!(layout.size() != 0); - loop { - if let Some(a) = self.alloc_raw_without_grow(layout) { - break a; - } - // No free space left. Allocate a new chunk to satisfy the request. - // On failure the grow will panic or abort. - self.grow(layout.size()); + if let Some(a) = self.alloc_raw_without_grow(layout) { + return a; } + // No free space left. Allocate a new chunk to satisfy the request. + // On failure the grow will panic or abort. + self.grow_and_alloc_raw(layout) } #[inline] pub fn alloc(&self, object: T) -> &mut T { assert!(!mem::needs_drop::()); + assert!(mem::size_of::() != 0); - let mem = self.alloc_raw(Layout::for_value::(&object)) as *mut T; + let mem = if let Some(a) = self.alloc_raw_without_grow(Layout::for_value::(&object)) { + a + } else { + // No free space left. Allocate a new chunk to satisfy the request. + // On failure the grow will panic or abort. + self.grow_and_alloc::() + } as *mut T; unsafe { // Write into uninitialized memory. diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 4360fbeb9bbc1..58725a08c7c5f 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -14,7 +14,7 @@ //! - [`Generics`], [`GenericParam`], [`WhereClause`]: Metadata associated with generic parameters. //! - [`EnumDef`] and [`Variant`]: Enum declaration. //! - [`MetaItemLit`] and [`LitKind`]: Literal expressions. -//! - [`MacroDef`], [`MacStmtStyle`], [`MacCall`], [`MacDelimiter`]: Macro definition and invocation. +//! - [`MacroDef`], [`MacStmtStyle`], [`MacCall`]: Macro definition and invocation. //! - [`Attribute`]: Metadata associated with item. //! - [`UnOp`], [`BinOp`], and [`BinOpKind`]: Unary and binary operators. @@ -313,6 +313,16 @@ pub enum TraitBoundModifier { MaybeConstMaybe, } +impl TraitBoundModifier { + pub fn to_constness(self) -> Const { + match self { + // FIXME(effects) span + Self::MaybeConst => Const::Yes(DUMMY_SP), + _ => Const::No, + } + } +} + /// The AST represents all type param bounds as types. /// `typeck::collect::compute_bounds` matches these against /// the "special" built-in traits (see `middle::lang_items`) and @@ -1295,6 +1305,7 @@ impl Expr { ExprKind::Yield(..) => ExprPrecedence::Yield, ExprKind::Yeet(..) => ExprPrecedence::Yeet, ExprKind::FormatArgs(..) => ExprPrecedence::FormatArgs, + ExprKind::Become(..) => ExprPrecedence::Become, ExprKind::Err => ExprPrecedence::Err, } } @@ -1461,7 +1472,8 @@ pub enum ExprKind { /// Access of a named (e.g., `obj.foo`) or unnamed (e.g., `obj.0`) struct field. Field(P, Ident), /// An indexing operation (e.g., `foo[2]`). - Index(P, P), + /// The span represents the span of the `[2]`, including brackets. + Index(P, P, Span), /// A range (e.g., `1..2`, `1..`, `..2`, `1..=2`, `..=2`; and `..` in destructuring assignment). Range(Option>, Option>, RangeLimits), /// An underscore, used in destructuring assignment to ignore a value. @@ -1515,6 +1527,11 @@ pub enum ExprKind { /// with an optional value to be returned. Yeet(Option>), + /// A tail call return, with the value to be returned. + /// + /// While `.0` must be a function call, we check this later, after parsing. + Become(P), + /// Bytes included via `include_bytes!` /// Added for optimization purposes to avoid the need to escape /// large binary blobs - should always behave like [`ExprKind::Lit`] @@ -1687,7 +1704,7 @@ where #[derive(Clone, Encodable, Decodable, Debug)] pub struct DelimArgs { pub dspan: DelimSpan, - pub delim: MacDelimiter, + pub delim: Delimiter, // Note: `Delimiter::Invisible` never occurs pub tokens: TokenStream, } @@ -1695,7 +1712,7 @@ impl DelimArgs { /// Whether a macro with these arguments needs a semicolon /// when used as a standalone item or statement. pub fn need_semicolon(&self) -> bool { - !matches!(self, DelimArgs { delim: MacDelimiter::Brace, .. }) + !matches!(self, DelimArgs { delim: Delimiter::Brace, .. }) } } @@ -1711,32 +1728,6 @@ where } } -#[derive(Copy, Clone, PartialEq, Eq, Encodable, Decodable, Debug, HashStable_Generic)] -pub enum MacDelimiter { - Parenthesis, - Bracket, - Brace, -} - -impl MacDelimiter { - pub fn to_token(self) -> Delimiter { - match self { - MacDelimiter::Parenthesis => Delimiter::Parenthesis, - MacDelimiter::Bracket => Delimiter::Bracket, - MacDelimiter::Brace => Delimiter::Brace, - } - } - - pub fn from_token(delim: Delimiter) -> Option { - match delim { - Delimiter::Parenthesis => Some(MacDelimiter::Parenthesis), - Delimiter::Bracket => Some(MacDelimiter::Bracket), - Delimiter::Brace => Some(MacDelimiter::Brace), - Delimiter::Invisible => None, - } - } -} - /// Represents a macro definition. #[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)] pub struct MacroDef { @@ -2347,7 +2338,12 @@ impl Param { /// Builds a `Param` object from `ExplicitSelf`. pub fn from_self(attrs: AttrVec, eself: ExplicitSelf, eself_ident: Ident) -> Param { let span = eself.span.to(eself_ident.span); - let infer_ty = P(Ty { id: DUMMY_NODE_ID, kind: TyKind::ImplicitSelf, span, tokens: None }); + let infer_ty = P(Ty { + id: DUMMY_NODE_ID, + kind: TyKind::ImplicitSelf, + span: eself_ident.span, + tokens: None, + }); let (mutbl, ty) = match eself.node { SelfKind::Explicit(ty, mutbl) => (mutbl, ty), SelfKind::Value(mutbl) => (mutbl, infer_ty), @@ -2646,6 +2642,15 @@ pub struct NormalAttr { pub tokens: Option, } +impl NormalAttr { + pub fn from_ident(ident: Ident) -> Self { + Self { + item: AttrItem { path: Path::from_ident(ident), args: AttrArgs::Empty, tokens: None }, + tokens: None, + } + } +} + #[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)] pub struct AttrItem { pub path: Path, @@ -2927,6 +2932,7 @@ pub struct StaticItem { #[derive(Clone, Encodable, Decodable, Debug)] pub struct ConstItem { pub defaultness: Defaultness, + pub generics: Generics, pub ty: P, pub expr: Option>, } @@ -3038,6 +3044,7 @@ impl ItemKind { match self { Self::Fn(box Fn { generics, .. }) | Self::TyAlias(box TyAlias { generics, .. }) + | Self::Const(box ConstItem { generics, .. }) | Self::Enum(_, generics) | Self::Struct(_, generics) | Self::Union(_, generics) diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 15fe29580c29c..19a2b3017bc53 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -2,7 +2,7 @@ use crate::ast::{AttrArgs, AttrArgsEq, AttrId, AttrItem, AttrKind, AttrStyle, AttrVec, Attribute}; use crate::ast::{DelimArgs, Expr, ExprKind, LitKind, MetaItemLit}; -use crate::ast::{MacDelimiter, MetaItem, MetaItemKind, NestedMetaItem, NormalAttr}; +use crate::ast::{MetaItem, MetaItemKind, NestedMetaItem, NormalAttr}; use crate::ast::{Path, PathSegment, DUMMY_NODE_ID}; use crate::ptr::P; use crate::token::{self, CommentKind, Delimiter, Token}; @@ -196,7 +196,7 @@ impl AttrItem { fn meta_item_list(&self) -> Option> { match &self.args { - AttrArgs::Delimited(args) if args.delim == MacDelimiter::Parenthesis => { + AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { MetaItemKind::list_from_tokens(args.tokens.clone()) } AttrArgs::Delimited(_) | AttrArgs::Eq(..) | AttrArgs::Empty => None, @@ -285,17 +285,17 @@ impl MetaItem { self.kind.value_str() } - fn from_tokens(tokens: &mut iter::Peekable) -> Option + fn from_tokens<'a, I>(tokens: &mut iter::Peekable) -> Option where - I: Iterator, + I: Iterator, { // FIXME: Share code with `parse_path`. - let path = match tokens.next().map(TokenTree::uninterpolate) { - Some(TokenTree::Token( - Token { kind: kind @ (token::Ident(..) | token::ModSep), span }, + let path = match tokens.next().map(|tt| TokenTree::uninterpolate(tt)).as_deref() { + Some(&TokenTree::Token( + Token { kind: ref kind @ (token::Ident(..) | token::ModSep), span }, _, )) => 'arm: { - let mut segments = if let token::Ident(name, _) = kind { + let mut segments = if let &token::Ident(name, _) = kind { if let Some(TokenTree::Token(Token { kind: token::ModSep, .. }, _)) = tokens.peek() { @@ -308,8 +308,8 @@ impl MetaItem { thin_vec![PathSegment::path_root(span)] }; loop { - if let Some(TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) = - tokens.next().map(TokenTree::uninterpolate) + if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) = + tokens.next().map(|tt| TokenTree::uninterpolate(tt)).as_deref() { segments.push(PathSegment::from_ident(Ident::new(name, span))); } else { @@ -326,7 +326,7 @@ impl MetaItem { let span = span.with_hi(segments.last().unwrap().ident.span.hi()); Path { span, segments, tokens: None } } - Some(TokenTree::Token(Token { kind: token::Interpolated(nt), .. }, _)) => match &*nt { + Some(TokenTree::Token(Token { kind: token::Interpolated(nt), .. }, _)) => match &**nt { token::Nonterminal::NtMeta(item) => return item.meta(item.path.span), token::Nonterminal::NtPath(path) => (**path).clone(), _ => return None, @@ -354,7 +354,7 @@ impl MetaItemKind { } fn list_from_tokens(tokens: TokenStream) -> Option> { - let mut tokens = tokens.into_trees().peekable(); + let mut tokens = tokens.trees().peekable(); let mut result = ThinVec::new(); while tokens.peek().is_some() { let item = NestedMetaItem::from_tokens(&mut tokens)?; @@ -367,12 +367,12 @@ impl MetaItemKind { Some(result) } - fn name_value_from_tokens( - tokens: &mut impl Iterator, + fn name_value_from_tokens<'a>( + tokens: &mut impl Iterator, ) -> Option { match tokens.next() { Some(TokenTree::Delimited(_, Delimiter::Invisible, inner_tokens)) => { - MetaItemKind::name_value_from_tokens(&mut inner_tokens.into_trees()) + MetaItemKind::name_value_from_tokens(&mut inner_tokens.trees()) } Some(TokenTree::Token(token, _)) => { MetaItemLit::from_token(&token).map(MetaItemKind::NameValue) @@ -381,8 +381,8 @@ impl MetaItemKind { } } - fn from_tokens( - tokens: &mut iter::Peekable>, + fn from_tokens<'a>( + tokens: &mut iter::Peekable>, ) -> Option { match tokens.peek() { Some(TokenTree::Delimited(_, Delimiter::Parenthesis, inner_tokens)) => { @@ -402,11 +402,9 @@ impl MetaItemKind { fn from_attr_args(args: &AttrArgs) -> Option { match args { AttrArgs::Empty => Some(MetaItemKind::Word), - AttrArgs::Delimited(DelimArgs { - dspan: _, - delim: MacDelimiter::Parenthesis, - tokens, - }) => MetaItemKind::list_from_tokens(tokens.clone()).map(MetaItemKind::List), + AttrArgs::Delimited(DelimArgs { dspan: _, delim: Delimiter::Parenthesis, tokens }) => { + MetaItemKind::list_from_tokens(tokens.clone()).map(MetaItemKind::List) + } AttrArgs::Delimited(..) => None, AttrArgs::Eq(_, AttrArgsEq::Ast(expr)) => match expr.kind { ExprKind::Lit(token_lit) => { @@ -501,9 +499,9 @@ impl NestedMetaItem { self.meta_item().is_some() } - fn from_tokens(tokens: &mut iter::Peekable) -> Option + fn from_tokens<'a, I>(tokens: &mut iter::Peekable) -> Option where - I: Iterator, + I: Iterator, { match tokens.peek() { Some(TokenTree::Token(token, _)) @@ -513,9 +511,8 @@ impl NestedMetaItem { return Some(NestedMetaItem::Lit(lit)); } Some(TokenTree::Delimited(_, Delimiter::Invisible, inner_tokens)) => { - let inner_tokens = inner_tokens.clone(); tokens.next(); - return NestedMetaItem::from_tokens(&mut inner_tokens.into_trees().peekable()); + return NestedMetaItem::from_tokens(&mut inner_tokens.trees().peekable()); } _ => {} } @@ -579,7 +576,7 @@ pub fn mk_attr_nested_word( let path = Path::from_ident(outer_ident); let attr_args = AttrArgs::Delimited(DelimArgs { dspan: DelimSpan::from_single(span), - delim: MacDelimiter::Parenthesis, + delim: Delimiter::Parenthesis, tokens: inner_tokens, }); mk_attr(g, style, path, attr_args, span) diff --git a/compiler/rustc_ast/src/expand/allocator.rs b/compiler/rustc_ast/src/expand/allocator.rs index 3593949634898..f825b10f489b0 100644 --- a/compiler/rustc_ast/src/expand/allocator.rs +++ b/compiler/rustc_ast/src/expand/allocator.rs @@ -1,20 +1,28 @@ use rustc_span::symbol::{sym, Symbol}; -#[derive(Clone, Debug, Copy, HashStable_Generic)] +#[derive(Clone, Debug, Copy, Eq, PartialEq, HashStable_Generic)] pub enum AllocatorKind { Global, Default, } -impl AllocatorKind { - pub fn fn_name(&self, base: Symbol) -> String { - match *self { - AllocatorKind::Global => format!("__rg_{base}"), - AllocatorKind::Default => format!("__rdl_{base}"), - } +pub fn global_fn_name(base: Symbol) -> String { + format!("__rust_{base}") +} + +pub fn default_fn_name(base: Symbol) -> String { + format!("__rdl_{base}") +} + +pub fn alloc_error_handler_name(alloc_error_handler_kind: AllocatorKind) -> &'static str { + match alloc_error_handler_kind { + AllocatorKind::Global => "__rg_oom", + AllocatorKind::Default => "__rdl_oom", } } +pub const NO_ALLOC_SHIM_IS_UNSTABLE: &str = "__rust_no_alloc_shim_is_unstable"; + pub enum AllocatorTy { Layout, Ptr, @@ -25,29 +33,41 @@ pub enum AllocatorTy { pub struct AllocatorMethod { pub name: Symbol, - pub inputs: &'static [AllocatorTy], + pub inputs: &'static [AllocatorMethodInput], pub output: AllocatorTy, } +pub struct AllocatorMethodInput { + pub name: &'static str, + pub ty: AllocatorTy, +} + pub static ALLOCATOR_METHODS: &[AllocatorMethod] = &[ AllocatorMethod { name: sym::alloc, - inputs: &[AllocatorTy::Layout], + inputs: &[AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }], output: AllocatorTy::ResultPtr, }, AllocatorMethod { name: sym::dealloc, - inputs: &[AllocatorTy::Ptr, AllocatorTy::Layout], + inputs: &[ + AllocatorMethodInput { name: "ptr", ty: AllocatorTy::Ptr }, + AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }, + ], output: AllocatorTy::Unit, }, AllocatorMethod { name: sym::realloc, - inputs: &[AllocatorTy::Ptr, AllocatorTy::Layout, AllocatorTy::Usize], + inputs: &[ + AllocatorMethodInput { name: "ptr", ty: AllocatorTy::Ptr }, + AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }, + AllocatorMethodInput { name: "new_size", ty: AllocatorTy::Usize }, + ], output: AllocatorTy::ResultPtr, }, AllocatorMethod { name: sym::alloc_zeroed, - inputs: &[AllocatorTy::Layout], + inputs: &[AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }], output: AllocatorTy::ResultPtr, }, ]; diff --git a/compiler/rustc_ast/src/expand/mod.rs b/compiler/rustc_ast/src/expand/mod.rs index 2ee1bfe0ae71b..942347383ce31 100644 --- a/compiler/rustc_ast/src/expand/mod.rs +++ b/compiler/rustc_ast/src/expand/mod.rs @@ -1,3 +1,20 @@ //! Definitions shared by macros / syntax extensions and e.g. `rustc_middle`. +use rustc_span::{def_id::DefId, symbol::Ident}; + +use crate::MetaItem; + pub mod allocator; + +#[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)] +pub struct StrippedCfgItem { + pub parent_module: ModId, + pub name: Ident, + pub cfg: MetaItem, +} + +impl StrippedCfgItem { + pub fn map_mod_id(self, f: impl FnOnce(ModId) -> New) -> StrippedCfgItem { + StrippedCfgItem { parent_module: f(self.parent_module), name: self.name, cfg: self.cfg } + } +} diff --git a/compiler/rustc_ast/src/format.rs b/compiler/rustc_ast/src/format.rs index 699946f307b50..805596ff00a98 100644 --- a/compiler/rustc_ast/src/format.rs +++ b/compiler/rustc_ast/src/format.rs @@ -67,12 +67,6 @@ pub struct FormatArguments { names: FxHashMap, } -// FIXME: Rustdoc has trouble proving Send/Sync for this. See #106930. -#[cfg(parallel_compiler)] -unsafe impl Sync for FormatArguments {} -#[cfg(parallel_compiler)] -unsafe impl Send for FormatArguments {} - impl FormatArguments { pub fn new() -> Self { Self { diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 66b94d12a32c6..48e9b180b74fc 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -13,6 +13,7 @@ use crate::tokenstream::*; use crate::{ast::*, StaticItem}; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; +use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::sync::Lrc; use rustc_span::source_map::Spanned; use rustc_span::symbol::Ident; @@ -1149,10 +1150,11 @@ pub fn noop_flat_map_assoc_item( } fn visit_const_item( - ConstItem { defaultness, ty, expr }: &mut ConstItem, + ConstItem { defaultness, generics, ty, expr }: &mut ConstItem, visitor: &mut T, ) { visit_defaultness(defaultness, visitor); + visitor.visit_generics(generics); visitor.visit_ty(ty); visit_opt(expr, |expr| visitor.visit_expr(expr)); } @@ -1368,7 +1370,7 @@ pub fn noop_visit_expr( ExprKind::If(cond, tr, fl) => { vis.visit_expr(cond); vis.visit_block(tr); - visit_opt(fl, |fl| vis.visit_expr(fl)); + visit_opt(fl, |fl| ensure_sufficient_stack(|| vis.visit_expr(fl))); } ExprKind::While(cond, body, label) => { vis.visit_expr(cond); @@ -1399,7 +1401,7 @@ pub fn noop_visit_expr( fn_decl, body, fn_decl_span, - fn_arg_span: _, + fn_arg_span, }) => { vis.visit_closure_binder(binder); visit_constness(constness, vis); @@ -1407,6 +1409,7 @@ pub fn noop_visit_expr( vis.visit_fn_decl(fn_decl); vis.visit_expr(body); vis.visit_span(fn_decl_span); + vis.visit_span(fn_arg_span); } ExprKind::Block(blk, label) => { vis.visit_block(blk); @@ -1419,9 +1422,10 @@ pub fn noop_visit_expr( vis.visit_expr(expr); vis.visit_span(await_kw_span); } - ExprKind::Assign(el, er, _) => { + ExprKind::Assign(el, er, span) => { vis.visit_expr(el); vis.visit_expr(er); + vis.visit_span(span); } ExprKind::AssignOp(_op, el, er) => { vis.visit_expr(el); @@ -1431,9 +1435,10 @@ pub fn noop_visit_expr( vis.visit_expr(el); vis.visit_ident(ident); } - ExprKind::Index(el, er) => { + ExprKind::Index(el, er, brackets_span) => { vis.visit_expr(el); vis.visit_expr(er); + vis.visit_span(brackets_span); } ExprKind::Range(e1, e2, _lim) => { visit_opt(e1, |e1| vis.visit_expr(e1)); @@ -1457,6 +1462,7 @@ pub fn noop_visit_expr( ExprKind::Yeet(expr) => { visit_opt(expr, |expr| vis.visit_expr(expr)); } + ExprKind::Become(expr) => vis.visit_expr(expr), ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm), ExprKind::FormatArgs(fmt) => vis.visit_format_args(fmt), ExprKind::OffsetOf(container, fields) => { diff --git a/compiler/rustc_ast/src/token.rs b/compiler/rustc_ast/src/token.rs index 7ef39f8026b20..f4ad0efa42344 100644 --- a/compiler/rustc_ast/src/token.rs +++ b/compiler/rustc_ast/src/token.rs @@ -11,6 +11,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::Lrc; use rustc_macros::HashStable_Generic; use rustc_span::symbol::{kw, sym}; +#[allow(hidden_glob_reexports)] use rustc_span::symbol::{Ident, Symbol}; use rustc_span::{self, edition::Edition, Span, DUMMY_SP}; use std::borrow::Cow; @@ -40,8 +41,6 @@ pub enum BinOpToken { /// Describes how a sequence of token trees is delimited. /// Cannot use `proc_macro::Delimiter` directly because this /// structure should implement some additional traits. -/// The `None` variant is also renamed to `Invisible` to be -/// less confusing and better convey the semantics. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Encodable, Decodable, Hash, HashStable_Generic)] pub enum Delimiter { @@ -225,7 +224,9 @@ fn ident_can_begin_type(name: Symbol, span: Span, is_raw: bool) -> bool { .contains(&name) } -#[derive(Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)] +// SAFETY: due to the `Clone` impl below, all fields of all variants other than +// `Interpolated` must impl `Copy`. +#[derive(PartialEq, Encodable, Decodable, Debug, HashStable_Generic)] pub enum TokenKind { /* Expression-operator symbols. */ Eq, @@ -298,6 +299,19 @@ pub enum TokenKind { Eof, } +impl Clone for TokenKind { + fn clone(&self) -> Self { + // `TokenKind` would impl `Copy` if it weren't for `Interpolated`. So + // for all other variants, this implementation of `clone` is just like + // a copy. This is faster than the `derive(Clone)` version which has a + // separate path for every variant. + match self { + Interpolated(nt) => Interpolated(nt.clone()), + _ => unsafe { std::ptr::read(self) }, + } + } +} + #[derive(Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)] pub struct Token { pub kind: TokenKind, diff --git a/compiler/rustc_ast/src/tokenstream.rs b/compiler/rustc_ast/src/tokenstream.rs index db296aa44db2b..e9591c7c8dbc6 100644 --- a/compiler/rustc_ast/src/tokenstream.rs +++ b/compiler/rustc_ast/src/tokenstream.rs @@ -13,7 +13,7 @@ //! and a borrowed `TokenStream` is sufficient to build an owned `TokenStream` without taking //! ownership of the original. -use crate::ast::StmtKind; +use crate::ast::{AttrStyle, StmtKind}; use crate::ast_traits::{HasAttrs, HasSpan, HasTokens}; use crate::token::{self, Delimiter, Nonterminal, Token, TokenKind}; use crate::AttrVec; @@ -22,10 +22,11 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::{self, Lrc}; use rustc_macros::HashStable_Generic; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; -use rustc_span::{Span, DUMMY_SP}; +use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use smallvec::{smallvec, SmallVec}; -use std::{fmt, iter}; +use std::borrow::Cow; +use std::{cmp, fmt, iter, mem}; /// When the main Rust parser encounters a syntax-extension invocation, it /// parses the arguments to the invocation as a token tree. This is a very @@ -98,12 +99,13 @@ impl TokenTree { TokenTree::Token(Token::new(kind, span), Spacing::Joint) } - pub fn uninterpolate(self) -> TokenTree { + pub fn uninterpolate(&self) -> Cow<'_, TokenTree> { match self { - TokenTree::Token(token, spacing) => { - TokenTree::Token(token.uninterpolate().into_owned(), spacing) - } - tt => tt, + TokenTree::Token(token, spacing) => match token.uninterpolate() { + Cow::Owned(token) => Cow::Owned(TokenTree::Token(token, *spacing)), + Cow::Borrowed(_) => Cow::Borrowed(self), + }, + _ => Cow::Borrowed(self), } } } @@ -410,8 +412,17 @@ impl TokenStream { t1.next().is_none() && t2.next().is_none() } - pub fn map_enumerated TokenTree>(self, mut f: F) -> TokenStream { - TokenStream(Lrc::new(self.0.iter().enumerate().map(|(i, tree)| f(i, tree)).collect())) + /// Applies the supplied function to each `TokenTree` and its index in `self`, returning a new `TokenStream` + /// + /// It is equivalent to `TokenStream::new(self.trees().cloned().enumerate().map(|(i, tt)| f(i, tt)).collect())`. + pub fn map_enumerated_owned( + mut self, + mut f: impl FnMut(usize, TokenTree) -> TokenTree, + ) -> TokenStream { + let owned = Lrc::make_mut(&mut self.0); // clone if necessary + // rely on vec's in-place optimizations to avoid another allocation + *owned = mem::take(owned).into_iter().enumerate().map(|(i, tree)| f(i, tree)).collect(); + self } /// Create a token stream containing a single token with alone spacing. @@ -555,6 +566,92 @@ impl TokenStream { pub fn chunks(&self, chunk_size: usize) -> core::slice::Chunks<'_, TokenTree> { self.0.chunks(chunk_size) } + + /// Desugar doc comments like `/// foo` in the stream into `#[doc = + /// r"foo"]`. Modifies the `TokenStream` via `Lrc::make_mut`, but as little + /// as possible. + pub fn desugar_doc_comments(&mut self) { + if let Some(desugared_stream) = desugar_inner(self.clone()) { + *self = desugared_stream; + } + + // The return value is `None` if nothing in `stream` changed. + fn desugar_inner(mut stream: TokenStream) -> Option { + let mut i = 0; + let mut modified = false; + while let Some(tt) = stream.0.get(i) { + match tt { + &TokenTree::Token( + Token { kind: token::DocComment(_, attr_style, data), span }, + _spacing, + ) => { + let desugared = desugared_tts(attr_style, data, span); + let desugared_len = desugared.len(); + Lrc::make_mut(&mut stream.0).splice(i..i + 1, desugared); + modified = true; + i += desugared_len; + } + + &TokenTree::Token(..) => i += 1, + + &TokenTree::Delimited(sp, delim, ref delim_stream) => { + if let Some(desugared_delim_stream) = desugar_inner(delim_stream.clone()) { + let new_tt = TokenTree::Delimited(sp, delim, desugared_delim_stream); + Lrc::make_mut(&mut stream.0)[i] = new_tt; + modified = true; + } + i += 1; + } + } + } + if modified { Some(stream) } else { None } + } + + fn desugared_tts(attr_style: AttrStyle, data: Symbol, span: Span) -> Vec { + // Searches for the occurrences of `"#*` and returns the minimum number of `#`s + // required to wrap the text. E.g. + // - `abc d` is wrapped as `r"abc d"` (num_of_hashes = 0) + // - `abc "d"` is wrapped as `r#"abc "d""#` (num_of_hashes = 1) + // - `abc "##d##"` is wrapped as `r###"abc ##"d"##"###` (num_of_hashes = 3) + let mut num_of_hashes = 0; + let mut count = 0; + for ch in data.as_str().chars() { + count = match ch { + '"' => 1, + '#' if count > 0 => count + 1, + _ => 0, + }; + num_of_hashes = cmp::max(num_of_hashes, count); + } + + // `/// foo` becomes `doc = r"foo"`. + let delim_span = DelimSpan::from_single(span); + let body = TokenTree::Delimited( + delim_span, + Delimiter::Bracket, + [ + TokenTree::token_alone(token::Ident(sym::doc, false), span), + TokenTree::token_alone(token::Eq, span), + TokenTree::token_alone( + TokenKind::lit(token::StrRaw(num_of_hashes), data, None), + span, + ), + ] + .into_iter() + .collect::(), + ); + + if attr_style == AttrStyle::Inner { + vec![ + TokenTree::token_alone(token::Pound, span), + TokenTree::token_alone(token::Not, span), + body, + ] + } else { + vec![TokenTree::token_alone(token::Pound, span), body] + } + } + } } /// By-reference iterator over a [`TokenStream`], that produces `&TokenTree` @@ -586,26 +683,21 @@ impl<'t> Iterator for RefTokenTreeCursor<'t> { } } -/// Owning by-value iterator over a [`TokenStream`], that produces `TokenTree` +/// Owning by-value iterator over a [`TokenStream`], that produces `&TokenTree` /// items. -// FIXME: Many uses of this can be replaced with by-reference iterator to avoid clones. +/// +/// Doesn't impl `Iterator` because Rust doesn't permit an owning iterator to +/// return `&T` from `next`; the need for an explicit lifetime in the `Item` +/// associated type gets in the way. Instead, use `next_ref` (which doesn't +/// involve associated types) for getting individual elements, or +/// `RefTokenTreeCursor` if you really want an `Iterator`, e.g. in a `for` +/// loop. #[derive(Clone)] pub struct TokenTreeCursor { pub stream: TokenStream, index: usize, } -impl Iterator for TokenTreeCursor { - type Item = TokenTree; - - fn next(&mut self) -> Option { - self.stream.0.get(self.index).map(|tree| { - self.index += 1; - tree.clone() - }) - } -} - impl TokenTreeCursor { fn new(stream: TokenStream) -> Self { TokenTreeCursor { stream, index: 0 } @@ -622,15 +714,6 @@ impl TokenTreeCursor { pub fn look_ahead(&self, n: usize) -> Option<&TokenTree> { self.stream.0.get(self.index + n) } - - // Replace the previously obtained token tree with `tts`, and rewind to - // just before them. - pub fn replace_prev_and_rewind(&mut self, tts: Vec) { - assert!(self.index > 0); - self.index -= 1; - let stream = Lrc::make_mut(&mut self.stream.0); - stream.splice(self.index..self.index + 1, tts); - } } #[derive(Debug, Copy, Clone, PartialEq, Encodable, Decodable, HashStable_Generic)] diff --git a/compiler/rustc_ast/src/util/comments.rs b/compiler/rustc_ast/src/util/comments.rs index eece99a3eef03..bdf5143b0f706 100644 --- a/compiler/rustc_ast/src/util/comments.rs +++ b/compiler/rustc_ast/src/util/comments.rs @@ -62,7 +62,7 @@ pub fn beautify_doc_string(data: Symbol, kind: CommentKind) -> Symbol { CommentKind::Block => { // Whatever happens, we skip the first line. let mut i = lines - .get(0) + .first() .map(|l| if l.trim_start().starts_with('*') { 0 } else { 1 }) .unwrap_or(0); let mut j = lines.len(); diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 35afd5423721d..d3e43e2023541 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -245,6 +245,7 @@ pub enum ExprPrecedence { Ret, Yield, Yeet, + Become, Range, @@ -298,7 +299,8 @@ impl ExprPrecedence { | ExprPrecedence::Continue | ExprPrecedence::Ret | ExprPrecedence::Yield - | ExprPrecedence::Yeet => PREC_JUMP, + | ExprPrecedence::Yeet + | ExprPrecedence::Become => PREC_JUMP, // `Range` claims to have higher precedence than `Assign`, but `x .. x = x` fails to // parse, instead of parsing as `(x .. x) = x`. Giving `Range` a lower precedence @@ -388,7 +390,7 @@ pub fn contains_exterior_struct_lit(value: &ast::Expr) -> bool { | ast::ExprKind::Cast(x, _) | ast::ExprKind::Type(x, _) | ast::ExprKind::Field(x, _) - | ast::ExprKind::Index(x, _) => { + | ast::ExprKind::Index(x, _, _) => { // &X { y: 1 }, X { y: 1 }.y contains_exterior_struct_lit(x) } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 275692ad5dda7..6d474de2d15f1 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -308,8 +308,12 @@ pub fn walk_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a Item) { match &item.kind { ItemKind::ExternCrate(_) => {} ItemKind::Use(use_tree) => visitor.visit_use_tree(use_tree, item.id, false), - ItemKind::Static(box StaticItem { ty, mutability: _, expr }) - | ItemKind::Const(box ConstItem { ty, expr, .. }) => { + ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => { + visitor.visit_ty(ty); + walk_list!(visitor, visit_expr, expr); + } + ItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => { + visitor.visit_generics(generics); visitor.visit_ty(ty); walk_list!(visitor, visit_expr, expr); } @@ -677,7 +681,8 @@ pub fn walk_assoc_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a AssocItem, visitor.visit_ident(ident); walk_list!(visitor, visit_attribute, attrs); match kind { - AssocItemKind::Const(box ConstItem { ty, expr, .. }) => { + AssocItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => { + visitor.visit_generics(generics); visitor.visit_ty(ty); walk_list!(visitor, visit_expr, expr); } @@ -880,7 +885,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { visitor.visit_expr(subexpression); visitor.visit_ident(*ident); } - ExprKind::Index(main_expression, index_expression) => { + ExprKind::Index(main_expression, index_expression, _) => { visitor.visit_expr(main_expression); visitor.visit_expr(index_expression) } @@ -908,6 +913,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { ExprKind::Yeet(optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } + ExprKind::Become(expr) => visitor.visit_expr(expr), ExprKind::MacCall(mac) => visitor.visit_mac_call(mac), ExprKind::Paren(subexpression) => visitor.visit_expr(subexpression), ExprKind::InlineAsm(asm) => visitor.visit_inline_asm(asm), diff --git a/compiler/rustc_ast_lowering/messages.ftl b/compiler/rustc_ast_lowering/messages.ftl index 21b2a3c22fa10..f63a9bfcd7055 100644 --- a/compiler/rustc_ast_lowering/messages.ftl +++ b/compiler/rustc_ast_lowering/messages.ftl @@ -1,99 +1,121 @@ -ast_lowering_generic_type_with_parentheses = - parenthesized type parameters may only be used with a `Fn` trait - .label = only `Fn` traits may use parentheses - -ast_lowering_use_angle_brackets = use angle brackets instead +ast_lowering_abi_specified_multiple_times = + `{$prev_name}` ABI specified multiple times + .label = previously specified here + .note = these ABIs are equivalent on the current target -ast_lowering_invalid_abi = - invalid ABI: found `{$abi}` - .label = invalid ABI - .note = invoke `{$command}` for a full list of supported calling conventions. +ast_lowering_arbitrary_expression_in_pattern = + arbitrary expressions aren't allowed in patterns -ast_lowering_invalid_abi_suggestion = did you mean +ast_lowering_argument = argument ast_lowering_assoc_ty_parentheses = parenthesized generic arguments cannot be used in associated type constraints -ast_lowering_remove_parentheses = remove these parentheses - -ast_lowering_misplaced_impl_trait = - `impl Trait` only allowed in function and inherent method return types, not in {$position} - -ast_lowering_misplaced_assoc_ty_binding = - associated type bounds are only allowed in where clauses and function signatures, not in {$position} +ast_lowering_async_generators_not_supported = + `async` generators are not yet supported -ast_lowering_underscore_expr_lhs_assign = - in expressions, `_` can only be used on the left-hand side of an assignment - .label = `_` not allowed here +ast_lowering_async_non_move_closure_not_supported = + `async` non-`move` closures with parameters are not currently supported + .help = consider using `let` statements to manually capture variables by reference before entering an `async move` closure -ast_lowering_base_expression_double_dot = - base expression required after `..` - .label = add a base expression here +ast_lowering_att_syntax_only_x86 = + the `att_syntax` option is only supported on x86 ast_lowering_await_only_in_async_fn_and_blocks = `await` is only allowed inside `async` functions and blocks .label = only allowed inside `async` functions and blocks -ast_lowering_this_not_async = this is not `async` +ast_lowering_bad_return_type_notation_inputs = + argument types not allowed with return type notation + .suggestion = remove the input types -ast_lowering_generator_too_many_parameters = - too many parameters for a generator (expected 0 or 1 parameters) +ast_lowering_bad_return_type_notation_needs_dots = + return type notation arguments must be elided with `..` + .suggestion = add `..` + +ast_lowering_bad_return_type_notation_output = + return type not allowed with return type notation + .suggestion = remove the return type + +ast_lowering_base_expression_double_dot = + base expression required after `..` + .label = add a base expression here + +ast_lowering_clobber_abi_not_supported = + `clobber_abi` is not supported on this target ast_lowering_closure_cannot_be_static = closures cannot be static -ast_lowering_async_non_move_closure_not_supported = - `async` non-`move` closures with parameters are not currently supported - .help = consider using `let` statements to manually capture variables by reference before entering an `async move` closure +ast_lowering_does_not_support_modifiers = + the `{$class_name}` register class does not support template modifiers + +ast_lowering_extra_double_dot = + `..` can only be used once per {$ctx} pattern + .label = can only be used once per {$ctx} pattern ast_lowering_functional_record_update_destructuring_assignment = functional record updates are not allowed in destructuring assignments .suggestion = consider removing the trailing pattern -ast_lowering_async_generators_not_supported = - `async` generators are not yet supported +ast_lowering_generator_too_many_parameters = + too many parameters for a generator (expected 0 or 1 parameters) -ast_lowering_inline_asm_unsupported_target = - inline assembly is unsupported on this target +ast_lowering_generic_type_with_parentheses = + parenthesized type parameters may only be used with a `Fn` trait + .label = only `Fn` traits may use parentheses -ast_lowering_att_syntax_only_x86 = - the `att_syntax` option is only supported on x86 +ast_lowering_inclusive_range_with_no_end = inclusive range with no end -ast_lowering_abi_specified_multiple_times = - `{$prev_name}` ABI specified multiple times - .label = previously specified here - .note = these ABIs are equivalent on the current target +ast_lowering_inline_asm_unsupported_target = + inline assembly is unsupported on this target -ast_lowering_clobber_abi_not_supported = - `clobber_abi` is not supported on this target +ast_lowering_invalid_abi = + invalid ABI: found `{$abi}` + .label = invalid ABI + .note = invoke `{$command}` for a full list of supported calling conventions. ast_lowering_invalid_abi_clobber_abi = invalid ABI for `clobber_abi` .note = the following ABIs are supported on this target: {$supported_abis} +ast_lowering_invalid_abi_suggestion = did you mean + +ast_lowering_invalid_asm_template_modifier_const = + asm template modifiers are not allowed for `const` arguments + +ast_lowering_invalid_asm_template_modifier_reg_class = + invalid asm template modifier for this register class + +ast_lowering_invalid_asm_template_modifier_sym = + asm template modifiers are not allowed for `sym` arguments + ast_lowering_invalid_register = invalid register `{$reg}`: {$error} ast_lowering_invalid_register_class = invalid register class `{$reg_class}`: {$error} -ast_lowering_invalid_asm_template_modifier_reg_class = - invalid asm template modifier for this register class +ast_lowering_misplaced_assoc_ty_binding = + associated type bounds are only allowed in where clauses and function signatures, not in {$position} -ast_lowering_argument = argument +ast_lowering_misplaced_double_dot = + `..` patterns are not allowed here + .note = only allowed in tuple, tuple struct, and slice patterns -ast_lowering_template_modifier = template modifier +ast_lowering_misplaced_impl_trait = + `impl Trait` only allowed in function and inherent method return types, not in {$position} -ast_lowering_support_modifiers = - the `{$class_name}` register class supports the following template modifiers: {$modifiers} +ast_lowering_misplaced_relax_trait_bound = + `?Trait` bounds are only permitted at the point where a type parameter is declared -ast_lowering_does_not_support_modifiers = - the `{$class_name}` register class does not support template modifiers +ast_lowering_not_supported_for_lifetime_binder_async_closure = + `for<...>` binders on `async` closures are not currently supported -ast_lowering_invalid_asm_template_modifier_const = - asm template modifiers are not allowed for `const` arguments +ast_lowering_previously_used_here = previously used here -ast_lowering_invalid_asm_template_modifier_sym = - asm template modifiers are not allowed for `sym` arguments +ast_lowering_register1 = register `{$reg1_name}` + +ast_lowering_register2 = register `{$reg2_name}` ast_lowering_register_class_only_clobber = register class `{$reg_class_name}` can only be used as a clobber, not as an input or output @@ -102,9 +124,7 @@ ast_lowering_register_conflict = register `{$reg1_name}` conflicts with register `{$reg2_name}` .help = use `lateout` instead of `out` to avoid conflict -ast_lowering_register1 = register `{$reg1_name}` - -ast_lowering_register2 = register `{$reg2_name}` +ast_lowering_remove_parentheses = remove these parentheses ast_lowering_sub_tuple_binding = `{$ident_name} @` is not allowed in a {$ctx} @@ -113,26 +133,12 @@ ast_lowering_sub_tuple_binding = ast_lowering_sub_tuple_binding_suggestion = if you don't need to use the contents of {$ident}, discard the tuple's remaining fields -ast_lowering_extra_double_dot = - `..` can only be used once per {$ctx} pattern - .label = can only be used once per {$ctx} pattern - -ast_lowering_previously_used_here = previously used here - -ast_lowering_misplaced_double_dot = - `..` patterns are not allowed here - .note = only allowed in tuple, tuple struct, and slice patterns - -ast_lowering_misplaced_relax_trait_bound = - `?Trait` bounds are only permitted at the point where a type parameter is declared - -ast_lowering_not_supported_for_lifetime_binder_async_closure = - `for<...>` binders on `async` closures are not currently supported +ast_lowering_support_modifiers = + the `{$class_name}` register class supports the following template modifiers: {$modifiers} -ast_lowering_arbitrary_expression_in_pattern = - arbitrary expressions aren't allowed in patterns +ast_lowering_template_modifier = template modifier -ast_lowering_inclusive_range_with_no_end = inclusive range with no end +ast_lowering_this_not_async = this is not `async` ast_lowering_trait_fn_async = functions in traits cannot be declared `async` @@ -140,14 +146,8 @@ ast_lowering_trait_fn_async = .note = `async` trait functions are not currently supported .note2 = consider using the `async-trait` crate: https://crates.io/crates/async-trait -ast_lowering_bad_return_type_notation_inputs = - argument types not allowed with return type notation - .suggestion = remove the input types - -ast_lowering_bad_return_type_notation_needs_dots = - return type notation arguments must be elided with `..` - .suggestion = add `..` +ast_lowering_underscore_expr_lhs_assign = + in expressions, `_` can only be used on the left-hand side of an assignment + .label = `_` not allowed here -ast_lowering_bad_return_type_notation_output = - return type not allowed with return type notation - .suggestion = remove the return type +ast_lowering_use_angle_brackets = use angle brackets instead diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index 941d3179587eb..a1e626996800f 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -44,6 +44,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { | asm::InlineAsmArch::AArch64 | asm::InlineAsmArch::RiscV32 | asm::InlineAsmArch::RiscV64 + | asm::InlineAsmArch::LoongArch64 ); if !is_stable && !self.tcx.features().asm_experimental_arch { feature_err( @@ -206,6 +207,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { &sym.path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); hir::InlineAsmOperand::SymStatic { path, def_id } } else { @@ -351,7 +353,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let idx2 = *o.get(); let (ref op2, op_sp2) = operands[idx2]; - let Some(asm::InlineAsmRegOrRegClass::Reg(reg2)) = op2.reg() else { + let Some(asm::InlineAsmRegOrRegClass::Reg(reg2)) = op2.reg() + else { unreachable!(); }; @@ -367,7 +370,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { assert!(!*late); let out_op_sp = if input { op_sp2 } else { op_sp }; Some(out_op_sp) - }, + } _ => None, }; @@ -376,7 +379,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { op_span2: op_sp2, reg1_name: reg.name(), reg2_name: reg2.name(), - in_out + in_out, }); } Entry::Vacant(v) => { diff --git a/compiler/rustc_ast_lowering/src/errors.rs b/compiler/rustc_ast_lowering/src/errors.rs index 72dc52a632992..a63bd4f8a0255 100644 --- a/compiler/rustc_ast_lowering/src/errors.rs +++ b/compiler/rustc_ast_lowering/src/errors.rs @@ -31,9 +31,26 @@ pub struct InvalidAbi { pub abi: Symbol, pub command: String, #[subdiagnostic] + pub explain: Option, + #[subdiagnostic] pub suggestion: Option, } +pub struct InvalidAbiReason(pub &'static str); + +impl rustc_errors::AddToDiagnostic for InvalidAbiReason { + fn add_to_diagnostic_with(self, diag: &mut rustc_errors::Diagnostic, _: F) + where + F: Fn( + &mut rustc_errors::Diagnostic, + rustc_errors::SubdiagnosticMessage, + ) -> rustc_errors::SubdiagnosticMessage, + { + #[allow(rustc::untranslatable_diagnostic)] + diag.note(self.0); + } +} + #[derive(Subdiagnostic)] #[suggestion( ast_lowering_invalid_abi_suggestion, diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 5e0ab80c6ac9f..7408b4fb0af48 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -71,9 +71,13 @@ impl<'hir> LoweringContext<'_, 'hir> { let kind = match &e.kind { ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(exprs)), - ExprKind::ConstBlock(anon_const) => { - let anon_const = self.lower_anon_const(anon_const); - hir::ExprKind::ConstBlock(anon_const) + ExprKind::ConstBlock(c) => { + let c = self.with_new_scopes(|this| hir::ConstBlock { + def_id: this.local_def_id(c.id), + hir_id: this.lower_node_id(c.id), + body: this.lower_const_body(c.value.span, Some(&c.value)), + }); + hir::ExprKind::ConstBlock(c) } ExprKind::Repeat(expr, count) => { let expr = self.lower_expr(expr); @@ -96,6 +100,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ParamMode::Optional, ParenthesizedGenericArgs::Err, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, )); let receiver = self.lower_expr(receiver); let args = @@ -236,8 +241,8 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::Field(el, ident) => { hir::ExprKind::Field(self.lower_expr(el), self.lower_ident(*ident)) } - ExprKind::Index(el, er) => { - hir::ExprKind::Index(self.lower_expr(el), self.lower_expr(er)) + ExprKind::Index(el, er, brackets_span) => { + hir::ExprKind::Index(self.lower_expr(el), self.lower_expr(er), *brackets_span) } ExprKind::Range(Some(e1), Some(e2), RangeLimits::Closed) => { self.lower_expr_range_closed(e.span, e1, e2) @@ -256,6 +261,7 @@ impl<'hir> LoweringContext<'_, 'hir> { path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); hir::ExprKind::Path(qpath) } @@ -271,6 +277,10 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Ret(e) } ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()), + ExprKind::Become(sub_expr) => { + let sub_expr = self.lower_expr(sub_expr); + hir::ExprKind::Become(sub_expr) + } ExprKind::InlineAsm(asm) => { hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm)) } @@ -278,7 +288,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::OffsetOf(container, fields) => hir::ExprKind::OffsetOf( self.lower_ty( container, - &mut ImplTraitContext::Disallowed(ImplTraitPosition::OffsetOf), + &ImplTraitContext::Disallowed(ImplTraitPosition::OffsetOf), ), self.arena.alloc_from_iter(fields.iter().map(|&ident| self.lower_ident(ident))), ), @@ -299,6 +309,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &se.path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, )), self.arena .alloc_from_iter(se.fields.iter().map(|x| self.lower_expr_field(x))), @@ -649,14 +660,14 @@ impl<'hir> LoweringContext<'_, 'hir> { } /// Forwards a possible `#[track_caller]` annotation from `outer_hir_id` to - /// `inner_hir_id` in case the `closure_track_caller` feature is enabled. + /// `inner_hir_id` in case the `async_fn_track_caller` feature is enabled. pub(super) fn maybe_forward_track_caller( &mut self, span: Span, outer_hir_id: hir::HirId, inner_hir_id: hir::HirId, ) { - if self.tcx.features().closure_track_caller + if self.tcx.features().async_fn_track_caller && let Some(attrs) = self.attrs.get(&outer_hir_id.local_id) && attrs.into_iter().any(|attr| attr.has_name(sym::track_caller)) { @@ -665,14 +676,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_attrs( inner_hir_id, &[Attribute { - kind: AttrKind::Normal(ptr::P(NormalAttr { - item: AttrItem { - path: Path::from_ident(Ident::new(sym::track_caller, span)), - args: AttrArgs::Empty, - tokens: None, - }, - tokens: None, - })), + kind: AttrKind::Normal(ptr::P(NormalAttr::from_ident(Ident::new(sym::track_caller, span)))), id: self.tcx.sess.parse_sess.attr_id_generator.mk_attr_id(), style: AttrStyle::Outer, span: unstable_span, @@ -1178,6 +1182,7 @@ impl<'hir> LoweringContext<'_, 'hir> { path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); // Destructure like a tuple struct. let tuple_struct_pat = hir::PatKind::TupleStruct( @@ -1197,6 +1202,7 @@ impl<'hir> LoweringContext<'_, 'hir> { path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); // Destructure like a unit struct. let unit_struct_pat = hir::PatKind::Path(qpath); @@ -1221,6 +1227,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &se.path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); let fields_omitted = match &se.rest { StructRest::Base(e) => { @@ -1641,7 +1648,7 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Match( scrutinee, arena_vec![self; break_arm, continue_arm], - hir::MatchSource::TryDesugar, + hir::MatchSource::TryDesugar(scrutinee.hir_id), ) } diff --git a/compiler/rustc_ast_lowering/src/index.rs b/compiler/rustc_ast_lowering/src/index.rs index 2e66c81eb0d05..ce847906fb99a 100644 --- a/compiler/rustc_ast_lowering/src/index.rs +++ b/compiler/rustc_ast_lowering/src/index.rs @@ -223,6 +223,14 @@ impl<'a, 'hir> Visitor<'hir> for NodeCollector<'a, 'hir> { }); } + fn visit_inline_const(&mut self, constant: &'hir ConstBlock) { + self.insert(DUMMY_SP, constant.hir_id, Node::ConstBlock(constant)); + + self.with_parent(constant.hir_id, |this| { + intravisit::walk_inline_const(this, constant); + }); + } + fn visit_expr(&mut self, expr: &'hir Expr<'hir>) { self.insert(expr.span, expr.hir_id, Node::Expr(expr)); diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 08ee3761bac2b..a59c83de0f46f 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1,8 +1,9 @@ -use super::errors::{InvalidAbi, InvalidAbiSuggestion, MisplacedRelaxTraitBound}; +use super::errors::{InvalidAbi, InvalidAbiReason, InvalidAbiSuggestion, MisplacedRelaxTraitBound}; use super::ResolverAstLoweringExt; use super::{AstOwner, ImplTraitContext, ImplTraitPosition}; use super::{FnDeclKind, LoweringContext, ParamMode}; +use hir::definitions::DefPathData; use rustc_ast::ptr::P; use rustc_ast::visit::AssocCtxt; use rustc_ast::*; @@ -55,6 +56,11 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> { owner: NodeId, f: impl FnOnce(&mut LoweringContext<'_, 'hir>) -> hir::OwnerNode<'hir>, ) { + let allow_gen_future = Some(if self.tcx.features().async_fn_track_caller { + [sym::gen_future, sym::closure_track_caller][..].into() + } else { + [sym::gen_future][..].into() + }); let mut lctx = LoweringContext { // Pseudo-globals. tcx: self.tcx, @@ -82,8 +88,9 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> { impl_trait_defs: Vec::new(), impl_trait_bounds: Vec::new(), allow_try_trait: Some([sym::try_trait_v2, sym::yeet_desugar_details][..].into()), - allow_gen_future: Some([sym::gen_future, sym::closure_track_caller][..].into()), + allow_gen_future, generics_def_id_map: Default::default(), + host_param_id: None, }; lctx.with_hir_id_owner(owner, |lctx| f(lctx)); @@ -138,8 +145,24 @@ impl<'a, 'hir> ItemLowerer<'a, 'hir> { // This is used to track which lifetimes have already been defined, // and which need to be replicated when lowering an async fn. - if let hir::ItemKind::Impl(impl_) = parent_hir.node().expect_item().kind { - lctx.is_in_trait_impl = impl_.of_trait.is_some(); + match parent_hir.node().expect_item().kind { + hir::ItemKind::Impl(impl_) => { + lctx.is_in_trait_impl = impl_.of_trait.is_some(); + } + hir::ItemKind::Trait(_, _, generics, _, _) if lctx.tcx.features().effects => { + lctx.host_param_id = generics + .params + .iter() + .find(|param| { + parent_hir + .attrs + .get(param.hir_id.local_id) + .iter() + .any(|attr| attr.has_name(sym::rustc_host)) + }) + .map(|param| param.def_id); + } + _ => {} } match ctxt { @@ -230,9 +253,15 @@ impl<'hir> LoweringContext<'_, 'hir> { let (ty, body_id) = self.lower_const_item(t, span, e.as_deref()); hir::ItemKind::Static(ty, *m, body_id) } - ItemKind::Const(box ast::ConstItem { ty, expr, .. }) => { - let (ty, body_id) = self.lower_const_item(ty, span, expr.as_deref()); - hir::ItemKind::Const(ty, body_id) + ItemKind::Const(box ast::ConstItem { generics, ty, expr, .. }) => { + let (generics, (ty, body_id)) = self.lower_generics( + generics, + Const::No, + id, + &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), + |this| this.lower_const_item(ty, span, expr.as_deref()), + ); + hir::ItemKind::Const(ty, generics, body_id) } ItemKind::Fn(box Fn { sig: FnSig { decl, header, span: fn_sig_span }, @@ -257,10 +286,11 @@ impl<'hir> LoweringContext<'_, 'hir> { ); let itctx = ImplTraitContext::Universal; - let (generics, decl) = this.lower_generics(generics, id, &itctx, |this| { - let ret_id = asyncness.opt_return_id(); - this.lower_fn_decl(&decl, id, *fn_sig_span, FnDeclKind::Fn, ret_id) - }); + let (generics, decl) = + this.lower_generics(generics, header.constness, id, &itctx, |this| { + let ret_id = asyncness.opt_return_id(); + this.lower_fn_decl(&decl, id, *fn_sig_span, FnDeclKind::Fn, ret_id) + }); let sig = hir::FnSig { decl, header: this.lower_fn_header(*header), @@ -295,6 +325,7 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, *where_clauses, true); let (generics, ty) = self.lower_generics( &generics, + Const::No, id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { @@ -316,6 +347,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ItemKind::Enum(enum_definition, generics) => { let (generics, variants) = self.lower_generics( generics, + Const::No, id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { @@ -329,6 +361,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ItemKind::Struct(struct_def, generics) => { let (generics, struct_def) = self.lower_generics( generics, + Const::No, id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, struct_def), @@ -338,6 +371,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ItemKind::Union(vdata, generics) => { let (generics, vdata) = self.lower_generics( generics, + Const::No, id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, vdata), @@ -369,9 +403,10 @@ impl<'hir> LoweringContext<'_, 'hir> { // parent lifetime. let itctx = ImplTraitContext::Universal; let (generics, (trait_ref, lowered_ty)) = - self.lower_generics(ast_generics, id, &itctx, |this| { + self.lower_generics(ast_generics, *constness, id, &itctx, |this| { let trait_ref = trait_ref.as_ref().map(|trait_ref| { this.lower_trait_ref( + *constness, trait_ref, &ImplTraitContext::Disallowed(ImplTraitPosition::Trait), ) @@ -402,7 +437,6 @@ impl<'hir> LoweringContext<'_, 'hir> { polarity, defaultness, defaultness_span, - constness: self.lower_constness(*constness), generics, of_trait: trait_ref, self_ty: lowered_ty, @@ -410,8 +444,15 @@ impl<'hir> LoweringContext<'_, 'hir> { })) } ItemKind::Trait(box Trait { is_auto, unsafety, generics, bounds, items }) => { + // FIXME(const_trait_impl, effects, fee1-dead) this should be simplified if possible + let constness = attrs + .unwrap_or(&[]) + .iter() + .find(|x| x.has_name(sym::const_trait)) + .map_or(Const::No, |x| Const::Yes(x.span)); let (generics, (unsafety, items, bounds)) = self.lower_generics( generics, + constness, id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { @@ -431,6 +472,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ItemKind::TraitAlias(generics, bounds) => { let (generics, bounds) = self.lower_generics( generics, + Const::No, id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { @@ -537,17 +579,6 @@ impl<'hir> LoweringContext<'_, 'hir> { for &(ref use_tree, id) in trees { let new_hir_id = self.local_def_id(id); - let mut prefix = prefix.clone(); - - // Give the segments new node-ids since they are being cloned. - for seg in &mut prefix.segments { - // Give the cloned segment the same resolution information - // as the old one (this is needed for stability checking). - let new_id = self.next_node_id(); - self.resolver.clone_res(seg.id, new_id); - seg.id = new_id; - } - // Each `use` import is an item and thus are owners of the // names in the path. Up to this point the nested import is // the current owner, since we want each desugared import to @@ -556,6 +587,9 @@ impl<'hir> LoweringContext<'_, 'hir> { self.with_hir_id_owner(id, |this| { let mut ident = *ident; + // `prefix` is lowered multiple times, but in different HIR owners. + // So each segment gets renewed `HirId` with the same + // `ItemLocalId` and the new owner. (See `lower_node_id`) let kind = this.lower_use_tree(use_tree, &prefix, id, vis_span, &mut ident, attrs); if let Some(attrs) = attrs { @@ -593,7 +627,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let fdec = &sig.decl; let itctx = ImplTraitContext::Universal; let (generics, (fn_dec, fn_args)) = - self.lower_generics(generics, i.id, &itctx, |this| { + self.lower_generics(generics, Const::No, i.id, &itctx, |this| { ( // Disallow `impl Trait` in foreign items. this.lower_fn_decl( @@ -709,11 +743,23 @@ impl<'hir> LoweringContext<'_, 'hir> { let trait_item_def_id = hir_id.expect_owner(); let (generics, kind, has_default) = match &i.kind { - AssocItemKind::Const(box ConstItem { ty, expr, .. }) => { - let ty = - self.lower_ty(ty, &ImplTraitContext::Disallowed(ImplTraitPosition::ConstTy)); - let body = expr.as_ref().map(|x| self.lower_const_body(i.span, Some(x))); - (hir::Generics::empty(), hir::TraitItemKind::Const(ty, body), body.is_some()) + AssocItemKind::Const(box ConstItem { generics, ty, expr, .. }) => { + let (generics, kind) = self.lower_generics( + &generics, + Const::No, + i.id, + &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), + |this| { + let ty = this.lower_ty( + ty, + &ImplTraitContext::Disallowed(ImplTraitPosition::ConstTy), + ); + let body = expr.as_ref().map(|x| this.lower_const_body(i.span, Some(x))); + + hir::TraitItemKind::Const(ty, body) + }, + ); + (generics, kind, expr.is_some()) } AssocItemKind::Fn(box Fn { sig, generics, body: None, .. }) => { let asyncness = sig.header.asyncness; @@ -745,6 +791,7 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, *where_clauses, false); let (generics, kind) = self.lower_generics( &generics, + Const::No, i.id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { @@ -810,14 +857,19 @@ impl<'hir> LoweringContext<'_, 'hir> { self.lower_attrs(hir_id, &i.attrs); let (generics, kind) = match &i.kind { - AssocItemKind::Const(box ConstItem { ty, expr, .. }) => { - let ty = - self.lower_ty(ty, &ImplTraitContext::Disallowed(ImplTraitPosition::ConstTy)); - ( - hir::Generics::empty(), - hir::ImplItemKind::Const(ty, self.lower_const_body(i.span, expr.as_deref())), - ) - } + AssocItemKind::Const(box ConstItem { generics, ty, expr, .. }) => self.lower_generics( + &generics, + Const::No, + i.id, + &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), + |this| { + let ty = this + .lower_ty(ty, &ImplTraitContext::Disallowed(ImplTraitPosition::ConstTy)); + let body = this.lower_const_body(i.span, expr.as_deref()); + + hir::ImplItemKind::Const(ty, body) + }, + ), AssocItemKind::Fn(box Fn { sig, generics, body, .. }) => { self.current_item = Some(i.span); let asyncness = sig.header.asyncness; @@ -843,6 +895,7 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, *where_clauses, false); self.lower_generics( &generics, + Const::No, i.id, &ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { @@ -1201,9 +1254,10 @@ impl<'hir> LoweringContext<'_, 'hir> { ) -> (&'hir hir::Generics<'hir>, hir::FnSig<'hir>) { let header = self.lower_fn_header(sig.header); let itctx = ImplTraitContext::Universal; - let (generics, decl) = self.lower_generics(generics, id, &itctx, |this| { - this.lower_fn_decl(&sig.decl, id, sig.span, kind, is_async) - }); + let (generics, decl) = + self.lower_generics(generics, sig.header.constness, id, &itctx, |this| { + this.lower_fn_decl(&sig.decl, id, sig.span, kind, is_async) + }); (generics, hir::FnSig { header, decl, span: self.lower_span(sig.span) }) } @@ -1217,8 +1271,8 @@ impl<'hir> LoweringContext<'_, 'hir> { } pub(super) fn lower_abi(&mut self, abi: StrLit) -> abi::Abi { - abi::lookup(abi.symbol_unescaped.as_str()).unwrap_or_else(|| { - self.error_on_invalid_abi(abi); + abi::lookup(abi.symbol_unescaped.as_str()).unwrap_or_else(|err| { + self.error_on_invalid_abi(abi, err); abi::Abi::Rust }) } @@ -1231,7 +1285,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn error_on_invalid_abi(&self, abi: StrLit) { + fn error_on_invalid_abi(&self, abi: StrLit, err: abi::AbiUnsupported) { let abi_names = abi::enabled_names(self.tcx.features(), abi.span) .iter() .map(|s| Symbol::intern(s)) @@ -1240,6 +1294,10 @@ impl<'hir> LoweringContext<'_, 'hir> { self.tcx.sess.emit_err(InvalidAbi { abi: abi.symbol_unescaped, span: abi.span, + explain: match err { + abi::AbiUnsupported::Reason { explain } => Some(InvalidAbiReason(explain)), + _ => None, + }, suggestion: suggested_name.map(|suggested_name| InvalidAbiSuggestion { span: abi.span, suggestion: format!("\"{suggested_name}\""), @@ -1275,6 +1333,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_generics( &mut self, generics: &Generics, + constness: Const, parent_node_id: NodeId, itctx: &ImplTraitContext, f: impl FnOnce(&mut Self) -> T, @@ -1325,6 +1384,29 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + // Desugar `~const` bound in generics into an additional `const host: bool` param + // if the effects feature is enabled. This needs to be done before we lower where + // clauses since where clauses need to bind to the DefId of the host param + let host_param_parts = if let Const::Yes(span) = constness && self.tcx.features().effects { + if let Some(param) = generics.params.iter().find(|x| { + x.attrs.iter().any(|x| x.has_name(sym::rustc_host)) + }) { + // user has manually specified a `rustc_host` param, in this case, we set + // the param id so that lowering logic can use that. But we don't create + // another host param, so this gives `None`. + self.host_param_id = Some(self.local_def_id(param.id)); + None + } else { + let param_node_id = self.next_node_id(); + let hir_id = self.next_id(); + let def_id = self.create_def(self.local_def_id(parent_node_id), param_node_id, DefPathData::TypeNs(sym::host), span); + self.host_param_id = Some(def_id); + Some((span, hir_id, def_id)) + } + } else { + None + }; + let mut predicates: SmallVec<[hir::WherePredicate<'hir>; 4]> = SmallVec::new(); predicates.extend(generics.params.iter().filter_map(|param| { self.lower_generic_bound_predicate( @@ -1372,6 +1454,81 @@ impl<'hir> LoweringContext<'_, 'hir> { let impl_trait_bounds = std::mem::take(&mut self.impl_trait_bounds); predicates.extend(impl_trait_bounds.into_iter()); + if let Some((span, hir_id, def_id)) = host_param_parts { + let const_node_id = self.next_node_id(); + let anon_const: LocalDefId = + self.create_def(def_id, const_node_id, DefPathData::AnonConst, span); + + let const_id = self.next_id(); + let const_expr_id = self.next_id(); + let bool_id = self.next_id(); + + self.children.push((def_id, hir::MaybeOwner::NonOwner(hir_id))); + self.children.push((anon_const, hir::MaybeOwner::NonOwner(const_id))); + + let attr_id = self.tcx.sess.parse_sess.attr_id_generator.mk_attr_id(); + + let attrs = self.arena.alloc_from_iter([Attribute { + kind: AttrKind::Normal(P(NormalAttr::from_ident(Ident::new( + sym::rustc_host, + span, + )))), + span, + id: attr_id, + style: AttrStyle::Outer, + }]); + self.attrs.insert(hir_id.local_id, attrs); + + let const_body = self.lower_body(|this| { + ( + &[], + hir::Expr { + hir_id: const_expr_id, + kind: hir::ExprKind::Lit( + this.arena.alloc(hir::Lit { node: LitKind::Bool(true), span }), + ), + span, + }, + ) + }); + + let param = hir::GenericParam { + def_id, + hir_id, + name: hir::ParamName::Plain(Ident { name: sym::host, span }), + span, + kind: hir::GenericParamKind::Const { + ty: self.arena.alloc(self.ty( + span, + hir::TyKind::Path(hir::QPath::Resolved( + None, + self.arena.alloc(hir::Path { + res: Res::PrimTy(hir::PrimTy::Bool), + span, + segments: self.arena.alloc_from_iter([hir::PathSegment { + ident: Ident { name: sym::bool, span }, + hir_id: bool_id, + res: Res::PrimTy(hir::PrimTy::Bool), + args: None, + infer_args: false, + }]), + }), + )), + )), + default: Some(hir::AnonConst { + def_id: anon_const, + hir_id: const_id, + body: const_body, + }), + }, + colon_span: None, + pure_wrt_drop: false, + source: hir::GenericParamSource::Generics, + }; + + params.push(param); + } + let lowered_generics = self.arena.alloc(hir::Generics { params: self.arena.alloc_from_iter(params), predicates: self.arena.alloc_from_iter(predicates), diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 211f5cb0a2a3c..4a47de1280c63 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -142,16 +142,14 @@ struct LoweringContext<'a, 'hir> { /// defined on the TAIT, so we have type Foo<'a1> = ... and we establish a mapping in this /// field from the original parameter 'a to the new parameter 'a1. generics_def_id_map: Vec>, + + host_param_id: Option, } trait ResolverAstLoweringExt { fn legacy_const_generic_args(&self, expr: &Expr) -> Option>; fn get_partial_res(&self, id: NodeId) -> Option; fn get_import_res(&self, id: NodeId) -> PerNS>>; - // Clones the resolution (if any) on 'source' and applies it - // to 'target'. Used when desugaring a `UseTreeKind::Nested` to - // multiple `UseTreeKind::Simple`s - fn clone_res(&mut self, source: NodeId, target: NodeId); fn get_label_res(&self, id: NodeId) -> Option; fn get_lifetime_res(&self, id: NodeId) -> Option; fn take_extra_lifetime_params(&mut self, id: NodeId) -> Vec<(Ident, NodeId, LifetimeRes)>; @@ -184,12 +182,6 @@ impl ResolverAstLoweringExt for ResolverAstLowering { None } - fn clone_res(&mut self, source: NodeId, target: NodeId) { - if let Some(res) = self.partial_res_map.get(&source) { - self.partial_res_map.insert(target, *res); - } - } - /// Obtains resolution for a `NodeId` with a single resolution. fn get_partial_res(&self, id: NodeId) -> Option { self.partial_res_map.get(&id).copied() @@ -465,7 +457,7 @@ pub fn lower_to_hir(tcx: TyCtxt<'_>, (): ()) -> hir::Crate<'_> { // Don't hash unless necessary, because it's expensive. let opt_hir_hash = - if tcx.sess.needs_crate_hash() { Some(compute_hir_hash(tcx, &owners)) } else { None }; + if tcx.needs_crate_hash() { Some(compute_hir_hash(tcx, &owners)) } else { None }; hir::Crate { owners, opt_hir_hash } } @@ -522,11 +514,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { self.resolver.node_id_to_def_id.get(&node).map(|local_def_id| *local_def_id) } - fn orig_local_def_id(&self, node: NodeId) -> LocalDefId { - self.orig_opt_local_def_id(node) - .unwrap_or_else(|| panic!("no entry for node id: `{node:?}`")) - } - /// Given the id of some node in the AST, finds the `LocalDefId` associated with it by the name /// resolver (if any), after applying any remapping from `get_remapped_def_id`. /// @@ -661,7 +648,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let bodies = SortedMap::from_presorted_elements(bodies); // Don't hash unless necessary, because it's expensive. - let (opt_hash_including_bodies, attrs_hash) = if self.tcx.sess.needs_crate_hash() { + let (opt_hash_including_bodies, attrs_hash) = if self.tcx.needs_crate_hash() { self.tcx.with_stable_hashing_context(|mut hcx| { let mut stable_hasher = StableHasher::new(); hcx.with_hir_bodies(node.def_id(), &bodies, |hcx| { @@ -1277,6 +1264,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { span: t.span }, itctx, + ast::Const::No, ); let bounds = this.arena.alloc_from_iter([bound]); let lifetime_bound = this.elided_dyn_bound(t.span); @@ -1287,7 +1275,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } let id = self.lower_node_id(t.id); - let qpath = self.lower_qpath(t.id, qself, path, param_mode, itctx); + let qpath = self.lower_qpath(t.id, qself, path, param_mode, itctx, None); self.ty_path(id, t.span, qpath) } @@ -1371,10 +1359,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { this.arena.alloc_from_iter(bounds.iter().filter_map(|bound| match bound { GenericBound::Trait( ty, - TraitBoundModifier::None + modifier @ (TraitBoundModifier::None | TraitBoundModifier::MaybeConst - | TraitBoundModifier::Negative, - ) => Some(this.lower_poly_trait_ref(ty, itctx)), + | TraitBoundModifier::Negative), + ) => { + Some(this.lower_poly_trait_ref(ty, itctx, modifier.to_constness())) + } // `~const ?Bound` will cause an error during AST validation // anyways, so treat it like `?Bound` as compilation proceeds. GenericBound::Trait( @@ -1425,7 +1415,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { DefPathData::ImplTrait, span, ); - let ident = Ident::from_str_and_span(&pprust::ty_to_string(t), span); + + // HACK: pprust breaks strings with newlines when the type + // gets too long. We don't want these to show up in compiler + // output or built artifacts, so replace them here... + // Perhaps we should instead format APITs more robustly. + let ident = Ident::from_str_and_span( + &pprust::ty_to_string(t).replace('\n', " "), + span, + ); + let (param, bounds, path) = self.lower_universal_param_and_bounds( *def_node_id, span, @@ -1522,194 +1521,86 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // frequently opened issues show. let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::OpaqueTy, span, None); - let opaque_ty_def_id = self.create_def( - self.current_hir_id_owner.def_id, - opaque_ty_node_id, - DefPathData::ImplTrait, - opaque_ty_span, - ); - debug!(?opaque_ty_def_id); - - // Contains the new lifetime definitions created for the TAIT (if any). - let mut collected_lifetimes = Vec::new(); - - // If this came from a TAIT (as opposed to a function that returns an RPIT), we only want - // to capture the lifetimes that appear in the bounds. So visit the bounds to find out - // exactly which ones those are. - let lifetimes_to_remap = match origin { + let captured_lifetimes_to_duplicate = match origin { hir::OpaqueTyOrigin::TyAlias { .. } => { - // in a TAIT like `type Foo<'a> = impl Foo<'a>`, we don't keep all the lifetime parameters + // in a TAIT like `type Foo<'a> = impl Foo<'a>`, we don't duplicate any + // lifetimes, since we don't have the issue that any are late-bound. Vec::new() } - hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..) => { - // in fn return position, like the `fn test<'a>() -> impl Debug + 'a` example, - // we only keep the lifetimes that appear in the `impl Debug` itself: + hir::OpaqueTyOrigin::FnReturn(..) => { + // in fn return position, like the `fn test<'a>() -> impl Debug + 'a` + // example, we only need to duplicate lifetimes that appear in the + // bounds, since those are the only ones that are captured by the opaque. lifetime_collector::lifetimes_in_bounds(&self.resolver, bounds) } + hir::OpaqueTyOrigin::AsyncFn(..) => { + unreachable!("should be using `lower_async_fn_ret_ty`") + } }; - debug!(?lifetimes_to_remap); - - self.with_hir_id_owner(opaque_ty_node_id, |lctx| { - let mut new_remapping = FxHashMap::default(); - - // If this opaque type is only capturing a subset of the lifetimes (those that appear - // in bounds), then create the new lifetime parameters required and create a mapping - // from the old `'a` (on the function) to the new `'a` (on the opaque type). - collected_lifetimes = lctx.create_lifetime_defs( - opaque_ty_def_id, - &lifetimes_to_remap, - &mut new_remapping, - ); - debug!(?collected_lifetimes); - debug!(?new_remapping); - - // Install the remapping from old to new (if any): - lctx.with_remapping(new_remapping, |lctx| { - // This creates HIR lifetime definitions as `hir::GenericParam`, in the given - // example `type TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection - // containing `&['x]`. - let lifetime_defs = lctx.arena.alloc_from_iter(collected_lifetimes.iter().map( - |&(new_node_id, lifetime)| { - let hir_id = lctx.lower_node_id(new_node_id); - debug_assert_ne!(lctx.opt_local_def_id(new_node_id), None); - - let (name, kind) = if lifetime.ident.name == kw::UnderscoreLifetime { - (hir::ParamName::Fresh, hir::LifetimeParamKind::Elided) - } else { - ( - hir::ParamName::Plain(lifetime.ident), - hir::LifetimeParamKind::Explicit, - ) - }; - - hir::GenericParam { - hir_id, - def_id: lctx.local_def_id(new_node_id), - name, - span: lifetime.ident.span, - pure_wrt_drop: false, - kind: hir::GenericParamKind::Lifetime { kind }, - colon_span: None, - source: hir::GenericParamSource::Generics, - } - }, - )); - debug!(?lifetime_defs); - - // Then when we lower the param bounds, references to 'a are remapped to 'a1, so we - // get back Debug + 'a1, which is suitable for use on the TAIT. - let hir_bounds = lctx.lower_param_bounds(bounds, itctx); - debug!(?hir_bounds); - - let opaque_ty_item = hir::OpaqueTy { - generics: self.arena.alloc(hir::Generics { - params: lifetime_defs, - predicates: &[], - has_where_clause_predicates: false, - where_clause_span: lctx.lower_span(span), - span: lctx.lower_span(span), - }), - bounds: hir_bounds, - origin, - in_trait, - }; - debug!(?opaque_ty_item); - - lctx.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span) - }) - }); + debug!(?captured_lifetimes_to_duplicate); - // This creates HIR lifetime arguments as `hir::GenericArg`, in the given example `type - // TestReturn<'a, T, 'x> = impl Debug + 'x`, it creates a collection containing `&['x]`. - let lifetimes = - self.arena.alloc_from_iter(collected_lifetimes.into_iter().map(|(_, lifetime)| { - let id = self.next_node_id(); - let l = self.new_named_lifetime(lifetime.id, id, lifetime.ident); - hir::GenericArg::Lifetime(l) - })); - debug!(?lifetimes); - - // `impl Trait` now just becomes `Foo<'a, 'b, ..>`. - hir::TyKind::OpaqueDef( - hir::ItemId { owner_id: hir::OwnerId { def_id: opaque_ty_def_id } }, - lifetimes, + self.lower_opaque_inner( + opaque_ty_node_id, + origin, in_trait, + captured_lifetimes_to_duplicate, + span, + opaque_ty_span, + |this| this.lower_param_bounds(bounds, itctx), ) } - /// Registers a new opaque type with the proper `NodeId`s and - /// returns the lowered node-ID for the opaque type. - fn generate_opaque_type( + fn lower_opaque_inner( &mut self, - opaque_ty_id: LocalDefId, - opaque_ty_item: hir::OpaqueTy<'hir>, + opaque_ty_node_id: NodeId, + origin: hir::OpaqueTyOrigin, + in_trait: bool, + captured_lifetimes_to_duplicate: Vec, span: Span, opaque_ty_span: Span, - ) -> hir::OwnerNode<'hir> { - let opaque_ty_item_kind = hir::ItemKind::OpaqueTy(opaque_ty_item); - // Generate an `type Foo = impl Trait;` declaration. - trace!("registering opaque type with id {:#?}", opaque_ty_id); - let opaque_ty_item = hir::Item { - owner_id: hir::OwnerId { def_id: opaque_ty_id }, - ident: Ident::empty(), - kind: opaque_ty_item_kind, - vis_span: self.lower_span(span.shrink_to_lo()), - span: self.lower_span(opaque_ty_span), - }; - hir::OwnerNode::Item(self.arena.alloc(opaque_ty_item)) - } - - /// Given a `parent_def_id`, a list of `lifetimes_in_bounds` and a `remapping` hash to be - /// filled, this function creates new definitions for `Param` and `Fresh` lifetimes, inserts the - /// new definition, adds it to the remapping with the definition of the given lifetime and - /// returns a list of lifetimes to be lowered afterwards. - fn create_lifetime_defs( - &mut self, - parent_def_id: LocalDefId, - lifetimes_in_bounds: &[Lifetime], - remapping: &mut FxHashMap, - ) -> Vec<(NodeId, Lifetime)> { - let mut result = Vec::new(); + lower_item_bounds: impl FnOnce(&mut Self) -> &'hir [hir::GenericBound<'hir>], + ) -> hir::TyKind<'hir> { + let opaque_ty_def_id = self.create_def( + self.current_hir_id_owner.def_id, + opaque_ty_node_id, + DefPathData::ImplTrait, + opaque_ty_span, + ); + debug!(?opaque_ty_def_id); - for lifetime in lifetimes_in_bounds { + // Map from captured (old) lifetime to synthetic (new) lifetime. + // Used to resolve lifetimes in the bounds of the opaque. + let mut captured_to_synthesized_mapping = FxHashMap::default(); + // List of (early-bound) synthetic lifetimes that are owned by the opaque. + // This is used to create the `hir::Generics` owned by the opaque. + let mut synthesized_lifetime_definitions = vec![]; + // Pairs of lifetime arg (that resolves to the captured lifetime) + // and the def-id of the (early-bound) synthetic lifetime definition. + // This is used both to create generics for the `TyKind::OpaqueDef` that + // we return, and also as a captured lifetime mapping for RPITITs. + let mut synthesized_lifetime_args = vec![]; + + for lifetime in captured_lifetimes_to_duplicate { let res = self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error); - debug!(?res); - - match res { - LifetimeRes::Param { param: old_def_id, binder: _ } => { - if remapping.get(&old_def_id).is_none() { - let node_id = self.next_node_id(); - - let new_def_id = self.create_def( - parent_def_id, - node_id, - DefPathData::LifetimeNs(lifetime.ident.name), - lifetime.ident.span, - ); - remapping.insert(old_def_id, new_def_id); - - result.push((node_id, *lifetime)); - } - } + let old_def_id = match res { + LifetimeRes::Param { param: old_def_id, binder: _ } => old_def_id, LifetimeRes::Fresh { param, binder: _ } => { debug_assert_eq!(lifetime.ident.name, kw::UnderscoreLifetime); - if let Some(old_def_id) = self.orig_opt_local_def_id(param) && remapping.get(&old_def_id).is_none() { - let node_id = self.next_node_id(); - - let new_def_id = self.create_def( - parent_def_id, - node_id, - DefPathData::LifetimeNs(kw::UnderscoreLifetime), - lifetime.ident.span, - ); - remapping.insert(old_def_id, new_def_id); - - result.push((node_id, *lifetime)); + if let Some(old_def_id) = self.orig_opt_local_def_id(param) { + old_def_id + } else { + self.tcx + .sess + .delay_span_bug(lifetime.ident.span, "no def-id for fresh lifetime"); + continue; } } - LifetimeRes::Static | LifetimeRes::Error => {} + // Opaques do not capture `'static` + LifetimeRes::Static | LifetimeRes::Error => { + continue; + } res => { let bug_msg = format!( @@ -1718,10 +1609,109 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ); span_bug!(lifetime.ident.span, "{}", bug_msg); } + }; + + if captured_to_synthesized_mapping.get(&old_def_id).is_none() { + // Create a new lifetime parameter local to the opaque. + let duplicated_lifetime_node_id = self.next_node_id(); + let duplicated_lifetime_def_id = self.create_def( + opaque_ty_def_id, + duplicated_lifetime_node_id, + DefPathData::LifetimeNs(lifetime.ident.name), + lifetime.ident.span, + ); + captured_to_synthesized_mapping.insert(old_def_id, duplicated_lifetime_def_id); + // FIXME: Instead of doing this, we could move this whole loop + // into the `with_hir_id_owner`, then just directly construct + // the `hir::GenericParam` here. + synthesized_lifetime_definitions.push(( + duplicated_lifetime_node_id, + duplicated_lifetime_def_id, + lifetime.ident, + )); + + // Now make an arg that we can use for the substs of the opaque tykind. + let id = self.next_node_id(); + let lifetime_arg = self.new_named_lifetime_with_res(id, lifetime.ident, res); + let duplicated_lifetime_def_id = self.local_def_id(duplicated_lifetime_node_id); + synthesized_lifetime_args.push((lifetime_arg, duplicated_lifetime_def_id)) } } - result + self.with_hir_id_owner(opaque_ty_node_id, |this| { + // Install the remapping from old to new (if any). This makes sure that + // any lifetimes that would have resolved to the def-id of captured + // lifetimes are remapped to the new *synthetic* lifetimes of the opaque. + let bounds = this + .with_remapping(captured_to_synthesized_mapping, |this| lower_item_bounds(this)); + + let generic_params = this.arena.alloc_from_iter( + synthesized_lifetime_definitions.iter().map(|&(new_node_id, new_def_id, ident)| { + let hir_id = this.lower_node_id(new_node_id); + let (name, kind) = if ident.name == kw::UnderscoreLifetime { + (hir::ParamName::Fresh, hir::LifetimeParamKind::Elided) + } else { + (hir::ParamName::Plain(ident), hir::LifetimeParamKind::Explicit) + }; + + hir::GenericParam { + hir_id, + def_id: new_def_id, + name, + span: ident.span, + pure_wrt_drop: false, + kind: hir::GenericParamKind::Lifetime { kind }, + colon_span: None, + source: hir::GenericParamSource::Generics, + } + }), + ); + debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params); + + let lifetime_mapping = self.arena.alloc_slice(&synthesized_lifetime_args); + + let opaque_ty_item = hir::OpaqueTy { + generics: this.arena.alloc(hir::Generics { + params: generic_params, + predicates: &[], + has_where_clause_predicates: false, + where_clause_span: this.lower_span(span), + span: this.lower_span(span), + }), + bounds, + origin, + lifetime_mapping, + in_trait, + }; + + // Generate an `type Foo = impl Trait;` declaration. + trace!("registering opaque type with id {:#?}", opaque_ty_def_id); + let opaque_ty_item = hir::Item { + owner_id: hir::OwnerId { def_id: opaque_ty_def_id }, + ident: Ident::empty(), + kind: hir::ItemKind::OpaqueTy(this.arena.alloc(opaque_ty_item)), + vis_span: this.lower_span(span.shrink_to_lo()), + span: this.lower_span(opaque_ty_span), + }; + + hir::OwnerNode::Item(this.arena.alloc(opaque_ty_item)) + }); + + let generic_args = self.arena.alloc_from_iter( + synthesized_lifetime_args + .iter() + .map(|(lifetime, _)| hir::GenericArg::Lifetime(*lifetime)), + ); + + // Create the `Foo<...>` reference itself. Note that the `type + // Foo = impl Trait` is, internally, created as a child of the + // async fn, so the *type parameters* are inherited. It's + // only the lifetime parameters that we must supply. + hir::TyKind::OpaqueDef( + hir::ItemId { owner_id: hir::OwnerId { def_id: opaque_ty_def_id } }, + generic_args, + in_trait, + ) } fn lower_fn_params_to_names(&mut self, decl: &FnDecl) -> &'hir [Ident] { @@ -1799,9 +1789,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } } + let fn_def_id = self.local_def_id(fn_node_id); self.lower_async_fn_ret_ty( &decl.output, - fn_node_id, + fn_def_id, ret_id, matches!(kind, FnDeclKind::Trait), ) @@ -1878,138 +1869,28 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { fn lower_async_fn_ret_ty( &mut self, output: &FnRetTy, - fn_node_id: NodeId, + fn_def_id: LocalDefId, opaque_ty_node_id: NodeId, in_trait: bool, ) -> hir::FnRetTy<'hir> { - let span = output.span(); - + let span = self.lower_span(output.span()); let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::Async, span, None); - let fn_def_id = self.local_def_id(fn_node_id); - - let opaque_ty_def_id = - self.create_def(fn_def_id, opaque_ty_node_id, DefPathData::ImplTrait, opaque_ty_span); - - // When we create the opaque type for this async fn, it is going to have - // to capture all the lifetimes involved in the signature (including in the - // return type). This is done by introducing lifetime parameters for: - // - // - all the explicitly declared lifetimes from the impl and function itself; - // - all the elided lifetimes in the fn arguments; - // - all the elided lifetimes in the return type. - // - // So for example in this snippet: - // - // ```rust - // impl<'a> Foo<'a> { - // async fn bar<'b>(&self, x: &'b Vec, y: &str) -> &u32 { - // // ^ '0 ^ '1 ^ '2 - // // elided lifetimes used below - // } - // } - // ``` - // - // we would create an opaque type like: - // - // ``` - // type Bar<'a, 'b, '0, '1, '2> = impl Future; - // ``` - // - // and we would then desugar `bar` to the equivalent of: - // - // ```rust - // impl<'a> Foo<'a> { - // fn bar<'b, '0, '1>(&'0 self, x: &'b Vec, y: &'1 str) -> Bar<'a, 'b, '0, '1, '_> - // } - // ``` - // - // Note that the final parameter to `Bar` is `'_`, not `'2` -- - // this is because the elided lifetimes from the return type - // should be figured out using the ordinary elision rules, and - // this desugaring achieves that. - - // Calculate all the lifetimes that should be captured - // by the opaque type. This should include all in-scope - // lifetime parameters, including those defined in-band. - - // Contains the new lifetime definitions created for the TAIT (if any) generated for the - // return type. - let mut collected_lifetimes = Vec::new(); - let mut new_remapping = FxHashMap::default(); - - let extra_lifetime_params = self.resolver.take_extra_lifetime_params(opaque_ty_node_id); - debug!(?extra_lifetime_params); - for (ident, outer_node_id, outer_res) in extra_lifetime_params { - let outer_def_id = self.orig_local_def_id(outer_node_id); - let inner_node_id = self.next_node_id(); - - // Add a definition for the in scope lifetime def. - let inner_def_id = self.create_def( - opaque_ty_def_id, - inner_node_id, - DefPathData::LifetimeNs(ident.name), - ident.span, - ); - new_remapping.insert(outer_def_id, inner_def_id); - - let inner_res = match outer_res { - // Input lifetime like `'a`: - LifetimeRes::Param { param, .. } => { - LifetimeRes::Param { param, binder: fn_node_id } - } - // Input lifetime like `'1`: - LifetimeRes::Fresh { param, .. } => { - LifetimeRes::Fresh { param, binder: fn_node_id } - } - LifetimeRes::Static | LifetimeRes::Error => continue, - res => { - panic!( - "Unexpected lifetime resolution {:?} for {:?} at {:?}", - res, ident, ident.span - ) - } - }; - - let lifetime = Lifetime { id: outer_node_id, ident }; - collected_lifetimes.push((inner_node_id, lifetime, Some(inner_res))); - } - - debug!(?collected_lifetimes); - - // We only want to capture the lifetimes that appear in the bounds. So visit the bounds to - // find out exactly which ones those are. - // in fn return position, like the `fn test<'a>() -> impl Debug + 'a` example, - // we only keep the lifetimes that appear in the `impl Debug` itself: - let lifetimes_to_remap = lifetime_collector::lifetimes_in_ret_ty(&self.resolver, output); - debug!(?lifetimes_to_remap); + let captured_lifetimes: Vec<_> = self + .resolver + .take_extra_lifetime_params(opaque_ty_node_id) + .into_iter() + .map(|(ident, id, _)| Lifetime { id, ident }) + .collect(); - self.with_hir_id_owner(opaque_ty_node_id, |this| { - // If this opaque type is only capturing a subset of the lifetimes (those that appear - // in bounds), then create the new lifetime parameters required and create a mapping - // from the old `'a` (on the function) to the new `'a` (on the opaque type). - collected_lifetimes.extend( - this.create_lifetime_defs( - opaque_ty_def_id, - &lifetimes_to_remap, - &mut new_remapping, - ) - .into_iter() - .map(|(new_node_id, lifetime)| (new_node_id, lifetime, None)), - ); - debug!(?collected_lifetimes); - debug!(?new_remapping); - - // Install the remapping from old to new (if any): - this.with_remapping(new_remapping, |this| { - // We have to be careful to get elision right here. The - // idea is that we create a lifetime parameter for each - // lifetime in the return type. So, given a return type - // like `async fn foo(..) -> &[&u32]`, we lower to `impl - // Future`. - // - // Then, we will create `fn foo(..) -> Foo<'_, '_>`, and - // hence the elision takes place at the fn site. + let opaque_ty_ref = self.lower_opaque_inner( + opaque_ty_node_id, + hir::OpaqueTyOrigin::AsyncFn(fn_def_id), + in_trait, + captured_lifetimes, + span, + opaque_ty_span, + |this| { let future_bound = this.lower_async_fn_output_type_to_future_bound( output, span, @@ -2025,87 +1906,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } }, ); - - let generic_params = this.arena.alloc_from_iter(collected_lifetimes.iter().map( - |&(new_node_id, lifetime, _)| { - let hir_id = this.lower_node_id(new_node_id); - debug_assert_ne!(this.opt_local_def_id(new_node_id), None); - - let (name, kind) = if lifetime.ident.name == kw::UnderscoreLifetime { - (hir::ParamName::Fresh, hir::LifetimeParamKind::Elided) - } else { - ( - hir::ParamName::Plain(lifetime.ident), - hir::LifetimeParamKind::Explicit, - ) - }; - - hir::GenericParam { - hir_id, - def_id: this.local_def_id(new_node_id), - name, - span: lifetime.ident.span, - pure_wrt_drop: false, - kind: hir::GenericParamKind::Lifetime { kind }, - colon_span: None, - source: hir::GenericParamSource::Generics, - } - }, - )); - debug!("lower_async_fn_ret_ty: generic_params={:#?}", generic_params); - - let opaque_ty_item = hir::OpaqueTy { - generics: this.arena.alloc(hir::Generics { - params: generic_params, - predicates: &[], - has_where_clause_predicates: false, - where_clause_span: this.lower_span(span), - span: this.lower_span(span), - }), - bounds: arena_vec![this; future_bound], - origin: hir::OpaqueTyOrigin::AsyncFn(fn_def_id), - in_trait, - }; - - trace!("exist ty from async fn def id: {:#?}", opaque_ty_def_id); - this.generate_opaque_type(opaque_ty_def_id, opaque_ty_item, span, opaque_ty_span) - }) - }); - - // As documented above, we need to create the lifetime - // arguments to our opaque type. Continuing with our example, - // we're creating the type arguments for the return type: - // - // ``` - // Bar<'a, 'b, '0, '1, '_> - // ``` - // - // For the "input" lifetime parameters, we wish to create - // references to the parameters themselves, including the - // "implicit" ones created from parameter types (`'a`, `'b`, - // '`0`, `'1`). - // - // For the "output" lifetime parameters, we just want to - // generate `'_`. - let generic_args = self.arena.alloc_from_iter(collected_lifetimes.into_iter().map( - |(_, lifetime, res)| { - let id = self.next_node_id(); - let res = res.unwrap_or( - self.resolver.get_lifetime_res(lifetime.id).unwrap_or(LifetimeRes::Error), - ); - hir::GenericArg::Lifetime(self.new_named_lifetime_with_res(id, lifetime.ident, res)) + arena_vec![this; future_bound] }, - )); - - // Create the `Foo<...>` reference itself. Note that the `type - // Foo = impl Trait` is, internally, created as a child of the - // async fn, so the *type parameters* are inherited. It's - // only the lifetime parameters that we must supply. - let opaque_ty_ref = hir::TyKind::OpaqueDef( - hir::ItemId { owner_id: hir::OwnerId { def_id: opaque_ty_def_id } }, - generic_args, - in_trait, ); + let opaque_ty = self.ty(opaque_ty_span, opaque_ty_ref); hir::FnRetTy::Return(self.arena.alloc(opaque_ty)) } @@ -2153,7 +1957,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ) -> hir::GenericBound<'hir> { match tpb { GenericBound::Trait(p, modifier) => hir::GenericBound::Trait( - self.lower_poly_trait_ref(p, itctx), + self.lower_poly_trait_ref(p, itctx, modifier.to_constness()), self.lower_trait_bound_modifier(*modifier), ), GenericBound::Outlives(lifetime) => { @@ -2296,8 +2100,20 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } } - fn lower_trait_ref(&mut self, p: &TraitRef, itctx: &ImplTraitContext) -> hir::TraitRef<'hir> { - let path = match self.lower_qpath(p.ref_id, &None, &p.path, ParamMode::Explicit, itctx) { + fn lower_trait_ref( + &mut self, + constness: ast::Const, + p: &TraitRef, + itctx: &ImplTraitContext, + ) -> hir::TraitRef<'hir> { + let path = match self.lower_qpath( + p.ref_id, + &None, + &p.path, + ParamMode::Explicit, + itctx, + Some(constness), + ) { hir::QPath::Resolved(None, path) => path, qpath => panic!("lower_trait_ref: unexpected QPath `{qpath:?}`"), }; @@ -2309,10 +2125,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { &mut self, p: &PolyTraitRef, itctx: &ImplTraitContext, + constness: ast::Const, ) -> hir::PolyTraitRef<'hir> { let bound_generic_params = self.lower_lifetime_binder(p.trait_ref.ref_id, &p.bound_generic_params); - let trait_ref = self.lower_trait_ref(&p.trait_ref, itctx); + let trait_ref = self.lower_trait_ref(constness, &p.trait_ref, itctx); hir::PolyTraitRef { bound_generic_params, trait_ref, span: self.lower_span(p.span) } } @@ -2666,6 +2483,63 @@ struct GenericArgsCtor<'hir> { } impl<'hir> GenericArgsCtor<'hir> { + fn push_constness(&mut self, lcx: &mut LoweringContext<'_, 'hir>, constness: ast::Const) { + if !lcx.tcx.features().effects { + return; + } + + // if bound is non-const, don't add host effect param + let ast::Const::Yes(span) = constness else { return }; + + let span = lcx.lower_span(span); + + let id = lcx.next_node_id(); + let hir_id = lcx.next_id(); + + let Some(host_param_id) = lcx.host_param_id else { + lcx.tcx + .sess + .delay_span_bug(span, "no host param id for call in const yet no errors reported"); + return; + }; + + let body = lcx.lower_body(|lcx| { + (&[], { + let hir_id = lcx.next_id(); + let res = Res::Def(DefKind::ConstParam, host_param_id.to_def_id()); + let expr_kind = hir::ExprKind::Path(hir::QPath::Resolved( + None, + lcx.arena.alloc(hir::Path { + span, + res, + segments: arena_vec![lcx; hir::PathSegment::new(Ident { + name: sym::host, + span, + }, hir_id, res)], + }), + )); + lcx.expr(span, expr_kind) + }) + }); + + let attr_id = lcx.tcx.sess.parse_sess.attr_id_generator.mk_attr_id(); + let attr = lcx.arena.alloc(Attribute { + kind: AttrKind::Normal(P(NormalAttr::from_ident(Ident::new(sym::rustc_host, span)))), + span, + id: attr_id, + style: AttrStyle::Outer, + }); + lcx.attrs.insert(hir_id.local_id, std::slice::from_ref(attr)); + + let def_id = + lcx.create_def(lcx.current_hir_id_owner.def_id, id, DefPathData::AnonConst, span); + lcx.children.push((def_id, hir::MaybeOwner::NonOwner(hir_id))); + self.args.push(hir::GenericArg::Const(hir::ConstArg { + value: hir::AnonConst { def_id, hir_id, body }, + span, + })) + } + fn is_empty(&self) -> bool { self.args.is_empty() && self.bindings.is_empty() diff --git a/compiler/rustc_ast_lowering/src/lifetime_collector.rs b/compiler/rustc_ast_lowering/src/lifetime_collector.rs index 3989fc486193e..6f75419c38765 100644 --- a/compiler/rustc_ast_lowering/src/lifetime_collector.rs +++ b/compiler/rustc_ast_lowering/src/lifetime_collector.rs @@ -1,7 +1,7 @@ use super::ResolverAstLoweringExt; use rustc_ast::visit::{self, BoundKind, LifetimeCtxt, Visitor}; -use rustc_ast::{FnRetTy, GenericBounds, Lifetime, NodeId, PathSegment, PolyTraitRef, Ty, TyKind}; -use rustc_hir::def::LifetimeRes; +use rustc_ast::{GenericBounds, Lifetime, NodeId, PathSegment, PolyTraitRef, Ty, TyKind}; +use rustc_hir::def::{DefKind, LifetimeRes, Res}; use rustc_middle::span_bug; use rustc_middle::ty::ResolverAstLowering; use rustc_span::symbol::{kw, Ident}; @@ -77,7 +77,20 @@ impl<'ast> Visitor<'ast> for LifetimeCollectVisitor<'ast> { } fn visit_ty(&mut self, t: &'ast Ty) { - match t.kind { + match &t.kind { + TyKind::Path(None, _) => { + // We can sometimes encounter bare trait objects + // which are represented in AST as paths. + if let Some(partial_res) = self.resolver.get_partial_res(t.id) + && let Some(Res::Def(DefKind::Trait | DefKind::TraitAlias, _)) = partial_res.full_res() + { + self.current_binders.push(t.id); + visit::walk_ty(self, t); + self.current_binders.pop(); + } else { + visit::walk_ty(self, t); + } + } TyKind::BareFn(_) => { self.current_binders.push(t.id); visit::walk_ty(self, t); @@ -94,12 +107,6 @@ impl<'ast> Visitor<'ast> for LifetimeCollectVisitor<'ast> { } } -pub fn lifetimes_in_ret_ty(resolver: &ResolverAstLowering, ret_ty: &FnRetTy) -> Vec { - let mut visitor = LifetimeCollectVisitor::new(resolver); - visitor.visit_fn_ret_ty(ret_ty); - visitor.collected_lifetimes -} - pub fn lifetimes_in_bounds( resolver: &ResolverAstLowering, bounds: &GenericBounds, diff --git a/compiler/rustc_ast_lowering/src/pat.rs b/compiler/rustc_ast_lowering/src/pat.rs index 2509b70563956..a30f264bc7dc7 100644 --- a/compiler/rustc_ast_lowering/src/pat.rs +++ b/compiler/rustc_ast_lowering/src/pat.rs @@ -38,6 +38,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); let (pats, ddpos) = self.lower_pat_tuple(pats, "tuple struct"); break hir::PatKind::TupleStruct(qpath, pats, ddpos); @@ -54,6 +55,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); break hir::PatKind::Path(qpath); } @@ -64,6 +66,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { path, ParamMode::Optional, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ); let fs = self.arena.alloc_from_iter(fields.iter().map(|f| { diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 441282c05b424..899f92a995821 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -23,6 +23,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { p: &Path, param_mode: ParamMode, itctx: &ImplTraitContext, + // constness of the impl/bound if this is a trait path + constness: Option, ) -> hir::QPath<'hir> { let qself_position = qself.as_ref().map(|q| q.position); let qself = qself.as_ref().map(|q| self.lower_ty(&q.ty, itctx)); @@ -73,6 +75,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { param_mode, parenthesized_generic_args, itctx, + // if this is the last segment, add constness to the trait path + if i == proj_start - 1 { constness } else { None }, ) }, )), @@ -119,6 +123,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { param_mode, ParenthesizedGenericArgs::Err, itctx, + None, )); let qpath = hir::QPath::TypeRelative(ty, hir_segment); @@ -159,6 +164,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { param_mode, ParenthesizedGenericArgs::Err, &ImplTraitContext::Disallowed(ImplTraitPosition::Path), + None, ) })), span: self.lower_span(p.span), @@ -172,8 +178,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { param_mode: ParamMode, parenthesized_generic_args: ParenthesizedGenericArgs, itctx: &ImplTraitContext, + constness: Option, ) -> hir::PathSegment<'hir> { - debug!("path_span: {:?}, lower_path_segment(segment: {:?})", path_span, segment,); + debug!("path_span: {:?}, lower_path_segment(segment: {:?})", path_span, segment); let (mut generic_args, infer_args) = if let Some(generic_args) = segment.args.as_deref() { match generic_args { GenericArgs::AngleBracketed(data) => { @@ -231,6 +238,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ) }; + if let Some(constness) = constness { + generic_args.push_constness(self, constness); + } + let has_lifetimes = generic_args.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_))); diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl index 2f413789e7704..f323bb4c254e0 100644 --- a/compiler/rustc_ast_passes/messages.ftl +++ b/compiler/rustc_ast_passes/messages.ftl @@ -1,64 +1,3 @@ -ast_passes_forbidden_let = - `let` expressions are not supported here - .note = only supported directly in conditions of `if` and `while` expressions - .not_supported_or = `||` operators are not supported in let chain expressions - .not_supported_parentheses = `let`s wrapped in parentheses are not supported in a context with let chains - -ast_passes_forbidden_let_stable = - expected expression, found statement (`let`) - .note = variable declaration using `let` is a statement - -ast_passes_deprecated_where_clause_location = - where clause not allowed here - -ast_passes_keyword_lifetime = - lifetimes cannot use keyword names - -ast_passes_invalid_label = - invalid label name `{$name}` - -ast_passes_visibility_not_permitted = - visibility qualifiers are not permitted here - .enum_variant = enum variants and their fields always share the visibility of the enum they are in - .trait_impl = trait items always share the visibility of their trait - .individual_impl_items = place qualifiers on individual impl items instead - .individual_foreign_items = place qualifiers on individual foreign items instead - -ast_passes_trait_fn_const = - functions in traits cannot be declared const - .label = functions in traits cannot be const - -ast_passes_forbidden_lifetime_bound = - lifetime bounds cannot be used in this context - -ast_passes_forbidden_non_lifetime_param = - only lifetime parameters can be used in this context - -ast_passes_fn_param_too_many = - function can not have more than {$max_num_args} arguments - -ast_passes_fn_param_c_var_args_only = - C-variadic function must be declared with at least one named argument - -ast_passes_fn_param_c_var_args_not_last = - `...` must be the last argument of a C-variadic function - -ast_passes_fn_param_doc_comment = - documentation comments cannot be applied to function parameters - .label = doc comments are not allowed here - -ast_passes_fn_param_forbidden_attr = - allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters - -ast_passes_fn_param_forbidden_self = - `self` parameter is only allowed in associated functions - .label = not semantically valid as function parameter - .note = associated functions are those in `impl` or `trait` definitions - -ast_passes_forbidden_default = - `default` is only allowed on items in trait impls - .label = `default` because of this - ast_passes_assoc_const_without_body = associated constant in `impl` without body .suggestion = provide a definition for the constant @@ -71,36 +10,73 @@ ast_passes_assoc_type_without_body = associated type in `impl` without body .suggestion = provide a definition for the type +ast_passes_at_least_one_trait = at least one trait must be specified + +ast_passes_auto_generic = auto traits cannot have generic parameters + .label = auto trait cannot have generic parameters + .suggestion = remove the parameters + +ast_passes_auto_items = auto traits cannot have associated items + .label = {ast_passes_auto_items} + .suggestion = remove these associated items + +ast_passes_auto_super_lifetime = auto traits cannot have super traits or lifetime bounds + .label = {ast_passes_auto_super_lifetime} + .suggestion = remove the super traits or lifetime bounds + +ast_passes_bad_c_variadic = only foreign or `unsafe extern "C"` functions may be C-variadic + +ast_passes_body_in_extern = incorrect `{$kind}` inside `extern` block + .cannot_have = cannot have a body + .invalid = the invalid body + .existing = `extern` blocks define existing foreign {$kind}s and {$kind}s inside of them cannot have a body + +ast_passes_bound_in_context = bounds on `type`s in {$ctx} have no effect + +ast_passes_const_and_async = functions cannot be both `const` and `async` + .const = `const` because of this + .async = `async` because of this + .label = {""} + ast_passes_const_without_body = free constant item without body .suggestion = provide a definition for the constant -ast_passes_static_without_body = - free static item without body - .suggestion = provide a definition for the static +ast_passes_constraint_on_negative_bound = + associated type constraints not allowed on negative bounds -ast_passes_ty_alias_without_body = - free type alias without body - .suggestion = provide a definition for the type +ast_passes_deprecated_where_clause_location = + where clause not allowed here -ast_passes_fn_without_body = - free function without a body - .suggestion = provide a definition for the function +ast_passes_equality_in_where = equality constraints are not yet supported in `where` clauses + .label = not supported + .suggestion = if `{$ident}` is an associated type you're trying to set, use the associated type binding syntax + .suggestion_path = if `{$trait_segment}::{$potential_assoc}` is an associated type you're trying to set, use the associated type binding syntax + .note = see issue #20041 for more information ast_passes_extern_block_suggestion = if you meant to declare an externally defined function, use an `extern` block -ast_passes_bound_in_context = bounds on `type`s in {$ctx} have no effect +ast_passes_extern_fn_qualifiers = functions in `extern` blocks cannot have qualifiers + .label = in this `extern` block + .suggestion = remove the qualifiers + +ast_passes_extern_item_ascii = items in `extern` blocks cannot use non-ascii identifiers + .label = in this `extern` block + .note = this limitation may be lifted in the future; see issue #83942 for more information + +ast_passes_extern_keyword_link = for more information, visit https://doc.rust-lang.org/std/keyword.extern.html ast_passes_extern_types_cannot = `type`s inside `extern` blocks cannot have {$descr} .suggestion = remove the {$remove_descr} .label = `extern` block begins here -ast_passes_extern_keyword_link = for more information, visit https://doc.rust-lang.org/std/keyword.extern.html +ast_passes_extern_without_abi = extern declarations without an explicit ABI are deprecated -ast_passes_body_in_extern = incorrect `{$kind}` inside `extern` block - .cannot_have = cannot have a body - .invalid = the invalid body - .existing = `extern` blocks define existing foreign {$kind}s and {$kind}s inside of them cannot have a body +ast_passes_feature_on_non_nightly = `#![feature]` may not be used on the {$channel} release channel + .suggestion = remove the attribute + .stable_since = the feature `{$name}` has been stable since `{$since}` and no longer requires an attribute to enable + +ast_passes_fieldless_union = unions cannot have zero fields ast_passes_fn_body_extern = incorrect function inside `extern` block .cannot_have = cannot have a body @@ -108,35 +84,50 @@ ast_passes_fn_body_extern = incorrect function inside `extern` block .help = you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block .label = `extern` blocks define existing foreign functions and functions inside of them cannot have a body -ast_passes_extern_fn_qualifiers = functions in `extern` blocks cannot have qualifiers - .label = in this `extern` block - .suggestion = remove the qualifiers +ast_passes_fn_param_c_var_args_not_last = + `...` must be the last argument of a C-variadic function -ast_passes_extern_item_ascii = items in `extern` blocks cannot use non-ascii identifiers - .label = in this `extern` block - .note = this limitation may be lifted in the future; see issue #83942 for more information +ast_passes_fn_param_c_var_args_only = + C-variadic function must be declared with at least one named argument -ast_passes_bad_c_variadic = only foreign or `unsafe extern "C"` functions may be C-variadic +ast_passes_fn_param_doc_comment = + documentation comments cannot be applied to function parameters + .label = doc comments are not allowed here -ast_passes_item_underscore = `{$kind}` items in this context need a name - .label = `_` is not a valid name for this `{$kind}` item +ast_passes_fn_param_forbidden_attr = + allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters -ast_passes_nomangle_ascii = `#[no_mangle]` requires ASCII identifier +ast_passes_fn_param_forbidden_self = + `self` parameter is only allowed in associated functions + .label = not semantically valid as function parameter + .note = associated functions are those in `impl` or `trait` definitions -ast_passes_module_nonascii = trying to load file for module `{$name}` with non-ascii identifier name - .help = consider using the `#[path]` attribute to specify filesystem path +ast_passes_fn_param_too_many = + function can not have more than {$max_num_args} arguments -ast_passes_auto_generic = auto traits cannot have generic parameters - .label = auto trait cannot have generic parameters - .suggestion = remove the parameters +ast_passes_fn_without_body = + free function without a body + .suggestion = provide a definition for the function -ast_passes_auto_super_lifetime = auto traits cannot have super traits or lifetime bounds - .label = {ast_passes_auto_super_lifetime} - .suggestion = remove the super traits or lifetime bounds +ast_passes_forbidden_default = + `default` is only allowed on items in trait impls + .label = `default` because of this -ast_passes_auto_items = auto traits cannot have associated items - .label = {ast_passes_auto_items} - .suggestion = remove these associated items +ast_passes_forbidden_let = + `let` expressions are not supported here + .note = only supported directly in conditions of `if` and `while` expressions + .not_supported_or = `||` operators are not supported in let chain expressions + .not_supported_parentheses = `let`s wrapped in parentheses are not supported in a context with let chains + +ast_passes_forbidden_let_stable = + expected expression, found statement (`let`) + .note = variable declaration using `let` is a statement + +ast_passes_forbidden_lifetime_bound = + lifetime bounds cannot be used in this context + +ast_passes_forbidden_non_lifetime_param = + only lifetime parameters can be used in this context ast_passes_generic_before_constraints = generic arguments must come before the first constraint .constraints = {$constraint_len -> @@ -156,88 +147,102 @@ ast_passes_generic_before_constraints = generic arguments must come before the f *[other] arguments } -ast_passes_pattern_in_fn_pointer = patterns aren't allowed in function pointer types - -ast_passes_trait_object_single_bound = only a single explicit lifetime bound is permitted +ast_passes_generic_default_trailing = generic parameters with a default must be trailing ast_passes_impl_trait_path = `impl Trait` is not allowed in path parameters +ast_passes_incompatible_features = `{$f1}` and `{$f2}` are incompatible, using them at the same time is not allowed + .help = remove one of these features + +ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation} + .because = {$annotation} because of this + .type = inherent impl for this type + .only_trait = only trait implementations may be annotated with {$annotation} + +ast_passes_invalid_label = + invalid label name `{$name}` + +ast_passes_item_underscore = `{$kind}` items in this context need a name + .label = `_` is not a valid name for this `{$kind}` item + +ast_passes_keyword_lifetime = + lifetimes cannot use keyword names + +ast_passes_module_nonascii = trying to load file for module `{$name}` with non-ascii identifier name + .help = consider using the `#[path]` attribute to specify filesystem path + +ast_passes_negative_bound_not_supported = + negative bounds are not supported + ast_passes_nested_impl_trait = nested `impl Trait` is not allowed .outer = outer `impl Trait` .inner = nested `impl Trait` here -ast_passes_at_least_one_trait = at least one trait must be specified - -ast_passes_extern_without_abi = extern declarations without an explicit ABI are deprecated +ast_passes_nested_lifetimes = nested quantification of lifetimes -ast_passes_out_of_order_params = {$param_ord} parameters must be declared prior to {$max_param} parameters - .suggestion = reorder the parameters: lifetimes, then consts and types +ast_passes_nomangle_ascii = `#[no_mangle]` requires ASCII identifier ast_passes_obsolete_auto = `impl Trait for .. {"{}"}` is an obsolete syntax .help = use `auto trait Trait {"{}"}` instead -ast_passes_unsafe_negative_impl = negative impls cannot be unsafe - .negative = negative because of this - .unsafe = unsafe because of this +ast_passes_optional_const_exclusive = `~const` and `{$modifier}` are mutually exclusive -ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation} - .because = {$annotation} because of this - .type = inherent impl for this type - .only_trait = only trait implementations may be annotated with {$annotation} +ast_passes_optional_trait_object = `?Trait` is not permitted in trait object types -ast_passes_unsafe_item = {$kind} cannot be declared unsafe +ast_passes_optional_trait_supertrait = `?Trait` is not permitted in supertraits + .note = traits are `?{$path_str}` by default -ast_passes_fieldless_union = unions cannot have zero fields +ast_passes_out_of_order_params = {$param_ord} parameters must be declared prior to {$max_param} parameters + .suggestion = reorder the parameters: lifetimes, then consts and types -ast_passes_where_after_type_alias = where clauses are not allowed after the type for type aliases - .note = see issue #89122 for more information +ast_passes_pattern_in_bodiless = patterns aren't allowed in functions without bodies + .label = pattern not allowed in function without body -ast_passes_generic_default_trailing = generic parameters with a default must be trailing +ast_passes_pattern_in_fn_pointer = patterns aren't allowed in function pointer types -ast_passes_nested_lifetimes = nested quantification of lifetimes +ast_passes_pattern_in_foreign = patterns aren't allowed in foreign function declarations + .label = pattern not allowed in foreign function -ast_passes_optional_trait_supertrait = `?Trait` is not permitted in supertraits - .note = traits are `?{$path_str}` by default +ast_passes_show_span = {$msg} -ast_passes_optional_trait_object = `?Trait` is not permitted in trait object types +ast_passes_stability_outside_std = stability attributes may not be used outside of the standard library + +ast_passes_static_without_body = + free static item without body + .suggestion = provide a definition for the static ast_passes_tilde_const_disallowed = `~const` is not allowed here .trait = trait objects cannot have `~const` trait bounds .closure = closures cannot have `~const` trait bounds .function = this function is not `const`, so it cannot have `~const` trait bounds -ast_passes_optional_const_exclusive = `~const` and `{$modifier}` are mutually exclusive - -ast_passes_const_and_async = functions cannot be both `const` and `async` - .const = `const` because of this - .async = `async` because of this - .label = {""} - -ast_passes_pattern_in_foreign = patterns aren't allowed in foreign function declarations - .label = pattern not allowed in foreign function - -ast_passes_pattern_in_bodiless = patterns aren't allowed in functions without bodies - .label = pattern not allowed in function without body +ast_passes_trait_fn_const = + functions in traits cannot be declared const + .label = functions in traits cannot be const -ast_passes_equality_in_where = equality constraints are not yet supported in `where` clauses - .label = not supported - .suggestion = if `{$ident}` is an associated type you're trying to set, use the associated type binding syntax - .suggestion_path = if `{$trait_segment}::{$potential_assoc}` is an associated type you're trying to set, use the associated type binding syntax - .note = see issue #20041 for more information +ast_passes_trait_object_single_bound = only a single explicit lifetime bound is permitted -ast_passes_stability_outside_std = stability attributes may not be used outside of the standard library +ast_passes_ty_alias_without_body = + free type alias without body + .suggestion = provide a definition for the type -ast_passes_feature_on_non_nightly = `#![feature]` may not be used on the {$channel} release channel - .suggestion = remove the attribute - .stable_since = the feature `{$name}` has been stable since `{$since}` and no longer requires an attribute to enable +ast_passes_unsafe_item = {$kind} cannot be declared unsafe -ast_passes_incompatible_features = `{$f1}` and `{$f2}` are incompatible, using them at the same time is not allowed - .help = remove one of these features +ast_passes_unsafe_negative_impl = negative impls cannot be unsafe + .negative = negative because of this + .unsafe = unsafe because of this -ast_passes_show_span = {$msg} +ast_passes_visibility_not_permitted = + visibility qualifiers are not permitted here + .enum_variant = enum variants and their fields always share the visibility of the enum they are in + .trait_impl = trait items always share the visibility of their trait + .individual_impl_items = place qualifiers on individual impl items instead + .individual_foreign_items = place qualifiers on individual foreign items instead -ast_passes_negative_bound_not_supported = - negative bounds are not supported +ast_passes_where_clause_after_type_alias = where clauses are not allowed after the type for type aliases + .note = see issue #112792 for more information + .help = add `#![feature(lazy_type_alias)]` to the crate attributes to enable -ast_passes_constraint_on_negative_bound = - associated type constraints not allowed on negative bounds +ast_passes_where_clause_before_type_alias = where clauses are not allowed before the type for type aliases + .note = see issue #89122 for more information + .suggestion = move it to the end of the type declaration diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 04ed276787652..bd3e676daa4c7 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -13,6 +13,7 @@ use rustc_ast::*; use rustc_ast::{walk_list, StaticItem}; use rustc_ast_pretty::pprust::{self, State}; use rustc_data_structures::fx::FxIndexMap; +use rustc_feature::Features; use rustc_macros::Subdiagnostic; use rustc_parse::validate_attr; use rustc_session::lint::builtin::{ @@ -45,6 +46,7 @@ enum DisallowTildeConstContext<'a> { struct AstValidator<'a> { session: &'a Session, + features: &'a Features, /// The span of the `extern` in an `extern { ... }` block, if any. extern_mod: Option<&'a Item>, @@ -136,40 +138,42 @@ impl<'a> AstValidator<'a> { } } - fn check_gat_where( + fn check_type_alias_where_clause_location( &mut self, - id: NodeId, - before_predicates: &[WherePredicate], - where_clauses: (ast::TyAliasWhereClause, ast::TyAliasWhereClause), - ) { - if !before_predicates.is_empty() { - let mut state = State::new(); - if !where_clauses.1.0 { - state.space(); - state.word_space("where"); - } else { + ty_alias: &TyAlias, + ) -> Result<(), errors::WhereClauseBeforeTypeAlias> { + let before_predicates = + ty_alias.generics.where_clause.predicates.split_at(ty_alias.where_predicates_split).0; + + if ty_alias.ty.is_none() || before_predicates.is_empty() { + return Ok(()); + } + + let mut state = State::new(); + if !ty_alias.where_clauses.1.0 { + state.space(); + state.word_space("where"); + } else { + state.word_space(","); + } + let mut first = true; + for p in before_predicates { + if !first { state.word_space(","); } - let mut first = true; - for p in before_predicates.iter() { - if !first { - state.word_space(","); - } - first = false; - state.print_where_predicate(p); - } - let suggestion = state.s.eof(); - self.lint_buffer.buffer_lint_with_diagnostic( - DEPRECATED_WHERE_CLAUSE_LOCATION, - id, - where_clauses.0.1, - fluent::ast_passes_deprecated_where_clause_location, - BuiltinLintDiagnostics::DeprecatedWhereclauseLocation( - where_clauses.1.1.shrink_to_hi(), - suggestion, - ), - ); + first = false; + state.print_where_predicate(p); } + + let span = ty_alias.where_clauses.0.1; + Err(errors::WhereClauseBeforeTypeAlias { + span, + sugg: errors::WhereClauseBeforeTypeAliasSugg { + left: span, + snippet: state.s.eof(), + right: ty_alias.where_clauses.1.1.shrink_to_hi(), + }, + }) } fn with_impl_trait(&mut self, outer: Option, f: impl FnOnce(&mut Self)) { @@ -364,7 +368,12 @@ impl<'a> AstValidator<'a> { self.err_handler().emit_err(errors::BoundInContext { span, ctx }); } - fn check_foreign_ty_genericless(&self, generics: &Generics, where_span: Span) { + fn check_foreign_ty_genericless( + &self, + generics: &Generics, + before_where_clause: &TyAliasWhereClause, + after_where_clause: &TyAliasWhereClause, + ) { let cannot_have = |span, descr, remove_descr| { self.err_handler().emit_err(errors::ExternTypesCannotHave { span, @@ -378,9 +387,14 @@ impl<'a> AstValidator<'a> { cannot_have(generics.span, "generic parameters", "generic parameters"); } - if !generics.where_clause.predicates.is_empty() { - cannot_have(where_span, "`where` clauses", "`where` clause"); - } + let check_where_clause = |where_clause: &TyAliasWhereClause| { + if let TyAliasWhereClause(true, where_clause_span) = where_clause { + cannot_have(*where_clause_span, "`where` clauses", "`where` clause"); + } + }; + + check_where_clause(before_where_clause); + check_where_clause(after_where_clause); } fn check_foreign_kind_bodyless(&self, ident: Ident, kind: &str, body: Option) { @@ -613,13 +627,12 @@ impl<'a> AstValidator<'a> { fn maybe_lint_missing_abi(&mut self, span: Span, id: NodeId) { // FIXME(davidtwco): This is a hack to detect macros which produce spans of the // call site which do not have a macro backtrace. See #61963. - let is_macro_callsite = self + if self .session .source_map() .span_to_snippet(span) - .map(|snippet| snippet.starts_with("#[")) - .unwrap_or(true); - if !is_macro_callsite { + .is_ok_and(|snippet| !snippet.starts_with("#[")) + { self.lint_buffer.buffer_lint_with_diagnostic( MISSING_ABI, id, @@ -650,7 +663,7 @@ fn validate_generic_param_order( GenericParamKind::Type { .. } => (ParamKindOrd::TypeOrConst, ident.to_string()), GenericParamKind::Const { ty, .. } => { let ty = pprust::ty_to_string(ty); - (ParamKindOrd::TypeOrConst, format!("const {}: {}", ident, ty)) + (ParamKindOrd::TypeOrConst, format!("const {ident}: {ty}")) } }; param_idents.push((kind, ord_kind, bounds, idx, ident)); @@ -1000,7 +1013,9 @@ impl<'a> Visitor<'a> for AstValidator<'a> { replace_span: self.ending_semi_or_hi(item.span), }); } - ItemKind::TyAlias(box TyAlias { defaultness, where_clauses, bounds, ty, .. }) => { + ItemKind::TyAlias( + ty_alias @ box TyAlias { defaultness, bounds, where_clauses, ty, .. }, + ) => { self.check_defaultness(item.span, *defaultness); if ty.is_none() { self.session.emit_err(errors::TyAliasWithoutBody { @@ -1009,9 +1024,16 @@ impl<'a> Visitor<'a> for AstValidator<'a> { }); } self.check_type_no_bounds(bounds, "this context"); - if where_clauses.1.0 { - self.err_handler() - .emit_err(errors::WhereAfterTypeAlias { span: where_clauses.1.1 }); + + if self.features.lazy_type_alias { + if let Err(err) = self.check_type_alias_where_clause_location(ty_alias) { + self.err_handler().emit_err(err); + } + } else if where_clauses.1.0 { + self.err_handler().emit_err(errors::WhereClauseAfterTypeAlias { + span: where_clauses.1.1, + help: self.session.is_nightly_build().then_some(()), + }); } } _ => {} @@ -1039,7 +1061,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { self.check_defaultness(fi.span, *defaultness); self.check_foreign_kind_bodyless(fi.ident, "type", ty.as_ref().map(|b| b.span)); self.check_type_no_bounds(bounds, "`extern` blocks"); - self.check_foreign_ty_genericless(generics, where_clauses.0.1); + self.check_foreign_ty_genericless(generics, &where_clauses.0, &where_clauses.1); self.check_foreign_item_ascii_only(fi.ident); } ForeignItemKind::Static(_, _, body) => { @@ -1291,14 +1313,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { }); } } - AssocItemKind::Type(box TyAlias { - generics, - where_clauses, - where_predicates_split, - bounds, - ty, - .. - }) => { + AssocItemKind::Type(box TyAlias { bounds, ty, .. }) => { if ty.is_none() { self.session.emit_err(errors::AssocTypeWithoutBody { span: item.span, @@ -1306,18 +1321,26 @@ impl<'a> Visitor<'a> for AstValidator<'a> { }); } self.check_type_no_bounds(bounds, "`impl`s"); - if ty.is_some() { - self.check_gat_where( - item.id, - generics.where_clause.predicates.split_at(*where_predicates_split).0, - *where_clauses, - ); - } } _ => {} } } + if let AssocItemKind::Type(ty_alias) = &item.kind + && let Err(err) = self.check_type_alias_where_clause_location(ty_alias) + { + self.lint_buffer.buffer_lint_with_diagnostic( + DEPRECATED_WHERE_CLAUSE_LOCATION, + item.id, + err.span, + fluent::ast_passes_deprecated_where_clause_location, + BuiltinLintDiagnostics::DeprecatedWhereclauseLocation( + err.sugg.right, + err.sugg.snippet, + ), + ); + } + if ctxt == AssocCtxt::Trait || self.in_trait_impl { self.visibility_not_permitted(&item.vis, errors::VisibilityNotPermittedNote::TraitImpl); if let AssocItemKind::Fn(box Fn { sig, .. }) = &item.kind { @@ -1453,15 +1476,12 @@ fn deny_equality_constraints( let Some(arg) = args.args.last() else { continue; }; - ( - format!(", {} = {}", assoc, ty), - arg.span().shrink_to_hi(), - ) + (format!(", {assoc} = {ty}"), arg.span().shrink_to_hi()) } _ => continue, }, None => ( - format!("<{} = {}>", assoc, ty), + format!("<{assoc} = {ty}>"), trait_segment.span().shrink_to_hi(), ), }; @@ -1482,9 +1502,15 @@ fn deny_equality_constraints( this.err_handler().emit_err(err); } -pub fn check_crate(session: &Session, krate: &Crate, lints: &mut LintBuffer) -> bool { +pub fn check_crate( + session: &Session, + features: &Features, + krate: &Crate, + lints: &mut LintBuffer, +) -> bool { let mut validator = AstValidator { session, + features, extern_mod: None, in_trait_impl: false, in_const_trait_impl: false, diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index 82fe2a21d0876..a6f217d4780fd 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -77,13 +77,6 @@ pub struct ForbiddenLifetimeBound { pub spans: Vec, } -#[derive(Diagnostic)] -#[diag(ast_passes_forbidden_non_lifetime_param)] -pub struct ForbiddenNonLifetimeParam { - #[primary_span] - pub spans: Vec, -} - #[derive(Diagnostic)] #[diag(ast_passes_fn_param_too_many)] pub struct FnParamTooMany { @@ -503,11 +496,37 @@ pub struct FieldlessUnion { } #[derive(Diagnostic)] -#[diag(ast_passes_where_after_type_alias)] +#[diag(ast_passes_where_clause_after_type_alias)] +#[note] +pub struct WhereClauseAfterTypeAlias { + #[primary_span] + pub span: Span, + #[help] + pub help: Option<()>, +} + +#[derive(Diagnostic)] +#[diag(ast_passes_where_clause_before_type_alias)] #[note] -pub struct WhereAfterTypeAlias { +pub struct WhereClauseBeforeTypeAlias { #[primary_span] pub span: Span, + #[subdiagnostic] + pub sugg: WhereClauseBeforeTypeAliasSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + ast_passes_suggestion, + applicability = "machine-applicable", + style = "verbose" +)] +pub struct WhereClauseBeforeTypeAliasSugg { + #[suggestion_part(code = "")] + pub left: Span, + pub snippet: String, + #[suggestion_part(code = "{snippet}")] + pub right: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 274f931e43f9d..10c9c3ef11188 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -2,7 +2,6 @@ use rustc_ast as ast; use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::{attr, AssocConstraint, AssocConstraintKind, NodeId}; use rustc_ast::{PatKind, RangeEnd}; -use rustc_errors::{Applicability, StashKey}; use rustc_feature::{AttributeGate, BuiltinAttribute, Features, GateIssue, BUILTIN_ATTRIBUTE_MAP}; use rustc_session::parse::{feature_err, feature_err_issue, feature_warn}; use rustc_session::Session; @@ -219,6 +218,19 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } } } + if !attr.is_doc_comment() + && attr.get_normal_item().path.segments.len() == 2 + && attr.get_normal_item().path.segments[0].ident.name == sym::diagnostic + && !self.features.diagnostic_namespace + { + let msg = "`#[diagnostic]` attribute name space is experimental"; + gate_feature_post!( + self, + diagnostic_namespace, + attr.get_normal_item().path.segments[0].ident.span, + msg + ); + } // Emit errors for non-staged-api crates. if !self.features.staged_api { @@ -374,55 +386,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } } - fn visit_stmt(&mut self, stmt: &'a ast::Stmt) { - if let ast::StmtKind::Semi(expr) = &stmt.kind - && let ast::ExprKind::Assign(lhs, _, _) = &expr.kind - && let ast::ExprKind::Type(..) = lhs.kind - && self.sess.parse_sess.span_diagnostic.err_count() == 0 - && !self.features.type_ascription - && !lhs.span.allows_unstable(sym::type_ascription) - { - // When we encounter a statement of the form `foo: Ty = val;`, this will emit a type - // ascription error, but the likely intention was to write a `let` statement. (#78907). - feature_err( - &self.sess.parse_sess, - sym::type_ascription, - lhs.span, - "type ascription is experimental", - ).span_suggestion_verbose( - lhs.span.shrink_to_lo(), - "you might have meant to introduce a new binding", - "let ", - Applicability::MachineApplicable, - ).emit(); - } - visit::walk_stmt(self, stmt); - } - fn visit_expr(&mut self, e: &'a ast::Expr) { match e.kind { - ast::ExprKind::Type(..) => { - if self.sess.parse_sess.span_diagnostic.err_count() == 0 { - // To avoid noise about type ascription in common syntax errors, - // only emit if it is the *only* error. - gate_feature_post!( - &self, - type_ascription, - e.span, - "type ascription is experimental" - ); - } else { - // And if it isn't, cancel the early-pass warning. - if let Some(err) = self - .sess - .parse_sess - .span_diagnostic - .steal_diagnostic(e.span, StashKey::EarlySyntaxWarning) - { - err.cancel() - } - } - } ast::ExprKind::TryBlock(_) => { gate_feature_post!(&self, try_blocks, e.span, "`try` expression is experimental"); } @@ -549,10 +514,10 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { } } -pub fn check_crate(krate: &ast::Crate, sess: &Session) { - maybe_stage_features(sess, krate); - check_incompatible_features(sess); - let mut visitor = PostExpansionVisitor { sess, features: &sess.features_untracked() }; +pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { + maybe_stage_features(sess, features, krate); + check_incompatible_features(sess, features); + let mut visitor = PostExpansionVisitor { sess, features }; let spans = sess.parse_sess.gated_spans.spans.borrow(); macro_rules! gate_all { @@ -603,6 +568,8 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) { gate_all!(dyn_star, "`dyn*` trait objects are experimental"); gate_all!(const_closures, "const closures are experimental"); gate_all!(builtin_syntax, "`builtin #` syntax is unstable"); + gate_all!(explicit_tail_calls, "`become` expression is experimental"); + gate_all!(generic_const_items, "generic const items are experimental"); if !visitor.features.negative_bounds { for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() { @@ -629,17 +596,16 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) { gate_all!(box_patterns, "box pattern syntax is experimental"); gate_all!(exclusive_range_pattern, "exclusive range pattern syntax is experimental"); gate_all!(try_blocks, "`try` blocks are unstable"); - gate_all!(type_ascription, "type ascription is experimental"); visit::walk_crate(&mut visitor, krate); } -fn maybe_stage_features(sess: &Session, krate: &ast::Crate) { +fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate) { // checks if `#![feature]` has been used to enable any lang feature // does not check the same for lib features unless there's at least one // declared lang feature if !sess.opts.unstable_features.is_nightly_build() { - let lang_features = &sess.features_untracked().declared_lang_features; + let lang_features = &features.declared_lang_features; if lang_features.len() == 0 { return; } @@ -674,9 +640,7 @@ fn maybe_stage_features(sess: &Session, krate: &ast::Crate) { } } -fn check_incompatible_features(sess: &Session) { - let features = sess.features_untracked(); - +fn check_incompatible_features(sess: &Session, features: &Features) { let declared_features = features .declared_lang_features .iter() diff --git a/compiler/rustc_ast_pretty/src/pprust/mod.rs b/compiler/rustc_ast_pretty/src/pprust/mod.rs index ac9e7d06c4e40..83b7e13905aee 100644 --- a/compiler/rustc_ast_pretty/src/pprust/mod.rs +++ b/compiler/rustc_ast_pretty/src/pprust/mod.rs @@ -32,6 +32,10 @@ pub fn bounds_to_string(bounds: &[ast::GenericBound]) -> String { State::new().bounds_to_string(bounds) } +pub fn where_bound_predicate_to_string(where_bound_predicate: &ast::WhereBoundPredicate) -> String { + State::new().where_bound_predicate_to_string(where_bound_predicate) +} + pub fn pat_to_string(pat: &ast::Pat) -> String { State::new().pat_to_string(pat) } diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 3f80728a2606b..58ce73047bcec 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -150,6 +150,8 @@ pub fn print_crate<'a>( /// and also addresses some specific regressions described in #63896 and #73345. fn tt_prepend_space(tt: &TokenTree, prev: &TokenTree) -> bool { if let TokenTree::Token(token, _) = prev { + // No space after these tokens, e.g. `x.y`, `$e` + // (The carets point to `prev`.) ^ ^ if matches!(token.kind, token::Dot | token::Dollar) { return false; } @@ -158,10 +160,19 @@ fn tt_prepend_space(tt: &TokenTree, prev: &TokenTree) -> bool { } } match tt { + // No space before these tokens, e.g. `foo,`, `println!`, `x.y` + // (The carets point to `token`.) ^ ^ ^ + // + // FIXME: having `Not` here works well for macro invocations like + // `println!()`, but is bad when `!` means "logical not" or "the never + // type", where the lack of space causes ugliness like this: + // `Fn() ->!`, `x =! y`, `if! x { f(); }`. TokenTree::Token(token, _) => !matches!(token.kind, token::Comma | token::Not | token::Dot), + // No space before parentheses if preceded by these tokens, e.g. `foo(...)` TokenTree::Delimited(_, Delimiter::Parenthesis, _) => { !matches!(prev, TokenTree::Token(Token { kind: token::Ident(..), .. }, _)) } + // No space before brackets if preceded by these tokens, e.g. `#[...]` TokenTree::Delimited(_, Delimiter::Bracket, _) => { !matches!(prev, TokenTree::Token(Token { kind: token::Pound, .. }, _)) } @@ -476,7 +487,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere Some(MacHeader::Path(&item.path)), false, None, - delim.to_token(), + *delim, tokens, true, span, @@ -640,7 +651,7 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere Some(MacHeader::Keyword(kw)), has_bang, Some(*ident), - macro_def.body.delim.to_token(), + macro_def.body.delim, ¯o_def.body.tokens.clone(), true, sp, @@ -824,6 +835,13 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere Self::to_string(|s| s.print_type_bounds(bounds)) } + fn where_bound_predicate_to_string( + &self, + where_bound_predicate: &ast::WhereBoundPredicate, + ) -> String { + Self::to_string(|s| s.print_where_bound_predicate(where_bound_predicate)) + } + fn pat_to_string(&self, pat: &ast::Pat) -> String { Self::to_string(|s| s.print_pat(pat)) } @@ -1233,7 +1251,7 @@ impl<'a> State<'a> { Some(MacHeader::Path(&m.path)), true, None, - m.args.delim.to_token(), + m.args.delim, &m.args.tokens.clone(), true, m.span(), diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 87c32ffce1214..39741a03930d1 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -477,7 +477,7 @@ impl<'a> State<'a> { self.word("."); self.print_ident(*ident); } - ast::ExprKind::Index(expr, index) => { + ast::ExprKind::Index(expr, index, _) => { self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX); self.word("["); self.print_expr(index); @@ -537,6 +537,11 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(expr, parser::PREC_JUMP); } } + ast::ExprKind::Become(result) => { + self.word("become"); + self.word(" "); + self.print_expr_maybe_paren(result, parser::PREC_JUMP); + } ast::ExprKind::InlineAsm(a) => { // FIXME: This should have its own syntax, distinct from a macro invocation. self.word("asm!"); @@ -692,15 +697,15 @@ pub fn reconstruct_format_args_template_string(pieces: &[FormatArgsPiece]) -> St write!(template, "{n}").unwrap(); if p.format_options != Default::default() || p.format_trait != FormatTrait::Display { - template.push_str(":"); + template.push(':'); } if let Some(fill) = p.format_options.fill { template.push(fill); } match p.format_options.alignment { - Some(FormatAlignment::Left) => template.push_str("<"), - Some(FormatAlignment::Right) => template.push_str(">"), - Some(FormatAlignment::Center) => template.push_str("^"), + Some(FormatAlignment::Left) => template.push('<'), + Some(FormatAlignment::Right) => template.push('>'), + Some(FormatAlignment::Center) => template.push('^'), None => {} } match p.format_options.sign { diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index c465f8c948a80..d27a44f1206dd 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -30,10 +30,15 @@ impl<'a> State<'a> { ast::ForeignItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => { self.print_fn_full(sig, ident, generics, vis, *defaultness, body.as_deref(), attrs); } - ast::ForeignItemKind::Static(ty, mutbl, body) => { - let def = ast::Defaultness::Final; - self.print_item_const(ident, Some(*mutbl), ty, body.as_deref(), vis, def); - } + ast::ForeignItemKind::Static(ty, mutbl, body) => self.print_item_const( + ident, + Some(*mutbl), + &ast::Generics::default(), + ty, + body.as_deref(), + vis, + ast::Defaultness::Final, + ), ast::ForeignItemKind::TyAlias(box ast::TyAlias { defaultness, generics, @@ -67,6 +72,7 @@ impl<'a> State<'a> { &mut self, ident: Ident, mutbl: Option, + generics: &ast::Generics, ty: &ast::Ty, body: Option<&ast::Expr>, vis: &ast::Visibility, @@ -82,6 +88,7 @@ impl<'a> State<'a> { }; self.word_space(leading); self.print_ident(ident); + self.print_generic_params(&generics.params); self.word_space(":"); self.print_type(ty); if body.is_some() { @@ -92,6 +99,7 @@ impl<'a> State<'a> { self.word_space("="); self.print_expr(body); } + self.print_where_clause(&generics.where_clause); self.word(";"); self.end(); // end the outer cbox } @@ -158,20 +166,21 @@ impl<'a> State<'a> { self.word(";"); } ast::ItemKind::Static(box StaticItem { ty, mutability: mutbl, expr: body }) => { - let def = ast::Defaultness::Final; self.print_item_const( item.ident, Some(*mutbl), + &ast::Generics::default(), ty, body.as_deref(), &item.vis, - def, + ast::Defaultness::Final, ); } - ast::ItemKind::Const(box ast::ConstItem { defaultness, ty, expr }) => { + ast::ItemKind::Const(box ast::ConstItem { defaultness, generics, ty, expr }) => { self.print_item_const( item.ident, None, + generics, ty, expr.as_deref(), &item.vis, @@ -515,8 +524,16 @@ impl<'a> State<'a> { ast::AssocItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => { self.print_fn_full(sig, ident, generics, vis, *defaultness, body.as_deref(), attrs); } - ast::AssocItemKind::Const(box ast::ConstItem { defaultness, ty, expr }) => { - self.print_item_const(ident, None, ty, expr.as_deref(), vis, *defaultness); + ast::AssocItemKind::Const(box ast::ConstItem { defaultness, generics, ty, expr }) => { + self.print_item_const( + ident, + None, + generics, + ty, + expr.as_deref(), + vis, + *defaultness, + ); } ast::AssocItemKind::Type(box ast::TyAlias { defaultness, @@ -623,19 +640,8 @@ impl<'a> State<'a> { pub fn print_where_predicate(&mut self, predicate: &ast::WherePredicate) { match predicate { - ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { - bound_generic_params, - bounded_ty, - bounds, - .. - }) => { - self.print_formal_generic_params(bound_generic_params); - self.print_type(bounded_ty); - self.word(":"); - if !bounds.is_empty() { - self.nbsp(); - self.print_type_bounds(bounds); - } + ast::WherePredicate::BoundPredicate(where_bound_predicate) => { + self.print_where_bound_predicate(where_bound_predicate); } ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate { lifetime, @@ -658,6 +664,19 @@ impl<'a> State<'a> { } } + pub fn print_where_bound_predicate( + &mut self, + where_bound_predicate: &ast::WhereBoundPredicate, + ) { + self.print_formal_generic_params(&where_bound_predicate.bound_generic_params); + self.print_type(&where_bound_predicate.bounded_ty); + self.word(":"); + if !where_bound_predicate.bounds.is_empty() { + self.nbsp(); + self.print_type_bounds(&where_bound_predicate.bounds); + } + } + fn print_use_tree(&mut self, tree: &ast::UseTree) { match &tree.kind { ast::UseTreeKind::Simple(rename) => { diff --git a/compiler/rustc_attr/messages.ftl b/compiler/rustc_attr/messages.ftl index a7f8c993d4225..e6cbbaf3704bc 100644 --- a/compiler/rustc_attr/messages.ftl +++ b/compiler/rustc_attr/messages.ftl @@ -1,27 +1,38 @@ +attr_cfg_predicate_identifier = + `cfg` predicate key must be an identifier + +attr_deprecated_item_suggestion = + suggestions on deprecated items are unstable + .help = add `#![feature(deprecated_suggestion)]` to the crate root + .note = see #94785 for more details + attr_expected_one_cfg_pattern = expected 1 cfg-pattern -attr_invalid_predicate = - invalid predicate `{$predicate}` +attr_expected_single_version_literal = + expected single version literal -attr_multiple_item = - multiple '{$item}' items +attr_expected_version_literal = + expected a version literal + +attr_expects_feature_list = + `{$name}` expects a list of feature names + +attr_expects_features = + `{$name}` expects feature names attr_incorrect_meta_item = incorrect meta item -attr_unknown_meta_item = - unknown meta item '{$item}' - .label = expected one of {$expected} - -attr_missing_since = - missing 'since' +attr_incorrect_repr_format_align_one_arg = + incorrect `repr(align)` attribute format: `align` takes exactly one argument in parentheses -attr_missing_note = - missing 'note' +attr_incorrect_repr_format_generic = + incorrect `repr({$repr_arg})` attribute format + .suggestion = use parentheses instead -attr_multiple_stability_levels = - multiple stability levels +attr_incorrect_repr_format_packed_one_or_zero_arg = + incorrect `repr(packed)` attribute format: `packed` takes exactly one parenthesized argument, or no parentheses at all attr_invalid_issue_string = `issue` must be a non-zero numeric string or "none" @@ -31,17 +42,15 @@ attr_invalid_issue_string = .pos_overflow = number too large to fit in target type .neg_overflow = number too small to fit in target type -attr_missing_feature = - missing 'feature' - -attr_non_ident_feature = - 'feature' is not an identifier +attr_invalid_predicate = + invalid predicate `{$predicate}` -attr_missing_issue = - missing 'issue' +attr_invalid_repr_align_need_arg = + invalid `repr(align)` attribute: `align` needs an argument + .suggestion = supply an argument here -attr_incorrect_repr_format_packed_one_or_zero_arg = - incorrect `repr(packed)` attribute format: `packed` takes exactly one parenthesized argument, or no parentheses at all +attr_invalid_repr_generic = + invalid `repr({$repr_arg})` attribute: {$error_part} attr_invalid_repr_hint_no_paren = invalid representation hint: `{$name}` does not take a parenthesized argument list @@ -49,59 +58,50 @@ attr_invalid_repr_hint_no_paren = attr_invalid_repr_hint_no_value = invalid representation hint: `{$name}` does not take a value -attr_unsupported_literal_generic = - unsupported literal -attr_unsupported_literal_cfg_string = - literal in `cfg` predicate value must be a string -attr_unsupported_literal_deprecated_string = - literal in `deprecated` value must be a string -attr_unsupported_literal_deprecated_kv_pair = - item in `deprecated` must be a key/value pair -attr_unsupported_literal_suggestion = - consider removing the prefix +attr_missing_feature = + missing 'feature' -attr_invalid_repr_align_need_arg = - invalid `repr(align)` attribute: `align` needs an argument - .suggestion = supply an argument here +attr_missing_issue = + missing 'issue' -attr_invalid_repr_generic = - invalid `repr({$repr_arg})` attribute: {$error_part} +attr_missing_note = + missing 'note' -attr_incorrect_repr_format_align_one_arg = - incorrect `repr(align)` attribute format: `align` takes exactly one argument in parentheses +attr_missing_since = + missing 'since' -attr_incorrect_repr_format_generic = - incorrect `repr({$repr_arg})` attribute format - .suggestion = use parentheses instead +attr_multiple_item = + multiple '{$item}' items -attr_rustc_promotable_pairing = - `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute +attr_multiple_stability_levels = + multiple stability levels + +attr_non_ident_feature = + 'feature' is not an identifier attr_rustc_allowed_unstable_pairing = `rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute -attr_cfg_predicate_identifier = - `cfg` predicate key must be an identifier - -attr_deprecated_item_suggestion = - suggestions on deprecated items are unstable - .help = add `#![feature(deprecated_suggestion)]` to the crate root - .note = see #94785 for more details - -attr_expected_single_version_literal = - expected single version literal - -attr_expected_version_literal = - expected a version literal - -attr_expects_feature_list = - `{$name}` expects a list of feature names - -attr_expects_features = - `{$name}` expects feature names +attr_rustc_promotable_pairing = + `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute attr_soft_no_args = `soft` should not have any arguments +attr_unknown_meta_item = + unknown meta item '{$item}' + .label = expected one of {$expected} + attr_unknown_version_literal = unknown version literal format, assuming it refers to a future version + +attr_unsupported_literal_cfg_string = + literal in `cfg` predicate value must be a string +attr_unsupported_literal_deprecated_kv_pair = + item in `deprecated` must be a key/value pair +attr_unsupported_literal_deprecated_string = + literal in `deprecated` value must be a string +attr_unsupported_literal_generic = + unsupported literal +attr_unsupported_literal_suggestion = + consider removing the prefix diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 372a58857f3d3..3592287b95cd5 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -28,7 +28,7 @@ pub fn rust_version_symbol() -> Symbol { } pub fn is_builtin_attr(attr: &Attribute) -> bool { - attr.is_doc_comment() || attr.ident().filter(|ident| is_builtin_attr_name(ident.name)).is_some() + attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name)) } enum AttrError { @@ -800,18 +800,15 @@ pub struct Deprecation { } /// Finds the deprecation attribute. `None` if none exists. -pub fn find_deprecation(sess: &Session, attrs: &[Attribute]) -> Option<(Deprecation, Span)> { - find_deprecation_generic(sess, attrs.iter()) -} - -fn find_deprecation_generic<'a, I>(sess: &Session, attrs_iter: I) -> Option<(Deprecation, Span)> -where - I: Iterator, -{ +pub fn find_deprecation( + sess: &Session, + features: &Features, + attrs: &[Attribute], +) -> Option<(Deprecation, Span)> { let mut depr: Option<(Deprecation, Span)> = None; - let is_rustc = sess.features_untracked().staged_api; + let is_rustc = features.staged_api; - 'outer: for attr in attrs_iter { + 'outer: for attr in attrs { if !attr.has_name(sym::deprecated) { continue; } @@ -872,7 +869,7 @@ where } } sym::suggestion => { - if !sess.features_untracked().deprecated_suggestion { + if !features.deprecated_suggestion { sess.emit_err(session_diagnostics::DeprecatedItemSuggestion { span: mi.span, is_nightly: sess.is_nightly_build().then_some(()), @@ -890,7 +887,7 @@ where meta.span(), AttrError::UnknownMetaItem( pprust::path_to_string(&mi.path), - if sess.features_untracked().deprecated_suggestion { + if features.deprecated_suggestion { &["since", "note", "suggestion"] } else { &["since", "note"] @@ -1217,3 +1214,20 @@ pub fn parse_alignment(node: &ast::LitKind) -> Result { Err("not an unsuffixed integer") } } + +/// Read the content of a `rustc_confusables` attribute, and return the list of candidate names. +pub fn parse_confusables(attr: &Attribute) -> Option> { + let meta = attr.meta()?; + let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None }; + + let mut candidates = Vec::new(); + + for meta in metas { + let NestedMetaItem::Lit(meta_lit) = meta else { + return None; + }; + candidates.push(meta_lit.symbol); + } + + return Some(candidates); +} diff --git a/compiler/rustc_borrowck/messages.ftl b/compiler/rustc_borrowck/messages.ftl index 4a616dc24641a..67fdb671742da 100644 --- a/compiler/rustc_borrowck/messages.ftl +++ b/compiler/rustc_borrowck/messages.ftl @@ -1,188 +1,108 @@ -borrowck_move_unsized = - cannot move a value of type `{$ty}` - .label = the size of `{$ty}` cannot be statically determined - -borrowck_higher_ranked_lifetime_error = - higher-ranked lifetime error - -borrowck_could_not_prove = - could not prove `{$predicate}` - -borrowck_could_not_normalize = - could not normalize `{$value}` - -borrowck_higher_ranked_subtype_error = - higher-ranked subtype error - -borrowck_generic_does_not_live_long_enough = - `{$kind}` does not live long enough - -borrowck_move_borrowed = - cannot move out of `{$desc}` because it is borrowed - -borrowck_var_does_not_need_mut = - variable does not need to be mutable - .suggestion = remove this `mut` - -borrowck_var_cannot_escape_closure = - captured variable cannot escape `FnMut` closure body - .note = `FnMut` closures only have access to their captured variables while they are executing... - .cannot_escape = ...therefore, they cannot allow references to captured variables to escape - -borrowck_var_here_defined = variable defined here - -borrowck_var_here_captured = variable captured here - -borrowck_closure_inferred_mut = inferred to be a `FnMut` closure - -borrowck_returned_closure_escaped = - returns a closure that contains a reference to a captured variable, which then escapes the closure body - -borrowck_returned_async_block_escaped = - returns an `async` block that contains a reference to a captured variable, which then escapes the closure body - -borrowck_returned_ref_escaped = - returns a reference to a captured variable which escapes the closure body - -borrowck_lifetime_constraints_error = - lifetime may not live long enough - -borrowck_returned_lifetime_wrong = - {$mir_def_name} was supposed to return data with lifetime `{$outlived_fr_name}` but it is returning data with lifetime `{$fr_name}` - -borrowck_returned_lifetime_short = - {$category_desc}requires that `{$free_region_name}` must outlive `{$outlived_fr_name}` - -borrowck_used_impl_require_static = - the used `impl` has a `'static` requirement - -borrowck_borrow_due_to_use_generator = - borrow occurs due to use in generator - -borrowck_use_due_to_use_generator = - use occurs due to use in generator +borrowck_assign_due_to_use_closure = + assignment occurs due to use in closure borrowck_assign_due_to_use_generator = assign occurs due to use in generator +borrowck_assign_part_due_to_use_closure = + assignment to part occurs due to use in closure + borrowck_assign_part_due_to_use_generator = assign to part occurs due to use in generator borrowck_borrow_due_to_use_closure = borrow occurs due to use in closure -borrowck_use_due_to_use_closure = - use occurs due to use in closure +borrowck_borrow_due_to_use_generator = + borrow occurs due to use in generator -borrowck_assign_due_to_use_closure = - assignment occurs due to use in closure +borrowck_calling_operator_moves_lhs = + calling this operator moves the left-hand side -borrowck_assign_part_due_to_use_closure = - assignment to part occurs due to use in closure +borrowck_cannot_move_when_borrowed = + cannot move out of {$place -> + [value] value + *[other] {$place} + } because it is borrowed + .label = borrow of {$borrow_place -> + [value] value + *[other] {$borrow_place} + } occurs here + .move_label = move out of {$value_place -> + [value] value + *[other] {$value_place} + } occurs here borrowck_capture_immute = capture is immutable because of use here -borrowck_capture_mut = - capture is mutable because of use here - borrowck_capture_move = capture is moved because of use here -borrowck_var_borrow_by_use_place_in_generator = - {$is_single_var -> - *[true] borrow occurs - [false] borrows occur - } due to use of {$place} in generator - -borrowck_var_borrow_by_use_place_in_closure = - {$is_single_var -> - *[true] borrow occurs - [false] borrows occur - } due to use of {$place} in closure +borrowck_capture_mut = + capture is mutable because of use here -borrowck_var_borrow_by_use_in_generator = - borrow occurs due to use in generator +borrowck_closure_inferred_mut = inferred to be a `FnMut` closure -borrowck_var_borrow_by_use_in_closure = - borrow occurs due to use in closure +borrowck_closure_invoked_twice = + closure cannot be invoked more than once because it moves the variable `{$place_name}` out of its environment -borrowck_var_move_by_use_place_in_generator = - move occurs due to use of {$place} in generator +borrowck_closure_moved_twice = + closure cannot be moved more than once as it is not `Copy` due to moving the variable `{$place_name}` out of its environment -borrowck_var_move_by_use_place_in_closure = - move occurs due to use of {$place} in closure +borrowck_consider_borrow_type_contents = + help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents -borrowck_var_move_by_use_in_generator = - move occurs due to use in generator +borrowck_could_not_normalize = + could not normalize `{$value}` -borrowck_var_move_by_use_in_closure = - move occurs due to use in closure +borrowck_could_not_prove = + could not prove `{$predicate}` -borrowck_partial_var_move_by_use_in_generator = - variable {$is_partial -> - [true] partially moved - *[false] moved - } due to use in generator +borrowck_func_take_self_moved_place = + `{$func}` takes ownership of the receiver `self`, which moves {$place_name} -borrowck_partial_var_move_by_use_in_closure = - variable {$is_partial -> - [true] partially moved - *[false] moved - } due to use in closure +borrowck_generic_does_not_live_long_enough = + `{$kind}` does not live long enough -borrowck_var_first_borrow_by_use_place_in_generator = - first borrow occurs due to use of {$place} in generator +borrowck_higher_ranked_lifetime_error = + higher-ranked lifetime error -borrowck_var_first_borrow_by_use_place_in_closure = - first borrow occurs due to use of {$place} in closure +borrowck_higher_ranked_subtype_error = + higher-ranked subtype error -borrowck_var_second_borrow_by_use_place_in_generator = - second borrow occurs due to use of {$place} in generator +borrowck_lifetime_constraints_error = + lifetime may not live long enough -borrowck_var_second_borrow_by_use_place_in_closure = - second borrow occurs due to use of {$place} in closure +borrowck_move_borrowed = + cannot move out of `{$desc}` because it is borrowed -borrowck_var_mutable_borrow_by_use_place_in_closure = - mutable borrow occurs due to use of {$place} in closure +borrowck_move_out_place_here = + {$place} is moved here -borrowck_cannot_move_when_borrowed = - cannot move out of {$place -> - [value] value - *[other] {$place} - } because it is borrowed - .label = borrow of {$borrow_place -> - [value] value - *[other] {$borrow_place} - } occurs here - .move_label = move out of {$value_place -> - [value] value - *[other] {$value_place} - } occurs here +borrowck_move_unsized = + cannot move a value of type `{$ty}` + .label = the size of `{$ty}` cannot be statically determined -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_moved_a_fn_once_in_call = + this value implements `FnOnce`, which causes it to be moved when called -borrowck_moved_due_to_call = +borrowck_moved_due_to_await = {$place_name} {$is_partial -> [true] partially moved *[false] moved } due to this {$is_loop_message -> - [true] call, in previous iteration of loop - *[false] call + [true] await, in previous iteration of loop + *[false] await } -borrowck_moved_due_to_usage_in_operator = +borrowck_moved_due_to_call = {$place_name} {$is_partial -> [true] partially moved *[false] moved - } due to usage in {$is_loop_message -> - [true] operator, in previous iteration of loop - *[false] operator + } due to this {$is_loop_message -> + [true] call, in previous iteration of loop + *[false] call } borrowck_moved_due_to_implicit_into_iter_call = @@ -203,13 +123,74 @@ borrowck_moved_due_to_method_call = *[false] call } -borrowck_moved_due_to_await = +borrowck_moved_due_to_usage_in_operator = {$place_name} {$is_partial -> [true] partially moved *[false] moved - } due to this {$is_loop_message -> - [true] await, in previous iteration of loop - *[false] await + } due to usage in {$is_loop_message -> + [true] operator, in previous iteration of loop + *[false] operator + } + +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 + *[false] moved + } due to use in closure + +borrowck_partial_var_move_by_use_in_generator = + variable {$is_partial -> + [true] partially moved + *[false] moved + } due to use in generator + +borrowck_returned_async_block_escaped = + returns an `async` block that contains a reference to a captured variable, which then escapes the closure body + +borrowck_returned_closure_escaped = + returns a closure that contains a reference to a captured variable, which then escapes the closure body + +borrowck_returned_lifetime_short = + {$category_desc}requires that `{$free_region_name}` must outlive `{$outlived_fr_name}` + +borrowck_returned_lifetime_wrong = + {$mir_def_name} was supposed to return data with lifetime `{$outlived_fr_name}` but it is returning data with lifetime `{$fr_name}` + +borrowck_returned_ref_escaped = + returns a reference to a captured variable which escapes the closure body + +borrowck_suggest_create_freash_reborrow = + consider reborrowing the `Pin` instead of moving it + +borrowck_suggest_iterate_over_slice = + consider iterating over a slice of the `{$ty}`'s content to avoid moving into the `for` loop + +borrowck_ty_no_impl_copy = + {$is_partial_move -> + [true] partial move + *[false] move + } occurs because {$place} has type `{$ty}`, which does not implement the `Copy` trait + +borrowck_use_due_to_use_closure = + use occurs due to use in closure + +borrowck_use_due_to_use_generator = + use occurs due to use in generator + +borrowck_used_impl_require_static = + the used `impl` has a `'static` requirement + +borrowck_value_capture_here = + value captured {$is_within -> + [true] here by generator + *[false] here } borrowck_value_moved_here = @@ -224,41 +205,60 @@ borrowck_value_moved_here = *[false] {""} } -borrowck_consider_borrow_type_contents = - help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents +borrowck_var_borrow_by_use_in_closure = + borrow occurs due to use in closure -borrowck_moved_a_fn_once_in_call = - this value implements `FnOnce`, which causes it to be moved when called +borrowck_var_borrow_by_use_in_generator = + borrow occurs due to use in generator -borrowck_calling_operator_moves_lhs = - calling this operator moves the left-hand side +borrowck_var_borrow_by_use_place_in_closure = + {$is_single_var -> + *[true] borrow occurs + [false] borrows occur + } due to use of {$place} in closure -borrowck_func_take_self_moved_place = - `{$func}` takes ownership of the receiver `self`, which moves {$place_name} +borrowck_var_borrow_by_use_place_in_generator = + {$is_single_var -> + *[true] borrow occurs + [false] borrows occur + } due to use of {$place} in generator -borrowck_suggest_iterate_over_slice = - consider iterating over a slice of the `{$ty}`'s content to avoid moving into the `for` loop +borrowck_var_cannot_escape_closure = + captured variable cannot escape `FnMut` closure body + .note = `FnMut` closures only have access to their captured variables while they are executing... + .cannot_escape = ...therefore, they cannot allow references to captured variables to escape -borrowck_suggest_create_freash_reborrow = - consider reborrowing the `Pin` instead of moving it +borrowck_var_does_not_need_mut = + variable does not need to be mutable + .suggestion = remove this `mut` -borrowck_value_capture_here = - value captured {$is_within -> - [true] here by generator - *[false] here - } +borrowck_var_first_borrow_by_use_place_in_closure = + first borrow occurs due to use of {$place} in closure -borrowck_move_out_place_here = - {$place} is moved here +borrowck_var_first_borrow_by_use_place_in_generator = + first borrow occurs due to use of {$place} in generator -borrowck_closure_invoked_twice = - closure cannot be invoked more than once because it moves the variable `{$place_name}` out of its environment +borrowck_var_here_captured = variable captured here -borrowck_closure_moved_twice = - closure cannot be moved more than once as it is not `Copy` due to moving the variable `{$place_name}` out of its environment +borrowck_var_here_defined = variable defined here -borrowck_ty_no_impl_copy = - {$is_partial_move -> - [true] partial move - *[false] move - } occurs because {$place} has type `{$ty}`, which does not implement the `Copy` trait +borrowck_var_move_by_use_in_closure = + move occurs due to use in closure + +borrowck_var_move_by_use_in_generator = + move occurs due to use in generator + +borrowck_var_move_by_use_place_in_closure = + move occurs due to use of {$place} in closure + +borrowck_var_move_by_use_place_in_generator = + move occurs due to use of {$place} in generator + +borrowck_var_mutable_borrow_by_use_place_in_closure = + mutable borrow occurs due to use of {$place} in closure + +borrowck_var_second_borrow_by_use_place_in_closure = + second borrow occurs due to use of {$place} in closure + +borrowck_var_second_borrow_by_use_place_in_generator = + second borrow occurs due to use of {$place} in generator diff --git a/compiler/rustc_borrowck/src/borrow_set.rs b/compiler/rustc_borrowck/src/borrow_set.rs index 6be20b0974ddb..0b44beeb004c4 100644 --- a/compiler/rustc_borrowck/src/borrow_set.rs +++ b/compiler/rustc_borrowck/src/borrow_set.rs @@ -72,8 +72,11 @@ impl<'tcx> fmt::Display for BorrowData<'tcx> { let kind = match self.kind { mir::BorrowKind::Shared => "", mir::BorrowKind::Shallow => "shallow ", - mir::BorrowKind::Unique => "uniq ", - mir::BorrowKind::Mut { .. } => "mut ", + mir::BorrowKind::Mut { kind: mir::MutBorrowKind::ClosureCapture } => "uniq ", + // FIXME: differentiate `TwoPhaseBorrow` + mir::BorrowKind::Mut { + kind: mir::MutBorrowKind::Default | mir::MutBorrowKind::TwoPhaseBorrow, + } => "mut ", }; write!(w, "&{:?} {}{:?}", self.region, kind, self.borrowed_place) } diff --git a/compiler/rustc_borrowck/src/borrowck_errors.rs b/compiler/rustc_borrowck/src/borrowck_errors.rs index acca1a1477f25..a2c7e767b4cc2 100644 --- a/compiler/rustc_borrowck/src/borrowck_errors.rs +++ b/compiler/rustc_borrowck/src/borrowck_errors.rs @@ -37,8 +37,8 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { desc, ); - err.span_label(borrow_span, format!("{} is borrowed here", borrow_desc)); - err.span_label(span, format!("use of borrowed {}", borrow_desc)); + err.span_label(borrow_span, format!("{borrow_desc} is borrowed here")); + err.span_label(span, format!("use of borrowed {borrow_desc}")); err } @@ -51,8 +51,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { old_opt_via: &str, old_load_end_span: Option, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { - let via = - |msg: &str| if msg.is_empty() { "".to_string() } else { format!(" (via {})", msg) }; + let via = |msg: &str| if msg.is_empty() { "".to_string() } else { format!(" (via {msg})") }; let mut err = struct_span_err!( self, new_loan_span, @@ -143,9 +142,9 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { ); err.span_label( new_loan_span, - format!("{} construction occurs here{}", container_name, opt_via), + format!("{container_name} construction occurs here{opt_via}"), ); - err.span_label(old_loan_span, format!("borrow occurs here{}", old_opt_via)); + err.span_label(old_loan_span, format!("borrow occurs here{old_opt_via}")); if let Some(previous_end_span) = previous_end_span { err.span_label(previous_end_span, "borrow ends here"); } @@ -173,13 +172,10 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { opt_via, kind_new, ); - err.span_label( - new_loan_span, - format!("{}borrow occurs here{}", second_borrow_desc, opt_via), - ); + err.span_label(new_loan_span, format!("{second_borrow_desc}borrow occurs here{opt_via}")); err.span_label( old_loan_span, - format!("{} construction occurs here{}", container_name, old_opt_via), + format!("{container_name} construction occurs here{old_opt_via}"), ); if let Some(previous_end_span) = previous_end_span { err.span_label(previous_end_span, "borrow from closure ends here"); @@ -199,8 +195,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { msg_old: &str, old_load_end_span: Option, ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { - let via = - |msg: &str| if msg.is_empty() { "".to_string() } else { format!(" (via {})", msg) }; + let via = |msg: &str| if msg.is_empty() { "".to_string() } else { format!(" (via {msg})") }; let mut err = struct_span_err!( self, span, @@ -216,22 +211,21 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { if msg_new == "" { // If `msg_new` is empty, then this isn't a borrow of a union field. - err.span_label(span, format!("{} borrow occurs here", kind_new)); - err.span_label(old_span, format!("{} borrow occurs here", kind_old)); + err.span_label(span, format!("{kind_new} borrow occurs here")); + err.span_label(old_span, format!("{kind_old} borrow occurs here")); } else { // If `msg_new` isn't empty, then this a borrow of a union field. err.span_label( span, format!( - "{} borrow of {} -- which overlaps with {} -- occurs here", - kind_new, msg_new, msg_old, + "{kind_new} borrow of {msg_new} -- which overlaps with {msg_old} -- occurs here", ), ); err.span_label(old_span, format!("{} borrow occurs here{}", kind_old, via(msg_old))); } if let Some(old_load_end_span) = old_load_end_span { - err.span_label(old_load_end_span, format!("{} borrow ends here", kind_old)); + err.span_label(old_load_end_span, format!("{kind_old} borrow ends here")); } err } @@ -250,8 +244,8 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { desc, ); - err.span_label(borrow_span, format!("{} is borrowed here", desc)); - err.span_label(span, format!("{} is assigned to here but it was already borrowed", desc)); + err.span_label(borrow_span, format!("{desc} is borrowed here")); + err.span_label(span, format!("{desc} is assigned to here but it was already borrowed")); err } @@ -278,7 +272,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { move_from_span: Span, move_from_desc: &str, ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { - struct_span_err!(self, move_from_span, E0507, "cannot move out of {}", move_from_desc,) + struct_span_err!(self, move_from_span, E0507, "cannot move out of {}", move_from_desc) } /// Signal an error due to an attempt to move out of the interior @@ -330,7 +324,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { optional_adverb_for_moved: &str, moved_path: Option, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { - let moved_path = moved_path.map(|mp| format!(": `{}`", mp)).unwrap_or_default(); + let moved_path = moved_path.map(|mp| format!(": `{mp}`")).unwrap_or_default(); struct_span_err!( self, @@ -369,8 +363,8 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { immutable_place, immutable_section, ); - err.span_label(mutate_span, format!("cannot {}", action)); - err.span_label(immutable_span, format!("value is immutable in {}", immutable_section)); + err.span_label(mutate_span, format!("cannot {action}")); + err.span_label(immutable_span, format!("value is immutable in {immutable_section}")); err } @@ -428,7 +422,7 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { err.span_label( span, - format!("{}s a {} data owned by the current function", return_kind, reference_desc), + format!("{return_kind}s a {reference_desc} data owned by the current function"), ); err @@ -449,8 +443,8 @@ impl<'cx, 'tcx> crate::MirBorrowckCtxt<'cx, 'tcx> { "{closure_kind} may outlive the current {scope}, but it borrows {borrowed_path}, \ which is owned by the current {scope}", ); - err.span_label(capture_span, format!("{} is borrowed here", borrowed_path)) - .span_label(closure_span, format!("may outlive borrowed value {}", borrowed_path)); + err.span_label(capture_span, format!("{borrowed_path} is borrowed here")) + .span_label(closure_span, format!("may outlive borrowed value {borrowed_path}")); err } diff --git a/compiler/rustc_borrowck/src/constraint_generation.rs b/compiler/rustc_borrowck/src/constraint_generation.rs index 2aa09a3f26c17..1f642099f0899 100644 --- a/compiler/rustc_borrowck/src/constraint_generation.rs +++ b/compiler/rustc_borrowck/src/constraint_generation.rs @@ -4,11 +4,11 @@ use rustc_infer::infer::InferCtxt; use rustc_middle::mir::visit::TyContext; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::{ - BasicBlock, BasicBlockData, Body, Local, Location, Place, PlaceRef, ProjectionElem, Rvalue, - SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UserTypeProjection, + Body, Local, Location, Place, PlaceRef, ProjectionElem, Rvalue, SourceInfo, Statement, + StatementKind, Terminator, TerminatorKind, UserTypeProjection, }; -use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::visit::TypeVisitable; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt}; use crate::{ @@ -49,15 +49,11 @@ struct ConstraintGeneration<'cg, 'tcx> { } impl<'cg, 'tcx> Visitor<'tcx> for ConstraintGeneration<'cg, 'tcx> { - fn visit_basic_block_data(&mut self, bb: BasicBlock, data: &BasicBlockData<'tcx>) { - self.super_basic_block_data(bb, data); - } - - /// We sometimes have `substs` within an rvalue, or within a + /// We sometimes have `args` within an rvalue, or within a /// call. Make them live at the location where they appear. - fn visit_substs(&mut self, substs: &SubstsRef<'tcx>, location: Location) { - self.add_regular_live_constraint(*substs, location); - self.super_substs(substs); + fn visit_args(&mut self, args: &GenericArgsRef<'tcx>, location: Location) { + self.add_regular_live_constraint(*args, location); + self.super_args(args); } /// We sometimes have `region` within an rvalue, or within a diff --git a/compiler/rustc_borrowck/src/consumers.rs b/compiler/rustc_borrowck/src/consumers.rs index d257145373f72..becfa535a59cc 100644 --- a/compiler/rustc_borrowck/src/consumers.rs +++ b/compiler/rustc_borrowck/src/consumers.rs @@ -30,7 +30,7 @@ pub use super::{ /// will be retrieved. #[derive(Debug, Copy, Clone)] pub enum ConsumerOptions { - /// Retrieve the [`Body`] along with the [`BorrowSet`](super::borrow_set::BorrowSet) + /// Retrieve the [`Body`] along with the [`BorrowSet`] /// and [`RegionInferenceContext`]. If you would like the body only, use /// [`TyCtxt::mir_promoted`]. /// diff --git a/compiler/rustc_borrowck/src/dataflow.rs b/compiler/rustc_borrowck/src/dataflow.rs index 2daa82aef3957..4ac633c263fab 100644 --- a/compiler/rustc_borrowck/src/dataflow.rs +++ b/compiler/rustc_borrowck/src/dataflow.rs @@ -2,12 +2,14 @@ #![deny(rustc::diagnostic_outside_of_impl)] use rustc_data_structures::fx::FxIndexMap; use rustc_index::bit_set::BitSet; -use rustc_middle::mir::{self, BasicBlock, Body, Location, Place}; +use rustc_middle::mir::{ + self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges, +}; use rustc_middle::ty::RegionVid; use rustc_middle::ty::TyCtxt; use rustc_mir_dataflow::impls::{EverInitializedPlaces, MaybeUninitializedPlaces}; use rustc_mir_dataflow::ResultsVisitable; -use rustc_mir_dataflow::{self, fmt::DebugWithContext, CallReturnPlaces, GenKill}; +use rustc_mir_dataflow::{self, fmt::DebugWithContext, GenKill}; use rustc_mir_dataflow::{Analysis, Direction, Results}; use std::fmt; @@ -59,7 +61,7 @@ macro_rules! impl_visitable { } fn reconstruct_before_statement_effect( - &self, + &mut self, state: &mut Self::FlowState, stmt: &mir::Statement<'tcx>, loc: Location, @@ -69,7 +71,7 @@ macro_rules! impl_visitable { } fn reconstruct_statement_effect( - &self, + &mut self, state: &mut Self::FlowState, stmt: &mir::Statement<'tcx>, loc: Location, @@ -79,7 +81,7 @@ macro_rules! impl_visitable { } fn reconstruct_before_terminator_effect( - &self, + &mut self, state: &mut Self::FlowState, term: &mir::Terminator<'tcx>, loc: Location, @@ -89,7 +91,7 @@ macro_rules! impl_visitable { } fn reconstruct_terminator_effect( - &self, + &mut self, state: &mut Self::FlowState, term: &mir::Terminator<'tcx>, loc: Location, @@ -125,15 +127,9 @@ pub struct Borrows<'a, 'tcx> { borrows_out_of_scope_at_location: FxIndexMap>, } -struct StackEntry { - bb: mir::BasicBlock, - lo: usize, - hi: usize, -} - struct OutOfScopePrecomputer<'a, 'tcx> { visited: BitSet, - visit_stack: Vec, + visit_stack: Vec, body: &'a Body<'tcx>, regioncx: &'a RegionInferenceContext<'tcx>, borrows_out_of_scope_at_location: FxIndexMap>, @@ -158,29 +154,50 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { borrow_region: RegionVid, first_location: Location, ) { - // We visit one BB at a time. The complication is that we may start in the - // middle of the first BB visited (the one containing `first_location`), in which - // case we may have to later on process the first part of that BB if there - // is a path back to its start. - - // For visited BBs, we record the index of the first statement processed. - // (In fully processed BBs this index is 0.) Note also that we add BBs to - // `visited` once they are added to `stack`, before they are actually - // processed, because this avoids the need to look them up again on - // completion. - self.visited.insert(first_location.block); - let first_block = first_location.block; - let mut first_lo = first_location.statement_index; - let first_hi = self.body[first_block].statements.len(); + let first_bb_data = &self.body.basic_blocks[first_block]; + + // This is the first block, we only want to visit it from the creation of the borrow at + // `first_location`. + let first_lo = first_location.statement_index; + let first_hi = first_bb_data.statements.len(); + + if let Some(kill_stmt) = self.regioncx.first_non_contained_inclusive( + borrow_region, + first_block, + first_lo, + first_hi, + ) { + let kill_location = Location { block: first_block, statement_index: kill_stmt }; + // If region does not contain a point at the location, then add to list and skip + // successor locations. + debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location); + self.borrows_out_of_scope_at_location + .entry(kill_location) + .or_default() + .push(borrow_index); + + // The borrow is already dead, there is no need to visit other blocks. + return; + } - self.visit_stack.push(StackEntry { bb: first_block, lo: first_lo, hi: first_hi }); + // The borrow is not dead. Add successor BBs to the work list, if necessary. + for succ_bb in first_bb_data.terminator().successors() { + if self.visited.insert(succ_bb) { + self.visit_stack.push(succ_bb); + } + } - 'preorder: while let Some(StackEntry { bb, lo, hi }) = self.visit_stack.pop() { + // We may end up visiting `first_block` again. This is not an issue: we know at this point + // that it does not kill the borrow in the `first_lo..=first_hi` range, so checking the + // `0..first_lo` range and the `0..first_hi` range give the same result. + while let Some(block) = self.visit_stack.pop() { + let bb_data = &self.body[block]; + let num_stmts = bb_data.statements.len(); if let Some(kill_stmt) = - self.regioncx.first_non_contained_inclusive(borrow_region, bb, lo, hi) + self.regioncx.first_non_contained_inclusive(borrow_region, block, 0, num_stmts) { - let kill_location = Location { block: bb, statement_index: kill_stmt }; + let kill_location = Location { block, statement_index: kill_stmt }; // If region does not contain a point at the location, then add to list and skip // successor locations. debug!("borrow {:?} gets killed at {:?}", borrow_index, kill_location); @@ -188,38 +205,15 @@ impl<'tcx> OutOfScopePrecomputer<'_, 'tcx> { .entry(kill_location) .or_default() .push(borrow_index); - continue 'preorder; - } - // If we process the first part of the first basic block (i.e. we encounter that block - // for the second time), we no longer have to visit its successors again. - if bb == first_block && hi != first_hi { + // We killed the borrow, so we do not visit this block's successors. continue; } // Add successor BBs to the work list, if necessary. - let bb_data = &self.body[bb]; - debug_assert!(hi == bb_data.statements.len()); for succ_bb in bb_data.terminator().successors() { - if !self.visited.insert(succ_bb) { - if succ_bb == first_block && first_lo > 0 { - // `succ_bb` has been seen before. If it wasn't - // fully processed, add its first part to `stack` - // for processing. - self.visit_stack.push(StackEntry { bb: succ_bb, lo: 0, hi: first_lo - 1 }); - - // And update this entry with 0, to represent the - // whole BB being processed. - first_lo = 0; - } - } else { - // succ_bb hasn't been seen before. Add it to - // `stack` for processing. - self.visit_stack.push(StackEntry { - bb: succ_bb, - lo: 0, - hi: self.body[succ_bb].statements.len(), - }); + if self.visited.insert(succ_bb) { + self.visit_stack.push(succ_bb); } } } @@ -342,8 +336,12 @@ impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> { impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { type Idx = BorrowIndex; + fn domain_size(&self, _: &mir::Body<'tcx>) -> usize { + self.borrow_set.len() + } + fn before_statement_effect( - &self, + &mut self, trans: &mut impl GenKill, _statement: &mir::Statement<'tcx>, location: Location, @@ -352,7 +350,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { } fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, stmt: &mir::Statement<'tcx>, location: Location, @@ -368,7 +366,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { return; } let index = self.borrow_set.get_index_of(&location).unwrap_or_else(|| { - panic!("could not find BorrowIndex for location {:?}", location); + panic!("could not find BorrowIndex for location {location:?}"); }); trans.gen(index); @@ -400,7 +398,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { } fn before_terminator_effect( - &self, + &mut self, trans: &mut impl GenKill, _terminator: &mir::Terminator<'tcx>, location: Location, @@ -408,12 +406,12 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { self.kill_loans_out_of_scope_at_location(trans, location); } - fn terminator_effect( - &self, - trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, _location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind { for op in operands { if let mir::InlineAsmOperand::Out { place: Some(place), .. } @@ -423,10 +421,11 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { } } } + terminator.edges() } fn call_return_effect( - &self, + &mut self, _trans: &mut impl GenKill, _block: mir::BasicBlock, _return_places: CallReturnPlaces<'_, 'tcx>, diff --git a/compiler/rustc_borrowck/src/def_use.rs b/compiler/rustc_borrowck/src/def_use.rs index b775739fed2ae..b719a610e07c7 100644 --- a/compiler/rustc_borrowck/src/def_use.rs +++ b/compiler/rustc_borrowck/src/def_use.rs @@ -50,7 +50,6 @@ pub fn categorize(context: PlaceContext) -> Option { PlaceContext::MutatingUse(MutatingUseContext::Borrow) | PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) | PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) | - PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow) | // `PlaceMention` and `AscribeUserType` both evaluate the place, which must not // contain dangling references. diff --git a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs index f41795d60a0b4..cfcf31fce32bd 100644 --- a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs @@ -180,24 +180,25 @@ trait TypeOpInfo<'tcx> { return; }; - let placeholder_region = tcx.mk_re_placeholder(ty::Placeholder { - universe: adjusted_universe.into(), - bound: placeholder.bound, - }); - - let error_region = - if let RegionElement::PlaceholderRegion(error_placeholder) = error_element { - let adjusted_universe = - error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32()); - adjusted_universe.map(|adjusted| { - tcx.mk_re_placeholder(ty::Placeholder { - universe: adjusted.into(), - bound: error_placeholder.bound, - }) - }) - } else { - None - }; + let placeholder_region = ty::Region::new_placeholder( + tcx, + ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound }, + ); + + let error_region = if let RegionElement::PlaceholderRegion(error_placeholder) = + error_element + { + let adjusted_universe = + error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32()); + adjusted_universe.map(|adjusted| { + ty::Region::new_placeholder( + tcx, + ty::Placeholder { universe: adjusted.into(), bound: error_placeholder.bound }, + ) + }) + } else { + None + }; debug!(?placeholder_region); @@ -390,7 +391,7 @@ fn try_extract_error_from_fulfill_cx<'tcx>( error_region, ®ion_constraints, |vid| ocx.infcx.region_var_origin(vid), - |vid| ocx.infcx.universe_of_region(ocx.infcx.tcx.mk_re_var(vid)), + |vid| ocx.infcx.universe_of_region(ty::Region::new_var(ocx.infcx.tcx, vid)), ) } @@ -411,7 +412,7 @@ fn try_extract_error_from_region_constraints<'tcx>( } // FIXME: Should this check the universe of the var? Constraint::VarSubReg(vid, sup) if sup == placeholder_region => { - Some((infcx.tcx.mk_re_var(vid), cause.clone())) + Some((ty::Region::new_var(infcx.tcx, vid), cause.clone())) } _ => None, } diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index 04b8174079acc..fe4a45b38989a 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1,6 +1,5 @@ -use std::iter; - use either::Either; +use hir::PatField; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{ @@ -14,9 +13,10 @@ use rustc_infer::traits::ObligationCause; use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::mir::tcx::PlaceTy; use rustc_middle::mir::{ - self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, ConstraintCategory, - FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef, - ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, VarBindingForm, + self, AggregateKind, BindingForm, BorrowKind, CallSource, ClearCrossCrate, ConstraintCategory, + FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, MutBorrowKind, Operand, Place, + PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + VarBindingForm, }; use rustc_middle::ty::{self, suggest_constraining_type_params, PredicateKind, Ty}; use rustc_middle::util::CallKind; @@ -27,6 +27,7 @@ use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{BytePos, Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::ObligationCtxt; +use std::iter; use crate::borrow_set::TwoPhaseActivation; use crate::borrowck_errors; @@ -362,21 +363,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } let hir = self.infcx.tcx.hir(); - if let Some(hir::Node::Item(hir::Item { - kind: hir::ItemKind::Fn(_, _, body_id), - .. - })) = hir.find(self.mir_hir_id()) - && let Some(hir::Node::Expr(expr)) = hir.find(body_id.hir_id) - { + if let Some(body_id) = hir.maybe_body_owned_by(self.mir_def_id()) { + let expr = hir.body(body_id).value; let place = &self.move_data.move_paths[mpi].place; - let span = place.as_local() - .map(|local| self.body.local_decls[local].source_info.span); - let mut finder = ExpressionFinder { - expr_span: move_span, - expr: None, - pat: None, - parent_pat: None, - }; + let span = place.as_local().map(|local| self.body.local_decls[local].source_info.span); + let mut finder = + ExpressionFinder { expr_span: move_span, expr: None, pat: None, parent_pat: None }; finder.visit_expr(expr); if let Some(span) = span && let Some(expr) = finder.expr { for (_, expr) in hir.parent_iter(expr.hir_id) { @@ -460,7 +452,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } = move_spans { // We already suggest cloning for these cases in `explain_captures`. } else { - self.suggest_cloning(err, ty, move_span); + self.suggest_cloning(err, ty, expr, move_span); } } } @@ -661,7 +653,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { err.span_suggestion_verbose( sugg_span.shrink_to_hi(), "consider assigning a value", - format!(" = {}", assign_value), + format!(" = {assign_value}"), Applicability::MaybeIncorrect, ); } @@ -677,8 +669,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { let tcx = self.infcx.tcx; // Find out if the predicates show that the type is a Fn or FnMut - let find_fn_kind_from_did = |(pred, _): (ty::Predicate<'tcx>, _)| { - if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = pred.kind().skip_binder() + let find_fn_kind_from_did = |(pred, _): (ty::Clause<'tcx>, _)| { + if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder() && pred.self_ty() == ty { if Some(pred.def_id()) == tcx.lang_items().fn_trait() { @@ -701,11 +693,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { .iter() .copied() .find_map(find_fn_kind_from_did), - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => tcx + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => tcx .explicit_item_bounds(def_id) - .subst_iter_copied(tcx, substs) - .find_map(find_fn_kind_from_did), - ty::Closure(_, substs) => match substs.as_closure().kind() { + .iter_instantiated_copied(tcx, args) + .find_map(|(clause, span)| find_fn_kind_from_did((clause, span))), + ty::Closure(_, args) => match args.as_closure().kind() { ty::ClosureKind::Fn => Some(hir::Mutability::Not), ty::ClosureKind::FnMut => Some(hir::Mutability::Mut), _ => None, @@ -713,7 +705,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { _ => None, }; - let Some(borrow_level) = borrow_level else { return false; }; + let Some(borrow_level) = borrow_level else { + return false; + }; let sugg = move_sites .iter() .map(|move_site| { @@ -733,9 +727,21 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { true } - fn suggest_cloning(&self, err: &mut Diagnostic, ty: Ty<'tcx>, span: Span) { + fn suggest_cloning( + &self, + err: &mut Diagnostic, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, + ) { let tcx = self.infcx.tcx; // Try to find predicates on *generic params* that would allow copying `ty` + let suggestion = + if let Some(symbol) = tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { + format!(": {symbol}.clone()") + } else { + ".clone()".to_owned() + }; if let Some(clone_trait_def) = tcx.lang_items().clone_trait() && self.infcx .type_implements_trait( @@ -745,10 +751,20 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ) .must_apply_modulo_regions() { + let msg = if let ty::Adt(def, _) = ty.kind() + && [ + tcx.get_diagnostic_item(sym::Arc), + tcx.get_diagnostic_item(sym::Rc), + ].contains(&Some(def.did())) + { + "clone the value to increment its reference count" + } else { + "consider cloning the value if the performance cost is acceptable" + }; err.span_suggestion_verbose( span.shrink_to_hi(), - "consider cloning the value if the performance cost is acceptable", - ".clone()", + msg, + suggestion, Applicability::MachineApplicable, ); } @@ -762,7 +778,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { .typeck_root_def_id(self.mir_def_id().to_def_id()) .as_local() .and_then(|def_id| tcx.hir().get_generics(def_id)) - else { return; }; + else { + return; + }; // Try to find predicates on *generic params* that would allow copying `ty` let ocx = ObligationCtxt::new(&self.infcx); let copy_did = tcx.require_lang_item(LangItem::Copy, Some(span)); @@ -775,7 +793,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { let predicates: Result, _> = errors .into_iter() .map(|err| match err.obligation.predicate.kind().skip_binder() { - PredicateKind::Clause(ty::Clause::Trait(predicate)) => { + PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) => { match predicate.self_ty().kind() { ty::Param(param_ty) => Ok(( generics.type_param(param_ty, tcx), @@ -926,7 +944,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { // FIXME: supply non-"" `opt_via` when appropriate let first_borrow_desc; let mut err = match (gen_borrow_kind, issued_borrow.kind) { - (BorrowKind::Shared, BorrowKind::Mut { .. }) => { + ( + BorrowKind::Shared, + BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow }, + ) => { first_borrow_desc = "mutable "; self.cannot_reborrow_already_borrowed( span, @@ -940,7 +961,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { None, ) } - (BorrowKind::Mut { .. }, BorrowKind::Shared) => { + ( + BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow }, + BorrowKind::Shared, + ) => { first_borrow_desc = "immutable "; let mut err = self.cannot_reborrow_already_borrowed( span, @@ -962,7 +986,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { err } - (BorrowKind::Mut { .. }, BorrowKind::Mut { .. }) => { + ( + BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow }, + BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow }, + ) => { first_borrow_desc = "first "; let mut err = self.cannot_mutably_borrow_multiply( span, @@ -972,7 +999,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { &msg_borrow, None, ); - self.suggest_split_at_mut_if_applicable( + self.suggest_slice_method_if_applicable( &mut err, place, issued_borrow.borrowed_place, @@ -982,15 +1009,23 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { issued_borrow.borrowed_place, &issued_spans, ); + self.explain_iterator_advancement_in_for_loop_if_applicable( + &mut err, + span, + &issued_spans, + ); err } - (BorrowKind::Unique, BorrowKind::Unique) => { + ( + BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }, + BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }, + ) => { first_borrow_desc = "first "; self.cannot_uniquely_borrow_by_two_closures(span, &desc_place, issued_span, None) } - (BorrowKind::Mut { .. } | BorrowKind::Unique, BorrowKind::Shallow) => { + (BorrowKind::Mut { .. }, BorrowKind::Shallow) => { if let Some(immutable_section_description) = self.classify_immutable_section(issued_borrow.assigned_place) { @@ -1004,7 +1039,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { borrow_spans.var_subdiag( None, &mut err, - Some(BorrowKind::Unique), + Some(BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }), |kind, var_span| { use crate::session_diagnostics::CaptureVarCause::*; match kind { @@ -1038,7 +1073,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } - (BorrowKind::Unique, _) => { + (BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }, _) => { first_borrow_desc = "first "; self.cannot_uniquely_borrow_by_one_closure( span, @@ -1052,7 +1087,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ) } - (BorrowKind::Shared, BorrowKind::Unique) => { + (BorrowKind::Shared, BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }) => { first_borrow_desc = "first "; self.cannot_reborrow_already_uniquely_borrowed( span, @@ -1067,7 +1102,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ) } - (BorrowKind::Mut { .. }, BorrowKind::Unique) => { + (BorrowKind::Mut { .. }, BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }) => { first_borrow_desc = "first "; self.cannot_reborrow_already_uniquely_borrowed( span, @@ -1085,10 +1120,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { (BorrowKind::Shared, BorrowKind::Shared | BorrowKind::Shallow) | ( BorrowKind::Shallow, - BorrowKind::Mut { .. } - | BorrowKind::Unique - | BorrowKind::Shared - | BorrowKind::Shallow, + BorrowKind::Mut { .. } | BorrowKind::Shared | BorrowKind::Shallow, ) => unreachable!(), }; @@ -1140,8 +1172,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { if union_type_name != "" { err.note(format!( - "{} is a field of the union `{}`, so it overlaps the field {}", - msg_place, union_type_name, msg_borrow, + "{msg_place} is a field of the union `{union_type_name}`, so it overlaps the field {msg_borrow}", )); } @@ -1205,18 +1236,20 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { return; }; let inner_param_uses = find_all_local_uses::find(self.body, inner_param.local); - let Some((inner_call_loc, inner_call_term)) = inner_param_uses.into_iter().find_map(|loc| { - let Either::Right(term) = self.body.stmt_at(loc) else { - debug!("{:?} is a statement, so it can't be a call", loc); - return None; - }; - let TerminatorKind::Call { args, .. } = &term.kind else { - debug!("not a call: {:?}", term); - return None; - }; - debug!("checking call args for uses of inner_param: {:?}", args); - args.contains(&Operand::Move(inner_param)).then_some((loc, term)) - }) else { + let Some((inner_call_loc, inner_call_term)) = + inner_param_uses.into_iter().find_map(|loc| { + let Either::Right(term) = self.body.stmt_at(loc) else { + debug!("{:?} is a statement, so it can't be a call", loc); + return None; + }; + let TerminatorKind::Call { args, .. } = &term.kind else { + debug!("not a call: {:?}", term); + return None; + }; + debug!("checking call args for uses of inner_param: {:?}", args); + args.contains(&Operand::Move(inner_param)).then_some((loc, term)) + }) + else { debug!("no uses of inner_param found as a by-move call arg"); return; }; @@ -1252,7 +1285,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ); } - fn suggest_split_at_mut_if_applicable( + fn suggest_slice_method_if_applicable( &self, err: &mut Diagnostic, place: Place<'tcx>, @@ -1264,7 +1297,74 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { err.help( "consider using `.split_at_mut(position)` or similar method to obtain \ two mutable non-overlapping sub-slices", - ); + ) + .help("consider using `.swap(index_1, index_2)` to swap elements at the specified indices"); + } + } + + /// Suggest using `while let` for call `next` on an iterator in a for loop. + /// + /// For example: + /// ```ignore (illustrative) + /// + /// for x in iter { + /// ... + /// iter.next() + /// } + /// ``` + pub(crate) fn explain_iterator_advancement_in_for_loop_if_applicable( + &self, + err: &mut Diagnostic, + span: Span, + issued_spans: &UseSpans<'tcx>, + ) { + let issue_span = issued_spans.args_or_use(); + let tcx = self.infcx.tcx; + let hir = tcx.hir(); + + let Some(body_id) = hir.get(self.mir_hir_id()).body_id() else { return }; + let typeck_results = tcx.typeck(self.mir_def_id()); + + struct ExprFinder<'hir> { + issue_span: Span, + expr_span: Span, + body_expr: Option<&'hir hir::Expr<'hir>>, + loop_bind: Option, + } + impl<'hir> Visitor<'hir> for ExprFinder<'hir> { + fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) { + if let hir::ExprKind::Loop(hir::Block{ stmts: [stmt, ..], ..}, _, hir::LoopSource::ForLoop, _) = ex.kind && + let hir::StmtKind::Expr(hir::Expr{ kind: hir::ExprKind::Match(call, [_, bind, ..], _), ..}) = stmt.kind && + let hir::ExprKind::Call(path, _args) = call.kind && + let hir::ExprKind::Path(hir::QPath::LangItem(LangItem::IteratorNext, _, _, )) = path.kind && + let hir::PatKind::Struct(path, [field, ..], _) = bind.pat.kind && + let hir::QPath::LangItem(LangItem::OptionSome, _, _) = path && + let PatField { pat: hir::Pat{ kind: hir::PatKind::Binding(_, _, ident, ..), .. }, ..} = field && + self.issue_span.source_equal(call.span) { + self.loop_bind = Some(ident.name); + } + + if let hir::ExprKind::MethodCall(body_call, _recv, ..) = ex.kind && + body_call.ident.name == sym::next && ex.span.source_equal(self.expr_span) { + self.body_expr = Some(ex); + } + + hir::intravisit::walk_expr(self, ex); + } + } + let mut finder = + ExprFinder { expr_span: span, issue_span, loop_bind: None, body_expr: None }; + finder.visit_expr(hir.body(body_id).value); + + if let Some(loop_bind) = finder.loop_bind && + let Some(body_expr) = finder.body_expr && + let Some(def_id) = typeck_results.type_dependent_def_id(body_expr.hir_id) && + let Some(trait_did) = tcx.trait_of_item(def_id) && + tcx.is_diagnostic_item(sym::Iterator, trait_did) { + err.note(format!( + "a for loop advances the iterator for you, the result is stored in `{loop_bind}`." + )); + err.help("if you want to call `next` on a iterator within the loop, consider using `while let`."); } } @@ -1359,21 +1459,24 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } // Get closure's arguments - let ty::Closure(_, substs) = typeck_results.expr_ty(closure_expr).kind() else { /* hir::Closure can be a generator too */ return }; - let sig = substs.as_closure().sig(); + let ty::Closure(_, args) = typeck_results.expr_ty(closure_expr).kind() else { + /* hir::Closure can be a generator too */ + return; + }; + let sig = args.as_closure().sig(); let tupled_params = tcx.erase_late_bound_regions(sig.inputs().iter().next().unwrap().map_bound(|&b| b)); let ty::Tuple(params) = tupled_params.kind() else { return }; // Find the first argument with a matching type, get its name - let Some((_, this_name)) = params - .iter() - .zip(hir.body_param_names(closure.body)) - .find(|(param_ty, name)|{ + let Some((_, this_name)) = + params.iter().zip(hir.body_param_names(closure.body)).find(|(param_ty, name)| { // FIXME: also support deref for stuff like `Rc` arguments param_ty.peel_refs() == local_ty && name != &Ident::empty() }) - else { return }; + else { + return; + }; let spans; if let Some((_path_expr, qpath)) = finder.error_path @@ -1635,34 +1738,6 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { }) } - /// Reports StorageDeadOrDrop of `place` conflicts with `borrow`. - /// - /// Depending on the origin of the StorageDeadOrDrop, this may be - /// reported as either a drop or an illegal mutation of a borrowed value. - /// The latter is preferred when the this is a drop triggered by a - /// reassignment, as it's more user friendly to report a problem with the - /// explicit assignment than the implicit drop. - #[instrument(level = "debug", skip(self))] - pub(crate) fn report_storage_dead_or_drop_of_borrowed( - &mut self, - location: Location, - place_span: (Place<'tcx>, Span), - borrow: &BorrowData<'tcx>, - ) { - // It's sufficient to check the last desugaring as Replace is the last - // one to be applied. - if let Some(DesugaringKind::Replace) = place_span.1.desugaring_kind() { - self.report_illegal_mutation_of_borrowed(location, place_span, borrow) - } else { - self.report_borrowed_value_does_not_live_long_enough( - location, - borrow, - place_span, - Some(WriteKind::StorageDeadOrDrop), - ) - } - } - /// This means that some data referenced by `borrow` needs to live /// past the point where the StorageDeadOrDrop of `place` occurs. /// This is usually interpreted as meaning that `place` has too @@ -1748,18 +1823,19 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ( Some(name), BorrowExplanation::UsedLater(LaterUseKind::ClosureCapture, var_or_use_span, _), - ) => self.report_escaping_closure_capture( - borrow_spans, - borrow_span, - &RegionName { - name: self.synthesize_region_name(), - source: RegionNameSource::Static, - }, - ConstraintCategory::CallArgument(None), - var_or_use_span, - &format!("`{}`", name), - "block", - ), + ) if borrow_spans.for_generator() || borrow_spans.for_closure() => self + .report_escaping_closure_capture( + borrow_spans, + borrow_span, + &RegionName { + name: self.synthesize_region_name(), + source: RegionNameSource::Static, + }, + ConstraintCategory::CallArgument(None), + var_or_use_span, + &format!("`{name}`"), + "block", + ), ( Some(name), BorrowExplanation::MustBeValidFor { @@ -1772,14 +1848,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { span, .. }, - ) if borrow_spans.for_generator() | borrow_spans.for_closure() => self + ) if borrow_spans.for_generator() || borrow_spans.for_closure() => self .report_escaping_closure_capture( borrow_spans, borrow_span, region_name, category, span, - &format!("`{}`", name), + &format!("`{name}`"), "function", ), ( @@ -1853,14 +1929,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } - let mut err = self.path_does_not_live_long_enough(borrow_span, &format!("`{}`", name)); + let mut err = self.path_does_not_live_long_enough(borrow_span, &format!("`{name}`")); if let Some(annotation) = self.annotate_argument_and_return_for_borrow(borrow) { let region_name = annotation.emit(self, &mut err); err.span_label( borrow_span, - format!("`{}` would have to be valid for `{}`...", name, region_name), + format!("`{name}` would have to be valid for `{region_name}`..."), ); err.span_label( @@ -1871,7 +1947,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { self.infcx .tcx .opt_item_name(self.mir_def_id().to_def_id()) - .map(|name| format!("function `{}`", name)) + .map(|name| format!("function `{name}`")) .unwrap_or_else(|| { match &self.infcx.tcx.def_kind(self.mir_def_id()) { DefKind::Closure => "enclosing closure", @@ -1906,7 +1982,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } } else { err.span_label(borrow_span, "borrowed value does not live long enough"); - err.span_label(drop_span, format!("`{}` dropped here while still borrowed", name)); + err.span_label(drop_span, format!("`{name}` dropped here while still borrowed")); borrow_spans.args_subdiag(&mut err, |args_span| { crate::session_diagnostics::CaptureArgLabel::Capture { @@ -1950,22 +2026,17 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { let mut err = self.cannot_borrow_across_destructor(borrow_span); let what_was_dropped = match self.describe_place(place.as_ref()) { - Some(name) => format!("`{}`", name), + Some(name) => format!("`{name}`"), None => String::from("temporary value"), }; let label = match self.describe_place(borrow.borrowed_place.as_ref()) { Some(borrowed) => format!( - "here, drop of {D} needs exclusive access to `{B}`, \ - because the type `{T}` implements the `Drop` trait", - D = what_was_dropped, - T = dropped_ty, - B = borrowed + "here, drop of {what_was_dropped} needs exclusive access to `{borrowed}`, \ + because the type `{dropped_ty}` implements the `Drop` trait" ), None => format!( - "here is drop of {D}; whose type `{T}` implements the `Drop` trait", - D = what_was_dropped, - T = dropped_ty + "here is drop of {what_was_dropped}; whose type `{dropped_ty}` implements the `Drop` trait" ), }; err.span_label(drop_span, label); @@ -2072,13 +2143,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { self.current -= 1; } fn visit_expr(&mut self, expr: &hir::Expr<'tcx>) { - if self.span == expr.span { + if self.span == expr.span.source_callsite() { self.found = self.current; } walk_expr(self, expr); } } let source_info = self.body.source_info(location); + let proper_span = proper_span.source_callsite(); if let Some(scope) = self.body.source_scopes.get(source_info.scope) && let ClearCrossCrate::Set(scope_data) = &scope.local_data && let Some(node) = self.infcx.tcx.hir().find(scope_data.lint_root) @@ -2177,10 +2249,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } else { "local data " }; - ( - format!("{}`{}`", local_kind, place_desc), - format!("`{}` is borrowed here", place_desc), - ) + (format!("{local_kind}`{place_desc}`"), format!("`{place_desc}` is borrowed here")) } else { let root_place = self.prefixes(borrow.borrowed_place.as_ref(), PrefixSet::All).last().unwrap(); @@ -2282,9 +2351,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { err.span_suggestion_verbose( sugg_span, format!( - "to force the {} to take ownership of {} (and any \ - other referenced variables), use the `move` keyword", - kind, captured_var + "to force the {kind} to take ownership of {captured_var} (and any \ + other referenced variables), use the `move` keyword" ), suggestion, Applicability::MachineApplicable, @@ -2292,7 +2360,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { match category { ConstraintCategory::Return(_) | ConstraintCategory::OpaqueType => { - let msg = format!("{} is returned here", kind); + let msg = format!("{kind} is returned here"); err.span_note(constraint_span, msg); } ConstraintCategory::CallArgument(_) => { @@ -2334,21 +2402,18 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { err.span_label( upvar_span, - format!("`{}` declared here, outside of the {} body", upvar_name, escapes_from), + format!("`{upvar_name}` declared here, outside of the {escapes_from} body"), ); - err.span_label(borrow_span, format!("borrow is only valid in the {} body", escapes_from)); + err.span_label(borrow_span, format!("borrow is only valid in the {escapes_from} body")); if let Some(name) = name { err.span_label( escape_span, - format!("reference to `{}` escapes the {} body here", name, escapes_from), + format!("reference to `{name}` escapes the {escapes_from} body here"), ); } else { - err.span_label( - escape_span, - format!("reference escapes the {} body here", escapes_from), - ); + err.span_label(escape_span, format!("reference escapes the {escapes_from} body here")); } err @@ -2607,8 +2672,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { fn explain_deref_coercion(&mut self, loan: &BorrowData<'tcx>, err: &mut Diagnostic) { let tcx = self.infcx.tcx; if let ( - Some(Terminator { kind: TerminatorKind::Call { from_hir_call: false, .. }, .. }), - Some((method_did, method_substs)), + Some(Terminator { + kind: TerminatorKind::Call { call_source: CallSource::OverloadedOperator, .. }, + .. + }), + Some((method_did, method_args)), ) = ( &self.body[loan.reserve_location.block].terminator, rustc_middle::util::find_self_call( @@ -2621,15 +2689,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { if tcx.is_diagnostic_item(sym::deref_method, method_did) { let deref_target = tcx.get_diagnostic_item(sym::deref_target).and_then(|deref_target| { - Instance::resolve(tcx, self.param_env, deref_target, method_substs) + Instance::resolve(tcx, self.param_env, deref_target, method_args) .transpose() }); if let Some(Ok(instance)) = deref_target { let deref_target_ty = instance.ty(tcx, self.param_env); - err.note(format!( - "borrow occurs due to deref coercion to `{}`", - deref_target_ty - )); + err.note(format!("borrow occurs due to deref coercion to `{deref_target_ty}`")); err.span_note(tcx.def_span(instance.def_id()), "deref defined here"); } } @@ -2685,7 +2750,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { "cannot assign twice to immutable variable" }; if span != assigned_span && !from_arg { - err.span_label(assigned_span, format!("first assignment to {}", place_description)); + err.span_label(assigned_span, format!("first assignment to {place_description}")); } if let Some(decl) = local_decl && let Some(name) = local_name @@ -2694,7 +2759,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { err.span_suggestion( decl.source_info.span, "consider making this binding mutable", - format!("mut {}", name), + format!("mut {name}"), Applicability::MachineApplicable, ); } @@ -2788,11 +2853,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { if is_closure { None } else { - let ty = self.infcx.tcx.type_of(self.mir_def_id()).subst_identity(); + let ty = self.infcx.tcx.type_of(self.mir_def_id()).instantiate_identity(); match ty.kind() { ty::FnDef(_, _) | ty::FnPtr(_) => self.annotate_fn_sig( self.mir_def_id(), - self.infcx.tcx.fn_sig(self.mir_def_id()).subst_identity(), + self.infcx.tcx.fn_sig(self.mir_def_id()).instantiate_identity(), ), _ => None, } @@ -2834,13 +2899,15 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ); // Check if our `target` was captured by a closure. if let Rvalue::Aggregate( - box AggregateKind::Closure(def_id, substs), + box AggregateKind::Closure(def_id, args), operands, ) = rvalue { let def_id = def_id.expect_local(); for operand in operands { - let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand else { + let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = + operand + else { continue; }; debug!( @@ -2849,7 +2916,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ); // Find the local from the operand. - let Some(assigned_from_local) = assigned_from.local_or_deref_local() else { + let Some(assigned_from_local) = + assigned_from.local_or_deref_local() + else { continue; }; @@ -2861,7 +2930,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { // into a place then we should annotate the closure in // case it ends up being assigned into the return place. annotated_closure = - self.annotate_fn_sig(def_id, substs.as_closure().sig()); + self.annotate_fn_sig(def_id, args.as_closure().sig()); debug!( "annotate_argument_and_return_for_borrow: \ annotated_closure={:?} assigned_from_local={:?} \ @@ -2902,7 +2971,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { ); // Find the local from the rvalue. - let Some(assigned_from_local) = assigned_from.local_or_deref_local() else { continue }; + let Some(assigned_from_local) = assigned_from.local_or_deref_local() else { + continue; + }; debug!( "annotate_argument_and_return_for_borrow: \ assigned_from_local={:?}", @@ -2950,7 +3021,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { assigned_to, args ); for operand in args { - let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand else { + let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand + else { continue; }; debug!( @@ -3148,7 +3220,7 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { return_span, } => { let argument_ty_name = cx.get_name_for_ty(argument_ty, 0); - diag.span_label(argument_span, format!("has type `{}`", argument_ty_name)); + diag.span_label(argument_span, format!("has type `{argument_ty_name}`")); let return_ty_name = cx.get_name_for_ty(return_ty, 0); let types_equal = return_ty_name == argument_ty_name; @@ -3175,15 +3247,14 @@ impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { // Region of return type and arguments checked to be the same earlier. let region_name = cx.get_region_name_for_ty(*return_ty, 0); for (_, argument_span) in arguments { - diag.span_label(*argument_span, format!("has lifetime `{}`", region_name)); + diag.span_label(*argument_span, format!("has lifetime `{region_name}`")); } - diag.span_label(*return_span, format!("also has lifetime `{}`", region_name,)); + diag.span_label(*return_span, format!("also has lifetime `{region_name}`",)); diag.help(format!( - "use data from the highlighted arguments which match the `{}` lifetime of \ + "use data from the highlighted arguments which match the `{region_name}` lifetime of \ the return type", - region_name, )); region_name diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index 1d430a93a876d..c66a24473562b 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -6,10 +6,10 @@ use rustc_hir::intravisit::Visitor; use rustc_index::IndexSlice; use rustc_infer::infer::NllRegionVariableOrigin; use rustc_middle::mir::{ - Body, CastKind, ConstraintCategory, FakeReadCause, Local, LocalInfo, Location, Operand, Place, - Rvalue, Statement, StatementKind, TerminatorKind, + Body, CallSource, CastKind, ConstraintCategory, FakeReadCause, Local, LocalInfo, Location, + Operand, Place, Rvalue, Statement, StatementKind, TerminatorKind, }; -use rustc_middle::ty::adjustment::PointerCast; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::{self, RegionVid, TyCtxt}; use rustc_span::symbol::{kw, Symbol}; use rustc_span::{sym, DesugaringKind, Span}; @@ -79,7 +79,7 @@ impl<'tcx> BorrowExplanation<'tcx> { | hir::ExprKind::Unary(hir::UnOp::Deref, inner) | hir::ExprKind::Field(inner, _) | hir::ExprKind::MethodCall(_, inner, _, _) - | hir::ExprKind::Index(inner, _) = &expr.kind + | hir::ExprKind::Index(inner, _, _) = &expr.kind { expr = inner; } @@ -168,17 +168,17 @@ impl<'tcx> BorrowExplanation<'tcx> { let local_decl = &body.local_decls[dropped_local]; let mut ty = local_decl.ty; if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) { - if let ty::Adt(adt, substs) = local_decl.ty.kind() { + if let ty::Adt(adt, args) = local_decl.ty.kind() { if tcx.is_diagnostic_item(sym::Option, adt.did()) { // in for loop desugaring, only look at the `Some(..)` inner type - ty = substs.type_at(0); + ty = args.type_at(0); } } } let (dtor_desc, type_desc) = match ty.kind() { // If type is an ADT that implements Drop, then // simplify output by reporting just the ADT name. - ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => { + ty::Adt(adt, _args) if adt.has_dtor(tcx) && !adt.is_box() => { ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did()))) } @@ -494,7 +494,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } else if self.was_captured_by_trait_object(borrow) { LaterUseKind::TraitCapture } else if location.statement_index == block.statements.len() { - if let TerminatorKind::Call { func, from_hir_call: true, .. } = + if let TerminatorKind::Call { func, call_source: CallSource::Normal, .. } = &block.terminator().kind { // Just point to the function, to reduce the chance of overlapping spans. @@ -584,7 +584,11 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { }, // If we see an unsized cast, then if it is our data we should check // whether it is being cast to a trait object. - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), operand, ty) => { + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::Unsize), + operand, + ty, + ) => { match operand { Operand::Copy(place) | Operand::Move(place) => { if let Some(from) = place.as_local() { diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 20370e4c6ac3c..099e07e88414a 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -13,8 +13,9 @@ use rustc_index::IndexSlice; use rustc_infer::infer::LateBoundRegionConversionTime; use rustc_middle::mir::tcx::PlaceTy; use rustc_middle::mir::{ - AggregateKind, Constant, FakeReadCause, Local, LocalInfo, LocalKind, Location, Operand, Place, - PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + AggregateKind, CallSource, Constant, FakeReadCause, Local, LocalInfo, LocalKind, Location, + Operand, Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, }; use rustc_middle::ty::print::Print; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; @@ -414,7 +415,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { if !is_terminator { continue; } else if let Some(Terminator { - kind: TerminatorKind::Call { func, from_hir_call: false, .. }, + kind: + TerminatorKind::Call { + func, + call_source: CallSource::OverloadedOperator, + .. + }, .. }) = &bbd.terminator { @@ -622,8 +628,7 @@ impl UseSpans<'_> { err.subdiagnostic(match kind { Some(kd) => match kd { rustc_middle::mir::BorrowKind::Shared - | rustc_middle::mir::BorrowKind::Shallow - | rustc_middle::mir::BorrowKind::Unique => { + | rustc_middle::mir::BorrowKind::Shallow => { CaptureVarKind::Immut { kind_span: capture_kind_span } } @@ -727,18 +732,18 @@ impl<'tcx> BorrowedContentSource<'tcx> { fn from_call(func: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option { match *func.kind() { - ty::FnDef(def_id, substs) => { + ty::FnDef(def_id, args) => { let trait_id = tcx.trait_of_item(def_id)?; let lang_items = tcx.lang_items(); if Some(trait_id) == lang_items.deref_trait() || Some(trait_id) == lang_items.deref_mut_trait() { - Some(BorrowedContentSource::OverloadedDeref(substs.type_at(0))) + Some(BorrowedContentSource::OverloadedDeref(args.type_at(0))) } else if Some(trait_id) == lang_items.index_trait() || Some(trait_id) == lang_items.index_mut_trait() { - Some(BorrowedContentSource::OverloadedIndex(substs.type_at(0))) + Some(BorrowedContentSource::OverloadedIndex(args.type_at(0))) } else { None } @@ -839,17 +844,15 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { debug!("move_spans: target_temp = {:?}", target_temp); if let Some(Terminator { - kind: TerminatorKind::Call { fn_span, from_hir_call, .. }, .. + kind: TerminatorKind::Call { fn_span, call_source, .. }, .. }) = &self.body[location.block].terminator { - let Some((method_did, method_substs)) = - rustc_middle::util::find_self_call( - self.infcx.tcx, - &self.body, - target_temp, - location.block, - ) - else { + let Some((method_did, method_args)) = rustc_middle::util::find_self_call( + self.infcx.tcx, + &self.body, + target_temp, + location.block, + ) else { return normal_ret; }; @@ -857,9 +860,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { self.infcx.tcx, self.param_env, method_did, - method_substs, + method_args, *fn_span, - *from_hir_call, + call_source.from_hir_call(), Some(self.infcx.tcx.fn_arg_names(method_did)[0]), ); @@ -1036,7 +1039,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { }); } } - CallKind::Normal { self_arg, desugaring, method_did, method_substs } => { + CallKind::Normal { self_arg, desugaring, method_did, method_args } => { let self_arg = self_arg.unwrap(); let tcx = self.infcx.tcx; if let Some((CallDesugaringKind::ForLoopIntoIter, _)) = desugaring { @@ -1045,7 +1048,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { Some(def_id) => type_known_to_meet_bound_modulo_regions( &self.infcx, self.param_env, - tcx.mk_imm_ref(tcx.lifetimes.re_erased, ty), + Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, ty), def_id, ), _ => false, @@ -1103,20 +1106,20 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { // Erase and shadow everything that could be passed to the new infcx. let ty = moved_place.ty(self.body, tcx).ty; - if let ty::Adt(def, substs) = ty.kind() + if let ty::Adt(def, args) = ty.kind() && Some(def.did()) == tcx.lang_items().pin_type() - && let ty::Ref(_, _, hir::Mutability::Mut) = substs.type_at(0).kind() + && let ty::Ref(_, _, hir::Mutability::Mut) = args.type_at(0).kind() && let self_ty = self.infcx.instantiate_binder_with_fresh_vars( fn_call_span, LateBoundRegionConversionTime::FnCall, - tcx.fn_sig(method_did).subst(tcx, method_substs).input(0), + tcx.fn_sig(method_did).instantiate(tcx, method_args).input(0), ) && self.infcx.can_eq(self.param_env, ty, self_ty) { err.eager_subdiagnostic( &self.infcx.tcx.sess.parse_sess.span_diagnostic, CaptureReasonSuggest::FreshReborrow { - span: fn_call_span.shrink_to_lo(), + span: move_span.shrink_to_hi(), }); } if let Some(clone_trait) = tcx.lang_items().clone_trait() @@ -1130,10 +1133,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { && self.infcx.predicate_must_hold_modulo_regions(&o) { err.span_suggestion_verbose( - fn_call_span.shrink_to_lo(), + move_span.shrink_to_hi(), "you can `clone` the value and consume it, but this might not be \ your desired behavior", - "clone().".to_string(), + ".clone()".to_string(), Applicability::MaybeIncorrect, ); } @@ -1141,6 +1144,12 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { // Avoid pointing to the same function in multiple different // error messages. if span != DUMMY_SP && self.fn_self_span_reported.insert(self_arg.span) { + self.explain_iterator_advancement_in_for_loop_if_applicable( + err, + span, + &move_spans, + ); + let func = tcx.def_path_str(method_did); err.subdiagnostic(CaptureReasonNote::FuncTakeSelf { func, @@ -1152,7 +1161,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { let parent_self_ty = matches!(tcx.def_kind(parent_did), rustc_hir::def::DefKind::Impl { .. }) .then_some(parent_did) - .and_then(|did| match tcx.type_of(did).subst_identity().kind() { + .and_then(|did| match tcx.type_of(did).instantiate_identity().kind() { ty::Adt(def, ..) => Some(def.did()), _ => None, }); diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 8b77477a31a33..e05c04e118808 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -184,7 +184,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { } // Error with the pattern LookupResult::Exact(_) => { - let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else { + let LookupResult::Parent(Some(mpi)) = + self.move_data.rev_lookup.find(move_from.as_ref()) + else { // move_from should be a projection from match_place. unreachable!("Probably not unreachable..."); }; @@ -322,10 +324,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { ty::Array(..) | ty::Slice(..) => { self.cannot_move_out_of_interior_noncopy(span, ty, None) } - ty::Closure(def_id, closure_substs) + ty::Closure(def_id, closure_args) if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() => { - let closure_kind_ty = closure_substs.as_closure().kind_ty(); + let closure_kind_ty = closure_args.as_closure().kind_ty(); let closure_kind = match closure_kind_ty.to_opt_closure_kind() { Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind, Some(ty::ClosureKind::FnOnce) => { @@ -494,8 +496,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span, .. })) = *bind_to.local_info() { - let Ok(pat_snippet) = - self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) else { continue; }; + let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) + else { + continue; + }; let Some(stripped) = pat_snippet.strip_prefix('&') else { suggestions.push(( bind_to.source_info.span.shrink_to_lo(), diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index 4bde372c847dd..d62541daf0731 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -2,7 +2,6 @@ use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed use rustc_hir as hir; use rustc_hir::intravisit::Visitor; use rustc_hir::Node; -use rustc_middle::hir::map::Map; use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{ @@ -123,13 +122,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { item_msg = access_place_desc; debug_assert!(self.body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty.is_ref()); debug_assert!(is_closure_or_generator( - Place::ty_from( - the_place_err.local, - the_place_err.projection, - self.body, - self.infcx.tcx - ) - .ty + the_place_err.ty(self.body, self.infcx.tcx).ty )); reason = if self.is_upvar_field_projection(access_place.as_ref()).is_some() { @@ -234,7 +227,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { borrow_spans.var_subdiag( None, &mut err, - Some(mir::BorrowKind::Mut { allow_two_phase_borrow: false }), + Some(mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default }), |_kind, var_span| { let place = self.describe_any_place(access_place.as_ref()); crate::session_diagnostics::CaptureVarCause::MutableBorrowUsePlaceClosure { @@ -300,7 +293,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { _, mir::Rvalue::Ref( _, - mir::BorrowKind::Mut { allow_two_phase_borrow: false }, + mir::BorrowKind::Mut { kind: mir::MutBorrowKind::Default }, _, ), )), @@ -416,12 +409,28 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { _, ) = pat.kind { - err.span_suggestion( - upvar_ident.span, - "consider changing this to be mutable", - format!("mut {}", upvar_ident.name), - Applicability::MachineApplicable, - ); + if upvar_ident.name == kw::SelfLower { + for (_, node) in self.infcx.tcx.hir().parent_iter(upvar_hir_id) { + if let Some(fn_decl) = node.fn_decl() { + if !matches!(fn_decl.implicit_self, hir::ImplicitSelfKind::ImmRef | hir::ImplicitSelfKind::MutRef) { + err.span_suggestion( + upvar_ident.span, + "consider changing this to be mutable", + format!("mut {}", upvar_ident.name), + Applicability::MachineApplicable, + ); + break; + } + } + } + } else { + err.span_suggestion( + upvar_ident.span, + "consider changing this to be mutable", + format!("mut {}", upvar_ident.name), + Applicability::MachineApplicable, + ); + } } let tcx = self.infcx.tcx; @@ -558,7 +567,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { } }; if let hir::ExprKind::Assign(place, rv, _sp) = expr.kind - && let hir::ExprKind::Index(val, index) = place.kind + && let hir::ExprKind::Index(val, index, _) = place.kind && (expr.span == self.assign_span || place.span == self.assign_span) { // val[index] = rv; @@ -611,7 +620,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { ); self.suggested = true; } else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind - && let hir::ExprKind::Index(val, index) = receiver.kind + && let hir::ExprKind::Index(val, index, _) = receiver.kind && expr.span == self.assign_span { // val[index].path(args..); @@ -636,18 +645,11 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { } let hir_map = self.infcx.tcx.hir(); let def_id = self.body.source.def_id(); - let hir_id = hir_map.local_def_id_to_hir_id(def_id.as_local().unwrap()); - let node = hir_map.find(hir_id); - let Some(hir::Node::Item(item)) = node else { return; }; - let hir::ItemKind::Fn(.., body_id) = item.kind else { return; }; + let Some(local_def_id) = def_id.as_local() else { return }; + let Some(body_id) = hir_map.maybe_body_owned_by(local_def_id) else { return }; let body = self.infcx.tcx.hir().body(body_id); - let mut assign_span = span; - // Drop desugaring is done at MIR build so it's not in the HIR - if let Some(DesugaringKind::Replace) = span.desugaring_kind() { - assign_span.remove_mark(); - } - let mut v = V { assign_span, err, ty, suggested: false }; + let mut v = V { assign_span: span, err, ty, suggested: false }; v.visit_body(body); if !v.suggested { err.help(format!( @@ -781,23 +783,12 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { // In the future, attempt in all path but initially for RHS of for_loop fn suggest_similar_mut_method_for_for_loop(&self, err: &mut Diagnostic) { use hir::{ - BodyId, Expr, + Expr, ExprKind::{Block, Call, DropTemps, Match, MethodCall}, - HirId, ImplItem, ImplItemKind, Item, ItemKind, }; - fn maybe_body_id_of_fn(hir_map: Map<'_>, id: HirId) -> Option { - match hir_map.find(id) { - Some(Node::Item(Item { kind: ItemKind::Fn(_, _, body_id), .. })) - | Some(Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })) => { - Some(*body_id) - } - _ => None, - } - } let hir_map = self.infcx.tcx.hir(); - let mir_body_hir_id = self.mir_hir_id(); - if let Some(fn_body_id) = maybe_body_id_of_fn(hir_map, mir_body_hir_id) { + if let Some(body_id) = hir_map.maybe_body_owned_by(self.mir_def_id()) { if let Block( hir::Block { expr: @@ -831,7 +822,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { .. }, _, - ) = hir_map.body(fn_body_id).value.kind + ) = hir_map.body(body_id).value.kind { let opt_suggestions = self .infcx @@ -1093,46 +1084,45 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { } let hir_map = self.infcx.tcx.hir(); let def_id = self.body.source.def_id(); - let hir_id = hir_map.local_def_id_to_hir_id(def_id.expect_local()); - let node = hir_map.find(hir_id); - let hir_id = if let Some(hir::Node::Item(item)) = node - && let hir::ItemKind::Fn(.., body_id) = item.kind - { - let body = hir_map.body(body_id); - let mut v = BindingFinder { - span: err_label_span, - hir_id: None, + let hir_id = if let Some(local_def_id) = def_id.as_local() && + let Some(body_id) = hir_map.maybe_body_owned_by(local_def_id) + { + let body = hir_map.body(body_id); + let mut v = BindingFinder { + span: err_label_span, + hir_id: None, + }; + v.visit_body(body); + v.hir_id + } else { + None }; - v.visit_body(body); - v.hir_id - } else { - None - }; + if let Some(hir_id) = hir_id && let Some(hir::Node::Local(local)) = hir_map.find(hir_id) - { - let (changing, span, sugg) = match local.ty { - Some(ty) => ("changing", ty.span, message), - None => ( - "specifying", - local.pat.span.shrink_to_hi(), - format!(": {message}"), - ), - }; - err.span_suggestion_verbose( - span, - format!("consider {changing} this binding's type"), - sugg, - Applicability::HasPlaceholders, - ); - } else { - err.span_label( - err_label_span, - format!( - "consider changing this binding's type to be: `{message}`" - ), - ); - } + { + let (changing, span, sugg) = match local.ty { + Some(ty) => ("changing", ty.span, message), + None => ( + "specifying", + local.pat.span.shrink_to_hi(), + format!(": {message}"), + ), + }; + err.span_suggestion_verbose( + span, + format!("consider {changing} this binding's type"), + sugg, + Applicability::HasPlaceholders, + ); + } else { + err.span_label( + err_label_span, + format!( + "consider changing this binding's type to be: `{message}`" + ), + ); + } } None => {} } diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index 8ec872e205792..2ea399789b9a3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -22,7 +22,7 @@ use rustc_infer::infer::{ }; use rustc_middle::hir::place::PlaceBase; use rustc_middle::mir::{ConstraintCategory, ReturnConstraint}; -use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::TypeVisitor; use rustc_middle::ty::{self, RegionVid, Ty}; use rustc_middle::ty::{Region, TyCtxt}; @@ -183,9 +183,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { fn is_closure_fn_mut(&self, fr: RegionVid) -> bool { if let Some(ty::ReFree(free_region)) = self.to_error_region(fr).as_deref() && let ty::BoundRegionKind::BrEnv = free_region.bound_region - && let DefiningTy::Closure(_, substs) = self.regioncx.universal_regions().defining_ty + && let DefiningTy::Closure(_, args) = self.regioncx.universal_regions().defining_ty { - return substs.as_closure().kind() == ty::ClosureKind::FnMut; + return args.as_closure().kind() == ty::ClosureKind::FnMut; } false @@ -224,12 +224,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { let mut hrtb_bounds = vec![]; gat_id_and_generics.iter().flatten().for_each(|(gat_hir_id, generics)| { for pred in generics.predicates { - let BoundPredicate( - WhereBoundPredicate { - bound_generic_params, - bounds, - .. - }) = pred else { continue; }; + let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }) = pred + else { + continue; + }; if bound_generic_params .iter() .rfind(|bgp| hir.local_def_id_to_hir_id(bgp.def_id) == *gat_hir_id) @@ -504,12 +502,12 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { .to_string(), ) } - ty::Adt(adt, substs) => { - let generic_arg = substs[param_index as usize]; - let identity_substs = - InternalSubsts::identity_for_item(self.infcx.tcx, adt.did()); - let base_ty = self.infcx.tcx.mk_adt(*adt, identity_substs); - let base_generic_arg = identity_substs[param_index as usize]; + ty::Adt(adt, args) => { + let generic_arg = args[param_index as usize]; + let identity_args = + GenericArgs::identity_for_item(self.infcx.tcx, adt.did()); + let base_ty = Ty::new_adt(self.infcx.tcx, *adt, identity_args); + let base_generic_arg = identity_args[param_index as usize]; let adt_desc = adt.descr(); let desc = format!( @@ -522,12 +520,11 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { } ty::FnDef(def_id, _) => { let name = self.infcx.tcx.item_name(*def_id); - let identity_substs = - InternalSubsts::identity_for_item(self.infcx.tcx, *def_id); + let identity_args = GenericArgs::identity_for_item(self.infcx.tcx, *def_id); let desc = format!("a function pointer to `{name}`"); let note = format!( "the function `{name}` is invariant over the parameter `{}`", - identity_substs[param_index as usize] + identity_args[param_index as usize] ); (desc, note) } @@ -575,7 +572,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { let mut output_ty = self.regioncx.universal_regions().unnormalized_output_ty; if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) = *output_ty.kind() { - output_ty = self.infcx.tcx.type_of(def_id).subst_identity() + output_ty = self.infcx.tcx.type_of(def_id).instantiate_identity() }; debug!("report_fnmut_error: output_ty={:?}", output_ty); @@ -813,7 +810,9 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { return; } let suitable_region = self.infcx.tcx.is_suitable_region(f); - let Some(suitable_region) = suitable_region else { return; }; + let Some(suitable_region) = suitable_region else { + return; + }; let fn_returns = self.infcx.tcx.return_type_impl_or_dyn_traits(suitable_region.def_id); @@ -848,7 +847,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { let Some((alias_tys, alias_span, lt_addition_span)) = self .infcx .tcx - .return_type_impl_or_dyn_traits_with_type_alias(suitable_region.def_id) else { return; }; + .return_type_impl_or_dyn_traits_with_type_alias(suitable_region.def_id) + else { + return; + }; // in case the return type of the method is a type alias let mut spans_suggs: Vec<_> = Vec::new(); @@ -896,14 +898,14 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { let tcx = self.infcx.tcx; let instance = if let ConstraintCategory::CallArgument(Some(func_ty)) = category { - let (fn_did, substs) = match func_ty.kind() { - ty::FnDef(fn_did, substs) => (fn_did, substs), + let (fn_did, args) = match func_ty.kind() { + ty::FnDef(fn_did, args) => (fn_did, args), _ => return, }; - debug!(?fn_did, ?substs); + debug!(?fn_did, ?args); // Only suggest this on function calls, not closures - let ty = tcx.type_of(fn_did).subst_identity(); + let ty = tcx.type_of(fn_did).instantiate_identity(); debug!("ty: {:?}, ty.kind: {:?}", ty, ty.kind()); if let ty::Closure(_, _) = ty.kind() { return; @@ -913,7 +915,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { tcx, self.param_env, *fn_did, - self.infcx.resolve_vars_if_possible(substs), + self.infcx.resolve_vars_if_possible(args), ) { instance } else { @@ -932,8 +934,13 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { let mut visitor = TraitObjectVisitor(FxIndexSet::default()); visitor.visit_ty(param.param_ty); - let Some((ident, self_ty)) = - NiceRegionError::get_impl_ident_and_self_ty_from_trait(tcx, instance.def_id(), &visitor.0) else { return; }; + let Some((ident, self_ty)) = NiceRegionError::get_impl_ident_and_self_ty_from_trait( + tcx, + instance.def_id(), + &visitor.0, + ) else { + return; + }; self.suggest_constrain_dyn_trait_in_impl(diag, &visitor.0, ident, self_ty); } @@ -981,23 +988,25 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { sup: RegionVid, ) { let (Some(sub), Some(sup)) = (self.to_error_region(sub), self.to_error_region(sup)) else { - return + return; }; let Some((ty_sub, _)) = self .infcx .tcx .is_suitable_region(sub) - .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sub, &anon_reg.boundregion)) else { - return + .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sub, &anon_reg.boundregion)) + else { + return; }; let Some((ty_sup, _)) = self .infcx .tcx .is_suitable_region(sup) - .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sup, &anon_reg.boundregion)) else { - return + .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sup, &anon_reg.boundregion)) + else { + return; }; suggest_adding_lifetime_params(self.infcx.tcx, sub, ty_sup, ty_sub, diag); diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index f38e1605fa5ca..337af89b21fe5 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -5,8 +5,8 @@ use rustc_errors::Diagnostic; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_middle::ty::print::RegionHighlightMode; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, RegionVid, Ty}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; @@ -321,18 +321,18 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { ty::BoundRegionKind::BrEnv => { let def_ty = self.regioncx.universal_regions().defining_ty; - let DefiningTy::Closure(_, substs) = def_ty else { + let DefiningTy::Closure(_, args) = def_ty else { // Can't have BrEnv in functions, constants or generators. bug!("BrEnv outside of closure."); }; - let hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }) - = tcx.hir().expect_expr(self.mir_hir_id()).kind + let hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }) = + tcx.hir().expect_expr(self.mir_hir_id()).kind else { bug!("Closure is not defined by a closure expr"); }; let region_name = self.synthesize_region_name(); - let closure_kind_ty = substs.as_closure().kind_ty(); + let closure_kind_ty = args.as_closure().kind_ty(); let note = match closure_kind_ty.to_opt_closure_kind() { Some(ty::ClosureKind::Fn) => { "closure implements `Fn`, so references to captured variables \ @@ -510,20 +510,17 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { } // Match up something like `Foo<'1>` - ( - ty::Adt(_adt_def, substs), - hir::TyKind::Path(hir::QPath::Resolved(None, path)), - ) => { + (ty::Adt(_adt_def, args), hir::TyKind::Path(hir::QPath::Resolved(None, path))) => { match path.res { // Type parameters of the type alias have no reason to // be the same as those of the ADT. // FIXME: We should be able to do something similar to // match_adt_and_segment in this case. - Res::Def(DefKind::TyAlias, _) => (), + Res::Def(DefKind::TyAlias { .. }, _) => (), _ => { if let Some(last_segment) = path.segments.last() { if let Some(highlight) = self.match_adt_and_segment( - substs, + args, needle_fr, last_segment, search_stack, @@ -560,22 +557,22 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { None } - /// We've found an enum/struct/union type with the substitutions - /// `substs` and -- in the HIR -- a path type with the final + /// We've found an enum/struct/union type with the generic args + /// `args` and -- in the HIR -- a path type with the final /// segment `last_segment`. Try to find a `'_` to highlight in /// the generic args (or, if not, to produce new zipped pairs of /// types+hir to search through). fn match_adt_and_segment<'hir>( &self, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, needle_fr: RegionVid, last_segment: &'hir hir::PathSegment<'hir>, search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty<'hir>)>, ) -> Option { // Did the user give explicit arguments? (e.g., `Foo<..>`) - let args = last_segment.args.as_ref()?; + let explicit_args = last_segment.args.as_ref()?; let lifetime = - self.try_match_adt_and_generic_args(substs, needle_fr, args, search_stack)?; + self.try_match_adt_and_generic_args(args, needle_fr, explicit_args, search_stack)?; if lifetime.is_anonymous() { None } else { @@ -583,19 +580,19 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { } } - /// We've found an enum/struct/union type with the substitutions - /// `substs` and -- in the HIR -- a path with the generic - /// arguments `args`. If `needle_fr` appears in the args, return + /// We've found an enum/struct/union type with the generic args + /// `args` and -- in the HIR -- a path with the generic + /// arguments `hir_args`. If `needle_fr` appears in the args, return /// the `hir::Lifetime` that corresponds to it. If not, push onto /// `search_stack` the types+hir to search through. fn try_match_adt_and_generic_args<'hir>( &self, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, needle_fr: RegionVid, - args: &'hir hir::GenericArgs<'hir>, + hir_args: &'hir hir::GenericArgs<'hir>, search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty<'hir>)>, ) -> Option<&'hir hir::Lifetime> { - for (kind, hir_arg) in iter::zip(substs, args.args) { + for (kind, hir_arg) in iter::zip(args, hir_args.args) { match (kind.unpack(), hir_arg) { (GenericArgKind::Lifetime(r), hir::GenericArg::Lifetime(lt)) => { if r.as_var() == needle_fr { @@ -849,9 +846,10 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { return None; }; - let found = tcx.any_free_region_meets(&tcx.type_of(region_parent).subst_identity(), |r| { - *r == ty::ReEarlyBound(region) - }); + let found = tcx + .any_free_region_meets(&tcx.type_of(region_parent).instantiate_identity(), |r| { + *r == ty::ReEarlyBound(region) + }); Some(RegionName { name: self.synthesize_region_name(), @@ -888,6 +886,7 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { .universal_regions() .defining_ty .upvar_tys() + .iter() .position(|ty| self.any_param_predicate_mentions(&predicates, ty, region)) { let (upvar_name, upvar_span) = self.regioncx.get_upvar_name_and_span_for_region( @@ -928,7 +927,7 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { fn any_param_predicate_mentions( &self, - predicates: &[ty::Predicate<'tcx>], + clauses: &[ty::Clause<'tcx>], ty: Ty<'tcx>, region: ty::EarlyBoundRegion, ) -> bool { @@ -937,10 +936,10 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { if let ty::GenericArgKind::Type(ty) = arg.unpack() && let ty::Param(_) = ty.kind() { - predicates.iter().any(|pred| { + clauses.iter().any(|pred| { match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) if data.self_ty() == ty => {} - ty::PredicateKind::Clause(ty::Clause::Projection(data)) if data.projection_ty.self_ty() == ty => {} + ty::ClauseKind::Trait(data) if data.self_ty() == ty => {} + ty::ClauseKind::Projection(data) if data.projection_ty.self_ty() == ty => {} _ => return false, } tcx.any_free_region_meets(pred, |r| { diff --git a/compiler/rustc_borrowck/src/diagnostics/var_name.rs b/compiler/rustc_borrowck/src/diagnostics/var_name.rs index 98418e2372f0e..8832d345df2ca 100644 --- a/compiler/rustc_borrowck/src/diagnostics/var_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/var_name.rs @@ -43,7 +43,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fr: RegionVid, ) -> Option { let upvar_index = - self.universal_regions().defining_ty.upvar_tys().position(|upvar_ty| { + self.universal_regions().defining_ty.upvar_tys().iter().position(|upvar_ty| { debug!("get_upvar_index_for_region: upvar_ty={upvar_ty:?}"); tcx.any_free_region_meets(&upvar_ty, |r| { let r = r.as_var(); @@ -52,7 +52,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { }) })?; - let upvar_ty = self.universal_regions().defining_ty.upvar_tys().nth(upvar_index); + let upvar_ty = self.universal_regions().defining_ty.upvar_tys().get(upvar_index); debug!( "get_upvar_index_for_region: found {fr:?} in upvar {upvar_index} which has type {upvar_ty:?}", diff --git a/compiler/rustc_borrowck/src/facts.rs b/compiler/rustc_borrowck/src/facts.rs index 87fad9a355d3d..9916ebca32fa9 100644 --- a/compiler/rustc_borrowck/src/facts.rs +++ b/compiler/rustc_borrowck/src/facts.rs @@ -202,7 +202,7 @@ trait FactCell { impl FactCell for A { default fn to_string(&self, _location_table: &LocationTable) -> String { - format!("{:?}", self) + format!("{self:?}") } } diff --git a/compiler/rustc_borrowck/src/invalidation.rs b/compiler/rustc_borrowck/src/invalidation.rs index 036391d074da8..df5e383ad40bd 100644 --- a/compiler/rustc_borrowck/src/invalidation.rs +++ b/compiler/rustc_borrowck/src/invalidation.rs @@ -112,11 +112,13 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> { TerminatorKind::SwitchInt { discr, targets: _ } => { self.consume_operand(location, discr); } - TerminatorKind::Drop { place: drop_place, target: _, unwind: _ } => { + TerminatorKind::Drop { place: drop_place, target: _, unwind: _, replace } => { + let write_kind = + if *replace { WriteKind::Replace } else { WriteKind::StorageDeadOrDrop }; self.access_place( location, *drop_place, - (AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)), + (AccessDepth::Drop, Write(write_kind)), LocalMutationIsAllowed::Yes, ); } @@ -126,7 +128,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> { destination, target: _, unwind: _, - from_hir_call: _, + call_source: _, fn_span: _, } => { self.consume_operand(location, func); @@ -253,7 +255,7 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> { (Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk))) } BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))), - BorrowKind::Unique | BorrowKind::Mut { .. } => { + BorrowKind::Mut { .. } => { let wk = WriteKind::MutableBorrow(bk); if allow_two_phase_borrow(bk) { (Deep, Reservation(wk)) @@ -271,7 +273,7 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> { Mutability::Mut => ( Deep, Write(WriteKind::MutableBorrow(BorrowKind::Mut { - allow_two_phase_borrow: false, + kind: mir::MutBorrowKind::Default, })), ), Mutability::Not => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))), @@ -351,7 +353,6 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> { let tcx = self.tcx; let body = self.body; let borrow_set = self.borrow_set; - let indices = self.borrow_set.indices(); each_borrow_involving_path( self, tcx, @@ -359,7 +360,7 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> { location, (sd, place), borrow_set, - indices, + |_| true, |this, borrow_index, borrow| { match (rw, borrow.kind) { // Obviously an activation is compatible with its own @@ -374,14 +375,11 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> { } (Read(_), BorrowKind::Shallow | BorrowKind::Shared) - | ( - Read(ReadKind::Borrow(BorrowKind::Shallow)), - BorrowKind::Unique | BorrowKind::Mut { .. }, - ) => { + | (Read(ReadKind::Borrow(BorrowKind::Shallow)), BorrowKind::Mut { .. }) => { // Reads don't invalidate shared or shallow borrows } - (Read(_), BorrowKind::Unique | BorrowKind::Mut { .. }) => { + (Read(_), BorrowKind::Mut { .. }) => { // Reading from mere reservations of mutable-borrows is OK. if !is_active(&this.dominators, borrow, location) { // If the borrow isn't active yet, reads don't invalidate it @@ -423,7 +421,7 @@ impl<'cx, 'tcx> InvalidationGenerator<'cx, 'tcx> { // only mutable borrows should be 2-phase assert!(match borrow.kind { BorrowKind::Shared | BorrowKind::Shallow => false, - BorrowKind::Unique | BorrowKind::Mut { .. } => true, + BorrowKind::Mut { .. } => true, }); self.access_place( diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 9277a262f9789..efe525c224d0a 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -11,6 +11,7 @@ #![feature(trusted_step)] #![feature(try_blocks)] #![recursion_limit = "256"] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_middle; @@ -23,14 +24,14 @@ use rustc_errors::{Diagnostic, DiagnosticBuilder, DiagnosticMessage, Subdiagnost use rustc_fluent_macro::fluent_messages; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; -use rustc_index::bit_set::ChunkedBitSet; +use rustc_index::bit_set::{BitSet, ChunkedBitSet}; use rustc_index::{IndexSlice, IndexVec}; use rustc_infer::infer::{ InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, TyCtxtInferExt, }; use rustc_middle::mir::{ - traversal, Body, ClearCrossCrate, Local, Location, Mutability, NonDivergingIntrinsic, Operand, - Place, PlaceElem, PlaceRef, VarDebugInfoContents, + traversal, Body, ClearCrossCrate, Local, Location, MutBorrowKind, Mutability, + NonDivergingIntrinsic, Operand, Place, PlaceElem, PlaceRef, VarDebugInfoContents, }; use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind}; use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind}; @@ -42,7 +43,6 @@ use rustc_session::lint::builtin::UNUSED_MUT; use rustc_span::{Span, Symbol}; use rustc_target::abi::FieldIdx; -use either::Either; use smallvec::SmallVec; use std::cell::RefCell; use std::collections::BTreeMap; @@ -222,7 +222,7 @@ fn do_mir_borrowck<'tcx>( let (move_data, move_errors): (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>) = match MoveData::gather_moves(&body, tcx, param_env) { - Ok((_, move_data)) => (move_data, Vec::new()), + Ok(move_data) => (move_data, Vec::new()), Err((move_data, move_errors)) => (move_data, move_errors), }; let promoted_errors = promoted @@ -301,7 +301,7 @@ fn do_mir_borrowck<'tcx>( let movable_generator = // The first argument is the generator type passed by value if let Some(local) = body.local_decls.raw.get(1) - // Get the interior types and substs which typeck computed + // Get the interior types and args which typeck computed && let ty::Generator(_, _, hir::Movability::Static) = local.ty.kind() { false @@ -368,7 +368,7 @@ fn do_mir_borrowck<'tcx>( // Compute and report region errors, if any. mbcx.report_region_errors(nll_errors); - let results = BorrowckResults { + let mut results = BorrowckResults { ever_inits: flow_ever_inits, uninits: flow_uninits, borrows: flow_borrows, @@ -379,7 +379,7 @@ fn do_mir_borrowck<'tcx>( rustc_mir_dataflow::visit_results( body, traversal::reverse_postorder(body).map(|(bb, _)| bb), - &results, + &mut results, &mut mbcx, ); @@ -598,11 +598,12 @@ struct MirBorrowckCtxt<'cx, 'tcx> { // 2. loans made in overlapping scopes do not conflict // 3. assignments do not affect things loaned out as immutable // 4. moves do not affect things loaned out in any way -impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tcx> { +impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorrowckCtxt<'cx, 'tcx> { type FlowState = Flows<'cx, 'tcx>; fn visit_statement_before_primary_effect( &mut self, + _results: &R, flow_state: &Flows<'cx, 'tcx>, stmt: &'cx Statement<'tcx>, location: Location, @@ -672,6 +673,7 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx fn visit_terminator_before_primary_effect( &mut self, + _results: &R, flow_state: &Flows<'cx, 'tcx>, term: &'cx Terminator<'tcx>, loc: Location, @@ -685,17 +687,19 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx TerminatorKind::SwitchInt { discr, targets: _ } => { self.consume_operand(loc, (discr, span), flow_state); } - TerminatorKind::Drop { place, target: _, unwind: _ } => { + TerminatorKind::Drop { place, target: _, unwind: _, replace } => { debug!( "visit_terminator_drop \ loc: {:?} term: {:?} place: {:?} span: {:?}", loc, term, place, span ); + let write_kind = + if *replace { WriteKind::Replace } else { WriteKind::StorageDeadOrDrop }; self.access_place( loc, (*place, span), - (AccessDepth::Drop, Write(WriteKind::StorageDeadOrDrop)), + (AccessDepth::Drop, Write(write_kind)), LocalMutationIsAllowed::Yes, flow_state, ); @@ -706,7 +710,7 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx destination, target: _, unwind: _, - from_hir_call: _, + call_source: _, fn_span: _, } => { self.consume_operand(loc, (func, span), flow_state); @@ -780,6 +784,7 @@ impl<'cx, 'tcx> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtx fn visit_terminator_after_primary_effect( &mut self, + _results: &R, flow_state: &Flows<'cx, 'tcx>, term: &'cx Terminator<'tcx>, loc: Location, @@ -885,6 +890,7 @@ enum ReadKind { #[derive(Copy, Clone, PartialEq, Eq, Debug)] enum WriteKind { StorageDeadOrDrop, + Replace, MutableBorrow(BorrowKind), Mutate, Move, @@ -1029,12 +1035,16 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { let borrow_set = self.borrow_set.clone(); // Use polonius output if it has been enabled. - let polonius_output = self.polonius_output.clone(); - let borrows_in_scope = if let Some(polonius) = &polonius_output { + let mut polonius_output; + let borrows_in_scope = if let Some(polonius) = &self.polonius_output { let location = self.location_table.start_index(location); - Either::Left(polonius.errors_at(location).iter().copied()) + polonius_output = BitSet::new_empty(borrow_set.len()); + for &idx in polonius.errors_at(location) { + polonius_output.insert(idx); + } + &polonius_output } else { - Either::Right(flow_state.borrows.iter()) + &flow_state.borrows }; each_borrow_involving_path( @@ -1044,7 +1054,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { location, (sd, place_span.0), &borrow_set, - borrows_in_scope, + |borrow_index| borrows_in_scope.contains(borrow_index), |this, borrow_index, borrow| match (rw, borrow.kind) { // Obviously an activation is compatible with its own // reservation (or even prior activating uses of same @@ -1065,10 +1075,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } (Read(_), BorrowKind::Shared | BorrowKind::Shallow) - | ( - Read(ReadKind::Borrow(BorrowKind::Shallow)), - BorrowKind::Unique | BorrowKind::Mut { .. }, - ) => Control::Continue, + | (Read(ReadKind::Borrow(BorrowKind::Shallow)), BorrowKind::Mut { .. }) => { + Control::Continue + } (Reservation(_), BorrowKind::Shallow | BorrowKind::Shared) => { // This used to be a future compatibility warning (to be @@ -1081,7 +1090,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { Control::Continue } - (Read(kind), BorrowKind::Unique | BorrowKind::Mut { .. }) => { + (Read(kind), BorrowKind::Mut { .. }) => { // Reading from mere reservations of mutable-borrows is OK. if !is_active(this.dominators(), borrow, location) { assert!(allow_two_phase_borrow(borrow.kind)); @@ -1132,13 +1141,21 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { this.buffer_error(err); } WriteKind::StorageDeadOrDrop => this - .report_storage_dead_or_drop_of_borrowed(location, place_span, borrow), + .report_borrowed_value_does_not_live_long_enough( + location, + borrow, + place_span, + Some(WriteKind::StorageDeadOrDrop), + ), WriteKind::Mutate => { this.report_illegal_mutation_of_borrowed(location, place_span, borrow) } WriteKind::Move => { this.report_move_out_while_borrowed(location, place_span, borrow) } + WriteKind::Replace => { + this.report_illegal_mutation_of_borrowed(location, place_span, borrow) + } } Control::Break } @@ -1180,7 +1197,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { (Shallow(Some(ArtificialField::ShallowBorrow)), Read(ReadKind::Borrow(bk))) } BorrowKind::Shared => (Deep, Read(ReadKind::Borrow(bk))), - BorrowKind::Unique | BorrowKind::Mut { .. } => { + BorrowKind::Mut { .. } => { let wk = WriteKind::MutableBorrow(bk); if allow_two_phase_borrow(bk) { (Deep, Reservation(wk)) @@ -1217,7 +1234,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { Mutability::Mut => ( Deep, Write(WriteKind::MutableBorrow(BorrowKind::Mut { - allow_two_phase_borrow: false, + kind: MutBorrowKind::Default, })), ), Mutability::Not => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))), @@ -1551,7 +1568,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { // only mutable borrows should be 2-phase assert!(match borrow.kind { BorrowKind::Shared | BorrowKind::Shallow => false, - BorrowKind::Unique | BorrowKind::Mut { .. } => true, + BorrowKind::Mut { .. } => true, }); self.access_place( @@ -1804,8 +1821,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { } ProjectionElem::Subslice { .. } => { - panic!("we don't allow assignments to subslices, location: {:?}", - location); + panic!("we don't allow assignments to subslices, location: {location:?}"); } ProjectionElem::Field(..) => { @@ -1945,16 +1961,15 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { let the_place_err; match kind { - Reservation(WriteKind::MutableBorrow( - borrow_kind @ (BorrowKind::Unique | BorrowKind::Mut { .. }), - )) - | Write(WriteKind::MutableBorrow( - borrow_kind @ (BorrowKind::Unique | BorrowKind::Mut { .. }), - )) => { - let is_local_mutation_allowed = match borrow_kind { - BorrowKind::Unique => LocalMutationIsAllowed::Yes, - BorrowKind::Mut { .. } => is_local_mutation_allowed, - BorrowKind::Shared | BorrowKind::Shallow => unreachable!(), + Reservation(WriteKind::MutableBorrow(BorrowKind::Mut { kind: mut_borrow_kind })) + | Write(WriteKind::MutableBorrow(BorrowKind::Mut { kind: mut_borrow_kind })) => { + let is_local_mutation_allowed = match mut_borrow_kind { + // `ClosureCapture` is used for mutable variable with a immutable binding. + // This is only behaviour difference between `ClosureCapture` and mutable borrows. + MutBorrowKind::ClosureCapture => LocalMutationIsAllowed::Yes, + MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow => { + is_local_mutation_allowed + } }; match self.is_mutable(place.as_ref(), is_local_mutation_allowed) { Ok(root_place) => { @@ -1982,12 +1997,14 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { Reservation( WriteKind::Move + | WriteKind::Replace | WriteKind::StorageDeadOrDrop | WriteKind::MutableBorrow(BorrowKind::Shared) | WriteKind::MutableBorrow(BorrowKind::Shallow), ) | Write( WriteKind::Move + | WriteKind::Replace | WriteKind::StorageDeadOrDrop | WriteKind::MutableBorrow(BorrowKind::Shared) | WriteKind::MutableBorrow(BorrowKind::Shallow), @@ -2003,8 +2020,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { self.infcx.tcx.sess.delay_span_bug( span, format!( - "Accessing `{:?}` with the kind `{:?}` shouldn't be possible", - place, kind, + "Accessing `{place:?}` with the kind `{kind:?}` shouldn't be possible", ), ); } @@ -2015,12 +2031,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { return false; } Read( - ReadKind::Borrow( - BorrowKind::Unique - | BorrowKind::Mut { .. } - | BorrowKind::Shared - | BorrowKind::Shallow, - ) + ReadKind::Borrow(BorrowKind::Mut { .. } | BorrowKind::Shared | BorrowKind::Shallow) | ReadKind::Copy, ) => { // Access authorized @@ -2297,11 +2308,10 @@ mod error { pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_, ErrorGuaranteed>) { if let None = self.tainted_by_errors { - self.tainted_by_errors = Some( - self.tcx - .sess - .delay_span_bug(t.span.clone(), "diagnostic buffered but not emitted"), - ) + self.tainted_by_errors = Some(self.tcx.sess.delay_span_bug( + t.span.clone_ignoring_labels(), + "diagnostic buffered but not emitted", + )) } t.buffer(&mut self.buffered); } diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 889acb3acbed7..679a19710a7f4 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -347,7 +347,7 @@ pub(super) fn dump_mir_results<'tcx>( for_each_region_constraint( infcx.tcx, closure_region_requirements, - &mut |msg| writeln!(out, "| {}", msg), + &mut |msg| writeln!(out, "| {msg}"), )?; writeln!(out, "|")?; } @@ -426,7 +426,7 @@ pub(super) fn dump_annotation<'tcx>( }; if !opaque_type_values.is_empty() { - err.note(format!("Inferred opaque type values:\n{:#?}", opaque_type_values)); + err.note(format!("Inferred opaque type values:\n{opaque_type_values:#?}")); } errors.buffer_non_error_diag(err); @@ -439,9 +439,9 @@ fn for_each_region_constraint<'tcx>( ) -> io::Result<()> { for req in &closure_region_requirements.outlives_requirements { let subject = match req.subject { - ClosureOutlivesSubject::Region(subject) => format!("{:?}", subject), + ClosureOutlivesSubject::Region(subject) => format!("{subject:?}"), ClosureOutlivesSubject::Ty(ty) => { - format!("{:?}", ty.instantiate(tcx, |vid| tcx.mk_re_var(vid))) + format!("{:?}", ty.instantiate(tcx, |vid| ty::Region::new_var(tcx, vid))) } }; with_msg(format!("where {}: {:?}", subject, req.outlived_free_region,))?; diff --git a/compiler/rustc_borrowck/src/path_utils.rs b/compiler/rustc_borrowck/src/path_utils.rs index ea9f8683ca7bb..ed93a560981e4 100644 --- a/compiler/rustc_borrowck/src/path_utils.rs +++ b/compiler/rustc_borrowck/src/path_utils.rs @@ -33,20 +33,24 @@ pub(super) fn each_borrow_involving_path<'tcx, F, I, S>( _location: Location, access_place: (AccessDepth, Place<'tcx>), borrow_set: &BorrowSet<'tcx>, - candidates: I, + is_candidate: I, mut op: F, ) where F: FnMut(&mut S, BorrowIndex, &BorrowData<'tcx>) -> Control, - I: Iterator, + I: Fn(BorrowIndex) -> bool, { let (access, place) = access_place; - // FIXME: analogous code in check_loans first maps `place` to - // its base_path. + // The number of candidates can be large, but borrows for different locals cannot conflict with + // each other, so we restrict the working set a priori. + let Some(borrows_for_place_base) = borrow_set.local_map.get(&place.local) else { return }; // check for loan restricting path P being used. Accounts for // borrows of P, P.a.b, etc. - for i in candidates { + for &i in borrows_for_place_base { + if !is_candidate(i) { + continue; + } let borrowed = &borrow_set[i]; if places_conflict::borrow_conflicts_with_place( diff --git a/compiler/rustc_borrowck/src/place_ext.rs b/compiler/rustc_borrowck/src/place_ext.rs index d521d0db21323..3d7e8c6ebf330 100644 --- a/compiler/rustc_borrowck/src/place_ext.rs +++ b/compiler/rustc_borrowck/src/place_ext.rs @@ -46,11 +46,9 @@ impl<'tcx> PlaceExt<'tcx> for Place<'tcx> { } } - for (i, elem) in self.projection.iter().enumerate() { - let proj_base = &self.projection[..i]; - + for (i, (proj_base, elem)) in self.iter_projections().enumerate() { if elem == ProjectionElem::Deref { - let ty = Place::ty_from(self.local, proj_base, body, tcx).ty; + let ty = proj_base.ty(body, tcx).ty; match ty.kind() { ty::Ref(_, _, hir::Mutability::Not) if i == 0 => { // For references to thread-local statics, we do need diff --git a/compiler/rustc_borrowck/src/places_conflict.rs b/compiler/rustc_borrowck/src/places_conflict.rs index 25c485b814f4a..c02f6f3b68782 100644 --- a/compiler/rustc_borrowck/src/places_conflict.rs +++ b/compiler/rustc_borrowck/src/places_conflict.rs @@ -1,10 +1,64 @@ +//! The borrowck rules for proving disjointness are applied from the "root" of the +//! borrow forwards, iterating over "similar" projections in lockstep until +//! we can prove overlap one way or another. Essentially, we treat `Overlap` as +//! a monoid and report a conflict if the product ends up not being `Disjoint`. +//! +//! At each step, if we didn't run out of borrow or place, we know that our elements +//! have the same type, and that they only overlap if they are the identical. +//! +//! For example, if we are comparing these: +//! ```text +//! BORROW: (*x1[2].y).z.a +//! ACCESS: (*x1[i].y).w.b +//! ``` +//! +//! Then our steps are: +//! ```text +//! x1 | x1 -- places are the same +//! x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ) +//! x1[2].y | x1[i].y -- equal or disjoint +//! *x1[2].y | *x1[i].y -- equal or disjoint +//! (*x1[2].y).z | (*x1[i].y).w -- we are disjoint and don't need to check more! +//! ``` +//! +//! Because `zip` does potentially bad things to the iterator inside, this loop +//! also handles the case where the access might be a *prefix* of the borrow, e.g. +//! +//! ```text +//! BORROW: (*x1[2].y).z.a +//! ACCESS: x1[i].y +//! ``` +//! +//! Then our steps are: +//! ```text +//! x1 | x1 -- places are the same +//! x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ) +//! x1[2].y | x1[i].y -- equal or disjoint +//! ``` +//! +//! -- here we run out of access - the borrow can access a part of it. If this +//! is a full deep access, then we *know* the borrow conflicts with it. However, +//! if the access is shallow, then we can proceed: +//! +//! ```text +//! x1[2].y | (*x1[i].y) -- a deref! the access can't get past this, so we +//! are disjoint +//! ``` +//! +//! Our invariant is, that at each step of the iteration: +//! - If we didn't run out of access to match, our borrow and access are comparable +//! and either equal or disjoint. +//! - If we did run out of access, the borrow can access a part of it. + #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] use crate::ArtificialField; use crate::Overlap; use crate::{AccessDepth, Deep, Shallow}; use rustc_hir as hir; -use rustc_middle::mir::{Body, BorrowKind, Local, Place, PlaceElem, PlaceRef, ProjectionElem}; +use rustc_middle::mir::{ + Body, BorrowKind, MutBorrowKind, Place, PlaceElem, PlaceRef, ProjectionElem, +}; use rustc_middle::ty::{self, TyCtxt}; use std::cmp::max; use std::iter; @@ -35,7 +89,7 @@ pub fn places_conflict<'tcx>( tcx, body, borrow_place, - BorrowKind::Mut { allow_two_phase_borrow: true }, + BorrowKind::Mut { kind: MutBorrowKind::TwoPhaseBorrow }, access_place.as_ref(), AccessDepth::Deep, bias, @@ -46,7 +100,7 @@ pub fn places_conflict<'tcx>( /// access depth. The `bias` parameter is used to determine how the unknowable (comparing runtime /// array indices, for example) should be interpreted - this depends on what the caller wants in /// order to make the conservative choice and preserve soundness. -#[instrument(level = "debug", skip(tcx, body))] +#[inline] pub(super) fn borrow_conflicts_with_place<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, @@ -56,15 +110,24 @@ pub(super) fn borrow_conflicts_with_place<'tcx>( access: AccessDepth, bias: PlaceConflictBias, ) -> bool { + let borrow_local = borrow_place.local; + let access_local = access_place.local; + + if borrow_local != access_local { + // We have proven the borrow disjoint - further projections will remain disjoint. + return false; + } + // This Local/Local case is handled by the more general code below, but // it's so common that it's a speed win to check for it first. - if let Some(l1) = borrow_place.as_local() && let Some(l2) = access_place.as_local() { - return l1 == l2; + if borrow_place.projection.is_empty() && access_place.projection.is_empty() { + return true; } place_components_conflict(tcx, body, borrow_place, borrow_kind, access_place, access, bias) } +#[instrument(level = "debug", skip(tcx, body))] fn place_components_conflict<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, @@ -74,74 +137,17 @@ fn place_components_conflict<'tcx>( access: AccessDepth, bias: PlaceConflictBias, ) -> bool { - // The borrowck rules for proving disjointness are applied from the "root" of the - // borrow forwards, iterating over "similar" projections in lockstep until - // we can prove overlap one way or another. Essentially, we treat `Overlap` as - // a monoid and report a conflict if the product ends up not being `Disjoint`. - // - // At each step, if we didn't run out of borrow or place, we know that our elements - // have the same type, and that they only overlap if they are the identical. - // - // For example, if we are comparing these: - // BORROW: (*x1[2].y).z.a - // ACCESS: (*x1[i].y).w.b - // - // Then our steps are: - // x1 | x1 -- places are the same - // x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ) - // x1[2].y | x1[i].y -- equal or disjoint - // *x1[2].y | *x1[i].y -- equal or disjoint - // (*x1[2].y).z | (*x1[i].y).w -- we are disjoint and don't need to check more! - // - // Because `zip` does potentially bad things to the iterator inside, this loop - // also handles the case where the access might be a *prefix* of the borrow, e.g. - // - // BORROW: (*x1[2].y).z.a - // ACCESS: x1[i].y - // - // Then our steps are: - // x1 | x1 -- places are the same - // x1[2] | x1[i] -- equal or disjoint (disjoint if indexes differ) - // x1[2].y | x1[i].y -- equal or disjoint - // - // -- here we run out of access - the borrow can access a part of it. If this - // is a full deep access, then we *know* the borrow conflicts with it. However, - // if the access is shallow, then we can proceed: - // - // x1[2].y | (*x1[i].y) -- a deref! the access can't get past this, so we - // are disjoint - // - // Our invariant is, that at each step of the iteration: - // - If we didn't run out of access to match, our borrow and access are comparable - // and either equal or disjoint. - // - If we did run out of access, the borrow can access a part of it. - let borrow_local = borrow_place.local; let access_local = access_place.local; - - match place_base_conflict(borrow_local, access_local) { - Overlap::Arbitrary => { - bug!("Two base can't return Arbitrary"); - } - Overlap::EqualOrDisjoint => { - // This is the recursive case - proceed to the next element. - } - Overlap::Disjoint => { - // We have proven the borrow disjoint - further - // projections will remain disjoint. - debug!("borrow_conflicts_with_place: disjoint"); - return false; - } - } + // borrow_conflicts_with_place should have checked that. + assert_eq!(borrow_local, access_local); // loop invariant: borrow_c is always either equal to access_c or disjoint from it. - for (i, (borrow_c, &access_c)) in - iter::zip(borrow_place.projection, access_place.projection).enumerate() + for ((borrow_place, borrow_c), &access_c) in + iter::zip(borrow_place.iter_projections(), access_place.projection) { debug!(?borrow_c, ?access_c); - let borrow_proj_base = &borrow_place.projection[..i]; - // Borrow and access path both have more components. // // Examples: @@ -154,15 +160,7 @@ fn place_components_conflict<'tcx>( // check whether the components being borrowed vs // accessed are disjoint (as in the second example, // but not the first). - match place_projection_conflict( - tcx, - body, - borrow_local, - borrow_proj_base, - borrow_c, - access_c, - bias, - ) { + match place_projection_conflict(tcx, body, borrow_place, borrow_c, access_c, bias) { Overlap::Arbitrary => { // We have encountered different fields of potentially // the same union - the borrow now partially overlaps. @@ -193,8 +191,7 @@ fn place_components_conflict<'tcx>( } if borrow_place.projection.len() > access_place.projection.len() { - for (i, elem) in borrow_place.projection[access_place.projection.len()..].iter().enumerate() - { + for (base, elem) in borrow_place.iter_projections().skip(access_place.projection.len()) { // Borrow path is longer than the access path. Examples: // // - borrow of `a.b.c`, access to `a.b` @@ -203,8 +200,7 @@ fn place_components_conflict<'tcx>( // our place. This is a conflict if that is a part our // access cares about. - let proj_base = &borrow_place.projection[..access_place.projection.len() + i]; - let base_ty = Place::ty_from(borrow_local, proj_base, body, tcx).ty; + let base_ty = base.ty(body, tcx).ty; match (elem, &base_ty.kind(), access) { (_, _, Shallow(Some(ArtificialField::ArrayLength))) @@ -287,29 +283,13 @@ fn place_components_conflict<'tcx>( } } -// Given that the bases of `elem1` and `elem2` are always either equal -// or disjoint (and have the same type!), return the overlap situation -// between `elem1` and `elem2`. -fn place_base_conflict(l1: Local, l2: Local) -> Overlap { - if l1 == l2 { - // the same local - base case, equal - debug!("place_element_conflict: DISJOINT-OR-EQ-LOCAL"); - Overlap::EqualOrDisjoint - } else { - // different locals - base case, disjoint - debug!("place_element_conflict: DISJOINT-LOCAL"); - Overlap::Disjoint - } -} - // Given that the bases of `elem1` and `elem2` are always either equal // or disjoint (and have the same type!), return the overlap situation // between `elem1` and `elem2`. fn place_projection_conflict<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, - pi1_local: Local, - pi1_proj_base: &[PlaceElem<'tcx>], + pi1: PlaceRef<'tcx>, pi1_elem: PlaceElem<'tcx>, pi2_elem: PlaceElem<'tcx>, bias: PlaceConflictBias, @@ -331,7 +311,7 @@ fn place_projection_conflict<'tcx>( debug!("place_element_conflict: DISJOINT-OR-EQ-FIELD"); Overlap::EqualOrDisjoint } else { - let ty = Place::ty_from(pi1_local, pi1_proj_base, body, tcx).ty; + let ty = pi1.ty(body, tcx).ty; if ty.is_union() { // Different fields of a union, we are basically stuck. debug!("place_element_conflict: STUCK-UNION"); diff --git a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs index 6524b594e44dc..4d620ac9de615 100644 --- a/compiler/rustc_borrowck/src/region_infer/dump_mir.rs +++ b/compiler/rustc_borrowck/src/region_infer/dump_mir.rs @@ -52,7 +52,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { writeln!(out, "|")?; writeln!(out, "| Inference Constraints")?; - self.for_each_constraint(tcx, &mut |msg| writeln!(out, "| {}", msg))?; + self.for_each_constraint(tcx, &mut |msg| writeln!(out, "| {msg}"))?; Ok(()) } @@ -69,7 +69,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { for region in self.definitions.indices() { let value = self.liveness_constraints.region_value_str(region); if value != "{}" { - with_msg(&format!("{:?} live at {}", region, value))?; + with_msg(&format!("{region:?} live at {value}"))?; } } @@ -81,12 +81,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { Locations::All(span) => { ("All", tcx.sess.source_map().span_to_embeddable_string(*span)) } - Locations::Single(loc) => ("Single", format!("{:?}", loc)), + Locations::Single(loc) => ("Single", format!("{loc:?}")), }; - with_msg(&format!( - "{:?}: {:?} due to {:?} at {}({}) ({:?}", - sup, sub, category, name, arg, span - ))?; + with_msg(&format!("{sup:?}: {sub:?} due to {category:?} at {name}({arg}) ({span:?}"))?; } Ok(()) diff --git a/compiler/rustc_borrowck/src/region_infer/graphviz.rs b/compiler/rustc_borrowck/src/region_infer/graphviz.rs index 2e15586e03b3b..a0cf22e935a94 100644 --- a/compiler/rustc_borrowck/src/region_infer/graphviz.rs +++ b/compiler/rustc_borrowck/src/region_infer/graphviz.rs @@ -49,7 +49,7 @@ impl<'a, 'this, 'tcx> dot::Labeller<'this> for RawConstraints<'a, 'tcx> { Some(dot::LabelText::LabelStr(Cow::Borrowed("box"))) } fn node_label(&'this self, n: &RegionVid) -> dot::LabelText<'this> { - dot::LabelText::LabelStr(format!("{:?}", n).into()) + dot::LabelText::LabelStr(format!("{n:?}").into()) } fn edge_label(&'this self, e: &OutlivesConstraint<'tcx>) -> dot::LabelText<'this> { dot::LabelText::LabelStr(format!("{:?}", e.locations).into()) @@ -100,7 +100,7 @@ impl<'a, 'this, 'tcx> dot::Labeller<'this> for SccConstraints<'a, 'tcx> { } fn node_label(&'this self, n: &ConstraintSccIndex) -> dot::LabelText<'this> { let nodes = &self.nodes_per_scc[*n]; - dot::LabelText::LabelStr(format!("{:?} = {:?}", n, nodes).into()) + dot::LabelText::LabelStr(format!("{n:?} = {nodes:?}").into()) } } diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 50b246b1478fc..b8cd94e542242 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -259,7 +259,7 @@ fn sccs_info<'cx, 'tcx>( let mut reg_vars_to_origins_str = "region variables to origins:\n".to_string(); for (reg_var, origin) in var_to_origin_sorted.into_iter() { - reg_vars_to_origins_str.push_str(&format!("{:?}: {:?}\n", reg_var, origin)); + reg_vars_to_origins_str.push_str(&format!("{reg_var:?}: {origin:?}\n")); } debug!("{}", reg_vars_to_origins_str); @@ -784,13 +784,20 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// is considered a *lower bound*. If possible, we will modify /// the constraint to set it equal to one of the option regions. /// If we make any changes, returns true, else false. + /// + /// This function only adds the member constraints to the region graph, + /// it does not check them. They are later checked in + /// `check_member_constraints` after the region graph has been computed. #[instrument(skip(self, member_constraint_index), level = "debug")] fn apply_member_constraint( &mut self, scc: ConstraintSccIndex, member_constraint_index: NllMemberConstraintIndex, choice_regions: &[ty::RegionVid], - ) -> bool { + ) { + // Lazily compute the reverse graph, we'll need it later. + self.compute_reverse_scc_graph(); + // Create a mutable vector of the options. We'll try to winnow // them down. let mut choice_regions: Vec = choice_regions.to_vec(); @@ -805,10 +812,11 @@ impl<'tcx> RegionInferenceContext<'tcx> { *c_r = self.scc_representatives[scc]; } - // The 'member region' in a member constraint is part of the - // hidden type, which must be in the root universe. Therefore, - // it cannot have any placeholders in its value. - assert!(self.scc_universes[scc] == ty::UniverseIndex::ROOT); + // If the member region lives in a higher universe, we currently choose + // the most conservative option by leaving it unchanged. + if self.scc_universes[scc] != ty::UniverseIndex::ROOT { + return; + } debug_assert!( self.scc_values.placeholders_contained_in(scc).next().is_none(), "scc {:?} in a member constraint has placeholder value: {:?}", @@ -832,7 +840,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { // free region that must outlive the member region `R0` (`UB: // R0`). Therefore, we need only keep an option `O` if `UB: O` // for all UB. - self.compute_reverse_scc_graph(); let universal_region_relations = &self.universal_region_relations; for ub in self.rev_scc_graph.as_ref().unwrap().upper_bounds(scc) { debug!(?ub); @@ -867,7 +874,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } }) else { debug!("no unique minimum choice"); - return false; + return; }; let min_choice_scc = self.constraint_sccs.scc(min_choice); @@ -878,10 +885,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { min_choice, member_constraint_index, }); - - true - } else { - false } } @@ -1115,7 +1118,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { ) -> Option> { let tcx = infcx.tcx; - // Opaque types' substs may include useless lifetimes. + // Opaque types' args may include useless lifetimes. // We will replace them with ReStatic. struct OpaqueFolder<'tcx> { tcx: TyCtxt<'tcx>, @@ -1127,19 +1130,18 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { use ty::TypeSuperFoldable as _; let tcx = self.tcx; - let &ty::Alias(ty::Opaque, ty::AliasTy { substs, def_id, .. }) = t.kind() else { + let &ty::Alias(ty::Opaque, ty::AliasTy { args, def_id, .. }) = t.kind() else { return t.super_fold_with(self); }; - let substs = - std::iter::zip(substs, tcx.variances_of(def_id)).map(|(arg, v)| { - match (arg.unpack(), v) { - (ty::GenericArgKind::Lifetime(_), ty::Bivariant) => { - tcx.lifetimes.re_static.into() - } - _ => arg.fold_with(self), + let args = std::iter::zip(args, tcx.variances_of(def_id)).map(|(arg, v)| { + match (arg.unpack(), v) { + (ty::GenericArgKind::Lifetime(_), ty::Bivariant) => { + tcx.lifetimes.re_static.into() } - }); - tcx.mk_opaque(def_id, tcx.mk_substs_from_iter(substs)) + _ => arg.fold_with(self), + } + }); + Ty::new_opaque(tcx, def_id, tcx.mk_args_from_iter(args)) } } @@ -1158,7 +1160,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { .universal_regions_outlived_by(r_scc) .filter(|&u_r| !self.universal_regions.is_local_free_region(u_r)) .find(|&u_r| self.eval_equal(u_r, r_vid)) - .map(|u_r| tcx.mk_re_var(u_r)) + .map(|u_r| ty::Region::new_var(tcx, u_r)) // In the case of a failure, use `ReErased`. We will eventually // return `None` in this case. .unwrap_or(tcx.lifetimes.re_erased) @@ -1355,7 +1357,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { let vid = self.to_region_vid(r); let scc = self.constraint_sccs.scc(vid); let repr = self.scc_representatives[scc]; - tcx.mk_re_var(repr) + ty::Region::new_var(tcx, repr) }) } @@ -1779,7 +1781,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { } // If not, report an error. - let member_region = infcx.tcx.mk_re_var(member_region_vid); + let member_region = ty::Region::new_var(infcx.tcx, member_region_vid); errors_buffer.push(RegionErrorKind::UnexpectedHiddenRegion { span: m_c.definition_span, hidden_ty: m_c.hidden_ty, @@ -2058,10 +2060,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { let mut extra_info = vec![]; for constraint in path.iter() { let outlived = constraint.sub; - let Some(origin) = self.var_infos.get(outlived) else { continue; }; - let RegionVariableOrigin::Nll(NllRegionVariableOrigin::Placeholder(p)) = origin.origin else { continue; }; + let Some(origin) = self.var_infos.get(outlived) else { + continue; + }; + let RegionVariableOrigin::Nll(NllRegionVariableOrigin::Placeholder(p)) = origin.origin + else { + continue; + }; debug!(?constraint, ?p); - let ConstraintCategory::Predicate(span) = constraint.category else { continue; }; + let ConstraintCategory::Predicate(span) = constraint.category else { + continue; + }; extra_info.push(ExtraConstraintInfo::PlaceholderFromPredicate(span)); // We only want to point to one break; diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs index 7fc89e89a3599..4da7b60257186 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types.rs @@ -6,9 +6,9 @@ use rustc_infer::infer::InferCtxt; use rustc_infer::infer::TyCtxtInferExt as _; use rustc_infer::traits::{Obligation, ObligationCause}; use rustc_middle::traits::DefiningAnchor; -use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts}; use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable}; +use rustc_middle::ty::{GenericArgKind, GenericArgs}; use rustc_span::Span; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; use rustc_trait_selection::traits::ObligationCtxt; @@ -38,15 +38,15 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// back to concrete lifetimes: `'static`, `ReEarlyBound` or `ReFree`. /// /// First we map all the lifetimes in the concrete type to an equal - /// universal region that occurs in the concrete type's substs, in this case - /// this would result in `&'1 i32`. We only consider regions in the substs + /// universal region that occurs in the concrete type's args, in this case + /// this would result in `&'1 i32`. We only consider regions in the args /// in case there is an equal region that does not. For example, this should /// be allowed: /// `fn f<'a: 'b, 'b: 'a>(x: *mut &'b i32) -> impl Sized + 'a { x }` /// /// Then we map the regions in both the type and the subst to their /// `external_name` giving `concrete_type = &'a i32`, - /// `substs = ['static, 'a]`. This will then allow + /// `args = ['static, 'a]`. This will then allow /// `infer_opaque_definition_from_instantiation` to determine that /// `_Return<'_a> = &'_a i32`. /// @@ -61,7 +61,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { pub(crate) fn infer_opaque_types( &self, infcx: &InferCtxt<'tcx>, - opaque_ty_decls: FxIndexMap, (OpaqueHiddenType<'tcx>, OpaqueTyOrigin)>, + opaque_ty_decls: FxIndexMap, OpaqueHiddenType<'tcx>>, ) -> FxIndexMap> { let mut result: FxIndexMap> = FxIndexMap::default(); @@ -72,9 +72,9 @@ impl<'tcx> RegionInferenceContext<'tcx> { .collect(); debug!(?member_constraints); - for (opaque_type_key, (concrete_type, origin)) in opaque_ty_decls { - let substs = opaque_type_key.substs; - debug!(?concrete_type, ?substs); + for (opaque_type_key, concrete_type) in opaque_ty_decls { + let args = opaque_type_key.args; + debug!(?concrete_type, ?args); let mut subst_regions = vec![self.universal_regions.fr_static]; @@ -92,9 +92,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { } None => { subst_regions.push(vid); - infcx.tcx.mk_re_error_with_message( + ty::Region::new_error_with_message( + infcx.tcx, concrete_type.span, - "opaque type with non-universal region substs", + "opaque type with non-universal region args", ) } } @@ -109,17 +110,17 @@ impl<'tcx> RegionInferenceContext<'tcx> { } debug!(?subst_regions); - // Next, insert universal regions from substs, so we can translate regions that appear - // in them but are not subject to member constraints, for instance closure substs. - let universal_substs = infcx.tcx.fold_regions(substs, |region, _| { + // Next, insert universal regions from args, so we can translate regions that appear + // in them but are not subject to member constraints, for instance closure args. + let universal_args = infcx.tcx.fold_regions(args, |region, _| { if let ty::RePlaceholder(..) = region.kind() { - // Higher kinded regions don't need remapping, they don't refer to anything outside of this the substs. + // Higher kinded regions don't need remapping, they don't refer to anything outside of this the args. return region; } let vid = self.to_region_vid(region); to_universal_region(vid, &mut subst_regions) }); - debug!(?universal_substs); + debug!(?universal_args); debug!(?subst_regions); // Deduplicate the set of regions while keeping the chosen order. @@ -138,11 +139,10 @@ impl<'tcx> RegionInferenceContext<'tcx> { debug!(?universal_concrete_type); let opaque_type_key = - OpaqueTypeKey { def_id: opaque_type_key.def_id, substs: universal_substs }; + OpaqueTypeKey { def_id: opaque_type_key.def_id, args: universal_args }; let ty = infcx.infer_opaque_definition_from_instantiation( opaque_type_key, universal_concrete_type, - origin, ); // Sometimes two opaque types are the same only after we remap the generic parameters // back to the opaque type definition. E.g. we may have `OpaqueType` mapped to `(X, Y)` @@ -158,7 +158,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { ) .emit() }); - prev.ty = infcx.tcx.ty_error(guar); + prev.ty = Ty::new_error(infcx.tcx, guar); } // Pick a better span if there is one. // FIXME(oli-obk): collect multiple spans for better diagnostics down the road. @@ -175,7 +175,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// Map the regions in the type to named regions. This is similar to what /// `infer_opaque_types` does, but can infer any universal region, not only - /// ones from the substs for the opaque type. It also doesn't double check + /// ones from the args for the opaque type. It also doesn't double check /// that the regions produced are in fact equal to the named region they are /// replaced with. This is fine because this function is only to improve the /// region names in error messages. @@ -185,6 +185,21 @@ impl<'tcx> RegionInferenceContext<'tcx> { { tcx.fold_regions(ty, |region, _| match *region { ty::ReVar(vid) => { + let scc = self.constraint_sccs.scc(vid); + + // Special handling of higher-ranked regions. + if self.scc_universes[scc] != ty::UniverseIndex::ROOT { + match self.scc_values.placeholders_contained_in(scc).enumerate().last() { + // If the region contains a single placeholder then they're equal. + Some((0, placeholder)) => { + return ty::Region::new_placeholder(tcx, placeholder); + } + + // Fallback: this will produce a cryptic error message. + _ => return region, + } + } + // Find something that we can name let upper_bound = self.approx_universal_upper_bound(vid); let upper_bound = &self.definitions[upper_bound]; @@ -214,7 +229,6 @@ pub trait InferCtxtExt<'tcx> { &self, opaque_type_key: OpaqueTypeKey<'tcx>, instantiated_ty: OpaqueHiddenType<'tcx>, - origin: OpaqueTyOrigin, ) -> Ty<'tcx>; } @@ -239,7 +253,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { /// # Parameters /// /// - `def_id`, the `impl Trait` type - /// - `substs`, the substs used to instantiate this opaque type + /// - `args`, the args used to instantiate this opaque type /// - `instantiated_ty`, the inferred type C1 -- fully resolved, lifted version of /// `opaque_defn.concrete_ty` #[instrument(level = "debug", skip(self))] @@ -247,132 +261,137 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { &self, opaque_type_key: OpaqueTypeKey<'tcx>, instantiated_ty: OpaqueHiddenType<'tcx>, - origin: OpaqueTyOrigin, ) -> Ty<'tcx> { if let Some(e) = self.tainted_by_errors() { - return self.tcx.ty_error(e); + return Ty::new_error(self.tcx, e); + } + + if let Err(guar) = + check_opaque_type_parameter_valid(self.tcx, opaque_type_key, instantiated_ty.span) + { + return Ty::new_error(self.tcx, guar); } let definition_ty = instantiated_ty .remap_generic_params_to_declaration_params(opaque_type_key, self.tcx, false) .ty; - if let Err(guar) = check_opaque_type_parameter_valid( + // `definition_ty` does not live in of the current inference context, + // so lets make sure that we don't accidentally misuse our current `infcx`. + match check_opaque_type_well_formed( self.tcx, - opaque_type_key, - origin, + self.next_trait_solver(), + opaque_type_key.def_id, instantiated_ty.span, + definition_ty, ) { - return self.tcx.ty_error(guar); + Ok(hidden_ty) => hidden_ty, + Err(guar) => Ty::new_error(self.tcx, guar), } + } +} - // Only check this for TAIT. RPIT already supports `tests/ui/impl-trait/nested-return-type2.rs` - // on stable and we'd break that. - let OpaqueTyOrigin::TyAlias { .. } = origin else { - return definition_ty; - }; - let def_id = opaque_type_key.def_id; - // This logic duplicates most of `check_opaque_meets_bounds`. - // FIXME(oli-obk): Also do region checks here and then consider removing `check_opaque_meets_bounds` entirely. - let param_env = self.tcx.param_env(def_id); - // HACK This bubble is required for this tests to pass: - // nested-return-type2-tait2.rs - // nested-return-type2-tait3.rs - let infcx = - self.tcx.infer_ctxt().with_opaque_type_inference(DefiningAnchor::Bubble).build(); - let ocx = ObligationCtxt::new(&infcx); - // Require the hidden type to be well-formed with only the generics of the opaque type. - // Defining use functions may have more bounds than the opaque type, which is ok, as long as the - // hidden type is well formed even without those bounds. - let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(definition_ty.into())); - - let id_substs = InternalSubsts::identity_for_item(self.tcx, def_id); - - // Require that the hidden type actually fulfills all the bounds of the opaque type, even without - // the bounds that the function supplies. - let opaque_ty = self.tcx.mk_opaque(def_id.to_def_id(), id_substs); - if let Err(err) = ocx.eq( - &ObligationCause::misc(instantiated_ty.span, def_id), - param_env, - opaque_ty, - definition_ty, - ) { +/// This logic duplicates most of `check_opaque_meets_bounds`. +/// FIXME(oli-obk): Also do region checks here and then consider removing +/// `check_opaque_meets_bounds` entirely. +fn check_opaque_type_well_formed<'tcx>( + tcx: TyCtxt<'tcx>, + next_trait_solver: bool, + def_id: LocalDefId, + definition_span: Span, + definition_ty: Ty<'tcx>, +) -> Result, ErrorGuaranteed> { + // Only check this for TAIT. RPIT already supports `tests/ui/impl-trait/nested-return-type2.rs` + // on stable and we'd break that. + let opaque_ty_hir = tcx.hir().expect_item(def_id); + let OpaqueTyOrigin::TyAlias { .. } = opaque_ty_hir.expect_opaque_ty().origin else { + return Ok(definition_ty); + }; + let param_env = tcx.param_env(def_id); + // HACK This bubble is required for this tests to pass: + // nested-return-type2-tait2.rs + // nested-return-type2-tait3.rs + // FIXME(-Ztrait-solver=next): We probably should use `DefiningAnchor::Error` + // and prepopulate this `InferCtxt` with known opaque values, rather than + // using the `Bind` anchor here. For now it's fine. + let infcx = tcx + .infer_ctxt() + .with_next_trait_solver(next_trait_solver) + .with_opaque_type_inference(if next_trait_solver { + DefiningAnchor::Bind(def_id) + } else { + DefiningAnchor::Bubble + }) + .build(); + let ocx = ObligationCtxt::new(&infcx); + let identity_args = GenericArgs::identity_for_item(tcx, def_id); + + // Require that the hidden type actually fulfills all the bounds of the opaque type, even without + // the bounds that the function supplies. + let opaque_ty = Ty::new_opaque(tcx, def_id.to_def_id(), identity_args); + ocx.eq(&ObligationCause::misc(definition_span, def_id), param_env, opaque_ty, definition_ty) + .map_err(|err| { infcx .err_ctxt() .report_mismatched_types( - &ObligationCause::misc(instantiated_ty.span, def_id), + &ObligationCause::misc(definition_span, def_id), opaque_ty, definition_ty, err, ) - .emit(); - } + .emit() + })?; - ocx.register_obligation(Obligation::misc( - infcx.tcx, - instantiated_ty.span, - def_id, - param_env, - predicate, - )); + // Require the hidden type to be well-formed with only the generics of the opaque type. + // Defining use functions may have more bounds than the opaque type, which is ok, as long as the + // hidden type is well formed even without those bounds. + let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + definition_ty.into(), + ))); + ocx.register_obligation(Obligation::misc(tcx, definition_span, def_id, param_env, predicate)); - // Check that all obligations are satisfied by the implementation's - // version. - let errors = ocx.select_all_or_error(); + // Check that all obligations are satisfied by the implementation's + // version. + let errors = ocx.select_all_or_error(); - // This is still required for many(half of the tests in ui/type-alias-impl-trait) - // tests to pass - let _ = infcx.take_opaque_types(); + // This is fishy, but we check it again in `check_opaque_meets_bounds`. + // Remove once we can prepopulate with known hidden types. + let _ = infcx.take_opaque_types(); - if errors.is_empty() { - definition_ty - } else { - let reported = infcx.err_ctxt().report_fulfillment_errors(&errors); - self.tcx.ty_error(reported) - } + if errors.is_empty() { + Ok(definition_ty) + } else { + Err(infcx.err_ctxt().report_fulfillment_errors(&errors)) } } fn check_opaque_type_parameter_valid( tcx: TyCtxt<'_>, opaque_type_key: OpaqueTypeKey<'_>, - origin: OpaqueTyOrigin, span: Span, ) -> Result<(), ErrorGuaranteed> { - match origin { - // No need to check return position impl trait (RPIT) - // because for type and const parameters they are correct - // by construction: we convert - // - // fn foo() -> impl Trait - // - // into - // - // type Foo - // fn foo() -> Foo. - // - // For lifetime parameters we convert - // - // fn foo<'l0..'ln>() -> impl Trait<'l0..'lm> - // - // into - // - // type foo::<'p0..'pn>::Foo<'q0..'qm> - // fn foo() -> foo::<'static..'static>::Foo<'l0..'lm>. - // - // which would error here on all of the `'static` args. - OpaqueTyOrigin::FnReturn(..) | OpaqueTyOrigin::AsyncFn(..) => return Ok(()), - // Check these - OpaqueTyOrigin::TyAlias { .. } => {} - } + let opaque_ty_hir = tcx.hir().expect_item(opaque_type_key.def_id); + let is_ty_alias = match opaque_ty_hir.expect_opaque_ty().origin { + OpaqueTyOrigin::TyAlias { .. } => true, + OpaqueTyOrigin::AsyncFn(..) | OpaqueTyOrigin::FnReturn(..) => false, + }; + let opaque_generics = tcx.generics_of(opaque_type_key.def_id); let mut seen_params: FxIndexMap<_, Vec<_>> = FxIndexMap::default(); - for (i, arg) in opaque_type_key.substs.iter().enumerate() { + for (i, arg) in opaque_type_key.args.iter().enumerate() { + if let Err(guar) = arg.error_reported() { + return Err(guar); + } + let arg_is_param = match arg.unpack() { GenericArgKind::Type(ty) => matches!(ty.kind(), ty::Param(_)), - GenericArgKind::Lifetime(lt) => { + GenericArgKind::Lifetime(lt) if is_ty_alias => { matches!(*lt, ty::ReEarlyBound(_) | ty::ReFree(_)) } + // FIXME(#113916): we can't currently check for unique lifetime params, + // see that issue for more. We will also have to ignore unused lifetime + // params for RPIT, but that's comparatively trivial ✨ + GenericArgKind::Lifetime(_) => continue, GenericArgKind::Const(ct) => matches!(ct.kind(), ty::ConstKind::Param(_)), }; @@ -402,7 +421,7 @@ fn check_opaque_type_parameter_valid( return Err(tcx .sess .struct_span_err(span, "non-defining opaque type use in defining scope") - .span_note(spans, format!("{} used multiple times", descr)) + .span_note(spans, format!("{descr} used multiple times")) .emit()); } } diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 9290e7479144a..d205862cd3fe0 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -470,7 +470,7 @@ fn region_value_str(elements: impl IntoIterator) -> String } push_sep(&mut result); - result.push_str(&format!("{:?}", fr)); + result.push_str(&format!("{fr:?}")); } RegionElement::PlaceholderRegion(placeholder) => { @@ -481,7 +481,7 @@ fn region_value_str(elements: impl IntoIterator) -> String } push_sep(&mut result); - result.push_str(&format!("{:?}", placeholder)); + result.push_str(&format!("{placeholder:?}")); } } } @@ -497,7 +497,7 @@ fn region_value_str(elements: impl IntoIterator) -> String fn push_location_range(str: &mut String, location1: Location, location2: Location) { if location1 == location2 { - str.push_str(&format!("{:?}", location1)); + str.push_str(&format!("{location1:?}")); } else { assert_eq!(location1.block, location2.block); str.push_str(&format!( diff --git a/compiler/rustc_borrowck/src/renumber.rs b/compiler/rustc_borrowck/src/renumber.rs index 4389d2b60bc55..4c69ea843c724 100644 --- a/compiler/rustc_borrowck/src/renumber.rs +++ b/compiler/rustc_borrowck/src/renumber.rs @@ -6,7 +6,7 @@ use rustc_infer::infer::NllRegionVariableOrigin; use rustc_middle::mir::visit::{MutVisitor, TyContext}; use rustc_middle::mir::Constant; use rustc_middle::mir::{Body, Location, Promoted}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc_span::{Span, Symbol}; @@ -94,10 +94,10 @@ impl<'a, 'tcx> MutVisitor<'tcx> for RegionRenumberer<'a, 'tcx> { } #[instrument(skip(self), level = "debug")] - fn visit_substs(&mut self, substs: &mut SubstsRef<'tcx>, location: Location) { - *substs = self.renumber_regions(*substs, || RegionCtxt::Location(location)); + fn visit_args(&mut self, args: &mut GenericArgsRef<'tcx>, location: Location) { + *args = self.renumber_regions(*args, || RegionCtxt::Location(location)); - debug!(?substs); + debug!(?args); } #[instrument(skip(self), level = "debug")] diff --git a/compiler/rustc_borrowck/src/session_diagnostics.rs b/compiler/rustc_borrowck/src/session_diagnostics.rs index fceae5bb3ffe0..d1d8cfa74aa7c 100644 --- a/compiler/rustc_borrowck/src/session_diagnostics.rs +++ b/compiler/rustc_borrowck/src/session_diagnostics.rs @@ -398,7 +398,7 @@ pub(crate) enum CaptureReasonSuggest<'tcx> { #[suggestion( borrowck_suggest_create_freash_reborrow, applicability = "maybe-incorrect", - code = "as_mut().", + code = ".as_mut()", style = "verbose" )] FreshReborrow { diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index 95dcc8d4b1916..16f5e68a06f55 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -1,12 +1,12 @@ use std::fmt; +use rustc_errors::ErrorGuaranteed; use rustc_infer::infer::canonical::Canonical; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeFoldable}; use rustc_span::def_id::DefId; use rustc_span::Span; use rustc_trait_selection::traits::query::type_op::{self, TypeOpOutput}; -use rustc_trait_selection::traits::query::{Fallible, NoSolution}; use rustc_trait_selection::traits::ObligationCause; use crate::diagnostics::{ToUniverseInfo, UniverseInfo}; @@ -30,14 +30,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { locations: Locations, category: ConstraintCategory<'tcx>, op: Op, - ) -> Fallible + ) -> Result where Op: type_op::TypeOp<'tcx, Output = R>, Op::ErrorInfo: ToUniverseInfo<'tcx>, { let old_universe = self.infcx.universe(); - let TypeOpOutput { output, constraints, error_info } = op.fully_perform(self.infcx)?; + let TypeOpOutput { output, constraints, error_info } = + op.fully_perform(self.infcx, locations.span(self.body))?; debug!(?output, ?constraints); @@ -88,11 +89,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { category: ConstraintCategory<'tcx>, ) { self.prove_predicate( - ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { - trait_ref, - constness: ty::BoundConstness::NotConst, - polarity: ty::ImplPolarity::Positive, - }))), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::Trait( + ty::TraitPredicate { trait_ref, polarity: ty::ImplPolarity::Positive }, + ))), locations, category, ); @@ -135,14 +134,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ) { let param_env = self.param_env; let predicate = predicate.to_predicate(self.tcx()); - self.fully_perform_op( + let _: Result<_, ErrorGuaranteed> = self.fully_perform_op( locations, category, param_env.and(type_op::prove_predicate::ProvePredicate::new(predicate)), - ) - .unwrap_or_else(|NoSolution| { - span_mirbug!(self, NoSolution, "could not prove {:?}", predicate); - }) + ); } pub(super) fn normalize(&mut self, value: T, location: impl NormalizeLocation) -> T @@ -163,15 +159,12 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { T: type_op::normalize::Normalizable<'tcx> + fmt::Display + Copy + 'tcx, { let param_env = self.param_env; - self.fully_perform_op( + let result: Result<_, ErrorGuaranteed> = self.fully_perform_op( location.to_locations(), category, param_env.and(type_op::normalize::Normalize::new(value)), - ) - .unwrap_or_else(|NoSolution| { - span_mirbug!(self, NoSolution, "failed to normalize `{:?}`", value); - value - }) + ); + result.unwrap_or(value) } #[instrument(skip(self), level = "debug")] @@ -181,18 +174,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { user_ty: ty::UserType<'tcx>, span: Span, ) { - self.fully_perform_op( + let _: Result<_, ErrorGuaranteed> = self.fully_perform_op( Locations::All(span), ConstraintCategory::Boring, self.param_env.and(type_op::ascribe_user_type::AscribeUserType::new(mir_ty, user_ty)), - ) - .unwrap_or_else(|err| { - span_mirbug!( - self, - span, - "ascribe_user_type `{mir_ty:?}=={user_ty:?}` failed with `{err:?}`", - ); - }); + ); } /// *Incorrectly* skips the WF checks we normally do in `ascribe_user_type`. @@ -219,7 +205,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let cause = ObligationCause::dummy_with_span(span); let param_env = self.param_env; - self.fully_perform_op( + let _: Result<_, ErrorGuaranteed> = self.fully_perform_op( Locations::All(span), ConstraintCategory::Boring, type_op::custom::CustomTypeOp::new( @@ -230,13 +216,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { }, "ascribe_user_type_skip_wf", ), - ) - .unwrap_or_else(|err| { - span_mirbug!( - self, - span, - "ascribe_user_type_skip_wf `{mir_ty:?}=={user_ty:?}` failed with `{err:?}`", - ); - }); + ); } } diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 71eae7b27d1db..21d8026e17089 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -5,7 +5,7 @@ use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelega use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound}; use rustc_infer::infer::{self, InferCtxt, SubregionOrigin}; use rustc_middle::mir::{ClosureOutlivesSubject, ClosureRegionRequirements, ConstraintCategory}; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::GenericArgKind; use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{TypeFoldable, TypeVisitableExt}; use rustc_span::{Span, DUMMY_SP}; @@ -89,20 +89,20 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { /// Given an instance of the closure type, this method instantiates the "extra" requirements /// that we computed for the closure. This has the effect of adding new outlives obligations - /// to existing region variables in `closure_substs`. + /// to existing region variables in `closure_args`. #[instrument(skip(self), level = "debug")] pub fn apply_closure_requirements( &mut self, closure_requirements: &ClosureRegionRequirements<'tcx>, closure_def_id: DefId, - closure_substs: ty::SubstsRef<'tcx>, + closure_args: ty::GenericArgsRef<'tcx>, ) { - // Extract the values of the free regions in `closure_substs` + // Extract the values of the free regions in `closure_args` // into a vector. These are the regions that we will be // relating to one another. let closure_mapping = &UniversalRegions::closure_mapping( self.tcx, - closure_substs, + closure_args, closure_requirements.num_external_vids, closure_def_id.expect_local(), ); diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs index bd01c0b504cb5..f22851d76b31a 100644 --- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs +++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs @@ -8,7 +8,7 @@ use rustc_infer::infer::InferCtxt; use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::query::OutlivesBound; use rustc_middle::ty::{self, RegionVid, Ty}; -use rustc_span::Span; +use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::traits::query::type_op::{self, TypeOp}; use std::rc::Rc; use type_op::TypeOpOutput; @@ -243,18 +243,11 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = self .param_env .and(type_op::normalize::Normalize::new(ty)) - .fully_perform(self.infcx) - .unwrap_or_else(|_| { - let guar = self - .infcx - .tcx - .sess - .delay_span_bug(span, format!("failed to normalize {:?}", ty)); - TypeOpOutput { - output: self.infcx.tcx.ty_error(guar), - constraints: None, - error_info: None, - } + .fully_perform(self.infcx, span) + .unwrap_or_else(|guar| TypeOpOutput { + output: Ty::new_error(self.infcx.tcx, guar), + constraints: None, + error_info: None, }); if let Some(c) = constraints_normalize { constraints.push(c) @@ -324,7 +317,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { let TypeOpOutput { output: bounds, constraints, .. } = self .param_env .and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty }) - .fully_perform(self.infcx) + .fully_perform(self.infcx, DUMMY_SP) .unwrap_or_else(|_| bug!("failed to compute implied bounds {:?}", ty)); debug!(?bounds, ?constraints); self.add_outlives_bounds(bounds); diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs index a06d4bcc6c7ae..eec886b7be48f 100644 --- a/compiler/rustc_borrowck/src/type_check/input_output.rs +++ b/compiler/rustc_borrowck/src/type_check/input_output.rs @@ -124,21 +124,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // Return types are a bit more complex. They may contain opaque `impl Trait` types. let mir_output_ty = body.local_decls[RETURN_PLACE].ty; let output_span = body.local_decls[RETURN_PLACE].source_info.span; - if let Err(terr) = self.eq_types( - normalized_output_ty, - mir_output_ty, - Locations::All(output_span), - ConstraintCategory::BoringNoLocation, - ) { - span_mirbug!( - self, - Location::START, - "equate_inputs_and_outputs: `{:?}=={:?}` failed with `{:?}`", - normalized_output_ty, - mir_output_ty, - terr - ); - }; + self.equate_normalized_input_or_output(normalized_output_ty, mir_output_ty, output_span); } #[instrument(skip(self), level = "debug")] diff --git a/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs b/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs index b344ab46adbde..c621df37106b2 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/polonius.rs @@ -2,7 +2,7 @@ use crate::def_use::{self, DefUse}; use crate::location::{LocationIndex, LocationTable}; use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{Body, Local, Location, Place}; -use rustc_middle::ty::subst::GenericArg; +use rustc_middle::ty::GenericArg; use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex}; use super::TypeChecker; @@ -20,7 +20,7 @@ struct UseFactsExtractor<'me, 'tcx> { } // A Visitor to walk through the MIR and extract point-wise facts -impl UseFactsExtractor<'_, '_> { +impl<'tcx> UseFactsExtractor<'_, 'tcx> { fn location_to_index(&self, location: Location) -> LocationIndex { self.location_table.mid_index(location) } @@ -45,7 +45,7 @@ impl UseFactsExtractor<'_, '_> { self.path_accessed_at_base.push((path, self.location_to_index(location))); } - fn place_to_mpi(&self, place: &Place<'_>) -> Option { + fn place_to_mpi(&self, place: &Place<'tcx>) -> Option { match self.move_data.rev_lookup.find(place.as_ref()) { LookupResult::Exact(mpi) => Some(mpi), LookupResult::Parent(mmpi) => mmpi, diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 9731b10aa99d9..5702d39db32d6 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -3,8 +3,9 @@ use rustc_index::bit_set::HybridBitSet; use rustc_index::interval::IntervalSet; use rustc_infer::infer::canonical::QueryRegionConstraints; use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location}; +use rustc_middle::traits::query::DropckOutlivesResult; use rustc_middle::ty::{Ty, TyCtxt, TypeVisitable, TypeVisitableExt}; -use rustc_trait_selection::traits::query::dropck_outlives::DropckOutlivesResult; +use rustc_span::DUMMY_SP; use rustc_trait_selection::traits::query::type_op::outlives::DropckOutlives; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; use std::rc::Rc; @@ -160,8 +161,12 @@ impl<'me, 'typeck, 'flow, 'tcx> LivenessResults<'me, 'typeck, 'flow, 'tcx> { } } - // Runs dropck for locals whose liveness isn't relevant. This is - // necessary to eagerly detect unbound recursion during drop glue computation. + /// Runs dropck for locals whose liveness isn't relevant. This is + /// necessary to eagerly detect unbound recursion during drop glue computation. + /// + /// These are all the locals which do not potentially reference a region local + /// to this body. Locals which only reference free regions are always drop-live + /// and can therefore safely be dropped. fn dropck_boring_locals(&mut self, boring_locals: Vec) { for local in boring_locals { let local_ty = self.cx.body.local_decls[local].ty; @@ -568,10 +573,15 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> { ) -> DropData<'tcx> { debug!("compute_drop_data(dropped_ty={:?})", dropped_ty,); - let param_env = typeck.param_env; - let TypeOpOutput { output, constraints, .. } = - param_env.and(DropckOutlives::new(dropped_ty)).fully_perform(typeck.infcx).unwrap(); - - DropData { dropck_result: output, region_constraint_data: constraints } + match typeck + .param_env + .and(DropckOutlives::new(dropped_ty)) + .fully_perform(typeck.infcx, DUMMY_SP) + { + Ok(TypeOpOutput { output, constraints, .. }) => { + DropData { dropck_result: output, region_constraint_data: constraints } + } + Err(_) => DropData { dropck_result: Default::default(), region_constraint_data: None }, + } } } diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index cf204cff6b3a7..50d875dfae99a 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -7,9 +7,9 @@ use std::{fmt, iter, mem}; use either::Either; -use hir::OpaqueTyOrigin; use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; +use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; @@ -26,14 +26,16 @@ use rustc_middle::mir::tcx::PlaceTy; use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::AssertKind; use rustc_middle::mir::*; -use rustc_middle::ty::adjustment::PointerCast; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::ObligationCause; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::cast::CastTy; -use rustc_middle::ty::subst::{SubstsRef, UserSubsts}; use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{ self, Binder, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, Dynamic, OpaqueHiddenType, OpaqueTypeKey, RegionVid, Ty, TyCtxt, UserType, UserTypeAnnotationIndex, }; +use rustc_middle::ty::{GenericArgsRef, UserArgs}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::symbol::sym; use rustc_span::{Span, DUMMY_SP}; @@ -41,14 +43,13 @@ use rustc_target::abi::{FieldIdx, FIRST_VARIANT}; use rustc_trait_selection::traits::query::type_op::custom::scrape_region_constraints; use rustc_trait_selection::traits::query::type_op::custom::CustomTypeOp; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; -use rustc_trait_selection::traits::query::Fallible; + use rustc_trait_selection::traits::PredicateObligation; use rustc_mir_dataflow::impls::MaybeInitializedPlaces; use rustc_mir_dataflow::move_paths::MoveData; use rustc_mir_dataflow::ResultsCursor; -use crate::renumber::RegionCtxt; use crate::session_diagnostics::MoveUnsized; use crate::{ borrow_set::BorrowSet, @@ -137,7 +138,7 @@ pub(crate) fn type_check<'mir, 'tcx>( upvars: &[Upvar<'tcx>], use_polonius: bool, ) -> MirTypeckResults<'tcx> { - let implicit_region_bound = infcx.tcx.mk_re_var(universal_regions.fr_fn_body); + let implicit_region_bound = ty::Region::new_var(infcx.tcx, universal_regions.fr_fn_body); let mut constraints = MirTypeckRegionConstraints { placeholder_indices: PlaceholderIndices::default(), placeholder_index_to_region: IndexVec::default(), @@ -186,10 +187,7 @@ pub(crate) fn type_check<'mir, 'tcx>( // FIXME(-Ztrait-solver=next): A bit dubious that we're only registering // predefined opaques in the typeck root. - // FIXME(-Ztrait-solver=next): This is also totally wrong for TAITs, since - // the HIR typeck map defining usages back to their definition params, - // they won't actually match up with the usages in this body... - if infcx.tcx.trait_solver_next() && !infcx.tcx.is_typeck_child(body.source.def_id()) { + if infcx.next_trait_solver() && !infcx.tcx.is_typeck_child(body.source.def_id()) { checker.register_predefined_opaques_in_new_solver(); } @@ -216,24 +214,22 @@ pub(crate) fn type_check<'mir, 'tcx>( let opaque_type_values = opaque_type_values .into_iter() .map(|(opaque_type_key, decl)| { - checker - .fully_perform_op( - Locations::All(body.span), - ConstraintCategory::OpaqueType, - CustomTypeOp::new( - |ocx| { - ocx.infcx.register_member_constraints( - param_env, - opaque_type_key, - decl.hidden_type.ty, - decl.hidden_type.span, - ); - Ok(()) - }, - "opaque_type_map", - ), - ) - .unwrap(); + let _: Result<_, ErrorGuaranteed> = checker.fully_perform_op( + Locations::All(body.span), + ConstraintCategory::OpaqueType, + CustomTypeOp::new( + |ocx| { + ocx.infcx.register_member_constraints( + param_env, + opaque_type_key, + decl.hidden_type.ty, + decl.hidden_type.span, + ); + Ok(()) + }, + "opaque_type_map", + ), + ); let mut hidden_type = infcx.resolve_vars_if_possible(decl.hidden_type); trace!("finalized opaque type {:?} to {:#?}", opaque_type_key, hidden_type.ty.kind()); if hidden_type.has_non_region_infer() { @@ -241,10 +237,10 @@ pub(crate) fn type_check<'mir, 'tcx>( decl.hidden_type.span, format!("could not resolve {:#?}", hidden_type.ty.kind()), ); - hidden_type.ty = infcx.tcx.ty_error(reported); + hidden_type.ty = Ty::new_error(infcx.tcx, reported); } - (opaque_type_key, (hidden_type, decl.origin)) + (opaque_type_key, hidden_type) }) .collect(); @@ -393,15 +389,12 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> { } else { self.cx.ascribe_user_type( constant.literal.ty(), - UserType::TypeOf( - uv.def, - UserSubsts { substs: uv.substs, user_self_ty: None }, - ), + UserType::TypeOf(uv.def, UserArgs { args: uv.args, user_self_ty: None }), locations.span(&self.cx.body), ); } } else if let Some(static_def_id) = constant.check_static_ptr(tcx) { - let unnormalized_ty = tcx.type_of(static_def_id).subst_identity(); + let unnormalized_ty = tcx.type_of(static_def_id).instantiate_identity(); let normalized_ty = self.cx.normalize(unnormalized_ty, locations); let literal_ty = constant.literal.ty().builtin_deref(true).unwrap().ty; @@ -415,19 +408,13 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> { } } - if let ty::FnDef(def_id, substs) = *constant.literal.ty().kind() { - // const_trait_impl: use a non-const param env when checking that a FnDef type is well formed. - // this is because the well-formedness of the function does not need to be proved to have `const` - // impls for trait bounds. - let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs); - let prev = self.cx.param_env; - self.cx.param_env = prev.without_const(); + if let ty::FnDef(def_id, args) = *constant.literal.ty().kind() { + let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, args); self.cx.normalize_and_prove_instantiated_predicates( def_id, instantiated_predicates, locations, ); - self.cx.param_env = prev; } } } @@ -480,9 +467,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> { fn visit_body(&mut self, body: &Body<'tcx>) { self.sanitize_type(&"return type", body.return_ty()); - for local_decl in &body.local_decls { - self.sanitize_type(local_decl, local_decl.ty); - } + // The types of local_decls are checked above which is called in super_body. self.super_body(body); } } @@ -513,20 +498,19 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { /// Checks that the types internal to the `place` match up with /// what would be expected. + #[instrument(level = "debug", skip(self, location), ret)] fn sanitize_place( &mut self, place: &Place<'tcx>, location: Location, context: PlaceContext, ) -> PlaceTy<'tcx> { - debug!("sanitize_place: {:?}", place); - let mut place_ty = PlaceTy::from_ty(self.body().local_decls[place.local].ty); for elem in place.projection.iter() { if place_ty.variant_index.is_none() { if let Err(guar) = place_ty.ty.error_reported() { - return PlaceTy::from_ty(self.tcx().ty_error(guar)); + return PlaceTy::from_ty(Ty::new_error(self.tcx(), guar)); } } place_ty = self.sanitize_projection(place_ty, elem, place, location, context); @@ -623,7 +607,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { } } - #[instrument(skip(self), level = "debug")] + #[instrument(skip(self, location), ret, level = "debug")] fn sanitize_projection( &mut self, base: PlaceTy<'tcx>, @@ -632,7 +616,6 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { location: Location, context: PlaceContext, ) -> PlaceTy<'tcx> { - debug!("sanitize_projection: {:?} {:?} {:?}", base, pi, place); let tcx = self.tcx(); let base_ty = base.ty; match pi { @@ -662,7 +645,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { PlaceTy::from_ty(match base_ty.kind() { ty::Array(inner, _) => { assert!(!from_end, "array subslices should not use from_end"); - tcx.mk_array(*inner, to - from) + Ty::new_array(tcx, *inner, to - from) } ty::Slice(..) => { assert!(from_end, "slice subslices should use from_end"); @@ -672,7 +655,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { }) } ProjectionElem::Downcast(maybe_name, index) => match base_ty.kind() { - ty::Adt(adt_def, _substs) if adt_def.is_enum() => { + ty::Adt(adt_def, _args) if adt_def.is_enum() => { if index.as_usize() >= adt_def.variants().len() { PlaceTy::from_ty(span_mirbug_and_err!( self, @@ -755,7 +738,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { } fn error(&mut self) -> Ty<'tcx> { - self.tcx().ty_error_misc() + Ty::new_misc_error(self.tcx()) } fn get_ambient_variance(&self, context: PlaceContext) -> ty::Variance { @@ -766,8 +749,8 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { PlaceContext::MutatingUse(_) => ty::Invariant, PlaceContext::NonUse(StorageDead | StorageLive | VarDebugInfo) => ty::Invariant, PlaceContext::NonMutatingUse( - Inspect | Copy | Move | PlaceMention | SharedBorrow | ShallowBorrow | UniqueBorrow - | AddressOf | Projection, + Inspect | Copy | Move | PlaceMention | SharedBorrow | ShallowBorrow | AddressOf + | Projection, ) => ty::Covariant, PlaceContext::NonUse(AscribeUserTy(variance)) => variance, } @@ -782,16 +765,16 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { ) -> Result, FieldAccessError> { let tcx = self.tcx(); - let (variant, substs) = match base_ty { + let (variant, args) = match base_ty { PlaceTy { ty, variant_index: Some(variant_index) } => match *ty.kind() { - ty::Adt(adt_def, substs) => (adt_def.variant(variant_index), substs), - ty::Generator(def_id, substs, _) => { - let mut variants = substs.as_generator().state_tys(def_id, tcx); + ty::Adt(adt_def, args) => (adt_def.variant(variant_index), args), + ty::Generator(def_id, args, _) => { + let mut variants = args.as_generator().state_tys(def_id, tcx); let Some(mut variant) = variants.nth(variant_index.into()) else { bug!( "variant_index of generator out of range: {:?}/{:?}", variant_index, - substs.as_generator().state_tys(def_id, tcx).count() + args.as_generator().state_tys(def_id, tcx).count() ); }; return match variant.nth(field.index()) { @@ -802,29 +785,24 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { _ => bug!("can't have downcast of non-adt non-generator type"), }, PlaceTy { ty, variant_index: None } => match *ty.kind() { - ty::Adt(adt_def, substs) if !adt_def.is_enum() => { - (adt_def.variant(FIRST_VARIANT), substs) + ty::Adt(adt_def, args) if !adt_def.is_enum() => { + (adt_def.variant(FIRST_VARIANT), args) } - ty::Closure(_, substs) => { - return match substs - .as_closure() - .tupled_upvars_ty() - .tuple_fields() - .get(field.index()) - { + ty::Closure(_, args) => { + return match args.as_closure().upvar_tys().get(field.index()) { Some(&ty) => Ok(ty), None => Err(FieldAccessError::OutOfRange { - field_count: substs.as_closure().upvar_tys().count(), + field_count: args.as_closure().upvar_tys().len(), }), }; } - ty::Generator(_, substs, _) => { + ty::Generator(_, args, _) => { // Only prefix fields (upvars and current state) are // accessible without a variant index. - return match substs.as_generator().prefix_tys().nth(field.index()) { - Some(ty) => Ok(ty), + return match args.as_generator().prefix_tys().get(field.index()) { + Some(ty) => Ok(*ty), None => Err(FieldAccessError::OutOfRange { - field_count: substs.as_generator().prefix_tys().count(), + field_count: args.as_generator().prefix_tys().len(), }), }; } @@ -846,7 +824,7 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { }; if let Some(field) = variant.fields.get(field) { - Ok(self.cx.normalize(field.ty(tcx, substs), location)) + Ok(self.cx.normalize(field.ty(tcx, args), location)) } else { Err(FieldAccessError::OutOfRange { field_count: variant.fields.len() }) } @@ -883,8 +861,7 @@ struct BorrowCheckContext<'a, 'tcx> { pub(crate) struct MirTypeckResults<'tcx> { pub(crate) constraints: MirTypeckRegionConstraints<'tcx>, pub(crate) universal_region_relations: Frozen>, - pub(crate) opaque_type_values: - FxIndexMap, (OpaqueHiddenType<'tcx>, OpaqueTyOrigin)>, + pub(crate) opaque_type_values: FxIndexMap, OpaqueHiddenType<'tcx>>, } /// A collection of region constraints that must be satisfied for the @@ -1042,16 +1019,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .typeck(self.body.source.def_id().expect_local()) .concrete_opaque_types .iter() - .map(|(&def_id, &hidden_ty)| { - let substs = ty::InternalSubsts::identity_for_item(self.infcx.tcx, def_id); - (ty::OpaqueTypeKey { def_id, substs }, hidden_ty) - }) + .map(|(k, v)| (*k, *v)) .collect(); let renumbered_opaques = self.infcx.tcx.fold_regions(opaques, |_, _| { - self.infcx.next_nll_region_var( + self.infcx.next_nll_region_var_in_universe( NllRegionVariableOrigin::Existential { from_forall: false }, - || RegionCtxt::Unknown, + ty::UniverseIndex::ROOT, ) }); @@ -1061,15 +1035,29 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::OpaqueType, CustomTypeOp::new( |ocx| { - for (key, hidden_ty) in renumbered_opaques { - ocx.register_infer_ok_obligations( - ocx.infcx.register_hidden_type_in_new_solver( - key, - param_env, - hidden_ty.ty, - )?, + let mut obligations = Vec::new(); + for (opaque_type_key, hidden_ty) in renumbered_opaques { + let cause = ObligationCause::dummy(); + ocx.infcx.insert_hidden_type( + opaque_type_key, + &cause, + param_env, + hidden_ty.ty, + true, + &mut obligations, + )?; + + ocx.infcx.add_item_bounds_for_hidden_type( + opaque_type_key.def_id.to_def_id(), + opaque_type_key.args, + cause, + param_env, + hidden_ty.ty, + &mut obligations, ); } + + ocx.register_obligations(obligations); Ok(()) }, "register pre-defined opaques", @@ -1134,7 +1122,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { sup: Ty<'tcx>, locations: Locations, category: ConstraintCategory<'tcx>, - ) -> Fallible<()> { + ) -> Result<(), NoSolution> { // Use this order of parameters because the sup type is usually the // "expected" type in diagnostics. self.relate_types(sup, ty::Variance::Contravariant, sub, locations, category) @@ -1147,7 +1135,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { found: Ty<'tcx>, locations: Locations, category: ConstraintCategory<'tcx>, - ) -> Fallible<()> { + ) -> Result<(), NoSolution> { self.relate_types(expected, ty::Variance::Invariant, found, locations, category) } @@ -1159,7 +1147,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { user_ty: &UserTypeProjection, locations: Locations, category: ConstraintCategory<'tcx>, - ) -> Fallible<()> { + ) -> Result<(), NoSolution> { let annotated_type = self.user_type_annotations[user_ty.base].inferred_ty; trace!(?annotated_type); let mut curr_projected_ty = PlaceTy::from_ty(annotated_type); @@ -1366,7 +1354,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } // FIXME: check the values } - TerminatorKind::Call { func, args, destination, from_hir_call, target, .. } => { + TerminatorKind::Call { func, args, destination, call_source, target, .. } => { self.check_operand(func, term_location); for arg in args { self.check_operand(arg, term_location); @@ -1415,9 +1403,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // // See #91068 for an example. self.prove_predicates( - sig.inputs_and_output - .iter() - .map(|ty| ty::Binder::dummy(ty::PredicateKind::WellFormed(ty.into()))), + sig.inputs_and_output.iter().map(|ty| { + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + ty.into(), + ))) + }), term_location.to_locations(), ConstraintCategory::Boring, ); @@ -1440,7 +1430,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .add_element(region_vid, term_location); } - self.check_call_inputs(body, term, &sig, args, term_location, *from_hir_call); + self.check_call_inputs(body, term, &sig, args, term_location, *call_source); } TerminatorKind::Assert { cond, msg, .. } => { self.check_operand(cond, term_location); @@ -1567,7 +1557,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { sig: &ty::FnSig<'tcx>, args: &[Operand<'tcx>], term_location: Location, - from_hir_call: bool, + call_source: CallSource, ) { debug!("check_call_inputs({:?}, {:?})", sig, args); if args.len() < sig.inputs().len() || (args.len() > sig.inputs().len() && !sig.c_variadic) { @@ -1585,7 +1575,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let op_arg_ty = op_arg.ty(body, self.tcx()); let op_arg_ty = self.normalize(op_arg_ty, term_location); - let category = if from_hir_call { + let category = if call_source.from_hir_call() { ConstraintCategory::CallArgument(self.infcx.tcx.erase_regions(func_ty)) } else { ConstraintCategory::Boring @@ -1764,32 +1754,32 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let tcx = self.tcx(); match *ak { - AggregateKind::Adt(adt_did, variant_index, substs, _, active_field_index) => { + AggregateKind::Adt(adt_did, variant_index, args, _, active_field_index) => { let def = tcx.adt_def(adt_did); let variant = &def.variant(variant_index); let adj_field_index = active_field_index.unwrap_or(field_index); if let Some(field) = variant.fields.get(adj_field_index) { - Ok(self.normalize(field.ty(tcx, substs), location)) + Ok(self.normalize(field.ty(tcx, args), location)) } else { Err(FieldAccessError::OutOfRange { field_count: variant.fields.len() }) } } - AggregateKind::Closure(_, substs) => { - match substs.as_closure().upvar_tys().nth(field_index.as_usize()) { - Some(ty) => Ok(ty), + AggregateKind::Closure(_, args) => { + match args.as_closure().upvar_tys().get(field_index.as_usize()) { + Some(ty) => Ok(*ty), None => Err(FieldAccessError::OutOfRange { - field_count: substs.as_closure().upvar_tys().count(), + field_count: args.as_closure().upvar_tys().len(), }), } } - AggregateKind::Generator(_, substs, _) => { + AggregateKind::Generator(_, args, _) => { // It doesn't make sense to look at a field beyond the prefix; // these require a variant index, and are not initialized in // aggregate rvalues. - match substs.as_generator().prefix_tys().nth(field_index.as_usize()) { - Some(ty) => Ok(ty), + match args.as_generator().prefix_tys().get(field_index.as_usize()) { + Some(ty) => Ok(*ty), None => Err(FieldAccessError::OutOfRange { - field_count: substs.as_generator().prefix_tys().count(), + field_count: args.as_generator().prefix_tys().len(), }), } } @@ -1815,8 +1805,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let def_id = uv.def; if tcx.def_kind(def_id) == DefKind::InlineConst { let def_id = def_id.expect_local(); - let predicates = - self.prove_closure_bounds(tcx, def_id, uv.substs, location); + let predicates = self.prove_closure_bounds(tcx, def_id, uv.args, location); self.normalize_and_prove_instantiated_predicates( def_id.to_def_id(), predicates, @@ -1846,7 +1835,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { let array_ty = rvalue.ty(body.local_decls(), tcx); self.prove_predicate( - ty::PredicateKind::WellFormed(array_ty.into()), + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(array_ty.into())), Locations::Single(location), ConstraintCategory::Boring, ); @@ -1902,7 +1891,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { self.check_operand(op, location); match cast_kind { - CastKind::Pointer(PointerCast::ReifyFnPointer) => { + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer) => { let fn_sig = op.ty(body, tcx).fn_sig(tcx); // The type that we see in the fcx is like @@ -1912,7 +1901,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // and hence may contain unnormalized results. let fn_sig = self.normalize(fn_sig, location); - let ty_fn_ptr_from = tcx.mk_fn_ptr(fn_sig); + let ty_fn_ptr_from = Ty::new_fn_ptr(tcx, fn_sig); if let Err(terr) = self.eq_types( *ty, @@ -1931,12 +1920,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } - CastKind::Pointer(PointerCast::ClosureFnPointer(unsafety)) => { + CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(unsafety)) => { let sig = match op.ty(body, tcx).kind() { - ty::Closure(_, substs) => substs.as_closure().sig(), + ty::Closure(_, args) => args.as_closure().sig(), _ => bug!(), }; - let ty_fn_ptr_from = tcx.mk_fn_ptr(tcx.signature_unclosure(sig, *unsafety)); + let ty_fn_ptr_from = + Ty::new_fn_ptr(tcx, tcx.signature_unclosure(sig, *unsafety)); if let Err(terr) = self.eq_types( *ty, @@ -1955,7 +1945,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } - CastKind::Pointer(PointerCast::UnsafeFnPointer) => { + CastKind::PointerCoercion(PointerCoercion::UnsafeFnPointer) => { let fn_sig = op.ty(body, tcx).fn_sig(tcx); // The type that we see in the fcx is like @@ -1984,7 +1974,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } - CastKind::Pointer(PointerCast::Unsize) => { + CastKind::PointerCoercion(PointerCoercion::Unsize) => { let &ty = ty; let trait_ref = ty::TraitRef::from_lang_item( tcx, @@ -2019,10 +2009,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::Cast, ); - let outlives_predicate = - tcx.mk_predicate(Binder::dummy(ty::PredicateKind::Clause( - ty::Clause::TypeOutlives(ty::OutlivesPredicate(self_ty, *region)), - ))); + let outlives_predicate = tcx.mk_predicate(Binder::dummy( + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives( + ty::OutlivesPredicate(self_ty, *region), + )), + )); self.prove_predicate( outlives_predicate, location.to_locations(), @@ -2030,29 +2021,17 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } - CastKind::Pointer(PointerCast::MutToConstPointer) => { - let ty::RawPtr(ty::TypeAndMut { - ty: ty_from, - mutbl: hir::Mutability::Mut, - }) = op.ty(body, tcx).kind() else { - span_mirbug!( - self, - rvalue, - "unexpected base type for cast {:?}", - ty, - ); + CastKind::PointerCoercion(PointerCoercion::MutToConstPointer) => { + let ty::RawPtr(ty::TypeAndMut { ty: ty_from, mutbl: hir::Mutability::Mut }) = + op.ty(body, tcx).kind() + else { + span_mirbug!(self, rvalue, "unexpected base type for cast {:?}", ty,); return; }; - let ty::RawPtr(ty::TypeAndMut { - ty: ty_to, - mutbl: hir::Mutability::Not, - }) = ty.kind() else { - span_mirbug!( - self, - rvalue, - "unexpected target type for cast {:?}", - ty, - ); + let ty::RawPtr(ty::TypeAndMut { ty: ty_to, mutbl: hir::Mutability::Not }) = + ty.kind() + else { + span_mirbug!(self, rvalue, "unexpected target type for cast {:?}", ty,); return; }; if let Err(terr) = self.sub_types( @@ -2072,7 +2051,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } - CastKind::Pointer(PointerCast::ArrayToPointer) => { + CastKind::PointerCoercion(PointerCoercion::ArrayToPointer) => { let ty_from = op.ty(body, tcx); let opt_ty_elem_mut = match ty_from.kind() { @@ -2495,7 +2474,6 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { location, borrow_region, borrowed_place ); - let mut cursor = borrowed_place.projection.as_ref(); let tcx = self.infcx.tcx; let field = path_utils::is_upvar_field_projection( tcx, @@ -2509,14 +2487,12 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::Boring }; - while let [proj_base @ .., elem] = cursor { - cursor = proj_base; - + for (base, elem) in borrowed_place.as_ref().iter_projections().rev() { debug!("add_reborrow_constraint - iteration {:?}", elem); match elem { ProjectionElem::Deref => { - let base_ty = Place::ty_from(borrowed_place.local, proj_base, body, tcx).ty; + let base_ty = base.ty(body, tcx).ty; debug!("add_reborrow_constraint - base_ty = {:?}", base_ty); match base_ty.kind() { @@ -2598,8 +2574,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); let (def_id, instantiated_predicates) = match *aggregate_kind { - AggregateKind::Adt(adt_did, _, substs, _, _) => { - (adt_did, tcx.predicates_of(adt_did).instantiate(tcx, substs)) + AggregateKind::Adt(adt_did, _, args, _, _) => { + (adt_did, tcx.predicates_of(adt_did).instantiate(tcx, args)) } // For closures, we have some **extra requirements** we @@ -2621,9 +2597,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { // desugaring. A closure gets desugared to a struct, and // these extra requirements are basically like where // clauses on the struct. - AggregateKind::Closure(def_id, substs) - | AggregateKind::Generator(def_id, substs, _) => { - (def_id, self.prove_closure_bounds(tcx, def_id.expect_local(), substs, location)) + AggregateKind::Closure(def_id, args) | AggregateKind::Generator(def_id, args, _) => { + (def_id, self.prove_closure_bounds(tcx, def_id.expect_local(), args, location)) } AggregateKind::Array(_) | AggregateKind::Tuple => { @@ -2642,7 +2617,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { &mut self, tcx: TyCtxt<'tcx>, def_id: LocalDefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, location: Location, ) -> ty::InstantiatedPredicates<'tcx> { if let Some(closure_requirements) = &tcx.mir_borrowck(def_id).closure_requirements { @@ -2660,26 +2635,26 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .apply_closure_requirements( &closure_requirements, def_id.to_def_id(), - substs, + args, ); } - // Now equate closure substs to regions inherited from `typeck_root_def_id`. Fixes #98589. + // Now equate closure args to regions inherited from `typeck_root_def_id`. Fixes #98589. let typeck_root_def_id = tcx.typeck_root_def_id(self.body.source.def_id()); - let typeck_root_substs = ty::InternalSubsts::identity_for_item(tcx, typeck_root_def_id); + let typeck_root_args = ty::GenericArgs::identity_for_item(tcx, typeck_root_def_id); - let parent_substs = match tcx.def_kind(def_id) { - DefKind::Closure => substs.as_closure().parent_substs(), - DefKind::Generator => substs.as_generator().parent_substs(), - DefKind::InlineConst => substs.as_inline_const().parent_substs(), + let parent_args = match tcx.def_kind(def_id) { + DefKind::Closure => args.as_closure().parent_args(), + DefKind::Generator => args.as_generator().parent_args(), + DefKind::InlineConst => args.as_inline_const().parent_args(), other => bug!("unexpected item {:?}", other), }; - let parent_substs = tcx.mk_substs(parent_substs); + let parent_args = tcx.mk_args(parent_args); - assert_eq!(typeck_root_substs.len(), parent_substs.len()); - if let Err(_) = self.eq_substs( - typeck_root_substs, - parent_substs, + assert_eq!(typeck_root_args.len(), parent_args.len()); + if let Err(_) = self.eq_args( + typeck_root_args, + parent_args, location.to_locations(), ConstraintCategory::BoringNoLocation, ) { @@ -2687,12 +2662,12 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { self, def_id, "could not relate closure to parent {:?} != {:?}", - typeck_root_substs, - parent_substs + typeck_root_args, + parent_args ); } - tcx.predicates_of(def_id).instantiate(tcx, substs) + tcx.predicates_of(def_id).instantiate(tcx, args) } #[instrument(skip(self, body), level = "debug")] @@ -2755,11 +2730,20 @@ impl<'tcx> TypeOp<'tcx> for InstantiateOpaqueType<'tcx> { /// constraints in our `InferCtxt` type ErrorInfo = InstantiateOpaqueType<'tcx>; - fn fully_perform(mut self, infcx: &InferCtxt<'tcx>) -> Fallible> { - let (mut output, region_constraints) = scrape_region_constraints(infcx, |ocx| { - ocx.register_obligations(self.obligations.clone()); - Ok(()) - })?; + fn fully_perform( + mut self, + infcx: &InferCtxt<'tcx>, + span: Span, + ) -> Result, ErrorGuaranteed> { + let (mut output, region_constraints) = scrape_region_constraints( + infcx, + |ocx| { + ocx.register_obligations(self.obligations.clone()); + Ok(()) + }, + "InstantiateOpaqueType", + span, + )?; self.region_constraints = Some(region_constraints); output.error_info = Some(self); Ok(output) diff --git a/compiler/rustc_borrowck/src/type_check/relate_tys.rs b/compiler/rustc_borrowck/src/type_check/relate_tys.rs index dd1f89e5b9157..e0c6295627bbf 100644 --- a/compiler/rustc_borrowck/src/type_check/relate_tys.rs +++ b/compiler/rustc_borrowck/src/type_check/relate_tys.rs @@ -1,12 +1,13 @@ +use rustc_errors::ErrorGuaranteed; use rustc_infer::infer::nll_relate::{TypeRelating, TypeRelatingDelegate}; use rustc_infer::infer::NllRegionVariableOrigin; use rustc_infer::traits::PredicateObligations; use rustc_middle::mir::ConstraintCategory; +use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::relate::TypeRelation; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; -use rustc_trait_selection::traits::query::Fallible; use crate::constraints::OutlivesConstraint; use crate::diagnostics::UniverseInfo; @@ -30,7 +31,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { b: Ty<'tcx>, locations: Locations, category: ConstraintCategory<'tcx>, - ) -> Fallible<()> { + ) -> Result<(), NoSolution> { TypeRelating::new( self.infcx, NllTypeRelatingDelegate::new(self, locations, category, UniverseInfo::relate(a, b)), @@ -41,13 +42,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } /// Add sufficient constraints to ensure `a == b`. See also [Self::relate_types]. - pub(super) fn eq_substs( + pub(super) fn eq_args( &mut self, - a: ty::SubstsRef<'tcx>, - b: ty::SubstsRef<'tcx>, + a: ty::GenericArgsRef<'tcx>, + b: ty::GenericArgsRef<'tcx>, locations: Locations, category: ConstraintCategory<'tcx>, - ) -> Fallible<()> { + ) -> Result<(), NoSolution> { TypeRelating::new( self.infcx, NllTypeRelatingDelegate::new(self, locations, category, UniverseInfo::other()), @@ -185,7 +186,7 @@ impl<'tcx> TypeRelatingDelegate<'tcx> for NllTypeRelatingDelegate<'_, '_, 'tcx> } fn register_obligations(&mut self, obligations: PredicateObligations<'tcx>) { - match self.type_checker.fully_perform_op( + let _: Result<_, ErrorGuaranteed> = self.type_checker.fully_perform_op( self.locations, self.category, InstantiateOpaqueType { @@ -194,16 +195,6 @@ impl<'tcx> TypeRelatingDelegate<'tcx> for NllTypeRelatingDelegate<'_, '_, 'tcx> base_universe: None, region_constraints: None, }, - ) { - Ok(()) => {} - Err(_) => { - // It's a bit redundant to delay a bug here, but I'd rather - // delay more bugs than accidentally not delay a bug at all. - self.type_checker.tcx().sess.delay_span_bug( - self.locations.span(self.type_checker.body), - "errors selecting obligation during MIR typeck", - ); - } - }; + ); } } diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 56f078f2da816..56945f43fcda6 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -12,7 +12,6 @@ //! The code in this file doesn't *do anything* with those results; it //! just returns them for other code to use. -use either::Either; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Diagnostic; use rustc_hir as hir; @@ -22,8 +21,8 @@ use rustc_hir::BodyOwnerKind; use rustc_index::IndexVec; use rustc_infer::infer::NllRegionVariableOrigin; use rustc_middle::ty::fold::TypeFoldable; -use rustc_middle::ty::{self, InlineConstSubsts, InlineConstSubstsParts, RegionVid, Ty, TyCtxt}; -use rustc_middle::ty::{InternalSubsts, SubstsRef}; +use rustc_middle::ty::{self, InlineConstArgs, InlineConstArgsParts, RegionVid, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArgs, GenericArgsRef}; use rustc_span::symbol::{kw, sym}; use rustc_span::Symbol; use std::iter; @@ -88,26 +87,26 @@ pub struct UniversalRegions<'tcx> { #[derive(Copy, Clone, Debug)] pub enum DefiningTy<'tcx> { /// The MIR is a closure. The signature is found via - /// `ClosureSubsts::closure_sig_ty`. - Closure(DefId, SubstsRef<'tcx>), + /// `ClosureArgs::closure_sig_ty`. + Closure(DefId, GenericArgsRef<'tcx>), /// The MIR is a generator. The signature is that generators take /// no parameters and return the result of - /// `ClosureSubsts::generator_return_ty`. - Generator(DefId, SubstsRef<'tcx>, hir::Movability), + /// `ClosureArgs::generator_return_ty`. + Generator(DefId, GenericArgsRef<'tcx>, hir::Movability), - /// The MIR is a fn item with the given `DefId` and substs. The signature + /// The MIR is a fn item with the given `DefId` and args. The signature /// of the function can be bound then with the `fn_sig` query. - FnDef(DefId, SubstsRef<'tcx>), + FnDef(DefId, GenericArgsRef<'tcx>), /// The MIR represents some form of constant. The signature then /// is that it has no inputs and a single return value, which is /// the value of the constant. - Const(DefId, SubstsRef<'tcx>), + Const(DefId, GenericArgsRef<'tcx>), /// The MIR represents an inline const. The signature has no inputs and a - /// single return value found via `InlineConstSubsts::ty`. - InlineConst(DefId, SubstsRef<'tcx>), + /// single return value found via `InlineConstArgs::ty`. + InlineConst(DefId, GenericArgsRef<'tcx>), } impl<'tcx> DefiningTy<'tcx> { @@ -115,14 +114,12 @@ impl<'tcx> DefiningTy<'tcx> { /// not a closure or generator, there are no upvars, and hence it /// will be an empty list. The order of types in this list will /// match up with the upvar order in the HIR, typesystem, and MIR. - pub fn upvar_tys(self) -> impl Iterator> + 'tcx { + pub fn upvar_tys(self) -> &'tcx ty::List> { match self { - DefiningTy::Closure(_, substs) => Either::Left(substs.as_closure().upvar_tys()), - DefiningTy::Generator(_, substs, _) => { - Either::Right(Either::Left(substs.as_generator().upvar_tys())) - } + DefiningTy::Closure(_, args) => args.as_closure().upvar_tys(), + DefiningTy::Generator(_, args, _) => args.as_generator().upvar_tys(), DefiningTy::FnDef(..) | DefiningTy::Const(..) | DefiningTy::InlineConst(..) => { - Either::Right(Either::Right(iter::empty())) + ty::List::empty() } } } @@ -164,9 +161,9 @@ struct UniversalRegionIndices<'tcx> { /// used because trait matching and type-checking will feed us /// region constraints that reference those regions and we need to /// be able to map them to our internal `RegionVid`. This is - /// basically equivalent to an `InternalSubsts`, except that it also + /// basically equivalent to an `GenericArgs`, except that it also /// contains an entry for `ReStatic` -- it might be nice to just - /// use a substs, and then handle `ReStatic` another way. + /// use a args, and then handle `ReStatic` another way. indices: FxHashMap, RegionVid>, /// The vid assigned to `'static`. Used only for diagnostics. @@ -243,13 +240,13 @@ impl<'tcx> UniversalRegions<'tcx> { /// `V[1]: V[2]`. pub fn closure_mapping( tcx: TyCtxt<'tcx>, - closure_substs: SubstsRef<'tcx>, + closure_args: GenericArgsRef<'tcx>, expected_num_vars: usize, closure_def_id: LocalDefId, ) -> IndexVec> { let mut region_mapping = IndexVec::with_capacity(expected_num_vars); region_mapping.push(tcx.lifetimes.re_static); - tcx.for_each_free_region(&closure_substs, |fr| { + tcx.for_each_free_region(&closure_args, |fr| { region_mapping.push(fr); }); @@ -334,11 +331,11 @@ impl<'tcx> UniversalRegions<'tcx> { /// state. pub(crate) fn annotate(&self, tcx: TyCtxt<'tcx>, err: &mut Diagnostic) { match self.defining_ty { - DefiningTy::Closure(def_id, substs) => { + DefiningTy::Closure(def_id, args) => { err.note(format!( - "defining type: {} with closure substs {:#?}", - tcx.def_path_str_with_substs(def_id, substs), - &substs[tcx.generics_of(def_id).parent_count..], + "defining type: {} with closure args {:#?}", + tcx.def_path_str_with_args(def_id, args), + &args[tcx.generics_of(def_id).parent_count..], )); // FIXME: It'd be nice to print the late-bound regions @@ -350,11 +347,11 @@ impl<'tcx> UniversalRegions<'tcx> { err.note(format!("late-bound region is {:?}", self.to_region_vid(r))); }); } - DefiningTy::Generator(def_id, substs, _) => { + DefiningTy::Generator(def_id, args, _) => { err.note(format!( - "defining type: {} with generator substs {:#?}", - tcx.def_path_str_with_substs(def_id, substs), - &substs[tcx.generics_of(def_id).parent_count..], + "defining type: {} with generator args {:#?}", + tcx.def_path_str_with_args(def_id, args), + &args[tcx.generics_of(def_id).parent_count..], )); // FIXME: As above, we'd like to print out the region @@ -364,22 +361,19 @@ impl<'tcx> UniversalRegions<'tcx> { err.note(format!("late-bound region is {:?}", self.to_region_vid(r))); }); } - DefiningTy::FnDef(def_id, substs) => { - err.note(format!( - "defining type: {}", - tcx.def_path_str_with_substs(def_id, substs), - )); + DefiningTy::FnDef(def_id, args) => { + err.note(format!("defining type: {}", tcx.def_path_str_with_args(def_id, args),)); } - DefiningTy::Const(def_id, substs) => { + DefiningTy::Const(def_id, args) => { err.note(format!( "defining constant type: {}", - tcx.def_path_str_with_substs(def_id, substs), + tcx.def_path_str_with_args(def_id, args), )); } - DefiningTy::InlineConst(def_id, substs) => { + DefiningTy::InlineConst(def_id, args) => { err.note(format!( "defining inline constant type: {}", - tcx.def_path_str_with_substs(def_id, substs), + tcx.def_path_str_with_args(def_id, args), )); } } @@ -500,9 +494,12 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { .next_nll_region_var(FR, || RegionCtxt::Free(Symbol::intern("c-variadic"))) .as_var(); - let region = self.infcx.tcx.mk_re_var(reg_vid); - let va_list_ty = - self.infcx.tcx.type_of(va_list_did).subst(self.infcx.tcx, &[region.into()]); + let region = ty::Region::new_var(self.infcx.tcx, reg_vid); + let va_list_ty = self + .infcx + .tcx + .type_of(va_list_did) + .instantiate(self.infcx.tcx, &[region.into()]); unnormalized_input_tys = self.infcx.tcx.mk_type_list_from_iter( unnormalized_input_tys.iter().copied().chain(iter::once(va_list_ty)), @@ -522,7 +519,7 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { debug!("build: local regions = {}..{}", first_local_index, num_universals); let yield_ty = match defining_ty { - DefiningTy::Generator(_, substs, _) => Some(substs.as_generator().yield_ty()), + DefiningTy::Generator(_, args, _) => Some(args.as_generator().yield_ty()), _ => None, }; @@ -548,7 +545,7 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { match tcx.hir().body_owner_kind(self.mir_def) { BodyOwnerKind::Closure | BodyOwnerKind::Fn => { - let defining_ty = tcx.type_of(self.mir_def).subst_identity(); + let defining_ty = tcx.type_of(self.mir_def).instantiate_identity(); debug!("defining_ty (pre-replacement): {:?}", defining_ty); @@ -556,11 +553,11 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { self.infcx.replace_free_regions_with_nll_infer_vars(FR, defining_ty); match *defining_ty.kind() { - ty::Closure(def_id, substs) => DefiningTy::Closure(def_id, substs), - ty::Generator(def_id, substs, movability) => { - DefiningTy::Generator(def_id, substs, movability) + ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), + ty::Generator(def_id, args, movability) => { + DefiningTy::Generator(def_id, args, movability) } - ty::FnDef(def_id, substs) => DefiningTy::FnDef(def_id, substs), + ty::FnDef(def_id, args) => DefiningTy::FnDef(def_id, args), _ => span_bug!( tcx.def_span(self.mir_def), "expected defining type for `{:?}`: `{:?}`", @@ -571,11 +568,11 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { } BodyOwnerKind::Const | BodyOwnerKind::Static(..) => { - let identity_substs = InternalSubsts::identity_for_item(tcx, typeck_root_def_id); + let identity_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); if self.mir_def.to_def_id() == typeck_root_def_id { - let substs = - self.infcx.replace_free_regions_with_nll_infer_vars(FR, identity_substs); - DefiningTy::Const(self.mir_def.to_def_id(), substs) + let args = + self.infcx.replace_free_regions_with_nll_infer_vars(FR, identity_args); + DefiningTy::Const(self.mir_def.to_def_id(), args) } else { // FIXME this line creates a dependency between borrowck and typeck. // @@ -584,18 +581,18 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { // into borrowck, which is ICE #78174. // // As a workaround, inline consts have an additional generic param (`ty` - // below), so that `type_of(inline_const_def_id).substs(substs)` uses the + // below), so that `type_of(inline_const_def_id).args(args)` uses the // proper type with NLL infer vars. let ty = tcx .typeck(self.mir_def) .node_type(tcx.local_def_id_to_hir_id(self.mir_def)); - let substs = InlineConstSubsts::new( + let args = InlineConstArgs::new( tcx, - InlineConstSubstsParts { parent_substs: identity_substs, ty }, + InlineConstArgsParts { parent_args: identity_args, ty }, ) - .substs; - let substs = self.infcx.replace_free_regions_with_nll_infer_vars(FR, substs); - DefiningTy::InlineConst(self.mir_def.to_def_id(), substs) + .args; + let args = self.infcx.replace_free_regions_with_nll_infer_vars(FR, args); + DefiningTy::InlineConst(self.mir_def.to_def_id(), args) } } } @@ -612,29 +609,29 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { ) -> UniversalRegionIndices<'tcx> { let tcx = self.infcx.tcx; let typeck_root_def_id = tcx.typeck_root_def_id(self.mir_def.to_def_id()); - let identity_substs = InternalSubsts::identity_for_item(tcx, typeck_root_def_id); - let fr_substs = match defining_ty { - DefiningTy::Closure(_, substs) - | DefiningTy::Generator(_, substs, _) - | DefiningTy::InlineConst(_, substs) => { + let identity_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); + let fr_args = match defining_ty { + DefiningTy::Closure(_, args) + | DefiningTy::Generator(_, args, _) + | DefiningTy::InlineConst(_, args) => { // In the case of closures, we rely on the fact that - // the first N elements in the ClosureSubsts are + // the first N elements in the ClosureArgs are // inherited from the `typeck_root_def_id`. // Therefore, when we zip together (below) with - // `identity_substs`, we will get only those regions + // `identity_args`, we will get only those regions // that correspond to early-bound regions declared on // the `typeck_root_def_id`. - assert!(substs.len() >= identity_substs.len()); - assert_eq!(substs.regions().count(), identity_substs.regions().count()); - substs + assert!(args.len() >= identity_args.len()); + assert_eq!(args.regions().count(), identity_args.regions().count()); + args } - DefiningTy::FnDef(_, substs) | DefiningTy::Const(_, substs) => substs, + DefiningTy::FnDef(_, args) | DefiningTy::Const(_, args) => args, }; let global_mapping = iter::once((tcx.lifetimes.re_static, fr_static)); let subst_mapping = - iter::zip(identity_substs.regions(), fr_substs.regions().map(|r| r.as_var())); + iter::zip(identity_args.regions(), fr_args.regions().map(|r| r.as_var())); UniversalRegionIndices { indices: global_mapping.chain(subst_mapping).collect(), fr_static } } @@ -646,9 +643,9 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { ) -> ty::Binder<'tcx, &'tcx ty::List>> { let tcx = self.infcx.tcx; match defining_ty { - DefiningTy::Closure(def_id, substs) => { + DefiningTy::Closure(def_id, args) => { assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = substs.as_closure().sig(); + let closure_sig = args.as_closure().sig(); let inputs_and_output = closure_sig.inputs_and_output(); let bound_vars = tcx.mk_bound_variable_kinds_from_iter( inputs_and_output @@ -660,8 +657,8 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BrEnv, }; - let env_region = tcx.mk_re_late_bound(ty::INNERMOST, br); - let closure_ty = tcx.closure_env_ty(def_id, substs, env_region).unwrap(); + let env_region = ty::Region::new_late_bound(tcx, ty::INNERMOST, br); + let closure_ty = tcx.closure_env_ty(def_id, args, env_region).unwrap(); // The "inputs" of the closure in the // signature appear as a tuple. The MIR side @@ -681,18 +678,18 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { ) } - DefiningTy::Generator(def_id, substs, movability) => { + DefiningTy::Generator(def_id, args, movability) => { assert_eq!(self.mir_def.to_def_id(), def_id); - let resume_ty = substs.as_generator().resume_ty(); - let output = substs.as_generator().return_ty(); - let generator_ty = tcx.mk_generator(def_id, substs, movability); + let resume_ty = args.as_generator().resume_ty(); + let output = args.as_generator().return_ty(); + let generator_ty = Ty::new_generator(tcx, def_id, args, movability); let inputs_and_output = self.infcx.tcx.mk_type_list(&[generator_ty, resume_ty, output]); ty::Binder::dummy(inputs_and_output) } DefiningTy::FnDef(def_id, _) => { - let sig = tcx.fn_sig(def_id).subst_identity(); + let sig = tcx.fn_sig(def_id).instantiate_identity(); let sig = indices.fold_to_region_vids(tcx, sig); sig.inputs_and_output() } @@ -701,14 +698,14 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> { // For a constant body, there are no inputs, and one // "output" (the type of the constant). assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = tcx.type_of(self.mir_def).subst_identity(); + let ty = tcx.type_of(self.mir_def).instantiate_identity(); let ty = indices.fold_to_region_vids(tcx, ty); ty::Binder::dummy(tcx.mk_type_list(&[ty])) } - DefiningTy::InlineConst(def_id, substs) => { + DefiningTy::InlineConst(def_id, args) => { assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = substs.as_inline_const().ty(); + let ty = args.as_inline_const().ty(); ty::Binder::dummy(tcx.mk_type_list(&[ty])) } } @@ -778,7 +775,8 @@ impl<'cx, 'tcx> InferCtxtExt<'tcx> for BorrowckInferCtxt<'cx, 'tcx> { { let (value, _map) = self.tcx.replace_late_bound_regions(value, |br| { debug!(?br); - let liberated_region = self.tcx.mk_re_free(all_outlive_scope.to_def_id(), br.kind); + let liberated_region = + ty::Region::new_free(self.tcx, all_outlive_scope.to_def_id(), br.kind); let region_vid = { let name = match br.kind.get_name() { Some(name) => name, @@ -889,7 +887,7 @@ impl<'tcx> UniversalRegionIndices<'tcx> { where T: TypeFoldable>, { - tcx.fold_regions(value, |region, _| tcx.mk_re_var(self.to_region_vid(region))) + tcx.fold_regions(value, |region, _| ty::Region::new_var(tcx, self.to_region_vid(region))) } } @@ -928,8 +926,10 @@ fn for_each_late_bound_region_in_item<'tcx>( } for bound_var in tcx.late_bound_vars(tcx.hir().local_def_id_to_hir_id(mir_def_id)) { - let ty::BoundVariableKind::Region(bound_region) = bound_var else { continue; }; - let liberated_region = tcx.mk_re_free(mir_def_id.to_def_id(), bound_region); + let ty::BoundVariableKind::Region(bound_region) = bound_var else { + continue; + }; + let liberated_region = ty::Region::new_free(tcx, mir_def_id.to_def_id(), bound_region); f(liberated_region); } } diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 3b458b1d30ba7..8d8db4c13fac9 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -1,10 +1,51 @@ -builtin_macros_requires_cfg_pattern = - macro requires a cfg-pattern as an argument - .label = cfg-pattern required +builtin_macros_alloc_error_must_be_fn = alloc_error_handler must be a function +builtin_macros_alloc_must_statics = allocators must be statics -builtin_macros_expected_one_cfg_pattern = expected 1 cfg-pattern +builtin_macros_asm_clobber_abi = clobber_abi +builtin_macros_asm_clobber_no_reg = asm with `clobber_abi` must specify explicit registers for outputs +builtin_macros_asm_clobber_outputs = generic outputs -builtin_macros_alloc_error_must_be_fn = alloc_error_handler must be a function +builtin_macros_asm_duplicate_arg = duplicate argument named `{$name}` + .label = previously here + .arg = duplicate argument + +builtin_macros_asm_expected_comma = expected token: `,` + .label = expected `,` + +builtin_macros_asm_expected_other = expected operand, {$is_global_asm -> + [true] options + *[false] clobber_abi, options + }, or additional template string + +builtin_macros_asm_explicit_register_name = explicit register arguments cannot have names + +builtin_macros_asm_modifier_invalid = asm template modifier must be a single character + +builtin_macros_asm_mutually_exclusive = the `{$opt1}` and `{$opt2}` options are mutually exclusive + +builtin_macros_asm_noreturn = asm outputs are not allowed with the `noreturn` option + +builtin_macros_asm_opt_already_provided = the `{$symbol}` option was already provided + .label = this option was already provided + .suggestion = remove this option + +builtin_macros_asm_pos_after = positional arguments cannot follow named arguments or explicit register arguments + .pos = positional argument + .named = named argument + .explicit = explicit register argument + +builtin_macros_asm_pure_combine = the `pure` option must be combined with either `nomem` or `readonly` + +builtin_macros_asm_pure_no_output = asm with the `pure` option must have at least one output + +builtin_macros_asm_requires_template = requires at least a template string argument + +builtin_macros_asm_sym_no_path = expected a path for argument to `sym` + +builtin_macros_asm_underscore_input = _ cannot be used for input operands + +builtin_macros_assert_missing_comma = unexpected string literal + .suggestion = try adding a comma builtin_macros_assert_requires_boolean = macro requires a boolean expression as an argument .label = boolean expression required @@ -12,51 +53,54 @@ builtin_macros_assert_requires_boolean = macro requires a boolean expression as builtin_macros_assert_requires_expression = macro requires an expression as an argument .suggestion = try removing semicolon -builtin_macros_assert_missing_comma = unexpected string literal - .suggestion = try adding a comma +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` + +builtin_macros_bench_sig = functions used as benches must have signature `fn(&mut Bencher) -> impl Termination` + + +builtin_macros_cannot_derive_union = this trait cannot be derived for unions -builtin_macros_cfg_accessible_unspecified_path = `cfg_accessible` path is not specified -builtin_macros_cfg_accessible_multiple_paths = multiple `cfg_accessible` paths are specified -builtin_macros_cfg_accessible_literal_path = `cfg_accessible` path cannot be a literal builtin_macros_cfg_accessible_has_args = `cfg_accessible` path cannot accept arguments builtin_macros_cfg_accessible_indeterminate = cannot determine whether the path is accessible or not -builtin_macros_concat_bytestr = cannot concatenate a byte string literal - -builtin_macros_concat_missing_literal = expected a literal - .note = only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` +builtin_macros_cfg_accessible_literal_path = `cfg_accessible` path cannot be a literal +builtin_macros_cfg_accessible_multiple_paths = multiple `cfg_accessible` paths are specified +builtin_macros_cfg_accessible_unspecified_path = `cfg_accessible` path is not specified +builtin_macros_concat_bytes_array = cannot concatenate doubly nested array + .note = byte strings are treated as arrays of bytes + .help = try flattening the array -builtin_macros_concat_bytes_missing_literal = expected a byte literal - .note = only byte literals (like `b"foo"`, `b's'` and `[3, 4, 5]`) can be passed to `concat_bytes!()` +builtin_macros_concat_bytes_bad_repeat = repeat count is not a positive number builtin_macros_concat_bytes_invalid = cannot concatenate {$lit_kind} literals .byte_char = try using a byte character .byte_str = try using a byte string .number_array = try wrapping the number in an array -builtin_macros_concat_bytes_oob = numeric literal is out of bounds +builtin_macros_concat_bytes_missing_literal = expected a byte literal + .note = only byte literals (like `b"foo"`, `b's'` and `[3, 4, 5]`) can be passed to `concat_bytes!()` builtin_macros_concat_bytes_non_u8 = numeric literal is not a `u8` -builtin_macros_concat_bytes_array = cannot concatenate doubly nested array - .note = byte strings are treated as arrays of bytes - .help = try flattening the array +builtin_macros_concat_bytes_oob = numeric literal is out of bounds -builtin_macros_concat_bytes_bad_repeat = repeat count is not a positive number +builtin_macros_concat_bytestr = cannot concatenate a byte string literal +builtin_macros_concat_c_str_lit = cannot concatenate a C string literal + +builtin_macros_concat_idents_ident_args = `concat_idents!()` requires ident args builtin_macros_concat_idents_missing_args = `concat_idents!()` takes 1 or more arguments builtin_macros_concat_idents_missing_comma = `concat_idents!()` expecting comma -builtin_macros_concat_idents_ident_args = `concat_idents!()` requires ident args +builtin_macros_concat_missing_literal = expected a literal + .note = only literals (like `"foo"`, `-42` and `3.14`) can be passed to `concat!()` -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` +builtin_macros_default_arg = `#[default]` attribute does not accept a value + .suggestion = try using `#[default]` -builtin_macros_unexpected_lit = expected path to a trait, found literal - .label = not a trait - .str_lit = try using `#[derive({$sym})]` - .other = for example, write `#[derive(Debug)]` for `Debug` +builtin_macros_derive_macro_call = `derive` cannot be used on items with type macros builtin_macros_derive_path_args_list = traits in `#[derive(...)]` don't accept arguments .suggestion = remove the arguments @@ -64,66 +108,42 @@ builtin_macros_derive_path_args_list = traits in `#[derive(...)]` don't accept a builtin_macros_derive_path_args_value = traits in `#[derive(...)]` don't accept values .suggestion = remove the value -builtin_macros_derive_macro_call = `derive` cannot be used on items with type macros - -builtin_macros_cannot_derive_union = this trait cannot be derived for unions - -builtin_macros_no_default_variant = no default declared - .help = make a unit variant default by placing `#[default]` above it - .suggestion = make `{$ident}` default - -builtin_macros_multiple_defaults = multiple declared defaults - .label = first default - .additional = additional default - .note = only one variant can be default - .suggestion = make `{$ident}` default - -builtin_macros_non_unit_default = the `#[default]` attribute may only be used on unit enum variants - .help = consider a manual implementation of `Default` - -builtin_macros_non_exhaustive_default = default variant must be exhaustive - .label = declared `#[non_exhaustive]` here - .help = consider a manual implementation of `Default` - -builtin_macros_multiple_default_attrs = multiple `#[default]` attributes - .note = only one `#[default]` attribute is needed - .label = `#[default]` used here - .label_again = `#[default]` used again here - .help = try removing {$only_one -> - [true] this - *[false] these - } - -builtin_macros_default_arg = `#[default]` attribute does not accept a value - .suggestion = try using `#[default]` +builtin_macros_env_not_defined = environment variable `{$var}` not defined at compile time + .cargo = Cargo sets build script variables at run time. Use `std::env::var({$var_expr})` instead + .custom = use `std::env::var({$var_expr})` to read the variable at run time builtin_macros_env_takes_args = `env!()` takes 1 or 2 arguments -builtin_macros_env_not_defined = environment variable `{$var}` not defined at compile time - .cargo = Cargo sets build script variables at run time. Use `std::env::var("{$var}")` instead - .other = use `std::env::var("{$var}")` to read the variable at run time +builtin_macros_expected_one_cfg_pattern = expected 1 cfg-pattern -builtin_macros_format_requires_string = requires at least a format string argument +builtin_macros_expected_register_class_or_explicit_register = expected register class or explicit register + +builtin_macros_export_macro_rules = cannot export macro_rules! macros from a `proc-macro` crate type currently builtin_macros_format_duplicate_arg = duplicate argument named `{$ident}` .label1 = previously here .label2 = duplicate argument +builtin_macros_format_no_arg_named = there is no argument named `{$name}` + .note = did you intend to capture a variable `{$name}` from the surrounding scope? + .note2 = to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro + +builtin_macros_format_pos_mismatch = {$n} positional {$n -> + [one] argument + *[more] arguments + } in format string, but {$desc} + builtin_macros_format_positional_after_named = positional arguments cannot follow named arguments .label = positional arguments must be before named arguments .named_args = named argument +builtin_macros_format_requires_string = requires at least a format string argument + builtin_macros_format_string_invalid = invalid format string: {$desc} .label = {$label1} in format string .note = {$note} .second_label = {$label} -builtin_macros_sugg = consider using a positional formatting argument instead - -builtin_macros_format_no_arg_named = there is no argument named `{$name}` - .note = did you intend to capture a variable `{$name}` from the surrounding scope? - .note2 = to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro - builtin_macros_format_unknown_trait = unknown format trait `{$ty}` .note = the only appropriate formatting traits are: - ``, which uses the `Display` trait @@ -145,60 +165,67 @@ builtin_macros_format_unused_arg = {$named -> builtin_macros_format_unused_args = multiple unused formatting arguments .label = multiple missing formatting specifiers -builtin_macros_format_pos_mismatch = {$n} positional {$n -> - [one] argument - *[more] arguments - } in format string, but {$desc} +builtin_macros_global_asm_clobber_abi = `clobber_abi` cannot be used with `global_asm!` -builtin_macros_test_case_non_item = `#[test_case]` attribute is only allowed on items +builtin_macros_invalid_crate_attribute = invalid crate attribute -builtin_macros_test_bad_fn = {$kind} functions cannot be used for tests - .label = `{$kind}` because of this +builtin_macros_multiple_default_attrs = multiple `#[default]` attributes + .note = only one `#[default]` attribute is needed + .label = `#[default]` used here + .label_again = `#[default]` used again here + .help = try removing {$only_one -> + [true] this + *[false] these + } -builtin_macros_asm_explicit_register_name = explicit register arguments cannot have names +builtin_macros_multiple_defaults = multiple declared defaults + .label = first default + .additional = additional default + .note = only one variant can be default + .suggestion = make `{$ident}` default -builtin_macros_asm_mutually_exclusive = the `{$opt1}` and `{$opt2}` options are mutually exclusive +builtin_macros_no_default_variant = no default declared + .help = make a unit variant default by placing `#[default]` above it + .suggestion = make `{$ident}` default -builtin_macros_asm_pure_combine = the `pure` option must be combined with either `nomem` or `readonly` +builtin_macros_non_abi = at least one abi must be provided as an argument to `clobber_abi` -builtin_macros_asm_pure_no_output = asm with the `pure` option must have at least one output +builtin_macros_non_exhaustive_default = default variant must be exhaustive + .label = declared `#[non_exhaustive]` here + .help = consider a manual implementation of `Default` -builtin_macros_asm_modifier_invalid = asm template modifier must be a single character +builtin_macros_non_unit_default = the `#[default]` attribute may only be used on unit enum variants + .help = consider a manual implementation of `Default` -builtin_macros_asm_requires_template = requires at least a template string argument +builtin_macros_proc_macro = `proc-macro` crate types currently cannot export any items other than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, or `#[proc_macro_attribute]` -builtin_macros_asm_expected_comma = expected token: `,` - .label = expected `,` +builtin_macros_requires_cfg_pattern = + macro requires a cfg-pattern as an argument + .label = cfg-pattern required -builtin_macros_asm_underscore_input = _ cannot be used for input operands +builtin_macros_should_panic = functions using `#[should_panic]` must return `()` -builtin_macros_asm_sym_no_path = expected a path for argument to `sym` +builtin_macros_sugg = consider using a positional formatting argument instead -builtin_macros_asm_expected_other = expected operand, {$is_global_asm -> - [true] options - *[false] clobber_abi, options - }, or additional template string +builtin_macros_test_arg_non_lifetime = functions used as tests can not have any non-lifetime generic parameters -builtin_macros_asm_duplicate_arg = duplicate argument named `{$name}` - .label = previously here - .arg = duplicate argument +builtin_macros_test_args = functions used as tests can not have any arguments -builtin_macros_asm_pos_after = positional arguments cannot follow named arguments or explicit register arguments - .pos = positional argument - .named = named argument - .explicit = explicit register argument +builtin_macros_test_bad_fn = {$kind} functions cannot be used for tests + .label = `{$kind}` because of this -builtin_macros_asm_noreturn = asm outputs are not allowed with the `noreturn` option +builtin_macros_test_case_non_item = `#[test_case]` attribute is only allowed on items -builtin_macros_global_asm_clobber_abi = `clobber_abi` cannot be used with `global_asm!` +builtin_macros_test_runner_invalid = `test_runner` argument must be a path +builtin_macros_test_runner_nargs = `#![test_runner(..)]` accepts exactly 1 argument -builtin_macros_asm_clobber_no_reg = asm with `clobber_abi` must specify explicit registers for outputs -builtin_macros_asm_clobber_abi = clobber_abi -builtin_macros_asm_clobber_outputs = generic outputs +builtin_macros_tests_not_support = building tests with panic=abort is not supported without `-Zpanic_abort_tests` -builtin_macros_asm_opt_already_provided = the `{$symbol}` option was already provided - .label = this option was already provided - .suggestion = remove this option +builtin_macros_trace_macros = trace_macros! accepts only `true` or `false` -builtin_macros_test_runner_invalid = `test_runner` argument must be a path -builtin_macros_test_runner_nargs = `#![test_runner(..)]` accepts exactly 1 argument +builtin_macros_unexpected_lit = expected path to a trait, found literal + .label = not a trait + .str_lit = try using `#[derive({$sym})]` + .other = for example, write `#[derive(Debug)]` for `Debug` + +builtin_macros_unnameable_test_items = cannot test inner items diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 5217e317adfee..9e66eaf73b3ba 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -157,8 +157,7 @@ pub fn parse_asm_args<'a>( } else if p.eat_keyword(sym::sym) { let expr = p.parse_expr()?; let ast::ExprKind::Path(qself, path) = &expr.kind else { - let err = diag - .create_err(errors::AsmSymNoPath { span: expr.span }); + let err = diag.create_err(errors::AsmSymNoPath { span: expr.span }); return Err(err); }; let sym = ast::InlineAsmSym { @@ -371,24 +370,16 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, p.expect(&token::OpenDelim(Delimiter::Parenthesis))?; if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) { - let err = p.sess.span_diagnostic.struct_span_err( - p.token.span, - "at least one abi must be provided as an argument to `clobber_abi`", - ); - return Err(err); + return Err(p.sess.span_diagnostic.create_err(errors::NonABI { span: p.token.span })); } let mut new_abis = Vec::new(); - loop { + while !p.eat(&token::CloseDelim(Delimiter::Parenthesis)) { match p.parse_str_lit() { Ok(str_lit) => { new_abis.push((str_lit.symbol_unescaped, str_lit.span)); } Err(opt_lit) => { - // If the non-string literal is a closing paren then it's the end of the list and is fine - if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) { - break; - } let span = opt_lit.map_or(p.token.span, |lit| lit.span); let mut err = p.sess.span_diagnostic.struct_span_err(span, "expected string literal"); @@ -410,7 +401,7 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, // should have errored above during parsing [] => unreachable!(), [(abi, _span)] => args.clobber_abis.push((*abi, full_span)), - [abis @ ..] => { + abis => { for (abi, span) in abis { args.clobber_abis.push((*abi, *span)); } @@ -432,9 +423,9 @@ fn parse_reg<'a>( ast::InlineAsmRegOrRegClass::Reg(symbol) } _ => { - return Err( - p.struct_span_err(p.token.span, "expected register class or explicit register") - ); + return Err(p.sess.create_err(errors::ExpectedRegisterClassOrExplicitRegister { + span: p.token.span, + })); } }; p.bump(); @@ -584,7 +575,7 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option, args: AsmArgs) -> Option format!("no {}arguments were given", positional), - 1 => format!("there is 1 {}argument", positional), - x => format!("there are {} {}arguments", x, positional), + 0 => format!("no {positional}arguments were given"), + 1 => format!("there is 1 {positional}argument"), + x => format!("there are {x} {positional}arguments"), }; err.note(msg); @@ -633,7 +624,7 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option Some(idx), None => { - let msg = format!("there is no argument named `{}`", name); + let msg = format!("there is no argument named `{name}`"); let span = arg.position_span; ecx.struct_span_err( template_span @@ -706,8 +697,7 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option, args: AsmArgs) -> Option( path: panic_path(), args: P(DelimArgs { dspan: DelimSpan::from_single(call_site_span), - delim: MacDelimiter::Parenthesis, + delim: Delimiter::Parenthesis, tokens, }), })), @@ -68,7 +69,7 @@ pub fn expand_assert<'cx>( // If `generic_assert` is enabled, generates rich captured outputs // // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949 - else if let Some(features) = cx.ecfg.features && features.generic_assert { + else if cx.ecfg.features.generic_assert { context::Context::new(cx, call_site_span).build(cond_expr, panic_path()) } // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..." diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index ea830a0ce60df..bda473120ed04 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -1,9 +1,10 @@ use rustc_ast::{ ptr::P, token, + token::Delimiter, tokenstream::{DelimSpan, TokenStream, TokenTree}, - BinOpKind, BorrowKind, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MacDelimiter, MethodCall, - Mutability, Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, DUMMY_NODE_ID, + BinOpKind, BorrowKind, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall, Mutability, + Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, DUMMY_NODE_ID, }; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; @@ -179,7 +180,7 @@ impl<'cx, 'a> Context<'cx, 'a> { path: panic_path, args: P(DelimArgs { dspan: DelimSpan::from_single(self.span), - delim: MacDelimiter::Parenthesis, + delim: Delimiter::Parenthesis, tokens: initial.into_iter().chain(captures).collect::(), }), })), @@ -233,10 +234,19 @@ impl<'cx, 'a> Context<'cx, 'a> { ExprKind::Cast(local_expr, _) => { self.manage_cond_expr(local_expr); } - ExprKind::Index(prefix, suffix) => { + ExprKind::If(local_expr, _, _) => { + self.manage_cond_expr(local_expr); + } + ExprKind::Index(prefix, suffix, _) => { self.manage_cond_expr(prefix); self.manage_cond_expr(suffix); } + ExprKind::Let(_, local_expr, _) => { + self.manage_cond_expr(local_expr); + } + ExprKind::Match(local_expr, _) => { + self.manage_cond_expr(local_expr); + } ExprKind::MethodCall(call) => { for arg in &mut call.args { self.manage_cond_expr(arg); @@ -295,17 +305,14 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Continue(_) | ExprKind::Err | ExprKind::Field(_, _) - | ExprKind::FormatArgs(_) | ExprKind::ForLoop(_, _, _, _) - | ExprKind::If(_, _, _) + | ExprKind::FormatArgs(_) | ExprKind::IncludedBytes(..) | ExprKind::InlineAsm(_) - | ExprKind::OffsetOf(_, _) - | ExprKind::Let(_, _, _) | ExprKind::Lit(_) | ExprKind::Loop(_, _, _) | ExprKind::MacCall(_) - | ExprKind::Match(_, _) + | ExprKind::OffsetOf(_, _) | ExprKind::Path(_, _) | ExprKind::Ret(_) | ExprKind::Try(_) @@ -314,6 +321,7 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Underscore | ExprKind::While(_, _, _) | ExprKind::Yeet(_) + | ExprKind::Become(_) | ExprKind::Yield(_) => {} } } diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs index 1397cee7af83b..31cac51845faa 100644 --- a/compiler/rustc_builtin_macros/src/cfg.rs +++ b/compiler/rustc_builtin_macros/src/cfg.rs @@ -24,7 +24,7 @@ pub fn expand_cfg( &cfg, &cx.sess.parse_sess, cx.current_expansion.lint_node_id, - cx.ecfg.features, + Some(cx.ecfg.features), ); MacEager::expr(cx.expr_bool(sp, matches_cfg)) } diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 49401e9ca9496..f826c6e7712d2 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -31,10 +31,11 @@ pub(crate) fn expand( pub(crate) fn cfg_eval( sess: &Session, - features: Option<&Features>, + features: &Features, annotatable: Annotatable, lint_node_id: NodeId, ) -> Annotatable { + let features = Some(features); CfgEval { cfg: &mut StripUnconfigured { sess, features, config_tokens: true, lint_node_id } } .configure_annotatable(annotatable) // Since the item itself has already been configured by the `InvocationCollector`, diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs index 2b6fcc169be06..7b75d7d84e4fc 100644 --- a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -1,5 +1,6 @@ //! Attributes injected into the crate root from command line using `-Z crate-attr`. +use crate::errors; use rustc_ast::attr::mk_attr; use rustc_ast::token; use rustc_ast::{self as ast, AttrItem, AttrStyle}; @@ -24,7 +25,9 @@ pub fn inject(krate: &mut ast::Crate, parse_sess: &ParseSess, attrs: &[String]) }; let end_span = parser.token.span; if parser.token != token::Eof { - parse_sess.span_diagnostic.span_err(start_span.to(end_span), "invalid crate attribute"); + parse_sess + .span_diagnostic + .emit_err(errors::InvalidCrateAttr { span: start_span.to(end_span) }); continue; } diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs index aeb3bb80045bb..5efc5a4e3eea3 100644 --- a/compiler/rustc_builtin_macros/src/compile_error.rs +++ b/compiler/rustc_builtin_macros/src/compile_error.rs @@ -18,7 +18,7 @@ pub fn expand_compile_error<'cx>( reason = "diagnostic message is specified by user" )] #[expect(rustc::untranslatable_diagnostic, reason = "diagnostic message is specified by user")] - cx.span_err(sp, var.as_str()); + cx.span_err(sp, var.to_string()); DummyResult::any(sp) } diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs index 50e88ae2eeede..9695fb4fee13f 100644 --- a/compiler/rustc_builtin_macros/src/concat.rs +++ b/compiler/rustc_builtin_macros/src/concat.rs @@ -33,7 +33,7 @@ pub fn expand_concat( accumulator.push_str(&b.to_string()); } Ok(ast::LitKind::CStr(..)) => { - cx.span_err(e.span, "cannot concatenate a C string literal"); + cx.emit_err(errors::ConcatCStrLit{ span: e.span}); has_errors = true; } Ok(ast::LitKind::Byte(..) | ast::LitKind::ByteStr(..)) => { diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs index 5ef35af0a059a..6a1586f071c45 100644 --- a/compiler/rustc_builtin_macros/src/concat_bytes.rs +++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs @@ -21,7 +21,7 @@ fn invalid_type_err( Ok(ast::LitKind::CStr(_, _)) => { // FIXME(c_str_literals): should concatenation of C string literals // include the null bytes in the end? - cx.span_err(span, "cannot concatenate C string literals"); + cx.emit_err(errors::ConcatCStrLit { span: span }); } Ok(ast::LitKind::Char(_)) => { let sugg = diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs index fe4483104eeb3..140853db695aa 100644 --- a/compiler/rustc_builtin_macros/src/derive.rs +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -8,7 +8,7 @@ use rustc_feature::AttributeTemplate; use rustc_parse::validate_attr; use rustc_session::Session; use rustc_span::symbol::{sym, Ident}; -use rustc_span::Span; +use rustc_span::{ErrorGuaranteed, Span}; pub(crate) struct Expander(pub bool); @@ -22,7 +22,7 @@ impl MultiItemModifier for Expander { _: bool, ) -> ExpandResult, Annotatable> { let sess = ecx.sess; - if report_bad_target(sess, &item, span) { + if report_bad_target(sess, &item, span).is_err() { // We don't want to pass inappropriate targets to derive macros to avoid // follow up errors, all other errors below are recoverable. return ExpandResult::Ready(vec![item]); @@ -103,7 +103,11 @@ fn dummy_annotatable() -> Annotatable { }) } -fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { +fn report_bad_target( + sess: &Session, + item: &Annotatable, + span: Span, +) -> Result<(), ErrorGuaranteed> { let item_kind = match item { Annotatable::Item(item) => Some(&item.kind), Annotatable::Stmt(stmt) => match &stmt.kind { @@ -116,9 +120,9 @@ fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool { let bad_target = !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..))); if bad_target { - sess.emit_err(errors::BadDeriveTarget { span, item: item.span() }); + return Err(sess.emit_err(errors::BadDeriveTarget { span, item: item.span() })); } - bad_target + Ok(()) } fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) { diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs index 9883563746e90..b468abe3249af 100644 --- a/compiler/rustc_builtin_macros/src/deriving/clone.rs +++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs @@ -68,7 +68,6 @@ pub fn expand_deriving_clone( _ => cx.span_bug(span, "`#[derive(Clone)]` on trait item or impl item"), } - let attrs = thin_vec![cx.attr_word(sym::inline, span)]; let trait_def = TraitDef { span, path: path_std!(clone::Clone), @@ -82,7 +81,7 @@ pub fn expand_deriving_clone( explicit_self: true, nonself_args: Vec::new(), ret_ty: Self_, - attributes: attrs, + attributes: thin_vec![cx.attr_word(sym::inline, span)], fieldless_variants_strategy: FieldlessVariantsStrategy::Default, combine_substructure: substructure, }], @@ -145,7 +144,7 @@ fn cs_clone_simple( } _ => cx.span_bug( trait_span, - format!("unexpected substructure in simple `derive({})`", name), + format!("unexpected substructure in simple `derive({name})`"), ), } } @@ -179,10 +178,10 @@ fn cs_clone( vdata = &variant.data; } EnumTag(..) | AllFieldlessEnum(..) => { - cx.span_bug(trait_span, format!("enum tags in `derive({})`", name,)) + cx.span_bug(trait_span, format!("enum tags in `derive({name})`",)) } StaticEnum(..) | StaticStruct(..) => { - cx.span_bug(trait_span, format!("associated function in `derive({})`", name)) + cx.span_bug(trait_span, format!("associated function in `derive({name})`")) } } @@ -194,7 +193,7 @@ fn cs_clone( let Some(ident) = field.name else { cx.span_bug( trait_span, - format!("unnamed field in normal struct in `derive({})`", name,), + format!("unnamed field in normal struct in `derive({name})`",), ); }; let call = subcall(cx, field); diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs index af971958680ae..c78a0eb04a074 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs @@ -18,11 +18,6 @@ pub fn expand_deriving_eq( is_const: bool, ) { let span = cx.with_def_site_ctxt(span); - let attrs = thin_vec![ - cx.attr_word(sym::inline, span), - cx.attr_nested_word(sym::doc, sym::hidden, span), - cx.attr_word(sym::no_coverage, span) - ]; let trait_def = TraitDef { span, path: path_std!(cmp::Eq), @@ -36,7 +31,11 @@ pub fn expand_deriving_eq( explicit_self: true, nonself_args: vec![], ret_ty: Unit, - attributes: attrs, + attributes: thin_vec![ + cx.attr_word(sym::inline, span), + cx.attr_nested_word(sym::doc, sym::hidden, span), + cx.attr_word(sym::no_coverage, span) + ], fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, combine_substructure: combine_substructure(Box::new(|a, b, c| { cs_total_eq_assert(a, b, c) diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs index cfd36f030a193..ea81cee78b716 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs @@ -15,7 +15,6 @@ pub fn expand_deriving_ord( push: &mut dyn FnMut(Annotatable), is_const: bool, ) { - let attrs = thin_vec![cx.attr_word(sym::inline, span)]; let trait_def = TraitDef { span, path: path_std!(cmp::Ord), @@ -29,7 +28,7 @@ pub fn expand_deriving_ord( explicit_self: true, nonself_args: vec![(self_ref(), sym::other)], ret_ty: Path(path_std!(cmp::Ordering)), - attributes: attrs, + attributes: thin_vec![cx.attr_word(sym::inline, span)], fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, combine_substructure: combine_substructure(Box::new(|a, b, c| cs_cmp(a, b, c))), }], @@ -62,8 +61,8 @@ pub fn cs_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> Bl |cx, fold| match fold { CsFold::Single(field) => { let [other_expr] = &field.other_selflike_exprs[..] else { - cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`"); - }; + cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`"); + }; let args = thin_vec![field.self_expr.clone(), other_expr.clone()]; cx.expr_call_global(field.span, cmp_path.clone(), args) } diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs index bad47db0de1d4..a71ecc5db7d97 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs @@ -82,14 +82,13 @@ pub fn expand_deriving_partial_eq( // No need to generate `ne`, the default suffices, and not generating it is // faster. - let attrs = thin_vec![cx.attr_word(sym::inline, span)]; let methods = vec![MethodDef { name: sym::eq, generics: Bounds::empty(), explicit_self: true, nonself_args: vec![(self_ref(), sym::other)], ret_ty: Path(path_local!(bool)), - attributes: attrs, + attributes: thin_vec![cx.attr_word(sym::inline, span)], fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, combine_substructure: combine_substructure(Box::new(|a, b, c| cs_eq(a, b, c))), }]; diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs index 9f46247908d0d..a5b3a504e38f7 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs @@ -19,8 +19,6 @@ pub fn expand_deriving_partial_ord( let ret_ty = Path(Path::new_(pathvec_std!(option::Option), vec![Box::new(ordering_ty)], PathKind::Std)); - let attrs = thin_vec![cx.attr_word(sym::inline, span)]; - // Order in which to perform matching let tag_then_data = if let Annotatable::Item(item) = item && let ItemKind::Enum(def, _) = &item.kind { @@ -48,7 +46,7 @@ pub fn expand_deriving_partial_ord( explicit_self: true, nonself_args: vec![(self_ref(), sym::other)], ret_ty, - attributes: attrs, + attributes: thin_vec![cx.attr_word(sym::inline, span)], fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, combine_substructure: combine_substructure(Box::new(|cx, span, substr| { cs_partial_cmp(cx, span, substr, tag_then_data) @@ -96,8 +94,8 @@ fn cs_partial_cmp( |cx, fold| match fold { CsFold::Single(field) => { let [other_expr] = &field.other_selflike_exprs[..] else { - cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`"); - }; + cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`"); + }; let args = thin_vec![field.self_expr.clone(), other_expr.clone()]; cx.expr_call_global(field.span, partial_cmp_path.clone(), args) } diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs index 3921533c84a19..bcf11cb4ce9d4 100644 --- a/compiler/rustc_builtin_macros/src/deriving/decodable.rs +++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs @@ -204,7 +204,7 @@ where let fields = fields .iter() .enumerate() - .map(|(i, &span)| getarg(cx, span, Symbol::intern(&format!("_field{}", i)), i)) + .map(|(i, &span)| getarg(cx, span, Symbol::intern(&format!("_field{i}")), i)) .collect(); cx.expr_call(trait_span, path_expr, fields) diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs index 33fe98b40e158..07b172bc757b2 100644 --- a/compiler/rustc_builtin_macros/src/deriving/default.rs +++ b/compiler/rustc_builtin_macros/src/deriving/default.rs @@ -20,7 +20,6 @@ pub fn expand_deriving_default( ) { item.visit_with(&mut DetectNonVariantDefaultAttr { cx }); - let attrs = thin_vec![cx.attr_word(sym::inline, span)]; let trait_def = TraitDef { span, path: Path::new(vec![kw::Default, sym::Default]), @@ -34,7 +33,7 @@ pub fn expand_deriving_default( explicit_self: false, nonself_args: Vec::new(), ret_ty: Self_, - attributes: attrs, + attributes: thin_vec![cx.attr_word(sym::inline, span)], fieldless_variants_strategy: FieldlessVariantsStrategy::Default, combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| { match substr.fields { diff --git a/compiler/rustc_builtin_macros/src/deriving/encodable.rs b/compiler/rustc_builtin_macros/src/deriving/encodable.rs index a3b11309d0ce1..2dc20c324972b 100644 --- a/compiler/rustc_builtin_macros/src/deriving/encodable.rs +++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs @@ -173,7 +173,7 @@ fn encodable_substructure( for (i, &FieldInfo { name, ref self_expr, span, .. }) in fields.iter().enumerate() { let name = match name { Some(id) => id.name, - None => Symbol::intern(&format!("_field{}", i)), + None => Symbol::intern(&format!("_field{i}")), }; let self_ref = cx.expr_addr_of(span, self_expr.clone()); let enc = diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 4ba09335cb7ab..6597ee3cf1b6c 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -1134,9 +1134,14 @@ impl<'a> MethodDef<'a> { trait_: &TraitDef<'b>, enum_def: &'b EnumDef, type_ident: Ident, - selflike_args: ThinVec>, + mut selflike_args: ThinVec>, nonselflike_args: &[P], ) -> BlockOrExpr { + assert!( + !selflike_args.is_empty(), + "static methods must use `expand_static_enum_method_body`", + ); + let span = trait_.span; let variants = &enum_def.variants; @@ -1144,10 +1149,15 @@ impl<'a> MethodDef<'a> { let unify_fieldless_variants = self.fieldless_variants_strategy == FieldlessVariantsStrategy::Unify; - // There is no sensible code to be generated for *any* deriving on a - // zero-variant enum. So we just generate a failing expression. + // For zero-variant enum, this function body is unreachable. Generate + // `match *self {}`. This produces machine code identical to `unsafe { + // core::intrinsics::unreachable() }` while being safe and stable. if variants.is_empty() { - return BlockOrExpr(ThinVec::new(), Some(deriving::call_unreachable(cx, span))); + selflike_args.truncate(1); + let match_arg = cx.expr_deref(span, selflike_args.pop().unwrap()); + let match_arms = ThinVec::new(); + let expr = cx.expr_match(span, match_arg, match_arms); + return BlockOrExpr(ThinVec::new(), Some(expr)); } let prefixes = iter::once("__self".to_string()) @@ -1156,7 +1166,7 @@ impl<'a> MethodDef<'a> { .iter() .enumerate() .skip(1) - .map(|(arg_count, _selflike_arg)| format!("__arg{}", arg_count)), + .map(|(arg_count, _selflike_arg)| format!("__arg{arg_count}")), ) .collect::>(); @@ -1171,7 +1181,7 @@ impl<'a> MethodDef<'a> { let get_tag_pieces = |cx: &ExtCtxt<'_>| { let tag_idents: Vec<_> = prefixes .iter() - .map(|name| Ident::from_str_and_span(&format!("{}_tag", name), span)) + .map(|name| Ident::from_str_and_span(&format!("{name}_tag"), span)) .collect(); let mut tag_exprs: Vec<_> = tag_idents @@ -1511,7 +1521,7 @@ impl<'a> TraitDef<'a> { } fn mk_pattern_ident(&self, prefix: &str, i: usize) -> Ident { - Ident::from_str_and_span(&format!("{}_{}", prefix, i), self.span) + Ident::from_str_and_span(&format!("{prefix}_{i}"), self.span) } fn create_struct_pattern_fields( @@ -1592,8 +1602,7 @@ impl<'a> TraitDef<'a> { sp, ast::CRATE_NODE_ID, format!( - "{} slice in a packed struct that derives a built-in trait", - ty + "{ty} slice in a packed struct that derives a built-in trait" ), rustc_lint_defs::BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive ); diff --git a/compiler/rustc_builtin_macros/src/deriving/hash.rs b/compiler/rustc_builtin_macros/src/deriving/hash.rs index 4eee573db4215..101401f9c85b7 100644 --- a/compiler/rustc_builtin_macros/src/deriving/hash.rs +++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs @@ -1,7 +1,7 @@ use crate::deriving::generic::ty::*; use crate::deriving::generic::*; use crate::deriving::{path_std, pathvec_std}; -use rustc_ast::{AttrVec, MetaItem, Mutability}; +use rustc_ast::{MetaItem, Mutability}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_span::symbol::sym; use rustc_span::Span; @@ -33,7 +33,7 @@ pub fn expand_deriving_hash( explicit_self: true, nonself_args: vec![(Ref(Box::new(Path(arg)), Mutability::Mut), sym::state)], ret_ty: Unit, - attributes: AttrVec::new(), + attributes: thin_vec![cx.attr_word(sym::inline, span)], fieldless_variants_strategy: FieldlessVariantsStrategy::Unify, combine_substructure: combine_substructure(Box::new(|a, b, c| { hash_substructure(a, b, c) diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs index ef0db23ff2f2d..1e1dadab48067 100644 --- a/compiler/rustc_builtin_macros/src/edition_panic.rs +++ b/compiler/rustc_builtin_macros/src/edition_panic.rs @@ -1,4 +1,5 @@ use rustc_ast::ptr::P; +use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::{DelimSpan, TokenStream}; use rustc_ast::*; use rustc_expand::base::*; @@ -60,7 +61,7 @@ fn expand<'cx>( }, args: P(DelimArgs { dspan: DelimSpan::from_single(sp), - delim: MacDelimiter::Parenthesis, + delim: Delimiter::Parenthesis, tokens: tts, }), })), diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs index 8f64e3328619e..92da0c069e51a 100644 --- a/compiler/rustc_builtin_macros/src/env.rs +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -4,7 +4,7 @@ // use rustc_ast::tokenstream::TokenStream; -use rustc_ast::{self as ast, GenericArg}; +use rustc_ast::{self as ast, AstDeref, GenericArg}; use rustc_expand::base::{self, *}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::Span; @@ -76,22 +76,36 @@ pub fn expand_env<'cx>( }, }; - let sp = cx.with_def_site_ctxt(sp); + let span = cx.with_def_site_ctxt(sp); let value = env::var(var.as_str()).ok().as_deref().map(Symbol::intern); cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value)); let e = match value { None => { - // Use the string literal in the code in the diagnostic to avoid confusing diagnostics, - // e.g. when the literal contains escape sequences. - let ast::ExprKind::Lit(ast::token::Lit { kind: ast::token::LitKind::Str, symbol: original_var, ..}) = &var_expr.kind else { + let ast::ExprKind::Lit(ast::token::Lit { + kind: ast::token::LitKind::Str | ast::token::LitKind::StrRaw(..), + symbol, + .. + }) = &var_expr.kind + else { unreachable!("`expr_to_string` ensures this is a string lit") }; - cx.emit_err(errors::EnvNotDefined { - span: sp, - msg: custom_msg, - var: *original_var, - help: custom_msg.is_none().then(|| help_for_missing_env_var(var.as_str())), - }); + + if let Some(msg_from_user) = custom_msg { + cx.emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user }); + } else if is_cargo_env_var(var.as_str()) { + cx.emit_err(errors::EnvNotDefined::CargoEnvVar { + span, + var: *symbol, + var_expr: var_expr.ast_deref(), + }); + } else { + cx.emit_err(errors::EnvNotDefined::CustomEnvVar { + span, + var: *symbol, + var_expr: var_expr.ast_deref(), + }); + } + return DummyResult::any(sp); } Some(value) => cx.expr_str(sp, value), @@ -99,13 +113,9 @@ pub fn expand_env<'cx>( MacEager::expr(e) } -fn help_for_missing_env_var(var: &str) -> errors::EnvNotDefinedHelp { - if var.starts_with("CARGO_") +/// Returns `true` if an environment variable from `env!` is one used by Cargo. +fn is_cargo_env_var(var: &str) -> bool { + var.starts_with("CARGO_") || var.starts_with("DEP_") || matches!(var, "OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET") - { - errors::EnvNotDefinedHelp::CargoVar - } else { - errors::EnvNotDefinedHelp::Other - } } diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index d0d78646009e1..fbf0395bb05ac 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -87,6 +87,83 @@ pub(crate) struct ConcatBytestr { pub(crate) span: Span, } +#[derive(Diagnostic)] +#[diag(builtin_macros_concat_c_str_lit)] +pub(crate) struct ConcatCStrLit { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_export_macro_rules)] +pub(crate) struct ExportMacroRules { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_proc_macro)] +pub(crate) struct ProcMacro { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_invalid_crate_attribute)] +pub(crate) struct InvalidCrateAttr { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_non_abi)] +pub(crate) struct NonABI { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_trace_macros)] +pub(crate) struct TraceMacros { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_bench_sig)] +pub(crate) struct BenchSig { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_test_arg_non_lifetime)] +pub(crate) struct TestArgNonLifetime { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_should_panic)] +pub(crate) struct ShouldPanic { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_test_args)] +pub(crate) struct TestArgs { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_alloc_must_statics)] +pub(crate) struct AllocMustStatics { + #[primary_span] + pub(crate) span: Span, +} + #[derive(Diagnostic)] #[diag(builtin_macros_concat_bytes_invalid)] pub(crate) struct ConcatBytesInvalid { @@ -201,6 +278,10 @@ pub(crate) struct BadDeriveTarget { pub(crate) item: Span, } +#[derive(Diagnostic)] +#[diag(builtin_macros_tests_not_support)] +pub(crate) struct TestsNotSupport {} + #[derive(Diagnostic)] #[diag(builtin_macros_unexpected_lit, code = "E0777")] pub(crate) struct BadDeriveLit { @@ -359,43 +440,43 @@ pub(crate) struct EnvTakesArgs { pub(crate) span: Span, } -//#[derive(Diagnostic)] -//#[diag(builtin_macros_env_not_defined)] -pub(crate) struct EnvNotDefined { +pub(crate) struct EnvNotDefinedWithUserMessage { pub(crate) span: Span, - pub(crate) msg: Option, - pub(crate) var: Symbol, - pub(crate) help: Option, + pub(crate) msg_from_user: Symbol, } -// Hand-written implementation to support custom user messages -impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for EnvNotDefined { +// Hand-written implementation to support custom user messages. +impl<'a, G: EmissionGuarantee> IntoDiagnostic<'a, G> for EnvNotDefinedWithUserMessage { #[track_caller] fn into_diagnostic(self, handler: &'a Handler) -> DiagnosticBuilder<'a, G> { - let mut diag = if let Some(msg) = self.msg { - #[expect( - rustc::untranslatable_diagnostic, - reason = "cannot translate user-provided messages" - )] - handler.struct_diagnostic(msg.as_str()) - } else { - handler.struct_diagnostic(crate::fluent_generated::builtin_macros_env_not_defined) - }; - diag.set_arg("var", self.var); + #[expect( + rustc::untranslatable_diagnostic, + reason = "cannot translate user-provided messages" + )] + let mut diag = handler.struct_diagnostic(self.msg_from_user.to_string()); diag.set_span(self.span); - if let Some(help) = self.help { - diag.subdiagnostic(help); - } diag } } -#[derive(Subdiagnostic)] -pub(crate) enum EnvNotDefinedHelp { +#[derive(Diagnostic)] +pub(crate) enum EnvNotDefined<'a> { + #[diag(builtin_macros_env_not_defined)] #[help(builtin_macros_cargo)] - CargoVar, - #[help(builtin_macros_other)] - Other, + CargoEnvVar { + #[primary_span] + span: Span, + var: Symbol, + var_expr: &'a rustc_ast::Expr, + }, + #[diag(builtin_macros_env_not_defined)] + #[help(builtin_macros_custom)] + CustomEnvVar { + #[primary_span] + span: Span, + var: Symbol, + var_expr: &'a rustc_ast::Expr, + }, } #[derive(Diagnostic)] @@ -732,3 +813,10 @@ pub(crate) struct TestRunnerNargs { #[primary_span] pub(crate) span: Span, } + +#[derive(Diagnostic)] +#[diag(builtin_macros_expected_register_class_or_explicit_register)] +pub(crate) struct ExpectedRegisterClassOrExplicitRegister { + #[primary_span] + pub(crate) span: Span, +} diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index c59a733c05568..ede95dbf897e7 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,6 +1,6 @@ use rustc_ast::ptr::P; -use rustc_ast::token; use rustc_ast::tokenstream::TokenStream; +use rustc_ast::{token, StmtKind}; use rustc_ast::{ Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount, @@ -163,7 +163,7 @@ fn make_format_args( let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input; - let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { + let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt.clone(), msg) { Ok(mut fmt) if append_newline => { fmt.0 = Symbol::intern(&format!("{}\n", fmt.0)); fmt @@ -171,17 +171,33 @@ fn make_format_args( Ok(fmt) => fmt, Err(err) => { if let Some((mut err, suggested)) = err { - let sugg_fmt = match args.explicit_args().len() { - 0 => "{}".to_string(), - _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())), - }; if !suggested { - err.span_suggestion( - unexpanded_fmt_span.shrink_to_lo(), - "you might be missing a string literal to format with", - format!("\"{}\", ", sugg_fmt), - Applicability::MaybeIncorrect, - ); + if let ExprKind::Block(block, None) = &efmt.kind + && block.stmts.len() == 1 + && let StmtKind::Expr(expr) = &block.stmts[0].kind + && let ExprKind::Path(None, path) = &expr.kind + && path.is_potential_trivial_const_arg() + { + err.multipart_suggestion( + "quote your inlined format argument to use as string literal", + vec![ + (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()), + (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } else { + let sugg_fmt = match args.explicit_args().len() { + 0 => "{}".to_string(), + _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())), + }; + err.span_suggestion( + unexpanded_fmt_span.shrink_to_lo(), + "you might be missing a string literal to format with", + format!("\"{sugg_fmt}\", "), + Applicability::MaybeIncorrect, + ); + } } err.emit(); } @@ -554,9 +570,6 @@ fn report_missing_placeholders( fmt_span: Span, ) { let mut diag = if let &[(span, named)] = &unused[..] { - //let mut diag = ecx.struct_span_err(span, msg); - //diag.span_label(span, msg); - //diag ecx.create_err(errors::FormatUnusedArg { span, named }) } else { let unused_labels = @@ -671,7 +684,7 @@ fn report_invalid_references( let num_args_desc = match args.explicit_args().len() { 0 => "no arguments were given".to_string(), 1 => "there is 1 argument".to_string(), - n => format!("there are {} arguments", n), + n => format!("there are {n} arguments"), }; let mut e; @@ -783,7 +796,7 @@ fn report_invalid_references( if num_placeholders == 1 { "is 1 argument".to_string() } else { - format!("are {} arguments", num_placeholders) + format!("are {num_placeholders} arguments") }, ), ); @@ -814,7 +827,7 @@ fn report_invalid_references( }; e = ecx.struct_span_err( span, - format!("invalid reference to positional {} ({})", arg_list, num_args_desc), + format!("invalid reference to positional {arg_list} ({num_args_desc})"), ); e.note("positional arguments are zero-based"); } diff --git a/compiler/rustc_builtin_macros/src/format_foreign.rs b/compiler/rustc_builtin_macros/src/format_foreign.rs index bd5356575ca87..2fc8a07636687 100644 --- a/compiler/rustc_builtin_macros/src/format_foreign.rs +++ b/compiler/rustc_builtin_macros/src/format_foreign.rs @@ -86,10 +86,7 @@ pub(crate) mod printf { '-' => c_left = true, '+' => c_plus = true, _ => { - return Err(Some(format!( - "the flag `{}` is unknown or unsupported", - c - ))); + return Err(Some(format!("the flag `{c}` is unknown or unsupported"))); } } } @@ -268,21 +265,21 @@ pub(crate) mod printf { impl Num { fn from_str(s: &str, arg: Option<&str>) -> Self { if let Some(arg) = arg { - Num::Arg(arg.parse().unwrap_or_else(|_| panic!("invalid format arg `{:?}`", arg))) + Num::Arg(arg.parse().unwrap_or_else(|_| panic!("invalid format arg `{arg:?}`"))) } else if s == "*" { Num::Next } else { - Num::Num(s.parse().unwrap_or_else(|_| panic!("invalid format num `{:?}`", s))) + Num::Num(s.parse().unwrap_or_else(|_| panic!("invalid format num `{s:?}`"))) } } fn translate(&self, s: &mut String) -> std::fmt::Result { use std::fmt::Write; match *self { - Num::Num(n) => write!(s, "{}", n), + Num::Num(n) => write!(s, "{n}"), Num::Arg(n) => { let n = n.checked_sub(1).ok_or(std::fmt::Error)?; - write!(s, "{}$", n) + write!(s, "{n}$") } Num::Next => write!(s, "*"), } @@ -626,8 +623,8 @@ pub mod shell { impl Substitution<'_> { pub fn as_str(&self) -> String { match self { - Substitution::Ordinal(n, _) => format!("${}", n), - Substitution::Name(n, _) => format!("${}", n), + Substitution::Ordinal(n, _) => format!("${n}"), + Substitution::Name(n, _) => format!("${n}"), Substitution::Escape(_) => "$$".into(), } } diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs index 866cc5adbf3b1..1bec00add5527 100644 --- a/compiler/rustc_builtin_macros/src/global_allocator.rs +++ b/compiler/rustc_builtin_macros/src/global_allocator.rs @@ -1,7 +1,8 @@ use crate::util::check_builtin_macro_attribute; +use crate::errors; use rustc_ast::expand::allocator::{ - AllocatorKind, AllocatorMethod, AllocatorTy, ALLOCATOR_METHODS, + global_fn_name, AllocatorMethod, AllocatorMethodInput, AllocatorTy, ALLOCATOR_METHODS, }; use rustc_ast::ptr::P; use rustc_ast::{self as ast, AttrVec, Expr, FnHeader, FnSig, Generics, Param, StmtKind}; @@ -34,14 +35,13 @@ pub fn expand( { (item, true, ecx.with_def_site_ctxt(ty.span)) } else { - ecx.sess.parse_sess.span_diagnostic.span_err(item.span(), "allocators must be statics"); + ecx.sess.parse_sess.span_diagnostic.emit_err(errors::AllocMustStatics{span: item.span()}); return vec![orig_item]; }; // Generate a bunch of new items using the AllocFnFactory let span = ecx.with_def_site_ctxt(item.span); - let f = - AllocFnFactory { span, ty_span, kind: AllocatorKind::Global, global: item.ident, cx: ecx }; + let f = AllocFnFactory { span, ty_span, global: item.ident, cx: ecx }; // Generate item statements for the allocator methods. let stmts = ALLOCATOR_METHODS.iter().map(|method| f.allocator_fn(method)).collect(); @@ -63,7 +63,6 @@ pub fn expand( struct AllocFnFactory<'a, 'b> { span: Span, ty_span: Span, - kind: AllocatorKind, global: Ident, cx: &'b ExtCtxt<'a>, } @@ -71,19 +70,13 @@ struct AllocFnFactory<'a, 'b> { impl AllocFnFactory<'_, '_> { fn allocator_fn(&self, method: &AllocatorMethod) -> Stmt { let mut abi_args = ThinVec::new(); - let mut i = 0; - let mut mk = || { - let name = Ident::from_str_and_span(&format!("arg{}", i), self.span); - i += 1; - name - }; - let args = method.inputs.iter().map(|ty| self.arg_ty(ty, &mut abi_args, &mut mk)).collect(); + let args = method.inputs.iter().map(|input| self.arg_ty(input, &mut abi_args)).collect(); let result = self.call_allocator(method.name, args); - let (output_ty, output_expr) = self.ret_ty(&method.output, result); + let output_ty = self.ret_ty(&method.output); let decl = self.cx.fn_decl(abi_args, ast::FnRetTy::Ty(output_ty)); let header = FnHeader { unsafety: Unsafe::Yes(self.span), ..FnHeader::default() }; let sig = FnSig { decl, header, span: self.span }; - let body = Some(self.cx.block_expr(output_expr)); + let body = Some(self.cx.block_expr(result)); let kind = ItemKind::Fn(Box::new(Fn { defaultness: ast::Defaultness::Final, sig, @@ -92,7 +85,7 @@ impl AllocFnFactory<'_, '_> { })); let item = self.cx.item( self.span, - Ident::from_str_and_span(&self.kind.fn_name(method.name), self.span), + Ident::from_str_and_span(&global_fn_name(method.name), self.span), self.attrs(), kind, ); @@ -114,18 +107,19 @@ impl AllocFnFactory<'_, '_> { thin_vec![self.cx.attr_word(sym::rustc_std_internal_symbol, self.span)] } - fn arg_ty( - &self, - ty: &AllocatorTy, - args: &mut ThinVec, - ident: &mut dyn FnMut() -> Ident, - ) -> P { - match *ty { + fn arg_ty(&self, input: &AllocatorMethodInput, args: &mut ThinVec) -> P { + match input.ty { AllocatorTy::Layout => { + // If an allocator method is ever introduced having multiple + // Layout arguments, these argument names need to be + // disambiguated somehow. Currently the generated code would + // fail to compile with "identifier is bound more than once in + // this parameter list". + let size = Ident::from_str_and_span("size", self.span); + let align = Ident::from_str_and_span("align", self.span); + let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span)); let ty_usize = self.cx.ty_path(usize); - let size = ident(); - let align = ident(); args.push(self.cx.param(self.span, size, ty_usize.clone())); args.push(self.cx.param(self.span, align, ty_usize)); @@ -139,14 +133,13 @@ impl AllocFnFactory<'_, '_> { } AllocatorTy::Ptr => { - let ident = ident(); + let ident = Ident::from_str_and_span(input.name, self.span); args.push(self.cx.param(self.span, ident, self.ptr_u8())); - let arg = self.cx.expr_ident(self.span, ident); - self.cx.expr_cast(self.span, arg, self.ptr_u8()) + self.cx.expr_ident(self.span, ident) } AllocatorTy::Usize => { - let ident = ident(); + let ident = Ident::from_str_and_span(input.name, self.span); args.push(self.cx.param(self.span, ident, self.usize())); self.cx.expr_ident(self.span, ident) } @@ -157,18 +150,11 @@ impl AllocFnFactory<'_, '_> { } } - fn ret_ty(&self, ty: &AllocatorTy, expr: P) -> (P, P) { + fn ret_ty(&self, ty: &AllocatorTy) -> P { match *ty { - AllocatorTy::ResultPtr => { - // We're creating: - // - // #expr as *mut u8 - - let expr = self.cx.expr_cast(self.span, expr, self.ptr_u8()); - (self.ptr_u8(), expr) - } + AllocatorTy::ResultPtr => self.ptr_u8(), - AllocatorTy::Unit => (self.cx.ty(self.span, TyKind::Tup(ThinVec::new())), expr), + AllocatorTy::Unit => self.cx.ty(self.span, TyKind::Tup(ThinVec::new())), AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { panic!("can't convert `AllocatorTy` to an output") diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index 52b5601bb11b8..dae1bc5bfe5bf 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -1,9 +1,11 @@ +use crate::errors; use rustc_ast::ptr::P; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, attr, NodeId}; use rustc_ast_pretty::pprust; use rustc_expand::base::{parse_macro_name_and_helper_attrs, ExtCtxt, ResolverExpand}; use rustc_expand::expand::{AstFragment, ExpansionConfig}; +use rustc_feature::Features; use rustc_session::Session; use rustc_span::hygiene::AstPass; use rustc_span::source_map::SourceMap; @@ -45,13 +47,14 @@ struct CollectProcMacros<'a> { pub fn inject( krate: &mut ast::Crate, sess: &Session, + features: &Features, resolver: &mut dyn ResolverExpand, is_proc_macro_crate: bool, has_proc_macro_decls: bool, is_test_crate: bool, handler: &rustc_errors::Handler, ) { - let ecfg = ExpansionConfig::default("proc_macro".to_string()); + let ecfg = ExpansionConfig::default("proc_macro".to_string(), features); let mut cx = ExtCtxt::new(sess, ecfg, resolver, None); let mut collect = CollectProcMacros { @@ -83,17 +86,14 @@ pub fn inject( impl<'a> CollectProcMacros<'a> { fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) { if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() { - self.handler.span_err( - sp, - "`proc-macro` crate types currently cannot export any items other \ - than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, \ - or `#[proc_macro_attribute]`", - ); + self.handler.emit_err(errors::ProcMacro { span: sp }); } } fn collect_custom_derive(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) { - let Some((trait_name, proc_attrs)) = parse_macro_name_and_helper_attrs(self.handler, attr, "derive") else { + let Some((trait_name, proc_attrs)) = + parse_macro_name_and_helper_attrs(self.handler, attr, "derive") + else { return; }; @@ -157,9 +157,9 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { fn visit_item(&mut self, item: &'a ast::Item) { if let ast::ItemKind::MacroDef(..) = item.kind { if self.is_proc_macro_crate && attr::contains_name(&item.attrs, sym::macro_export) { - let msg = - "cannot export macro_rules! macros from a `proc-macro` crate type currently"; - self.handler.span_err(self.source_map.guess_head_span(item.span), msg); + self.handler.emit_err(errors::ExportMacroRules { + span: self.source_map.guess_head_span(item.span), + }); } } @@ -181,8 +181,7 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> { == prev_item.path.segments[0].ident.name { format!( - "only one `#[{}]` attribute is allowed on any given function", - path_str, + "only one `#[{path_str}]` attribute is allowed on any given function", ) } else { format!( diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index e613b904d2eaf..433da74231f7a 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -149,7 +149,7 @@ pub fn expand_include<'cx>( Ok(None) => { if self.p.token != token::Eof { let token = pprust::token_to_string(&self.p.token); - let msg = format!("expected item, found `{}`", token); + let msg = format!("expected item, found `{token}`"); self.p.struct_span_err(self.p.token.span, msg).emit(); } diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs index 6493c6f13d541..3ee3112f021ca 100644 --- a/compiler/rustc_builtin_macros/src/standard_library_imports.rs +++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs @@ -1,6 +1,7 @@ use rustc_ast::{self as ast, attr}; use rustc_expand::base::{ExtCtxt, ResolverExpand}; use rustc_expand::expand::ExpansionConfig; +use rustc_feature::Features; use rustc_session::Session; use rustc_span::edition::Edition::*; use rustc_span::hygiene::AstPass; @@ -13,6 +14,7 @@ pub fn inject( pre_configured_attrs: &[ast::Attribute], resolver: &mut dyn ResolverExpand, sess: &Session, + features: &Features, ) -> usize { let orig_num_items = krate.items.len(); let edition = sess.parse_sess.edition; @@ -39,25 +41,34 @@ pub fn inject( let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id()); let call_site = DUMMY_SP.with_call_site_ctxt(expn_id.to_expn_id()); - let ecfg = ExpansionConfig::default("std_lib_injection".to_string()); + let ecfg = ExpansionConfig::default("std_lib_injection".to_string(), features); let cx = ExtCtxt::new(sess, ecfg, resolver, None); // .rev() to preserve ordering above in combination with insert(0, ...) for &name in names.iter().rev() { - let ident = if edition >= Edition2018 { - Ident::new(name, span) + let ident_span = if edition >= Edition2018 { span } else { call_site }; + let item = if name == sym::compiler_builtins { + // compiler_builtins is a private implementation detail. We only + // need to insert it into the crate graph for linking and should not + // expose any of its public API. + // + // FIXME(#113634) We should inject this during post-processing like + // we do for the panic runtime, profiler runtime, etc. + cx.item( + span, + Ident::new(kw::Underscore, ident_span), + thin_vec![], + ast::ItemKind::ExternCrate(Some(name)), + ) } else { - Ident::new(name, call_site) - }; - krate.items.insert( - 0, cx.item( span, - ident, + Ident::new(name, ident_span), thin_vec![cx.attr_word(sym::macro_use, span)], ast::ItemKind::ExternCrate(None), - ), - ); + ) + }; + krate.items.insert(0, item); } // The crates have been injected, the assumption is that the first one is diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 49ee276af4e6f..1580a6f6dd3ca 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -3,12 +3,12 @@ use crate::errors; /// Ideally, this code would be in libtest but for efficiency and error messages it lives here. use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; use rustc_ast::ptr::P; -use rustc_ast::{self as ast, attr}; +use rustc_ast::{self as ast, attr, GenericParamKind}; use rustc_ast_pretty::pprust; use rustc_errors::Applicability; use rustc_expand::base::*; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_span::{FileNameDisplayPreference, Span}; +use rustc_span::{ErrorGuaranteed, FileNameDisplayPreference, Span}; use std::iter; use thin_vec::{thin_vec, ThinVec}; @@ -122,23 +122,26 @@ pub fn expand_test_or_bench( let ast::ItemKind::Fn(fn_) = &item.kind else { not_testable_error(cx, attr_sp, Some(&item)); return if is_stmt { - vec![Annotatable::Stmt(P(ast::Stmt { - id: ast::DUMMY_NODE_ID, - span: item.span, - kind: ast::StmtKind::Item(item), - }))] + vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))] } else { vec![Annotatable::Item(item)] }; }; - // has_*_signature will report any errors in the type so compilation + // check_*_signature will report any errors in the type so compilation // will fail. We shouldn't try to expand in this case because the errors // would be spurious. - if (!is_bench && !has_test_signature(cx, &item)) - || (is_bench && !has_bench_signature(cx, &item)) - { - return vec![Annotatable::Item(item)]; + let check_result = if is_bench { + check_bench_signature(cx, &item, &fn_) + } else { + check_test_signature(cx, &item, &fn_) + }; + if check_result.is_err() { + return if is_stmt { + vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))] + } else { + vec![Annotatable::Item(item)] + }; } let sp = cx.with_def_site_ctxt(item.span); @@ -252,6 +255,7 @@ pub fn expand_test_or_bench( ast::ItemKind::Const( ast::ConstItem { defaultness: ast::Defaultness::Final, + generics: ast::Generics::default(), ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), // test::TestDescAndFn { expr: Some( @@ -523,75 +527,57 @@ fn test_type(cx: &ExtCtxt<'_>) -> TestType { } } -fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { +fn check_test_signature( + cx: &ExtCtxt<'_>, + i: &ast::Item, + f: &ast::Fn, +) -> Result<(), ErrorGuaranteed> { let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic); let sd = &cx.sess.parse_sess.span_diagnostic; - match &i.kind { - ast::ItemKind::Fn(box ast::Fn { sig, generics, .. }) => { - if let ast::Unsafe::Yes(span) = sig.header.unsafety { - sd.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "unsafe" }); - return false; - } - if let ast::Async::Yes { span, .. } = sig.header.asyncness { - sd.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "async" }); - return false; - } - // If the termination trait is active, the compiler will check that the output - // type implements the `Termination` trait as `libtest` enforces that. - let has_output = match &sig.decl.output { - ast::FnRetTy::Default(..) => false, - ast::FnRetTy::Ty(t) if t.kind.is_unit() => false, - _ => true, - }; - - if !sig.decl.inputs.is_empty() { - sd.span_err(i.span, "functions used as tests can not have any arguments"); - return false; - } + if let ast::Unsafe::Yes(span) = f.sig.header.unsafety { + return Err(sd.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "unsafe" })); + } - match (has_output, has_should_panic_attr) { - (true, true) => { - sd.span_err(i.span, "functions using `#[should_panic]` must return `()`"); - false - } - (true, false) => { - if !generics.params.is_empty() { - sd.span_err( - i.span, - "functions used as tests must have signature fn() -> ()", - ); - false - } else { - true - } - } - (false, _) => true, - } - } - _ => { - // should be unreachable because `is_test_fn_item` should catch all non-fn items - debug_assert!(false); - false - } + if let ast::Async::Yes { span, .. } = f.sig.header.asyncness { + return Err(sd.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "async" })); } -} -fn has_bench_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool { - let has_sig = match &i.kind { - // N.B., inadequate check, but we're running - // well before resolve, can't get too deep. - ast::ItemKind::Fn(box ast::Fn { sig, .. }) => sig.decl.inputs.len() == 1, - _ => false, + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let has_output = match &f.sig.decl.output { + ast::FnRetTy::Default(..) => false, + ast::FnRetTy::Ty(t) if t.kind.is_unit() => false, + _ => true, }; - if !has_sig { - cx.sess.parse_sess.span_diagnostic.span_err( + if !f.sig.decl.inputs.is_empty() { + return Err(sd.span_err(i.span, "functions used as tests can not have any arguments")); + } + + if has_should_panic_attr && has_output { + return Err(sd.span_err(i.span, "functions using `#[should_panic]` must return `()`")); + } + + if f.generics.params.iter().any(|param| !matches!(param.kind, GenericParamKind::Lifetime)) { + return Err(sd.span_err( i.span, - "functions used as benches must have \ - signature `fn(&mut Bencher) -> impl Termination`", - ); + "functions used as tests can not have any non-lifetime generic parameters", + )); } - has_sig + Ok(()) +} + +fn check_bench_signature( + cx: &ExtCtxt<'_>, + i: &ast::Item, + f: &ast::Fn, +) -> Result<(), ErrorGuaranteed> { + // N.B., inadequate check, but we're running + // well before resolve, can't get too deep. + if f.sig.decl.inputs.len() != 1 { + return Err(cx.sess.parse_sess.span_diagnostic.emit_err(errors::BenchSig { span: i.span })); + } + Ok(()) } diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 9bc1e27b4ec74..d8846a9f0aa3f 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -4,10 +4,12 @@ use rustc_ast as ast; use rustc_ast::entry::EntryPointType; use rustc_ast::mut_visit::{ExpectOne, *}; use rustc_ast::ptr::P; +use rustc_ast::visit::{walk_item, Visitor}; use rustc_ast::{attr, ModKind}; use rustc_expand::base::{ExtCtxt, ResolverExpand}; use rustc_expand::expand::{AstFragment, ExpansionConfig}; use rustc_feature::Features; +use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS; use rustc_session::Session; use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency}; use rustc_span::symbol::{sym, Ident, Symbol}; @@ -39,7 +41,12 @@ struct TestCtxt<'a> { /// Traverse the crate, collecting all the test functions, eliding any /// existing main functions, and synthesizing a main test harness -pub fn inject(krate: &mut ast::Crate, sess: &Session, resolver: &mut dyn ResolverExpand) { +pub fn inject( + krate: &mut ast::Crate, + sess: &Session, + features: &Features, + resolver: &mut dyn ResolverExpand, +) { let span_diagnostic = sess.diagnostic(); let panic_strategy = sess.panic_strategy(); let platform_panic_strategy = sess.target.panic_strategy; @@ -63,10 +70,7 @@ pub fn inject(krate: &mut ast::Crate, sess: &Session, resolver: &mut dyn Resolve // Silently allow compiling with panic=abort on these platforms, // but with old behavior (abort if a test fails). } else { - span_diagnostic.err( - "building tests with panic=abort is not supported \ - without `-Zpanic_abort_tests`", - ); + span_diagnostic.emit_err(errors::TestsNotSupport {}); } PanicStrategy::Unwind } @@ -77,7 +81,7 @@ pub fn inject(krate: &mut ast::Crate, sess: &Session, resolver: &mut dyn Resolve resolver, reexport_test_harness_main, krate, - &sess.features_untracked(), + features, panic_strategy, test_runner, ) @@ -140,11 +144,31 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> { let prev_tests = mem::take(&mut self.tests); noop_visit_item_kind(&mut item.kind, self); self.add_test_cases(item.id, span, prev_tests); + } else { + // But in those cases, we emit a lint to warn the user of these missing tests. + walk_item(&mut InnerItemLinter { sess: self.cx.ext_cx.sess }, &item); } smallvec![P(item)] } } +struct InnerItemLinter<'a> { + sess: &'a Session, +} + +impl<'a> Visitor<'a> for InnerItemLinter<'_> { + fn visit_item(&mut self, i: &'a ast::Item) { + if let Some(attr) = attr::find_by_name(&i.attrs, sym::rustc_test_marker) { + self.sess.parse_sess.buffer_lint( + UNNAMEABLE_TEST_ITEMS, + attr.span, + i.id, + crate::fluent_generated::builtin_macros_unnameable_test_items, + ); + } + } +} + // Beware, this is duplicated in librustc_passes/entry.rs (with // `rustc_hir::Item`), so make sure to keep them in sync. fn entry_point_type(item: &ast::Item, depth: usize) -> EntryPointType { @@ -224,9 +248,7 @@ fn generate_test_harness( panic_strategy: PanicStrategy, test_runner: Option, ) { - let mut econfig = ExpansionConfig::default("test".to_string()); - econfig.features = Some(features); - + let econfig = ExpansionConfig::default("test".to_string(), features); let ext_cx = ExtCtxt::new(sess, econfig, resolver, None); let expn_id = ext_cx.resolver.expansion_for_ast_pass( diff --git a/compiler/rustc_builtin_macros/src/trace_macros.rs b/compiler/rustc_builtin_macros/src/trace_macros.rs index 9c98723e1f43f..af1a392acc5e5 100644 --- a/compiler/rustc_builtin_macros/src/trace_macros.rs +++ b/compiler/rustc_builtin_macros/src/trace_macros.rs @@ -1,3 +1,4 @@ +use crate::errors; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_expand::base::{self, ExtCtxt}; use rustc_span::symbol::kw; @@ -20,7 +21,7 @@ pub fn expand_trace_macros( }; err |= cursor.next().is_some(); if err { - cx.span_err(sp, "trace_macros! accepts only `true` or `false`") + cx.emit_err(errors::TraceMacros { span: sp }); } else { cx.set_trace_macros(value); } diff --git a/compiler/rustc_codegen_cranelift/.cirrus.yml b/compiler/rustc_codegen_cranelift/.cirrus.yml index 7886cae42a15a..8b4efd4e39488 100644 --- a/compiler/rustc_codegen_cranelift/.cirrus.yml +++ b/compiler/rustc_codegen_cranelift/.cirrus.yml @@ -1,16 +1,16 @@ task: name: freebsd freebsd_instance: - image: freebsd-13-1-release-amd64 + image: freebsd-13-2-release-amd64 setup_rust_script: - - pkg install -y curl git bash + - pkg install -y git bash - curl https://sh.rustup.rs -sSf --output rustup.sh - sh rustup.sh --default-toolchain none -y --profile=minimal target_cache: folder: target prepare_script: - . $HOME/.cargo/env - - ./y.rs prepare + - ./y.sh prepare test_script: - . $HOME/.cargo/env - - ./y.rs test + - ./y.sh test diff --git a/compiler/rustc_codegen_cranelift/.github/workflows/abi-cafe.yml b/compiler/rustc_codegen_cranelift/.github/workflows/abi-cafe.yml index 3c40555669cb3..12aa69d3c7956 100644 --- a/compiler/rustc_codegen_cranelift/.github/workflows/abi-cafe.yml +++ b/compiler/rustc_codegen_cranelift/.github/workflows/abi-cafe.yml @@ -46,12 +46,12 @@ jobs: run: rustup set default-host x86_64-pc-windows-gnu - name: Prepare dependencies - run: ./y.rs prepare + run: ./y.sh prepare - name: Build - run: ./y.rs build --sysroot none + run: ./y.sh build --sysroot none - name: Test abi-cafe env: TARGET_TRIPLE: ${{ matrix.env.TARGET_TRIPLE }} - run: ./y.rs abi-cafe + run: ./y.sh abi-cafe diff --git a/compiler/rustc_codegen_cranelift/.github/workflows/audit.yml b/compiler/rustc_codegen_cranelift/.github/workflows/audit.yml new file mode 100644 index 0000000000000..3efdec4155932 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/.github/workflows/audit.yml @@ -0,0 +1,19 @@ +name: Security audit +on: + workflow_dispatch: + schedule: + - cron: '0 10 * * 1' # every monday at 10:00 UTC +permissions: + issues: write + checks: write +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: | + sed -i 's/components.*/components = []/' rust-toolchain + echo 'profile = "minimal"' >> rust-toolchain + - uses: rustsec/audit-check@v1.4.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/compiler/rustc_codegen_cranelift/.github/workflows/main.yml b/compiler/rustc_codegen_cranelift/.github/workflows/main.yml index e4af73ea6442e..652d6eca3f6e9 100644 --- a/compiler/rustc_codegen_cranelift/.github/workflows/main.yml +++ b/compiler/rustc_codegen_cranelift/.github/workflows/main.yml @@ -12,14 +12,16 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Install rustfmt + - name: Avoid installing rustc-dev run: | - rustup component add rustfmt + sed -i 's/components.*/components = ["rustfmt"]/' rust-toolchain + echo 'profile = "minimal"' >> rust-toolchain + rustfmt -v - name: Rustfmt run: | cargo fmt --check - rustfmt --check build_system/mod.rs + rustfmt --check build_system/main.rs rustfmt --check example/* @@ -91,22 +93,52 @@ jobs: sudo apt-get install -y gcc-s390x-linux-gnu qemu-user - name: Prepare dependencies - run: ./y.rs prepare + run: ./y.sh prepare - - name: Build without unstable features + - name: Build + run: ./y.sh build --sysroot none + + - name: Test env: TARGET_TRIPLE: ${{ matrix.env.TARGET_TRIPLE }} - # This is the config rust-lang/rust uses for builds - run: ./y.rs build --no-unstable-features + run: ./y.sh test - - name: Build - run: ./y.rs build --sysroot none + - name: Install LLVM standard library + run: rustup target add ${{ matrix.env.TARGET_TRIPLE }} - - name: Test + # This is roughly config rust-lang/rust uses for testing + - name: Test with LLVM sysroot + # Skip native x86_64-pc-windows-gnu. It is way too slow and cross-compiled + # x86_64-pc-windows-gnu covers at least part of the tests. + if: matrix.os != 'windows-latest' || matrix.env.TARGET_TRIPLE != 'x86_64-pc-windows-gnu' env: TARGET_TRIPLE: ${{ matrix.env.TARGET_TRIPLE }} - run: ./y.rs test + run: ./y.sh test --sysroot llvm --no-unstable-features + + + # This job doesn't use cg_clif in any way. It checks that all cg_clif tests work with cg_llvm too. + test_llvm: + runs-on: ubuntu-latest + timeout-minutes: 60 + + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v3 + - name: Prepare dependencies + run: ./y.sh prepare + + - name: Disable JIT tests + run: | + sed -i 's/jit./#jit./' config.txt + + - name: Test + env: + TARGET_TRIPLE: x86_64-unknown-linux-gnu + run: ./y.sh test --use-backend llvm bench: runs-on: ubuntu-latest @@ -135,13 +167,13 @@ jobs: run: cargo install hyperfine || true - name: Prepare dependencies - run: ./y.rs prepare + run: ./y.sh prepare - name: Build - run: CI_OPT=1 ./y.rs build --sysroot none + run: CI_OPT=1 ./y.sh build --sysroot none - name: Benchmark - run: CI_OPT=1 ./y.rs bench + run: CI_OPT=1 ./y.sh bench dist: @@ -194,13 +226,13 @@ jobs: sudo apt-get install -y gcc-mingw-w64-x86-64 wine-stable - name: Prepare dependencies - run: ./y.rs prepare + run: ./y.sh prepare - name: Build backend - run: CI_OPT=1 ./y.rs build --sysroot none + run: CI_OPT=1 ./y.sh build --sysroot none - name: Build sysroot - run: CI_OPT=1 ./y.rs build + run: CI_OPT=1 ./y.sh build - name: Package prebuilt cg_clif run: tar cvfJ cg_clif.tar.xz dist diff --git a/compiler/rustc_codegen_cranelift/.github/workflows/rustc.yml b/compiler/rustc_codegen_cranelift/.github/workflows/rustc.yml index b2f772c4fc444..b49dc3aff7aaa 100644 --- a/compiler/rustc_codegen_cranelift/.github/workflows/rustc.yml +++ b/compiler/rustc_codegen_cranelift/.github/workflows/rustc.yml @@ -18,7 +18,7 @@ jobs: key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('rust-toolchain', '**/Cargo.lock') }} - name: Prepare dependencies - run: ./y.rs prepare + run: ./y.sh prepare - name: Test run: ./scripts/test_bootstrap.sh @@ -38,7 +38,7 @@ jobs: key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('rust-toolchain', '**/Cargo.lock') }} - name: Prepare dependencies - run: ./y.rs prepare + run: ./y.sh prepare - name: Test run: ./scripts/test_rustc_tests.sh diff --git a/compiler/rustc_codegen_cranelift/.gitignore b/compiler/rustc_codegen_cranelift/.gitignore index e5d10a937ae24..e6ac8c8408da6 100644 --- a/compiler/rustc_codegen_cranelift/.gitignore +++ b/compiler/rustc_codegen_cranelift/.gitignore @@ -1,4 +1,5 @@ /target +/build_system/target **/*.rs.bk *.rlib *.o diff --git a/compiler/rustc_codegen_cranelift/.vscode/settings.json b/compiler/rustc_codegen_cranelift/.vscode/settings.json index 7c8703cba505c..60cb51d566362 100644 --- a/compiler/rustc_codegen_cranelift/.vscode/settings.json +++ b/compiler/rustc_codegen_cranelift/.vscode/settings.json @@ -6,9 +6,10 @@ "rust-analyzer.imports.granularity.enforce": true, "rust-analyzer.imports.granularity.group": "module", "rust-analyzer.imports.prefix": "crate", - "rust-analyzer.cargo.features": ["unstable-features", "__check_build_system_using_ra"], + "rust-analyzer.cargo.features": ["unstable-features"], "rust-analyzer.linkedProjects": [ "./Cargo.toml", + "./build_system/Cargo.toml", { "crates": [ { diff --git a/compiler/rustc_codegen_cranelift/Cargo.lock b/compiler/rustc_codegen_cranelift/Cargo.lock index 07a8e431a0e31..af8e43da4eafe 100644 --- a/compiler/rustc_codegen_cranelift/Cargo.lock +++ b/compiler/rustc_codegen_cranelift/Cargo.lock @@ -19,6 +19,12 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +[[package]] +name = "arbitrary" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" + [[package]] name = "autocfg" version = "1.1.0" @@ -37,12 +43,6 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "cfg-if" version = "1.0.0" @@ -51,23 +51,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cranelift-bforest" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1277fbfa94bc82c8ec4af2ded3e639d49ca5f7f3c7eeab2c66accd135ece4e70" +checksum = "ec27af72e56235eb326b5bf2de4e70ab7c5ac1fb683a1829595badaf821607fd" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e8c31ad3b2270e9aeec38723888fe1b0ace3bea2b06b3f749ccf46661d3220" +checksum = "2231e12925e6c5f4bc9c95b62a798eea6ed669a95bc3e00f8b2adb3b7b9b7a80" dependencies = [ "bumpalo", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", + "cranelift-control", "cranelift-entity", "cranelift-isle", "gimli", @@ -80,30 +81,39 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ac5ac30d62b2d66f12651f6b606dbdfd9c2cfd0908de6b387560a277c5c9da" +checksum = "413b00b8dfb3aab85674a534677e7ca08854b503f164a70ec0634fce80996e2c" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.95.1" +version = "0.98.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0feb9ecc8193ef5cb04f494c5bd835e5bfec4bde726e7ac0444fc9dd76229e" + +[[package]] +name = "cranelift-control" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd82b8b376247834b59ed9bdc0ddeb50f517452827d4a11bccf5937b213748b8" +checksum = "72eedd2afcf5fee1e042eaaf18d3750e48ad0eca364a9f5971ecfdd5ef85bf71" +dependencies = [ + "arbitrary", +] [[package]] name = "cranelift-entity" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" +checksum = "7af19157be42671073cf8c2a52d6a4ae1e7b11f1dcb4131fede356d9f91c29dd" [[package]] name = "cranelift-frontend" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a25d9d0a0ae3079c463c34115ec59507b4707175454f0eee0891e83e30e82d" +checksum = "c2dc7636c5fad156be7d9ae691cd1aaecd97326caf2ab534ba168056d56aa76c" dependencies = [ "cranelift-codegen", "log", @@ -113,18 +123,19 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80de6a7d0486e4acbd5f9f87ec49912bf4c8fb6aea00087b989685460d4469ba" +checksum = "c1111aea4fb6fade5779903f184249a3fc685a799fe4ec59126f9af59c7c2a74" [[package]] name = "cranelift-jit" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca96b05988aa057eda09a817a6e31915fabd7f476b513123aff08053cd193dd" +checksum = "dadf88076317f6286ec77ebbe65978734fb43b6befdc96f52ff4c4c511841644" dependencies = [ "anyhow", "cranelift-codegen", + "cranelift-control", "cranelift-entity", "cranelift-module", "cranelift-native", @@ -138,19 +149,20 @@ dependencies = [ [[package]] name = "cranelift-module" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5112c0be9cc5da064e0620570d67852f11ce44f2e572a58ecf7f11df73978b8" +checksum = "c6bae8a82dbf82241b1083e57e06870d2c2bdc9852727be99d58477513816953" dependencies = [ "anyhow", "cranelift-codegen", + "cranelift-control", ] [[package]] name = "cranelift-native" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb6b03e0e03801c4b3fd8ce0758a94750c07a44e7944cc0ffbf0d3f2e7c79b00" +checksum = "1ecfc01a634448468a698beac433d98040033046678a0eed3ca39a3a9f63ae86" dependencies = [ "cranelift-codegen", "libc", @@ -159,12 +171,13 @@ dependencies = [ [[package]] name = "cranelift-object" -version = "0.95.1" +version = "0.98.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ed1b37d0972abe804cb5bf2b35f3a76a276ebbe148e3a726d8e31042790978" +checksum = "0ee14a7276999f0dcaae2de84043e2c2de50820fb89b3db56fab586a4ad26734" dependencies = [ "anyhow", "cranelift-codegen", + "cranelift-control", "cranelift-module", "log", "object", @@ -181,19 +194,16 @@ dependencies = [ ] [[package]] -name = "fallible-iterator" -version = "0.2.0" +name = "equivalent" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" [[package]] -name = "fxhash" -version = "0.2.1" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "gimli" @@ -202,7 +212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 1.9.3", "stable_deref_trait", ] @@ -221,6 +231,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "indexmap" version = "1.9.3" @@ -231,6 +247,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "libc" version = "0.2.138" @@ -273,13 +299,13 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "object" -version = "0.30.3" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ "crc32fast", "hashbrown 0.13.2", - "indexmap", + "indexmap 1.9.3", "memchr", ] @@ -291,12 +317,13 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "regalloc2" -version = "0.6.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" +checksum = "5b4dcbd3a2ae7fb94b5813fa0e957c6ab51bf5d0a8ee1b69e0c2d0f1e6eb8485" dependencies = [ - "fxhash", + "hashbrown 0.13.2", "log", + "rustc-hash", "slice-group-by", "smallvec", ] @@ -313,6 +340,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_codegen_cranelift" version = "0.1.0" @@ -324,10 +357,9 @@ dependencies = [ "cranelift-native", "cranelift-object", "gimli", - "indexmap", + "indexmap 2.0.0", "libloading", "object", - "once_cell", "smallvec", "target-lexicon", ] @@ -364,9 +396,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasmtime-jit-icache-coherence" -version = "8.0.1" +version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" +checksum = "e34eb67f0829a5614ec54716c8e0c9fe68fab7b9df3686c85f719c9d247f7169" dependencies = [ "cfg-if", "libc", @@ -397,18 +429,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -421,42 +453,42 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/compiler/rustc_codegen_cranelift/Cargo.toml b/compiler/rustc_codegen_cranelift/Cargo.toml index a2890f6ddf9d4..8ded81d7399b5 100644 --- a/compiler/rustc_codegen_cranelift/Cargo.toml +++ b/compiler/rustc_codegen_cranelift/Cargo.toml @@ -3,31 +3,23 @@ name = "rustc_codegen_cranelift" version = "0.1.0" edition = "2021" -[[bin]] -# This is used just to teach rust-analyzer how to check the build system. required-features is used -# to disable it for regular builds. -name = "y" -path = "./y.rs" -required-features = ["__check_build_system_using_ra"] - [lib] crate-type = ["dylib"] [dependencies] # These have to be in sync with each other -cranelift-codegen = { version = "0.95.1", features = ["unwind", "all-arch"] } -cranelift-frontend = { version = "0.95.1" } -cranelift-module = { version = "0.95.1" } -cranelift-native = { version = "0.95.1" } -cranelift-jit = { version = "0.95.1", optional = true } -cranelift-object = { version = "0.95.1" } +cranelift-codegen = { version = "0.98", features = ["unwind", "all-arch"] } +cranelift-frontend = { version = "0.98" } +cranelift-module = { version = "0.98" } +cranelift-native = { version = "0.98" } +cranelift-jit = { version = "0.98", optional = true } +cranelift-object = { version = "0.98" } target-lexicon = "0.12.0" gimli = { version = "0.27.2", default-features = false, features = ["write"]} object = { version = "0.30.3", default-features = false, features = ["std", "read_core", "write", "archive", "coff", "elf", "macho", "pe"] } -indexmap = "1.9.3" +indexmap = "2.0.0" libloading = { version = "0.7.3", optional = true } -once_cell = "1.10.0" smallvec = "1.8.1" [patch.crates-io] @@ -46,7 +38,6 @@ smallvec = "1.8.1" unstable-features = ["jit", "inline_asm"] jit = ["cranelift-jit", "libloading"] inline_asm = [] -__check_build_system_using_ra = [] [package.metadata.rust-analyzer] rustc_private = true diff --git a/compiler/rustc_codegen_cranelift/Readme.md b/compiler/rustc_codegen_cranelift/Readme.md index c5222982aa739..62eaef359af92 100644 --- a/compiler/rustc_codegen_cranelift/Readme.md +++ b/compiler/rustc_codegen_cranelift/Readme.md @@ -10,8 +10,8 @@ If not please open an issue. ```bash $ git clone https://github.com/bjorn3/rustc_codegen_cranelift $ cd rustc_codegen_cranelift -$ ./y.rs prepare -$ ./y.rs build +$ ./y.sh prepare +$ ./y.sh build ``` To run the test suite replace the last command with: @@ -20,9 +20,14 @@ To run the test suite replace the last command with: $ ./test.sh ``` -For more docs on how to build and test see [build_system/usage.txt](build_system/usage.txt) or the help message of `./y.rs`. +For more docs on how to build and test see [build_system/usage.txt](build_system/usage.txt) or the help message of `./y.sh`. + +## Precompiled builds Alternatively you can download a pre built version from the [releases] page. +Extract the `dist` directory in the archive anywhere you want. +If you want to use `cargo clif build` instead of having to specify the full path to the `cargo-clif` executable, you can add the `bin` subdirectory of the extracted `dist` directory to your `PATH`. +(tutorial [for Windows](https://stackoverflow.com/a/44272417), and [for Linux/MacOS](https://unix.stackexchange.com/questions/26047/how-to-correctly-add-a-path-to-path/26059#26059)). [releases]: https://github.com/bjorn3/rustc_codegen_cranelift/releases/tag/dev @@ -30,7 +35,7 @@ Alternatively you can download a pre built version from the [releases] page. rustc_codegen_cranelift can be used as a near-drop-in replacement for `cargo build` or `cargo run` for existing projects. -Assuming `$cg_clif_dir` is the directory you cloned this repo into and you followed the instructions (`y.rs prepare` and `y.rs build` or `test.sh`). +Assuming `$cg_clif_dir` is the directory you cloned this repo into and you followed the instructions (`y.sh prepare` and `y.sh build` or `test.sh`). In the directory with your project (where you can do the usual `cargo build`), run: @@ -42,6 +47,32 @@ This will build your project with rustc_codegen_cranelift instead of the usual L For additional ways to use rustc_codegen_cranelift like the JIT mode see [usage.md](docs/usage.md). +## Building and testing with changes in rustc code + +This is useful when changing code in `rustc_codegen_cranelift` as part of changing [main Rust repository](https://github.com/rust-lang/rust/). +This can happen, for example, when you are implementing a new compiler intrinsic. + +Instruction below uses `$RustCheckoutDir` as substitute for any folder where you cloned Rust repository. + +You need to do this steps to successfully compile and use the cranelift backend with your changes in rustc code: + +1. `cd $RustCheckoutDir` +2. Run `python x.py setup` and choose option for compiler (`b`). +3. Build compiler and necessary tools: `python x.py build --stage=2 compiler library/std src/tools/rustdoc src/tools/rustfmt` + * (Optional) You can also build cargo by adding `src/tools/cargo` to previous command. +4. Copy exectutable files from `./build/host/stage2-tools//release` +to `./build/host/stage2/bin/`. Note that you would need to do this every time you rebuilt `rust` repository. +5. Copy cargo from another toolchain: `cp $(rustup which cargo) .build//stage2/bin/cargo` + * Another option is to build it at step 3 and copy with other executables at step 4. +6. Link your new `rustc` to toolchain: `rustup toolchain link stage2 ./build/host/stage2/`. +7. (Windows only) compile the build system: `rustc +stage2 -O build_system/main.rs -o y.exe`. +8. You need to prefix every `./y.sh` (or `y` if you built `build_system/main.rs` as `y`) command by `rustup run stage2` to make cg_clif use your local changes in rustc. + + * `rustup run stage2 ./y.sh prepare` + * `rustup run stage2 ./y.sh build` + * (Optional) run tests: `rustup run stage2 ./y.sh test` +9. Now you can use your cg_clif build to compile other Rust programs, e.g. you can open any Rust crate and run commands like `$RustCheckoutDir/compiler/rustc_codegen_cranelift/dist/cargo-clif build --release`. + ## Configuration See the documentation on the `BackendConfig` struct in [config.rs](src/config.rs) for all diff --git a/compiler/rustc_codegen_cranelift/build_sysroot/Cargo.lock b/compiler/rustc_codegen_cranelift/build_sysroot/Cargo.lock deleted file mode 100644 index 7ddf91ad01faa..0000000000000 --- a/compiler/rustc_codegen_cranelift/build_sysroot/Cargo.lock +++ /dev/null @@ -1,323 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "compiler_builtins", - "gimli", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-core", -] - -[[package]] -name = "alloc" -version = "0.0.0" -dependencies = [ - "compiler_builtins", - "core", -] - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-core", -] - -[[package]] -name = "compiler_builtins" -version = "0.1.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571298a3cce7e2afbd3d61abb91a18667d5ab25993ec577a88ee8ac45f00cc3a" -dependencies = [ - "rustc-std-workspace-core", -] - -[[package]] -name = "core" -version = "0.0.0" - -[[package]] -name = "dlmalloc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" -dependencies = [ - "compiler_builtins", - "libc", - "rustc-std-workspace-core", -] - -[[package]] -name = "fortanix-sgx-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cafc2274c10fab234f176b25903ce17e690fca7597090d50880e047a0389c5" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-core", -] - -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "rustc-std-workspace-core", - "rustc-std-workspace-std", - "unicode-width", -] - -[[package]] -name = "gimli" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "libc" -version = "0.2.142" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" -dependencies = [ - "rustc-std-workspace-core", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-core", -] - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", - "compiler_builtins", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "object" -version = "0.30.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" -dependencies = [ - "compiler_builtins", - "memchr", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "panic_abort" -version = "0.0.0" -dependencies = [ - "alloc", - "cfg-if", - "compiler_builtins", - "core", - "libc", -] - -[[package]] -name = "panic_unwind" -version = "0.0.0" -dependencies = [ - "alloc", - "cfg-if", - "compiler_builtins", - "core", - "libc", - "unwind", -] - -[[package]] -name = "proc_macro" -version = "0.0.0" -dependencies = [ - "core", - "std", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-core", -] - -[[package]] -name = "rustc-std-workspace-alloc" -version = "1.99.0" -dependencies = [ - "alloc", -] - -[[package]] -name = "rustc-std-workspace-core" -version = "1.99.0" -dependencies = [ - "core", -] - -[[package]] -name = "rustc-std-workspace-std" -version = "1.99.0" -dependencies = [ - "std", -] - -[[package]] -name = "std" -version = "0.0.0" -dependencies = [ - "addr2line", - "alloc", - "cfg-if", - "compiler_builtins", - "core", - "dlmalloc", - "fortanix-sgx-abi", - "hashbrown", - "hermit-abi", - "libc", - "miniz_oxide", - "object", - "panic_abort", - "panic_unwind", - "rustc-demangle", - "std_detect", - "unwind", - "wasi", -] - -[[package]] -name = "std_detect" -version = "0.1.5" -dependencies = [ - "cfg-if", - "compiler_builtins", - "libc", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] - -[[package]] -name = "sysroot" -version = "0.0.0" -dependencies = [ - "alloc", - "compiler_builtins", - "core", - "std", - "test", -] - -[[package]] -name = "test" -version = "0.0.0" -dependencies = [ - "core", - "getopts", - "panic_abort", - "panic_unwind", - "proc_macro", - "std", -] - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-core", - "rustc-std-workspace-std", -] - -[[package]] -name = "unwind" -version = "0.0.0" -dependencies = [ - "cc", - "cfg-if", - "compiler_builtins", - "core", - "libc", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -dependencies = [ - "compiler_builtins", - "rustc-std-workspace-alloc", - "rustc-std-workspace-core", -] diff --git a/compiler/rustc_codegen_cranelift/build_sysroot/Cargo.toml b/compiler/rustc_codegen_cranelift/build_sysroot/Cargo.toml deleted file mode 100644 index 8219e6b6ccf3b..0000000000000 --- a/compiler/rustc_codegen_cranelift/build_sysroot/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "sysroot" -version = "0.0.0" - -[dependencies] -core = { path = "./sysroot_src/library/core" } -alloc = { path = "./sysroot_src/library/alloc" } -std = { path = "./sysroot_src/library/std", features = ["panic_unwind", "backtrace"] } -test = { path = "./sysroot_src/library/test" } - -compiler_builtins = { version = "0.1.87", default-features = false, features = ["no-asm"] } - -[patch.crates-io] -rustc-std-workspace-core = { path = "./sysroot_src/library/rustc-std-workspace-core" } -rustc-std-workspace-alloc = { path = "./sysroot_src/library/rustc-std-workspace-alloc" } -rustc-std-workspace-std = { path = "./sysroot_src/library/rustc-std-workspace-std" } - -[profile.dev] -lto = "off" - -[profile.release] -debug = true -incremental = true -lto = "off" - -# Mandatory for correctly compiling compiler-builtins -[profile.dev.package.compiler_builtins] -debug-assertions = false -overflow-checks = false -codegen-units = 10000 - -[profile.release.package.compiler_builtins] -debug-assertions = false -overflow-checks = false -codegen-units = 10000 diff --git a/compiler/rustc_codegen_cranelift/build_sysroot/src/lib.rs b/compiler/rustc_codegen_cranelift/build_sysroot/src/lib.rs deleted file mode 100644 index 0c9ac1ac8e4bd..0000000000000 --- a/compiler/rustc_codegen_cranelift/build_sysroot/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/compiler/rustc_codegen_cranelift/build_system/Cargo.lock b/compiler/rustc_codegen_cranelift/build_system/Cargo.lock new file mode 100644 index 0000000000000..86268e1916030 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/build_system/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "y" +version = "0.1.0" diff --git a/compiler/rustc_codegen_cranelift/build_system/Cargo.toml b/compiler/rustc_codegen_cranelift/build_system/Cargo.toml new file mode 100644 index 0000000000000..f47b9bc554041 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/build_system/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "y" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "y" +path = "main.rs" + +[features] +unstable-features = [] # for rust-analyzer + +# Do not add any dependencies diff --git a/compiler/rustc_codegen_cranelift/build_system/abi_cafe.rs b/compiler/rustc_codegen_cranelift/build_system/abi_cafe.rs index 0da27f529b3ec..2e7ba1b2060b6 100644 --- a/compiler/rustc_codegen_cranelift/build_system/abi_cafe.rs +++ b/compiler/rustc_codegen_cranelift/build_system/abi_cafe.rs @@ -1,25 +1,29 @@ -use std::path::Path; - -use super::build_sysroot; -use super::path::Dirs; -use super::prepare::GitRepo; -use super::utils::{spawn_and_wait, CargoProject, Compiler}; -use super::SysrootKind; - -static ABI_CAFE_REPO: GitRepo = - GitRepo::github("Gankra", "abi-cafe", "4c6dc8c9c687e2b3a760ff2176ce236872b37212", "abi-cafe"); - -static ABI_CAFE: CargoProject = CargoProject::new(&ABI_CAFE_REPO.source_dir(), "abi_cafe"); +use crate::build_sysroot; +use crate::path::Dirs; +use crate::prepare::GitRepo; +use crate::utils::{spawn_and_wait, CargoProject, Compiler}; +use crate::{CodegenBackend, SysrootKind}; + +static ABI_CAFE_REPO: GitRepo = GitRepo::github( + "Gankra", + "abi-cafe", + "4c6dc8c9c687e2b3a760ff2176ce236872b37212", + "588df6d66abbe105", + "abi-cafe", +); + +static ABI_CAFE: CargoProject = CargoProject::new(&ABI_CAFE_REPO.source_dir(), "abi_cafe_target"); pub(crate) fn run( channel: &str, sysroot_kind: SysrootKind, dirs: &Dirs, - cg_clif_dylib: &Path, + cg_clif_dylib: &CodegenBackend, + rustup_toolchain_name: Option<&str>, bootstrap_host_compiler: &Compiler, ) { ABI_CAFE_REPO.fetch(dirs); - spawn_and_wait(ABI_CAFE.fetch("cargo", &bootstrap_host_compiler.rustc, dirs)); + ABI_CAFE_REPO.patch(dirs); eprintln!("Building sysroot for abi-cafe"); build_sysroot::build_sysroot( @@ -28,6 +32,7 @@ pub(crate) fn run( sysroot_kind, cg_clif_dylib, bootstrap_host_compiler, + rustup_toolchain_name, bootstrap_host_compiler.triple.clone(), ); @@ -40,7 +45,14 @@ pub(crate) fn run( cmd.arg("--pairs"); cmd.args(pairs); cmd.arg("--add-rustc-codegen-backend"); - cmd.arg(format!("cgclif:{}", cg_clif_dylib.display())); + match cg_clif_dylib { + CodegenBackend::Local(path) => { + cmd.arg(format!("cgclif:{}", path.display())); + } + CodegenBackend::Builtin(name) => { + cmd.arg(format!("cgclif:{name}")); + } + } cmd.current_dir(ABI_CAFE.source_dir(dirs)); spawn_and_wait(cmd); diff --git a/compiler/rustc_codegen_cranelift/build_system/bench.rs b/compiler/rustc_codegen_cranelift/build_system/bench.rs index a9a851d0a8afc..6c64faaa2563e 100644 --- a/compiler/rustc_codegen_cranelift/build_system/bench.rs +++ b/compiler/rustc_codegen_cranelift/build_system/bench.rs @@ -1,26 +1,20 @@ use std::env; -use std::fs; +use std::io::Write; use std::path::Path; -use super::path::{Dirs, RelPath}; -use super::prepare::GitRepo; -use super::rustc_info::get_file_name; -use super::utils::{hyperfine_command, spawn_and_wait, CargoProject, Compiler}; +use crate::path::{Dirs, RelPath}; +use crate::prepare::GitRepo; +use crate::rustc_info::get_file_name; +use crate::utils::{hyperfine_command, spawn_and_wait, Compiler}; static SIMPLE_RAYTRACER_REPO: GitRepo = GitRepo::github( "ebobby", "simple-raytracer", "804a7a21b9e673a482797aa289a18ed480e4d813", + "ad6f59a2331a3f56", "", ); -// Use a separate target dir for the initial LLVM build to reduce unnecessary recompiles -static SIMPLE_RAYTRACER_LLVM: CargoProject = - CargoProject::new(&SIMPLE_RAYTRACER_REPO.source_dir(), "simple_raytracer_llvm"); - -static SIMPLE_RAYTRACER: CargoProject = - CargoProject::new(&SIMPLE_RAYTRACER_REPO.source_dir(), "simple_raytracer"); - pub(crate) fn benchmark(dirs: &Dirs, bootstrap_host_compiler: &Compiler) { benchmark_simple_raytracer(dirs, bootstrap_host_compiler); } @@ -32,35 +26,23 @@ fn benchmark_simple_raytracer(dirs: &Dirs, bootstrap_host_compiler: &Compiler) { std::process::exit(1); } - if !SIMPLE_RAYTRACER_REPO.source_dir().to_path(dirs).exists() { - SIMPLE_RAYTRACER_REPO.fetch(dirs); - spawn_and_wait(SIMPLE_RAYTRACER.fetch( - &bootstrap_host_compiler.cargo, - &bootstrap_host_compiler.rustc, - dirs, - )); - } - - eprintln!("[LLVM BUILD] simple-raytracer"); - let build_cmd = SIMPLE_RAYTRACER_LLVM.build(bootstrap_host_compiler, dirs); - spawn_and_wait(build_cmd); - fs::copy( - SIMPLE_RAYTRACER_LLVM - .target_dir(dirs) - .join(&bootstrap_host_compiler.triple) - .join("debug") - .join(get_file_name("main", "bin")), - RelPath::BUILD.to_path(dirs).join(get_file_name("raytracer_cg_llvm", "bin")), - ) - .unwrap(); + SIMPLE_RAYTRACER_REPO.fetch(dirs); + SIMPLE_RAYTRACER_REPO.patch(dirs); let bench_runs = env::var("BENCH_RUNS").unwrap_or_else(|_| "10".to_string()).parse().unwrap(); + let mut gha_step_summary = if let Ok(file) = std::env::var("GITHUB_STEP_SUMMARY") { + Some(std::fs::OpenOptions::new().append(true).open(file).unwrap()) + } else { + None + }; + eprintln!("[BENCH COMPILE] ebobby/simple-raytracer"); - let cargo_clif = - RelPath::DIST.to_path(dirs).join(get_file_name("cargo_clif", "bin").replace('_', "-")); - let manifest_path = SIMPLE_RAYTRACER.manifest_path(dirs); - let target_dir = SIMPLE_RAYTRACER.target_dir(dirs); + let cargo_clif = RelPath::DIST + .to_path(dirs) + .join(get_file_name(&bootstrap_host_compiler.rustc, "cargo_clif", "bin").replace('_', "-")); + let manifest_path = SIMPLE_RAYTRACER_REPO.source_dir().to_path(dirs).join("Cargo.toml"); + let target_dir = RelPath::BUILD.join("simple_raytracer").to_path(dirs); let clean_cmd = format!( "RUSTC=rustc cargo clean --manifest-path {manifest_path} --target-dir {target_dir}", @@ -68,36 +50,81 @@ fn benchmark_simple_raytracer(dirs: &Dirs, bootstrap_host_compiler: &Compiler) { target_dir = target_dir.display(), ); let llvm_build_cmd = format!( - "RUSTC=rustc cargo build --manifest-path {manifest_path} --target-dir {target_dir}", + "RUSTC=rustc cargo build --manifest-path {manifest_path} --target-dir {target_dir} && (rm build/raytracer_cg_llvm || true) && ln build/simple_raytracer/debug/main build/raytracer_cg_llvm", manifest_path = manifest_path.display(), target_dir = target_dir.display(), ); let clif_build_cmd = format!( - "RUSTC=rustc {cargo_clif} build --manifest-path {manifest_path} --target-dir {target_dir}", + "RUSTC=rustc {cargo_clif} build --manifest-path {manifest_path} --target-dir {target_dir} && (rm build/raytracer_cg_clif || true) && ln build/simple_raytracer/debug/main build/raytracer_cg_clif", + cargo_clif = cargo_clif.display(), + manifest_path = manifest_path.display(), + target_dir = target_dir.display(), + ); + let clif_build_opt_cmd = format!( + "RUSTC=rustc {cargo_clif} build --manifest-path {manifest_path} --target-dir {target_dir} --release && (rm build/raytracer_cg_clif_opt || true) && ln build/simple_raytracer/release/main build/raytracer_cg_clif_opt", cargo_clif = cargo_clif.display(), manifest_path = manifest_path.display(), target_dir = target_dir.display(), ); - let bench_compile = - hyperfine_command(1, bench_runs, Some(&clean_cmd), &llvm_build_cmd, &clif_build_cmd); + let bench_compile_markdown = RelPath::DIST.to_path(dirs).join("bench_compile.md"); + + let bench_compile = hyperfine_command( + 1, + bench_runs, + Some(&clean_cmd), + &[ + ("cargo build", &llvm_build_cmd), + ("cargo-clif build", &clif_build_cmd), + ("cargo-clif build --release", &clif_build_opt_cmd), + ], + &bench_compile_markdown, + ); spawn_and_wait(bench_compile); + if let Some(gha_step_summary) = gha_step_summary.as_mut() { + gha_step_summary.write_all(b"## Compile ebobby/simple-raytracer\n\n").unwrap(); + gha_step_summary.write_all(&std::fs::read(bench_compile_markdown).unwrap()).unwrap(); + gha_step_summary.write_all(b"\n").unwrap(); + } + eprintln!("[BENCH RUN] ebobby/simple-raytracer"); - fs::copy( - target_dir.join("debug").join(get_file_name("main", "bin")), - RelPath::BUILD.to_path(dirs).join(get_file_name("raytracer_cg_clif", "bin")), - ) - .unwrap(); + let bench_run_markdown = RelPath::DIST.to_path(dirs).join("bench_run.md"); + + let raytracer_cg_llvm = Path::new(".").join(get_file_name( + &bootstrap_host_compiler.rustc, + "raytracer_cg_llvm", + "bin", + )); + let raytracer_cg_clif = Path::new(".").join(get_file_name( + &bootstrap_host_compiler.rustc, + "raytracer_cg_clif", + "bin", + )); + let raytracer_cg_clif_opt = Path::new(".").join(get_file_name( + &bootstrap_host_compiler.rustc, + "raytracer_cg_clif_opt", + "bin", + )); let mut bench_run = hyperfine_command( 0, bench_runs, None, - Path::new(".").join(get_file_name("raytracer_cg_llvm", "bin")).to_str().unwrap(), - Path::new(".").join(get_file_name("raytracer_cg_clif", "bin")).to_str().unwrap(), + &[ + ("", raytracer_cg_llvm.to_str().unwrap()), + ("", raytracer_cg_clif.to_str().unwrap()), + ("", raytracer_cg_clif_opt.to_str().unwrap()), + ], + &bench_run_markdown, ); bench_run.current_dir(RelPath::BUILD.to_path(dirs)); spawn_and_wait(bench_run); + + if let Some(gha_step_summary) = gha_step_summary.as_mut() { + gha_step_summary.write_all(b"## Run ebobby/simple-raytracer\n\n").unwrap(); + gha_step_summary.write_all(&std::fs::read(bench_run_markdown).unwrap()).unwrap(); + gha_step_summary.write_all(b"\n").unwrap(); + } } diff --git a/compiler/rustc_codegen_cranelift/build_system/build_backend.rs b/compiler/rustc_codegen_cranelift/build_system/build_backend.rs index 4b740fa2db65f..e434c36f99227 100644 --- a/compiler/rustc_codegen_cranelift/build_system/build_backend.rs +++ b/compiler/rustc_codegen_cranelift/build_system/build_backend.rs @@ -1,9 +1,9 @@ -use std::env; use std::path::PathBuf; -use super::path::{Dirs, RelPath}; -use super::rustc_info::get_file_name; -use super::utils::{is_ci, is_ci_opt, CargoProject, Compiler}; +use crate::path::{Dirs, RelPath}; +use crate::rustc_info::get_file_name; +use crate::shared_utils::{rustflags_from_env, rustflags_to_cmd_env}; +use crate::utils::{is_ci, is_ci_opt, maybe_incremental, CargoProject, Compiler, LogGroup}; pub(crate) static CG_CLIF: CargoProject = CargoProject::new(&RelPath::SOURCE, "cg_clif"); @@ -13,21 +13,20 @@ pub(crate) fn build_backend( bootstrap_host_compiler: &Compiler, use_unstable_features: bool, ) -> PathBuf { - let mut cmd = CG_CLIF.build(&bootstrap_host_compiler, dirs); + let _group = LogGroup::guard("Build backend"); - cmd.env("CARGO_BUILD_INCREMENTAL", "true"); // Force incr comp even in release mode + let mut cmd = CG_CLIF.build(&bootstrap_host_compiler, dirs); + maybe_incremental(&mut cmd); - let mut rustflags = env::var("RUSTFLAGS").unwrap_or_default(); + let mut rustflags = rustflags_from_env("RUSTFLAGS"); if is_ci() { // Deny warnings on CI - rustflags += " -Dwarnings"; - - // Disabling incr comp reduces cache size and incr comp doesn't save as much on CI anyway - cmd.env("CARGO_BUILD_INCREMENTAL", "false"); + rustflags.push("-Dwarnings".to_owned()); if !is_ci_opt() { cmd.env("CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS", "true"); + cmd.env("CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS", "true"); } } @@ -43,14 +42,14 @@ pub(crate) fn build_backend( _ => unreachable!(), } - cmd.env("RUSTFLAGS", rustflags); + rustflags_to_cmd_env(&mut cmd, "RUSTFLAGS", &rustflags); eprintln!("[BUILD] rustc_codegen_cranelift"); - super::utils::spawn_and_wait(cmd); + crate::utils::spawn_and_wait(cmd); CG_CLIF .target_dir(dirs) .join(&bootstrap_host_compiler.triple) .join(channel) - .join(get_file_name("rustc_codegen_cranelift", "dylib")) + .join(get_file_name(&bootstrap_host_compiler.rustc, "rustc_codegen_cranelift", "dylib")) } diff --git a/compiler/rustc_codegen_cranelift/build_system/build_sysroot.rs b/compiler/rustc_codegen_cranelift/build_system/build_sysroot.rs index 76b602fe71963..31a4b209826f9 100644 --- a/compiler/rustc_codegen_cranelift/build_system/build_sysroot.rs +++ b/compiler/rustc_codegen_cranelift/build_system/build_sysroot.rs @@ -1,11 +1,14 @@ use std::fs; use std::path::{Path, PathBuf}; -use std::process::{self, Command}; +use std::process::Command; -use super::path::{Dirs, RelPath}; -use super::rustc_info::{get_file_name, get_rustc_version, get_toolchain_name}; -use super::utils::{remove_dir_if_exists, spawn_and_wait, try_hard_link, CargoProject, Compiler}; -use super::SysrootKind; +use crate::path::{Dirs, RelPath}; +use crate::rustc_info::get_file_name; +use crate::utils::{ + maybe_incremental, remove_dir_if_exists, spawn_and_wait, try_hard_link, CargoProject, Compiler, + LogGroup, +}; +use crate::{config, CodegenBackend, SysrootKind}; static DIST_DIR: RelPath = RelPath::DIST; static BIN_DIR: RelPath = RelPath::DIST.join("bin"); @@ -15,10 +18,13 @@ pub(crate) fn build_sysroot( dirs: &Dirs, channel: &str, sysroot_kind: SysrootKind, - cg_clif_dylib_src: &Path, + cg_clif_dylib_src: &CodegenBackend, bootstrap_host_compiler: &Compiler, + rustup_toolchain_name: Option<&str>, target_triple: String, ) -> Compiler { + let _guard = LogGroup::guard("Build sysroot"); + eprintln!("[BUILD] sysroot {:?}", sysroot_kind); DIST_DIR.ensure_fresh(dirs); @@ -27,32 +33,52 @@ pub(crate) fn build_sysroot( let is_native = bootstrap_host_compiler.triple == target_triple; - // Copy the backend - let cg_clif_dylib_path = if cfg!(windows) { - // Windows doesn't have rpath support, so the cg_clif dylib needs to be next to the - // binaries. - BIN_DIR - } else { - LIB_DIR - } - .to_path(dirs) - .join(cg_clif_dylib_src.file_name().unwrap()); - try_hard_link(cg_clif_dylib_src, &cg_clif_dylib_path); + let cg_clif_dylib_path = match cg_clif_dylib_src { + CodegenBackend::Local(src_path) => { + // Copy the backend + let cg_clif_dylib_path = if cfg!(windows) { + // Windows doesn't have rpath support, so the cg_clif dylib needs to be next to the + // binaries. + BIN_DIR + } else { + LIB_DIR + } + .to_path(dirs) + .join(src_path.file_name().unwrap()); + try_hard_link(src_path, &cg_clif_dylib_path); + CodegenBackend::Local(cg_clif_dylib_path) + } + CodegenBackend::Builtin(name) => CodegenBackend::Builtin(name.clone()), + }; // Build and copy rustc and cargo wrappers - let wrapper_base_name = get_file_name("____", "bin"); - let toolchain_name = get_toolchain_name(); + let wrapper_base_name = get_file_name(&bootstrap_host_compiler.rustc, "____", "bin"); for wrapper in ["rustc-clif", "rustdoc-clif", "cargo-clif"] { let wrapper_name = wrapper_base_name.replace("____", wrapper); let mut build_cargo_wrapper_cmd = Command::new(&bootstrap_host_compiler.rustc); let wrapper_path = DIST_DIR.to_path(dirs).join(&wrapper_name); build_cargo_wrapper_cmd - .env("TOOLCHAIN_NAME", toolchain_name.clone()) .arg(RelPath::SCRIPTS.to_path(dirs).join(&format!("{wrapper}.rs"))) .arg("-o") .arg(&wrapper_path) .arg("-Cstrip=debuginfo"); + if let Some(rustup_toolchain_name) = &rustup_toolchain_name { + build_cargo_wrapper_cmd + .env("TOOLCHAIN_NAME", rustup_toolchain_name) + .env_remove("CARGO") + .env_remove("RUSTC") + .env_remove("RUSTDOC"); + } else { + build_cargo_wrapper_cmd + .env_remove("TOOLCHAIN_NAME") + .env("CARGO", &bootstrap_host_compiler.cargo) + .env("RUSTC", &bootstrap_host_compiler.rustc) + .env("RUSTDOC", &bootstrap_host_compiler.rustdoc); + } + if let CodegenBackend::Builtin(name) = cg_clif_dylib_src { + build_cargo_wrapper_cmd.env("BUILTIN_BACKEND", name); + } spawn_and_wait(build_cargo_wrapper_cmd); try_hard_link(wrapper_path, BIN_DIR.to_path(dirs).join(wrapper_name)); } @@ -102,8 +128,8 @@ pub(crate) fn build_sysroot( cargo: bootstrap_host_compiler.cargo.clone(), rustc: rustc_clif.clone(), rustdoc: rustdoc_clif.clone(), - rustflags: String::new(), - rustdocflags: String::new(), + rustflags: vec![], + rustdocflags: vec![], triple: target_triple, runner: vec![], } @@ -134,12 +160,9 @@ impl SysrootTarget { } } -pub(crate) static ORIG_BUILD_SYSROOT: RelPath = RelPath::SOURCE.join("build_sysroot"); -pub(crate) static BUILD_SYSROOT: RelPath = RelPath::DOWNLOAD.join("sysroot"); -pub(crate) static SYSROOT_RUSTC_VERSION: RelPath = BUILD_SYSROOT.join("rustc_version"); -pub(crate) static SYSROOT_SRC: RelPath = BUILD_SYSROOT.join("sysroot_src"); +pub(crate) static STDLIB_SRC: RelPath = RelPath::BUILD.join("stdlib"); pub(crate) static STANDARD_LIBRARY: CargoProject = - CargoProject::new(&BUILD_SYSROOT, "build_sysroot"); + CargoProject::new(&STDLIB_SRC.join("library/sysroot"), "stdlib_target"); pub(crate) static RTSTARTUP_SYSROOT: RelPath = RelPath::BUILD.join("rtstartup"); #[must_use] @@ -147,7 +170,7 @@ fn build_sysroot_for_triple( dirs: &Dirs, channel: &str, compiler: Compiler, - cg_clif_dylib_path: &Path, + cg_clif_dylib_path: &CodegenBackend, sysroot_kind: SysrootKind, ) -> SysrootTarget { match sysroot_kind { @@ -155,14 +178,14 @@ fn build_sysroot_for_triple( .unwrap_or(SysrootTarget { triple: compiler.triple, libs: vec![] }), SysrootKind::Llvm => build_llvm_sysroot_for_triple(compiler), SysrootKind::Clif => { - build_clif_sysroot_for_triple(dirs, channel, compiler, &cg_clif_dylib_path) + build_clif_sysroot_for_triple(dirs, channel, compiler, cg_clif_dylib_path) } } } #[must_use] fn build_llvm_sysroot_for_triple(compiler: Compiler) -> SysrootTarget { - let default_sysroot = super::rustc_info::get_default_sysroot(&compiler.rustc); + let default_sysroot = crate::rustc_info::get_default_sysroot(&compiler.rustc); let mut target_libs = SysrootTarget { triple: compiler.triple, libs: vec![] }; @@ -199,26 +222,8 @@ fn build_clif_sysroot_for_triple( dirs: &Dirs, channel: &str, mut compiler: Compiler, - cg_clif_dylib_path: &Path, + cg_clif_dylib_path: &CodegenBackend, ) -> SysrootTarget { - match fs::read_to_string(SYSROOT_RUSTC_VERSION.to_path(dirs)) { - Err(e) => { - eprintln!("Failed to get rustc version for patched sysroot source: {}", e); - eprintln!("Hint: Try `./y.rs prepare` to patch the sysroot source"); - process::exit(1); - } - Ok(source_version) => { - let rustc_version = get_rustc_version(&compiler.rustc); - if source_version != rustc_version { - eprintln!("The patched sysroot source is outdated"); - eprintln!("Source version: {}", source_version.trim()); - eprintln!("Rustc version: {}", rustc_version.trim()); - eprintln!("Hint: Try `./y.rs prepare` to update the patched sysroot source"); - process::exit(1); - } - } - } - let mut target_libs = SysrootTarget { triple: compiler.triple.clone(), libs: vec![] }; if let Some(rtstartup_target_libs) = build_rtstartup(dirs, &compiler) { @@ -229,27 +234,39 @@ fn build_clif_sysroot_for_triple( let build_dir = STANDARD_LIBRARY.target_dir(dirs).join(&compiler.triple).join(channel); - if !super::config::get_bool("keep_sysroot") { + if !config::get_bool("keep_sysroot") { // Cleanup the deps dir, but keep build scripts and the incremental cache for faster // recompilation as they are not affected by changes in cg_clif. remove_dir_if_exists(&build_dir.join("deps")); } // Build sysroot - let mut rustflags = " -Zforce-unstable-if-unmarked -Cpanic=abort".to_string(); - rustflags.push_str(&format!(" -Zcodegen-backend={}", cg_clif_dylib_path.to_str().unwrap())); + let mut rustflags = vec!["-Zforce-unstable-if-unmarked".to_owned(), "-Cpanic=abort".to_owned()]; + match cg_clif_dylib_path { + CodegenBackend::Local(path) => { + rustflags.push(format!("-Zcodegen-backend={}", path.to_str().unwrap())); + } + CodegenBackend::Builtin(name) => { + rustflags.push(format!("-Zcodegen-backend={name}")); + } + }; // Necessary for MinGW to find rsbegin.o and rsend.o - rustflags - .push_str(&format!(" --sysroot={}", RTSTARTUP_SYSROOT.to_path(dirs).to_str().unwrap())); + rustflags.push("--sysroot".to_owned()); + rustflags.push(RTSTARTUP_SYSROOT.to_path(dirs).to_str().unwrap().to_owned()); if channel == "release" { - rustflags.push_str(" -Zmir-opt-level=3"); + // Incremental compilation by default disables mir inlining. This leads to both a decent + // compile perf and a significant runtime perf regression. As such forcefully enable mir + // inlining. + rustflags.push("-Zinline-mir".to_owned()); } - compiler.rustflags += &rustflags; + compiler.rustflags.extend(rustflags); let mut build_cmd = STANDARD_LIBRARY.build(&compiler, dirs); + maybe_incremental(&mut build_cmd); if channel == "release" { build_cmd.arg("--release"); } - build_cmd.arg("--locked"); + build_cmd.arg("--features").arg("compiler-builtins-no-asm backtrace panic-unwind"); + build_cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true"); build_cmd.env("__CARGO_DEFAULT_LIB_METADATA", "cg_clif"); if compiler.triple.contains("apple") { build_cmd.env("CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO", "packed"); @@ -272,19 +289,24 @@ fn build_clif_sysroot_for_triple( } fn build_rtstartup(dirs: &Dirs, compiler: &Compiler) -> Option { + if !config::get_bool("keep_sysroot") { + crate::prepare::prepare_stdlib(dirs, &compiler.rustc); + } + if !compiler.triple.ends_with("windows-gnu") { return None; } RTSTARTUP_SYSROOT.ensure_fresh(dirs); - let rtstartup_src = SYSROOT_SRC.to_path(dirs).join("library").join("rtstartup"); + let rtstartup_src = STDLIB_SRC.to_path(dirs).join("library").join("rtstartup"); let mut target_libs = SysrootTarget { triple: compiler.triple.clone(), libs: vec![] }; for file in ["rsbegin", "rsend"] { let obj = RTSTARTUP_SYSROOT.to_path(dirs).join(format!("{file}.o")); let mut build_rtstartup_cmd = Command::new(&compiler.rustc); build_rtstartup_cmd + .arg("-Ainternal_features") // Missing #[allow(internal_features)] .arg("--target") .arg(&compiler.triple) .arg("--emit=obj") diff --git a/compiler/rustc_codegen_cranelift/build_system/main.rs b/compiler/rustc_codegen_cranelift/build_system/main.rs new file mode 100644 index 0000000000000..798ae9dbd5006 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/build_system/main.rs @@ -0,0 +1,273 @@ +#![warn(rust_2018_idioms)] +#![warn(unused_lifetimes)] +#![warn(unreachable_pub)] + +use std::env; +use std::path::PathBuf; +use std::process; + +use self::utils::{is_ci, is_ci_opt, Compiler}; + +mod abi_cafe; +mod bench; +mod build_backend; +mod build_sysroot; +mod config; +mod path; +mod prepare; +mod rustc_info; +mod shared_utils; +mod tests; +mod utils; + +fn usage() { + eprintln!("{}", include_str!("usage.txt")); +} + +macro_rules! arg_error { + ($($err:tt)*) => {{ + eprintln!($($err)*); + usage(); + std::process::exit(1); + }}; +} + +#[derive(PartialEq, Debug)] +enum Command { + Prepare, + Build, + Test, + AbiCafe, + Bench, +} + +#[derive(Copy, Clone, Debug)] +enum SysrootKind { + None, + Clif, + Llvm, +} + +#[derive(Clone, Debug)] +enum CodegenBackend { + Local(PathBuf), + Builtin(String), +} + +fn main() { + if env::var("RUST_BACKTRACE").is_err() { + env::set_var("RUST_BACKTRACE", "1"); + } + env::set_var("CG_CLIF_DISABLE_INCR_CACHE", "1"); + + if is_ci() { + // Disabling incr comp reduces cache size and incr comp doesn't save as much on CI anyway + env::set_var("CARGO_BUILD_INCREMENTAL", "false"); + + if !is_ci_opt() { + // Enable the Cranelift verifier + env::set_var("CG_CLIF_ENABLE_VERIFIER", "1"); + } + } + + let mut args = env::args().skip(1); + let command = match args.next().as_deref() { + Some("prepare") => Command::Prepare, + Some("build") => Command::Build, + Some("test") => Command::Test, + Some("abi-cafe") => Command::AbiCafe, + Some("bench") => Command::Bench, + Some(flag) if flag.starts_with('-') => arg_error!("Expected command found flag {}", flag), + Some(command) => arg_error!("Unknown command {}", command), + None => { + usage(); + process::exit(0); + } + }; + + let mut out_dir = PathBuf::from("."); + let mut download_dir = None; + let mut channel = "release"; + let mut sysroot_kind = SysrootKind::Clif; + let mut use_unstable_features = true; + let mut frozen = false; + let mut skip_tests = vec![]; + let mut use_backend = None; + while let Some(arg) = args.next().as_deref() { + match arg { + "--out-dir" => { + out_dir = PathBuf::from(args.next().unwrap_or_else(|| { + arg_error!("--out-dir requires argument"); + })); + } + "--download-dir" => { + download_dir = Some(PathBuf::from(args.next().unwrap_or_else(|| { + arg_error!("--download-dir requires argument"); + }))); + } + "--debug" => channel = "debug", + "--sysroot" => { + sysroot_kind = match args.next().as_deref() { + Some("none") => SysrootKind::None, + Some("clif") => SysrootKind::Clif, + Some("llvm") => SysrootKind::Llvm, + Some(arg) => arg_error!("Unknown sysroot kind {}", arg), + None => arg_error!("--sysroot requires argument"), + } + } + "--no-unstable-features" => use_unstable_features = false, + "--frozen" => frozen = true, + "--skip-test" => { + // FIXME check that all passed in tests actually exist + skip_tests.push(args.next().unwrap_or_else(|| { + arg_error!("--skip-test requires argument"); + })); + } + "--use-backend" => { + use_backend = Some(match args.next() { + Some(name) => name, + None => arg_error!("--use-backend requires argument"), + }); + } + flag if flag.starts_with("-") => arg_error!("Unknown flag {}", flag), + arg => arg_error!("Unexpected argument {}", arg), + } + } + + let current_dir = std::env::current_dir().unwrap(); + out_dir = current_dir.join(out_dir); + + if command == Command::Prepare { + prepare::prepare(&path::Dirs { + source_dir: current_dir.clone(), + download_dir: download_dir + .map(|dir| current_dir.join(dir)) + .unwrap_or_else(|| out_dir.join("download")), + build_dir: PathBuf::from("dummy_do_not_use"), + dist_dir: PathBuf::from("dummy_do_not_use"), + frozen, + }); + process::exit(0); + } + + let rustup_toolchain_name = match (env::var("CARGO"), env::var("RUSTC"), env::var("RUSTDOC")) { + (Ok(_), Ok(_), Ok(_)) => None, + (Err(_), Err(_), Err(_)) => Some(rustc_info::get_toolchain_name()), + _ => { + eprintln!("All of CARGO, RUSTC and RUSTDOC need to be set or none must be set"); + process::exit(1); + } + }; + let bootstrap_host_compiler = { + let cargo = rustc_info::get_cargo_path(); + let rustc = rustc_info::get_rustc_path(); + let rustdoc = rustc_info::get_rustdoc_path(); + let triple = std::env::var("HOST_TRIPLE") + .ok() + .or_else(|| config::get_value("host")) + .unwrap_or_else(|| rustc_info::get_host_triple(&rustc)); + Compiler { + cargo, + rustc, + rustdoc, + rustflags: vec![], + rustdocflags: vec![], + triple, + runner: vec![], + } + }; + let target_triple = std::env::var("TARGET_TRIPLE") + .ok() + .or_else(|| config::get_value("target")) + .unwrap_or_else(|| bootstrap_host_compiler.triple.clone()); + + let dirs = path::Dirs { + source_dir: current_dir.clone(), + download_dir: download_dir + .map(|dir| current_dir.join(dir)) + .unwrap_or_else(|| out_dir.join("download")), + build_dir: out_dir.join("build"), + dist_dir: out_dir.join("dist"), + frozen, + }; + + path::RelPath::BUILD.ensure_exists(&dirs); + + { + // Make sure we always explicitly specify the target dir + let target = + path::RelPath::BUILD.join("target_dir_should_be_set_explicitly").to_path(&dirs); + env::set_var("CARGO_TARGET_DIR", &target); + let _ = std::fs::remove_file(&target); + std::fs::File::create(target).unwrap(); + } + + env::set_var("RUSTC", "rustc_should_be_set_explicitly"); + env::set_var("RUSTDOC", "rustdoc_should_be_set_explicitly"); + + let cg_clif_dylib = if let Some(name) = use_backend { + CodegenBackend::Builtin(name) + } else { + CodegenBackend::Local(build_backend::build_backend( + &dirs, + channel, + &bootstrap_host_compiler, + use_unstable_features, + )) + }; + match command { + Command::Prepare => { + // Handled above + } + Command::Test => { + tests::run_tests( + &dirs, + channel, + sysroot_kind, + use_unstable_features, + &skip_tests.iter().map(|test| &**test).collect::>(), + &cg_clif_dylib, + &bootstrap_host_compiler, + rustup_toolchain_name.as_deref(), + target_triple.clone(), + ); + } + Command::AbiCafe => { + if bootstrap_host_compiler.triple != target_triple { + eprintln!("Abi-cafe doesn't support cross-compilation"); + process::exit(1); + } + abi_cafe::run( + channel, + sysroot_kind, + &dirs, + &cg_clif_dylib, + rustup_toolchain_name.as_deref(), + &bootstrap_host_compiler, + ); + } + Command::Build => { + build_sysroot::build_sysroot( + &dirs, + channel, + sysroot_kind, + &cg_clif_dylib, + &bootstrap_host_compiler, + rustup_toolchain_name.as_deref(), + target_triple, + ); + } + Command::Bench => { + build_sysroot::build_sysroot( + &dirs, + channel, + sysroot_kind, + &cg_clif_dylib, + &bootstrap_host_compiler, + rustup_toolchain_name.as_deref(), + target_triple, + ); + bench::benchmark(&dirs, &bootstrap_host_compiler); + } + } +} diff --git a/compiler/rustc_codegen_cranelift/build_system/mod.rs b/compiler/rustc_codegen_cranelift/build_system/mod.rs deleted file mode 100644 index e4ed9be23b705..0000000000000 --- a/compiler/rustc_codegen_cranelift/build_system/mod.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::env; -use std::path::PathBuf; -use std::process; - -use self::utils::{is_ci, is_ci_opt, Compiler}; - -mod abi_cafe; -mod bench; -mod build_backend; -mod build_sysroot; -mod config; -mod path; -mod prepare; -mod rustc_info; -mod tests; -mod utils; - -fn usage() { - eprintln!("{}", include_str!("usage.txt")); -} - -macro_rules! arg_error { - ($($err:tt)*) => {{ - eprintln!($($err)*); - usage(); - std::process::exit(1); - }}; -} - -#[derive(PartialEq, Debug)] -enum Command { - Prepare, - Build, - Test, - AbiCafe, - Bench, -} - -#[derive(Copy, Clone, Debug)] -pub(crate) enum SysrootKind { - None, - Clif, - Llvm, -} - -pub(crate) fn main() { - if env::var("RUST_BACKTRACE").is_err() { - env::set_var("RUST_BACKTRACE", "1"); - } - env::set_var("CG_CLIF_DISABLE_INCR_CACHE", "1"); - - if is_ci() { - // Disabling incr comp reduces cache size and incr comp doesn't save as much on CI anyway - env::set_var("CARGO_BUILD_INCREMENTAL", "false"); - - if !is_ci_opt() { - // Enable the Cranelift verifier - env::set_var("CG_CLIF_ENABLE_VERIFIER", "1"); - } - } - - let mut args = env::args().skip(1); - let command = match args.next().as_deref() { - Some("prepare") => Command::Prepare, - Some("build") => Command::Build, - Some("test") => Command::Test, - Some("abi-cafe") => Command::AbiCafe, - Some("bench") => Command::Bench, - Some(flag) if flag.starts_with('-') => arg_error!("Expected command found flag {}", flag), - Some(command) => arg_error!("Unknown command {}", command), - None => { - usage(); - process::exit(0); - } - }; - - let mut out_dir = PathBuf::from("."); - let mut channel = "release"; - let mut sysroot_kind = SysrootKind::Clif; - let mut use_unstable_features = true; - while let Some(arg) = args.next().as_deref() { - match arg { - "--out-dir" => { - out_dir = PathBuf::from(args.next().unwrap_or_else(|| { - arg_error!("--out-dir requires argument"); - })) - } - "--debug" => channel = "debug", - "--sysroot" => { - sysroot_kind = match args.next().as_deref() { - Some("none") => SysrootKind::None, - Some("clif") => SysrootKind::Clif, - Some("llvm") => SysrootKind::Llvm, - Some(arg) => arg_error!("Unknown sysroot kind {}", arg), - None => arg_error!("--sysroot requires argument"), - } - } - "--no-unstable-features" => use_unstable_features = false, - flag if flag.starts_with("-") => arg_error!("Unknown flag {}", flag), - arg => arg_error!("Unexpected argument {}", arg), - } - } - - let bootstrap_host_compiler = Compiler::bootstrap_with_triple( - std::env::var("HOST_TRIPLE") - .ok() - .or_else(|| config::get_value("host")) - .unwrap_or_else(|| rustc_info::get_host_triple()), - ); - let target_triple = std::env::var("TARGET_TRIPLE") - .ok() - .or_else(|| config::get_value("target")) - .unwrap_or_else(|| bootstrap_host_compiler.triple.clone()); - - // FIXME allow changing the location of these dirs using cli arguments - let current_dir = std::env::current_dir().unwrap(); - out_dir = current_dir.join(out_dir); - let dirs = path::Dirs { - source_dir: current_dir.clone(), - download_dir: out_dir.join("download"), - build_dir: out_dir.join("build"), - dist_dir: out_dir.join("dist"), - }; - - path::RelPath::BUILD.ensure_exists(&dirs); - - { - // Make sure we always explicitly specify the target dir - let target = - path::RelPath::BUILD.join("target_dir_should_be_set_explicitly").to_path(&dirs); - env::set_var("CARGO_TARGET_DIR", &target); - let _ = std::fs::remove_file(&target); - std::fs::File::create(target).unwrap(); - } - - if command == Command::Prepare { - prepare::prepare(&dirs); - process::exit(0); - } - - env::set_var("RUSTC", "rustc_should_be_set_explicitly"); - env::set_var("RUSTDOC", "rustdoc_should_be_set_explicitly"); - - let cg_clif_dylib = build_backend::build_backend( - &dirs, - channel, - &bootstrap_host_compiler, - use_unstable_features, - ); - match command { - Command::Prepare => { - // Handled above - } - Command::Test => { - tests::run_tests( - &dirs, - channel, - sysroot_kind, - &cg_clif_dylib, - &bootstrap_host_compiler, - target_triple.clone(), - ); - } - Command::AbiCafe => { - if bootstrap_host_compiler.triple != target_triple { - eprintln!("Abi-cafe doesn't support cross-compilation"); - process::exit(1); - } - abi_cafe::run(channel, sysroot_kind, &dirs, &cg_clif_dylib, &bootstrap_host_compiler); - } - Command::Build => { - build_sysroot::build_sysroot( - &dirs, - channel, - sysroot_kind, - &cg_clif_dylib, - &bootstrap_host_compiler, - target_triple, - ); - } - Command::Bench => { - build_sysroot::build_sysroot( - &dirs, - channel, - sysroot_kind, - &cg_clif_dylib, - &bootstrap_host_compiler, - target_triple, - ); - bench::benchmark(&dirs, &bootstrap_host_compiler); - } - } -} diff --git a/compiler/rustc_codegen_cranelift/build_system/path.rs b/compiler/rustc_codegen_cranelift/build_system/path.rs index 3290723005dd9..8572815fc55e4 100644 --- a/compiler/rustc_codegen_cranelift/build_system/path.rs +++ b/compiler/rustc_codegen_cranelift/build_system/path.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::PathBuf; -use super::utils::remove_dir_if_exists; +use crate::utils::remove_dir_if_exists; #[derive(Debug, Clone)] pub(crate) struct Dirs { @@ -9,6 +9,7 @@ pub(crate) struct Dirs { pub(crate) download_dir: PathBuf, pub(crate) build_dir: PathBuf, pub(crate) dist_dir: PathBuf, + pub(crate) frozen: bool, } #[doc(hidden)] diff --git a/compiler/rustc_codegen_cranelift/build_system/prepare.rs b/compiler/rustc_codegen_cranelift/build_system/prepare.rs index 6769e42d44b94..165296cb4a98f 100644 --- a/compiler/rustc_codegen_cranelift/build_system/prepare.rs +++ b/compiler/rustc_codegen_cranelift/build_system/prepare.rs @@ -3,77 +3,61 @@ use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; -use super::build_sysroot::{BUILD_SYSROOT, ORIG_BUILD_SYSROOT, SYSROOT_RUSTC_VERSION, SYSROOT_SRC}; -use super::path::{Dirs, RelPath}; -use super::rustc_info::{get_default_sysroot, get_rustc_version}; -use super::tests::LIBCORE_TESTS_SRC; -use super::utils::{copy_dir_recursively, git_command, retry_spawn_and_wait, spawn_and_wait}; +use crate::build_sysroot::STDLIB_SRC; +use crate::path::{Dirs, RelPath}; +use crate::rustc_info::get_default_sysroot; +use crate::utils::{ + copy_dir_recursively, git_command, remove_dir_if_exists, retry_spawn_and_wait, spawn_and_wait, +}; pub(crate) fn prepare(dirs: &Dirs) { - RelPath::DOWNLOAD.ensure_fresh(dirs); - - spawn_and_wait(super::build_backend::CG_CLIF.fetch("cargo", "rustc", dirs)); - - prepare_stdlib(dirs); - spawn_and_wait(super::build_sysroot::STANDARD_LIBRARY.fetch("cargo", "rustc", dirs)); - - prepare_coretests(dirs); - spawn_and_wait(super::tests::LIBCORE_TESTS.fetch("cargo", "rustc", dirs)); - - super::tests::RAND_REPO.fetch(dirs); - spawn_and_wait(super::tests::RAND.fetch("cargo", "rustc", dirs)); - super::tests::REGEX_REPO.fetch(dirs); - spawn_and_wait(super::tests::REGEX.fetch("cargo", "rustc", dirs)); - super::tests::PORTABLE_SIMD_REPO.fetch(dirs); - spawn_and_wait(super::tests::PORTABLE_SIMD.fetch("cargo", "rustc", dirs)); + RelPath::DOWNLOAD.ensure_exists(dirs); + crate::tests::RAND_REPO.fetch(dirs); + crate::tests::REGEX_REPO.fetch(dirs); + crate::tests::PORTABLE_SIMD_REPO.fetch(dirs); } -fn prepare_stdlib(dirs: &Dirs) { - let sysroot_src_orig = get_default_sysroot(Path::new("rustc")).join("lib/rustlib/src/rust"); +pub(crate) fn prepare_stdlib(dirs: &Dirs, rustc: &Path) { + let sysroot_src_orig = get_default_sysroot(rustc).join("lib/rustlib/src/rust"); assert!(sysroot_src_orig.exists()); - eprintln!("[COPY] stdlib src"); - - // FIXME ensure builds error out or update the copy if any of the files copied here change - BUILD_SYSROOT.ensure_fresh(dirs); - copy_dir_recursively(&ORIG_BUILD_SYSROOT.to_path(dirs), &BUILD_SYSROOT.to_path(dirs)); - - fs::create_dir_all(SYSROOT_SRC.to_path(dirs).join("library")).unwrap(); - copy_dir_recursively( - &sysroot_src_orig.join("library"), - &SYSROOT_SRC.to_path(dirs).join("library"), - ); - - let rustc_version = get_rustc_version(Path::new("rustc")); - fs::write(SYSROOT_RUSTC_VERSION.to_path(dirs), &rustc_version).unwrap(); - - eprintln!("[GIT] init"); - init_git_repo(&SYSROOT_SRC.to_path(dirs)); - - apply_patches(dirs, "stdlib", &SYSROOT_SRC.to_path(dirs)); -} - -fn prepare_coretests(dirs: &Dirs) { - let sysroot_src_orig = get_default_sysroot(Path::new("rustc")).join("lib/rustlib/src/rust"); - assert!(sysroot_src_orig.exists()); - - eprintln!("[COPY] coretests src"); - - fs::create_dir_all(LIBCORE_TESTS_SRC.to_path(dirs)).unwrap(); - copy_dir_recursively( - &sysroot_src_orig.join("library/core/tests"), - &LIBCORE_TESTS_SRC.to_path(dirs), - ); - - eprintln!("[GIT] init"); - init_git_repo(&LIBCORE_TESTS_SRC.to_path(dirs)); - - apply_patches(dirs, "coretests", &LIBCORE_TESTS_SRC.to_path(dirs)); + apply_patches(dirs, "stdlib", &sysroot_src_orig, &STDLIB_SRC.to_path(dirs)); + + std::fs::write( + STDLIB_SRC.to_path(dirs).join("Cargo.toml"), + r#" +[workspace] +resolver = "1" +members = ["./library/sysroot"] + +[patch.crates-io] +rustc-std-workspace-core = { path = "./library/rustc-std-workspace-core" } +rustc-std-workspace-alloc = { path = "./library/rustc-std-workspace-alloc" } +rustc-std-workspace-std = { path = "./library/rustc-std-workspace-std" } + +# Mandatory for correctly compiling compiler-builtins +[profile.dev.package.compiler_builtins] +debug-assertions = false +overflow-checks = false +codegen-units = 10000 + +[profile.release.package.compiler_builtins] +debug-assertions = false +overflow-checks = false +codegen-units = 10000 +"#, + ) + .unwrap(); + + let source_lockfile = RelPath::PATCHES.to_path(dirs).join("stdlib-lock.toml"); + let target_lockfile = STDLIB_SRC.to_path(dirs).join("Cargo.lock"); + fs::copy(source_lockfile, target_lockfile).unwrap(); } pub(crate) struct GitRepo { url: GitRepoUrl, rev: &'static str, + content_hash: &'static str, patch_name: &'static str, } @@ -81,35 +65,107 @@ enum GitRepoUrl { Github { user: &'static str, repo: &'static str }, } +// Note: This uses a hasher which is not cryptographically secure. This is fine as the hash is meant +// to protect against accidental modification and outdated downloads, not against manipulation. +fn hash_file(file: &std::path::Path) -> u64 { + let contents = std::fs::read(file).unwrap(); + #[allow(deprecated)] + let mut hasher = std::hash::SipHasher::new(); + std::hash::Hash::hash(&contents, &mut hasher); + std::hash::Hasher::finish(&hasher) +} + +fn hash_dir(dir: &std::path::Path) -> u64 { + let mut sub_hashes = std::collections::BTreeMap::new(); + for entry in std::fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + if entry.file_type().unwrap().is_dir() { + sub_hashes + .insert(entry.file_name().to_str().unwrap().to_owned(), hash_dir(&entry.path())); + } else { + sub_hashes + .insert(entry.file_name().to_str().unwrap().to_owned(), hash_file(&entry.path())); + } + } + #[allow(deprecated)] + let mut hasher = std::hash::SipHasher::new(); + std::hash::Hash::hash(&sub_hashes, &mut hasher); + std::hash::Hasher::finish(&hasher) +} + impl GitRepo { pub(crate) const fn github( user: &'static str, repo: &'static str, rev: &'static str, + content_hash: &'static str, patch_name: &'static str, ) -> GitRepo { - GitRepo { url: GitRepoUrl::Github { user, repo }, rev, patch_name } + GitRepo { url: GitRepoUrl::Github { user, repo }, rev, content_hash, patch_name } + } + + fn download_dir(&self, dirs: &Dirs) -> PathBuf { + match self.url { + GitRepoUrl::Github { user: _, repo } => RelPath::DOWNLOAD.join(repo).to_path(dirs), + } } pub(crate) const fn source_dir(&self) -> RelPath { match self.url { - GitRepoUrl::Github { user: _, repo } => RelPath::DOWNLOAD.join(repo), + GitRepoUrl::Github { user: _, repo } => RelPath::BUILD.join(repo), } } pub(crate) fn fetch(&self, dirs: &Dirs) { + let download_dir = self.download_dir(dirs); + + if download_dir.exists() { + let actual_hash = format!("{:016x}", hash_dir(&download_dir)); + if actual_hash == self.content_hash { + println!("[FRESH] {}", download_dir.display()); + return; + } else { + println!( + "Mismatched content hash for {download_dir}: {actual_hash} != {content_hash}. Downloading again.", + download_dir = download_dir.display(), + content_hash = self.content_hash, + ); + } + } + match self.url { GitRepoUrl::Github { user, repo } => { - clone_repo_shallow_github( - dirs, - &self.source_dir().to_path(dirs), - user, - repo, - self.rev, - ); + clone_repo_shallow_github(dirs, &download_dir, user, repo, self.rev); } } - apply_patches(dirs, self.patch_name, &self.source_dir().to_path(dirs)); + + let source_lockfile = + RelPath::PATCHES.to_path(dirs).join(format!("{}-lock.toml", self.patch_name)); + let target_lockfile = download_dir.join("Cargo.lock"); + if source_lockfile.exists() { + fs::copy(source_lockfile, target_lockfile).unwrap(); + } else { + assert!(target_lockfile.exists()); + } + + let actual_hash = format!("{:016x}", hash_dir(&download_dir)); + if actual_hash != self.content_hash { + println!( + "Download of {download_dir} failed with mismatched content hash: {actual_hash} != {content_hash}", + download_dir = download_dir.display(), + content_hash = self.content_hash, + ); + std::process::exit(1); + } + } + + pub(crate) fn patch(&self, dirs: &Dirs) { + apply_patches( + dirs, + self.patch_name, + &self.download_dir(dirs), + &self.source_dir().to_path(dirs), + ); } } @@ -126,6 +182,8 @@ fn clone_repo(download_dir: &Path, repo: &str, rev: &str) { let mut checkout_cmd = git_command(download_dir, "checkout"); checkout_cmd.arg("-q").arg(rev); spawn_and_wait(checkout_cmd); + + std::fs::remove_dir_all(download_dir.join(".git")).unwrap(); } fn clone_repo_shallow_github(dirs: &Dirs, download_dir: &Path, user: &str, repo: &str, rev: &str) { @@ -173,8 +231,6 @@ fn clone_repo_shallow_github(dirs: &Dirs, download_dir: &Path, user: &str, repo: // Rename unpacked dir to the expected name std::fs::rename(archive_dir, &download_dir).unwrap(); - init_git_repo(&download_dir); - // Cleanup std::fs::remove_file(archive_file).unwrap(); } @@ -213,7 +269,22 @@ fn get_patches(dirs: &Dirs, crate_name: &str) -> Vec { patches } -fn apply_patches(dirs: &Dirs, crate_name: &str, target_dir: &Path) { +pub(crate) fn apply_patches(dirs: &Dirs, crate_name: &str, source_dir: &Path, target_dir: &Path) { + // FIXME avoid copy and patch if src, patches and target are unchanged + + eprintln!("[COPY] {crate_name} source"); + + remove_dir_if_exists(target_dir); + fs::create_dir_all(target_dir).unwrap(); + if crate_name == "stdlib" { + fs::create_dir(target_dir.join("library")).unwrap(); + copy_dir_recursively(&source_dir.join("library"), &target_dir.join("library")); + } else { + copy_dir_recursively(source_dir, target_dir); + } + + init_git_repo(target_dir); + if crate_name == "" { return; } diff --git a/compiler/rustc_codegen_cranelift/build_system/rustc_info.rs b/compiler/rustc_codegen_cranelift/build_system/rustc_info.rs index a70453b442289..5b71504e90a4f 100644 --- a/compiler/rustc_codegen_cranelift/build_system/rustc_info.rs +++ b/compiler/rustc_codegen_cranelift/build_system/rustc_info.rs @@ -1,15 +1,9 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; -pub(crate) fn get_rustc_version(rustc: &Path) -> String { +pub(crate) fn get_host_triple(rustc: &Path) -> String { let version_info = - Command::new(rustc).stderr(Stdio::inherit()).args(&["-V"]).output().unwrap().stdout; - String::from_utf8(version_info).unwrap() -} - -pub(crate) fn get_host_triple() -> String { - let version_info = - Command::new("rustc").stderr(Stdio::inherit()).args(&["-vV"]).output().unwrap().stdout; + Command::new(rustc).stderr(Stdio::inherit()).args(&["-vV"]).output().unwrap().stdout; String::from_utf8(version_info) .unwrap() .lines() @@ -34,6 +28,9 @@ pub(crate) fn get_toolchain_name() -> String { } pub(crate) fn get_cargo_path() -> PathBuf { + if let Ok(cargo) = std::env::var("CARGO") { + return PathBuf::from(cargo); + } let cargo_path = Command::new("rustup") .stderr(Stdio::inherit()) .args(&["which", "cargo"]) @@ -44,6 +41,9 @@ pub(crate) fn get_cargo_path() -> PathBuf { } pub(crate) fn get_rustc_path() -> PathBuf { + if let Ok(rustc) = std::env::var("RUSTC") { + return PathBuf::from(rustc); + } let rustc_path = Command::new("rustup") .stderr(Stdio::inherit()) .args(&["which", "rustc"]) @@ -54,6 +54,9 @@ pub(crate) fn get_rustc_path() -> PathBuf { } pub(crate) fn get_rustdoc_path() -> PathBuf { + if let Ok(rustdoc) = std::env::var("RUSTDOC") { + return PathBuf::from(rustdoc); + } let rustc_path = Command::new("rustup") .stderr(Stdio::inherit()) .args(&["which", "rustdoc"]) @@ -73,8 +76,9 @@ pub(crate) fn get_default_sysroot(rustc: &Path) -> PathBuf { Path::new(String::from_utf8(default_sysroot).unwrap().trim()).to_owned() } -pub(crate) fn get_file_name(crate_name: &str, crate_type: &str) -> String { - let file_name = Command::new("rustc") +// FIXME call once for each target and pass result around in struct +pub(crate) fn get_file_name(rustc: &Path, crate_name: &str, crate_type: &str) -> String { + let file_name = Command::new(rustc) .stderr(Stdio::inherit()) .args(&[ "--crate-name", diff --git a/compiler/rustc_codegen_cranelift/build_system/shared_utils.rs b/compiler/rustc_codegen_cranelift/build_system/shared_utils.rs new file mode 100644 index 0000000000000..0aea545ff7dae --- /dev/null +++ b/compiler/rustc_codegen_cranelift/build_system/shared_utils.rs @@ -0,0 +1,26 @@ +// This file is used by both the build system as well as cargo-clif.rs + +// Adapted from https://github.com/rust-lang/cargo/blob/6dc1deaddf62c7748c9097c7ea88e9ec77ff1a1a/src/cargo/core/compiler/build_context/target_info.rs#L750-L77 +pub(crate) fn rustflags_from_env(kind: &str) -> Vec { + // First try CARGO_ENCODED_RUSTFLAGS from the environment. + // Prefer this over RUSTFLAGS since it's less prone to encoding errors. + if let Ok(a) = std::env::var(format!("CARGO_ENCODED_{}", kind)) { + if a.is_empty() { + return Vec::new(); + } + return a.split('\x1f').map(str::to_string).collect(); + } + + // Then try RUSTFLAGS from the environment + if let Ok(a) = std::env::var(kind) { + let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string); + return args.collect(); + } + + // No rustflags to be collected from the environment + Vec::new() +} + +pub(crate) fn rustflags_to_cmd_env(cmd: &mut std::process::Command, kind: &str, flags: &[String]) { + cmd.env(format!("CARGO_ENCODED_{}", kind), flags.join("\x1f")); +} diff --git a/compiler/rustc_codegen_cranelift/build_system/tests.rs b/compiler/rustc_codegen_cranelift/build_system/tests.rs index 0c25b4aadfa08..e7bd8b1278c66 100644 --- a/compiler/rustc_codegen_cranelift/build_system/tests.rs +++ b/compiler/rustc_codegen_cranelift/build_system/tests.rs @@ -1,15 +1,17 @@ -use super::build_sysroot; -use super::config; -use super::path::{Dirs, RelPath}; -use super::prepare::GitRepo; -use super::utils::{spawn_and_wait, spawn_and_wait_with_input, CargoProject, Compiler}; -use super::SysrootKind; -use std::env; use std::ffi::OsStr; use std::fs; -use std::path::Path; +use std::path::PathBuf; use std::process::Command; +use crate::build_sysroot; +use crate::config; +use crate::path::{Dirs, RelPath}; +use crate::prepare::{apply_patches, GitRepo}; +use crate::rustc_info::get_default_sysroot; +use crate::shared_utils::rustflags_from_env; +use crate::utils::{spawn_and_wait, spawn_and_wait_with_input, CargoProject, Compiler, LogGroup}; +use crate::{CodegenBackend, SysrootKind}; + static BUILD_EXAMPLE_OUT_DIR: RelPath = RelPath::BUILD.join("example"); struct TestCase { @@ -18,15 +20,16 @@ struct TestCase { } enum TestCaseCmd { - Custom { func: &'static dyn Fn(&TestRunner) }, + Custom { func: &'static dyn Fn(&TestRunner<'_>) }, BuildLib { source: &'static str, crate_types: &'static str }, + BuildBin { source: &'static str }, BuildBinAndRun { source: &'static str, args: &'static [&'static str] }, JitBin { source: &'static str, args: &'static str }, } impl TestCase { // FIXME reduce usage of custom test case commands - const fn custom(config: &'static str, func: &'static dyn Fn(&TestRunner)) -> Self { + const fn custom(config: &'static str, func: &'static dyn Fn(&TestRunner<'_>)) -> Self { Self { config, cmd: TestCaseCmd::Custom { func } } } @@ -38,6 +41,10 @@ impl TestCase { Self { config, cmd: TestCaseCmd::BuildLib { source, crate_types } } } + const fn build_bin(config: &'static str, source: &'static str) -> Self { + Self { config, cmd: TestCaseCmd::BuildBin { source } } + } + const fn build_bin_and_run( config: &'static str, source: &'static str, @@ -91,36 +98,50 @@ const BASE_SYSROOT_SUITE: &[TestCase] = &[ TestCase::build_bin_and_run("aot.float-minmax-pass", "example/float-minmax-pass.rs", &[]), TestCase::build_bin_and_run("aot.mod_bench", "example/mod_bench.rs", &[]), TestCase::build_bin_and_run("aot.issue-72793", "example/issue-72793.rs", &[]), + TestCase::build_bin("aot.issue-59326", "example/issue-59326.rs"), ]; // FIXME(rust-random/rand#1293): Newer rand versions fail to test on Windows. Update once this is // fixed. -pub(crate) static RAND_REPO: GitRepo = - GitRepo::github("rust-random", "rand", "50b9a447410860af8d6db9a208c3576886955874", "rand"); +pub(crate) static RAND_REPO: GitRepo = GitRepo::github( + "rust-random", + "rand", + "50b9a447410860af8d6db9a208c3576886955874", + "446203b96054891e", + "rand", +); -pub(crate) static RAND: CargoProject = CargoProject::new(&RAND_REPO.source_dir(), "rand"); +pub(crate) static RAND: CargoProject = CargoProject::new(&RAND_REPO.source_dir(), "rand_target"); -pub(crate) static REGEX_REPO: GitRepo = - GitRepo::github("rust-lang", "regex", "32fed9429eafba0ae92a64b01796a0c5a75b88c8", "regex"); +pub(crate) static REGEX_REPO: GitRepo = GitRepo::github( + "rust-lang", + "regex", + "32fed9429eafba0ae92a64b01796a0c5a75b88c8", + "fcc4df7c5b902633", + "regex", +); -pub(crate) static REGEX: CargoProject = CargoProject::new(®EX_REPO.source_dir(), "regex"); +pub(crate) static REGEX: CargoProject = CargoProject::new(®EX_REPO.source_dir(), "regex_target"); pub(crate) static PORTABLE_SIMD_REPO: GitRepo = GitRepo::github( "rust-lang", "portable-simd", - "ad8afa8c81273b3b49acbea38cd3bcf17a34cf2b", + "7c7dbe0c505ccbc02ff30c1e37381ab1d47bf46f", + "5bcc9c544f6fa7bd", "portable-simd", ); pub(crate) static PORTABLE_SIMD: CargoProject = - CargoProject::new(&PORTABLE_SIMD_REPO.source_dir(), "portable_simd"); + CargoProject::new(&PORTABLE_SIMD_REPO.source_dir(), "portable-simd_target"); -pub(crate) static LIBCORE_TESTS_SRC: RelPath = RelPath::DOWNLOAD.join("coretests_src"); +static LIBCORE_TESTS_SRC: RelPath = RelPath::BUILD.join("coretests"); -pub(crate) static LIBCORE_TESTS: CargoProject = CargoProject::new(&LIBCORE_TESTS_SRC, "core_tests"); +static LIBCORE_TESTS: CargoProject = CargoProject::new(&LIBCORE_TESTS_SRC, "coretests_target"); const EXTENDED_SYSROOT_SUITE: &[TestCase] = &[ TestCase::custom("test.rust-random/rand", &|runner| { + RAND_REPO.patch(&runner.dirs); + RAND.clean(&runner.dirs); if runner.is_native { @@ -135,6 +156,17 @@ const EXTENDED_SYSROOT_SUITE: &[TestCase] = &[ } }), TestCase::custom("test.libcore", &|runner| { + apply_patches( + &runner.dirs, + "coretests", + &runner.stdlib_source.join("library/core/tests"), + &LIBCORE_TESTS_SRC.to_path(&runner.dirs), + ); + + let source_lockfile = RelPath::PATCHES.to_path(&runner.dirs).join("coretests-lock.toml"); + let target_lockfile = LIBCORE_TESTS_SRC.to_path(&runner.dirs).join("Cargo.lock"); + fs::copy(source_lockfile, target_lockfile).unwrap(); + LIBCORE_TESTS.clean(&runner.dirs); if runner.is_native { @@ -149,6 +181,8 @@ const EXTENDED_SYSROOT_SUITE: &[TestCase] = &[ } }), TestCase::custom("test.regex-shootout-regex-dna", &|runner| { + REGEX_REPO.patch(&runner.dirs); + REGEX.clean(&runner.dirs); let mut build_cmd = REGEX.build(&runner.target_compiler, &runner.dirs); @@ -181,6 +215,8 @@ const EXTENDED_SYSROOT_SUITE: &[TestCase] = &[ } }), TestCase::custom("test.regex", &|runner| { + REGEX_REPO.patch(&runner.dirs); + REGEX.clean(&runner.dirs); if runner.is_native { @@ -197,6 +233,8 @@ const EXTENDED_SYSROOT_SUITE: &[TestCase] = &[ } }), TestCase::custom("test.portable-simd", &|runner| { + PORTABLE_SIMD_REPO.patch(&runner.dirs); + PORTABLE_SIMD.clean(&runner.dirs); let mut build_cmd = PORTABLE_SIMD.build(&runner.target_compiler, &runner.dirs); @@ -215,24 +253,35 @@ pub(crate) fn run_tests( dirs: &Dirs, channel: &str, sysroot_kind: SysrootKind, - cg_clif_dylib: &Path, + use_unstable_features: bool, + skip_tests: &[&str], + cg_clif_dylib: &CodegenBackend, bootstrap_host_compiler: &Compiler, + rustup_toolchain_name: Option<&str>, target_triple: String, ) { - if config::get_bool("testsuite.no_sysroot") { + let stdlib_source = + get_default_sysroot(&bootstrap_host_compiler.rustc).join("lib/rustlib/src/rust"); + assert!(stdlib_source.exists()); + + if config::get_bool("testsuite.no_sysroot") && !skip_tests.contains(&"testsuite.no_sysroot") { let target_compiler = build_sysroot::build_sysroot( dirs, channel, SysrootKind::None, cg_clif_dylib, bootstrap_host_compiler, + rustup_toolchain_name, target_triple.clone(), ); let runner = TestRunner::new( dirs.clone(), target_compiler, + use_unstable_features, + skip_tests, bootstrap_host_compiler.triple == target_triple, + stdlib_source.clone(), ); BUILD_EXAMPLE_OUT_DIR.ensure_fresh(dirs); @@ -241,23 +290,32 @@ pub(crate) fn run_tests( eprintln!("[SKIP] no_sysroot tests"); } - let run_base_sysroot = config::get_bool("testsuite.base_sysroot"); - let run_extended_sysroot = config::get_bool("testsuite.extended_sysroot"); + let run_base_sysroot = config::get_bool("testsuite.base_sysroot") + && !skip_tests.contains(&"testsuite.base_sysroot"); + let run_extended_sysroot = config::get_bool("testsuite.extended_sysroot") + && !skip_tests.contains(&"testsuite.extended_sysroot"); if run_base_sysroot || run_extended_sysroot { - let target_compiler = build_sysroot::build_sysroot( + let mut target_compiler = build_sysroot::build_sysroot( dirs, channel, sysroot_kind, cg_clif_dylib, bootstrap_host_compiler, + rustup_toolchain_name, target_triple.clone(), ); + // Rust's build system denies a couple of lints that trigger on several of the test + // projects. Changing the code to fix them is not worth it, so just silence all lints. + target_compiler.rustflags.push("--cap-lints=allow".to_owned()); let runner = TestRunner::new( dirs.clone(), target_compiler, + use_unstable_features, + skip_tests, bootstrap_host_compiler.triple == target_triple, + stdlib_source, ); if run_base_sysroot { @@ -274,34 +332,50 @@ pub(crate) fn run_tests( } } -struct TestRunner { +struct TestRunner<'a> { is_native: bool, jit_supported: bool, + use_unstable_features: bool, + skip_tests: &'a [&'a str], dirs: Dirs, target_compiler: Compiler, + stdlib_source: PathBuf, } -impl TestRunner { - fn new(dirs: Dirs, mut target_compiler: Compiler, is_native: bool) -> Self { - if let Ok(rustflags) = env::var("RUSTFLAGS") { - target_compiler.rustflags.push(' '); - target_compiler.rustflags.push_str(&rustflags); - } - if let Ok(rustdocflags) = env::var("RUSTDOCFLAGS") { - target_compiler.rustdocflags.push(' '); - target_compiler.rustdocflags.push_str(&rustdocflags); - } +impl<'a> TestRunner<'a> { + fn new( + dirs: Dirs, + mut target_compiler: Compiler, + use_unstable_features: bool, + skip_tests: &'a [&'a str], + is_native: bool, + stdlib_source: PathBuf, + ) -> Self { + target_compiler.rustflags.extend(rustflags_from_env("RUSTFLAGS")); + target_compiler.rustdocflags.extend(rustflags_from_env("RUSTDOCFLAGS")); // FIXME fix `#[linkage = "extern_weak"]` without this if target_compiler.triple.contains("darwin") { - target_compiler.rustflags.push_str(" -Clink-arg=-undefined -Clink-arg=dynamic_lookup"); + target_compiler.rustflags.extend([ + "-Clink-arg=-undefined".to_owned(), + "-Clink-arg=dynamic_lookup".to_owned(), + ]); } - let jit_supported = is_native + let jit_supported = use_unstable_features + && is_native && target_compiler.triple.contains("x86_64") && !target_compiler.triple.contains("windows"); - Self { is_native, jit_supported, dirs, target_compiler } + Self { + is_native, + jit_supported, + use_unstable_features, + skip_tests, + dirs, + target_compiler, + stdlib_source, + } } fn run_testsuite(&self, tests: &[TestCase]) { @@ -310,20 +384,46 @@ impl TestRunner { let tag = tag.to_uppercase(); let is_jit_test = tag == "JIT"; - if !config::get_bool(config) || (is_jit_test && !self.jit_supported) { + let _guard = if !config::get_bool(config) + || (is_jit_test && !self.jit_supported) + || self.skip_tests.contains(&config) + { eprintln!("[{tag}] {testname} (skipped)"); continue; } else { + let guard = LogGroup::guard(&format!("[{tag}] {testname}")); eprintln!("[{tag}] {testname}"); - } + guard + }; match *cmd { TestCaseCmd::Custom { func } => func(self), TestCaseCmd::BuildLib { source, crate_types } => { - self.run_rustc([source, "--crate-type", crate_types]); + if self.use_unstable_features { + self.run_rustc([source, "--crate-type", crate_types]); + } else { + self.run_rustc([ + source, + "--crate-type", + crate_types, + "--cfg", + "no_unstable_features", + ]); + } + } + TestCaseCmd::BuildBin { source } => { + if self.use_unstable_features { + self.run_rustc([source]); + } else { + self.run_rustc([source, "--cfg", "no_unstable_features"]); + } } TestCaseCmd::BuildBinAndRun { source, args } => { - self.run_rustc([source]); + if self.use_unstable_features { + self.run_rustc([source]); + } else { + self.run_rustc([source, "--cfg", "no_unstable_features"]); + } self.run_out_command( source.split('/').last().unwrap().split('.').next().unwrap(), args, @@ -368,7 +468,7 @@ impl TestRunner { S: AsRef, { let mut cmd = Command::new(&self.target_compiler.rustc); - cmd.args(self.target_compiler.rustflags.split_whitespace()); + cmd.args(&self.target_compiler.rustflags); cmd.arg("-L"); cmd.arg(format!("crate={}", BUILD_EXAMPLE_OUT_DIR.to_path(&self.dirs).display())); cmd.arg("--out-dir"); diff --git a/compiler/rustc_codegen_cranelift/build_system/usage.txt b/compiler/rustc_codegen_cranelift/build_system/usage.txt index ab98ccc35a58a..f652599447bcf 100644 --- a/compiler/rustc_codegen_cranelift/build_system/usage.txt +++ b/compiler/rustc_codegen_cranelift/build_system/usage.txt @@ -1,11 +1,11 @@ The build system of cg_clif. USAGE: - ./y.rs prepare [--out-dir DIR] - ./y.rs build [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--no-unstable-features] - ./y.rs test [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--no-unstable-features] - ./y.rs abi-cafe [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--no-unstable-features] - ./y.rs bench [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--no-unstable-features] + ./y.sh prepare [--out-dir DIR] [--download-dir DIR] + ./y.sh build [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen] + ./y.sh test [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen] [--skip-test TESTNAME] + ./y.sh abi-cafe [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen] + ./y.sh bench [--debug] [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen] OPTIONS: --debug @@ -22,14 +22,28 @@ OPTIONS: Specify the directory in which the download, build and dist directories are stored. By default this is the working directory. + --download-dir DIR + Specify the directory in which the download directory is stored. Overrides --out-dir. + --no-unstable-features Some features are not yet ready for production usage. This option will disable these features. This includes the JIT mode and inline assembly support. + --frozen + Require Cargo.lock and cache are up to date + + --skip-test TESTNAME + Skip testing the TESTNAME test. The test name format is the same as config.txt. + + --use-backend NAME + Use the existing Cranelift (or other) backend of the rustc with which we built. + Warning: This is meant for use in rust's CI only! + REQUIREMENTS: - * Rustup: The build system has a hard coded dependency on rustup to install the right nightly - version and make sure it is used where necessary. - * Git: `./y.rs prepare` uses git for applying patches and on Windows for downloading test repos. - * Curl and tar (non-Windows only): Used by `./y.rs prepare` to download a single commit for + * Rustup: By default rustup is used to install the right nightly version. If you don't want to + use rustup, you can manually install the nightly version indicated by rust-toolchain.toml and + point the CARGO, RUSTC and RUSTDOC env vars to the right executables. + * Git: Git is used for applying patches and on Windows for downloading test repos. + * Curl and tar (non-Windows only): Used by `./y.sh prepare` to download a single commit for repos. Git will be used to clone the whole repo when using Windows. - * [Hyperfine](https://github.com/sharkdp/hyperfine/): Used for benchmarking with `./y.rs bench`. + * [Hyperfine](https://github.com/sharkdp/hyperfine/): Used for benchmarking with `./y.sh bench`. diff --git a/compiler/rustc_codegen_cranelift/build_system/utils.rs b/compiler/rustc_codegen_cranelift/build_system/utils.rs index abc5bab494224..24624cdeab1f1 100644 --- a/compiler/rustc_codegen_cranelift/build_system/utils.rs +++ b/compiler/rustc_codegen_cranelift/build_system/utils.rs @@ -3,40 +3,29 @@ use std::fs; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::{self, Command, Stdio}; +use std::sync::atomic::{AtomicBool, Ordering}; -use super::path::{Dirs, RelPath}; -use super::rustc_info::{get_cargo_path, get_rustc_path, get_rustdoc_path}; +use crate::path::{Dirs, RelPath}; +use crate::shared_utils::rustflags_to_cmd_env; #[derive(Clone, Debug)] pub(crate) struct Compiler { pub(crate) cargo: PathBuf, pub(crate) rustc: PathBuf, pub(crate) rustdoc: PathBuf, - pub(crate) rustflags: String, - pub(crate) rustdocflags: String, + pub(crate) rustflags: Vec, + pub(crate) rustdocflags: Vec, pub(crate) triple: String, pub(crate) runner: Vec, } impl Compiler { - pub(crate) fn bootstrap_with_triple(triple: String) -> Compiler { - Compiler { - cargo: get_cargo_path(), - rustc: get_rustc_path(), - rustdoc: get_rustdoc_path(), - rustflags: String::new(), - rustdocflags: String::new(), - triple, - runner: vec![], - } - } - pub(crate) fn set_cross_linker_and_runner(&mut self) { match self.triple.as_str() { "aarch64-unknown-linux-gnu" => { // We are cross-compiling for aarch64. Use the correct linker and run tests in qemu. - self.rustflags += " -Clinker=aarch64-linux-gnu-gcc"; - self.rustdocflags += " -Clinker=aarch64-linux-gnu-gcc"; + self.rustflags.push("-Clinker=aarch64-linux-gnu-gcc".to_owned()); + self.rustdocflags.push("-Clinker=aarch64-linux-gnu-gcc".to_owned()); self.runner = vec![ "qemu-aarch64".to_owned(), "-L".to_owned(), @@ -45,8 +34,8 @@ impl Compiler { } "s390x-unknown-linux-gnu" => { // We are cross-compiling for s390x. Use the correct linker and run tests in qemu. - self.rustflags += " -Clinker=s390x-linux-gnu-gcc"; - self.rustdocflags += " -Clinker=s390x-linux-gnu-gcc"; + self.rustflags.push("-Clinker=s390x-linux-gnu-gcc".to_owned()); + self.rustdocflags.push("-Clinker=s390x-linux-gnu-gcc".to_owned()); self.runner = vec![ "qemu-s390x".to_owned(), "-L".to_owned(), @@ -95,7 +84,11 @@ impl CargoProject { .arg(self.manifest_path(dirs)) .arg("--target-dir") .arg(self.target_dir(dirs)) - .arg("--frozen"); + .arg("--locked"); + + if dirs.frozen { + cmd.arg("--frozen"); + } cmd } @@ -108,8 +101,8 @@ impl CargoProject { cmd.env("RUSTC", &compiler.rustc); cmd.env("RUSTDOC", &compiler.rustdoc); - cmd.env("RUSTFLAGS", &compiler.rustflags); - cmd.env("RUSTDOCFLAGS", &compiler.rustdocflags); + rustflags_to_cmd_env(&mut cmd, "RUSTFLAGS", &compiler.rustflags); + rustflags_to_cmd_env(&mut cmd, "RUSTDOCFLAGS", &compiler.rustdocflags); if !compiler.runner.is_empty() { cmd.env( format!("CARGO_TARGET_{}_RUNNER", compiler.triple.to_uppercase().replace('-', "_")), @@ -120,23 +113,6 @@ impl CargoProject { cmd } - #[must_use] - pub(crate) fn fetch( - &self, - cargo: impl AsRef, - rustc: impl AsRef, - dirs: &Dirs, - ) -> Command { - let mut cmd = Command::new(cargo.as_ref()); - - cmd.env("RUSTC", rustc.as_ref()) - .arg("fetch") - .arg("--manifest-path") - .arg(self.manifest_path(dirs)); - - cmd - } - pub(crate) fn clean(&self, dirs: &Dirs) { let _ = fs::remove_dir_all(self.target_dir(dirs)); } @@ -162,11 +138,13 @@ pub(crate) fn hyperfine_command( warmup: u64, runs: u64, prepare: Option<&str>, - a: &str, - b: &str, + cmds: &[(&str, &str)], + markdown_export: &Path, ) -> Command { let mut bench = Command::new("hyperfine"); + bench.arg("--export-markdown").arg(markdown_export); + if warmup != 0 { bench.arg("--warmup").arg(warmup.to_string()); } @@ -179,7 +157,12 @@ pub(crate) fn hyperfine_command( bench.arg("--prepare").arg(prepare); } - bench.arg(a).arg(b); + for &(name, cmd) in cmds { + if name != "" { + bench.arg("-n").arg(name); + } + bench.arg(cmd); + } bench } @@ -194,6 +177,8 @@ pub(crate) fn git_command<'a>(repo_dir: impl Into>, cmd: &str) .arg("user.email=dummy@example.com") .arg("-c") .arg("core.autocrlf=false") + .arg("-c") + .arg("commit.gpgSign=false") .arg(cmd); if let Some(repo_dir) = repo_dir.into() { git_cmd.current_dir(repo_dir); @@ -285,3 +270,40 @@ pub(crate) fn is_ci() -> bool { pub(crate) fn is_ci_opt() -> bool { env::var("CI_OPT").is_ok() } + +static IN_GROUP: AtomicBool = AtomicBool::new(false); +pub(crate) struct LogGroup { + is_gha: bool, +} + +impl LogGroup { + pub(crate) fn guard(name: &str) -> LogGroup { + let is_gha = env::var("GITHUB_ACTIONS").is_ok(); + + assert!(!IN_GROUP.swap(true, Ordering::SeqCst)); + if is_gha { + eprintln!("::group::{name}"); + } + + LogGroup { is_gha } + } +} + +impl Drop for LogGroup { + fn drop(&mut self) { + if self.is_gha { + eprintln!("::endgroup::"); + } + IN_GROUP.store(false, Ordering::SeqCst); + } +} + +pub(crate) fn maybe_incremental(cmd: &mut Command) { + if is_ci() || std::env::var("CARGO_BUILD_INCREMENTAL").map_or(false, |val| val == "false") { + // Disabling incr comp reduces cache size and incr comp doesn't save as much on CI anyway + cmd.env("CARGO_BUILD_INCREMENTAL", "false"); + } else { + // Force incr comp even in release mode unless in CI or incremental builds are explicitly disabled + cmd.env("CARGO_BUILD_INCREMENTAL", "true"); + } +} diff --git a/compiler/rustc_codegen_cranelift/clean_all.sh b/compiler/rustc_codegen_cranelift/clean_all.sh index cdfc2e143e674..19405a53d1c65 100755 --- a/compiler/rustc_codegen_cranelift/clean_all.sh +++ b/compiler/rustc_codegen_cranelift/clean_all.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -rm -rf target/ download/ build/ dist/ y.bin y.bin.dSYM y.exe y.pdb +rm -rf target/ build_system/target download/ build/ dist/ y.bin y.bin.dSYM y.exe y.pdb # Kept for now in case someone updates their checkout of cg_clif before running clean_all.sh # FIXME remove at some point in the future diff --git a/compiler/rustc_codegen_cranelift/config.txt b/compiler/rustc_codegen_cranelift/config.txt index d6e3924a24d64..fa1c9f4259c49 100644 --- a/compiler/rustc_codegen_cranelift/config.txt +++ b/compiler/rustc_codegen_cranelift/config.txt @@ -41,6 +41,7 @@ aot.track-caller-attribute aot.float-minmax-pass aot.mod_bench aot.issue-72793 +aot.issue-59326 testsuite.extended_sysroot test.rust-random/rand diff --git a/compiler/rustc_codegen_cranelift/docs/usage.md b/compiler/rustc_codegen_cranelift/docs/usage.md index 4c2b0fa170498..c6210f958d6c1 100644 --- a/compiler/rustc_codegen_cranelift/docs/usage.md +++ b/compiler/rustc_codegen_cranelift/docs/usage.md @@ -2,7 +2,7 @@ rustc_codegen_cranelift can be used as a near-drop-in replacement for `cargo build` or `cargo run` for existing projects. -Assuming `$cg_clif_dir` is the directory you cloned this repo into and you followed the instructions (`y.rs prepare` and `y.rs build` or `test.sh`). +Assuming `$cg_clif_dir` is the directory you cloned this repo into and you followed the instructions (`y.sh prepare` and `y.sh build` or `test.sh`). ## Cargo diff --git a/compiler/rustc_codegen_cranelift/example/alloc_example.rs b/compiler/rustc_codegen_cranelift/example/alloc_example.rs index d994e2fbc0ae0..117eed5afd8ab 100644 --- a/compiler/rustc_codegen_cranelift/example/alloc_example.rs +++ b/compiler/rustc_codegen_cranelift/example/alloc_example.rs @@ -1,4 +1,4 @@ -#![feature(start, core_intrinsics, alloc_error_handler)] +#![feature(start, core_intrinsics, alloc_error_handler, lang_items)] #![no_std] extern crate alloc; @@ -27,6 +27,11 @@ fn alloc_error_handler(_: alloc::alloc::Layout) -> ! { core::intrinsics::abort(); } +#[lang = "eh_personality"] +fn eh_personality() -> ! { + loop {} +} + #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { let world: Box<&str> = Box::new("Hello World!\0"); diff --git a/compiler/rustc_codegen_cranelift/example/float-minmax-pass.rs b/compiler/rustc_codegen_cranelift/example/float-minmax-pass.rs index b8f901d1ba176..80a2776ca1e2f 100644 --- a/compiler/rustc_codegen_cranelift/example/float-minmax-pass.rs +++ b/compiler/rustc_codegen_cranelift/example/float-minmax-pass.rs @@ -22,7 +22,7 @@ fn main() { #[cfg(not(any(target_arch = "mips", target_arch = "mips64")))] let nan = f32::NAN; - // MIPS hardware treats f32::NAN as SNAN. Clear the signaling bit. + // MIPS hardware except MIPS R6 treats f32::NAN as SNAN. Clear the signaling bit. // See https://github.com/rust-lang/rust/issues/52746. #[cfg(any(target_arch = "mips", target_arch = "mips64"))] let nan = f32::from_bits(f32::NAN.to_bits() - 1); diff --git a/compiler/rustc_codegen_cranelift/example/issue-59326.rs b/compiler/rustc_codegen_cranelift/example/issue-59326.rs new file mode 100644 index 0000000000000..70b7c94e15c11 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/example/issue-59326.rs @@ -0,0 +1,27 @@ +// Based on https://github.com/rust-lang/rust/blob/689511047a75a30825e367d4fd45c74604d0b15e/tests/ui/issues/issue-59326.rs#L1 +// check-pass +trait Service { + type S; +} + +trait Framing { + type F; +} + +impl Framing for () { + type F = (); +} + +trait HttpService: Service {} + +type BoxService = Box>; + +fn build_server BoxService>(_: F) {} + +fn make_server() -> Box> { + unimplemented!() +} + +fn main() { + build_server(|| make_server()) +} diff --git a/compiler/rustc_codegen_cranelift/example/mini_core.rs b/compiler/rustc_codegen_cranelift/example/mini_core.rs index ea97e9f060e04..34c7e44b2881c 100644 --- a/compiler/rustc_codegen_cranelift/example/mini_core.rs +++ b/compiler/rustc_codegen_cranelift/example/mini_core.rs @@ -11,7 +11,7 @@ thread_local )] #![no_core] -#![allow(dead_code)] +#![allow(dead_code, internal_features)] #[lang = "sized"] pub trait Sized {} @@ -502,6 +502,9 @@ pub unsafe fn drop_in_place(to_drop: *mut T) { drop_in_place(to_drop); } +#[lang = "unpin"] +pub auto trait Unpin {} + #[lang = "deref"] pub trait Deref { type Target: ?Sized; @@ -526,7 +529,7 @@ impl CoerceUnsized> for Unique where T: Unsiz impl DispatchFromDyn> for Unique where T: Unsize {} #[lang = "owned_box"] -pub struct Box(Unique, ()); +pub struct Box(Unique, A); impl, U: ?Sized> CoerceUnsized> for Box {} @@ -541,9 +544,12 @@ impl Box { } } -impl Drop for Box { +impl Drop for Box { fn drop(&mut self) { - // drop is currently performed by compiler. + // inner value is dropped by compiler + unsafe { + libc::free(self.0.pointer.0 as *mut u8); + } } } @@ -560,11 +566,6 @@ unsafe fn allocate(size: usize, _align: usize) -> *mut u8 { libc::malloc(size) } -#[lang = "box_free"] -unsafe fn box_free(ptr: Unique, _alloc: ()) { - libc::free(ptr.pointer.0 as *mut u8); -} - #[lang = "drop"] pub trait Drop { fn drop(&mut self); diff --git a/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs b/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs index 5a55aa215bfd7..91de04d97702f 100644 --- a/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs +++ b/compiler/rustc_codegen_cranelift/example/mini_core_hello_world.rs @@ -1,6 +1,6 @@ #![feature(no_core, lang_items, never_type, linkage, extern_types, thread_local, repr_simd)] #![no_core] -#![allow(dead_code, non_camel_case_types)] +#![allow(dead_code, non_camel_case_types, internal_features)] extern crate mini_core; @@ -322,7 +322,12 @@ fn main() { #[cfg(all(not(jit), not(all(windows, target_env = "gnu"))))] test_tls(); - #[cfg(all(not(jit), target_arch = "x86_64", any(target_os = "linux", target_os = "darwin")))] + #[cfg(all( + not(jit), + not(no_unstable_features), + target_arch = "x86_64", + any(target_os = "linux", target_os = "macos") + ))] unsafe { global_asm_test(); } @@ -350,12 +355,17 @@ fn main() { let _a = f.0[0]; } -#[cfg(all(not(jit), target_arch = "x86_64", any(target_os = "linux", target_os = "darwin")))] +#[cfg(all( + not(jit), + not(no_unstable_features), + target_arch = "x86_64", + any(target_os = "linux", target_os = "macos") +))] extern "C" { fn global_asm_test(); } -#[cfg(all(not(jit), target_arch = "x86_64", target_os = "linux"))] +#[cfg(all(not(jit), not(no_unstable_features), target_arch = "x86_64", target_os = "linux"))] global_asm! { " .global global_asm_test @@ -365,7 +375,7 @@ global_asm! { " } -#[cfg(all(not(jit), target_arch = "x86_64", target_os = "darwin"))] +#[cfg(all(not(jit), not(no_unstable_features), target_arch = "x86_64", target_os = "macos"))] global_asm! { " .global _global_asm_test diff --git a/compiler/rustc_codegen_cranelift/example/std_example.rs b/compiler/rustc_codegen_cranelift/example/std_example.rs index ab4045d11a663..490cc2404f626 100644 --- a/compiler/rustc_codegen_cranelift/example/std_example.rs +++ b/compiler/rustc_codegen_cranelift/example/std_example.rs @@ -1,4 +1,12 @@ -#![feature(core_intrinsics, generators, generator_trait, is_sorted, repr_simd)] +#![feature( + core_intrinsics, + generators, + generator_trait, + is_sorted, + repr_simd, + tuple_trait, + unboxed_closures +)] #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; @@ -155,12 +163,34 @@ fn main() { } foo(I64X2(0, 0)); + + transmute_fat_pointer(); + + rust_call_abi(); } fn panic(_: u128) { panic!(); } +use std::mem::transmute; + +#[cfg(target_pointer_width = "32")] +type TwoPtrs = i64; +#[cfg(target_pointer_width = "64")] +type TwoPtrs = i128; + +fn transmute_fat_pointer() -> TwoPtrs { + unsafe { transmute::<_, TwoPtrs>("true !") } +} + +extern "rust-call" fn rust_call_abi_callee(_: T) {} + +fn rust_call_abi() { + rust_call_abi_callee(()); + rust_call_abi_callee((1, 2)); +} + #[repr(simd)] struct I64X2(i64, i64); @@ -197,6 +227,10 @@ unsafe fn test_simd() { test_mm_extract_epi8(); test_mm_insert_epi16(); + test_mm_shuffle_epi8(); + + test_mm256_shuffle_epi8(); + test_mm256_permute2x128_si256(); #[rustfmt::skip] let mask1 = _mm_movemask_epi8(dbg!(_mm_setr_epi8(255u8 as i8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))); @@ -293,6 +327,12 @@ pub unsafe fn assert_eq_m128d(a: __m128d, b: __m128d) { } } +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx")] +pub unsafe fn assert_eq_m256i(a: __m256i, b: __m256i) { + assert_eq!(std::mem::transmute::<_, [u64; 4]>(a), std::mem::transmute::<_, [u64; 4]>(b)) +} + #[cfg(target_arch = "x86_64")] #[target_feature(enable = "sse2")] unsafe fn test_mm_cvtsi128_si64() { @@ -336,6 +376,64 @@ unsafe fn test_mm_insert_epi16() { assert_eq_m128i(r, e); } +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "ssse3")] +unsafe fn test_mm_shuffle_epi8() { + #[rustfmt::skip] + let a = _mm_setr_epi8( + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + ); + #[rustfmt::skip] + let b = _mm_setr_epi8( + 4, 128_u8 as i8, 4, 3, + 24, 12, 6, 19, + 12, 5, 5, 10, + 4, 1, 8, 0, + ); + let expected = _mm_setr_epi8(5, 0, 5, 4, 9, 13, 7, 4, 13, 6, 6, 11, 5, 2, 9, 1); + let r = _mm_shuffle_epi8(a, b); + assert_eq_m128i(r, expected); +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn test_mm256_shuffle_epi8() { + #[rustfmt::skip] + let a = _mm256_setr_epi8( + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + ); + #[rustfmt::skip] + let b = _mm256_setr_epi8( + 4, 128u8 as i8, 4, 3, 24, 12, 6, 19, + 12, 5, 5, 10, 4, 1, 8, 0, + 4, 128u8 as i8, 4, 3, 24, 12, 6, 19, + 12, 5, 5, 10, 4, 1, 8, 0, + ); + #[rustfmt::skip] + let expected = _mm256_setr_epi8( + 5, 0, 5, 4, 9, 13, 7, 4, + 13, 6, 6, 11, 5, 2, 9, 1, + 21, 0, 21, 20, 25, 29, 23, 20, + 29, 22, 22, 27, 21, 18, 25, 17, + ); + let r = _mm256_shuffle_epi8(a, b); + assert_eq_m256i(r, expected); +} + +#[cfg(target_arch = "x86_64")] +#[target_feature(enable = "avx2")] +unsafe fn test_mm256_permute2x128_si256() { + let a = _mm256_setr_epi64x(100, 200, 500, 600); + let b = _mm256_setr_epi64x(300, 400, 700, 800); + let r = _mm256_permute2x128_si256::<0b00_01_00_11>(a, b); + let e = _mm256_setr_epi64x(700, 800, 500, 600); + assert_eq_m256i(r, e); +} + fn test_checked_mul() { let u: Option = u8::from_str_radix("1000", 10).ok(); assert_eq!(u, None); diff --git a/compiler/rustc_codegen_cranelift/patches/0001-portable-simd-Allow-internal-features.patch b/compiler/rustc_codegen_cranelift/patches/0001-portable-simd-Allow-internal-features.patch new file mode 100644 index 0000000000000..87252df1eabe3 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/0001-portable-simd-Allow-internal-features.patch @@ -0,0 +1,24 @@ +From fcf75306d88e533b83eaff3f8d0ab9f307e8a84d Mon Sep 17 00:00:00 2001 +From: bjorn3 <17426603+bjorn3@users.noreply.github.com> +Date: Wed, 9 Aug 2023 10:01:17 +0000 +Subject: [PATCH] Allow internal features + +--- + crates/core_simd/src/lib.rs | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/crates/core_simd/src/lib.rs b/crates/core_simd/src/lib.rs +index fde406b..b386116 100644 +--- a/crates/core_simd/src/lib.rs ++++ b/crates/core_simd/src/lib.rs +@@ -19,6 +19,7 @@ + #![warn(missing_docs, clippy::missing_inline_in_public_items)] // basically all items, really + #![deny(unsafe_op_in_unsafe_fn, clippy::undocumented_unsafe_blocks)] + #![unstable(feature = "portable_simd", issue = "86656")] ++#![allow(internal_features)] + //! Portable SIMD module. + + #[path = "mod.rs"] +-- +2.34.1 + diff --git a/compiler/rustc_codegen_cranelift/patches/0023-coretests-Ignore-failing-tests.patch b/compiler/rustc_codegen_cranelift/patches/0023-coretests-Ignore-failing-tests.patch index f2cb82751f082..385f5a8a2e039 100644 --- a/compiler/rustc_codegen_cranelift/patches/0023-coretests-Ignore-failing-tests.patch +++ b/compiler/rustc_codegen_cranelift/patches/0023-coretests-Ignore-failing-tests.patch @@ -10,42 +10,6 @@ Subject: [PATCH] [core] Ignore failing tests library/core/tests/time.rs | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) -diff --git a/array.rs b/array.rs -index 4bc44e9..8e3c7a4 100644 ---- a/array.rs -+++ b/array.rs -@@ -242,6 +242,7 @@ fn iterator_drops() { - assert_eq!(i.get(), 5); - } - -+/* - // This test does not work on targets without panic=unwind support. - // To work around this problem, test is marked is should_panic, so it will - // be automagically skipped on unsuitable targets, such as -@@ -283,6 +284,7 @@ fn array_default_impl_avoids_leaks_on_panic() { - assert_eq!(COUNTER.load(Relaxed), 0); - panic!("test succeeded") - } -+*/ - - #[test] - fn empty_array_is_always_default() { -@@ -304,6 +304,7 @@ fn array_map() { - assert_eq!(b, [1, 2, 3]); - } - -+/* - // See note on above test for why `should_panic` is used. - #[test] - #[should_panic(expected = "test succeeded")] -@@ -332,6 +333,7 @@ fn array_map_drop_safety() { - assert_eq!(DROPPED.load(Ordering::SeqCst), num_to_create); - panic!("test succeeded") - } -+*/ - - #[test] - fn cell_allows_array_cycle() { diff --git a/atomic.rs b/atomic.rs index 13b12db..96fe4b9 100644 --- a/atomic.rs diff --git a/compiler/rustc_codegen_cranelift/patches/0027-coretests-128bit-atomic-operations.patch b/compiler/rustc_codegen_cranelift/patches/0027-coretests-128bit-atomic-operations.patch index 1d5479beddee9..a650e10110bf2 100644 --- a/compiler/rustc_codegen_cranelift/patches/0027-coretests-128bit-atomic-operations.patch +++ b/compiler/rustc_codegen_cranelift/patches/0027-coretests-128bit-atomic-operations.patch @@ -10,6 +10,18 @@ Cranelift doesn't support them yet library/core/tests/atomic.rs | 4 --- 4 files changed, 4 insertions(+), 50 deletions(-) +diff --git a/lib.rs b/lib.rs +index 897a5e9..331f66f 100644 +--- a/lib.rs ++++ b/lib.rs +@@ -93,7 +93,6 @@ + #![feature(const_option)] + #![feature(const_option_ext)] + #![feature(const_result)] +-#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))] + #![feature(int_roundings)] + #![feature(slice_group_by)] + #![feature(split_array)] diff --git a/atomic.rs b/atomic.rs index b735957..ea728b6 100644 --- a/atomic.rs diff --git a/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch b/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch index 45f73f36b9317..646928893e96a 100644 --- a/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch +++ b/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch @@ -38,9 +38,9 @@ diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index d9de37e..8293fce 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs -@@ -2234,46 +2234,6 @@ atomic_int! { - "AtomicU64::new(0)", - u64 AtomicU64 ATOMIC_U64_INIT +@@ -2996,42 +2996,6 @@ atomic_int! { + 8, + u64 AtomicU64 } -#[cfg(target_has_atomic_load_store = "128")] -atomic_int! { @@ -53,14 +53,12 @@ index d9de37e..8293fce 100644 - unstable(feature = "integer_atomics", issue = "99069"), - unstable(feature = "integer_atomics", issue = "99069"), - rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), -- unstable(feature = "integer_atomics", issue = "99069"), - cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"), - "i128", - "#![feature(integer_atomics)]\n\n", - atomic_min, atomic_max, - 16, -- "AtomicI128::new(0)", -- i128 AtomicI128 ATOMIC_I128_INIT +- i128 AtomicI128 -} -#[cfg(target_has_atomic_load_store = "128")] -atomic_int! { @@ -73,16 +71,15 @@ index d9de37e..8293fce 100644 - unstable(feature = "integer_atomics", issue = "99069"), - unstable(feature = "integer_atomics", issue = "99069"), - rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), -- unstable(feature = "integer_atomics", issue = "99069"), - cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"), - "u128", - "#![feature(integer_atomics)]\n\n", - atomic_umin, atomic_umax, - 16, -- "AtomicU128::new(0)", -- u128 AtomicU128 ATOMIC_U128_INIT +- u128 AtomicU128 -} + #[cfg(target_has_atomic_load_store = "ptr")] macro_rules! atomic_int_ptr_sized { ( $($target_pointer_width:literal $align:literal)* ) => { $( -- diff --git a/compiler/rustc_codegen_cranelift/patches/coretests-lock.toml b/compiler/rustc_codegen_cranelift/patches/coretests-lock.toml new file mode 100644 index 0000000000000..af8f28a193bcd --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/coretests-lock.toml @@ -0,0 +1,35 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "coretests" +version = "0.0.0" +dependencies = [ + "rand", + "rand_xorshift", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] diff --git a/compiler/rustc_codegen_cranelift/patches/portable-simd-lock.toml b/compiler/rustc_codegen_cranelift/patches/portable-simd-lock.toml new file mode 100644 index 0000000000000..e7db1fd2c7fb6 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/portable-simd-lock.toml @@ -0,0 +1,304 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "core_simd" +version = "0.1.0" +dependencies = [ + "proptest", + "std_float", + "test_helpers", + "wasm-bindgen", + "wasm-bindgen-test", +] + +[[package]] +name = "js-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e6c80c1139113c28ee4670dc50cc42915228b51f56a9e407f0ec60f966646f" +dependencies = [ + "bitflags", + "byteorder", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "std_float" +version = "0.1.0" +dependencies = [ + "core_simd", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test_helpers" +version = "0.1.0" +dependencies = [ + "proptest", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "wasm-bindgen" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e636f3a428ff62b3742ebc3c70e254dfe12b8c2b469d688ea59cdd4abcf502" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18c1fad2f7c4958e7bcce014fa212f59a65d5e3721d0f77e6c0b27ede936ba3" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-sys" +version = "0.3.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/compiler/rustc_codegen_cranelift/patches/rand-lock.toml b/compiler/rustc_codegen_cranelift/patches/rand-lock.toml new file mode 100644 index 0000000000000..66c515731c5e3 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/rand-lock.toml @@ -0,0 +1,346 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "average" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843ec791d3f24503bbf72bbd5e49a3ab4dbb4bcd0a8ef6b0c908efa73caa27b1" +dependencies = [ + "easy-cast", + "float-ord", + "num-traits", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "easy-cast" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd102ee8c418348759919b83b81cdbdc933ffe29740b903df448b4bafaa348e" +dependencies = [ + "libm", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "log" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +dependencies = [ + "bincode", + "libc", + "log", + "rand_chacha", + "rand_core", + "rand_pcg", + "rayon", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.4.0" +dependencies = [ + "ppv-lite86", + "rand_core", + "serde", + "serde_json", +] + +[[package]] +name = "rand_core" +version = "0.7.0" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "rand_distr" +version = "0.5.0" +dependencies = [ + "average", + "num-traits", + "rand", + "rand_pcg", + "serde", + "special", +] + +[[package]] +name = "rand_pcg" +version = "0.4.0" +dependencies = [ + "bincode", + "rand_core", + "serde", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "special" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" +dependencies = [ + "libc", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/compiler/rustc_codegen_cranelift/patches/regex-lock.toml b/compiler/rustc_codegen_cranelift/patches/regex-lock.toml new file mode 100644 index 0000000000000..0e4a33b90ea1f --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/regex-lock.toml @@ -0,0 +1,439 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bzip2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "docopt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3f119846c823f9eafcf953a8f6ffb6ed69bf6240883261a7f13b634579a51f" +dependencies = [ + "lazy_static", + "regex 1.8.3", + "serde", + "strsim", +] + +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "libpcre-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ff3dd28ba96d6fe6752882f2f1b25ba8e1646448e79042442347cf3a92a6666" +dependencies = [ + "bzip2", + "libc", + "pkg-config", + "tar", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "onig" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5eeb268a4620c74ea5768c6d2ccd492d60a47a8754666b91a46bfc35cd4d1ba" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "68.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195ebddbb56740be48042ca117b8fb6e0d99fe392191a9362d82f5f69e510379" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "rand", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.2" +dependencies = [ + "aho-corasick", + "lazy_static", + "memchr", + "quickcheck", + "rand", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +dependencies = [ + "regex-syntax 0.7.2", +] + +[[package]] +name = "regex-benchmark" +version = "0.1.0" +dependencies = [ + "cc", + "cfg-if 0.1.10", + "docopt", + "lazy_static", + "libc", + "libpcre-sys", + "memmap", + "onig", + "pkg-config", + "regex 1.7.2", + "regex-syntax 0.6.29", + "serde", +] + +[[package]] +name = "regex-debug" +version = "0.1.0" +dependencies = [ + "docopt", + "regex 1.7.2", + "regex-syntax 0.6.29", + "serde", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" + +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + +[[package]] +name = "rure" +version = "0.2.2" +dependencies = [ + "libc", + "regex 1.7.2", +] + +[[package]] +name = "serde" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.163" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] diff --git a/compiler/rustc_codegen_cranelift/patches/stdlib-lock.toml b/compiler/rustc_codegen_cranelift/patches/stdlib-lock.toml new file mode 100644 index 0000000000000..fa175edcae60f --- /dev/null +++ b/compiler/rustc_codegen_cranelift/patches/stdlib-lock.toml @@ -0,0 +1,428 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "compiler_builtins", + "gimli", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "alloc" +version = "0.0.0" +dependencies = [ + "compiler_builtins", + "core", + "rand", + "rand_xorshift", +] + +[[package]] +name = "allocator-api2" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" + +[[package]] +name = "auxv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50430f9beb8effb02399fa81c76eeaa26b05e4f03b09285cad8d079c1af5a3d" +dependencies = [ + "byteorder", + "gcc", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "compiler_builtins" +version = "0.1.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c0f24437059853f0fa64afc51f338f93647a3de4cf3358ba1bb4171a199775" +dependencies = [ + "cc", + "rustc-std-workspace-core", +] + +[[package]] +name = "core" +version = "0.0.0" +dependencies = [ + "rand", + "rand_xorshift", +] + +[[package]] +name = "cupid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bad352a84b567cc38a5854e3aa8ee903cb8519a25d0b799b739bafffd1f91a1" +dependencies = [ + "gcc", + "rustc_version", +] + +[[package]] +name = "dlmalloc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" +dependencies = [ + "compiler_builtins", + "libc", + "rustc-std-workspace-core", +] + +[[package]] +name = "fortanix-sgx-abi" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57cafc2274c10fab234f176b25903ce17e690fca7597090d50880e047a0389c5" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "rustc-std-workspace-core", + "rustc-std-workspace-std", + "unicode-width", +] + +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "allocator-api2", + "compiler_builtins", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "libc" +version = "0.2.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +dependencies = [ + "rustc-std-workspace-core", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "compiler_builtins", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "compiler_builtins", + "memchr", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "panic_abort" +version = "0.0.0" +dependencies = [ + "alloc", + "cfg-if", + "compiler_builtins", + "core", + "libc", +] + +[[package]] +name = "panic_unwind" +version = "0.0.0" +dependencies = [ + "alloc", + "cfg-if", + "compiler_builtins", + "core", + "libc", + "unwind", +] + +[[package]] +name = "proc_macro" +version = "0.0.0" +dependencies = [ + "core", + "std", +] + +[[package]] +name = "profiler_builtins" +version = "0.0.0" +dependencies = [ + "cc", + "compiler_builtins", + "core", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "rustc-std-workspace-alloc" +version = "1.99.0" +dependencies = [ + "alloc", +] + +[[package]] +name = "rustc-std-workspace-core" +version = "1.99.0" +dependencies = [ + "core", +] + +[[package]] +name = "rustc-std-workspace-std" +version = "1.99.0" +dependencies = [ + "std", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "std" +version = "0.0.0" +dependencies = [ + "addr2line", + "alloc", + "cfg-if", + "compiler_builtins", + "core", + "dlmalloc", + "fortanix-sgx-abi", + "hashbrown", + "hermit-abi", + "libc", + "miniz_oxide", + "object", + "panic_abort", + "panic_unwind", + "profiler_builtins", + "rand", + "rand_xorshift", + "rustc-demangle", + "std_detect", + "unwind", + "wasi", +] + +[[package]] +name = "std_detect" +version = "0.1.5" +dependencies = [ + "auxv", + "cfg-if", + "compiler_builtins", + "cupid", + "libc", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] + +[[package]] +name = "sysroot" +version = "0.0.0" +dependencies = [ + "proc_macro", + "std", + "test", +] + +[[package]] +name = "test" +version = "0.0.0" +dependencies = [ + "core", + "getopts", + "panic_abort", + "panic_unwind", + "std", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", + "rustc-std-workspace-std", +] + +[[package]] +name = "unwind" +version = "0.0.0" +dependencies = [ + "cc", + "cfg-if", + "compiler_builtins", + "core", + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-alloc", + "rustc-std-workspace-core", +] diff --git a/compiler/rustc_codegen_cranelift/rust-toolchain b/compiler/rustc_codegen_cranelift/rust-toolchain index 59ad80c3207dc..5689bdee64d26 100644 --- a/compiler/rustc_codegen_cranelift/rust-toolchain +++ b/compiler/rustc_codegen_cranelift/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-04-29" -components = ["rust-src", "rustc-dev", "llvm-tools-preview"] +channel = "nightly-2023-08-08" +components = ["rust-src", "rustc-dev", "llvm-tools"] diff --git a/compiler/rustc_codegen_cranelift/scripts/cargo-clif.rs b/compiler/rustc_codegen_cranelift/scripts/cargo-clif.rs index e2db7d03a9d38..1e14f41d4a2c2 100644 --- a/compiler/rustc_codegen_cranelift/scripts/cargo-clif.rs +++ b/compiler/rustc_codegen_cranelift/scripts/cargo-clif.rs @@ -3,6 +3,8 @@ use std::env; use std::os::unix::process::CommandExt; use std::process::Command; +include!("../build_system/shared_utils.rs"); + fn main() { let current_exe = env::current_exe().unwrap(); let mut sysroot = current_exe.parent().unwrap(); @@ -10,35 +12,41 @@ fn main() { sysroot = sysroot.parent().unwrap(); } - let mut rustflags = String::new(); - rustflags.push_str(" -Cpanic=abort -Zpanic-abort-tests -Zcodegen-backend="); - rustflags.push_str( - sysroot - .join(if cfg!(windows) { "bin" } else { "lib" }) - .join( - env::consts::DLL_PREFIX.to_string() - + "rustc_codegen_cranelift" - + env::consts::DLL_SUFFIX, - ) - .to_str() - .unwrap(), - ); - rustflags.push_str(" --sysroot "); - rustflags.push_str(sysroot.to_str().unwrap()); - env::set_var("RUSTFLAGS", env::var("RUSTFLAGS").unwrap_or(String::new()) + &rustflags); - env::set_var("RUSTDOCFLAGS", env::var("RUSTDOCFLAGS").unwrap_or(String::new()) + &rustflags); + let mut rustflags = vec!["-Cpanic=abort".to_owned(), "-Zpanic-abort-tests".to_owned()]; + if let Some(name) = option_env!("BUILTIN_BACKEND") { + rustflags.push(format!("-Zcodegen-backend={name}")); + } else { + let dylib = sysroot.join(if cfg!(windows) { "bin" } else { "lib" }).join( + env::consts::DLL_PREFIX.to_string() + + "rustc_codegen_cranelift" + + env::consts::DLL_SUFFIX, + ); + rustflags.push(format!("-Zcodegen-backend={}", dylib.to_str().unwrap())); + } + rustflags.push("--sysroot".to_owned()); + rustflags.push(sysroot.to_str().unwrap().to_owned()); + + let cargo = if let Some(cargo) = option_env!("CARGO") { + cargo + } else { + // Ensure that the right toolchain is used + env::set_var("RUSTUP_TOOLCHAIN", option_env!("TOOLCHAIN_NAME").expect("TOOLCHAIN_NAME")); + "cargo" + }; - // Ensure that the right toolchain is used - env::set_var("RUSTUP_TOOLCHAIN", env!("TOOLCHAIN_NAME")); + let mut args = env::args().skip(1).collect::>(); + if args.get(0).map(|arg| &**arg) == Some("clif") { + // Avoid infinite recursion when invoking `cargo-clif` as cargo subcommand using + // `cargo clif`. + args.remove(0); + } - let args: Vec<_> = match env::args().nth(1).as_deref() { + let args: Vec<_> = match args.get(0).map(|arg| &**arg) { Some("jit") => { - env::set_var( - "RUSTFLAGS", - env::var("RUSTFLAGS").unwrap_or(String::new()) + " -Cprefer-dynamic", - ); + rustflags.push("-Cprefer-dynamic".to_owned()); + args.remove(0); IntoIterator::into_iter(["rustc".to_string()]) - .chain(env::args().skip(2)) + .chain(args) .chain([ "--".to_string(), "-Zunstable-options".to_string(), @@ -47,12 +55,10 @@ fn main() { .collect() } Some("lazy-jit") => { - env::set_var( - "RUSTFLAGS", - env::var("RUSTFLAGS").unwrap_or(String::new()) + " -Cprefer-dynamic", - ); + rustflags.push("-Cprefer-dynamic".to_owned()); + args.remove(0); IntoIterator::into_iter(["rustc".to_string()]) - .chain(env::args().skip(2)) + .chain(args) .chain([ "--".to_string(), "-Zunstable-options".to_string(), @@ -60,14 +66,31 @@ fn main() { ]) .collect() } - _ => env::args().skip(1).collect(), + _ => args, }; + let mut cmd = Command::new(cargo); + cmd.args(args); + rustflags_to_cmd_env( + &mut cmd, + "RUSTFLAGS", + &rustflags_from_env("RUSTFLAGS") + .into_iter() + .chain(rustflags.iter().map(|flag| flag.clone())) + .collect::>(), + ); + rustflags_to_cmd_env( + &mut cmd, + "RUSTDOCFLAGS", + &rustflags_from_env("RUSTDOCFLAGS") + .into_iter() + .chain(rustflags.iter().map(|flag| flag.clone())) + .collect::>(), + ); + #[cfg(unix)] - panic!("Failed to spawn cargo: {}", Command::new("cargo").args(args).exec()); + panic!("Failed to spawn cargo: {}", cmd.exec()); #[cfg(not(unix))] - std::process::exit( - Command::new("cargo").args(args).spawn().unwrap().wait().unwrap().code().unwrap_or(1), - ); + std::process::exit(cmd.spawn().unwrap().wait().unwrap().code().unwrap_or(1)); } diff --git a/compiler/rustc_codegen_cranelift/scripts/rustc-clif.rs b/compiler/rustc_codegen_cranelift/scripts/rustc-clif.rs index ab496a4a6844e..33d51bdddeaf9 100644 --- a/compiler/rustc_codegen_cranelift/scripts/rustc-clif.rs +++ b/compiler/rustc_codegen_cranelift/scripts/rustc-clif.rs @@ -19,23 +19,34 @@ fn main() { let mut args = vec![]; args.push(OsString::from("-Cpanic=abort")); args.push(OsString::from("-Zpanic-abort-tests")); - let mut codegen_backend_arg = OsString::from("-Zcodegen-backend="); - codegen_backend_arg.push(cg_clif_dylib_path); - args.push(codegen_backend_arg); - if !passed_args.contains(&OsString::from("--sysroot")) { + if let Some(name) = option_env!("BUILTIN_BACKEND") { + args.push(OsString::from(format!("-Zcodegen-backend={name}"))) + } else { + let mut codegen_backend_arg = OsString::from("-Zcodegen-backend="); + codegen_backend_arg.push(cg_clif_dylib_path); + args.push(codegen_backend_arg); + } + if !passed_args.iter().any(|arg| { + arg == "--sysroot" || arg.to_str().map(|s| s.starts_with("--sysroot=")) == Some(true) + }) { args.push(OsString::from("--sysroot")); args.push(OsString::from(sysroot.to_str().unwrap())); } args.extend(passed_args); - // Ensure that the right toolchain is used - env::set_var("RUSTUP_TOOLCHAIN", env!("TOOLCHAIN_NAME")); + let rustc = if let Some(rustc) = option_env!("RUSTC") { + rustc + } else { + // Ensure that the right toolchain is used + env::set_var("RUSTUP_TOOLCHAIN", option_env!("TOOLCHAIN_NAME").expect("TOOLCHAIN_NAME")); + "rustc" + }; #[cfg(unix)] - panic!("Failed to spawn rustc: {}", Command::new("rustc").args(args).exec()); + panic!("Failed to spawn rustc: {}", Command::new(rustc).args(args).exec()); #[cfg(not(unix))] std::process::exit( - Command::new("rustc").args(args).spawn().unwrap().wait().unwrap().code().unwrap_or(1), + Command::new(rustc).args(args).spawn().unwrap().wait().unwrap().code().unwrap_or(1), ); } diff --git a/compiler/rustc_codegen_cranelift/scripts/rustdoc-clif.rs b/compiler/rustc_codegen_cranelift/scripts/rustdoc-clif.rs index 545844446c508..10582cc7bb320 100644 --- a/compiler/rustc_codegen_cranelift/scripts/rustdoc-clif.rs +++ b/compiler/rustc_codegen_cranelift/scripts/rustdoc-clif.rs @@ -19,23 +19,34 @@ fn main() { let mut args = vec![]; args.push(OsString::from("-Cpanic=abort")); args.push(OsString::from("-Zpanic-abort-tests")); - let mut codegen_backend_arg = OsString::from("-Zcodegen-backend="); - codegen_backend_arg.push(cg_clif_dylib_path); - args.push(codegen_backend_arg); - if !passed_args.contains(&OsString::from("--sysroot")) { + if let Some(name) = option_env!("BUILTIN_BACKEND") { + args.push(OsString::from(format!("-Zcodegen-backend={name}"))) + } else { + let mut codegen_backend_arg = OsString::from("-Zcodegen-backend="); + codegen_backend_arg.push(cg_clif_dylib_path); + args.push(codegen_backend_arg); + } + if !passed_args.iter().any(|arg| { + arg == "--sysroot" || arg.to_str().map(|s| s.starts_with("--sysroot=")) == Some(true) + }) { args.push(OsString::from("--sysroot")); args.push(OsString::from(sysroot.to_str().unwrap())); } args.extend(passed_args); - // Ensure that the right toolchain is used - env::set_var("RUSTUP_TOOLCHAIN", env!("TOOLCHAIN_NAME")); + let rustdoc = if let Some(rustdoc) = option_env!("RUSTDOC") { + rustdoc + } else { + // Ensure that the right toolchain is used + env::set_var("RUSTUP_TOOLCHAIN", option_env!("TOOLCHAIN_NAME").expect("TOOLCHAIN_NAME")); + "rustdoc" + }; #[cfg(unix)] - panic!("Failed to spawn rustdoc: {}", Command::new("rustdoc").args(args).exec()); + panic!("Failed to spawn rustdoc: {}", Command::new(rustdoc).args(args).exec()); #[cfg(not(unix))] std::process::exit( - Command::new("rustdoc").args(args).spawn().unwrap().wait().unwrap().code().unwrap_or(1), + Command::new(rustdoc).args(args).spawn().unwrap().wait().unwrap().code().unwrap_or(1), ); } diff --git a/compiler/rustc_codegen_cranelift/scripts/rustup.sh b/compiler/rustc_codegen_cranelift/scripts/rustup.sh index 3cbeb6375de10..e62788f2e507d 100755 --- a/compiler/rustc_codegen_cranelift/scripts/rustup.sh +++ b/compiler/rustc_codegen_cranelift/scripts/rustup.sh @@ -32,12 +32,10 @@ case $1 in ./clean_all.sh - ./y.rs prepare - - (cd download/sysroot && cargo update && cargo fetch && cp Cargo.lock ../../build_sysroot/) + ./y.sh prepare ;; "commit") - git add rust-toolchain build_sysroot/Cargo.lock + git add rust-toolchain git commit -m "Rustup to $(rustc -V)" ;; "push") diff --git a/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh b/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh index abb09775d2135..e6bbac647e5a2 100644 --- a/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh +++ b/compiler/rustc_codegen_cranelift/scripts/setup_rust_fork.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -./y.rs build --no-unstable-features +./y.sh build --no-unstable-features echo "[SETUP] Rust fork" git clone https://github.com/rust-lang/rust.git || true @@ -10,7 +10,8 @@ git fetch git checkout -- . git checkout "$(rustc -V | cut -d' ' -f3 | tr -d '(')" -git -c user.name=Dummy -c user.email=dummy@example.com am ../patches/*-stdlib-*.patch +git -c user.name=Dummy -c user.email=dummy@example.com -c commit.gpgSign=false \ + am ../patches/*-stdlib-*.patch git apply - </dev/null 2>&1 || cargo install ripgrep -# FIXME add needs-asm-support to all tests in tests/ui/asm rm -r tests/ui/{unsized-locals/,lto/,linkage*} || true -for test in $(rg --files-with-matches "lto|// needs-asm-support|// needs-unwind" tests/{codegen-units,ui,incremental}); do +for test in $(rg --files-with-matches "lto|// needs-asm-support" tests/{codegen-units,ui,incremental}); do rm $test done +for test in tests/run-make/**/Makefile; do + if rg "# needs-asm-support" $test >/dev/null; then + rm -r $(dirname $test) + fi +done + for test in $(rg -i --files-with-matches "//(\[\w+\])?~[^\|]*\s*ERR|// error-pattern:|// build-fail|// run-fail|-Cllvm-args" tests/ui); do rm $test done @@ -27,31 +32,25 @@ rm tests/ui/parser/unclosed-delimiter-in-dep.rs # submodule contains //~ERROR # missing features # ================ +rm -r tests/run-make/comment-section # cg_clif doesn't yet write the .comment section + # requires stack unwinding -# FIXME add needs-unwind to these tests -rm tests/incremental/change_crate_dep_kind.rs -rm tests/incremental/issue-80691-bad-eval-cache.rs # -Cpanic=abort causes abort instead of exit(101) -rm -r tests/run-make/c-unwind-abi-catch-lib-panic -rm -r tests/run-make/c-unwind-abi-catch-panic -rm -r tests/run-make/debug-assertions -rm -r tests/run-make/foreign-double-unwind -rm -r tests/run-make/foreign-exceptions -rm -r tests/run-make/foreign-rust-exceptions -rm -r tests/run-make/libtest-json -rm -r tests/run-make/static-unwinding - -# requires compiling with -Cpanic=unwind -rm -r tests/ui/macros/rfc-2011-nicer-assert-messages/ -rm -r tests/run-make/test-benches -rm tests/ui/test-attrs/test-type.rs -rm -r tests/run-make/const_fn_mir -rm -r tests/run-make/intrinsic-unreachable +# FIXME add needs-unwind to this test +rm -r tests/run-make/libtest-junit + +# extra warning about -Cpanic=abort for proc macros +rm tests/ui/proc-macro/crt-static.rs +rm tests/ui/proc-macro/proc-macro-deprecated-attr.rs +rm tests/ui/proc-macro/quote-debug.rs +rm tests/ui/proc-macro/no-missing-docs.rs +rm tests/ui/rust-2018/proc-macro-crate-in-paths.rs +rm tests/ui/proc-macro/allowed-signatures.rs # vendor intrinsics rm tests/ui/sse2.rs # cpuid not supported, so sse2 not detected -rm tests/ui/intrinsics/const-eval-select-x86_64.rs # requires x86_64 vendor intrinsics rm tests/ui/simd/array-type.rs # "Index argument for `simd_insert` is not a constant" -rm tests/ui/simd/intrinsic/float-math-pass.rs # simd_fcos unimplemented +rm tests/ui/simd/intrinsic/generic-bswap-byte.rs # simd_bswap not yet implemented +rm tests/ui/simd/intrinsic/generic-arithmetic-pass.rs # many missing simd intrinsics # exotic linkages rm tests/ui/issues/issue-33992.rs # unsupported linkages @@ -85,6 +84,7 @@ rm -r tests/run-make/issue-64153 rm -r tests/run-make/codegen-options-parsing rm -r tests/run-make/lto-* rm -r tests/run-make/reproducible-build-2 +rm -r tests/run-make/issue-109934-lto-debuginfo # optimization tests # ================== @@ -102,8 +102,11 @@ rm -r tests/run-make/sepcomp-inlining # same rm -r tests/run-make/sepcomp-separate # same rm -r tests/run-make/sepcomp-cci-copies # same rm -r tests/run-make/volatile-intrinsics # same +rm -r tests/run-make/llvm-ident # same +rm -r tests/run-make/no-builtins-attribute # same rm tests/ui/abi/stack-protector.rs # requires stack protector support rm -r tests/run-make/emit-stack-sizes # requires support for -Z emit-stack-sizes +rm -r tests/run-make/optimization-remarks-dir # remarks are LLVM specific # giving different but possibly correct results # ============================================= @@ -120,13 +123,11 @@ rm tests/ui/lint/lint-const-item-mutation.rs # same rm tests/ui/pattern/usefulness/doc-hidden-non-exhaustive.rs # same rm tests/ui/suggestions/derive-trait-for-method-call.rs # same rm tests/ui/typeck/issue-46112.rs # same - -rm tests/ui/proc-macro/crt-static.rs # extra warning about -Cpanic=abort for proc macros -rm tests/ui/proc-macro/proc-macro-deprecated-attr.rs # same -rm tests/ui/proc-macro/quote-debug.rs # same -rm tests/ui/proc-macro/no-missing-docs.rs # same -rm tests/ui/rust-2018/proc-macro-crate-in-paths.rs # same -rm tests/ui/proc-macro/allowed-signatures.rs # same +rm tests/ui/consts/const_cmp_type_id.rs # same +rm tests/ui/consts/issue-73976-monomorphic.rs # same +rm tests/ui/rfcs/rfc-3348-c-string-literals/non-ascii.rs # same +rm tests/ui/consts/const-eval/nonnull_as_ref_ub.rs # same +rm tests/ui/consts/issue-94675.rs # same # rustdoc-clif passes extra args, suppressing the help message when no args are passed rm -r tests/run-make/issue-88756-default-output @@ -142,15 +143,17 @@ rm -r tests/ui/consts/missing_span_in_backtrace.rs # expects sysroot source to b rm tests/incremental/spike-neg1.rs # errors out for some reason rm tests/incremental/spike-neg2.rs # same -rm tests/ui/simd/intrinsic/generic-reduction-pass.rs # simd_reduce_add_unordered doesn't accept an accumulator for integer vectors - -rm tests/ui/simd/simd-bitmask.rs # crash +rm tests/ui/simd/simd-bitmask.rs # simd_bitmask doesn't implement [u*; N] return type rm -r tests/run-make/issue-51671 # wrong filename given in case of --emit=obj rm -r tests/run-make/issue-30063 # same rm -r tests/run-make/multiple-emits # same rm -r tests/run-make/output-type-permutations # same rm -r tests/run-make/used # same +rm -r tests/run-make/no-alloc-shim +rm -r tests/run-make/emit-to-stdout + +rm -r tests/run-make/extern-fn-explicit-align # argument alignment not yet supported # bugs in the test suite # ====================== @@ -171,7 +174,7 @@ index ea06b620c4c..b969d0009c6 100644 @@ -9,7 +9,7 @@ RUSTC_ORIGINAL := \$(RUSTC) BARE_RUSTC := \$(HOST_RPATH_ENV) '\$(RUSTC)' BARE_RUSTDOC := \$(HOST_RPATH_ENV) '\$(RUSTDOC)' - RUSTC := \$(BARE_RUSTC) --out-dir \$(TMPDIR) -L \$(TMPDIR) \$(RUSTFLAGS) + RUSTC := \$(BARE_RUSTC) --out-dir \$(TMPDIR) -L \$(TMPDIR) \$(RUSTFLAGS) -Ainternal_features -RUSTDOC := \$(BARE_RUSTDOC) -L \$(TARGET_RPATH_DIR) +RUSTDOC := \$(BARE_RUSTDOC) ifdef RUSTC_LINKER diff --git a/compiler/rustc_codegen_cranelift/src/abi/comments.rs b/compiler/rustc_codegen_cranelift/src/abi/comments.rs index 364503fd3639a..ade6968de2bb6 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/comments.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/comments.rs @@ -80,14 +80,7 @@ pub(super) fn add_local_place_comments<'tcx>( return; } let TyAndLayout { ty, layout } = place.layout(); - let rustc_target::abi::LayoutS { - size, - align, - abi: _, - variants: _, - fields: _, - largest_niche: _, - } = layout.0.0; + let rustc_target::abi::LayoutS { size, align, .. } = layout.0.0; let (kind, extra) = place.debug_comment(); diff --git a/compiler/rustc_codegen_cranelift/src/abi/mod.rs b/compiler/rustc_codegen_cranelift/src/abi/mod.rs index 84e09cf0abe4f..b7f56a2986cfc 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/mod.rs @@ -48,7 +48,9 @@ pub(crate) fn conv_to_call_conv(sess: &Session, c: Conv, default_call_conv: Call default_call_conv } - Conv::X86Intr => sess.fatal("x86-interrupt call conv not yet implemented"), + Conv::X86Intr | Conv::RiscvInterrupt { .. } => { + sess.fatal(format!("interrupt call conv {c:?} not yet implemented")) + } Conv::ArmAapcs => sess.fatal("aapcs call conv not yet implemented"), Conv::CCmseNonSecureCall => { @@ -70,7 +72,7 @@ pub(crate) fn get_function_sig<'tcx>( default_call_conv: CallConv, inst: Instance<'tcx>, ) -> Signature { - assert!(!inst.substs.has_infer()); + assert!(!inst.args.has_infer()); clif_sig_from_fn_abi( tcx, default_call_conv, @@ -377,16 +379,16 @@ pub(crate) fn codegen_terminator_call<'tcx>( let ret_place = codegen_place(fx, destination); // Handle special calls like intrinsics and empty drop glue. - let instance = if let ty::FnDef(def_id, substs) = *func.layout().ty.kind() { + let instance = if let ty::FnDef(def_id, fn_args) = *func.layout().ty.kind() { let instance = - ty::Instance::expect_resolve(fx.tcx, ty::ParamEnv::reveal_all(), def_id, substs) + ty::Instance::expect_resolve(fx.tcx, ty::ParamEnv::reveal_all(), def_id, fn_args) .polymorphize(fx.tcx); if fx.tcx.symbol_name(instance).name.starts_with("llvm.") { crate::intrinsics::codegen_llvm_intrinsic_call( fx, &fx.tcx.symbol_name(instance).name, - substs, + fn_args, args, ret_place, target, @@ -445,9 +447,14 @@ pub(crate) fn codegen_terminator_call<'tcx>( // Unpack arguments tuple for closures let mut args = if fn_sig.abi() == Abi::RustCall { - assert_eq!(args.len(), 2, "rust-call abi requires two arguments"); - let self_arg = codegen_call_argument_operand(fx, &args[0]); - let pack_arg = codegen_call_argument_operand(fx, &args[1]); + let (self_arg, pack_arg) = match args { + [pack_arg] => (None, codegen_call_argument_operand(fx, pack_arg)), + [self_arg, pack_arg] => ( + Some(codegen_call_argument_operand(fx, self_arg)), + codegen_call_argument_operand(fx, pack_arg), + ), + _ => panic!("rust-call abi requires one or two arguments"), + }; let tupled_arguments = match pack_arg.value.layout().ty.kind() { ty::Tuple(ref tupled_arguments) => tupled_arguments, @@ -455,7 +462,7 @@ pub(crate) fn codegen_terminator_call<'tcx>( }; let mut args = Vec::with_capacity(1 + tupled_arguments.len()); - args.push(self_arg); + args.extend(self_arg); for i in 0..tupled_arguments.len() { args.push(CallArgument { value: pack_arg.value.value_field(fx, FieldIdx::new(i)), @@ -611,7 +618,7 @@ pub(crate) fn codegen_drop<'tcx>( // `Instance::resolve_drop_in_place`? let virtual_drop = Instance { def: ty::InstanceDef::Virtual(drop_instance.def_id(), 0), - substs: drop_instance.substs, + args: drop_instance.args, }; let fn_abi = RevealAllLayoutCx(fx.tcx).fn_abi_of_instance(virtual_drop, ty::List::empty()); @@ -648,7 +655,7 @@ pub(crate) fn codegen_drop<'tcx>( let virtual_drop = Instance { def: ty::InstanceDef::Virtual(drop_instance.def_id(), 0), - substs: drop_instance.substs, + args: drop_instance.args, }; let fn_abi = RevealAllLayoutCx(fx.tcx).fn_abi_of_instance(virtual_drop, ty::List::empty()); @@ -665,7 +672,8 @@ pub(crate) fn codegen_drop<'tcx>( let arg_value = drop_place.place_ref( fx, - fx.layout_of(fx.tcx.mk_ref( + fx.layout_of(Ty::new_ref( + fx.tcx, fx.tcx.lifetimes.re_erased, TypeAndMut { ty, mutbl: crate::rustc_hir::Mutability::Mut }, )), diff --git a/compiler/rustc_codegen_cranelift/src/allocator.rs b/compiler/rustc_codegen_cranelift/src/allocator.rs index 2c246ceb37d54..4e4c595de8257 100644 --- a/compiler/rustc_codegen_cranelift/src/allocator.rs +++ b/compiler/rustc_codegen_cranelift/src/allocator.rs @@ -3,10 +3,12 @@ use crate::prelude::*; -use rustc_ast::expand::allocator::{AllocatorKind, AllocatorTy, ALLOCATOR_METHODS}; +use rustc_ast::expand::allocator::{ + alloc_error_handler_name, default_fn_name, global_fn_name, AllocatorKind, AllocatorTy, + ALLOCATOR_METHODS, NO_ALLOC_SHIM_IS_UNSTABLE, +}; use rustc_codegen_ssa::base::allocator_kind_for_codegen; use rustc_session::config::OomStrategy; -use rustc_span::symbol::sym; /// Returns whether an allocator shim was created pub(crate) fn codegen( @@ -34,41 +36,43 @@ fn codegen_inner( ) { let usize_ty = module.target_config().pointer_type(); - for method in ALLOCATOR_METHODS { - let mut arg_tys = Vec::with_capacity(method.inputs.len()); - for ty in method.inputs.iter() { - match *ty { - AllocatorTy::Layout => { - arg_tys.push(usize_ty); // size - arg_tys.push(usize_ty); // align - } - AllocatorTy::Ptr => arg_tys.push(usize_ty), - AllocatorTy::Usize => arg_tys.push(usize_ty), + if kind == AllocatorKind::Default { + for method in ALLOCATOR_METHODS { + let mut arg_tys = Vec::with_capacity(method.inputs.len()); + for input in method.inputs.iter() { + match input.ty { + AllocatorTy::Layout => { + arg_tys.push(usize_ty); // size + arg_tys.push(usize_ty); // align + } + AllocatorTy::Ptr => arg_tys.push(usize_ty), + AllocatorTy::Usize => arg_tys.push(usize_ty), - AllocatorTy::ResultPtr | AllocatorTy::Unit => panic!("invalid allocator arg"), + AllocatorTy::ResultPtr | AllocatorTy::Unit => panic!("invalid allocator arg"), + } } - } - let output = match method.output { - AllocatorTy::ResultPtr => Some(usize_ty), - AllocatorTy::Unit => None, + let output = match method.output { + AllocatorTy::ResultPtr => Some(usize_ty), + AllocatorTy::Unit => None, - AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { - panic!("invalid allocator output") - } - }; + AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { + panic!("invalid allocator output") + } + }; - let sig = Signature { - call_conv: module.target_config().default_call_conv, - params: arg_tys.iter().cloned().map(AbiParam::new).collect(), - returns: output.into_iter().map(AbiParam::new).collect(), - }; - crate::common::create_wrapper_function( - module, - unwind_context, - sig, - &format!("__rust_{}", method.name), - &kind.fn_name(method.name), - ); + let sig = Signature { + call_conv: module.target_config().default_call_conv, + params: arg_tys.iter().cloned().map(AbiParam::new).collect(), + returns: output.into_iter().map(AbiParam::new).collect(), + }; + crate::common::create_wrapper_function( + module, + unwind_context, + sig, + &global_fn_name(method.name), + &default_fn_name(method.name), + ); + } } let sig = Signature { @@ -81,13 +85,20 @@ fn codegen_inner( unwind_context, sig, "__rust_alloc_error_handler", - &alloc_error_handler_kind.fn_name(sym::oom), + &alloc_error_handler_name(alloc_error_handler_kind), ); let data_id = module.declare_data(OomStrategy::SYMBOL, Linkage::Export, false, false).unwrap(); - let mut data_ctx = DataContext::new(); - data_ctx.set_align(1); + let mut data = DataDescription::new(); + data.set_align(1); let val = oom_strategy.should_panic(); - data_ctx.define(Box::new([val])); - module.define_data(data_id, &data_ctx).unwrap(); + data.define(Box::new([val])); + module.define_data(data_id, &data).unwrap(); + + let data_id = + module.declare_data(NO_ALLOC_SHIM_IS_UNSTABLE, Linkage::Export, false, false).unwrap(); + let mut data = DataDescription::new(); + data.set_align(1); + data.define(Box::new([0])); + module.define_data(data_id, &data).unwrap(); } diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 9c6a0fae327cf..522dd7189fe63 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -2,11 +2,13 @@ use rustc_ast::InlineAsmOptions; use rustc_index::IndexVec; -use rustc_middle::ty::adjustment::PointerCast; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::FnAbiOf; use rustc_middle::ty::print::with_no_trimmed_paths; use cranelift_codegen::ir::UserFuncName; +use cranelift_codegen::CodegenError; +use cranelift_module::ModuleError; use crate::constant::ConstantCx; use crate::debuginfo::FunctionDebugContext; @@ -28,7 +30,7 @@ pub(crate) fn codegen_fn<'tcx>( module: &mut dyn Module, instance: Instance<'tcx>, ) -> CodegenedFunction { - debug_assert!(!instance.substs.has_infer()); + debug_assert!(!instance.args.has_infer()); let symbol_name = tcx.symbol_name(instance).name.to_string(); let _timer = tcx.prof.generic_activity_with_arg("codegen fn", &*symbol_name); @@ -156,6 +158,7 @@ pub(crate) fn compile_fn( write!(clif, " {}", isa_flag).unwrap(); } writeln!(clif, "\n").unwrap(); + writeln!(clif, "; symbol {}", codegened_func.symbol_name).unwrap(); crate::PrintOnPanic(move || { let mut clif = clif.clone(); ::cranelift_codegen::write::decorate_function( @@ -171,7 +174,21 @@ pub(crate) fn compile_fn( // Define function cx.profiler.generic_activity("define function").run(|| { context.want_disasm = cx.should_write_ir; - module.define_function(codegened_func.func_id, context).unwrap(); + match module.define_function(codegened_func.func_id, context) { + Ok(()) => {} + Err(ModuleError::Compilation(CodegenError::ImplLimitExceeded)) => { + let handler = rustc_session::EarlyErrorHandler::new( + rustc_session::config::ErrorOutputType::default(), + ); + handler.early_error(format!( + "backend implementation limit exceeded while compiling {name}", + name = codegened_func.symbol_name + )); + } + Err(err) => { + panic!("Error while defining {name}: {err:?}", name = codegened_func.symbol_name); + } + } }); if cx.should_write_ir { @@ -355,7 +372,7 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { codegen_panic_inner( fx, - rustc_hir::LangItem::PanicBoundsCheck, + rustc_hir::LangItem::PanicMisalignedPointerDereference, &[required, found, location], source_info.span, ); @@ -420,7 +437,7 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { target, fn_span, unwind: _, - from_hir_call: _, + call_source: _, } => { fx.tcx.prof.generic_activity("codegen call").run(|| { crate::abi::codegen_terminator_call( @@ -473,7 +490,7 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { | TerminatorKind::GeneratorDrop => { bug!("shouldn't exist at codegen {:?}", bb_data.terminator()); } - TerminatorKind::Drop { place, target, unwind: _ } => { + TerminatorKind::Drop { place, target, unwind: _, replace: _ } => { let drop_place = codegen_place(fx, *place); crate::abi::codegen_drop(fx, source_info, drop_place); @@ -570,20 +587,20 @@ fn codegen_stmt<'tcx>( lval.write_cvalue(fx, res); } Rvalue::Cast( - CastKind::Pointer(PointerCast::ReifyFnPointer), + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer), ref operand, to_ty, ) => { let from_ty = fx.monomorphize(operand.ty(&fx.mir.local_decls, fx.tcx)); let to_layout = fx.layout_of(fx.monomorphize(to_ty)); match *from_ty.kind() { - ty::FnDef(def_id, substs) => { + ty::FnDef(def_id, args) => { let func_ref = fx.get_function_ref( Instance::resolve_for_fn_ptr( fx.tcx, ParamEnv::reveal_all(), def_id, - substs, + args, ) .unwrap() .polymorphize(fx.tcx), @@ -595,17 +612,17 @@ fn codegen_stmt<'tcx>( } } Rvalue::Cast( - CastKind::Pointer(PointerCast::UnsafeFnPointer), + CastKind::PointerCoercion(PointerCoercion::UnsafeFnPointer), ref operand, to_ty, ) | Rvalue::Cast( - CastKind::Pointer(PointerCast::MutToConstPointer), + CastKind::PointerCoercion(PointerCoercion::MutToConstPointer), ref operand, to_ty, ) | Rvalue::Cast( - CastKind::Pointer(PointerCast::ArrayToPointer), + CastKind::PointerCoercion(PointerCoercion::ArrayToPointer), ref operand, to_ty, ) => { @@ -661,17 +678,17 @@ fn codegen_stmt<'tcx>( } } Rvalue::Cast( - CastKind::Pointer(PointerCast::ClosureFnPointer(_)), + CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)), ref operand, _to_ty, ) => { let operand = codegen_operand(fx, operand); match *operand.layout().ty.kind() { - ty::Closure(def_id, substs) => { + ty::Closure(def_id, args) => { let instance = Instance::resolve_closure( fx.tcx, def_id, - substs, + args, ty::ClosureKind::FnOnce, ) .expect("failed to normalize and resolve closure during codegen") @@ -683,7 +700,11 @@ fn codegen_stmt<'tcx>( _ => bug!("{} cannot be cast to a fn ptr", operand.layout().ty), } } - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), ref operand, _to_ty) => { + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::Unsize), + ref operand, + _to_ty, + ) => { let operand = codegen_operand(fx, operand); crate::unsize::coerce_unsized_into(fx, operand, lval); } @@ -705,7 +726,6 @@ fn codegen_stmt<'tcx>( let times = fx .monomorphize(times) .eval(fx.tcx, ParamEnv::reveal_all()) - .kind() .try_to_bits(fx.tcx.data_layout.pointer_size) .unwrap(); if operand.layout().size.bytes() == 0 { @@ -746,7 +766,7 @@ fn codegen_stmt<'tcx>( } Rvalue::ShallowInitBox(ref operand, content_ty) => { let content_ty = fx.monomorphize(content_ty); - let box_layout = fx.layout_of(fx.tcx.mk_box(content_ty)); + let box_layout = fx.layout_of(Ty::new_box(fx.tcx, content_ty)); let operand = codegen_operand(fx, operand); let operand = operand.load_scalar(fx); lval.write_cvalue(fx, CValue::by_val(operand, box_layout)); @@ -887,7 +907,7 @@ pub(crate) fn codegen_place<'tcx>( let ptr = cplace.to_ptr(); cplace = CPlace::for_ptr( ptr.offset_i64(fx, elem_layout.size.bytes() as i64 * (from as i64)), - fx.layout_of(fx.tcx.mk_array(*elem_ty, to - from)), + fx.layout_of(Ty::new_array(fx.tcx, *elem_ty, to - from)), ); } ty::Slice(elem_ty) => { diff --git a/compiler/rustc_codegen_cranelift/src/codegen_i128.rs b/compiler/rustc_codegen_cranelift/src/codegen_i128.rs index f751d8c179db5..b2bc289a5b6ba 100644 --- a/compiler/rustc_codegen_cranelift/src/codegen_i128.rs +++ b/compiler/rustc_codegen_cranelift/src/codegen_i128.rs @@ -22,8 +22,8 @@ pub(crate) fn maybe_codegen<'tcx>( match bin_op { BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor => None, - BinOp::Add | BinOp::Sub => None, - BinOp::Mul => { + BinOp::Add | BinOp::AddUnchecked | BinOp::Sub | BinOp::SubUnchecked => None, + BinOp::Mul | BinOp::MulUnchecked => { let args = [lhs.load_scalar(fx), rhs.load_scalar(fx)]; let ret_val = fx.lib_call( "__multi3", @@ -69,7 +69,7 @@ pub(crate) fn maybe_codegen<'tcx>( } } BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne => None, - BinOp::Shl | BinOp::Shr => None, + BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => None, } } @@ -92,7 +92,7 @@ pub(crate) fn maybe_codegen_checked<'tcx>( match bin_op { BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor => unreachable!(), BinOp::Mul if is_signed => { - let out_ty = fx.tcx.mk_tup(&[lhs.layout().ty, fx.tcx.types.bool]); + let out_ty = Ty::new_tup(fx.tcx, &[lhs.layout().ty, fx.tcx.types.bool]); let oflow = CPlace::new_stack_slot(fx, fx.layout_of(fx.tcx.types.i32)); let lhs = lhs.load_scalar(fx); let rhs = rhs.load_scalar(fx); @@ -112,7 +112,7 @@ pub(crate) fn maybe_codegen_checked<'tcx>( Some(CValue::by_val_pair(res, oflow, fx.layout_of(out_ty))) } BinOp::Add | BinOp::Sub | BinOp::Mul => { - let out_ty = fx.tcx.mk_tup(&[lhs.layout().ty, fx.tcx.types.bool]); + let out_ty = Ty::new_tup(fx.tcx, &[lhs.layout().ty, fx.tcx.types.bool]); let out_place = CPlace::new_stack_slot(fx, fx.layout_of(out_ty)); let param_types = vec![ AbiParam::special(fx.pointer_type, ArgumentPurpose::StructReturn), @@ -131,9 +131,10 @@ pub(crate) fn maybe_codegen_checked<'tcx>( fx.lib_call(name, param_types, vec![], &args); Some(out_place.to_cvalue(fx)) } + BinOp::AddUnchecked | BinOp::SubUnchecked | BinOp::MulUnchecked => unreachable!(), BinOp::Offset => unreachable!("offset should only be used on pointers, not 128bit ints"), BinOp::Div | BinOp::Rem => unreachable!(), BinOp::Lt | BinOp::Le | BinOp::Eq | BinOp::Ge | BinOp::Gt | BinOp::Ne => unreachable!(), - BinOp::Shl | BinOp::Shr => unreachable!(), + BinOp::Shl | BinOp::ShlUnchecked | BinOp::Shr | BinOp::ShrUnchecked => unreachable!(), } } diff --git a/compiler/rustc_codegen_cranelift/src/common.rs b/compiler/rustc_codegen_cranelift/src/common.rs index ccb3a0c4f27e4..3081dcfa2b7a3 100644 --- a/compiler/rustc_codegen_cranelift/src/common.rs +++ b/compiler/rustc_codegen_cranelift/src/common.rs @@ -6,6 +6,7 @@ use rustc_index::IndexVec; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, }; +use rustc_span::source_map::Spanned; use rustc_span::SourceFile; use rustc_target::abi::call::FnAbi; use rustc_target::abi::{Integer, Primitive}; @@ -98,7 +99,7 @@ fn clif_pair_type_from_ty<'tcx>( /// Is a pointer to this type a fat ptr? pub(crate) fn has_ptr_meta<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { - let ptr_ty = tcx.mk_ptr(TypeAndMut { ty, mutbl: rustc_hir::Mutability::Not }); + let ptr_ty = Ty::new_ptr(tcx, TypeAndMut { ty, mutbl: rustc_hir::Mutability::Not }); match &tcx.layout_of(ParamEnv::reveal_all().and(ptr_ty)).unwrap().abi { Abi::Scalar(_) => false, Abi::ScalarPair(_, _) => true, @@ -361,7 +362,7 @@ impl<'tcx> FunctionCx<'_, '_, 'tcx> { self.instance.subst_mir_and_normalize_erasing_regions( self.tcx, ty::ParamEnv::reveal_all(), - ty::EarlyBinder(value), + ty::EarlyBinder::bind(value), ) } @@ -413,11 +414,7 @@ impl<'tcx> FunctionCx<'_, '_, 'tcx> { // Note: must be kept in sync with get_caller_location from cg_ssa pub(crate) fn get_caller_location(&mut self, mut source_info: mir::SourceInfo) -> CValue<'tcx> { - let span_to_caller_location = |fx: &mut FunctionCx<'_, '_, 'tcx>, mut span: Span| { - // Remove `Inlined` marks as they pollute `expansion_cause`. - while span.is_inlined() { - span.remove_mark(); - } + let span_to_caller_location = |fx: &mut FunctionCx<'_, '_, 'tcx>, span: Span| { let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span); let caller = fx.tcx.sess.source_map().lookup_char_pos(topmost.lo()); let const_loc = fx.tcx.const_caller_location(( @@ -458,12 +455,12 @@ impl<'tcx> FunctionCx<'_, '_, 'tcx> { } pub(crate) fn anonymous_str(&mut self, msg: &str) -> Value { - let mut data_ctx = DataContext::new(); - data_ctx.define(msg.as_bytes().to_vec().into_boxed_slice()); + let mut data = DataDescription::new(); + data.define(msg.as_bytes().to_vec().into_boxed_slice()); let msg_id = self.module.declare_anonymous_data(false, false).unwrap(); // Ignore DuplicateDefinition error, as the data will be the same - let _ = self.module.define_data(msg_id, &data_ctx); + let _ = self.module.define_data(msg_id, &data); let local_msg_id = self.module.declare_data_in_func(msg_id, self.bcx.func); if self.clif_comments.enabled() { @@ -480,7 +477,7 @@ impl<'tcx> LayoutOfHelpers<'tcx> for RevealAllLayoutCx<'tcx> { #[inline] fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! { - if let layout::LayoutError::SizeOverflow(_) = err { + if let LayoutError::SizeOverflow(_) | LayoutError::ReferencesError(_) = err { self.0.sess.span_fatal(span, err.to_string()) } else { span_bug!(span, "failed to get layout for `{}`: {}", ty, err) @@ -499,25 +496,16 @@ impl<'tcx> FnAbiOfHelpers<'tcx> for RevealAllLayoutCx<'tcx> { fn_abi_request: FnAbiRequest<'tcx>, ) -> ! { if let FnAbiError::Layout(LayoutError::SizeOverflow(_)) = err { - self.0.sess.span_fatal(span, err.to_string()) + self.0.sess.emit_fatal(Spanned { span, node: err }) } else { match fn_abi_request { FnAbiRequest::OfFnPtr { sig, extra_args } => { - span_bug!( - span, - "`fn_abi_of_fn_ptr({}, {:?})` failed: {}", - sig, - extra_args, - err - ); + span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}"); } FnAbiRequest::OfInstance { instance, extra_args } => { span_bug!( span, - "`fn_abi_of_instance({}, {:?})` failed: {}", - instance, - extra_args, - err + "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}" ); } } diff --git a/compiler/rustc_codegen_cranelift/src/config.rs b/compiler/rustc_codegen_cranelift/src/config.rs index 263401e1c4b83..9e92d656c76ef 100644 --- a/compiler/rustc_codegen_cranelift/src/config.rs +++ b/compiler/rustc_codegen_cranelift/src/config.rs @@ -82,6 +82,11 @@ impl BackendConfig { let mut config = BackendConfig::default(); for opt in opts { + if opt.starts_with("-import-instr-limit") { + // Silently ignore -import-instr-limit. It is set by rust's build system even when + // testing cg_clif. + continue; + } if let Some((name, value)) = opt.split_once('=') { match name { "mode" => config.codegen_mode = value.parse()?, diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 77af561a58724..c31535742957c 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -57,7 +57,7 @@ pub(crate) fn codegen_tls_ref<'tcx>( let tls_ptr = if !def_id.is_local() && fx.tcx.needs_thread_local_shim(def_id) { let instance = ty::Instance { def: ty::InstanceDef::ThreadLocalShim(def_id), - substs: ty::InternalSubsts::empty(), + args: ty::GenericArgs::empty(), }; let func_ref = fx.get_function_ref(instance); let call = fx.bcx.ins().call(func_ref, &[]); @@ -324,12 +324,12 @@ fn data_id_for_static( let ref_name = format!("_rust_extern_with_linkage_{}", symbol_name); let ref_data_id = module.declare_data(&ref_name, Linkage::Local, false, false).unwrap(); - let mut data_ctx = DataContext::new(); - data_ctx.set_align(align); - let data = module.declare_data_in_data(data_id, &mut data_ctx); - data_ctx.define(std::iter::repeat(0).take(pointer_ty(tcx).bytes() as usize).collect()); - data_ctx.write_data_addr(0, data, 0); - match module.define_data(ref_data_id, &data_ctx) { + let mut data = DataDescription::new(); + data.set_align(align); + let data_gv = module.declare_data_in_data(data_id, &mut data); + data.define(std::iter::repeat(0).take(pointer_ty(tcx).bytes() as usize).collect()); + data.write_data_addr(0, data_gv, 0); + match module.define_data(ref_data_id, &data) { // Every time the static is referenced there will be another definition of this global, // so duplicate definitions are expected and allowed. Err(ModuleError::DuplicateDefinition(_)) => {} @@ -394,9 +394,9 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant continue; } - let mut data_ctx = DataContext::new(); + let mut data = DataDescription::new(); let alloc = alloc.inner(); - data_ctx.set_align(alloc.align.bytes()); + 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 { @@ -412,11 +412,11 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant } else { ("", section_name.as_str()) }; - data_ctx.set_segment_section(segment_name, section_name); + data.set_segment_section(segment_name, section_name); } let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len()).to_vec(); - data_ctx.define(bytes.into_boxed_slice()); + data.define(bytes.into_boxed_slice()); for &(offset, alloc_id) in alloc.provenance().ptrs().iter() { let addend = { @@ -435,8 +435,8 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant assert_eq!(addend, 0); let func_id = crate::abi::import_function(tcx, module, instance.polymorphize(tcx)); - let local_func_id = module.declare_func_in_data(func_id, &mut data_ctx); - data_ctx.write_function_addr(offset.bytes() as u32, local_func_id); + let local_func_id = module.declare_func_in_data(func_id, &mut data); + data.write_function_addr(offset.bytes() as u32, local_func_id); continue; } GlobalAlloc::Memory(target_alloc) => { @@ -462,11 +462,11 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant } }; - let global_value = module.declare_data_in_data(data_id, &mut data_ctx); - data_ctx.write_data_addr(offset.bytes() as u32, global_value, addend as i64); + let global_value = module.declare_data_in_data(data_id, &mut data); + data.write_data_addr(offset.bytes() as u32, global_value, addend as i64); } - module.define_data(data_id, &data_ctx).unwrap(); + module.define_data(data_id, &data).unwrap(); cx.done.insert(data_id); } diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs index 463de6a91c74c..50bc7a127af09 100644 --- a/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs +++ b/compiler/rustc_codegen_cranelift/src/debuginfo/line_info.rs @@ -81,7 +81,7 @@ impl DebugContext { match tcx.sess.source_map().lookup_line(span.lo()) { Ok(SourceFileAndLine { sf: file, line }) => { - let line_pos = file.line_begin_pos(span.lo()); + let line_pos = file.lines(|lines| lines[line]); ( file, @@ -165,7 +165,7 @@ impl FunctionDebugContext { for &MachSrcLoc { start, end, loc } in mcr.buffer.get_srclocs_sorted() { debug_context.dwarf.unit.line_program.row().address_offset = u64::from(start); if !loc.is_default() { - let source_loc = *self.source_loc_set.get_index(loc.bits() as usize).unwrap(); + let source_loc = self.source_loc_set[loc.bits() as usize]; create_row_for_span(debug_context, source_loc); } else { create_row_for_span(debug_context, self.function_source_loc); diff --git a/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs b/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs index 3a7421d8b30d8..8a4b1cccf1460 100644 --- a/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/debuginfo/mod.rs @@ -38,7 +38,7 @@ pub(crate) struct DebugContext { pub(crate) struct FunctionDebugContext { entry_id: UnitEntryId, function_source_loc: (FileId, u64, u64), - source_loc_set: indexmap::IndexSet<(FileId, u64, u64)>, + source_loc_set: IndexSet<(FileId, u64, u64)>, } impl DebugContext { diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index aad9a9647f8c9..d143bcc96ef93 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -54,8 +54,8 @@ impl OngoingCodegen { self, sess: &Session, backend_config: &BackendConfig, - ) -> (CodegenResults, FxHashMap) { - let mut work_products = FxHashMap::default(); + ) -> (CodegenResults, FxIndexMap) { + let mut work_products = FxIndexMap::default(); let mut modules = vec![]; for module_codegen in self.modules { diff --git a/compiler/rustc_codegen_cranelift/src/driver/jit.rs b/compiler/rustc_codegen_cranelift/src/driver/jit.rs index 3118105a4e2d7..1c606494f383e 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/jit.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/jit.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::ffi::CString; use std::os::raw::{c_char, c_int}; -use std::sync::{mpsc, Mutex}; +use std::sync::{mpsc, Mutex, OnceLock}; use rustc_codegen_ssa::CrateInfo; use rustc_middle::mir::mono::MonoItem; @@ -13,9 +13,6 @@ use rustc_span::Symbol; use cranelift_jit::{JITBuilder, JITModule}; -// FIXME use std::sync::OnceLock once it stabilizes -use once_cell::sync::OnceCell; - use crate::{prelude::*, BackendConfig}; use crate::{CodegenCx, CodegenMode}; @@ -29,7 +26,7 @@ thread_local! { } /// The Sender owned by the rustc thread -static GLOBAL_MESSAGE_SENDER: OnceCell>> = OnceCell::new(); +static GLOBAL_MESSAGE_SENDER: OnceLock>> = OnceLock::new(); /// A message that is sent from the jitted runtime to the rustc thread. /// Senders are responsible for upholding `Send` semantics. @@ -101,7 +98,7 @@ pub(crate) fn run_jit(tcx: TyCtxt<'_>, backend_config: BackendConfig) -> ! { tcx.sess.fatal("JIT mode doesn't work with `cargo check`"); } - if !tcx.sess.crate_types().contains(&rustc_session::config::CrateType::Executable) { + if !tcx.crate_types().contains(&rustc_session::config::CrateType::Executable) { tcx.sess.fatal("can't jit non-executable crate"); } @@ -117,9 +114,9 @@ pub(crate) fn run_jit(tcx: TyCtxt<'_>, backend_config: BackendConfig) -> ! { .iter() .map(|cgu| cgu.items_in_deterministic_order(tcx).into_iter()) .flatten() - .collect::>() + .collect::>() .into_iter() - .collect::>(); + .collect::>(); tcx.sess.time("codegen mono items", || { super::predefine_mono_items(tcx, &mut jit_module, &mono_items); @@ -325,7 +322,7 @@ fn dep_symbol_lookup_fn( Linkage::NotLinked | Linkage::IncludedFromDylib => {} Linkage::Static => { let name = crate_info.crate_name[&cnum]; - let mut err = sess.struct_err(&format!("Can't load static lib {}", name)); + let mut err = sess.struct_err(format!("Can't load static lib {}", name)); err.note("rustc_codegen_cranelift can only load dylibs in JIT mode."); err.emit(); } diff --git a/compiler/rustc_codegen_cranelift/src/driver/mod.rs b/compiler/rustc_codegen_cranelift/src/driver/mod.rs index 5c52c9c18adfd..12e90b5841034 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/mod.rs @@ -5,7 +5,7 @@ //! [`codegen_static`]: crate::constant::codegen_static use rustc_data_structures::profiling::SelfProfilerRef; -use rustc_middle::mir::mono::{Linkage as RLinkage, MonoItem, Visibility}; +use rustc_middle::mir::mono::{MonoItem, MonoItemData}; use crate::prelude::*; @@ -16,11 +16,11 @@ pub(crate) mod jit; fn predefine_mono_items<'tcx>( tcx: TyCtxt<'tcx>, module: &mut dyn Module, - mono_items: &[(MonoItem<'tcx>, (RLinkage, Visibility))], + mono_items: &[(MonoItem<'tcx>, MonoItemData)], ) { tcx.prof.generic_activity("predefine functions").run(|| { let is_compiler_builtins = tcx.is_compiler_builtins(LOCAL_CRATE); - for &(mono_item, (linkage, visibility)) in mono_items { + for &(mono_item, data) in mono_items { match mono_item { MonoItem::Fn(instance) => { let name = tcx.symbol_name(instance).name; @@ -29,8 +29,8 @@ fn predefine_mono_items<'tcx>( get_function_sig(tcx, module.target_config().default_call_conv, instance); let linkage = crate::linkage::get_clif_linkage( mono_item, - linkage, - visibility, + data.linkage, + data.visibility, is_compiler_builtins, ); module.declare_function(name, linkage, &sig).unwrap(); diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs index 63a1f6959ddae..baadd7a9e812b 100644 --- a/compiler/rustc_codegen_cranelift/src/global_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs @@ -42,7 +42,7 @@ pub(crate) fn codegen_global_asm_item(tcx: TyCtxt<'_>, global_asm: &mut String, InlineAsmOperand::SymFn { anon_const } => { let ty = tcx.typeck_body(anon_const.body).node_type(anon_const.hir_id); let instance = match ty.kind() { - &ty::FnDef(def_id, substs) => Instance::new(def_id, substs), + &ty::FnDef(def_id, args) => Instance::new(def_id, args), _ => span_bug!(op_sp, "asm sym is not a function"), }; let symbol = tcx.symbol_name(instance); diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index 3ba530c040f7f..518e3da07a44d 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -254,12 +254,12 @@ pub(crate) fn codegen_inline_asm<'tcx>( } InlineAsmOperand::SymFn { ref value } => { let literal = fx.monomorphize(value.literal); - if let ty::FnDef(def_id, substs) = *literal.ty().kind() { + if let ty::FnDef(def_id, args) = *literal.ty().kind() { let instance = ty::Instance::resolve_for_fn_ptr( fx.tcx, ty::ParamEnv::reveal_all(), def_id, - substs, + args, ) .unwrap(); let symbol = fx.tcx.symbol_name(instance); diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/llvm.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/llvm.rs index f67fdb592700f..63b5402f2b6d9 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/llvm.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/llvm.rs @@ -3,23 +3,35 @@ use crate::intrinsics::*; use crate::prelude::*; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; pub(crate) fn codegen_llvm_intrinsic_call<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, intrinsic: &str, - substs: SubstsRef<'tcx>, + generic_args: GenericArgsRef<'tcx>, args: &[mir::Operand<'tcx>], ret: CPlace<'tcx>, target: Option, ) { if intrinsic.starts_with("llvm.aarch64") { return llvm_aarch64::codegen_aarch64_llvm_intrinsic_call( - fx, intrinsic, substs, args, ret, target, + fx, + intrinsic, + generic_args, + args, + ret, + target, ); } if intrinsic.starts_with("llvm.x86") { - return llvm_x86::codegen_x86_llvm_intrinsic_call(fx, intrinsic, substs, args, ret, target); + return llvm_x86::codegen_x86_llvm_intrinsic_call( + fx, + intrinsic, + generic_args, + args, + ret, + target, + ); } match intrinsic { diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_aarch64.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_aarch64.rs index 33b2f4702a7a3..c20a9915930eb 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_aarch64.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_aarch64.rs @@ -3,12 +3,12 @@ use crate::intrinsics::*; use crate::prelude::*; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; pub(crate) fn codegen_aarch64_llvm_intrinsic_call<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, intrinsic: &str, - _substs: SubstsRef<'tcx>, + _args: GenericArgsRef<'tcx>, args: &[mir::Operand<'tcx>], ret: CPlace<'tcx>, target: Option, diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_x86.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_x86.rs index 56d8f13cec5e5..fdd27a454e066 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_x86.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/llvm_x86.rs @@ -3,12 +3,12 @@ use crate::intrinsics::*; use crate::prelude::*; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, intrinsic: &str, - _substs: SubstsRef<'tcx>, + _args: GenericArgsRef<'tcx>, args: &[mir::Operand<'tcx>], ret: CPlace<'tcx>, target: Option, @@ -18,8 +18,25 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( // Spin loop hint } + // Used by is_x86_feature_detected!(); + "llvm.x86.xgetbv" => { + // FIXME use the actual xgetbv instruction + intrinsic_args!(fx, args => (v); intrinsic); + + let v = v.load_scalar(fx); + + // As of writing on XCR0 exists + fx.bcx.ins().trapnz(v, TrapCode::UnreachableCodeReached); + + let res = fx.bcx.ins().iconst(types::I64, 1 /* bit 0 must be set */); + ret.write_cvalue(fx, CValue::by_val(res, fx.layout_of(fx.tcx.types.i64))); + } + // Used by `_mm_movemask_epi8` and `_mm256_movemask_epi8` - "llvm.x86.sse2.pmovmskb.128" | "llvm.x86.avx2.pmovmskb" | "llvm.x86.sse2.movmsk.pd" => { + "llvm.x86.sse2.pmovmskb.128" + | "llvm.x86.avx2.pmovmskb" + | "llvm.x86.sse.movmsk.ps" + | "llvm.x86.sse2.movmsk.pd" => { intrinsic_args!(fx, args => (a); intrinsic); let (lane_count, lane_ty) = a.layout().ty.simd_size_and_type(fx.tcx); @@ -50,7 +67,7 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( let res = CValue::by_val(res, fx.layout_of(fx.tcx.types.i32)); ret.write_cvalue(fx, res); } - "llvm.x86.sse2.cmp.ps" | "llvm.x86.sse2.cmp.pd" => { + "llvm.x86.sse.cmp.ps" | "llvm.x86.sse2.cmp.pd" => { let (x, y, kind) = match args { [x, y, kind] => (x, y, kind), _ => bug!("wrong number of args for intrinsic {intrinsic}"), @@ -63,18 +80,95 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( let flt_cc = match kind .try_to_bits(Size::from_bytes(1)) .unwrap_or_else(|| panic!("kind not scalar: {:?}", kind)) + .try_into() + .unwrap() { - 0 => FloatCC::Equal, - 1 => FloatCC::LessThan, - 2 => FloatCC::LessThanOrEqual, - 7 => FloatCC::Ordered, - 3 => FloatCC::Unordered, - 4 => FloatCC::NotEqual, - 5 => FloatCC::UnorderedOrGreaterThanOrEqual, - 6 => FloatCC::UnorderedOrGreaterThan, + _CMP_EQ_OQ | _CMP_EQ_OS => FloatCC::Equal, + _CMP_LT_OS | _CMP_LT_OQ => FloatCC::LessThan, + _CMP_LE_OS | _CMP_LE_OQ => FloatCC::LessThanOrEqual, + _CMP_UNORD_Q | _CMP_UNORD_S => FloatCC::Unordered, + _CMP_NEQ_UQ | _CMP_NEQ_US => FloatCC::NotEqual, + _CMP_NLT_US | _CMP_NLT_UQ => FloatCC::UnorderedOrGreaterThanOrEqual, + _CMP_NLE_US | _CMP_NLE_UQ => FloatCC::UnorderedOrGreaterThan, + _CMP_ORD_Q | _CMP_ORD_S => FloatCC::Ordered, + _CMP_EQ_UQ | _CMP_EQ_US => FloatCC::UnorderedOrEqual, + _CMP_NGE_US | _CMP_NGE_UQ => FloatCC::UnorderedOrLessThan, + _CMP_NGT_US | _CMP_NGT_UQ => FloatCC::UnorderedOrLessThanOrEqual, + _CMP_FALSE_OQ | _CMP_FALSE_OS => todo!(), + _CMP_NEQ_OQ | _CMP_NEQ_OS => FloatCC::OrderedNotEqual, + _CMP_GE_OS | _CMP_GE_OQ => FloatCC::GreaterThanOrEqual, + _CMP_GT_OS | _CMP_GT_OQ => FloatCC::GreaterThan, + _CMP_TRUE_UQ | _CMP_TRUE_US => todo!(), + kind => unreachable!("kind {:?}", kind), }; + // Copied from stdarch + /// Equal (ordered, non-signaling) + const _CMP_EQ_OQ: i32 = 0x00; + /// Less-than (ordered, signaling) + const _CMP_LT_OS: i32 = 0x01; + /// Less-than-or-equal (ordered, signaling) + const _CMP_LE_OS: i32 = 0x02; + /// Unordered (non-signaling) + const _CMP_UNORD_Q: i32 = 0x03; + /// Not-equal (unordered, non-signaling) + const _CMP_NEQ_UQ: i32 = 0x04; + /// Not-less-than (unordered, signaling) + const _CMP_NLT_US: i32 = 0x05; + /// Not-less-than-or-equal (unordered, signaling) + const _CMP_NLE_US: i32 = 0x06; + /// Ordered (non-signaling) + const _CMP_ORD_Q: i32 = 0x07; + /// Equal (unordered, non-signaling) + const _CMP_EQ_UQ: i32 = 0x08; + /// Not-greater-than-or-equal (unordered, signaling) + const _CMP_NGE_US: i32 = 0x09; + /// Not-greater-than (unordered, signaling) + const _CMP_NGT_US: i32 = 0x0a; + /// False (ordered, non-signaling) + const _CMP_FALSE_OQ: i32 = 0x0b; + /// Not-equal (ordered, non-signaling) + const _CMP_NEQ_OQ: i32 = 0x0c; + /// Greater-than-or-equal (ordered, signaling) + const _CMP_GE_OS: i32 = 0x0d; + /// Greater-than (ordered, signaling) + const _CMP_GT_OS: i32 = 0x0e; + /// True (unordered, non-signaling) + const _CMP_TRUE_UQ: i32 = 0x0f; + /// Equal (ordered, signaling) + const _CMP_EQ_OS: i32 = 0x10; + /// Less-than (ordered, non-signaling) + const _CMP_LT_OQ: i32 = 0x11; + /// Less-than-or-equal (ordered, non-signaling) + const _CMP_LE_OQ: i32 = 0x12; + /// Unordered (signaling) + const _CMP_UNORD_S: i32 = 0x13; + /// Not-equal (unordered, signaling) + const _CMP_NEQ_US: i32 = 0x14; + /// Not-less-than (unordered, non-signaling) + const _CMP_NLT_UQ: i32 = 0x15; + /// Not-less-than-or-equal (unordered, non-signaling) + const _CMP_NLE_UQ: i32 = 0x16; + /// Ordered (signaling) + const _CMP_ORD_S: i32 = 0x17; + /// Equal (unordered, signaling) + const _CMP_EQ_US: i32 = 0x18; + /// Not-greater-than-or-equal (unordered, non-signaling) + const _CMP_NGE_UQ: i32 = 0x19; + /// Not-greater-than (unordered, non-signaling) + const _CMP_NGT_UQ: i32 = 0x1a; + /// False (ordered, signaling) + const _CMP_FALSE_OS: i32 = 0x1b; + /// Not-equal (ordered, signaling) + const _CMP_NEQ_OS: i32 = 0x1c; + /// Greater-than-or-equal (ordered, non-signaling) + const _CMP_GE_OQ: i32 = 0x1d; + /// Greater-than (ordered, non-signaling) + const _CMP_GT_OQ: i32 = 0x1e; + /// True (unordered, signaling) + const _CMP_TRUE_US: i32 = 0x1f; + simd_pair_for_each_lane(fx, x, y, ret, &|fx, lane_ty, res_lane_ty, x_lane, y_lane| { let res_lane = match lane_ty.kind() { ty::Float(_) => fx.bcx.ins().fcmp(flt_cc, x_lane, y_lane), @@ -100,7 +194,41 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( _ => fx.bcx.ins().iconst(types::I32, 0), }); } + "llvm.x86.sse2.psrai.d" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.sse2.psrai.d imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 32 => fx.bcx.ins().sshr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } "llvm.x86.sse2.pslli.d" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.sse2.pslli.d imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 32 => fx.bcx.ins().ishl_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.sse2.psrli.w" => { let (a, imm8) = match args { [a, imm8] => (a, imm8), _ => bug!("wrong number of args for intrinsic {intrinsic}"), @@ -109,6 +237,125 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) .expect("llvm.x86.sse2.psrli.d imm8 not const"); + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 16 => fx.bcx.ins().ushr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.sse2.psrai.w" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.sse2.psrai.d imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 16 => fx.bcx.ins().sshr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.sse2.pslli.w" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.sse2.pslli.d imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 16 => fx.bcx.ins().ishl_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.avx.psrli.d" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.psrli.d imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 32 => fx.bcx.ins().ushr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.avx.psrai.d" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.psrai.d imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 32 => fx.bcx.ins().sshr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.sse2.psrli.q" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.psrli.q imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 64 => fx.bcx.ins().ushr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.sse2.pslli.q" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.pslli.q imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 64 => fx.bcx.ins().ishl_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.avx.pslli.d" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.pslli.d imm8 not const"); + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 .try_to_bits(Size::from_bytes(4)) .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) @@ -117,7 +364,149 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( _ => fx.bcx.ins().iconst(types::I32, 0), }); } - "llvm.x86.sse2.storeu.dq" => { + "llvm.x86.avx2.psrli.w" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.psrli.w imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 16 => fx.bcx.ins().ushr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.avx2.psrai.w" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.psrai.w imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 16 => fx.bcx.ins().sshr_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.avx2.pslli.w" => { + let (a, imm8) = match args { + [a, imm8] => (a, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let imm8 = crate::constant::mir_operand_get_const_val(fx, imm8) + .expect("llvm.x86.avx.pslli.w imm8 not const"); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| match imm8 + .try_to_bits(Size::from_bytes(4)) + .unwrap_or_else(|| panic!("imm8 not scalar: {:?}", imm8)) + { + imm8 if imm8 < 16 => fx.bcx.ins().ishl_imm(lane, i64::from(imm8 as u8)), + _ => fx.bcx.ins().iconst(types::I32, 0), + }); + } + "llvm.x86.ssse3.pshuf.b.128" | "llvm.x86.avx2.pshuf.b" => { + let (a, b) = match args { + [a, b] => (a, b), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let b = codegen_operand(fx, b); + + // Based on the pseudocode at https://github.com/rust-lang/stdarch/blob/1cfbca8b38fd9b4282b2f054f61c6ca69fc7ce29/crates/core_arch/src/x86/avx2.rs#L2319-L2332 + let zero = fx.bcx.ins().iconst(types::I8, 0); + for i in 0..16 { + let b_lane = b.value_lane(fx, i).load_scalar(fx); + let is_zero = fx.bcx.ins().band_imm(b_lane, 0x80); + let a_idx = fx.bcx.ins().band_imm(b_lane, 0xf); + let a_idx = fx.bcx.ins().uextend(fx.pointer_type, a_idx); + let a_lane = a.value_lane_dyn(fx, a_idx).load_scalar(fx); + let res = fx.bcx.ins().select(is_zero, zero, a_lane); + ret.place_lane(fx, i).to_ptr().store(fx, res, MemFlags::trusted()); + } + + if intrinsic == "llvm.x86.avx2.pshuf.b" { + for i in 16..32 { + let b_lane = b.value_lane(fx, i).load_scalar(fx); + let is_zero = fx.bcx.ins().band_imm(b_lane, 0x80); + let b_lane_masked = fx.bcx.ins().band_imm(b_lane, 0xf); + let a_idx = fx.bcx.ins().iadd_imm(b_lane_masked, 16); + let a_idx = fx.bcx.ins().uextend(fx.pointer_type, a_idx); + let a_lane = a.value_lane_dyn(fx, a_idx).load_scalar(fx); + let res = fx.bcx.ins().select(is_zero, zero, a_lane); + ret.place_lane(fx, i).to_ptr().store(fx, res, MemFlags::trusted()); + } + } + } + "llvm.x86.avx2.vperm2i128" => { + // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_permute2x128_si256 + let (a, b, imm8) = match args { + [a, b, imm8] => (a, b, imm8), + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + let b = codegen_operand(fx, b); + let imm8 = codegen_operand(fx, imm8).load_scalar(fx); + + let a_0 = a.value_lane(fx, 0).load_scalar(fx); + let a_1 = a.value_lane(fx, 1).load_scalar(fx); + let a_low = fx.bcx.ins().iconcat(a_0, a_1); + let a_2 = a.value_lane(fx, 2).load_scalar(fx); + let a_3 = a.value_lane(fx, 3).load_scalar(fx); + let a_high = fx.bcx.ins().iconcat(a_2, a_3); + + let b_0 = b.value_lane(fx, 0).load_scalar(fx); + let b_1 = b.value_lane(fx, 1).load_scalar(fx); + let b_low = fx.bcx.ins().iconcat(b_0, b_1); + let b_2 = b.value_lane(fx, 2).load_scalar(fx); + let b_3 = b.value_lane(fx, 3).load_scalar(fx); + let b_high = fx.bcx.ins().iconcat(b_2, b_3); + + fn select4( + fx: &mut FunctionCx<'_, '_, '_>, + a_high: Value, + a_low: Value, + b_high: Value, + b_low: Value, + control: Value, + ) -> Value { + let a_or_b = fx.bcx.ins().band_imm(control, 0b0010); + let high_or_low = fx.bcx.ins().band_imm(control, 0b0001); + let is_zero = fx.bcx.ins().band_imm(control, 0b1000); + + let zero = fx.bcx.ins().iconst(types::I64, 0); + let zero = fx.bcx.ins().iconcat(zero, zero); + + let res_a = fx.bcx.ins().select(high_or_low, a_high, a_low); + let res_b = fx.bcx.ins().select(high_or_low, b_high, b_low); + let res = fx.bcx.ins().select(a_or_b, res_b, res_a); + fx.bcx.ins().select(is_zero, zero, res) + } + + let control0 = imm8; + let res_low = select4(fx, a_high, a_low, b_high, b_low, control0); + let (res_0, res_1) = fx.bcx.ins().isplit(res_low); + + let control1 = fx.bcx.ins().ushr_imm(imm8, 4); + let res_high = select4(fx, a_high, a_low, b_high, b_low, control1); + let (res_2, res_3) = fx.bcx.ins().isplit(res_high); + + ret.place_lane(fx, 0).to_ptr().store(fx, res_0, MemFlags::trusted()); + ret.place_lane(fx, 1).to_ptr().store(fx, res_1, MemFlags::trusted()); + ret.place_lane(fx, 2).to_ptr().store(fx, res_2, MemFlags::trusted()); + ret.place_lane(fx, 3).to_ptr().store(fx, res_3, MemFlags::trusted()); + } + "llvm.x86.sse2.storeu.dq" | "llvm.x86.sse2.storeu.pd" => { intrinsic_args!(fx, args => (mem_addr, a); intrinsic); let mem_addr = mem_addr.load_scalar(fx); @@ -125,17 +514,45 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( let dest = CPlace::for_ptr(Pointer::new(mem_addr), a.layout()); dest.write_cvalue(fx, a); } - "llvm.x86.addcarry.64" => { + "llvm.x86.ssse3.pabs.b.128" | "llvm.x86.ssse3.pabs.w.128" | "llvm.x86.ssse3.pabs.d.128" => { + let a = match args { + [a] => a, + _ => bug!("wrong number of args for intrinsic {intrinsic}"), + }; + let a = codegen_operand(fx, a); + + simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| { + fx.bcx.ins().iabs(lane) + }); + } + "llvm.x86.addcarry.32" | "llvm.x86.addcarry.64" => { intrinsic_args!(fx, args => (c_in, a, b); intrinsic); let c_in = c_in.load_scalar(fx); - llvm_add_sub(fx, BinOp::Add, ret, c_in, a, b); + let (cb_out, c) = llvm_add_sub(fx, BinOp::Add, c_in, a, b); + + let layout = fx.layout_of(Ty::new_tup(fx.tcx, &[fx.tcx.types.u8, a.layout().ty])); + let val = CValue::by_val_pair(cb_out, c, layout); + ret.write_cvalue(fx, val); } - "llvm.x86.subborrow.64" => { + "llvm.x86.addcarryx.u32" | "llvm.x86.addcarryx.u64" => { + intrinsic_args!(fx, args => (c_in, a, b, out); intrinsic); + let c_in = c_in.load_scalar(fx); + + let (cb_out, c) = llvm_add_sub(fx, BinOp::Add, c_in, a, b); + + Pointer::new(out.load_scalar(fx)).store(fx, c, MemFlags::trusted()); + ret.write_cvalue(fx, CValue::by_val(cb_out, fx.layout_of(fx.tcx.types.u8))); + } + "llvm.x86.subborrow.32" | "llvm.x86.subborrow.64" => { intrinsic_args!(fx, args => (b_in, a, b); intrinsic); let b_in = b_in.load_scalar(fx); - llvm_add_sub(fx, BinOp::Sub, ret, b_in, a, b); + let (cb_out, c) = llvm_add_sub(fx, BinOp::Sub, b_in, a, b); + + let layout = fx.layout_of(Ty::new_tup(fx.tcx, &[fx.tcx.types.u8, a.layout().ty])); + let val = CValue::by_val_pair(cb_out, c, layout); + ret.write_cvalue(fx, val); } _ => { fx.tcx @@ -160,21 +577,11 @@ pub(crate) fn codegen_x86_llvm_intrinsic_call<'tcx>( fn llvm_add_sub<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, bin_op: BinOp, - ret: CPlace<'tcx>, cb_in: Value, a: CValue<'tcx>, b: CValue<'tcx>, -) { - assert_eq!( - a.layout().ty, - fx.tcx.types.u64, - "llvm.x86.addcarry.64/llvm.x86.subborrow.64 second operand must be u64" - ); - assert_eq!( - b.layout().ty, - fx.tcx.types.u64, - "llvm.x86.addcarry.64/llvm.x86.subborrow.64 third operand must be u64" - ); +) -> (Value, Value) { + assert_eq!(a.layout().ty, b.layout().ty); // c + carry -> c + first intermediate carry or borrow respectively let int0 = crate::num::codegen_checked_int_binop(fx, bin_op, a, b); @@ -182,15 +589,14 @@ fn llvm_add_sub<'tcx>( let cb0 = int0.value_field(fx, FieldIdx::new(1)).load_scalar(fx); // c + carry -> c + second intermediate carry or borrow respectively - let cb_in_as_u64 = fx.bcx.ins().uextend(types::I64, cb_in); - let cb_in_as_u64 = CValue::by_val(cb_in_as_u64, fx.layout_of(fx.tcx.types.u64)); - let int1 = crate::num::codegen_checked_int_binop(fx, bin_op, c, cb_in_as_u64); + let clif_ty = fx.clif_type(a.layout().ty).unwrap(); + let cb_in_as_int = fx.bcx.ins().uextend(clif_ty, cb_in); + let cb_in_as_int = CValue::by_val(cb_in_as_int, fx.layout_of(a.layout().ty)); + let int1 = crate::num::codegen_checked_int_binop(fx, bin_op, c, cb_in_as_int); let (c, cb1) = int1.load_scalar_pair(fx); // carry0 | carry1 -> carry or borrow respectively let cb_out = fx.bcx.ins().bor(cb0, cb1); - let layout = fx.layout_of(fx.tcx.mk_tup(&[fx.tcx.types.u8, fx.tcx.types.u64])); - let val = CValue::by_val_pair(cb_out, c, layout); - ret.write_cvalue(fx, val); + (cb_out, c) } diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs index 0a513b08b74f5..36e9ba9c7f8e4 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs @@ -24,7 +24,7 @@ pub(crate) use llvm::codegen_llvm_intrinsic_call; use rustc_middle::ty; use rustc_middle::ty::layout::{HasParamEnv, ValidityRequirement}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_span::symbol::{kw, sym, Symbol}; use crate::prelude::*; @@ -213,13 +213,13 @@ pub(crate) fn codegen_intrinsic_call<'tcx>( source_info: mir::SourceInfo, ) { let intrinsic = fx.tcx.item_name(instance.def_id()); - let substs = instance.substs; + let instance_args = instance.args; if intrinsic.as_str().starts_with("simd_") { self::simd::codegen_simd_intrinsic_call( fx, intrinsic, - substs, + instance_args, args, destination, target.expect("target for simd intrinsic"), @@ -233,7 +233,7 @@ pub(crate) fn codegen_intrinsic_call<'tcx>( fx, instance, intrinsic, - substs, + instance_args, args, destination, target, @@ -365,7 +365,7 @@ fn codegen_regular_intrinsic_call<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, instance: Instance<'tcx>, intrinsic: Symbol, - substs: SubstsRef<'tcx>, + generic_args: GenericArgsRef<'tcx>, args: &[mir::Operand<'tcx>], ret: CPlace<'tcx>, destination: Option, @@ -394,7 +394,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let dst = dst.load_scalar(fx); let count = count.load_scalar(fx); - let elem_ty = substs.type_at(0); + let elem_ty = generic_args.type_at(0); let elem_size: u64 = fx.layout_of(elem_ty).size.bytes(); assert_eq!(args.len(), 3); let byte_amount = @@ -410,7 +410,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let src = src.load_scalar(fx); let count = count.load_scalar(fx); - let elem_ty = substs.type_at(0); + let elem_ty = generic_args.type_at(0); let elem_size: u64 = fx.layout_of(elem_ty).size.bytes(); assert_eq!(args.len(), 3); let byte_amount = @@ -428,7 +428,7 @@ fn codegen_regular_intrinsic_call<'tcx>( sym::size_of_val => { intrinsic_args!(fx, args => (ptr); intrinsic); - let layout = fx.layout_of(substs.type_at(0)); + let layout = fx.layout_of(generic_args.type_at(0)); // Note: Can't use is_unsized here as truly unsized types need to take the fixed size // branch let size = if let Abi::ScalarPair(_, _) = ptr.layout().abi { @@ -443,7 +443,7 @@ fn codegen_regular_intrinsic_call<'tcx>( sym::min_align_of_val => { intrinsic_args!(fx, args => (ptr); intrinsic); - let layout = fx.layout_of(substs.type_at(0)); + let layout = fx.layout_of(generic_args.type_at(0)); // Note: Can't use is_unsized here as truly unsized types need to take the fixed size // branch let align = if let Abi::ScalarPair(_, _) = ptr.layout().abi { @@ -472,28 +472,11 @@ fn codegen_regular_intrinsic_call<'tcx>( ret.write_cvalue(fx, CValue::by_val(align, usize_layout)); } - sym::unchecked_add - | sym::unchecked_sub - | sym::unchecked_mul - | sym::unchecked_div - | sym::exact_div - | sym::unchecked_rem - | sym::unchecked_shl - | sym::unchecked_shr => { + sym::exact_div => { intrinsic_args!(fx, args => (x, y); intrinsic); - // FIXME trap on overflow - let bin_op = match intrinsic { - sym::unchecked_add => BinOp::Add, - sym::unchecked_sub => BinOp::Sub, - sym::unchecked_mul => BinOp::Mul, - sym::unchecked_div | sym::exact_div => BinOp::Div, - sym::unchecked_rem => BinOp::Rem, - sym::unchecked_shl => BinOp::Shl, - sym::unchecked_shr => BinOp::Shr, - _ => unreachable!(), - }; - let res = crate::num::codegen_int_binop(fx, bin_op, x, y); + // FIXME trap on inexact + let res = crate::num::codegen_int_binop(fx, BinOp::Div, x, y); ret.write_cvalue(fx, res); } sym::saturating_add | sym::saturating_sub => { @@ -619,7 +602,7 @@ fn codegen_regular_intrinsic_call<'tcx>( sym::assert_inhabited | sym::assert_zero_valid | sym::assert_mem_uninitialized_valid => { intrinsic_args!(fx, args => (); intrinsic); - let ty = substs.type_at(0); + let ty = generic_args.type_at(0); let requirement = ValidityRequirement::from_intrinsic(intrinsic); @@ -664,12 +647,13 @@ fn codegen_regular_intrinsic_call<'tcx>( let val = CValue::by_ref(Pointer::new(ptr.load_scalar(fx)), inner_layout); ret.write_cvalue(fx, val); } - sym::volatile_store | sym::unaligned_volatile_store => { + sym::volatile_store | sym::unaligned_volatile_store | sym::nontemporal_store => { intrinsic_args!(fx, args => (ptr, val); intrinsic); let ptr = ptr.load_scalar(fx); // Cranelift treats stores as volatile by default // FIXME correctly handle unaligned_volatile_store + // FIXME actually do nontemporal stores if requested let dest = CPlace::for_ptr(Pointer::new(ptr), val.layout()); dest.write_cvalue(fx, val); } @@ -691,7 +675,7 @@ fn codegen_regular_intrinsic_call<'tcx>( intrinsic_args!(fx, args => (ptr, base); intrinsic); let ptr = ptr.load_scalar(fx); let base = base.load_scalar(fx); - let ty = substs.type_at(0); + let ty = generic_args.type_at(0); let pointee_size: u64 = fx.layout_of(ty).size.bytes(); let diff_bytes = fx.bcx.ins().isub(ptr, base); @@ -737,7 +721,7 @@ fn codegen_regular_intrinsic_call<'tcx>( intrinsic_args!(fx, args => (ptr); intrinsic); let ptr = ptr.load_scalar(fx); - let ty = substs.type_at(0); + let ty = generic_args.type_at(0); match ty.kind() { ty::Uint(UintTy::U128) | ty::Int(IntTy::I128) => { // FIXME implement 128bit atomics @@ -768,7 +752,7 @@ fn codegen_regular_intrinsic_call<'tcx>( intrinsic_args!(fx, args => (ptr, val); intrinsic); let ptr = ptr.load_scalar(fx); - let ty = substs.type_at(0); + let ty = generic_args.type_at(0); match ty.kind() { ty::Uint(UintTy::U128) | ty::Int(IntTy::I128) => { // FIXME implement 128bit atomics @@ -1145,7 +1129,7 @@ fn codegen_regular_intrinsic_call<'tcx>( let lhs_ref = lhs_ref.load_scalar(fx); let rhs_ref = rhs_ref.load_scalar(fx); - let size = fx.layout_of(substs.type_at(0)).layout.size(); + let size = fx.layout_of(generic_args.type_at(0)).layout.size(); // FIXME add and use emit_small_memcmp let is_eq_value = if size == Size::ZERO { // No bytes means they're trivially equal @@ -1171,6 +1155,20 @@ fn codegen_regular_intrinsic_call<'tcx>( ret.write_cvalue(fx, CValue::by_val(is_eq_value, ret.layout())); } + sym::compare_bytes => { + intrinsic_args!(fx, args => (lhs_ptr, rhs_ptr, bytes_val); intrinsic); + let lhs_ptr = lhs_ptr.load_scalar(fx); + let rhs_ptr = rhs_ptr.load_scalar(fx); + let bytes_val = bytes_val.load_scalar(fx); + + let params = vec![AbiParam::new(fx.pointer_type); 3]; + let returns = vec![AbiParam::new(types::I32)]; + let args = &[lhs_ptr, rhs_ptr, bytes_val]; + // Here we assume that the `memcmp` provided by the target is a NOP for size 0. + let cmp = fx.lib_call("memcmp", params, returns, args)[0]; + ret.write_cvalue(fx, CValue::by_val(cmp, ret.layout())); + } + sym::const_allocate => { intrinsic_args!(fx, args => (_size, _align); intrinsic); diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs index 5a038bfca5d2d..9863e40b5b7a1 100644 --- a/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs +++ b/compiler/rustc_codegen_cranelift/src/intrinsics/simd.rs @@ -1,6 +1,6 @@ //! Codegen `extern "platform-intrinsic"` intrinsics. -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_span::Symbol; use rustc_target::abi::Endian; @@ -21,7 +21,7 @@ fn report_simd_type_validation_error( pub(super) fn codegen_simd_intrinsic_call<'tcx>( fx: &mut FunctionCx<'_, '_, 'tcx>, intrinsic: Symbol, - _substs: SubstsRef<'tcx>, + _args: GenericArgsRef<'tcx>, args: &[mir::Operand<'tcx>], ret: CPlace<'tcx>, target: BasicBlock, @@ -117,8 +117,8 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( }); } - // simd_shuffle32(x: T, y: T, idx: [u32; 32]) -> U - _ if intrinsic.as_str().starts_with("simd_shuffle") => { + // simd_shuffle(x: T, y: T, idx: I) -> U + sym::simd_shuffle => { let (x, y, idx) = match args { [x, y, idx] => (x, y, idx), _ => { @@ -133,36 +133,26 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( return; } - // If this intrinsic is the older "simd_shuffleN" form, simply parse the integer. - // If there is no suffix, use the index array length. - let n: u16 = if intrinsic == sym::simd_shuffle { - // Make sure this is actually an array, since typeck only checks the length-suffixed - // version of this intrinsic. - let idx_ty = fx.monomorphize(idx.ty(fx.mir, fx.tcx)); - match idx_ty.kind() { - ty::Array(ty, len) if matches!(ty.kind(), ty::Uint(ty::UintTy::U32)) => len - .try_eval_target_usize(fx.tcx, ty::ParamEnv::reveal_all()) - .unwrap_or_else(|| { - span_bug!(span, "could not evaluate shuffle index array length") - }) - .try_into() - .unwrap(), - _ => { - fx.tcx.sess.span_err( - span, - format!( - "simd_shuffle index must be an array of `u32`, got `{}`", - idx_ty, - ), - ); - // Prevent verifier error - fx.bcx.ins().trap(TrapCode::UnreachableCodeReached); - return; - } + // Make sure this is actually an array, since typeck only checks the length-suffixed + // version of this intrinsic. + let idx_ty = fx.monomorphize(idx.ty(fx.mir, fx.tcx)); + let n: u16 = match idx_ty.kind() { + ty::Array(ty, len) if matches!(ty.kind(), ty::Uint(ty::UintTy::U32)) => len + .try_eval_target_usize(fx.tcx, ty::ParamEnv::reveal_all()) + .unwrap_or_else(|| { + span_bug!(span, "could not evaluate shuffle index array length") + }) + .try_into() + .unwrap(), + _ => { + fx.tcx.sess.span_err( + span, + format!("simd_shuffle index must be an array of `u32`, got `{}`", idx_ty), + ); + // Prevent verifier error + fx.bcx.ins().trap(TrapCode::UnreachableCodeReached); + return; } - } else { - // FIXME remove this case - intrinsic.as_str()["simd_shuffle".len()..].parse().unwrap() }; assert_eq!(x.layout(), y.layout()); @@ -179,7 +169,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( let indexes = { use rustc_middle::mir::interpret::*; let idx_const = crate::constant::mir_operand_get_const_val(fx, idx) - .expect("simd_shuffle* idx not const"); + .expect("simd_shuffle idx not const"); let idx_bytes = match idx_const { ConstValue::ByRef { alloc, offset } => { @@ -434,8 +424,36 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( }); } - sym::simd_round => { - intrinsic_args!(fx, args => (a); intrinsic); + sym::simd_fpow => { + intrinsic_args!(fx, args => (a, b); intrinsic); + + if !a.layout().ty.is_simd() { + report_simd_type_validation_error(fx, intrinsic, span, a.layout().ty); + return; + } + + simd_pair_for_each_lane(fx, a, b, ret, &|fx, lane_ty, _ret_lane_ty, a_lane, b_lane| { + match lane_ty.kind() { + ty::Float(FloatTy::F32) => fx.lib_call( + "powf", + vec![AbiParam::new(types::F32), AbiParam::new(types::F32)], + vec![AbiParam::new(types::F32)], + &[a_lane, b_lane], + )[0], + ty::Float(FloatTy::F64) => fx.lib_call( + "pow", + vec![AbiParam::new(types::F64), AbiParam::new(types::F64)], + vec![AbiParam::new(types::F64)], + &[a_lane, b_lane], + )[0], + _ => unreachable!("{:?}", lane_ty), + } + }); + } + + sym::simd_fpowi => { + intrinsic_args!(fx, args => (a, exp); intrinsic); + let exp = exp.load_scalar(fx); if !a.layout().ty.is_simd() { report_simd_type_validation_error(fx, intrinsic, span, a.layout().ty); @@ -448,22 +466,71 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( ret, &|fx, lane_ty, _ret_lane_ty, lane| match lane_ty.kind() { ty::Float(FloatTy::F32) => fx.lib_call( - "roundf", - vec![AbiParam::new(types::F32)], + "__powisf2", // compiler-builtins + vec![AbiParam::new(types::F32), AbiParam::new(types::I32)], vec![AbiParam::new(types::F32)], - &[lane], + &[lane, exp], )[0], ty::Float(FloatTy::F64) => fx.lib_call( - "round", + "__powidf2", // compiler-builtins + vec![AbiParam::new(types::F64), AbiParam::new(types::I32)], vec![AbiParam::new(types::F64)], - vec![AbiParam::new(types::F64)], - &[lane], + &[lane, exp], )[0], _ => unreachable!("{:?}", lane_ty), }, ); } + sym::simd_fsin + | sym::simd_fcos + | sym::simd_fexp + | sym::simd_fexp2 + | sym::simd_flog + | sym::simd_flog10 + | sym::simd_flog2 + | sym::simd_round => { + intrinsic_args!(fx, args => (a); intrinsic); + + if !a.layout().ty.is_simd() { + report_simd_type_validation_error(fx, intrinsic, span, a.layout().ty); + return; + } + + simd_for_each_lane(fx, a, ret, &|fx, lane_ty, _ret_lane_ty, lane| { + let lane_ty = match lane_ty.kind() { + ty::Float(FloatTy::F32) => types::F32, + ty::Float(FloatTy::F64) => types::F64, + _ => unreachable!("{:?}", lane_ty), + }; + let name = match (intrinsic, lane_ty) { + (sym::simd_fsin, types::F32) => "sinf", + (sym::simd_fsin, types::F64) => "sin", + (sym::simd_fcos, types::F32) => "cosf", + (sym::simd_fcos, types::F64) => "cos", + (sym::simd_fexp, types::F32) => "expf", + (sym::simd_fexp, types::F64) => "exp", + (sym::simd_fexp2, types::F32) => "exp2f", + (sym::simd_fexp2, types::F64) => "exp2", + (sym::simd_flog, types::F32) => "logf", + (sym::simd_flog, types::F64) => "log", + (sym::simd_flog10, types::F32) => "log10f", + (sym::simd_flog10, types::F64) => "log10", + (sym::simd_flog2, types::F32) => "log2f", + (sym::simd_flog2, types::F64) => "log2", + (sym::simd_round, types::F32) => "roundf", + (sym::simd_round, types::F64) => "round", + _ => unreachable!("{:?}", intrinsic), + }; + fx.lib_call( + name, + vec![AbiParam::new(lane_ty)], + vec![AbiParam::new(lane_ty)], + &[lane], + )[0] + }); + } + sym::simd_fabs | sym::simd_fsqrt | sym::simd_ceil | sym::simd_floor | sym::simd_trunc => { intrinsic_args!(fx, args => (a); intrinsic); @@ -488,7 +555,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( }); } - sym::simd_reduce_add_ordered | sym::simd_reduce_add_unordered => { + sym::simd_reduce_add_ordered => { intrinsic_args!(fx, args => (v, acc); intrinsic); let acc = acc.load_scalar(fx); @@ -507,7 +574,25 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( }); } - sym::simd_reduce_mul_ordered | sym::simd_reduce_mul_unordered => { + sym::simd_reduce_add_unordered => { + intrinsic_args!(fx, args => (v); intrinsic); + + // FIXME there must be no acc param for integer vectors + if !v.layout().ty.is_simd() { + report_simd_type_validation_error(fx, intrinsic, span, v.layout().ty); + return; + } + + simd_reduce(fx, v, None, ret, &|fx, lane_ty, a, b| { + if lane_ty.is_floating_point() { + fx.bcx.ins().fadd(a, b) + } else { + fx.bcx.ins().iadd(a, b) + } + }); + } + + sym::simd_reduce_mul_ordered => { intrinsic_args!(fx, args => (v, acc); intrinsic); let acc = acc.load_scalar(fx); @@ -526,6 +611,24 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( }); } + sym::simd_reduce_mul_unordered => { + intrinsic_args!(fx, args => (v); intrinsic); + + // FIXME there must be no acc param for integer vectors + if !v.layout().ty.is_simd() { + report_simd_type_validation_error(fx, intrinsic, span, v.layout().ty); + return; + } + + simd_reduce(fx, v, None, ret, &|fx, lane_ty, a, b| { + if lane_ty.is_floating_point() { + fx.bcx.ins().fmul(a, b) + } else { + fx.bcx.ins().imul(a, b) + } + }); + } + sym::simd_reduce_all => { intrinsic_args!(fx, args => (v); intrinsic); @@ -581,7 +684,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( simd_reduce(fx, v, None, ret, &|fx, _ty, a, b| fx.bcx.ins().bxor(a, b)); } - sym::simd_reduce_min => { + sym::simd_reduce_min | sym::simd_reduce_min_nanless => { intrinsic_args!(fx, args => (v); intrinsic); if !v.layout().ty.is_simd() { @@ -600,7 +703,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( }); } - sym::simd_reduce_max => { + sym::simd_reduce_max | sym::simd_reduce_max_nanless => { intrinsic_args!(fx, args => (v); intrinsic); if !v.layout().ty.is_simd() { @@ -878,6 +981,7 @@ pub(super) fn codegen_simd_intrinsic_call<'tcx>( fx.tcx.sess.span_err(span, format!("Unknown SIMD intrinsic {}", intrinsic)); // Prevent verifier error fx.bcx.ins().trap(TrapCode::UnreachableCodeReached); + return; } } let ret_block = fx.get_block(target); diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index 9966cc2ef3c12..d01ded8abaa52 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -88,7 +88,7 @@ mod prelude { }; pub(crate) use rustc_target::abi::{Abi, FieldIdx, Scalar, Size, VariantIdx, FIRST_VARIANT}; - pub(crate) use rustc_data_structures::fx::FxHashMap; + pub(crate) use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; pub(crate) use rustc_index::Idx; @@ -102,7 +102,7 @@ mod prelude { pub(crate) use cranelift_codegen::isa::{self, CallConv}; pub(crate) use cranelift_codegen::Context; pub(crate) use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; - pub(crate) use cranelift_module::{self, DataContext, FuncId, Linkage, Module}; + pub(crate) use cranelift_module::{self, DataDescription, FuncId, Linkage, Module}; pub(crate) use crate::abi::*; pub(crate) use crate::base::{codegen_operand, codegen_place}; @@ -223,7 +223,7 @@ impl CodegenBackend for CraneliftCodegenBackend { ongoing_codegen: Box, sess: &Session, _outputs: &OutputFilenames, - ) -> Result<(CodegenResults, FxHashMap), ErrorGuaranteed> { + ) -> Result<(CodegenResults, FxIndexMap), ErrorGuaranteed> { Ok(ongoing_codegen .downcast::() .unwrap() @@ -260,6 +260,13 @@ fn build_isa(sess: &Session, backend_config: &BackendConfig) -> Arc "elf_gd", BinaryFormat::Macho => "macho", @@ -268,8 +275,6 @@ fn build_isa(sess: &Session, backend_config: &BackendConfig) -> Arc( let rhs = in_rhs.load_scalar(fx); let b = fx.bcx.ins(); + // FIXME trap on overflow for the Unchecked versions let val = match bin_op { - BinOp::Add => b.iadd(lhs, rhs), - BinOp::Sub => b.isub(lhs, rhs), - BinOp::Mul => b.imul(lhs, rhs), + BinOp::Add | BinOp::AddUnchecked => b.iadd(lhs, rhs), + BinOp::Sub | BinOp::SubUnchecked => b.isub(lhs, rhs), + BinOp::Mul | BinOp::MulUnchecked => b.imul(lhs, rhs), BinOp::Div => { if signed { b.sdiv(lhs, rhs) @@ -149,16 +150,19 @@ pub(crate) fn codegen_int_binop<'tcx>( BinOp::BitXor => b.bxor(lhs, rhs), BinOp::BitAnd => b.band(lhs, rhs), BinOp::BitOr => b.bor(lhs, rhs), - BinOp::Shl => b.ishl(lhs, rhs), - BinOp::Shr => { + BinOp::Shl | BinOp::ShlUnchecked => b.ishl(lhs, rhs), + BinOp::Shr | BinOp::ShrUnchecked => { if signed { b.sshr(lhs, rhs) } else { b.ushr(lhs, rhs) } } + BinOp::Offset => unreachable!("Offset is not an integer operation"), // Compare binops handles by `codegen_binop`. - _ => unreachable!("{:?}({:?}, {:?})", bin_op, in_lhs.layout().ty, in_rhs.layout().ty), + BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => { + unreachable!("{:?}({:?}, {:?})", bin_op, in_lhs.layout().ty, in_rhs.layout().ty); + } }; CValue::by_val(val, in_lhs.layout()) @@ -266,7 +270,7 @@ pub(crate) fn codegen_checked_int_binop<'tcx>( _ => bug!("binop {:?} on checked int/uint lhs: {:?} rhs: {:?}", bin_op, in_lhs, in_rhs), }; - let out_layout = fx.layout_of(fx.tcx.mk_tup(&[in_lhs.layout().ty, fx.tcx.types.bool])); + let out_layout = fx.layout_of(Ty::new_tup(fx.tcx, &[in_lhs.layout().ty, fx.tcx.types.bool])); CValue::by_val_pair(res, has_overflow, out_layout) } diff --git a/compiler/rustc_codegen_cranelift/src/pretty_clif.rs b/compiler/rustc_codegen_cranelift/src/pretty_clif.rs index 1007b33eca42d..0ead50c34eac4 100644 --- a/compiler/rustc_codegen_cranelift/src/pretty_clif.rs +++ b/compiler/rustc_codegen_cranelift/src/pretty_clif.rs @@ -9,7 +9,7 @@ //! //! function u0:22(i64) -> i8, i8 system_v { //! ; symbol _ZN97_$LT$example..IsNotEmpty$u20$as$u20$mini_core..FnOnce$LT$$LP$$RF$$RF$$u5b$u16$u5d$$C$$RP$$GT$$GT$9call_once17hd517c453d67c0915E -//! ; instance Instance { def: Item(WithOptConstParam { did: DefId(0:42 ~ example[4e51]::{impl#0}::call_once), const_param_did: None }), substs: [ReErased, ReErased] } +//! ; instance Instance { def: Item(WithOptConstParam { did: DefId(0:42 ~ example[4e51]::{impl#0}::call_once), const_param_did: None }), args: [ReErased, ReErased] } //! ; abi FnAbi { args: [ArgAbi { layout: TyAndLayout { ty: IsNotEmpty, layout: Layout { size: Size(0 bytes), align: AbiAndPrefAlign { abi: Align(1 bytes), pref: Align(8 bytes) }, abi: Aggregate { sized: true }, fields: Arbitrary { offsets: [], memory_index: [] }, largest_niche: None, variants: Single { index: 0 } } }, mode: Ignore }, ArgAbi { layout: TyAndLayout { ty: &&[u16], layout: Layout { size: Size(8 bytes), align: AbiAndPrefAlign { abi: Align(8 bytes), pref: Align(8 bytes) }, abi: Scalar(Initialized { value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 }), fields: Primitive, largest_niche: Some(Niche { offset: Size(0 bytes), value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 }), variants: Single { index: 0 } } }, mode: Direct(ArgAttributes { regular: NonNull | NoUndef, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: Some(Align(8 bytes)) }) }], ret: ArgAbi { layout: TyAndLayout { ty: (u8, u8), layout: Layout { size: Size(2 bytes), align: AbiAndPrefAlign { abi: Align(1 bytes), pref: Align(8 bytes) }, abi: ScalarPair(Initialized { value: Int(I8, false), valid_range: 0..=255 }, Initialized { value: Int(I8, false), valid_range: 0..=255 }), fields: Arbitrary { offsets: [Size(0 bytes), Size(1 bytes)], memory_index: [0, 1] }, largest_niche: None, variants: Single { index: 0 } } }, mode: Pair(ArgAttributes { regular: NoUndef, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: None }, ArgAttributes { regular: NoUndef, arg_ext: None, pointee_size: Size(0 bytes), pointee_align: None }) }, c_variadic: false, fixed_count: 1, conv: Rust, can_unwind: false } //! //! ; kind loc.idx param pass mode ty @@ -25,7 +25,7 @@ //! //! ss0 = explicit_slot 16 //! sig0 = (i64, i64) -> i8, i8 system_v -//! fn0 = colocated u0:23 sig0 ; Instance { def: Item(WithOptConstParam { did: DefId(0:46 ~ example[4e51]::{impl#1}::call_mut), const_param_did: None }), substs: [ReErased, ReErased] } +//! fn0 = colocated u0:23 sig0 ; Instance { def: Item(WithOptConstParam { did: DefId(0:46 ~ example[4e51]::{impl#1}::call_mut), const_param_did: None }), args: [ReErased, ReErased] } //! //! block0(v0: i64): //! nop @@ -225,10 +225,10 @@ pub(crate) fn write_ir_file( let res = std::fs::File::create(clif_file_name).and_then(|mut file| write(&mut file)); if let Err(err) = res { // Using early_warn as no Session is available here - rustc_session::early_warn( + let handler = rustc_session::EarlyErrorHandler::new( rustc_session::config::ErrorOutputType::default(), - format!("error writing ir file: {}", err), ); + handler.early_warn(format!("error writing ir file: {}", err)); } } @@ -261,7 +261,7 @@ pub(crate) fn write_clif_file( impl fmt::Debug for FunctionCx<'_, '_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "{:?}", self.instance.substs)?; + writeln!(f, "{:?}", self.instance.args)?; writeln!(f, "{:?}", self.local_map)?; let mut clif = String::new(); diff --git a/compiler/rustc_codegen_cranelift/src/trap.rs b/compiler/rustc_codegen_cranelift/src/trap.rs index 82a2ec5795496..2fb0c2164c309 100644 --- a/compiler/rustc_codegen_cranelift/src/trap.rs +++ b/compiler/rustc_codegen_cranelift/src/trap.rs @@ -30,5 +30,9 @@ fn codegen_print(fx: &mut FunctionCx<'_, '_, '_>, msg: &str) { /// Trap code: user65535 pub(crate) fn trap_unimplemented(fx: &mut FunctionCx<'_, '_, '_>, msg: impl AsRef) { codegen_print(fx, msg.as_ref()); + + let one = fx.bcx.ins().iconst(types::I32, 1); + fx.lib_call("exit", vec![AbiParam::new(types::I32)], vec![], &[one]); + fx.bcx.ins().trap(TrapCode::User(!0)); } diff --git a/compiler/rustc_codegen_cranelift/src/unsize.rs b/compiler/rustc_codegen_cranelift/src/unsize.rs index ff0e12410e703..6aeba13f63949 100644 --- a/compiler/rustc_codegen_cranelift/src/unsize.rs +++ b/compiler/rustc_codegen_cranelift/src/unsize.rs @@ -1,6 +1,6 @@ -//! Codegen of the [`PointerCast::Unsize`] operation. +//! Codegen of the [`PointerCoercion::Unsize`] operation. //! -//! [`PointerCast::Unsize`]: `rustc_middle::ty::adjustment::PointerCast::Unsize` +//! [`PointerCoercion::Unsize`]: `rustc_middle::ty::adjustment::PointerCoercion::Unsize` use crate::prelude::*; diff --git a/compiler/rustc_codegen_cranelift/src/value_and_place.rs b/compiler/rustc_codegen_cranelift/src/value_and_place.rs index b1fda6ff21337..ff95141ce90fb 100644 --- a/compiler/rustc_codegen_cranelift/src/value_and_place.rs +++ b/compiler/rustc_codegen_cranelift/src/value_and_place.rs @@ -2,6 +2,8 @@ use crate::prelude::*; +use rustc_middle::ty::FnSig; + use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir::immediates::Offset32; @@ -160,6 +162,7 @@ impl<'tcx> CValue<'tcx> { } /// Load a value with layout.abi of scalar + #[track_caller] pub(crate) fn load_scalar(self, fx: &mut FunctionCx<'_, '_, 'tcx>) -> Value { let layout = self.1; match self.0 { @@ -182,6 +185,7 @@ impl<'tcx> CValue<'tcx> { } /// Load a value pair with layout.abi of scalar pair + #[track_caller] pub(crate) fn load_scalar_pair(self, fx: &mut FunctionCx<'_, '_, 'tcx>) -> (Value, Value) { let layout = self.1; match self.0 { @@ -258,6 +262,27 @@ impl<'tcx> CValue<'tcx> { } } + /// Like [`CValue::value_lane`] except allowing a dynamically calculated lane index. + pub(crate) fn value_lane_dyn( + self, + fx: &mut FunctionCx<'_, '_, 'tcx>, + lane_idx: Value, + ) -> CValue<'tcx> { + let layout = self.1; + assert!(layout.ty.is_simd()); + let (_lane_count, lane_ty) = layout.ty.simd_size_and_type(fx.tcx); + let lane_layout = fx.layout_of(lane_ty); + match self.0 { + CValueInner::ByVal(_) | CValueInner::ByValPair(_, _) => unreachable!(), + CValueInner::ByRef(ptr, None) => { + let field_offset = fx.bcx.ins().imul_imm(lane_idx, lane_layout.size.bytes() as i64); + let field_ptr = ptr.offset_value(fx, field_offset); + CValue::by_ref(field_ptr, lane_layout) + } + CValueInner::ByRef(_, Some(_)) => unreachable!(), + } + } + /// If `ty` is signed, `const_val` must already be sign extended. pub(crate) fn const_val( fx: &mut FunctionCx<'_, '_, 'tcx>, @@ -562,17 +587,25 @@ impl<'tcx> CPlace<'tcx> { let dst_layout = self.layout(); match self.inner { CPlaceInner::Var(_local, var) => { - let data = CValue(from.0, dst_layout).load_scalar(fx); + let data = match from.1.abi { + Abi::Scalar(_) => CValue(from.0, dst_layout).load_scalar(fx), + _ => { + let (ptr, meta) = from.force_stack(fx); + assert!(meta.is_none()); + CValue(CValueInner::ByRef(ptr, None), dst_layout).load_scalar(fx) + } + }; let dst_ty = fx.clif_type(self.layout().ty).unwrap(); transmute_scalar(fx, var, data, dst_ty); } CPlaceInner::VarPair(_local, var1, var2) => { - let (data1, data2) = if from.layout().ty == dst_layout.ty { - CValue(from.0, dst_layout).load_scalar_pair(fx) - } else { - let (ptr, meta) = from.force_stack(fx); - assert!(meta.is_none()); - CValue(CValueInner::ByRef(ptr, None), dst_layout).load_scalar_pair(fx) + let (data1, data2) = match from.1.abi { + Abi::ScalarPair(_, _) => CValue(from.0, dst_layout).load_scalar_pair(fx), + _ => { + let (ptr, meta) = from.force_stack(fx); + assert!(meta.is_none()); + CValue(CValueInner::ByRef(ptr, None), dst_layout).load_scalar_pair(fx) + } }; let (dst_ty1, dst_ty2) = fx.clif_pair_type(self.layout().ty).unwrap(); transmute_scalar(fx, var1, data1, dst_ty1); @@ -586,30 +619,38 @@ impl<'tcx> CPlace<'tcx> { let mut flags = MemFlags::new(); flags.set_notrap(); - match from.layout().abi { - Abi::Scalar(_) => { - let val = from.load_scalar(fx); - to_ptr.store(fx, val, flags); - return; - } - Abi::ScalarPair(a_scalar, b_scalar) => { - let (value, extra) = from.load_scalar_pair(fx); - let b_offset = scalar_pair_calculate_b_offset(fx.tcx, a_scalar, b_scalar); - to_ptr.store(fx, value, flags); - to_ptr.offset(fx, b_offset).store(fx, extra, flags); - return; - } - _ => {} - } match from.0 { CValueInner::ByVal(val) => { to_ptr.store(fx, val, flags); } - CValueInner::ByValPair(_, _) => { - bug!("Non ScalarPair abi {:?} for ByValPair CValue", dst_layout.abi); - } + CValueInner::ByValPair(val1, val2) => match from.layout().abi { + Abi::ScalarPair(a_scalar, b_scalar) => { + let b_offset = + scalar_pair_calculate_b_offset(fx.tcx, a_scalar, b_scalar); + to_ptr.store(fx, val1, flags); + to_ptr.offset(fx, b_offset).store(fx, val2, flags); + } + _ => bug!("Non ScalarPair abi {:?} for ByValPair CValue", dst_layout.abi), + }, CValueInner::ByRef(from_ptr, None) => { + match from.layout().abi { + Abi::Scalar(_) => { + let val = from.load_scalar(fx); + to_ptr.store(fx, val, flags); + return; + } + Abi::ScalarPair(a_scalar, b_scalar) => { + let b_offset = + scalar_pair_calculate_b_offset(fx.tcx, a_scalar, b_scalar); + let (val1, val2) = from.load_scalar_pair(fx); + to_ptr.store(fx, val1, flags); + to_ptr.offset(fx, b_offset).store(fx, val2, flags); + return; + } + _ => {} + } + let from_addr = from_ptr.get_addr(fx); let to_addr = to_ptr.get_addr(fx); let src_layout = from.1; @@ -794,11 +835,42 @@ pub(crate) fn assert_assignable<'tcx>( ParamEnv::reveal_all(), from_ty.fn_sig(fx.tcx), ); + let FnSig { + inputs_and_output: types_from, + c_variadic: c_variadic_from, + unsafety: unsafety_from, + abi: abi_from, + } = from_sig; let to_sig = fx .tcx .normalize_erasing_late_bound_regions(ParamEnv::reveal_all(), to_ty.fn_sig(fx.tcx)); + let FnSig { + inputs_and_output: types_to, + c_variadic: c_variadic_to, + unsafety: unsafety_to, + abi: abi_to, + } = to_sig; + let mut types_from = types_from.iter(); + let mut types_to = types_to.iter(); + loop { + match (types_from.next(), types_to.next()) { + (Some(a), Some(b)) => assert_assignable(fx, a, b, limit - 1), + (None, None) => break, + (Some(_), None) | (None, Some(_)) => panic!("{:#?}/{:#?}", from_ty, to_ty), + } + } + assert_eq!( + c_variadic_from, c_variadic_to, + "Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}", + from_sig, to_sig, fx, + ); + assert_eq!( + unsafety_from, unsafety_to, + "Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}", + from_sig, to_sig, fx, + ); assert_eq!( - from_sig, to_sig, + abi_from, abi_to, "Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}", from_sig, to_sig, fx, ); @@ -829,11 +901,11 @@ pub(crate) fn assert_assignable<'tcx>( } } } - (&ty::Adt(adt_def_a, substs_a), &ty::Adt(adt_def_b, substs_b)) + (&ty::Adt(adt_def_a, args_a), &ty::Adt(adt_def_b, args_b)) if adt_def_a.did() == adt_def_b.did() => { - let mut types_a = substs_a.types(); - let mut types_b = substs_b.types(); + let mut types_a = args_a.types(); + let mut types_b = args_b.types(); loop { match (types_a.next(), types_b.next()) { (Some(a), Some(b)) => assert_assignable(fx, a, b, limit - 1), @@ -843,11 +915,11 @@ pub(crate) fn assert_assignable<'tcx>( } } (ty::Array(a, _), ty::Array(b, _)) => assert_assignable(fx, *a, *b, limit - 1), - (&ty::Closure(def_id_a, substs_a), &ty::Closure(def_id_b, substs_b)) + (&ty::Closure(def_id_a, args_a), &ty::Closure(def_id_b, args_b)) if def_id_a == def_id_b => { - let mut types_a = substs_a.types(); - let mut types_b = substs_b.types(); + let mut types_a = args_a.types(); + let mut types_b = args_b.types(); loop { match (types_a.next(), types_b.next()) { (Some(a), Some(b)) => assert_assignable(fx, a, b, limit - 1), diff --git a/compiler/rustc_codegen_cranelift/test.sh b/compiler/rustc_codegen_cranelift/test.sh index 13e7784539d5a..6357eebf02696 100755 --- a/compiler/rustc_codegen_cranelift/test.sh +++ b/compiler/rustc_codegen_cranelift/test.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -exec ./y.rs test "$@" +exec ./y.sh test "$@" diff --git a/compiler/rustc_codegen_cranelift/y.rs b/compiler/rustc_codegen_cranelift/y.rs index a68a10500f508..e806a64d94344 100755 --- a/compiler/rustc_codegen_cranelift/y.rs +++ b/compiler/rustc_codegen_cranelift/y.rs @@ -1,35 +1,6 @@ #!/usr/bin/env bash #![deny(unsafe_code)] /*This line is ignored by bash # This block is ignored by rustc -set -e -echo "[BUILD] y.rs" 1>&2 -rustc $0 -o ${0/.rs/.bin} -Cdebuginfo=1 --edition 2021 -exec ${0/.rs/.bin} $@ +echo "Warning: y.rs is a deprecated alias for y.sh" 1>&2 +exec ./y.sh "$@" */ - -#![warn(rust_2018_idioms)] -#![warn(unused_lifetimes)] -#![warn(unreachable_pub)] - -//! The build system for cg_clif -//! -//! # Manual compilation -//! -//! If your system doesn't support shell scripts you can manually compile and run this file using -//! for example: -//! -//! ```shell -//! $ rustc y.rs -o y.bin -//! $ ./y.bin -//! ``` -//! -//! # Naming -//! -//! The name `y.rs` was chosen to not conflict with rustc's `x.py`. - -#[path = "build_system/mod.rs"] -mod build_system; - -fn main() { - build_system::main(); -} diff --git a/compiler/rustc_codegen_cranelift/y.sh b/compiler/rustc_codegen_cranelift/y.sh new file mode 100755 index 0000000000000..bc925a23e2a88 --- /dev/null +++ b/compiler/rustc_codegen_cranelift/y.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +echo "[BUILD] build system" 1>&2 +rustc build_system/main.rs -o y.bin -Cdebuginfo=1 --edition 2021 +exec ./y.bin "$@" diff --git a/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml b/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml index 42fb35e738ffd..556c644483320 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml @@ -20,9 +20,9 @@ jobs: matrix: libgccjit_version: - { gcc: "libgccjit.so", artifacts_branch: "master" } - commands: [ - "--test-successful-rustc --nb-parts 2 --current-part 0", - "--test-successful-rustc --nb-parts 2 --current-part 1", + cargo_runner: [ + "sde -future -rtm_mode full --", + "", ] steps: @@ -36,6 +36,20 @@ jobs: - name: Install packages run: sudo apt-get install ninja-build ripgrep + - name: Install Intel Software Development Emulator + if: ${{ matrix.cargo_runner }} + run: | + mkdir intel-sde + cd intel-sde + dir=sde-external-9.14.0-2022-10-25-lin + file=$dir.tar.xz + wget https://downloadmirror.intel.com/751535/$file + tar xvf $file + sudo mkdir /usr/share/intel-sde + sudo cp -r $dir/* /usr/share/intel-sde + sudo ln -s /usr/share/intel-sde/sde /usr/bin/sde + sudo ln -s /usr/share/intel-sde/sde64 /usr/bin/sde64 + - name: Download artifact uses: dawidd6/action-download-artifact@v2 with: @@ -91,6 +105,10 @@ jobs: ./prepare_build.sh ./build.sh --release --release-sysroot cargo test + + - name: Clean + if: ${{ !matrix.cargo_runner }} + run: | ./clean_all.sh - name: Prepare dependencies @@ -107,10 +125,18 @@ jobs: args: --release - name: Run tests + if: ${{ !matrix.cargo_runner }} run: | ./test.sh --release --clean --release-sysroot --build-sysroot --mini-tests --std-tests --test-libcore - name: Run stdarch tests + if: ${{ !matrix.cargo_runner }} run: | cd build_sysroot/sysroot_src/library/stdarch/ CHANNEL=release TARGET=x86_64-unknown-linux-gnu ../../../../cargo.sh test + + - name: Run stdarch tests + if: ${{ matrix.cargo_runner }} + run: | + cd build_sysroot/sysroot_src/library/stdarch/ + STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu ../../../../cargo.sh test -- --skip rtm --skip tbm --skip sse4a diff --git a/compiler/rustc_codegen_gcc/.gitignore b/compiler/rustc_codegen_gcc/.gitignore index 12ed56675639c..c5ed7de200c24 100644 --- a/compiler/rustc_codegen_gcc/.gitignore +++ b/compiler/rustc_codegen_gcc/.gitignore @@ -23,3 +23,5 @@ benchmarks tools/llvm-project tools/llvmint tools/llvmint-2 +# The `llvm` folder is generated by the `tools/generate_intrinsics.py` script to update intrinsics. +llvm diff --git a/compiler/rustc_codegen_gcc/Cargo.lock b/compiler/rustc_codegen_gcc/Cargo.lock index 0f2e152f8ce56..1c8754bf675ea 100644 --- a/compiler/rustc_codegen_gcc/Cargo.lock +++ b/compiler/rustc_codegen_gcc/Cargo.lock @@ -35,7 +35,7 @@ dependencies = [ [[package]] name = "gccjit" version = "1.0.0" -source = "git+https://github.com/antoyo/gccjit.rs#fe242b7eb26980e6c78859d51c8d4cc1e43381a3" +source = "git+https://github.com/antoyo/gccjit.rs#d6e52626cfc6f487094a5d5ac66302baf3439984" dependencies = [ "gccjit_sys", ] @@ -43,7 +43,7 @@ dependencies = [ [[package]] name = "gccjit_sys" version = "0.0.1" -source = "git+https://github.com/antoyo/gccjit.rs#fe242b7eb26980e6c78859d51c8d4cc1e43381a3" +source = "git+https://github.com/antoyo/gccjit.rs#d6e52626cfc6f487094a5d5ac66302baf3439984" dependencies = [ "libc", ] diff --git a/compiler/rustc_codegen_gcc/Readme.md b/compiler/rustc_codegen_gcc/Readme.md index bb74194389254..a93637d9038dc 100644 --- a/compiler/rustc_codegen_gcc/Readme.md +++ b/compiler/rustc_codegen_gcc/Readme.md @@ -193,7 +193,7 @@ Using git-subtree with `rustc` requires a patched git to make it work. The PR that is needed is [here](https://github.com/gitgitgadget/git/pull/493). Use the following instructions to install it: -``` +```bash git clone git@github.com:tqc/git.git cd git git checkout tqc/subtree @@ -204,6 +204,21 @@ make cp git-subtree ~/bin ``` +Then, do a sync with this command: + +```bash +PATH="$HOME/bin:$PATH" ~/bin/git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name +cd ../rustc_codegen_gcc +git checkout master +git pull +git checkout sync_branch_name +git merge master +``` + +TODO: write a script that does the above. + +https://rust-lang.zulipchat.com/#narrow/stream/301329-t-devtools/topic/subtree.20madness/near/258877725 + ### How to use [mem-trace](https://github.com/antoyo/mem-trace) `rustc` needs to be built without `jemalloc` so that `mem-trace` can overload `malloc` since `jemalloc` is linked statically, so a `LD_PRELOAD`-ed library won't a chance to intercept the calls to `malloc`. diff --git a/compiler/rustc_codegen_gcc/build_sysroot/Cargo.toml b/compiler/rustc_codegen_gcc/build_sysroot/Cargo.toml index cfadf47cc3f86..a84f86a821898 100644 --- a/compiler/rustc_codegen_gcc/build_sysroot/Cargo.toml +++ b/compiler/rustc_codegen_gcc/build_sysroot/Cargo.toml @@ -9,6 +9,7 @@ compiler_builtins = "0.1" alloc = { path = "./sysroot_src/library/alloc" } std = { path = "./sysroot_src/library/std", features = ["panic_unwind", "backtrace"] } test = { path = "./sysroot_src/library/test" } +proc_macro = { path = "./sysroot_src/library/proc_macro" } [patch.crates-io] rustc-std-workspace-core = { path = "./sysroot_src/library/rustc-std-workspace-core" } diff --git a/compiler/rustc_codegen_gcc/build_sysroot/prepare_sysroot_src.sh b/compiler/rustc_codegen_gcc/build_sysroot/prepare_sysroot_src.sh index 56768bbf1d015..71b3876bac2cf 100755 --- a/compiler/rustc_codegen_gcc/build_sysroot/prepare_sysroot_src.sh +++ b/compiler/rustc_codegen_gcc/build_sysroot/prepare_sysroot_src.sh @@ -29,10 +29,10 @@ git config user.name || git config user.name "None" git commit -m "Initial commit" -q for file in $(ls ../../patches/ | grep -v patcha); do -echo "[GIT] apply" $file -git apply ../../patches/$file -git add -A -git commit --no-gpg-sign -m "Patch $file" + echo "[GIT] apply" $file + git apply ../../patches/$file + git add -A + git commit --no-gpg-sign -m "Patch $file" done popd diff --git a/compiler/rustc_codegen_gcc/example/alloc_system.rs b/compiler/rustc_codegen_gcc/example/alloc_system.rs index 046903fe5aca4..3deef419f42e1 100644 --- a/compiler/rustc_codegen_gcc/example/alloc_system.rs +++ b/compiler/rustc_codegen_gcc/example/alloc_system.rs @@ -10,13 +10,16 @@ #[cfg(any(target_arch = "x86", target_arch = "arm", target_arch = "mips", + target_arch = "mips32r6", target_arch = "powerpc", + target_arch = "csky" target_arch = "powerpc64"))] const MIN_ALIGN: usize = 8; #[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "loongarch64", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "s390x", target_arch = "sparc64"))] const MIN_ALIGN: usize = 16; diff --git a/compiler/rustc_codegen_gcc/example/mini_core.rs b/compiler/rustc_codegen_gcc/example/mini_core.rs index 637b8dc53fefd..0cd7e6047c20a 100644 --- a/compiler/rustc_codegen_gcc/example/mini_core.rs +++ b/compiler/rustc_codegen_gcc/example/mini_core.rs @@ -451,6 +451,9 @@ pub unsafe fn drop_in_place(to_drop: *mut T) { drop_in_place(to_drop); } +#[lang = "unpin"] +pub auto trait Unpin {} + #[lang = "deref"] pub trait Deref { type Target: ?Sized; @@ -488,9 +491,23 @@ pub struct Box(Unique, A); impl, U: ?Sized, A: Allocator> CoerceUnsized> for Box {} +impl Box { + pub fn new(val: T) -> Box { + unsafe { + let size = intrinsics::size_of::(); + let ptr = libc::malloc(size); + intrinsics::copy(&val as *const T as *const u8, ptr, size); + Box(Unique { pointer: NonNull(ptr as *const T), _marker: PhantomData }, Global) + } + } +} + impl Drop for Box { fn drop(&mut self) { - // drop is currently performed by compiler. + // inner value is dropped by compiler. + unsafe { + libc::free(self.0.pointer.0 as *mut u8); + } } } @@ -507,11 +524,6 @@ unsafe fn allocate(size: usize, _align: usize) -> *mut u8 { libc::malloc(size) } -#[lang = "box_free"] -unsafe fn box_free(ptr: Unique, _alloc: ()) { - libc::free(ptr.pointer.0 as *mut u8); -} - #[lang = "drop"] pub trait Drop { fn drop(&mut self); diff --git a/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs b/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs index cff26077740b0..b93d685970631 100644 --- a/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs +++ b/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs @@ -168,6 +168,9 @@ fn main() { world as Box; assert_eq!(intrinsics::bitreverse(0b10101000u8), 0b00010101u8); + assert_eq!(intrinsics::bitreverse(0xddccu16), 0x33bbu16); + assert_eq!(intrinsics::bitreverse(0xffee_ddccu32), 0x33bb77ffu32); + assert_eq!(intrinsics::bitreverse(0x1234_5678_ffee_ddccu64), 0x33bb77ff1e6a2c48u64); assert_eq!(intrinsics::bswap(0xabu8), 0xabu8); assert_eq!(intrinsics::bswap(0xddccu16), 0xccddu16); diff --git a/compiler/rustc_codegen_gcc/example/std_example.rs b/compiler/rustc_codegen_gcc/example/std_example.rs index 5c171c49fd194..18f2ddcde126b 100644 --- a/compiler/rustc_codegen_gcc/example/std_example.rs +++ b/compiler/rustc_codegen_gcc/example/std_example.rs @@ -58,6 +58,7 @@ fn main() { assert_eq!(0b0000000000000000000000000010000010000000000000000000000000000000_0000000000100000000000000000000000001000000000000100000000000000u128.leading_zeros(), 26); assert_eq!(0b0000000000000000000000000010000000000000000000000000000000000000_0000000000000000000000000000000000001000000000000000000010000000u128.trailing_zeros(), 7); + assert_eq!(0x1234_5678_ffee_ddcc_1234_5678_ffee_ddccu128.reverse_bits(), 0x33bb77ff1e6a2c4833bb77ff1e6a2c48u128); let _d = 0i128.checked_div(2i128); let _d = 0u128.checked_div(2u128); diff --git a/compiler/rustc_codegen_gcc/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/failing-ui-tests.txt index 8539e27ea6a58..801464daae9a5 100644 --- a/compiler/rustc_codegen_gcc/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/failing-ui-tests.txt @@ -54,8 +54,8 @@ tests/ui/issues/issue-40883.rs tests/ui/issues/issue-43853.rs tests/ui/issues/issue-47364.rs tests/ui/macros/rfc-2011-nicer-assert-messages/assert-without-captures-does-not-create-unnecessary-code.rs -tests/ui/rfc-2091-track-caller/std-panic-locations.rs -tests/ui/rfcs/rfc1857-drop-order.rs +tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs +tests/ui/rfcs/rfc-1857-stabilize-drop-order/drop-order.rs tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs tests/ui/simd/issue-89193.rs @@ -66,3 +66,5 @@ tests/ui/generator/panic-safe.rs tests/ui/issues/issue-14875.rs tests/ui/issues/issue-29948.rs tests/ui/panic-while-printing.rs +tests/ui/enum-discriminant/get_discr.rs +tests/ui/panics/nested_panic_caught.rs diff --git a/compiler/rustc_codegen_gcc/messages.ftl b/compiler/rustc_codegen_gcc/messages.ftl index 0a94a08f8dca8..2fd0daee3e73f 100644 --- a/compiler/rustc_codegen_gcc/messages.ftl +++ b/compiler/rustc_codegen_gcc/messages.ftl @@ -1,68 +1,11 @@ -codegen_gcc_unwinding_inline_asm = - GCC backend does not support unwinding from inline asm +codegen_gcc_invalid_minimum_alignment = + invalid minimum global alignment: {$err} codegen_gcc_lto_not_supported = LTO is not supported. You may get a linker error. -codegen_gcc_invalid_monomorphization_basic_integer = - invalid monomorphization of `{$name}` intrinsic: expected basic integer type, found `{$ty}` - -codegen_gcc_invalid_monomorphization_invalid_float_vector = - invalid monomorphization of `{$name}` intrinsic: unsupported element type `{$elem_ty}` of floating-point vector `{$vec_ty}` - -codegen_gcc_invalid_monomorphization_not_float = - invalid monomorphization of `{$name}` intrinsic: `{$ty}` is not a floating-point type - -codegen_gcc_invalid_monomorphization_unrecognized = - invalid monomorphization of `{$name}` intrinsic: unrecognized intrinsic `{$name}` - -codegen_gcc_invalid_monomorphization_expected_signed_unsigned = - invalid monomorphization of `{$name}` intrinsic: expected element type `{$elem_ty}` of vector type `{$vec_ty}` to be a signed or unsigned integer type - -codegen_gcc_invalid_monomorphization_unsupported_element = - invalid monomorphization of `{$name}` intrinsic: unsupported {$name} from `{$in_ty}` with element `{$elem_ty}` to `{$ret_ty}` - -codegen_gcc_invalid_monomorphization_invalid_bitmask = - invalid monomorphization of `{$name}` intrinsic: invalid bitmask `{$ty}`, expected `u{$expected_int_bits}` or `[u8; {$expected_bytes}]` - -codegen_gcc_invalid_monomorphization_simd_shuffle = - invalid monomorphization of `{$name}` intrinsic: simd_shuffle index must be an array of `u32`, got `{$ty}` - -codegen_gcc_invalid_monomorphization_expected_simd = - invalid monomorphization of `{$name}` intrinsic: expected SIMD {$expected_ty} type, found non-SIMD `{$found_ty}` - -codegen_gcc_invalid_monomorphization_mask_type = - invalid monomorphization of `{$name}` intrinsic: mask element type is `{$ty}`, expected `i_` - -codegen_gcc_invalid_monomorphization_return_length = - invalid monomorphization of `{$name}` intrinsic: expected return type of length {$in_len}, found `{$ret_ty}` with length {$out_len} - -codegen_gcc_invalid_monomorphization_return_length_input_type = - invalid monomorphization of `{$name}` intrinsic: expected return type with length {$in_len} (same as input type `{$in_ty}`), found `{$ret_ty}` with length {$out_len} - -codegen_gcc_invalid_monomorphization_return_element = - invalid monomorphization of `{$name}` intrinsic: expected return element type `{$in_elem}` (element of input `{$in_ty}`), found `{$ret_ty}` with element type `{$out_ty}` - -codegen_gcc_invalid_monomorphization_return_type = - invalid monomorphization of `{$name}` intrinsic: expected return type `{$in_elem}` (element of input `{$in_ty}`), found `{$ret_ty}` - -codegen_gcc_invalid_monomorphization_inserted_type = - invalid monomorphization of `{$name}` intrinsic: expected inserted type `{$in_elem}` (element of input `{$in_ty}`), found `{$out_ty}` - -codegen_gcc_invalid_monomorphization_return_integer_type = - invalid monomorphization of `{$name}` intrinsic: expected return type with integer elements, found `{$ret_ty}` with non-integer `{$out_ty}` - -codegen_gcc_invalid_monomorphization_mismatched_lengths = - invalid monomorphization of `{$name}` intrinsic: mismatched lengths: mask length `{$m_len}` != other vector length `{$v_len}` - -codegen_gcc_invalid_monomorphization_unsupported_cast = - invalid monomorphization of `{$name}` intrinsic: unsupported cast from `{$in_ty}` with element `{$in_elem}` to `{$ret_ty}` with element `{$out_elem}` - -codegen_gcc_invalid_monomorphization_unsupported_operation = - invalid monomorphization of `{$name}` intrinsic: unsupported operation on `{$in_ty}` with element `{$in_elem}` - -codegen_gcc_invalid_minimum_alignment = - invalid minimum global alignment: {$err} - codegen_gcc_tied_target_features = the target features {$features} must all be either enabled or disabled together .help = add the missing features in a `target_feature` attribute + +codegen_gcc_unwinding_inline_asm = + GCC backend does not support unwinding from inline asm diff --git a/compiler/rustc_codegen_gcc/patches/0023-core-Ignore-failing-tests.patch b/compiler/rustc_codegen_gcc/patches/0023-core-Ignore-failing-tests.patch deleted file mode 100644 index ee5ba449fb8e6..0000000000000 --- a/compiler/rustc_codegen_gcc/patches/0023-core-Ignore-failing-tests.patch +++ /dev/null @@ -1,49 +0,0 @@ -From dd82e95c9de212524e14fc60155de1ae40156dfc Mon Sep 17 00:00:00 2001 -From: bjorn3 -Date: Sun, 24 Nov 2019 15:34:06 +0100 -Subject: [PATCH] [core] Ignore failing tests - ---- - library/core/tests/iter.rs | 4 ++++ - library/core/tests/num/bignum.rs | 10 ++++++++++ - library/core/tests/num/mod.rs | 5 +++-- - library/core/tests/time.rs | 1 + - 4 files changed, 18 insertions(+), 2 deletions(-) - -diff --git a/library/core/tests/array.rs b/library/core/tests/array.rs -index 4bc44e9..8e3c7a4 100644 ---- a/library/core/tests/array.rs -+++ b/library/core/tests/array.rs -@@ -242,6 +242,7 @@ fn iterator_drops() { - assert_eq!(i.get(), 5); - } - -+/* - // This test does not work on targets without panic=unwind support. - // To work around this problem, test is marked is should_panic, so it will - // be automagically skipped on unsuitable targets, such as -@@ -283,6 +284,7 @@ fn array_default_impl_avoids_leaks_on_panic() { - assert_eq!(COUNTER.load(Relaxed), 0); - panic!("test succeeded") - } -+*/ - - #[test] - fn empty_array_is_always_default() { -@@ -304,6 +304,7 @@ fn array_map() { - assert_eq!(b, [1, 2, 3]); - } - -+/* - // See note on above test for why `should_panic` is used. - #[test] - #[should_panic(expected = "test succeeded")] -@@ -332,6 +333,7 @@ fn array_map_drop_safety() { - assert_eq!(DROPPED.load(Ordering::SeqCst), num_to_create); - panic!("test succeeded") - } -+*/ - - #[test] - fn cell_allows_array_cycle() { --- 2.21.0 (Apple Git-122) diff --git a/compiler/rustc_codegen_gcc/rust-toolchain b/compiler/rustc_codegen_gcc/rust-toolchain index 933ecd45baadb..ebb04d0069cf5 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain +++ b/compiler/rustc_codegen_gcc/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-03-02" +channel = "nightly-2023-06-19" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] diff --git a/compiler/rustc_codegen_gcc/src/allocator.rs b/compiler/rustc_codegen_gcc/src/allocator.rs index 4bad33ee879ee..edd7ab722f617 100644 --- a/compiler/rustc_codegen_gcc/src/allocator.rs +++ b/compiler/rustc_codegen_gcc/src/allocator.rs @@ -1,11 +1,13 @@ #[cfg(feature="master")] use gccjit::FnAttribute; use gccjit::{FunctionType, GlobalKind, ToRValue}; -use rustc_ast::expand::allocator::{AllocatorKind, AllocatorTy, ALLOCATOR_METHODS}; +use rustc_ast::expand::allocator::{ + alloc_error_handler_name, default_fn_name, global_fn_name, AllocatorKind, AllocatorTy, + ALLOCATOR_METHODS, NO_ALLOC_SHIM_IS_UNSTABLE, +}; use rustc_middle::bug; use rustc_middle::ty::TyCtxt; use rustc_session::config::OomStrategy; -use rustc_span::symbol::sym; use crate::GccContext; @@ -22,69 +24,71 @@ pub(crate) unsafe fn codegen(tcx: TyCtxt<'_>, mods: &mut GccContext, _module_nam let i8p = i8.make_pointer(); let void = context.new_type::<()>(); - for method in ALLOCATOR_METHODS { - let mut types = Vec::with_capacity(method.inputs.len()); - for ty in method.inputs.iter() { - match *ty { - AllocatorTy::Layout => { - types.push(usize); - types.push(usize); + if kind == AllocatorKind::Default { + for method in ALLOCATOR_METHODS { + let mut types = Vec::with_capacity(method.inputs.len()); + for input in method.inputs.iter() { + match input.ty { + AllocatorTy::Layout => { + types.push(usize); + types.push(usize); + } + AllocatorTy::Ptr => types.push(i8p), + AllocatorTy::Usize => types.push(usize), + + AllocatorTy::ResultPtr | AllocatorTy::Unit => panic!("invalid allocator arg"), } - AllocatorTy::Ptr => types.push(i8p), - AllocatorTy::Usize => types.push(usize), - - AllocatorTy::ResultPtr | AllocatorTy::Unit => panic!("invalid allocator arg"), } - } - let output = match method.output { - AllocatorTy::ResultPtr => Some(i8p), - AllocatorTy::Unit => None, + let output = match method.output { + AllocatorTy::ResultPtr => Some(i8p), + AllocatorTy::Unit => None, - AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { - panic!("invalid allocator output") - } - }; - let name = format!("__rust_{}", method.name); + AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { + panic!("invalid allocator output") + } + }; + let name = global_fn_name(method.name); - let args: Vec<_> = types.iter().enumerate() - .map(|(index, typ)| context.new_parameter(None, *typ, &format!("param{}", index))) - .collect(); - let func = context.new_function(None, FunctionType::Exported, output.unwrap_or(void), &args, name, false); + let args: Vec<_> = types.iter().enumerate() + .map(|(index, typ)| context.new_parameter(None, *typ, &format!("param{}", index))) + .collect(); + let func = context.new_function(None, FunctionType::Exported, output.unwrap_or(void), &args, name, false); - if tcx.sess.target.options.default_hidden_visibility { + if tcx.sess.target.options.default_hidden_visibility { + #[cfg(feature="master")] + func.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); + } + if tcx.sess.must_emit_unwind_tables() { + // TODO(antoyo): emit unwind tables. + } + + let callee = default_fn_name(method.name); + let args: Vec<_> = types.iter().enumerate() + .map(|(index, typ)| context.new_parameter(None, *typ, &format!("param{}", index))) + .collect(); + let callee = context.new_function(None, FunctionType::Extern, output.unwrap_or(void), &args, callee, false); #[cfg(feature="master")] - func.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); - } - if tcx.sess.must_emit_unwind_tables() { - // TODO(antoyo): emit unwind tables. - } + callee.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); + + let block = func.new_block("entry"); + + let args = args + .iter() + .enumerate() + .map(|(i, _)| func.get_param(i as i32).to_rvalue()) + .collect::>(); + let ret = context.new_call(None, callee, &args); + //llvm::LLVMSetTailCall(ret, True); + if output.is_some() { + block.end_with_return(None, ret); + } + else { + block.end_with_void_return(None); + } - let callee = kind.fn_name(method.name); - let args: Vec<_> = types.iter().enumerate() - .map(|(index, typ)| context.new_parameter(None, *typ, &format!("param{}", index))) - .collect(); - let callee = context.new_function(None, FunctionType::Extern, output.unwrap_or(void), &args, callee, false); - #[cfg(feature="master")] - callee.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); - - let block = func.new_block("entry"); - - let args = args - .iter() - .enumerate() - .map(|(i, _)| func.get_param(i as i32).to_rvalue()) - .collect::>(); - let ret = context.new_call(None, callee, &args); - //llvm::LLVMSetTailCall(ret, True); - if output.is_some() { - block.end_with_return(None, ret); - } - else { - block.end_with_void_return(None); + // TODO(@Commeownist): Check if we need to emit some extra debugging info in certain circumstances + // as described in https://github.com/rust-lang/rust/commit/77a96ed5646f7c3ee8897693decc4626fe380643 } - - // TODO(@Commeownist): Check if we need to emit some extra debugging info in certain circumstances - // as described in https://github.com/rust-lang/rust/commit/77a96ed5646f7c3ee8897693decc4626fe380643 } let types = [usize, usize]; @@ -99,7 +103,7 @@ pub(crate) unsafe fn codegen(tcx: TyCtxt<'_>, mods: &mut GccContext, _module_nam func.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); } - let callee = alloc_error_handler_kind.fn_name(sym::oom); + let callee = alloc_error_handler_name(alloc_error_handler_kind); let args: Vec<_> = types.iter().enumerate() .map(|(index, typ)| context.new_parameter(None, *typ, &format!("param{}", index))) .collect(); @@ -123,4 +127,9 @@ pub(crate) unsafe fn codegen(tcx: TyCtxt<'_>, mods: &mut GccContext, _module_nam let value = tcx.sess.opts.unstable_opts.oom.should_panic(); let value = context.new_rvalue_from_int(i8, value as i32); global.global_set_initializer_rvalue(value); + + let name = NO_ALLOC_SHIM_IS_UNSTABLE.to_string(); + let global = context.new_global(None, GlobalKind::Exported, i8, name); + let value = context.new_rvalue_from_int(i8, 0); + global.global_set_initializer_rvalue(value); } diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index 250aa79f8d609..905fdac92e9ee 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -107,7 +107,7 @@ enum ConstraintOrRegister { impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { - fn codegen_inline_asm(&mut self, template: &[InlineAsmTemplatePiece], rust_operands: &[InlineAsmOperandRef<'tcx, Self>], options: InlineAsmOptions, span: &[Span], _instance: Instance<'_>, _dest_catch_funclet: Option<(Self::BasicBlock, Self::BasicBlock, Option<&Self::Funclet>)>) { + fn codegen_inline_asm(&mut self, template: &[InlineAsmTemplatePiece], rust_operands: &[InlineAsmOperandRef<'tcx, Self>], options: InlineAsmOptions, span: &[Span], instance: Instance<'_>, _dest_catch_funclet: Option<(Self::BasicBlock, Self::BasicBlock, Option<&Self::Funclet>)>) { if options.contains(InlineAsmOptions::MAY_UNWIND) { self.sess() .create_err(UnwindingInlineAsm { span: span[0] }) @@ -173,7 +173,7 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { let is_target_supported = reg.reg_class().supported_types(asm_arch).iter() .any(|&(_, feature)| { if let Some(feature) = feature { - self.tcx.sess.target_features.contains(&feature) + self.tcx.asm_target_features(instance.def_id()).contains(&feature) } else { true // Register class is unconditionally supported } @@ -518,7 +518,6 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { OperandValue::Immediate(op.tmp_var.to_rvalue()).store(self, place); } } - } } @@ -598,6 +597,8 @@ fn reg_to_gcc(reg: InlineAsmRegOrRegClass) -> ConstraintOrRegister { InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg) => "r", InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_addr) => "a", InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_data) => "d", + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::reg) => "r", + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::freg) => "f", InlineAsmRegClass::Mips(MipsInlineAsmRegClass::reg) => "d", // more specific than "r" InlineAsmRegClass::Mips(MipsInlineAsmRegClass::freg) => "f", InlineAsmRegClass::Msp430(Msp430InlineAsmRegClass::reg) => "r", @@ -674,6 +675,8 @@ fn dummy_output_type<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, reg: InlineAsmRegCl InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg) => cx.type_i32(), InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_addr) => cx.type_i32(), InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_data) => cx.type_i32(), + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::reg) => cx.type_i32(), + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::freg) => cx.type_f32(), InlineAsmRegClass::Mips(MipsInlineAsmRegClass::reg) => cx.type_i32(), InlineAsmRegClass::Mips(MipsInlineAsmRegClass::freg) => cx.type_f32(), InlineAsmRegClass::Msp430(_) => unimplemented!(), @@ -861,6 +864,7 @@ fn modifier_to_gcc(arch: InlineAsmArch, reg: InlineAsmRegClass, modifier: Option InlineAsmRegClass::S390x(_) => None, InlineAsmRegClass::Msp430(_) => None, InlineAsmRegClass::M68k(_) => None, + InlineAsmRegClass::CSKY(_) => None, InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => { bug!("LLVM backend does not support SPIR-V") } diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index db841b1b52408..eb0cce19b85cb 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -2,9 +2,13 @@ use gccjit::FnAttribute; use gccjit::Function; use rustc_attr::InstructionSetAttr; +#[cfg(feature="master")] +use rustc_attr::InlineAttr; use rustc_codegen_ssa::target_features::tied_target_features; use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty; +#[cfg(feature="master")] +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_session::Session; use rustc_span::symbol::sym; use smallvec::{smallvec, SmallVec}; @@ -67,6 +71,24 @@ fn to_gcc_features<'a>(sess: &Session, s: &'a str) -> SmallVec<[&'a str; 2]> { } } +/// Get GCC attribute for the provided inline heuristic. +#[cfg(feature="master")] +#[inline] +fn inline_attr<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, inline: InlineAttr) -> Option> { + match inline { + InlineAttr::Hint => Some(FnAttribute::Inline), + InlineAttr::Always => Some(FnAttribute::AlwaysInline), + InlineAttr::Never => { + if cx.sess().target.arch != "amdgpu" { + Some(FnAttribute::NoInline) + } else { + None + } + } + InlineAttr::None => None, + } +} + /// Composite function which sets GCC attributes for function depending on its AST (`#[attribute]`) /// attributes. pub fn from_fn_attrs<'gcc, 'tcx>( @@ -77,6 +99,23 @@ pub fn from_fn_attrs<'gcc, 'tcx>( ) { let codegen_fn_attrs = cx.tcx.codegen_fn_attrs(instance.def_id()); + #[cfg(feature="master")] + { + let inline = + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NAKED) { + InlineAttr::Never + } + else if codegen_fn_attrs.inline == InlineAttr::None && instance.def.requires_inline(cx.tcx) { + InlineAttr::Hint + } + else { + codegen_fn_attrs.inline + }; + if let Some(attr) = inline_attr(cx, inline) { + func.add_attribute(attr); + } + } + let function_features = codegen_fn_attrs.target_features.iter().map(|features| features.as_str()).collect::>(); diff --git a/compiler/rustc_codegen_gcc/src/base.rs b/compiler/rustc_codegen_gcc/src/base.rs index dcd560b3dcd95..9e614ca4ace0b 100644 --- a/compiler/rustc_codegen_gcc/src/base.rs +++ b/compiler/rustc_codegen_gcc/src/base.rs @@ -159,8 +159,8 @@ pub fn compile_codegen_unit(tcx: TyCtxt<'_>, cgu_name: Symbol, supports_128bit_i let cx = CodegenCx::new(&context, cgu, tcx, supports_128bit_integers); let mono_items = cgu.items_in_deterministic_order(tcx); - for &(mono_item, (linkage, visibility)) in &mono_items { - mono_item.predefine::>(&cx, linkage, visibility); + for &(mono_item, data) in &mono_items { + mono_item.predefine::>(&cx, data.linkage, data.visibility); } // ... and now that we have everything pre-defined, fill out those definitions. diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 869344ce92d7d..0b1f2fe6a87d9 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -27,7 +27,6 @@ use rustc_codegen_ssa::traits::{ BaseTypeMethods, BuilderMethods, ConstMethods, - DerivedTypeMethods, LayoutTypeMethods, HasCodegen, OverflowOp, @@ -181,6 +180,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { }) .collect(); + debug_assert_eq!(casted_args.len(), args.len()); + Cow::Owned(casted_args) } @@ -207,7 +208,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let func_name = format!("{:?}", func_ptr); - let casted_args: Vec<_> = param_types + let mut casted_args: Vec<_> = param_types .into_iter() .zip(args.iter()) .enumerate() @@ -237,6 +238,11 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { }) .collect(); + // NOTE: to take into account variadic functions. + for i in casted_args.len()..args.len() { + casted_args.push(args[i]); + } + Cow::Owned(casted_args) } @@ -280,8 +286,17 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } } - fn function_ptr_call(&mut self, func_ptr: RValue<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>) -> RValue<'gcc> { - let gcc_func = func_ptr.get_type().dyncast_function_ptr_type().expect("function ptr"); + fn function_ptr_call(&mut self, typ: Type<'gcc>, mut func_ptr: RValue<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>) -> RValue<'gcc> { + let gcc_func = + match func_ptr.get_type().dyncast_function_ptr_type() { + Some(func) => func, + None => { + // NOTE: due to opaque pointers now being used, we need to cast here. + let new_func_type = typ.dyncast_function_ptr_type().expect("function ptr"); + func_ptr = self.context.new_cast(None, func_ptr, typ); + new_func_type + }, + }; let func_name = format!("{:?}", func_ptr); let previous_arg_count = args.len(); let orig_args = args; @@ -424,16 +439,17 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { self.llbb().end_with_void_return(None) } - fn ret(&mut self, value: RValue<'gcc>) { - let value = - if self.structs_as_pointer.borrow().contains(&value) { - // NOTE: hack to workaround a limitation of the rustc API: see comment on - // CodegenCx.structs_as_pointer - value.dereference(None).to_rvalue() - } - else { - value - }; + fn ret(&mut self, mut value: RValue<'gcc>) { + if self.structs_as_pointer.borrow().contains(&value) { + // NOTE: hack to workaround a limitation of the rustc API: see comment on + // CodegenCx.structs_as_pointer + value = value.dereference(None).to_rvalue(); + } + let expected_return_type = self.current_func().get_return_type(); + if !expected_return_type.is_compatible_with(value.get_type()) { + // NOTE: due to opaque pointers now being used, we need to cast here. + value = self.context.new_cast(None, value, expected_return_type); + } self.llbb().end_with_return(None, value); } @@ -719,17 +735,25 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { unimplemented!(); } - fn load(&mut self, pointee_ty: Type<'gcc>, ptr: RValue<'gcc>, _align: Align) -> RValue<'gcc> { + fn load(&mut self, pointee_ty: Type<'gcc>, ptr: RValue<'gcc>, align: Align) -> RValue<'gcc> { let block = self.llbb(); let function = block.get_function(); // NOTE: instead of returning the dereference here, we have to assign it to a variable in // the current basic block. Otherwise, it could be used in another basic block, causing a // dereference after a drop, for instance. - // TODO(antoyo): handle align of the load instruction. - let ptr = self.context.new_cast(None, ptr, pointee_ty.make_pointer()); + // FIXME(antoyo): this check that we don't call get_aligned() a second time on a type. + // Ideally, we shouldn't need to do this check. + let aligned_type = + if pointee_ty == self.cx.u128_type || pointee_ty == self.cx.i128_type { + pointee_ty + } + else { + pointee_ty.get_aligned(align.bytes()) + }; + let ptr = self.context.new_cast(None, ptr, aligned_type.make_pointer()); let deref = ptr.dereference(None).to_rvalue(); unsafe { RETURN_VALUE_COUNT += 1 }; - let loaded_value = function.new_local(None, pointee_ty, &format!("loadedValue{}", unsafe { RETURN_VALUE_COUNT })); + let loaded_value = function.new_local(None, aligned_type, &format!("loadedValue{}", unsafe { RETURN_VALUE_COUNT })); block.add_assignment(None, loaded_value, deref); loaded_value.to_rvalue() } @@ -758,7 +782,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { assert_eq!(place.llextra.is_some(), place.layout.is_unsized()); if place.layout.is_zst() { - return OperandRef::new_zst(self, place.layout); + return OperandRef::zero_sized(place.layout); } fn scalar_load_metadata<'a, 'gcc, 'tcx>(bx: &mut Builder<'a, 'gcc, 'tcx>, load: RValue<'gcc>, scalar: &abi::Scalar) { @@ -909,7 +933,9 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { self.context.new_bitcast(None, result, ptr_type) } - fn inbounds_gep(&mut self, _typ: Type<'gcc>, ptr: RValue<'gcc>, indices: &[RValue<'gcc>]) -> RValue<'gcc> { + fn inbounds_gep(&mut self, typ: Type<'gcc>, ptr: RValue<'gcc>, indices: &[RValue<'gcc>]) -> RValue<'gcc> { + // NOTE: due to opaque pointers now being used, we need to cast here. + let ptr = self.context.new_cast(None, ptr, typ.make_pointer()); // NOTE: array indexing is always considered in bounds in GCC (TODO(antoyo): to be verified). let mut indices = indices.into_iter(); let index = indices.next().expect("first index in inbounds_gep"); @@ -938,6 +964,8 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { element.get_address(None) } else if let Some(struct_type) = value_type.is_struct() { + // NOTE: due to opaque pointers now being used, we need to bitcast here. + let ptr = self.bitcast_if_needed(ptr, value_type.make_pointer()); ptr.dereference_field(None, struct_type.get_field(idx as i32)).get_address(None) } else { @@ -1356,7 +1384,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { fn call( &mut self, - _typ: Type<'gcc>, + typ: Type<'gcc>, _fn_attrs: Option<&CodegenFnAttrs>, fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, func: RValue<'gcc>, @@ -1370,7 +1398,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { } else { // If it's a not function that was defined, it's a function pointer. - self.function_ptr_call(func, args, funclet) + self.function_ptr_call(typ, func, args, funclet) }; if let Some(_fn_abi) = fn_abi { // TODO(bjorn3): Apply function attributes @@ -1843,7 +1871,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { #[cfg(feature="master")] let (cond, element_type) = { - let then_val_vector_type = then_val.get_type().dyncast_vector().expect("vector type"); + // TODO(antoyo): dyncast_vector should not require a call to unqualified. + let then_val_vector_type = then_val.get_type().unqualified().dyncast_vector().expect("vector type"); let then_val_element_type = then_val_vector_type.get_element_type(); let then_val_element_size = then_val_element_type.get_size(); diff --git a/compiler/rustc_codegen_gcc/src/callee.rs b/compiler/rustc_codegen_gcc/src/callee.rs index 433b2585f82a8..a96bd66ba79ef 100644 --- a/compiler/rustc_codegen_gcc/src/callee.rs +++ b/compiler/rustc_codegen_gcc/src/callee.rs @@ -17,8 +17,8 @@ use crate::context::CodegenCx; pub fn get_fn<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, instance: Instance<'tcx>) -> Function<'gcc> { let tcx = cx.tcx(); - assert!(!instance.substs.has_infer()); - assert!(!instance.substs.has_escaping_bound_vars()); + assert!(!instance.args.has_infer()); + assert!(!instance.args.has_escaping_bound_vars()); let sym = tcx.symbol_name(instance).name; @@ -100,7 +100,7 @@ pub fn get_fn<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, instance: Instance<'tcx>) // whether we are sharing generics or not. The important thing here is // that the visibility we apply to the declaration is the same one that // has been applied to the definition (wherever that definition may be). - let is_generic = instance.substs.non_erasable_generics().next().is_some(); + let is_generic = instance.args.non_erasable_generics().next().is_some(); if is_generic { // This is a monomorphization. Its expected visibility depends diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index ac04b61a30672..5f54cb16d8e2b 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -1,23 +1,25 @@ use gccjit::LValue; use gccjit::{RValue, Type, ToRValue}; -use rustc_codegen_ssa::mir::place::PlaceRef; use rustc_codegen_ssa::traits::{ BaseTypeMethods, ConstMethods, - DerivedTypeMethods, MiscMethods, StaticMethods, }; use rustc_middle::mir::Mutability; -use rustc_middle::ty::layout::{TyAndLayout, LayoutOf}; +use rustc_middle::ty::layout::{LayoutOf}; use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, Scalar}; -use rustc_target::abi::{self, HasDataLayout, Pointer, Size}; +use rustc_target::abi::{self, HasDataLayout, Pointer}; use crate::consts::const_alloc_to_gcc; use crate::context::CodegenCx; use crate::type_of::LayoutGccExt; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { + pub fn const_ptrcast(&self, val: RValue<'gcc>, ty: Type<'gcc>) -> RValue<'gcc> { + self.context.new_cast(None, val, ty) + } + pub fn const_bytes(&self, bytes: &[u8]) -> RValue<'gcc> { bytes_in_context(self, bytes) } @@ -110,6 +112,10 @@ impl<'gcc, 'tcx> ConstMethods<'tcx> for CodegenCx<'gcc, 'tcx> { self.const_uint(self.type_u64(), i) } + fn const_u128(&self, i: u128) -> RValue<'gcc> { + self.const_uint_big(self.type_u128(), i) + } + fn const_usize(&self, i: u64) -> RValue<'gcc> { let bit_size = self.data_layout().pointer_size.bits(); if bit_size < 64 { @@ -240,27 +246,21 @@ impl<'gcc, 'tcx> ConstMethods<'tcx> for CodegenCx<'gcc, 'tcx> { const_alloc_to_gcc(self, alloc) } - fn from_const_alloc(&self, layout: TyAndLayout<'tcx>, alloc: ConstAllocation<'tcx>, offset: Size) -> PlaceRef<'tcx, RValue<'gcc>> { - assert_eq!(alloc.inner().align, layout.align.abi); - let ty = self.type_ptr_to(layout.gcc_type(self)); - let value = - if layout.size == Size::ZERO { - let value = self.const_usize(alloc.inner().align.bytes()); - self.const_bitcast(value, ty) + fn const_bitcast(&self, value: RValue<'gcc>, typ: Type<'gcc>) -> RValue<'gcc> { + if value.get_type() == self.bool_type.make_pointer() { + if let Some(pointee) = typ.get_pointee() { + if pointee.dyncast_vector().is_some() { + panic!() + } } - else { - let init = const_alloc_to_gcc(self, alloc); - let base_addr = self.static_addr_of(init, alloc.inner().align, None); - - let array = self.const_bitcast(base_addr, self.type_i8p()); - let value = self.context.new_array_access(None, array, self.const_usize(offset.bytes())).get_address(None); - self.const_bitcast(value, ty) - }; - PlaceRef::new_sized(value, layout) + } + // NOTE: since bitcast makes a value non-constant, don't bitcast if not necessary as some + // SIMD builtins require a constant value. + self.bitcast_if_needed(value, typ) } - fn const_ptrcast(&self, val: RValue<'gcc>, ty: Type<'gcc>) -> RValue<'gcc> { - self.context.new_cast(None, val, ty) + fn const_ptr_byte_offset(&self, base_addr: Self::Value, offset: abi::Size) -> Self::Value { + self.context.new_array_access(None, base_addr, self.const_usize(offset.bytes())).get_address(None) } } diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 792ab8f890d8f..d8a1fd315c0a5 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -1,6 +1,6 @@ #[cfg(feature = "master")] -use gccjit::FnAttribute; -use gccjit::{Function, GlobalKind, LValue, RValue, ToRValue, Type}; +use gccjit::{FnAttribute, VarAttribute, Visibility}; +use gccjit::{Function, GlobalKind, LValue, RValue, ToRValue}; use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods, DerivedTypeMethods, StaticMethods}; use rustc_middle::span_bug; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; @@ -16,21 +16,6 @@ use crate::context::CodegenCx; use crate::errors::InvalidMinimumAlignment; use crate::type_of::LayoutGccExt; -impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { - pub fn const_bitcast(&self, value: RValue<'gcc>, typ: Type<'gcc>) -> RValue<'gcc> { - if value.get_type() == self.bool_type.make_pointer() { - if let Some(pointee) = typ.get_pointee() { - if pointee.dyncast_vector().is_some() { - panic!() - } - } - } - // NOTE: since bitcast makes a value non-constant, don't bitcast if not necessary as some - // SIMD builtins require a constant value. - self.bitcast_if_needed(value, typ) - } -} - fn set_global_alignment<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, gv: LValue<'gcc>, mut align: Align) { // The target may require greater alignment for globals than the type does. // Note: GCC and Clang also allow `__attribute__((aligned))` on variables, @@ -39,7 +24,7 @@ fn set_global_alignment<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, gv: LValue<'gcc> match Align::from_bits(min) { Ok(min) => align = align.max(min), Err(err) => { - cx.sess().emit_err(InvalidMinimumAlignment { err }); + cx.sess().emit_err(InvalidMinimumAlignment { err: err.to_string() }); } } } @@ -249,7 +234,8 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { ); if !self.tcx.is_reachable_non_generic(def_id) { - // TODO(antoyo): set visibility. + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Visibility(Visibility::Hidden)); } global diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index 661681bdb50f2..88dcafa7370e5 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -476,8 +476,8 @@ impl<'gcc, 'tcx> LayoutOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> { #[inline] fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! { - if let LayoutError::SizeOverflow(_) = err { - self.sess().emit_fatal(respan(span, err)) + if let LayoutError::SizeOverflow(_) | LayoutError::ReferencesError(_) = err { + self.sess().emit_fatal(respan(span, err.into_diagnostic())) } else { span_bug!(span, "failed to get layout for `{}`: {}", ty, err) } @@ -499,21 +499,12 @@ impl<'gcc, 'tcx> FnAbiOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> { } else { match fn_abi_request { FnAbiRequest::OfFnPtr { sig, extra_args } => { - span_bug!( - span, - "`fn_abi_of_fn_ptr({}, {:?})` failed: {}", - sig, - extra_args, - err - ); + span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}"); } FnAbiRequest::OfInstance { instance, extra_args } => { span_bug!( span, - "`fn_abi_of_instance({}, {:?})` failed: {}", - instance, - extra_args, - err + "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}" ); } } diff --git a/compiler/rustc_codegen_gcc/src/coverageinfo.rs b/compiler/rustc_codegen_gcc/src/coverageinfo.rs index 872fc2472e223..849e9886ef39d 100644 --- a/compiler/rustc_codegen_gcc/src/coverageinfo.rs +++ b/compiler/rustc_codegen_gcc/src/coverageinfo.rs @@ -1,69 +1,11 @@ -use gccjit::RValue; -use rustc_codegen_ssa::traits::{CoverageInfoBuilderMethods, CoverageInfoMethods}; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::coverage::{ - CodeRegion, - CounterValueReference, - ExpressionOperandId, - InjectedExpressionId, - Op, -}; +use rustc_codegen_ssa::traits::CoverageInfoBuilderMethods; +use rustc_middle::mir::Coverage; use rustc_middle::ty::Instance; use crate::builder::Builder; -use crate::context::CodegenCx; impl<'a, 'gcc, 'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { - fn set_function_source_hash( - &mut self, - _instance: Instance<'tcx>, - _function_source_hash: u64, - ) -> bool { - unimplemented!(); - } - - fn add_coverage_counter(&mut self, _instance: Instance<'tcx>, _id: CounterValueReference, _region: CodeRegion) -> bool { - // TODO(antoyo) - false - } - - fn add_coverage_counter_expression(&mut self, _instance: Instance<'tcx>, _id: InjectedExpressionId, _lhs: ExpressionOperandId, _op: Op, _rhs: ExpressionOperandId, _region: Option) -> bool { - // TODO(antoyo) - false - } - - fn add_coverage_unreachable(&mut self, _instance: Instance<'tcx>, _region: CodeRegion) -> bool { + fn add_coverage(&mut self, _instance: Instance<'tcx>, _coverage: &Coverage) { // TODO(antoyo) - false - } -} - -impl<'gcc, 'tcx> CoverageInfoMethods<'tcx> for CodegenCx<'gcc, 'tcx> { - fn coverageinfo_finalize(&self) { - // TODO(antoyo) - } - - fn get_pgo_func_name_var(&self, _instance: Instance<'tcx>) -> RValue<'gcc> { - unimplemented!(); - } - - /// Functions with MIR-based coverage are normally codegenned _only_ if - /// called. LLVM coverage tools typically expect every function to be - /// defined (even if unused), with at least one call to LLVM intrinsic - /// `instrprof.increment`. - /// - /// Codegen a small function that will never be called, with one counter - /// that will never be incremented. - /// - /// For used/called functions, the coverageinfo was already added to the - /// `function_coverage_map` (keyed by function `Instance`) during codegen. - /// But in this case, since the unused function was _not_ previously - /// codegenned, collect the coverage `CodeRegion`s from the MIR and add - /// them. The first `CodeRegion` is used to add a single counter, with the - /// same counter ID used in the injected `instrprof.increment` intrinsic - /// call. Since the function is never called, all other `CodeRegion`s can be - /// added as `unreachable_region`s. - fn define_unused_fn(&self, _def_id: DefId) { - unimplemented!(); } } diff --git a/compiler/rustc_codegen_gcc/src/declare.rs b/compiler/rustc_codegen_gcc/src/declare.rs index 4748e7e4be2a3..493626c3cf5de 100644 --- a/compiler/rustc_codegen_gcc/src/declare.rs +++ b/compiler/rustc_codegen_gcc/src/declare.rs @@ -132,7 +132,7 @@ fn declare_raw_fn<'gcc>(cx: &CodegenCx<'gcc, '_>, name: &str, _callconv: () /*ll pub fn mangle_name(name: &str) -> String { name.replace(|char: char| { if !char.is_alphanumeric() && char != '_' { - debug_assert!("$.".contains(char), "Unsupported char in function name: {}", char); + debug_assert!("$.*".contains(char), "Unsupported char in function name {}: {}", name, char); true } else { diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs index 9305bd1e043d5..693367192b1fe 100644 --- a/compiler/rustc_codegen_gcc/src/errors.rs +++ b/compiler/rustc_codegen_gcc/src/errors.rs @@ -1,7 +1,6 @@ use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; use rustc_macros::Diagnostic; -use rustc_middle::ty::Ty; -use rustc_span::{Span, Symbol}; +use rustc_span::Span; use std::borrow::Cow; struct ExitCode(Option); @@ -16,201 +15,6 @@ impl IntoDiagnosticArg for ExitCode { } } -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_basic_integer, code = "E0511")] -pub(crate) struct InvalidMonomorphizationBasicInteger<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_invalid_float_vector, code = "E0511")] -pub(crate) struct InvalidMonomorphizationInvalidFloatVector<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub elem_ty: &'a str, - pub vec_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_not_float, code = "E0511")] -pub(crate) struct InvalidMonomorphizationNotFloat<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_unrecognized, code = "E0511")] -pub(crate) struct InvalidMonomorphizationUnrecognized { - #[primary_span] - pub span: Span, - pub name: Symbol, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_expected_signed_unsigned, code = "E0511")] -pub(crate) struct InvalidMonomorphizationExpectedSignedUnsigned<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub elem_ty: Ty<'a>, - pub vec_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_unsupported_element, code = "E0511")] -pub(crate) struct InvalidMonomorphizationUnsupportedElement<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_ty: Ty<'a>, - pub elem_ty: Ty<'a>, - pub ret_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_invalid_bitmask, code = "E0511")] -pub(crate) struct InvalidMonomorphizationInvalidBitmask<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub ty: Ty<'a>, - pub expected_int_bits: u64, - pub expected_bytes: u64, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_simd_shuffle, code = "E0511")] -pub(crate) struct InvalidMonomorphizationSimdShuffle<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_expected_simd, code = "E0511")] -pub(crate) struct InvalidMonomorphizationExpectedSimd<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub position: &'a str, - pub found_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_mask_type, code = "E0511")] -pub(crate) struct InvalidMonomorphizationMaskType<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_return_length, code = "E0511")] -pub(crate) struct InvalidMonomorphizationReturnLength<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_len: u64, - pub ret_ty: Ty<'a>, - pub out_len: u64, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_return_length_input_type, code = "E0511")] -pub(crate) struct InvalidMonomorphizationReturnLengthInputType<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_len: u64, - pub in_ty: Ty<'a>, - pub ret_ty: Ty<'a>, - pub out_len: u64, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_return_element, code = "E0511")] -pub(crate) struct InvalidMonomorphizationReturnElement<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_elem: Ty<'a>, - pub in_ty: Ty<'a>, - pub ret_ty: Ty<'a>, - pub out_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_return_type, code = "E0511")] -pub(crate) struct InvalidMonomorphizationReturnType<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_elem: Ty<'a>, - pub in_ty: Ty<'a>, - pub ret_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_inserted_type, code = "E0511")] -pub(crate) struct InvalidMonomorphizationInsertedType<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_elem: Ty<'a>, - pub in_ty: Ty<'a>, - pub out_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_return_integer_type, code = "E0511")] -pub(crate) struct InvalidMonomorphizationReturnIntegerType<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub ret_ty: Ty<'a>, - pub out_ty: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_mismatched_lengths, code = "E0511")] -pub(crate) struct InvalidMonomorphizationMismatchedLengths { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub m_len: u64, - pub v_len: u64, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_unsupported_cast, code = "E0511")] -pub(crate) struct InvalidMonomorphizationUnsupportedCast<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_ty: Ty<'a>, - pub in_elem: Ty<'a>, - pub ret_ty: Ty<'a>, - pub out_elem: Ty<'a>, -} - -#[derive(Diagnostic)] -#[diag(codegen_gcc_invalid_monomorphization_unsupported_operation, code = "E0511")] -pub(crate) struct InvalidMonomorphizationUnsupportedOperation<'a> { - #[primary_span] - pub span: Span, - pub name: Symbol, - pub in_ty: Ty<'a>, - pub in_elem: Ty<'a>, -} - #[derive(Diagnostic)] #[diag(codegen_gcc_lto_not_supported)] pub(crate) struct LTONotSupported; diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs index 8a4559355ea67..438eab78943af 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs @@ -2967,10 +2967,6 @@ match name { "llvm.nvvm.clz.ll" => "__nvvm_clz_ll", "llvm.nvvm.cos.approx.f" => "__nvvm_cos_approx_f", "llvm.nvvm.cos.approx.ftz.f" => "__nvvm_cos_approx_ftz_f", - "llvm.nvvm.cp.async.ca.shared.global.16" => "__nvvm_cp_async_ca_shared_global_16", - "llvm.nvvm.cp.async.ca.shared.global.4" => "__nvvm_cp_async_ca_shared_global_4", - "llvm.nvvm.cp.async.ca.shared.global.8" => "__nvvm_cp_async_ca_shared_global_8", - "llvm.nvvm.cp.async.cg.shared.global.16" => "__nvvm_cp_async_cg_shared_global_16", "llvm.nvvm.cp.async.commit.group" => "__nvvm_cp_async_commit_group", "llvm.nvvm.cp.async.mbarrier.arrive" => "__nvvm_cp_async_mbarrier_arrive", "llvm.nvvm.cp.async.mbarrier.arrive.noinc" => "__nvvm_cp_async_mbarrier_arrive_noinc", @@ -3086,18 +3082,8 @@ match name { "llvm.nvvm.fma.rn.f16" => "__nvvm_fma_rn_f16", "llvm.nvvm.fma.rn.f16x2" => "__nvvm_fma_rn_f16x2", "llvm.nvvm.fma.rn.ftz.f" => "__nvvm_fma_rn_ftz_f", - "llvm.nvvm.fma.rn.ftz.f16" => "__nvvm_fma_rn_ftz_f16", - "llvm.nvvm.fma.rn.ftz.f16x2" => "__nvvm_fma_rn_ftz_f16x2", - "llvm.nvvm.fma.rn.ftz.relu.f16" => "__nvvm_fma_rn_ftz_relu_f16", - "llvm.nvvm.fma.rn.ftz.relu.f16x2" => "__nvvm_fma_rn_ftz_relu_f16x2", - "llvm.nvvm.fma.rn.ftz.sat.f16" => "__nvvm_fma_rn_ftz_sat_f16", - "llvm.nvvm.fma.rn.ftz.sat.f16x2" => "__nvvm_fma_rn_ftz_sat_f16x2", "llvm.nvvm.fma.rn.relu.bf16" => "__nvvm_fma_rn_relu_bf16", "llvm.nvvm.fma.rn.relu.bf16x2" => "__nvvm_fma_rn_relu_bf16x2", - "llvm.nvvm.fma.rn.relu.f16" => "__nvvm_fma_rn_relu_f16", - "llvm.nvvm.fma.rn.relu.f16x2" => "__nvvm_fma_rn_relu_f16x2", - "llvm.nvvm.fma.rn.sat.f16" => "__nvvm_fma_rn_sat_f16", - "llvm.nvvm.fma.rn.sat.f16x2" => "__nvvm_fma_rn_sat_f16x2", "llvm.nvvm.fma.rp.d" => "__nvvm_fma_rp_d", "llvm.nvvm.fma.rp.f" => "__nvvm_fma_rp_f", "llvm.nvvm.fma.rp.ftz.f" => "__nvvm_fma_rp_ftz_f", @@ -3111,32 +3097,18 @@ match name { "llvm.nvvm.fmax.f16" => "__nvvm_fmax_f16", "llvm.nvvm.fmax.f16x2" => "__nvvm_fmax_f16x2", "llvm.nvvm.fmax.ftz.f" => "__nvvm_fmax_ftz_f", - "llvm.nvvm.fmax.ftz.f16" => "__nvvm_fmax_ftz_f16", - "llvm.nvvm.fmax.ftz.f16x2" => "__nvvm_fmax_ftz_f16x2", "llvm.nvvm.fmax.ftz.nan.f" => "__nvvm_fmax_ftz_nan_f", - "llvm.nvvm.fmax.ftz.nan.f16" => "__nvvm_fmax_ftz_nan_f16", - "llvm.nvvm.fmax.ftz.nan.f16x2" => "__nvvm_fmax_ftz_nan_f16x2", "llvm.nvvm.fmax.ftz.nan.xorsign.abs.f" => "__nvvm_fmax_ftz_nan_xorsign_abs_f", - "llvm.nvvm.fmax.ftz.nan.xorsign.abs.f16" => "__nvvm_fmax_ftz_nan_xorsign_abs_f16", - "llvm.nvvm.fmax.ftz.nan.xorsign.abs.f16x2" => "__nvvm_fmax_ftz_nan_xorsign_abs_f16x2", "llvm.nvvm.fmax.ftz.xorsign.abs.f" => "__nvvm_fmax_ftz_xorsign_abs_f", - "llvm.nvvm.fmax.ftz.xorsign.abs.f16" => "__nvvm_fmax_ftz_xorsign_abs_f16", - "llvm.nvvm.fmax.ftz.xorsign.abs.f16x2" => "__nvvm_fmax_ftz_xorsign_abs_f16x2", "llvm.nvvm.fmax.nan.bf16" => "__nvvm_fmax_nan_bf16", "llvm.nvvm.fmax.nan.bf16x2" => "__nvvm_fmax_nan_bf16x2", "llvm.nvvm.fmax.nan.f" => "__nvvm_fmax_nan_f", - "llvm.nvvm.fmax.nan.f16" => "__nvvm_fmax_nan_f16", - "llvm.nvvm.fmax.nan.f16x2" => "__nvvm_fmax_nan_f16x2", "llvm.nvvm.fmax.nan.xorsign.abs.bf16" => "__nvvm_fmax_nan_xorsign_abs_bf16", "llvm.nvvm.fmax.nan.xorsign.abs.bf16x2" => "__nvvm_fmax_nan_xorsign_abs_bf16x2", "llvm.nvvm.fmax.nan.xorsign.abs.f" => "__nvvm_fmax_nan_xorsign_abs_f", - "llvm.nvvm.fmax.nan.xorsign.abs.f16" => "__nvvm_fmax_nan_xorsign_abs_f16", - "llvm.nvvm.fmax.nan.xorsign.abs.f16x2" => "__nvvm_fmax_nan_xorsign_abs_f16x2", "llvm.nvvm.fmax.xorsign.abs.bf16" => "__nvvm_fmax_xorsign_abs_bf16", "llvm.nvvm.fmax.xorsign.abs.bf16x2" => "__nvvm_fmax_xorsign_abs_bf16x2", "llvm.nvvm.fmax.xorsign.abs.f" => "__nvvm_fmax_xorsign_abs_f", - "llvm.nvvm.fmax.xorsign.abs.f16" => "__nvvm_fmax_xorsign_abs_f16", - "llvm.nvvm.fmax.xorsign.abs.f16x2" => "__nvvm_fmax_xorsign_abs_f16x2", "llvm.nvvm.fmin.bf16" => "__nvvm_fmin_bf16", "llvm.nvvm.fmin.bf16x2" => "__nvvm_fmin_bf16x2", "llvm.nvvm.fmin.d" => "__nvvm_fmin_d", @@ -3144,32 +3116,18 @@ match name { "llvm.nvvm.fmin.f16" => "__nvvm_fmin_f16", "llvm.nvvm.fmin.f16x2" => "__nvvm_fmin_f16x2", "llvm.nvvm.fmin.ftz.f" => "__nvvm_fmin_ftz_f", - "llvm.nvvm.fmin.ftz.f16" => "__nvvm_fmin_ftz_f16", - "llvm.nvvm.fmin.ftz.f16x2" => "__nvvm_fmin_ftz_f16x2", "llvm.nvvm.fmin.ftz.nan.f" => "__nvvm_fmin_ftz_nan_f", - "llvm.nvvm.fmin.ftz.nan.f16" => "__nvvm_fmin_ftz_nan_f16", - "llvm.nvvm.fmin.ftz.nan.f16x2" => "__nvvm_fmin_ftz_nan_f16x2", "llvm.nvvm.fmin.ftz.nan.xorsign.abs.f" => "__nvvm_fmin_ftz_nan_xorsign_abs_f", - "llvm.nvvm.fmin.ftz.nan.xorsign.abs.f16" => "__nvvm_fmin_ftz_nan_xorsign_abs_f16", - "llvm.nvvm.fmin.ftz.nan.xorsign.abs.f16x2" => "__nvvm_fmin_ftz_nan_xorsign_abs_f16x2", "llvm.nvvm.fmin.ftz.xorsign.abs.f" => "__nvvm_fmin_ftz_xorsign_abs_f", - "llvm.nvvm.fmin.ftz.xorsign.abs.f16" => "__nvvm_fmin_ftz_xorsign_abs_f16", - "llvm.nvvm.fmin.ftz.xorsign.abs.f16x2" => "__nvvm_fmin_ftz_xorsign_abs_f16x2", "llvm.nvvm.fmin.nan.bf16" => "__nvvm_fmin_nan_bf16", "llvm.nvvm.fmin.nan.bf16x2" => "__nvvm_fmin_nan_bf16x2", "llvm.nvvm.fmin.nan.f" => "__nvvm_fmin_nan_f", - "llvm.nvvm.fmin.nan.f16" => "__nvvm_fmin_nan_f16", - "llvm.nvvm.fmin.nan.f16x2" => "__nvvm_fmin_nan_f16x2", "llvm.nvvm.fmin.nan.xorsign.abs.bf16" => "__nvvm_fmin_nan_xorsign_abs_bf16", "llvm.nvvm.fmin.nan.xorsign.abs.bf16x2" => "__nvvm_fmin_nan_xorsign_abs_bf16x2", "llvm.nvvm.fmin.nan.xorsign.abs.f" => "__nvvm_fmin_nan_xorsign_abs_f", - "llvm.nvvm.fmin.nan.xorsign.abs.f16" => "__nvvm_fmin_nan_xorsign_abs_f16", - "llvm.nvvm.fmin.nan.xorsign.abs.f16x2" => "__nvvm_fmin_nan_xorsign_abs_f16x2", "llvm.nvvm.fmin.xorsign.abs.bf16" => "__nvvm_fmin_xorsign_abs_bf16", "llvm.nvvm.fmin.xorsign.abs.bf16x2" => "__nvvm_fmin_xorsign_abs_bf16x2", "llvm.nvvm.fmin.xorsign.abs.f" => "__nvvm_fmin_xorsign_abs_f", - "llvm.nvvm.fmin.xorsign.abs.f16" => "__nvvm_fmin_xorsign_abs_f16", - "llvm.nvvm.fmin.xorsign.abs.f16x2" => "__nvvm_fmin_xorsign_abs_f16x2", "llvm.nvvm.fns" => "__nvvm_fns", "llvm.nvvm.h2f" => "__nvvm_h2f", "llvm.nvvm.i2d.rm" => "__nvvm_i2d_rm", @@ -7895,6 +7853,10 @@ match name { "llvm.x86.subborrow.u64" => "__builtin_ia32_subborrow_u64", "llvm.x86.tbm.bextri.u32" => "__builtin_ia32_bextri_u32", "llvm.x86.tbm.bextri.u64" => "__builtin_ia32_bextri_u64", + "llvm.x86.tcmmimfp16ps" => "__builtin_ia32_tcmmimfp16ps", + "llvm.x86.tcmmimfp16ps.internal" => "__builtin_ia32_tcmmimfp16ps_internal", + "llvm.x86.tcmmrlfp16ps" => "__builtin_ia32_tcmmrlfp16ps", + "llvm.x86.tcmmrlfp16ps.internal" => "__builtin_ia32_tcmmrlfp16ps_internal", "llvm.x86.tdpbf16ps" => "__builtin_ia32_tdpbf16ps", "llvm.x86.tdpbf16ps.internal" => "__builtin_ia32_tdpbf16ps_internal", "llvm.x86.tdpbssd" => "__builtin_ia32_tdpbssd", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs b/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs index 0edec566be309..f28348380d7bc 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs @@ -313,6 +313,13 @@ pub fn adjust_intrinsic_arguments<'a, 'b, 'gcc, 'tcx>(builder: &Builder<'a, 'gcc let new_args = args.to_vec(); args = vec![new_args[1], new_args[0], new_args[2], new_args[3], new_args[4]].into(); }, + "__builtin_ia32_vpshrdv_v8di" | "__builtin_ia32_vpshrdv_v4di" | "__builtin_ia32_vpshrdv_v2di" | + "__builtin_ia32_vpshrdv_v16si" | "__builtin_ia32_vpshrdv_v8si" | "__builtin_ia32_vpshrdv_v4si" | + "__builtin_ia32_vpshrdv_v32hi" | "__builtin_ia32_vpshrdv_v16hi" | "__builtin_ia32_vpshrdv_v8hi" => { + // The first two arguments are reversed, compared to LLVM. + let new_args = args.to_vec(); + args = vec![new_args[1], new_args[0], new_args[2]].into(); + }, _ => (), } } diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 6017687474726..f8c32c6dbbb56 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -12,7 +12,8 @@ use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue}; use rustc_codegen_ssa::mir::place::PlaceRef; use rustc_codegen_ssa::traits::{ArgAbiMethods, BaseTypeMethods, BuilderMethods, ConstMethods, IntrinsicCallMethods}; #[cfg(feature="master")] -use rustc_codegen_ssa::traits::{DerivedTypeMethods, MiscMethods}; +use rustc_codegen_ssa::traits::MiscMethods; +use rustc_codegen_ssa::errors::InvalidMonomorphization; use rustc_middle::bug; use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::ty::layout::LayoutOf; @@ -31,7 +32,6 @@ use crate::abi::FnAbiGccExt; use crate::builder::Builder; use crate::common::{SignType, TypeReflection}; use crate::context::CodegenCx; -use crate::errors::InvalidMonomorphizationBasicInteger; use crate::type_of::LayoutGccExt; use crate::intrinsic::simd::generic_simd_intrinsic; @@ -92,8 +92,8 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { let tcx = self.tcx; let callee_ty = instance.ty(tcx, ty::ParamEnv::reveal_all()); - let (def_id, substs) = match *callee_ty.kind() { - ty::FnDef(def_id, substs) => (def_id, substs), + let (def_id, fn_args) = match *callee_ty.kind() { + ty::FnDef(def_id, fn_args) => (def_id, fn_args), _ => bug!("expected fn item type, found {}", callee_ty), }; @@ -142,7 +142,7 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { } sym::volatile_load | sym::unaligned_volatile_load => { - let tp_ty = substs.type_at(0); + let tp_ty = fn_args.type_at(0); let mut ptr = args[0].immediate(); if let PassMode::Cast(ty, _) = &fn_abi.ret.mode { ptr = self.pointercast(ptr, self.type_ptr_to(ty.gcc_type(self))); @@ -256,7 +256,7 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { _ => bug!(), }, None => { - tcx.sess.emit_err(InvalidMonomorphizationBasicInteger { span, name, ty }); + tcx.sess.emit_err(InvalidMonomorphization::BasicIntegerType { span, name, ty }); return; } } @@ -264,7 +264,7 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { sym::raw_eq => { use rustc_target::abi::Abi::*; - let tp_ty = substs.type_at(0); + let tp_ty = fn_args.type_at(0); let layout = self.layout_of(tp_ty).layout; let _use_integer_compare = match layout.abi() { Scalar(_) | ScalarPair(_, _) => true, @@ -302,6 +302,21 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { } } + sym::compare_bytes => { + let a = args[0].immediate(); + let b = args[1].immediate(); + let n = args[2].immediate(); + + let void_ptr_type = self.context.new_type::<*const ()>(); + let a_ptr = self.bitcast(a, void_ptr_type); + let b_ptr = self.bitcast(b, void_ptr_type); + + // Here we assume that the `memcmp` provided by the target is a NOP for size 0. + let builtin = self.context.get_builtin_function("memcmp"); + let cmp = self.context.new_call(None, builtin, &[a_ptr, b_ptr, n]); + self.sext(cmp, self.type_ix(32)) + } + sym::black_box => { args[0].val.store(self, result); @@ -551,141 +566,52 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let context = &self.cx.context; let result = match width { - 8 => { - // First step. - let left = self.and(value, context.new_rvalue_from_int(typ, 0xF0)); - let left = self.lshr(left, context.new_rvalue_from_int(typ, 4)); - let right = self.and(value, context.new_rvalue_from_int(typ, 0x0F)); - let right = self.shl(right, context.new_rvalue_from_int(typ, 4)); - let step1 = self.or(left, right); - - // Second step. - let left = self.and(step1, context.new_rvalue_from_int(typ, 0xCC)); - let left = self.lshr(left, context.new_rvalue_from_int(typ, 2)); - let right = self.and(step1, context.new_rvalue_from_int(typ, 0x33)); - let right = self.shl(right, context.new_rvalue_from_int(typ, 2)); - let step2 = self.or(left, right); - - // Third step. - let left = self.and(step2, context.new_rvalue_from_int(typ, 0xAA)); - let left = self.lshr(left, context.new_rvalue_from_int(typ, 1)); - let right = self.and(step2, context.new_rvalue_from_int(typ, 0x55)); - let right = self.shl(right, context.new_rvalue_from_int(typ, 1)); - let step3 = self.or(left, right); - - step3 - }, - 16 => { - // First step. - let left = self.and(value, context.new_rvalue_from_int(typ, 0x5555)); - let left = self.shl(left, context.new_rvalue_from_int(typ, 1)); - let right = self.and(value, context.new_rvalue_from_int(typ, 0xAAAA)); - let right = self.lshr(right, context.new_rvalue_from_int(typ, 1)); - let step1 = self.or(left, right); - - // Second step. - let left = self.and(step1, context.new_rvalue_from_int(typ, 0x3333)); - let left = self.shl(left, context.new_rvalue_from_int(typ, 2)); - let right = self.and(step1, context.new_rvalue_from_int(typ, 0xCCCC)); - let right = self.lshr(right, context.new_rvalue_from_int(typ, 2)); - let step2 = self.or(left, right); - - // Third step. - let left = self.and(step2, context.new_rvalue_from_int(typ, 0x0F0F)); - let left = self.shl(left, context.new_rvalue_from_int(typ, 4)); - let right = self.and(step2, context.new_rvalue_from_int(typ, 0xF0F0)); - let right = self.lshr(right, context.new_rvalue_from_int(typ, 4)); - let step3 = self.or(left, right); - - // Fourth step. - let left = self.and(step3, context.new_rvalue_from_int(typ, 0x00FF)); - let left = self.shl(left, context.new_rvalue_from_int(typ, 8)); - let right = self.and(step3, context.new_rvalue_from_int(typ, 0xFF00)); - let right = self.lshr(right, context.new_rvalue_from_int(typ, 8)); - let step4 = self.or(left, right); + 8 | 16 | 32 | 64 => { + let mask = ((1u128 << width) - 1) as u64; + let (m0, m1, m2) = if width > 16 { + ( + context.new_rvalue_from_long(typ, (0x5555555555555555u64 & mask) as i64), + context.new_rvalue_from_long(typ, (0x3333333333333333u64 & mask) as i64), + context.new_rvalue_from_long(typ, (0x0f0f0f0f0f0f0f0fu64 & mask) as i64), + ) + } else { + ( + context.new_rvalue_from_int(typ, (0x5555u64 & mask) as i32), + context.new_rvalue_from_int(typ, (0x3333u64 & mask) as i32), + context.new_rvalue_from_int(typ, (0x0f0fu64 & mask) as i32), + ) + }; + let one = context.new_rvalue_from_int(typ, 1); + let two = context.new_rvalue_from_int(typ, 2); + let four = context.new_rvalue_from_int(typ, 4); - step4 - }, - 32 => { - // TODO(antoyo): Refactor with other implementations. // First step. - let left = self.and(value, context.new_rvalue_from_long(typ, 0x55555555)); - let left = self.shl(left, context.new_rvalue_from_long(typ, 1)); - let right = self.and(value, context.new_rvalue_from_long(typ, 0xAAAAAAAA)); - let right = self.lshr(right, context.new_rvalue_from_long(typ, 1)); + let left = self.lshr(value, one); + let left = self.and(left, m0); + let right = self.and(value, m0); + let right = self.shl(right, one); let step1 = self.or(left, right); // Second step. - let left = self.and(step1, context.new_rvalue_from_long(typ, 0x33333333)); - let left = self.shl(left, context.new_rvalue_from_long(typ, 2)); - let right = self.and(step1, context.new_rvalue_from_long(typ, 0xCCCCCCCC)); - let right = self.lshr(right, context.new_rvalue_from_long(typ, 2)); + let left = self.lshr(step1, two); + let left = self.and(left, m1); + let right = self.and(step1, m1); + let right = self.shl(right, two); let step2 = self.or(left, right); // Third step. - let left = self.and(step2, context.new_rvalue_from_long(typ, 0x0F0F0F0F)); - let left = self.shl(left, context.new_rvalue_from_long(typ, 4)); - let right = self.and(step2, context.new_rvalue_from_long(typ, 0xF0F0F0F0)); - let right = self.lshr(right, context.new_rvalue_from_long(typ, 4)); + let left = self.lshr(step2, four); + let left = self.and(left, m2); + let right = self.and(step2, m2); + let right = self.shl(right, four); let step3 = self.or(left, right); // Fourth step. - let left = self.and(step3, context.new_rvalue_from_long(typ, 0x00FF00FF)); - let left = self.shl(left, context.new_rvalue_from_long(typ, 8)); - let right = self.and(step3, context.new_rvalue_from_long(typ, 0xFF00FF00)); - let right = self.lshr(right, context.new_rvalue_from_long(typ, 8)); - let step4 = self.or(left, right); - - // Fifth step. - let left = self.and(step4, context.new_rvalue_from_long(typ, 0x0000FFFF)); - let left = self.shl(left, context.new_rvalue_from_long(typ, 16)); - let right = self.and(step4, context.new_rvalue_from_long(typ, 0xFFFF0000)); - let right = self.lshr(right, context.new_rvalue_from_long(typ, 16)); - let step5 = self.or(left, right); - - step5 - }, - 64 => { - // First step. - let left = self.shl(value, context.new_rvalue_from_long(typ, 32)); - let right = self.lshr(value, context.new_rvalue_from_long(typ, 32)); - let step1 = self.or(left, right); - - // Second step. - let left = self.and(step1, context.new_rvalue_from_long(typ, 0x0001FFFF0001FFFF)); - let left = self.shl(left, context.new_rvalue_from_long(typ, 15)); - let right = self.and(step1, context.new_rvalue_from_long(typ, 0xFFFE0000FFFE0000u64 as i64)); // TODO(antoyo): transmute the number instead? - let right = self.lshr(right, context.new_rvalue_from_long(typ, 17)); - let step2 = self.or(left, right); - - // Third step. - let left = self.lshr(step2, context.new_rvalue_from_long(typ, 10)); - let left = self.xor(step2, left); - let temp = self.and(left, context.new_rvalue_from_long(typ, 0x003F801F003F801F)); - - let left = self.shl(temp, context.new_rvalue_from_long(typ, 10)); - let left = self.or(temp, left); - let step3 = self.xor(left, step2); - - // Fourth step. - let left = self.lshr(step3, context.new_rvalue_from_long(typ, 4)); - let left = self.xor(step3, left); - let temp = self.and(left, context.new_rvalue_from_long(typ, 0x0E0384210E038421)); - - let left = self.shl(temp, context.new_rvalue_from_long(typ, 4)); - let left = self.or(temp, left); - let step4 = self.xor(left, step3); - - // Fifth step. - let left = self.lshr(step4, context.new_rvalue_from_long(typ, 2)); - let left = self.xor(step4, left); - let temp = self.and(left, context.new_rvalue_from_long(typ, 0x2248884222488842)); - - let left = self.shl(temp, context.new_rvalue_from_long(typ, 2)); - let left = self.or(temp, left); - let step5 = self.xor(left, step4); - - step5 + if width == 8 { + step3 + } else { + self.gcc_bswap(step3, width) + } }, 128 => { // TODO(antoyo): find a more efficient implementation? @@ -1236,19 +1162,19 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>(cx: &'a CodegenCx<'gcc, 'tcx>, codegen: &mut // Define the type up front for the signature of the rust_try function. let tcx = cx.tcx; - let i8p = tcx.mk_mut_ptr(tcx.types.i8); + let i8p = Ty::new_mut_ptr(tcx,tcx.types.i8); // `unsafe fn(*mut i8) -> ()` - let try_fn_ty = tcx.mk_fn_ptr(ty::Binder::dummy(tcx.mk_fn_sig( + let try_fn_ty = Ty::new_fn_ptr(tcx,ty::Binder::dummy(tcx.mk_fn_sig( iter::once(i8p), - tcx.mk_unit(), + Ty::new_unit(tcx,), false, rustc_hir::Unsafety::Unsafe, Abi::Rust, ))); // `unsafe fn(*mut i8, *mut i8) -> ()` - let catch_fn_ty = tcx.mk_fn_ptr(ty::Binder::dummy(tcx.mk_fn_sig( + let catch_fn_ty = Ty::new_fn_ptr(tcx,ty::Binder::dummy(tcx.mk_fn_sig( [i8p, i8p].iter().cloned(), - tcx.mk_unit(), + Ty::new_unit(tcx,), false, rustc_hir::Unsafety::Unsafe, Abi::Rust, diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index b59c3a64f5728..85d3e7234a0e6 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -1,11 +1,11 @@ -#[cfg(feature="master")] -use gccjit::{ComparisonOp, UnaryOp}; use gccjit::ToRValue; use gccjit::{BinaryOp, RValue, Type}; +#[cfg(feature = "master")] +use gccjit::{ComparisonOp, UnaryOp}; use rustc_codegen_ssa::base::compare_simd_types; use rustc_codegen_ssa::common::{IntPredicate, TypeKind}; -#[cfg(feature="master")] +#[cfg(feature = "master")] use rustc_codegen_ssa::errors::ExpectedPointerMutability; use rustc_codegen_ssa::errors::InvalidMonomorphization; use rustc_codegen_ssa::mir::operand::OperandRef; @@ -19,21 +19,8 @@ use rustc_span::{sym, Span, Symbol}; use rustc_target::abi::Align; use crate::builder::Builder; -#[cfg(feature="master")] +#[cfg(feature = "master")] use crate::context::CodegenCx; -#[cfg(feature="master")] -use crate::errors::{InvalidMonomorphizationExpectedSignedUnsigned, InvalidMonomorphizationInsertedType}; -use crate::errors::{ - InvalidMonomorphizationExpectedSimd, - InvalidMonomorphizationInvalidBitmask, - InvalidMonomorphizationInvalidFloatVector, InvalidMonomorphizationMaskType, - InvalidMonomorphizationMismatchedLengths, InvalidMonomorphizationNotFloat, - InvalidMonomorphizationReturnElement, InvalidMonomorphizationReturnIntegerType, - InvalidMonomorphizationReturnLength, InvalidMonomorphizationReturnLengthInputType, - InvalidMonomorphizationReturnType, InvalidMonomorphizationSimdShuffle, - InvalidMonomorphizationUnrecognized, InvalidMonomorphizationUnsupportedElement, - InvalidMonomorphizationUnsupportedOperation, -}; pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( bx: &mut Builder<'a, 'gcc, 'tcx>, @@ -59,16 +46,8 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( }; } macro_rules! require_simd { - ($ty: expr, $position: expr) => { - require!( - $ty.is_simd(), - InvalidMonomorphizationExpectedSimd { - span, - name, - position: $position, - found_ty: $ty - } - ) + ($ty: expr, $diag: expr) => { + require!($ty.is_simd(), $diag) }; } @@ -78,7 +57,10 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let arg_tys = sig.inputs(); if name == sym::simd_select_bitmask { - require_simd!(arg_tys[1], "argument"); + require_simd!( + arg_tys[1], + InvalidMonomorphization::SimdArgument { span, name, ty: arg_tys[1] } + ); let (len, _) = arg_tys[1].simd_size_and_type(bx.tcx()); let expected_int_bits = (len.max(8) - 1).next_power_of_two(); @@ -99,10 +81,10 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let ptr = bx.pointercast(place.llval, bx.cx.type_ptr_to(int_ty)); bx.load(int_ty, ptr, Align::ONE) } - _ => return_error!(InvalidMonomorphizationInvalidBitmask { + _ => return_error!(InvalidMonomorphization::InvalidBitmask { span, name, - ty: mask_ty, + mask_ty, expected_int_bits, expected_bytes }), @@ -116,7 +98,8 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( // NOTE: since the arguments can be vectors of floats, make sure the mask is a vector of // integer. let mask_element_type = bx.type_ix(arg1_element_type.get_size() as u64 * 8); - let vector_mask_type = bx.context.new_vector_type(mask_element_type, arg1_vector_type.get_num_units() as u64); + let vector_mask_type = + bx.context.new_vector_type(mask_element_type, arg1_vector_type.get_num_units() as u64); let mut elements = vec![]; let one = bx.context.new_rvalue_one(mask.get_type()); @@ -131,7 +114,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( } // every intrinsic below takes a SIMD vector as its first argument - require_simd!(arg_tys[0], "input"); + require_simd!(arg_tys[0], InvalidMonomorphization::SimdInput { span, name, ty: arg_tys[0] }); let in_ty = arg_tys[0]; let comparison = match name { @@ -146,12 +129,12 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let (in_len, in_elem) = arg_tys[0].simd_size_and_type(bx.tcx()); if let Some(cmp_op) = comparison { - require_simd!(ret_ty, "return"); + require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); let (out_len, out_ty) = ret_ty.simd_size_and_type(bx.tcx()); require!( in_len == out_len, - InvalidMonomorphizationReturnLengthInputType { + InvalidMonomorphization::ReturnLengthInputType { span, name, in_len, @@ -162,51 +145,42 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( ); require!( bx.type_kind(bx.element_type(llret_ty)) == TypeKind::Integer, - InvalidMonomorphizationReturnIntegerType { span, name, ret_ty, out_ty } + InvalidMonomorphization::ReturnIntegerType { span, name, ret_ty, out_ty } ); - return Ok(compare_simd_types( - bx, - args[0].immediate(), - args[1].immediate(), - in_elem, - llret_ty, - cmp_op, - )); + let arg1 = args[0].immediate(); + // NOTE: we get different vector types for the same vector type and libgccjit doesn't + // compare them as equal, so bitcast. + // FIXME(antoyo): allow comparing vector types as equal in libgccjit. + let arg2 = bx.context.new_bitcast(None, args[1].immediate(), arg1.get_type()); + return Ok(compare_simd_types(bx, arg1, arg2, in_elem, llret_ty, cmp_op)); } - if let Some(stripped) = name.as_str().strip_prefix("simd_shuffle") { - let n: u64 = if stripped.is_empty() { - // Make sure this is actually an array, since typeck only checks the length-suffixed - // version of this intrinsic. - match args[2].layout.ty.kind() { - ty::Array(ty, len) if matches!(ty.kind(), ty::Uint(ty::UintTy::U32)) => { - len.try_eval_target_usize(bx.cx.tcx, ty::ParamEnv::reveal_all()).unwrap_or_else( - || span_bug!(span, "could not evaluate shuffle index array length"), - ) - } - _ => return_error!(InvalidMonomorphizationSimdShuffle { - span, - name, - ty: args[2].layout.ty - }), + if name == sym::simd_shuffle { + // Make sure this is actually an array, since typeck only checks the length-suffixed + // version of this intrinsic. + let n: u64 = match args[2].layout.ty.kind() { + ty::Array(ty, len) if matches!(ty.kind(), ty::Uint(ty::UintTy::U32)) => { + len.try_eval_target_usize(bx.cx.tcx, ty::ParamEnv::reveal_all()).unwrap_or_else( + || span_bug!(span, "could not evaluate shuffle index array length"), + ) } - } else { - stripped.parse().unwrap_or_else(|_| { - span_bug!(span, "bad `simd_shuffle` instruction only caught in codegen?") - }) + _ => return_error!(InvalidMonomorphization::SimdShuffle { + span, + name, + ty: args[2].layout.ty + }), }; - - require_simd!(ret_ty, "return"); + require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); let (out_len, out_ty) = ret_ty.simd_size_and_type(bx.tcx()); require!( out_len == n, - InvalidMonomorphizationReturnLength { span, name, in_len: n, ret_ty, out_len } + InvalidMonomorphization::ReturnLength { span, name, in_len: n, ret_ty, out_len } ); require!( in_elem == out_ty, - InvalidMonomorphizationReturnElement { span, name, in_elem, in_ty, ret_ty, out_ty } + InvalidMonomorphization::ReturnElement { span, name, in_elem, in_ty, ret_ty, out_ty } ); let vector = args[2].immediate(); @@ -218,7 +192,13 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( if name == sym::simd_insert { require!( in_elem == arg_tys[2], - InvalidMonomorphizationInsertedType { span, name, in_elem, in_ty, out_ty: arg_tys[2] } + InvalidMonomorphization::InsertedType { + span, + name, + in_elem, + in_ty, + out_ty: arg_tys[2] + } ); let vector = args[0].immediate(); let index = args[1].immediate(); @@ -235,7 +215,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( if name == sym::simd_extract { require!( ret_ty == in_elem, - InvalidMonomorphizationReturnType { span, name, in_elem, in_ty, ret_ty } + InvalidMonomorphization::ReturnType { span, name, in_elem, in_ty, ret_ty } ); let vector = args[0].immediate(); return Ok(bx.context.new_vector_access(None, vector, args[1].immediate()).to_rvalue()); @@ -244,26 +224,29 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( if name == sym::simd_select { let m_elem_ty = in_elem; let m_len = in_len; - require_simd!(arg_tys[1], "argument"); + require_simd!( + arg_tys[1], + InvalidMonomorphization::SimdArgument { span, name, ty: arg_tys[1] } + ); let (v_len, _) = arg_tys[1].simd_size_and_type(bx.tcx()); require!( m_len == v_len, - InvalidMonomorphizationMismatchedLengths { span, name, m_len, v_len } + InvalidMonomorphization::MismatchedLengths { span, name, m_len, v_len } ); match m_elem_ty.kind() { ty::Int(_) => {} - _ => return_error!(InvalidMonomorphizationMaskType { span, name, ty: m_elem_ty }), + _ => return_error!(InvalidMonomorphization::MaskType { span, name, ty: m_elem_ty }), } return Ok(bx.vector_select(args[0].immediate(), args[1].immediate(), args[2].immediate())); } - #[cfg(feature="master")] + #[cfg(feature = "master")] if name == sym::simd_cast || name == sym::simd_as { - require_simd!(ret_ty, "return"); + require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); let (out_len, out_elem) = ret_ty.simd_size_and_type(bx.tcx()); require!( in_len == out_len, - InvalidMonomorphizationReturnLengthInputType { + InvalidMonomorphization::ReturnLengthInputType { span, name, in_len, @@ -283,19 +266,17 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( Unsupported, } - let in_style = - match in_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, - ty::Float(_) => Style::Float, - _ => Style::Unsupported, - }; + let in_style = match in_elem.kind() { + ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Float(_) => Style::Float, + _ => Style::Unsupported, + }; - let out_style = - match out_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, - ty::Float(_) => Style::Float, - _ => Style::Unsupported, - }; + let out_style = match out_elem.kind() { + ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Float(_) => Style::Float, + _ => Style::Unsupported, + }; match (in_style, out_style) { (Style::Unsupported, Style::Unsupported) => { @@ -310,7 +291,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( out_elem } ); - }, + } _ => return Ok(bx.context.convert_vector(None, args[0].immediate(), llret_ty)), } } @@ -324,7 +305,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( })* _ => {}, } - return_error!(InvalidMonomorphizationUnsupportedOperation { span, name, in_ty, in_elem }) + return_error!(InvalidMonomorphization::UnsupportedOperation { span, name, in_ty, in_elem }) })* } } @@ -341,7 +322,8 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( // endian and MSB-first for big endian. let vector = args[0].immediate(); - let vector_type = vector.get_type().dyncast_vector().expect("vector type"); + // TODO(antoyo): dyncast_vector should not require a call to unqualified. + let vector_type = vector.get_type().unqualified().dyncast_vector().expect("vector type"); let elem_type = vector_type.get_element_type(); let expected_int_bits = in_len.max(8); @@ -357,10 +339,13 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let mut shift = 0; for i in 0..in_len { - let elem = bx.extract_element(vector, bx.context.new_rvalue_from_int(bx.int_type, i as i32)); + let elem = + bx.extract_element(vector, bx.context.new_rvalue_from_int(bx.int_type, i as i32)); let shifted = elem >> sign_shift; let masked = shifted & one; - result = result | (bx.context.new_cast(None, masked, result_type) << bx.context.new_rvalue_from_int(result_type, shift)); + result = result + | (bx.context.new_cast(None, masked, result_type) + << bx.context.new_rvalue_from_int(result_type, shift)); shift += 1; } @@ -409,46 +394,50 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Err(()); }}; } - let (elem_ty_str, elem_ty) = - if let ty::Float(f) = in_elem.kind() { - let elem_ty = bx.cx.type_float_from_ty(*f); - match f.bit_width() { - 32 => ("f", elem_ty), - 64 => ("", elem_ty), - _ => { - return_error!(InvalidMonomorphizationInvalidFloatVector { span, name, elem_ty: f.name_str(), vec_ty: in_ty }); - } + let (elem_ty_str, elem_ty) = if let ty::Float(f) = in_elem.kind() { + let elem_ty = bx.cx.type_float_from_ty(*f); + match f.bit_width() { + 32 => ("f", elem_ty), + 64 => ("", elem_ty), + _ => { + return_error!(InvalidMonomorphization::FloatingPointVector { + span, + name, + f_ty: *f, + in_ty + }); } } - else { - return_error!(InvalidMonomorphizationNotFloat { span, name, ty: in_ty }); - }; + } else { + return_error!(InvalidMonomorphization::FloatingPointType { span, name, in_ty }); + }; let vec_ty = bx.cx.type_vector(elem_ty, in_len); - let intr_name = - match name { - sym::simd_ceil => "ceil", - sym::simd_fabs => "fabs", // TODO(antoyo): pand with 170141183420855150465331762880109871103 - sym::simd_fcos => "cos", - sym::simd_fexp2 => "exp2", - sym::simd_fexp => "exp", - sym::simd_flog10 => "log10", - sym::simd_flog2 => "log2", - sym::simd_flog => "log", - sym::simd_floor => "floor", - sym::simd_fma => "fma", - sym::simd_fpowi => "__builtin_powi", - sym::simd_fpow => "pow", - sym::simd_fsin => "sin", - sym::simd_fsqrt => "sqrt", - sym::simd_round => "round", - sym::simd_trunc => "trunc", - _ => return_error!(InvalidMonomorphizationUnrecognized { span, name }) - }; + let intr_name = match name { + sym::simd_ceil => "ceil", + sym::simd_fabs => "fabs", // TODO(antoyo): pand with 170141183420855150465331762880109871103 + sym::simd_fcos => "cos", + sym::simd_fexp2 => "exp2", + sym::simd_fexp => "exp", + sym::simd_flog10 => "log10", + sym::simd_flog2 => "log2", + sym::simd_flog => "log", + sym::simd_floor => "floor", + sym::simd_fma => "fma", + sym::simd_fpowi => "__builtin_powi", + sym::simd_fpow => "pow", + sym::simd_fsin => "sin", + sym::simd_fsqrt => "sqrt", + sym::simd_round => "round", + sym::simd_trunc => "trunc", + _ => return_error!(InvalidMonomorphization::UnrecognizedIntrinsic { span, name }), + }; let builtin_name = format!("{}{}", intr_name, elem_ty_str); let funcs = bx.cx.functions.borrow(); - let function = funcs.get(&builtin_name).unwrap_or_else(|| panic!("unable to find builtin function {}", builtin_name)); + let function = funcs + .get(&builtin_name) + .unwrap_or_else(|| panic!("unable to find builtin function {}", builtin_name)); // TODO(antoyo): add platform-specific behavior here for architectures that have these // intrinsics as instructions (for instance, gpus) @@ -494,8 +483,12 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return simd_simple_float_intrinsic(name, in_elem, in_ty, in_len, bx, span, args); } - #[cfg(feature="master")] - fn vector_ty<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, elem_ty: Ty<'tcx>, vec_len: u64) -> Type<'gcc> { + #[cfg(feature = "master")] + fn vector_ty<'gcc, 'tcx>( + cx: &CodegenCx<'gcc, 'tcx>, + elem_ty: Ty<'tcx>, + vec_len: u64, + ) -> Type<'gcc> { // FIXME: use cx.layout_of(ty).llvm_type() ? let elem_ty = match *elem_ty.kind() { ty::Int(v) => cx.type_int_from_ty(v), @@ -506,15 +499,22 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( cx.type_vector(elem_ty, vec_len) } - #[cfg(feature="master")] - fn gather<'a, 'gcc, 'tcx>(default: RValue<'gcc>, pointers: RValue<'gcc>, mask: RValue<'gcc>, pointer_count: usize, bx: &mut Builder<'a, 'gcc, 'tcx>, in_len: u64, underlying_ty: Ty<'tcx>, invert: bool) -> RValue<'gcc> { - let vector_type = - if pointer_count > 1 { - bx.context.new_vector_type(bx.usize_type, in_len) - } - else { - vector_ty(bx, underlying_ty, in_len) - }; + #[cfg(feature = "master")] + fn gather<'a, 'gcc, 'tcx>( + default: RValue<'gcc>, + pointers: RValue<'gcc>, + mask: RValue<'gcc>, + pointer_count: usize, + bx: &mut Builder<'a, 'gcc, 'tcx>, + in_len: u64, + underlying_ty: Ty<'tcx>, + invert: bool, + ) -> RValue<'gcc> { + let vector_type = if pointer_count > 1 { + bx.context.new_vector_type(bx.usize_type, in_len) + } else { + vector_ty(bx, underlying_ty, in_len) + }; let elem_type = vector_type.dyncast_vector().expect("vector type").get_element_type(); let mut values = vec![]; @@ -545,13 +545,12 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( if invert { bx.shuffle_vector(vector, default, mask) - } - else { + } else { bx.shuffle_vector(default, vector, mask) } } - #[cfg(feature="master")] + #[cfg(feature = "master")] if name == sym::simd_gather { // simd_gather(values: , pointers: , // mask: ) -> @@ -560,10 +559,16 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( // * M: any integer width is supported, will be truncated to i1 // All types must be simd vector types - require_simd!(in_ty, "first"); - require_simd!(arg_tys[1], "second"); - require_simd!(arg_tys[2], "third"); - require_simd!(ret_ty, "return"); + require_simd!(in_ty, InvalidMonomorphization::SimdFirst { span, name, ty: in_ty }); + require_simd!( + arg_tys[1], + InvalidMonomorphization::SimdSecond { span, name, ty: arg_tys[1] } + ); + require_simd!( + arg_tys[2], + InvalidMonomorphization::SimdThird { span, name, ty: arg_tys[2] } + ); + require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); // Of the same length: let (out_len, _) = arg_tys[1].simd_size_and_type(bx.tcx()); @@ -656,10 +661,19 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( } } - return Ok(gather(args[0].immediate(), args[1].immediate(), args[2].immediate(), pointer_count, bx, in_len, underlying_ty, false)); + return Ok(gather( + args[0].immediate(), + args[1].immediate(), + args[2].immediate(), + pointer_count, + bx, + in_len, + underlying_ty, + false, + )); } - #[cfg(feature="master")] + #[cfg(feature = "master")] if name == sym::simd_scatter { // simd_scatter(values: , pointers: , // mask: ) -> () @@ -668,9 +682,15 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( // * M: any integer width is supported, will be truncated to i1 // All types must be simd vector types - require_simd!(in_ty, "first"); - require_simd!(arg_tys[1], "second"); - require_simd!(arg_tys[2], "third"); + require_simd!(in_ty, InvalidMonomorphization::SimdFirst { span, name, ty: in_ty }); + require_simd!( + arg_tys[1], + InvalidMonomorphization::SimdSecond { span, name, ty: arg_tys[1] } + ); + require_simd!( + arg_tys[2], + InvalidMonomorphization::SimdThird { span, name, ty: arg_tys[2] } + ); // Of the same length: let (element_len1, _) = arg_tys[1].simd_size_and_type(bx.tcx()); @@ -759,17 +779,24 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( } } - let result = gather(args[0].immediate(), args[1].immediate(), args[2].immediate(), pointer_count, bx, in_len, underlying_ty, true); + let result = gather( + args[0].immediate(), + args[1].immediate(), + args[2].immediate(), + pointer_count, + bx, + in_len, + underlying_ty, + true, + ); let pointers = args[1].immediate(); - let vector_type = - if pointer_count > 1 { - bx.context.new_vector_type(bx.usize_type, in_len) - } - else { - vector_ty(bx, underlying_ty, in_len) - }; + let vector_type = if pointer_count > 1 { + bx.context.new_vector_type(bx.usize_type, in_len) + } else { + vector_ty(bx, underlying_ty, in_len) + }; let elem_type = vector_type.dyncast_vector().expect("vector type").get_element_type(); for i in 0..in_len { @@ -809,7 +836,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( })* _ => {}, } - return_error!(InvalidMonomorphizationUnsupportedOperation { span, name, in_ty, in_elem }) + return_error!(InvalidMonomorphization::UnsupportedOperation { span, name, in_ty, in_elem }) })* } } @@ -824,89 +851,97 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let rhs = args[1].immediate(); let is_add = name == sym::simd_saturating_add; let ptr_bits = bx.tcx().data_layout.pointer_size.bits() as _; - let (signed, elem_width, elem_ty) = - match *in_elem.kind() { - ty::Int(i) => (true, i.bit_width().unwrap_or(ptr_bits) / 8, bx.cx.type_int_from_ty(i)), - ty::Uint(i) => (false, i.bit_width().unwrap_or(ptr_bits) / 8, bx.cx.type_uint_from_ty(i)), - _ => { - return_error!(InvalidMonomorphizationExpectedSignedUnsigned { + let (signed, elem_width, elem_ty) = match *in_elem.kind() { + ty::Int(i) => (true, i.bit_width().unwrap_or(ptr_bits) / 8, bx.cx.type_int_from_ty(i)), + ty::Uint(i) => { + (false, i.bit_width().unwrap_or(ptr_bits) / 8, bx.cx.type_uint_from_ty(i)) + } + _ => { + return_error!(InvalidMonomorphization::ExpectedVectorElementType { span, name, - elem_ty: arg_tys[0].simd_size_and_type(bx.tcx()).1, - vec_ty: arg_tys[0], + expected_element: arg_tys[0].simd_size_and_type(bx.tcx()).1, + vector_type: arg_tys[0], }); } }; - let result = - match (signed, is_add) { - (false, true) => { - let res = lhs + rhs; - let cmp = bx.context.new_comparison(None, ComparisonOp::LessThan, res, lhs); - res | cmp - }, - (true, true) => { - // Algorithm from: https://codereview.stackexchange.com/questions/115869/saturated-signed-addition - // TODO(antoyo): improve using conditional operators if possible. - let arg_type = lhs.get_type(); - // TODO(antoyo): convert lhs and rhs to unsigned. - let sum = lhs + rhs; - let vector_type = arg_type.dyncast_vector().expect("vector type"); - let unit = vector_type.get_num_units(); - let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); - let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); - - let xor1 = lhs ^ rhs; - let xor2 = lhs ^ sum; - let and = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; - let mask = and >> width; - - let one = bx.context.new_rvalue_one(elem_ty); - let ones = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); - let shift1 = ones << width; - let shift2 = sum >> width; - let mask_min = shift1 ^ shift2; - - let and1 = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; - let and2 = mask & mask_min; - - and1 + and2 - }, - (false, false) => { - let res = lhs - rhs; - let cmp = bx.context.new_comparison(None, ComparisonOp::LessThanEquals, res, lhs); - res & cmp - }, - (true, false) => { - let arg_type = lhs.get_type(); - // TODO(antoyo): this uses the same algorithm from saturating add, but add the - // negative of the right operand. Find a proper subtraction algorithm. - let rhs = bx.context.new_unary_op(None, UnaryOp::Minus, arg_type, rhs); - - // TODO(antoyo): convert lhs and rhs to unsigned. - let sum = lhs + rhs; - let vector_type = arg_type.dyncast_vector().expect("vector type"); - let unit = vector_type.get_num_units(); - let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); - let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); - - let xor1 = lhs ^ rhs; - let xor2 = lhs ^ sum; - let and = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; - let mask = and >> width; - - let one = bx.context.new_rvalue_one(elem_ty); - let ones = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); - let shift1 = ones << width; - let shift2 = sum >> width; - let mask_min = shift1 ^ shift2; - - let and1 = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; - let and2 = mask & mask_min; - - and1 + and2 - } - }; + let result = match (signed, is_add) { + (false, true) => { + let res = lhs + rhs; + let cmp = bx.context.new_comparison(None, ComparisonOp::LessThan, res, lhs); + res | cmp + } + (true, true) => { + // Algorithm from: https://codereview.stackexchange.com/questions/115869/saturated-signed-addition + // TODO(antoyo): improve using conditional operators if possible. + // TODO(antoyo): dyncast_vector should not require a call to unqualified. + let arg_type = lhs.get_type().unqualified(); + // TODO(antoyo): convert lhs and rhs to unsigned. + let sum = lhs + rhs; + let vector_type = arg_type.dyncast_vector().expect("vector type"); + let unit = vector_type.get_num_units(); + let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); + let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); + + let xor1 = lhs ^ rhs; + let xor2 = lhs ^ sum; + let and = + bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; + let mask = and >> width; + + let one = bx.context.new_rvalue_one(elem_ty); + let ones = + bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); + let shift1 = ones << width; + let shift2 = sum >> width; + let mask_min = shift1 ^ shift2; + + let and1 = + bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; + let and2 = mask & mask_min; + + and1 + and2 + } + (false, false) => { + let res = lhs - rhs; + let cmp = bx.context.new_comparison(None, ComparisonOp::LessThanEquals, res, lhs); + res & cmp + } + (true, false) => { + // TODO(antoyo): dyncast_vector should not require a call to unqualified. + let arg_type = lhs.get_type().unqualified(); + // TODO(antoyo): this uses the same algorithm from saturating add, but add the + // negative of the right operand. Find a proper subtraction algorithm. + let rhs = bx.context.new_unary_op(None, UnaryOp::Minus, arg_type, rhs); + + // TODO(antoyo): convert lhs and rhs to unsigned. + let sum = lhs + rhs; + let vector_type = arg_type.dyncast_vector().expect("vector type"); + let unit = vector_type.get_num_units(); + let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); + let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); + + let xor1 = lhs ^ rhs; + let xor2 = lhs ^ sum; + let and = + bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; + let mask = and >> width; + + let one = bx.context.new_rvalue_one(elem_ty); + let ones = + bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); + let shift1 = ones << width; + let shift2 = sum >> width; + let mask_min = shift1 ^ shift2; + + let and1 = + bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; + let and2 = mask & mask_min; + + and1 + and2 + } + }; return Ok(result); } @@ -917,7 +952,7 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( if name == sym::$name { require!( ret_ty == in_elem, - InvalidMonomorphizationReturnType { span, name, in_elem, in_ty, ret_ty } + InvalidMonomorphization::ReturnType { span, name, in_elem, in_ty, ret_ty } ); return match in_elem.kind() { ty::Int(_) | ty::Uint(_) => { @@ -939,11 +974,12 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( Ok(bx.vector_reduce_op(args[0].immediate(), $vec_op)) } } - _ => return_error!(InvalidMonomorphizationUnsupportedElement { + _ => return_error!(InvalidMonomorphization::UnsupportedSymbol { span, name, + symbol: sym::$name, in_ty, - elem_ty: in_elem, + in_elem, ret_ty }), }; @@ -980,18 +1016,24 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( 1.0 ); - macro_rules! minmax_red { ($name:ident: $int_red:ident, $float_red:ident) => { if name == sym::$name { require!( ret_ty == in_elem, - InvalidMonomorphizationReturnType { span, name, in_elem, in_ty, ret_ty } + InvalidMonomorphization::ReturnType { span, name, in_elem, in_ty, ret_ty } ); return match in_elem.kind() { ty::Int(_) | ty::Uint(_) => Ok(bx.$int_red(args[0].immediate())), ty::Float(_) => Ok(bx.$float_red(args[0].immediate())), - _ => return_error!(InvalidMonomorphizationUnsupportedElement { span, name, in_ty, elem_ty: in_elem, ret_ty }), + _ => return_error!(InvalidMonomorphization::UnsupportedSymbol { + span, + name, + symbol: sym::$name, + in_ty, + in_elem, + ret_ty + }), }; } }; @@ -1009,17 +1051,18 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( let input = if !$boolean { require!( ret_ty == in_elem, - InvalidMonomorphizationReturnType { span, name, in_elem, in_ty, ret_ty } + InvalidMonomorphization::ReturnType { span, name, in_elem, in_ty, ret_ty } ); args[0].immediate() } else { match in_elem.kind() { ty::Int(_) | ty::Uint(_) => {} - _ => return_error!(InvalidMonomorphizationUnsupportedElement { + _ => return_error!(InvalidMonomorphization::UnsupportedSymbol { span, name, + symbol: sym::$name, in_ty, - elem_ty: in_elem, + in_elem, ret_ty }), } @@ -1029,13 +1072,22 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return match in_elem.kind() { ty::Int(_) | ty::Uint(_) => { let r = bx.vector_reduce_op(input, $op); - Ok(if !$boolean { r } else { bx.icmp(IntPredicate::IntNE, r, bx.context.new_rvalue_zero(r.get_type())) }) + Ok(if !$boolean { + r + } else { + bx.icmp( + IntPredicate::IntNE, + r, + bx.context.new_rvalue_zero(r.get_type()), + ) + }) } - _ => return_error!(InvalidMonomorphizationUnsupportedElement { + _ => return_error!(InvalidMonomorphization::UnsupportedSymbol { span, name, + symbol: sym::$name, in_ty, - elem_ty: in_elem, + in_elem, ret_ty }), }; diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 442ce0ea54209..697ae015fed9a 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -71,11 +71,11 @@ use gccjit::{Context, OptimizationLevel, CType}; use rustc_ast::expand::allocator::AllocatorKind; use rustc_codegen_ssa::{CodegenResults, CompiledModule, ModuleCodegen}; use rustc_codegen_ssa::base::codegen_crate; -use rustc_codegen_ssa::back::write::{CodegenContext, FatLTOInput, ModuleConfig, TargetMachineFactoryFn}; +use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryFn}; use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule}; use rustc_codegen_ssa::target_features::supported_target_features; use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, ModuleBufferMethods, ThinBufferMethods, WriteBackendMethods}; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_errors::{DiagnosticMessage, ErrorGuaranteed, Handler, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_metadata::EncodedMetadata; @@ -111,6 +111,8 @@ impl CodegenBackend for GccCodegenBackend { } fn init(&self, sess: &Session) { + #[cfg(feature="master")] + gccjit::set_global_personality_function_name(b"rust_eh_personality\0"); if sess.lto() != Lto::No { sess.emit_warning(LTONotSupported {}); } @@ -137,7 +139,7 @@ impl CodegenBackend for GccCodegenBackend { Box::new(res) } - fn join_codegen(&self, ongoing_codegen: Box, sess: &Session, _outputs: &OutputFilenames) -> Result<(CodegenResults, FxHashMap), ErrorGuaranteed> { + fn join_codegen(&self, ongoing_codegen: Box, sess: &Session, _outputs: &OutputFilenames) -> Result<(CodegenResults, FxIndexMap), ErrorGuaranteed> { let (codegen_results, work_products) = ongoing_codegen .downcast::>() .expect("Expected GccCodegenBackend's OngoingCodegen, found Box") @@ -215,14 +217,14 @@ impl WriteBackendMethods for GccCodegenBackend { type ThinData = (); type ThinBuffer = ThinBuffer; - fn run_fat_lto(_cgcx: &CodegenContext, mut modules: Vec>, _cached_modules: Vec<(SerializedModule, WorkProduct)>) -> Result, FatalError> { + fn run_fat_lto(_cgcx: &CodegenContext, mut modules: Vec>, _cached_modules: Vec<(SerializedModule, WorkProduct)>) -> Result, FatalError> { // TODO(antoyo): implement LTO by sending -flto to libgccjit and adding the appropriate gcc linker plugins. // NOTE: implemented elsewhere. // TODO(antoyo): what is implemented elsewhere ^ ? let module = match modules.remove(0) { - FatLTOInput::InMemory(module) => module, - FatLTOInput::Serialized { .. } => { + FatLtoInput::InMemory(module) => module, + FatLtoInput::Serialized { .. } => { unimplemented!(); } }; @@ -237,6 +239,10 @@ impl WriteBackendMethods for GccCodegenBackend { unimplemented!(); } + fn print_statistics(&self) { + unimplemented!() + } + unsafe fn optimize(_cgcx: &CodegenContext, _diag_handler: &Handler, module: &ModuleCodegen, config: &ModuleConfig) -> Result<(), FatalError> { module.module_llvm.context.set_optimization_level(to_gcc_opt_level(config.opt_level)); Ok(()) diff --git a/compiler/rustc_codegen_gcc/src/mono_item.rs b/compiler/rustc_codegen_gcc/src/mono_item.rs index 342b830cedb12..3322d56513bbe 100644 --- a/compiler/rustc_codegen_gcc/src/mono_item.rs +++ b/compiler/rustc_codegen_gcc/src/mono_item.rs @@ -31,7 +31,7 @@ impl<'gcc, 'tcx> PreDefineMethods<'tcx> for CodegenCx<'gcc, 'tcx> { #[cfg_attr(not(feature="master"), allow(unused_variables))] fn predefine_fn(&self, instance: Instance<'tcx>, linkage: Linkage, visibility: Visibility, symbol_name: &str) { - assert!(!instance.substs.has_infer()); + assert!(!instance.args.has_infer()); let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty()); self.linkage.set(base::linkage_to_gcc(linkage)); diff --git a/compiler/rustc_codegen_gcc/src/type_.rs b/compiler/rustc_codegen_gcc/src/type_.rs index 521b64ad34d15..31899740514ae 100644 --- a/compiler/rustc_codegen_gcc/src/type_.rs +++ b/compiler/rustc_codegen_gcc/src/type_.rs @@ -54,6 +54,23 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.u128_type } + pub fn type_ptr_to(&self, ty: Type<'gcc>) -> Type<'gcc> { + ty.make_pointer() + } + + pub fn type_ptr_to_ext(&self, ty: Type<'gcc>, _address_space: AddressSpace) -> Type<'gcc> { + // TODO(antoyo): use address_space, perhaps with TYPE_ADDR_SPACE? + ty.make_pointer() + } + + pub fn type_i8p(&self) -> Type<'gcc> { + self.type_ptr_to(self.type_i8()) + } + + pub fn type_i8p_ext(&self, address_space: AddressSpace) -> Type<'gcc> { + self.type_ptr_to_ext(self.type_i8(), address_space) + } + pub fn type_pointee_for_align(&self, align: Align) -> Type<'gcc> { // FIXME(eddyb) We could find a better approximation if ity.align < align. let ity = Integer::approximate_align(self, align); @@ -149,13 +166,12 @@ impl<'gcc, 'tcx> BaseTypeMethods<'tcx> for CodegenCx<'gcc, 'tcx> { } } - fn type_ptr_to(&self, ty: Type<'gcc>) -> Type<'gcc> { - ty.make_pointer() + fn type_ptr(&self) -> Type<'gcc> { + self.type_ptr_to(self.type_void()) } - fn type_ptr_to_ext(&self, ty: Type<'gcc>, _address_space: AddressSpace) -> Type<'gcc> { - // TODO(antoyo): use address_space, perhaps with TYPE_ADDR_SPACE? - ty.make_pointer() + fn type_ptr_ext(&self, address_space: AddressSpace) -> Type<'gcc> { + self.type_ptr_to_ext(self.type_void(), address_space) } fn element_type(&self, ty: Type<'gcc>) -> Type<'gcc> { diff --git a/compiler/rustc_codegen_gcc/src/type_of.rs b/compiler/rustc_codegen_gcc/src/type_of.rs index 5df8c1a209db2..84d5783851273 100644 --- a/compiler/rustc_codegen_gcc/src/type_of.rs +++ b/compiler/rustc_codegen_gcc/src/type_of.rs @@ -101,7 +101,7 @@ fn uncached_gcc_type<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, layout: TyAndLayout if let (&ty::Generator(_, _, _), &Variants::Single { index }) = (layout.ty.kind(), &layout.variants) { - write!(&mut name, "::{}", ty::GeneratorSubsts::variant_name(index)).unwrap(); + write!(&mut name, "::{}", ty::GeneratorArgs::variant_name(index)).unwrap(); } Some(name) } @@ -159,8 +159,7 @@ impl<'tcx> LayoutGccExt<'tcx> for TyAndLayout<'tcx> { fn is_gcc_immediate(&self) -> bool { match self.abi { Abi::Scalar(_) | Abi::Vector { .. } => true, - Abi::ScalarPair(..) => false, - Abi::Uninhabited | Abi::Aggregate { .. } => self.is_zst(), + Abi::ScalarPair(..) | Abi::Uninhabited | Abi::Aggregate { .. } => false, } } @@ -283,8 +282,8 @@ impl<'tcx> LayoutGccExt<'tcx> for TyAndLayout<'tcx> { } // only wide pointer boxes are handled as pointers // thin pointer boxes with scalar allocators are handled by the general logic below - ty::Adt(def, substs) if def.is_box() && cx.layout_of(substs.type_at(1)).is_zst() => { - let ptr_ty = cx.tcx.mk_mut_ptr(self.ty.boxed_ty()); + ty::Adt(def, args) if def.is_box() && cx.layout_of(args.type_at(1)).is_zst() => { + let ptr_ty = Ty::new_mut_ptr(cx.tcx,self.ty.boxed_ty()); return cx.layout_of(ptr_ty).scalar_pair_element_gcc_type(cx, index, immediate); } _ => {} @@ -384,8 +383,8 @@ impl<'gcc, 'tcx> LayoutTypeMethods<'tcx> for CodegenCx<'gcc, 'tcx> { unimplemented!(); } - fn fn_decl_backend_type(&self, _fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Type<'gcc> { - // FIXME(antoyo): return correct type. - self.type_void() + fn fn_decl_backend_type(&self, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Type<'gcc> { + let (return_type, param_types, variadic, _) = fn_abi.gcc_type(self); + self.context.new_function_pointer_type(None, return_type, ¶m_types, variadic) } } diff --git a/compiler/rustc_codegen_gcc/test.sh b/compiler/rustc_codegen_gcc/test.sh index 6139892aefca7..592997b8ab9da 100755 --- a/compiler/rustc_codegen_gcc/test.sh +++ b/compiler/rustc_codegen_gcc/test.sh @@ -214,12 +214,14 @@ function setup_rustc() { rm config.toml || true cat > config.toml < "{}",\n'.format(entry[0], entry[1])) elif "_round_mask" in entry[1]: out.write(' // [INVALID CONVERSION]: "{}" => "{}",\n'.format(entry[0], entry[1])) diff --git a/compiler/rustc_codegen_llvm/Cargo.toml b/compiler/rustc_codegen_llvm/Cargo.toml index ad51f2d095857..be09820d08da2 100644 --- a/compiler/rustc_codegen_llvm/Cargo.toml +++ b/compiler/rustc_codegen_llvm/Cargo.toml @@ -11,7 +11,7 @@ bitflags = "1.0" cstr = "0.2" libc = "0.2" measureme = "10.0.0" -object = { version = "0.31.1", default-features = false, features = [ +object = { version = "0.32.0", default-features = false, features = [ "std", "read", ] } diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl index d77634741fb88..aed4a8f3c85ff 100644 --- a/compiler/rustc_codegen_llvm/messages.ftl +++ b/compiler/rustc_codegen_llvm/messages.ftl @@ -1,90 +1,95 @@ -codegen_llvm_unknown_ctarget_feature = - unknown feature specified for `-Ctarget-feature`: `{$feature}` - .note = it is still passed through to the codegen backend - .possible_feature = you might have meant: `{$rust_feature}` - .consider_filing_feature_request = consider filing a feature request - -codegen_llvm_unknown_ctarget_feature_prefix = - unknown feature specified for `-Ctarget-feature`: `{$feature}` - .note = features must begin with a `+` to enable or `-` to disable it +codegen_llvm_copy_bitcode = failed to copy bitcode to object file: {$err} -codegen_llvm_error_creating_import_library = - Error creating import library for {$lib_name}: {$error} +codegen_llvm_dlltool_fail_import_library = + Dlltool could not create import library with {$dlltool_path} {$dlltool_args}: + {$stdout} + {$stderr} -codegen_llvm_symbol_already_defined = - symbol `{$symbol_name}` is already defined +codegen_llvm_dynamic_linking_with_lto = + cannot prefer dynamic linking when performing LTO + .note = only 'staticlib', 'bin', and 'cdylib' outputs are supported with LTO -codegen_llvm_invalid_minimum_alignment = - invalid minimum global alignment: {$err} +codegen_llvm_error_calling_dlltool = + Error calling dlltool '{$dlltool_path}': {$error} -codegen_llvm_sanitizer_memtag_requires_mte = - `-Zsanitizer=memtag` requires `-Ctarget-feature=+mte` +codegen_llvm_error_creating_import_library = + Error creating import library for {$lib_name}: {$error} codegen_llvm_error_writing_def_file = Error writing .DEF file: {$error} -codegen_llvm_error_calling_dlltool = - Error calling dlltool '{$dlltool_path}': {$error} +codegen_llvm_from_llvm_diag = {$message} -codegen_llvm_dlltool_fail_import_library = - Dlltool could not create import library: {$stdout} - {$stderr} +codegen_llvm_from_llvm_optimization_diag = {$filename}:{$line}:{$column} {$pass_name} ({$kind}): {$message} -codegen_llvm_target_feature_disable_or_enable = - the target features {$features} must all be either enabled or disabled together +codegen_llvm_invalid_minimum_alignment_not_power_of_two = + invalid minimum global alignment: {$align} is not power of 2 -codegen_llvm_missing_features = - add the missing features in a `target_feature` attribute +codegen_llvm_invalid_minimum_alignment_too_large = + invalid minimum global alignment: {$align} is too large -codegen_llvm_dynamic_linking_with_lto = - cannot prefer dynamic linking when performing LTO - .note = only 'staticlib', 'bin', and 'cdylib' outputs are supported with LTO +codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}" +codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err} -codegen_llvm_parse_target_machine_config = - failed to parse target machine config to target machine: {$error} +codegen_llvm_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$llvm_err}) codegen_llvm_lto_disallowed = lto can only be run for executables, cdylibs and static library outputs codegen_llvm_lto_dylib = lto cannot be used for `dylib` crate type without `-Zdylib-lto` -codegen_llvm_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$llvm_err}) +codegen_llvm_missing_features = + add the missing features in a `target_feature` attribute -codegen_llvm_write_output = could not write output to {$path} -codegen_llvm_write_output_with_llvm_err = could not write output to {$path}: {$llvm_err} +codegen_llvm_multiple_source_dicompileunit = multiple source DICompileUnits found +codegen_llvm_multiple_source_dicompileunit_with_llvm_err = multiple source DICompileUnits found: {$llvm_err} -codegen_llvm_target_machine = could not create LLVM TargetMachine for triple: {$triple} -codegen_llvm_target_machine_with_llvm_err = could not create LLVM TargetMachine for triple: {$triple}: {$llvm_err} +codegen_llvm_parse_bitcode = failed to parse bitcode for LTO module +codegen_llvm_parse_bitcode_with_llvm_err = failed to parse bitcode for LTO module: {$llvm_err} + +codegen_llvm_parse_target_machine_config = + failed to parse target machine config to target machine: {$error} + +codegen_llvm_prepare_thin_lto_context = failed to prepare thin LTO context +codegen_llvm_prepare_thin_lto_context_with_llvm_err = failed to prepare thin LTO context: {$llvm_err} + +codegen_llvm_prepare_thin_lto_module = failed to prepare thin LTO module +codegen_llvm_prepare_thin_lto_module_with_llvm_err = failed to prepare thin LTO module: {$llvm_err} 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_memtag_requires_mte = + `-Zsanitizer=memtag` requires `-Ctarget-feature=+mte` + codegen_llvm_serialize_module = failed to serialize module {$name} codegen_llvm_serialize_module_with_llvm_err = failed to serialize module {$name}: {$llvm_err} -codegen_llvm_write_ir = failed to write LLVM IR to {$path} -codegen_llvm_write_ir_with_llvm_err = failed to write LLVM IR to {$path}: {$llvm_err} - -codegen_llvm_prepare_thin_lto_context = failed to prepare thin LTO context -codegen_llvm_prepare_thin_lto_context_with_llvm_err = failed to prepare thin LTO context: {$llvm_err} +codegen_llvm_symbol_already_defined = + symbol `{$symbol_name}` is already defined -codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}" -codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err} +codegen_llvm_target_feature_disable_or_enable = + the target features {$features} must all be either enabled or disabled together -codegen_llvm_write_thinlto_key = error while writing ThinLTO key data: {$err} -codegen_llvm_write_thinlto_key_with_llvm_err = error while writing ThinLTO key data: {$err}: {$llvm_err} +codegen_llvm_target_machine = could not create LLVM TargetMachine for triple: {$triple} +codegen_llvm_target_machine_with_llvm_err = could not create LLVM TargetMachine for triple: {$triple}: {$llvm_err} -codegen_llvm_multiple_source_dicompileunit = multiple source DICompileUnits found -codegen_llvm_multiple_source_dicompileunit_with_llvm_err = multiple source DICompileUnits found: {$llvm_err} +codegen_llvm_unknown_ctarget_feature = + unknown feature specified for `-Ctarget-feature`: `{$feature}` + .note = it is still passed through to the codegen backend + .possible_feature = you might have meant: `{$rust_feature}` + .consider_filing_feature_request = consider filing a feature request -codegen_llvm_prepare_thin_lto_module = failed to prepare thin LTO module -codegen_llvm_prepare_thin_lto_module_with_llvm_err = failed to prepare thin LTO module: {$llvm_err} +codegen_llvm_unknown_ctarget_feature_prefix = + unknown feature specified for `-Ctarget-feature`: `{$feature}` + .note = features must begin with a `+` to enable or `-` to disable it -codegen_llvm_parse_bitcode = failed to parse bitcode for LTO module -codegen_llvm_parse_bitcode_with_llvm_err = failed to parse bitcode for LTO module: {$llvm_err} +codegen_llvm_write_bytecode = failed to write bytecode to {$path}: {$err} -codegen_llvm_from_llvm_optimization_diag = {$filename}:{$line}:{$column} {$pass_name} ({$kind}): {$message} -codegen_llvm_from_llvm_diag = {$message} +codegen_llvm_write_ir = failed to write LLVM IR to {$path} +codegen_llvm_write_ir_with_llvm_err = failed to write LLVM IR to {$path}: {$llvm_err} -codegen_llvm_write_bytecode = failed to write bytecode to {$path}: {$err} +codegen_llvm_write_output = could not write output to {$path} +codegen_llvm_write_output_with_llvm_err = could not write output to {$path}: {$llvm_err} -codegen_llvm_copy_bitcode = failed to copy bitcode to object file: {$err} +codegen_llvm_write_thinlto_key = error while writing ThinLTO key data: {$err} +codegen_llvm_write_thinlto_key_with_llvm_err = error while writing ThinLTO key data: {$err}: {$llvm_err} diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index 28be6d033f8bf..c6a7dc95d77af 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -216,9 +216,7 @@ impl<'ll, 'tcx> ArgAbiExt<'ll, 'tcx> for ArgAbi<'tcx, Ty<'tcx>> { // uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}. let can_store_through_cast_ptr = false; if can_store_through_cast_ptr { - let cast_ptr_llty = bx.type_ptr_to(cast.llvm_type(bx)); - let cast_dst = bx.pointercast(dst.llval, cast_ptr_llty); - bx.store(val, cast_dst, self.layout.align.abi); + bx.store(val, dst.llval, self.layout.align.abi); } else { // The actual return type is a struct, but the ABI // adaptation code has cast it into some scalar type. The @@ -336,7 +334,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { PassMode::Direct(_) | PassMode::Pair(..) => self.ret.layout.immediate_llvm_type(cx), PassMode::Cast(cast, _) => cast.llvm_type(cx), PassMode::Indirect { .. } => { - llargument_tys.push(cx.type_ptr_to(self.ret.memory_ty(cx))); + llargument_tys.push(cx.type_ptr()); cx.type_void() } }; @@ -351,7 +349,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { continue; } PassMode::Indirect { attrs: _, extra_attrs: Some(_), on_stack: _ } => { - let ptr_ty = cx.tcx.mk_mut_ptr(arg.layout.ty); + let ptr_ty = Ty::new_mut_ptr(cx.tcx, arg.layout.ty); let ptr_layout = cx.layout_of(ptr_ty); llargument_tys.push(ptr_layout.scalar_pair_element_llvm_type(cx, 0, true)); llargument_tys.push(ptr_layout.scalar_pair_element_llvm_type(cx, 1, true)); @@ -364,9 +362,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { } cast.llvm_type(cx) } - PassMode::Indirect { attrs: _, extra_attrs: None, on_stack: _ } => { - cx.type_ptr_to(arg.memory_ty(cx)) - } + PassMode::Indirect { attrs: _, extra_attrs: None, on_stack: _ } => cx.type_ptr(), }; llargument_tys.push(llarg_ty); } @@ -379,12 +375,7 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { } fn ptr_to_llvm_type(&self, cx: &CodegenCx<'ll, 'tcx>) -> &'ll Type { - unsafe { - llvm::LLVMPointerType( - self.llvm_type(cx), - cx.data_layout().instruction_address_space.0 as c_uint, - ) - } + cx.type_ptr_ext(cx.data_layout().instruction_address_space) } fn llvm_cconv(&self) -> llvm::CallConv { @@ -392,13 +383,16 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { } fn apply_attrs_llfn(&self, cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value) { - let mut func_attrs = SmallVec::<[_; 2]>::new(); + let mut func_attrs = SmallVec::<[_; 3]>::new(); if self.ret.layout.abi.is_uninhabited() { func_attrs.push(llvm::AttributeKind::NoReturn.create_attr(cx.llcx)); } if !self.can_unwind { func_attrs.push(llvm::AttributeKind::NoUnwind.create_attr(cx.llcx)); } + if let Conv::RiscvInterrupt { kind } = self.conv { + func_attrs.push(llvm::CreateAttrStringValue(cx.llcx, "interrupt", kind.as_str())); + } attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &{ func_attrs }); let mut i = 0; @@ -574,7 +568,9 @@ impl<'tcx> AbiBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { impl From for llvm::CallConv { fn from(conv: Conv) -> Self { match conv { - Conv::C | Conv::Rust | Conv::CCmseNonSecureCall => llvm::CCallConv, + Conv::C | Conv::Rust | Conv::CCmseNonSecureCall | Conv::RiscvInterrupt { .. } => { + llvm::CCallConv + } Conv::RustCold => llvm::ColdCallConv, Conv::AmdGpuKernel => llvm::AmdGpuKernel, Conv::AvrInterrupt => llvm::AvrInterrupt, diff --git a/compiler/rustc_codegen_llvm/src/allocator.rs b/compiler/rustc_codegen_llvm/src/allocator.rs index 668d929270530..db5c1388ef846 100644 --- a/compiler/rustc_codegen_llvm/src/allocator.rs +++ b/compiler/rustc_codegen_llvm/src/allocator.rs @@ -1,13 +1,15 @@ use crate::attributes; use libc::c_uint; -use rustc_ast::expand::allocator::{AllocatorKind, AllocatorTy, ALLOCATOR_METHODS}; +use rustc_ast::expand::allocator::{ + alloc_error_handler_name, default_fn_name, global_fn_name, AllocatorKind, AllocatorTy, + ALLOCATOR_METHODS, NO_ALLOC_SHIM_IS_UNSTABLE, +}; use rustc_middle::bug; use rustc_middle::ty::TyCtxt; use rustc_session::config::{DebugInfo, OomStrategy}; -use rustc_span::symbol::sym; use crate::debuginfo; -use crate::llvm::{self, False, True}; +use crate::llvm::{self, Context, False, Module, True, Type}; use crate::ModuleLlvm; pub(crate) unsafe fn codegen( @@ -26,39 +28,107 @@ pub(crate) unsafe fn codegen( tws => bug!("Unsupported target word size for int: {}", tws), }; let i8 = llvm::LLVMInt8TypeInContext(llcx); - let i8p = llvm::LLVMPointerType(i8, 0); - let void = llvm::LLVMVoidTypeInContext(llcx); - - for method in ALLOCATOR_METHODS { - let mut args = Vec::with_capacity(method.inputs.len()); - for ty in method.inputs.iter() { - match *ty { - AllocatorTy::Layout => { - args.push(usize); // size - args.push(usize); // align + let i8p = llvm::LLVMPointerTypeInContext(llcx, 0); + + if kind == AllocatorKind::Default { + for method in ALLOCATOR_METHODS { + let mut args = Vec::with_capacity(method.inputs.len()); + for input in method.inputs.iter() { + match input.ty { + AllocatorTy::Layout => { + args.push(usize); // size + args.push(usize); // align + } + AllocatorTy::Ptr => args.push(i8p), + AllocatorTy::Usize => args.push(usize), + + AllocatorTy::ResultPtr | AllocatorTy::Unit => panic!("invalid allocator arg"), } - AllocatorTy::Ptr => args.push(i8p), - AllocatorTy::Usize => args.push(usize), - - AllocatorTy::ResultPtr | AllocatorTy::Unit => panic!("invalid allocator arg"), } + let output = match method.output { + AllocatorTy::ResultPtr => Some(i8p), + AllocatorTy::Unit => None, + + AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { + panic!("invalid allocator output") + } + }; + + let from_name = global_fn_name(method.name); + let to_name = default_fn_name(method.name); + + create_wrapper_function(tcx, llcx, llmod, &from_name, &to_name, &args, output, false); } - let output = match method.output { - AllocatorTy::ResultPtr => Some(i8p), - AllocatorTy::Unit => None, + } - AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => { - panic!("invalid allocator output") - } - }; + // rust alloc error handler + create_wrapper_function( + tcx, + llcx, + llmod, + "__rust_alloc_error_handler", + &alloc_error_handler_name(alloc_error_handler_kind), + &[usize, usize], // size, align + None, + true, + ); + + // __rust_alloc_error_handler_should_panic + let name = OomStrategy::SYMBOL; + let ll_g = llvm::LLVMRustGetOrInsertGlobal(llmod, name.as_ptr().cast(), name.len(), i8); + if tcx.sess.target.default_hidden_visibility { + llvm::LLVMRustSetVisibility(ll_g, llvm::Visibility::Hidden); + } + let val = tcx.sess.opts.unstable_opts.oom.should_panic(); + let llval = llvm::LLVMConstInt(i8, val as u64, False); + llvm::LLVMSetInitializer(ll_g, llval); + + let name = NO_ALLOC_SHIM_IS_UNSTABLE; + let ll_g = llvm::LLVMRustGetOrInsertGlobal(llmod, name.as_ptr().cast(), name.len(), i8); + if tcx.sess.target.default_hidden_visibility { + llvm::LLVMRustSetVisibility(ll_g, llvm::Visibility::Hidden); + } + let llval = llvm::LLVMConstInt(i8, 0, False); + llvm::LLVMSetInitializer(ll_g, llval); + + if tcx.sess.opts.debuginfo != DebugInfo::None { + let dbg_cx = debuginfo::CodegenUnitDebugContext::new(llmod); + debuginfo::metadata::build_compile_unit_di_node(tcx, module_name, &dbg_cx); + dbg_cx.finalize(tcx.sess); + } +} + +fn create_wrapper_function( + tcx: TyCtxt<'_>, + llcx: &Context, + llmod: &Module, + from_name: &str, + to_name: &str, + args: &[&Type], + output: Option<&Type>, + no_return: bool, +) { + unsafe { let ty = llvm::LLVMFunctionType( - output.unwrap_or(void), + output.unwrap_or_else(|| llvm::LLVMVoidTypeInContext(llcx)), args.as_ptr(), args.len() as c_uint, False, ); - let name = format!("__rust_{}", method.name); - let llfn = llvm::LLVMRustGetOrInsertFunction(llmod, name.as_ptr().cast(), name.len(), ty); + let llfn = llvm::LLVMRustGetOrInsertFunction( + llmod, + from_name.as_ptr().cast(), + from_name.len(), + ty, + ); + let no_return = if no_return { + // -> ! DIFlagNoReturn + let no_return = llvm::AttributeKind::NoReturn.create_attr(llcx); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[no_return]); + Some(no_return) + } else { + None + }; if tcx.sess.target.default_hidden_visibility { llvm::LLVMRustSetVisibility(llfn, llvm::Visibility::Hidden); @@ -68,9 +138,12 @@ pub(crate) unsafe fn codegen( attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[uwtable]); } - let callee = kind.fn_name(method.name); let callee = - llvm::LLVMRustGetOrInsertFunction(llmod, callee.as_ptr().cast(), callee.len(), ty); + llvm::LLVMRustGetOrInsertFunction(llmod, to_name.as_ptr().cast(), to_name.len(), ty); + if let Some(no_return) = no_return { + // -> ! DIFlagNoReturn + attributes::apply_to_llfn(callee, llvm::AttributePlace::Function, &[no_return]); + } llvm::LLVMRustSetVisibility(callee, llvm::Visibility::Hidden); let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, "entry\0".as_ptr().cast()); @@ -99,66 +172,4 @@ pub(crate) unsafe fn codegen( } llvm::LLVMDisposeBuilder(llbuilder); } - - // rust alloc error handler - let args = [usize, usize]; // size, align - - let ty = llvm::LLVMFunctionType(void, args.as_ptr(), args.len() as c_uint, False); - let name = "__rust_alloc_error_handler"; - let llfn = llvm::LLVMRustGetOrInsertFunction(llmod, name.as_ptr().cast(), name.len(), ty); - // -> ! DIFlagNoReturn - let no_return = llvm::AttributeKind::NoReturn.create_attr(llcx); - attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[no_return]); - - if tcx.sess.target.default_hidden_visibility { - llvm::LLVMRustSetVisibility(llfn, llvm::Visibility::Hidden); - } - if tcx.sess.must_emit_unwind_tables() { - let uwtable = attributes::uwtable_attr(llcx); - attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &[uwtable]); - } - - let callee = alloc_error_handler_kind.fn_name(sym::oom); - let callee = llvm::LLVMRustGetOrInsertFunction(llmod, callee.as_ptr().cast(), callee.len(), ty); - // -> ! DIFlagNoReturn - attributes::apply_to_llfn(callee, llvm::AttributePlace::Function, &[no_return]); - llvm::LLVMRustSetVisibility(callee, llvm::Visibility::Hidden); - - let llbb = llvm::LLVMAppendBasicBlockInContext(llcx, llfn, "entry\0".as_ptr().cast()); - - let llbuilder = llvm::LLVMCreateBuilderInContext(llcx); - llvm::LLVMPositionBuilderAtEnd(llbuilder, llbb); - let args = args - .iter() - .enumerate() - .map(|(i, _)| llvm::LLVMGetParam(llfn, i as c_uint)) - .collect::>(); - let ret = llvm::LLVMRustBuildCall( - llbuilder, - ty, - callee, - args.as_ptr(), - args.len() as c_uint, - [].as_ptr(), - 0 as c_uint, - ); - llvm::LLVMSetTailCall(ret, True); - llvm::LLVMBuildRetVoid(llbuilder); - llvm::LLVMDisposeBuilder(llbuilder); - - // __rust_alloc_error_handler_should_panic - let name = OomStrategy::SYMBOL; - let ll_g = llvm::LLVMRustGetOrInsertGlobal(llmod, name.as_ptr().cast(), name.len(), i8); - if tcx.sess.target.default_hidden_visibility { - llvm::LLVMRustSetVisibility(ll_g, llvm::Visibility::Hidden); - } - let val = tcx.sess.opts.unstable_opts.oom.should_panic(); - let llval = llvm::LLVMConstInt(i8, val as u64, False); - llvm::LLVMSetInitializer(ll_g, llval); - - if tcx.sess.opts.debuginfo != DebugInfo::None { - let dbg_cx = debuginfo::CodegenUnitDebugContext::new(llmod); - debuginfo::metadata::build_compile_unit_di_node(tcx, module_name, &dbg_cx); - dbg_cx.finalize(tcx.sess); - } } diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 2a6ad1be76309..1323261ae9240 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -44,9 +44,10 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { let is_target_supported = |reg_class: InlineAsmRegClass| { for &(_, feature) in reg_class.supported_types(asm_arch) { if let Some(feature) = feature { - let codegen_fn_attrs = self.tcx.codegen_fn_attrs(instance.def_id()); - if self.tcx.sess.target_features.contains(&feature) - || codegen_fn_attrs.target_features.contains(&feature) + if self + .tcx + .asm_target_features(instance.def_id()) + .contains(&feature) { return true; } @@ -261,6 +262,7 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { InlineAsmArch::M68k => { constraints.push("~{ccr}".to_string()); } + InlineAsmArch::CSKY => {} } } if !options.contains(InlineAsmOptions::NOMEM) { @@ -693,6 +695,8 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass, layout: Option<&TyAndLayout<'_>>) -> InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg) => "r", InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_addr) => "a", InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_data) => "d", + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::reg) => "r", + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::freg) => "f", InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => { bug!("LLVM backend does not support SPIR-V") } @@ -792,6 +796,7 @@ fn modifier_to_llvm( bug!("LLVM backend does not support SPIR-V") } InlineAsmRegClass::M68k(_) => None, + InlineAsmRegClass::CSKY(_) => None, InlineAsmRegClass::Err => unreachable!(), } } @@ -868,6 +873,8 @@ fn dummy_output_type<'ll>(cx: &CodegenCx<'ll, '_>, reg: InlineAsmRegClass) -> &' InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg) => cx.type_i32(), InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_addr) => cx.type_i32(), InlineAsmRegClass::M68k(M68kInlineAsmRegClass::reg_data) => cx.type_i32(), + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::reg) => cx.type_i32(), + InlineAsmRegClass::CSKY(CSKYInlineAsmRegClass::freg) => cx.type_f32(), InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => { bug!("LLVM backend does not support SPIR-V") } diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index 651d644ebb63d..b6c01545f308c 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -1,7 +1,6 @@ //! Set and unset common attributes on LLVM values. use rustc_codegen_ssa::traits::*; -use rustc_data_structures::small_str::SmallStr; use rustc_hir::def_id::DefId; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::ty::{self, TyCtxt}; @@ -88,6 +87,9 @@ pub fn sanitize_attrs<'ll>( attrs.push(llvm::AttributeKind::SanitizeMemTag.create_attr(cx.llcx)); } + if enabled.contains(SanitizerSet::SAFESTACK) { + attrs.push(llvm::AttributeKind::SanitizeSafeStack.create_attr(cx.llcx)); + } attrs } @@ -126,7 +128,10 @@ fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attr // The function name varies on platforms. // See test/CodeGen/mcount.c in clang. - let mcount_name = cx.sess().target.mcount.as_ref(); + let mcount_name = match &cx.sess().target.llvm_mcount_intrinsic { + Some(llvm_mcount_intrinsic) => llvm_mcount_intrinsic.as_ref(), + None => cx.sess().target.mcount.as_ref(), + }; attrs.push(llvm::CreateAttrStringValue( cx.llcx, @@ -333,6 +338,10 @@ pub fn from_fn_attrs<'ll, 'tcx>( to_add.extend(probestack_attr(cx)); to_add.extend(stackprotector_attr(cx)); + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_BUILTINS) { + to_add.push(llvm::CreateAttrString(cx.llcx, "no-builtins")); + } + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::COLD) { to_add.push(AttributeKind::Cold.create_attr(cx.llcx)); } @@ -357,50 +366,44 @@ pub fn from_fn_attrs<'ll, 'tcx>( if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR_ZEROED) { - if llvm_util::get_version() >= (15, 0, 0) { - to_add.push(create_alloc_family_attr(cx.llcx)); - // apply to argument place instead of function - let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx); - attributes::apply_to_llfn(llfn, AttributePlace::Argument(1), &[alloc_align]); - to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 0)); - let mut flags = AllocKindFlags::Alloc | AllocKindFlags::Aligned; - if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) { - flags |= AllocKindFlags::Uninitialized; - } else { - flags |= AllocKindFlags::Zeroed; - } - to_add.push(llvm::CreateAllocKindAttr(cx.llcx, flags)); + to_add.push(create_alloc_family_attr(cx.llcx)); + // apply to argument place instead of function + let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx); + attributes::apply_to_llfn(llfn, AttributePlace::Argument(1), &[alloc_align]); + to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 0)); + let mut flags = AllocKindFlags::Alloc | AllocKindFlags::Aligned; + if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::ALLOCATOR) { + flags |= AllocKindFlags::Uninitialized; + } else { + flags |= AllocKindFlags::Zeroed; } + to_add.push(llvm::CreateAllocKindAttr(cx.llcx, flags)); // apply to return place instead of function (unlike all other attributes applied in this function) let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx); attributes::apply_to_llfn(llfn, AttributePlace::ReturnValue, &[no_alias]); } if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::REALLOCATOR) { - if llvm_util::get_version() >= (15, 0, 0) { - to_add.push(create_alloc_family_attr(cx.llcx)); - to_add.push(llvm::CreateAllocKindAttr( - cx.llcx, - AllocKindFlags::Realloc | AllocKindFlags::Aligned, - )); - // applies to argument place instead of function place - let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx); - attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]); - // apply to argument place instead of function - let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx); - attributes::apply_to_llfn(llfn, AttributePlace::Argument(2), &[alloc_align]); - to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 3)); - } + to_add.push(create_alloc_family_attr(cx.llcx)); + to_add.push(llvm::CreateAllocKindAttr( + cx.llcx, + AllocKindFlags::Realloc | AllocKindFlags::Aligned, + )); + // applies to argument place instead of function place + let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx); + attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]); + // apply to argument place instead of function + let alloc_align = AttributeKind::AllocAlign.create_attr(cx.llcx); + attributes::apply_to_llfn(llfn, AttributePlace::Argument(2), &[alloc_align]); + to_add.push(llvm::CreateAllocSizeAttr(cx.llcx, 3)); let no_alias = AttributeKind::NoAlias.create_attr(cx.llcx); attributes::apply_to_llfn(llfn, AttributePlace::ReturnValue, &[no_alias]); } if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::DEALLOCATOR) { - if llvm_util::get_version() >= (15, 0, 0) { - to_add.push(create_alloc_family_attr(cx.llcx)); - to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free)); - // applies to argument place instead of function place - let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx); - attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]); - } + to_add.push(create_alloc_family_attr(cx.llcx)); + to_add.push(llvm::CreateAllocKindAttr(cx.llcx, AllocKindFlags::Free)); + // applies to argument place instead of function place + let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx); + attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]); } if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::CMSE_NONSECURE_ENTRY) { to_add.push(llvm::CreateAttrString(cx.llcx, "cmse_nonsecure_entry")); @@ -444,7 +447,7 @@ pub fn from_fn_attrs<'ll, 'tcx>( let mut function_features = function_features .iter() .flat_map(|feat| { - llvm_util::to_llvm_features(cx.tcx.sess, feat).into_iter().map(|f| format!("+{}", f)) + llvm_util::to_llvm_features(cx.tcx.sess, feat).into_iter().map(|f| format!("+{f}")) }) .chain(codegen_fn_attrs.instruction_set.iter().map(|x| match x { InstructionSetAttr::ArmA32 => "-thumb-mode".to_string(), @@ -478,8 +481,8 @@ pub fn from_fn_attrs<'ll, 'tcx>( let global_features = cx.tcx.global_backend_features(()).iter().map(|s| s.as_str()); let function_features = function_features.iter().map(|s| s.as_str()); - let target_features = - global_features.chain(function_features).intersperse(",").collect::>(); + let target_features: String = + global_features.chain(function_features).intersperse(",").collect(); if !target_features.is_empty() { to_add.push(llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features)); } diff --git a/compiler/rustc_codegen_llvm/src/back/archive.rs b/compiler/rustc_codegen_llvm/src/back/archive.rs index a6416e9540cc0..a82d2c5771a35 100644 --- a/compiler/rustc_codegen_llvm/src/back/archive.rs +++ b/compiler/rustc_codegen_llvm/src/back/archive.rs @@ -56,7 +56,7 @@ fn llvm_machine_type(cpu: &str) -> LLVMMachineType { "x86" => LLVMMachineType::I386, "aarch64" => LLVMMachineType::ARM64, "arm" => LLVMMachineType::ARM, - _ => panic!("unsupported cpu type {}", cpu), + _ => panic!("unsupported cpu type {cpu}"), } } @@ -128,7 +128,7 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { let name_suffix = if is_direct_dependency { "_imports" } else { "_imports_indirect" }; let output_path = { let mut output_path: PathBuf = tmpdir.to_path_buf(); - output_path.push(format!("{}{}", lib_name, name_suffix)); + output_path.push(format!("{lib_name}{name_suffix}")); output_path.with_extension("lib") }; @@ -156,7 +156,7 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { // functions. Therefore, use binutils to create the import library instead, // by writing a .DEF file to the temp dir and calling binutils's dlltool. let def_file_path = - tmpdir.join(format!("{}{}", lib_name, name_suffix)).with_extension("def"); + tmpdir.join(format!("{lib_name}{name_suffix}")).with_extension("def"); let def_file_content = format!( "EXPORTS\n{}", @@ -164,7 +164,7 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { .into_iter() .map(|(name, ordinal)| { match ordinal { - Some(n) => format!("{} @{} NONAME", name, n), + Some(n) => format!("{name} @{n} NONAME"), None => name, } }) @@ -198,25 +198,24 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { "arm" => ("arm", "--32"), _ => panic!("unsupported arch {}", sess.target.arch), }; - let result = std::process::Command::new(&dlltool) - .args([ - "-d", - def_file_path.to_str().unwrap(), - "-D", - lib_name, - "-l", - output_path.to_str().unwrap(), - "-m", - dlltool_target_arch, - "-f", - dlltool_target_bitness, - "--no-leading-underscore", - "--temp-prefix", - temp_prefix.to_str().unwrap(), - ]) - .output(); - - match result { + let mut dlltool_cmd = std::process::Command::new(&dlltool); + dlltool_cmd.args([ + "-d", + def_file_path.to_str().unwrap(), + "-D", + lib_name, + "-l", + output_path.to_str().unwrap(), + "-m", + dlltool_target_arch, + "-f", + dlltool_target_bitness, + "--no-leading-underscore", + "--temp-prefix", + temp_prefix.to_str().unwrap(), + ]); + + match dlltool_cmd.output() { Err(e) => { sess.emit_fatal(ErrorCallingDllTool { dlltool_path: dlltool.to_string_lossy(), @@ -226,6 +225,12 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { // dlltool returns '0' on failure, so check for error output instead. Ok(output) if !output.stderr.is_empty() => { sess.emit_fatal(DlltoolFailImportLibrary { + dlltool_path: dlltool.to_string_lossy(), + dlltool_args: dlltool_cmd + .get_args() + .map(|arg| arg.to_string_lossy()) + .collect::>() + .join(" "), stdout: String::from_utf8_lossy(&output.stdout), stderr: String::from_utf8_lossy(&output.stderr), }) @@ -430,7 +435,7 @@ impl<'a> LlvmArchiveBuilder<'a> { } fn string_to_io_error(s: String) -> io::Error { - io::Error::new(io::ErrorKind::Other, format!("bad archive: {}", s)) + io::Error::new(io::ErrorKind::Other, format!("bad archive: {s}")) } fn find_binutils_dlltool(sess: &Session) -> OsString { diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index 604f68eb6a47e..b2d28cef89976 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -1,4 +1,4 @@ -use crate::back::write::{self, save_temp_bitcode, DiagnosticHandlers}; +use crate::back::write::{self, save_temp_bitcode, CodegenDiagnosticsStage, DiagnosticHandlers}; use crate::errors::{ DynamicLinkingWithLTO, LlvmError, LtoBitcodeFromRlib, LtoDisallowed, LtoDylib, }; @@ -7,7 +7,7 @@ use crate::{LlvmCodegenBackend, ModuleLlvm}; use object::read::archive::ArchiveFile; use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule, ThinShared}; use rustc_codegen_ssa::back::symbol_export; -use rustc_codegen_ssa::back::write::{CodegenContext, FatLTOInput, TargetMachineFactoryConfig}; +use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, TargetMachineFactoryConfig}; use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{looks_like_rust_object_file, ModuleCodegen, ModuleKind}; use rustc_data_structures::fx::FxHashMap; @@ -166,7 +166,7 @@ fn get_bitcode_slice_from_object_data(obj: &[u8]) -> Result<&[u8], LtoBitcodeFro /// for further optimization. pub(crate) fn run_fat( cgcx: &CodegenContext, - modules: Vec>, + modules: Vec>, cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> Result, FatalError> { let diag_handler = cgcx.create_diag_handler(); @@ -220,7 +220,7 @@ pub(crate) fn prepare_thin(module: ModuleCodegen) -> (String, ThinBu fn fat_lto( cgcx: &CodegenContext, diag_handler: &Handler, - modules: Vec>, + modules: Vec>, cached_modules: Vec<(SerializedModule, WorkProduct)>, mut serialized_modules: Vec<(SerializedModule, CString)>, symbols_below_threshold: &[*const libc::c_char], @@ -245,8 +245,8 @@ fn fat_lto( })); for module in modules { match module { - FatLTOInput::InMemory(m) => in_memory.push(m), - FatLTOInput::Serialized { name, buffer } => { + FatLtoInput::InMemory(m) => in_memory.push(m), + FatLtoInput::Serialized { name, buffer } => { info!("pushing serialized module {:?}", name); let buffer = SerializedModule::Local(buffer); serialized_modules.push((buffer, CString::new(name).unwrap())); @@ -302,7 +302,13 @@ fn fat_lto( // The linking steps below may produce errors and diagnostics within LLVM // which we'd like to handle and print, so set up our diagnostic handlers // (which get unregistered when they go out of scope below). - let _handler = DiagnosticHandlers::new(cgcx, diag_handler, llcx); + let _handler = DiagnosticHandlers::new( + cgcx, + diag_handler, + llcx, + &module, + CodegenDiagnosticsStage::LTO, + ); // For all other modules we codegened we'll need to link them into our own // bitcode. All modules were codegened in their own LLVM context, however, @@ -326,7 +332,7 @@ fn fat_lto( let _timer = cgcx .prof .generic_activity_with_arg_recorder("LLVM_fat_lto_link_module", |recorder| { - recorder.record_arg(format!("{:?}", name)) + recorder.record_arg(format!("{name:?}")) }); info!("linking {:?}", name); let data = bc_decoded.data(); @@ -781,7 +787,7 @@ impl ThinLTOKeysMap { let file = File::create(path)?; let mut writer = io::BufWriter::new(file); for (module, key) in &self.keys { - writeln!(writer, "{} {}", module, key)?; + writeln!(writer, "{module} {key}")?; } Ok(()) } @@ -795,7 +801,7 @@ impl ThinLTOKeysMap { let mut split = line.split(' '); let module = split.next().unwrap(); let key = split.next().unwrap(); - assert_eq!(split.next(), None, "Expected two space-separated values, found {:?}", line); + assert_eq!(split.next(), None, "Expected two space-separated values, found {line:?}"); keys.insert(module.to_string(), key.to_string()); } Ok(Self { keys }) diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index ca2eab28f872b..47cc5bd52e2ac 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -4,7 +4,6 @@ use crate::back::profiling::{ }; use crate::base; use crate::common; -use crate::consts; use crate::errors::{ CopyBitcode, FromLlvmDiag, FromLlvmOptimizationDiag, LlvmError, WithLlvmError, WriteBytecode, }; @@ -259,7 +258,7 @@ pub(crate) fn save_temp_bitcode( return; } unsafe { - let ext = format!("{}.bc", name); + let ext = format!("{name}.bc"); let cgu = Some(&module.name[..]); let path = cgcx.output_filenames.temp_path_ext(&ext, cgu); let cstr = path_to_c_string(&path); @@ -268,6 +267,16 @@ pub(crate) fn save_temp_bitcode( } } +/// In what context is a dignostic handler being attached to a codegen unit? +pub enum CodegenDiagnosticsStage { + /// Prelink optimization stage. + Opt, + /// LTO/ThinLTO postlink optimization stage. + LTO, + /// Code generation. + Codegen, +} + pub struct DiagnosticHandlers<'a> { data: *mut (&'a CodegenContext, &'a Handler), llcx: &'a llvm::Context, @@ -279,6 +288,8 @@ impl<'a> DiagnosticHandlers<'a> { cgcx: &'a CodegenContext, handler: &'a Handler, llcx: &'a llvm::Context, + module: &ModuleCodegen, + stage: CodegenDiagnosticsStage, ) -> Self { let remark_passes_all: bool; let remark_passes: Vec; @@ -295,6 +306,21 @@ impl<'a> DiagnosticHandlers<'a> { }; let remark_passes: Vec<*const c_char> = remark_passes.iter().map(|name: &CString| name.as_ptr()).collect(); + let remark_file = cgcx + .remark_dir + .as_ref() + // Use the .opt.yaml file suffix, which is supported by LLVM's opt-viewer. + .map(|dir| { + let stage_suffix = match stage { + CodegenDiagnosticsStage::Codegen => "codegen", + CodegenDiagnosticsStage::Opt => "opt", + CodegenDiagnosticsStage::LTO => "lto", + }; + dir.join(format!("{}.{stage_suffix}.opt.yaml", module.name)) + }) + .and_then(|dir| dir.to_str().and_then(|p| CString::new(p).ok())); + + let pgo_available = cgcx.opts.cg.profile_use.is_some(); let data = Box::into_raw(Box::new((cgcx, handler))); unsafe { let old_handler = llvm::LLVMRustContextGetDiagnosticHandler(llcx); @@ -305,6 +331,10 @@ impl<'a> DiagnosticHandlers<'a> { remark_passes_all, remark_passes.as_ptr(), remark_passes.len(), + // The `as_ref()` is important here, otherwise the `CString` will be dropped + // too soon! + remark_file.as_ref().map(|dir| dir.as_ptr()).unwrap_or(std::ptr::null()), + pgo_available, ); DiagnosticHandlers { data, llcx, old_handler } } @@ -353,29 +383,22 @@ unsafe extern "C" fn diagnostic_handler(info: &DiagnosticInfo, user: *mut c_void } llvm::diagnostic::Optimization(opt) => { - let enabled = match cgcx.remark { - Passes::All => true, - Passes::Some(ref v) => v.iter().any(|s| *s == opt.pass_name), - }; - - if enabled { - diag_handler.emit_note(FromLlvmOptimizationDiag { - filename: &opt.filename, - line: opt.line, - column: opt.column, - pass_name: &opt.pass_name, - kind: match opt.kind { - OptimizationDiagnosticKind::OptimizationRemark => "success", - OptimizationDiagnosticKind::OptimizationMissed - | OptimizationDiagnosticKind::OptimizationFailure => "missed", - OptimizationDiagnosticKind::OptimizationAnalysis - | OptimizationDiagnosticKind::OptimizationAnalysisFPCommute - | OptimizationDiagnosticKind::OptimizationAnalysisAliasing => "analysis", - OptimizationDiagnosticKind::OptimizationRemarkOther => "other", - }, - message: &opt.message, - }); - } + diag_handler.emit_note(FromLlvmOptimizationDiag { + filename: &opt.filename, + line: opt.line, + column: opt.column, + pass_name: &opt.pass_name, + kind: match opt.kind { + OptimizationDiagnosticKind::OptimizationRemark => "success", + OptimizationDiagnosticKind::OptimizationMissed + | OptimizationDiagnosticKind::OptimizationFailure => "missed", + OptimizationDiagnosticKind::OptimizationAnalysis + | OptimizationDiagnosticKind::OptimizationAnalysisFPCommute + | OptimizationDiagnosticKind::OptimizationAnalysisAliasing => "analysis", + OptimizationDiagnosticKind::OptimizationRemarkOther => "other", + }, + message: &opt.message, + }); } llvm::diagnostic::PGO(diagnostic_ref) | llvm::diagnostic::Linker(diagnostic_ref) => { let message = llvm::build_string(|s| { @@ -449,6 +472,8 @@ pub(crate) unsafe fn llvm_optimize( Some(llvm::SanitizerOptions { sanitize_address: config.sanitizer.contains(SanitizerSet::ADDRESS), sanitize_address_recover: config.sanitizer_recover.contains(SanitizerSet::ADDRESS), + sanitize_cfi: config.sanitizer.contains(SanitizerSet::CFI), + sanitize_kcfi: config.sanitizer.contains(SanitizerSet::KCFI), sanitize_memory: config.sanitizer.contains(SanitizerSet::MEMORY), sanitize_memory_recover: config.sanitizer_recover.contains(SanitizerSet::MEMORY), sanitize_memory_track_origins: config.sanitizer_memory_track_origins as c_int, @@ -484,6 +509,7 @@ pub(crate) unsafe fn llvm_optimize( &*module.module_llvm.tm, to_pass_builder_opt_level(opt_level), opt_stage, + cgcx.opts.cg.linker_plugin_lto.enabled(), config.no_prepopulate_passes, config.verify_llvm_ir, using_thin_buffers, @@ -523,7 +549,8 @@ pub(crate) unsafe fn optimize( let llmod = module.module_llvm.llmod(); let llcx = &*module.module_llvm.llcx; - let _handlers = DiagnosticHandlers::new(cgcx, diag_handler, llcx); + let _handlers = + DiagnosticHandlers::new(cgcx, diag_handler, llcx, module, CodegenDiagnosticsStage::Opt); let module_name = module.name.clone(); let module_name = Some(&module_name[..]); @@ -582,7 +609,13 @@ pub(crate) unsafe fn codegen( let tm = &*module.module_llvm.tm; let module_name = module.name.clone(); let module_name = Some(&module_name[..]); - let handlers = DiagnosticHandlers::new(cgcx, diag_handler, llcx); + let _handlers = DiagnosticHandlers::new( + cgcx, + diag_handler, + llcx, + &module, + CodegenDiagnosticsStage::Codegen, + ); if cgcx.msvc_imps_needed { create_msvc_imps(cgcx, llcx, llmod); @@ -677,7 +710,7 @@ pub(crate) unsafe fn codegen( let Ok(demangled) = rustc_demangle::try_demangle(input) else { return 0 }; - if write!(cursor, "{:#}", demangled).is_err() { + if write!(cursor, "{demangled:#}").is_err() { // Possible only if provided buffer is not big enough return 0; } @@ -775,7 +808,6 @@ pub(crate) unsafe fn codegen( } record_llvm_cgu_instructions_stats(&cgcx.prof, llmod); - drop(handlers); } // `.dwo` files are only emitted if: @@ -799,7 +831,7 @@ pub(crate) unsafe fn codegen( } fn create_section_with_flags_asm(section_name: &str, section_flags: &str, data: &[u8]) -> Vec { - let mut asm = format!(".section {},\"{}\"\n", section_name, section_flags).into_bytes(); + let mut asm = format!(".section {section_name},\"{section_flags}\"\n").into_bytes(); asm.extend_from_slice(b".ascii \""); asm.reserve(data.len()); for &byte in data { @@ -875,14 +907,19 @@ unsafe fn embed_bitcode( // passed though then these sections will show up in the final output. // Additionally the flag that we need to set here is `SHF_EXCLUDE`. // + // * XCOFF - AIX linker ignores content in .ipa and .info if no auxiliary + // symbol associated with these sections. + // // Unfortunately, LLVM provides no way to set custom section flags. For ELF // and COFF we emit the sections using module level inline assembly for that // reason (see issue #90326 for historical background). + let is_aix = cgcx.opts.target_triple.triple().contains("-aix"); let is_apple = cgcx.opts.target_triple.triple().contains("-ios") || cgcx.opts.target_triple.triple().contains("-darwin") || cgcx.opts.target_triple.triple().contains("-tvos") || cgcx.opts.target_triple.triple().contains("-watchos"); if is_apple + || is_aix || cgcx.opts.target_triple.triple().starts_with("wasm") || cgcx.opts.target_triple.triple().starts_with("asmjs") { @@ -895,7 +932,13 @@ unsafe fn embed_bitcode( ); llvm::LLVMSetInitializer(llglobal, llconst); - let section = if is_apple { "__LLVM,__bitcode\0" } else { ".llvmbc\0" }; + let section = if is_apple { + "__LLVM,__bitcode\0" + } else if is_aix { + ".ipa\0" + } else { + ".llvmbc\0" + }; llvm::LLVMSetSection(llglobal, section.as_ptr().cast()); llvm::LLVMRustSetLinkage(llglobal, llvm::Linkage::PrivateLinkage); llvm::LLVMSetGlobalConstant(llglobal, llvm::True); @@ -907,7 +950,13 @@ unsafe fn embed_bitcode( "rustc.embedded.cmdline\0".as_ptr().cast(), ); llvm::LLVMSetInitializer(llglobal, llconst); - let section = if is_apple { "__LLVM,__cmdline\0" } else { ".llvmcmd\0" }; + let section = if is_apple { + "__LLVM,__cmdline\0" + } else if is_aix { + ".info\0" + } else { + ".llvmcmd\0" + }; llvm::LLVMSetSection(llglobal, section.as_ptr().cast()); llvm::LLVMRustSetLinkage(llglobal, llvm::Linkage::PrivateLinkage); } else { @@ -940,7 +989,7 @@ fn create_msvc_imps( let prefix = if cgcx.target_arch == "x86" { "\x01__imp__" } else { "\x01__imp_" }; unsafe { - let i8p_ty = Type::i8p_llcx(llcx); + let ptr_ty = Type::ptr_llcx(llcx); let globals = base::iter_globals(llmod) .filter(|&val| { llvm::LLVMRustGetLinkage(val) == llvm::Linkage::ExternalLinkage @@ -960,8 +1009,8 @@ fn create_msvc_imps( .collect::>(); for (imp_name, val) in globals { - let imp = llvm::LLVMAddGlobal(llmod, i8p_ty, imp_name.as_ptr().cast()); - llvm::LLVMSetInitializer(imp, consts::ptrcast(val, i8p_ty)); + let imp = llvm::LLVMAddGlobal(llmod, ptr_ty, imp_name.as_ptr().cast()); + llvm::LLVMSetInitializer(imp, val); llvm::LLVMRustSetLinkage(imp, llvm::Linkage::ExternalLinkage); } } diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 5b2bbdb4bde1e..b659fd02eecf6 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -86,8 +86,8 @@ pub fn compile_codegen_unit(tcx: TyCtxt<'_>, cgu_name: Symbol) -> (ModuleCodegen { let cx = CodegenCx::new(tcx, cgu, &llvm_module); let mono_items = cx.codegen_unit.items_in_deterministic_order(cx.tcx); - for &(mono_item, (linkage, visibility)) in &mono_items { - mono_item.predefine::>(&cx, linkage, visibility); + for &(mono_item, data) in &mono_items { + mono_item.predefine::>(&cx, data.linkage, data.visibility); } // ... and now that we have everything pre-defined, fill out those definitions. @@ -123,8 +123,7 @@ pub fn compile_codegen_unit(tcx: TyCtxt<'_>, cgu_name: Symbol) -> (ModuleCodegen // happen after the llvm.used variables are created. for &(old_g, new_g) in cx.statics_to_rauw().borrow().iter() { unsafe { - let bitcast = llvm::LLVMConstPointerCast(new_g, cx.val_ty(old_g)); - llvm::LLVMReplaceAllUsesWith(old_g, bitcast); + llvm::LLVMReplaceAllUsesWith(old_g, new_g); llvm::LLVMDeleteGlobal(old_g); } } diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index 4d0bcd53d1562..ac6d8f84142e7 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -24,6 +24,7 @@ use rustc_span::Span; use rustc_symbol_mangling::typeid::{kcfi_typeid_for_fnabi, typeid_for_fnabi, TypeIdOptions}; use rustc_target::abi::{self, call::FnAbi, Align, Size, WrappingRange}; use rustc_target::spec::{HasTargetSpec, SanitizerSet, Target}; +use smallvec::SmallVec; use std::borrow::Cow; use std::ffi::CStr; use std::iter; @@ -230,7 +231,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let args = self.check_call("invoke", llty, llfn, args); let funclet_bundle = funclet.map(|funclet| funclet.bundle()); let funclet_bundle = funclet_bundle.as_ref().map(|b| &*b.raw); - let mut bundles = vec![funclet_bundle]; + let mut bundles: SmallVec<[_; 2]> = SmallVec::new(); + if let Some(funclet_bundle) = funclet_bundle { + bundles.push(funclet_bundle); + } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, llfn); @@ -238,9 +242,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { // Emit KCFI operand bundle let kcfi_bundle = self.kcfi_operand_bundle(fn_attrs, fn_abi, llfn); let kcfi_bundle = kcfi_bundle.as_ref().map(|b| &*b.raw); - bundles.push(kcfi_bundle); + if let Some(kcfi_bundle) = kcfi_bundle { + bundles.push(kcfi_bundle); + } - bundles.retain(|bundle| bundle.is_some()); let invoke = unsafe { llvm::LLVMRustBuildInvoke( self.llbuilder, @@ -486,7 +491,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { assert_eq!(place.llextra.is_some(), place.layout.is_unsized()); if place.layout.is_zst() { - return OperandRef::new_zst(self, place.layout); + return OperandRef::zero_sized(place.layout); } #[instrument(level = "trace", skip(bx))] @@ -577,8 +582,6 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { ) { let zero = self.const_usize(0); let count = self.const_usize(count); - let start = dest.project_index(self, zero).llval; - let end = dest.project_index(self, count).llval; let header_bb = self.append_sibling_block("repeat_loop_header"); let body_bb = self.append_sibling_block("repeat_loop_body"); @@ -587,24 +590,18 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { self.br(header_bb); let mut header_bx = Self::build(self.cx, header_bb); - let current = header_bx.phi(self.val_ty(start), &[start], &[self.llbb()]); + let i = header_bx.phi(self.val_ty(zero), &[zero], &[self.llbb()]); - let keep_going = header_bx.icmp(IntPredicate::IntNE, current, end); + let keep_going = header_bx.icmp(IntPredicate::IntULT, i, count); header_bx.cond_br(keep_going, body_bb, next_bb); let mut body_bx = Self::build(self.cx, body_bb); - let align = dest.align.restrict_for_offset(dest.layout.field(self.cx(), 0).size); - cg_elem - .val - .store(&mut body_bx, PlaceRef::new_sized_aligned(current, cg_elem.layout, align)); - - let next = body_bx.inbounds_gep( - self.backend_type(cg_elem.layout), - current, - &[self.const_usize(1)], - ); + let dest_elem = dest.project_index(&mut body_bx, i); + cg_elem.val.store(&mut body_bx, dest_elem); + + let next = body_bx.unchecked_uadd(i, self.const_usize(1)); body_bx.br(header_bb); - header_bx.add_incoming_to_phi(current, next, body_bb); + header_bx.add_incoming_to_phi(i, next, body_bb); *self = Self::build(self.cx, next_bb); } @@ -655,7 +652,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { flags: MemFlags, ) -> &'ll Value { debug!("Store {:?} -> {:?} ({:?})", val, ptr, flags); - let ptr = self.check_store(val, ptr); + assert_eq!(self.cx.type_kind(self.cx.val_ty(ptr)), TypeKind::Pointer); unsafe { let store = llvm::LLVMBuildStore(self.llbuilder, val, ptr); let align = @@ -685,7 +682,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { size: Size, ) { debug!("Store {:?} -> {:?}", val, ptr); - let ptr = self.check_store(val, ptr); + assert_eq!(self.cx.type_kind(self.cx.val_ty(ptr)), TypeKind::Pointer); unsafe { let store = llvm::LLVMRustBuildAtomicStore( self.llbuilder, @@ -876,8 +873,6 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memcpy not supported"); let size = self.intcast(size, self.type_isize(), false); let is_volatile = flags.contains(MemFlags::VOLATILE); - let dst = self.pointercast(dst, self.type_i8p()); - let src = self.pointercast(src, self.type_i8p()); unsafe { llvm::LLVMRustBuildMemCpy( self.llbuilder, @@ -903,8 +898,6 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memmove not supported"); let size = self.intcast(size, self.type_isize(), false); let is_volatile = flags.contains(MemFlags::VOLATILE); - let dst = self.pointercast(dst, self.type_i8p()); - let src = self.pointercast(src, self.type_i8p()); unsafe { llvm::LLVMRustBuildMemMove( self.llbuilder, @@ -927,7 +920,6 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { flags: MemFlags, ) { let is_volatile = flags.contains(MemFlags::VOLATILE); - let ptr = self.pointercast(ptr, self.type_i8p()); unsafe { llvm::LLVMRustBuildMemSet( self.llbuilder, @@ -984,7 +976,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { } fn cleanup_landing_pad(&mut self, pers_fn: &'ll Value) -> (&'ll Value, &'ll Value) { - let ty = self.type_struct(&[self.type_i8p(), self.type_i32()], false); + let ty = self.type_struct(&[self.type_ptr(), self.type_i32()], false); let landing_pad = self.landing_pad(ty, pers_fn, 0); unsafe { llvm::LLVMSetCleanup(landing_pad, llvm::True); @@ -993,14 +985,14 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { } fn filter_landing_pad(&mut self, pers_fn: &'ll Value) -> (&'ll Value, &'ll Value) { - let ty = self.type_struct(&[self.type_i8p(), self.type_i32()], false); + let ty = self.type_struct(&[self.type_ptr(), self.type_i32()], false); let landing_pad = self.landing_pad(ty, pers_fn, 1); - self.add_clause(landing_pad, self.const_array(self.type_i8p(), &[])); + self.add_clause(landing_pad, self.const_array(self.type_ptr(), &[])); (self.extract_value(landing_pad, 0), self.extract_value(landing_pad, 1)) } fn resume(&mut self, exn0: &'ll Value, exn1: &'ll Value) { - let ty = self.type_struct(&[self.type_i8p(), self.type_i32()], false); + let ty = self.type_struct(&[self.type_ptr(), self.type_i32()], false); let mut exn = self.const_poison(ty); exn = self.insert_value(exn, exn0, 0); exn = self.insert_value(exn, exn1, 1); @@ -1164,7 +1156,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let llfn = unsafe { llvm::LLVMRustGetInstrProfIncrementIntrinsic(self.cx().llmod) }; let llty = self.cx.type_func( - &[self.cx.type_i8p(), self.cx.type_i64(), self.cx.type_i32(), self.cx.type_i32()], + &[self.cx.type_ptr(), self.cx.type_i64(), self.cx.type_i32(), self.cx.type_i32()], self.cx.type_void(), ); let args = &[fn_name, hash, num_counters, index]; @@ -1197,7 +1189,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let args = self.check_call("call", llty, llfn, args); let funclet_bundle = funclet.map(|funclet| funclet.bundle()); let funclet_bundle = funclet_bundle.as_ref().map(|b| &*b.raw); - let mut bundles = vec![funclet_bundle]; + let mut bundles: SmallVec<[_; 2]> = SmallVec::new(); + if let Some(funclet_bundle) = funclet_bundle { + bundles.push(funclet_bundle); + } // Emit CFI pointer type membership test self.cfi_type_test(fn_attrs, fn_abi, llfn); @@ -1205,9 +1200,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { // Emit KCFI operand bundle let kcfi_bundle = self.kcfi_operand_bundle(fn_attrs, fn_abi, llfn); let kcfi_bundle = kcfi_bundle.as_ref().map(|b| &*b.raw); - bundles.push(kcfi_bundle); + if let Some(kcfi_bundle) = kcfi_bundle { + bundles.push(kcfi_bundle); + } - bundles.retain(|bundle| bundle.is_some()); let call = unsafe { llvm::LLVMRustBuildCall( self.llbuilder, @@ -1386,25 +1382,6 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { ret.expect("LLVM does not have support for catchret") } - fn check_store(&mut self, val: &'ll Value, ptr: &'ll Value) -> &'ll Value { - let dest_ptr_ty = self.cx.val_ty(ptr); - let stored_ty = self.cx.val_ty(val); - let stored_ptr_ty = self.cx.type_ptr_to(stored_ty); - - assert_eq!(self.cx.type_kind(dest_ptr_ty), TypeKind::Pointer); - - if dest_ptr_ty == stored_ptr_ty { - ptr - } else { - debug!( - "type mismatch in store. \ - Expected {:?}, got {:?}; inserting bitcast", - dest_ptr_ty, stored_ptr_ty - ); - self.bitcast(ptr, stored_ptr_ty) - } - } - fn check_call<'b>( &mut self, typ: &str, @@ -1414,9 +1391,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { ) -> Cow<'b, [&'ll Value]> { assert!( self.cx.type_kind(fn_ty) == TypeKind::Function, - "builder::{} not passed a function, but {:?}", - typ, - fn_ty + "builder::{typ} not passed a function, but {fn_ty:?}" ); let param_tys = self.cx.func_params_types(fn_ty); @@ -1467,7 +1442,6 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { return; } - let ptr = self.pointercast(ptr, self.cx.type_i8p()); self.call_intrinsic(intrinsic, &[self.cx.const_u64(size), ptr]); } @@ -1508,12 +1482,9 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { let instr = if signed { "fptosi" } else { "fptoui" }; let name = if let Some(vector_length) = vector_length { - format!( - "llvm.{}.sat.v{}i{}.v{}f{}", - instr, vector_length, int_width, vector_length, float_width - ) + format!("llvm.{instr}.sat.v{vector_length}i{int_width}.v{vector_length}f{float_width}") } else { - format!("llvm.{}.sat.i{}.f{}", instr, int_width, float_width) + format!("llvm.{instr}.sat.i{int_width}.f{float_width}") }; let f = self.declare_cfn(&name, llvm::UnnamedAddr::No, self.type_func(&[src_ty], dest_ty)); self.call(self.type_func(&[src_ty], dest_ty), None, None, f, &[val], None) @@ -1541,9 +1512,9 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, llfn: &'ll Value, ) { - let is_indirect_call = unsafe { llvm::LLVMIsAFunction(llfn).is_none() }; - if is_indirect_call && fn_abi.is_some() && self.tcx.sess.is_sanitizer_cfi_enabled() { - if fn_attrs.is_some() && fn_attrs.unwrap().no_sanitize.contains(SanitizerSet::CFI) { + let is_indirect_call = unsafe { llvm::LLVMRustIsNonGVFunctionPointerTy(llfn) }; + if self.tcx.sess.is_sanitizer_cfi_enabled() && let Some(fn_abi) = fn_abi && is_indirect_call { + if let Some(fn_attrs) = fn_attrs && fn_attrs.no_sanitize.contains(SanitizerSet::CFI) { return; } @@ -1555,7 +1526,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { options.insert(TypeIdOptions::NORMALIZE_INTEGERS); } - let typeid = typeid_for_fnabi(self.tcx, fn_abi.unwrap(), options); + let typeid = typeid_for_fnabi(self.tcx, fn_abi, options); let typeid_metadata = self.cx.typeid_metadata(typeid).unwrap(); // Test whether the function pointer is associated with the type identifier. @@ -1579,25 +1550,26 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, llfn: &'ll Value, ) -> Option> { - let is_indirect_call = unsafe { llvm::LLVMIsAFunction(llfn).is_none() }; - let kcfi_bundle = if is_indirect_call && self.tcx.sess.is_sanitizer_kcfi_enabled() { - if fn_attrs.is_some() && fn_attrs.unwrap().no_sanitize.contains(SanitizerSet::KCFI) { - return None; - } + let is_indirect_call = unsafe { llvm::LLVMRustIsNonGVFunctionPointerTy(llfn) }; + let kcfi_bundle = + if self.tcx.sess.is_sanitizer_kcfi_enabled() && let Some(fn_abi) = fn_abi && is_indirect_call { + if let Some(fn_attrs) = fn_attrs && fn_attrs.no_sanitize.contains(SanitizerSet::KCFI) { + return None; + } - let mut options = TypeIdOptions::empty(); - if self.tcx.sess.is_sanitizer_cfi_generalize_pointers_enabled() { - options.insert(TypeIdOptions::GENERALIZE_POINTERS); - } - if self.tcx.sess.is_sanitizer_cfi_normalize_integers_enabled() { - options.insert(TypeIdOptions::NORMALIZE_INTEGERS); - } + let mut options = TypeIdOptions::empty(); + if self.tcx.sess.is_sanitizer_cfi_generalize_pointers_enabled() { + options.insert(TypeIdOptions::GENERALIZE_POINTERS); + } + if self.tcx.sess.is_sanitizer_cfi_normalize_integers_enabled() { + options.insert(TypeIdOptions::NORMALIZE_INTEGERS); + } - let kcfi_typeid = kcfi_typeid_for_fnabi(self.tcx, fn_abi.unwrap(), options); - Some(llvm::OperandBundleDef::new("kcfi", &[self.const_u32(kcfi_typeid)])) - } else { - None - }; + let kcfi_typeid = kcfi_typeid_for_fnabi(self.tcx, fn_abi, options); + Some(llvm::OperandBundleDef::new("kcfi", &[self.const_u32(kcfi_typeid)])) + } else { + None + }; kcfi_bundle } } diff --git a/compiler/rustc_codegen_llvm/src/callee.rs b/compiler/rustc_codegen_llvm/src/callee.rs index 4b9ca2e7d19c3..36c098218cf3b 100644 --- a/compiler/rustc_codegen_llvm/src/callee.rs +++ b/compiler/rustc_codegen_llvm/src/callee.rs @@ -4,13 +4,11 @@ //! and methods are represented as just a fn ptr and not a full //! closure. -use crate::abi::FnAbiLlvmExt; use crate::attributes; use crate::common; use crate::context::CodegenCx; use crate::llvm; use crate::value::Value; -use rustc_codegen_ssa::traits::*; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt}; use rustc_middle::ty::{self, Instance, TypeVisitableExt}; @@ -27,8 +25,8 @@ pub fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>) -> debug!("get_fn(instance={:?})", instance); - assert!(!instance.substs.has_infer()); - assert!(!instance.substs.has_escaping_bound_vars()); + assert!(!instance.args.has_infer()); + assert!(!instance.args.has_escaping_bound_vars()); if let Some(&llfn) = cx.instances.borrow().get(&instance) { return llfn; @@ -45,39 +43,7 @@ pub fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>) -> let fn_abi = cx.fn_abi_of_instance(instance, ty::List::empty()); let llfn = if let Some(llfn) = cx.get_declared_value(sym) { - // Create a fn pointer with the new signature. - let llptrty = fn_abi.ptr_to_llvm_type(cx); - - // This is subtle and surprising, but sometimes we have to bitcast - // the resulting fn pointer. The reason has to do with external - // functions. If you have two crates that both bind the same C - // library, they may not use precisely the same types: for - // example, they will probably each declare their own structs, - // which are distinct types from LLVM's point of view (nominal - // types). - // - // Now, if those two crates are linked into an application, and - // they contain inlined code, you can wind up with a situation - // where both of those functions wind up being loaded into this - // application simultaneously. In that case, the same function - // (from LLVM's point of view) requires two types. But of course - // LLVM won't allow one function to have two types. - // - // What we currently do, therefore, is declare the function with - // one of the two types (whichever happens to come first) and then - // bitcast as needed when the function is referenced to make sure - // it has the type we expect. - // - // This can occur on either a crate-local or crate-external - // reference. It also occurs when testing libcore and in some - // other weird situations. Annoying. - if cx.val_ty(llfn) != llptrty { - debug!("get_fn: casting {:?} to {:?}", llfn, llptrty); - cx.const_ptrcast(llfn, llptrty) - } else { - debug!("get_fn: not casting pointer!"); - llfn - } + llfn } else { let instance_def_id = instance.def_id(); let llfn = if tcx.sess.target.arch == "x86" && @@ -129,7 +95,7 @@ pub fn get_fn<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>) -> unsafe { llvm::LLVMRustSetLinkage(llfn, llvm::Linkage::ExternalLinkage); - let is_generic = instance.substs.non_erasable_generics().next().is_some(); + let is_generic = instance.args.non_erasable_generics().next().is_some(); if is_generic { // This is a monomorphization. Its expected visibility depends diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 9127fba388bab..0b0816c27b6df 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -1,23 +1,20 @@ //! Code that is useful in various codegen modules. -use crate::consts::{self, const_alloc_to_llvm}; +use crate::consts::const_alloc_to_llvm; pub use crate::context::CodegenCx; use crate::llvm::{self, BasicBlock, Bool, ConstantInt, False, OperandBundleDef, True}; use crate::type_::Type; -use crate::type_of::LayoutLlvmExt; use crate::value::Value; use rustc_ast::Mutability; -use rustc_codegen_ssa::mir::place::PlaceRef; use rustc_codegen_ssa::traits::*; use rustc_data_structures::stable_hasher::{Hash128, HashStable, StableHasher}; use rustc_hir::def_id::DefId; use rustc_middle::bug; use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, Scalar}; -use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::TyCtxt; use rustc_session::cstore::{DllCallingConvention, DllImport, PeImportNameType}; -use rustc_target::abi::{self, AddressSpace, HasDataLayout, Pointer, Size}; +use rustc_target::abi::{self, AddressSpace, HasDataLayout, Pointer}; use rustc_target::spec::Target; use libc::{c_char, c_uint}; @@ -169,6 +166,10 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { self.const_uint(self.type_i64(), i) } + fn const_u128(&self, i: u128) -> &'ll Value { + self.const_uint_big(self.type_i128(), i) + } + fn const_usize(&self, i: u64) -> &'ll Value { let bit_size = self.data_layout().pointer_size.bits(); if bit_size < 64 { @@ -208,11 +209,7 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { }) .1; let len = s.len(); - let cs = consts::ptrcast( - str_global, - self.type_ptr_to(self.layout_of(self.tcx.types.str_).llvm_type(self)), - ); - (cs, self.const_usize(len as u64)) + (str_global, self.const_usize(len as u64)) } fn const_struct(&self, elts: &[&'ll Value], packed: bool) -> &'ll Value { @@ -287,9 +284,9 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { } }; let llval = unsafe { - llvm::LLVMRustConstInBoundsGEP2( + llvm::LLVMConstInBoundsGEP2( self.type_i8(), - self.const_bitcast(base_addr, self.type_i8p_ext(base_addr_space)), + self.const_bitcast(base_addr, self.type_ptr_ext(base_addr_space)), &self.const_usize(offset.bytes()), 1, ) @@ -307,37 +304,19 @@ impl<'ll, 'tcx> ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> { const_alloc_to_llvm(self, alloc) } - fn from_const_alloc( - &self, - layout: TyAndLayout<'tcx>, - alloc: ConstAllocation<'tcx>, - offset: Size, - ) -> PlaceRef<'tcx, &'ll Value> { - let alloc_align = alloc.inner().align; - assert_eq!(alloc_align, layout.align.abi); - let llty = self.type_ptr_to(layout.llvm_type(self)); - let llval = if layout.size == Size::ZERO { - let llval = self.const_usize(alloc_align.bytes()); - unsafe { llvm::LLVMConstIntToPtr(llval, llty) } - } else { - let init = const_alloc_to_llvm(self, alloc); - let base_addr = self.static_addr_of(init, alloc_align, None); - - let llval = unsafe { - llvm::LLVMRustConstInBoundsGEP2( - self.type_i8(), - self.const_bitcast(base_addr, self.type_i8p()), - &self.const_usize(offset.bytes()), - 1, - ) - }; - self.const_bitcast(llval, llty) - }; - PlaceRef::new_sized(llval, layout) - } - - fn const_ptrcast(&self, val: &'ll Value, ty: &'ll Type) -> &'ll Value { - consts::ptrcast(val, ty) + fn const_bitcast(&self, val: &'ll Value, ty: &'ll Type) -> &'ll Value { + self.const_bitcast(val, ty) + } + + fn const_ptr_byte_offset(&self, base_addr: Self::Value, offset: abi::Size) -> Self::Value { + unsafe { + llvm::LLVMConstInBoundsGEP2( + self.type_i8(), + base_addr, + &self.const_usize(offset.bytes()), + 1, + ) + } } } @@ -431,10 +410,10 @@ pub(crate) fn i686_decorated_name( DllCallingConvention::C => {} DllCallingConvention::Stdcall(arg_list_size) | DllCallingConvention::Fastcall(arg_list_size) => { - write!(&mut decorated_name, "@{}", arg_list_size).unwrap(); + write!(&mut decorated_name, "@{arg_list_size}").unwrap(); } DllCallingConvention::Vectorcall(arg_list_size) => { - write!(&mut decorated_name, "@@{}", arg_list_size).unwrap(); + write!(&mut decorated_name, "@@{arg_list_size}").unwrap(); } } } diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 940358acde9a2..95af2f8ef4adc 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -1,7 +1,9 @@ use crate::base; use crate::common::{self, CodegenCx}; use crate::debuginfo; -use crate::errors::{InvalidMinimumAlignment, SymbolAlreadyDefined}; +use crate::errors::{ + InvalidMinimumAlignmentNotPowerOfTwo, InvalidMinimumAlignmentTooLarge, SymbolAlreadyDefined, +}; use crate::llvm::{self, True}; use crate::type_::Type; use crate::type_of::LayoutLlvmExt; @@ -19,7 +21,9 @@ use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::{bug, span_bug}; use rustc_session::config::Lto; -use rustc_target::abi::{Align, HasDataLayout, Primitive, Scalar, Size, WrappingRange}; +use rustc_target::abi::{ + Align, AlignFromBytesError, HasDataLayout, Primitive, Scalar, Size, WrappingRange, +}; use std::ops::Range; pub fn const_alloc_to_llvm<'ll>(cx: &CodegenCx<'ll, '_>, alloc: ConstAllocation<'_>) -> &'ll Value { @@ -99,7 +103,7 @@ pub fn const_alloc_to_llvm<'ll>(cx: &CodegenCx<'ll, '_>, alloc: ConstAllocation< value: Primitive::Pointer(address_space), valid_range: WrappingRange::full(dl.pointer_size), }, - cx.type_i8p_ext(address_space), + cx.type_ptr_ext(address_space), )); next_offset = offset + pointer_size; } @@ -129,9 +133,14 @@ fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: if let Some(min) = cx.sess().target.min_global_align { match Align::from_bits(min) { Ok(min) => align = align.max(min), - Err(err) => { - cx.sess().emit_err(InvalidMinimumAlignment { err }); - } + Err(err) => match err { + AlignFromBytesError::NotPowerOfTwo(align) => { + cx.sess().emit_err(InvalidMinimumAlignmentNotPowerOfTwo { align }); + } + AlignFromBytesError::TooLarge(align) => { + cx.sess().emit_err(InvalidMinimumAlignmentTooLarge { align }); + } + }, } } unsafe { @@ -170,7 +179,7 @@ fn check_and_apply_linkage<'ll, 'tcx>( }) }); llvm::LLVMRustSetLinkage(g2, llvm::Linkage::InternalLinkage); - llvm::LLVMSetInitializer(g2, cx.const_ptrcast(g1, llty)); + llvm::LLVMSetInitializer(g2, g1); g2 } } else if cx.tcx.sess.target.arch == "x86" && @@ -184,10 +193,6 @@ fn check_and_apply_linkage<'ll, 'tcx>( } } -pub fn ptrcast<'ll>(val: &'ll Value, ty: &'ll Type) -> &'ll Value { - unsafe { llvm::LLVMConstPointerCast(val, ty) } -} - impl<'ll> CodegenCx<'ll, '_> { pub(crate) fn const_bitcast(&self, val: &'ll Value, ty: &'ll Type) -> &'ll Value { unsafe { llvm::LLVMConstBitCast(val, ty) } @@ -229,8 +234,7 @@ impl<'ll> CodegenCx<'ll, '_> { assert!( !defined_in_current_codegen_unit, "consts::get_static() should always hit the cache for \ - statics defined in the same CGU, but did not for `{:?}`", - def_id + statics defined in the same CGU, but did not for `{def_id:?}`" ); let ty = instance.ty(self.tcx, ty::ParamEnv::reveal_all()); @@ -242,7 +246,7 @@ impl<'ll> CodegenCx<'ll, '_> { let g = if def_id.is_local() && !self.tcx.is_foreign_item(def_id) { let llty = self.layout_of(ty).llvm_type(self); if let Some(g) = self.get_declared_value(sym) { - if self.val_ty(g) != self.type_ptr_to(llty) { + if self.val_ty(g) != self.type_ptr() { span_bug!(self.tcx.def_span(def_id), "Conflicting types for static"); } } @@ -543,16 +547,14 @@ impl<'ll> StaticMethods for CodegenCx<'ll, '_> { } } - /// Add a global value to a list to be stored in the `llvm.used` variable, an array of i8*. + /// Add a global value to a list to be stored in the `llvm.used` variable, an array of ptr. fn add_used_global(&self, global: &'ll Value) { - let cast = unsafe { llvm::LLVMConstPointerCast(global, self.type_i8p()) }; - self.used_statics.borrow_mut().push(cast); + self.used_statics.borrow_mut().push(global); } /// Add a global value to a list to be stored in the `llvm.compiler.used` variable, - /// an array of i8*. + /// an array of ptr. fn add_compiler_used_global(&self, global: &'ll Value) { - let cast = unsafe { llvm::LLVMConstPointerCast(global, self.type_i8p()) }; - self.compiler_used_statics.borrow_mut().push(cast); + self.compiler_used_statics.borrow_mut().push(global); } } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 83101a85435a0..24fd5bbf8c526 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -9,7 +9,7 @@ use crate::type_::Type; use crate::value::Value; use cstr::cstr; -use rustc_codegen_ssa::base::wants_msvc_seh; +use rustc_codegen_ssa::base::{wants_msvc_seh, wants_wasm_eh}; use rustc_codegen_ssa::traits::*; use rustc_data_structures::base_n; use rustc_data_structures::fx::FxHashMap; @@ -33,6 +33,7 @@ use rustc_target::abi::{ use rustc_target::spec::{HasTargetSpec, RelocModel, Target, TlsModel}; use smallvec::SmallVec; +use libc::c_uint; use std::cell::{Cell, RefCell}; use std::ffi::CStr; use std::str; @@ -58,17 +59,6 @@ pub struct CodegenCx<'ll, 'tcx> { /// Cache of constant strings, pub const_str_cache: RefCell>, - /// Reverse-direction for const ptrs cast from globals. - /// - /// Key is a Value holding a `*T`, - /// Val is a Value holding a `*[T]`. - /// - /// Needed because LLVM loses pointer->pointee association - /// when we ptrcast, and we have to ptrcast during codegen - /// of a `[T]` const because we form a slice, a `(*T,usize)` pair, not - /// a pointer to an LLVM array type. Similar for trait objects. - pub const_unsized: RefCell>, - /// Cache of emitted const globals (value -> global) pub const_globals: RefCell>, @@ -155,6 +145,17 @@ pub unsafe fn create_module<'ll>( target_data_layout = target_data_layout.replace("-n32:64-", "-n64-"); } } + if llvm_version < (17, 0, 0) { + if sess.target.arch.starts_with("powerpc") { + // LLVM 17 specifies function pointer alignment for ppc: + // https://reviews.llvm.org/D147016 + target_data_layout = target_data_layout + .replace("-Fn32", "") + .replace("-Fi32", "") + .replace("-Fn64", "") + .replace("-Fi64", ""); + } + } // Ensure the data-layout values hardcoded remain the defaults. if sess.target.is_builtin { @@ -208,7 +209,7 @@ pub unsafe fn create_module<'ll>( // PIE is potentially more effective than PIC, but can only be used in executables. // If all our outputs are executables, then we can relax PIC to PIE. if reloc_model == RelocModel::Pie - || sess.crate_types().iter().all(|ty| *ty == CrateType::Executable) + || tcx.crate_types().iter().all(|ty| *ty == CrateType::Executable) { llvm::LLVMRustSetModulePIELevel(llmod); } @@ -349,6 +350,23 @@ pub unsafe fn create_module<'ll>( ); } + // Insert `llvm.ident` metadata. + // + // On the wasm targets it will get hooked up to the "producer" sections + // `processed-by` information. + let rustc_producer = + format!("rustc version {}", option_env!("CFG_VERSION").expect("CFG_VERSION")); + let name_metadata = llvm::LLVMMDStringInContext( + llcx, + rustc_producer.as_ptr().cast(), + rustc_producer.as_bytes().len() as c_uint, + ); + llvm::LLVMAddNamedMetadataOperand( + llmod, + cstr!("llvm.ident").as_ptr(), + llvm::LLVMMDNodeInContext(llcx, &name_metadata, 1), + ); + llmod } @@ -446,7 +464,6 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { instances: Default::default(), vtables: Default::default(), const_str_cache: Default::default(), - const_unsized: Default::default(), const_globals: Default::default(), statics_to_rauw: RefCell::new(Vec::new()), used_statics: RefCell::new(Vec::new()), @@ -477,7 +494,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { pub(crate) fn create_used_variable_impl(&self, name: &'static CStr, values: &[&'ll Value]) { let section = cstr!("llvm.metadata"); - let array = self.const_array(self.type_ptr_to(self.type_i8()), values); + let array = self.const_array(self.type_ptr(), values); unsafe { let g = llvm::LLVMAddGlobal(self.llmod, self.val_ty(array), name.as_ptr()); @@ -528,19 +545,28 @@ impl<'ll, 'tcx> MiscMethods<'tcx> for CodegenCx<'ll, 'tcx> { if let Some(llpersonality) = self.eh_personality.get() { return llpersonality; } + + let name = if wants_msvc_seh(self.sess()) { + Some("__CxxFrameHandler3") + } else if wants_wasm_eh(self.sess()) { + // LLVM specifically tests for the name of the personality function + // There is no need for this function to exist anywhere, it will + // not be called. However, its name has to be "__gxx_wasm_personality_v0" + // for native wasm exceptions. + Some("__gxx_wasm_personality_v0") + } else { + None + }; + let tcx = self.tcx; let llfn = match tcx.lang_items().eh_personality() { - Some(def_id) if !wants_msvc_seh(self.sess()) => self.get_fn_addr( + Some(def_id) if name.is_none() => self.get_fn_addr( ty::Instance::resolve(tcx, ty::ParamEnv::reveal_all(), def_id, ty::List::empty()) .unwrap() .unwrap(), ), _ => { - let name = if wants_msvc_seh(self.sess()) { - "__CxxFrameHandler3" - } else { - "rust_eh_personality" - }; + let name = name.unwrap_or("rust_eh_personality"); if let Some(llfn) = self.get_declared_value(name) { llfn } else { @@ -646,7 +672,7 @@ impl<'ll> CodegenCx<'ll, '_> { ($($field_ty:expr),*) => (self.type_struct( &[$($field_ty),*], false)) } - let i8p = self.type_i8p(); + let ptr = self.type_ptr(); let void = self.type_void(); let i1 = self.type_i1(); let t_i8 = self.type_i8(); @@ -658,6 +684,10 @@ impl<'ll> CodegenCx<'ll, '_> { let t_f32 = self.type_f32(); let t_f64 = self.type_f64(); let t_metadata = self.type_metadata(); + let t_token = self.type_token(); + + ifn!("llvm.wasm.get.exception", fn(t_token) -> ptr); + ifn!("llvm.wasm.get.ehselector", fn(t_token) -> t_i32); ifn!("llvm.wasm.trunc.unsigned.i32.f32", fn(t_f32) -> t_i32); ifn!("llvm.wasm.trunc.unsigned.i32.f64", fn(t_f64) -> t_i32); @@ -692,7 +722,7 @@ impl<'ll> CodegenCx<'ll, '_> { ifn!("llvm.trap", fn() -> void); ifn!("llvm.debugtrap", fn() -> void); - ifn!("llvm.frameaddress", fn(t_i32) -> i8p); + ifn!("llvm.frameaddress", fn(t_i32) -> ptr); ifn!("llvm.powi.f32", fn(t_f32, t_i32) -> t_f32); ifn!("llvm.powi.f64", fn(t_f64, t_i32) -> t_f64); @@ -859,43 +889,44 @@ impl<'ll> CodegenCx<'ll, '_> { ifn!("llvm.usub.sat.i64", fn(t_i64, t_i64) -> t_i64); ifn!("llvm.usub.sat.i128", fn(t_i128, t_i128) -> t_i128); - ifn!("llvm.lifetime.start.p0i8", fn(t_i64, i8p) -> void); - ifn!("llvm.lifetime.end.p0i8", fn(t_i64, i8p) -> void); + ifn!("llvm.lifetime.start.p0i8", fn(t_i64, ptr) -> void); + ifn!("llvm.lifetime.end.p0i8", fn(t_i64, ptr) -> void); ifn!("llvm.expect.i1", fn(i1, i1) -> i1); - ifn!("llvm.eh.typeid.for", fn(i8p) -> t_i32); + ifn!("llvm.eh.typeid.for", fn(ptr) -> t_i32); ifn!("llvm.localescape", fn(...) -> void); - ifn!("llvm.localrecover", fn(i8p, i8p, t_i32) -> i8p); - ifn!("llvm.x86.seh.recoverfp", fn(i8p, i8p) -> i8p); + ifn!("llvm.localrecover", fn(ptr, ptr, t_i32) -> ptr); + ifn!("llvm.x86.seh.recoverfp", fn(ptr, ptr) -> ptr); ifn!("llvm.assume", fn(i1) -> void); - ifn!("llvm.prefetch", fn(i8p, t_i32, t_i32, t_i32) -> void); + ifn!("llvm.prefetch", fn(ptr, t_i32, t_i32, t_i32) -> void); // This isn't an "LLVM intrinsic", but LLVM's optimization passes - // recognize it like one and we assume it exists in `core::slice::cmp` + // recognize it like one (including turning it into `bcmp` sometimes) + // and we use it to implement intrinsics like `raw_eq` and `compare_bytes` match self.sess().target.arch.as_ref() { - "avr" | "msp430" => ifn!("memcmp", fn(i8p, i8p, t_isize) -> t_i16), - _ => ifn!("memcmp", fn(i8p, i8p, t_isize) -> t_i32), + "avr" | "msp430" => ifn!("memcmp", fn(ptr, ptr, t_isize) -> t_i16), + _ => ifn!("memcmp", fn(ptr, ptr, t_isize) -> t_i32), } // variadic intrinsics - ifn!("llvm.va_start", fn(i8p) -> void); - ifn!("llvm.va_end", fn(i8p) -> void); - ifn!("llvm.va_copy", fn(i8p, i8p) -> void); + ifn!("llvm.va_start", fn(ptr) -> void); + ifn!("llvm.va_end", fn(ptr) -> void); + ifn!("llvm.va_copy", fn(ptr, ptr) -> void); if self.sess().instrument_coverage() { - ifn!("llvm.instrprof.increment", fn(i8p, t_i64, t_i32, t_i32) -> void); + ifn!("llvm.instrprof.increment", fn(ptr, t_i64, t_i32, t_i32) -> void); } - ifn!("llvm.type.test", fn(i8p, t_metadata) -> i1); - ifn!("llvm.type.checked.load", fn(i8p, t_i32, t_metadata) -> mk_struct! {i8p, i1}); + ifn!("llvm.type.test", fn(ptr, t_metadata) -> i1); + ifn!("llvm.type.checked.load", fn(ptr, t_i32, t_metadata) -> mk_struct! {ptr, i1}); if self.sess().opts.debuginfo != DebugInfo::None { ifn!("llvm.dbg.declare", fn(t_metadata, t_metadata) -> void); ifn!("llvm.dbg.value", fn(t_metadata, t_i64, t_metadata) -> void); } - ifn!("llvm.ptrmask", fn(i8p, t_isize) -> i8p); + ifn!("llvm.ptrmask", fn(ptr, t_isize) -> ptr); None } @@ -909,12 +940,10 @@ impl<'ll> CodegenCx<'ll, '_> { let eh_catch_typeinfo = match tcx.lang_items().eh_catch_typeinfo() { Some(def_id) => self.get_static(def_id), _ => { - let ty = self - .type_struct(&[self.type_ptr_to(self.type_isize()), self.type_i8p()], false); + let ty = self.type_struct(&[self.type_ptr(), self.type_ptr()], false); self.declare_global("rust_eh_catch_typeinfo", ty) } }; - let eh_catch_typeinfo = self.const_bitcast(eh_catch_typeinfo, self.type_i8p()); self.eh_catch_typeinfo.set(Some(eh_catch_typeinfo)); eh_catch_typeinfo } @@ -968,10 +997,10 @@ impl<'tcx> LayoutOfHelpers<'tcx> for CodegenCx<'_, 'tcx> { #[inline] fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! { - if let LayoutError::SizeOverflow(_) = err { - self.sess().emit_fatal(Spanned { span, node: err }) + if let LayoutError::SizeOverflow(_) | LayoutError::ReferencesError(_) = err { + self.sess().emit_fatal(Spanned { span, node: err.into_diagnostic() }) } else { - span_bug!(span, "failed to get layout for `{}`: {}", ty, err) + span_bug!(span, "failed to get layout for `{ty}`: {err:?}") } } } @@ -991,21 +1020,12 @@ impl<'tcx> FnAbiOfHelpers<'tcx> for CodegenCx<'_, 'tcx> { } else { match fn_abi_request { FnAbiRequest::OfFnPtr { sig, extra_args } => { - span_bug!( - span, - "`fn_abi_of_fn_ptr({}, {:?})` failed: {}", - sig, - extra_args, - err - ); + span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}",); } FnAbiRequest::OfInstance { instance, extra_args } => { span_bug!( span, - "`fn_abi_of_instance({}, {:?})` failed: {}", - instance, - extra_args, - err + "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}", ); } } diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs new file mode 100644 index 0000000000000..7a82d05ce9ea2 --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs @@ -0,0 +1,281 @@ +use rustc_middle::mir::coverage::{CounterId, MappedExpressionIndex}; + +/// Must match the layout of `LLVMRustCounterKind`. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub enum CounterKind { + Zero = 0, + CounterValueReference = 1, + Expression = 2, +} + +/// A reference to an instance of an abstract "counter" that will yield a value in a coverage +/// report. Note that `id` has different interpretations, depending on the `kind`: +/// * For `CounterKind::Zero`, `id` is assumed to be `0` +/// * For `CounterKind::CounterValueReference`, `id` matches the `counter_id` of the injected +/// instrumentation counter (the `index` argument to the LLVM intrinsic +/// `instrprof.increment()`) +/// * For `CounterKind::Expression`, `id` is the index into the coverage map's array of +/// counter expressions. +/// +/// Corresponds to struct `llvm::coverage::Counter`. +/// +/// Must match the layout of `LLVMRustCounter`. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct Counter { + // Important: The layout (order and types of fields) must match its C++ counterpart. + pub kind: CounterKind, + id: u32, +} + +impl Counter { + /// Constructs a new `Counter` of kind `Zero`. For this `CounterKind`, the + /// `id` is not used. + pub fn zero() -> Self { + Self { kind: CounterKind::Zero, id: 0 } + } + + /// Constructs a new `Counter` of kind `CounterValueReference`. + pub fn counter_value_reference(counter_id: CounterId) -> Self { + Self { kind: CounterKind::CounterValueReference, id: counter_id.as_u32() } + } + + /// Constructs a new `Counter` of kind `Expression`. + pub fn expression(mapped_expression_index: MappedExpressionIndex) -> Self { + Self { kind: CounterKind::Expression, id: mapped_expression_index.into() } + } + + /// Returns true if the `Counter` kind is `Zero`. + pub fn is_zero(&self) -> bool { + matches!(self.kind, CounterKind::Zero) + } + + /// An explicitly-named function to get the ID value, making it more obvious + /// that the stored value is now 0-based. + pub fn zero_based_id(&self) -> u32 { + debug_assert!(!self.is_zero(), "`id` is undefined for CounterKind::Zero"); + self.id + } +} + +/// Corresponds to enum `llvm::coverage::CounterExpression::ExprKind`. +/// +/// Must match the layout of `LLVMRustCounterExprKind`. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub enum ExprKind { + Subtract = 0, + Add = 1, +} + +/// Corresponds to struct `llvm::coverage::CounterExpression`. +/// +/// Must match the layout of `LLVMRustCounterExpression`. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct CounterExpression { + pub kind: ExprKind, + pub lhs: Counter, + pub rhs: Counter, +} + +impl CounterExpression { + pub fn new(lhs: Counter, kind: ExprKind, rhs: Counter) -> Self { + Self { kind, lhs, rhs } + } +} + +/// Corresponds to enum `llvm::coverage::CounterMappingRegion::RegionKind`. +/// +/// Must match the layout of `LLVMRustCounterMappingRegionKind`. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub enum RegionKind { + /// A CodeRegion associates some code with a counter + CodeRegion = 0, + + /// An ExpansionRegion represents a file expansion region that associates + /// a source range with the expansion of a virtual source file, such as + /// for a macro instantiation or #include file. + ExpansionRegion = 1, + + /// A SkippedRegion represents a source range with code that was skipped + /// by a preprocessor or similar means. + SkippedRegion = 2, + + /// A GapRegion is like a CodeRegion, but its count is only set as the + /// line execution count when its the only region in the line. + GapRegion = 3, + + /// A BranchRegion represents leaf-level boolean expressions and is + /// associated with two counters, each representing the number of times the + /// expression evaluates to true or false. + BranchRegion = 4, +} + +/// This struct provides LLVM's representation of a "CoverageMappingRegion", encoded into the +/// coverage map, in accordance with the +/// [LLVM Code Coverage Mapping Format](https://github.com/rust-lang/llvm-project/blob/rustc/13.0-2021-09-30/llvm/docs/CoverageMappingFormat.rst#llvm-code-coverage-mapping-format). +/// The struct composes fields representing the `Counter` type and value(s) (injected counter +/// ID, or expression type and operands), the source file (an indirect index into a "filenames +/// array", encoded separately), and source location (start and end positions of the represented +/// code region). +/// +/// Corresponds to struct `llvm::coverage::CounterMappingRegion`. +/// +/// Must match the layout of `LLVMRustCounterMappingRegion`. +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct CounterMappingRegion { + /// The counter type and type-dependent counter data, if any. + counter: Counter, + + /// If the `RegionKind` is a `BranchRegion`, this represents the counter + /// for the false branch of the region. + false_counter: Counter, + + /// An indirect reference to the source filename. In the LLVM Coverage Mapping Format, the + /// file_id is an index into a function-specific `virtual_file_mapping` array of indexes + /// that, in turn, are used to look up the filename for this region. + file_id: u32, + + /// If the `RegionKind` is an `ExpansionRegion`, the `expanded_file_id` can be used to find + /// the mapping regions created as a result of macro expansion, by checking if their file id + /// matches the expanded file id. + expanded_file_id: u32, + + /// 1-based starting line of the mapping region. + start_line: u32, + + /// 1-based starting column of the mapping region. + start_col: u32, + + /// 1-based ending line of the mapping region. + end_line: u32, + + /// 1-based ending column of the mapping region. If the high bit is set, the current + /// mapping region is a gap area. + end_col: u32, + + kind: RegionKind, +} + +impl CounterMappingRegion { + pub(crate) fn code_region( + counter: Counter, + file_id: u32, + start_line: u32, + start_col: u32, + end_line: u32, + end_col: u32, + ) -> Self { + Self { + counter, + false_counter: Counter::zero(), + file_id, + expanded_file_id: 0, + start_line, + start_col, + end_line, + end_col, + kind: RegionKind::CodeRegion, + } + } + + // This function might be used in the future; the LLVM API is still evolving, as is coverage + // support. + #[allow(dead_code)] + pub(crate) fn branch_region( + counter: Counter, + false_counter: Counter, + file_id: u32, + start_line: u32, + start_col: u32, + end_line: u32, + end_col: u32, + ) -> Self { + Self { + counter, + false_counter, + file_id, + expanded_file_id: 0, + start_line, + start_col, + end_line, + end_col, + kind: RegionKind::BranchRegion, + } + } + + // This function might be used in the future; the LLVM API is still evolving, as is coverage + // support. + #[allow(dead_code)] + pub(crate) fn expansion_region( + file_id: u32, + expanded_file_id: u32, + start_line: u32, + start_col: u32, + end_line: u32, + end_col: u32, + ) -> Self { + Self { + counter: Counter::zero(), + false_counter: Counter::zero(), + file_id, + expanded_file_id, + start_line, + start_col, + end_line, + end_col, + kind: RegionKind::ExpansionRegion, + } + } + + // This function might be used in the future; the LLVM API is still evolving, as is coverage + // support. + #[allow(dead_code)] + pub(crate) fn skipped_region( + file_id: u32, + start_line: u32, + start_col: u32, + end_line: u32, + end_col: u32, + ) -> Self { + Self { + counter: Counter::zero(), + false_counter: Counter::zero(), + file_id, + expanded_file_id: 0, + start_line, + start_col, + end_line, + end_col, + kind: RegionKind::SkippedRegion, + } + } + + // This function might be used in the future; the LLVM API is still evolving, as is coverage + // support. + #[allow(dead_code)] + pub(crate) fn gap_region( + counter: Counter, + file_id: u32, + start_line: u32, + start_col: u32, + end_line: u32, + end_col: u32, + ) -> Self { + Self { + counter, + false_counter: Counter::zero(), + file_id, + expanded_file_id: 0, + start_line, + start_col, + end_line, + end_col: (1_u32 << 31) | end_col, + kind: RegionKind::GapRegion, + } + } +} diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs new file mode 100644 index 0000000000000..f1e68af25d406 --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs @@ -0,0 +1,311 @@ +use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind}; + +use rustc_index::{IndexSlice, IndexVec}; +use rustc_middle::bug; +use rustc_middle::mir::coverage::{ + CodeRegion, CounterId, ExpressionId, MappedExpressionIndex, Op, Operand, +}; +use rustc_middle::ty::Instance; +use rustc_middle::ty::TyCtxt; + +#[derive(Clone, Debug, PartialEq)] +pub struct Expression { + lhs: Operand, + op: Op, + rhs: Operand, + region: Option, +} + +/// Collects all of the coverage regions associated with (a) injected counters, (b) counter +/// expressions (additions or subtraction), and (c) unreachable regions (always counted as zero), +/// for a given Function. This struct also stores the `function_source_hash`, +/// computed during instrumentation, and forwarded with counters. +/// +/// Note, it may be important to understand LLVM's definitions of `unreachable` regions versus "gap +/// regions" (or "gap areas"). A gap region is a code region within a counted region (either counter +/// or expression), but the line or lines in the gap region are not executable (such as lines with +/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count +/// for a gap area is only used as the line execution count if there are no other regions on a +/// line." +#[derive(Debug)] +pub struct FunctionCoverage<'tcx> { + instance: Instance<'tcx>, + source_hash: u64, + is_used: bool, + counters: IndexVec>, + expressions: IndexVec>, + unreachable_regions: Vec, +} + +impl<'tcx> FunctionCoverage<'tcx> { + /// Creates a new set of coverage data for a used (called) function. + pub fn new(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { + Self::create(tcx, instance, true) + } + + /// Creates a new set of coverage data for an unused (never called) function. + pub fn unused(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { + Self::create(tcx, instance, false) + } + + fn create(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, is_used: bool) -> Self { + let coverageinfo = tcx.coverageinfo(instance.def); + debug!( + "FunctionCoverage::create(instance={:?}) has coverageinfo={:?}. is_used={}", + instance, coverageinfo, is_used + ); + Self { + instance, + source_hash: 0, // will be set with the first `add_counter()` + is_used, + counters: IndexVec::from_elem_n(None, coverageinfo.num_counters as usize), + expressions: IndexVec::from_elem_n(None, coverageinfo.num_expressions as usize), + unreachable_regions: Vec::new(), + } + } + + /// Returns true for a used (called) function, and false for an unused function. + pub fn is_used(&self) -> bool { + self.is_used + } + + /// Sets the function source hash value. If called multiple times for the same function, all + /// calls should have the same hash value. + pub fn set_function_source_hash(&mut self, source_hash: u64) { + if self.source_hash == 0 { + self.source_hash = source_hash; + } else { + debug_assert_eq!(source_hash, self.source_hash); + } + } + + /// Adds a code region to be counted by an injected counter intrinsic. + pub fn add_counter(&mut self, id: CounterId, region: CodeRegion) { + if let Some(previous_region) = self.counters[id].replace(region.clone()) { + assert_eq!(previous_region, region, "add_counter: code region for id changed"); + } + } + + /// Both counters and "counter expressions" (or simply, "expressions") can be operands in other + /// expressions. These are tracked as separate variants of `Operand`, so there is no ambiguity + /// between operands that are counter IDs and operands that are expression IDs. + pub fn add_counter_expression( + &mut self, + expression_id: ExpressionId, + lhs: Operand, + op: Op, + rhs: Operand, + region: Option, + ) { + debug!( + "add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}", + expression_id, lhs, op, rhs, region + ); + debug_assert!( + expression_id.as_usize() < self.expressions.len(), + "expression_id {} is out of range for expressions.len() = {} + for {:?}", + expression_id.as_usize(), + self.expressions.len(), + self, + ); + if let Some(previous_expression) = self.expressions[expression_id].replace(Expression { + lhs, + op, + rhs, + region: region.clone(), + }) { + assert_eq!( + previous_expression, + Expression { lhs, op, rhs, region }, + "add_counter_expression: expression for id changed" + ); + } + } + + /// Add a region that will be marked as "unreachable", with a constant "zero counter". + pub fn add_unreachable_region(&mut self, region: CodeRegion) { + self.unreachable_regions.push(region) + } + + /// Return the source hash, generated from the HIR node structure, and used to indicate whether + /// or not the source code structure changed between different compilations. + pub fn source_hash(&self) -> u64 { + self.source_hash + } + + /// Generate an array of CounterExpressions, and an iterator over all `Counter`s and their + /// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create + /// `CounterMappingRegion`s. + pub fn get_expressions_and_counter_regions( + &self, + ) -> (Vec, impl Iterator) { + assert!( + self.source_hash != 0 || !self.is_used, + "No counters provided the source_hash for used function: {:?}", + self.instance + ); + + let counter_regions = self.counter_regions(); + let (counter_expressions, expression_regions) = self.expressions_with_regions(); + let unreachable_regions = self.unreachable_regions(); + + let counter_regions = + counter_regions.chain(expression_regions.into_iter().chain(unreachable_regions)); + (counter_expressions, counter_regions) + } + + fn counter_regions(&self) -> impl Iterator { + self.counters.iter_enumerated().filter_map(|(index, entry)| { + // Option::map() will return None to filter out missing counters. This may happen + // if, for example, a MIR-instrumented counter is removed during an optimization. + entry.as_ref().map(|region| (Counter::counter_value_reference(index), region)) + }) + } + + fn expressions_with_regions( + &self, + ) -> (Vec, impl Iterator) { + let mut counter_expressions = Vec::with_capacity(self.expressions.len()); + let mut expression_regions = Vec::with_capacity(self.expressions.len()); + let mut new_indexes = IndexVec::from_elem_n(None, self.expressions.len()); + + // This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or + // `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type + // and value. + // + // Expressions will be returned from this function in a sequential vector (array) of + // `CounterExpression`, so the expression IDs must be mapped from their original, + // potentially sparse set of indexes. + // + // An `Expression` as an operand will have already been encountered as an `Expression` with + // operands, so its new_index will already have been generated (as a 1-up index value). + // (If an `Expression` as an operand does not have a corresponding new_index, it was + // probably optimized out, after the expression was injected into the MIR, so it will + // get a `CounterKind::Zero` instead.) + // + // In other words, an `Expression`s at any given index can include other expressions as + // operands, but expression operands can only come from the subset of expressions having + // `expression_index`s lower than the referencing `Expression`. Therefore, it is + // reasonable to look up the new index of an expression operand while the `new_indexes` + // vector is only complete up to the current `ExpressionIndex`. + type NewIndexes = IndexSlice>; + let id_to_counter = |new_indexes: &NewIndexes, operand: Operand| match operand { + Operand::Zero => Some(Counter::zero()), + Operand::Counter(id) => Some(Counter::counter_value_reference(id)), + Operand::Expression(id) => { + self.expressions + .get(id) + .expect("expression id is out of range") + .as_ref() + // If an expression was optimized out, assume it would have produced a count + // of zero. This ensures that expressions dependent on optimized-out + // expressions are still valid. + .map_or(Some(Counter::zero()), |_| new_indexes[id].map(Counter::expression)) + } + }; + + for (original_index, expression) in + self.expressions.iter_enumerated().filter_map(|(original_index, entry)| { + // Option::map() will return None to filter out missing expressions. This may happen + // if, for example, a MIR-instrumented expression is removed during an optimization. + entry.as_ref().map(|expression| (original_index, expression)) + }) + { + let optional_region = &expression.region; + let Expression { lhs, op, rhs, .. } = *expression; + + if let Some(Some((lhs_counter, mut rhs_counter))) = id_to_counter(&new_indexes, lhs) + .map(|lhs_counter| { + id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter)) + }) + { + if lhs_counter.is_zero() && op.is_subtract() { + // The left side of a subtraction was probably optimized out. As an example, + // a branch condition might be evaluated as a constant expression, and the + // branch could be removed, dropping unused counters in the process. + // + // Since counters are unsigned, we must assume the result of the expression + // can be no more and no less than zero. An expression known to evaluate to zero + // does not need to be added to the coverage map. + // + // Coverage test `loops_branches.rs` includes multiple variations of branches + // based on constant conditional (literal `true` or `false`), and demonstrates + // that the expected counts are still correct. + debug!( + "Expression subtracts from zero (assume unreachable): \ + original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", + original_index, lhs, op, rhs, optional_region, + ); + rhs_counter = Counter::zero(); + } + debug_assert!( + lhs_counter.is_zero() + // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` + || ((lhs_counter.zero_based_id() as usize) + <= usize::max(self.counters.len(), self.expressions.len())), + "lhs id={} > both counters.len()={} and expressions.len()={} + ({:?} {:?} {:?})", + lhs_counter.zero_based_id(), + self.counters.len(), + self.expressions.len(), + lhs_counter, + op, + rhs_counter, + ); + + debug_assert!( + rhs_counter.is_zero() + // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` + || ((rhs_counter.zero_based_id() as usize) + <= usize::max(self.counters.len(), self.expressions.len())), + "rhs id={} > both counters.len()={} and expressions.len()={} + ({:?} {:?} {:?})", + rhs_counter.zero_based_id(), + self.counters.len(), + self.expressions.len(), + lhs_counter, + op, + rhs_counter, + ); + + // Both operands exist. `Expression` operands exist in `self.expressions` and have + // been assigned a `new_index`. + let mapped_expression_index = + MappedExpressionIndex::from(counter_expressions.len()); + let expression = CounterExpression::new( + lhs_counter, + match op { + Op::Add => ExprKind::Add, + Op::Subtract => ExprKind::Subtract, + }, + rhs_counter, + ); + debug!( + "Adding expression {:?} = {:?}, region: {:?}", + mapped_expression_index, expression, optional_region + ); + counter_expressions.push(expression); + new_indexes[original_index] = Some(mapped_expression_index); + if let Some(region) = optional_region { + expression_regions.push((Counter::expression(mapped_expression_index), region)); + } + } else { + bug!( + "expression has one or more missing operands \ + original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", + original_index, + lhs, + op, + rhs, + optional_region, + ); + } + } + (counter_expressions, expression_regions.into_iter()) + } + + fn unreachable_regions(&self) -> impl Iterator { + self.unreachable_regions.iter().map(|region| (Counter::zero(), region)) + } +} diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 21a1ac348443e..97a99e5105675 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -1,10 +1,9 @@ use crate::common::CodegenCx; use crate::coverageinfo; +use crate::coverageinfo::ffi::{Counter, CounterExpression, CounterMappingRegion}; use crate::llvm; -use llvm::coverageinfo::CounterMappingRegion; -use rustc_codegen_ssa::coverageinfo::map::{Counter, CounterExpression}; -use rustc_codegen_ssa::traits::{ConstMethods, CoverageInfoMethods}; +use rustc_codegen_ssa::traits::ConstMethods; use rustc_data_structures::fx::FxIndexSet; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -13,8 +12,7 @@ use rustc_middle::bug; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::coverage::CodeRegion; use rustc_middle::ty::TyCtxt; - -use std::ffi::CString; +use rustc_span::Symbol; /// Generates and exports the Coverage Map. /// @@ -63,7 +61,7 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) { let mut function_data = Vec::new(); for (instance, function_coverage) in function_coverage_map { debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance); - let mangled_function_name = tcx.symbol_name(instance).to_string(); + let mangled_function_name = tcx.symbol_name(instance).name; let source_hash = function_coverage.source_hash(); let is_used = function_coverage.is_used(); let (expressions, counter_regions) = @@ -90,19 +88,24 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) { // Encode all filenames referenced by counters/expressions in this module let filenames_buffer = llvm::build_byte_buffer(|filenames_buffer| { - coverageinfo::write_filenames_section_to_buffer(&mapgen.filenames, filenames_buffer); + coverageinfo::write_filenames_section_to_buffer( + mapgen.filenames.iter().map(Symbol::as_str), + filenames_buffer, + ); }); let filenames_size = filenames_buffer.len(); let filenames_val = cx.const_bytes(&filenames_buffer); - let filenames_ref = coverageinfo::hash_bytes(filenames_buffer); + let filenames_ref = coverageinfo::hash_bytes(&filenames_buffer); // Generate the LLVM IR representation of the coverage map and store it in a well-known global let cov_data_val = mapgen.generate_coverage_map(cx, version, filenames_size, filenames_val); + let covfun_section_name = coverageinfo::covfun_section_name(cx); for (mangled_function_name, source_hash, is_used, coverage_mapping_buffer) in function_data { save_function_record( cx, + &covfun_section_name, mangled_function_name, source_hash, filenames_ref, @@ -116,7 +119,7 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) { } struct CoverageMapGenerator { - filenames: FxIndexSet, + filenames: FxIndexSet, } impl CoverageMapGenerator { @@ -127,11 +130,10 @@ impl CoverageMapGenerator { // Since rustc generates coverage maps with relative paths, the // compilation directory can be combined with the relative paths // to get absolute paths, if needed. - let working_dir = - tcx.sess.opts.working_dir.remapped_path_if_available().to_string_lossy().to_string(); - let c_filename = - CString::new(working_dir).expect("null error converting filename to C string"); - filenames.insert(c_filename); + let working_dir = Symbol::intern( + &tcx.sess.opts.working_dir.remapped_path_if_available().to_string_lossy(), + ); + filenames.insert(working_dir); Self { filenames } } @@ -169,10 +171,8 @@ impl CoverageMapGenerator { current_file_id += 1; } current_file_name = Some(file_name); - let c_filename = CString::new(file_name.to_string()) - .expect("null error converting filename to C string"); - debug!(" file_id: {} = '{:?}'", current_file_id, c_filename); - let (filenames_index, _) = self.filenames.insert_full(c_filename); + debug!(" file_id: {} = '{:?}'", current_file_id, file_name); + let (filenames_index, _) = self.filenames.insert_full(file_name); virtual_file_mapping.push(filenames_index as u32); } debug!("Adding counter {:?} to map for {:?}", counter, region); @@ -228,7 +228,8 @@ impl CoverageMapGenerator { /// specific, well-known section and name. fn save_function_record( cx: &CodegenCx<'_, '_>, - mangled_function_name: String, + covfun_section_name: &str, + mangled_function_name: &str, source_hash: u64, filenames_ref: u64, coverage_mapping_buffer: Vec, @@ -238,7 +239,7 @@ fn save_function_record( let coverage_mapping_size = coverage_mapping_buffer.len(); let coverage_mapping_val = cx.const_bytes(&coverage_mapping_buffer); - let func_name_hash = coverageinfo::hash_str(&mangled_function_name); + let func_name_hash = coverageinfo::hash_bytes(mangled_function_name.as_bytes()); let func_name_hash_val = cx.const_u64(func_name_hash); let coverage_mapping_size_val = cx.const_u32(coverage_mapping_size as u32); let source_hash_val = cx.const_u64(source_hash); @@ -254,7 +255,13 @@ fn save_function_record( /*packed=*/ true, ); - coverageinfo::save_func_record_to_mod(cx, func_name_hash, func_record_val, is_used); + coverageinfo::save_func_record_to_mod( + cx, + covfun_section_name, + func_name_hash, + func_record_val, + is_used, + ); } /// When finalizing the coverage map, `FunctionCoverage` only has the `CodeRegion`s and counters for diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs index cd261293e9b2d..621fd36b2a323 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs @@ -3,33 +3,34 @@ use crate::llvm; use crate::abi::Abi; use crate::builder::Builder; use crate::common::CodegenCx; +use crate::coverageinfo::ffi::{CounterExpression, CounterMappingRegion}; +use crate::coverageinfo::map_data::FunctionCoverage; use libc::c_uint; -use llvm::coverageinfo::CounterMappingRegion; -use rustc_codegen_ssa::coverageinfo::map::{CounterExpression, FunctionCoverage}; use rustc_codegen_ssa::traits::{ - BaseTypeMethods, BuilderMethods, ConstMethods, CoverageInfoBuilderMethods, CoverageInfoMethods, - MiscMethods, StaticMethods, + BaseTypeMethods, BuilderMethods, ConstMethods, CoverageInfoBuilderMethods, MiscMethods, + StaticMethods, }; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_llvm::RustString; use rustc_middle::bug; -use rustc_middle::mir::coverage::{ - CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionId, Op, -}; +use rustc_middle::mir::coverage::{CodeRegion, CounterId, CoverageKind, ExpressionId, Op, Operand}; +use rustc_middle::mir::Coverage; use rustc_middle::ty; -use rustc_middle::ty::layout::FnAbiOf; -use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt}; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::Instance; +use rustc_middle::ty::Ty; use std::cell::RefCell; -use std::ffi::CString; +pub(crate) mod ffi; +pub(crate) mod map_data; pub mod mapgen; -const UNUSED_FUNCTION_COUNTER_ID: CounterValueReference = CounterValueReference::START; +const UNUSED_FUNCTION_COUNTER_ID: CounterId = CounterId::START; const VAR_ALIGN_BYTES: usize = 8; @@ -53,11 +54,17 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> { } } -impl<'ll, 'tcx> CoverageInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { - fn coverageinfo_finalize(&self) { +// These methods used to be part of trait `CoverageInfoMethods`, which no longer +// exists after most coverage code was moved out of SSA. +impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { + pub(crate) fn coverageinfo_finalize(&self) { mapgen::finalize(self) } + /// For LLVM codegen, returns a function-specific `Value` for a global + /// string, to hold the function name passed to LLVM intrinsic + /// `instrprof.increment()`. The `Value` is only created once per instance. + /// Multiple invocations with the same instance return the same `Value`. fn get_pgo_func_name_var(&self, instance: Instance<'tcx>) -> &'ll llvm::Value { if let Some(coverage_context) = self.coverage_context() { debug!("getting pgo_func_name_var for instance={:?}", instance); @@ -94,6 +101,54 @@ impl<'ll, 'tcx> CoverageInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { } impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { + fn add_coverage(&mut self, instance: Instance<'tcx>, coverage: &Coverage) { + let bx = self; + + let Coverage { kind, code_region } = coverage.clone(); + match kind { + CoverageKind::Counter { function_source_hash, id } => { + if bx.set_function_source_hash(instance, function_source_hash) { + // If `set_function_source_hash()` returned true, the coverage map is enabled, + // so continue adding the counter. + if let Some(code_region) = code_region { + // Note: Some counters do not have code regions, but may still be referenced + // from expressions. In that case, don't add the counter to the coverage map, + // but do inject the counter intrinsic. + bx.add_coverage_counter(instance, id, code_region); + } + + let coverageinfo = bx.tcx().coverageinfo(instance.def); + + let fn_name = bx.get_pgo_func_name_var(instance); + let hash = bx.const_u64(function_source_hash); + let num_counters = bx.const_u32(coverageinfo.num_counters); + let index = bx.const_u32(id.as_u32()); + debug!( + "codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})", + fn_name, hash, num_counters, index, + ); + bx.instrprof_increment(fn_name, hash, num_counters, index); + } + } + CoverageKind::Expression { id, lhs, op, rhs } => { + bx.add_coverage_counter_expression(instance, id, lhs, op, rhs, code_region); + } + CoverageKind::Unreachable => { + bx.add_coverage_unreachable( + instance, + code_region.expect("unreachable regions always have code regions"), + ); + } + } + } +} + +// These methods used to be part of trait `CoverageInfoBuilderMethods`, but +// after moving most coverage code out of SSA they are now just ordinary methods. +impl<'tcx> Builder<'_, '_, 'tcx> { + /// Returns true if the function source hash was added to the coverage map (even if it had + /// already been added, for this instance). Returns false *only* if `-C instrument-coverage` is + /// not enabled (a coverage map is not being generated). fn set_function_source_hash( &mut self, instance: Instance<'tcx>, @@ -115,10 +170,12 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { } } + /// Returns true if the counter was added to the coverage map; false if `-C instrument-coverage` + /// is not enabled (a coverage map is not being generated). fn add_coverage_counter( &mut self, instance: Instance<'tcx>, - id: CounterValueReference, + id: CounterId, region: CodeRegion, ) -> bool { if let Some(coverage_context) = self.coverage_context() { @@ -137,13 +194,15 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { } } + /// Returns true if the expression was added to the coverage map; false if + /// `-C instrument-coverage` is not enabled (a coverage map is not being generated). fn add_coverage_counter_expression( &mut self, instance: Instance<'tcx>, - id: InjectedExpressionId, - lhs: ExpressionOperandId, + id: ExpressionId, + lhs: Operand, op: Op, - rhs: ExpressionOperandId, + rhs: Operand, region: Option, ) -> bool { if let Some(coverage_context) = self.coverage_context() { @@ -163,6 +222,8 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> { } } + /// Returns true if the region was added to the coverage map; false if `-C instrument-coverage` + /// is not enabled (a coverage map is not being generated). fn add_coverage_unreachable(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool { if let Some(coverage_context) = self.coverage_context() { debug!( @@ -186,7 +247,7 @@ fn declare_unused_fn<'tcx>(cx: &CodegenCx<'_, 'tcx>, def_id: DefId) -> Instance< let instance = Instance::new( def_id, - InternalSubsts::for_item(tcx, def_id, |param, _| { + GenericArgs::for_item(tcx, def_id, |param, _| { if let ty::GenericParamDefKind::Lifetime = param.kind { tcx.lifetimes.re_erased.into() } else { @@ -199,8 +260,8 @@ fn declare_unused_fn<'tcx>(cx: &CodegenCx<'_, 'tcx>, def_id: DefId) -> Instance< tcx.symbol_name(instance).name, cx.fn_abi_of_fn_ptr( ty::Binder::dummy(tcx.mk_fn_sig( - [tcx.mk_unit()], - tcx.mk_unit(), + [Ty::new_unit(tcx)], + Ty::new_unit(tcx), false, hir::Unsafety::Unsafe, Abi::Rust, @@ -270,21 +331,32 @@ fn create_pgo_func_name_var<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>, ) -> &'ll llvm::Value { - let mangled_fn_name = CString::new(cx.tcx.symbol_name(instance).name) - .expect("error converting function name to C string"); + let mangled_fn_name: &str = cx.tcx.symbol_name(instance).name; let llfn = cx.get_fn(instance); - unsafe { llvm::LLVMRustCoverageCreatePGOFuncNameVar(llfn, mangled_fn_name.as_ptr()) } + unsafe { + llvm::LLVMRustCoverageCreatePGOFuncNameVar( + llfn, + mangled_fn_name.as_ptr().cast(), + mangled_fn_name.len(), + ) + } } pub(crate) fn write_filenames_section_to_buffer<'a>( - filenames: impl IntoIterator, + filenames: impl IntoIterator, buffer: &RustString, ) { - let c_str_vec = filenames.into_iter().map(|cstring| cstring.as_ptr()).collect::>(); + let (pointers, lengths) = filenames + .into_iter() + .map(|s: &str| (s.as_ptr().cast(), s.len())) + .unzip::<_, _, Vec<_>, Vec<_>>(); + unsafe { llvm::LLVMRustCoverageWriteFilenamesSectionToBuffer( - c_str_vec.as_ptr(), - c_str_vec.len(), + pointers.as_ptr(), + pointers.len(), + lengths.as_ptr(), + lengths.len(), buffer, ); } @@ -309,12 +381,7 @@ pub(crate) fn write_mapping_to_buffer( } } -pub(crate) fn hash_str(strval: &str) -> u64 { - let strval = CString::new(strval).expect("null error converting hashable str to C string"); - unsafe { llvm::LLVMRustCoverageHashCString(strval.as_ptr()) } -} - -pub(crate) fn hash_bytes(bytes: Vec) -> u64 { +pub(crate) fn hash_bytes(bytes: &[u8]) -> u64 { unsafe { llvm::LLVMRustCoverageHashByteArray(bytes.as_ptr().cast(), bytes.len()) } } @@ -349,6 +416,7 @@ pub(crate) fn save_cov_data_to_mod<'ll, 'tcx>( pub(crate) fn save_func_record_to_mod<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, + covfun_section_name: &str, func_name_hash: u64, func_record_val: &'ll llvm::Value, is_used: bool, @@ -364,20 +432,33 @@ pub(crate) fn save_func_record_to_mod<'ll, 'tcx>( let func_record_var_name = format!("__covrec_{:X}{}", func_name_hash, if is_used { "u" } else { "" }); debug!("function record var name: {:?}", func_record_var_name); - - let func_record_section_name = llvm::build_string(|s| unsafe { - llvm::LLVMRustCoverageWriteFuncSectionNameToString(cx.llmod, s); - }) - .expect("Rust Coverage function record section name failed UTF-8 conversion"); - debug!("function record section name: {:?}", func_record_section_name); + debug!("function record section name: {:?}", covfun_section_name); let llglobal = llvm::add_global(cx.llmod, cx.val_ty(func_record_val), &func_record_var_name); llvm::set_initializer(llglobal, func_record_val); llvm::set_global_constant(llglobal, true); llvm::set_linkage(llglobal, llvm::Linkage::LinkOnceODRLinkage); llvm::set_visibility(llglobal, llvm::Visibility::Hidden); - llvm::set_section(llglobal, &func_record_section_name); + llvm::set_section(llglobal, covfun_section_name); llvm::set_alignment(llglobal, VAR_ALIGN_BYTES); llvm::set_comdat(cx.llmod, llglobal, &func_record_var_name); cx.add_used_global(llglobal); } + +/// Returns the section name string to pass through to the linker when embedding +/// per-function coverage information in the object file, according to the target +/// platform's object file format. +/// +/// LLVM's coverage tools read coverage mapping details from this section when +/// producing coverage reports. +/// +/// Typical values are: +/// - `__llvm_covfun` on Linux +/// - `__LLVM_COV,__llvm_covfun` on macOS (includes `__LLVM_COV,` segment prefix) +/// - `.lcovfun$M` on Windows (includes `$M` sorting suffix) +pub(crate) fn covfun_section_name(cx: &CodegenCx<'_, '_>) -> String { + llvm::build_string(|s| unsafe { + llvm::LLVMRustCoverageWriteFuncSectionNameToString(cx.llmod, s); + }) + .expect("Rust Coverage function record section name failed UTF-8 conversion") +} diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs b/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs index 3fff112a02056..d174a3593b999 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs @@ -65,10 +65,10 @@ fn make_mir_scope<'ll, 'tcx>( debug_context.scopes[parent] } else { // The root is the function itself. - let loc = cx.lookup_debug_loc(mir.span.lo()); + let file = cx.sess().source_map().lookup_source_file(mir.span.lo()); debug_context.scopes[scope] = DebugScope { - file_start_pos: loc.file.start_pos, - file_end_pos: loc.file.end_pos, + file_start_pos: file.start_pos, + file_end_pos: file.end_pos, ..debug_context.scopes[scope] }; instantiated.insert(scope); @@ -91,9 +91,9 @@ fn make_mir_scope<'ll, 'tcx>( // FIXME(eddyb) this would be `self.monomorphize(&callee)` // if this is moved to `rustc_codegen_ssa::mir::debuginfo`. let callee = cx.tcx.subst_and_normalize_erasing_regions( - instance.substs, + instance.args, ty::ParamEnv::reveal_all(), - ty::EarlyBinder(callee), + ty::EarlyBinder::bind(callee), ); let callee_fn_abi = cx.fn_abi_of_instance(callee, ty::List::empty()); cx.dbg_scope_fn(callee, callee_fn_abi, None) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index 37f30917609ae..425e935bc9f2e 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -17,8 +17,7 @@ use rustc_span::symbol::sym; /// .debug_gdb_scripts global is referenced, so it isn't removed by the linker. pub fn insert_reference_to_gdb_debug_scripts_section_global(bx: &mut Builder<'_, '_, '_>) { if needs_gdb_debug_scripts_section(bx) { - let gdb_debug_scripts_section = - bx.const_bitcast(get_or_insert_gdb_debug_scripts_section_global(bx), bx.type_i8p()); + let gdb_debug_scripts_section = get_or_insert_gdb_debug_scripts_section_global(bx); // Load just the first byte as that's all that's necessary to force // LLVM to keep around the reference to the global. let volatile_load_instruction = bx.volatile_load(bx.type_i8(), gdb_debug_scripts_section); @@ -54,7 +53,7 @@ pub fn get_or_insert_gdb_debug_scripts_section_global<'ll>(cx: &CodegenCx<'ll, ' // The initial byte `4` instructs GDB that the following pretty printer // is defined inline as opposed to in a standalone file. section_contents.extend_from_slice(b"\x04"); - let vis_name = format!("pretty-printer-{}-{}\n", crate_name, index); + let vis_name = format!("pretty-printer-{crate_name}-{index}\n"); section_contents.extend_from_slice(vis_name.as_bytes()); section_contents.extend_from_slice(&visualizer.src); @@ -93,7 +92,7 @@ pub fn needs_gdb_debug_scripts_section(cx: &CodegenCx<'_, '_>) -> bool { // each rlib could produce a different set of visualizers that would be embedded // in the `.debug_gdb_scripts` section. For that reason, we make sure that the // section is only emitted for leaf crates. - let embed_visualizers = cx.sess().crate_types().iter().any(|&crate_type| match crate_type { + let embed_visualizers = cx.tcx.crate_types().iter().any(|&crate_type| match crate_type { CrateType::Executable | CrateType::Dylib | CrateType::Cdylib | CrateType::Staticlib => { // These are crate types for which we will embed pretty printers since they // are treated as leaf crates. diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index bd2fba1260282..f8cbcbd5ec852 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -168,7 +168,7 @@ fn build_pointer_or_reference_di_node<'ll, 'tcx>( // a (fat) pointer. Make sure it is not called for e.g. `Box`. debug_assert_eq!( cx.size_and_align_of(ptr_type), - cx.size_and_align_of(cx.tcx.mk_mut_ptr(pointee_type)) + cx.size_and_align_of(Ty::new_mut_ptr(cx.tcx, pointee_type)) ); let pointee_type_di_node = type_di_node(cx, pointee_type); @@ -184,9 +184,7 @@ fn build_pointer_or_reference_di_node<'ll, 'tcx>( debug_assert_eq!( (data_layout.pointer_size, data_layout.pointer_align.abi), cx.size_and_align_of(ptr_type), - "ptr_type={}, pointee_type={}", - ptr_type, - pointee_type, + "ptr_type={ptr_type}, pointee_type={pointee_type}", ); let di_node = unsafe { @@ -223,8 +221,11 @@ fn build_pointer_or_reference_di_node<'ll, 'tcx>( // at all and instead emit regular struct debuginfo for it. We just // need to make sure that we don't break existing debuginfo consumers // by doing that (at least not without a warning period). - let layout_type = - if ptr_type.is_box() { cx.tcx.mk_mut_ptr(pointee_type) } else { ptr_type }; + let layout_type = if ptr_type.is_box() { + Ty::new_mut_ptr(cx.tcx, pointee_type) + } else { + ptr_type + }; let layout = cx.layout_of(layout_type); let addr_field = layout.field(cx, abi::FAT_PTR_ADDR); @@ -430,7 +431,7 @@ pub fn type_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, t: Ty<'tcx>) -> &'ll D return existing_di_node; } - debug!("type_di_node: {:?}", t); + debug!("type_di_node: {:?} kind: {:?}", t, t.kind()); let DINodeCreationResult { di_node, already_stored_in_typemap } = match *t.kind() { ty::Never | ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) => { @@ -446,7 +447,7 @@ pub fn type_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, t: Ty<'tcx>) -> &'ll D } // Box may have a non-ZST allocator A. In that case, we // cannot treat Box as just an owned alias of `*mut T`. - ty::Adt(def, substs) if def.is_box() && cx.layout_of(substs.type_at(1)).is_zst() => { + ty::Adt(def, args) if def.is_box() && cx.layout_of(args.type_at(1)).is_zst() => { build_pointer_or_reference_di_node(cx, t, t.boxed_ty(), unique_type_id) } ty::FnDef(..) | ty::FnPtr(_) => build_subroutine_type_di_node(cx, unique_type_id), @@ -518,7 +519,7 @@ fn recursion_marker_type_di_node<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) -> &'ll D fn hex_encode(data: &[u8]) -> String { let mut hex_string = String::with_capacity(data.len() * 2); for byte in data.iter() { - write!(&mut hex_string, "{:02x}", byte).unwrap(); + write!(&mut hex_string, "{byte:02x}").unwrap(); } hex_string } @@ -736,7 +737,10 @@ fn build_foreign_type_di_node<'ll, 'tcx>( debug!("build_foreign_type_di_node: {:?}", t); let &ty::Foreign(def_id) = unique_type_id.expect_ty().kind() else { - bug!("build_foreign_type_di_node() called with unexpected type: {:?}", unique_type_id.expect_ty()); + bug!( + "build_foreign_type_di_node() called with unexpected type: {:?}", + unique_type_id.expect_ty() + ); }; build_type_with_children( @@ -760,7 +764,7 @@ fn build_param_type_di_node<'ll, 'tcx>( t: Ty<'tcx>, ) -> DINodeCreationResult<'ll> { debug!("build_param_type_di_node: {:?}", t); - let name = format!("{:?}", t); + let name = format!("{t:?}"); DINodeCreationResult { di_node: unsafe { llvm::LLVMRustDIBuilderCreateBasicType( @@ -808,7 +812,7 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>( debug!("build_compile_unit_di_node: {:?}", name_in_debuginfo); let rustc_producer = format!("rustc version {}", tcx.sess.cfg_version); // FIXME(#41252) Remove "clang LLVM" if we can get GDB and LLVM to play nice. - let producer = format!("clang LLVM ({})", rustc_producer); + let producer = format!("clang LLVM ({rustc_producer})"); let name_in_debuginfo = name_in_debuginfo.to_string_lossy(); let work_dir = tcx.sess.opts.working_dir.to_string_lossy(FileNameDisplayPreference::Remapped); @@ -882,21 +886,6 @@ pub fn build_compile_unit_di_node<'ll, 'tcx>( llvm::LLVMAddNamedMetadataOperand(debug_context.llmod, llvm_gcov_ident.as_ptr(), val); } - // Insert `llvm.ident` metadata on the wasm targets since that will - // get hooked up to the "producer" sections `processed-by` information. - if tcx.sess.target.is_like_wasm { - let name_metadata = llvm::LLVMMDStringInContext( - debug_context.llcontext, - rustc_producer.as_ptr().cast(), - rustc_producer.as_bytes().len() as c_uint, - ); - llvm::LLVMAddNamedMetadataOperand( - debug_context.llmod, - cstr!("llvm.ident").as_ptr(), - llvm::LLVMMDNodeInContext(debug_context.llcontext, &name_metadata, 1), - ); - } - return unit_metadata; }; @@ -1001,14 +990,8 @@ fn build_upvar_field_di_nodes<'ll, 'tcx>( closure_or_generator_di_node: &'ll DIType, ) -> SmallVec<&'ll DIType> { let (&def_id, up_var_tys) = match closure_or_generator_ty.kind() { - ty::Generator(def_id, substs, _) => { - let upvar_tys: SmallVec<_> = substs.as_generator().prefix_tys().collect(); - (def_id, upvar_tys) - } - ty::Closure(def_id, substs) => { - let upvar_tys: SmallVec<_> = substs.as_closure().upvar_tys().collect(); - (def_id, upvar_tys) - } + ty::Generator(def_id, args, _) => (def_id, args.as_generator().prefix_tys()), + ty::Closure(def_id, args) => (def_id, args.as_closure().upvar_tys()), _ => { bug!( "build_upvar_field_di_nodes() called with non-closure-or-generator-type: {:?}", @@ -1018,9 +1001,7 @@ fn build_upvar_field_di_nodes<'ll, 'tcx>( }; debug_assert!( - up_var_tys - .iter() - .all(|&t| t == cx.tcx.normalize_erasing_regions(ParamEnv::reveal_all(), t)) + up_var_tys.iter().all(|t| t == cx.tcx.normalize_erasing_regions(ParamEnv::reveal_all(), t)) ); let capture_names = cx.tcx.closure_saved_names_of_captured_variables(def_id); @@ -1034,7 +1015,7 @@ fn build_upvar_field_di_nodes<'ll, 'tcx>( build_field_di_node( cx, closure_or_generator_di_node, - capture_name, + capture_name.as_str(), cx.size_and_align_of(up_var_ty), layout.fields.offset(index), DIFlags::FlagZero, @@ -1096,7 +1077,7 @@ fn build_closure_env_di_node<'ll, 'tcx>( unique_type_id: UniqueTypeId<'tcx>, ) -> DINodeCreationResult<'ll> { let closure_env_type = unique_type_id.expect_ty(); - let &ty::Closure(def_id, _substs) = closure_env_type.kind() else { + let &ty::Closure(def_id, _args) = closure_env_type.kind() else { bug!("build_closure_env_di_node() called with non-closure-type: {:?}", closure_env_type) }; let containing_scope = get_namespace_for_item(cx, def_id); @@ -1174,11 +1155,11 @@ fn build_generic_type_param_di_nodes<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, ty: Ty<'tcx>, ) -> SmallVec<&'ll DIType> { - if let ty::Adt(def, substs) = *ty.kind() { - if substs.types().next().is_some() { + if let ty::Adt(def, args) = *ty.kind() { + if args.types().next().is_some() { let generics = cx.tcx.generics_of(def.did()); let names = get_parameter_names(cx, generics); - let template_params: SmallVec<_> = iter::zip(substs, names) + let template_params: SmallVec<_> = iter::zip(args, names) .filter_map(|(kind, name)| { kind.as_type().map(|ty| { let actual_type = @@ -1298,7 +1279,7 @@ fn build_vtable_type_di_node<'ll, 'tcx>( // All function pointers are described as opaque pointers. This could be improved in the future // by describing them as actual function pointers. - let void_pointer_ty = tcx.mk_imm_ptr(tcx.types.unit); + let void_pointer_ty = Ty::new_imm_ptr(tcx, tcx.types.unit); let void_pointer_type_di_node = type_di_node(cx, void_pointer_ty); let usize_di_node = type_di_node(cx, tcx.types.usize); let (pointer_size, pointer_align) = cx.size_and_align_of(void_pointer_ty); @@ -1340,10 +1321,10 @@ fn build_vtable_type_di_node<'ll, 'tcx>( // Note: This code does not try to give a proper name to each method // because their might be multiple methods with the same name // (coming from different traits). - (format!("__method{}", index), void_pointer_type_di_node) + (format!("__method{index}"), void_pointer_type_di_node) } ty::VtblEntry::TraitVPtr(_) => { - (format!("__super_trait_ptr{}", index), void_pointer_type_di_node) + (format!("__super_trait_ptr{index}"), void_pointer_type_di_node) } ty::VtblEntry::MetadataAlign => ("align".to_string(), usize_di_node), ty::VtblEntry::MetadataSize => ("size".to_string(), usize_di_node), @@ -1388,7 +1369,7 @@ fn vcall_visibility_metadata<'ll, 'tcx>( let trait_def_id = trait_ref_self.def_id(); let trait_vis = cx.tcx.visibility(trait_def_id); - let cgus = cx.sess().codegen_units(); + let cgus = cx.sess().codegen_units().as_usize(); let single_cgu = cgus == 1; let lto = cx.sess().lto(); @@ -1513,5 +1494,5 @@ pub fn tuple_field_name(field_index: usize) -> Cow<'static, str> { TUPLE_FIELD_NAMES .get(field_index) .map(|s| Cow::from(*s)) - .unwrap_or_else(|| Cow::from(format!("__{}", field_index))) + .unwrap_or_else(|| Cow::from(format!("__{field_index}"))) } diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs index ecb0912d32881..88040557a9b33 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs @@ -12,7 +12,7 @@ use rustc_middle::{ ty::{ self, layout::{LayoutOf, TyAndLayout}, - AdtDef, GeneratorSubsts, Ty, + AdtDef, GeneratorArgs, Ty, }, }; use rustc_target::abi::{Align, Endian, Size, TagEncoding, VariantIdx, Variants}; @@ -199,7 +199,7 @@ pub(super) fn build_enum_type_di_node<'ll, 'tcx>( let enum_type = unique_type_id.expect_ty(); let &ty::Adt(enum_adt_def, _) = enum_type.kind() else { bug!("build_enum_type_di_node() called with non-enum type: `{:?}`", enum_type) - }; + }; let enum_type_and_layout = cx.layout_of(enum_type); let enum_type_name = compute_debuginfo_type_name(cx.tcx, enum_type, false); @@ -667,20 +667,21 @@ fn build_union_fields_for_direct_tag_generator<'ll, 'tcx>( generator_type_and_layout: TyAndLayout<'tcx>, generator_type_di_node: &'ll DIType, ) -> SmallVec<&'ll DIType> { - let Variants::Multiple { tag_encoding: TagEncoding::Direct, tag_field, .. } = generator_type_and_layout.variants else { + let Variants::Multiple { tag_encoding: TagEncoding::Direct, tag_field, .. } = + generator_type_and_layout.variants + else { bug!("This function only supports layouts with directly encoded tags.") }; - let (generator_def_id, generator_substs) = match generator_type_and_layout.ty.kind() { - &ty::Generator(def_id, substs, _) => (def_id, substs.as_generator()), + let (generator_def_id, generator_args) = match generator_type_and_layout.ty.kind() { + &ty::Generator(def_id, args, _) => (def_id, args.as_generator()), _ => unreachable!(), }; - let (generator_layout, state_specific_upvar_names) = - cx.tcx.generator_layout_and_saved_local_names(generator_def_id); + let generator_layout = cx.tcx.optimized_mir(generator_def_id).generator_layout().unwrap(); let common_upvar_names = cx.tcx.closure_saved_names_of_captured_variables(generator_def_id); - let variant_range = generator_substs.variant_range(generator_def_id, cx.tcx); + let variant_range = generator_args.variant_range(generator_def_id, cx.tcx); let variant_count = (variant_range.start.as_u32()..variant_range.end.as_u32()).len(); let tag_base_type = tag_base_type(cx, generator_type_and_layout); @@ -690,11 +691,11 @@ fn build_union_fields_for_direct_tag_generator<'ll, 'tcx>( generator_type_di_node, variant_range .clone() - .map(|variant_index| (variant_index, GeneratorSubsts::variant_name(variant_index))), + .map(|variant_index| (variant_index, GeneratorArgs::variant_name(variant_index))), ); let discriminants: IndexVec = { - let discriminants_iter = generator_substs.discriminants(generator_def_id, cx.tcx); + let discriminants_iter = generator_args.discriminants(generator_def_id, cx.tcx); let mut discriminants: IndexVec = IndexVec::with_capacity(variant_count); for (variant_index, discr) in discriminants_iter { @@ -714,7 +715,6 @@ fn build_union_fields_for_direct_tag_generator<'ll, 'tcx>( generator_type_and_layout, generator_type_di_node, generator_layout, - &state_specific_upvar_names, &common_upvar_names, ); diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/mod.rs index 9e0e847a15565..d3239d5c358f5 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/mod.rs @@ -6,11 +6,11 @@ use rustc_hir::def::CtorKind; use rustc_index::IndexSlice; use rustc_middle::{ bug, - mir::{GeneratorLayout, GeneratorSavedLocal}, + mir::GeneratorLayout, ty::{ self, layout::{IntegerExt, LayoutOf, PrimitiveExt, TyAndLayout}, - AdtDef, GeneratorSubsts, Ty, VariantDef, + AdtDef, GeneratorArgs, Ty, VariantDef, }, }; use rustc_span::Symbol; @@ -51,7 +51,7 @@ pub(super) fn build_enum_type_di_node<'ll, 'tcx>( let enum_type = unique_type_id.expect_ty(); let &ty::Adt(enum_adt_def, _) = enum_type.kind() else { bug!("build_enum_type_di_node() called with non-enum type: `{:?}`", enum_type) - }; + }; let enum_type_and_layout = cx.layout_of(enum_type); @@ -323,10 +323,9 @@ pub fn build_generator_variant_struct_type_di_node<'ll, 'tcx>( generator_type_and_layout: TyAndLayout<'tcx>, generator_type_di_node: &'ll DIType, generator_layout: &GeneratorLayout<'tcx>, - state_specific_upvar_names: &IndexSlice>, - common_upvar_names: &[String], + common_upvar_names: &IndexSlice, ) -> &'ll DIType { - let variant_name = GeneratorSubsts::variant_name(variant_index); + let variant_name = GeneratorArgs::variant_name(variant_index); let unique_type_id = UniqueTypeId::for_enum_variant_struct_type( cx.tcx, generator_type_and_layout.ty, @@ -335,8 +334,8 @@ pub fn build_generator_variant_struct_type_di_node<'ll, 'tcx>( let variant_layout = generator_type_and_layout.for_variant(cx, variant_index); - let generator_substs = match generator_type_and_layout.ty.kind() { - ty::Generator(_, substs, _) => substs.as_generator(), + let generator_args = match generator_type_and_layout.ty.kind() { + ty::Generator(_, args, _) => args.as_generator(), _ => unreachable!(), }; @@ -357,7 +356,7 @@ pub fn build_generator_variant_struct_type_di_node<'ll, 'tcx>( .map(|field_index| { let generator_saved_local = generator_layout.variant_fields[variant_index] [FieldIdx::from_usize(field_index)]; - let field_name_maybe = state_specific_upvar_names[generator_saved_local]; + let field_name_maybe = generator_layout.field_names[generator_saved_local]; let field_name = field_name_maybe .as_ref() .map(|s| Cow::from(s.as_str())) @@ -378,14 +377,16 @@ pub fn build_generator_variant_struct_type_di_node<'ll, 'tcx>( .collect(); // Fields that are common to all states - let common_fields: SmallVec<_> = generator_substs + let common_fields: SmallVec<_> = generator_args .prefix_tys() + .iter() + .zip(common_upvar_names) .enumerate() - .map(|(index, upvar_ty)| { + .map(|(index, (upvar_ty, upvar_name))| { build_field_di_node( cx, variant_struct_type_di_node, - &common_upvar_names[index], + upvar_name.as_str(), cx.size_and_align_of(upvar_ty), generator_type_and_layout.fields.offset(index), DIFlags::FlagZero, diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs index 978141917c6b0..feac40d8c306c 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs @@ -57,7 +57,7 @@ pub(super) fn build_enum_type_di_node<'ll, 'tcx>( let enum_type = unique_type_id.expect_ty(); let &ty::Adt(enum_adt_def, _) = enum_type.kind() else { bug!("build_enum_type_di_node() called with non-enum type: `{:?}`", enum_type) - }; + }; let containing_scope = get_namespace_for_item(cx, enum_adt_def.did()); let enum_type_and_layout = cx.layout_of(enum_type); @@ -132,9 +132,9 @@ pub(super) fn build_generator_di_node<'ll, 'tcx>( unique_type_id: UniqueTypeId<'tcx>, ) -> DINodeCreationResult<'ll> { let generator_type = unique_type_id.expect_ty(); - let &ty::Generator(generator_def_id, _, _ ) = generator_type.kind() else { + let &ty::Generator(generator_def_id, _, _) = generator_type.kind() else { bug!("build_generator_di_node() called with non-generator type: `{:?}`", generator_type) - }; + }; let containing_scope = get_namespace_for_item(cx, generator_def_id); let generator_type_and_layout = cx.layout_of(generator_type); @@ -155,10 +155,12 @@ pub(super) fn build_generator_di_node<'ll, 'tcx>( DIFlags::FlagZero, ), |cx, generator_type_di_node| { - let (generator_layout, state_specific_upvar_names) = - cx.tcx.generator_layout_and_saved_local_names(generator_def_id); + let generator_layout = + cx.tcx.optimized_mir(generator_def_id).generator_layout().unwrap(); - let Variants::Multiple { tag_encoding: TagEncoding::Direct, ref variants, .. } = generator_type_and_layout.variants else { + let Variants::Multiple { tag_encoding: TagEncoding::Direct, ref variants, .. } = + generator_type_and_layout.variants + else { bug!( "Encountered generator with non-direct-tag layout: {:?}", generator_type_and_layout @@ -173,7 +175,7 @@ pub(super) fn build_generator_di_node<'ll, 'tcx>( .indices() .map(|variant_index| { // FIXME: This is problematic because just a number is not a valid identifier. - // GeneratorSubsts::variant_name(variant_index), would be consistent + // GeneratorArgs::variant_name(variant_index), would be consistent // with enums? let variant_name = format!("{}", variant_index.as_usize()).into(); @@ -195,7 +197,6 @@ pub(super) fn build_generator_di_node<'ll, 'tcx>( generator_type_and_layout, generator_type_di_node, generator_layout, - &state_specific_upvar_names, &common_upvar_names, ), source_info, @@ -412,13 +413,7 @@ fn build_enum_variant_member_di_node<'ll, 'tcx>( enum_type_and_layout.size.bits(), enum_type_and_layout.align.abi.bits() as u32, Size::ZERO.bits(), - discr_value.opt_single_val().map(|value| { - // NOTE(eddyb) do *NOT* remove this assert, until - // we pass the full 128-bit value to LLVM, otherwise - // truncation will be silent and remain undetected. - assert_eq!(value as u64 as u128, value); - cx.const_u64(value as u64) - }), + discr_value.opt_single_val().map(|value| cx.const_u128(value)), DIFlags::FlagZero, variant_member_info.variant_struct_type_di_node, ) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs index c3f0a0033b0ea..40714a0afe91c 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs @@ -27,7 +27,7 @@ use rustc_hir::def_id::{DefId, DefIdMap}; use rustc_index::IndexVec; use rustc_middle::mir; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TypeVisitableExt}; use rustc_session::config::{self, DebugInfo}; use rustc_session::Session; @@ -263,7 +263,7 @@ impl CodegenCx<'_, '_> { pub fn lookup_debug_loc(&self, pos: BytePos) -> DebugLoc { let (file, line, col) = match self.sess().source_map().lookup_line(pos) { Ok(SourceFileAndLine { sf: file, line }) => { - let line_pos = file.line_begin_pos(pos); + let line_pos = file.lines(|lines| lines[line]); // Use 1-based indexing. let line = (line + 1) as u32; @@ -332,25 +332,25 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { llvm::LLVMRustDIBuilderCreateSubroutineType(DIB(self), fn_signature) }; - let mut name = String::new(); + let mut name = String::with_capacity(64); type_names::push_item_name(tcx, def_id, false, &mut name); // Find the enclosing function, in case this is a closure. let enclosing_fn_def_id = tcx.typeck_root_def_id(def_id); - // We look up the generics of the enclosing function and truncate the substs + // We look up the generics of the enclosing function and truncate the args // to their length in order to cut off extra stuff that might be in there for // closures or generators. let generics = tcx.generics_of(enclosing_fn_def_id); - let substs = instance.substs.truncate_to(tcx, generics); + let args = instance.args.truncate_to(tcx, generics); type_names::push_generic_params( tcx, - tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), substs), + tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), args), &mut name, ); - let template_parameters = get_template_parameters(self, generics, substs); + let template_parameters = get_template_parameters(self, generics, args); let linkage_name = &mangled_name_of_instance(self, instance).name; // Omit the linkage_name if it is the same as subprogram name. @@ -454,7 +454,7 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { ty::Array(ct, _) if (*ct == cx.tcx.types.u8) || cx.layout_of(*ct).is_zst() => { - cx.tcx.mk_imm_ptr(*ct) + Ty::new_imm_ptr(cx.tcx, *ct) } _ => t, }; @@ -471,16 +471,16 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { fn get_template_parameters<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, generics: &ty::Generics, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> &'ll DIArray { - if substs.types().next().is_none() { + if args.types().next().is_none() { return create_DIArray(DIB(cx), &[]); } // Again, only create type information if full debuginfo is enabled let template_params: Vec<_> = if cx.sess().opts.debuginfo == DebugInfo::Full { let names = get_parameter_names(cx, generics); - iter::zip(substs, names) + iter::zip(args, names) .filter_map(|(kind, name)| { kind.as_type().map(|ty| { let actual_type = @@ -527,7 +527,7 @@ impl<'ll, 'tcx> DebugInfoMethods<'tcx> for CodegenCx<'ll, 'tcx> { // If the method does *not* belong to a trait, proceed if cx.tcx.trait_id_of_impl(impl_def_id).is_none() { let impl_self_ty = cx.tcx.subst_and_normalize_erasing_regions( - instance.substs, + instance.args, ty::ParamEnv::reveal_all(), cx.tcx.type_of(impl_def_id), ); diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs b/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs index d5ea48c311b94..fa61c7dde18b4 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs @@ -28,7 +28,7 @@ pub fn item_namespace<'ll>(cx: &CodegenCx<'ll, '_>, def_id: DefId) -> &'ll DISco .map(|parent| item_namespace(cx, DefId { krate: def_id.krate, index: parent })); let namespace_name_string = { - let mut output = String::new(); + let mut output = String::with_capacity(64); type_names::push_item_name(cx.tcx, def_id, false, &mut output); output }; diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs b/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs index 6bcd3e5bf58f3..c758010c58192 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs @@ -82,8 +82,8 @@ pub(crate) fn fat_pointer_kind<'ll, 'tcx>( ty::Foreign(_) => { // Assert that pointers to foreign types really are thin: debug_assert_eq!( - cx.size_of(cx.tcx.mk_imm_ptr(pointee_tail_ty)), - cx.size_of(cx.tcx.mk_imm_ptr(cx.tcx.types.u8)) + cx.size_of(Ty::new_imm_ptr(cx.tcx, pointee_tail_ty)), + cx.size_of(Ty::new_imm_ptr(cx.tcx, cx.tcx.types.u8)) ); None } @@ -91,8 +91,7 @@ pub(crate) fn fat_pointer_kind<'ll, 'tcx>( // For all other pointee types we should already have returned None // at the beginning of the function. panic!( - "fat_pointer_kind() - Encountered unexpected `pointee_tail_ty`: {:?}", - pointee_tail_ty + "fat_pointer_kind() - Encountered unexpected `pointee_tail_ty`: {pointee_tail_ty:?}" ) } } diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs index 6a9173ab450a3..fced6d504d26e 100644 --- a/compiler/rustc_codegen_llvm/src/errors.rs +++ b/compiler/rustc_codegen_llvm/src/errors.rs @@ -50,9 +50,15 @@ pub(crate) struct SymbolAlreadyDefined<'a> { } #[derive(Diagnostic)] -#[diag(codegen_llvm_invalid_minimum_alignment)] -pub(crate) struct InvalidMinimumAlignment { - pub err: String, +#[diag(codegen_llvm_invalid_minimum_alignment_not_power_of_two)] +pub(crate) struct InvalidMinimumAlignmentNotPowerOfTwo { + pub align: u64, +} + +#[derive(Diagnostic)] +#[diag(codegen_llvm_invalid_minimum_alignment_too_large)] +pub(crate) struct InvalidMinimumAlignmentTooLarge { + pub align: u64, } #[derive(Diagnostic)] @@ -75,6 +81,8 @@ pub(crate) struct ErrorCallingDllTool<'a> { #[derive(Diagnostic)] #[diag(codegen_llvm_dlltool_fail_import_library)] pub(crate) struct DlltoolFailImportLibrary<'a> { + pub dlltool_path: Cow<'a, str>, + pub dlltool_args: String, pub stdout: Cow<'a, str>, pub stderr: Cow<'a, str>, } diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 4e28034a8507b..a9b06030e70a6 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -7,7 +7,7 @@ use crate::type_of::LayoutLlvmExt; use crate::va_arg::emit_va_arg; use crate::value::Value; -use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh}; +use rustc_codegen_ssa::base::{compare_simd_types, wants_msvc_seh, wants_wasm_eh}; use rustc_codegen_ssa::common::{IntPredicate, TypeKind}; use rustc_codegen_ssa::errors::{ExpectedPointerMutability, InvalidMonomorphization}; use rustc_codegen_ssa::mir::operand::OperandRef; @@ -90,7 +90,7 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { let tcx = self.tcx; let callee_ty = instance.ty(tcx, ty::ParamEnv::reveal_all()); - let ty::FnDef(def_id, substs) = *callee_ty.kind() else { + let ty::FnDef(def_id, fn_args) = *callee_ty.kind() else { bug!("expected fn item type, found {}", callee_ty); }; @@ -163,11 +163,10 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { } sym::volatile_load | sym::unaligned_volatile_load => { - let tp_ty = substs.type_at(0); + let tp_ty = fn_args.type_at(0); let ptr = args[0].immediate(); let load = if let PassMode::Cast(ty, _) = &fn_abi.ret.mode { let llty = ty.llvm_type(self); - let ptr = self.pointercast(ptr, self.type_ptr_to(llty)); self.volatile_load(llty, ptr) } else { self.volatile_load(self.layout_of(tp_ty).llvm_type(self), ptr) @@ -230,22 +229,22 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::ctlz | sym::cttz => { let y = self.const_bool(false); self.call_intrinsic( - &format!("llvm.{}.i{}", name, width), + &format!("llvm.{name}.i{width}"), &[args[0].immediate(), y], ) } sym::ctlz_nonzero => { let y = self.const_bool(true); - let llvm_name = &format!("llvm.ctlz.i{}", width); + let llvm_name = &format!("llvm.ctlz.i{width}"); self.call_intrinsic(llvm_name, &[args[0].immediate(), y]) } sym::cttz_nonzero => { let y = self.const_bool(true); - let llvm_name = &format!("llvm.cttz.i{}", width); + let llvm_name = &format!("llvm.cttz.i{width}"); self.call_intrinsic(llvm_name, &[args[0].immediate(), y]) } sym::ctpop => self.call_intrinsic( - &format!("llvm.ctpop.i{}", width), + &format!("llvm.ctpop.i{width}"), &[args[0].immediate()], ), sym::bswap => { @@ -253,13 +252,13 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { args[0].immediate() // byte swap a u8/i8 is just a no-op } else { self.call_intrinsic( - &format!("llvm.bswap.i{}", width), + &format!("llvm.bswap.i{width}"), &[args[0].immediate()], ) } } sym::bitreverse => self.call_intrinsic( - &format!("llvm.bitreverse.i{}", width), + &format!("llvm.bitreverse.i{width}"), &[args[0].immediate()], ), sym::rotate_left | sym::rotate_right => { @@ -298,7 +297,7 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { sym::raw_eq => { use abi::Abi::*; - let tp_ty = substs.type_at(0); + let tp_ty = fn_args.type_at(0); let layout = self.layout_of(tp_ty).layout; let use_integer_compare = match layout.abi() { Scalar(_) | ScalarPair(_, _) => true, @@ -317,18 +316,12 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { self.const_bool(true) } else if use_integer_compare { let integer_ty = self.type_ix(layout.size().bits()); - let ptr_ty = self.type_ptr_to(integer_ty); - let a_ptr = self.bitcast(a, ptr_ty); - let a_val = self.load(integer_ty, a_ptr, layout.align().abi); - let b_ptr = self.bitcast(b, ptr_ty); - let b_val = self.load(integer_ty, b_ptr, layout.align().abi); + let a_val = self.load(integer_ty, a, layout.align().abi); + let b_val = self.load(integer_ty, b, layout.align().abi); self.icmp(IntPredicate::IntEQ, a_val, b_val) } else { - let i8p_ty = self.type_i8p(); - let a_ptr = self.bitcast(a, i8p_ty); - let b_ptr = self.bitcast(b, i8p_ty); let n = self.const_usize(layout.size().bytes()); - let cmp = self.call_intrinsic("memcmp", &[a_ptr, b_ptr, n]); + let cmp = self.call_intrinsic("memcmp", &[a, b, n]); match self.cx.sess().target.arch.as_ref() { "avr" | "msp430" => self.icmp(IntPredicate::IntEQ, cmp, self.const_i16(0)), _ => self.icmp(IntPredicate::IntEQ, cmp, self.const_i32(0)), @@ -336,6 +329,16 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { } } + sym::compare_bytes => { + // Here we assume that the `memcmp` provided by the target is a NOP for size 0. + let cmp = self.call_intrinsic( + "memcmp", + &[args[0].immediate(), args[1].immediate(), args[2].immediate()], + ); + // Some targets have `memcmp` returning `i16`, but the intrinsic is always `i32`. + self.sext(cmp, self.type_ix(32)) + } + sym::black_box => { args[0].val.store(self, result); let result_val_span = [result.llval]; @@ -383,10 +386,8 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { }; if !fn_abi.ret.is_ignore() { - if let PassMode::Cast(ty, _) = &fn_abi.ret.mode { - let ptr_llty = self.type_ptr_to(ty.llvm_type(self)); - let ptr = self.pointercast(result.llval, ptr_llty); - self.store(llval, ptr, result.align); + if let PassMode::Cast(_, _) = &fn_abi.ret.mode { + self.store(llval, result.llval, result.align); } else { OperandRef::from_immediate_or_packed_pair(self, llval, result.layout) .val @@ -410,9 +411,7 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { fn type_test(&mut self, pointer: Self::Value, typeid: Self::Value) -> Self::Value { // Test the called operand using llvm.type.test intrinsic. The LowerTypeTests link-time // optimization pass replaces calls to this intrinsic with code to test type membership. - let i8p_ty = self.type_i8p(); - let bitcast = self.bitcast(pointer, i8p_ty); - self.call_intrinsic("llvm.type.test", &[bitcast, typeid]) + self.call_intrinsic("llvm.type.test", &[pointer, typeid]) } fn type_checked_load( @@ -444,7 +443,7 @@ fn try_intrinsic<'ll>( dest: &'ll Value, ) { if bx.sess().panic_strategy() == PanicStrategy::Abort { - let try_func_ty = bx.type_func(&[bx.type_i8p()], bx.type_void()); + let try_func_ty = bx.type_func(&[bx.type_ptr()], bx.type_void()); bx.call(try_func_ty, None, None, try_func, &[data], None); // Return 0 unconditionally from the intrinsic call; // we can never unwind. @@ -452,6 +451,8 @@ fn try_intrinsic<'ll>( bx.store(bx.const_i32(0), dest, ret_align); } else if wants_msvc_seh(bx.sess()) { codegen_msvc_try(bx, try_func, data, catch_func, dest); + } else if wants_wasm_eh(bx.sess()) { + codegen_wasm_try(bx, try_func, data, catch_func, dest); } else if bx.sess().target.os == "emscripten" { codegen_emcc_try(bx, try_func, data, catch_func, dest); } else { @@ -542,8 +543,8 @@ fn codegen_msvc_try<'ll>( // // More information can be found in libstd's seh.rs implementation. let ptr_align = bx.tcx().data_layout.pointer_align.abi; - let slot = bx.alloca(bx.type_i8p(), ptr_align); - let try_func_ty = bx.type_func(&[bx.type_i8p()], bx.type_void()); + let slot = bx.alloca(bx.type_ptr(), ptr_align); + let try_func_ty = bx.type_func(&[bx.type_ptr()], bx.type_void()); bx.invoke(try_func_ty, None, None, try_func, &[data], normal, catchswitch, None); bx.switch_to_block(normal); @@ -566,10 +567,10 @@ fn codegen_msvc_try<'ll>( // // When modifying, make sure that the type_name string exactly matches // the one used in library/panic_unwind/src/seh.rs. - let type_info_vtable = bx.declare_global("??_7type_info@@6B@", bx.type_i8p()); + let type_info_vtable = bx.declare_global("??_7type_info@@6B@", bx.type_ptr()); let type_name = bx.const_bytes(b"rust_panic\0"); let type_info = - bx.const_struct(&[type_info_vtable, bx.const_null(bx.type_i8p()), type_name], false); + bx.const_struct(&[type_info_vtable, bx.const_null(bx.type_ptr()), type_name], false); let tydesc = bx.declare_global("__rust_panic_type_info", bx.val_ty(type_info)); unsafe { llvm::LLVMRustSetLinkage(tydesc, llvm::Linkage::LinkOnceODRLinkage); @@ -586,15 +587,15 @@ fn codegen_msvc_try<'ll>( bx.switch_to_block(catchpad_rust); let flags = bx.const_i32(8); let funclet = bx.catch_pad(cs, &[tydesc, flags, slot]); - let ptr = bx.load(bx.type_i8p(), slot, ptr_align); - let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void()); + let ptr = bx.load(bx.type_ptr(), slot, ptr_align); + let catch_ty = bx.type_func(&[bx.type_ptr(), bx.type_ptr()], bx.type_void()); bx.call(catch_ty, None, None, catch_func, &[data, ptr], Some(&funclet)); bx.catch_ret(&funclet, caught); // The flag value of 64 indicates a "catch-all". bx.switch_to_block(catchpad_foreign); let flags = bx.const_i32(64); - let null = bx.const_null(bx.type_i8p()); + let null = bx.const_null(bx.type_ptr()); let funclet = bx.catch_pad(cs, &[null, flags, null]); bx.call(catch_ty, None, None, catch_func, &[data, null], Some(&funclet)); bx.catch_ret(&funclet, caught); @@ -610,6 +611,80 @@ fn codegen_msvc_try<'ll>( bx.store(ret, dest, i32_align); } +// WASM's definition of the `rust_try` function. +fn codegen_wasm_try<'ll>( + bx: &mut Builder<'_, 'll, '_>, + try_func: &'ll Value, + data: &'ll Value, + catch_func: &'ll Value, + dest: &'ll Value, +) { + let (llty, llfn) = get_rust_try_fn(bx, &mut |mut bx| { + bx.set_personality_fn(bx.eh_personality()); + + let normal = bx.append_sibling_block("normal"); + let catchswitch = bx.append_sibling_block("catchswitch"); + let catchpad = bx.append_sibling_block("catchpad"); + let caught = bx.append_sibling_block("caught"); + + let try_func = llvm::get_param(bx.llfn(), 0); + let data = llvm::get_param(bx.llfn(), 1); + let catch_func = llvm::get_param(bx.llfn(), 2); + + // We're generating an IR snippet that looks like: + // + // declare i32 @rust_try(%try_func, %data, %catch_func) { + // %slot = alloca i8* + // invoke %try_func(%data) to label %normal unwind label %catchswitch + // + // normal: + // ret i32 0 + // + // catchswitch: + // %cs = catchswitch within none [%catchpad] unwind to caller + // + // catchpad: + // %tok = catchpad within %cs [null] + // %ptr = call @llvm.wasm.get.exception(token %tok) + // %sel = call @llvm.wasm.get.ehselector(token %tok) + // call %catch_func(%data, %ptr) + // catchret from %tok to label %caught + // + // caught: + // ret i32 1 + // } + // + let try_func_ty = bx.type_func(&[bx.type_ptr()], bx.type_void()); + bx.invoke(try_func_ty, None, None, try_func, &[data], normal, catchswitch, None); + + bx.switch_to_block(normal); + bx.ret(bx.const_i32(0)); + + bx.switch_to_block(catchswitch); + let cs = bx.catch_switch(None, None, &[catchpad]); + + bx.switch_to_block(catchpad); + let null = bx.const_null(bx.type_ptr()); + let funclet = bx.catch_pad(cs, &[null]); + + let ptr = bx.call_intrinsic("llvm.wasm.get.exception", &[funclet.cleanuppad()]); + let _sel = bx.call_intrinsic("llvm.wasm.get.ehselector", &[funclet.cleanuppad()]); + + let catch_ty = bx.type_func(&[bx.type_ptr(), bx.type_ptr()], bx.type_void()); + bx.call(catch_ty, None, None, catch_func, &[data, ptr], Some(&funclet)); + bx.catch_ret(&funclet, caught); + + bx.switch_to_block(caught); + bx.ret(bx.const_i32(1)); + }); + + // Note that no invoke is used here because by definition this function + // can't panic (that's what it's catching). + let ret = bx.call(llty, None, None, llfn, &[try_func, data, catch_func], None); + let i32_align = bx.tcx().data_layout.i32_align.abi; + bx.store(ret, dest, i32_align); +} + // Definition of the standard `try` function for Rust using the GNU-like model // of exceptions (e.g., the normal semantics of LLVM's `landingpad` and `invoke` // instructions). @@ -647,7 +722,7 @@ fn codegen_gnu_try<'ll>( let try_func = llvm::get_param(bx.llfn(), 0); let data = llvm::get_param(bx.llfn(), 1); let catch_func = llvm::get_param(bx.llfn(), 2); - let try_func_ty = bx.type_func(&[bx.type_i8p()], bx.type_void()); + let try_func_ty = bx.type_func(&[bx.type_ptr()], bx.type_void()); bx.invoke(try_func_ty, None, None, try_func, &[data], then, catch, None); bx.switch_to_block(then); @@ -660,12 +735,12 @@ fn codegen_gnu_try<'ll>( // the landing pad clauses the exception's type had been matched to. // rust_try ignores the selector. bx.switch_to_block(catch); - let lpad_ty = bx.type_struct(&[bx.type_i8p(), bx.type_i32()], false); + let lpad_ty = bx.type_struct(&[bx.type_ptr(), bx.type_i32()], false); let vals = bx.landing_pad(lpad_ty, bx.eh_personality(), 1); - let tydesc = bx.const_null(bx.type_i8p()); + let tydesc = bx.const_null(bx.type_ptr()); bx.add_clause(vals, tydesc); let ptr = bx.extract_value(vals, 0); - let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void()); + let catch_ty = bx.type_func(&[bx.type_ptr(), bx.type_ptr()], bx.type_void()); bx.call(catch_ty, None, None, catch_func, &[data, ptr], None); bx.ret(bx.const_i32(1)); }); @@ -711,7 +786,7 @@ fn codegen_emcc_try<'ll>( let try_func = llvm::get_param(bx.llfn(), 0); let data = llvm::get_param(bx.llfn(), 1); let catch_func = llvm::get_param(bx.llfn(), 2); - let try_func_ty = bx.type_func(&[bx.type_i8p()], bx.type_void()); + let try_func_ty = bx.type_func(&[bx.type_ptr()], bx.type_void()); bx.invoke(try_func_ty, None, None, try_func, &[data], then, catch, None); bx.switch_to_block(then); @@ -724,10 +799,10 @@ fn codegen_emcc_try<'ll>( // the landing pad clauses the exception's type had been matched to. bx.switch_to_block(catch); let tydesc = bx.eh_catch_typeinfo(); - let lpad_ty = bx.type_struct(&[bx.type_i8p(), bx.type_i32()], false); + let lpad_ty = bx.type_struct(&[bx.type_ptr(), bx.type_i32()], false); let vals = bx.landing_pad(lpad_ty, bx.eh_personality(), 2); bx.add_clause(vals, tydesc); - bx.add_clause(vals, bx.const_null(bx.type_i8p())); + bx.add_clause(vals, bx.const_null(bx.type_ptr())); let ptr = bx.extract_value(vals, 0); let selector = bx.extract_value(vals, 1); @@ -740,7 +815,7 @@ fn codegen_emcc_try<'ll>( // create an alloca and pass a pointer to that. let ptr_align = bx.tcx().data_layout.pointer_align.abi; let i8_align = bx.tcx().data_layout.i8_align.abi; - let catch_data_type = bx.type_struct(&[bx.type_i8p(), bx.type_bool()], false); + let catch_data_type = bx.type_struct(&[bx.type_ptr(), bx.type_bool()], false); let catch_data = bx.alloca(catch_data_type, ptr_align); let catch_data_0 = bx.inbounds_gep(catch_data_type, catch_data, &[bx.const_usize(0), bx.const_usize(0)]); @@ -748,9 +823,8 @@ fn codegen_emcc_try<'ll>( let catch_data_1 = bx.inbounds_gep(catch_data_type, catch_data, &[bx.const_usize(0), bx.const_usize(1)]); bx.store(is_rust_panic, catch_data_1, i8_align); - let catch_data = bx.bitcast(catch_data, bx.type_i8p()); - let catch_ty = bx.type_func(&[bx.type_i8p(), bx.type_i8p()], bx.type_void()); + let catch_ty = bx.type_func(&[bx.type_ptr(), bx.type_ptr()], bx.type_void()); bx.call(catch_ty, None, None, catch_func, &[data, catch_data], None); bx.ret(bx.const_i32(1)); }); @@ -797,23 +871,29 @@ fn get_rust_try_fn<'ll, 'tcx>( // Define the type up front for the signature of the rust_try function. let tcx = cx.tcx; - let i8p = tcx.mk_mut_ptr(tcx.types.i8); + let i8p = Ty::new_mut_ptr(tcx, tcx.types.i8); // `unsafe fn(*mut i8) -> ()` - let try_fn_ty = tcx.mk_fn_ptr(ty::Binder::dummy(tcx.mk_fn_sig( - [i8p], - tcx.mk_unit(), - false, - hir::Unsafety::Unsafe, - Abi::Rust, - ))); + let try_fn_ty = Ty::new_fn_ptr( + tcx, + ty::Binder::dummy(tcx.mk_fn_sig( + [i8p], + Ty::new_unit(tcx), + false, + hir::Unsafety::Unsafe, + Abi::Rust, + )), + ); // `unsafe fn(*mut i8, *mut i8) -> ()` - let catch_fn_ty = tcx.mk_fn_ptr(ty::Binder::dummy(tcx.mk_fn_sig( - [i8p, i8p], - tcx.mk_unit(), - false, - hir::Unsafety::Unsafe, - Abi::Rust, - ))); + let catch_fn_ty = Ty::new_fn_ptr( + tcx, + ty::Binder::dummy(tcx.mk_fn_sig( + [i8p, i8p], + Ty::new_unit(tcx), + false, + hir::Unsafety::Unsafe, + Abi::Rust, + )), + ); // `unsafe fn(unsafe fn(*mut i8) -> (), *mut i8, unsafe fn(*mut i8, *mut i8) -> ()) -> i32` let rust_fn_sig = ty::Binder::dummy(cx.tcx.mk_fn_sig( [try_fn_ty, i8p, catch_fn_ty], @@ -885,8 +965,7 @@ fn generic_simd_intrinsic<'ll, 'tcx>( let place = PlaceRef::alloca(bx, args[0].layout); args[0].val.store(bx, place); let int_ty = bx.type_ix(expected_bytes * 8); - let ptr = bx.pointercast(place.llval, bx.cx.type_ptr_to(int_ty)); - bx.load(int_ty, ptr, Align::ONE) + bx.load(int_ty, place.llval, Align::ONE) } _ => return_error!(InvalidMonomorphization::InvalidBitmask { span, @@ -951,28 +1030,20 @@ fn generic_simd_intrinsic<'ll, 'tcx>( )); } - if let Some(stripped) = name.as_str().strip_prefix("simd_shuffle") { - // If this intrinsic is the older "simd_shuffleN" form, simply parse the integer. - // If there is no suffix, use the index array length. - let n: u64 = if stripped.is_empty() { - // Make sure this is actually an array, since typeck only checks the length-suffixed - // version of this intrinsic. - match args[2].layout.ty.kind() { - ty::Array(ty, len) if matches!(ty.kind(), ty::Uint(ty::UintTy::U32)) => { - len.try_eval_target_usize(bx.cx.tcx, ty::ParamEnv::reveal_all()).unwrap_or_else( - || span_bug!(span, "could not evaluate shuffle index array length"), - ) - } - _ => return_error!(InvalidMonomorphization::SimdShuffle { - span, - name, - ty: args[2].layout.ty - }), + if name == sym::simd_shuffle { + // Make sure this is actually an array, since typeck only checks the length-suffixed + // version of this intrinsic. + let n: u64 = match args[2].layout.ty.kind() { + ty::Array(ty, len) if matches!(ty.kind(), ty::Uint(ty::UintTy::U32)) => { + len.try_eval_target_usize(bx.cx.tcx, ty::ParamEnv::reveal_all()).unwrap_or_else( + || span_bug!(span, "could not evaluate shuffle index array length"), + ) } - } else { - stripped.parse().unwrap_or_else(|_| { - span_bug!(span, "bad `simd_shuffle` instruction only caught in codegen?") - }) + _ => return_error!(InvalidMonomorphization::SimdShuffle { + span, + name, + ty: args[2].layout.ty + }), }; require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); @@ -1135,7 +1206,6 @@ fn generic_simd_intrinsic<'ll, 'tcx>( let ptr = bx.alloca(bx.type_ix(expected_bytes * 8), Align::ONE); bx.store(ze, ptr, Align::ONE); let array_ty = bx.type_array(bx.type_i8(), expected_bytes); - let ptr = bx.pointercast(ptr, bx.cx.type_ptr_to(array_ty)); return Ok(bx.load(array_ty, ptr, Align::ONE)); } _ => return_error!(InvalidMonomorphization::CannotReturn { @@ -1201,7 +1271,7 @@ fn generic_simd_intrinsic<'ll, 'tcx>( sym::simd_trunc => ("trunc", bx.type_func(&[vec_ty], vec_ty)), _ => return_error!(InvalidMonomorphization::UnrecognizedIntrinsic { span, name }), }; - let llvm_name = &format!("llvm.{0}.v{1}{2}", intr_name, in_len, elem_ty_str); + let llvm_name = &format!("llvm.{intr_name}.v{in_len}{elem_ty_str}"); let f = bx.declare_cfn(llvm_name, llvm::UnnamedAddr::No, fn_ty); let c = bx.call( fn_ty, @@ -1239,50 +1309,34 @@ fn generic_simd_intrinsic<'ll, 'tcx>( // FIXME: use: // https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/Function.h#L182 // https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/Intrinsics.h#L81 - fn llvm_vector_str( - elem_ty: Ty<'_>, - vec_len: u64, - no_pointers: usize, - bx: &Builder<'_, '_, '_>, - ) -> String { - let p0s: String = "p0".repeat(no_pointers); + fn llvm_vector_str(bx: &Builder<'_, '_, '_>, elem_ty: Ty<'_>, vec_len: u64) -> String { match *elem_ty.kind() { ty::Int(v) => format!( - "v{}{}i{}", + "v{}i{}", vec_len, - p0s, // Normalize to prevent crash if v: IntTy::Isize v.normalize(bx.target_spec().pointer_width).bit_width().unwrap() ), ty::Uint(v) => format!( - "v{}{}i{}", + "v{}i{}", vec_len, - p0s, // Normalize to prevent crash if v: UIntTy::Usize v.normalize(bx.target_spec().pointer_width).bit_width().unwrap() ), - ty::Float(v) => format!("v{}{}f{}", vec_len, p0s, v.bit_width()), + ty::Float(v) => format!("v{}f{}", vec_len, v.bit_width()), + ty::RawPtr(_) => format!("v{}p0", vec_len), _ => unreachable!(), } } - fn llvm_vector_ty<'ll>( - cx: &CodegenCx<'ll, '_>, - elem_ty: Ty<'_>, - vec_len: u64, - mut no_pointers: usize, - ) -> &'ll Type { - // FIXME: use cx.layout_of(ty).llvm_type() ? - let mut elem_ty = match *elem_ty.kind() { + fn llvm_vector_ty<'ll>(cx: &CodegenCx<'ll, '_>, elem_ty: Ty<'_>, vec_len: u64) -> &'ll Type { + let elem_ty = match *elem_ty.kind() { ty::Int(v) => cx.type_int_from_ty(v), ty::Uint(v) => cx.type_uint_from_ty(v), ty::Float(v) => cx.type_float_from_ty(v), + ty::RawPtr(_) => cx.type_ptr(), _ => unreachable!(), }; - while no_pointers > 0 { - elem_ty = cx.type_ptr_to(elem_ty); - no_pointers -= 1; - } cx.type_vector(elem_ty, vec_len) } @@ -1337,47 +1391,26 @@ fn generic_simd_intrinsic<'ll, 'tcx>( InvalidMonomorphization::ExpectedReturnType { span, name, in_ty, ret_ty } ); - // This counts how many pointers - fn ptr_count(t: Ty<'_>) -> usize { - match t.kind() { - ty::RawPtr(p) => 1 + ptr_count(p.ty), - _ => 0, - } - } - - // Non-ptr type - fn non_ptr(t: Ty<'_>) -> Ty<'_> { - match t.kind() { - ty::RawPtr(p) => non_ptr(p.ty), - _ => t, - } - } - // The second argument must be a simd vector with an element type that's a pointer // to the element type of the first argument let (_, element_ty0) = arg_tys[0].simd_size_and_type(bx.tcx()); let (_, element_ty1) = arg_tys[1].simd_size_and_type(bx.tcx()); - let (pointer_count, underlying_ty) = match element_ty1.kind() { - ty::RawPtr(p) if p.ty == in_elem => (ptr_count(element_ty1), non_ptr(element_ty1)), - _ => { - require!( - false, - InvalidMonomorphization::ExpectedElementType { - span, - name, - expected_element: element_ty1, - second_arg: arg_tys[1], - in_elem, - in_ty, - mutability: ExpectedPointerMutability::Not, - } - ); - unreachable!(); + + require!( + matches!( + element_ty1.kind(), + ty::RawPtr(p) if p.ty == in_elem && p.ty.kind() == element_ty0.kind() + ), + InvalidMonomorphization::ExpectedElementType { + span, + name, + expected_element: element_ty1, + second_arg: arg_tys[1], + in_elem, + in_ty, + mutability: ExpectedPointerMutability::Not, } - }; - assert!(pointer_count > 0); - assert_eq!(pointer_count - 1, ptr_count(element_ty0)); - assert_eq!(underlying_ty, non_ptr(element_ty0)); + ); // The element type of the third argument must be a signed integer type of any width: let (_, element_ty2) = arg_tys[2].simd_size_and_type(bx.tcx()); @@ -1408,15 +1441,15 @@ fn generic_simd_intrinsic<'ll, 'tcx>( }; // Type of the vector of pointers: - let llvm_pointer_vec_ty = llvm_vector_ty(bx, underlying_ty, in_len, pointer_count); - let llvm_pointer_vec_str = llvm_vector_str(underlying_ty, in_len, pointer_count, bx); + let llvm_pointer_vec_ty = llvm_vector_ty(bx, element_ty1, in_len); + let llvm_pointer_vec_str = llvm_vector_str(bx, element_ty1, in_len); // Type of the vector of elements: - let llvm_elem_vec_ty = llvm_vector_ty(bx, underlying_ty, in_len, pointer_count - 1); - let llvm_elem_vec_str = llvm_vector_str(underlying_ty, in_len, pointer_count - 1, bx); + let llvm_elem_vec_ty = llvm_vector_ty(bx, element_ty0, in_len); + let llvm_elem_vec_str = llvm_vector_str(bx, element_ty0, in_len); let llvm_intrinsic = - format!("llvm.masked.gather.{}.{}", llvm_elem_vec_str, llvm_pointer_vec_str); + format!("llvm.masked.gather.{llvm_elem_vec_str}.{llvm_pointer_vec_str}"); let fn_ty = bx.type_func( &[llvm_pointer_vec_ty, alignment_ty, mask_ty, llvm_elem_vec_ty], llvm_elem_vec_ty, @@ -1477,50 +1510,28 @@ fn generic_simd_intrinsic<'ll, 'tcx>( } ); - // This counts how many pointers - fn ptr_count(t: Ty<'_>) -> usize { - match t.kind() { - ty::RawPtr(p) => 1 + ptr_count(p.ty), - _ => 0, - } - } - - // Non-ptr type - fn non_ptr(t: Ty<'_>) -> Ty<'_> { - match t.kind() { - ty::RawPtr(p) => non_ptr(p.ty), - _ => t, - } - } - // The second argument must be a simd vector with an element type that's a pointer // to the element type of the first argument let (_, element_ty0) = arg_tys[0].simd_size_and_type(bx.tcx()); let (_, element_ty1) = arg_tys[1].simd_size_and_type(bx.tcx()); let (_, element_ty2) = arg_tys[2].simd_size_and_type(bx.tcx()); - let (pointer_count, underlying_ty) = match element_ty1.kind() { - ty::RawPtr(p) if p.ty == in_elem && p.mutbl.is_mut() => { - (ptr_count(element_ty1), non_ptr(element_ty1)) - } - _ => { - require!( - false, - InvalidMonomorphization::ExpectedElementType { - span, - name, - expected_element: element_ty1, - second_arg: arg_tys[1], - in_elem, - in_ty, - mutability: ExpectedPointerMutability::Mut, - } - ); - unreachable!(); + + require!( + matches!( + element_ty1.kind(), + ty::RawPtr(p) + if p.ty == in_elem && p.mutbl.is_mut() && p.ty.kind() == element_ty0.kind() + ), + InvalidMonomorphization::ExpectedElementType { + span, + name, + expected_element: element_ty1, + second_arg: arg_tys[1], + in_elem, + in_ty, + mutability: ExpectedPointerMutability::Mut, } - }; - assert!(pointer_count > 0); - assert_eq!(pointer_count - 1, ptr_count(element_ty0)); - assert_eq!(underlying_ty, non_ptr(element_ty0)); + ); // The element type of the third argument must be a signed integer type of any width: match element_ty2.kind() { @@ -1552,15 +1563,15 @@ fn generic_simd_intrinsic<'ll, 'tcx>( let ret_t = bx.type_void(); // Type of the vector of pointers: - let llvm_pointer_vec_ty = llvm_vector_ty(bx, underlying_ty, in_len, pointer_count); - let llvm_pointer_vec_str = llvm_vector_str(underlying_ty, in_len, pointer_count, bx); + let llvm_pointer_vec_ty = llvm_vector_ty(bx, element_ty1, in_len); + let llvm_pointer_vec_str = llvm_vector_str(bx, element_ty1, in_len); // Type of the vector of elements: - let llvm_elem_vec_ty = llvm_vector_ty(bx, underlying_ty, in_len, pointer_count - 1); - let llvm_elem_vec_str = llvm_vector_str(underlying_ty, in_len, pointer_count - 1, bx); + let llvm_elem_vec_ty = llvm_vector_ty(bx, element_ty0, in_len); + let llvm_elem_vec_str = llvm_vector_str(bx, element_ty0, in_len); let llvm_intrinsic = - format!("llvm.masked.scatter.{}.{}", llvm_elem_vec_str, llvm_pointer_vec_str); + format!("llvm.masked.scatter.{llvm_elem_vec_str}.{llvm_pointer_vec_str}"); let fn_ty = bx.type_func(&[llvm_elem_vec_ty, llvm_pointer_vec_ty, alignment_ty, mask_ty], ret_t); let f = bx.declare_cfn(&llvm_intrinsic, llvm::UnnamedAddr::No, fn_ty); @@ -1775,11 +1786,7 @@ fn generic_simd_intrinsic<'ll, 'tcx>( } } - if in_elem == out_elem { - return Ok(args[0].immediate()); - } else { - return Ok(bx.pointercast(args[0].immediate(), llret_ty)); - } + return Ok(args[0].immediate()); } if name == sym::simd_expose_addr { @@ -1992,6 +1999,52 @@ fn generic_simd_intrinsic<'ll, 'tcx>( simd_neg: Int => neg, Float => fneg; } + // Unary integer intrinsics + if matches!(name, sym::simd_bswap | sym::simd_bitreverse | sym::simd_ctlz | sym::simd_cttz) { + let vec_ty = bx.cx.type_vector( + match *in_elem.kind() { + ty::Int(i) => bx.cx.type_int_from_ty(i), + ty::Uint(i) => bx.cx.type_uint_from_ty(i), + _ => return_error!(InvalidMonomorphization::UnsupportedOperation { + span, + name, + in_ty, + in_elem + }), + }, + in_len as u64, + ); + let intrinsic_name = match name { + sym::simd_bswap => "bswap", + sym::simd_bitreverse => "bitreverse", + sym::simd_ctlz => "ctlz", + sym::simd_cttz => "cttz", + _ => unreachable!(), + }; + let int_size = in_elem.int_size_and_signed(bx.tcx()).0.bits(); + let llvm_intrinsic = &format!("llvm.{}.v{}i{}", intrinsic_name, in_len, int_size,); + + return if name == sym::simd_bswap && int_size == 8 { + // byte swap is no-op for i8/u8 + Ok(args[0].immediate()) + } else if matches!(name, sym::simd_ctlz | sym::simd_cttz) { + let fn_ty = bx.type_func(&[vec_ty, bx.type_i1()], vec_ty); + let f = bx.declare_cfn(llvm_intrinsic, llvm::UnnamedAddr::No, fn_ty); + Ok(bx.call( + fn_ty, + None, + None, + f, + &[args[0].immediate(), bx.const_int(bx.type_i1(), 0)], + None, + )) + } else { + let fn_ty = bx.type_func(&[vec_ty], vec_ty); + let f = bx.declare_cfn(llvm_intrinsic, llvm::UnnamedAddr::No, fn_ty); + Ok(bx.call(fn_ty, None, None, f, &[args[0].immediate()], None)) + }; + } + if name == sym::simd_arith_offset { // This also checks that the first operand is a ptr type. let pointee = in_elem.builtin_deref(true).unwrap_or_else(|| { diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 805843e5863e6..d283299ac46b4 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -28,24 +28,25 @@ pub use llvm_util::target_features; use rustc_ast::expand::allocator::AllocatorKind; use rustc_codegen_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule}; use rustc_codegen_ssa::back::write::{ - CodegenContext, FatLTOInput, ModuleConfig, TargetMachineFactoryConfig, TargetMachineFactoryFn, + CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryConfig, TargetMachineFactoryFn, }; use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::ModuleCodegen; use rustc_codegen_ssa::{CodegenResults, CompiledModule}; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_errors::{DiagnosticMessage, ErrorGuaranteed, FatalError, Handler, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; -use rustc_session::config::{OptLevel, OutputFilenames, PrintRequest}; +use rustc_session::config::{OptLevel, OutputFilenames, PrintKind, PrintRequest}; use rustc_session::Session; use rustc_span::symbol::Symbol; use std::any::Any; use std::ffi::CStr; +use std::io::Write; mod back { pub mod archive; @@ -140,18 +141,6 @@ impl ExtraBackendMethods for LlvmCodegenBackend { back::write::target_machine_factory(sess, optlvl, target_features) } - fn spawn_thread(time_trace: bool, f: F) -> std::thread::JoinHandle - where - F: FnOnce() -> T, - F: Send + 'static, - T: Send + 'static, - { - std::thread::spawn(move || { - let _profiler = TimeTraceProfiler::new(time_trace); - f() - }) - } - fn spawn_named_thread( time_trace: bool, name: String, @@ -178,7 +167,28 @@ impl WriteBackendMethods for LlvmCodegenBackend { type ThinBuffer = back::lto::ThinBuffer; fn print_pass_timings(&self) { unsafe { - llvm::LLVMRustPrintPassTimings(); + let mut size = 0; + let cstr = llvm::LLVMRustPrintPassTimings(&mut size as *mut usize); + if cstr.is_null() { + println!("failed to get pass timings"); + } else { + let timings = std::slice::from_raw_parts(cstr as *const u8, size); + std::io::stdout().write_all(timings).unwrap(); + libc::free(cstr as *mut _); + } + } + } + fn print_statistics(&self) { + unsafe { + let mut size = 0; + let cstr = llvm::LLVMRustPrintStatistics(&mut size as *mut usize); + if cstr.is_null() { + println!("failed to get pass stats"); + } else { + let stats = std::slice::from_raw_parts(cstr as *const u8, size); + std::io::stdout().write_all(stats).unwrap(); + libc::free(cstr as *mut _); + } } } fn run_link( @@ -190,7 +200,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { } fn run_fat_lto( cgcx: &CodegenContext, - modules: Vec>, + modules: Vec>, cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> Result, FatalError> { back::lto::run_fat(cgcx, modules, cached_modules) @@ -262,10 +272,10 @@ impl CodegenBackend for LlvmCodegenBackend { |tcx, ()| llvm_util::global_llvm_features(tcx.sess, true) } - fn print(&self, req: PrintRequest, sess: &Session) { - match req { - PrintRequest::RelocationModels => { - println!("Available relocation models:"); + fn print(&self, req: &PrintRequest, out: &mut dyn PrintBackendInfo, sess: &Session) { + match req.kind { + PrintKind::RelocationModels => { + writeln!(out, "Available relocation models:"); for name in &[ "static", "pic", @@ -276,26 +286,27 @@ impl CodegenBackend for LlvmCodegenBackend { "ropi-rwpi", "default", ] { - println!(" {}", name); + writeln!(out, " {name}"); } - println!(); + writeln!(out); } - PrintRequest::CodeModels => { - println!("Available code models:"); + PrintKind::CodeModels => { + writeln!(out, "Available code models:"); for name in &["tiny", "small", "kernel", "medium", "large"] { - println!(" {}", name); + writeln!(out, " {name}"); } - println!(); + writeln!(out); } - PrintRequest::TlsModels => { - println!("Available TLS models:"); + PrintKind::TlsModels => { + writeln!(out, "Available TLS models:"); for name in &["global-dynamic", "local-dynamic", "initial-exec", "local-exec"] { - println!(" {}", name); + writeln!(out, " {name}"); } - println!(); + writeln!(out); } - PrintRequest::StackProtectorStrategies => { - println!( + PrintKind::StackProtectorStrategies => { + writeln!( + out, r#"Available stack protector strategies: all Generate stack canaries in all functions. @@ -319,7 +330,7 @@ impl CodegenBackend for LlvmCodegenBackend { "# ); } - req => llvm_util::print(req, sess), + _other => llvm_util::print(req, out, sess), } } @@ -355,7 +366,7 @@ impl CodegenBackend for LlvmCodegenBackend { ongoing_codegen: Box, sess: &Session, outputs: &OutputFilenames, - ) -> Result<(CodegenResults, FxHashMap), ErrorGuaranteed> { + ) -> Result<(CodegenResults, FxIndexMap), ErrorGuaranteed> { let (codegen_results, work_products) = ongoing_codegen .downcast::>() .expect("Expected LlvmCodegenBackend's OngoingCodegen, found Box") diff --git a/compiler/rustc_codegen_llvm/src/llvm/diagnostic.rs b/compiler/rustc_codegen_llvm/src/llvm/diagnostic.rs index 45de284d22a67..06e846a2b45eb 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/diagnostic.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/diagnostic.rs @@ -9,7 +9,7 @@ use libc::c_uint; use super::{DiagnosticInfo, SMDiagnostic}; use rustc_span::InnerSpan; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum OptimizationDiagnosticKind { OptimizationRemark, OptimizationMissed, diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index de93a64c0d6f5..84157d1e25ca3 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1,8 +1,6 @@ #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] -use rustc_codegen_ssa::coverageinfo::map as coverage_map; - use super::debuginfo::{ DIArray, DIBasicType, DIBuilder, DICompositeType, DIDerivedType, DIDescriptor, DIEnumerator, DIFile, DIFlags, DIGlobalVariableExpression, DILexicalBlock, DILocation, DINameSpace, @@ -196,6 +194,7 @@ pub enum AttributeKind { AllocSize = 37, AllocatedPointer = 38, AllocAlign = 39, + SanitizeSafeStack = 40, } /// LLVMIntPredicate @@ -476,6 +475,8 @@ pub enum OptStage { pub struct SanitizerOptions { pub sanitize_address: bool, pub sanitize_address_recover: bool, + pub sanitize_cfi: bool, + pub sanitize_kcfi: bool, pub sanitize_memory: bool, pub sanitize_memory_recover: bool, pub sanitize_memory_track_origins: c_int, @@ -584,6 +585,16 @@ pub enum ThreadLocalMode { LocalExec, } +/// LLVMRustTailCallKind +#[derive(Copy, Clone)] +#[repr(C)] +pub enum TailCallKind { + None, + Tail, + MustTail, + NoTail, +} + /// LLVMRustChecksumKind #[derive(Copy, Clone)] #[repr(C)] @@ -677,204 +688,6 @@ extern "C" { pub type DiagnosticHandlerTy = unsafe extern "C" fn(&DiagnosticInfo, *mut c_void); pub type InlineAsmDiagHandlerTy = unsafe extern "C" fn(&SMDiagnostic, *const c_void, c_uint); -pub mod coverageinfo { - use super::coverage_map; - - /// Corresponds to enum `llvm::coverage::CounterMappingRegion::RegionKind`. - /// - /// Must match the layout of `LLVMRustCounterMappingRegionKind`. - #[derive(Copy, Clone, Debug)] - #[repr(C)] - pub enum RegionKind { - /// A CodeRegion associates some code with a counter - CodeRegion = 0, - - /// An ExpansionRegion represents a file expansion region that associates - /// a source range with the expansion of a virtual source file, such as - /// for a macro instantiation or #include file. - ExpansionRegion = 1, - - /// A SkippedRegion represents a source range with code that was skipped - /// by a preprocessor or similar means. - SkippedRegion = 2, - - /// A GapRegion is like a CodeRegion, but its count is only set as the - /// line execution count when its the only region in the line. - GapRegion = 3, - - /// A BranchRegion represents leaf-level boolean expressions and is - /// associated with two counters, each representing the number of times the - /// expression evaluates to true or false. - BranchRegion = 4, - } - - /// This struct provides LLVM's representation of a "CoverageMappingRegion", encoded into the - /// coverage map, in accordance with the - /// [LLVM Code Coverage Mapping Format](https://github.com/rust-lang/llvm-project/blob/rustc/13.0-2021-09-30/llvm/docs/CoverageMappingFormat.rst#llvm-code-coverage-mapping-format). - /// The struct composes fields representing the `Counter` type and value(s) (injected counter - /// ID, or expression type and operands), the source file (an indirect index into a "filenames - /// array", encoded separately), and source location (start and end positions of the represented - /// code region). - /// - /// Corresponds to struct `llvm::coverage::CounterMappingRegion`. - /// - /// Must match the layout of `LLVMRustCounterMappingRegion`. - #[derive(Copy, Clone, Debug)] - #[repr(C)] - pub struct CounterMappingRegion { - /// The counter type and type-dependent counter data, if any. - counter: coverage_map::Counter, - - /// If the `RegionKind` is a `BranchRegion`, this represents the counter - /// for the false branch of the region. - false_counter: coverage_map::Counter, - - /// An indirect reference to the source filename. In the LLVM Coverage Mapping Format, the - /// file_id is an index into a function-specific `virtual_file_mapping` array of indexes - /// that, in turn, are used to look up the filename for this region. - file_id: u32, - - /// If the `RegionKind` is an `ExpansionRegion`, the `expanded_file_id` can be used to find - /// the mapping regions created as a result of macro expansion, by checking if their file id - /// matches the expanded file id. - expanded_file_id: u32, - - /// 1-based starting line of the mapping region. - start_line: u32, - - /// 1-based starting column of the mapping region. - start_col: u32, - - /// 1-based ending line of the mapping region. - end_line: u32, - - /// 1-based ending column of the mapping region. If the high bit is set, the current - /// mapping region is a gap area. - end_col: u32, - - kind: RegionKind, - } - - impl CounterMappingRegion { - pub(crate) fn code_region( - counter: coverage_map::Counter, - file_id: u32, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - ) -> Self { - Self { - counter, - false_counter: coverage_map::Counter::zero(), - file_id, - expanded_file_id: 0, - start_line, - start_col, - end_line, - end_col, - kind: RegionKind::CodeRegion, - } - } - - // This function might be used in the future; the LLVM API is still evolving, as is coverage - // support. - #[allow(dead_code)] - pub(crate) fn branch_region( - counter: coverage_map::Counter, - false_counter: coverage_map::Counter, - file_id: u32, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - ) -> Self { - Self { - counter, - false_counter, - file_id, - expanded_file_id: 0, - start_line, - start_col, - end_line, - end_col, - kind: RegionKind::BranchRegion, - } - } - - // This function might be used in the future; the LLVM API is still evolving, as is coverage - // support. - #[allow(dead_code)] - pub(crate) fn expansion_region( - file_id: u32, - expanded_file_id: u32, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - ) -> Self { - Self { - counter: coverage_map::Counter::zero(), - false_counter: coverage_map::Counter::zero(), - file_id, - expanded_file_id, - start_line, - start_col, - end_line, - end_col, - kind: RegionKind::ExpansionRegion, - } - } - - // This function might be used in the future; the LLVM API is still evolving, as is coverage - // support. - #[allow(dead_code)] - pub(crate) fn skipped_region( - file_id: u32, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - ) -> Self { - Self { - counter: coverage_map::Counter::zero(), - false_counter: coverage_map::Counter::zero(), - file_id, - expanded_file_id: 0, - start_line, - start_col, - end_line, - end_col, - kind: RegionKind::SkippedRegion, - } - } - - // This function might be used in the future; the LLVM API is still evolving, as is coverage - // support. - #[allow(dead_code)] - pub(crate) fn gap_region( - counter: coverage_map::Counter, - file_id: u32, - start_line: u32, - start_col: u32, - end_line: u32, - end_col: u32, - ) -> Self { - Self { - counter, - false_counter: coverage_map::Counter::zero(), - file_id, - expanded_file_id: 0, - start_line, - start_col, - end_line, - end_col: (1_u32 << 31) | end_col, - kind: RegionKind::GapRegion, - } - } - } -} - pub mod debuginfo { use super::{InvariantOpaque, Metadata}; use bitflags::bitflags; @@ -1062,7 +875,7 @@ extern "C" { // Operations on array, pointer, and vector types (sequence types) pub fn LLVMRustArrayType(ElementType: &Type, ElementCount: u64) -> &Type; - pub fn LLVMPointerType(ElementType: &Type, AddressSpace: c_uint) -> &Type; + pub fn LLVMPointerTypeInContext(C: &Context, AddressSpace: c_uint) -> &Type; pub fn LLVMVectorType(ElementType: &Type, ElementCount: c_uint) -> &Type; pub fn LLVMGetElementType(Ty: &Type) -> &Type; @@ -1070,6 +883,7 @@ extern "C" { // Operations on other types pub fn LLVMVoidTypeInContext(C: &Context) -> &Type; + pub fn LLVMTokenTypeInContext(C: &Context) -> &Type; pub fn LLVMMetadataTypeInContext(C: &Context) -> &Type; // Operations on all values @@ -1082,6 +896,7 @@ extern "C" { pub fn LLVMRustGlobalAddMetadata<'a>(Val: &'a Value, KindID: c_uint, Metadata: &'a Metadata); pub fn LLVMValueAsMetadata(Node: &Value) -> &Metadata; pub fn LLVMIsAFunction(Val: &Value) -> Option<&Value>; + pub fn LLVMRustIsNonGVFunctionPointerTy(Val: &Value) -> bool; // Operations on constants of any type pub fn LLVMConstNull(Ty: &Type) -> &Value; @@ -1143,7 +958,7 @@ extern "C" { pub fn LLVMConstVector(ScalarConstantVals: *const &Value, Size: c_uint) -> &Value; // Constant expressions - pub fn LLVMRustConstInBoundsGEP2<'a>( + pub fn LLVMConstInBoundsGEP2<'a>( ty: &'a Type, ConstantVal: &'a Value, ConstantIndices: *const &'a Value, @@ -1194,6 +1009,7 @@ extern "C" { NameLen: size_t, ) -> Option<&Value>; pub fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool); + pub fn LLVMRustSetTailCallKind(CallInst: &Value, TKC: TailCallKind); // Operations on attributes pub fn LLVMRustCreateAttrNoValue(C: &Context, attr: AttributeKind) -> &Attribute; @@ -1300,7 +1116,7 @@ extern "C" { NumArgs: c_uint, Then: &'a BasicBlock, Catch: &'a BasicBlock, - OpBundles: *const Option<&OperandBundleDef<'a>>, + OpBundles: *const &OperandBundleDef<'a>, NumOpBundles: c_uint, Name: *const c_char, ) -> &'a Value; @@ -1672,7 +1488,7 @@ extern "C" { Fn: &'a Value, Args: *const &'a Value, NumArgs: c_uint, - OpBundles: *const Option<&OperandBundleDef<'a>>, + OpBundles: *const &OperandBundleDef<'a>, NumOpBundles: c_uint, ) -> &'a Value; pub fn LLVMRustBuildMemCpy<'a>( @@ -1855,7 +1671,10 @@ extern "C" { pub fn LLVMRustGetLastError() -> *const c_char; /// Print the pass timings since static dtors aren't picking them up. - pub fn LLVMRustPrintPassTimings(); + pub fn LLVMRustPrintPassTimings(size: *const size_t) -> *const c_char; + + /// Print the statistics since static dtors aren't picking them up. + pub fn LLVMRustPrintStatistics(size: *const size_t) -> *const c_char; pub fn LLVMStructCreateNamed(C: &Context, Name: *const c_char) -> &Type; @@ -1888,6 +1707,8 @@ extern "C" { pub fn LLVMRustCoverageWriteFilenamesSectionToBuffer( Filenames: *const *const c_char, FilenamesLen: size_t, + Lengths: *const size_t, + LengthsLen: size_t, BufferOut: &RustString, ); @@ -1895,15 +1716,18 @@ extern "C" { pub fn LLVMRustCoverageWriteMappingToBuffer( VirtualFileMappingIDs: *const c_uint, NumVirtualFileMappingIDs: c_uint, - Expressions: *const coverage_map::CounterExpression, + Expressions: *const crate::coverageinfo::ffi::CounterExpression, NumExpressions: c_uint, - MappingRegions: *const coverageinfo::CounterMappingRegion, + MappingRegions: *const crate::coverageinfo::ffi::CounterMappingRegion, NumMappingRegions: c_uint, BufferOut: &RustString, ); - pub fn LLVMRustCoverageCreatePGOFuncNameVar(F: &Value, FuncName: *const c_char) -> &Value; - pub fn LLVMRustCoverageHashCString(StrVal: *const c_char) -> u64; + pub fn LLVMRustCoverageCreatePGOFuncNameVar( + F: &Value, + FuncName: *const c_char, + FuncNameLen: size_t, + ) -> &Value; pub fn LLVMRustCoverageHashByteArray(Bytes: *const c_char, NumBytes: size_t) -> u64; #[allow(improper_ctypes)] @@ -2268,7 +2092,12 @@ extern "C" { pub fn LLVMRustHasFeature(T: &TargetMachine, s: *const c_char) -> bool; - pub fn LLVMRustPrintTargetCPUs(T: &TargetMachine, cpu: *const c_char); + pub fn LLVMRustPrintTargetCPUs( + T: &TargetMachine, + cpu: *const c_char, + print: unsafe extern "C" fn(out: *mut c_void, string: *const c_char, len: usize), + out: *mut c_void, + ); pub fn LLVMRustGetTargetFeaturesCount(T: &TargetMachine) -> size_t; pub fn LLVMRustGetTargetFeature( T: &TargetMachine, @@ -2318,6 +2147,7 @@ extern "C" { TM: &'a TargetMachine, OptLevel: PassBuilderOptLevel, OptStage: OptStage, + IsLinkerPluginLTO: bool, NoPrepopulatePasses: bool, VerifyIR: bool, UseThinLTOBuffers: bool, @@ -2511,6 +2341,8 @@ extern "C" { remark_all_passes: bool, remark_passes: *const *const c_char, remark_passes_len: usize, + remark_file: *const c_char, + pgo_available: bool, ); #[allow(improper_ctypes)] diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 03be0654b50bb..a76c9c9b7356c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -8,16 +8,17 @@ use libc::c_int; use rustc_codegen_ssa::target_features::{ supported_target_features, tied_target_features, RUSTC_SPECIFIC_FEATURES, }; +use rustc_codegen_ssa::traits::PrintBackendInfo; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::small_c_str::SmallCStr; use rustc_fs_util::path_to_c_string; use rustc_middle::bug; -use rustc_session::config::PrintRequest; +use rustc_session::config::{PrintKind, PrintRequest}; use rustc_session::Session; use rustc_span::symbol::Symbol; use rustc_target::spec::{MergeFunctions, PanicStrategy}; -use std::ffi::{CStr, CString}; +use std::ffi::{c_char, c_void, CStr, CString}; use std::path::Path; use std::ptr; use std::slice; @@ -110,6 +111,10 @@ unsafe fn configure_llvm(sess: &Session) { // Use non-zero `import-instr-limit` multiplier for cold callsites. add("-import-cold-multiplier=0.1", false); + if sess.print_llvm_stats() { + add("-stats", false); + } + for arg in sess_args { add(&(*arg), true); } @@ -310,7 +315,7 @@ pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec { pub fn print_version() { let (major, minor, patch) = get_version(); - println!("LLVM version: {}.{}.{}", major, minor, patch); + println!("LLVM version: {major}.{minor}.{patch}"); } pub fn get_version() -> (u32, u32, u32) { @@ -350,7 +355,7 @@ fn llvm_target_features(tm: &llvm::TargetMachine) -> Vec<(&str, &str)> { ret } -fn print_target_features(sess: &Session, tm: &llvm::TargetMachine) { +fn print_target_features(out: &mut dyn PrintBackendInfo, sess: &Session, tm: &llvm::TargetMachine) { let mut llvm_target_features = llvm_target_features(tm); let mut known_llvm_target_features = FxHashSet::<&'static str>::default(); let mut rustc_target_features = supported_target_features(sess) @@ -383,36 +388,48 @@ fn print_target_features(sess: &Session, tm: &llvm::TargetMachine) { .max() .unwrap_or(0); - println!("Features supported by rustc for this target:"); + writeln!(out, "Features supported by rustc for this target:"); for (feature, desc) in &rustc_target_features { - println!(" {1:0$} - {2}.", max_feature_len, feature, desc); + writeln!(out, " {feature:max_feature_len$} - {desc}."); } - println!("\nCode-generation features supported by LLVM for this target:"); + writeln!(out, "\nCode-generation features supported by LLVM for this target:"); for (feature, desc) in &llvm_target_features { - println!(" {1:0$} - {2}.", max_feature_len, feature, desc); + writeln!(out, " {feature:max_feature_len$} - {desc}."); } if llvm_target_features.is_empty() { - println!(" Target features listing is not supported by this LLVM version."); + writeln!(out, " Target features listing is not supported by this LLVM version."); } - println!("\nUse +feature to enable a feature, or -feature to disable it."); - println!("For example, rustc -C target-cpu=mycpu -C target-feature=+feature1,-feature2\n"); - println!("Code-generation features cannot be used in cfg or #[target_feature],"); - println!("and may be renamed or removed in a future version of LLVM or rustc.\n"); + writeln!(out, "\nUse +feature to enable a feature, or -feature to disable it."); + writeln!(out, "For example, rustc -C target-cpu=mycpu -C target-feature=+feature1,-feature2\n"); + writeln!(out, "Code-generation features cannot be used in cfg or #[target_feature],"); + writeln!(out, "and may be renamed or removed in a future version of LLVM or rustc.\n"); } -pub(crate) fn print(req: PrintRequest, sess: &Session) { +pub(crate) fn print(req: &PrintRequest, mut out: &mut dyn PrintBackendInfo, sess: &Session) { require_inited(); let tm = create_informational_target_machine(sess); - match req { - PrintRequest::TargetCPUs => { + match req.kind { + PrintKind::TargetCPUs => { // SAFETY generate a C compatible string from a byte slice to pass // the target CPU name into LLVM, the lifetime of the reference is // at least as long as the C function let cpu_cstring = CString::new(handle_native(sess.target.cpu.as_ref())) .unwrap_or_else(|e| bug!("failed to convert to cstring: {}", e)); - unsafe { llvm::LLVMRustPrintTargetCPUs(tm, cpu_cstring.as_ptr()) }; + unsafe extern "C" fn callback(out: *mut c_void, string: *const c_char, len: usize) { + let out = &mut *(out as *mut &mut dyn PrintBackendInfo); + let bytes = slice::from_raw_parts(string as *const u8, len); + write!(out, "{}", String::from_utf8_lossy(bytes)); + } + unsafe { + llvm::LLVMRustPrintTargetCPUs( + tm, + cpu_cstring.as_ptr(), + callback, + &mut out as *mut &mut dyn PrintBackendInfo as *mut c_void, + ); + } } - PrintRequest::TargetFeatures => print_target_features(sess, tm), + PrintKind::TargetFeatures => print_target_features(out, sess, tm), _ => bug!("rustc_codegen_llvm can't handle print request: {:?}", req), } } @@ -490,8 +507,6 @@ pub(crate) fn global_llvm_features(sess: &Session, diagnostics: bool) -> Vec= (15, 0, 0)) .map(String::from), ); @@ -558,7 +573,7 @@ pub(crate) fn global_llvm_features(sess: &Session, diagnostics: bool) -> Vec { - Some(format!("{}{}", enable_disable, f)) + Some(format!("{enable_disable}{f}")) } _ => None, } diff --git a/compiler/rustc_codegen_llvm/src/mono_item.rs b/compiler/rustc_codegen_llvm/src/mono_item.rs index c24854b27a02e..38e8220569a2a 100644 --- a/compiler/rustc_codegen_llvm/src/mono_item.rs +++ b/compiler/rustc_codegen_llvm/src/mono_item.rs @@ -48,7 +48,7 @@ impl<'tcx> PreDefineMethods<'tcx> for CodegenCx<'_, 'tcx> { visibility: Visibility, symbol_name: &str, ) { - assert!(!instance.substs.has_infer()); + assert!(!instance.args.has_infer()); let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty()); let lldecl = self.declare_fn(symbol_name, fn_abi, Some(instance)); @@ -111,7 +111,7 @@ impl CodegenCx<'_, '_> { } // Symbols from executables can't really be imported any further. - let all_exe = self.tcx.sess.crate_types().iter().all(|ty| *ty == CrateType::Executable); + let all_exe = self.tcx.crate_types().iter().all(|ty| *ty == CrateType::Executable); let is_declaration_for_linker = is_declaration || linkage == llvm::Linkage::AvailableExternallyLinkage; if all_exe && !is_declaration_for_linker { diff --git a/compiler/rustc_codegen_llvm/src/type_.rs b/compiler/rustc_codegen_llvm/src/type_.rs index d3fad5699c800..8db6195d931f0 100644 --- a/compiler/rustc_codegen_llvm/src/type_.rs +++ b/compiler/rustc_codegen_llvm/src/type_.rs @@ -52,6 +52,10 @@ impl<'ll> CodegenCx<'ll, '_> { unsafe { llvm::LLVMVoidTypeInContext(self.llcx) } } + pub(crate) fn type_token(&self) -> &'ll Type { + unsafe { llvm::LLVMTokenTypeInContext(self.llcx) } + } + pub(crate) fn type_metadata(&self) -> &'ll Type { unsafe { llvm::LLVMMetadataTypeInContext(self.llcx) } } @@ -108,12 +112,6 @@ impl<'ll> CodegenCx<'ll, '_> { } } - pub(crate) fn type_pointee_for_align(&self, align: Align) -> &'ll Type { - // FIXME(eddyb) We could find a better approximation if ity.align < align. - let ity = Integer::approximate_align(self, align); - self.type_from_integer(ity) - } - /// Return a LLVM type that has at most the required alignment, /// and exactly the required size, as a best-effort padding array. pub(crate) fn type_padding_filler(&self, size: Size, align: Align) -> &'ll Type { @@ -185,17 +183,12 @@ impl<'ll, 'tcx> BaseTypeMethods<'tcx> for CodegenCx<'ll, 'tcx> { unsafe { llvm::LLVMRustGetTypeKind(ty).to_generic() } } - fn type_ptr_to(&self, ty: &'ll Type) -> &'ll Type { - assert_ne!( - self.type_kind(ty), - TypeKind::Function, - "don't call ptr_to on function types, use ptr_to_llvm_type on FnAbi instead or explicitly specify an address space if it makes sense" - ); - ty.ptr_to(AddressSpace::DATA) + fn type_ptr(&self) -> &'ll Type { + self.type_ptr_ext(AddressSpace::DATA) } - fn type_ptr_to_ext(&self, ty: &'ll Type, address_space: AddressSpace) -> &'ll Type { - ty.ptr_to(address_space) + fn type_ptr_ext(&self, address_space: AddressSpace) -> &'ll Type { + unsafe { llvm::LLVMPointerTypeInContext(self.llcx, address_space.0) } } fn element_type(&self, ty: &'ll Type) -> &'ll Type { @@ -243,12 +236,8 @@ impl Type { unsafe { llvm::LLVMIntTypeInContext(llcx, num_bits as c_uint) } } - pub fn i8p_llcx(llcx: &llvm::Context) -> &Type { - Type::i8_llcx(llcx).ptr_to(AddressSpace::DATA) - } - - fn ptr_to(&self, address_space: AddressSpace) -> &Type { - unsafe { llvm::LLVMPointerType(self, address_space.0) } + pub fn ptr_llcx(llcx: &llvm::Context) -> &Type { + unsafe { llvm::LLVMPointerTypeInContext(llcx, AddressSpace::DATA.0) } } } @@ -288,6 +277,9 @@ impl<'ll, 'tcx> LayoutTypeMethods<'tcx> for CodegenCx<'ll, 'tcx> { fn reg_backend_type(&self, ty: &Reg) -> &'ll Type { ty.llvm_type(self) } + fn scalar_copy_backend_type(&self, layout: TyAndLayout<'tcx>) -> Option { + layout.scalar_copy_llvm_type(self) + } } impl<'ll, 'tcx> TypeMembershipMethods<'tcx> for CodegenCx<'ll, 'tcx> { diff --git a/compiler/rustc_codegen_llvm/src/type_of.rs b/compiler/rustc_codegen_llvm/src/type_of.rs index e264ce78f0d67..831645579b94a 100644 --- a/compiler/rustc_codegen_llvm/src/type_of.rs +++ b/compiler/rustc_codegen_llvm/src/type_of.rs @@ -6,6 +6,7 @@ use rustc_middle::bug; use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_target::abi::HasDataLayout; use rustc_target::abi::{Abi, Align, FieldsShape}; use rustc_target::abi::{Int, Pointer, F32, F64}; use rustc_target::abi::{PointeeInfo, Scalar, Size, TyAbiInterface, Variants}; @@ -22,7 +23,7 @@ fn uncached_llvm_type<'a, 'tcx>( match layout.abi { Abi::Scalar(_) => bug!("handled elsewhere"), Abi::Vector { element, count } => { - let element = layout.scalar_llvm_type_at(cx, element, Size::ZERO); + let element = layout.scalar_llvm_type_at(cx, element); return cx.type_vector(element, count); } Abi::ScalarPair(..) => { @@ -56,13 +57,10 @@ fn uncached_llvm_type<'a, 'tcx>( if let (&ty::Generator(_, _, _), &Variants::Single { index }) = (layout.ty.kind(), &layout.variants) { - write!(&mut name, "::{}", ty::GeneratorSubsts::variant_name(index)).unwrap(); + write!(&mut name, "::{}", ty::GeneratorArgs::variant_name(index)).unwrap(); } Some(name) } - // Use identified structure types for ADT. Due to pointee types in LLVM IR their definition - // might be recursive. Other cases are non-recursive and we can use literal structure types. - ty::Adt(..) => Some(String::new()), _ => None, }; @@ -178,12 +176,7 @@ pub trait LayoutLlvmExt<'tcx> { fn is_llvm_scalar_pair(&self) -> bool; fn llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>) -> &'a Type; fn immediate_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>) -> &'a Type; - fn scalar_llvm_type_at<'a>( - &self, - cx: &CodegenCx<'a, 'tcx>, - scalar: Scalar, - offset: Size, - ) -> &'a Type; + fn scalar_llvm_type_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>, scalar: Scalar) -> &'a Type; fn scalar_pair_element_llvm_type<'a>( &self, cx: &CodegenCx<'a, 'tcx>, @@ -192,14 +185,14 @@ pub trait LayoutLlvmExt<'tcx> { ) -> &'a Type; fn llvm_field_index<'a>(&self, cx: &CodegenCx<'a, 'tcx>, index: usize) -> u64; fn pointee_info_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>, offset: Size) -> Option; + fn scalar_copy_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>) -> Option<&'a Type>; } impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> { fn is_llvm_immediate(&self) -> bool { match self.abi { Abi::Scalar(_) | Abi::Vector { .. } => true, - Abi::ScalarPair(..) => false, - Abi::Uninhabited | Abi::Aggregate { .. } => self.is_zst(), + Abi::ScalarPair(..) | Abi::Uninhabited | Abi::Aggregate { .. } => false, } } @@ -229,16 +222,12 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> { return llty; } let llty = match *self.ty.kind() { - ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => { - cx.type_ptr_to(cx.layout_of(ty).llvm_type(cx)) - } - ty::Adt(def, _) if def.is_box() => { - cx.type_ptr_to(cx.layout_of(self.ty.boxed_ty()).llvm_type(cx)) - } + ty::Ref(..) | ty::RawPtr(_) => cx.type_ptr(), + ty::Adt(def, _) if def.is_box() => cx.type_ptr(), ty::FnPtr(sig) => { cx.fn_ptr_backend_type(cx.fn_abi_of_fn_ptr(sig, ty::List::empty())) } - _ => self.scalar_llvm_type_at(cx, scalar, Size::ZERO), + _ => self.scalar_llvm_type_at(cx, scalar), }; cx.scalar_lltypes.borrow_mut().insert(self.ty, llty); return llty; @@ -299,25 +288,12 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> { self.llvm_type(cx) } - fn scalar_llvm_type_at<'a>( - &self, - cx: &CodegenCx<'a, 'tcx>, - scalar: Scalar, - offset: Size, - ) -> &'a Type { + fn scalar_llvm_type_at<'a>(&self, cx: &CodegenCx<'a, 'tcx>, scalar: Scalar) -> &'a Type { match scalar.primitive() { Int(i, _) => cx.type_from_integer(i), F32 => cx.type_f32(), F64 => cx.type_f64(), - Pointer(address_space) => { - // If we know the alignment, pick something better than i8. - let pointee = if let Some(pointee) = self.pointee_info_at(cx, offset) { - cx.type_pointee_for_align(pointee.align) - } else { - cx.type_i8() - }; - cx.type_ptr_to_ext(pointee, address_space) - } + Pointer(address_space) => cx.type_ptr_ext(address_space), } } @@ -335,13 +311,14 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> { } // only wide pointer boxes are handled as pointers // thin pointer boxes with scalar allocators are handled by the general logic below - ty::Adt(def, substs) if def.is_box() && cx.layout_of(substs.type_at(1)).is_zst() => { - let ptr_ty = cx.tcx.mk_mut_ptr(self.ty.boxed_ty()); + ty::Adt(def, args) if def.is_box() && cx.layout_of(args.type_at(1)).is_zst() => { + let ptr_ty = Ty::new_mut_ptr(cx.tcx, self.ty.boxed_ty()); return cx.layout_of(ptr_ty).scalar_pair_element_llvm_type(cx, index, immediate); } // `dyn* Trait` has the same ABI as `*mut dyn Trait` ty::Dynamic(bounds, region, ty::DynStar) => { - let ptr_ty = cx.tcx.mk_mut_ptr(cx.tcx.mk_dynamic(bounds, region, ty::Dyn)); + let ptr_ty = + Ty::new_mut_ptr(cx.tcx, Ty::new_dynamic(cx.tcx, bounds, region, ty::Dyn)); return cx.layout_of(ptr_ty).scalar_pair_element_llvm_type(cx, index, immediate); } _ => {} @@ -362,8 +339,7 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> { return cx.type_i1(); } - let offset = if index == 0 { Size::ZERO } else { a.size(cx).align_to(b.align(cx).abi) }; - self.scalar_llvm_type_at(cx, scalar, offset) + self.scalar_llvm_type_at(cx, scalar) } fn llvm_field_index<'a>(&self, cx: &CodegenCx<'a, 'tcx>, index: usize) -> u64 { @@ -415,4 +391,39 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> { cx.pointee_infos.borrow_mut().insert((self.ty, offset), result); result } + + fn scalar_copy_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>) -> Option<&'a Type> { + debug_assert!(self.is_sized()); + + // FIXME: this is a fairly arbitrary choice, but 128 bits on WASM + // (matching the 128-bit SIMD types proposal) and 256 bits on x64 + // (like AVX2 registers) seems at least like a tolerable starting point. + let threshold = cx.data_layout().pointer_size * 4; + if self.layout.size() > threshold { + return None; + } + + // Vectors, even for non-power-of-two sizes, have the same layout as + // arrays but don't count as aggregate types + // While LLVM theoretically supports non-power-of-two sizes, and they + // often work fine, sometimes x86-isel deals with them horribly + // (see #115212) so for now only use power-of-two ones. + if let FieldsShape::Array { count, .. } = self.layout.fields() + && count.is_power_of_two() + && let element = self.field(cx, 0) + && element.ty.is_integral() + { + // `cx.type_ix(bits)` is tempting here, but while that works great + // for things that *stay* as memory-to-memory copies, it also ends + // up suppressing vectorization as it introduces shifts when it + // extracts all the individual values. + + let ety = element.llvm_type(cx); + return Some(cx.type_vector(ety, *count)); + } + + // FIXME: The above only handled integer arrays; surely more things + // would also be possible. Be careful about provenance, though! + None + } } diff --git a/compiler/rustc_codegen_llvm/src/va_arg.rs b/compiler/rustc_codegen_llvm/src/va_arg.rs index b19398e68c260..172c66a7af13c 100644 --- a/compiler/rustc_codegen_llvm/src/va_arg.rs +++ b/compiler/rustc_codegen_llvm/src/va_arg.rs @@ -5,7 +5,7 @@ use crate::value::Value; use rustc_codegen_ssa::mir::operand::OperandRef; use rustc_codegen_ssa::{ common::IntPredicate, - traits::{BaseTypeMethods, BuilderMethods, ConstMethods, DerivedTypeMethods}, + traits::{BaseTypeMethods, BuilderMethods, ConstMethods}, }; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; use rustc_middle::ty::Ty; @@ -26,24 +26,18 @@ fn round_pointer_up_to_alignment<'ll>( fn emit_direct_ptr_va_arg<'ll, 'tcx>( bx: &mut Builder<'_, 'll, 'tcx>, list: OperandRef<'tcx, &'ll Value>, - llty: &'ll Type, size: Size, align: Align, slot_size: Align, allow_higher_align: bool, ) -> (&'ll Value, Align) { - let va_list_ty = bx.type_i8p(); - let va_list_ptr_ty = bx.type_ptr_to(va_list_ty); - let va_list_addr = if list.layout.llvm_type(bx.cx) != va_list_ptr_ty { - bx.bitcast(list.immediate(), va_list_ptr_ty) - } else { - list.immediate() - }; + let va_list_ty = bx.type_ptr(); + let va_list_addr = list.immediate(); let ptr = bx.load(va_list_ty, va_list_addr, bx.tcx().data_layout.pointer_align.abi); let (addr, addr_align) = if allow_higher_align && align > slot_size { - (round_pointer_up_to_alignment(bx, ptr, align, bx.cx().type_i8p()), align) + (round_pointer_up_to_alignment(bx, ptr, align, bx.type_ptr()), align) } else { (ptr, slot_size) }; @@ -56,9 +50,9 @@ fn emit_direct_ptr_va_arg<'ll, 'tcx>( if size.bytes() < slot_size.bytes() && bx.tcx().sess.target.endian == Endian::Big { let adjusted_size = bx.cx().const_i32((slot_size.bytes() - size.bytes()) as i32); let adjusted = bx.inbounds_gep(bx.type_i8(), addr, &[adjusted_size]); - (bx.bitcast(adjusted, bx.cx().type_ptr_to(llty)), addr_align) + (adjusted, addr_align) } else { - (bx.bitcast(addr, bx.cx().type_ptr_to(llty)), addr_align) + (addr, addr_align) } } @@ -73,7 +67,7 @@ fn emit_ptr_va_arg<'ll, 'tcx>( let layout = bx.cx.layout_of(target_ty); let (llty, size, align) = if indirect { ( - bx.cx.layout_of(bx.cx.tcx.mk_imm_ptr(target_ty)).llvm_type(bx.cx), + bx.cx.layout_of(Ty::new_imm_ptr(bx.cx.tcx, target_ty)).llvm_type(bx.cx), bx.cx.data_layout().pointer_size, bx.cx.data_layout().pointer_align, ) @@ -81,7 +75,7 @@ fn emit_ptr_va_arg<'ll, 'tcx>( (layout.llvm_type(bx.cx), layout.size, layout.align) }; let (addr, addr_align) = - emit_direct_ptr_va_arg(bx, list, llty, size, align.abi, slot_size, allow_higher_align); + emit_direct_ptr_va_arg(bx, list, size, align.abi, slot_size, allow_higher_align); if indirect { let tmp_ret = bx.load(llty, addr, addr_align); bx.load(bx.cx.layout_of(target_ty).llvm_type(bx.cx), tmp_ret, align.abi) @@ -146,7 +140,7 @@ fn emit_aapcs_va_arg<'ll, 'tcx>( bx.cond_br(use_stack, on_stack, in_reg); bx.switch_to_block(in_reg); - let top_type = bx.type_i8p(); + let top_type = bx.type_ptr(); let top = bx.struct_gep(va_list_ty, va_list_addr, reg_top_index); let top = bx.load(top_type, top, bx.tcx().data_layout.pointer_align.abi); @@ -158,7 +152,6 @@ fn emit_aapcs_va_arg<'ll, 'tcx>( reg_addr = bx.gep(bx.type_i8(), reg_addr, &[offset]); } let reg_type = layout.llvm_type(bx); - let reg_addr = bx.bitcast(reg_addr, bx.cx.type_ptr_to(reg_type)); let reg_value = bx.load(reg_type, reg_addr, layout.align.abi); bx.br(end); @@ -218,7 +211,7 @@ fn emit_s390x_va_arg<'ll, 'tcx>( // Work out the address of the value in the register save area. let reg_ptr = bx.struct_gep(va_list_ty, va_list_addr, va_list_layout.llvm_field_index(bx.cx, 3)); - let reg_ptr_v = bx.load(bx.type_i8p(), reg_ptr, bx.tcx().data_layout.pointer_align.abi); + let reg_ptr_v = bx.load(bx.type_ptr(), reg_ptr, bx.tcx().data_layout.pointer_align.abi); let scaled_reg_count = bx.mul(reg_count_v, bx.const_u64(8)); let reg_off = bx.add(scaled_reg_count, bx.const_u64(reg_save_index * 8 + reg_padding)); let reg_addr = bx.gep(bx.type_i8(), reg_ptr_v, &[reg_off]); @@ -234,7 +227,7 @@ fn emit_s390x_va_arg<'ll, 'tcx>( // Work out the address of the value in the argument overflow area. let arg_ptr = bx.struct_gep(va_list_ty, va_list_addr, va_list_layout.llvm_field_index(bx.cx, 2)); - let arg_ptr_v = bx.load(bx.type_i8p(), arg_ptr, bx.tcx().data_layout.pointer_align.abi); + let arg_ptr_v = bx.load(bx.type_ptr(), arg_ptr, bx.tcx().data_layout.pointer_align.abi); let arg_off = bx.const_u64(padding); let mem_addr = bx.gep(bx.type_i8(), arg_ptr_v, &[arg_off]); @@ -246,14 +239,12 @@ fn emit_s390x_va_arg<'ll, 'tcx>( // Return the appropriate result. bx.switch_to_block(end); - let val_addr = bx.phi(bx.type_i8p(), &[reg_addr, mem_addr], &[in_reg, in_mem]); + let val_addr = bx.phi(bx.type_ptr(), &[reg_addr, mem_addr], &[in_reg, in_mem]); let val_type = layout.llvm_type(bx); let val_addr = if indirect { - let ptr_type = bx.cx.type_ptr_to(val_type); - let ptr_addr = bx.bitcast(val_addr, bx.cx.type_ptr_to(ptr_type)); - bx.load(ptr_type, ptr_addr, bx.tcx().data_layout.pointer_align.abi) + bx.load(bx.cx.type_ptr(), val_addr, bx.tcx().data_layout.pointer_align.abi) } else { - bx.bitcast(val_addr, bx.cx.type_ptr_to(val_type)) + val_addr }; bx.load(val_type, val_addr, layout.align.abi) } diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index 0ac12d32be535..34d0e2d1df663 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -3,21 +3,17 @@ name = "rustc_codegen_ssa" version = "0.0.0" edition = "2021" -[lib] -test = false - [dependencies] -ar_archive_writer = "0.1.3" +ar_archive_writer = "0.1.5" bitflags = "1.2.1" cc = "1.0.69" itertools = "0.10.1" tracing = "0.1" jobserver = "0.1.22" tempfile = "3.2" -thorin-dwp = "0.6" +thorin-dwp = "0.7" pathdiff = "0.2.0" serde_json = "1.0.59" -snap = "1" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } regex = "1.4" @@ -46,9 +42,9 @@ rustc_session = { path = "../rustc_session" } libc = "0.2.50" [dependencies.object] -version = "0.31.1" +version = "0.32.0" default-features = false -features = ["read_core", "elf", "macho", "pe", "unaligned", "archive", "write"] +features = ["read_core", "elf", "macho", "pe", "xcoff", "unaligned", "archive", "write"] [target.'cfg(windows)'.dependencies.windows] version = "0.48.0" diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 375fdec10075a..b6c70c6224977 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -1,306 +1,312 @@ -codegen_ssa_lib_def_write_failure = failed to write lib.def file: {$error} +codegen_ssa_L4Bender_exporting_symbols_unimplemented = exporting symbols not implemented yet for L4Bender -codegen_ssa_version_script_write_failure = failed to write version script: {$error} +codegen_ssa_add_native_library = failed to add native library {$library_path}: {$error} -codegen_ssa_symbol_file_write_failure = failed to write symbols file: {$error} +codegen_ssa_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error} -codegen_ssa_ld64_unimplemented_modifier = `as-needed` modifier not implemented yet for ld64 +codegen_ssa_archive_build_failure = + failed to build archive: {$error} -codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for current linker +codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing failure memory ordering -codegen_ssa_L4Bender_exporting_symbols_unimplemented = exporting symbols not implemented yet for L4Bender +codegen_ssa_binary_output_to_tty = option `-o` or `--emit` is used to write binary output type `{$shorthand}` to stdout, but stdout is a tty -codegen_ssa_no_natvis_directory = error enumerating natvis directory: {$error} +codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error} codegen_ssa_copy_path_buf = unable to copy {$source_file} to {$output_path}: {$error} +codegen_ssa_create_temp_dir = couldn't create a temp dir: {$error} + +codegen_ssa_erroneous_constant = erroneous constant encountered + +codegen_ssa_error_creating_remark_dir = failed to create remark directory: {$error} + +codegen_ssa_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)` + +codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified + +codegen_ssa_extract_bundled_libs_archive_member = failed to get data from archive member '{$rlib}': {$error} +codegen_ssa_extract_bundled_libs_convert_name = failed to convert name '{$rlib}': {$error} +codegen_ssa_extract_bundled_libs_mmap_file = failed to mmap file '{$rlib}': {$error} +codegen_ssa_extract_bundled_libs_open_file = failed to open file '{$rlib}': {$error} +codegen_ssa_extract_bundled_libs_parse_archive = failed to parse archive '{$rlib}': {$error} +codegen_ssa_extract_bundled_libs_read_entry = failed to read entry '{$rlib}': {$error} +codegen_ssa_extract_bundled_libs_write_file = failed to write file '{$rlib}': {$error} + +codegen_ssa_failed_to_write = failed to write {$path}: {$error} + codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files were produced -codegen_ssa_create_temp_dir = couldn't create a temp dir: {$error} +codegen_ssa_illegal_link_ordinal_format = illegal ordinal format in `link_ordinal` + .note = an unsuffixed integer value, e.g., `1`, is expected codegen_ssa_incompatible_linking_modifiers = link modifiers combination `+bundle,+whole-archive` is unstable when generating rlibs -codegen_ssa_add_native_library = failed to add native library {$library_path}: {$error} - -codegen_ssa_multiple_external_func_decl = multiple declarations of external function `{$function}` from library `{$library_name}` have different calling conventions +codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient. -codegen_ssa_rlib_missing_format = could not find formats for rlibs +codegen_ssa_invalid_link_ordinal_nargs = incorrect number of arguments to `#[link_ordinal]` + .note = the attribute requires exactly one argument -codegen_ssa_rlib_only_rmeta_found = could not find rlib for: `{$crate_name}`, found rmeta (metadata) file +codegen_ssa_invalid_monomorphization_basic_float_type = invalid monomorphization of `{$name}` intrinsic: expected basic float type, found `{$ty}` -codegen_ssa_rlib_not_found = could not find rlib for: `{$crate_name}` +codegen_ssa_invalid_monomorphization_basic_integer_type = invalid monomorphization of `{$name}` intrinsic: expected basic integer type, found `{$ty}` -codegen_ssa_rlib_incompatible_dependency_formats = `{$ty1}` and `{$ty2}` do not have equivalent dependency formats (`{$list1}` vs `{$list2}`) +codegen_ssa_invalid_monomorphization_cannot_return = invalid monomorphization of `{$name}` intrinsic: cannot return `{$ret_ty}`, expected `u{$expected_int_bits}` or `[u8; {$expected_bytes}]` -codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status} +codegen_ssa_invalid_monomorphization_cast_fat_pointer = invalid monomorphization of `{$name}` intrinsic: cannot cast fat pointer `{$ty}` -codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified +codegen_ssa_invalid_monomorphization_expected_element_type = invalid monomorphization of `{$name}` intrinsic: expected element type `{$expected_element}` of second argument `{$second_arg}` to be a pointer to the element type `{$in_elem}` of the first argument `{$in_ty}`, found `{$expected_element}` != `{$mutability} {$in_elem}` -codegen_ssa_specify_libraries_to_link = use the `-l` flag to specify native libraries to link +codegen_ssa_invalid_monomorphization_expected_pointer = invalid monomorphization of `{$name}` intrinsic: expected pointer, got `{$ty}` -codegen_ssa_use_cargo_directive = use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname) +codegen_ssa_invalid_monomorphization_expected_return_type = invalid monomorphization of `{$name}` intrinsic: expected return type `{$in_ty}`, found `{$ret_ty}` -codegen_ssa_thorin_read_input_failure = failed to read input file +codegen_ssa_invalid_monomorphization_expected_usize = invalid monomorphization of `{$name}` intrinsic: expected `usize`, got `{$ty}` -codegen_ssa_thorin_parse_input_file_kind = failed to parse input file kind +codegen_ssa_invalid_monomorphization_expected_vector_element_type = invalid monomorphization of `{$name}` intrinsic: expected element type `{$expected_element}` of vector type `{$vector_type}` to be a signed or unsigned integer type -codegen_ssa_thorin_parse_input_object_file = failed to parse input object file +codegen_ssa_invalid_monomorphization_float_to_int_unchecked = invalid monomorphization of `float_to_int_unchecked` intrinsic: expected basic float type, found `{$ty}` -codegen_ssa_thorin_parse_input_archive_file = failed to parse input archive file +codegen_ssa_invalid_monomorphization_floating_point_type = invalid monomorphization of `{$name}` intrinsic: `{$in_ty}` is not a floating-point type -codegen_ssa_thorin_parse_archive_member = failed to parse archive member +codegen_ssa_invalid_monomorphization_floating_point_vector = invalid monomorphization of `{$name}` intrinsic: unsupported element type `{$f_ty}` of floating-point vector `{$in_ty}` -codegen_ssa_thorin_invalid_input_kind = input is not an archive or elf object +codegen_ssa_invalid_monomorphization_inserted_type = invalid monomorphization of `{$name}` intrinsic: expected inserted type `{$in_elem}` (element of input `{$in_ty}`), found `{$out_ty}` -codegen_ssa_thorin_decompress_data = failed to decompress compressed section +codegen_ssa_invalid_monomorphization_invalid_bitmask = invalid monomorphization of `{$name}` intrinsic: invalid bitmask `{$mask_ty}`, expected `u{$expected_int_bits}` or `[u8; {$expected_bytes}]` -codegen_ssa_thorin_section_without_name = section without name at offset {$offset} +codegen_ssa_invalid_monomorphization_mask_type = invalid monomorphization of `{$name}` intrinsic: mask element type is `{$ty}`, expected `i_` -codegen_ssa_thorin_relocation_with_invalid_symbol = relocation with invalid symbol for section `{$section}` at offset {$offset} +codegen_ssa_invalid_monomorphization_mismatched_lengths = invalid monomorphization of `{$name}` intrinsic: mismatched lengths: mask length `{$m_len}` != other vector length `{$v_len}` -codegen_ssa_thorin_multiple_relocations = multiple relocations for section `{$section}` at offset {$offset} +codegen_ssa_invalid_monomorphization_return_element = invalid monomorphization of `{$name}` intrinsic: expected return element type `{$in_elem}` (element of input `{$in_ty}`), found `{$ret_ty}` with element type `{$out_ty}` -codegen_ssa_thorin_unsupported_relocation = unsupported relocation for section {$section} at offset {$offset} +codegen_ssa_invalid_monomorphization_return_integer_type = invalid monomorphization of `{$name}` intrinsic: expected return type with integer elements, found `{$ret_ty}` with non-integer `{$out_ty}` -codegen_ssa_thorin_missing_dwo_name = missing path attribute to DWARF object ({$id}) +codegen_ssa_invalid_monomorphization_return_length = invalid monomorphization of `{$name}` intrinsic: expected return type of length {$in_len}, found `{$ret_ty}` with length {$out_len} -codegen_ssa_thorin_no_compilation_units = input object has no compilation units +codegen_ssa_invalid_monomorphization_return_length_input_type = invalid monomorphization of `{$name}` intrinsic: expected return type with length {$in_len} (same as input type `{$in_ty}`), found `{$ret_ty}` with length {$out_len} -codegen_ssa_thorin_no_die = no top-level debugging information entry in compilation/type unit +codegen_ssa_invalid_monomorphization_return_type = invalid monomorphization of `{$name}` intrinsic: expected return type `{$in_elem}` (element of input `{$in_ty}`), found `{$ret_ty}` -codegen_ssa_thorin_top_level_die_not_unit = top-level debugging information entry is not a compilation/type unit +codegen_ssa_invalid_monomorphization_second_argument_length = invalid monomorphization of `{$name}` intrinsic: expected second argument with length {$in_len} (same as input type `{$in_ty}`), found `{$arg_ty}` with length {$out_len} -codegen_ssa_thorin_missing_required_section = input object missing required section `{$section}` +codegen_ssa_invalid_monomorphization_shuffle_index_not_constant = invalid monomorphization of `{$name}` intrinsic: shuffle index #{$arg_idx} is not a constant -codegen_ssa_thorin_parse_unit_abbreviations = failed to parse unit abbreviations +codegen_ssa_invalid_monomorphization_shuffle_index_out_of_bounds = invalid monomorphization of `{$name}` intrinsic: shuffle index #{$arg_idx} is out of bounds (limit {$total_len}) -codegen_ssa_thorin_parse_unit_attribute = failed to parse unit attribute +codegen_ssa_invalid_monomorphization_simd_argument = invalid monomorphization of `{$name}` intrinsic: expected SIMD argument type, found non-SIMD `{$ty}` -codegen_ssa_thorin_parse_unit_header = failed to parse unit header +codegen_ssa_invalid_monomorphization_simd_first = invalid monomorphization of `{$name}` intrinsic: expected SIMD first type, found non-SIMD `{$ty}` -codegen_ssa_thorin_parse_unit = failed to parse unit +codegen_ssa_invalid_monomorphization_simd_input = invalid monomorphization of `{$name}` intrinsic: expected SIMD input type, found non-SIMD `{$ty}` -codegen_ssa_thorin_incompatible_index_version = incompatible `{$section}` index version: found version {$actual}, expected version {$format} +codegen_ssa_invalid_monomorphization_simd_return = invalid monomorphization of `{$name}` intrinsic: expected SIMD return type, found non-SIMD `{$ty}` -codegen_ssa_thorin_offset_at_index = read offset at index {$index} of `.debug_str_offsets.dwo` section +codegen_ssa_invalid_monomorphization_simd_second = invalid monomorphization of `{$name}` intrinsic: expected SIMD second type, found non-SIMD `{$ty}` -codegen_ssa_thorin_str_at_offset = read string at offset {$offset} of `.debug_str.dwo` section +codegen_ssa_invalid_monomorphization_simd_shuffle = invalid monomorphization of `{$name}` intrinsic: simd_shuffle index must be an array of `u32`, got `{$ty}` -codegen_ssa_thorin_parse_index = failed to parse `{$section}` index section +codegen_ssa_invalid_monomorphization_simd_third = invalid monomorphization of `{$name}` intrinsic: expected SIMD third type, found non-SIMD `{$ty}` -codegen_ssa_thorin_unit_not_in_index = unit {$unit} from input package is not in its index +codegen_ssa_invalid_monomorphization_third_arg_element_type = invalid monomorphization of `{$name}` intrinsic: expected element type `{$expected_element}` of third argument `{$third_arg}` to be a signed integer type -codegen_ssa_thorin_row_not_in_index = row {$row} found in index's hash table not present in index +codegen_ssa_invalid_monomorphization_third_argument_length = invalid monomorphization of `{$name}` intrinsic: expected third argument with length {$in_len} (same as input type `{$in_ty}`), found `{$arg_ty}` with length {$out_len} -codegen_ssa_thorin_section_not_in_row = section not found in unit's row in index +codegen_ssa_invalid_monomorphization_unrecognized_intrinsic = invalid monomorphization of `{$name}` intrinsic: unrecognized intrinsic `{$name}` -codegen_ssa_thorin_empty_unit = unit {$unit} in input DWARF object with no data +codegen_ssa_invalid_monomorphization_unsupported_cast = invalid monomorphization of `{$name}` intrinsic: unsupported cast from `{$in_ty}` with element `{$in_elem}` to `{$ret_ty}` with element `{$out_elem}` -codegen_ssa_thorin_multiple_debug_info_section = multiple `.debug_info.dwo` sections +codegen_ssa_invalid_monomorphization_unsupported_operation = invalid monomorphization of `{$name}` intrinsic: unsupported operation on `{$in_ty}` with element `{$in_elem}` -codegen_ssa_thorin_multiple_debug_types_section = multiple `.debug_types.dwo` sections in a package +codegen_ssa_invalid_monomorphization_unsupported_symbol = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` to `{$ret_ty}` -codegen_ssa_thorin_not_split_unit = regular compilation unit in object (missing dwo identifier) +codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` of size `{$size}` to `{$ret_ty}` -codegen_ssa_thorin_duplicate_unit = duplicate split compilation unit ({$unit}) +codegen_ssa_invalid_monomorphization_vector_argument = invalid monomorphization of `{$name}` intrinsic: vector argument `{$in_ty}`'s element type `{$in_elem}`, expected integer element type -codegen_ssa_thorin_missing_referenced_unit = unit {$unit} referenced by executable was not found +codegen_ssa_invalid_no_sanitize = invalid argument for `no_sanitize` + .note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread` -codegen_ssa_thorin_not_output_object_created = no output object was created from inputs +codegen_ssa_invalid_windows_subsystem = invalid windows subsystem `{$subsystem}`, only `windows` and `console` are allowed -codegen_ssa_thorin_mixed_input_encodings = input objects haved mixed encodings +codegen_ssa_ld64_unimplemented_modifier = `as-needed` modifier not implemented yet for ld64 -codegen_ssa_thorin_io = {$error} -codegen_ssa_thorin_object_read = {$error} -codegen_ssa_thorin_object_write = {$error} -codegen_ssa_thorin_gimli_read = {$error} -codegen_ssa_thorin_gimli_write = {$error} +codegen_ssa_lib_def_write_failure = failed to write lib.def file: {$error} codegen_ssa_link_exe_unexpected_error = `link.exe` returned an unexpected error -codegen_ssa_repair_vs_build_tools = the Visual Studio build tools may need to be repaired using the Visual Studio installer - -codegen_ssa_missing_cpp_build_tool_component = or a necessary component may be missing from the "C++ build tools" workload +codegen_ssa_link_script_unavailable = can only use link script when linking with GNU-like linker -codegen_ssa_select_cpp_build_tool_workload = in the Visual Studio installer, ensure the "C++ build tools" workload is selected +codegen_ssa_link_script_write_failure = failed to write link script to {$path}: {$error} -codegen_ssa_visual_studio_not_installed = you may need to install Visual Studio build tools with the "C++ build tools" workload +codegen_ssa_linker_file_stem = couldn't extract file stem from specified linker codegen_ssa_linker_not_found = linker `{$linker_path}` not found .note = {$error} -codegen_ssa_unable_to_exe_linker = could not exec the linker `{$linker_path}` - .note = {$error} - .command_note = {$command_formatted} +codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for current linker -codegen_ssa_msvc_missing_linker = the msvc targets depend on the msvc linker but `link.exe` was not found +codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status} -codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. +codegen_ssa_metadata_object_file_write = error writing metadata object file: {$error} -codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient. +codegen_ssa_missing_cpp_build_tool_component = or a necessary component may be missing from the "C++ build tools" workload -codegen_ssa_processing_dymutil_failed = processing debug info with `dsymutil` failed: {$status} - .note = {$output} +codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering -codegen_ssa_unable_to_run_dsymutil = unable to run `dsymutil`: {$error} +codegen_ssa_msvc_missing_linker = the msvc targets depend on the msvc linker but `link.exe` was not found -codegen_ssa_stripping_debug_info_failed = stripping debug info with `{$util}` failed: {$status} - .note = {$output} +codegen_ssa_multiple_external_func_decl = multiple declarations of external function `{$function}` from library `{$library_name}` have different calling conventions -codegen_ssa_unable_to_run = unable to run `{$util}`: {$error} +codegen_ssa_multiple_main_functions = entry symbol `main` declared multiple times + .help = did you use `#[no_mangle]` on `fn main`? Use `#[start]` instead -codegen_ssa_linker_file_stem = couldn't extract file stem from specified linker +codegen_ssa_no_natvis_directory = error enumerating natvis directory: {$error} -codegen_ssa_static_library_native_artifacts = Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms. +codegen_ssa_option_gcc_only = option `-Z gcc-ld` is used even though linker flavor is not gcc -codegen_ssa_link_script_unavailable = can only use link script when linking with GNU-like linker +codegen_ssa_polymorphic_constant_too_generic = codegen encountered polymorphic constant: TooGeneric -codegen_ssa_link_script_write_failure = failed to write link script to {$path}: {$error} +codegen_ssa_processing_dymutil_failed = processing debug info with `dsymutil` failed: {$status} + .note = {$output} -codegen_ssa_failed_to_write = failed to write {$path}: {$error} +codegen_ssa_read_file = failed to read file: {$message} -codegen_ssa_unable_to_write_debugger_visualizer = Unable to write debugger visualizer file `{$path}`: {$error} +codegen_ssa_repair_vs_build_tools = the Visual Studio build tools may need to be repaired using the Visual Studio installer codegen_ssa_rlib_archive_build_failure = failed to build archive from rlib: {$error} -codegen_ssa_option_gcc_only = option `-Z gcc-ld` is used even though linker flavor is not gcc - -codegen_ssa_extract_bundled_libs_open_file = failed to open file '{$rlib}': {$error} -codegen_ssa_extract_bundled_libs_mmap_file = failed to mmap file '{$rlib}': {$error} -codegen_ssa_extract_bundled_libs_parse_archive = failed to parse archive '{$rlib}': {$error} -codegen_ssa_extract_bundled_libs_read_entry = failed to read entry '{$rlib}': {$error} -codegen_ssa_extract_bundled_libs_archive_member = failed to get data from archive member '{$rlib}': {$error} -codegen_ssa_extract_bundled_libs_convert_name = failed to convert name '{$rlib}': {$error} -codegen_ssa_extract_bundled_libs_write_file = failed to write file '{$rlib}': {$error} - -codegen_ssa_unsupported_arch = unsupported arch `{$arch}` for os `{$os}` +codegen_ssa_rlib_incompatible_dependency_formats = `{$ty1}` and `{$ty2}` do not have equivalent dependency formats (`{$list1}` vs `{$list2}`) -codegen_ssa_apple_sdk_error_sdk_path = failed to get {$sdk_name} SDK path: {$error} +codegen_ssa_rlib_missing_format = could not find formats for rlibs -codegen_ssa_read_file = failed to read file: {$message} +codegen_ssa_rlib_not_found = could not find rlib for: `{$crate_name}` -codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target +codegen_ssa_rlib_only_rmeta_found = could not find rlib for: `{$crate_name}`, found rmeta (metadata) file -codegen_ssa_archive_build_failure = - failed to build archive: {$error} +codegen_ssa_select_cpp_build_tool_workload = in the Visual Studio installer, ensure the "C++ build tools" workload is selected -codegen_ssa_unknown_archive_kind = - Don't know how to build archive of type: {$kind} +codegen_ssa_shuffle_indices_evaluation = could not evaluate shuffle_indices at compile time -codegen_ssa_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)` +codegen_ssa_specify_libraries_to_link = use the `-l` flag to specify native libraries to link -codegen_ssa_multiple_main_functions = entry symbol `main` declared multiple times - .help = did you use `#[no_mangle]` on `fn main`? Use `#[start]` instead +codegen_ssa_static_library_native_artifacts = Link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms. -codegen_ssa_metadata_object_file_write = error writing metadata object file: {$error} +codegen_ssa_static_library_native_artifacts_to_file = Native artifacts to link against have been written to {$path}. The order and any duplication can be significant on some platforms. -codegen_ssa_invalid_windows_subsystem = invalid windows subsystem `{$subsystem}`, only `windows` and `console` are allowed +codegen_ssa_stripping_debug_info_failed = stripping debug info with `{$util}` failed: {$status} + .note = {$output} -codegen_ssa_erroneous_constant = erroneous constant encountered +codegen_ssa_symbol_file_write_failure = failed to write symbols file: {$error} -codegen_ssa_shuffle_indices_evaluation = could not evaluate shuffle_indices at compile time +codegen_ssa_target_feature_safe_trait = `#[target_feature(..)]` cannot be applied to safe trait method + .label = cannot be applied to safe trait method + .label_def = not an `unsafe` function -codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering +codegen_ssa_thorin_decompress_data = failed to decompress compressed section -codegen_ssa_unknown_atomic_ordering = unknown ordering in atomic intrinsic +codegen_ssa_thorin_duplicate_unit = duplicate split compilation unit ({$unit}) -codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing failure memory ordering +codegen_ssa_thorin_empty_unit = unit {$unit} in input DWARF object with no data -codegen_ssa_unknown_atomic_operation = unknown atomic operation +codegen_ssa_thorin_gimli_read = {$error} +codegen_ssa_thorin_gimli_write = {$error} -codegen_ssa_invalid_monomorphization_basic_integer_type = invalid monomorphization of `{$name}` intrinsic: expected basic integer type, found `{$ty}` +codegen_ssa_thorin_incompatible_index_version = incompatible `{$section}` index version: found version {$actual}, expected version {$format} -codegen_ssa_invalid_monomorphization_basic_float_type = invalid monomorphization of `{$name}` intrinsic: expected basic float type, found `{$ty}` +codegen_ssa_thorin_invalid_input_kind = input is not an archive or elf object -codegen_ssa_invalid_monomorphization_float_to_int_unchecked = invalid monomorphization of `float_to_int_unchecked` intrinsic: expected basic float type, found `{$ty}` +codegen_ssa_thorin_io = {$error} +codegen_ssa_thorin_missing_dwo_name = missing path attribute to DWARF object ({$id}) -codegen_ssa_invalid_monomorphization_floating_point_vector = invalid monomorphization of `{$name}` intrinsic: unsupported element type `{$f_ty}` of floating-point vector `{$in_ty}` +codegen_ssa_thorin_missing_referenced_unit = unit {$unit} referenced by executable was not found -codegen_ssa_invalid_monomorphization_floating_point_type = invalid monomorphization of `{$name}` intrinsic: `{$in_ty}` is not a floating-point type +codegen_ssa_thorin_missing_required_section = input object missing required section `{$section}` -codegen_ssa_invalid_monomorphization_unrecognized_intrinsic = invalid monomorphization of `{$name}` intrinsic: unrecognized intrinsic `{$name}` +codegen_ssa_thorin_mixed_input_encodings = input objects haved mixed encodings -codegen_ssa_invalid_monomorphization_simd_argument = invalid monomorphization of `{$name}` intrinsic: expected SIMD argument type, found non-SIMD `{$ty}` +codegen_ssa_thorin_multiple_debug_info_section = multiple `.debug_info.dwo` sections -codegen_ssa_invalid_monomorphization_simd_input = invalid monomorphization of `{$name}` intrinsic: expected SIMD input type, found non-SIMD `{$ty}` +codegen_ssa_thorin_multiple_debug_types_section = multiple `.debug_types.dwo` sections in a package -codegen_ssa_invalid_monomorphization_simd_first = invalid monomorphization of `{$name}` intrinsic: expected SIMD first type, found non-SIMD `{$ty}` +codegen_ssa_thorin_multiple_relocations = multiple relocations for section `{$section}` at offset {$offset} -codegen_ssa_invalid_monomorphization_simd_second = invalid monomorphization of `{$name}` intrinsic: expected SIMD second type, found non-SIMD `{$ty}` +codegen_ssa_thorin_no_compilation_units = input object has no compilation units -codegen_ssa_invalid_monomorphization_simd_third = invalid monomorphization of `{$name}` intrinsic: expected SIMD third type, found non-SIMD `{$ty}` +codegen_ssa_thorin_no_die = no top-level debugging information entry in compilation/type unit -codegen_ssa_invalid_monomorphization_simd_return = invalid monomorphization of `{$name}` intrinsic: expected SIMD return type, found non-SIMD `{$ty}` +codegen_ssa_thorin_not_output_object_created = no output object was created from inputs -codegen_ssa_invalid_monomorphization_invalid_bitmask = invalid monomorphization of `{$name}` intrinsic: invalid bitmask `{$mask_ty}`, expected `u{$expected_int_bits}` or `[u8; {$expected_bytes}]` +codegen_ssa_thorin_not_split_unit = regular compilation unit in object (missing dwo identifier) -codegen_ssa_polymorphic_constant_too_generic = codegen encountered polymorphic constant: TooGeneric +codegen_ssa_thorin_object_read = {$error} +codegen_ssa_thorin_object_write = {$error} +codegen_ssa_thorin_offset_at_index = read offset at index {$index} of `.debug_str_offsets.dwo` section -codegen_ssa_invalid_monomorphization_return_length_input_type = invalid monomorphization of `{$name}` intrinsic: expected return type with length {$in_len} (same as input type `{$in_ty}`), found `{$ret_ty}` with length {$out_len} +codegen_ssa_thorin_parse_archive_member = failed to parse archive member -codegen_ssa_invalid_monomorphization_second_argument_length = invalid monomorphization of `{$name}` intrinsic: expected second argument with length {$in_len} (same as input type `{$in_ty}`), found `{$arg_ty}` with length {$out_len} +codegen_ssa_thorin_parse_index = failed to parse `{$section}` index section -codegen_ssa_invalid_monomorphization_third_argument_length = invalid monomorphization of `{$name}` intrinsic: expected third argument with length {$in_len} (same as input type `{$in_ty}`), found `{$arg_ty}` with length {$out_len} +codegen_ssa_thorin_parse_input_archive_file = failed to parse input archive file -codegen_ssa_invalid_monomorphization_return_integer_type = invalid monomorphization of `{$name}` intrinsic: expected return type with integer elements, found `{$ret_ty}` with non-integer `{$out_ty}` +codegen_ssa_thorin_parse_input_file_kind = failed to parse input file kind -codegen_ssa_invalid_monomorphization_simd_shuffle = invalid monomorphization of `{$name}` intrinsic: simd_shuffle index must be an array of `u32`, got `{$ty}` +codegen_ssa_thorin_parse_input_object_file = failed to parse input object file -codegen_ssa_invalid_monomorphization_return_length = invalid monomorphization of `{$name}` intrinsic: expected return type of length {$in_len}, found `{$ret_ty}` with length {$out_len} +codegen_ssa_thorin_parse_unit = failed to parse unit -codegen_ssa_invalid_monomorphization_return_element = invalid monomorphization of `{$name}` intrinsic: expected return element type `{$in_elem}` (element of input `{$in_ty}`), found `{$ret_ty}` with element type `{$out_ty}` +codegen_ssa_thorin_parse_unit_abbreviations = failed to parse unit abbreviations -codegen_ssa_invalid_monomorphization_shuffle_index_not_constant = invalid monomorphization of `{$name}` intrinsic: shuffle index #{$arg_idx} is not a constant +codegen_ssa_thorin_parse_unit_attribute = failed to parse unit attribute -codegen_ssa_invalid_monomorphization_shuffle_index_out_of_bounds = invalid monomorphization of `{$name}` intrinsic: shuffle index #{$arg_idx} is out of bounds (limit {$total_len}) +codegen_ssa_thorin_parse_unit_header = failed to parse unit header -codegen_ssa_invalid_monomorphization_inserted_type = invalid monomorphization of `{$name}` intrinsic: expected inserted type `{$in_elem}` (element of input `{$in_ty}`), found `{$out_ty}` +codegen_ssa_thorin_read_input_failure = failed to read input file -codegen_ssa_invalid_monomorphization_return_type = invalid monomorphization of `{$name}` intrinsic: expected return type `{$in_elem}` (element of input `{$in_ty}`), found `{$ret_ty}` +codegen_ssa_thorin_relocation_with_invalid_symbol = relocation with invalid symbol for section `{$section}` at offset {$offset} -codegen_ssa_invalid_monomorphization_expected_return_type = invalid monomorphization of `{$name}` intrinsic: expected return type `{$in_ty}`, found `{$ret_ty}` +codegen_ssa_thorin_row_not_in_index = row {$row} found in index's hash table not present in index -codegen_ssa_invalid_monomorphization_mismatched_lengths = invalid monomorphization of `{$name}` intrinsic: mismatched lengths: mask length `{$m_len}` != other vector length `{$v_len}` +codegen_ssa_thorin_section_not_in_row = section not found in unit's row in index -codegen_ssa_invalid_monomorphization_mask_type = invalid monomorphization of `{$name}` intrinsic: mask element type is `{$ty}`, expected `i_` +codegen_ssa_thorin_section_without_name = section without name at offset {$offset} -codegen_ssa_invalid_monomorphization_vector_argument = invalid monomorphization of `{$name}` intrinsic: vector argument `{$in_ty}`'s element type `{$in_elem}`, expected integer element type +codegen_ssa_thorin_str_at_offset = read string at offset {$offset} of `.debug_str.dwo` section -codegen_ssa_invalid_monomorphization_cannot_return = invalid monomorphization of `{$name}` intrinsic: cannot return `{$ret_ty}`, expected `u{$expected_int_bits}` or `[u8; {$expected_bytes}]` +codegen_ssa_thorin_top_level_die_not_unit = top-level debugging information entry is not a compilation/type unit -codegen_ssa_invalid_monomorphization_expected_element_type = invalid monomorphization of `{$name}` intrinsic: expected element type `{$expected_element}` of second argument `{$second_arg}` to be a pointer to the element type `{$in_elem}` of the first argument `{$in_ty}`, found `{$expected_element}` != `{$mutability} {$in_elem}` +codegen_ssa_thorin_unit_not_in_index = unit {$unit} from input package is not in its index -codegen_ssa_invalid_monomorphization_third_arg_element_type = invalid monomorphization of `{$name}` intrinsic: expected element type `{$expected_element}` of third argument `{$third_arg}` to be a signed integer type +codegen_ssa_thorin_unsupported_relocation = unsupported relocation for section {$section} at offset {$offset} -codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` of size `{$size}` to `{$ret_ty}` +codegen_ssa_unable_to_exe_linker = could not exec the linker `{$linker_path}` + .note = {$error} + .command_note = {$command_formatted} -codegen_ssa_invalid_monomorphization_unsupported_symbol = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` to `{$ret_ty}` +codegen_ssa_unable_to_run = unable to run `{$util}`: {$error} -codegen_ssa_invalid_monomorphization_cast_fat_pointer = invalid monomorphization of `{$name}` intrinsic: cannot cast fat pointer `{$ty}` +codegen_ssa_unable_to_run_dsymutil = unable to run `dsymutil`: {$error} -codegen_ssa_invalid_monomorphization_expected_pointer = invalid monomorphization of `{$name}` intrinsic: expected pointer, got `{$ty}` +codegen_ssa_unable_to_write_debugger_visualizer = Unable to write debugger visualizer file `{$path}`: {$error} -codegen_ssa_invalid_monomorphization_expected_usize = invalid monomorphization of `{$name}` intrinsic: expected `usize`, got `{$ty}` +codegen_ssa_unknown_archive_kind = + Don't know how to build archive of type: {$kind} -codegen_ssa_invalid_monomorphization_unsupported_cast = invalid monomorphization of `{$name}` intrinsic: unsupported cast from `{$in_ty}` with element `{$in_elem}` to `{$ret_ty}` with element `{$out_elem}` +codegen_ssa_unknown_atomic_operation = unknown atomic operation -codegen_ssa_invalid_monomorphization_unsupported_operation = invalid monomorphization of `{$name}` intrinsic: unsupported operation on `{$in_ty}` with element `{$in_elem}` +codegen_ssa_unknown_atomic_ordering = unknown ordering in atomic intrinsic -codegen_ssa_invalid_monomorphization_expected_vector_element_type = invalid monomorphization of `{$name}` intrinsic: expected element type `{$expected_element}` of vector type `{$vector_type}` to be a signed or unsigned integer type +codegen_ssa_unsupported_arch = unsupported arch `{$arch}` for os `{$os}` -codegen_ssa_invalid_no_sanitize = invalid argument for `no_sanitize` - .note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread` +codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target -codegen_ssa_invalid_link_ordinal_nargs = incorrect number of arguments to `#[link_ordinal]` - .note = the attribute requires exactly one argument +codegen_ssa_use_cargo_directive = use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname) -codegen_ssa_illegal_link_ordinal_format = illegal ordinal format in `link_ordinal` - .note = an unsuffixed integer value, e.g., `1`, is expected +codegen_ssa_version_script_write_failure = failed to write version script: {$error} -codegen_ssa_target_feature_safe_trait = `#[target_feature(..)]` cannot be applied to safe trait method - .label = cannot be applied to safe trait method - .label_def = not an `unsafe` function +codegen_ssa_visual_studio_not_installed = you may need to install Visual Studio build tools with the "C++ build tools" workload diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 8a00c42a0e8bd..a7ac728c59b02 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -5,15 +5,15 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_errors::{ErrorGuaranteed, Handler}; -use rustc_fs_util::fix_windows_verbatim_for_gcc; +use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize}; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_metadata::find_native_static_library; -use rustc_metadata::fs::{emit_wrapper_file, METADATA_FILENAME}; +use rustc_metadata::fs::{copy_to_stdout, emit_wrapper_file, METADATA_FILENAME}; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::SymbolExportKind; -use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip}; -use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, SplitDwarfKind}; +use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, OutFileName, Strip}; +use rustc_session::config::{OutputFilenames, OutputType, PrintKind, SplitDwarfKind}; use rustc_session::cstore::DllImport; use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename}; use rustc_session::search_paths::PathKind; @@ -23,7 +23,7 @@ use rustc_session::utils::NativeLibKind; use rustc_session::{filesearch, Session}; use rustc_span::symbol::Symbol; use rustc_target::spec::crt_objects::{CrtObjects, LinkSelfContainedDefault}; -use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy}; +use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, Lld, PanicStrategy}; use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo}; use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder}; @@ -68,7 +68,8 @@ pub fn link_binary<'a>( ) -> Result<(), ErrorGuaranteed> { let _timer = sess.timer("link_binary"); let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); - for &crate_type in sess.crate_types().iter() { + let mut tempfiles_for_stdout_output: Vec = Vec::new(); + for &crate_type in &codegen_results.crate_info.crate_types { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) && !output_metadata @@ -97,12 +98,15 @@ pub fn link_binary<'a>( .tempdir() .unwrap_or_else(|error| sess.emit_fatal(errors::CreateTempDir { error })); let path = MaybeTempDir::new(tmpdir, sess.opts.cg.save_temps); - let out_filename = out_filename( + let output = out_filename( sess, crate_type, outputs, codegen_results.crate_info.local_crate_name, ); + let crate_name = format!("{}", codegen_results.crate_info.local_crate_name); + let out_filename = + output.file_for_writing(outputs, OutputType::Exe, Some(crate_name.as_str())); match crate_type { CrateType::Rlib => { let _timer = sess.timer("link_rlib"); @@ -152,6 +156,17 @@ pub fn link_binary<'a>( ); } } + + if output.is_stdout() { + if output.is_tty() { + sess.emit_err(errors::BinaryOutputToTty { + shorthand: OutputType::Exe.shorthand(), + }); + } else if let Err(e) = copy_to_stdout(&out_filename) { + sess.emit_err(errors::CopyPath::new(&out_filename, output.as_path(), e)); + } + tempfiles_for_stdout_output.push(out_filename); + } } } @@ -189,6 +204,11 @@ pub fn link_binary<'a>( remove_temps_from_module(allocator_module); } + // Remove the temporary files if output goes to stdout + for temp in tempfiles_for_stdout_output { + ensure_removed(sess.diagnostic(), &temp); + } + // If no requested outputs require linking, then the object temporaries should // be kept. if !sess.opts.output_types.should_link() { @@ -576,8 +596,10 @@ fn link_staticlib<'a>( all_native_libs.extend_from_slice(&codegen_results.crate_info.used_libraries); - if sess.opts.prints.contains(&PrintRequest::NativeStaticLibs) { - print_native_static_libs(sess, &all_native_libs, &all_rust_dylibs); + for print in &sess.opts.prints { + if print.kind == PrintKind::NativeStaticLibs { + print_native_static_libs(sess, &print.out, &all_native_libs, &all_rust_dylibs); + } } Ok(()) @@ -724,8 +746,11 @@ fn link_natively<'a>( cmd.env_remove(k.as_ref()); } - if sess.opts.prints.contains(&PrintRequest::LinkArgs) { - println!("{:?}", &cmd); + for print in &sess.opts.prints { + if print.kind == PrintKind::LinkArgs { + let content = format!("{cmd:?}"); + print.out.overwrite(&content, sess); + } } // May have not found libraries in the right formats. @@ -893,7 +918,7 @@ fn link_natively<'a>( linker_path: &linker_path, exit_status: prog.status, command: &cmd, - escaped_output: &escaped_output, + escaped_output, }; sess.diagnostic().emit_err(err); // If MSVC's `link.exe` was expected but the return code @@ -1188,6 +1213,9 @@ fn add_sanitizer_libraries(sess: &Session, crate_type: CrateType, linker: &mut d if sanitizer.contains(SanitizerSet::HWADDRESS) { link_sanitizer_runtime(sess, linker, "hwasan"); } + if sanitizer.contains(SanitizerSet::SAFESTACK) { + link_sanitizer_runtime(sess, linker, "safestack"); + } } fn link_sanitizer_runtime(sess: &Session, linker: &mut dyn Linker, name: &str) { @@ -1208,22 +1236,21 @@ fn link_sanitizer_runtime(sess: &Session, linker: &mut dyn Linker, name: &str) { } } - let channel = option_env!("CFG_RELEASE_CHANNEL") - .map(|channel| format!("-{}", channel)) - .unwrap_or_default(); + let channel = + option_env!("CFG_RELEASE_CHANNEL").map(|channel| format!("-{channel}")).unwrap_or_default(); if sess.target.is_like_osx { // 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 // PR #41352 for details). - let filename = format!("rustc{}_rt.{}", channel, name); + let filename = format!("rustc{channel}_rt.{name}"); let path = find_sanitizer_runtime(&sess, &filename); let rpath = path.to_str().expect("non-utf8 component in path"); linker.args(&["-Wl,-rpath", "-Xlinker", rpath]); linker.link_dylib(&filename, false, true); } else { - let filename = format!("librustc{}_rt.{}.a", channel, name); + let filename = format!("librustc{channel}_rt.{name}.a"); let path = find_sanitizer_runtime(&sess, &filename).join(&filename); linker.link_whole_rlib(&path); } @@ -1299,44 +1326,7 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) { let stem = linker.file_stem().and_then(|stem| stem.to_str()).unwrap_or_else(|| { sess.emit_fatal(errors::LinkerFileStem); }); - - // Remove any version postfix. - let stem = stem - .rsplit_once('-') - .and_then(|(lhs, rhs)| rhs.chars().all(char::is_numeric).then_some(lhs)) - .unwrap_or(stem); - - // GCC/Clang can have an optional target prefix. - let flavor = if stem == "emcc" { - LinkerFlavor::EmCc - } else if stem == "gcc" - || stem.ends_with("-gcc") - || stem == "g++" - || stem.ends_with("-g++") - || stem == "clang" - || stem.ends_with("-clang") - || stem == "clang++" - || stem.ends_with("-clang++") - { - LinkerFlavor::from_cli(LinkerFlavorCli::Gcc, &sess.target) - } else if stem == "wasm-ld" || stem.ends_with("-wasm-ld") { - LinkerFlavor::WasmLld(Cc::No) - } else if stem == "ld" || stem.ends_with("-ld") { - LinkerFlavor::from_cli(LinkerFlavorCli::Ld, &sess.target) - } else if stem == "ld.lld" { - LinkerFlavor::Gnu(Cc::No, Lld::Yes) - } else if stem == "link" { - LinkerFlavor::Msvc(Lld::No) - } else if stem == "lld-link" { - LinkerFlavor::Msvc(Lld::Yes) - } else if stem == "lld" || stem == "rust-lld" { - let lld_flavor = sess.target.linker_flavor.lld_flavor(); - LinkerFlavor::from_cli(LinkerFlavorCli::Lld(lld_flavor), &sess.target) - } else { - // fall back to the value in the target spec - sess.target.linker_flavor - }; - + let flavor = sess.target.linker_flavor.with_linker_hints(stem); Some((linker, flavor)) } (None, None) => None, @@ -1346,7 +1336,7 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) { // linker and linker flavor specified via command line have precedence over what the target // specification specifies let linker_flavor = - sess.opts.cg.linker_flavor.map(|flavor| LinkerFlavor::from_cli(flavor, &sess.target)); + sess.opts.cg.linker_flavor.map(|flavor| sess.target.linker_flavor.with_cli_hints(flavor)); if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), linker_flavor) { return ret; } @@ -1400,12 +1390,18 @@ enum RlibFlavor { fn print_native_static_libs( sess: &Session, + out: &OutFileName, all_native_libs: &[NativeLib], all_rust_dylibs: &[&Path], ) { let mut lib_args: Vec<_> = all_native_libs .iter() .filter(|l| relevant_lib(sess, l)) + // Deduplication of successive repeated libraries, see rust-lang/rust#113209 + // + // note: we don't use PartialEq/Eq because NativeLib transitively depends on local + // elements like spans, which we don't care about and would make the deduplication impossible + .dedup_by(|l1, l2| l1.name == l2.name && l1.kind == l2.kind && l1.verbatim == l2.verbatim) .filter_map(|lib| { let name = lib.name; match lib.kind { @@ -1418,12 +1414,12 @@ fn print_native_static_libs( } else if sess.target.linker_flavor.is_gnu() { Some(format!("-l{}{}", if verbatim { ":" } else { "" }, name)) } else { - Some(format!("-l{}", name)) + Some(format!("-l{name}")) } } NativeLibKind::Framework { .. } => { // ld-only syntax, since there are no frameworks in MSVC - Some(format!("-framework {}", name)) + Some(format!("-framework {name}")) } // These are included, no need to print them NativeLibKind::Static { bundle: None | Some(true), .. } @@ -1460,19 +1456,30 @@ fn print_native_static_libs( // `foo.lib` file if the dll doesn't actually export any symbols, so we // check to see if the file is there and just omit linking to it if it's // not present. - let name = format!("{}.dll.lib", lib); + let name = format!("{lib}.dll.lib"); if path.join(&name).exists() { lib_args.push(name); } } else { - lib_args.push(format!("-l{}", lib)); + lib_args.push(format!("-l{lib}")); } } - if !lib_args.is_empty() { - sess.emit_note(errors::StaticLibraryNativeArtifacts); - // Prefix for greppability - // Note: This must not be translated as tools are allowed to depend on this exact string. - sess.note_without_error(format!("native-static-libs: {}", &lib_args.join(" "))); + + match out { + OutFileName::Real(path) => { + out.overwrite(&lib_args.join(" "), sess); + if !lib_args.is_empty() { + sess.emit_note(errors::StaticLibraryNativeArtifactsToFile { path }); + } + } + OutFileName::Stdout => { + if !lib_args.is_empty() { + sess.emit_note(errors::StaticLibraryNativeArtifacts); + // Prefix for greppability + // Note: This must not be translated as tools are allowed to depend on this exact string. + sess.note_without_error(format!("native-static-libs: {}", &lib_args.join(" "))); + } + } } } @@ -1620,8 +1627,8 @@ fn exec_linker( write!(f, "\"")?; for c in self.arg.chars() { match c { - '"' => write!(f, "\\{}", c)?, - c => write!(f, "{}", c)?, + '"' => write!(f, "\\{c}")?, + c => write!(f, "{c}")?, } } write!(f, "\"")?; @@ -1638,8 +1645,8 @@ fn exec_linker( // ensure the line is interpreted as one whole argument. for c in self.arg.chars() { match c { - '\\' | ' ' => write!(f, "\\{}", c)?, - c => write!(f, "{}", c)?, + '\\' | ' ' => write!(f, "\\{c}")?, + c => write!(f, "{c}")?, } } } @@ -1702,7 +1709,7 @@ fn detect_self_contained_mingw(sess: &Session) -> bool { /// instead of being found somewhere on the host system. /// We only provide such support for a very limited number of targets. fn self_contained(sess: &Session, crate_type: CrateType) -> bool { - if let Some(self_contained) = sess.opts.cg.link_self_contained { + if let Some(self_contained) = sess.opts.cg.link_self_contained.explicitly_set { if sess.target.link_self_contained == LinkSelfContainedDefault::False { sess.emit_err(errors::UnsupportedLinkSelfContained); } @@ -2131,7 +2138,14 @@ fn linker_with_args<'a>( cmd.add_as_needed(); // Local native libraries of all kinds. - add_local_native_libraries(cmd, sess, archive_builder_builder, codegen_results, tmpdir); + add_local_native_libraries( + cmd, + sess, + archive_builder_builder, + codegen_results, + tmpdir, + link_output_kind, + ); // Upstream rust crates and their non-dynamic native libraries. add_upstream_rust_crates( @@ -2141,10 +2155,18 @@ fn linker_with_args<'a>( codegen_results, crate_type, tmpdir, + link_output_kind, ); // Dynamic native libraries from upstream crates. - add_upstream_native_libraries(cmd, sess, archive_builder_builder, codegen_results, tmpdir); + add_upstream_native_libraries( + cmd, + sess, + archive_builder_builder, + codegen_results, + tmpdir, + link_output_kind, + ); // Link with the import library generated for any raw-dylib functions. for (raw_dylib_name, raw_dylib_imports) in @@ -2245,7 +2267,8 @@ fn add_order_independent_options( out_filename: &Path, tmpdir: &Path, ) { - add_gcc_ld_path(cmd, sess, flavor); + // Take care of the flavors and CLI options requesting the `lld` linker. + add_lld_args(cmd, sess, flavor); add_apple_sdk(cmd, sess, flavor); @@ -2260,7 +2283,7 @@ fn add_order_independent_options( } else { "" }; - cmd.arg(format!("--dynamic-linker={}ld.so.1", prefix)); + cmd.arg(format!("--dynamic-linker={prefix}ld.so.1")); } if sess.target.eh_frame_header { @@ -2290,11 +2313,13 @@ fn add_order_independent_options( } else if flavor == LinkerFlavor::Bpf { cmd.arg("--cpu"); cmd.arg(&codegen_results.crate_info.target_cpu); - cmd.arg("--cpu-features"); - cmd.arg(match &sess.opts.cg.target_feature { - feat if !feat.is_empty() => feat.as_ref(), - _ => sess.target.options.features.as_ref(), - }); + if let Some(feat) = [sess.opts.cg.target_feature.as_str(), &sess.target.options.features] + .into_iter() + .find(|feat| !feat.is_empty()) + { + cmd.arg("--cpu-features"); + cmd.arg(feat); + } } cmd.linker_plugin_lto(); @@ -2399,6 +2424,7 @@ fn add_native_libs_from_crate( cnum: CrateNum, link_static: bool, link_dynamic: bool, + link_output_kind: LinkOutputKind, ) { if !sess.opts.unstable_opts.link_native_libraries { // If `-Zlink-native-libraries=false` is set, then the assumption is that an @@ -2478,8 +2504,16 @@ fn add_native_libs_from_crate( } } NativeLibKind::Unspecified => { - if link_dynamic { - cmd.link_dylib(name, verbatim, true); + // If we are generating a static binary, prefer static library when the + // link kind is unspecified. + if !link_output_kind.can_link_dylib() && !sess.target.crt_static_allows_dylibs { + if link_static { + cmd.link_staticlib(name, verbatim) + } + } else { + if link_dynamic { + cmd.link_dylib(name, verbatim, true); + } } } NativeLibKind::Framework { as_needed } => { @@ -2506,6 +2540,7 @@ fn add_local_native_libraries( archive_builder_builder: &dyn ArchiveBuilderBuilder, codegen_results: &CodegenResults, tmpdir: &Path, + link_output_kind: LinkOutputKind, ) { if sess.opts.unstable_opts.link_native_libraries { // User-supplied library search paths (-L on the command line). These are the same paths @@ -2535,6 +2570,7 @@ fn add_local_native_libraries( LOCAL_CRATE, link_static, link_dynamic, + link_output_kind, ); } @@ -2545,6 +2581,7 @@ fn add_upstream_rust_crates<'a>( codegen_results: &CodegenResults, crate_type: CrateType, tmpdir: &Path, + link_output_kind: LinkOutputKind, ) { // All of the heavy lifting has previously been accomplished by the // dependency_format module of the compiler. This is just crawling the @@ -2622,6 +2659,7 @@ fn add_upstream_rust_crates<'a>( cnum, link_static, link_dynamic, + link_output_kind, ); } } @@ -2632,6 +2670,7 @@ fn add_upstream_native_libraries( archive_builder_builder: &dyn ArchiveBuilderBuilder, codegen_results: &CodegenResults, tmpdir: &Path, + link_output_kind: LinkOutputKind, ) { let search_path = OnceCell::new(); for &cnum in &codegen_results.crate_info.used_crates { @@ -2660,10 +2699,35 @@ fn add_upstream_native_libraries( cnum, link_static, link_dynamic, + link_output_kind, ); } } +// Rehome lib paths (which exclude the library file name) that point into the sysroot lib directory +// to be relative to the sysroot directory, which may be a relative path specified by the user. +// +// If the sysroot is a relative path, and the sysroot libs are specified as an absolute path, the +// linker command line can be non-deterministic due to the paths including the current working +// directory. The linker command line needs to be deterministic since it appears inside the PDB +// file generated by the MSVC linker. See https://github.com/rust-lang/rust/issues/112586. +// +// The returned path will always have `fix_windows_verbatim_for_gcc()` applied to it. +fn rehome_sysroot_lib_dir<'a>(sess: &'a Session, lib_dir: &Path) -> PathBuf { + let sysroot_lib_path = sess.target_filesearch(PathKind::All).get_lib_path(); + let canonical_sysroot_lib_path = + { try_canonicalize(&sysroot_lib_path).unwrap_or_else(|_| sysroot_lib_path.clone()) }; + + let canonical_lib_dir = try_canonicalize(lib_dir).unwrap_or_else(|_| lib_dir.to_path_buf()); + if canonical_lib_dir == canonical_sysroot_lib_path { + // This path, returned by `target_filesearch().get_lib_path()`, has + // already had `fix_windows_verbatim_for_gcc()` applied if needed. + sysroot_lib_path + } else { + fix_windows_verbatim_for_gcc(&lib_dir) + } +} + // Adds the static "rlib" versions of all crates to the command line. // There's a bit of magic which happens here specifically related to LTO, // namely that we remove upstream object files. @@ -2695,7 +2759,13 @@ fn add_static_crate<'a>( let cratepath = &src.rlib.as_ref().unwrap().0; let mut link_upstream = |path: &Path| { - cmd.link_rlib(&fix_windows_verbatim_for_gcc(path)); + let rlib_path = if let Some(dir) = path.parent() { + let file_name = path.file_name().expect("rlib path has no file name path component"); + rehome_sysroot_lib_dir(sess, &dir).join(file_name) + } else { + fix_windows_verbatim_for_gcc(path) + }; + cmd.link_rlib(&rlib_path); }; if !are_upstream_rust_objects_already_included(sess) @@ -2764,7 +2834,7 @@ fn add_dynamic_crate(cmd: &mut dyn Linker, sess: &Session, cratepath: &Path) { // what its name is let parent = cratepath.parent(); if let Some(dir) = parent { - cmd.include_path(&fix_windows_verbatim_for_gcc(dir)); + cmd.include_path(&rehome_sysroot_lib_dir(sess, dir)); } let stem = cratepath.file_stem().unwrap().to_str().unwrap(); // Convert library file-stem into a cc -l argument. @@ -2900,55 +2970,66 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result { - // Implement the "self-contained" part of -Zgcc-ld - // by adding rustc distribution directories to the tool search path. - for path in sess.get_tools_search_paths(false) { - cmd.arg({ - let mut arg = OsString::from("-B"); - arg.push(path.join("gcc-ld")); - arg - }); - } - // Implement the "linker flavor" part of -Zgcc-ld - // by asking cc to use some kind of lld. - cmd.arg("-fuse-ld=lld"); - - if !flavor.is_gnu() { - // Tell clang to use a non-default LLD flavor. - // Gcc doesn't understand the target option, but we currently assume - // that gcc is not used for Apple and Wasm targets (#97402). - // - // Note that we don't want to do that by default on macOS: e.g. passing a - // 10.7 target to LLVM works, but not to recent versions of clang/macOS, as - // shown in issue #101653 and the discussion in PR #101792. - // - // It could be required in some cases of cross-compiling with - // `-Zgcc-ld=lld`, but this is generally unspecified, and we don't know - // which specific versions of clang, macOS SDK, host and target OS - // combinations impact us here. - // - // So we do a simple first-approximation until we know more of what the - // Apple targets require (and which would be handled prior to hitting this - // `-Zgcc-ld=lld` codepath anyway), but the expectation is that until then - // this should be manually passed if needed. We specify the target when - // targeting a different linker flavor on macOS, and that's also always - // the case when targeting WASM. - if sess.target.linker_flavor != sess.host.linker_flavor { - cmd.arg(format!("--target={}", sess.target.llvm_target)); - } - } - } - } - } else { - sess.emit_fatal(errors::OptionGccOnly); +/// When using the linker flavors opting in to `lld`, or the unstable `-Zgcc-ld=lld` flag, add the +/// necessary paths and arguments to invoke it: +/// - when the self-contained linker flag is active: the build of `lld` distributed with rustc, +/// - or any `lld` available to `cc`. +fn add_lld_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) { + let unstable_use_lld = sess.opts.unstable_opts.gcc_ld.is_some(); + debug!("add_lld_args requested, flavor: '{flavor:?}', `-Zgcc-ld=lld`: {unstable_use_lld}"); + + // Sanity check: using the old unstable `-Zgcc-ld=lld` option requires a `cc`-using flavor. + let flavor_uses_cc = flavor.uses_cc(); + if unstable_use_lld && !flavor_uses_cc { + sess.emit_fatal(errors::OptionGccOnly); + } + + // If the flavor doesn't use a C/C++ compiler to invoke the linker, or doesn't opt in to `lld`, + // we don't need to do anything. + let use_lld = flavor.uses_lld() || unstable_use_lld; + if !flavor_uses_cc || !use_lld { + return; + } + + // 1. Implement the "self-contained" part of this feature by adding rustc distribution + // directories to the tool's search path. + let self_contained_linker = sess.opts.cg.link_self_contained.linker() || unstable_use_lld; + if self_contained_linker { + for path in sess.get_tools_search_paths(false) { + cmd.arg({ + let mut arg = OsString::from("-B"); + arg.push(path.join("gcc-ld")); + arg + }); + } + } + + // 2. Implement the "linker flavor" part of this feature by asking `cc` to use some kind of + // `lld` as the linker. + cmd.arg("-fuse-ld=lld"); + + if !flavor.is_gnu() { + // Tell clang to use a non-default LLD flavor. + // Gcc doesn't understand the target option, but we currently assume + // that gcc is not used for Apple and Wasm targets (#97402). + // + // Note that we don't want to do that by default on macOS: e.g. passing a + // 10.7 target to LLVM works, but not to recent versions of clang/macOS, as + // shown in issue #101653 and the discussion in PR #101792. + // + // It could be required in some cases of cross-compiling with + // `-Zgcc-ld=lld`, but this is generally unspecified, and we don't know + // which specific versions of clang, macOS SDK, host and target OS + // combinations impact us here. + // + // So we do a simple first-approximation until we know more of what the + // Apple targets require (and which would be handled prior to hitting this + // `-Zgcc-ld=lld` codepath anyway), but the expectation is that until then + // this should be manually passed if needed. We specify the target when + // targeting a different linker flavor on macOS, and that's also always + // the case when targeting WASM. + if sess.target.linker_flavor != sess.host.linker_flavor { + cmd.arg(format!("--target={}", sess.target.llvm_target)); } } } diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index cd56f85cccdea..f5642552362a9 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -13,6 +13,7 @@ use std::{env, mem, str}; use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_metadata::find_native_static_library; use rustc_middle::middle::dependency_format::Linkage; +use rustc_middle::middle::exported_symbols; use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportKind}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{self, CrateType, DebugInfo, LinkerPluginLto, Lto, OptLevel, Strip}; @@ -309,7 +310,7 @@ impl<'a> GccLinker<'a> { self.linker_arg(&format!("-plugin-opt=sample-profile={}", path.display())); }; self.linker_args(&[ - &format!("-plugin-opt={}", opt_level), + &format!("-plugin-opt={opt_level}"), &format!("-plugin-opt=mcpu={}", self.target_cpu), ]); } @@ -487,7 +488,7 @@ impl<'a> Linker for GccLinker<'a> { fn link_rust_dylib(&mut self, lib: &str, _path: &Path) { self.hint_dynamic(); - self.cmd.arg(format!("-l{}", lib)); + self.cmd.arg(format!("-l{lib}")); } fn link_framework(&mut self, framework: &str, as_needed: bool) { @@ -659,8 +660,6 @@ impl<'a> Linker for GccLinker<'a> { return; } - // FIXME(#99978) hide #[no_mangle] symbols for proc-macros - let is_windows = self.sess.target.is_like_windows; let path = tmpdir.join(if is_windows { "list.def" } else { "list" }); @@ -671,8 +670,8 @@ impl<'a> Linker for GccLinker<'a> { let res: io::Result<()> = try { let mut f = BufWriter::new(File::create(&path)?); for sym in symbols { - debug!(" _{}", sym); - writeln!(f, "_{}", sym)?; + debug!(" _{sym}"); + writeln!(f, "_{sym}")?; } }; if let Err(error) = res { @@ -686,8 +685,8 @@ impl<'a> Linker for GccLinker<'a> { // because LD doesn't like when it's empty writeln!(f, "EXPORTS")?; for symbol in symbols { - debug!(" _{}", symbol); - writeln!(f, " {}", symbol)?; + debug!(" _{symbol}"); + writeln!(f, " {symbol}")?; } }; if let Err(error) = res { @@ -701,8 +700,8 @@ impl<'a> Linker for GccLinker<'a> { if !symbols.is_empty() { writeln!(f, " global:")?; for sym in symbols { - debug!(" {};", sym); - writeln!(f, " {};", sym)?; + debug!(" {sym};"); + writeln!(f, " {sym};")?; } } writeln!(f, "\n local:\n *;\n}};")?; @@ -837,7 +836,7 @@ impl<'a> Linker for MsvcLinker<'a> { // `foo.lib` file if the dll doesn't actually export any symbols, so we // check to see if the file is there and just omit linking to it if it's // not present. - let name = format!("{}.dll.lib", lib); + let name = format!("{lib}.dll.lib"); if path.join(&name).exists() { self.cmd.arg(name); } @@ -977,8 +976,8 @@ impl<'a> Linker for MsvcLinker<'a> { writeln!(f, "LIBRARY")?; writeln!(f, "EXPORTS")?; for symbol in symbols { - debug!(" _{}", symbol); - writeln!(f, " {}", symbol)?; + debug!(" _{symbol}"); + writeln!(f, " {symbol}")?; } }; if let Err(error) = res { @@ -992,7 +991,7 @@ impl<'a> Linker for MsvcLinker<'a> { fn subsystem(&mut self, subsystem: &str) { // Note that previous passes of the compiler validated this subsystem, // so we just blindly pass it to the linker. - self.cmd.arg(&format!("/SUBSYSTEM:{}", subsystem)); + self.cmd.arg(&format!("/SUBSYSTEM:{subsystem}")); // Windows has two subsystems we're interested in right now, the console // and windows subsystems. These both implicitly have different entry @@ -1147,7 +1146,7 @@ impl<'a> Linker for EmLinker<'a> { &symbols.iter().map(|sym| "_".to_owned() + sym).collect::>(), ) .unwrap(); - debug!("{}", encoded); + debug!("{encoded}"); arg.push(encoded); @@ -1177,7 +1176,7 @@ impl<'a> WasmLd<'a> { // the one linear memory as `shared` // // * `--max-memory=1G` - when specifying a shared memory this must also - // be specified. We conservatively choose 1GB but users should be able + // be specified. We conservatively choose 4GB but users should be able // to override this with `-C link-arg`. // // * `--import-memory` - it doesn't make much sense for memory to be @@ -1190,7 +1189,11 @@ impl<'a> WasmLd<'a> { // symbols are how the TLS segments are initialized and configured. if sess.target_features.contains(&sym::atomics) { cmd.arg("--shared-memory"); - cmd.arg("--max-memory=1073741824"); + if sess.target.arch == "wasm64" { + cmd.arg("--max-memory=1099511627776"); + } else { + cmd.arg("--max-memory=4294967296"); + } cmd.arg("--import-memory"); if sess.target.os == "unknown" { cmd.arg("--export=__wasm_init_tls"); @@ -1324,10 +1327,13 @@ impl<'a> Linker for WasmLd<'a> { // symbols explicitly passed via the `--export` flags above and hides all // others. Various bits and pieces of wasm32-unknown-unknown tooling use // this, so be sure these symbols make their way out of the linker as well. - if self.sess.target.os == "unknown" { + + // FIXME: evaluate if check + // if self.sess.target.os == "unknown" { self.cmd.arg("--export=__heap_base"); + self.cmd.arg("--export=__stack_pointer"); self.cmd.arg("--export=__data_end"); - } + //} } fn subsystem(&mut self, _subsystem: &str) {} @@ -1350,7 +1356,7 @@ impl<'a> Linker for L4Bender<'a> { } fn link_staticlib(&mut self, lib: &str, _verbatim: bool) { self.hint_static(); - self.cmd.arg(format!("-PC{}", lib)); + self.cmd.arg(format!("-PC{lib}")); } fn link_rlib(&mut self, lib: &Path) { self.hint_static(); @@ -1399,7 +1405,7 @@ impl<'a> Linker for L4Bender<'a> { fn link_whole_staticlib(&mut self, lib: &str, _verbatim: bool, _search_path: &[PathBuf]) { self.hint_static(); - self.cmd.arg("--whole-archive").arg(format!("-l{}", lib)); + self.cmd.arg("--whole-archive").arg(format!("-l{lib}")); self.cmd.arg("--no-whole-archive"); } @@ -1453,7 +1459,7 @@ impl<'a> Linker for L4Bender<'a> { } fn subsystem(&mut self, subsystem: &str) { - self.cmd.arg(&format!("--subsystem {}", subsystem)); + self.cmd.arg(&format!("--subsystem {subsystem}")); } fn reset_per_library_state(&mut self) { @@ -1518,12 +1524,12 @@ impl<'a> AixLinker<'a> { impl<'a> Linker for AixLinker<'a> { fn link_dylib(&mut self, lib: &str, _verbatim: bool, _as_needed: bool) { self.hint_dynamic(); - self.cmd.arg(format!("-l{}", lib)); + self.cmd.arg(format!("-l{lib}")); } fn link_staticlib(&mut self, lib: &str, _verbatim: bool) { self.hint_static(); - self.cmd.arg(format!("-l{}", lib)); + self.cmd.arg(format!("-l{lib}")); } fn link_rlib(&mut self, lib: &Path) { @@ -1573,7 +1579,7 @@ impl<'a> Linker for AixLinker<'a> { fn link_rust_dylib(&mut self, lib: &str, _: &Path) { self.hint_dynamic(); - self.cmd.arg(format!("-l{}", lib)); + self.cmd.arg(format!("-l{lib}")); } fn link_framework(&mut self, _framework: &str, _as_needed: bool) { @@ -1626,12 +1632,12 @@ impl<'a> Linker for AixLinker<'a> { let mut f = BufWriter::new(File::create(&path)?); // FIXME: use llvm-nm to generate export list. for symbol in symbols { - debug!(" _{}", symbol); - writeln!(f, " {}", symbol)?; + debug!(" _{symbol}"); + writeln!(f, " {symbol}")?; } }; if let Err(e) = res { - self.sess.fatal(format!("failed to write export file: {}", e)); + self.sess.fatal(format!("failed to write export file: {e}")); } self.cmd.arg(format!("-bE:{}", path.to_str().unwrap())); } @@ -1679,8 +1685,15 @@ pub(crate) fn exported_symbols(tcx: TyCtxt<'_>, crate_type: CrateType) -> Vec, crate_type: CrateType) -> Vec { + let mut symbols = Vec::new(); let export_threshold = symbol_export::crates_export_threshold(&[crate_type]); for_each_exported_symbols_include_dep(tcx, crate_type, |symbol, info, cnum| { if info.level.is_below_threshold(export_threshold) { @@ -1691,6 +1704,19 @@ pub(crate) fn exported_symbols(tcx: TyCtxt<'_>, crate_type: CrateType) -> Vec) -> Vec { + // `exported_symbols` will be empty when !should_codegen. + if !tcx.sess.opts.output_types.should_codegen() { + return Vec::new(); + } + + let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); + let proc_macro_decls_name = tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id); + let metadata_symbol_name = exported_symbols::metadata_symbol_name(tcx); + + vec![proc_macro_decls_name, metadata_symbol_name] +} + pub(crate) fn linked_symbols( tcx: TyCtxt<'_>, crate_type: CrateType, @@ -1908,7 +1934,7 @@ impl<'a> Linker for BpfLinker<'a> { let res: io::Result<()> = try { let mut f = BufWriter::new(File::create(&path)?); for sym in symbols { - writeln!(f, "{}", sym)?; + writeln!(f, "{sym}")?; } }; if let Err(error) = res { diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 8bf84772f0869..4c8547407531d 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -6,21 +6,19 @@ use std::path::Path; use object::write::{self, StandardSegment, Symbol, SymbolSection}; use object::{ - elf, pe, Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, - SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, + elf, pe, xcoff, Architecture, BinaryFormat, Endianness, FileFlags, Object, ObjectSection, + ObjectSymbol, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, }; -use snap::write::FrameEncoder; - -use object::elf::NT_GNU_PROPERTY_TYPE_0; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::owned_slice::{try_slice_owned, OwnedSlice}; use rustc_metadata::fs::METADATA_FILENAME; use rustc_metadata::EncodedMetadata; use rustc_session::cstore::MetadataLoader; use rustc_session::Session; +use rustc_span::sym; use rustc_target::abi::Endian; -use rustc_target::spec::{RelocModel, Target}; +use rustc_target::spec::{ef_avr_arch, RelocModel, Target}; /// The default metadata loader. This is used by cg_llvm and cg_clif. /// @@ -35,6 +33,8 @@ use rustc_target::spec::{RelocModel, Target}; #[derive(Debug)] pub struct DefaultMetadataLoader; +static AIX_METADATA_SYMBOL_NAME: &'static str = "__aix_rust_metadata"; + fn load_metadata_with( path: &Path, f: impl for<'a> FnOnce(&'a [u8]) -> Result<&'a [u8], String>, @@ -48,7 +48,7 @@ fn load_metadata_with( } impl MetadataLoader for DefaultMetadataLoader { - fn get_rlib_metadata(&self, _target: &Target, path: &Path) -> Result { + fn get_rlib_metadata(&self, target: &Target, path: &Path) -> Result { load_metadata_with(path, |data| { let archive = object::read::archive::ArchiveFile::parse(&*data) .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; @@ -60,7 +60,11 @@ impl MetadataLoader for DefaultMetadataLoader { let data = entry .data(data) .map_err(|e| format!("failed to parse rlib '{}': {}", path.display(), e))?; - return search_for_section(path, data, ".rmeta"); + if target.is_like_aix { + return get_metadata_xcoff(path, data); + } else { + return search_for_section(path, data, ".rmeta"); + } } } @@ -68,8 +72,12 @@ impl MetadataLoader for DefaultMetadataLoader { }) } - fn get_dylib_metadata(&self, _target: &Target, path: &Path) -> Result { - load_metadata_with(path, |data| search_for_section(path, data, ".rustc")) + fn get_dylib_metadata(&self, target: &Target, path: &Path) -> Result { + if target.is_like_aix { + load_metadata_with(path, |data| get_metadata_xcoff(path, data)) + } else { + load_metadata_with(path, |data| search_for_section(path, data, ".rustc")) + } } } @@ -114,7 +122,7 @@ fn add_gnu_property_note( let mut data: Vec = Vec::new(); let n_namsz: u32 = 4; // Size of the n_name field let n_descsz: u32 = 16; // Size of the n_desc field - let n_type: u32 = NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor + let n_type: u32 = object::elf::NT_GNU_PROPERTY_TYPE_0; // Type of note descriptor let header_values = [n_namsz, n_descsz, n_type]; header_values.iter().for_each(|v| { data.extend_from_slice(&match endianness { @@ -124,8 +132,8 @@ fn add_gnu_property_note( }); data.extend_from_slice(b"GNU\0"); // Owner of the program property note let pr_type: u32 = match architecture { - Architecture::X86_64 => 0xc0000002, - Architecture::Aarch64 => 0xc0000000, + Architecture::X86_64 => object::elf::GNU_PROPERTY_X86_FEATURE_1_AND, + Architecture::Aarch64 => object::elf::GNU_PROPERTY_AARCH64_FEATURE_1_AND, _ => unreachable!(), }; let pr_datasz: u32 = 4; //size of the pr_data field @@ -141,6 +149,32 @@ fn add_gnu_property_note( file.append_section_data(section, &data, 8); } +pub(super) fn get_metadata_xcoff<'a>(path: &Path, data: &'a [u8]) -> Result<&'a [u8], String> { + let Ok(file) = object::File::parse(data) else { + return Ok(data); + }; + let info_data = search_for_section(path, data, ".info")?; + if let Some(metadata_symbol) = + file.symbols().find(|sym| sym.name() == Ok(AIX_METADATA_SYMBOL_NAME)) + { + let offset = metadata_symbol.address() as usize; + if offset < 4 { + return Err(format!("Invalid metadata symbol offset: {offset}")); + } + // The offset specifies the location of rustc metadata in the comment section. + // The metadata is preceded by a 4-byte length field. + let len = u32::from_be_bytes(info_data[(offset - 4)..offset].try_into().unwrap()) as usize; + if offset + len > (info_data.len() as usize) { + return Err(format!( + "Metadata at offset {offset} with size {len} is beyond .info section" + )); + } + return Ok(&info_data[offset..(offset + len)]); + } else { + return Err(format!("Unable to find symbol {AIX_METADATA_SYMBOL_NAME}")); + }; +} + pub(crate) fn create_object_file(sess: &Session) -> Option> { let endianness = match sess.target.options.endian { Endian::Little => Endianness::Little, @@ -157,8 +191,8 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Architecture::I386, "s390x" => Architecture::S390x, - "mips" => Architecture::Mips, - "mips64" => Architecture::Mips64, + "mips" | "mips32r6" => Architecture::Mips, + "mips64" | "mips64r6" => Architecture::Mips64, "x86_64" => { if sess.target.pointer_width == 32 { Architecture::X86_64_X32 @@ -176,6 +210,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Architecture::Hexagon, "bpf" => Architecture::Bpf, "loongarch64" => Architecture::LoongArch64, + "csky" => Architecture::Csky, // Unsupported architecture. _ => return None, }; @@ -183,11 +218,18 @@ pub(crate) fn create_object_file(sess: &Session) -> Option { let arch = match sess.target.options.cpu.as_ref() { @@ -199,8 +241,16 @@ pub(crate) fn create_object_file(sess: &Session) -> Option elf::EF_MIPS_ARCH_32R6, _ => elf::EF_MIPS_ARCH_32R2, }; - // The only ABI LLVM supports for 32-bit MIPS CPUs is o32. - let mut e_flags = elf::EF_MIPS_CPIC | elf::EF_MIPS_ABI_O32 | arch; + + let mut e_flags = elf::EF_MIPS_CPIC | arch; + + // If the ABI is explicitly given, use it or default to O32. + match sess.target.options.llvm_abiname.to_lowercase().as_str() { + "n32" => e_flags |= elf::EF_MIPS_ABI2, + "o32" => e_flags |= elf::EF_MIPS_ABI_O32, + _ => e_flags |= elf::EF_MIPS_ABI_O32, + }; + if sess.target.options.relocation_model != RelocModel::Static { e_flags |= elf::EF_MIPS_PIC; } @@ -223,25 +273,51 @@ pub(crate) fn create_object_file(sess: &Session) -> Option { // Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc let mut e_flags: u32 = 0x0; - let features = &sess.target.options.features; + // Check if compressed is enabled - if features.contains("+c") { + // `unstable_target_features` is used here because "c" is gated behind riscv_target_feature. + if sess.unstable_target_features.contains(&sym::c) { e_flags |= elf::EF_RISCV_RVC; } - // Select the appropriate floating-point ABI - if features.contains("+d") { - e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE; - } else if features.contains("+f") { - e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE; - } else { - e_flags |= elf::EF_RISCV_FLOAT_ABI_SOFT; + // Set the appropriate flag based on ABI + // This needs to match LLVM `RISCVELFStreamer.cpp` + match &*sess.target.llvm_abiname { + "" | "ilp32" | "lp64" => (), + "ilp32f" | "lp64f" => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE, + "ilp32d" | "lp64d" => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE, + "ilp32e" => e_flags |= elf::EF_RISCV_RVE, + _ => bug!("unknown RISC-V ABI name"), } + e_flags } Architecture::LoongArch64 => { - // Source: https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html#_e_flags_identifies_abi_type_and_version - elf::EF_LARCH_OBJABI_V1 | elf::EF_LARCH_ABI_DOUBLE_FLOAT + // Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version + let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1; + + // Set the appropriate flag based on ABI + // This needs to match LLVM `LoongArchELFStreamer.cpp` + match &*sess.target.llvm_abiname { + "ilp32s" | "lp64s" => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT, + "ilp32f" | "lp64f" => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT, + "ilp32d" | "lp64d" => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT, + _ => bug!("unknown RISC-V ABI name"), + } + + e_flags + } + Architecture::Avr => { + // Resolve the ISA revision and set + // the appropriate EF_AVR_ARCH flag. + ef_avr_arch(&sess.target.options.cpu) + } + Architecture::Csky => { + let e_flags = match sess.target.options.abi.as_ref() { + "abiv2" => elf::EF_CSKY_ABIV2, + _ => elf::EF_CSKY_ABIV1, + }; + e_flags } _ => 0, }; @@ -258,6 +334,33 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Option { + if !target.llvm_target.ends_with("-macabi") { + return None; + } + /// 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): (u32, u32)) -> u32 { + (major << 16) | (minor << 8) + } + + let platform = object::macho::PLATFORM_MACCATALYST; + let min_os = (14, 0); + let sdk = (16, 2); + + let mut build_version = object::write::MachOBuildVersion::default(); + build_version.platform = platform; + build_version.minos = pack_version(min_os); + build_version.sdk = pack_version(sdk); + Some(build_version) +} + pub enum MetadataPosition { First, Last, @@ -319,11 +422,15 @@ pub fn create_wrapper_file( // to add a case above. return (data.to_vec(), MetadataPosition::Last); }; - let section = file.add_section( - file.segment_name(StandardSegment::Debug).to_vec(), - section_name, - SectionKind::Debug, - ); + let section = if file.format() == BinaryFormat::Xcoff { + file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug) + } else { + file.add_section( + file.segment_name(StandardSegment::Debug).to_vec(), + section_name, + SectionKind::Debug, + ) + }; match file.format() { BinaryFormat::Coff => { file.section_mut(section).flags = @@ -333,6 +440,31 @@ pub fn create_wrapper_file( file.section_mut(section).flags = SectionFlags::Elf { sh_flags: elf::SHF_EXCLUDE as u64 }; } + BinaryFormat::Xcoff => { + // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss. + file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text); + file.section_mut(section).flags = + SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 }; + + let len = data.len() as u32; + let offset = file.append_section_data(section, &len.to_be_bytes(), 1); + // Add a symbol referring to the data in .info section. + file.add_symbol(Symbol { + name: AIX_METADATA_SYMBOL_NAME.into(), + value: offset + 4, + size: 0, + kind: SymbolKind::Unknown, + scope: SymbolScope::Compilation, + weak: false, + section: SymbolSection::Section(section), + flags: SymbolFlags::Xcoff { + n_sclass: xcoff::C_INFO, + x_smtyp: xcoff::C_HIDEXT, + x_smclas: xcoff::C_HIDEXT, + containing_csect: None, + }, + }); + } _ => {} }; file.append_section_data(section, data, 1); @@ -358,17 +490,16 @@ pub fn create_compressed_metadata_file( metadata: &EncodedMetadata, symbol_name: &str, ) -> Vec { - let mut compressed = rustc_metadata::METADATA_HEADER.to_vec(); - // Our length will be backfilled once we're done writing - compressed.write_all(&[0; 4]).unwrap(); - FrameEncoder::new(&mut compressed).write_all(metadata.raw_data()).unwrap(); - let meta_len = rustc_metadata::METADATA_HEADER.len(); - let data_len = (compressed.len() - meta_len - 4) as u32; - compressed[meta_len..meta_len + 4].copy_from_slice(&data_len.to_be_bytes()); + let mut packed_metadata = rustc_metadata::METADATA_HEADER.to_vec(); + packed_metadata.write_all(&(metadata.raw_data().len() as u32).to_be_bytes()).unwrap(); + packed_metadata.extend(metadata.raw_data()); let Some(mut file) = create_object_file(sess) else { - return compressed.to_vec(); + return packed_metadata.to_vec(); }; + if file.format() == BinaryFormat::Xcoff { + return create_compressed_metadata_file_for_xcoff(file, &packed_metadata, symbol_name); + } let section = file.add_section( file.segment_name(StandardSegment::Data).to_vec(), b".rustc".to_vec(), @@ -381,14 +512,14 @@ pub fn create_compressed_metadata_file( } _ => {} }; - let offset = file.append_section_data(section, &compressed, 1); + let offset = file.append_section_data(section, &packed_metadata, 1); // For MachO and probably PE this is necessary to prevent the linker from throwing away the // .rustc section. For ELF this isn't necessary, but it also doesn't harm. file.add_symbol(Symbol { name: symbol_name.as_bytes().to_vec(), value: offset, - size: compressed.len() as u64, + size: packed_metadata.len() as u64, kind: SymbolKind::Data, scope: SymbolScope::Dynamic, weak: false, @@ -398,3 +529,61 @@ pub fn create_compressed_metadata_file( file.write().unwrap() } + +/// * Xcoff - On AIX, custom sections are merged into predefined sections, +/// so custom .rustc section is not preserved during linking. +/// For this reason, we store metadata in predefined .info section, and +/// define a symbol to reference the metadata. To preserve metadata during +/// linking on AIX, we have to +/// 1. Create an empty .text section, a empty .data section. +/// 2. Define an empty symbol named `symbol_name` inside .data section. +/// 3. Define an symbol named `AIX_METADATA_SYMBOL_NAME` referencing +/// data inside .info section. +/// From XCOFF's view, (2) creates a csect entry in the symbol table, the +/// symbol created by (3) is a info symbol for the preceding csect. Thus +/// two symbols are preserved during linking and we can use the second symbol +/// to reference the metadata. +pub fn create_compressed_metadata_file_for_xcoff( + mut file: write::Object<'_>, + data: &[u8], + symbol_name: &str, +) -> Vec { + assert!(file.format() == BinaryFormat::Xcoff); + // AIX system linker may aborts if it meets a valid XCOFF file in archive with no .text, no .data and no .bss. + file.add_section(Vec::new(), b".text".to_vec(), SectionKind::Text); + let data_section = file.add_section(Vec::new(), b".data".to_vec(), SectionKind::Data); + let section = file.add_section(Vec::new(), b".info".to_vec(), SectionKind::Debug); + file.add_file_symbol("lib.rmeta".into()); + file.section_mut(section).flags = SectionFlags::Xcoff { s_flags: xcoff::STYP_INFO as u32 }; + // Add a global symbol to data_section. + file.add_symbol(Symbol { + name: symbol_name.as_bytes().into(), + value: 0, + size: 0, + kind: SymbolKind::Data, + scope: SymbolScope::Dynamic, + weak: true, + section: SymbolSection::Section(data_section), + flags: SymbolFlags::None, + }); + let len = data.len() as u32; + let offset = file.append_section_data(section, &len.to_be_bytes(), 1); + // Add a symbol referring to the rustc metadata. + file.add_symbol(Symbol { + name: AIX_METADATA_SYMBOL_NAME.into(), + value: offset + 4, // The metadata is preceded by a 4-byte length field. + size: 0, + kind: SymbolKind::Unknown, + scope: SymbolScope::Dynamic, + weak: false, + section: SymbolSection::Section(section), + flags: SymbolFlags::Xcoff { + n_sclass: xcoff::C_INFO, + x_smtyp: xcoff::C_HIDEXT, + x_smclas: xcoff::C_HIDEXT, + containing_csect: None, + }, + }); + file.append_section_data(section, data, 1); + file.write().unwrap() +} diff --git a/compiler/rustc_codegen_ssa/src/back/rpath.rs b/compiler/rustc_codegen_ssa/src/back/rpath.rs index 0b5656c9ad127..ebf04e7a399bc 100644 --- a/compiler/rustc_codegen_ssa/src/back/rpath.rs +++ b/compiler/rustc_codegen_ssa/src/back/rpath.rs @@ -1,6 +1,7 @@ use pathdiff::diff_paths; use rustc_data_structures::fx::FxHashSet; use std::env; +use std::ffi::OsString; use std::fs; use std::path::{Path, PathBuf}; @@ -12,7 +13,7 @@ pub struct RPathConfig<'a> { pub linker_is_gnu: bool, } -pub fn get_rpath_flags(config: &mut RPathConfig<'_>) -> Vec { +pub fn get_rpath_flags(config: &mut RPathConfig<'_>) -> Vec { // No rpath on windows if !config.has_rpath { return Vec::new(); @@ -21,36 +22,38 @@ pub fn get_rpath_flags(config: &mut RPathConfig<'_>) -> Vec { debug!("preparing the RPATH!"); let rpaths = get_rpaths(config); - let mut flags = rpaths_to_flags(&rpaths); + let mut flags = rpaths_to_flags(rpaths); if config.linker_is_gnu { // Use DT_RUNPATH instead of DT_RPATH if available - flags.push("-Wl,--enable-new-dtags".to_owned()); + flags.push("-Wl,--enable-new-dtags".into()); // Set DF_ORIGIN for substitute $ORIGIN - flags.push("-Wl,-z,origin".to_owned()); + flags.push("-Wl,-z,origin".into()); } flags } -fn rpaths_to_flags(rpaths: &[String]) -> Vec { +fn rpaths_to_flags(rpaths: Vec) -> Vec { let mut ret = Vec::with_capacity(rpaths.len()); // the minimum needed capacity for rpath in rpaths { - if rpath.contains(',') { + if rpath.to_string_lossy().contains(',') { ret.push("-Wl,-rpath".into()); ret.push("-Xlinker".into()); - ret.push(rpath.clone()); + ret.push(rpath); } else { - ret.push(format!("-Wl,-rpath,{}", &(*rpath))); + let mut single_arg = OsString::from("-Wl,-rpath,"); + single_arg.push(rpath); + ret.push(single_arg); } } ret } -fn get_rpaths(config: &mut RPathConfig<'_>) -> Vec { +fn get_rpaths(config: &mut RPathConfig<'_>) -> Vec { debug!("output: {:?}", config.out_filename.display()); debug!("libs:"); for libpath in config.libs { @@ -64,18 +67,18 @@ fn get_rpaths(config: &mut RPathConfig<'_>) -> Vec { debug!("rpaths:"); for rpath in &rpaths { - debug!(" {}", rpath); + debug!(" {:?}", rpath); } // Remove duplicates minimize_rpaths(&rpaths) } -fn get_rpaths_relative_to_output(config: &mut RPathConfig<'_>) -> Vec { +fn get_rpaths_relative_to_output(config: &mut RPathConfig<'_>) -> Vec { config.libs.iter().map(|a| get_rpath_relative_to_output(config, a)).collect() } -fn get_rpath_relative_to_output(config: &mut RPathConfig<'_>, lib: &Path) -> String { +fn get_rpath_relative_to_output(config: &mut RPathConfig<'_>, lib: &Path) -> OsString { // Mac doesn't appear to support $ORIGIN let prefix = if config.is_like_osx { "@loader_path" } else { "$ORIGIN" }; @@ -86,9 +89,12 @@ fn get_rpath_relative_to_output(config: &mut RPathConfig<'_>, lib: &Path) -> Str output.pop(); // strip filename let output = fs::canonicalize(&output).unwrap_or(output); let relative = path_relative_from(&lib, &output) - .unwrap_or_else(|| panic!("couldn't create relative path from {:?} to {:?}", output, lib)); - // FIXME (#9639): This needs to handle non-utf8 paths - format!("{}/{}", prefix, relative.to_str().expect("non-utf8 component in path")) + .unwrap_or_else(|| panic!("couldn't create relative path from {output:?} to {lib:?}")); + + let mut rpath = OsString::from(prefix); + rpath.push("/"); + rpath.push(relative); + rpath } // This routine is adapted from the *old* Path's `path_relative_from` @@ -99,7 +105,7 @@ fn path_relative_from(path: &Path, base: &Path) -> Option { diff_paths(path, base) } -fn minimize_rpaths(rpaths: &[String]) -> Vec { +fn minimize_rpaths(rpaths: &[OsString]) -> Vec { let mut set = FxHashSet::default(); let mut minimized = Vec::new(); for rpath in rpaths { diff --git a/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs b/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs index 604f19144a6a4..ac2e54072c416 100644 --- a/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs +++ b/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs @@ -1,32 +1,33 @@ use super::RPathConfig; use super::{get_rpath_relative_to_output, minimize_rpaths, rpaths_to_flags}; +use std::ffi::OsString; use std::path::{Path, PathBuf}; #[test] fn test_rpaths_to_flags() { - let flags = rpaths_to_flags(&["path1".to_string(), "path2".to_string()]); + let flags = rpaths_to_flags(vec!["path1".into(), "path2".into()]); assert_eq!(flags, ["-Wl,-rpath,path1", "-Wl,-rpath,path2"]); } #[test] fn test_minimize1() { - let res = minimize_rpaths(&["rpath1".to_string(), "rpath2".to_string(), "rpath1".to_string()]); + let res = minimize_rpaths(&["rpath1".into(), "rpath2".into(), "rpath1".into()]); assert!(res == ["rpath1", "rpath2",]); } #[test] fn test_minimize2() { let res = minimize_rpaths(&[ - "1a".to_string(), - "2".to_string(), - "2".to_string(), - "1a".to_string(), - "4a".to_string(), - "1a".to_string(), - "2".to_string(), - "3".to_string(), - "4a".to_string(), - "3".to_string(), + "1a".into(), + "2".into(), + "2".into(), + "1a".into(), + "4a".into(), + "1a".into(), + "2".into(), + "3".into(), + "4a".into(), + "3".into(), ]); assert!(res == ["1a", "2", "4a", "3",]); } @@ -58,15 +59,15 @@ fn test_rpath_relative() { #[test] fn test_xlinker() { - let args = rpaths_to_flags(&["a/normal/path".to_string(), "a,comma,path".to_string()]); + let args = rpaths_to_flags(vec!["a/normal/path".into(), "a,comma,path".into()]); assert_eq!( args, vec![ - "-Wl,-rpath,a/normal/path".to_string(), - "-Wl,-rpath".to_string(), - "-Xlinker".to_string(), - "a,comma,path".to_string() + OsString::from("-Wl,-rpath,a/normal/path"), + OsString::from("-Wl,-rpath"), + OsString::from("-Xlinker"), + OsString::from("a,comma,path") ] ); } diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 14460efc1b051..8fb2ccb7e8a4c 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -2,7 +2,7 @@ use crate::base::allocator_kind_for_codegen; use std::collections::hash_map::Entry::*; -use rustc_ast::expand::allocator::ALLOCATOR_METHODS; +use rustc_ast::expand::allocator::{ALLOCATOR_METHODS, NO_ALLOC_SHIM_IS_UNSTABLE}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::DefKind; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE}; @@ -12,14 +12,14 @@ use rustc_middle::middle::exported_symbols::{ }; use rustc_middle::query::LocalCrate; use rustc_middle::query::{ExternProviders, Providers}; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; use rustc_middle::ty::Instance; use rustc_middle::ty::{self, SymbolName, TyCtxt}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; use rustc_session::config::{CrateType, OomStrategy}; use rustc_target::spec::SanitizerSet; pub fn threshold(tcx: TyCtxt<'_>) -> SymbolExportLevel { - crates_export_threshold(&tcx.sess.crate_types()) + crates_export_threshold(tcx.crate_types()) } fn crate_export_threshold(crate_type: CrateType) -> SymbolExportLevel { @@ -233,14 +233,16 @@ fn exported_symbols_provider_local( )); } + let exported_symbol = + ExportedSymbol::NoDefId(SymbolName::new(tcx, NO_ALLOC_SHIM_IS_UNSTABLE)); symbols.push(( - ExportedSymbol::NoDefId(SymbolName::new(tcx, OomStrategy::SYMBOL)), + exported_symbol, SymbolExportInfo { level: SymbolExportLevel::Rust, - kind: SymbolExportKind::Text, + kind: SymbolExportKind::Data, used: false, }, - )); + )) } if tcx.sess.instrument_coverage() || tcx.sess.opts.cg.profile_generate.enabled() { @@ -288,8 +290,8 @@ fn exported_symbols_provider_local( })); } - if tcx.sess.crate_types().contains(&CrateType::Dylib) - || tcx.sess.crate_types().contains(&CrateType::ProcMacro) + if tcx.crate_types().contains(&CrateType::Dylib) + || tcx.crate_types().contains(&CrateType::ProcMacro) { let symbol_name = metadata_symbol_name(tcx); let exported_symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, &symbol_name)); @@ -317,23 +319,23 @@ fn exported_symbols_provider_local( let (_, cgus) = tcx.collect_and_partition_mono_items(()); - for (mono_item, &(linkage, visibility)) in cgus.iter().flat_map(|cgu| cgu.items().iter()) { - if linkage != Linkage::External { + for (mono_item, data) in cgus.iter().flat_map(|cgu| cgu.items().iter()) { + if data.linkage != Linkage::External { // We can only re-use things with external linkage, otherwise // we'll get a linker error continue; } - if need_visibility && visibility == Visibility::Hidden { + if need_visibility && data.visibility == Visibility::Hidden { // If we potentially share things from Rust dylibs, they must // not be hidden continue; } match *mono_item { - MonoItem::Fn(Instance { def: InstanceDef::Item(def), substs }) => { - if substs.non_erasable_generics().next().is_some() { - let symbol = ExportedSymbol::Generic(def, substs); + MonoItem::Fn(Instance { def: InstanceDef::Item(def), args }) => { + if args.non_erasable_generics().next().is_some() { + let symbol = ExportedSymbol::Generic(def, args); symbols.push(( symbol, SymbolExportInfo { @@ -344,10 +346,10 @@ fn exported_symbols_provider_local( )); } } - MonoItem::Fn(Instance { def: InstanceDef::DropGlue(_, Some(ty)), substs }) => { + MonoItem::Fn(Instance { def: InstanceDef::DropGlue(_, Some(ty)), args }) => { // A little sanity-check debug_assert_eq!( - substs.non_erasable_generics().next(), + args.non_erasable_generics().next(), Some(GenericArgKind::Type(ty)) ); symbols.push(( @@ -375,7 +377,7 @@ fn exported_symbols_provider_local( fn upstream_monomorphizations_provider( tcx: TyCtxt<'_>, (): (), -) -> DefIdMap, CrateNum>> { +) -> DefIdMap, CrateNum>> { let cnums = tcx.crates(()); let mut instances: DefIdMap> = Default::default(); @@ -384,11 +386,11 @@ fn upstream_monomorphizations_provider( for &cnum in cnums.iter() { for (exported_symbol, _) in tcx.exported_symbols(cnum).iter() { - let (def_id, substs) = match *exported_symbol { - ExportedSymbol::Generic(def_id, substs) => (def_id, substs), + let (def_id, args) = match *exported_symbol { + ExportedSymbol::Generic(def_id, args) => (def_id, args), ExportedSymbol::DropGlue(ty) => { if let Some(drop_in_place_fn_def_id) = drop_in_place_fn_def_id { - (drop_in_place_fn_def_id, tcx.mk_substs(&[ty.into()])) + (drop_in_place_fn_def_id, tcx.mk_args(&[ty.into()])) } else { // `drop_in_place` in place does not exist, don't try // to use it. @@ -403,9 +405,9 @@ fn upstream_monomorphizations_provider( } }; - let substs_map = instances.entry(def_id).or_default(); + let args_map = instances.entry(def_id).or_default(); - match substs_map.entry(substs) { + match args_map.entry(args) { Occupied(mut e) => { // If there are multiple monomorphizations available, // we select one deterministically. @@ -427,17 +429,17 @@ fn upstream_monomorphizations_provider( fn upstream_monomorphizations_for_provider( tcx: TyCtxt<'_>, def_id: DefId, -) -> Option<&FxHashMap, CrateNum>> { +) -> Option<&FxHashMap, CrateNum>> { debug_assert!(!def_id.is_local()); tcx.upstream_monomorphizations(()).get(&def_id) } fn upstream_drop_glue_for_provider<'tcx>( tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Option { if let Some(def_id) = tcx.lang_items().drop_in_place_fn() { - tcx.upstream_monomorphizations_for(def_id).and_then(|monos| monos.get(&substs).cloned()) + tcx.upstream_monomorphizations_for(def_id).and_then(|monos| monos.get(&args).cloned()) } else { None } @@ -510,10 +512,10 @@ pub fn symbol_name_for_instance_in_crate<'tcx>( instantiating_crate, ) } - ExportedSymbol::Generic(def_id, substs) => { + ExportedSymbol::Generic(def_id, args) => { rustc_symbol_mangling::symbol_name_for_instance_in_crate( tcx, - Instance::new(def_id, substs), + Instance::new(def_id, args), instantiating_crate, ) } @@ -522,7 +524,7 @@ pub fn symbol_name_for_instance_in_crate<'tcx>( tcx, ty::Instance { def: ty::InstanceDef::ThreadLocalShim(def_id), - substs: ty::InternalSubsts::empty(), + args: ty::GenericArgs::empty(), }, instantiating_crate, ) @@ -569,7 +571,7 @@ pub fn linking_symbol_name_for_instance_in_crate<'tcx>( None } ExportedSymbol::NonGeneric(def_id) => Some(Instance::mono(tcx, def_id)), - ExportedSymbol::Generic(def_id, substs) => Some(Instance::new(def_id, substs)), + ExportedSymbol::Generic(def_id, args) => Some(Instance::new(def_id, args)), // DropGlue always use the Rust calling convention and thus follow the target's default // symbol decoration scheme. ExportedSymbol::DropGlue(..) => None, diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index c323372bda42d..f485af00bcad7 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -9,11 +9,9 @@ use crate::{ }; use jobserver::{Acquired, Client}; use rustc_ast::attr; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::memmap::Mmap; -use rustc_data_structures::profiling::SelfProfilerRef; -use rustc_data_structures::profiling::TimingGuard; -use rustc_data_structures::profiling::VerboseTimingGuard; +use rustc_data_structures::profiling::{SelfProfilerRef, VerboseTimingGuard}; use rustc_data_structures::sync::Lrc; use rustc_errors::emitter::Emitter; use rustc_errors::{translation::Translate, DiagnosticId, FatalError, Handler, Level}; @@ -23,12 +21,13 @@ use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_incremental::{ copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir, in_incr_comp_dir_sess, }; +use rustc_metadata::fs::copy_to_stdout; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::middle::exported_symbols::SymbolExportInfo; use rustc_middle::ty::TyCtxt; use rustc_session::cgu_reuse_tracker::CguReuseTracker; -use rustc_session::config::{self, CrateType, Lto, OutputFilenames, OutputType}; +use rustc_session::config::{self, CrateType, Lto, OutFileName, OutputFilenames, OutputType}; use rustc_session::config::{Passes, SwitchWithOptPath}; use rustc_session::Session; use rustc_span::source_map::SourceMap; @@ -36,6 +35,7 @@ use rustc_span::symbol::sym; use rustc_span::{BytePos, FileName, InnerSpan, Pos, Span}; use rustc_target::spec::{MergeFunctions, SanitizerSet}; +use crate::errors::ErrorCreatingRemarkDir; use std::any::Any; use std::borrow::Cow; use std::fs; @@ -123,7 +123,7 @@ pub struct ModuleConfig { impl ModuleConfig { fn new( kind: ModuleKind, - sess: &Session, + tcx: TyCtxt<'_>, no_builtins: bool, is_compiler_builtins: bool, ) -> ModuleConfig { @@ -135,6 +135,7 @@ impl ModuleConfig { }; } + let sess = tcx.sess; let opt_level_and_size = if_regular!(Some(sess.opts.optimize), None); let save_temps = sess.opts.cg.save_temps; @@ -166,7 +167,7 @@ impl ModuleConfig { // `#![no_builtins]` is assumed to not participate in LTO and // instead goes on to generate object code. EmitObj::Bitcode - } else if need_bitcode_in_object(sess) { + } else if need_bitcode_in_object(tcx) { EmitObj::ObjectCode(BitcodeSection::Full) } else { EmitObj::ObjectCode(BitcodeSection::None) @@ -321,7 +322,6 @@ pub type ExportedSymbols = FxHashMap { // Resources needed when running LTO - pub backend: B, pub prof: SelfProfilerRef, pub lto: Lto, pub save_temps: bool, @@ -339,20 +339,17 @@ pub struct CodegenContext { pub msvc_imps_needed: bool, pub is_pe_coff: bool, pub target_can_use_split_dwarf: bool, - pub target_pointer_width: u32, pub target_arch: String, - pub debuginfo: config::DebugInfo, pub split_debuginfo: rustc_target::spec::SplitDebuginfo, pub split_dwarf_kind: rustc_session::config::SplitDwarfKind, - /// Number of cgus excluding the allocator/metadata modules - pub total_cgus: usize, /// Handler to use for diagnostics produced during codegen. pub diag_emitter: SharedEmitter, /// LLVM optimizations for which we want to print remarks. pub remark: Passes, - /// Worker thread number - pub worker: usize, + /// Directory into which should the LLVM optimization remarks be written. + /// If `None`, they will be written to stderr. + pub remark_dir: Option, /// The incremental compilation session directory, or None if we are not /// compiling incrementally pub incr_comp_session_dir: Option, @@ -364,7 +361,7 @@ pub struct CodegenContext { impl CodegenContext { pub fn create_diag_handler(&self) -> Handler { - Handler::with_emitter(true, None, Box::new(self.diag_emitter.clone())) + Handler::with_emitter(Box::new(self.diag_emitter.clone())) } pub fn config(&self, kind: ModuleKind) -> &ModuleConfig { @@ -378,38 +375,39 @@ impl CodegenContext { fn generate_lto_work( cgcx: &CodegenContext, - needs_fat_lto: Vec>, + needs_fat_lto: Vec>, needs_thin_lto: Vec<(String, B::ThinBuffer)>, import_only_modules: Vec<(SerializedModule, WorkProduct)>, ) -> Vec<(WorkItem, u64)> { let _prof_timer = cgcx.prof.generic_activity("codegen_generate_lto_work"); - let (lto_modules, copy_jobs) = if !needs_fat_lto.is_empty() { + if !needs_fat_lto.is_empty() { assert!(needs_thin_lto.is_empty()); - let lto_module = + let module = B::run_fat_lto(cgcx, needs_fat_lto, import_only_modules).unwrap_or_else(|e| e.raise()); - (vec![lto_module], vec![]) + // We are adding a single work item, so the cost doesn't matter. + vec![(WorkItem::LTO(module), 0)] } else { assert!(needs_fat_lto.is_empty()); - B::run_thin_lto(cgcx, needs_thin_lto, import_only_modules).unwrap_or_else(|e| e.raise()) - }; - - lto_modules - .into_iter() - .map(|module| { - let cost = module.cost(); - (WorkItem::LTO(module), cost) - }) - .chain(copy_jobs.into_iter().map(|wp| { - ( - WorkItem::CopyPostLtoArtifacts(CachedModuleCodegen { - name: wp.cgu_name.clone(), - source: wp, - }), - 0, - ) - })) - .collect() + let (lto_modules, copy_jobs) = B::run_thin_lto(cgcx, needs_thin_lto, import_only_modules) + .unwrap_or_else(|e| e.raise()); + lto_modules + .into_iter() + .map(|module| { + let cost = module.cost(); + (WorkItem::LTO(module), cost) + }) + .chain(copy_jobs.into_iter().map(|wp| { + ( + WorkItem::CopyPostLtoArtifacts(CachedModuleCodegen { + name: wp.cgu_name.clone(), + source: wp, + }), + 0, // copying is very cheap + ) + })) + .collect() + } } pub struct CompiledModules { @@ -417,9 +415,10 @@ pub struct CompiledModules { pub allocator_module: Option, } -fn need_bitcode_in_object(sess: &Session) -> bool { +fn need_bitcode_in_object(tcx: TyCtxt<'_>) -> bool { + let sess = tcx.sess; let requested_for_rlib = sess.opts.cg.embed_bitcode - && sess.crate_types().contains(&CrateType::Rlib) + && tcx.crate_types().contains(&CrateType::Rlib) && sess.opts.output_types.contains_key(&OutputType::Exe); let forced_by_target = sess.target.forces_embed_bitcode; requested_for_rlib || forced_by_target @@ -442,7 +441,6 @@ pub fn start_async_codegen( target_cpu: String, metadata: EncodedMetadata, metadata_module: Option, - total_cgus: usize, ) -> OngoingCodegen { let (coordinator_send, coordinator_receive) = channel(); let sess = tcx.sess; @@ -454,11 +452,11 @@ pub fn start_async_codegen( let crate_info = CrateInfo::new(tcx, target_cpu); let regular_config = - ModuleConfig::new(ModuleKind::Regular, sess, no_builtins, is_compiler_builtins); + ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins, is_compiler_builtins); let metadata_config = - ModuleConfig::new(ModuleKind::Metadata, sess, no_builtins, is_compiler_builtins); + ModuleConfig::new(ModuleKind::Metadata, tcx, no_builtins, is_compiler_builtins); let allocator_config = - ModuleConfig::new(ModuleKind::Allocator, sess, no_builtins, is_compiler_builtins); + ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins, is_compiler_builtins); let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); let (codegen_worker_send, codegen_worker_receive) = channel(); @@ -470,7 +468,6 @@ pub fn start_async_codegen( shared_emitter, codegen_worker_send, coordinator_receive, - total_cgus, sess.jobserver.clone(), Arc::new(regular_config), Arc::new(metadata_config), @@ -498,8 +495,8 @@ pub fn start_async_codegen( fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( sess: &Session, compiled_modules: &CompiledModules, -) -> FxHashMap { - let mut work_products = FxHashMap::default(); +) -> FxIndexMap { + let mut work_products = FxIndexMap::default(); if sess.opts.incremental.is_none() { return work_products; @@ -535,9 +532,16 @@ fn produce_final_output_artifacts( let mut user_wants_objects = false; // Produce final compile outputs. - let copy_gracefully = |from: &Path, to: &Path| { - if let Err(e) = fs::copy(from, to) { - sess.emit_err(errors::CopyPath::new(from, to, e)); + let copy_gracefully = |from: &Path, to: &OutFileName| match to { + OutFileName::Stdout => { + if let Err(e) = copy_to_stdout(from) { + sess.emit_err(errors::CopyPath::new(from, to.as_path(), e)); + } + } + OutFileName::Real(path) => { + if let Err(e) = fs::copy(from, path) { + sess.emit_err(errors::CopyPath::new(from, path, e)); + } } }; @@ -547,7 +551,12 @@ fn produce_final_output_artifacts( // to copy `foo.0.x` to `foo.x`. let module_name = Some(&compiled_modules.modules[0].name[..]); let path = crate_output.temp_path(output_type, module_name); - copy_gracefully(&path, &crate_output.path(output_type)); + let output = crate_output.path(output_type); + if !output_type.is_text_output() && output.is_tty() { + sess.emit_err(errors::BinaryOutputToTty { shorthand: output_type.shorthand() }); + } else { + copy_gracefully(&path, &output); + } if !sess.opts.cg.save_temps && !keep_numbered { // The user just wants `foo.x`, not `foo.#module-name#.x`. ensure_removed(sess.diagnostic(), &path); @@ -633,10 +642,10 @@ fn produce_final_output_artifacts( // rlib. let needs_crate_object = crate_output.outputs.contains_key(&OutputType::Exe); - let keep_numbered_bitcode = user_wants_bitcode && sess.codegen_units() > 1; + let keep_numbered_bitcode = user_wants_bitcode && sess.codegen_units().as_usize() > 1; let keep_numbered_objects = - needs_crate_object || (user_wants_objects && sess.codegen_units() > 1); + needs_crate_object || (user_wants_objects && sess.codegen_units().as_usize() > 1); for module in compiled_modules.modules.iter() { if let Some(ref path) = module.object { @@ -674,7 +683,7 @@ fn produce_final_output_artifacts( // These are used in linking steps and will be cleaned up afterward. } -pub enum WorkItem { +pub(crate) enum WorkItem { /// Optimize a newly codegened, totally unoptimized module. Optimize(ModuleCodegen), /// Copy the post-LTO artifacts from the incremental cache to the output @@ -692,75 +701,78 @@ impl WorkItem { } } - fn start_profiling<'a>(&self, cgcx: &'a CodegenContext) -> TimingGuard<'a> { - match *self { - WorkItem::Optimize(ref m) => { - cgcx.prof.generic_activity_with_arg("codegen_module_optimize", &*m.name) - } - WorkItem::CopyPostLtoArtifacts(ref m) => cgcx - .prof - .generic_activity_with_arg("codegen_copy_artifacts_from_incr_cache", &*m.name), - WorkItem::LTO(ref m) => { - cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", m.name()) - } - } - } - /// Generate a short description of this work item suitable for use as a thread name. fn short_description(&self) -> String { - // `pthread_setname()` on *nix is limited to 15 characters and longer names are ignored. - // Use very short descriptions in this case to maximize the space available for the module name. - // Windows does not have that limitation so use slightly more descriptive names there. + // `pthread_setname()` on *nix ignores anything beyond the first 15 + // bytes. Use short descriptions to maximize the space available for + // the module name. + #[cfg(not(windows))] + fn desc(short: &str, _long: &str, name: &str) -> String { + // The short label is three bytes, and is followed by a space. That + // leaves 11 bytes for the CGU name. How we obtain those 11 bytes + // depends on the CGU name form. + // + // - Non-incremental, e.g. `regex.f10ba03eb5ec7975-cgu.0`: the part + // before the `-cgu.0` is the same for every CGU, so use the + // `cgu.0` part. The number suffix will be different for each + // CGU. + // + // - Incremental (normal), e.g. `2i52vvl2hco29us0`: use the whole + // name because each CGU will have a unique ASCII hash, and the + // first 11 bytes will be enough to identify it. + // + // - Incremental (with `-Zhuman-readable-cgu-names`), e.g. + // `regex.f10ba03eb5ec7975-re_builder.volatile`: use the whole + // name. The first 11 bytes won't be enough to uniquely identify + // it, but no obvious substring will, and this is a rarely used + // option so it doesn't matter much. + // + assert_eq!(short.len(), 3); + let name = if let Some(index) = name.find("-cgu.") { + &name[index + 1..] // +1 skips the leading '-'. + } else { + name + }; + format!("{short} {name}") + } + + // Windows has no thread name length limit, so use more descriptive names. + #[cfg(windows)] + fn desc(_short: &str, long: &str, name: &str) -> String { + format!("{long} {name}") + } + match self { - WorkItem::Optimize(m) => { - #[cfg(windows)] - return format!("optimize module {}", m.name); - #[cfg(not(windows))] - return format!("opt {}", m.name); - } - WorkItem::CopyPostLtoArtifacts(m) => { - #[cfg(windows)] - return format!("copy LTO artifacts for {}", m.name); - #[cfg(not(windows))] - return format!("copy {}", m.name); - } - WorkItem::LTO(m) => { - #[cfg(windows)] - return format!("LTO module {}", m.name()); - #[cfg(not(windows))] - return format!("LTO {}", m.name()); - } + WorkItem::Optimize(m) => desc("opt", "optimize module", &m.name), + WorkItem::CopyPostLtoArtifacts(m) => desc("cpy", "copy LTO artifacts for", &m.name), + WorkItem::LTO(m) => desc("lto", "LTO module", m.name()), } } } -enum WorkItemResult { - Compiled(CompiledModule), +/// A result produced by the backend. +pub(crate) enum WorkItemResult { + /// The backend has finished compiling a CGU, nothing more required. + Finished(CompiledModule), + + /// The backend has finished compiling a CGU, which now needs linking + /// because `-Zcombine-cgu` was specified. NeedsLink(ModuleCodegen), - NeedsFatLTO(FatLTOInput), - NeedsThinLTO(String, B::ThinBuffer), + + /// The backend has finished compiling a CGU, which now needs to go through + /// fat LTO. + NeedsFatLto(FatLtoInput), + + /// The backend has finished compiling a CGU, which now needs to go through + /// thin LTO. + NeedsThinLto(String, B::ThinBuffer), } -pub enum FatLTOInput { +pub enum FatLtoInput { Serialized { name: String, buffer: B::ModuleBuffer }, InMemory(ModuleCodegen), } -fn execute_work_item( - cgcx: &CodegenContext, - work_item: WorkItem, -) -> Result, FatalError> { - let module_config = cgcx.config(work_item.module_kind()); - - match work_item { - WorkItem::Optimize(module) => execute_optimize_work_item(cgcx, module, module_config), - WorkItem::CopyPostLtoArtifacts(module) => { - Ok(execute_copy_from_cache_work_item(cgcx, module, module_config)) - } - WorkItem::LTO(module) => execute_lto_work_item(cgcx, module, module_config), - } -} - /// Actual LTO type we end up choosing based on multiple factors. pub enum ComputedLtoType { No, @@ -845,7 +857,7 @@ fn execute_optimize_work_item( panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e); }); } - Ok(WorkItemResult::NeedsThinLTO(name, thin_buffer)) + Ok(WorkItemResult::NeedsThinLto(name, thin_buffer)) } ComputedLtoType::Fat => match bitcode { Some(path) => { @@ -853,9 +865,9 @@ fn execute_optimize_work_item( fs::write(&path, buffer.data()).unwrap_or_else(|e| { panic!("Error writing pre-lto-bitcode file `{}`: {}", path.display(), e); }); - Ok(WorkItemResult::NeedsFatLTO(FatLTOInput::Serialized { name, buffer })) + Ok(WorkItemResult::NeedsFatLto(FatLtoInput::Serialized { name, buffer })) } - None => Ok(WorkItemResult::NeedsFatLTO(FatLTOInput::InMemory(module))), + None => Ok(WorkItemResult::NeedsFatLto(FatLtoInput::InMemory(module))), }, } } @@ -905,7 +917,7 @@ fn execute_copy_from_cache_work_item( load_from_incr_comp_dir(dwarf_obj_out, &saved_dwarf_object_file) }); - WorkItemResult::Compiled(CompiledModule { + WorkItemResult::Finished(CompiledModule { name: module.name, kind: ModuleKind::Regular, object, @@ -935,44 +947,47 @@ fn finish_intra_module_work( || module.kind == ModuleKind::Allocator { let module = unsafe { B::codegen(cgcx, &diag_handler, module, module_config)? }; - Ok(WorkItemResult::Compiled(module)) + Ok(WorkItemResult::Finished(module)) } else { Ok(WorkItemResult::NeedsLink(module)) } } -pub enum Message { +/// Messages sent to the coordinator. +pub(crate) enum Message { + /// A jobserver token has become available. Sent from the jobserver helper + /// thread. Token(io::Result), - NeedsFatLTO { - result: FatLTOInput, - worker_id: usize, - }, - NeedsThinLTO { - name: String, - thin_buffer: B::ThinBuffer, - worker_id: usize, - }, - NeedsLink { - module: ModuleCodegen, - worker_id: usize, - }, - Done { - result: Result>, - worker_id: usize, - }, - CodegenDone { - llvm_work_item: WorkItem, - cost: u64, - }, + + /// The backend has finished processing a work item for a codegen unit. + /// Sent from a backend worker thread. + WorkItem { result: Result, Option>, worker_id: usize }, + + /// The frontend has finished generating something (backend IR or a + /// post-LTO artifact) for a codegen unit, and it should be passed to the + /// backend. Sent from the main thread. + CodegenDone { llvm_work_item: WorkItem, cost: u64 }, + + /// Similar to `CodegenDone`, but for reusing a pre-LTO artifact + /// Sent from the main thread. AddImportOnlyModule { module_data: SerializedModule, work_product: WorkProduct, }, + + /// The frontend has finished generating everything for all codegen units. + /// Sent from the main thread. CodegenComplete, - CodegenItem, + + /// Some normal-ish compiler error occurred, and codegen should be wound + /// down. Sent from the main thread. CodegenAborted, } +/// A message sent from the coordinator thread to the main thread telling it to +/// process another codegen unit. +pub struct CguMessage; + type DiagnosticArgName<'source> = Cow<'source, str>; struct Diagnostic { @@ -983,10 +998,15 @@ struct Diagnostic { } #[derive(PartialEq, Clone, Copy, Debug)] -enum MainThreadWorkerState { +enum MainThreadState { + /// Doing nothing. Idle, + + /// Doing codegen, i.e. MIR-to-LLVM-IR conversion. Codegenning, - LLVMing, + + /// Idle, but lending the compiler process's Token to an LLVM thread so it can do useful work. + Lending, } fn start_executing_work( @@ -994,9 +1014,8 @@ fn start_executing_work( tcx: TyCtxt<'_>, crate_info: &CrateInfo, shared_emitter: SharedEmitter, - codegen_worker_send: Sender>, + codegen_worker_send: Sender, coordinator_receive: Receiver>, - total_cgus: usize, jobserver: Client, regular_config: Arc, metadata_config: Arc, @@ -1063,9 +1082,19 @@ fn start_executing_work( tcx.backend_optimization_level(()) }; let backend_features = tcx.global_backend_features(()); + + let remark_dir = if let Some(ref dir) = sess.opts.unstable_opts.remark_dir { + let result = fs::create_dir_all(dir).and_then(|_| dir.canonicalize()); + match result { + Ok(dir) => Some(dir), + Err(error) => sess.emit_fatal(ErrorCreatingRemarkDir { error }), + } + } else { + None + }; + let cgcx = CodegenContext:: { - backend: backend.clone(), - crate_types: sess.crate_types().to_vec(), + crate_types: tcx.crate_types().to_vec(), each_linked_rlib_for_lto, lto: sess.lto(), fewer_names: sess.fewer_names(), @@ -1075,7 +1104,7 @@ fn start_executing_work( prof: sess.prof.clone(), exported_symbols, remark: sess.opts.cg.remark.clone(), - worker: 0, + remark_dir, incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()), cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(), coordinator_send, @@ -1085,13 +1114,10 @@ fn start_executing_work( metadata_module_config: metadata_config, allocator_module_config: allocator_config, tm_factory: backend.target_machine_factory(tcx.sess, ol, backend_features), - total_cgus, msvc_imps_needed: msvc_imps_needed(tcx), is_pe_coff: tcx.sess.target.is_like_windows, target_can_use_split_dwarf: tcx.sess.target_can_use_split_dwarf(), - target_pointer_width: tcx.sess.target.pointer_width, target_arch: tcx.sess.target.arch.to_string(), - debuginfo: tcx.sess.opts.debuginfo, split_debuginfo: tcx.sess.split_debuginfo(), split_dwarf_kind: tcx.sess.opts.unstable_opts.split_dwarf_kind, }; @@ -1231,7 +1257,7 @@ fn start_executing_work( // Each LLVM module is automatically sent back to the coordinator for LTO if // necessary. There's already optimizations in place to avoid sending work // back to the coordinator if LTO isn't requested. - return B::spawn_thread(cgcx.time_trace, move || { + return B::spawn_named_thread(cgcx.time_trace, "coordinator".to_string(), move || { let mut worker_id_counter = 0; let mut free_worker_ids = Vec::new(); let mut get_worker_id = |free_worker_ids: &mut Vec| { @@ -1253,10 +1279,19 @@ fn start_executing_work( let mut needs_thin_lto = Vec::new(); let mut lto_import_only_modules = Vec::new(); let mut started_lto = false; - let mut codegen_aborted = false; - // This flag tracks whether all items have gone through codegens - let mut codegen_done = false; + /// Possible state transitions: + /// - Ongoing -> Completed + /// - Ongoing -> Aborted + /// - Completed -> Aborted + #[derive(Debug, PartialEq)] + enum CodegenState { + Ongoing, + Completed, + Aborted, + } + use CodegenState::*; + let mut codegen_state = Ongoing; // This is the queue of LLVM work items that still need processing. let mut work_items = Vec::<(WorkItem, u64)>::new(); @@ -1265,10 +1300,19 @@ fn start_executing_work( // the implicit Token the compiler process owns no matter what. let mut tokens = Vec::new(); - let mut main_thread_worker_state = MainThreadWorkerState::Idle; - let mut running = 0; + let mut main_thread_state = MainThreadState::Idle; + + // How many LLVM worker threads are running while holding a Token. This + // *excludes* any that the main thread is lending a Token to. + let mut running_with_own_token = 0; + + // How many LLVM worker threads are running in total. This *includes* + // any that the main thread is lending a Token to. + let running_with_any_token = |main_thread_state, running_with_own_token| { + running_with_own_token + + if main_thread_state == MainThreadState::Lending { 1 } else { 0 } + }; - let prof = &cgcx.prof; let mut llvm_start_time: Option> = None; // Run the message loop while there's still anything that needs message @@ -1276,69 +1320,62 @@ fn start_executing_work( // wait for all existing work to finish, so many of the conditions here // only apply if codegen hasn't been aborted as they represent pending // work to be done. - while !codegen_done - || running > 0 - || main_thread_worker_state == MainThreadWorkerState::LLVMing - || (!codegen_aborted - && !(work_items.is_empty() - && needs_fat_lto.is_empty() - && needs_thin_lto.is_empty() - && lto_import_only_modules.is_empty() - && main_thread_worker_state == MainThreadWorkerState::Idle)) - { + loop { // While there are still CGUs to be codegened, the coordinator has // to decide how to utilize the compiler processes implicit Token: // For codegenning more CGU or for running them through LLVM. - if !codegen_done { - if main_thread_worker_state == MainThreadWorkerState::Idle { + if codegen_state == Ongoing { + if main_thread_state == MainThreadState::Idle { // Compute the number of workers that will be running once we've taken as many // items from the work queue as we can, plus one for the main thread. It's not - // critically important that we use this instead of just `running`, but it - // prevents the `queue_full_enough` heuristic from fluctuating just because a - // worker finished up and we decreased the `running` count, even though we're - // just going to increase it right after this when we put a new worker to work. - let extra_tokens = tokens.len().checked_sub(running).unwrap(); + // critically important that we use this instead of just + // `running_with_own_token`, but it prevents the `queue_full_enough` heuristic + // from fluctuating just because a worker finished up and we decreased the + // `running_with_own_token` count, even though we're just going to increase it + // right after this when we put a new worker to work. + let extra_tokens = tokens.len().checked_sub(running_with_own_token).unwrap(); let additional_running = std::cmp::min(extra_tokens, work_items.len()); - let anticipated_running = running + additional_running + 1; + let anticipated_running = running_with_own_token + additional_running + 1; if !queue_full_enough(work_items.len(), anticipated_running) { - // The queue is not full enough, codegen more items: - if codegen_worker_send.send(Message::CodegenItem).is_err() { - panic!("Could not send Message::CodegenItem to main thread") + // The queue is not full enough, process more codegen units: + if codegen_worker_send.send(CguMessage).is_err() { + panic!("Could not send CguMessage to main thread") } - main_thread_worker_state = MainThreadWorkerState::Codegenning; + main_thread_state = MainThreadState::Codegenning; } else { // The queue is full enough to not let the worker // threads starve. Use the implicit Token to do some // LLVM work too. let (item, _) = work_items.pop().expect("queue empty - queue_full_enough() broken?"); - let cgcx = CodegenContext { - worker: get_worker_id(&mut free_worker_ids), - ..cgcx.clone() - }; - maybe_start_llvm_timer( - prof, - cgcx.config(item.module_kind()), + main_thread_state = MainThreadState::Lending; + spawn_work( + &cgcx, &mut llvm_start_time, + get_worker_id(&mut free_worker_ids), + item, ); - main_thread_worker_state = MainThreadWorkerState::LLVMing; - spawn_work(cgcx, item); } } - } else if codegen_aborted { - // don't queue up any more work if codegen was aborted, we're - // just waiting for our existing children to finish - } else { - // If we've finished everything related to normal codegen - // then it must be the case that we've got some LTO work to do. - // Perform the serial work here of figuring out what we're - // going to LTO and then push a bunch of work items onto our - // queue to do LTO - if work_items.is_empty() - && running == 0 - && main_thread_worker_state == MainThreadWorkerState::Idle + } else if codegen_state == Completed { + if running_with_any_token(main_thread_state, running_with_own_token) == 0 + && work_items.is_empty() { + // All codegen work is done. Do we have LTO work to do? + if needs_fat_lto.is_empty() + && needs_thin_lto.is_empty() + && lto_import_only_modules.is_empty() + { + // Nothing more to do! + break; + } + + // We have LTO work to do. Perform the serial work here of + // figuring out what we're going to LTO and then push a + // bunch of work items onto our queue to do LTO. This all + // happens on the coordinator thread but it's very quick so + // we don't worry about tokens. assert!(!started_lto); started_lto = true; @@ -1362,20 +1399,16 @@ fn start_executing_work( // In this branch, we know that everything has been codegened, // so it's just a matter of determining whether the implicit // Token is free to use for LLVM work. - match main_thread_worker_state { - MainThreadWorkerState::Idle => { + match main_thread_state { + MainThreadState::Idle => { if let Some((item, _)) = work_items.pop() { - let cgcx = CodegenContext { - worker: get_worker_id(&mut free_worker_ids), - ..cgcx.clone() - }; - maybe_start_llvm_timer( - prof, - cgcx.config(item.module_kind()), + main_thread_state = MainThreadState::Lending; + spawn_work( + &cgcx, &mut llvm_start_time, + get_worker_id(&mut free_worker_ids), + item, ); - main_thread_worker_state = MainThreadWorkerState::LLVMing; - spawn_work(cgcx, item); } else { // There is no unstarted work, so let the main thread // take over for a running worker. Otherwise the @@ -1383,48 +1416,56 @@ fn start_executing_work( // We reduce the `running` counter by one. The // `tokens.truncate()` below will take care of // giving the Token back. - debug_assert!(running > 0); - running -= 1; - main_thread_worker_state = MainThreadWorkerState::LLVMing; + debug_assert!(running_with_own_token > 0); + running_with_own_token -= 1; + main_thread_state = MainThreadState::Lending; } } - MainThreadWorkerState::Codegenning => bug!( + MainThreadState::Codegenning => bug!( "codegen worker should not be codegenning after \ codegen was already completed" ), - MainThreadWorkerState::LLVMing => { + MainThreadState::Lending => { // Already making good use of that token } } + } else { + // Don't queue up any more work if codegen was aborted, we're + // just waiting for our existing children to finish. + assert!(codegen_state == Aborted); + if running_with_any_token(main_thread_state, running_with_own_token) == 0 { + break; + } } // Spin up what work we can, only doing this while we've got available // parallelism slots and work left to spawn. - while !codegen_aborted && !work_items.is_empty() && running < tokens.len() { - let (item, _) = work_items.pop().unwrap(); - - maybe_start_llvm_timer(prof, cgcx.config(item.module_kind()), &mut llvm_start_time); - - let cgcx = - CodegenContext { worker: get_worker_id(&mut free_worker_ids), ..cgcx.clone() }; - - spawn_work(cgcx, item); - running += 1; + if codegen_state != Aborted { + while !work_items.is_empty() && running_with_own_token < tokens.len() { + let (item, _) = work_items.pop().unwrap(); + spawn_work( + &cgcx, + &mut llvm_start_time, + get_worker_id(&mut free_worker_ids), + item, + ); + running_with_own_token += 1; + } } - // Relinquish accidentally acquired extra tokens - tokens.truncate(running); + // Relinquish accidentally acquired extra tokens. + tokens.truncate(running_with_own_token); // If a thread exits successfully then we drop a token associated - // with that worker and update our `running` count. We may later - // re-acquire a token to continue running more work. We may also not - // actually drop a token here if the worker was running with an - // "ephemeral token" + // with that worker and update our `running_with_own_token` count. + // We may later re-acquire a token to continue running more work. + // We may also not actually drop a token here if the worker was + // running with an "ephemeral token". let mut free_worker = |worker_id| { - if main_thread_worker_state == MainThreadWorkerState::LLVMing { - main_thread_worker_state = MainThreadWorkerState::Idle; + if main_thread_state == MainThreadState::Lending { + main_thread_state = MainThreadState::Idle; } else { - running -= 1; + running_with_own_token -= 1; } free_worker_ids.push(worker_id); @@ -1440,20 +1481,19 @@ fn start_executing_work( Ok(token) => { tokens.push(token); - if main_thread_worker_state == MainThreadWorkerState::LLVMing { + if main_thread_state == MainThreadState::Lending { // If the main thread token is used for LLVM work // at the moment, we turn that thread into a regular // LLVM worker thread, so the main thread is free // to react to codegen demand. - main_thread_worker_state = MainThreadWorkerState::Idle; - running += 1; + main_thread_state = MainThreadState::Idle; + running_with_own_token += 1; } } Err(e) => { - let msg = &format!("failed to acquire jobserver token: {}", e); + let msg = &format!("failed to acquire jobserver token: {e}"); shared_emitter.fatal(msg); - codegen_done = true; - codegen_aborted = true; + codegen_state = Aborted; } } } @@ -1476,14 +1516,16 @@ fn start_executing_work( if !cgcx.opts.unstable_opts.no_parallel_llvm { helper.request_token(); } - assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning); - main_thread_worker_state = MainThreadWorkerState::Idle; + assert_eq!(main_thread_state, MainThreadState::Codegenning); + main_thread_state = MainThreadState::Idle; } Message::CodegenComplete => { - codegen_done = true; - assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning); - main_thread_worker_state = MainThreadWorkerState::Idle; + if codegen_state != Aborted { + codegen_state = Completed; + } + assert_eq!(main_thread_state, MainThreadState::Codegenning); + main_thread_state = MainThreadState::Idle; } // If codegen is aborted that means translation was aborted due @@ -1491,60 +1533,66 @@ fn start_executing_work( // to exit as soon as possible, but we want to make sure all // existing work has finished. Flag codegen as being done, and // then conditions above will ensure no more work is spawned but - // we'll keep executing this loop until `running` hits 0. + // we'll keep executing this loop until `running_with_own_token` + // hits 0. Message::CodegenAborted => { - codegen_done = true; - codegen_aborted = true; + codegen_state = Aborted; } - Message::Done { result: Ok(compiled_module), worker_id } => { + + Message::WorkItem { result, worker_id } => { free_worker(worker_id); - match compiled_module.kind { - ModuleKind::Regular => { - compiled_modules.push(compiled_module); + + match result { + Ok(WorkItemResult::Finished(compiled_module)) => { + match compiled_module.kind { + ModuleKind::Regular => { + assert!(needs_link.is_empty()); + compiled_modules.push(compiled_module); + } + ModuleKind::Allocator => { + assert!(compiled_allocator_module.is_none()); + compiled_allocator_module = Some(compiled_module); + } + ModuleKind::Metadata => bug!("Should be handled separately"), + } + } + Ok(WorkItemResult::NeedsLink(module)) => { + assert!(compiled_modules.is_empty()); + needs_link.push(module); } - ModuleKind::Allocator => { - assert!(compiled_allocator_module.is_none()); - compiled_allocator_module = Some(compiled_module); + Ok(WorkItemResult::NeedsFatLto(fat_lto_input)) => { + assert!(!started_lto); + assert!(needs_thin_lto.is_empty()); + needs_fat_lto.push(fat_lto_input); + } + Ok(WorkItemResult::NeedsThinLto(name, thin_buffer)) => { + assert!(!started_lto); + assert!(needs_fat_lto.is_empty()); + needs_thin_lto.push((name, thin_buffer)); + } + Err(Some(WorkerFatalError)) => { + // Like `CodegenAborted`, wait for remaining work to finish. + codegen_state = Aborted; + } + Err(None) => { + // If the thread failed that means it panicked, so + // we abort immediately. + bug!("worker thread panicked"); } - ModuleKind::Metadata => bug!("Should be handled separately"), } } - Message::NeedsLink { module, worker_id } => { - free_worker(worker_id); - needs_link.push(module); - } - Message::NeedsFatLTO { result, worker_id } => { - assert!(!started_lto); - free_worker(worker_id); - needs_fat_lto.push(result); - } - Message::NeedsThinLTO { name, thin_buffer, worker_id } => { - assert!(!started_lto); - free_worker(worker_id); - needs_thin_lto.push((name, thin_buffer)); - } + Message::AddImportOnlyModule { module_data, work_product } => { assert!(!started_lto); - assert!(!codegen_done); - assert_eq!(main_thread_worker_state, MainThreadWorkerState::Codegenning); + assert_eq!(codegen_state, Ongoing); + assert_eq!(main_thread_state, MainThreadState::Codegenning); lto_import_only_modules.push((module_data, work_product)); - main_thread_worker_state = MainThreadWorkerState::Idle; + main_thread_state = MainThreadState::Idle; } - // If the thread failed that means it panicked, so we abort immediately. - Message::Done { result: Err(None), worker_id: _ } => { - bug!("worker thread panicked"); - } - Message::Done { result: Err(Some(WorkerFatalError)), worker_id } => { - // Similar to CodegenAborted, wait for remaining work to finish. - free_worker(worker_id); - codegen_done = true; - codegen_aborted = true; - } - Message::CodegenItem => bug!("the coordinator should not receive codegen requests"), } } - if codegen_aborted { + if codegen_state == Aborted { return Err(()); } @@ -1572,7 +1620,8 @@ fn start_executing_work( modules: compiled_modules, allocator_module: compiled_allocator_module, }) - }); + }) + .expect("failed to spawn coordinator thread"); // A heuristic that determines if we have enough LLVM WorkItems in the // queue so that the main thread can do LLVM work instead of codegen @@ -1630,23 +1679,24 @@ fn start_executing_work( let quarter_of_workers = workers_running - 3 * workers_running / 4; items_in_queue > 0 && items_in_queue >= quarter_of_workers } - - fn maybe_start_llvm_timer<'a>( - prof: &'a SelfProfilerRef, - config: &ModuleConfig, - llvm_start_time: &mut Option>, - ) { - if config.time_module && llvm_start_time.is_none() { - *llvm_start_time = Some(prof.verbose_generic_activity("LLVM_passes")); - } - } } /// `FatalError` is explicitly not `Send`. #[must_use] pub struct WorkerFatalError; -fn spawn_work(cgcx: CodegenContext, work: WorkItem) { +fn spawn_work<'a, B: ExtraBackendMethods>( + cgcx: &'a CodegenContext, + llvm_start_time: &mut Option>, + worker_id: usize, + work: WorkItem, +) { + if cgcx.config(work.module_kind()).time_module && llvm_start_time.is_none() { + *llvm_start_time = Some(cgcx.prof.verbose_generic_activity("LLVM_passes")); + } + + let cgcx = cgcx.clone(); + B::spawn_named_thread(cgcx.time_trace, work.short_description(), move || { // Set up a destructor which will fire off a message that we're done as // we exit. @@ -1659,32 +1709,18 @@ fn spawn_work(cgcx: CodegenContext, work: WorkItem fn drop(&mut self) { let worker_id = self.worker_id; let msg = match self.result.take() { - Some(Ok(WorkItemResult::Compiled(m))) => { - Message::Done:: { result: Ok(m), worker_id } - } - Some(Ok(WorkItemResult::NeedsLink(m))) => { - Message::NeedsLink:: { module: m, worker_id } - } - Some(Ok(WorkItemResult::NeedsFatLTO(m))) => { - Message::NeedsFatLTO:: { result: m, worker_id } - } - Some(Ok(WorkItemResult::NeedsThinLTO(name, thin_buffer))) => { - Message::NeedsThinLTO:: { name, thin_buffer, worker_id } - } + Some(Ok(result)) => Message::WorkItem:: { result: Ok(result), worker_id }, Some(Err(FatalError)) => { - Message::Done:: { result: Err(Some(WorkerFatalError)), worker_id } + Message::WorkItem:: { result: Err(Some(WorkerFatalError)), worker_id } } - None => Message::Done:: { result: Err(None), worker_id }, + None => Message::WorkItem:: { result: Err(None), worker_id }, }; drop(self.coordinator_send.send(Box::new(msg))); } } - let mut bomb = Bomb:: { - coordinator_send: cgcx.coordinator_send.clone(), - result: None, - worker_id: cgcx.worker, - }; + let mut bomb = + Bomb:: { coordinator_send: cgcx.coordinator_send.clone(), result: None, worker_id }; // Execute the work itself, and if it finishes successfully then flag // ourselves as a success as well. @@ -1693,11 +1729,30 @@ fn spawn_work(cgcx: CodegenContext, work: WorkItem // as a diagnostic was already sent off to the main thread - just // surface that there was an error in this worker. bomb.result = { - let _prof_timer = work.start_profiling(&cgcx); - Some(execute_work_item(&cgcx, work)) + let module_config = cgcx.config(work.module_kind()); + + Some(match work { + WorkItem::Optimize(m) => { + let _timer = + cgcx.prof.generic_activity_with_arg("codegen_module_optimize", &*m.name); + execute_optimize_work_item(&cgcx, m, module_config) + } + WorkItem::CopyPostLtoArtifacts(m) => { + let _timer = cgcx.prof.generic_activity_with_arg( + "codegen_copy_artifacts_from_incr_cache", + &*m.name, + ); + Ok(execute_copy_from_cache_work_item(&cgcx, m, module_config)) + } + WorkItem::LTO(m) => { + let _timer = + cgcx.prof.generic_activity_with_arg("codegen_module_perform_lto", m.name()); + execute_lto_work_item(&cgcx, m, module_config) + } + }) }; }) - .expect("failed to spawn thread"); + .expect("failed to spawn work thread"); } enum SharedEmitterMessage { @@ -1800,7 +1855,7 @@ impl SharedEmitterMain { handler.emit_diagnostic(&mut d); } Ok(SharedEmitterMessage::InlineAsmError(cookie, msg, level, source)) => { - let msg = msg.strip_prefix("error: ").unwrap_or(&msg); + let msg = msg.strip_prefix("error: ").unwrap_or(&msg).to_string(); let mut err = match level { Level::Error { lint: false } => sess.struct_err(msg).forget_guarantee(), @@ -1878,14 +1933,14 @@ pub struct OngoingCodegen { pub metadata: EncodedMetadata, pub metadata_module: Option, pub crate_info: CrateInfo, - pub codegen_worker_receive: Receiver>, + pub codegen_worker_receive: Receiver, pub shared_emitter_main: SharedEmitterMain, pub output_filenames: Arc, pub coordinator: Coordinator, } impl OngoingCodegen { - pub fn join(self, sess: &Session) -> (CodegenResults, FxHashMap) { + pub fn join(self, sess: &Session) -> (CodegenResults, FxIndexMap) { let _timer = sess.timer("finish_ongoing_codegen"); self.shared_emitter_main.check(sess, true); @@ -1910,10 +1965,14 @@ impl OngoingCodegen { // FIXME: time_llvm_passes support - does this use a global context or // something? - if sess.codegen_units() == 1 && sess.opts.unstable_opts.time_llvm_passes { + if sess.codegen_units().as_usize() == 1 && sess.opts.unstable_opts.time_llvm_passes { self.backend.print_pass_timings() } + if sess.print_llvm_stats() { + self.backend.print_statistics() + } + ( CodegenResults { metadata: self.metadata, @@ -1927,19 +1986,6 @@ impl OngoingCodegen { ) } - pub fn submit_pre_codegened_module_to_llvm( - &self, - tcx: TyCtxt<'_>, - module: ModuleCodegen, - ) { - self.wait_for_signal_to_codegen_item(); - self.check_for_errors(tcx.sess); - - // These are generally cheap and won't throw off scheduling. - let cost = 0; - submit_codegened_module_to_llvm(&self.backend, &self.coordinator.sender, module, cost); - } - pub fn codegen_finished(&self, tcx: TyCtxt<'_>) { self.wait_for_signal_to_codegen_item(); self.check_for_errors(tcx.sess); @@ -1952,10 +1998,9 @@ impl OngoingCodegen { pub fn wait_for_signal_to_codegen_item(&self) { match self.codegen_worker_receive.recv() { - Ok(Message::CodegenItem) => { - // Nothing to do + Ok(CguMessage) => { + // Ok to proceed. } - Ok(_) => panic!("unexpected message"), Err(_) => { // One of the LLVM threads must have panicked, fall through so // error handling can be reached. @@ -2006,8 +2051,8 @@ pub fn submit_pre_lto_module_to_llvm( }))); } -pub fn pre_lto_bitcode_filename(module_name: &str) -> String { - format!("{}.{}", module_name, PRE_LTO_BC_EXT) +fn pre_lto_bitcode_filename(module_name: &str) -> String { + format!("{module_name}.{PRE_LTO_BC_EXT}") } fn msvc_imps_needed(tcx: TyCtxt<'_>) -> bool { @@ -2020,7 +2065,7 @@ fn msvc_imps_needed(tcx: TyCtxt<'_>) -> bool { ); tcx.sess.target.is_like_windows && - tcx.sess.crate_types().iter().any(|ct| *ct == CrateType::Rlib) && + tcx.crate_types().iter().any(|ct| *ct == CrateType::Rlib) && // ThinLTO can't handle this workaround in all cases, so we don't // emit the `__imp_` symbols. Instead we make them unnecessary by disallowing // dynamic linking when linker plugin LTO is enabled. diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 15c7847155d2f..aa003e4e89811 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -13,7 +13,7 @@ use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{CachedModuleCodegen, CompiledModule, CrateInfo, MemFlags, ModuleCodegen, ModuleKind}; -use rustc_ast::expand::allocator::AllocatorKind; +use rustc_ast::expand::allocator::{global_fn_name, AllocatorKind, ALLOCATOR_METHODS}; use rustc_attr as attr; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::profiling::{get_resident_set_size, print_time_passes_entry}; @@ -38,6 +38,7 @@ use rustc_span::symbol::sym; use rustc_span::Symbol; use rustc_target::abi::{Align, FIRST_VARIANT}; +use std::cmp; use std::collections::BTreeSet; use std::time::{Duration, Instant}; @@ -164,50 +165,27 @@ pub fn unsized_info<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cx.tcx().vtable_trait_upcasting_coercion_new_vptr_slot((source, target)); if let Some(entry_idx) = vptr_entry_idx { - let ptr_ty = cx.type_i8p(); + let ptr_ty = cx.type_ptr(); let ptr_align = cx.tcx().data_layout.pointer_align.abi; - let vtable_ptr_ty = vtable_ptr_ty(cx, target, target_dyn_kind); - let llvtable = bx.pointercast(old_info, bx.type_ptr_to(ptr_ty)); let gep = bx.inbounds_gep( ptr_ty, - llvtable, + old_info, &[bx.const_usize(u64::try_from(entry_idx).unwrap())], ); let new_vptr = bx.load(ptr_ty, gep, ptr_align); bx.nonnull_metadata(new_vptr); // VTable loads are invariant. bx.set_invariant_load(new_vptr); - bx.pointercast(new_vptr, vtable_ptr_ty) + new_vptr } else { old_info } } - (_, &ty::Dynamic(ref data, _, target_dyn_kind)) => { - let vtable_ptr_ty = vtable_ptr_ty(cx, target, target_dyn_kind); - cx.const_ptrcast(meth::get_vtable(cx, source, data.principal()), vtable_ptr_ty) - } + (_, &ty::Dynamic(ref data, _, _)) => meth::get_vtable(cx, source, data.principal()), _ => bug!("unsized_info: invalid unsizing {:?} -> {:?}", source, target), } } -// Returns the vtable pointer type of a `dyn` or `dyn*` type -fn vtable_ptr_ty<'tcx, Cx: CodegenMethods<'tcx>>( - cx: &Cx, - target: Ty<'tcx>, - kind: ty::DynKind, -) -> ::Type { - cx.scalar_pair_element_backend_type( - cx.layout_of(match kind { - // vtable is the second field of `*mut dyn Trait` - ty::Dyn => cx.tcx().mk_mut_ptr(target), - // vtable is the second field of `dyn* Trait` - ty::DynStar => target, - }), - 1, - true, - ) -} - /// Coerces `src` to `dst_ty`. `src_ty` must be a pointer. pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &mut Bx, @@ -221,8 +199,7 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( (&ty::Ref(_, a, _), &ty::Ref(_, b, _) | &ty::RawPtr(ty::TypeAndMut { ty: b, .. })) | (&ty::RawPtr(ty::TypeAndMut { ty: a, .. }), &ty::RawPtr(ty::TypeAndMut { ty: b, .. })) => { assert_eq!(bx.cx().type_is_sized(a), old_info.is_none()); - let ptr_ty = bx.cx().type_ptr_to(bx.cx().backend_type(bx.cx().layout_of(b))); - (bx.pointercast(src, ptr_ty), unsized_info(bx, a, b, old_info)) + (src, unsized_info(bx, a, b, old_info)) } (&ty::Adt(def_a, _), &ty::Adt(def_b, _)) => { assert_eq!(def_a, def_b); @@ -247,11 +224,7 @@ pub fn unsize_ptr<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( assert_eq!(result, None); result = Some(unsize_ptr(bx, src, src_f.ty, dst_f.ty, old_info)); } - let (lldata, llextra) = result.unwrap(); - let lldata_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 0, true); - let llextra_ty = bx.cx().scalar_pair_element_backend_type(dst_layout, 1, true); - // HACK(eddyb) have to bitcast pointers until LLVM removes pointee types. - (bx.bitcast(lldata, lldata_ty), bx.bitcast(llextra, llextra_ty)) + result.unwrap() } _ => bug!("unsize_ptr: called on bad types"), } @@ -270,11 +243,9 @@ pub fn cast_to_dyn_star<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( matches!(dst_ty.kind(), ty::Dynamic(_, _, ty::DynStar)), "destination type must be a dyn*" ); - // FIXME(dyn-star): We can remove this when all supported LLVMs use opaque ptrs only. - let unit_ptr = bx.cx().type_ptr_to(bx.cx().type_struct(&[], false)); let src = match bx.cx().type_kind(bx.cx().backend_type(src_ty_and_layout)) { - TypeKind::Pointer => bx.pointercast(src, unit_ptr), - TypeKind::Integer => bx.inttoptr(src, unit_ptr), + TypeKind::Pointer => src, + TypeKind::Integer => bx.inttoptr(src, bx.type_ptr()), // FIXME(dyn-star): We probably have to do a bitcast first, then inttoptr. kind => bug!("unexpected TypeKind for left-hand side of `dyn*` cast: {kind:?}"), }; @@ -295,7 +266,7 @@ pub fn coerce_unsized_into<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let (base, info) = match bx.load_operand(src).val { OperandValue::Pair(base, info) => unsize_ptr(bx, base, src_ty, dst_ty, Some(info)), OperandValue::Immediate(base) => unsize_ptr(bx, base, src_ty, dst_ty, None), - OperandValue::Ref(..) => bug!(), + OperandValue::Ref(..) | OperandValue::ZeroSized => bug!(), }; OperandValue::Pair(base, info).store(bx, dst); } @@ -357,6 +328,13 @@ pub fn cast_shift_expr_rhs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } } +// Returns `true` if this session's target will use native wasm +// exceptions. This means that the VM does the unwinding for +// us +pub fn wants_wasm_eh(sess: &Session) -> bool { + sess.target.is_like_wasm && sess.target.os != "emscripten" +} + /// Returns `true` if this session's target will use SEH-based unwinding. /// /// This is only true for MSVC targets, and even then the 64-bit MSVC target @@ -366,6 +344,13 @@ pub fn wants_msvc_seh(sess: &Session) -> bool { sess.target.is_like_msvc } +/// Returns `true` if this session's target requires the new exception +/// handling LLVM IR instructions (catchpad / cleanuppad / ... instead +/// of landingpad) +pub fn wants_new_eh_instructions(sess: &Session) -> bool { + wants_wasm_eh(sess) || wants_msvc_seh(sess) +} + pub fn memcpy_ty<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &mut Bx, dst: Bx::Value, @@ -380,7 +365,14 @@ pub fn memcpy_ty<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( return; } - bx.memcpy(dst, dst_align, src, src_align, bx.cx().const_usize(size), flags); + if flags == MemFlags::empty() + && let Some(bty) = bx.cx().scalar_copy_backend_type(layout) + { + let temp = bx.load(bty, src, src_align); + bx.store(temp, dst, dst_align); + } else { + bx.memcpy(dst, dst_align, src, src_align, bx.cx().const_usize(size), flags); + } } pub fn codegen_instance<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>( @@ -429,7 +421,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // The entry function is either `int main(void)` or `int main(int argc, char **argv)`, // depending on whether the target needs `argc` and `argv` to be passed in. let llfty = if cx.sess().target.main_needs_argc_argv { - cx.type_func(&[cx.type_int(), cx.type_ptr_to(cx.type_i8p())], cx.type_int()) + cx.type_func(&[cx.type_int(), cx.type_ptr()], cx.type_int()) } else { cx.type_func(&[], cx.type_int()) }; @@ -463,7 +455,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx.insert_reference_to_gdb_debug_scripts_section_global(); let isize_ty = cx.type_isize(); - let i8pp_ty = cx.type_ptr_to(cx.type_i8p()); + let ptr_ty = cx.type_ptr(); let (arg_argc, arg_argv) = get_argc_argv(cx, &mut bx); let (start_fn, start_ty, args) = if let EntryFnType::Main { sigpipe } = entry_type { @@ -473,7 +465,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cx.tcx(), ty::ParamEnv::reveal_all(), start_def_id, - cx.tcx().mk_substs(&[main_ret_ty.into()]), + cx.tcx().mk_args(&[main_ret_ty.into()]), ) .unwrap() .unwrap(), @@ -482,12 +474,11 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let i8_ty = cx.type_i8(); let arg_sigpipe = bx.const_u8(sigpipe); - let start_ty = - cx.type_func(&[cx.val_ty(rust_main), isize_ty, i8pp_ty, i8_ty], isize_ty); + let start_ty = cx.type_func(&[cx.val_ty(rust_main), isize_ty, ptr_ty, i8_ty], isize_ty); (start_fn, start_ty, vec![rust_main, arg_argc, arg_argv, arg_sigpipe]) } else { debug!("using user-defined start fn"); - let start_ty = cx.type_func(&[isize_ty, i8pp_ty], isize_ty); + let start_ty = cx.type_func(&[isize_ty, ptr_ty], isize_ty); (rust_main, start_ty, vec![arg_argc, arg_argv]) }; @@ -514,7 +505,7 @@ fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } else { // The Rust start function doesn't need `argc` and `argv`, so just pass zeros. let arg_argc = bx.const_int(cx.type_int(), 0); - let arg_argv = bx.const_null(cx.type_ptr_to(cx.type_i8p())); + let arg_argv = bx.const_null(cx.type_ptr()); (arg_argc, arg_argv) } } @@ -568,7 +559,7 @@ pub fn codegen_crate( ) -> OngoingCodegen { // Skip crate items and just output metadata in -Z no-codegen mode. if tcx.sess.opts.unstable_opts.no_codegen || !tcx.sess.opts.output_types.should_codegen() { - let ongoing_codegen = start_async_codegen(backend, tcx, target_cpu, metadata, None, 1); + let ongoing_codegen = start_async_codegen(backend, tcx, target_cpu, metadata, None); ongoing_codegen.codegen_finished(tcx); @@ -619,14 +610,8 @@ pub fn codegen_crate( }) }); - let ongoing_codegen = start_async_codegen( - backend.clone(), - tcx, - target_cpu, - metadata, - metadata_module, - codegen_units.len(), - ); + let ongoing_codegen = + start_async_codegen(backend.clone(), tcx, target_cpu, metadata, metadata_module); // Codegen an allocator shim, if necessary. if let Some(kind) = allocator_kind_for_codegen(tcx) { @@ -643,9 +628,16 @@ pub fn codegen_crate( ) }); - ongoing_codegen.submit_pre_codegened_module_to_llvm( - tcx, + ongoing_codegen.wait_for_signal_to_codegen_item(); + ongoing_codegen.check_for_errors(tcx.sess); + + // These modules are generally cheap and won't throw off scheduling. + let cost = 0; + submit_codegened_module_to_llvm( + &backend, + &ongoing_codegen.coordinator.sender, ModuleCodegen { name: llmod_id, module_llvm, kind: ModuleKind::Allocator }, + cost, ); } @@ -662,10 +654,10 @@ pub fn codegen_crate( // are large size variations, this can reduce memory usage significantly. let codegen_units: Vec<_> = { let mut sorted_cgus = codegen_units.iter().collect::>(); - sorted_cgus.sort_by_cached_key(|cgu| cgu.size_estimate()); + sorted_cgus.sort_by_key(|cgu| cmp::Reverse(cgu.size_estimate())); let (first_half, second_half) = sorted_cgus.split_at(sorted_cgus.len() / 2); - second_half.iter().rev().interleave(first_half).copied().collect() + first_half.iter().interleave(second_half.iter().rev()).copied().collect() }; // Calculate the CGU reuse @@ -740,7 +732,6 @@ pub fn codegen_crate( module, cost, ); - false } CguReuse::PreLto => { submit_pre_lto_module_to_llvm( @@ -752,7 +743,6 @@ pub fn codegen_crate( source: cgu.previous_work_product(tcx), }, ); - true } CguReuse::PostLto => { submit_post_lto_module_to_llvm( @@ -763,9 +753,8 @@ pub fn codegen_crate( source: cgu.previous_work_product(tcx), }, ); - true } - }; + } } ongoing_codegen.codegen_finished(tcx); @@ -790,18 +779,13 @@ pub fn codegen_crate( impl CrateInfo { pub fn new(tcx: TyCtxt<'_>, target_cpu: String) -> CrateInfo { - let exported_symbols = tcx - .sess - .crate_types() + let crate_types = tcx.crate_types().to_vec(); + let exported_symbols = crate_types .iter() .map(|&c| (c, crate::back::linker::exported_symbols(tcx, c))) .collect(); - let linked_symbols = tcx - .sess - .crate_types() - .iter() - .map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))) - .collect(); + let linked_symbols = + crate_types.iter().map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))).collect(); let local_crate_name = tcx.crate_name(LOCAL_CRATE); let crate_attrs = tcx.hir().attrs(rustc_hir::CRATE_HIR_ID); let subsystem = attr::first_attr_value_str_by_name(crate_attrs, sym::windows_subsystem); @@ -840,6 +824,7 @@ impl CrateInfo { let mut info = CrateInfo { target_cpu, + crate_types, exported_symbols, linked_symbols, local_crate_name, @@ -909,11 +894,25 @@ impl CrateInfo { missing_weak_lang_items .iter() .map(|item| (format!("{prefix}{item}"), SymbolExportKind::Text)), - ) + ); + if tcx.allocator_kind(()).is_some() { + // At least one crate needs a global allocator. This crate may be placed + // after the crate that defines it in the linker order, in which case some + // linkers return an error. By adding the global allocator shim methods to + // the linked_symbols list, linking the generated symbols.o will ensure that + // circular dependencies involving the global allocator don't lead to linker + // errors. + linked_symbols.extend(ALLOCATOR_METHODS.iter().map(|method| { + ( + format!("{prefix}{}", global_fn_name(method.name).as_str()), + SymbolExportKind::Text, + ) + })); + } }); } - let embed_visualizers = tcx.sess.crate_types().iter().any(|&crate_type| match crate_type { + let embed_visualizers = tcx.crate_types().iter().any(|&crate_type| match crate_type { CrateType::Executable | CrateType::Dylib | CrateType::Cdylib => { // These are crate types for which we invoke the linker and can embed // NatVis visualizers. @@ -1010,7 +1009,7 @@ fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguR match compute_per_cgu_lto_type( &tcx.sess.lto(), &tcx.sess.opts, - &tcx.sess.crate_types(), + tcx.crate_types(), ModuleKind::Regular, ) { ComputedLtoType::No => CguReuse::PostLto, diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index d6c2301276262..f6936c80b7758 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -1,4 +1,4 @@ -use rustc_ast::{ast, MetaItemKind, NestedMetaItem}; +use rustc_ast::{ast, attr, MetaItemKind, NestedMetaItem}; use rustc_attr::{list_contains_name, InlineAttr, InstructionSetAttr, OptimizeAttr}; use rustc_errors::struct_span_err; use rustc_hir as hir; @@ -60,6 +60,14 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER; } + // When `no_builtins` is applied at the crate level, we should add the + // `no-builtins` attribute to each function to ensure it takes effect in LTO. + let crate_attrs = tcx.hir().attrs(rustc_hir::CRATE_HIR_ID); + let no_builtins = attr::contains_name(crate_attrs, sym::no_builtins); + if no_builtins { + codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_BUILTINS; + } + let supported_target_features = tcx.supported_target_features(LOCAL_CRATE); let mut inline_span = None; @@ -207,14 +215,19 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { } sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL, sym::track_caller => { - if !tcx.is_closure(did.to_def_id()) + let is_closure = tcx.is_closure(did.to_def_id()); + + if !is_closure && let Some(fn_sig) = fn_sig() && fn_sig.skip_binder().abi() != abi::Abi::Rust { struct_span_err!(tcx.sess, attr.span, E0737, "`#[track_caller]` requires Rust ABI") .emit(); } - if tcx.is_closure(did.to_def_id()) && !tcx.features().closure_track_caller { + if is_closure + && !tcx.features().closure_track_caller + && !attr.span.allows_unstable(sym::closure_track_caller) + { feature_err( &tcx.sess.parse_sess, sym::closure_track_caller, @@ -493,7 +506,22 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs { }); // #73631: closures inherit `#[target_feature]` annotations - if tcx.features().target_feature_11 && tcx.is_closure(did.to_def_id()) { + // + // If this closure is marked `#[inline(always)]`, simply skip adding `#[target_feature]`. + // + // At this point, `unsafe` has already been checked and `#[target_feature]` only affects codegen. + // Emitting both `#[inline(always)]` and `#[target_feature]` can potentially result in an + // ICE, because LLVM errors when the function fails to be inlined due to a target feature + // mismatch. + // + // Using `#[inline(always)]` implies that this closure will most likely be inlined into + // its parent function, which effectively inherits the features anyway. Boxing this closure + // would result in this closure being compiled without the inherited target features, but this + // is probably a poor usage of `#[inline(always)]` and easily avoided by not using the attribute. + if tcx.features().target_feature_11 + && tcx.is_closure(did.to_def_id()) + && codegen_fn_attrs.inline != InlineAttr::Always + { let owner_id = tcx.parent(did.to_def_id()); if tcx.def_kind(owner_id).has_codegen_attrs() { codegen_fn_attrs diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index e1abb73a504a3..5a68075991f16 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -132,7 +132,7 @@ pub fn build_langcall<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // all shifts). For 32- and 64-bit types, this matches the semantics // of Java. (See related discussion on #1877 and #10183.) -pub fn build_unchecked_lshift<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( +pub fn build_masked_lshift<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &mut Bx, lhs: Bx::Value, rhs: Bx::Value, @@ -143,7 +143,7 @@ pub fn build_unchecked_lshift<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx.shl(lhs, rhs) } -pub fn build_unchecked_rshift<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( +pub fn build_masked_rshift<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( bx: &mut Bx, lhs_t: Ty<'tcx>, lhs: Bx::Value, diff --git a/compiler/rustc_codegen_ssa/src/coverageinfo/ffi.rs b/compiler/rustc_codegen_ssa/src/coverageinfo/ffi.rs deleted file mode 100644 index 1791ce4b31559..0000000000000 --- a/compiler/rustc_codegen_ssa/src/coverageinfo/ffi.rs +++ /dev/null @@ -1,89 +0,0 @@ -use rustc_middle::mir::coverage::{CounterValueReference, MappedExpressionIndex}; - -/// Must match the layout of `LLVMRustCounterKind`. -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub enum CounterKind { - Zero = 0, - CounterValueReference = 1, - Expression = 2, -} - -/// A reference to an instance of an abstract "counter" that will yield a value in a coverage -/// report. Note that `id` has different interpretations, depending on the `kind`: -/// * For `CounterKind::Zero`, `id` is assumed to be `0` -/// * For `CounterKind::CounterValueReference`, `id` matches the `counter_id` of the injected -/// instrumentation counter (the `index` argument to the LLVM intrinsic -/// `instrprof.increment()`) -/// * For `CounterKind::Expression`, `id` is the index into the coverage map's array of -/// counter expressions. -/// -/// Corresponds to struct `llvm::coverage::Counter`. -/// -/// Must match the layout of `LLVMRustCounter`. -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub struct Counter { - // Important: The layout (order and types of fields) must match its C++ counterpart. - pub kind: CounterKind, - id: u32, -} - -impl Counter { - /// Constructs a new `Counter` of kind `Zero`. For this `CounterKind`, the - /// `id` is not used. - pub fn zero() -> Self { - Self { kind: CounterKind::Zero, id: 0 } - } - - /// Constructs a new `Counter` of kind `CounterValueReference`, and converts - /// the given 1-based counter_id to the required 0-based equivalent for - /// the `Counter` encoding. - pub fn counter_value_reference(counter_id: CounterValueReference) -> Self { - Self { kind: CounterKind::CounterValueReference, id: counter_id.zero_based_index() } - } - - /// Constructs a new `Counter` of kind `Expression`. - pub fn expression(mapped_expression_index: MappedExpressionIndex) -> Self { - Self { kind: CounterKind::Expression, id: mapped_expression_index.into() } - } - - /// Returns true if the `Counter` kind is `Zero`. - pub fn is_zero(&self) -> bool { - matches!(self.kind, CounterKind::Zero) - } - - /// An explicitly-named function to get the ID value, making it more obvious - /// that the stored value is now 0-based. - pub fn zero_based_id(&self) -> u32 { - debug_assert!(!self.is_zero(), "`id` is undefined for CounterKind::Zero"); - self.id - } -} - -/// Corresponds to enum `llvm::coverage::CounterExpression::ExprKind`. -/// -/// Must match the layout of `LLVMRustCounterExprKind`. -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub enum ExprKind { - Subtract = 0, - Add = 1, -} - -/// Corresponds to struct `llvm::coverage::CounterExpression`. -/// -/// Must match the layout of `LLVMRustCounterExpression`. -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub struct CounterExpression { - pub kind: ExprKind, - pub lhs: Counter, - pub rhs: Counter, -} - -impl CounterExpression { - pub fn new(lhs: Counter, kind: ExprKind, rhs: Counter) -> Self { - Self { kind, lhs, rhs } - } -} diff --git a/compiler/rustc_codegen_ssa/src/coverageinfo/map.rs b/compiler/rustc_codegen_ssa/src/coverageinfo/map.rs deleted file mode 100644 index e4da3b8de0512..0000000000000 --- a/compiler/rustc_codegen_ssa/src/coverageinfo/map.rs +++ /dev/null @@ -1,347 +0,0 @@ -pub use super::ffi::*; - -use rustc_index::{IndexSlice, IndexVec}; -use rustc_middle::mir::coverage::{ - CodeRegion, CounterValueReference, ExpressionOperandId, InjectedExpressionId, - InjectedExpressionIndex, MappedExpressionIndex, Op, -}; -use rustc_middle::ty::Instance; -use rustc_middle::ty::TyCtxt; - -#[derive(Clone, Debug, PartialEq)] -pub struct Expression { - lhs: ExpressionOperandId, - op: Op, - rhs: ExpressionOperandId, - region: Option, -} - -/// Collects all of the coverage regions associated with (a) injected counters, (b) counter -/// expressions (additions or subtraction), and (c) unreachable regions (always counted as zero), -/// for a given Function. Counters and counter expressions have non-overlapping `id`s because they -/// can both be operands in an expression. This struct also stores the `function_source_hash`, -/// computed during instrumentation, and forwarded with counters. -/// -/// Note, it may be important to understand LLVM's definitions of `unreachable` regions versus "gap -/// regions" (or "gap areas"). A gap region is a code region within a counted region (either counter -/// or expression), but the line or lines in the gap region are not executable (such as lines with -/// only whitespace or comments). According to LLVM Code Coverage Mapping documentation, "A count -/// for a gap area is only used as the line execution count if there are no other regions on a -/// line." -#[derive(Debug)] -pub struct FunctionCoverage<'tcx> { - instance: Instance<'tcx>, - source_hash: u64, - is_used: bool, - counters: IndexVec>, - expressions: IndexVec>, - unreachable_regions: Vec, -} - -impl<'tcx> FunctionCoverage<'tcx> { - /// Creates a new set of coverage data for a used (called) function. - pub fn new(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { - Self::create(tcx, instance, true) - } - - /// Creates a new set of coverage data for an unused (never called) function. - pub fn unused(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Self { - Self::create(tcx, instance, false) - } - - fn create(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, is_used: bool) -> Self { - let coverageinfo = tcx.coverageinfo(instance.def); - debug!( - "FunctionCoverage::create(instance={:?}) has coverageinfo={:?}. is_used={}", - instance, coverageinfo, is_used - ); - Self { - instance, - source_hash: 0, // will be set with the first `add_counter()` - is_used, - counters: IndexVec::from_elem_n(None, coverageinfo.num_counters as usize), - expressions: IndexVec::from_elem_n(None, coverageinfo.num_expressions as usize), - unreachable_regions: Vec::new(), - } - } - - /// Returns true for a used (called) function, and false for an unused function. - pub fn is_used(&self) -> bool { - self.is_used - } - - /// Sets the function source hash value. If called multiple times for the same function, all - /// calls should have the same hash value. - pub fn set_function_source_hash(&mut self, source_hash: u64) { - if self.source_hash == 0 { - self.source_hash = source_hash; - } else { - debug_assert_eq!(source_hash, self.source_hash); - } - } - - /// Adds a code region to be counted by an injected counter intrinsic. - pub fn add_counter(&mut self, id: CounterValueReference, region: CodeRegion) { - if let Some(previous_region) = self.counters[id].replace(region.clone()) { - assert_eq!(previous_region, region, "add_counter: code region for id changed"); - } - } - - /// Both counters and "counter expressions" (or simply, "expressions") can be operands in other - /// expressions. Expression IDs start from `u32::MAX` and go down, so the range of expression - /// IDs will not overlap with the range of counter IDs. Counters and expressions can be added in - /// any order, and expressions can still be assigned contiguous (though descending) IDs, without - /// knowing what the last counter ID will be. - /// - /// When storing the expression data in the `expressions` vector in the `FunctionCoverage` - /// struct, its vector index is computed, from the given expression ID, by subtracting from - /// `u32::MAX`. - /// - /// Since the expression operands (`lhs` and `rhs`) can reference either counters or - /// expressions, an operand that references an expression also uses its original ID, descending - /// from `u32::MAX`. Theses operands are translated only during code generation, after all - /// counters and expressions have been added. - pub fn add_counter_expression( - &mut self, - expression_id: InjectedExpressionId, - lhs: ExpressionOperandId, - op: Op, - rhs: ExpressionOperandId, - region: Option, - ) { - debug!( - "add_counter_expression({:?}, lhs={:?}, op={:?}, rhs={:?} at {:?}", - expression_id, lhs, op, rhs, region - ); - let expression_index = self.expression_index(u32::from(expression_id)); - debug_assert!( - expression_index.as_usize() < self.expressions.len(), - "expression_index {} is out of range for expressions.len() = {} - for {:?}", - expression_index.as_usize(), - self.expressions.len(), - self, - ); - if let Some(previous_expression) = self.expressions[expression_index].replace(Expression { - lhs, - op, - rhs, - region: region.clone(), - }) { - assert_eq!( - previous_expression, - Expression { lhs, op, rhs, region }, - "add_counter_expression: expression for id changed" - ); - } - } - - /// Add a region that will be marked as "unreachable", with a constant "zero counter". - pub fn add_unreachable_region(&mut self, region: CodeRegion) { - self.unreachable_regions.push(region) - } - - /// Return the source hash, generated from the HIR node structure, and used to indicate whether - /// or not the source code structure changed between different compilations. - pub fn source_hash(&self) -> u64 { - self.source_hash - } - - /// Generate an array of CounterExpressions, and an iterator over all `Counter`s and their - /// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create - /// `CounterMappingRegion`s. - pub fn get_expressions_and_counter_regions( - &self, - ) -> (Vec, impl Iterator) { - assert!( - self.source_hash != 0 || !self.is_used, - "No counters provided the source_hash for used function: {:?}", - self.instance - ); - - let counter_regions = self.counter_regions(); - let (counter_expressions, expression_regions) = self.expressions_with_regions(); - let unreachable_regions = self.unreachable_regions(); - - let counter_regions = - counter_regions.chain(expression_regions.into_iter().chain(unreachable_regions)); - (counter_expressions, counter_regions) - } - - fn counter_regions(&self) -> impl Iterator { - self.counters.iter_enumerated().filter_map(|(index, entry)| { - // Option::map() will return None to filter out missing counters. This may happen - // if, for example, a MIR-instrumented counter is removed during an optimization. - entry.as_ref().map(|region| (Counter::counter_value_reference(index), region)) - }) - } - - fn expressions_with_regions( - &self, - ) -> (Vec, impl Iterator) { - let mut counter_expressions = Vec::with_capacity(self.expressions.len()); - let mut expression_regions = Vec::with_capacity(self.expressions.len()); - let mut new_indexes = IndexVec::from_elem_n(None, self.expressions.len()); - - // This closure converts any `Expression` operand (`lhs` or `rhs` of the `Op::Add` or - // `Op::Subtract` operation) into its native `llvm::coverage::Counter::CounterKind` type - // and value. Operand ID value `0` maps to `CounterKind::Zero`; values in the known range - // of injected LLVM counters map to `CounterKind::CounterValueReference` (and the value - // matches the injected counter index); and any other value is converted into a - // `CounterKind::Expression` with the expression's `new_index`. - // - // Expressions will be returned from this function in a sequential vector (array) of - // `CounterExpression`, so the expression IDs must be mapped from their original, - // potentially sparse set of indexes, originally in reverse order from `u32::MAX`. - // - // An `Expression` as an operand will have already been encountered as an `Expression` with - // operands, so its new_index will already have been generated (as a 1-up index value). - // (If an `Expression` as an operand does not have a corresponding new_index, it was - // probably optimized out, after the expression was injected into the MIR, so it will - // get a `CounterKind::Zero` instead.) - // - // In other words, an `Expression`s at any given index can include other expressions as - // operands, but expression operands can only come from the subset of expressions having - // `expression_index`s lower than the referencing `Expression`. Therefore, it is - // reasonable to look up the new index of an expression operand while the `new_indexes` - // vector is only complete up to the current `ExpressionIndex`. - let id_to_counter = |new_indexes: &IndexSlice< - InjectedExpressionIndex, - Option, - >, - id: ExpressionOperandId| { - if id == ExpressionOperandId::ZERO { - Some(Counter::zero()) - } else if id.index() < self.counters.len() { - debug_assert!( - id.index() > 0, - "ExpressionOperandId indexes for counters are 1-based, but this id={}", - id.index() - ); - // Note: Some codegen-injected Counters may be only referenced by `Expression`s, - // and may not have their own `CodeRegion`s, - let index = CounterValueReference::from(id.index()); - // Note, the conversion to LLVM `Counter` adjusts the index to be zero-based. - Some(Counter::counter_value_reference(index)) - } else { - let index = self.expression_index(u32::from(id)); - self.expressions - .get(index) - .expect("expression id is out of range") - .as_ref() - // If an expression was optimized out, assume it would have produced a count - // of zero. This ensures that expressions dependent on optimized-out - // expressions are still valid. - .map_or(Some(Counter::zero()), |_| new_indexes[index].map(Counter::expression)) - } - }; - - for (original_index, expression) in - self.expressions.iter_enumerated().filter_map(|(original_index, entry)| { - // Option::map() will return None to filter out missing expressions. This may happen - // if, for example, a MIR-instrumented expression is removed during an optimization. - entry.as_ref().map(|expression| (original_index, expression)) - }) - { - let optional_region = &expression.region; - let Expression { lhs, op, rhs, .. } = *expression; - - if let Some(Some((lhs_counter, mut rhs_counter))) = id_to_counter(&new_indexes, lhs) - .map(|lhs_counter| { - id_to_counter(&new_indexes, rhs).map(|rhs_counter| (lhs_counter, rhs_counter)) - }) - { - if lhs_counter.is_zero() && op.is_subtract() { - // The left side of a subtraction was probably optimized out. As an example, - // a branch condition might be evaluated as a constant expression, and the - // branch could be removed, dropping unused counters in the process. - // - // Since counters are unsigned, we must assume the result of the expression - // can be no more and no less than zero. An expression known to evaluate to zero - // does not need to be added to the coverage map. - // - // Coverage test `loops_branches.rs` includes multiple variations of branches - // based on constant conditional (literal `true` or `false`), and demonstrates - // that the expected counts are still correct. - debug!( - "Expression subtracts from zero (assume unreachable): \ - original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", - original_index, lhs, op, rhs, optional_region, - ); - rhs_counter = Counter::zero(); - } - debug_assert!( - lhs_counter.is_zero() - // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` - || ((lhs_counter.zero_based_id() as usize) - <= usize::max(self.counters.len(), self.expressions.len())), - "lhs id={} > both counters.len()={} and expressions.len()={} - ({:?} {:?} {:?})", - lhs_counter.zero_based_id(), - self.counters.len(), - self.expressions.len(), - lhs_counter, - op, - rhs_counter, - ); - - debug_assert!( - rhs_counter.is_zero() - // Note: with `as usize` the ID _could_ overflow/wrap if `usize = u16` - || ((rhs_counter.zero_based_id() as usize) - <= usize::max(self.counters.len(), self.expressions.len())), - "rhs id={} > both counters.len()={} and expressions.len()={} - ({:?} {:?} {:?})", - rhs_counter.zero_based_id(), - self.counters.len(), - self.expressions.len(), - lhs_counter, - op, - rhs_counter, - ); - - // Both operands exist. `Expression` operands exist in `self.expressions` and have - // been assigned a `new_index`. - let mapped_expression_index = - MappedExpressionIndex::from(counter_expressions.len()); - let expression = CounterExpression::new( - lhs_counter, - match op { - Op::Add => ExprKind::Add, - Op::Subtract => ExprKind::Subtract, - }, - rhs_counter, - ); - debug!( - "Adding expression {:?} = {:?}, region: {:?}", - mapped_expression_index, expression, optional_region - ); - counter_expressions.push(expression); - new_indexes[original_index] = Some(mapped_expression_index); - if let Some(region) = optional_region { - expression_regions.push((Counter::expression(mapped_expression_index), region)); - } - } else { - bug!( - "expression has one or more missing operands \ - original_index={:?}, lhs={:?}, op={:?}, rhs={:?}, region={:?}", - original_index, - lhs, - op, - rhs, - optional_region, - ); - } - } - (counter_expressions, expression_regions.into_iter()) - } - - fn unreachable_regions(&self) -> impl Iterator { - self.unreachable_regions.iter().map(|region| (Counter::zero(), region)) - } - - fn expression_index(&self, id_descending_from_max: u32) -> InjectedExpressionIndex { - debug_assert!(id_descending_from_max >= self.counters.len() as u32); - InjectedExpressionIndex::from(u32::MAX - id_descending_from_max) - } -} diff --git a/compiler/rustc_codegen_ssa/src/coverageinfo/mod.rs b/compiler/rustc_codegen_ssa/src/coverageinfo/mod.rs deleted file mode 100644 index 569fd3f1a516d..0000000000000 --- a/compiler/rustc_codegen_ssa/src/coverageinfo/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod ffi; -pub mod map; diff --git a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs index 6297f91341d44..067c824aba03a 100644 --- a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs +++ b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs @@ -17,8 +17,8 @@ use rustc_hir::def_id::DefId; use rustc_hir::definitions::{DefPathData, DefPathDataName, DisambiguatedDefPathData}; use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Mutability}; use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, ExistentialProjection, ParamEnv, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; use rustc_target::abi::Integer; use smallvec::SmallVec; @@ -77,7 +77,7 @@ fn push_debuginfo_type_name<'tcx>( ty::Uint(uint_ty) => output.push_str(uint_ty.name_str()), ty::Float(float_ty) => output.push_str(float_ty.name_str()), ty::Foreign(def_id) => push_item_name(tcx, def_id, qualified, output), - ty::Adt(def, substs) => { + ty::Adt(def, args) => { // `layout_for_cpp_like_fallback` will be `Some` if we want to use the fallback encoding. let layout_for_cpp_like_fallback = if cpp_like_debuginfo && def.is_enum() { match tcx.layout_of(ParamEnv::reveal_all().and(t)) { @@ -93,8 +93,7 @@ fn push_debuginfo_type_name<'tcx>( Err(e) => { // Computing the layout can still fail here, e.g. if the target architecture // cannot represent the type. See https://github.com/rust-lang/rust/issues/94961. - // FIXME: migrate once `rustc_middle::mir::interpret::InterpError` is translatable. - tcx.sess.fatal(format!("{}", e)); + tcx.sess.emit_fatal(e.into_diagnostic()); } } } else { @@ -107,14 +106,14 @@ fn push_debuginfo_type_name<'tcx>( ty_and_layout, &|output, visited| { push_item_name(tcx, def.did(), true, output); - push_generic_params_internal(tcx, substs, output, visited); + push_generic_params_internal(tcx, args, output, visited); }, output, visited, ); } else { push_item_name(tcx, def.did(), qualified, output); - push_generic_params_internal(tcx, substs, output, visited); + push_generic_params_internal(tcx, args, output, visited); } } ty::Tuple(component_types) => { @@ -239,7 +238,7 @@ fn push_debuginfo_type_name<'tcx>( tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), principal); push_item_name(tcx, principal.def_id, qualified, output); let principal_has_generic_params = - push_generic_params_internal(tcx, principal.substs, output, visited); + push_generic_params_internal(tcx, principal.args, output, visited); let projection_bounds: SmallVec<[_; 4]> = trait_data .projection_bounds() @@ -394,7 +393,7 @@ fn push_debuginfo_type_name<'tcx>( // processing visited.remove(&t); } - ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => { + ty::Closure(def_id, args) | ty::Generator(def_id, args, ..) => { // Name will be "{closure_env#0}", "{generator_env#0}", or // "{async_fn_env#0}", etc. // In the case of cpp-like debuginfo, the name additionally gets wrapped inside of @@ -404,18 +403,18 @@ fn push_debuginfo_type_name<'tcx>( msvc_enum_fallback( ty_and_layout, &|output, visited| { - push_closure_or_generator_name(tcx, def_id, substs, true, output, visited); + push_closure_or_generator_name(tcx, def_id, args, true, output, visited); }, output, visited, ); } else { - push_closure_or_generator_name(tcx, def_id, substs, qualified, output, visited); + push_closure_or_generator_name(tcx, def_id, args, qualified, output, visited); } } // Type parameters from polymorphized functions. ty::Param(_) => { - write!(output, "{:?}", t).unwrap(); + write!(output, "{t:?}").unwrap(); } ty::Error(_) | ty::Infer(_) @@ -517,7 +516,7 @@ pub fn compute_debuginfo_vtable_name<'tcx>( tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), trait_ref); push_item_name(tcx, trait_ref.def_id, true, &mut vtable_name); visited.clear(); - push_generic_params_internal(tcx, trait_ref.substs, &mut vtable_name, &mut visited); + push_generic_params_internal(tcx, trait_ref.args, &mut vtable_name, &mut visited); } else { vtable_name.push('_'); } @@ -566,9 +565,9 @@ fn push_disambiguated_special_name( output: &mut String, ) { if cpp_like_debuginfo { - write!(output, "{}${}", label, disambiguator).unwrap(); + write!(output, "{label}${disambiguator}").unwrap(); } else { - write!(output, "{{{}#{}}}", label, disambiguator).unwrap(); + write!(output, "{{{label}#{disambiguator}}}").unwrap(); } } @@ -610,21 +609,21 @@ fn push_unqualified_item_name( fn push_generic_params_internal<'tcx>( tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, output: &mut String, visited: &mut FxHashSet>, ) -> bool { - if substs.non_erasable_generics().next().is_none() { + if args.non_erasable_generics().next().is_none() { return false; } - debug_assert_eq!(substs, tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), substs)); + debug_assert_eq!(args, tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), args)); let cpp_like_debuginfo = cpp_like_debuginfo(tcx); output.push('<'); - for type_parameter in substs.non_erasable_generics() { + for type_parameter in args.non_erasable_generics() { match type_parameter { GenericArgKind::Type(type_parameter) => { push_debuginfo_type_name(tcx, type_parameter, true, output, visited); @@ -652,15 +651,15 @@ fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut S ty::Int(ity) => { let bits = ct.eval_bits(tcx, ty::ParamEnv::reveal_all(), ct.ty()); let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128; - write!(output, "{}", val) + write!(output, "{val}") } ty::Uint(_) => { let val = ct.eval_bits(tcx, ty::ParamEnv::reveal_all(), ct.ty()); - write!(output, "{}", val) + write!(output, "{val}") } ty::Bool => { let val = ct.try_eval_bool(tcx, ty::ParamEnv::reveal_all()).unwrap(); - write!(output, "{}", val) + write!(output, "{val}") } _ => { // If we cannot evaluate the constant to a known type, we fall back @@ -679,9 +678,9 @@ fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut S }); if cpp_like_debuginfo(tcx) { - write!(output, "CONST${:x}", hash_short) + write!(output, "CONST${hash_short:x}") } else { - write!(output, "{{CONST#{:x}}}", hash_short) + write!(output, "{{CONST#{hash_short:x}}}") } } }, @@ -689,16 +688,20 @@ fn push_const_param<'tcx>(tcx: TyCtxt<'tcx>, ct: ty::Const<'tcx>, output: &mut S .unwrap(); } -pub fn push_generic_params<'tcx>(tcx: TyCtxt<'tcx>, substs: SubstsRef<'tcx>, output: &mut String) { +pub fn push_generic_params<'tcx>( + tcx: TyCtxt<'tcx>, + args: GenericArgsRef<'tcx>, + output: &mut String, +) { let _prof = tcx.prof.generic_activity("compute_debuginfo_type_name"); let mut visited = FxHashSet::default(); - push_generic_params_internal(tcx, substs, output, &mut visited); + push_generic_params_internal(tcx, args, output, &mut visited); } fn push_closure_or_generator_name<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, qualified: bool, output: &mut String, visited: &mut FxHashSet>, @@ -732,10 +735,10 @@ fn push_closure_or_generator_name<'tcx>( let enclosing_fn_def_id = tcx.typeck_root_def_id(def_id); let generics = tcx.generics_of(enclosing_fn_def_id); - // Truncate the substs to the length of the above generics. This will cut off + // Truncate the args to the length of the above generics. This will cut off // anything closure- or generator-specific. - let substs = substs.truncate_to(tcx, generics); - push_generic_params_internal(tcx, substs, output, visited); + let args = args.truncate_to(tcx, generics); + push_generic_params_internal(tcx, args, output, visited); } fn push_close_angle_bracket(cpp_like_debuginfo: bool, output: &mut String) { @@ -749,7 +752,7 @@ fn push_close_angle_bracket(cpp_like_debuginfo: bool, output: &mut String) { } fn pop_close_angle_bracket(output: &mut String) { - assert!(output.ends_with('>'), "'output' does not end with '>': {}", output); + assert!(output.ends_with('>'), "'output' does not end with '>': {output}"); output.pop(); if output.ends_with(' ') { output.pop(); diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index cf4893b822651..b7d8b9b45bf16 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -82,6 +82,12 @@ impl IntoDiagnosticArg for DebugArgPath<'_> { } } +#[derive(Diagnostic)] +#[diag(codegen_ssa_binary_output_to_tty)] +pub struct BinaryOutputToTty { + pub shorthand: &'static str, +} + #[derive(Diagnostic)] #[diag(codegen_ssa_ignoring_emit_path)] pub struct IgnoringEmitPath { @@ -171,31 +177,31 @@ impl IntoDiagnostic<'_> for ThorinErrorWrapper { } thorin::Error::NamelessSection(_, offset) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_section_without_name); - diag.set_arg("offset", format!("0x{:08x}", offset)); + diag.set_arg("offset", format!("0x{offset:08x}")); diag } thorin::Error::RelocationWithInvalidSymbol(section, offset) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_relocation_with_invalid_symbol); diag.set_arg("section", section); - diag.set_arg("offset", format!("0x{:08x}", offset)); + diag.set_arg("offset", format!("0x{offset:08x}")); diag } thorin::Error::MultipleRelocations(section, offset) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_multiple_relocations); diag.set_arg("section", section); - diag.set_arg("offset", format!("0x{:08x}", offset)); + diag.set_arg("offset", format!("0x{offset:08x}")); diag } thorin::Error::UnsupportedRelocation(section, offset) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_unsupported_relocation); diag.set_arg("section", section); - diag.set_arg("offset", format!("0x{:08x}", offset)); + diag.set_arg("offset", format!("0x{offset:08x}")); diag } thorin::Error::MissingDwoName(id) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_missing_dwo_name); - diag.set_arg("id", format!("0x{:08x}", id)); + diag.set_arg("id", format!("0x{id:08x}")); diag } thorin::Error::NoCompilationUnits => { @@ -245,7 +251,7 @@ impl IntoDiagnostic<'_> for ThorinErrorWrapper { } thorin::Error::StrAtOffset(_, offset) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_str_at_offset); - diag.set_arg("offset", format!("0x{:08x}", offset)); + diag.set_arg("offset", format!("0x{offset:08x}")); diag } thorin::Error::ParseIndex(_, section) => { @@ -255,7 +261,7 @@ impl IntoDiagnostic<'_> for ThorinErrorWrapper { } thorin::Error::UnitNotInIndex(unit) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_unit_not_in_index); - diag.set_arg("unit", format!("0x{:08x}", unit)); + diag.set_arg("unit", format!("0x{unit:08x}")); diag } thorin::Error::RowNotInIndex(_, row) => { @@ -269,7 +275,7 @@ impl IntoDiagnostic<'_> for ThorinErrorWrapper { } thorin::Error::EmptyUnit(unit) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_empty_unit); - diag.set_arg("unit", format!("0x{:08x}", unit)); + diag.set_arg("unit", format!("0x{unit:08x}")); diag } thorin::Error::MultipleDebugInfoSection => { @@ -286,12 +292,12 @@ impl IntoDiagnostic<'_> for ThorinErrorWrapper { } thorin::Error::DuplicateUnit(unit) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_duplicate_unit); - diag.set_arg("unit", format!("0x{:08x}", unit)); + diag.set_arg("unit", format!("0x{unit:08x}")); diag } thorin::Error::MissingReferencedUnit(unit) => { diag = handler.struct_err(fluent::codegen_ssa_thorin_missing_referenced_unit); - diag.set_arg("unit", format!("0x{:08x}", unit)); + diag.set_arg("unit", format!("0x{unit:08x}")); diag } thorin::Error::NoOutputObjectCreated => { @@ -336,7 +342,7 @@ pub struct LinkingFailed<'a> { pub linker_path: &'a PathBuf, pub exit_status: ExitStatus, pub command: &'a Command, - pub escaped_output: &'a str, + pub escaped_output: String, } impl IntoDiagnostic<'_> for LinkingFailed<'_> { @@ -345,11 +351,13 @@ impl IntoDiagnostic<'_> for LinkingFailed<'_> { diag.set_arg("linker_path", format!("{}", self.linker_path.display())); diag.set_arg("exit_status", format!("{}", self.exit_status)); + let contains_undefined_ref = self.escaped_output.contains("undefined reference to"); + diag.note(format!("{:?}", self.command)).note(self.escaped_output); // Trying to match an error from OS linkers // which by now we have no way to translate. - if self.escaped_output.contains("undefined reference to") { + if contains_undefined_ref { diag.note(fluent::codegen_ssa_extern_funcs_not_found) .note(fluent::codegen_ssa_specify_libraries_to_link) .note(fluent::codegen_ssa_use_cargo_directive); @@ -447,6 +455,12 @@ pub struct LinkerFileStem; #[diag(codegen_ssa_static_library_native_artifacts)] pub struct StaticLibraryNativeArtifacts; +#[derive(Diagnostic)] +#[diag(codegen_ssa_static_library_native_artifacts_to_file)] +pub struct StaticLibraryNativeArtifactsToFile<'a> { + pub path: &'a Path, +} + #[derive(Diagnostic)] #[diag(codegen_ssa_link_script_unavailable)] pub struct LinkScriptUnavailable; @@ -1015,3 +1029,9 @@ pub struct TargetFeatureSafeTrait { #[label(codegen_ssa_label_def)] pub def: Span, } + +#[derive(Diagnostic)] +#[diag(codegen_ssa_error_creating_remark_dir)] +pub struct ErrorCreatingRemarkDir { + pub error: std::io::Error, +} diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 31854c7f4c4e0..7bed3fa615034 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -2,8 +2,8 @@ #![feature(associated_type_bounds)] #![feature(box_patterns)] #![feature(if_let_guard)] -#![feature(int_roundings)] #![feature(let_chains)] +#![feature(negative_impls)] #![feature(never_type)] #![feature(strict_provenance)] #![feature(try_blocks)] @@ -47,7 +47,6 @@ pub mod back; pub mod base; pub mod codegen_attrs; pub mod common; -pub mod coverageinfo; pub mod debuginfo; pub mod errors; pub mod glue; @@ -150,6 +149,7 @@ impl From<&cstore::NativeLib> for NativeLib { #[derive(Debug, Encodable, Decodable)] pub struct CrateInfo { pub target_cpu: String, + pub crate_types: Vec, pub exported_symbols: FxHashMap>, pub linked_symbols: FxHashMap>, pub local_crate_name: Symbol, diff --git a/compiler/rustc_codegen_ssa/src/meth.rs b/compiler/rustc_codegen_ssa/src/meth.rs index a8b935bd65ce1..12146a54d3b92 100644 --- a/compiler/rustc_codegen_ssa/src/meth.rs +++ b/compiler/rustc_codegen_ssa/src/meth.rs @@ -1,6 +1,6 @@ use crate::traits::*; -use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; +use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_session::config::Lto; use rustc_symbol_mangling::typeid_for_trait_ref; use rustc_target::abi::call::FnAbi; @@ -23,7 +23,6 @@ impl<'a, 'tcx> VirtualIndex { // Load the data pointer from the object. debug!("get_fn({llvtable:?}, {ty:?}, {self:?})"); let llty = bx.fn_ptr_backend_type(fn_abi); - let llvtable = bx.pointercast(llvtable, bx.type_ptr_to(llty)); if bx.cx().sess().opts.unstable_opts.virtual_function_elimination && bx.cx().sess().lto() == Lto::Fat @@ -33,7 +32,7 @@ impl<'a, 'tcx> VirtualIndex { .unwrap(); let vtable_byte_offset = self.0 * bx.data_layout().pointer_size.bytes(); let func = bx.type_checked_load(llvtable, vtable_byte_offset, typeid); - bx.pointercast(func, llty) + func } else { let ptr_align = bx.tcx().data_layout.pointer_align.abi; let gep = bx.inbounds_gep(llty, llvtable, &[bx.const_usize(self.0)]); @@ -54,7 +53,6 @@ impl<'a, 'tcx> VirtualIndex { debug!("get_int({:?}, {:?})", llvtable, self); let llty = bx.type_isize(); - let llvtable = bx.pointercast(llvtable, bx.type_ptr_to(llty)); let usize_align = bx.tcx().data_layout.pointer_align.abi; let gep = bx.inbounds_gep(llty, llvtable, &[bx.const_usize(self.0)]); let ptr = bx.load(llty, gep, usize_align); diff --git a/compiler/rustc_codegen_ssa/src/mir/analyze.rs b/compiler/rustc_codegen_ssa/src/mir/analyze.rs index 835074806e90f..22c1f05974ddd 100644 --- a/compiler/rustc_codegen_ssa/src/mir/analyze.rs +++ b/compiler/rustc_codegen_ssa/src/mir/analyze.rs @@ -234,7 +234,6 @@ impl<'mir, 'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> Visitor<'tcx> | PlaceContext::NonMutatingUse( NonMutatingUseContext::Inspect | NonMutatingUseContext::SharedBorrow - | NonMutatingUseContext::UniqueBorrow | NonMutatingUseContext::ShallowBorrow | NonMutatingUseContext::AddressOf | NonMutatingUseContext::Projection, diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 1f5f5b69d4dc8..4f26383ed05f0 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1,5 +1,5 @@ use super::operand::OperandRef; -use super::operand::OperandValue::{Immediate, Pair, Ref}; +use super::operand::OperandValue::{Immediate, Pair, Ref, ZeroSized}; use super::place::PlaceRef; use super::{CachedLlbb, FunctionCx, LocalRef}; @@ -23,6 +23,8 @@ use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode, Reg}; use rustc_target::abi::{self, HasDataLayout, WrappingRange}; use rustc_target::spec::abi::Abi; +use std::cmp; + // Indicates if we are in the middle of merging a BB's successor into it. This // can happen when BB jumps directly to its successor and the successor has no // other predecessors. @@ -79,8 +81,8 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> { lltarget = fx.landing_pad_for(target); } if is_cleanupret { - // MSVC cross-funclet jump - need a trampoline - debug_assert!(base::wants_msvc_seh(fx.cx.tcx().sess)); + // Cross-funclet jump - need a trampoline + debug_assert!(base::wants_new_eh_instructions(fx.cx.tcx().sess)); debug!("llbb_with_cleanup: creating cleanup trampoline for {:?}", target); let name = &format!("{:?}_cleanup_trampoline_{:?}", self.bb, target); let trampoline_llbb = Bx::append_block(fx.cx, fx.llfn, name); @@ -177,9 +179,16 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> { mir::UnwindAction::Continue => None, mir::UnwindAction::Unreachable => None, mir::UnwindAction::Terminate => { - if fx.mir[self.bb].is_cleanup && base::wants_msvc_seh(fx.cx.tcx().sess) { - // SEH will abort automatically if an exception tries to + if fx.mir[self.bb].is_cleanup && base::wants_new_eh_instructions(fx.cx.tcx().sess) { + // MSVC SEH will abort automatically if an exception tries to // propagate out from cleanup. + + // FIXME(@mirkootter): For wasm, we currently do not support terminate during + // cleanup, because this requires a few more changes: The current code + // caches the `terminate_block` for each function; funclet based code - however - + // requires a different terminate_block for each funclet + // Until this is implemented, we just do not unwind inside cleanup blocks + None } else { Some(fx.terminate_block()) @@ -427,10 +436,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { assert_eq!(align, op.layout.align.abi, "return place is unaligned!"); llval } + ZeroSized => bug!("ZST return value shouldn't be in PassMode::Cast"), }; let ty = bx.cast_backend_type(cast_ty); - let addr = bx.pointercast(llslot, bx.type_ptr_to(ty)); - bx.load(ty, addr, self.fn_abi.ret.layout.align.abi) + bx.load(ty, llslot, self.fn_abi.ret.layout.align.abi) } }; bx.ret(llval); @@ -483,7 +492,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // let virtual_drop = Instance { def: ty::InstanceDef::Virtual(drop_fn.def_id(), 0), - substs: drop_fn.substs, + args: drop_fn.args, }; debug!("ty = {:?}", ty); debug!("drop_fn = {:?}", drop_fn); @@ -523,7 +532,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // SO THEN WE CAN USE THE ABOVE CODE. let virtual_drop = Instance { def: ty::InstanceDef::Virtual(drop_fn.def_id(), 0), - substs: drop_fn.substs, + args: drop_fn.args, }; debug!("ty = {:?}", ty); debug!("drop_fn = {:?}", drop_fn); @@ -615,7 +624,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { AssertKind::MisalignedPointerDereference { ref required, ref found } => { let required = self.codegen_operand(bx, required).immediate(); let found = self.codegen_operand(bx, found).immediate(); - // It's `fn panic_bounds_check(index: usize, len: usize)`, + // It's `fn panic_misaligned_pointer_dereference(required: usize, found: usize)`, // and `#[track_caller]` adds an implicit third argument. (LangItem::PanicMisalignedPointerDereference, vec![required, found, location]) } @@ -679,7 +688,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // which mentions the offending type, even from a const context. let panic_intrinsic = intrinsic.and_then(|s| ValidityRequirement::from_intrinsic(s)); if let Some(requirement) = panic_intrinsic { - let ty = instance.unwrap().substs.type_at(0); + let ty = instance.unwrap().args.type_at(0); let do_panic = !bx .tcx() @@ -693,13 +702,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { with_no_trimmed_paths!({ if layout.abi.is_uninhabited() { // Use this error even for the other intrinsics as it is more precise. - format!("attempted to instantiate uninhabited type `{}`", ty) + format!("attempted to instantiate uninhabited type `{ty}`") } else if requirement == ValidityRequirement::Zero { - format!("attempted to zero-initialize type `{}`, which is invalid", ty) + format!("attempted to zero-initialize type `{ty}`, which is invalid") } else { format!( - "attempted to leave type `{}` uninitialized, which is invalid", - ty + "attempted to leave type `{ty}` uninitialized, which is invalid" ) } }) @@ -752,13 +760,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let callee = self.codegen_operand(bx, func); let (instance, mut llfn) = match *callee.layout.ty.kind() { - ty::FnDef(def_id, substs) => ( + ty::FnDef(def_id, args) => ( Some( ty::Instance::expect_resolve( bx.tcx(), ty::ParamEnv::reveal_all(), def_id, - substs, + args, ) .polymorphize(bx.tcx()), ), @@ -843,9 +851,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { Some(intrinsic) => { let dest = match ret_dest { _ if fn_abi.ret.is_indirect() => llargs[0], - ReturnDest::Nothing => { - bx.const_undef(bx.type_ptr_to(bx.arg_memory_ty(&fn_abi.ret))) - } + ReturnDest::Nothing => bx.const_undef(bx.type_ptr()), ReturnDest::IndirectOperand(dst, _) | ReturnDest::Store(dst) => dst.llval, ReturnDest::DirectOperand(_) => { bug!("Cannot use direct operand with an intrinsic call") @@ -856,19 +862,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { .iter() .enumerate() .map(|(i, arg)| { - // The indices passed to simd_shuffle* in the + // The indices passed to simd_shuffle in the // third argument must be constant. This is // checked by const-qualification, which also // promotes any complex rvalues to constants. - if i == 2 && intrinsic.as_str().starts_with("simd_shuffle") { + if i == 2 && intrinsic == sym::simd_shuffle { if let mir::Operand::Constant(constant) = arg { - let c = self.eval_mir_constant(constant); - let (llval, ty) = self.simd_shuffle_indices( - &bx, - constant.span, - self.monomorphize(constant.ty()), - c, - ); + let (llval, ty) = self.simd_shuffle_indices(&bx, constant); return OperandRef { val: Immediate(llval), layout: bx.layout_of(ty), @@ -1041,10 +1041,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { assert_eq!( fn_abi.args.len(), mir_args + 1, - "#[track_caller] fn's must have 1 more argument in their ABI than in their MIR: {:?} {:?} {:?}", - instance, - fn_span, - fn_abi, + "#[track_caller] fn's must have 1 more argument in their ABI than in their MIR: {instance:?} {fn_span:?} {fn_abi:?}", ); let location = self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info }); @@ -1123,12 +1120,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } mir::InlineAsmOperand::SymFn { ref value } => { let literal = self.monomorphize(value.literal); - if let ty::FnDef(def_id, substs) = *literal.ty().kind() { + if let ty::FnDef(def_id, args) = *literal.ty().kind() { let instance = ty::Instance::resolve_for_fn_ptr( bx.tcx(), ty::ParamEnv::reveal_all(), def_id, - substs, + args, ) .unwrap(); InlineAsmOperandRef::SymFn { instance } @@ -1256,7 +1253,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { MergingSucc::False } - mir::TerminatorKind::Drop { place, target, unwind } => { + mir::TerminatorKind::Drop { place, target, unwind, replace: _ } => { self.codegen_drop_terminator(helper, bx, place, target, unwind, mergeable_succ()) } @@ -1279,7 +1276,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { destination, target, unwind, - from_hir_call: _, + call_source: _, fn_span, } => self.codegen_call_terminator( helper, @@ -1358,42 +1355,73 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Force by-ref if we have to load through a cast pointer. let (mut llval, align, by_ref) = match op.val { Immediate(_) | Pair(..) => match arg.mode { - PassMode::Indirect { .. } | PassMode::Cast(..) => { + PassMode::Indirect { attrs, .. } => { + // Indirect argument may have higher alignment requirements than the type's alignment. + // This can happen, e.g. when passing types with <4 byte alignment on the stack on x86. + let required_align = match attrs.pointee_align { + Some(pointee_align) => cmp::max(pointee_align, arg.layout.align.abi), + None => arg.layout.align.abi, + }; + let scratch = PlaceRef::alloca_aligned(bx, arg.layout, required_align); + op.val.store(bx, scratch); + (scratch.llval, scratch.align, true) + } + PassMode::Cast(..) => { let scratch = PlaceRef::alloca(bx, arg.layout); op.val.store(bx, scratch); (scratch.llval, scratch.align, true) } _ => (op.immediate_or_packed_pair(bx), arg.layout.align.abi, false), }, - Ref(llval, _, align) => { - if arg.is_indirect() && align < arg.layout.align.abi { - // `foo(packed.large_field)`. We can't pass the (unaligned) field directly. I - // think that ATM (Rust 1.16) we only pass temporaries, but we shouldn't - // have scary latent bugs around. - + Ref(llval, _, align) => match arg.mode { + PassMode::Indirect { attrs, .. } => { + let required_align = match attrs.pointee_align { + Some(pointee_align) => cmp::max(pointee_align, arg.layout.align.abi), + None => arg.layout.align.abi, + }; + if align < required_align { + // For `foo(packed.large_field)`, and types with <4 byte alignment on x86, + // alignment requirements may be higher than the type's alignment, so copy + // to a higher-aligned alloca. + let scratch = PlaceRef::alloca_aligned(bx, arg.layout, required_align); + base::memcpy_ty( + bx, + scratch.llval, + scratch.align, + llval, + align, + op.layout, + MemFlags::empty(), + ); + (scratch.llval, scratch.align, true) + } else { + (llval, align, true) + } + } + _ => (llval, align, true), + }, + ZeroSized => match arg.mode { + PassMode::Indirect { on_stack, .. } => { + if on_stack { + // It doesn't seem like any target can have `byval` ZSTs, so this assert + // is here to replace a would-be untested codepath. + bug!("ZST {op:?} passed on stack with abi {arg:?}"); + } + // Though `extern "Rust"` doesn't pass ZSTs, some ABIs pass + // a pointer for `repr(C)` structs even when empty, so get + // one from an `alloca` (which can be left uninitialized). let scratch = PlaceRef::alloca(bx, arg.layout); - base::memcpy_ty( - bx, - scratch.llval, - scratch.align, - llval, - align, - op.layout, - MemFlags::empty(), - ); (scratch.llval, scratch.align, true) - } else { - (llval, align, true) } - } + _ => bug!("ZST {op:?} wasn't ignored, but was passed with abi {arg:?}"), + }, }; if by_ref && !arg.is_indirect() { // Have to load the argument, maybe while casting it. if let PassMode::Cast(ty, _) = &arg.mode { let llty = bx.cast_backend_type(ty); - let addr = bx.pointercast(llval, bx.type_ptr_to(llty)); - llval = bx.load(llty, addr, align.min(arg.layout.align.abi)); + llval = bx.load(llty, llval, align.min(arg.layout.align.abi)); } else { // We can't use `PlaceRef::load` here because the argument // may have a type we don't treat as immediate, but the ABI @@ -1450,11 +1478,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ) -> OperandRef<'tcx, Bx::Value> { let tcx = bx.tcx(); - let mut span_to_caller_location = |mut span: Span| { - // Remove `Inlined` marks as they pollute `expansion_cause`. - while span.is_inlined() { - span.remove_mark(); - } + let mut span_to_caller_location = |span: Span| { let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span); let caller = tcx.sess.source_map().lookup_char_pos(topmost.lo()); let const_loc = tcx.const_caller_location(( @@ -1497,9 +1521,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { if let Some(slot) = self.personality_slot { slot } else { - let layout = cx.layout_of( - cx.tcx().mk_tup(&[cx.tcx().mk_mut_ptr(cx.tcx().types.u8), cx.tcx().types.i32]), - ); + let layout = cx.layout_of(Ty::new_tup( + cx.tcx(), + &[Ty::new_mut_ptr(cx.tcx(), cx.tcx().types.u8), cx.tcx().types.i32], + )); let slot = PlaceRef::alloca(bx, layout); self.personality_slot = Some(slot); slot @@ -1521,8 +1546,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // FIXME(eddyb) rename this to `eh_pad_for_uncached`. fn landing_pad_for_uncached(&mut self, bb: mir::BasicBlock) -> Bx::BasicBlock { let llbb = self.llbb(bb); - if base::wants_msvc_seh(self.cx.sess()) { - let cleanup_bb = Bx::append_block(self.cx, self.llfn, &format!("funclet_{:?}", bb)); + if base::wants_new_eh_instructions(self.cx.sess()) { + let cleanup_bb = Bx::append_block(self.cx, self.llfn, &format!("funclet_{bb:?}")); let mut cleanup_bx = Bx::build(self.cx, cleanup_bb); let funclet = cleanup_bx.cleanup_pad(None, &[]); cleanup_bx.br(llbb); @@ -1580,6 +1605,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // } catch (...) { // bar(); // } + // + // which creates an IR snippet like + // + // cs_terminate: + // %cs = catchswitch within none [%cp_terminate] unwind to caller + // cp_terminate: + // %cp = catchpad within %cs [null, i32 64, null] + // ... + llbb = Bx::append_block(self.cx, self.llfn, "cs_terminate"); let cp_llbb = Bx::append_block(self.cx, self.llfn, "cp_terminate"); @@ -1592,7 +1626,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // represents that this is a catch-all block. bx = Bx::build(self.cx, cp_llbb); let null = - bx.const_null(bx.type_i8p_ext(bx.cx().data_layout().instruction_address_space)); + bx.const_null(bx.type_ptr_ext(bx.cx().data_layout().instruction_address_space)); let sixty_four = bx.const_i32(64); funclet = Some(bx.catch_pad(cs, &[null, sixty_four, null])); } else { @@ -1633,7 +1667,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { match self.cached_llbbs[bb] { CachedLlbb::None => { // FIXME(eddyb) only name the block if `fewer_names` is `false`. - let llbb = Bx::append_block(self.cx, self.llfn, &format!("{:?}", bb)); + let llbb = Bx::append_block(self.cx, self.llfn, &format!("{bb:?}")); self.cached_llbbs[bb] = CachedLlbb::Some(llbb); Some(llbb) } @@ -1722,7 +1756,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { IndirectOperand(tmp, index) => { let op = bx.load_operand(tmp); tmp.storage_dead(bx); - self.locals[index] = LocalRef::Operand(op); + self.overwrite_local(index, LocalRef::Operand(op)); self.debug_introduce_local(bx, index); } DirectOperand(index) => { @@ -1737,7 +1771,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } else { OperandRef::from_immediate_or_packed_pair(bx, llval, ret_abi.layout) }; - self.locals[index] = LocalRef::Operand(op); + self.overwrite_local(index, LocalRef::Operand(op)); self.debug_introduce_local(bx, index); } } diff --git a/compiler/rustc_codegen_ssa/src/mir/constant.rs b/compiler/rustc_codegen_ssa/src/mir/constant.rs index 14fe84a146da0..babcf9bee2491 100644 --- a/compiler/rustc_codegen_ssa/src/mir/constant.rs +++ b/compiler/rustc_codegen_ssa/src/mir/constant.rs @@ -5,7 +5,6 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::{ConstValue, ErrorHandled}; use rustc_middle::ty::layout::HasTyCtxt; use rustc_middle::ty::{self, Ty}; -use rustc_span::source_map::Span; use rustc_target::abi::Abi; use super::FunctionCx; @@ -59,22 +58,54 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }) } + /// This is a convenience helper for `simd_shuffle_indices`. It has the precondition + /// that the given `constant` is an `ConstantKind::Unevaluated` and must be convertible to + /// a `ValTree`. If you want a more general version of this, talk to `wg-const-eval` on zulip. + pub fn eval_unevaluated_mir_constant_to_valtree( + &self, + constant: &mir::Constant<'tcx>, + ) -> Result>, ErrorHandled> { + let uv = match self.monomorphize(constant.literal) { + mir::ConstantKind::Unevaluated(uv, _) => uv.shrink(), + mir::ConstantKind::Ty(c) => match c.kind() { + // A constant that came from a const generic but was then used as an argument to old-style + // simd_shuffle (passing as argument instead of as a generic param). + rustc_type_ir::ConstKind::Value(valtree) => return Ok(Some(valtree)), + other => span_bug!(constant.span, "{other:#?}"), + }, + // We should never encounter `ConstantKind::Val` unless MIR opts (like const prop) evaluate + // a constant and write that value back into `Operand`s. This could happen, but is unlikely. + // Also: all users of `simd_shuffle` are on unstable and already need to take a lot of care + // around intrinsics. For an issue to happen here, it would require a macro expanding to a + // `simd_shuffle` call without wrapping the constant argument in a `const {}` block, but + // the user pass through arbitrary expressions. + // FIXME(oli-obk): replace the magic const generic argument of `simd_shuffle` with a real + // const generic. + other => span_bug!(constant.span, "{other:#?}"), + }; + let uv = self.monomorphize(uv); + self.cx.tcx().const_eval_resolve_for_typeck( + ty::ParamEnv::reveal_all(), + uv, + Some(constant.span), + ) + } + /// process constant containing SIMD shuffle indices pub fn simd_shuffle_indices( &mut self, bx: &Bx, - span: Span, - ty: Ty<'tcx>, - constant: Result, ErrorHandled>, + constant: &mir::Constant<'tcx>, ) -> (Bx::Value, Ty<'tcx>) { - constant + let ty = self.monomorphize(constant.ty()); + let val = self + .eval_unevaluated_mir_constant_to_valtree(constant) + .ok() + .flatten() .map(|val| { let field_ty = ty.builtin_index().unwrap(); - let c = mir::ConstantKind::from_value(val, ty); - let values: Vec<_> = bx - .tcx() - .destructure_mir_constant(ty::ParamEnv::reveal_all(), c) - .fields + let values: Vec<_> = val + .unwrap_branch() .iter() .map(|field| { if let Some(prim) = field.try_to_scalar() { @@ -88,15 +119,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } }) .collect(); - let llval = bx.const_struct(&values, false); - (llval, c.ty()) + bx.const_struct(&values, false) }) - .unwrap_or_else(|_| { - bx.tcx().sess.emit_err(errors::ShuffleIndicesEvaluation { span }); + .unwrap_or_else(|| { + bx.tcx().sess.emit_err(errors::ShuffleIndicesEvaluation { span: constant.span }); // We've errored, so we don't have to produce working code. - let ty = self.monomorphize(ty); let llty = bx.backend_type(bx.layout_of(ty)); - (bx.const_undef(llty), ty) - }) + bx.const_undef(llty) + }); + (val, ty) } } diff --git a/compiler/rustc_codegen_ssa/src/mir/coverageinfo.rs b/compiler/rustc_codegen_ssa/src/mir/coverageinfo.rs index f1fe495282abc..ee70465966d0c 100644 --- a/compiler/rustc_codegen_ssa/src/mir/coverageinfo.rs +++ b/compiler/rustc_codegen_ssa/src/mir/coverageinfo.rs @@ -1,13 +1,12 @@ use crate::traits::*; -use rustc_middle::mir::coverage::*; use rustc_middle::mir::Coverage; use rustc_middle::mir::SourceScope; use super::FunctionCx; impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { - pub fn codegen_coverage(&self, bx: &mut Bx, coverage: Coverage, scope: SourceScope) { + pub fn codegen_coverage(&self, bx: &mut Bx, coverage: &Coverage, scope: SourceScope) { // Determine the instance that coverage data was originally generated for. let instance = if let Some(inlined) = scope.inlined_instance(&self.mir.source_scopes) { self.monomorphize(inlined) @@ -15,41 +14,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.instance }; - let Coverage { kind, code_region } = coverage; - match kind { - CoverageKind::Counter { function_source_hash, id } => { - if bx.set_function_source_hash(instance, function_source_hash) { - // If `set_function_source_hash()` returned true, the coverage map is enabled, - // so continue adding the counter. - if let Some(code_region) = code_region { - // Note: Some counters do not have code regions, but may still be referenced - // from expressions. In that case, don't add the counter to the coverage map, - // but do inject the counter intrinsic. - bx.add_coverage_counter(instance, id, code_region); - } - - let coverageinfo = bx.tcx().coverageinfo(instance.def); - - let fn_name = bx.get_pgo_func_name_var(instance); - let hash = bx.const_u64(function_source_hash); - let num_counters = bx.const_u32(coverageinfo.num_counters); - let index = bx.const_u32(id.zero_based_index()); - debug!( - "codegen intrinsic instrprof.increment(fn_name={:?}, hash={:?}, num_counters={:?}, index={:?})", - fn_name, hash, num_counters, index, - ); - bx.instrprof_increment(fn_name, hash, num_counters, index); - } - } - CoverageKind::Expression { id, lhs, op, rhs } => { - bx.add_coverage_counter_expression(instance, id, lhs, op, rhs, code_region); - } - CoverageKind::Unreachable => { - bx.add_coverage_unreachable( - instance, - code_region.expect("unreachable regions always have code regions"), - ); - } - } + // Handle the coverage info in a backend-specific way. + bx.add_coverage(instance, coverage); } } diff --git a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs index bba2800fb0545..526c16a59ded0 100644 --- a/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs +++ b/compiler/rustc_codegen_ssa/src/mir/debuginfo.rs @@ -5,6 +5,7 @@ use rustc_middle::mir; use rustc_middle::ty; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; +use rustc_middle::ty::Ty; use rustc_session::config::DebugInfo; use rustc_span::symbol::{kw, Symbol}; use rustc_span::{BytePos, Span}; @@ -41,9 +42,6 @@ pub struct PerLocalVarDebugInfo<'tcx, D> { /// `.place.projection` from `mir::VarDebugInfo`. pub projection: &'tcx ty::List>, - - /// `references` from `mir::VarDebugInfo`. - pub references: u8, } #[derive(Clone, Copy, Debug)] @@ -185,7 +183,11 @@ fn calculate_debuginfo_offset< } => { let offset = indirect_offsets.last_mut().unwrap_or(&mut direct_offset); let FieldsShape::Array { stride, count: _ } = place.layout().fields else { - span_bug!(var.source_info.span, "ConstantIndex on non-array type {:?}", place.layout()) + span_bug!( + var.source_info.span, + "ConstantIndex on non-array type {:?}", + place.layout() + ) }; *offset += stride * index; place = place.project_constant_index(bx, index); @@ -248,7 +250,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } fn spill_operand_to_stack( - operand: &OperandRef<'tcx, Bx::Value>, + operand: OperandRef<'tcx, Bx::Value>, name: Option, bx: &mut Bx, ) -> PlaceRef<'tcx, Bx::Value> { @@ -318,7 +320,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { dbg_var, fragment: None, projection: ty::List::empty(), - references: 0, }) } } else { @@ -327,13 +328,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let local_ref = &self.locals[local]; - // FIXME Should the return place be named? - let name = if bx.sess().fewer_names() || local == mir::RETURN_PLACE { + let name = if bx.sess().fewer_names() { None } else { Some(match whole_local_var.or(fallback_var.clone()) { Some(var) if var.name != kw::Empty => var.name.to_string(), - _ => format!("{:?}", local), + _ => format!("{local:?}"), }) }; @@ -352,6 +352,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.set_var_name(a, &(name.clone() + ".0")); bx.set_var_name(b, &(name.clone() + ".1")); } + OperandValue::ZeroSized => { + // These never have a value to talk about + } }, LocalRef::PendingOperand => {} } @@ -372,7 +375,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { return; } - Self::spill_operand_to_stack(operand, name, bx) + Self::spill_operand_to_stack(*operand, name, bx) } LocalRef::Place(place) => *place, @@ -392,15 +395,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { &self, bx: &mut Bx, local: mir::Local, - mut base: PlaceRef<'tcx, Bx::Value>, + base: PlaceRef<'tcx, Bx::Value>, var: PerLocalVarDebugInfo<'tcx, Bx::DIVariable>, ) { let Some(dbg_var) = var.dbg_var else { return }; let Some(dbg_loc) = self.dbg_loc(var.source_info) else { return }; - let DebugInfoOffset { mut direct_offset, indirect_offsets, result: _ } = + let DebugInfoOffset { direct_offset, indirect_offsets, result: _ } = calculate_debuginfo_offset(bx, local, &var, base.layout); - let mut indirect_offsets = &indirect_offsets[..]; // When targeting MSVC, create extra allocas for arguments instead of pointing multiple // dbg_var_addr() calls into the same alloca with offsets. MSVC uses CodeView records @@ -414,44 +416,43 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // LLVM can handle simple things but anything more complex than just a direct // offset or one indirect offset of 0 is too complex for it to generate CV records // correctly. - && (direct_offset != Size::ZERO || !matches!(indirect_offsets, [Size::ZERO] | [])); + && (direct_offset != Size::ZERO || !matches!(&indirect_offsets[..], [Size::ZERO] | [])); + + if should_create_individual_allocas { + let DebugInfoOffset { direct_offset: _, indirect_offsets: _, result: place } = + calculate_debuginfo_offset(bx, local, &var, base); - let create_alloca = |bx: &mut Bx, place: PlaceRef<'tcx, Bx::Value>, refcount| { // Create a variable which will be a pointer to the actual value - let ptr_ty = bx - .tcx() - .mk_ptr(ty::TypeAndMut { mutbl: mir::Mutability::Mut, ty: place.layout.ty }); + let ptr_ty = Ty::new_ptr( + bx.tcx(), + ty::TypeAndMut { mutbl: mir::Mutability::Mut, ty: place.layout.ty }, + ); let ptr_layout = bx.layout_of(ptr_ty); let alloca = PlaceRef::alloca(bx, ptr_layout); - bx.set_var_name(alloca.llval, &format!("{}.ref{}.dbg.spill", var.name, refcount)); + bx.set_var_name(alloca.llval, &(var.name.to_string() + ".dbg.spill")); // Write the pointer to the variable bx.store(place.llval, alloca.llval, alloca.align); // Point the debug info to `*alloca` for the current variable - alloca - }; - - if var.references > 0 { - base = calculate_debuginfo_offset(bx, local, &var, base).result; - - // Point the debug info to `&...&base == alloca` for the current variable - for refcount in 0..var.references { - base = create_alloca(bx, base, refcount); - } - - direct_offset = Size::ZERO; - indirect_offsets = &[]; - } else if should_create_individual_allocas { - let place = calculate_debuginfo_offset(bx, local, &var, base).result; - - // Point the debug info to `*alloca` for the current variable - base = create_alloca(bx, place, 0); - direct_offset = Size::ZERO; - indirect_offsets = &[Size::ZERO]; + bx.dbg_var_addr( + dbg_var, + dbg_loc, + alloca.llval, + Size::ZERO, + &[Size::ZERO], + var.fragment, + ); + } else { + bx.dbg_var_addr( + dbg_var, + dbg_loc, + base.llval, + direct_offset, + &indirect_offsets, + var.fragment, + ); } - - bx.dbg_var_addr(dbg_var, dbg_loc, base.llval, direct_offset, indirect_offsets, None); } pub fn debug_introduce_locals(&self, bx: &mut Bx) { @@ -484,7 +485,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }; let dbg_var = dbg_scope_and_span.map(|(dbg_scope, _, span)| { - let (mut var_ty, var_kind) = match var.value { + let (var_ty, var_kind) = match var.value { mir::VarDebugInfoContents::Place(place) => { let var_ty = self.monomorphized_place_ty(place.as_ref()); let var_kind = if let Some(arg_index) = var.argument_index @@ -521,11 +522,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } }; - for _ in 0..var.references { - var_ty = - bx.tcx().mk_ptr(ty::TypeAndMut { mutbl: mir::Mutability::Mut, ty: var_ty }); - } - self.cx.create_dbg_var(var.name, var_ty, dbg_scope, var_kind, span) }); @@ -537,7 +533,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { dbg_var, fragment: None, projection: place.projection, - references: var.references, }); } mir::VarDebugInfoContents::Const(c) => { @@ -547,7 +542,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { if let Ok(operand) = self.eval_mir_constant_to_operand(bx, &c) { self.set_debug_loc(bx, var.source_info); let base = Self::spill_operand_to_stack( - &operand, + operand, Some(var.name.to_string()), bx, ); @@ -579,19 +574,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } let place = fragment.contents; + let fragment = if fragment_layout.size == Size::ZERO { + // Fragment is a ZST, so does not represent anything. + continue; + } else if fragment_layout.size == var_layout.size { + // Fragment covers entire variable, so as far as + // DWARF is concerned, it's not really a fragment. + None + } else { + Some(fragment_start..fragment_start + fragment_layout.size) + }; + per_local[place.local].push(PerLocalVarDebugInfo { name: var.name, source_info: var.source_info, dbg_var, - fragment: if fragment_layout.size == var_layout.size { - // Fragment covers entire variable, so as far as - // DWARF is concerned, it's not really a fragment. - None - } else { - Some(fragment_start..fragment_start + fragment_layout.size) - }, + fragment, projection: place.projection, - references: var.references, }); } } diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index 1479242f23a6c..8821fb21fd002 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -64,7 +64,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ) { let callee_ty = instance.ty(bx.tcx(), ty::ParamEnv::reveal_all()); - let ty::FnDef(def_id, substs) = *callee_ty.kind() else { + let ty::FnDef(def_id, fn_args) = *callee_ty.kind() else { bug!("expected fn item type, found {}", callee_ty); }; @@ -87,7 +87,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { sym::va_start => bx.va_start(args[0].immediate()), sym::va_end => bx.va_end(args[0].immediate()), sym::size_of_val => { - let tp_ty = substs.type_at(0); + let tp_ty = fn_args.type_at(0); if let OperandValue::Pair(_, meta) = args[0].val { let (llsize, _) = glue::size_and_align_of_dst(bx, tp_ty, Some(meta)); llsize @@ -96,7 +96,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } sym::min_align_of_val => { - let tp_ty = substs.type_at(0); + let tp_ty = fn_args.type_at(0); if let OperandValue::Pair(_, meta) = args[0].val { let (_, llalign) = glue::size_and_align_of_dst(bx, tp_ty, Some(meta)); llalign @@ -136,7 +136,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandRef::from_const(bx, value, ret_ty).immediate_or_packed_pair(bx) } sym::arith_offset => { - let ty = substs.type_at(0); + let ty = fn_args.type_at(0); let layout = bx.layout_of(ty); let ptr = args[0].immediate(); let offset = args[1].immediate(); @@ -147,7 +147,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx, true, false, - substs.type_at(0), + fn_args.type_at(0), args[1].immediate(), args[0].immediate(), args[2].immediate(), @@ -158,7 +158,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { memset_intrinsic( bx, false, - substs.type_at(0), + fn_args.type_at(0), args[0].immediate(), args[1].immediate(), args[2].immediate(), @@ -171,7 +171,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx, false, true, - substs.type_at(0), + fn_args.type_at(0), args[0].immediate(), args[1].immediate(), args[2].immediate(), @@ -183,7 +183,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx, true, true, - substs.type_at(0), + fn_args.type_at(0), args[0].immediate(), args[1].immediate(), args[2].immediate(), @@ -194,7 +194,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { memset_intrinsic( bx, true, - substs.type_at(0), + fn_args.type_at(0), args[0].immediate(), args[1].immediate(), args[2].immediate(), @@ -211,68 +211,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args[1].val.unaligned_volatile_store(bx, dst); return; } - | sym::unchecked_div - | sym::unchecked_rem - | sym::unchecked_shl - | sym::unchecked_shr - | sym::unchecked_add - | sym::unchecked_sub - | sym::unchecked_mul - | sym::exact_div => { + sym::exact_div => { let ty = arg_tys[0]; match int_type_width_signed(ty, bx.tcx()) { - Some((_width, signed)) => match name { - sym::exact_div => { - if signed { - bx.exactsdiv(args[0].immediate(), args[1].immediate()) - } else { - bx.exactudiv(args[0].immediate(), args[1].immediate()) - } - } - sym::unchecked_div => { - if signed { - bx.sdiv(args[0].immediate(), args[1].immediate()) - } else { - bx.udiv(args[0].immediate(), args[1].immediate()) - } - } - sym::unchecked_rem => { - if signed { - bx.srem(args[0].immediate(), args[1].immediate()) - } else { - bx.urem(args[0].immediate(), args[1].immediate()) - } - } - sym::unchecked_shl => bx.shl(args[0].immediate(), args[1].immediate()), - sym::unchecked_shr => { - if signed { - bx.ashr(args[0].immediate(), args[1].immediate()) - } else { - bx.lshr(args[0].immediate(), args[1].immediate()) - } - } - sym::unchecked_add => { - if signed { - bx.unchecked_sadd(args[0].immediate(), args[1].immediate()) - } else { - bx.unchecked_uadd(args[0].immediate(), args[1].immediate()) - } - } - sym::unchecked_sub => { - if signed { - bx.unchecked_ssub(args[0].immediate(), args[1].immediate()) - } else { - bx.unchecked_usub(args[0].immediate(), args[1].immediate()) - } - } - sym::unchecked_mul => { - if signed { - bx.unchecked_smul(args[0].immediate(), args[1].immediate()) - } else { - bx.unchecked_umul(args[0].immediate(), args[1].immediate()) - } + Some((_width, signed)) => { + if signed { + bx.exactsdiv(args[0].immediate(), args[1].immediate()) + } else { + bx.exactudiv(args[0].immediate(), args[1].immediate()) } - _ => bug!(), }, None => { bx.tcx().sess.emit_err(InvalidMonomorphization::BasicIntegerType { span, name, ty }); @@ -323,7 +270,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { sym::const_allocate => { // returns a null pointer at runtime. - bx.const_null(bx.type_i8p()) + bx.const_null(bx.type_ptr()) } sym::const_deallocate => { @@ -360,17 +307,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let Some((success, failure)) = ordering.split_once('_') else { bx.sess().emit_fatal(errors::AtomicCompareExchange); }; - let ty = substs.type_at(0); + let ty = fn_args.type_at(0); if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() { let weak = instruction == "cxchgweak"; - let mut dst = args[0].immediate(); + let dst = args[0].immediate(); let mut cmp = args[1].immediate(); let mut src = args[2].immediate(); if ty.is_unsafe_ptr() { // Some platforms do not support atomic operations on pointers, // so we cast to integer first. - let ptr_llty = bx.type_ptr_to(bx.type_isize()); - dst = bx.pointercast(dst, ptr_llty); cmp = bx.ptrtoint(cmp, bx.type_isize()); src = bx.ptrtoint(src, bx.type_isize()); } @@ -391,17 +336,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } "load" => { - let ty = substs.type_at(0); + let ty = fn_args.type_at(0); if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() { let layout = bx.layout_of(ty); let size = layout.size; - let mut source = args[0].immediate(); + let source = args[0].immediate(); if ty.is_unsafe_ptr() { // Some platforms do not support atomic operations on pointers, // so we cast to integer first... let llty = bx.type_isize(); - let ptr_llty = bx.type_ptr_to(llty); - source = bx.pointercast(source, ptr_llty); let result = bx.atomic_load(llty, source, parse_ordering(bx, ordering), size); // ... and then cast the result back to a pointer bx.inttoptr(result, bx.backend_type(layout)) @@ -414,16 +357,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } "store" => { - let ty = substs.type_at(0); + let ty = fn_args.type_at(0); if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() { let size = bx.layout_of(ty).size; let mut val = args[1].immediate(); - let mut ptr = args[0].immediate(); + let ptr = args[0].immediate(); if ty.is_unsafe_ptr() { // Some platforms do not support atomic operations on pointers, // so we cast to integer first. - let ptr_llty = bx.type_ptr_to(bx.type_isize()); - ptr = bx.pointercast(ptr, ptr_llty); val = bx.ptrtoint(val, bx.type_isize()); } bx.atomic_store(val, ptr, parse_ordering(bx, ordering), size); @@ -460,15 +401,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => bx.sess().emit_fatal(errors::UnknownAtomicOperation), }; - let ty = substs.type_at(0); + let ty = fn_args.type_at(0); if int_type_width_signed(ty, bx.tcx()).is_some() || ty.is_unsafe_ptr() { - let mut ptr = args[0].immediate(); + let ptr = args[0].immediate(); let mut val = args[1].immediate(); if ty.is_unsafe_ptr() { // Some platforms do not support atomic operations on pointers, // so we cast to integer first. - let ptr_llty = bx.type_ptr_to(bx.type_isize()); - ptr = bx.pointercast(ptr, ptr_llty); val = bx.ptrtoint(val, bx.type_isize()); } bx.atomic_rmw(atom_op, ptr, val, parse_ordering(bx, ordering)) @@ -492,7 +431,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } sym::ptr_offset_from | sym::ptr_offset_from_unsigned => { - let ty = substs.type_at(0); + let ty = fn_args.type_at(0); let pointee_size = bx.layout_of(ty).size; let a = args[0].immediate(); @@ -523,10 +462,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }; if !fn_abi.ret.is_ignore() { - if let PassMode::Cast(ty, _) = &fn_abi.ret.mode { - let ptr_llty = bx.type_ptr_to(bx.cast_backend_type(ty)); - let ptr = bx.pointercast(result.llval, ptr_llty); - bx.store(llval, ptr, result.align); + if let PassMode::Cast(..) = &fn_abi.ret.mode { + bx.store(llval, result.llval, result.align); } else { OperandRef::from_immediate_or_packed_pair(bx, llval, result.layout) .val diff --git a/compiler/rustc_codegen_ssa/src/mir/locals.rs b/compiler/rustc_codegen_ssa/src/mir/locals.rs new file mode 100644 index 0000000000000..378c540132207 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/mir/locals.rs @@ -0,0 +1,75 @@ +//! Locals are in a private module as updating `LocalRef::Operand` has to +//! be careful wrt to subtyping. To deal with this we only allow updates by using +//! `FunctionCx::overwrite_local` which handles it automatically. +use crate::mir::{FunctionCx, LocalRef}; +use crate::traits::BuilderMethods; +use rustc_index::IndexVec; +use rustc_middle::mir; +use rustc_middle::ty::print::with_no_trimmed_paths; +use std::ops::{Index, IndexMut}; +pub(super) struct Locals<'tcx, V> { + values: IndexVec>, +} + +impl<'tcx, V> Index for Locals<'tcx, V> { + type Output = LocalRef<'tcx, V>; + #[inline] + fn index(&self, index: mir::Local) -> &LocalRef<'tcx, V> { + &self.values[index] + } +} + +/// To mutate locals, use `FunctionCx::overwrite_local` instead. +impl<'tcx, V, Idx: ?Sized> !IndexMut for Locals<'tcx, V> {} + +impl<'tcx, V> Locals<'tcx, V> { + pub(super) fn empty() -> Locals<'tcx, V> { + Locals { values: IndexVec::default() } + } + + pub(super) fn indices(&self) -> impl DoubleEndedIterator + Clone + 'tcx { + self.values.indices() + } +} + +impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { + pub(super) fn initialize_locals(&mut self, values: Vec>) { + assert!(self.locals.values.is_empty()); + // FIXME(#115215): After #115025 get's merged this might not be necessary + for (local, value) in values.into_iter().enumerate() { + match value { + LocalRef::Place(_) | LocalRef::UnsizedPlace(_) | LocalRef::PendingOperand => (), + LocalRef::Operand(op) => { + let local = mir::Local::from_usize(local); + let expected_ty = self.monomorphize(self.mir.local_decls[local].ty); + if expected_ty != op.layout.ty { + warn!("Unexpected initial operand type. See the issues/114858"); + } + } + } + self.locals.values.push(value); + } + } + + pub(super) fn overwrite_local( + &mut self, + local: mir::Local, + mut value: LocalRef<'tcx, Bx::Value>, + ) { + match value { + LocalRef::Place(_) | LocalRef::UnsizedPlace(_) | LocalRef::PendingOperand => (), + LocalRef::Operand(ref mut op) => { + let local_ty = self.monomorphize(self.mir.local_decls[local].ty); + if local_ty != op.layout.ty { + // FIXME(#112651): This can be changed to an ICE afterwards. + debug!("updating type of operand due to subtyping"); + with_no_trimmed_paths!(debug!(?op.layout.ty)); + with_no_trimmed_paths!(debug!(?local_ty)); + op.layout.ty = local_ty; + } + } + }; + + self.locals.values[local] = value; + } +} diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 1204c99e533e2..3464f910829da 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -1,21 +1,31 @@ use crate::base; use crate::traits::*; +use rustc_index::bit_set::BitSet; +use rustc_index::IndexVec; use rustc_middle::mir; use rustc_middle::mir::interpret::ErrorHandled; +use rustc_middle::mir::traversal; use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, TyAndLayout}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_target::abi::call::{FnAbi, PassMode}; use std::iter; -use rustc_index::bit_set::BitSet; -use rustc_index::IndexVec; +mod analyze; +mod block; +pub mod constant; +pub mod coverageinfo; +pub mod debuginfo; +mod intrinsic; +mod locals; +pub mod operand; +pub mod place; +mod rvalue; +mod statement; use self::debuginfo::{FunctionDebugContext, PerLocalVarDebugInfo}; -use self::place::PlaceRef; -use rustc_middle::mir::traversal; - use self::operand::{OperandRef, OperandValue}; +use self::place::PlaceRef; // Used for tracking the state of generated basic blocks. enum CachedLlbb { @@ -91,7 +101,7 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> { /// /// Avoiding allocs can also be important for certain intrinsics, /// notably `expect`. - locals: IndexVec>, + locals: locals::Locals<'tcx, Bx::Value>, /// All `VarDebugInfo` from the MIR body, partitioned by `Local`. /// This is `None` if no var`#[non_exhaustive]`iable debuginfo/names are needed. @@ -111,7 +121,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { self.instance.subst_mir_and_normalize_erasing_regions( self.cx.tcx(), ty::ParamEnv::reveal_all(), - ty::EarlyBinder(value), + ty::EarlyBinder::bind(value), ) } } @@ -129,16 +139,13 @@ enum LocalRef<'tcx, V> { PendingOperand, } -impl<'a, 'tcx, V: CodegenObject> LocalRef<'tcx, V> { - fn new_operand>( - bx: &mut Bx, - layout: TyAndLayout<'tcx>, - ) -> LocalRef<'tcx, V> { +impl<'tcx, V: CodegenObject> LocalRef<'tcx, V> { + fn new_operand(layout: TyAndLayout<'tcx>) -> LocalRef<'tcx, V> { if layout.is_zst() { // Zero-size temporaries aren't always initialized, which // doesn't matter because they don't contain data, but // we need something in the operand. - LocalRef::Operand(OperandRef::new_zst(bx, layout)) + LocalRef::Operand(OperandRef::zero_sized(layout)) } else { LocalRef::PendingOperand } @@ -152,7 +159,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cx: &'a Bx::CodegenCx, instance: Instance<'tcx>, ) { - assert!(!instance.substs.has_infer()); + assert!(!instance.args.has_infer()); let llfn = cx.get_fn(instance); @@ -172,7 +179,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( start_bx.set_personality_fn(cx.eh_personality()); } - let cleanup_kinds = base::wants_msvc_seh(cx.tcx().sess).then(|| analyze::cleanup_kinds(&mir)); + let cleanup_kinds = + base::wants_new_eh_instructions(cx.tcx().sess).then(|| analyze::cleanup_kinds(&mir)); let cached_llbbs: IndexVec> = mir.basic_blocks @@ -195,7 +203,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cleanup_kinds, landing_pads: IndexVec::from_elem(None, &mir.basic_blocks), funclets: IndexVec::from_fn_n(|_| None, mir.basic_blocks.len()), - locals: IndexVec::new(), + locals: locals::Locals::empty(), debug_context, per_local_var_debug_info: None, caller_location: None, @@ -226,7 +234,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let memory_locals = analyze::non_ssa_locals(&fx); // Allocate variable and temp allocas - fx.locals = { + let local_values = { let args = arg_local_refs(&mut start_bx, &mut fx, &memory_locals); let mut allocate_local = |local| { @@ -249,7 +257,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } } else { debug!("alloc: {:?} -> operand", local); - LocalRef::new_operand(&mut start_bx, layout) + LocalRef::new_operand(layout) } }; @@ -259,6 +267,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( .chain(mir.vars_and_temps_iter().map(allocate_local)) .collect() }; + fx.initialize_locals(local_values); // Apply debuginfo to the newly allocated locals. fx.debug_introduce_locals(&mut start_bx); @@ -292,14 +301,13 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( .enumerate() .map(|(arg_index, local)| { let arg_decl = &mir.local_decls[local]; + let arg_ty = fx.monomorphize(arg_decl.ty); if Some(local) == mir.spread_arg { // This argument (e.g., the last argument in the "rust-call" ABI) // is a tuple that was spread at the ABI level and now we have // to reconstruct it into a tuple local variable, from multiple // individual LLVM function arguments. - - let arg_ty = fx.monomorphize(arg_decl.ty); let ty::Tuple(tupled_arg_tys) = arg_ty.kind() else { bug!("spread argument isn't a tuple?!"); }; @@ -334,8 +342,6 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( } if fx.fn_abi.c_variadic && arg_index == fx.fn_abi.args.len() { - let arg_ty = fx.monomorphize(arg_decl.ty); - let va_list = PlaceRef::alloca(bx, bx.layout_of(arg_ty)); bx.va_start(va_list.llval); @@ -355,7 +361,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let local = |op| LocalRef::Operand(op); match arg.mode { PassMode::Ignore => { - return local(OperandRef::new_zst(bx, arg.layout)); + return local(OperandRef::zero_sized(arg.layout)); } PassMode::Direct(_) => { let llarg = bx.get_param(llarg_idx); @@ -432,14 +438,3 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( args } - -mod analyze; -mod block; -pub mod constant; -pub mod coverageinfo; -pub mod debuginfo; -mod intrinsic; -pub mod operand; -pub mod place; -mod rvalue; -mod statement; diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index 2301c3ef13e21..f90d1a0fc9cda 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -2,16 +2,15 @@ use super::place::PlaceRef; use super::{FunctionCx, LocalRef}; use crate::base; -use crate::common::TypeKind; use crate::glue; use crate::traits::*; use crate::MemFlags; use rustc_middle::mir; -use rustc_middle::mir::interpret::{ConstValue, Pointer, Scalar}; +use rustc_middle::mir::interpret::{alloc_range, ConstValue, Pointer, Scalar}; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::Ty; -use rustc_target::abi::{Abi, Align, Size}; +use rustc_target::abi::{self, Abi, Align, Size}; use std::fmt; @@ -45,6 +44,14 @@ pub enum OperandValue { /// as returned by [`LayoutTypeMethods::scalar_pair_element_backend_type`] /// with `immediate: true`. Pair(V, V), + /// A value taking no bytes, and which therefore needs no LLVM value at all. + /// + /// If you ever need a `V` to pass to something, get a fresh poison value + /// from [`ConstMethods::const_poison`]. + /// + /// An `OperandValue` *must* be this variant for any type for which + /// `is_zst` on its `Layout` returns `true`. + ZeroSized, } /// An `OperandRef` is an "SSA" reference to a Rust value, along with @@ -71,15 +78,9 @@ impl fmt::Debug for OperandRef<'_, V> { } impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { - pub fn new_zst>( - bx: &mut Bx, - layout: TyAndLayout<'tcx>, - ) -> OperandRef<'tcx, V> { + pub fn zero_sized(layout: TyAndLayout<'tcx>) -> OperandRef<'tcx, V> { assert!(layout.is_zst()); - OperandRef { - val: OperandValue::Immediate(bx.const_poison(bx.immediate_backend_type(layout))), - layout, - } + OperandRef { val: OperandValue::ZeroSized, layout } } pub fn from_const>( @@ -97,7 +98,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let llval = bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout)); OperandValue::Immediate(llval) } - ConstValue::ZeroSized => return OperandRef::new_zst(bx, layout), + ConstValue::ZeroSized => return OperandRef::zero_sized(layout), ConstValue::Slice { data, start, end } => { let Abi::ScalarPair(a_scalar, _) = layout.abi else { bug!("from_const: invalid ScalarPair layout: {:#?}", layout); @@ -115,13 +116,80 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { OperandValue::Pair(a_llval, b_llval) } ConstValue::ByRef { alloc, offset } => { - return bx.load_operand(bx.from_const_alloc(layout, alloc, offset)); + return Self::from_const_alloc(bx, layout, alloc, offset); } }; OperandRef { val, layout } } + fn from_const_alloc>( + bx: &mut Bx, + layout: TyAndLayout<'tcx>, + alloc: rustc_middle::mir::interpret::ConstAllocation<'tcx>, + offset: Size, + ) -> Self { + let alloc_align = alloc.inner().align; + assert_eq!(alloc_align, layout.align.abi); + + let read_scalar = |start, size, s: abi::Scalar, ty| { + let val = alloc + .0 + .read_scalar( + bx, + alloc_range(start, size), + /*read_provenance*/ matches!(s.primitive(), abi::Pointer(_)), + ) + .unwrap(); + bx.scalar_to_backend(val, s, ty) + }; + + // It may seem like all types with `Scalar` or `ScalarPair` ABI are fair game at this point. + // However, `MaybeUninit` is considered a `Scalar` as far as its layout is concerned -- + // and yet cannot be represented by an interpreter `Scalar`, since we have to handle the + // case where some of the bytes are initialized and others are not. So, we need an extra + // check that walks over the type of `mplace` to make sure it is truly correct to treat this + // like a `Scalar` (or `ScalarPair`). + match layout.abi { + Abi::Scalar(s @ abi::Scalar::Initialized { .. }) => { + let size = s.size(bx); + assert_eq!(size, layout.size, "abi::Scalar size does not match layout size"); + let val = read_scalar(Size::ZERO, size, s, bx.type_ptr()); + OperandRef { val: OperandValue::Immediate(val), layout } + } + Abi::ScalarPair( + a @ abi::Scalar::Initialized { .. }, + b @ abi::Scalar::Initialized { .. }, + ) => { + let (a_size, b_size) = (a.size(bx), b.size(bx)); + let b_offset = a_size.align_to(b.align(bx).abi); + assert!(b_offset.bytes() > 0); + let a_val = read_scalar( + Size::ZERO, + a_size, + a, + bx.scalar_pair_element_backend_type(layout, 0, true), + ); + let b_val = read_scalar( + b_offset, + b_size, + b, + bx.scalar_pair_element_backend_type(layout, 1, true), + ); + OperandRef { val: OperandValue::Pair(a_val, b_val), layout } + } + _ if layout.is_zst() => OperandRef::zero_sized(layout), + _ => { + // Neither a scalar nor scalar pair. Load from a place + let init = bx.const_data_from_alloc(alloc); + let base_addr = bx.static_addr_of(init, alloc_align, None); + + let llval = bx.const_ptr_byte_offset(base_addr, offset); + bx.load_operand(PlaceRef::new_sized(llval, layout)) + } + } + } + /// Asserts that this operand refers to a scalar and returns /// a reference to its value. pub fn immediate(self) -> V { @@ -147,6 +215,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { OperandValue::Immediate(llptr) => (llptr, None), OperandValue::Pair(llptr, llextra) => (llptr, Some(llextra)), OperandValue::Ref(..) => bug!("Deref of by-Ref operand {:?}", self), + OperandValue::ZeroSized => bug!("Deref of ZST operand {:?}", self), }; let layout = cx.layout_of(projected_ty); PlaceRef { llval: llptr, llextra, layout, align: layout.align.abi } @@ -204,9 +273,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let mut val = match (self.val, self.layout.abi) { // If the field is ZST, it has no data. - _ if field.is_zst() => { - return OperandRef::new_zst(bx, field); - } + _ if field.is_zst() => OperandValue::ZeroSized, // Newtype of a scalar, scalar pair or vector. (OperandValue::Immediate(_) | OperandValue::Pair(..), _) @@ -237,44 +304,29 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { }; match (&mut val, field.abi) { + (OperandValue::ZeroSized, _) => {} ( OperandValue::Immediate(llval), Abi::Scalar(_) | Abi::ScalarPair(..) | Abi::Vector { .. }, ) => { // Bools in union fields needs to be truncated. *llval = bx.to_immediate(*llval, field); - // HACK(eddyb) have to bitcast pointers until LLVM removes pointee types. - let ty = bx.cx().immediate_backend_type(field); - if bx.type_kind(ty) == TypeKind::Pointer { - *llval = bx.pointercast(*llval, ty); - } } (OperandValue::Pair(a, b), Abi::ScalarPair(a_abi, b_abi)) => { // Bools in union fields needs to be truncated. *a = bx.to_immediate_scalar(*a, a_abi); *b = bx.to_immediate_scalar(*b, b_abi); - // HACK(eddyb) have to bitcast pointers until LLVM removes pointee types. - let a_ty = bx.cx().scalar_pair_element_backend_type(field, 0, true); - let b_ty = bx.cx().scalar_pair_element_backend_type(field, 1, true); - if bx.type_kind(a_ty) == TypeKind::Pointer { - *a = bx.pointercast(*a, a_ty); - } - if bx.type_kind(b_ty) == TypeKind::Pointer { - *b = bx.pointercast(*b, b_ty); - } } // Newtype vector of array, e.g. #[repr(simd)] struct S([i32; 4]); (OperandValue::Immediate(llval), Abi::Aggregate { sized: true }) => { assert!(matches!(self.layout.abi, Abi::Vector { .. })); - let llty = bx.cx().backend_type(self.layout); let llfield_ty = bx.cx().backend_type(field); // Can't bitcast an aggregate, so round trip through memory. - let lltemp = bx.alloca(llfield_ty, field.align.abi); - let llptr = bx.pointercast(lltemp, bx.cx().type_ptr_to(llty)); + let llptr = bx.alloca(llfield_ty, field.align.abi); bx.store(*llval, llptr, field.align.abi); - *llval = bx.load(llfield_ty, lltemp, field.align.abi); + *llval = bx.load(llfield_ty, llptr, field.align.abi); } (OperandValue::Immediate(_), Abi::Uninhabited | Abi::Aggregate { sized: false }) => { bug!() @@ -290,8 +342,8 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { impl<'a, 'tcx, V: CodegenObject> OperandValue { /// Returns an `OperandValue` that's generally UB to use in any way. /// - /// Depending on the `layout`, returns an `Immediate` or `Pair` containing - /// poison value(s), or a `Ref` containing a poison pointer. + /// Depending on the `layout`, returns `ZeroSized` for ZSTs, an `Immediate` or + /// `Pair` containing poison value(s), or a `Ref` containing a poison pointer. /// /// Supports sized types only. pub fn poison>( @@ -299,7 +351,9 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue { layout: TyAndLayout<'tcx>, ) -> OperandValue { assert!(layout.is_sized()); - if bx.cx().is_backend_immediate(layout) { + if layout.is_zst() { + OperandValue::ZeroSized + } else if bx.cx().is_backend_immediate(layout) { let ibty = bx.cx().immediate_backend_type(layout); OperandValue::Immediate(bx.const_poison(ibty)) } else if bx.cx().is_backend_scalar_pair(layout) { @@ -307,9 +361,8 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue { let ibty1 = bx.cx().scalar_pair_element_backend_type(layout, 1, true); OperandValue::Pair(bx.const_poison(ibty0), bx.const_poison(ibty1)) } else { - let bty = bx.cx().backend_type(layout); - let ptr_bty = bx.cx().type_ptr_to(bty); - OperandValue::Ref(bx.const_poison(ptr_bty), None, layout.align.abi) + let ptr = bx.cx().type_ptr(); + OperandValue::Ref(bx.const_poison(ptr), None, layout.align.abi) } } @@ -352,18 +405,16 @@ impl<'a, 'tcx, V: CodegenObject> OperandValue { flags: MemFlags, ) { debug!("OperandRef::store: operand={:?}, dest={:?}", self, dest); - // Avoid generating stores of zero-sized values, because the only way to have a zero-sized - // value is through `undef`, and store itself is useless. - if dest.layout.is_zst() { - return; - } match self { + OperandValue::ZeroSized => { + // Avoid generating stores of zero-sized values, because the only way to have a zero-sized + // value is through `undef`/`poison`, and the store itself is useless. + } OperandValue::Ref(r, None, source_align) => { if flags.contains(MemFlags::NONTEMPORAL) { // HACK(nox): This is inefficient but there is no nontemporal memcpy. let ty = bx.backend_type(dest.layout); - let ptr = bx.pointercast(r, bx.type_ptr_to(ty)); - let val = bx.load(ty, ptr, source_align); + let val = bx.load(ty, r, source_align); bx.store_with_flags(val, dest.llval, dest.align, flags); return; } @@ -458,7 +509,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // checks in `codegen_consume` and `extract_field`. let elem = o.layout.field(bx.cx(), 0); if elem.is_zst() { - o = OperandRef::new_zst(bx, elem); + o = OperandRef::zero_sized(elem); } else { return None; } @@ -492,7 +543,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // ZSTs don't require any actual memory access. if layout.is_zst() { - return OperandRef::new_zst(bx, layout); + return OperandRef::zero_sized(layout); } if let Some(o) = self.maybe_codegen_consume_direct(bx, place_ref) { diff --git a/compiler/rustc_codegen_ssa/src/mir/place.rs b/compiler/rustc_codegen_ssa/src/mir/place.rs index a58a61cd567fc..e7c3906d977df 100644 --- a/compiler/rustc_codegen_ssa/src/mir/place.rs +++ b/compiler/rustc_codegen_ssa/src/mir/place.rs @@ -47,10 +47,18 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { pub fn alloca>( bx: &mut Bx, layout: TyAndLayout<'tcx>, + ) -> Self { + Self::alloca_aligned(bx, layout, layout.align.abi) + } + + pub fn alloca_aligned>( + bx: &mut Bx, + layout: TyAndLayout<'tcx>, + align: Align, ) -> Self { assert!(layout.is_sized(), "tried to statically allocate unsized place"); - let tmp = bx.alloca(bx.cx().backend_type(layout), layout.align.abi); - Self::new_sized(tmp, layout) + let tmp = bx.alloca(bx.cx().backend_type(layout), align); + Self::new_sized_aligned(tmp, layout, align) } /// Returns a place for an indirect reference to an unsized place. @@ -61,7 +69,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { layout: TyAndLayout<'tcx>, ) -> Self { assert!(layout.is_unsized(), "tried to allocate indirect place for sized values"); - let ptr_ty = bx.cx().tcx().mk_mut_ptr(layout.ty); + let ptr_ty = Ty::new_mut_ptr(bx.cx().tcx(), layout.ty); let ptr_layout = bx.cx().layout_of(ptr_ty); Self::alloca(bx, ptr_layout) } @@ -107,8 +115,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { } Abi::Scalar(_) | Abi::ScalarPair(..) | Abi::Vector { .. } if field.is_zst() => { // ZST fields are not included in Scalar, ScalarPair, and Vector layouts, so manually offset the pointer. - let byte_ptr = bx.pointercast(self.llval, bx.cx().type_i8p()); - bx.gep(bx.cx().type_i8(), byte_ptr, &[bx.const_usize(offset.bytes())]) + bx.gep(bx.cx().type_i8(), self.llval, &[bx.const_usize(offset.bytes())]) } Abi::Scalar(_) | Abi::ScalarPair(..) => { // All fields of Scalar and ScalarPair layouts must have been handled by this point. @@ -125,8 +132,7 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { } }; PlaceRef { - // HACK(eddyb): have to bitcast pointers until LLVM removes pointee types. - llval: bx.pointercast(llval, bx.cx().type_ptr_to(bx.cx().backend_type(field))), + llval, llextra: if bx.cx().type_has_metadata(field.ty) { self.llextra } else { None }, layout: field, align: effective_field_align, @@ -186,20 +192,10 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { debug!("struct_field_ptr: DST field offset: {:?}", offset); - // Cast and adjust pointer. - let byte_ptr = bx.pointercast(self.llval, bx.cx().type_i8p()); - let byte_ptr = bx.gep(bx.cx().type_i8(), byte_ptr, &[offset]); + // Adjust pointer. + let ptr = bx.gep(bx.cx().type_i8(), self.llval, &[offset]); - // Finally, cast back to the type expected. - let ll_fty = bx.cx().backend_type(field); - debug!("struct_field_ptr: Field type is {:?}", ll_fty); - - PlaceRef { - llval: bx.pointercast(byte_ptr, bx.cx().type_ptr_to(ll_fty)), - llextra: self.llextra, - layout: field, - align: effective_field_align, - } + PlaceRef { llval: ptr, llextra: self.llextra, layout: field, align: effective_field_align } } /// Obtain the actual discriminant of a value. @@ -408,11 +404,6 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { ) -> Self { let mut downcast = *self; downcast.layout = self.layout.for_variant(bx.cx(), variant_index); - - // Cast to the appropriate variant struct type. - let variant_ty = bx.cx().backend_type(downcast.layout); - downcast.llval = bx.pointercast(downcast.llval, bx.cx().type_ptr_to(variant_ty)); - downcast } @@ -423,11 +414,6 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> { ) -> Self { let mut downcast = *self; downcast.layout = bx.cx().layout_of(ty); - - // Cast to the appropriate type. - let variant_ty = bx.cx().backend_type(downcast.layout); - downcast.llval = bx.pointercast(downcast.llval, bx.cx().type_ptr_to(variant_ty)); - downcast } @@ -455,7 +441,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { LocalRef::Place(place) => place, LocalRef::UnsizedPlace(place) => bx.load_operand(place).deref(cx), LocalRef::Operand(..) => { - if place_ref.has_deref() { + if place_ref.is_indirect_first_projection() { base = 1; let cg_base = self.codegen_consume( bx, @@ -507,13 +493,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { )); } - // Cast the place pointer type to the new - // array or slice type (`*[%_; new_len]`). - subslice.llval = bx.pointercast( - subslice.llval, - bx.cx().type_ptr_to(bx.cx().backend_type(subslice.layout)), - ); - subslice } mir::ProjectionElem::Downcast(_, v) => cg_base.project_downcast(bx, v), diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 6e7065713b817..07c61df21403b 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -11,7 +11,7 @@ use rustc_middle::mir; use rustc_middle::mir::Operand; use rustc_middle::ty::cast::{CastTy, IntTy}; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout}; -use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt}; +use rustc_middle::ty::{self, adjustment::PointerCoercion, Instance, Ty, TyCtxt}; use rustc_session::config::OptLevel; use rustc_span::source_map::{Span, DUMMY_SP}; use rustc_target::abi::{self, FIRST_VARIANT}; @@ -32,7 +32,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { cg_operand.val.store(bx, dest); } - mir::Rvalue::Cast(mir::CastKind::Pointer(PointerCast::Unsize), ref source, _) => { + mir::Rvalue::Cast( + mir::CastKind::PointerCoercion(PointerCoercion::Unsize), + ref source, + _, + ) => { // The destination necessarily contains a fat pointer, so if // it's a scalar pair, it's a fat pointer or newtype thereof. if bx.cx().is_backend_scalar_pair(dest.layout) { @@ -70,6 +74,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandValue::Ref(_, Some(_), _) => { bug!("unsized coercion on an unsized rvalue"); } + OperandValue::ZeroSized => { + bug!("unsized coercion on a ZST rvalue"); + } } } @@ -165,19 +172,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } match src.val { - OperandValue::Ref(..) => { + OperandValue::Ref(..) | OperandValue::ZeroSized => { span_bug!( self.mir.span, "Operand path should have handled transmute \ - from `Ref` {src:?} to place {dst:?}" + from {src:?} to place {dst:?}" ); } OperandValue::Immediate(..) | OperandValue::Pair(..) => { // When we have immediate(s), the alignment of the source is irrelevant, // so we can store them using the destination's alignment. - let llty = bx.backend_type(src.layout); - let cast_ptr = bx.pointercast(dst.llval, bx.type_ptr_to(llty)); - src.val.store(bx, PlaceRef::new_sized_aligned(cast_ptr, src.layout, dst.align)); + src.val.store(bx, PlaceRef::new_sized_aligned(dst.llval, src.layout, dst.align)); } } } @@ -215,22 +220,25 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandValue::Ref(ptr, meta, align) => { debug_assert_eq!(meta, None); debug_assert!(matches!(operand_kind, OperandValueKind::Ref)); - let cast_bty = bx.backend_type(cast); - let cast_ptr = bx.pointercast(ptr, bx.type_ptr_to(cast_bty)); - let fake_place = PlaceRef::new_sized_aligned(cast_ptr, cast, align); + let fake_place = PlaceRef::new_sized_aligned(ptr, cast, align); Some(bx.load_operand(fake_place).val) } + OperandValue::ZeroSized => { + let OperandValueKind::ZeroSized = operand_kind else { + bug!("Found {operand_kind:?} for operand {operand:?}"); + }; + if let OperandValueKind::ZeroSized = cast_kind { + Some(OperandValue::ZeroSized) + } else { + None + } + } OperandValue::Immediate(imm) => { let OperandValueKind::Immediate(in_scalar) = operand_kind else { bug!("Found {operand_kind:?} for operand {operand:?}"); }; - if let OperandValueKind::Immediate(out_scalar) = cast_kind { - match (in_scalar, out_scalar) { - (ScalarOrZst::Zst, ScalarOrZst::Zst) => { - Some(OperandRef::new_zst(bx, cast).val) - } - (ScalarOrZst::Scalar(in_scalar), ScalarOrZst::Scalar(out_scalar)) - if in_scalar.size(self.cx) == out_scalar.size(self.cx) => + if let OperandValueKind::Immediate(out_scalar) = cast_kind + && in_scalar.size(self.cx) == out_scalar.size(self.cx) { let operand_bty = bx.backend_type(operand.layout); let cast_bty = bx.backend_type(cast); @@ -242,9 +250,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { out_scalar, cast_bty, ))) - } - _ => None, - } } else { None } @@ -388,8 +393,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ) -> OperandRef<'tcx, Bx::Value> { assert!( self.rvalue_creates_operand(rvalue, DUMMY_SP), - "cannot codegen {:?} to operand", - rvalue, + "cannot codegen {rvalue:?} to operand", ); match *rvalue { @@ -406,14 +410,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let lladdr = bx.ptrtoint(llptr, llcast_ty); OperandValue::Immediate(lladdr) } - mir::CastKind::Pointer(PointerCast::ReifyFnPointer) => { + mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer) => { match *operand.layout.ty.kind() { - ty::FnDef(def_id, substs) => { + ty::FnDef(def_id, args) => { let instance = ty::Instance::resolve_for_fn_ptr( bx.tcx(), ty::ParamEnv::reveal_all(), def_id, - substs, + args, ) .unwrap() .polymorphize(bx.cx().tcx()); @@ -422,13 +426,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => bug!("{} cannot be reified to a fn ptr", operand.layout.ty), } } - mir::CastKind::Pointer(PointerCast::ClosureFnPointer(_)) => { + mir::CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)) => { match *operand.layout.ty.kind() { - ty::Closure(def_id, substs) => { + ty::Closure(def_id, args) => { let instance = Instance::resolve_closure( bx.cx().tcx(), def_id, - substs, + args, ty::ClosureKind::FnOnce, ) .expect("failed to normalize and resolve closure during codegen") @@ -438,11 +442,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { _ => bug!("{} cannot be cast to a fn ptr", operand.layout.ty), } } - mir::CastKind::Pointer(PointerCast::UnsafeFnPointer) => { + mir::CastKind::PointerCoercion(PointerCoercion::UnsafeFnPointer) => { // This is a no-op at the LLVM level. operand.val } - mir::CastKind::Pointer(PointerCast::Unsize) => { + mir::CastKind::PointerCoercion(PointerCoercion::Unsize) => { assert!(bx.cx().is_backend_scalar_pair(cast)); let (lldata, llextra) = match operand.val { OperandValue::Pair(lldata, llextra) => { @@ -457,29 +461,24 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandValue::Ref(..) => { bug!("by-ref operand {:?} in `codegen_rvalue_operand`", operand); } + OperandValue::ZeroSized => { + bug!("zero-sized operand {:?} in `codegen_rvalue_operand`", operand); + } }; let (lldata, llextra) = base::unsize_ptr(bx, lldata, operand.layout.ty, cast.ty, llextra); OperandValue::Pair(lldata, llextra) } - mir::CastKind::Pointer(PointerCast::MutToConstPointer) + mir::CastKind::PointerCoercion(PointerCoercion::MutToConstPointer) | mir::CastKind::PtrToPtr if bx.cx().is_backend_scalar_pair(operand.layout) => { if let OperandValue::Pair(data_ptr, meta) = operand.val { if bx.cx().is_backend_scalar_pair(cast) { - let data_cast = bx.pointercast( - data_ptr, - bx.cx().scalar_pair_element_backend_type(cast, 0, true), - ); - OperandValue::Pair(data_cast, meta) + OperandValue::Pair(data_ptr, meta) } else { - // cast to thin-ptr - // Cast of fat-ptr to thin-ptr is an extraction of data-ptr and - // pointer-cast of that pointer to desired pointer type. - let llcast_ty = bx.cx().immediate_backend_type(cast); - let llval = bx.pointercast(data_ptr, llcast_ty); - OperandValue::Immediate(llval) + // Cast of fat-ptr to thin-ptr is an extraction of data-ptr. + OperandValue::Immediate(data_ptr) } } else { bug!("unexpected non-pair operand"); @@ -490,13 +489,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { OperandValue::Ref(_, _, _) => todo!(), OperandValue::Immediate(v) => (v, None), OperandValue::Pair(v, l) => (v, Some(l)), + OperandValue::ZeroSized => bug!("ZST -- which is not PointerLike -- in DynStar"), }; let (lldata, llextra) = base::cast_to_dyn_star(bx, lldata, operand.layout, cast.ty, llextra); OperandValue::Pair(lldata, llextra) } - mir::CastKind::Pointer( - PointerCast::MutToConstPointer | PointerCast::ArrayToPointer, + mir::CastKind::PointerCoercion( + PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, ) | mir::CastKind::IntToInt | mir::CastKind::FloatToInt @@ -572,7 +572,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::Rvalue::Ref(_, bk, place) => { let mk_ref = move |tcx: TyCtxt<'tcx>, ty: Ty<'tcx>| { - tcx.mk_ref( + Ty::new_ref( + tcx, tcx.lifetimes.re_erased, ty::TypeAndMut { ty, mutbl: bk.to_mutbl_lossy() }, ) @@ -583,7 +584,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::Rvalue::CopyForDeref(place) => self.codegen_operand(bx, &Operand::Copy(place)), mir::Rvalue::AddressOf(mutability, place) => { let mk_ptr = move |tcx: TyCtxt<'tcx>, ty: Ty<'tcx>| { - tcx.mk_ptr(ty::TypeAndMut { ty, mutbl: mutability }) + Ty::new_ptr(tcx, ty::TypeAndMut { ty, mutbl: mutability }) }; self.codegen_place_to_pointer(bx, place, mk_ptr) } @@ -635,7 +636,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { lhs.layout.ty, ); let val_ty = op.ty(bx.tcx(), lhs.layout.ty, rhs.layout.ty); - let operand_ty = bx.tcx().mk_tup(&[val_ty, bx.tcx().types.bool]); + let operand_ty = Ty::new_tup(bx.tcx(), &[val_ty, bx.tcx().types.bool]); OperandRef { val: result, layout: bx.cx().layout_of(operand_ty) } } @@ -668,11 +669,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::Rvalue::NullaryOp(ref null_op, ty) => { let ty = self.monomorphize(ty); - assert!(bx.cx().type_is_sized(ty)); let layout = bx.cx().layout_of(ty); let val = match null_op { - mir::NullOp::SizeOf => layout.size.bytes(), - mir::NullOp::AlignOf => layout.align.abi.bytes(), + mir::NullOp::SizeOf => { + assert!(bx.cx().type_is_sized(ty)); + layout.size.bytes() + } + mir::NullOp::AlignOf => { + assert!(bx.cx().type_is_sized(ty)); + layout.align.abi.bytes() + } mir::NullOp::OffsetOf(fields) => { layout.offset_of_subfield(bx.cx(), fields.iter().map(|f| f.index())).bytes() } @@ -692,7 +698,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { { let instance = ty::Instance { def: ty::InstanceDef::ThreadLocalShim(def_id), - substs: ty::InternalSubsts::empty(), + args: ty::GenericArgs::empty(), }; let fn_ptr = bx.get_fn_addr(instance); let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty()); @@ -713,17 +719,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // According to `rvalue_creates_operand`, only ZST // aggregate rvalues are allowed to be operands. let ty = rvalue.ty(self.mir, self.cx.tcx()); - OperandRef::new_zst(bx, self.cx.layout_of(self.monomorphize(ty))) + OperandRef::zero_sized(self.cx.layout_of(self.monomorphize(ty))) } mir::Rvalue::ShallowInitBox(ref operand, content_ty) => { let operand = self.codegen_operand(bx, operand); - let lloperand = operand.immediate(); + let val = operand.immediate(); let content_ty = self.monomorphize(content_ty); - let box_layout = bx.cx().layout_of(bx.tcx().mk_box(content_ty)); - let llty_ptr = bx.cx().backend_type(box_layout); + let box_layout = bx.cx().layout_of(Ty::new_box(bx.tcx(), content_ty)); - let val = bx.pointercast(lloperand, llty_ptr); OperandRef { val: OperandValue::Immediate(val), layout: box_layout } } } @@ -784,6 +788,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.add(lhs, rhs) } } + mir::BinOp::AddUnchecked => { + if is_signed { + bx.unchecked_sadd(lhs, rhs) + } else { + bx.unchecked_uadd(lhs, rhs) + } + } mir::BinOp::Sub => { if is_float { bx.fsub(lhs, rhs) @@ -791,6 +802,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.sub(lhs, rhs) } } + mir::BinOp::SubUnchecked => { + if is_signed { + bx.unchecked_ssub(lhs, rhs) + } else { + bx.unchecked_usub(lhs, rhs) + } + } mir::BinOp::Mul => { if is_float { bx.fmul(lhs, rhs) @@ -798,6 +816,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.mul(lhs, rhs) } } + mir::BinOp::MulUnchecked => { + if is_signed { + bx.unchecked_smul(lhs, rhs) + } else { + bx.unchecked_umul(lhs, rhs) + } + } mir::BinOp::Div => { if is_float { bx.fdiv(lhs, rhs) @@ -834,8 +859,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.inbounds_gep(llty, lhs, &[rhs]) } } - mir::BinOp::Shl => common::build_unchecked_lshift(bx, lhs, rhs), - mir::BinOp::Shr => common::build_unchecked_rshift(bx, input_ty, lhs, rhs), + mir::BinOp::Shl => common::build_masked_lshift(bx, lhs, rhs), + mir::BinOp::ShlUnchecked => { + let rhs = base::cast_shift_expr_rhs(bx, lhs, rhs); + bx.shl(lhs, rhs) + } + mir::BinOp::Shr => common::build_masked_rshift(bx, input_ty, lhs, rhs), + mir::BinOp::ShrUnchecked => { + let rhs = base::cast_shift_expr_rhs(bx, lhs, rhs); + if is_signed { bx.ashr(lhs, rhs) } else { bx.lshr(lhs, rhs) } + } mir::BinOp::Ne | mir::BinOp::Lt | mir::BinOp::Gt @@ -931,6 +964,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Can always load from a pointer as needed (OperandValueKind::Ref, _) => true, + // ZST-to-ZST is the easiest thing ever + (OperandValueKind::ZeroSized, OperandValueKind::ZeroSized) => true, + + // But if only one of them is a ZST the sizes can't match + (OperandValueKind::ZeroSized, _) | (_, OperandValueKind::ZeroSized) => false, + // Need to generate an `alloc` to get a pointer from an immediate (OperandValueKind::Immediate(..) | OperandValueKind::Pair(..), OperandValueKind::Ref) => false, @@ -974,12 +1013,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { /// Gets which variant of [`OperandValue`] is expected for a particular type. fn value_kind(&self, layout: TyAndLayout<'tcx>) -> OperandValueKind { - if self.cx.is_backend_immediate(layout) { + if layout.is_zst() { + OperandValueKind::ZeroSized + } else if self.cx.is_backend_immediate(layout) { debug_assert!(!self.cx.is_backend_scalar_pair(layout)); OperandValueKind::Immediate(match layout.abi { - abi::Abi::Scalar(s) => ScalarOrZst::Scalar(s), - abi::Abi::Vector { element, .. } => ScalarOrZst::Scalar(element), - _ if layout.is_zst() => ScalarOrZst::Zst, + abi::Abi::Scalar(s) => s, + abi::Abi::Vector { element, .. } => element, x => span_bug!(self.mir.span, "Couldn't translate {x:?} as backend immediate"), }) } else if self.cx.is_backend_scalar_pair(layout) { @@ -1002,21 +1042,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { #[derive(Debug, Copy, Clone)] enum OperandValueKind { Ref, - Immediate(ScalarOrZst), + Immediate(abi::Scalar), Pair(abi::Scalar, abi::Scalar), -} - -#[derive(Debug, Copy, Clone)] -enum ScalarOrZst { - Zst, - Scalar(abi::Scalar), -} - -impl ScalarOrZst { - pub fn size(self, cx: &impl abi::HasDataLayout) -> abi::Size { - match self { - ScalarOrZst::Zst => abi::Size::ZERO, - ScalarOrZst::Scalar(s) => s.size(cx), - } - } + ZeroSized, } diff --git a/compiler/rustc_codegen_ssa/src/mir/statement.rs b/compiler/rustc_codegen_ssa/src/mir/statement.rs index 3fd7397ad3865..899e41265bba5 100644 --- a/compiler/rustc_codegen_ssa/src/mir/statement.rs +++ b/compiler/rustc_codegen_ssa/src/mir/statement.rs @@ -20,7 +20,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } LocalRef::PendingOperand => { let operand = self.codegen_rvalue_operand(bx, rvalue); - self.locals[index] = LocalRef::Operand(operand); + self.overwrite_local(index, LocalRef::Operand(operand)); self.debug_introduce_local(bx, index); } LocalRef::Operand(op) => { @@ -65,7 +65,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } mir::StatementKind::Coverage(box ref coverage) => { - self.codegen_coverage(bx, coverage.clone(), statement.source_info.scope); + self.codegen_coverage(bx, coverage, statement.source_info.scope); } mir::StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(ref op)) => { let op_val = self.codegen_operand(bx, op); diff --git a/compiler/rustc_codegen_ssa/src/mono_item.rs b/compiler/rustc_codegen_ssa/src/mono_item.rs index 27da33581a1a9..6fbf992eda9ec 100644 --- a/compiler/rustc_codegen_ssa/src/mono_item.rs +++ b/compiler/rustc_codegen_ssa/src/mono_item.rs @@ -64,7 +64,7 @@ impl<'a, 'tcx: 'a> MonoItemExt<'a, 'tcx> for MonoItem<'tcx> { .typeck_body(anon_const.body) .node_type(anon_const.hir_id); let instance = match ty.kind() { - &ty::FnDef(def_id, substs) => Instance::new(def_id, substs), + &ty::FnDef(def_id, args) => Instance::new(def_id, args), _ => span_bug!(*op_sp, "asm sym is not a function"), }; @@ -138,10 +138,10 @@ impl<'a, 'tcx: 'a> MonoItemExt<'a, 'tcx> for MonoItem<'tcx> { fn to_raw_string(&self) -> String { match *self { MonoItem::Fn(instance) => { - format!("Fn({:?}, {})", instance.def, instance.substs.as_ptr().addr()) + format!("Fn({:?}, {})", instance.def, instance.args.as_ptr().addr()) } - MonoItem::Static(id) => format!("Static({:?})", id), - MonoItem::GlobalAsm(id) => format!("GlobalAsm({:?})", id), + MonoItem::Static(id) => format!("Static({id:?})"), + MonoItem::GlobalAsm(id) => format!("GlobalAsm({id:?})"), } } } diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index c5976a65411fd..baf6b19d3f97e 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -29,7 +29,6 @@ const ARM_ALLOWED_FEATURES: &[(&str, Option)] = &[ ("aclass", Some(sym::arm_target_feature)), ("aes", Some(sym::arm_target_feature)), ("crc", Some(sym::arm_target_feature)), - ("crypto", Some(sym::arm_target_feature)), ("d32", Some(sym::arm_target_feature)), ("dotprod", Some(sym::arm_target_feature)), ("dsp", Some(sym::arm_target_feature)), @@ -44,6 +43,7 @@ const ARM_ALLOWED_FEATURES: &[(&str, Option)] = &[ // #[target_feature]. ("thumb-mode", Some(sym::arm_target_feature)), ("thumb2", Some(sym::arm_target_feature)), + ("trustzone", Some(sym::arm_target_feature)), ("v5te", Some(sym::arm_target_feature)), ("v6", Some(sym::arm_target_feature)), ("v6k", Some(sym::arm_target_feature)), @@ -53,6 +53,7 @@ const ARM_ALLOWED_FEATURES: &[(&str, Option)] = &[ ("vfp2", Some(sym::arm_target_feature)), ("vfp3", Some(sym::arm_target_feature)), ("vfp4", Some(sym::arm_target_feature)), + ("virtualization", Some(sym::arm_target_feature)), // tidy-alphabetical-end ]; @@ -282,6 +283,7 @@ const WASM_ALLOWED_FEATURES: &[(&str, Option)] = &[ // tidy-alphabetical-start ("atomics", Some(sym::wasm_target_feature)), ("bulk-memory", Some(sym::wasm_target_feature)), + ("exception-handling", Some(sym::wasm_target_feature)), ("multivalue", Some(sym::wasm_target_feature)), ("mutable-globals", Some(sym::wasm_target_feature)), ("nontrapping-fptoint", Some(sym::wasm_target_feature)), @@ -294,6 +296,52 @@ const WASM_ALLOWED_FEATURES: &[(&str, Option)] = &[ const BPF_ALLOWED_FEATURES: &[(&str, Option)] = &[("alu32", Some(sym::bpf_target_feature))]; +const CSKY_ALLOWED_FEATURES: &[(&str, Option)] = &[ + // tidy-alphabetical-start + ("10e60", Some(sym::csky_target_feature)), + ("2e3", Some(sym::csky_target_feature)), + ("3e3r1", Some(sym::csky_target_feature)), + ("3e3r2", Some(sym::csky_target_feature)), + ("3e3r3", Some(sym::csky_target_feature)), + ("3e7", Some(sym::csky_target_feature)), + ("7e10", Some(sym::csky_target_feature)), + ("cache", Some(sym::csky_target_feature)), + ("doloop", Some(sym::csky_target_feature)), + ("dsp1e2", Some(sym::csky_target_feature)), + ("dspe60", Some(sym::csky_target_feature)), + ("e1", Some(sym::csky_target_feature)), + ("e2", Some(sym::csky_target_feature)), + ("edsp", Some(sym::csky_target_feature)), + ("elrw", Some(sym::csky_target_feature)), + ("float1e2", Some(sym::csky_target_feature)), + ("float1e3", Some(sym::csky_target_feature)), + ("float3e4", Some(sym::csky_target_feature)), + ("float7e60", Some(sym::csky_target_feature)), + ("floate1", Some(sym::csky_target_feature)), + ("hard-tp", Some(sym::csky_target_feature)), + ("high-registers", Some(sym::csky_target_feature)), + ("hwdiv", Some(sym::csky_target_feature)), + ("mp", Some(sym::csky_target_feature)), + ("mp1e2", Some(sym::csky_target_feature)), + ("nvic", Some(sym::csky_target_feature)), + ("trust", Some(sym::csky_target_feature)), + ("vdsp2e60f", Some(sym::csky_target_feature)), + ("vdspv1", Some(sym::csky_target_feature)), + ("vdspv2", Some(sym::csky_target_feature)), + // tidy-alphabetical-end + //fpu + // tidy-alphabetical-start + ("fdivdu", Some(sym::csky_target_feature)), + ("fpuv2_df", Some(sym::csky_target_feature)), + ("fpuv2_sf", Some(sym::csky_target_feature)), + ("fpuv3_df", Some(sym::csky_target_feature)), + ("fpuv3_hf", Some(sym::csky_target_feature)), + ("fpuv3_hi", Some(sym::csky_target_feature)), + ("fpuv3_sf", Some(sym::csky_target_feature)), + ("hard-float", Some(sym::csky_target_feature)), + ("hard-float-abi", Some(sym::csky_target_feature)), + // tidy-alphabetical-end +]; /// When rustdoc is running, provide a list of all known features so that all their respective /// primitives may be documented. /// @@ -309,6 +357,7 @@ pub fn all_known_features() -> impl Iterator &'static [(&'static str, Opt "aarch64" => AARCH64_ALLOWED_FEATURES, "x86" | "x86_64" => X86_ALLOWED_FEATURES, "hexagon" => HEXAGON_ALLOWED_FEATURES, - "mips" | "mips64" => MIPS_ALLOWED_FEATURES, + "mips" | "mips32r6" | "mips64" | "mips64r6" => MIPS_ALLOWED_FEATURES, "powerpc" | "powerpc64" => POWERPC_ALLOWED_FEATURES, "riscv32" | "riscv64" => RISCV_ALLOWED_FEATURES, "wasm32" | "wasm64" => WASM_ALLOWED_FEATURES, "bpf" => BPF_ALLOWED_FEATURES, + "csky" => CSKY_ALLOWED_FEATURES, _ => &[], } } @@ -366,13 +416,9 @@ pub fn from_target_feature( // We allow comma separation to enable multiple features. target_features.extend(value.as_str().split(',').filter_map(|feature| { let Some(feature_gate) = supported_target_features.get(feature) else { - let msg = - format!("the feature named `{}` is not valid for this target", feature); + let msg = format!("the feature named `{feature}` is not valid for this target"); let mut err = tcx.sess.struct_span_err(item.span(), msg); - err.span_label( - item.span(), - format!("`{}` is not valid for this target", feature), - ); + err.span_label(item.span(), format!("`{feature}` is not valid for this target")); if let Some(stripped) = feature.strip_prefix('+') { let valid = supported_target_features.contains_key(stripped); if valid { @@ -398,6 +444,7 @@ pub fn from_target_feature( Some(sym::ermsb_target_feature) => rust_features.ermsb_target_feature, Some(sym::bpf_target_feature) => rust_features.bpf_target_feature, Some(sym::aarch64_ver_target_feature) => rust_features.aarch64_ver_target_feature, + Some(sym::csky_target_feature) => rust_features.csky_target_feature, Some(name) => bug!("unknown target feature gate {}", name), None => true, }; @@ -406,7 +453,7 @@ pub fn from_target_feature( &tcx.sess.parse_sess, feature_gate.unwrap(), item.span(), - format!("the target feature `{}` is currently unstable", feature), + format!("the target feature `{feature}` is currently unstable"), ) .emit(); } diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index d83bfc74082f6..0a02ca6b317c2 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -6,7 +6,7 @@ use crate::back::write::TargetMachineFactoryFn; use crate::{CodegenResults, ModuleCodegen}; use rustc_ast::expand::allocator::AllocatorKind; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_errors::ErrorGuaranteed; use rustc_metadata::EncodedMetadata; @@ -23,6 +23,8 @@ use rustc_span::symbol::Symbol; use rustc_target::abi::call::FnAbi; use rustc_target::spec::Target; +use std::fmt; + pub trait BackendTypes { type Value: CodegenObject; type Function: CodegenObject; @@ -61,7 +63,7 @@ pub trait CodegenBackend { fn locale_resource(&self) -> &'static str; fn init(&self, _sess: &Session) {} - fn print(&self, _req: PrintRequest, _sess: &Session) {} + fn print(&self, _req: &PrintRequest, _out: &mut dyn PrintBackendInfo, _sess: &Session) {} fn target_features(&self, _sess: &Session, _allow_unstable: bool) -> Vec { vec![] } @@ -101,7 +103,7 @@ pub trait CodegenBackend { ongoing_codegen: Box, sess: &Session, outputs: &OutputFilenames, - ) -> Result<(CodegenResults, FxHashMap), ErrorGuaranteed>; + ) -> Result<(CodegenResults, FxIndexMap), ErrorGuaranteed>; /// This is called on the returned `Box` from `join_codegen` /// @@ -140,15 +142,6 @@ pub trait ExtraBackendMethods: target_features: &[String], ) -> TargetMachineFactoryFn; - fn spawn_thread(_time_trace: bool, f: F) -> std::thread::JoinHandle - where - F: FnOnce() -> T, - F: Send + 'static, - T: Send + 'static, - { - std::thread::spawn(f) - } - fn spawn_named_thread( _time_trace: bool, name: String, @@ -162,3 +155,19 @@ pub trait ExtraBackendMethods: std::thread::Builder::new().name(name).spawn(f) } } + +pub trait PrintBackendInfo { + fn infallible_write_fmt(&mut self, args: fmt::Arguments<'_>); +} + +impl PrintBackendInfo for String { + fn infallible_write_fmt(&mut self, args: fmt::Arguments<'_>) { + fmt::Write::write_fmt(self, args).unwrap(); + } +} + +impl dyn PrintBackendInfo + '_ { + pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) { + self.infallible_write_fmt(args); + } +} diff --git a/compiler/rustc_codegen_ssa/src/traits/consts.rs b/compiler/rustc_codegen_ssa/src/traits/consts.rs index 61906302779e9..4dff9c7684f18 100644 --- a/compiler/rustc_codegen_ssa/src/traits/consts.rs +++ b/compiler/rustc_codegen_ssa/src/traits/consts.rs @@ -1,13 +1,17 @@ use super::BackendTypes; -use crate::mir::place::PlaceRef; use rustc_middle::mir::interpret::{ConstAllocation, Scalar}; -use rustc_middle::ty::layout::TyAndLayout; -use rustc_target::abi::{self, Size}; +use rustc_target::abi; pub trait ConstMethods<'tcx>: BackendTypes { // Constant constructors fn const_null(&self, t: Self::Type) -> Self::Value; + /// Generate an uninitialized value (matching uninitialized memory in MIR). + /// Whether memory is initialized or not is tracked byte-for-byte. fn const_undef(&self, t: Self::Type) -> Self::Value; + /// Generate a fake value. Poison always affects the entire value, even if just a single byte is + /// poison. This can only be used in codepaths that are already UB, i.e., UB-free Rust code + /// (including code that e.g. copies uninit memory with `MaybeUninit`) can never encounter a + /// poison value. fn const_poison(&self, t: Self::Type) -> Self::Value; fn const_int(&self, t: Self::Type, i: i64) -> Self::Value; fn const_uint(&self, t: Self::Type, i: u64) -> Self::Value; @@ -17,6 +21,7 @@ pub trait ConstMethods<'tcx>: BackendTypes { fn const_i32(&self, i: i32) -> Self::Value; fn const_u32(&self, i: u32) -> Self::Value; fn const_u64(&self, i: u64) -> Self::Value; + fn const_u128(&self, i: u128) -> Self::Value; fn const_usize(&self, i: u64) -> Self::Value; fn const_u8(&self, i: u8) -> Self::Value; fn const_real(&self, t: Self::Type, val: f64) -> Self::Value; @@ -30,12 +35,7 @@ pub trait ConstMethods<'tcx>: BackendTypes { fn const_data_from_alloc(&self, alloc: ConstAllocation<'tcx>) -> Self::Value; fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, llty: Self::Type) -> Self::Value; - fn from_const_alloc( - &self, - layout: TyAndLayout<'tcx>, - alloc: ConstAllocation<'tcx>, - offset: Size, - ) -> PlaceRef<'tcx, Self::Value>; - fn const_ptrcast(&self, val: Self::Value, ty: Self::Type) -> Self::Value; + fn const_bitcast(&self, val: Self::Value, ty: Self::Type) -> Self::Value; + fn const_ptr_byte_offset(&self, val: Self::Value, offset: abi::Size) -> Self::Value; } diff --git a/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs b/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs index e77201cf0c800..7e8de0ddc5bf4 100644 --- a/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs +++ b/compiler/rustc_codegen_ssa/src/traits/coverageinfo.rs @@ -1,57 +1,11 @@ use super::BackendTypes; -use rustc_hir::def_id::DefId; -use rustc_middle::mir::coverage::*; +use rustc_middle::mir::Coverage; use rustc_middle::ty::Instance; -pub trait CoverageInfoMethods<'tcx>: BackendTypes { - fn coverageinfo_finalize(&self); - - /// Codegen a small function that will never be called, with one counter - /// that will never be incremented, that gives LLVM coverage tools a - /// function definition it needs in order to resolve coverage map references - /// to unused functions. This is necessary so unused functions will appear - /// as uncovered (coverage execution count `0`) in LLVM coverage reports. - fn define_unused_fn(&self, def_id: DefId); - - /// For LLVM codegen, returns a function-specific `Value` for a global - /// string, to hold the function name passed to LLVM intrinsic - /// `instrprof.increment()`. The `Value` is only created once per instance. - /// Multiple invocations with the same instance return the same `Value`. - fn get_pgo_func_name_var(&self, instance: Instance<'tcx>) -> Self::Value; -} - pub trait CoverageInfoBuilderMethods<'tcx>: BackendTypes { - /// Returns true if the function source hash was added to the coverage map (even if it had - /// already been added, for this instance). Returns false *only* if `-C instrument-coverage` is - /// not enabled (a coverage map is not being generated). - fn set_function_source_hash( - &mut self, - instance: Instance<'tcx>, - function_source_hash: u64, - ) -> bool; - - /// Returns true if the counter was added to the coverage map; false if `-C instrument-coverage` - /// is not enabled (a coverage map is not being generated). - fn add_coverage_counter( - &mut self, - instance: Instance<'tcx>, - index: CounterValueReference, - region: CodeRegion, - ) -> bool; - - /// Returns true if the expression was added to the coverage map; false if - /// `-C instrument-coverage` is not enabled (a coverage map is not being generated). - fn add_coverage_counter_expression( - &mut self, - instance: Instance<'tcx>, - id: InjectedExpressionId, - lhs: ExpressionOperandId, - op: Op, - rhs: ExpressionOperandId, - region: Option, - ) -> bool; - - /// Returns true if the region was added to the coverage map; false if `-C instrument-coverage` - /// is not enabled (a coverage map is not being generated). - fn add_coverage_unreachable(&mut self, instance: Instance<'tcx>, region: CodeRegion) -> bool; + /// Handle the MIR coverage info in a backend-specific way. + /// + /// This can potentially be a no-op in backends that don't support + /// coverage instrumentation. + fn add_coverage(&mut self, instance: Instance<'tcx>, coverage: &Coverage); } diff --git a/compiler/rustc_codegen_ssa/src/traits/mod.rs b/compiler/rustc_codegen_ssa/src/traits/mod.rs index 782fdadbfb87c..728c2bc8c49bc 100644 --- a/compiler/rustc_codegen_ssa/src/traits/mod.rs +++ b/compiler/rustc_codegen_ssa/src/traits/mod.rs @@ -30,10 +30,12 @@ mod write; pub use self::abi::AbiBuilderMethods; pub use self::asm::{AsmBuilderMethods, AsmMethods, GlobalAsmOperandRef, InlineAsmOperandRef}; -pub use self::backend::{Backend, BackendTypes, CodegenBackend, ExtraBackendMethods}; +pub use self::backend::{ + Backend, BackendTypes, CodegenBackend, ExtraBackendMethods, PrintBackendInfo, +}; pub use self::builder::{BuilderMethods, OverflowOp}; pub use self::consts::ConstMethods; -pub use self::coverageinfo::{CoverageInfoBuilderMethods, CoverageInfoMethods}; +pub use self::coverageinfo::CoverageInfoBuilderMethods; pub use self::debuginfo::{DebugInfoBuilderMethods, DebugInfoMethods}; pub use self::declare::PreDefineMethods; pub use self::intrinsic::IntrinsicCallMethods; @@ -59,7 +61,6 @@ pub trait CodegenMethods<'tcx>: + MiscMethods<'tcx> + ConstMethods<'tcx> + StaticMethods - + CoverageInfoMethods<'tcx> + DebugInfoMethods<'tcx> + AsmMethods<'tcx> + PreDefineMethods<'tcx> @@ -75,7 +76,6 @@ impl<'tcx, T> CodegenMethods<'tcx> for T where + MiscMethods<'tcx> + ConstMethods<'tcx> + StaticMethods - + CoverageInfoMethods<'tcx> + DebugInfoMethods<'tcx> + AsmMethods<'tcx> + PreDefineMethods<'tcx> diff --git a/compiler/rustc_codegen_ssa/src/traits/type_.rs b/compiler/rustc_codegen_ssa/src/traits/type_.rs index 36d9864221bcb..dc3dbd9d81949 100644 --- a/compiler/rustc_codegen_ssa/src/traits/type_.rs +++ b/compiler/rustc_codegen_ssa/src/traits/type_.rs @@ -26,8 +26,8 @@ pub trait BaseTypeMethods<'tcx>: Backend<'tcx> { fn type_func(&self, args: &[Self::Type], ret: Self::Type) -> Self::Type; fn type_struct(&self, els: &[Self::Type], packed: bool) -> Self::Type; fn type_kind(&self, ty: Self::Type) -> TypeKind; - fn type_ptr_to(&self, ty: Self::Type) -> Self::Type; - fn type_ptr_to_ext(&self, ty: Self::Type, address_space: AddressSpace) -> Self::Type; + fn type_ptr(&self) -> Self::Type; + fn type_ptr_ext(&self, address_space: AddressSpace) -> Self::Type; fn element_type(&self, ty: Self::Type) -> Self::Type; /// Returns the number of elements in `self` if it is a LLVM vector type. @@ -42,14 +42,6 @@ pub trait BaseTypeMethods<'tcx>: Backend<'tcx> { } pub trait DerivedTypeMethods<'tcx>: BaseTypeMethods<'tcx> + MiscMethods<'tcx> { - fn type_i8p(&self) -> Self::Type { - self.type_i8p_ext(AddressSpace::DATA) - } - - fn type_i8p_ext(&self, address_space: AddressSpace) -> Self::Type { - self.type_ptr_to_ext(self.type_i8(), address_space) - } - fn type_int(&self) -> Self::Type { match &self.sess().target.c_int_width[..] { "16" => self.type_i16(), @@ -126,6 +118,28 @@ pub trait LayoutTypeMethods<'tcx>: Backend<'tcx> { index: usize, immediate: bool, ) -> Self::Type; + + /// A type that can be used in a [`super::BuilderMethods::load`] + + /// [`super::BuilderMethods::store`] pair to implement a *typed* copy, + /// such as a MIR `*_0 = *_1`. + /// + /// It's always legal to return `None` here, as the provided impl does, + /// in which case callers should use [`super::BuilderMethods::memcpy`] + /// instead of the `load`+`store` pair. + /// + /// This can be helpful for things like arrays, where the LLVM backend type + /// `[3 x i16]` optimizes to three separate loads and stores, but it can + /// instead be copied via an `i48` that stays as the single `load`+`store`. + /// (As of 2023-05 LLVM cannot necessarily optimize away a `memcpy` in these + /// cases, due to `poison` handling, but in codegen we have more information + /// about the type invariants, so can emit something better instead.) + /// + /// This *should* return `None` for particularly-large types, where leaving + /// the `memcpy` may well be important to avoid code size explosion. + fn scalar_copy_backend_type(&self, layout: TyAndLayout<'tcx>) -> Option { + let _ = layout; + None + } } // For backends that support CFI using type membership (i.e., testing whether a given pointer is diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index 9826256a4c5d5..ecf5095d8a335 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -1,5 +1,5 @@ use crate::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule}; -use crate::back::write::{CodegenContext, FatLTOInput, ModuleConfig}; +use crate::back::write::{CodegenContext, FatLtoInput, ModuleConfig}; use crate::{CompiledModule, ModuleCodegen}; use rustc_errors::{FatalError, Handler}; @@ -23,7 +23,7 @@ pub trait WriteBackendMethods: 'static + Sized + Clone { /// for further optimization. fn run_fat_lto( cgcx: &CodegenContext, - modules: Vec>, + modules: Vec>, cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> Result, FatalError>; /// Performs thin LTO by performing necessary global analysis and returning two @@ -35,6 +35,7 @@ pub trait WriteBackendMethods: 'static + Sized + Clone { cached_modules: Vec<(SerializedModule, WorkProduct)>, ) -> Result<(Vec>, Vec), FatalError>; fn print_pass_timings(&self); + fn print_statistics(&self); unsafe fn optimize( cgcx: &CodegenContext, diag_handler: &Handler, diff --git a/compiler/rustc_const_eval/Cargo.toml b/compiler/rustc_const_eval/Cargo.toml index 74030a43c5053..4e47fed864045 100644 --- a/compiler/rustc_const_eval/Cargo.toml +++ b/compiler/rustc_const_eval/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] tracing = "0.1" either = "1" -rustc_apfloat = { path = "../rustc_apfloat" } +rustc_apfloat = "0.2.0" rustc_ast = { path = "../rustc_ast" } rustc_attr = { path = "../rustc_attr" } rustc_data_structures = { path = "../rustc_data_structures" } diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index f6751df443f7a..e5dd5729d8905 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -1,47 +1,349 @@ -const_eval_unstable_in_stable = - const-stable function cannot use `#[feature({$gate})]` - .unstable_sugg = if it is not part of the public API, make this function unstably const - .bypass_sugg = otherwise `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks +const_eval_address_space_full = + there are no more free addresses in the address space +const_eval_align_check_failed = accessing memory with alignment {$has}, but alignment {$required} is required +const_eval_align_offset_invalid_align = + `align_offset` called with non-power-of-two align: {$target_align} -const_eval_thread_local_access = - thread-local statics cannot be accessed at compile-time +const_eval_alignment_check_failed = + accessing memory with alignment {$has}, but alignment {$required} is required +const_eval_already_reported = + an error has already been reported elsewhere (this should not usually be printed) +const_eval_assume_false = + `assume` called with `false` -const_eval_static_access = - {$kind}s cannot refer to statics - .help = consider extracting the value of the `static` to a `const`, and referring to that - .teach_note = `static` and `const` variables can refer to other `const` variables. A `const` variable, however, cannot refer to a `static` variable. - .teach_help = To fix this, the value can be extracted to a `const` and then used. +const_eval_await_non_const = + cannot convert `{$ty}` into a future in {const_eval_const_context}s +const_eval_bounds_check_failed = + indexing out of bounds: the len is {$len} but the index is {$index} +const_eval_call_nonzero_intrinsic = + `{$name}` called on 0 + +const_eval_closure_call = + closures need an RFC before allowed to be called in {const_eval_const_context}s +const_eval_closure_fndef_not_const = + function defined here, but it is not `const` +const_eval_closure_non_const = + cannot call non-const closure in {const_eval_const_context}s +const_eval_consider_dereferencing = + consider dereferencing here +const_eval_const_accesses_static = constant accesses static + +const_eval_const_context = {$kind -> + [const] constant + [static] static + [const_fn] constant function + *[other] {""} +} + +const_eval_copy_nonoverlapping_overlapping = + `copy_nonoverlapping` called on overlapping ranges + +const_eval_dangling_int_pointer = + {$bad_pointer_message}: {$pointer} is a dangling pointer (it has no provenance) +const_eval_dangling_null_pointer = + {$bad_pointer_message}: null pointer is a dangling pointer (it has no provenance) +const_eval_dangling_ptr_in_final = encountered dangling pointer in final constant + +const_eval_dead_local = + accessing a dead local variable +const_eval_dealloc_immutable = + deallocating immutable allocation {$alloc} + +const_eval_dealloc_incorrect_layout = + incorrect layout on deallocation: {$alloc} has size {$size} and alignment {$align}, but gave size {$size_found} and alignment {$align_found} + +const_eval_dealloc_kind_mismatch = + deallocating {$alloc}, which is {$alloc_kind} memory, using {$kind} deallocation operation + +const_eval_deref_coercion_non_const = + cannot perform deref coercion on `{$ty}` in {const_eval_const_context}s + .note = attempting to deref into `{$target_ty}` + .target_note = deref defined here +const_eval_deref_function_pointer = + accessing {$allocation} which contains a function +const_eval_deref_test = dereferencing pointer failed +const_eval_deref_vtable_pointer = + accessing {$allocation} which contains a vtable +const_eval_different_allocations = + `{$name}` called on pointers into different allocations + +const_eval_division_by_zero = + dividing by zero +const_eval_division_overflow = + overflow in signed division (dividing MIN by -1) +const_eval_double_storage_live = + StorageLive on a local that was already live + +const_eval_dyn_call_not_a_method = + `dyn` call trying to call something that is not a method + +const_eval_dyn_call_vtable_mismatch = + `dyn` call on a pointer whose vtable does not match its type + +const_eval_dyn_star_call_vtable_mismatch = + `dyn*` call on a pointer whose vtable does not match its type + +const_eval_erroneous_constant = + erroneous constant used + +const_eval_error = {$error_kind -> + [static] could not evaluate static initializer + [const] evaluation of constant value failed + [const_with_path] evaluation of `{$instance}` failed + *[other] {""} +} + +const_eval_exact_div_has_remainder = + exact_div: {$a} cannot be divided by {$b} without remainder + +const_eval_fn_ptr_call = + function pointers need an RFC before allowed to be called in {const_eval_const_context}s +const_eval_for_loop_into_iter_non_const = + cannot convert `{$ty}` into an iterator in {const_eval_const_context}s + +const_eval_frame_note = {$times -> + [0] {const_eval_frame_note_inner} + *[other] [... {$times} additional calls {const_eval_frame_note_inner} ...] +} + +const_eval_frame_note_inner = inside {$where_ -> + [closure] closure + [instance] `{$instance}` + *[other] {""} +} + +const_eval_in_bounds_test = out-of-bounds pointer use +const_eval_incompatible_calling_conventions = + calling a function with calling convention {$callee_conv} using calling convention {$caller_conv} + +const_eval_incompatible_return_types = + calling a function with return type {$callee_ty} passing return place of type {$caller_ty} + +const_eval_incompatible_types = + calling a function with argument of type {$callee_ty} passing data of type {$caller_ty} + +const_eval_interior_mutability_borrow = + cannot borrow here, since the borrowed element may contain interior mutability + +const_eval_interior_mutable_data_refer = + {const_eval_const_context}s cannot refer to interior mutable data + .label = this borrow of an interior mutable value may end up in the final value + .help = to fix this, the value can be extracted to a separate `static` item and then referenced + .teach_note = + A constant containing interior mutable data behind a reference can allow you to modify that data. + This would make multiple uses of a constant to be able to see different values and allow circumventing + the `Send` and `Sync` requirements for shared mutable data, which is unsound. + +const_eval_invalid_align = + align has to be a power of 2 + +const_eval_invalid_align_details = + invalid align passed to `{$name}`: {$align} is {$err_kind -> + [not_power_of_two] not a power of 2 + [too_large] too large + *[other] {""} + } + +const_eval_invalid_bool = + interpreting an invalid 8-bit value as a bool: 0x{$value} +const_eval_invalid_char = + interpreting an invalid 32-bit value as a char: 0x{$value} +const_eval_invalid_dealloc = + deallocating {$alloc_id}, which is {$kind -> + [fn] a function + [vtable] a vtable + [static_mem] static memory + *[other] {""} + } + +const_eval_invalid_function_pointer = + using {$pointer} as function pointer but it does not point to a function +const_eval_invalid_meta = + invalid metadata in wide pointer: total size is bigger than largest supported object +const_eval_invalid_meta_slice = + invalid metadata in wide pointer: slice is bigger than largest supported object +const_eval_invalid_str = + this string is not valid UTF-8: {$err} +const_eval_invalid_tag = + enum value has invalid tag: {$tag} +const_eval_invalid_transmute = + transmuting from {$src_bytes}-byte type to {$dest_bytes}-byte type: `{$src}` -> `{$dest}` + +const_eval_invalid_uninit_bytes = + reading memory at {$alloc}{$access}, but memory is uninitialized at {$uninit}, and this operation requires initialized memory +const_eval_invalid_uninit_bytes_unknown = + using uninitialized data, but this operation requires initialized memory + +const_eval_invalid_vtable_pointer = + using {$pointer} as vtable pointer but it does not point to a vtable + + +const_eval_live_drop = + destructor of `{$dropped_ty}` cannot be evaluated at compile-time + .label = the destructor for this type cannot be evaluated in {const_eval_const_context}s + .dropped_at_label = value is dropped here + +const_eval_long_running = + constant evaluation is taking a long time + .note = this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. + If your compilation actually takes a long time, you can safely allow the lint. + .label = the const evaluator is currently interpreting this expression + .help = the constant being evaluated + +const_eval_match_eq_non_const = cannot match on `{$ty}` in {const_eval_const_context}s + .note = `{$ty}` cannot be compared in compile-time, and therefore cannot be used in `match`es + +const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id} + +const_eval_memory_access_test = memory access failed +const_eval_memory_exhausted = + tried to allocate more memory than available to compiler + +const_eval_modified_global = + modifying a static's initial value from another static's initializer + +const_eval_mut_deref = + mutation through a reference is not allowed in {const_eval_const_context}s + +const_eval_non_const_fmt_macro_call = + cannot call non-const formatting macro in {const_eval_const_context}s + +const_eval_non_const_fn_call = + cannot call non-const fn `{$def_path_str}` in {const_eval_const_context}s + +const_eval_non_const_impl = + impl defined here, but it is not `const` + +const_eval_noreturn_asm_returned = + returned from noreturn inline assembly + +const_eval_not_enough_caller_args = + calling a function with fewer arguments than it requires + +const_eval_nullary_intrinsic_fail = + could not evaluate nullary intrinsic + +const_eval_offset_from_overflow = + `{$name}` called when first pointer is too far ahead of second + +const_eval_offset_from_test = out-of-bounds `offset_from` +const_eval_offset_from_underflow = + `{$name}` called when first pointer is too far before second + +const_eval_operator_non_const = + cannot call non-const operator in {const_eval_const_context}s +const_eval_overflow = + overflow executing `{$name}` + +const_eval_overflow_shift = + overflowing shift by {$val} in `{$name}` + +const_eval_panic = + the evaluated program panicked at '{$msg}', {$file}:{$line}:{$col} + +const_eval_panic_non_str = argument to `panic!()` in a const context must have type `&str` + +const_eval_partial_pointer_copy = + unable to copy parts of a pointer from memory at {$ptr} +const_eval_partial_pointer_overwrite = + unable to overwrite parts of a pointer in memory at {$ptr} +const_eval_pointer_arithmetic_overflow = + overflowing in-bounds pointer arithmetic +const_eval_pointer_arithmetic_test = out-of-bounds pointer arithmetic +const_eval_pointer_out_of_bounds = + {$bad_pointer_message}: {$alloc_id} has size {$alloc_size}, so pointer to {$ptr_size} {$ptr_size -> + [1] byte + *[many] bytes + } starting at offset {$ptr_offset} is out-of-bounds +const_eval_pointer_use_after_free = + {$bad_pointer_message}: {$alloc_id} has been freed, so this pointer is dangling +const_eval_ptr_as_bytes_1 = + this code performed an operation that depends on the underlying bytes representing a pointer +const_eval_ptr_as_bytes_2 = + the absolute address of a pointer is not known at compile-time, so such operations are not supported +const_eval_question_branch_non_const = + `?` cannot determine the branch of `{$ty}` in {const_eval_const_context}s + +const_eval_question_from_residual_non_const = + `?` cannot convert from residual of `{$ty}` in {const_eval_const_context}s + +const_eval_range = in the range {$lo}..={$hi} +const_eval_range_lower = greater or equal to {$lo} +const_eval_range_singular = equal to {$lo} +const_eval_range_upper = less or equal to {$hi} +const_eval_range_wrapping = less or equal to {$hi}, or greater or equal to {$lo} +const_eval_raw_bytes = the raw bytes of the constant (size: {$size}, align: {$align}) {"{"}{$bytes}{"}"} + +const_eval_raw_eq_with_provenance = + `raw_eq` on bytes with provenance + +const_eval_raw_ptr_comparison = + pointers cannot be reliably compared during const eval + .note = see issue #53020 for more information const_eval_raw_ptr_to_int = pointers cannot be cast to integers during const eval .note = at compile-time, pointers do not have an integer value .note2 = avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior -const_eval_raw_ptr_comparison = - pointers cannot be reliably compared during const eval - .note = see issue #53020 for more information +const_eval_read_extern_static = + cannot read from extern static ({$did}) +const_eval_read_pointer_as_int = + unable to turn pointer into integer +const_eval_realloc_or_alloc_with_offset = + {$kind -> + [dealloc] deallocating + [realloc] reallocating + *[other] {""} + } {$ptr} which does not point to the beginning of an object -const_eval_panic_non_str = argument to `panic!()` in a const context must have type `&str` +const_eval_remainder_by_zero = + calculating the remainder with a divisor of zero +const_eval_remainder_overflow = + overflow in signed remainder (dividing MIN by -1) +const_eval_scalar_size_mismatch = + scalar size mismatch: expected {$target_size} bytes but got {$data_size} bytes instead +const_eval_size_overflow = + overflow computing total size of `{$name}` -const_eval_mut_deref = - mutation through a reference is not allowed in {$kind}s +const_eval_stack_frame_limit_reached = + reached the configured maximum number of stack frames -const_eval_transient_mut_borrow = mutable references are not allowed in {$kind}s +const_eval_static_access = + {const_eval_const_context}s cannot refer to statics + .help = consider extracting the value of the `static` to a `const`, and referring to that + .teach_note = `static` and `const` variables can refer to other `const` variables. A `const` variable, however, cannot refer to a `static` variable. + .teach_help = To fix this, the value can be extracted to a `const` and then used. -const_eval_transient_mut_borrow_raw = raw mutable references are not allowed in {$kind}s +const_eval_thread_local_access = + thread-local statics cannot be accessed at compile-time -const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id} +const_eval_thread_local_static = + cannot access thread local static ({$did}) +const_eval_too_generic = + encountered overly generic constant +const_eval_too_many_caller_args = + calling a function with more arguments than it expected -const_eval_unallowed_fn_pointer_call = function pointer calls are not allowed in {$kind}s +const_eval_transient_mut_borrow = mutable references are not allowed in {const_eval_const_context}s -const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn +const_eval_transient_mut_borrow_raw = raw mutable references are not allowed in {const_eval_const_context}s -const_eval_unallowed_mutable_refs = - mutable references are not allowed in the final value of {$kind}s - .teach_note = - References in statics and constants may only refer to immutable values. +const_eval_try_block_from_output_non_const = + `try` block cannot convert `{$ty}` to the result in {const_eval_const_context}s +const_eval_unallowed_fn_pointer_call = function pointer calls are not allowed in {const_eval_const_context}s +const_eval_unallowed_heap_allocations = + allocations are not allowed in {const_eval_const_context}s + .label = allocation not allowed in {const_eval_const_context}s + .teach_note = + The value of statics and constants must be known at compile time, and they live for the entire lifetime of a program. Creating a boxed value allocates memory on the heap at runtime, and therefore cannot be done at compile time. +const_eval_unallowed_inline_asm = + inline assembly is not allowed in {const_eval_const_context}s +const_eval_unallowed_mutable_refs = + mutable references are not allowed in the final value of {const_eval_const_context}s + .teach_note = Statics are shared everywhere, and if they refer to mutable data one might violate memory safety since holding multiple mutable references to shared data is not allowed. @@ -49,7 +351,7 @@ const_eval_unallowed_mutable_refs = If you really want global mutable state, try using static mut or a global UnsafeCell. const_eval_unallowed_mutable_refs_raw = - raw mutable references are not allowed in the final value of {$kind}s + raw mutable references are not allowed in the final value of {const_eval_const_context}s .teach_note = References in statics and constants may only refer to immutable values. @@ -60,32 +362,106 @@ const_eval_unallowed_mutable_refs_raw = If you really want global mutable state, try using static mut or a global UnsafeCell. -const_eval_non_const_fmt_macro_call = - cannot call non-const formatting macro in {$kind}s - -const_eval_non_const_fn_call = - cannot call non-const fn `{$def_path_str}` in {$kind}s - const_eval_unallowed_op_in_const_context = {$msg} -const_eval_unallowed_heap_allocations = - allocations are not allowed in {$kind}s - .label = allocation not allowed in {$kind}s - .teach_note = - The value of statics and constants must be known at compile time, and they live for the entire lifetime of a program. Creating a boxed value allocates memory on the heap at runtime, and therefore cannot be done at compile time. +const_eval_unavailable_target_features_for_fn = + calling a function that requires unavailable target features: {$unavailable_feats} -const_eval_unallowed_inline_asm = - inline assembly is not allowed in {$kind}s +const_eval_undefined_behavior = + it is undefined behavior to use this value -const_eval_interior_mutable_data_refer = - {$kind}s cannot refer to interior mutable data - .label = this borrow of an interior mutable value may end up in the final value - .help = to fix this, the value can be extracted to a separate `static` item and then referenced - .teach_note = - A constant containing interior mutable data behind a reference can allow you to modify that data. - This would make multiple uses of a constant to be able to see different values and allow circumventing - the `Send` and `Sync` requirements for shared mutable data, which is unsound. +const_eval_undefined_behavior_note = + The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. -const_eval_interior_mutability_borrow = - cannot borrow here, since the borrowed element may contain interior mutability +const_eval_uninhabited_enum_variant_read = + read discriminant of an uninhabited enum variant +const_eval_uninhabited_enum_variant_written = + writing discriminant of an uninhabited enum variant +const_eval_unreachable = entering unreachable code +const_eval_unreachable_unwind = + unwinding past a stack frame that does not allow unwinding + +const_eval_unsigned_offset_from_overflow = + `ptr_offset_from_unsigned` called when first pointer has smaller offset than second: {$a_offset} < {$b_offset} + +const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn + +const_eval_unstable_in_stable = + const-stable function cannot use `#[feature({$gate})]` + .unstable_sugg = if it is not part of the public API, make this function unstably const + .bypass_sugg = otherwise `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks + +const_eval_unsupported_untyped_pointer = unsupported untyped pointer in constant + .note = memory only reachable via raw pointers is not supported + +const_eval_unterminated_c_string = + reading a null-terminated string starting at {$pointer} with no null found before end of allocation + +const_eval_unwind_past_top = + unwinding past the topmost frame of the stack + +const_eval_upcast_mismatch = + upcast on a pointer whose vtable does not match its type + +## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. +## (We'd love to sort this differently to make that more clear but tidy won't let us...) +const_eval_validation_box_to_mut = {$front_matter}: encountered a box pointing to mutable memory in a constant +const_eval_validation_box_to_static = {$front_matter}: encountered a box pointing to a static variable in a constant +const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty} +const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance) +const_eval_validation_dangling_box_out_of_bounds = {$front_matter}: encountered a dangling box (going beyond the bounds of its allocation) +const_eval_validation_dangling_box_use_after_free = {$front_matter}: encountered a dangling box (use-after-free) +const_eval_validation_dangling_ref_no_provenance = {$front_matter}: encountered a dangling reference ({$pointer} has no provenance) +const_eval_validation_dangling_ref_out_of_bounds = {$front_matter}: encountered a dangling reference (going beyond the bounds of its allocation) +const_eval_validation_dangling_ref_use_after_free = {$front_matter}: encountered a dangling reference (use-after-free) + +const_eval_validation_expected_bool = expected a boolean +const_eval_validation_expected_box = expected a box +const_eval_validation_expected_char = expected a unicode scalar value +const_eval_validation_expected_enum_tag = expected a valid enum tag +const_eval_validation_expected_float = expected a floating point number +const_eval_validation_expected_fn_ptr = expected a function pointer +const_eval_validation_expected_init_scalar = expected initialized scalar value +const_eval_validation_expected_int = expected an integer +const_eval_validation_expected_raw_ptr = expected a raw pointer +const_eval_validation_expected_ref = expected a reference +const_eval_validation_expected_str = expected a string + +const_eval_validation_front_matter_invalid_value = constructing invalid value +const_eval_validation_front_matter_invalid_value_with_path = constructing invalid value at {$path} + +const_eval_validation_invalid_bool = {$front_matter}: encountered {$value}, but expected a boolean +const_eval_validation_invalid_box_meta = {$front_matter}: encountered invalid box metadata: total size is bigger than largest supported object +const_eval_validation_invalid_box_slice_meta = {$front_matter}: encountered invalid box metadata: slice is bigger than largest supported object +const_eval_validation_invalid_char = {$front_matter}: encountered {$value}, but expected a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`) + +const_eval_validation_invalid_enum_tag = {$front_matter}: encountered {$value}, but expected a valid enum tag +const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, but expected a function pointer +const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object +const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object +const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer +const_eval_validation_mutable_ref_in_const = {$front_matter}: encountered mutable reference in a `const` +const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!` +const_eval_validation_null_box = {$front_matter}: encountered a null box +const_eval_validation_null_fn_ptr = {$front_matter}: encountered a null function pointer +const_eval_validation_null_ref = {$front_matter}: encountered a null reference +const_eval_validation_nullable_ptr_out_of_range = {$front_matter}: encountered a potentially null pointer, but expected something that cannot possibly fail to be {$in_range} +const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but expected something {$in_range} +const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers +const_eval_validation_pointer_as_int = {$front_matter}: encountered a pointer, but {$expected} +const_eval_validation_ptr_out_of_range = {$front_matter}: encountered a pointer, but expected something that cannot possibly fail to be {$in_range} +const_eval_validation_ref_to_mut = {$front_matter}: encountered a reference pointing to mutable memory in a constant +const_eval_validation_ref_to_static = {$front_matter}: encountered a reference pointing to a static variable in a constant +const_eval_validation_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty} +const_eval_validation_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes}) +const_eval_validation_unaligned_ref = {$front_matter}: encountered an unaligned reference (required {$required_bytes} byte alignment but found {$found_bytes}) +const_eval_validation_uninhabited_enum_variant = {$front_matter}: encountered an uninhabited enum variant +const_eval_validation_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}` +const_eval_validation_uninit = {$front_matter}: encountered uninitialized memory, but {$expected} +const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in a `const` + +const_eval_write_to_read_only = + writing to {$allocation} which is read-only +const_eval_zst_pointer_out_of_bounds = + {$bad_pointer_message}: {$alloc_id} has size {$alloc_size}, so pointer at offset {$ptr_offset} is out-of-bounds diff --git a/compiler/rustc_const_eval/src/const_eval/error.rs b/compiler/rustc_const_eval/src/const_eval/error.rs index c591ff75ab878..d39a7e8a19250 100644 --- a/compiler/rustc_const_eval/src/const_eval/error.rs +++ b/compiler/rustc_const_eval/src/const_eval/error.rs @@ -1,17 +1,15 @@ -use std::error::Error; -use std::fmt; +use std::mem; -use rustc_errors::Diagnostic; +use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg}; use rustc_middle::mir::AssertKind; -use rustc_middle::query::TyCtxtAt; +use rustc_middle::ty::TyCtxt; use rustc_middle::ty::{layout::LayoutError, ConstInt}; -use rustc_span::{Span, Symbol}; +use rustc_span::source_map::Spanned; +use rustc_span::{ErrorGuaranteed, Span, Symbol}; use super::InterpCx; -use crate::interpret::{ - struct_error, ErrorHandled, FrameInfo, InterpError, InterpErrorInfo, Machine, MachineStopType, - UnsupportedOpInfo, -}; +use crate::errors::{self, FrameNote, ReportErrorExt}; +use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType}; /// The CTFE machine has some custom error kinds. #[derive(Clone, Debug)] @@ -23,7 +21,35 @@ pub enum ConstEvalErrKind { Abort(String), } -impl MachineStopType for ConstEvalErrKind {} +impl MachineStopType for ConstEvalErrKind { + fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + use ConstEvalErrKind::*; + match self { + ConstAccessesStatic => const_eval_const_accesses_static, + ModifiedGlobal => const_eval_modified_global, + Panic { .. } => const_eval_panic, + AssertFailure(x) => x.diagnostic_message(), + Abort(msg) => msg.to_string().into(), + } + } + fn add_args( + self: Box, + adder: &mut dyn FnMut(std::borrow::Cow<'static, str>, DiagnosticArgValue<'static>), + ) { + use ConstEvalErrKind::*; + match *self { + ConstAccessesStatic | ModifiedGlobal | Abort(_) => {} + AssertFailure(kind) => kind.add_args(adder), + Panic { msg, line, col, file } => { + adder("msg".into(), msg.into_diagnostic_arg()); + adder("file".into(), file.into_diagnostic_arg()); + adder("line".into(), line.into_diagnostic_arg()); + adder("col".into(), col.into_diagnostic_arg()); + } + } + } +} // The errors become `MachineStop` with plain strings when being raised. // `ConstEvalErr` (in `librustc_middle/mir/interpret/error.rs`) knows to @@ -34,151 +60,120 @@ impl<'tcx> Into> for ConstEvalErrKind { } } -impl fmt::Display for ConstEvalErrKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::ConstEvalErrKind::*; - match self { - ConstAccessesStatic => write!(f, "constant accesses static"), - ModifiedGlobal => { - write!(f, "modifying a static's initial value from another static's initializer") +pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>( + ecx: &InterpCx<'mir, 'tcx, M>, +) -> (Span, Vec) +where + 'tcx: 'mir, +{ + let mut stacktrace = ecx.generate_stacktrace(); + // Filter out `requires_caller_location` frames. + stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx)); + let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span); + + let mut frames = Vec::new(); + + // Add notes to the backtrace. Don't print a single-line backtrace though. + if stacktrace.len() > 1 { + // Helper closure to print duplicated lines. + let mut add_frame = |mut frame: errors::FrameNote| { + frames.push(errors::FrameNote { times: 0, ..frame.clone() }); + // Don't print [... additional calls ...] if the number of lines is small + if frame.times < 3 { + let times = frame.times; + frame.times = 0; + frames.extend(std::iter::repeat(frame).take(times as usize)); + } else { + frames.push(frame); } - AssertFailure(msg) => write!(f, "{:?}", msg), - Panic { msg, line, col, file } => { - write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col) + }; + + let mut last_frame: Option = None; + for frame_info in &stacktrace { + let frame = frame_info.as_note(*ecx.tcx); + match last_frame.as_mut() { + Some(last_frame) + if last_frame.span == frame.span + && last_frame.where_ == frame.where_ + && last_frame.instance == frame.instance => + { + last_frame.times += 1; + } + Some(last_frame) => { + add_frame(mem::replace(last_frame, frame)); + } + None => { + last_frame = Some(frame); + } } - Abort(msg) => write!(f, "{}", msg), + } + if let Some(frame) = last_frame { + add_frame(frame); } } -} - -impl Error for ConstEvalErrKind {} -/// When const-evaluation errors, this type is constructed with the resulting information, -/// and then used to emit the error as a lint or hard error. -#[derive(Debug)] -pub(super) struct ConstEvalErr<'tcx> { - pub span: Span, - pub error: InterpError<'tcx>, - pub stacktrace: Vec>, + (span, frames) } -impl<'tcx> ConstEvalErr<'tcx> { - /// Turn an interpreter error into something to report to the user. - /// As a side-effect, if RUSTC_CTFE_BACKTRACE is set, this prints the backtrace. - /// Should be called only if the error is actually going to be reported! - pub fn new<'mir, M: Machine<'mir, 'tcx>>( - ecx: &InterpCx<'mir, 'tcx, M>, - error: InterpErrorInfo<'tcx>, - span: Option, - ) -> ConstEvalErr<'tcx> - where - 'tcx: 'mir, - { - error.print_backtrace(); - let mut stacktrace = ecx.generate_stacktrace(); - // Filter out `requires_caller_location` frames. - stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx)); - // If `span` is missing, use topmost remaining frame, or else the "root" span from `ecx.tcx`. - let span = span.or_else(|| stacktrace.first().map(|f| f.span)).unwrap_or(ecx.tcx.span); - ConstEvalErr { error: error.into_kind(), stacktrace, span } - } - - pub(super) fn report(&self, tcx: TyCtxtAt<'tcx>, message: &str) -> ErrorHandled { - self.report_decorated(tcx, message, |_| {}) - } - - #[instrument(level = "trace", skip(self, decorate))] - pub(super) fn decorate(&self, err: &mut Diagnostic, decorate: impl FnOnce(&mut Diagnostic)) { - trace!("reporting const eval failure at {:?}", self.span); - // Add some more context for select error types. - match self.error { - InterpError::Unsupported( - UnsupportedOpInfo::ReadPointerAsBytes - | UnsupportedOpInfo::PartialPointerOverwrite(_) - | UnsupportedOpInfo::PartialPointerCopy(_), - ) => { - err.help("this code performed an operation that depends on the underlying bytes representing a pointer"); - err.help("the absolute address of a pointer is not known at compile-time, so such operations are not supported"); - } - _ => {} +/// Create a diagnostic for a const eval error. +/// +/// This will use the `mk` function for creating the error which will get passed labels according to +/// the `InterpError` and the span and a stacktrace of current execution according to +/// `get_span_and_frames`. +pub(super) fn report<'tcx, C, F, E>( + tcx: TyCtxt<'tcx>, + error: InterpError<'tcx>, + span: Option, + get_span_and_frames: C, + mk: F, +) -> ErrorHandled +where + C: FnOnce() -> (Span, Vec), + F: FnOnce(Span, Vec) -> E, + E: IntoDiagnostic<'tcx, ErrorGuaranteed>, +{ + // Special handling for certain errors + match error { + // Don't emit a new diagnostic for these errors + err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => { + ErrorHandled::TooGeneric } - // Add spans for the stacktrace. Don't print a single-line backtrace though. - if self.stacktrace.len() > 1 { - // Helper closure to print duplicated lines. - let mut flush_last_line = |last_frame: Option<(String, _)>, times| { - if let Some((line, span)) = last_frame { - err.span_note(span, line.clone()); - // Don't print [... additional calls ...] if the number of lines is small - if times < 3 { - for _ in 0..times { - err.span_note(span, line.clone()); - } - } else { - err.span_note( - span, - format!("[... {} additional calls {} ...]", times, &line), - ); - } - } + err_inval!(AlreadyReported(guar)) => ErrorHandled::Reported(guar), + err_inval!(Layout(LayoutError::ReferencesError(guar))) => { + ErrorHandled::Reported(guar.into()) + } + err_inval!(Layout(layout_error @ LayoutError::SizeOverflow(_))) => { + // We must *always* hard error on these, even if the caller wants just a lint. + // The `message` makes little sense here, this is a more serious error than the + // caller thinks anyway. + // See . + let (our_span, frames) = get_span_and_frames(); + let span = span.unwrap_or(our_span); + let mut err = + tcx.sess.create_err(Spanned { span, node: layout_error.into_diagnostic() }); + err.code(rustc_errors::error_code!(E0080)); + let Some((mut err, handler)) = err.into_diagnostic() else { + panic!("did not emit diag"); }; - - let mut last_frame = None; - let mut times = 0; - for frame_info in &self.stacktrace { - let frame = (frame_info.to_string(), frame_info.span); - if last_frame.as_ref() == Some(&frame) { - times += 1; - } else { - flush_last_line(last_frame, times); - last_frame = Some(frame); - times = 0; - } + for frame in frames { + err.eager_subdiagnostic(handler, frame); } - flush_last_line(last_frame, times); + + ErrorHandled::Reported(handler.emit_diagnostic(&mut err).unwrap().into()) } - // Let the caller attach any additional information it wants. - decorate(err); - } + _ => { + // Report as hard error. + let (our_span, frames) = get_span_and_frames(); + let span = span.unwrap_or(our_span); + let err = mk(span, frames); + let mut err = tcx.sess.create_err(err); - /// Create a diagnostic for this const eval error. - /// - /// Sets the message passed in via `message` and adds span labels with detailed error - /// information before handing control back to `decorate` to do any final annotations, - /// after which the diagnostic is emitted. - /// - /// If `lint_root.is_some()` report it as a lint, else report it as a hard error. - /// (Except that for some errors, we ignore all that -- see `must_error` below.) - #[instrument(skip(self, tcx, decorate), level = "debug")] - pub(super) fn report_decorated( - &self, - tcx: TyCtxtAt<'tcx>, - message: &str, - decorate: impl FnOnce(&mut Diagnostic), - ) -> ErrorHandled { - debug!("self.error: {:?}", self.error); - // Special handling for certain errors - match &self.error { - // Don't emit a new diagnostic for these errors - err_inval!(Layout(LayoutError::Unknown(_))) | err_inval!(TooGeneric) => { - ErrorHandled::TooGeneric - } - err_inval!(AlreadyReported(error_reported)) => ErrorHandled::Reported(*error_reported), - err_inval!(Layout(LayoutError::SizeOverflow(_))) => { - // We must *always* hard error on these, even if the caller wants just a lint. - // The `message` makes little sense here, this is a more serious error than the - // caller thinks anyway. - // See . - let mut err = struct_error(tcx, &self.error.to_string()); - self.decorate(&mut err, decorate); - ErrorHandled::Reported(err.emit().into()) - } - _ => { - // Report as hard error. - let mut err = struct_error(tcx, message); - err.span_label(self.span, self.error.to_string()); - self.decorate(&mut err, decorate); - ErrorHandled::Reported(err.emit().into()) - } + let msg = error.diagnostic_message(); + error.add_args(&tcx.sess.parse_sess.span_diagnostic, &mut err); + + // Use *our* span to label the interp error + err.span_label(our_span, msg); + ErrorHandled::Reported(err.emit().into()) } } } diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 046d2052968a3..4c7e9194401ac 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -1,12 +1,12 @@ use crate::const_eval::CheckAlignment; -use std::borrow::Cow; +use crate::errors::ConstEvalError; use either::{Left, Right}; use rustc_hir::def::DefKind; use rustc_middle::mir; -use rustc_middle::mir::interpret::ErrorHandled; -use rustc_middle::mir::pretty::display_allocation; +use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo}; +use rustc_middle::mir::pretty::write_allocation_bytes; use rustc_middle::traits::Reveal; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; @@ -14,7 +14,8 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_span::source_map::Span; use rustc_target::abi::{self, Abi}; -use super::{CompileTimeEvalContext, CompileTimeInterpreter, ConstEvalErr}; +use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter}; +use crate::errors; use crate::interpret::eval_nullary_intrinsic; use crate::interpret::{ intern_const_alloc_recursive, Allocation, ConstAlloc, ConstValue, CtfeValidationMode, GlobalId, @@ -22,10 +23,6 @@ use crate::interpret::{ RefTracking, StackPopCleanup, }; -const NOTE_ON_UNDEFINED_BEHAVIOR_ERROR: &str = "The rules on what exactly is undefined behavior aren't clear, \ - so this check might be overzealous. Please open an issue on the rustc \ - repository if you believe it should not be considered undefined behavior."; - // Returns a pointer to where the result lives fn eval_body_using_ecx<'mir, 'tcx>( ecx: &mut CompileTimeEvalContext<'mir, 'tcx>, @@ -48,20 +45,20 @@ fn eval_body_using_ecx<'mir, 'tcx>( "Unexpected DefKind: {:?}", ecx.tcx.def_kind(cid.instance.def_id()) ); - let layout = ecx.layout_of(body.bound_return_ty().subst(tcx, cid.instance.substs))?; + let layout = ecx.layout_of(body.bound_return_ty().instantiate(tcx, cid.instance.args))?; assert!(layout.is_sized()); let ret = ecx.allocate(layout, MemoryKind::Stack)?; trace!( "eval_body_using_ecx: pushing stack frame for global: {}{}", with_no_trimmed_paths!(ecx.tcx.def_path_str(cid.instance.def_id())), - cid.promoted.map_or_else(String::new, |p| format!("::promoted[{:?}]", p)) + cid.promoted.map_or_else(String::new, |p| format!("::promoted[{p:?}]")) ); ecx.push_stack_frame( cid.instance, body, - &ret.into(), + &ret.clone().into(), StackPopCleanup::Root { cleanup: false }, )?; @@ -96,14 +93,14 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>( tcx: TyCtxt<'tcx>, root_span: Span, param_env: ty::ParamEnv<'tcx>, - can_access_statics: bool, + can_access_statics: CanAccessStatics, ) -> CompileTimeEvalContext<'mir, 'tcx> { debug!("mk_eval_cx: {:?}", param_env); InterpCx::new( tcx, root_span, param_env, - CompileTimeInterpreter::new(tcx.const_eval_limit(), can_access_statics, CheckAlignment::No), + CompileTimeInterpreter::new(can_access_statics, CheckAlignment::No), ) } @@ -210,7 +207,7 @@ pub(crate) fn turn_into_const_value<'tcx>( tcx, tcx.def_span(key.value.instance.def_id()), key.param_env, - /*can_access_statics:*/ is_static, + CanAccessStatics::from(is_static), ); let mplace = ecx.raw_const_to_mplace(constant).expect( @@ -231,7 +228,6 @@ pub fn eval_to_const_value_raw_provider<'tcx>( tcx: TyCtxt<'tcx>, key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, ) -> ::rustc_middle::mir::interpret::EvalToConstValueResult<'tcx> { - assert!(key.param_env.is_const()); // see comment in eval_to_allocation_raw_provider for what we're doing here if key.param_env.reveal() == Reveal::All { let mut key = key; @@ -248,13 +244,19 @@ pub fn eval_to_const_value_raw_provider<'tcx>( // Catch such calls and evaluate them instead of trying to load a constant's MIR. if let ty::InstanceDef::Intrinsic(def_id) = key.value.instance.def { let ty = key.value.instance.ty(tcx, key.param_env); - let ty::FnDef(_, substs) = ty.kind() else { + let ty::FnDef(_, args) = ty.kind() else { bug!("intrinsic with type {:?}", ty); }; - return eval_nullary_intrinsic(tcx, key.param_env, def_id, substs).map_err(|error| { + return eval_nullary_intrinsic(tcx, key.param_env, def_id, args).map_err(|error| { let span = tcx.def_span(def_id); - let error = ConstEvalErr { error: error.into_kind(), stacktrace: vec![], span }; - error.report(tcx.at(span), "could not evaluate nullary intrinsic") + + super::report( + tcx, + error.into_kind(), + Some(span), + || (span, vec![]), + |span, _| errors::NullaryIntrinsicError { span }, + ) }); } @@ -266,7 +268,6 @@ pub fn eval_to_allocation_raw_provider<'tcx>( tcx: TyCtxt<'tcx>, key: ty::ParamEnvAnd<'tcx, GlobalId<'tcx>>, ) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> { - assert!(key.param_env.is_const()); // Because the constant is computed twice (once per value of `Reveal`), we are at risk of // reporting the same error twice here. To resolve this, we check whether we can evaluate the // constant in the more restrictive `Reveal::UserFacing`, which most likely already was @@ -306,8 +307,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>( // Statics (and promoteds inside statics) may access other statics, because unlike consts // they do not have to behave "as if" they were evaluated at runtime. CompileTimeInterpreter::new( - tcx.const_eval_limit(), - /*can_access_statics:*/ is_static, + CanAccessStatics::from(is_static), if tcx.sess.opts.unstable_opts.extra_const_ub_checks { CheckAlignment::Error } else { @@ -319,30 +319,42 @@ pub fn eval_to_allocation_raw_provider<'tcx>( let res = ecx.load_mir(cid.instance.def, cid.promoted); match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) { Err(error) => { - let err = ConstEvalErr::new(&ecx, error, None); - let msg = if is_static { - Cow::from("could not evaluate static initializer") + let (error, backtrace) = error.into_parts(); + backtrace.print_backtrace(); + + let (kind, instance) = if is_static { + ("static", String::new()) } else { // If the current item has generics, we'd like to enrich the message with the - // instance and its substs: to show the actual compile-time values, in addition to + // instance and its args: to show the actual compile-time values, in addition to // the expression, leading to the const eval error. let instance = &key.value.instance; - if !instance.substs.is_empty() { + if !instance.args.is_empty() { let instance = with_no_trimmed_paths!(instance.to_string()); - let msg = format!("evaluation of `{}` failed", instance); - Cow::from(msg) + ("const_with_path", instance) } else { - Cow::from("evaluation of constant value failed") + ("const", String::new()) } }; - Err(err.report(ecx.tcx.at(err.span), &msg)) + Err(super::report( + *ecx.tcx, + error, + None, + || super::get_span_and_frames(&ecx), + |span, frames| ConstEvalError { + span, + error_kind: kind, + instance, + frame_notes: frames, + }, + )) } Ok(mplace) => { // Since evaluation had no errors, validate the resulting constant. // This is a separate `try` block to provide more targeted error reporting. - let validation = try { - let mut ref_tracking = RefTracking::new(mplace); + let validation: Result<_, InterpErrorInfo<'_>> = try { + let mut ref_tracking = RefTracking::new(mplace.clone()); let mut inner = false; while let Some((mplace, path)) = ref_tracking.todo.pop() { let mode = match tcx.static_mutability(cid.instance.def_id()) { @@ -358,23 +370,37 @@ pub fn eval_to_allocation_raw_provider<'tcx>( } }; let alloc_id = mplace.ptr.provenance.unwrap(); + + // Validation failed, report an error. This is always a hard error. if let Err(error) = validation { - // Validation failed, report an error. This is always a hard error. - let err = ConstEvalErr::new(&ecx, error, None); - Err(err.report_decorated( - ecx.tcx, - "it is undefined behavior to use this value", - |diag| { - if matches!(err.error, InterpError::UndefinedBehavior(_)) { - diag.note(NOTE_ON_UNDEFINED_BEHAVIOR_ERROR); - } - diag.note(format!( - "the raw bytes of the constant ({}", - display_allocation( - *ecx.tcx, - ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner() - ) - )); + let (error, backtrace) = error.into_parts(); + backtrace.print_backtrace(); + + let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {}); + + let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner(); + let mut bytes = String::new(); + if alloc.size() != abi::Size::ZERO { + bytes = "\n".into(); + // FIXME(translation) there might be pieces that are translatable. + write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap(); + } + let raw_bytes = errors::RawBytesNote { + size: alloc.size().bytes(), + align: alloc.align.bytes(), + bytes, + }; + + Err(super::report( + *ecx.tcx, + error, + None, + || super::get_span_and_frames(&ecx), + move |span, frames| errors::UndefinedBehavior { + span, + ub_note, + frames, + raw_bytes, }, )) } else { diff --git a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs index fa8253d5e4966..4ee4ebbb9e449 100644 --- a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs @@ -28,16 +28,19 @@ pub fn is_parent_const_impl_raw(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { && tcx.constness(parent_id) == hir::Constness::Const } -/// Checks whether an item is considered to be `const`. If it is a constructor, it is const. If -/// it is a trait impl/function, return if it has a `const` modifier. If it is an intrinsic, -/// report whether said intrinsic has a `rustc_const_{un,}stable` attribute. Otherwise, return -/// `Constness::NotConst`. +/// Checks whether an item is considered to be `const`. If it is a constructor, anonymous const, +/// const block, const item or associated const, it is const. If it is a trait impl/function, +/// return if it has a `const` modifier. If it is an intrinsic, report whether said intrinsic +/// has a `rustc_const_{un,}stable` attribute. Otherwise, return `Constness::NotConst`. fn constness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Constness { let node = tcx.hir().get_by_def_id(def_id); match node { - hir::Node::Ctor(_) => hir::Constness::Const, - hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(impl_), .. }) => impl_.constness, + hir::Node::Ctor(_) + | hir::Node::AnonConst(_) + | hir::Node::ConstBlock(_) + | hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Const(..), .. }) => hir::Constness::Const, + hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(_), .. }) => tcx.generics_of(def_id).host_effect_index.map_or(hir::Constness::NotConst, |_| hir::Constness::Const), hir::Node::ForeignItem(hir::ForeignItem { kind: hir::ForeignItemKind::Fn(..), .. }) => { // Intrinsics use `rustc_const_{un,}stable` attributes to indicate constness. All other // foreign items cannot be evaluated at compile-time. diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 58b5755af07f2..b740b79d16241 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -16,25 +16,37 @@ use std::fmt; use rustc_ast::Mutability; use rustc_hir::def_id::DefId; use rustc_middle::mir::AssertMessage; -use rustc_session::Limit; use rustc_span::symbol::{sym, Symbol}; use rustc_target::abi::{Align, Size}; use rustc_target::spec::abi::Abi as CallAbi; +use crate::errors::{LongRunning, LongRunningWarn}; use crate::interpret::{ - self, compile_time_machine, AllocId, ConstAllocation, FnVal, Frame, ImmTy, InterpCx, + self, compile_time_machine, AllocId, ConstAllocation, FnArg, FnVal, Frame, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, Scalar, }; +use crate::{errors, fluent_generated as fluent}; use super::error::*; +/// When hitting this many interpreted terminators we emit a deny by default lint +/// that notfies the user that their constant takes a long time to evaluate. If that's +/// what they intended, they can just allow the lint. +const LINT_TERMINATOR_LIMIT: usize = 2_000_000; +/// The limit used by `-Z tiny-const-eval-limit`. This smaller limit is useful for internal +/// tests not needing to run 30s or more to show some behaviour. +const TINY_LINT_TERMINATOR_LIMIT: usize = 20; +/// After this many interpreted terminators, we start emitting progress indicators at every +/// power of two of interpreted terminators. +const PROGRESS_INDICATOR_START: usize = 4_000_000; + /// Extra machine state for CTFE, and the Machine instance pub struct CompileTimeInterpreter<'mir, 'tcx> { - /// For now, the number of terminators that can be evaluated before we throw a resource - /// exhaustion error. + /// The number of terminators that have been evaluated. /// - /// Setting this to `0` disables the limit and allows the interpreter to run forever. - pub(super) steps_remaining: usize, + /// This is used to produce lints informing the user that the compiler is not stuck. + /// Set to `usize::MAX` to never report anything. + pub(super) num_evaluated_steps: usize, /// The virtual call stack. pub(super) stack: Vec>, @@ -45,7 +57,7 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> { /// * Interning makes everything outside of statics immutable. /// * Pointers to allocations inside of statics can never leak outside, to a non-static global. /// This boolean here controls the second part. - pub(super) can_access_statics: bool, + pub(super) can_access_statics: CanAccessStatics, /// Whether to check alignment during evaluation. pub(super) check_alignment: CheckAlignment, @@ -71,14 +83,25 @@ impl CheckAlignment { } } +#[derive(Copy, Clone, PartialEq)] +pub(crate) enum CanAccessStatics { + No, + Yes, +} + +impl From for CanAccessStatics { + fn from(value: bool) -> Self { + if value { Self::Yes } else { Self::No } + } +} + impl<'mir, 'tcx> CompileTimeInterpreter<'mir, 'tcx> { pub(crate) fn new( - const_eval_limit: Limit, - can_access_statics: bool, + can_access_statics: CanAccessStatics, check_alignment: CheckAlignment, ) -> Self { CompileTimeInterpreter { - steps_remaining: const_eval_limit.0, + num_evaluated_steps: 0, stack: Vec::new(), can_access_statics, check_alignment, @@ -178,7 +201,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> { fn hook_special_const_fn( &mut self, instance: ty::Instance<'tcx>, - args: &[OpTy<'tcx>], + args: &[FnArg<'tcx>], dest: &PlaceTy<'tcx>, ret: Option, ) -> InterpResult<'tcx, Option>> { @@ -187,12 +210,13 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> { if Some(def_id) == self.tcx.lang_items().panic_display() || Some(def_id) == self.tcx.lang_items().begin_panic_fn() { + let args = self.copy_fn_args(args)?; // &str or &&str assert!(args.len() == 1); - let mut msg_place = self.deref_operand(&args[0])?; + let mut msg_place = self.deref_pointer(&args[0])?; while msg_place.layout.ty.is_ref() { - msg_place = self.deref_operand(&msg_place.into())?; + msg_place = self.deref_pointer(&msg_place)?; } let msg = Symbol::intern(self.read_str(&msg_place)?); @@ -206,15 +230,16 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> { *self.tcx, ty::ParamEnv::reveal_all(), const_def_id, - instance.substs, + instance.args, ) .unwrap() .unwrap(); return Ok(Some(new_instance)); } else if Some(def_id) == self.tcx.lang_items().align_offset_fn() { + let args = self.copy_fn_args(args)?; // For align_offset, we replace the function call if the pointer has no address. - match self.align_offset(instance, args, dest, ret)? { + match self.align_offset(instance, &args, dest, ret)? { ControlFlow::Continue(()) => return Ok(Some(instance)), ControlFlow::Break(()) => return Ok(None), } @@ -247,7 +272,10 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> { let target_align = self.read_scalar(&args[1])?.to_target_usize(self)?; if !target_align.is_power_of_two() { - throw_ub_format!("`align_offset` called with non-power-of-two align: {}", target_align); + throw_ub_custom!( + fluent::const_eval_align_offset_invalid_align, + target_align = target_align, + ); } match self.ptr_try_get_alloc_id(ptr) { @@ -267,7 +295,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> { self.eval_fn_call( FnVal::Instance(instance), (CallAbi::Rust, fn_abi), - &[addr, align], + &[FnArg::Copy(addr), FnArg::Copy(align)], /* with_caller_location = */ false, dest, ret, @@ -353,15 +381,18 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, "`alignment_check_failed` called when no alignment check requested" ), CheckAlignment::FutureIncompat => { - let err = ConstEvalErr::new(ecx, err, None); - ecx.tcx.struct_span_lint_hir( + let (_, backtrace) = err.into_parts(); + backtrace.print_backtrace(); + let (span, frames) = super::get_span_and_frames(&ecx); + + ecx.tcx.emit_spanned_lint( INVALID_ALIGNMENT, ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID), - err.span, - err.error.to_string(), - |db| { - err.decorate(db, |_| {}); - db + span, + errors::AlignmentCheckFailed { + has: has.bytes(), + required: required.bytes(), + frames, }, ); Ok(()) @@ -396,52 +427,41 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, fn find_mir_or_eval_fn( ecx: &mut InterpCx<'mir, 'tcx, Self>, - instance: ty::Instance<'tcx>, + orig_instance: ty::Instance<'tcx>, _abi: CallAbi, - args: &[OpTy<'tcx>], + args: &[FnArg<'tcx>], dest: &PlaceTy<'tcx>, ret: Option, _unwind: mir::UnwindAction, // unwinding is not supported in consts ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> { - debug!("find_mir_or_eval_fn: {:?}", instance); + debug!("find_mir_or_eval_fn: {:?}", orig_instance); + + // Replace some functions. + let Some(instance) = ecx.hook_special_const_fn(orig_instance, args, dest, ret)? else { + // Call has already been handled. + return Ok(None); + }; // Only check non-glue functions if let ty::InstanceDef::Item(def) = instance.def { // Execution might have wandered off into other crates, so we cannot do a stability- - // sensitive check here. But we can at least rule out functions that are not const - // at all. - if !ecx.tcx.is_const_fn_raw(def) { - // allow calling functions inside a trait marked with #[const_trait]. - if !ecx.tcx.is_const_default_method(def) { - // We certainly do *not* want to actually call the fn - // though, so be sure we return here. - throw_unsup_format!("calling non-const function `{}`", instance) - } - } - - let Some(new_instance) = ecx.hook_special_const_fn(instance, args, dest, ret)? else { - return Ok(None); - }; - - if new_instance != instance { - // We call another const fn instead. - // However, we return the *original* instance to make backtraces work out - // (and we hope this does not confuse the FnAbi checks too much). - return Ok(Self::find_mir_or_eval_fn( - ecx, - new_instance, - _abi, - args, - dest, - ret, - _unwind, - )? - .map(|(body, _instance)| (body, instance))); + // sensitive check here. But we can at least rule out functions that are not const at + // all. That said, we have to allow calling functions inside a trait marked with + // #[const_trait]. These *are* const-checked! + // FIXME: why does `is_const_fn_raw` not classify them as const? + if (!ecx.tcx.is_const_fn_raw(def) && !ecx.tcx.is_const_default_method(def)) + || ecx.tcx.has_attr(def, sym::rustc_do_not_const_check) + { + // We certainly do *not* want to actually call the fn + // though, so be sure we return here. + throw_unsup_format!("calling non-const function `{}`", instance) } } // This is a const fn. Call it. - Ok(Some((ecx.load_mir(instance.def, None)?, instance))) + // In case of replacement, we return the *original* instance to make backtraces work out + // (and we hope this does not confuse the FnAbi checks too much). + Ok(Some((ecx.load_mir(instance.def, None)?, orig_instance))) } fn call_intrinsic( @@ -475,7 +495,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, let align = match Align::from_bytes(align) { Ok(a) => a, - Err(err) => throw_ub_format!("align has to be a power of 2, {}", err), + Err(err) => throw_ub_custom!( + fluent::const_eval_invalid_align_details, + name = "const_allocate", + err_kind = err.diag_ident(), + align = err.align() + ), }; let ptr = ecx.allocate_ptr( @@ -493,7 +518,12 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, let size = Size::from_bytes(size); let align = match Align::from_bytes(align) { Ok(a) => a, - Err(err) => throw_ub_format!("align has to be a power of 2, {}", err), + Err(err) => throw_ub_custom!( + fluent::const_eval_invalid_align_details, + name = "const_deallocate", + err_kind = err.diag_ident(), + align = err.align() + ), }; // If an allocation is created in an another const, @@ -569,13 +599,54 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, fn increment_const_eval_counter(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> { // The step limit has already been hit in a previous call to `increment_const_eval_counter`. - if ecx.machine.steps_remaining == 0 { - return Ok(()); - } - ecx.machine.steps_remaining -= 1; - if ecx.machine.steps_remaining == 0 { - throw_exhaust!(StepLimitReached) + if let Some(new_steps) = ecx.machine.num_evaluated_steps.checked_add(1) { + let (limit, start) = if ecx.tcx.sess.opts.unstable_opts.tiny_const_eval_limit { + (TINY_LINT_TERMINATOR_LIMIT, TINY_LINT_TERMINATOR_LIMIT) + } else { + (LINT_TERMINATOR_LIMIT, PROGRESS_INDICATOR_START) + }; + + ecx.machine.num_evaluated_steps = new_steps; + // By default, we have a *deny* lint kicking in after some time + // to ensure `loop {}` doesn't just go forever. + // In case that lint got reduced, in particular for `--cap-lint` situations, we also + // have a hard warning shown every now and then for really long executions. + if new_steps == limit { + // By default, we stop after a million steps, but the user can disable this lint + // to be able to run until the heat death of the universe or power loss, whichever + // comes first. + let hir_id = ecx.best_lint_scope(); + let is_error = ecx + .tcx + .lint_level_at_node( + rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL, + hir_id, + ) + .0 + .is_error(); + let span = ecx.cur_span(); + ecx.tcx.emit_spanned_lint( + rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL, + hir_id, + span, + LongRunning { item_span: ecx.tcx.span }, + ); + // If this was a hard error, don't bother continuing evaluation. + if is_error { + let guard = ecx + .tcx + .sess + .delay_span_bug(span, "The deny lint should have already errored"); + throw_inval!(AlreadyReported(guard.into())); + } + } else if new_steps > start && new_steps.is_power_of_two() { + // Only report after a certain number of terminators have been evaluated and the + // current number of evaluated terminators is a power of 2. The latter gives us a cheap + // way to implement exponential backoff. + let span = ecx.cur_span(); + ecx.tcx.sess.emit_warning(LongRunningWarn { span, item_span: ecx.tcx.span }); + } } Ok(()) @@ -634,7 +705,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, } } else { // Read access. These are usually allowed, with some exceptions. - if machine.can_access_statics { + if machine.can_access_statics == CanAccessStatics::Yes { // Machine configuration allows us read from anything (e.g., `static` initializer). Ok(()) } else if static_def_id.is_some() { diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs index 05be45fef1391..854104622f133 100644 --- a/compiler/rustc_const_eval/src/const_eval/mod.rs +++ b/compiler/rustc_const_eval/src/const_eval/mod.rs @@ -1,14 +1,10 @@ // Not in interpret to make sure we do not use private implementation details use crate::errors::MaxNumNodesInConstErr; -use crate::interpret::{ - intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MemPlaceMeta, - Scalar, -}; -use rustc_hir::Mutability; +use crate::interpret::{intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, Scalar}; use rustc_middle::mir; use rustc_middle::mir::interpret::{EvalToValTreeResult, GlobalId}; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::{source_map::DUMMY_SP, symbol::Symbol}; mod error; @@ -28,7 +24,7 @@ pub(crate) fn const_caller_location( (file, line, col): (Symbol, u32, u32), ) -> ConstValue<'_> { trace!("const_caller_location: {}:{}:{}", file, line, col); - let mut ecx = mk_eval_cx(tcx, DUMMY_SP, ty::ParamEnv::reveal_all(), false); + let mut ecx = mk_eval_cx(tcx, DUMMY_SP, ty::ParamEnv::reveal_all(), CanAccessStatics::No); let loc_place = ecx.alloc_caller_location(file, line, col); if intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &loc_place).is_err() { @@ -57,10 +53,12 @@ pub(crate) fn eval_to_valtree<'tcx>( // FIXME Need to provide a span to `eval_to_valtree` let ecx = mk_eval_cx( - tcx, DUMMY_SP, param_env, + tcx, + DUMMY_SP, + param_env, // It is absolutely crucial for soundness that // we do not read from static items or other mutable memory. - false, + CanAccessStatics::No, ); let place = ecx.raw_const_to_mplace(const_alloc).unwrap(); debug!(?place); @@ -75,17 +73,8 @@ pub(crate) fn eval_to_valtree<'tcx>( let global_const_id = cid.display(tcx); match err { ValTreeCreationError::NodesOverflow => { - let msg = format!( - "maximum number of nodes exceeded in constant {}", - &global_const_id - ); - let mut diag = match tcx.hir().span_if_local(did) { - Some(span) => { - tcx.sess.create_err(MaxNumNodesInConstErr { span, global_const_id }) - } - None => tcx.sess.struct_err(msg), - }; - diag.emit(); + let span = tcx.hir().span_if_local(did); + tcx.sess.emit_err(MaxNumNodesInConstErr { span, global_const_id }); Ok(None) } @@ -96,73 +85,38 @@ pub(crate) fn eval_to_valtree<'tcx>( } #[instrument(skip(tcx), level = "debug")] -pub(crate) fn try_destructure_mir_constant<'tcx>( +pub fn try_destructure_mir_constant_for_diagnostics<'tcx>( tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - val: mir::ConstantKind<'tcx>, -) -> InterpResult<'tcx, mir::DestructuredConstant<'tcx>> { - trace!("destructure_mir_constant: {:?}", val); - let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); - let op = ecx.eval_mir_constant(&val, None, None)?; + val: ConstValue<'tcx>, + ty: Ty<'tcx>, +) -> Option> { + let param_env = ty::ParamEnv::reveal_all(); + let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No); + let op = ecx.const_val_to_op(val, ty, None).ok()?; // We go to `usize` as we cannot allocate anything bigger anyway. - let (field_count, variant, down) = match val.ty().kind() { + let (field_count, variant, down) = match ty.kind() { ty::Array(_, len) => (len.eval_target_usize(tcx, param_env) as usize, None, op), ty::Adt(def, _) if def.variants().is_empty() => { - throw_ub!(Unreachable) + return None; } ty::Adt(def, _) => { - let variant = ecx.read_discriminant(&op)?.1; - let down = ecx.operand_downcast(&op, variant)?; + let variant = ecx.read_discriminant(&op).ok()?; + let down = ecx.project_downcast(&op, variant).ok()?; (def.variants()[variant].fields.len(), Some(variant), down) } - ty::Tuple(substs) => (substs.len(), None, op), + ty::Tuple(args) => (args.len(), None, op), _ => bug!("cannot destructure mir constant {:?}", val), }; let fields_iter = (0..field_count) .map(|i| { - let field_op = ecx.operand_field(&down, i)?; + let field_op = ecx.project_field(&down, i).ok()?; let val = op_to_const(&ecx, &field_op); - Ok(mir::ConstantKind::Val(val, field_op.layout.ty)) + Some((val, field_op.layout.ty)) }) - .collect::>>()?; + .collect::>>()?; let fields = tcx.arena.alloc_from_iter(fields_iter); - Ok(mir::DestructuredConstant { variant, fields }) -} - -#[instrument(skip(tcx), level = "debug")] -pub(crate) fn deref_mir_constant<'tcx>( - tcx: TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - val: mir::ConstantKind<'tcx>, -) -> mir::ConstantKind<'tcx> { - let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); - let op = ecx.eval_mir_constant(&val, None, None).unwrap(); - let mplace = ecx.deref_operand(&op).unwrap(); - if let Some(alloc_id) = mplace.ptr.provenance { - assert_eq!( - tcx.global_alloc(alloc_id).unwrap_memory().0.0.mutability, - Mutability::Not, - "deref_mir_constant cannot be used with mutable allocations as \ - that could allow pattern matching to observe mutable statics", - ); - } - - let ty = match mplace.meta { - MemPlaceMeta::None => mplace.layout.ty, - // In case of unsized types, figure out the real type behind. - MemPlaceMeta::Meta(scalar) => match mplace.layout.ty.kind() { - ty::Str => bug!("there's no sized equivalent of a `str`"), - ty::Slice(elem_ty) => tcx.mk_array(*elem_ty, scalar.to_target_usize(&tcx).unwrap()), - _ => bug!( - "type {} should not have metadata, but had {:?}", - mplace.layout.ty, - mplace.meta - ), - }, - }; - - mir::ConstantKind::Val(op_to_const(&ecx, &mplace.into()), ty) + Some(mir::DestructuredConstant { variant, fields }) } diff --git a/compiler/rustc_const_eval/src/const_eval/valtrees.rs b/compiler/rustc_const_eval/src/const_eval/valtrees.rs index b10f2e9f862db..b15a65d67a3d3 100644 --- a/compiler/rustc_const_eval/src/const_eval/valtrees.rs +++ b/compiler/rustc_const_eval/src/const_eval/valtrees.rs @@ -1,14 +1,16 @@ use super::eval_queries::{mk_eval_cx, op_to_const}; use super::machine::CompileTimeEvalContext; use super::{ValTreeCreationError, ValTreeCreationResult, VALTREE_MAX_NODES}; +use crate::const_eval::CanAccessStatics; +use crate::interpret::MPlaceTy; use crate::interpret::{ intern_const_alloc_recursive, ConstValue, ImmTy, Immediate, InternKind, MemPlaceMeta, - MemoryKind, PlaceTy, Scalar, + MemoryKind, Place, Projectable, Scalar, }; -use crate::interpret::{MPlaceTy, Value}; +use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt}; use rustc_span::source_map::DUMMY_SP; -use rustc_target::abi::{Align, FieldIdx, VariantIdx, FIRST_VARIANT}; +use rustc_target::abi::VariantIdx; #[instrument(skip(ecx), level = "debug")] fn branches<'tcx>( @@ -19,15 +21,15 @@ fn branches<'tcx>( num_nodes: &mut usize, ) -> ValTreeCreationResult<'tcx> { let place = match variant { - Some(variant) => ecx.mplace_downcast(&place, variant).unwrap(), - None => *place, + Some(variant) => ecx.project_downcast(place, variant).unwrap(), + None => place.clone(), }; let variant = variant.map(|variant| Some(ty::ValTree::Leaf(ScalarInt::from(variant.as_u32())))); debug!(?place, ?variant); let mut fields = Vec::with_capacity(n); for i in 0..n { - let field = ecx.mplace_field(&place, i).unwrap(); + let field = ecx.project_field(&place, i).unwrap(); let valtree = const_to_valtree_inner(ecx, &field, num_nodes)?; fields.push(Some(valtree)); } @@ -54,13 +56,11 @@ fn slice_branches<'tcx>( place: &MPlaceTy<'tcx>, num_nodes: &mut usize, ) -> ValTreeCreationResult<'tcx> { - let n = place - .len(&ecx.tcx.tcx) - .unwrap_or_else(|_| panic!("expected to use len of place {:?}", place)); + let n = place.len(ecx).unwrap_or_else(|_| panic!("expected to use len of place {place:?}")); let mut elems = Vec::with_capacity(n as usize); for i in 0..n { - let place_elem = ecx.mplace_index(place, i).unwrap(); + let place_elem = ecx.project_index(place, i).unwrap(); let valtree = const_to_valtree_inner(ecx, &place_elem, num_nodes)?; elems.push(valtree); } @@ -87,7 +87,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>( Ok(ty::ValTree::zst()) } ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => { - let Ok(val) = ecx.read_immediate(&place.into()) else { + let Ok(val) = ecx.read_immediate(place) else { return Err(ValTreeCreationError::Other); }; let val = val.to_scalar(); @@ -103,7 +103,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>( ty::FnPtr(_) | ty::RawPtr(_) => Err(ValTreeCreationError::NonSupportedType), ty::Ref(_, _, _) => { - let Ok(derefd_place)= ecx.deref_operand(&place.into()) else { + let Ok(derefd_place)= ecx.deref_pointer(place) else { return Err(ValTreeCreationError::Other); }; debug!(?derefd_place); @@ -131,7 +131,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>( bug!("uninhabited types should have errored and never gotten converted to valtree") } - let Ok((_, variant)) = ecx.read_discriminant(&place.into()) else { + let Ok(variant) = ecx.read_discriminant(place) else { return Err(ValTreeCreationError::Other); }; branches(ecx, place, def.variant(variant).fields.len(), def.is_enum().then_some(variant), num_nodes) @@ -155,52 +155,37 @@ pub(crate) fn const_to_valtree_inner<'tcx>( } } -#[instrument(skip(ecx), level = "debug")] -fn create_mplace_from_layout<'tcx>( - ecx: &mut CompileTimeEvalContext<'tcx, 'tcx>, - ty: Ty<'tcx>, -) -> MPlaceTy<'tcx> { - let tcx = ecx.tcx; - let param_env = ecx.param_env; - let layout = tcx.layout_of(param_env.and(ty)).unwrap(); - debug!(?layout); - - ecx.allocate(layout, MemoryKind::Stack).unwrap() -} - -// Walks custom DSTs and gets the type of the unsized field and the number of elements -// in the unsized field. -fn get_info_on_unsized_field<'tcx>( - ty: Ty<'tcx>, +/// Valtrees don't store the `MemPlaceMeta` that all dynamically sized values have in the interpreter. +/// This function reconstructs it. +fn reconstruct_place_meta<'tcx>( + layout: TyAndLayout<'tcx>, valtree: ty::ValTree<'tcx>, tcx: TyCtxt<'tcx>, -) -> (Ty<'tcx>, usize) { +) -> MemPlaceMeta { + if layout.is_sized() { + return MemPlaceMeta::None; + } + let mut last_valtree = valtree; + // Traverse the type, and update `last_valtree` as we go. let tail = tcx.struct_tail_with_normalize( - ty, + layout.ty, |ty| ty, || { let branches = last_valtree.unwrap_branch(); - last_valtree = branches[branches.len() - 1]; + last_valtree = *branches.last().unwrap(); debug!(?branches, ?last_valtree); }, ); - let unsized_inner_ty = match tail.kind() { - ty::Slice(t) => *t, - ty::Str => tail, - _ => bug!("expected Slice or Str"), - }; - - // Have to adjust type for ty::Str - let unsized_inner_ty = match unsized_inner_ty.kind() { - ty::Str => tcx.types.u8, - _ => unsized_inner_ty, + // Sanity-check that we got a tail we support. + match tail.kind() { + ty::Slice(..) | ty::Str => {} + _ => bug!("unsized tail of a valtree must be Slice or Str"), }; - // Get the number of elements in the unsized field + // Get the number of elements in the unsized field. let num_elems = last_valtree.unwrap_branch().len(); - - (unsized_inner_ty, num_elems) + MemPlaceMeta::Meta(Scalar::from_target_usize(num_elems as u64, &tcx)) } #[instrument(skip(ecx), level = "debug", ret)] @@ -209,41 +194,9 @@ fn create_pointee_place<'tcx>( ty: Ty<'tcx>, valtree: ty::ValTree<'tcx>, ) -> MPlaceTy<'tcx> { - let tcx = ecx.tcx.tcx; - - if !ty.is_sized(*ecx.tcx, ty::ParamEnv::empty()) { - // We need to create `Allocation`s for custom DSTs - - let (unsized_inner_ty, num_elems) = get_info_on_unsized_field(ty, valtree, tcx); - let unsized_inner_ty = match unsized_inner_ty.kind() { - ty::Str => tcx.types.u8, - _ => unsized_inner_ty, - }; - let unsized_inner_ty_size = - tcx.layout_of(ty::ParamEnv::empty().and(unsized_inner_ty)).unwrap().layout.size(); - debug!(?unsized_inner_ty, ?unsized_inner_ty_size, ?num_elems); - - // for custom DSTs only the last field/element is unsized, but we need to also allocate - // space for the other fields/elements - let layout = tcx.layout_of(ty::ParamEnv::empty().and(ty)).unwrap(); - let size_of_sized_part = layout.layout.size(); - - // Get the size of the memory behind the DST - let dst_size = unsized_inner_ty_size.checked_mul(num_elems as u64, &tcx).unwrap(); - - let size = size_of_sized_part.checked_add(dst_size, &tcx).unwrap(); - let align = Align::from_bytes(size.bytes().next_power_of_two()).unwrap(); - let ptr = ecx.allocate_ptr(size, align, MemoryKind::Stack).unwrap(); - debug!(?ptr); - - MPlaceTy::from_aligned_ptr_with_meta( - ptr.into(), - layout, - MemPlaceMeta::Meta(Scalar::from_target_usize(num_elems as u64, &tcx)), - ) - } else { - create_mplace_from_layout(ecx, ty) - } + let layout = ecx.layout_of(ty).unwrap(); + let meta = reconstruct_place_meta(layout, valtree, ecx.tcx.tcx); + ecx.allocate_dyn(layout, MemoryKind::Stack, meta).unwrap() } /// Converts a `ValTree` to a `ConstValue`, which is needed after mir @@ -263,7 +216,11 @@ pub fn valtree_to_const_value<'tcx>( // FIXME Does this need an example? let (param_env, ty) = param_env_ty.into_parts(); - let mut ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false); + let mut ecx: crate::interpret::InterpCx< + '_, + '_, + crate::const_eval::CompileTimeInterpreter<'_, '_>, + > = mk_eval_cx(tcx, DUMMY_SP, param_env, CanAccessStatics::No); match ty.kind() { ty::FnDef(..) => { @@ -277,17 +234,20 @@ pub fn valtree_to_const_value<'tcx>( ), }, ty::Ref(_, _, _) | ty::Tuple(_) | ty::Array(_, _) | ty::Adt(..) => { - let mut place = match ty.kind() { + let place = match ty.kind() { ty::Ref(_, inner_ty, _) => { - // Need to create a place for the pointee to fill for Refs + // Need to create a place for the pointee (the reference itself will be an immediate) create_pointee_place(&mut ecx, *inner_ty, valtree) } - _ => create_mplace_from_layout(&mut ecx, ty), + _ => { + // Need to create a place for this valtree. + create_pointee_place(&mut ecx, ty, valtree) + } }; debug!(?place); - valtree_into_mplace(&mut ecx, &mut place, valtree); - dump_place(&ecx, place.into()); + valtree_into_mplace(&mut ecx, &place, valtree); + dump_place(&ecx, &place); intern_const_alloc_recursive(&mut ecx, InternKind::Constant, &place).unwrap(); match ty.kind() { @@ -326,7 +286,7 @@ pub fn valtree_to_const_value<'tcx>( #[instrument(skip(ecx), level = "debug")] fn valtree_into_mplace<'tcx>( ecx: &mut CompileTimeEvalContext<'tcx, 'tcx>, - place: &mut MPlaceTy<'tcx>, + place: &MPlaceTy<'tcx>, valtree: ty::ValTree<'tcx>, ) { // This will match on valtree and write the value(s) corresponding to the ValTree @@ -342,14 +302,14 @@ fn valtree_into_mplace<'tcx>( ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => { let scalar_int = valtree.unwrap_leaf(); debug!("writing trivial valtree {:?} to place {:?}", scalar_int, place); - ecx.write_immediate(Immediate::Scalar(scalar_int.into()), &place.into()).unwrap(); + ecx.write_immediate(Immediate::Scalar(scalar_int.into()), place).unwrap(); } ty::Ref(_, inner_ty, _) => { - let mut pointee_place = create_pointee_place(ecx, *inner_ty, valtree); + let pointee_place = create_pointee_place(ecx, *inner_ty, valtree); debug!(?pointee_place); - valtree_into_mplace(ecx, &mut pointee_place, valtree); - dump_place(ecx, pointee_place.into()); + valtree_into_mplace(ecx, &pointee_place, valtree); + dump_place(ecx, &pointee_place); intern_const_alloc_recursive(ecx, InternKind::Constant, &pointee_place).unwrap(); let imm = match inner_ty.kind() { @@ -366,7 +326,7 @@ fn valtree_into_mplace<'tcx>( }; debug!(?imm); - ecx.write_immediate(imm, &place.into()).unwrap(); + ecx.write_immediate(imm, place).unwrap(); } ty::Adt(_, _) | ty::Tuple(_) | ty::Array(_, _) | ty::Str | ty::Slice(_) => { let branches = valtree.unwrap_branch(); @@ -381,12 +341,12 @@ fn valtree_into_mplace<'tcx>( debug!(?variant); ( - place.project_downcast(ecx, variant_idx).unwrap(), + ecx.project_downcast(place, variant_idx).unwrap(), &branches[1..], Some(variant_idx), ) } - _ => (*place, branches, None), + _ => (place.clone(), branches, None), }; debug!(?place_adjusted, ?branches); @@ -395,70 +355,33 @@ fn valtree_into_mplace<'tcx>( for (i, inner_valtree) in branches.iter().enumerate() { debug!(?i, ?inner_valtree); - let mut place_inner = match ty.kind() { - ty::Str | ty::Slice(_) => ecx.mplace_index(&place, i as u64).unwrap(), - _ if !ty.is_sized(*ecx.tcx, ty::ParamEnv::empty()) - && i == branches.len() - 1 => - { - // Note: For custom DSTs we need to manually process the last unsized field. - // We created a `Pointer` for the `Allocation` of the complete sized version of - // the Adt in `create_pointee_place` and now we fill that `Allocation` with the - // values in the ValTree. For the unsized field we have to additionally add the meta - // data. - - let (unsized_inner_ty, num_elems) = - get_info_on_unsized_field(ty, valtree, tcx); - debug!(?unsized_inner_ty); - - let inner_ty = match ty.kind() { - ty::Adt(def, substs) => { - let i = FieldIdx::from_usize(i); - def.variant(FIRST_VARIANT).fields[i].ty(tcx, substs) - } - ty::Tuple(inner_tys) => inner_tys[i], - _ => bug!("unexpected unsized type {:?}", ty), - }; - - let inner_layout = - tcx.layout_of(ty::ParamEnv::empty().and(inner_ty)).unwrap(); - debug!(?inner_layout); - - let offset = place_adjusted.layout.fields.offset(i); - place - .offset_with_meta( - offset, - MemPlaceMeta::Meta(Scalar::from_target_usize( - num_elems as u64, - &tcx, - )), - inner_layout, - &tcx, - ) - .unwrap() + let place_inner = match ty.kind() { + ty::Str | ty::Slice(_) | ty::Array(..) => { + ecx.project_index(place, i as u64).unwrap() } - _ => ecx.mplace_field(&place_adjusted, i).unwrap(), + _ => ecx.project_field(&place_adjusted, i).unwrap(), }; debug!(?place_inner); - valtree_into_mplace(ecx, &mut place_inner, *inner_valtree); - dump_place(&ecx, place_inner.into()); + valtree_into_mplace(ecx, &place_inner, *inner_valtree); + dump_place(&ecx, &place_inner); } debug!("dump of place_adjusted:"); - dump_place(ecx, place_adjusted.into()); + dump_place(ecx, &place_adjusted); if let Some(variant_idx) = variant_idx { // don't forget filling the place with the discriminant of the enum - ecx.write_discriminant(variant_idx, &place.into()).unwrap(); + ecx.write_discriminant(variant_idx, place).unwrap(); } debug!("dump of place after writing discriminant:"); - dump_place(ecx, place.into()); + dump_place(ecx, place); } _ => bug!("shouldn't have created a ValTree for {:?}", ty), } } -fn dump_place<'tcx>(ecx: &CompileTimeEvalContext<'tcx, 'tcx>, place: PlaceTy<'tcx>) { - trace!("{:?}", ecx.dump_place(*place)); +fn dump_place<'tcx>(ecx: &CompileTimeEvalContext<'tcx, 'tcx>, place: &MPlaceTy<'tcx>) { + trace!("{:?}", ecx.dump_place(Place::Ptr(**place))); } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index f8b7cc6d7e16b..4362cae7ed746 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -1,6 +1,24 @@ +use rustc_errors::{ + DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler, + IntoDiagnostic, +}; use rustc_hir::ConstContext; -use rustc_macros::Diagnostic; +use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; +use rustc_middle::mir::interpret::{ + CheckInAllocMsg, ExpectedKind, InterpError, InvalidMetaKind, InvalidProgramInfo, PointerKind, + ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo, +}; +use rustc_middle::ty::{self, Ty}; use rustc_span::Span; +use rustc_target::abi::call::AdjustForForeignAbiError; +use rustc_target::abi::{Size, WrappingRange}; + +#[derive(Diagnostic)] +#[diag(const_eval_dangling_ptr_in_final)] +pub(crate) struct DanglingPtrInFinal { + #[primary_span] + pub span: Span, +} #[derive(Diagnostic)] #[diag(const_eval_unstable_in_stable)] @@ -92,7 +110,7 @@ pub(crate) struct TransientMutBorrowErrRaw { #[diag(const_eval_max_num_nodes_in_const)] pub(crate) struct MaxNumNodesInConstErr { #[primary_span] - pub span: Span, + pub span: Option, pub global_const_id: String, } @@ -175,6 +193,14 @@ pub(crate) struct UnallowedInlineAsm { pub kind: ConstContext, } +#[derive(Diagnostic)] +#[diag(const_eval_unsupported_untyped_pointer)] +#[note] +pub(crate) struct UnsupportedUntypedPointer { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(const_eval_interior_mutable_data_refer, code = "E0492")] pub(crate) struct InteriorMutableDataRefer { @@ -194,3 +220,687 @@ pub(crate) struct InteriorMutabilityBorrow { #[primary_span] pub span: Span, } + +#[derive(LintDiagnostic)] +#[diag(const_eval_long_running)] +#[note] +pub struct LongRunning { + #[help] + pub item_span: Span, +} + +#[derive(Diagnostic)] +#[diag(const_eval_long_running)] +pub struct LongRunningWarn { + #[primary_span] + #[label] + pub span: Span, + #[help] + pub item_span: Span, +} + +#[derive(Diagnostic)] +#[diag(const_eval_erroneous_constant)] +pub(crate) struct ErroneousConstUsed { + #[primary_span] + pub span: Span, +} + +#[derive(Subdiagnostic)] +#[note(const_eval_non_const_impl)] +pub(crate) struct NonConstImplNote { + #[primary_span] + pub span: Span, +} + +#[derive(Subdiagnostic, PartialEq, Eq, Clone)] +#[note(const_eval_frame_note)] +pub struct FrameNote { + #[primary_span] + pub span: Span, + pub times: i32, + pub where_: &'static str, + pub instance: String, +} + +#[derive(Subdiagnostic)] +#[note(const_eval_raw_bytes)] +pub struct RawBytesNote { + pub size: u64, + pub align: u64, + pub bytes: String, +} + +// FIXME(fee1-dead) do not use stringly typed `ConstContext` + +#[derive(Diagnostic)] +#[diag(const_eval_match_eq_non_const, code = "E0015")] +#[note] +pub struct NonConstMatchEq<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] +#[diag(const_eval_for_loop_into_iter_non_const, code = "E0015")] +pub struct NonConstForLoopIntoIter<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] +#[diag(const_eval_question_branch_non_const, code = "E0015")] +pub struct NonConstQuestionBranch<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] +#[diag(const_eval_question_from_residual_non_const, code = "E0015")] +pub struct NonConstQuestionFromResidual<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] +#[diag(const_eval_try_block_from_output_non_const, code = "E0015")] +pub struct NonConstTryBlockFromOutput<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] +#[diag(const_eval_await_non_const, code = "E0015")] +pub struct NonConstAwait<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] +#[diag(const_eval_closure_non_const, code = "E0015")] +pub struct NonConstClosure { + #[primary_span] + pub span: Span, + pub kind: ConstContext, + #[subdiagnostic] + pub note: Option, +} + +#[derive(Subdiagnostic)] +pub enum NonConstClosureNote { + #[note(const_eval_closure_fndef_not_const)] + FnDef { + #[primary_span] + span: Span, + }, + #[note(const_eval_fn_ptr_call)] + FnPtr, + #[note(const_eval_closure_call)] + Closure, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(const_eval_consider_dereferencing, applicability = "machine-applicable")] +pub struct ConsiderDereferencing { + pub deref: String, + #[suggestion_part(code = "{deref}")] + pub span: Span, + #[suggestion_part(code = "{deref}")] + pub rhs_span: Span, +} + +#[derive(Diagnostic)] +#[diag(const_eval_operator_non_const, code = "E0015")] +pub struct NonConstOperator { + #[primary_span] + pub span: Span, + pub kind: ConstContext, + #[subdiagnostic] + pub sugg: Option, +} + +#[derive(Diagnostic)] +#[diag(const_eval_deref_coercion_non_const, code = "E0015")] +#[note] +pub struct NonConstDerefCoercion<'tcx> { + #[primary_span] + pub span: Span, + pub ty: Ty<'tcx>, + pub kind: ConstContext, + pub target_ty: Ty<'tcx>, + #[note(const_eval_target_note)] + pub deref_target: Option, +} + +#[derive(Diagnostic)] +#[diag(const_eval_live_drop, code = "E0493")] +pub struct LiveDrop<'tcx> { + #[primary_span] + #[label] + pub span: Span, + pub kind: ConstContext, + pub dropped_ty: Ty<'tcx>, + #[label(const_eval_dropped_at_label)] + pub dropped_at: Option, +} + +#[derive(LintDiagnostic)] +#[diag(const_eval_align_check_failed)] +pub struct AlignmentCheckFailed { + pub has: u64, + pub required: u64, + #[subdiagnostic] + pub frames: Vec, +} + +#[derive(Diagnostic)] +#[diag(const_eval_error, code = "E0080")] +pub struct ConstEvalError { + #[primary_span] + pub span: Span, + /// One of "const", "const_with_path", and "static" + pub error_kind: &'static str, + pub instance: String, + #[subdiagnostic] + pub frame_notes: Vec, +} + +#[derive(Diagnostic)] +#[diag(const_eval_nullary_intrinsic_fail)] +pub struct NullaryIntrinsicError { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(const_eval_undefined_behavior, code = "E0080")] +pub struct UndefinedBehavior { + #[primary_span] + pub span: Span, + #[note(const_eval_undefined_behavior_note)] + pub ub_note: Option<()>, + #[subdiagnostic] + pub frames: Vec, + #[subdiagnostic] + pub raw_bytes: RawBytesNote, +} + +pub trait ReportErrorExt { + /// Returns the diagnostic message for this error. + fn diagnostic_message(&self) -> DiagnosticMessage; + fn add_args( + self, + handler: &Handler, + builder: &mut DiagnosticBuilder<'_, G>, + ); + + fn debug(self) -> String + where + Self: Sized, + { + ty::tls::with(move |tcx| { + let mut builder = tcx.sess.struct_allow(DiagnosticMessage::Str(String::new().into())); + let handler = &tcx.sess.parse_sess.span_diagnostic; + let message = self.diagnostic_message(); + self.add_args(handler, &mut builder); + let s = handler.eagerly_translate_to_string(message, builder.args()); + builder.cancel(); + s + }) + } +} + +fn bad_pointer_message(msg: CheckInAllocMsg, handler: &Handler) -> String { + use crate::fluent_generated::*; + + let msg = match msg { + CheckInAllocMsg::DerefTest => const_eval_deref_test, + CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test, + CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test, + CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test, + CheckInAllocMsg::InboundsTest => const_eval_in_bounds_test, + }; + + handler.eagerly_translate_to_string(msg, [].into_iter()) +} + +impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { + fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + use UndefinedBehaviorInfo::*; + match self { + Ub(msg) => msg.clone().into(), + Unreachable => const_eval_unreachable, + BoundsCheckFailed { .. } => const_eval_bounds_check_failed, + DivisionByZero => const_eval_division_by_zero, + RemainderByZero => const_eval_remainder_by_zero, + DivisionOverflow => const_eval_division_overflow, + RemainderOverflow => const_eval_remainder_overflow, + PointerArithOverflow => const_eval_pointer_arithmetic_overflow, + InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice, + InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta, + UnterminatedCString(_) => const_eval_unterminated_c_string, + PointerUseAfterFree(_, _) => const_eval_pointer_use_after_free, + PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds, + PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds, + DanglingIntPointer(0, _) => const_eval_dangling_null_pointer, + DanglingIntPointer(_, _) => const_eval_dangling_int_pointer, + AlignmentCheckFailed { .. } => const_eval_alignment_check_failed, + WriteToReadOnly(_) => const_eval_write_to_read_only, + DerefFunctionPointer(_) => const_eval_deref_function_pointer, + DerefVTablePointer(_) => const_eval_deref_vtable_pointer, + InvalidBool(_) => const_eval_invalid_bool, + InvalidChar(_) => const_eval_invalid_char, + InvalidTag(_) => const_eval_invalid_tag, + InvalidFunctionPointer(_) => const_eval_invalid_function_pointer, + InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer, + InvalidStr(_) => const_eval_invalid_str, + InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown, + InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes, + DeadLocal => const_eval_dead_local, + ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch, + UninhabitedEnumVariantWritten(_) => const_eval_uninhabited_enum_variant_written, + UninhabitedEnumVariantRead(_) => const_eval_uninhabited_enum_variant_read, + ValidationError(e) => e.diagnostic_message(), + Custom(x) => (x.msg)(), + } + } + + fn add_args( + self, + handler: &Handler, + builder: &mut DiagnosticBuilder<'_, G>, + ) { + use UndefinedBehaviorInfo::*; + match self { + Ub(_) + | Unreachable + | DivisionByZero + | RemainderByZero + | DivisionOverflow + | RemainderOverflow + | PointerArithOverflow + | InvalidMeta(InvalidMetaKind::SliceTooBig) + | InvalidMeta(InvalidMetaKind::TooBig) + | InvalidUninitBytes(None) + | DeadLocal + | UninhabitedEnumVariantWritten(_) + | UninhabitedEnumVariantRead(_) => {} + BoundsCheckFailed { len, index } => { + builder.set_arg("len", len); + builder.set_arg("index", index); + } + UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => { + builder.set_arg("pointer", ptr); + } + PointerUseAfterFree(alloc_id, msg) => { + builder + .set_arg("alloc_id", alloc_id) + .set_arg("bad_pointer_message", bad_pointer_message(msg, handler)); + } + PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => { + builder + .set_arg("alloc_id", alloc_id) + .set_arg("alloc_size", alloc_size.bytes()) + .set_arg("ptr_offset", ptr_offset) + .set_arg("ptr_size", ptr_size.bytes()) + .set_arg("bad_pointer_message", bad_pointer_message(msg, handler)); + } + DanglingIntPointer(ptr, msg) => { + if ptr != 0 { + builder.set_arg("pointer", format!("{ptr:#x}[noalloc]")); + } + + builder.set_arg("bad_pointer_message", bad_pointer_message(msg, handler)); + } + AlignmentCheckFailed { required, has } => { + builder.set_arg("required", required.bytes()); + builder.set_arg("has", has.bytes()); + } + WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) => { + builder.set_arg("allocation", alloc); + } + InvalidBool(b) => { + builder.set_arg("value", format!("{b:02x}")); + } + InvalidChar(c) => { + builder.set_arg("value", format!("{c:08x}")); + } + InvalidTag(tag) => { + builder.set_arg("tag", format!("{tag:x}")); + } + InvalidStr(err) => { + builder.set_arg("err", format!("{err}")); + } + InvalidUninitBytes(Some((alloc, info))) => { + builder.set_arg("alloc", alloc); + builder.set_arg("access", info.access); + builder.set_arg("uninit", info.bad); + } + ScalarSizeMismatch(info) => { + builder.set_arg("target_size", info.target_size); + builder.set_arg("data_size", info.data_size); + } + ValidationError(e) => e.add_args(handler, builder), + Custom(custom) => { + (custom.add_args)(&mut |name, value| { + builder.set_arg(name, value); + }); + } + } + } +} + +impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> { + fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + use rustc_middle::mir::interpret::ValidationErrorKind::*; + match self.kind { + PtrToUninhabited { ptr_kind: PointerKind::Box, .. } => { + const_eval_validation_box_to_uninhabited + } + PtrToUninhabited { ptr_kind: PointerKind::Ref, .. } => { + const_eval_validation_ref_to_uninhabited + } + + PtrToStatic { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_static, + PtrToStatic { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_static, + + PtrToMut { ptr_kind: PointerKind::Box } => const_eval_validation_box_to_mut, + PtrToMut { ptr_kind: PointerKind::Ref } => const_eval_validation_ref_to_mut, + + PointerAsInt { .. } => const_eval_validation_pointer_as_int, + PartialPointer => const_eval_validation_partial_pointer, + MutableRefInConst => const_eval_validation_mutable_ref_in_const, + NullFnPtr => const_eval_validation_null_fn_ptr, + NeverVal => const_eval_validation_never_val, + NullablePtrOutOfRange { .. } => const_eval_validation_nullable_ptr_out_of_range, + PtrOutOfRange { .. } => const_eval_validation_ptr_out_of_range, + OutOfRange { .. } => const_eval_validation_out_of_range, + UnsafeCell => const_eval_validation_unsafe_cell, + UninhabitedVal { .. } => const_eval_validation_uninhabited_val, + InvalidEnumTag { .. } => const_eval_validation_invalid_enum_tag, + UninhabitedEnumVariant => const_eval_validation_uninhabited_enum_variant, + Uninit { .. } => const_eval_validation_uninit, + InvalidVTablePtr { .. } => const_eval_validation_invalid_vtable_ptr, + InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Box } => { + const_eval_validation_invalid_box_slice_meta + } + InvalidMetaSliceTooLarge { ptr_kind: PointerKind::Ref } => { + const_eval_validation_invalid_ref_slice_meta + } + + InvalidMetaTooLarge { ptr_kind: PointerKind::Box } => { + const_eval_validation_invalid_box_meta + } + InvalidMetaTooLarge { ptr_kind: PointerKind::Ref } => { + const_eval_validation_invalid_ref_meta + } + UnalignedPtr { ptr_kind: PointerKind::Ref, .. } => const_eval_validation_unaligned_ref, + UnalignedPtr { ptr_kind: PointerKind::Box, .. } => const_eval_validation_unaligned_box, + + NullPtr { ptr_kind: PointerKind::Box } => const_eval_validation_null_box, + NullPtr { ptr_kind: PointerKind::Ref } => const_eval_validation_null_ref, + DanglingPtrNoProvenance { ptr_kind: PointerKind::Box, .. } => { + const_eval_validation_dangling_box_no_provenance + } + DanglingPtrNoProvenance { ptr_kind: PointerKind::Ref, .. } => { + const_eval_validation_dangling_ref_no_provenance + } + DanglingPtrOutOfBounds { ptr_kind: PointerKind::Box } => { + const_eval_validation_dangling_box_out_of_bounds + } + DanglingPtrOutOfBounds { ptr_kind: PointerKind::Ref } => { + const_eval_validation_dangling_ref_out_of_bounds + } + DanglingPtrUseAfterFree { ptr_kind: PointerKind::Box } => { + const_eval_validation_dangling_box_use_after_free + } + DanglingPtrUseAfterFree { ptr_kind: PointerKind::Ref } => { + const_eval_validation_dangling_ref_use_after_free + } + InvalidBool { .. } => const_eval_validation_invalid_bool, + InvalidChar { .. } => const_eval_validation_invalid_char, + InvalidFnPtr { .. } => const_eval_validation_invalid_fn_ptr, + } + } + + fn add_args(self, handler: &Handler, err: &mut DiagnosticBuilder<'_, G>) { + use crate::fluent_generated as fluent; + use rustc_middle::mir::interpret::ValidationErrorKind::*; + + if let PointerAsInt { .. } | PartialPointer = self.kind { + err.help(fluent::const_eval_ptr_as_bytes_1); + err.help(fluent::const_eval_ptr_as_bytes_2); + } + + let message = if let Some(path) = self.path { + handler.eagerly_translate_to_string( + fluent::const_eval_validation_front_matter_invalid_value_with_path, + [("path".into(), DiagnosticArgValue::Str(path.into()))].iter().map(|(a, b)| (a, b)), + ) + } else { + handler.eagerly_translate_to_string( + fluent::const_eval_validation_front_matter_invalid_value, + [].into_iter(), + ) + }; + + err.set_arg("front_matter", message); + + fn add_range_arg( + r: WrappingRange, + max_hi: u128, + handler: &Handler, + err: &mut DiagnosticBuilder<'_, G>, + ) { + let WrappingRange { start: lo, end: hi } = r; + assert!(hi <= max_hi); + let msg = if lo > hi { + fluent::const_eval_range_wrapping + } else if lo == hi { + fluent::const_eval_range_singular + } else if lo == 0 { + assert!(hi < max_hi, "should not be printing if the range covers everything"); + fluent::const_eval_range_upper + } else if hi == max_hi { + assert!(lo > 0, "should not be printing if the range covers everything"); + fluent::const_eval_range_lower + } else { + fluent::const_eval_range + }; + + let args = [ + ("lo".into(), DiagnosticArgValue::Str(lo.to_string().into())), + ("hi".into(), DiagnosticArgValue::Str(hi.to_string().into())), + ]; + let args = args.iter().map(|(a, b)| (a, b)); + let message = handler.eagerly_translate_to_string(msg, args); + err.set_arg("in_range", message); + } + + match self.kind { + PtrToUninhabited { ty, .. } | UninhabitedVal { ty } => { + err.set_arg("ty", ty); + } + PointerAsInt { expected } | Uninit { expected } => { + let msg = match expected { + ExpectedKind::Reference => fluent::const_eval_validation_expected_ref, + ExpectedKind::Box => fluent::const_eval_validation_expected_box, + ExpectedKind::RawPtr => fluent::const_eval_validation_expected_raw_ptr, + ExpectedKind::InitScalar => fluent::const_eval_validation_expected_init_scalar, + ExpectedKind::Bool => fluent::const_eval_validation_expected_bool, + ExpectedKind::Char => fluent::const_eval_validation_expected_char, + ExpectedKind::Float => fluent::const_eval_validation_expected_float, + ExpectedKind::Int => fluent::const_eval_validation_expected_int, + ExpectedKind::FnPtr => fluent::const_eval_validation_expected_fn_ptr, + ExpectedKind::EnumTag => fluent::const_eval_validation_expected_enum_tag, + ExpectedKind::Str => fluent::const_eval_validation_expected_str, + }; + let msg = handler.eagerly_translate_to_string(msg, [].into_iter()); + err.set_arg("expected", msg); + } + InvalidEnumTag { value } + | InvalidVTablePtr { value } + | InvalidBool { value } + | InvalidChar { value } + | InvalidFnPtr { value } => { + err.set_arg("value", value); + } + NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => { + add_range_arg(range, max_value, handler, err) + } + OutOfRange { range, max_value, value } => { + err.set_arg("value", value); + add_range_arg(range, max_value, handler, err); + } + UnalignedPtr { required_bytes, found_bytes, .. } => { + err.set_arg("required_bytes", required_bytes); + err.set_arg("found_bytes", found_bytes); + } + DanglingPtrNoProvenance { pointer, .. } => { + err.set_arg("pointer", pointer); + } + NullPtr { .. } + | PtrToStatic { .. } + | PtrToMut { .. } + | MutableRefInConst + | NullFnPtr + | NeverVal + | UnsafeCell + | InvalidMetaSliceTooLarge { .. } + | InvalidMetaTooLarge { .. } + | DanglingPtrUseAfterFree { .. } + | DanglingPtrOutOfBounds { .. } + | UninhabitedEnumVariant + | PartialPointer => {} + } + } +} + +impl ReportErrorExt for UnsupportedOpInfo { + fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + match self { + UnsupportedOpInfo::Unsupported(s) => s.clone().into(), + UnsupportedOpInfo::OverwritePartialPointer(_) => const_eval_partial_pointer_overwrite, + UnsupportedOpInfo::ReadPartialPointer(_) => const_eval_partial_pointer_copy, + UnsupportedOpInfo::ReadPointerAsInt(_) => const_eval_read_pointer_as_int, + UnsupportedOpInfo::ThreadLocalStatic(_) => const_eval_thread_local_static, + UnsupportedOpInfo::ReadExternStatic(_) => const_eval_read_extern_static, + } + } + fn add_args(self, _: &Handler, builder: &mut DiagnosticBuilder<'_, G>) { + use crate::fluent_generated::*; + + use UnsupportedOpInfo::*; + if let ReadPointerAsInt(_) | OverwritePartialPointer(_) | ReadPartialPointer(_) = self { + builder.help(const_eval_ptr_as_bytes_1); + builder.help(const_eval_ptr_as_bytes_2); + } + match self { + // `ReadPointerAsInt(Some(info))` is never printed anyway, it only serves as an error to + // be further processed by validity checking which then turns it into something nice to + // print. So it's not worth the effort of having diagnostics that can print the `info`. + Unsupported(_) | ReadPointerAsInt(_) => {} + OverwritePartialPointer(ptr) | ReadPartialPointer(ptr) => { + builder.set_arg("ptr", ptr); + } + ThreadLocalStatic(did) | ReadExternStatic(did) => { + builder.set_arg("did", format!("{did:?}")); + } + } + } +} + +impl<'tcx> ReportErrorExt for InterpError<'tcx> { + fn diagnostic_message(&self) -> DiagnosticMessage { + match self { + InterpError::UndefinedBehavior(ub) => ub.diagnostic_message(), + InterpError::Unsupported(e) => e.diagnostic_message(), + InterpError::InvalidProgram(e) => e.diagnostic_message(), + InterpError::ResourceExhaustion(e) => e.diagnostic_message(), + InterpError::MachineStop(e) => e.diagnostic_message(), + } + } + fn add_args( + self, + handler: &Handler, + builder: &mut DiagnosticBuilder<'_, G>, + ) { + match self { + InterpError::UndefinedBehavior(ub) => ub.add_args(handler, builder), + InterpError::Unsupported(e) => e.add_args(handler, builder), + InterpError::InvalidProgram(e) => e.add_args(handler, builder), + InterpError::ResourceExhaustion(e) => e.add_args(handler, builder), + InterpError::MachineStop(e) => e.add_args(&mut |name, value| { + builder.set_arg(name, value); + }), + } + } +} + +impl<'tcx> ReportErrorExt for InvalidProgramInfo<'tcx> { + fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + match self { + InvalidProgramInfo::TooGeneric => const_eval_too_generic, + InvalidProgramInfo::AlreadyReported(_) => const_eval_already_reported, + InvalidProgramInfo::Layout(e) => e.diagnostic_message(), + InvalidProgramInfo::FnAbiAdjustForForeignAbi(_) => { + rustc_middle::error::middle_adjust_for_foreign_abi_error + } + InvalidProgramInfo::ConstPropNonsense => { + panic!("We had const-prop nonsense, this should never be printed") + } + } + } + fn add_args( + self, + handler: &Handler, + builder: &mut DiagnosticBuilder<'_, G>, + ) { + match self { + InvalidProgramInfo::TooGeneric + | InvalidProgramInfo::AlreadyReported(_) + | InvalidProgramInfo::ConstPropNonsense => {} + InvalidProgramInfo::Layout(e) => { + let diag: DiagnosticBuilder<'_, ()> = e.into_diagnostic().into_diagnostic(handler); + for (name, val) in diag.args() { + builder.set_arg(name.clone(), val.clone()); + } + diag.cancel(); + } + InvalidProgramInfo::FnAbiAdjustForForeignAbi( + AdjustForForeignAbiError::Unsupported { arch, abi }, + ) => { + builder.set_arg("arch", arch); + builder.set_arg("abi", abi.name()); + } + } + } +} + +impl ReportErrorExt for ResourceExhaustionInfo { + fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + match self { + ResourceExhaustionInfo::StackFrameLimitReached => const_eval_stack_frame_limit_reached, + ResourceExhaustionInfo::MemoryExhausted => const_eval_memory_exhausted, + ResourceExhaustionInfo::AddressSpaceFull => const_eval_address_space_full, + } + } + fn add_args(self, _: &Handler, _: &mut DiagnosticBuilder<'_, G>) {} +} diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index 163e3f8699322..98e853dc4d9e0 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -4,7 +4,7 @@ use rustc_apfloat::ieee::{Double, Single}; use rustc_apfloat::{Float, FloatConvert}; use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar}; use rustc_middle::mir::CastKind; -use rustc_middle::ty::adjustment::PointerCast; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::{IntegerExt, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, FloatTy, Ty, TypeAndMut}; use rustc_target::abi::Integer; @@ -14,6 +14,8 @@ use super::{ util::ensure_monomorphic_enough, FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy, }; +use crate::fluent_generated as fluent; + impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { pub fn cast( &mut self, @@ -22,62 +24,63 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { cast_ty: Ty<'tcx>, dest: &PlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { - use rustc_middle::mir::CastKind::*; // FIXME: In which cases should we trigger UB when the source is uninit? match cast_kind { - Pointer(PointerCast::Unsize) => { + CastKind::PointerCoercion(PointerCoercion::Unsize) => { let cast_ty = self.layout_of(cast_ty)?; self.unsize_into(src, cast_ty, dest)?; } - PointerExposeAddress => { + CastKind::PointerExposeAddress => { let src = self.read_immediate(src)?; let res = self.pointer_expose_address_cast(&src, cast_ty)?; self.write_immediate(res, dest)?; } - PointerFromExposedAddress => { + CastKind::PointerFromExposedAddress => { let src = self.read_immediate(src)?; let res = self.pointer_from_exposed_address_cast(&src, cast_ty)?; self.write_immediate(res, dest)?; } - IntToInt | IntToFloat => { + CastKind::IntToInt | CastKind::IntToFloat => { let src = self.read_immediate(src)?; let res = self.int_to_int_or_float(&src, cast_ty)?; self.write_immediate(res, dest)?; } - FloatToFloat | FloatToInt => { + CastKind::FloatToFloat | CastKind::FloatToInt => { let src = self.read_immediate(src)?; let res = self.float_to_float_or_int(&src, cast_ty)?; self.write_immediate(res, dest)?; } - FnPtrToPtr | PtrToPtr => { - let src = self.read_immediate(&src)?; + CastKind::FnPtrToPtr | CastKind::PtrToPtr => { + let src = self.read_immediate(src)?; let res = self.ptr_to_ptr(&src, cast_ty)?; self.write_immediate(res, dest)?; } - Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer) => { + CastKind::PointerCoercion( + PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, + ) => { // These are NOPs, but can be wide pointers. let v = self.read_immediate(src)?; self.write_immediate(*v, dest)?; } - Pointer(PointerCast::ReifyFnPointer) => { + CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer) => { // All reifications must be monomorphic, bail out otherwise. ensure_monomorphic_enough(*self.tcx, src.layout.ty)?; // The src operand does not matter, just its type match *src.layout.ty.kind() { - ty::FnDef(def_id, substs) => { + ty::FnDef(def_id, args) => { let instance = ty::Instance::resolve_for_fn_ptr( *self.tcx, self.param_env, def_id, - substs, + args, ) .ok_or_else(|| err_inval!(TooGeneric))?; @@ -88,7 +91,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - Pointer(PointerCast::UnsafeFnPointer) => { + CastKind::PointerCoercion(PointerCoercion::UnsafeFnPointer) => { let src = self.read_immediate(src)?; match cast_ty.kind() { ty::FnPtr(_) => { @@ -99,17 +102,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - Pointer(PointerCast::ClosureFnPointer(_)) => { + CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)) => { // All reifications must be monomorphic, bail out otherwise. ensure_monomorphic_enough(*self.tcx, src.layout.ty)?; // The src operand does not matter, just its type match *src.layout.ty.kind() { - ty::Closure(def_id, substs) => { + ty::Closure(def_id, args) => { let instance = ty::Instance::resolve_closure( *self.tcx, def_id, - substs, + args, ty::ClosureKind::FnOnce, ) .ok_or_else(|| err_inval!(TooGeneric))?; @@ -120,7 +123,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - DynStar => { + CastKind::DynStar => { if let ty::Dynamic(data, _, ty::DynStar) = cast_ty.kind() { // Initial cast from sized to dyn trait let vtable = self.get_vtable_ptr(src.layout.ty, data.principal())?; @@ -134,16 +137,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - Transmute => { + CastKind::Transmute => { assert!(src.layout.is_sized()); assert!(dest.layout.is_sized()); if src.layout.size != dest.layout.size { - throw_ub_format!( - "transmuting from {}-byte type to {}-byte type: `{}` -> `{}`", - src.layout.size.bytes(), - dest.layout.size.bytes(), - src.layout.ty, - dest.layout.ty, + let src_bytes = src.layout.size.bytes(); + let dest_bytes = dest.layout.size.bytes(); + let src_ty = format!("{}", src.layout.ty); + let dest_ty = format!("{}", dest.layout.ty); + throw_ub_custom!( + fluent::const_eval_invalid_transmute, + src_bytes = src_bytes, + dest_bytes = dest_bytes, + src = src_ty, + dest = dest_ty, ); } @@ -363,7 +370,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let old_vptr = old_vptr.to_pointer(self)?; let (ty, old_trait) = self.get_ptr_vtable(old_vptr)?; if old_trait != data_a.principal() { - throw_ub_format!("upcast on a pointer whose vtable does not match its type"); + throw_ub_custom!(fluent::const_eval_upcast_mismatch); } let new_vptr = self.get_vtable_ptr(ty, data_b.principal())?; self.write_immediate(Immediate::new_dyn_trait(old_data, new_vptr, self), dest) @@ -413,8 +420,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { if cast_ty_field.is_zst() { continue; } - let src_field = self.operand_field(src, i)?; - let dst_field = self.place_field(dest, i)?; + let src_field = self.project_field(src, i)?; + let dst_field = self.project_field(dest, i)?; if src_field.layout.ty == cast_ty_field.ty { self.copy_op(&src_field, &dst_field, /*allow_transmute*/ false)?; } else { diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index 015a9beab832d..6c35fb01a93b1 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -1,11 +1,11 @@ //! Functions for reading and writing discriminants of multi-variant layouts (enums and generators). -use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt}; +use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt, TyAndLayout}; use rustc_middle::{mir, ty}; use rustc_target::abi::{self, TagEncoding}; use rustc_target::abi::{VariantIdx, Variants}; -use super::{ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Scalar}; +use super::{ImmTy, InterpCx, InterpResult, Machine, Readable, Scalar, Writeable}; impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Writes the discriminant of the given variant. @@ -13,7 +13,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { pub fn write_discriminant( &mut self, variant_index: VariantIdx, - dest: &PlaceTy<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { // Layout computation excludes uninhabited variants from consideration // therefore there's no way to represent those variants in the given layout. @@ -21,11 +21,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // discriminant, so we cannot do anything here. // When evaluating we will always error before even getting here, but ConstProp 'executes' // dead code, so we cannot ICE here. - if dest.layout.for_variant(self, variant_index).abi.is_uninhabited() { - throw_ub!(UninhabitedEnumVariantWritten) + if dest.layout().for_variant(self, variant_index).abi.is_uninhabited() { + throw_ub!(UninhabitedEnumVariantWritten(variant_index)) } - match dest.layout.variants { + match dest.layout().variants { abi::Variants::Single { index } => { assert_eq!(index, variant_index); } @@ -38,8 +38,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // No need to validate that the discriminant here because the // `TyAndLayout::for_variant()` call earlier already checks the variant is valid. - let discr_val = - dest.layout.ty.discriminant_for_variant(*self.tcx, variant_index).unwrap().val; + let discr_val = dest + .layout() + .ty + .discriminant_for_variant(*self.tcx, variant_index) + .unwrap() + .val; // raw discriminants for enums are isize or bigger during // their computation, but the in-memory tag is the smallest possible @@ -47,7 +51,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let size = tag_layout.size(self); let tag_val = size.truncate(discr_val); - let tag_dest = self.place_field(dest, tag_field)?; + let tag_dest = self.project_field(dest, tag_field)?; self.write_scalar(Scalar::from_uint(tag_val, size), &tag_dest)?; } abi::Variants::Multiple { @@ -78,7 +82,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { &niche_start_val, )?; // Write result. - let niche_dest = self.place_field(dest, tag_field)?; + let niche_dest = self.project_field(dest, tag_field)?; self.write_immediate(*tag_val, &niche_dest)?; } } @@ -92,11 +96,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { #[instrument(skip(self), level = "trace")] pub fn read_discriminant( &self, - op: &OpTy<'tcx, M::Provenance>, - ) -> InterpResult<'tcx, (Scalar, VariantIdx)> { - trace!("read_discriminant_value {:#?}", op.layout); + op: &impl Readable<'tcx, M::Provenance>, + ) -> InterpResult<'tcx, VariantIdx> { + let ty = op.layout().ty; + trace!("read_discriminant_value {:#?}", op.layout()); // Get type and layout of the discriminant. - let discr_layout = self.layout_of(op.layout.ty.discriminant_ty(*self.tcx))?; + let discr_layout = self.layout_of(ty.discriminant_ty(*self.tcx))?; trace!("discriminant type: {:?}", discr_layout.ty); // We use "discriminant" to refer to the value associated with a particular enum variant. @@ -104,21 +109,23 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // declared list of variants -- they can differ with explicitly assigned discriminants. // We use "tag" to refer to how the discriminant is encoded in memory, which can be either // straight-forward (`TagEncoding::Direct`) or with a niche (`TagEncoding::Niche`). - let (tag_scalar_layout, tag_encoding, tag_field) = match op.layout.variants { + let (tag_scalar_layout, tag_encoding, tag_field) = match op.layout().variants { Variants::Single { index } => { - let discr = match op.layout.ty.discriminant_for_variant(*self.tcx, index) { - Some(discr) => { - // This type actually has discriminants. - assert_eq!(discr.ty, discr_layout.ty); - Scalar::from_uint(discr.val, discr_layout.size) + // Do some extra checks on enums. + if ty.is_enum() { + // Hilariously, `Single` is used even for 0-variant enums. + // (See https://github.com/rust-lang/rust/issues/89765). + if matches!(ty.kind(), ty::Adt(def, ..) if def.variants().is_empty()) { + throw_ub!(UninhabitedEnumVariantRead(index)) } - None => { - // On a type without actual discriminants, variant is 0. - assert_eq!(index.as_u32(), 0); - Scalar::from_uint(index.as_u32(), discr_layout.size) + // For consisteny with `write_discriminant`, and to make sure that + // `project_downcast` cannot fail due to strange layouts, we declare immediate UB + // for uninhabited variants. + if op.layout().for_variant(self, index).abi.is_uninhabited() { + throw_ub!(UninhabitedEnumVariantRead(index)) } - }; - return Ok((discr, index)); + } + return Ok(index); } Variants::Multiple { tag, ref tag_encoding, tag_field, .. } => { (tag, tag_encoding, tag_field) @@ -138,13 +145,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let tag_layout = self.layout_of(tag_scalar_layout.primitive().to_int_ty(*self.tcx))?; // Read tag and sanity-check `tag_layout`. - let tag_val = self.read_immediate(&self.operand_field(op, tag_field)?)?; + let tag_val = self.read_immediate(&self.project_field(op, tag_field)?)?; assert_eq!(tag_layout.size, tag_val.layout.size); assert_eq!(tag_layout.abi.is_signed(), tag_val.layout.abi.is_signed()); trace!("tag value: {}", tag_val); // Figure out which discriminant and variant this corresponds to. - Ok(match *tag_encoding { + let index = match *tag_encoding { TagEncoding::Direct => { let scalar = tag_val.to_scalar(); // Generate a specific error if `tag_val` is not an integer. @@ -160,21 +167,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.cast_from_int_like(scalar, tag_val.layout, discr_layout.ty).unwrap(); let discr_bits = discr_val.assert_bits(discr_layout.size); // Convert discriminant to variant index, and catch invalid discriminants. - let index = match *op.layout.ty.kind() { + let index = match *ty.kind() { ty::Adt(adt, _) => { adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits) } - ty::Generator(def_id, substs, _) => { - let substs = substs.as_generator(); - substs - .discriminants(def_id, *self.tcx) - .find(|(_, var)| var.val == discr_bits) + ty::Generator(def_id, args, _) => { + let args = args.as_generator(); + args.discriminants(def_id, *self.tcx).find(|(_, var)| var.val == discr_bits) } _ => span_bug!(self.cur_span(), "tagged layout for non-adt non-generator"), } .ok_or_else(|| err_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))))?; // Return the cast value, and the index. - (discr_val, index.0) + index.0 } TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => { let tag_val = tag_val.to_scalar(); @@ -216,12 +221,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { .checked_add(variant_index_relative) .expect("overflow computing absolute variant idx"), ); - let variants = op - .layout - .ty - .ty_adt_def() - .expect("tagged layout for non adt") - .variants(); + let variants = + ty.ty_adt_def().expect("tagged layout for non adt").variants(); assert!(variant_index < variants.next_index()); variant_index } else { @@ -232,7 +233,32 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Compute the size of the scalar we need to return. // No need to cast, because the variant index directly serves as discriminant and is // encoded in the tag. - (Scalar::from_uint(variant.as_u32(), discr_layout.size), variant) + variant + } + }; + // For consisteny with `write_discriminant`, and to make sure that `project_downcast` cannot fail due to strange layouts, we declare immediate UB for uninhabited variants. + if op.layout().for_variant(self, index).abi.is_uninhabited() { + throw_ub!(UninhabitedEnumVariantRead(index)) + } + Ok(index) + } + + pub fn discriminant_for_variant( + &self, + layout: TyAndLayout<'tcx>, + variant: VariantIdx, + ) -> InterpResult<'tcx, Scalar> { + let discr_layout = self.layout_of(layout.ty.discriminant_ty(*self.tcx))?; + Ok(match layout.ty.discriminant_for_variant(*self.tcx, variant) { + Some(discr) => { + // This type actually has discriminants. + assert_eq!(discr.ty, discr_layout.ty); + Scalar::from_uint(discr.val, discr_layout.size) + } + None => { + // On a type without actual discriminants, variant is 0. + assert_eq!(variant.as_u32(), 0); + Scalar::from_uint(variant.as_u32(), discr_layout.size) } }) } diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 040eba10eb4f7..3ac6f07e8b7ba 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -1,19 +1,19 @@ use std::cell::Cell; -use std::fmt; -use std::mem; +use std::{fmt, mem}; use either::{Either, Left, Right}; +use hir::CRATE_HIR_ID; use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData}; use rustc_index::IndexVec; use rustc_middle::mir; -use rustc_middle::mir::interpret::{ErrorHandled, InterpError, ReportedErrorInfo}; +use rustc_middle::mir::interpret::{ErrorHandled, InterpError, InvalidMetaKind, ReportedErrorInfo}; use rustc_middle::query::TyCtxtAt; use rustc_middle::ty::layout::{ self, FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout, }; -use rustc_middle::ty::{self, subst::SubstsRef, ParamEnv, Ty, TyCtxt, TypeFoldable}; +use rustc_middle::ty::{self, GenericArgsRef, ParamEnv, Ty, TyCtxt, TypeFoldable}; use rustc_mir_dataflow::storage::always_storage_live_locals; use rustc_session::Limit; use rustc_span::Span; @@ -24,6 +24,8 @@ use super::{ MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, PointerArithmetic, Provenance, Scalar, StackPopJump, }; +use crate::errors::{self, ErroneousConstUsed}; +use crate::fluent_generated as fluent; use crate::util; pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> { @@ -89,7 +91,7 @@ pub struct Frame<'mir, 'tcx, Prov: Provenance = AllocId, Extra = ()> { /// The MIR for the function called on this frame. pub body: &'mir mir::Body<'tcx>, - /// The def_id and substs of the current function. + /// The def_id and args of the current function. pub instance: ty::Instance<'tcx>, /// Extra data for the machine. @@ -246,6 +248,7 @@ impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> { } } +// FIXME: only used by miri, should be removed once translatable. impl<'tcx> fmt::Display for FrameInfo<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ty::tls::with(|tcx| { @@ -263,6 +266,21 @@ impl<'tcx> fmt::Display for FrameInfo<'tcx> { } } +impl<'tcx> FrameInfo<'tcx> { + pub fn as_note(&self, tcx: TyCtxt<'tcx>) -> errors::FrameNote { + let span = self.span; + if tcx.def_key(self.instance.def_id()).disambiguated_data.data == DefPathData::ClosureExpr { + errors::FrameNote { where_: "closure", span, instance: String::new(), times: 0 } + } else { + let instance = format!("{}", self.instance); + // Note: this triggers a `good_path_bug` state, which means that if we ever get here + // we must emit a diagnostic. We should never display a `FrameInfo` unless we + // actually want to emit a warning or error to the user. + errors::FrameNote { where_: "instance", span, instance, times: 0 } + } + } +} + impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> HasDataLayout for InterpCx<'mir, 'tcx, M> { #[inline] fn data_layout(&self) -> &TargetDataLayout { @@ -405,6 +423,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.stack().last().map_or(self.tcx.span, |f| f.current_span()) } + #[inline(always)] + /// Find the first stack frame that is within the current crate, if any, otherwise return the crate's HirId + pub fn best_lint_scope(&self) -> hir::HirId { + self.stack() + .iter() + .find_map(|frame| frame.body.source.def_id().as_local()) + .map_or(CRATE_HIR_ID, |def_id| self.tcx.hir().local_def_id_to_hir_id(def_id)) + } + #[inline(always)] pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>] { M::stack(self) @@ -497,21 +524,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { .try_subst_mir_and_normalize_erasing_regions( *self.tcx, self.param_env, - ty::EarlyBinder(value), + ty::EarlyBinder::bind(value), ) .map_err(|_| err_inval!(TooGeneric)) } - /// The `substs` are assumed to already be in our interpreter "universe" (param_env). + /// The `args` are assumed to already be in our interpreter "universe" (param_env). pub(super) fn resolve( &self, def: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> InterpResult<'tcx, ty::Instance<'tcx>> { - trace!("resolve: {:?}, {:#?}", def, substs); + trace!("resolve: {:?}, {:#?}", def, args); trace!("param_env: {:#?}", self.param_env); - trace!("substs: {:#?}", substs); - match ty::Instance::resolve(*self.tcx, self.param_env, def, substs) { + trace!("args: {:#?}", args); + match ty::Instance::resolve(*self.tcx, self.param_env, def, args) { Ok(Some(instance)) => Ok(instance), Ok(None) => throw_inval!(TooGeneric), @@ -577,7 +604,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // the last field). Can't have foreign types here, how would we // adjust alignment and size for them? let field = layout.field(self, layout.fields.count() - 1); - let Some((unsized_size, mut unsized_align)) = self.size_and_align_of(metadata, &field)? else { + let Some((unsized_size, mut unsized_align)) = + self.size_and_align_of(metadata, &field)? + else { // A field with an extern type. We don't know the actual dynamic size // or the alignment. return Ok(None); @@ -610,7 +639,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Check if this brought us over the size limit. if size > self.max_size_of_val() { - throw_ub!(InvalidMeta("total size is bigger than largest supported object")); + throw_ub!(InvalidMeta(InvalidMetaKind::TooBig)); } Ok(Some((size, align))) } @@ -628,7 +657,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let size = elem.size.bytes().saturating_mul(len); // we rely on `max_size_of_val` being smaller than `u64::MAX`. let size = Size::from_bytes(size); if size > self.max_size_of_val() { - throw_ub!(InvalidMeta("slice is bigger than largest supported object")); + throw_ub!(InvalidMeta(InvalidMetaKind::SliceTooBig)); } Ok(Some((size, elem.align.abi))) } @@ -655,11 +684,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { return_to_block: StackPopCleanup, ) -> InterpResult<'tcx> { trace!("body: {:#?}", body); - // Clobber previous return place contents, nobody is supposed to be able to see them any more - // This also checks dereferenceable, but not align. We rely on all constructed places being - // sufficiently aligned (in particular we rely on `deref_operand` checking alignment). - self.write_uninit(return_place)?; - // first push a stack frame so we have access to the local substs + // First push a stack frame so we have access to the local args let pre_frame = Frame { body, loc: Right(body.span), // Span used for errors caused during preamble. @@ -736,7 +761,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { mir::UnwindAction::Cleanup(block) => Left(mir::Location { block, statement_index: 0 }), mir::UnwindAction::Continue => Right(self.frame_mut().body.span), mir::UnwindAction::Unreachable => { - throw_ub_format!("unwinding past a stack frame that does not allow unwinding") + throw_ub_custom!(fluent::const_eval_unreachable_unwind); } mir::UnwindAction::Terminate => { self.frame_mut().loc = Right(self.frame_mut().body.span); @@ -775,9 +800,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } ); if unwinding && self.frame_idx() == 0 { - throw_ub_format!("unwinding past the topmost frame of the stack"); + throw_ub_custom!(fluent::const_eval_unwind_past_top); } + M::before_stack_pop(self, self.frame())?; + // Copy return value. Must of course happen *before* we deallocate the locals. let copy_ret_result = if !unwinding { let op = self @@ -863,7 +890,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // StorageLive expects the local to be dead, and marks it live. let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val); if !matches!(old, LocalValue::Dead) { - throw_ub_format!("StorageLive on a local that was already live"); + throw_ub_custom!(fluent::const_eval_double_storage_live); } Ok(()) } @@ -906,7 +933,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ErrorHandled::Reported(err) => { if !err.is_tainted_by_errors() && let Some(span) = span { // To make it easier to figure out where this error comes from, also add a note at the current location. - self.tcx.sess.span_note_without_error(span, "erroneous constant used"); + self.tcx.sess.emit_note(ErroneousConstUsed { span }); } err_inval!(AlreadyReported(err)) } @@ -931,7 +958,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } else { self.param_env }; - let param_env = param_env.with_const(); let val = self.ctfe_query(span, |tcx| tcx.eval_to_allocation_raw(param_env.and(gid)))?; self.raw_const_to_mplace(val) } @@ -949,7 +975,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // This deliberately does *not* honor `requires_caller_location` since it is used for much // more than just panics. for frame in stack.iter().rev() { - let span = frame.current_span(); + let span = match frame.loc { + Left(loc) => { + // If the stacktrace passes through MIR-inlined source scopes, add them. + let mir::SourceInfo { mut span, scope } = *frame.body.source_info(loc); + let mut scope_data = &frame.body.source_scopes[scope]; + while let Some((instance, call_span)) = scope_data.inlined { + frames.push(FrameInfo { span, instance }); + span = call_span; + scope_data = &frame.body.source_scopes[scope_data.parent_scope.unwrap()]; + } + span + } + Right(span) => span, + }; frames.push(FrameInfo { span, instance: frame.instance }); } trace!("generate stacktrace: {:#?}", frames); @@ -974,9 +1013,12 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self.place { - Place::Local { frame, local } => { + Place::Local { frame, local, offset } => { let mut allocs = Vec::new(); - write!(fmt, "{:?}", local)?; + write!(fmt, "{local:?}")?; + if let Some(offset) = offset { + write!(fmt, "+{:#x}", offset.bytes())?; + } if frame != self.ecx.frame_idx() { write!(fmt, " ({} frames up)", self.ecx.frame_idx() - frame)?; } @@ -992,7 +1034,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug fmt, " by {} ref {:?}:", match mplace.meta { - MemPlaceMeta::Meta(meta) => format!(" meta({:?})", meta), + MemPlaceMeta::Meta(meta) => format!(" meta({meta:?})"), MemPlaceMeta::None => String::new(), }, mplace.ptr, @@ -1000,13 +1042,13 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug allocs.extend(mplace.ptr.provenance.map(Provenance::get_alloc_id)); } LocalValue::Live(Operand::Immediate(Immediate::Scalar(val))) => { - write!(fmt, " {:?}", val)?; + write!(fmt, " {val:?}")?; if let Scalar::Ptr(ptr, _size) = val { allocs.push(ptr.provenance.get_alloc_id()); } } LocalValue::Live(Operand::Immediate(Immediate::ScalarPair(val1, val2))) => { - write!(fmt, " ({:?}, {:?})", val1, val2)?; + write!(fmt, " ({val1:?}, {val2:?})")?; if let Scalar::Ptr(ptr, _size) = val1 { allocs.push(ptr.provenance.get_alloc_id()); } @@ -1022,7 +1064,7 @@ impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug Some(alloc_id) => { write!(fmt, "by ref {:?}: {:?}", mplace.ptr, self.ecx.dump_alloc(alloc_id)) } - ptr => write!(fmt, " integral by ref: {:?}", ptr), + ptr => write!(fmt, " integral by ref: {ptr:?}"), }, } } diff --git a/compiler/rustc_const_eval/src/interpret/intern.rs b/compiler/rustc_const_eval/src/interpret/intern.rs index c2b82ba9b0792..910c3ca5d0a97 100644 --- a/compiler/rustc_const_eval/src/interpret/intern.rs +++ b/compiler/rustc_const_eval/src/interpret/intern.rs @@ -28,8 +28,9 @@ use super::{ ValueVisitor, }; use crate::const_eval; +use crate::errors::{DanglingPtrInFinal, UnsupportedUntypedPointer}; -pub trait CompileTimeMachine<'mir, 'tcx, T> = Machine< +pub trait CompileTimeMachine<'mir, 'tcx: 'mir, T> = Machine< 'mir, 'tcx, MemoryKind = T, @@ -163,82 +164,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory &self.ecx } - fn visit_aggregate( - &mut self, - mplace: &MPlaceTy<'tcx>, - fields: impl Iterator>, - ) -> InterpResult<'tcx> { - // We want to walk the aggregate to look for references to intern. While doing that we - // also need to take special care of interior mutability. - // - // As an optimization, however, if the allocation does not contain any references: we don't - // need to do the walk. It can be costly for big arrays for example (e.g. issue #93215). - let is_walk_needed = |mplace: &MPlaceTy<'tcx>| -> InterpResult<'tcx, bool> { - // ZSTs cannot contain pointers, we can avoid the interning walk. - if mplace.layout.is_zst() { - return Ok(false); - } - - // Now, check whether this allocation could contain references. - // - // Note, this check may sometimes not be cheap, so we only do it when the walk we'd like - // to avoid could be expensive: on the potentially larger types, arrays and slices, - // rather than on all aggregates unconditionally. - if matches!(mplace.layout.ty.kind(), ty::Array(..) | ty::Slice(..)) { - let Some((size, align)) = self.ecx.size_and_align_of_mplace(&mplace)? else { - // We do the walk if we can't determine the size of the mplace: we may be - // dealing with extern types here in the future. - return Ok(true); - }; - - // If there is no provenance in this allocation, it does not contain references - // that point to another allocation, and we can avoid the interning walk. - if let Some(alloc) = self.ecx.get_ptr_alloc(mplace.ptr, size, align)? { - if !alloc.has_provenance() { - return Ok(false); - } - } else { - // We're encountering a ZST here, and can avoid the walk as well. - return Ok(false); - } - } - - // In the general case, we do the walk. - Ok(true) - }; - - // If this allocation contains no references to intern, we avoid the potentially costly - // walk. - // - // We can do this before the checks for interior mutability below, because only references - // are relevant in that situation, and we're checking if there are any here. - if !is_walk_needed(mplace)? { - return Ok(()); - } - - if let Some(def) = mplace.layout.ty.ty_adt_def() { - if def.is_unsafe_cell() { - // We are crossing over an `UnsafeCell`, we can mutate again. This means that - // References we encounter inside here are interned as pointing to mutable - // allocations. - // Remember the `old` value to handle nested `UnsafeCell`. - let old = std::mem::replace(&mut self.inside_unsafe_cell, true); - let walked = self.walk_aggregate(mplace, fields); - self.inside_unsafe_cell = old; - return walked; - } - } - - self.walk_aggregate(mplace, fields) - } - fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> { // Handle Reference types, as these are the only types with provenance supported by const eval. // Raw pointers (and boxes) are handled by the `leftover_allocations` logic. let tcx = self.ecx.tcx; let ty = mplace.layout.ty; if let ty::Ref(_, referenced_ty, ref_mutability) = *ty.kind() { - let value = self.ecx.read_immediate(&mplace.into())?; + let value = self.ecx.read_immediate(mplace)?; let mplace = self.ecx.ref_to_mplace(&value)?; assert_eq!(mplace.layout.ty, referenced_ty); // Handle trait object vtables. @@ -314,16 +246,74 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory } Ok(()) } else { - // Not a reference -- proceed recursively. + // Not a reference. Check if we want to recurse. + let is_walk_needed = |mplace: &MPlaceTy<'tcx>| -> InterpResult<'tcx, bool> { + // ZSTs cannot contain pointers, we can avoid the interning walk. + if mplace.layout.is_zst() { + return Ok(false); + } + + // Now, check whether this allocation could contain references. + // + // Note, this check may sometimes not be cheap, so we only do it when the walk we'd like + // to avoid could be expensive: on the potentially larger types, arrays and slices, + // rather than on all aggregates unconditionally. + if matches!(mplace.layout.ty.kind(), ty::Array(..) | ty::Slice(..)) { + let Some((size, align)) = self.ecx.size_and_align_of_mplace(&mplace)? else { + // We do the walk if we can't determine the size of the mplace: we may be + // dealing with extern types here in the future. + return Ok(true); + }; + + // If there is no provenance in this allocation, it does not contain references + // that point to another allocation, and we can avoid the interning walk. + if let Some(alloc) = self.ecx.get_ptr_alloc(mplace.ptr, size, align)? { + if !alloc.has_provenance() { + return Ok(false); + } + } else { + // We're encountering a ZST here, and can avoid the walk as well. + return Ok(false); + } + } + + // In the general case, we do the walk. + Ok(true) + }; + + // If this allocation contains no references to intern, we avoid the potentially costly + // walk. + // + // We can do this before the checks for interior mutability below, because only references + // are relevant in that situation, and we're checking if there are any here. + if !is_walk_needed(mplace)? { + return Ok(()); + } + + if let Some(def) = mplace.layout.ty.ty_adt_def() { + if def.is_unsafe_cell() { + // We are crossing over an `UnsafeCell`, we can mutate again. This means that + // References we encounter inside here are interned as pointing to mutable + // allocations. + // Remember the `old` value to handle nested `UnsafeCell`. + let old = std::mem::replace(&mut self.inside_unsafe_cell, true); + let walked = self.walk_value(mplace); + self.inside_unsafe_cell = old; + return walked; + } + } + self.walk_value(mplace) } } } +/// How a constant value should be interned. #[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)] pub enum InternKind { /// The `mutability` of the static, ignoring the type which may have interior mutability. Static(hir::Mutability), + /// A `const` item Constant, Promoted, } @@ -368,7 +358,7 @@ pub fn intern_const_alloc_recursive< Some(ret.layout.ty), ); - ref_tracking.track((*ret, base_intern_mode), || ()); + ref_tracking.track((ret.clone(), base_intern_mode), || ()); while let Some(((mplace, mode), _)) = ref_tracking.todo.pop() { let res = InternVisitor { @@ -388,8 +378,7 @@ pub fn intern_const_alloc_recursive< ecx.tcx.sess.delay_span_bug( ecx.tcx.span, format!( - "error during interning should later cause validation failure: {}", - error + "error during interning should later cause validation failure: {error:?}" ), ); } @@ -425,14 +414,16 @@ pub fn intern_const_alloc_recursive< // immutability is so important. alloc.mutability = Mutability::Not; } + // If it's a constant, we should not have any "leftovers" as everything + // is tracked by const-checking. + // FIXME: downgrade this to a warning? It rejects some legitimate consts, + // such as `const CONST_RAW: *const Vec = &Vec::new() as *const _;`. + // + // NOTE: it looks likes this code path is only reachable when we try to intern + // something that cannot be promoted, which in constants means values that have + // drop glue, such as the example above. InternKind::Constant => { - // If it's a constant, we should not have any "leftovers" as everything - // is tracked by const-checking. - // FIXME: downgrade this to a warning? It rejects some legitimate consts, - // such as `const CONST_RAW: *const Vec = &Vec::new() as *const _;`. - ecx.tcx - .sess - .span_err(ecx.tcx.span, "untyped pointers are not allowed in constant"); + ecx.tcx.sess.emit_err(UnsupportedUntypedPointer { span: ecx.tcx.span }); // For better errors later, mark the allocation as immutable. alloc.mutability = Mutability::Not; } @@ -447,10 +438,7 @@ pub fn intern_const_alloc_recursive< } else if ecx.memory.dead_alloc_map.contains_key(&alloc_id) { // Codegen does not like dangling pointers, and generally `tcx` assumes that // all allocations referenced anywhere actually exist. So, make sure we error here. - let reported = ecx - .tcx - .sess - .span_err(ecx.tcx.span, "encountered dangling pointer in final constant"); + let reported = ecx.tcx.sess.emit_err(DanglingPtrInFinal { span: ecx.tcx.span }); return Err(reported); } else if ecx.tcx.try_get_global_alloc(alloc_id).is_none() { // We have hit an `AllocId` that is neither in local or global memory and isn't @@ -476,7 +464,7 @@ impl<'mir, 'tcx: 'mir, M: super::intern::CompileTimeMachine<'mir, 'tcx, !>> ) -> InterpResult<'tcx, ()>, ) -> InterpResult<'tcx, ConstAllocation<'tcx>> { let dest = self.allocate(layout, MemoryKind::Stack)?; - f(self, &dest.into())?; + f(self, &dest.clone().into())?; let mut alloc = self.memory.alloc_map.remove(&dest.ptr.provenance.unwrap()).unwrap().1; alloc.mutability = Mutability::Not; Ok(self.tcx.mk_const_alloc(alloc)) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index a77c699c22f7e..f22cd919c3695 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -1,4 +1,4 @@ -//! Intrinsics and other functions that the miri engine executes without +//! Intrinsics and other functions that the interpreter executes without //! looking at their MIR. Intrinsics/functions supported here are shared by CTFE //! and miri. @@ -12,7 +12,7 @@ use rustc_middle::mir::{ }; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf as _, ValidityRequirement}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{Ty, TyCtxt}; use rustc_span::symbol::{sym, Symbol}; use rustc_target::abi::{Abi, Align, Primitive, Size}; @@ -22,6 +22,8 @@ use super::{ Pointer, }; +use crate::fluent_generated as fluent; + mod caller_location; fn numeric_intrinsic(name: Symbol, bits: u128, kind: Primitive) -> Scalar { @@ -54,9 +56,9 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>( tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> InterpResult<'tcx, ConstValue<'tcx>> { - let tp_ty = substs.type_at(0); + let tp_ty = args.type_at(0); let name = tcx.item_name(def_id); Ok(match name { sym::type_name => { @@ -70,12 +72,12 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>( } sym::pref_align_of => { // Correctly handles non-monomorphic calls, so there is no need for ensure_monomorphic_enough. - let layout = tcx.layout_of(param_env.and(tp_ty)).map_err(|e| err_inval!(Layout(e)))?; + let layout = tcx.layout_of(param_env.and(tp_ty)).map_err(|e| err_inval!(Layout(*e)))?; ConstValue::from_target_usize(layout.align.pref.bytes(), &tcx) } sym::type_id => { ensure_monomorphic_enough(tcx, tp_ty)?; - ConstValue::from_u64(tcx.type_id_hash(tp_ty).as_u64()) + ConstValue::from_u128(tcx.type_id_hash(tp_ty).as_u128()) } sym::variant_count => match tp_ty.kind() { // Correctly handles non-monomorphic calls, so there is no need for ensure_monomorphic_enough. @@ -121,7 +123,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { dest: &PlaceTy<'tcx, M::Provenance>, ret: Option, ) -> InterpResult<'tcx, bool> { - let substs = instance.substs; + let instance_args = instance.args; let intrinsic_name = self.tcx.item_name(instance.def_id()); // First handle intrinsics without return place. @@ -142,7 +144,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } sym::min_align_of_val | sym::size_of_val => { - // Avoid `deref_operand` -- this is not a deref, the ptr does not have to be + // Avoid `deref_pointer` -- this is not a deref, the ptr does not have to be // dereferenceable! let place = self.ref_to_mplace(&self.read_immediate(&args[0])?)?; let (size, align) = self @@ -167,8 +169,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let ty = match intrinsic_name { sym::pref_align_of | sym::variant_count => self.tcx.types.usize, sym::needs_drop => self.tcx.types.bool, - sym::type_id => self.tcx.types.u64, - sym::type_name => self.tcx.mk_static_str(), + sym::type_id => self.tcx.types.u128, + sym::type_name => Ty::new_static_str(self.tcx.tcx), _ => bug!(), }; let val = self.ctfe_query(None, |tcx| { @@ -185,7 +187,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { | sym::ctlz_nonzero | sym::bswap | sym::bitreverse => { - let ty = substs.type_at(0); + let ty = instance_args.type_at(0); let layout_of = self.layout_of(ty)?; let val = self.read_scalar(&args[0])?; let bits = val.to_bits(layout_of.size)?; @@ -198,15 +200,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ty ), }; - let (nonzero, intrinsic_name) = match intrinsic_name { + let (nonzero, actual_intrinsic_name) = match intrinsic_name { sym::cttz_nonzero => (true, sym::cttz), sym::ctlz_nonzero => (true, sym::ctlz), other => (false, other), }; if nonzero && bits == 0 { - throw_ub_format!("`{}_nonzero` called on 0", intrinsic_name); + throw_ub_custom!( + fluent::const_eval_call_nonzero_intrinsic, + name = intrinsic_name, + ); } - let out_val = numeric_intrinsic(intrinsic_name, bits, kind); + let out_val = numeric_intrinsic(actual_intrinsic_name, bits, kind); self.write_scalar(out_val, dest)?; } sym::saturating_add | sym::saturating_sub => { @@ -220,50 +225,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.write_scalar(val, dest)?; } sym::discriminant_value => { - let place = self.deref_operand(&args[0])?; - let discr_val = self.read_discriminant(&place.into())?.0; - self.write_scalar(discr_val, dest)?; + let place = self.deref_pointer(&args[0])?; + let variant = self.read_discriminant(&place)?; + let discr = self.discriminant_for_variant(place.layout, variant)?; + self.write_scalar(discr, dest)?; } sym::exact_div => { let l = self.read_immediate(&args[0])?; let r = self.read_immediate(&args[1])?; self.exact_div(&l, &r, dest)?; } - sym::unchecked_shl - | sym::unchecked_shr - | sym::unchecked_add - | sym::unchecked_sub - | sym::unchecked_mul - | sym::unchecked_div - | sym::unchecked_rem => { - let l = self.read_immediate(&args[0])?; - let r = self.read_immediate(&args[1])?; - let bin_op = match intrinsic_name { - sym::unchecked_shl => BinOp::Shl, - sym::unchecked_shr => BinOp::Shr, - sym::unchecked_add => BinOp::Add, - sym::unchecked_sub => BinOp::Sub, - sym::unchecked_mul => BinOp::Mul, - sym::unchecked_div => BinOp::Div, - sym::unchecked_rem => BinOp::Rem, - _ => bug!(), - }; - let (val, overflowed, _ty) = self.overflowing_binary_op(bin_op, &l, &r)?; - if overflowed { - let layout = self.layout_of(substs.type_at(0))?; - let r_val = r.to_scalar().to_bits(layout.size)?; - if let sym::unchecked_shl | sym::unchecked_shr = intrinsic_name { - throw_ub_format!("overflowing shift by {} in `{}`", r_val, intrinsic_name); - } else { - throw_ub_format!("overflow executing `{}`", intrinsic_name); - } - } - self.write_scalar(val, dest)?; - } sym::rotate_left | sym::rotate_right => { // rotate_left: (X << (S % BW)) | (X >> ((BW - S) % BW)) // rotate_right: (X << ((BW - S) % BW)) | (X >> (S % BW)) - let layout = self.layout_of(substs.type_at(0))?; + let layout = self.layout_of(instance_args.type_at(0))?; let val = self.read_scalar(&args[0])?; let val_bits = val.to_bits(layout.size)?; let raw_shift = self.read_scalar(&args[1])?; @@ -286,10 +261,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { sym::write_bytes => { self.write_bytes_intrinsic(&args[0], &args[1], &args[2])?; } + sym::compare_bytes => { + let result = self.compare_bytes_intrinsic(&args[0], &args[1], &args[2])?; + self.write_scalar(result, dest)?; + } sym::arith_offset => { let ptr = self.read_pointer(&args[0])?; let offset_count = self.read_target_isize(&args[1])?; - let pointee_ty = substs.type_at(0); + let pointee_ty = instance_args.type_at(0); let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap(); let offset_bytes = offset_count.wrapping_mul(pointee_size); @@ -314,17 +293,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { (Err(_), _) | (_, Err(_)) => { // We managed to find a valid allocation for one pointer, but not the other. // That means they are definitely not pointing to the same allocation. - throw_ub_format!( - "`{}` called on pointers into different allocations", - intrinsic_name + throw_ub_custom!( + fluent::const_eval_different_allocations, + name = intrinsic_name, ); } (Ok((a_alloc_id, a_offset, _)), Ok((b_alloc_id, b_offset, _))) => { // Found allocation for both. They must be into the same allocation. if a_alloc_id != b_alloc_id { - throw_ub_format!( - "`{}` called on pointers into different allocations", - intrinsic_name + throw_ub_custom!( + fluent::const_eval_different_allocations, + name = intrinsic_name, ); } // Use these offsets for distance calculation. @@ -344,11 +323,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { if overflowed { // a < b if intrinsic_name == sym::ptr_offset_from_unsigned { - throw_ub_format!( - "`{}` called when first pointer has smaller offset than second: {} < {}", - intrinsic_name, - a_offset, - b_offset, + throw_ub_custom!( + fluent::const_eval_unsigned_offset_from_overflow, + a_offset = a_offset, + b_offset = b_offset, ); } // The signed form of the intrinsic allows this. If we interpret the @@ -356,9 +334,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // seems *positive*, they were more than isize::MAX apart. let dist = val.to_target_isize(self)?; if dist >= 0 { - throw_ub_format!( - "`{}` called when first pointer is too far before second", - intrinsic_name + throw_ub_custom!( + fluent::const_eval_offset_from_underflow, + name = intrinsic_name, ); } dist @@ -368,9 +346,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // If converting to isize produced a *negative* result, we had an overflow // because they were more than isize::MAX apart. if dist < 0 { - throw_ub_format!( - "`{}` called when first pointer is too far ahead of second", - intrinsic_name + throw_ub_custom!( + fluent::const_eval_offset_from_overflow, + name = intrinsic_name, ); } dist @@ -395,7 +373,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { assert!(self.target_isize_min() <= dist && dist <= self.target_isize_max()); isize_layout }; - let pointee_layout = self.layout_of(substs.type_at(0))?; + let pointee_layout = self.layout_of(instance_args.type_at(0))?; // If ret_layout is unsigned, we checked that so is the distance, so we are good. let val = ImmTy::from_int(dist, ret_layout); let size = ImmTy::from_int(pointee_layout.size.bytes(), ret_layout); @@ -405,7 +383,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { sym::assert_inhabited | sym::assert_zero_valid | sym::assert_mem_uninitialized_valid => { - let ty = instance.substs.type_at(0); + let ty = instance.args.type_at(0); let requirement = ValidityRequirement::from_intrinsic(intrinsic_name).unwrap(); let should_panic = !self @@ -420,17 +398,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // For *all* intrinsics we first check `is_uninhabited` to give a more specific // error message. _ if layout.abi.is_uninhabited() => format!( - "aborted execution: attempted to instantiate uninhabited type `{}`", - ty + "aborted execution: attempted to instantiate uninhabited type `{ty}`" ), ValidityRequirement::Inhabited => bug!("handled earlier"), ValidityRequirement::Zero => format!( - "aborted execution: attempted to zero-initialize type `{}`, which is invalid", - ty + "aborted execution: attempted to zero-initialize type `{ty}`, which is invalid" ), ValidityRequirement::UninitMitigated0x01Fill => format!( - "aborted execution: attempted to leave type `{}` uninitialized, which is invalid", - ty + "aborted execution: attempted to leave type `{ty}` uninitialized, which is invalid" ), ValidityRequirement::Uninit => bug!("assert_uninit_valid doesn't exist"), }; @@ -446,19 +421,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { assert_eq!(input_len, dest_len, "Return vector length must match input length"); assert!( index < dest_len, - "Index `{}` must be in bounds of vector with length {}", - index, - dest_len + "Index `{index}` must be in bounds of vector with length {dest_len}" ); for i in 0..dest_len { - let place = self.mplace_index(&dest, i)?; + let place = self.project_index(&dest, i)?; let value = if i == index { elem.clone() } else { - self.mplace_index(&input, i)?.into() + self.project_index(&input, i)?.into() }; - self.copy_op(&value, &place.into(), /*allow_transmute*/ false)?; + self.copy_op(&value, &place, /*allow_transmute*/ false)?; } } sym::simd_extract => { @@ -466,12 +439,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let (input, input_len) = self.operand_to_simd(&args[0])?; assert!( index < input_len, - "index `{}` must be in bounds of vector with length {}", - index, - input_len + "index `{index}` must be in bounds of vector with length {input_len}" ); self.copy_op( - &self.mplace_index(&input, index)?.into(), + &self.project_index(&input, index)?, dest, /*allow_transmute*/ false, )?; @@ -513,7 +484,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let op = self.eval_operand(op, None)?; let cond = self.read_scalar(&op)?.to_bool()?; if !cond { - throw_ub_format!("`assume` called with `false`"); + throw_ub_custom!(fluent::const_eval_assume_false); } Ok(()) } @@ -542,7 +513,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let (res, overflow, _ty) = self.overflowing_binary_op(BinOp::Rem, &a, &b)?; assert!(!overflow); // All overflow is UB, so this should never return on overflow. if res.assert_bits(a.layout.size) != 0 { - throw_ub_format!("exact_div: {} cannot be divided by {} without remainder", a, b) + throw_ub_custom!( + fluent::const_eval_exact_div_has_remainder, + a = format!("{a}"), + b = format!("{b}") + ) } // `Rem` says this is all right, so we can let `Div` do its job. self.binop_ignore_overflow(BinOp::Div, &a, &b, dest) @@ -632,20 +607,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { count: &OpTy<'tcx, >::Provenance>, nonoverlapping: bool, ) -> InterpResult<'tcx> { - let count = self.read_target_usize(&count)?; + let count = self.read_target_usize(count)?; let layout = self.layout_of(src.layout.ty.builtin_deref(true).unwrap().ty)?; let (size, align) = (layout.size, layout.align.abi); // `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max), // but no actual allocation can be big enough for the difference to be noticeable. let size = size.checked_mul(count, self).ok_or_else(|| { - err_ub_format!( - "overflow computing total size of `{}`", - if nonoverlapping { "copy_nonoverlapping" } else { "copy" } + err_ub_custom!( + fluent::const_eval_size_overflow, + name = if nonoverlapping { "copy_nonoverlapping" } else { "copy" } ) })?; - let src = self.read_pointer(&src)?; - let dst = self.read_pointer(&dst)?; + let src = self.read_pointer(src)?; + let dst = self.read_pointer(dst)?; self.mem_copy(src, align, dst, align, size, nonoverlapping) } @@ -658,21 +633,38 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ) -> InterpResult<'tcx> { let layout = self.layout_of(dst.layout.ty.builtin_deref(true).unwrap().ty)?; - let dst = self.read_pointer(&dst)?; - let byte = self.read_scalar(&byte)?.to_u8()?; - let count = self.read_target_usize(&count)?; + let dst = self.read_pointer(dst)?; + let byte = self.read_scalar(byte)?.to_u8()?; + let count = self.read_target_usize(count)?; // `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max), // but no actual allocation can be big enough for the difference to be noticeable. - let len = layout - .size - .checked_mul(count, self) - .ok_or_else(|| err_ub_format!("overflow computing total size of `write_bytes`"))?; + let len = layout.size.checked_mul(count, self).ok_or_else(|| { + err_ub_custom!(fluent::const_eval_size_overflow, name = "write_bytes") + })?; let bytes = std::iter::repeat(byte).take(len.bytes_usize()); self.write_bytes_ptr(dst, bytes) } + pub(crate) fn compare_bytes_intrinsic( + &mut self, + left: &OpTy<'tcx, >::Provenance>, + right: &OpTy<'tcx, >::Provenance>, + byte_count: &OpTy<'tcx, >::Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let left = self.read_pointer(left)?; + let right = self.read_pointer(right)?; + let n = Size::from_bytes(self.read_target_usize(byte_count)?); + + let left_bytes = self.read_bytes_ptr_strip_provenance(left, n)?; + let right_bytes = self.read_bytes_ptr_strip_provenance(right, n)?; + + // `Ordering`'s discriminants are -1/0/+1, so casting does the right thing. + let result = Ord::cmp(left_bytes, right_bytes) as i32; + Ok(Scalar::from_i32(result)) + } + pub(crate) fn raw_eq_intrinsic( &mut self, lhs: &OpTy<'tcx, >::Provenance>, @@ -691,7 +683,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { return Ok(&[]); }; if alloc_ref.has_provenance() { - throw_ub_format!("`raw_eq` on bytes with provenance"); + throw_ub_custom!(fluent::const_eval_raw_eq_with_provenance); } alloc_ref.get_bytes_strip_provenance() }; diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics/caller_location.rs b/compiler/rustc_const_eval/src/interpret/intrinsics/caller_location.rs index 3701eb93ec834..948bec7464ad2 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics/caller_location.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics/caller_location.rs @@ -96,26 +96,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let loc_ty = self .tcx .type_of(self.tcx.require_lang_item(LangItem::PanicLocation, None)) - .subst(*self.tcx, self.tcx.mk_substs(&[self.tcx.lifetimes.re_erased.into()])); + .instantiate(*self.tcx, self.tcx.mk_args(&[self.tcx.lifetimes.re_erased.into()])); let loc_layout = self.layout_of(loc_ty).unwrap(); let location = self.allocate(loc_layout, MemoryKind::CallerLocation).unwrap(); // Initialize fields. - self.write_immediate(file.to_ref(self), &self.mplace_field(&location, 0).unwrap().into()) + self.write_immediate(file.to_ref(self), &self.project_field(&location, 0).unwrap()) .expect("writing to memory we just allocated cannot fail"); - self.write_scalar(line, &self.mplace_field(&location, 1).unwrap().into()) + self.write_scalar(line, &self.project_field(&location, 1).unwrap()) .expect("writing to memory we just allocated cannot fail"); - self.write_scalar(col, &self.mplace_field(&location, 2).unwrap().into()) + self.write_scalar(col, &self.project_field(&location, 2).unwrap()) .expect("writing to memory we just allocated cannot fail"); location } - pub(crate) fn location_triple_for_span(&self, mut span: Span) -> (Symbol, u32, u32) { - // Remove `Inlined` marks as they pollute `expansion_cause`. - while span.is_inlined() { - span.remove_mark(); - } + pub(crate) fn location_triple_for_span(&self, span: Span) -> (Symbol, u32, u32) { let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span); let caller = self.tcx.sess.source_map().lookup_char_pos(topmost.lo()); ( diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index b448e3a24c68f..e101785b6e242 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -17,7 +17,7 @@ use rustc_target::spec::abi::Abi as CallAbi; use crate::const_eval::CheckAlignment; use super::{ - AllocBytes, AllocId, AllocRange, Allocation, ConstAllocation, Frame, ImmTy, InterpCx, + AllocBytes, AllocId, AllocRange, Allocation, ConstAllocation, FnArg, Frame, ImmTy, InterpCx, InterpResult, MemoryKind, OpTy, Operand, PlaceTy, Pointer, Provenance, Scalar, }; @@ -84,7 +84,7 @@ pub trait AllocMap { /// Methods of this trait signifies a point where CTFE evaluation would fail /// and some use case dependent behaviour can instead be applied. -pub trait Machine<'mir, 'tcx>: Sized { +pub trait Machine<'mir, 'tcx: 'mir>: Sized { /// Additional memory kinds a machine wishes to distinguish from the builtin ones type MemoryKind: Debug + std::fmt::Display + MayLeak + Eq + 'static; @@ -182,7 +182,7 @@ pub trait Machine<'mir, 'tcx>: Sized { ecx: &mut InterpCx<'mir, 'tcx, Self>, instance: ty::Instance<'tcx>, abi: CallAbi, - args: &[OpTy<'tcx, Self::Provenance>], + args: &[FnArg<'tcx, Self::Provenance>], destination: &PlaceTy<'tcx, Self::Provenance>, target: Option, unwind: mir::UnwindAction, @@ -194,7 +194,7 @@ pub trait Machine<'mir, 'tcx>: Sized { ecx: &mut InterpCx<'mir, 'tcx, Self>, fn_val: Self::ExtraFnVal, abi: CallAbi, - args: &[OpTy<'tcx, Self::Provenance>], + args: &[FnArg<'tcx, Self::Provenance>], destination: &PlaceTy<'tcx, Self::Provenance>, target: Option, unwind: mir::UnwindAction, @@ -418,6 +418,18 @@ pub trait Machine<'mir, 'tcx>: Sized { Ok(()) } + /// Called on places used for in-place function argument and return value handling. + /// + /// These places need to be protected to make sure the program cannot tell whether the + /// argument/return value was actually copied or passed in-place.. + fn protect_in_place_function_argument( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + place: &PlaceTy<'tcx, Self::Provenance>, + ) -> InterpResult<'tcx> { + // Without an aliasing model, all we can do is put `Uninit` into the place. + ecx.write_uninit(place) + } + /// Called immediately before a new stack frame gets pushed. fn init_frame_extra( ecx: &mut InterpCx<'mir, 'tcx, Self>, @@ -439,6 +451,14 @@ pub trait Machine<'mir, 'tcx>: Sized { Ok(()) } + /// Called just before the return value is copied to the caller-provided return place. + fn before_stack_pop( + _ecx: &InterpCx<'mir, 'tcx, Self>, + _frame: &Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>, + ) -> InterpResult<'tcx> { + Ok(()) + } + /// Called immediately after a stack frame got popped, but before jumping back to the caller. /// The `locals` have already been destroyed! fn after_stack_pop( @@ -484,7 +504,7 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) { _ecx: &mut InterpCx<$mir, $tcx, Self>, fn_val: !, _abi: CallAbi, - _args: &[OpTy<$tcx>], + _args: &[FnArg<$tcx>], _destination: &PlaceTy<$tcx, Self::Provenance>, _target: Option, _unwind: mir::UnwindAction, diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index d5b6a581a79f6..11bffedf54a61 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -19,6 +19,7 @@ use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt}; use rustc_target::abi::{Align, HasDataLayout, Size}; use crate::const_eval::CheckAlignment; +use crate::fluent_generated as fluent; use super::{ alloc_range, AllocBytes, AllocId, AllocMap, AllocRange, Allocation, CheckInAllocMsg, @@ -52,7 +53,7 @@ impl fmt::Display for MemoryKind { match self { MemoryKind::Stack => write!(f, "stack variable"), MemoryKind::CallerLocation => write!(f, "caller location"), - MemoryKind::Machine(m) => write!(f, "{}", m), + MemoryKind::Machine(m) => write!(f, "{m}"), } } } @@ -90,7 +91,7 @@ impl<'tcx, Other> FnVal<'tcx, Other> { // `Memory` has to depend on the `Machine` because some of its operations // (e.g., `get`) call a `Machine` hook. pub struct Memory<'mir, 'tcx, M: Machine<'mir, 'tcx>> { - /// Allocations local to this instance of the miri engine. The kind + /// Allocations local to this instance of the interpreter. The kind /// helps ensure that the same mechanism is used for allocation and /// deallocation. When an allocation is not found here, it is a /// global and looked up in the `tcx` for read access. Some machines may @@ -200,7 +201,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { align: Align, kind: MemoryKind, ) -> InterpResult<'tcx, Pointer> { - let alloc = Allocation::uninit(size, align, M::PANIC_ON_ALLOC_FAIL)?; + let alloc = if M::PANIC_ON_ALLOC_FAIL { + Allocation::uninit(size, align) + } else { + Allocation::try_uninit(size, align)? + }; self.allocate_raw_ptr(alloc, kind) } @@ -242,9 +247,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ) -> InterpResult<'tcx, Pointer> { let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr)?; if offset.bytes() != 0 { - throw_ub_format!( - "reallocating {:?} which does not point to the beginning of an object", - ptr + throw_ub_custom!( + fluent::const_eval_realloc_or_alloc_with_offset, + ptr = format!("{ptr:?}"), + kind = "realloc" ); } @@ -280,9 +286,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { trace!("deallocating: {alloc_id:?}"); if offset.bytes() != 0 { - throw_ub_format!( - "deallocating {:?} which does not point to the beginning of an object", - ptr + throw_ub_custom!( + fluent::const_eval_realloc_or_alloc_with_offset, + ptr = format!("{ptr:?}"), + kind = "dealloc", ); } @@ -290,35 +297,51 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Deallocating global memory -- always an error return Err(match self.tcx.try_get_global_alloc(alloc_id) { Some(GlobalAlloc::Function(..)) => { - err_ub_format!("deallocating {alloc_id:?}, which is a function") + err_ub_custom!( + fluent::const_eval_invalid_dealloc, + alloc_id = alloc_id, + kind = "fn", + ) } Some(GlobalAlloc::VTable(..)) => { - err_ub_format!("deallocating {alloc_id:?}, which is a vtable") + err_ub_custom!( + fluent::const_eval_invalid_dealloc, + alloc_id = alloc_id, + kind = "vtable", + ) } Some(GlobalAlloc::Static(..) | GlobalAlloc::Memory(..)) => { - err_ub_format!("deallocating {alloc_id:?}, which is static memory") + err_ub_custom!( + fluent::const_eval_invalid_dealloc, + alloc_id = alloc_id, + kind = "static_mem" + ) } - None => err_ub!(PointerUseAfterFree(alloc_id)), + None => err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccessTest)), } .into()); }; if alloc.mutability.is_not() { - throw_ub_format!("deallocating immutable allocation {alloc_id:?}"); + throw_ub_custom!(fluent::const_eval_dealloc_immutable, alloc = alloc_id,); } if alloc_kind != kind { - throw_ub_format!( - "deallocating {alloc_id:?}, which is {alloc_kind} memory, using {kind} deallocation operation" + throw_ub_custom!( + fluent::const_eval_dealloc_kind_mismatch, + alloc = alloc_id, + alloc_kind = format!("{alloc_kind}"), + kind = format!("{kind}"), ); } if let Some((size, align)) = old_size_and_align { if size != alloc.size() || align != alloc.align { - throw_ub_format!( - "incorrect layout on deallocation: {alloc_id:?} has size {} and alignment {}, but gave size {} and alignment {}", - alloc.size().bytes(), - alloc.align.bytes(), - size.bytes(), - align.bytes(), + throw_ub_custom!( + fluent::const_eval_dealloc_incorrect_layout, + alloc = alloc_id, + size = alloc.size().bytes(), + align = alloc.align.bytes(), + size_found = size.bytes(), + align_found = align.bytes(), ) } } @@ -357,7 +380,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { M::enforce_alignment(self), CheckInAllocMsg::MemoryAccessTest, |alloc_id, offset, prov| { - let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?; + let (size, align) = self + .get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccessTest)?; Ok((size, align, (alloc_id, offset, prov))) }, ) @@ -381,7 +405,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { CheckAlignment::Error, msg, |alloc_id, _, _| { - let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?; + let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?; Ok((size, align, ())) }, )?; @@ -391,7 +415,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Low-level helper function to check if a ptr is in-bounds and potentially return a reference /// to the allocation it points to. Supports both shared and mutable references, as the actual /// checking is offloaded to a helper closure. `align` defines whether and which alignment check - /// is done. Returns `None` for size 0, and otherwise `Some` of what `alloc_size` returned. + /// is done. + /// + /// If this returns `None`, the size is 0; it can however return `Some` even for size 0. fn check_and_deref_ptr( &self, ptr: Pointer>, @@ -492,7 +518,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)), Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)), - None => throw_ub!(PointerUseAfterFree(id)), + None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccessTest)), Some(GlobalAlloc::Static(def_id)) => { assert!(self.tcx.is_static(def_id)); assert!(!self.tcx.is_thread_local_static(def_id)); @@ -738,11 +764,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - /// Obtain the size and alignment of a live allocation. - pub fn get_live_alloc_size_and_align(&self, id: AllocId) -> InterpResult<'tcx, (Size, Align)> { + /// Obtain the size and alignment of a *live* allocation. + fn get_live_alloc_size_and_align( + &self, + id: AllocId, + msg: CheckInAllocMsg, + ) -> InterpResult<'tcx, (Size, Align)> { let (size, align, kind) = self.get_alloc_info(id); if matches!(kind, AllocKind::Dead) { - throw_ub!(PointerUseAfterFree(id)) + throw_ub!(PointerUseAfterFree(id, msg)) } Ok((size, align)) } @@ -884,7 +914,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a, match self.ecx.memory.alloc_map.get(id) { Some((kind, alloc)) => { // normal alloc - write!(fmt, " ({}, ", kind)?; + write!(fmt, " ({kind}, ")?; write_allocation_track_relocs( &mut *fmt, *self.ecx.tcx, @@ -1037,11 +1067,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let size = Size::from_bytes(len); let Some(alloc_ref) = self.get_ptr_alloc_mut(ptr, size, Align::ONE)? else { // zero-sized access - assert_matches!( - src.next(), - None, - "iterator said it was empty but returned an element" - ); + assert_matches!(src.next(), None, "iterator said it was empty but returned an element"); return Ok(()); }; @@ -1166,7 +1192,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { if (src_offset <= dest_offset && src_offset + size > dest_offset) || (dest_offset <= src_offset && dest_offset + size > src_offset) { - throw_ub_format!("copy_nonoverlapping called on overlapping ranges") + throw_ub_custom!(fluent::const_eval_copy_nonoverlapping_overlapping); } } diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 898d62361ab2a..b0b553c45d432 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -24,10 +24,12 @@ pub use self::eval_context::{Frame, FrameInfo, InterpCx, LocalState, LocalValue, pub use self::intern::{intern_const_alloc_recursive, InternKind}; pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump}; pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind}; -pub use self::operand::{ImmTy, Immediate, OpTy, Operand}; -pub use self::place::{MPlaceTy, MemPlace, MemPlaceMeta, Place, PlaceTy}; +pub use self::operand::{ImmTy, Immediate, OpTy, Operand, Readable}; +pub use self::place::{MPlaceTy, MemPlace, MemPlaceMeta, Place, PlaceTy, Writeable}; +pub use self::projection::Projectable; +pub use self::terminator::FnArg; pub use self::validity::{CtfeValidationMode, RefTracking}; -pub use self::visitor::{MutValueVisitor, Value, ValueVisitor}; +pub use self::visitor::ValueVisitor; pub(crate) use self::intrinsics::eval_nullary_intrinsic; use eval_context::{from_known_layout, mir_assign_valid_types}; diff --git a/compiler/rustc_const_eval/src/interpret/operand.rs b/compiler/rustc_const_eval/src/interpret/operand.rs index e30af165501e5..6e57a56b445ac 100644 --- a/compiler/rustc_const_eval/src/interpret/operand.rs +++ b/compiler/rustc_const_eval/src/interpret/operand.rs @@ -1,6 +1,8 @@ //! Functions concerning immediate values and operands, and reading from operands. //! All high-level functions to read from memory work on operands as sources. +use std::assert_matches::assert_matches; + use either::{Either, Left, Right}; use rustc_hir::def::Namespace; @@ -13,8 +15,8 @@ use rustc_target::abi::{self, Abi, Align, HasDataLayout, Size}; use super::{ alloc_range, from_known_layout, mir_assign_valid_types, AllocId, ConstValue, Frame, GlobalId, - InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Place, PlaceTy, Pointer, - Provenance, Scalar, + InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, PlaceTy, Pointer, + Projectable, Provenance, Scalar, }; /// An `Immediate` represents a single immediate self-contained Rust value. @@ -31,7 +33,7 @@ pub enum Immediate { /// A pair of two scalar value (must have `ScalarPair` ABI where both fields are /// `Scalar::Initialized`). ScalarPair(Scalar, Scalar), - /// A value of fully uninitialized memory. Can have and size and layout. + /// A value of fully uninitialized memory. Can have arbitrary size and layout. Uninit, } @@ -106,7 +108,7 @@ impl std::fmt::Display for ImmTy<'_, Prov> { // Just print the ptr value. `pretty_print_const_scalar_ptr` would also try to // print what is points to, which would fail since it has no access to the local // memory. - cx.pretty_print_const_pointer(ptr, ty, true) + cx.pretty_print_const_pointer(ptr, ty) } } } @@ -178,20 +180,6 @@ impl<'tcx, Prov: Provenance> From> for OpTy<'tcx, Prov> { } } -impl<'tcx, Prov: Provenance> From<&'_ MPlaceTy<'tcx, Prov>> for OpTy<'tcx, Prov> { - #[inline(always)] - fn from(mplace: &MPlaceTy<'tcx, Prov>) -> Self { - OpTy { op: Operand::Indirect(**mplace), layout: mplace.layout, align: Some(mplace.align) } - } -} - -impl<'tcx, Prov: Provenance> From<&'_ mut MPlaceTy<'tcx, Prov>> for OpTy<'tcx, Prov> { - #[inline(always)] - fn from(mplace: &mut MPlaceTy<'tcx, Prov>) -> Self { - OpTy { op: Operand::Indirect(**mplace), layout: mplace.layout, align: Some(mplace.align) } - } -} - impl<'tcx, Prov: Provenance> From> for OpTy<'tcx, Prov> { #[inline(always)] fn from(val: ImmTy<'tcx, Prov>) -> Self { @@ -240,43 +228,126 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> { let int = self.to_scalar().assert_int(); ConstInt::new(int, self.layout.ty.is_signed(), self.layout.ty.is_ptr_sized_integral()) } + + /// Compute the "sub-immediate" that is located within the `base` at the given offset with the + /// given layout. + // Not called `offset` to avoid confusion with the trait method. + fn offset_(&self, offset: Size, layout: TyAndLayout<'tcx>, cx: &impl HasDataLayout) -> Self { + // This makes several assumptions about what layouts we will encounter; we match what + // codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`). + let inner_val: Immediate<_> = match (**self, self.layout.abi) { + // if the entire value is uninit, then so is the field (can happen in ConstProp) + (Immediate::Uninit, _) => Immediate::Uninit, + // the field contains no information, can be left uninit + _ if layout.is_zst() => Immediate::Uninit, + // some fieldless enum variants can have non-zero size but still `Aggregate` ABI... try + // to detect those here and also give them no data + _ if matches!(layout.abi, Abi::Aggregate { .. }) + && matches!(&layout.fields, abi::FieldsShape::Arbitrary { offsets, .. } if offsets.len() == 0) => + { + Immediate::Uninit + } + // the field covers the entire type + _ if layout.size == self.layout.size => { + assert_eq!(offset.bytes(), 0); + assert!( + match (self.layout.abi, layout.abi) { + (Abi::Scalar(..), Abi::Scalar(..)) => true, + (Abi::ScalarPair(..), Abi::ScalarPair(..)) => true, + _ => false, + }, + "cannot project into {} immediate with equally-sized field {}\nouter ABI: {:#?}\nfield ABI: {:#?}", + self.layout.ty, + layout.ty, + self.layout.abi, + layout.abi, + ); + **self + } + // extract fields from types with `ScalarPair` ABI + (Immediate::ScalarPair(a_val, b_val), Abi::ScalarPair(a, b)) => { + assert!(matches!(layout.abi, Abi::Scalar(..))); + Immediate::from(if offset.bytes() == 0 { + debug_assert_eq!(layout.size, a.size(cx)); + a_val + } else { + debug_assert_eq!(offset, a.size(cx).align_to(b.align(cx).abi)); + debug_assert_eq!(layout.size, b.size(cx)); + b_val + }) + } + // everything else is a bug + _ => bug!("invalid field access on immediate {}, layout {:#?}", self, self.layout), + }; + + ImmTy::from_immediate(inner_val, layout) + } +} + +impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> { + #[inline(always)] + fn layout(&self) -> TyAndLayout<'tcx> { + self.layout + } + + fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + _ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MemPlaceMeta> { + assert!(self.layout.is_sized()); // unsized ImmTy can only exist temporarily and should never reach this here + Ok(MemPlaceMeta::None) + } + + fn offset_with_meta( + &self, + offset: Size, + meta: MemPlaceMeta, + layout: TyAndLayout<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Self> { + assert_matches!(meta, MemPlaceMeta::None); // we can't store this anywhere anyway + Ok(self.offset_(offset, layout, cx)) + } + + fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + _ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + Ok(self.clone().into()) + } } impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> { - pub fn len(&self, cx: &impl HasDataLayout) -> InterpResult<'tcx, u64> { - if self.layout.is_unsized() { - if matches!(self.op, Operand::Immediate(Immediate::Uninit)) { - // Uninit unsized places shouldn't occur. In the interpreter we have them - // temporarily for unsized arguments before their value is put in; in ConstProp they - // remain uninit and this code can actually be reached. - throw_inval!(UninitUnsizedLocal); + // Provided as inherent method since it doesn't need the `ecx` of `Projectable::meta`. + pub fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta> { + Ok(if self.layout.is_unsized() { + if matches!(self.op, Operand::Immediate(_)) { + // Unsized immediate OpTy cannot occur. We create a MemPlace for all unsized locals during argument passing. + // However, ConstProp doesn't do that, so we can run into this nonsense situation. + throw_inval!(ConstPropNonsense); } // There are no unsized immediates. - self.assert_mem_place().len(cx) + self.assert_mem_place().meta } else { - match self.layout.fields { - abi::FieldsShape::Array { count, .. } => Ok(count), - _ => bug!("len not supported on sized type {:?}", self.layout.ty), - } - } + MemPlaceMeta::None + }) } +} - /// Replace the layout of this operand. There's basically no sanity check that this makes sense, - /// you better know what you are doing! If this is an immediate, applying the wrong layout can - /// not just lead to invalid data, it can actually *shift the data around* since the offsets of - /// a ScalarPair are entirely determined by the layout, not the data. - pub fn transmute(&self, layout: TyAndLayout<'tcx>) -> Self { - assert_eq!( - self.layout.size, layout.size, - "transmuting with a size change, that doesn't seem right" - ); - OpTy { layout, ..*self } +impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> { + #[inline(always)] + fn layout(&self) -> TyAndLayout<'tcx> { + self.layout } - /// Offset the operand in memory (if possible) and change its metadata. - /// - /// This can go wrong very easily if you give the wrong layout for the new place! - pub(super) fn offset_with_meta( + fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + _ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MemPlaceMeta> { + self.meta() + } + + fn offset_with_meta( &self, offset: Size, meta: MemPlaceMeta, @@ -286,28 +357,43 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> { match self.as_mplace_or_imm() { Left(mplace) => Ok(mplace.offset_with_meta(offset, meta, layout, cx)?.into()), Right(imm) => { - assert!( - matches!(*imm, Immediate::Uninit), - "Scalar/ScalarPair cannot be offset into" - ); assert!(!meta.has_meta()); // no place to store metadata here // Every part of an uninit is uninit. - Ok(ImmTy::uninit(layout).into()) + Ok(imm.offset(offset, layout, cx)?.into()) } } } - /// Offset the operand in memory (if possible). - /// - /// This can go wrong very easily if you give the wrong layout for the new place! - pub fn offset( + fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( &self, - offset: Size, - layout: TyAndLayout<'tcx>, - cx: &impl HasDataLayout, - ) -> InterpResult<'tcx, Self> { - assert!(layout.is_sized()); - self.offset_with_meta(offset, MemPlaceMeta::None, layout, cx) + _ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + Ok(self.clone()) + } +} + +pub trait Readable<'tcx, Prov: Provenance>: Projectable<'tcx, Prov> { + fn as_mplace_or_imm(&self) -> Either, ImmTy<'tcx, Prov>>; +} + +impl<'tcx, Prov: Provenance + 'static> Readable<'tcx, Prov> for OpTy<'tcx, Prov> { + #[inline(always)] + fn as_mplace_or_imm(&self) -> Either, ImmTy<'tcx, Prov>> { + self.as_mplace_or_imm() + } +} + +impl<'tcx, Prov: Provenance + 'static> Readable<'tcx, Prov> for MPlaceTy<'tcx, Prov> { + #[inline(always)] + fn as_mplace_or_imm(&self) -> Either, ImmTy<'tcx, Prov>> { + Left(self.clone()) + } +} + +impl<'tcx, Prov: Provenance> Readable<'tcx, Prov> for ImmTy<'tcx, Prov> { + #[inline(always)] + fn as_mplace_or_imm(&self) -> Either, ImmTy<'tcx, Prov>> { + Right(self.clone()) } } @@ -383,14 +469,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// ConstProp needs it, though. pub fn read_immediate_raw( &self, - src: &OpTy<'tcx, M::Provenance>, + src: &impl Readable<'tcx, M::Provenance>, ) -> InterpResult<'tcx, Either, ImmTy<'tcx, M::Provenance>>> { Ok(match src.as_mplace_or_imm() { Left(ref mplace) => { if let Some(val) = self.read_immediate_from_mplace_raw(mplace)? { Right(val) } else { - Left(*mplace) + Left(mplace.clone()) } } Right(val) => Right(val), @@ -403,14 +489,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { #[inline(always)] pub fn read_immediate( &self, - op: &OpTy<'tcx, M::Provenance>, + op: &impl Readable<'tcx, M::Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { if !matches!( - op.layout.abi, + op.layout().abi, Abi::Scalar(abi::Scalar::Initialized { .. }) | Abi::ScalarPair(abi::Scalar::Initialized { .. }, abi::Scalar::Initialized { .. }) ) { - span_bug!(self.cur_span(), "primitive read not possible for type: {:?}", op.layout.ty); + span_bug!( + self.cur_span(), + "primitive read not possible for type: {:?}", + op.layout().ty + ); } let imm = self.read_immediate_raw(op)?.right().unwrap(); if matches!(*imm, Immediate::Uninit) { @@ -422,7 +512,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Read a scalar from a place pub fn read_scalar( &self, - op: &OpTy<'tcx, M::Provenance>, + op: &impl Readable<'tcx, M::Provenance>, ) -> InterpResult<'tcx, Scalar> { Ok(self.read_immediate(op)?.to_scalar()) } @@ -433,16 +523,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Read a pointer from a place. pub fn read_pointer( &self, - op: &OpTy<'tcx, M::Provenance>, + op: &impl Readable<'tcx, M::Provenance>, ) -> InterpResult<'tcx, Pointer>> { self.read_scalar(op)?.to_pointer(self) } /// Read a pointer-sized unsigned integer from a place. - pub fn read_target_usize(&self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx, u64> { + pub fn read_target_usize( + &self, + op: &impl Readable<'tcx, M::Provenance>, + ) -> InterpResult<'tcx, u64> { self.read_scalar(op)?.to_target_usize(self) } /// Read a pointer-sized signed integer from a place. - pub fn read_target_isize(&self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx, i64> { + pub fn read_target_isize( + &self, + op: &impl Readable<'tcx, M::Provenance>, + ) -> InterpResult<'tcx, i64> { self.read_scalar(op)?.to_target_isize(self) } @@ -497,18 +593,28 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Every place can be read from, so we can turn them into an operand. /// This will definitely return `Indirect` if the place is a `Ptr`, i.e., this /// will never actually read from memory. - #[inline(always)] pub fn place_to_op( &self, place: &PlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - let op = match **place { - Place::Ptr(mplace) => Operand::Indirect(mplace), - Place::Local { frame, local } => { - *self.local_to_op(&self.stack()[frame], local, None)? + match place.as_mplace_or_local() { + Left(mplace) => Ok(mplace.into()), + Right((frame, local, offset)) => { + let base = self.local_to_op(&self.stack()[frame], local, None)?; + let mut field = if let Some(offset) = offset { + // This got offset. We can be sure that the field is sized. + base.offset(offset, place.layout, self)? + } else { + assert_eq!(place.layout, base.layout); + // Unsized cases are possible here since an unsized local will be a + // `Place::Local` until the first projection calls `place_to_op` to extract the + // underlying mplace. + base + }; + field.align = Some(place.align); + Ok(field) } - }; - Ok(OpTy { op, layout: place.layout, align: Some(place.align) }) + } } /// Evaluate a place with the goal of reading from it. This lets us sometimes @@ -525,7 +631,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let mut op = self.local_to_op(self.frame(), mir_place.local, layout)?; // Using `try_fold` turned out to be bad for performance, hence the loop. for elem in mir_place.projection.iter() { - op = self.operand_projection(&op, elem)? + op = self.project(&op, elem)? } trace!("eval_place_to_op: got {:?}", *op); @@ -575,14 +681,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { Ok(op) } - /// Evaluate a bunch of operands at once - pub(super) fn eval_operands( - &self, - ops: &[mir::Operand<'tcx>], - ) -> InterpResult<'tcx, Vec>> { - ops.iter().map(|op| self.eval_operand(op, None)).collect() - } - fn eval_ty_constant( &self, val: ty::Const<'tcx>, @@ -598,12 +696,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { throw_inval!(AlreadyReported(reported.into())) } ty::ConstKind::Unevaluated(uv) => { - let instance = self.resolve(uv.def, uv.substs)?; + let instance = self.resolve(uv.def, uv.args)?; let cid = GlobalId { instance, promoted: None }; - self.ctfe_query(span, |tcx| { - tcx.eval_to_valtree(self.param_env.with_const().and(cid)) - })? - .unwrap_or_else(|| bug!("unable to create ValTree for {uv:?}")) + self.ctfe_query(span, |tcx| tcx.eval_to_valtree(self.param_env.and(cid)))? + .unwrap_or_else(|| bug!("unable to create ValTree for {uv:?}")) } ty::ConstKind::Bound(..) | ty::ConstKind::Infer(..) => { span_bug!(self.cur_span(), "unexpected ConstKind in ctfe: {val:?}") @@ -627,13 +723,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } mir::ConstantKind::Val(val, ty) => self.const_val_to_op(val, ty, layout), mir::ConstantKind::Unevaluated(uv, _) => { - let instance = self.resolve(uv.def, uv.substs)?; + let instance = self.resolve(uv.def, uv.args)?; Ok(self.eval_global(GlobalId { instance, promoted: uv.promoted }, span)?.into()) } } } - pub(super) fn const_val_to_op( + pub(crate) fn const_val_to_op( &self, val_val: ConstValue<'tcx>, ty: Ty<'tcx>, diff --git a/compiler/rustc_const_eval/src/interpret/operator.rs b/compiler/rustc_const_eval/src/interpret/operator.rs index 7186148daf0ba..eb0645780673c 100644 --- a/compiler/rustc_const_eval/src/interpret/operator.rs +++ b/compiler/rustc_const_eval/src/interpret/operator.rs @@ -3,10 +3,13 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::{InterpResult, Scalar}; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, FloatTy, Ty}; +use rustc_span::symbol::sym; use rustc_target::abi::Abi; use super::{ImmTy, Immediate, InterpCx, Machine, PlaceTy}; +use crate::fluent_generated as fluent; + impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Applies the binary operation `op` to the two operands and writes a tuple of the result /// and a boolean signifying the potential overflow to the destination. @@ -19,10 +22,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ) -> InterpResult<'tcx> { let (val, overflowed, ty) = self.overflowing_binary_op(op, &left, &right)?; debug_assert_eq!( - self.tcx.mk_tup(&[ty, self.tcx.types.bool]), + Ty::new_tup(self.tcx.tcx, &[ty, self.tcx.types.bool]), dest.layout.ty, - "type mismatch for result of {:?}", - op, + "type mismatch for result of {op:?}", ); // Write the result to `dest`. if let Abi::ScalarPair(..) = dest.layout.abi { @@ -35,9 +37,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // With randomized layout, `(int, bool)` might cease to be a `ScalarPair`, so we have to // do a component-wise write here. This code path is slower than the above because // `place_field` will have to `force_allocate` locals here. - let val_field = self.place_field(&dest, 0)?; + let val_field = self.project_field(dest, 0)?; self.write_scalar(val, &val_field)?; - let overflowed_field = self.place_field(&dest, 1)?; + let overflowed_field = self.project_field(dest, 1)?; self.write_scalar(Scalar::from_bool(overflowed), &overflowed_field)?; } Ok(()) @@ -53,7 +55,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { dest: &PlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { let (val, _overflowed, ty) = self.overflowing_binary_op(op, left, right)?; - assert_eq!(ty, dest.layout.ty, "type mismatch for result of {:?}", op); + assert_eq!(ty, dest.layout.ty, "type mismatch for result of {op:?}"); self.write_scalar(val, dest) } } @@ -139,8 +141,17 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> { use rustc_middle::mir::BinOp::*; + let throw_ub_on_overflow = match bin_op { + AddUnchecked => Some(sym::unchecked_add), + SubUnchecked => Some(sym::unchecked_sub), + MulUnchecked => Some(sym::unchecked_mul), + ShlUnchecked => Some(sym::unchecked_shl), + ShrUnchecked => Some(sym::unchecked_shr), + _ => None, + }; + // Shift ops can have an RHS with a different numeric type. - if bin_op == Shl || bin_op == Shr { + if matches!(bin_op, Shl | ShlUnchecked | Shr | ShrUnchecked) { let size = u128::from(left_layout.size.bits()); // Even if `r` is signed, we treat it as if it was unsigned (i.e., we use its // zero-extended form). This matches the codegen backend: @@ -155,6 +166,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // integers are maximally 128bits wide, so negative shifts *always* overflow and we have // consistent results for the same value represented at different bit widths. assert!(size <= 128); + let original_r = r; let overflow = r >= size; // The shift offset is implicitly masked to the type size, to make sure this operation // is always defined. This is the one MIR operator that does *not* directly map to a @@ -166,19 +178,28 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let result = if left_layout.abi.is_signed() { let l = self.sign_extend(l, left_layout) as i128; let result = match bin_op { - Shl => l.checked_shl(r).unwrap(), - Shr => l.checked_shr(r).unwrap(), + Shl | ShlUnchecked => l.checked_shl(r).unwrap(), + Shr | ShrUnchecked => l.checked_shr(r).unwrap(), _ => bug!(), }; result as u128 } else { match bin_op { - Shl => l.checked_shl(r).unwrap(), - Shr => l.checked_shr(r).unwrap(), + Shl | ShlUnchecked => l.checked_shl(r).unwrap(), + Shr | ShrUnchecked => l.checked_shr(r).unwrap(), _ => bug!(), } }; let truncated = self.truncate(result, left_layout); + + if overflow && let Some(intrinsic_name) = throw_ub_on_overflow { + throw_ub_custom!( + fluent::const_eval_overflow_shift, + val = original_r, + name = intrinsic_name + ); + } + return Ok((Scalar::from_uint(truncated, left_layout.size), overflow, left_layout.ty)); } @@ -216,9 +237,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { Rem if r == 0 => throw_ub!(RemainderByZero), Div => Some(i128::overflowing_div), Rem => Some(i128::overflowing_rem), - Add => Some(i128::overflowing_add), - Sub => Some(i128::overflowing_sub), - Mul => Some(i128::overflowing_mul), + Add | AddUnchecked => Some(i128::overflowing_add), + Sub | SubUnchecked => Some(i128::overflowing_sub), + Mul | MulUnchecked => Some(i128::overflowing_mul), _ => None, }; if let Some(op) = op { @@ -242,11 +263,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // If that truncation loses any information, we have an overflow. let result = result as u128; let truncated = self.truncate(result, left_layout); - return Ok(( - Scalar::from_uint(truncated, size), - oflo || self.sign_extend(truncated, left_layout) != result, - left_layout.ty, - )); + let overflow = oflo || self.sign_extend(truncated, left_layout) != result; + if overflow && let Some(intrinsic_name) = throw_ub_on_overflow { + throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name); + } + return Ok((Scalar::from_uint(truncated, size), overflow, left_layout.ty)); } } @@ -263,12 +284,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { BitAnd => (Scalar::from_uint(l & r, size), left_layout.ty), BitXor => (Scalar::from_uint(l ^ r, size), left_layout.ty), - Add | Sub | Mul | Rem | Div => { + Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Rem | Div => { assert!(!left_layout.abi.is_signed()); let op: fn(u128, u128) -> (u128, bool) = match bin_op { - Add => u128::overflowing_add, - Sub => u128::overflowing_sub, - Mul => u128::overflowing_mul, + Add | AddUnchecked => u128::overflowing_add, + Sub | SubUnchecked => u128::overflowing_sub, + Mul | MulUnchecked => u128::overflowing_mul, Div if r == 0 => throw_ub!(DivisionByZero), Rem if r == 0 => throw_ub!(RemainderByZero), Div => u128::overflowing_div, @@ -279,11 +300,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Truncate to target type. // If that truncation loses any information, we have an overflow. let truncated = self.truncate(result, left_layout); - return Ok(( - Scalar::from_uint(truncated, size), - oflo || truncated != result, - left_layout.ty, - )); + let overflow = oflo || truncated != result; + if overflow && let Some(intrinsic_name) = throw_ub_on_overflow { + throw_ub_custom!(fluent::const_eval_overflow, name = intrinsic_name); + } + return Ok((Scalar::from_uint(truncated, size), overflow, left_layout.ty)); } _ => span_bug!( diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 2a31a59ad6c7c..daadb75896472 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -2,19 +2,23 @@ //! into a place. //! All high-level functions to write to memory work on places as destinations. +use std::assert_matches::assert_matches; + use either::{Either, Left, Right}; use rustc_ast::Mutability; use rustc_index::IndexSlice; use rustc_middle::mir; +use rustc_middle::mir::interpret::PointerArithmetic; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; +use rustc_middle::ty::Ty; use rustc_target::abi::{self, Abi, Align, FieldIdx, HasDataLayout, Size, FIRST_VARIANT}; use super::{ alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, CheckInAllocMsg, ConstAlloc, ImmTy, Immediate, InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand, - Pointer, Provenance, Scalar, + Pointer, Projectable, Provenance, Readable, Scalar, }; #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] @@ -43,6 +47,27 @@ impl MemPlaceMeta { Self::None => false, } } + + pub(crate) fn len<'tcx>( + &self, + layout: TyAndLayout<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, u64> { + if layout.is_unsized() { + // We need to consult `meta` metadata + match layout.ty.kind() { + ty::Slice(..) | ty::Str => self.unwrap_meta().to_target_usize(cx), + _ => bug!("len not supported on unsized type {:?}", layout.ty), + } + } else { + // Go through the layout. There are lots of types that support a length, + // e.g., SIMD types. (But not all repr(simd) types even have FieldsShape::Array!) + match layout.fields { + abi::FieldsShape::Array { count, .. } => Ok(count), + _ => bug!("len not supported on sized type {:?}", layout.ty), + } + } + } } #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)] @@ -56,7 +81,7 @@ pub struct MemPlace { } /// A MemPlace with its layout. Constructing it is only possible in this module. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] +#[derive(Clone, Hash, Eq, PartialEq, Debug)] pub struct MPlaceTy<'tcx, Prov: Provenance = AllocId> { mplace: MemPlace, pub layout: TyAndLayout<'tcx>, @@ -67,14 +92,26 @@ pub struct MPlaceTy<'tcx, Prov: Provenance = AllocId> { pub align: Align, } +impl<'tcx, Prov: Provenance> std::ops::Deref for MPlaceTy<'tcx, Prov> { + type Target = MemPlace; + #[inline(always)] + fn deref(&self) -> &MemPlace { + &self.mplace + } +} + #[derive(Copy, Clone, Debug)] pub enum Place { /// A place referring to a value allocated in the `Memory` system. Ptr(MemPlace), - /// To support alloc-free locals, we are able to write directly to a local. + /// To support alloc-free locals, we are able to write directly to a local. The offset indicates + /// where in the local this place is located; if it is `None`, no projection has been applied. + /// Such projections are meaningful even if the offset is 0, since they can change layouts. /// (Without that optimization, we'd just always be a `MemPlace`.) - Local { frame: usize, local: mir::Local }, + /// Note that this only stores the frame index, not the thread this frame belongs to -- that is + /// implicit. This means a `Place` must never be moved across interpreter thread boundaries! + Local { frame: usize, local: mir::Local, offset: Option }, } #[derive(Clone, Debug)] @@ -96,14 +133,6 @@ impl<'tcx, Prov: Provenance> std::ops::Deref for PlaceTy<'tcx, Prov> { } } -impl<'tcx, Prov: Provenance> std::ops::Deref for MPlaceTy<'tcx, Prov> { - type Target = MemPlace; - #[inline(always)] - fn deref(&self) -> &MemPlace { - &self.mplace - } -} - impl<'tcx, Prov: Provenance> From> for PlaceTy<'tcx, Prov> { #[inline(always)] fn from(mplace: MPlaceTy<'tcx, Prov>) -> Self { @@ -111,33 +140,23 @@ impl<'tcx, Prov: Provenance> From> for PlaceTy<'tcx, Prov> } } -impl<'tcx, Prov: Provenance> From<&'_ MPlaceTy<'tcx, Prov>> for PlaceTy<'tcx, Prov> { - #[inline(always)] - fn from(mplace: &MPlaceTy<'tcx, Prov>) -> Self { - PlaceTy { place: Place::Ptr(**mplace), layout: mplace.layout, align: mplace.align } - } -} - -impl<'tcx, Prov: Provenance> From<&'_ mut MPlaceTy<'tcx, Prov>> for PlaceTy<'tcx, Prov> { - #[inline(always)] - fn from(mplace: &mut MPlaceTy<'tcx, Prov>) -> Self { - PlaceTy { place: Place::Ptr(**mplace), layout: mplace.layout, align: mplace.align } - } -} - impl MemPlace { #[inline(always)] pub fn from_ptr(ptr: Pointer>) -> Self { MemPlace { ptr, meta: MemPlaceMeta::None } } + #[inline(always)] + pub fn from_ptr_with_meta(ptr: Pointer>, meta: MemPlaceMeta) -> Self { + MemPlace { ptr, meta } + } + /// Adjust the provenance of the main pointer (metadata is unaffected). pub fn map_provenance(self, f: impl FnOnce(Option) -> Option) -> Self { MemPlace { ptr: self.ptr.map_provenance(f), ..self } } /// Turn a mplace into a (thin or wide) pointer, as a reference, pointing to the same space. - /// This is the inverse of `ref_to_mplace`. #[inline(always)] pub fn to_ref(self, cx: &impl HasDataLayout) -> Immediate { match self.meta { @@ -149,7 +168,8 @@ impl MemPlace { } #[inline] - pub(super) fn offset_with_meta<'tcx>( + // Not called `offset_with_meta` to avoid confusion with the trait method. + fn offset_with_meta_<'tcx>( self, offset: Size, meta: MemPlaceMeta, @@ -163,19 +183,6 @@ impl MemPlace { } } -impl Place { - /// Asserts that this points to some local variable. - /// Returns the frame idx and the variable idx. - #[inline] - #[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980) - pub fn assert_local(&self) -> (usize, mir::Local) { - match self { - Place::Local { frame, local } => (*frame, *local), - _ => bug!("assert_local: expected Place::Local, got {:?}", self), - } - } -} - impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> { /// Produces a MemPlace that works for ZST but nothing else. /// Conceptually this is a new allocation, but it doesn't actually create an allocation so you @@ -188,11 +195,39 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> { MPlaceTy { mplace: MemPlace { ptr, meta: MemPlaceMeta::None }, layout, align } } - /// Offset the place in memory and change its metadata. - /// - /// This can go wrong very easily if you give the wrong layout for the new place! #[inline] - pub(crate) fn offset_with_meta( + pub fn from_aligned_ptr(ptr: Pointer>, layout: TyAndLayout<'tcx>) -> Self { + MPlaceTy { mplace: MemPlace::from_ptr(ptr), layout, align: layout.align.abi } + } + + #[inline] + pub fn from_aligned_ptr_with_meta( + ptr: Pointer>, + layout: TyAndLayout<'tcx>, + meta: MemPlaceMeta, + ) -> Self { + MPlaceTy { + mplace: MemPlace::from_ptr_with_meta(ptr, meta), + layout, + align: layout.align.abi, + } + } +} + +impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for MPlaceTy<'tcx, Prov> { + #[inline(always)] + fn layout(&self) -> TyAndLayout<'tcx> { + self.layout + } + + fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + _ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MemPlaceMeta> { + Ok(self.meta) + } + + fn offset_with_meta( &self, offset: Size, meta: MemPlaceMeta, @@ -200,58 +235,65 @@ impl<'tcx, Prov: Provenance> MPlaceTy<'tcx, Prov> { cx: &impl HasDataLayout, ) -> InterpResult<'tcx, Self> { Ok(MPlaceTy { - mplace: self.mplace.offset_with_meta(offset, meta, cx)?, + mplace: self.mplace.offset_with_meta_(offset, meta, cx)?, align: self.align.restrict_for_offset(offset), layout, }) } - /// Offset the place in memory. - /// - /// This can go wrong very easily if you give the wrong layout for the new place! - pub fn offset( + fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( &self, - offset: Size, - layout: TyAndLayout<'tcx>, - cx: &impl HasDataLayout, - ) -> InterpResult<'tcx, Self> { - assert!(layout.is_sized()); - self.offset_with_meta(offset, MemPlaceMeta::None, layout, cx) + _ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + Ok(self.clone().into()) } +} - #[inline] - pub fn from_aligned_ptr(ptr: Pointer>, layout: TyAndLayout<'tcx>) -> Self { - MPlaceTy { mplace: MemPlace::from_ptr(ptr), layout, align: layout.align.abi } +impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for PlaceTy<'tcx, Prov> { + #[inline(always)] + fn layout(&self) -> TyAndLayout<'tcx> { + self.layout } - #[inline] - pub fn from_aligned_ptr_with_meta( - ptr: Pointer>, - layout: TyAndLayout<'tcx>, - meta: MemPlaceMeta, - ) -> Self { - let mut mplace = MemPlace::from_ptr(ptr); - mplace.meta = meta; - - MPlaceTy { mplace, layout, align: layout.align.abi } + fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MemPlaceMeta> { + ecx.place_meta(self) } - #[inline] - pub(crate) fn len(&self, cx: &impl HasDataLayout) -> InterpResult<'tcx, u64> { - if self.layout.is_unsized() { - // We need to consult `meta` metadata - match self.layout.ty.kind() { - ty::Slice(..) | ty::Str => self.mplace.meta.unwrap_meta().to_target_usize(cx), - _ => bug!("len not supported on unsized type {:?}", self.layout.ty), - } - } else { - // Go through the layout. There are lots of types that support a length, - // e.g., SIMD types. (But not all repr(simd) types even have FieldsShape::Array!) - match self.layout.fields { - abi::FieldsShape::Array { count, .. } => Ok(count), - _ => bug!("len not supported on sized type {:?}", self.layout.ty), + fn offset_with_meta( + &self, + offset: Size, + meta: MemPlaceMeta, + layout: TyAndLayout<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Self> { + Ok(match self.as_mplace_or_local() { + Left(mplace) => mplace.offset_with_meta(offset, meta, layout, cx)?.into(), + Right((frame, local, old_offset)) => { + assert_matches!(meta, MemPlaceMeta::None); // we couldn't store it anyway... + let new_offset = cx + .data_layout() + .offset(old_offset.unwrap_or(Size::ZERO).bytes(), offset.bytes())?; + PlaceTy { + place: Place::Local { + frame, + local, + offset: Some(Size::from_bytes(new_offset)), + }, + align: self.align.restrict_for_offset(offset), + layout, + } } - } + }) + } + + fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + ecx.place_to_op(self) } } @@ -279,13 +321,15 @@ impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> { } } -impl<'tcx, Prov: Provenance> PlaceTy<'tcx, Prov> { +impl<'tcx, Prov: Provenance + 'static> PlaceTy<'tcx, Prov> { /// A place is either an mplace or some local. #[inline] - pub fn as_mplace_or_local(&self) -> Either, (usize, mir::Local)> { + pub fn as_mplace_or_local( + &self, + ) -> Either, (usize, mir::Local, Option)> { match **self { Place::Ptr(mplace) => Left(MPlaceTy { mplace, layout: self.layout, align: self.align }), - Place::Local { frame, local } => Right((frame, local)), + Place::Local { frame, local, offset } => Right((frame, local, offset)), } } @@ -301,18 +345,80 @@ impl<'tcx, Prov: Provenance> PlaceTy<'tcx, Prov> { } } +pub trait Writeable<'tcx, Prov: Provenance>: Projectable<'tcx, Prov> { + fn as_mplace_or_local( + &self, + ) -> Either, (usize, mir::Local, Option, Align, TyAndLayout<'tcx>)>; + + fn force_mplace<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &mut InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Prov>>; +} + +impl<'tcx, Prov: Provenance + 'static> Writeable<'tcx, Prov> for PlaceTy<'tcx, Prov> { + #[inline(always)] + fn as_mplace_or_local( + &self, + ) -> Either, (usize, mir::Local, Option, Align, TyAndLayout<'tcx>)> + { + self.as_mplace_or_local() + .map_right(|(frame, local, offset)| (frame, local, offset, self.align, self.layout)) + } + + #[inline(always)] + fn force_mplace<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &mut InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Prov>> { + ecx.force_allocation(self) + } +} + +impl<'tcx, Prov: Provenance + 'static> Writeable<'tcx, Prov> for MPlaceTy<'tcx, Prov> { + #[inline(always)] + fn as_mplace_or_local( + &self, + ) -> Either, (usize, mir::Local, Option, Align, TyAndLayout<'tcx>)> + { + Left(self.clone()) + } + + #[inline(always)] + fn force_mplace<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + _ecx: &mut InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Prov>> { + Ok(self.clone()) + } +} + // FIXME: Working around https://github.com/rust-lang/rust/issues/54385 impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M> where Prov: Provenance + 'static, M: Machine<'mir, 'tcx, Provenance = Prov>, { + /// Get the metadata of the given place. + pub(super) fn place_meta( + &self, + place: &PlaceTy<'tcx, M::Provenance>, + ) -> InterpResult<'tcx, MemPlaceMeta> { + if place.layout.is_unsized() { + // For `Place::Local`, the metadata is stored with the local, not the place. So we have + // to look that up first. + self.place_to_op(place)?.meta() + } else { + Ok(MemPlaceMeta::None) + } + } + /// Take a value, which represents a (thin or wide) reference, and make it a place. - /// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref()`. + /// Alignment is just based on the type. This is the inverse of `mplace_to_ref()`. /// /// Only call this if you are sure the place is "valid" (aligned and inbounds), or do not /// want to ever use the place for memory access! - /// Generally prefer `deref_operand`. + /// Generally prefer `deref_pointer`. pub fn ref_to_mplace( &self, val: &ImmTy<'tcx, M::Provenance>, @@ -326,17 +432,29 @@ where Immediate::Uninit => throw_ub!(InvalidUninitBytes(None)), }; - let mplace = MemPlace { ptr: ptr.to_pointer(self)?, meta }; - // When deref'ing a pointer, the *static* alignment given by the type is what matters. - let align = layout.align.abi; - Ok(MPlaceTy { mplace, layout, align }) + // `ref_to_mplace` is called on raw pointers even if they don't actually get dereferenced; + // we hence can't call `size_and_align_of` since that asserts more validity than we want. + Ok(MPlaceTy::from_aligned_ptr_with_meta(ptr.to_pointer(self)?, layout, meta)) + } + + /// Turn a mplace into a (thin or wide) mutable raw pointer, pointing to the same space. + /// `align` information is lost! + /// This is the inverse of `ref_to_mplace`. + pub fn mplace_to_ref( + &self, + mplace: &MPlaceTy<'tcx, M::Provenance>, + ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { + let imm = mplace.to_ref(self); + let layout = self.layout_of(Ty::new_mut_ptr(self.tcx.tcx, mplace.layout.ty))?; + Ok(ImmTy::from_immediate(imm, layout)) } /// Take an operand, representing a pointer, and dereference it to a place. + /// Corresponds to the `*` operator in Rust. #[instrument(skip(self), level = "debug")] - pub fn deref_operand( + pub fn deref_pointer( &self, - src: &OpTy<'tcx, M::Provenance>, + src: &impl Readable<'tcx, M::Provenance>, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { let val = self.read_immediate(src)?; trace!("deref to {} on {:?}", val.layout.ty, *val); @@ -346,41 +464,44 @@ where } let mplace = self.ref_to_mplace(&val)?; - self.check_mplace(mplace)?; + self.check_mplace(&mplace)?; Ok(mplace) } #[inline] pub(super) fn get_place_alloc( &self, - place: &MPlaceTy<'tcx, M::Provenance>, + mplace: &MPlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, Option>> { - assert!(place.layout.is_sized()); - assert!(!place.meta.has_meta()); - let size = place.layout.size; - self.get_ptr_alloc(place.ptr, size, place.align) + let (size, _align) = self + .size_and_align_of_mplace(&mplace)? + .unwrap_or((mplace.layout.size, mplace.layout.align.abi)); + // Due to packed places, only `mplace.align` matters. + self.get_ptr_alloc(mplace.ptr, size, mplace.align) } #[inline] pub(super) fn get_place_alloc_mut( &mut self, - place: &MPlaceTy<'tcx, M::Provenance>, + mplace: &MPlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, Option>> { - assert!(place.layout.is_sized()); - assert!(!place.meta.has_meta()); - let size = place.layout.size; - self.get_ptr_alloc_mut(place.ptr, size, place.align) + let (size, _align) = self + .size_and_align_of_mplace(&mplace)? + .unwrap_or((mplace.layout.size, mplace.layout.align.abi)); + // Due to packed places, only `mplace.align` matters. + self.get_ptr_alloc_mut(mplace.ptr, size, mplace.align) } /// Check if this mplace is dereferenceable and sufficiently aligned. - pub fn check_mplace(&self, mplace: MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { - let (size, align) = self + pub fn check_mplace(&self, mplace: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { + let (size, _align) = self .size_and_align_of_mplace(&mplace)? .unwrap_or((mplace.layout.size, mplace.layout.align.abi)); - assert!(mplace.align <= align, "dynamic alignment less strict than static one?"); - let align = if M::enforce_alignment(self).should_check() { align } else { Align::ONE }; + // Due to packed places, only `mplace.align` matters. + let align = + if M::enforce_alignment(self).should_check() { mplace.align } else { Align::ONE }; self.check_ptr_access_align(mplace.ptr, size, align, CheckInAllocMsg::DerefTest)?; Ok(()) } @@ -395,7 +516,7 @@ where // (Transmuting is okay since this is an in-memory place. We also double-check the size // stays the same.) let (len, e_ty) = mplace.layout.ty.simd_size_and_type(*self.tcx); - let array = self.tcx.mk_array(e_ty, len); + let array = Ty::new_array(self.tcx.tcx, e_ty, len); let layout = self.layout_of(array)?; assert_eq!(layout.size, mplace.layout.size); Ok((MPlaceTy { layout, ..*mplace }, len)) @@ -417,7 +538,7 @@ where local: mir::Local, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { let layout = self.layout_of_local(&self.stack()[frame], local, None)?; - let place = Place::Local { frame, local }; + let place = Place::Local { frame, local, offset: None }; Ok(PlaceTy { place, layout, align: layout.align.abi }) } @@ -425,13 +546,13 @@ where /// place; for reading, a more efficient alternative is `eval_place_to_op`. #[instrument(skip(self), level = "debug")] pub fn eval_place( - &mut self, + &self, mir_place: mir::Place<'tcx>, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { let mut place = self.local_to_place(self.frame_idx(), mir_place.local)?; // Using `try_fold` turned out to be bad for performance, hence the loop. for elem in mir_place.projection.iter() { - place = self.place_projection(&place, elem)? + place = self.project(&place, elem)? } trace!("{:?}", self.dump_place(place.place)); @@ -458,13 +579,13 @@ where pub fn write_immediate( &mut self, src: Immediate, - dest: &PlaceTy<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { self.write_immediate_no_validate(src, dest)?; - if M::enforce_validity(self, dest.layout) { + if M::enforce_validity(self, dest.layout()) { // Data got changed, better make sure it matches the type! - self.validate_operand(&self.place_to_op(dest)?)?; + self.validate_operand(&dest.to_op(self)?)?; } Ok(()) @@ -475,7 +596,7 @@ where pub fn write_scalar( &mut self, val: impl Into>, - dest: &PlaceTy<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { self.write_immediate(Immediate::Scalar(val.into()), dest) } @@ -485,7 +606,7 @@ where pub fn write_pointer( &mut self, ptr: impl Into>>, - dest: &PlaceTy<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { self.write_scalar(Scalar::from_maybe_pointer(ptr.into(), self), dest) } @@ -496,32 +617,63 @@ where fn write_immediate_no_validate( &mut self, src: Immediate, - dest: &PlaceTy<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { - assert!(dest.layout.is_sized(), "Cannot write unsized data"); - trace!("write_immediate: {:?} <- {:?}: {}", *dest, src, dest.layout.ty); + assert!(dest.layout().is_sized(), "Cannot write unsized immediate data"); // See if we can avoid an allocation. This is the counterpart to `read_immediate_raw`, // but not factored as a separate function. - let mplace = match dest.place { - Place::Local { frame, local } => { - match M::access_local_mut(self, frame, local)? { - Operand::Immediate(local) => { - // Local can be updated in-place. - *local = src; - return Ok(()); - } - Operand::Indirect(mplace) => { - // The local is in memory, go on below. - *mplace + let mplace = match dest.as_mplace_or_local() { + Right((frame, local, offset, align, layout)) => { + if offset.is_some() { + // This has been projected to a part of this local. We could have complicated + // logic to still keep this local as an `Operand`... but it's much easier to + // just fall back to the indirect path. + dest.force_mplace(self)? + } else { + match M::access_local_mut(self, frame, local)? { + Operand::Immediate(local_val) => { + // Local can be updated in-place. + *local_val = src; + // Double-check that the value we are storing and the local fit to each other. + // (*After* doing the update for borrow checker reasons.) + if cfg!(debug_assertions) { + let local_layout = + self.layout_of_local(&self.stack()[frame], local, None)?; + match (src, local_layout.abi) { + (Immediate::Scalar(scalar), Abi::Scalar(s)) => { + assert_eq!(scalar.size(), s.size(self)) + } + ( + Immediate::ScalarPair(a_val, b_val), + Abi::ScalarPair(a, b), + ) => { + assert_eq!(a_val.size(), a.size(self)); + assert_eq!(b_val.size(), b.size(self)); + } + (Immediate::Uninit, _) => {} + (src, abi) => { + bug!( + "value {src:?} cannot be written into local with type {} (ABI {abi:?})", + local_layout.ty + ) + } + }; + } + return Ok(()); + } + Operand::Indirect(mplace) => { + // The local is in memory, go on below. + MPlaceTy { mplace: *mplace, align, layout } + } } } } - Place::Ptr(mplace) => mplace, // already referring to memory + Left(mplace) => mplace, // already referring to memory }; // This is already in memory, write there. - self.write_immediate_to_mplace_no_validate(src, dest.layout, dest.align, mplace) + self.write_immediate_to_mplace_no_validate(src, mplace.layout, mplace.align, mplace.mplace) } /// Write an immediate to memory. @@ -540,14 +692,17 @@ where // wrong type. let tcx = *self.tcx; - let Some(mut alloc) = self.get_place_alloc_mut(&MPlaceTy { mplace: dest, layout, align })? else { + let Some(mut alloc) = + self.get_place_alloc_mut(&MPlaceTy { mplace: dest, layout, align })? + else { // zero-sized access return Ok(()); }; match value { Immediate::Scalar(scalar) => { - let Abi::Scalar(s) = layout.abi else { span_bug!( + let Abi::Scalar(s) = layout.abi else { + span_bug!( self.cur_span(), "write_immediate_to_mplace: invalid Scalar layout: {layout:#?}", ) @@ -560,7 +715,8 @@ where // We checked `ptr_align` above, so all fields will have the alignment they need. // We would anyway check against `ptr_align.restrict_for_offset(b_offset)`, // which `ptr.offset(b_offset)` cannot possibly fail to satisfy. - let Abi::ScalarPair(a, b) = layout.abi else { span_bug!( + let Abi::ScalarPair(a, b) = layout.abi else { + span_bug!( self.cur_span(), "write_immediate_to_mplace: invalid ScalarPair layout: {:#?}", layout @@ -581,18 +737,29 @@ where } } - pub fn write_uninit(&mut self, dest: &PlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { + pub fn write_uninit( + &mut self, + dest: &impl Writeable<'tcx, M::Provenance>, + ) -> InterpResult<'tcx> { let mplace = match dest.as_mplace_or_local() { Left(mplace) => mplace, - Right((frame, local)) => { - match M::access_local_mut(self, frame, local)? { - Operand::Immediate(local) => { - *local = Immediate::Uninit; - return Ok(()); - } - Operand::Indirect(mplace) => { - // The local is in memory, go on below. - MPlaceTy { mplace: *mplace, layout: dest.layout, align: dest.align } + Right((frame, local, offset, align, layout)) => { + if offset.is_some() { + // This has been projected to a part of this local. We could have complicated + // logic to still keep this local as an `Operand`... but it's much easier to + // just fall back to the indirect path. + // FIXME: share the logic with `write_immediate_no_validate`. + dest.force_mplace(self)? + } else { + match M::access_local_mut(self, frame, local)? { + Operand::Immediate(local) => { + *local = Immediate::Uninit; + return Ok(()); + } + Operand::Indirect(mplace) => { + // The local is in memory, go on below. + MPlaceTy { mplace: *mplace, layout, align } + } } } } @@ -611,15 +778,15 @@ where #[instrument(skip(self), level = "debug")] pub fn copy_op( &mut self, - src: &OpTy<'tcx, M::Provenance>, - dest: &PlaceTy<'tcx, M::Provenance>, + src: &impl Readable<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, allow_transmute: bool, ) -> InterpResult<'tcx> { self.copy_op_no_validate(src, dest, allow_transmute)?; - if M::enforce_validity(self, dest.layout) { + if M::enforce_validity(self, dest.layout()) { // Data got changed, better make sure it matches the type! - self.validate_operand(&self.place_to_op(dest)?)?; + self.validate_operand(&dest.to_op(self)?)?; } Ok(()) @@ -632,20 +799,20 @@ where #[instrument(skip(self), level = "debug")] fn copy_op_no_validate( &mut self, - src: &OpTy<'tcx, M::Provenance>, - dest: &PlaceTy<'tcx, M::Provenance>, + src: &impl Readable<'tcx, M::Provenance>, + dest: &impl Writeable<'tcx, M::Provenance>, allow_transmute: bool, ) -> InterpResult<'tcx> { // We do NOT compare the types for equality, because well-typed code can // actually "transmute" `&mut T` to `&T` in an assignment without a cast. let layout_compat = - mir_assign_valid_types(*self.tcx, self.param_env, src.layout, dest.layout); + mir_assign_valid_types(*self.tcx, self.param_env, src.layout(), dest.layout()); if !allow_transmute && !layout_compat { span_bug!( self.cur_span(), "type mismatch when copying!\nsrc: {:?},\ndest: {:?}", - src.layout.ty, - dest.layout.ty, + src.layout().ty, + dest.layout().ty, ); } @@ -658,13 +825,13 @@ where // actually sized, due to a trivially false where-clause // predicate like `where Self: Sized` with `Self = dyn Trait`. // See #102553 for an example of such a predicate. - if src.layout.is_unsized() { - throw_inval!(SizeOfUnsizedType(src.layout.ty)); + if src.layout().is_unsized() { + throw_inval!(ConstPropNonsense); } - if dest.layout.is_unsized() { - throw_inval!(SizeOfUnsizedType(dest.layout.ty)); + if dest.layout().is_unsized() { + throw_inval!(ConstPropNonsense); } - assert_eq!(src.layout.size, dest.layout.size); + assert_eq!(src.layout().size, dest.layout().size); // Yay, we got a value that we can write directly. return if layout_compat { self.write_immediate_no_validate(*src_val, dest) @@ -673,10 +840,10 @@ where // loaded using the offsets defined by `src.layout`. When we put this back into // the destination, we have to use the same offsets! So (a) we make sure we // write back to memory, and (b) we use `dest` *with the source layout*. - let dest_mem = self.force_allocation(dest)?; + let dest_mem = dest.force_mplace(self)?; self.write_immediate_to_mplace_no_validate( *src_val, - src.layout, + src.layout(), dest_mem.align, *dest_mem, ) @@ -685,9 +852,9 @@ where Left(mplace) => mplace, }; // Slow path, this does not fit into an immediate. Just memcpy. - trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout.ty); + trace!("copy_op: {:?} <- {:?}: {}", *dest, src, dest.layout().ty); - let dest = self.force_allocation(&dest)?; + let dest = dest.force_mplace(self)?; let Some((dest_size, _)) = self.size_and_align_of_mplace(&dest)? else { span_bug!(self.cur_span(), "copy_op needs (dynamically) sized values") }; @@ -699,8 +866,13 @@ where assert_eq!(src.layout.size, dest.layout.size); } + // Setting `nonoverlapping` here only has an effect when we don't hit the fast-path above, + // but that should at least match what LLVM does where `memcpy` is also only used when the + // type does not have Scalar/ScalarPair layout. + // (Or as the `Assign` docs put it, assignments "not producing primitives" must be + // non-overlapping.) self.mem_copy( - src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ false, + src.ptr, src.align, dest.ptr, dest.align, dest_size, /*nonoverlapping*/ true, ) } @@ -714,8 +886,8 @@ where place: &PlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { let mplace = match place.place { - Place::Local { frame, local } => { - match M::access_local_mut(self, frame, local)? { + Place::Local { frame, local, offset } => { + let whole_local = match M::access_local_mut(self, frame, local)? { &mut Operand::Immediate(local_val) => { // We need to make an allocation. @@ -728,10 +900,11 @@ where throw_unsup_format!("unsized locals are not supported"); } let mplace = *self.allocate(local_layout, MemoryKind::Stack)?; + // Preserve old value. (As an optimization, we can skip this if it was uninit.) if !matches!(local_val, Immediate::Uninit) { - // Preserve old value. (As an optimization, we can skip this if it was uninit.) - // We don't have to validate as we can assume the local - // was already valid for its type. + // We don't have to validate as we can assume the local was already + // valid for its type. We must not use any part of `place` here, that + // could be a projection to a part of the local! self.write_immediate_to_mplace_no_validate( local_val, local_layout, @@ -739,29 +912,48 @@ where mplace, )?; } - // Now we can call `access_mut` again, asserting it goes well, - // and actually overwrite things. + // Now we can call `access_mut` again, asserting it goes well, and actually + // overwrite things. This points to the entire allocation, not just the part + // the place refers to, i.e. we do this before we apply `offset`. *M::access_local_mut(self, frame, local).unwrap() = Operand::Indirect(mplace); mplace } &mut Operand::Indirect(mplace) => mplace, // this already was an indirect local + }; + if let Some(offset) = offset { + whole_local.offset_with_meta_(offset, MemPlaceMeta::None, self)? + } else { + // Preserve wide place metadata, do not call `offset`. + whole_local } } Place::Ptr(mplace) => mplace, }; - // Return with the original layout, so that the caller can go on + // Return with the original layout and align, so that the caller can go on Ok(MPlaceTy { mplace, layout: place.layout, align: place.align }) } + pub fn allocate_dyn( + &mut self, + layout: TyAndLayout<'tcx>, + kind: MemoryKind, + meta: MemPlaceMeta, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { + let Some((size, align)) = self.size_and_align_of(&meta, &layout)? else { + span_bug!(self.cur_span(), "cannot allocate space for `extern` type, size is not known") + }; + let ptr = self.allocate_ptr(size, align, kind)?; + Ok(MPlaceTy::from_aligned_ptr_with_meta(ptr.into(), layout, meta)) + } + pub fn allocate( &mut self, layout: TyAndLayout<'tcx>, kind: MemoryKind, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { assert!(layout.is_sized()); - let ptr = self.allocate_ptr(layout.size, layout.align.abi, kind)?; - Ok(MPlaceTy::from_aligned_ptr(ptr.into(), layout)) + self.allocate_dyn(layout, kind, MemPlaceMeta::None) } /// Returns a wide MPlace of type `&'static [mut] str` to a new 1-aligned allocation. @@ -775,7 +967,8 @@ where let meta = Scalar::from_target_usize(u64::try_from(str.len()).unwrap(), self); let mplace = MemPlace { ptr: ptr.into(), meta: MemPlaceMeta::Meta(meta) }; - let ty = self.tcx.mk_ref( + let ty = Ty::new_ref( + self.tcx.tcx, self.tcx.lifetimes.re_static, ty::TypeAndMut { ty: self.tcx.types.str_, mutbl }, ); @@ -791,10 +984,10 @@ where operands: &IndexSlice>, dest: &PlaceTy<'tcx, M::Provenance>, ) -> InterpResult<'tcx> { - self.write_uninit(&dest)?; + self.write_uninit(dest)?; let (variant_index, variant_dest, active_field_index) = match *kind { mir::AggregateKind::Adt(_, variant_index, _, _, active_field_index) => { - let variant_dest = self.place_downcast(&dest, variant_index)?; + let variant_dest = self.project_downcast(dest, variant_index)?; (variant_index, variant_dest, active_field_index) } _ => (FIRST_VARIANT, dest.clone(), None), @@ -804,11 +997,11 @@ where } for (field_index, operand) in operands.iter_enumerated() { let field_index = active_field_index.unwrap_or(field_index); - let field_dest = self.place_field(&variant_dest, field_index.as_usize())?; + let field_dest = self.project_field(&variant_dest, field_index.as_usize())?; let op = self.eval_operand(operand, Some(field_dest.layout))?; self.copy_op(&op, &field_dest, /*allow_transmute*/ false)?; } - self.write_discriminant(variant_index, &dest) + self.write_discriminant(variant_index, dest) } pub fn raw_const_to_mplace( @@ -844,22 +1037,24 @@ where Ok((mplace, vtable)) } - /// Turn an operand with a `dyn* Trait` type into an operand with the actual dynamic type. - /// Aso returns the vtable. - pub(super) fn unpack_dyn_star( + /// Turn a `dyn* Trait` type into an value with the actual dynamic type. + /// Also returns the vtable. + pub(super) fn unpack_dyn_star>( &self, - op: &OpTy<'tcx, M::Provenance>, - ) -> InterpResult<'tcx, (OpTy<'tcx, M::Provenance>, Pointer>)> { + val: &P, + ) -> InterpResult<'tcx, (P, Pointer>)> { assert!( - matches!(op.layout.ty.kind(), ty::Dynamic(_, _, ty::DynStar)), + matches!(val.layout().ty.kind(), ty::Dynamic(_, _, ty::DynStar)), "`unpack_dyn_star` only makes sense on `dyn*` types" ); - let data = self.operand_field(&op, 0)?; - let vtable = self.operand_field(&op, 1)?; - let vtable = self.read_pointer(&vtable)?; + let data = self.project_field(val, 0)?; + let vtable = self.project_field(val, 1)?; + let vtable = self.read_pointer(&vtable.to_op(self)?)?; let (ty, _) = self.get_ptr_vtable(vtable)?; let layout = self.layout_of(ty)?; - let data = data.transmute(layout); + // `data` is already the right thing but has the wrong type. So we transmute it, by + // projecting with offset 0. + let data = data.transmute(layout, self)?; Ok((data, vtable)) } } diff --git a/compiler/rustc_const_eval/src/interpret/projection.rs b/compiler/rustc_const_eval/src/interpret/projection.rs index 91da930db4fbf..882097ad2c340 100644 --- a/compiler/rustc_const_eval/src/interpret/projection.rs +++ b/compiler/rustc_const_eval/src/interpret/projection.rs @@ -7,17 +7,70 @@ //! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually //! implement the logic on OpTy, and MPlaceTy calls that. -use either::{Left, Right}; - use rustc_middle::mir; use rustc_middle::ty; -use rustc_middle::ty::layout::LayoutOf; -use rustc_target::abi::{self, Abi, VariantIdx}; +use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; +use rustc_middle::ty::Ty; +use rustc_middle::ty::TyCtxt; +use rustc_target::abi::HasDataLayout; +use rustc_target::abi::Size; +use rustc_target::abi::{self, VariantIdx}; + +use super::{InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar}; + +/// A thing that we can project into, and that has a layout. +pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug { + /// Get the layout. + fn layout(&self) -> TyAndLayout<'tcx>; + + /// Get the metadata of a wide value. + fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, MemPlaceMeta>; + + fn len<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, u64> { + self.meta(ecx)?.len(self.layout(), ecx) + } + + /// Offset the value by the given amount, replacing the layout and metadata. + fn offset_with_meta( + &self, + offset: Size, + meta: MemPlaceMeta, + layout: TyAndLayout<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Self>; + + fn offset( + &self, + offset: Size, + layout: TyAndLayout<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Self> { + assert!(layout.is_sized()); + self.offset_with_meta(offset, MemPlaceMeta::None, layout, cx) + } + + fn transmute( + &self, + layout: TyAndLayout<'tcx>, + cx: &impl HasDataLayout, + ) -> InterpResult<'tcx, Self> { + assert_eq!(self.layout().size, layout.size); + self.offset_with_meta(Size::ZERO, MemPlaceMeta::None, layout, cx) + } -use super::{ - ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, PlaceTy, - Provenance, Scalar, -}; + /// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for + /// reading from this thing. + fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>( + &self, + ecx: &InterpCx<'mir, 'tcx, M>, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>; +} // FIXME: Working around https://github.com/rust-lang/rust/issues/54385 impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M> @@ -25,167 +78,83 @@ where Prov: Provenance + 'static, M: Machine<'mir, 'tcx, Provenance = Prov>, { - //# Field access - /// Offset a pointer to project to a field of a struct/union. Unlike `place_field`, this is /// always possible without allocating, so it can take `&self`. Also return the field's layout. - /// This supports both struct and array fields. + /// This supports both struct and array fields, but not slices! /// /// This also works for arrays, but then the `usize` index type is restricting. /// For indexing into arrays, use `mplace_index`. - pub fn mplace_field( + pub fn project_field>( &self, - base: &MPlaceTy<'tcx, M::Provenance>, + base: &P, field: usize, - ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { - let offset = base.layout.fields.offset(field); - let field_layout = base.layout.field(self, field); + ) -> InterpResult<'tcx, P> { + // Slices nominally have length 0, so they will panic somewhere in `fields.offset`. + debug_assert!( + !matches!(base.layout().ty.kind(), ty::Slice(..)), + "`field` projection called on a slice -- call `index` projection instead" + ); + let offset = base.layout().fields.offset(field); + let field_layout = base.layout().field(self, field); // Offset may need adjustment for unsized fields. let (meta, offset) = if field_layout.is_unsized() { + if base.layout().is_sized() { + // An unsized field of a sized type? Sure... + // But const-prop actually feeds us such nonsense MIR! (see test `const_prop/issue-86351.rs`) + throw_inval!(ConstPropNonsense); + } + let base_meta = base.meta(self)?; // Re-use parent metadata to determine dynamic field layout. // With custom DSTS, this *will* execute user-defined code, but the same // happens at run-time so that's okay. - match self.size_and_align_of(&base.meta, &field_layout)? { - Some((_, align)) => (base.meta, offset.align_to(align)), + match self.size_and_align_of(&base_meta, &field_layout)? { + Some((_, align)) => (base_meta, offset.align_to(align)), None => { // For unsized types with an extern type tail we perform no adjustments. // NOTE: keep this in sync with `PlaceRef::project_field` in the codegen backend. - assert!(matches!(base.meta, MemPlaceMeta::None)); - (base.meta, offset) + assert!(matches!(base_meta, MemPlaceMeta::None)); + (base_meta, offset) } } } else { - // base.meta could be present; we might be accessing a sized field of an unsized + // base_meta could be present; we might be accessing a sized field of an unsized // struct. (MemPlaceMeta::None, offset) }; - // We do not look at `base.layout.align` nor `field_layout.align`, unlike - // codegen -- mostly to see if we can get away with that base.offset_with_meta(offset, meta, field_layout, self) } - /// Gets the place of a field inside the place, and also the field's type. - /// Just a convenience function, but used quite a bit. - /// This is the only projection that might have a side-effect: We cannot project - /// into the field of a local `ScalarPair`, we have to first allocate it. - pub fn place_field( - &mut self, - base: &PlaceTy<'tcx, M::Provenance>, - field: usize, - ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - // FIXME: We could try to be smarter and avoid allocation for fields that span the - // entire place. - let base = self.force_allocation(base)?; - Ok(self.mplace_field(&base, field)?.into()) - } - - pub fn operand_field( - &self, - base: &OpTy<'tcx, M::Provenance>, - field: usize, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - let base = match base.as_mplace_or_imm() { - Left(ref mplace) => { - // We can reuse the mplace field computation logic for indirect operands. - let field = self.mplace_field(mplace, field)?; - return Ok(field.into()); - } - Right(value) => value, - }; - - let field_layout = base.layout.field(self, field); - let offset = base.layout.fields.offset(field); - // This makes several assumptions about what layouts we will encounter; we match what - // codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`). - let field_val: Immediate<_> = match (*base, base.layout.abi) { - // if the entire value is uninit, then so is the field (can happen in ConstProp) - (Immediate::Uninit, _) => Immediate::Uninit, - // the field contains no information, can be left uninit - _ if field_layout.is_zst() => Immediate::Uninit, - // the field covers the entire type - _ if field_layout.size == base.layout.size => { - assert!(match (base.layout.abi, field_layout.abi) { - (Abi::Scalar(..), Abi::Scalar(..)) => true, - (Abi::ScalarPair(..), Abi::ScalarPair(..)) => true, - _ => false, - }); - assert!(offset.bytes() == 0); - *base - } - // extract fields from types with `ScalarPair` ABI - (Immediate::ScalarPair(a_val, b_val), Abi::ScalarPair(a, b)) => { - assert!(matches!(field_layout.abi, Abi::Scalar(..))); - Immediate::from(if offset.bytes() == 0 { - debug_assert_eq!(field_layout.size, a.size(self)); - a_val - } else { - debug_assert_eq!(offset, a.size(self).align_to(b.align(self).abi)); - debug_assert_eq!(field_layout.size, b.size(self)); - b_val - }) - } - // everything else is a bug - _ => span_bug!( - self.cur_span(), - "invalid field access on immediate {}, layout {:#?}", - base, - base.layout - ), - }; - - Ok(ImmTy::from_immediate(field_val, field_layout).into()) - } - - //# Downcasting - - pub fn mplace_downcast( + /// Downcasting to an enum variant. + pub fn project_downcast>( &self, - base: &MPlaceTy<'tcx, M::Provenance>, + base: &P, variant: VariantIdx, - ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { + ) -> InterpResult<'tcx, P> { + assert!(!base.meta(self)?.has_meta()); // Downcasts only change the layout. // (In particular, no check about whether this is even the active variant -- that's by design, // see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.) - assert!(!base.meta.has_meta()); - let mut base = *base; - base.layout = base.layout.for_variant(self, variant); - Ok(base) - } - - pub fn place_downcast( - &self, - base: &PlaceTy<'tcx, M::Provenance>, - variant: VariantIdx, - ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - // Downcast just changes the layout - let mut base = base.clone(); - base.layout = base.layout.for_variant(self, variant); - Ok(base) - } - - pub fn operand_downcast( - &self, - base: &OpTy<'tcx, M::Provenance>, - variant: VariantIdx, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - // Downcast just changes the layout - let mut base = base.clone(); - base.layout = base.layout.for_variant(self, variant); - Ok(base) + // So we just "offset" by 0. + let layout = base.layout().for_variant(self, variant); + if layout.abi.is_uninhabited() { + // `read_discriminant` should have excluded uninhabited variants... but ConstProp calls + // us on dead code. + throw_inval!(ConstPropNonsense) + } + // This cannot be `transmute` as variants *can* have a smaller size than the entire enum. + base.offset(Size::ZERO, layout, self) } - //# Slice indexing - - #[inline(always)] - pub fn operand_index( + /// Compute the offset and field layout for accessing the given index. + pub fn project_index>( &self, - base: &OpTy<'tcx, M::Provenance>, + base: &P, index: u64, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + ) -> InterpResult<'tcx, P> { // Not using the layout method because we want to compute on u64 - match base.layout.fields { + let (offset, field_layout) = match base.layout().fields { abi::FieldsShape::Array { stride, count: _ } => { // `count` is nonsense for slices, use the dynamic length instead. let len = base.len(self)?; @@ -195,63 +164,26 @@ where } let offset = stride * index; // `Size` multiplication // All fields have the same layout. - let field_layout = base.layout.field(self, 0); - base.offset(offset, field_layout, self) + let field_layout = base.layout().field(self, 0); + (offset, field_layout) } _ => span_bug!( self.cur_span(), "`mplace_index` called on non-array type {:?}", - base.layout.ty + base.layout().ty ), - } - } - - /// Iterates over all fields of an array. Much more efficient than doing the - /// same by repeatedly calling `operand_index`. - pub fn operand_array_fields<'a>( - &self, - base: &'a OpTy<'tcx, Prov>, - ) -> InterpResult<'tcx, impl Iterator>> + 'a> { - let len = base.len(self)?; // also asserts that we have a type where this makes sense - let abi::FieldsShape::Array { stride, .. } = base.layout.fields else { - span_bug!(self.cur_span(), "operand_array_fields: expected an array layout"); }; - let field_layout = base.layout.field(self, 0); - let dl = &self.tcx.data_layout; - // `Size` multiplication - Ok((0..len).map(move |i| base.offset(stride * i, field_layout, dl))) - } - - /// Index into an array. - pub fn mplace_index( - &self, - base: &MPlaceTy<'tcx, M::Provenance>, - index: u64, - ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { - Ok(self.operand_index(&base.into(), index)?.assert_mem_place()) - } - pub fn place_index( - &mut self, - base: &PlaceTy<'tcx, M::Provenance>, - index: u64, - ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - // There's not a lot we can do here, since we cannot have a place to a part of a local. If - // we are accessing the only element of a 1-element array, it's still the entire local... - // that doesn't seem worth it. - let base = self.force_allocation(base)?; - Ok(self.mplace_index(&base, index)?.into()) + base.offset(offset, field_layout, self) } - //# ConstantIndex support - - fn operand_constant_index( + fn project_constant_index>( &self, - base: &OpTy<'tcx, M::Provenance>, + base: &P, offset: u64, min_length: u64, from_end: bool, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + ) -> InterpResult<'tcx, P> { let n = base.len(self)?; if n < min_length { // This can only be reached in ConstProp and non-rustc-MIR. @@ -266,32 +198,38 @@ where offset }; - self.operand_index(base, index) + self.project_index(base, index) } - fn place_constant_index( - &mut self, - base: &PlaceTy<'tcx, M::Provenance>, - offset: u64, - min_length: u64, - from_end: bool, - ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - let base = self.force_allocation(base)?; - Ok(self - .operand_constant_index(&base.into(), offset, min_length, from_end)? - .assert_mem_place() - .into()) + /// Iterates over all fields of an array. Much more efficient than doing the + /// same by repeatedly calling `operand_index`. + pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>( + &self, + base: &'a P, + ) -> InterpResult<'tcx, impl Iterator> + 'a> + where + 'tcx: 'a, + { + let abi::FieldsShape::Array { stride, .. } = base.layout().fields else { + span_bug!(self.cur_span(), "operand_array_fields: expected an array layout"); + }; + let len = base.len(self)?; + let field_layout = base.layout().field(self, 0); + let tcx: TyCtxt<'tcx> = *self.tcx; + // `Size` multiplication + Ok((0..len).map(move |i| { + base.offset_with_meta(stride * i, MemPlaceMeta::None, field_layout, &tcx) + })) } - //# Subslicing - - fn operand_subslice( + /// Subslicing + fn project_subslice>( &self, - base: &OpTy<'tcx, M::Provenance>, + base: &P, from: u64, to: u64, from_end: bool, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + ) -> InterpResult<'tcx, P> { let len = base.len(self)?; // also asserts that we have a type where this makes sense let actual_to = if from_end { if from.checked_add(to).map_or(true, |to| to > len) { @@ -305,100 +243,64 @@ where // Not using layout method because that works with usize, and does not work with slices // (that have count 0 in their layout). - let from_offset = match base.layout.fields { + let from_offset = match base.layout().fields { abi::FieldsShape::Array { stride, .. } => stride * from, // `Size` multiplication is checked _ => { - span_bug!(self.cur_span(), "unexpected layout of index access: {:#?}", base.layout) + span_bug!( + self.cur_span(), + "unexpected layout of index access: {:#?}", + base.layout() + ) } }; // Compute meta and new layout let inner_len = actual_to.checked_sub(from).unwrap(); - let (meta, ty) = match base.layout.ty.kind() { + let (meta, ty) = match base.layout().ty.kind() { // It is not nice to match on the type, but that seems to be the only way to // implement this. - ty::Array(inner, _) => (MemPlaceMeta::None, self.tcx.mk_array(*inner, inner_len)), + ty::Array(inner, _) => { + (MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len)) + } ty::Slice(..) => { let len = Scalar::from_target_usize(inner_len, self); - (MemPlaceMeta::Meta(len), base.layout.ty) + (MemPlaceMeta::Meta(len), base.layout().ty) } _ => { - span_bug!(self.cur_span(), "cannot subslice non-array type: `{:?}`", base.layout.ty) + span_bug!( + self.cur_span(), + "cannot subslice non-array type: `{:?}`", + base.layout().ty + ) } }; let layout = self.layout_of(ty)?; - base.offset_with_meta(from_offset, meta, layout, self) - } - - pub fn place_subslice( - &mut self, - base: &PlaceTy<'tcx, M::Provenance>, - from: u64, - to: u64, - from_end: bool, - ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - let base = self.force_allocation(base)?; - Ok(self.operand_subslice(&base.into(), from, to, from_end)?.assert_mem_place().into()) - } - - //# Applying a general projection - /// Projects into a place. - #[instrument(skip(self), level = "trace")] - pub fn place_projection( - &mut self, - base: &PlaceTy<'tcx, M::Provenance>, - proj_elem: mir::PlaceElem<'tcx>, - ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { - use rustc_middle::mir::ProjectionElem::*; - Ok(match proj_elem { - OpaqueCast(ty) => { - let mut place = base.clone(); - place.layout = self.layout_of(ty)?; - place - } - Field(field, _) => self.place_field(base, field.index())?, - Downcast(_, variant) => self.place_downcast(base, variant)?, - Deref => self.deref_operand(&self.place_to_op(base)?)?.into(), - Index(local) => { - let layout = self.layout_of(self.tcx.types.usize)?; - let n = self.local_to_op(self.frame(), local, Some(layout))?; - let n = self.read_target_usize(&n)?; - self.place_index(base, n)? - } - ConstantIndex { offset, min_length, from_end } => { - self.place_constant_index(base, offset, min_length, from_end)? - } - Subslice { from, to, from_end } => self.place_subslice(base, from, to, from_end)?, - }) + base.offset_with_meta(from_offset, meta, layout, self) } + /// Applying a general projection #[instrument(skip(self), level = "trace")] - pub fn operand_projection( - &self, - base: &OpTy<'tcx, M::Provenance>, - proj_elem: mir::PlaceElem<'tcx>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + pub fn project

(&self, base: &P, proj_elem: mir::PlaceElem<'tcx>) -> InterpResult<'tcx, P> + where + P: Projectable<'tcx, M::Provenance> + From> + std::fmt::Debug, + { use rustc_middle::mir::ProjectionElem::*; Ok(match proj_elem { - OpaqueCast(ty) => { - let mut op = base.clone(); - op.layout = self.layout_of(ty)?; - op - } - Field(field, _) => self.operand_field(base, field.index())?, - Downcast(_, variant) => self.operand_downcast(base, variant)?, - Deref => self.deref_operand(base)?.into(), + OpaqueCast(ty) => base.transmute(self.layout_of(ty)?, self)?, + Field(field, _) => self.project_field(base, field.index())?, + Downcast(_, variant) => self.project_downcast(base, variant)?, + Deref => self.deref_pointer(&base.to_op(self)?)?.into(), Index(local) => { let layout = self.layout_of(self.tcx.types.usize)?; let n = self.local_to_op(self.frame(), local, Some(layout))?; let n = self.read_target_usize(&n)?; - self.operand_index(base, n)? + self.project_index(base, n)? } ConstantIndex { offset, min_length, from_end } => { - self.operand_constant_index(base, offset, min_length, from_end)? + self.project_constant_index(base, offset, min_length, from_end)? } - Subslice { from, to, from_end } => self.operand_subslice(base, from, to, from_end)?, + Subslice { from, to, from_end } => self.project_subslice(base, from, to, from_end)?, }) } } diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 1e60a1e72ea07..0740894a4ffa6 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -8,28 +8,8 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::{InterpResult, Scalar}; use rustc_middle::ty::layout::LayoutOf; -use super::{ImmTy, InterpCx, Machine}; - -/// Classify whether an operator is "left-homogeneous", i.e., the LHS has the -/// same type as the result. -#[inline] -fn binop_left_homogeneous(op: mir::BinOp) -> bool { - use rustc_middle::mir::BinOp::*; - match op { - Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Offset | Shl | Shr => true, - Eq | Ne | Lt | Le | Gt | Ge => false, - } -} -/// Classify whether an operator is "right-homogeneous", i.e., the RHS has the -/// same type as the LHS. -#[inline] -fn binop_right_homogeneous(op: mir::BinOp) -> bool { - use rustc_middle::mir::BinOp::*; - match op { - Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true, - Offset | Shl | Shr => false, - } -} +use super::{ImmTy, InterpCx, Machine, Projectable}; +use crate::util; impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// Returns `true` as long as there are more things to do. @@ -179,9 +159,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } BinaryOp(bin_op, box (ref left, ref right)) => { - let layout = binop_left_homogeneous(bin_op).then_some(dest.layout); + let layout = util::binop_left_homogeneous(bin_op).then_some(dest.layout); let left = self.read_immediate(&self.eval_operand(left, layout)?)?; - let layout = binop_right_homogeneous(bin_op).then_some(left.layout); + let layout = util::binop_right_homogeneous(bin_op).then_some(left.layout); let right = self.read_immediate(&self.eval_operand(right, layout)?)?; self.binop_ignore_overflow(bin_op, &left, &right, &dest)?; } @@ -189,7 +169,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { CheckedBinaryOp(bin_op, box (ref left, ref right)) => { // Due to the extra boolean in the result, we can never reuse the `dest.layout`. let left = self.read_immediate(&self.eval_operand(left, None)?)?; - let layout = binop_right_homogeneous(bin_op).then_some(left.layout); + let layout = util::binop_right_homogeneous(bin_op).then_some(left.layout); let right = self.read_immediate(&self.eval_operand(right, layout)?)?; self.binop_with_overflow(bin_op, &left, &right, &dest)?; } @@ -198,7 +178,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // The operand always has the same type as the result. let val = self.read_immediate(&self.eval_operand(operand, Some(dest.layout))?)?; let val = self.unary_op(un_op, &val)?; - assert_eq!(val.layout, dest.layout, "layout mismatch for result of {:?}", un_op); + assert_eq!(val.layout, dest.layout, "layout mismatch for result of {un_op:?}"); self.write_immediate(*val, &dest)?; } @@ -217,8 +197,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { self.get_place_alloc_mut(&dest)?; } else { // Write the src to the first element. - let first = self.mplace_field(&dest, 0)?; - self.copy_op(&src, &first.into(), /*allow_transmute*/ false)?; + let first = self.project_index(&dest, 0)?; + self.copy_op(&src, &first, /*allow_transmute*/ false)?; // This is performance-sensitive code for big static/const arrays! So we // avoid writing each operand individually and instead just make many copies @@ -228,13 +208,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let rest_ptr = first_ptr.offset(elem_size, self)?; // For the alignment of `rest_ptr`, we crucially do *not* use `first.align` as // that place might be more aligned than its type mandates (a `u8` array could - // be 4-aligned if it sits at the right spot in a struct). Instead we use - // `first.layout.align`, i.e., the alignment given by the type. + // be 4-aligned if it sits at the right spot in a struct). We have to also factor + // in element size. self.mem_copy_repeatedly( first_ptr, - first.align, + dest.align, rest_ptr, - first.layout.align.abi, + dest.align.restrict_for_offset(elem_size), elem_size, length - 1, /*nonoverlapping:*/ true, @@ -244,8 +224,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { Len(place) => { let src = self.eval_place(place)?; - let op = self.place_to_op(&src)?; - let len = op.len(self)?; + let len = src.len(self)?; self.write_scalar(Scalar::from_target_usize(len, self), &dest)?; } @@ -268,7 +247,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { AddressOf(_, place) => { // Figure out whether this is an addr_of of an already raw place. - let place_base_raw = if place.has_deref() { + let place_base_raw = if place.is_indirect_first_projection() { let ty = self.frame().body.local_decls[place.local].ty; ty.is_unsafe_ptr() } else { @@ -290,12 +269,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let ty = self.subst_from_current_frame_and_normalize_erasing_regions(ty)?; let layout = self.layout_of(ty)?; if let mir::NullOp::SizeOf | mir::NullOp::AlignOf = null_op && layout.is_unsized() { - // FIXME: This should be a span_bug (#80742) - self.tcx.sess.delay_span_bug( + span_bug!( self.frame().current_span(), - format!("{null_op:?} MIR operator called for unsized type {ty}"), + "{null_op:?} MIR operator called for unsized type {ty}", ); - throw_inval!(SizeOfUnsizedType(ty)); } let val = match null_op { mir::NullOp::SizeOf => layout.size.bytes(), @@ -322,8 +299,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { Discriminant(place) => { let op = self.eval_place_to_op(place, None)?; - let discr_val = self.read_discriminant(&op)?.0; - self.write_scalar(discr_val, &dest)?; + let variant = self.read_discriminant(&op)?; + let discr = self.discriminant_for_variant(op.layout, variant)?; + self.write_scalar(discr, &dest)?; } } diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index df3879200101b..3c03172bbeff1 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -1,7 +1,8 @@ use std::borrow::Cow; +use either::Either; use rustc_ast::ast::InlineAsmOptions; -use rustc_middle::ty::layout::{FnAbiOf, LayoutOf}; +use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout}; use rustc_middle::ty::Instance; use rustc_middle::{ mir, @@ -12,11 +13,63 @@ use rustc_target::abi::call::{ArgAbi, ArgAttribute, ArgAttributes, FnAbi, PassMo use rustc_target::spec::abi::Abi; use super::{ - FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, Operand, - PlaceTy, Scalar, StackPopCleanup, + AllocId, FnVal, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, + Operand, PlaceTy, Provenance, Scalar, StackPopCleanup, }; +use crate::fluent_generated as fluent; + +/// An argment passed to a function. +#[derive(Clone, Debug)] +pub enum FnArg<'tcx, Prov: Provenance = AllocId> { + /// Pass a copy of the given operand. + Copy(OpTy<'tcx, Prov>), + /// Allow for the argument to be passed in-place: destroy the value originally stored at that place and + /// make the place inaccessible for the duration of the function call. + InPlace(PlaceTy<'tcx, Prov>), +} + +impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> { + pub fn layout(&self) -> &TyAndLayout<'tcx> { + match self { + FnArg::Copy(op) => &op.layout, + FnArg::InPlace(place) => &place.layout, + } + } +} impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { + /// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the + /// original memory occurs. + pub fn copy_fn_arg( + &self, + arg: &FnArg<'tcx, M::Provenance>, + ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { + match arg { + FnArg::Copy(op) => Ok(op.clone()), + FnArg::InPlace(place) => self.place_to_op(&place), + } + } + + /// Make a copy of the given fn_args. Any `InPlace` are degenerated to copies, no protection of the + /// original memory occurs. + pub fn copy_fn_args( + &self, + args: &[FnArg<'tcx, M::Provenance>], + ) -> InterpResult<'tcx, Vec>> { + args.iter().map(|fn_arg| self.copy_fn_arg(fn_arg)).collect() + } + + pub fn fn_arg_field( + &self, + arg: &FnArg<'tcx, M::Provenance>, + field: usize, + ) -> InterpResult<'tcx, FnArg<'tcx, M::Provenance>> { + Ok(match arg { + FnArg::Copy(op) => FnArg::Copy(self.project_field(op, field)?), + FnArg::InPlace(place) => FnArg::InPlace(self.project_field(place, field)?), + }) + } + pub(super) fn eval_terminator( &mut self, terminator: &mir::Terminator<'tcx>, @@ -61,20 +114,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { destination, target, unwind, - from_hir_call: _, + call_source: _, fn_span: _, } => { let old_stack = self.frame_idx(); let old_loc = self.frame().loc; let func = self.eval_operand(func, None)?; - let args = self.eval_operands(args)?; + let args = self.eval_fn_call_arguments(args)?; let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder); let extra_args = &args[fn_sig.inputs().len()..]; let extra_args = - self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout.ty)); + self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty)); let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() { ty::FnPtr(_sig) => { @@ -82,8 +135,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let fn_val = self.get_ptr_fn(fn_ptr)?; (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false) } - ty::FnDef(def_id, substs) => { - let instance = self.resolve(def_id, substs)?; + ty::FnDef(def_id, args) => { + let instance = self.resolve(def_id, args)?; ( FnVal::Instance(instance), self.fn_abi_of_instance(instance, extra_args)?, @@ -114,7 +167,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } - Drop { place, target, unwind } => { + Drop { place, target, unwind, replace: _ } => { let frame = self.frame(); let ty = place.ty(&frame.body.local_decls, *self.tcx).ty; let ty = self.subst_from_frame_and_normalize_erasing_regions(frame, ty)?; @@ -172,7 +225,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { InlineAsm { template, ref operands, options, destination, .. } => { M::eval_inline_asm(self, template, operands, options)?; if options.contains(InlineAsmOptions::NORETURN) { - throw_ub_format!("returned from noreturn inline assembly"); + throw_ub_custom!(fluent::const_eval_noreturn_asm_returned); } self.go_to_block( destination @@ -184,6 +237,21 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { Ok(()) } + /// Evaluate the arguments of a function call + pub(super) fn eval_fn_call_arguments( + &self, + ops: &[mir::Operand<'tcx>], + ) -> InterpResult<'tcx, Vec>> { + ops.iter() + .map(|op| { + Ok(match op { + mir::Operand::Move(place) => FnArg::InPlace(self.eval_place(*place)?), + _ => FnArg::Copy(self.eval_operand(op, None)?), + }) + }) + .collect() + } + fn check_argument_compat( caller_abi: &ArgAbi<'tcx, Ty<'tcx>>, callee_abi: &ArgAbi<'tcx, Ty<'tcx>>, @@ -274,7 +342,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { fn pass_argument<'x, 'y>( &mut self, caller_args: &mut impl Iterator< - Item = (&'x OpTy<'tcx, M::Provenance>, &'y ArgAbi<'tcx, Ty<'tcx>>), + Item = (&'x FnArg<'tcx, M::Provenance>, &'y ArgAbi<'tcx, Ty<'tcx>>), >, callee_abi: &ArgAbi<'tcx, Ty<'tcx>>, callee_arg: &PlaceTy<'tcx, M::Provenance>, @@ -288,39 +356,44 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { return Ok(()); } // Find next caller arg. - let (caller_arg, caller_abi) = caller_args.next().ok_or_else(|| { - err_ub_format!("calling a function with fewer arguments than it requires") - })?; + let Some((caller_arg, caller_abi)) = caller_args.next() else { + throw_ub_custom!(fluent::const_eval_not_enough_caller_args); + }; // Now, check if !Self::check_argument_compat(caller_abi, callee_abi) { - throw_ub_format!( - "calling a function with argument of type {:?} passing data of type {:?}", - callee_arg.layout.ty, - caller_arg.layout.ty + let callee_ty = format!("{}", callee_arg.layout.ty); + let caller_ty = format!("{}", caller_arg.layout().ty); + throw_ub_custom!( + fluent::const_eval_incompatible_types, + callee_ty = callee_ty, + caller_ty = caller_ty, ) } + // We work with a copy of the argument for now; if this is in-place argument passing, we + // will later protect the source it comes from. This means the callee cannot observe if we + // did in-place of by-copy argument passing, except for pointer equality tests. + let caller_arg_copy = self.copy_fn_arg(&caller_arg)?; // Special handling for unsized parameters. - if caller_arg.layout.is_unsized() { + if caller_arg_copy.layout.is_unsized() { // `check_argument_compat` ensures that both have the same type, so we know they will use the metadata the same way. - assert_eq!(caller_arg.layout.ty, callee_arg.layout.ty); + assert_eq!(caller_arg_copy.layout.ty, callee_arg.layout.ty); // We have to properly pre-allocate the memory for the callee. - // So let's tear down some wrappers. + // So let's tear down some abstractions. // This all has to be in memory, there are no immediate unsized values. - let src = caller_arg.assert_mem_place(); + let src = caller_arg_copy.assert_mem_place(); // The destination cannot be one of these "spread args". - let (dest_frame, dest_local) = callee_arg.assert_local(); + let (dest_frame, dest_local, dest_offset) = callee_arg + .as_mplace_or_local() + .right() + .expect("callee fn arguments must be locals"); // We are just initializing things, so there can't be anything here yet. assert!(matches!( *self.local_to_op(&self.stack()[dest_frame], dest_local, None)?, Operand::Immediate(Immediate::Uninit) )); + assert_eq!(dest_offset, None); // Allocate enough memory to hold `src`. - let Some((size, align)) = self.size_and_align_of_mplace(&src)? else { - span_bug!(self.cur_span(), "unsized fn arg with `extern` type tail should not be allowed") - }; - let ptr = self.allocate_ptr(size, align, MemoryKind::Stack)?; - let dest_place = - MPlaceTy::from_aligned_ptr_with_meta(ptr.into(), callee_arg.layout, src.meta); + let dest_place = self.allocate_dyn(src.layout, MemoryKind::Stack, src.meta)?; // Update the local to be that new place. *M::access_local_mut(self, dest_frame, dest_local)? = Operand::Indirect(*dest_place); } @@ -328,7 +401,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // FIXME: Depending on the PassMode, this should reset some padding to uninitialized. (This // is true for all `copy_op`, but there are a lot of special cases for argument passing // specifically.) - self.copy_op(&caller_arg, callee_arg, /*allow_transmute*/ true) + self.copy_op(&caller_arg_copy, callee_arg, /*allow_transmute*/ true)?; + // If this was an in-place pass, protect the place it comes from for the duration of the call. + if let FnArg::InPlace(place) = caller_arg { + M::protect_in_place_function_argument(self, place)?; + } + Ok(()) } /// Call this function -- pushing the stack frame and initializing the arguments. @@ -343,7 +421,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { &mut self, fn_val: FnVal<'tcx, M::ExtraFnVal>, (caller_abi, caller_fn_abi): (Abi, &FnAbi<'tcx, Ty<'tcx>>), - args: &[OpTy<'tcx, M::Provenance>], + args: &[FnArg<'tcx, M::Provenance>], with_caller_location: bool, destination: &PlaceTy<'tcx, M::Provenance>, target: Option, @@ -369,8 +447,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { match instance.def { ty::InstanceDef::Intrinsic(def_id) => { assert!(self.tcx.is_intrinsic(def_id)); - // caller_fn_abi is not relevant here, we interpret the arguments directly for each intrinsic. - M::call_intrinsic(self, instance, args, destination, target, unwind) + // FIXME: Should `InPlace` arguments be reset to uninit? + M::call_intrinsic( + self, + instance, + &self.copy_fn_args(args)?, + destination, + target, + unwind, + ) } ty::InstanceDef::VTableShim(..) | ty::InstanceDef::ReifyShim(..) @@ -382,10 +467,18 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { | ty::InstanceDef::ThreadLocalShim(..) | ty::InstanceDef::Item(_) => { // We need MIR for this fn - let Some((body, instance)) = - M::find_mir_or_eval_fn(self, instance, caller_abi, args, destination, target, unwind)? else { - return Ok(()); - }; + let Some((body, instance)) = M::find_mir_or_eval_fn( + self, + instance, + caller_abi, + args, + destination, + target, + unwind, + )? + else { + return Ok(()); + }; // Compute callee information using the `instance` returned by // `find_mir_or_eval_fn`. @@ -398,14 +491,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { if M::enforce_abi(self) { if caller_fn_abi.conv != callee_fn_abi.conv { - throw_ub_format!( - "calling a function with calling convention {:?} using calling convention {:?}", - callee_fn_abi.conv, - caller_fn_abi.conv + throw_ub_custom!( + fluent::const_eval_incompatible_calling_conventions, + callee_conv = format!("{:?}", callee_fn_abi.conv), + caller_conv = format!("{:?}", caller_fn_abi.conv), ) } } + // Check that all target features required by the callee (i.e., from + // the attribute `#[target_feature(enable = ...)]`) are enabled at + // compile time. + self.check_fn_target_features(instance)?; + if !callee_fn_abi.can_unwind { // The callee cannot unwind, so force the `Unreachable` unwind handling. unwind = mir::UnwindAction::Unreachable; @@ -425,7 +523,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { "caller ABI: {:?}, args: {:#?}", caller_abi, args.iter() - .map(|arg| (arg.layout.ty, format!("{:?}", **arg))) + .map(|arg| ( + arg.layout().ty, + match arg { + FnArg::Copy(op) => format!("copy({:?})", *op), + FnArg::InPlace(place) => format!("in-place({:?})", *place), + } + )) .collect::>() ); trace!( @@ -446,7 +550,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // last incoming argument. These two iterators do not have the same type, // so to keep the code paths uniform we accept an allocation // (for RustCall ABI only). - let caller_args: Cow<'_, [OpTy<'tcx, M::Provenance>]> = + let caller_args: Cow<'_, [FnArg<'tcx, M::Provenance>]> = if caller_abi == Abi::RustCall && !args.is_empty() { // Untuple let (untuple_arg, args) = args.split_last().unwrap(); @@ -455,11 +559,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { args.iter() .map(|a| Ok(a.clone())) .chain( - (0..untuple_arg.layout.fields.count()) - .map(|i| self.operand_field(untuple_arg, i)), + (0..untuple_arg.layout().fields.count()) + .map(|i| self.fn_arg_field(untuple_arg, i)), ) - .collect::>>>( - )?, + .collect::>>()?, ) } else { // Plain arg passing @@ -488,7 +591,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { if Some(local) == body.spread_arg { // Must be a tuple for i in 0..dest.layout.fields.count() { - let dest = self.place_field(&dest, i)?; + let dest = self.project_field(&dest, i)?; let callee_abi = callee_args_abis.next().unwrap(); self.pass_argument(&mut caller_args, callee_abi, &dest)?; } @@ -508,17 +611,26 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { "mismatch between callee ABI and callee body arguments" ); if caller_args.next().is_some() { - throw_ub_format!("calling a function with more arguments than it expected") + throw_ub_custom!(fluent::const_eval_too_many_caller_args); } // Don't forget to check the return type! if !Self::check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret) { - throw_ub_format!( - "calling a function with return type {:?} passing \ - return place of type {:?}", - callee_fn_abi.ret.layout.ty, - caller_fn_abi.ret.layout.ty, + let callee_ty = format!("{}", callee_fn_abi.ret.layout.ty); + let caller_ty = format!("{}", caller_fn_abi.ret.layout.ty); + throw_ub_custom!( + fluent::const_eval_incompatible_return_types, + callee_ty = callee_ty, + caller_ty = caller_ty, ) } + // Ensure the return place is aligned and dereferenceable, and protect it for + // in-place return value passing. + if let Either::Left(mplace) = destination.as_mplace_or_local() { + self.check_mplace(&mplace)?; + } else { + // Nothing to do for locals, they are always properly allocated and aligned. + } + M::protect_in_place_function_argument(self, destination)?; }; match res { Err(err) => { @@ -534,11 +646,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // We have to implement all "object safe receivers". So we have to go search for a // pointer or `dyn Trait` type, but it could be wrapped in newtypes. So recursively // unwrap those newtypes until we are there. - let mut receiver = args[0].clone(); + // An `InPlace` does nothing here, we keep the original receiver intact. We can't + // really pass the argument in-place anyway, and we are constructing a new + // `Immediate` receiver. + let mut receiver = self.copy_fn_arg(&args[0])?; let receiver_place = loop { match receiver.layout.ty.kind() { ty::Ref(..) | ty::RawPtr(..) => { - // We do *not* use `deref_operand` here: we don't want to conceptually + // We do *not* use `deref_pointer` here: we don't want to conceptually // create a place that must be dereferenceable, since the receiver might // be a raw pointer and (for `*const dyn Trait`) we don't need to // actually access memory to resolve this method. @@ -558,7 +673,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Not there yet, search for the only non-ZST field. let mut non_zst_field = None; for i in 0..receiver.layout.fields.count() { - let field = self.operand_field(&receiver, i)?; + let field = self.project_field(&receiver, i)?; let zst = field.layout.is_zst() && field.layout.align.abi.bytes() == 1; if !zst { @@ -584,14 +699,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let (vptr, dyn_ty, adjusted_receiver) = if let ty::Dynamic(data, _, ty::DynStar) = receiver_place.layout.ty.kind() { - let (recv, vptr) = self.unpack_dyn_star(&receiver_place.into())?; + let (recv, vptr) = self.unpack_dyn_star(&receiver_place)?; let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?; if dyn_trait != data.principal() { - throw_ub_format!( - "`dyn*` call on a pointer whose vtable does not match its type" - ); + throw_ub_custom!(fluent::const_eval_dyn_star_call_vtable_mismatch); } - let recv = recv.assert_mem_place(); // we passed an MPlaceTy to `unpack_dyn_star` so we definitely still have one (vptr, dyn_ty, recv.ptr) } else { @@ -601,17 +713,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { .tcx .struct_tail_erasing_lifetimes(receiver_place.layout.ty, self.param_env); let ty::Dynamic(data, _, ty::Dyn) = receiver_tail.kind() else { - span_bug!(self.cur_span(), "dynamic call on non-`dyn` type {}", receiver_tail) - }; + span_bug!( + self.cur_span(), + "dynamic call on non-`dyn` type {}", + receiver_tail + ) + }; assert!(receiver_place.layout.is_unsized()); // Get the required information from the vtable. let vptr = receiver_place.meta.unwrap_meta().to_pointer(self)?; let (dyn_ty, dyn_trait) = self.get_ptr_vtable(vptr)?; if dyn_trait != data.principal() { - throw_ub_format!( - "`dyn` call on a pointer whose vtable does not match its type" - ); + throw_ub_custom!(fluent::const_eval_dyn_call_vtable_mismatch); } // It might be surprising that we use a pointer as the receiver even if this @@ -622,8 +736,11 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Now determine the actual method to call. We can do that in two different ways and // compare them to ensure everything fits. - let Some(ty::VtblEntry::Method(fn_inst)) = self.get_vtable_entries(vptr)?.get(idx).copied() else { - throw_ub_format!("`dyn` call trying to call something that is not a method") + let Some(ty::VtblEntry::Method(fn_inst)) = + self.get_vtable_entries(vptr)?.get(idx).copied() + else { + // FIXME(fee1-dead) these could be variants of the UB info enum instead of this + throw_ub_custom!(fluent::const_eval_dyn_call_not_a_method); }; trace!("Virtual call dispatches to {fn_inst:#?}"); if cfg!(debug_assertions) { @@ -631,7 +748,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let trait_def_id = tcx.trait_of_item(def_id).unwrap(); let virtual_trait_ref = - ty::TraitRef::from_method(tcx, trait_def_id, instance.substs); + ty::TraitRef::from_method(tcx, trait_def_id, instance.args); let existential_trait_ref = ty::ExistentialTraitRef::erase_self_ty(tcx, virtual_trait_ref); let concrete_trait_ref = existential_trait_ref.with_self_ty(tcx, dyn_ty); @@ -640,18 +757,20 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { tcx, self.param_env, def_id, - instance.substs.rebase_onto(tcx, trait_def_id, concrete_trait_ref.substs), + instance.args.rebase_onto(tcx, trait_def_id, concrete_trait_ref.args), ) .unwrap(); assert_eq!(fn_inst, concrete_method); } // Adjust receiver argument. Layout can be any (thin) ptr. - args[0] = ImmTy::from_immediate( - Scalar::from_maybe_pointer(adjusted_receiver, self).into(), - self.layout_of(self.tcx.mk_mut_ptr(dyn_ty))?, - ) - .into(); + args[0] = FnArg::Copy( + ImmTy::from_immediate( + Scalar::from_maybe_pointer(adjusted_receiver, self).into(), + self.layout_of(Ty::new_mut_ptr(self.tcx.tcx, dyn_ty))?, + ) + .into(), + ); trace!("Patched receiver operand to {:#?}", args[0]); // recurse with concrete function self.eval_fn_call( @@ -667,6 +786,31 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } + fn check_fn_target_features(&self, instance: ty::Instance<'tcx>) -> InterpResult<'tcx, ()> { + let attrs = self.tcx.codegen_fn_attrs(instance.def_id()); + if attrs + .target_features + .iter() + .any(|feature| !self.tcx.sess.target_features.contains(feature)) + { + throw_ub_custom!( + fluent::const_eval_unavailable_target_features_for_fn, + unavailable_feats = attrs + .target_features + .iter() + .filter(|&feature| !self.tcx.sess.target_features.contains(feature)) + .fold(String::new(), |mut s, feature| { + if !s.is_empty() { + s.push_str(", "); + } + s.push_str(feature.as_str()); + s + }), + ); + } + Ok(()) + } + fn drop_in_place( &mut self, place: &PlaceTy<'tcx, M::Provenance>, @@ -687,7 +831,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } ty::Dynamic(_, _, ty::DynStar) => { // Dropping a `dyn*`. Need to find actual drop fn. - self.unpack_dyn_star(&place.into())?.0.assert_mem_place() + self.unpack_dyn_star(&place)?.0 } _ => { debug_assert_eq!( @@ -700,16 +844,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let instance = ty::Instance::resolve_drop_in_place(*self.tcx, place.layout.ty); let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty())?; - let arg = ImmTy::from_immediate( - place.to_ref(self), - self.layout_of(self.tcx.mk_mut_ptr(place.layout.ty))?, - ); + let arg = self.mplace_to_ref(&place)?; let ret = MPlaceTy::fake_alloc_zst(self.layout_of(self.tcx.types.unit)?); self.eval_fn_call( FnVal::Instance(instance), (Abi::Rust, fn_abi), - &[arg.into()], + &[FnArg::Copy(arg.into())], false, &ret.into(), Some(target), diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index 22bdd4d2c3fbc..b33194423887c 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -33,12 +33,12 @@ where match *ty.kind() { ty::Param(_) => ControlFlow::Break(FoundParam), - ty::Closure(def_id, substs) - | ty::Generator(def_id, substs, ..) - | ty::FnDef(def_id, substs) => { + ty::Closure(def_id, args) + | ty::Generator(def_id, args, ..) + | ty::FnDef(def_id, args) => { let instance = ty::InstanceDef::Item(def_id); let unused_params = self.tcx.unused_generic_params(instance); - for (index, subst) in substs.into_iter().enumerate() { + for (index, subst) in args.into_iter().enumerate() { let index = index .try_into() .expect("more generic parameters than can fit into a `u32`"); diff --git a/compiler/rustc_const_eval/src/interpret/validity.rs b/compiler/rustc_const_eval/src/interpret/validity.rs index 01b77289937fb..d3f05af1c72c3 100644 --- a/compiler/rustc_const_eval/src/interpret/validity.rs +++ b/compiler/rustc_const_eval/src/interpret/validity.rs @@ -4,7 +4,7 @@ //! That's useful because it means other passes (e.g. promotion) can rely on `const`s //! to be const-safe. -use std::fmt::{Display, Write}; +use std::fmt::Write; use std::num::NonZeroUsize; use either::{Left, Right}; @@ -12,7 +12,10 @@ use either::{Left, Right}; use rustc_ast::Mutability; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; -use rustc_middle::mir::interpret::InterpError; +use rustc_middle::mir::interpret::{ + ExpectedKind, InterpError, InvalidMetaKind, PointerKind, ValidationErrorInfo, + ValidationErrorKind, ValidationErrorKind::*, +}; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_span::symbol::{sym, Symbol}; @@ -22,22 +25,19 @@ use rustc_target::abi::{ use std::hash::Hash; -// for the validation errors -use super::UndefinedBehaviorInfo::*; use super::{ AllocId, CheckInAllocMsg, GlobalAlloc, ImmTy, Immediate, InterpCx, InterpResult, MPlaceTy, - Machine, MemPlaceMeta, OpTy, Pointer, Scalar, ValueVisitor, + Machine, MemPlaceMeta, OpTy, Pointer, Projectable, Scalar, ValueVisitor, }; +// for the validation errors +use super::InterpError::UndefinedBehavior as Ub; +use super::InterpError::Unsupported as Unsup; +use super::UndefinedBehaviorInfo::*; +use super::UnsupportedOpInfo::*; + macro_rules! throw_validation_failure { - ($where:expr, { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )?) => {{ - let mut msg = String::new(); - msg.push_str("encountered "); - write!(&mut msg, $($what_fmt)*).unwrap(); - $( - msg.push_str(", but expected "); - write!(&mut msg, $($expected_fmt)*).unwrap(); - )? + ($where:expr, $kind: expr) => {{ let where_ = &$where; let path = if !where_.is_empty() { let mut path = String::new(); @@ -46,7 +46,8 @@ macro_rules! throw_validation_failure { } else { None }; - throw_ub!(ValidationFailure { path, msg }) + + throw_ub!(ValidationError(ValidationErrorInfo { path, kind: $kind })) }}; } @@ -82,7 +83,7 @@ macro_rules! throw_validation_failure { /// macro_rules! try_validation { ($e:expr, $where:expr, - $( $( $p:pat_param )|+ => { $( $what_fmt:tt )* } $( expected { $( $expected_fmt:tt )* } )? ),+ $(,)? + $( $( $p:pat_param )|+ => $kind: expr ),+ $(,)? ) => {{ match $e { Ok(x) => x, @@ -90,10 +91,10 @@ macro_rules! try_validation { // allocation here as this can only slow down builds that fail anyway. Err(e) => match e.kind() { $( - InterpError::UndefinedBehavior($($p)|+) => + $($p)|+ => throw_validation_failure!( $where, - { $( $what_fmt )* } $( expected { $( $expected_fmt )* } )? + $kind ) ),+, #[allow(unreachable_patterns)] @@ -139,19 +140,19 @@ pub struct RefTracking { pub todo: Vec<(T, PATH)>, } -impl RefTracking { +impl RefTracking { pub fn empty() -> Self { RefTracking { seen: FxHashSet::default(), todo: vec![] } } pub fn new(op: T) -> Self { let mut ref_tracking_for_consts = - RefTracking { seen: FxHashSet::default(), todo: vec![(op, PATH::default())] }; + RefTracking { seen: FxHashSet::default(), todo: vec![(op.clone(), PATH::default())] }; ref_tracking_for_consts.seen.insert(op); ref_tracking_for_consts } pub fn track(&mut self, op: T, path: impl FnOnce() -> PATH) { - if self.seen.insert(op) { + if self.seen.insert(op.clone()) { trace!("Recursing below ptr {:#?}", op); let path = path(); // Remember to come back to this later. @@ -160,20 +161,21 @@ impl RefTracking } } +// FIXME make this translatable as well? /// Format a path fn write_path(out: &mut String, path: &[PathElem]) { use self::PathElem::*; for elem in path.iter() { match elem { - Field(name) => write!(out, ".{}", name), + Field(name) => write!(out, ".{name}"), EnumTag => write!(out, "."), - Variant(name) => write!(out, ".", name), + Variant(name) => write!(out, "."), GeneratorTag => write!(out, "."), GeneratorState(idx) => write!(out, ".", idx.index()), - CapturedVar(name) => write!(out, ".", name), - TupleElem(idx) => write!(out, ".{}", idx), - ArrayElem(idx) => write!(out, "[{}]", idx), + CapturedVar(name) => write!(out, "."), + TupleElem(idx) => write!(out, ".{idx}"), + ArrayElem(idx) => write!(out, "[{idx}]"), // `.` does not match Rust syntax, but it is more readable for long paths -- and // some of the other items here also are not Rust syntax. Actually we can't // even use the usual syntax because we are just showing the projections, @@ -185,26 +187,6 @@ fn write_path(out: &mut String, path: &[PathElem]) { } } -// Formats such that a sentence like "expected something {}" to mean -// "expected something " makes sense. -fn wrapping_range_format(r: WrappingRange, max_hi: u128) -> String { - let WrappingRange { start: lo, end: hi } = r; - assert!(hi <= max_hi); - if lo > hi { - format!("less or equal to {}, or greater or equal to {}", hi, lo) - } else if lo == hi { - format!("equal to {}", lo) - } else if lo == 0 { - assert!(hi < max_hi, "should not be printing if the range covers everything"); - format!("less or equal to {}", hi) - } else if hi == max_hi { - assert!(lo > 0, "should not be printing if the range covers everything"); - format!("greater or equal to {}", lo) - } else { - format!("in the range {:?}", r) - } -} - struct ValidityVisitor<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> { /// The `path` may be pushed to, but the part that is present when a function /// starts must not be changed! `visit_fields` and `visit_array` rely on @@ -311,19 +293,25 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' fn read_immediate( &self, op: &OpTy<'tcx, M::Provenance>, - expected: impl Display, + expected: ExpectedKind, ) -> InterpResult<'tcx, ImmTy<'tcx, M::Provenance>> { Ok(try_validation!( self.ecx.read_immediate(op), self.path, - InvalidUninitBytes(None) => { "uninitialized memory" } expected { "{expected}" } + Ub(InvalidUninitBytes(None)) => + Uninit { expected }, + // The `Unsup` cases can only occur during CTFE + Unsup(ReadPointerAsInt(_)) => + PointerAsInt { expected }, + Unsup(ReadPartialPointer(_)) => + PartialPointer, )) } fn read_scalar( &self, op: &OpTy<'tcx, M::Provenance>, - expected: impl Display, + expected: ExpectedKind, ) -> InterpResult<'tcx, Scalar> { Ok(self.read_immediate(op, expected)?.to_scalar()) } @@ -341,9 +329,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' let (_ty, _trait) = try_validation!( self.ecx.get_ptr_vtable(vtable), self.path, - DanglingIntPointer(..) | - InvalidVTablePointer(..) => - { "{vtable}" } expected { "a vtable pointer" }, + Ub(DanglingIntPointer(..) | InvalidVTablePointer(..)) => + InvalidVTablePtr { value: format!("{vtable}") } ); // FIXME: check if the type/trait match what ty::Dynamic says? } @@ -366,10 +353,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' fn check_safe_pointer( &mut self, value: &OpTy<'tcx, M::Provenance>, - kind: &str, + ptr_kind: PointerKind, ) -> InterpResult<'tcx> { - let place = - self.ecx.ref_to_mplace(&self.read_immediate(value, format_args!("a {kind}"))?)?; + // Not using `deref_pointer` since we do the dereferenceable check ourselves below. + let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?; // Handle wide pointers. // Check metadata early, for better diagnostics if place.layout.is_unsized() { @@ -379,7 +366,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' let size_and_align = try_validation!( self.ecx.size_and_align_of_mplace(&place), self.path, - InvalidMeta(msg) => { "invalid {} metadata: {}", kind, msg }, + Ub(InvalidMeta(msg)) => match msg { + InvalidMetaKind::SliceTooBig => InvalidMetaSliceTooLarge { ptr_kind }, + InvalidMetaKind::TooBig => InvalidMetaTooLarge { ptr_kind }, + } ); let (size, align) = size_and_align // for the purpose of validity, consider foreign types to have @@ -395,31 +385,30 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message ), self.path, - AlignmentCheckFailed { required, has } => - { - "an unaligned {kind} (required {} byte alignment but found {})", - required.bytes(), - has.bytes(), - }, - DanglingIntPointer(0, _) => - { "a null {kind}" }, - DanglingIntPointer(i, _) => - { - "a dangling {kind} ({pointer} has no provenance)", - pointer = Pointer::>::from_addr_invalid(*i), - }, - PointerOutOfBounds { .. } => - { "a dangling {kind} (going beyond the bounds of its allocation)" }, + Ub(AlignmentCheckFailed { required, has }) => UnalignedPtr { + ptr_kind, + required_bytes: required.bytes(), + found_bytes: has.bytes() + }, + Ub(DanglingIntPointer(0, _)) => NullPtr { ptr_kind }, + Ub(DanglingIntPointer(i, _)) => DanglingPtrNoProvenance { + ptr_kind, + // FIXME this says "null pointer" when null but we need translate + pointer: format!("{}", Pointer::>::from_addr_invalid(*i)) + }, + Ub(PointerOutOfBounds { .. }) => DanglingPtrOutOfBounds { + ptr_kind + }, // This cannot happen during const-eval (because interning already detects // dangling pointers), but it can happen in Miri. - PointerUseAfterFree(..) => - { "a dangling {kind} (use-after-free)" }, + Ub(PointerUseAfterFree(..)) => DanglingPtrUseAfterFree { + ptr_kind, + }, ); // Do not allow pointers to uninhabited types. if place.layout.abi.is_uninhabited() { - throw_validation_failure!(self.path, - { "a {kind} pointing to uninhabited type {}", place.layout.ty } - ) + let ty = place.layout.ty; + throw_validation_failure!(self.path, PtrToUninhabited { ptr_kind, ty }) } // Recursive checking if let Some(ref_tracking) = self.ref_tracking.as_deref_mut() { @@ -441,9 +430,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // this check is so important. // This check is reachable when the const just referenced the static, // but never read it (so we never entered `before_access_global`). - throw_validation_failure!(self.path, - { "a {} pointing to a static variable in a constant", kind } - ); + throw_validation_failure!(self.path, PtrToStatic { ptr_kind }); } // We skip recursively checking other statics. These statics must be sound by // themselves, and the only way to get broken statics here is by using @@ -464,9 +451,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // This should be unreachable, but if someone manages to copy a pointer // out of a `static`, then that pointer might point to mutable memory, // and we would catch that here. - throw_validation_failure!(self.path, - { "a {} pointing to mutable memory in a constant", kind } - ); + throw_validation_failure!(self.path, PtrToMut { ptr_kind }); } } // Nothing to check for these. @@ -488,6 +473,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' /// Check if this is a value of primitive type, and if yes check the validity of the value /// at that type. Return `true` if the type is indeed primitive. + /// + /// Note that not all of these have `FieldsShape::Primitive`, e.g. wide references. fn try_visit_primitive( &mut self, value: &OpTy<'tcx, M::Provenance>, @@ -496,51 +483,43 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' let ty = value.layout.ty; match ty.kind() { ty::Bool => { - let value = self.read_scalar(value, "a boolean")?; + let value = self.read_scalar(value, ExpectedKind::Bool)?; try_validation!( value.to_bool(), self.path, - InvalidBool(..) => - { "{:x}", value } expected { "a boolean" }, + Ub(InvalidBool(..)) => ValidationErrorKind::InvalidBool { + value: format!("{value:x}"), + } ); Ok(true) } ty::Char => { - let value = self.read_scalar(value, "a unicode scalar value")?; + let value = self.read_scalar(value, ExpectedKind::Char)?; try_validation!( value.to_char(), self.path, - InvalidChar(..) => - { "{:x}", value } expected { "a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)" }, + Ub(InvalidChar(..)) => ValidationErrorKind::InvalidChar { + value: format!("{value:x}"), + } ); Ok(true) } ty::Float(_) | ty::Int(_) | ty::Uint(_) => { // NOTE: Keep this in sync with the array optimization for int/float // types below! - let value = self.read_scalar( + self.read_scalar( value, if matches!(ty.kind(), ty::Float(..)) { - "a floating point number" + ExpectedKind::Float } else { - "an integer" + ExpectedKind::Int }, )?; - // As a special exception we *do* match on a `Scalar` here, since we truly want - // to know its underlying representation (and *not* cast it to an integer). - if matches!(value, Scalar::Ptr(..)) { - throw_validation_failure!(self.path, - { "{:x}", value } expected { "plain (non-pointer) bytes" } - ) - } Ok(true) } ty::RawPtr(..) => { - // We are conservative with uninit for integers, but try to - // actually enforce the strict rules for raw pointers (mostly because - // that lets us re-use `ref_to_mplace`). let place = - self.ecx.ref_to_mplace(&self.read_immediate(value, "a raw pointer")?)?; + self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?; if place.layout.is_unsized() { self.check_wide_ptr_meta(place.meta, place.layout)?; } @@ -554,14 +533,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' // a ZST). let layout = self.ecx.layout_of(*ty)?; if !layout.is_zst() { - throw_validation_failure!(self.path, { "mutable reference in a `const`" }); + throw_validation_failure!(self.path, MutableRefInConst); } } - self.check_safe_pointer(value, "reference")?; + self.check_safe_pointer(value, PointerKind::Ref)?; Ok(true) } ty::FnPtr(_sig) => { - let value = self.read_scalar(value, "a function pointer")?; + let value = self.read_scalar(value, ExpectedKind::FnPtr)?; // If we check references recursively, also check that this points to a function. if let Some(_) = self.ref_tracking { @@ -569,20 +548,19 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' let _fn = try_validation!( self.ecx.get_ptr_fn(ptr), self.path, - DanglingIntPointer(..) | - InvalidFunctionPointer(..) => - { "{ptr}" } expected { "a function pointer" }, + Ub(DanglingIntPointer(..) | InvalidFunctionPointer(..)) => + InvalidFnPtr { value: format!("{ptr}") }, ); // FIXME: Check if the signature matches } else { // Otherwise (for standalone Miri), we have to still check it to be non-null. if self.ecx.scalar_may_be_null(value)? { - throw_validation_failure!(self.path, { "a null function pointer" }); + throw_validation_failure!(self.path, NullFnPtr); } } Ok(true) } - ty::Never => throw_validation_failure!(self.path, { "a value of the never type `!`" }), + ty::Never => throw_validation_failure!(self.path, NeverVal), ty::Foreign(..) | ty::FnDef(..) => { // Nothing to check. Ok(true) @@ -629,12 +607,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' if start == 1 && end == max_value { // Only null is the niche. So make sure the ptr is NOT null. if self.ecx.scalar_may_be_null(scalar)? { - throw_validation_failure!(self.path, - { "a potentially null pointer" } - expected { - "something that cannot possibly fail to be {}", - wrapping_range_format(valid_range, max_value) - } + throw_validation_failure!( + self.path, + NullablePtrOutOfRange { range: valid_range, max_value } ) } else { return Ok(()); @@ -645,12 +620,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' } else { // Conservatively, we reject, because the pointer *could* have a bad // value. - throw_validation_failure!(self.path, - { "a pointer" } - expected { - "something that cannot possibly fail to be {}", - wrapping_range_format(valid_range, max_value) - } + throw_validation_failure!( + self.path, + PtrOutOfRange { range: valid_range, max_value } ) } } @@ -659,9 +631,9 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' if valid_range.contains(bits) { Ok(()) } else { - throw_validation_failure!(self.path, - { "{}", bits } - expected { "something {}", wrapping_range_format(valid_range, max_value) } + throw_validation_failure!( + self.path, + OutOfRange { value: format!("{bits}"), range: valid_range, max_value } ) } } @@ -685,12 +657,13 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> Ok(try_validation!( this.ecx.read_discriminant(op), this.path, - InvalidTag(val) => - { "{:x}", val } expected { "a valid enum tag" }, - InvalidUninitBytes(None) => - { "uninitialized bytes" } expected { "a valid enum tag" }, - ) - .1) + Ub(InvalidTag(val)) => InvalidEnumTag { + value: format!("{val:x}"), + }, + Ub(UninhabitedEnumVariantRead(_)) => UninhabitedEnumVariant, + // Uninit / bad provenance are not possible since the field was already previously + // checked at its integer type. + )) }) } @@ -730,7 +703,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> // Special check preventing `UnsafeCell` inside unions in the inner part of constants. if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) { if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) { - throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" }); + throw_validation_failure!(self.path, UnsafeCell); } } Ok(()) @@ -738,7 +711,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> #[inline] fn visit_box(&mut self, op: &OpTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { - self.check_safe_pointer(op, "box")?; + self.check_safe_pointer(op, PointerKind::Box)?; Ok(()) } @@ -756,65 +729,11 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) && def.is_unsafe_cell() { - throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" }); - } - } - - // Recursively walk the value at its type. - self.walk_value(op)?; - - // *After* all of this, check the ABI. We need to check the ABI to handle - // types like `NonNull` where the `Scalar` info is more restrictive than what - // the fields say (`rustc_layout_scalar_valid_range_start`). - // But in most cases, this will just propagate what the fields say, - // and then we want the error to point at the field -- so, first recurse, - // then check ABI. - // - // FIXME: We could avoid some redundant checks here. For newtypes wrapping - // scalars, we do the same check on every "level" (e.g., first we check - // MyNewtype and then the scalar in there). - match op.layout.abi { - Abi::Uninhabited => { - throw_validation_failure!(self.path, - { "a value of uninhabited type {:?}", op.layout.ty } - ); - } - Abi::Scalar(scalar_layout) => { - if !scalar_layout.is_uninit_valid() { - // There is something to check here. - let scalar = self.read_scalar(op, "initialized scalar value")?; - self.visit_scalar(scalar, scalar_layout)?; - } - } - Abi::ScalarPair(a_layout, b_layout) => { - // We can only proceed if *both* scalars need to be initialized. - // FIXME: find a way to also check ScalarPair when one side can be uninit but - // the other must be init. - if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() { - let (a, b) = - self.read_immediate(op, "initialized scalar value")?.to_scalar_pair(); - self.visit_scalar(a, a_layout)?; - self.visit_scalar(b, b_layout)?; - } - } - Abi::Vector { .. } => { - // No checks here, we assume layout computation gets this right. - // (This is harder to check since Miri does not represent these as `Immediate`. We - // also cannot use field projections since this might be a newtype around a vector.) - } - Abi::Aggregate { .. } => { - // Nothing to do. + throw_validation_failure!(self.path, UnsafeCell); } } - Ok(()) - } - - fn visit_aggregate( - &mut self, - op: &OpTy<'tcx, M::Provenance>, - fields: impl Iterator>, - ) -> InterpResult<'tcx> { + // Recursively walk the value at its type. Apply optimizations for some large types. match op.layout.ty.kind() { ty::Str => { let mplace = op.assert_mem_place(); // strings are unsized and hence never immediate @@ -822,7 +741,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> try_validation!( self.ecx.read_bytes_ptr_strip_provenance(mplace.ptr, Size::from_bytes(len)), self.path, - InvalidUninitBytes(..) => { "uninitialized data in `str`" }, + Ub(InvalidUninitBytes(..)) => Uninit { expected: ExpectedKind::Str }, + Unsup(ReadPointerAsInt(_)) => PointerAsInt { expected: ExpectedKind::Str } ); } ty::Array(tys, ..) | ty::Slice(tys) @@ -834,6 +754,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> if matches!(tys.kind(), ty::Int(..) | ty::Uint(..) | ty::Float(..)) => { + let expected = if tys.is_integral() { ExpectedKind::Int } else { ExpectedKind::Float }; // Optimized handling for arrays of integer/float type. // This is the length of the array/slice. @@ -852,7 +773,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> Left(mplace) => mplace, Right(imm) => match *imm { Immediate::Uninit => - throw_validation_failure!(self.path, { "uninitialized bytes" }), + throw_validation_failure!(self.path, Uninit { expected }), Immediate::Scalar(..) | Immediate::ScalarPair(..) => bug!("arrays/slices can never have Scalar/ScalarPair layout"), } @@ -878,17 +799,21 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> // For some errors we might be able to provide extra information. // (This custom logic does not fit the `try_validation!` macro.) match err.kind() { - err_ub!(InvalidUninitBytes(Some((_alloc_id, access)))) => { + Ub(InvalidUninitBytes(Some((_alloc_id, access)))) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => { // Some byte was uninitialized, determine which // element that byte belongs to so we can // provide an index. let i = usize::try_from( - access.uninit.start.bytes() / layout.size.bytes(), + access.bad.start.bytes() / layout.size.bytes(), ) .unwrap(); self.path.push(PathElem::ArrayElem(i)); - throw_validation_failure!(self.path, { "uninitialized bytes" }) + if matches!(err.kind(), Ub(InvalidUninitBytes(_))) { + throw_validation_failure!(self.path, Uninit { expected }) + } else { + throw_validation_failure!(self.path, PointerAsInt { expected }) + } } // Propagate upwards (that will also check for unexpected errors). @@ -902,12 +827,58 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> // ZST type, so either validation fails for all elements or none. ty::Array(tys, ..) | ty::Slice(tys) if self.ecx.layout_of(*tys)?.is_zst() => { // Validate just the first element (if any). - self.walk_aggregate(op, fields.take(1))? + if op.len(self.ecx)? > 0 { + self.visit_field(op, 0, &self.ecx.project_index(op, 0)?)?; + } } _ => { - self.walk_aggregate(op, fields)? // default handler + self.walk_value(op)?; // default handler + } + } + + // *After* all of this, check the ABI. We need to check the ABI to handle + // types like `NonNull` where the `Scalar` info is more restrictive than what + // the fields say (`rustc_layout_scalar_valid_range_start`). + // But in most cases, this will just propagate what the fields say, + // and then we want the error to point at the field -- so, first recurse, + // then check ABI. + // + // FIXME: We could avoid some redundant checks here. For newtypes wrapping + // scalars, we do the same check on every "level" (e.g., first we check + // MyNewtype and then the scalar in there). + match op.layout.abi { + Abi::Uninhabited => { + let ty = op.layout.ty; + throw_validation_failure!(self.path, UninhabitedVal { ty }); + } + Abi::Scalar(scalar_layout) => { + if !scalar_layout.is_uninit_valid() { + // There is something to check here. + let scalar = self.read_scalar(op, ExpectedKind::InitScalar)?; + self.visit_scalar(scalar, scalar_layout)?; + } + } + Abi::ScalarPair(a_layout, b_layout) => { + // We can only proceed if *both* scalars need to be initialized. + // FIXME: find a way to also check ScalarPair when one side can be uninit but + // the other must be init. + if !a_layout.is_uninit_valid() && !b_layout.is_uninit_valid() { + let (a, b) = + self.read_immediate(op, ExpectedKind::InitScalar)?.to_scalar_pair(); + self.visit_scalar(a, a_layout)?; + self.visit_scalar(b, b_layout)?; + } + } + Abi::Vector { .. } => { + // No checks here, we assume layout computation gets this right. + // (This is harder to check since Miri does not represent these as `Immediate`. We + // also cannot use field projections since this might be a newtype around a vector.) + } + Abi::Aggregate { .. } => { + // Nothing to do. } } + Ok(()) } } @@ -928,16 +899,22 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { // Run it. match visitor.visit_value(&op) { Ok(()) => Ok(()), - // Pass through validation failures. - Err(err) if matches!(err.kind(), err_ub!(ValidationFailure { .. })) => Err(err), - // Complain about any other kind of UB error -- those are bad because we'd like to + // Pass through validation failures and "invalid program" issues. + Err(err) + if matches!( + err.kind(), + err_ub!(ValidationError { .. }) | InterpError::InvalidProgram(_) + ) => + { + Err(err) + } + // Complain about any other kind of error -- those are bad because we'd like to // report them in a way that shows *where* in the value the issue lies. - Err(err) if matches!(err.kind(), InterpError::UndefinedBehavior(_)) => { - err.print_backtrace(); - bug!("Unexpected Undefined Behavior error during validation: {}", err); + Err(err) => { + let (err, backtrace) = err.into_parts(); + backtrace.print_backtrace(); + bug!("Unexpected Undefined Behavior error during validation: {err:?}"); } - // Pass through everything else. - Err(err) => Err(err), } } diff --git a/compiler/rustc_const_eval/src/interpret/visitor.rs b/compiler/rustc_const_eval/src/interpret/visitor.rs index 7a14459399c43..531e2bd3ee064 100644 --- a/compiler/rustc_const_eval/src/interpret/visitor.rs +++ b/compiler/rustc_const_eval/src/interpret/visitor.rs @@ -1,544 +1,202 @@ //! Visitor for a run-time value with a given layout: Traverse enums, structs and other compound //! types until we arrive at the leaves, with custom handling for primitive types. +use rustc_index::IndexVec; use rustc_middle::mir::interpret::InterpResult; use rustc_middle::ty; -use rustc_middle::ty::layout::TyAndLayout; +use rustc_target::abi::FieldIdx; use rustc_target::abi::{FieldsShape, VariantIdx, Variants}; use std::num::NonZeroUsize; -use super::{InterpCx, MPlaceTy, Machine, OpTy, PlaceTy}; +use super::{InterpCx, MPlaceTy, Machine, Projectable}; -/// A thing that we can project into, and that has a layout. -/// This wouldn't have to depend on `Machine` but with the current type inference, -/// that's just more convenient to work with (avoids repeating all the `Machine` bounds). -pub trait Value<'mir, 'tcx, M: Machine<'mir, 'tcx>>: Sized { - /// Gets this value's layout. - fn layout(&self) -> TyAndLayout<'tcx>; +/// How to traverse a value and what to do when we are at the leaves. +pub trait ValueVisitor<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>>: Sized { + type V: Projectable<'tcx, M::Provenance> + From>; - /// Makes this into an `OpTy`, in a cheap way that is good for reading. - fn to_op_for_read( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>; - - /// Makes this into an `OpTy`, in a potentially more expensive way that is good for projections. - fn to_op_for_proj( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - self.to_op_for_read(ecx) - } - - /// Creates this from an `OpTy`. - /// - /// If `to_op_for_proj` only ever produces `Indirect` operands, then this one is definitely `Indirect`. - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self; - - /// Projects to the given enum variant. - fn project_downcast( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self>; - - /// Projects to the n-th field. - fn project_field( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self>; -} - -/// A thing that we can project into given *mutable* access to `ecx`, and that has a layout. -/// This wouldn't have to depend on `Machine` but with the current type inference, -/// that's just more convenient to work with (avoids repeating all the `Machine` bounds). -pub trait ValueMut<'mir, 'tcx, M: Machine<'mir, 'tcx>>: Sized { - /// Gets this value's layout. - fn layout(&self) -> TyAndLayout<'tcx>; - - /// Makes this into an `OpTy`, in a cheap way that is good for reading. - fn to_op_for_read( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>; - - /// Makes this into an `OpTy`, in a potentially more expensive way that is good for projections. - fn to_op_for_proj( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>; - - /// Creates this from an `OpTy`. - /// - /// If `to_op_for_proj` only ever produces `Indirect` operands, then this one is definitely `Indirect`. - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self; - - /// Projects to the given enum variant. - fn project_downcast( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self>; - - /// Projects to the n-th field. - fn project_field( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self>; -} - -// We cannot have a general impl which shows that Value implies ValueMut. (When we do, it says we -// cannot `impl ValueMut for PlaceTy` because some downstream crate could `impl Value for PlaceTy`.) -// So we have some copy-paste here. (We could have a macro but since we only have 2 types with this -// double-impl, that would barely make the code shorter, if at all.) - -impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> Value<'mir, 'tcx, M> for OpTy<'tcx, M::Provenance> { - #[inline(always)] - fn layout(&self) -> TyAndLayout<'tcx> { - self.layout - } - - #[inline(always)] - fn to_op_for_read( - &self, - _ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - Ok(self.clone()) - } - - #[inline(always)] - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self { - op.clone() - } - - #[inline(always)] - fn project_downcast( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self> { - ecx.operand_downcast(self, variant) - } - - #[inline(always)] - fn project_field( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self> { - ecx.operand_field(self, field) - } -} - -impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueMut<'mir, 'tcx, M> - for OpTy<'tcx, M::Provenance> -{ - #[inline(always)] - fn layout(&self) -> TyAndLayout<'tcx> { - self.layout - } - - #[inline(always)] - fn to_op_for_read( - &self, - _ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - Ok(self.clone()) - } - - #[inline(always)] - fn to_op_for_proj( - &self, - _ecx: &mut InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - Ok(self.clone()) - } - - #[inline(always)] - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self { - op.clone() - } - - #[inline(always)] - fn project_downcast( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self> { - ecx.operand_downcast(self, variant) - } - - #[inline(always)] - fn project_field( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self> { - ecx.operand_field(self, field) - } -} - -impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> Value<'mir, 'tcx, M> - for MPlaceTy<'tcx, M::Provenance> -{ - #[inline(always)] - fn layout(&self) -> TyAndLayout<'tcx> { - self.layout - } - - #[inline(always)] - fn to_op_for_read( - &self, - _ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - Ok(self.into()) - } - - #[inline(always)] - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self { - // assert is justified because our `to_op_for_read` only ever produces `Indirect` operands. - op.assert_mem_place() - } - - #[inline(always)] - fn project_downcast( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self> { - ecx.mplace_downcast(self, variant) - } - - #[inline(always)] - fn project_field( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self> { - ecx.mplace_field(self, field) - } -} - -impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueMut<'mir, 'tcx, M> - for MPlaceTy<'tcx, M::Provenance> -{ - #[inline(always)] - fn layout(&self) -> TyAndLayout<'tcx> { - self.layout - } - - #[inline(always)] - fn to_op_for_read( - &self, - _ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - Ok(self.into()) - } - - #[inline(always)] - fn to_op_for_proj( - &self, - _ecx: &mut InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - Ok(self.into()) - } - - #[inline(always)] - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self { - // assert is justified because our `to_op_for_proj` only ever produces `Indirect` operands. - op.assert_mem_place() - } - - #[inline(always)] - fn project_downcast( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self> { - ecx.mplace_downcast(self, variant) - } - - #[inline(always)] - fn project_field( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self> { - ecx.mplace_field(self, field) - } -} - -impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueMut<'mir, 'tcx, M> - for PlaceTy<'tcx, M::Provenance> -{ - #[inline(always)] - fn layout(&self) -> TyAndLayout<'tcx> { - self.layout - } - - #[inline(always)] - fn to_op_for_read( - &self, - ecx: &InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - // No need for `force_allocation` since we are just going to read from this. - ecx.place_to_op(self) - } + /// The visitor must have an `InterpCx` in it. + fn ecx(&self) -> &InterpCx<'mir, 'tcx, M>; + /// `read_discriminant` can be hooked for better error messages. #[inline(always)] - fn to_op_for_proj( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { - // We `force_allocation` here so that `from_op` below can work. - Ok(ecx.force_allocation(self)?.into()) + fn read_discriminant(&mut self, v: &Self::V) -> InterpResult<'tcx, VariantIdx> { + Ok(self.ecx().read_discriminant(&v.to_op(self.ecx())?)?) } - #[inline(always)] - fn from_op(op: &OpTy<'tcx, M::Provenance>) -> Self { - // assert is justified because our `to_op` only ever produces `Indirect` operands. - op.assert_mem_place().into() - } - - #[inline(always)] - fn project_downcast( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - variant: VariantIdx, - ) -> InterpResult<'tcx, Self> { - ecx.place_downcast(self, variant) - } - - #[inline(always)] - fn project_field( - &self, - ecx: &mut InterpCx<'mir, 'tcx, M>, - field: usize, - ) -> InterpResult<'tcx, Self> { - ecx.place_field(self, field) - } -} - -macro_rules! make_value_visitor { - ($visitor_trait:ident, $value_trait:ident, $($mutability:ident)?) => { - /// How to traverse a value and what to do when we are at the leaves. - pub trait $visitor_trait<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>>: Sized { - type V: $value_trait<'mir, 'tcx, M>; - - /// The visitor must have an `InterpCx` in it. - fn ecx(&$($mutability)? self) - -> &$($mutability)? InterpCx<'mir, 'tcx, M>; - - /// `read_discriminant` can be hooked for better error messages. - #[inline(always)] - fn read_discriminant( - &mut self, - op: &OpTy<'tcx, M::Provenance>, - ) -> InterpResult<'tcx, VariantIdx> { - Ok(self.ecx().read_discriminant(op)?.1) - } - - // Recursive actions, ready to be overloaded. - /// Visits the given value, dispatching as appropriate to more specialized visitors. - #[inline(always)] - fn visit_value(&mut self, v: &Self::V) -> InterpResult<'tcx> - { - self.walk_value(v) - } - /// Visits the given value as a union. No automatic recursion can happen here. - #[inline(always)] - fn visit_union(&mut self, _v: &Self::V, _fields: NonZeroUsize) -> InterpResult<'tcx> - { - Ok(()) - } - /// Visits the given value as the pointer of a `Box`. There is nothing to recurse into. - /// The type of `v` will be a raw pointer, but this is a field of `Box` and the - /// pointee type is the actual `T`. - #[inline(always)] - fn visit_box(&mut self, _v: &Self::V) -> InterpResult<'tcx> - { - Ok(()) + /// This function provides the chance to reorder the order in which fields are visited for + /// `FieldsShape::Aggregate`: The order of fields will be + /// `(0..num_fields).map(aggregate_field_order)`. + /// + /// The default means we iterate in source declaration order; alternative this can do an inverse + /// lookup in `memory_index` to use memory field order instead. + #[inline(always)] + fn aggregate_field_order(_memory_index: &IndexVec, idx: usize) -> usize { + idx + } + + // Recursive actions, ready to be overloaded. + /// Visits the given value, dispatching as appropriate to more specialized visitors. + #[inline(always)] + fn visit_value(&mut self, v: &Self::V) -> InterpResult<'tcx> { + self.walk_value(v) + } + /// Visits the given value as a union. No automatic recursion can happen here. + #[inline(always)] + fn visit_union(&mut self, _v: &Self::V, _fields: NonZeroUsize) -> InterpResult<'tcx> { + Ok(()) + } + /// Visits the given value as the pointer of a `Box`. There is nothing to recurse into. + /// The type of `v` will be a raw pointer, but this is a field of `Box` and the + /// pointee type is the actual `T`. + #[inline(always)] + fn visit_box(&mut self, _v: &Self::V) -> InterpResult<'tcx> { + Ok(()) + } + + /// Called each time we recurse down to a field of a "product-like" aggregate + /// (structs, tuples, arrays and the like, but not enums), passing in old (outer) + /// and new (inner) value. + /// This gives the visitor the chance to track the stack of nested fields that + /// we are descending through. + #[inline(always)] + fn visit_field( + &mut self, + _old_val: &Self::V, + _field: usize, + new_val: &Self::V, + ) -> InterpResult<'tcx> { + self.visit_value(new_val) + } + /// Called when recursing into an enum variant. + /// This gives the visitor the chance to track the stack of nested fields that + /// we are descending through. + #[inline(always)] + fn visit_variant( + &mut self, + _old_val: &Self::V, + _variant: VariantIdx, + new_val: &Self::V, + ) -> InterpResult<'tcx> { + self.visit_value(new_val) + } + + fn walk_value(&mut self, v: &Self::V) -> InterpResult<'tcx> { + let ty = v.layout().ty; + trace!("walk_value: type: {ty}"); + + // Special treatment for special types, where the (static) layout is not sufficient. + match *ty.kind() { + // If it is a trait object, switch to the real type that was used to create it. + ty::Dynamic(_, _, ty::Dyn) => { + // Dyn types. This is unsized, and the actual dynamic type of the data is given by the + // vtable stored in the place metadata. + // unsized values are never immediate, so we can assert_mem_place + let op = v.to_op(self.ecx())?; + let dest = op.assert_mem_place(); + let inner_mplace = self.ecx().unpack_dyn_trait(&dest)?.0; + trace!("walk_value: dyn object layout: {:#?}", inner_mplace.layout); + // recurse with the inner type + return self.visit_field(&v, 0, &inner_mplace.into()); } - /// Visits this value as an aggregate, you are getting an iterator yielding - /// all the fields (still in an `InterpResult`, you have to do error handling yourself). - /// Recurses into the fields. - #[inline(always)] - fn visit_aggregate( - &mut self, - v: &Self::V, - fields: impl Iterator>, - ) -> InterpResult<'tcx> { - self.walk_aggregate(v, fields) + ty::Dynamic(_, _, ty::DynStar) => { + // DynStar types. Very different from a dyn type (but strangely part of the + // same variant in `TyKind`): These are pairs where the 2nd component is the + // vtable, and the first component is the data (which must be ptr-sized). + let data = self.ecx().unpack_dyn_star(v)?.0; + return self.visit_field(&v, 0, &data); } - - /// Called each time we recurse down to a field of a "product-like" aggregate - /// (structs, tuples, arrays and the like, but not enums), passing in old (outer) - /// and new (inner) value. - /// This gives the visitor the chance to track the stack of nested fields that - /// we are descending through. - #[inline(always)] - fn visit_field( - &mut self, - _old_val: &Self::V, - _field: usize, - new_val: &Self::V, - ) -> InterpResult<'tcx> { - self.visit_value(new_val) + // Slices do not need special handling here: they have `Array` field + // placement with length 0, so we enter the `Array` case below which + // indirectly uses the metadata to determine the actual length. + + // However, `Box`... let's talk about `Box`. + ty::Adt(def, ..) if def.is_box() => { + // `Box` is a hybrid primitive-library-defined type that one the one hand is + // a dereferenceable pointer, on the other hand has *basically arbitrary + // user-defined layout* since the user controls the 'allocator' field. So it + // cannot be treated like a normal pointer, since it does not fit into an + // `Immediate`. Yeah, it is quite terrible. But many visitors want to do + // something with "all boxed pointers", so we handle this mess for them. + // + // When we hit a `Box`, we do not do the usual field recursion; instead, + // we (a) call `visit_box` on the pointer value, and (b) recurse on the + // allocator field. We also assert tons of things to ensure we do not miss + // any other fields. + + // `Box` has two fields: the pointer we care about, and the allocator. + assert_eq!(v.layout().fields.count(), 2, "`Box` must have exactly 2 fields"); + let (unique_ptr, alloc) = + (self.ecx().project_field(v, 0)?, self.ecx().project_field(v, 1)?); + // Unfortunately there is some type junk in the way here: `unique_ptr` is a `Unique`... + // (which means another 2 fields, the second of which is a `PhantomData`) + assert_eq!(unique_ptr.layout().fields.count(), 2); + let (nonnull_ptr, phantom) = ( + self.ecx().project_field(&unique_ptr, 0)?, + self.ecx().project_field(&unique_ptr, 1)?, + ); + assert!( + phantom.layout().ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()), + "2nd field of `Unique` should be PhantomData but is {:?}", + phantom.layout().ty, + ); + // ... that contains a `NonNull`... (gladly, only a single field here) + assert_eq!(nonnull_ptr.layout().fields.count(), 1); + let raw_ptr = self.ecx().project_field(&nonnull_ptr, 0)?; // the actual raw ptr + // ... whose only field finally is a raw ptr we can dereference. + self.visit_box(&raw_ptr)?; + + // The second `Box` field is the allocator, which we recursively check for validity + // like in regular structs. + self.visit_field(v, 1, &alloc)?; + + // We visited all parts of this one. + return Ok(()); } - /// Called when recursing into an enum variant. - /// This gives the visitor the chance to track the stack of nested fields that - /// we are descending through. - #[inline(always)] - fn visit_variant( - &mut self, - _old_val: &Self::V, - _variant: VariantIdx, - new_val: &Self::V, - ) -> InterpResult<'tcx> { - self.visit_value(new_val) + _ => {} + }; + + // Visit the fields of this value. + match &v.layout().fields { + FieldsShape::Primitive => {} + &FieldsShape::Union(fields) => { + self.visit_union(v, fields)?; } - - // Default recursors. Not meant to be overloaded. - fn walk_aggregate( - &mut self, - v: &Self::V, - fields: impl Iterator>, - ) -> InterpResult<'tcx> { - // Now iterate over it. - for (idx, field_val) in fields.enumerate() { - self.visit_field(v, idx, &field_val?)?; + FieldsShape::Arbitrary { offsets, memory_index } => { + for idx in 0..offsets.len() { + let idx = Self::aggregate_field_order(memory_index, idx); + let field = self.ecx().project_field(v, idx)?; + self.visit_field(v, idx, &field)?; } - Ok(()) } - fn walk_value(&mut self, v: &Self::V) -> InterpResult<'tcx> - { - let ty = v.layout().ty; - trace!("walk_value: type: {ty}"); - - // Special treatment for special types, where the (static) layout is not sufficient. - match *ty.kind() { - // If it is a trait object, switch to the real type that was used to create it. - ty::Dynamic(_, _, ty::Dyn) => { - // Dyn types. This is unsized, and the actual dynamic type of the data is given by the - // vtable stored in the place metadata. - // unsized values are never immediate, so we can assert_mem_place - let op = v.to_op_for_read(self.ecx())?; - let dest = op.assert_mem_place(); - let inner_mplace = self.ecx().unpack_dyn_trait(&dest)?.0; - trace!("walk_value: dyn object layout: {:#?}", inner_mplace.layout); - // recurse with the inner type - return self.visit_field(&v, 0, &$value_trait::from_op(&inner_mplace.into())); - }, - ty::Dynamic(_, _, ty::DynStar) => { - // DynStar types. Very different from a dyn type (but strangely part of the - // same variant in `TyKind`): These are pairs where the 2nd component is the - // vtable, and the first component is the data (which must be ptr-sized). - let op = v.to_op_for_proj(self.ecx())?; - let data = self.ecx().unpack_dyn_star(&op)?.0; - return self.visit_field(&v, 0, &$value_trait::from_op(&data)); - } - // Slices do not need special handling here: they have `Array` field - // placement with length 0, so we enter the `Array` case below which - // indirectly uses the metadata to determine the actual length. - - // However, `Box`... let's talk about `Box`. - ty::Adt(def, ..) if def.is_box() => { - // `Box` is a hybrid primitive-library-defined type that one the one hand is - // a dereferenceable pointer, on the other hand has *basically arbitrary - // user-defined layout* since the user controls the 'allocator' field. So it - // cannot be treated like a normal pointer, since it does not fit into an - // `Immediate`. Yeah, it is quite terrible. But many visitors want to do - // something with "all boxed pointers", so we handle this mess for them. - // - // When we hit a `Box`, we do not do the usual `visit_aggregate`; instead, - // we (a) call `visit_box` on the pointer value, and (b) recurse on the - // allocator field. We also assert tons of things to ensure we do not miss - // any other fields. - - // `Box` has two fields: the pointer we care about, and the allocator. - assert_eq!(v.layout().fields.count(), 2, "`Box` must have exactly 2 fields"); - let (unique_ptr, alloc) = - (v.project_field(self.ecx(), 0)?, v.project_field(self.ecx(), 1)?); - // Unfortunately there is some type junk in the way here: `unique_ptr` is a `Unique`... - // (which means another 2 fields, the second of which is a `PhantomData`) - assert_eq!(unique_ptr.layout().fields.count(), 2); - let (nonnull_ptr, phantom) = ( - unique_ptr.project_field(self.ecx(), 0)?, - unique_ptr.project_field(self.ecx(), 1)?, - ); - assert!( - phantom.layout().ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()), - "2nd field of `Unique` should be PhantomData but is {:?}", - phantom.layout().ty, - ); - // ... that contains a `NonNull`... (gladly, only a single field here) - assert_eq!(nonnull_ptr.layout().fields.count(), 1); - let raw_ptr = nonnull_ptr.project_field(self.ecx(), 0)?; // the actual raw ptr - // ... whose only field finally is a raw ptr we can dereference. - self.visit_box(&raw_ptr)?; - - // The second `Box` field is the allocator, which we recursively check for validity - // like in regular structs. - self.visit_field(v, 1, &alloc)?; - - // We visited all parts of this one. - return Ok(()); - } - _ => {}, - }; - - // Visit the fields of this value. - match &v.layout().fields { - FieldsShape::Primitive => {} - &FieldsShape::Union(fields) => { - self.visit_union(v, fields)?; - } - FieldsShape::Arbitrary { offsets, .. } => { - // FIXME: We collect in a vec because otherwise there are lifetime - // errors: Projecting to a field needs access to `ecx`. - let fields: Vec> = - (0..offsets.len()).map(|i| { - v.project_field(self.ecx(), i) - }) - .collect(); - self.visit_aggregate(v, fields.into_iter())?; - } - FieldsShape::Array { .. } => { - // Let's get an mplace (or immediate) first. - // This might `force_allocate` if `v` is a `PlaceTy`, but `place_index` does that anyway. - let op = v.to_op_for_proj(self.ecx())?; - // Now we can go over all the fields. - // This uses the *run-time length*, i.e., if we are a slice, - // the dynamic info from the metadata is used. - let iter = self.ecx().operand_array_fields(&op)? - .map(|f| f.and_then(|f| { - Ok($value_trait::from_op(&f)) - })); - self.visit_aggregate(v, iter)?; - } + FieldsShape::Array { .. } => { + for (idx, field) in self.ecx().project_array_fields(v)?.enumerate() { + self.visit_field(v, idx, &field?)?; } + } + } - match v.layout().variants { - // If this is a multi-variant layout, find the right variant and proceed - // with *its* fields. - Variants::Multiple { .. } => { - let op = v.to_op_for_read(self.ecx())?; - let idx = self.read_discriminant(&op)?; - let inner = v.project_downcast(self.ecx(), idx)?; - trace!("walk_value: variant layout: {:#?}", inner.layout()); - // recurse with the inner type - self.visit_variant(v, idx, &inner) - } - // For single-variant layouts, we already did anything there is to do. - Variants::Single { .. } => Ok(()) - } + match v.layout().variants { + // If this is a multi-variant layout, find the right variant and proceed + // with *its* fields. + Variants::Multiple { .. } => { + let idx = self.read_discriminant(v)?; + // There are 3 cases where downcasts can turn a Scalar/ScalarPair into a different ABI which + // could be a problem for `ImmTy` (see layout_sanity_check): + // - variant.size == Size::ZERO: works fine because `ImmTy::offset` has a special case for + // zero-sized layouts. + // - variant.fields.count() == 0: works fine because `ImmTy::offset` has a special case for + // zero-field aggregates. + // - variant.abi.is_uninhabited(): triggers UB in `read_discriminant` so we never get here. + let inner = self.ecx().project_downcast(v, idx)?; + trace!("walk_value: variant layout: {:#?}", inner.layout()); + // recurse with the inner type + self.visit_variant(v, idx, &inner)?; } + // For single-variant layouts, we already did anything there is to do. + Variants::Single { .. } => {} } + + Ok(()) } } - -make_value_visitor!(ValueVisitor, Value,); -make_value_visitor!(MutValueVisitor, ValueMut, mut); diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index c36282d5ed442..c126f749bf328 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -4,6 +4,7 @@ Rust MIR: a lowered representation of Rust. */ +#![deny(rustc::untranslatable_diagnostic)] #![feature(assert_matches)] #![feature(box_patterns)] #![feature(decl_macro)] @@ -33,6 +34,8 @@ pub mod interpret; pub mod transform; pub mod util; +pub use errors::ReportErrorExt; + use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_middle::query::Providers; @@ -49,17 +52,11 @@ pub fn provide(providers: &mut Providers) { let (param_env, raw) = param_env_and_value.into_parts(); const_eval::eval_to_valtree(tcx, param_env, raw) }; - providers.try_destructure_mir_constant = |tcx, param_env_and_value| { - let (param_env, value) = param_env_and_value.into_parts(); - const_eval::try_destructure_mir_constant(tcx, param_env, value).ok() - }; + providers.try_destructure_mir_constant_for_diagnostics = + |tcx, (cv, ty)| const_eval::try_destructure_mir_constant_for_diagnostics(tcx, cv, ty); providers.valtree_to_const_val = |tcx, (ty, valtree)| { const_eval::valtree_to_const_value(tcx, ty::ParamEnv::empty().and(ty), valtree) }; - providers.deref_mir_constant = |tcx, param_env_and_value| { - let (param_env, value) = param_env_and_value.into_parts(); - const_eval::deref_mir_constant(tcx, param_env, value) - }; providers.check_validity_requirement = |tcx, (init_kind, param_env_and_ty)| { util::check_validity_requirement(tcx, init_kind, param_env_and_ty) }; diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index 138bc3eb74a4f..fae047bff9e79 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -8,9 +8,10 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{ImplSource, Obligation, ObligationCause}; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; -use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts}; -use rustc_middle::ty::{self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt}; -use rustc_middle::ty::{Binder, TraitRef, TypeVisitableExt}; +use rustc_middle::traits::BuiltinImplSource; +use rustc_middle::ty::{self, adjustment::PointerCoercion, Instance, InstanceDef, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArgKind, GenericArgs}; +use rustc_middle::ty::{TraitRef, TypeVisitableExt}; use rustc_mir_dataflow::{self, Analysis}; use rustc_span::{sym, Span, Symbol}; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; @@ -20,7 +21,7 @@ use std::mem; use std::ops::Deref; use super::ops::{self, NonConstOp, Status}; -use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop, NeedsNonConstDrop}; +use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop}; use super::resolver::FlowSensitiveAnalysis; use super::{ConstCx, Qualif}; use crate::const_eval::is_unstable_const_fn; @@ -33,7 +34,7 @@ type QualifResults<'mir, 'tcx, Q> = pub struct Qualifs<'mir, 'tcx> { has_mut_interior: Option>, needs_drop: Option>, - needs_non_const_drop: Option>, + // needs_non_const_drop: Option>, } impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { @@ -76,15 +77,17 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { local: Local, location: Location, ) -> bool { + // FIXME(effects) replace with `NeedsNonconstDrop` after const traits work again + /* let ty = ccx.body.local_decls[local].ty; - if !NeedsNonConstDrop::in_any_value_of_ty(ccx, ty) { + if !NeedsDrop::in_any_value_of_ty(ccx, ty) { return false; } let needs_non_const_drop = self.needs_non_const_drop.get_or_insert_with(|| { let ConstCx { tcx, body, .. } = *ccx; - FlowSensitiveAnalysis::new(NeedsNonConstDrop, ccx) + FlowSensitiveAnalysis::new(NeedsDrop, ccx) .into_engine(tcx, &body) .iterate_to_fixpoint() .into_results_cursor(&body) @@ -92,6 +95,9 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { needs_non_const_drop.seek_before_primary_effect(location); needs_non_const_drop.get().contains(local) + */ + + self.needs_drop(ccx, local, location) } /// Returns `true` if `local` is `HasMutInterior` at the given `Location`. @@ -412,9 +418,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { BorrowKind::Shallow => { PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) } - BorrowKind::Unique => { - PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow) - } BorrowKind::Mut { .. } => { PlaceContext::MutatingUse(MutatingUseContext::Borrow) } @@ -459,7 +462,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } - Rvalue::Ref(_, kind @ (BorrowKind::Mut { .. } | BorrowKind::Unique), place) => { + Rvalue::Ref(_, BorrowKind::Mut { .. }, place) => { let ty = place.ty(self.body, self.tcx).ty; let is_allowed = match ty.kind() { // Inside a `static mut`, `&mut [...]` is allowed. @@ -480,11 +483,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { }; if !is_allowed { - if let BorrowKind::Mut { .. } = kind { - self.check_mut_borrow(place.local, hir::BorrowKind::Ref) - } else { - self.check_op(ops::CellBorrow); - } + self.check_mut_borrow(place.local, hir::BorrowKind::Ref) } } @@ -528,12 +527,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } Rvalue::Cast( - CastKind::Pointer( - PointerCast::MutToConstPointer - | PointerCast::ArrayToPointer - | PointerCast::UnsafeFnPointer - | PointerCast::ClosureFnPointer(_) - | PointerCast::ReifyFnPointer, + CastKind::PointerCoercion( + PointerCoercion::MutToConstPointer + | PointerCoercion::ArrayToPointer + | PointerCoercion::UnsafeFnPointer + | PointerCoercion::ClosureFnPointer(_) + | PointerCoercion::ReifyFnPointer, ), _, _, @@ -541,7 +540,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // These are all okay; they only change the type, not the data. } - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), _, _) => { + Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), _, _) => { // Unsizing is implemented for CTFE. } @@ -619,30 +618,28 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } fn visit_projection_elem( &mut self, - place_local: Local, - proj_base: &[PlaceElem<'tcx>], + place_ref: PlaceRef<'tcx>, elem: PlaceElem<'tcx>, context: PlaceContext, location: Location, ) { trace!( - "visit_projection_elem: place_local={:?} proj_base={:?} elem={:?} \ + "visit_projection_elem: place_ref={:?} elem={:?} \ context={:?} location={:?}", - place_local, - proj_base, + place_ref, elem, context, location, ); - self.super_projection_elem(place_local, proj_base, elem, context, location); + self.super_projection_elem(place_ref, elem, context, location); match elem { ProjectionElem::Deref => { - let base_ty = Place::ty_from(place_local, proj_base, self.body, self.tcx).ty; + let base_ty = place_ref.ty(self.body, self.tcx).ty; if base_ty.is_unsafe_ptr() { - if proj_base.is_empty() { - let decl = &self.body.local_decls[place_local]; + if place_ref.projection.is_empty() { + let decl = &self.body.local_decls[place_ref.local]; if let LocalInfo::StaticRef { def_id, .. } = *decl.local_info() { let span = decl.source_info.span; self.check_static(def_id, span); @@ -704,14 +701,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.super_terminator(terminator, location); match &terminator.kind { - TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => { + TerminatorKind::Call { func, args, fn_span, call_source, .. } => { let ConstCx { tcx, body, param_env, .. } = *self.ccx; let caller = self.def_id(); let fn_ty = func.ty(body, tcx); - let (mut callee, mut substs) = match *fn_ty.kind() { - ty::FnDef(def_id, substs) => (def_id, substs), + let (mut callee, mut fn_args) = match *fn_ty.kind() { + ty::FnDef(def_id, fn_args) => (def_id, fn_args), ty::FnPtr(_) => { self.check_op(ops::FnCallIndirect); @@ -730,7 +727,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { let infcx = tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); - let predicates = tcx.predicates_of(callee).instantiate(tcx, substs); + let predicates = tcx.predicates_of(callee).instantiate(tcx, fn_args); let cause = ObligationCause::new( terminator.source_info.span, self.body.source.def_id().expect_local(), @@ -749,25 +746,24 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } // Attempting to call a trait method? + // FIXME(effects) do we need this? if let Some(trait_id) = tcx.trait_of_item(callee) { trace!("attempting to call a trait method"); if !self.tcx.features().const_trait_impl { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, - from_hir_call: *from_hir_call, + call_source: *call_source, feature: Some(sym::const_trait_impl), }); return; } - let trait_ref = TraitRef::from_method(tcx, trait_id, substs); - let poly_trait_pred = - Binder::dummy(trait_ref).with_constness(ty::BoundConstness::ConstIfConst); + let trait_ref = TraitRef::from_method(tcx, trait_id, fn_args); let obligation = - Obligation::new(tcx, ObligationCause::dummy(), param_env, poly_trait_pred); + Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref); let implsrc = { let infcx = tcx.infer_ctxt().build(); @@ -776,21 +772,29 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { }; match implsrc { - Ok(Some(ImplSource::Param(_, ty::BoundConstness::ConstIfConst))) => { + Ok(Some(ImplSource::Param(_))) if tcx.features().effects => { debug!( "const_trait_impl: provided {:?} via where-clause in {:?}", trait_ref, param_env ); return; } - Ok(Some(ImplSource::Closure(data))) => { - if !tcx.is_const_fn_raw(data.closure_def_id) { + // Closure: Fn{Once|Mut} + Ok(Some(ImplSource::Builtin(BuiltinImplSource::Misc, _))) + if trait_ref.self_ty().is_closure() + && tcx.fn_trait_kind_from_def_id(trait_id).is_some() => + { + let ty::Closure(closure_def_id, fn_args) = *trait_ref.self_ty().kind() + else { + unreachable!() + }; + if !tcx.is_const_fn_raw(closure_def_id) { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, - from_hir_call: *from_hir_call, + call_source: *call_source, feature: None, }); @@ -799,28 +803,29 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } Ok(Some(ImplSource::UserDefined(data))) => { let callee_name = tcx.item_name(callee); - if let Some(&did) = tcx - .associated_item_def_ids(data.impl_def_id) - .iter() - .find(|did| tcx.item_name(**did) == callee_name) - { - // using internal substs is ok here, since this is only - // used for the `resolve` call below - substs = InternalSubsts::identity_for_item(tcx, did); - callee = did; - } if let hir::Constness::NotConst = tcx.constness(data.impl_def_id) { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, - from_hir_call: *from_hir_call, + call_source: *call_source, feature: None, }); return; } + + if let Some(&did) = tcx + .associated_item_def_ids(data.impl_def_id) + .iter() + .find(|did| tcx.item_name(**did) == callee_name) + { + // using internal args is ok here, since this is only + // used for the `resolve` call below + fn_args = GenericArgs::identity_for_item(tcx, did); + callee = did; + } } _ if !tcx.is_const_fn_raw(callee) => { // At this point, it is only legal when the caller is in a trait @@ -830,7 +835,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { && tcx.has_attr(callee_trait, sym::const_trait) && Some(callee_trait) == tcx.trait_of_item(caller.to_def_id()) // Can only call methods when it's `::f`. - && tcx.types.self_param == substs.type_at(0) + && tcx.types.self_param == fn_args.type_at(0) { nonconst_call_permission = true; } @@ -840,7 +845,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { tcx, ObligationCause::dummy_with_span(*fn_span), param_env, - poly_trait_pred, + trait_ref, ); // improve diagnostics by showing what failed. Our requirements are stricter this time @@ -857,9 +862,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, - from_hir_call: *from_hir_call, + call_source: *call_source, feature: None, }); return; @@ -870,7 +875,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // Resolve a trait method call to its concrete implementation, which may be in a // `const` trait impl. - let instance = Instance::resolve(tcx, param_env, callee, substs); + let instance = Instance::resolve(tcx, param_env, callee, fn_args); debug!("Resolving ({:?}) -> {:?}", callee, instance); if let Ok(Some(func)) = instance { if let InstanceDef::Item(def) = func.def { @@ -917,9 +922,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.check_op(ops::FnCallNonConst { caller, callee, - substs, + args: fn_args, span: *fn_span, - from_hir_call: *from_hir_call, + call_source: *call_source, feature: None, }); return; @@ -997,8 +1002,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { let mut err_span = self.span; let ty_of_dropped_place = dropped_place.ty(self.body, self.tcx).ty; + // FIXME(effects) replace with `NeedsNonConstDrop` once we fix const traits let ty_needs_non_const_drop = - qualifs::NeedsNonConstDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place); + qualifs::NeedsDrop::in_any_value_of_ty(self.ccx, ty_of_dropped_place); debug!(?ty_of_dropped_place, ?ty_needs_non_const_drop); diff --git a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs index 8ebfee8878cbb..e51082e1ec0e3 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/mod.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/mod.rs @@ -68,11 +68,11 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> { pub fn fn_sig(&self) -> PolyFnSig<'tcx> { let did = self.def_id().to_def_id(); if self.tcx.is_closure(did) { - let ty = self.tcx.type_of(did).subst_identity(); - let ty::Closure(_, substs) = ty.kind() else { bug!("type_of closure not ty::Closure") }; - substs.as_closure().sig() + let ty = self.tcx.type_of(did).instantiate_identity(); + let ty::Closure(_, args) = ty.kind() else { bug!("type_of closure not ty::Closure") }; + args.as_closure().sig() } else { - self.tcx.fn_sig(did).subst_identity() + self.tcx.fn_sig(did).instantiate_identity() } } } @@ -127,15 +127,8 @@ fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool { let hir_id = tcx.local_def_id_to_hir_id(local_def_id); let Some(parent) = tcx.hir().opt_parent_id(hir_id) else { return false }; - let parent_def = tcx.hir().get(parent); - - if !matches!( - parent_def, - hir::Node::Item(hir::Item { - kind: hir::ItemKind::Impl(hir::Impl { constness: hir::Constness::Const, .. }), - .. - }) - ) { + + if !tcx.is_const_trait_impl_raw(parent.owner.def_id.to_def_id()) { return false; } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs index 21f3c2c891713..1f3cda35c2ba1 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/ops.rs @@ -2,18 +2,16 @@ use hir::def_id::LocalDefId; use hir::{ConstContext, LangItem}; -use rustc_errors::{ - error_code, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, -}; +use rustc_errors::{error_code, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{ImplSource, Obligation, ObligationCause}; -use rustc_middle::mir; +use rustc_middle::mir::{self, CallSource}; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; +use rustc_middle::ty::TraitRef; use rustc_middle::ty::{suggest_constraining_type_param, Adt, Closure, FnDef, FnPtr, Param, Ty}; -use rustc_middle::ty::{Binder, TraitRef}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; use rustc_middle::util::{call_kind, CallDesugaringKind, CallKind}; use rustc_session::parse::feature_err; use rustc_span::symbol::sym; @@ -100,9 +98,9 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect { pub struct FnCallNonConst<'tcx> { pub caller: LocalDefId, pub callee: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, pub span: Span, - pub from_hir_call: bool, + pub call_source: CallSource, pub feature: Option, } @@ -112,11 +110,11 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { ccx: &ConstCx<'_, 'tcx>, _: Span, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { - let FnCallNonConst { caller, callee, substs, span, from_hir_call, feature } = *self; + let FnCallNonConst { caller, callee, args, span, call_source, feature } = *self; let ConstCx { tcx, param_env, .. } = *ccx; let diag_trait = |err, self_ty: Ty<'_>, trait_id| { - let trait_ref = TraitRef::from_method(tcx, trait_id, substs); + let trait_ref = TraitRef::from_method(tcx, trait_id, args); match self_ty.kind() { Param(param_ty) => { @@ -139,53 +137,57 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { } } Adt(..) => { - let obligation = Obligation::new( - tcx, - ObligationCause::dummy(), - param_env, - Binder::dummy(trait_ref), - ); + let obligation = + Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref); let infcx = tcx.infer_ctxt().build(); let mut selcx = SelectionContext::new(&infcx); let implsrc = selcx.select(&obligation); if let Ok(Some(ImplSource::UserDefined(data))) = implsrc { - let span = tcx.def_span(data.impl_def_id); - err.span_note(span, "impl defined here, but it is not `const`"); + // FIXME(effects) revisit this + if !tcx.is_const_trait_impl_raw(data.impl_def_id) { + let span = tcx.def_span(data.impl_def_id); + err.subdiagnostic(errors::NonConstImplNote { span }); + } } } _ => {} } }; - let call_kind = call_kind(tcx, ccx.param_env, callee, substs, span, from_hir_call, None); + let call_kind = + call_kind(tcx, ccx.param_env, callee, args, span, call_source.from_hir_call(), None); debug!(?call_kind); let mut err = match call_kind { CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => { macro_rules! error { - ($fmt:literal) => { - struct_span_err!(tcx.sess, span, E0015, $fmt, self_ty, ccx.const_kind()) + ($err:ident) => { + tcx.sess.create_err(errors::$err { + span, + ty: self_ty, + kind: ccx.const_kind(), + }) }; } let mut err = match kind { CallDesugaringKind::ForLoopIntoIter => { - error!("cannot convert `{}` into an iterator in {}s") + error!(NonConstForLoopIntoIter) } CallDesugaringKind::QuestionBranch => { - error!("`?` cannot determine the branch of `{}` in {}s") + error!(NonConstQuestionBranch) } CallDesugaringKind::QuestionFromResidual => { - error!("`?` cannot convert from residual of `{}` in {}s") + error!(NonConstQuestionFromResidual) } CallDesugaringKind::TryBlockFromOutput => { - error!("`try` block cannot convert `{}` to the result in {}s") + error!(NonConstTryBlockFromOutput) } CallDesugaringKind::Await => { - error!("cannot convert `{}` into a future in {}s") + error!(NonConstAwait) } }; @@ -193,108 +195,101 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { err } CallKind::FnCall { fn_trait_id, self_ty } => { - let mut err = struct_span_err!( - tcx.sess, - span, - E0015, - "cannot call non-const closure in {}s", - ccx.const_kind(), - ); - - match self_ty.kind() { + let note = match self_ty.kind() { FnDef(def_id, ..) => { let span = tcx.def_span(*def_id); if ccx.tcx.is_const_fn_raw(*def_id) { span_bug!(span, "calling const FnDef errored when it shouldn't"); } - err.span_note(span, "function defined here, but it is not `const`"); - } - FnPtr(..) => { - err.note(format!( - "function pointers need an RFC before allowed to be called in {}s", - ccx.const_kind() - )); - } - Closure(..) => { - err.note(format!( - "closures need an RFC before allowed to be called in {}s", - ccx.const_kind() - )); + Some(errors::NonConstClosureNote::FnDef { span }) } - _ => {} - } + FnPtr(..) => Some(errors::NonConstClosureNote::FnPtr), + Closure(..) => Some(errors::NonConstClosureNote::Closure), + _ => None, + }; + + let mut err = tcx.sess.create_err(errors::NonConstClosure { + span, + kind: ccx.const_kind(), + note, + }); diag_trait(&mut err, self_ty, fn_trait_id); err } CallKind::Operator { trait_id, self_ty, .. } => { - let mut err = struct_span_err!( - tcx.sess, - span, - E0015, - "cannot call non-const operator in {}s", - ccx.const_kind() - ); - - if Some(trait_id) == ccx.tcx.lang_items().eq_trait() { - match (substs[0].unpack(), substs[1].unpack()) { - (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty)) - if self_ty == rhs_ty - && self_ty.is_ref() - && self_ty.peel_refs().is_primitive() => - { - let mut num_refs = 0; - let mut tmp_ty = self_ty; - while let rustc_middle::ty::Ref(_, inner_ty, _) = tmp_ty.kind() { - num_refs += 1; - tmp_ty = *inner_ty; - } - let deref = "*".repeat(num_refs); - - if let Ok(call_str) = ccx.tcx.sess.source_map().span_to_snippet(span) { - if let Some(eq_idx) = call_str.find("==") { - if let Some(rhs_idx) = - call_str[(eq_idx + 2)..].find(|c: char| !c.is_whitespace()) - { - let rhs_pos = - span.lo() + BytePos::from_usize(eq_idx + 2 + rhs_idx); - let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos); - err.multipart_suggestion( - "consider dereferencing here", - vec![ - (span.shrink_to_lo(), deref.clone()), - (rhs_span, deref), - ], - Applicability::MachineApplicable, - ); + let mut err = if let CallSource::MatchCmp = call_source { + tcx.sess.create_err(errors::NonConstMatchEq { + span, + kind: ccx.const_kind(), + ty: self_ty, + }) + } else { + let mut sugg = None; + + if Some(trait_id) == ccx.tcx.lang_items().eq_trait() { + match (args[0].unpack(), args[1].unpack()) { + (GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty)) + if self_ty == rhs_ty + && self_ty.is_ref() + && self_ty.peel_refs().is_primitive() => + { + let mut num_refs = 0; + let mut tmp_ty = self_ty; + while let rustc_middle::ty::Ref(_, inner_ty, _) = tmp_ty.kind() { + num_refs += 1; + tmp_ty = *inner_ty; + } + let deref = "*".repeat(num_refs); + + if let Ok(call_str) = + ccx.tcx.sess.source_map().span_to_snippet(span) + { + if let Some(eq_idx) = call_str.find("==") { + if let Some(rhs_idx) = call_str[(eq_idx + 2)..] + .find(|c: char| !c.is_whitespace()) + { + let rhs_pos = span.lo() + + BytePos::from_usize(eq_idx + 2 + rhs_idx); + let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos); + sugg = Some(errors::ConsiderDereferencing { + deref, + span: span.shrink_to_lo(), + rhs_span, + }); + } } } } + _ => {} } - _ => {} } - } + tcx.sess.create_err(errors::NonConstOperator { + span, + kind: ccx.const_kind(), + sugg, + }) + }; diag_trait(&mut err, self_ty, trait_id); err } CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => { - let mut err = struct_span_err!( - tcx.sess, - span, - E0015, - "cannot perform deref coercion on `{}` in {}s", - self_ty, - ccx.const_kind() - ); - - err.note(format!("attempting to deref into `{}`", deref_target_ty)); - // Check first whether the source is accessible (issue #87060) - if tcx.sess.source_map().is_span_accessible(deref_target) { - err.span_note(deref_target, "deref defined here"); - } + let target = if tcx.sess.source_map().is_span_accessible(deref_target) { + Some(deref_target) + } else { + None + }; + + let mut err = tcx.sess.create_err(errors::NonConstDerefCoercion { + span, + ty: self_ty, + kind: ccx.const_kind(), + target_ty: deref_target_ty, + deref_target: target, + }); diag_trait(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, Some(span))); err @@ -305,7 +300,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { .create_err(errors::NonConstFmtMacroCall { span, kind: ccx.const_kind() }), _ => ccx.tcx.sess.create_err(errors::NonConstFnCall { span, - def_path_str: ccx.tcx.def_path_str_with_substs(callee, substs), + def_path_str: ccx.tcx.def_path_str_with_args(callee, args), kind: ccx.const_kind(), }), }; @@ -318,8 +313,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { if let Some(feature) = feature && ccx.tcx.sess.is_nightly_build() { err.help(format!( - "add `#![feature({})]` to the crate attributes to enable", - feature, + "add `#![feature({feature})]` to the crate attributes to enable", )); } @@ -354,10 +348,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallUnstable { err.help("const-stable functions can only call other const-stable functions"); } else if ccx.tcx.sess.is_nightly_build() { if let Some(feature) = feature { - err.help(format!( - "add `#![feature({})]` to the crate attributes to enable", - feature - )); + err.help(format!("add `#![feature({feature})]` to the crate attributes to enable")); } } @@ -432,21 +423,12 @@ impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> { ccx: &ConstCx<'_, 'tcx>, span: Span, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { - let mut err = struct_span_err!( - ccx.tcx.sess, + ccx.tcx.sess.create_err(errors::LiveDrop { span, - E0493, - "destructor of `{}` cannot be evaluated at compile-time", - self.dropped_ty, - ); - err.span_label( - span, - format!("the destructor for this type cannot be evaluated in {}s", ccx.const_kind()), - ); - if let Some(span) = self.dropped_at { - err.span_label(span, "value is dropped here"); - } - err + dropped_ty: self.dropped_ty, + kind: ccx.const_kind(), + dropped_at: self.dropped_at, + }) } } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs index 1f1640fd80ae6..e3377bd10c617 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs @@ -5,7 +5,7 @@ use rustc_span::{symbol::sym, Span}; use super::check::Qualifs; use super::ops::{self, NonConstOp}; -use super::qualifs::{NeedsNonConstDrop, Qualif}; +use super::qualifs::{NeedsDrop, Qualif}; use super::ConstCx; /// Returns `true` if we should use the more precise live drop checker that runs after drop @@ -82,7 +82,9 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> { match &terminator.kind { mir::TerminatorKind::Drop { place: dropped_place, .. } => { let dropped_ty = dropped_place.ty(self.body, self.tcx).ty; - if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) { + + // FIXME(effects) use `NeedsNonConstDrop` + if !NeedsDrop::in_any_value_of_ty(self.ccx, dropped_ty) { // Instead of throwing a bug, we just return here. This is because we have to // run custom `const Drop` impls. return; diff --git a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs index 1da20579021a1..b1b2859ef9dc7 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/qualifs.rs @@ -7,7 +7,8 @@ use rustc_hir::LangItem; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir; use rustc_middle::mir::*; -use rustc_middle::ty::{self, subst::SubstsRef, AdtDef, Ty}; +use rustc_middle::traits::BuiltinImplSource; +use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty}; use rustc_trait_selection::traits::{ self, ImplSource, Obligation, ObligationCause, ObligationCtxt, SelectionContext, }; @@ -22,7 +23,8 @@ pub fn in_any_value_of_ty<'tcx>( ConstQualifs { has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty), needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty), - needs_non_const_drop: NeedsNonConstDrop::in_any_value_of_ty(cx, ty), + // FIXME(effects) + needs_non_const_drop: NeedsDrop::in_any_value_of_ty(cx, ty), custom_eq: CustomEq::in_any_value_of_ty(cx, ty), tainted_by_errors, } @@ -72,7 +74,7 @@ pub trait Qualif { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> bool; } @@ -97,7 +99,7 @@ impl Qualif for HasMutInterior { fn in_adt_inherently<'tcx>( _cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - _: SubstsRef<'tcx>, + _: GenericArgsRef<'tcx>, ) -> bool { // Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently. // It arises structurally for all other types. @@ -127,7 +129,7 @@ impl Qualif for NeedsDrop { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - _: SubstsRef<'tcx>, + _: GenericArgsRef<'tcx>, ) -> bool { adt.has_dtor(cx.tcx) } @@ -153,12 +155,12 @@ impl Qualif for NeedsNonConstDrop { return false; } + // FIXME(effects) constness let obligation = Obligation::new( cx.tcx, ObligationCause::dummy_with_span(cx.body.span), cx.param_env, - ty::TraitRef::from_lang_item(cx.tcx, LangItem::Destruct, cx.body.span, [ty]) - .with_constness(ty::BoundConstness::ConstIfConst), + ty::TraitRef::from_lang_item(cx.tcx, LangItem::Destruct, cx.body.span, [ty]), ); let infcx = cx.tcx.infer_ctxt().build(); @@ -172,7 +174,7 @@ impl Qualif for NeedsNonConstDrop { if !matches!( impl_src, - ImplSource::ConstDestruct(_) | ImplSource::Param(_, ty::BoundConstness::ConstIfConst) + ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(_) ) { // If our const destruct candidate is not ConstDestruct or implied by the param env, // then it's bad @@ -193,7 +195,7 @@ impl Qualif for NeedsNonConstDrop { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, adt: AdtDef<'tcx>, - _: SubstsRef<'tcx>, + _: GenericArgsRef<'tcx>, ) -> bool { adt.has_non_const_dtor(cx.tcx) } @@ -221,9 +223,9 @@ impl Qualif for CustomEq { fn in_adt_inherently<'tcx>( cx: &ConstCx<'_, 'tcx>, def: AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> bool { - let ty = cx.tcx.mk_adt(def, substs); + let ty = Ty::new_adt(cx.tcx, def, args); !ty.is_structural_eq_shallow(cx.tcx) } } @@ -276,9 +278,9 @@ where Rvalue::Aggregate(kind, operands) => { // Return early if we know that the struct or enum being constructed is always // qualified. - if let AggregateKind::Adt(adt_did, _, substs, ..) = **kind { + if let AggregateKind::Adt(adt_did, _, args, ..) = **kind { let def = cx.tcx.adt_def(adt_did); - if Q::in_adt_inherently(cx, def, substs) { + if Q::in_adt_inherently(cx, def, args) { return true; } if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) { @@ -344,20 +346,23 @@ where }; // Check the qualifs of the value of `const` items. - // FIXME(valtrees): check whether const qualifs should behave the same - // way for type and mir constants. let uneval = match constant.literal { ConstantKind::Ty(ct) - if matches!(ct.kind(), ty::ConstKind::Param(_) | ty::ConstKind::Error(_)) => + if matches!( + ct.kind(), + ty::ConstKind::Param(_) | ty::ConstKind::Error(_) | ty::ConstKind::Value(_) + ) => { None } - ConstantKind::Ty(c) => bug!("expected ConstKind::Param here, found {:?}", c), + ConstantKind::Ty(c) => { + bug!("expected ConstKind::Param or ConstKind::Value here, found {:?}", c) + } ConstantKind::Unevaluated(uv, _) => Some(uv), ConstantKind::Val(..) => None, }; - if let Some(mir::UnevaluatedConst { def, substs: _, promoted }) = uneval { + if let Some(mir::UnevaluatedConst { def, args: _, promoted }) = uneval { // Use qualifs of the type for the promoted. Promoteds in MIR body should be possible // only for `NeedsNonConstDrop` with precise drop checking. This is the only const // check performed after the promotion. Verify that with an assertion. diff --git a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs index 78c74e1892dc5..a137f84b73824 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/resolver.rs @@ -4,10 +4,12 @@ use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::Visitor; -use rustc_middle::mir::{self, BasicBlock, Local, Location, Statement, StatementKind}; +use rustc_middle::mir::{ + self, BasicBlock, CallReturnPlaces, Local, Location, Statement, StatementKind, TerminatorEdges, +}; use rustc_mir_dataflow::fmt::DebugWithContext; use rustc_mir_dataflow::JoinSemiLattice; -use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces}; +use rustc_mir_dataflow::{Analysis, AnalysisDomain}; use std::fmt; use std::marker::PhantomData; @@ -103,7 +105,7 @@ where fn ref_allows_mutation(&self, kind: mir::BorrowKind, place: mir::Place<'tcx>) -> bool { match kind { mir::BorrowKind::Mut { .. } => true, - mir::BorrowKind::Shared | mir::BorrowKind::Shallow | mir::BorrowKind::Unique => { + mir::BorrowKind::Shared | mir::BorrowKind::Shallow => { self.shared_borrow_allows_mutation(place) } } @@ -337,7 +339,7 @@ where Q: Qualif, { fn apply_statement_effect( - &self, + &mut self, state: &mut Self::Domain, statement: &mir::Statement<'tcx>, location: Location, @@ -345,17 +347,18 @@ where self.transfer_function(state).visit_statement(statement, location); } - fn apply_terminator_effect( - &self, + fn apply_terminator_effect<'mir>( + &mut self, state: &mut Self::Domain, - terminator: &mir::Terminator<'tcx>, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { self.transfer_function(state).visit_terminator(terminator, location); + terminator.edges() } fn apply_call_return_effect( - &self, + &mut self, state: &mut Self::Domain, block: BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs index 0e2d9ee8fb2fb..d79c65f1d1fee 100644 --- a/compiler/rustc_const_eval/src/transform/promote_consts.rs +++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs @@ -14,11 +14,10 @@ use rustc_hir as hir; use rustc_middle::mir; -use rustc_middle::mir::traversal::ReversePostorderIter; use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; -use rustc_middle::ty::subst::InternalSubsts; -use rustc_middle::ty::{self, List, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::GenericArgs; +use rustc_middle::ty::{self, List, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use rustc_index::{Idx, IndexSlice, IndexVec}; @@ -53,9 +52,8 @@ impl<'tcx> MirPass<'tcx> for PromoteTemps<'tcx> { return; } - let mut rpo = traversal::reverse_postorder(body); let ccx = ConstCx::new(tcx, body); - let (mut temps, all_candidates) = collect_temps_and_candidates(&ccx, &mut rpo); + let (mut temps, all_candidates) = collect_temps_and_candidates(&ccx); let promotable_candidates = validate_candidates(&ccx, &mut temps, &all_candidates); @@ -166,14 +164,13 @@ impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> { pub fn collect_temps_and_candidates<'tcx>( ccx: &ConstCx<'_, 'tcx>, - rpo: &mut ReversePostorderIter<'_, 'tcx>, ) -> (IndexVec, Vec) { let mut collector = Collector { temps: IndexVec::from_elem(TempState::Undefined, &ccx.body.local_decls), candidates: vec![], ccx, }; - for (bb, data) in rpo { + for (bb, data) in traversal::reverse_postorder(ccx.body) { collector.visit_basic_block_data(bb, data); } (collector.temps, collector.candidates) @@ -457,7 +454,9 @@ impl<'tcx> Validator<'_, 'tcx> { match kind { // Reject these borrow types just to be safe. // FIXME(RalfJung): could we allow them? Should we? No point in it until we have a usecase. - BorrowKind::Shallow | BorrowKind::Unique => return Err(Unpromotable), + BorrowKind::Shallow | BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => { + return Err(Unpromotable); + } BorrowKind::Shared => { let has_mut_interior = self.qualif_local::(place.local); @@ -466,7 +465,9 @@ impl<'tcx> Validator<'_, 'tcx> { } } - BorrowKind::Mut { .. } => { + // FIXME: consider changing this to only promote &mut [] for default borrows, + // also forbidding two phase borrows + BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow } => { let ty = place.ty(self.body, self.tcx).ty; // In theory, any zero-sized value could be borrowed @@ -572,13 +573,18 @@ impl<'tcx> Validator<'_, 'tcx> { | BinOp::Gt | BinOp::Offset | BinOp::Add + | BinOp::AddUnchecked | BinOp::Sub + | BinOp::SubUnchecked | BinOp::Mul + | BinOp::MulUnchecked | BinOp::BitXor | BinOp::BitAnd | BinOp::BitOr | BinOp::Shl - | BinOp::Shr => {} + | BinOp::ShlUnchecked + | BinOp::Shr + | BinOp::ShrUnchecked => {} } self.validate_operand(lhs)?; @@ -753,11 +759,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let (mut rvalue, source_info) = { let statement = &mut self.source[loc.block].statements[loc.statement_index]; let StatementKind::Assign(box (_, rhs)) = &mut statement.kind else { - span_bug!( - statement.source_info.span, - "{:?} is not an assignment", - statement - ); + span_bug!(statement.source_info.span, "{:?} is not an assignment", statement); }; ( @@ -795,7 +797,9 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { }; match terminator.kind { - TerminatorKind::Call { mut func, mut args, from_hir_call, fn_span, .. } => { + TerminatorKind::Call { + mut func, mut args, call_source: desugar, fn_span, .. + } => { self.visit_operand(&mut func, loc); for arg in &mut args { self.visit_operand(arg, loc); @@ -811,7 +815,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { unwind: UnwindAction::Continue, destination: Place::from(new_temp), target: Some(new_target), - from_hir_call, + call_source: desugar, fn_span, }, source_info: SourceInfo::outermost(terminator.source_info.span), @@ -837,8 +841,8 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let mut promoted_operand = |ty, span| { promoted.span = span; promoted.local_decls[RETURN_PLACE] = LocalDecl::new(ty, span); - let substs = tcx.erase_regions(InternalSubsts::identity_for_item(tcx, def)); - let uneval = mir::UnevaluatedConst { def, substs, promoted: Some(promoted_id) }; + let args = tcx.erase_regions(GenericArgs::identity_for_item(tcx, def)); + let uneval = mir::UnevaluatedConst { def, args, promoted: Some(promoted_id) }; Operand::Constant(Box::new(Constant { span, @@ -851,7 +855,9 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let local_decls = &mut self.source.local_decls; let loc = candidate.location; let statement = &mut blocks[loc.block].statements[loc.statement_index]; - let StatementKind::Assign(box (_, Rvalue::Ref(region, borrow_kind, place))) = &mut statement.kind else { + let StatementKind::Assign(box (_, Rvalue::Ref(region, borrow_kind, place))) = + &mut statement.kind + else { bug!() }; @@ -859,7 +865,8 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { let ty = local_decls[place.local].ty; let span = statement.source_info.span; - let ref_ty = tcx.mk_ref( + let ref_ty = Ty::new_ref( + tcx, tcx.lifetimes.re_erased, ty::TypeAndMut { ty, mutbl: borrow_kind.to_mutbl_lossy() }, ); diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index 3c350e25ba6ec..783b52d004020 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -18,6 +18,7 @@ use rustc_mir_dataflow::impls::MaybeStorageLive; use rustc_mir_dataflow::storage::always_storage_live_locals; use rustc_mir_dataflow::{Analysis, ResultsCursor}; use rustc_target::abi::{Size, FIRST_VARIANT}; +use rustc_target::spec::abi::Abi; #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum EdgeKind { @@ -58,25 +59,48 @@ impl<'tcx> MirPass<'tcx> for Validator { .iterate_to_fixpoint() .into_results_cursor(body); - let mut checker = TypeChecker { + let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) { + // In this case `AbortUnwindingCalls` haven't yet been executed. + true + } else if !tcx.def_kind(def_id).is_fn_like() { + true + } else { + let body_ty = tcx.type_of(def_id).skip_binder(); + let body_abi = match body_ty.kind() { + ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), + ty::Closure(..) => Abi::RustCall, + ty::Generator(..) => Abi::Rust, + _ => { + span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase) + } + }; + + ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi) + }; + + let mut cfg_checker = CfgChecker { when: &self.when, body, tcx, - param_env, mir_phase, unwind_edge_count: 0, reachable_blocks: traversal::reachable_as_bitset(body), storage_liveness, - place_cache: Vec::new(), - value_cache: Vec::new(), + place_cache: FxHashSet::default(), + value_cache: FxHashSet::default(), + can_unwind, }; - checker.visit_body(body); - checker.check_cleanup_control_flow(); + cfg_checker.visit_body(body); + cfg_checker.check_cleanup_control_flow(); + + for (location, msg) in validate_types(tcx, self.mir_phase, param_env, body) { + cfg_checker.fail(location, msg); + } if let MirPhase::Runtime(_) = body.phase { if let ty::InstanceDef::Item(_) = body.source.instance { if body.has_free_regions() { - checker.fail( + cfg_checker.fail( Location::START, format!("Free regions in optimized {} MIR", body.phase.name()), ); @@ -86,20 +110,22 @@ impl<'tcx> MirPass<'tcx> for Validator { } } -struct TypeChecker<'a, 'tcx> { +struct CfgChecker<'a, 'tcx> { when: &'a str, body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, mir_phase: MirPhase, unwind_edge_count: usize, reachable_blocks: BitSet, storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>, - place_cache: Vec>, - value_cache: Vec, + place_cache: FxHashSet>, + value_cache: FxHashSet, + // If `false`, then the MIR must not contain `UnwindAction::Continue` or + // `TerminatorKind::Resume`. + can_unwind: bool, } -impl<'a, 'tcx> TypeChecker<'a, 'tcx> { +impl<'a, 'tcx> CfgChecker<'a, 'tcx> { #[track_caller] fn fail(&self, location: Location, msg: impl AsRef) { let span = self.body.source_info(location).span; @@ -147,7 +173,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } } } else { - self.fail(location, format!("encountered jump to invalid basic block {:?}", bb)) + self.fail(location, format!("encountered jump to invalid basic block {bb:?}")) } } @@ -214,16 +240,13 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { stack.clear(); stack.insert(bb); loop { - let Some(parent)= parent[bb].take() else { - break - }; + let Some(parent) = parent[bb].take() else { break }; let no_cycle = stack.insert(parent); if !no_cycle { self.fail( Location { block: bb, statement_index: 0 }, format!( - "Cleanup control flow violation: Cycle involving edge {:?} -> {:?}", - bb, parent, + "Cleanup control flow violation: Cycle involving edge {bb:?} -> {parent:?}", ), ); break; @@ -238,47 +261,30 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { match unwind { UnwindAction::Cleanup(unwind) => { if is_cleanup { - self.fail(location, "unwind on cleanup block"); + self.fail(location, "`UnwindAction::Cleanup` in cleanup block"); } self.check_edge(location, unwind, EdgeKind::Unwind); } UnwindAction::Continue => { if is_cleanup { - self.fail(location, "unwind on cleanup block"); + self.fail(location, "`UnwindAction::Continue` in cleanup block"); + } + + if !self.can_unwind { + self.fail(location, "`UnwindAction::Continue` in no-unwind function"); } } UnwindAction::Unreachable | UnwindAction::Terminate => (), } } - - /// Check if src can be assigned into dest. - /// This is not precise, it will accept some incorrect assignments. - fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool { - // Fast path before we normalize. - if src == dest { - // Equal types, all is good. - return true; - } - - // We sometimes have to use `defining_opaque_types` for subtyping - // to succeed here and figuring out how exactly that should work - // is annoying. It is harmless enough to just not validate anything - // in that case. We still check this after analysis as all opaque - // types have been revealed at this point. - if (src, dest).has_opaque_types() { - return true; - } - - crate::util::is_subtype(self.tcx, self.param_env, src, dest) - } } -impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { +impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) { if self.body.local_decls.get(local).is_none() { self.fail( location, - format!("local {:?} has no corresponding declaration in `body.local_decls`", local), + format!("local {local:?} has no corresponding declaration in `body.local_decls`"), ); } @@ -293,11 +299,286 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.storage_liveness.seek_after_primary_effect(location); let locals_with_storage = self.storage_liveness.get(); if !locals_with_storage.contains(local) { - self.fail(location, format!("use of local {:?}, which has no storage here", local)); + self.fail(location, format!("use of local {local:?}, which has no storage here")); + } + } + } + + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + match &statement.kind { + StatementKind::Assign(box (dest, rvalue)) => { + // FIXME(JakobDegen): Check this for all rvalues, not just this one. + if let Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) = rvalue { + // The sides of an assignment must not alias. Currently this just checks whether + // the places are identical. + if dest == src { + self.fail( + location, + "encountered `Assign` statement with overlapping memory", + ); + } + } + } + StatementKind::AscribeUserType(..) => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`AscribeUserType` should have been removed after drop lowering phase", + ); + } + } + StatementKind::FakeRead(..) => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`FakeRead` should have been removed after drop lowering phase", + ); + } + } + StatementKind::SetDiscriminant { .. } => { + if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`SetDiscriminant`is not allowed until deaggregation"); + } + } + StatementKind::Deinit(..) => { + if self.mir_phase < MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`Deinit`is not allowed until deaggregation"); + } } + StatementKind::Retag(kind, _) => { + // FIXME(JakobDegen) The validator should check that `self.mir_phase < + // DropsLowered`. However, this causes ICEs with generation of drop shims, which + // seem to fail to set their `MirPhase` correctly. + if matches!(kind, RetagKind::Raw | RetagKind::TwoPhase) { + self.fail(location, format!("explicit `{kind:?}` is forbidden")); + } + } + StatementKind::StorageLive(local) => { + // We check that the local is not live when entering a `StorageLive` for it. + // Technically, violating this restriction is only UB and not actually indicative + // of not well-formed MIR. This means that an optimization which turns MIR that + // already has UB into MIR that fails this check is not necessarily wrong. However, + // we have no such optimizations at the moment, and so we include this check anyway + // to help us catch bugs. If you happen to write an optimization that might cause + // this to incorrectly fire, feel free to remove this check. + if self.reachable_blocks.contains(location.block) { + self.storage_liveness.seek_before_primary_effect(location); + let locals_with_storage = self.storage_liveness.get(); + if locals_with_storage.contains(*local) { + self.fail( + location, + format!("StorageLive({local:?}) which already has storage here"), + ); + } + } + } + StatementKind::StorageDead(_) + | StatementKind::Intrinsic(_) + | StatementKind::Coverage(_) + | StatementKind::ConstEvalCounter + | StatementKind::PlaceMention(..) + | StatementKind::Nop => {} } + + self.super_statement(statement, location); + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + match &terminator.kind { + TerminatorKind::Goto { target } => { + self.check_edge(location, *target, EdgeKind::Normal); + } + TerminatorKind::SwitchInt { targets, discr: _ } => { + for (_, target) in targets.iter() { + self.check_edge(location, target, EdgeKind::Normal); + } + self.check_edge(location, targets.otherwise(), EdgeKind::Normal); + + self.value_cache.clear(); + self.value_cache.extend(targets.iter().map(|(value, _)| value)); + let has_duplicates = targets.iter().len() != self.value_cache.len(); + if has_duplicates { + self.fail( + location, + format!( + "duplicated values in `SwitchInt` terminator: {:?}", + terminator.kind, + ), + ); + } + } + TerminatorKind::Drop { target, unwind, .. } => { + self.check_edge(location, *target, EdgeKind::Normal); + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::Call { args, destination, target, unwind, .. } => { + if let Some(target) = target { + self.check_edge(location, *target, EdgeKind::Normal); + } + self.check_unwind_edge(location, *unwind); + + // The call destination place and Operand::Move place used as an argument might be + // passed by a reference to the callee. Consequently they must be non-overlapping. + // Currently this simply checks for duplicate places. + self.place_cache.clear(); + self.place_cache.insert(destination.as_ref()); + let mut has_duplicates = false; + for arg in args { + if let Operand::Move(place) = arg { + has_duplicates |= !self.place_cache.insert(place.as_ref()); + } + } + + if has_duplicates { + self.fail( + location, + format!( + "encountered overlapping memory in `Call` terminator: {:?}", + terminator.kind, + ), + ); + } + } + TerminatorKind::Assert { target, unwind, .. } => { + self.check_edge(location, *target, EdgeKind::Normal); + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::Yield { resume, drop, .. } => { + if self.body.generator.is_none() { + self.fail(location, "`Yield` cannot appear outside generator bodies"); + } + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail(location, "`Yield` should have been replaced by generator lowering"); + } + self.check_edge(location, *resume, EdgeKind::Normal); + if let Some(drop) = drop { + self.check_edge(location, *drop, EdgeKind::Normal); + } + } + TerminatorKind::FalseEdge { real_target, imaginary_target } => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`FalseEdge` should have been removed after drop elaboration", + ); + } + self.check_edge(location, *real_target, EdgeKind::Normal); + self.check_edge(location, *imaginary_target, EdgeKind::Normal); + } + TerminatorKind::FalseUnwind { real_target, unwind } => { + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`FalseUnwind` should have been removed after drop elaboration", + ); + } + self.check_edge(location, *real_target, EdgeKind::Normal); + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::InlineAsm { destination, unwind, .. } => { + if let Some(destination) = destination { + self.check_edge(location, *destination, EdgeKind::Normal); + } + self.check_unwind_edge(location, *unwind); + } + TerminatorKind::GeneratorDrop => { + if self.body.generator.is_none() { + self.fail(location, "`GeneratorDrop` cannot appear outside generator bodies"); + } + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { + self.fail( + location, + "`GeneratorDrop` should have been replaced by generator lowering", + ); + } + } + TerminatorKind::Resume => { + let bb = location.block; + if !self.body.basic_blocks[bb].is_cleanup { + self.fail(location, "Cannot `Resume` from non-cleanup basic block") + } + if !self.can_unwind { + self.fail(location, "Cannot `Resume` in a function that cannot unwind") + } + } + TerminatorKind::Terminate => { + let bb = location.block; + if !self.body.basic_blocks[bb].is_cleanup { + self.fail(location, "Cannot `Terminate` from non-cleanup basic block") + } + } + TerminatorKind::Return => { + let bb = location.block; + if self.body.basic_blocks[bb].is_cleanup { + self.fail(location, "Cannot `Return` from cleanup basic block") + } + } + TerminatorKind::Unreachable => {} + } + + self.super_terminator(terminator, location); } + fn visit_source_scope(&mut self, scope: SourceScope) { + if self.body.source_scopes.get(scope).is_none() { + self.tcx.sess.diagnostic().delay_span_bug( + self.body.span, + format!( + "broken MIR in {:?} ({}):\ninvalid source scope {:?}", + self.body.source.instance, self.when, scope, + ), + ); + } + } +} + +pub fn validate_types<'tcx>( + tcx: TyCtxt<'tcx>, + mir_phase: MirPhase, + param_env: ty::ParamEnv<'tcx>, + body: &Body<'tcx>, +) -> Vec<(Location, String)> { + let mut type_checker = TypeChecker { body, tcx, param_env, mir_phase, failures: Vec::new() }; + type_checker.visit_body(body); + type_checker.failures +} + +struct TypeChecker<'a, 'tcx> { + body: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + mir_phase: MirPhase, + failures: Vec<(Location, String)>, +} + +impl<'a, 'tcx> TypeChecker<'a, 'tcx> { + fn fail(&mut self, location: Location, msg: impl Into) { + self.failures.push((location, msg.into())); + } + + /// Check if src can be assigned into dest. + /// This is not precise, it will accept some incorrect assignments. + fn mir_assign_valid_types(&self, src: Ty<'tcx>, dest: Ty<'tcx>) -> bool { + // Fast path before we normalize. + if src == dest { + // Equal types, all is good. + return true; + } + + // We sometimes have to use `defining_opaque_types` for subtyping + // to succeed here and figuring out how exactly that should work + // is annoying. It is harmless enough to just not validate anything + // in that case. We still check this after analysis as all opaque + // types have been revealed at this point. + if (src, dest).has_opaque_types() { + return true; + } + + crate::util::is_subtype(self.tcx, self.param_env, src, dest) + } +} + +impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { // This check is somewhat expensive, so only run it when -Zvalidate-mir is passed. if self.tcx.sess.opts.unstable_opts.validate_mir @@ -308,7 +589,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { let ty = place.ty(&self.body.local_decls, self.tcx).ty; if !ty.is_copy_modulo_regions(self.tcx, self.param_env) { - self.fail(location, format!("`Operand::Copy` with non-`Copy` type {}", ty)); + self.fail(location, format!("`Operand::Copy` with non-`Copy` type {ty}")); } } } @@ -318,8 +599,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_projection_elem( &mut self, - local: Local, - proj_base: &[PlaceElem<'tcx>], + place_ref: PlaceRef<'tcx>, elem: PlaceElem<'tcx>, context: PlaceContext, location: Location, @@ -328,42 +608,40 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ProjectionElem::Index(index) => { let index_ty = self.body.local_decls[index].ty; if index_ty != self.tcx.types.usize { - self.fail(location, format!("bad index ({:?} != usize)", index_ty)) + self.fail(location, format!("bad index ({index_ty:?} != usize)")) } } ProjectionElem::Deref if self.mir_phase >= MirPhase::Runtime(RuntimePhase::PostCleanup) => { - let base_ty = Place::ty_from(local, proj_base, &self.body.local_decls, self.tcx).ty; + let base_ty = place_ref.ty(&self.body.local_decls, self.tcx).ty; if base_ty.is_box() { self.fail( location, - format!("{:?} dereferenced after ElaborateBoxDerefs", base_ty), + format!("{base_ty:?} dereferenced after ElaborateBoxDerefs"), ) } } ProjectionElem::Field(f, ty) => { - let parent = Place { local, projection: self.tcx.mk_place_elems(proj_base) }; - let parent_ty = parent.ty(&self.body.local_decls, self.tcx); - let fail_out_of_bounds = |this: &Self, location| { - this.fail(location, format!("Out of bounds field {:?} for {:?}", f, parent_ty)); + let parent_ty = place_ref.ty(&self.body.local_decls, self.tcx); + let fail_out_of_bounds = |this: &mut Self, location| { + this.fail(location, format!("Out of bounds field {f:?} for {parent_ty:?}")); }; - let check_equal = |this: &Self, location, f_ty| { + let check_equal = |this: &mut Self, location, f_ty| { if !this.mir_assign_valid_types(ty, f_ty) { this.fail( location, format!( - "Field projection `{:?}.{:?}` specified type `{:?}`, but actual type is `{:?}`", - parent, f, ty, f_ty + "Field projection `{place_ref:?}.{f:?}` specified type `{ty:?}`, but actual type is `{f_ty:?}`" ) ) } }; let kind = match parent_ty.ty.kind() { - &ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - self.tcx.type_of(def_id).subst(self.tcx, substs).kind() + &ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { + self.tcx.type_of(def_id).instantiate(self.tcx, args).kind() } kind => kind, }; @@ -376,23 +654,23 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { }; check_equal(self, location, *f_ty); } - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { let var = parent_ty.variant_index.unwrap_or(FIRST_VARIANT); let Some(field) = adt_def.variant(var).fields.get(f) else { fail_out_of_bounds(self, location); return; }; - check_equal(self, location, field.ty(self.tcx, substs)); + check_equal(self, location, field.ty(self.tcx, args)); } - ty::Closure(_, substs) => { - let substs = substs.as_closure(); - let Some(f_ty) = substs.upvar_tys().nth(f.as_usize()) else { + ty::Closure(_, args) => { + let args = args.as_closure(); + let Some(&f_ty) = args.upvar_tys().get(f.as_usize()) else { fail_out_of_bounds(self, location); return; }; check_equal(self, location, f_ty); } - &ty::Generator(def_id, substs, _) => { + &ty::Generator(def_id, args, _) => { let f_ty = if let Some(var) = parent_ty.variant_index { let gen_body = if def_id == self.body.source.def_id() { self.body @@ -401,7 +679,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { }; let Some(layout) = gen_body.generator_layout() else { - self.fail(location, format!("No generator layout for {:?}", parent_ty)); + self.fail( + location, + format!("No generator layout for {parent_ty:?}"), + ); return; }; @@ -411,13 +692,17 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { }; let Some(f_ty) = layout.field_tys.get(local) else { - self.fail(location, format!("Out of bounds local {:?} for {:?}", local, parent_ty)); + self.fail( + location, + format!("Out of bounds local {local:?} for {parent_ty:?}"), + ); return; }; - f_ty.ty + ty::EarlyBinder::bind(f_ty.ty).instantiate(self.tcx, args) } else { - let Some(f_ty) = substs.as_generator().prefix_tys().nth(f.index()) else { + let Some(&f_ty) = args.as_generator().prefix_tys().get(f.index()) + else { fail_out_of_bounds(self, location); return; }; @@ -434,13 +719,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } _ => {} } - self.super_projection_elem(local, proj_base, elem, context, location); + self.super_projection_elem(place_ref, elem, context, location); } fn visit_var_debug_info(&mut self, debuginfo: &VarDebugInfo<'tcx>) { - let check_place = |place: Place<'_>| { + let check_place = |this: &mut Self, place: Place<'_>| { if place.projection.iter().any(|p| !p.can_use_in_debuginfo()) { - self.fail( + this.fail( START_BLOCK.start_location(), format!("illegal place {:?} in debuginfo for {:?}", place, debuginfo.name), ); @@ -449,21 +734,15 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { match debuginfo.value { VarDebugInfoContents::Const(_) => {} VarDebugInfoContents::Place(place) => { - check_place(place); - if debuginfo.references != 0 && place.projection.last() == Some(&PlaceElem::Deref) { - self.fail( - START_BLOCK.start_location(), - format!("debuginfo {:?}, has both ref and deref", debuginfo), - ); - } + check_place(self, place); } VarDebugInfoContents::Composite { ty, ref fragments } => { for f in fragments { - check_place(f.contents); + check_place(self, f.contents); if ty.is_union() || ty.is_enum() { self.fail( START_BLOCK.start_location(), - format!("invalid type {:?} for composite debuginfo", ty), + format!("invalid type {ty:?} for composite debuginfo"), ); } if f.projection.iter().any(|p| !matches!(p, PlaceElem::Field(..))) { @@ -490,7 +769,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { && cntxt != PlaceContext::NonUse(NonUseContext::VarDebugInfo) && place.projection[1..].contains(&ProjectionElem::Deref) { - self.fail(location, format!("{:?}, has deref at the wrong place", place)); + self.fail(location, format!("{place:?}, has deref at the wrong place")); } self.super_place(place, cntxt, location); @@ -498,8 +777,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { macro_rules! check_kinds { - ($t:expr, $text:literal, $($patterns:tt)*) => { - if !matches!(($t).kind(), $($patterns)*) { + ($t:expr, $text:literal, $typat:pat) => { + if !matches!(($t).kind(), $typat) { self.fail(location, format!($text, $t)); } }; @@ -527,18 +806,37 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { use BinOp::*; let a = vals.0.ty(&self.body.local_decls, self.tcx); let b = vals.1.ty(&self.body.local_decls, self.tcx); + if crate::util::binop_right_homogeneous(*op) { + if let Eq | Lt | Le | Ne | Ge | Gt = op { + // The function pointer types can have lifetimes + if !self.mir_assign_valid_types(a, b) { + self.fail( + location, + format!("Cannot {op:?} compare incompatible types {a:?} and {b:?}"), + ); + } + } else if a != b { + self.fail( + location, + format!( + "Cannot perform binary op {op:?} on unequal types {a:?} and {b:?}" + ), + ); + } + } + match op { Offset => { check_kinds!(a, "Cannot offset non-pointer type {:?}", ty::RawPtr(..)); if b != self.tcx.types.isize && b != self.tcx.types.usize { - self.fail(location, format!("Cannot offset by non-isize type {:?}", b)); + self.fail(location, format!("Cannot offset by non-isize type {b:?}")); } } Eq | Lt | Le | Ne | Ge | Gt => { for x in [a, b] { check_kinds!( x, - "Cannot compare type {:?}", + "Cannot {op:?} compare type {:?}", ty::Bool | ty::Char | ty::Int(..) @@ -548,19 +846,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { | ty::FnPtr(..) ) } - // The function pointer types can have lifetimes - if !self.mir_assign_valid_types(a, b) { - self.fail( - location, - format!("Cannot compare unequal types {:?} and {:?}", a, b), - ); - } } - Shl | Shr => { + AddUnchecked | SubUnchecked | MulUnchecked | Shl | ShlUnchecked | Shr + | ShrUnchecked => { for x in [a, b] { check_kinds!( x, - "Cannot shift non-integer type {:?}", + "Cannot {op:?} non-integer type {:?}", ty::Uint(..) | ty::Int(..) ) } @@ -569,37 +861,19 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { for x in [a, b] { check_kinds!( x, - "Cannot perform bitwise op on type {:?}", + "Cannot perform bitwise op {op:?} on type {:?}", ty::Uint(..) | ty::Int(..) | ty::Bool ) } - if a != b { - self.fail( - location, - format!( - "Cannot perform bitwise op on unequal types {:?} and {:?}", - a, b - ), - ); - } } Add | Sub | Mul | Div | Rem => { for x in [a, b] { check_kinds!( x, - "Cannot perform arithmetic on type {:?}", + "Cannot perform arithmetic {op:?} on type {:?}", ty::Uint(..) | ty::Int(..) | ty::Float(..) ) } - if a != b { - self.fail( - location, - format!( - "Cannot perform arithmetic on unequal types {:?} and {:?}", - a, b - ), - ); - } } } } @@ -620,13 +894,12 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.fail( location, format!( - "Cannot perform checked arithmetic on unequal types {:?} and {:?}", - a, b + "Cannot perform checked arithmetic on unequal types {a:?} and {b:?}" ), ); } } - _ => self.fail(location, format!("There is no checked version of {:?}", op)), + _ => self.fail(location, format!("There is no checked version of {op:?}")), } } Rvalue::UnaryOp(op, operand) => { @@ -657,7 +930,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { // FIXME: Add Checks for these CastKind::PointerFromExposedAddress | CastKind::PointerExposeAddress - | CastKind::Pointer(_) => {} + | CastKind::PointerCoercion(_) => {} CastKind::IntToInt | CastKind::IntToFloat => { let input_valid = op_ty.is_integral() || op_ty.is_char() || op_ty.is_bool(); let target_valid = target_type.is_numeric() || target_type.is_char(); @@ -721,7 +994,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } Rvalue::NullaryOp(NullOp::OffsetOf(fields), container) => { - let fail_out_of_bounds = |this: &Self, location, field, ty| { + let fail_out_of_bounds = |this: &mut Self, location, field, ty| { this.fail(location, format!("Out of bounds field {field:?} for {ty:?}")); }; @@ -737,7 +1010,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty); } - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { if adt_def.is_enum() { self.fail( location, @@ -751,7 +1024,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { return; }; - let f_ty = field.ty(self.tcx, substs); + let f_ty = field.ty(self.tcx, args); current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty); } _ => { @@ -831,7 +1104,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { if !ty.is_bool() { self.fail( location, - format!("`assume` argument must be `bool`, but got: `{}`", ty), + format!("`assume` argument must be `bool`, but got: `{ty}`"), ); } } @@ -844,7 +1117,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } else { self.fail( location, - format!("Expected src to be ptr in copy_nonoverlapping, got: {}", src_ty), + format!("Expected src to be ptr in copy_nonoverlapping, got: {src_ty}"), ); return; }; @@ -854,19 +1127,19 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } else { self.fail( location, - format!("Expected dst to be ptr in copy_nonoverlapping, got: {}", dst_ty), + format!("Expected dst to be ptr in copy_nonoverlapping, got: {dst_ty}"), ); return; }; // since CopyNonOverlapping is parametrized by 1 type, // we only need to check that they are equal and not keep an extra parameter. if !self.mir_assign_valid_types(op_src_ty, op_dst_ty) { - self.fail(location, format!("bad arg ({:?} != {:?})", op_src_ty, op_dst_ty)); + self.fail(location, format!("bad arg ({op_src_ty:?} != {op_dst_ty:?})")); } let op_cnt_ty = count.ty(&self.body.local_decls, self.tcx); if op_cnt_ty != self.tcx.types.usize { - self.fail(location, format!("bad arg ({:?} != usize)", op_cnt_ty)) + self.fail(location, format!("bad arg ({op_cnt_ty:?} != usize)")) } } StatementKind::SetDiscriminant { place, .. } => { @@ -878,8 +1151,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.fail( location, format!( - "`SetDiscriminant` is only allowed on ADTs and generators, not {:?}", - pty + "`SetDiscriminant` is only allowed on ADTs and generators, not {pty:?}" ), ); } @@ -894,29 +1166,11 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { // DropsLowered`. However, this causes ICEs with generation of drop shims, which // seem to fail to set their `MirPhase` correctly. if matches!(kind, RetagKind::Raw | RetagKind::TwoPhase) { - self.fail(location, format!("explicit `{:?}` is forbidden", kind)); + self.fail(location, format!("explicit `{kind:?}` is forbidden")); } } - StatementKind::StorageLive(local) => { - // We check that the local is not live when entering a `StorageLive` for it. - // Technically, violating this restriction is only UB and not actually indicative - // of not well-formed MIR. This means that an optimization which turns MIR that - // already has UB into MIR that fails this check is not necessarily wrong. However, - // we have no such optimizations at the moment, and so we include this check anyway - // to help us catch bugs. If you happen to write an optimization that might cause - // this to incorrectly fire, feel free to remove this check. - if self.reachable_blocks.contains(location.block) { - self.storage_liveness.seek_before_primary_effect(location); - let locals_with_storage = self.storage_liveness.get(); - if locals_with_storage.contains(*local) { - self.fail( - location, - format!("StorageLive({local:?}) which already has storage here"), - ); - } - } - } - StatementKind::StorageDead(_) + StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) | StatementKind::Coverage(_) | StatementKind::ConstEvalCounter | StatementKind::PlaceMention(..) @@ -928,9 +1182,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { match &terminator.kind { - TerminatorKind::Goto { target } => { - self.check_edge(location, *target, EdgeKind::Normal); - } TerminatorKind::SwitchInt { targets, discr } => { let switch_ty = discr.ty(&self.body.local_decls, self.tcx); @@ -944,169 +1195,49 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { other => bug!("unhandled type: {:?}", other), }); - for (value, target) in targets.iter() { + for (value, _) in targets.iter() { if Scalar::<()>::try_from_uint(value, size).is_none() { self.fail( location, - format!("the value {:#x} is not a proper {:?}", value, switch_ty), + format!("the value {value:#x} is not a proper {switch_ty:?}"), ) } - - self.check_edge(location, target, EdgeKind::Normal); } - self.check_edge(location, targets.otherwise(), EdgeKind::Normal); - - self.value_cache.clear(); - self.value_cache.extend(targets.iter().map(|(value, _)| value)); - let all_len = self.value_cache.len(); - self.value_cache.sort_unstable(); - self.value_cache.dedup(); - let has_duplicates = all_len != self.value_cache.len(); - if has_duplicates { - self.fail( - location, - format!( - "duplicated values in `SwitchInt` terminator: {:?}", - terminator.kind, - ), - ); - } - } - TerminatorKind::Drop { target, unwind, .. } => { - self.check_edge(location, *target, EdgeKind::Normal); - self.check_unwind_edge(location, *unwind); } - TerminatorKind::Call { func, args, destination, target, unwind, .. } => { + TerminatorKind::Call { func, .. } => { let func_ty = func.ty(&self.body.local_decls, self.tcx); match func_ty.kind() { ty::FnPtr(..) | ty::FnDef(..) => {} _ => self.fail( location, - format!("encountered non-callable type {} in `Call` terminator", func_ty), + format!("encountered non-callable type {func_ty} in `Call` terminator"), ), } - if let Some(target) = target { - self.check_edge(location, *target, EdgeKind::Normal); - } - self.check_unwind_edge(location, *unwind); - - // The call destination place and Operand::Move place used as an argument might be - // passed by a reference to the callee. Consequently they must be non-overlapping. - // Currently this simply checks for duplicate places. - self.place_cache.clear(); - self.place_cache.push(destination.as_ref()); - for arg in args { - if let Operand::Move(place) = arg { - self.place_cache.push(place.as_ref()); - } - } - let all_len = self.place_cache.len(); - let mut dedup = FxHashSet::default(); - self.place_cache.retain(|p| dedup.insert(*p)); - let has_duplicates = all_len != self.place_cache.len(); - if has_duplicates { - self.fail( - location, - format!( - "encountered overlapping memory in `Call` terminator: {:?}", - terminator.kind, - ), - ); - } } - TerminatorKind::Assert { cond, target, unwind, .. } => { + TerminatorKind::Assert { cond, .. } => { let cond_ty = cond.ty(&self.body.local_decls, self.tcx); if cond_ty != self.tcx.types.bool { self.fail( location, format!( - "encountered non-boolean condition of type {} in `Assert` terminator", - cond_ty + "encountered non-boolean condition of type {cond_ty} in `Assert` terminator" ), ); } - self.check_edge(location, *target, EdgeKind::Normal); - self.check_unwind_edge(location, *unwind); } - TerminatorKind::Yield { resume, drop, .. } => { - if self.body.generator.is_none() { - self.fail(location, "`Yield` cannot appear outside generator bodies"); - } - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail(location, "`Yield` should have been replaced by generator lowering"); - } - self.check_edge(location, *resume, EdgeKind::Normal); - if let Some(drop) = drop { - self.check_edge(location, *drop, EdgeKind::Normal); - } - } - TerminatorKind::FalseEdge { real_target, imaginary_target } => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`FalseEdge` should have been removed after drop elaboration", - ); - } - self.check_edge(location, *real_target, EdgeKind::Normal); - self.check_edge(location, *imaginary_target, EdgeKind::Normal); - } - TerminatorKind::FalseUnwind { real_target, unwind } => { - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`FalseUnwind` should have been removed after drop elaboration", - ); - } - self.check_edge(location, *real_target, EdgeKind::Normal); - self.check_unwind_edge(location, *unwind); - } - TerminatorKind::InlineAsm { destination, unwind, .. } => { - if let Some(destination) = destination { - self.check_edge(location, *destination, EdgeKind::Normal); - } - self.check_unwind_edge(location, *unwind); - } - TerminatorKind::GeneratorDrop => { - if self.body.generator.is_none() { - self.fail(location, "`GeneratorDrop` cannot appear outside generator bodies"); - } - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Initial) { - self.fail( - location, - "`GeneratorDrop` should have been replaced by generator lowering", - ); - } - } - TerminatorKind::Resume | TerminatorKind::Terminate => { - let bb = location.block; - if !self.body.basic_blocks[bb].is_cleanup { - self.fail( - location, - "Cannot `Resume` or `Terminate` from non-cleanup basic block", - ) - } - } - TerminatorKind::Return => { - let bb = location.block; - if self.body.basic_blocks[bb].is_cleanup { - self.fail(location, "Cannot `Return` from cleanup basic block") - } - } - TerminatorKind::Unreachable => {} + TerminatorKind::Goto { .. } + | TerminatorKind::Drop { .. } + | TerminatorKind::Yield { .. } + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } + | TerminatorKind::GeneratorDrop + | TerminatorKind::Resume + | TerminatorKind::Terminate + | TerminatorKind::Return + | TerminatorKind::Unreachable => {} } self.super_terminator(terminator, location); } - - fn visit_source_scope(&mut self, scope: SourceScope) { - if self.body.source_scopes.get(scope).is_none() { - self.tcx.sess.diagnostic().delay_span_bug( - self.body.span, - format!( - "broken MIR in {:?} ({}):\ninvalid source scope {:?}", - self.body.source.instance, self.when, scope, - ), - ); - } - } } diff --git a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs index 23fcd22c52b8e..2d1970791cacd 100644 --- a/compiler/rustc_const_eval/src/util/check_validity_requirement.rs +++ b/compiler/rustc_const_eval/src/util/check_validity_requirement.rs @@ -1,9 +1,8 @@ use rustc_middle::ty::layout::{LayoutCx, LayoutError, LayoutOf, TyAndLayout, ValidityRequirement}; use rustc_middle::ty::{ParamEnv, ParamEnvAnd, Ty, TyCtxt}; -use rustc_session::Limit; use rustc_target::abi::{Abi, FieldsShape, Scalar, Variants}; -use crate::const_eval::{CheckAlignment, CompileTimeInterpreter}; +use crate::const_eval::{CanAccessStatics, CheckAlignment, CompileTimeInterpreter}; use crate::interpret::{InterpCx, MemoryKind, OpTy}; /// Determines if this type permits "raw" initialization by just transmuting some memory into an @@ -22,7 +21,7 @@ pub fn check_validity_requirement<'tcx>( tcx: TyCtxt<'tcx>, kind: ValidityRequirement, param_env_and_ty: ParamEnvAnd<'tcx, Ty<'tcx>>, -) -> Result> { +) -> Result> { let layout = tcx.layout_of(param_env_and_ty)?; // There is nothing strict or lax about inhabitedness. @@ -44,12 +43,8 @@ fn might_permit_raw_init_strict<'tcx>( ty: TyAndLayout<'tcx>, tcx: TyCtxt<'tcx>, kind: ValidityRequirement, -) -> Result> { - let machine = CompileTimeInterpreter::new( - Limit::new(0), - /*can_access_statics:*/ false, - CheckAlignment::Error, - ); +) -> Result> { + let machine = CompileTimeInterpreter::new(CanAccessStatics::No, CheckAlignment::Error); let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine); @@ -80,7 +75,7 @@ fn might_permit_raw_init_lax<'tcx>( this: TyAndLayout<'tcx>, cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, init_kind: ValidityRequirement, -) -> Result> { +) -> Result> { let scalar_allows_raw_init = move |s: Scalar| -> bool { match init_kind { ValidityRequirement::Inhabited => { diff --git a/compiler/rustc_const_eval/src/util/compare_types.rs b/compiler/rustc_const_eval/src/util/compare_types.rs index d6a2ffb75111a..83376c8e99289 100644 --- a/compiler/rustc_const_eval/src/util/compare_types.rs +++ b/compiler/rustc_const_eval/src/util/compare_types.rs @@ -56,8 +56,16 @@ pub fn is_subtype<'tcx>( // With `Reveal::All`, opaque types get normalized away, with `Reveal::UserFacing` // we would get unification errors because we're unable to look into opaque types, // even if they're constrained in our current function. - // - // It seems very unlikely that this hides any bugs. - let _ = infcx.take_opaque_types(); + for (key, ty) in infcx.take_opaque_types() { + let hidden_ty = tcx.type_of(key.def_id).instantiate(tcx, key.args); + if hidden_ty != ty.hidden_type.ty { + span_bug!( + ty.hidden_type.span, + "{}, {}", + tcx.type_of(key.def_id).instantiate(tcx, key.args), + ty.hidden_type.ty + ); + } + } errors.is_empty() } diff --git a/compiler/rustc_const_eval/src/util/mod.rs b/compiler/rustc_const_eval/src/util/mod.rs index 7641f560714d4..289e342259547 100644 --- a/compiler/rustc_const_eval/src/util/mod.rs +++ b/compiler/rustc_const_eval/src/util/mod.rs @@ -1,3 +1,5 @@ +use rustc_middle::mir; + mod alignment; mod check_validity_requirement; mod compare_types; @@ -7,3 +9,27 @@ pub use self::alignment::is_disaligned; pub use self::check_validity_requirement::check_validity_requirement; pub use self::compare_types::{is_equal_up_to_subtyping, is_subtype}; pub use self::type_name::type_name; + +/// Classify whether an operator is "left-homogeneous", i.e., the LHS has the +/// same type as the result. +#[inline] +pub(crate) fn binop_left_homogeneous(op: mir::BinOp) -> bool { + use rustc_middle::mir::BinOp::*; + match op { + Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor + | BitAnd | BitOr | Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => true, + Eq | Ne | Lt | Le | Gt | Ge => false, + } +} + +/// Classify whether an operator is "right-homogeneous", i.e., the RHS has the +/// same type as the LHS. +#[inline] +pub(crate) fn binop_right_homogeneous(op: mir::BinOp) -> bool { + use rustc_middle::mir::BinOp::*; + match op { + Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor + | BitAnd | BitOr | Eq | Ne | Lt | Le | Gt | Ge => true, + Offset | Shl | ShlUnchecked | Shr | ShrUnchecked => false, + } +} diff --git a/compiler/rustc_const_eval/src/util/type_name.rs b/compiler/rustc_const_eval/src/util/type_name.rs index 11ad5b49df2ef..14a840ad1b1e8 100644 --- a/compiler/rustc_const_eval/src/util/type_name.rs +++ b/compiler/rustc_const_eval/src/util/type_name.rs @@ -4,8 +4,7 @@ use rustc_hir::definitions::DisambiguatedDefPathData; use rustc_middle::ty::{ self, print::{PrettyPrinter, Print, Printer}, - subst::{GenericArg, GenericArgKind}, - Ty, TyCtxt, + GenericArg, GenericArgKind, Ty, TyCtxt, }; use std::fmt::Write; @@ -56,13 +55,14 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { } // Types with identity (print the module path). - ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did: def_id, .. }, _)), substs) - | ty::FnDef(def_id, substs) - | ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, substs, .. }) - | ty::Closure(def_id, substs) - | ty::Generator(def_id, substs, _) => self.print_def_path(def_id, substs), + ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did: def_id, .. }, _)), args) + | ty::FnDef(def_id, args) + | ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) + | ty::Closure(def_id, args) + | ty::Generator(def_id, args, _) => self.print_def_path(def_id, args), ty::Foreign(def_id) => self.print_def_path(def_id, &[]), + ty::Alias(ty::Weak, _) => bug!("type_name: unexpected weak projection"), ty::Alias(ty::Inherent, _) => bug!("type_name: unexpected inherent projection"), ty::GeneratorWitness(_) => bug!("type_name: unexpected `GeneratorWitness`"), ty::GeneratorWitnessMIR(..) => bug!("type_name: unexpected `GeneratorWitnessMIR`"), diff --git a/compiler/rustc_data_structures/Cargo.toml b/compiler/rustc_data_structures/Cargo.toml index 78f73d193e380..c4afae860dbae 100644 --- a/compiler/rustc_data_structures/Cargo.toml +++ b/compiler/rustc_data_structures/Cargo.toml @@ -10,7 +10,7 @@ arrayvec = { version = "0.7", default-features = false } bitflags = "1.2.1" cfg-if = "1.0" ena = "0.14.2" -indexmap = { version = "1.9.3" } +indexmap = { version = "2.0.0" } jobserver_crate = { version = "0.1.13", package = "jobserver" } libc = "0.2" measureme = "10.0.0" @@ -35,7 +35,7 @@ elsa = "=1.7.1" itertools = "0.10.1" [dependencies.parking_lot] -version = "0.11" +version = "0.12" [target.'cfg(windows)'.dependencies.windows] version = "0.48.0" @@ -47,7 +47,7 @@ features = [ "Win32_System_Threading", ] -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(not(target_family = "wasm"))'.dependencies] memmap2 = "0.2.1" [features] diff --git a/compiler/rustc_data_structures/src/base_n.rs b/compiler/rustc_data_structures/src/base_n.rs index 4567759c004da..a3eb2b9c416fc 100644 --- a/compiler/rustc_data_structures/src/base_n.rs +++ b/compiler/rustc_data_structures/src/base_n.rs @@ -16,22 +16,24 @@ const BASE_64: &[u8; MAX_BASE] = pub fn push_str(mut n: u128, base: usize, output: &mut String) { debug_assert!(base >= 2 && base <= MAX_BASE); let mut s = [0u8; 128]; - let mut index = 0; + let mut index = s.len(); let base = base as u128; loop { + index -= 1; s[index] = BASE_64[(n % base) as usize]; - index += 1; n /= base; if n == 0 { break; } } - s[0..index].reverse(); - output.push_str(str::from_utf8(&s[0..index]).unwrap()); + output.push_str(unsafe { + // SAFETY: `s` is populated using only valid utf8 characters from `BASE_64` + str::from_utf8_unchecked(&s[index..]) + }); } #[inline] diff --git a/compiler/rustc_data_structures/src/binary_search_util/mod.rs b/compiler/rustc_data_structures/src/binary_search_util/mod.rs index d40172a2e2fe8..bc8a6b9eac0c3 100644 --- a/compiler/rustc_data_structures/src/binary_search_util/mod.rs +++ b/compiler/rustc_data_structures/src/binary_search_util/mod.rs @@ -10,41 +10,17 @@ pub fn binary_search_slice<'d, E, K>(data: &'d [E], key_fn: impl Fn(&E) -> K, ke where K: Ord, { - let Ok(mid) = data.binary_search_by_key(key, &key_fn) else { + let size = data.len(); + let start = data.partition_point(|x| key_fn(x) < *key); + // At this point `start` either points at the first entry with equal or + // greater key or is equal to `size` in case all elements have smaller keys + if start == size || key_fn(&data[start]) != *key { return &[]; }; - let size = data.len(); - - // We get back *some* element with the given key -- so do - // a galloping search backwards to find the *first* one. - let mut start = mid; - let mut previous = mid; - let mut step = 1; - loop { - start = start.saturating_sub(step); - if start == 0 || key_fn(&data[start]) != *key { - break; - } - previous = start; - step *= 2; - } - step = previous - start; - while step > 1 { - let half = step / 2; - let mid = start + half; - if key_fn(&data[mid]) != *key { - start = mid; - } - step -= half; - } - // adjust by one if we have overshot - if start < size && key_fn(&data[start]) != *key { - start += 1; - } // Now search forward to find the *last* one. - let mut end = mid; - let mut previous = mid; + let mut end = start; + let mut previous = start; let mut step = 1; loop { end = end.saturating_add(step).min(size); diff --git a/compiler/rustc_data_structures/src/graph/dominators/mod.rs b/compiler/rustc_data_structures/src/graph/dominators/mod.rs index a5db14d9102c4..85ef2de9b5e38 100644 --- a/compiler/rustc_data_structures/src/graph/dominators/mod.rs +++ b/compiler/rustc_data_structures/src/graph/dominators/mod.rs @@ -176,9 +176,7 @@ pub fn dominators(graph: &G) -> Dominators { // // ...this may be the case if a MirPass modifies the CFG to remove // or rearrange certain blocks/edges. - let Some(v) = real_to_pre_order[v] else { - continue - }; + let Some(v) = real_to_pre_order[v] else { continue }; // eval returns a vertex x from which semi[x] is minimum among // vertices semi[v] +> x *> v. diff --git a/compiler/rustc_data_structures/src/intern.rs b/compiler/rustc_data_structures/src/intern.rs index ba94f3776eb90..e0f8c350c2a86 100644 --- a/compiler/rustc_data_structures/src/intern.rs +++ b/compiler/rustc_data_structures/src/intern.rs @@ -1,5 +1,6 @@ use crate::stable_hasher::{HashStable, StableHasher}; use std::cmp::Ordering; +use std::fmt::{self, Debug}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::ptr; @@ -20,7 +21,6 @@ mod private { /// The `PrivateZst` field means you can pattern match with `Interned(v, _)` /// but you can only construct a `Interned` with `new_unchecked`, and not /// directly. -#[derive(Debug)] #[rustc_pass_by_value] pub struct Interned<'a, T>(pub &'a T, pub private::PrivateZst); @@ -108,5 +108,11 @@ where } } +impl Debug for Interned<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + #[cfg(test)] mod tests; diff --git a/compiler/rustc_data_structures/src/lib.rs b/compiler/rustc_data_structures/src/lib.rs index 859e384d8b529..33772089744d1 100644 --- a/compiler/rustc_data_structures/src/lib.rs +++ b/compiler/rustc_data_structures/src/lib.rs @@ -37,6 +37,7 @@ #![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #![deny(unsafe_op_in_unsafe_fn)] #[macro_use] @@ -68,7 +69,6 @@ pub mod macros; pub mod obligation_forest; pub mod sip128; pub mod small_c_str; -pub mod small_str; pub mod snapshot_map; pub mod svh; pub use ena::snapshot_vec; diff --git a/compiler/rustc_data_structures/src/memmap.rs b/compiler/rustc_data_structures/src/memmap.rs index ca908671ae530..1855a389c4638 100644 --- a/compiler/rustc_data_structures/src/memmap.rs +++ b/compiler/rustc_data_structures/src/memmap.rs @@ -3,13 +3,13 @@ use std::io; use std::ops::{Deref, DerefMut}; /// A trivial wrapper for [`memmap2::Mmap`] (or `Vec` on WASM). -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] pub struct Mmap(memmap2::Mmap); -#[cfg(target_arch = "wasm32")] +#[cfg(target_family = "wasm")] pub struct Mmap(Vec); -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] impl Mmap { #[inline] pub unsafe fn map(file: File) -> io::Result { @@ -18,7 +18,7 @@ impl Mmap { } } -#[cfg(target_arch = "wasm32")] +#[cfg(target_family = "wasm")] impl Mmap { #[inline] pub unsafe fn map(mut file: File) -> io::Result { diff --git a/compiler/rustc_data_structures/src/sharded.rs b/compiler/rustc_data_structures/src/sharded.rs index 7ed70ba1e0fc7..40cbf14958e77 100644 --- a/compiler/rustc_data_structures/src/sharded.rs +++ b/compiler/rustc_data_structures/src/sharded.rs @@ -1,4 +1,6 @@ use crate::fx::{FxHashMap, FxHasher}; +#[cfg(parallel_compiler)] +use crate::sync::is_dyn_thread_safe; use crate::sync::{CacheAligned, Lock, LockGuard}; use std::borrow::Borrow; use std::collections::hash_map::RawEntryMut; @@ -18,6 +20,11 @@ pub const SHARDS: usize = 1 << SHARD_BITS; /// An array of cache-line aligned inner locked structures with convenience methods. pub struct Sharded { + /// This mask is used to ensure that accesses are inbounds of `shards`. + /// When dynamic thread safety is off, this field is set to 0 causing only + /// a single shard to be used for greater cache efficiency. + #[cfg(parallel_compiler)] + mask: usize, shards: [CacheAligned>; SHARDS], } @@ -31,31 +38,54 @@ impl Default for Sharded { impl Sharded { #[inline] pub fn new(mut value: impl FnMut() -> T) -> Self { - Sharded { shards: [(); SHARDS].map(|()| CacheAligned(Lock::new(value()))) } + Sharded { + #[cfg(parallel_compiler)] + mask: if is_dyn_thread_safe() { SHARDS - 1 } else { 0 }, + shards: [(); SHARDS].map(|()| CacheAligned(Lock::new(value()))), + } + } + + #[inline(always)] + fn mask(&self) -> usize { + #[cfg(parallel_compiler)] + { + if SHARDS == 1 { 0 } else { self.mask } + } + #[cfg(not(parallel_compiler))] + { + 0 + } + } + + #[inline(always)] + fn count(&self) -> usize { + // `self.mask` is always one below the used shard count + self.mask() + 1 } /// The shard is selected by hashing `val` with `FxHasher`. #[inline] pub fn get_shard_by_value(&self, val: &K) -> &Lock { - if SHARDS == 1 { &self.shards[0].0 } else { self.get_shard_by_hash(make_hash(val)) } + self.get_shard_by_hash(if SHARDS == 1 { 0 } else { make_hash(val) }) } #[inline] pub fn get_shard_by_hash(&self, hash: u64) -> &Lock { - &self.shards[get_shard_index_by_hash(hash)].0 + self.get_shard_by_index(get_shard_hash(hash)) } #[inline] pub fn get_shard_by_index(&self, i: usize) -> &Lock { - &self.shards[i].0 + // SAFETY: The index get ANDed with the mask, ensuring it is always inbounds. + unsafe { &self.shards.get_unchecked(i & self.mask()).0 } } pub fn lock_shards(&self) -> Vec> { - (0..SHARDS).map(|i| self.shards[i].0.lock()).collect() + (0..self.count()).map(|i| self.get_shard_by_index(i).lock()).collect() } pub fn try_lock_shards(&self) -> Option>> { - (0..SHARDS).map(|i| self.shards[i].0.try_lock()).collect() + (0..self.count()).map(|i| self.get_shard_by_index(i).try_lock()).collect() } } @@ -136,11 +166,9 @@ pub fn make_hash(val: &K) -> u64 { /// `hash` can be computed with any hasher, so long as that hasher is used /// consistently for each `Sharded` instance. #[inline] -#[allow(clippy::modulo_one)] -pub fn get_shard_index_by_hash(hash: u64) -> usize { +fn get_shard_hash(hash: u64) -> usize { let hash_len = mem::size_of::(); // Ignore the top 7 bits as hashbrown uses these and get the next SHARD_BITS highest bits. // hashbrown also uses the lowest bits, so we can't use those - let bits = (hash >> (hash_len * 8 - 7 - SHARD_BITS)) as usize; - bits % SHARDS + (hash >> (hash_len * 8 - 7 - SHARD_BITS)) as usize } diff --git a/compiler/rustc_data_structures/src/small_str.rs b/compiler/rustc_data_structures/src/small_str.rs deleted file mode 100644 index 800acb1b03e5a..0000000000000 --- a/compiler/rustc_data_structures/src/small_str.rs +++ /dev/null @@ -1,68 +0,0 @@ -use smallvec::SmallVec; - -#[cfg(test)] -mod tests; - -/// Like SmallVec but for strings. -#[derive(Default)] -pub struct SmallStr(SmallVec<[u8; N]>); - -impl SmallStr { - #[inline] - pub fn new() -> Self { - SmallStr(SmallVec::default()) - } - - #[inline] - pub fn push_str(&mut self, s: &str) { - self.0.extend_from_slice(s.as_bytes()); - } - - #[inline] - pub fn empty(&self) -> bool { - self.0.is_empty() - } - - #[inline] - pub fn spilled(&self) -> bool { - self.0.spilled() - } - - #[inline] - pub fn as_str(&self) -> &str { - unsafe { std::str::from_utf8_unchecked(self.0.as_slice()) } - } -} - -impl std::ops::Deref for SmallStr { - type Target = str; - - #[inline] - fn deref(&self) -> &str { - self.as_str() - } -} - -impl> FromIterator for SmallStr { - #[inline] - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - let mut s = SmallStr::default(); - s.extend(iter); - s - } -} - -impl> Extend for SmallStr { - #[inline] - fn extend(&mut self, iter: T) - where - T: IntoIterator, - { - for a in iter.into_iter() { - self.push_str(a.as_ref()); - } - } -} diff --git a/compiler/rustc_data_structures/src/small_str/tests.rs b/compiler/rustc_data_structures/src/small_str/tests.rs deleted file mode 100644 index 7635a9b7204db..0000000000000 --- a/compiler/rustc_data_structures/src/small_str/tests.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::*; - -#[test] -fn empty() { - let s = SmallStr::<1>::new(); - assert!(s.empty()); - assert_eq!("", s.as_str()); - assert!(!s.spilled()); -} - -#[test] -fn from_iter() { - let s = ["aa", "bb", "cc"].iter().collect::>(); - assert_eq!("aabbcc", s.as_str()); - assert!(!s.spilled()); - - let s = ["aa", "bb", "cc", "dd"].iter().collect::>(); - assert_eq!("aabbccdd", s.as_str()); - assert!(s.spilled()); -} diff --git a/compiler/rustc_data_structures/src/sorted_map.rs b/compiler/rustc_data_structures/src/sorted_map.rs index 9409057d4847e..60b343afbed9c 100644 --- a/compiler/rustc_data_structures/src/sorted_map.rs +++ b/compiler/rustc_data_structures/src/sorted_map.rs @@ -49,12 +49,11 @@ impl SortedMap { } #[inline] - pub fn insert(&mut self, key: K, mut value: V) -> Option { + pub fn insert(&mut self, key: K, value: V) -> Option { match self.lookup_index_for(&key) { Ok(index) => { let slot = unsafe { self.data.get_unchecked_mut(index) }; - mem::swap(&mut slot.1, &mut value); - Some(value) + Some(mem::replace(&mut slot.1, value)) } Err(index) => { self.data.insert(index, (key, value)); diff --git a/compiler/rustc_data_structures/src/sso/map.rs b/compiler/rustc_data_structures/src/sso/map.rs index 99581ed237590..04e359a54708c 100644 --- a/compiler/rustc_data_structures/src/sso/map.rs +++ b/compiler/rustc_data_structures/src/sso/map.rs @@ -268,11 +268,7 @@ impl SsoHashMap { pub fn remove_entry(&mut self, key: &K) -> Option<(K, V)> { match self { SsoHashMap::Array(array) => { - if let Some(index) = array.iter().position(|(k, _v)| k == key) { - Some(array.swap_remove(index)) - } else { - None - } + array.iter().position(|(k, _v)| k == key).map(|index| array.swap_remove(index)) } SsoHashMap::Map(map) => map.remove_entry(key), } diff --git a/compiler/rustc_data_structures/src/stable_hasher.rs b/compiler/rustc_data_structures/src/stable_hasher.rs index 6d57d81c56a4b..6d75b0fb8a0ca 100644 --- a/compiler/rustc_data_structures/src/stable_hasher.rs +++ b/compiler/rustc_data_structures/src/stable_hasher.rs @@ -1,6 +1,6 @@ use crate::sip128::SipHasher128; use rustc_index::bit_set::{self, BitSet}; -use rustc_index::{Idx, IndexVec}; +use rustc_index::{Idx, IndexSlice, IndexVec}; use smallvec::SmallVec; use std::fmt; use std::hash::{BuildHasher, Hash, Hasher}; @@ -233,7 +233,17 @@ pub trait ToStableHashKey { /// - `DefIndex`, `CrateNum`, `LocalDefId`, because their concrete /// values depend on state that might be different between /// compilation sessions. -pub unsafe trait StableOrd: Ord {} +/// +/// The associated constant `CAN_USE_UNSTABLE_SORT` denotes whether +/// unstable sorting can be used for this type. Set to true if and +/// only if `a == b` implies `a` and `b` are fully indistinguishable. +pub unsafe trait StableOrd: Ord { + const CAN_USE_UNSTABLE_SORT: bool; +} + +unsafe impl StableOrd for &T { + const CAN_USE_UNSTABLE_SORT: bool = T::CAN_USE_UNSTABLE_SORT; +} /// Implement HashStable by just calling `Hash::hash()`. Also implement `StableOrd` for the type since /// that has the same requirements. @@ -253,7 +263,9 @@ macro_rules! impl_stable_traits_for_trivial_type { } } - unsafe impl $crate::stable_hasher::StableOrd for $t {} + unsafe impl $crate::stable_hasher::StableOrd for $t { + const CAN_USE_UNSTABLE_SORT: bool = true; + } }; } @@ -339,6 +351,10 @@ impl, T2: HashStable, CTX> HashStable for (T1, T2) } } +unsafe impl StableOrd for (T1, T2) { + const CAN_USE_UNSTABLE_SORT: bool = T1::CAN_USE_UNSTABLE_SORT && T2::CAN_USE_UNSTABLE_SORT; +} + impl HashStable for (T1, T2, T3) where T1: HashStable, @@ -353,6 +369,11 @@ where } } +unsafe impl StableOrd for (T1, T2, T3) { + const CAN_USE_UNSTABLE_SORT: bool = + T1::CAN_USE_UNSTABLE_SORT && T2::CAN_USE_UNSTABLE_SORT && T3::CAN_USE_UNSTABLE_SORT; +} + impl HashStable for (T1, T2, T3, T4) where T1: HashStable, @@ -369,6 +390,15 @@ where } } +unsafe impl StableOrd + for (T1, T2, T3, T4) +{ + const CAN_USE_UNSTABLE_SORT: bool = T1::CAN_USE_UNSTABLE_SORT + && T2::CAN_USE_UNSTABLE_SORT + && T3::CAN_USE_UNSTABLE_SORT + && T4::CAN_USE_UNSTABLE_SORT; +} + impl, CTX> HashStable for [T] { default fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) { self.len().hash_stable(ctx, hasher); @@ -459,6 +489,10 @@ impl HashStable for str { } } +unsafe impl StableOrd for &str { + const CAN_USE_UNSTABLE_SORT: bool = true; +} + impl HashStable for String { #[inline] fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) { @@ -468,7 +502,9 @@ impl HashStable for String { // Safety: String comparison only depends on their contents and the // contents are not changed by (de-)serialization. -unsafe impl StableOrd for String {} +unsafe impl StableOrd for String { + const CAN_USE_UNSTABLE_SORT: bool = true; +} impl ToStableHashKey for String { type KeyType = String; @@ -494,7 +530,9 @@ impl HashStable for bool { } // Safety: sort order of bools is not changed by (de-)serialization. -unsafe impl StableOrd for bool {} +unsafe impl StableOrd for bool { + const CAN_USE_UNSTABLE_SORT: bool = true; +} impl HashStable for Option where @@ -512,7 +550,9 @@ where } // Safety: the Option wrapper does not add instability to comparison. -unsafe impl StableOrd for Option {} +unsafe impl StableOrd for Option { + const CAN_USE_UNSTABLE_SORT: bool = T::CAN_USE_UNSTABLE_SORT; +} impl HashStable for Result where @@ -557,6 +597,18 @@ where } } +impl HashStable for IndexSlice +where + T: HashStable, +{ + fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) { + self.len().hash_stable(ctx, hasher); + for v in &self.raw { + v.hash_stable(ctx, hasher); + } + } +} + impl HashStable for IndexVec where T: HashStable, diff --git a/compiler/rustc_data_structures/src/sync.rs b/compiler/rustc_data_structures/src/sync.rs index 6c3197d8ec2c5..25a08237346dd 100644 --- a/compiler/rustc_data_structures/src/sync.rs +++ b/compiler/rustc_data_structures/src/sync.rs @@ -139,9 +139,14 @@ cfg_if! { impl Atomic { pub fn fetch_or(&self, val: bool, _: Ordering) -> bool { - let result = self.0.get() | val; - self.0.set(val); - result + let old = self.0.get(); + self.0.set(val | old); + old + } + pub fn fetch_and(&self, val: bool, _: Ordering) -> bool { + let old = self.0.get(); + self.0.set(val & old); + old } } diff --git a/compiler/rustc_data_structures/src/sync/vec.rs b/compiler/rustc_data_structures/src/sync/vec.rs index e36dded9e5e5e..314496ce9f095 100644 --- a/compiler/rustc_data_structures/src/sync/vec.rs +++ b/compiler/rustc_data_structures/src/sync/vec.rs @@ -43,37 +43,23 @@ impl AppendOnlyIndexVec { #[derive(Default)] pub struct AppendOnlyVec { - #[cfg(not(parallel_compiler))] - vec: elsa::vec::FrozenVec, - #[cfg(parallel_compiler)] - vec: elsa::sync::LockFreeFrozenVec, + vec: parking_lot::RwLock>, } impl AppendOnlyVec { pub fn new() -> Self { - Self { - #[cfg(not(parallel_compiler))] - vec: elsa::vec::FrozenVec::new(), - #[cfg(parallel_compiler)] - vec: elsa::sync::LockFreeFrozenVec::new(), - } + Self { vec: Default::default() } } pub fn push(&self, val: T) -> usize { - #[cfg(not(parallel_compiler))] - let i = self.vec.len(); - #[cfg(not(parallel_compiler))] - self.vec.push(val); - #[cfg(parallel_compiler)] - let i = self.vec.push(val); - i + let mut v = self.vec.write(); + let n = v.len(); + v.push(val); + n } pub fn get(&self, i: usize) -> Option { - #[cfg(not(parallel_compiler))] - return self.vec.get_copy(i); - #[cfg(parallel_compiler)] - return self.vec.get(i); + self.vec.read().get(i).copied() } pub fn iter_enumerated(&self) -> impl Iterator + '_ { diff --git a/compiler/rustc_data_structures/src/sync/worker_local.rs b/compiler/rustc_data_structures/src/sync/worker_local.rs index d61bb55be6836..8c84daf4f1653 100644 --- a/compiler/rustc_data_structures/src/sync/worker_local.rs +++ b/compiler/rustc_data_structures/src/sync/worker_local.rs @@ -116,7 +116,7 @@ pub struct WorkerLocal { // This is safe because the `deref` call will return a reference to a `T` unique to each thread // or it will panic for threads without an associated local. So there isn't a need for `T` to do -// it's own synchronization. The `verify` method on `RegistryId` has an issue where the the id +// it's own synchronization. The `verify` method on `RegistryId` has an issue where the id // can be reused, but `WorkerLocal` has a reference to `Registry` which will prevent any reuse. #[cfg(parallel_compiler)] unsafe impl Sync for WorkerLocal {} diff --git a/compiler/rustc_data_structures/src/temp_dir.rs b/compiler/rustc_data_structures/src/temp_dir.rs index a780d2386a63c..621d3011a2aa1 100644 --- a/compiler/rustc_data_structures/src/temp_dir.rs +++ b/compiler/rustc_data_structures/src/temp_dir.rs @@ -16,7 +16,7 @@ impl Drop for MaybeTempDir { // occur. let dir = unsafe { ManuallyDrop::take(&mut self.dir) }; if self.keep { - dir.into_path(); + let _ = dir.into_path(); } } } diff --git a/compiler/rustc_data_structures/src/unord.rs b/compiler/rustc_data_structures/src/unord.rs index 6c8d541463158..47c56eba7adc8 100644 --- a/compiler/rustc_data_structures/src/unord.rs +++ b/compiler/rustc_data_structures/src/unord.rs @@ -31,6 +31,7 @@ use crate::{ /// /// It's still possible to do the same thing with an `Fn` by using interior mutability, /// but the chance of doing it accidentally is reduced. +#[derive(Clone)] pub struct UnordItems>(I); impl> UnordItems { @@ -107,6 +108,10 @@ impl> UnordItems { { UnordItems(self.0.flat_map(f)) } + + pub fn collect>>(self) -> C { + self.into() + } } impl UnordItems> { @@ -140,12 +145,12 @@ impl> UnordItems { } #[inline] - pub fn into_sorted_stable_ord(self, use_stable_sort: bool) -> Vec + pub fn into_sorted_stable_ord(self) -> Vec where T: Ord + StableOrd, { let mut items: Vec = self.0.collect(); - if use_stable_sort { + if !T::CAN_USE_UNSTABLE_SORT { items.sort(); } else { items.sort_unstable() @@ -163,6 +168,14 @@ impl> UnordItems { } } +/// A marker trait specifying that `Self` can consume `UnordItems<_>` without +/// exposing any internal ordering. +/// +/// Note: right now this is just a marker trait. It could be extended to contain +/// some useful, common methods though, like `len`, `clear`, or the various +/// kinds of `to_sorted`. +trait UnordCollection {} + /// This is a set collection type that tries very hard to not expose /// any internal iteration. This is a useful property when trying to /// uphold the determinism invariants imposed by the query system. @@ -177,6 +190,8 @@ pub struct UnordSet { inner: FxHashSet, } +impl UnordCollection for UnordSet {} + impl Default for UnordSet { #[inline] fn default() -> Self { @@ -190,6 +205,11 @@ impl UnordSet { Self { inner: Default::default() } } + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { inner: FxHashSet::with_capacity_and_hasher(capacity, Default::default()) } + } + #[inline] pub fn len(&self) -> usize { self.inner.len() @@ -254,9 +274,9 @@ impl UnordSet { #[inline] pub fn to_sorted_stable_ord(&self) -> Vec where - V: Ord + StableOrd + Copy, + V: Ord + StableOrd + Clone, { - let mut items: Vec = self.inner.iter().copied().collect(); + let mut items: Vec = self.inner.iter().cloned().collect(); items.sort_unstable(); items } @@ -275,16 +295,28 @@ impl UnordSet { to_sorted_vec(hcx, self.inner.into_iter(), cache_sort_key, |x| x) } - // We can safely extend this UnordSet from a set of unordered values because that - // won't expose the internal ordering anywhere. #[inline] - pub fn extend_unord>(&mut self, items: UnordItems) { - self.inner.extend(items.0) + pub fn clear(&mut self) { + self.inner.clear(); } +} +pub trait ExtendUnord { + /// Extend this unord collection with the given `UnordItems`. + /// This method is called `extend_unord` instead of just `extend` so it + /// does not conflict with `Extend::extend`. Otherwise there would be many + /// places where the two methods would have to be explicitly disambiguated + /// via UFCS. + fn extend_unord>(&mut self, items: UnordItems); +} + +// Note: it is important that `C` implements `UnordCollection` in addition to +// `Extend`, otherwise this impl would leak the internal iteration order of +// `items`, e.g. when calling `some_vec.extend_unord(some_unord_items)`. +impl + UnordCollection, T> ExtendUnord for C { #[inline] - pub fn clear(&mut self) { - self.inner.clear(); + fn extend_unord>(&mut self, items: UnordItems) { + self.extend(items.0) } } @@ -308,6 +340,12 @@ impl From> for UnordSet { } } +impl> From> for UnordSet { + fn from(value: UnordItems) -> Self { + UnordSet { inner: FxHashSet::from_iter(value.0) } + } +} + impl> HashStable for UnordSet { #[inline] fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) { @@ -329,6 +367,8 @@ pub struct UnordMap { inner: FxHashMap, } +impl UnordCollection for UnordMap {} + impl Default for UnordMap { #[inline] fn default() -> Self { @@ -358,6 +398,11 @@ impl> From> fo } impl UnordMap { + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Self { inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()) } + } + #[inline] pub fn len(&self) -> usize { self.inner.len() @@ -424,13 +469,6 @@ impl UnordMap { UnordItems(self.inner.into_iter()) } - // We can safely extend this UnordMap from a set of unordered values because that - // won't expose the internal ordering anywhere. - #[inline] - pub fn extend>(&mut self, items: UnordItems<(K, V), I>) { - self.inner.extend(items.0) - } - /// Returns the entries of this map in stable sort order (as defined by `ToStableHashKey`). /// /// The `cache_sort_key` parameter controls if [slice::sort_by_cached_key] or @@ -550,15 +588,10 @@ impl UnordBag { pub fn into_items(self) -> UnordItems> { UnordItems(self.inner.into_iter()) } - - // We can safely extend this UnordSet from a set of unordered values because that - // won't expose the internal ordering anywhere. - #[inline] - pub fn extend>(&mut self, items: UnordItems) { - self.inner.extend(items.0) - } } +impl UnordCollection for UnordBag {} + impl Extend for UnordBag { fn extend>(&mut self, iter: I) { self.inner.extend(iter) diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs index 4eabba575f42a..0cd0b51b6ad49 100644 --- a/compiler/rustc_driver/src/lib.rs +++ b/compiler/rustc_driver/src/lib.rs @@ -1,4 +1,4 @@ -// This crate is intentionally empty and a rexport of `rustc_driver_impl` to allow the code in +// This crate is intentionally empty and a re-export of `rustc_driver_impl` to allow the code in // `rustc_driver_impl` to be compiled in parallel with other crates. pub use rustc_driver_impl::*; diff --git a/compiler/rustc_driver_impl/Cargo.toml b/compiler/rustc_driver_impl/Cargo.toml index 67352c55c9019..a7b01618ade39 100644 --- a/compiler/rustc_driver_impl/Cargo.toml +++ b/compiler/rustc_driver_impl/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [lib] [dependencies] +time = { version = "0.3", default-features = false, features = ["formatting", ] } tracing = { version = "0.1.35" } serde_json = "1.0.59" rustc_log = { path = "../rustc_log" } diff --git a/compiler/rustc_driver_impl/messages.ftl b/compiler/rustc_driver_impl/messages.ftl index f19b1ff642617..d3bd3244a52aa 100644 --- a/compiler/rustc_driver_impl/messages.ftl +++ b/compiler/rustc_driver_impl/messages.ftl @@ -1,19 +1,21 @@ -driver_impl_rlink_unable_to_read = failed to read rlink file: `{$err}` +driver_impl_ice = the compiler unexpectedly panicked. this is a bug. +driver_impl_ice_bug_report = we would appreciate a bug report: {$bug_report_url} +driver_impl_ice_exclude_cargo_defaults = some of the compiler flags provided by cargo are hidden -driver_impl_rlink_wrong_file_type = The input does not look like a .rlink file +driver_impl_ice_flags = compiler flags: {$flags} +driver_impl_ice_path = please attach the file at `{$path}` to your bug report +driver_impl_ice_path_error = the ICE couldn't be written to `{$path}`: {$error} +driver_impl_ice_path_error_env = the environment variable `RUSTC_ICE` is set to `{$env_var}` +driver_impl_ice_version = rustc {$version} running on {$triple} driver_impl_rlink_empty_version_number = The input does not contain version number driver_impl_rlink_encoding_version_mismatch = .rlink file was produced with encoding version `{$version_array}`, but the current version is `{$rlink_version}` -driver_impl_rlink_rustc_version_mismatch = .rlink file was produced by rustc version `{$rustc_version}`, but the current version is `{$current_version}` - driver_impl_rlink_no_a_file = rlink must be a file -driver_impl_unpretty_dump_fail = pretty-print failed to write `{$path}` due to error `{$err}` +driver_impl_rlink_rustc_version_mismatch = .rlink file was produced by rustc version `{$rustc_version}`, but the current version is `{$current_version}` -driver_impl_ice = the compiler unexpectedly panicked. this is a bug. -driver_impl_ice_bug_report = we would appreciate a bug report: {$bug_report_url} -driver_impl_ice_version = rustc {$version} running on {$triple} -driver_impl_ice_flags = compiler flags: {$flags} -driver_impl_ice_exclude_cargo_defaults = some of the compiler flags provided by cargo are hidden +driver_impl_rlink_unable_to_read = failed to read rlink file: `{$err}` + +driver_impl_rlink_wrong_file_type = The input does not look like a .rlink file diff --git a/compiler/rustc_driver_impl/src/args.rs b/compiler/rustc_driver_impl/src/args.rs index a713affa09919..654d7636da200 100644 --- a/compiler/rustc_driver_impl/src/args.rs +++ b/compiler/rustc_driver_impl/src/args.rs @@ -3,6 +3,8 @@ use std::fmt; use std::fs; use std::io; +use rustc_session::EarlyErrorHandler; + fn arg_expand(arg: String) -> Result, Error> { if let Some(path) = arg.strip_prefix('@') { let file = match fs::read_to_string(path) { @@ -18,15 +20,15 @@ fn arg_expand(arg: String) -> Result, Error> { } } -pub fn arg_expand_all(at_args: &[String]) -> Vec { +/// **Note:** This function doesn't interpret argument 0 in any special way. +/// If this function is intended to be used with command line arguments, +/// `argv[0]` must be removed prior to calling it manually. +pub fn arg_expand_all(handler: &EarlyErrorHandler, at_args: &[String]) -> Vec { let mut args = Vec::new(); for arg in at_args { match arg_expand(arg.clone()) { Ok(arg) => args.extend(arg), - Err(err) => rustc_session::early_error( - rustc_session::config::ErrorOutputType::default(), - format!("Failed to load argument file: {err}"), - ), + Err(err) => handler.early_error(format!("Failed to load argument file: {err}")), } } args diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 40aa69e5a4105..736877bde88d9 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -7,6 +7,8 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(lazy_cell)] #![feature(decl_macro)] +#![feature(panic_update_hook)] +#![feature(let_chains)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] @@ -24,22 +26,20 @@ use rustc_data_structures::profiling::{ }; use rustc_data_structures::sync::SeqCst; use rustc_errors::registry::{InvalidErrorCode, Registry}; -use rustc_errors::{ - DiagnosticMessage, ErrorGuaranteed, Handler, PResult, SubdiagnosticMessage, TerminalUrl, -}; +use rustc_errors::{markdown, ColorConfig}; +use rustc_errors::{DiagnosticMessage, ErrorGuaranteed, Handler, PResult, SubdiagnosticMessage}; use rustc_feature::find_gated_cfg; use rustc_fluent_macro::fluent_messages; use rustc_interface::util::{self, collect_crate_types, get_codegen_backend}; use rustc_interface::{interface, Queries}; -use rustc_lint::LintStore; +use rustc_lint::{unerased_lint_store, LintStore}; use rustc_metadata::locator; use rustc_session::config::{nightly_options, CG_OPTIONS, Z_OPTIONS}; -use rustc_session::config::{ErrorOutputType, Input, OutputType, PrintRequest, TrimmedDefPaths}; +use rustc_session::config::{ErrorOutputType, Input, OutFileName, OutputType, TrimmedDefPaths}; use rustc_session::cstore::MetadataLoader; use rustc_session::getopts::{self, Matches}; use rustc_session::lint::{Lint, LintId}; -use rustc_session::{config, Session}; -use rustc_session::{early_error, early_error_no_abort, early_warn}; +use rustc_session::{config, EarlyErrorHandler, Session}; use rustc_span::source_map::{FileLoader, FileName}; use rustc_span::symbol::sym; use rustc_target::json::ToJson; @@ -49,20 +49,36 @@ use std::cmp::max; use std::collections::BTreeMap; use std::env; use std::ffi::OsString; -use std::fs; +use std::fmt::Write as _; +use std::fs::{self, File}; use std::io::{self, IsTerminal, Read, Write}; -use std::panic::{self, catch_unwind}; +use std::panic::{self, catch_unwind, PanicInfo}; use std::path::PathBuf; use std::process::{self, Command, Stdio}; use std::str; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::OnceLock; -use std::time::Instant; +use std::time::{Instant, SystemTime}; +use time::format_description::well_known::Rfc3339; +use time::OffsetDateTime; + +#[allow(unused_macros)] +macro do_not_use_print($($t:tt)*) { + std::compile_error!( + "Don't use `print` or `println` here, use `safe_print` or `safe_println` instead" + ) +} + +#[allow(unused_macros)] +macro do_not_use_safe_print($($t:tt)*) { + std::compile_error!("Don't use `safe_print` or `safe_println` here, use `println_info` instead") +} // This import blocks the use of panicking `print` and `println` in all the code // below. Please use `safe_print` and `safe_println` to avoid ICE when // encountering an I/O error during print. #[allow(unused_imports)] -use std::{compile_error as print, compile_error as println}; +use {do_not_use_print as print, do_not_use_print as println}; pub mod args; pub mod pretty; @@ -88,6 +104,7 @@ pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[ rustc_codegen_ssa::DEFAULT_LOCALE_RESOURCE, rustc_const_eval::DEFAULT_LOCALE_RESOURCE, rustc_error_messages::DEFAULT_LOCALE_RESOURCE, + rustc_errors::DEFAULT_LOCALE_RESOURCE, rustc_expand::DEFAULT_LOCALE_RESOURCE, rustc_hir_analysis::DEFAULT_LOCALE_RESOURCE, rustc_hir_typeck::DEFAULT_LOCALE_RESOURCE, @@ -164,6 +181,7 @@ pub trait Callbacks { /// continue the compilation afterwards (defaults to `Compilation::Continue`) fn after_analysis<'tcx>( &mut self, + _handler: &EarlyErrorHandler, _compiler: &interface::Compiler, _queries: &'tcx Queries<'tcx>, ) -> Compilation { @@ -250,22 +268,31 @@ fn run_compiler( Box Box + Send>, >, ) -> interface::Result<()> { - let args = args::arg_expand_all(at_args); + let mut early_error_handler = EarlyErrorHandler::new(ErrorOutputType::default()); + + // Throw away the first argument, the name of the binary. + // In case of at_args being empty, as might be the case by + // passing empty argument array to execve under some platforms, + // just use an empty slice. + // + // This situation was possible before due to arg_expand_all being + // called before removing the argument, enabling a crash by calling + // the compiler with @empty_file as argv[0] and no more arguments. + let at_args = at_args.get(1..).unwrap_or_default(); - let Some(matches) = handle_options(&args) else { return Ok(()) }; + let args = args::arg_expand_all(&early_error_handler, at_args); - let sopts = config::build_session_options(&matches); + let Some(matches) = handle_options(&early_error_handler, &args) else { return Ok(()) }; - // Set parallel mode before thread pool creation, which will create `Lock`s. - interface::set_thread_safe_mode(&sopts.unstable_opts); + let sopts = config::build_session_options(&mut early_error_handler, &matches); if let Some(ref code) = matches.opt_str("explain") { - handle_explain(diagnostics_registry(), code, sopts.error_format); + handle_explain(&early_error_handler, diagnostics_registry(), code, sopts.color); return Ok(()); } - let cfg = interface::parse_cfgspecs(matches.opt_strs("cfg")); - let check_cfg = interface::parse_check_cfg(matches.opt_strs("check-cfg")); + let cfg = interface::parse_cfgspecs(&early_error_handler, matches.opt_strs("cfg")); + let check_cfg = interface::parse_check_cfg(&early_error_handler, matches.opt_strs("check-cfg")); let (odir, ofile) = make_output(&matches); let mut config = interface::Config { opts: sopts, @@ -274,6 +301,7 @@ fn run_compiler( input: Input::File(PathBuf::new()), output_file: ofile, output_dir: odir, + ice_file: ice_path().clone(), file_loader, locale_resources: DEFAULT_LOCALE_RESOURCES, lint_caps: Default::default(), @@ -284,7 +312,7 @@ fn run_compiler( registry: diagnostics_registry(), }; - match make_input(config.opts.error_format, &matches.free) { + match make_input(&early_error_handler, &matches.free) { Err(reported) => return Err(reported), Ok(Some(input)) => { config.input = input; @@ -294,8 +322,13 @@ fn run_compiler( Ok(None) => match matches.free.len() { 0 => { callbacks.config(&mut config); + + early_error_handler.abort_if_errors(); + interface::run_compiler(config, |compiler| { let sopts = &compiler.session().opts; + let handler = EarlyErrorHandler::new(sopts.error_format); + if sopts.describe_lints { let mut lint_store = rustc_lint::new_lint_store(compiler.session().enable_internal_lints()); @@ -309,31 +342,38 @@ fn run_compiler( describe_lints(compiler.session(), &lint_store, registered_lints); return; } - let should_stop = - print_crate_info(&***compiler.codegen_backend(), compiler.session(), false); + let should_stop = print_crate_info( + &handler, + &**compiler.codegen_backend(), + compiler.session(), + false, + ); if should_stop == Compilation::Stop { return; } - early_error(sopts.error_format, "no input filename given") + handler.early_error("no input filename given") }); return Ok(()); } 1 => panic!("make_input should have provided valid inputs"), - _ => early_error( - config.opts.error_format, - format!( - "multiple input filenames provided (first two filenames are `{}` and `{}`)", - matches.free[0], matches.free[1], - ), - ), + _ => early_error_handler.early_error(format!( + "multiple input filenames provided (first two filenames are `{}` and `{}`)", + matches.free[0], matches.free[1], + )), }, }; + early_error_handler.abort_if_errors(); + interface::run_compiler(config, |compiler| { let sess = compiler.session(); - let should_stop = print_crate_info(&***compiler.codegen_backend(), sess, true) - .and_then(|| list_metadata(sess, &*compiler.codegen_backend().metadata_loader())) + let handler = EarlyErrorHandler::new(sess.opts.error_format); + + let should_stop = print_crate_info(&handler, &**compiler.codegen_backend(), sess, true) + .and_then(|| { + list_metadata(&handler, sess, &*compiler.codegen_backend().metadata_loader()) + }) .and_then(|| try_process_rlink(sess, compiler)); if should_stop == Compilation::Stop { @@ -351,6 +391,10 @@ fn run_compiler( pretty::print_after_hir_lowering(tcx, *ppm); Ok(()) })?; + + // Make sure the `output_filenames` query is run for its side + // effects of writing the dep-info and reporting errors. + queries.global_ctxt()?.enter(|tcx| tcx.output_filenames(())); } else { let krate = queries.parse()?.steal(); pretty::print_after_parsing(sess, &krate, *ppm); @@ -367,15 +411,11 @@ fn run_compiler( return early_exit(); } - { - let plugins = queries.register_plugins()?; - let (.., lint_store) = &*plugins.borrow(); - - // Lint plugins are registered; now we can process command line flags. - if sess.opts.describe_lints { - describe_lints(sess, lint_store, true); - return early_exit(); - } + if sess.opts.describe_lints { + queries + .global_ctxt()? + .enter(|tcx| describe_lints(sess, unerased_lint_store(tcx), true)); + return early_exit(); } // Make sure name resolution and macro expansion is run. @@ -401,17 +441,24 @@ fn run_compiler( queries.global_ctxt()?.enter(|tcx| tcx.analysis(()))?; - if callbacks.after_analysis(compiler, queries) == Compilation::Stop { + if callbacks.after_analysis(&handler, compiler, queries) == Compilation::Stop { return early_exit(); } - queries.ongoing_codegen()?; + let ongoing_codegen = queries.ongoing_codegen()?; if sess.opts.unstable_opts.print_type_sizes { sess.code_stats.print_type_sizes(); } - let linker = queries.linker()?; + if sess.opts.unstable_opts.print_vtable_sizes { + let crate_name = + compiler.session().opts.crate_name.as_deref().unwrap_or(""); + + sess.code_stats.print_vtable_sizes(crate_name); + } + + let linker = queries.linker(ongoing_codegen)?; Ok(Some(linker)) })?; @@ -437,15 +484,18 @@ fn run_compiler( } // Extract output directory and file from matches. -fn make_output(matches: &getopts::Matches) -> (Option, Option) { +fn make_output(matches: &getopts::Matches) -> (Option, Option) { let odir = matches.opt_str("out-dir").map(|o| PathBuf::from(&o)); - let ofile = matches.opt_str("o").map(|o| PathBuf::from(&o)); + let ofile = matches.opt_str("o").map(|o| match o.as_str() { + "-" => OutFileName::Stdout, + path => OutFileName::Real(PathBuf::from(path)), + }); (odir, ofile) } // Extract input (string or file and optional path) from matches. fn make_input( - error_format: ErrorOutputType, + handler: &EarlyErrorHandler, free_matches: &[String], ) -> Result, ErrorGuaranteed> { if free_matches.len() == 1 { @@ -455,8 +505,7 @@ fn make_input( if io::stdin().read_to_string(&mut src).is_err() { // Immediately stop compilation if there was an issue reading // the input (for example if the input stream is not UTF-8). - let reported = early_error_no_abort( - error_format, + let reported = handler.early_error_no_abort( "couldn't read from stdin, as it did not contain valid UTF-8", ); return Err(reported); @@ -497,7 +546,7 @@ impl Compilation { } } -fn handle_explain(registry: Registry, code: &str, output: ErrorOutputType) { +fn handle_explain(handler: &EarlyErrorHandler, registry: Registry, code: &str, color: ColorConfig) { let upper_cased_code = code.to_ascii_uppercase(); let normalised = if upper_cased_code.starts_with('E') { upper_cased_code } else { format!("E{code:0>4}") }; @@ -521,53 +570,89 @@ fn handle_explain(registry: Registry, code: &str, output: ErrorOutputType) { text.push('\n'); } if io::stdout().is_terminal() { - show_content_with_pager(&text); + show_md_content_with_pager(&text, color); } else { safe_print!("{text}"); } } Err(InvalidErrorCode) => { - early_error(output, format!("{code} is not a valid error code")); + handler.early_error(format!("{code} is not a valid error code")); } } } -fn show_content_with_pager(content: &str) { +/// If color is always or auto, print formatted & colorized markdown. If color is never or +/// if formatted printing fails, print the raw text. +/// +/// Prefers a pager, falls back standard print +fn show_md_content_with_pager(content: &str, color: ColorConfig) { + let mut fallback_to_println = false; let pager_name = env::var_os("PAGER").unwrap_or_else(|| { if cfg!(windows) { OsString::from("more.com") } else { OsString::from("less") } }); - let mut fallback_to_println = false; + let mut cmd = Command::new(&pager_name); + // FIXME: find if other pagers accept color options + let mut print_formatted = if pager_name == "less" { + cmd.arg("-r"); + true + } else if ["bat", "catbat", "delta"].iter().any(|v| *v == pager_name) { + true + } else { + false + }; - match Command::new(pager_name).stdin(Stdio::piped()).spawn() { - Ok(mut pager) => { - if let Some(pipe) = pager.stdin.as_mut() { - if pipe.write_all(content.as_bytes()).is_err() { - fallback_to_println = true; - } - } + if color == ColorConfig::Never { + print_formatted = false; + } else if color == ColorConfig::Always { + print_formatted = true; + } - if pager.wait().is_err() { + let mdstream = markdown::MdStream::parse_str(content); + let bufwtr = markdown::create_stdout_bufwtr(); + let mut mdbuf = bufwtr.buffer(); + if mdstream.write_termcolor_buf(&mut mdbuf).is_err() { + print_formatted = false; + } + + if let Ok(mut pager) = cmd.stdin(Stdio::piped()).spawn() { + if let Some(pipe) = pager.stdin.as_mut() { + let res = if print_formatted { + pipe.write_all(mdbuf.as_slice()) + } else { + pipe.write_all(content.as_bytes()) + }; + + if res.is_err() { fallback_to_println = true; } } - Err(_) => { + + if pager.wait().is_err() { fallback_to_println = true; } + } else { + fallback_to_println = true; } // If pager fails for whatever reason, we should still print the content // to standard output if fallback_to_println { - safe_print!("{content}"); + let fmt_success = match color { + ColorConfig::Auto => io::stdout().is_terminal() && bufwtr.print(&mdbuf).is_ok(), + ColorConfig::Always => bufwtr.print(&mdbuf).is_ok(), + ColorConfig::Never => false, + }; + + if !fmt_success { + safe_print!("{content}"); + } } } pub fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Compilation { if sess.opts.unstable_opts.link_only { if let Input::File(file) = &sess.io.input { - // FIXME: #![crate_type] and #![crate_name] support not implemented yet - sess.init_crate_types(collect_crate_types(sess, &[])); let outputs = compiler.build_output_filenames(sess, &[]); let rlink_data = fs::read(file).unwrap_or_else(|err| { sess.emit_fatal(RlinkUnableToRead { err }); @@ -606,7 +691,11 @@ pub fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Comp } } -pub fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) -> Compilation { +pub fn list_metadata( + handler: &EarlyErrorHandler, + sess: &Session, + metadata_loader: &dyn MetadataLoader, +) -> Compilation { if sess.opts.unstable_opts.ls { match sess.io.input { Input::File(ref ifile) => { @@ -616,7 +705,7 @@ pub fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) -> Co safe_println!("{}", String::from_utf8(v).unwrap()); } Input::Str { .. } => { - early_error(ErrorOutputType::default(), "cannot list metadata for stdin"); + handler.early_error("cannot list metadata for stdin"); } } return Compilation::Stop; @@ -626,14 +715,22 @@ pub fn list_metadata(sess: &Session, metadata_loader: &dyn MetadataLoader) -> Co } fn print_crate_info( + handler: &EarlyErrorHandler, codegen_backend: &dyn CodegenBackend, sess: &Session, parse_attrs: bool, ) -> Compilation { - use rustc_session::config::PrintRequest::*; + use rustc_session::config::PrintKind::*; + + // This import prevents the following code from using the printing macros + // used by the rest of the module. Within this function, we only write to + // the output specified by `sess.io.output_file`. + #[allow(unused_imports)] + use {do_not_use_safe_print as safe_print, do_not_use_safe_print as safe_println}; + // NativeStaticLibs and LinkArgs are special - printed during linking // (empty iterator returns true) - if sess.opts.prints.iter().all(|&p| p == NativeStaticLibs || p == LinkArgs) { + if sess.opts.prints.iter().all(|p| p.kind == NativeStaticLibs || p.kind == LinkArgs) { return Compilation::Continue; } @@ -649,17 +746,23 @@ fn print_crate_info( } else { None }; + for req in &sess.opts.prints { - match *req { + let mut crate_info = String::new(); + macro println_info($($arg:tt)*) { + crate_info.write_fmt(format_args!("{}\n", format_args!($($arg)*))).unwrap() + } + + match req.kind { TargetList => { let mut targets = rustc_target::spec::TARGETS.to_vec(); targets.sort_unstable(); - safe_println!("{}", targets.join("\n")); + println_info!("{}", targets.join("\n")); } - Sysroot => safe_println!("{}", sess.sysroot.display()), - TargetLibdir => safe_println!("{}", sess.target_tlib_path.dir.display()), + Sysroot => println_info!("{}", sess.sysroot.display()), + TargetLibdir => println_info!("{}", sess.target_tlib_path.dir.display()), TargetSpec => { - safe_println!("{}", serde_json::to_string_pretty(&sess.target.to_json()).unwrap()); + println_info!("{}", serde_json::to_string_pretty(&sess.target.to_json()).unwrap()); } AllTargetSpecs => { let mut targets = BTreeMap::new(); @@ -668,26 +771,30 @@ fn print_crate_info( let target = Target::expect_builtin(&triple); targets.insert(name, target.to_json()); } - safe_println!("{}", serde_json::to_string_pretty(&targets).unwrap()); + println_info!("{}", serde_json::to_string_pretty(&targets).unwrap()); } - FileNames | CrateName => { + FileNames => { let Some(attrs) = attrs.as_ref() else { // no crate attributes, print out an error and exit return Compilation::Continue; }; let t_outputs = rustc_interface::util::build_output_filenames(attrs, sess); let id = rustc_session::output::find_crate_name(sess, attrs); - if *req == PrintRequest::CrateName { - safe_println!("{id}"); - continue; - } let crate_types = collect_crate_types(sess, attrs); for &style in &crate_types { let fname = rustc_session::output::filename_for_input(sess, style, id, &t_outputs); - safe_println!("{}", fname.file_name().unwrap().to_string_lossy()); + println_info!("{}", fname.as_path().file_name().unwrap().to_string_lossy()); } } + CrateName => { + let Some(attrs) = attrs.as_ref() else { + // no crate attributes, print out an error and exit + return Compilation::Continue; + }; + let id = rustc_session::output::find_crate_name(sess, attrs); + println_info!("{id}"); + } Cfg => { let mut cfgs = sess .parse_sess @@ -719,13 +826,13 @@ fn print_crate_info( cfgs.sort(); for cfg in cfgs { - safe_println!("{cfg}"); + println_info!("{cfg}"); } } CallingConventions => { let mut calling_conventions = rustc_target::spec::abi::all_names(); calling_conventions.sort_unstable(); - safe_println!("{}", calling_conventions.join("\n")); + println_info!("{}", calling_conventions.join("\n")); } RelocationModels | CodeModels @@ -733,7 +840,7 @@ fn print_crate_info( | TargetCPUs | StackProtectorStrategies | TargetFeatures => { - codegen_backend.print(*req, sess); + codegen_backend.print(req, &mut crate_info, sess); } // Any output here interferes with Cargo's parsing of other printed output NativeStaticLibs => {} @@ -742,10 +849,8 @@ fn print_crate_info( use rustc_target::spec::SplitDebuginfo::{Off, Packed, Unpacked}; for split in &[Off, Packed, Unpacked] { - let stable = sess.target.options.supported_split_debuginfo.contains(split); - let unstable_ok = sess.unstable_options(); - if stable || unstable_ok { - safe_println!("{split}"); + if sess.target.options.supported_split_debuginfo.contains(split) { + println_info!("{split}"); } } } @@ -753,19 +858,19 @@ fn print_crate_info( use rustc_target::spec::current_apple_deployment_target; if sess.target.is_like_osx { - safe_println!( + println_info!( "deployment_target={}", current_apple_deployment_target(&sess.target) .expect("unknown Apple target OS") ) } else { - early_error( - ErrorOutputType::default(), - "only Apple targets currently support deployment version info", - ) + handler + .early_error("only Apple targets currently support deployment version info") } } } + + req.out.overwrite(&crate_info, sess); } Compilation::Stop } @@ -773,11 +878,12 @@ fn print_crate_info( /// Prints version information /// /// NOTE: this is a macro to support drivers built at a different time than the main `rustc_driver` crate. -pub macro version($binary: literal, $matches: expr) { +pub macro version($handler: expr, $binary: literal, $matches: expr) { fn unw(x: Option<&str>) -> &str { x.unwrap_or("unknown") } $crate::version_at_macro_invocation( + $handler, $binary, $matches, unw(option_env!("CFG_VERSION")), @@ -789,6 +895,7 @@ pub macro version($binary: literal, $matches: expr) { #[doc(hidden)] // use the macro instead pub fn version_at_macro_invocation( + handler: &EarlyErrorHandler, binary: &str, matches: &getopts::Matches, version: &str, @@ -809,7 +916,7 @@ pub fn version_at_macro_invocation( let debug_flags = matches.opt_strs("Z"); let backend_name = debug_flags.iter().find_map(|x| x.strip_prefix("codegen-backend=")); - get_codegen_backend(&None, backend_name).print_version(); + get_codegen_backend(handler, &None, backend_name).print_version(); } } @@ -986,7 +1093,7 @@ Available lint options: /// Show help for flag categories shared between rustdoc and rustc. /// /// Returns whether a help option was printed. -pub fn describe_flag_categories(matches: &Matches) -> bool { +pub fn describe_flag_categories(handler: &EarlyErrorHandler, matches: &Matches) -> bool { // Handle the special case of -Wall. let wall = matches.opt_strs("W"); if wall.iter().any(|x| *x == "all") { @@ -1008,15 +1115,12 @@ pub fn describe_flag_categories(matches: &Matches) -> bool { } if cg_flags.iter().any(|x| *x == "no-stack-check") { - early_warn( - ErrorOutputType::default(), - "the --no-stack-check flag is deprecated and does nothing", - ); + handler.early_warn("the --no-stack-check flag is deprecated and does nothing"); } if cg_flags.iter().any(|x| *x == "passes=list") { let backend_name = debug_flags.iter().find_map(|x| x.strip_prefix("codegen-backend=")); - get_codegen_backend(&None, backend_name).print_passes(); + get_codegen_backend(handler, &None, backend_name).print_passes(); return true; } @@ -1073,10 +1177,7 @@ fn print_flag_list( /// /// So with all that in mind, the comments below have some more detail about the /// contortions done here to get things to work out correctly. -pub fn handle_options(args: &[String]) -> Option { - // Throw away the first argument, the name of the binary - let args = &args[1..]; - +pub fn handle_options(handler: &EarlyErrorHandler, args: &[String]) -> Option { if args.is_empty() { // user did not write `-v` nor `-Z unstable-options`, so do not // include that extra information. @@ -1102,7 +1203,7 @@ pub fn handle_options(args: &[String]) -> Option { .map(|(flag, _)| format!("{e}. Did you mean `-{flag} {opt}`?")), _ => None, }; - early_error(ErrorOutputType::default(), msg.unwrap_or_else(|| e.to_string())); + handler.early_error(msg.unwrap_or_else(|| e.to_string())); }); // For all options we just parsed, we check a few aspects: @@ -1116,7 +1217,7 @@ pub fn handle_options(args: &[String]) -> Option { // we're good to go. // * Otherwise, if we're an unstable option then we generate an error // (unstable option being used on stable) - nightly_options::check_nightly_options(&matches, &config::rustc_optgroups()); + nightly_options::check_nightly_options(handler, &matches, &config::rustc_optgroups()); if matches.opt_present("h") || matches.opt_present("help") { // Only show unstable options in --help if we accept unstable options. @@ -1126,12 +1227,12 @@ pub fn handle_options(args: &[String]) -> Option { return None; } - if describe_flag_categories(&matches) { + if describe_flag_categories(handler, &matches) { return None; } if matches.opt_present("version") { - version!("rustc", &matches); + version!(handler, "rustc", &matches); return None; } @@ -1216,9 +1317,29 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 { } } -/// Stores the default panic hook, from before [`install_ice_hook`] was called. -static DEFAULT_HOOK: OnceLock) + Sync + Send + 'static>> = - OnceLock::new(); +pub static ICE_PATH: OnceLock> = OnceLock::new(); + +pub fn ice_path() -> &'static Option { + ICE_PATH.get_or_init(|| { + if !rustc_feature::UnstableFeatures::from_environment(None).is_nightly_build() { + return None; + } + if let Ok("0") = std::env::var("RUST_BACKTRACE").as_deref() { + return None; + } + let mut path = match std::env::var("RUSTC_ICE").as_deref() { + // Explicitly opting out of writing ICEs to disk. + Ok("0") => return None, + Ok(s) => PathBuf::from(s), + Err(_) => std::env::current_dir().unwrap_or_default(), + }; + let now: OffsetDateTime = SystemTime::now().into(); + let file_now = now.format(&Rfc3339).unwrap_or(String::new()); + let pid = std::process::id(); + path.push(format!("rustc-ice-{file_now}-{pid}.txt")); + Some(path) + }) +} /// Installs a panic hook that will print the ICE message on unexpected panics. /// @@ -1242,32 +1363,59 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler)) std::env::set_var("RUST_BACKTRACE", "full"); } - let default_hook = DEFAULT_HOOK.get_or_init(panic::take_hook); + panic::update_hook(Box::new( + move |default_hook: &(dyn Fn(&PanicInfo<'_>) + Send + Sync + 'static), + info: &PanicInfo<'_>| { + // If the error was caused by a broken pipe then this is not a bug. + // Write the error and return immediately. See #98700. + #[cfg(windows)] + if let Some(msg) = info.payload().downcast_ref::() { + if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") + { + // the error code is already going to be reported when the panic unwinds up the stack + let handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let _ = handler.early_error_no_abort(msg.clone()); + return; + } + }; - panic::set_hook(Box::new(move |info| { - // If the error was caused by a broken pipe then this is not a bug. - // Write the error and return immediately. See #98700. - #[cfg(windows)] - if let Some(msg) = info.payload().downcast_ref::() { - if msg.starts_with("failed printing to stdout: ") && msg.ends_with("(os error 232)") { - // the error code is already going to be reported when the panic unwinds up the stack - let _ = early_error_no_abort(ErrorOutputType::default(), msg.as_str()); - return; + // Invoke the default handler, which prints the actual panic message and optionally a backtrace + // Don't do this for delayed bugs, which already emit their own more useful backtrace. + if !info.payload().is::() { + default_hook(info); + // Separate the output with an empty line + eprintln!(); + + if let Some(ice_path) = ice_path() + && let Ok(mut out) = + File::options().create(true).append(true).open(&ice_path) + { + // The current implementation always returns `Some`. + let location = info.location().unwrap(); + let msg = match info.payload().downcast_ref::<&'static str>() { + Some(s) => *s, + None => match info.payload().downcast_ref::() { + Some(s) => &s[..], + None => "Box", + }, + }; + let thread = std::thread::current(); + let name = thread.name().unwrap_or(""); + let _ = write!( + &mut out, + "thread '{name}' panicked at {location}:\n\ + {msg}\n\ + stack backtrace:\n\ + {:#}", + std::backtrace::Backtrace::force_capture() + ); + } } - }; - - // Invoke the default handler, which prints the actual panic message and optionally a backtrace - // Don't do this for delayed bugs, which already emit their own more useful backtrace. - if !info.payload().is::() { - (*default_hook)(info); - // Separate the output with an empty line - eprintln!(); - } - - // Print the ICE message - report_ice(info, bug_report_url, extra_info); - })); + // Print the ICE message + report_ice(info, bug_report_url, extra_info); + }, + )); } /// Prints the ICE message, including query stack, but without backtrace. @@ -1281,17 +1429,9 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: rustc_errors::fallback_fluent_bundle(crate::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( rustc_errors::ColorConfig::Auto, - None, - None, fallback_bundle, - false, - false, - None, - false, - false, - TerminalUrl::No, )); - let handler = rustc_errors::Handler::with_emitter(true, None, emitter); + let handler = rustc_errors::Handler::with_emitter(emitter); // a .span_bug or .bug call has already printed what // it wants to print. @@ -1302,10 +1442,40 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: } handler.emit_note(session_diagnostics::IceBugReport { bug_report_url }); - handler.emit_note(session_diagnostics::IceVersion { - version: util::version_str!().unwrap_or("unknown_version"), - triple: config::host_triple(), - }); + + let version = util::version_str!().unwrap_or("unknown_version"); + let triple = config::host_triple(); + + static FIRST_PANIC: AtomicBool = AtomicBool::new(true); + + let file = if let Some(path) = ice_path().as_ref() { + // Create the ICE dump target file. + match crate::fs::File::options().create(true).append(true).open(&path) { + Ok(mut file) => { + handler + .emit_note(session_diagnostics::IcePath { path: path.display().to_string() }); + if FIRST_PANIC.swap(false, Ordering::SeqCst) { + let _ = write!(file, "\n\nrustc version: {version}\nplatform: {triple}"); + } + Some(file) + } + Err(err) => { + // The path ICE couldn't be written to disk, provide feedback to the user as to why. + handler.emit_warning(session_diagnostics::IcePathError { + path: path.display().to_string(), + error: err.to_string(), + env_var: std::env::var("RUSTC_ICE") + .ok() + .map(|env_var| session_diagnostics::IcePathErrorEnv { env_var }), + }); + handler.emit_note(session_diagnostics::IceVersion { version, triple }); + None + } + } + } else { + handler.emit_note(session_diagnostics::IceVersion { version, triple }); + None + }; if let Some((flags, excluded_cargo_defaults)) = extra_compiler_flags() { handler.emit_note(session_diagnostics::IceFlags { flags: flags.join(" ") }); @@ -1319,7 +1489,7 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: let num_frames = if backtrace { None } else { Some(2) }; - interface::try_print_query_stack(&handler, num_frames); + interface::try_print_query_stack(&handler, num_frames, file); // We don't trust this callback not to panic itself, so run it at the end after we're sure we've // printed all the relevant info. @@ -1334,16 +1504,16 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: /// This allows tools to enable rust logging without having to magically match rustc's /// tracing crate version. -pub fn init_rustc_env_logger() { - init_env_logger("RUSTC_LOG"); +pub fn init_rustc_env_logger(handler: &EarlyErrorHandler) { + init_env_logger(handler, "RUSTC_LOG"); } /// This allows tools to enable rust logging without having to magically match rustc's /// tracing crate version. In contrast to `init_rustc_env_logger` it allows you to choose an env var /// other than `RUSTC_LOG`. -pub fn init_env_logger(env: &str) { +pub fn init_env_logger(handler: &EarlyErrorHandler, env: &str) { if let Err(error) = rustc_log::init_env_logger(env) { - early_error(ErrorOutputType::default(), error.to_string()); + handler.early_error(error.to_string()); } } @@ -1373,13 +1543,13 @@ mod signal_handler { /// When an error signal (such as SIGABRT or SIGSEGV) is delivered to the /// process, print a stack trace and then exit. pub(super) fn install() { + use std::alloc::{alloc, Layout}; + unsafe { - const ALT_STACK_SIZE: usize = libc::MINSIGSTKSZ + 64 * 1024; + let alt_stack_size: usize = min_sigstack_size() + 64 * 1024; let mut alt_stack: libc::stack_t = std::mem::zeroed(); - alt_stack.ss_sp = - std::alloc::alloc(std::alloc::Layout::from_size_align(ALT_STACK_SIZE, 1).unwrap()) - as *mut libc::c_void; - alt_stack.ss_size = ALT_STACK_SIZE; + alt_stack.ss_sp = alloc(Layout::from_size_align(alt_stack_size, 1).unwrap()).cast(); + alt_stack.ss_size = alt_stack_size; libc::sigaltstack(&alt_stack, std::ptr::null_mut()); let mut sa: libc::sigaction = std::mem::zeroed(); @@ -1389,6 +1559,23 @@ mod signal_handler { libc::sigaction(libc::SIGSEGV, &sa, std::ptr::null_mut()); } } + + /// Modern kernels on modern hardware can have dynamic signal stack sizes. + #[cfg(any(target_os = "linux", target_os = "android"))] + fn min_sigstack_size() -> usize { + const AT_MINSIGSTKSZ: core::ffi::c_ulong = 51; + let dynamic_sigstksz = unsafe { libc::getauxval(AT_MINSIGSTKSZ) }; + // If getauxval couldn't find the entry, it returns 0, + // so take the higher of the "constant" and auxval. + // This transparently supports older kernels which don't provide AT_MINSIGSTKSZ + libc::MINSIGSTKSZ.max(dynamic_sigstksz as _) + } + + /// Not all OS support hardware where this is needed. + #[cfg(not(any(target_os = "linux", target_os = "android")))] + fn min_sigstack_size() -> usize { + libc::MINSIGSTKSZ + } } #[cfg(not(all(unix, any(target_env = "gnu", target_os = "macos"))))] @@ -1399,7 +1586,10 @@ mod signal_handler { pub fn main() -> ! { let start_time = Instant::now(); let start_rss = get_resident_set_size(); - init_rustc_env_logger(); + + let handler = EarlyErrorHandler::new(ErrorOutputType::default()); + + init_rustc_env_logger(&handler); signal_handler::install(); let mut callbacks = TimePassesCallbacks::default(); install_ice_hook(DEFAULT_BUG_REPORT_URL, |_| ()); @@ -1408,10 +1598,7 @@ pub fn main() -> ! { .enumerate() .map(|(i, arg)| { arg.into_string().unwrap_or_else(|arg| { - early_error( - ErrorOutputType::default(), - format!("argument {i} is not valid Unicode: {arg:?}"), - ) + handler.early_error(format!("argument {i} is not valid Unicode: {arg:?}")) }) }) .collect::>(); diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index ee64b18d3f60c..222c7b5d6a72a 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -1,6 +1,5 @@ //! The various pretty-printing routines. -use crate::session_diagnostics::UnprettyDumpFail; use rustc_ast as ast; use rustc_ast_pretty::pprust; use rustc_errors::ErrorGuaranteed; @@ -9,7 +8,7 @@ use rustc_hir_pretty as pprust_hir; use rustc_middle::hir::map as hir_map; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::ty::{self, TyCtxt}; -use rustc_session::config::{PpAstTreeMode, PpHirMode, PpMode, PpSourceMode}; +use rustc_session::config::{OutFileName, PpAstTreeMode, PpHirMode, PpMode, PpSourceMode}; use rustc_session::Session; use rustc_span::symbol::Ident; use rustc_span::FileName; @@ -358,17 +357,7 @@ fn get_source(sess: &Session) -> (String, FileName) { } fn write_or_print(out: &str, sess: &Session) { - match &sess.io.output_file { - None => print!("{out}"), - Some(p) => { - if let Err(e) = std::fs::write(p, out) { - sess.emit_fatal(UnprettyDumpFail { - path: p.display().to_string(), - err: e.to_string(), - }); - } - } - } + sess.io.output_file.as_ref().unwrap_or(&OutFileName::Stdout).overwrite(out, sess); } pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) { diff --git a/compiler/rustc_driver_impl/src/session_diagnostics.rs b/compiler/rustc_driver_impl/src/session_diagnostics.rs index 638b368f70214..5eb587c54d990 100644 --- a/compiler/rustc_driver_impl/src/session_diagnostics.rs +++ b/compiler/rustc_driver_impl/src/session_diagnostics.rs @@ -1,4 +1,4 @@ -use rustc_macros::Diagnostic; +use rustc_macros::{Diagnostic, Subdiagnostic}; #[derive(Diagnostic)] #[diag(driver_impl_rlink_unable_to_read)] @@ -32,13 +32,6 @@ pub(crate) struct RLinkRustcVersionMismatch<'a> { #[diag(driver_impl_rlink_no_a_file)] pub(crate) struct RlinkNotAFile; -#[derive(Diagnostic)] -#[diag(driver_impl_unpretty_dump_fail)] -pub(crate) struct UnprettyDumpFail { - pub path: String, - pub err: String, -} - #[derive(Diagnostic)] #[diag(driver_impl_ice)] pub(crate) struct Ice; @@ -56,6 +49,27 @@ pub(crate) struct IceVersion<'a> { pub triple: &'a str, } +#[derive(Diagnostic)] +#[diag(driver_impl_ice_path)] +pub(crate) struct IcePath { + pub path: String, +} + +#[derive(Diagnostic)] +#[diag(driver_impl_ice_path_error)] +pub(crate) struct IcePathError { + pub path: String, + pub error: String, + #[subdiagnostic] + pub env_var: Option, +} + +#[derive(Subdiagnostic)] +#[note(driver_impl_ice_path_error_env)] +pub(crate) struct IcePathErrorEnv { + pub env_var: String, +} + #[derive(Diagnostic)] #[diag(driver_impl_ice_flags)] pub(crate) struct IceFlags { diff --git a/compiler/rustc_error_codes/src/error_codes/E0092.md b/compiler/rustc_error_codes/src/error_codes/E0092.md index 5cbe2a188b022..84ec0656d1ac1 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0092.md +++ b/compiler/rustc_error_codes/src/error_codes/E0092.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0092 #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { fn atomic_foo(); // error: unrecognized atomic operation @@ -17,6 +18,7 @@ functions are defined in `compiler/rustc_codegen_llvm/src/intrinsic.rs` and in ``` #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { fn atomic_fence_seqcst(); // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0093.md b/compiler/rustc_error_codes/src/error_codes/E0093.md index b1683cf4fc4ae..2bda4d74f726d 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0093.md +++ b/compiler/rustc_error_codes/src/error_codes/E0093.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0093 #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { fn foo(); // error: unrecognized intrinsic function: `foo` @@ -22,6 +23,7 @@ functions are defined in `compiler/rustc_codegen_llvm/src/intrinsic.rs` and in ``` #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { fn atomic_fence_seqcst(); // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0094.md b/compiler/rustc_error_codes/src/error_codes/E0094.md index cc546bdbb3b35..67a8c3678c5ff 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0094.md +++ b/compiler/rustc_error_codes/src/error_codes/E0094.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0094 #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { #[rustc_safe_intrinsic] @@ -18,6 +19,7 @@ Example: ``` #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { #[rustc_safe_intrinsic] diff --git a/compiler/rustc_error_codes/src/error_codes/E0132.md b/compiler/rustc_error_codes/src/error_codes/E0132.md index a23cc988bdb66..51258739b89cb 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0132.md +++ b/compiler/rustc_error_codes/src/error_codes/E0132.md @@ -13,7 +13,7 @@ It is not possible to declare type parameters on a function that has the `start` attribute. Such a function must have the following type signature (for more information, view [the unstable book][1]): -[1]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib +[1]: https://doc.rust-lang.org/unstable-book/language-features/start.html ``` # let _: diff --git a/compiler/rustc_error_codes/src/error_codes/E0133.md b/compiler/rustc_error_codes/src/error_codes/E0133.md index 1adbcc3135632..8ca3f03ce156f 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0133.md +++ b/compiler/rustc_error_codes/src/error_codes/E0133.md @@ -1,4 +1,4 @@ -Unsafe code was used outside of an unsafe function or block. +Unsafe code was used outside of an unsafe block. Erroneous code example: @@ -30,4 +30,21 @@ fn main() { See the [unsafe section][unsafe-section] of the Book for more details. +#### Unsafe code in functions + +Unsafe code is currently accepted in unsafe functions, but that is being phased +out in favor of requiring unsafe blocks here too. + +``` +unsafe fn f() { return; } + +unsafe fn g() { + f(); // Is accepted, but no longer recommended + unsafe { f(); } // Recommended way to write this +} +``` + +Linting against this is controlled via the `unsafe_op_in_unsafe_fn` lint, which +is `allow` by default but will be upgraded to `warn` in a future edition. + [unsafe-section]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html diff --git a/compiler/rustc_error_codes/src/error_codes/E0152.md b/compiler/rustc_error_codes/src/error_codes/E0152.md index ef17b8b4c75c9..d862766571df6 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0152.md +++ b/compiler/rustc_error_codes/src/error_codes/E0152.md @@ -20,6 +20,6 @@ attributes: #![no_std] ``` -See also the [unstable book][1]. +See also [this section of the Rustonomicon][beneath std]. -[1]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib +[beneath std]: https://doc.rust-lang.org/nomicon/beneath-std.html diff --git a/compiler/rustc_error_codes/src/error_codes/E0208.md b/compiler/rustc_error_codes/src/error_codes/E0208.md index c6db9b5d61bea..2b811b4b8500f 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0208.md +++ b/compiler/rustc_error_codes/src/error_codes/E0208.md @@ -8,6 +8,7 @@ Erroneous code example: ```compile_fail // NOTE: this feature is perma-unstable and should *only* be used for // testing purposes. +#![allow(internal_features)] #![feature(rustc_attrs)] #[rustc_variance] diff --git a/compiler/rustc_error_codes/src/error_codes/E0211.md b/compiler/rustc_error_codes/src/error_codes/E0211.md index 8c2462ebd9b86..70f14fffae673 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0211.md +++ b/compiler/rustc_error_codes/src/error_codes/E0211.md @@ -5,6 +5,7 @@ used. Erroneous code examples: ```compile_fail #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { #[rustc_safe_intrinsic] @@ -41,6 +42,7 @@ For the first code example, please check the function definition. Example: ``` #![feature(intrinsics)] +#![allow(internal_features)] extern "rust-intrinsic" { #[rustc_safe_intrinsic] diff --git a/compiler/rustc_error_codes/src/error_codes/E0230.md b/compiler/rustc_error_codes/src/error_codes/E0230.md index cfb72e74319c1..87ea90e73c902 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0230.md +++ b/compiler/rustc_error_codes/src/error_codes/E0230.md @@ -5,6 +5,7 @@ compiled: ```compile_fail,E0230 #![feature(rustc_attrs)] +#![allow(internal_features)] #[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{B}>`"] // error trait BadAnnotation {} diff --git a/compiler/rustc_error_codes/src/error_codes/E0231.md b/compiler/rustc_error_codes/src/error_codes/E0231.md index 23a0a88ecdd9b..a1aaf90df496a 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0231.md +++ b/compiler/rustc_error_codes/src/error_codes/E0231.md @@ -5,6 +5,7 @@ compiled: ```compile_fail,E0231 #![feature(rustc_attrs)] +#![allow(internal_features)] #[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{}>`"] // error! trait BadAnnotation {} diff --git a/compiler/rustc_error_codes/src/error_codes/E0232.md b/compiler/rustc_error_codes/src/error_codes/E0232.md index b310caefa6e31..0e50cf589ee69 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0232.md +++ b/compiler/rustc_error_codes/src/error_codes/E0232.md @@ -5,6 +5,7 @@ compiled: ```compile_fail,E0232 #![feature(rustc_attrs)] +#![allow(internal_features)] #[rustc_on_unimplemented(lorem="")] // error! trait BadAnnotation {} diff --git a/compiler/rustc_error_codes/src/error_codes/E0264.md b/compiler/rustc_error_codes/src/error_codes/E0264.md index e2a27f7b10672..d790607622992 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0264.md +++ b/compiler/rustc_error_codes/src/error_codes/E0264.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0264 #![feature(lang_items)] +#![allow(internal_features)] extern "C" { #[lang = "cake"] // error: unknown external lang item: `cake` @@ -16,6 +17,7 @@ A list of available external lang items is available in ``` #![feature(lang_items)] +#![allow(internal_features)] extern "C" { #[lang = "panic_impl"] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0391.md b/compiler/rustc_error_codes/src/error_codes/E0391.md index dff50ccaa0b77..457fbd002a14b 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0391.md +++ b/compiler/rustc_error_codes/src/error_codes/E0391.md @@ -14,3 +14,6 @@ trait SecondTrait : FirstTrait { The previous example contains a circular dependency between two traits: `FirstTrait` depends on `SecondTrait` which itself depends on `FirstTrait`. + +See https://rustc-dev-guide.rust-lang.org/overview.html#queries and +https://rustc-dev-guide.rust-lang.org/query.html for more information. diff --git a/compiler/rustc_error_codes/src/error_codes/E0439.md b/compiler/rustc_error_codes/src/error_codes/E0439.md index 24268aef2222a..369226c383ec5 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0439.md +++ b/compiler/rustc_error_codes/src/error_codes/E0439.md @@ -16,7 +16,7 @@ extern "platform-intrinsic" { The `simd_shuffle` function needs the length of the array passed as last parameter in its name. Example: -``` +```ignore (no longer compiles) #![feature(platform_intrinsics)] extern "platform-intrinsic" { diff --git a/compiler/rustc_error_codes/src/error_codes/E0539.md b/compiler/rustc_error_codes/src/error_codes/E0539.md index c53d60a5f4757..cd28afbc48de2 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0539.md +++ b/compiler/rustc_error_codes/src/error_codes/E0539.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0539 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[deprecated(note)] // error! @@ -28,6 +29,7 @@ To fix these issues you need to give required key-value pairs. ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[deprecated(since = "1.39.0", note = "reason")] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0542.md b/compiler/rustc_error_codes/src/error_codes/E0542.md index c69e574179b10..be186dbd2cc3b 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0542.md +++ b/compiler/rustc_error_codes/src/error_codes/E0542.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0542 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[stable(feature = "_stable_fn")] // invalid @@ -23,6 +24,7 @@ To fix this issue, you need to provide the `since` field. Example: ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[stable(feature = "_stable_fn", since = "1.0.0")] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0543.md b/compiler/rustc_error_codes/src/error_codes/E0543.md index d0b2e2f7a7d0f..5051c72a15149 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0543.md +++ b/compiler/rustc_error_codes/src/error_codes/E0543.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0543 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[stable(since = "0.1.0", feature = "_deprecated_fn")] @@ -17,6 +18,7 @@ To fix this issue, you need to provide the `note` field. Example: ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[stable(since = "0.1.0", feature = "_deprecated_fn")] diff --git a/compiler/rustc_error_codes/src/error_codes/E0544.md b/compiler/rustc_error_codes/src/error_codes/E0544.md index 2227e2a06bf92..202401f9d45f9 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0544.md +++ b/compiler/rustc_error_codes/src/error_codes/E0544.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0544 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "rust1")] #[stable(feature = "rust1", since = "1.0.0")] @@ -15,6 +16,7 @@ To fix this issue, ensure that each item has at most one stability attribute. ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "rust1")] #[stable(feature = "test", since = "2.0.0")] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0545.md b/compiler/rustc_error_codes/src/error_codes/E0545.md index 7aba084f4d3aa..880378ebd3a27 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0545.md +++ b/compiler/rustc_error_codes/src/error_codes/E0545.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0545 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[unstable(feature = "_unstable_fn", issue = "0")] // invalid @@ -18,6 +19,7 @@ Example: ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[unstable(feature = "_unstable_fn", issue = "none")] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0546.md b/compiler/rustc_error_codes/src/error_codes/E0546.md index a33dcb7a9ac58..8c98eaa07bbaf 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0546.md +++ b/compiler/rustc_error_codes/src/error_codes/E0546.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0546 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[unstable(issue = "none")] // invalid @@ -17,6 +18,7 @@ To fix this issue, you need to provide the `feature` field. Example: ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[unstable(feature = "unstable_fn", issue = "none")] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0547.md b/compiler/rustc_error_codes/src/error_codes/E0547.md index 4950325df6400..5b0f7cd44cf8c 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0547.md +++ b/compiler/rustc_error_codes/src/error_codes/E0547.md @@ -4,6 +4,7 @@ Erroneous code example: ```compile_fail,E0547 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[unstable(feature = "_unstable_fn")] // invalid @@ -17,6 +18,7 @@ To fix this issue, you need to provide the `issue` field. Example: ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[unstable(feature = "_unstable_fn", issue = "none")] // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0549.md b/compiler/rustc_error_codes/src/error_codes/E0549.md index 70e458a98673c..cc6a47fe253e2 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0549.md +++ b/compiler/rustc_error_codes/src/error_codes/E0549.md @@ -5,6 +5,7 @@ Erroneous code example: ```compile_fail,E0549 #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[deprecated( @@ -19,6 +20,7 @@ Example: ``` #![feature(staged_api)] +#![allow(internal_features)] #![stable(since = "1.0.0", feature = "test")] #[stable(since = "1.0.0", feature = "test")] diff --git a/compiler/rustc_error_codes/src/error_codes/E0577.md b/compiler/rustc_error_codes/src/error_codes/E0577.md index eba2d3b14175b..383ca61f6c48a 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0577.md +++ b/compiler/rustc_error_codes/src/error_codes/E0577.md @@ -3,7 +3,7 @@ Something other than a module was found in visibility scope. Erroneous code example: ```compile_fail,E0577,edition2018 -pub struct Sea; +pub enum Sea {} pub (in crate::Sea) struct Shark; // error! diff --git a/compiler/rustc_error_codes/src/error_codes/E0622.md b/compiler/rustc_error_codes/src/error_codes/E0622.md index 3ba3ed10e5c62..5d71ee9949d86 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0622.md +++ b/compiler/rustc_error_codes/src/error_codes/E0622.md @@ -4,6 +4,8 @@ Erroneous code example: ```compile_fail,E0622 #![feature(intrinsics)] +#![allow(internal_features)] + extern "rust-intrinsic" { pub static breakpoint: fn(); // error: intrinsic must be a function } @@ -17,6 +19,8 @@ error, just declare a function. Example: ```no_run #![feature(intrinsics)] +#![allow(internal_features)] + extern "rust-intrinsic" { pub fn breakpoint(); // ok! } diff --git a/compiler/rustc_error_codes/src/error_codes/E0691.md b/compiler/rustc_error_codes/src/error_codes/E0691.md index 60060cacbd60b..483c74c0ff56c 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0691.md +++ b/compiler/rustc_error_codes/src/error_codes/E0691.md @@ -11,7 +11,8 @@ struct ForceAlign32; #[repr(transparent)] struct Wrapper(f32, ForceAlign32); // error: zero-sized field in transparent - // struct has alignment larger than 1 + // struct has alignment of 32, which + // is larger than 1 ``` A transparent struct, enum, or union is supposed to be represented exactly like diff --git a/compiler/rustc_error_codes/src/error_codes/E0741.md b/compiler/rustc_error_codes/src/error_codes/E0741.md index 70d963cd41f21..0c7010526655e 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0741.md +++ b/compiler/rustc_error_codes/src/error_codes/E0741.md @@ -10,15 +10,19 @@ struct A; struct B; // error! ``` -Only structural-match types (that is, types that derive `PartialEq` and `Eq`) -may be used as the types of const generic parameters. +Only structural-match types, which are types that derive `PartialEq` and `Eq` +and implement `ConstParamTy`, may be used as the types of const generic +parameters. -To fix the previous code example, we derive `PartialEq` and `Eq`: +To fix the previous code example, we derive `PartialEq`, `Eq`, and +`ConstParamTy`: ``` #![feature(adt_const_params)] -#[derive(PartialEq, Eq)] // We derive both traits here. +use std::marker::ConstParamTy; + +#[derive(PartialEq, Eq, ConstParamTy)] // We derive both traits here. struct A; struct B; // ok! diff --git a/compiler/rustc_error_codes/src/error_codes/E0773.md b/compiler/rustc_error_codes/src/error_codes/E0773.md index b19a58bf33d2c..aa65a475a4f9d 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0773.md +++ b/compiler/rustc_error_codes/src/error_codes/E0773.md @@ -5,6 +5,7 @@ Erroneous code example: ```compile_fail,E0773 #![feature(decl_macro)] #![feature(rustc_attrs)] +#![allow(internal_features)] #[rustc_builtin_macro] pub macro test($item:item) { @@ -24,6 +25,7 @@ To fix the issue, remove the duplicate declaration: ``` #![feature(decl_macro)] #![feature(rustc_attrs)] +#![allow(internal_features)] #[rustc_builtin_macro] pub macro test($item:item) { diff --git a/compiler/rustc_error_codes/src/error_codes/E0789.md b/compiler/rustc_error_codes/src/error_codes/E0789.md index 89b7cd422fe96..2c0018cc799f4 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0789.md +++ b/compiler/rustc_error_codes/src/error_codes/E0789.md @@ -10,6 +10,7 @@ Erroneous code example: // used outside of the compiler and standard library. #![feature(rustc_attrs)] #![feature(staged_api)] +#![allow(internal_features)] #![unstable(feature = "foo_module", reason = "...", issue = "123")] diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs index 0accb4ab96f59..3bf15505090cf 100644 --- a/compiler/rustc_error_messages/src/lib.rs +++ b/compiler/rustc_error_messages/src/lib.rs @@ -4,6 +4,7 @@ #![feature(type_alias_impl_trait)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate tracing; @@ -71,17 +72,17 @@ pub enum TranslationBundleError { impl fmt::Display for TranslationBundleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TranslationBundleError::ReadFtl(e) => write!(f, "could not read ftl file: {}", e), + TranslationBundleError::ReadFtl(e) => write!(f, "could not read ftl file: {e}"), TranslationBundleError::ParseFtl(e) => { - write!(f, "could not parse ftl file: {}", e) + write!(f, "could not parse ftl file: {e}") } - TranslationBundleError::AddResource(e) => write!(f, "failed to add resource: {}", e), + TranslationBundleError::AddResource(e) => write!(f, "failed to add resource: {e}"), TranslationBundleError::MissingLocale => write!(f, "missing locale directory"), TranslationBundleError::ReadLocalesDir(e) => { - write!(f, "could not read locales dir: {}", e) + write!(f, "could not read locales dir: {e}") } TranslationBundleError::ReadLocalesDirEntry(e) => { - write!(f, "could not read locales dir entry: {}", e) + write!(f, "could not read locales dir entry: {e}") } TranslationBundleError::LocaleIsNotDir => { write!(f, "`$sysroot/share/locales/$locale` is not a directory") @@ -226,7 +227,7 @@ fn register_functions(bundle: &mut FluentBundle) { pub type LazyFallbackBundle = Lrc FluentBundle>>; /// Return the default `FluentBundle` with standard "en-US" diagnostic messages. -#[instrument(level = "trace")] +#[instrument(level = "trace", skip(resources))] pub fn fallback_fluent_bundle( resources: Vec<&'static str>, with_directionality_markers: bool, @@ -242,7 +243,6 @@ pub fn fallback_fluent_bundle( for resource in resources { let resource = FluentResource::try_new(resource.to_string()) .expect("failed to parse fallback fluent resource"); - trace!(?resource); fallback_bundle.add_resource_overriding(resource); } @@ -263,8 +263,7 @@ type FluentId = Cow<'static, str>; #[rustc_diagnostic_item = "SubdiagnosticMessage"] pub enum SubdiagnosticMessage { /// Non-translatable diagnostic message. - // FIXME(davidtwco): can a `Cow<'static, str>` be used here? - Str(String), + Str(Cow<'static, str>), /// Translatable message which has already been translated eagerly. /// /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would @@ -275,8 +274,7 @@ pub enum SubdiagnosticMessage { /// incorrect diagnostics. Eager translation results in translation for a subdiagnostic /// happening immediately after the subdiagnostic derive's logic has been run. This variant /// stores messages which have been translated eagerly. - // FIXME(#100717): can a `Cow<'static, str>` be used here? - Eager(String), + Eager(Cow<'static, str>), /// Identifier of a Fluent message. Instances of this variant are generated by the /// `Subdiagnostic` derive. FluentIdentifier(FluentId), @@ -290,17 +288,17 @@ pub enum SubdiagnosticMessage { impl From for SubdiagnosticMessage { fn from(s: String) -> Self { - SubdiagnosticMessage::Str(s) + SubdiagnosticMessage::Str(Cow::Owned(s)) } } -impl<'a> From<&'a str> for SubdiagnosticMessage { - fn from(s: &'a str) -> Self { - SubdiagnosticMessage::Str(s.to_string()) +impl From<&'static str> for SubdiagnosticMessage { + fn from(s: &'static str) -> Self { + SubdiagnosticMessage::Str(Cow::Borrowed(s)) } } impl From> for SubdiagnosticMessage { fn from(s: Cow<'static, str>) -> Self { - SubdiagnosticMessage::Str(s.to_string()) + SubdiagnosticMessage::Str(s) } } @@ -312,8 +310,7 @@ impl From> for SubdiagnosticMessage { #[rustc_diagnostic_item = "DiagnosticMessage"] pub enum DiagnosticMessage { /// Non-translatable diagnostic message. - // FIXME(#100717): can a `Cow<'static, str>` be used here? - Str(String), + Str(Cow<'static, str>), /// Translatable message which has already been translated eagerly. /// /// Some diagnostics have repeated subdiagnostics where the same interpolated variables would @@ -324,8 +321,7 @@ pub enum DiagnosticMessage { /// incorrect diagnostics. Eager translation results in translation for a subdiagnostic /// happening immediately after the subdiagnostic derive's logic has been run. This variant /// stores messages which have been translated eagerly. - // FIXME(#100717): can a `Cow<'static, str>` be used here? - Eager(String), + Eager(Cow<'static, str>), /// Identifier for a Fluent message (with optional attribute) corresponding to the diagnostic /// message. /// @@ -359,21 +355,28 @@ impl DiagnosticMessage { } } } + + pub fn as_str(&self) -> Option<&str> { + match self { + DiagnosticMessage::Eager(s) | DiagnosticMessage::Str(s) => Some(s), + DiagnosticMessage::FluentIdentifier(_, _) => None, + } + } } impl From for DiagnosticMessage { fn from(s: String) -> Self { - DiagnosticMessage::Str(s) + DiagnosticMessage::Str(Cow::Owned(s)) } } -impl<'a> From<&'a str> for DiagnosticMessage { - fn from(s: &'a str) -> Self { - DiagnosticMessage::Str(s.to_string()) +impl From<&'static str> for DiagnosticMessage { + fn from(s: &'static str) -> Self { + DiagnosticMessage::Str(Cow::Borrowed(s)) } } impl From> for DiagnosticMessage { fn from(s: Cow<'static, str>) -> Self { - DiagnosticMessage::Str(s.to_string()) + DiagnosticMessage::Str(s) } } @@ -496,6 +499,10 @@ impl MultiSpan { replacements_occurred } + pub fn pop_span_label(&mut self) -> Option<(Span, DiagnosticMessage)> { + self.span_labels.pop() + } + /// Returns the strings to highlight. We always ensure that there /// is an entry for each of the primary spans -- for each primary /// span `P`, if there is at least one label with span `P`, we return @@ -527,6 +534,14 @@ impl MultiSpan { pub fn has_span_labels(&self) -> bool { self.span_labels.iter().any(|(sp, _)| !sp.is_dummy()) } + + /// Clone this `MultiSpan` without keeping any of the span labels - sometimes a `MultiSpan` is + /// to be re-used in another diagnostic, but includes `span_labels` which have translated + /// messages. These translated messages would fail to translate without their diagnostic + /// arguments which are unlikely to be cloned alongside the `Span`. + pub fn clone_ignoring_labels(&self) -> Self { + Self { primary_spans: self.primary_spans.clone(), ..MultiSpan::new() } + } } impl From for MultiSpan { diff --git a/compiler/rustc_errors/Cargo.toml b/compiler/rustc_errors/Cargo.toml index bd3033fcb3e86..faab9f09da838 100644 --- a/compiler/rustc_errors/Cargo.toml +++ b/compiler/rustc_errors/Cargo.toml @@ -20,11 +20,12 @@ rustc_hir = { path = "../rustc_hir" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_type_ir = { path = "../rustc_type_ir" } unicode-width = "0.1.4" -termcolor = "1.0" +termcolor = "1.2.0" annotate-snippets = "0.9" termize = "0.1.1" serde = { version = "1.0.125", features = [ "derive" ] } serde_json = "1.0.59" +derive_setters = "0.1.6" [target.'cfg(windows)'.dependencies.windows] version = "0.48.0" diff --git a/compiler/rustc_errors/messages.ftl b/compiler/rustc_errors/messages.ftl index dde1d6c0a819c..d68dba0be5e9e 100644 --- a/compiler/rustc_errors/messages.ftl +++ b/compiler/rustc_errors/messages.ftl @@ -1,14 +1,24 @@ -errors_target_invalid_address_space = - invalid address space `{$addr_space}` for `{$cause}` in "data-layout": {$err} +errors_delayed_at_with_newline = + delayed at {$emitted_at} + {$note} -errors_target_invalid_bits = - invalid {$kind} `{$bit}` for `{$cause}` in "data-layout": {$err} +errors_delayed_at_without_newline = + delayed at {$emitted_at} - {$note} -errors_target_missing_alignment = - missing alignment for `{$cause}` in "data-layout" +errors_expected_lifetime_parameter = + expected lifetime {$count -> + [1] parameter + *[other] parameters + } -errors_target_invalid_alignment = - invalid alignment for `{$cause}` in "data-layout": {$err} +errors_indicate_anonymous_lifetime = + indicate the anonymous {$count -> + [1] lifetime + *[other] lifetimes + } + +errors_invalid_flushed_delayed_diagnostic_level = + `flushed_delayed` got diagnostic with level {$level}, instead of the expected `DelayedBug` errors_target_inconsistent_architecture = inconsistent target specification: "data-layout" claims architecture is {$dl}-endian, while "target-endian" is `{$target}` @@ -16,4 +26,20 @@ errors_target_inconsistent_architecture = errors_target_inconsistent_pointer_width = inconsistent target specification: "data-layout" claims pointers are {$pointer_size}-bit, while "target-pointer-width" is `{$target}` +errors_target_invalid_address_space = + invalid address space `{$addr_space}` for `{$cause}` in "data-layout": {$err} + +errors_target_invalid_alignment = + invalid alignment for `{$cause}` in "data-layout": `{$align}` is {$err_kind -> + [not_power_of_two] not a power of 2 + [too_large] too large + *[other] {""} + } + +errors_target_invalid_bits = + invalid {$kind} `{$bit}` for `{$cause}` in "data-layout": {$err} + errors_target_invalid_bits_size = {$err} + +errors_target_missing_alignment = + missing alignment for `{$cause}` in "data-layout" diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index 9872b3bda1e06..a88fba6dae63d 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -157,10 +157,8 @@ impl AnnotateSnippetEmitterWriter { { annotated_files.swap(0, pos); } - // owned: line source, line index, annotations - type Owned = (String, usize, Vec); - let filename = source_map.filename_for_diagnostics(&primary_lo.file.name); - let origin = filename.to_string_lossy(); + // owned: file name, line source, line index, annotations + type Owned = (String, String, usize, Vec); let annotated_files: Vec = annotated_files .into_iter() .flat_map(|annotated_file| { @@ -169,7 +167,15 @@ impl AnnotateSnippetEmitterWriter { .lines .into_iter() .map(|line| { - (source_string(file.clone(), &line), line.line_index, line.annotations) + // Ensure the source file is present before we try + // to load a string from it. + source_map.ensure_source_file_source_present(file.clone()); + ( + format!("{}", source_map.filename_for_diagnostics(&file.name)), + source_string(file.clone(), &line), + line.line_index, + line.annotations, + ) }) .collect::>() }) @@ -192,11 +198,11 @@ impl AnnotateSnippetEmitterWriter { }, slices: annotated_files .iter() - .map(|(source, line_index, annotations)| { + .map(|(file_name, source, line_index, annotations)| { Slice { source, line_start: *line_index, - origin: Some(&origin), + origin: Some(&file_name), // FIXME(#59346): Not really sure when `fold` should be true or false fold: false, annotations: annotations diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 29c692128bcf9..a96e317df55dc 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -10,7 +10,7 @@ use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_span::symbol::Symbol; use rustc_span::{Span, DUMMY_SP}; use std::borrow::Cow; -use std::fmt; +use std::fmt::{self, Debug}; use std::hash::{Hash, Hasher}; use std::panic::Location; @@ -33,7 +33,7 @@ pub type DiagnosticArgName<'source> = Cow<'source, str>; #[derive(Clone, Debug, PartialEq, Eq, Hash, Encodable, Decodable)] pub enum DiagnosticArgValue<'source> { Str(Cow<'source, str>), - Number(usize), + Number(i128), StrListSepByAnd(Vec>), } @@ -352,14 +352,9 @@ impl Diagnostic { /// Labels all the given spans with the provided label. /// See [`Self::span_label()`] for more information. - pub fn span_labels( - &mut self, - spans: impl IntoIterator, - label: impl AsRef, - ) -> &mut Self { - let label = label.as_ref(); + pub fn span_labels(&mut self, spans: impl IntoIterator, label: &str) -> &mut Self { for span in spans { - self.span_label(span, label); + self.span_label(span, label.to_string()); } self } @@ -394,17 +389,18 @@ impl Diagnostic { expected: DiagnosticStyledString, found: DiagnosticStyledString, ) -> &mut Self { - let mut msg: Vec<_> = vec![("required when trying to coerce from type `", Style::NoStyle)]; + let mut msg: Vec<_> = + vec![(Cow::from("required when trying to coerce from type `"), Style::NoStyle)]; msg.extend(expected.0.iter().map(|x| match *x { - StringPart::Normal(ref s) => (s.as_str(), Style::NoStyle), - StringPart::Highlighted(ref s) => (s.as_str(), Style::Highlight), + StringPart::Normal(ref s) => (Cow::from(s.clone()), Style::NoStyle), + StringPart::Highlighted(ref s) => (Cow::from(s.clone()), Style::Highlight), })); - msg.push(("` to type '", Style::NoStyle)); + msg.push((Cow::from("` to type '"), Style::NoStyle)); msg.extend(found.0.iter().map(|x| match *x { - StringPart::Normal(ref s) => (s.as_str(), Style::NoStyle), - StringPart::Highlighted(ref s) => (s.as_str(), Style::Highlight), + StringPart::Normal(ref s) => (Cow::from(s.clone()), Style::NoStyle), + StringPart::Highlighted(ref s) => (Cow::from(s.clone()), Style::Highlight), })); - msg.push(("`", Style::NoStyle)); + msg.push((Cow::from("`"), Style::NoStyle)); // For now, just attach these as notes self.highlighted_note(msg); @@ -424,13 +420,13 @@ impl Diagnostic { let expected_label = if expected_label.is_empty() { "expected".to_string() } else { - format!("expected {}", expected_label) + format!("expected {expected_label}") }; let found_label = found_label.to_string(); let found_label = if found_label.is_empty() { "found".to_string() } else { - format!("found {}", found_label) + format!("found {found_label}") }; let (found_padding, expected_padding) = if expected_label.len() > found_label.len() { (expected_label.len() - found_label.len(), 0) @@ -443,13 +439,13 @@ impl Diagnostic { StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle), StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight), })); - msg.push((format!("`{}\n", expected_extra), Style::NoStyle)); + msg.push((format!("`{expected_extra}\n"), Style::NoStyle)); msg.push((format!("{}{} `", " ".repeat(found_padding), found_label), Style::NoStyle)); msg.extend(found.0.iter().map(|x| match *x { StringPart::Normal(ref s) => (s.to_owned(), Style::NoStyle), StringPart::Highlighted(ref s) => (s.to_owned(), Style::Highlight), })); - msg.push((format!("`{}", found_extra), Style::NoStyle)); + msg.push((format!("`{found_extra}"), Style::NoStyle)); // For now, just attach these as notes. self.highlighted_note(msg); @@ -458,7 +454,7 @@ impl Diagnostic { pub fn note_trait_signature(&mut self, name: Symbol, signature: String) -> &mut Self { self.highlighted_note(vec![ - (format!("`{}` from trait: `", name), Style::NoStyle), + (format!("`{name}` from trait: `"), Style::NoStyle), (signature, Style::Highlight), ("`".to_string(), Style::NoStyle), ]); diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index db97d96fccd18..5e23ae655fe80 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -115,36 +115,22 @@ pub trait EmissionGuarantee: Sized { ) -> DiagnosticBuilder<'_, Self>; } -/// Private module for sealing the `IsError` helper trait. -mod sealed_level_is_error { - use crate::Level; - - /// Sealed helper trait for statically checking that a `Level` is an error. - pub(crate) trait IsError {} - - impl IsError<{ Level::Bug }> for () {} - impl IsError<{ Level::DelayedBug }> for () {} - impl IsError<{ Level::Fatal }> for () {} - // NOTE(eddyb) `Level::Error { lint: true }` is also an error, but lints - // don't need error guarantees, as their levels are always dynamic. - impl IsError<{ Level::Error { lint: false } }> for () {} -} - impl<'a> DiagnosticBuilder<'a, ErrorGuaranteed> { /// Convenience function for internal use, clients should use one of the /// `struct_*` methods on [`Handler`]. #[track_caller] - pub(crate) fn new_guaranteeing_error, const L: Level>( + pub(crate) fn new_guaranteeing_error>( handler: &'a Handler, message: M, - ) -> Self - where - (): sealed_level_is_error::IsError, - { + ) -> Self { Self { inner: DiagnosticBuilderInner { state: DiagnosticBuilderState::Emittable(handler), - diagnostic: Box::new(Diagnostic::new_with_code(L, None, message)), + diagnostic: Box::new(Diagnostic::new_with_code( + Level::Error { lint: false }, + None, + message, + )), }, _marker: PhantomData, } @@ -203,9 +189,7 @@ impl EmissionGuarantee for ErrorGuaranteed { handler: &Handler, msg: impl Into, ) -> DiagnosticBuilder<'_, Self> { - DiagnosticBuilder::new_guaranteeing_error::<_, { Level::Error { lint: false } }>( - handler, msg, - ) + DiagnosticBuilder::new_guaranteeing_error(handler, msg) } } @@ -552,13 +536,15 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { } }; - if handler.flags.dont_buffer_diagnostics || handler.flags.treat_err_as_bug.is_some() { + if handler.inner.lock().flags.dont_buffer_diagnostics + || handler.inner.lock().flags.treat_err_as_bug.is_some() + { self.emit(); return None; } // Take the `Diagnostic` by replacing it with a dummy. - let dummy = Diagnostic::new(Level::Allow, DiagnosticMessage::Str("".to_string())); + let dummy = Diagnostic::new(Level::Allow, DiagnosticMessage::from("")); let diagnostic = std::mem::replace(&mut *self.inner.diagnostic, dummy); // Disable the ICE on `Drop`. @@ -627,7 +613,7 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> { pub fn span_labels( &mut self, spans: impl IntoIterator, - label: impl AsRef, + label: &str, ) -> &mut Self); forward!(pub fn note_expected_found( @@ -781,8 +767,8 @@ impl Drop for DiagnosticBuilderInner<'_> { if !panicking() { handler.emit_diagnostic(&mut Diagnostic::new( Level::Bug, - DiagnosticMessage::Str( - "the following error was constructed but not emitted".to_string(), + DiagnosticMessage::from( + "the following error was constructed but not emitted", ), )); handler.emit_diagnostic(&mut self.diagnostic); diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs index 65f8a61a30a9a..a170e3a8943ff 100644 --- a/compiler/rustc_errors/src/diagnostic_impls.rs +++ b/compiler/rustc_errors/src/diagnostic_impls.rs @@ -1,3 +1,4 @@ +use crate::diagnostic::DiagnosticLocation; use crate::{fluent_generated as fluent, AddToDiagnostic}; use crate::{DiagnosticArgValue, DiagnosticBuilder, Handler, IntoDiagnostic, IntoDiagnosticArg}; use rustc_ast as ast; @@ -10,6 +11,7 @@ use rustc_span::Span; use rustc_target::abi::TargetDataLayoutErrors; use rustc_target::spec::{PanicStrategy, SplitDebuginfo, StackProtector, TargetTriple}; use rustc_type_ir as type_ir; +use std::backtrace::Backtrace; use std::borrow::Cow; use std::fmt; use std::num::ParseIntError; @@ -60,10 +62,8 @@ into_diagnostic_arg_using_display!( u8, i16, u16, - i32, u32, i64, - u64, i128, u128, std::io::Error, @@ -80,6 +80,18 @@ into_diagnostic_arg_using_display!( ExitStatus, ); +impl IntoDiagnosticArg for i32 { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Number(self.into()) + } +} + +impl IntoDiagnosticArg for u64 { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Number(self.into()) + } +} + impl IntoDiagnosticArg for bool { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { if self { @@ -92,7 +104,7 @@ impl IntoDiagnosticArg for bool { impl IntoDiagnosticArg for char { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - DiagnosticArgValue::Str(Cow::Owned(format!("{:?}", self))) + DiagnosticArgValue::Str(Cow::Owned(format!("{self:?}"))) } } @@ -134,7 +146,7 @@ impl IntoDiagnosticArg for PathBuf { impl IntoDiagnosticArg for usize { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - DiagnosticArgValue::Number(self) + DiagnosticArgValue::Number(self as i128) } } @@ -147,13 +159,19 @@ impl IntoDiagnosticArg for PanicStrategy { impl IntoDiagnosticArg for hir::ConstContext { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { DiagnosticArgValue::Str(Cow::Borrowed(match self { - hir::ConstContext::ConstFn => "constant function", + hir::ConstContext::ConstFn => "const_fn", hir::ConstContext::Static(_) => "static", - hir::ConstContext::Const => "constant", + hir::ConstContext::Const => "const", })) } } +impl IntoDiagnosticArg for ast::Expr { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Owned(pprust::expr_to_string(&self))) + } +} + impl IntoDiagnosticArg for ast::Path { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { DiagnosticArgValue::Str(Cow::Owned(pprust::path_to_string(&self))) @@ -254,7 +272,8 @@ impl IntoDiagnostic<'_, !> for TargetDataLayoutErrors<'_> { TargetDataLayoutErrors::InvalidAlignment { cause, err } => { diag = handler.struct_fatal(fluent::errors_target_invalid_alignment); diag.set_arg("cause", cause); - diag.set_arg("err", err); + diag.set_arg("err_kind", err.diag_ident()); + diag.set_arg("align", err.align()); diag } TargetDataLayoutErrors::InconsistentTargetArchitecture { dl, target } => { @@ -300,3 +319,62 @@ pub enum LabelKind { Label, Help, } + +#[derive(Subdiagnostic)] +#[label(errors_expected_lifetime_parameter)] +pub struct ExpectedLifetimeParameter { + #[primary_span] + pub span: Span, + pub count: usize, +} + +#[derive(Subdiagnostic)] +#[note(errors_delayed_at_with_newline)] +pub struct DelayedAtWithNewline { + #[primary_span] + pub span: Span, + pub emitted_at: DiagnosticLocation, + pub note: Backtrace, +} +#[derive(Subdiagnostic)] +#[note(errors_delayed_at_without_newline)] +pub struct DelayedAtWithoutNewline { + #[primary_span] + pub span: Span, + pub emitted_at: DiagnosticLocation, + pub note: Backtrace, +} + +impl IntoDiagnosticArg for DiagnosticLocation { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::from(self.to_string())) + } +} + +impl IntoDiagnosticArg for Backtrace { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::from(self.to_string())) + } +} + +#[derive(Subdiagnostic)] +#[note(errors_invalid_flushed_delayed_diagnostic_level)] +pub struct InvalidFlushedDelayedDiagnosticLevel { + #[primary_span] + pub span: Span, + pub level: rustc_errors::Level, +} +impl IntoDiagnosticArg for rustc_errors::Level { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::from(self.to_string())) + } +} + +#[derive(Subdiagnostic)] +#[suggestion(errors_indicate_anonymous_lifetime, code = "{suggestion}", style = "verbose")] +pub struct IndicateAnonymousLifetime { + #[primary_span] + pub span: Span, + pub count: usize, + pub suggestion: String, +} diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 6d944e5131456..0cae06881b174 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -7,8 +7,6 @@ //! //! The output types are defined in `rustc_session::config::ErrorOutputType`. -use Destination::*; - use rustc_span::source_map::SourceMap; use rustc_span::{FileLines, SourceFile, Span}; @@ -24,6 +22,7 @@ use crate::{ }; use rustc_lint_defs::pluralize; +use derive_setters::Setters; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::sync::Lrc; use rustc_error_messages::{FluentArgs, SpanLabel}; @@ -35,8 +34,8 @@ use std::io::prelude::*; use std::io::{self, IsTerminal}; use std::iter; use std::path::Path; -use termcolor::{Ansi, BufferWriter, ColorChoice, ColorSpec, StandardStream}; -use termcolor::{Buffer, Color, WriteColor}; +use termcolor::{Ansi, Buffer, BufferWriter, ColorChoice, ColorSpec, StandardStream}; +use termcolor::{Color, WriteColor}; /// Default column width, used in tests and when terminal dimensions cannot be determined. const DEFAULT_COLUMN_WIDTH: usize = 140; @@ -60,31 +59,15 @@ impl HumanReadableErrorType { } pub fn new_emitter( self, - dst: Box, - source_map: Option>, - bundle: Option>, + mut dst: Box, fallback_bundle: LazyFallbackBundle, - teach: bool, - diagnostic_width: Option, - macro_backtrace: bool, - track_diagnostics: bool, - terminal_url: TerminalUrl, ) -> EmitterWriter { let (short, color_config) = self.unzip(); let color = color_config.suggests_using_colors(); - EmitterWriter::new( - dst, - source_map, - bundle, - fallback_bundle, - short, - teach, - color, - diagnostic_width, - macro_backtrace, - track_diagnostics, - terminal_url, - ) + if !dst.supports_color() && color { + dst = Box::new(Ansi::new(dst)); + } + EmitterWriter::new(dst, fallback_bundle).short_message(short) } } @@ -279,12 +262,12 @@ pub trait Emitter: Translate { let msg = if substitution.is_empty() || sugg.style.hide_inline() { // This substitution is only removal OR we explicitly don't want to show the // code inline (`hide_inline`). Therefore, we don't show the substitution. - format!("help: {}", &msg) + format!("help: {msg}") } else { // Show the default suggestion text with the substitution format!( "help: {}{}: `{}`", - &msg, + msg, if self.source_map().is_some_and(|sm| is_case_difference( sm, substitution, @@ -332,7 +315,7 @@ pub trait Emitter: Translate { // Skip past non-macro entries, just in case there // are some which do actually involve macros. - ExpnKind::Inlined | ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None, + ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None, ExpnKind::Macro(macro_kind, name) => Some((macro_kind, name)), } @@ -367,7 +350,7 @@ pub trait Emitter: Translate { children.push(SubDiagnostic { level: Level::Note, - message: vec![(DiagnosticMessage::Str(msg), Style::NoStyle)], + message: vec![(DiagnosticMessage::from(msg), Style::NoStyle)], span: MultiSpan::new(), render_span: None, }); @@ -403,7 +386,7 @@ pub trait Emitter: Translate { continue; } - if always_backtrace && !matches!(trace.kind, ExpnKind::Inlined) { + if always_backtrace { new_labels.push(( trace.def_site, format!( @@ -442,7 +425,6 @@ pub trait Emitter: Translate { "this derive macro expansion".into() } ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(), - ExpnKind::Inlined => "this inlined function call".into(), ExpnKind::Root => "the crate root".into(), ExpnKind::AstPass(kind) => kind.descr().into(), ExpnKind::Desugaring(kind) => { @@ -617,7 +599,7 @@ pub enum ColorConfig { } impl ColorConfig { - fn to_color_choice(self) -> ColorChoice { + pub fn to_color_choice(self) -> ColorChoice { match self { ColorConfig::Always => { if io::stderr().is_terminal() { @@ -640,10 +622,13 @@ impl ColorConfig { } /// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short` +#[derive(Setters)] pub struct EmitterWriter { + #[setters(skip)] dst: Destination, sm: Option>, fluent_bundle: Option>, + #[setters(skip)] fallback_bundle: LazyFallbackBundle, short_message: bool, teach: bool, @@ -663,65 +648,32 @@ pub struct FileWithAnnotatedLines { } impl EmitterWriter { - pub fn stderr( - color_config: ColorConfig, - source_map: Option>, - fluent_bundle: Option>, - fallback_bundle: LazyFallbackBundle, - short_message: bool, - teach: bool, - diagnostic_width: Option, - macro_backtrace: bool, - track_diagnostics: bool, - terminal_url: TerminalUrl, - ) -> EmitterWriter { - let dst = Destination::from_stderr(color_config); + pub fn stderr(color_config: ColorConfig, fallback_bundle: LazyFallbackBundle) -> EmitterWriter { + let dst = from_stderr(color_config); + Self::create(dst, fallback_bundle) + } + + fn create(dst: Destination, fallback_bundle: LazyFallbackBundle) -> EmitterWriter { EmitterWriter { dst, - sm: source_map, - fluent_bundle, + sm: None, + fluent_bundle: None, fallback_bundle, - short_message, - teach, + short_message: false, + teach: false, ui_testing: false, - diagnostic_width, - macro_backtrace, - track_diagnostics, - terminal_url, + diagnostic_width: None, + macro_backtrace: false, + track_diagnostics: false, + terminal_url: TerminalUrl::No, } } pub fn new( - dst: Box, - source_map: Option>, - fluent_bundle: Option>, + dst: Box, fallback_bundle: LazyFallbackBundle, - short_message: bool, - teach: bool, - colored: bool, - diagnostic_width: Option, - macro_backtrace: bool, - track_diagnostics: bool, - terminal_url: TerminalUrl, ) -> EmitterWriter { - EmitterWriter { - dst: Raw(dst, colored), - sm: source_map, - fluent_bundle, - fallback_bundle, - short_message, - teach, - ui_testing: false, - diagnostic_width, - macro_backtrace, - track_diagnostics, - terminal_url, - } - } - - pub fn ui_testing(mut self, ui_testing: bool) -> Self { - self.ui_testing = ui_testing; - self + Self::create(dst, fallback_bundle) } fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> { @@ -1983,7 +1935,7 @@ impl EmitterWriter { // We special case `#[derive(_)]\n` and other attribute suggestions, because those // are the ones where context is most useful. let file_lines = sm - .span_to_lines(span.primary_span().unwrap().shrink_to_hi()) + .span_to_lines(parts[0].span.shrink_to_hi()) .expect("span_to_lines failed when emitting suggestion"); let line_num = sm.lookup_char_pos(parts[0].span.lo()).line; if let Some(line) = file_lines.file.get_line(line_num - 1) { @@ -2146,7 +2098,7 @@ impl EmitterWriter { &mut self.dst, self.short_message, ) { - panic!("failed to emit error: {}", e) + panic!("failed to emit error: {e}") } } if !self.short_message { @@ -2162,7 +2114,7 @@ impl EmitterWriter { true, None, ) { - panic!("failed to emit error: {}", err); + panic!("failed to emit error: {err}"); } } for sugg in suggestions { @@ -2181,7 +2133,7 @@ impl EmitterWriter { true, None, ) { - panic!("failed to emit error: {}", e); + panic!("failed to emit error: {e}"); } } SuggestionStyle::HideCodeInline @@ -2194,22 +2146,21 @@ impl EmitterWriter { &Level::Help, max_line_num_len, ) { - panic!("failed to emit error: {}", e); + panic!("failed to emit error: {e}"); } } } } } } - Err(e) => panic!("failed to emit error: {}", e), + Err(e) => panic!("failed to emit error: {e}"), } - let mut dst = self.dst.writable(); - match writeln!(dst) { - Err(e) => panic!("failed to emit error: {}", e), + match writeln!(self.dst) { + Err(e) => panic!("failed to emit error: {e}"), _ => { - if let Err(e) = dst.flush() { - panic!("failed to emit error: {}", e) + if let Err(e) = self.dst.flush() { + panic!("failed to emit error: {e}") } } } @@ -2619,8 +2570,6 @@ fn emit_to_destination( ) -> io::Result<()> { use crate::lock; - let mut dst = dst.writable(); - // In order to prevent error message interleaving, where multiple error lines get intermixed // when multiple compiler processes error simultaneously, we emit errors with additional // steps. @@ -2636,7 +2585,8 @@ fn emit_to_destination( let _buffer_lock = lock::acquire_global_lock("rustc_errors"); for (pos, line) in rendered_buffer.iter().enumerate() { for part in line { - dst.apply_style(*lvl, part.style)?; + let style = part.style.color_spec(*lvl); + dst.set_color(&style)?; write!(dst, "{}", part.text)?; dst.reset()?; } @@ -2648,61 +2598,69 @@ fn emit_to_destination( Ok(()) } -pub enum Destination { - Terminal(StandardStream), - Buffered(BufferWriter), - // The bool denotes whether we should be emitting ansi color codes or not - Raw(Box<(dyn Write + Send)>, bool), -} +pub type Destination = Box<(dyn WriteColor + Send)>; -pub enum WritableDst<'a> { - Terminal(&'a mut StandardStream), - Buffered(&'a mut BufferWriter, Buffer), - Raw(&'a mut (dyn Write + Send)), - ColoredRaw(Ansi<&'a mut (dyn Write + Send)>), +struct Buffy { + buffer_writer: BufferWriter, + buffer: Buffer, } -impl Destination { - fn from_stderr(color: ColorConfig) -> Destination { - let choice = color.to_color_choice(); - // On Windows we'll be performing global synchronization on the entire - // system for emitting rustc errors, so there's no need to buffer - // anything. - // - // On non-Windows we rely on the atomicity of `write` to ensure errors - // don't get all jumbled up. - if cfg!(windows) { - Terminal(StandardStream::stderr(choice)) - } else { - Buffered(BufferWriter::stderr(choice)) - } +impl Write for Buffy { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.buffer.write(buf) } - fn writable(&mut self) -> WritableDst<'_> { - match *self { - Destination::Terminal(ref mut t) => WritableDst::Terminal(t), - Destination::Buffered(ref mut t) => { - let buf = t.buffer(); - WritableDst::Buffered(t, buf) - } - Destination::Raw(ref mut t, false) => WritableDst::Raw(t), - Destination::Raw(ref mut t, true) => WritableDst::ColoredRaw(Ansi::new(t)), + fn flush(&mut self) -> io::Result<()> { + self.buffer_writer.print(&self.buffer)?; + self.buffer.clear(); + Ok(()) + } +} + +impl Drop for Buffy { + fn drop(&mut self) { + if !self.buffer.is_empty() { + self.flush().unwrap(); + panic!("buffers need to be flushed in order to print their contents"); } } +} +impl WriteColor for Buffy { fn supports_color(&self) -> bool { - match *self { - Self::Terminal(ref stream) => stream.supports_color(), - Self::Buffered(ref buffer) => buffer.buffer().supports_color(), - Self::Raw(_, supports_color) => supports_color, - } + self.buffer.supports_color() + } + + fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { + self.buffer.set_color(spec) + } + + fn reset(&mut self) -> io::Result<()> { + self.buffer.reset() + } +} + +fn from_stderr(color: ColorConfig) -> Destination { + let choice = color.to_color_choice(); + // On Windows we'll be performing global synchronization on the entire + // system for emitting rustc errors, so there's no need to buffer + // anything. + // + // On non-Windows we rely on the atomicity of `write` to ensure errors + // don't get all jumbled up. + if cfg!(windows) { + Box::new(StandardStream::stderr(choice)) + } else { + let buffer_writer = BufferWriter::stderr(choice); + let buffer = buffer_writer.buffer(); + Box::new(Buffy { buffer_writer, buffer }) } } -impl<'a> WritableDst<'a> { - fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> { +impl Style { + fn color_spec(&self, lvl: Level) -> ColorSpec { let mut spec = ColorSpec::new(); - match style { + match self { Style::Addition => { spec.set_fg(Some(Color::Green)).set_intense(true); } @@ -2747,53 +2705,7 @@ impl<'a> WritableDst<'a> { spec.set_bold(true); } } - self.set_color(&spec) - } - - fn set_color(&mut self, color: &ColorSpec) -> io::Result<()> { - match *self { - WritableDst::Terminal(ref mut t) => t.set_color(color), - WritableDst::Buffered(_, ref mut t) => t.set_color(color), - WritableDst::ColoredRaw(ref mut t) => t.set_color(color), - WritableDst::Raw(_) => Ok(()), - } - } - - fn reset(&mut self) -> io::Result<()> { - match *self { - WritableDst::Terminal(ref mut t) => t.reset(), - WritableDst::Buffered(_, ref mut t) => t.reset(), - WritableDst::ColoredRaw(ref mut t) => t.reset(), - WritableDst::Raw(_) => Ok(()), - } - } -} - -impl<'a> Write for WritableDst<'a> { - fn write(&mut self, bytes: &[u8]) -> io::Result { - match *self { - WritableDst::Terminal(ref mut t) => t.write(bytes), - WritableDst::Buffered(_, ref mut buf) => buf.write(bytes), - WritableDst::Raw(ref mut w) => w.write(bytes), - WritableDst::ColoredRaw(ref mut t) => t.write(bytes), - } - } - - fn flush(&mut self) -> io::Result<()> { - match *self { - WritableDst::Terminal(ref mut t) => t.flush(), - WritableDst::Buffered(_, ref mut buf) => buf.flush(), - WritableDst::Raw(ref mut w) => w.flush(), - WritableDst::ColoredRaw(ref mut w) => w.flush(), - } - } -} - -impl<'a> Drop for WritableDst<'a> { - fn drop(&mut self) { - if let WritableDst::Buffered(ref mut dst, ref mut buf) = self { - drop(dst.print(buf)); - } + spec } } diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index f32d6b96b9b24..b8f58e3057c23 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -10,6 +10,7 @@ // FIXME: spec the JSON output properly. use rustc_span::source_map::{FilePathMapping, SourceMap}; +use termcolor::{ColorSpec, WriteColor}; use crate::emitter::{Emitter, HumanReadableErrorType}; use crate::registry::Registry; @@ -159,7 +160,7 @@ impl Emitter for JsonEmitter { } .and_then(|_| self.dst.flush()); if let Err(e) = result { - panic!("failed to print diagnostics: {:?}", e); + panic!("failed to print diagnostics: {e:?}"); } } @@ -172,7 +173,7 @@ impl Emitter for JsonEmitter { } .and_then(|_| self.dst.flush()); if let Err(e) = result { - panic!("failed to print notification: {:?}", e); + panic!("failed to print notification: {e:?}"); } } @@ -194,7 +195,7 @@ impl Emitter for JsonEmitter { } .and_then(|_| self.dst.flush()); if let Err(e) = result { - panic!("failed to print future breakage report: {:?}", e); + panic!("failed to print future breakage report: {e:?}"); } } @@ -208,7 +209,7 @@ impl Emitter for JsonEmitter { } .and_then(|_| self.dst.flush()); if let Err(e) = result { - panic!("failed to print unused externs: {:?}", e); + panic!("failed to print unused externs: {e:?}"); } } @@ -356,20 +357,29 @@ impl Diagnostic { self.0.lock().unwrap().flush() } } + impl WriteColor for BufWriter { + fn supports_color(&self) -> bool { + false + } + + fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> { + Ok(()) + } + + fn reset(&mut self) -> io::Result<()> { + Ok(()) + } + } let buf = BufWriter::default(); let output = buf.clone(); je.json_rendered - .new_emitter( - Box::new(buf), - Some(je.sm.clone()), - je.fluent_bundle.clone(), - je.fallback_bundle.clone(), - false, - je.diagnostic_width, - je.macro_backtrace, - je.track_diagnostics, - je.terminal_url, - ) + .new_emitter(Box::new(buf), je.fallback_bundle.clone()) + .sm(Some(je.sm.clone())) + .fluent_bundle(je.fluent_bundle.clone()) + .diagnostic_width(je.diagnostic_width) + .macro_backtrace(je.macro_backtrace) + .track_diagnostics(je.track_diagnostics) + .terminal_url(je.terminal_url) .ui_testing(je.ui_testing) .emit_diagnostic(diag); let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap(); diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs index 671dc449eaa73..1f9a2981e02be 100644 --- a/compiler/rustc_errors/src/json/tests.rs +++ b/compiler/rustc_errors/src/json/tests.rs @@ -64,7 +64,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) { ); let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1)); - let handler = Handler::with_emitter(true, None, Box::new(je)); + let handler = Handler::with_emitter(Box::new(je)); handler.span_err(span, "foo"); let bytes = output.lock().unwrap(); diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 3dec0d9299c4e..34518b53759d7 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -4,9 +4,8 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(array_windows)] -#![feature(drain_filter)] +#![feature(extract_if)] #![feature(if_let_guard)] -#![feature(adt_const_params)] #![feature(let_chains)] #![feature(never_type)] #![feature(result_option_inspect)] @@ -16,6 +15,7 @@ #![feature(box_patterns)] #![feature(error_reporter)] #![allow(incomplete_features)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_macros; @@ -23,6 +23,8 @@ extern crate rustc_macros; #[macro_use] extern crate tracing; +extern crate self as rustc_errors; + pub use emitter::ColorConfig; use rustc_lint_defs::LintExpectationId; @@ -48,9 +50,10 @@ use std::borrow::Cow; use std::error::Report; use std::fmt; use std::hash::Hash; +use std::io::Write; use std::num::NonZeroUsize; use std::panic; -use std::path::Path; +use std::path::{Path, PathBuf}; use termcolor::{Color, ColorSpec}; @@ -62,6 +65,7 @@ pub mod emitter; pub mod error; pub mod json; mod lock; +pub mod markdown; pub mod registry; mod snippet; mod styled_buffer; @@ -376,21 +380,23 @@ pub struct ExplicitBug; /// rather than a failed assertion, etc. pub struct DelayedBugPanic; +use crate::diagnostic_impls::{DelayedAtWithNewline, DelayedAtWithoutNewline}; pub use diagnostic::{ AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic, }; pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee, Noted}; pub use diagnostic_impls::{ - DiagnosticArgFromDisplay, DiagnosticSymbolList, LabelKind, SingleLabelManySpans, + DiagnosticArgFromDisplay, DiagnosticSymbolList, ExpectedLifetimeParameter, + IndicateAnonymousLifetime, InvalidFlushedDelayedDiagnosticLevel, LabelKind, + SingleLabelManySpans, }; -use std::backtrace::Backtrace; +use std::backtrace::{Backtrace, BacktraceStatus}; /// A handler deals with errors and other compiler output. /// Certain errors (fatal, bug, unimpl) may cause immediate exit, /// others log errors for later reporting. pub struct Handler { - flags: HandlerFlags, inner: Lock, } @@ -446,11 +452,11 @@ struct HandlerInner { /// have been converted. check_unstable_expect_diagnostics: bool, - /// Expected [`Diagnostic`][diagnostic::Diagnostic]s store a [`LintExpectationId`] as part of + /// Expected [`Diagnostic`][struct@diagnostic::Diagnostic]s store a [`LintExpectationId`] as part of /// the lint level. [`LintExpectationId`]s created early during the compilation /// (before `HirId`s have been defined) are not stable and can therefore not be /// stored on disk. This buffer stores these diagnostics until the ID has been - /// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`][diagnostic::Diagnostic]s are the + /// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`][struct@diagnostic::Diagnostic]s are the /// submitted for storage and added to the list of fulfilled expectations. unstable_expect_diagnostics: Vec, @@ -461,6 +467,10 @@ struct HandlerInner { /// /// [RFC-2383]: https://rust-lang.github.io/rfcs/2383-lint-reasons.html fulfilled_expectations: FxHashSet, + + /// The file where the ICE information is stored. This allows delayed_span_bug backtraces to be + /// stored along side the main panic backtrace. + ice_file: Option, } /// A key denoting where from a diagnostic was stashed. @@ -544,63 +554,36 @@ impl Drop for HandlerInner { impl Handler { pub fn with_tty_emitter( - color_config: ColorConfig, - can_emit_warnings: bool, - treat_err_as_bug: Option, sm: Option>, - fluent_bundle: Option>, fallback_bundle: LazyFallbackBundle, ) -> Self { - Self::with_tty_emitter_and_flags( - color_config, - sm, - fluent_bundle, - fallback_bundle, - HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() }, - ) + let emitter = Box::new(EmitterWriter::stderr(ColorConfig::Auto, fallback_bundle).sm(sm)); + Self::with_emitter(emitter) + } + pub fn disable_warnings(mut self) -> Self { + self.inner.get_mut().flags.can_emit_warnings = false; + self } - pub fn with_tty_emitter_and_flags( - color_config: ColorConfig, - sm: Option>, - fluent_bundle: Option>, - fallback_bundle: LazyFallbackBundle, - flags: HandlerFlags, - ) -> Self { - let emitter = Box::new(EmitterWriter::stderr( - color_config, - sm, - fluent_bundle, - fallback_bundle, - false, - false, - None, - flags.macro_backtrace, - flags.track_diagnostics, - TerminalUrl::No, - )); - Self::with_emitter_and_flags(emitter, flags) - } - - pub fn with_emitter( - can_emit_warnings: bool, - treat_err_as_bug: Option, - emitter: Box, - ) -> Self { - Handler::with_emitter_and_flags( - emitter, - HandlerFlags { can_emit_warnings, treat_err_as_bug, ..Default::default() }, - ) + pub fn treat_err_as_bug(mut self, treat_err_as_bug: NonZeroUsize) -> Self { + self.inner.get_mut().flags.treat_err_as_bug = Some(treat_err_as_bug); + self } - pub fn with_emitter_and_flags( - emitter: Box, - flags: HandlerFlags, - ) -> Self { + pub fn with_flags(mut self, flags: HandlerFlags) -> Self { + self.inner.get_mut().flags = flags; + self + } + + pub fn with_ice_file(mut self, ice_file: PathBuf) -> Self { + self.inner.get_mut().ice_file = Some(ice_file); + self + } + + pub fn with_emitter(emitter: Box) -> Self { Self { - flags, inner: Lock::new(HandlerInner { - flags, + flags: HandlerFlags { can_emit_warnings: true, ..Default::default() }, lint_err_count: 0, err_count: 0, warn_count: 0, @@ -618,6 +601,7 @@ impl Handler { check_unstable_expect_diagnostics: false, unstable_expect_diagnostics: Vec::new(), fulfilled_expectations: Default::default(), + ice_file: None, }), } } @@ -628,7 +612,7 @@ impl Handler { message: DiagnosticMessage, args: impl Iterator>, ) -> SubdiagnosticMessage { - SubdiagnosticMessage::Eager(self.eagerly_translate_to_string(message, args)) + SubdiagnosticMessage::Eager(Cow::from(self.eagerly_translate_to_string(message, args))) } /// Translate `message` eagerly with `args` to `String`. @@ -645,7 +629,7 @@ impl Handler { // This is here to not allow mutation of flags; // as of this writing it's only used in tests in librustc_middle. pub fn can_emit_warnings(&self) -> bool { - self.flags.can_emit_warnings + self.inner.lock().flags.can_emit_warnings } /// Resets the diagnostic error count as well as the cached emitted diagnostics. @@ -845,7 +829,7 @@ impl Handler { &self, msg: impl Into, ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { - DiagnosticBuilder::new_guaranteeing_error::<_, { Level::Error { lint: false } }>(self, msg) + DiagnosticBuilder::new_guaranteeing_error(self, msg) } /// This should only be used by `rustc_middle::lint::struct_lint_level`. Do not use it for hard errors. @@ -991,7 +975,7 @@ impl Handler { self.emit_diag_at_span(Diagnostic::new_with_code(Warning(None), Some(code), msg), span); } - pub fn span_bug(&self, span: impl Into, msg: impl Into) -> ! { + pub fn span_bug(&self, span: impl Into, msg: impl Into) -> ! { self.inner.borrow_mut().span_bug(span, msg) } @@ -1000,7 +984,7 @@ impl Handler { pub fn delay_span_bug( &self, span: impl Into, - msg: impl Into, + msg: impl Into, ) -> ErrorGuaranteed { self.inner.borrow_mut().delay_span_bug(span, msg) } @@ -1332,7 +1316,7 @@ impl HandlerInner { // once *any* errors were emitted (and truncate `delayed_span_bugs` // when an error is first emitted, also), but maybe there's a case // in which that's not sound? otherwise this is really inefficient. - let backtrace = std::backtrace::Backtrace::force_capture(); + let backtrace = std::backtrace::Backtrace::capture(); self.delayed_span_bugs .push(DelayedDiagnostic::with_backtrace(diagnostic.clone(), backtrace)); @@ -1400,7 +1384,7 @@ impl HandlerInner { !self.emitted_diagnostics.insert(diagnostic_hash) }; - diagnostic.children.drain_filter(already_emitted_sub).for_each(|_| {}); + diagnostic.children.extract_if(already_emitted_sub).for_each(|_| {}); self.emitter.emit_diagnostic(diagnostic); if diagnostic.is_error() { @@ -1450,14 +1434,14 @@ impl HandlerInner { self.emit_stashed_diagnostics(); let warnings = match self.deduplicated_warn_count { - 0 => String::new(), - 1 => "1 warning emitted".to_string(), - count => format!("{count} warnings emitted"), + 0 => Cow::from(""), + 1 => Cow::from("1 warning emitted"), + count => Cow::from(format!("{count} warnings emitted")), }; let errors = match self.deduplicated_err_count { - 0 => String::new(), - 1 => "aborting due to previous error".to_string(), - count => format!("aborting due to {count} previous errors"), + 0 => Cow::from(""), + 1 => Cow::from("aborting due to previous error"), + count => Cow::from(format!("aborting due to {count} previous errors")), }; if self.treat_err_as_bug() { return; @@ -1473,7 +1457,7 @@ impl HandlerInner { let _ = self.fatal(errors); } (_, _) => { - let _ = self.fatal(format!("{}; {}", &errors, &warnings)); + let _ = self.fatal(format!("{errors}; {warnings}")); } } @@ -1584,8 +1568,8 @@ impl HandlerInner { } #[track_caller] - fn span_bug(&mut self, sp: impl Into, msg: impl Into) -> ! { - self.emit_diag_at_span(Diagnostic::new(Bug, msg), sp); + fn span_bug(&mut self, sp: impl Into, msg: impl Into) -> ! { + self.emit_diag_at_span(Diagnostic::new(Bug, msg.into()), sp); panic::panic_any(ExplicitBug); } @@ -1598,7 +1582,7 @@ impl HandlerInner { fn delay_span_bug( &mut self, sp: impl Into, - msg: impl Into, + msg: impl Into, ) -> ErrorGuaranteed { // This is technically `self.treat_err_as_bug()` but `delay_span_bug` is called before // incrementing `err_count` by one, so we need to +1 the comparing. @@ -1607,9 +1591,9 @@ impl HandlerInner { self.err_count() + self.lint_err_count + self.delayed_bug_count() + 1 >= c.get() }) { // FIXME: don't abort here if report_delayed_bugs is off - self.span_bug(sp, msg); + self.span_bug(sp, msg.into()); } - let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg); + let mut diagnostic = Diagnostic::new(Level::DelayedBug, msg.into()); diagnostic.set_span(sp.into()); self.emit_diagnostic(&mut diagnostic).unwrap() } @@ -1621,7 +1605,7 @@ impl HandlerInner { if self.flags.report_delayed_bugs { self.emit_diagnostic(&mut diagnostic); } - let backtrace = std::backtrace::Backtrace::force_capture(); + let backtrace = std::backtrace::Backtrace::capture(); self.delayed_good_path_bugs.push(DelayedDiagnostic::with_backtrace(diagnostic, backtrace)); } @@ -1657,8 +1641,21 @@ impl HandlerInner { explanation: impl Into + Copy, ) { let mut no_bugs = true; + // If backtraces are enabled, also print the query stack + let backtrace = std::env::var_os("RUST_BACKTRACE").map_or(true, |x| &x != "0"); for bug in bugs { - let mut bug = bug.decorate(); + if let Some(file) = self.ice_file.as_ref() + && let Ok(mut out) = std::fs::File::options().create(true).append(true).open(file) + { + let _ = write!( + &mut out, + "delayed span bug: {}\n{}\n", + bug.inner.styled_message().iter().filter_map(|(msg, _)| msg.as_str()).collect::(), + &bug.note + ); + } + let mut bug = + if backtrace || self.ice_file.is_none() { bug.decorate() } else { bug.inner }; if no_bugs { // Put the overall explanation before the `DelayedBug`s, to @@ -1671,11 +1668,10 @@ impl HandlerInner { if bug.level != Level::DelayedBug { // NOTE(eddyb) not panicking here because we're already producing // an ICE, and the more information the merrier. - bug.note(format!( - "`flushed_delayed` got diagnostic with level {:?}, \ - instead of the expected `DelayedBug`", - bug.level, - )); + bug.subdiagnostic(InvalidFlushedDelayedDiagnosticLevel { + span: bug.span.primary_span().unwrap(), + level: bug.level, + }); } bug.level = Level::Bug; @@ -1714,13 +1710,11 @@ impl HandlerInner { (count, delayed_count, as_bug) => { if delayed_count > 0 { panic!( - "aborting after {} errors and {} delayed bugs due to `-Z treat-err-as-bug={}`", - count, delayed_count, as_bug, + "aborting after {count} errors and {delayed_count} delayed bugs due to `-Z treat-err-as-bug={as_bug}`", ) } else { panic!( - "aborting after {} errors due to `-Z treat-err-as-bug={}`", - count, as_bug, + "aborting after {count} errors due to `-Z treat-err-as-bug={as_bug}`", ) } } @@ -1740,7 +1734,27 @@ impl DelayedDiagnostic { } fn decorate(mut self) -> Diagnostic { - self.inner.note(format!("delayed at {}\n{}", self.inner.emitted_at, self.note)); + match self.note.status() { + BacktraceStatus::Captured => { + let inner = &self.inner; + self.inner.subdiagnostic(DelayedAtWithNewline { + span: inner.span.primary_span().unwrap(), + emitted_at: inner.emitted_at.clone(), + note: self.note, + }); + } + // Avoid the needless newline when no backtrace has been captured, + // the display impl should just be a single line. + _ => { + let inner = &self.inner; + self.inner.subdiagnostic(DelayedAtWithoutNewline { + span: inner.span.primary_span().unwrap(), + emitted_at: inner.emitted_at.clone(), + note: self.note, + }); + } + } + self.inner } } @@ -1829,20 +1843,36 @@ pub fn add_elided_lifetime_in_path_suggestion( incl_angl_brckt: bool, insertion_span: Span, ) { - diag.span_label(path_span, format!("expected lifetime parameter{}", pluralize!(n))); + diag.subdiagnostic(ExpectedLifetimeParameter { span: path_span, count: n }); if !source_map.is_span_accessible(insertion_span) { // Do not try to suggest anything if generated by a proc-macro. return; } let anon_lts = vec!["'_"; n].join(", "); let suggestion = - if incl_angl_brckt { format!("<{}>", anon_lts) } else { format!("{}, ", anon_lts) }; - diag.span_suggestion_verbose( - insertion_span.shrink_to_hi(), - format!("indicate the anonymous lifetime{}", pluralize!(n)), + if incl_angl_brckt { format!("<{anon_lts}>") } else { format!("{anon_lts}, ") }; + + diag.subdiagnostic(IndicateAnonymousLifetime { + span: insertion_span.shrink_to_hi(), + count: n, suggestion, - Applicability::MachineApplicable, - ); + }); +} + +pub fn report_ambiguity_error<'a, G: EmissionGuarantee>( + db: &mut DiagnosticBuilder<'a, G>, + ambiguity: rustc_lint_defs::AmbiguityErrorDiag, +) { + db.span_label(ambiguity.label_span, ambiguity.label_msg); + db.note(ambiguity.note_msg); + db.span_note(ambiguity.b1_span, ambiguity.b1_note_msg); + for help_msg in ambiguity.b1_help_msgs { + db.help(help_msg); + } + db.span_note(ambiguity.b2_span, ambiguity.b2_note_msg); + for help_msg in ambiguity.b2_help_msgs { + db.help(help_msg); + } } #[derive(Clone, Copy, PartialEq, Hash, Debug)] diff --git a/compiler/rustc_errors/src/markdown/mod.rs b/compiler/rustc_errors/src/markdown/mod.rs new file mode 100644 index 0000000000000..53b766dfccec1 --- /dev/null +++ b/compiler/rustc_errors/src/markdown/mod.rs @@ -0,0 +1,76 @@ +//! A simple markdown parser that can write formatted text to the terminal +//! +//! Entrypoint is `MdStream::parse_str(...)` +use std::io; + +use termcolor::{Buffer, BufferWriter, ColorChoice}; +mod parse; +mod term; + +/// An AST representation of a Markdown document +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MdStream<'a>(Vec>); + +impl<'a> MdStream<'a> { + /// Parse a markdown string to a tokenstream + #[must_use] + pub fn parse_str(s: &str) -> MdStream<'_> { + parse::entrypoint(s) + } + + /// Write formatted output to a termcolor buffer + pub fn write_termcolor_buf(&self, buf: &mut Buffer) -> io::Result<()> { + term::entrypoint(self, buf) + } +} + +/// Create a termcolor buffer with the `Always` color choice +pub fn create_stdout_bufwtr() -> BufferWriter { + BufferWriter::stdout(ColorChoice::Always) +} + +/// A single tokentree within a Markdown document +#[derive(Clone, Debug, PartialEq)] +pub enum MdTree<'a> { + /// Leaf types + Comment(&'a str), + CodeBlock { + txt: &'a str, + lang: Option<&'a str>, + }, + CodeInline(&'a str), + Strong(&'a str), + Emphasis(&'a str), + Strikethrough(&'a str), + PlainText(&'a str), + /// [Foo](www.foo.com) or simple anchor + Link { + disp: &'a str, + link: &'a str, + }, + /// `[Foo link][ref]` + RefLink { + disp: &'a str, + id: Option<&'a str>, + }, + /// [ref]: www.foo.com + LinkDef { + id: &'a str, + link: &'a str, + }, + /// Break bewtween two paragraphs (double `\n`), not directly parsed but + /// added later + ParagraphBreak, + /// Break bewtween two lines (single `\n`) + LineBreak, + HorizontalRule, + Heading(u8, MdStream<'a>), + OrderedListItem(u16, MdStream<'a>), + UnorderedListItem(MdStream<'a>), +} + +impl<'a> From>> for MdStream<'a> { + fn from(value: Vec>) -> Self { + Self(value) + } +} diff --git a/compiler/rustc_errors/src/markdown/parse.rs b/compiler/rustc_errors/src/markdown/parse.rs new file mode 100644 index 0000000000000..d3a08da628394 --- /dev/null +++ b/compiler/rustc_errors/src/markdown/parse.rs @@ -0,0 +1,588 @@ +use crate::markdown::{MdStream, MdTree}; +use std::{iter, mem, str}; + +/// Short aliases that we can use in match patterns. If an end pattern is not +/// included, this type may be variable +const ANC_E: &[u8] = b">"; +const ANC_S: &[u8] = b"<"; +const BRK: &[u8] = b"---"; +const CBK: &[u8] = b"```"; +const CIL: &[u8] = b"`"; +const CMT_E: &[u8] = b"-->"; +const CMT_S: &[u8] = b"rest"; + let (t, r) = parse_simple_pat(buf.as_bytes(), CMT_S, CMT_E, opt, MdTree::Comment).unwrap(); + assert_eq!(t, MdTree::Comment("foobar!")); + assert_eq!(r, b"rest"); + + let buf = r"rest"; + let (t, r) = parse_simple_pat(buf.as_bytes(), CMT_S, CMT_E, opt, MdTree::Comment).unwrap(); + assert_eq!(t, MdTree::Comment(r"foobar! \")); + assert_eq!(r, b"rest"); +} + +#[test] +fn test_parse_heading() { + let buf1 = "# Top level\nrest"; + let (t, r) = parse_heading(buf1.as_bytes()).unwrap(); + assert_eq!(t, MdTree::Heading(1, vec![MdTree::PlainText("Top level")].into())); + assert_eq!(r, b"\nrest"); + + let buf1 = "# Empty"; + let (t, r) = parse_heading(buf1.as_bytes()).unwrap(); + assert_eq!(t, MdTree::Heading(1, vec![MdTree::PlainText("Empty")].into())); + assert_eq!(r, b""); + + // Combo + let buf2 = "### Top `level` _woo_\nrest"; + let (t, r) = parse_heading(buf2.as_bytes()).unwrap(); + assert_eq!( + t, + MdTree::Heading( + 3, + vec![ + MdTree::PlainText("Top "), + MdTree::CodeInline("level"), + MdTree::PlainText(" "), + MdTree::Emphasis("woo"), + ] + .into() + ) + ); + assert_eq!(r, b"\nrest"); +} + +#[test] +fn test_parse_code_inline() { + let buf1 = "`abcd` rest"; + let (t, r) = parse_codeinline(buf1.as_bytes()).unwrap(); + assert_eq!(t, MdTree::CodeInline("abcd")); + assert_eq!(r, b" rest"); + + // extra backticks, newline + let buf2 = "```ab\ncd``` rest"; + let (t, r) = parse_codeinline(buf2.as_bytes()).unwrap(); + assert_eq!(t, MdTree::CodeInline("ab\ncd")); + assert_eq!(r, b" rest"); + + // test no escaping + let buf3 = r"`abcd\` rest"; + let (t, r) = parse_codeinline(buf3.as_bytes()).unwrap(); + assert_eq!(t, MdTree::CodeInline(r"abcd\")); + assert_eq!(r, b" rest"); +} + +#[test] +fn test_parse_code_block() { + let buf1 = "```rust\ncode\ncode\n```\nleftovers"; + let (t, r) = parse_codeblock(buf1.as_bytes()); + assert_eq!(t, MdTree::CodeBlock { txt: "code\ncode", lang: Some("rust") }); + assert_eq!(r, b"\nleftovers"); + + let buf2 = "`````\ncode\ncode````\n`````\nleftovers"; + let (t, r) = parse_codeblock(buf2.as_bytes()); + assert_eq!(t, MdTree::CodeBlock { txt: "code\ncode````", lang: None }); + assert_eq!(r, b"\nleftovers"); +} + +#[test] +fn test_parse_link() { + let simple = "[see here](docs.rs) other"; + let (t, r) = parse_any_link(simple.as_bytes(), false).unwrap(); + assert_eq!(t, MdTree::Link { disp: "see here", link: "docs.rs" }); + assert_eq!(r, b" other"); + + let simple_toplevel = "[see here](docs.rs) other"; + let (t, r) = parse_any_link(simple_toplevel.as_bytes(), true).unwrap(); + assert_eq!(t, MdTree::Link { disp: "see here", link: "docs.rs" }); + assert_eq!(r, b" other"); + + let reference = "[see here] other"; + let (t, r) = parse_any_link(reference.as_bytes(), true).unwrap(); + assert_eq!(t, MdTree::RefLink { disp: "see here", id: None }); + assert_eq!(r, b" other"); + + let reference_full = "[see here][docs-rs] other"; + let (t, r) = parse_any_link(reference_full.as_bytes(), false).unwrap(); + assert_eq!(t, MdTree::RefLink { disp: "see here", id: Some("docs-rs") }); + assert_eq!(r, b" other"); + + let reference_def = "[see here]: docs.rs\nother"; + let (t, r) = parse_any_link(reference_def.as_bytes(), true).unwrap(); + assert_eq!(t, MdTree::LinkDef { id: "see here", link: "docs.rs" }); + assert_eq!(r, b"\nother"); +} + +const IND1: &str = r"test standard + ind + ind2 +not ind"; +const IND2: &str = r"test end of stream + 1 + 2 +"; +const IND3: &str = r"test empty lines + 1 + 2 + +not ind"; + +#[test] +fn test_indented_section() { + let (t, r) = get_indented_section(IND1.as_bytes()); + assert_eq!(str::from_utf8(t).unwrap(), "test standard\n ind\n ind2"); + assert_eq!(str::from_utf8(r).unwrap(), "\nnot ind"); + + let (txt, rest) = get_indented_section(IND2.as_bytes()); + assert_eq!(str::from_utf8(txt).unwrap(), "test end of stream\n 1\n 2"); + assert_eq!(str::from_utf8(rest).unwrap(), "\n"); + + let (txt, rest) = get_indented_section(IND3.as_bytes()); + assert_eq!(str::from_utf8(txt).unwrap(), "test empty lines\n 1\n 2"); + assert_eq!(str::from_utf8(rest).unwrap(), "\n\nnot ind"); +} + +const HBT: &str = r"# Heading + +content"; + +#[test] +fn test_heading_breaks() { + let expected = vec![ + MdTree::Heading(1, vec![MdTree::PlainText("Heading")].into()), + MdTree::PlainText("content"), + ] + .into(); + let res = entrypoint(HBT); + assert_eq!(res, expected); +} + +const NL1: &str = r"start + +end"; +const NL2: &str = r"start + + +end"; +const NL3: &str = r"start + + + +end"; + +#[test] +fn test_newline_breaks() { + let expected = + vec![MdTree::PlainText("start"), MdTree::ParagraphBreak, MdTree::PlainText("end")].into(); + for (idx, check) in [NL1, NL2, NL3].iter().enumerate() { + let res = entrypoint(check); + assert_eq!(res, expected, "failed {idx}"); + } +} + +const WRAP: &str = "plain _italics +italics_"; + +#[test] +fn test_wrap_pattern() { + let expected = vec![ + MdTree::PlainText("plain "), + MdTree::Emphasis("italics"), + MdTree::Emphasis(" "), + MdTree::Emphasis("italics"), + ] + .into(); + let res = entrypoint(WRAP); + assert_eq!(res, expected); +} + +const WRAP_NOTXT: &str = r"_italics_ +**bold**"; + +#[test] +fn test_wrap_notxt() { + let expected = + vec![MdTree::Emphasis("italics"), MdTree::PlainText(" "), MdTree::Strong("bold")].into(); + let res = entrypoint(WRAP_NOTXT); + assert_eq!(res, expected); +} + +const MIXED_LIST: &str = r"start +- _italics item_ + +- **bold item** + second line [link1](foobar1) + third line [link2][link-foo] +- :crab: + extra indent +end +[link-foo]: foobar2 +"; + +#[test] +fn test_list() { + let expected = vec![ + MdTree::PlainText("start"), + MdTree::ParagraphBreak, + MdTree::UnorderedListItem(vec![MdTree::Emphasis("italics item")].into()), + MdTree::LineBreak, + MdTree::UnorderedListItem( + vec![ + MdTree::Strong("bold item"), + MdTree::PlainText(" second line "), + MdTree::Link { disp: "link1", link: "foobar1" }, + MdTree::PlainText(" third line "), + MdTree::Link { disp: "link2", link: "foobar2" }, + ] + .into(), + ), + MdTree::LineBreak, + MdTree::UnorderedListItem( + vec![MdTree::PlainText("🦀"), MdTree::PlainText(" extra indent")].into(), + ), + MdTree::ParagraphBreak, + MdTree::PlainText("end"), + ] + .into(); + let res = entrypoint(MIXED_LIST); + assert_eq!(res, expected); +} + +const SMOOSHED: &str = r#" +start +### heading +1. ordered item +```rust +println!("Hello, world!"); +``` +`inline` +``end`` +"#; + +#[test] +fn test_without_breaks() { + let expected = vec![ + MdTree::PlainText("start"), + MdTree::ParagraphBreak, + MdTree::Heading(3, vec![MdTree::PlainText("heading")].into()), + MdTree::OrderedListItem(1, vec![MdTree::PlainText("ordered item")].into()), + MdTree::ParagraphBreak, + MdTree::CodeBlock { txt: r#"println!("Hello, world!");"#, lang: Some("rust") }, + MdTree::ParagraphBreak, + MdTree::CodeInline("inline"), + MdTree::PlainText(" "), + MdTree::CodeInline("end"), + ] + .into(); + let res = entrypoint(SMOOSHED); + assert_eq!(res, expected); +} + +const CODE_STARTLINE: &str = r#" +start +`code` +middle +`more code` +end +"#; + +#[test] +fn test_code_at_start() { + let expected = vec![ + MdTree::PlainText("start"), + MdTree::PlainText(" "), + MdTree::CodeInline("code"), + MdTree::PlainText(" "), + MdTree::PlainText("middle"), + MdTree::PlainText(" "), + MdTree::CodeInline("more code"), + MdTree::PlainText(" "), + MdTree::PlainText("end"), + ] + .into(); + let res = entrypoint(CODE_STARTLINE); + assert_eq!(res, expected); +} diff --git a/compiler/rustc_errors/src/markdown/tests/term.rs b/compiler/rustc_errors/src/markdown/tests/term.rs new file mode 100644 index 0000000000000..6f68fb25a5893 --- /dev/null +++ b/compiler/rustc_errors/src/markdown/tests/term.rs @@ -0,0 +1,90 @@ +use std::io::BufWriter; +use std::path::PathBuf; +use termcolor::{BufferWriter, ColorChoice}; + +use super::*; +use crate::markdown::MdStream; + +const INPUT: &str = include_str!("input.md"); +const OUTPUT_PATH: &[&str] = &[env!("CARGO_MANIFEST_DIR"), "src","markdown","tests","output.stdout"]; + +const TEST_WIDTH: usize = 80; + +// We try to make some words long to create corner cases +const TXT: &str = r"Lorem ipsum dolor sit amet, consecteturadipiscingelit. +Fusce-id-urna-sollicitudin, pharetra nisl nec, lobortis tellus. In at +metus hendrerit, tincidunteratvel, ultrices turpis. Curabitur_risus_sapien, +porta-sed-nunc-sed, ultricesposuerelacus. Sed porttitor quis +dolor non venenatis. Aliquam ut. "; + +const WRAPPED: &str = r"Lorem ipsum dolor sit amet, consecteturadipiscingelit. Fusce-id-urna- +sollicitudin, pharetra nisl nec, lobortis tellus. In at metus hendrerit, +tincidunteratvel, ultrices turpis. Curabitur_risus_sapien, porta-sed-nunc-sed, +ultricesposuerelacus. Sed porttitor quis dolor non venenatis. Aliquam ut. Lorem + ipsum dolor sit amet, consecteturadipiscingelit. Fusce-id-urna- + sollicitudin, pharetra nisl nec, lobortis tellus. In at metus hendrerit, + tincidunteratvel, ultrices turpis. Curabitur_risus_sapien, porta-sed-nunc- + sed, ultricesposuerelacus. Sed porttitor quis dolor non venenatis. Aliquam + ut. Sample link lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, +consecteturadipiscingelit. Fusce-id-urna-sollicitudin, pharetra nisl nec, +lobortis tellus. In at metus hendrerit, tincidunteratvel, ultrices turpis. +Curabitur_risus_sapien, porta-sed-nunc-sed, ultricesposuerelacus. Sed porttitor +quis dolor non venenatis. Aliquam ut. "; + +#[test] +fn test_wrapping_write() { + WIDTH.with(|w| w.set(TEST_WIDTH)); + let mut buf = BufWriter::new(Vec::new()); + let txt = TXT.replace("-\n","-").replace("_\n","_").replace('\n', " ").replace(" ", ""); + write_wrapping(&mut buf, &txt, 0, None).unwrap(); + write_wrapping(&mut buf, &txt, 4, None).unwrap(); + write_wrapping( + &mut buf, + "Sample link lorem ipsum dolor sit amet. ", + 4, + Some("link-address-placeholder"), + ) + .unwrap(); + write_wrapping(&mut buf, &txt, 0, None).unwrap(); + let out = String::from_utf8(buf.into_inner().unwrap()).unwrap(); + let out = out + .replace("\x1b\\", "") + .replace('\x1b', "") + .replace("]8;;", "") + .replace("link-address-placeholder", ""); + + for line in out.lines() { + assert!(line.len() <= TEST_WIDTH, "line length\n'{line}'") + } + + assert_eq!(out, WRAPPED); +} + +#[test] +fn test_output() { + // Capture `--bless` when run via ./x + let bless = std::env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); + let ast = MdStream::parse_str(INPUT); + let bufwtr = BufferWriter::stderr(ColorChoice::Always); + let mut buffer = bufwtr.buffer(); + ast.write_termcolor_buf(&mut buffer).unwrap(); + + let mut blessed = PathBuf::new(); + blessed.extend(OUTPUT_PATH); + + if bless { + std::fs::write(&blessed, buffer.into_inner()).unwrap(); + eprintln!("blessed output at {}", blessed.display()); + } else { + let output = buffer.into_inner(); + if std::fs::read(blessed).unwrap() != output { + // hack: I don't know any way to write bytes to the captured stdout + // that cargo test uses + let mut out = std::io::stdout(); + out.write_all(b"\n\nMarkdown output did not match. Expected:\n").unwrap(); + out.write_all(&output).unwrap(); + out.write_all(b"\n\n").unwrap(); + panic!("markdown output mismatch"); + } + } +} diff --git a/compiler/rustc_expand/Cargo.toml b/compiler/rustc_expand/Cargo.toml index 2dae0e3f53c3c..02da5b5dc53f9 100644 --- a/compiler/rustc_expand/Cargo.toml +++ b/compiler/rustc_expand/Cargo.toml @@ -27,3 +27,4 @@ rustc_span = { path = "../rustc_span" } smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } thin-vec = "0.2.12" tracing = "0.1" +termcolor = "1.2" diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 70d2718b70639..6c7e68246ea5f 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -1,142 +1,142 @@ -expand_explain_doc_comment_outer = - outer doc comments expand to `#[doc = "..."]`, which is what this macro attempted to match - -expand_explain_doc_comment_inner = - inner doc comments expand to `#![doc = "..."]`, which is what this macro attempted to match - -expand_expr_repeat_no_syntax_vars = - attempted to repeat an expression containing no syntax variables matched as repeating at this depth - -expand_must_repeat_once = - this must repeat at least once - -expand_count_repetition_misplaced = - `count` can not be placed inside the inner-most repetition - -expand_meta_var_expr_unrecognized_var = - variable `{$key}` is not recognized in meta-variable expression - -expand_var_still_repeating = - variable '{$ident}' is still repeating at this depth - -expand_meta_var_dif_seq_matchers = {$msg} - -expand_macro_const_stability = - macros cannot have const stability attributes - .label = invalid const stability attribute - .label2 = const stability attribute affects this macro - -expand_macro_body_stability = - macros cannot have body stability attributes - .label = invalid body stability attribute - .label2 = body stability attribute affects this macro - -expand_resolve_relative_path = - cannot resolve relative path in non-file source `{$path}` +expand_arg_not_attributes = + second argument must be `attributes` expand_attr_no_arguments = attribute must have either one or two arguments -expand_not_a_meta_item = - not a meta item - -expand_only_one_word = - must only be one word - -expand_cannot_be_name_of_macro = - `{$trait_ident}` cannot be a name of {$macro_type} macro +expand_attribute_meta_item = + attribute must be a meta item, not a literal -expand_arg_not_attributes = - second argument must be `attributes` +expand_attribute_single_word = + attribute must only be a single word expand_attributes_wrong_form = attribute must be of form: `attributes(foo, bar)` -expand_attribute_meta_item = - attribute must be a meta item, not a literal +expand_cannot_be_name_of_macro = + `{$trait_ident}` cannot be a name of {$macro_type} macro -expand_attribute_single_word = - attribute must only be a single word +expand_count_repetition_misplaced = + `count` can not be placed inside the inner-most repetition -expand_helper_attribute_name_invalid = - `{$name}` cannot be a name of derive helper attribute +expand_duplicate_matcher_binding = duplicate matcher binding + .label = duplicate binding + .label2 = previous binding expand_expected_comma_in_list = expected token: `,` -expand_only_one_argument = - {$name} takes 1 argument +expand_explain_doc_comment_inner = + inner doc comments expand to `#![doc = "..."]`, which is what this macro attempted to match -expand_takes_no_arguments = - {$name} takes no arguments +expand_explain_doc_comment_outer = + outer doc comments expand to `#[doc = "..."]`, which is what this macro attempted to match + +expand_expr_repeat_no_syntax_vars = + attempted to repeat an expression containing no syntax variables matched as repeating at this depth expand_feature_included_in_edition = the feature `{$feature}` is included in the Rust {$edition} edition +expand_feature_not_allowed = + the feature `{$name}` is not in the list of allowed features + expand_feature_removed = feature has been removed .label = feature has been removed .reason = {$reason} -expand_feature_not_allowed = - the feature `{$name}` is not in the list of allowed features - -expand_recursion_limit_reached = - recursion limit reached while expanding `{$descr}` - .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) +expand_helper_attribute_name_invalid = + `{$name}` cannot be a name of derive helper attribute -expand_malformed_feature_attribute = - malformed `feature` attribute input - .expected = expected just one word +expand_incomplete_parse = + macro expansion ignores token `{$token}` and any following + .label = caused by the macro expansion here + .note = the usage of `{$macro_path}!` is likely invalid in {$kind_name} context + .suggestion_add_semi = you might be missing a semicolon here -expand_remove_expr_not_supported = - removing an expression is not supported in this position +expand_invalid_cfg_expected_syntax = expected syntax is +expand_invalid_cfg_multiple_predicates = multiple `cfg` predicates are specified expand_invalid_cfg_no_parens = `cfg` is not followed by parentheses expand_invalid_cfg_no_predicate = `cfg` predicate is not specified -expand_invalid_cfg_multiple_predicates = multiple `cfg` predicates are specified expand_invalid_cfg_predicate_literal = `cfg` predicate key cannot be a literal -expand_invalid_cfg_expected_syntax = expected syntax is +expand_macro_body_stability = + macros cannot have body stability attributes + .label = invalid body stability attribute + .label2 = body stability attribute affects this macro -expand_wrong_fragment_kind = - non-{$kind} macro in {$kind} position: {$name} +expand_macro_const_stability = + macros cannot have const stability attributes + .label = invalid const stability attribute + .label2 = const stability attribute affects this macro -expand_unsupported_key_value = - key-value macro attributes are not supported +expand_malformed_feature_attribute = + malformed `feature` attribute input + .expected = expected just one word -expand_incomplete_parse = - macro expansion ignores token `{$token}` and any following - .label = caused by the macro expansion here - .note = the usage of `{$macro_path}!` is likely invalid in {$kind_name} context - .suggestion_add_semi = you might be missing a semicolon here +expand_meta_var_dif_seq_matchers = {$msg} -expand_remove_node_not_supported = - removing {$descr} is not supported in this position +expand_meta_var_expr_unrecognized_var = + variable `{$key}` is not recognized in meta-variable expression expand_module_circular = circular modules: {$modules} -expand_module_in_block = - cannot declare a non-inline module inside a block unless it has a path attribute - .note = maybe `use` the module `{$name}` instead of redeclaring it - expand_module_file_not_found = file not found for module `{$name}` .help = to create the module `{$name}`, create file "{$default_path}" or "{$secondary_path}" +expand_module_in_block = + cannot declare a non-inline module inside a block unless it has a path attribute + .note = maybe `use` the module `{$name}` instead of redeclaring it + expand_module_multiple_candidates = file for module `{$name}` found at both "{$default_path}" and "{$secondary_path}" .help = delete or rename one of them to remove the ambiguity -expand_trace_macro = trace_macro +expand_must_repeat_once = + this must repeat at least once + +expand_not_a_meta_item = + not a meta item + +expand_only_one_argument = + {$name} takes 1 argument + +expand_only_one_word = + must only be one word + +expand_proc_macro_derive_tokens = + proc-macro derive produced unparsable tokens expand_proc_macro_panicked = proc macro panicked .help = message: {$message} -expand_proc_macro_derive_tokens = - proc-macro derive produced unparsable tokens +expand_recursion_limit_reached = + recursion limit reached while expanding `{$descr}` + .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) -expand_duplicate_matcher_binding = duplicate matcher binding - .label = duplicate binding - .label2 = previous binding +expand_remove_expr_not_supported = + removing an expression is not supported in this position + +expand_remove_node_not_supported = + removing {$descr} is not supported in this position + +expand_resolve_relative_path = + cannot resolve relative path in non-file source `{$path}` + +expand_takes_no_arguments = + {$name} takes no arguments + +expand_trace_macro = trace_macro + +expand_unsupported_key_value = + key-value macro attributes are not supported + +expand_var_still_repeating = + variable '{$ident}' is still repeating at this depth + +expand_wrong_fragment_kind = + non-{$kind} macro in {$kind} position: {$name} diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 4671adccc5416..c4d2a374f0c67 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -18,6 +18,7 @@ use rustc_errors::{ Applicability, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed, IntoDiagnostic, MultiSpan, PResult, }; +use rustc_feature::Features; use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT; use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, RegisteredTools}; use rustc_parse::{self, parser, MACRO_ARGUMENTS}; @@ -767,6 +768,7 @@ impl SyntaxExtension { /// and other properties converted from attributes. pub fn new( sess: &Session, + features: &Features, kind: SyntaxExtensionKind, span: Span, helper_attrs: Vec, @@ -816,7 +818,7 @@ impl SyntaxExtension { allow_internal_unstable: (!allow_internal_unstable.is_empty()) .then(|| allow_internal_unstable.into()), stability: stability.map(|(s, _)| s), - deprecation: attr::find_deprecation(&sess, attrs).map(|(d, _)| d), + deprecation: attr::find_deprecation(&sess, features, attrs).map(|(d, _)| d), helper_attrs, edition, builtin_name, @@ -947,6 +949,8 @@ pub trait ResolverExpand { /// HIR proc macros items back to their harness items. fn declare_proc_macro(&mut self, id: NodeId); + fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem); + /// Tools registered with `#![register_tool]` and used by tool attributes and lints. fn registered_tools(&self) -> &RegisteredTools; } @@ -955,6 +959,7 @@ pub trait LintStoreExpand { fn pre_expansion_lint( &self, sess: &Session, + features: &Features, registered_tools: &RegisteredTools, node_id: NodeId, attrs: &[Attribute], @@ -965,7 +970,7 @@ pub trait LintStoreExpand { type LintStoreExpandDyn<'a> = Option<&'a (dyn LintStoreExpand + 'a)>; -#[derive(Clone, Default)] +#[derive(Debug, Clone, Default)] pub struct ModuleData { /// Path to the module starting from the crate name, like `my_crate::foo::bar`. pub mod_path: Vec, @@ -1108,6 +1113,7 @@ impl<'a> ExtCtxt<'a> { } #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_err>( &self, sp: S, @@ -1116,6 +1122,7 @@ impl<'a> ExtCtxt<'a> { self.sess.parse_sess.span_diagnostic.struct_span_err(sp, msg) } + #[track_caller] pub fn create_err( &self, err: impl IntoDiagnostic<'a>, @@ -1123,6 +1130,7 @@ impl<'a> ExtCtxt<'a> { self.sess.create_err(err) } + #[track_caller] pub fn emit_err(&self, err: impl IntoDiagnostic<'a>) -> ErrorGuaranteed { self.sess.emit_err(err) } @@ -1133,14 +1141,16 @@ impl<'a> ExtCtxt<'a> { /// Compilation will be stopped in the near future (at the end of /// the macro expansion phase). #[rustc_lint_diagnostics] + #[track_caller] pub fn span_err>(&self, sp: S, msg: impl Into) { self.sess.parse_sess.span_diagnostic.span_err(sp, msg); } #[rustc_lint_diagnostics] + #[track_caller] pub fn span_warn>(&self, sp: S, msg: impl Into) { self.sess.parse_sess.span_diagnostic.span_warn(sp, msg); } - pub fn span_bug>(&self, sp: S, msg: impl Into) -> ! { + pub fn span_bug>(&self, sp: S, msg: impl Into) -> ! { self.sess.parse_sess.span_diagnostic.span_bug(sp, msg); } pub fn trace_macros_diag(&mut self) { @@ -1154,7 +1164,7 @@ impl<'a> ExtCtxt<'a> { // Fixme: does this result in errors? self.expansions.clear(); } - pub fn bug(&self, msg: &str) -> ! { + pub fn bug(&self, msg: &'static str) -> ! { self.sess.parse_sess.span_diagnostic.bug(msg); } pub fn trace_macros(&self) -> bool { @@ -1224,7 +1234,7 @@ pub fn resolve_path( pub fn expr_to_spanned_string<'a>( cx: &'a mut ExtCtxt<'_>, expr: P, - err_msg: &str, + err_msg: &'static str, ) -> Result<(Symbol, ast::StrStyle, Span), Option<(DiagnosticBuilder<'a, ErrorGuaranteed>, bool)>> { // Perform eager expansion on the expression. // We want to be able to handle e.g., `concat!("foo", "bar")`. @@ -1262,7 +1272,7 @@ pub fn expr_to_spanned_string<'a>( pub fn expr_to_string( cx: &mut ExtCtxt<'_>, expr: P, - err_msg: &str, + err_msg: &'static str, ) -> Option<(Symbol, ast::StrStyle)> { expr_to_spanned_string(cx, expr, err_msg) .map_err(|err| { @@ -1359,7 +1369,7 @@ pub fn parse_macro_name_and_helper_attrs( return None; } let Some(trait_attr) = list[0].meta_item() else { - diag.emit_err(errors::NotAMetaItem {span: list[0].span()}); + diag.emit_err(errors::NotAMetaItem { span: list[0].span() }); return None; }; let trait_ident = match trait_attr.ident() { diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs index 264f30fb10a12..7de46994434c5 100644 --- a/compiler/rustc_expand/src/build.rs +++ b/compiler/rustc_expand/src/build.rs @@ -643,7 +643,16 @@ impl<'a> ExtCtxt<'a> { span, name, AttrVec::new(), - ast::ItemKind::Const(ast::ConstItem { defaultness, ty, expr: Some(expr) }.into()), + ast::ItemKind::Const( + ast::ConstItem { + defaultness, + // FIXME(generic_const_items): Pass the generics as a parameter. + generics: ast::Generics::default(), + ty, + expr: Some(expr), + } + .into(), + ), ) } diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 4ff8e409d88e3..8658cea137a7d 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -197,9 +197,11 @@ pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec config_tokens: false, lint_node_id: ast::CRATE_NODE_ID, }; - let attrs: ast::AttrVec = - attrs.iter().flat_map(|attr| strip_unconfigured.process_cfg_attr(attr)).collect(); - if strip_unconfigured.in_cfg(&attrs) { attrs } else { ast::AttrVec::new() } + attrs + .iter() + .flat_map(|attr| strip_unconfigured.process_cfg_attr(attr)) + .take_while(|attr| !is_cfg(attr) || strip_unconfigured.cfg_true(attr).0) + .collect() } #[macro_export] @@ -311,9 +313,10 @@ impl<'a> StripUnconfigured<'a> { /// the attribute is incorrect. pub(crate) fn expand_cfg_attr(&self, attr: &Attribute, recursive: bool) -> Vec { let Some((cfg_predicate, expanded_attrs)) = - rustc_parse::parse_cfg_attr(attr, &self.sess.parse_sess) else { - return vec![]; - }; + rustc_parse::parse_cfg_attr(attr, &self.sess.parse_sess) + else { + return vec![]; + }; // Lint on zero attributes in source. if expanded_attrs.is_empty() { @@ -362,17 +365,21 @@ impl<'a> StripUnconfigured<'a> { // Use the `#` in `#[cfg_attr(pred, attr)]` as the `#` token // for `attr` when we expand it to `#[attr]` - let mut orig_trees = orig_tokens.into_trees(); - let TokenTree::Token(pound_token @ Token { kind: TokenKind::Pound, .. }, _) = orig_trees.next().unwrap() else { - panic!("Bad tokens for attribute {:?}", attr); + let mut orig_trees = orig_tokens.trees(); + let TokenTree::Token(pound_token @ Token { kind: TokenKind::Pound, .. }, _) = + orig_trees.next().unwrap().clone() + else { + panic!("Bad tokens for attribute {attr:?}"); }; let pound_span = pound_token.span; let mut trees = vec![AttrTokenTree::Token(pound_token, Spacing::Alone)]; if attr.style == AttrStyle::Inner { // For inner attributes, we do the same thing for the `!` in `#![some_attr]` - let TokenTree::Token(bang_token @ Token { kind: TokenKind::Not, .. }, _) = orig_trees.next().unwrap() else { - panic!("Bad tokens for attribute {:?}", attr); + let TokenTree::Token(bang_token @ Token { kind: TokenKind::Not, .. }, _) = + orig_trees.next().unwrap().clone() + else { + panic!("Bad tokens for attribute {attr:?}"); }; trees.push(AttrTokenTree::Token(bang_token, Spacing::Alone)); } @@ -383,7 +390,7 @@ impl<'a> StripUnconfigured<'a> { Delimiter::Bracket, item.tokens .as_ref() - .unwrap_or_else(|| panic!("Missing tokens for {:?}", item)) + .unwrap_or_else(|| panic!("Missing tokens for {item:?}")) .to_attr_token_stream(), ); trees.push(bracket_group); @@ -416,26 +423,34 @@ impl<'a> StripUnconfigured<'a> { /// Determines if a node with the given attributes should be included in this configuration. fn in_cfg(&self, attrs: &[Attribute]) -> bool { - attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr)) + attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr).0) } - pub(crate) fn cfg_true(&self, attr: &Attribute) -> bool { + pub(crate) fn cfg_true(&self, attr: &Attribute) -> (bool, Option) { let meta_item = match validate_attr::parse_meta(&self.sess.parse_sess, attr) { Ok(meta_item) => meta_item, Err(mut err) => { err.emit(); - return true; + return (true, None); } }; - parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| { - attr::cfg_matches(&meta_item, &self.sess.parse_sess, self.lint_node_id, self.features) - }) + ( + parse_cfg(&meta_item, &self.sess).map_or(true, |meta_item| { + attr::cfg_matches( + &meta_item, + &self.sess.parse_sess, + self.lint_node_id, + self.features, + ) + }), + Some(meta_item), + ) } /// If attributes are not allowed on expressions, emit an error for `attr` #[instrument(level = "trace", skip(self))] pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) { - if !self.features.map_or(true, |features| features.stmt_expr_attributes) { + if self.features.is_some_and(|features| !features.stmt_expr_attributes) { let mut err = feature_err( &self.sess.parse_sess, sym::stmt_expr_attributes, diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index ce0093c7d4c0e..34d16bf00cd67 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -651,7 +651,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { ExpandResult::Ready(match invoc.kind { InvocationKind::Bang { mac, .. } => match ext { SyntaxExtensionKind::Bang(expander) => { - let Ok(tok_result) = expander.expand(self.cx, span, mac.args.tokens.clone()) else { + let Ok(tok_result) = expander.expand(self.cx, span, mac.args.tokens.clone()) + else { return ExpandResult::Ready(fragment_kind.dummy(span)); }; self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span) @@ -704,7 +705,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.emit_err(UnsupportedKeyValue { span }); } let inner_tokens = attr_item.args.inner_tokens(); - let Ok(tok_result) = expander.expand(self.cx, span, inner_tokens, tokens) else { + let Ok(tok_result) = expander.expand(self.cx, span, inner_tokens, tokens) + else { return ExpandResult::Ready(fragment_kind.dummy(span)); }; self.parse_ast_fragment(tok_result, fragment_kind, &attr_item.path, span) @@ -794,14 +796,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> { | Annotatable::FieldDef(..) | Annotatable::Variant(..) => panic!("unexpected annotatable"), }; - if self.cx.ecfg.proc_macro_hygiene() { + if self.cx.ecfg.features.proc_macro_hygiene { return; } feature_err( &self.cx.sess.parse_sess, sym::proc_macro_hygiene, span, - format!("custom attributes cannot be applied to {}", kind), + format!("custom attributes cannot be applied to {kind}"), ) .emit(); } @@ -832,7 +834,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } } - if !self.cx.ecfg.proc_macro_hygiene() { + if !self.cx.ecfg.features.proc_macro_hygiene { annotatable .visit_with(&mut GateProcMacroInput { parse_sess: &self.cx.sess.parse_sess }); } @@ -1039,9 +1041,20 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { ) -> Result { Ok(noop_flat_map(node, collector)) } - fn expand_cfg_false(&mut self, collector: &mut InvocationCollector<'_, '_>, span: Span) { + fn expand_cfg_false( + &mut self, + collector: &mut InvocationCollector<'_, '_>, + _pos: usize, + span: Span, + ) { collector.cx.emit_err(RemoveNodeNotSupported { span, descr: Self::descr() }); } + + /// All of the names (items) declared by this node. + /// This is an approximation and should only be used for diagnostics. + fn declared_names(&self) -> Vec { + vec![] + } } impl InvocationCollectorNode for P { @@ -1076,9 +1089,7 @@ impl InvocationCollectorNode for P { // Work around borrow checker not seeing through `P`'s deref. let (ident, span, mut attrs) = (node.ident, node.span, mem::take(&mut node.attrs)); - let ItemKind::Mod(_, mod_kind) = &mut node.kind else { - unreachable!() - }; + let ItemKind::Mod(_, mod_kind) = &mut node.kind else { unreachable!() }; let ecx = &mut collector.cx; let (file_path, dir_path, dir_ownership) = match mod_kind { @@ -1111,6 +1122,7 @@ impl InvocationCollectorNode for P { if let Some(lint_store) = ecx.lint_store { lint_store.pre_expansion_lint( ecx.sess, + ecx.ecfg.features, ecx.resolver.registered_tools(), ecx.current_expansion.lint_node_id, &attrs, @@ -1148,6 +1160,27 @@ impl InvocationCollectorNode for P { collector.cx.current_expansion.module = orig_module; res } + fn declared_names(&self) -> Vec { + if let ItemKind::Use(ut) = &self.kind { + fn collect_use_tree_leaves(ut: &ast::UseTree, idents: &mut Vec) { + match &ut.kind { + ast::UseTreeKind::Glob => {} + ast::UseTreeKind::Simple(_) => idents.push(ut.ident()), + ast::UseTreeKind::Nested(nested) => { + for (ut, _) in nested { + collect_use_tree_leaves(&ut, idents); + } + } + } + } + + let mut idents = Vec::new(); + collect_use_tree_leaves(&ut, &mut idents); + return idents; + } + + vec![self.ident] + } } struct TraitItemTag; @@ -1382,8 +1415,15 @@ impl InvocationCollectorNode for ast::Crate { fn noop_visit(&mut self, visitor: &mut V) { noop_visit_crate(self, visitor) } - fn expand_cfg_false(&mut self, collector: &mut InvocationCollector<'_, '_>, _span: Span) { - self.attrs.clear(); + fn expand_cfg_false( + &mut self, + collector: &mut InvocationCollector<'_, '_>, + pos: usize, + _span: Span, + ) { + // Attributes above `cfg(FALSE)` are left in place, because we may want to configure + // some global crate properties even on fully unconfigured crates. + self.attrs.truncate(pos); // Standard prelude imports are left in the crate for backward compatibility. self.items.truncate(collector.cx.num_standard_library_imports); } @@ -1541,7 +1581,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { fn cfg(&self) -> StripUnconfigured<'_> { StripUnconfigured { sess: &self.cx.sess, - features: self.cx.ecfg.features, + features: Some(self.cx.ecfg.features), config_tokens: false, lint_node_id: self.cx.current_expansion.lint_node_id, } @@ -1637,7 +1677,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { // Detect use of feature-gated or invalid attributes on macro invocations // since they will not be detected after macro expansion. fn check_attributes(&self, attrs: &[ast::Attribute], call: &ast::MacCall) { - let features = self.cx.ecfg.features.unwrap(); + let features = self.cx.ecfg.features; let mut attrs = attrs.iter().peekable(); let mut span: Option = None; while let Some(attr) = attrs.next() { @@ -1668,7 +1708,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { &UNUSED_ATTRIBUTES, attr.span, self.cx.current_expansion.lint_node_id, - format!("unused attribute `{}`", attr_name), + format!("unused attribute `{attr_name}`"), BuiltinLintDiagnostics::UnusedBuiltinAttribute { attr_name, macro_name: pprust::path_to_string(&call.path), @@ -1685,8 +1725,8 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { node: &mut impl HasAttrs, attr: ast::Attribute, pos: usize, - ) -> bool { - let res = self.cfg().cfg_true(&attr); + ) -> (bool, Option) { + let (res, meta_item) = self.cfg().cfg_true(&attr); if res { // FIXME: `cfg(TRUE)` attributes do not currently remove themselves during expansion, // and some tools like rustdoc and clippy rely on that. Find a way to remove them @@ -1694,7 +1734,8 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.cx.expanded_inert_attrs.mark(&attr); node.visit_attrs(|attrs| attrs.insert(pos, attr)); } - res + + (res, meta_item) } fn expand_cfg_attr(&self, node: &mut impl HasAttrs, attr: &ast::Attribute, pos: usize) { @@ -1715,9 +1756,20 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { return match self.take_first_attr(&mut node) { Some((attr, pos, derives)) => match attr.name_or_empty() { sym::cfg => { - if self.expand_cfg_true(&mut node, attr, pos) { + let (res, meta_item) = self.expand_cfg_true(&mut node, attr, pos); + if res { continue; } + + if let Some(meta_item) = meta_item { + for name in node.declared_names() { + self.cx.resolver.append_stripped_cfg_item( + self.cx.current_expansion.lint_node_id, + name, + meta_item.clone(), + ) + } + } Default::default() } sym::cfg_attr => { @@ -1761,11 +1813,11 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { Some((attr, pos, derives)) => match attr.name_or_empty() { sym::cfg => { let span = attr.span; - if self.expand_cfg_true(node, attr, pos) { + if self.expand_cfg_true(node, attr, pos).0 { continue; } - node.expand_cfg_false(self, span); + node.expand_cfg_false(self, pos, span); continue; } sym::cfg_attr => { @@ -1925,7 +1977,7 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> { pub struct ExpansionConfig<'feat> { pub crate_name: String, - pub features: Option<&'feat Features>, + pub features: &'feat Features, pub recursion_limit: Limit, pub trace_mac: bool, /// If false, strip `#[test]` nodes @@ -1936,11 +1988,11 @@ pub struct ExpansionConfig<'feat> { pub proc_macro_backtrace: bool, } -impl<'feat> ExpansionConfig<'feat> { - pub fn default(crate_name: String) -> ExpansionConfig<'static> { +impl ExpansionConfig<'_> { + pub fn default(crate_name: String, features: &Features) -> ExpansionConfig<'_> { ExpansionConfig { crate_name, - features: None, + features, recursion_limit: Limit::new(1024), trace_mac: false, should_test: false, @@ -1948,8 +2000,4 @@ impl<'feat> ExpansionConfig<'feat> { proc_macro_backtrace: false, } } - - fn proc_macro_hygiene(&self) -> bool { - self.features.is_some_and(|features| features.proc_macro_hygiene) - } } diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 83a5043b0aa6f..c4a9b2ace9a02 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -11,6 +11,7 @@ #![feature(try_blocks)] #![recursion_limit = "256"] #![deny(rustc::untranslatable_diagnostic)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_macros; diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index cb8b4899e485c..e060375646c2d 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -42,7 +42,8 @@ pub(super) fn failed_to_match_macro<'cx>( return result; } - let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure else { + let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure + else { return DummyResult::any(sp); }; @@ -170,7 +171,7 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx, } Error(err_sp, msg) => { let span = err_sp.substitute_dummy(self.root_span); - self.cx.struct_span_err(span, msg.as_str()).emit(); + self.cx.struct_span_err(span, msg.clone()).emit(); self.result = Some(DummyResult::any(span)); } ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)), @@ -222,7 +223,7 @@ pub(super) fn emit_frag_parse_err( { let msg = &e.message[0]; e.message[0] = ( - DiagnosticMessage::Str(format!( + DiagnosticMessage::from(format!( "macro expansion ends with an incomplete expression: {}", message.replace(", found ``", ""), )), @@ -256,7 +257,7 @@ pub(super) fn emit_frag_parse_err( e.span_suggestion_verbose( site_span, "surround the macro invocation with `{}` to interpret the expansion as a statement", - format!("{{ {}; }}", snippet), + format!("{{ {snippet}; }}"), Applicability::MaybeIncorrect, ); } @@ -313,9 +314,9 @@ pub(super) fn annotate_doc_comment(err: &mut Diagnostic, sm: &SourceMap, span: S /// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For /// other tokens, this is "unexpected token...". -pub(super) fn parse_failure_msg(tok: &Token) -> String { +pub(super) fn parse_failure_msg(tok: &Token) -> Cow<'static, str> { match tok.kind { - token::Eof => "unexpected end of macro invocation".to_string(), - _ => format!("no rules expected the token `{}`", pprust::token_to_string(tok),), + token::Eof => Cow::from("unexpected end of macro invocation"), + _ => Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))), } } diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index 34f998274e995..95f5bb2d2e230 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -593,7 +593,7 @@ fn check_ops_is_prefix( return; } } - buffer_lint(sess, span.into(), node_id, format!("unknown macro variable `{}`", name)); + buffer_lint(sess, span.into(), node_id, format!("unknown macro variable `{name}`")); } /// Returns whether `binder_ops` is a prefix of `occurrence_ops`. @@ -626,7 +626,7 @@ fn ops_is_prefix( if i >= occurrence_ops.len() { let mut span = MultiSpan::from_span(span); span.push_span_label(binder.span, "expected repetition"); - let message = format!("variable '{}' is still repeating at this depth", name); + let message = format!("variable '{name}' is still repeating at this depth"); buffer_lint(sess, span, node_id, message); return; } diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 1c222fb4a898c..7e85beaadcbcc 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -81,7 +81,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lrc; use rustc_errors::ErrorGuaranteed; use rustc_lint_defs::pluralize; -use rustc_parse::parser::{NtOrTt, Parser}; +use rustc_parse::parser::{ParseNtResult, Parser}; use rustc_span::symbol::Ident; use rustc_span::symbol::MacroRulesNormalizedIdent; use rustc_span::Span; @@ -156,7 +156,7 @@ impl Display for MatcherLoc { MatcherLoc::MetaVarDecl { bind, kind, .. } => { write!(f, "meta-variable `${bind}")?; if let Some(kind) = kind { - write!(f, ":{}", kind)?; + write!(f, ":{kind}")?; } write!(f, "`")?; Ok(()) @@ -249,6 +249,7 @@ pub(super) fn compute_locs(matcher: &[TokenTree]) -> Vec { } /// A single matcher position, representing the state of matching. +#[derive(Debug)] struct MatcherPos { /// The index into `TtParser::locs`, which represents the "dot". idx: usize, @@ -691,8 +692,8 @@ impl TtParser { Ok(nt) => nt, }; let m = match nt { - NtOrTt::Nt(nt) => MatchedNonterminal(Lrc::new(nt)), - NtOrTt::Tt(tt) => MatchedTokenTree(tt), + ParseNtResult::Nt(nt) => MatchedNonterminal(Lrc::new(nt)), + ParseNtResult::Tt(tt) => MatchedTokenTree(tt), }; mp.push_match(next_metavar, seq_depth, m); mp.idx += 1; @@ -722,7 +723,7 @@ impl TtParser { .iter() .map(|mp| match &matcher[mp.idx] { MatcherLoc::MetaVarDecl { bind, kind: Some(kind), .. } => { - format!("{} ('{}')", kind, bind) + format!("{kind} ('{bind}')") } _ => unreachable!(), }) @@ -735,8 +736,8 @@ impl TtParser { "local ambiguity when calling macro `{}`: multiple parsing options: {}", self.macro_name, match self.next_mps.len() { - 0 => format!("built-in NTs {}.", nts), - n => format!("built-in NTs {} or {n} other option{s}.", nts, s = pluralize!(n)), + 0 => format!("built-in NTs {nts}."), + n => format!("built-in NTs {nts} or {n} other option{s}.", s = pluralize!(n)), } ), ) @@ -756,7 +757,7 @@ impl TtParser { match ret_val.entry(MacroRulesNormalizedIdent::new(bind)) { Vacant(spot) => spot.insert(res.next().unwrap()), Occupied(..) => { - return Error(span, format!("duplicated bind name: {}", bind)); + return Error(span, format!("duplicated bind name: {bind}")); } }; } else { diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index e4c65a2049bf6..a5959d68fbc8f 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -16,6 +16,7 @@ use rustc_ast_pretty::pprust; use rustc_attr::{self as attr, TransparencyError}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::{Applicability, ErrorGuaranteed}; +use rustc_feature::Features; use rustc_lint_defs::builtin::{ RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, }; @@ -223,8 +224,7 @@ fn expand_macro<'cx>( // Replace all the tokens for the corresponding positions in the macro, to maintain // proper positions in error reporting, while maintaining the macro_backtrace. if tts.len() == rhs.tts.len() { - tts = tts.map_enumerated(|i, tt| { - let mut tt = tt.clone(); + tts = tts.map_enumerated_owned(|i, mut tt| { let rhs_tt = &rhs.tts[i]; let ctxt = tt.span().ctxt(); match (&mut tt, rhs_tt) { @@ -250,7 +250,7 @@ fn expand_macro<'cx>( trace_macros_note(&mut cx.expansions, sp, msg); } - let p = Parser::new(sess, tts, false, None); + let p = Parser::new(sess, tts, None); if is_local { cx.resolver.record_macro_rule_usage(node_id, i); @@ -258,7 +258,7 @@ fn expand_macro<'cx>( // Let the context choose how to interpret the result. // Weird, but useful for X-macros. - return Box::new(ParserAnyMacro { + Box::new(ParserAnyMacro { parser: p, // Pass along the original expansion site and the name of the macro @@ -270,18 +270,17 @@ fn expand_macro<'cx>( is_trailing_mac: cx.current_expansion.is_trailing_mac, arm_span, is_local, - }); + }) } Err(CanRetry::No(_)) => { debug!("Will not retry matching as an error was emitted already"); - return DummyResult::any(sp); + DummyResult::any(sp) } Err(CanRetry::Yes) => { - // Retry and emit a better error below. + // Retry and emit a better error. + diagnostics::failed_to_match_macro(cx, sp, def_span, name, arg, lhses) } } - - diagnostics::failed_to_match_macro(cx, sp, def_span, name, arg, lhses) } pub(super) enum CanRetry { @@ -377,6 +376,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( /// Converts a macro item into a syntax extension. pub fn compile_declarative_macro( sess: &Session, + features: &Features, def: &ast::Item, edition: Edition, ) -> (SyntaxExtension, Vec<(usize, Span)>) { @@ -384,6 +384,7 @@ pub fn compile_declarative_macro( let mk_syn_ext = |expander| { SyntaxExtension::new( sess, + features, SyntaxExtensionKind::LegacyBang(expander), def.span, Vec::new(), @@ -448,7 +449,7 @@ pub fn compile_declarative_macro( let create_parser = || { let body = macro_def.body.tokens.clone(); - Parser::new(&sess.parse_sess, body, true, rustc_parse::MACRO_ARGUMENTS) + Parser::new(&sess.parse_sess, body, rustc_parse::MACRO_ARGUMENTS) }; let parser = create_parser(); @@ -458,8 +459,8 @@ pub fn compile_declarative_macro( match tt_parser.parse_tt(&mut Cow::Owned(parser), &argument_gram, &mut NoopTracker) { Success(m) => m, Failure(()) => { - // The fast `NoopTracker` doesn't have any info on failure, so we need to retry it with another one - // that gives us the information we need. + // The fast `NoopTracker` doesn't have any info on failure, so we need to retry it + // with another one that gives us the information we need. // For this we need to reclone the macro body as the previous parser consumed it. let retry_parser = create_parser(); @@ -501,11 +502,11 @@ pub fn compile_declarative_macro( .map(|m| { if let MatchedTokenTree(tt) = m { let tt = mbe::quoted::parse( - TokenStream::new(vec![tt.clone()]), + &TokenStream::new(vec![tt.clone()]), true, &sess.parse_sess, def.id, - sess.features_untracked(), + features, edition, ) .pop() @@ -525,17 +526,17 @@ pub fn compile_declarative_macro( .map(|m| { if let MatchedTokenTree(tt) = m { return mbe::quoted::parse( - TokenStream::new(vec![tt.clone()]), + &TokenStream::new(vec![tt.clone()]), false, &sess.parse_sess, def.id, - sess.features_untracked(), + features, edition, ) .pop() .unwrap(); } - sess.parse_sess.span_diagnostic.span_bug(def.span, "wrong-structured lhs") + sess.parse_sess.span_diagnostic.span_bug(def.span, "wrong-structured rhs") }) .collect::>(), _ => sess.parse_sess.span_diagnostic.span_bug(def.span, "wrong-structured rhs"), @@ -555,7 +556,7 @@ pub fn compile_declarative_macro( let (transparency, transparency_error) = attr::find_transparency(&def.attrs, macro_rules); match transparency_error { Some(TransparencyError::UnknownTransparency(value, span)) => { - diag.span_err(span, format!("unknown macro transparency: `{}`", value)); + diag.span_err(span, format!("unknown macro transparency: `{value}`")); } Some(TransparencyError::MultipleTransparencyAttrs(old_span, new_span)) => { diag.span_err(vec![old_span, new_span], "multiple macro transparency attributes"); @@ -628,6 +629,40 @@ fn check_lhs_nt_follows(sess: &ParseSess, def: &ast::Item, lhs: &mbe::TokenTree) // after parsing/expansion. we can report every error in every macro this way. } +fn is_empty_token_tree(sess: &ParseSess, seq: &mbe::SequenceRepetition) -> bool { + if seq.separator.is_some() { + false + } else { + let mut is_empty = true; + let mut iter = seq.tts.iter().peekable(); + while let Some(tt) = iter.next() { + match tt { + mbe::TokenTree::MetaVarDecl(_, _, Some(NonterminalKind::Vis)) => {} + mbe::TokenTree::Token(t @ Token { kind: DocComment(..), .. }) => { + let mut now = t; + while let Some(&mbe::TokenTree::Token( + next @ Token { kind: DocComment(..), .. }, + )) = iter.peek() + { + now = next; + iter.next(); + } + let span = t.span.to(now.span); + sess.span_diagnostic.span_note_without_error( + span, + "doc comments are ignored in matcher position", + ); + } + mbe::TokenTree::Sequence(_, sub_seq) + if (sub_seq.kleene.op == mbe::KleeneOp::ZeroOrMore + || sub_seq.kleene.op == mbe::KleeneOp::ZeroOrOne) => {} + _ => is_empty = false, + } + } + is_empty + } +} + /// Checks that the lhs contains no repetition which could match an empty token /// tree, because then the matcher would hang indefinitely. fn check_lhs_no_empty_seq(sess: &ParseSess, tts: &[mbe::TokenTree]) -> bool { @@ -644,16 +679,7 @@ fn check_lhs_no_empty_seq(sess: &ParseSess, tts: &[mbe::TokenTree]) -> bool { } } TokenTree::Sequence(span, seq) => { - if seq.separator.is_none() - && seq.tts.iter().all(|seq_tt| match seq_tt { - TokenTree::MetaVarDecl(_, _, Some(NonterminalKind::Vis)) => true, - TokenTree::Sequence(_, sub_seq) => { - sub_seq.kleene.op == mbe::KleeneOp::ZeroOrMore - || sub_seq.kleene.op == mbe::KleeneOp::ZeroOrOne - } - _ => false, - }) - { + if is_empty_token_tree(sess, seq) { let sp = span.entire(); sess.span_diagnostic.span_err(sp, "repetition matches empty token tree"); return false; @@ -1173,10 +1199,10 @@ fn check_matcher_core<'tt>( may_be = may_be ), ); - err.span_label(sp, format!("not allowed after `{}` fragments", kind)); + err.span_label(sp, format!("not allowed after `{kind}` fragments")); if kind == NonterminalKind::PatWithOr - && sess.edition.rust_2021() + && sess.edition.at_least_rust_2021() && next_token.is_token(&BinOp(token::BinOpToken::Or)) { let suggestion = quoted_tt_to_string(&TokenTree::MetaVarDecl( @@ -1197,8 +1223,7 @@ fn check_matcher_core<'tt>( &[] => {} &[t] => { err.note(format!( - "only {} is allowed after `{}` fragments", - t, kind, + "only {t} is allowed after `{kind}` fragments", )); } ts => { @@ -1303,7 +1328,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { _ => IsInFollow::No(TOKENS), } } - NonterminalKind::PatWithOr { .. } => { + NonterminalKind::PatWithOr => { const TOKENS: &[&str] = &["`=>`", "`,`", "`=`", "`if`", "`in`"]; match tok { TokenTree::Token(token) => match token.kind { @@ -1383,9 +1408,9 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow { fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String { match tt { mbe::TokenTree::Token(token) => pprust::token_to_string(&token).into(), - mbe::TokenTree::MetaVar(_, name) => format!("${}", name), - mbe::TokenTree::MetaVarDecl(_, name, Some(kind)) => format!("${}:{}", name, kind), - mbe::TokenTree::MetaVarDecl(_, name, None) => format!("${}:", name), + mbe::TokenTree::MetaVar(_, name) => format!("${name}"), + mbe::TokenTree::MetaVarDecl(_, name, Some(kind)) => format!("${name}:{kind}"), + mbe::TokenTree::MetaVarDecl(_, name, None) => format!("${name}:"), _ => panic!( "{}", "unexpected mbe::TokenTree::{Sequence or Delimited} \ @@ -1394,6 +1419,11 @@ fn quoted_tt_to_string(tt: &mbe::TokenTree) -> String { } } -pub(super) fn parser_from_cx(sess: &ParseSess, tts: TokenStream, recovery: Recovery) -> Parser<'_> { - Parser::new(sess, tts, true, rustc_parse::MACRO_ARGUMENTS).recovery(recovery) +pub(super) fn parser_from_cx( + sess: &ParseSess, + mut tts: TokenStream, + recovery: Recovery, +) -> Parser<'_> { + tts.desugar_doc_comments(); + Parser::new(sess, tts, rustc_parse::MACRO_ARGUMENTS).recovery(recovery) } diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index 6e919615019fb..7c37aadc67ae4 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -93,7 +93,17 @@ fn parse_count<'sess>( span: Span, ) -> PResult<'sess, MetaVarExpr> { let ident = parse_ident(iter, sess, span)?; - let depth = if try_eat_comma(iter) { Some(parse_depth(iter, sess, span)?) } else { None }; + let depth = if try_eat_comma(iter) { + if iter.look_ahead(0).is_none() { + return Err(sess.span_diagnostic.struct_span_err( + span, + "`count` followed by a comma must have an associated index indicating its depth", + )); + } + Some(parse_depth(iter, sess, span)?) + } else { + None + }; Ok(MetaVarExpr::Count(ident, depth)) } @@ -104,13 +114,10 @@ fn parse_depth<'sess>( span: Span, ) -> PResult<'sess, usize> { let Some(tt) = iter.next() else { return Ok(0) }; - let TokenTree::Token(token::Token { - kind: token::TokenKind::Literal(lit), .. - }, _) = tt else { - return Err(sess.span_diagnostic.struct_span_err( - span, - "meta-variable expression depth must be a literal" - )); + let TokenTree::Token(token::Token { kind: token::TokenKind::Literal(lit), .. }, _) = tt else { + return Err(sess + .span_diagnostic + .struct_span_err(span, "meta-variable expression depth must be a literal")); }; if let Ok(lit_kind) = LitKind::from_token_lit(*lit) && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index b2bdf9c7e6db6..6546199f5e6a7 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -9,7 +9,7 @@ use rustc_session::parse::{feature_err, ParseSess}; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::edition::Edition; -use rustc_span::{Span, SyntaxContext}; +use rustc_span::Span; const VALID_FRAGMENT_NAMES_MSG: &str = "valid fragment specifiers are \ `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, \ @@ -36,7 +36,7 @@ const VALID_FRAGMENT_NAMES_MSG: &str = "valid fragment specifiers are \ /// /// A collection of `self::TokenTree`. There may also be some errors emitted to `sess`. pub(super) fn parse( - input: tokenstream::TokenStream, + input: &tokenstream::TokenStream, parsing_patterns: bool, sess: &ParseSess, node_id: NodeId, @@ -48,7 +48,7 @@ pub(super) fn parse( // For each token tree in `input`, parse the token into a `self::TokenTree`, consuming // additional trees if need be. - let mut trees = input.into_trees(); + let mut trees = input.trees(); while let Some(tree) = trees.next() { // Given the parsed tree, if there is a metavar and we are expecting matchers, actually // parse out the matcher (i.e., in `$id:ident` this would parse the `:` and `ident`). @@ -56,7 +56,7 @@ pub(super) fn parse( match tree { TokenTree::MetaVar(start_sp, ident) if parsing_patterns => { let span = match trees.next() { - Some(tokenstream::TokenTree::Token(Token { kind: token::Colon, span }, _)) => { + Some(&tokenstream::TokenTree::Token(Token { kind: token::Colon, span }, _)) => { match trees.next() { Some(tokenstream::TokenTree::Token(token, _)) => match token.ident() { Some((frag, _)) => { @@ -72,7 +72,7 @@ pub(super) fn parse( // `SyntaxContext::root()` from a foreign crate will // have the edition of that crate (which we manually // retrieve via the `edition` parameter). - if span.ctxt() == SyntaxContext::root() { + if span.ctxt().is_root() { edition } else { span.edition() @@ -96,10 +96,10 @@ pub(super) fn parse( } _ => token.span, }, - tree => tree.as_ref().map_or(span, tokenstream::TokenTree::span), + tree => tree.map_or(span, tokenstream::TokenTree::span), } } - tree => tree.as_ref().map_or(start_sp, tokenstream::TokenTree::span), + tree => tree.map_or(start_sp, tokenstream::TokenTree::span), }; result.push(TokenTree::MetaVarDecl(span, ident, None)); @@ -134,9 +134,9 @@ fn maybe_emit_macro_metavar_expr_feature(features: &Features, sess: &ParseSess, /// - `parsing_patterns`: same as [parse]. /// - `sess`: the parsing session. Any errors will be emitted to this session. /// - `features`: language features so we can do feature gating. -fn parse_tree( - tree: tokenstream::TokenTree, - outer_trees: &mut impl Iterator, +fn parse_tree<'a>( + tree: &'a tokenstream::TokenTree, + outer_trees: &mut impl Iterator, parsing_patterns: bool, sess: &ParseSess, node_id: NodeId, @@ -146,13 +146,13 @@ fn parse_tree( // Depending on what `tree` is, we could be parsing different parts of a macro match tree { // `tree` is a `$` token. Look at the next token in `trees` - tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _) => { + &tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _) => { // FIXME: Handle `Invisible`-delimited groups in a more systematic way // during parsing. let mut next = outer_trees.next(); - let mut trees: Box>; + let mut trees: Box>; if let Some(tokenstream::TokenTree::Delimited(_, Delimiter::Invisible, tts)) = next { - trees = Box::new(tts.into_trees()); + trees = Box::new(tts.trees()); next = trees.next(); } else { trees = Box::new(outer_trees); @@ -160,7 +160,7 @@ fn parse_tree( match next { // `tree` is followed by a delimited set of token trees. - Some(tokenstream::TokenTree::Delimited(delim_span, delim, tts)) => { + Some(&tokenstream::TokenTree::Delimited(delim_span, delim, ref tts)) => { if parsing_patterns { if delim != Delimiter::Parenthesis { span_dollar_dollar_or_metavar_in_the_lhs_err( @@ -194,7 +194,7 @@ fn parse_tree( Delimiter::Parenthesis => {} _ => { let tok = pprust::token_kind_to_string(&token::OpenDelim(delim)); - let msg = format!("expected `(` or `{{`, found `{}`", tok); + let msg = format!("expected `(` or `{{`, found `{tok}`"); sess.span_diagnostic.span_err(delim_span.entire(), msg); } } @@ -228,7 +228,7 @@ fn parse_tree( } // `tree` is followed by another `$`. This is an escaped `$`. - Some(tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _)) => { + Some(&tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _)) => { if parsing_patterns { span_dollar_dollar_or_metavar_in_the_lhs_err( sess, @@ -256,11 +256,11 @@ fn parse_tree( } // `tree` is an arbitrary token. Keep it. - tokenstream::TokenTree::Token(token, _) => TokenTree::Token(token), + tokenstream::TokenTree::Token(token, _) => TokenTree::Token(token.clone()), // `tree` is the beginning of a delimited set of tokens (e.g., `(` or `{`). We need to // descend into the delimited set and further parse it. - tokenstream::TokenTree::Delimited(span, delim, tts) => TokenTree::Delimited( + &tokenstream::TokenTree::Delimited(span, delim, ref tts) => TokenTree::Delimited( span, Delimited { delim, @@ -286,16 +286,16 @@ fn kleene_op(token: &Token) -> Option { /// - Ok(Ok((op, span))) if the next token tree is a KleeneOp /// - Ok(Err(tok, span)) if the next token tree is a token but not a KleeneOp /// - Err(span) if the next token tree is not a token -fn parse_kleene_op( - input: &mut impl Iterator, +fn parse_kleene_op<'a>( + input: &mut impl Iterator, span: Span, ) -> Result, Span> { match input.next() { Some(tokenstream::TokenTree::Token(token, _)) => match kleene_op(&token) { Some(op) => Ok(Ok((op, token.span))), - None => Ok(Err(token)), + None => Ok(Err(token.clone())), }, - tree => Err(tree.as_ref().map_or(span, tokenstream::TokenTree::span)), + tree => Err(tree.map_or(span, tokenstream::TokenTree::span)), } } @@ -311,8 +311,8 @@ fn parse_kleene_op( /// session `sess`. If the next one (or possibly two) tokens in `input` correspond to a Kleene /// operator and separator, then a tuple with `(separator, KleeneOp)` is returned. Otherwise, an /// error with the appropriate span is emitted to `sess` and a dummy value is returned. -fn parse_sep_and_kleene_op( - input: &mut impl Iterator, +fn parse_sep_and_kleene_op<'a>( + input: &mut impl Iterator, span: Span, sess: &ParseSess, ) -> (Option, KleeneToken) { diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index d523d3eacbeb9..15e7ab3fe3ee6 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -182,9 +182,7 @@ pub(super) fn transcribe<'a>( LockstepIterSize::Constraint(len, _) => { // We do this to avoid an extra clone above. We know that this is a // sequence already. - let mbe::TokenTree::Sequence(sp, seq) = seq else { - unreachable!() - }; + let mbe::TokenTree::Sequence(sp, seq) = seq else { unreachable!() }; // Is the repetition empty? if len == 0 { @@ -222,16 +220,15 @@ pub(super) fn transcribe<'a>( MatchedTokenTree(tt) => { // `tt`s are emitted into the output stream directly as "raw tokens", // without wrapping them into groups. - let token = tt.clone(); - result.push(token); + result.push(tt.clone()); } MatchedNonterminal(nt) => { // Other variables are emitted into the output stream as groups with // `Delimiter::Invisible` to maintain parsing priorities. // `Interpolated` is currently used for such groups in rustc parser. marker.visit_span(&mut sp); - let token = TokenTree::token_alone(token::Interpolated(nt.clone()), sp); - result.push(token); + result + .push(TokenTree::token_alone(token::Interpolated(nt.clone()), sp)); } MatchedSeq(..) => { // We were unable to descend far enough. This is an error. @@ -399,7 +396,9 @@ fn lockstep_iter_size( } TokenTree::MetaVarExpr(_, expr) => { let default_rslt = LockstepIterSize::Unconstrained; - let Some(ident) = expr.ident() else { return default_rslt; }; + let Some(ident) = expr.ident() else { + return default_rslt; + }; let name = MacroRulesNormalizedIdent::new(ident); match lookup_cur_matched(name, interpolations, repeats) { Some(MatchedSeq(ads)) => { diff --git a/compiler/rustc_expand/src/parse/tests.rs b/compiler/rustc_expand/src/parse/tests.rs index 8b37728b60fea..bdc20882a9d52 100644 --- a/compiler/rustc_expand/src/parse/tests.rs +++ b/compiler/rustc_expand/src/parse/tests.rs @@ -1,4 +1,6 @@ -use crate::tests::{matches_codepattern, string_to_stream, with_error_checking_parse}; +use crate::tests::{ + matches_codepattern, string_to_stream, with_error_checking_parse, with_expected_parse_error, +}; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, Token}; @@ -51,11 +53,15 @@ fn string_to_item(source_str: String) -> Option> { with_error_checking_parse(source_str, &sess(), |p| p.parse_item(ForceCollect::No)) } -#[should_panic] #[test] fn bad_path_expr_1() { + // This should trigger error: expected identifier, found keyword `return` create_default_session_globals_then(|| { - string_to_expr("::abc::def::return".to_string()); + with_expected_parse_error( + "::abc::def::return", + "expected identifier, found keyword `return`", + |p| p.parse_expr(), + ); }) } @@ -63,9 +69,8 @@ fn bad_path_expr_1() { #[test] fn string_to_tts_macro() { create_default_session_globals_then(|| { - let tts: Vec<_> = - string_to_stream("macro_rules! zip (($a)=>($a))".to_string()).into_trees().collect(); - let tts: &[TokenTree] = &tts[..]; + let stream = string_to_stream("macro_rules! zip (($a)=>($a))".to_string()); + let tts = &stream.trees().collect::>()[..]; match tts { [ @@ -294,9 +299,7 @@ fn ttdelim_span() { .unwrap(); let ast::ExprKind::MacCall(mac) = &expr.kind else { panic!("not a macro") }; - let tts: Vec<_> = mac.args.tokens.clone().into_trees().collect(); - - let span = tts.iter().rev().next().unwrap().span(); + let span = mac.args.tokens.trees().last().unwrap().span(); match sess.source_map().span_to_snippet(span) { Ok(s) => assert_eq!(&s[..], "{ body }"), diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs index e9af688ee2b61..82cac229284d8 100644 --- a/compiler/rustc_expand/src/placeholders.rs +++ b/compiler/rustc_expand/src/placeholders.rs @@ -2,6 +2,7 @@ use crate::expand::{AstFragment, AstFragmentKind}; use rustc_ast as ast; use rustc_ast::mut_visit::*; use rustc_ast::ptr::P; +use rustc_ast::token::Delimiter; use rustc_data_structures::fx::FxHashMap; use rustc_span::source_map::DUMMY_SP; use rustc_span::symbol::Ident; @@ -18,7 +19,7 @@ pub fn placeholder( path: ast::Path { span: DUMMY_SP, segments: ThinVec::new(), tokens: None }, args: P(ast::DelimArgs { dspan: ast::tokenstream::DelimSpan::dummy(), - delim: ast::MacDelimiter::Parenthesis, + delim: Delimiter::Parenthesis, tokens: ast::tokenstream::TokenStream::new(Vec::new()), }), }) diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 41b24407fa07c..c617cd76e3cb0 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -95,7 +95,7 @@ impl base::AttrProcMacro for AttrProcMacro { |e| { let mut err = ecx.struct_span_err(span, "custom attribute panicked"); if let Some(s) = e.as_str() { - err.help(format!("message: {}", s)); + err.help(format!("message: {s}")); } err.emit() }, @@ -148,7 +148,7 @@ impl MultiItemModifier for DeriveProcMacro { Err(e) => { let mut err = ecx.struct_span_err(span, "proc-macro derive panicked"); if let Some(s) = e.as_str() { - err.help(format!("message: {}", s)); + err.help(format!("message: {s}")); } err.emit(); return ExpandResult::Ready(vec![]); diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs index 891e84a2f3071..2dc9b51a37ea0 100644 --- a/compiler/rustc_expand/src/proc_macro_server.rs +++ b/compiler/rustc_expand/src/proc_macro_server.rs @@ -2,7 +2,7 @@ use crate::base::ExtCtxt; use pm::bridge::{ server, DelimSpan, Diagnostic, ExpnGlobals, Group, Ident, LitKind, Literal, Punct, TokenTree, }; -use pm::{Delimiter, Level, LineColumn}; +use pm::{Delimiter, Level}; use rustc_ast as ast; use rustc_ast::token; use rustc_ast::tokenstream::{self, Spacing::*, TokenStream}; @@ -94,10 +94,10 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec { let delimiter = pm::Delimiter::from_internal(delim); trees.push(TokenTree::Group(Group { @@ -622,7 +622,7 @@ impl server::SourceFile for Rustc<'_, '_> { impl server::Span for Rustc<'_, '_> { fn debug(&mut self, span: Self::Span) -> String { if self.ecx.ecfg.span_debug { - format!("{:?}", span) + format!("{span:?}") } else { format!("{:?} bytes({}..{})", span.ctxt(), span.lo().0, span.hi().0) } @@ -648,23 +648,22 @@ impl server::Span for Rustc<'_, '_> { Range { start: relative_start_pos.0 as usize, end: relative_end_pos.0 as usize } } - - fn start(&mut self, span: Self::Span) -> LineColumn { - let loc = self.sess().source_map().lookup_char_pos(span.lo()); - LineColumn { line: loc.line, column: loc.col.to_usize() } + fn start(&mut self, span: Self::Span) -> Self::Span { + span.shrink_to_lo() } - fn end(&mut self, span: Self::Span) -> LineColumn { - let loc = self.sess().source_map().lookup_char_pos(span.hi()); - LineColumn { line: loc.line, column: loc.col.to_usize() } + fn end(&mut self, span: Self::Span) -> Self::Span { + span.shrink_to_hi() } - fn before(&mut self, span: Self::Span) -> Self::Span { - span.shrink_to_lo() + fn line(&mut self, span: Self::Span) -> usize { + let loc = self.sess().source_map().lookup_char_pos(span.lo()); + loc.line } - fn after(&mut self, span: Self::Span) -> Self::Span { - span.shrink_to_hi() + fn column(&mut self, span: Self::Span) -> usize { + let loc = self.sess().source_map().lookup_char_pos(span.lo()); + loc.col.to_usize() + 1 } fn join(&mut self, first: Self::Span, second: Self::Span) -> Option { diff --git a/compiler/rustc_expand/src/tests.rs b/compiler/rustc_expand/src/tests.rs index 8a5e09475ff13..8e3219c138c6e 100644 --- a/compiler/rustc_expand/src/tests.rs +++ b/compiler/rustc_expand/src/tests.rs @@ -8,7 +8,8 @@ use rustc_span::{BytePos, Span}; use rustc_data_structures::sync::Lrc; use rustc_errors::emitter::EmitterWriter; -use rustc_errors::{Handler, MultiSpan, PResult, TerminalUrl}; +use rustc_errors::{Handler, MultiSpan, PResult}; +use termcolor::WriteColor; use std::io; use std::io::prelude::*; @@ -22,6 +23,23 @@ fn string_to_parser(ps: &ParseSess, source_str: String) -> Parser<'_> { new_parser_from_source_str(ps, PathBuf::from("bogofile").into(), source_str) } +fn create_test_handler() -> (Handler, Lrc, Arc>>) { + let output = Arc::new(Mutex::new(Vec::new())); + let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let fallback_bundle = rustc_errors::fallback_fluent_bundle( + vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE], + false, + ); + let emitter = EmitterWriter::new(Box::new(Shared { data: output.clone() }), fallback_bundle) + .sm(Some(source_map.clone())) + .diagnostic_width(Some(140)); + let handler = Handler::with_emitter(Box::new(emitter)); + (handler, source_map, output) +} + +/// Returns the result of parsing the given string via the given callback. +/// +/// If there are any errors, this will panic. pub(crate) fn with_error_checking_parse<'a, T, F>(s: String, ps: &'a ParseSess, f: F) -> T where F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>, @@ -32,6 +50,26 @@ where x } +/// Verifies that parsing the given string using the given callback will +/// generate an error that contains the given text. +pub(crate) fn with_expected_parse_error(source_str: &str, expected_output: &str, f: F) +where + F: for<'a> FnOnce(&mut Parser<'a>) -> PResult<'a, T>, +{ + let (handler, source_map, output) = create_test_handler(); + let ps = ParseSess::with_span_handler(handler, source_map); + let mut p = string_to_parser(&ps, source_str.to_string()); + let result = f(&mut p); + assert!(result.is_ok()); + + let bytes = output.lock().unwrap(); + let actual_output = str::from_utf8(&bytes).unwrap(); + println!("expected output:\n------\n{}------", expected_output); + println!("actual output:\n------\n{}------", actual_output); + + assert!(actual_output.contains(expected_output)) +} + /// Maps a string to tts, using a made-up filename. pub(crate) fn string_to_stream(source_str: String) -> TokenStream { let ps = ParseSess::new( @@ -118,6 +156,20 @@ pub(crate) struct Shared { pub data: Arc>, } +impl WriteColor for Shared { + fn supports_color(&self) -> bool { + false + } + + fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> { + Ok(()) + } + + fn reset(&mut self) -> io::Result<()> { + Ok(()) + } +} + impl Write for Shared { fn write(&mut self, buf: &[u8]) -> io::Result { self.data.lock().unwrap().write(buf) @@ -130,13 +182,7 @@ impl Write for Shared { fn test_harness(file_text: &str, span_labels: Vec, expected_output: &str) { create_default_session_if_not_set_then(|_| { - let output = Arc::new(Mutex::new(Vec::new())); - - let fallback_bundle = rustc_errors::fallback_fluent_bundle( - vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE], - false, - ); - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let (handler, source_map, output) = create_test_handler(); source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); @@ -148,20 +194,6 @@ fn test_harness(file_text: &str, span_labels: Vec, expected_output: & println!("text: {:?}", source_map.span_to_snippet(span)); } - let emitter = EmitterWriter::new( - Box::new(Shared { data: output.clone() }), - Some(source_map.clone()), - None, - fallback_bundle, - false, - false, - false, - None, - false, - false, - TerminalUrl::No, - ); - let handler = Handler::with_emitter(true, None, Box::new(emitter)); #[allow(rustc::untranslatable_diagnostic)] handler.span_err(msp, "foo"); diff --git a/compiler/rustc_feature/src/accepted.rs b/compiler/rustc_feature/src/accepted.rs index 5b2e4d15dfebe..afcf30d0b293e 100644 --- a/compiler/rustc_feature/src/accepted.rs +++ b/compiler/rustc_feature/src/accepted.rs @@ -53,6 +53,8 @@ declare_features! ( /// Allows the sysV64 ABI to be specified on all platforms /// instead of just the platforms on which it is the C ABI. (accepted, abi_sysv64, "1.24.0", Some(36167), None), + /// Allows using the `thiscall` ABI. + (accepted, abi_thiscall, "1.73.0", None, None), /// Allows using ADX intrinsics from `core::arch::{x86, x86_64}`. (accepted, adx_target_feature, "1.61.0", Some(44839), None), /// Allows explicit discriminants on non-unit enum variants. @@ -131,7 +133,7 @@ declare_features! ( /// Allows `crate` in paths. (accepted, crate_in_paths, "1.30.0", Some(45477), None), /// Allows using `#[debugger_visualizer]` attribute. - (accepted, debugger_visualizer, "CURRENT_RUSTC_VERSION", Some(95939), None), + (accepted, debugger_visualizer, "1.71.0", Some(95939), None), /// Allows rustc to inject a default alloc_error_handler (accepted, default_alloc_error_handler, "1.68.0", Some(66741), None), /// Allows using assigning a default type to type parameters in algebraic data type definitions. @@ -281,7 +283,7 @@ declare_features! ( /// Allows use of the postfix `?` operator in expressions. (accepted, question_mark, "1.13.0", Some(31436), None), /// Allows the use of raw-dylibs (RFC 2627). - (accepted, raw_dylib, "CURRENT_RUSTC_VERSION", Some(58713), None), + (accepted, raw_dylib, "1.71.0", Some(58713), None), /// Allows keywords to be escaped for use as identifiers. (accepted, raw_identifiers, "1.30.0", Some(48589), None), /// Allows relaxing the coherence rules such that diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 57e55752027c7..f5bc140c049f2 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -16,12 +16,22 @@ macro_rules! set { }}; } +#[derive(PartialEq)] +enum FeatureStatus { + Default, + Incomplete, + Internal, +} + macro_rules! declare_features { - (__status_to_bool active) => { - false + (__status_to_enum active) => { + FeatureStatus::Default }; - (__status_to_bool incomplete) => { - true + (__status_to_enum incomplete) => { + FeatureStatus::Incomplete + }; + (__status_to_enum internal) => { + FeatureStatus::Internal }; ($( $(#[doc = $doc:tt])* ($status:ident, $feature:ident, $ver:expr, $issue:expr, $edition:expr), @@ -83,7 +93,7 @@ macro_rules! declare_features { pub fn incomplete(&self, feature: Symbol) -> bool { match feature { $( - sym::$feature => declare_features!(__status_to_bool $status), + sym::$feature => declare_features!(__status_to_enum $status) == FeatureStatus::Incomplete, )* // accepted and removed features aren't in this file but are never incomplete _ if self.declared_lang_features.iter().any(|f| f.0 == feature) => false, @@ -91,6 +101,22 @@ macro_rules! declare_features { _ => panic!("`{}` was not listed in `declare_features`", feature), } } + + /// Some features are internal to the compiler and standard library and should not + /// be used in normal projects. We warn the user about these + /// to alert them. + pub fn internal(&self, feature: Symbol) -> bool { + match feature { + $( + sym::$feature => declare_features!(__status_to_enum $status) == FeatureStatus::Internal, + )* + // accepted and removed features aren't in this file but are never internal + // (a removed feature might have been internal, but it doesn't matter anymore) + _ if self.declared_lang_features.iter().any(|f| f.0 == feature) => false, + _ if self.declared_lib_features.iter().any(|f| f.0 == feature) => false, + _ => panic!("`{}` was not listed in `declare_features`", feature), + } + } } }; } @@ -130,59 +156,57 @@ declare_features! ( // ------------------------------------------------------------------------- // no-tracking-issue-start - /// Allows using the `thiscall` ABI. - (active, abi_thiscall, "1.19.0", None, None), /// Allows using the `unadjusted` ABI; perma-unstable. (active, abi_unadjusted, "1.16.0", None, None), /// Allows using the `vectorcall` ABI. (active, abi_vectorcall, "1.7.0", None, None), /// Allows using `#![needs_allocator]`, an implementation detail of `#[global_allocator]`. - (active, allocator_internals, "1.20.0", None, None), + (internal, allocator_internals, "1.20.0", None, None), /// Allows using `#[allow_internal_unsafe]`. This is an /// attribute on `macro_rules!` and can't use the attribute handling /// below (it has to be checked before expansion possibly makes /// macros disappear). - (active, allow_internal_unsafe, "1.0.0", None, None), + (internal, allow_internal_unsafe, "1.0.0", None, None), /// Allows using `#[allow_internal_unstable]`. This is an /// attribute on `macro_rules!` and can't use the attribute handling /// below (it has to be checked before expansion possibly makes /// macros disappear). - (active, allow_internal_unstable, "1.0.0", None, None), + (internal, allow_internal_unstable, "1.0.0", None, None), /// Allows using anonymous lifetimes in argument-position impl-trait. (active, anonymous_lifetime_in_impl_trait, "1.63.0", None, None), /// Allows identifying the `compiler_builtins` crate. - (active, compiler_builtins, "1.13.0", None, None), + (internal, compiler_builtins, "1.13.0", None, None), /// Allows writing custom MIR - (active, custom_mir, "1.65.0", None, None), + (internal, custom_mir, "1.65.0", None, None), /// Outputs useful `assert!` messages (active, generic_assert, "1.63.0", None, None), /// Allows using the `rust-intrinsic`'s "ABI". - (active, intrinsics, "1.0.0", None, None), + (internal, intrinsics, "1.0.0", None, None), /// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic. - (active, lang_items, "1.0.0", None, None), + (internal, lang_items, "1.0.0", None, None), /// Allows `#[link(..., cfg(..))]`; perma-unstable per #37406 (active, link_cfg, "1.14.0", None, None), /// Allows the `multiple_supertrait_upcastable` lint. (active, multiple_supertrait_upcastable, "1.69.0", None, None), /// Allow negative trait bounds. This is an internal-only feature for testing the trait solver! - (incomplete, negative_bounds, "CURRENT_RUSTC_VERSION", None, None), + (incomplete, negative_bounds, "1.71.0", None, None), /// Allows using `#[omit_gdb_pretty_printer_section]`. - (active, omit_gdb_pretty_printer_section, "1.5.0", None, None), + (internal, omit_gdb_pretty_printer_section, "1.5.0", None, None), /// Allows using `#[prelude_import]` on glob `use` items. - (active, prelude_import, "1.2.0", None, None), + (internal, prelude_import, "1.2.0", None, None), /// Used to identify crates that contain the profiler runtime. - (active, profiler_runtime, "1.18.0", None, None), + (internal, profiler_runtime, "1.18.0", None, None), /// Allows using `rustc_*` attributes (RFC 572). - (active, rustc_attrs, "1.0.0", None, None), + (internal, rustc_attrs, "1.0.0", None, None), /// Allows using the `#[stable]` and `#[unstable]` attributes. - (active, staged_api, "1.0.0", None, None), + (internal, staged_api, "1.0.0", None, None), /// Added for testing E0705; perma-unstable. - (active, test_2018_feature, "1.31.0", None, Some(Edition::Edition2018)), + (internal, test_2018_feature, "1.31.0", None, Some(Edition::Edition2018)), /// Added for testing unstable lints; perma-unstable. - (active, test_unstable_lint, "1.60.0", None, None), + (internal, test_unstable_lint, "1.60.0", None, None), /// Allows non-`unsafe` —and thus, unsound— access to `Pin` constructions. - /// Marked `incomplete` since perma-unstable and unsound. - (incomplete, unsafe_pin_internals, "1.60.0", None, None), + /// Marked `internal` since perma-unstable and unsound. + (internal, unsafe_pin_internals, "1.60.0", None, None), /// Use for stable + negative coherence and strict coherence depending on trait's /// rustc_strict_coherence value. (active, with_negative_coherence, "1.60.0", None, None), @@ -216,19 +240,19 @@ declare_features! ( /// Allows using the `#[linkage = ".."]` attribute. (active, linkage, "1.0.0", Some(29603), None), /// Allows declaring with `#![needs_panic_runtime]` that a panic runtime is needed. - (active, needs_panic_runtime, "1.10.0", Some(32837), None), + (internal, needs_panic_runtime, "1.10.0", Some(32837), None), /// Allows using `+bundled,+whole-archive` native libs. (active, packed_bundled_libs, "1.69.0", Some(108081), None), /// Allows using the `#![panic_runtime]` attribute. - (active, panic_runtime, "1.10.0", Some(32837), None), + (internal, panic_runtime, "1.10.0", Some(32837), None), /// Allows using `#[rustc_allow_const_fn_unstable]`. /// This is an attribute on `const fn` for the same /// purpose as `#[allow_internal_unstable]`. - (active, rustc_allow_const_fn_unstable, "1.49.0", Some(69399), None), + (internal, rustc_allow_const_fn_unstable, "1.49.0", Some(69399), None), /// Allows using compiler's own crates. (active, rustc_private, "1.0.0", Some(27812), None), /// Allows using internal rustdoc features like `doc(keyword)`. - (active, rustdoc_internals, "1.58.0", Some(90418), None), + (internal, rustdoc_internals, "1.58.0", Some(90418), None), /// Allows using the `rustdoc::missing_doc_code_examples` lint (active, rustdoc_missing_doc_code_examples, "1.31.0", Some(101730), None), /// Allows using `#[start]` on a function indicating that it is the program entrypoint. @@ -258,6 +282,7 @@ declare_features! ( (active, arm_target_feature, "1.27.0", Some(44839), None), (active, avx512_target_feature, "1.27.0", Some(44839), None), (active, bpf_target_feature, "1.54.0", Some(44839), None), + (active, csky_target_feature, "1.73.0", Some(44839), None), (active, ermsb_target_feature, "1.49.0", Some(44839), None), (active, hexagon_target_feature, "1.27.0", Some(44839), None), (active, mips_target_feature, "1.27.0", Some(44839), None), @@ -289,6 +314,8 @@ declare_features! ( (active, abi_msp430_interrupt, "1.16.0", Some(38487), None), /// Allows `extern "ptx-*" fn()`. (active, abi_ptx, "1.15.0", Some(38788), None), + /// Allows `extern "riscv-interrupt-m" fn()` and `extern "riscv-interrupt-s" fn()`. + (active, abi_riscv_interrupt, "1.73.0", Some(111889), None), /// Allows `extern "x86-interrupt" fn()`. (active, abi_x86_interrupt, "1.17.0", Some(40180), None), /// Allows additional const parameter types, such as `&'static str` or user defined types @@ -313,16 +340,20 @@ declare_features! ( (active, async_closure, "1.37.0", Some(62290), None), /// Allows async functions to be declared, implemented, and used in traits. (active, async_fn_in_trait, "1.66.0", Some(91611), None), + /// Allows `#[track_caller]` on async functions. + (active, async_fn_track_caller, "1.73.0", Some(110011), None), /// Allows builtin # foo() syntax - (active, builtin_syntax, "CURRENT_RUSTC_VERSION", Some(110680), None), + (active, builtin_syntax, "1.71.0", Some(110680), None), /// Allows `c"foo"` literals. - (active, c_str_literals, "CURRENT_RUSTC_VERSION", Some(105723), None), + (active, c_str_literals, "1.71.0", Some(105723), None), /// Treat `extern "C"` function as nounwind. (active, c_unwind, "1.52.0", Some(74990), None), /// Allows using C-variadics. (active, c_variadic, "1.34.0", Some(44930), None), /// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour. - (active, cfg_overflow_checks, "CURRENT_RUSTC_VERSION", Some(111466), None), + (active, cfg_overflow_checks, "1.71.0", Some(111466), None), + /// Provides the relocation model information as cfg entry + (active, cfg_relocation_model, "1.73.0", Some(114929), None), /// Allows the use of `#[cfg(sanitize = "option")]`; set when -Zsanitizer is used. (active, cfg_sanitize, "1.41.0", Some(39699), None), /// Allows `cfg(target_abi = "...")`. @@ -338,7 +369,7 @@ declare_features! ( /// Allow conditional compilation depending on rust version (active, cfg_version, "1.45.0", Some(64796), None), /// Allows to use the `#[cfi_encoding = ""]` attribute. - (active, cfi_encoding, "CURRENT_RUSTC_VERSION", Some(89653), None), + (active, cfi_encoding, "1.71.0", Some(89653), None), /// Allows `for<...>` on closures and generators. (active, closure_lifetime_binder, "1.64.0", Some(97362), None), /// Allows `#[track_caller]` on closures and generators. @@ -351,8 +382,6 @@ declare_features! ( (active, const_async_blocks, "1.53.0", Some(85368), None), /// Allows `const || {}` closures in const contexts. (incomplete, const_closures, "1.68.0", Some(106003), None), - /// Allows limiting the evaluation steps of const expressions - (active, const_eval_limit, "1.43.0", Some(67217), None), /// Allows the definition of `const extern fn` and `const unsafe extern fn`. (active, const_extern_fn, "1.40.0", Some(64926), None), /// Allows basic arithmetic on floating point types in a `const fn`. @@ -381,6 +410,8 @@ declare_features! ( (active, deprecated_safe, "1.61.0", Some(94978), None), /// Allows having using `suggestion` in the `#[deprecated]` attribute. (active, deprecated_suggestion, "1.61.0", Some(94785), None), + /// Allows using the `#[diagnostic]` attribute tool namespace + (active, diagnostic_namespace, "1.73.0", Some(94785), None), /// Controls errors in trait implementations. (active, do_not_recommend, "1.67.0", Some(51992), None), /// Tells rustdoc to automatically generate `#[doc(cfg(...))]`. @@ -393,10 +424,14 @@ declare_features! ( (active, doc_masked, "1.21.0", Some(44027), None), /// Allows `dyn* Trait` objects. (incomplete, dyn_star, "1.65.0", Some(102425), None), + // Uses generic effect parameters for ~const bounds + (active, effects, "1.72.0", Some(102090), None), /// Allows `X..Y` patterns. (active, exclusive_range_pattern, "1.11.0", Some(37854), None), /// Allows exhaustive pattern matching on types that contain uninhabited types. (active, exhaustive_patterns, "1.13.0", Some(51085), None), + /// Allows explicit tail calls via `become` expression. + (incomplete, explicit_tail_calls, "1.72.0", Some(112788), None), /// Allows using `efiapi`, `sysv64` and `win64` as calling convention /// for functions with varargs. (active, extended_varargs_abi_support, "1.65.0", Some(100189), None), @@ -420,6 +455,8 @@ declare_features! ( (incomplete, generic_associated_types_extended, "1.61.0", Some(95451), None), /// Allows non-trivial generic constants which have to have wfness manually propagated to callers (incomplete, generic_const_exprs, "1.56.0", Some(76560), None), + /// Allows generic parameters and where-clauses on free & associated const items. + (incomplete, generic_const_items, "1.73.0", Some(113521), None), /// Allows using `..=X` as a patterns in slices. (active, half_open_range_patterns_in_slices, "1.66.0", Some(67264), None), /// Allows `if let` guard in match arms. @@ -442,6 +479,8 @@ declare_features! ( (active, intra_doc_pointers, "1.51.0", Some(80896), None), // Allows setting the threshold for the `large_assignments` lint. (active, large_assignments, "1.52.0", Some(83518), None), + /// Allow to have type alias types for inter-crate use. + (incomplete, lazy_type_alias, "1.72.0", Some(112792), None), /// Allows `if/while p && let q = r && ...` chains. (active, let_chains, "1.37.0", Some(53667), None), /// Allows using `reason` in lint attributes and the `#[expect(lint)]` lint check. @@ -541,6 +580,8 @@ declare_features! ( /// Allows creation of instances of a struct by moving fields that have /// not changed from prior instances of the same struct (RFC #2528) (active, type_changing_struct_update, "1.58.0", Some(86555), None), + /// Allows using type privacy lints (`private_interfaces`, `private_bounds`, `unnameable_types`). + (active, type_privacy_lints, "1.72.0", Some(48054), None), /// Enables rustc to generate code that instructs libstd to NOT ignore SIGPIPE. (active, unix_sigpipe, "1.65.0", Some(97889), None), /// Allows unsized fn parameters. diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 06f4a0b5eef84..2f7cff3ce5c83 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -35,6 +35,7 @@ const GATED_CFGS: &[GatedCfg] = &[ (sym::target_has_atomic_load_store, sym::cfg_target_has_atomic, cfg_fn!(cfg_target_has_atomic)), (sym::sanitize, sym::cfg_sanitize, cfg_fn!(cfg_sanitize)), (sym::version, sym::cfg_version, cfg_fn!(cfg_version)), + (sym::relocation_model, sym::cfg_relocation_model, cfg_fn!(cfg_relocation_model)), ]; /// Find a gated cfg determined by the `pred`icate which is given the cfg's name. @@ -355,10 +356,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Limits: ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), - gated!( - const_eval_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, - const_eval_limit, experimental!(const_eval_limit) - ), gated!( move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, large_assignments, experimental!(move_size_limit) @@ -629,6 +626,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ ErrorFollowing, INTERNAL_UNSTABLE ), + rustc_attr!( + rustc_confusables, Normal, + template!(List: r#""name1", "name2", ..."#), + ErrorFollowing, + INTERNAL_UNSTABLE, + ), // Enumerates "identity-like" conversion methods to suggest on type mismatch. rustc_attr!( rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE @@ -709,7 +712,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl." ), rustc_attr!( - rustc_deny_explicit_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: false, + rustc_deny_explicit_impl, + AttributeType::Normal, + template!(List: "implement_via_object = (true|false)"), + ErrorFollowing, + @only_local: true, "#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls" ), rustc_attr!( @@ -723,6 +730,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ and it is only intended to be used in `alloc`." ), + rustc_attr!( + rustc_host, AttributeType::Normal, template!(Word), ErrorFollowing, + "#[rustc_host] annotates const generic parameters as the `host` effect param, \ + and it is only intended for internal use and as a desugaring." + ), + BuiltinAttribute { name: sym::rustc_diagnostic_item, // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`. @@ -806,7 +819,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ TEST, rustc_error, Normal, template!(Word, List: "delay_span_bug_from_inside_query"), WarnFollowingWordOnly ), - rustc_attr!(TEST, rustc_dump_user_substs, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dump_user_args, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing), rustc_attr!( TEST, rustc_if_this_changed, Normal, template!(Word, List: "DepNode"), DuplicatesOk diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index beb6307846d3e..69e33115922ab 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -108,8 +108,6 @@ impl UnstableFeatures { fn find_lang_feature_issue(feature: Symbol) -> Option { if let Some(info) = ACTIVE_FEATURES.iter().find(|t| t.name == feature) { - // FIXME (#28244): enforce that active features have issue numbers - // assert!(info.issue.is_some()) info.issue } else { // search in Accepted, Removed, or Stable Removed features diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 8bca24b2bf01e..ed5d76b861a49 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -59,8 +59,10 @@ declare_features! ( /// Allows comparing raw pointers during const eval. (removed, const_compare_raw_pointers, "1.46.0", Some(53020), None, Some("cannot be allowed in const eval in any meaningful way")), + /// Allows limiting the evaluation steps of const expressions + (removed, const_eval_limit, "1.43.0", Some(67217), None, Some("removed the limit entirely")), /// Allows non-trivial generic constants which have to be manually propagated upwards. - (removed, const_evaluatable_checked, "1.48.0", Some(76560), None, Some("renamed to `generic_const_exprs`")), + (removed, const_evaluatable_checked, "1.48.0", Some(76560), None, Some("renamed to `generic_const_exprs`")), /// Allows the definition of `const` functions with some advanced features. (removed, const_fn, "1.54.0", Some(57563), None, Some("split into finer-grained feature gates")), diff --git a/compiler/rustc_fluent_macro/src/fluent.rs b/compiler/rustc_fluent_macro/src/fluent.rs index 9dffc9a764548..56e23ac277520 100644 --- a/compiler/rustc_fluent_macro/src/fluent.rs +++ b/compiler/rustc_fluent_macro/src/fluent.rs @@ -179,7 +179,8 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok let mut previous_defns = HashMap::new(); let mut message_refs = Vec::new(); for entry in resource.entries() { - if let Entry::Message(Message { id: Identifier { name }, attributes, value, .. }) = entry { + if let Entry::Message(msg) = entry { + let Message { id: Identifier { name }, attributes, value, .. } = msg; let _ = previous_defns.entry(name.to_string()).or_insert(resource_span); if name.contains('-') { Diagnostic::spanned( @@ -229,9 +230,10 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok continue; } - let msg = format!("Constant referring to Fluent message `{name}` from `{crate_name}`"); + let docstr = + format!("Constant referring to Fluent message `{name}` from `{crate_name}`"); constants.extend(quote! { - #[doc = #msg] + #[doc = #docstr] pub const #snake_name: crate::DiagnosticMessage = crate::DiagnosticMessage::FluentIdentifier( std::borrow::Cow::Borrowed(#name), @@ -269,6 +271,15 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok ); }); } + + // Record variables referenced by these messages so we can produce + // tests in the derive diagnostics to validate them. + let ident = quote::format_ident!("{snake_name}_refs"); + let vrefs = variable_references(msg); + constants.extend(quote! { + #[cfg(test)] + pub const #ident: &[&str] = &[#(#vrefs),*]; + }) } } @@ -334,3 +345,28 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok } .into() } + +fn variable_references<'a>(msg: &Message<&'a str>) -> Vec<&'a str> { + let mut refs = vec![]; + if let Some(Pattern { elements }) = &msg.value { + for elt in elements { + if let PatternElement::Placeable { + expression: Expression::Inline(InlineExpression::VariableReference { id }), + } = elt + { + refs.push(id.name); + } + } + } + for attr in &msg.attributes { + for elt in &attr.value.elements { + if let PatternElement::Placeable { + expression: Expression::Inline(InlineExpression::VariableReference { id }), + } = elt + { + refs.push(id.name); + } + } + } + refs +} diff --git a/compiler/rustc_hir/src/def.rs b/compiler/rustc_hir/src/def.rs index 30bf8c2ad104b..64271309664ec 100644 --- a/compiler/rustc_hir/src/def.rs +++ b/compiler/rustc_hir/src/def.rs @@ -61,7 +61,9 @@ pub enum DefKind { Variant, Trait, /// Type alias: `type Foo = Bar;` - TyAlias, + TyAlias { + lazy: bool, + }, /// Type from an `extern` block. ForeignTy, /// Trait alias: `trait IntIterator = Iterator;` @@ -109,8 +111,6 @@ pub enum DefKind { InlineConst, /// Opaque type, aka `impl Trait`. OpaqueTy, - /// A return-position `impl Trait` in a trait definition - ImplTraitPlaceholder, Field, /// Lifetime parameter: the `'a` in `struct Foo<'a> { ... }` LifetimeParam, @@ -143,8 +143,7 @@ impl DefKind { DefKind::Ctor(CtorOf::Struct, CtorKind::Fn) => "tuple struct", DefKind::Ctor(CtorOf::Struct, CtorKind::Const) => "unit struct", DefKind::OpaqueTy => "opaque type", - DefKind::ImplTraitPlaceholder => "opaque type in trait", - DefKind::TyAlias => "type alias", + DefKind::TyAlias { .. } => "type alias", DefKind::TraitAlias => "trait alias", DefKind::AssocTy => "associated type", DefKind::Union => "union", @@ -200,7 +199,7 @@ impl DefKind { | DefKind::Variant | DefKind::Trait | DefKind::OpaqueTy - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -227,8 +226,7 @@ impl DefKind { | DefKind::Use | DefKind::ForeignMod | DefKind::GlobalAsm - | DefKind::Impl { .. } - | DefKind::ImplTraitPlaceholder => None, + | DefKind::Impl { .. } => None, } } @@ -252,7 +250,7 @@ impl DefKind { | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -262,7 +260,6 @@ impl DefKind { | DefKind::Use | DefKind::ForeignMod | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Impl { .. } | DefKind::Field | DefKind::TyParam diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index e844731091761..0bfd62d68b295 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1675,6 +1675,14 @@ pub struct AnonConst { pub body: BodyId, } +/// An inline constant expression `const { something }`. +#[derive(Copy, Clone, Debug, HashStable_Generic)] +pub struct ConstBlock { + pub hir_id: HirId, + pub def_id: LocalDefId, + pub body: BodyId, +} + /// An expression. #[derive(Debug, Clone, Copy, HashStable_Generic)] pub struct Expr<'hir> { @@ -1711,6 +1719,7 @@ impl Expr<'_> { ExprKind::Break(..) => ExprPrecedence::Break, ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, + ExprKind::Become(..) => ExprPrecedence::Become, ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf, ExprKind::Struct(..) => ExprPrecedence::Struct, @@ -1745,7 +1754,7 @@ impl Expr<'_> { ExprKind::Unary(UnOp::Deref, _) => true, - ExprKind::Field(ref base, _) | ExprKind::Index(ref base, _) => { + ExprKind::Field(ref base, _) | ExprKind::Index(ref base, _, _) => { allow_projections_from(base) || base.is_place_expr(allow_projections_from) } @@ -1768,6 +1777,7 @@ impl Expr<'_> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::Become(..) | ExprKind::Let(..) | ExprKind::Loop(..) | ExprKind::Assign(..) @@ -1821,7 +1831,7 @@ impl Expr<'_> { ExprKind::Type(base, _) | ExprKind::Unary(_, base) | ExprKind::Field(base, _) - | ExprKind::Index(base, _) + | ExprKind::Index(base, _, _) | ExprKind::AddrOf(.., base) | ExprKind::Cast(base, _) => { // This isn't exactly true for `Index` and all `Unary`, but we are using this @@ -1833,7 +1843,7 @@ impl Expr<'_> { .iter() .map(|field| field.expr) .chain(init.into_iter()) - .all(|e| e.can_have_side_effects()), + .any(|e| e.can_have_side_effects()), ExprKind::Array(args) | ExprKind::Tup(args) @@ -1847,7 +1857,7 @@ impl Expr<'_> { .. }, args, - ) => args.iter().all(|arg| arg.can_have_side_effects()), + ) => args.iter().any(|arg| arg.can_have_side_effects()), ExprKind::If(..) | ExprKind::Match(..) | ExprKind::MethodCall(..) @@ -1858,6 +1868,7 @@ impl Expr<'_> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::Become(..) | ExprKind::Let(..) | ExprKind::Loop(..) | ExprKind::Assign(..) @@ -1922,7 +1933,7 @@ pub fn is_range_literal(expr: &Expr<'_>) -> bool { #[derive(Debug, Clone, Copy, HashStable_Generic)] pub enum ExprKind<'hir> { /// Allow anonymous constants from an inline `const` block - ConstBlock(AnonConst), + ConstBlock(ConstBlock), /// An array (e.g., `[a, b, c, d]`). Array(&'hir [Expr<'hir>]), /// A function call. @@ -2004,7 +2015,9 @@ pub enum ExprKind<'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]`). - Index(&'hir Expr<'hir>, &'hir Expr<'hir>), + /// Similar to [`ExprKind::MethodCall`], the final `Span` represents the span of the brackets + /// and index. + Index(&'hir Expr<'hir>, &'hir Expr<'hir>, Span), /// Path to a definition, possibly containing lifetime or type parameters. Path(QPath<'hir>), @@ -2017,6 +2030,8 @@ pub enum ExprKind<'hir> { Continue(Destination), /// A `return`, with an optional value to be returned. Ret(Option<&'hir Expr<'hir>>), + /// A `become`, with the value to be returned. + Become(&'hir Expr<'hir>), /// Inline assembly (from `asm!`), with its outputs and inputs. InlineAsm(&'hir InlineAsm<'hir>), @@ -2133,7 +2148,7 @@ pub enum MatchSource { /// A desugared `for _ in _ { .. }` loop. ForLoopDesugar, /// A desugared `?` operator. - TryDesugar, + TryDesugar(HirId), /// A desugared `.await`. AwaitDesugar, /// A desugared `format_args!()`. @@ -2147,7 +2162,7 @@ impl MatchSource { match self { Normal => "match", ForLoopDesugar => "for", - TryDesugar => "?", + TryDesugar(_) => "?", AwaitDesugar => ".await", FormatArgs => "format_args!()", } @@ -2651,6 +2666,19 @@ pub struct OpaqueTy<'hir> { pub generics: &'hir Generics<'hir>, pub bounds: GenericBounds<'hir>, pub origin: OpaqueTyOrigin, + /// Return-position impl traits (and async futures) must "reify" any late-bound + /// lifetimes that are captured from the function signature they originate from. + /// + /// This is done by generating a new early-bound lifetime parameter local to the + /// opaque which is substituted in the function signature with the late-bound + /// lifetime. + /// + /// This mapping associated a captured lifetime (first parameter) with the new + /// early-bound lifetime that was generated for the opaque. + pub lifetime_mapping: &'hir [(&'hir Lifetime, LocalDefId)], + /// Whether the opaque is a return-position impl trait (or async future) + /// originating from a trait method. This makes it so that the opaque is + /// lowered as an associated type. pub in_trait: bool, } @@ -2987,8 +3015,7 @@ pub struct FieldDef<'hir> { impl FieldDef<'_> { // Still necessary in couple of places pub fn is_positional(&self) -> bool { - let first = self.ident.as_str().as_bytes()[0]; - (b'0'..=b'9').contains(&first) + self.ident.as_str().as_bytes()[0].is_ascii_digit() } } @@ -3105,9 +3132,9 @@ impl<'hir> Item<'hir> { } /// Expect an [`ItemKind::Const`] or panic. #[track_caller] - pub fn expect_const(&self) -> (&'hir Ty<'hir>, BodyId) { - let ItemKind::Const(ty, body) = self.kind else { self.expect_failed("a constant") }; - (ty, body) + pub fn expect_const(&self) -> (&'hir Ty<'hir>, &'hir Generics<'hir>, BodyId) { + let ItemKind::Const(ty, gen, body) = self.kind else { self.expect_failed("a constant") }; + (ty, gen, body) } /// Expect an [`ItemKind::Fn`] or panic. #[track_caller] @@ -3133,7 +3160,9 @@ impl<'hir> Item<'hir> { /// Expect an [`ItemKind::ForeignMod`] or panic. #[track_caller] pub fn expect_foreign_mod(&self) -> (Abi, &'hir [ForeignItemRef]) { - let ItemKind::ForeignMod { abi, items } = self.kind else { self.expect_failed("a foreign module") }; + let ItemKind::ForeignMod { abi, items } = self.kind else { + self.expect_failed("a foreign module") + }; (abi, items) } @@ -3184,14 +3213,18 @@ impl<'hir> Item<'hir> { pub fn expect_trait( self, ) -> (IsAuto, Unsafety, &'hir Generics<'hir>, GenericBounds<'hir>, &'hir [TraitItemRef]) { - let ItemKind::Trait(is_auto, unsafety, gen, bounds, items) = self.kind else { self.expect_failed("a trait") }; + let ItemKind::Trait(is_auto, unsafety, gen, bounds, items) = self.kind else { + self.expect_failed("a trait") + }; (is_auto, unsafety, gen, bounds, items) } /// Expect an [`ItemKind::TraitAlias`] or panic. #[track_caller] pub fn expect_trait_alias(&self) -> (&'hir Generics<'hir>, GenericBounds<'hir>) { - let ItemKind::TraitAlias(gen, bounds) = self.kind else { self.expect_failed("a trait alias") }; + let ItemKind::TraitAlias(gen, bounds) = self.kind else { + self.expect_failed("a trait alias") + }; (gen, bounds) } @@ -3288,7 +3321,7 @@ pub enum ItemKind<'hir> { /// A `static` item. Static(&'hir Ty<'hir>, Mutability, BodyId), /// A `const` item. - Const(&'hir Ty<'hir>, BodyId), + Const(&'hir Ty<'hir>, &'hir Generics<'hir>, BodyId), /// A function declaration. Fn(FnSig<'hir>, &'hir Generics<'hir>, BodyId), /// A MBE macro definition (`macro_rules!` or `macro`). @@ -3302,7 +3335,7 @@ pub enum ItemKind<'hir> { /// A type alias, e.g., `type Foo = Bar`. TyAlias(&'hir Ty<'hir>, &'hir Generics<'hir>), /// An opaque `impl Trait` type alias, e.g., `type Foo = impl Bar;`. - OpaqueTy(OpaqueTy<'hir>), + OpaqueTy(&'hir OpaqueTy<'hir>), /// An enum definition, e.g., `enum Foo {C, D}`. Enum(EnumDef<'hir>, &'hir Generics<'hir>), /// A struct definition, e.g., `struct Foo {x: A}`. @@ -3326,7 +3359,6 @@ pub struct Impl<'hir> { // We do not put a `Span` in `Defaultness` because it breaks foreign crate metadata // decoding as `Span`s cannot be decoded when a `Session` is not available. pub defaultness_span: Option, - pub constness: Constness, pub generics: &'hir Generics<'hir>, /// The trait being implemented, if any. @@ -3341,6 +3373,7 @@ impl ItemKind<'_> { Some(match *self { ItemKind::Fn(_, ref generics, _) | ItemKind::TyAlias(_, ref generics) + | ItemKind::Const(_, ref generics, _) | ItemKind::OpaqueTy(OpaqueTy { ref generics, .. }) | ItemKind::Enum(_, ref generics) | ItemKind::Struct(_, ref generics) @@ -3536,7 +3569,9 @@ impl<'hir> OwnerNode<'hir> { match self { OwnerNode::Item(Item { kind: - ItemKind::Static(_, _, body) | ItemKind::Const(_, body) | ItemKind::Fn(_, _, body), + ItemKind::Static(_, _, body) + | ItemKind::Const(_, _, body) + | ItemKind::Fn(_, _, body), .. }) | OwnerNode::TraitItem(TraitItem { @@ -3641,6 +3676,7 @@ pub enum Node<'hir> { Variant(&'hir Variant<'hir>), Field(&'hir FieldDef<'hir>), AnonConst(&'hir AnonConst), + ConstBlock(&'hir ConstBlock), Expr(&'hir Expr<'hir>), ExprField(&'hir ExprField<'hir>), Stmt(&'hir Stmt<'hir>), @@ -3695,6 +3731,7 @@ impl<'hir> Node<'hir> { Node::TypeBinding(b) => Some(b.ident), Node::Param(..) | Node::AnonConst(..) + | Node::ConstBlock(..) | Node::Expr(..) | Node::Stmt(..) | Node::Block(..) @@ -3733,6 +3770,29 @@ impl<'hir> Node<'hir> { } } + /// Get the type for constants, assoc types, type aliases and statics. + pub fn ty(self) -> Option<&'hir Ty<'hir>> { + match self { + Node::Item(it) => match it.kind { + ItemKind::TyAlias(ty, _) + | ItemKind::Static(ty, _, _) + | ItemKind::Const(ty, _, _) => Some(ty), + _ => None, + }, + Node::TraitItem(it) => match it.kind { + TraitItemKind::Const(ty, _) => Some(ty), + TraitItemKind::Type(_, ty) => ty, + _ => None, + }, + Node::ImplItem(it) => match it.kind { + ImplItemKind::Const(ty, _) => Some(ty), + ImplItemKind::Type(ty) => Some(ty), + _ => None, + }, + _ => None, + } + } + pub fn alias_ty(self) -> Option<&'hir Ty<'hir>> { match self { Node::Item(Item { kind: ItemKind::TyAlias(ty, ..), .. }) => Some(ty), @@ -3744,7 +3804,9 @@ impl<'hir> Node<'hir> { match self { Node::Item(Item { kind: - ItemKind::Static(_, _, body) | ItemKind::Const(_, body) | ItemKind::Fn(_, _, body), + ItemKind::Static(_, _, body) + | ItemKind::Const(_, _, body) + | ItemKind::Fn(_, _, body), .. }) | Node::TraitItem(TraitItem { @@ -3758,7 +3820,7 @@ impl<'hir> Node<'hir> { }) | Node::Expr(Expr { kind: - ExprKind::ConstBlock(AnonConst { body, .. }) + ExprKind::ConstBlock(ConstBlock { body, .. }) | ExprKind::Closure(Closure { body, .. }) | ExprKind::Repeat(_, ArrayLen::Body(AnonConst { body, .. })), .. @@ -3878,6 +3940,13 @@ impl<'hir> Node<'hir> { this } + /// Expect a [`Node::ConstBlock`] or panic. + #[track_caller] + pub fn expect_inline_const(self) -> &'hir ConstBlock { + let Node::ConstBlock(this) = self else { self.expect_failed("an inline constant") }; + this + } + /// Expect a [`Node::Expr`] or panic. #[track_caller] pub fn expect_expr(self) -> &'hir Expr<'hir> { diff --git a/compiler/rustc_hir/src/hir_id.rs b/compiler/rustc_hir/src/hir_id.rs index d549f52f873a9..34c6157793663 100644 --- a/compiler/rustc_hir/src/hir_id.rs +++ b/compiler/rustc_hir/src/hir_id.rs @@ -166,7 +166,9 @@ impl ItemLocalId { // Safety: Ord is implement as just comparing the ItemLocalId's numerical // values and these are not changed by (de-)serialization. -unsafe impl StableOrd for ItemLocalId {} +unsafe impl StableOrd for ItemLocalId { + const CAN_USE_UNSTABLE_SORT: bool = true; +} /// The `HirId` corresponding to `CRATE_NODE_ID` and `CRATE_DEF_ID`. pub const CRATE_HIR_ID: HirId = diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index df0047d82e12e..172f557f8e2ae 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -335,6 +335,9 @@ pub trait Visitor<'v>: Sized { fn visit_anon_const(&mut self, c: &'v AnonConst) { walk_anon_const(self, c) } + fn visit_inline_const(&mut self, c: &'v ConstBlock) { + walk_inline_const(self, c) + } fn visit_expr(&mut self, ex: &'v Expr<'v>) { walk_expr(self, ex) } @@ -464,9 +467,15 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) { ItemKind::Use(ref path, _) => { visitor.visit_use(path, item.hir_id()); } - ItemKind::Static(ref typ, _, body) | ItemKind::Const(ref typ, body) => { + ItemKind::Static(ref typ, _, body) => { + visitor.visit_id(item.hir_id()); + visitor.visit_ty(typ); + visitor.visit_nested_body(body); + } + ItemKind::Const(ref typ, ref generics, body) => { visitor.visit_id(item.hir_id()); visitor.visit_ty(typ); + visitor.visit_generics(generics); visitor.visit_nested_body(body); } ItemKind::Fn(ref sig, ref generics, body_id) => { @@ -499,7 +508,7 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) { visitor.visit_ty(ty); visitor.visit_generics(generics) } - ItemKind::OpaqueTy(OpaqueTy { ref generics, bounds, .. }) => { + ItemKind::OpaqueTy(&OpaqueTy { generics, bounds, .. }) => { visitor.visit_id(item.hir_id()); walk_generics(visitor, generics); walk_list!(visitor, visit_param_bound, bounds); @@ -513,7 +522,6 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) { unsafety: _, defaultness: _, polarity: _, - constness: _, defaultness_span: _, ref generics, ref of_trait, @@ -679,13 +687,18 @@ pub fn walk_anon_const<'v, V: Visitor<'v>>(visitor: &mut V, constant: &'v AnonCo visitor.visit_nested_body(constant.body); } +pub fn walk_inline_const<'v, V: Visitor<'v>>(visitor: &mut V, constant: &'v ConstBlock) { + visitor.visit_id(constant.hir_id); + visitor.visit_nested_body(constant.body); +} + pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) { visitor.visit_id(expression.hir_id); match expression.kind { ExprKind::Array(subexpressions) => { walk_list!(visitor, visit_expr, subexpressions); } - ExprKind::ConstBlock(ref anon_const) => visitor.visit_anon_const(anon_const), + ExprKind::ConstBlock(ref const_block) => visitor.visit_inline_const(const_block), ExprKind::Repeat(ref element, ref count) => { visitor.visit_expr(element); visitor.visit_array_length(count) @@ -766,7 +779,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) visitor.visit_expr(subexpression); visitor.visit_ident(ident); } - ExprKind::Index(ref main_expression, ref index_expression) => { + ExprKind::Index(ref main_expression, ref index_expression, _) => { visitor.visit_expr(main_expression); visitor.visit_expr(index_expression) } @@ -783,6 +796,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) ExprKind::Ret(ref optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } + ExprKind::Become(ref expr) => visitor.visit_expr(expr), ExprKind::InlineAsm(ref asm) => { visitor.visit_inline_asm(asm, expression.hir_id); } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 4b3bc816b9531..302a949841316 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -250,7 +250,6 @@ language_item_table! { FormatUnsafeArg, sym::format_unsafe_arg, format_unsafe_arg, Target::Struct, GenericRequirement::None; ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None; - BoxFree, sym::box_free, box_free_fn, Target::Fn, GenericRequirement::Minimum(1); DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1); AllocLayout, sym::alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None; @@ -260,6 +259,8 @@ language_item_table! { EhCatchTypeinfo, sym::eh_catch_typeinfo, eh_catch_typeinfo, Target::Static, GenericRequirement::None; OwnedBox, sym::owned_box, owned_box, Target::Struct, GenericRequirement::Minimum(1); + // Experimental language item for Miri + PtrUnique, sym::ptr_unique, ptr_unique, Target::Struct, GenericRequirement::Exact(1); PhantomData, sym::phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1); diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs index 616de57dc6372..34214931a081d 100644 --- a/compiler/rustc_hir/src/lib.rs +++ b/compiler/rustc_hir/src/lib.rs @@ -13,6 +13,7 @@ #![recursion_limit = "256"] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_macros; diff --git a/compiler/rustc_hir/src/target.rs b/compiler/rustc_hir/src/target.rs index 961deac544a88..644c4d8265dd5 100644 --- a/compiler/rustc_hir/src/target.rs +++ b/compiler/rustc_hir/src/target.rs @@ -36,7 +36,6 @@ pub enum Target { GlobalAsm, TyAlias, OpaqueTy, - ImplTraitPlaceholder, Enum, Variant, Struct, @@ -80,13 +79,7 @@ impl Target { ItemKind::ForeignMod { .. } => Target::ForeignMod, ItemKind::GlobalAsm(..) => Target::GlobalAsm, ItemKind::TyAlias(..) => Target::TyAlias, - ItemKind::OpaqueTy(ref opaque) => { - if opaque.in_trait { - Target::ImplTraitPlaceholder - } else { - Target::OpaqueTy - } - } + ItemKind::OpaqueTy(..) => Target::OpaqueTy, ItemKind::Enum(..) => Target::Enum, ItemKind::Struct(..) => Target::Struct, ItemKind::Union(..) => Target::Union, @@ -108,9 +101,8 @@ impl Target { DefKind::Mod => Target::Mod, DefKind::ForeignMod => Target::ForeignMod, DefKind::GlobalAsm => Target::GlobalAsm, - DefKind::TyAlias => Target::TyAlias, + DefKind::TyAlias { .. } => Target::TyAlias, DefKind::OpaqueTy => Target::OpaqueTy, - DefKind::ImplTraitPlaceholder => Target::ImplTraitPlaceholder, DefKind::Enum => Target::Enum, DefKind::Struct => Target::Struct, DefKind::Union => Target::Union, @@ -165,7 +157,6 @@ impl Target { Target::GlobalAsm => "global asm", Target::TyAlias => "type alias", Target::OpaqueTy => "opaque type", - Target::ImplTraitPlaceholder => "opaque type in trait", Target::Enum => "enum", Target::Variant => "enum variant", Target::Struct => "struct", diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 5e5c984a7ea15..597cae6ff33ca 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -1,77 +1,156 @@ -hir_analysis_unrecognized_atomic_operation = - unrecognized atomic operation function: `{$op}` - .label = unrecognized atomic operation +hir_analysis_ambiguous_lifetime_bound = + ambiguous lifetime bound, explicit lifetime bound required -hir_analysis_wrong_number_of_generic_arguments_to_intrinsic = - intrinsic has wrong number of {$descr} parameters: found {$found}, expected {$expected} - .label = expected {$expected} {$descr} {$expected -> - [one] parameter - *[other] parameters - } +hir_analysis_assoc_bound_on_const = expected associated type, found {$descr} + .note = trait bounds not allowed on {$descr} -hir_analysis_unrecognized_intrinsic_function = - unrecognized intrinsic function: `{$name}` - .label = unrecognized intrinsic +hir_analysis_assoc_type_binding_not_allowed = + associated type bindings are not allowed here + .label = associated type not allowed here -hir_analysis_lifetimes_or_bounds_mismatch_on_trait = - lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration - .label = lifetimes do not match {$item_kind} in trait - .generics_label = lifetimes in impl do not match this {$item_kind} in trait - .where_label = this `where` clause might not match the one in the trait - .bounds_label = this bound might be missing in the impl +hir_analysis_associated_type_trait_uninferred_generic_params = cannot use the associated type of a trait with uninferred generic parameters + .suggestion = use a fully qualified path with inferred lifetimes + +hir_analysis_associated_type_trait_uninferred_generic_params_multipart_suggestion = use a fully qualified path with explicit lifetimes hir_analysis_async_trait_impl_should_be_async = method `{$method_name}` should be async because the method from the trait is async .trait_item_label = required because the trait method is async +hir_analysis_auto_deref_reached_recursion_limit = reached the recursion limit while auto-dereferencing `{$ty}` + .label = deref recursion limit reached + .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) + +hir_analysis_cannot_capture_late_bound_const_in_anon_const = + cannot capture late-bound const parameter in a constant + .label = parameter defined here + +hir_analysis_cannot_capture_late_bound_ty_in_anon_const = + cannot capture late-bound type parameter in a constant + .label = parameter defined here + +hir_analysis_cast_thin_pointer_to_fat_pointer = cannot cast thin pointer `{$expr_ty}` to fat pointer `{$cast_ty}` + +hir_analysis_closure_implicit_hrtb = implicit types in closure signatures are forbidden when `for<...>` is present + .label = `for<...>` is here + +hir_analysis_const_bound_for_non_const_trait = + ~const can only be applied to `#[const_trait]` traits + +hir_analysis_const_impl_for_non_const_trait = + const `impl` for trait `{$trait_name}` which is not marked with `#[const_trait]` + .suggestion = mark `{$trait_name}` as const + .note = marking a trait with `#[const_trait]` ensures all default method bodies are `const` + .adding = adding a non-const method body in the future would be a breaking change + +hir_analysis_const_param_ty_impl_on_non_adt = + the trait `ConstParamTy` may not be implemented for this type + .label = type is not a structure or enumeration + +hir_analysis_const_specialize = cannot specialize on const impl with non-const impl + +hir_analysis_copy_impl_on_non_adt = + the trait `Copy` cannot be implemented for this type + .label = type is not a structure or enumeration + +hir_analysis_copy_impl_on_type_with_dtor = + the trait `Copy` cannot be implemented for this type; the type has a destructor + .label = `Copy` not allowed on types with destructors + +hir_analysis_drop_impl_negative = negative `Drop` impls are not supported + hir_analysis_drop_impl_on_wrong_item = the `Drop` trait may only be implemented for local structs, enums, and unions .label = must be a struct, enum, or union in the current crate +hir_analysis_drop_impl_reservation = reservation `Drop` impls are not supported + +hir_analysis_empty_specialization = specialization impl does not specialize any associated items + .note = impl is a specialization of this impl + +hir_analysis_enum_discriminant_overflowed = enum discriminant overflowed + .label = overflowed on value after {$discr} + .note = explicitly set `{$item_name} = {$wrapped_discr}` if that is desired outcome + +hir_analysis_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)` + hir_analysis_field_already_declared = field `{$field_name}` is already declared .label = field already declared .previous_decl_label = `{$field_name}` first declared here -hir_analysis_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)` +hir_analysis_function_not_found_in_trait = function not found in this trait -hir_analysis_const_param_ty_impl_on_non_adt = - the trait `ConstParamTy` may not be implemented for this type - .label = type is not a structure or enumeration +hir_analysis_function_not_have_default_implementation = function doesn't have a default implementation + .note = required by this annotation -hir_analysis_ambiguous_lifetime_bound = - ambiguous lifetime bound, explicit lifetime bound required +hir_analysis_functions_names_duplicated = functions names are duplicated + .note = all `#[rustc_must_implement_one_of]` arguments must be unique -hir_analysis_assoc_type_binding_not_allowed = - associated type bindings are not allowed here - .label = associated type not allowed here +hir_analysis_impl_not_marked_default = `{$ident}` specializes an item from a parent `impl`, but that item is not marked `default` + .label = cannot specialize default item `{$ident}` + .ok_label = parent `impl` is here + .note = to specialize, `{$ident}` in the parent `impl` must be marked `default` -hir_analysis_parenthesized_fn_trait_expansion = - parenthesized trait syntax expands to `{$expanded_type}` +hir_analysis_impl_not_marked_default_err = `{$ident}` specializes an item from a parent `impl`, but that item is not marked `default` + .note = parent implementation is in crate `{$cname}` -hir_analysis_typeof_reserved_keyword_used = - `typeof` is a reserved keyword but unimplemented - .suggestion = consider replacing `typeof(...)` with an actual type - .label = reserved keyword +hir_analysis_invalid_union_field = + field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be used in a union + .note = union fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` -hir_analysis_value_of_associated_struct_already_specified = - the value of the associated type `{$item_name}` (from trait `{$def_path}`) is already specified - .label = re-bound here - .previous_bound_label = `{$item_name}` bound here first +hir_analysis_invalid_union_field_sugg = + wrap the field type in `ManuallyDrop<...>` -hir_analysis_unconstrained_opaque_type = unconstrained opaque type - .note = `{$name}` must be used in combination with a concrete type within the same {$what} +hir_analysis_late_bound_const_in_apit = `impl Trait` can only mention const parameters from an fn or impl + .label = const parameter declared here + +hir_analysis_late_bound_lifetime_in_apit = `impl Trait` can only mention lifetimes from an fn or impl + .label = lifetime declared here + +hir_analysis_late_bound_type_in_apit = `impl Trait` can only mention type parameters from an fn or impl + .label = type parameter declared here + +hir_analysis_lifetimes_or_bounds_mismatch_on_trait = + lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration + .label = lifetimes do not match {$item_kind} in trait + .generics_label = lifetimes in impl do not match this {$item_kind} in trait + .where_label = this `where` clause might not match the one in the trait + .bounds_label = this bound might be missing in the impl + +hir_analysis_linkage_type = + invalid type for variable with `#[linkage]` attribute + +hir_analysis_main_function_async = `main` function is not allowed to be `async` + .label = `main` function is not allowed to be `async` + +hir_analysis_main_function_generic_parameters = `main` function is not allowed to have generic parameters + .label = `main` cannot have generic parameters + +hir_analysis_main_function_return_type_generic = `main` function return type is not allowed to have generic parameters hir_analysis_manual_implementation = manual implementations of `{$trait_name}` are experimental .label = manual implementations of `{$trait_name}` are experimental .help = add `#![feature(unboxed_closures)]` to the crate attributes to enable -hir_analysis_substs_on_overridden_impl = could not resolve substs on overridden impl +hir_analysis_missing_one_of_trait_item = not all trait items implemented, missing one of: `{$missing_items_msg}` + .label = missing one of `{$missing_items_msg}` in implementation + .note = required because of this annotation -hir_analysis_trait_object_declared_with_no_traits = - at least one trait is required for an object type - .alias_span = this alias does not contain a trait +hir_analysis_missing_tilde_const = missing `~const` qualifier for specialization + +hir_analysis_missing_trait_item = not all trait items implemented, missing: `{$missing_items_msg}` + .label = missing `{$missing_items_msg}` in implementation + +hir_analysis_missing_trait_item_label = `{$item}` from trait + +hir_analysis_missing_trait_item_suggestion = implement the missing item: `{$snippet}` + +hir_analysis_missing_trait_item_unstable = not all trait items implemented, missing: `{$missing_item_name}` + .note = default implementation of `{$missing_item_name}` is unstable + .some_note = use of unstable library feature '{$feature}': {$reason} + .none_note = use of unstable library feature '{$feature}' hir_analysis_missing_type_params = the type {$parameterCount -> @@ -95,199 +174,147 @@ hir_analysis_missing_type_params = } to {$parameters} .note = because of the default `Self` reference, type parameters must be specified on object types -hir_analysis_copy_impl_on_type_with_dtor = - the trait `Copy` cannot be implemented for this type; the type has a destructor - .label = `Copy` not allowed on types with destructors - hir_analysis_multiple_relaxed_default_bounds = type parameter has more than one relaxed default bound, only one is supported -hir_analysis_copy_impl_on_non_adt = - the trait `Copy` cannot be implemented for this type - .label = type is not a structure or enumeration - -hir_analysis_const_impl_for_non_const_trait = - const `impl` for trait `{$trait_name}` which is not marked with `#[const_trait]` - .suggestion = mark `{$trait_name}` as const - .note = marking a trait with `#[const_trait]` ensures all default method bodies are `const` - .adding = adding a non-const method body in the future would be a breaking change - -hir_analysis_const_bound_for_non_const_trait = - ~const can only be applied to `#[const_trait]` traits - -hir_analysis_self_in_impl_self = - `Self` is not valid in the self type of an impl block - .note = replace `Self` with a different type - -hir_analysis_linkage_type = - invalid type for variable with `#[linkage]` attribute - -hir_analysis_auto_deref_reached_recursion_limit = reached the recursion limit while auto-dereferencing `{$ty}` - .label = deref recursion limit reached - .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) - -hir_analysis_where_clause_on_main = `main` function is not allowed to have a `where` clause - .label = `main` cannot have a `where` clause - -hir_analysis_track_caller_on_main = `main` function is not allowed to be `#[track_caller]` - .suggestion = remove this annotation - -hir_analysis_target_feature_on_main = `main` function is not allowed to have `#[target_feature]` - -hir_analysis_start_not_track_caller = `start` is not allowed to be `#[track_caller]` - .label = `start` is not allowed to be `#[track_caller]` - -hir_analysis_start_not_target_feature = `start` is not allowed to have `#[target_feature]` - .label = `start` is not allowed to have `#[target_feature]` - -hir_analysis_start_not_async = `start` is not allowed to be `async` - .label = `start` is not allowed to be `async` - -hir_analysis_start_function_where = start function is not allowed to have a `where` clause - .label = start function cannot have a `where` clause - -hir_analysis_start_function_parameters = start function is not allowed to have type parameters - .label = start function cannot have type parameters - -hir_analysis_main_function_return_type_generic = `main` function return type is not allowed to have generic parameters +hir_analysis_must_be_name_of_associated_function = must be a name of an associated function -hir_analysis_main_function_async = `main` function is not allowed to be `async` - .label = `main` function is not allowed to be `async` +hir_analysis_must_implement_not_function = not a function -hir_analysis_main_function_generic_parameters = `main` function is not allowed to have generic parameters - .label = `main` cannot have generic parameters +hir_analysis_must_implement_not_function_note = all `#[rustc_must_implement_one_of]` arguments must be associated function names -hir_analysis_variadic_function_compatible_convention = C-variadic function must have a compatible calling convention, like {$conventions} - .label = C-variadic function must have a compatible calling convention +hir_analysis_must_implement_not_function_span_note = required by this annotation -hir_analysis_cannot_capture_late_bound_ty_in_anon_const = - cannot capture late-bound type parameter in a constant - .label = parameter defined here +hir_analysis_must_implement_one_of_attribute = the `#[rustc_must_implement_one_of]` attribute must be used with at least 2 args -hir_analysis_cannot_capture_late_bound_const_in_anon_const = - cannot capture late-bound const parameter in a constant - .label = parameter defined here +hir_analysis_paren_sugar_attribute = the `#[rustc_paren_sugar]` attribute is a temporary means of controlling which traits can use parenthetical notation + .help = add `#![feature(unboxed_closures)]` to the crate attributes to use it -hir_analysis_variances_of = {$variances_of} +hir_analysis_parenthesized_fn_trait_expansion = + parenthesized trait syntax expands to `{$expanded_type}` hir_analysis_pass_to_variadic_function = can't pass `{$ty}` to variadic function .suggestion = cast the value to `{$cast_ty}` .help = cast the value to `{$cast_ty}` -hir_analysis_cast_thin_pointer_to_fat_pointer = cannot cast thin pointer `{$expr_ty}` to fat pointer `{$cast_ty}` - -hir_analysis_invalid_union_field = - field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be used in a union - .note = union fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` - -hir_analysis_invalid_union_field_sugg = - wrap the field type in `ManuallyDrop<...>` - -hir_analysis_return_type_notation_on_non_rpitit = - return type notation used on function that is not `async` and does not return `impl Trait` - .note = function returns `{$ty}`, which is not compatible with associated type return bounds - .label = this function must be `async` or return `impl Trait` - -hir_analysis_return_type_notation_equality_bound = - return type notation is not allowed to use type equality - -hir_analysis_return_type_notation_missing_method = - cannot find associated function `{$assoc_name}` for `{$ty_name}` +hir_analysis_placeholder_not_allowed_item_signatures = the placeholder `_` is not allowed within types on item signatures for {$kind} + .label = not allowed in type signatures hir_analysis_return_type_notation_conflicting_bound = ambiguous associated function `{$assoc_name}` for `{$ty_name}` .note = `{$assoc_name}` is declared in two supertraits: `{$first_bound}` and `{$second_bound}` -hir_analysis_placeholder_not_allowed_item_signatures = the placeholder `_` is not allowed within types on item signatures for {$kind} - .label = not allowed in type signatures - -hir_analysis_associated_type_trait_uninferred_generic_params = cannot use the associated type of a trait with uninferred generic parameters - .suggestion = use a fully qualified path with inferred lifetimes - -hir_analysis_associated_type_trait_uninferred_generic_params_multipart_suggestion = use a fully qualified path with explicit lifetimes +hir_analysis_return_type_notation_equality_bound = + return type notation is not allowed to use type equality -hir_analysis_enum_discriminant_overflowed = enum discriminant overflowed - .label = overflowed on value after {$discr} - .note = explicitly set `{$item_name} = {$wrapped_discr}` if that is desired outcome +hir_analysis_return_type_notation_illegal_param_const = + return type notation is not allowed for functions that have const parameters + .label = const parameter declared here +hir_analysis_return_type_notation_illegal_param_type = + return type notation is not allowed for functions that have type parameters + .label = type parameter declared here -hir_analysis_paren_sugar_attribute = the `#[rustc_paren_sugar]` attribute is a temporary means of controlling which traits can use parenthetical notation - .help = add `#![feature(unboxed_closures)]` to the crate attributes to use it +hir_analysis_return_type_notation_missing_method = + cannot find associated function `{$assoc_name}` for `{$ty_name}` -hir_analysis_must_implement_one_of_attribute = the `#[rustc_must_implement_one_of]` attribute must be used with at least 2 args +hir_analysis_return_type_notation_on_non_rpitit = + return type notation used on function that is not `async` and does not return `impl Trait` + .note = function returns `{$ty}`, which is not compatible with associated type return bounds + .label = this function must be `async` or return `impl Trait` -hir_analysis_must_be_name_of_associated_function = must be a name of an associated function +hir_analysis_self_in_impl_self = + `Self` is not valid in the self type of an impl block + .note = replace `Self` with a different type -hir_analysis_function_not_have_default_implementation = function doesn't have a default implementation - .note = required by this annotation +hir_analysis_simd_ffi_highly_experimental = use of SIMD type{$snip} in FFI is highly experimental and may result in invalid code + .help = add `#![feature(simd_ffi)]` to the crate attributes to enable -hir_analysis_must_implement_not_function = not a function +hir_analysis_specialization_trait = implementing `rustc_specialization_trait` traits is unstable + .help = add `#![feature(min_specialization)]` to the crate attributes to enable -hir_analysis_must_implement_not_function_span_note = required by this annotation +hir_analysis_start_function_parameters = start function is not allowed to have type parameters + .label = start function cannot have type parameters -hir_analysis_must_implement_not_function_note = all `#[rustc_must_implement_one_of]` arguments must be associated function names +hir_analysis_start_function_where = start function is not allowed to have a `where` clause + .label = start function cannot have a `where` clause -hir_analysis_function_not_found_in_trait = function not found in this trait +hir_analysis_start_not_async = `start` is not allowed to be `async` + .label = `start` is not allowed to be `async` -hir_analysis_functions_names_duplicated = functions names are duplicated - .note = all `#[rustc_must_implement_one_of]` arguments must be unique +hir_analysis_start_not_target_feature = `start` is not allowed to have `#[target_feature]` + .label = `start` is not allowed to have `#[target_feature]` -hir_analysis_simd_ffi_highly_experimental = use of SIMD type{$snip} in FFI is highly experimental and may result in invalid code - .help = add `#![feature(simd_ffi)]` to the crate attributes to enable +hir_analysis_start_not_track_caller = `start` is not allowed to be `#[track_caller]` + .label = `start` is not allowed to be `#[track_caller]` -hir_analysis_impl_not_marked_default = `{$ident}` specializes an item from a parent `impl`, but that item is not marked `default` - .label = cannot specialize default item `{$ident}` - .ok_label = parent `impl` is here - .note = to specialize, `{$ident}` in the parent `impl` must be marked `default` +hir_analysis_static_specialize = cannot specialize on `'static` lifetime -hir_analysis_impl_not_marked_default_err = `{$ident}` specializes an item from a parent `impl`, but that item is not marked `default` - .note = parent implementation is in crate `{$cname}` +hir_analysis_substs_on_overridden_impl = could not resolve substs on overridden impl -hir_analysis_missing_trait_item = not all trait items implemented, missing: `{$missing_items_msg}` - .label = missing `{$missing_items_msg}` in implementation +hir_analysis_tait_forward_compat = item constrains opaque type that is not in its signature + .note = this item must mention the opaque type in its signature in order to be able to register hidden types -hir_analysis_missing_trait_item_suggestion = implement the missing item: `{$snippet}` +hir_analysis_target_feature_on_main = `main` function is not allowed to have `#[target_feature]` -hir_analysis_missing_trait_item_label = `{$item}` from trait +hir_analysis_too_large_static = extern static is too large for the current architecture -hir_analysis_missing_one_of_trait_item = not all trait items implemented, missing one of: `{$missing_items_msg}` - .label = missing one of `{$missing_items_msg}` in implementation - .note = required because of this annotation +hir_analysis_track_caller_on_main = `main` function is not allowed to be `#[track_caller]` + .suggestion = remove this annotation -hir_analysis_missing_trait_item_unstable = not all trait items implemented, missing: `{$missing_item_name}` - .note = default implementation of `{$missing_item_name}` is unstable - .some_note = use of unstable library feature '{$feature}': {$r} - .none_note = use of unstable library feature '{$feature}' +hir_analysis_trait_object_declared_with_no_traits = + at least one trait is required for an object type + .alias_span = this alias does not contain a trait hir_analysis_transparent_enum_variant = transparent enum needs exactly one variant, but has {$number} .label = needs exactly one variant, but has {$number} .many_label = too many variants in `{$path}` .multi_label = variant here -hir_analysis_transparent_non_zero_sized_enum = the variant of a transparent {$desc} needs at most one non-zero-sized field, but has {$field_count} +hir_analysis_transparent_non_zero_sized = transparent {$desc} needs at most one non-zero-sized field, but has {$field_count} .label = needs at most one non-zero-sized field, but has {$field_count} .labels = this field is non-zero-sized -hir_analysis_transparent_non_zero_sized = transparent {$desc} needs at most one non-zero-sized field, but has {$field_count} +hir_analysis_transparent_non_zero_sized_enum = the variant of a transparent {$desc} needs at most one non-zero-sized field, but has {$field_count} .label = needs at most one non-zero-sized field, but has {$field_count} .labels = this field is non-zero-sized -hir_analysis_too_large_static = extern static is too large for the current architecture +hir_analysis_typeof_reserved_keyword_used = + `typeof` is a reserved keyword but unimplemented + .suggestion = consider replacing `typeof(...)` with an actual type + .label = reserved keyword -hir_analysis_specialization_trait = implementing `rustc_specialization_trait` traits is unstable - .help = add `#![feature(min_specialization)]` to the crate attributes to enable +hir_analysis_unconstrained_opaque_type = unconstrained opaque type + .note = `{$name}` must be used in combination with a concrete type within the same {$what} -hir_analysis_closure_implicit_hrtb = implicit types in closure signatures are forbidden when `for<...>` is present - .label = `for<...>` is here +hir_analysis_unrecognized_atomic_operation = + unrecognized atomic operation function: `{$op}` + .label = unrecognized atomic operation -hir_analysis_empty_specialization = specialization impl does not specialize any associated items - .note = impl is a specialization of this impl +hir_analysis_unrecognized_intrinsic_function = + unrecognized intrinsic function: `{$name}` + .label = unrecognized intrinsic -hir_analysis_const_specialize = cannot specialize on const impl with non-const impl +hir_analysis_unused_associated_type_bounds = + unnecessary associated type bound for not object safe associated type + .note = this associated type has a `where Self: Sized` bound. Thus, while the associated type can be specified, it cannot be used in any way, because trait objects are not `Sized`. + .suggestion = remove this bound -hir_analysis_static_specialize = cannot specialize on `'static` lifetime +hir_analysis_value_of_associated_struct_already_specified = + the value of the associated type `{$item_name}` (from trait `{$def_path}`) is already specified + .label = re-bound here + .previous_bound_label = `{$item_name}` bound here first -hir_analysis_missing_tilde_const = missing `~const` qualifier for specialization +hir_analysis_variadic_function_compatible_convention = C-variadic function must have a compatible calling convention, like {$conventions} + .label = C-variadic function must have a compatible calling convention -hir_analysis_drop_impl_negative = negative `Drop` impls are not supported +hir_analysis_variances_of = {$variances_of} -hir_analysis_drop_impl_reservation = reservation `Drop` impls are not supported +hir_analysis_where_clause_on_main = `main` function is not allowed to have a `where` clause + .label = `main` cannot have a `where` clause + +hir_analysis_wrong_number_of_generic_arguments_to_intrinsic = + intrinsic has wrong number of {$descr} parameters: found {$found}, expected {$expected} + .label = expected {$expected} {$descr} {$expected -> + [one] parameter + *[other] parameters + } diff --git a/compiler/rustc_hir_analysis/src/astconv/bounds.rs b/compiler/rustc_hir_analysis/src/astconv/bounds.rs new file mode 100644 index 0000000000000..ba152cd48dea4 --- /dev/null +++ b/compiler/rustc_hir_analysis/src/astconv/bounds.rs @@ -0,0 +1,584 @@ +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::struct_span_err; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_lint_defs::Applicability; +use rustc_middle::ty::{self as ty, Ty, TypeVisitableExt}; +use rustc_span::symbol::Ident; +use rustc_span::{ErrorGuaranteed, Span}; +use rustc_trait_selection::traits; + +use crate::astconv::{ + AstConv, ConvertedBinding, ConvertedBindingKind, OnlySelfBounds, PredicateFilter, +}; +use crate::bounds::Bounds; +use crate::errors; + +impl<'tcx> dyn AstConv<'tcx> + '_ { + /// Sets `implicitly_sized` to true on `Bounds` if necessary + pub(crate) fn add_implicitly_sized( + &self, + bounds: &mut Bounds<'tcx>, + self_ty: Ty<'tcx>, + ast_bounds: &'tcx [hir::GenericBound<'tcx>], + self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>, + span: Span, + ) { + let tcx = self.tcx(); + + // Try to find an unbound in bounds. + let mut unbound = None; + let mut search_bounds = |ast_bounds: &'tcx [hir::GenericBound<'tcx>]| { + for ab in ast_bounds { + if let hir::GenericBound::Trait(ptr, hir::TraitBoundModifier::Maybe) = ab { + if unbound.is_none() { + unbound = Some(&ptr.trait_ref); + } else { + tcx.sess.emit_err(errors::MultipleRelaxedDefaultBounds { span }); + } + } + } + }; + search_bounds(ast_bounds); + if let Some((self_ty, where_clause)) = self_ty_where_predicates { + for clause in where_clause { + if let hir::WherePredicate::BoundPredicate(pred) = clause { + if pred.is_param_bound(self_ty.to_def_id()) { + search_bounds(pred.bounds); + } + } + } + } + + let sized_def_id = tcx.lang_items().sized_trait(); + match (&sized_def_id, unbound) { + (Some(sized_def_id), Some(tpb)) + if tpb.path.res == Res::Def(DefKind::Trait, *sized_def_id) => + { + // There was in fact a `?Sized` bound, return without doing anything + return; + } + (_, Some(_)) => { + // There was a `?Trait` bound, but it was not `?Sized`; warn. + tcx.sess.span_warn( + span, + "default bound relaxed for a type parameter, but \ + this does nothing because the given bound is not \ + a default; only `?Sized` is supported", + ); + // Otherwise, add implicitly sized if `Sized` is available. + } + _ => { + // There was no `?Sized` bound; add implicitly sized if `Sized` is available. + } + } + if sized_def_id.is_none() { + // No lang item for `Sized`, so we can't add it as a bound. + return; + } + bounds.push_sized(tcx, self_ty, span); + } + + /// This helper takes a *converted* parameter type (`param_ty`) + /// and an *unconverted* list of bounds: + /// + /// ```text + /// fn foo + /// ^ ^^^^^ `ast_bounds` parameter, in HIR form + /// | + /// `param_ty`, in ty form + /// ``` + /// + /// It adds these `ast_bounds` into the `bounds` structure. + /// + /// **A note on binders:** there is an implied binder around + /// `param_ty` and `ast_bounds`. See `instantiate_poly_trait_ref` + /// for more details. + #[instrument(level = "debug", skip(self, ast_bounds, bounds))] + pub(crate) fn add_bounds<'hir, I: Iterator>>( + &self, + param_ty: Ty<'tcx>, + ast_bounds: I, + bounds: &mut Bounds<'tcx>, + bound_vars: &'tcx ty::List, + only_self_bounds: OnlySelfBounds, + ) { + for ast_bound in ast_bounds { + match ast_bound { + hir::GenericBound::Trait(poly_trait_ref, modifier) => { + let (constness, polarity) = match modifier { + hir::TraitBoundModifier::MaybeConst => { + (ty::BoundConstness::ConstIfConst, ty::ImplPolarity::Positive) + } + hir::TraitBoundModifier::None => { + (ty::BoundConstness::NotConst, ty::ImplPolarity::Positive) + } + hir::TraitBoundModifier::Negative => { + (ty::BoundConstness::NotConst, ty::ImplPolarity::Negative) + } + hir::TraitBoundModifier::Maybe => continue, + }; + let _ = self.instantiate_poly_trait_ref( + &poly_trait_ref.trait_ref, + poly_trait_ref.span, + constness, + polarity, + param_ty, + bounds, + false, + only_self_bounds, + ); + } + &hir::GenericBound::LangItemTrait(lang_item, span, hir_id, args) => { + self.instantiate_lang_item_trait_ref( + lang_item, + span, + hir_id, + args, + param_ty, + bounds, + only_self_bounds, + ); + } + hir::GenericBound::Outlives(lifetime) => { + let region = self.ast_region_to_region(lifetime, None); + bounds.push_region_bound( + self.tcx(), + ty::Binder::bind_with_vars( + ty::OutlivesPredicate(param_ty, region), + bound_vars, + ), + lifetime.ident.span, + ); + } + } + } + } + + /// Translates a list of bounds from the HIR into the `Bounds` data structure. + /// The self-type for the bounds is given by `param_ty`. + /// + /// Example: + /// + /// ```ignore (illustrative) + /// fn foo() { } + /// // ^ ^^^^^^^^^ ast_bounds + /// // param_ty + /// ``` + /// + /// The `sized_by_default` parameter indicates if, in this context, the `param_ty` should be + /// considered `Sized` unless there is an explicit `?Sized` bound. This would be true in the + /// example above, but is not true in supertrait listings like `trait Foo: Bar + Baz`. + /// + /// `span` should be the declaration size of the parameter. + pub(crate) fn compute_bounds( + &self, + param_ty: Ty<'tcx>, + ast_bounds: &[hir::GenericBound<'_>], + filter: PredicateFilter, + ) -> Bounds<'tcx> { + let mut bounds = Bounds::default(); + + let only_self_bounds = match filter { + PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => { + OnlySelfBounds(false) + } + PredicateFilter::SelfOnly | PredicateFilter::SelfThatDefines(_) => OnlySelfBounds(true), + }; + + self.add_bounds( + param_ty, + ast_bounds.iter().filter(|bound| { + match filter { + PredicateFilter::All + | PredicateFilter::SelfOnly + | PredicateFilter::SelfAndAssociatedTypeBounds => true, + PredicateFilter::SelfThatDefines(assoc_name) => { + if let Some(trait_ref) = bound.trait_ref() + && let Some(trait_did) = trait_ref.trait_def_id() + && self.tcx().trait_may_define_assoc_item(trait_did, assoc_name) + { + true + } else { + false + } + } + } + }), + &mut bounds, + ty::List::empty(), + only_self_bounds, + ); + debug!(?bounds); + + bounds + } + + /// Given an HIR binding like `Item = Foo` or `Item: Foo`, pushes the corresponding predicates + /// onto `bounds`. + /// + /// **A note on binders:** given something like `T: for<'a> Iterator`, the + /// `trait_ref` here will be `for<'a> T: Iterator`. The `binding` data however is from *inside* + /// the binder (e.g., `&'a u32`) and hence may reference bound regions. + #[instrument(level = "debug", skip(self, bounds, speculative, dup_bindings, path_span))] + pub(super) fn add_predicates_for_ast_type_binding( + &self, + hir_ref_id: hir::HirId, + trait_ref: ty::PolyTraitRef<'tcx>, + binding: &ConvertedBinding<'_, 'tcx>, + bounds: &mut Bounds<'tcx>, + speculative: bool, + dup_bindings: &mut FxHashMap, + path_span: Span, + constness: ty::BoundConstness, + only_self_bounds: OnlySelfBounds, + polarity: ty::ImplPolarity, + ) -> Result<(), ErrorGuaranteed> { + // Given something like `U: SomeTrait`, we want to produce a + // predicate like `::T = X`. This is somewhat + // subtle in the event that `T` is defined in a supertrait of + // `SomeTrait`, because in that case we need to upcast. + // + // That is, consider this case: + // + // ``` + // trait SubTrait: SuperTrait { } + // trait SuperTrait { type T; } + // + // ... B: SubTrait ... + // ``` + // + // We want to produce `>::T == foo`. + + let tcx = self.tcx(); + + let return_type_notation = + binding.gen_args.parenthesized == hir::GenericArgsParentheses::ReturnTypeNotation; + + let candidate = if return_type_notation { + if self.trait_defines_associated_item_named( + trait_ref.def_id(), + ty::AssocKind::Fn, + binding.item_name, + ) { + trait_ref + } else { + self.one_bound_for_assoc_method( + traits::supertraits(tcx, trait_ref), + trait_ref.print_only_trait_path(), + binding.item_name, + path_span, + )? + } + } else if self.trait_defines_associated_item_named( + trait_ref.def_id(), + ty::AssocKind::Type, + binding.item_name, + ) { + // Simple case: X is defined in the current trait. + trait_ref + } else { + // Otherwise, we have to walk through the supertraits to find + // those that do. + self.one_bound_for_assoc_type( + || traits::supertraits(tcx, trait_ref), + trait_ref.skip_binder().print_only_trait_name(), + binding.item_name, + path_span, + match binding.kind { + ConvertedBindingKind::Equality(term) => Some(term), + _ => None, + }, + )? + }; + + let (assoc_ident, def_scope) = + tcx.adjust_ident_and_get_scope(binding.item_name, candidate.def_id(), hir_ref_id); + + // We have already adjusted the item name above, so compare with `ident.normalize_to_macros_2_0()` instead + // of calling `filter_by_name_and_kind`. + let find_item_of_kind = |kind| { + tcx.associated_items(candidate.def_id()) + .filter_by_name_unhygienic(assoc_ident.name) + .find(|i| i.kind == kind && i.ident(tcx).normalize_to_macros_2_0() == assoc_ident) + }; + let assoc_item = if return_type_notation { + find_item_of_kind(ty::AssocKind::Fn) + } else { + find_item_of_kind(ty::AssocKind::Type) + .or_else(|| find_item_of_kind(ty::AssocKind::Const)) + } + .expect("missing associated type"); + + if !assoc_item.visibility(tcx).is_accessible_from(def_scope, tcx) { + tcx.sess + .struct_span_err( + binding.span, + format!("{} `{}` is private", assoc_item.kind, binding.item_name), + ) + .span_label(binding.span, format!("private {}", assoc_item.kind)) + .emit(); + } + tcx.check_stability(assoc_item.def_id, Some(hir_ref_id), binding.span, None); + + if !speculative { + dup_bindings + .entry(assoc_item.def_id) + .and_modify(|prev_span| { + tcx.sess.emit_err(errors::ValueOfAssociatedStructAlreadySpecified { + span: binding.span, + prev_span: *prev_span, + item_name: binding.item_name, + def_path: tcx.def_path_str(assoc_item.container_id(tcx)), + }); + }) + .or_insert(binding.span); + } + + let projection_ty = if return_type_notation { + let mut emitted_bad_param_err = false; + // If we have an method return type bound, then we need to substitute + // the method's early bound params with suitable late-bound params. + let mut num_bound_vars = candidate.bound_vars().len(); + let args = + candidate.skip_binder().args.extend_to(tcx, assoc_item.def_id, |param, _| { + let subst = match param.kind { + ty::GenericParamDefKind::Lifetime => ty::Region::new_late_bound( + tcx, + ty::INNERMOST, + ty::BoundRegion { + var: ty::BoundVar::from_usize(num_bound_vars), + kind: ty::BoundRegionKind::BrNamed(param.def_id, param.name), + }, + ) + .into(), + ty::GenericParamDefKind::Type { .. } => { + if !emitted_bad_param_err { + tcx.sess.emit_err( + crate::errors::ReturnTypeNotationIllegalParam::Type { + span: path_span, + param_span: tcx.def_span(param.def_id), + }, + ); + emitted_bad_param_err = true; + } + Ty::new_bound( + tcx, + ty::INNERMOST, + ty::BoundTy { + var: ty::BoundVar::from_usize(num_bound_vars), + kind: ty::BoundTyKind::Param(param.def_id, param.name), + }, + ) + .into() + } + ty::GenericParamDefKind::Const { .. } => { + if !emitted_bad_param_err { + tcx.sess.emit_err( + crate::errors::ReturnTypeNotationIllegalParam::Const { + span: path_span, + param_span: tcx.def_span(param.def_id), + }, + ); + emitted_bad_param_err = true; + } + let ty = tcx + .type_of(param.def_id) + .no_bound_vars() + .expect("ct params cannot have early bound vars"); + ty::Const::new_bound( + tcx, + ty::INNERMOST, + ty::BoundVar::from_usize(num_bound_vars), + ty, + ) + .into() + } + }; + num_bound_vars += 1; + subst + }); + + // Next, we need to check that the return-type notation is being used on + // an RPITIT (return-position impl trait in trait) or AFIT (async fn in trait). + let output = tcx.fn_sig(assoc_item.def_id).skip_binder().output(); + let output = if let ty::Alias(ty::Projection, alias_ty) = *output.skip_binder().kind() + && tcx.is_impl_trait_in_trait(alias_ty.def_id) + { + alias_ty + } else { + return Err(self.tcx().sess.emit_err( + crate::errors::ReturnTypeNotationOnNonRpitit { + span: binding.span, + ty: tcx.liberate_late_bound_regions(assoc_item.def_id, output), + fn_span: tcx.hir().span_if_local(assoc_item.def_id), + note: (), + }, + )); + }; + + // Finally, move the fn return type's bound vars over to account for the early bound + // params (and trait ref's late bound params). This logic is very similar to + // `Predicate::subst_supertrait`, and it's no coincidence why. + let shifted_output = tcx.shift_bound_var_indices(num_bound_vars, output); + let subst_output = ty::EarlyBinder::bind(shifted_output).instantiate(tcx, args); + + let bound_vars = tcx.late_bound_vars(binding.hir_id); + ty::Binder::bind_with_vars(subst_output, bound_vars) + } else { + // Include substitutions for generic parameters of associated types + candidate.map_bound(|trait_ref| { + let ident = Ident::new(assoc_item.name, binding.item_name.span); + let item_segment = hir::PathSegment { + ident, + hir_id: binding.hir_id, + res: Res::Err, + args: Some(binding.gen_args), + infer_args: false, + }; + + let args_trait_ref_and_assoc_item = self.create_args_for_associated_item( + path_span, + assoc_item.def_id, + &item_segment, + trait_ref.args, + ); + + debug!(?args_trait_ref_and_assoc_item); + + tcx.mk_alias_ty(assoc_item.def_id, args_trait_ref_and_assoc_item) + }) + }; + + if !speculative { + // Find any late-bound regions declared in `ty` that are not + // declared in the trait-ref or assoc_item. These are not well-formed. + // + // Example: + // + // for<'a> ::Item = &'a str // <-- 'a is bad + // for<'a> >::Output = &'a str // <-- 'a is ok + if let ConvertedBindingKind::Equality(ty) = binding.kind { + let late_bound_in_trait_ref = + tcx.collect_constrained_late_bound_regions(&projection_ty); + let late_bound_in_ty = + tcx.collect_referenced_late_bound_regions(&trait_ref.rebind(ty)); + debug!(?late_bound_in_trait_ref); + debug!(?late_bound_in_ty); + + // FIXME: point at the type params that don't have appropriate lifetimes: + // struct S1 Fn(&i32, &i32) -> &'a i32>(F); + // ---- ---- ^^^^^^^ + self.validate_late_bound_regions( + late_bound_in_trait_ref, + late_bound_in_ty, + |br_name| { + struct_span_err!( + tcx.sess, + binding.span, + E0582, + "binding for associated type `{}` references {}, \ + which does not appear in the trait input types", + binding.item_name, + br_name + ) + }, + ); + } + } + + let assoc_item_def_id = projection_ty.skip_binder().def_id; + let def_kind = tcx.def_kind(assoc_item_def_id); + match binding.kind { + ConvertedBindingKind::Equality(..) if return_type_notation => { + return Err(self.tcx().sess.emit_err( + crate::errors::ReturnTypeNotationEqualityBound { span: binding.span }, + )); + } + ConvertedBindingKind::Equality(mut term) => { + // "Desugar" a constraint like `T: Iterator` this to + // the "projection predicate" for: + // + // `::Item = u32` + match (def_kind, term.unpack()) { + (DefKind::AssocTy, ty::TermKind::Ty(_)) + | (DefKind::AssocConst, ty::TermKind::Const(_)) => (), + (_, _) => { + let got = if let Some(_) = term.ty() { "type" } else { "constant" }; + let expected = tcx.def_descr(assoc_item_def_id); + let mut err = tcx.sess.struct_span_err( + binding.span, + format!("expected {expected} bound, found {got}"), + ); + err.span_note( + tcx.def_span(assoc_item_def_id), + format!("{expected} defined here"), + ); + + if let DefKind::AssocConst = def_kind + && let Some(t) = term.ty() && (t.is_enum() || t.references_error()) + && tcx.features().associated_const_equality { + err.span_suggestion( + binding.span, + "if equating a const, try wrapping with braces", + format!("{} = {{ const }}", binding.item_name), + Applicability::HasPlaceholders, + ); + } + let reported = err.emit(); + term = match def_kind { + DefKind::AssocTy => Ty::new_error(tcx, reported).into(), + DefKind::AssocConst => ty::Const::new_error( + tcx, + reported, + tcx.type_of(assoc_item_def_id) + .instantiate(tcx, projection_ty.skip_binder().args), + ) + .into(), + _ => unreachable!(), + }; + } + } + bounds.push_projection_bound( + tcx, + projection_ty + .map_bound(|projection_ty| ty::ProjectionPredicate { projection_ty, term }), + binding.span, + ); + } + ConvertedBindingKind::Constraint(ast_bounds) => { + match def_kind { + DefKind::AssocTy => {} + _ => { + return Err(tcx.sess.emit_err(errors::AssocBoundOnConst { + span: assoc_ident.span, + descr: tcx.def_descr(assoc_item_def_id), + })); + } + } + // "Desugar" a constraint like `T: Iterator` to + // + // `::Item: Debug` + // + // Calling `skip_binder` is okay, because `add_bounds` expects the `param_ty` + // parameter to have a skipped binder. + // + // NOTE: If `only_self_bounds` is true, do NOT expand this associated + // type bound into a trait predicate, since we only want to add predicates + // for the `Self` type. + if !only_self_bounds.0 { + let param_ty = Ty::new_alias(tcx, ty::Projection, projection_ty.skip_binder()); + self.add_bounds( + param_ty, + ast_bounds.iter(), + bounds, + projection_ty.bound_vars(), + only_self_bounds, + ); + } + } + } + Ok(()) + } +} diff --git a/compiler/rustc_hir_analysis/src/astconv/errors.rs b/compiler/rustc_hir_analysis/src/astconv/errors.rs index 7b922f5d52599..bd311c98facfb 100644 --- a/compiler/rustc_hir_analysis/src/astconv/errors.rs +++ b/compiler/rustc_hir_analysis/src/astconv/errors.rs @@ -122,9 +122,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let all_candidate_names: Vec<_> = all_candidates() .flat_map(|r| self.tcx().associated_items(r.def_id()).in_definition_order()) - .filter_map( - |item| if item.kind == ty::AssocKind::Type { Some(item.name) } else { None }, - ) + .filter_map(|item| { + if !item.is_impl_trait_in_trait() && item.kind == ty::AssocKind::Type { + Some(item.name) + } else { + None + } + }) .collect(); if let (Some(suggested_name), true) = ( @@ -159,9 +163,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { .flat_map(|trait_def_id| { self.tcx().associated_items(*trait_def_id).in_definition_order() }) - .filter_map( - |item| if item.kind == ty::AssocKind::Type { Some(item.name) } else { None }, - ) + .filter_map(|item| { + if !item.is_impl_trait_in_trait() && item.kind == ty::AssocKind::Type { + Some(item.name) + } else { + None + } + }) .collect(); if let (Some(suggested_name), true) = ( @@ -189,7 +197,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } } - err.span_label(span, format!("associated type `{}` not found", assoc_name)); + err.span_label(span, format!("associated type `{assoc_name}` not found")); err.emit() } @@ -239,7 +247,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { "the candidate".into() }; - let impl_ty = tcx.at(span).type_of(impl_).subst_identity(); + let impl_ty = tcx.at(span).type_of(impl_).instantiate_identity(); let note = format!("{title} is defined in an impl for the type `{impl_ty}`"); if let Some(span) = note_span { @@ -287,7 +295,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let type_candidates = candidates .iter() .take(limit) - .map(|&(impl_, _)| format!("- `{}`", tcx.at(span).type_of(impl_).subst_identity())) + .map(|&(impl_, _)| { + format!("- `{}`", tcx.at(span).type_of(impl_).instantiate_identity()) + }) .collect::>() .join("\n"); let additional_types = if candidates.len() > limit { @@ -343,18 +353,18 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let format_pred = |pred: ty::Predicate<'tcx>| { let bound_predicate = pred.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred)) => { let pred = bound_predicate.rebind(pred); // `::Item = String`. let projection_ty = pred.skip_binder().projection_ty; - let substs_with_infer_self = tcx.mk_substs_from_iter( - std::iter::once(tcx.mk_ty_var(ty::TyVid::from_u32(0)).into()) - .chain(projection_ty.substs.iter().skip(1)), + let args_with_infer_self = tcx.mk_args_from_iter( + std::iter::once(Ty::new_var(tcx, ty::TyVid::from_u32(0)).into()) + .chain(projection_ty.args.iter().skip(1)), ); let quiet_projection_ty = - tcx.mk_alias_ty(projection_ty.def_id, substs_with_infer_self); + tcx.mk_alias_ty(projection_ty.def_id, args_with_infer_self); let term = pred.skip_binder().term; @@ -364,7 +374,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { bound_span_label(projection_ty.self_ty(), &obligation, &quiet); Some((obligation, projection_ty.self_ty())) } - ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_ref)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(poly_trait_ref)) => { let p = poly_trait_ref.trait_ref; let self_ty = p.self_ty(); let path = p.print_only_trait_path(); @@ -383,7 +393,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { .into_iter() .map(|error| error.root_obligation.predicate) .filter_map(format_pred) - .map(|(p, _)| format!("`{}`", p)) + .map(|(p, _)| format!("`{p}`")) .collect(); bounds.sort(); bounds.dedup(); @@ -642,7 +652,7 @@ pub(crate) fn fn_trait_to_string( } .map(|s| { // `s.empty()` checks to see if the type is the unit tuple, if so we don't want a comma - if parenthesized || s.is_empty() { format!("({})", s) } else { format!("({},)", s) } + if parenthesized || s.is_empty() { format!("({s})") } else { format!("({s},)") } }) .ok(), _ => None, diff --git a/compiler/rustc_hir_analysis/src/astconv/generics.rs b/compiler/rustc_hir_analysis/src/astconv/generics.rs index 39d1d1f2de52a..1372cc896be3f 100644 --- a/compiler/rustc_hir_analysis/src/astconv/generics.rs +++ b/compiler/rustc_hir_analysis/src/astconv/generics.rs @@ -11,7 +11,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::GenericArg; use rustc_middle::ty::{ - self, subst, subst::SubstsRef, GenericParamDef, GenericParamDefKind, IsSuggestable, Ty, TyCtxt, + self, GenericArgsRef, GenericParamDef, GenericParamDefKind, IsSuggestable, Ty, TyCtxt, }; use rustc_session::lint::builtin::LATE_BOUND_LIFETIME_ARGUMENTS; use rustc_span::{symbol::kw, Span}; @@ -76,12 +76,12 @@ fn generic_arg_mismatch_err( Res::Def(DefKind::TyParam, src_def_id) => { if let Some(param_local_id) = param.def_id.as_local() { let param_name = tcx.hir().ty_param_name(param_local_id); - let param_type = tcx.type_of(param.def_id).subst_identity(); + let param_type = tcx.type_of(param.def_id).instantiate_identity(); if param_type.is_suggestable(tcx, false) { err.span_suggestion( tcx.def_span(src_def_id), "consider changing this type parameter to a const parameter", - format!("const {}: {}", param_name, param_type), + format!("const {param_name}: {param_type}"), Applicability::MaybeIncorrect, ); }; @@ -102,7 +102,7 @@ fn generic_arg_mismatch_err( err.span_suggestion( arg.span(), "array type provided where a `usize` was expected, try", - format!("{{ {} }}", snippet), + format!("{{ {snippet} }}"), Applicability::MaybeIncorrect, ); } @@ -130,7 +130,7 @@ fn generic_arg_mismatch_err( } else { (arg.descr(), param.kind.descr()) }; - err.note(format!("{} arguments must be provided before {} arguments", first, last)); + err.note(format!("{first} arguments must be provided before {last} arguments")); if let Some(help) = help { err.help(help); } @@ -146,14 +146,14 @@ fn generic_arg_mismatch_err( /// /// To start, we are given the `def_id` of the thing we are /// creating the substitutions for, and a partial set of -/// substitutions `parent_substs`. In general, the substitutions +/// substitutions `parent_args`. In general, the substitutions /// for an item begin with substitutions for all the "parents" of /// that item -- e.g., for a method it might include the /// parameters from the impl. /// /// Therefore, the method begins by walking down these parents, /// starting with the outermost parent and proceed inwards until -/// it reaches `def_id`. For each parent `P`, it will check `parent_substs` +/// it reaches `def_id`. For each parent `P`, it will check `parent_args` /// first to see if the parent's substitutions are listed in there. If so, /// we can append those and move on. Otherwise, it invokes the /// three callback functions: @@ -168,15 +168,15 @@ fn generic_arg_mismatch_err( /// instantiate a `GenericArg`. /// - `inferred_kind`: if no parameter was provided, and inference is enabled, then /// creates a suitable inference variable. -pub fn create_substs_for_generic_args<'tcx, 'a>( +pub fn create_args_for_parent_generic_args<'tcx, 'a>( tcx: TyCtxt<'tcx>, def_id: DefId, - parent_substs: &[subst::GenericArg<'tcx>], + parent_args: &[ty::GenericArg<'tcx>], has_self: bool, self_ty: Option>, arg_count: &GenericArgCountResult, ctx: &mut impl CreateSubstsForGenericArgsCtxt<'a, 'tcx>, -) -> SubstsRef<'tcx> { +) -> GenericArgsRef<'tcx> { // Collect the segments of the path; we need to substitute arguments // for parameters throughout the entire path (wherever there are // generic parameters). @@ -191,27 +191,27 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( // We manually build up the substitution, rather than using convenience // methods in `subst.rs`, so that we can iterate over the arguments and // parameters in lock-step linearly, instead of trying to match each pair. - let mut substs: SmallVec<[subst::GenericArg<'tcx>; 8]> = SmallVec::with_capacity(count); + let mut args: SmallVec<[ty::GenericArg<'tcx>; 8]> = SmallVec::with_capacity(count); // Iterate over each segment of the path. while let Some((def_id, defs)) = stack.pop() { let mut params = defs.params.iter().peekable(); // If we have already computed substitutions for parents, we can use those directly. while let Some(¶m) = params.peek() { - if let Some(&kind) = parent_substs.get(param.index as usize) { - substs.push(kind); + if let Some(&kind) = parent_args.get(param.index as usize) { + args.push(kind); params.next(); } else { break; } } - // `Self` is handled first, unless it's been handled in `parent_substs`. + // `Self` is handled first, unless it's been handled in `parent_args`. if has_self { if let Some(¶m) = params.peek() { if param.index == 0 { if let GenericParamDefKind::Type { .. } = param.kind { - substs.push( + args.push( self_ty .map(|ty| ty.into()) .unwrap_or_else(|| ctx.inferred_kind(None, param, true)), @@ -226,7 +226,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( let (generic_args, infer_args) = ctx.args_for_def_id(def_id); let args_iter = generic_args.iter().flat_map(|generic_args| generic_args.args.iter()); - let mut args = args_iter.clone().peekable(); + let mut args_iter = args_iter.clone().peekable(); // If we encounter a type or const when we expect a lifetime, we infer the lifetimes. // If we later encounter a lifetime, we know that the arguments were provided in the @@ -239,7 +239,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( // provided, matching them with the generic parameters we expect. // Mismatches can occur as a result of elided lifetimes, or for malformed // input. We try to handle both sensibly. - match (args.peek(), params.peek()) { + match (args_iter.peek(), params.peek()) { (Some(&arg), Some(¶m)) => { match (arg, ¶m.kind, arg_count.explicit_late_bound) { (GenericArg::Lifetime(_), GenericParamDefKind::Lifetime, _) @@ -253,8 +253,8 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( GenericParamDefKind::Const { .. }, _, ) => { - substs.push(ctx.provided_kind(param, arg)); - args.next(); + args.push(ctx.provided_kind(param, arg)); + args_iter.next(); params.next(); } ( @@ -264,7 +264,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( ) => { // We expected a lifetime argument, but got a type or const // argument. That means we're inferring the lifetimes. - substs.push(ctx.inferred_kind(None, param, infer_args)); + args.push(ctx.inferred_kind(None, param, infer_args)); force_infer_lt = Some((arg, param)); params.next(); } @@ -273,7 +273,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( // the presence of explicit late bounds. This is most likely // due to the presence of the explicit bound so we're just going to // ignore it. - args.next(); + args_iter.next(); } (_, _, _) => { // We expected one kind of parameter, but the user provided @@ -304,7 +304,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( "reorder the arguments: {}: `<{}>`", param_types_present .into_iter() - .map(|ord| format!("{}s", ord)) + .map(|ord| format!("{ord}s")) .collect::>() .join(", then "), ordered_params @@ -327,7 +327,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( // errors. In this case, we're simply going to ignore the argument // and any following arguments. The rest of the parameters will be // inferred. - while args.next().is_some() {} + while args_iter.next().is_some() {} } } } @@ -360,7 +360,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( (None, Some(¶m)) => { // If there are fewer arguments than parameters, it means // we're inferring the remaining arguments. - substs.push(ctx.inferred_kind(Some(&substs), param, infer_args)); + args.push(ctx.inferred_kind(Some(&args), param, infer_args)); params.next(); } @@ -369,7 +369,7 @@ pub fn create_substs_for_generic_args<'tcx, 'a>( } } - tcx.mk_substs(&substs) + tcx.mk_args(&args) } /// Checks that the correct number of generic arguments have been provided. diff --git a/compiler/rustc_hir_analysis/src/astconv/lint.rs b/compiler/rustc_hir_analysis/src/astconv/lint.rs new file mode 100644 index 0000000000000..1bd1270beaf8e --- /dev/null +++ b/compiler/rustc_hir_analysis/src/astconv/lint.rs @@ -0,0 +1,124 @@ +use rustc_ast::TraitObjectSyntax; +use rustc_errors::{Diagnostic, StashKey}; +use rustc_hir as hir; +use rustc_lint_defs::{builtin::BARE_TRAIT_OBJECTS, Applicability}; +use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName; + +use super::AstConv; + +impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { + /// Make sure that we are in the condition to suggest the blanket implementation. + pub(super) fn maybe_lint_blanket_trait_impl( + &self, + self_ty: &hir::Ty<'_>, + diag: &mut Diagnostic, + ) { + let tcx = self.tcx(); + let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id; + if let hir::Node::Item(hir::Item { + kind: + hir::ItemKind::Impl(hir::Impl { + self_ty: impl_self_ty, of_trait: Some(of_trait_ref), generics, .. + }), + .. + }) = tcx.hir().get_by_def_id(parent_id) && self_ty.hir_id == impl_self_ty.hir_id + { + if !of_trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) { + return; + } + let of_trait_span = of_trait_ref.path.span; + // make sure that we are not calling unwrap to abort during the compilation + let Ok(impl_trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else { return; }; + let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else { return; }; + // check if the trait has generics, to make a correct suggestion + let param_name = generics.params.next_type_param_name(None); + + let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() { + (span, format!(", {param_name}: {impl_trait_name}")) + } else { + (generics.span, format!("<{param_name}: {impl_trait_name}>")) + }; + diag.multipart_suggestion( + format!("alternatively use a blanket \ + implementation to implement `{of_trait_name}` for \ + all types that also implement `{impl_trait_name}`"), + vec![ + (self_ty.span, param_name), + add_generic_sugg, + ], + Applicability::MaybeIncorrect, + ); + } + } + + pub(super) fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) { + let tcx = self.tcx(); + if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) = + self_ty.kind + { + let needs_bracket = in_path + && !tcx + .sess + .source_map() + .span_to_prev_source(self_ty.span) + .ok() + .is_some_and(|s| s.trim_end().ends_with('<')); + + let is_global = poly_trait_ref.trait_ref.path.is_global(); + + let mut sugg = Vec::from_iter([( + self_ty.span.shrink_to_lo(), + format!( + "{}dyn {}", + if needs_bracket { "<" } else { "" }, + if is_global { "(" } else { "" }, + ), + )]); + + if is_global || needs_bracket { + sugg.push(( + self_ty.span.shrink_to_hi(), + format!( + "{}{}", + if is_global { ")" } else { "" }, + if needs_bracket { ">" } else { "" }, + ), + )); + } + + if self_ty.span.edition().at_least_rust_2021() { + let msg = "trait objects must include the `dyn` keyword"; + let label = "add `dyn` keyword before this trait"; + let mut diag = + rustc_errors::struct_span_err!(tcx.sess, self_ty.span, E0782, "{}", msg); + if self_ty.span.can_be_used_for_suggestions() { + diag.multipart_suggestion_verbose( + label, + sugg, + Applicability::MachineApplicable, + ); + } + // check if the impl trait that we are considering is a impl of a local trait + self.maybe_lint_blanket_trait_impl(&self_ty, &mut diag); + diag.stash(self_ty.span, StashKey::TraitMissingMethod); + } else { + let msg = "trait objects without an explicit `dyn` are deprecated"; + tcx.struct_span_lint_hir( + BARE_TRAIT_OBJECTS, + self_ty.hir_id, + self_ty.span, + msg, + |lint| { + lint.multipart_suggestion_verbose( + "use `dyn`", + sugg, + Applicability::MachineApplicable, + ); + self.maybe_lint_blanket_trait_impl(&self_ty, lint); + lint + }, + ); + } + } + } +} diff --git a/compiler/rustc_hir_analysis/src/astconv/mod.rs b/compiler/rustc_hir_analysis/src/astconv/mod.rs index 5fb06cf94652e..668763f9bf60d 100644 --- a/compiler/rustc_hir_analysis/src/astconv/mod.rs +++ b/compiler/rustc_hir_analysis/src/astconv/mod.rs @@ -2,53 +2,46 @@ //! The main routine here is `ast_ty_to_ty()`; each use is parameterized by an //! instance of `AstConv`. +mod bounds; mod errors; pub mod generics; +mod lint; +mod object_safety; use crate::astconv::errors::prohibit_assoc_ty_binding; -use crate::astconv::generics::{check_generic_arg_count, create_substs_for_generic_args}; +use crate::astconv::generics::{check_generic_arg_count, create_args_for_parent_generic_args}; use crate::bounds::Bounds; use crate::collect::HirPlaceholderCollector; -use crate::errors::{ - AmbiguousLifetimeBound, MultipleRelaxedDefaultBounds, TraitObjectDeclaredWithNoTraits, - TypeofReservedKeywordUsed, ValueOfAssociatedStructAlreadySpecified, -}; +use crate::errors::{AmbiguousLifetimeBound, TypeofReservedKeywordUsed}; use crate::middle::resolve_bound_vars as rbv; use crate::require_c_abi_if_c_variadic; use rustc_ast::TraitObjectSyntax; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::{ struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, FatalError, - MultiSpan, StashKey, + MultiSpan, }; use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind, Namespace, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{walk_generics, Visitor as _}; use rustc_hir::{GenericArg, GenericArgs, OpaqueTyOrigin}; -use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; -use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; +use rustc_infer::infer::{InferCtxt, InferOk, TyCtxtInferExt}; use rustc_infer::traits::ObligationCause; -use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; use rustc_middle::middle::stability::AllowUnstable; -use rustc_middle::ty::fold::FnMutDelegate; -use rustc_middle::ty::subst::{self, GenericArgKind, InternalSubsts, SubstsRef}; use rustc_middle::ty::GenericParamDefKind; -use rustc_middle::ty::{self, Const, IsSuggestable, Ty, TyCtxt, TypeVisitableExt}; -use rustc_middle::ty::{DynKind, ToPredicate}; -use rustc_session::lint::builtin::{AMBIGUOUS_ASSOCIATED_ITEMS, BARE_TRAIT_OBJECTS}; +use rustc_middle::ty::{ + self, Const, GenericArgKind, GenericArgsRef, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, +}; +use rustc_session::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::symbol::{kw, Ident, Symbol}; use rustc_span::{sym, Span, DUMMY_SP}; use rustc_target::spec::abi; -use rustc_trait_selection::traits::error_reporting::{ - report_object_safety_error, suggestions::NextTypeParamName, -}; use rustc_trait_selection::traits::wf::object_region_bounds; -use rustc_trait_selection::traits::{self, astconv_object_safety_violations, ObligationCtxt}; +use rustc_trait_selection::traits::{self, NormalizeExt, ObligationCtxt}; +use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; -use smallvec::{smallvec, SmallVec}; -use std::collections::BTreeSet; use std::fmt::Display; use std::slice; @@ -58,6 +51,24 @@ pub struct PathSeg(pub DefId, pub usize); #[derive(Copy, Clone, Debug)] pub struct OnlySelfBounds(pub bool); +#[derive(Copy, Clone, Debug)] +pub enum PredicateFilter { + /// All predicates may be implied by the trait. + All, + + /// Only traits that reference `Self: ..` are implied by the trait. + SelfOnly, + + /// Only traits that reference `Self: ..` and define an associated type + /// with the given ident are implied by the trait. + SelfThatDefines(Ident), + + /// Only traits that reference `Self: ..` and their associated type bounds. + /// For example, given `Self: Tr`, this would expand to `Self: Tr` + /// and `::A: B`. + SelfAndAssociatedTypeBounds, +} + pub trait AstConv<'tcx> { fn tcx(&self) -> TyCtxt<'tcx>; @@ -210,14 +221,14 @@ pub trait CreateSubstsForGenericArgsCtxt<'a, 'tcx> { &mut self, param: &ty::GenericParamDef, arg: &GenericArg<'_>, - ) -> subst::GenericArg<'tcx>; + ) -> ty::GenericArg<'tcx>; fn inferred_kind( &mut self, - substs: Option<&[subst::GenericArg<'tcx>]>, + args: Option<&[ty::GenericArg<'tcx>]>, param: &ty::GenericParamDef, infer_args: bool, - ) -> subst::GenericArg<'tcx>; + ) -> ty::GenericArg<'tcx>; } impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { @@ -239,7 +250,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { var: ty::BoundVar::from_u32(index), kind: ty::BrNamed(def_id, name), }; - tcx.mk_re_late_bound(debruijn, br) + ty::Region::new_late_bound(tcx, debruijn, br) } Some(rbv::ResolvedArg::EarlyBound(def_id)) => { @@ -247,12 +258,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let item_def_id = tcx.hir().ty_param_owner(def_id.expect_local()); let generics = tcx.generics_of(item_def_id); let index = generics.param_def_id_to_index[&def_id]; - tcx.mk_re_early_bound(ty::EarlyBoundRegion { def_id, index, name }) + ty::Region::new_early_bound(tcx, ty::EarlyBoundRegion { def_id, index, name }) } Some(rbv::ResolvedArg::Free(scope, id)) => { let name = lifetime_name(id.expect_local()); - tcx.mk_re_free(scope, ty::BrNamed(id, name)) + ty::Region::new_free(tcx, scope, ty::BrNamed(id, name)) // (*) -- not late-bound, won't change } @@ -269,7 +280,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { // elision. `resolve_lifetime` should have // reported an error in this case -- but if // not, let's error out. - tcx.mk_re_error_with_message( + ty::Region::new_error_with_message( + tcx, lifetime.ident.span, "unelided lifetime in signature", ) @@ -280,13 +292,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { /// Given a path `path` that refers to an item `I` with the declared generics `decl_generics`, /// returns an appropriate set of substitutions for this particular reference to `I`. - pub fn ast_path_substs_for_ty( + pub fn ast_path_args_for_ty( &self, span: Span, def_id: DefId, item_segment: &hir::PathSegment<'_>, - ) -> SubstsRef<'tcx> { - let (substs, _) = self.create_substs_for_ast_path( + ) -> GenericArgsRef<'tcx> { + let (args, _) = self.create_args_for_ast_path( span, def_id, &[], @@ -300,7 +312,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { prohibit_assoc_ty_binding(self.tcx(), b.span, Some((item_segment, span))); } - substs + args } /// Given the type/lifetime/const arguments provided to some path (along with @@ -319,7 +331,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { /// 2. The path in question is the path to the trait `std::ops::Index`, /// which will have been resolved to a `def_id` /// 3. The `generic_args` contains info on the `<...>` contents. The `usize` type - /// parameters are returned in the `SubstsRef`, the associated type bindings like + /// parameters are returned in the `GenericArgsRef`, the associated type bindings like /// `Output = u32` are returned from `create_assoc_bindings_for_generic_args`. /// /// Note that the type listing given here is *exactly* what the user provided. @@ -330,22 +342,22 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { /// as Iterable>::Iter::<'a> /// ``` /// - /// We have the parent substs are the substs for the parent trait: + /// We have the parent args are the args for the parent trait: /// `[Vec, u8]` and `generic_args` are the arguments for the associated - /// type itself: `['a]`. The returned `SubstsRef` concatenates these two + /// type itself: `['a]`. The returned `GenericArgsRef` concatenates these two /// lists: `[Vec, u8, 'a]`. #[instrument(level = "debug", skip(self, span), ret)] - fn create_substs_for_ast_path<'a>( + fn create_args_for_ast_path<'a>( &self, span: Span, def_id: DefId, - parent_substs: &[subst::GenericArg<'tcx>], + parent_args: &[ty::GenericArg<'tcx>], seg: &hir::PathSegment<'_>, generic_args: &'a hir::GenericArgs<'_>, infer_args: bool, self_ty: Option>, constness: ty::BoundConstness, - ) -> (SubstsRef<'tcx>, GenericArgCountResult) { + ) -> (GenericArgsRef<'tcx>, GenericArgCountResult) { // If the type is parameterized by this region, then replace this // region with the current anon region binding (in other words, // whatever & would get replaced with). @@ -358,7 +370,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { if generics.parent.is_some() { // The parent is a trait so it should have at least one subst // for the `Self` type. - assert!(!parent_substs.is_empty()) + assert!(!parent_args.is_empty()) } else { // This item (presumably a trait) needs a self-type. assert!(self_ty.is_some()); @@ -384,7 +396,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { // here and so associated type bindings will be handled regardless of whether there are any // non-`Self` generic parameters. if generics.params.is_empty() { - return (tcx.mk_substs(parent_substs), arg_count); + return (tcx.mk_args(parent_args), arg_count); } struct SubstsForAstPathCtxt<'a, 'tcx> { @@ -410,7 +422,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { &mut self, param: &ty::GenericParamDef, arg: &GenericArg<'_>, - ) -> subst::GenericArg<'tcx> { + ) -> ty::GenericArg<'tcx> { let tcx = self.astconv.tcx(); let mut handle_ty_args = |has_default, ty: &hir::Ty<'_>| { @@ -432,7 +444,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } if let (hir::TyKind::Infer, false) = (&ty.kind, self.astconv.allow_ty_infer()) { self.inferred_params.push(ty.span); - tcx.ty_error_misc().into() + Ty::new_misc_error(tcx).into() } else { self.astconv.ast_ty_to_ty(ty).into() } @@ -463,7 +475,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { self.astconv.ct_infer(ty, Some(param), inf.span).into() } else { self.inferred_params.push(inf.span); - tcx.const_error_misc(ty).into() + ty::Const::new_misc_error(tcx, ty).into() } } _ => unreachable!(), @@ -472,10 +484,10 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { fn inferred_kind( &mut self, - substs: Option<&[subst::GenericArg<'tcx>]>, + args: Option<&[ty::GenericArg<'tcx>]>, param: &ty::GenericParamDef, infer_args: bool, - ) -> subst::GenericArg<'tcx> { + ) -> ty::GenericArg<'tcx> { let tcx = self.astconv.tcx(); match param.kind { GenericParamDefKind::Lifetime => self @@ -485,7 +497,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { debug!(?param, "unelided lifetime in signature"); // This indicates an illegal lifetime in a non-assoc-trait position - tcx.mk_re_error_with_message( + ty::Region::new_error_with_message( + tcx, self.span, "unelided lifetime in signature", ) @@ -494,20 +507,20 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { GenericParamDefKind::Type { has_default, .. } => { if !infer_args && has_default { // No type parameter provided, but a default exists. - let substs = substs.unwrap(); - if substs.iter().any(|arg| match arg.unpack() { + let args = args.unwrap(); + if args.iter().any(|arg| match arg.unpack() { GenericArgKind::Type(ty) => ty.references_error(), _ => false, }) { // Avoid ICE #86756 when type error recovery goes awry. - return tcx.ty_error_misc().into(); + return Ty::new_misc_error(tcx).into(); } - tcx.at(self.span).type_of(param.def_id).subst(tcx, substs).into() + tcx.at(self.span).type_of(param.def_id).instantiate(tcx, args).into() } else if infer_args { self.astconv.ty_infer(Some(param), self.span).into() } else { // We've already errored above about the mismatch. - tcx.ty_error_misc().into() + Ty::new_misc_error(tcx).into() } } GenericParamDefKind::Const { has_default } => { @@ -517,16 +530,19 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { .no_bound_vars() .expect("const parameter types cannot be generic"); if let Err(guar) = ty.error_reported() { - return tcx.const_error(ty, guar).into(); + return ty::Const::new_error(tcx, guar, ty).into(); } + // FIXME(effects) see if we should special case effect params here if !infer_args && has_default { - tcx.const_param_default(param.def_id).subst(tcx, substs.unwrap()).into() + tcx.const_param_default(param.def_id) + .instantiate(tcx, args.unwrap()) + .into() } else { if infer_args { self.astconv.ct_infer(ty, Some(param), self.span).into() } else { // We've already errored above about the mismatch. - tcx.const_error_misc(ty).into() + ty::Const::new_misc_error(tcx, ty).into() } } } @@ -534,7 +550,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } } - let mut substs_ctx = SubstsForAstPathCtxt { + let mut args_ctx = SubstsForAstPathCtxt { astconv: self, def_id, span, @@ -542,14 +558,14 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { inferred_params: vec![], infer_args, }; - let substs = create_substs_for_generic_args( + let args = create_args_for_parent_generic_args( tcx, def_id, - parent_substs, + parent_args, self_ty.is_some(), self_ty, &arg_count, - &mut substs_ctx, + &mut args_ctx, ); if let ty::BoundConstness::ConstIfConst = constness @@ -558,7 +574,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { tcx.sess.emit_err(crate::errors::ConstBoundForNonConstTrait { span } ); } - (substs, arg_count) + (args, arg_count) } fn create_assoc_bindings_for_generic_args<'a>( @@ -605,21 +621,21 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { assoc_bindings } - pub fn create_substs_for_associated_item( + pub fn create_args_for_associated_item( &self, span: Span, item_def_id: DefId, item_segment: &hir::PathSegment<'_>, - parent_substs: SubstsRef<'tcx>, - ) -> SubstsRef<'tcx> { + parent_args: GenericArgsRef<'tcx>, + ) -> GenericArgsRef<'tcx> { debug!( - "create_substs_for_associated_item(span: {:?}, item_def_id: {:?}, item_segment: {:?}", + "create_args_for_associated_item(span: {:?}, item_def_id: {:?}, item_segment: {:?}", span, item_def_id, item_segment ); - let (args, _) = self.create_substs_for_ast_path( + let (args, _) = self.create_args_for_ast_path( span, item_def_id, - parent_substs, + parent_args, item_segment, item_segment.args(), item_segment.infer_args, @@ -644,7 +660,6 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { &self, trait_ref: &hir::TraitRef<'_>, self_ty: Ty<'tcx>, - constness: ty::BoundConstness, ) -> ty::TraitRef<'tcx> { self.prohibit_generics(trait_ref.path.segments.split_last().unwrap().1.iter(), |_| {}); @@ -654,7 +669,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { self_ty, trait_ref.path.segments.last().unwrap(), true, - constness, + ty::BoundConstness::NotConst, ) } @@ -675,7 +690,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { self_ty: Ty<'tcx>, only_self_bounds: OnlySelfBounds, ) -> GenericArgCountResult { - let (substs, arg_count) = self.create_substs_for_ast_path( + let (generic_args, arg_count) = self.create_args_for_ast_path( trait_ref_span, trait_def_id, &[], @@ -692,11 +707,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let assoc_bindings = self.create_assoc_bindings_for_generic_args(args); - let poly_trait_ref = - ty::Binder::bind_with_vars(ty::TraitRef::new(tcx, trait_def_id, substs), bound_vars); + let poly_trait_ref = ty::Binder::bind_with_vars( + ty::TraitRef::new(tcx, trait_def_id, generic_args), + bound_vars, + ); debug!(?poly_trait_ref, ?assoc_bindings); - bounds.push_trait_bound(tcx, poly_trait_ref, span, constness, polarity); + bounds.push_trait_bound(tcx, poly_trait_ref, span, polarity); let mut dup_bindings = FxHashMap::default(); for binding in &assoc_bindings { @@ -832,9 +849,10 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { self_ty: Ty<'tcx>, trait_segment: &hir::PathSegment<'_>, is_impl: bool, + // FIXME(effects) move all host param things in astconv to hir lowering constness: ty::BoundConstness, ) -> ty::TraitRef<'tcx> { - let (substs, _) = self.create_substs_for_ast_trait_ref( + let (generic_args, _) = self.create_args_for_ast_trait_ref( span, trait_def_id, self_ty, @@ -845,11 +863,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { if let Some(b) = trait_segment.args().bindings.first() { prohibit_assoc_ty_binding(self.tcx(), b.span, Some((trait_segment, span))); } - ty::TraitRef::new(self.tcx(), trait_def_id, substs) + ty::TraitRef::new(self.tcx(), trait_def_id, generic_args) } #[instrument(level = "debug", skip(self, span))] - fn create_substs_for_ast_trait_ref<'a>( + fn create_args_for_ast_trait_ref<'a>( &self, span: Span, trait_def_id: DefId, @@ -857,10 +875,10 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { trait_segment: &'a hir::PathSegment<'a>, is_impl: bool, constness: ty::BoundConstness, - ) -> (SubstsRef<'tcx>, GenericArgCountResult) { + ) -> (GenericArgsRef<'tcx>, GenericArgCountResult) { self.complain_about_internal_fn_trait(span, trait_def_id, trait_segment, is_impl); - self.create_substs_for_ast_path( + self.create_args_for_ast_path( span, trait_def_id, &[], @@ -884,936 +902,28 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { .is_some() } - /// Sets `implicitly_sized` to true on `Bounds` if necessary - pub(crate) fn add_implicitly_sized( - &self, - bounds: &mut Bounds<'tcx>, - self_ty: Ty<'tcx>, - ast_bounds: &'tcx [hir::GenericBound<'tcx>], - self_ty_where_predicates: Option<(LocalDefId, &'tcx [hir::WherePredicate<'tcx>])>, - span: Span, - ) { - let tcx = self.tcx(); - - // Try to find an unbound in bounds. - let mut unbound = None; - let mut search_bounds = |ast_bounds: &'tcx [hir::GenericBound<'tcx>]| { - for ab in ast_bounds { - if let hir::GenericBound::Trait(ptr, hir::TraitBoundModifier::Maybe) = ab { - if unbound.is_none() { - unbound = Some(&ptr.trait_ref); - } else { - tcx.sess.emit_err(MultipleRelaxedDefaultBounds { span }); - } - } - } - }; - search_bounds(ast_bounds); - if let Some((self_ty, where_clause)) = self_ty_where_predicates { - for clause in where_clause { - if let hir::WherePredicate::BoundPredicate(pred) = clause { - if pred.is_param_bound(self_ty.to_def_id()) { - search_bounds(pred.bounds); - } - } - } - } - - let sized_def_id = tcx.lang_items().sized_trait(); - match (&sized_def_id, unbound) { - (Some(sized_def_id), Some(tpb)) - if tpb.path.res == Res::Def(DefKind::Trait, *sized_def_id) => - { - // There was in fact a `?Sized` bound, return without doing anything - return; - } - (_, Some(_)) => { - // There was a `?Trait` bound, but it was not `?Sized`; warn. - tcx.sess.span_warn( - span, - "default bound relaxed for a type parameter, but \ - this does nothing because the given bound is not \ - a default; only `?Sized` is supported", - ); - // Otherwise, add implicitly sized if `Sized` is available. - } - _ => { - // There was no `?Sized` bound; add implicitly sized if `Sized` is available. - } - } - if sized_def_id.is_none() { - // No lang item for `Sized`, so we can't add it as a bound. - return; - } - bounds.push_sized(tcx, self_ty, span); - } - - /// This helper takes a *converted* parameter type (`param_ty`) - /// and an *unconverted* list of bounds: - /// - /// ```text - /// fn foo - /// ^ ^^^^^ `ast_bounds` parameter, in HIR form - /// | - /// `param_ty`, in ty form - /// ``` - /// - /// It adds these `ast_bounds` into the `bounds` structure. - /// - /// **A note on binders:** there is an implied binder around - /// `param_ty` and `ast_bounds`. See `instantiate_poly_trait_ref` - /// for more details. - #[instrument(level = "debug", skip(self, ast_bounds, bounds))] - pub(crate) fn add_bounds<'hir, I: Iterator>>( - &self, - param_ty: Ty<'tcx>, - ast_bounds: I, - bounds: &mut Bounds<'tcx>, - bound_vars: &'tcx ty::List, - only_self_bounds: OnlySelfBounds, - ) { - for ast_bound in ast_bounds { - match ast_bound { - hir::GenericBound::Trait(poly_trait_ref, modifier) => { - let (constness, polarity) = match modifier { - hir::TraitBoundModifier::MaybeConst => { - (ty::BoundConstness::ConstIfConst, ty::ImplPolarity::Positive) - } - hir::TraitBoundModifier::None => { - (ty::BoundConstness::NotConst, ty::ImplPolarity::Positive) - } - hir::TraitBoundModifier::Negative => { - (ty::BoundConstness::NotConst, ty::ImplPolarity::Negative) - } - hir::TraitBoundModifier::Maybe => continue, - }; - let _ = self.instantiate_poly_trait_ref( - &poly_trait_ref.trait_ref, - poly_trait_ref.span, - constness, - polarity, - param_ty, - bounds, - false, - only_self_bounds, - ); - } - &hir::GenericBound::LangItemTrait(lang_item, span, hir_id, args) => { - self.instantiate_lang_item_trait_ref( - lang_item, - span, - hir_id, - args, - param_ty, - bounds, - only_self_bounds, - ); - } - hir::GenericBound::Outlives(lifetime) => { - let region = self.ast_region_to_region(lifetime, None); - bounds.push_region_bound( - self.tcx(), - ty::Binder::bind_with_vars( - ty::OutlivesPredicate(param_ty, region), - bound_vars, - ), - lifetime.ident.span, - ); - } - } - } - } - - /// Translates a list of bounds from the HIR into the `Bounds` data structure. - /// The self-type for the bounds is given by `param_ty`. - /// - /// Example: - /// - /// ```ignore (illustrative) - /// fn foo() { } - /// // ^ ^^^^^^^^^ ast_bounds - /// // param_ty - /// ``` - /// - /// The `sized_by_default` parameter indicates if, in this context, the `param_ty` should be - /// considered `Sized` unless there is an explicit `?Sized` bound. This would be true in the - /// example above, but is not true in supertrait listings like `trait Foo: Bar + Baz`. - /// - /// `span` should be the declaration size of the parameter. - pub(crate) fn compute_bounds( - &self, - param_ty: Ty<'tcx>, - ast_bounds: &[hir::GenericBound<'_>], - only_self_bounds: OnlySelfBounds, - ) -> Bounds<'tcx> { - let mut bounds = Bounds::default(); - self.add_bounds( - param_ty, - ast_bounds.iter(), - &mut bounds, - ty::List::empty(), - only_self_bounds, - ); - debug!(?bounds); - - bounds - } - - /// Convert the bounds in `ast_bounds` that refer to traits which define an associated type - /// named `assoc_name` into ty::Bounds. Ignore the rest. - pub(crate) fn compute_bounds_that_match_assoc_item( - &self, - param_ty: Ty<'tcx>, - ast_bounds: &[hir::GenericBound<'_>], - assoc_name: Ident, - ) -> Bounds<'tcx> { - let mut result = Vec::new(); - - for ast_bound in ast_bounds { - if let Some(trait_ref) = ast_bound.trait_ref() - && let Some(trait_did) = trait_ref.trait_def_id() - && self.tcx().trait_may_define_assoc_item(trait_did, assoc_name) - { - result.push(ast_bound.clone()); - } - } - - let mut bounds = Bounds::default(); - self.add_bounds( - param_ty, - result.iter(), - &mut bounds, - ty::List::empty(), - OnlySelfBounds(true), - ); - debug!(?bounds); - - bounds - } - - /// Given an HIR binding like `Item = Foo` or `Item: Foo`, pushes the corresponding predicates - /// onto `bounds`. - /// - /// **A note on binders:** given something like `T: for<'a> Iterator`, the - /// `trait_ref` here will be `for<'a> T: Iterator`. The `binding` data however is from *inside* - /// the binder (e.g., `&'a u32`) and hence may reference bound regions. - #[instrument(level = "debug", skip(self, bounds, speculative, dup_bindings, path_span))] - fn add_predicates_for_ast_type_binding( - &self, - hir_ref_id: hir::HirId, - trait_ref: ty::PolyTraitRef<'tcx>, - binding: &ConvertedBinding<'_, 'tcx>, - bounds: &mut Bounds<'tcx>, - speculative: bool, - dup_bindings: &mut FxHashMap, - path_span: Span, - constness: ty::BoundConstness, - only_self_bounds: OnlySelfBounds, - polarity: ty::ImplPolarity, - ) -> Result<(), ErrorGuaranteed> { - // Given something like `U: SomeTrait`, we want to produce a - // predicate like `::T = X`. This is somewhat - // subtle in the event that `T` is defined in a supertrait of - // `SomeTrait`, because in that case we need to upcast. - // - // That is, consider this case: - // - // ``` - // trait SubTrait: SuperTrait { } - // trait SuperTrait { type T; } - // - // ... B: SubTrait ... - // ``` - // - // We want to produce `>::T == foo`. - - let tcx = self.tcx(); - - let return_type_notation = - binding.gen_args.parenthesized == hir::GenericArgsParentheses::ReturnTypeNotation; - - let candidate = if return_type_notation { - if self.trait_defines_associated_item_named( - trait_ref.def_id(), - ty::AssocKind::Fn, - binding.item_name, - ) { - trait_ref - } else { - self.one_bound_for_assoc_method( - traits::supertraits(tcx, trait_ref), - trait_ref.print_only_trait_path(), - binding.item_name, - path_span, - )? - } - } else if self.trait_defines_associated_item_named( - trait_ref.def_id(), - ty::AssocKind::Type, - binding.item_name, - ) { - // Simple case: X is defined in the current trait. - trait_ref - } else { - // Otherwise, we have to walk through the supertraits to find - // those that do. - self.one_bound_for_assoc_type( - || traits::supertraits(tcx, trait_ref), - trait_ref.print_only_trait_path(), - binding.item_name, - path_span, - match binding.kind { - ConvertedBindingKind::Equality(term) => Some(term), - _ => None, - }, - )? - }; - - let (assoc_ident, def_scope) = - tcx.adjust_ident_and_get_scope(binding.item_name, candidate.def_id(), hir_ref_id); - - // We have already adjusted the item name above, so compare with `ident.normalize_to_macros_2_0()` instead - // of calling `filter_by_name_and_kind`. - let find_item_of_kind = |kind| { - tcx.associated_items(candidate.def_id()) - .filter_by_name_unhygienic(assoc_ident.name) - .find(|i| i.kind == kind && i.ident(tcx).normalize_to_macros_2_0() == assoc_ident) - }; - let assoc_item = if return_type_notation { - find_item_of_kind(ty::AssocKind::Fn) - } else { - find_item_of_kind(ty::AssocKind::Type) - .or_else(|| find_item_of_kind(ty::AssocKind::Const)) - } - .expect("missing associated type"); - - if !assoc_item.visibility(tcx).is_accessible_from(def_scope, tcx) { - tcx.sess - .struct_span_err( - binding.span, - format!("{} `{}` is private", assoc_item.kind, binding.item_name), - ) - .span_label(binding.span, format!("private {}", assoc_item.kind)) - .emit(); - } - tcx.check_stability(assoc_item.def_id, Some(hir_ref_id), binding.span, None); - - if !speculative { - dup_bindings - .entry(assoc_item.def_id) - .and_modify(|prev_span| { - tcx.sess.emit_err(ValueOfAssociatedStructAlreadySpecified { - span: binding.span, - prev_span: *prev_span, - item_name: binding.item_name, - def_path: tcx.def_path_str(assoc_item.container_id(tcx)), - }); - }) - .or_insert(binding.span); - } - - let projection_ty = if return_type_notation { - // If we have an method return type bound, then we need to substitute - // the method's early bound params with suitable late-bound params. - let mut num_bound_vars = candidate.bound_vars().len(); - let substs = - candidate.skip_binder().substs.extend_to(tcx, assoc_item.def_id, |param, _| { - let subst = match param.kind { - GenericParamDefKind::Lifetime => tcx - .mk_re_late_bound( - ty::INNERMOST, - ty::BoundRegion { - var: ty::BoundVar::from_usize(num_bound_vars), - kind: ty::BoundRegionKind::BrNamed(param.def_id, param.name), - }, - ) - .into(), - GenericParamDefKind::Type { .. } => tcx - .mk_bound( - ty::INNERMOST, - ty::BoundTy { - var: ty::BoundVar::from_usize(num_bound_vars), - kind: ty::BoundTyKind::Param(param.def_id, param.name), - }, - ) - .into(), - GenericParamDefKind::Const { .. } => { - let ty = tcx - .type_of(param.def_id) - .no_bound_vars() - .expect("ct params cannot have early bound vars"); - tcx.mk_const( - ty::ConstKind::Bound( - ty::INNERMOST, - ty::BoundVar::from_usize(num_bound_vars), - ), - ty, - ) - .into() - } - }; - num_bound_vars += 1; - subst - }); - - // Next, we need to check that the return-type notation is being used on - // an RPITIT (return-position impl trait in trait) or AFIT (async fn in trait). - let output = tcx.fn_sig(assoc_item.def_id).skip_binder().output(); - let output = if let ty::Alias(ty::Projection, alias_ty) = *output.skip_binder().kind() - && tcx.def_kind(alias_ty.def_id) == DefKind::ImplTraitPlaceholder - { - alias_ty - } else { - return Err(self.tcx().sess.emit_err( - crate::errors::ReturnTypeNotationOnNonRpitit { - span: binding.span, - ty: tcx.liberate_late_bound_regions(assoc_item.def_id, output), - fn_span: tcx.hir().span_if_local(assoc_item.def_id), - note: (), - }, - )); - }; - - // Finally, move the fn return type's bound vars over to account for the early bound - // params (and trait ref's late bound params). This logic is very similar to - // `Predicate::subst_supertrait`, and it's no coincidence why. - let shifted_output = tcx.shift_bound_var_indices(num_bound_vars, output); - let subst_output = ty::EarlyBinder(shifted_output).subst(tcx, substs); - - let bound_vars = tcx.late_bound_vars(binding.hir_id); - ty::Binder::bind_with_vars(subst_output, bound_vars) - } else { - // Include substitutions for generic parameters of associated types - candidate.map_bound(|trait_ref| { - let ident = Ident::new(assoc_item.name, binding.item_name.span); - let item_segment = hir::PathSegment { - ident, - hir_id: binding.hir_id, - res: Res::Err, - args: Some(binding.gen_args), - infer_args: false, - }; - - let substs_trait_ref_and_assoc_item = self.create_substs_for_associated_item( - path_span, - assoc_item.def_id, - &item_segment, - trait_ref.substs, - ); - - debug!(?substs_trait_ref_and_assoc_item); - - tcx.mk_alias_ty(assoc_item.def_id, substs_trait_ref_and_assoc_item) - }) - }; - - if !speculative { - // Find any late-bound regions declared in `ty` that are not - // declared in the trait-ref or assoc_item. These are not well-formed. - // - // Example: - // - // for<'a> ::Item = &'a str // <-- 'a is bad - // for<'a> >::Output = &'a str // <-- 'a is ok - if let ConvertedBindingKind::Equality(ty) = binding.kind { - let late_bound_in_trait_ref = - tcx.collect_constrained_late_bound_regions(&projection_ty); - let late_bound_in_ty = - tcx.collect_referenced_late_bound_regions(&trait_ref.rebind(ty)); - debug!(?late_bound_in_trait_ref); - debug!(?late_bound_in_ty); - - // FIXME: point at the type params that don't have appropriate lifetimes: - // struct S1 Fn(&i32, &i32) -> &'a i32>(F); - // ---- ---- ^^^^^^^ - self.validate_late_bound_regions( - late_bound_in_trait_ref, - late_bound_in_ty, - |br_name| { - struct_span_err!( - tcx.sess, - binding.span, - E0582, - "binding for associated type `{}` references {}, \ - which does not appear in the trait input types", - binding.item_name, - br_name - ) - }, - ); - } - } - - match binding.kind { - ConvertedBindingKind::Equality(..) if return_type_notation => { - return Err(self.tcx().sess.emit_err( - crate::errors::ReturnTypeNotationEqualityBound { span: binding.span }, - )); - } - ConvertedBindingKind::Equality(mut term) => { - // "Desugar" a constraint like `T: Iterator` this to - // the "projection predicate" for: - // - // `::Item = u32` - let assoc_item_def_id = projection_ty.skip_binder().def_id; - let def_kind = tcx.def_kind(assoc_item_def_id); - match (def_kind, term.unpack()) { - (hir::def::DefKind::AssocTy, ty::TermKind::Ty(_)) - | (hir::def::DefKind::AssocConst, ty::TermKind::Const(_)) => (), - (_, _) => { - let got = if let Some(_) = term.ty() { "type" } else { "constant" }; - let expected = tcx.def_descr(assoc_item_def_id); - let mut err = tcx.sess.struct_span_err( - binding.span, - format!("expected {expected} bound, found {got}"), - ); - err.span_note( - tcx.def_span(assoc_item_def_id), - format!("{expected} defined here"), - ); - - if let hir::def::DefKind::AssocConst = def_kind - && let Some(t) = term.ty() && (t.is_enum() || t.references_error()) - && tcx.features().associated_const_equality { - err.span_suggestion( - binding.span, - "if equating a const, try wrapping with braces", - format!("{} = {{ const }}", binding.item_name), - Applicability::HasPlaceholders, - ); - } - let reported = err.emit(); - term = match def_kind { - hir::def::DefKind::AssocTy => tcx.ty_error(reported).into(), - hir::def::DefKind::AssocConst => tcx - .const_error( - tcx.type_of(assoc_item_def_id) - .subst(tcx, projection_ty.skip_binder().substs), - reported, - ) - .into(), - _ => unreachable!(), - }; - } - } - bounds.push_projection_bound( - tcx, - projection_ty - .map_bound(|projection_ty| ty::ProjectionPredicate { projection_ty, term }), - binding.span, - ); - } - ConvertedBindingKind::Constraint(ast_bounds) => { - // "Desugar" a constraint like `T: Iterator` to - // - // `::Item: Debug` - // - // Calling `skip_binder` is okay, because `add_bounds` expects the `param_ty` - // parameter to have a skipped binder. - // - // NOTE: If `only_self_bounds` is true, do NOT expand this associated - // type bound into a trait predicate, since we only want to add predicates - // for the `Self` type. - if !only_self_bounds.0 { - let param_ty = tcx.mk_alias(ty::Projection, projection_ty.skip_binder()); - self.add_bounds( - param_ty, - ast_bounds.iter(), - bounds, - projection_ty.bound_vars(), - only_self_bounds, - ); - } - } - } - Ok(()) - } - fn ast_path_to_ty( &self, span: Span, did: DefId, item_segment: &hir::PathSegment<'_>, - ) -> Ty<'tcx> { - let substs = self.ast_path_substs_for_ty(span, did, item_segment); - self.tcx().at(span).type_of(did).subst(self.tcx(), substs) - } - - fn conv_object_ty_poly_trait_ref( - &self, - span: Span, - hir_trait_bounds: &[hir::PolyTraitRef<'_>], - lifetime: &hir::Lifetime, - borrowed: bool, - representation: DynKind, ) -> Ty<'tcx> { let tcx = self.tcx(); + let args = self.ast_path_args_for_ty(span, did, item_segment); + let ty = tcx.at(span).type_of(did); - let mut bounds = Bounds::default(); - let mut potential_assoc_types = Vec::new(); - let dummy_self = self.tcx().types.trait_object_dummy_self; - for trait_bound in hir_trait_bounds.iter().rev() { - if let GenericArgCountResult { - correct: - Err(GenericArgCountMismatch { invalid_args: cur_potential_assoc_types, .. }), - .. - } = self.instantiate_poly_trait_ref( - &trait_bound.trait_ref, - trait_bound.span, - ty::BoundConstness::NotConst, - ty::ImplPolarity::Positive, - dummy_self, - &mut bounds, - false, - // FIXME: This should be `true`, but we don't really handle - // associated type bounds or type aliases in objects in a way - // that makes this meaningful, I think. - OnlySelfBounds(false), - ) { - potential_assoc_types.extend(cur_potential_assoc_types); - } - } - - let mut trait_bounds = vec![]; - let mut projection_bounds = vec![]; - for (pred, span) in bounds.predicates() { - let bound_pred = pred.kind(); - match bound_pred.skip_binder() { - ty::PredicateKind::Clause(clause) => match clause { - ty::Clause::Trait(trait_pred) => { - assert_eq!(trait_pred.polarity, ty::ImplPolarity::Positive); - trait_bounds.push(( - bound_pred.rebind(trait_pred.trait_ref), - span, - trait_pred.constness, - )); - } - ty::Clause::Projection(proj) => { - projection_bounds.push((bound_pred.rebind(proj), span)); - } - ty::Clause::TypeOutlives(_) => { - // Do nothing, we deal with regions separately - } - ty::Clause::RegionOutlives(_) | ty::Clause::ConstArgHasType(..) => bug!(), - }, - ty::PredicateKind::WellFormed(_) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::ObjectSafe(_) - | ty::PredicateKind::ClosureKind(_, _, _) - | ty::PredicateKind::Subtype(_) - | ty::PredicateKind::Coerce(_) - | ty::PredicateKind::ConstEvaluatable(_) - | ty::PredicateKind::ConstEquate(_, _) - | ty::PredicateKind::TypeWellFormedFromEnv(_) - | ty::PredicateKind::Ambiguous => bug!(), - } - } - - // Expand trait aliases recursively and check that only one regular (non-auto) trait - // is used and no 'maybe' bounds are used. - let expanded_traits = - traits::expand_trait_aliases(tcx, trait_bounds.iter().map(|&(a, b, _)| (a, b))); - - let (mut auto_traits, regular_traits): (Vec<_>, Vec<_>) = expanded_traits - .filter(|i| i.trait_ref().self_ty().skip_binder() == dummy_self) - .partition(|i| tcx.trait_is_auto(i.trait_ref().def_id())); - if regular_traits.len() > 1 { - let first_trait = ®ular_traits[0]; - let additional_trait = ®ular_traits[1]; - let mut err = struct_span_err!( - tcx.sess, - additional_trait.bottom().1, - E0225, - "only auto traits can be used as additional traits in a trait object" - ); - additional_trait.label_with_exp_info( - &mut err, - "additional non-auto trait", - "additional use", - ); - first_trait.label_with_exp_info(&mut err, "first non-auto trait", "first use"); - err.help(format!( - "consider creating a new trait with all of these as supertraits and using that \ - trait here instead: `trait NewTrait: {} {{}}`", - regular_traits - .iter() - .map(|t| t.trait_ref().print_only_trait_path().to_string()) - .collect::>() - .join(" + "), - )); - err.note( - "auto-traits like `Send` and `Sync` are traits that have special properties; \ - for more information on them, visit \ - ", - ); - err.emit(); - } - - if regular_traits.is_empty() && auto_traits.is_empty() { - let trait_alias_span = trait_bounds - .iter() - .map(|&(trait_ref, _, _)| trait_ref.def_id()) - .find(|&trait_ref| tcx.is_trait_alias(trait_ref)) - .map(|trait_ref| tcx.def_span(trait_ref)); - let reported = - tcx.sess.emit_err(TraitObjectDeclaredWithNoTraits { span, trait_alias_span }); - return tcx.ty_error(reported); - } - - // Check that there are no gross object safety violations; - // most importantly, that the supertraits don't contain `Self`, - // to avoid ICEs. - for item in ®ular_traits { - let object_safety_violations = - astconv_object_safety_violations(tcx, item.trait_ref().def_id()); - if !object_safety_violations.is_empty() { - let reported = report_object_safety_error( - tcx, - span, - item.trait_ref().def_id(), - &object_safety_violations, - ) - .emit(); - return tcx.ty_error(reported); - } - } - - // Use a `BTreeSet` to keep output in a more consistent order. - let mut associated_types: FxHashMap> = FxHashMap::default(); - - let regular_traits_refs_spans = trait_bounds - .into_iter() - .filter(|(trait_ref, _, _)| !tcx.trait_is_auto(trait_ref.def_id())); - - for (base_trait_ref, span, constness) in regular_traits_refs_spans { - assert_eq!(constness, ty::BoundConstness::NotConst); - let base_pred: ty::Predicate<'tcx> = base_trait_ref.to_predicate(tcx); - for pred in traits::elaborate(tcx, [base_pred]) { - debug!("conv_object_ty_poly_trait_ref: observing object predicate `{:?}`", pred); - - let bound_predicate = pred.kind(); - match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { - let pred = bound_predicate.rebind(pred); - associated_types.entry(span).or_default().extend( - tcx.associated_items(pred.def_id()) - .in_definition_order() - .filter(|item| item.kind == ty::AssocKind::Type) - .filter(|item| tcx.opt_rpitit_info(item.def_id).is_none()) - .map(|item| item.def_id), - ); - } - ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { - let pred = bound_predicate.rebind(pred); - // A `Self` within the original bound will be substituted with a - // `trait_object_dummy_self`, so check for that. - let references_self = match pred.skip_binder().term.unpack() { - ty::TermKind::Ty(ty) => ty.walk().any(|arg| arg == dummy_self.into()), - ty::TermKind::Const(c) => { - c.ty().walk().any(|arg| arg == dummy_self.into()) - } - }; - - // If the projection output contains `Self`, force the user to - // elaborate it explicitly to avoid a lot of complexity. - // - // The "classically useful" case is the following: - // ``` - // trait MyTrait: FnMut() -> ::MyOutput { - // type MyOutput; - // } - // ``` - // - // Here, the user could theoretically write `dyn MyTrait`, - // but actually supporting that would "expand" to an infinitely-long type - // `fix $ τ → dyn MyTrait::MyOutput`. - // - // Instead, we force the user to write - // `dyn MyTrait`, which is uglier but works. See - // the discussion in #56288 for alternatives. - if !references_self { - // Include projections defined on supertraits. - projection_bounds.push((pred, span)); - } - } - _ => (), - } - } - } - - for (projection_bound, _) in &projection_bounds { - for def_ids in associated_types.values_mut() { - def_ids.remove(&projection_bound.projection_def_id()); - } - } - - self.complain_about_missing_associated_types( - associated_types, - potential_assoc_types, - hir_trait_bounds, - ); - - // De-duplicate auto traits so that, e.g., `dyn Trait + Send + Send` is the same as - // `dyn Trait + Send`. - // We remove duplicates by inserting into a `FxHashSet` to avoid re-ordering - // the bounds - let mut duplicates = FxHashSet::default(); - auto_traits.retain(|i| duplicates.insert(i.trait_ref().def_id())); - debug!("regular_traits: {:?}", regular_traits); - debug!("auto_traits: {:?}", auto_traits); - - // Erase the `dummy_self` (`trait_object_dummy_self`) used above. - let existential_trait_refs = regular_traits.iter().map(|i| { - i.trait_ref().map_bound(|trait_ref: ty::TraitRef<'tcx>| { - assert_eq!(trait_ref.self_ty(), dummy_self); - - // Verify that `dummy_self` did not leak inside default type parameters. This - // could not be done at path creation, since we need to see through trait aliases. - let mut missing_type_params = vec![]; - let mut references_self = false; - let generics = tcx.generics_of(trait_ref.def_id); - let substs: Vec<_> = trait_ref - .substs - .iter() - .enumerate() - .skip(1) // Remove `Self` for `ExistentialPredicate`. - .map(|(index, arg)| { - if arg == dummy_self.into() { - let param = &generics.params[index]; - missing_type_params.push(param.name); - return tcx.ty_error_misc().into(); - } else if arg.walk().any(|arg| arg == dummy_self.into()) { - references_self = true; - return tcx.ty_error_misc().into(); - } - arg - }) - .collect(); - let substs = tcx.mk_substs(&substs); - - let span = i.bottom().1; - let empty_generic_args = hir_trait_bounds.iter().any(|hir_bound| { - hir_bound.trait_ref.path.res == Res::Def(DefKind::Trait, trait_ref.def_id) - && hir_bound.span.contains(span) - }); - self.complain_about_missing_type_params( - missing_type_params, - trait_ref.def_id, - span, - empty_generic_args, - ); - - if references_self { - let def_id = i.bottom().0.def_id(); - let mut err = struct_span_err!( - tcx.sess, - i.bottom().1, - E0038, - "the {} `{}` cannot be made into an object", - tcx.def_descr(def_id), - tcx.item_name(def_id), - ); - err.note( - rustc_middle::traits::ObjectSafetyViolation::SupertraitSelf(smallvec![]) - .error_msg(), - ); - err.emit(); - } - - ty::ExistentialTraitRef { def_id: trait_ref.def_id, substs } - }) - }); - - let existential_projections = projection_bounds - .iter() - // We filter out traits that don't have `Self` as their self type above, - // we need to do the same for projections. - .filter(|(bound, _)| bound.skip_binder().self_ty() == dummy_self) - .map(|(bound, _)| { - bound.map_bound(|mut b| { - assert_eq!(b.projection_ty.self_ty(), dummy_self); - - // Like for trait refs, verify that `dummy_self` did not leak inside default type - // parameters. - let references_self = b.projection_ty.substs.iter().skip(1).any(|arg| { - if arg.walk().any(|arg| arg == dummy_self.into()) { - return true; - } - false - }); - if references_self { - let guar = tcx.sess.delay_span_bug( - span, - "trait object projection bounds reference `Self`", - ); - let substs: Vec<_> = b - .projection_ty - .substs - .iter() - .map(|arg| { - if arg.walk().any(|arg| arg == dummy_self.into()) { - return tcx.ty_error(guar).into(); - } - arg - }) - .collect(); - b.projection_ty.substs = tcx.mk_substs(&substs); - } - - ty::ExistentialProjection::erase_self_ty(tcx, b) - }) - }); - - let regular_trait_predicates = existential_trait_refs - .map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait)); - let auto_trait_predicates = auto_traits.into_iter().map(|trait_ref| { - ty::Binder::dummy(ty::ExistentialPredicate::AutoTrait(trait_ref.trait_ref().def_id())) - }); - // N.b. principal, projections, auto traits - // FIXME: This is actually wrong with multiple principals in regards to symbol mangling - let mut v = regular_trait_predicates - .chain( - existential_projections.map(|x| x.map_bound(ty::ExistentialPredicate::Projection)), - ) - .chain(auto_trait_predicates) - .collect::>(); - v.sort_by(|a, b| a.skip_binder().stable_cmp(tcx, &b.skip_binder())); - v.dedup(); - let existential_predicates = tcx.mk_poly_existential_predicates(&v); - - // Use explicitly-specified region bound. - let region_bound = if !lifetime.is_elided() { - self.ast_region_to_region(lifetime, None) + if let DefKind::TyAlias { lazy } = tcx.def_kind(did) + && (lazy || ty.skip_binder().has_opaque_types()) + { + // Type aliases referring to types that contain opaque types (but aren't just directly + // referencing a single opaque type) as well as those defined in crates that have the + // feature `lazy_type_alias` enabled get encoded as a type alias that normalization will + // then actually instantiate the where bounds of. + let alias_ty = tcx.mk_alias_ty(did, args); + Ty::new_alias(tcx, ty::Weak, alias_ty) } else { - self.compute_object_lifetime_bound(span, existential_predicates).unwrap_or_else(|| { - if tcx.named_bound_var(lifetime.hir_id).is_some() { - self.ast_region_to_region(lifetime, None) - } else { - self.re_infer(None, span).unwrap_or_else(|| { - let mut err = struct_span_err!( - tcx.sess, - span, - E0228, - "the lifetime bound for this object type cannot be deduced \ - from context; please supply an explicit bound" - ); - let e = if borrowed { - // We will have already emitted an error E0106 complaining about a - // missing named lifetime in `&dyn Trait`, so we elide this one. - err.delay_as_bug() - } else { - err.emit() - }; - tcx.mk_re_error(e) - }) - } - }) - }; - debug!("region_bound: {:?}", region_bound); - - let ty = tcx.mk_dynamic(existential_predicates, region_bound, representation); - debug!("trait_object_type: {:?}", ty); - ty + ty.instantiate(tcx, args) + } } fn report_ambiguous_associated_type( @@ -1948,9 +1058,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { || { traits::transitive_bounds_that_define_assoc_item( tcx, - predicates.iter().filter_map(|(p, _)| { - Some(p.to_opt_poly_trait_pred()?.map_bound(|t| t.trait_ref)) - }), + predicates + .iter() + .filter_map(|(p, _)| Some(p.as_trait_clause()?.map_bound(|t| t.trait_ref))), assoc_name, ) }, @@ -2021,7 +1131,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { ty_param_name ) }; - err.span_label(span, format!("ambiguous associated type `{}`", assoc_name)); + err.span_label(span, format!("ambiguous associated type `{assoc_name}`")); let mut where_bounds = vec![]; for bound in bounds { @@ -2165,9 +1275,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { "you might have meant to specify type parameters on enum \ `{type_name}`" ); - let Some(args) = assoc_segment.args else { return; }; + let Some(args) = assoc_segment.args else { + return; + }; // Get the span of the generics args *including* the leading `::`. - let args_span = assoc_segment.ident.span.shrink_to_hi().to(args.span_ext); + let args_span = + assoc_segment.ident.span.shrink_to_hi().to(args.span_ext); if tcx.generics_of(adt_def.did()).count() == 0 { // FIXME(estebank): we could also verify that the arguments being // work for the `enum`, instead of just looking if it takes *any*. @@ -2179,49 +1292,56 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { ); return; } - let Ok(snippet) = tcx.sess.source_map().span_to_snippet(args_span) else { + let Ok(snippet) = tcx.sess.source_map().span_to_snippet(args_span) + else { err.note(msg); return; }; - let (qself_sugg_span, is_self) = if let hir::TyKind::Path( - hir::QPath::Resolved(_, path) - ) = &qself.kind { - // If the path segment already has type params, we want to overwrite - // them. - match &path.segments { - // `segment` is the previous to last element on the path, - // which would normally be the `enum` itself, while the last - // `_` `PathSegment` corresponds to the variant. - [.., hir::PathSegment { - ident, - args, - res: Res::Def(DefKind::Enum, _), - .. - }, _] => ( - // We need to include the `::` in `Type::Variant::` - // to point the span to `::`, not just ``. - ident.span.shrink_to_hi().to(args.map_or( - ident.span.shrink_to_hi(), - |a| a.span_ext)), - false, - ), - [segment] => ( - // We need to include the `::` in `Type::Variant::` - // to point the span to `::`, not just ``. - segment.ident.span.shrink_to_hi().to(segment.args.map_or( - segment.ident.span.shrink_to_hi(), - |a| a.span_ext)), - kw::SelfUpper == segment.ident.name, - ), - _ => { - err.note(msg); - return; + let (qself_sugg_span, is_self) = + if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = + &qself.kind + { + // If the path segment already has type params, we want to overwrite + // them. + match &path.segments { + // `segment` is the previous to last element on the path, + // which would normally be the `enum` itself, while the last + // `_` `PathSegment` corresponds to the variant. + [ + .., + hir::PathSegment { + ident, + args, + res: Res::Def(DefKind::Enum, _), + .. + }, + _, + ] => ( + // We need to include the `::` in `Type::Variant::` + // to point the span to `::`, not just ``. + ident.span.shrink_to_hi().to(args + .map_or(ident.span.shrink_to_hi(), |a| a.span_ext)), + false, + ), + [segment] => ( + // We need to include the `::` in `Type::Variant::` + // to point the span to `::`, not just ``. + segment.ident.span.shrink_to_hi().to(segment + .args + .map_or(segment.ident.span.shrink_to_hi(), |a| { + a.span_ext + })), + kw::SelfUpper == segment.ident.name, + ), + _ => { + err.note(msg); + return; + } } - } - } else { - err.note(msg); - return; - }; + } else { + err.note(msg); + return; + }; let suggestion = vec![ if is_self { // Account for people writing `Self::Variant::`, where @@ -2271,7 +1391,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { }; self.one_bound_for_assoc_type( - || traits::supertraits(tcx, ty::Binder::dummy(trait_ref.subst_identity())), + || { + traits::supertraits( + tcx, + ty::Binder::dummy(trait_ref.instantiate_identity()), + ) + }, kw::SelfUpper, assoc_ident, span, @@ -2285,7 +1410,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { _ => { let reported = if variant_resolution.is_some() { // Variant in type position - let msg = format!("expected type, found variant `{}`", assoc_ident); + let msg = format!("expected type, found variant `{assoc_ident}`"); tcx.sess.span_err(span, msg) } else if qself_ty.is_enum() { let mut err = struct_span_err!( @@ -2316,12 +1441,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } else { err.span_label( assoc_ident.span, - format!("variant not found in `{}`", qself_ty), + format!("variant not found in `{qself_ty}`"), ); } if let Some(sp) = tcx.hir().span_if_local(adt_def.did()) { - err.span_label(sp, format!("variant `{}` not found here", assoc_ident)); + err.span_label(sp, format!("variant `{assoc_ident}` not found here")); } err.emit() @@ -2340,7 +1465,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let traits: Vec<_> = self.probe_traits_that_match_assoc_ty(qself_ty, assoc_ident); - // Don't print `TyErr` to the user. + // Don't print `ty::Error` to the user. self.report_ambiguous_associated_type( span, &[qself_ty.to_string()], @@ -2353,7 +1478,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { }; let trait_did = bound.def_id(); - let Some(assoc_ty_did) = self.lookup_assoc_ty(assoc_ident, hir_ref_id, span, trait_did) else { + let Some(assoc_ty_did) = self.lookup_assoc_ty(assoc_ident, hir_ref_id, span, trait_did) + else { // Assume that if it's not matched, there must be a const defined with the same name // but it was used in a type position. let msg = format!("found associated const `{assoc_ident}` when type was expected"); @@ -2408,6 +1534,15 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { ) -> Result, DefId)>, ErrorGuaranteed> { let tcx = self.tcx(); + // Don't attempt to look up inherent associated types when the feature is not enabled. + // Theoretically it'd be fine to do so since we feature-gate their definition site. + // However, due to current limitations of the implementation (caused by us performing + // selection in AstConv), IATs can lead to cycle errors (#108491, #110106) which mask the + // feature-gate error, needlessly confusing users that use IATs by accident (#113265). + if !tcx.features().inherent_associated_types { + return Ok(None); + } + let candidates: Vec<_> = tcx .inherent_impls(adt_did) .iter() @@ -2441,33 +1576,65 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let mut fulfillment_errors = Vec::new(); let mut applicable_candidates: Vec<_> = infcx.probe(|_| { - let universe = infcx.create_next_universe(); - // Regions are not considered during selection. - // FIXME(non_lifetime_binders): Here we are "truncating" or "flattening" the universes - // of type and const binders. Is that correct in the selection phase? See also #109505. - let self_ty = tcx.replace_escaping_bound_vars_uncached( - self_ty, - FnMutDelegate { - regions: &mut |_| tcx.lifetimes.re_erased, - types: &mut |bv| { - tcx.mk_placeholder(ty::PlaceholderType { universe, bound: bv }) - }, - consts: &mut |bv, ty| { - tcx.mk_const(ty::PlaceholderConst { universe, bound: bv }, ty) - }, - }, - ); + let self_ty = self_ty + .fold_with(&mut BoundVarEraser { tcx, universe: infcx.create_next_universe() }); + + struct BoundVarEraser<'tcx> { + tcx: TyCtxt<'tcx>, + universe: ty::UniverseIndex, + } + + // FIXME(non_lifetime_binders): Don't assign the same universe to each placeholder. + impl<'tcx> TypeFolder> for BoundVarEraser<'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { + if r.is_late_bound() { self.tcx.lifetimes.re_erased } else { r } + } + + fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { + match *ty.kind() { + ty::Bound(_, bv) => Ty::new_placeholder( + self.tcx, + ty::PlaceholderType { universe: self.universe, bound: bv }, + ), + _ => ty.super_fold_with(self), + } + } + + fn fold_const( + &mut self, + ct: ty::Const<'tcx>, + ) -> as rustc_type_ir::Interner>::Const { + assert!(!ct.ty().has_escaping_bound_vars()); + + match ct.kind() { + ty::ConstKind::Bound(_, bv) => ty::Const::new_placeholder( + self.tcx, + ty::PlaceholderConst { universe: self.universe, bound: bv }, + ct.ty(), + ), + _ => ct.super_fold_with(self), + } + } + } + + let InferOk { value: self_ty, obligations } = + infcx.at(&cause, param_env).normalize(self_ty); candidates .iter() .copied() .filter(|&(impl_, _)| { infcx.probe(|_| { - let ocx = ObligationCtxt::new_in_snapshot(&infcx); + let ocx = ObligationCtxt::new(&infcx); + ocx.register_obligations(obligations.clone()); - let impl_substs = infcx.fresh_item_substs(impl_); - let impl_ty = tcx.type_of(impl_).subst(tcx, impl_substs); + let impl_args = infcx.fresh_args_for_item(span, impl_); + let impl_ty = tcx.type_of(impl_).instantiate(tcx, impl_args); let impl_ty = ocx.normalize(&cause, param_env, impl_ty); // Check that the self types can be related. @@ -2479,7 +1646,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } // Check whether the impl imposes obligations we have to worry about. - let impl_bounds = tcx.predicates_of(impl_).instantiate(tcx, impl_substs); + let impl_bounds = tcx.predicates_of(impl_).instantiate(tcx, impl_args); let impl_bounds = ocx.normalize(&cause, param_env, impl_bounds); let impl_obligations = traits::predicates_for_generics( |_, _| cause.clone(), @@ -2511,18 +1678,17 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { if let Some((impl_, (assoc_item, def_scope))) = applicable_candidates.pop() { self.check_assoc_ty(assoc_item, name, def_scope, block, span); - // FIXME(fmease): Currently creating throwaway `parent_substs` to please - // `create_substs_for_associated_item`. Modify the latter instead (or sth. similar) to - // not require the parent substs logic. - let parent_substs = InternalSubsts::identity_for_item(tcx, impl_); - let substs = - self.create_substs_for_associated_item(span, assoc_item, segment, parent_substs); - let substs = tcx.mk_substs_from_iter( + // FIXME(fmease): Currently creating throwaway `parent_args` to please + // `create_args_for_associated_item`. Modify the latter instead (or sth. similar) to + // not require the parent args logic. + let parent_args = ty::GenericArgs::identity_for_item(tcx, impl_); + let args = self.create_args_for_associated_item(span, assoc_item, segment, parent_args); + let args = tcx.mk_args_from_iter( std::iter::once(ty::GenericArg::from(self_ty)) - .chain(substs.into_iter().skip(parent_substs.len())), + .chain(args.into_iter().skip(parent_args.len())), ); - let ty = tcx.mk_alias(ty::Inherent, tcx.mk_alias_ty(assoc_item, substs)); + let ty = Ty::new_alias(tcx, ty::Inherent, tcx.mk_alias_ty(assoc_item, args)); return Ok(Some((ty, assoc_item))); } @@ -2626,9 +1792,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { .any(|impl_def_id| { let trait_ref = tcx.impl_trait_ref(impl_def_id); trait_ref.is_some_and(|trait_ref| { - let impl_ = trait_ref.subst( + let impl_ = trait_ref.instantiate( tcx, - infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id), + infcx.fresh_args_for_item(DUMMY_SP, impl_def_id), ); let value = tcx.fold_regions(qself_ty, |_, _| tcx.lifetimes.re_erased); // FIXME: Don't bother dealing with non-lifetime binders here... @@ -2671,7 +1837,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { debug!("qpath_to_ty: self.item_def_id()={:?}", def_id); - let parent_def_id = def_id.as_local().map(|def_id| tcx.hir().local_def_id_to_hir_id(def_id)) + let parent_def_id = def_id + .as_local() + .map(|def_id| tcx.hir().local_def_id_to_hir_id(def_id)) .map(|hir_id| tcx.hir().get_parent_item(hir_id).to_def_id()); debug!("qpath_to_ty: parent_def_id={:?}", parent_def_id); @@ -2692,7 +1860,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { && tcx.impl_polarity(impl_def_id) != ty::ImplPolarity::Negative }) .filter_map(|impl_def_id| tcx.impl_trait_ref(impl_def_id)) - .map(|impl_| impl_.subst_identity().self_ty()) + .map(|impl_| impl_.instantiate_identity().self_ty()) // We don't care about blanket impls. .filter(|self_ty| !self_ty.has_non_region_param()) .map(|self_ty| tcx.erase_regions(self_ty).to_string()) @@ -2707,7 +1875,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { &[path_str], item_segment.ident.name, ); - return tcx.ty_error(reported) + return Ty::new_error(tcx, reported); }; debug!("qpath_to_ty: self_type={:?}", self_ty); @@ -2721,16 +1889,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { constness, ); - let item_substs = self.create_substs_for_associated_item( - span, - item_def_id, - item_segment, - trait_ref.substs, - ); + let item_args = + self.create_args_for_associated_item(span, item_def_id, item_segment, trait_ref.args); debug!("qpath_to_ty: trait_ref={:?}", trait_ref); - tcx.mk_projection(item_def_id, item_substs) + Ty::new_projection(tcx, item_def_id, item_args) } pub fn prohibit_generics<'a>( @@ -2985,19 +2149,19 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let span = path.span; match path.res { - Res::Def(DefKind::OpaqueTy | DefKind::ImplTraitPlaceholder, did) => { + Res::Def(DefKind::OpaqueTy, did) => { // Check for desugared `impl Trait`. assert!(tcx.is_type_alias_impl_trait(did)); let item_segment = path.segments.split_last().unwrap(); self.prohibit_generics(item_segment.1.iter(), |err| { err.note("`impl Trait` types can't have type parameters"); }); - let substs = self.ast_path_substs_for_ty(span, did, item_segment.0); - tcx.mk_opaque(did, substs) + let args = self.ast_path_args_for_ty(span, did, item_segment.0); + Ty::new_opaque(tcx, did, args) } Res::Def( DefKind::Enum - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::Struct | DefKind::Union | DefKind::ForeignTy, @@ -3045,16 +2209,16 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { var: ty::BoundVar::from_u32(index), kind: ty::BoundTyKind::Param(def_id, name), }; - tcx.mk_bound(debruijn, br) + Ty::new_bound(tcx, debruijn, br) } Some(rbv::ResolvedArg::EarlyBound(_)) => { let def_id = def_id.expect_local(); let item_def_id = tcx.hir().ty_param_owner(def_id); let generics = tcx.generics_of(item_def_id); let index = generics.param_def_id_to_index[&def_id.to_def_id()]; - tcx.mk_ty_param(index, tcx.hir().ty_param_name(def_id)) + Ty::new_param(tcx, index, tcx.hir().ty_param_name(def_id)) } - Some(rbv::ResolvedArg::Error(guar)) => tcx.ty_error(guar), + Some(rbv::ResolvedArg::Error(guar)) => Ty::new_error(tcx, guar), arg => bug!("unexpected bound var resolution for {hir_id:?}: {arg:?}"), } } @@ -3077,7 +2241,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { // `Self` in impl (we know the concrete type). assert_eq!(opt_self_ty, None); // Try to evaluate any array length constants. - let ty = tcx.at(span).type_of(def_id).subst_identity(); + let ty = tcx.at(span).type_of(def_id).instantiate_identity(); let span_of_impl = tcx.span_of_impl(def_id); self.prohibit_generics(path.segments.iter(), |err| { let def_id = match *ty.kind() { @@ -3166,7 +2330,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { { err.span_note(impl_.self_ty.span, "not a concrete type"); } - tcx.ty_error(err.emit()) + Ty::new_error(tcx, err.emit()) } else { ty } @@ -3207,9 +2371,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { match prim_ty { hir::PrimTy::Bool => tcx.types.bool, hir::PrimTy::Char => tcx.types.char, - hir::PrimTy::Int(it) => tcx.mk_mach_int(ty::int_ty(it)), - hir::PrimTy::Uint(uit) => tcx.mk_mach_uint(ty::uint_ty(uit)), - hir::PrimTy::Float(ft) => tcx.mk_mach_float(ty::float_ty(ft)), + hir::PrimTy::Int(it) => Ty::new_int(tcx, ty::int_ty(it)), + hir::PrimTy::Uint(uit) => Ty::new_uint(tcx, ty::uint_ty(uit)), + hir::PrimTy::Float(ft) => Ty::new_float(tcx, ty::float_ty(ft)), hir::PrimTy::Str => tcx.types.str_, } } @@ -3219,7 +2383,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { .sess .delay_span_bug(path.span, "path with `Res::Err` but no error emitted"); self.set_tainted_by_errors(e); - self.tcx().ty_error(e) + Ty::new_error(self.tcx(), e) } _ => span_bug!(span, "unexpected resolution: {:?}", path.res), } @@ -3244,31 +2408,27 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let tcx = self.tcx(); let result_ty = match &ast_ty.kind { - hir::TyKind::Slice(ty) => tcx.mk_slice(self.ast_ty_to_ty(ty)), + hir::TyKind::Slice(ty) => Ty::new_slice(tcx, self.ast_ty_to_ty(ty)), hir::TyKind::Ptr(mt) => { - tcx.mk_ptr(ty::TypeAndMut { ty: self.ast_ty_to_ty(mt.ty), mutbl: mt.mutbl }) + Ty::new_ptr(tcx, ty::TypeAndMut { ty: self.ast_ty_to_ty(mt.ty), mutbl: mt.mutbl }) } hir::TyKind::Ref(region, mt) => { let r = self.ast_region_to_region(region, None); debug!(?r); let t = self.ast_ty_to_ty_inner(mt.ty, true, false); - tcx.mk_ref(r, ty::TypeAndMut { ty: t, mutbl: mt.mutbl }) + Ty::new_ref(tcx, r, ty::TypeAndMut { ty: t, mutbl: mt.mutbl }) } hir::TyKind::Never => tcx.types.never, hir::TyKind::Tup(fields) => { - tcx.mk_tup_from_iter(fields.iter().map(|t| self.ast_ty_to_ty(t))) + Ty::new_tup_from_iter(tcx, fields.iter().map(|t| self.ast_ty_to_ty(t))) } hir::TyKind::BareFn(bf) => { require_c_abi_if_c_variadic(tcx, bf.decl, bf.abi, ast_ty.span); - tcx.mk_fn_ptr(self.ty_of_fn( - ast_ty.hir_id, - bf.unsafety, - bf.abi, - bf.decl, - None, - Some(ast_ty), - )) + Ty::new_fn_ptr( + tcx, + self.ty_of_fn(ast_ty.hir_id, bf.unsafety, bf.abi, bf.decl, None, Some(ast_ty)), + ) } hir::TyKind::TraitObject(bounds, lifetime, repr) => { self.maybe_lint_bare_trait(ast_ty, in_path); @@ -3277,7 +2437,14 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { TraitObjectSyntax::DynStar => ty::DynStar, }; - self.conv_object_ty_poly_trait_ref(ast_ty.span, bounds, lifetime, borrowed, repr) + self.conv_object_ty_poly_trait_ref( + ast_ty.span, + ast_ty.hir_id, + bounds, + lifetime, + borrowed, + repr, + ) } hir::TyKind::Path(hir::QPath::Resolved(maybe_qself, path)) => { debug!(?maybe_qself, ?path); @@ -3288,12 +2455,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let opaque_ty = tcx.hir().item(item_id); match opaque_ty.kind { - hir::ItemKind::OpaqueTy(hir::OpaqueTy { origin, .. }) => { + hir::ItemKind::OpaqueTy(&hir::OpaqueTy { origin, .. }) => { let local_def_id = item_id.owner_id.def_id; // If this is an RPITIT and we are using the new RPITIT lowering scheme, we // generate the def_id of an associated type for the trait and return as // type a projection. - let def_id = if in_trait && tcx.lower_impl_trait_in_trait_to_assoc_ty() { + let def_id = if in_trait { tcx.associated_type_for_impl_trait_in_trait(local_def_id).to_def_id() } else { local_def_id.to_def_id() @@ -3308,11 +2475,11 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let ty = self.ast_ty_to_ty_inner(qself, false, true); self.associated_path_to_ty(ast_ty.hir_id, ast_ty.span, ty, qself, segment, false) .map(|(ty, _, _)| ty) - .unwrap_or_else(|guar| tcx.ty_error(guar)) + .unwrap_or_else(|guar| Ty::new_error(tcx, guar)) } &hir::TyKind::Path(hir::QPath::LangItem(lang_item, span, _)) => { let def_id = tcx.require_lang_item(lang_item, Some(span)); - let (substs, _) = self.create_substs_for_ast_path( + let (args, _) = self.create_args_for_ast_path( span, def_id, &[], @@ -3322,7 +2489,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { None, ty::BoundConstness::NotConst, ); - tcx.at(span).type_of(def_id).subst(tcx, substs) + tcx.at(span).type_of(def_id).instantiate(tcx, args) } hir::TyKind::Array(ty, length) => { let length = match length { @@ -3332,10 +2499,10 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } }; - tcx.mk_array_with_const_len(self.ast_ty_to_ty(ty), length) + Ty::new_array_with_const_len(tcx, self.ast_ty_to_ty(ty), length) } hir::TyKind::Typeof(e) => { - let ty_erased = tcx.type_of(e.def_id).subst_identity(); + let ty_erased = tcx.type_of(e.def_id).instantiate_identity(); let ty = tcx.fold_regions(ty_erased, |r, _| { if r.is_erased() { tcx.lifetimes.re_static } else { r } }); @@ -3356,7 +2523,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { // handled specially and will not descend into this routine. self.ty_infer(None, ast_ty.span) } - hir::TyKind::Err(guar) => tcx.ty_error(*guar), + hir::TyKind::Err(guar) => Ty::new_error(tcx, *guar), }; self.record_ty(ast_ty.hir_id, result_ty, ast_ty.span); @@ -3377,7 +2544,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let generics = tcx.generics_of(def_id); debug!("impl_trait_ty_to_ty: generics={:?}", generics); - let substs = InternalSubsts::for_item(tcx, def_id, |param, _| { + let args = ty::GenericArgs::for_item(tcx, def_id, |param, _| { // We use `generics.count() - lifetimes.len()` here instead of `generics.parent_count` // since return-position impl trait in trait squashes all of the generics from its source fn // into its own generics, so the opaque's "own" params isn't always just lifetimes. @@ -3391,9 +2558,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { tcx.mk_param_from_def(param) } }); - debug!("impl_trait_ty_to_ty: substs={:?}", substs); + debug!("impl_trait_ty_to_ty: args={:?}", args); - if in_trait { tcx.mk_projection(def_id, substs) } else { tcx.mk_opaque(def_id, substs) } + if in_trait { + Ty::new_projection(tcx, def_id, args) + } else { + Ty::new_opaque(tcx, def_id, args) + } } pub fn ty_of_arg(&self, ty: &hir::Ty<'_>, expected_ty: Option>) -> Ty<'tcx> { @@ -3462,7 +2633,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { self.ast_ty_to_ty(output) } } - hir::FnRetTy::DefaultReturn(..) => tcx.mk_unit(), + hir::FnRetTy::DefaultReturn(..) => Ty::new_unit(tcx,), }; debug!(?output_ty); @@ -3538,14 +2709,14 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { let hir = tcx.hir(); let hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Fn(..), ident, .. }) = - hir.get(fn_hir_id) else { return None }; + hir.get(fn_hir_id) + else { + return None; + }; let i = hir.get_parent(fn_hir_id).expect_item().expect_impl(); - let trait_ref = self.instantiate_mono_trait_ref( - i.of_trait.as_ref()?, - self.ast_ty_to_ty(i.self_ty), - ty::BoundConstness::NotConst, - ); + let trait_ref = + self.instantiate_mono_trait_ref(i.of_trait.as_ref()?, self.ast_ty_to_ty(i.self_ty)); let assoc = tcx.associated_items(trait_ref.def_id).find_by_name_and_kind( tcx, @@ -3554,9 +2725,9 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { trait_ref.def_id, )?; - let fn_sig = tcx.fn_sig(assoc.def_id).subst( + let fn_sig = tcx.fn_sig(assoc.def_id).instantiate( tcx, - trait_ref.substs.extend_to(tcx, assoc.def_id, |param, _| tcx.mk_param_from_def(param)), + trait_ref.args.extend_to(tcx, assoc.def_id, |param, _| tcx.mk_param_from_def(param)), ); let fn_sig = tcx.liberate_late_bound_regions(fn_hir_id.expect_owner().to_def_id(), fn_sig); @@ -3579,7 +2750,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { ty::BrNamed(_, kw::UnderscoreLifetime) | ty::BrAnon(..) | ty::BrEnv => { "an anonymous lifetime".to_string() } - ty::BrNamed(_, name) => format!("lifetime `{}`", name), + ty::BrNamed(_, name) => format!("lifetime `{name}`"), }; let mut err = generate_err(&br_name); @@ -3641,148 +2812,4 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { } Some(r) } - - /// Make sure that we are in the condition to suggest the blanket implementation. - fn maybe_lint_blanket_trait_impl(&self, self_ty: &hir::Ty<'_>, diag: &mut Diagnostic) { - let tcx = self.tcx(); - let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id; - if let hir::Node::Item(hir::Item { - kind: - hir::ItemKind::Impl(hir::Impl { - self_ty: impl_self_ty, of_trait: Some(of_trait_ref), generics, .. - }), - .. - }) = tcx.hir().get_by_def_id(parent_id) && self_ty.hir_id == impl_self_ty.hir_id - { - if !of_trait_ref.trait_def_id().is_some_and(|def_id| def_id.is_local()) { - return; - } - let of_trait_span = of_trait_ref.path.span; - // make sure that we are not calling unwrap to abort during the compilation - let Ok(impl_trait_name) = tcx.sess.source_map().span_to_snippet(self_ty.span) else { return; }; - let Ok(of_trait_name) = tcx.sess.source_map().span_to_snippet(of_trait_span) else { return; }; - // check if the trait has generics, to make a correct suggestion - let param_name = generics.params.next_type_param_name(None); - - let add_generic_sugg = if let Some(span) = generics.span_for_param_suggestion() { - (span, format!(", {}: {}", param_name, impl_trait_name)) - } else { - (generics.span, format!("<{}: {}>", param_name, impl_trait_name)) - }; - diag.multipart_suggestion( - format!("alternatively use a blanket \ - implementation to implement `{of_trait_name}` for \ - all types that also implement `{impl_trait_name}`"), - vec![ - (self_ty.span, param_name), - add_generic_sugg, - ], - Applicability::MaybeIncorrect, - ); - } - } - - fn maybe_lint_bare_trait(&self, self_ty: &hir::Ty<'_>, in_path: bool) { - let tcx = self.tcx(); - if let hir::TyKind::TraitObject([poly_trait_ref, ..], _, TraitObjectSyntax::None) = - self_ty.kind - { - let needs_bracket = in_path - && !tcx - .sess - .source_map() - .span_to_prev_source(self_ty.span) - .ok() - .is_some_and(|s| s.trim_end().ends_with('<')); - - let is_global = poly_trait_ref.trait_ref.path.is_global(); - - let mut sugg = Vec::from_iter([( - self_ty.span.shrink_to_lo(), - format!( - "{}dyn {}", - if needs_bracket { "<" } else { "" }, - if is_global { "(" } else { "" }, - ), - )]); - - if is_global || needs_bracket { - sugg.push(( - self_ty.span.shrink_to_hi(), - format!( - "{}{}", - if is_global { ")" } else { "" }, - if needs_bracket { ">" } else { "" }, - ), - )); - } - - if self_ty.span.edition().rust_2021() { - let msg = "trait objects must include the `dyn` keyword"; - let label = "add `dyn` keyword before this trait"; - let mut diag = - rustc_errors::struct_span_err!(tcx.sess, self_ty.span, E0782, "{}", msg); - if self_ty.span.can_be_used_for_suggestions() { - diag.multipart_suggestion_verbose( - label, - sugg, - Applicability::MachineApplicable, - ); - } - // check if the impl trait that we are considering is a impl of a local trait - self.maybe_lint_blanket_trait_impl(&self_ty, &mut diag); - diag.stash(self_ty.span, StashKey::TraitMissingMethod); - } else { - let msg = "trait objects without an explicit `dyn` are deprecated"; - tcx.struct_span_lint_hir( - BARE_TRAIT_OBJECTS, - self_ty.hir_id, - self_ty.span, - msg, - |lint| { - lint.multipart_suggestion_verbose( - "use `dyn`", - sugg, - Applicability::MachineApplicable, - ); - self.maybe_lint_blanket_trait_impl(&self_ty, lint); - lint - }, - ); - } - } - } -} - -pub trait InferCtxtExt<'tcx> { - fn fresh_item_substs(&self, def_id: DefId) -> SubstsRef<'tcx>; -} - -impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { - fn fresh_item_substs(&self, def_id: DefId) -> SubstsRef<'tcx> { - InternalSubsts::for_item(self.tcx, def_id, |param, _| match param.kind { - GenericParamDefKind::Lifetime => self.tcx.lifetimes.re_erased.into(), - GenericParamDefKind::Type { .. } => self - .next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::SubstitutionPlaceholder, - span: self.tcx.def_span(def_id), - }) - .into(), - GenericParamDefKind::Const { .. } => { - let span = self.tcx.def_span(def_id); - let origin = ConstVariableOrigin { - kind: ConstVariableOriginKind::SubstitutionPlaceholder, - span, - }; - self.next_const_var( - self.tcx - .type_of(param.def_id) - .no_bound_vars() - .expect("const parameter types cannot be generic"), - origin, - ) - .into() - } - }) - } } diff --git a/compiler/rustc_hir_analysis/src/astconv/object_safety.rs b/compiler/rustc_hir_analysis/src/astconv/object_safety.rs new file mode 100644 index 0000000000000..30c2ab8f5458c --- /dev/null +++ b/compiler/rustc_hir_analysis/src/astconv/object_safety.rs @@ -0,0 +1,403 @@ +use crate::astconv::{GenericArgCountMismatch, GenericArgCountResult, OnlySelfBounds}; +use crate::bounds::Bounds; +use crate::errors::TraitObjectDeclaredWithNoTraits; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::struct_span_err; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_lint_defs::builtin::UNUSED_ASSOCIATED_TYPE_BOUNDS; +use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{DynKind, ToPredicate}; +use rustc_span::Span; +use rustc_trait_selection::traits::error_reporting::report_object_safety_error; +use rustc_trait_selection::traits::{self, astconv_object_safety_violations}; + +use smallvec::{smallvec, SmallVec}; +use std::collections::BTreeSet; + +use super::AstConv; + +impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { + pub(super) fn conv_object_ty_poly_trait_ref( + &self, + span: Span, + hir_id: hir::HirId, + hir_trait_bounds: &[hir::PolyTraitRef<'_>], + lifetime: &hir::Lifetime, + borrowed: bool, + representation: DynKind, + ) -> Ty<'tcx> { + let tcx = self.tcx(); + + let mut bounds = Bounds::default(); + let mut potential_assoc_types = Vec::new(); + let dummy_self = self.tcx().types.trait_object_dummy_self; + for trait_bound in hir_trait_bounds.iter().rev() { + if let GenericArgCountResult { + correct: + Err(GenericArgCountMismatch { invalid_args: cur_potential_assoc_types, .. }), + .. + } = self.instantiate_poly_trait_ref( + &trait_bound.trait_ref, + trait_bound.span, + ty::BoundConstness::NotConst, + ty::ImplPolarity::Positive, + dummy_self, + &mut bounds, + false, + // FIXME: This should be `true`, but we don't really handle + // associated type bounds or type aliases in objects in a way + // that makes this meaningful, I think. + OnlySelfBounds(false), + ) { + potential_assoc_types.extend(cur_potential_assoc_types); + } + } + + let mut trait_bounds = vec![]; + let mut projection_bounds = vec![]; + for (pred, span) in bounds.clauses() { + let bound_pred = pred.kind(); + match bound_pred.skip_binder() { + ty::ClauseKind::Trait(trait_pred) => { + assert_eq!(trait_pred.polarity, ty::ImplPolarity::Positive); + trait_bounds.push((bound_pred.rebind(trait_pred.trait_ref), span)); + } + ty::ClauseKind::Projection(proj) => { + projection_bounds.push((bound_pred.rebind(proj), span)); + } + ty::ClauseKind::TypeOutlives(_) => { + // Do nothing, we deal with regions separately + } + ty::ClauseKind::RegionOutlives(_) + | ty::ClauseKind::ConstArgHasType(..) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => { + bug!() + } + } + } + + // Expand trait aliases recursively and check that only one regular (non-auto) trait + // is used and no 'maybe' bounds are used. + let expanded_traits = + traits::expand_trait_aliases(tcx, trait_bounds.iter().map(|&(a, b)| (a, b))); + + let (mut auto_traits, regular_traits): (Vec<_>, Vec<_>) = expanded_traits + .filter(|i| i.trait_ref().self_ty().skip_binder() == dummy_self) + .partition(|i| tcx.trait_is_auto(i.trait_ref().def_id())); + if regular_traits.len() > 1 { + let first_trait = ®ular_traits[0]; + let additional_trait = ®ular_traits[1]; + let mut err = struct_span_err!( + tcx.sess, + additional_trait.bottom().1, + E0225, + "only auto traits can be used as additional traits in a trait object" + ); + additional_trait.label_with_exp_info( + &mut err, + "additional non-auto trait", + "additional use", + ); + first_trait.label_with_exp_info(&mut err, "first non-auto trait", "first use"); + err.help(format!( + "consider creating a new trait with all of these as supertraits and using that \ + trait here instead: `trait NewTrait: {} {{}}`", + regular_traits + .iter() + .map(|t| t.trait_ref().print_only_trait_path().to_string()) + .collect::>() + .join(" + "), + )); + err.note( + "auto-traits like `Send` and `Sync` are traits that have special properties; \ + for more information on them, visit \ + ", + ); + err.emit(); + } + + if regular_traits.is_empty() && auto_traits.is_empty() { + let trait_alias_span = trait_bounds + .iter() + .map(|&(trait_ref, _)| trait_ref.def_id()) + .find(|&trait_ref| tcx.is_trait_alias(trait_ref)) + .map(|trait_ref| tcx.def_span(trait_ref)); + let reported = + tcx.sess.emit_err(TraitObjectDeclaredWithNoTraits { span, trait_alias_span }); + return Ty::new_error(tcx, reported); + } + + // Check that there are no gross object safety violations; + // most importantly, that the supertraits don't contain `Self`, + // to avoid ICEs. + for item in ®ular_traits { + let object_safety_violations = + astconv_object_safety_violations(tcx, item.trait_ref().def_id()); + if !object_safety_violations.is_empty() { + let reported = report_object_safety_error( + tcx, + span, + item.trait_ref().def_id(), + &object_safety_violations, + ) + .emit(); + return Ty::new_error(tcx, reported); + } + } + + // Use a `BTreeSet` to keep output in a more consistent order. + let mut associated_types: FxHashMap> = FxHashMap::default(); + + let regular_traits_refs_spans = trait_bounds + .into_iter() + .filter(|(trait_ref, _)| !tcx.trait_is_auto(trait_ref.def_id())); + + for (base_trait_ref, span) in regular_traits_refs_spans { + let base_pred: ty::Predicate<'tcx> = base_trait_ref.to_predicate(tcx); + for pred in traits::elaborate(tcx, [base_pred]) { + debug!("conv_object_ty_poly_trait_ref: observing object predicate `{:?}`", pred); + + let bound_predicate = pred.kind(); + match bound_predicate.skip_binder() { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { + let pred = bound_predicate.rebind(pred); + associated_types.entry(span).or_default().extend( + tcx.associated_items(pred.def_id()) + .in_definition_order() + .filter(|item| item.kind == ty::AssocKind::Type) + .filter(|item| !item.is_impl_trait_in_trait()) + .map(|item| item.def_id), + ); + } + ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred)) => { + let pred = bound_predicate.rebind(pred); + // A `Self` within the original bound will be substituted with a + // `trait_object_dummy_self`, so check for that. + let references_self = match pred.skip_binder().term.unpack() { + ty::TermKind::Ty(ty) => ty.walk().any(|arg| arg == dummy_self.into()), + ty::TermKind::Const(c) => { + c.ty().walk().any(|arg| arg == dummy_self.into()) + } + }; + + // If the projection output contains `Self`, force the user to + // elaborate it explicitly to avoid a lot of complexity. + // + // The "classically useful" case is the following: + // ``` + // trait MyTrait: FnMut() -> ::MyOutput { + // type MyOutput; + // } + // ``` + // + // Here, the user could theoretically write `dyn MyTrait`, + // but actually supporting that would "expand" to an infinitely-long type + // `fix $ τ → dyn MyTrait::MyOutput`. + // + // Instead, we force the user to write + // `dyn MyTrait`, which is uglier but works. See + // the discussion in #56288 for alternatives. + if !references_self { + // Include projections defined on supertraits. + projection_bounds.push((pred, span)); + } + } + _ => (), + } + } + } + + // `dyn Trait` desugars to (not Rust syntax) `dyn Trait where ::Assoc = Foo`. + // So every `Projection` clause is an `Assoc = Foo` bound. `associated_types` contains all associated + // types's `DefId`, so the following loop removes all the `DefIds` of the associated types that have a + // corresponding `Projection` clause + for def_ids in associated_types.values_mut() { + for (projection_bound, span) in &projection_bounds { + let def_id = projection_bound.projection_def_id(); + def_ids.remove(&def_id); + if tcx.generics_require_sized_self(def_id) { + tcx.emit_spanned_lint( + UNUSED_ASSOCIATED_TYPE_BOUNDS, + hir_id, + *span, + crate::errors::UnusedAssociatedTypeBounds { span: *span }, + ); + } + } + // If the associated type has a `where Self: Sized` bound, we do not need to constrain the associated + // type in the `dyn Trait`. + def_ids.retain(|def_id| !tcx.generics_require_sized_self(def_id)); + } + + self.complain_about_missing_associated_types( + associated_types, + potential_assoc_types, + hir_trait_bounds, + ); + + // De-duplicate auto traits so that, e.g., `dyn Trait + Send + Send` is the same as + // `dyn Trait + Send`. + // We remove duplicates by inserting into a `FxHashSet` to avoid re-ordering + // the bounds + let mut duplicates = FxHashSet::default(); + auto_traits.retain(|i| duplicates.insert(i.trait_ref().def_id())); + debug!("regular_traits: {:?}", regular_traits); + debug!("auto_traits: {:?}", auto_traits); + + // Erase the `dummy_self` (`trait_object_dummy_self`) used above. + let existential_trait_refs = regular_traits.iter().map(|i| { + i.trait_ref().map_bound(|trait_ref: ty::TraitRef<'tcx>| { + assert_eq!(trait_ref.self_ty(), dummy_self); + + // Verify that `dummy_self` did not leak inside default type parameters. This + // could not be done at path creation, since we need to see through trait aliases. + let mut missing_type_params = vec![]; + let mut references_self = false; + let generics = tcx.generics_of(trait_ref.def_id); + let args: Vec<_> = trait_ref + .args + .iter() + .enumerate() + .skip(1) // Remove `Self` for `ExistentialPredicate`. + .map(|(index, arg)| { + if arg == dummy_self.into() { + let param = &generics.params[index]; + missing_type_params.push(param.name); + return Ty::new_misc_error(tcx).into(); + } else if arg.walk().any(|arg| arg == dummy_self.into()) { + references_self = true; + return Ty::new_misc_error(tcx).into(); + } + arg + }) + .collect(); + let args = tcx.mk_args(&args); + + let span = i.bottom().1; + let empty_generic_args = hir_trait_bounds.iter().any(|hir_bound| { + hir_bound.trait_ref.path.res == Res::Def(DefKind::Trait, trait_ref.def_id) + && hir_bound.span.contains(span) + }); + self.complain_about_missing_type_params( + missing_type_params, + trait_ref.def_id, + span, + empty_generic_args, + ); + + if references_self { + let def_id = i.bottom().0.def_id(); + let mut err = struct_span_err!( + tcx.sess, + i.bottom().1, + E0038, + "the {} `{}` cannot be made into an object", + tcx.def_descr(def_id), + tcx.item_name(def_id), + ); + err.note( + rustc_middle::traits::ObjectSafetyViolation::SupertraitSelf(smallvec![]) + .error_msg(), + ); + err.emit(); + } + + ty::ExistentialTraitRef { def_id: trait_ref.def_id, args } + }) + }); + + let existential_projections = projection_bounds + .iter() + // We filter out traits that don't have `Self` as their self type above, + // we need to do the same for projections. + .filter(|(bound, _)| bound.skip_binder().self_ty() == dummy_self) + .map(|(bound, _)| { + bound.map_bound(|mut b| { + assert_eq!(b.projection_ty.self_ty(), dummy_self); + + // Like for trait refs, verify that `dummy_self` did not leak inside default type + // parameters. + let references_self = b.projection_ty.args.iter().skip(1).any(|arg| { + if arg.walk().any(|arg| arg == dummy_self.into()) { + return true; + } + false + }); + if references_self { + let guar = tcx.sess.delay_span_bug( + span, + "trait object projection bounds reference `Self`", + ); + let args: Vec<_> = b + .projection_ty + .args + .iter() + .map(|arg| { + if arg.walk().any(|arg| arg == dummy_self.into()) { + return Ty::new_error(tcx, guar).into(); + } + arg + }) + .collect(); + b.projection_ty.args = tcx.mk_args(&args); + } + + ty::ExistentialProjection::erase_self_ty(tcx, b) + }) + }); + + let regular_trait_predicates = existential_trait_refs + .map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait)); + let auto_trait_predicates = auto_traits.into_iter().map(|trait_ref| { + ty::Binder::dummy(ty::ExistentialPredicate::AutoTrait(trait_ref.trait_ref().def_id())) + }); + // N.b. principal, projections, auto traits + // FIXME: This is actually wrong with multiple principals in regards to symbol mangling + let mut v = regular_trait_predicates + .chain( + existential_projections.map(|x| x.map_bound(ty::ExistentialPredicate::Projection)), + ) + .chain(auto_trait_predicates) + .collect::>(); + v.sort_by(|a, b| a.skip_binder().stable_cmp(tcx, &b.skip_binder())); + v.dedup(); + let existential_predicates = tcx.mk_poly_existential_predicates(&v); + + // Use explicitly-specified region bound. + let region_bound = if !lifetime.is_elided() { + self.ast_region_to_region(lifetime, None) + } else { + self.compute_object_lifetime_bound(span, existential_predicates).unwrap_or_else(|| { + if tcx.named_bound_var(lifetime.hir_id).is_some() { + self.ast_region_to_region(lifetime, None) + } else { + self.re_infer(None, span).unwrap_or_else(|| { + let mut err = struct_span_err!( + tcx.sess, + span, + E0228, + "the lifetime bound for this object type cannot be deduced \ + from context; please supply an explicit bound" + ); + let e = if borrowed { + // We will have already emitted an error E0106 complaining about a + // missing named lifetime in `&dyn Trait`, so we elide this one. + err.delay_as_bug() + } else { + err.emit() + }; + ty::Region::new_error(tcx, e) + }) + } + }) + }; + debug!("region_bound: {:?}", region_bound); + + let ty = Ty::new_dynamic(tcx, existential_predicates, region_bound, representation); + debug!("trait_object_type: {:?}", ty); + ty + } +} diff --git a/compiler/rustc_hir_analysis/src/autoderef.rs b/compiler/rustc_hir_analysis/src/autoderef.rs index d6d1498d708ed..39db295044ed0 100644 --- a/compiler/rustc_hir_analysis/src/autoderef.rs +++ b/compiler/rustc_hir_analysis/src/autoderef.rs @@ -73,8 +73,8 @@ impl<'a, 'tcx> Iterator for Autoderef<'a, 'tcx> { // NOTE: we may still need to normalize the built-in deref in case // we have some type like `&::Assoc`, since users of // autoderef expect this type to have been structurally normalized. - if self.infcx.tcx.trait_solver_next() - && let ty::Alias(ty::Projection, _) = ty.kind() + if self.infcx.next_trait_solver() + && let ty::Alias(ty::Projection | ty::Inherent | ty::Weak, _) = ty.kind() { let (normalized_ty, obligations) = self.structurally_normalize(ty)?; self.state.obligations.extend(obligations); @@ -148,8 +148,11 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> { return None; } - let (normalized_ty, obligations) = - self.structurally_normalize(tcx.mk_projection(tcx.lang_items().deref_target()?, [ty]))?; + let (normalized_ty, obligations) = self.structurally_normalize(Ty::new_projection( + tcx, + tcx.lang_items().deref_target()?, + [ty], + ))?; debug!("overloaded_deref_ty({:?}) = ({:?}, {:?})", ty, normalized_ty, obligations); self.state.obligations.extend(obligations); @@ -161,8 +164,7 @@ impl<'a, 'tcx> Autoderef<'a, 'tcx> { &self, ty: Ty<'tcx>, ) -> Option<(Ty<'tcx>, Vec>)> { - let tcx = self.infcx.tcx; - let mut fulfill_cx = >::new_in_snapshot(tcx); + let mut fulfill_cx = >::new(self.infcx); let cause = traits::ObligationCause::misc(self.span, self.body_id); let normalized_ty = match self diff --git a/compiler/rustc_hir_analysis/src/bounds.rs b/compiler/rustc_hir_analysis/src/bounds.rs index 686066abbf079..1d9ae2b9cb730 100644 --- a/compiler/rustc_hir_analysis/src/bounds.rs +++ b/compiler/rustc_hir_analysis/src/bounds.rs @@ -23,7 +23,7 @@ use rustc_span::Span; /// include the self type (e.g., `trait_bounds`) but in others we do not #[derive(Default, PartialEq, Eq, Clone, Debug)] pub struct Bounds<'tcx> { - pub predicates: Vec<(ty::Predicate<'tcx>, Span)>, + pub clauses: Vec<(ty::Clause<'tcx>, Span)>, } impl<'tcx> Bounds<'tcx> { @@ -33,7 +33,8 @@ impl<'tcx> Bounds<'tcx> { region: ty::PolyTypeOutlivesPredicate<'tcx>, span: Span, ) { - self.predicates.push((region.to_predicate(tcx), span)); + self.clauses + .push((region.map_bound(|p| ty::ClauseKind::TypeOutlives(p)).to_predicate(tcx), span)); } pub fn push_trait_bound( @@ -41,12 +42,13 @@ impl<'tcx> Bounds<'tcx> { tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, span: Span, - constness: ty::BoundConstness, polarity: ty::ImplPolarity, ) { - self.predicates.push(( + self.clauses.push(( trait_ref - .map_bound(|trait_ref| ty::TraitPredicate { trait_ref, constness, polarity }) + .map_bound(|trait_ref| { + ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, polarity }) + }) .to_predicate(tcx), span, )); @@ -58,17 +60,20 @@ impl<'tcx> Bounds<'tcx> { projection: ty::PolyProjectionPredicate<'tcx>, span: Span, ) { - self.predicates.push((projection.to_predicate(tcx), span)); + self.clauses.push(( + projection.map_bound(|proj| ty::ClauseKind::Projection(proj)).to_predicate(tcx), + span, + )); } pub fn push_sized(&mut self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) { let sized_def_id = tcx.require_lang_item(LangItem::Sized, Some(span)); let trait_ref = ty::TraitRef::new(tcx, sized_def_id, [ty]); // Preferable to put this obligation first, since we report better errors for sized ambiguity. - self.predicates.insert(0, (trait_ref.without_const().to_predicate(tcx), span)); + self.clauses.insert(0, (trait_ref.to_predicate(tcx), span)); } - pub fn predicates(&self) -> impl Iterator, Span)> + '_ { - self.predicates.iter().cloned() + pub fn clauses(&self) -> impl Iterator, Span)> + '_ { + self.clauses.iter().cloned() } } diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 3b2c052e8f459..2c7788498efe9 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -8,7 +8,7 @@ use rustc_attr as attr; use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::intravisit::Visitor; use rustc_hir::{ItemKind, Node, PathSegment}; use rustc_infer::infer::opaque_types::ConstrainOpaqueTypeRegionVisitor; @@ -19,11 +19,13 @@ use rustc_lint_defs::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS; use rustc_middle::hir::nested_filter; use rustc_middle::middle::stability::EvalResult; use rustc_middle::traits::DefiningAnchor; +use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES}; -use rustc_middle::ty::subst::GenericArgKind; use rustc_middle::ty::util::{Discr, IntTypeExt}; +use rustc_middle::ty::GenericArgKind; use rustc_middle::ty::{ - self, AdtDef, ParamEnv, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, + self, AdtDef, ParamEnv, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, }; use rustc_session::lint::builtin::{UNINHABITED_STATIC, UNSUPPORTED_CALLING_CONVENTIONS}; use rustc_span::symbol::sym; @@ -34,6 +36,7 @@ use rustc_trait_selection::traits::error_reporting::on_unimplemented::OnUnimplem use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _; use rustc_trait_selection::traits::{self, ObligationCtxt, TraitEngine, TraitEngineExt as _}; +use rustc_type_ir::fold::TypeFoldable; use std::ops::ControlFlow; @@ -96,8 +99,8 @@ fn check_union(tcx: TyCtxt<'_>, def_id: LocalDefId) { /// Check that the fields of the `union` do not need dropping. fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> bool { - let item_type = tcx.type_of(item_def_id).subst_identity(); - if let ty::Adt(def, substs) = item_type.kind() { + let item_type = tcx.type_of(item_def_id).instantiate_identity(); + if let ty::Adt(def, args) = item_type.kind() { assert!(def.is_union()); fn allowed_union_field<'tcx>( @@ -128,7 +131,7 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b let param_env = tcx.param_env(item_def_id); for field in &def.non_enum_variant().fields { - let field_ty = tcx.normalize_erasing_regions(param_env, field.ty(tcx, substs)); + let field_ty = tcx.normalize_erasing_regions(param_env, field.ty(tcx, args)); if !allowed_union_field(field_ty, tcx, param_env) { let (field_span, ty_span) = match tcx.hir().get_if_local(field.did) { @@ -163,7 +166,7 @@ fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) { // would be enough to check this for `extern` statics, as statics with an initializer will // have UB during initialization if they are uninhabited, but there also seems to be no good // reason to allow any statics to be uninhabited. - let ty = tcx.type_of(def_id).subst_identity(); + let ty = tcx.type_of(def_id).instantiate_identity(); let span = tcx.def_span(def_id); let layout = match tcx.layout_of(ParamEnv::reveal_all().and(ty)) { Ok(l) => l, @@ -177,7 +180,7 @@ fn check_static_inhabited(tcx: TyCtxt<'_>, def_id: LocalDefId) { } // Generic statics are rejected, but we still reach this case. Err(e) => { - tcx.sess.delay_span_bug(span, e.to_string()); + tcx.sess.delay_span_bug(span, format!("{e:?}")); return; } }; @@ -212,19 +215,20 @@ fn check_opaque(tcx: TyCtxt<'_>, id: hir::ItemId) { return; } - let substs = InternalSubsts::identity_for_item(tcx, item.owner_id); + let args = GenericArgs::identity_for_item(tcx, item.owner_id); let span = tcx.def_span(item.owner_id.def_id); if !tcx.features().impl_trait_projections { check_opaque_for_inheriting_lifetimes(tcx, item.owner_id.def_id, span); } - if tcx.type_of(item.owner_id.def_id).subst_identity().references_error() { + if tcx.type_of(item.owner_id.def_id).instantiate_identity().references_error() { return; } - if check_opaque_for_cycles(tcx, item.owner_id.def_id, substs, span, &origin).is_err() { + if check_opaque_for_cycles(tcx, item.owner_id.def_id, args, span, &origin).is_err() { return; } - check_opaque_meets_bounds(tcx, item.owner_id.def_id, span, &origin); + + let _ = check_opaque_meets_bounds(tcx, item.owner_id.def_id, span, &origin); } /// Checks that an opaque type does not use `Self` or `T::Foo` projections that would result @@ -299,18 +303,13 @@ pub(super) fn check_opaque_for_inheriting_lifetimes( } } - if let ItemKind::OpaqueTy(hir::OpaqueTy { + if let ItemKind::OpaqueTy(&hir::OpaqueTy { origin: hir::OpaqueTyOrigin::AsyncFn(..) | hir::OpaqueTyOrigin::FnReturn(..), - in_trait, .. }) = item.kind { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - let opaque_identity_ty = if in_trait && !tcx.lower_impl_trait_in_trait_to_assoc_ty() { - tcx.mk_projection(def_id.to_def_id(), substs) - } else { - tcx.mk_opaque(def_id.to_def_id(), substs) - }; + let args = GenericArgs::identity_for_item(tcx, def_id); + let opaque_identity_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args); let mut visitor = ProhibitOpaqueVisitor { opaque_identity_ty, parent_count: tcx.generics_of(def_id).parent_count as u32, @@ -320,7 +319,7 @@ pub(super) fn check_opaque_for_inheriting_lifetimes( }; let prohibit_opaque = tcx .explicit_item_bounds(def_id) - .subst_identity_iter_copied() + .instantiate_identity_iter_copied() .try_for_each(|(predicate, _)| predicate.visit_with(&mut visitor)); if let Some(ty) = prohibit_opaque.break_value() { @@ -346,7 +345,7 @@ pub(super) fn check_opaque_for_inheriting_lifetimes( err.span_suggestion( span, "consider spelling out the type instead", - name.unwrap_or_else(|| format!("{:?}", ty)), + name.unwrap_or_else(|| format!("{ty:?}")), Applicability::MaybeIncorrect, ); } @@ -359,11 +358,11 @@ pub(super) fn check_opaque_for_inheriting_lifetimes( pub(super) fn check_opaque_for_cycles<'tcx>( tcx: TyCtxt<'tcx>, def_id: LocalDefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, span: Span, origin: &hir::OpaqueTyOrigin, ) -> Result<(), ErrorGuaranteed> { - if tcx.try_expand_impl_trait_type(def_id.to_def_id(), substs).is_err() { + if tcx.try_expand_impl_trait_type(def_id.to_def_id(), args).is_err() { let reported = match origin { hir::OpaqueTyOrigin::AsyncFn(..) => async_opaque_type_cycle_error(tcx, span), _ => opaque_type_cycle_error(tcx, def_id, span), @@ -395,7 +394,7 @@ fn check_opaque_meets_bounds<'tcx>( def_id: LocalDefId, span: Span, origin: &hir::OpaqueTyOrigin, -) { +) -> Result<(), ErrorGuaranteed> { let defining_use_anchor = match *origin { hir::OpaqueTyOrigin::FnReturn(did) | hir::OpaqueTyOrigin::AsyncFn(did) => did, hir::OpaqueTyOrigin::TyAlias { .. } => tcx.impl_trait_parent(def_id), @@ -408,16 +407,26 @@ fn check_opaque_meets_bounds<'tcx>( .build(); let ocx = ObligationCtxt::new(&infcx); - let substs = InternalSubsts::identity_for_item(tcx, def_id.to_def_id()); - let opaque_ty = tcx.mk_opaque(def_id.to_def_id(), substs); + let args = match *origin { + hir::OpaqueTyOrigin::FnReturn(parent) | hir::OpaqueTyOrigin::AsyncFn(parent) => { + GenericArgs::identity_for_item(tcx, parent).extend_to( + tcx, + def_id.to_def_id(), + |param, _| tcx.map_rpit_lifetime_to_fn_lifetime(param.def_id.expect_local()).into(), + ) + } + hir::OpaqueTyOrigin::TyAlias { .. } => GenericArgs::identity_for_item(tcx, def_id), + }; + + let opaque_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args); - // `ReErased` regions appear in the "parent_substs" of closures/generators. + // `ReErased` regions appear in the "parent_args" of closures/generators. // We're ignoring them here and replacing them with fresh region variables. - // See tests in ui/type-alias-impl-trait/closure_{parent_substs,wf_outlives}.rs. + // See tests in ui/type-alias-impl-trait/closure_{parent_args,wf_outlives}.rs. // // FIXME: Consider wrapping the hidden type in an existential `Binder` and instantiating it // here rather than using ReErased. - let hidden_ty = tcx.type_of(def_id.to_def_id()).subst(tcx, substs); + let hidden_ty = tcx.type_of(def_id.to_def_id()).instantiate(tcx, args); let hidden_ty = tcx.fold_regions(hidden_ty, |re, _dbi| match re.kind() { ty::ReErased => infcx.next_region_var(RegionVariableOrigin::MiscVariable(span)), _ => re, @@ -429,28 +438,38 @@ fn check_opaque_meets_bounds<'tcx>( Ok(()) => {} Err(ty_err) => { let ty_err = ty_err.to_string(tcx); - tcx.sess.delay_span_bug( + return Err(tcx.sess.delay_span_bug( span, format!("could not unify `{hidden_ty}` with revealed type:\n{ty_err}"), - ); + )); } } // Additionally require the hidden type to be well-formed with only the generics of the opaque type. // Defining use functions may have more bounds than the opaque type, which is ok, as long as the // hidden type is well formed even without those bounds. - let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(hidden_ty.into())); - ocx.register_obligation(Obligation::new(tcx, misc_cause, param_env, predicate)); + let predicate = + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(hidden_ty.into()))); + ocx.register_obligation(Obligation::new(tcx, misc_cause.clone(), param_env, predicate)); // Check that all obligations are satisfied by the implementation's // version. let errors = ocx.select_all_or_error(); if !errors.is_empty() { - infcx.err_ctxt().report_fulfillment_errors(&errors); + let guar = infcx.err_ctxt().report_fulfillment_errors(&errors); + return Err(guar); } match origin { // Checked when type checking the function containing them. - hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) => {} + hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) => { + // HACK: this should also fall through to the hidden type check below, but the original + // implementation had a bug where equivalent lifetimes are not identical. This caused us + // to reject existing stable code that is otherwise completely fine. The real fix is to + // compare the hidden types via our type equivalence/relation infra instead of doing an + // identity check. + let _ = infcx.take_opaque_types(); + return Ok(()); + } // Nested opaque types occur only in associated types: // ` type Opaque = impl Trait<&'static T, AssocTy = impl Nested>; ` // They can only be referenced as ` as Trait<&'static T>>::AssocTy`. @@ -460,20 +479,69 @@ fn check_opaque_meets_bounds<'tcx>( if tcx.def_kind(tcx.parent(def_id.to_def_id())) == DefKind::OpaqueTy => {} // Can have different predicates to their defining use hir::OpaqueTyOrigin::TyAlias { .. } => { - let wf_tys = ocx.assumed_wf_types(param_env, span, def_id); + let wf_tys = ocx.assumed_wf_types_and_report_errors(param_env, def_id)?; let implied_bounds = infcx.implied_bounds_tys(param_env, def_id, wf_tys); let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); - let _ = ocx.resolve_regions_and_report_errors(defining_use_anchor, &outlives_env); + ocx.resolve_regions_and_report_errors(defining_use_anchor, &outlives_env)?; + } + } + // Check that any hidden types found during wf checking match the hidden types that `type_of` sees. + for (mut key, mut ty) in infcx.take_opaque_types() { + ty.hidden_type.ty = infcx.resolve_vars_if_possible(ty.hidden_type.ty); + key = infcx.resolve_vars_if_possible(key); + sanity_check_found_hidden_type(tcx, key, ty.hidden_type)?; + } + Ok(()) +} + +fn sanity_check_found_hidden_type<'tcx>( + tcx: TyCtxt<'tcx>, + key: ty::OpaqueTypeKey<'tcx>, + mut ty: ty::OpaqueHiddenType<'tcx>, +) -> Result<(), ErrorGuaranteed> { + if ty.ty.is_ty_var() { + // Nothing was actually constrained. + return Ok(()); + } + if let ty::Alias(ty::Opaque, alias) = ty.ty.kind() { + if alias.def_id == key.def_id.to_def_id() && alias.args == key.args { + // Nothing was actually constrained, this is an opaque usage that was + // only discovered to be opaque after inference vars resolved. + return Ok(()); } } - // Clean up after ourselves - let _ = infcx.take_opaque_types(); + let strip_vars = |ty: Ty<'tcx>| { + ty.fold_with(&mut BottomUpFolder { + tcx, + ty_op: |t| t, + ct_op: |c| c, + lt_op: |l| match l.kind() { + RegionKind::ReVar(_) => tcx.lifetimes.re_erased, + _ => l, + }, + }) + }; + // Closures frequently end up containing erased lifetimes in their final representation. + // These correspond to lifetime variables that never got resolved, so we patch this up here. + ty.ty = strip_vars(ty.ty); + // Get the hidden type. + let hidden_ty = tcx.type_of(key.def_id).instantiate(tcx, key.args); + let hidden_ty = strip_vars(hidden_ty); + + // If the hidden types differ, emit a type mismatch diagnostic. + if hidden_ty == ty.ty { + Ok(()) + } else { + let span = tcx.def_span(key.def_id); + let other = ty::OpaqueHiddenType { ty: hidden_ty, span }; + Err(ty.report_mismatch(&other, key.def_id, tcx).emit()) + } } fn is_enum_of_nonnullable_ptr<'tcx>( tcx: TyCtxt<'tcx>, adt_def: AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> bool { if adt_def.repr().inhibit_enum_layout_opt() { return false; @@ -485,14 +553,14 @@ fn is_enum_of_nonnullable_ptr<'tcx>( let (([], [field]) | ([field], [])) = (&var_one.fields.raw[..], &var_two.fields.raw[..]) else { return false; }; - matches!(field.ty(tcx, substs).kind(), ty::FnPtr(..) | ty::Ref(..)) + matches!(field.ty(tcx, args).kind(), ty::FnPtr(..) | ty::Ref(..)) } fn check_static_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) { if tcx.codegen_fn_attrs(def_id).import_linkage.is_some() { - if match tcx.type_of(def_id).subst_identity().kind() { + if match tcx.type_of(def_id).instantiate_identity().kind() { ty::RawPtr(_) => false, - ty::Adt(adt_def, substs) => !is_enum_of_nonnullable_ptr(tcx, *adt_def, *substs), + ty::Adt(adt_def, args) => !is_enum_of_nonnullable_ptr(tcx, *adt_def, *args), _ => true, } { tcx.sess.emit_err(LinkageType { span: tcx.def_span(def_id) }); @@ -526,7 +594,7 @@ fn check_item_type(tcx: TyCtxt<'_>, id: hir::ItemId) { check_impl_items_against_trait( tcx, id.owner_id.def_id, - impl_trait_ref.subst_identity(), + impl_trait_ref.instantiate_identity(), ); check_on_unimplemented(tcx, id); } @@ -542,13 +610,13 @@ fn check_item_type(tcx: TyCtxt<'_>, id: hir::ItemId) { fn_maybe_err(tcx, assoc_item.ident(tcx).span, abi); } ty::AssocKind::Type if assoc_item.defaultness(tcx).has_value() => { - let trait_substs = - InternalSubsts::identity_for_item(tcx, id.owner_id); + let trait_args = + GenericArgs::identity_for_item(tcx, id.owner_id); let _: Result<_, rustc_errors::ErrorGuaranteed> = check_type_bounds( tcx, assoc_item, assoc_item, - ty::TraitRef::new(tcx, id.owner_id.to_def_id(), trait_substs), + ty::TraitRef::new(tcx, id.owner_id.to_def_id(), trait_args), ); } _ => {} @@ -562,8 +630,8 @@ fn check_item_type(tcx: TyCtxt<'_>, id: hir::ItemId) { check_union(tcx, id.owner_id.def_id); } DefKind::OpaqueTy => { - let opaque = tcx.hir().expect_item(id.owner_id.def_id).expect_opaque_ty(); - if let hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id) = opaque.origin + let origin = tcx.opaque_type_origin(id.owner_id.def_id); + if let hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id) = origin && let hir::Node::TraitItem(trait_item) = tcx.hir().get_by_def_id(fn_def_id) && let (_, hir::TraitFn::Required(..)) = trait_item.expect_fn() { @@ -572,19 +640,8 @@ fn check_item_type(tcx: TyCtxt<'_>, id: hir::ItemId) { check_opaque(tcx, id); } } - DefKind::ImplTraitPlaceholder => { - let parent = tcx.impl_trait_in_trait_parent_fn(id.owner_id.to_def_id()); - // Only check the validity of this opaque type if the function has a default body - if let hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)), - .. - }) = tcx.hir().get_by_def_id(parent.expect_local()) - { - check_opaque(tcx, id); - } - } - DefKind::TyAlias => { - let pty_ty = tcx.type_of(id.owner_id).subst_identity(); + DefKind::TyAlias { .. } => { + let pty_ty = tcx.type_of(id.owner_id).instantiate_identity(); let generics = tcx.generics_of(id.owner_id); check_type_params_are_used(tcx, &generics, pty_ty); } @@ -638,7 +695,7 @@ fn check_item_type(tcx: TyCtxt<'_>, id: hir::ItemId) { "replace the {} parameters with concrete {}{}", kinds, kinds_pl, - egs.map(|egs| format!(" like `{}`", egs)).unwrap_or_default(), + egs.map(|egs| format!(" like `{egs}`")).unwrap_or_default(), ), ) .emit(); @@ -704,7 +761,7 @@ pub(super) fn check_specialization_validity<'tcx>( // grandparent. In that case, if parent is a `default impl`, inherited items use the // "defaultness" from the grandparent, else they are final. None => { - if tcx.impl_defaultness(parent_impl.def_id()).is_default() { + if tcx.defaultness(parent_impl.def_id()).is_default() { None } else { Some(Err(parent_impl.def_id())) @@ -718,7 +775,14 @@ pub(super) fn check_specialization_validity<'tcx>( let result = opt_result.unwrap_or(Ok(())); if let Err(parent_impl) = result { - report_forbidden_specialization(tcx, impl_item, parent_impl); + if !tcx.is_impl_trait_in_trait(impl_item) { + report_forbidden_specialization(tcx, impl_item, parent_impl); + } else { + tcx.sess.delay_span_bug( + DUMMY_SP, + format!("parent item: {parent_impl:?} not marked as default"), + ); + } } } @@ -803,7 +867,7 @@ fn check_impl_items_against_trait<'tcx>( .as_ref() .is_some_and(|node_item| node_item.item.defaultness(tcx).has_value()); - if !is_implemented && tcx.impl_defaultness(impl_id).is_final() { + if !is_implemented && tcx.defaultness(impl_id).is_final() { missing_items.push(tcx.associated_item(trait_item_id)); } @@ -891,8 +955,8 @@ fn check_impl_items_against_trait<'tcx>( } pub fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) { - let t = tcx.type_of(def_id).subst_identity(); - if let ty::Adt(def, substs) = t.kind() + let t = tcx.type_of(def_id).instantiate_identity(); + if let ty::Adt(def, args) = t.kind() && def.is_struct() { let fields = &def.non_enum_variant().fields; @@ -900,8 +964,8 @@ pub fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) { struct_span_err!(tcx.sess, sp, E0075, "SIMD vector cannot be empty").emit(); return; } - let e = fields[FieldIdx::from_u32(0)].ty(tcx, substs); - if !fields.iter().all(|f| f.ty(tcx, substs) == e) { + let e = fields[FieldIdx::from_u32(0)].ty(tcx, args); + if !fields.iter().all(|f| f.ty(tcx, args) == e) { struct_span_err!(tcx.sess, sp, E0076, "SIMD vector should be homogeneous") .span_label(sp, "SIMD elements must have the same type") .emit(); @@ -1008,7 +1072,7 @@ pub(super) fn check_packed(tcx: TyCtxt<'_>, sp: Span, def: ty::AdtDef<'_>) { if first { format!( "`{}` contains a field of type `{}`", - tcx.type_of(def.did()).subst_identity(), + tcx.type_of(def.did()).instantiate_identity(), ident ) } else { @@ -1030,7 +1094,7 @@ pub(super) fn check_packed_inner( def_id: DefId, stack: &mut Vec, ) -> Option> { - if let ty::Adt(def, substs) = tcx.type_of(def_id).subst_identity().kind() { + if let ty::Adt(def, args) = tcx.type_of(def_id).instantiate_identity().kind() { if def.is_struct() || def.is_union() { if def.repr().align.is_some() { return Some(vec![(def.did(), DUMMY_SP)]); @@ -1038,7 +1102,7 @@ pub(super) fn check_packed_inner( stack.push(def_id); for field in &def.non_enum_variant().fields { - if let ty::Adt(def, _) = field.ty(tcx, substs).kind() + if let ty::Adt(def, _) = field.ty(tcx, args).kind() && !stack.contains(&def.did()) && let Some(mut defs) = check_packed_inner(tcx, def.did(), stack) { @@ -1077,21 +1141,21 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) // For each field, figure out if it's known to be a ZST and align(1), with "known" // respecting #[non_exhaustive] attributes. let field_infos = adt.all_fields().map(|field| { - let ty = field.ty(tcx, InternalSubsts::identity_for_item(tcx, field.did)); + let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did)); let param_env = tcx.param_env(field.did); let layout = tcx.layout_of(param_env.and(ty)); // We are currently checking the type this field came from, so it must be local let span = tcx.hir().span_if_local(field.did).unwrap(); let zst = layout.is_ok_and(|layout| layout.is_zst()); - let align1 = layout.is_ok_and(|layout| layout.align.abi.bytes() == 1); + let align = layout.ok().map(|layout| layout.align.abi.bytes()); if !zst { - return (span, zst, align1, None); + return (span, zst, align, None); } fn check_non_exhaustive<'tcx>( tcx: TyCtxt<'tcx>, t: Ty<'tcx>, - ) -> ControlFlow<(&'static str, DefId, SubstsRef<'tcx>, bool)> { + ) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> { match t.kind() { ty::Tuple(list) => list.iter().try_for_each(|t| check_non_exhaustive(tcx, t)), ty::Array(ty, _) => check_non_exhaustive(tcx, *ty), @@ -1120,12 +1184,12 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) } } - (span, zst, align1, check_non_exhaustive(tcx, ty).break_value()) + (span, zst, align, check_non_exhaustive(tcx, ty).break_value()) }); let non_zst_fields = field_infos .clone() - .filter_map(|(span, zst, _align1, _non_exhaustive)| if !zst { Some(span) } else { None }); + .filter_map(|(span, zst, _align, _non_exhaustive)| if !zst { Some(span) } else { None }); let non_zst_count = non_zst_fields.clone().count(); if non_zst_count >= 2 { bad_non_zero_sized_fields(tcx, adt, non_zst_count, non_zst_fields, tcx.def_span(adt.did())); @@ -1133,19 +1197,28 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) let incompatible_zst_fields = field_infos.clone().filter(|(_, _, _, opt)| opt.is_some()).count(); let incompat = incompatible_zst_fields + non_zst_count >= 2 && non_zst_count < 2; - for (span, zst, align1, non_exhaustive) in field_infos { - if zst && !align1 { - struct_span_err!( + for (span, zst, align, non_exhaustive) in field_infos { + if zst && align != Some(1) { + let mut err = struct_span_err!( tcx.sess, span, E0691, "zero-sized field in transparent {} has alignment larger than 1", adt.descr(), - ) - .span_label(span, "has alignment larger than 1") - .emit(); + ); + + if let Some(align_bytes) = align { + err.span_label( + span, + format!("has alignment of {align_bytes}, which is larger than 1"), + ); + } else { + err.span_label(span, "may have alignment larger than 1"); + } + + err.emit(); } - if incompat && let Some((descr, def_id, substs, non_exhaustive)) = non_exhaustive { + if incompat && let Some((descr, def_id, args, non_exhaustive)) = non_exhaustive { tcx.struct_span_lint_hir( REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, tcx.hir().local_def_id_to_hir_id(adt.did().expect_local()), @@ -1157,7 +1230,7 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) } else { "contains private fields" }; - let field_ty = tcx.def_path_str_with_substs(def_id, substs); + let field_ty = tcx.def_path_str_with_args(def_id, args); lint .note(format!("this {descr} contains `{field_ty}`, which {note}, \ and makes it not a breaking change to become non-zero-sized in the future.")) @@ -1378,11 +1451,14 @@ pub(super) fn check_type_params_are_used<'tcx>( } } -pub(super) fn check_mod_item_types(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +pub(super) fn check_mod_item_types(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { let module = tcx.hir_module_items(module_def_id); for id in module.items() { check_item_type(tcx, id); } + if module_def_id == LocalModDefId::CRATE_DEF_ID { + super::entry::check_for_entry_fn(tcx); + } } fn async_opaque_type_cycle_error(tcx: TyCtxt<'_>, span: Span) -> ErrorGuaranteed { @@ -1481,7 +1557,9 @@ fn opaque_type_cycle_error( } for closure_def_id in visitor.closures { - let Some(closure_local_did) = closure_def_id.as_local() else { continue; }; + let Some(closure_local_did) = closure_def_id.as_local() else { + continue; + }; let typeck_results = tcx.typeck(closure_local_did); let mut label_match = |ty: Ty<'_>, span| { @@ -1549,7 +1627,7 @@ pub(super) fn check_generator_obligations(tcx: TyCtxt<'_>, def_id: LocalDefId) { .with_opaque_type_inference(DefiningAnchor::Bind(def_id)) .build(); - let mut fulfillment_cx = >::new(infcx.tcx); + let mut fulfillment_cx = >::new(&infcx); for (predicate, cause) in generator_interior_predicates { let obligation = Obligation::new(tcx, cause.clone(), param_env, *predicate); fulfillment_cx.register_predicate_obligation(&infcx, obligation); diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 8bf1e0e84a4fa..bd0ab6463f08a 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -1,7 +1,7 @@ use super::potentially_plural_count; use crate::errors::LifetimesOrBoundsMismatchOnTrait; use hir::def_id::{DefId, LocalDefId}; -use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_errors::{ pluralize, struct_span_err, Applicability, DiagnosticId, ErrorGuaranteed, MultiSpan, }; @@ -16,15 +16,16 @@ use rustc_infer::traits::util; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::util::ExplicitSelf; use rustc_middle::ty::{ - self, InternalSubsts, Ty, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, + self, GenericArgs, Ty, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, }; use rustc_middle::ty::{GenericParamDefKind, ToPredicate, TyCtxt}; -use rustc_span::Span; +use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _; use rustc_trait_selection::traits::{ self, ObligationCause, ObligationCauseCode, ObligationCtxt, Reveal, }; +use std::borrow::Cow; use std::iter; /// Checks that a method from an impl conforms to the signature of @@ -44,12 +45,7 @@ pub(super) fn compare_impl_method<'tcx>( debug!("compare_impl_method(impl_trait_ref={:?})", impl_trait_ref); let _: Result<_, ErrorGuaranteed> = try { - compare_self_type(tcx, impl_m, trait_m, impl_trait_ref)?; - compare_number_of_generics(tcx, impl_m, trait_m, false)?; - compare_generic_param_kinds(tcx, impl_m, trait_m, false)?; - compare_number_of_method_arguments(tcx, impl_m, trait_m)?; - compare_synthetic_generics(tcx, impl_m, trait_m)?; - compare_asyncness(tcx, impl_m, trait_m)?; + check_method_is_structurally_compatible(tcx, impl_m, trait_m, impl_trait_ref, false)?; compare_method_predicate_entailment( tcx, impl_m, @@ -60,7 +56,27 @@ pub(super) fn compare_impl_method<'tcx>( }; } -/// This function is best explained by example. Consider a trait with it's implementation: +/// Checks a bunch of different properties of the impl/trait methods for +/// compatibility, such as asyncness, number of argument, self receiver kind, +/// and number of early- and late-bound generics. +fn check_method_is_structurally_compatible<'tcx>( + tcx: TyCtxt<'tcx>, + impl_m: ty::AssocItem, + trait_m: ty::AssocItem, + impl_trait_ref: ty::TraitRef<'tcx>, + delay: bool, +) -> Result<(), ErrorGuaranteed> { + compare_self_type(tcx, impl_m, trait_m, impl_trait_ref, delay)?; + compare_number_of_generics(tcx, impl_m, trait_m, delay)?; + compare_generic_param_kinds(tcx, impl_m, trait_m, delay)?; + compare_number_of_method_arguments(tcx, impl_m, trait_m, delay)?; + compare_synthetic_generics(tcx, impl_m, trait_m, delay)?; + compare_asyncness(tcx, impl_m, trait_m, delay)?; + check_region_bounds_on_impl_item(tcx, impl_m, trait_m, delay)?; + Ok(()) +} + +/// This function is best explained by example. Consider a trait with its implementation: /// /// ```rust /// trait Trait<'t, T> { @@ -80,15 +96,15 @@ pub(super) fn compare_impl_method<'tcx>( /// For this we have to show that, assuming the bounds of the impl hold, the /// bounds of `trait_m` imply the bounds of `impl_m`. /// -/// We start out with `trait_to_impl_substs`, that maps the trait +/// We start out with `trait_to_impl_args`, that maps the trait /// type parameters to impl type parameters. This is taken from the /// impl trait reference: /// /// ```rust,ignore (pseudo-Rust) -/// trait_to_impl_substs = {'t => 'j, T => &'i U, Self => Foo} +/// trait_to_impl_args = {'t => 'j, T => &'i U, Self => Foo} /// ``` /// -/// We create a mapping `dummy_substs` that maps from the impl type +/// We create a mapping `dummy_args` that maps from the impl type /// parameters to fresh types and regions. For type parameters, /// this is the identity transform, but we could as well use any /// placeholder types. For regions, we convert from bound to free @@ -96,32 +112,32 @@ pub(super) fn compare_impl_method<'tcx>( /// declared on the impl or used in type parameter bounds). /// /// ```rust,ignore (pseudo-Rust) -/// impl_to_placeholder_substs = {'i => 'i0, U => U0, N => N0 } +/// impl_to_placeholder_args = {'i => 'i0, U => U0, N => N0 } /// ``` /// -/// Now we can apply `placeholder_substs` to the type of the impl method +/// Now we can apply `placeholder_args` to the type of the impl method /// to yield a new function type in terms of our fresh, placeholder /// types: /// /// ```rust,ignore (pseudo-Rust) -/// <'b> fn(t: &'i0 U0, m: &'b) -> Foo +/// <'b> fn(t: &'i0 U0, m: &'b N0) -> Foo /// ``` /// /// We now want to extract and substitute the type of the *trait* /// method and compare it. To do so, we must create a compound -/// substitution by combining `trait_to_impl_substs` and -/// `impl_to_placeholder_substs`, and also adding a mapping for the method +/// substitution by combining `trait_to_impl_args` and +/// `impl_to_placeholder_args`, and also adding a mapping for the method /// type parameters. We extend the mapping to also include /// the method parameters. /// /// ```rust,ignore (pseudo-Rust) -/// trait_to_placeholder_substs = { T => &'i0 U0, Self => Foo, M => N0 } +/// trait_to_placeholder_args = { T => &'i0 U0, Self => Foo, M => N0 } /// ``` /// /// Applying this to the trait method type yields: /// /// ```rust,ignore (pseudo-Rust) -/// <'a> fn(t: &'i0 U0, m: &'a) -> Foo +/// <'a> fn(t: &'i0 U0, m: &'a N0) -> Foo /// ``` /// /// This type is also the same but the name of the bound region (`'a` @@ -132,8 +148,8 @@ pub(super) fn compare_impl_method<'tcx>( /// satisfied by the implementation's method. /// /// We do this by creating a parameter environment which contains a -/// substitution corresponding to `impl_to_placeholder_substs`. We then build -/// `trait_to_placeholder_substs` and use it to convert the predicates contained +/// substitution corresponding to `impl_to_placeholder_args`. We then build +/// `trait_to_placeholder_args` and use it to convert the predicates contained /// in the `trait_m` generics to the placeholder form. /// /// Finally we register each of these predicates as an obligation and check that @@ -146,7 +162,7 @@ fn compare_method_predicate_entailment<'tcx>( impl_trait_ref: ty::TraitRef<'tcx>, check_implied_wf: CheckImpliedWfMode, ) -> Result<(), ErrorGuaranteed> { - let trait_to_impl_substs = impl_trait_ref.substs; + let trait_to_impl_args = impl_trait_ref.args; // This node-id should be used for the `body_id` field on each // `ObligationCause` (and the `FnCtxt`). @@ -166,19 +182,16 @@ fn compare_method_predicate_entailment<'tcx>( ); // Create mapping from impl to placeholder. - let impl_to_placeholder_substs = InternalSubsts::identity_for_item(tcx, impl_m.def_id); + let impl_to_placeholder_args = GenericArgs::identity_for_item(tcx, impl_m.def_id); // Create mapping from trait to placeholder. - let trait_to_placeholder_substs = - impl_to_placeholder_substs.rebase_onto(tcx, impl_m.container_id(tcx), trait_to_impl_substs); - debug!("compare_impl_method: trait_to_placeholder_substs={:?}", trait_to_placeholder_substs); + let trait_to_placeholder_args = + impl_to_placeholder_args.rebase_onto(tcx, impl_m.container_id(tcx), trait_to_impl_args); + debug!("compare_impl_method: trait_to_placeholder_args={:?}", trait_to_placeholder_args); let impl_m_predicates = tcx.predicates_of(impl_m.def_id); let trait_m_predicates = tcx.predicates_of(trait_m.def_id); - // Check region bounds. - check_region_bounds_on_impl_item(tcx, impl_m, trait_m, false)?; - // Create obligations for each predicate declared by the impl // definition in the context of the trait's parameter // environment. We can't just use `impl_env.caller_bounds`, @@ -198,7 +211,7 @@ fn compare_method_predicate_entailment<'tcx>( // if all constraints hold. hybrid_preds.predicates.extend( trait_m_predicates - .instantiate_own(tcx, trait_to_placeholder_substs) + .instantiate_own(tcx, trait_to_placeholder_args) .map(|(predicate, _)| predicate), ); @@ -206,11 +219,7 @@ fn compare_method_predicate_entailment<'tcx>( // The key step here is to update the caller_bounds's predicates to be // the new hybrid bounds we computed. let normalize_cause = traits::ObligationCause::misc(impl_m_span, impl_m_def_id); - let param_env = ty::ParamEnv::new( - tcx.mk_predicates(&hybrid_preds.predicates), - Reveal::UserFacing, - hir::Constness::NotConst, - ); + let param_env = ty::ParamEnv::new(tcx.mk_clauses(&hybrid_preds.predicates), Reveal::UserFacing); let param_env = traits::normalize_param_env_or_error(tcx, param_env, normalize_cause); let infcx = &tcx.infer_ctxt().build(); @@ -218,7 +227,7 @@ fn compare_method_predicate_entailment<'tcx>( debug!("compare_impl_method: caller_bounds={:?}", param_env.caller_bounds()); - let impl_m_own_bounds = impl_m_predicates.instantiate_own(tcx, impl_to_placeholder_substs); + let impl_m_own_bounds = impl_m_predicates.instantiate_own(tcx, impl_to_placeholder_args); for (predicate, span) in impl_m_own_bounds { let normalize_cause = traits::ObligationCause::misc(span, impl_m_def_id); let predicate = ocx.normalize(&normalize_cause, param_env, predicate); @@ -249,22 +258,19 @@ fn compare_method_predicate_entailment<'tcx>( // type. // Compute placeholder form of impl and trait method tys. - let tcx = infcx.tcx; - let mut wf_tys = FxIndexSet::default(); let unnormalized_impl_sig = infcx.instantiate_binder_with_fresh_vars( impl_m_span, infer::HigherRankedType, - tcx.fn_sig(impl_m.def_id).subst_identity(), + tcx.fn_sig(impl_m.def_id).instantiate_identity(), ); - let unnormalized_impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(unnormalized_impl_sig)); let norm_cause = ObligationCause::misc(impl_m_span, impl_m_def_id); let impl_sig = ocx.normalize(&norm_cause, param_env, unnormalized_impl_sig); debug!("compare_impl_method: impl_fty={:?}", impl_sig); - let trait_sig = tcx.fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs); + let trait_sig = tcx.fn_sig(trait_m.def_id).instantiate(tcx, trait_to_placeholder_args); let trait_sig = tcx.liberate_late_bound_regions(impl_m.def_id, trait_sig); // Next, add all inputs and output as well-formed tys. Importantly, @@ -275,7 +281,7 @@ fn compare_method_predicate_entailment<'tcx>( // We also have to add the normalized trait signature // as we don't normalize during implied bounds computation. wf_tys.extend(trait_sig.inputs_and_output.iter()); - let trait_fty = tcx.mk_fn_ptr(ty::Binder::dummy(trait_sig)); + let trait_fty = Ty::new_fn_ptr(tcx, ty::Binder::dummy(trait_sig)); debug!("compare_impl_method: trait_fty={:?}", trait_fty); @@ -301,15 +307,61 @@ fn compare_method_predicate_entailment<'tcx>( return Err(emitted); } - if check_implied_wf == CheckImpliedWfMode::Check { - // We need to check that the impl's args are well-formed given - // the hybrid param-env (impl + trait method where-clauses). - ocx.register_obligation(traits::Obligation::new( - infcx.tcx, - ObligationCause::dummy(), - param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(unnormalized_impl_fty.into())), - )); + if check_implied_wf == CheckImpliedWfMode::Check && !(impl_sig, trait_sig).references_error() { + // Select obligations to make progress on inference before processing + // the wf obligation below. + // FIXME(-Ztrait-solver=next): Not needed when the hack below is removed. + let errors = ocx.select_where_possible(); + if !errors.is_empty() { + let reported = infcx.err_ctxt().report_fulfillment_errors(&errors); + return Err(reported); + } + + // See #108544. Annoying, we can end up in cases where, because of winnowing, + // we pick param env candidates over a more general impl, leading to more + // stricter lifetime requirements than we would otherwise need. This can + // trigger the lint. Instead, let's only consider type outlives and + // region outlives obligations. + // + // FIXME(-Ztrait-solver=next): Try removing this hack again once + // the new solver is stable. + let mut wf_args: smallvec::SmallVec<[_; 4]> = + unnormalized_impl_sig.inputs_and_output.iter().map(|ty| ty.into()).collect(); + // Annoyingly, asking for the WF predicates of an array (with an unevaluated const (only?)) + // will give back the well-formed predicate of the same array. + let mut wf_args_seen: FxHashSet<_> = wf_args.iter().copied().collect(); + while let Some(arg) = wf_args.pop() { + let Some(obligations) = rustc_trait_selection::traits::wf::obligations( + infcx, + param_env, + impl_m_def_id, + 0, + arg, + impl_m_span, + ) else { + continue; + }; + for obligation in obligations { + debug!(?obligation); + match obligation.predicate.kind().skip_binder() { + // We need to register Projection oblgiations too, because we may end up with + // an implied `X::Item: 'a`, which gets desugared into `X::Item = ?0`, `?0: 'a`. + // If we only register the region outlives obligation, this leads to an unconstrained var. + // See `implied_bounds_entailment_alias_var` test. + ty::PredicateKind::Clause( + ty::ClauseKind::RegionOutlives(..) + | ty::ClauseKind::TypeOutlives(..) + | ty::ClauseKind::Projection(..), + ) => ocx.register_obligation(obligation), + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { + if wf_args_seen.insert(arg) { + wf_args.push(arg) + } + } + _ => {} + } + } + } } // Check that all obligations are satisfied by the implementation's @@ -342,7 +394,7 @@ fn compare_method_predicate_entailment<'tcx>( // lifetime parameters. let outlives_env = OutlivesEnvironment::with_bounds( param_env, - infcx.implied_bounds_tys(param_env, impl_m_def_id, wf_tys.clone()), + infcx.implied_bounds_tys(param_env, impl_m_def_id, wf_tys), ); let errors = infcx.resolve_regions(&outlives_env); if !errors.is_empty() { @@ -471,7 +523,8 @@ impl<'tcx> TypeFolder> for RemapLateBound<'_, 'tcx> { fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { if let ty::ReFree(fr) = *r { - self.tcx.mk_re_free( + ty::Region::new_free( + self.tcx, fr.scope, self.mapping.get(&fr.bound_region).copied().unwrap_or(fr.bound_region), ) @@ -532,6 +585,7 @@ fn compare_asyncness<'tcx>( tcx: TyCtxt<'tcx>, impl_m: ty::AssocItem, trait_m: ty::AssocItem, + delay: bool, ) -> Result<(), ErrorGuaranteed> { if tcx.asyncness(trait_m.def_id) == hir::IsAsync::Async { match tcx.fn_sig(impl_m.def_id).skip_binder().skip_binder().output().kind() { @@ -542,11 +596,14 @@ fn compare_asyncness<'tcx>( // We don't know if it's ok, but at least it's already an error. } _ => { - return Err(tcx.sess.emit_err(crate::errors::AsyncTraitImplShouldBeAsync { - span: tcx.def_span(impl_m.def_id), - method_name: trait_m.name, - trait_item_span: tcx.hir().span_if_local(trait_m.def_id), - })); + return Err(tcx + .sess + .create_err(crate::errors::AsyncTraitImplShouldBeAsync { + span: tcx.def_span(impl_m.def_id), + method_name: trait_m.name, + trait_item_span: tcx.hir().span_if_local(trait_m.def_id), + }) + .emit_unless(delay)); } }; } @@ -595,16 +652,14 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( let impl_m = tcx.opt_associated_item(impl_m_def_id.to_def_id()).unwrap(); let trait_m = tcx.opt_associated_item(impl_m.trait_item_def_id.unwrap()).unwrap(); let impl_trait_ref = - tcx.impl_trait_ref(impl_m.impl_container(tcx).unwrap()).unwrap().subst_identity(); + tcx.impl_trait_ref(impl_m.impl_container(tcx).unwrap()).unwrap().instantiate_identity(); let param_env = tcx.param_env(impl_m_def_id); // First, check a few of the same things as `compare_impl_method`, // just so we don't ICE during substitution later. - compare_number_of_generics(tcx, impl_m, trait_m, true)?; - compare_generic_param_kinds(tcx, impl_m, trait_m, true)?; - check_region_bounds_on_impl_item(tcx, impl_m, trait_m, true)?; + check_method_is_structurally_compatible(tcx, impl_m, trait_m, impl_trait_ref, true)?; - let trait_to_impl_substs = impl_trait_ref.substs; + let trait_to_impl_args = impl_trait_ref.args; let impl_m_hir_id = tcx.hir().local_def_id_to_hir_id(impl_m_def_id); let return_span = tcx.hir().fn_decl_by_hir_id(impl_m_hir_id).unwrap().output.span(); @@ -619,11 +674,11 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( ); // Create mapping from impl to placeholder. - let impl_to_placeholder_substs = InternalSubsts::identity_for_item(tcx, impl_m.def_id); + let impl_to_placeholder_args = GenericArgs::identity_for_item(tcx, impl_m.def_id); // Create mapping from trait to placeholder. - let trait_to_placeholder_substs = - impl_to_placeholder_substs.rebase_onto(tcx, impl_m.container_id(tcx), trait_to_impl_substs); + let trait_to_placeholder_args = + impl_to_placeholder_args.rebase_onto(tcx, impl_m.container_id(tcx), trait_to_impl_args); let infcx = &tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(infcx); @@ -633,10 +688,9 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( let impl_sig = ocx.normalize( &norm_cause, param_env, - infcx.instantiate_binder_with_fresh_vars( - return_span, - infer::HigherRankedType, - tcx.fn_sig(impl_m.def_id).subst_identity(), + tcx.liberate_late_bound_regions( + impl_m.def_id, + tcx.fn_sig(impl_m.def_id).instantiate_identity(), ), ); impl_sig.error_reported()?; @@ -647,18 +701,21 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( // them with inference variables. // We will use these inference variables to collect the hidden types of RPITITs. let mut collector = ImplTraitInTraitCollector::new(&ocx, return_span, param_env, impl_m_def_id); - let unnormalized_trait_sig = tcx - .liberate_late_bound_regions( - impl_m.def_id, - tcx.fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs), + let unnormalized_trait_sig = infcx + .instantiate_binder_with_fresh_vars( + return_span, + infer::HigherRankedType, + tcx.fn_sig(trait_m.def_id).instantiate(tcx, trait_to_placeholder_args), ) .fold_with(&mut collector); - debug_assert_ne!( - collector.types.len(), - 0, - "expect >1 RPITITs in call to `collect_return_position_impl_trait_in_trait_tys`" - ); + if !unnormalized_trait_sig.output().references_error() { + debug_assert_ne!( + collector.types.len(), + 0, + "expect >1 RPITITs in call to `collect_return_position_impl_trait_in_trait_tys`" + ); + } let trait_sig = ocx.normalize(&norm_cause, param_env, unnormalized_trait_sig); trait_sig.error_reported()?; @@ -684,7 +741,7 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( &cause, hir.get_if_local(impl_m.def_id) .and_then(|node| node.fn_decl()) - .map(|decl| (decl.output.span(), "return type in trait".to_owned())), + .map(|decl| (decl.output.span(), Cow::from("return type in trait"))), Some(infer::ValuePairs::Terms(ExpectedFound { expected: trait_return_ty.into(), found: impl_return_ty.into(), @@ -740,78 +797,92 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( ); ocx.resolve_regions_and_report_errors(impl_m_def_id, &outlives_env)?; - let mut collected_tys = FxHashMap::default(); - for (def_id, (ty, substs)) in collected_types { - match infcx.fully_resolve(ty) { - Ok(ty) => { + let mut remapped_types = FxHashMap::default(); + for (def_id, (ty, args)) in collected_types { + match infcx.fully_resolve((ty, args)) { + Ok((ty, args)) => { // `ty` contains free regions that we created earlier while liberating the // trait fn signature. However, projection normalization expects `ty` to // contains `def_id`'s early-bound regions. - let id_substs = InternalSubsts::identity_for_item(tcx, def_id); - debug!(?id_substs, ?substs); - let map: FxHashMap, ty::GenericArg<'tcx>> = - std::iter::zip(substs, id_substs).collect(); + let id_args = GenericArgs::identity_for_item(tcx, def_id); + debug!(?id_args, ?args); + let map: FxHashMap<_, _> = std::iter::zip(args, id_args) + .skip(tcx.generics_of(trait_m.def_id).count()) + .filter_map(|(a, b)| Some((a.as_region()?, b.as_region()?))) + .collect(); debug!(?map); // NOTE(compiler-errors): RPITITs, like all other RPITs, have early-bound - // region substs that are synthesized during AST lowering. These are substs - // that are appended to the parent substs (trait and trait method). However, + // region args that are synthesized during AST lowering. These are args + // that are appended to the parent args (trait and trait method). However, // we're trying to infer the unsubstituted type value of the RPITIT inside - // the *impl*, so we can later use the impl's method substs to normalize + // the *impl*, so we can later use the impl's method args to normalize // an RPITIT to a concrete type (`confirm_impl_trait_in_trait_candidate`). // // Due to the design of RPITITs, during AST lowering, we have no idea that // an impl method corresponds to a trait method with RPITITs in it. Therefore, - // we don't have a list of early-bound region substs for the RPITIT in the impl. + // we don't have a list of early-bound region args for the RPITIT in the impl. // Since early region parameters are index-based, we can't just rebase these - // (trait method) early-bound region substs onto the impl, and there's no - // guarantee that the indices from the trait substs and impl substs line up. - // So to fix this, we subtract the number of trait substs and add the number of - // impl substs to *renumber* these early-bound regions to their corresponding + // (trait method) early-bound region args onto the impl, and there's no + // guarantee that the indices from the trait args and impl args line up. + // So to fix this, we subtract the number of trait args and add the number of + // impl args to *renumber* these early-bound regions to their corresponding // indices in the impl's substitutions list. // - // Also, we only need to account for a difference in trait and impl substs, + // Also, we only need to account for a difference in trait and impl args, // since we previously enforce that the trait method and impl method have the // same generics. - let num_trait_substs = trait_to_impl_substs.len(); - let num_impl_substs = tcx.generics_of(impl_m.container_id(tcx)).params.len(); - let ty = tcx.fold_regions(ty, |region, _| { - match region.kind() { - // Remap all free regions, which correspond to late-bound regions in the function. - ty::ReFree(_) => {} - // Remap early-bound regions as long as they don't come from the `impl` itself. - ty::ReEarlyBound(ebr) if tcx.parent(ebr.def_id) != impl_m.container_id(tcx) => {} - _ => return region, - } - let Some(ty::ReEarlyBound(e)) = map.get(®ion.into()).map(|r| r.expect_region().kind()) - else { - return tcx.mk_re_error_with_message(return_span, "expected ReFree to map to ReEarlyBound") - }; - tcx.mk_re_early_bound(ty::EarlyBoundRegion { - def_id: e.def_id, - name: e.name, - index: (e.index as usize - num_trait_substs + num_impl_substs) as u32, - }) - }); - debug!(%ty); - collected_tys.insert(def_id, ty::EarlyBinder(ty)); + let num_trait_args = trait_to_impl_args.len(); + let num_impl_args = tcx.generics_of(impl_m.container_id(tcx)).params.len(); + let ty = match ty.try_fold_with(&mut RemapHiddenTyRegions { + tcx, + map, + num_trait_args, + num_impl_args, + def_id, + impl_def_id: impl_m.container_id(tcx), + ty, + return_span, + }) { + Ok(ty) => ty, + Err(guar) => Ty::new_error(tcx, guar), + }; + remapped_types.insert(def_id, ty::EarlyBinder::bind(ty)); } Err(err) => { let reported = tcx.sess.delay_span_bug( return_span, format!("could not fully resolve: {ty} => {err:?}"), ); - collected_tys.insert(def_id, ty::EarlyBinder(tcx.ty_error(reported))); + remapped_types.insert(def_id, ty::EarlyBinder::bind(Ty::new_error(tcx, reported))); } } } - Ok(&*tcx.arena.alloc(collected_tys)) + // We may not collect all RPITITs that we see in the HIR for a trait signature + // because an RPITIT was located within a missing item. Like if we have a sig + // returning `-> Missing`, that gets converted to `-> [type error]`, + // and when walking through the signature we end up never collecting the def id + // of the `impl Sized`. Insert that here, so we don't ICE later. + for assoc_item in tcx.associated_types_for_impl_traits_in_associated_fn(trait_m.def_id) { + if !remapped_types.contains_key(assoc_item) { + remapped_types.insert( + *assoc_item, + ty::EarlyBinder::bind(Ty::new_error_with_message( + tcx, + return_span, + "missing synthetic item for RPITIT", + )), + ); + } + } + + Ok(&*tcx.arena.alloc(remapped_types)) } struct ImplTraitInTraitCollector<'a, 'tcx> { ocx: &'a ObligationCtxt<'a, 'tcx>, - types: FxHashMap, ty::SubstsRef<'tcx>)>, + types: FxHashMap, ty::GenericArgsRef<'tcx>)>, span: Span, param_env: ty::ParamEnv<'tcx>, body_id: LocalDefId, @@ -840,8 +911,8 @@ impl<'tcx> TypeFolder> for ImplTraitInTraitCollector<'_, 'tcx> { if let Some((ty, _)) = self.types.get(&proj.def_id) { return *ty; } - //FIXME(RPITIT): Deny nested RPITIT in substs too - if proj.substs.has_escaping_bound_vars() { + //FIXME(RPITIT): Deny nested RPITIT in args too + if proj.args.has_escaping_bound_vars() { bug!("FIXME(RPITIT): error here"); } // Replace with infer var @@ -849,9 +920,9 @@ impl<'tcx> TypeFolder> for ImplTraitInTraitCollector<'_, 'tcx> { span: self.span, kind: TypeVariableOriginKind::MiscVariable, }); - self.types.insert(proj.def_id, (infer_ty, proj.substs)); + self.types.insert(proj.def_id, (infer_ty, proj.args)); // Recurse into bounds - for (pred, pred_span) in self.interner().explicit_item_bounds(proj.def_id).subst_iter_copied(self.interner(), proj.substs) { + for (pred, pred_span) in self.interner().explicit_item_bounds(proj.def_id).iter_instantiated_copied(self.interner(), proj.args) { let pred = pred.fold_with(self); let pred = self.ocx.normalize( &ObligationCause::misc(self.span, self.body_id), @@ -877,6 +948,97 @@ impl<'tcx> TypeFolder> for ImplTraitInTraitCollector<'_, 'tcx> { } } +struct RemapHiddenTyRegions<'tcx> { + tcx: TyCtxt<'tcx>, + map: FxHashMap, ty::Region<'tcx>>, + num_trait_args: usize, + num_impl_args: usize, + def_id: DefId, + impl_def_id: DefId, + ty: Ty<'tcx>, + return_span: Span, +} + +impl<'tcx> ty::FallibleTypeFolder> for RemapHiddenTyRegions<'tcx> { + type Error = ErrorGuaranteed; + + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result, Self::Error> { + if let ty::Alias(ty::Opaque, ty::AliasTy { args, def_id, .. }) = *t.kind() { + let mut mapped_args = Vec::with_capacity(args.len()); + for (arg, v) in std::iter::zip(args, self.tcx.variances_of(def_id)) { + mapped_args.push(match (arg.unpack(), v) { + // Skip uncaptured opaque args + (ty::GenericArgKind::Lifetime(_), ty::Bivariant) => arg, + _ => arg.try_fold_with(self)?, + }); + } + Ok(Ty::new_opaque(self.tcx, def_id, self.tcx.mk_args(&mapped_args))) + } else { + t.try_super_fold_with(self) + } + } + + fn try_fold_region( + &mut self, + region: ty::Region<'tcx>, + ) -> Result, Self::Error> { + match region.kind() { + // Remap all free regions, which correspond to late-bound regions in the function. + ty::ReFree(_) => {} + // Remap early-bound regions as long as they don't come from the `impl` itself, + // in which case we don't really need to renumber them. + ty::ReEarlyBound(ebr) if self.tcx.parent(ebr.def_id) != self.impl_def_id => {} + _ => return Ok(region), + } + + let e = if let Some(region) = self.map.get(®ion) { + if let ty::ReEarlyBound(e) = region.kind() { e } else { bug!() } + } else { + let guar = match region.kind() { + ty::ReEarlyBound(ty::EarlyBoundRegion { def_id, .. }) + | ty::ReFree(ty::FreeRegion { + bound_region: ty::BoundRegionKind::BrNamed(def_id, _), + .. + }) => { + let return_span = if let ty::Alias(ty::Opaque, opaque_ty) = self.ty.kind() { + self.tcx.def_span(opaque_ty.def_id) + } else { + self.return_span + }; + self.tcx + .sess + .struct_span_err( + return_span, + "return type captures more lifetimes than trait definition", + ) + .span_label(self.tcx.def_span(def_id), "this lifetime was captured") + .span_note( + self.tcx.def_span(self.def_id), + "hidden type must only reference lifetimes captured by this impl trait", + ) + .note(format!("hidden type inferred to be `{}`", self.ty)) + .emit() + } + _ => self.tcx.sess.delay_span_bug(DUMMY_SP, "should've been able to remap region"), + }; + return Err(guar); + }; + + Ok(ty::Region::new_early_bound( + self.tcx, + ty::EarlyBoundRegion { + def_id: e.def_id, + name: e.name, + index: (e.index as usize - self.num_trait_args + self.num_impl_args) as u32, + }, + )) + } +} + fn report_trait_method_mismatch<'tcx>( infcx: &InferCtxt<'tcx>, mut cause: ObligationCause<'tcx>, @@ -901,7 +1063,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 == impl_trait_ref.self_ty()) { + 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(), @@ -963,7 +1125,7 @@ fn report_trait_method_mismatch<'tcx>( infcx.err_ctxt().note_type_err( &mut diag, &cause, - trait_err_span.map(|sp| (sp, "type in trait".to_owned())), + trait_err_span.map(|sp| (sp, Cow::from("type in trait"))), Some(infer::ValuePairs::Sigs(ExpectedFound { expected: trait_sig, found: impl_sig })), terr, false, @@ -1095,6 +1257,7 @@ fn compare_self_type<'tcx>( impl_m: ty::AssocItem, trait_m: ty::AssocItem, impl_trait_ref: ty::TraitRef<'tcx>, + delay: bool, ) -> Result<(), ErrorGuaranteed> { // Try to give more informative error messages about self typing // mismatches. Note that any mismatch will also be detected @@ -1109,7 +1272,7 @@ fn compare_self_type<'tcx>( ty::ImplContainer => impl_trait_ref.self_ty(), ty::TraitContainer => tcx.types.self_param, }; - let self_arg_ty = tcx.fn_sig(method.def_id).subst_identity().input(0); + let self_arg_ty = tcx.fn_sig(method.def_id).instantiate_identity().input(0); let param_env = ty::ParamEnv::reveal_all(); let infcx = tcx.infer_ctxt().build(); @@ -1143,7 +1306,7 @@ fn compare_self_type<'tcx>( } else { err.note_trait_signature(trait_m.name, trait_m.signature(tcx)); } - return Err(err.emit()); + return Err(err.emit_unless(delay)); } (true, false) => { @@ -1164,7 +1327,7 @@ fn compare_self_type<'tcx>( err.note_trait_signature(trait_m.name, trait_m.signature(tcx)); } - return Err(err.emit()); + return Err(err.emit_unless(delay)); } } @@ -1214,7 +1377,7 @@ fn compare_number_of_generics<'tcx>( // has mismatched type or const generic arguments, then the method that it's // inheriting the generics from will also have mismatched arguments, and // we'll report an error for that instead. Delay a bug for safety, though. - if tcx.opt_rpitit_info(trait_.def_id).is_some() { + if trait_.is_impl_trait_in_trait() { return Err(tcx.sess.delay_span_bug( rustc_span::DUMMY_SP, "errors comparing numbers of generics of trait/impl functions were not emitted", @@ -1350,6 +1513,7 @@ fn compare_number_of_method_arguments<'tcx>( tcx: TyCtxt<'tcx>, impl_m: ty::AssocItem, trait_m: ty::AssocItem, + delay: bool, ) -> Result<(), ErrorGuaranteed> { let impl_m_fty = tcx.fn_sig(impl_m.def_id); let trait_m_fty = tcx.fn_sig(trait_m.def_id); @@ -1420,7 +1584,7 @@ fn compare_number_of_method_arguments<'tcx>( ), ); - return Err(err.emit()); + return Err(err.emit_unless(delay)); } Ok(()) @@ -1430,6 +1594,7 @@ fn compare_synthetic_generics<'tcx>( tcx: TyCtxt<'tcx>, impl_m: ty::AssocItem, trait_m: ty::AssocItem, + delay: bool, ) -> Result<(), ErrorGuaranteed> { // FIXME(chrisvittal) Clean up this function, list of FIXME items: // 1. Better messages for the span labels @@ -1549,7 +1714,7 @@ fn compare_synthetic_generics<'tcx>( } _ => unreachable!(), } - error_found = Some(err.emit()); + error_found = Some(err.emit_unless(delay)); } } if let Some(reported) = error_found { Err(reported) } else { Ok(()) } @@ -1562,19 +1727,19 @@ fn compare_synthetic_generics<'tcx>( /// ```rust,ignore (pseudo-Rust) /// trait Foo { /// fn foo(); -/// type bar; +/// type Bar; /// fn baz(); -/// type blah; +/// type Blah; /// } /// /// impl Foo for () { /// fn foo() {} /// //~^ error -/// type bar {} +/// type Bar = (); /// //~^ error /// fn baz() {} /// //~^ error -/// type blah = u32; +/// type Blah = u32; /// //~^ error /// } /// ``` @@ -1631,10 +1796,10 @@ fn compare_generic_param_kinds<'tcx>( format!( "{} const parameter of type `{}`", prefix, - tcx.type_of(param.def_id).subst_identity() + tcx.type_of(param.def_id).instantiate_identity() ) } - Type { .. } => format!("{} type parameter", prefix), + Type { .. } => format!("{prefix} type parameter"), Lifetime { .. } => unreachable!(), }; @@ -1662,37 +1827,83 @@ pub(super) fn compare_impl_const_raw( let impl_const_item = tcx.associated_item(impl_const_item_def); let trait_const_item = tcx.associated_item(trait_const_item_def); let impl_trait_ref = - tcx.impl_trait_ref(impl_const_item.container_id(tcx)).unwrap().subst_identity(); - debug!("compare_const_impl(impl_trait_ref={:?})", impl_trait_ref); + tcx.impl_trait_ref(impl_const_item.container_id(tcx)).unwrap().instantiate_identity(); - let impl_c_span = tcx.def_span(impl_const_item_def.to_def_id()); + debug!("compare_impl_const(impl_trait_ref={:?})", impl_trait_ref); - let infcx = tcx.infer_ctxt().build(); - let param_env = tcx.param_env(impl_const_item_def.to_def_id()); - let ocx = ObligationCtxt::new(&infcx); + compare_number_of_generics(tcx, impl_const_item, trait_const_item, false)?; + compare_generic_param_kinds(tcx, impl_const_item, trait_const_item, false)?; + compare_const_predicate_entailment(tcx, impl_const_item, trait_const_item, impl_trait_ref) +} + +/// The equivalent of [compare_method_predicate_entailment], but for associated constants +/// instead of associated functions. +// FIXME(generic_const_items): If possible extract the common parts of `compare_{type,const}_predicate_entailment`. +fn compare_const_predicate_entailment<'tcx>( + tcx: TyCtxt<'tcx>, + impl_ct: ty::AssocItem, + trait_ct: ty::AssocItem, + impl_trait_ref: ty::TraitRef<'tcx>, +) -> Result<(), ErrorGuaranteed> { + let impl_ct_def_id = impl_ct.def_id.expect_local(); + let impl_ct_span = tcx.def_span(impl_ct_def_id); // The below is for the most part highly similar to the procedure // for methods above. It is simpler in many respects, especially // because we shouldn't really have to deal with lifetimes or // predicates. In fact some of this should probably be put into // shared functions because of DRY violations... - let trait_to_impl_substs = impl_trait_ref.substs; + let impl_args = GenericArgs::identity_for_item(tcx, impl_ct.def_id); + let trait_to_impl_args = + impl_args.rebase_onto(tcx, impl_ct.container_id(tcx), impl_trait_ref.args); // Create a parameter environment that represents the implementation's // method. // Compute placeholder form of impl and trait const tys. - let impl_ty = tcx.type_of(impl_const_item_def.to_def_id()).subst_identity(); - let trait_ty = tcx.type_of(trait_const_item_def).subst(tcx, trait_to_impl_substs); - let mut cause = ObligationCause::new( - impl_c_span, - impl_const_item_def, - ObligationCauseCode::CompareImplItemObligation { - impl_item_def_id: impl_const_item_def, - trait_item_def_id: trait_const_item_def, - kind: impl_const_item.kind, - }, + let impl_ty = tcx.type_of(impl_ct_def_id).instantiate_identity(); + + let trait_ty = tcx.type_of(trait_ct.def_id).instantiate(tcx, trait_to_impl_args); + let code = ObligationCauseCode::CompareImplItemObligation { + impl_item_def_id: impl_ct_def_id, + trait_item_def_id: trait_ct.def_id, + kind: impl_ct.kind, + }; + let mut cause = ObligationCause::new(impl_ct_span, impl_ct_def_id, code.clone()); + + let impl_ct_predicates = tcx.predicates_of(impl_ct.def_id); + let trait_ct_predicates = tcx.predicates_of(trait_ct.def_id); + + check_region_bounds_on_impl_item(tcx, impl_ct, trait_ct, false)?; + + // The predicates declared by the impl definition, the trait and the + // associated const in the trait are assumed. + let impl_predicates = tcx.predicates_of(impl_ct_predicates.parent.unwrap()); + let mut hybrid_preds = impl_predicates.instantiate_identity(tcx); + hybrid_preds.predicates.extend( + trait_ct_predicates + .instantiate_own(tcx, trait_to_impl_args) + .map(|(predicate, _)| predicate), ); + let param_env = ty::ParamEnv::new(tcx.mk_clauses(&hybrid_preds.predicates), Reveal::UserFacing); + let param_env = traits::normalize_param_env_or_error( + tcx, + param_env, + ObligationCause::misc(impl_ct_span, impl_ct_def_id), + ); + + let infcx = tcx.infer_ctxt().build(); + let ocx = ObligationCtxt::new(&infcx); + + let impl_ct_own_bounds = impl_ct_predicates.instantiate_own(tcx, impl_args); + for (predicate, span) in impl_ct_own_bounds { + let cause = ObligationCause::misc(span, impl_ct_def_id); + let predicate = ocx.normalize(&cause, param_env, predicate); + + let cause = ObligationCause::new(span, impl_ct_def_id, code.clone()); + ocx.register_obligation(traits::Obligation::new(tcx, cause, param_env, predicate)); + } + // There is no "body" here, so just pass dummy id. let impl_ty = ocx.normalize(&cause, param_env, impl_ty); @@ -1711,7 +1922,7 @@ pub(super) fn compare_impl_const_raw( ); // Locate the Span containing just the type of the offending impl - let (ty, _) = tcx.hir().expect_impl_item(impl_const_item_def).expect_const(); + let (ty, _) = tcx.hir().expect_impl_item(impl_ct_def_id).expect_const(); cause.span = ty.span; let mut diag = struct_span_err!( @@ -1719,19 +1930,19 @@ pub(super) fn compare_impl_const_raw( cause.span, E0326, "implemented const `{}` has an incompatible type for trait", - trait_const_item.name + trait_ct.name ); - let trait_c_span = trait_const_item_def.as_local().map(|trait_c_def_id| { + let trait_c_span = trait_ct.def_id.as_local().map(|trait_ct_def_id| { // Add a label to the Span containing just the type of the const - let (ty, _) = tcx.hir().expect_trait_item(trait_c_def_id).expect_const(); + let (ty, _) = tcx.hir().expect_trait_item(trait_ct_def_id).expect_const(); ty.span }); infcx.err_ctxt().note_type_err( &mut diag, &cause, - trait_c_span.map(|span| (span, "type in trait".to_owned())), + trait_c_span.map(|span| (span, Cow::from("type in trait"))), Some(infer::ValuePairs::Terms(ExpectedFound { expected: trait_ty.into(), found: impl_ty.into(), @@ -1751,7 +1962,7 @@ pub(super) fn compare_impl_const_raw( } let outlives_env = OutlivesEnvironment::new(param_env); - ocx.resolve_regions_and_report_errors(impl_const_item_def, &outlives_env) + ocx.resolve_regions_and_report_errors(impl_ct_def_id, &outlives_env) } pub(super) fn compare_impl_ty<'tcx>( @@ -1778,26 +1989,26 @@ fn compare_type_predicate_entailment<'tcx>( trait_ty: ty::AssocItem, impl_trait_ref: ty::TraitRef<'tcx>, ) -> Result<(), ErrorGuaranteed> { - let impl_substs = InternalSubsts::identity_for_item(tcx, impl_ty.def_id); - let trait_to_impl_substs = - impl_substs.rebase_onto(tcx, impl_ty.container_id(tcx), impl_trait_ref.substs); + let impl_args = GenericArgs::identity_for_item(tcx, impl_ty.def_id); + let trait_to_impl_args = + impl_args.rebase_onto(tcx, impl_ty.container_id(tcx), impl_trait_ref.args); let impl_ty_predicates = tcx.predicates_of(impl_ty.def_id); let trait_ty_predicates = tcx.predicates_of(trait_ty.def_id); check_region_bounds_on_impl_item(tcx, impl_ty, trait_ty, false)?; - let impl_ty_own_bounds = impl_ty_predicates.instantiate_own(tcx, impl_substs); + let impl_ty_own_bounds = impl_ty_predicates.instantiate_own(tcx, impl_args); if impl_ty_own_bounds.len() == 0 { // Nothing to check. return Ok(()); } - // This `HirId` should be used for the `body_id` field on each + // This `DefId` should be used for the `body_id` field on each // `ObligationCause` (and the `FnCtxt`). This is what // `regionck_item` expects. let impl_ty_def_id = impl_ty.def_id.expect_local(); - debug!("compare_type_predicate_entailment: trait_to_impl_substs={:?}", trait_to_impl_substs); + debug!("compare_type_predicate_entailment: trait_to_impl_args={:?}", trait_to_impl_args); // The predicates declared by the impl definition, the trait and the // associated type in the trait are assumed. @@ -1805,19 +2016,15 @@ fn compare_type_predicate_entailment<'tcx>( let mut hybrid_preds = impl_predicates.instantiate_identity(tcx); hybrid_preds.predicates.extend( trait_ty_predicates - .instantiate_own(tcx, trait_to_impl_substs) + .instantiate_own(tcx, trait_to_impl_args) .map(|(predicate, _)| predicate), ); debug!("compare_type_predicate_entailment: bounds={:?}", hybrid_preds); let impl_ty_span = tcx.def_span(impl_ty_def_id); - let normalize_cause = traits::ObligationCause::misc(impl_ty_span, impl_ty_def_id); - let param_env = ty::ParamEnv::new( - tcx.mk_predicates(&hybrid_preds.predicates), - Reveal::UserFacing, - hir::Constness::NotConst, - ); + let normalize_cause = ObligationCause::misc(impl_ty_span, impl_ty_def_id); + let param_env = ty::ParamEnv::new(tcx.mk_clauses(&hybrid_preds.predicates), Reveal::UserFacing); let param_env = traits::normalize_param_env_or_error(tcx, param_env, normalize_cause); let infcx = tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); @@ -1861,7 +2068,7 @@ fn compare_type_predicate_entailment<'tcx>( /// /// trait X { type Y: Copy } impl X for T { type Y = S; } /// -/// We are able to normalize `::U` to `S`, and so when we check the +/// We are able to normalize `::Y` to `S`, and so when we check the /// impl is well-formed we have to prove `S: Copy`. /// /// For default associated types the normalization is not possible (the value @@ -1883,9 +2090,9 @@ pub(super) fn check_type_bounds<'tcx>( // } // // - `impl_trait_ref` would be `<(A, B) as Foo>` - // - `normalize_impl_ty_substs` would be `[A, B, ^0.0]` (`^0.0` here is the bound var with db 0 and index 0) + // - `normalize_impl_ty_args` would be `[A, B, ^0.0]` (`^0.0` here is the bound var with db 0 and index 0) // - `normalize_impl_ty` would be `Wrapper` - // - `rebased_substs` would be `[(A, B), u32, ^0.0]`, combining the substs from + // - `rebased_args` would be `[(A, B), u32, ^0.0]`, combining the args from // the *trait* with the generic associated type parameters (as bound vars). // // A note regarding the use of bound vars here: @@ -1915,14 +2122,17 @@ pub(super) fn check_type_bounds<'tcx>( // the trait (notably, that X: Eq and T: Family). let mut bound_vars: smallvec::SmallVec<[ty::BoundVariableKind; 8]> = smallvec::SmallVec::with_capacity(tcx.generics_of(impl_ty.def_id).params.len()); - // Extend the impl's identity substs with late-bound GAT vars - let normalize_impl_ty_substs = ty::InternalSubsts::identity_for_item(tcx, container_id) - .extend_to(tcx, impl_ty.def_id, |param, _| match param.kind { + // Extend the impl's identity args with late-bound GAT vars + let normalize_impl_ty_args = ty::GenericArgs::identity_for_item(tcx, container_id).extend_to( + tcx, + impl_ty.def_id, + |param, _| match param.kind { GenericParamDefKind::Type { .. } => { let kind = ty::BoundTyKind::Param(param.def_id, param.name); let bound_var = ty::BoundVariableKind::Ty(kind); bound_vars.push(bound_var); - tcx.mk_bound( + Ty::new_bound( + tcx, ty::INNERMOST, ty::BoundTy { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind }, ) @@ -1932,7 +2142,8 @@ pub(super) fn check_type_bounds<'tcx>( let kind = ty::BoundRegionKind::BrNamed(param.def_id, param.name); let bound_var = ty::BoundVariableKind::Region(kind); bound_vars.push(bound_var); - tcx.mk_re_late_bound( + ty::Region::new_late_bound( + tcx, ty::INNERMOST, ty::BoundRegion { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind }, ) @@ -1941,18 +2152,18 @@ pub(super) fn check_type_bounds<'tcx>( GenericParamDefKind::Const { .. } => { let bound_var = ty::BoundVariableKind::Const; bound_vars.push(bound_var); - tcx.mk_const( - ty::ConstKind::Bound( - ty::INNERMOST, - ty::BoundVar::from_usize(bound_vars.len() - 1), - ), + ty::Const::new_bound( + tcx, + ty::INNERMOST, + ty::BoundVar::from_usize(bound_vars.len() - 1), tcx.type_of(param.def_id) .no_bound_vars() .expect("const parameter types cannot be generic"), ) .into() } - }); + }, + ); // When checking something like // // trait X { type Y: PartialEq<::Y> } @@ -1962,15 +2173,14 @@ pub(super) fn check_type_bounds<'tcx>( // we want ::Y to normalize to S. This is valid because we are // checking the default value specifically here. Add this equality to the // ParamEnv for normalization specifically. - let normalize_impl_ty = tcx.type_of(impl_ty.def_id).subst(tcx, normalize_impl_ty_substs); - let rebased_substs = - normalize_impl_ty_substs.rebase_onto(tcx, container_id, impl_trait_ref.substs); + let normalize_impl_ty = tcx.type_of(impl_ty.def_id).instantiate(tcx, normalize_impl_ty_args); + let rebased_args = normalize_impl_ty_args.rebase_onto(tcx, container_id, impl_trait_ref.args); let bound_vars = tcx.mk_bound_variable_kinds(&bound_vars); let normalize_param_env = { let mut predicates = param_env.caller_bounds().iter().collect::>(); match normalize_impl_ty.kind() { ty::Alias(ty::Projection, proj) - if proj.def_id == trait_ty.def_id && proj.substs == rebased_substs => + if proj.def_id == trait_ty.def_id && proj.args == rebased_args => { // Don't include this predicate if the projected type is // exactly the same as the projection. This can occur in @@ -1981,7 +2191,7 @@ pub(super) fn check_type_bounds<'tcx>( _ => predicates.push( ty::Binder::bind_with_vars( ty::ProjectionPredicate { - projection_ty: tcx.mk_alias_ty(trait_ty.def_id, rebased_substs), + projection_ty: tcx.mk_alias_ty(trait_ty.def_id, rebased_args), term: normalize_impl_ty.into(), }, bound_vars, @@ -1989,13 +2199,13 @@ pub(super) fn check_type_bounds<'tcx>( .to_predicate(tcx), ), }; - ty::ParamEnv::new(tcx.mk_predicates(&predicates), Reveal::UserFacing, param_env.constness()) + ty::ParamEnv::new(tcx.mk_clauses(&predicates), Reveal::UserFacing) }; debug!(?normalize_param_env); let impl_ty_def_id = impl_ty.def_id.expect_local(); - let impl_ty_substs = InternalSubsts::identity_for_item(tcx, impl_ty.def_id); - let rebased_substs = impl_ty_substs.rebase_onto(tcx, container_id, impl_trait_ref.substs); + let impl_ty_args = GenericArgs::identity_for_item(tcx, impl_ty.def_id); + let rebased_args = impl_ty_args.rebase_onto(tcx, container_id, impl_trait_ref.args); let infcx = tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); @@ -2003,7 +2213,7 @@ pub(super) fn check_type_bounds<'tcx>( // A synthetic impl Trait for RPITIT desugaring has no HIR, which we currently use to get the // span for an impl's associated type. Instead, for these, use the def_span for the synthesized // associated type. - let impl_ty_span = if tcx.opt_rpitit_info(impl_ty.def_id).is_some() { + let impl_ty_span = if impl_ty.is_impl_trait_in_trait() { tcx.def_span(impl_ty_def_id) } else { match tcx.hir().get_by_def_id(impl_ty_def_id) { @@ -2015,7 +2225,7 @@ pub(super) fn check_type_bounds<'tcx>( _ => bug!(), } }; - let assumed_wf_types = ocx.assumed_wf_types(param_env, impl_ty_span, impl_ty_def_id); + let assumed_wf_types = ocx.assumed_wf_types_and_report_errors(param_env, impl_ty_def_id)?; let normalize_cause = ObligationCause::new( impl_ty_span, @@ -2036,7 +2246,7 @@ pub(super) fn check_type_bounds<'tcx>( let obligations: Vec<_> = tcx .explicit_item_bounds(trait_ty.def_id) - .subst_iter_copied(tcx, rebased_substs) + .iter_instantiated_copied(tcx, rebased_args) .map(|(concrete_ty_bound, span)| { debug!("check_type_bounds: concrete_ty_bound = {:?}", concrete_ty_bound); traits::Obligation::new(tcx, mk_cause(span), param_env, concrete_ty_bound) diff --git a/compiler/rustc_hir_analysis/src/check/dropck.rs b/compiler/rustc_hir_analysis/src/check/dropck.rs index e0ba255cc069c..dda3f7425697b 100644 --- a/compiler/rustc_hir_analysis/src/check/dropck.rs +++ b/compiler/rustc_hir_analysis/src/check/dropck.rs @@ -5,8 +5,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::{struct_span_err, ErrorGuaranteed}; use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::{RegionResolutionError, TyCtxtInferExt}; -use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::util::CheckRegions; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, TyCtxt}; use rustc_trait_selection::traits::{self, ObligationCtxt}; @@ -44,21 +44,21 @@ pub fn check_drop_impl(tcx: TyCtxt<'_>, drop_impl_did: DefId) -> Result<(), Erro })); } } - let dtor_self_type = tcx.type_of(drop_impl_did).subst_identity(); + let dtor_self_type = tcx.type_of(drop_impl_did).instantiate_identity(); match dtor_self_type.kind() { - ty::Adt(adt_def, adt_to_impl_substs) => { + ty::Adt(adt_def, adt_to_impl_args) => { ensure_drop_params_and_item_params_correspond( tcx, drop_impl_did.expect_local(), adt_def.did(), - adt_to_impl_substs, + adt_to_impl_args, )?; ensure_drop_predicates_are_implied_by_item_defn( tcx, drop_impl_did.expect_local(), adt_def.did().expect_local(), - adt_to_impl_substs, + adt_to_impl_args, ) } _ => { @@ -79,10 +79,11 @@ fn ensure_drop_params_and_item_params_correspond<'tcx>( tcx: TyCtxt<'tcx>, drop_impl_did: LocalDefId, self_type_did: DefId, - adt_to_impl_substs: SubstsRef<'tcx>, + adt_to_impl_args: GenericArgsRef<'tcx>, ) -> Result<(), ErrorGuaranteed> { - let Err(arg) = tcx.uses_unique_generic_params(adt_to_impl_substs, CheckRegions::OnlyEarlyBound) else { - return Ok(()) + let Err(arg) = tcx.uses_unique_generic_params(adt_to_impl_args, CheckRegions::OnlyEarlyBound) + else { + return Ok(()); }; let drop_impl_span = tcx.def_span(drop_impl_did); @@ -114,12 +115,12 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( tcx: TyCtxt<'tcx>, drop_impl_def_id: LocalDefId, adt_def_id: LocalDefId, - adt_to_impl_substs: SubstsRef<'tcx>, + adt_to_impl_args: GenericArgsRef<'tcx>, ) -> Result<(), ErrorGuaranteed> { let infcx = tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); - // Take the param-env of the adt and substitute the substs that show up in + // Take the param-env of the adt and substitute the args that show up in // the implementation's self type. This gives us the assumptions that the // self ty of the implementation is allowed to know just from it being a // well-formed adt, since that's all we're allowed to assume while proving @@ -128,9 +129,8 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( // We don't need to normalize this param-env or anything, since we're only // substituting it with free params, so no additional param-env normalization // can occur on top of what has been done in the param_env query itself. - let param_env = ty::EarlyBinder(tcx.param_env(adt_def_id)) - .subst(tcx, adt_to_impl_substs) - .with_constness(tcx.constness(drop_impl_def_id)); + let param_env = + ty::EarlyBinder::bind(tcx.param_env(adt_def_id)).instantiate(tcx, adt_to_impl_args); for (pred, span) in tcx.predicates_of(drop_impl_def_id).instantiate_identity(tcx) { let normalize_cause = traits::ObligationCause::misc(span, adt_def_id); @@ -183,7 +183,7 @@ fn ensure_drop_predicates_are_implied_by_item_defn<'tcx>( } RegionResolutionError::SubSupConflict(_, _, _, a, _, b, _) => format!("{b}: {a}"), RegionResolutionError::UpperBoundUniverseConflict(a, _, _, _, b) => { - format!("{b}: {a}", a = tcx.mk_re_var(a)) + format!("{b}: {a}", a = ty::Region::new_var(tcx, a)) } }; guar = Some( diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs new file mode 100644 index 0000000000000..fcaefe0261b0b --- /dev/null +++ b/compiler/rustc_hir_analysis/src/check/entry.rs @@ -0,0 +1,277 @@ +use rustc_hir as hir; +use rustc_hir::Node; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_session::config::EntryFnType; +use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; +use rustc_span::{symbol::sym, Span}; +use rustc_target::spec::abi::Abi; +use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; +use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode}; + +use std::ops::Not; + +use crate::errors; +use crate::require_same_types; + +pub(crate) fn check_for_entry_fn(tcx: TyCtxt<'_>) { + match tcx.entry_fn(()) { + Some((def_id, EntryFnType::Main { .. })) => check_main_fn_ty(tcx, def_id), + Some((def_id, EntryFnType::Start)) => check_start_fn_ty(tcx, def_id), + _ => {} + } +} + +fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { + let main_fnsig = tcx.fn_sig(main_def_id).instantiate_identity(); + let main_span = tcx.def_span(main_def_id); + + fn main_fn_diagnostics_def_id(tcx: TyCtxt<'_>, def_id: DefId, sp: Span) -> LocalDefId { + if let Some(local_def_id) = def_id.as_local() { + let hir_type = tcx.type_of(local_def_id).instantiate_identity(); + if !matches!(hir_type.kind(), ty::FnDef(..)) { + span_bug!(sp, "main has a non-function type: found `{}`", hir_type); + } + local_def_id + } else { + CRATE_DEF_ID + } + } + + fn main_fn_generics_params_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { + if !def_id.is_local() { + return None; + } + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); + match tcx.hir().find(hir_id) { + Some(Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. })) => { + generics.params.is_empty().not().then_some(generics.span) + } + _ => { + span_bug!(tcx.def_span(def_id), "main has a non-function type"); + } + } + } + + fn main_fn_where_clauses_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { + if !def_id.is_local() { + return None; + } + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); + match tcx.hir().find(hir_id) { + Some(Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. })) => { + Some(generics.where_clause_span) + } + _ => { + span_bug!(tcx.def_span(def_id), "main has a non-function type"); + } + } + } + + fn main_fn_asyncness_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { + if !def_id.is_local() { + return None; + } + Some(tcx.def_span(def_id)) + } + + fn main_fn_return_type_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { + if !def_id.is_local() { + return None; + } + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); + match tcx.hir().find(hir_id) { + Some(Node::Item(hir::Item { kind: hir::ItemKind::Fn(fn_sig, _, _), .. })) => { + Some(fn_sig.decl.output.span()) + } + _ => { + span_bug!(tcx.def_span(def_id), "main has a non-function type"); + } + } + } + + let mut error = false; + let main_diagnostics_def_id = main_fn_diagnostics_def_id(tcx, main_def_id, main_span); + let main_fn_generics = tcx.generics_of(main_def_id); + let main_fn_predicates = tcx.predicates_of(main_def_id); + if main_fn_generics.count() != 0 || !main_fnsig.bound_vars().is_empty() { + let generics_param_span = main_fn_generics_params_span(tcx, main_def_id); + tcx.sess.emit_err(errors::MainFunctionGenericParameters { + span: generics_param_span.unwrap_or(main_span), + label_span: generics_param_span, + }); + error = true; + } else if !main_fn_predicates.predicates.is_empty() { + // generics may bring in implicit predicates, so we skip this check if generics is present. + let generics_where_clauses_span = main_fn_where_clauses_span(tcx, main_def_id); + tcx.sess.emit_err(errors::WhereClauseOnMain { + span: generics_where_clauses_span.unwrap_or(main_span), + generics_span: generics_where_clauses_span, + }); + error = true; + } + + let main_asyncness = tcx.asyncness(main_def_id); + if let hir::IsAsync::Async = main_asyncness { + let asyncness_span = main_fn_asyncness_span(tcx, main_def_id); + tcx.sess.emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span }); + error = true; + } + + for attr in tcx.get_attrs(main_def_id, sym::track_caller) { + tcx.sess.emit_err(errors::TrackCallerOnMain { span: attr.span, annotated: main_span }); + error = true; + } + + if !tcx.codegen_fn_attrs(main_def_id).target_features.is_empty() + // Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988 + && !tcx.sess.target.is_like_wasm + && !tcx.sess.opts.actually_rustdoc + { + tcx.sess.emit_err(errors::TargetFeatureOnMain { main: main_span }); + error = true; + } + + if error { + return; + } + + // Main should have no WC, so empty param env is OK here. + let param_env = ty::ParamEnv::empty(); + let expected_return_type; + if let Some(term_did) = tcx.lang_items().termination() { + let return_ty = main_fnsig.output(); + let return_ty_span = main_fn_return_type_span(tcx, main_def_id).unwrap_or(main_span); + if !return_ty.bound_vars().is_empty() { + tcx.sess.emit_err(errors::MainFunctionReturnTypeGeneric { span: return_ty_span }); + error = true; + } + let return_ty = return_ty.skip_binder(); + let infcx = tcx.infer_ctxt().build(); + let cause = traits::ObligationCause::new( + return_ty_span, + main_diagnostics_def_id, + ObligationCauseCode::MainFunctionType, + ); + let ocx = traits::ObligationCtxt::new(&infcx); + let norm_return_ty = ocx.normalize(&cause, param_env, return_ty); + ocx.register_bound(cause, param_env, norm_return_ty, term_did); + let errors = ocx.select_all_or_error(); + if !errors.is_empty() { + infcx.err_ctxt().report_fulfillment_errors(&errors); + error = true; + } + // now we can take the return type of the given main function + expected_return_type = main_fnsig.output(); + } else { + // standard () main return type + expected_return_type = ty::Binder::dummy(Ty::new_unit(tcx)); + } + + if error { + return; + } + + let se_ty = Ty::new_fn_ptr( + tcx, + expected_return_type.map_bound(|expected_return_type| { + tcx.mk_fn_sig([], expected_return_type, false, hir::Unsafety::Normal, Abi::Rust) + }), + ); + + require_same_types( + tcx, + &ObligationCause::new( + main_span, + main_diagnostics_def_id, + ObligationCauseCode::MainFunctionType, + ), + param_env, + se_ty, + Ty::new_fn_ptr(tcx, main_fnsig), + ); +} + +fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) { + let start_def_id = start_def_id.expect_local(); + let start_id = tcx.hir().local_def_id_to_hir_id(start_def_id); + let start_span = tcx.def_span(start_def_id); + let start_t = tcx.type_of(start_def_id).instantiate_identity(); + match start_t.kind() { + ty::FnDef(..) => { + if let Some(Node::Item(it)) = tcx.hir().find(start_id) { + if let hir::ItemKind::Fn(sig, generics, _) = &it.kind { + let mut error = false; + if !generics.params.is_empty() { + tcx.sess.emit_err(errors::StartFunctionParameters { span: generics.span }); + error = true; + } + if generics.has_where_clause_predicates { + tcx.sess.emit_err(errors::StartFunctionWhere { + span: generics.where_clause_span, + }); + error = true; + } + if let hir::IsAsync::Async = sig.header.asyncness { + let span = tcx.def_span(it.owner_id); + tcx.sess.emit_err(errors::StartAsync { span: span }); + error = true; + } + + let attrs = tcx.hir().attrs(start_id); + for attr in attrs { + if attr.has_name(sym::track_caller) { + tcx.sess.emit_err(errors::StartTrackCaller { + span: attr.span, + start: start_span, + }); + error = true; + } + if attr.has_name(sym::target_feature) + // Calling functions with `#[target_feature]` is + // not unsafe on WASM, see #84988 + && !tcx.sess.target.is_like_wasm + && !tcx.sess.opts.actually_rustdoc + { + tcx.sess.emit_err(errors::StartTargetFeature { + span: attr.span, + start: start_span, + }); + error = true; + } + } + + if error { + return; + } + } + } + + let se_ty = Ty::new_fn_ptr( + tcx, + ty::Binder::dummy(tcx.mk_fn_sig( + [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))], + tcx.types.isize, + false, + hir::Unsafety::Normal, + Abi::Rust, + )), + ); + + require_same_types( + tcx, + &ObligationCause::new( + start_span, + start_def_id, + ObligationCauseCode::StartFunctionType, + ), + ty::ParamEnv::empty(), // start should not have any where bounds. + se_ty, + Ty::new_fn_ptr(tcx, tcx.fn_sig(start_def_id).instantiate_identity()), + ); + } + _ => { + span_bug!(start_span, "start has a non-function type: found `{}`", start_t); + } + } +} diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index e8785235c8318..f89e2e5c25bf2 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -11,7 +11,7 @@ use hir::def_id::DefId; use rustc_errors::{struct_span_err, DiagnosticMessage}; use rustc_hir as hir; use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_target::spec::abi::Abi; @@ -53,14 +53,14 @@ fn equate_intrinsic_type<'tcx>( && gen_count_ok(own_counts.types, n_tps, "type") && gen_count_ok(own_counts.consts, 0, "const") { - let fty = tcx.mk_fn_ptr(sig); + let fty = Ty::new_fn_ptr(tcx, sig); let it_def_id = it.owner_id.def_id; let cause = ObligationCause::new(it.span, it_def_id, ObligationCauseCode::IntrinsicType); require_same_types( tcx, &cause, ty::ParamEnv::empty(), // FIXME: do all intrinsics have an empty param env? - tcx.mk_fn_ptr(tcx.fn_sig(it.owner_id).subst_identity()), + Ty::new_fn_ptr(tcx, tcx.fn_sig(it.owner_id).instantiate_identity()), fty, ); } @@ -121,10 +121,11 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir if has_safe_attr != is_in_list { tcx.sess.struct_span_err( tcx.def_span(intrinsic_id), - DiagnosticMessage::Str(format!( - "intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `{}`", - tcx.item_name(intrinsic_id) - ))).emit(); + DiagnosticMessage::from(format!( + "intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `{}`", + tcx.item_name(intrinsic_id) + ) + )).emit(); } is_in_list @@ -133,7 +134,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: DefId) -> hir /// Remember to add all intrinsics here, in `compiler/rustc_codegen_llvm/src/intrinsic.rs`, /// and in `library/core/src/intrinsics.rs`. pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { - let param = |n| tcx.mk_ty_param(n, Symbol::intern(&format!("P{}", n))); + let param = |n| Ty::new_param(tcx, n, Symbol::intern(&format!("P{n}"))); let intrinsic_id = it.owner_id.to_def_id(); let intrinsic_name = tcx.item_name(intrinsic_id); let name_str = intrinsic_name.as_str(); @@ -144,16 +145,18 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { ]); let mk_va_list_ty = |mutbl| { tcx.lang_items().va_list().map(|did| { - let region = tcx.mk_re_late_bound( + let region = ty::Region::new_late_bound( + tcx, ty::INNERMOST, ty::BoundRegion { var: ty::BoundVar::from_u32(0), kind: ty::BrAnon(None) }, ); - let env_region = tcx.mk_re_late_bound( + let env_region = ty::Region::new_late_bound( + tcx, ty::INNERMOST, ty::BoundRegion { var: ty::BoundVar::from_u32(1), kind: ty::BrEnv }, ); - let va_list_ty = tcx.type_of(did).subst(tcx, &[region.into()]); - (tcx.mk_ref(env_region, ty::TypeAndMut { ty: va_list_ty, mutbl }), va_list_ty) + let va_list_ty = tcx.type_of(did).instantiate(tcx, &[region.into()]); + (Ty::new_ref(tcx, env_region, ty::TypeAndMut { ty: va_list_ty, mutbl }), va_list_ty) }) }; @@ -165,15 +168,15 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { let (n_tps, inputs, output) = match split[1] { "cxchg" | "cxchgweak" => ( 1, - vec![tcx.mk_mut_ptr(param(0)), param(0), param(0)], - tcx.mk_tup(&[param(0), tcx.types.bool]), + vec![Ty::new_mut_ptr(tcx, param(0)), param(0), param(0)], + Ty::new_tup(tcx, &[param(0), tcx.types.bool]), ), - "load" => (1, vec![tcx.mk_imm_ptr(param(0))], param(0)), - "store" => (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], tcx.mk_unit()), + "load" => (1, vec![Ty::new_imm_ptr(tcx, param(0))], param(0)), + "store" => (1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], Ty::new_unit(tcx)), "xchg" | "xadd" | "xsub" | "and" | "nand" | "or" | "xor" | "max" | "min" | "umax" - | "umin" => (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], param(0)), - "fence" | "singlethreadfence" => (0, Vec::new(), tcx.mk_unit()), + | "umin" => (1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], param(0)), + "fence" | "singlethreadfence" => (0, Vec::new(), Ty::new_unit(tcx)), op => { tcx.sess.emit_err(UnrecognizedAtomicOperation { span: it.span, op }); return; @@ -185,19 +188,19 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { let (n_tps, inputs, output) = match intrinsic_name { sym::abort => (0, Vec::new(), tcx.types.never), sym::unreachable => (0, Vec::new(), tcx.types.never), - sym::breakpoint => (0, Vec::new(), tcx.mk_unit()), + sym::breakpoint => (0, Vec::new(), Ty::new_unit(tcx)), sym::size_of | sym::pref_align_of | sym::min_align_of | sym::variant_count => { (1, Vec::new(), tcx.types.usize) } sym::size_of_val | sym::min_align_of_val => { - (1, vec![tcx.mk_imm_ptr(param(0))], tcx.types.usize) + (1, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.usize) } sym::rustc_peek => (1, vec![param(0)], param(0)), sym::caller_location => (0, vec![], tcx.caller_location_ty()), sym::assert_inhabited | sym::assert_zero_valid - | sym::assert_mem_uninitialized_valid => (1, Vec::new(), tcx.mk_unit()), - sym::forget => (1, vec![param(0)], tcx.mk_unit()), + | sym::assert_mem_uninitialized_valid => (1, Vec::new(), Ty::new_unit(tcx)), + sym::forget => (1, vec![param(0)], Ty::new_unit(tcx)), sym::transmute | sym::transmute_unchecked => (2, vec![param(0)], param(1)), sym::prefetch_read_data | sym::prefetch_write_data @@ -205,75 +208,83 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { | sym::prefetch_write_instruction => ( 1, vec![ - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), tcx.types.i32, ], - tcx.mk_unit(), + Ty::new_unit(tcx), ), - sym::drop_in_place => (1, vec![tcx.mk_mut_ptr(param(0))], tcx.mk_unit()), + sym::drop_in_place => (1, vec![Ty::new_mut_ptr(tcx, param(0))], Ty::new_unit(tcx)), sym::needs_drop => (1, Vec::new(), tcx.types.bool), - sym::type_name => (1, Vec::new(), tcx.mk_static_str()), - sym::type_id => (1, Vec::new(), tcx.types.u64), + sym::type_name => (1, Vec::new(), Ty::new_static_str(tcx)), + sym::type_id => (1, Vec::new(), tcx.types.u128), sym::offset => (2, vec![param(0), param(1)], param(0)), sym::arith_offset => ( 1, vec![ - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), tcx.types.isize, ], - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), ), sym::option_payload_ptr => { let option_def_id = tcx.require_lang_item(hir::LangItem::Option, None); let p0 = param(0); ( 1, - vec![tcx.mk_ptr(ty::TypeAndMut { - ty: tcx.mk_adt( - tcx.adt_def(option_def_id), - tcx.mk_substs_from_iter([ty::GenericArg::from(p0)].into_iter()), - ), - mutbl: hir::Mutability::Not, - })], - tcx.mk_ptr(ty::TypeAndMut { ty: p0, mutbl: hir::Mutability::Not }), + vec![Ty::new_ptr( + tcx, + ty::TypeAndMut { + ty: Ty::new_adt( + tcx, + tcx.adt_def(option_def_id), + tcx.mk_args_from_iter([ty::GenericArg::from(p0)].into_iter()), + ), + mutbl: hir::Mutability::Not, + }, + )], + Ty::new_ptr(tcx, ty::TypeAndMut { ty: p0, mutbl: hir::Mutability::Not }), ) } sym::ptr_mask => ( 1, vec![ - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), tcx.types.usize, ], - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), ), sym::copy | sym::copy_nonoverlapping => ( 1, vec![ - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Mut }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Mut }), tcx.types.usize, ], - tcx.mk_unit(), + Ty::new_unit(tcx), ), sym::volatile_copy_memory | sym::volatile_copy_nonoverlapping_memory => ( 1, vec![ - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Mut }), - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Mut }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Not }), tcx.types.usize, ], - tcx.mk_unit(), + Ty::new_unit(tcx), ), + sym::compare_bytes => { + let byte_ptr = Ty::new_imm_ptr(tcx, tcx.types.u8); + (0, vec![byte_ptr, byte_ptr, tcx.types.usize], tcx.types.i32) + } sym::write_bytes | sym::volatile_set_memory => ( 1, vec![ - tcx.mk_ptr(ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Mut }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: param(0), mutbl: hir::Mutability::Mut }), tcx.types.u8, tcx.types.usize, ], - tcx.mk_unit(), + Ty::new_unit(tcx), ), sym::sqrtf32 => (0, vec![tcx.types.f32], tcx.types.f32), sym::sqrtf64 => (0, vec![tcx.types.f64], tcx.types.f64), @@ -321,10 +332,10 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { sym::roundevenf64 => (0, vec![tcx.types.f64], tcx.types.f64), sym::volatile_load | sym::unaligned_volatile_load => { - (1, vec![tcx.mk_imm_ptr(param(0))], param(0)) + (1, vec![Ty::new_imm_ptr(tcx, param(0))], param(0)) } sym::volatile_store | sym::unaligned_volatile_store => { - (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], tcx.mk_unit()) + (1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], Ty::new_unit(tcx)) } sym::ctpop @@ -336,28 +347,34 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { | sym::bitreverse => (1, vec![param(0)], param(0)), sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => { - (1, vec![param(0), param(0)], tcx.mk_tup(&[param(0), tcx.types.bool])) + (1, vec![param(0), param(0)], Ty::new_tup(tcx, &[param(0), tcx.types.bool])) } - sym::ptr_guaranteed_cmp => { - (1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.u8) - } + sym::ptr_guaranteed_cmp => ( + 1, + vec![Ty::new_imm_ptr(tcx, param(0)), Ty::new_imm_ptr(tcx, param(0))], + tcx.types.u8, + ), sym::const_allocate => { - (0, vec![tcx.types.usize, tcx.types.usize], tcx.mk_mut_ptr(tcx.types.u8)) + (0, vec![tcx.types.usize, tcx.types.usize], Ty::new_mut_ptr(tcx, tcx.types.u8)) } sym::const_deallocate => ( 0, - vec![tcx.mk_mut_ptr(tcx.types.u8), tcx.types.usize, tcx.types.usize], - tcx.mk_unit(), + vec![Ty::new_mut_ptr(tcx, tcx.types.u8), tcx.types.usize, tcx.types.usize], + Ty::new_unit(tcx), ), - sym::ptr_offset_from => { - (1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.isize) - } - sym::ptr_offset_from_unsigned => { - (1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.usize) - } + sym::ptr_offset_from => ( + 1, + vec![Ty::new_imm_ptr(tcx, param(0)), Ty::new_imm_ptr(tcx, param(0))], + tcx.types.isize, + ), + sym::ptr_offset_from_unsigned => ( + 1, + vec![Ty::new_imm_ptr(tcx, param(0)), Ty::new_imm_ptr(tcx, param(0))], + tcx.types.usize, + ), sym::unchecked_div | sym::unchecked_rem | sym::exact_div => { (1, vec![param(0), param(0)], param(0)) } @@ -376,12 +393,14 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { } sym::float_to_int_unchecked => (2, vec![param(0)], param(1)), - sym::assume => (0, vec![tcx.types.bool], tcx.mk_unit()), + sym::assume => (0, vec![tcx.types.bool], Ty::new_unit(tcx)), sym::likely => (0, vec![tcx.types.bool], tcx.types.bool), sym::unlikely => (0, vec![tcx.types.bool], tcx.types.bool), - sym::read_via_copy => (1, vec![tcx.mk_imm_ptr(param(0))], param(0)), - sym::write_via_move => (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], tcx.mk_unit()), + sym::read_via_copy => (1, vec![Ty::new_imm_ptr(tcx, param(0))], param(0)), + sym::write_via_move => { + (1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], Ty::new_unit(tcx)) + } sym::discriminant_value => { let assoc_items = tcx.associated_item_def_ids( @@ -392,43 +411,47 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { let br = ty::BoundRegion { var: ty::BoundVar::from_u32(0), kind: ty::BrAnon(None) }; ( 1, - vec![tcx.mk_imm_ref(tcx.mk_re_late_bound(ty::INNERMOST, br), param(0))], - tcx.mk_projection(discriminant_def_id, tcx.mk_substs(&[param(0).into()])), + vec![Ty::new_imm_ref( + tcx, + ty::Region::new_late_bound(tcx, ty::INNERMOST, br), + param(0), + )], + Ty::new_projection(tcx, discriminant_def_id, tcx.mk_args(&[param(0).into()])), ) } kw::Try => { - let mut_u8 = tcx.mk_mut_ptr(tcx.types.u8); + let mut_u8 = Ty::new_mut_ptr(tcx, tcx.types.u8); let try_fn_ty = ty::Binder::dummy(tcx.mk_fn_sig( [mut_u8], - tcx.mk_unit(), + Ty::new_unit(tcx), false, hir::Unsafety::Normal, Abi::Rust, )); let catch_fn_ty = ty::Binder::dummy(tcx.mk_fn_sig( [mut_u8, mut_u8], - tcx.mk_unit(), + Ty::new_unit(tcx), false, hir::Unsafety::Normal, Abi::Rust, )); ( 0, - vec![tcx.mk_fn_ptr(try_fn_ty), mut_u8, tcx.mk_fn_ptr(catch_fn_ty)], + vec![Ty::new_fn_ptr(tcx, try_fn_ty), mut_u8, Ty::new_fn_ptr(tcx, catch_fn_ty)], tcx.types.i32, ) } sym::va_start | sym::va_end => match mk_va_list_ty(hir::Mutability::Mut) { - Some((va_list_ref_ty, _)) => (0, vec![va_list_ref_ty], tcx.mk_unit()), + Some((va_list_ref_ty, _)) => (0, vec![va_list_ref_ty], Ty::new_unit(tcx)), None => bug!("`va_list` language item needed for C-variadic intrinsics"), }, sym::va_copy => match mk_va_list_ty(hir::Mutability::Not) { Some((va_list_ref_ty, va_list_ty)) => { - let va_list_ptr_ty = tcx.mk_mut_ptr(va_list_ty); - (0, vec![va_list_ptr_ty, va_list_ref_ty], tcx.mk_unit()) + let va_list_ptr_ty = Ty::new_mut_ptr(tcx, va_list_ty); + (0, vec![va_list_ptr_ty, va_list_ref_ty], Ty::new_unit(tcx)) } None => bug!("`va_list` language item needed for C-variadic intrinsics"), }, @@ -438,11 +461,17 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { None => bug!("`va_list` language item needed for C-variadic intrinsics"), }, - sym::nontemporal_store => (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], tcx.mk_unit()), + sym::nontemporal_store => { + (1, vec![Ty::new_mut_ptr(tcx, param(0)), param(0)], Ty::new_unit(tcx)) + } sym::raw_eq => { let br = ty::BoundRegion { var: ty::BoundVar::from_u32(0), kind: ty::BrAnon(None) }; - let param_ty = tcx.mk_imm_ref(tcx.mk_re_late_bound(ty::INNERMOST, br), param(0)); + let param_ty = Ty::new_imm_ref( + tcx, + ty::Region::new_late_bound(tcx, ty::INNERMOST, br), + param(0), + ); (1, vec![param_ty; 2], tcx.types.bool) } @@ -451,7 +480,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { sym::const_eval_select => (4, vec![param(0), param(1), param(2)], param(3)), sym::vtable_size | sym::vtable_align => { - (0, vec![tcx.mk_imm_ptr(tcx.mk_unit())], tcx.types.usize) + (0, vec![Ty::new_imm_ptr(tcx, Ty::new_unit(tcx))], tcx.types.usize) } other => { @@ -469,8 +498,8 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { /// Type-check `extern "platform-intrinsic" { ... }` functions. pub fn check_platform_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { let param = |n| { - let name = Symbol::intern(&format!("P{}", n)); - tcx.mk_ty_param(n, name) + let name = Symbol::intern(&format!("P{n}")); + Ty::new_param(tcx, n, name) }; let name = it.ident.name; @@ -496,6 +525,10 @@ pub fn check_platform_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) | sym::simd_saturating_sub => (1, vec![param(0), param(0)], param(0)), sym::simd_arith_offset => (2, vec![param(0), param(1)], param(0)), sym::simd_neg + | sym::simd_bswap + | sym::simd_bitreverse + | sym::simd_ctlz + | sym::simd_cttz | sym::simd_fsqrt | sym::simd_fsin | sym::simd_fcos @@ -512,7 +545,7 @@ pub fn check_platform_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) sym::simd_fpowi => (1, vec![param(0), tcx.types.i32], param(0)), sym::simd_fma => (1, vec![param(0), param(0), param(0)], param(0)), sym::simd_gather => (3, vec![param(0), param(1), param(2)], param(0)), - sym::simd_scatter => (3, vec![param(0), param(1), param(2)], tcx.mk_unit()), + sym::simd_scatter => (3, vec![param(0), param(1), param(2)], Ty::new_unit(tcx)), sym::simd_insert => (2, vec![param(0), tcx.types.u32, param(1)], param(0)), sym::simd_extract => (2, vec![param(0), tcx.types.u32], param(1)), sym::simd_cast @@ -538,20 +571,6 @@ pub fn check_platform_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) | sym::simd_reduce_min_nanless | sym::simd_reduce_max_nanless => (2, vec![param(0)], param(1)), sym::simd_shuffle => (3, vec![param(0), param(0), param(1)], param(2)), - name if name.as_str().starts_with("simd_shuffle") => { - match name.as_str()["simd_shuffle".len()..].parse() { - Ok(n) => { - let params = vec![param(0), param(0), tcx.mk_array(tcx.types.u32, n)]; - (2, params, param(1)) - } - Err(_) => { - let msg = - format!("unrecognized platform-specific intrinsic function: `{name}`"); - tcx.sess.struct_span_err(it.span, msg).emit(); - return; - } - } - } _ => { let msg = format!("unrecognized platform-specific intrinsic function: `{name}`"); tcx.sess.struct_span_err(it.span, msg).emit(); diff --git a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs index 0bb1467ef3170..945953edd5aec 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsicck.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsicck.rs @@ -68,7 +68,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { let asm_ty = match *ty.kind() { // `!` is allowed for input but not for output (issue #87802) ty::Never if is_input => return None, - ty::Error(_) => return None, + _ if ty.references_error() => return None, ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Some(InlineAsmType::I8), ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Some(InlineAsmType::I16), ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Some(InlineAsmType::I32), @@ -81,9 +81,9 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { ty::RawPtr(ty::TypeAndMut { ty, mutbl: _ }) if self.is_thin_ptr_ty(ty) => { Some(asm_ty_isize) } - ty::Adt(adt, substs) if adt.repr().simd() => { + ty::Adt(adt, args) if adt.repr().simd() => { let fields = &adt.non_enum_variant().fields; - let elem_ty = fields[FieldIdx::from_u32(0)].ty(self.tcx, substs); + let elem_ty = fields[FieldIdx::from_u32(0)].ty(self.tcx, args); let (size, ty) = match elem_ty.kind() { ty::Array(ty, len) => { @@ -186,18 +186,14 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { let Some((_, feature)) = supported_tys.iter().find(|&&(t, _)| t == asm_ty) else { let msg = format!("type `{ty}` cannot be used with this register class"); let mut err = self.tcx.sess.struct_span_err(expr.span, msg); - let supported_tys: Vec<_> = - supported_tys.iter().map(|(t, _)| t.to_string()).collect(); + let supported_tys: Vec<_> = supported_tys.iter().map(|(t, _)| t.to_string()).collect(); err.note(format!( "register class `{}` supports these types: {}", reg_class.name(), supported_tys.join(", "), )); if let Some(suggest) = reg_class.suggest_class(asm_arch, asm_ty) { - err.help(format!( - "consider using the `{}` register class instead", - suggest.name() - )); + err.help(format!("consider using the `{}` register class instead", suggest.name())); } err.emit(); return Some(asm_ty); @@ -215,7 +211,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { // register class is usable at all. if let Some(feature) = feature { if !target_features.contains(feature) { - let msg = format!("`{}` target feature is not enabled", feature); + let msg = format!("`{feature}` target feature is not enabled"); let mut err = self.tcx.sess.struct_span_err(expr.span, msg); err.note(format!( "this is required to use type `{}` with register class `{}`", @@ -427,7 +423,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { // Check that sym actually points to a function. Later passes // depend on this. hir::InlineAsmOperand::SymFn { anon_const } => { - let ty = self.tcx.type_of(anon_const.def_id).subst_identity(); + let ty = self.tcx.type_of(anon_const.def_id).instantiate_identity(); match ty.kind() { ty::Never | ty::Error(_) => {} ty::FnDef(..) => {} diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 3971a4c01d661..4cf3587327d23 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -38,7 +38,7 @@ can be broken down into several distinct phases: While type checking a function, the intermediate types for the expressions, blocks, and so forth contained within the function are -stored in `fcx.node_types` and `fcx.node_substs`. These types +stored in `fcx.node_types` and `fcx.node_args`. These types may contain unresolved type variables. After type checking is complete, the functions in the writeback module are used to take the types from this table, resolve them, and then write them into their @@ -65,6 +65,7 @@ a type parameter). mod check; mod compare_impl_item; pub mod dropck; +mod entry; pub mod intrinsic; pub mod intrinsicck; mod region; @@ -80,7 +81,7 @@ use rustc_hir::intravisit::Visitor; use rustc_index::bit_set::BitSet; use rustc_middle::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_middle::ty::{InternalSubsts, SubstsRef}; +use rustc_middle::ty::{GenericArgs, GenericArgsRef}; use rustc_session::parse::feature_err; use rustc_span::source_map::DUMMY_SP; use rustc_span::symbol::{kw, Ident}; @@ -188,7 +189,7 @@ fn missing_items_err( full_impl_span: Span, ) { let missing_items = - missing_items.iter().filter(|trait_item| tcx.opt_rpitit_info(trait_item.def_id).is_none()); + missing_items.iter().filter(|trait_item| !trait_item.is_impl_trait_in_trait()); let missing_items_msg = missing_items .clone() @@ -211,9 +212,9 @@ fn missing_items_err( let snippet = suggestion_signature( tcx, trait_item, - tcx.impl_trait_ref(impl_def_id).unwrap().subst_identity(), + tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity(), ); - let code = format!("{}{}\n{}", padding, snippet, padding); + let code = format!("{padding}{snippet}\n{padding}"); if let Some(span) = tcx.hir().span_if_local(trait_item.def_id) { missing_trait_item_label .push(errors::MissingTraitItemLabel { span, item: trait_item.name }); @@ -296,7 +297,7 @@ fn default_body_is_unstable( /// Re-sugar `ty::GenericPredicates` in a way suitable to be used in structured suggestions. fn bounds_from_generic_predicates<'tcx>( tcx: TyCtxt<'tcx>, - predicates: impl IntoIterator, Span)>, + predicates: impl IntoIterator, Span)>, ) -> (String, String) { let mut types: FxHashMap, Vec> = FxHashMap::default(); let mut projections = vec![]; @@ -304,7 +305,7 @@ fn bounds_from_generic_predicates<'tcx>( debug!("predicate {:?}", predicate); let bound_predicate = predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => { + 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() { @@ -313,7 +314,7 @@ fn bounds_from_generic_predicates<'tcx>( entry.push(trait_predicate.def_id()); } } - ty::PredicateKind::Clause(ty::Clause::Projection(projection_pred)) => { + ty::ClauseKind::Projection(projection_pred) => { projections.push(bound_predicate.rebind(projection_pred)); } _ => {} @@ -362,7 +363,7 @@ fn fn_sig_suggestion<'tcx>( tcx: TyCtxt<'tcx>, sig: ty::FnSig<'tcx>, ident: Ident, - predicates: impl IntoIterator, Span)>, + predicates: impl IntoIterator, Span)>, assoc: ty::AssocItem, ) -> String { let args = sig @@ -403,7 +404,30 @@ fn fn_sig_suggestion<'tcx>( .flatten() .collect::>() .join(", "); - let output = sig.output(); + let mut output = sig.output(); + + let asyncness = if tcx.asyncness(assoc.def_id).is_async() { + output = if let ty::Alias(_, alias_ty) = *output.kind() { + tcx.explicit_item_bounds(alias_ty.def_id) + .iter_instantiated_copied(tcx, alias_ty.args) + .find_map(|(bound, _)| bound.as_projection_clause()?.no_bound_vars()?.term.ty()) + .unwrap_or_else(|| { + span_bug!( + ident.span, + "expected async fn to have `impl Future` output, but it returns {output}" + ) + }) + } else { + span_bug!( + ident.span, + "expected async fn to have `impl Future` output, but it returns {output}" + ) + }; + "async " + } else { + "" + }; + let output = if !output.is_unit() { format!(" -> {output}") } else { String::new() }; let unsafety = sig.unsafety.prefix_str(); @@ -414,7 +438,9 @@ fn fn_sig_suggestion<'tcx>( // lifetimes between the `impl` and the `trait`, but this should be good enough to // fill in a significant portion of the missing code, and other subsequent // suggestions can help the user fix the code. - format!("{unsafety}fn {ident}{generics}({args}){output}{where_clauses} {{ todo!() }}") + format!( + "{unsafety}{asyncness}fn {ident}{generics}({args}){output}{where_clauses} {{ todo!() }}" + ) } pub fn ty_kind_suggestion(ty: Ty<'_>) -> Option<&'static str> { @@ -436,35 +462,32 @@ fn suggestion_signature<'tcx>( assoc: ty::AssocItem, impl_trait_ref: ty::TraitRef<'tcx>, ) -> String { - let substs = ty::InternalSubsts::identity_for_item(tcx, assoc.def_id).rebase_onto( + let args = ty::GenericArgs::identity_for_item(tcx, assoc.def_id).rebase_onto( tcx, assoc.container_id(tcx), - impl_trait_ref.with_self_ty(tcx, tcx.types.self_param).substs, + impl_trait_ref.with_self_ty(tcx, tcx.types.self_param).args, ); match assoc.kind { - ty::AssocKind::Fn => { - // We skip the binder here because the binder would deanonymize all - // late-bound regions, and we don't want method signatures to show up - // `as for<'r> fn(&'r MyType)`. Pretty-printing handles late-bound - // regions just fine, showing `fn(&MyType)`. - fn_sig_suggestion( - tcx, - tcx.fn_sig(assoc.def_id).subst(tcx, substs).skip_binder(), - assoc.ident(tcx), - tcx.predicates_of(assoc.def_id).instantiate_own(tcx, substs), - assoc, - ) - } + ty::AssocKind::Fn => fn_sig_suggestion( + tcx, + tcx.liberate_late_bound_regions( + assoc.def_id, + tcx.fn_sig(assoc.def_id).instantiate(tcx, args), + ), + assoc.ident(tcx), + tcx.predicates_of(assoc.def_id).instantiate_own(tcx, args), + assoc, + ), ty::AssocKind::Type => { let (generics, where_clauses) = bounds_from_generic_predicates( tcx, - tcx.predicates_of(assoc.def_id).instantiate_own(tcx, substs), + tcx.predicates_of(assoc.def_id).instantiate_own(tcx, args), ); format!("type {}{generics} = /* Type */{where_clauses};", assoc.name) } ty::AssocKind::Const => { - let ty = tcx.type_of(assoc.def_id).subst_identity(); + let ty = tcx.type_of(assoc.def_id).instantiate_identity(); let val = ty_kind_suggestion(ty).unwrap_or("todo!()"); format!("const {}: {} = {};", assoc.name, ty, val) } diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 6ab5556e951d9..5bd6fcb9612dd 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -392,7 +392,7 @@ fn resolve_expr<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, expr: &'tcx h // Manually recurse over closures and inline consts, because they are the only // case of nested bodies that share the parent environment. hir::ExprKind::Closure(&hir::Closure { body, .. }) - | hir::ExprKind::ConstBlock(hir::AnonConst { body, .. }) => { + | hir::ExprKind::ConstBlock(hir::ConstBlock { body, .. }) => { let body = visitor.tcx.hir().body(body); visitor.visit_body(body); } diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index b403ee96b4229..f5beefc47f374 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -5,7 +5,7 @@ use rustc_ast as ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::lang_items::LangItem; use rustc_hir::ItemKind; use rustc_infer::infer::outlives::env::{OutlivesEnvironment, RegionBoundPairs}; @@ -15,10 +15,10 @@ use rustc_middle::mir::ConstraintCategory; use rustc_middle::query::Providers; use rustc_middle::ty::trait_def::TraitSpecializationKind; use rustc_middle::ty::{ - self, AdtKind, GenericParamDefKind, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, + self, AdtKind, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, }; -use rustc_middle::ty::{GenericArgKind, InternalSubsts}; +use rustc_middle::ty::{GenericArgKind, GenericArgs}; use rustc_session::parse::feature_err; use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; @@ -75,13 +75,11 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { self.body_def_id, ObligationCauseCode::WellFormed(loc), ); - // for a type to be WF, we do not need to check if const trait predicates satisfy. - let param_env = self.param_env.without_const(); self.ocx.register_obligation(traits::Obligation::new( self.tcx(), cause, - param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(arg)), + self.param_env, + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg))), )); } } @@ -105,7 +103,12 @@ pub(super) fn enter_wf_checking_ctxt<'tcx, F>( } f(&mut wfcx); - let assumed_wf_types = wfcx.ocx.assumed_wf_types(param_env, span, body_def_id); + let assumed_wf_types = match wfcx.ocx.assumed_wf_types_and_report_errors(param_env, body_def_id) + { + Ok(wf_types) => wf_types, + Err(_guar) => return, + }; + let implied_bounds = infcx.implied_bounds_tys(param_env, body_def_id, assumed_wf_types); let errors = wfcx.select_all_or_error(); @@ -191,7 +194,7 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) { // We match on both `ty::ImplPolarity` and `ast::ImplPolarity` just to get the `!` span. match (tcx.impl_polarity(def_id), impl_.polarity) { (ty::ImplPolarity::Positive, _) => { - check_impl(tcx, item, impl_.self_ty, &impl_.of_trait, impl_.constness); + check_impl(tcx, item, impl_.self_ty, &impl_.of_trait); } (ty::ImplPolarity::Negative, ast::ImplPolarity::Negative(span)) => { // FIXME(#27579): what amount of WF checking do we need for neg impls? @@ -217,10 +220,10 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) { check_item_fn(tcx, def_id, item.ident, item.span, sig.decl); } hir::ItemKind::Static(ty, ..) => { - check_item_type(tcx, def_id, ty.span, false); + check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid); } hir::ItemKind::Const(ty, ..) => { - check_item_type(tcx, def_id, ty.span, false); + check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid); } hir::ItemKind::Struct(_, ast_generics) => { check_type_defn(tcx, item, false); @@ -242,6 +245,16 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) { } // `ForeignItem`s are handled separately. hir::ItemKind::ForeignMod { .. } => {} + hir::ItemKind::TyAlias(hir_ty, ast_generics) => { + if tcx.features().lazy_type_alias + || tcx.type_of(item.owner_id).skip_binder().has_opaque_types() + { + // Bounds of lazy type aliases and of eager ones that contain opaque types are respected. + // E.g: `type X = impl Trait;`, `type X = (impl Trait, Y);`. + check_item_type(tcx, def_id, hir_ty.span, UnsizedHandling::Allow); + check_variances_for_type_defn(tcx, item, ast_generics); + } + } _ => {} } } @@ -258,7 +271,9 @@ fn check_foreign_item(tcx: TyCtxt<'_>, item: &hir::ForeignItem<'_>) { hir::ForeignItemKind::Fn(decl, ..) => { check_item_fn(tcx, def_id, item.ident, item.span, decl) } - hir::ForeignItemKind::Static(ty, ..) => check_item_type(tcx, def_id, ty.span, true), + hir::ForeignItemKind::Static(ty, ..) => { + check_item_type(tcx, def_id, ty.span, UnsizedHandling::AllowIfForeignTail) + } hir::ForeignItemKind::Type => (), } } @@ -273,11 +288,22 @@ fn check_trait_item(tcx: TyCtxt<'_>, trait_item: &hir::TraitItem<'_>) { }; check_object_unsafe_self_trait_by_name(tcx, trait_item); check_associated_item(tcx, def_id, span, method_sig); + + if matches!(trait_item.kind, hir::TraitItemKind::Fn(..)) { + for &assoc_ty_def_id in tcx.associated_types_for_impl_traits_in_associated_fn(def_id) { + check_associated_item( + tcx, + assoc_ty_def_id.expect_local(), + tcx.def_span(assoc_ty_def_id), + None, + ); + } + } } /// Require that the user writes where clauses on GATs for the implicit /// outlives bounds involving trait parameters in trait functions and -/// lifetimes passed as GAT substs. See `self-outlives-lint` test. +/// lifetimes passed as GAT args. See `self-outlives-lint` test. /// /// We use the following trait as an example throughout this function: /// ```rust,ignore (this code fails due to this lint) @@ -301,7 +327,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe for gat_item in associated_items { let gat_def_id = gat_item.id.owner_id; let gat_item = tcx.associated_item(gat_def_id); - // If this item is not an assoc ty, or has no substs, then it's not a GAT + // If this item is not an assoc ty, or has no args, then it's not a GAT if gat_item.kind != ty::AssocKind::Type { continue; } @@ -314,7 +340,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe // Gather the bounds with which all other items inside of this trait constrain the GAT. // This is calculated by taking the intersection of the bounds that each item // constrains the GAT with individually. - let mut new_required_bounds: Option>> = None; + let mut new_required_bounds: Option>> = None; for item in associated_items { let item_def_id = item.id.owner_id; // Skip our own GAT, since it does not constrain itself at all. @@ -332,7 +358,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe // `Self::Iter<'a>` is a GAT we want to gather any potential missing bounds from. let sig: ty::FnSig<'_> = tcx.liberate_late_bound_regions( item_def_id.to_def_id(), - tcx.fn_sig(item_def_id).subst_identity(), + tcx.fn_sig(item_def_id).instantiate_identity(), ); gather_gat_bounds( tcx, @@ -361,7 +387,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe param_env, item_def_id, tcx.explicit_item_bounds(item_def_id) - .subst_identity_iter_copied() + .instantiate_identity_iter_copied() .collect::>(), &FxIndexSet::default(), gat_def_id.def_id, @@ -411,21 +437,17 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe let mut unsatisfied_bounds: Vec<_> = required_bounds .into_iter() .filter(|clause| match clause.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate( - a, - b, - ))) => !region_known_to_outlive( - tcx, - gat_def_id.def_id, - param_env, - &FxIndexSet::default(), - a, - b, - ), - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - a, - b, - ))) => !ty_known_to_outlive( + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => { + !region_known_to_outlive( + tcx, + gat_def_id.def_id, + param_env, + &FxIndexSet::default(), + a, + b, + ) + } + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(a, b)) => !ty_known_to_outlive( tcx, gat_def_id.def_id, param_env, @@ -433,7 +455,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe a, b, ), - _ => bug!("Unexpected PredicateKind"), + _ => bug!("Unexpected ClauseKind"), }) .map(|clause| clause.to_string()) .collect(); @@ -463,8 +485,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe let bound = if unsatisfied_bounds.len() > 1 { "these bounds are" } else { "this bound is" }; err.note(format!( - "{} currently required to ensure that impls have maximum flexibility", - bound + "{bound} currently required to ensure that impls have maximum flexibility" )); err.note( "we are soliciting feedback, see issue #87479 \ @@ -481,7 +502,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe fn augment_param_env<'tcx>( tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, - new_predicates: Option<&FxHashSet>>, + new_predicates: Option<&FxHashSet>>, ) -> ty::ParamEnv<'tcx> { let Some(new_predicates) = new_predicates else { return param_env; @@ -491,12 +512,12 @@ fn augment_param_env<'tcx>( return param_env; } - let bounds = tcx.mk_predicates_from_iter( + let bounds = tcx.mk_clauses_from_iter( param_env.caller_bounds().iter().chain(new_predicates.iter().cloned()), ); // FIXME(compiler-errors): Perhaps there is a case where we need to normalize this // i.e. traits::normalize_param_env_or_error - ty::ParamEnv::new(bounds, param_env.reveal(), param_env.constness()) + ty::ParamEnv::new(bounds, param_env.reveal()) } /// We use the following trait as an example throughout this function. @@ -517,7 +538,7 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( wf_tys: &FxIndexSet>, gat_def_id: LocalDefId, gat_generics: &'tcx ty::Generics, -) -> Option>> { +) -> Option>> { // The bounds we that we would require from `to_check` let mut bounds = FxHashSet::default(); @@ -535,8 +556,8 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( for (region_a, region_a_idx) in ®ions { // Ignore `'static` lifetimes for the purpose of this lint: it's // because we know it outlives everything and so doesn't give meaningful - // clues - if let ty::ReStatic = **region_a { + // clues. Also ignore `ReError`, to avoid knock-down errors. + if let ty::ReStatic | ty::ReError(_) = **region_a { continue; } // For each region argument (e.g., `'a` in our example), check for a @@ -552,22 +573,24 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( // our example, the type was `Self`, which will also be // `Self` in the GAT. let ty_param = gat_generics.param_at(*ty_idx, tcx); - let ty_param = tcx.mk_ty_param(ty_param.index, ty_param.name); + let ty_param = Ty::new_param(tcx, ty_param.index, ty_param.name); // Same for the region. In our example, 'a corresponds // to the 'me parameter. let region_param = gat_generics.param_at(*region_a_idx, tcx); - let region_param = tcx.mk_re_early_bound(ty::EarlyBoundRegion { - def_id: region_param.def_id, - index: region_param.index, - name: region_param.name, - }); + let region_param = ty::Region::new_early_bound( + tcx, + ty::EarlyBoundRegion { + def_id: region_param.def_id, + index: region_param.index, + name: region_param.name, + }, + ); // The predicate we expect to see. (In our example, // `Self: 'me`.) - let clause = ty::PredicateKind::Clause(ty::Clause::TypeOutlives( - ty::OutlivesPredicate(ty_param, region_param), - )); - let clause = tcx.mk_predicate(ty::Binder::dummy(clause)); - bounds.insert(clause); + bounds.insert( + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty_param, region_param)) + .to_predicate(tcx), + ); } } @@ -577,8 +600,9 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( // on the GAT itself. for (region_b, region_b_idx) in ®ions { // Again, skip `'static` because it outlives everything. Also, we trivially - // know that a region outlives itself. - if ty::ReStatic == **region_b || region_a == region_b { + // know that a region outlives itself. Also ignore `ReError`, to avoid + // knock-down errors. + if matches!(**region_b, ty::ReStatic | ty::ReError(_)) || region_a == region_b { continue; } if region_known_to_outlive( @@ -593,24 +617,32 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable>>( debug!("required clause: {region_a} must outlive {region_b}"); // Translate into the generic parameters of the GAT. let region_a_param = gat_generics.param_at(*region_a_idx, tcx); - let region_a_param = tcx.mk_re_early_bound(ty::EarlyBoundRegion { - def_id: region_a_param.def_id, - index: region_a_param.index, - name: region_a_param.name, - }); + let region_a_param = ty::Region::new_early_bound( + tcx, + ty::EarlyBoundRegion { + def_id: region_a_param.def_id, + index: region_a_param.index, + name: region_a_param.name, + }, + ); // Same for the region. let region_b_param = gat_generics.param_at(*region_b_idx, tcx); - let region_b_param = tcx.mk_re_early_bound(ty::EarlyBoundRegion { - def_id: region_b_param.def_id, - index: region_b_param.index, - name: region_b_param.name, - }); + let region_b_param = ty::Region::new_early_bound( + tcx, + ty::EarlyBoundRegion { + def_id: region_b_param.def_id, + index: region_b_param.index, + name: region_b_param.name, + }, + ); // The predicate we expect to see. - let clause = ty::PredicateKind::Clause(ty::Clause::RegionOutlives( - ty::OutlivesPredicate(region_a_param, region_b_param), - )); - let clause = tcx.mk_predicate(ty::Binder::dummy(clause)); - bounds.insert(clause); + bounds.insert( + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate( + region_a_param, + region_b_param, + )) + .to_predicate(tcx), + ); } } } @@ -718,7 +750,7 @@ impl<'tcx> TypeVisitor> for GATSubstCollector<'tcx> { fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { match t.kind() { ty::Alias(ty::Projection, p) if p.def_id == self.gat => { - for (idx, subst) in p.substs.iter().enumerate() { + for (idx, subst) in p.args.iter().enumerate() { match subst.unpack() { GenericArgKind::Lifetime(lt) if !lt.is_late_bound() => { self.regions.insert((lt, idx)); @@ -817,86 +849,23 @@ fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) { // Const parameters are well formed if their type is structural match. hir::GenericParamKind::Const { ty: hir_ty, default: _ } => { - let ty = tcx.type_of(param.def_id).subst_identity(); + let ty = tcx.type_of(param.def_id).instantiate_identity(); if tcx.features().adt_const_params { - if let Some(non_structural_match_ty) = - traits::search_for_adt_const_param_violation(param.span, tcx, ty) - { - // We use the same error code in both branches, because this is really the same - // issue: we just special-case the message for type parameters to make it - // clearer. - match non_structural_match_ty.kind() { - ty::Param(_) => { - // Const parameters may not have type parameters as their types, - // because we cannot be sure that the type parameter derives `PartialEq` - // and `Eq` (just implementing them is not enough for `structural_match`). - struct_span_err!( - tcx.sess, - hir_ty.span, - E0741, - "`{ty}` is not guaranteed to `#[derive(PartialEq, Eq)]`, so may not be \ - used as the type of a const parameter", - ) - .span_label( - hir_ty.span, - format!("`{ty}` may not derive both `PartialEq` and `Eq`"), - ) - .note( - "it is not currently possible to use a type parameter as the type of a \ - const parameter", - ) - .emit(); - } - ty::Float(_) => { - struct_span_err!( - tcx.sess, - hir_ty.span, - E0741, - "`{ty}` is forbidden as the type of a const generic parameter", - ) - .note("floats do not derive `Eq` or `Ord`, which are required for const parameters") - .emit(); - } - ty::FnPtr(_) => { - struct_span_err!( - tcx.sess, - hir_ty.span, - E0741, - "using function pointers as const generic parameters is forbidden", - ) - .emit(); - } - ty::RawPtr(_) => { - struct_span_err!( - tcx.sess, - hir_ty.span, - E0741, - "using raw pointers as const generic parameters is forbidden", - ) - .emit(); - } - _ => { - let mut diag = struct_span_err!( - tcx.sess, - hir_ty.span, - E0741, - "`{}` must be annotated with `#[derive(PartialEq, Eq)]` to be used as \ - the type of a const parameter", - non_structural_match_ty, - ); - - if ty == non_structural_match_ty { - diag.span_label( - hir_ty.span, - format!("`{ty}` doesn't derive both `PartialEq` and `Eq`"), - ); - } - - diag.emit(); - } - } - } + enter_wf_checking_ctxt(tcx, hir_ty.span, param.def_id, |wfcx| { + let trait_def_id = + tcx.require_lang_item(LangItem::ConstParamTy, Some(hir_ty.span)); + wfcx.register_bound( + ObligationCause::new( + hir_ty.span, + param.def_id, + ObligationCauseCode::ConstParam(ty), + ), + wfcx.param_env, + ty, + trait_def_id, + ); + }); } else { let err_ty_str; let mut is_ptr = true; @@ -954,17 +923,17 @@ fn check_associated_item( let self_ty = match item.container { ty::TraitContainer => tcx.types.self_param, - ty::ImplContainer => tcx.type_of(item.container_id(tcx)).subst_identity(), + ty::ImplContainer => tcx.type_of(item.container_id(tcx)).instantiate_identity(), }; match item.kind { ty::AssocKind::Const => { - let ty = tcx.type_of(item.def_id).subst_identity(); + let ty = tcx.type_of(item.def_id).instantiate_identity(); let ty = wfcx.normalize(span, Some(WellFormedLoc::Ty(item_id)), ty); wfcx.register_wf_obligation(span, loc, ty.into()); } ty::AssocKind::Fn => { - let sig = tcx.fn_sig(item.def_id).subst_identity(); + let sig = tcx.fn_sig(item.def_id).instantiate_identity(); let hir_sig = sig_if_method.expect("bad signature for method"); check_fn_or_method( wfcx, @@ -980,7 +949,7 @@ fn check_associated_item( check_associated_type_bounds(wfcx, item, span) } if item.defaultness(tcx).has_value() { - let ty = tcx.type_of(item.def_id).subst_identity(); + let ty = tcx.type_of(item.def_id).instantiate_identity(); let ty = wfcx.normalize(span, Some(WellFormedLoc::Ty(item_id)), ty); wfcx.register_wf_obligation(span, loc, ty.into()); } @@ -1013,7 +982,11 @@ fn check_type_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, all_sized: b let field_id = field.did.expect_local(); let hir::FieldDef { ty: hir_ty, .. } = tcx.hir().get_by_def_id(field_id).expect_field(); - let ty = wfcx.normalize(hir_ty.span, None, tcx.type_of(field.did).subst_identity()); + let ty = wfcx.normalize( + hir_ty.span, + None, + tcx.type_of(field.did).instantiate_identity(), + ); wfcx.register_wf_obligation( hir_ty.span, Some(WellFormedLoc::Ty(field_id)), @@ -1025,11 +998,11 @@ fn check_type_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, all_sized: b // intermediate types must be sized. let needs_drop_copy = || { packed && { - let ty = tcx.type_of(variant.fields.raw.last().unwrap().did).subst_identity(); + let ty = tcx.type_of(variant.tail().did).instantiate_identity(); let ty = tcx.erase_regions(ty); if ty.has_infer() { tcx.sess - .delay_span_bug(item.span, format!("inference variables in {:?}", ty)); + .delay_span_bug(item.span, format!("inference variables in {ty:?}")); // Just treat unresolved type expression as if it needs drop. true } else { @@ -1047,7 +1020,11 @@ fn check_type_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, all_sized: b let field_id = field.did.expect_local(); let hir::FieldDef { ty: hir_ty, .. } = tcx.hir().get_by_def_id(field_id).expect_field(); - let ty = wfcx.normalize(hir_ty.span, None, tcx.type_of(field.did).subst_identity()); + let ty = wfcx.normalize( + hir_ty.span, + None, + tcx.type_of(field.did).instantiate_identity(), + ); wfcx.register_bound( traits::ObligationCause::new( hir_ty.span, @@ -1078,9 +1055,9 @@ fn check_type_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, all_sized: b tcx, cause, wfcx.param_env, - ty::Binder::dummy(ty::PredicateKind::ConstEvaluatable( + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable( ty::Const::from_anon_const(tcx, discr_def_id.expect_local()), - )), + ))), )); } } @@ -1127,16 +1104,17 @@ fn check_associated_type_bounds(wfcx: &WfCheckingCtxt<'_, '_>, item: ty::AssocIt let bounds = wfcx.tcx().explicit_item_bounds(item.def_id); debug!("check_associated_type_bounds: bounds={:?}", bounds); - let wf_obligations = bounds.subst_identity_iter_copied().flat_map(|(bound, bound_span)| { - let normalized_bound = wfcx.normalize(span, None, bound); - traits::wf::predicate_obligations( - wfcx.infcx, - wfcx.param_env, - wfcx.body_def_id, - normalized_bound, - bound_span, - ) - }); + let wf_obligations = + bounds.instantiate_identity_iter_copied().flat_map(|(bound, bound_span)| { + let normalized_bound = wfcx.normalize(span, None, bound); + traits::wf::predicate_obligations( + wfcx.infcx, + wfcx.param_env, + wfcx.body_def_id, + normalized_bound.as_predicate(), + bound_span, + ) + }); wfcx.register_obligations(wf_obligations); } @@ -1149,25 +1127,37 @@ fn check_item_fn( decl: &hir::FnDecl<'_>, ) { enter_wf_checking_ctxt(tcx, span, def_id, |wfcx| { - let sig = tcx.fn_sig(def_id).subst_identity(); + let sig = tcx.fn_sig(def_id).instantiate_identity(); check_fn_or_method(wfcx, ident.span, sig, decl, def_id); }) } -fn check_item_type(tcx: TyCtxt<'_>, item_id: LocalDefId, ty_span: Span, allow_foreign_ty: bool) { +enum UnsizedHandling { + Forbid, + Allow, + AllowIfForeignTail, +} + +fn check_item_type( + tcx: TyCtxt<'_>, + item_id: LocalDefId, + ty_span: Span, + unsized_handling: UnsizedHandling, +) { debug!("check_item_type: {:?}", item_id); enter_wf_checking_ctxt(tcx, ty_span, item_id, |wfcx| { - let ty = tcx.type_of(item_id).subst_identity(); + let ty = tcx.type_of(item_id).instantiate_identity(); let item_ty = wfcx.normalize(ty_span, Some(WellFormedLoc::Ty(item_id)), ty); - let mut forbid_unsized = true; - if allow_foreign_ty { - let tail = tcx.struct_tail_erasing_lifetimes(item_ty, wfcx.param_env); - if let ty::Foreign(_) = tail.kind() { - forbid_unsized = false; + let forbid_unsized = match unsized_handling { + UnsizedHandling::Forbid => true, + UnsizedHandling::Allow => false, + UnsizedHandling::AllowIfForeignTail => { + let tail = tcx.struct_tail_erasing_lifetimes(item_ty, wfcx.param_env); + !matches!(tail.kind(), ty::Foreign(_)) } - } + }; wfcx.register_wf_obligation(ty_span, Some(WellFormedLoc::Ty(item_id)), item_ty.into()); if forbid_unsized { @@ -1202,7 +1192,6 @@ fn check_impl<'tcx>( item: &'tcx hir::Item<'tcx>, ast_self_ty: &hir::Ty<'_>, ast_trait_ref: &Option>, - constness: hir::Constness, ) { enter_wf_checking_ctxt(tcx, item.span, item.owner_id.def_id, |wfcx| { match ast_trait_ref { @@ -1210,20 +1199,14 @@ fn check_impl<'tcx>( // `#[rustc_reservation_impl]` impls are not real impls and // therefore don't need to be WF (the trait's `Self: Trait` predicate // won't hold). - let trait_ref = tcx.impl_trait_ref(item.owner_id).unwrap().subst_identity(); + let trait_ref = tcx.impl_trait_ref(item.owner_id).unwrap().instantiate_identity(); let trait_ref = wfcx.normalize( ast_trait_ref.path.span, Some(WellFormedLoc::Ty(item.hir_id().expect_owner().def_id)), trait_ref, ); - let trait_pred = ty::TraitPredicate { - trait_ref, - constness: match constness { - hir::Constness::Const => ty::BoundConstness::ConstIfConst, - hir::Constness::NotConst => ty::BoundConstness::NotConst, - }, - polarity: ty::ImplPolarity::Positive, - }; + let trait_pred = + ty::TraitPredicate { trait_ref, polarity: ty::ImplPolarity::Positive }; let mut obligations = traits::wf::trait_obligations( wfcx.infcx, wfcx.param_env, @@ -1243,7 +1226,7 @@ fn check_impl<'tcx>( wfcx.register_obligations(obligations); } None => { - let self_ty = tcx.type_of(item.owner_id).subst_identity(); + let self_ty = tcx.type_of(item.owner_id).instantiate_identity(); let self_ty = wfcx.normalize( item.span, Some(WellFormedLoc::Ty(item.hir_id().expect_owner().def_id)), @@ -1288,7 +1271,7 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id match param.kind { GenericParamDefKind::Type { .. } => { if is_our_default(param) { - let ty = tcx.type_of(param.def_id).subst_identity(); + let ty = tcx.type_of(param.def_id).instantiate_identity(); // Ignore dependent defaults -- that is, where the default of one type // parameter includes another (e.g., ``). In those cases, we can't // be sure if it will error or not as user might always specify the other. @@ -1304,10 +1287,10 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id GenericParamDefKind::Const { .. } => { if is_our_default(param) { // FIXME(const_generics_defaults): This - // is incorrect when dealing with unused substs, for example + // is incorrect when dealing with unused args, for example // for `struct Foo` // we should eagerly error. - let default_ct = tcx.const_param_default(param.def_id).subst_identity(); + let default_ct = tcx.const_param_default(param.def_id).instantiate_identity(); if !default_ct.has_param() { wfcx.register_wf_obligation( tcx.def_span(param.def_id), @@ -1330,7 +1313,7 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id // For more examples see tests `defaults-well-formedness.rs` and `type-check-defaults.rs`. // // First we build the defaulted substitution. - let substs = InternalSubsts::for_item(tcx, def_id.to_def_id(), |param, _| { + let args = GenericArgs::for_item(tcx, def_id.to_def_id(), |param, _| { match param.kind { GenericParamDefKind::Lifetime => { // All regions are identity. @@ -1340,7 +1323,7 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id GenericParamDefKind::Type { .. } => { // If the param has a default, ... if is_our_default(param) { - let default_ty = tcx.type_of(param.def_id).subst_identity(); + let default_ty = tcx.type_of(param.def_id).instantiate_identity(); // ... and it's not a dependent default, ... if !default_ty.has_param() { // ... then substitute it with the default. @@ -1353,7 +1336,7 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id GenericParamDefKind::Const { .. } => { // If the param has a default, ... if is_our_default(param) { - let default_ct = tcx.const_param_default(param.def_id).subst_identity(); + let default_ct = tcx.const_param_default(param.def_id).instantiate_identity(); // ... and it's not a dependent default, ... if !default_ct.has_param() { // ... then substitute it with the default. @@ -1398,7 +1381,7 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id } let mut param_count = CountParams::default(); let has_region = pred.visit_with(&mut param_count).is_break(); - let substituted_pred = ty::EarlyBinder(pred).subst(tcx, substs); + let substituted_pred = ty::EarlyBinder::bind(pred).instantiate(tcx, args); // Don't check non-defaulted params, dependent defaults (including lifetimes) // or preds with multiple params. if substituted_pred.has_non_region_param() || param_count.params.len() > 1 || has_region @@ -1439,9 +1422,9 @@ fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, span: Span, def_id let wf_obligations = predicates.into_iter().flat_map(|(p, sp)| { traits::wf::predicate_obligations( infcx, - wfcx.param_env.without_const(), + wfcx.param_env, wfcx.body_def_id, - p, + p.as_predicate(), sp, ) }); @@ -1492,18 +1475,11 @@ fn check_fn_or_method<'tcx>( check_where_clauses(wfcx, span, def_id); - check_return_position_impl_trait_in_trait_bounds( - wfcx, - def_id, - sig.output(), - hir_decl.output.span(), - ); - if sig.abi == Abi::RustCall { let span = tcx.def_span(def_id); let has_implicit_self = hir_decl.implicit_self != hir::ImplicitSelfKind::None; let mut inputs = sig.inputs().iter().skip(if has_implicit_self { 1 } else { 0 }); - // Check that the argument is a tuple + // Check that the argument is a tuple and is sized if let Some(ty) = inputs.next() { wfcx.register_bound( ObligationCause::new(span, wfcx.body_def_id, ObligationCauseCode::RustCall), @@ -1511,6 +1487,12 @@ fn check_fn_or_method<'tcx>( *ty, tcx.require_lang_item(hir::LangItem::Tuple, Some(span)), ); + wfcx.register_bound( + ObligationCause::new(span, wfcx.body_def_id, ObligationCauseCode::RustCall), + wfcx.param_env, + *ty, + tcx.require_lang_item(hir::LangItem::Sized, Some(span)), + ); } else { tcx.sess.span_err( hir_decl.inputs.last().map_or(span, |input| input.span), @@ -1527,87 +1509,6 @@ fn check_fn_or_method<'tcx>( } } -/// Basically `check_associated_type_bounds`, but separated for now and should be -/// deduplicated when RPITITs get lowered into real associated items. -#[tracing::instrument(level = "trace", skip(wfcx))] -fn check_return_position_impl_trait_in_trait_bounds<'tcx>( - wfcx: &WfCheckingCtxt<'_, 'tcx>, - fn_def_id: LocalDefId, - fn_output: Ty<'tcx>, - span: Span, -) { - let tcx = wfcx.tcx(); - let Some(assoc_item) = tcx.opt_associated_item(fn_def_id.to_def_id()) else { - return; - }; - if assoc_item.container != ty::AssocItemContainer::TraitContainer { - return; - } - fn_output.visit_with(&mut ImplTraitInTraitFinder { - wfcx, - fn_def_id, - depth: ty::INNERMOST, - seen: FxHashSet::default(), - }); -} - -// FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): Even with the new lowering -// strategy, we can't just call `check_associated_item` on the new RPITITs, -// because tests like `tests/ui/async-await/in-trait/implied-bounds.rs` will fail. -// That's because we need to check that the bounds of the RPITIT hold using -// the special substs that we create during opaque type lowering, otherwise we're -// getting a bunch of early bound and free regions mixed up... Haven't looked too -// deep into this, though. -struct ImplTraitInTraitFinder<'a, 'tcx> { - wfcx: &'a WfCheckingCtxt<'a, 'tcx>, - fn_def_id: LocalDefId, - depth: ty::DebruijnIndex, - seen: FxHashSet, -} -impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { - type BreakTy = !; - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { - let tcx = self.wfcx.tcx(); - if let ty::Alias(ty::Opaque, unshifted_opaque_ty) = *ty.kind() - && self.seen.insert(unshifted_opaque_ty.def_id) - && let Some(opaque_def_id) = unshifted_opaque_ty.def_id.as_local() - && let opaque = tcx.hir().expect_item(opaque_def_id).expect_opaque_ty() - && let hir::OpaqueTyOrigin::FnReturn(source) | hir::OpaqueTyOrigin::AsyncFn(source) = opaque.origin - && source == self.fn_def_id - { - let opaque_ty = tcx.fold_regions(unshifted_opaque_ty, |re, _depth| { - match re.kind() { - ty::ReEarlyBound(_) | ty::ReFree(_) | ty::ReError(_) => re, - r => bug!("unexpected region: {r:?}"), - } - }); - for (bound, bound_span) in tcx - .explicit_item_bounds(opaque_ty.def_id) - .subst_iter_copied(tcx, opaque_ty.substs) - { - let bound = self.wfcx.normalize(bound_span, None, bound); - self.wfcx.register_obligations(traits::wf::predicate_obligations( - self.wfcx.infcx, - self.wfcx.param_env, - self.wfcx.body_def_id, - bound, - bound_span, - )); - // Set the debruijn index back to innermost here, since we already eagerly - // shifted the substs that we use to generate these bounds. This is unfortunately - // subtly different behavior than the `ImplTraitInTraitFinder` we use in `param_env`, - // but that function doesn't actually need to normalize the bound it's visiting - // (whereas we have to do so here)... - let old_depth = std::mem::replace(&mut self.depth, ty::INNERMOST); - bound.visit_with(self); - self.depth = old_depth; - } - } - ty.super_visit_with(self) - } -} - const HELP_FOR_SELF_TYPE: &str = "consider changing to `self`, `&self`, `&mut self`, `self: Box`, \ `self: Rc`, `self: Arc`, or `self: Pin

` (where P is one \ of the previous types except `Self`)"; @@ -1627,7 +1528,7 @@ fn check_method_receiver<'tcx>( let span = fn_sig.decl.inputs[0].span; - let sig = tcx.fn_sig(method.def_id).subst_identity(); + let sig = tcx.fn_sig(method.def_id).instantiate_identity(); let sig = tcx.liberate_late_bound_regions(method.def_id, sig); let sig = wfcx.normalize(span, None, sig); @@ -1799,9 +1700,28 @@ fn check_variances_for_type_defn<'tcx>( item: &hir::Item<'tcx>, hir_generics: &hir::Generics<'_>, ) { - let ty = tcx.type_of(item.owner_id).subst_identity(); - if tcx.has_error_field(ty) { - return; + let identity_args = ty::GenericArgs::identity_for_item(tcx, item.owner_id); + + match item.kind { + ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) => { + for field in tcx.adt_def(item.owner_id).all_fields() { + if field.ty(tcx, identity_args).references_error() { + return; + } + } + } + ItemKind::TyAlias(..) => { + let ty = tcx.type_of(item.owner_id).instantiate_identity(); + + if tcx.features().lazy_type_alias || ty.has_opaque_types() { + if ty.references_error() { + return; + } + } else { + bug!(); + } + } + _ => bug!(), } let ty_predicates = tcx.predicates_of(item.owner_id); @@ -1878,8 +1798,7 @@ fn report_bivariance( if matches!(param.kind, hir::GenericParamKind::Type { .. }) && !has_explicit_bounds { err.help(format!( - "if you intended `{0}` to be a const parameter, use `const {0}: usize` instead", - param_name + "if you intended `{param_name}` to be a const parameter, use `const {param_name}: usize` instead" )); } err.emit() @@ -1902,7 +1821,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { // We lower empty bounds like `Vec:` as // `WellFormed(Vec)`, which will later get checked by // regular WF checking - if let ty::PredicateKind::WellFormed(..) = pred.kind().skip_binder() { + if let ty::ClauseKind::WellFormed(..) = pred.kind().skip_binder() { continue; } // Match the existing behavior. @@ -1935,7 +1854,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { } } -fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalDefId) { +fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalModDefId) { let items = tcx.hir_module_items(module); items.par_items(|item| tcx.ensure().check_well_formed(item.owner_id)); items.par_impl_items(|item| tcx.ensure().check_well_formed(item.owner_id)); diff --git a/compiler/rustc_hir_analysis/src/check_unused.rs b/compiler/rustc_hir_analysis/src/check_unused.rs index 268b9ac530f18..9ad73eeffc6ae 100644 --- a/compiler/rustc_hir_analysis/src/check_unused.rs +++ b/compiler/rustc_hir_analysis/src/check_unused.rs @@ -1,12 +1,21 @@ -use rustc_data_structures::unord::UnordSet; +use rustc_data_structures::unord::{ExtendUnord, UnordSet}; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; +use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_session::lint; -pub fn check_crate(tcx: TyCtxt<'_>) { - let mut used_trait_imports: UnordSet = Default::default(); +pub fn provide(providers: &mut Providers) { + *providers = Providers { check_unused_traits, ..*providers }; +} + +fn check_unused_traits(tcx: TyCtxt<'_>, (): ()) { + let mut used_trait_imports = UnordSet::::default(); + // FIXME: Use `tcx.hir().par_body_owners()` when we implement creating `DefId`s + // for anon constants during their parents' typeck. + // Doing so at current will produce queries cycle errors because it may typeck + // on anon constants directly. for item_def_id in tcx.hir().body_owners() { let imports = tcx.used_trait_imports(item_def_id); debug!("GatherVisitor: item_def_id={:?} with imports {:#?}", item_def_id, imports); @@ -27,7 +36,7 @@ pub fn check_crate(tcx: TyCtxt<'_>) { } let (path, _) = item.expect_use(); let msg = if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(path.span) { - format!("unused import: `{}`", snippet) + format!("unused import: `{snippet}`") } else { "unused import".to_owned() }; diff --git a/compiler/rustc_hir_analysis/src/coherence/builtin.rs b/compiler/rustc_hir_analysis/src/coherence/builtin.rs index a98d8e17153d8..c930537d4aee8 100644 --- a/compiler/rustc_hir_analysis/src/coherence/builtin.rs +++ b/compiler/rustc_hir_analysis/src/coherence/builtin.rs @@ -57,7 +57,7 @@ impl<'tcx> Checker<'tcx> { fn visit_implementation_of_drop(tcx: TyCtxt<'_>, impl_did: LocalDefId) { // Destructors only work on local ADT types. - match tcx.type_of(impl_did).subst_identity().kind() { + match tcx.type_of(impl_did).instantiate_identity().kind() { ty::Adt(def, _) if def.did().is_local() => return, ty::Error(_) => return, _ => {} @@ -71,7 +71,7 @@ fn visit_implementation_of_drop(tcx: TyCtxt<'_>, impl_did: LocalDefId) { fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) { debug!("visit_implementation_of_copy: impl_did={:?}", impl_did); - let self_type = tcx.type_of(impl_did).subst_identity(); + let self_type = tcx.type_of(impl_did).instantiate_identity(); debug!("visit_implementation_of_copy: self_type={:?} (bound)", self_type); let param_env = tcx.param_env(impl_did); @@ -100,7 +100,7 @@ fn visit_implementation_of_copy(tcx: TyCtxt<'_>, impl_did: LocalDefId) { } fn visit_implementation_of_const_param_ty(tcx: TyCtxt<'_>, impl_did: LocalDefId) { - let self_type = tcx.type_of(impl_did).subst_identity(); + let self_type = tcx.type_of(impl_did).instantiate_identity(); assert!(!self_type.has_escaping_bound_vars()); let param_env = tcx.param_env(impl_did); @@ -139,13 +139,13 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: LocalDef let dispatch_from_dyn_trait = tcx.require_lang_item(LangItem::DispatchFromDyn, Some(span)); - let source = tcx.type_of(impl_did).subst_identity(); + let source = tcx.type_of(impl_did).instantiate_identity(); assert!(!source.has_escaping_bound_vars()); let target = { - let trait_ref = tcx.impl_trait_ref(impl_did).unwrap().subst_identity(); + let trait_ref = tcx.impl_trait_ref(impl_did).unwrap().instantiate_identity(); assert_eq!(trait_ref.def_id, dispatch_from_dyn_trait); - trait_ref.substs.type_at(1) + trait_ref.args.type_at(1) }; debug!("visit_implementation_of_dispatch_from_dyn: {:?} -> {:?}", source, target); @@ -163,9 +163,7 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: LocalDef if infcx.at(&cause, param_env).eq(DefineOpaqueTypes::No, r_a, *r_b).is_ok() && mutbl_a == *mutbl_b => {} (&RawPtr(tm_a), &RawPtr(tm_b)) if tm_a.mutbl == tm_b.mutbl => (), - (&Adt(def_a, substs_a), &Adt(def_b, substs_b)) - if def_a.is_struct() && def_b.is_struct() => - { + (&Adt(def_a, args_a), &Adt(def_b, args_b)) if def_a.is_struct() && def_b.is_struct() => { if def_a != def_b { let source_path = tcx.def_path_str(def_a.did()); let target_path = tcx.def_path_str(def_b.did()); @@ -173,8 +171,7 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: LocalDef create_err(&format!( "the trait `DispatchFromDyn` may only be implemented \ for a coercion between structures with the same \ - definition; expected `{}`, found `{}`", - source_path, target_path, + definition; expected `{source_path}`, found `{target_path}`", )) .emit(); @@ -194,8 +191,8 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: LocalDef let coerced_fields = fields .iter() .filter(|field| { - let ty_a = field.ty(tcx, substs_a); - let ty_b = field.ty(tcx, substs_b); + let ty_a = field.ty(tcx, args_a); + let ty_b = field.ty(tcx, args_b); if let Ok(layout) = tcx.layout_of(param_env.and(ty_a)) { if layout.is_zst() && layout.align.abi.bytes() == 1 { @@ -250,8 +247,8 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: LocalDef format!( "`{}` (`{}` to `{}`)", field.name, - field.ty(tcx, substs_a), - field.ty(tcx, substs_b), + field.ty(tcx, args_a), + field.ty(tcx, args_b), ) }) .collect::>() @@ -268,7 +265,7 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: LocalDef ty::TraitRef::new( tcx, dispatch_from_dyn_trait, - [field.ty(tcx, substs_a), field.ty(tcx, substs_b)], + [field.ty(tcx, args_a), field.ty(tcx, args_b)], ), )); } @@ -300,10 +297,10 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: LocalDefId) -> Coe let unsize_trait = tcx.require_lang_item(LangItem::Unsize, Some(span)); - let source = tcx.type_of(impl_did).subst_identity(); - let trait_ref = tcx.impl_trait_ref(impl_did).unwrap().subst_identity(); + let source = tcx.type_of(impl_did).instantiate_identity(); + let trait_ref = tcx.impl_trait_ref(impl_did).unwrap().instantiate_identity(); assert_eq!(trait_ref.def_id, coerce_unsized_trait); - let target = trait_ref.substs.type_at(1); + let target = trait_ref.args.type_at(1); debug!("visit_implementation_of_coerce_unsized: {:?} -> {:?} (bound)", source, target); let param_env = tcx.param_env(impl_did); @@ -336,17 +333,19 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: LocalDefId) -> Coe infcx.sub_regions(infer::RelateObjectBound(span), r_b, r_a); let mt_a = ty::TypeAndMut { ty: ty_a, mutbl: mutbl_a }; let mt_b = ty::TypeAndMut { ty: ty_b, mutbl: mutbl_b }; - check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ref(r_b, ty)) + check_mutbl(mt_a, mt_b, &|ty| Ty::new_imm_ref(tcx, r_b, ty)) } (&ty::Ref(_, ty_a, mutbl_a), &ty::RawPtr(mt_b)) => { let mt_a = ty::TypeAndMut { ty: ty_a, mutbl: mutbl_a }; - check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ptr(ty)) + check_mutbl(mt_a, mt_b, &|ty| Ty::new_imm_ptr(tcx, ty)) } - (&ty::RawPtr(mt_a), &ty::RawPtr(mt_b)) => check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ptr(ty)), + (&ty::RawPtr(mt_a), &ty::RawPtr(mt_b)) => { + check_mutbl(mt_a, mt_b, &|ty| Ty::new_imm_ptr(tcx, ty)) + } - (&ty::Adt(def_a, substs_a), &ty::Adt(def_b, substs_b)) + (&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b)) if def_a.is_struct() && def_b.is_struct() => { if def_a != def_b { @@ -409,9 +408,9 @@ pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: LocalDefId) -> Coe let diff_fields = fields .iter_enumerated() .filter_map(|(i, f)| { - let (a, b) = (f.ty(tcx, substs_a), f.ty(tcx, substs_b)); + let (a, b) = (f.ty(tcx, args_a), f.ty(tcx, args_b)); - if tcx.type_of(f.did).subst_identity().is_phantom_data() { + if tcx.type_of(f.did).instantiate_identity().is_phantom_data() { // Ignore PhantomData fields return None; } @@ -571,7 +570,7 @@ fn infringing_fields_error( .or_default() .push(error.obligation.cause.span); } - if let ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, polarity: ty::ImplPolarity::Positive, .. diff --git a/compiler/rustc_hir_analysis/src/coherence/inherent_impls.rs b/compiler/rustc_hir_analysis/src/coherence/inherent_impls.rs index 3355902069248..a94c75f918a90 100644 --- a/compiler/rustc_hir_analysis/src/coherence/inherent_impls.rs +++ b/compiler/rustc_hir_analysis/src/coherence/inherent_impls.rs @@ -148,8 +148,7 @@ impl<'tcx> InherentCollect<'tcx> { if let ty::Ref(_, subty, _) = ty.kind() { err.note(format!( "you could also try moving the reference to \ - uses of `{}` (such as `self`) within the implementation", - subty + uses of `{subty}` (such as `self`) within the implementation" )); } err.emit(); @@ -171,7 +170,7 @@ impl<'tcx> InherentCollect<'tcx> { let id = id.owner_id.def_id; let item_span = self.tcx.def_span(id); - let self_ty = self.tcx.type_of(id).subst_identity(); + let self_ty = self.tcx.type_of(id).instantiate_identity(); match *self_ty.kind() { ty::Adt(def, _) => self.check_def_id(id, self_ty, def.did()), ty::Foreign(did) => self.check_def_id(id, self_ty, did), diff --git a/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs b/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs index bd6252344b2c2..7205b7a21a823 100644 --- a/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs +++ b/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs @@ -77,8 +77,8 @@ impl<'tcx> InherentOverlapChecker<'tcx> { "duplicate definitions with name `{}`", ident, ); - err.span_label(span, format!("duplicate definitions for `{}`", ident)); - err.span_label(*former, format!("other definition for `{}`", ident)); + err.span_label(span, format!("duplicate definitions for `{ident}`")); + err.span_label(*former, format!("other definition for `{ident}`")); err.emit(); } @@ -114,11 +114,11 @@ impl<'tcx> InherentOverlapChecker<'tcx> { ); err.span_label( self.tcx.def_span(item1.def_id), - format!("duplicate definitions for `{}`", name), + format!("duplicate definitions for `{name}`"), ); err.span_label( self.tcx.def_span(item2.def_id), - format!("other definition for `{}`", name), + format!("other definition for `{name}`"), ); for cause in &overlap.intercrate_ambiguity_causes { @@ -140,7 +140,7 @@ impl<'tcx> InherentOverlapChecker<'tcx> { impl1_def_id: DefId, impl2_def_id: DefId, ) { - traits::overlapping_impls( + let maybe_overlap = traits::overlapping_impls( self.tcx, impl1_def_id, impl2_def_id, @@ -148,11 +148,11 @@ impl<'tcx> InherentOverlapChecker<'tcx> { // inherent impls without warning. SkipLeakCheck::Yes, overlap_mode, - ) - .map_or(true, |overlap| { + ); + + if let Some(overlap) = maybe_overlap { self.check_for_common_items_in_impls(impl1_def_id, impl2_def_id, overlap); - false - }); + } } fn check_item(&mut self, id: hir::ItemId) { diff --git a/compiler/rustc_hir_analysis/src/coherence/mod.rs b/compiler/rustc_hir_analysis/src/coherence/mod.rs index 4524b87a418aa..fc8fab0eabc83 100644 --- a/compiler/rustc_hir_analysis/src/coherence/mod.rs +++ b/compiler/rustc_hir_analysis/src/coherence/mod.rs @@ -10,7 +10,6 @@ use rustc_errors::{error_code, struct_span_err}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::query::Providers; use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; -use rustc_span::sym; use rustc_trait_selection::traits; mod builtin; @@ -44,7 +43,7 @@ fn enforce_trait_manually_implementable( let impl_header_span = tcx.def_span(impl_def_id); // Disallow *all* explicit impls of traits marked `#[rustc_deny_explicit_impl]` - if tcx.has_attr(trait_def_id, sym::rustc_deny_explicit_impl) { + if tcx.trait_def(trait_def_id).deny_explicit_impl { let trait_name = tcx.item_name(trait_def_id); let mut err = struct_span_err!( tcx.sess, @@ -123,7 +122,7 @@ fn coherent_trait(tcx: TyCtxt<'_>, def_id: DefId) { let impls = tcx.hir().trait_impls(def_id); for &impl_def_id in impls { - let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().subst_identity(); + let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity(); check_impl(tcx, impl_def_id, trait_ref); check_object_overlap(tcx, impl_def_id, trait_ref); diff --git a/compiler/rustc_hir_analysis/src/coherence/orphan.rs b/compiler/rustc_hir_analysis/src/coherence/orphan.rs index 23beacd2a8c2c..bbdb108c59b63 100644 --- a/compiler/rustc_hir_analysis/src/coherence/orphan.rs +++ b/compiler/rustc_hir_analysis/src/coherence/orphan.rs @@ -5,8 +5,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::{struct_span_err, DelayDm}; use rustc_errors::{Diagnostic, ErrorGuaranteed}; use rustc_hir as hir; -use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::util::CheckRegions; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{ self, AliasKind, ImplPolarity, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, @@ -22,7 +22,7 @@ pub(crate) fn orphan_check_impl( tcx: TyCtxt<'_>, impl_def_id: LocalDefId, ) -> Result<(), ErrorGuaranteed> { - let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().subst_identity(); + let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity(); trait_ref.error_reported()?; let ret = do_orphan_check_impl(tcx, trait_ref, impl_def_id); @@ -200,35 +200,32 @@ fn do_orphan_check_impl<'tcx>( NonlocalImpl::DisallowOther, ), - // trait Id { type This: ?Sized; } - // impl Id for T { - // type This = T; - // } - // impl AutoTrait for ::This {} - ty::Alias(AliasKind::Projection, _) => ( - LocalImpl::Disallow { problematic_kind: "associated type" }, - NonlocalImpl::DisallowOther, - ), - - // ``` - // struct S(T); - // impl S { - // type This = T; - // } - // impl AutoTrait for S::This {} - // ``` - // FIXME(inherent_associated_types): The example code above currently leads to a cycle - ty::Alias(AliasKind::Inherent, _) => ( - LocalImpl::Disallow { problematic_kind: "associated type" }, - NonlocalImpl::DisallowOther, - ), - - // type Opaque = impl Trait; - // impl AutoTrait for Opaque {} - ty::Alias(AliasKind::Opaque, _) => ( - LocalImpl::Disallow { problematic_kind: "opaque type" }, - NonlocalImpl::DisallowOther, - ), + ty::Alias(kind, _) => { + let problematic_kind = match kind { + // trait Id { type This: ?Sized; } + // impl Id for T { + // type This = T; + // } + // impl AutoTrait for ::This {} + AliasKind::Projection => "associated type", + // type Foo = (impl Sized, bool) + // impl AutoTrait for Foo {} + AliasKind::Weak => "type alias", + // type Opaque = impl Trait; + // impl AutoTrait for Opaque {} + AliasKind::Opaque => "opaque type", + // ``` + // struct S(T); + // impl S { + // type This = T; + // } + // impl AutoTrait for S::This {} + // ``` + // FIXME(inherent_associated_types): The example code above currently leads to a cycle + AliasKind::Inherent => "associated type", + }; + (LocalImpl::Disallow { problematic_kind }, NonlocalImpl::DisallowOther) + } ty::Bool | ty::Char @@ -346,7 +343,7 @@ fn emit_orphan_check_error<'tcx>( // That way if we had `Vec`, we will properly attribute the // problem to `Vec` and avoid confusing the user if they were to see // `MyType` in the error. - ty::Adt(def, _) => tcx.mk_adt(*def, ty::List::empty()), + ty::Adt(def, _) => Ty::new_adt(tcx, *def, ty::List::empty()), _ => ty, }; let msg = |ty: &str, postfix: &str| { @@ -355,7 +352,7 @@ fn emit_orphan_check_error<'tcx>( let this = |name: &str| { if !trait_ref.def_id.is_local() && !is_target_ty { - msg("this", &format!(" because this is a foreign trait")) + msg("this", " because this is a foreign trait") } else { msg("this", &format!(" because {name} are always foreign")) } @@ -415,9 +412,8 @@ fn emit_orphan_check_error<'tcx>( .span_label( sp, format!( - "type parameter `{}` must be covered by another type \ - when it appears before the first local type (`{}`)", - param_ty, local_type + "type parameter `{param_ty}` must be covered by another type \ + when it appears before the first local type (`{local_type}`)" ), ) .note( @@ -444,9 +440,8 @@ fn emit_orphan_check_error<'tcx>( .span_label( sp, format!( - "type parameter `{}` must be used as the type parameter for some \ + "type parameter `{param_ty}` must be used as the type parameter for some \ local type", - param_ty, ), ) .note( @@ -491,10 +486,10 @@ fn lint_auto_trait_impl<'tcx>( trait_ref: ty::TraitRef<'tcx>, impl_def_id: LocalDefId, ) { - assert_eq!(trait_ref.substs.len(), 1); + assert_eq!(trait_ref.args.len(), 1); let self_ty = trait_ref.self_ty(); - let (self_type_did, substs) = match self_ty.kind() { - ty::Adt(def, substs) => (def.did(), substs), + let (self_type_did, args) = match self_ty.kind() { + ty::Adt(def, args) => (def.did(), args), _ => { // FIXME: should also lint for stuff like `&i32` but // considering that auto traits are unstable, that @@ -505,9 +500,9 @@ fn lint_auto_trait_impl<'tcx>( }; // Impls which completely cover a given root type are fine as they - // disable auto impls entirely. So only lint if the substs - // are not a permutation of the identity substs. - let Err(arg) = tcx.uses_unique_generic_params(substs, CheckRegions::No) else { + // disable auto impls entirely. So only lint if the args + // are not a permutation of the identity args. + let Err(arg) = tcx.uses_unique_generic_params(args, CheckRegions::No) else { // ok return; }; @@ -544,17 +539,16 @@ fn lint_auto_trait_impl<'tcx>( let self_descr = tcx.def_descr(self_type_did); match arg { ty::util::NotUniqueParam::DuplicateParam(arg) => { - lint.note(format!("`{}` is mentioned multiple times", arg)); + lint.note(format!("`{arg}` is mentioned multiple times")); } ty::util::NotUniqueParam::NotParam(arg) => { - lint.note(format!("`{}` is not a generic parameter", arg)); + lint.note(format!("`{arg}` is not a generic parameter")); } } lint.span_note( item_span, format!( - "try using the same sequence of generic parameters as the {} definition", - self_descr, + "try using the same sequence of generic parameters as the {self_descr} definition", ), ) }, @@ -571,10 +565,10 @@ fn fast_reject_auto_impl<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId, self_ty: impl<'tcx> TypeVisitor> for DisableAutoTraitVisitor<'tcx> { type BreakTy = (); - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { let tcx = self.tcx; - if t != self.self_ty_root { - for impl_def_id in tcx.non_blanket_impls_for_ty(self.trait_def_id, t) { + if ty != self.self_ty_root { + for impl_def_id in tcx.non_blanket_impls_for_ty(self.trait_def_id, ty) { match tcx.impl_polarity(impl_def_id) { ImplPolarity::Negative => return ControlFlow::Break(()), ImplPolarity::Reservation => {} @@ -587,28 +581,28 @@ fn fast_reject_auto_impl<'tcx>(tcx: TyCtxt<'tcx>, trait_def_id: DefId, self_ty: } } - match t.kind() { - ty::Adt(def, substs) if def.is_phantom_data() => substs.visit_with(self), - ty::Adt(def, substs) => { + match ty.kind() { + ty::Adt(def, args) if def.is_phantom_data() => args.visit_with(self), + ty::Adt(def, args) => { // @lcnr: This is the only place where cycles can happen. We avoid this // by only visiting each `DefId` once. // // This will be is incorrect in subtle cases, but I don't care :) if self.seen.insert(def.did()) { - for ty in def.all_fields().map(|field| field.ty(tcx, substs)) { + for ty in def.all_fields().map(|field| field.ty(tcx, args)) { ty.visit_with(self)?; } } ControlFlow::Continue(()) } - _ => t.super_visit_with(self), + _ => ty.super_visit_with(self), } } } let self_ty_root = match self_ty.kind() { - ty::Adt(def, _) => tcx.mk_adt(*def, InternalSubsts::identity_for_item(tcx, def.did())), + ty::Adt(def, _) => Ty::new_adt(tcx, *def, GenericArgs::identity_for_item(tcx, def.did())), _ => unimplemented!("unexpected self ty {:?}", self_ty), }; diff --git a/compiler/rustc_hir_analysis/src/coherence/unsafety.rs b/compiler/rustc_hir_analysis/src/coherence/unsafety.rs index c6b16171311fb..6b18b0ebe9d1a 100644 --- a/compiler/rustc_hir_analysis/src/coherence/unsafety.rs +++ b/compiler/rustc_hir_analysis/src/coherence/unsafety.rs @@ -12,7 +12,7 @@ pub(super) fn check_item(tcx: TyCtxt<'_>, def_id: LocalDefId) { let impl_ = item.expect_impl(); if let Some(trait_ref) = tcx.impl_trait_ref(item.owner_id) { - let trait_ref = trait_ref.subst_identity(); + let trait_ref = trait_ref.instantiate_identity(); let trait_def = tcx.trait_def(trait_ref.def_id); let unsafe_attr = impl_.generics.params.iter().find(|p| p.pure_wrt_drop).map(|_| "may_dangle"); diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index ca0d5509c5768..7b9f61d7ab213 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -22,7 +22,7 @@ use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed, StashKey}; use rustc_hir as hir; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{GenericParamKind, Node}; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; @@ -48,7 +48,7 @@ mod type_of; /////////////////////////////////////////////////////////////////////////// // Main entry point -fn collect_mod_item_types(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn collect_mod_item_types(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { tcx.hir().visit_item_likes_in_module(module_def_id, &mut CollectItemTypesVisitor { tcx }); } @@ -195,9 +195,9 @@ pub(crate) fn placeholder_type_error_diag<'tcx>( sugg.push((arg.span, (*type_name).to_string())); } else if let Some(span) = generics.span_for_param_suggestion() { // Account for bounds, we want `fn foo(_: K)` not `fn foo(_: K)`. - sugg.push((span, format!(", {}", type_name))); + sugg.push((span, format!(", {type_name}"))); } else { - sugg.push((generics.span, format!("<{}>", type_name))); + sugg.push((generics.span, format!("<{type_name}>"))); } } @@ -329,7 +329,7 @@ fn bad_placeholder<'tcx>( mut spans: Vec, kind: &'static str, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { - let kind = if kind.ends_with('s') { format!("{}es", kind) } else { format!("{}s", kind) }; + let kind = if kind.ends_with('s') { format!("{kind}es") } else { format!("{kind}s") }; spans.sort(); tcx.sess.create_err(errors::PlaceholderNotAllowedItemSignatures { spans, kind }) @@ -380,7 +380,7 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> { } fn ty_infer(&self, _: Option<&ty::GenericParamDef>, span: Span) -> Ty<'tcx> { - self.tcx().ty_error_with_message(span, "bad placeholder type") + Ty::new_error_with_message(self.tcx(), span, "bad placeholder type") } fn ct_infer(&self, ty: Ty<'tcx>, _: Option<&ty::GenericParamDef>, span: Span) -> Const<'tcx> { @@ -390,7 +390,7 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> { // left alone. r => bug!("unexpected region: {r:?}"), }); - self.tcx().const_error_with_message(ty, span, "bad placeholder constant") + ty::Const::new_error_with_message(self.tcx(), ty, span, "bad placeholder constant") } fn projected_ty_from_poly_trait_ref( @@ -401,13 +401,13 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> { poly_trait_ref: ty::PolyTraitRef<'tcx>, ) -> Ty<'tcx> { if let Some(trait_ref) = poly_trait_ref.no_bound_vars() { - let item_substs = self.astconv().create_substs_for_associated_item( + let item_args = self.astconv().create_args_for_associated_item( span, item_def_id, item_segment, - trait_ref.substs, + trait_ref.args, ); - self.tcx().mk_projection(item_def_id, item_substs) + Ty::new_projection(self.tcx(), item_def_id, item_args) } else { // There are no late-bound regions; we can just ignore the binder. let (mut mpart_sugg, mut inferred_sugg) = (None, None); @@ -425,10 +425,8 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> { | hir::ItemKind::Union(_, generics) => { let lt_name = get_new_lifetime_name(self.tcx, poly_trait_ref, generics); let (lt_sp, sugg) = match generics.params { - [] => (generics.span, format!("<{}>", lt_name)), - [bound, ..] => { - (bound.span.shrink_to_lo(), format!("{}, ", lt_name)) - } + [] => (generics.span, format!("<{lt_name}>")), + [bound, ..] => (bound.span.shrink_to_lo(), format!("{lt_name}, ")), }; mpart_sugg = Some(errors::AssociatedTypeTraitUninferredGenericParamsMultipartSuggestion { fspan: lt_sp, @@ -440,7 +438,7 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> { self.tcx.replace_late_bound_regions_uncached( poly_trait_ref, |_| { - self.tcx.mk_re_early_bound(ty::EarlyBoundRegion { + ty::Region::new_early_bound(self.tcx, ty::EarlyBoundRegion { def_id: item_def_id, index: 0, name: Symbol::intern(<_name), @@ -471,14 +469,15 @@ impl<'tcx> AstConv<'tcx> for ItemCtxt<'tcx> { } _ => {} } - self.tcx().ty_error(self.tcx().sess.emit_err( - errors::AssociatedTypeTraitUninferredGenericParams { + Ty::new_error( + self.tcx(), + self.tcx().sess.emit_err(errors::AssociatedTypeTraitUninferredGenericParams { span, inferred_sugg, bound, mpart_sugg, - }, - )) + }), + ) } } @@ -666,17 +665,15 @@ fn convert_trait_item(tcx: TyCtxt<'_>, trait_item_id: hir::TraitItemId) { tcx.ensure().fn_sig(def_id); } - hir::TraitItemKind::Const(.., Some(_)) => { - tcx.ensure().type_of(def_id); - } - - hir::TraitItemKind::Const(hir_ty, _) => { + hir::TraitItemKind::Const(ty, body_id) => { tcx.ensure().type_of(def_id); - // Account for `const C: _;`. - let mut visitor = HirPlaceholderCollector::default(); - visitor.visit_trait_item(trait_item); - if !tcx.sess.diagnostic().has_stashed_diagnostic(hir_ty.span, StashKey::ItemNoType) { - placeholder_type_error(tcx, None, visitor.0, false, None, "constant"); + if !tcx.sess.diagnostic().has_stashed_diagnostic(ty.span, StashKey::ItemNoType) + && !(is_suggestable_infer_ty(ty) && body_id.is_some()) + { + // Account for `const C: _;`. + let mut visitor = HirPlaceholderCollector::default(); + visitor.visit_trait_item(trait_item); + placeholder_type_error(tcx, None, visitor.0, false, None, "associated constant"); } } @@ -721,7 +718,14 @@ fn convert_impl_item(tcx: TyCtxt<'_>, impl_item_id: hir::ImplItemId) { placeholder_type_error(tcx, None, visitor.0, false, None, "associated type"); } - hir::ImplItemKind::Const(..) => {} + hir::ImplItemKind::Const(ty, _) => { + // Account for `const T: _ = ..;` + if !is_suggestable_infer_ty(ty) { + let mut visitor = HirPlaceholderCollector::default(); + visitor.visit_impl_item(impl_item); + placeholder_type_error(tcx, None, visitor.0, false, None, "associated constant"); + } + } } } @@ -941,7 +945,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { match item { Some(item) if matches!(item.kind, hir::AssocItemKind::Fn { .. }) => { - if !tcx.impl_defaultness(item.id.owner_id).has_value() { + if !tcx.defaultness(item.id.owner_id).has_value() { tcx.sess.emit_err(errors::FunctionNotHaveDefaultImplementation { span: item.span, note_span: attr_span, @@ -986,6 +990,50 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { no_dups.then_some(list) }); + let mut deny_explicit_impl = false; + let mut implement_via_object = true; + if let Some(attr) = tcx.get_attr(def_id, sym::rustc_deny_explicit_impl) { + deny_explicit_impl = true; + let mut seen_attr = false; + for meta in attr.meta_item_list().iter().flatten() { + if let Some(meta) = meta.meta_item() + && meta.name_or_empty() == sym::implement_via_object + && let Some(lit) = meta.name_value_literal() + { + if seen_attr { + tcx.sess.span_err( + meta.span, + "duplicated `implement_via_object` meta item", + ); + } + seen_attr = true; + + match lit.symbol { + kw::True => { + implement_via_object = true; + } + kw::False => { + implement_via_object = false; + } + _ => { + tcx.sess.span_err( + meta.span, + format!("unknown literal passed to `implement_via_object` attribute: {}", lit.symbol), + ); + } + } + } else { + tcx.sess.span_err( + meta.span(), + format!("unknown meta item passed to `rustc_deny_explicit_impl` {meta:?}"), + ); + } + } + if !seen_attr { + tcx.sess.span_err(attr.span, "missing `implement_via_object` meta item"); + } + } + ty::TraitDef { def_id: def_id.to_def_id(), unsafety, @@ -996,6 +1044,8 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { skip_array_during_method_dispatch, specialization_kind, must_implement_one_of, + implement_via_object, + deny_explicit_impl, } } @@ -1093,8 +1143,8 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder { - let ty = tcx.type_of(tcx.hir().get_parent_item(hir_id)).subst_identity(); - let inputs = data.fields().iter().map(|f| tcx.type_of(f.def_id).subst_identity()); + let ty = tcx.type_of(tcx.hir().get_parent_item(hir_id)).instantiate_identity(); + let inputs = data.fields().iter().map(|f| tcx.type_of(f.def_id).instantiate_identity()); ty::Binder::dummy(tcx.mk_fn_sig( inputs, ty, @@ -1109,22 +1159,20 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder { bug!("unexpected sort of node in fn_sig(): {:?}", x); } }; - ty::EarlyBinder(output) + ty::EarlyBinder::bind(output) } fn infer_return_ty_for_fn_sig<'tcx>( @@ -1188,7 +1236,7 @@ fn infer_return_ty_for_fn_sig<'tcx>( } else { ty::Binder::dummy(tcx.mk_fn_sig( fn_sig.inputs().iter().copied(), - tcx.ty_error(guar), + Ty::new_error(tcx, guar), fn_sig.c_variadic, fn_sig.unsafety, fn_sig.abi, @@ -1214,7 +1262,7 @@ fn suggest_impl_trait<'tcx>( ) -> Option { let format_as_assoc: fn(_, _, _, _, _) -> _ = |tcx: TyCtxt<'tcx>, - _: ty::SubstsRef<'tcx>, + _: ty::GenericArgsRef<'tcx>, trait_def_id: DefId, assoc_item_def_id: DefId, item_ty: Ty<'tcx>| { @@ -1224,13 +1272,15 @@ fn suggest_impl_trait<'tcx>( }; let format_as_parenthesized: fn(_, _, _, _, _) -> _ = |tcx: TyCtxt<'tcx>, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, trait_def_id: DefId, _: DefId, item_ty: Ty<'tcx>| { let trait_name = tcx.item_name(trait_def_id); - let args_tuple = substs.type_at(1); - let ty::Tuple(types) = *args_tuple.kind() else { return None; }; + let args_tuple = args.type_at(1); + let ty::Tuple(types) = *args_tuple.kind() else { + return None; + }; let types = types.make_suggestable(tcx, false)?; let maybe_ret = if item_ty.is_unit() { String::new() } else { format!(" -> {item_ty}") }; @@ -1263,31 +1313,34 @@ fn suggest_impl_trait<'tcx>( format_as_parenthesized, ), ] { - let Some(trait_def_id) = trait_def_id else { continue; }; - let Some(assoc_item_def_id) = assoc_item_def_id else { continue; }; + let Some(trait_def_id) = trait_def_id else { + continue; + }; + let Some(assoc_item_def_id) = assoc_item_def_id else { + continue; + }; if tcx.def_kind(assoc_item_def_id) != DefKind::AssocTy { continue; } let param_env = tcx.param_env(def_id); let infcx = tcx.infer_ctxt().build(); - let substs = ty::InternalSubsts::for_item(tcx, trait_def_id, |param, _| { + let args = ty::GenericArgs::for_item(tcx, trait_def_id, |param, _| { if param.index == 0 { ret_ty.into() } else { infcx.var_for_def(span, param) } }); - if !infcx.type_implements_trait(trait_def_id, substs, param_env).must_apply_modulo_regions() - { + if !infcx.type_implements_trait(trait_def_id, args, param_env).must_apply_modulo_regions() { continue; } - let ocx = ObligationCtxt::new_in_snapshot(&infcx); + let ocx = ObligationCtxt::new(&infcx); let item_ty = ocx.normalize( &ObligationCause::misc(span, def_id), param_env, - tcx.mk_projection(assoc_item_def_id, substs), + Ty::new_projection(tcx, assoc_item_def_id, args), ); // FIXME(compiler-errors): We may benefit from resolving regions here. if ocx.select_where_possible().is_empty() && let item_ty = infcx.resolve_vars_if_possible(item_ty) && let Some(item_ty) = item_ty.make_suggestable(tcx, false) - && let Some(sugg) = formatter(tcx, infcx.resolve_vars_if_possible(substs), trait_def_id, assoc_item_def_id, item_ty) + && let Some(sugg) = formatter(tcx, infcx.resolve_vars_if_possible(args), trait_def_id, assoc_item_def_id, item_ty) { return Some(sugg); } @@ -1305,39 +1358,61 @@ fn impl_trait_ref( .of_trait .as_ref() .map(|ast_trait_ref| { - let selfty = tcx.type_of(def_id).subst_identity(); - icx.astconv().instantiate_mono_trait_ref( - ast_trait_ref, - selfty, - check_impl_constness(tcx, impl_.constness, ast_trait_ref), - ) + let selfty = tcx.type_of(def_id).instantiate_identity(); + + if let Some(ErrorGuaranteed { .. }) = check_impl_constness( + tcx, + tcx.is_const_trait_impl_raw(def_id.to_def_id()), + &ast_trait_ref, + ) { + // we have a const impl, but for a trait without `#[const_trait]`, so + // without the host param. If we continue with the HIR trait ref, we get + // ICEs for generic arg count mismatch. We do a little HIR editing to + // make astconv happy. + let mut path_segments = ast_trait_ref.path.segments.to_vec(); + let last_segment = path_segments.len() - 1; + let mut args = path_segments[last_segment].args().clone(); + let last_arg = args.args.len() - 1; + assert!(matches!(args.args[last_arg], hir::GenericArg::Const(anon_const) if tcx.has_attr(anon_const.value.def_id, sym::rustc_host))); + args.args = &args.args[..args.args.len() - 1]; + path_segments[last_segment].args = Some(&args); + let path = hir::Path { + span: ast_trait_ref.path.span, + res: ast_trait_ref.path.res, + segments: &path_segments, + }; + let trait_ref = hir::TraitRef { path: &path, hir_ref_id: ast_trait_ref.hir_ref_id }; + icx.astconv().instantiate_mono_trait_ref(&trait_ref, selfty) + } else { + icx.astconv().instantiate_mono_trait_ref(&ast_trait_ref, selfty) + } }) - .map(ty::EarlyBinder) + .map(ty::EarlyBinder::bind) } fn check_impl_constness( tcx: TyCtxt<'_>, - constness: hir::Constness, + is_const: bool, ast_trait_ref: &hir::TraitRef<'_>, -) -> ty::BoundConstness { - match constness { - hir::Constness::Const => { - if let Some(trait_def_id) = ast_trait_ref.trait_def_id() && !tcx.has_attr(trait_def_id, sym::const_trait) { - let trait_name = tcx.item_name(trait_def_id).to_string(); - tcx.sess.emit_err(errors::ConstImplForNonConstTrait { - trait_ref_span: ast_trait_ref.path.span, - trait_name, - local_trait_span: trait_def_id.as_local().map(|_| tcx.def_span(trait_def_id).shrink_to_lo()), - marking: (), - adding: (), - }); - ty::BoundConstness::NotConst - } else { - ty::BoundConstness::ConstIfConst - } - }, - hir::Constness::NotConst => ty::BoundConstness::NotConst, +) -> Option { + if !is_const { + return None; } + + let trait_def_id = ast_trait_ref.trait_def_id()?; + if tcx.has_attr(trait_def_id, sym::const_trait) { + return None; + } + + let trait_name = tcx.item_name(trait_def_id).to_string(); + Some(tcx.sess.emit_err(errors::ConstImplForNonConstTrait { + trait_ref_span: ast_trait_ref.path.span, + trait_name, + local_trait_span: + trait_def_id.as_local().map(|_| tcx.def_span(trait_def_id).shrink_to_lo()), + marking: (), + adding: (), + })) } fn impl_polarity(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::ImplPolarity { @@ -1450,7 +1525,7 @@ fn compute_sig_of_foreign_fn_decl<'tcx>( .sess .source_map() .span_to_snippet(ast_ty.span) - .map_or_else(|_| String::new(), |s| format!(" `{}`", s)); + .map_or_else(|_| String::new(), |s| format!(" `{s}`")); tcx.sess.emit_err(errors::SIMDFFIHighlyExperimental { span: ast_ty.span, snip }); } }; diff --git a/compiler/rustc_hir_analysis/src/collect/generics_of.rs b/compiler/rustc_hir_analysis/src/collect/generics_of.rs index ed60998ec8dcf..4842008279a90 100644 --- a/compiler/rustc_hir_analysis/src/collect/generics_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/generics_of.rs @@ -9,7 +9,7 @@ use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::lint; use rustc_span::symbol::{kw, Symbol}; -use rustc_span::Span; +use rustc_span::{sym, Span}; pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { use rustc_hir::*; @@ -50,7 +50,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { // We do not allow generic parameters in anon consts if we are inside // of a const parameter type, e.g. `struct Foo` is not allowed. None - } else if tcx.lazy_normalization() { + } else if tcx.features().generic_const_exprs { let parent_node = tcx.hir().get_parent(hir_id); if let Node::Variant(Variant { disr_expr: Some(constant), .. }) = parent_node && constant.hir_id == hir_id @@ -68,17 +68,17 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { // ^ parent_def_id // // then we only want to return generics for params to the left of `N`. If we don't do that we - // end up with that const looking like: `ty::ConstKind::Unevaluated(def_id, substs: [N#0])`. + // end up with that const looking like: `ty::ConstKind::Unevaluated(def_id, args: [N#0])`. // - // This causes ICEs (#86580) when building the substs for Foo in `fn foo() -> Foo { .. }` as - // we substitute the defaults with the partially built substs when we build the substs. Subst'ing - // the `N#0` on the unevaluated const indexes into the empty substs we're in the process of building. + // This causes ICEs (#86580) when building the args for Foo in `fn foo() -> Foo { .. }` as + // we substitute the defaults with the partially built args when we build the args. Subst'ing + // the `N#0` on the unevaluated const indexes into the empty args we're in the process of building. // // We fix this by having this function return the parent's generics ourselves and truncating the // generics to only include non-forward declared params (with the exception of the `Self` ty) // - // For the above code example that means we want `substs: []` - // For the following struct def we want `substs: [N#0]` when generics_of is called on + // For the above code example that means we want `args: []` + // For the following struct def we want `args: [N#0]` when generics_of is called on // the def id of the `{ N + 1 }` anon const // struct Foo; // @@ -93,7 +93,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { return ty::Generics { // we set the parent of these generics to be our parent's parent so that we - // dont end up with substs: [N, M, N] for the const default on a struct like this: + // dont end up with args: [N, M, N] for the const default on a struct like this: // struct Foo; parent: generics.parent, parent_count: generics.parent_count, @@ -101,6 +101,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { param_def_id_to_index, has_self: generics.has_self, has_late_bound_regions: generics.has_late_bound_regions, + host_effect_index: None, }; } else { // HACK(eddyb) this provides the correct generics when @@ -123,9 +124,6 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { { Some(parent_def_id.to_def_id()) } - Node::Expr(&Expr { kind: ExprKind::ConstBlock(_), .. }) => { - Some(tcx.typeck_root_def_id(def_id.to_def_id())) - } // Exclude `GlobalAsm` here which cannot have generics. Node::Expr(&Expr { kind: ExprKind::InlineAsm(asm), .. }) if asm.operands.iter().any(|(op, _op_sp)| match op { @@ -142,11 +140,12 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { } } } - Node::Expr(&hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => { + Node::ConstBlock(_) + | Node::Expr(&hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => { Some(tcx.typeck_root_def_id(def_id.to_def_id())) } Node::Item(item) => match item.kind { - ItemKind::OpaqueTy(hir::OpaqueTy { + ItemKind::OpaqueTy(&hir::OpaqueTy { origin: hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id), in_trait, @@ -210,6 +209,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { | ItemKind::Struct(..) | ItemKind::OpaqueTy(..) | ItemKind::Union(..) => (None, Defaults::Allowed), + ItemKind::Const(..) => (None, Defaults::Deny), _ => (None, Defaults::FutureCompatDisallowed), } } @@ -228,10 +228,12 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { let has_self = opt_self.is_some(); let mut parent_has_self = false; let mut own_start = has_self as u32; + let mut host_effect_index = None; let parent_count = parent_def_id.map_or(0, |def_id| { let generics = tcx.generics_of(def_id); assert!(!has_self); parent_has_self = generics.has_self; + host_effect_index = generics.host_effect_index; own_start = generics.count() as u32; generics.parent_count + generics.params.len() }); @@ -253,11 +255,11 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { // Now create the real type and const parameters. let type_start = own_start - has_self as u32 + params.len() as u32; - let mut i = 0; + let mut i: u32 = 0; let mut next_index = || { let prev = i; i += 1; - prev as u32 + type_start + prev + type_start }; const TYPE_DEFAULT_NOT_ALLOWED: &'static str = "defaults for type parameters are only allowed in \ @@ -297,7 +299,13 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { }) } GenericParamKind::Const { default, .. } => { - if !matches!(allow_defaults, Defaults::Allowed) && default.is_some() { + let is_host_param = tcx.has_attr(param.def_id, sym::rustc_host); + + if !matches!(allow_defaults, Defaults::Allowed) + && default.is_some() + // `rustc_host` effect params are allowed to have defaults. + && !is_host_param + { tcx.sess.span_err( param.span, "defaults for const parameters are only allowed in \ @@ -305,8 +313,18 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { ); } + let index = next_index(); + + if is_host_param { + if let Some(idx) = host_effect_index { + bug!("parent also has host effect param? index: {idx}, def: {def_id:?}"); + } + + host_effect_index = Some(index as usize); + } + Some(ty::GenericParamDef { - index: next_index(), + index, name: param.name.ident().name, def_id: param.def_id.to_def_id(), pure_wrt_drop: param.pure_wrt_drop, @@ -339,17 +357,14 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { } // provide junk type parameter defs for const blocks. - if let Node::AnonConst(_) = node { - let parent_node = tcx.hir().get_parent(hir_id); - if let Node::Expr(&Expr { kind: ExprKind::ConstBlock(_), .. }) = parent_node { - params.push(ty::GenericParamDef { - index: next_index(), - name: Symbol::intern(""), - def_id: def_id.to_def_id(), - pure_wrt_drop: false, - kind: ty::GenericParamDefKind::Type { has_default: false, synthetic: false }, - }); - } + if let Node::ConstBlock(_) = node { + params.push(ty::GenericParamDef { + index: next_index(), + name: Symbol::intern(""), + def_id: def_id.to_def_id(), + pure_wrt_drop: false, + kind: ty::GenericParamDefKind::Type { has_default: false, synthetic: false }, + }); } let param_def_id_to_index = params.iter().map(|param| (param.def_id, param.index)).collect(); @@ -361,6 +376,7 @@ pub(super) fn generics_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Generics { param_def_id_to_index, has_self: has_self || parent_has_self, has_late_bound_regions: has_late_bound_regions(tcx, node), + host_effect_index, } } diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs index 948b903e509ad..4b7743fae53b9 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -1,16 +1,16 @@ use super::ItemCtxt; -use crate::astconv::{AstConv, OnlySelfBounds}; +use crate::astconv::{AstConv, PredicateFilter}; use rustc_hir as hir; use rustc_infer::traits::util; -use rustc_middle::ty::subst::InternalSubsts; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::GenericArgs; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeFolder}; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::Span; /// For associated types we include both bounds written on the type /// (`type X: Trait`) and predicates from the trait: `where Self::X: Trait`. /// -/// Note that this filtering is done with the items identity substs to +/// Note that this filtering is done with the items identity args to /// simplify checking that these bounds are met in impls. This means that /// a bound such as `for<'b> >::U: Clone` can't be used, as in /// `hr-associated-type-bound-1.rs`. @@ -19,32 +19,34 @@ fn associated_type_bounds<'tcx>( assoc_item_def_id: LocalDefId, ast_bounds: &'tcx [hir::GenericBound<'tcx>], span: Span, -) -> &'tcx [(ty::Predicate<'tcx>, Span)] { - let item_ty = tcx.mk_projection( +) -> &'tcx [(ty::Clause<'tcx>, Span)] { + let item_ty = Ty::new_projection( + tcx, assoc_item_def_id.to_def_id(), - InternalSubsts::identity_for_item(tcx, assoc_item_def_id), + GenericArgs::identity_for_item(tcx, assoc_item_def_id), ); let icx = ItemCtxt::new(tcx, assoc_item_def_id); - let mut bounds = icx.astconv().compute_bounds(item_ty, ast_bounds, OnlySelfBounds(false)); + let mut bounds = icx.astconv().compute_bounds(item_ty, ast_bounds, PredicateFilter::All); // Associated types are implicitly sized unless a `?Sized` bound is found icx.astconv().add_implicitly_sized(&mut bounds, item_ty, ast_bounds, None, span); let trait_def_id = tcx.local_parent(assoc_item_def_id); let trait_predicates = tcx.trait_explicit_predicates_and_bounds(trait_def_id); - let bounds_from_parent = trait_predicates.predicates.iter().copied().filter(|(pred, _)| { - match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(tr)) => tr.self_ty() == item_ty, - ty::PredicateKind::Clause(ty::Clause::Projection(proj)) => { - proj.projection_ty.self_ty() == item_ty - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(outlives)) => outlives.0 == item_ty, + let bounds_from_parent = trait_predicates + .predicates + .iter() + .copied() + .filter(|(pred, _)| match pred.kind().skip_binder() { + ty::ClauseKind::Trait(tr) => tr.self_ty() == item_ty, + ty::ClauseKind::Projection(proj) => proj.projection_ty.self_ty() == item_ty, + ty::ClauseKind::TypeOutlives(outlives) => outlives.0 == item_ty, _ => false, - } - }); + }) + .map(|(clause, span)| (clause, span)); - let all_bounds = tcx.arena.alloc_from_iter(bounds.predicates().chain(bounds_from_parent)); + let all_bounds = tcx.arena.alloc_from_iter(bounds.clauses().chain(bounds_from_parent)); debug!( "associated_type_bounds({}) = {:?}", tcx.def_path_str(assoc_item_def_id.to_def_id()), @@ -64,35 +66,36 @@ fn opaque_type_bounds<'tcx>( ast_bounds: &'tcx [hir::GenericBound<'tcx>], item_ty: Ty<'tcx>, span: Span, -) -> &'tcx [(ty::Predicate<'tcx>, Span)] { +) -> &'tcx [(ty::Clause<'tcx>, Span)] { ty::print::with_no_queries!({ let icx = ItemCtxt::new(tcx, opaque_def_id); - let mut bounds = icx.astconv().compute_bounds(item_ty, ast_bounds, OnlySelfBounds(false)); + let mut bounds = icx.astconv().compute_bounds(item_ty, ast_bounds, PredicateFilter::All); // Opaque types are implicitly sized unless a `?Sized` bound is found icx.astconv().add_implicitly_sized(&mut bounds, item_ty, ast_bounds, None, span); debug!(?bounds); - tcx.arena.alloc_from_iter(bounds.predicates()) + tcx.arena.alloc_from_iter(bounds.clauses()) }) } pub(super) fn explicit_item_bounds( tcx: TyCtxt<'_>, def_id: LocalDefId, -) -> ty::EarlyBinder<&'_ [(ty::Predicate<'_>, Span)]> { +) -> ty::EarlyBinder<&'_ [(ty::Clause<'_>, Span)]> { match tcx.opt_rpitit_info(def_id.to_def_id()) { // RPITIT's bounds are the same as opaque type bounds, but with // a projection self type. Some(ty::ImplTraitInTraitData::Trait { opaque_def_id, .. }) => { let item = tcx.hir().get_by_def_id(opaque_def_id.expect_local()).expect_item(); let opaque_ty = item.expect_opaque_ty(); - return ty::EarlyBinder(opaque_type_bounds( + return ty::EarlyBinder::bind(opaque_type_bounds( tcx, opaque_def_id.expect_local(), opaque_ty.bounds, - tcx.mk_projection( + Ty::new_projection( + tcx, def_id.to_def_id(), - ty::InternalSubsts::identity_for_item(tcx, def_id), + ty::GenericArgs::identity_for_item(tcx, def_id), ), item.span, )); @@ -110,31 +113,69 @@ pub(super) fn explicit_item_bounds( .. }) => associated_type_bounds(tcx, def_id, bounds, *span), hir::Node::Item(hir::Item { - kind: hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds, in_trait, .. }), + kind: hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds, in_trait: false, .. }), span, .. }) => { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - let item_ty = if *in_trait && !tcx.lower_impl_trait_in_trait_to_assoc_ty() { - tcx.mk_projection(def_id.to_def_id(), substs) - } else { - tcx.mk_opaque(def_id.to_def_id(), substs) - }; + let args = GenericArgs::identity_for_item(tcx, def_id); + let item_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args); opaque_type_bounds(tcx, def_id, bounds, item_ty, *span) } + // Since RPITITs are astconv'd as projections in `ast_ty_to_ty`, when we're asking + // for the item bounds of the *opaques* in a trait's default method signature, we + // need to map these projections back to opaques. + hir::Node::Item(hir::Item { + kind: hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds, in_trait: true, origin, .. }), + span, + .. + }) => { + let (hir::OpaqueTyOrigin::FnReturn(fn_def_id) + | hir::OpaqueTyOrigin::AsyncFn(fn_def_id)) = *origin + else { + bug!() + }; + let args = GenericArgs::identity_for_item(tcx, def_id); + let item_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args); + tcx.arena.alloc_slice( + &opaque_type_bounds(tcx, def_id, bounds, item_ty, *span) + .to_vec() + .fold_with(&mut AssocTyToOpaque { tcx, fn_def_id: fn_def_id.to_def_id() }), + ) + } + hir::Node::Item(hir::Item { kind: hir::ItemKind::TyAlias(..), .. }) => &[], _ => bug!("item_bounds called on {:?}", def_id), }; - ty::EarlyBinder(bounds) + ty::EarlyBinder::bind(bounds) } pub(super) fn item_bounds( tcx: TyCtxt<'_>, def_id: DefId, -) -> ty::EarlyBinder<&'_ ty::List>> { +) -> ty::EarlyBinder<&'_ ty::List>> { tcx.explicit_item_bounds(def_id).map_bound(|bounds| { - tcx.mk_predicates_from_iter(util::elaborate( - tcx, - bounds.iter().map(|&(bound, _span)| bound), - )) + tcx.mk_clauses_from_iter(util::elaborate(tcx, bounds.iter().map(|&(bound, _span)| bound))) }) } + +struct AssocTyToOpaque<'tcx> { + tcx: TyCtxt<'tcx>, + fn_def_id: DefId, +} + +impl<'tcx> TypeFolder> for AssocTyToOpaque<'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { + if let ty::Alias(ty::Projection, projection_ty) = ty.kind() + && let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, .. }) + = self.tcx.opt_rpitit_info(projection_ty.def_id) + && fn_def_id == self.fn_def_id + { + self.tcx.type_of(projection_ty.def_id).instantiate(self.tcx, projection_ty.args) + } else { + ty + } + } +} diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index e5b5dae551e87..495e663666cb9 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -1,4 +1,4 @@ -use crate::astconv::{AstConv, OnlySelfBounds}; +use crate::astconv::{AstConv, OnlySelfBounds, PredicateFilter}; use crate::bounds::Bounds; use crate::collect::ItemCtxt; use crate::constrained_generic_params as cgp; @@ -8,10 +8,9 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{self, Visitor}; -use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_middle::ty::{GenericPredicates, ToPredicate}; -use rustc_span::symbol::{sym, Ident}; +use rustc_middle::ty::{GenericPredicates, ImplTraitInTraitData, ToPredicate}; +use rustc_span::symbol::Ident; use rustc_span::{Span, DUMMY_SP}; /// Returns a list of all type predicates (explicit and implicit) for the definition with @@ -38,17 +37,10 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic // from the trait itself that *shouldn't* be shown as the source of // an obligation and instead be skipped. Otherwise we'd use // `tcx.def_span(def_id);` - - let constness = if tcx.has_attr(def_id, sym::const_trait) { - ty::BoundConstness::ConstIfConst - } else { - ty::BoundConstness::NotConst - }; - let span = rustc_span::DUMMY_SP; result.predicates = tcx.arena.alloc_from_iter(result.predicates.iter().copied().chain(std::iter::once(( - ty::TraitRef::identity(tcx, def_id).with_constness(constness).to_predicate(tcx), + ty::TraitRef::identity(tcx, def_id).to_predicate(tcx), span, )))); } @@ -62,6 +54,59 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::GenericPredicates<'_> { use rustc_hir::*; + match tcx.opt_rpitit_info(def_id.to_def_id()) { + Some(ImplTraitInTraitData::Trait { fn_def_id, .. }) => { + let mut predicates = Vec::new(); + + // RPITITs should inherit the predicates of their parent. This is + // both to ensure that the RPITITs are only instantiated when the + // parent predicates would hold, and also so that the param-env + // inherits these predicates as assumptions. + let identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + predicates + .extend(tcx.explicit_predicates_of(fn_def_id).instantiate_own(tcx, identity_args)); + + // We also install bidirectional outlives predicates for the RPITIT + // to keep the duplicates lifetimes from opaque lowering in sync. + // We only need to compute bidirectional outlives for the duplicated + // opaque lifetimes, which explains the slicing below. + compute_bidirectional_outlives_predicates( + tcx, + &tcx.generics_of(def_id.to_def_id()).params + [tcx.generics_of(fn_def_id).params.len()..], + &mut predicates, + ); + + return ty::GenericPredicates { + parent: Some(tcx.parent(def_id.to_def_id())), + predicates: tcx.arena.alloc_from_iter(predicates), + }; + } + + Some(ImplTraitInTraitData::Impl { fn_def_id }) => { + let assoc_item = tcx.associated_item(def_id); + let trait_assoc_predicates = + tcx.explicit_predicates_of(assoc_item.trait_item_def_id.unwrap()); + + let impl_assoc_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); + let impl_def_id = tcx.parent(fn_def_id); + let impl_trait_ref_args = + tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity().args; + + let impl_assoc_args = + impl_assoc_identity_args.rebase_onto(tcx, impl_def_id, impl_trait_ref_args); + + let impl_predicates = trait_assoc_predicates.instantiate_own(tcx, impl_assoc_args); + + return ty::GenericPredicates { + parent: Some(impl_def_id), + predicates: tcx.arena.alloc_from_iter(impl_predicates), + }; + } + + None => {} + } + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); let node = tcx.hir().get(hir_id); @@ -75,7 +120,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen // We use an `IndexSet` to preserve order of insertion. // Preserving the order of insertion is important here so as not to break UI tests. - let mut predicates: FxIndexSet<(ty::Predicate<'_>, Span)> = FxIndexSet::default(); + let mut predicates: FxIndexSet<(ty::Clause<'_>, Span)> = FxIndexSet::default(); let ast_generics = match node { Node::TraitItem(item) => item.generics, @@ -85,13 +130,15 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen Node::Item(item) => match item.kind { ItemKind::Impl(impl_) => { if impl_.defaultness.is_default() { - is_default_impl_trait = - tcx.impl_trait_ref(def_id).map(|t| ty::Binder::dummy(t.subst_identity())); + is_default_impl_trait = tcx + .impl_trait_ref(def_id) + .map(|t| ty::Binder::dummy(t.instantiate_identity())); } impl_.generics } ItemKind::Fn(.., generics, _) | ItemKind::TyAlias(_, generics) + | ItemKind::Const(_, generics, _) | ItemKind::Enum(_, generics) | ItemKind::Struct(_, generics) | ItemKind::Union(_, generics) => generics, @@ -125,8 +172,8 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen if let Some(self_bounds) = is_trait { predicates.extend( icx.astconv() - .compute_bounds(tcx.types.self_param, self_bounds, OnlySelfBounds(false)) - .predicates(), + .compute_bounds(tcx.types.self_param, self_bounds, PredicateFilter::All) + .clauses(), ); } @@ -139,7 +186,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen // (see below). Recall that a default impl is not itself an impl, but rather a // set of defaults that can be incorporated into another impl. if let Some(trait_ref) = is_default_impl_trait { - predicates.insert((trait_ref.without_const().to_predicate(tcx), tcx.def_span(def_id))); + predicates.insert((trait_ref.to_predicate(tcx), tcx.def_span(def_id))); } // Collect the region predicates that were declared inline as @@ -175,22 +222,24 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen param.span, ); trace!(?bounds); - predicates.extend(bounds.predicates()); + predicates.extend(bounds.clauses()); trace!(?predicates); } GenericParamKind::Const { .. } => { let name = param.name.ident().name; let param_const = ty::ParamConst::new(index, name); - let ct_ty = tcx.type_of(param.def_id.to_def_id()).subst_identity(); + let ct_ty = tcx + .type_of(param.def_id.to_def_id()) + .no_bound_vars() + .expect("const parameters cannot be generic"); - let ct = tcx.mk_const(param_const, ct_ty); + let ct = ty::Const::new_param(tcx, param_const, ct_ty); - let predicate = ty::Binder::dummy(ty::PredicateKind::Clause( - ty::Clause::ConstArgHasType(ct, ct_ty), - )) - .to_predicate(tcx); - predicates.insert((predicate, param.span)); + predicates.insert(( + ty::ClauseKind::ConstArgHasType(ct, ct_ty).to_predicate(tcx), + param.span, + )); index += 1; } @@ -219,7 +268,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen } else { let span = bound_pred.bounded_ty.span; let predicate = ty::Binder::bind_with_vars( - ty::PredicateKind::WellFormed(ty.into()), + ty::ClauseKind::WellFormed(ty.into()), bound_vars, ); predicates.insert((predicate.to_predicate(tcx), span)); @@ -234,7 +283,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen bound_vars, OnlySelfBounds(false), ); - predicates.extend(bounds.predicates()); + predicates.extend(bounds.clauses()); } hir::WherePredicate::RegionPredicate(region_pred) => { @@ -246,11 +295,8 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen } _ => bug!(), }; - let pred = ty::Binder::dummy(ty::PredicateKind::Clause( - ty::Clause::RegionOutlives(ty::OutlivesPredicate(r1, r2)), - )) - .to_predicate(icx.tcx); - + let pred = ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(r1, r2)) + .to_predicate(icx.tcx); (pred, span) })) } @@ -273,8 +319,8 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen // in trait checking. See `setup_constraining_predicates` // for details. if let Node::Item(&Item { kind: ItemKind::Impl { .. }, .. }) = node { - let self_ty = tcx.type_of(def_id).subst_identity(); - let trait_ref = tcx.impl_trait_ref(def_id).map(ty::EarlyBinder::subst_identity); + let self_ty = tcx.type_of(def_id).instantiate_identity(); + let trait_ref = tcx.impl_trait_ref(def_id).map(ty::EarlyBinder::instantiate_identity); cgp::setup_constraining_predicates( tcx, &mut predicates, @@ -293,55 +339,53 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen bug!("unexpected {opaque_ty_node:?}") }; debug!(?lifetimes); - for (arg, duplicate) in std::iter::zip(lifetimes, ast_generics.params) { - let hir::GenericArg::Lifetime(arg) = arg else { bug!() }; - let orig_region = icx.astconv().ast_region_to_region(&arg, None); - if !matches!(orig_region.kind(), ty::ReEarlyBound(..)) { - // Only early-bound regions can point to the original generic parameter. - continue; - } - let hir::GenericParamKind::Lifetime { .. } = duplicate.kind else { continue }; - let dup_def = duplicate.def_id.to_def_id(); + compute_bidirectional_outlives_predicates(tcx, &generics.params, &mut predicates); + debug!(?predicates); + } - let Some(dup_index) = generics.param_def_id_to_index(tcx, dup_def) else { bug!() }; + ty::GenericPredicates { + parent: generics.parent, + predicates: tcx.arena.alloc_from_iter(predicates), + } +} - let dup_region = tcx.mk_re_early_bound(ty::EarlyBoundRegion { - def_id: dup_def, - index: dup_index, - name: duplicate.name.ident().name, - }); +/// Opaques have duplicated lifetimes and we need to compute bidirectional outlives predicates to +/// enforce that these lifetimes stay in sync. +fn compute_bidirectional_outlives_predicates<'tcx>( + tcx: TyCtxt<'tcx>, + opaque_own_params: &[ty::GenericParamDef], + predicates: &mut Vec<(ty::Clause<'tcx>, Span)>, +) { + for param in opaque_own_params { + let orig_lifetime = tcx.map_rpit_lifetime_to_fn_lifetime(param.def_id.expect_local()); + if let ty::ReEarlyBound(..) = *orig_lifetime { + let dup_lifetime = ty::Region::new_early_bound( + tcx, + ty::EarlyBoundRegion { def_id: param.def_id, index: param.index, name: param.name }, + ); + let span = tcx.def_span(param.def_id); predicates.push(( - ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::RegionOutlives( - ty::OutlivesPredicate(orig_region, dup_region), - ))) - .to_predicate(icx.tcx), - duplicate.span, + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(orig_lifetime, dup_lifetime)) + .to_predicate(tcx), + span, )); predicates.push(( - ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::RegionOutlives( - ty::OutlivesPredicate(dup_region, orig_region), - ))) - .to_predicate(icx.tcx), - duplicate.span, + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(dup_lifetime, orig_lifetime)) + .to_predicate(tcx), + span, )); } - debug!(?predicates); - } - - ty::GenericPredicates { - parent: generics.parent, - predicates: tcx.arena.alloc_from_iter(predicates), } } fn const_evaluatable_predicates_of( tcx: TyCtxt<'_>, def_id: LocalDefId, -) -> FxIndexSet<(ty::Predicate<'_>, Span)> { +) -> FxIndexSet<(ty::Clause<'_>, Span)> { struct ConstCollector<'tcx> { tcx: TyCtxt<'tcx>, - preds: FxIndexSet<(ty::Predicate<'tcx>, Span)>, + preds: FxIndexSet<(ty::Clause<'tcx>, Span)>, } impl<'tcx> intravisit::Visitor<'tcx> for ConstCollector<'tcx> { @@ -349,11 +393,8 @@ fn const_evaluatable_predicates_of( let ct = ty::Const::from_anon_const(self.tcx, c.def_id); if let ty::ConstKind::Unevaluated(_) = ct.kind() { let span = self.tcx.def_span(c.def_id); - self.preds.insert(( - ty::Binder::dummy(ty::PredicateKind::ConstEvaluatable(ct)) - .to_predicate(self.tcx), - span, - )); + self.preds + .insert((ty::ClauseKind::ConstEvaluatable(ct).to_predicate(self.tcx), span)); } } @@ -413,20 +454,20 @@ pub(super) fn explicit_predicates_of<'tcx>( // Remove bounds on associated types from the predicates, they will be // returned by `explicit_item_bounds`. let predicates_and_bounds = tcx.trait_explicit_predicates_and_bounds(def_id); - let trait_identity_substs = InternalSubsts::identity_for_item(tcx, def_id); + let trait_identity_args = ty::GenericArgs::identity_for_item(tcx, def_id); let is_assoc_item_ty = |ty: Ty<'tcx>| { // For a predicate from a where clause to become a bound on an // associated type: - // * It must use the identity substs of the item. + // * It must use the identity args of the item. // * We're in the scope of the trait, so we can't name any // parameters of the GAT. That means that all we need to - // check are that the substs of the projection are the - // identity substs of the trait. + // check are that the args of the projection are the + // identity args of the trait. // * It must be an associated type for this trait (*not* a // supertrait). if let ty::Alias(ty::Projection, projection) = ty.kind() { - projection.substs == trait_identity_substs + projection.args == trait_identity_args // FIXME(return_type_notation): This check should be more robust && !tcx.is_impl_trait_in_trait(projection.def_id) && tcx.associated_item(projection.def_id).container_id(tcx) @@ -441,13 +482,9 @@ pub(super) fn explicit_predicates_of<'tcx>( .iter() .copied() .filter(|(pred, _)| match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(tr)) => !is_assoc_item_ty(tr.self_ty()), - ty::PredicateKind::Clause(ty::Clause::Projection(proj)) => { - !is_assoc_item_ty(proj.projection_ty.self_ty()) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(outlives)) => { - !is_assoc_item_ty(outlives.0) - } + ty::ClauseKind::Trait(tr) => !is_assoc_item_ty(tr.self_ty()), + ty::ClauseKind::Projection(proj) => !is_assoc_item_ty(proj.projection_ty.self_ty()), + ty::ClauseKind::TypeOutlives(outlives) => !is_assoc_item_ty(outlives.0), _ => true, }) .collect(); @@ -460,7 +497,7 @@ pub(super) fn explicit_predicates_of<'tcx>( } } } else { - if matches!(def_kind, DefKind::AnonConst) && tcx.lazy_normalization() { + if matches!(def_kind, DefKind::AnonConst) && tcx.features().generic_const_exprs { let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); let parent_def_id = tcx.hir().get_parent_item(hir_id); @@ -488,9 +525,7 @@ pub(super) fn explicit_predicates_of<'tcx>( .predicates .into_iter() .filter(|(pred, _)| { - if let ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, _)) = - pred.kind().skip_binder() - { + if let ty::ClauseKind::ConstArgHasType(ct, _) = pred.kind().skip_binder() { match ct.kind() { ty::ConstKind::Param(param_const) => { let defaulted_param_idx = tcx @@ -544,19 +579,6 @@ pub(super) fn explicit_predicates_of<'tcx>( } } -#[derive(Copy, Clone, Debug)] -pub enum PredicateFilter { - /// All predicates may be implied by the trait - All, - - /// Only traits that reference `Self: ..` are implied by the trait - SelfOnly, - - /// Only traits that reference `Self: ..` and define an associated type - /// with the given ident are implied by the trait - SelfThatDefines(Ident), -} - /// Ensures that the super-predicates of the trait with a `DefId` /// of `trait_def_id` are converted and stored. This also ensures that /// the transitive super-predicates are converted. @@ -578,11 +600,15 @@ pub(super) fn implied_predicates_of( tcx: TyCtxt<'_>, trait_def_id: LocalDefId, ) -> ty::GenericPredicates<'_> { - if tcx.is_trait_alias(trait_def_id.to_def_id()) { - implied_predicates_with_filter(tcx, trait_def_id.to_def_id(), PredicateFilter::All) - } else { - tcx.super_predicates_of(trait_def_id) - } + implied_predicates_with_filter( + tcx, + trait_def_id.to_def_id(), + if tcx.is_trait_alias(trait_def_id.to_def_id()) { + PredicateFilter::All + } else { + PredicateFilter::SelfAndAssociatedTypeBounds + }, + ) } /// Ensures that the super-predicates of the trait with a `DefId` @@ -615,48 +641,18 @@ pub(super) fn implied_predicates_with_filter( let icx = ItemCtxt::new(tcx, trait_def_id); let self_param_ty = tcx.types.self_param; - let (superbounds, where_bounds_that_match) = match filter { - PredicateFilter::All => ( - // Convert the bounds that follow the colon (or equal in trait aliases) - icx.astconv().compute_bounds(self_param_ty, bounds, OnlySelfBounds(false)), - // Also include all where clause bounds - icx.type_parameter_bounds_in_generics( - generics, - item.owner_id.def_id, - self_param_ty, - OnlySelfBounds(false), - None, - ), - ), - PredicateFilter::SelfOnly => ( - // Convert the bounds that follow the colon (or equal in trait aliases) - icx.astconv().compute_bounds(self_param_ty, bounds, OnlySelfBounds(true)), - // Include where clause bounds for `Self` - icx.type_parameter_bounds_in_generics( - generics, - item.owner_id.def_id, - self_param_ty, - OnlySelfBounds(true), - None, - ), - ), - PredicateFilter::SelfThatDefines(assoc_name) => ( - // Convert the bounds that follow the colon (or equal) that reference the associated name - icx.astconv().compute_bounds_that_match_assoc_item(self_param_ty, bounds, assoc_name), - // Include where clause bounds for `Self` that reference the associated name - icx.type_parameter_bounds_in_generics( - generics, - item.owner_id.def_id, - self_param_ty, - OnlySelfBounds(true), - Some(assoc_name), - ), - ), - }; + let superbounds = icx.astconv().compute_bounds(self_param_ty, bounds, filter); + + let where_bounds_that_match = icx.type_parameter_bounds_in_generics( + generics, + item.owner_id.def_id, + self_param_ty, + filter, + ); // Combine the two lists to form the complete set of superbounds: let implied_bounds = - &*tcx.arena.alloc_from_iter(superbounds.predicates().chain(where_bounds_that_match)); + &*tcx.arena.alloc_from_iter(superbounds.clauses().chain(where_bounds_that_match)); debug!(?implied_bounds); // Now require that immediate supertraits are converted, which will, in @@ -665,7 +661,7 @@ pub(super) fn implied_predicates_with_filter( if matches!(filter, PredicateFilter::SelfOnly) { for &(pred, span) in implied_bounds { debug!("superbound: {:?}", pred); - if let ty::PredicateKind::Clause(ty::Clause::Trait(bound)) = pred.kind().skip_binder() + if let ty::ClauseKind::Trait(bound) = pred.kind().skip_binder() && bound.polarity == ty::ImplPolarity::Positive { tcx.at(span).super_predicates_of(bound.def_id()); @@ -684,6 +680,7 @@ pub(super) fn type_param_predicates( (item_def_id, def_id, assoc_name): (LocalDefId, LocalDefId, Ident), ) -> ty::GenericPredicates<'_> { use rustc_hir::*; + use rustc_middle::ty::Ty; // In the AST, bounds can derive from two places. Either // written inline like `` or in a where-clause like @@ -693,7 +690,7 @@ pub(super) fn type_param_predicates( let param_owner = tcx.hir().ty_param_owner(def_id); let generics = tcx.generics_of(param_owner); let index = generics.param_def_id_to_index[&def_id.to_def_id()]; - let ty = tcx.mk_ty_param(index, tcx.hir().ty_param_name(def_id)); + let ty = Ty::new_param(tcx, index, tcx.hir().ty_param_name(def_id)); // Don't look for bounds where the type parameter isn't in scope. let parent = if item_def_id == param_owner { @@ -721,7 +718,8 @@ pub(super) fn type_param_predicates( ItemKind::Fn(.., generics, _) | ItemKind::Impl(&hir::Impl { generics, .. }) | ItemKind::TyAlias(_, generics) - | ItemKind::OpaqueTy(OpaqueTy { + | ItemKind::Const(_, generics, _) + | ItemKind::OpaqueTy(&OpaqueTy { generics, origin: hir::OpaqueTyOrigin::TyAlias { .. }, .. @@ -734,8 +732,7 @@ pub(super) fn type_param_predicates( if param_id == item_hir_id { let identity_trait_ref = ty::TraitRef::identity(tcx, item_def_id.to_def_id()); - extend = - Some((identity_trait_ref.without_const().to_predicate(tcx), item.span)); + extend = Some((identity_trait_ref.to_predicate(tcx), item.span)); } generics } @@ -757,12 +754,11 @@ pub(super) fn type_param_predicates( ast_generics, def_id, ty, - OnlySelfBounds(true), - Some(assoc_name), + PredicateFilter::SelfThatDefines(assoc_name), ) .into_iter() .filter(|(predicate, _)| match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => data.self_ty().is_param(index), + ty::ClauseKind::Trait(data) => data.self_ty().is_param(index), _ => false, }), ); @@ -782,9 +778,8 @@ impl<'tcx> ItemCtxt<'tcx> { ast_generics: &'tcx hir::Generics<'tcx>, param_def_id: LocalDefId, ty: Ty<'tcx>, - only_self_bounds: OnlySelfBounds, - assoc_name: Option, - ) -> Vec<(ty::Predicate<'tcx>, Span)> { + filter: PredicateFilter, + ) -> Vec<(ty::Clause<'tcx>, Span)> { let mut bounds = Bounds::default(); for predicate in ast_generics.predicates { @@ -792,9 +787,23 @@ impl<'tcx> ItemCtxt<'tcx> { continue; }; + let (only_self_bounds, assoc_name) = match filter { + PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => { + (OnlySelfBounds(false), None) + } + PredicateFilter::SelfOnly => (OnlySelfBounds(true), None), + PredicateFilter::SelfThatDefines(assoc_name) => { + (OnlySelfBounds(true), Some(assoc_name)) + } + }; + + // Subtle: If we're collecting `SelfAndAssociatedTypeBounds`, then we + // want to only consider predicates with `Self: ...`, but we don't want + // `OnlySelfBounds(true)` since we want to collect the nested associated + // type bound as well. let bound_ty = if predicate.is_param_bound(param_def_id.to_def_id()) { ty - } else if !only_self_bounds.0 { + } else if matches!(filter, PredicateFilter::All) { self.to_ty(predicate.bounded_ty) } else { continue; @@ -813,7 +822,7 @@ impl<'tcx> ItemCtxt<'tcx> { ); } - bounds.predicates().collect() + bounds.clauses().collect() } #[instrument(level = "trace", skip(self))] diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs index 794812a5ce7d9..6dd0c840de632 100644 --- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs +++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs @@ -22,7 +22,7 @@ use rustc_middle::ty::{self, TyCtxt, TypeSuperVisitable, TypeVisitor}; use rustc_session::lint; use rustc_span::def_id::DefId; use rustc_span::symbol::{sym, Ident}; -use rustc_span::Span; +use rustc_span::{Span, DUMMY_SP}; use std::fmt; use crate::errors; @@ -137,12 +137,6 @@ enum Scope<'a> { s: ScopeRef<'a>, }, - /// A scope which either determines unspecified lifetimes or errors - /// on them (e.g., due to ambiguity). - Elision { - s: ScopeRef<'a>, - }, - /// Use a specific lifetime (if `Some`) or leave it unset (to be /// inferred in a function body or potentially error outside one), /// for the default choice of lifetime in a trait object type. @@ -211,7 +205,6 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> { Scope::Body { id, s: _ } => { f.debug_struct("Body").field("id", id).field("s", &"..").finish() } - Scope::Elision { s: _ } => f.debug_struct("Elision").field("s", &"..").finish(), Scope::ObjectLifetimeDefault { lifetime, s: _ } => f .debug_struct("ObjectLifetimeDefault") .field("lifetime", lifetime) @@ -325,9 +318,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { break (vec![], BinderScopeType::Normal); } - Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } - | Scope::AnonConstBoundary { s } => { + Scope::ObjectLifetimeDefault { s, .. } | Scope::AnonConstBoundary { s } => { scope = s; } @@ -338,7 +329,17 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { Scope::TraitRefBoundary { .. } => { // We should only see super trait lifetimes if there is a `Binder` above - assert!(supertrait_bound_vars.is_empty()); + // though this may happen when we call `poly_trait_ref_binder_info` with + // an (erroneous, #113423) associated return type bound in an impl header. + if !supertrait_bound_vars.is_empty() { + self.tcx.sess.delay_span_bug( + DUMMY_SP, + format!( + "found supertrait lifetimes without a binder to append \ + them to: {supertrait_bound_vars:?}" + ), + ); + } break (vec![], BinderScopeType::Normal); } @@ -516,16 +517,11 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { | hir::ItemKind::Macro(..) | hir::ItemKind::Mod(..) | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::Static(..) | hir::ItemKind::GlobalAsm(..) => { // These sorts of items have no lifetime parameters at all. intravisit::walk_item(self, item); } - hir::ItemKind::Static(..) | hir::ItemKind::Const(..) => { - // No lifetime parameters, but implied 'static. - self.with(Scope::Elision { s: self.scope }, |this| { - intravisit::walk_item(this, item) - }); - } hir::ItemKind::OpaqueTy(hir::OpaqueTy { origin: hir::OpaqueTyOrigin::TyAlias { .. }, .. @@ -556,7 +552,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { }); } } - hir::ItemKind::OpaqueTy(hir::OpaqueTy { + hir::ItemKind::OpaqueTy(&hir::OpaqueTy { origin: hir::OpaqueTyOrigin::FnReturn(parent) | hir::OpaqueTyOrigin::AsyncFn(parent), generics, .. @@ -586,6 +582,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { }) } hir::ItemKind::TyAlias(_, generics) + | hir::ItemKind::Const(_, generics, _) | hir::ItemKind::Enum(_, generics) | hir::ItemKind::Struct(_, generics) | hir::ItemKind::Union(_, generics) @@ -593,21 +590,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { | hir::ItemKind::TraitAlias(generics, ..) | hir::ItemKind::Impl(&hir::Impl { generics, .. }) => { // These kinds of items have only early-bound lifetime parameters. - let bound_vars = generics.params.iter().map(ResolvedArg::early).collect(); - self.record_late_bound_vars(item.hir_id(), vec![]); - let scope = Scope::Binder { - hir_id: item.hir_id(), - bound_vars, - scope_type: BinderScopeType::Normal, - s: self.scope, - where_bound_origin: None, - }; - self.with(scope, |this| { - let scope = Scope::TraitRefBoundary { s: this.scope }; - this.with(scope, |this| { - intravisit::walk_item(this, item); - }); - }); + self.visit_early(item.hir_id(), generics, |this| intravisit::walk_item(this, item)); } } } @@ -717,12 +700,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { // Elided lifetimes are not allowed in non-return // position impl Trait let scope = Scope::TraitRefBoundary { s: self.scope }; - self.with(scope, |this| { - let scope = Scope::Elision { s: this.scope }; - this.with(scope, |this| { - intravisit::walk_item(this, opaque_ty); - }) - }); + self.with(scope, |this| intravisit::walk_item(this, opaque_ty)); return; } @@ -739,9 +717,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { // `fn foo<'a>() -> MyAnonTy<'a> { ... }` // ^ ^this gets resolved in the current scope for lifetime in lifetimes { - let hir::GenericArg::Lifetime(lifetime) = lifetime else { - continue - }; + let hir::GenericArg::Lifetime(lifetime) = lifetime else { continue }; self.visit_lifetime(lifetime); // Check for predicates like `impl for<'a> Trait>` @@ -749,12 +725,8 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { // well-supported at the moment, so this doesn't work. // In the future, this should be fixed and this error should be removed. let def = self.map.defs.get(&lifetime.hir_id).cloned(); - let Some(ResolvedArg::LateBound(_, _, def_id)) = def else { - continue - }; - let Some(def_id) = def_id.as_local() else { - continue - }; + let Some(ResolvedArg::LateBound(_, _, def_id)) = def else { continue }; + let Some(def_id) = def_id.as_local() else { continue }; let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); // Ensure that the parent of the def is an item, not HRTB let parent_id = self.tcx.hir().parent_id(hir_id); @@ -791,39 +763,24 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { use self::hir::TraitItemKind::*; match trait_item.kind { Fn(_, _) => { - self.visit_early_late(trait_item.hir_id(), &trait_item.generics, |this| { + self.visit_early_late(trait_item.hir_id(), trait_item.generics, |this| { intravisit::walk_trait_item(this, trait_item) }); } Type(bounds, ty) => { - let generics = &trait_item.generics; - let bound_vars = generics.params.iter().map(ResolvedArg::early).collect(); - self.record_late_bound_vars(trait_item.hir_id(), vec![]); - let scope = Scope::Binder { - hir_id: trait_item.hir_id(), - bound_vars, - s: self.scope, - scope_type: BinderScopeType::Normal, - where_bound_origin: None, - }; - self.with(scope, |this| { - let scope = Scope::TraitRefBoundary { s: this.scope }; - this.with(scope, |this| { - this.visit_generics(generics); - for bound in bounds { - this.visit_param_bound(bound); - } - if let Some(ty) = ty { - this.visit_ty(ty); - } - }) - }); - } - Const(_, _) => { - // Only methods and types support generics. - assert!(trait_item.generics.params.is_empty()); - intravisit::walk_trait_item(self, trait_item); + self.visit_early(trait_item.hir_id(), trait_item.generics, |this| { + this.visit_generics(&trait_item.generics); + for bound in bounds { + this.visit_param_bound(bound); + } + if let Some(ty) = ty { + this.visit_ty(ty); + } + }) } + Const(_, _) => self.visit_early(trait_item.hir_id(), trait_item.generics, |this| { + intravisit::walk_trait_item(this, trait_item) + }), } } @@ -831,34 +788,16 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> { fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { use self::hir::ImplItemKind::*; match impl_item.kind { - Fn(..) => self.visit_early_late(impl_item.hir_id(), &impl_item.generics, |this| { + Fn(..) => self.visit_early_late(impl_item.hir_id(), impl_item.generics, |this| { + intravisit::walk_impl_item(this, impl_item) + }), + Type(ty) => self.visit_early(impl_item.hir_id(), impl_item.generics, |this| { + this.visit_generics(impl_item.generics); + this.visit_ty(ty); + }), + Const(_, _) => self.visit_early(impl_item.hir_id(), impl_item.generics, |this| { intravisit::walk_impl_item(this, impl_item) }), - Type(ty) => { - let generics = &impl_item.generics; - let bound_vars: FxIndexMap = - generics.params.iter().map(ResolvedArg::early).collect(); - self.record_late_bound_vars(impl_item.hir_id(), vec![]); - let scope = Scope::Binder { - hir_id: impl_item.hir_id(), - bound_vars, - s: self.scope, - scope_type: BinderScopeType::Normal, - where_bound_origin: None, - }; - self.with(scope, |this| { - let scope = Scope::TraitRefBoundary { s: this.scope }; - this.with(scope, |this| { - this.visit_generics(generics); - this.visit_ty(ty); - }) - }); - } - Const(_, _) => { - // Only methods and types support generics. - assert!(impl_item.generics.params.is_empty()); - intravisit::walk_impl_item(self, impl_item); - } } } @@ -1194,6 +1133,25 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { self.with(scope, walk); } + fn visit_early(&mut self, hir_id: hir::HirId, generics: &'tcx hir::Generics<'tcx>, walk: F) + where + F: for<'b, 'c> FnOnce(&'b mut BoundVarContext<'c, 'tcx>), + { + let bound_vars = generics.params.iter().map(ResolvedArg::early).collect(); + self.record_late_bound_vars(hir_id, vec![]); + let scope = Scope::Binder { + hir_id, + bound_vars, + s: self.scope, + scope_type: BinderScopeType::Normal, + where_bound_origin: None, + }; + self.with(scope, |this| { + let scope = Scope::TraitRefBoundary { s: this.scope }; + this.with(scope, walk) + }); + } + #[instrument(level = "debug", skip(self))] fn resolve_lifetime_ref( &mut self, @@ -1289,8 +1247,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { scope = s; } - Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } + Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } | Scope::AnonConstBoundary { s } => { @@ -1344,18 +1301,15 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { Scope::Binder { where_bound_origin: Some(hir::PredicateOrigin::ImplTrait), .. } => { - let mut err = self.tcx.sess.struct_span_err( - lifetime_ref.ident.span, - "`impl Trait` can only mention lifetimes bound at the fn or impl level", - ); - err.span_note(self.tcx.def_span(region_def_id), "lifetime declared here"); - err.emit(); + self.tcx.sess.emit_err(errors::LateBoundInApit::Lifetime { + span: lifetime_ref.ident.span, + param_span: self.tcx.def_span(region_def_id), + }); return; } Scope::Root { .. } => break, Scope::Binder { s, .. } | Scope::Body { s, .. } - | Scope::Elision { s, .. } | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } @@ -1379,6 +1333,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { let mut late_depth = 0; let mut scope = self.scope; let mut crossed_anon_const = false; + let result = loop { match *scope { Scope::Body { s, .. } => { @@ -1406,8 +1361,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { scope = s; } - Scope::Elision { s, .. } - | Scope::ObjectLifetimeDefault { s, .. } + Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } => { scope = s; @@ -1446,6 +1400,49 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { return; } + // We may fail to resolve higher-ranked ty/const vars that are mentioned by APIT. + // AST-based resolution does not care for impl-trait desugaring, which are the + // responsibility of lowering. This may create a mismatch between the resolution + // AST found (`param_def_id`) which points to HRTB, and what HIR allows. + // ``` + // fn foo(x: impl for Trait>) {} + // ``` + // + // In such case, walk back the binders to diagnose it properly. + let mut scope = self.scope; + loop { + match *scope { + Scope::Binder { + where_bound_origin: Some(hir::PredicateOrigin::ImplTrait), .. + } => { + let guar = self.tcx.sess.emit_err(match self.tcx.def_kind(param_def_id) { + DefKind::TyParam => errors::LateBoundInApit::Type { + span: self.tcx.hir().span(hir_id), + param_span: self.tcx.def_span(param_def_id), + }, + DefKind::ConstParam => errors::LateBoundInApit::Const { + span: self.tcx.hir().span(hir_id), + param_span: self.tcx.def_span(param_def_id), + }, + kind => { + bug!("unexpected def-kind: {}", kind.descr(param_def_id.to_def_id())) + } + }); + self.map.defs.insert(hir_id, ResolvedArg::Error(guar)); + return; + } + Scope::Root { .. } => break, + Scope::Binder { s, .. } + | Scope::Body { s, .. } + | Scope::ObjectLifetimeDefault { s, .. } + | Scope::Supertrait { s, .. } + | Scope::TraitRefBoundary { s, .. } + | Scope::AnonConstBoundary { s } => { + scope = s; + } + } + } + self.tcx.sess.delay_span_bug( self.tcx.hir().span(hir_id), format!("could not resolve {param_def_id:?}"), @@ -1483,7 +1480,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { DefKind::Struct | DefKind::Union | DefKind::Enum - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::Trait, def_id, ) if depth == 0 => Some(def_id), @@ -1517,7 +1514,6 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { Scope::Body { .. } => break true, Scope::Binder { s, .. } - | Scope::Elision { s, .. } | Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } | Scope::TraitRefBoundary { s, .. } @@ -1674,7 +1670,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { }, )); bound_vars - .extend(self.tcx.fn_sig(assoc_fn.def_id).subst_identity().bound_vars()); + .extend(self.tcx.fn_sig(assoc_fn.def_id).instantiate_identity().bound_vars()); bound_vars } else { self.tcx.sess.delay_span_bug( @@ -1761,7 +1757,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { let obligations = predicates.predicates.iter().filter_map(|&(pred, _)| { let bound_predicate = pred.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + ty::ClauseKind::Trait(data) => { // The order here needs to match what we would get from `subst_supertrait` let pred_bound_vars = bound_predicate.bound_vars(); let mut all_bound_vars = bound_vars.clone(); @@ -1785,14 +1781,20 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { output: Option<&'tcx hir::Ty<'tcx>>, in_closure: bool, ) { - self.with(Scope::Elision { s: self.scope }, |this| { - for input in inputs { - this.visit_ty(input); - } - if !in_closure && let Some(output) = output { - this.visit_ty(output); - } - }); + self.with( + Scope::ObjectLifetimeDefault { + lifetime: Some(ResolvedArg::StaticLifetime), + s: self.scope, + }, + |this| { + for input in inputs { + this.visit_ty(input); + } + if !in_closure && let Some(output) = output { + this.visit_ty(output); + } + }, + ); if in_closure && let Some(output) = output { self.visit_ty(output); } @@ -1812,7 +1814,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> { scope = s; } - Scope::Root { .. } | Scope::Elision { .. } => break ResolvedArg::StaticLifetime, + Scope::Root { .. } => break ResolvedArg::StaticLifetime, Scope::Body { .. } | Scope::ObjectLifetimeDefault { lifetime: None, .. } => return, @@ -1988,15 +1990,15 @@ fn is_late_bound_map( hir::TyKind::Path(hir::QPath::Resolved( None, - hir::Path { res: Res::Def(DefKind::TyAlias, alias_def), segments, span }, + hir::Path { res: Res::Def(DefKind::TyAlias { .. }, alias_def), segments, span }, )) => { // See comments on `ConstrainedCollectorPostAstConv` for why this arm does not just consider - // substs to be unconstrained. + // args to be unconstrained. let generics = self.tcx.generics_of(alias_def); let mut walker = ConstrainedCollectorPostAstConv { arg_is_constrained: vec![false; generics.params.len()].into_boxed_slice(), }; - walker.visit_ty(self.tcx.type_of(alias_def).subst_identity()); + walker.visit_ty(self.tcx.type_of(alias_def).instantiate_identity()); match segments.last() { Some(hir::PathSegment { args: Some(args), .. }) => { @@ -2010,8 +2012,7 @@ fn is_late_bound_map( tcx.sess.delay_span_bug( *span, format!( - "Incorrect generic arg count for alias {:?}", - alias_def + "Incorrect generic arg count for alias {alias_def:?}" ), ); None diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 8e082d3c5328b..2bbdbe3a1f624 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -3,7 +3,6 @@ use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_hir::HirId; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::util::IntTypeExt; use rustc_middle::ty::{self, ImplTraitInTraitData, IsSuggestable, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::symbol::Ident; @@ -16,6 +15,7 @@ mod opaque; fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { use hir::*; + use rustc_middle::ty::Ty; let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); let Node::AnonConst(_) = tcx.hir().get(hir_id) else { panic!() }; @@ -25,21 +25,15 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { let (generics, arg_idx) = match parent_node { // Easy case: arrays repeat expressions. - Node::Ty(&Ty { kind: TyKind::Array(_, ref constant), .. }) + Node::Ty(&hir::Ty { kind: TyKind::Array(_, ref constant), .. }) | Node::Expr(&Expr { kind: ExprKind::Repeat(_, ref constant), .. }) if constant.hir_id() == hir_id => { return tcx.types.usize } - Node::Ty(&Ty { kind: TyKind::Typeof(ref e), .. }) if e.hir_id == hir_id => { + Node::Ty(&hir::Ty { kind: TyKind::Typeof(ref e), .. }) if e.hir_id == hir_id => { return tcx.typeck(def_id).node_type(e.hir_id) } - Node::Expr(&Expr { kind: ExprKind::ConstBlock(ref anon_const), .. }) - if anon_const.hir_id == hir_id => - { - let substs = InternalSubsts::identity_for_item(tcx, def_id.to_def_id()); - return substs.as_inline_const().ty() - } Node::Expr(&Expr { kind: ExprKind::InlineAsm(asm), .. }) | Node::Item(&Item { kind: ItemKind::GlobalAsm(asm), .. }) if asm.operands.iter().any(|(op, _op_sp)| match op { @@ -73,7 +67,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { ) => { let Some(trait_def_id) = trait_ref.trait_def_id() else { - return tcx.ty_error_with_message(tcx.def_span(def_id), "Could not find trait"); + return Ty::new_error_with_message(tcx,tcx.def_span(def_id), "Could not find trait"); }; let assoc_items = tcx.associated_items(trait_def_id); let assoc_item = assoc_items.find_by_name_and_kind( @@ -85,7 +79,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { .expect("const parameter types cannot be generic") } else { // FIXME(associated_const_equality): add a useful error message here. - tcx.ty_error_with_message(tcx.def_span(def_id), "Could not find associated const on trait") + Ty::new_error_with_message(tcx,tcx.def_span(def_id), "Could not find associated const on trait") } } @@ -105,7 +99,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { // arm would handle this. // // I believe this match arm is only needed for GAT but I am not 100% sure - BoxyUwU - Node::Ty(hir_ty @ Ty { kind: TyKind::Path(QPath::TypeRelative(_, segment)), .. }) => { + Node::Ty(hir_ty @ hir::Ty { kind: TyKind::Path(QPath::TypeRelative(_, segment)), .. }) => { // Find the Item containing the associated type so we can create an ItemCtxt. // Using the ItemCtxt convert the HIR for the unresolved assoc type into a // ty which is a fully resolved projection. @@ -143,7 +137,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { (generics, arg_index) } else { // I dont think it's possible to reach this but I'm not 100% sure - BoxyUwU - return tcx.ty_error_with_message( + return Ty::new_error_with_message(tcx, tcx.def_span(def_id), "unexpected non-GAT usage of an anon const", ); @@ -160,9 +154,9 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { // As there is no relevant param for `def_id`, we simply return // `None` here. let Some(type_dependent_def) = tables.type_dependent_def_id(parent_node_id) else { - return tcx.ty_error_with_message( + return Ty::new_error_with_message(tcx, tcx.def_span(def_id), - format!("unable to find type-dependent def for {:?}", parent_node_id), + format!("unable to find type-dependent def for {parent_node_id:?}"), ); }; let idx = segment @@ -180,12 +174,12 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { (tcx.generics_of(type_dependent_def), idx) } - Node::Ty(&Ty { kind: TyKind::Path(_), .. }) + Node::Ty(&hir::Ty { kind: TyKind::Path(_), .. }) | Node::Expr(&Expr { kind: ExprKind::Path(_) | ExprKind::Struct(..), .. }) | Node::TraitRef(..) | Node::Pat(_) => { let path = match parent_node { - Node::Ty(&Ty { kind: TyKind::Path(QPath::Resolved(_, path)), .. }) + Node::Ty(&hir::Ty { kind: TyKind::Path(QPath::Resolved(_, path)), .. }) | Node::TraitRef(&TraitRef { path, .. }) => &*path, Node::Expr(&Expr { kind: @@ -201,16 +195,16 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { if let Some(path) = get_path_containing_arg_in_pat(pat, hir_id) { path } else { - return tcx.ty_error_with_message( + return Ty::new_error_with_message(tcx, tcx.def_span(def_id), - format!("unable to find const parent for {} in pat {:?}", hir_id, pat), + format!("unable to find const parent for {hir_id} in pat {pat:?}"), ); } } _ => { - return tcx.ty_error_with_message( + return Ty::new_error_with_message(tcx, tcx.def_span(def_id), - format!("unexpected const parent path {:?}", parent_node), + format!("unexpected const parent path {parent_node:?}"), ); } }; @@ -230,7 +224,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { .position(|ct| ct.hir_id == hir_id) .map(|idx| (idx, seg))) }) else { - return tcx.ty_error_with_message( + return Ty::new_error_with_message(tcx, tcx.def_span(def_id), "no arg matching AnonConst in path", ); @@ -239,7 +233,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { let generics = match tcx.res_generics_def_id(segment.res) { Some(def_id) => tcx.generics_of(def_id), None => { - return tcx.ty_error_with_message( + return Ty::new_error_with_message(tcx, tcx.def_span(def_id), format!("unexpected anon const res {:?} in path: {:?}", segment.res, path), ); @@ -249,7 +243,7 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { (generics, arg_index) } - _ => return tcx.ty_error_with_message( + _ => return Ty::new_error_with_message(tcx, tcx.def_span(def_id), format!("unexpected const parent in type_of(): {parent_node:?}"), ), @@ -275,7 +269,8 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> { { tcx.type_of(param_def_id).no_bound_vars().expect("const parameter types cannot be generic") } else { - return tcx.ty_error_with_message( + return Ty::new_error_with_message( + tcx, tcx.def_span(def_id), format!("const generic parameter not found in {generics:?} at position {arg_idx:?}"), ); @@ -311,6 +306,9 @@ fn get_path_containing_arg_in_pat<'hir>( } pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder> { + use rustc_hir::*; + use rustc_middle::ty::Ty; + // If we are computing `type_of` the synthesized associated type for an RPITIT in the impl // side, use `collect_return_position_impl_trait_in_trait_tys` to infer the value of the // associated type in the impl. @@ -323,7 +321,8 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder { - return ty::EarlyBinder(tcx.ty_error_with_message( + return ty::EarlyBinder::bind(Ty::new_error_with_message( + tcx, DUMMY_SP, "Could not collect return position impl trait in trait tys", )); @@ -331,8 +330,6 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder, def_id: LocalDefId) -> ty::EarlyBinder match item.kind { TraitItemKind::Fn(..) => { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - tcx.mk_fn_def(def_id.to_def_id(), substs) + let args = ty::GenericArgs::identity_for_item(tcx, def_id); + Ty::new_fn_def(tcx, def_id.to_def_id(), args) } TraitItemKind::Const(ty, body_id) => body_id .and_then(|body_id| { is_suggestable_infer_ty(ty).then(|| { infer_placeholder_type( - tcx, def_id, body_id, ty.span, item.ident, "constant", + tcx, + def_id, + body_id, + ty.span, + item.ident, + "associated constant", ) }) }) @@ -360,12 +362,19 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder match item.kind { ImplItemKind::Fn(..) => { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - tcx.mk_fn_def(def_id.to_def_id(), substs) + let args = ty::GenericArgs::identity_for_item(tcx, def_id); + Ty::new_fn_def(tcx, def_id.to_def_id(), args) } ImplItemKind::Const(ty, body_id) => { if is_suggestable_infer_ty(ty) { - infer_placeholder_type(tcx, def_id, body_id, ty.span, item.ident, "constant") + infer_placeholder_type( + tcx, + def_id, + body_id, + ty.span, + item.ident, + "associated constant", + ) } else { icx.to_ty(ty) } @@ -395,7 +404,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder { + ItemKind::Const(ty, _, body_id) => { if is_suggestable_infer_ty(ty) { infer_placeholder_type( tcx, def_id, body_id, ty.span, item.ident, "constant", @@ -411,31 +420,31 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder icx.to_ty(*self_ty), }, ItemKind::Fn(..) => { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - tcx.mk_fn_def(def_id.to_def_id(), substs) + let args = ty::GenericArgs::identity_for_item(tcx, def_id); + Ty::new_fn_def(tcx, def_id.to_def_id(), args) } ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Union(..) => { let def = tcx.adt_def(def_id); - let substs = InternalSubsts::identity_for_item(tcx, def_id); - tcx.mk_adt(def, substs) + let args = ty::GenericArgs::identity_for_item(tcx, def_id); + Ty::new_adt(tcx, def, args) } ItemKind::OpaqueTy(OpaqueTy { origin: hir::OpaqueTyOrigin::TyAlias { .. }, .. }) => opaque::find_opaque_ty_constraints_for_tait(tcx, def_id), // Opaque types desugared from `impl Trait`. - ItemKind::OpaqueTy(OpaqueTy { + ItemKind::OpaqueTy(&OpaqueTy { origin: hir::OpaqueTyOrigin::FnReturn(owner) | hir::OpaqueTyOrigin::AsyncFn(owner), in_trait, .. }) => { - if in_trait && !tcx.impl_defaultness(owner).has_value() { + if in_trait && !tcx.defaultness(owner).has_value() { span_bug!( tcx.def_span(def_id), "tried to get type of this RPITIT with no definition" @@ -462,20 +471,20 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder match foreign_item.kind { ForeignItemKind::Fn(..) => { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - tcx.mk_fn_def(def_id.to_def_id(), substs) + let args = ty::GenericArgs::identity_for_item(tcx, def_id); + Ty::new_fn_def(tcx, def_id.to_def_id(), args) } ForeignItemKind::Static(t, _) => icx.to_ty(t), - ForeignItemKind::Type => tcx.mk_foreign(def_id.to_def_id()), + ForeignItemKind::Type => Ty::new_foreign(tcx, def_id.to_def_id()), }, Node::Ctor(def) | Node::Variant(Variant { data: def, .. }) => match def { VariantData::Unit(..) | VariantData::Struct(..) => { - tcx.type_of(tcx.hir().get_parent_item(hir_id)).subst_identity() + tcx.type_of(tcx.hir().get_parent_item(hir_id)).instantiate_identity() } VariantData::Tuple(..) => { - let substs = InternalSubsts::identity_for_item(tcx, def_id); - tcx.mk_fn_def(def_id.to_def_id(), substs) + let args = ty::GenericArgs::identity_for_item(tcx, def_id); + Ty::new_fn_def(tcx, def_id.to_def_id(), args) } }, @@ -487,6 +496,11 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder anon_const_type_of(tcx, def_id), + Node::ConstBlock(_) => { + let args = ty::GenericArgs::identity_for_item(tcx, def_id.to_def_id()); + args.as_inline_const().ty() + } + Node::GenericParam(param) => match ¶m.kind { GenericParamKind::Type { default: Some(ty), .. } | GenericParamKind::Const { ty, .. } => icx.to_ty(ty), @@ -497,7 +511,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder( @@ -530,7 +544,7 @@ fn infer_placeholder_type<'a>( if let Some(ty) = ty.make_suggestable(tcx, false) { err.span_suggestion( span, - format!("provide a type for the {item}", item = kind), + format!("provide a type for the {kind}"), format!("{colon} {ty}"), Applicability::MachineApplicable, ); diff --git a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs index f7c5b44678f72..957a6bb348109 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs @@ -1,3 +1,4 @@ +use rustc_errors::StashKey; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{self as hir, Expr, ImplItem, Item, Node, TraitItem}; @@ -5,7 +6,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::DUMMY_SP; -use crate::errors::UnconstrainedOpaqueType; +use crate::errors::{TaitForwardCompat, UnconstrainedOpaqueType}; /// Checks "defining uses" of opaque `impl Trait` types to ensure that they meet the restrictions /// laid for "higher-order pattern unification". @@ -59,7 +60,20 @@ pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: Local } } - let Some(hidden) = locator.found else { + 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) + && !(concrete_type, hidden).references_error() + { + hidden.report_mismatch(&concrete_type, def_id, tcx).emit(); + } + } + } + + hidden.ty + } else { let reported = tcx.sess.emit_err(UnconstrainedOpaqueType { span: tcx.def_span(def_id), name: tcx.item_name(tcx.local_parent(def_id).to_def_id()), @@ -70,21 +84,8 @@ pub(super) fn find_opaque_ty_constraints_for_tait(tcx: TyCtxt<'_>, def_id: Local _ => "item", }, }); - return tcx.ty_error(reported); - }; - - // 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) - && !(concrete_type, hidden).references_error() - { - hidden.report_mismatch(&concrete_type, def_id, tcx).emit(); - } - } + Ty::new_error(tcx, reported) } - - hidden.ty } struct TaitConstraintLocator<'tcx> { @@ -127,16 +128,41 @@ impl TaitConstraintLocator<'_> { // ``` 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: self.tcx.ty_error(guar) }); + self.found = + Some(ty::OpaqueHiddenType { span: DUMMY_SP, ty: Ty::new_error(self.tcx, guar) }); return; } - let Some(&typeck_hidden_ty) = tables.concrete_opaque_types.get(&self.def_id) else { + + 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; + if !self.tcx.opaque_types_defined_by(item_def_id).contains(&self.def_id) { + self.tcx.sess.emit_err(TaitForwardCompat { + span: hidden_type.span, + item_span: self + .tcx + .def_ident_span(item_def_id) + .unwrap_or_else(|| self.tcx.def_span(item_def_id)), + }); + } + 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"); return; }; - if self.typeck_types.iter().all(|prev| prev.ty != typeck_hidden_ty.ty) { - self.typeck_types.push(typeck_hidden_ty); - } // Use borrowck to get the type with unerased regions. let concrete_opaque_types = &self.tcx.mir_borrowck(item_def_id).concrete_opaque_types; @@ -146,7 +172,7 @@ impl TaitConstraintLocator<'_> { if let Some(prev) = &mut self.found { if concrete_type.ty != prev.ty && !(concrete_type, prev.ty).references_error() { let guar = prev.report_mismatch(&concrete_type, self.def_id, self.tcx).emit(); - prev.ty = self.tcx.ty_error(guar); + prev.ty = Ty::new_error(self.tcx, guar); } } else { self.found = Some(concrete_type); @@ -190,17 +216,45 @@ impl<'tcx> intravisit::Visitor<'tcx> for TaitConstraintLocator<'tcx> { } } -pub(super) fn find_opaque_ty_constraints_for_rpit( - tcx: TyCtxt<'_>, +pub(super) fn find_opaque_ty_constraints_for_rpit<'tcx>( + tcx: TyCtxt<'tcx>, def_id: LocalDefId, owner_def_id: LocalDefId, ) -> Ty<'_> { - let concrete = tcx.mir_borrowck(owner_def_id).concrete_opaque_types.get(&def_id).copied(); + let tables = tcx.typeck(owner_def_id); - if let Some(concrete) = concrete { + // 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> = 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 && !(concrete_type, prev.ty).references_error() { + prev.report_mismatch(&concrete_type, def_id, tcx).stash( + tcx.def_span(opaque_type_key.def_id), + StashKey::OpaqueHiddenTypeMismatch, + ); + } + } else { + hir_opaque_ty = Some(concrete_type); + } + } + } + + 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 { let scope = tcx.hir().local_def_id_to_hir_id(owner_def_id); debug!(?scope); - let mut locator = RpitConstraintChecker { def_id, tcx, found: concrete }; + let mut locator = RpitConstraintChecker { def_id, tcx, found: mir_opaque_ty }; match tcx.hir().get(scope) { Node::Item(it) => intravisit::walk_item(&mut locator, it), @@ -208,27 +262,28 @@ pub(super) fn find_opaque_ty_constraints_for_rpit( Node::TraitItem(it) => intravisit::walk_trait_item(&mut locator, it), other => bug!("{:?} is not a valid scope for an opaque type item", other), } - } - concrete.map(|concrete| concrete.ty).unwrap_or_else(|| { - let table = tcx.typeck(owner_def_id); - if let Some(guar) = table.tainted_by_errors { - // Some error in the - // owner fn prevented us from populating + 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. - tcx.ty_error(guar) + Ty::new_error(tcx, guar) } else { - table.concrete_opaque_types.get(&def_id).map(|ty| ty.ty).unwrap_or_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. - tcx.mk_diverging_default() - }) + Ty::new_diverging_default(tcx) + } } - }) + } } struct RpitConstraintChecker<'tcx> { diff --git a/compiler/rustc_hir_analysis/src/constrained_generic_params.rs b/compiler/rustc_hir_analysis/src/constrained_generic_params.rs index 9200c2aecf55c..5591fa6f2a53d 100644 --- a/compiler/rustc_hir_analysis/src/constrained_generic_params.rs +++ b/compiler/rustc_hir_analysis/src/constrained_generic_params.rs @@ -59,7 +59,7 @@ struct ParameterCollector { impl<'tcx> TypeVisitor> for ParameterCollector { fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { match *t.kind() { - ty::Alias(ty::Projection | ty::Inherent, ..) if !self.include_nonconstraining => { + ty::Alias(..) if !self.include_nonconstraining => { // projections are not injective return ControlFlow::Continue(()); } @@ -151,7 +151,7 @@ pub fn identify_constrained_generic_params<'tcx>( /// think of any. pub fn setup_constraining_predicates<'tcx>( tcx: TyCtxt<'tcx>, - predicates: &mut [(ty::Predicate<'tcx>, Span)], + predicates: &mut [(ty::Clause<'tcx>, Span)], impl_trait_ref: Option>, input_parameters: &mut FxHashSet, ) { @@ -187,9 +187,7 @@ pub fn setup_constraining_predicates<'tcx>( for j in i..predicates.len() { // Note that we don't have to care about binders here, // as the impl trait ref never contains any late-bound regions. - if let ty::PredicateKind::Clause(ty::Clause::Projection(projection)) = - predicates[j].0.kind().skip_binder() - { + if let ty::ClauseKind::Projection(projection) = predicates[j].0.kind().skip_binder() { // Special case: watch out for some kind of sneaky attempt // to project out an associated type defined by this very // trait. diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 6e7eb4f6cdcd8..9471ad9ca9063 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -5,7 +5,7 @@ use rustc_errors::{ error_code, Applicability, DiagnosticBuilder, ErrorGuaranteed, Handler, IntoDiagnostic, MultiSpan, }; -use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::{self, print::TraitRefPrintOnlyTraitPath, Ty}; use rustc_span::{symbol::Ident, Span, Symbol}; @@ -184,6 +184,16 @@ pub struct UnconstrainedOpaqueType { pub what: &'static str, } +#[derive(Diagnostic)] +#[diag(hir_analysis_tait_forward_compat)] +#[note] +pub struct TaitForwardCompat { + #[primary_span] + pub span: Span, + #[note] + pub item_span: Span, +} + pub struct MissingTypeParams { pub span: Span, pub def_span: Span, @@ -206,7 +216,7 @@ impl<'a> IntoDiagnostic<'a> for MissingTypeParams { "parameters", self.missing_type_params .iter() - .map(|n| format!("`{}`", n)) + .map(|n| format!("`{n}`")) .collect::>() .join(", "), ); @@ -857,3 +867,63 @@ pub(crate) enum DropImplPolarity { span: Span, }, } + +#[derive(Diagnostic)] +pub(crate) enum ReturnTypeNotationIllegalParam { + #[diag(hir_analysis_return_type_notation_illegal_param_type)] + Type { + #[primary_span] + span: Span, + #[label] + param_span: Span, + }, + #[diag(hir_analysis_return_type_notation_illegal_param_const)] + Const { + #[primary_span] + span: Span, + #[label] + param_span: Span, + }, +} + +#[derive(Diagnostic)] +pub(crate) enum LateBoundInApit { + #[diag(hir_analysis_late_bound_type_in_apit)] + Type { + #[primary_span] + span: Span, + #[label] + param_span: Span, + }, + #[diag(hir_analysis_late_bound_const_in_apit)] + Const { + #[primary_span] + span: Span, + #[label] + param_span: Span, + }, + #[diag(hir_analysis_late_bound_lifetime_in_apit)] + Lifetime { + #[primary_span] + span: Span, + #[label] + param_span: Span, + }, +} + +#[derive(LintDiagnostic)] +#[diag(hir_analysis_unused_associated_type_bounds)] +#[note] +pub struct UnusedAssociatedTypeBounds { + #[suggestion(code = "")] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(hir_analysis_assoc_bound_on_const)] +#[note] +pub struct AssocBoundOnConst { + #[primary_span] + pub span: Span, + pub descr: &'static str, +} diff --git a/compiler/rustc_hir_analysis/src/hir_wf_check.rs b/compiler/rustc_hir_analysis/src/hir_wf_check.rs index e4c6e6e391a11..ca7679cfba06e 100644 --- a/compiler/rustc_hir_analysis/src/hir_wf_check.rs +++ b/compiler/rustc_hir_analysis/src/hir_wf_check.rs @@ -79,7 +79,7 @@ fn diagnostic_hir_wf_check<'tcx>( self.tcx, cause, self.param_env, - ty::PredicateKind::WellFormed(tcx_ty.into()), + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(tcx_ty.into())), )); for error in ocx.select_all_or_error() { @@ -128,7 +128,9 @@ fn diagnostic_hir_wf_check<'tcx>( ref item => bug!("Unexpected TraitItem {:?}", item), }, hir::Node::Item(item) => match item.kind { - hir::ItemKind::Static(ty, _, _) | hir::ItemKind::Const(ty, _) => vec![ty], + hir::ItemKind::TyAlias(ty, _) + | hir::ItemKind::Static(ty, _, _) + | hir::ItemKind::Const(ty, _, _) => vec![ty], hir::ItemKind::Impl(impl_) => match &impl_.of_trait { Some(t) => t .path diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check.rs b/compiler/rustc_hir_analysis/src/impl_wf_check.rs index 612d4ff3df843..788121f7a304f 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check.rs @@ -14,7 +14,7 @@ use min_specialization::check_min_specialization; use rustc_data_structures::fx::FxHashSet; use rustc_errors::struct_span_err; use rustc_hir::def::DefKind; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_middle::query::Providers; use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; use rustc_span::{Span, Symbol}; @@ -51,7 +51,7 @@ mod min_specialization; /// impl<'a> Trait for Bar { type X = &'a i32; } /// // ^ 'a is unused and appears in assoc type, error /// ``` -fn check_mod_impl_wf(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_impl_wf(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { let min_specialization = tcx.features().min_specialization; let module = tcx.hir_module_items(module_def_id); for id in module.items() { @@ -70,22 +70,21 @@ pub fn provide(providers: &mut Providers) { fn enforce_impl_params_are_constrained(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) { // Every lifetime used in an associated type must be constrained. - let impl_self_ty = tcx.type_of(impl_def_id).subst_identity(); + let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity(); if impl_self_ty.references_error() { // Don't complain about unconstrained type params when self ty isn't known due to errors. // (#36836) tcx.sess.delay_span_bug( tcx.def_span(impl_def_id), format!( - "potentially unconstrained type parameters weren't evaluated: {:?}", - impl_self_ty, + "potentially unconstrained type parameters weren't evaluated: {impl_self_ty:?}", ), ); return; } let impl_generics = tcx.generics_of(impl_def_id); let impl_predicates = tcx.predicates_of(impl_def_id); - let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::subst_identity); + let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity); let mut input_parameters = cgp::parameters_for_impl(impl_self_ty, impl_trait_ref); cgp::identify_constrained_generic_params( @@ -104,12 +103,12 @@ fn enforce_impl_params_are_constrained(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) match item.kind { ty::AssocKind::Type => { if item.defaultness(tcx).has_value() { - cgp::parameters_for(&tcx.type_of(def_id).subst_identity(), true) + cgp::parameters_for(&tcx.type_of(def_id).instantiate_identity(), true) } else { - Vec::new() + vec![] } } - ty::AssocKind::Fn | ty::AssocKind::Const => Vec::new(), + ty::AssocKind::Fn | ty::AssocKind::Const => vec![], } }) .collect(); @@ -180,7 +179,7 @@ fn report_unused_parameter(tcx: TyCtxt<'_>, span: Span, kind: &str, name: Symbol kind, name ); - err.span_label(span, format!("unconstrained {} parameter", kind)); + err.span_label(span, format!("unconstrained {kind} parameter")); if kind == "const" { err.note( "expressions using a const parameter must map each value to a distinct output value", diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs index e84da2519ae81..3760195a5e8ec 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs @@ -14,15 +14,15 @@ //! To enforce this requirement on specializations we take the following //! approach: //! -//! 1. Match up the substs for `impl2` so that the implemented trait and +//! 1. Match up the args for `impl2` so that the implemented trait and //! self-type match those for `impl1`. -//! 2. Check for any direct use of `'static` in the substs of `impl2`. +//! 2. Check for any direct use of `'static` in the args of `impl2`. //! 3. Check that all of the generic parameters of `impl1` occur at most once -//! in the *unconstrained* substs for `impl2`. A parameter is constrained if +//! in the *unconstrained* args for `impl2`. A parameter is constrained if //! its value is completely determined by an associated type projection //! predicate. //! 4. Check that all predicates on `impl1` either exist on `impl2` (after -//! matching substs), or are well-formed predicates for the trait's type +//! matching args), or are well-formed predicates for the trait's type //! arguments. //! //! ## Example @@ -74,13 +74,13 @@ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::specialization_graph::Node; -use rustc_middle::ty::subst::{GenericArg, InternalSubsts, SubstsRef}; use rustc_middle::ty::trait_def::TraitSpecializationKind; use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; -use rustc_span::Span; +use rustc_middle::ty::{GenericArg, GenericArgs, GenericArgsRef}; +use rustc_span::{ErrorGuaranteed, Span}; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _; -use rustc_trait_selection::traits::{self, translate_substs_with_cause, wf, ObligationCtxt}; +use rustc_trait_selection::traits::{self, translate_args_with_cause, wf, ObligationCtxt}; pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) { if let Some(node) = parent_specialization_node(tcx, impl_def_id) { @@ -113,20 +113,20 @@ fn check_always_applicable(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node let span = tcx.def_span(impl1_def_id); check_has_items(tcx, impl1_def_id, impl2_node, span); - if let Some((impl1_substs, impl2_substs)) = get_impl_substs(tcx, impl1_def_id, impl2_node) { + if let Ok((impl1_args, impl2_args)) = get_impl_args(tcx, impl1_def_id, impl2_node) { let impl2_def_id = impl2_node.def_id(); - debug!(?impl2_def_id, ?impl2_substs); + debug!(?impl2_def_id, ?impl2_args); - let parent_substs = if impl2_node.is_from_trait() { - impl2_substs.to_vec() + let parent_args = if impl2_node.is_from_trait() { + impl2_args.to_vec() } else { - unconstrained_parent_impl_substs(tcx, impl2_def_id, impl2_substs) + unconstrained_parent_impl_args(tcx, impl2_def_id, impl2_args) }; check_constness(tcx, impl1_def_id, impl2_node, span); - check_static_lifetimes(tcx, &parent_substs, span); - check_duplicate_params(tcx, impl1_substs, &parent_substs, span); - check_predicates(tcx, impl1_def_id, impl1_substs, impl2_node, impl2_substs, span); + check_static_lifetimes(tcx, &parent_args, span); + check_duplicate_params(tcx, impl1_args, &parent_args, span); + check_predicates(tcx, impl1_def_id, impl1_args, impl2_node, impl2_args, span); } } @@ -167,25 +167,23 @@ fn check_constness(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node: Node, /// ``` /// /// Would return `S1 = [C]` and `S2 = [Vec, C]`. -fn get_impl_substs( +fn get_impl_args( tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node: Node, -) -> Option<(SubstsRef<'_>, SubstsRef<'_>)> { +) -> Result<(GenericArgsRef<'_>, GenericArgsRef<'_>), ErrorGuaranteed> { let infcx = &tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(infcx); let param_env = tcx.param_env(impl1_def_id); - - let assumed_wf_types = - ocx.assumed_wf_types(param_env, tcx.def_span(impl1_def_id), impl1_def_id); - - let impl1_substs = InternalSubsts::identity_for_item(tcx, impl1_def_id); let impl1_span = tcx.def_span(impl1_def_id); - let impl2_substs = translate_substs_with_cause( + let assumed_wf_types = ocx.assumed_wf_types_and_report_errors(param_env, impl1_def_id)?; + + let impl1_args = GenericArgs::identity_for_item(tcx, impl1_def_id); + let impl2_args = translate_args_with_cause( infcx, param_env, impl1_def_id.to_def_id(), - impl1_substs, + impl1_args, impl2_node, |_, span| { traits::ObligationCause::new( @@ -198,19 +196,19 @@ fn get_impl_substs( let errors = ocx.select_all_or_error(); if !errors.is_empty() { - ocx.infcx.err_ctxt().report_fulfillment_errors(&errors); - return None; + let guar = ocx.infcx.err_ctxt().report_fulfillment_errors(&errors); + return Err(guar); } let implied_bounds = infcx.implied_bounds_tys(param_env, impl1_def_id, assumed_wf_types); let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); let _ = ocx.resolve_regions_and_report_errors(impl1_def_id, &outlives_env); - let Ok(impl2_substs) = infcx.fully_resolve(impl2_substs) else { + let Ok(impl2_args) = infcx.fully_resolve(impl2_args) else { let span = tcx.def_span(impl1_def_id); - tcx.sess.emit_err(SubstsOnOverriddenImpl { span }); - return None; + let guar = tcx.sess.emit_err(SubstsOnOverriddenImpl { span }); + return Err(guar); }; - Some((impl1_substs, impl2_substs)) + Ok((impl1_args, impl2_args)) } /// Returns a list of all of the unconstrained subst of the given impl. @@ -219,26 +217,24 @@ fn get_impl_substs( /// /// impl<'a, T, I> ... where &'a I: IntoIterator /// -/// This would return the substs corresponding to `['a, I]`, because knowing +/// This would return the args corresponding to `['a, I]`, because knowing /// `'a` and `I` determines the value of `T`. -fn unconstrained_parent_impl_substs<'tcx>( +fn unconstrained_parent_impl_args<'tcx>( tcx: TyCtxt<'tcx>, impl_def_id: DefId, - impl_substs: SubstsRef<'tcx>, + impl_args: GenericArgsRef<'tcx>, ) -> Vec> { let impl_generic_predicates = tcx.predicates_of(impl_def_id); let mut unconstrained_parameters = FxHashSet::default(); let mut constrained_params = FxHashSet::default(); - let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::subst_identity); + let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity); // Unfortunately the functions in `constrained_generic_parameters` don't do // what we want here. We want only a list of constrained parameters while // the functions in `cgp` add the constrained parameters to a list of // unconstrained parameters. - for (predicate, _) in impl_generic_predicates.predicates.iter() { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = - predicate.kind().skip_binder() - { + for (clause, _) in impl_generic_predicates.predicates.iter() { + if let ty::ClauseKind::Projection(proj) = clause.kind().skip_binder() { let projection_ty = proj.projection_ty; let projected_ty = proj.term; @@ -259,7 +255,7 @@ fn unconstrained_parent_impl_substs<'tcx>( } } - impl_substs + impl_args .iter() .enumerate() .filter(|&(idx, _)| !constrained_params.contains(&(idx as u32))) @@ -268,7 +264,7 @@ fn unconstrained_parent_impl_substs<'tcx>( } /// Check that parameters of the derived impl don't occur more than once in the -/// equated substs of the base impl. +/// equated args of the base impl. /// /// For example forbid the following: /// @@ -284,21 +280,21 @@ fn unconstrained_parent_impl_substs<'tcx>( /// impl Tr for Vec { } /// ``` /// -/// The substs for the parent impl here are `[T, Vec]`, which repeats `T`, -/// but `S` is constrained in the parent impl, so `parent_substs` is only +/// The args for the parent impl here are `[T, Vec]`, which repeats `T`, +/// but `S` is constrained in the parent impl, so `parent_args` is only /// `[Vec]`. This means we allow this impl. fn check_duplicate_params<'tcx>( tcx: TyCtxt<'tcx>, - impl1_substs: SubstsRef<'tcx>, - parent_substs: &Vec>, + impl1_args: GenericArgsRef<'tcx>, + parent_args: &Vec>, span: Span, ) { - let mut base_params = cgp::parameters_for(parent_substs, true); + let mut base_params = cgp::parameters_for(parent_args, true); base_params.sort_by_key(|param| param.0); if let (_, [duplicate, ..]) = base_params.partition_dedup() { - let param = impl1_substs[duplicate.0 as usize]; + let param = impl1_args[duplicate.0 as usize]; tcx.sess - .struct_span_err(span, format!("specializing impl repeats parameter `{}`", param)) + .struct_span_err(span, format!("specializing impl repeats parameter `{param}`")) .emit(); } } @@ -313,10 +309,10 @@ fn check_duplicate_params<'tcx>( /// ``` fn check_static_lifetimes<'tcx>( tcx: TyCtxt<'tcx>, - parent_substs: &Vec>, + parent_args: &Vec>, span: Span, ) { - if tcx.any_free_region_meets(parent_substs, |r| r.is_static()) { + if tcx.any_free_region_meets(parent_args, |r| r.is_static()) { tcx.sess.emit_err(errors::StaticSpecialize { span }); } } @@ -335,13 +331,16 @@ fn check_static_lifetimes<'tcx>( fn check_predicates<'tcx>( tcx: TyCtxt<'tcx>, impl1_def_id: LocalDefId, - impl1_substs: SubstsRef<'tcx>, + impl1_args: GenericArgsRef<'tcx>, impl2_node: Node, - impl2_substs: SubstsRef<'tcx>, + impl2_args: GenericArgsRef<'tcx>, span: Span, ) { - let instantiated = tcx.predicates_of(impl1_def_id).instantiate(tcx, impl1_substs); - let impl1_predicates: Vec<_> = traits::elaborate(tcx, instantiated.into_iter()).collect(); + let impl1_predicates: Vec<_> = traits::elaborate( + tcx, + tcx.predicates_of(impl1_def_id).instantiate(tcx, impl1_args).into_iter(), + ) + .collect(); let mut impl2_predicates = if impl2_node.is_from_trait() { // Always applicable traits have to be always applicable without any @@ -351,9 +350,9 @@ fn check_predicates<'tcx>( traits::elaborate( tcx, tcx.predicates_of(impl2_node.def_id()) - .instantiate(tcx, impl2_substs) - .predicates - .into_iter(), + .instantiate(tcx, impl2_args) + .into_iter() + .map(|(c, _s)| c.as_predicate()), ) .collect() }; @@ -377,16 +376,16 @@ fn check_predicates<'tcx>( let always_applicable_traits = impl1_predicates .iter() .copied() - .filter(|&(predicate, _)| { + .filter(|(clause, _span)| { matches!( - trait_predicate_kind(tcx, predicate), + trait_predicate_kind(tcx, clause.as_predicate()), Some(TraitSpecializationKind::AlwaysApplicable) ) }) - .map(|(pred, _span)| pred); + .map(|(c, _span)| c.as_predicate()); // Include the well-formed predicates of the type parameters of the impl. - for arg in tcx.impl_trait_ref(impl1_def_id).unwrap().subst_identity().substs { + for arg in tcx.impl_trait_ref(impl1_def_id).unwrap().instantiate_identity().args { let infcx = &tcx.infer_ctxt().build(); let obligations = wf::obligations(infcx, tcx.param_env(impl1_def_id), impl1_def_id, 0, arg, span) @@ -398,9 +397,12 @@ fn check_predicates<'tcx>( } impl2_predicates.extend(traits::elaborate(tcx, always_applicable_traits)); - for (predicate, span) in impl1_predicates { - if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(tcx, predicate, *pred2, span)) { - check_specialization_on(tcx, predicate, span) + for (clause, span) in impl1_predicates { + if !impl2_predicates + .iter() + .any(|pred2| trait_predicates_eq(tcx, clause.as_predicate(), *pred2, span)) + { + check_specialization_on(tcx, clause.as_predicate(), span) } } } @@ -429,45 +431,13 @@ fn check_predicates<'tcx>( /// /// So we make that check in this function and try to raise a helpful error message. fn trait_predicates_eq<'tcx>( - tcx: TyCtxt<'tcx>, + _tcx: TyCtxt<'tcx>, predicate1: ty::Predicate<'tcx>, predicate2: ty::Predicate<'tcx>, - span: Span, + _span: Span, ) -> bool { - let pred1_kind = predicate1.kind().skip_binder(); - let pred2_kind = predicate2.kind().skip_binder(); - let (trait_pred1, trait_pred2) = match (pred1_kind, pred2_kind) { - ( - ty::PredicateKind::Clause(ty::Clause::Trait(pred1)), - ty::PredicateKind::Clause(ty::Clause::Trait(pred2)), - ) => (pred1, pred2), - // Just use plain syntactic equivalence if either of the predicates aren't - // trait predicates or have bound vars. - _ => return predicate1 == predicate2, - }; - - let predicates_equal_modulo_constness = { - let pred1_unconsted = - ty::TraitPredicate { constness: ty::BoundConstness::NotConst, ..trait_pred1 }; - let pred2_unconsted = - ty::TraitPredicate { constness: ty::BoundConstness::NotConst, ..trait_pred2 }; - pred1_unconsted == pred2_unconsted - }; - - if !predicates_equal_modulo_constness { - return false; - } - - // Check that the predicate on the specializing impl is at least as const as - // the one on the base. - match (trait_pred2.constness, trait_pred1.constness) { - (ty::BoundConstness::ConstIfConst, ty::BoundConstness::NotConst) => { - tcx.sess.emit_err(errors::MissingTildeConst { span }); - } - _ => {} - } - - true + // FIXME(effects) + predicate1 == predicate2 } #[instrument(level = "debug", skip(tcx))] @@ -478,9 +448,8 @@ fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc _ if predicate.is_global() => (), // We allow specializing on explicitly marked traits with no associated // items. - ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, - constness: _, polarity: _, })) => { if !matches!( @@ -498,7 +467,7 @@ fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc .emit(); } } - ty::PredicateKind::Clause(ty::Clause::Projection(ty::ProjectionPredicate { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(ty::ProjectionPredicate { projection_ty, term, })) => { @@ -509,7 +478,7 @@ fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc ) .emit(); } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) => { // FIXME(min_specialization), FIXME(const_generics): // It probably isn't right to allow _every_ `ConstArgHasType` but I am somewhat unsure // about the actual rules that would be sound. Can't just always error here because otherwise @@ -521,7 +490,7 @@ fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tc } _ => { tcx.sess - .struct_span_err(span, format!("cannot specialize on predicate `{}`", predicate)) + .struct_span_err(span, format!("cannot specialize on predicate `{predicate}`")) .emit(); } } @@ -532,24 +501,22 @@ fn trait_predicate_kind<'tcx>( predicate: ty::Predicate<'tcx>, ) -> Option { match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, - constness: _, polarity: _, })) => Some(tcx.trait_def(trait_ref.def_id).specialization_kind), - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(_)) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(_)) - | ty::PredicateKind::Clause(ty::Clause::Projection(_)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::WellFormed(_) + | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) | ty::PredicateKind::Subtype(_) | ty::PredicateKind::Coerce(_) | ty::PredicateKind::ObjectSafe(_) | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::ConstEvaluatable(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => None, + | ty::PredicateKind::Ambiguous => None, } } diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index 5cd2cd50c113c..4f95174f869e7 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -59,8 +59,6 @@ This API is completely unstable and subject to change. #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(box_patterns)] #![feature(control_flow_enum)] -#![feature(drain_filter)] -#![feature(hash_drain_filter)] #![feature(if_let_guard)] #![feature(is_sorted)] #![feature(iter_intersperse)] @@ -101,23 +99,20 @@ use rustc_errors::ErrorGuaranteed; use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_hir as hir; -use rustc_hir::Node; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::middle; use rustc_middle::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::util; -use rustc_session::{config::EntryFnType, parse::feature_err}; -use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; +use rustc_session::parse::feature_err; use rustc_span::{symbol::sym, Span, DUMMY_SP}; use rustc_target::spec::abi::Abi; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; -use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode, ObligationCtxt}; - -use std::ops::Not; +use rustc_trait_selection::traits::{self, ObligationCause, ObligationCtxt}; use astconv::{AstConv, OnlySelfBounds}; use bounds::Bounds; +use rustc_hir::def::DefKind; fluent_messages! { "../messages.ftl" } @@ -178,265 +173,11 @@ fn require_same_types<'tcx>( } } -fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { - let main_fnsig = tcx.fn_sig(main_def_id).subst_identity(); - let main_span = tcx.def_span(main_def_id); - - fn main_fn_diagnostics_def_id(tcx: TyCtxt<'_>, def_id: DefId, sp: Span) -> LocalDefId { - if let Some(local_def_id) = def_id.as_local() { - let hir_type = tcx.type_of(local_def_id).subst_identity(); - if !matches!(hir_type.kind(), ty::FnDef(..)) { - span_bug!(sp, "main has a non-function type: found `{}`", hir_type); - } - local_def_id - } else { - CRATE_DEF_ID - } - } - - fn main_fn_generics_params_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { - if !def_id.is_local() { - return None; - } - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); - match tcx.hir().find(hir_id) { - Some(Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. })) => { - generics.params.is_empty().not().then_some(generics.span) - } - _ => { - span_bug!(tcx.def_span(def_id), "main has a non-function type"); - } - } - } - - fn main_fn_where_clauses_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { - if !def_id.is_local() { - return None; - } - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); - match tcx.hir().find(hir_id) { - Some(Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. })) => { - Some(generics.where_clause_span) - } - _ => { - span_bug!(tcx.def_span(def_id), "main has a non-function type"); - } - } - } - - fn main_fn_asyncness_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { - if !def_id.is_local() { - return None; - } - Some(tcx.def_span(def_id)) - } - - fn main_fn_return_type_span(tcx: TyCtxt<'_>, def_id: DefId) -> Option { - if !def_id.is_local() { - return None; - } - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); - match tcx.hir().find(hir_id) { - Some(Node::Item(hir::Item { kind: hir::ItemKind::Fn(fn_sig, _, _), .. })) => { - Some(fn_sig.decl.output.span()) - } - _ => { - span_bug!(tcx.def_span(def_id), "main has a non-function type"); - } - } - } - - let mut error = false; - let main_diagnostics_def_id = main_fn_diagnostics_def_id(tcx, main_def_id, main_span); - let main_fn_generics = tcx.generics_of(main_def_id); - let main_fn_predicates = tcx.predicates_of(main_def_id); - if main_fn_generics.count() != 0 || !main_fnsig.bound_vars().is_empty() { - let generics_param_span = main_fn_generics_params_span(tcx, main_def_id); - tcx.sess.emit_err(errors::MainFunctionGenericParameters { - span: generics_param_span.unwrap_or(main_span), - label_span: generics_param_span, - }); - error = true; - } else if !main_fn_predicates.predicates.is_empty() { - // generics may bring in implicit predicates, so we skip this check if generics is present. - let generics_where_clauses_span = main_fn_where_clauses_span(tcx, main_def_id); - tcx.sess.emit_err(errors::WhereClauseOnMain { - span: generics_where_clauses_span.unwrap_or(main_span), - generics_span: generics_where_clauses_span, - }); - error = true; - } - - let main_asyncness = tcx.asyncness(main_def_id); - if let hir::IsAsync::Async = main_asyncness { - let asyncness_span = main_fn_asyncness_span(tcx, main_def_id); - tcx.sess.emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span }); - error = true; - } - - for attr in tcx.get_attrs(main_def_id, sym::track_caller) { - tcx.sess.emit_err(errors::TrackCallerOnMain { span: attr.span, annotated: main_span }); - error = true; - } - - if !tcx.codegen_fn_attrs(main_def_id).target_features.is_empty() - // Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988 - && !tcx.sess.target.is_like_wasm - && !tcx.sess.opts.actually_rustdoc - { - tcx.sess.emit_err(errors::TargetFeatureOnMain { main: main_span }); - error = true; - } - - if error { - return; - } - - // Main should have no WC, so empty param env is OK here. - let param_env = ty::ParamEnv::empty(); - let expected_return_type; - if let Some(term_did) = tcx.lang_items().termination() { - let return_ty = main_fnsig.output(); - let return_ty_span = main_fn_return_type_span(tcx, main_def_id).unwrap_or(main_span); - if !return_ty.bound_vars().is_empty() { - tcx.sess.emit_err(errors::MainFunctionReturnTypeGeneric { span: return_ty_span }); - error = true; - } - let return_ty = return_ty.skip_binder(); - let infcx = tcx.infer_ctxt().build(); - let cause = traits::ObligationCause::new( - return_ty_span, - main_diagnostics_def_id, - ObligationCauseCode::MainFunctionType, - ); - let ocx = traits::ObligationCtxt::new(&infcx); - let norm_return_ty = ocx.normalize(&cause, param_env, return_ty); - ocx.register_bound(cause, param_env, norm_return_ty, term_did); - let errors = ocx.select_all_or_error(); - if !errors.is_empty() { - infcx.err_ctxt().report_fulfillment_errors(&errors); - error = true; - } - // now we can take the return type of the given main function - expected_return_type = main_fnsig.output(); - } else { - // standard () main return type - expected_return_type = ty::Binder::dummy(tcx.mk_unit()); - } - - if error { - return; - } - - let se_ty = tcx.mk_fn_ptr(expected_return_type.map_bound(|expected_return_type| { - tcx.mk_fn_sig([], expected_return_type, false, hir::Unsafety::Normal, Abi::Rust) - })); - - require_same_types( - tcx, - &ObligationCause::new( - main_span, - main_diagnostics_def_id, - ObligationCauseCode::MainFunctionType, - ), - param_env, - se_ty, - tcx.mk_fn_ptr(main_fnsig), - ); -} -fn check_start_fn_ty(tcx: TyCtxt<'_>, start_def_id: DefId) { - let start_def_id = start_def_id.expect_local(); - let start_id = tcx.hir().local_def_id_to_hir_id(start_def_id); - let start_span = tcx.def_span(start_def_id); - let start_t = tcx.type_of(start_def_id).subst_identity(); - match start_t.kind() { - ty::FnDef(..) => { - if let Some(Node::Item(it)) = tcx.hir().find(start_id) { - if let hir::ItemKind::Fn(sig, generics, _) = &it.kind { - let mut error = false; - if !generics.params.is_empty() { - tcx.sess.emit_err(errors::StartFunctionParameters { span: generics.span }); - error = true; - } - if generics.has_where_clause_predicates { - tcx.sess.emit_err(errors::StartFunctionWhere { - span: generics.where_clause_span, - }); - error = true; - } - if let hir::IsAsync::Async = sig.header.asyncness { - let span = tcx.def_span(it.owner_id); - tcx.sess.emit_err(errors::StartAsync { span: span }); - error = true; - } - - let attrs = tcx.hir().attrs(start_id); - for attr in attrs { - if attr.has_name(sym::track_caller) { - tcx.sess.emit_err(errors::StartTrackCaller { - span: attr.span, - start: start_span, - }); - error = true; - } - if attr.has_name(sym::target_feature) - // Calling functions with `#[target_feature]` is - // not unsafe on WASM, see #84988 - && !tcx.sess.target.is_like_wasm - && !tcx.sess.opts.actually_rustdoc - { - tcx.sess.emit_err(errors::StartTargetFeature { - span: attr.span, - start: start_span, - }); - error = true; - } - } - - if error { - return; - } - } - } - - let se_ty = tcx.mk_fn_ptr(ty::Binder::dummy(tcx.mk_fn_sig( - [tcx.types.isize, tcx.mk_imm_ptr(tcx.mk_imm_ptr(tcx.types.u8))], - tcx.types.isize, - false, - hir::Unsafety::Normal, - Abi::Rust, - ))); - - require_same_types( - tcx, - &ObligationCause::new( - start_span, - start_def_id, - ObligationCauseCode::StartFunctionType, - ), - ty::ParamEnv::empty(), // start should not have any where bounds. - se_ty, - tcx.mk_fn_ptr(tcx.fn_sig(start_def_id).subst_identity()), - ); - } - _ => { - span_bug!(start_span, "start has a non-function type: found `{}`", start_t); - } - } -} - -fn check_for_entry_fn(tcx: TyCtxt<'_>) { - match tcx.entry_fn(()) { - Some((def_id, EntryFnType::Main { .. })) => check_main_fn_ty(tcx, def_id), - Some((def_id, EntryFnType::Start)) => check_start_fn_ty(tcx, def_id), - _ => {} - } -} - pub fn provide(providers: &mut Providers) { collect::provide(providers); coherence::provide(providers); check::provide(providers); + check_unused::provide(providers); variance::provide(providers); outlives::provide(providers); impl_wf_check::provide(providers); @@ -496,8 +237,18 @@ pub fn check_crate(tcx: TyCtxt<'_>) -> Result<(), ErrorGuaranteed> { tcx.hir().for_each_module(|module| tcx.ensure().check_mod_item_types(module)) }); - check_unused::check_crate(tcx); - check_for_entry_fn(tcx); + // FIXME: Remove this when we implement creating `DefId`s + // for anon constants during their parents' typeck. + // Typeck all body owners in parallel will produce queries + // cycle errors because it may typeck on anon constants directly. + tcx.hir().par_body_owners(|item_def_id| { + let def_kind = tcx.def_kind(item_def_id); + if !matches!(def_kind, DefKind::AnonConst) { + tcx.ensure().typeck(item_def_id); + } + }); + + tcx.ensure().check_unused_traits(()); if let Some(reported) = tcx.sess.has_errors() { Err(reported) } else { Ok(()) } } diff --git a/compiler/rustc_hir_analysis/src/outlives/explicit.rs b/compiler/rustc_hir_analysis/src/outlives/explicit.rs index 357deb07b8f31..a7fca41f86aca 100644 --- a/compiler/rustc_hir_analysis/src/outlives/explicit.rs +++ b/compiler/rustc_hir_analysis/src/outlives/explicit.rs @@ -30,45 +30,34 @@ impl<'tcx> ExplicitPredicatesMap<'tcx> { // process predicates and convert to `RequiredPredicates` entry, see below for &(predicate, span) in predicates.predicates { match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(OutlivesPredicate( - ty, - reg, - ))) => insert_outlives_predicate( - tcx, - ty.into(), - reg, - span, - &mut required_predicates, - ), - - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(OutlivesPredicate( - reg1, - reg2, - ))) => insert_outlives_predicate( - tcx, - reg1.into(), - reg2, - span, - &mut required_predicates, - ), - - ty::PredicateKind::Clause(ty::Clause::Trait(..)) - | ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => (), + ty::ClauseKind::TypeOutlives(OutlivesPredicate(ty, reg)) => { + insert_outlives_predicate( + tcx, + ty.into(), + reg, + span, + &mut required_predicates, + ) + } + + ty::ClauseKind::RegionOutlives(OutlivesPredicate(reg1, reg2)) => { + insert_outlives_predicate( + tcx, + reg1.into(), + reg2, + span, + &mut required_predicates, + ) + } + ty::ClauseKind::Trait(_) + | ty::ClauseKind::Projection(_) + | ty::ClauseKind::ConstArgHasType(_, _) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => {} } } - ty::EarlyBinder(required_predicates) + ty::EarlyBinder::bind(required_predicates) }) } } diff --git a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs index 0cd2fc1aa299a..c17925471d983 100644 --- a/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs +++ b/compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs @@ -46,7 +46,7 @@ pub(super) fn infer_predicates( // For field of type &'a T (reference) or Adt // (struct/enum/union) there will be outlive // requirements for adt_def. - let field_ty = tcx.type_of(field_def.did).subst_identity(); + let field_ty = tcx.type_of(field_def.did).instantiate_identity(); let field_span = tcx.def_span(field_def.did); insert_required_predicates_to_be_wf( tcx, @@ -68,12 +68,13 @@ pub(super) fn infer_predicates( // Therefore mark `predicates_added` as true and which will ensure // we walk the crates again and re-calculate predicates for all // items. - let item_predicates_len: usize = - global_inferred_outlives.get(&item_did.to_def_id()).map_or(0, |p| p.0.len()); + let item_predicates_len: usize = global_inferred_outlives + .get(&item_did.to_def_id()) + .map_or(0, |p| p.as_ref().skip_binder().len()); if item_required_predicates.len() > item_predicates_len { predicates_added = true; global_inferred_outlives - .insert(item_did.to_def_id(), ty::EarlyBinder(item_required_predicates)); + .insert(item_did.to_def_id(), ty::EarlyBinder::bind(item_required_predicates)); } } @@ -116,7 +117,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( // can load the current set of inferred and explicit // predicates from `global_inferred_outlives` and filter the // ones that are TypeOutlives. - ty::Adt(def, substs) => { + ty::Adt(def, args) => { // First check the inferred predicates // // Example 1: @@ -137,13 +138,15 @@ fn insert_required_predicates_to_be_wf<'tcx>( // 'a` holds for `Foo`. debug!("Adt"); if let Some(unsubstituted_predicates) = global_inferred_outlives.get(&def.did()) { - for (unsubstituted_predicate, &span) in &unsubstituted_predicates.0 { + for (unsubstituted_predicate, &span) in + unsubstituted_predicates.as_ref().skip_binder() + { // `unsubstituted_predicate` is `U: 'b` in the // example above. So apply the substitution to // get `T: 'a` (or `predicate`): let predicate = unsubstituted_predicates .rebind(*unsubstituted_predicate) - .subst(tcx, substs); + .instantiate(tcx, args); insert_outlives_predicate( tcx, predicate.0, @@ -156,11 +159,11 @@ fn insert_required_predicates_to_be_wf<'tcx>( // Check if the type has any explicit predicates that need // to be added to `required_predicates` - // let _: () = substs.region_at(0); + // let _: () = args.region_at(0); check_explicit_predicates( tcx, def.did(), - substs, + args, required_predicates, explicit_map, None, @@ -183,12 +186,11 @@ fn insert_required_predicates_to_be_wf<'tcx>( // predicates in `check_explicit_predicates` we // need to ignore checking the explicit_map for // Self type. - let substs = - ex_trait_ref.with_self_ty(tcx, tcx.types.usize).skip_binder().substs; + let args = ex_trait_ref.with_self_ty(tcx, tcx.types.usize).skip_binder().args; check_explicit_predicates( tcx, ex_trait_ref.skip_binder().def_id, - substs, + args, required_predicates, explicit_map, Some(tcx.types.self_param), @@ -203,7 +205,7 @@ fn insert_required_predicates_to_be_wf<'tcx>( check_explicit_predicates( tcx, tcx.parent(obj.def_id), - obj.substs, + obj.args, required_predicates, explicit_map, None, @@ -236,22 +238,22 @@ fn insert_required_predicates_to_be_wf<'tcx>( fn check_explicit_predicates<'tcx>( tcx: TyCtxt<'tcx>, def_id: DefId, - substs: &[GenericArg<'tcx>], + args: &[GenericArg<'tcx>], required_predicates: &mut RequiredPredicates<'tcx>, explicit_map: &mut ExplicitPredicatesMap<'tcx>, ignored_self_ty: Option>, ) { debug!( "check_explicit_predicates(def_id={:?}, \ - substs={:?}, \ + args={:?}, \ explicit_map={:?}, \ required_predicates={:?}, \ ignored_self_ty={:?})", - def_id, substs, explicit_map, required_predicates, ignored_self_ty, + def_id, args, explicit_map, required_predicates, ignored_self_ty, ); let explicit_predicates = explicit_map.explicit_predicates_of(tcx, def_id); - for (outlives_predicate, &span) in &explicit_predicates.0 { + for (outlives_predicate, &span) in explicit_predicates.as_ref().skip_binder() { debug!("outlives_predicate = {:?}", &outlives_predicate); // Careful: If we are inferring the effects of a `dyn Trait<..>` @@ -275,10 +277,10 @@ fn check_explicit_predicates<'tcx>( // that is represented by the `dyn Trait`, not to the `X` type parameter // (or any other generic parameter) declared on `MyStruct`. // - // Note that we do this check for self **before** applying `substs`. In the - // case that `substs` come from a `dyn Trait` type, our caller will have + // Note that we do this check for self **before** applying `args`. In the + // case that `args` come from a `dyn Trait` type, our caller will have // included `Self = usize` as the value for `Self`. If we were - // to apply the substs, and not filter this predicate, we might then falsely + // to apply the args, and not filter this predicate, we might then falsely // conclude that e.g., `X: 'x` was a reasonable inferred requirement. // // Another similar case is where we have an inferred @@ -296,7 +298,7 @@ fn check_explicit_predicates<'tcx>( continue; } - let predicate = explicit_predicates.rebind(*outlives_predicate).subst(tcx, substs); + let predicate = explicit_predicates.rebind(*outlives_predicate).instantiate(tcx, args); debug!("predicate = {:?}", &predicate); insert_outlives_predicate(tcx, predicate.0, predicate.1, span, required_predicates); } diff --git a/compiler/rustc_hir_analysis/src/outlives/mod.rs b/compiler/rustc_hir_analysis/src/outlives/mod.rs index a8596c707f3a4..be9d076bd6eb0 100644 --- a/compiler/rustc_hir_analysis/src/outlives/mod.rs +++ b/compiler/rustc_hir_analysis/src/outlives/mod.rs @@ -2,8 +2,8 @@ use hir::Node; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_middle::query::Providers; -use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, CratePredicatesMap, TyCtxt}; +use rustc_middle::ty::GenericArgKind; +use rustc_middle::ty::{self, CratePredicatesMap, ToPredicate, TyCtxt}; use rustc_span::symbol::sym; use rustc_span::Span; @@ -20,7 +20,8 @@ pub fn provide(providers: &mut Providers) { fn inferred_outlives_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[(ty::Clause<'_>, Span)] { let id = tcx.hir().local_def_id_to_hir_id(item_def_id); - if matches!(tcx.def_kind(item_def_id), hir::def::DefKind::AnonConst) && tcx.lazy_normalization() + if matches!(tcx.def_kind(item_def_id), hir::def::DefKind::AnonConst) + && tcx.features().generic_const_exprs { if tcx.hir().opt_const_param_default_param_def_id(id).is_some() { // In `generics_of` we set the generics' parent to be our parent's parent which means that @@ -51,9 +52,9 @@ fn inferred_outlives_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[(ty::Clau if tcx.has_attr(item_def_id, sym::rustc_outlives) { let mut pred: Vec = predicates .iter() - .map(|(out_pred, _)| match out_pred { - ty::Clause::RegionOutlives(p) => p.to_string(), - ty::Clause::TypeOutlives(p) => p.to_string(), + .map(|(out_pred, _)| match out_pred.kind().skip_binder() { + ty::ClauseKind::RegionOutlives(p) => p.to_string(), + ty::ClauseKind::TypeOutlives(p) => p.to_string(), err => bug!("unexpected clause {:?}", err), }) .collect(); @@ -98,24 +99,29 @@ fn inferred_outlives_crate(tcx: TyCtxt<'_>, (): ()) -> CratePredicatesMap<'_> { let predicates = global_inferred_outlives .iter() .map(|(&def_id, set)| { - let predicates = &*tcx.arena.alloc_from_iter(set.0.iter().filter_map( - |(ty::OutlivesPredicate(kind1, region2), &span)| { - match kind1.unpack() { - GenericArgKind::Type(ty1) => Some(( - ty::Clause::TypeOutlives(ty::OutlivesPredicate(ty1, *region2)), - span, - )), - GenericArgKind::Lifetime(region1) => Some(( - ty::Clause::RegionOutlives(ty::OutlivesPredicate(region1, *region2)), - span, - )), - GenericArgKind::Const(_) => { - // Generic consts don't impose any constraints. - None + let predicates = + &*tcx.arena.alloc_from_iter(set.as_ref().skip_binder().iter().filter_map( + |(ty::OutlivesPredicate(kind1, region2), &span)| { + match kind1.unpack() { + GenericArgKind::Type(ty1) => Some(( + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty1, *region2)) + .to_predicate(tcx), + span, + )), + GenericArgKind::Lifetime(region1) => Some(( + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate( + region1, *region2, + )) + .to_predicate(tcx), + span, + )), + GenericArgKind::Const(_) => { + // Generic consts don't impose any constraints. + None + } } - } - }, - )); + }, + )); (def_id, predicates) }) .collect(); diff --git a/compiler/rustc_hir_analysis/src/outlives/utils.rs b/compiler/rustc_hir_analysis/src/outlives/utils.rs index c5c5f63a108b3..a6410c944f778 100644 --- a/compiler/rustc_hir_analysis/src/outlives/utils.rs +++ b/compiler/rustc_hir_analysis/src/outlives/utils.rs @@ -1,6 +1,6 @@ use rustc_infer::infer::outlives::components::{push_outlives_components, Component}; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; use rustc_middle::ty::{self, Region, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_span::Span; use smallvec::smallvec; use std::collections::BTreeMap; diff --git a/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs b/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs index ee3457282d331..61b182b1be782 100644 --- a/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs +++ b/compiler/rustc_hir_analysis/src/structured_errors/wrong_number_of_generic_args.rs @@ -360,9 +360,11 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { &[] }; ret.extend(params.iter().filter_map(|p| { - let hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } - = p.kind - else { return None }; + let hir::GenericParamKind::Lifetime { kind: hir::LifetimeParamKind::Explicit } = + p.kind + else { + return None; + }; let hir::ParamName::Plain(name) = p.name else { return None }; Some(name.to_string()) })); @@ -472,7 +474,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { verb ) } else { - format!("missing generics for {} `{}`", def_kind, def_path) + format!("missing generics for {def_kind} `{def_path}`") } } @@ -576,6 +578,9 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { MissingTypesOrConsts { .. } => { self.suggest_adding_type_and_const_args(err); } + ExcessTypesOrConsts { .. } => { + // this can happen with `~const T` where T isn't a const_trait. + } _ => unreachable!(), } } @@ -597,7 +602,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { let span = self.path_segment.ident.span; // insert a suggestion of the form "Y<'a, 'b>" - let sugg = format!("<{}>", suggested_args); + let sugg = format!("<{suggested_args}>"); debug!("sugg: {:?}", sugg); err.span_suggestion_verbose( @@ -622,7 +627,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { let sugg_suffix = if is_first && (has_non_lt_args || has_bindings) { ", " } else { "" }; - let sugg = format!("{}{}{}", sugg_prefix, suggested_args, sugg_suffix); + let sugg = format!("{sugg_prefix}{suggested_args}{sugg_suffix}"); debug!("sugg: {:?}", sugg); err.span_suggestion_verbose(sugg_span, msg, sugg, Applicability::HasPlaceholders); @@ -647,7 +652,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { let span = self.path_segment.ident.span; // insert a suggestion of the form "Y" - let sugg = format!("<{}>", suggested_args); + let sugg = format!("<{suggested_args}>"); debug!("sugg: {:?}", sugg); err.span_suggestion_verbose( @@ -680,7 +685,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { let sugg_suffix = if is_first && !self.gen_args.bindings.is_empty() { ", " } else { "" }; - let sugg = format!("{}{}{}", sugg_prefix, suggested_args, sugg_suffix); + let sugg = format!("{sugg_prefix}{suggested_args}{sugg_suffix}"); debug!("sugg: {:?}", sugg); err.span_suggestion_verbose(sugg_span, msg, sugg, Applicability::HasPlaceholders); @@ -793,29 +798,36 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { num_trait_generics_except_self: usize, ) { let sm = self.tcx.sess.source_map(); - let hir::ExprKind::MethodCall(_, rcvr, args, _) = expr.kind else { return; }; + let hir::ExprKind::MethodCall(_, rcvr, args, _) = expr.kind else { + return; + }; if num_assoc_fn_excess_args != num_trait_generics_except_self { return; } - let Some(gen_args) = self.gen_args.span_ext() else { return; }; - let Ok(generics) = sm.span_to_snippet(gen_args) else { return; }; - let Ok(rcvr) = sm.span_to_snippet( - rcvr.span.find_ancestor_inside(expr.span).unwrap_or(rcvr.span) - ) else { return; }; - let Ok(rest) = - (match args { - [] => Ok(String::new()), - [arg] => sm.span_to_snippet( - arg.span.find_ancestor_inside(expr.span).unwrap_or(arg.span), - ), - [first, .., last] => { - let first_span = - first.span.find_ancestor_inside(expr.span).unwrap_or(first.span); - let last_span = - last.span.find_ancestor_inside(expr.span).unwrap_or(last.span); - sm.span_to_snippet(first_span.to(last_span)) - } - }) else { return; }; + let Some(gen_args) = self.gen_args.span_ext() else { + return; + }; + let Ok(generics) = sm.span_to_snippet(gen_args) else { + return; + }; + let Ok(rcvr) = + sm.span_to_snippet(rcvr.span.find_ancestor_inside(expr.span).unwrap_or(rcvr.span)) + else { + return; + }; + let Ok(rest) = (match args { + [] => Ok(String::new()), + [arg] => { + sm.span_to_snippet(arg.span.find_ancestor_inside(expr.span).unwrap_or(arg.span)) + } + [first, .., last] => { + let first_span = first.span.find_ancestor_inside(expr.span).unwrap_or(first.span); + let last_span = last.span.find_ancestor_inside(expr.span).unwrap_or(last.span); + sm.span_to_snippet(first_span.to(last_span)) + } + }) else { + return; + }; let comma = if args.len() > 0 { ", " } else { "" }; let trait_path = self.tcx.def_path_str(trait_def_id); let method_name = self.tcx.item_name(self.def_id); @@ -1015,7 +1027,7 @@ impl<'a, 'tcx> WrongNumberOfGenericArgs<'a, 'tcx> { .collect::>() .join(", "); - format!(": {}", params) + format!(": {params}") }; format!( diff --git a/compiler/rustc_hir_analysis/src/variance/constraints.rs b/compiler/rustc_hir_analysis/src/variance/constraints.rs index 6f0afae1b4c68..8a40509d7cc5f 100644 --- a/compiler/rustc_hir_analysis/src/variance/constraints.rs +++ b/compiler/rustc_hir_analysis/src/variance/constraints.rs @@ -6,8 +6,8 @@ use hir::def_id::{DefId, LocalDefId}; use rustc_hir as hir; use rustc_hir::def::DefKind; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef}; use super::terms::VarianceTerm::*; use super::terms::*; @@ -78,6 +78,11 @@ pub fn add_constraints_from_crate<'a, 'tcx>( } } DefKind::Fn | DefKind::AssocFn => constraint_cx.build_constraints_for_item(def_id), + DefKind::TyAlias { lazy } + if lazy || tcx.type_of(def_id).instantiate_identity().has_opaque_types() => + { + constraint_cx.build_constraints_for_item(def_id) + } _ => {} } } @@ -101,7 +106,18 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { let inferred_start = self.terms_cx.inferred_starts[&def_id]; let current_item = &CurrentItem { inferred_start }; - match tcx.type_of(def_id).subst_identity().kind() { + let ty = tcx.type_of(def_id).instantiate_identity(); + + // The type as returned by `type_of` is the underlying type and generally not a weak projection. + // Therefore we need to check the `DefKind` first. + if let DefKind::TyAlias { lazy } = tcx.def_kind(def_id) + && (lazy || ty.has_opaque_types()) + { + self.add_constraints_from_ty(current_item, ty, self.covariant); + return; + } + + match ty.kind() { ty::Adt(def, _) => { // Not entirely obvious: constraints on structs/enums do not // affect the variance of their type parameters. See discussion @@ -112,7 +128,7 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { for field in def.all_fields() { self.add_constraints_from_ty( current_item, - tcx.type_of(field.did).subst_identity(), + tcx.type_of(field.did).instantiate_identity(), self.covariant, ); } @@ -121,12 +137,13 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { ty::FnDef(..) => { self.add_constraints_from_sig( current_item, - tcx.fn_sig(def_id).subst_identity(), + tcx.fn_sig(def_id).instantiate_identity(), self.covariant, ); } ty::Error(_) => {} + _ => { span_bug!( tcx.def_span(def_id), @@ -175,16 +192,16 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self, current))] - fn add_constraints_from_invariant_substs( + fn add_constraints_from_invariant_args( &mut self, current: &CurrentItem, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, variance: VarianceTermPtr<'a>, ) { // Trait are always invariant so we can take advantage of that. let variance_i = self.invariant(variance); - for k in substs { + for k in args { match k.unpack() { GenericArgKind::Lifetime(lt) => { self.add_constraints_from_region(current, lt, variance_i) @@ -248,12 +265,16 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { } } - ty::Adt(def, substs) => { - self.add_constraints_from_substs(current, def.did(), substs, variance); + ty::Adt(def, args) => { + self.add_constraints_from_args(current, def.did(), args, variance); + } + + ty::Alias(ty::Projection | ty::Inherent | ty::Opaque, ref data) => { + self.add_constraints_from_invariant_args(current, data.args, variance); } - ty::Alias(_, ref data) => { - self.add_constraints_from_invariant_substs(current, data.substs, variance); + ty::Alias(ty::Weak, ref data) => { + self.add_constraints_from_args(current, data.def_id, data.args, variance); } ty::Dynamic(data, r, _) => { @@ -261,9 +282,9 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { self.add_constraints_from_region(current, r, variance); if let Some(poly_trait_ref) = data.principal() { - self.add_constraints_from_invariant_substs( + self.add_constraints_from_invariant_args( current, - poly_trait_ref.skip_binder().substs, + poly_trait_ref.skip_binder().args, variance, ); } @@ -305,20 +326,20 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { /// Adds constraints appropriate for a nominal type (enum, struct, /// object, etc) appearing in a context with ambient variance `variance` - fn add_constraints_from_substs( + fn add_constraints_from_args( &mut self, current: &CurrentItem, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, variance: VarianceTermPtr<'a>, ) { debug!( - "add_constraints_from_substs(def_id={:?}, substs={:?}, variance={:?})", - def_id, substs, variance + "add_constraints_from_args(def_id={:?}, args={:?}, variance={:?})", + def_id, args, variance ); // We don't record `inferred_starts` entries for empty generics. - if substs.is_empty() { + if args.is_empty() { return; } @@ -328,7 +349,7 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { (None, Some(self.tcx().variances_of(def_id))) }; - for (i, k) in substs.iter().enumerate() { + for (i, k) in args.iter().enumerate() { let variance_decl = if let Some(InferredIndex(start)) = local { // Parameter on an item defined within current crate: // variance not yet inferred, so return a symbolic @@ -341,7 +362,7 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { }; let variance_i = self.xform(variance, variance_decl); debug!( - "add_constraints_from_substs: variance_decl={:?} variance_i={:?}", + "add_constraints_from_args: variance_decl={:?} variance_i={:?}", variance_decl, variance_i ); match k.unpack() { @@ -368,7 +389,7 @@ impl<'a, 'tcx> ConstraintContext<'a, 'tcx> { match &c.kind() { ty::ConstKind::Unevaluated(uv) => { - self.add_constraints_from_invariant_substs(current, uv.substs, variance); + self.add_constraints_from_invariant_args(current, uv.args, variance); } _ => {} } diff --git a/compiler/rustc_hir_analysis/src/variance/mod.rs b/compiler/rustc_hir_analysis/src/variance/mod.rs index 3ebd9e134bfa2..d91d9fcbc8e11 100644 --- a/compiler/rustc_hir_analysis/src/variance/mod.rs +++ b/compiler/rustc_hir_analysis/src/variance/mod.rs @@ -7,8 +7,8 @@ use rustc_arena::DroplessArena; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, CrateVariancesMap, SubstsRef, Ty, TyCtxt}; -use rustc_middle::ty::{TypeSuperVisitable, TypeVisitable}; +use rustc_middle::ty::{self, CrateVariancesMap, GenericArgsRef, Ty, TyCtxt}; +use rustc_middle::ty::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt}; use std::ops::ControlFlow; /// Defines the `TermsContext` basically houses an arena where we can @@ -51,20 +51,26 @@ fn variances_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Variance] { | DefKind::Struct | DefKind::Union | DefKind::Variant - | DefKind::Ctor(..) => {} - DefKind::OpaqueTy | DefKind::ImplTraitPlaceholder => { - return variance_of_opaque(tcx, item_def_id); + | DefKind::Ctor(..) => { + // These are inferred. + let crate_map = tcx.crate_variances(()); + return crate_map.variances.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]); + } + DefKind::TyAlias { lazy } + if lazy || tcx.type_of(item_def_id).instantiate_identity().has_opaque_types() => + { + // These are inferred. + let crate_map = tcx.crate_variances(()); + return crate_map.variances.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]); } - _ => { - // Variance not relevant. - span_bug!(tcx.def_span(item_def_id), "asked to compute variance for wrong kind of item") + DefKind::OpaqueTy => { + return variance_of_opaque(tcx, item_def_id); } + _ => {} } - // Everything else must be inferred. - - let crate_map = tcx.crate_variances(()); - crate_map.variances.get(&item_def_id.to_def_id()).copied().unwrap_or(&[]) + // Variance not relevant. + span_bug!(tcx.def_span(item_def_id), "asked to compute variance for wrong kind of item"); } #[instrument(level = "trace", skip(tcx), ret)] @@ -84,17 +90,17 @@ fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Varianc impl<'tcx> OpaqueTypeLifetimeCollector<'tcx> { #[instrument(level = "trace", skip(self), ret)] - fn visit_opaque(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) -> ControlFlow { + fn visit_opaque(&mut self, def_id: DefId, args: GenericArgsRef<'tcx>) -> ControlFlow { if def_id != self.root_def_id && self.tcx.is_descendant_of(def_id, self.root_def_id) { let child_variances = self.tcx.variances_of(def_id); - for (a, v) in substs.iter().zip(child_variances) { + for (a, v) in args.iter().zip(child_variances) { if *v != ty::Bivariant { a.visit_with(self)?; } } ControlFlow::Continue(()) } else { - substs.visit_with(self) + args.visit_with(self) } } } @@ -111,17 +117,10 @@ fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Varianc #[instrument(level = "trace", skip(self), ret)] fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { match t.kind() { - ty::Alias(_, ty::AliasTy { def_id, substs, .. }) + ty::Alias(_, ty::AliasTy { def_id, args, .. }) if matches!(self.tcx.def_kind(*def_id), DefKind::OpaqueTy) => { - self.visit_opaque(*def_id, substs) - } - // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty) check whether this is necessary - // at all for RPITITs. - ty::Alias(_, ty::AliasTy { def_id, substs, .. }) - if self.tcx.is_impl_trait_in_trait(*def_id) => - { - self.visit_opaque(*def_id, substs) + self.visit_opaque(*def_id, args) } _ => t.super_visit_with(self), } @@ -152,38 +151,34 @@ fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Varianc let mut collector = OpaqueTypeLifetimeCollector { tcx, root_def_id: item_def_id.to_def_id(), variances }; - let id_substs = ty::InternalSubsts::identity_for_item(tcx, item_def_id); - for (pred, _) in tcx.explicit_item_bounds(item_def_id).subst_iter_copied(tcx, id_substs) { + let id_args = ty::GenericArgs::identity_for_item(tcx, item_def_id); + for (pred, _) in tcx.explicit_item_bounds(item_def_id).iter_instantiated_copied(tcx, id_args) { debug!(?pred); - // We only ignore opaque type substs if the opaque type is the outermost type. + // We only ignore opaque type args if the opaque type is the outermost type. // The opaque type may be nested within itself via recursion in e.g. // type Foo<'a> = impl PartialEq>; // which thus mentions `'a` and should thus accept hidden types that borrow 'a // instead of requiring an additional `+ 'a`. match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { - trait_ref: ty::TraitRef { def_id: _, substs, .. }, - constness: _, + ty::ClauseKind::Trait(ty::TraitPredicate { + trait_ref: ty::TraitRef { def_id: _, args, .. }, polarity: _, - })) => { - for subst in &substs[1..] { + }) => { + for subst in &args[1..] { subst.visit_with(&mut collector); } } - ty::PredicateKind::Clause(ty::Clause::Projection(ty::ProjectionPredicate { - projection_ty: ty::AliasTy { substs, .. }, + ty::ClauseKind::Projection(ty::ProjectionPredicate { + projection_ty: ty::AliasTy { args, .. }, term, - })) => { - for subst in &substs[1..] { + }) => { + for subst in &args[1..] { subst.visit_with(&mut collector); } term.visit_with(&mut collector); } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - _, - region, - ))) => { + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_, region)) => { region.visit_with(&mut collector); } _ => { diff --git a/compiler/rustc_hir_analysis/src/variance/solve.rs b/compiler/rustc_hir_analysis/src/variance/solve.rs index c27c176e35f58..54da327702675 100644 --- a/compiler/rustc_hir_analysis/src/variance/solve.rs +++ b/compiler/rustc_hir_analysis/src/variance/solve.rs @@ -103,7 +103,7 @@ impl<'a, 'tcx> SolveContext<'a, 'tcx> { self.enforce_const_invariance(generics, variances); // Functions are permitted to have unused generic parameters: make those invariant. - if let ty::FnDef(..) = tcx.type_of(def_id).subst_identity().kind() { + if let ty::FnDef(..) = tcx.type_of(def_id).instantiate_identity().kind() { for variance in variances.iter_mut() { if *variance == ty::Bivariant { *variance = ty::Invariant; diff --git a/compiler/rustc_hir_analysis/src/variance/terms.rs b/compiler/rustc_hir_analysis/src/variance/terms.rs index 3b286bb9c93cf..1a8ec5f085333 100644 --- a/compiler/rustc_hir_analysis/src/variance/terms.rs +++ b/compiler/rustc_hir_analysis/src/variance/terms.rs @@ -12,7 +12,7 @@ use rustc_arena::DroplessArena; use rustc_hir::def::DefKind; use rustc_hir::def_id::{LocalDefId, LocalDefIdMap}; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; use std::fmt; use self::VarianceTerm::*; @@ -32,8 +32,8 @@ pub enum VarianceTerm<'a> { impl<'a> fmt::Debug for VarianceTerm<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - ConstantTerm(c1) => write!(f, "{:?}", c1), - TransformTerm(v1, v2) => write!(f, "({:?} \u{00D7} {:?})", v1, v2), + ConstantTerm(c1) => write!(f, "{c1:?}"), + TransformTerm(v1, v2) => write!(f, "({v1:?} \u{00D7} {v2:?})"), InferredTerm(id) => write!(f, "[{}]", { let InferredIndex(i) = id; i @@ -97,6 +97,11 @@ pub fn determine_parameters_to_be_inferred<'a, 'tcx>( } } DefKind::Fn | DefKind::AssocFn => terms_cx.add_inferreds_for_item(def_id), + DefKind::TyAlias { lazy } + if lazy || tcx.type_of(def_id).instantiate_identity().has_opaque_types() => + { + terms_cx.add_inferreds_for_item(def_id) + } _ => {} } } diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index d93e8efc1b59a..89efdc269c466 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -84,6 +84,7 @@ impl<'a> State<'a> { Node::ImplItem(a) => self.print_impl_item(a), Node::Variant(a) => self.print_variant(a), Node::AnonConst(a) => self.print_anon_const(a), + Node::ConstBlock(a) => self.print_inline_const(a), Node::Expr(a) => self.print_expr(a), Node::ExprField(a) => self.print_expr_field(&a), Node::Stmt(a) => self.print_stmt(a), @@ -419,12 +420,13 @@ impl<'a> State<'a> { fn print_associated_const( &mut self, ident: Ident, + generics: &hir::Generics<'_>, ty: &hir::Ty<'_>, default: Option, ) { - self.head(""); self.word_space("const"); self.print_ident(ident); + self.print_generic_params(generics.params); self.word_space(":"); self.print_type(ty); if let Some(expr) = default { @@ -432,6 +434,7 @@ impl<'a> State<'a> { self.word_space("="); self.ann.nested(self, Nested::Body(expr)); } + self.print_where_clause(generics); self.word(";") } @@ -531,9 +534,10 @@ impl<'a> State<'a> { self.word(";"); self.end(); // end the outer cbox } - hir::ItemKind::Const(ty, expr) => { + hir::ItemKind::Const(ty, generics, expr) => { self.head("const"); self.print_ident(item.ident); + self.print_generic_params(generics.params); self.word_space(":"); self.print_type(ty); self.space(); @@ -541,6 +545,7 @@ impl<'a> State<'a> { self.word_space("="); self.ann.nested(self, Nested::Body(expr)); + self.print_where_clause(generics); self.word(";"); self.end(); // end the outer cbox } @@ -621,7 +626,6 @@ impl<'a> State<'a> { unsafety, polarity, defaultness, - constness, defaultness_span: _, generics, ref of_trait, @@ -638,10 +642,6 @@ impl<'a> State<'a> { self.space(); } - if constness == hir::Constness::Const { - self.word_nbsp("const"); - } - if let hir::ImplPolarity::Negative(_) = polarity { self.word("!"); } @@ -835,7 +835,7 @@ impl<'a> State<'a> { self.print_outer_attributes(self.attrs(ti.hir_id())); match ti.kind { hir::TraitItemKind::Const(ty, default) => { - self.print_associated_const(ti.ident, ty, default); + self.print_associated_const(ti.ident, ti.generics, ty, default); } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(arg_names)) => { self.print_method_sig(ti.ident, sig, ti.generics, arg_names, None); @@ -864,7 +864,7 @@ impl<'a> State<'a> { match ii.kind { hir::ImplItemKind::Const(ty, expr) => { - self.print_associated_const(ii.ident, ty, Some(expr)); + self.print_associated_const(ii.ident, ii.generics, ty, Some(expr)); } hir::ImplItemKind::Fn(ref sig, body) => { self.head(""); @@ -1095,10 +1095,10 @@ impl<'a> State<'a> { self.end() } - fn print_expr_anon_const(&mut self, anon_const: &hir::AnonConst) { + fn print_inline_const(&mut self, constant: &hir::ConstBlock) { self.ibox(INDENT_UNIT); self.word_space("const"); - self.print_anon_const(anon_const); + self.ann.nested(self, Nested::Body(constant.body)); self.end() } @@ -1370,7 +1370,7 @@ impl<'a> State<'a> { self.print_expr_vec(exprs); } hir::ExprKind::ConstBlock(ref anon_const) => { - self.print_expr_anon_const(anon_const); + self.print_inline_const(anon_const); } hir::ExprKind::Repeat(element, ref count) => { self.print_expr_repeat(element, count); @@ -1521,7 +1521,7 @@ impl<'a> State<'a> { self.word("."); self.print_ident(ident); } - hir::ExprKind::Index(expr, index) => { + hir::ExprKind::Index(expr, index, _) => { self.print_expr_maybe_paren(expr, parser::PREC_POSTFIX); self.word("["); self.print_expr(index); @@ -1553,6 +1553,11 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(expr, parser::PREC_JUMP); } } + hir::ExprKind::Become(result) => { + self.word("become"); + self.word(" "); + self.print_expr_maybe_paren(result, parser::PREC_JUMP); + } hir::ExprKind::InlineAsm(asm) => { self.word("asm!"); self.print_inline_asm(asm); @@ -2409,7 +2414,7 @@ fn contains_exterior_struct_lit(value: &hir::Expr<'_>) -> bool { | hir::ExprKind::Cast(x, _) | hir::ExprKind::Type(x, _) | hir::ExprKind::Field(x, _) - | hir::ExprKind::Index(x, _) => { + | hir::ExprKind::Index(x, _, _) => { // `&X { y: 1 }, X { y: 1 }.y` contains_exterior_struct_lit(x) } diff --git a/compiler/rustc_hir_typeck/Cargo.toml b/compiler/rustc_hir_typeck/Cargo.toml index 13e1ea31c4d54..ce91d023a0ae8 100644 --- a/compiler/rustc_hir_typeck/Cargo.toml +++ b/compiler/rustc_hir_typeck/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } tracing = "0.1" rustc_ast = { path = "../rustc_ast" } +rustc_attr = { path = "../rustc_attr" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_graphviz = { path = "../rustc_graphviz" } diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl index 4a669e3f8b8a4..2281343e250b8 100644 --- a/compiler/rustc_hir_typeck/messages.ftl +++ b/compiler/rustc_hir_typeck/messages.ftl @@ -1,92 +1,102 @@ -hir_typeck_field_multiply_specified_in_initializer = - field `{$ident}` specified more than once - .label = used more than once - .previous_use_label = first use of `{$ident}` +hir_typeck_add_missing_parentheses_in_range = you must surround the range in parentheses to call its `{$func_name}` function -hir_typeck_functional_record_update_on_non_struct = - functional record update syntax requires a struct +hir_typeck_add_return_type_add = try adding a return type -hir_typeck_return_stmt_outside_of_fn_body = - return statement outside of function body - .encl_body_label = the return is part of this body... - .encl_fn_label = ...not the enclosing function body +hir_typeck_add_return_type_missing_here = a return type might be missing here -hir_typeck_yield_expr_outside_of_generator = - yield expression outside of generator literal +hir_typeck_address_of_temporary_taken = cannot take address of a temporary + .label = temporary value -hir_typeck_struct_expr_non_exhaustive = - cannot create non-exhaustive {$what} using struct expression +hir_typeck_arg_mismatch_indeterminate = argument type mismatch was detected, but rustc had trouble determining where + .note = we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new -hir_typeck_method_call_on_unknown_type = - the type of this value must be known to call a method on a raw pointer on it +hir_typeck_candidate_trait_note = `{$trait_name}` defines an item `{$item_name}`{$action_or_ty -> + [NONE] {""} + [implement] , perhaps you need to implement it + *[other] , perhaps you need to restrict type parameter `{$action_or_ty}` with it +} -hir_typeck_address_of_temporary_taken = cannot take address of a temporary - .label = temporary value +hir_typeck_const_select_must_be_const = this argument must be a `const fn` + .help = consult the documentation on `const_eval_select` for more information -hir_typeck_add_return_type_add = try adding a return type +hir_typeck_const_select_must_be_fn = this argument must be a function item + .note = expected a function item, found {$ty} + .help = consult the documentation on `const_eval_select` for more information -hir_typeck_add_return_type_missing_here = a return type might be missing here +hir_typeck_convert_to_str = try converting the passed type into a `&str` + +hir_typeck_convert_using_method = try using `{$sugg}` to convert `{$found}` to `{$expected}` + +hir_typeck_ctor_is_private = tuple struct constructor `{$def}` is private hir_typeck_expected_default_return_type = expected `()` because of default return type hir_typeck_expected_return_type = expected `{$expected}` because of return type -hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}` +hir_typeck_field_multiply_specified_in_initializer = + field `{$ident}` specified more than once + .label = used more than once + .previous_use_label = first use of `{$ident}` -hir_typeck_add_missing_parentheses_in_range = you must surround the range in parentheses to call its `{$func_name}` function +hir_typeck_fru_expr = this expression does not end in a comma... +hir_typeck_fru_expr2 = ... so this is interpreted as a `..` range expression, instead of functional record update syntax +hir_typeck_fru_note = this expression may have been misinterpreted as a `..` range expression +hir_typeck_fru_suggestion = + to set the remaining fields{$expr -> + [NONE]{""} + *[other] {" "}from `{$expr}` + }, separate the last named field with a comma -hir_typeck_lang_start_incorrect_number_params = incorrect number of parameters for the `start` lang item -hir_typeck_lang_start_incorrect_number_params_note_expected_count = the `start` lang item should have four parameters, but found {$found_param_count} +hir_typeck_functional_record_update_on_non_struct = + functional record update syntax requires a struct +hir_typeck_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml` +hir_typeck_help_set_edition_standalone = pass `--edition {$edition}` to `rustc` hir_typeck_lang_start_expected_sig_note = the `start` lang item should have the signature `fn(fn() -> T, isize, *const *const u8, u8) -> isize` +hir_typeck_lang_start_incorrect_number_params = incorrect number of parameters for the `start` lang item +hir_typeck_lang_start_incorrect_number_params_note_expected_count = the `start` lang item should have four parameters, but found {$found_param_count} + hir_typeck_lang_start_incorrect_param = parameter {$param_num} of the `start` lang item is incorrect .suggestion = change the type from `{$found_ty}` to `{$expected_ty}` hir_typeck_lang_start_incorrect_ret_ty = the return type of the `start` lang item is incorrect .suggestion = change the type from `{$found_ty}` to `{$expected_ty}` -hir_typeck_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml` -hir_typeck_help_set_edition_standalone = pass `--edition {$edition}` to `rustc` -hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang.org/edition-guide +hir_typeck_method_call_on_unknown_raw_pointee = + cannot call a method on a raw pointer with an unknown pointee type -hir_typeck_convert_to_str = try converting the passed type into a `&str` +hir_typeck_missing_parentheses_in_range = can't call method `{$method_name}` on type `{$ty_str}` -hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters +hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method -> + [true] {""} + *[other] {" "}in the current scope +} -hir_typeck_fru_note = this expression may have been misinterpreted as a `..` range expression -hir_typeck_fru_expr = this expression does not end in a comma... -hir_typeck_fru_expr2 = ... so this is interpreted as a `..` range expression, instead of functional record update syntax -hir_typeck_fru_suggestion = - to set the remaining fields{$expr -> - [NONE]{""} - *[other] {" "}from `{$expr}` - }, separate the last named field with a comma +hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang.org/edition-guide -hir_typeck_const_select_must_be_const = this argument must be a `const fn` - .help = consult the documentation on `const_eval_select` for more information +hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters -hir_typeck_const_select_must_be_fn = this argument must be a function item - .note = expected a function item, found {$ty} - .help = consult the documentation on `const_eval_select` for more information +hir_typeck_option_result_asref = use `{$def_path}::as_ref` to convert `{$expected_ty}` to `{$expr_ty}` +hir_typeck_option_result_cloned = use `{$def_path}::cloned` to clone the value inside the `{$def_path}` +hir_typeck_option_result_copied = use `{$def_path}::copied` to copy the value inside the `{$def_path}` -hir_typeck_union_pat_multiple_fields = union patterns should have exactly one field -hir_typeck_union_pat_dotdot = `..` cannot be used in union patterns +hir_typeck_return_stmt_outside_of_fn_body = + {$statement_kind} statement outside of function body + .encl_body_label = the {$statement_kind} is part of this body... + .encl_fn_label = ...not the enclosing function body -hir_typeck_arg_mismatch_indeterminate = argument type mismatch was detected, but rustc had trouble determining where - .note = we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new +hir_typeck_struct_expr_non_exhaustive = + cannot create non-exhaustive {$what} using struct expression hir_typeck_suggest_boxing_note = for more on the distinction between the stack and the heap, read https://doc.rust-lang.org/book/ch15-01-box.html, https://doc.rust-lang.org/rust-by-example/std/box.html, and https://doc.rust-lang.org/std/boxed/index.html hir_typeck_suggest_boxing_when_appropriate = store this in the heap by calling `Box::new` -hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method -> - [true] {""} - *[other] {" "}in the current scope -} +hir_typeck_suggest_ptr_null_mut = consider using `core::ptr::null_mut` instead -hir_typeck_candidate_trait_note = `{$trait_name}` defines an item `{$item_name}`{$action_or_ty -> - [NONE] {""} - [implement] , perhaps you need to implement it - *[other] , perhaps you need to restrict type parameter `{$action_or_ty}` with it -} +hir_typeck_union_pat_dotdot = `..` cannot be used in union patterns + +hir_typeck_union_pat_multiple_fields = union patterns should have exactly one field +hir_typeck_yield_expr_outside_of_generator = + yield expression outside of generator literal diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index 7d2f7e876083a..7ad9f51ba705d 100644 --- a/compiler/rustc_hir_typeck/src/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -41,7 +41,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // #55810: Type check patterns first so we get types for all bindings. let scrut_span = scrut.span.find_ancestor_inside(expr.span).unwrap_or(scrut.span); for arm in arms { - self.check_pat_top(&arm.pat, scrutinee_ty, Some(scrut_span), Some(scrut)); + self.check_pat_top(&arm.pat, scrutinee_ty, Some(scrut_span), Some(scrut), None); } // Now typecheck the blocks. @@ -65,7 +65,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // us to give better error messages (pointing to a usually better // arm for inconsistent arms or to the whole match when a `()` type // is required). - Expectation::ExpectHasType(ety) if ety != self.tcx.mk_unit() => ety, + Expectation::ExpectHasType(ety) if ety != Ty::new_unit(self.tcx) => ety, _ => self.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::MiscVariable, span: expr.span, @@ -107,7 +107,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (span, code) = match prior_arm { // The reason for the first arm to fail is not that the match arms diverge, // but rather that there's a prior obligation that doesn't hold. - None => (arm_span, ObligationCauseCode::BlockTailExpression(arm.body.hir_id)), + None => { + (arm_span, ObligationCauseCode::BlockTailExpression(arm.body.hir_id, match_src)) + } Some((prior_arm_block_id, prior_arm_ty, prior_arm_span)) => ( expr.span, ObligationCauseCode::MatchExpressionArm(Box::new(MatchExpressionArmCause { @@ -120,7 +122,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { scrut_span: scrut.span, source: match_src, prior_arms: other_arms.clone(), - scrut_hir_id: scrut.hir_id, opt_suggest_box_span, })), ), @@ -136,15 +137,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &cause, Some(&arm.body), arm_ty, - Some(&mut |err| { - self.suggest_removing_semicolon_for_coerce( - err, - expr, - orig_expected, - arm_ty, - prior_arm, - ) - }), + |err| self.suggest_removing_semicolon_for_coerce(err, expr, arm_ty, prior_arm), false, ); @@ -153,7 +146,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { other_arms.remove(0); } - prior_arm = Some((arm_block_id, arm_ty, arm_span)); + if !arm_ty.is_never() { + // When a match arm has type `!`, then it doesn't influence the expected type for + // the following arm. If all of the prior arms are `!`, then the influence comes + // from elsewhere and we shouldn't point to any previous arm. + prior_arm = Some((arm_block_id, arm_ty, arm_span)); + } } // If all of the arms in the `match` diverge, @@ -181,18 +179,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, diag: &mut Diagnostic, expr: &hir::Expr<'tcx>, - expectation: Expectation<'tcx>, arm_ty: Ty<'tcx>, prior_arm: Option<(Option, Ty<'tcx>, Span)>, ) { let hir = self.tcx.hir(); // First, check that we're actually in the tail of a function. - let Some(body_id) = hir.maybe_body_owned_by(self.body_id) else { return; }; + let Some(body_id) = hir.maybe_body_owned_by(self.body_id) else { + return; + }; let body = hir.body(body_id); - let hir::ExprKind::Block(block, _) = body.value.kind else { return; }; - let Some(hir::Stmt { kind: hir::StmtKind::Semi(last_expr), .. }) - = block.innermost_block().stmts.last() else { return; }; + let hir::ExprKind::Block(block, _) = body.value.kind else { + return; + }; + let Some(hir::Stmt { kind: hir::StmtKind::Semi(last_expr), span: semi_span, .. }) = + block.innermost_block().stmts.last() + else { + return; + }; if last_expr.hir_id != expr.hir_id { return; } @@ -201,8 +205,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some(ret) = hir .find_by_def_id(self.body_id) .and_then(|owner| owner.fn_decl()) - .map(|decl| decl.output.span()) else { return; }; - let Expectation::IsLast(stmt) = expectation else { + .map(|decl| decl.output.span()) + else { return; }; @@ -221,7 +225,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } - let semi_span = expr.span.shrink_to_hi().with_hi(stmt.hi()); + let semi_span = expr.span.shrink_to_hi().with_hi(semi_span.hi()); let mut ret_span: MultiSpan = semi_span.into(); ret_span.push_span_label( expr.span, @@ -269,7 +273,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { coercion.coerce_forced_unit( self, &cause, - &mut |err| { + |err| { if let Some((span, msg)) = &ret_reason { err.span_label(*span, msg.clone()); } else if let ExprKind::Block(block, _) = &then_expr.kind @@ -508,16 +512,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, kind: TypeVariableOriginKind::OpaqueTypeInference(rpit_def_id), .. - } = self.type_var_origin(expected)? else { return None; }; + } = self.type_var_origin(expected)? + else { + return None; + }; + + let Some(rpit_local_def_id) = rpit_def_id.as_local() else { + return None; + }; + if !matches!( + self.tcx.hir().expect_item(rpit_local_def_id).expect_opaque_ty().origin, + hir::OpaqueTyOrigin::FnReturn(..) + ) { + return None; + } let sig = self.body_fn_sig()?; - let substs = sig.output().walk().find_map(|arg| { + let args = sig.output().walk().find_map(|arg| { if let ty::GenericArgKind::Type(ty) = arg.unpack() - && let ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) = *ty.kind() + && let ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) = *ty.kind() && def_id == rpit_def_id { - Some(substs) + Some(args) } else { None } @@ -528,31 +545,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } for ty in [first_ty, second_ty] { - for (pred, _) in self + for (clause, _) in self .tcx .explicit_item_bounds(rpit_def_id) - .subst_iter_copied(self.tcx, substs) + .iter_instantiated_copied(self.tcx, args) { - let pred = pred.kind().rebind(match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) => { - // FIXME(rpitit): This will need to be fixed when we move to associated types + let pred = clause.kind().rebind(match clause.kind().skip_binder() { + ty::ClauseKind::Trait(trait_pred) => { assert!(matches!( *trait_pred.trait_ref.self_ty().kind(), - ty::Alias(_, ty::AliasTy { def_id, substs: alias_substs, .. }) - if def_id == rpit_def_id && substs == alias_substs + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args: alias_args, .. }) + if def_id == rpit_def_id && args == alias_args )); - ty::PredicateKind::Clause(ty::Clause::Trait( - trait_pred.with_self_ty(self.tcx, ty), - )) + ty::ClauseKind::Trait(trait_pred.with_self_ty(self.tcx, ty)) } - ty::PredicateKind::Clause(ty::Clause::Projection(mut proj_pred)) => { + ty::ClauseKind::Projection(mut proj_pred) => { assert!(matches!( *proj_pred.projection_ty.self_ty().kind(), - ty::Alias(_, ty::AliasTy { def_id, substs: alias_substs, .. }) - if def_id == rpit_def_id && substs == alias_substs + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args: alias_args, .. }) + if def_id == rpit_def_id && args == alias_args )); proj_pred = proj_pred.with_self_ty(self.tcx, ty); - ty::PredicateKind::Clause(ty::Clause::Projection(proj_pred)) + ty::ClauseKind::Projection(proj_pred) } _ => continue, }); diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 655ab94eb48d1..02371f85ac3b6 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -6,8 +6,9 @@ use crate::type_error_struct; use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::{struct_span_err, Applicability, Diagnostic, ErrorGuaranteed, StashKey}; use rustc_hir as hir; -use rustc_hir::def::{self, CtorKind, Namespace, Res}; +use rustc_hir::def::{self, CtorKind, DefKind, Namespace, Res}; use rustc_hir::def_id::DefId; +use rustc_hir::HirId; use rustc_hir_analysis::autoderef::Autoderef; use rustc_infer::{ infer, @@ -20,7 +21,7 @@ use rustc_infer::{ use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, }; -use rustc_middle::ty::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::{sym, Ident}; @@ -89,7 +90,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => self.check_expr(callee_expr), }; - let expr_ty = self.structurally_resolved_type(call_expr.span, original_callee_ty); + let expr_ty = self.structurally_resolve_type(call_expr.span, original_callee_ty); let mut autoderef = self.autoderef(callee_expr.span, expr_ty); let mut result = None; @@ -138,7 +139,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { autoderef: &Autoderef<'a, 'tcx>, ) -> Option> { let adjusted_ty = - self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); + self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false)); // If the callee is a bare function or a closure, then we're all set. match *adjusted_ty.kind() { @@ -148,14 +149,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Some(CallStep::Builtin(adjusted_ty)); } - ty::Closure(def_id, substs) => { + ty::Closure(def_id, args) => { let def_id = def_id.expect_local(); // Check whether this is a call to a closure where we // haven't yet decided on whether the closure is fn vs // fnmut vs fnonce. If so, we have to defer further processing. - if self.closure_kind(substs).is_none() { - let closure_sig = substs.as_closure().sig(); + if self.closure_kind(args).is_none() { + let closure_sig = args.as_closure().sig(); let closure_sig = self.instantiate_binder_with_fresh_vars( call_expr.span, infer::FnCall, @@ -170,7 +171,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { adjusted_ty, adjustments, fn_sig: closure_sig, - closure_substs: substs, + closure_args: args, }, ); return Some(CallStep::DeferredClosure(def_id, closure_sig)); @@ -232,12 +233,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some(trait_def_id) = opt_trait_def_id else { continue }; let opt_input_type = opt_arg_exprs.map(|arg_exprs| { - self.tcx.mk_tup_from_iter(arg_exprs.iter().map(|e| { - self.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeInference, - span: e.span, - }) - })) + Ty::new_tup_from_iter( + self.tcx, + arg_exprs.iter().map(|e| { + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span: e.span, + }) + }), + ) }); if let Some(ok) = self.lookup_method_in_trait( @@ -376,15 +380,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Expectation<'tcx>, ) -> Ty<'tcx> { let (fn_sig, def_id) = match *callee_ty.kind() { - ty::FnDef(def_id, subst) => { - let fn_sig = self.tcx.fn_sig(def_id).subst(self.tcx, subst); + ty::FnDef(def_id, args) => { + self.enforce_context_effects(call_expr.hir_id, call_expr.span, def_id, args); + let fn_sig = self.tcx.fn_sig(def_id).instantiate(self.tcx, args); // Unit testing: function items annotated with // `#[rustc_evaluate_where_clauses]` trigger special output // to let us test the trait evaluation system. if self.tcx.has_attr(def_id, sym::rustc_evaluate_where_clauses) { let predicates = self.tcx.predicates_of(def_id); - let predicates = predicates.instantiate(self.tcx, subst); + let predicates = predicates.instantiate(self.tcx, args); for (predicate, predicate_span) in predicates { let obligation = Obligation::new( self.tcx, @@ -397,7 +402,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .sess .struct_span_err( callee_expr.span, - format!("evaluate({:?}) = {:?}", predicate, result), + format!("evaluate({predicate:?}) = {result:?}"), ) .span_label(predicate_span, "predicate") .emit(); @@ -405,6 +410,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } (fn_sig, Some(def_id)) } + // FIXME(effects): these arms should error because we can't enforce them ty::FnPtr(sig) => (sig, None), _ => { for arg in arg_exprs { @@ -420,25 +426,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod) { // Try suggesting `foo(a)` -> `a.foo()` if possible. - if let Some(ty) = - self.suggest_call_as_method( - &mut diag, - segment, - arg_exprs, - call_expr, - expected - ) - { - diag.emit(); - return ty; - } else { - diag.emit(); - } + self.suggest_call_as_method( + &mut diag, + segment, + arg_exprs, + call_expr, + expected + ); + diag.emit(); } let err = self.report_invalid_callee(call_expr, callee_expr, callee_ty, arg_exprs); - return self.tcx.ty_error(err); + return Ty::new_error(self.tcx, err); } }; @@ -476,6 +476,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.require_lang_item(hir::LangItem::Tuple, Some(sp)), traits::ObligationCause::new(sp, self.body_id, traits::RustCall), ); + self.require_type_is_sized(ty, sp, traits::RustCall); } else { self.tcx.sess.span_err( sp, @@ -496,15 +497,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], call_expr: &'tcx hir::Expr<'tcx>, expected: Expectation<'tcx>, - ) -> Option> { + ) { if let [callee_expr, rest @ ..] = arg_exprs { - let callee_ty = self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr)?; + let Some(callee_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr) + else { + return; + }; // First, do a probe with `IsSuggestion(true)` to avoid emitting // any strange errors. If it's successful, then we'll do a true // method lookup. - let Ok(pick) = self - .lookup_probe_for_diagnostic( + let Ok(pick) = self.lookup_probe_for_diagnostic( segment.ident, callee_ty, call_expr, @@ -513,7 +516,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ProbeScope::AllTraits, expected.only_has_type(self), ) else { - return None; + return; }; let pick = self.confirm_method( @@ -525,11 +528,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { segment, ); if pick.illegal_sized_bound.is_some() { - return None; + return; } - let up_to_rcvr_span = segment.ident.span.until(callee_expr.span); - let rest_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi()); + let Some(callee_expr_span) = callee_expr.span.find_ancestor_inside(call_expr.span) + else { + return; + }; + let up_to_rcvr_span = segment.ident.span.until(callee_expr_span); + let rest_span = callee_expr_span.shrink_to_hi().to(call_expr.span.shrink_to_hi()); let rest_snippet = if let Some(first) = rest.first() { self.tcx .sess @@ -567,22 +574,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sugg, Applicability::MaybeIncorrect, ); - - // Let's check the method fully now - let return_ty = self.check_method_argument_types( - segment.ident.span, - call_expr, - Ok(pick.callee), - rest, - TupleArgumentsFlag::DontTupleArguments, - expected, - ); - - return Some(return_ty); } } - - None } fn report_invalid_callee( @@ -592,12 +585,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { callee_ty: Ty<'tcx>, arg_exprs: &'tcx [hir::Expr<'tcx>], ) -> ErrorGuaranteed { + // Callee probe fails when APIT references errors, so suppress those + // errors here. + if let Some((_, _, args)) = self.extract_callable_info(callee_ty) + && let Err(err) = args.error_reported() + { + return err; + } + let mut unit_variant = None; if let hir::ExprKind::Path(qpath) = &callee_expr.kind && let Res::Def(def::DefKind::Ctor(kind, CtorKind::Const), _) = self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) // Only suggest removing parens if there are no arguments && arg_exprs.is_empty() + && call_expr.span.contains(callee_expr.span) { let descr = match kind { def::CtorOf::Struct => "struct", @@ -644,18 +646,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) } hir::ExprKind::Call(ref inner_callee, _) => { - // If the call spans more than one line and the callee kind is - // itself another `ExprCall`, that's a clue that we might just be - // missing a semicolon (Issue #51055) - let call_is_multiline = self.tcx.sess.source_map().is_multiline(call_expr.span); - if call_is_multiline { - err.span_suggestion( - callee_expr.span.shrink_to_hi(), - "consider using a semicolon here", - ";", - Applicability::MaybeIncorrect, - ); - } if let hir::ExprKind::Path(ref inner_qpath) = inner_callee.kind { inner_callee_path = Some(inner_qpath); self.typeck_results.borrow().qpath_res(inner_qpath, inner_callee.hir_id) @@ -667,6 +657,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if !self.maybe_suggest_bad_array_definition(&mut err, call_expr, callee_expr) { + // If the call spans more than one line and the callee kind is + // itself another `ExprCall`, that's a clue that we might just be + // missing a semicolon (#51055, #106515). + let call_is_multiline = self + .tcx + .sess + .source_map() + .is_multiline(call_expr.span.with_lo(callee_expr.span.hi())) + && call_expr.span.ctxt() == callee_expr.span.ctxt(); + if call_is_multiline { + err.span_suggestion( + callee_expr.span.shrink_to_hi(), + "consider using a semicolon here to finish the statement", + ";", + Applicability::MaybeIncorrect, + ); + } if let Some((maybe_def, output_ty, _)) = self.extract_callable_info(callee_ty) && !self.type_is_sized_modulo_regions(self.param_env, output_ty) { @@ -756,6 +763,63 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.output() } + #[tracing::instrument(level = "debug", skip(self, span))] + pub(super) fn enforce_context_effects( + &self, + call_expr_hir: HirId, + span: Span, + callee_did: DefId, + callee_args: GenericArgsRef<'tcx>, + ) { + let tcx = self.tcx; + + // fast-reject if callee doesn't have the host effect param (non-const) + let generics = tcx.generics_of(callee_did); + let Some(host_effect_index) = generics.host_effect_index else { return }; + + // if the callee does have the param, we need to equate the param to some const + // value no matter whether the effects feature is enabled in the local crate, + // because inference will fail if we don't. + let mut host_always_on = + !tcx.features().effects || tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you; + + // Compute the constness required by the context. + let context = tcx.hir().enclosing_body_owner(call_expr_hir); + let const_context = tcx.hir().body_const_context(context); + + let kind = tcx.def_kind(context.to_def_id()); + debug_assert_ne!(kind, DefKind::ConstParam); + + if tcx.has_attr(context.to_def_id(), sym::rustc_do_not_const_check) { + trace!("do not const check this context"); + host_always_on = true; + } + + let effect = match const_context { + _ if host_always_on => tcx.consts.true_, + Some(hir::ConstContext::Static(_) | hir::ConstContext::Const) => tcx.consts.false_, + Some(hir::ConstContext::ConstFn) => { + let args = ty::GenericArgs::identity_for_item(tcx, context); + args.host_effect_param().expect("ConstContext::Maybe must have host effect param") + } + None => tcx.consts.true_, + }; + + trace!(?effect, ?generics, ?callee_args); + + let param = callee_args.const_at(host_effect_index); + let cause = self.misc(span); + match self.at(&cause, self.param_env).eq(infer::DefineOpaqueTypes::No, effect, param) { + Ok(infer::InferOk { obligations, value: () }) => { + self.register_predicates(obligations); + } + Err(e) => { + // FIXME(effects): better diagnostic + self.err_ctxt().report_mismatched_consts(&cause, effect, param, e).emit(); + } + } + } + fn confirm_overloaded_call( &self, call_expr: &'tcx hir::Expr<'tcx>, @@ -784,7 +848,7 @@ pub struct DeferredCallResolution<'tcx> { adjusted_ty: Ty<'tcx>, adjustments: Vec>, fn_sig: ty::FnSig<'tcx>, - closure_substs: SubstsRef<'tcx>, + closure_args: GenericArgsRef<'tcx>, } impl<'a, 'tcx> DeferredCallResolution<'tcx> { @@ -793,7 +857,7 @@ impl<'a, 'tcx> DeferredCallResolution<'tcx> { // we should not be invoked until the closure kind has been // determined by upvar inference - assert!(fcx.closure_kind(self.closure_substs).is_some()); + assert!(fcx.closure_kind(self.closure_args).is_some()); // We may now know enough to figure out fn vs fnmut etc. match fcx.try_overloaded_call_traits(self.call_expr, self.adjusted_ty, None) { diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 98c683f0200a3..31a03fabe4f20 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -103,15 +103,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(match *t.kind() { ty::Slice(_) | ty::Str => Some(PointerKind::Length), ty::Dynamic(ref tty, _, ty::Dyn) => Some(PointerKind::VTable(tty.principal_def_id())), - ty::Adt(def, substs) if def.is_struct() => { - match def.non_enum_variant().fields.raw.last() { - None => Some(PointerKind::Thin), - Some(f) => { - let field_ty = self.field_ty(span, f, substs); - self.pointer_kind(field_ty, span)? - } + ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() { + None => Some(PointerKind::Thin), + Some(f) => { + let field_ty = self.field_ty(span, f, args); + self.pointer_kind(field_ty, span)? } - } + }, ty::Tuple(fields) => match fields.last() { None => Some(PointerKind::Thin), Some(&f) => self.pointer_kind(f, span)?, @@ -146,7 +144,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let reported = self .tcx .sess - .delay_span_bug(span, format!("`{:?}` should be sized but is not?", t)); + .delay_span_bug(span, format!("`{t:?}` should be sized but is not?")); return Err(reported); } }) @@ -391,34 +389,26 @@ impl<'a, 'tcx> CastCheck<'tcx> { if let ty::Ref(reg, cast_ty, mutbl) = *self.cast_ty.kind() { if let ty::RawPtr(TypeAndMut { ty: expr_ty, .. }) = *self.expr_ty.kind() && fcx - .try_coerce( - self.expr, - fcx.tcx.mk_ref( + .can_coerce( + Ty::new_ref(fcx.tcx, fcx.tcx.lifetimes.re_erased, TypeAndMut { ty: expr_ty, mutbl }, ), self.cast_ty, - AllowTwoPhase::No, - None, ) - .is_ok() { sugg = Some((format!("&{}*", mutbl.prefix_str()), cast_ty == expr_ty)); } else if let ty::Ref(expr_reg, expr_ty, expr_mutbl) = *self.expr_ty.kind() && expr_mutbl == Mutability::Not && mutbl == Mutability::Mut && fcx - .try_coerce( - self.expr, - fcx.tcx.mk_ref( + .can_coerce( + Ty::new_ref(fcx.tcx, expr_reg, TypeAndMut { ty: expr_ty, mutbl: Mutability::Mut }, ), self.cast_ty, - AllowTwoPhase::No, - None, ) - .is_ok() { sugg_mutref = true; } @@ -426,30 +416,22 @@ impl<'a, 'tcx> CastCheck<'tcx> { if !sugg_mutref && sugg == None && fcx - .try_coerce( - self.expr, - fcx.tcx.mk_ref(reg, TypeAndMut { ty: self.expr_ty, mutbl }), + .can_coerce( + Ty::new_ref(fcx.tcx,reg, TypeAndMut { ty: self.expr_ty, mutbl }), self.cast_ty, - AllowTwoPhase::No, - None, ) - .is_ok() { sugg = Some((format!("&{}", mutbl.prefix_str()), false)); } } else if let ty::RawPtr(TypeAndMut { mutbl, .. }) = *self.cast_ty.kind() && fcx - .try_coerce( - self.expr, - fcx.tcx.mk_ref( + .can_coerce( + Ty::new_ref(fcx.tcx, fcx.tcx.lifetimes.re_erased, TypeAndMut { ty: self.expr_ty, mutbl }, ), self.cast_ty, - AllowTwoPhase::No, - None, ) - .is_ok() { sugg = Some((format!("&{}", mutbl.prefix_str()), false)); } @@ -646,12 +628,12 @@ impl<'a, 'tcx> CastCheck<'tcx> { err.span_suggestion( self.cast_span, "try casting to a reference instead", - format!("&{}{}", mtstr, s), + format!("&{mtstr}{s}"), Applicability::MachineApplicable, ); } Err(_) => { - let msg = format!("did you mean `&{}{}`?", mtstr, tstr); + let msg = format!("did you mean `&{mtstr}{tstr}`?"); err.span_help(self.cast_span, msg); } } @@ -689,8 +671,6 @@ impl<'a, 'tcx> CastCheck<'tcx> { fn trivial_cast_lint(&self, fcx: &FnCtxt<'a, 'tcx>) { let t_cast = self.cast_ty; let t_expr = self.expr_ty; - let type_asc_or = - if fcx.tcx.features().type_ascription { "type ascription or " } else { "" }; let (adjective, lint) = if t_cast.is_numeric() && t_expr.is_numeric() { ("numeric ", lint::builtin::TRIVIAL_NUMERIC_CASTS) } else { @@ -709,18 +689,18 @@ impl<'a, 'tcx> CastCheck<'tcx> { ) }), |lint| { - lint.help(format!( + lint.help( "cast can be replaced by coercion; this might \ - require {type_asc_or}a temporary variable" - )) + require a temporary variable", + ) }, ); } #[instrument(skip(fcx), level = "debug")] pub fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) { - self.expr_ty = fcx.structurally_resolved_type(self.expr_span, self.expr_ty); - self.cast_ty = fcx.structurally_resolved_type(self.cast_span, self.cast_ty); + self.expr_ty = fcx.structurally_resolve_type(self.expr_span, self.expr_ty); + self.cast_ty = fcx.structurally_resolve_type(self.cast_span, self.cast_ty); debug!("check_cast({}, {:?} as {:?})", self.expr.hir_id, self.expr_ty, self.cast_ty); @@ -764,10 +744,10 @@ impl<'a, 'tcx> CastCheck<'tcx> { ty::FnDef(..) => { // Attempt a coercion to a fn pointer type. let f = fcx.normalize(self.expr_span, self.expr_ty.fn_sig(fcx.tcx)); - let res = fcx.try_coerce( + let res = fcx.coerce( self.expr, self.expr_ty, - fcx.tcx.mk_fn_ptr(f), + Ty::new_fn_ptr(fcx.tcx, f), AllowTwoPhase::No, None, ); @@ -864,7 +844,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { (_, DynStar) => { if fcx.tcx.features().dyn_star { - bug!("should be handled by `try_coerce`") + bug!("should be handled by `coerce`") } else { Err(CastError::IllegalCast) } @@ -959,8 +939,8 @@ impl<'a, 'tcx> CastCheck<'tcx> { // from a region pointer to a vector. // Coerce to a raw pointer so that we generate AddressOf in MIR. - let array_ptr_type = fcx.tcx.mk_ptr(m_expr); - fcx.try_coerce(self.expr, self.expr_ty, array_ptr_type, AllowTwoPhase::No, None) + let array_ptr_type = Ty::new_ptr(fcx.tcx, m_expr); + fcx.coerce(self.expr, self.expr_ty, array_ptr_type, AllowTwoPhase::No, None) .unwrap_or_else(|_| { bug!( "could not cast from reference to array to pointer to array ({:?} to {:?})", @@ -996,7 +976,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { } fn try_coercion_cast(&self, fcx: &FnCtxt<'a, 'tcx>) -> Result<(), ty::error::TypeError<'tcx>> { - match fcx.try_coerce(self.expr, self.expr_ty, self.cast_ty, AllowTwoPhase::No, None) { + match fcx.coerce(self.expr, self.expr_ty, self.cast_ty, AllowTwoPhase::No, None) { Ok(_) => Ok(()), Err(err) => Err(err), } diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs index bfabd44bb5792..1fc1e5aca2b3c 100644 --- a/compiler/rustc_hir_typeck/src/check.rs +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -62,11 +62,11 @@ pub(super) fn check_fn<'a, 'tcx>( fcx.require_type_is_sized(yield_ty, span, traits::SizedYieldType); yield_ty } else { - tcx.mk_unit() + Ty::new_unit(tcx,) }; // Resume type defaults to `()` if the generator has no argument. - let resume_ty = fn_sig.inputs().get(0).copied().unwrap_or_else(|| tcx.mk_unit()); + let resume_ty = fn_sig.inputs().get(0).copied().unwrap_or_else(|| Ty::new_unit(tcx,)); fcx.resume_yield_tys = Some((resume_ty, yield_ty)); } @@ -80,7 +80,7 @@ pub(super) fn check_fn<'a, 'tcx>( let va_list_did = tcx.require_lang_item(LangItem::VaList, Some(span)); let region = fcx.next_region_var(RegionVariableOrigin::MiscVariable(span)); - tcx.type_of(va_list_did).subst(tcx, &[region.into()]) + tcx.type_of(va_list_did).instantiate(tcx, &[region.into()]) }); // Add formal parameters. @@ -89,14 +89,23 @@ pub(super) fn check_fn<'a, 'tcx>( for (idx, (param_ty, param)) in inputs_fn.chain(maybe_va_list).zip(body.params).enumerate() { // Check the pattern. let ty_span = try { inputs_hir?.get(idx)?.span }; - fcx.check_pat_top(¶m.pat, param_ty, ty_span, None); + fcx.check_pat_top(¶m.pat, param_ty, ty_span, None, None); // Check that argument is Sized. - // The check for a non-trivial pattern is a hack to avoid duplicate warnings - // for simple cases like `fn foo(x: Trait)`, - // where we would error once on the parameter as a whole, and once on the binding `x`. - if param.pat.simple_ident().is_none() && !params_can_be_unsized { - fcx.require_type_is_sized(param_ty, param.pat.span, traits::SizedArgumentType(ty_span)); + if !params_can_be_unsized { + fcx.require_type_is_sized( + param_ty, + param.pat.span, + // ty_span == binding_span iff this is a closure parameter with no type ascription, + // or if it's an implicit `self` parameter + traits::SizedArgumentType( + if ty_span == Some(param.span) && tcx.is_closure(fn_def_id.into()) { + None + } else { + ty_span + }, + ), + ); } fcx.write_ty(param.hir_id, param_ty); @@ -247,10 +256,10 @@ fn check_lang_start_fn<'tcx>( // for example `start`'s generic should be a type parameter let generics = tcx.generics_of(def_id); let fn_generic = generics.param_at(0, tcx); - let generic_ty = tcx.mk_ty_param(fn_generic.index, fn_generic.name); + let generic_ty = Ty::new_param(tcx, fn_generic.index, fn_generic.name); let expected_fn_sig = tcx.mk_fn_sig([], generic_ty, false, hir::Unsafety::Normal, Abi::Rust); - let expected_ty = tcx.mk_fn_ptr(Binder::dummy(expected_fn_sig)); + let expected_ty = Ty::new_fn_ptr(tcx, Binder::dummy(expected_fn_sig)); // we emit the same error to suggest changing the arg no matter what's wrong with the arg let emit_main_fn_arg_err = || { @@ -307,9 +316,9 @@ fn check_lang_start_fn<'tcx>( if !argv_is_okay { let inner_ptr_ty = - tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: tcx.types.u8 }); + Ty::new_ptr(tcx, ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: tcx.types.u8 }); let expected_ty = - tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: inner_ptr_ty }); + Ty::new_ptr(tcx, ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: inner_ptr_ty }); tcx.sess.emit_err(LangStartIncorrectParam { param_span: decl.inputs[2].span, param_num: 3, diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs index 9659a0ec13d40..b19fb6da6defd 100644 --- a/compiler/rustc_hir_typeck/src/closure.rs +++ b/compiler/rustc_hir_typeck/src/closure.rs @@ -10,8 +10,8 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::{DefineOpaqueTypes, LateBoundRegionConversionTime}; use rustc_infer::infer::{InferOk, InferResult}; use rustc_macros::{TypeFoldable, TypeVisitable}; -use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor}; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; @@ -81,7 +81,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!(?bound_sig, ?liberated_sig); - let mut fcx = FnCtxt::new(self, self.param_env.without_const(), closure.def_id); + let mut fcx = FnCtxt::new(self, self.param_env, closure.def_id); let generator_types = check_fn( &mut fcx, liberated_sig, @@ -93,22 +93,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false, ); - let parent_substs = InternalSubsts::identity_for_item( + let parent_args = GenericArgs::identity_for_item( self.tcx, self.tcx.typeck_root_def_id(expr_def_id.to_def_id()), ); - let tupled_upvars_ty = self.next_ty_var(TypeVariableOrigin { + let tupled_upvars_ty = self.next_root_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::ClosureSynthetic, span: self.tcx.def_span(expr_def_id), }); if let Some(GeneratorTypes { resume_ty, yield_ty, interior, movability }) = generator_types { - let generator_substs = ty::GeneratorSubsts::new( + let generator_args = ty::GeneratorArgs::new( self.tcx, - ty::GeneratorSubstsParts { - parent_substs, + ty::GeneratorArgsParts { + parent_args, resume_ty, yield_ty, return_ty: liberated_sig.output(), @@ -117,9 +117,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }, ); - return self.tcx.mk_generator( + return Ty::new_generator( + self.tcx, expr_def_id.to_def_id(), - generator_substs.substs, + generator_args.args, movability, ); } @@ -128,7 +129,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // the `closures` table. let sig = bound_sig.map_bound(|sig| { self.tcx.mk_fn_sig( - [self.tcx.mk_tup(sig.inputs())], + [Ty::new_tup(self.tcx, sig.inputs())], sig.output(), sig.c_variadic, sig.unsafety, @@ -143,24 +144,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Create a type variable (for now) to represent the closure kind. // It will be unified during the upvar inference phase (`upvar.rs`) - None => self.next_ty_var(TypeVariableOrigin { + None => self.next_root_ty_var(TypeVariableOrigin { // FIXME(eddyb) distinguish closure kind inference variables from the rest. kind: TypeVariableOriginKind::ClosureSynthetic, span: expr_span, }), }; - let closure_substs = ty::ClosureSubsts::new( + let closure_args = ty::ClosureArgs::new( self.tcx, - ty::ClosureSubstsParts { - parent_substs, + ty::ClosureArgsParts { + parent_args, closure_kind_ty, - closure_sig_as_fn_ptr_ty: self.tcx.mk_fn_ptr(sig), + closure_sig_as_fn_ptr_ty: Ty::new_fn_ptr(self.tcx, sig), tupled_upvars_ty, }, ); - self.tcx.mk_closure(expr_def_id.to_def_id(), closure_substs.substs) + Ty::new_closure(self.tcx, expr_def_id.to_def_id(), closure_args.args) } /// Given the expected type, figures out what it can about this closure we @@ -171,10 +172,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected_ty: Ty<'tcx>, ) -> (Option>, Option) { match *expected_ty.kind() { - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => self + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => self .deduce_closure_signature_from_predicates( expected_ty, - self.tcx.explicit_item_bounds(def_id).subst_iter_copied(self.tcx, substs), + self.tcx + .explicit_item_bounds(def_id) + .iter_instantiated_copied(self.tcx, args) + .map(|(c, s)| (c.as_predicate(), s)), ), ty::Dynamic(ref object_type, ..) => { let sig = object_type.projection_bounds().find_map(|pb| { @@ -187,7 +191,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { (sig, kind) } ty::Infer(ty::TyVar(vid)) => self.deduce_closure_signature_from_predicates( - self.tcx.mk_ty_var(self.root_var(vid)), + Ty::new_var(self.tcx, self.root_var(vid)), self.obligations_for_self_ty(vid).map(|obl| (obl.predicate, obl.cause.span)), ), ty::FnPtr(sig) => { @@ -222,7 +226,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Given a Projection predicate, we can potentially infer // the complete signature. if expected_sig.is_none() - && let ty::PredicateKind::Clause(ty::Clause::Projection(proj_predicate)) = bound_predicate.skip_binder() + && let ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj_predicate)) = bound_predicate.skip_binder() { let inferred_sig = self.normalize( span, @@ -258,10 +262,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // like `F : Fn`. Note that due to subtyping we could encounter // many viable options, so pick the most restrictive. let trait_def_id = match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => { Some(data.projection_ty.trait_def_id(self.tcx)) } - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => Some(data.def_id()), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => Some(data.def_id()), _ => None, }; if let Some(closure_kind) = @@ -311,7 +315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let input_tys = if is_fn { - let arg_param_ty = projection.skip_binder().projection_ty.substs.type_at(1); + let arg_param_ty = projection.skip_binder().projection_ty.args.type_at(1); let arg_param_ty = self.resolve_vars_if_possible(arg_param_ty); debug!(?arg_param_ty); @@ -695,7 +699,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // where R is the return type we are expecting. This type `T` // will be our output. let bound_predicate = predicate.kind(); - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj_predicate)) = + if let ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj_predicate)) = bound_predicate.skip_binder() { self.deduce_future_output_from_projection( @@ -707,38 +711,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; + let span = self.tcx.def_span(expr_def_id); + let output_ty = match *ret_ty.kind() { ty::Infer(ty::TyVar(ret_vid)) => { self.obligations_for_self_ty(ret_vid).find_map(|obligation| { get_future_output(obligation.predicate, obligation.cause.span) })? } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => self + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => self .tcx .explicit_item_bounds(def_id) - .subst_iter_copied(self.tcx, substs) - .find_map(|(p, s)| get_future_output(p, s))?, + .iter_instantiated_copied(self.tcx, args) + .find_map(|(p, s)| get_future_output(p.as_predicate(), s))?, ty::Error(_) => return None, - ty::Alias(ty::Projection, proj) if self.tcx.is_impl_trait_in_trait(proj.def_id) => self - .tcx - .explicit_item_bounds(proj.def_id) - .subst_iter_copied(self.tcx, proj.substs) - .find_map(|(p, s)| get_future_output(p, s))?, _ => span_bug!( - self.tcx.def_span(expr_def_id), + span, "async fn generator return type not an inference variable: {ret_ty}" ), }; + let output_ty = self.normalize(span, output_ty); + // async fn that have opaque types in their return type need to redo the conversion to inference variables // as they fetch the still opaque version from the signature. let InferOk { value: output_ty, obligations } = self - .replace_opaque_types_with_inference_vars( - output_ty, - body_def_id, - self.tcx.def_span(expr_def_id), - self.param_env, - ); + .replace_opaque_types_with_inference_vars(output_ty, body_def_id, span, self.param_env); self.register_predicates(obligations); Some(output_ty) @@ -796,14 +794,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Converts the types that the user supplied, in case that doing /// so should yield an error, but returns back a signature where - /// all parameters are of type `TyErr`. + /// all parameters are of type `ty::Error`. fn error_sig_of_closure( &self, decl: &hir::FnDecl<'_>, guar: ErrorGuaranteed, ) -> ty::PolyFnSig<'tcx> { let astconv: &dyn AstConv<'_> = self; - let err_ty = self.tcx.ty_error(guar); + let err_ty = Ty::new_error(self.tcx, guar); let supplied_arguments = decl.inputs.iter().map(|a| { // Convert the types that the user supplied (if any), but ignore them. diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 08c4082e885d9..fca675ea9d80e 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -36,9 +36,7 @@ //! ``` use crate::FnCtxt; -use rustc_errors::{ - struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, -}; +use rustc_errors::{struct_span_err, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{self, Visitor}; @@ -48,20 +46,22 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi use rustc_infer::infer::{Coercion, DefineOpaqueTypes, InferOk, InferResult}; use rustc_infer::traits::{Obligation, PredicateObligation}; use rustc_middle::lint::in_external_macro; +use rustc_middle::traits::BuiltinImplSource; use rustc_middle::ty::adjustment::{ - Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCast, + Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability, PointerCoercion, }; use rustc_middle::ty::error::TypeError; use rustc_middle::ty::relate::RelateResult; -use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::visit::TypeVisitableExt; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Ty, TypeAndMut}; use rustc_session::parse::feature_err; use rustc_span::symbol::sym; -use rustc_span::{self, BytePos, DesugaringKind, Span}; +use rustc_span::{self, DesugaringKind}; use rustc_target::spec::abi::Abi; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{ self, NormalizeExt, ObligationCause, ObligationCauseCode, ObligationCtxt, }; @@ -144,12 +144,28 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { debug!("unify(a: {:?}, b: {:?}, use_lub: {})", a, b, self.use_lub); self.commit_if_ok(|_| { let at = self.at(&self.cause, self.fcx.param_env); - if self.use_lub { + + let res = if self.use_lub { at.lub(DefineOpaqueTypes::Yes, b, a) } else { at.sup(DefineOpaqueTypes::Yes, b, a) .map(|InferOk { value: (), obligations }| InferOk { value: a, obligations }) + }; + + // In the new solver, lazy norm may allow us to shallowly equate + // more types, but we emit possibly impossible-to-satisfy obligations. + // Filter these cases out to make sure our coercion is more accurate. + if self.next_trait_solver() { + if let Ok(res) = &res { + for obligation in &res.obligations { + if !self.predicate_may_hold(&obligation) { + return Err(TypeError::Mismatch); + } + } + } } + + res }) } @@ -177,7 +193,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { let _ = self.commit_if_ok(|_| { self.at(&self.cause, self.param_env).eq(DefineOpaqueTypes::Yes, a, b) }); - return success(vec![], self.fcx.tcx.ty_error(guar), vec![]); + return success(vec![], Ty::new_error(self.fcx.tcx, guar), vec![]); } // Coercing from `!` to any type is allowed: @@ -236,11 +252,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // unsafe qualifier. self.coerce_from_fn_pointer(a, a_f, b) } - ty::Closure(closure_def_id_a, substs_a) => { + ty::Closure(closure_def_id_a, args_a) => { // Non-capturing closures are coercible to // function pointers or unsafe function pointers. // It cannot convert closures that require unsafe. - self.coerce_closure_to_fn(a, closure_def_id_a, substs_a, b) + self.coerce_closure_to_fn(a, closure_def_id_a, args_a, b) } _ => { // Otherwise, just use unification rules. @@ -425,7 +441,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { } r_borrow_var.unwrap() }; - let derefd_ty_a = self.tcx.mk_ref( + let derefd_ty_a = Ty::new_ref( + self.tcx, r, TypeAndMut { ty: referent_ty, @@ -493,9 +510,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { success(adjustments, ty, obligations) } - // &[T; n] or &mut [T; n] -> &[T] - // or &mut [T; n] -> &mut [T] - // or &Concrete -> &Trait, etc. + /// Performs [unsized coercion] by emulating a fulfillment loop on a + /// `CoerceUnsized` goal until all `CoerceUnsized` and `Unsize` goals + /// are successfully selected. + /// + /// [unsized coercion](https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions) #[instrument(skip(self), level = "debug")] fn coerce_unsized(&self, mut source: Ty<'tcx>, mut target: Ty<'tcx>) -> CoerceResult<'tcx> { source = self.shallow_resolve(source); @@ -543,9 +562,11 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { Adjustment { kind: Adjust::Deref(None), target: ty_a }, Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(r_borrow, mutbl)), - target: self - .tcx - .mk_ref(r_borrow, ty::TypeAndMut { mutbl: mutbl_b, ty: ty_a }), + target: Ty::new_ref( + self.tcx, + r_borrow, + ty::TypeAndMut { mutbl: mutbl_b, ty: ty_a }, + ), }, )) } @@ -556,7 +577,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { Adjustment { kind: Adjust::Deref(None), target: ty_a }, Adjustment { kind: Adjust::Borrow(AutoBorrow::RawPtr(mt_b)), - target: self.tcx.mk_ptr(ty::TypeAndMut { mutbl: mt_b, ty: ty_a }), + target: Ty::new_ptr(self.tcx, ty::TypeAndMut { mutbl: mt_b, ty: ty_a }), }, )) } @@ -574,7 +595,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { }; let coerce_target = self.next_ty_var(origin); let mut coercion = self.unify_and(coerce_target, target, |target| { - let unsize = Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target }; + let unsize = Adjustment { kind: Adjust::Pointer(PointerCoercion::Unsize), target }; match reborrow { None => vec![unsize], Some((ref deref, ref autoref)) => vec![deref.clone(), autoref.clone(), unsize], @@ -614,40 +635,24 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { while !queue.is_empty() { let obligation = queue.remove(0); debug!("coerce_unsized resolve step: {:?}", obligation); - let bound_predicate = obligation.predicate.kind(); - let trait_pred = match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) + let trait_pred = match obligation.predicate.kind().no_bound_vars() { + Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred))) if traits.contains(&trait_pred.def_id()) => { - if unsize_did == trait_pred.def_id() { - let self_ty = trait_pred.self_ty(); - let unsize_ty = trait_pred.trait_ref.substs[1].expect_ty(); - if let (ty::Dynamic(ref data_a, ..), ty::Dynamic(ref data_b, ..)) = - (self_ty.kind(), unsize_ty.kind()) - && data_a.principal_def_id() != data_b.principal_def_id() - { - debug!("coerce_unsized: found trait upcasting coercion"); - has_trait_upcasting_coercion = Some((self_ty, unsize_ty)); - } - if let ty::Tuple(..) = unsize_ty.kind() { - debug!("coerce_unsized: found unsized tuple coercion"); - has_unsized_tuple_coercion = true; - } - } - bound_predicate.rebind(trait_pred) + trait_pred } _ => { coercion.obligations.push(obligation); continue; } }; + let trait_pred = self.resolve_vars_if_possible(trait_pred); match selcx.select(&obligation.with(selcx.tcx(), trait_pred)) { // Uncertain or unimplemented. Ok(None) => { if trait_pred.def_id() == unsize_did { - let trait_pred = self.resolve_vars_if_possible(trait_pred); - let self_ty = trait_pred.skip_binder().self_ty(); - let unsize_ty = trait_pred.skip_binder().trait_ref.substs[1].expect_ty(); + let self_ty = trait_pred.self_ty(); + let unsize_ty = trait_pred.trait_ref.args[1].expect_ty(); debug!("coerce_unsized: ambiguous unsize case for {:?}", trait_pred); match (self_ty.kind(), unsize_ty.kind()) { (&ty::Infer(ty::TyVar(v)), ty::Dynamic(..)) @@ -684,20 +689,28 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // be silent, as it causes a type mismatch later. } - Ok(Some(impl_source)) => queue.extend(impl_source.nested_obligations()), + Ok(Some(impl_source)) => { + // Some builtin coercions are still unstable so we detect + // these here and emit a feature error if coercion doesn't fail + // due to another reason. + match impl_source { + traits::ImplSource::Builtin( + BuiltinImplSource::TraitUpcasting { .. }, + _, + ) => { + has_trait_upcasting_coercion = + Some((trait_pred.self_ty(), trait_pred.trait_ref.args.type_at(1))); + } + traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => { + has_unsized_tuple_coercion = true; + } + _ => {} + } + queue.extend(impl_source.nested_obligations()) + } } } - if has_unsized_tuple_coercion && !self.tcx.features().unsized_tuple_coercion { - feature_err( - &self.tcx.sess.parse_sess, - sym::unsized_tuple_coercion, - self.cause.span, - "unsized tuple coercion is not stable enough for use and is subject to change", - ) - .emit(); - } - if let Some((sub, sup)) = has_trait_upcasting_coercion && !self.tcx().features().trait_upcasting { @@ -713,6 +726,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { err.emit(); } + if has_unsized_tuple_coercion && !self.tcx.features().unsized_tuple_coercion { + feature_err( + &self.tcx.sess.parse_sess, + sym::unsized_tuple_coercion, + self.cause.span, + "unsized tuple coercion is not stable enough for use and is subject to change", + ) + .emit(); + } + Ok(coercion) } @@ -752,7 +775,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { self.tcx, self.cause.clone(), self.param_env, - ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::TypeOutlives( + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives( ty::OutlivesPredicate(a, b_region), ))), ), @@ -791,6 +814,8 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { G: FnOnce(Ty<'tcx>) -> Vec>, { self.commit_if_ok(|snapshot| { + let outer_universe = self.infcx.universe(); + let result = if let ty::FnPtr(fn_ty_b) = b.kind() && let (hir::Unsafety::Normal, hir::Unsafety::Unsafe) = (fn_ty_a.unsafety(), fn_ty_b.unsafety()) @@ -807,7 +832,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // want the coerced type to be the actual supertype of these two, // but for now, we want to just error to ensure we don't lock // ourselves into a specific behavior with NLL. - self.leak_check(false, snapshot)?; + self.leak_check(outer_universe, Some(snapshot))?; result }) @@ -830,7 +855,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { a, fn_ty_a, b, - simple(Adjust::Pointer(PointerCast::UnsafeFnPointer)), + simple(Adjust::Pointer(PointerCoercion::UnsafeFnPointer)), identity, ) } @@ -866,7 +891,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { self.at(&self.cause, self.param_env).normalize(a_sig); obligations.extend(o1); - let a_fn_pointer = self.tcx.mk_fn_ptr(a_sig); + let a_fn_pointer = Ty::new_fn_ptr(self.tcx, a_sig); let InferOk { value, obligations: o2 } = self.coerce_from_safe_fn( a_fn_pointer, a_sig, @@ -874,16 +899,16 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { |unsafe_ty| { vec![ Adjustment { - kind: Adjust::Pointer(PointerCast::ReifyFnPointer), + kind: Adjust::Pointer(PointerCoercion::ReifyFnPointer), target: a_fn_pointer, }, Adjustment { - kind: Adjust::Pointer(PointerCast::UnsafeFnPointer), + kind: Adjust::Pointer(PointerCoercion::UnsafeFnPointer), target: unsafe_ty, }, ] }, - simple(Adjust::Pointer(PointerCast::ReifyFnPointer)), + simple(Adjust::Pointer(PointerCoercion::ReifyFnPointer)), )?; obligations.extend(o2); @@ -897,7 +922,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { &self, a: Ty<'tcx>, closure_def_id_a: DefId, - substs_a: SubstsRef<'tcx>, + args_a: GenericArgsRef<'tcx>, b: Ty<'tcx>, ) -> CoerceResult<'tcx> { //! Attempts to coerce from the type of a non-capturing closure @@ -908,7 +933,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { match b.kind() { // At this point we haven't done capture analysis, which means - // that the ClosureSubsts just contains an inference variable instead + // that the ClosureArgs just contains an inference variable instead // of tuple of captured types. // // All we care here is if any variable is being captured and not the exact paths, @@ -925,15 +950,15 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { // `fn(arg0,arg1,...) -> _` // or // `unsafe fn(arg0,arg1,...) -> _` - let closure_sig = substs_a.as_closure().sig(); + let closure_sig = args_a.as_closure().sig(); let unsafety = fn_ty.unsafety(); let pointer_ty = - self.tcx.mk_fn_ptr(self.tcx.signature_unclosure(closure_sig, unsafety)); + Ty::new_fn_ptr(self.tcx, self.tcx.signature_unclosure(closure_sig, unsafety)); debug!("coerce_closure_to_fn(a={:?}, b={:?}, pty={:?})", a, b, pointer_ty); self.unify_and( pointer_ty, b, - simple(Adjust::Pointer(PointerCast::ClosureFnPointer(unsafety))), + simple(Adjust::Pointer(PointerCoercion::ClosureFnPointer(unsafety))), ) } _ => self.unify_and(a, b, identity), @@ -956,7 +981,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { coerce_mutbls(mt_a.mutbl, mutbl_b)?; // Check that the types which they point at are compatible. - let a_unsafe = self.tcx.mk_ptr(ty::TypeAndMut { mutbl: mutbl_b, ty: mt_a.ty }); + let a_unsafe = Ty::new_ptr(self.tcx, ty::TypeAndMut { mutbl: mutbl_b, ty: mt_a.ty }); // Although references and unsafe ptrs have the same // representation, we still register an Adjust::DerefRef so that // regionck knows that the region for `a` must be valid here. @@ -968,7 +993,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> { ] }) } else if mt_a.mutbl != mutbl_b { - self.unify_and(a_unsafe, b, simple(Adjust::Pointer(PointerCast::MutToConstPointer))) + self.unify_and(a_unsafe, b, simple(Adjust::Pointer(PointerCoercion::MutToConstPointer))) } else { self.unify_and(a_unsafe, b, identity) } @@ -980,15 +1005,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// adjusted type of the expression, if successful. /// Adjustments are only recorded if the coercion succeeded. /// The expressions *must not* have any preexisting adjustments. - pub fn try_coerce( + pub fn coerce( &self, expr: &hir::Expr<'_>, expr_ty: Ty<'tcx>, - target: Ty<'tcx>, + mut target: Ty<'tcx>, allow_two_phase: AllowTwoPhase, cause: Option>, ) -> RelateResult<'tcx, Ty<'tcx>> { - let source = self.resolve_vars_with_obligations(expr_ty); + let source = self.try_structurally_resolve_type(expr.span, expr_ty); + if self.next_trait_solver() { + target = self.try_structurally_resolve_type( + cause.as_ref().map_or(expr.span, |cause| cause.span), + target, + ); + } debug!("coercion::try({:?}: {:?} -> {:?})", expr, source, target); let cause = @@ -998,14 +1029,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (adjustments, _) = self.register_infer_ok_obligations(ok); self.apply_adjustments(expr, adjustments); - Ok(if let Err(guar) = expr_ty.error_reported() { self.tcx.ty_error(guar) } else { target }) + Ok(if let Err(guar) = expr_ty.error_reported() { + Ty::new_error(self.tcx, guar) + } else { + target + }) } - /// Same as `try_coerce()`, but without side-effects. + /// Same as `coerce()`, but without side-effects. /// /// Returns false if the coercion creates any obligations that result in /// errors. pub fn can_coerce(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> bool { + // FIXME(-Ztrait-solver=next): We need to structurally resolve both types here. let source = self.resolve_vars_with_obligations(expr_ty); debug!("coercion::can_with_predicates({:?} -> {:?})", source, target); @@ -1016,7 +1052,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Ok(ok) = coerce.coerce(source, target) else { return false; }; - let ocx = ObligationCtxt::new_in_snapshot(self); + let ocx = ObligationCtxt::new(self); ocx.register_obligations(ok.obligations); ocx.select_where_possible().is_empty() }) @@ -1070,8 +1106,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { where E: AsCoercionSite, { - let prev_ty = self.resolve_vars_with_obligations(prev_ty); - let new_ty = self.resolve_vars_with_obligations(new_ty); + let prev_ty = self.try_structurally_resolve_type(cause.span, prev_ty); + let new_ty = self.try_structurally_resolve_type(new.span, new_ty); debug!( "coercion::try_find_coercion_lub({:?}, {:?}, exprs={:?} exprs)", prev_ty, @@ -1086,10 +1122,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Special-case that coercion alone cannot handle: - // Function items or non-capturing closures of differing IDs or InternalSubsts. + // Function items or non-capturing closures of differing IDs or GenericArgs. let (a_sig, b_sig) = { let is_capturing_closure = |ty: Ty<'tcx>| { - if let &ty::Closure(closure_def_id, _substs) = ty.kind() { + if let &ty::Closure(closure_def_id, _args) = ty.kind() { self.tcx.upvars_mentioned(closure_def_id.expect_local()).is_some() } else { false @@ -1116,30 +1152,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } - (ty::Closure(_, substs), ty::FnDef(..)) => { + (ty::Closure(_, args), ty::FnDef(..)) => { let b_sig = new_ty.fn_sig(self.tcx); - let a_sig = self - .tcx - .signature_unclosure(substs.as_closure().sig(), b_sig.unsafety()); + let a_sig = + self.tcx.signature_unclosure(args.as_closure().sig(), b_sig.unsafety()); (Some(a_sig), Some(b_sig)) } - (ty::FnDef(..), ty::Closure(_, substs)) => { + (ty::FnDef(..), ty::Closure(_, args)) => { let a_sig = prev_ty.fn_sig(self.tcx); - let b_sig = self - .tcx - .signature_unclosure(substs.as_closure().sig(), a_sig.unsafety()); + let b_sig = + self.tcx.signature_unclosure(args.as_closure().sig(), a_sig.unsafety()); (Some(a_sig), Some(b_sig)) } - (ty::Closure(_, substs_a), ty::Closure(_, substs_b)) => ( - Some(self.tcx.signature_unclosure( - substs_a.as_closure().sig(), - hir::Unsafety::Normal, - )), - Some(self.tcx.signature_unclosure( - substs_b.as_closure().sig(), - hir::Unsafety::Normal, - )), - ), + (ty::Closure(_, args_a), ty::Closure(_, args_b)) => { + ( + Some(self.tcx.signature_unclosure( + args_a.as_closure().sig(), + hir::Unsafety::Normal, + )), + Some(self.tcx.signature_unclosure( + args_b.as_closure().sig(), + hir::Unsafety::Normal, + )), + ) + } _ => (None, None), } } @@ -1162,15 +1198,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .map(|ok| self.register_infer_ok_obligations(ok))?; // Reify both sides and return the reified fn pointer type. - let fn_ptr = self.tcx.mk_fn_ptr(sig); + let fn_ptr = Ty::new_fn_ptr(self.tcx, sig); let prev_adjustment = match prev_ty.kind() { - ty::Closure(..) => Adjust::Pointer(PointerCast::ClosureFnPointer(a_sig.unsafety())), - ty::FnDef(..) => Adjust::Pointer(PointerCast::ReifyFnPointer), + ty::Closure(..) => { + Adjust::Pointer(PointerCoercion::ClosureFnPointer(a_sig.unsafety())) + } + ty::FnDef(..) => Adjust::Pointer(PointerCoercion::ReifyFnPointer), _ => unreachable!(), }; let next_adjustment = match new_ty.kind() { - ty::Closure(..) => Adjust::Pointer(PointerCast::ClosureFnPointer(b_sig.unsafety())), - ty::FnDef(..) => Adjust::Pointer(PointerCast::ReifyFnPointer), + ty::Closure(..) => { + Adjust::Pointer(PointerCoercion::ClosureFnPointer(b_sig.unsafety())) + } + ty::FnDef(..) => Adjust::Pointer(PointerCoercion::ReifyFnPointer), _ => unreachable!(), }; for expr in exprs.iter().map(|e| e.as_coercion_site()) { @@ -1387,7 +1427,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { expression: &'tcx hir::Expr<'tcx>, expression_ty: Ty<'tcx>, ) { - self.coerce_inner(fcx, cause, Some(expression), expression_ty, None, false) + self.coerce_inner(fcx, cause, Some(expression), expression_ty, |_| {}, false) } /// Indicates that one of the inputs is a "forced unit". This @@ -1406,15 +1446,15 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { &mut self, fcx: &FnCtxt<'a, 'tcx>, cause: &ObligationCause<'tcx>, - augment_error: &mut dyn FnMut(&mut Diagnostic), + augment_error: impl FnOnce(&mut Diagnostic), label_unit_as_expected: bool, ) { self.coerce_inner( fcx, cause, None, - fcx.tcx.mk_unit(), - Some(augment_error), + Ty::new_unit(fcx.tcx), + augment_error, label_unit_as_expected, ) } @@ -1429,7 +1469,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { cause: &ObligationCause<'tcx>, expression: Option<&'tcx hir::Expr<'tcx>>, mut expression_ty: Ty<'tcx>, - augment_error: Option<&mut dyn FnMut(&mut Diagnostic)>, + augment_error: impl FnOnce(&mut Diagnostic), label_expression_as_expected: bool, ) { // Incorporate whatever type inference information we have @@ -1444,7 +1484,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { // If we see any error types, just propagate that error // upwards. if let Err(guar) = (expression_ty, self.merged_ty()).error_reported() { - self.final_ty = Some(fcx.tcx.ty_error(guar)); + self.final_ty = Some(Ty::new_error(fcx.tcx, guar)); return; } @@ -1454,7 +1494,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { // Special-case the first expression we are coercing. // To be honest, I'm not entirely sure why we do this. // We don't allow two-phase borrows, see comment in try_find_coercion_lub for why - fcx.try_coerce( + fcx.coerce( expression, expression_ty, self.expected_ty, @@ -1563,7 +1603,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { ); err.span_label(cause.span, "return type is not `()`"); } - ObligationCauseCode::BlockTailExpression(blk_id) => { + ObligationCauseCode::BlockTailExpression(blk_id, ..) => { let parent_id = fcx.tcx.hir().parent_id(blk_id); err = self.report_return_mismatched_types( cause, @@ -1576,7 +1616,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { Some(blk_id), ); if !fcx.tcx.features().unsized_locals { - unsized_return = self.is_return_ty_unsized(fcx, blk_id); + unsized_return = self.is_return_ty_definitely_unsized(fcx); } if let Some(expression) = expression && let hir::ExprKind::Loop(loop_blk, ..) = expression.kind { @@ -1595,8 +1635,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { None, ); if !fcx.tcx.features().unsized_locals { - let id = fcx.tcx.hir().parent_id(id); - unsized_return = self.is_return_ty_unsized(fcx, id); + unsized_return = self.is_return_ty_definitely_unsized(fcx); } } _ => { @@ -1609,14 +1648,9 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { } } - if let Some(augment_error) = augment_error { - augment_error(&mut err); - } - - let is_insufficiently_polymorphic = - matches!(coercion_error, TypeError::RegionsInsufficientlyPolymorphic(..)); + augment_error(&mut err); - if !is_insufficiently_polymorphic && let Some(expr) = expression { + if let Some(expr) = expression { fcx.emit_coerce_suggestions( &mut err, expr, @@ -1633,7 +1667,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { let reported = err.emit_unless(unsized_return); - self.final_ty = Some(fcx.tcx.ty_error(reported)); + self.final_ty = Some(Ty::new_error(fcx.tcx, reported)); } } } @@ -1644,7 +1678,9 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { expr: &hir::Expr<'tcx>, ret_exprs: &Vec<&'tcx hir::Expr<'tcx>>, ) { - let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else { return;}; + let hir::ExprKind::Loop(_, _, _, loop_span) = expr.kind else { + return; + }; let mut span: MultiSpan = vec![loop_span].into(); span.push_span_label(loop_span, "this might have zero elements to iterate on"); const MAXITER: usize = 3; @@ -1684,9 +1720,6 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { let mut err = fcx.err_ctxt().report_mismatched_types(cause, expected, found, ty_err); - let mut pointing_at_return_type = false; - let mut fn_output = None; - let parent_id = fcx.tcx.hir().parent_id(id); let parent = fcx.tcx.hir().get(parent_id); if let Some(expr) = expression @@ -1699,7 +1732,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { // label pointing out the cause for the type coercion will be wrong // as prior return coercions would not be relevant (#57664). let fn_decl = if let (Some(expr), Some(blk_id)) = (expression, blk_id) { - pointing_at_return_type = + let pointing_at_return_type = fcx.suggest_mismatched_types_on_tail(&mut err, expr, expected, found, blk_id); if let (Some(cond_expr), true, false) = ( fcx.tcx.hir().get_if_cause(expr.hir_id), @@ -1715,7 +1748,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { ) && !in_external_macro(fcx.tcx.sess, cond_expr.span) && !matches!( cond_expr.kind, - hir::ExprKind::Match(.., hir::MatchSource::TryDesugar) + hir::ExprKind::Match(.., hir::MatchSource::TryDesugar(_)) ) { err.span_label(cond_expr.span, "expected this to be `()`"); @@ -1731,7 +1764,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { if let Some((fn_id, fn_decl, can_suggest)) = fn_decl { if blk_id.is_none() { - pointing_at_return_type |= fcx.suggest_missing_return_type( + fcx.suggest_missing_return_type( &mut err, &fn_decl, expected, @@ -1740,9 +1773,6 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { fn_id, ); } - if !pointing_at_return_type { - fn_output = Some(&fn_decl.output); // `impl Trait` return type - } } let parent_id = fcx.tcx.hir().get_parent_item(id); @@ -1771,121 +1801,32 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { err.span_note( sp, format!( - "return type inferred to be `{}` here", - expected + "return type inferred to be `{expected}` here" ), ); } - if let (Some(sp), Some(fn_output)) = (ret_coercion_span, fn_output) { - self.add_impl_trait_explanation(&mut err, cause, fcx, expected, sp, fn_output); - } - err } - fn add_impl_trait_explanation<'a>( - &self, - err: &mut Diagnostic, - cause: &ObligationCause<'tcx>, - fcx: &FnCtxt<'a, 'tcx>, - expected: Ty<'tcx>, - sp: Span, - fn_output: &hir::FnRetTy<'_>, - ) { - let return_sp = fn_output.span(); - err.span_label(return_sp, "expected because this return type..."); - err.span_label( - sp, - format!("...is found to be `{}` here", fcx.resolve_vars_with_obligations(expected)), - ); - let impl_trait_msg = "for information on `impl Trait`, see \ - "; - let trait_obj_msg = "for information on trait objects, see \ - "; - err.note("to return `impl Trait`, all returned values must be of the same type"); - err.note(impl_trait_msg); - let snippet = fcx - .tcx - .sess - .source_map() - .span_to_snippet(return_sp) - .unwrap_or_else(|_| "dyn Trait".to_string()); - let mut snippet_iter = snippet.split_whitespace(); - let has_impl = snippet_iter.next().is_some_and(|s| s == "impl"); - // Only suggest `Box` if `Trait` in `impl Trait` is object safe. - let mut is_object_safe = false; - if let hir::FnRetTy::Return(ty) = fn_output - // Get the return type. - && let hir::TyKind::OpaqueDef(..) = ty.kind - { - let ty = fcx.astconv().ast_ty_to_ty( ty); - // Get the `impl Trait`'s `DefId`. - if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) = ty.kind() - // Get the `impl Trait`'s `Item` so that we can get its trait bounds and - // get the `Trait`'s `DefId`. - && let hir::ItemKind::OpaqueTy(hir::OpaqueTy { bounds, .. }) = - fcx.tcx.hir().expect_item(def_id.expect_local()).kind - { - // Are of this `impl Trait`'s traits object safe? - is_object_safe = bounds.iter().all(|bound| { - bound - .trait_ref() - .and_then(|t| t.trait_def_id()) - .is_some_and(|def_id| { - fcx.tcx.check_is_object_safe(def_id) - }) - }) - } - }; - if has_impl { - if is_object_safe { - err.multipart_suggestion( - "you could change the return type to be a boxed trait object", - vec![ - (return_sp.with_hi(return_sp.lo() + BytePos(4)), "Box".to_string()), - ], - Applicability::MachineApplicable, - ); - let sugg = [sp, cause.span] - .into_iter() - .flat_map(|sp| { - [ - (sp.shrink_to_lo(), "Box::new(".to_string()), - (sp.shrink_to_hi(), ")".to_string()), - ] - .into_iter() - }) - .collect::>(); - err.multipart_suggestion( - "if you change the return type to expect trait objects, box the returned \ - expressions", - sugg, - Applicability::MaybeIncorrect, - ); - } else { - err.help(format!( - "if the trait `{}` were object safe, you could return a boxed trait object", - &snippet[5..] - )); - } - err.note(trait_obj_msg); - } - err.help("you could instead create a new `enum` with a variant for each returned type"); - } - - fn is_return_ty_unsized<'a>(&self, fcx: &FnCtxt<'a, 'tcx>, blk_id: hir::HirId) -> bool { - if let Some((_, fn_decl, _)) = fcx.get_fn_decl(blk_id) - && let hir::FnRetTy::Return(ty) = fn_decl.output - && let ty = fcx.astconv().ast_ty_to_ty( ty) - && let ty::Dynamic(..) = ty.kind() - { - return true; + /// Checks whether the return type is unsized via an obligation, which makes + /// sure we consider `dyn Trait: Sized` where clauses, which are trivially + /// false but technically valid for typeck. + fn is_return_ty_definitely_unsized(&self, fcx: &FnCtxt<'_, 'tcx>) -> bool { + if let Some(sig) = fcx.body_fn_sig() { + !fcx.predicate_may_hold(&Obligation::new( + fcx.tcx, + ObligationCause::dummy(), + fcx.param_env, + ty::TraitRef::new( + fcx.tcx, + fcx.tcx.require_lang_item(hir::LangItem::Sized, None), + [sig.output()], + ), + )) + } else { + false } - false } pub fn complete<'a>(self, fcx: &FnCtxt<'a, 'tcx>) -> Ty<'tcx> { diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index b50630e636b2e..2c16f21b49160 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -3,7 +3,7 @@ use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::MultiSpan; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; use rustc_hir as hir; -use rustc_hir::def::CtorKind; +use rustc_hir::def::{CtorKind, Res}; use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items::LangItem; use rustc_hir::{is_range_literal, Node}; @@ -15,7 +15,7 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut, TypeFoldable}; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::sym; use rustc_span::{BytePos, Span, DUMMY_SP}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::ObligationCause; @@ -53,7 +53,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { || self.suggest_no_capture_closure(err, expected, expr_ty) || self.suggest_boxing_when_appropriate(err, expr.span, expr.hir_id, expected, expr_ty) || self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected) - || self.suggest_copied_or_cloned(err, expr, expr_ty, expected) + || self.suggest_copied_cloned_or_as_ref(err, expr, expr_ty, expected) || self.suggest_clone_for_ref(err, expr, expr_ty, expected) || self.suggest_into(err, expr, expr_ty, expected) || self.suggest_floating_point_literal(err, expr, expected) @@ -83,6 +83,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } self.annotate_expected_due_to_let_ty(err, expr, error); + + // FIXME(#73154): For now, we do leak check when coercing function + // pointers in typeck, instead of only during borrowck. This can lead + // to these `RegionsInsufficientlyPolymorphic` errors that aren't helpful. + if matches!(error, Some(TypeError::RegionsInsufficientlyPolymorphic(..))) { + return; + } + + if self.is_destruct_assignment_desugaring(expr) { + return; + } self.emit_type_mismatch_suggestions(err, expr, expr_ty, expected, expected_ty_expr, error); self.note_type_is_not_clone(err, expected, expr_ty, expr); self.note_internal_mutation_in_method(err, expr, Some(expected), expr_ty); @@ -91,6 +102,64 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.note_wrong_return_ty_due_to_generic_arg(err, expr, expr_ty); } + /// Really hacky heuristic to remap an `assert_eq!` error to the user + /// expressions provided to the macro. + fn adjust_expr_for_assert_eq_macro( + &self, + found_expr: &mut &'tcx hir::Expr<'tcx>, + expected_expr: &mut Option<&'tcx hir::Expr<'tcx>>, + ) { + let Some(expected_expr) = expected_expr else { + return; + }; + + if !found_expr.span.eq_ctxt(expected_expr.span) { + return; + } + + if !found_expr + .span + .ctxt() + .outer_expn_data() + .macro_def_id + .is_some_and(|def_id| self.tcx.is_diagnostic_item(sym::assert_eq_macro, def_id)) + { + return; + } + + let hir::ExprKind::Unary( + hir::UnOp::Deref, + hir::Expr { kind: hir::ExprKind::Path(found_path), .. }, + ) = found_expr.kind + else { + return; + }; + let hir::ExprKind::Unary( + hir::UnOp::Deref, + hir::Expr { kind: hir::ExprKind::Path(expected_path), .. }, + ) = expected_expr.kind + else { + return; + }; + + for (path, name, idx, var) in [ + (expected_path, "left_val", 0, expected_expr), + (found_path, "right_val", 1, found_expr), + ] { + if let hir::QPath::Resolved(_, path) = path + && let [segment] = path.segments + && segment.ident.name.as_str() == name + && let Res::Local(hir_id) = path.res + && let Some((_, hir::Node::Expr(match_expr))) = self.tcx.hir().parent_iter(hir_id).nth(2) + && let hir::ExprKind::Match(scrutinee, _, _) = match_expr.kind + && let hir::ExprKind::Tup(exprs) = scrutinee.kind + && let hir::ExprKind::AddrOf(_, _, macro_arg) = exprs[idx].kind + { + *var = macro_arg; + } + } + } + /// Requires that the two types unify, and prints an error message if /// they don't. pub fn demand_suptype(&self, sp: Span, expected: Ty<'tcx>, actual: Ty<'tcx>) { @@ -156,7 +225,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn demand_coerce( &self, - expr: &hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, checked_ty: Ty<'tcx>, expected: Ty<'tcx>, expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, @@ -177,44 +246,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { #[instrument(level = "debug", skip(self, expr, expected_ty_expr, allow_two_phase))] pub fn demand_coerce_diag( &self, - expr: &hir::Expr<'tcx>, + mut expr: &'tcx hir::Expr<'tcx>, checked_ty: Ty<'tcx>, expected: Ty<'tcx>, - expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, + mut expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, allow_two_phase: AllowTwoPhase, ) -> (Ty<'tcx>, Option>) { let expected = self.resolve_vars_with_obligations(expected); - let e = match self.try_coerce(expr, checked_ty, expected, allow_two_phase, None) { + let e = match self.coerce(expr, checked_ty, expected, allow_two_phase, None) { Ok(ty) => return (ty, None), Err(e) => e, }; + self.adjust_expr_for_assert_eq_macro(&mut expr, &mut expected_ty_expr); + self.set_tainted_by_errors(self.tcx.sess.delay_span_bug( expr.span, "`TypeError` when attempting coercion but no error emitted", )); let expr = expr.peel_drop_temps(); let cause = self.misc(expr.span); - let expr_ty = self.resolve_vars_with_obligations(checked_ty); + let expr_ty = self.resolve_vars_if_possible(checked_ty); let mut err = self.err_ctxt().report_mismatched_types(&cause, expected, expr_ty, e); - let is_insufficiently_polymorphic = - matches!(e, TypeError::RegionsInsufficientlyPolymorphic(..)); - - // FIXME(#73154): For now, we do leak check when coercing function - // pointers in typeck, instead of only during borrowck. This can lead - // to these `RegionsInsufficientlyPolymorphic` errors that aren't helpful. - if !is_insufficiently_polymorphic { - self.emit_coerce_suggestions( - &mut err, - expr, - expr_ty, - expected, - expected_ty_expr, - Some(e), - ); - } + self.emit_coerce_suggestions(&mut err, expr, expr_ty, expected, expected_ty_expr, Some(e)); (expected, Some(err)) } @@ -229,16 +285,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> bool { let hir = self.tcx.hir(); - let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind else { return false; }; - let [hir::PathSegment { ident, args: None, .. }] = p.segments else { return false; }; - let hir::def::Res::Local(local_hir_id) = p.res else { return false; }; - let hir::Node::Pat(pat) = hir.get(local_hir_id) else { return false; }; + let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind else { + return false; + }; + let [hir::PathSegment { ident, args: None, .. }] = p.segments else { + return false; + }; + let hir::def::Res::Local(local_hir_id) = p.res else { + return false; + }; + let hir::Node::Pat(pat) = hir.get(local_hir_id) else { + return false; + }; let (init_ty_hir_id, init) = match hir.get_parent(pat.hir_id) { hir::Node::Local(hir::Local { ty: Some(ty), init, .. }) => (ty.hir_id, *init), hir::Node::Local(hir::Local { init: Some(init), .. }) => (init.hir_id, Some(*init)), _ => return false, }; - let Some(init_ty) = self.node_ty_opt(init_ty_hir_id) else { return false; }; + let Some(init_ty) = self.node_ty_opt(init_ty_hir_id) else { + return false; + }; // Locate all the usages of the relevant binding. struct FindExprs<'tcx> { @@ -318,13 +384,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Fudge the receiver, so we can do new inference on it. let possible_rcvr_ty = possible_rcvr_ty.fold_with(&mut fudger); let method = self - .lookup_method( + .lookup_method_for_diagnostic( possible_rcvr_ty, segment, DUMMY_SP, call_expr, binding, - args, ) .ok()?; // Unify the method signature with our incompatible arg, to @@ -358,14 +423,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Bindings always update their recorded type after the fact, so we // need to look at the *following* usage's type to see when the // binding became incompatible. - let [binding, next_usage] = *window else { continue; }; + let [binding, next_usage] = *window else { + continue; + }; // Don't go past the binding (always gonna be a nonsense label if so) if binding.hir_id == expr.hir_id { break; } - let Some(next_use_ty) = self.node_ty_opt(next_usage.hir_id) else { continue; }; + let Some(next_use_ty) = self.node_ty_opt(next_usage.hir_id) else { + continue; + }; // If the type is not constrained in a way making it not possible to // equate with `expected_ty` by this point, skip. @@ -383,14 +452,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some(rcvr_ty) = self.node_ty_opt(rcvr.hir_id) else { continue; }; let rcvr_ty = rcvr_ty.fold_with(&mut fudger); let Ok(method) = - self.lookup_method(rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args) + self.lookup_method_for_diagnostic(rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr) else { continue; }; let ideal_rcvr_ty = rcvr_ty.fold_with(&mut fudger); let ideal_method = self - .lookup_method(ideal_rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr, args) + .lookup_method_for_diagnostic(ideal_rcvr_ty, segment, DUMMY_SP, parent_expr, rcvr) .ok() .and_then(|method| { let _ = self.at(&ObligationCause::dummy(), self.param_env) @@ -406,7 +475,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { let Some(arg_ty) = self.node_ty_opt(arg_expr.hir_id) else { continue; }; let arg_ty = arg_ty.fold_with(&mut fudger); - let _ = self.try_coerce( + let _ = self.coerce( arg_expr, arg_ty, *expected_arg_ty, @@ -544,7 +613,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // is in a different line, so we point at both. err.span_label(secondary_span, "expected due to the type of this binding"); err.span_label(primary_span, format!("expected due to this{post_message}")); - } else if post_message == "" { + } else if post_message.is_empty() { // We are pointing at either the assignment lhs or the binding def pattern. err.span_label(primary_span, "expected due to the type of this binding"); } else { @@ -579,27 +648,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { error: Option>, ) { let parent = self.tcx.hir().parent_id(expr.hir_id); - let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else {return;}; - let Some(hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Assign(lhs, rhs, _), .. - })) = self.tcx.hir().find(parent) else {return; }; + let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else { + return; + }; + let Some(hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Assign(lhs, rhs, _), .. })) = + self.tcx.hir().find(parent) + else { + return; + }; if rhs.hir_id != expr.hir_id || expected.is_closure() { return; } - let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else { return; }; - let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else { return; }; - let Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else { return; }; + let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else { + return; + }; + let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else { + return; + }; + let Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else { + return; + }; - let Ok(pick) = self - .lookup_probe_for_diagnostic( - path.ident, - self_ty, - deref, - probe::ProbeScope::TraitsInScope, - None, - ) else { - return; - }; + let Ok(pick) = self.lookup_probe_for_diagnostic( + path.ident, + self_ty, + deref, + probe::ProbeScope::TraitsInScope, + None, + ) else { + return; + }; let in_scope_methods = self.probe_for_name_many( probe::Mode::MethodCall, path.ident, @@ -626,7 +704,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .filter(|c| c.item.def_id != pick.item.def_id) .map(|c| { let m = c.item; - let substs = ty::InternalSubsts::for_item(self.tcx, m.def_id, |param, _| { + let generic_args = ty::GenericArgs::for_item(self.tcx, m.def_id, |param, _| { self.var_for_def(deref.span, param) }); let mutability = @@ -641,7 +719,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!( "{}({}", with_no_trimmed_paths!( - self.tcx.def_path_str_with_substs(m.def_id, substs,) + self.tcx.def_path_str_with_args(m.def_id, generic_args,) ), mutability, ), @@ -738,8 +816,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, found: Ty<'tcx>, ) -> bool { - let ty::Adt(e, substs_e) = expected.kind() else { return false; }; - let ty::Adt(f, substs_f) = found.kind() else { return false; }; + let ty::Adt(e, args_e) = expected.kind() else { + return false; + }; + let ty::Adt(f, args_f) = found.kind() else { + return false; + }; if e.did() != f.did() { return false; } @@ -756,8 +838,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { return false; } - let e = substs_e.type_at(1); - let f = substs_f.type_at(1); + let e = args_e.type_at(1); + let f = args_f.type_at(1); if self .infcx .type_implements_trait( @@ -790,7 +872,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, expr_ty: Ty<'tcx>, ) -> bool { - if let ty::Adt(expected_adt, substs) = expected.kind() { + if in_external_macro(self.tcx.sess, expr.span) { + return false; + } + if let ty::Adt(expected_adt, args) = expected.kind() { if let hir::ExprKind::Field(base, ident) = expr.kind { let base_ty = self.typeck_results.borrow().expr_ty(base); if self.can_eq(self.param_env, base_ty, expected) @@ -889,7 +974,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let note_about_variant_field_privacy = (field_is_local && !field_is_accessible) .then(|| " (its field is private, but it's local to this crate and its privacy can be changed)".to_string()); - let sole_field_ty = sole_field.ty(self.tcx, substs); + let sole_field_ty = sole_field.ty(self.tcx, args); if self.can_coerce(expr_ty, sole_field_ty) { let variant_path = with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); @@ -907,7 +992,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .collect(); let suggestions_for = |variant: &_, ctor_kind, field_name| { - let prefix = match self.maybe_get_struct_pattern_shorthand_field(expr) { + let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { Some(ident) => format!("{ident}: "), None => String::new(), }; @@ -982,9 +1067,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let tcx = self.tcx; let (adt, unwrap) = match expected.kind() { // In case Option is wanted, but * is provided, suggest calling new - ty::Adt(adt, substs) if tcx.is_diagnostic_item(sym::Option, adt.did()) => { + ty::Adt(adt, args) if tcx.is_diagnostic_item(sym::Option, adt.did()) => { // Unwrap option - let ty::Adt(adt, _) = substs.type_at(0).kind() else { return false; }; + let ty::Adt(adt, _) = args.type_at(0).kind() else { + return false; + }; (adt, "") } @@ -1006,10 +1093,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { (sym::NonZeroI128, tcx.types.i128), ]; - let Some((s, _)) = map - .iter() - .find(|&&(s, t)| self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t)) - else { return false; }; + let Some((s, _)) = map.iter().find(|&&(s, t)| { + self.tcx.is_diagnostic_item(s, adt.did()) && self.can_coerce(expr_ty, t) + }) else { + return false; + }; let path = self.tcx.def_path_str(adt.non_enum_variant().def_id); @@ -1097,7 +1185,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let local_parent = self.tcx.hir().parent_id(local_id); - let Some(Node::Param(hir::Param { hir_id: param_hir_id, .. })) = self.tcx.hir().find(local_parent) else { + let Some(Node::Param(hir::Param { hir_id: param_hir_id, .. })) = + self.tcx.hir().find(local_parent) + else { return None; }; @@ -1106,7 +1196,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir_id: expr_hir_id, kind: hir::ExprKind::Closure(hir::Closure { fn_decl: closure_fn_decl, .. }), .. - })) = self.tcx.hir().find(param_parent) else { + })) = self.tcx.hir().find(param_parent) + else { return None; }; @@ -1119,7 +1210,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .. })), 1, - ) = (hir, closure_params_len) else { + ) = (hir, closure_params_len) + else { return None; }; @@ -1143,39 +1235,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - pub(crate) fn maybe_get_struct_pattern_shorthand_field( - &self, - expr: &hir::Expr<'_>, - ) -> Option { - let hir = self.tcx.hir(); - let local = match expr { - hir::Expr { - kind: - hir::ExprKind::Path(hir::QPath::Resolved( - None, - hir::Path { - res: hir::def::Res::Local(_), - segments: [hir::PathSegment { ident, .. }], - .. - }, - )), - .. - } => Some(ident), - _ => None, - }?; - - match hir.find_parent(expr.hir_id)? { - Node::ExprField(field) => { - if field.ident.name == local.name && field.is_shorthand { - return Some(local.name); - } - } - _ => {} - } - - None - } - /// If the given `HirId` corresponds to a block with a trailing expression, return that expression pub(crate) fn maybe_get_block_expr( &self, @@ -1202,6 +1261,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } + // Returns whether the given expression is a destruct assignment desugaring. + // For example, `(a, b) = (1, &2);` + // Here we try to find the pattern binding of the expression, + // `default_binding_modes` is false only for destruct assignment desugaring. + pub(crate) fn is_destruct_assignment_desugaring(&self, expr: &hir::Expr<'_>) -> bool { + if let hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(bind_hir_id), .. }, + )) = expr.kind + { + let bind = self.tcx.hir().find(*bind_hir_id); + let parent = self.tcx.hir().find(self.tcx.hir().parent_id(*bind_hir_id)); + if let Some(hir::Node::Pat(hir::Pat { kind: hir::PatKind::Binding(_, _hir_id, _, _), .. })) = bind && + let Some(hir::Node::Pat(hir::Pat { default_binding_modes: false, .. })) = parent { + return true; + } + } + return false; + } + /// This function is used to determine potential "simple" improvements or users' errors and /// provide them useful help. For example: /// @@ -1291,10 +1370,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // ``` let ref_ty = match mutability { hir::Mutability::Mut => { - self.tcx.mk_mut_ref(self.tcx.lifetimes.re_static, checked_ty) + Ty::new_mut_ref(self.tcx,self.tcx.lifetimes.re_static, checked_ty) } hir::Mutability::Not => { - self.tcx.mk_imm_ref(self.tcx.lifetimes.re_static, checked_ty) + Ty::new_imm_ref(self.tcx,self.tcx.lifetimes.re_static, checked_ty) } }; if self.can_coerce(ref_ty, expected) { @@ -1350,7 +1429,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )); } - let prefix = match self.maybe_get_struct_pattern_shorthand_field(expr) { + let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { Some(ident) => format!("{ident}: "), None => String::new(), }; @@ -1392,6 +1471,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _, &ty::Ref(_, checked, _), ) if self.can_sub(self.param_env, checked, expected) => { + let make_sugg = |start: Span, end: BytePos| { + // skip `(` for tuples such as `(c) = (&123)`. + // make sure we won't suggest like `(c) = 123)` which is incorrect. + let sp = sm.span_extend_while(start.shrink_to_lo(), |c| c == '(' || c.is_whitespace()) + .map_or(start, |s| s.shrink_to_hi()); + Some(( + vec![(sp.with_hi(end), String::new())], + "consider removing the borrow".to_string(), + Applicability::MachineApplicable, + true, + true, + )) + }; + // We have `&T`, check if what was expected was `T`. If so, // we may want to suggest removing a `&`. if sm.is_imported(expr.span) { @@ -1405,24 +1498,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .find(|&s| sp.contains(s)) && sm.is_span_accessible(call_span) { - return Some(( - vec![(sp.with_hi(call_span.lo()), String::new())], - "consider removing the borrow".to_string(), - Applicability::MachineApplicable, - true, - true, - )); + return make_sugg(sp, call_span.lo()) } return None; } if sp.contains(expr.span) && sm.is_span_accessible(expr.span) { - return Some(( - vec![(sp.with_hi(expr.span.lo()), String::new())], - "consider removing the borrow".to_string(), - Applicability::MachineApplicable, - true, - true, - )); + return make_sugg(sp, expr.span.lo()) } } ( @@ -1542,7 +1623,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }; - let prefix = match self.maybe_get_struct_pattern_shorthand_field(expr) { + let prefix = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { Some(ident) => format!("{ident}: "), None => String::new(), }; @@ -1951,11 +2032,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if !hir::is_range_literal(expr) { return; } - let hir::ExprKind::Struct( - hir::QPath::LangItem(LangItem::Range, ..), - [start, end], - _, - ) = expr.kind else { return; }; + let hir::ExprKind::Struct(hir::QPath::LangItem(LangItem::Range, ..), [start, end], _) = + expr.kind + else { + return; + }; let parent = self.tcx.hir().parent_id(expr.hir_id); if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) { // Ignore `Foo { field: a..Default::default() }` @@ -1971,8 +2052,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // cannot guide the method probe. expectation = None; } - let hir::ExprKind::Call(method_name, _) = expr.kind else { return; }; - let ty::Adt(adt, _) = checked_ty.kind() else { return; }; + let hir::ExprKind::Call(method_name, _) = expr.kind else { + return; + }; + let ty::Adt(adt, _) = checked_ty.kind() else { + return; + }; if self.tcx.lang_items().range_struct() != Some(adt.did()) { return; } @@ -1982,8 +2067,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } // Check if start has method named end. - let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { return; }; - let [hir::PathSegment { ident, .. }] = p.segments else { return; }; + let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { + return; + }; + let [hir::PathSegment { ident, .. }] = p.segments else { + return; + }; let self_ty = self.typeck_results.borrow().expr_ty(start.expr); let Ok(_pick) = self.lookup_probe_for_diagnostic( *ident, @@ -1991,7 +2080,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr, probe::ProbeScope::AllTraits, expectation, - ) else { return; }; + ) else { + return; + }; let mut sugg = "."; let mut span = start.expr.span.between(end.expr.span); if span.lo() + BytePos(2) == span.hi() { @@ -2020,17 +2111,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if !checked_ty.is_unit() { return; } - let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { return; }; - let hir::def::Res::Local(hir_id) = path.res else { return; }; + let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { + return; + }; + let hir::def::Res::Local(hir_id) = path.res else { + return; + }; let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else { return; }; - let Some(hir::Node::Local(hir::Local { - ty: None, - init: Some(init), - .. - })) = self.tcx.hir().find_parent(pat.hir_id) else { return; }; - let hir::ExprKind::Block(block, None) = init.kind else { return; }; + let Some(hir::Node::Local(hir::Local { ty: None, init: Some(init), .. })) = + self.tcx.hir().find_parent(pat.hir_id) + else { + return; + }; + let hir::ExprKind::Block(block, None) = init.kind else { + return; + }; if block.expr.is_some() { return; } @@ -2038,8 +2135,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(block.span, "this empty block is missing a tail expression"); return; }; - let hir::StmtKind::Semi(tail_expr) = stmt.kind else { return; }; - let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else { return; }; + let hir::StmtKind::Semi(tail_expr) = stmt.kind else { + return; + }; + let Some(ty) = self.node_ty_opt(tail_expr.hir_id) else { + return; + }; if self.can_eq(self.param_env, expected_ty, ty) { err.span_suggestion_short( stmt.span.with_lo(tail_expr.span.hi()), @@ -2058,7 +2159,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &hir::Expr<'_>, checked_ty: Ty<'tcx>, ) { - let Some(hir::Node::Expr(parent_expr)) = self.tcx.hir().find_parent(expr.hir_id) else { return; }; + let Some(hir::Node::Expr(parent_expr)) = self.tcx.hir().find_parent(expr.hir_id) else { + return; + }; enum CallableKind { Function, Method, @@ -2074,7 +2177,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } let fn_sig = fn_ty.fn_sig(self.tcx).skip_binder(); - let Some(&arg) = fn_sig.inputs().get(arg_idx + if matches!(kind, CallableKind::Method) { 1 } else { 0 }) else { return; }; + let Some(&arg) = fn_sig + .inputs() + .get(arg_idx + if matches!(kind, CallableKind::Method) { 1 } else { 0 }) + else { + return; + }; if matches!(arg.kind(), ty::Param(_)) && fn_sig.output().contains(arg) && self.node_ty(args[arg_idx].hir_id) == checked_ty @@ -2108,8 +2216,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; match parent_expr.kind { hir::ExprKind::Call(fun, args) => { - let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = fun.kind else { return; }; - let hir::def::Res::Def(kind, def_id) = path.res else { return; }; + let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = fun.kind else { + return; + }; + let hir::def::Res::Def(kind, def_id) = path.res else { + return; + }; let callable_kind = if matches!(kind, hir::def::DefKind::Ctor(_, _)) { CallableKind::Constructor } else { @@ -2118,7 +2230,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { maybe_emit_help(def_id, path.segments[0].ident, args, callable_kind); } hir::ExprKind::MethodCall(method, _receiver, args, _span) => { - let Some(def_id) = self.typeck_results.borrow().type_dependent_def_id(parent_expr.hir_id) else { return; }; + let Some(def_id) = + self.typeck_results.borrow().type_dependent_def_id(parent_expr.hir_id) + else { + return; + }; maybe_emit_help(def_id, method.ident, args, CallableKind::Method) } _ => return, diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 102a313067fd2..054d23c71d4a7 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -2,7 +2,10 @@ use std::borrow::Cow; use crate::fluent_generated as fluent; -use rustc_errors::{AddToDiagnostic, Applicability, Diagnostic, MultiSpan, SubdiagnosticMessage}; +use rustc_errors::{ + AddToDiagnostic, Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnosticArg, MultiSpan, + SubdiagnosticMessage, +}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_middle::ty::Ty; use rustc_span::{ @@ -31,6 +34,24 @@ pub struct ReturnStmtOutsideOfFnBody { pub encl_body_span: Option, #[label(hir_typeck_encl_fn_label)] pub encl_fn_span: Option, + pub statement_kind: ReturnLikeStatementKind, +} + +pub enum ReturnLikeStatementKind { + Return, + Become, +} + +impl IntoDiagnosticArg for ReturnLikeStatementKind { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + let kind = match self { + Self::Return => "return", + Self::Become => "become", + } + .into(); + + DiagnosticArgValue::Str(kind) + } } #[derive(Diagnostic)] @@ -49,8 +70,8 @@ pub struct StructExprNonExhaustive { } #[derive(Diagnostic)] -#[diag(hir_typeck_method_call_on_unknown_type, code = "E0699")] -pub struct MethodCallOnUnknownType { +#[diag(hir_typeck_method_call_on_unknown_raw_pointee, code = "E0699")] +pub struct MethodCallOnUnknownRawPointee { #[primary_span] pub span: Span, } @@ -231,6 +252,46 @@ impl HelpUseLatestEdition { } } +#[derive(Subdiagnostic)] +pub enum OptionResultRefMismatch { + #[suggestion( + hir_typeck_option_result_copied, + code = ".copied()", + style = "verbose", + applicability = "machine-applicable" + )] + Copied { + #[primary_span] + span: Span, + def_path: String, + }, + #[suggestion( + hir_typeck_option_result_cloned, + code = ".cloned()", + style = "verbose", + applicability = "machine-applicable" + )] + Cloned { + #[primary_span] + span: Span, + def_path: String, + }, + // FIXME: #114050 + // #[suggestion( + // hir_typeck_option_result_asref, + // code = ".as_ref()", + // style = "verbose", + // applicability = "machine-applicable" + // )] + // AsRef { + // #[primary_span] + // span: Span, + // def_path: String, + // expected_ty: Ty<'tcx>, + // expr_ty: Ty<'tcx>, + // }, +} + #[derive(Diagnostic)] #[diag(hir_typeck_const_select_must_be_const)] #[help] @@ -298,6 +359,17 @@ pub enum SuggestBoxing { }, } +#[derive(Subdiagnostic)] +#[suggestion( + hir_typeck_suggest_ptr_null_mut, + applicability = "maybe-incorrect", + code = "core::ptr::null_mut()" +)] +pub struct SuggestPtrNullMut { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(hir_typeck_no_associated_item, code = "E0599")] pub struct NoAssociatedItem { @@ -319,3 +391,27 @@ pub struct CandidateTraitNote { pub item_name: Ident, pub action_or_ty: String, } + +#[derive(Diagnostic)] +#[diag(hir_typeck_ctor_is_private, code = "E0603")] +pub struct CtorIsPrivate { + #[primary_span] + pub span: Span, + pub def: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + hir_typeck_convert_using_method, + applicability = "machine-applicable", + style = "verbose" +)] +pub struct SuggestConvertViaMethod<'tcx> { + #[suggestion_part(code = "{sugg}")] + pub span: Span, + #[suggestion_part(code = "")] + pub borrow_removal_span: Option, + pub sugg: &'static str, + pub expected: Ty<'tcx>, + pub found: Ty<'tcx>, +} diff --git a/compiler/rustc_hir_typeck/src/expectation.rs b/compiler/rustc_hir_typeck/src/expectation.rs index 4f086cf597d88..35e5fb769a58b 100644 --- a/compiler/rustc_hir_typeck/src/expectation.rs +++ b/compiler/rustc_hir_typeck/src/expectation.rs @@ -21,8 +21,6 @@ pub enum Expectation<'tcx> { /// This rvalue expression will be wrapped in `&` or `Box` and coerced /// to `&Ty` or `Box`, respectively. `Ty` is `[A]` or `Trait`. ExpectRvalueLikeUnsized(Ty<'tcx>), - - IsLast(Span), } impl<'a, 'tcx> Expectation<'tcx> { @@ -88,13 +86,12 @@ impl<'a, 'tcx> Expectation<'tcx> { ExpectCastableToType(t) => ExpectCastableToType(fcx.resolve_vars_if_possible(t)), ExpectHasType(t) => ExpectHasType(fcx.resolve_vars_if_possible(t)), ExpectRvalueLikeUnsized(t) => ExpectRvalueLikeUnsized(fcx.resolve_vars_if_possible(t)), - IsLast(sp) => IsLast(sp), } } pub(super) fn to_option(self, fcx: &FnCtxt<'a, 'tcx>) -> Option> { match self.resolve(fcx) { - NoExpectation | IsLast(_) => None, + NoExpectation => None, ExpectCastableToType(ty) | ExpectHasType(ty) | ExpectRvalueLikeUnsized(ty) => Some(ty), } } @@ -106,9 +103,7 @@ impl<'a, 'tcx> Expectation<'tcx> { pub(super) fn only_has_type(self, fcx: &FnCtxt<'a, 'tcx>) -> Option> { match self { ExpectHasType(ty) => Some(fcx.resolve_vars_if_possible(ty)), - NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) | IsLast(_) => { - None - } + NoExpectation | ExpectCastableToType(_) | ExpectRvalueLikeUnsized(_) => None, } } diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 19ff77d8349aa..7cea40fdd64c2 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -5,6 +5,7 @@ use crate::cast; use crate::coercion::CoerceMany; use crate::coercion::DynamicCoerceMany; +use crate::errors::ReturnLikeStatementKind; use crate::errors::TypeMismatchFruTypo; use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive}; use crate::errors::{ @@ -12,7 +13,7 @@ use crate::errors::{ YieldExprOutsideOfGenerator, }; use crate::fatally_break_rust; -use crate::method::SelfSource; +use crate::method::{MethodCallComponents, SelfSource}; use crate::type_error_struct; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; use crate::{ @@ -43,7 +44,7 @@ use rustc_infer::traits::ObligationCause; use rustc_middle::middle::stability; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; use rustc_middle::ty::error::TypeError::FieldMisMatch; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, AdtKind, Ty, TypeVisitableExt}; use rustc_session::errors::ExprParenthesesNeeded; use rustc_session::parse::feature_err; @@ -59,28 +60,13 @@ use rustc_trait_selection::traits::ObligationCtxt; use rustc_trait_selection::traits::{self, ObligationCauseCode}; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { - fn check_expr_eq_type(&self, expr: &'tcx hir::Expr<'tcx>, expected: Ty<'tcx>) { - let ty = self.check_expr_with_hint(expr, expected); - self.demand_eqtype(expr.span, expected, ty); - } - pub fn check_expr_has_type_or_error( &self, expr: &'tcx hir::Expr<'tcx>, - expected: Ty<'tcx>, - extend_err: impl FnMut(&mut Diagnostic), - ) -> Ty<'tcx> { - self.check_expr_meets_expectation_or_error(expr, ExpectHasType(expected), extend_err) - } - - fn check_expr_meets_expectation_or_error( - &self, - expr: &'tcx hir::Expr<'tcx>, - expected: Expectation<'tcx>, - mut extend_err: impl FnMut(&mut Diagnostic), + expected_ty: Ty<'tcx>, + extend_err: impl FnOnce(&mut Diagnostic), ) -> Ty<'tcx> { - let expected_ty = expected.to_option(&self).unwrap_or(self.tcx.types.bool); - let mut ty = self.check_expr_with_expectation(expr, expected); + let mut ty = self.check_expr_with_expectation(expr, ExpectHasType(expected_ty)); // While we don't allow *arbitrary* coercions here, we *do* allow // coercions from ! to `expected`. @@ -93,7 +79,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return if let [Adjustment { kind: Adjust::NeverToAny, target }] = &adjustments[..] { target.to_owned() } else { - self.tcx().ty_error(reported) + Ty::new_error(self.tcx(), reported) }; } @@ -320,10 +306,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tcx.types.never } else { // There was an error; make type-check fail. - tcx.ty_error_misc() + Ty::new_misc_error(tcx) } } ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr), + ExprKind::Become(call) => self.check_expr_become(call, expr), ExprKind::Let(let_expr) => self.check_expr_let(let_expr), ExprKind::Loop(body, _, source, _) => { self.check_expr_loop(body, source, expected, expr) @@ -339,18 +326,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } ExprKind::Cast(e, t) => self.check_expr_cast(e, t, expr), ExprKind::Type(e, t) => { - let ty = self.to_ty_saving_user_provided_ty(&t); - self.check_expr_eq_type(&e, ty); - ty + let ascribed_ty = self.to_ty_saving_user_provided_ty(&t); + let ty = self.check_expr_with_hint(e, ascribed_ty); + self.demand_eqtype(e.span, ascribed_ty, ty); + ascribed_ty } ExprKind::If(cond, then_expr, opt_else_expr) => { self.check_then_else(cond, then_expr, opt_else_expr, expr.span, expected) } ExprKind::DropTemps(e) => self.check_expr_with_expectation(e, expected), ExprKind::Array(args) => self.check_expr_array(args, expected, expr), - ExprKind::ConstBlock(ref anon_const) => { - self.check_expr_const_block(anon_const, expected, expr) - } + ExprKind::ConstBlock(ref block) => self.check_expr_const_block(block, expected, expr), ExprKind::Repeat(element, ref count) => { self.check_expr_repeat(element, count, expected, expr) } @@ -359,9 +345,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_struct(expr, expected, qpath, fields, base_expr) } ExprKind::Field(base, field) => self.check_field(expr, &base, field, expected), - ExprKind::Index(base, idx) => self.check_expr_index(base, idx, expr), + ExprKind::Index(base, idx, brackets_span) => { + self.check_expr_index(base, idx, expr, brackets_span) + } ExprKind::Yield(value, ref src) => self.check_expr_yield(value, expr, src), - hir::ExprKind::Err(guar) => tcx.ty_error(guar), + hir::ExprKind::Err(guar) => Ty::new_error(tcx, guar), } } @@ -380,7 +368,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut oprnd_t = self.check_expr_with_expectation(&oprnd, expected_inner); if !oprnd_t.references_error() { - oprnd_t = self.structurally_resolved_type(expr.span, oprnd_t); + oprnd_t = self.structurally_resolve_type(expr.span, oprnd_t); match unop { hir::UnOp::Deref => { if let Some(ty) = self.lookup_derefing(expr, oprnd, oprnd_t) { @@ -399,7 +387,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); } - oprnd_t = tcx.ty_error(err.emit()); + oprnd_t = Ty::new_error(tcx, err.emit()); } } hir::UnOp::Not => { @@ -449,10 +437,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let tm = ty::TypeAndMut { ty, mutbl }; match kind { - _ if tm.ty.references_error() => self.tcx.ty_error_misc(), + _ if tm.ty.references_error() => Ty::new_misc_error(self.tcx), hir::BorrowKind::Raw => { self.check_named_place_expr(oprnd); - self.tcx.mk_ptr(tm) + Ty::new_ptr(self.tcx, tm) } hir::BorrowKind::Ref => { // Note: at this point, we cannot say what the best lifetime @@ -470,7 +458,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // whose address was taken can actually be made to live as long // as it needs to live. let region = self.next_region_var(infer::AddrOfRegion(expr.span)); - self.tcx.mk_ref(region, tm) + Ty::new_ref(self.tcx, region, tm) } } } @@ -528,11 +516,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let e = self.tcx.sess.delay_span_bug(qpath.span(), "`Res::Err` but no error emitted"); self.set_tainted_by_errors(e); - tcx.ty_error(e) + Ty::new_error(tcx, e) } Res::Def(DefKind::Variant, _) => { let e = report_unexpected_variant_res(tcx, res, qpath, expr.span, "E0533", "value"); - tcx.ty_error(e) + Ty::new_error(tcx, e) } _ => self.instantiate_value_path(segs, opt_ty, res, expr.span, expr.hir_id).0, }; @@ -593,8 +581,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We always require that the type provided as the value for // a type parameter outlives the moment of instantiation. - let substs = self.typeck_results.borrow().node_substs(expr.hir_id); - self.add_wf_bounds(substs, expr); + let args = self.typeck_results.borrow().node_args(expr.hir_id); + self.add_wf_bounds(args, expr); ty } @@ -620,7 +608,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Some(ctxt) => ctxt.coerce.as_ref().map(|coerce| coerce.expected_ty()), None => { // Avoid ICE when `break` is inside a closure (#65383). - return tcx.ty_error_with_message( + return Ty::new_error_with_message( + tcx, expr.span, "break was outside loop, but no error was emitted", ); @@ -631,7 +620,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If the loop context is not a `loop { }`, then break with // a value is illegal, and `opt_coerce_to` will be `None`. // Just set expectation to error in that case. - let coerce_to = opt_coerce_to.unwrap_or_else(|| tcx.ty_error_misc()); + let coerce_to = opt_coerce_to.unwrap_or_else(|| Ty::new_misc_error(tcx)); // Recurse without `enclosing_breakables` borrowed. e_ty = self.check_expr_with_hint(e, coerce_to); @@ -639,7 +628,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { // Otherwise, this is a break *without* a value. That's // always legal, and is equivalent to `break ()`. - e_ty = tcx.mk_unit(); + e_ty = Ty::new_unit(tcx); cause = self.misc(expr.span); } @@ -649,7 +638,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut enclosing_breakables = self.enclosing_breakables.borrow_mut(); let Some(ctxt) = enclosing_breakables.opt_find_breakable(target_id) else { // Avoid ICE when `break` is inside a closure (#65383). - return tcx.ty_error_with_message( + return Ty::new_error_with_message( + tcx, expr.span, "break was outside loop, but no error was emitted", ); @@ -664,7 +654,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { coerce.coerce_forced_unit( self, &cause, - &mut |mut err| { + |mut err| { self.suggest_mismatched_types_on_tail( &mut err, expr, ty, e_ty, target_id, ); @@ -707,7 +697,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // this can only happen if the `break` was not // inside a loop at all, which is caught by the // loop-checking pass. - let err = self.tcx.ty_error_with_message( + let err = Ty::new_error_with_message( + self.tcx, expr.span, "break was outside loop, but no error was emitted", ); @@ -737,47 +728,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &'tcx hir::Expr<'tcx>, ) -> Ty<'tcx> { if self.ret_coercion.is_none() { - let mut err = ReturnStmtOutsideOfFnBody { - span: expr.span, - encl_body_span: None, - encl_fn_span: None, - }; - - let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id); - - if let Some(hir::Node::Item(hir::Item { - kind: hir::ItemKind::Fn(..), - span: encl_fn_span, - .. - })) - | Some(hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)), - span: encl_fn_span, - .. - })) - | Some(hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Fn(..), - span: encl_fn_span, - .. - })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id) - { - // We are inside a function body, so reporting "return statement - // outside of function body" needs an explanation. - - let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id); - - // If this didn't hold, we would not have to report an error in - // the first place. - assert_ne!(encl_item_id.def_id, encl_body_owner_id); - - let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id); - let encl_body = self.tcx.hir().body(encl_body_id); - - err.encl_body_span = Some(encl_body.value.span); - err.encl_fn_span = Some(*encl_fn_span); - } - - self.tcx.sess.emit_err(err); + self.emit_return_outside_of_fn_body(expr, ReturnLikeStatementKind::Return); if let Some(e) = expr_opt { // We still have to type-check `e` (issue #86188), but calling @@ -799,7 +750,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { coercion.coerce_forced_unit( self, &cause, - &mut |db| { + |db| { let span = fn_decl.output.span(); if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { db.span_label( @@ -811,12 +762,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { true, ); } else { - coercion.coerce_forced_unit(self, &cause, &mut |_| (), true); + coercion.coerce_forced_unit(self, &cause, |_| (), true); } } self.tcx.types.never } + fn check_expr_become( + &self, + call: &'tcx hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + match &self.ret_coercion { + Some(ret_coercion) => { + let ret_ty = ret_coercion.borrow().expected_ty(); + let call_expr_ty = self.check_expr_with_hint(call, ret_ty); + + // N.B. don't coerce here, as tail calls can't support most/all coercions + // FIXME(explicit_tail_calls): add a diagnostic note that `become` doesn't allow coercions + self.demand_suptype(expr.span, ret_ty, call_expr_ty); + } + None => { + self.emit_return_outside_of_fn_body(expr, ReturnLikeStatementKind::Become); + + // Fallback to simply type checking `call` without hint/demanding the right types. + // Best effort to highlight more errors. + self.check_expr(call); + } + } + + self.tcx.types.never + } + + /// Check an expression that _is being returned_. + /// For example, this is called with `return_expr: $expr` when `return $expr` + /// is encountered. + /// + /// Note that this function must only be called in function bodies. + /// /// `explicit_return` is `true` if we're checking an explicit `return expr`, /// and `false` if we're checking a trailing expression. pub(super) fn check_return_expr( @@ -833,10 +816,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut span = return_expr.span; // Use the span of the trailing expression for our cause, // not the span of the entire function - if !explicit_return { - if let ExprKind::Block(body, _) = return_expr.kind && let Some(last_expr) = body.expr { + if !explicit_return + && let ExprKind::Block(body, _) = return_expr.kind + && let Some(last_expr) = body.expr + { span = last_expr.span; - } } ret_coercion.borrow_mut().coerce( self, @@ -856,6 +840,55 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Emit an error because `return` or `become` is used outside of a function body. + /// + /// `expr` is the `return` (`become`) "statement", `kind` is the kind of the statement + /// either `Return` or `Become`. + fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: ReturnLikeStatementKind) { + let mut err = ReturnStmtOutsideOfFnBody { + span: expr.span, + encl_body_span: None, + encl_fn_span: None, + statement_kind: kind, + }; + + let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id); + + if let Some(hir::Node::Item(hir::Item { + kind: hir::ItemKind::Fn(..), + span: encl_fn_span, + .. + })) + | Some(hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)), + span: encl_fn_span, + .. + })) + | Some(hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(..), + span: encl_fn_span, + .. + })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id) + { + // We are inside a function body, so reporting "return statement + // outside of function body" needs an explanation. + + let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id); + + // If this didn't hold, we would not have to report an error in + // the first place. + assert_ne!(encl_item_id.def_id, encl_body_owner_id); + + let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id); + let encl_body = self.tcx.hir().body(encl_body_id); + + err.encl_body_span = Some(encl_body.value.span); + err.encl_fn_span = Some(*encl_fn_span); + } + + self.tcx.sess.emit_err(err); + } + fn point_at_return_for_opaque_ty_error( &self, errors: &mut Vec>, @@ -1030,7 +1063,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let result_ty = coerce.complete(self); - if let Err(guar) = cond_ty.error_reported() { self.tcx.ty_error(guar) } else { result_ty } + if let Err(guar) = cond_ty.error_reported() { + Ty::new_error(self.tcx, guar) + } else { + result_ty + } } /// Type check assignment expression `expr` of form `lhs = rhs`. @@ -1048,7 +1085,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // The expected type is `bool` but this will result in `()` so we can reasonably // say that the user intended to write `lhs == rhs` instead of `lhs = rhs`. // The likely cause of this is `if foo = bar { .. }`. - let actual_ty = self.tcx.mk_unit(); + let actual_ty = Ty::new_unit(self.tcx); let mut err = self.demand_suptype_diag(expr.span, expected_ty, actual_ty).unwrap(); let lhs_ty = self.check_expr(&lhs); let rhs_ty = self.check_expr(&rhs); @@ -1106,7 +1143,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If the assignment expression itself is ill-formed, don't // bother emitting another error let reported = err.emit_unless(lhs_ty.references_error() || rhs_ty.references_error()); - return self.tcx.ty_error(reported); + return Ty::new_error(self.tcx, reported); } let lhs_ty = self.check_expr_with_needs(&lhs, Needs::MutPlace); @@ -1153,9 +1190,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.require_type_is_sized(lhs_ty, lhs.span, traits::AssignmentLhsSized); if let Err(guar) = (lhs_ty, rhs_ty).error_reported() { - self.tcx.ty_error(guar) + Ty::new_error(self.tcx, guar) } else { - self.tcx.mk_unit() + Ty::new_unit(self.tcx) } } @@ -1210,7 +1247,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // [1] self.tcx.sess.delay_span_bug(body.span, "no coercion, but loop may not break"); } - ctxt.coerce.map(|c| c.complete(self)).unwrap_or_else(|| self.tcx.mk_unit()) + ctxt.coerce.map(|c| c.complete(self)).unwrap_or_else(|| Ty::new_unit(self.tcx)) } /// Checks a method call. @@ -1224,14 +1261,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> Ty<'tcx> { let rcvr_t = self.check_expr(&rcvr); // no need to check for bot/err -- callee does that - let rcvr_t = self.structurally_resolved_type(rcvr.span, rcvr_t); + let rcvr_t = self.structurally_resolve_type(rcvr.span, rcvr_t); let span = segment.ident.span; let method = match self.lookup_method(rcvr_t, segment, span, expr, rcvr, args) { Ok(method) => { // We could add a "consider `foo::`" suggestion here, but I wasn't able to - // trigger this codepath causing `structurally_resolved_type` to emit an error. + // trigger this codepath causing `structurally_resolve_type` to emit an error. + self.enforce_context_effects(expr.hir_id, expr.span, method.def_id, method.args); self.write_method_call(expr.hir_id, method); Ok(method) } @@ -1243,7 +1281,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { segment.ident, SelfSource::MethodCall(rcvr), error, - Some((rcvr, args)), + Some(MethodCallComponents { receiver: rcvr, args, full_expr: expr }), expected, false, ) { @@ -1273,7 +1311,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Eagerly check for some obvious errors. if let Err(guar) = (t_expr, t_cast).error_reported() { - self.tcx.ty_error(guar) + Ty::new_error(self.tcx, guar) } else { // Defer other checks until we're done type checking. let mut deferred_cast_checks = self.deferred_cast_checks.borrow_mut(); @@ -1284,7 +1322,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { t_cast, t.span, expr.span, - self.param_env.constness(), + hir::Constness::NotConst, ) { Ok(cast_check) => { debug!( @@ -1294,7 +1332,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { deferred_cast_checks.push(cast_check); t_cast } - Err(guar) => self.tcx.ty_error(guar), + Err(guar) => Ty::new_error(self.tcx, guar), } } } @@ -1334,18 +1372,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let array_len = args.len() as u64; self.suggest_array_len(expr, array_len); - self.tcx.mk_array(element_ty, array_len) + Ty::new_array(self.tcx, element_ty, array_len) } fn suggest_array_len(&self, expr: &'tcx hir::Expr<'tcx>, array_len: u64) { let parent_node = self.tcx.hir().parent_iter(expr.hir_id).find(|(_, node)| { !matches!(node, hir::Node::Expr(hir::Expr { kind: hir::ExprKind::AddrOf(..), .. })) }); - let Some((_, + let Some(( + _, hir::Node::Local(hir::Local { ty: Some(ty), .. }) - | hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _), .. })) - ) = parent_node else { - return + | hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _, _), .. }), + )) = parent_node + else { + return; }; if let hir::TyKind::Array(_, length) = ty.peel_refs().kind && let hir::ArrayLen::Body(hir::AnonConst { hir_id, .. }) = length @@ -1368,20 +1408,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn check_expr_const_block( &self, - anon_const: &'tcx hir::AnonConst, + block: &'tcx hir::ConstBlock, expected: Expectation<'tcx>, _expr: &'tcx hir::Expr<'tcx>, ) -> Ty<'tcx> { - let body = self.tcx.hir().body(anon_const.body); + let body = self.tcx.hir().body(block.body); // Create a new function context. - let def_id = anon_const.def_id; - let fcx = FnCtxt::new(self, self.param_env.with_const(), def_id); + let def_id = block.def_id; + let fcx = FnCtxt::new(self, self.param_env, def_id); crate::GatherLocalsVisitor::new(&fcx).visit_body(body); let ty = fcx.check_expr_with_expectation(&body.value, expected); fcx.require_type_is_sized(ty, body.value.span, traits::ConstSized); - fcx.write_ty(anon_const.hir_id, ty); + fcx.write_ty(block.hir_id, ty); ty } @@ -1422,18 +1462,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if let Err(guar) = element_ty.error_reported() { - return tcx.ty_error(guar); + return Ty::new_error(tcx, guar); } self.check_repeat_element_needs_copy_bound(element, count, element_ty); self.register_wf_obligation( - tcx.mk_array_with_const_len(t, count).into(), + Ty::new_array_with_const_len(tcx, t, count).into(), expr.span, traits::WellFormed(None), ); - tcx.mk_array_with_const_len(t, count) + Ty::new_array_with_const_len(tcx, t, count) } fn check_repeat_element_needs_copy_bound( @@ -1496,9 +1536,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } _ => self.check_expr_with_expectation(&e, NoExpectation), }); - let tuple = self.tcx.mk_tup_from_iter(elt_ts_iter); + let tuple = Ty::new_tup_from_iter(self.tcx, elt_ts_iter); if let Err(guar) = tuple.error_reported() { - self.tcx.ty_error(guar) + Ty::new_error(self.tcx, guar) } else { self.require_type_is_sized(tuple, expr.span, traits::TupleInitializerSized); tuple @@ -1518,7 +1558,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(data) => data, Err(guar) => { self.check_struct_fields_on_error(fields, base_expr); - return self.tcx.ty_error(guar); + return Ty::new_error(self.tcx, guar); } }; @@ -1568,7 +1608,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // re-link the regions that EIfEO can erase. self.demand_eqtype(span, adt_ty_hint, adt_ty); - let ty::Adt(adt, substs) = adt_ty.kind() else { + let ty::Adt(adt, args) = adt_ty.kind() else { span_bug!(span, "non-ADT passed to check_expr_struct_fields"); }; let adt_kind = adt.adt_kind(); @@ -1597,7 +1637,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tcx.check_stability(v_field.did, Some(expr_id), field.span, None); } - self.field_ty(field.span, v_field, substs) + self.field_ty(field.span, v_field, args) } else { error_happened = true; let guar = if let Some(prev_span) = seen_fields.get(&ident) { @@ -1617,7 +1657,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }; - tcx.ty_error(guar) + Ty::new_error(tcx, guar) }; // Make sure to give a type to the field even if there's @@ -1629,7 +1669,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(mut diag) = diag { if idx == ast_fields.len() - 1 { if remaining_fields.is_empty() { - self.suggest_fru_from_range(field, variant, substs, &mut diag); + self.suggest_fru_from_range(field, variant, args, &mut diag); diag.emit(); } else { diag.stash(field.span, StashKey::MaybeFruTypo); @@ -1669,7 +1709,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let fru_tys = if self.tcx.features().type_changing_struct_update { if adt.is_struct() { // Make some fresh substitutions for our ADT type. - let fresh_substs = self.fresh_substs_for_item(base_expr.span, adt.did()); + let fresh_args = self.fresh_args_for_item(base_expr.span, adt.did()); // We do subtyping on the FRU fields first, so we can // learn exactly what types we expect the base expr // needs constrained to be compatible with the struct @@ -1678,13 +1718,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .fields .iter() .map(|f| { - let fru_ty = self.normalize( - expr_span, - self.field_ty(base_expr.span, f, fresh_substs), - ); + let fru_ty = self + .normalize(expr_span, self.field_ty(base_expr.span, f, fresh_args)); let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id); if let Some(_) = remaining_fields.remove(&ident) { - let target_ty = self.field_ty(base_expr.span, f, substs); + let target_ty = self.field_ty(base_expr.span, f, args); let cause = self.misc(base_expr.span); match self.at(&cause, self.param_env).sup( DefineOpaqueTypes::No, @@ -1711,7 +1749,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.resolve_vars_if_possible(fru_ty) }) .collect(); - // The use of fresh substs that we have subtyped against + // The use of fresh args that we have subtyped against // our base ADT type's fields allows us to guide inference // along so that, e.g. // ``` @@ -1729,7 +1767,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // `MyStruct<'a, _, F2, C>`, as opposed to just `_`... // This is important to allow coercions to happen in // `other_struct` itself. See `coerce-in-base-expr.rs`. - let fresh_base_ty = self.tcx.mk_adt(*adt, fresh_substs); + let fresh_base_ty = Ty::new_adt(self.tcx, *adt, fresh_args); self.check_expr_has_type_or_error( base_expr, self.resolve_vars_if_possible(fresh_base_ty), @@ -1761,10 +1799,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }); match adt_ty.kind() { - ty::Adt(adt, substs) if adt.is_struct() => variant + ty::Adt(adt, args) if adt.is_struct() => variant .fields .iter() - .map(|f| self.normalize(expr_span, f.ty(self.tcx, substs))) + .map(|f| self.normalize(expr_span, f.ty(self.tcx, args))) .collect(), _ => { self.tcx @@ -1792,7 +1830,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { remaining_fields, variant, ast_fields, - substs, + args, ); } } @@ -1829,7 +1867,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { remaining_fields: FxHashMap, variant: &'tcx ty::VariantDef, ast_fields: &'tcx [hir::ExprField<'tcx>], - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) { let len = remaining_fields.len(); @@ -1840,7 +1878,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut truncated_fields_error = String::new(); let remaining_fields_names = match &displayable_field_names[..] { - [field1] => format!("`{}`", field1), + [field1] => format!("`{field1}`"), [field1, field2] => format!("`{field1}` and `{field2}`"), [field1, field2, field3] => format!("`{field1}`, `{field2}` and `{field3}`"), _ => { @@ -1868,7 +1906,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(span, format!("missing {remaining_fields_names}{truncated_fields_error}")); if let Some(last) = ast_fields.last() { - self.suggest_fru_from_range(last, variant, substs, &mut err); + self.suggest_fru_from_range(last, variant, args, &mut err); } err.emit(); @@ -1880,7 +1918,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, last_expr_field: &hir::ExprField<'tcx>, variant: &ty::VariantDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, err: &mut Diagnostic, ) { // I don't use 'is_range_literal' because only double-sided, half-open ranges count. @@ -1893,7 +1931,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { variant.fields.iter().find(|field| field.ident(self.tcx) == last_expr_field.ident) && let range_def_id = self.tcx.lang_items().range_struct() && variant_field - .and_then(|field| field.ty(self.tcx, substs).ty_adt_def()) + .and_then(|field| field.ty(self.tcx, args).ty_adt_def()) .map(|adt| adt.did()) != range_def_id { @@ -2067,29 +2105,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } _ => { - err.span_label(variant_ident_span, format!("`{adt}` defined here", adt = ty)); + err.span_label(variant_ident_span, format!("`{ty}` defined here")); err.span_label(field.ident.span, "field does not exist"); err.span_suggestion_verbose( expr_span, - format!( - "`{adt}` is a tuple {kind_name}, use the appropriate syntax", - adt = ty, - kind_name = kind_name, - ), - format!("{adt}(/* fields */)", adt = ty), + format!("`{ty}` is a tuple {kind_name}, use the appropriate syntax",), + format!("{ty}(/* fields */)"), Applicability::HasPlaceholders, ); } }, _ => { // prevent all specified fields from being suggested - let skip_fields = skip_fields.iter().map(|x| x.ident.name); - if let Some(field_name) = self.suggest_field_name( - variant, - field.ident.name, - skip_fields.collect(), - expr_span, - ) { + let skip_fields: Vec<_> = skip_fields.iter().map(|x| x.ident.name).collect(); + if let Some(field_name) = + self.suggest_field_name(variant, field.ident.name, &skip_fields, expr_span) + { err.span_suggestion( field.ident.span, "a field with a similar name exists", @@ -2110,9 +2141,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!("`{ty}` does not have this field"), ); } - let available_field_names = + let mut available_field_names = self.available_field_names(variant, expr_span); - if !available_field_names.is_empty() { + available_field_names + .retain(|name| skip_fields.iter().all(|skip| name != skip)); + if available_field_names.is_empty() { + err.note("all struct fields are already assigned"); + } else { err.note(format!( "available fields are: {}", self.name_series_display(available_field_names) @@ -2132,7 +2167,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, variant: &'tcx ty::VariantDef, field: Symbol, - skip: Vec, + skip: &[Symbol], // The span where stability will be checked span: Span, ) -> Option { @@ -2192,7 +2227,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // dynamic limit, to never omit just one field let limit = if names.len() == 6 { 6 } else { 5 }; let mut display = - names.iter().take(limit).map(|n| format!("`{}`", n)).collect::>().join(", "); + names.iter().take(limit).map(|n| format!("`{n}`")).collect::>().join(", "); if names.len() > limit { display = format!("{} ... and {} others", display, names.len() - limit); } @@ -2209,13 +2244,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> Ty<'tcx> { debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field); let base_ty = self.check_expr(base); - let base_ty = self.structurally_resolved_type(base.span, base_ty); + let base_ty = self.structurally_resolve_type(base.span, base_ty); let mut private_candidate = None; let mut autoderef = self.autoderef(expr.span, base_ty); while let Some((deref_base_ty, _)) = autoderef.next() { debug!("deref_base_ty: {:?}", deref_base_ty); match deref_base_ty.kind() { - ty::Adt(base_def, substs) if !base_def.is_enum() => { + ty::Adt(base_def, args) if !base_def.is_enum() => { debug!("struct named {:?}", deref_base_ty); let body_hir_id = self.tcx.hir().local_def_id_to_hir_id(self.body_id); let (ident, def_scope) = @@ -2225,7 +2260,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .iter_enumerated() .find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == ident) { - let field_ty = self.field_ty(expr.span, field, substs); + let field_ty = self.field_ty(expr.span, field, args); // Save the index of all fields regardless of their visibility in case // of error recovery. self.write_field_index(expr.hir_id, index); @@ -2257,7 +2292,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => {} } } - self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); + self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false)); if let Some((adjustments, did)) = private_candidate { // (#90483) apply adjustments to avoid ExprUseVisitor from @@ -2270,7 +2305,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { did, expected.only_has_type(self), ); - return self.tcx().ty_error(guar); + return Ty::new_error(self.tcx(), guar); } let guar = if field.name == kw::Empty { @@ -2356,7 +2391,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.emit() }; - self.tcx().ty_error(guar) + Ty::new_error(self.tcx(), guar) } fn suggest_await_on_field_access( @@ -2366,7 +2401,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { base: &'tcx hir::Expr<'tcx>, ty: Ty<'tcx>, ) { - let Some(output_ty) = self.get_impl_future_output_ty(ty) else { return; }; + let Some(output_ty) = self.get_impl_future_output_ty(ty) else { + return; + }; let mut add_label = true; if let ty::Adt(def, _) = output_ty.kind() { // no field access on enum type @@ -2584,7 +2621,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { access_span: Span, ) { if let Some(suggested_field_name) = - self.suggest_field_name(def.non_enum_variant(), field.name, vec![], access_span) + self.suggest_field_name(def.non_enum_variant(), field.name, &[], access_span) { err.span_suggestion( field.span, @@ -2661,7 +2698,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // try to add a suggestion in case the field is a nested field of a field of the Adt let mod_id = self.tcx.parent_module(id).to_def_id(); - if let Some((fields, substs)) = + if let Some((fields, args)) = self.get_field_candidates_considering_privacy(span, expr_t, mod_id) { let candidate_fields: Vec<_> = fields @@ -2670,7 +2707,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, &|candidate_field, _| candidate_field.ident(self.tcx()) == field, candidate_field, - substs, + args, vec![], mod_id, ) @@ -2725,12 +2762,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span: Span, base_ty: Ty<'tcx>, mod_id: DefId, - ) -> Option<(impl Iterator + 'tcx, SubstsRef<'tcx>)> { + ) -> Option<(impl Iterator + 'tcx, GenericArgsRef<'tcx>)> { debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty); for (base_t, _) in self.autoderef(span, base_ty) { match base_t.kind() { - ty::Adt(base_def, substs) if !base_def.is_enum() => { + ty::Adt(base_def, args) if !base_def.is_enum() => { let tcx = self.tcx; let fields = &base_def.non_enum_variant().fields; // Some struct, e.g. some that impl `Deref`, have all private fields @@ -2745,7 +2782,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .filter(move |field| field.vis.is_accessible_from(mod_id, tcx)) // For compile-time reasons put a limit on number of fields we search .take(100), - substs, + args, )); } _ => {} @@ -2761,7 +2798,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span: Span, matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool, candidate_field: &ty::FieldDef, - subst: SubstsRef<'tcx>, + subst: GenericArgsRef<'tcx>, mut field_path: Vec, mod_id: DefId, ) -> Option> { @@ -2805,6 +2842,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { base: &'tcx hir::Expr<'tcx>, idx: &'tcx hir::Expr<'tcx>, expr: &'tcx hir::Expr<'tcx>, + brackets_span: Span, ) -> Ty<'tcx> { let base_t = self.check_expr(&base); let idx_t = self.check_expr(&idx); @@ -2814,7 +2852,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if idx_t.references_error() { idx_t } else { - let base_t = self.structurally_resolved_type(base.span, base_t); + let base_t = self.structurally_resolve_type(base.span, base_t); match self.lookup_indexing(expr, base, base_t, idx, idx_t) { Some((index_ty, element_ty)) => { // two-phase not needed because index_ty is never mutable @@ -2838,7 +2876,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut err = type_error_struct!( self.tcx.sess, - expr.span, + brackets_span, base_t, E0608, "cannot index into a value of type `{base_t}`", @@ -2852,16 +2890,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && let ast::LitKind::Int(i, ast::LitIntType::Unsuffixed) = lit.node && i < types.len().try_into().expect("expected tuple index to be < usize length") { - let snip = self.tcx.sess.source_map().span_to_snippet(base.span); - if let Ok(snip) = snip { - err.span_suggestion( - expr.span, - "to access tuple elements, use", - format!("{snip}.{i}"), - Applicability::MachineApplicable, - ); - needs_note = false; - } + + err.span_suggestion( + brackets_span, + "to access tuple elements, use", + format!(".{i}"), + Applicability::MachineApplicable, + ); + needs_note = false; } else if let ExprKind::Path(..) = idx.peel_borrows().kind { err.span_label(idx.span, "cannot access tuple elements at a variable index"); } @@ -2872,8 +2908,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } } + + if base_t.is_unsafe_ptr() && idx_t.is_integral() { + err.multipart_suggestion( + "consider using `wrapping_add` or `add` for indexing into raw pointer", + vec![ + (base.span.between(idx.span), ".wrapping_add(".to_owned()), + ( + idx.span.shrink_to_hi().until(expr.span.shrink_to_hi()), + ")".to_owned(), + ), + ], + Applicability::MaybeIncorrect, + ); + } + let reported = err.emit(); - self.tcx.ty_error(reported) + Ty::new_error(self.tcx, reported) } } } @@ -2904,10 +2955,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; self.commit_if_ok(|_| { - let ocx = ObligationCtxt::new_in_snapshot(self); - let impl_substs = self.fresh_substs_for_item(base_expr.span, impl_def_id); + let ocx = ObligationCtxt::new(self); + let impl_args = self.fresh_args_for_item(base_expr.span, impl_def_id); let impl_trait_ref = - self.tcx.impl_trait_ref(impl_def_id).unwrap().subst(self.tcx, impl_substs); + self.tcx.impl_trait_ref(impl_def_id).unwrap().instantiate(self.tcx, impl_args); let cause = self.misc(base_expr.span); // Match the impl self type against the base ty. If this fails, @@ -2924,7 +2975,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::Binder::dummy(ty::TraitPredicate { trait_ref: impl_trait_ref, polarity: ty::ImplPolarity::Positive, - constness: ty::BoundConstness::NotConst, }), |derived| { traits::ImplDerivedObligation(Box::new( @@ -2939,7 +2989,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }, self.param_env, - self.tcx.predicates_of(impl_def_id).instantiate(self.tcx, impl_substs), + self.tcx.predicates_of(impl_def_id).instantiate(self.tcx, impl_args), )); // Normalize the output type, which we can use later on as the @@ -2947,7 +2997,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let element_ty = ocx.normalize( &cause, self.param_env, - self.tcx.mk_projection(index_trait_output_def_id, impl_trait_ref.substs), + Ty::new_projection(self.tcx, index_trait_output_def_id, impl_trait_ref.args), ); let errors = ocx.select_where_possible(); @@ -2955,7 +3005,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // will still delay a span bug in `report_fulfillment_errors`. Ok::<_, NoSolution>(( self.err_ctxt().report_fulfillment_errors(&errors), - impl_trait_ref.substs.type_at(1), + impl_trait_ref.args.type_at(1), element_ty, )) }) @@ -2969,7 +3019,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { for error in errors { match error.obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) + ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) if self.tcx.is_diagnostic_item(sym::SliceIndex, predicate.trait_ref.def_id) => { } _ => continue, @@ -2995,14 +3045,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // information. Hence, we check the source of the yield expression here and check its // value's type against `()` (this check should always hold). None if src.is_await() => { - self.check_expr_coercible_to_type(&value, self.tcx.mk_unit(), None); - self.tcx.mk_unit() + self.check_expr_coercible_to_type(&value, Ty::new_unit(self.tcx), None); + Ty::new_unit(self.tcx) } _ => { self.tcx.sess.emit_err(YieldExprOutsideOfGenerator { span: expr.span }); // Avoid expressions without types during writeback (#78653). self.check_expr(value); - self.tcx.mk_unit() + Ty::new_unit(self.tcx) } } } @@ -3026,14 +3076,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // allows them to be inferred based on how they are used later in the // function. if is_input { - let ty = self.structurally_resolved_type(expr.span, ty); + let ty = self.structurally_resolve_type(expr.span, ty); match *ty.kind() { ty::FnDef(..) => { - let fnptr_ty = self.tcx.mk_fn_ptr(ty.fn_sig(self.tcx)); + let fnptr_ty = Ty::new_fn_ptr(self.tcx, ty.fn_sig(self.tcx)); self.demand_coerce(expr, ty, fnptr_ty, None, AllowTwoPhase::No); } ty::Ref(_, base_ty, mutbl) => { - let ptr_ty = self.tcx.mk_ptr(ty::TypeAndMut { ty: base_ty, mutbl }); + let ptr_ty = Ty::new_ptr(self.tcx, ty::TypeAndMut { ty: base_ty, mutbl }); self.demand_coerce(expr, ty, ptr_ty, None, AllowTwoPhase::No); } _ => {} @@ -3068,7 +3118,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if asm.options.contains(ast::InlineAsmOptions::NORETURN) { self.tcx.types.never } else { - self.tcx.mk_unit() + Ty::new_unit(self.tcx) } } @@ -3084,10 +3134,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut current_container = container; for &field in fields { - let container = self.structurally_resolved_type(expr.span, current_container); + let container = self.structurally_resolve_type(expr.span, current_container); match container.kind() { - ty::Adt(container_def, substs) if !container_def.is_enum() => { + ty::Adt(container_def, args) if !container_def.is_enum() => { let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id); let (ident, def_scope) = self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block); @@ -3097,7 +3147,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .iter_enumerated() .find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == ident) { - let field_ty = self.field_ty(expr.span, field, substs); + let field_ty = self.field_ty(expr.span, field, args); // FIXME: DSTs with static alignment should be allowed self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation); @@ -3117,16 +3167,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } ty::Tuple(tys) => { - let fstr = field.as_str(); - - if let Ok(index) = fstr.parse::() { - if fstr == index.to_string() { - if let Some(&field_ty) = tys.get(index) { - field_indices.push(index.into()); - current_container = field_ty; + if let Ok(index) = field.as_str().parse::() + && field.name == sym::integer(index) + { + for ty in tys.iter().take(index + 1) { + self.require_type_is_sized(ty, expr.span, traits::MiscObligation); + } + if let Some(&field_ty) = tys.get(index) { + field_indices.push(index.into()); + current_container = field_ty; - continue; - } + continue; } } } diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index 94b6a0f8f47d7..840910732d89f 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -211,7 +211,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { self.select_from_expr(base); } - hir::ExprKind::Index(lhs, rhs) => { + hir::ExprKind::Index(lhs, rhs, _) => { // lhs[rhs] self.select_from_expr(lhs); self.consume_expr(rhs); @@ -326,6 +326,10 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { } } + hir::ExprKind::Become(call) => { + self.consume_expr(call); + } + hir::ExprKind::Assign(lhs, rhs, _) => { self.mutate_expr(lhs); self.consume_expr(rhs); @@ -438,12 +442,19 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { // to borrow discr. needs_to_be_read = true; } - PatKind::Or(_) - | PatKind::Box(_) - | PatKind::Slice(..) - | PatKind::Ref(..) - | PatKind::Wild => { - // If the PatKind is Or, Box, Slice or Ref, the decision is made later + PatKind::Slice(lhs, wild, rhs) => { + // We don't need to test the length if the pattern is `[..]` + if matches!((lhs, wild, rhs), (&[], Some(_), &[])) + // Arrays have a statically known size, so + // there is no need to read their length + || place.place.ty().peel_refs().is_array() + { + } else { + needs_to_be_read = true; + } + } + PatKind::Or(_) | PatKind::Box(_) | PatKind::Ref(..) | PatKind::Wild => { + // If the PatKind is Or, Box, or Ref, the decision is made later // as these patterns contains subpatterns // If the PatKind is Wild, the decision is made based on the other patterns being // examined @@ -538,7 +549,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { // Select just those fields of the `with` // expression that will actually be used match with_place.place.ty().kind() { - ty::Adt(adt, substs) if adt.is_struct() => { + ty::Adt(adt, args) if adt.is_struct() => { // Consume those fields of the with expression that are needed. for (f_index, with_field) in adt.non_enum_variant().fields.iter_enumerated() { let is_mentioned = fields @@ -548,7 +559,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { let field_place = self.mc.cat_projection( &*with_expr, with_place.clone(), - with_field.ty(self.tcx(), substs), + with_field.ty(self.tcx(), args), ProjectionKind::Field(f_index, FIRST_VARIANT), ); self.delegate_consume(&field_place, field_place.hir_id); diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index b7ae621c6859f..5b5986a349f07 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -1,8 +1,8 @@ use crate::FnCtxt; use rustc_data_structures::{ - fx::{FxHashMap, FxHashSet}, graph::WithSuccessors, graph::{iterate::DepthFirstSearch, vec_graph::VecGraph}, + unord::{UnordBag, UnordMap, UnordSet}, }; use rustc_middle::ty::{self, Ty}; @@ -83,7 +83,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { fn fallback_if_possible( &self, ty: Ty<'tcx>, - diverging_fallback: &FxHashMap, Ty<'tcx>>, + diverging_fallback: &UnordMap, Ty<'tcx>>, ) { // Careful: we do NOT shallow-resolve `ty`. We know that `ty` // is an unsolved variable, and we determine its fallback @@ -104,7 +104,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // type, `?T` is not considered unsolved, but `?I` is. The // same is true for float variables.) let fallback = match ty.kind() { - _ if let Some(e) = self.tainted_by_errors() => self.tcx.ty_error(e), + _ if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx,e), ty::Infer(ty::IntVar(_)) => self.tcx.types.i32, ty::Infer(ty::FloatVar(_)) => self.tcx.types.f64, _ => match diverging_fallback.get(&ty) { @@ -193,7 +193,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { fn calculate_diverging_fallback( &self, unsolved_variables: &[Ty<'tcx>], - ) -> FxHashMap, Ty<'tcx>> { + ) -> UnordMap, Ty<'tcx>> { debug!("calculate_diverging_fallback({:?})", unsolved_variables); // Construct a coercion graph where an edge `A -> B` indicates @@ -210,10 +210,10 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // // These variables are the ones that are targets for fallback to // either `!` or `()`. - let diverging_roots: FxHashSet = self + let diverging_roots: UnordSet = self .diverging_type_vars .borrow() - .iter() + .items() .map(|&ty| self.shallow_resolve(ty)) .filter_map(|ty| ty.ty_vid()) .map(|vid| self.root_var(vid)) @@ -284,23 +284,27 @@ impl<'tcx> FnCtxt<'_, 'tcx> { // For each diverging variable, figure out whether it can // reach a member of N. If so, it falls back to `()`. Else // `!`. - let mut diverging_fallback = FxHashMap::default(); - diverging_fallback.reserve(diverging_vids.len()); + let mut diverging_fallback = UnordMap::with_capacity(diverging_vids.len()); for &diverging_vid in &diverging_vids { - let diverging_ty = self.tcx.mk_ty_var(diverging_vid); + let diverging_ty = Ty::new_var(self.tcx, diverging_vid); let root_vid = self.root_var(diverging_vid); let can_reach_non_diverging = coercion_graph .depth_first_search(root_vid) .any(|n| roots_reachable_from_non_diverging.visited(n)); - let mut found_infer_var_info = ty::InferVarInfo { self_in_trait: false, output: false }; + let infer_var_infos: UnordBag<_> = self + .inh + .infer_var_info + .borrow() + .items() + .filter(|&(vid, _)| self.infcx.root_var(*vid) == root_vid) + .map(|(_, info)| *info) + .collect(); - for (vid, info) in self.inh.infer_var_info.borrow().iter() { - if self.infcx.root_var(*vid) == root_vid { - found_infer_var_info.self_in_trait |= info.self_in_trait; - found_infer_var_info.output |= info.output; - } - } + let found_infer_var_info = ty::InferVarInfo { + self_in_trait: infer_var_infos.items().any(|info| info.self_in_trait), + output: infer_var_infos.items().any(|info| info.output), + }; if found_infer_var_info.self_in_trait && found_infer_var_info.output { // This case falls back to () to ensure that the code pattern in @@ -334,7 +338,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { diverging_fallback.insert(diverging_ty, self.tcx.types.unit); } else { debug!("fallback to ! - all diverging: {:?}", diverging_vid); - diverging_fallback.insert(diverging_ty, self.tcx.mk_diverging_default()); + diverging_fallback.insert(diverging_ty, Ty::new_diverging_default(self.tcx)); } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 2fdcd09b9a2f4..28fe2e062e589 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -1,7 +1,8 @@ use crate::callee::{self, DeferredCallResolution}; +use crate::errors::CtorIsPrivate; use crate::method::{self, MethodCallee, SelfSource}; use crate::rvalue_scopes; -use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LocalTy, RawTy}; +use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, RawTy}; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, Diagnostic, ErrorGuaranteed, MultiSpan, StashKey}; @@ -11,7 +12,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_hir::{ExprKind, GenericArg, Node, QPath}; use rustc_hir_analysis::astconv::generics::{ - check_generic_arg_count_for_call, create_substs_for_generic_args, + check_generic_arg_count_for_call, create_args_for_parent_generic_args, }; use rustc_hir_analysis::astconv::{ AstConv, CreateSubstsForGenericArgsCtxt, ExplicitLateBound, GenericArgCountMismatch, @@ -27,7 +28,7 @@ use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; use rustc_middle::ty::{ self, AdtKind, CanonicalUserType, GenericParamDefKind, Ty, TyCtxt, UserType, }; -use rustc_middle::ty::{GenericArgKind, SubstsRef, UserSelfTy, UserSubsts}; +use rustc_middle::ty::{GenericArgKind, GenericArgsRef, UserArgs, UserSelfTy}; use rustc_session::lint; use rustc_span::def_id::LocalDefId; use rustc_span::hygiene::DesugaringKind; @@ -60,7 +61,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!("warn_if_unreachable: id={:?} span={:?} kind={}", id, span, kind); - let msg = format!("unreachable {}", kind); + let msg = format!("unreachable {kind}"); self.tcx().struct_span_lint_hir( lint::builtin::UNREACHABLE_CODE, id, @@ -82,16 +83,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// version (resolve_vars_if_possible), this version will /// also select obligations if it seems useful, in an effort /// to get more type information. - pub(in super::super) fn resolve_vars_with_obligations(&self, ty: Ty<'tcx>) -> Ty<'tcx> { - self.resolve_vars_with_obligations_and_mutate_fulfillment(ty, |_| {}) - } - - #[instrument(skip(self, mutate_fulfillment_errors), level = "debug", ret)] - pub(in super::super) fn resolve_vars_with_obligations_and_mutate_fulfillment( - &self, - mut ty: Ty<'tcx>, - mutate_fulfillment_errors: impl Fn(&mut Vec>), - ) -> Ty<'tcx> { + // FIXME(-Ztrait-solver=next): A lot of the calls to this method should + // probably be `try_structurally_resolve_type` or `structurally_resolve_type` instead. + #[instrument(skip(self), level = "debug", ret)] + pub(in super::super) fn resolve_vars_with_obligations(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> { // No Infer()? Nothing needs doing. if !ty.has_non_region_infer() { debug!("no inference var, nothing needs doing"); @@ -109,7 +104,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // possible. This can help substantially when there are // indirect dependencies that don't seem worth tracking // precisely. - self.select_obligations_where_possible(mutate_fulfillment_errors); + self.select_obligations_where_possible(|_| {}); self.resolve_vars_if_possible(ty) } @@ -131,10 +126,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } pub fn tag(&self) -> String { - format!("{:p}", self) + format!("{self:p}") } - pub fn local_ty(&self, span: Span, nid: hir::HirId) -> LocalTy<'tcx> { + pub fn local_ty(&self, span: Span, nid: hir::HirId) -> Ty<'tcx> { self.locals.borrow().get(&nid).cloned().unwrap_or_else(|| { span_bug!(span, "no type for local variable {}", self.tcx.hir().node_to_string(nid)) }) @@ -166,18 +161,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { #[instrument(level = "debug", skip(self))] pub fn write_method_call(&self, hir_id: hir::HirId, method: MethodCallee<'tcx>) { self.write_resolution(hir_id, Ok((DefKind::AssocFn, method.def_id))); - self.write_substs(hir_id, method.substs); + self.write_args(hir_id, method.args); } - pub fn write_substs(&self, node_id: hir::HirId, substs: SubstsRef<'tcx>) { - if !substs.is_empty() { - debug!("write_substs({:?}, {:?}) in fcx {}", node_id, substs, self.tag()); + pub fn write_args(&self, node_id: hir::HirId, args: GenericArgsRef<'tcx>) { + if !args.is_empty() { + debug!("write_args({:?}, {:?}) in fcx {}", node_id, args, self.tag()); - self.typeck_results.borrow_mut().node_substs_mut().insert(node_id, substs); + self.typeck_results.borrow_mut().node_args_mut().insert(node_id, args); } } - /// Given the substs that we just converted from the HIR, try to + /// Given the args that we just converted from the HIR, try to /// canonicalize them and store them as user-given substitutions /// (i.e., substitutions that must be respected by the NLL check). /// @@ -185,19 +180,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// occurred**, so that annotations like `Vec<_>` are preserved /// properly. #[instrument(skip(self), level = "debug")] - pub fn write_user_type_annotation_from_substs( + pub fn write_user_type_annotation_from_args( &self, hir_id: hir::HirId, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, user_self_ty: Option>, ) { debug!("fcx {}", self.tag()); - if Self::can_contain_user_lifetime_bounds((substs, user_self_ty)) { + if Self::can_contain_user_lifetime_bounds((args, user_self_ty)) { let canonicalized = self.canonicalize_user_type_annotation(UserType::TypeOf( def_id, - UserSubsts { substs, user_self_ty }, + UserArgs { args, user_self_ty }, )); debug!(?canonicalized); self.write_user_type_annotation(hir_id, canonicalized); @@ -218,7 +213,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .user_provided_types_mut() .insert(hir_id, canonical_user_type_annotation); } else { - debug!("skipping identity substs"); + debug!("skipping identity args"); } } @@ -303,12 +298,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, span: Span, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> ty::InstantiatedPredicates<'tcx> { let bounds = self.tcx.predicates_of(def_id); - let result = bounds.instantiate(self.tcx, substs); + let result = bounds.instantiate(self.tcx, args); let result = self.normalize(span, result); - debug!("instantiate_bounds(bounds={:?}, substs={:?}) = {:?}", bounds, substs, result); + debug!("instantiate_bounds(bounds={:?}, args={:?}) = {:?}", bounds, args, result); result } @@ -394,11 +389,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty.normalized } - pub(super) fn user_substs_for_adt(ty: RawTy<'tcx>) -> UserSubsts<'tcx> { + pub(super) fn user_args_for_adt(ty: RawTy<'tcx>) -> UserArgs<'tcx> { match (ty.raw.kind(), ty.normalized.kind()) { - (ty::Adt(_, substs), _) => UserSubsts { substs, user_self_ty: None }, - (_, ty::Adt(adt, substs)) => UserSubsts { - substs, + (ty::Adt(_, args), _) => UserArgs { args, user_self_ty: None }, + (_, ty::Adt(adt, args)) => UserArgs { + args, user_self_ty: Some(UserSelfTy { impl_def_id: adt.did(), self_ty: ty.raw }), }, _ => bug!("non-adt type {:?}", ty), @@ -450,7 +445,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn node_ty(&self, id: hir::HirId) -> Ty<'tcx> { match self.typeck_results.borrow().node_types().get(id) { Some(&t) => t, - None if let Some(e) = self.tainted_by_errors() => self.tcx.ty_error(e), + None if let Some(e) = self.tainted_by_errors() => Ty::new_error(self.tcx,e), None => { bug!( "no type for node {} in fcx {}", @@ -464,7 +459,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn node_ty_opt(&self, id: hir::HirId) -> Option> { match self.typeck_results.borrow().node_types().get(id) { Some(&t) => Some(t), - None if let Some(e) = self.tainted_by_errors() => Some(self.tcx.ty_error(e)), + None if let Some(e) = self.tainted_by_errors() => Some(Ty::new_error(self.tcx,e)), None => None, } } @@ -482,13 +477,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx, cause, self.param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(arg)), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg))), )); } - /// Registers obligations that all `substs` are well-formed. - pub fn add_wf_bounds(&self, substs: SubstsRef<'tcx>, expr: &hir::Expr<'_>) { - for arg in substs.iter().filter(|arg| { + /// Registers obligations that all `args` are well-formed. + pub fn add_wf_bounds(&self, args: GenericArgsRef<'tcx>, expr: &hir::Expr<'_>) { + for arg in args.iter().filter(|arg| { matches!(arg.unpack(), GenericArgKind::Type(..) | GenericArgKind::Const(..)) }) { self.register_wf_obligation(arg, expr.span, traits::WellFormed(None)); @@ -502,9 +497,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, span: Span, field: &'tcx ty::FieldDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Ty<'tcx> { - self.normalize(span, field.ty(self.tcx, substs)) + self.normalize(span, field.ty(self.tcx, args)) } pub(in super::super) fn resolve_rvalue_scopes(&self, def_id: DefId) { @@ -551,11 +546,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!(?expr_def_id); // Create the `GeneratorWitness` type that we will unify with `interior`. - let substs = ty::InternalSubsts::identity_for_item( + let args = ty::GenericArgs::identity_for_item( self.tcx, self.tcx.typeck_root_def_id(expr_def_id.to_def_id()), ); - let witness = self.tcx.mk_generator_witness_mir(expr_def_id.to_def_id(), substs); + let witness = Ty::new_generator_witness_mir(self.tcx, expr_def_id.to_def_id(), args); // Unify `interior` with `witness` and collect all the resulting obligations. let span = self.tcx.hir().body(body_id).value.span; @@ -623,8 +618,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match *self_ty.kind() { ty::Infer(ty::TyVar(found_vid)) => { - // FIXME: consider using `sub_root_var` here so we - // can see through subtyping. let found_vid = self.root_var(found_vid); debug!("self_type_matches_expected_vid - found_vid={:?}", found_vid); expected_vid == found_vid @@ -639,14 +632,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self_ty: ty::TyVid, ) -> impl DoubleEndedIterator> + Captures<'tcx> + 'b { - // FIXME: consider using `sub_root_var` here so we - // can see through subtyping. let ty_var_root = self.root_var(self_ty); trace!("pending_obligations = {:#?}", self.fulfillment_cx.borrow().pending_obligations()); self.fulfillment_cx.borrow().pending_obligations().into_iter().filter_map( move |obligation| match &obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(data)) + ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) if self.self_type_matches_expected_vid( data.projection_ty.self_ty(), ty_var_root, @@ -654,23 +645,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { Some(obligation) } - ty::PredicateKind::Clause(ty::Clause::Trait(data)) + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) if self.self_type_matches_expected_vid(data.self_ty(), ty_var_root) => { Some(obligation) } - ty::PredicateKind::Clause(ty::Clause::Trait(..)) - | ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) + ty::PredicateKind::Clause(ty::ClauseKind::Trait(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) | ty::PredicateKind::Subtype(..) | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) - | ty::PredicateKind::WellFormed(..) + | ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..)) | ty::PredicateKind::ObjectSafe(..) | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::ConstEvaluatable(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) | ty::PredicateKind::ConstEquate(..) // N.B., this predicate is created by breaking down a // `ClosureType: FnFoo()` predicate, where @@ -682,7 +673,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // inference variable. | ty::PredicateKind::ClosureKind(..) | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => None, + => None, }, ) } @@ -691,7 +682,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let sized_did = self.tcx.lang_items().sized_trait(); self.obligations_for_self_ty(self_ty).any(|obligation| { match obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => { Some(data.def_id()) == sized_did } _ => false, @@ -700,7 +691,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } pub(in super::super) fn err_args(&self, len: usize) -> Vec> { - let ty_error = self.tcx.ty_error_misc(); + let ty_error = Ty::new_misc_error(self.tcx); vec![ty_error; len] } @@ -734,7 +725,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // tests/ui/impl-trait/hidden-type-is-opaque-2.rs for examples that hit this path. if formal_ret.has_infer_types() { for ty in ret_ty.walk() { - if let ty::subst::GenericArgKind::Type(ty) = ty.unpack() + if let ty::GenericArgKind::Type(ty) = ty.unpack() && let ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) = *ty.kind() && let Some(def_id) = def_id.as_local() && self.opaque_type_origin(def_id).is_some() { @@ -745,7 +736,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let expect_args = self .fudge_inference_if_ok(|| { - let ocx = ObligationCtxt::new_in_snapshot(self); + let ocx = ObligationCtxt::new(self); // Attempt to apply a subtyping relationship between the formal // return type (likely containing type variables if the function @@ -781,8 +772,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { self.tcx.type_of(def_id) }; - let substs = self.fresh_substs_for_item(span, def_id); - let ty = item_ty.subst(self.tcx, substs); + let args = self.fresh_args_for_item(span, def_id); + let ty = item_ty.instantiate(self.tcx, args); self.write_resolution(hir_id, Ok((def_kind, def_id))); @@ -799,9 +790,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, }; if let Some(code) = code { - self.add_required_obligations_with_code(span, def_id, substs, move |_, _| code.clone()); + self.add_required_obligations_with_code(span, def_id, args, move |_, _| code.clone()); } else { - self.add_required_obligations_for_hir(span, def_id, substs, hir_id); + self.add_required_obligations_for_hir(span, def_id, args, hir_id); } (Res::Def(def_kind, def_id), ty) @@ -857,7 +848,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .resolve_fully_qualified_call(span, item_name, ty.normalized, qself.span, hir_id) .and_then(|r| { // lint bare trait if the method is found in the trait - if span.edition().rust_2021() && let Some(mut diag) = self.tcx.sess.diagnostic().steal_diagnostic(qself.span, StashKey::TraitMissingMethod) { + if span.edition().at_least_rust_2021() && let Some(mut diag) = self.tcx.sess.diagnostic().steal_diagnostic(qself.span, StashKey::TraitMissingMethod) { diag.emit(); } Ok(r) @@ -887,7 +878,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // emit or cancel the diagnostic for bare traits - if span.edition().rust_2021() && let Some(mut diag) = self.tcx.sess.diagnostic().steal_diagnostic(qself.span, StashKey::TraitMissingMethod) { + if span.edition().at_least_rust_2021() && let Some(mut diag) = self.tcx.sess.diagnostic().steal_diagnostic(qself.span, StashKey::TraitMissingMethod) { if trait_missing_method { // cancel the diag for bare traits when meeting `MyTrait::missing_method` diag.cancel(); @@ -905,7 +896,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { error, None, Expectation::NoExpectation, - trait_missing_method && span.edition().rust_2021(), // emits missing method for trait only after edition 2021 + trait_missing_method && span.edition().at_least_rust_2021(), // emits missing method for trait only after edition 2021 ) { e.emit(); } @@ -1066,7 +1057,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if let ExprKind::MethodCall(..) = rcvr.kind { err.span_note( sp, - modifies_rcvr_note.clone() + ", it is not meant to be used in method chains.", + modifies_rcvr_note + ", it is not meant to be used in method chains.", ); } else { err.span_note(sp, modifies_rcvr_note); @@ -1151,7 +1142,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); if let Res::Local(hid) = res { - let ty = self.local_ty(span, hid).decl_ty; + let ty = self.local_ty(span, hid); let ty = self.normalize(span, ty); self.write_ty(hir_id, ty); return (ty, res); @@ -1202,15 +1193,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let has_self = path_segs.last().is_some_and(|PathSeg(def_id, _)| tcx.generics_of(*def_id).has_self); - let (res, self_ctor_substs) = if let Res::SelfCtor(impl_def_id) = res { - let ty = self.handle_raw_ty(span, tcx.at(span).type_of(impl_def_id).subst_identity()); + let (res, self_ctor_args) = if let Res::SelfCtor(impl_def_id) = res { + let ty = + self.handle_raw_ty(span, tcx.at(span).type_of(impl_def_id).instantiate_identity()); match ty.normalized.ty_adt_def() { Some(adt_def) if adt_def.has_ctor() => { let (ctor_kind, ctor_def_id) = adt_def.non_enum_variant().ctor.unwrap(); + // Check the visibility of the ctor. + let vis = tcx.visibility(ctor_def_id); + if !vis.is_accessible_from(tcx.parent_module(hir_id).to_def_id(), tcx) { + tcx.sess + .emit_err(CtorIsPrivate { span, def: tcx.def_path_str(adt_def.did()) }); + } let new_res = Res::Def(DefKind::Ctor(CtorOf::Struct, ctor_kind), ctor_def_id); - let user_substs = Self::user_substs_for_adt(ty); - user_self_ty = user_substs.user_self_ty; - (new_res, Some(user_substs.substs)) + let user_args = Self::user_args_for_adt(ty); + user_self_ty = user_args.user_self_ty; + (new_res, Some(user_args.args)) } _ => { let mut err = tcx.sess.struct_span_err( @@ -1233,7 +1231,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } let reported = err.emit(); - return (tcx.ty_error(reported), res); + return (Ty::new_error(tcx, reported), res); } } } else { @@ -1315,7 +1313,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn inferred_kind( &mut self, - substs: Option<&[ty::GenericArg<'tcx>]>, + args: Option<&[ty::GenericArg<'tcx>]>, param: &ty::GenericParamDef, infer_args: bool, ) -> ty::GenericArg<'tcx> { @@ -1329,7 +1327,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If we have a default, then we it doesn't matter that we're not // inferring the type arguments: we provide the default where any // is missing. - tcx.type_of(param.def_id).subst(tcx, substs.unwrap()).into() + tcx.type_of(param.def_id).instantiate(tcx, args.unwrap()).into() } else { // If no type arguments were provided, we have to infer them. // This case also occurs as a result of some malformed input, e.g. @@ -1339,8 +1337,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } GenericParamDefKind::Const { has_default } => { - if !infer_args && has_default { - tcx.const_param_default(param.def_id).subst(tcx, substs.unwrap()).into() + if !infer_args + && has_default + && !tcx.has_attr(param.def_id, sym::rustc_host) + { + tcx.const_param_default(param.def_id) + .instantiate(tcx, args.unwrap()) + .into() } else { self.fcx.var_for_def(self.span, param) } @@ -1349,8 +1352,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - let substs_raw = self_ctor_substs.unwrap_or_else(|| { - create_substs_for_generic_args( + let args_raw = self_ctor_args.unwrap_or_else(|| { + create_args_for_parent_generic_args( tcx, def_id, &[], @@ -1367,20 +1370,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }); - // First, store the "user substs" for later. - self.write_user_type_annotation_from_substs(hir_id, def_id, substs_raw, user_self_ty); + // First, store the "user args" for later. + self.write_user_type_annotation_from_args(hir_id, def_id, args_raw, user_self_ty); // Normalize only after registering type annotations. - let substs = self.normalize(span, substs_raw); + let args = self.normalize(span, args_raw); - self.add_required_obligations_for_hir(span, def_id, &substs, hir_id); + self.add_required_obligations_for_hir(span, def_id, &args, hir_id); // Substitute the values for the type parameters into the type of // the referenced item. let ty = tcx.type_of(def_id); - assert!(!substs.has_escaping_bound_vars()); - assert!(!ty.0.has_escaping_bound_vars()); - let ty_substituted = self.normalize(span, ty.subst(tcx, substs)); + assert!(!args.has_escaping_bound_vars()); + assert!(!ty.skip_binder().has_escaping_bound_vars()); + let ty_substituted = self.normalize(span, ty.instantiate(tcx, args)); if let Some(UserSelfTy { impl_def_id, self_ty }) = user_self_ty { // In the case of `Foo::method` and `>::method`, if `method` @@ -1388,7 +1391,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // type parameters, which we can infer by unifying the provided `Self` // with the substituted impl type. // This also occurs for an enum variant on a type alias. - let impl_ty = self.normalize(span, tcx.type_of(impl_def_id).subst(tcx, substs)); + let impl_ty = self.normalize(span, tcx.type_of(impl_def_id).instantiate(tcx, args)); let self_ty = self.normalize(span, self_ty); match self.at(&self.misc(span), self.param_env).eq( DefineOpaqueTypes::No, @@ -1400,9 +1403,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.sess.delay_span_bug( span, format!( - "instantiate_value_path: (UFCS) {:?} was a subtype of {:?} but now is not?", - self_ty, - impl_ty, + "instantiate_value_path: (UFCS) {self_ty:?} was a subtype of {impl_ty:?} but now is not?", ), ); } @@ -1410,7 +1411,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } debug!("instantiate_value_path: type of {:?} is {:?}", hir_id, ty_substituted); - self.write_substs(hir_id, substs); + self.write_args(hir_id, args); (ty_substituted, res) } @@ -1420,10 +1421,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, span: Span, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, hir_id: hir::HirId, ) { - self.add_required_obligations_with_code(span, def_id, substs, |idx, span| { + self.add_required_obligations_with_code(span, def_id, args, |idx, span| { if span.is_dummy() { ObligationCauseCode::ExprItemObligation(def_id, hir_id, idx) } else { @@ -1432,17 +1433,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) } - #[instrument(level = "debug", skip(self, code, span, substs))] + #[instrument(level = "debug", skip(self, code, span, args))] fn add_required_obligations_with_code( &self, span: Span, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, code: impl Fn(usize, Span) -> ObligationCauseCode<'tcx>, ) { let param_env = self.param_env; - let bounds = self.instantiate_bounds(span, def_id, &substs); + let bounds = self.instantiate_bounds(span, def_id, &args); for obligation in traits::predicates_for_generics( |idx, predicate_span| { @@ -1451,40 +1452,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { param_env, bounds, ) { - // N.B. We are remapping all predicates to non-const since we don't know if we just - // want them as function pointers or we are calling them from a const-context. The - // actual checking will occur in `rustc_const_eval::transform::check_consts`. - self.register_predicate(obligation.without_const(self.tcx)); + self.register_predicate(obligation); } } - /// Resolves `typ` by a single level if `typ` is a type variable. + /// Try to resolve `ty` to a structural type, normalizing aliases. /// - /// When the new solver is enabled, this will also attempt to normalize - /// the type if it's a projection (note that it will not deeply normalize - /// projections within the type, just the outermost layer of the type). - /// - /// If no resolution is possible, then an error is reported. - /// Numeric inference variables may be left unresolved. - pub fn structurally_resolved_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { - let mut ty = self.resolve_vars_with_obligations(ty); - - if self.tcx.trait_solver_next() - && let ty::Alias(ty::Projection, _) = ty.kind() + /// In case there is still ambiguity, the returned type may be an inference + /// variable. This is different from `structurally_resolve_type` which errors + /// in this case. + pub fn try_structurally_resolve_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { + let ty = self.resolve_vars_with_obligations(ty); + + if self.next_trait_solver() + && let ty::Alias(ty::Projection | ty::Inherent | ty::Weak, _) = ty.kind() { match self .at(&self.misc(sp), self.param_env) .structurally_normalize(ty, &mut **self.fulfillment_cx.borrow_mut()) { - Ok(normalized_ty) => { - ty = normalized_ty; - }, + Ok(normalized_ty) => normalized_ty, Err(errors) => { let guar = self.err_ctxt().report_fulfillment_errors(&errors); - return self.tcx.ty_error(guar); + return Ty::new_error(self.tcx,guar); } } - } + } else { + ty + } + } + + /// Resolves `ty` by a single level if `ty` is a type variable. + /// + /// When the new solver is enabled, this will also attempt to normalize + /// the type if it's a projection (note that it will not deeply normalize + /// projections within the type, just the outermost layer of the type). + /// + /// If no resolution is possible, then an error is reported. + /// Numeric inference variables may be left unresolved. + pub fn structurally_resolve_type(&self, sp: Span, ty: Ty<'tcx>) -> Ty<'tcx> { + let ty = self.try_structurally_resolve_type(sp, ty); if !ty.is_ty_var() { ty @@ -1494,7 +1501,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .emit_inference_failure_err(self.body_id, sp, ty.into(), E0282, true) .emit() }); - let err = self.tcx.ty_error(e); + let err = Ty::new_error(self.tcx, e); self.demand_suptype(sp, err, ty); err } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs index 3efdab534384b..c44d12e61e33d 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs @@ -2,7 +2,7 @@ use crate::FnCtxt; use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::def_id::DefId; -use rustc_infer::traits::ObligationCauseCode; +use rustc_infer::{infer::type_variable::TypeVariableOriginKind, traits::ObligationCauseCode}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_span::{self, symbol::kw, Span}; use rustc_trait_selection::traits; @@ -14,30 +14,41 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, error: &mut traits::FulfillmentError<'tcx>, ) -> bool { - let (traits::ExprItemObligation(def_id, hir_id, idx) | traits::ExprBindingObligation(def_id, _, hir_id, idx)) - = *error.obligation.cause.code().peel_derives() else { return false; }; + let (traits::ExprItemObligation(def_id, hir_id, idx) + | traits::ExprBindingObligation(def_id, _, hir_id, idx)) = + *error.obligation.cause.code().peel_derives() + else { + return false; + }; let hir = self.tcx.hir(); - let hir::Node::Expr(expr) = hir.get(hir_id) else { return false; }; + let hir::Node::Expr(expr) = hir.get(hir_id) else { + return false; + }; - let Some(unsubstituted_pred) = - self.tcx.predicates_of(def_id).instantiate_identity(self.tcx).predicates.into_iter().nth(idx) - else { return false; }; + let Some(unsubstituted_pred) = self + .tcx + .predicates_of(def_id) + .instantiate_identity(self.tcx) + .predicates + .into_iter() + .nth(idx) + else { + return false; + }; let generics = self.tcx.generics_of(def_id); - let predicate_substs = match unsubstituted_pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => pred.trait_ref.substs.to_vec(), - ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { - pred.projection_ty.substs.to_vec() - } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(arg, ty)) => { + let predicate_args = match unsubstituted_pred.kind().skip_binder() { + ty::ClauseKind::Trait(pred) => pred.trait_ref.args.to_vec(), + ty::ClauseKind::Projection(pred) => pred.projection_ty.args.to_vec(), + ty::ClauseKind::ConstArgHasType(arg, ty) => { vec![ty.into(), arg.into()] } - ty::PredicateKind::ConstEvaluatable(e) => vec![e.into()], + ty::ClauseKind::ConstEvaluatable(e) => vec![e.into()], _ => return false, }; let find_param_matching = |matches: &dyn Fn(ty::ParamTerm) -> bool| { - predicate_substs.iter().find_map(|arg| { + predicate_args.iter().find_map(|arg| { arg.walk().find_map(|arg| { if let ty::GenericArgKind::Type(ty) = arg.unpack() && let ty::Param(param_ty) = *ty.kind() @@ -227,18 +238,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { param_to_point_at: ty::GenericArg<'tcx>, segment: &hir::PathSegment<'tcx>, ) -> bool { - let own_substs = self + let own_args = self .tcx .generics_of(def_id) - .own_substs(ty::InternalSubsts::identity_for_item(self.tcx, def_id)); - let Some((index, _)) = own_substs - .iter() - .enumerate() - .find(|(_, arg)| **arg == param_to_point_at) else { return false }; - let Some(arg) = segment - .args() - .args - .get(index) else { return false; }; + .own_args(ty::GenericArgs::identity_for_item(self.tcx, def_id)); + let Some((index, _)) = + own_args.iter().enumerate().find(|(_, arg)| **arg == param_to_point_at) + else { + return false; + }; + let Some(arg) = segment.args().args.get(index) else { + return false; + }; error.obligation.cause.span = arg .span() .find_ancestor_in_same_ctxt(error.obligation.cause.span) @@ -256,11 +267,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { type BreakTy = ty::GenericArg<'tcx>; fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow { if let Some(origin) = self.0.type_var_origin(ty) - && let rustc_infer::infer::type_variable::TypeVariableOriginKind::TypeParameterDefinition(_, Some(def_id)) = - origin.kind + && let TypeVariableOriginKind::TypeParameterDefinition(_, def_id) = origin.kind && let generics = self.0.tcx.generics_of(self.1) && let Some(index) = generics.param_def_id_to_index(self.0.tcx, def_id) - && let Some(subst) = ty::InternalSubsts::identity_for_item(self.0.tcx, self.1) + && let Some(subst) = ty::GenericArgs::identity_for_item(self.0.tcx, self.1) .get(index as usize) { ControlFlow::Break(*subst) @@ -300,13 +310,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> Option<(&'tcx hir::Expr<'tcx>, Ty<'tcx>)> { let def = self.tcx.adt_def(def_id); - let identity_substs = ty::InternalSubsts::identity_for_item(self.tcx, def_id); + let identity_args = ty::GenericArgs::identity_for_item(self.tcx, def_id); let fields_referencing_param: Vec<_> = def .variant_with_id(variant_def_id) .fields .iter() .filter(|field| { - let field_ty = field.ty(self.tcx, identity_substs); + let field_ty = field.ty(self.tcx, identity_args); find_param_in_ty(field_ty.into(), param_to_point_at) }) .collect(); @@ -317,7 +327,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // same rules that check_expr_struct uses for macro hygiene. if self.tcx.adjust_ident(expr_field.ident, variant_def_id) == field.ident(self.tcx) { - return Some((expr_field.expr, self.tcx.type_of(field.did).subst_identity())); + return Some(( + expr_field.expr, + self.tcx.type_of(field.did).instantiate_identity(), + )); } } } @@ -344,7 +357,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { receiver: Option<&'tcx hir::Expr<'tcx>>, args: &'tcx [hir::Expr<'tcx>], ) -> bool { - let ty = self.tcx.type_of(def_id).subst_identity(); + let ty = self.tcx.type_of(def_id).instantiate_identity(); if !ty.is_fn() { return false; } @@ -486,7 +499,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::TraitRef::new( self.tcx, obligation.impl_or_alias_def_id, - ty::InternalSubsts::identity_for_item(self.tcx, obligation.impl_or_alias_def_id), + ty::GenericArgs::identity_for_item(self.tcx, obligation.impl_or_alias_def_id), ) } else { self.tcx @@ -510,11 +523,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // This shouldn't happen, but since this is only a diagnostic improvement, avoid breaking things. return Err(expr); } - let relevant_broken_predicate: ty::PredicateKind<'tcx> = - impl_predicates.predicates[impl_predicate_index].0.kind().skip_binder(); - match relevant_broken_predicate { - ty::PredicateKind::Clause(ty::Clause::Trait(broken_trait)) => { + match impl_predicates.predicates[impl_predicate_index].0.kind().skip_binder() { + ty::ClauseKind::Trait(broken_trait) => { // ... self.blame_specific_part_of_expr_corresponding_to_generic_param( broken_trait.trait_ref.self_ty().into(), @@ -577,9 +588,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Find out which of `in_ty_elements` refer to `param`. // FIXME: It may be better to take the first if there are multiple, // just so that the error points to a smaller expression. - let Some((drill_expr, drill_ty)) = is_iterator_singleton(expr_elements.iter().zip( in_ty_elements.iter()).filter(|(_expr_elem, in_ty_elem)| { - find_param_in_ty((*in_ty_elem).into(), param) - })) else { + let Some((drill_expr, drill_ty)) = + is_iterator_singleton(expr_elements.iter().zip(in_ty_elements.iter()).filter( + |(_expr_elem, in_ty_elem)| find_param_in_ty((*in_ty_elem).into(), param), + )) + else { // The param is not mentioned, or it is mentioned in multiple indexes. return Err(expr); }; @@ -598,7 +611,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { // First, confirm that this struct is the same one as in the types, and if so, // find the right variant. - let Res::Def(expr_struct_def_kind, expr_struct_def_id) = self.typeck_results.borrow().qpath_res(expr_struct_path, expr.hir_id) else { + let Res::Def(expr_struct_def_kind, expr_struct_def_id) = + self.typeck_results.borrow().qpath_res(expr_struct_path, expr.hir_id) + else { return Err(expr); }; @@ -625,16 +640,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We need to know which of the generic parameters mentions our target param. // We expect that at least one of them does, since it is expected to be mentioned. - let Some((drill_generic_index, generic_argument_type)) = - is_iterator_singleton( - in_ty_adt_generic_args.iter().enumerate().filter( - |(_index, in_ty_generic)| { - find_param_in_ty(*in_ty_generic, param) - }, - ), - ) else { - return Err(expr); - }; + let Some((drill_generic_index, generic_argument_type)) = is_iterator_singleton( + in_ty_adt_generic_args + .iter() + .enumerate() + .filter(|(_index, in_ty_generic)| find_param_in_ty(*in_ty_generic, param)), + ) else { + return Err(expr); + }; let struct_generic_parameters: &ty::Generics = self.tcx.generics_of(in_ty_adt.did()); if drill_generic_index >= struct_generic_parameters.params.len() { @@ -707,7 +720,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // This is (possibly) a constructor call, like `Some(...)` or `MyStruct(a, b, c)`. - let Res::Def(expr_struct_def_kind, expr_ctor_def_id) = self.typeck_results.borrow().qpath_res(expr_callee_path, expr_callee.hir_id) else { + let Res::Def(expr_struct_def_kind, expr_ctor_def_id) = + self.typeck_results.borrow().qpath_res(expr_callee_path, expr_callee.hir_id) + else { return Err(expr); }; @@ -748,16 +763,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We need to know which of the generic parameters mentions our target param. // We expect that at least one of them does, since it is expected to be mentioned. - let Some((drill_generic_index, generic_argument_type)) = - is_iterator_singleton( - in_ty_adt_generic_args.iter().enumerate().filter( - |(_index, in_ty_generic)| { - find_param_in_ty(*in_ty_generic, param) - }, - ), - ) else { - return Err(expr); - }; + let Some((drill_generic_index, generic_argument_type)) = is_iterator_singleton( + in_ty_adt_generic_args + .iter() + .enumerate() + .filter(|(_index, in_ty_generic)| find_param_in_ty(*in_ty_generic, param)), + ) else { + return Err(expr); + }; let struct_generic_parameters: &ty::Generics = self.tcx.generics_of(in_ty_adt.did()); if drill_generic_index >= struct_generic_parameters.params.len() { @@ -798,7 +811,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .iter() .map(|field| field.ty(self.tcx, *in_ty_adt_generic_args)) .enumerate() - .filter(|(_index, field_type)| find_param_in_ty((*field_type).into(), param)) + .filter(|(_index, field_type)| find_param_in_ty((*field_type).into(), param)), ) else { return Err(expr); }; @@ -850,7 +863,7 @@ fn find_param_in_ty<'tcx>( // This logic may seem a bit strange, but typically when // we have a projection type in a function signature, the // argument that's being passed into that signature is - // not actually constraining that projection's substs in + // not actually constraining that projection's args in // a meaningful way. So we skip it, and see improvements // in some UI tests. walk.skip_current_subtree(); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs index d45e3d395e430..7aadb95d9393e 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/arg_matrix.rs @@ -1,7 +1,7 @@ -use std::cmp; - +use core::cmp::Ordering; use rustc_index::IndexVec; use rustc_middle::ty::error::TypeError; +use std::cmp; rustc_index::newtype_index! { #[debug_format = "ExpectedIdx({})"] @@ -34,14 +34,14 @@ enum Issue { Permutation(Vec>), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum Compatibility<'tcx> { Compatible, Incompatible(Option>), } /// Similar to `Issue`, but contains some extra information -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub(crate) enum Error<'tcx> { /// The provided argument is the invalid type for the expected input Invalid(ProvidedIdx, ExpectedIdx, Compatibility<'tcx>), @@ -55,6 +55,34 @@ pub(crate) enum Error<'tcx> { Permutation(Vec<(ExpectedIdx, ProvidedIdx)>), } +impl Ord for Error<'_> { + fn cmp(&self, other: &Self) -> Ordering { + let key = |error: &Error<'_>| -> usize { + match error { + Error::Invalid(..) => 0, + Error::Extra(_) => 1, + Error::Missing(_) => 2, + Error::Swap(..) => 3, + Error::Permutation(..) => 4, + } + }; + match (self, other) { + (Error::Invalid(a, _, _), Error::Invalid(b, _, _)) => a.cmp(b), + (Error::Extra(a), Error::Extra(b)) => a.cmp(b), + (Error::Missing(a), Error::Missing(b)) => a.cmp(b), + (Error::Swap(a, b, ..), Error::Swap(c, d, ..)) => a.cmp(c).then(b.cmp(d)), + (Error::Permutation(a), Error::Permutation(b)) => a.cmp(b), + _ => key(self).cmp(&key(other)), + } + } +} + +impl PartialOrd for Error<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + pub(crate) struct ArgMatrix<'tcx> { /// Maps the indices in the `compatibility_matrix` rows to the indices of /// the *user provided* inputs @@ -177,7 +205,7 @@ impl<'tcx> ArgMatrix<'tcx> { // If an argument is unsatisfied, and the input in its position is useless // then the most likely explanation is that we just got the types wrong (true, true, true, true) => return Some(Issue::Invalid(i)), - // Otherwise, if an input is useless, then indicate that this is an extra argument + // Otherwise, if an input is useless then indicate that this is an extra input (true, _, true, _) => return Some(Issue::Extra(i)), // Otherwise, if an argument is unsatisfiable, indicate that it's missing (_, true, _, true) => return Some(Issue::Missing(i)), @@ -376,6 +404,9 @@ impl<'tcx> ArgMatrix<'tcx> { }; } + // sort errors with same type by the order they appear in the source + // so that suggestion will be handled properly, see #112507 + errors.sort(); return (errors, matched_inputs); } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 72c42f8e78952..4def78673841c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1,12 +1,12 @@ use crate::coercion::CoerceMany; +use crate::errors::SuggestPtrNullMut; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; use crate::gather_locals::Declaration; use crate::method::MethodCallee; use crate::TupleArgumentsFlag::*; use crate::{errors, Expectation::*}; use crate::{ - struct_span_err, BreakableCtxt, Diverges, Expectation, FnCtxt, LocalTy, Needs, RawTy, - TupleArgumentsFlag, + struct_span_err, BreakableCtxt, Diverges, Expectation, FnCtxt, Needs, RawTy, TupleArgumentsFlag, }; use rustc_ast as ast; use rustc_data_structures::fx::FxIndexSet; @@ -45,12 +45,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!("FnCtxt::check_casts: {} deferred checks", deferred_cast_checks.len()); for cast in deferred_cast_checks.drain(..) { - let prev_env = self.param_env; - self.param_env = self.param_env.with_constness(cast.constness); - cast.check(self); - - self.param_env = prev_env; } *self.deferred_cast_checks.borrow_mut() = deferred_cast_checks; @@ -73,7 +68,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty = self.typeck_results.borrow().expr_ty_adjusted(expr); let ty = self.resolve_vars_if_possible(ty); if ty.has_non_region_infer() { - self.tcx.ty_error_misc() + Ty::new_misc_error(self.tcx) } else { self.tcx.erase_regions(ty) } @@ -93,7 +88,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Expectation<'tcx>, ) -> Ty<'tcx> { let has_error = match method { - Ok(method) => method.substs.references_error() || method.sig.references_error(), + Ok(method) => method.args.references_error() || method.sig.references_error(), Err(_) => true, }; if has_error { @@ -101,7 +96,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let err_inputs = match tuple_arguments { DontTupleArguments => err_inputs, - TupleArguments => vec![self.tcx.mk_tup(&err_inputs)], + TupleArguments => vec![Ty::new_tup(self.tcx, &err_inputs)], }; self.check_argument_types( @@ -114,7 +109,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tuple_arguments, method.ok().map(|method| method.def_id), ); - return self.tcx.ty_error_misc(); + return Ty::new_misc_error(self.tcx); } let method = method.unwrap(); @@ -184,7 +179,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If the arguments should be wrapped in a tuple (ex: closures), unwrap them here let (formal_input_tys, expected_input_tys) = if tuple_arguments == TupleArguments { - let tuple_type = self.structurally_resolved_type(call_span, formal_input_tys[0]); + let tuple_type = self.structurally_resolve_type(call_span, formal_input_tys[0]); match tuple_type.kind() { // We expected a tuple and got a tuple ty::Tuple(arg_types) => { @@ -265,9 +260,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // fulfillment error to be more accurate. let coerced_ty = self.resolve_vars_with_obligations(coerced_ty); - let coerce_error = self - .try_coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None) - .err(); + let coerce_error = + self.coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None).err(); if coerce_error.is_some() { return Compatibility::Incompatible(coerce_error); @@ -362,7 +356,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { continue; } - let is_closure = matches!(arg.kind, ExprKind::Closure { .. }); + // For this check, we do *not* want to treat async generator closures (async blocks) + // as proper closures. Doing so would regress type inference when feeding + // the return value of an argument-position async block to an argument-position + // closure wrapped in a block. + // See . + let is_closure = if let ExprKind::Closure(closure) = arg.kind { + !tcx.generator_is_async(closure.def_id.to_def_id()) + } else { + false + }; if is_closure != check_closures { continue; } @@ -403,7 +406,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // There are a few types which get autopromoted when passed via varargs // in C but we just error out instead and require explicit casts. - let arg_ty = self.structurally_resolved_type(arg.span, arg_ty); + let arg_ty = self.structurally_resolve_type(arg.span, arg_ty); match arg_ty.kind() { ty::Float(ty::FloatTy::F32) => { variadic_error(tcx.sess, arg.span, arg_ty, "c_double"); @@ -415,7 +418,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { variadic_error(tcx.sess, arg.span, arg_ty, "c_uint"); } ty::FnDef(..) => { - let ptr_ty = self.tcx.mk_fn_ptr(arg_ty.fn_sig(self.tcx)); + let ptr_ty = Ty::new_fn_ptr(self.tcx, arg_ty.fn_sig(self.tcx)); let ptr_ty = self.resolve_vars_if_possible(ptr_ty); variadic_error(tcx.sess, arg.span, arg_ty, &ptr_ty.to_string()); } @@ -515,7 +518,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // suggestions and labels are (more) correct when an arg is a // macro invocation. let normalize_span = |span: Span| -> Span { - let normalized_span = span.find_ancestor_inside(error_span).unwrap_or(span); + let normalized_span = span.find_ancestor_inside_same_ctxt(error_span).unwrap_or(span); // Sometimes macros mess up the spans, so do not normalize the // arg span to equal the error span, because that's less useful // than pointing out the arg expr in the wrong context. @@ -530,7 +533,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .typeck_results .borrow() .expr_ty_adjusted_opt(*expr) - .unwrap_or_else(|| tcx.ty_error_misc()); + .unwrap_or_else(|| Ty::new_misc_error(tcx)); (self.resolve_vars_if_possible(ty), normalize_span(expr.span)) }) .collect(); @@ -639,7 +642,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && provided_arg_tys.len() == formal_and_expected_inputs.len() - 1 + tys.len() { // Wrap up the N provided arguments starting at this position in a tuple. - let provided_as_tuple = tcx.mk_tup_from_iter( + let provided_as_tuple = Ty::new_tup_from_iter(tcx, provided_arg_tys.iter().map(|(ty, _)| *ty).skip(mismatch_idx).take(tys.len()), ); @@ -680,7 +683,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); err.span_label( full_call_span, - format!("arguments to this {} are incorrect", call_name), + format!("arguments to this {call_name} are incorrect"), ); } else { err = tcx.sess.struct_span_err_with_code( @@ -743,20 +746,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return; } - errors.drain_filter(|error| { - let Error::Invalid( - provided_idx, - expected_idx, - Compatibility::Incompatible(Some(e)), - ) = error else { return false }; + errors.retain(|error| { + let Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(e))) = + error + else { + return true; + }; let (provided_ty, provided_span) = provided_arg_tys[*provided_idx]; let trace = mk_trace(provided_span, formal_and_expected_inputs[*expected_idx], provided_ty); if !matches!(trace.cause.as_failure_code(*e), FailureCode::Error0308) { self.err_ctxt().report_and_explain_type_error(trace, *e).emit(); - return true; + return false; } - false + true }); // We're done if we found errors, but we already emitted them. @@ -787,10 +790,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None, None, ); - err.span_label( - full_call_span, - format!("arguments to this {} are incorrect", call_name), - ); + err.span_label(full_call_span, format!("arguments to this {call_name} are incorrect")); if let hir::ExprKind::MethodCall(_, rcvr, _, _) = call_expr.kind && provided_idx.as_usize() == expected_idx.as_usize() @@ -805,6 +805,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } + self.suggest_ptr_null_mut( + expected_ty, + provided_ty, + provided_args[*provided_idx], + &mut err, + ); + // Call out where the function is defined self.label_fn_like( &mut err, @@ -858,7 +865,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if ty.is_unit() { "()".to_string() } else if ty.is_suggestable(tcx, false) { - format!("/* {} */", ty) + format!("/* {ty} */") } else if let Some(fn_def_id) = fn_def_id && self.tcx.def_kind(fn_def_id).is_fn_like() && let self_implicit = @@ -915,14 +922,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (provided_ty, provided_span) = provided_arg_tys[arg_idx]; let provided_ty_name = if !has_error_or_infer([provided_ty]) { // FIXME: not suggestable, use something else - format!(" of type `{}`", provided_ty) + format!(" of type `{provided_ty}`") } else { "".to_string() }; - labels - .push((provided_span, format!("unexpected argument{}", provided_ty_name))); + labels.push((provided_span, format!("unexpected argument{provided_ty_name}"))); let mut span = provided_span; - if span.can_be_used_for_suggestions() { + if span.can_be_used_for_suggestions() + && error_span.can_be_used_for_suggestions() + { if arg_idx.index() > 0 && let Some((_, prev)) = provided_arg_tys .get(ProvidedIdx::from_usize(arg_idx.index() - 1) @@ -938,9 +946,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // - f(0, 1,) // + f() if only_extras_so_far - && errors + && !errors .peek() - .map_or(true, |next_error| !matches!(next_error, Error::Extra(_))) + .is_some_and(|next_error| matches!(next_error, Error::Extra(_))) { let next = provided_arg_tys .get(arg_idx + 1) @@ -993,11 +1001,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args_span }; let rendered = if !has_error_or_infer([input_ty]) { - format!(" of type `{}`", input_ty) + format!(" of type `{input_ty}`") } else { "".to_string() }; - labels.push((span, format!("an argument{} is missing", rendered))); + labels.push((span, format!("an argument{rendered} is missing"))); suggestion_text = match suggestion_text { SuggestionText::None => SuggestionText::Provide(false), SuggestionText::Provide(_) => SuggestionText::Provide(true), @@ -1018,13 +1026,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let rendered = if !has_error_or_infer([first_expected_ty, second_expected_ty]) { format!( - " of type `{}` and `{}`", - first_expected_ty, second_expected_ty + " of type `{first_expected_ty}` and `{second_expected_ty}`" ) } else { "".to_string() }; - labels.push((span, format!("two arguments{} are missing", rendered))); + labels.push((span, format!("two arguments{rendered} are missing"))); suggestion_text = match suggestion_text { SuggestionText::None | SuggestionText::Provide(_) => { SuggestionText::Provide(true) @@ -1050,13 +1057,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { third_expected_ty, ]) { format!( - " of type `{}`, `{}`, and `{}`", - first_expected_ty, second_expected_ty, third_expected_ty + " of type `{first_expected_ty}`, `{second_expected_ty}`, and `{third_expected_ty}`" ) } else { "".to_string() }; - labels.push((span, format!("three arguments{} are missing", rendered))); + labels.push((span, format!("three arguments{rendered} are missing"))); suggestion_text = match suggestion_text { SuggestionText::None | SuggestionText::Provide(_) => { SuggestionText::Provide(true) @@ -1097,25 +1103,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (first_provided_ty, first_span) = provided_arg_tys[first_provided_idx]; let (_, first_expected_ty) = formal_and_expected_inputs[first_expected_idx]; let first_provided_ty_name = if !has_error_or_infer([first_provided_ty]) { - format!(", found `{}`", first_provided_ty) + format!(", found `{first_provided_ty}`") } else { String::new() }; labels.push(( first_span, - format!("expected `{}`{}", first_expected_ty, first_provided_ty_name), + format!("expected `{first_expected_ty}`{first_provided_ty_name}"), )); let (second_provided_ty, second_span) = provided_arg_tys[second_provided_idx]; let (_, second_expected_ty) = formal_and_expected_inputs[second_expected_idx]; let second_provided_ty_name = if !has_error_or_infer([second_provided_ty]) { - format!(", found `{}`", second_provided_ty) + format!(", found `{second_provided_ty}`") } else { String::new() }; labels.push(( second_span, - format!("expected `{}`{}", second_expected_ty, second_provided_ty_name), + format!("expected `{second_expected_ty}`{second_provided_ty_name}"), )); suggestion_text = match suggestion_text { @@ -1128,13 +1134,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (_, expected_ty) = formal_and_expected_inputs[dst_arg]; let (provided_ty, provided_span) = provided_arg_tys[dest_input]; let provided_ty_name = if !has_error_or_infer([provided_ty]) { - format!(", found `{}`", provided_ty) + format!(", found `{provided_ty}`") } else { String::new() }; labels.push(( provided_span, - format!("expected `{}`{}", expected_ty, provided_ty_name), + format!("expected `{expected_ty}`{provided_ty_name}"), )); } @@ -1215,22 +1221,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; if let Some(suggestion_text) = suggestion_text { let source_map = self.sess().source_map(); - let (mut suggestion, suggestion_span) = - if let Some(call_span) = full_call_span.find_ancestor_inside(error_span) { - ("(".to_string(), call_span.shrink_to_hi().to(error_span.shrink_to_hi())) - } else { - ( - format!( - "{}(", - source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| { - fn_def_id.map_or("".to_string(), |fn_def_id| { - tcx.item_name(fn_def_id).to_string() - }) + let (mut suggestion, suggestion_span) = if let Some(call_span) = + full_call_span.find_ancestor_inside_same_ctxt(error_span) + { + ("(".to_string(), call_span.shrink_to_hi().to(error_span.shrink_to_hi())) + } else { + ( + format!( + "{}(", + source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| { + fn_def_id.map_or("".to_string(), |fn_def_id| { + tcx.item_name(fn_def_id).to_string() }) - ), - error_span, - ) - }; + }) + ), + error_span, + ) + }; let mut needs_comma = false; for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() { if needs_comma { @@ -1262,6 +1269,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.emit(); } + fn suggest_ptr_null_mut( + &self, + expected_ty: Ty<'tcx>, + provided_ty: Ty<'tcx>, + arg: &hir::Expr<'tcx>, + err: &mut rustc_errors::DiagnosticBuilder<'tcx, ErrorGuaranteed>, + ) { + if let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Mut, .. }) = expected_ty.kind() + && let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Not, .. }) = provided_ty.kind() + && let hir::ExprKind::Call(callee, _) = arg.kind + && let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = callee.kind + && let Res::Def(_, def_id) = path.res + && self.tcx.get_diagnostic_item(sym::ptr_null) == Some(def_id) + { + // The user provided `ptr::null()`, but the function expects + // `ptr::null_mut()`. + err.subdiagnostic(SuggestPtrNullMut { + span: arg.span + }); + } + } + // AST fragment checking pub(in super::super) fn check_lit( &self, @@ -1271,14 +1300,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let tcx = self.tcx; match lit.node { - ast::LitKind::Str(..) => tcx.mk_static_str(), - ast::LitKind::ByteStr(ref v, _) => { - tcx.mk_imm_ref(tcx.lifetimes.re_static, tcx.mk_array(tcx.types.u8, v.len() as u64)) - } + ast::LitKind::Str(..) => Ty::new_static_str(tcx), + ast::LitKind::ByteStr(ref v, _) => Ty::new_imm_ref( + tcx, + tcx.lifetimes.re_static, + Ty::new_array(tcx, tcx.types.u8, v.len() as u64), + ), ast::LitKind::Byte(_) => tcx.types.u8, ast::LitKind::Char(_) => tcx.types.char, - ast::LitKind::Int(_, ast::LitIntType::Signed(t)) => tcx.mk_mach_int(ty::int_ty(t)), - ast::LitKind::Int(_, ast::LitIntType::Unsigned(t)) => tcx.mk_mach_uint(ty::uint_ty(t)), + ast::LitKind::Int(_, ast::LitIntType::Signed(t)) => Ty::new_int(tcx, ty::int_ty(t)), + ast::LitKind::Int(_, ast::LitIntType::Unsigned(t)) => Ty::new_uint(tcx, ty::uint_ty(t)), ast::LitKind::Int(_, ast::LitIntType::Unsuffixed) => { let opt_ty = expected.to_option(self).and_then(|ty| match ty.kind() { ty::Int(_) | ty::Uint(_) => Some(ty), @@ -1290,7 +1321,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { opt_ty.unwrap_or_else(|| self.next_int_var()) } ast::LitKind::Float(_, ast::LitFloatType::Suffixed(t)) => { - tcx.mk_mach_float(ty::float_ty(t)) + Ty::new_float(tcx, ty::float_ty(t)) } ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) => { let opt_ty = expected.to_option(self).and_then(|ty| match ty.kind() { @@ -1300,12 +1331,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { opt_ty.unwrap_or_else(|| self.next_float_var()) } ast::LitKind::Bool(_) => tcx.types.bool, - ast::LitKind::CStr(_, _) => tcx.mk_imm_ref( + ast::LitKind::CStr(_, _) => Ty::new_imm_ref( + tcx, tcx.lifetimes.re_static, tcx.type_of(tcx.require_lang_item(hir::LangItem::CStr, Some(lit.span))) .skip_binder(), ), - ast::LitKind::Err => tcx.ty_error_misc(), + ast::LitKind::Err => Ty::new_misc_error(tcx), } } @@ -1325,29 +1357,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } Res::Def(DefKind::Variant, _) => match ty.normalized.ty_adt_def() { Some(adt) => { - Some((adt.variant_of_res(def), adt.did(), Self::user_substs_for_adt(ty))) + Some((adt.variant_of_res(def), adt.did(), Self::user_args_for_adt(ty))) } _ => bug!("unexpected type: {:?}", ty.normalized), }, - Res::Def(DefKind::Struct | DefKind::Union | DefKind::TyAlias | DefKind::AssocTy, _) + Res::Def( + DefKind::Struct | DefKind::Union | DefKind::TyAlias { .. } | DefKind::AssocTy, + _, + ) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } => match ty.normalized.ty_adt_def() { Some(adt) if !adt.is_enum() => { - Some((adt.non_enum_variant(), adt.did(), Self::user_substs_for_adt(ty))) + Some((adt.non_enum_variant(), adt.did(), Self::user_args_for_adt(ty))) } _ => None, }, _ => bug!("unexpected definition: {:?}", def), }; - if let Some((variant, did, ty::UserSubsts { substs, user_self_ty })) = variant { - debug!("check_struct_path: did={:?} substs={:?}", did, substs); + if let Some((variant, did, ty::UserArgs { args, user_self_ty })) = variant { + debug!("check_struct_path: did={:?} args={:?}", did, args); // Register type annotation. - self.write_user_type_annotation_from_substs(hir_id, did, substs, user_self_ty); + self.write_user_type_annotation_from_args(hir_id, did, args, user_self_ty); // Check bounds on type arguments used in the path. - self.add_required_obligations_for_hir(path_span, did, substs, hir_id); + self.add_required_obligations_for_hir(path_span, did, args, hir_id); Ok((variant, ty.normalized)) } else { @@ -1384,7 +1419,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // See #44848. let ref_bindings = pat.contains_explicit_ref_binding(); - let local_ty = self.local_ty(init.span, hir_id).revealed_ty; + let local_ty = self.local_ty(init.span, hir_id); if let Some(m) = ref_bindings { // Somewhat subtle: if we have a `ref` binding in the pattern, // we want to avoid introducing coercions for the RHS. This is @@ -1395,7 +1430,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // type of the place it is referencing, and not some // supertype thereof. let init_ty = self.check_expr_with_needs(init, Needs::maybe_mut_place(m)); - self.demand_eqtype(init.span, local_ty, init_ty); + if let Some(mut diag) = self.demand_eqtype_diag(init.span, local_ty, init_ty) { + self.emit_type_mismatch_suggestions( + &mut diag, + init.peel_drop_temps(), + init_ty, + local_ty, + None, + None, + ); + diag.emit(); + } init_ty } else { self.check_expr_coercible_to_type(init, local_ty, None) @@ -1404,7 +1449,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(in super::super) fn check_decl(&self, decl: Declaration<'tcx>) { // Determine and write the type which we'll check the pattern against. - let decl_ty = self.local_ty(decl.span, decl.hir_id).decl_ty; + let decl_ty = self.local_ty(decl.span, decl.hir_id); self.write_ty(decl.hir_id, decl_ty); // Type check the initializer. @@ -1423,11 +1468,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // Type check the pattern. Override if necessary to avoid knock-on errors. - self.check_pat_top(&decl.pat, decl_ty, ty_span, origin_expr); + self.check_pat_top(&decl.pat, decl_ty, ty_span, origin_expr, Some(decl.origin)); let pat_ty = self.node_ty(decl.pat.hir_id); self.overwrite_local_ty_if_err(decl.hir_id, decl.pat, pat_ty); - if let Some(blk) = decl.els { + if let Some(blk) = decl.origin.try_get_else() { let previous_diverges = self.diverges.get(); let else_ty = self.check_block_with_expected(blk, NoExpectation); let cause = self.cause(blk.span, ObligationCauseCode::LetElse); @@ -1445,7 +1490,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_decl(local.into()); } - pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>, is_last: bool) { + pub fn check_stmt(&self, stmt: &'tcx hir::Stmt<'tcx>) { // Don't do all the complex logic below for `DeclItem`. match stmt.kind { hir::StmtKind::Item(..) => return, @@ -1465,21 +1510,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir::StmtKind::Item(_) => {} hir::StmtKind::Expr(ref expr) => { // Check with expected type of `()`. - self.check_expr_has_type_or_error(&expr, self.tcx.mk_unit(), |err| { + self.check_expr_has_type_or_error(&expr, Ty::new_unit(self.tcx), |err| { if expr.can_have_side_effects() { self.suggest_semicolon_at_end(expr.span, err); } }); } hir::StmtKind::Semi(ref expr) => { - // All of this is equivalent to calling `check_expr`, but it is inlined out here - // in order to capture the fact that this `match` is the last statement in its - // function. This is done for better suggestions to remove the `;`. - let expectation = match expr.kind { - hir::ExprKind::Match(..) if is_last => IsLast(stmt.span), - _ => NoExpectation, - }; - self.check_expr_with_expectation(expr, expectation); + self.check_expr(expr); } } @@ -1488,7 +1526,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } pub fn check_block_no_value(&self, blk: &'tcx hir::Block<'tcx>) { - let unit = self.tcx.mk_unit(); + let unit = Ty::new_unit(self.tcx); let ty = self.check_block_with_expected(blk, ExpectHasType(unit)); // if the block produces a `!` value, that can always be @@ -1530,8 +1568,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ctxt = BreakableCtxt { coerce: Some(coerce), may_break: false }; let (ctxt, ()) = self.with_breakable_ctxt(blk.hir_id, ctxt, || { - for (pos, s) in blk.stmts.iter().enumerate() { - self.check_stmt(s, blk.stmts.len() - 1 == pos); + for s in blk.stmts { + self.check_stmt(s); } // check the tail expression **without** holding the @@ -1544,7 +1582,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let coerce = ctxt.coerce.as_mut().unwrap(); if let Some((tail_expr, tail_expr_ty)) = tail_expr_ty { let span = self.get_expr_coercion_span(tail_expr); - let cause = self.cause(span, ObligationCauseCode::BlockTailExpression(blk.hir_id)); + let cause = self.cause( + span, + ObligationCauseCode::BlockTailExpression(blk.hir_id, hir::MatchSource::Normal), + ); let ty_for_diagnostic = coerce.merged_ty(); // We use coerce_inner here because we want to augment the error // suggesting to wrap the block in square brackets if it might've @@ -1554,9 +1595,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &cause, Some(tail_expr), tail_expr_ty, - Some(&mut |diag: &mut Diagnostic| { + |diag| { self.suggest_block_to_brackets(diag, blk, tail_expr_ty, ty_for_diagnostic); - }), + }, false, ); } else { @@ -1593,7 +1634,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { coerce.coerce_forced_unit( self, &self.misc(sp), - &mut |err| { + |err| { if let Some(expected_ty) = expected.only_has_type(self) { if blk.stmts.is_empty() && blk.expr.is_none() { self.suggest_boxing_when_appropriate( @@ -1601,7 +1642,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { blk.span, blk.hir_id, expected_ty, - self.tcx.mk_unit(), + Ty::new_unit(self.tcx), ); } if !self.consider_removing_semicolon(blk, expected_ty, err) { @@ -1631,7 +1672,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir::Stmt { kind: hir::StmtKind::Expr(hir::Expr { - kind: hir::ExprKind::Assign(..), + kind: hir::ExprKind::Assign(lhs, ..), .. }), .. @@ -1641,7 +1682,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } = blk { self.comes_from_while_condition(blk.hir_id, |_| { - err.downgrade_to_delayed_bug(); + // We cannot suppress the error if the LHS of assignment + // is a syntactic place expression because E0070 would + // not be emitted by `check_lhs_assignable`. + let res = self.typeck_results.borrow().expr_ty_opt(lhs); + + if !lhs.is_syntactic_place_expr() + || res.references_error() + { + err.downgrade_to_delayed_bug(); + } }) } } @@ -1738,12 +1788,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { if let Err(guar) = ty.error_reported() { // Override the types everywhere with `err()` to avoid knock on errors. - let err = self.tcx.ty_error(guar); + let err = Ty::new_error(self.tcx, guar); self.write_ty(hir_id, err); self.write_ty(pat.hir_id, err); - let local_ty = LocalTy { decl_ty: err, revealed_ty: err }; - self.locals.borrow_mut().insert(hir_id, local_ty); - self.locals.borrow_mut().insert(pat.hir_id, local_ty); + self.locals.borrow_mut().insert(hir_id, err); + self.locals.borrow_mut().insert(pat.hir_id, err); } } @@ -1767,8 +1816,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let result = self .astconv() .associated_path_to_ty(hir_id, path_span, ty.raw, qself, segment, true); - let ty = - result.map(|(ty, _, _)| ty).unwrap_or_else(|guar| self.tcx().ty_error(guar)); + let ty = result + .map(|(ty, _, _)| ty) + .unwrap_or_else(|guar| Ty::new_error(self.tcx(), guar)); let ty = self.handle_raw_ty(path_span, ty); let result = result.map(|(_, kind, def_id)| (kind, def_id)); @@ -1807,19 +1857,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if self.adjust_fulfillment_error_for_expr_obligation(error) || before_span != error.obligation.cause.span { - // Store both the predicate and the predicate *without constness* - // since sometimes we instantiate and check both of these in a - // method call, for example. remap_cause.insert(( before_span, error.obligation.predicate, error.obligation.cause.clone(), )); - remap_cause.insert(( - before_span, - error.obligation.predicate.without_const(self.tcx), - error.obligation.cause.clone(), - )); } else { // If it failed to be adjusted once around, it may be adjusted // via the "remap cause" mapping the second time... @@ -1890,7 +1932,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // do that, so it's OK. for (predicate, span) in instantiated { - if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = predicate.kind().skip_binder() + if let ty::ClauseKind::Trait(pred) = predicate.kind().skip_binder() && pred.self_ty().peel_refs() == callee_ty && self.tcx.is_fn_trait(pred.def_id()) { @@ -1922,7 +1964,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx, traits::ObligationCause::dummy(), self.param_env, - ty::Binder::dummy(trait_ref), + trait_ref, ); match SelectionContext::new(&self).select(&obligation) { Ok(Some(traits::ImplSource::UserDefined(impl_source))) => { @@ -1971,7 +2013,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { ("closure", self.tcx.def_span(def_id)) }; - err.span_note(span, format!("{} defined here", kind)); + err.span_note(span, format!("{kind} defined here")); } else { err.span_note( self.tcx.def_span(def_id), diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index 67f45f9aa3f0d..6a82b00211e45 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -163,7 +163,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return fn_sig; } self.probe(|_| { - let ocx = ObligationCtxt::new_in_snapshot(self); + let ocx = ObligationCtxt::new(self); let normalized_fn_sig = ocx.normalize(&ObligationCause::dummy(), self.param_env, fn_sig); if ocx.select_all_or_error().is_empty() { @@ -189,6 +189,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub fn errors_reported_since_creation(&self) -> bool { self.tcx.sess.err_count() > self.err_count_on_creation } + + pub fn next_root_ty_var(&self, origin: TypeVariableOrigin) -> Ty<'tcx> { + Ty::new_var(self.tcx, self.next_ty_var_id_in_universe(origin, ty::UniverseIndex::ROOT)) + } } impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> { @@ -222,9 +226,7 @@ impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> { predicates: tcx.arena.alloc_from_iter( self.param_env.caller_bounds().iter().filter_map(|predicate| { match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) - if data.self_ty().is_param(index) => - { + ty::ClauseKind::Trait(data) if data.self_ty().is_param(index) => { // HACK(eddyb) should get the original `Span`. let span = tcx.def_span(def_id); Some((predicate, span)) @@ -286,21 +288,23 @@ impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> { poly_trait_ref, ); - let item_substs = self.astconv().create_substs_for_associated_item( + let item_args = self.astconv().create_args_for_associated_item( span, item_def_id, item_segment, - trait_ref.substs, + trait_ref.args, ); - self.tcx().mk_projection(item_def_id, item_substs) + Ty::new_projection(self.tcx(), item_def_id, item_args) } fn probe_adt(&self, span: Span, ty: Ty<'tcx>) -> Option> { match ty.kind() { ty::Adt(adt_def, _) => Some(*adt_def), // FIXME(#104767): Should we handle bound regions here? - ty::Alias(ty::Projection | ty::Inherent, _) if !ty.has_escaping_bound_vars() => { + ty::Alias(ty::Projection | ty::Inherent | ty::Weak, _) + if !ty.has_escaping_bound_vars() => + { self.normalize(span, ty).ty_adt_def() } _ => None, diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index c4add4dbdfb2f..d2a53ee8b5e10 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -1,6 +1,6 @@ use super::FnCtxt; -use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel, SuggestBoxing}; +use crate::errors; use crate::fluent_generated as fluent; use crate::method::probe::{IsSuggestion, Mode, ProbeScope}; use rustc_ast::util::parser::{ExprPrecedence, PREC_POSTFIX}; @@ -95,8 +95,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { found: Ty<'tcx>, can_satisfy: impl FnOnce(Ty<'tcx>) -> bool, ) -> bool { - let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(found) - else { return false; }; + let Some((def_id_or_name, output, inputs)) = self.extract_callable_info(found) else { + return false; + }; if can_satisfy(output) { let (sugg_call, mut applicability) = match inputs.len() { 0 => ("".to_string(), Applicability::MachineApplicable), @@ -178,10 +179,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rhs_ty: Ty<'tcx>, can_satisfy: impl FnOnce(Ty<'tcx>, Ty<'tcx>) -> bool, ) -> bool { - let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_ty) - else { return false; }; - let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_ty) - else { return false; }; + let Some((_, lhs_output_ty, lhs_inputs)) = self.extract_callable_info(lhs_ty) else { + return false; + }; + let Some((_, rhs_output_ty, rhs_inputs)) = self.extract_callable_info(rhs_ty) else { + return false; + }; if can_satisfy(lhs_output_ty, rhs_output_ty) { let mut sugg = vec![]; @@ -275,6 +278,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>, ) -> bool { let expr = expr.peel_blocks(); + let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); + if let Some((suggestion, msg, applicability, verbose, annotation)) = self.suggest_deref_or_ref(expr, found, expected) { @@ -325,9 +330,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } return true; - } else if self.suggest_else_fn_with_closure(err, expr, found, expected) { + } + + if self.suggest_else_fn_with_closure(err, expr, found, expected) { return true; - } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) + } + + if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) && let ty::FnDef(def_id, ..) = *found.kind() && let Some(sp) = self.tcx.hir().span_if_local(def_id) { @@ -343,97 +352,156 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_label(sp, format!("{descr} `{name}` defined here")); } return true; - } else if self.suggest_cast(err, expr, found, expected, expected_ty_expr) { + } + + if self.suggest_cast(err, expr, found, expected, expected_ty_expr) { return true; - } else { - let methods = self.get_conversion_methods(expr.span, expected, found, expr.hir_id); - if !methods.is_empty() { - let mut suggestions = methods.iter() - .filter_map(|conversion_method| { - let receiver_method_ident = expr.method_ident(); - if let Some(method_ident) = receiver_method_ident - && method_ident.name == conversion_method.name - { - return None // do not suggest code that is already there (#53348) - } + } - let method_call_list = [sym::to_vec, sym::to_string]; - let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind - && receiver_method.ident.name == sym::clone - && method_call_list.contains(&conversion_method.name) - // If receiver is `.clone()` and found type has one of those methods, - // we guess that the user wants to convert from a slice type (`&[]` or `&str`) - // to an owned type (`Vec` or `String`). These conversions clone internally, - // so we remove the user's `clone` call. - { - vec![( - receiver_method.ident.span, - conversion_method.name.to_string() - )] - } else if expr.precedence().order() - < ExprPrecedence::MethodCall.order() - { - vec![ - (expr.span.shrink_to_lo(), "(".to_string()), - (expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)), - ] - } else { - vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))] - }; - let struct_pat_shorthand_field = self.maybe_get_struct_pattern_shorthand_field(expr); - if let Some(name) = struct_pat_shorthand_field { - sugg.insert( - 0, - (expr.span.shrink_to_lo(), format!("{}: ", name)), - ); - } - Some(sugg) - }) - .peekable(); - if suggestions.peek().is_some() { - err.multipart_suggestions( - "try using a conversion method", - suggestions, - Applicability::MaybeIncorrect, - ); - return true; - } - } else if let ty::Adt(found_adt, found_substs) = found.kind() - && self.tcx.is_diagnostic_item(sym::Option, found_adt.did()) - && let ty::Adt(expected_adt, expected_substs) = expected.kind() - && self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) - && let ty::Ref(_, inner_ty, _) = expected_substs.type_at(0).kind() - && inner_ty.is_str() - { - let ty = found_substs.type_at(0); - let mut peeled = ty; - let mut ref_cnt = 0; - while let ty::Ref(_, inner, _) = peeled.kind() { - peeled = *inner; - ref_cnt += 1; - } - if let ty::Adt(adt, _) = peeled.kind() - && Some(adt.did()) == self.tcx.lang_items().string() - { - let sugg = if ref_cnt == 0 { - ".as_deref()" + if !methods.is_empty() { + let mut suggestions = methods + .iter() + .filter_map(|conversion_method| { + let receiver_method_ident = expr.method_ident(); + if let Some(method_ident) = receiver_method_ident + && method_ident.name == conversion_method.name + { + return None // do not suggest code that is already there (#53348) + } + + let method_call_list = [sym::to_vec, sym::to_string]; + let mut sugg = if let ExprKind::MethodCall(receiver_method, ..) = expr.kind + && receiver_method.ident.name == sym::clone + && method_call_list.contains(&conversion_method.name) + // If receiver is `.clone()` and found type has one of those methods, + // we guess that the user wants to convert from a slice type (`&[]` or `&str`) + // to an owned type (`Vec` or `String`). These conversions clone internally, + // so we remove the user's `clone` call. + { + vec![( + receiver_method.ident.span, + conversion_method.name.to_string() + )] + } else if expr.precedence().order() + < ExprPrecedence::MethodCall.order() + { + vec![ + (expr.span.shrink_to_lo(), "(".to_string()), + (expr.span.shrink_to_hi(), format!(").{}()", conversion_method.name)), + ] } else { - ".map(|x| x.as_str())" + vec![(expr.span.shrink_to_hi(), format!(".{}()", conversion_method.name))] }; - err.span_suggestion_verbose( - expr.span.shrink_to_hi(), - fluent::hir_typeck_convert_to_str, - sugg, - Applicability::MachineApplicable, - ); - return true; - } + let struct_pat_shorthand_field = + self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr); + if let Some(name) = struct_pat_shorthand_field { + sugg.insert(0, (expr.span.shrink_to_lo(), format!("{name}: "))); + } + Some(sugg) + }) + .peekable(); + if suggestions.peek().is_some() { + err.multipart_suggestions( + "try using a conversion method", + suggestions, + Applicability::MaybeIncorrect, + ); + return true; + } + } + + if let Some((found_ty_inner, expected_ty_inner, error_tys)) = + self.deconstruct_option_or_result(found, expected) + && let ty::Ref(_, peeled, hir::Mutability::Not) = *expected_ty_inner.kind() + { + // Suggest removing any stray borrows (unless there's macro shenanigans involved). + let inner_expr = expr.peel_borrows(); + if !inner_expr.span.eq_ctxt(expr.span) { + return false; + } + let borrow_removal_span = if inner_expr.hir_id == expr.hir_id { + None + } else { + Some(expr.span.shrink_to_lo().until(inner_expr.span)) + }; + // Given `Result<_, E>`, check our expected ty is `Result<_, &E>` for + // `as_ref` and `as_deref` compatibility. + let error_tys_equate_as_ref = error_tys.map_or(true, |(found, expected)| { + self.can_eq(self.param_env, Ty::new_imm_ref(self.tcx,self.tcx.lifetimes.re_erased, found), expected) + }); + // FIXME: This could/should be extended to suggest `as_mut` and `as_deref_mut`, + // but those checks need to be a bit more delicate and the benefit is diminishing. + if self.can_eq(self.param_env, found_ty_inner, peeled) && error_tys_equate_as_ref { + err.subdiagnostic(errors::SuggestConvertViaMethod { + span: expr.span.shrink_to_hi(), + sugg: ".as_ref()", + expected, + found, + borrow_removal_span, + }); + return true; + } else if let Some((deref_ty, _)) = + self.autoderef(expr.span, found_ty_inner).silence_errors().nth(1) + && self.can_eq(self.param_env, deref_ty, peeled) + && error_tys_equate_as_ref + { + err.subdiagnostic(errors::SuggestConvertViaMethod { + span: expr.span.shrink_to_hi(), + sugg: ".as_deref()", + expected, + found, + borrow_removal_span, + }); + return true; + } else if let ty::Adt(adt, _) = found_ty_inner.peel_refs().kind() + && Some(adt.did()) == self.tcx.lang_items().string() + && peeled.is_str() + // `Result::map`, conversely, does not take ref of the error type. + && error_tys.map_or(true, |(found, expected)| { + self.can_eq(self.param_env, found, expected) + }) + { + err.span_suggestion_verbose( + expr.span.shrink_to_hi(), + fluent::hir_typeck_convert_to_str, + ".map(|x| x.as_str())", + Applicability::MachineApplicable, + ); + return true; } } false } + fn deconstruct_option_or_result( + &self, + found_ty: Ty<'tcx>, + expected_ty: Ty<'tcx>, + ) -> Option<(Ty<'tcx>, Ty<'tcx>, Option<(Ty<'tcx>, Ty<'tcx>)>)> { + let ty::Adt(found_adt, found_args) = found_ty.peel_refs().kind() else { + return None; + }; + let ty::Adt(expected_adt, expected_args) = expected_ty.kind() else { + return None; + }; + if self.tcx.is_diagnostic_item(sym::Option, found_adt.did()) + && self.tcx.is_diagnostic_item(sym::Option, expected_adt.did()) + { + Some((found_args.type_at(0), expected_args.type_at(0), None)) + } else if self.tcx.is_diagnostic_item(sym::Result, found_adt.did()) + && self.tcx.is_diagnostic_item(sym::Result, expected_adt.did()) + { + Some(( + found_args.type_at(0), + expected_args.type_at(0), + Some((found_args.type_at(1), expected_args.type_at(1))), + )) + } else { + None + } + } + /// When encountering the expected boxed value allocated in the stack, suggest allocating it /// in the heap by calling `Box::new()`. pub(in super::super) fn suggest_boxing_when_appropriate( @@ -448,10 +516,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if self.tcx.hir().is_inside_const_context(hir_id) || !expected.is_box() || found.is_box() { return false; } - if self.can_coerce(self.tcx.mk_box(found), expected) { + if self.can_coerce(Ty::new_box(self.tcx, found), expected) { let suggest_boxing = match found.kind() { ty::Tuple(tuple) if tuple.is_empty() => { - SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span } + errors::SuggestBoxing::Unit { start: span.shrink_to_lo(), end: span } } ty::Generator(def_id, ..) if matches!( @@ -459,9 +527,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Some(GeneratorKind::Async(AsyncGeneratorKind::Closure)) ) => { - SuggestBoxing::AsyncBody + errors::SuggestBoxing::AsyncBody } - _ => SuggestBoxing::Other { start: span.shrink_to_lo(), end: span.shrink_to_hi() }, + _ => errors::SuggestBoxing::Other { + start: span.shrink_to_lo(), + end: span.shrink_to_hi(), + }, }; err.subdiagnostic(suggest_boxing); @@ -488,7 +559,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .take(4) .map(|(var_hir_id, upvar)| { let var_name = self.tcx.hir().name(*var_hir_id).to_string(); - let msg = format!("`{}` captured here", var_name); + let msg = format!("`{var_name}` captured here"); (upvar.span, msg) }) .collect::>(); @@ -528,9 +599,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if pin_did.is_none() || self.tcx.lang_items().owned_box().is_none() { return false; } - let box_found = self.tcx.mk_box(found); - let pin_box_found = self.tcx.mk_lang_item(box_found, LangItem::Pin).unwrap(); - let pin_found = self.tcx.mk_lang_item(found, LangItem::Pin).unwrap(); + let box_found = Ty::new_box(self.tcx, found); + let pin_box_found = Ty::new_lang_item(self.tcx, box_found, LangItem::Pin).unwrap(); + let pin_found = Ty::new_lang_item(self.tcx, found, LangItem::Pin).unwrap(); match expected.kind() { ty::Adt(def, _) if Some(def.did()) == pin_did => { if self.can_coerce(pin_box_found, expected) { @@ -568,7 +639,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // is and we were expecting a Box, ergo Pin>, we // can suggest Box::pin. let parent = self.tcx.hir().parent_id(expr.hir_id); - let Some(Node::Expr(Expr { kind: ExprKind::Call(fn_name, _), .. })) = self.tcx.hir().find(parent) else { + let Some(Node::Expr(Expr { kind: ExprKind::Call(fn_name, _), .. })) = + self.tcx.hir().find(parent) + else { return false; }; match fn_name.kind { @@ -684,23 +757,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match &fn_decl.output { &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() && !can_suggest => { // `fn main()` must return `()`, do not suggest changing return type - err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span }); + err.subdiagnostic(errors::ExpectedReturnTypeLabel::Unit { span }); return true; } &hir::FnRetTy::DefaultReturn(span) if expected.is_unit() => { if let Some(found) = found.make_suggestable(self.tcx, false) { - err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: found.to_string() }); + err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() }); return true; - } else if let ty::Closure(_, substs) = found.kind() + } else if let ty::Closure(_, args) = found.kind() // FIXME(compiler-errors): Get better at printing binders... - && let closure = substs.as_closure() + && let closure = args.as_closure() && closure.sig().is_suggestable(self.tcx, false) { - err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() }); + err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: closure.print_as_impl_trait().to_string() }); return true; } else { // FIXME: if `found` could be `impl Iterator` we should suggest that. - err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span }); + err.subdiagnostic(errors::AddReturnTypeSuggestion::MissingHere { span }); return true } } @@ -722,10 +795,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!(?found); if found.is_suggestable(self.tcx, false) { if term.span.is_empty() { - err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found: found.to_string() }); + err.subdiagnostic(errors::AddReturnTypeSuggestion::Add { span, found: found.to_string() }); return true; } else { - err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); + err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected }); } } } @@ -741,7 +814,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty = self.normalize(span, ty); let ty = self.tcx.erase_late_bound_regions(ty); if self.can_coerce(expected, ty) { - err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected }); + err.subdiagnostic(errors::ExpectedReturnTypeLabel::Other { span, expected }); self.try_suggest_return_impl_trait(err, expected, ty, fn_id); return true; } @@ -783,12 +856,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn( - hir::FnSig { decl: hir::FnDecl { inputs: fn_parameters, output: fn_return, .. }, .. }, + hir::FnSig { + decl: hir::FnDecl { inputs: fn_parameters, output: fn_return, .. }, + .. + }, hir::Generics { params, predicates, .. }, _body_id, ), .. - })) = fn_node else { return }; + })) = fn_node + else { + return; + }; if params.get(expected_ty_as_param.index as usize).is_none() { return; @@ -853,7 +932,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_suggestion( fn_return.span(), "consider using an impl return type", - format!("impl {}", all_bounds_str), + format!("impl {all_bounds_str}"), Applicability::MaybeIncorrect, ); } @@ -871,10 +950,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if !expected.is_unit() { return; } - let found = self.resolve_vars_with_obligations(found); + let found = self.resolve_vars_if_possible(found); let in_loop = self.is_loop(id) - || self.tcx.hir().parent_iter(id).any(|(parent_id, _)| self.is_loop(parent_id)); + || self + .tcx + .hir() + .parent_iter(id) + .take_while(|(_, node)| { + // look at parents until we find the first body owner + node.body_id().is_none() + }) + .any(|(parent_id, _)| self.is_loop(parent_id)); let in_local_statement = self.is_local_statement(id) || self @@ -907,14 +994,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let ty = self.normalize(expr.span, ty); if self.can_coerce(found, ty) { - err.multipart_suggestion( - "you might have meant to return this value", - vec![ - (expr.span.shrink_to_lo(), "return ".to_string()), - (expr.span.shrink_to_hi(), ";".to_string()), - ], - Applicability::MaybeIncorrect, - ); + if let Some(node) = self.tcx.hir().find(fn_id) + && let Some(owner_node) = node.as_owner() + && let Some(span) = expr.span.find_ancestor_inside(owner_node.span()) + { + err.multipart_suggestion( + "you might have meant to return this value", + vec![ + (span.shrink_to_lo(), "return ".to_string()), + (span.shrink_to_hi(), ";".to_string()), + ], + Applicability::MaybeIncorrect, + ); + } } } } @@ -983,8 +1075,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) .must_apply_modulo_regions() { - let suggestion = match self.maybe_get_struct_pattern_shorthand_field(expr) { - Some(ident) => format!(": {}.clone()", ident), + let suggestion = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { + Some(ident) => format!(": {ident}.clone()"), None => ".clone()".to_string() }; @@ -999,68 +1091,55 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } - pub(crate) fn suggest_copied_or_cloned( + pub(crate) fn suggest_copied_cloned_or_as_ref( &self, diag: &mut Diagnostic, expr: &hir::Expr<'_>, expr_ty: Ty<'tcx>, expected_ty: Ty<'tcx>, ) -> bool { - let ty::Adt(adt_def, substs) = expr_ty.kind() else { return false; }; - let ty::Adt(expected_adt_def, expected_substs) = expected_ty.kind() else { return false; }; + let ty::Adt(adt_def, args) = expr_ty.kind() else { + return false; + }; + let ty::Adt(expected_adt_def, expected_args) = expected_ty.kind() else { + return false; + }; if adt_def != expected_adt_def { return false; } - let mut suggest_copied_or_cloned = || { - let expr_inner_ty = substs.type_at(0); - let expected_inner_ty = expected_substs.type_at(0); - if let &ty::Ref(_, ty, hir::Mutability::Not) = expr_inner_ty.kind() - && self.can_eq(self.param_env, ty, expected_inner_ty) - { - let def_path = self.tcx.def_path_str(adt_def.did()); - if self.type_is_copy_modulo_regions(self.param_env, ty) { - diag.span_suggestion_verbose( - expr.span.shrink_to_hi(), - format!( - "use `{def_path}::copied` to copy the value inside the `{def_path}`" - ), - ".copied()", - Applicability::MachineApplicable, - ); - return true; - } else if let Some(clone_did) = self.tcx.lang_items().clone_trait() - && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions( - self, - self.param_env, - ty, - clone_did, - ) + if Some(adt_def.did()) == self.tcx.get_diagnostic_item(sym::Result) + && self.can_eq(self.param_env, args.type_at(1), expected_args.type_at(1)) + || Some(adt_def.did()) == self.tcx.get_diagnostic_item(sym::Option) + { + let expr_inner_ty = args.type_at(0); + let expected_inner_ty = expected_args.type_at(0); + if let &ty::Ref(_, ty, _mutability) = expr_inner_ty.kind() + && self.can_eq(self.param_env, ty, expected_inner_ty) { - diag.span_suggestion_verbose( - expr.span.shrink_to_hi(), - format!( - "use `{def_path}::cloned` to clone the value inside the `{def_path}`" - ), - ".cloned()", - Applicability::MachineApplicable, - ); + let def_path = self.tcx.def_path_str(adt_def.did()); + let span = expr.span.shrink_to_hi(); + let subdiag = if self.type_is_copy_modulo_regions(self.param_env, ty) { + errors::OptionResultRefMismatch::Copied { + span, def_path + } + } else if let Some(clone_did) = self.tcx.lang_items().clone_trait() + && rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions( + self, + self.param_env, + ty, + clone_did, + ) + { + errors::OptionResultRefMismatch::Cloned { + span, def_path + } + } else { + return false; + }; + diag.subdiagnostic(subdiag); return true; } - } - false - }; - - if let Some(result_did) = self.tcx.get_diagnostic_item(sym::Result) - && adt_def.did() == result_did - // Check that the error types are equal - && self.can_eq(self.param_env, substs.type_at(1), expected_substs.type_at(1)) - { - return suggest_copied_or_cloned(); - } else if let Some(option_did) = self.tcx.get_diagnostic_item(sym::Option) - && adt_def.did() == option_did - { - return suggest_copied_or_cloned(); } false @@ -1102,10 +1181,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), )) { + let mut span = expr.span; + while expr.span.eq_ctxt(span) && let Some(parent_callsite) = span.parent_callsite() + { + span = parent_callsite; + } + let sugg = if expr.precedence().order() >= PREC_POSTFIX { - vec![(expr.span.shrink_to_hi(), ".into()".to_owned())] + vec![(span.shrink_to_hi(), ".into()".to_owned())] } else { - vec![(expr.span.shrink_to_lo(), "(".to_owned()), (expr.span.shrink_to_hi(), ").into()".to_owned())] + vec![(span.shrink_to_lo(), "(".to_owned()), (span.shrink_to_hi(), ").into()".to_owned())] }; diag.multipart_suggestion( format!("call `Into::into` on this expression to convert `{expr_ty}` into `{expected_ty}`"), @@ -1130,7 +1215,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return false; } - let ty::Adt(def, _) = expr_ty.peel_refs().kind() else { return false; }; + let ty::Adt(def, _) = expr_ty.peel_refs().kind() else { + return false; + }; if !self.tcx.is_diagnostic_item(sym::Option, def.did()) { return false; } @@ -1155,8 +1242,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return false; } - let suggestion = match self.maybe_get_struct_pattern_shorthand_field(expr) { - Some(ident) => format!(": {}.is_some()", ident), + let suggestion = match self.tcx.hir().maybe_get_struct_pattern_shorthand_field(expr) { + Some(ident) => format!(": {ident}.is_some()"), None => ".is_some()".to_string(), }; @@ -1252,7 +1339,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { node: rustc_ast::LitKind::Int(lit, rustc_ast::LitIntType::Unsuffixed), span, }) => { - let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(*span) else { return false; }; + let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(*span) else { + return false; + }; if !(snippet.starts_with("0x") || snippet.starts_with("0X")) { return false; } @@ -1292,10 +1381,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // Provided expression needs to be a literal `0`. - let ExprKind::Lit(Spanned { - node: rustc_ast::LitKind::Int(0, _), - span, - }) = expr.kind else { + let ExprKind::Lit(Spanned { node: rustc_ast::LitKind::Int(0, _), span }) = expr.kind else { return false; }; @@ -1326,7 +1412,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &hir::Expr<'_>, expected_ty: Ty<'tcx>, ) -> bool { - let Some((DefKind::AssocFn, old_def_id)) = self.typeck_results.borrow().type_dependent_def(expr.hir_id) else { + let Some((DefKind::AssocFn, old_def_id)) = + self.typeck_results.borrow().type_dependent_def(expr.hir_id) + else { return false; }; let old_item_name = self.tcx.item_name(old_def_id); @@ -1382,7 +1470,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Same item return false; } - let item_ty = self.tcx.type_of(item.def_id).subst_identity(); + let item_ty = self.tcx.type_of(item.def_id).instantiate_identity(); // FIXME(compiler-errors): This check is *so* rudimentary if item_ty.has_param() { return false; @@ -1419,8 +1507,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { found_ty: Ty<'tcx>, expr: &hir::Expr<'_>, ) { - let hir::ExprKind::MethodCall(segment, callee_expr, &[], _) = expr.kind else { return; }; - let Some(clone_trait_did) = self.tcx.lang_items().clone_trait() else { return; }; + // When `expr` is `x` in something like `let x = foo.clone(); x`, need to recurse up to get + // `foo` and `clone`. + let expr = self.note_type_is_not_clone_inner_expr(expr); + + // If we've recursed to an `expr` of `foo.clone()`, get `foo` and `clone`. + let hir::ExprKind::MethodCall(segment, callee_expr, &[], _) = expr.kind else { + return; + }; + + let Some(clone_trait_did) = self.tcx.lang_items().clone_trait() else { + return; + }; let ty::Ref(_, pointee_ty, _) = found_ty.kind() else { return }; let results = self.typeck_results.borrow(); // First, look for a `Clone::clone` call @@ -1470,6 +1568,83 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Given a type mismatch error caused by `&T` being cloned instead of `T`, and + /// the `expr` as the source of this type mismatch, try to find the method call + /// as the source of this error and return that instead. Otherwise, return the + /// original expression. + fn note_type_is_not_clone_inner_expr<'b>( + &'b self, + expr: &'b hir::Expr<'b>, + ) -> &'b hir::Expr<'b> { + match expr.peel_blocks().kind { + hir::ExprKind::Path(hir::QPath::Resolved( + None, + hir::Path { segments: [_], res: crate::Res::Local(binding), .. }, + )) => { + let Some(hir::Node::Pat(hir::Pat { hir_id, .. })) = self.tcx.hir().find(*binding) + else { + return expr; + }; + let Some(parent) = self.tcx.hir().find(self.tcx.hir().parent_id(*hir_id)) else { + return expr; + }; + + match parent { + // foo.clone() + hir::Node::Local(hir::Local { init: Some(init), .. }) => { + self.note_type_is_not_clone_inner_expr(init) + } + // When `expr` is more complex like a tuple + hir::Node::Pat(hir::Pat { + hir_id: pat_hir_id, + kind: hir::PatKind::Tuple(pats, ..), + .. + }) => { + let Some(hir::Node::Local(hir::Local { init: Some(init), .. })) = + self.tcx.hir().find(self.tcx.hir().parent_id(*pat_hir_id)) else { + return expr; + }; + + match init.peel_blocks().kind { + ExprKind::Tup(init_tup) => { + if let Some(init) = pats + .iter() + .enumerate() + .filter(|x| x.1.hir_id == *hir_id) + .find_map(|(i, _)| init_tup.get(i)) + { + self.note_type_is_not_clone_inner_expr(init) + } else { + expr + } + } + _ => expr, + } + } + _ => expr, + } + } + // If we're calling into a closure that may not be typed recurse into that call. no need + // to worry if it's a call to a typed function or closure as this would ne handled + // previously. + hir::ExprKind::Call(Expr { kind: call_expr_kind, .. }, _) => { + if let hir::ExprKind::Path(hir::QPath::Resolved(None, call_expr_path)) = call_expr_kind + && let hir::Path { segments: [_], res: crate::Res::Local(binding), .. } = call_expr_path + && let Some(hir::Node::Pat(hir::Pat { hir_id, .. })) = self.tcx.hir().find(*binding) + && let Some(closure) = self.tcx.hir().find(self.tcx.hir().parent_id(*hir_id)) + && let hir::Node::Local(hir::Local { init: Some(init), .. }) = closure + && let Expr { kind: hir::ExprKind::Closure(hir::Closure { body: body_id, .. }), ..} = init + { + let hir::Body { value: body_expr, .. } = self.tcx.hir().body(*body_id); + self.note_type_is_not_clone_inner_expr(body_expr) + } else { + expr + } + } + _ => expr, + } + } + /// A common error is to add an extra semicolon: /// /// ```compile_fail,E0308 diff --git a/compiler/rustc_hir_typeck/src/gather_locals.rs b/compiler/rustc_hir_typeck/src/gather_locals.rs index 38445f2844052..ed4c63f171c42 100644 --- a/compiler/rustc_hir_typeck/src/gather_locals.rs +++ b/compiler/rustc_hir_typeck/src/gather_locals.rs @@ -1,4 +1,4 @@ -use crate::{FnCtxt, LocalTy}; +use crate::FnCtxt; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::PatKind; @@ -9,6 +9,26 @@ use rustc_span::def_id::LocalDefId; use rustc_span::Span; use rustc_trait_selection::traits; +/// Provides context for checking patterns in declarations. More specifically this +/// allows us to infer array types if the pattern is irrefutable and allows us to infer +/// the size of the array. See issue #76342. +#[derive(Debug, Copy, Clone)] +pub(super) enum DeclOrigin<'a> { + // from an `if let` expression + LetExpr, + // from `let x = ..` + LocalDecl { els: Option<&'a hir::Block<'a>> }, +} + +impl<'a> DeclOrigin<'a> { + pub(super) fn try_get_else(&self) -> Option<&'a hir::Block<'a>> { + match self { + Self::LocalDecl { els } => *els, + Self::LetExpr => None, + } + } +} + /// A declaration is an abstraction of [hir::Local] and [hir::Let]. /// /// It must have a hir_id, as this is how we connect gather_locals to the check functions. @@ -18,20 +38,20 @@ pub(super) struct Declaration<'a> { pub ty: Option<&'a hir::Ty<'a>>, pub span: Span, pub init: Option<&'a hir::Expr<'a>>, - pub els: Option<&'a hir::Block<'a>>, + pub origin: DeclOrigin<'a>, } impl<'a> From<&'a hir::Local<'a>> for Declaration<'a> { fn from(local: &'a hir::Local<'a>) -> Self { let hir::Local { hir_id, pat, ty, span, init, els, source: _ } = *local; - Declaration { hir_id, pat, ty, span, init, els } + Declaration { hir_id, pat, ty, span, init, origin: DeclOrigin::LocalDecl { els } } } } impl<'a> From<&'a hir::Let<'a>> for Declaration<'a> { fn from(let_expr: &'a hir::Let<'a>) -> Self { let hir::Let { hir_id, pat, ty, span, init } = *let_expr; - Declaration { hir_id, pat, ty, span, init: Some(init), els: None } + Declaration { hir_id, pat, ty, span, init: Some(init), origin: DeclOrigin::LetExpr } } } @@ -48,7 +68,7 @@ impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> { Self { fcx, outermost_fn_param_pat: None } } - fn assign(&mut self, span: Span, nid: hir::HirId, ty_opt: Option>) -> Ty<'tcx> { + fn assign(&mut self, span: Span, nid: hir::HirId, ty_opt: Option>) -> Ty<'tcx> { match ty_opt { None => { // Infer the variable's type. @@ -56,23 +76,20 @@ impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> { kind: TypeVariableOriginKind::TypeInference, span, }); - self.fcx - .locals - .borrow_mut() - .insert(nid, LocalTy { decl_ty: var_ty, revealed_ty: var_ty }); + self.fcx.locals.borrow_mut().insert(nid, var_ty); var_ty } Some(typ) => { // Take type that the user specified. self.fcx.locals.borrow_mut().insert(nid, typ); - typ.revealed_ty + typ } } } - /// Allocates a [LocalTy] for a declaration, which may have a type annotation. If it does have - /// a type annotation, then the LocalTy stored will be the resolved type. This may be found - /// again during type checking by querying [FnCtxt::local_ty] for the same hir_id. + /// Allocates a type for a declaration, which may have a type annotation. If it does have + /// a type annotation, then the [`Ty`] stored will be the resolved type. This may be found + /// again during type checking by querying [`FnCtxt::local_ty`] for the same hir_id. fn declare(&mut self, decl: Declaration<'tcx>) { let local_ty = match decl.ty { Some(ref ty) => { @@ -87,7 +104,7 @@ impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> { .user_provided_types_mut() .insert(ty.hir_id, c_ty); - Some(LocalTy { decl_ty: o_ty.normalized, revealed_ty: o_ty.normalized }) + Some(o_ty.normalized) } None => None, }; @@ -96,7 +113,7 @@ impl<'a, 'tcx> GatherLocalsVisitor<'a, 'tcx> { debug!( "local variable {:?} is assigned type {}", decl.pat, - self.fcx.ty_to_string(self.fcx.locals.borrow().get(&decl.hir_id).unwrap().decl_ty) + self.fcx.ty_to_string(*self.fcx.locals.borrow().get(&decl.hir_id).unwrap()) ); } } @@ -129,7 +146,17 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> { self.fcx.require_type_is_sized( var_ty, p.span, - traits::SizedArgumentType(Some(ty_span)), + // ty_span == ident.span iff this is a closure parameter with no type + // ascription, or if it's an implicit `self` parameter + traits::SizedArgumentType( + if ty_span == ident.span + && self.fcx.tcx.is_closure(self.fcx.body_id.into()) + { + None + } else { + Some(ty_span) + }, + ), ); } } else { @@ -141,7 +168,7 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherLocalsVisitor<'a, 'tcx> { debug!( "pattern binding {} is assigned to {} with type {:?}", ident, - self.fcx.ty_to_string(self.fcx.locals.borrow().get(&p.hir_id).unwrap().decl_ty), + self.fcx.ty_to_string(*self.fcx.locals.borrow().get(&p.hir_id).unwrap()), var_ty ); } diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs index e4a62ec05aef1..cfedcee995659 100644 --- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs @@ -6,7 +6,7 @@ use hir::{ intravisit::{self, Visitor}, Body, Expr, ExprKind, Guard, HirId, LoopIdError, }; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir as hir; use rustc_index::IndexVec; use rustc_infer::infer::InferCtxt; @@ -28,7 +28,7 @@ pub(super) fn build_control_flow_graph<'tcx>( consumed_borrowed_places: ConsumedAndBorrowedPlaces, body: &'tcx Body<'tcx>, num_exprs: usize, -) -> (DropRangesBuilder, FxHashSet) { +) -> (DropRangesBuilder, UnordSet) { let mut drop_range_visitor = DropRangeVisitor::new( infcx, typeck_results, @@ -214,6 +214,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::Become(..) | ExprKind::InlineAsm(..) | ExprKind::OffsetOf(..) | ExprKind::Struct(..) @@ -270,6 +271,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | hir::Node::Variant(..) | hir::Node::Field(..) | hir::Node::AnonConst(..) + | hir::Node::ConstBlock(..) | hir::Node::Stmt(..) | hir::Node::PathSegment(..) | hir::Node::Ty(..) @@ -441,15 +443,17 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { // We add an edge to the hir_id of the expression/block we are breaking out of, and // then in process_deferred_edges we will map this hir_id to its PostOrderId, which // will refer to the end of the block due to the post order traversal. - self.find_target_expression_from_destination(destination).map_or((), |target| { + if let Ok(target) = self.find_target_expression_from_destination(destination) { self.drop_ranges.add_control_edge_hir_id(self.expr_index, target) - }); + } if let Some(value) = value { self.visit_expr(value); } } + ExprKind::Become(_call) => bug!("encountered a tail-call inside a generator"), + ExprKind::Call(f, args) => { self.visit_expr(f); for arg in args { @@ -524,7 +528,7 @@ impl DropRangesBuilder { hir: Map<'_>, num_exprs: usize, ) -> Self { - let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default(); + let mut tracked_value_map = UnordMap::<_, TrackedValueIndex>::default(); let mut next = <_>::from(0u32); for value in tracked_values { for_each_consumable(hir, value, |value| { diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs index ecafbd668e22d..e563bd40b6542 100644 --- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs @@ -17,7 +17,7 @@ use self::record_consumed_borrow::find_consumed_and_borrowed; use crate::FnCtxt; use hir::def_id::DefId; use hir::{Body, HirId, HirIdMap, Node}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir as hir; use rustc_index::bit_set::BitSet; use rustc_index::IndexVec; @@ -63,7 +63,7 @@ pub fn compute_drop_ranges<'a, 'tcx>( // If drop range tracking is not enabled, skip all the analysis and produce an // empty set of DropRanges. DropRanges { - tracked_value_map: FxHashMap::default(), + tracked_value_map: UnordMap::default(), nodes: IndexVec::new(), borrowed_temporaries: None, } @@ -125,8 +125,8 @@ impl Debug for TrackedValue { write!(f, "{}", tcx.hir().node_to_string(self.hir_id())) } else { match self { - Self::Variable(hir_id) => write!(f, "Variable({:?})", hir_id), - Self::Temporary(hir_id) => write!(f, "Temporary({:?})", hir_id), + Self::Variable(hir_id) => write!(f, "Variable({hir_id:?})"), + Self::Temporary(hir_id) => write!(f, "Temporary({hir_id:?})"), } } }) @@ -182,9 +182,9 @@ impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue { } pub struct DropRanges { - tracked_value_map: FxHashMap, + tracked_value_map: UnordMap, nodes: IndexVec, - borrowed_temporaries: Option>, + borrowed_temporaries: Option>, } impl DropRanges { @@ -227,7 +227,7 @@ struct DropRangesBuilder { /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping /// from HirIds to the HirIdIndex that is used to represent that value in /// bitvector. - tracked_value_map: FxHashMap, + tracked_value_map: UnordMap, /// When building the control flow graph, we don't always know the /// post-order index of the target node at the point we encounter it. diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs index 8ab0bd535d6fd..29413f0801237 100644 --- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs @@ -4,7 +4,7 @@ use crate::{ FnCtxt, }; use hir::{def_id::DefId, Body, HirId, HirIdMap}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::{fx::FxIndexSet, unord::UnordSet}; use rustc_hir as hir; use rustc_middle::ty::{ParamEnv, TyCtxt}; use rustc_middle::{ @@ -30,13 +30,13 @@ pub(super) struct ConsumedAndBorrowedPlaces { /// /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is /// not considered a drop of `x`, although it would be a drop of `x.y`. - pub(super) consumed: HirIdMap>, + pub(super) consumed: HirIdMap>, /// A set of hir-ids of values or variables that are borrowed at some point within the body. - pub(super) borrowed: FxHashSet, + pub(super) borrowed: UnordSet, /// A set of hir-ids of values or variables that are borrowed at some point within the body. - pub(super) borrowed_temporaries: FxHashSet, + pub(super) borrowed_temporaries: UnordSet, } /// Works with ExprUseVisitor to find interesting values for the drop range analysis. @@ -150,9 +150,10 @@ impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> { hir.node_to_string(diag_expr_id), hir.node_to_string(parent) ); - place_with_id - .try_into() - .map_or((), |tracked_value| self.mark_consumed(parent, tracked_value)); + + if let Ok(tracked_value) = place_with_id.try_into() { + self.mark_consumed(parent, tracked_value) + } } fn borrow( diff --git a/compiler/rustc_hir_typeck/src/generator_interior/mod.rs b/compiler/rustc_hir_typeck/src/generator_interior/mod.rs index 019fb86f55c4c..6a8171224913b 100644 --- a/compiler/rustc_hir_typeck/src/generator_interior/mod.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/mod.rs @@ -112,7 +112,7 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { self.fcx .tcx .sess - .delay_span_bug(span, format!("Encountered var {:?}", unresolved_term)); + .delay_span_bug(span, format!("Encountered var {unresolved_term:?}")); } else { let note = format!( "the type is part of the {} because of this {}", @@ -122,7 +122,7 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> { self.fcx .need_type_info_err_in_generator(self.kind, span, unresolved_term) - .span_note(yield_data.span, &*note) + .span_note(yield_data.span, note) .emit(); } } else { @@ -269,7 +269,7 @@ pub fn resolve_interior<'a, 'tcx>( }, _ => mk_bound_region(ty::BrAnon(None)), }; - let r = fcx.tcx.mk_re_late_bound(current_depth, br); + let r = ty::Region::new_late_bound(fcx.tcx, current_depth, br); r }); captured_tys.insert(ty).then(|| { @@ -295,7 +295,11 @@ pub fn resolve_interior<'a, 'tcx>( let var = ty::BoundVar::from_usize(bound_vars.len()); bound_vars.push(ty::BoundVariableKind::Region(kind)); counter += 1; - fcx.tcx.mk_re_late_bound(ty::INNERMOST, ty::BoundRegion { var, kind }) + ty::Region::new_late_bound( + fcx.tcx, + ty::INNERMOST, + ty::BoundRegion { var, kind }, + ) }, types: &mut |b| bug!("unexpected bound ty in binder: {b:?}"), consts: &mut |b, ty| bug!("unexpected bound ct in binder: {b:?} {ty}"), @@ -308,7 +312,8 @@ pub fn resolve_interior<'a, 'tcx>( // Extract type components to build the witness type. let type_list = fcx.tcx.mk_type_list_from_iter(type_causes.iter().map(|cause| cause.ty)); let bound_vars = fcx.tcx.mk_bound_variable_kinds(&bound_vars); - let witness = fcx.tcx.mk_generator_witness(ty::Binder::bind_with_vars(type_list, bound_vars)); + let witness = + Ty::new_generator_witness(fcx.tcx, ty::Binder::bind_with_vars(type_list, bound_vars)); drop(typeck_results); // Store the generator types and spans into the typeck results for this generator. @@ -357,7 +362,8 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> { let ty = self.interior_visitor.fcx.typeck_results.borrow().node_type(id); let tcx = self.interior_visitor.fcx.tcx; - let ty = tcx.mk_ref( + let ty = Ty::new_ref( + tcx, // Use `ReErased` as `resolve_interior` is going to replace all the // regions anyway. tcx.lifetimes.re_erased, @@ -573,7 +579,7 @@ fn check_must_not_suspend_ty<'tcx>( let mut has_emitted = false; for &(predicate, _) in fcx.tcx.explicit_item_bounds(def).skip_binder() { // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::PredicateKind::Clause(ty::Clause::Trait(ref poly_trait_predicate)) = + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = predicate.kind().skip_binder() { let def_id = poly_trait_predicate.trait_ref.def_id; @@ -686,7 +692,7 @@ fn check_must_not_suspend_def( // Add optional reason note if let Some(note) = attr.value_str() { // FIXME(guswynn): consider formatting this better - lint.span_note(data.source_span, note.as_str()); + lint.span_note(data.source_span, note.to_string()); } // Add some quick suggestions on what to do diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs index 294c3bb78a5ba..7064484a40ff0 100644 --- a/compiler/rustc_hir_typeck/src/inherited.rs +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -1,6 +1,6 @@ use super::callee::DeferredCallResolution; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_hir::HirIdMap; @@ -30,7 +30,7 @@ pub struct Inherited<'tcx> { pub(super) typeck_results: RefCell>, - pub(super) locals: RefCell>>, + pub(super) locals: RefCell>>, pub(super) fulfillment_cx: RefCell>>, @@ -61,9 +61,9 @@ pub struct Inherited<'tcx> { /// Whenever we introduce an adjustment from `!` into a type variable, /// we record that type variable here. This is later used to inform /// fallback. See the `fallback` module for details. - pub(super) diverging_type_vars: RefCell>>, + pub(super) diverging_type_vars: RefCell>>, - pub(super) infer_var_info: RefCell>, + pub(super) infer_var_info: RefCell>, } impl<'tcx> Deref for Inherited<'tcx> { @@ -80,14 +80,14 @@ impl<'tcx> Inherited<'tcx> { let infcx = tcx .infer_ctxt() .ignoring_regions() - .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id)) + .with_opaque_type_inference(DefiningAnchor::Bind(def_id)) .build(); let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner)); Inherited { typeck_results, + fulfillment_cx: RefCell::new(>::new(&infcx)), infcx, - fulfillment_cx: RefCell::new(>::new(tcx)), locals: RefCell::new(Default::default()), deferred_sized_obligations: RefCell::new(Vec::new()), deferred_call_resolutions: RefCell::new(Default::default()), @@ -129,7 +129,7 @@ impl<'tcx> Inherited<'tcx> { let infer_var_info = &mut self.infer_var_info.borrow_mut(); // (*) binder skipped - if let ty::PredicateKind::Clause(ty::Clause::Trait(tpred)) = obligation.predicate.kind().skip_binder() + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(tpred)) = obligation.predicate.kind().skip_binder() && let Some(ty) = self.shallow_resolve(tpred.self_ty()).ty_vid().map(|t| self.root_var(t)) && self.tcx.lang_items().sized_trait().is_some_and(|st| st != tpred.trait_ref.def_id) { @@ -143,7 +143,7 @@ impl<'tcx> Inherited<'tcx> { .kind() .rebind( // (*) binder moved here - ty::PredicateKind::Clause(ty::Clause::Trait(tpred.with_self_ty(self.tcx, new_self_ty))) + ty::PredicateKind::Clause(ty::ClauseKind::Trait(tpred.with_self_ty(self.tcx, new_self_ty))) ), ); // Don't report overflow errors. Otherwise equivalent to may_hold. @@ -152,7 +152,7 @@ impl<'tcx> Inherited<'tcx> { } } - if let ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) = + if let ty::PredicateKind::Clause(ty::ClauseKind::Projection(predicate)) = obligation.predicate.kind().skip_binder() { // If the projection predicate (Foo::Bar == X) has X as a non-TyVid, diff --git a/compiler/rustc_hir_typeck/src/intrinsicck.rs b/compiler/rustc_hir_typeck/src/intrinsicck.rs index 3c5eafd948488..4e65182f15806 100644 --- a/compiler/rustc_hir_typeck/src/intrinsicck.rs +++ b/compiler/rustc_hir_typeck/src/intrinsicck.rs @@ -11,7 +11,7 @@ use super::FnCtxt; /// If the type is `Option`, it will return `T`, otherwise /// the type itself. Works on most `Option`-like types. fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - let ty::Adt(def, substs) = *ty.kind() else { return ty }; + let ty::Adt(def, args) = *ty.kind() else { return ty }; if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() { let data_idx; @@ -28,7 +28,7 @@ fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { } if def.variant(data_idx).fields.len() == 1 { - return def.variant(data_idx).single_field().ty(tcx, substs); + return def.variant(data_idx).single_field().ty(tcx, args); } } @@ -81,9 +81,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Try to display a sensible error with as much information as possible. - let skeleton_string = |ty: Ty<'tcx>, sk| match sk { - Ok(SizeSkeleton::Known(size)) => format!("{} bits", size.bits()), + let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk { Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"), + Ok(SizeSkeleton::Known(size)) => { + if let Some(v) = u128::from(size.bytes()).checked_mul(8) { + format!("{v} bits") + } else { + // `u128` should definitely be able to hold the size of different architectures + // larger sizes should be reported as error `are too big for the current architecture` + // otherwise we have a bug somewhere + bug!("{:?} overflow for u128", size) + } + } Ok(SizeSkeleton::Generic(size)) => { if let Some(size) = size.try_eval_target_usize(tcx, self.param_env) { format!("{size} bytes") @@ -92,7 +101,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } Err(LayoutError::Unknown(bad)) => { - if bad == ty { + if *bad == ty { "this type does not have a fixed size".to_owned() } else { format!("size can vary because of {bad}") @@ -113,14 +122,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { err.note(format!("source type: `{}` ({})", from, skeleton_string(from, sk_from))) .note(format!("target type: `{}` ({})", to, skeleton_string(to, sk_to))); - let mut should_delay_as_bug = false; - if let Err(LayoutError::Unknown(bad_from)) = sk_from && bad_from.references_error() { - should_delay_as_bug = true; - } - if let Err(LayoutError::Unknown(bad_to)) = sk_to && bad_to.references_error() { - should_delay_as_bug = true; - } - if should_delay_as_bug { + if let Err(LayoutError::ReferencesError(_)) = sk_from { + err.delay_as_bug(); + } else if let Err(LayoutError::ReferencesError(_)) = sk_to { err.delay_as_bug(); } } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index b97b55d8f7ee8..c4d3cbc9faab0 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -5,9 +5,7 @@ #![feature(box_patterns)] #![feature(min_specialization)] #![feature(control_flow_enum)] -#![feature(drain_filter)] #![feature(option_as_slice)] -#![allow(rustc::potential_query_instability)] #![recursion_limit = "256"] #[macro_use] @@ -73,7 +71,7 @@ use rustc_middle::traits; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::config; use rustc_span::def_id::{DefId, LocalDefId}; -use rustc_span::{sym, Span}; +use rustc_span::Span; fluent_messages! { "../messages.ftl" } @@ -90,13 +88,6 @@ macro_rules! type_error_struct { }) } -/// The type of a local binding, including the revealed type for anon types. -#[derive(Copy, Clone, Debug)] -pub struct LocalTy<'tcx> { - decl_ty: Ty<'tcx>, - revealed_ty: Ty<'tcx>, -} - /// If this `DefId` is a "primary tables entry", returns /// `Some((body_id, body_ty, fn_sig))`. Otherwise, returns `None`. /// @@ -110,7 +101,7 @@ fn primary_body_of( ) -> Option<(hir::BodyId, Option<&hir::Ty<'_>>, Option<&hir::FnSig<'_>>)> { match node { Node::Item(item) => match item.kind { - hir::ItemKind::Const(ty, body) | hir::ItemKind::Static(ty, _, body) => { + hir::ItemKind::Const(ty, _, body) | hir::ItemKind::Static(ty, _, body) => { Some((body, Some(ty), None)) } hir::ItemKind::Fn(ref sig, .., body) => Some((body, None, Some(sig))), @@ -149,11 +140,11 @@ fn has_typeck_results(tcx: TyCtxt<'_>, def_id: DefId) -> bool { } fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &UnordSet { - &*tcx.typeck(def_id).used_trait_imports + &tcx.typeck(def_id).used_trait_imports } fn typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::TypeckResults<'tcx> { - let fallback = move || tcx.type_of(def_id.to_def_id()).subst_identity(); + let fallback = move || tcx.type_of(def_id.to_def_id()).instantiate_identity(); typeck_with_fallback(tcx, def_id, fallback) } @@ -162,7 +153,7 @@ fn typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::TypeckResults<'tc fn diagnostic_only_typeck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &ty::TypeckResults<'tcx> { let fallback = move || { let span = tcx.hir().span(tcx.hir().local_def_id_to_hir_id(def_id)); - tcx.ty_error_with_message(span, "diagnostic only typeck table used") + Ty::new_error_with_message(tcx, span, "diagnostic only typeck table used") }; typeck_with_fallback(tcx, def_id, fallback) } @@ -191,11 +182,7 @@ fn typeck_with_fallback<'tcx>( let body = tcx.hir().body(body_id); let param_env = tcx.param_env(def_id); - let param_env = if tcx.has_attr(def_id, sym::rustc_do_not_const_check) { - param_env.without_const() - } else { - param_env - }; + let inh = Inherited::new(tcx, def_id); let mut fcx = FnCtxt::new(&inh, param_env, def_id); @@ -203,7 +190,7 @@ fn typeck_with_fallback<'tcx>( let fn_sig = if rustc_hir_analysis::collect::get_infer_ret_ty(&decl.output).is_some() { fcx.astconv().ty_of_fn(id, header.unsafety, header.abi, decl, None, None) } else { - tcx.fn_sig(def_id).subst_identity() + tcx.fn_sig(def_id).instantiate_identity() }; check_abi(tcx, id, span, fn_sig.abi()); @@ -272,11 +259,7 @@ fn typeck_with_fallback<'tcx>( // Closure and generator analysis may run after fallback // because they don't constrain other type variables. - // Closure analysis only runs on closures. Therefore they only need to fulfill non-const predicates (as of now) - let prev_constness = fcx.param_env.constness(); - fcx.param_env = fcx.param_env.without_const(); fcx.closure_analyze(body); - fcx.param_env = fcx.param_env.with_constness(prev_constness); assert!(fcx.deferred_call_resolutions.borrow().is_empty()); // Before the generator analysis, temporary scopes shall be marked to provide more // precise information on types to be captured. diff --git a/compiler/rustc_hir_typeck/src/mem_categorization.rs b/compiler/rustc_hir_typeck/src/mem_categorization.rs index 78171e0b20e8c..9574da0212f58 100644 --- a/compiler/rustc_hir_typeck/src/mem_categorization.rs +++ b/compiler/rustc_hir_typeck/src/mem_categorization.rs @@ -198,13 +198,14 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { } /// Like `pat_ty`, but ignores implicit `&` patterns. + #[instrument(level = "debug", skip(self), ret)] fn pat_ty_unadjusted(&self, pat: &hir::Pat<'_>) -> McResult> { let base_ty = self.node_ty(pat.hir_id)?; - debug!("pat_ty(pat={:?}) base_ty={:?}", pat, base_ty); + trace!(?base_ty); // This code detects whether we are looking at a `ref x`, // and if so, figures out what the type *being borrowed* is. - let ret_ty = match pat.kind { + match pat.kind { PatKind::Binding(..) => { let bm = *self .typeck_results @@ -217,21 +218,18 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { // but what we want here is the type of the underlying value being borrowed. // So peel off one-level, turning the &T into T. match base_ty.builtin_deref(false) { - Some(t) => t.ty, + Some(t) => Ok(t.ty), None => { - debug!("By-ref binding of non-derefable type {:?}", base_ty); - return Err(()); + debug!("By-ref binding of non-derefable type"); + Err(()) } } } else { - base_ty + Ok(base_ty) } } - _ => base_ty, - }; - debug!("pat_ty(pat={:?}) ret_ty={:?}", pat, ret_ty); - - Ok(ret_ty) + _ => Ok(base_ty), + } } pub(crate) fn cat_expr(&self, expr: &hir::Expr<'_>) -> McResult> { @@ -277,9 +275,11 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { adjustment::Adjust::Deref(overloaded) => { // Equivalent to *expr or something similar. let base = if let Some(deref) = overloaded { - let ref_ty = self - .tcx() - .mk_ref(deref.region, ty::TypeAndMut { ty: target, mutbl: deref.mutbl }); + let ref_ty = Ty::new_ref( + self.tcx(), + deref.region, + ty::TypeAndMut { ty: target, mutbl: deref.mutbl }, + ); self.cat_rvalue(expr.hir_id, expr.span, ref_ty) } else { previous()? @@ -297,13 +297,11 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { } } - #[instrument(level = "debug", skip(self))] + #[instrument(level = "debug", skip(self), ret)] pub(crate) fn cat_expr_unadjusted( &self, expr: &hir::Expr<'_>, ) -> McResult> { - debug!("cat_expr: id={} expr={:?}", expr.hir_id, expr); - let expr_ty = self.expr_ty(expr)?; match expr.kind { hir::ExprKind::Unary(hir::UnOp::Deref, ref e_base) => { @@ -317,7 +315,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { hir::ExprKind::Field(ref base, _) => { let base = self.cat_expr(base)?; - debug!("cat_expr(cat_field): id={} expr={:?} base={:?}", expr.hir_id, expr, base); + debug!(?base); let field_idx = self .typeck_results @@ -334,7 +332,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { )) } - hir::ExprKind::Index(ref base, _) => { + hir::ExprKind::Index(ref base, _, _) => { if self.typeck_results.is_method_call(expr) { // If this is an index implemented by a method call, then it // will include an implicit deref of the result. @@ -361,6 +359,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { | hir::ExprKind::AssignOp(..) | hir::ExprKind::Closure { .. } | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Unary(..) | hir::ExprKind::Yield(..) | hir::ExprKind::MethodCall(..) @@ -386,7 +385,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { } } - #[instrument(level = "debug", skip(self, span))] + #[instrument(level = "debug", skip(self, span), ret)] pub(crate) fn cat_res( &self, hir_id: hir::HirId, @@ -427,6 +426,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { /// Note: the actual upvar access contains invisible derefs of closure /// environment and upvar reference as appropriate. Only regionck cares /// about these dereferences, so we let it compute them as needed. + #[instrument(level = "debug", skip(self), ret)] fn cat_upvar(&self, hir_id: hir::HirId, var_id: hir::HirId) -> McResult> { let closure_expr_def_id = self.body_owner; @@ -436,24 +436,20 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { }; let var_ty = self.node_ty(var_id)?; - let ret = PlaceWithHirId::new(hir_id, var_ty, PlaceBase::Upvar(upvar_id), Vec::new()); - - debug!("cat_upvar ret={:?}", ret); - Ok(ret) + Ok(PlaceWithHirId::new(hir_id, var_ty, PlaceBase::Upvar(upvar_id), Vec::new())) } + #[instrument(level = "debug", skip(self), ret)] pub(crate) fn cat_rvalue( &self, hir_id: hir::HirId, span: Span, expr_ty: Ty<'tcx>, ) -> PlaceWithHirId<'tcx> { - debug!("cat_rvalue hir_id={:?}, expr_ty={:?}, span={:?}", hir_id, expr_ty, span); - let ret = PlaceWithHirId::new(hir_id, expr_ty, PlaceBase::Rvalue, Vec::new()); - debug!("cat_rvalue ret={:?}", ret); - ret + PlaceWithHirId::new(hir_id, expr_ty, PlaceBase::Rvalue, Vec::new()) } + #[instrument(level = "debug", skip(self, node), ret)] pub(crate) fn cat_projection( &self, node: &N, @@ -461,16 +457,23 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { ty: Ty<'tcx>, kind: ProjectionKind, ) -> PlaceWithHirId<'tcx> { + let place_ty = base_place.place.ty(); let mut projections = base_place.place.projections; + + let node_ty = self.typeck_results.node_type(node.hir_id()); + // Opaque types can't have field projections, but we can instead convert + // the current place in-place (heh) to the hidden type, and then apply all + // follow up projections on that. + if node_ty != place_ty && matches!(place_ty.kind(), ty::Alias(ty::Opaque, ..)) { + projections.push(Projection { kind: ProjectionKind::OpaqueCast, ty: node_ty }); + } projections.push(Projection { kind, ty }); - let ret = PlaceWithHirId::new( + PlaceWithHirId::new( node.hir_id(), base_place.place.base_ty, base_place.place.base, projections, - ); - debug!("cat_field ret {:?}", ret); - ret + ) } #[instrument(level = "debug", skip(self))] @@ -488,13 +491,13 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { let ty::Ref(region, _, mutbl) = *base_ty.kind() else { span_bug!(expr.span, "cat_overloaded_place: base is not a reference"); }; - let ref_ty = self.tcx().mk_ref(region, ty::TypeAndMut { ty: place_ty, mutbl }); + let ref_ty = Ty::new_ref(self.tcx(), region, ty::TypeAndMut { ty: place_ty, mutbl }); let base = self.cat_rvalue(expr.hir_id, expr.span, ref_ty); self.cat_deref(expr, base) } - #[instrument(level = "debug", skip(self, node))] + #[instrument(level = "debug", skip(self, node), ret)] fn cat_deref( &self, node: &impl HirNode, @@ -511,14 +514,12 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { let mut projections = base_place.place.projections; projections.push(Projection { kind: ProjectionKind::Deref, ty: deref_ty }); - let ret = PlaceWithHirId::new( + Ok(PlaceWithHirId::new( node.hir_id(), base_place.place.base_ty, base_place.place.base, projections, - ); - debug!("cat_deref ret {:?}", ret); - Ok(ret) + )) } pub(crate) fn cat_pattern( @@ -556,7 +557,10 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { Ok(adt_def.variant_index_with_ctor_id(variant_ctor_id)) } Res::Def(DefKind::Ctor(CtorOf::Struct, ..), _) - | Res::Def(DefKind::Struct | DefKind::Union | DefKind::TyAlias | DefKind::AssocTy, _) + | Res::Def( + DefKind::Struct | DefKind::Union | DefKind::TyAlias { .. } | DefKind::AssocTy, + _, + ) | Res::SelfCtor(..) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } => { @@ -592,7 +596,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { fn total_fields_in_tuple(&self, pat_hir_id: hir::HirId, span: Span) -> McResult { let ty = self.typeck_results.node_type(pat_hir_id); match ty.kind() { - ty::Tuple(substs) => Ok(substs.len()), + ty::Tuple(args) => Ok(args.len()), _ => { self.tcx().sess.delay_span_bug(span, "tuple pattern not applied to a tuple"); Err(()) @@ -600,6 +604,13 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { } } + /// Here, `place` is the `PlaceWithHirId` being matched and pat is the pattern it + /// is being matched against. + /// + /// In general, the way that this works is that we walk down the pattern, + /// constructing a `PlaceWithHirId` that represents the path that will be taken + /// to reach the value being matched. + #[instrument(skip(self, op), ret, level = "debug")] fn cat_pattern_( &self, mut place_with_id: PlaceWithHirId<'tcx>, @@ -609,15 +620,6 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { where F: FnMut(&PlaceWithHirId<'tcx>, &hir::Pat<'_>), { - // Here, `place` is the `PlaceWithHirId` being matched and pat is the pattern it - // is being matched against. - // - // In general, the way that this works is that we walk down the pattern, - // constructing a `PlaceWithHirId` that represents the path that will be taken - // to reach the value being matched. - - debug!("cat_pattern(pat={:?}, place_with_id={:?})", pat, place_with_id); - // If (pattern) adjustments are active for this pattern, adjust the `PlaceWithHirId` correspondingly. // `PlaceWithHirId`s are constructed differently from patterns. For example, in // @@ -651,11 +653,11 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { // `deref { deref { place_foo }}` instead of `place_foo` since the pattern is now `Some(x,)` // and not `&&Some(x,)`, even though its assigned type is that of `&&Some(x,)`. for _ in 0..self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(0, |v| v.len()) { - debug!("cat_pattern: applying adjustment to place_with_id={:?}", place_with_id); + debug!("applying adjustment to place_with_id={:?}", place_with_id); place_with_id = self.cat_deref(pat, place_with_id)?; } let place_with_id = place_with_id; // lose mutability - debug!("cat_pattern: applied adjustment derefs to get place_with_id={:?}", place_with_id); + debug!("applied adjustment derefs to get place_with_id={:?}", place_with_id); // Invoke the callback, but only now, after the `place_with_id` has adjusted. // diff --git a/compiler/rustc_hir_typeck/src/method/confirm.rs b/compiler/rustc_hir_typeck/src/method/confirm.rs index 98529b66602fa..7c73f6a89cdb2 100644 --- a/compiler/rustc_hir_typeck/src/method/confirm.rs +++ b/compiler/rustc_hir_typeck/src/method/confirm.rs @@ -5,17 +5,17 @@ use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::GenericArg; use rustc_hir_analysis::astconv::generics::{ - check_generic_arg_count_for_call, create_substs_for_generic_args, + check_generic_arg_count_for_call, create_args_for_parent_generic_args, }; use rustc_hir_analysis::astconv::{AstConv, CreateSubstsForGenericArgsCtxt, IsMethodCall}; use rustc_infer::infer::{self, DefineOpaqueTypes, InferOk}; use rustc_middle::traits::{ObligationCauseCode, UnifyReceiverContext}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCoercion}; use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::fold::TypeFoldable; -use rustc_middle::ty::subst::{self, SubstsRef}; -use rustc_middle::ty::{self, GenericParamDefKind, Ty, TyCtxt}; -use rustc_middle::ty::{InternalSubsts, UserSubsts, UserType}; +use rustc_middle::ty::{ + self, GenericArgs, GenericArgsRef, GenericParamDefKind, Ty, TyCtxt, UserArgs, UserType, +}; use rustc_span::{Span, DUMMY_SP}; use rustc_trait_selection::traits; @@ -26,6 +26,7 @@ struct ConfirmContext<'a, 'tcx> { span: Span, self_expr: &'tcx hir::Expr<'tcx>, call_expr: &'tcx hir::Expr<'tcx>, + skip_record_for_diagnostics: bool, } impl<'a, 'tcx> Deref for ConfirmContext<'a, 'tcx> { @@ -59,6 +60,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut confirm_cx = ConfirmContext::new(self, span, self_expr, call_expr); confirm_cx.confirm(unadjusted_self_ty, pick, segment) } + + pub fn confirm_method_for_diagnostic( + &self, + span: Span, + self_expr: &'tcx hir::Expr<'tcx>, + call_expr: &'tcx hir::Expr<'tcx>, + unadjusted_self_ty: Ty<'tcx>, + pick: &probe::Pick<'tcx>, + segment: &hir::PathSegment<'_>, + ) -> ConfirmResult<'tcx> { + let mut confirm_cx = ConfirmContext::new(self, span, self_expr, call_expr); + confirm_cx.skip_record_for_diagnostics = true; + confirm_cx.confirm(unadjusted_self_ty, pick, segment) + } } impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { @@ -68,7 +83,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { self_expr: &'tcx hir::Expr<'tcx>, call_expr: &'tcx hir::Expr<'tcx>, ) -> ConfirmContext<'a, 'tcx> { - ConfirmContext { fcx, span, self_expr, call_expr } + ConfirmContext { fcx, span, self_expr, call_expr, skip_record_for_diagnostics: false } } fn confirm( @@ -81,13 +96,13 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { let self_ty = self.adjust_self_ty(unadjusted_self_ty, &pick); // Create substitutions for the method's type parameters. - let rcvr_substs = self.fresh_receiver_substs(self_ty, &pick); - let all_substs = self.instantiate_method_substs(&pick, segment, rcvr_substs); + let rcvr_args = self.fresh_receiver_args(self_ty, &pick); + let all_args = self.instantiate_method_args(&pick, segment, rcvr_args); - debug!("rcvr_substs={rcvr_substs:?}, all_substs={all_substs:?}"); + debug!("rcvr_args={rcvr_args:?}, all_args={all_args:?}"); // Create the final signature for the method, replacing late-bound regions. - let (method_sig, method_predicates) = self.instantiate_method_sig(&pick, all_substs); + let (method_sig, method_predicates) = self.instantiate_method_sig(&pick, all_args); // If there is a `Self: Sized` bound and `Self` is a trait object, it is possible that // something which derefs to `Self` actually implements the trait and the caller @@ -97,10 +112,10 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // In that case, we'll error anyway, but we'll also re-run the search with all traits // in scope, and if we find another method which can be used, we'll output an // appropriate hint suggesting to import the trait. - let filler_substs = rcvr_substs + let filler_args = rcvr_args .extend_to(self.tcx, pick.item.def_id, |def, _| self.tcx.mk_param_from_def(def)); let illegal_sized_bound = self.predicates_require_illegal_sized_bound( - self.tcx.predicates_of(pick.item.def_id).instantiate(self.tcx, filler_substs), + self.tcx.predicates_of(pick.item.def_id).instantiate(self.tcx, filler_args), ); // Unify the (adjusted) self type with what the method expects. @@ -114,7 +129,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { "confirm: self_ty={:?} method_sig_rcvr={:?} method_sig={:?} method_predicates={:?}", self_ty, method_sig_rcvr, method_sig, method_predicates ); - self.unify_receivers(self_ty, method_sig_rcvr, &pick, all_substs); + self.unify_receivers(self_ty, method_sig_rcvr, &pick, all_args); let (method_sig, method_predicates) = self.normalize(self.span, (method_sig, method_predicates)); @@ -128,8 +143,8 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // a custom error in that case. if illegal_sized_bound.is_none() { self.add_obligations( - self.tcx.mk_fn_ptr(method_sig), - all_substs, + Ty::new_fn_ptr(self.tcx, method_sig), + all_args, method_predicates, pick.item.def_id, ); @@ -138,7 +153,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // Create the final `MethodCallee`. let callee = MethodCallee { def_id: pick.item.def_id, - substs: all_substs, + args: all_args, sig: method_sig.skip_binder(), }; ConfirmResult { callee, illegal_sized_bound } @@ -156,7 +171,8 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // time writing the results into the various typeck results. let mut autoderef = self.autoderef(self.call_expr.span, unadjusted_self_ty); let Some((ty, n)) = autoderef.nth(pick.autoderefs) else { - return self.tcx.ty_error_with_message( + return Ty::new_error_with_message( + self.tcx, rustc_span::DUMMY_SP, format!("failed autoderef {}", pick.autoderefs), ); @@ -164,7 +180,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { assert_eq!(n, pick.autoderefs); let mut adjustments = self.adjust_steps(&autoderef); - let mut target = self.structurally_resolved_type(autoderef.span(), ty); + let mut target = self.structurally_resolve_type(autoderef.span(), ty); match pick.autoref_or_ptr_adjustment { Some(probe::AutorefOrPtrAdjustment::Autoref { mutbl, unsize }) => { @@ -172,7 +188,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // Type we're wrapping in a reference, used later for unsizing let base_ty = target; - target = self.tcx.mk_ref(region, ty::TypeAndMut { mutbl, ty: target }); + target = Ty::new_ref(self.tcx, region, ty::TypeAndMut { mutbl, ty: target }); // Method call receivers are the primary use case // for two-phase borrows. @@ -185,31 +201,35 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { if unsize { let unsized_ty = if let ty::Array(elem_ty, _) = base_ty.kind() { - self.tcx.mk_slice(*elem_ty) + Ty::new_slice(self.tcx, *elem_ty) } else { bug!( "AutorefOrPtrAdjustment's unsize flag should only be set for array ty, found {}", base_ty ) }; - target = self - .tcx - .mk_ref(region, ty::TypeAndMut { mutbl: mutbl.into(), ty: unsized_ty }); - adjustments - .push(Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target }); + target = Ty::new_ref( + self.tcx, + region, + ty::TypeAndMut { mutbl: mutbl.into(), ty: unsized_ty }, + ); + adjustments.push(Adjustment { + kind: Adjust::Pointer(PointerCoercion::Unsize), + target, + }); } } Some(probe::AutorefOrPtrAdjustment::ToConstPtr) => { target = match target.kind() { &ty::RawPtr(ty::TypeAndMut { ty, mutbl }) => { assert!(mutbl.is_mut()); - self.tcx.mk_ptr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ty }) + Ty::new_ptr(self.tcx, ty::TypeAndMut { mutbl: hir::Mutability::Not, ty }) } - other => panic!("Cannot adjust receiver type {:?} to const ptr", other), + other => panic!("Cannot adjust receiver type {other:?} to const ptr"), }; adjustments.push(Adjustment { - kind: Adjust::Pointer(PointerCast::MutToConstPointer), + kind: Adjust::Pointer(PointerCoercion::MutToConstPointer), target, }); } @@ -219,7 +239,9 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { self.register_predicates(autoderef.into_obligations()); // Write out the final adjustments. - self.apply_adjustments(self.self_expr, adjustments); + if !self.skip_record_for_diagnostics { + self.apply_adjustments(self.self_expr, adjustments); + } target } @@ -230,20 +252,19 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { /// /// Note that this substitution may include late-bound regions from the impl level. If so, /// these are instantiated later in the `instantiate_method_sig` routine. - fn fresh_receiver_substs( + fn fresh_receiver_args( &mut self, self_ty: Ty<'tcx>, pick: &probe::Pick<'tcx>, - ) -> SubstsRef<'tcx> { + ) -> GenericArgsRef<'tcx> { match pick.kind { probe::InherentImplPick => { let impl_def_id = pick.item.container_id(self.tcx); assert!( self.tcx.impl_trait_ref(impl_def_id).is_none(), - "impl {:?} is not an inherent impl", - impl_def_id + "impl {impl_def_id:?} is not an inherent impl" ); - self.fresh_substs_for_item(self.span, impl_def_id) + self.fresh_args_for_item(self.span, impl_def_id) } probe::ObjectPick => { @@ -267,7 +288,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { "original_poly_trait_ref={:?} upcast_trait_ref={:?} target_trait={:?}", original_poly_trait_ref, upcast_trait_ref, trait_def_id ); - upcast_trait_ref.substs + upcast_trait_ref.args }) } @@ -279,13 +300,13 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // the process we will unify the transformed-self-type // of the method with the actual type in order to // unify some of these variables. - self.fresh_substs_for_item(self.span, trait_def_id) + self.fresh_args_for_item(self.span, trait_def_id) } probe::WhereClausePick(poly_trait_ref) => { // Where clauses can have bound regions in them. We need to instantiate // those to convert from a poly-trait-ref to a trait-ref. - self.instantiate_binder_with_fresh_vars(poly_trait_ref).substs + self.instantiate_binder_with_fresh_vars(poly_trait_ref).args } } } @@ -322,12 +343,12 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { }) } - fn instantiate_method_substs( + fn instantiate_method_args( &mut self, pick: &probe::Pick<'tcx>, seg: &hir::PathSegment<'_>, - parent_substs: SubstsRef<'tcx>, - ) -> SubstsRef<'tcx> { + parent_args: GenericArgsRef<'tcx>, + ) -> GenericArgsRef<'tcx> { // Determine the values for the generic parameters of the method. // If they were not explicitly supplied, just construct fresh // variables. @@ -344,7 +365,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // Create subst for early-bound lifetime parameters, combining // parameters from the type and those from the method. - assert_eq!(generics.parent_count, parent_substs.len()); + assert_eq!(generics.parent_count, parent_args.len()); struct MethodSubstsCtxt<'a, 'tcx> { cfcx: &'a ConfirmContext<'a, 'tcx>, @@ -368,7 +389,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { &mut self, param: &ty::GenericParamDef, arg: &GenericArg<'_>, - ) -> subst::GenericArg<'tcx> { + ) -> ty::GenericArg<'tcx> { match (¶m.kind, arg) { (GenericParamDefKind::Lifetime, GenericArg::Lifetime(lt)) => { self.cfcx.fcx.astconv().ast_region_to_region(lt, Some(param)).into() @@ -400,31 +421,31 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { fn inferred_kind( &mut self, - _substs: Option<&[subst::GenericArg<'tcx>]>, + _args: Option<&[ty::GenericArg<'tcx>]>, param: &ty::GenericParamDef, _infer_args: bool, - ) -> subst::GenericArg<'tcx> { + ) -> ty::GenericArg<'tcx> { self.cfcx.var_for_def(self.cfcx.span, param) } } - let substs = create_substs_for_generic_args( + let args = create_args_for_parent_generic_args( self.tcx, pick.item.def_id, - parent_substs, + parent_args, false, None, &arg_count_correct, &mut MethodSubstsCtxt { cfcx: self, pick, seg }, ); - // When the method is confirmed, the `substs` includes + // When the method is confirmed, the `args` includes // parameters from not just the method, but also the impl of // the method -- in particular, the `Self` type will be fully // resolved. However, those are not something that the "user // specified" -- i.e., those types come from the inferred type // of the receiver, not something the user wrote. So when we - // create the user-substs, we want to replace those earlier + // create the user-args, we want to replace those earlier // types with just the types that the user actually wrote -- // that is, those that appear on the *method itself*. // @@ -432,15 +453,15 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // `foo.bar::(...)` -- the `Self` type here will be the // type of `foo` (possibly adjusted), but we don't want to // include that. We want just the `[_, u32]` part. - if !substs.is_empty() && !generics.params.is_empty() { + if !args.is_empty() && !generics.params.is_empty() { let user_type_annotation = self.probe(|_| { - let user_substs = UserSubsts { - substs: InternalSubsts::for_item(self.tcx, pick.item.def_id, |param, _| { + let user_args = UserArgs { + args: GenericArgs::for_item(self.tcx, pick.item.def_id, |param, _| { let i = param.index as usize; if i < generics.parent_count { self.fcx.var_for_def(DUMMY_SP, param) } else { - substs[i] + args[i] } }), user_self_ty: None, // not relevant here @@ -448,15 +469,18 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { self.fcx.canonicalize_user_type_annotation(UserType::TypeOf( pick.item.def_id, - user_substs, + user_args, )) }); - debug!("instantiate_method_substs: user_type_annotation={:?}", user_type_annotation); - self.fcx.write_user_type_annotation(self.call_expr.hir_id, user_type_annotation); + debug!("instantiate_method_args: user_type_annotation={:?}", user_type_annotation); + + if !self.skip_record_for_diagnostics { + self.fcx.write_user_type_annotation(self.call_expr.hir_id, user_type_annotation); + } } - self.normalize(self.span, substs) + self.normalize(self.span, args) } fn unify_receivers( @@ -464,7 +488,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { self_ty: Ty<'tcx>, method_self_ty: Ty<'tcx>, pick: &probe::Pick<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) { debug!( "unify_receivers: self_ty={:?} method_self_ty={:?} span={:?} pick={:?}", @@ -475,7 +499,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { ObligationCauseCode::UnifyReceiver(Box::new(UnifyReceiverContext { assoc_item: pick.item, param_env: self.param_env, - substs, + args, })), ); match self.at(&cause, self.param_env).sup(DefineOpaqueTypes::No, method_self_ty, self_ty) { @@ -485,7 +509,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { Err(terr) => { // FIXME(arbitrary_self_types): We probably should limit the // situations where this can occur by adding additional restrictions - // to the feature, like the self type can't reference method substs. + // to the feature, like the self type can't reference method args. if self.tcx.features().arbitrary_self_types { self.err_ctxt() .report_mismatched_types(&cause, method_self_ty, self_ty, terr) @@ -508,19 +532,19 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { fn instantiate_method_sig( &mut self, pick: &probe::Pick<'tcx>, - all_substs: SubstsRef<'tcx>, + all_args: GenericArgsRef<'tcx>, ) -> (ty::FnSig<'tcx>, ty::InstantiatedPredicates<'tcx>) { - debug!("instantiate_method_sig(pick={:?}, all_substs={:?})", pick, all_substs); + debug!("instantiate_method_sig(pick={:?}, all_args={:?})", pick, all_args); // Instantiate the bounds on the method with the // type/early-bound-regions substitutions performed. There can // be no late-bound regions appearing here. let def_id = pick.item.def_id; - let method_predicates = self.tcx.predicates_of(def_id).instantiate(self.tcx, all_substs); + let method_predicates = self.tcx.predicates_of(def_id).instantiate(self.tcx, all_args); debug!("method_predicates after subst = {:?}", method_predicates); - let sig = self.tcx.fn_sig(def_id).subst(self.tcx, all_substs); + let sig = self.tcx.fn_sig(def_id).instantiate(self.tcx, all_args); debug!("type scheme substituted, sig={:?}", sig); let sig = self.instantiate_binder_with_fresh_vars(sig); @@ -532,18 +556,18 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { fn add_obligations( &mut self, fty: Ty<'tcx>, - all_substs: SubstsRef<'tcx>, + all_args: GenericArgsRef<'tcx>, method_predicates: ty::InstantiatedPredicates<'tcx>, def_id: DefId, ) { debug!( - "add_obligations: fty={:?} all_substs={:?} method_predicates={:?} def_id={:?}", - fty, all_substs, method_predicates, def_id + "add_obligations: fty={:?} all_args={:?} method_predicates={:?} def_id={:?}", + fty, all_args, method_predicates, def_id ); // FIXME: could replace with the following, but we already calculated `method_predicates`, // so we just call `predicates_for_generics` directly to avoid redoing work. - // `self.add_required_obligations(self.span, def_id, &all_substs);` + // `self.add_required_obligations(self.span, def_id, &all_args);` for obligation in traits::predicates_for_generics( |idx, span| { let code = if span.is_dummy() { @@ -566,10 +590,10 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { // this is a projection from a trait reference, so we have to // make sure that the trait reference inputs are well-formed. - self.add_wf_bounds(all_substs, self.call_expr); + self.add_wf_bounds(all_args, self.call_expr); // the function type must also be well-formed (this is not - // implied by the substs being well-formed because of inherent + // implied by the args being well-formed because of inherent // impls and late-bound regions - see issue #28609). self.register_wf_obligation(fty.into(), self.span, traits::WellFormed(None)); } @@ -586,9 +610,7 @@ impl<'a, 'tcx> ConfirmContext<'a, 'tcx> { traits::elaborate(self.tcx, predicates.predicates.iter().copied()) // We don't care about regions here. .filter_map(|pred| match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) - if trait_pred.def_id() == sized_def_id => - { + ty::ClauseKind::Trait(trait_pred) if trait_pred.def_id() == sized_def_id => { let span = predicates .iter() .find_map(|(p, span)| if p == pred { Some(span) } else { None }) diff --git a/compiler/rustc_hir_typeck/src/method/mod.rs b/compiler/rustc_hir_typeck/src/method/mod.rs index 6f4d674ba103b..6dd131aa28397 100644 --- a/compiler/rustc_hir_typeck/src/method/mod.rs +++ b/compiler/rustc_hir_typeck/src/method/mod.rs @@ -7,12 +7,11 @@ mod prelude2021; pub mod probe; mod suggest; -pub use self::suggest::SelfSource; +pub use self::suggest::{MethodCallComponents, SelfSource}; pub use self::MethodError::*; use crate::errors::OpMethodGenericParams; use crate::FnCtxt; -use rustc_data_structures::sync::Lrc; use rustc_errors::{Applicability, Diagnostic, SubdiagnosticMessage}; use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind, Namespace}; @@ -20,8 +19,8 @@ use rustc_hir::def_id::DefId; use rustc_infer::infer::{self, InferOk}; use rustc_middle::query::Providers; use rustc_middle::traits::ObligationCause; -use rustc_middle::ty::subst::{InternalSubsts, SubstsRef}; use rustc_middle::ty::{self, GenericParamDefKind, Ty, TypeVisitableExt}; +use rustc_middle::ty::{GenericArgs, GenericArgsRef}; use rustc_span::symbol::Ident; use rustc_span::Span; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; @@ -37,7 +36,7 @@ pub fn provide(providers: &mut Providers) { pub struct MethodCallee<'tcx> { /// Impl method ID, for inherent methods, or trait method ID, otherwise. pub def_id: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, /// Instantiated method signature, i.e., it has been /// substituted, normalized, and has had late-bound @@ -190,11 +189,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.lint_dot_call_from_2018(self_ty, segment, span, call_expr, self_expr, &pick, args); - for import_id in &pick.import_ids { + for &import_id in &pick.import_ids { debug!("used_trait_import: {:?}", import_id); - Lrc::get_mut(&mut self.typeck_results.borrow_mut().used_trait_imports) - .unwrap() - .insert(*import_id); + self.typeck_results.borrow_mut().used_trait_imports.insert(import_id); } self.tcx.check_stability(pick.item.def_id, Some(call_expr.hir_id), span, None); @@ -205,9 +202,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(span) = result.illegal_sized_bound { let mut needs_mut = false; if let ty::Ref(region, t_type, mutability) = self_ty.kind() { - let trait_type = self - .tcx - .mk_ref(*region, ty::TypeAndMut { ty: *t_type, mutbl: mutability.invert() }); + let trait_type = Ty::new_ref( + self.tcx, + *region, + ty::TypeAndMut { ty: *t_type, mutbl: mutability.invert() }, + ); // We probe again to see if there might be a borrow mutability discrepancy. match self.lookup_probe( segment.ident, @@ -254,6 +253,27 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(result.callee) } + pub fn lookup_method_for_diagnostic( + &self, + self_ty: Ty<'tcx>, + segment: &hir::PathSegment<'_>, + span: Span, + call_expr: &'tcx hir::Expr<'tcx>, + self_expr: &'tcx hir::Expr<'tcx>, + ) -> Result, MethodError<'tcx>> { + let pick = self.lookup_probe_for_diagnostic( + segment.ident, + self_ty, + call_expr, + ProbeScope::TraitsInScope, + None, + )?; + + Ok(self + .confirm_method_for_diagnostic(span, self_expr, call_expr, self_ty, &pick, segment) + .callee) + } + #[instrument(level = "debug", skip(self, call_expr))] pub fn lookup_probe( &self, @@ -301,9 +321,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { trait_def_id: DefId, self_ty: Ty<'tcx>, opt_input_types: Option<&[Ty<'tcx>]>, - ) -> (traits::PredicateObligation<'tcx>, &'tcx ty::List>) { + ) -> (traits::PredicateObligation<'tcx>, &'tcx ty::List>) { // Construct a trait-reference `self_ty : Trait` - let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| { + let args = GenericArgs::for_item(self.tcx, trait_def_id, |param, _| { match param.kind { GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => {} GenericParamDefKind::Type { .. } => { @@ -317,19 +337,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.var_for_def(cause.span, param) }); - let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, substs); + let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, args); // Construct an obligation let poly_trait_ref = ty::Binder::dummy(trait_ref); - ( - traits::Obligation::new( - self.tcx, - cause, - self.param_env, - poly_trait_ref.without_const(), - ), - substs, - ) + (traits::Obligation::new(self.tcx, cause, self.param_env, poly_trait_ref), args) } /// `lookup_method_in_trait` is used for overloaded operators. @@ -346,9 +358,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self_ty: Ty<'tcx>, opt_input_types: Option<&[Ty<'tcx>]>, ) -> Option>> { - let (obligation, substs) = + let (obligation, args) = self.obligation_for_method(cause, trait_def_id, self_ty, opt_input_types); - self.construct_obligation_for_trait(m_name, trait_def_id, obligation, substs) + self.construct_obligation_for_trait(m_name, trait_def_id, obligation, args) } // FIXME(#18741): it seems likely that we can consolidate some of this @@ -359,7 +371,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { m_name: Ident, trait_def_id: DefId, obligation: traits::PredicateObligation<'tcx>, - substs: &'tcx ty::List>, + args: &'tcx ty::List>, ) -> Option>> { debug!(?obligation); @@ -405,7 +417,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // N.B., instantiate late-bound regions before normalizing the // function signature so that normalization does not need to deal // with bound regions. - let fn_sig = tcx.fn_sig(def_id).subst(self.tcx, substs); + let fn_sig = tcx.fn_sig(def_id).instantiate(self.tcx, args); let fn_sig = self.instantiate_binder_with_fresh_vars(obligation.cause.span, infer::FnCall, fn_sig); @@ -424,7 +436,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // // Note that as the method comes from a trait, it should not have // any late-bound regions appearing in its bounds. - let bounds = self.tcx.predicates_of(def_id).instantiate(self.tcx, substs); + let bounds = self.tcx.predicates_of(def_id).instantiate(self.tcx, args); let InferOk { value, obligations: o } = self.at(&obligation.cause, self.param_env).normalize(bounds); @@ -443,7 +455,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )); // Also add an obligation for the method type being well-formed. - let method_ty = tcx.mk_fn_ptr(ty::Binder::dummy(fn_sig)); + let method_ty = Ty::new_fn_ptr(tcx, ty::Binder::dummy(fn_sig)); debug!( "lookup_in_trait_adjusted: matched method method_ty={:?} obligation={:?}", method_ty, obligation @@ -452,10 +464,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tcx, obligation.cause, self.param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(method_ty.into())), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + method_ty.into(), + ))), )); - let callee = MethodCallee { def_id, substs, sig: fn_sig }; + let callee = MethodCallee { def_id, args, sig: fn_sig }; debug!("callee = {:?}", callee); @@ -542,10 +556,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!(?pick); { let mut typeck_results = self.typeck_results.borrow_mut(); - let used_trait_imports = Lrc::get_mut(&mut typeck_results.used_trait_imports).unwrap(); for import_id in pick.import_ids { debug!(used_trait_import=?import_id); - used_trait_imports.insert(import_id); + typeck_results.used_trait_imports.insert(import_id); } } diff --git a/compiler/rustc_hir_typeck/src/method/prelude2021.rs b/compiler/rustc_hir_typeck/src/method/prelude2021.rs index ec4e7f7f88af5..3f1dca5b1deaa 100644 --- a/compiler/rustc_hir_typeck/src/method/prelude2021.rs +++ b/compiler/rustc_hir_typeck/src/method/prelude2021.rs @@ -14,6 +14,7 @@ use rustc_span::symbol::kw::{Empty, Underscore}; use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; use rustc_trait_selection::infer::InferCtxtExt; +use std::fmt::Write; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(super) fn lint_dot_call_from_2018( @@ -32,7 +33,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); // Rust 2021 and later is already using the new prelude - if span.rust_2021() { + if span.at_least_rust_2021() { return; } @@ -97,28 +98,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) = pick.autoref_or_ptr_adjustment { - format!("{}{} as *const _", derefs, self_expr) + format!("{derefs}{self_expr} as *const _") } else { - format!("{}{}{}", autoref, derefs, self_expr) + format!("{autoref}{derefs}{self_expr}") }; lint.span_suggestion( sp, "disambiguate the method call", - format!("({})", self_adjusted), + format!("({self_adjusted})"), Applicability::MachineApplicable, ); } else { let self_adjusted = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) = pick.autoref_or_ptr_adjustment { - format!("{}(...) as *const _", derefs) + format!("{derefs}(...) as *const _") } else { - format!("{}{}...", autoref, derefs) + format!("{autoref}{derefs}...") }; lint.span_help( sp, - format!("disambiguate the method call with `({})`", self_adjusted,), + format!("disambiguate the method call with `({self_adjusted})`",), ); } @@ -143,16 +144,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (self_adjusted, precise) = self.adjust_expr(pick, self_expr, sp); if precise { - let args = args - .iter() - .map(|arg| { - let span = arg.span.find_ancestor_inside(sp).unwrap_or_default(); - format!( - ", {}", - self.sess().source_map().span_to_snippet(span).unwrap() - ) - }) - .collect::(); + let args = args.iter().fold(String::new(), |mut string, arg| { + let span = arg.span.find_ancestor_inside(sp).unwrap_or_default(); + write!( + string, + ", {}", + self.sess().source_map().span_to_snippet(span).unwrap() + ) + .unwrap(); + string + }); lint.span_suggestion( sp, @@ -168,7 +169,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .ok()) { // Keep turbofish. - format!("::{}", args) + format!("::{args}") } else { String::new() }, @@ -203,7 +204,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pick: &Pick<'tcx>, ) { // Rust 2021 and later is already using the new prelude - if span.rust_2021() { + if span.at_least_rust_2021() { return; } @@ -347,7 +348,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Glob import, so just use its name. return None; } else { - return Some(format!("{}", any_id)); + return Some(format!("{any_id}")); } } @@ -396,9 +397,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let adjusted_text = if let Some(probe::AutorefOrPtrAdjustment::ToConstPtr) = pick.autoref_or_ptr_adjustment { - format!("{}{} as *const _", derefs, expr_text) + format!("{derefs}{expr_text} as *const _") } else { - format!("{}{}{}", autoref, derefs, expr_text) + format!("{autoref}{derefs}{expr_text}") }; (adjusted_text, precise) diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index ba21edea30b1c..7164102a30ed8 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -3,16 +3,16 @@ use super::CandidateSource; use super::MethodError; use super::NoMatchData; -use crate::errors::MethodCallOnUnknownType; +use crate::errors::MethodCallOnUnknownRawPointee; use crate::FnCtxt; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::DefKind; -use rustc_hir_analysis::astconv::InferCtxtExt as _; use rustc_hir_analysis::autoderef::{self, Autoderef}; use rustc_infer::infer::canonical::OriginalQueryValues; use rustc_infer::infer::canonical::{Canonical, QueryResponse}; +use rustc_infer::infer::error_reporting::TypeAnnotationNeeded::E0282; use rustc_infer::infer::DefineOpaqueTypes; use rustc_infer::infer::{self, InferOk, TyCtxtInferExt}; use rustc_middle::middle::stability; @@ -22,7 +22,7 @@ use rustc_middle::ty::AssocItem; use rustc_middle::ty::GenericParamDefKind; use rustc_middle::ty::ToPredicate; use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; -use rustc_middle::ty::{InternalSubsts, SubstsRef}; +use rustc_middle::ty::{GenericArgs, GenericArgsRef}; use rustc_session::lint; use rustc_span::def_id::DefId; use rustc_span::def_id::LocalDefId; @@ -100,10 +100,10 @@ impl<'a, 'tcx> Deref for ProbeContext<'a, 'tcx> { #[derive(Debug, Clone)] pub(crate) struct Candidate<'tcx> { // Candidates are (I'm not quite sure, but they are mostly) basically - // some metadata on top of a `ty::AssocItem` (without substs). + // some metadata on top of a `ty::AssocItem` (without args). // // However, method probing wants to be able to evaluate the predicates - // for a function with the substs applied - for example, if a function + // for a function with the args applied - for example, if a function // has `where Self: Sized`, we don't want to consider it unless `Self` // is actually `Sized`, and similarly, return-type suggestions want // to consider the "actual" return type. @@ -140,7 +140,7 @@ pub(crate) struct Candidate<'tcx> { #[derive(Debug, Clone)] pub(crate) enum CandidateKind<'tcx> { InherentImplCandidate( - SubstsRef<'tcx>, + GenericArgsRef<'tcx>, // Normalize obligations Vec>, ), @@ -437,8 +437,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // this case used to be allowed by the compiler, // so we do a future-compat lint here for the 2015 edition // (see https://github.com/rust-lang/rust/issues/46906) - if self.tcx.sess.rust_2018() { - self.tcx.sess.emit_err(MethodCallOnUnknownType { span }); + if self.tcx.sess.at_least_rust_2018() { + self.tcx.sess.emit_err(MethodCallOnUnknownRawPointee { span }); } else { self.tcx.struct_span_lint_hir( lint::builtin::TYVAR_BEHIND_RAW_POINTER, @@ -449,15 +449,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } } else { - // Encountered a real ambiguity, so abort the lookup. If `ty` is not - // an `Err`, report the right "type annotations needed" error pointing - // to it. + // Ended up encountering a type variable when doing autoderef, + // but it may not be a type variable after processing obligations + // in our local `FnCtxt`, so don't call `structurally_resolve_type`. let ty = &bad_ty.ty; let ty = self .probe_instantiate_query_response(span, &orig_values, ty) .unwrap_or_else(|_| span_bug!(span, "instantiating {:?} failed?", ty)); - let ty = self.structurally_resolved_type(span, ty.value); - assert!(matches!(ty.kind(), ty::Error(_))); + let ty = self.resolve_vars_if_possible(ty.value); + let guar = match *ty.kind() { + ty::Infer(ty::TyVar(_)) => self + .err_ctxt() + .emit_inference_failure_err(self.body_id, span, ty.into(), E0282, true) + .emit(), + ty::Error(guar) => guar, + _ => bug!("unexpected bad final type in method autoderef"), + }; + self.demand_eqtype(span, ty, Ty::new_error(self.tcx, guar)); return Err(MethodError::NoMatch(NoMatchData { static_candidates: Vec::new(), unsatisfied_predicates: Vec::new(), @@ -543,7 +551,7 @@ fn method_autoderef_steps<'tcx>( steps.push(CandidateStep { self_ty: infcx.make_query_response_ignoring_pending_obligations( inference_vars, - infcx.tcx.mk_slice(*elem_ty), + Ty::new_slice(infcx.tcx, *elem_ty), ), autoderefs: dereferences, // this could be from an unsafe deref if we had @@ -730,13 +738,13 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { continue; } - let (impl_ty, impl_substs) = self.impl_ty_and_substs(impl_def_id); - let impl_ty = impl_ty.subst(self.tcx, impl_substs); + let (impl_ty, impl_args) = self.impl_ty_and_args(impl_def_id); + let impl_ty = impl_ty.instantiate(self.tcx, impl_args); debug!("impl_ty: {:?}", impl_ty); // Determine the receiver type that the method itself expects. - let (xform_self_ty, xform_ret_ty) = self.xform_self_ty(item, impl_ty, impl_substs); + let (xform_self_ty, xform_ret_ty) = self.xform_self_ty(item, impl_ty, impl_args); debug!("xform_self_ty: {:?}, xform_ret_ty: {:?}", xform_self_ty, xform_ret_ty); // We can't use normalize_associated_types_in as it will pollute the @@ -762,7 +770,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { xform_self_ty, xform_ret_ty, item, - kind: InherentImplCandidate(impl_substs, obligations), + kind: InherentImplCandidate(impl_args, obligations), import_ids: smallvec![], }, true, @@ -805,7 +813,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let new_trait_ref = this.erase_late_bound_regions(new_trait_ref); let (xform_self_ty, xform_ret_ty) = - this.xform_self_ty(item, new_trait_ref.self_ty(), new_trait_ref.substs); + this.xform_self_ty(item, new_trait_ref.self_ty(), new_trait_ref.args); this.push_candidate( Candidate { xform_self_ty, @@ -826,7 +834,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let bounds = self.param_env.caller_bounds().iter().filter_map(|predicate| { let bound_predicate = predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => { + ty::ClauseKind::Trait(trait_predicate) => { match *trait_predicate.trait_ref.self_ty().kind() { ty::Param(p) if p == param_ty => { Some(bound_predicate.rebind(trait_predicate.trait_ref)) @@ -834,20 +842,12 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { _ => None, } } - ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::TypeWellFormedFromEnv(..) => None, + ty::ClauseKind::RegionOutlives(_) + | ty::ClauseKind::TypeOutlives(_) + | ty::ClauseKind::Projection(_) + | ty::ClauseKind::ConstArgHasType(_, _) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => None, } }); @@ -859,7 +859,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { ); let (xform_self_ty, xform_ret_ty) = - this.xform_self_ty(item, trait_ref.self_ty(), trait_ref.substs); + this.xform_self_ty(item, trait_ref.self_ty(), trait_ref.args); this.push_candidate( Candidate { @@ -929,8 +929,8 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { ) -> bool { match method.kind { ty::AssocKind::Fn => self.probe(|_| { - let substs = self.fresh_substs_for_item(self.span, method.def_id); - let fty = self.tcx.fn_sig(method.def_id).subst(self.tcx, substs); + let args = self.fresh_args_for_item(self.span, method.def_id); + let fty = self.tcx.fn_sig(method.def_id).instantiate(self.tcx, args); let fty = self.instantiate_binder_with_fresh_vars(self.span, infer::FnCall, fty); if let Some(self_ty) = self_ty { @@ -954,8 +954,8 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { trait_def_id: DefId, ) { debug!("assemble_extension_candidates_for_trait(trait_def_id={:?})", trait_def_id); - let trait_substs = self.fresh_item_substs(trait_def_id); - let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, trait_substs); + let trait_args = self.fresh_args_for_item(self.span, trait_def_id); + let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, trait_args); if self.tcx.is_trait_alias(trait_def_id) { // For trait aliases, recursively assume all explicitly named traits are relevant @@ -977,7 +977,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { ); let (xform_self_ty, xform_ret_ty) = - self.xform_self_ty(item, new_trait_ref.self_ty(), new_trait_ref.substs); + self.xform_self_ty(item, new_trait_ref.self_ty(), new_trait_ref.args); self.push_candidate( Candidate { xform_self_ty, @@ -1005,7 +1005,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } let (xform_self_ty, xform_ret_ty) = - self.xform_self_ty(item, trait_ref.self_ty(), trait_substs); + self.xform_self_ty(item, trait_ref.self_ty(), trait_args); self.push_candidate( Candidate { xform_self_ty, @@ -1215,7 +1215,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { // In general, during probing we erase regions. let region = tcx.lifetimes.re_erased; - let autoref_ty = tcx.mk_ref(region, ty::TypeAndMut { ty: self_ty, mutbl }); + let autoref_ty = Ty::new_ref(tcx, region, ty::TypeAndMut { ty: self_ty, mutbl }); self.pick_method(autoref_ty, unstable_candidates).map(|r| { r.map(|mut pick| { pick.autoderefs = step.autoderefs; @@ -1245,7 +1245,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { }; let const_self_ty = ty::TypeAndMut { ty, mutbl: hir::Mutability::Not }; - let const_ptr_ty = self.tcx.mk_ptr(const_self_ty); + let const_ptr_ty = Ty::new_ptr(self.tcx, const_self_ty); self.pick_method(const_ptr_ty, unstable_candidates).map(|r| { r.map(|mut pick| { pick.autoderefs = step.autoderefs; @@ -1441,8 +1441,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { trait_ref: ty::TraitRef<'tcx>, ) -> traits::SelectionResult<'tcx, traits::Selection<'tcx>> { let cause = traits::ObligationCause::misc(self.span, self.body_id); - let predicate = ty::Binder::dummy(trait_ref); - let obligation = traits::Obligation::new(self.tcx, cause, self.param_env, predicate); + let obligation = traits::Obligation::new(self.tcx, cause, self.param_env, trait_ref); traits::SelectionContext::new(self).select(&obligation) } @@ -1511,7 +1510,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { // match as well (or at least may match, sometimes we // don't have enough information to fully evaluate). match probe.kind { - InherentImplCandidate(ref substs, ref ref_obligations) => { + InherentImplCandidate(ref args, ref ref_obligations) => { // `xform_ret_ty` hasn't been normalized yet, only `xform_self_ty`, // see the reasons mentioned in the comments in `assemble_inherent_impl_probe` // for why this is necessary @@ -1525,7 +1524,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { // Check whether the impl imposes obligations we have to worry about. let impl_def_id = probe.item.container_id(self.tcx); let impl_bounds = self.tcx.predicates_of(impl_def_id); - let impl_bounds = impl_bounds.instantiate(self.tcx, substs); + let impl_bounds = impl_bounds.instantiate(self.tcx, args); let InferOk { value: impl_bounds, obligations: norm_obligations } = self.fcx.at(&cause, self.param_env).normalize(impl_bounds); @@ -1593,15 +1592,14 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { if let Some(method_name) = self.method_name { // Some trait methods are excluded for arrays before 2021. // (`array.into_iter()` wants a slice iterator for compatibility.) - if self_ty.is_array() && !method_name.span.rust_2021() { + if self_ty.is_array() && !method_name.span.at_least_rust_2021() { let trait_def = self.tcx.trait_def(trait_ref.def_id); if trait_def.skip_array_during_method_dispatch { return ProbeResult::NoMatch; } } } - let predicate = - ty::Binder::dummy(trait_ref).without_const().to_predicate(self.tcx); + let predicate = ty::Binder::dummy(trait_ref).to_predicate(self.tcx); parent_pred = Some(predicate); let obligation = traits::Obligation::new(self.tcx, cause.clone(), self.param_env, predicate); @@ -1844,10 +1842,10 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { &self, item: ty::AssocItem, impl_ty: Ty<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> (Ty<'tcx>, Option>) { if item.kind == ty::AssocKind::Fn && self.mode == Mode::MethodCall { - let sig = self.xform_method_sig(item.def_id, substs); + let sig = self.xform_method_sig(item.def_id, args); (sig.inputs()[0], Some(sig.output())) } else { (impl_ty, None) @@ -1855,11 +1853,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } #[instrument(level = "debug", skip(self))] - fn xform_method_sig(&self, method: DefId, substs: SubstsRef<'tcx>) -> ty::FnSig<'tcx> { + fn xform_method_sig(&self, method: DefId, args: GenericArgsRef<'tcx>) -> ty::FnSig<'tcx> { let fn_sig = self.tcx.fn_sig(method); debug!(?fn_sig); - assert!(!substs.has_escaping_bound_vars()); + assert!(!args.has_escaping_bound_vars()); // It is possible for type parameters or early-bound lifetimes // to appear in the signature of `self`. The substitutions we @@ -1867,15 +1865,15 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { // method yet. So create fresh variables here for those too, // if there are any. let generics = self.tcx.generics_of(method); - assert_eq!(substs.len(), generics.parent_count as usize); + assert_eq!(args.len(), generics.parent_count as usize); let xform_fn_sig = if generics.params.is_empty() { - fn_sig.subst(self.tcx, substs) + fn_sig.instantiate(self.tcx, args) } else { - let substs = InternalSubsts::for_item(self.tcx, method, |param, _| { + let args = GenericArgs::for_item(self.tcx, method, |param, _| { let i = param.index as usize; - if i < substs.len() { - substs[i] + if i < args.len() { + args[i] } else { match param.kind { GenericParamDefKind::Lifetime => { @@ -1888,18 +1886,18 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } } }); - fn_sig.subst(self.tcx, substs) + fn_sig.instantiate(self.tcx, args) }; self.erase_late_bound_regions(xform_fn_sig) } /// Gets the type of an impl and generate substitutions with inference vars. - fn impl_ty_and_substs( + fn impl_ty_and_args( &self, impl_def_id: DefId, - ) -> (ty::EarlyBinder>, SubstsRef<'tcx>) { - (self.tcx.type_of(impl_def_id), self.fresh_item_substs(impl_def_id)) + ) -> (ty::EarlyBinder>, GenericArgsRef<'tcx>) { + (self.tcx.type_of(impl_def_id), self.fresh_args_for_item(self.span, impl_def_id)) } /// Replaces late-bound-regions bound by `value` with `'static` using @@ -1939,13 +1937,21 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { /// Determine if the associated item withe the given DefId matches /// the desired name via a doc alias. fn matches_by_doc_alias(&self, def_id: DefId) -> bool { - let Some(name) = self.method_name else { return false; }; - let Some(local_def_id) = def_id.as_local() else { return false; }; + let Some(name) = self.method_name else { + return false; + }; + let Some(local_def_id) = def_id.as_local() else { + return false; + }; let hir_id = self.fcx.tcx.hir().local_def_id_to_hir_id(local_def_id); let attrs = self.fcx.tcx.hir().attrs(hir_id); for attr in attrs { - let sym::doc = attr.name_or_empty() else { continue; }; - let Some(values) = attr.meta_item_list() else { continue; }; + let sym::doc = attr.name_or_empty() else { + continue; + }; + let Some(values) = attr.meta_item_list() else { + continue; + }; for v in values { if v.name_or_empty() != sym::alias { continue; @@ -2033,8 +2039,8 @@ impl<'tcx> Candidate<'tcx> { // means they are safe to put into the // `WhereClausePick`. assert!( - !trait_ref.skip_binder().substs.has_infer() - && !trait_ref.skip_binder().substs.has_placeholders() + !trait_ref.skip_binder().args.has_infer() + && !trait_ref.skip_binder().args.has_placeholders() ); WhereClausePick(*trait_ref) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 8555c20204ad6..72a04a02bf4fb 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -2,12 +2,13 @@ //! found or is otherwise invalid. use crate::errors; -use crate::errors::CandidateTraitNote; -use crate::errors::NoAssociatedItem; +use crate::errors::{CandidateTraitNote, NoAssociatedItem}; use crate::Expectation; use crate::FnCtxt; use rustc_ast::ast::Mutability; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_attr::parse_confusables; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; +use rustc_data_structures::unord::UnordSet; use rustc_errors::StashKey; use rustc_errors::{ pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, @@ -31,6 +32,7 @@ use rustc_middle::ty::fast_reject::{simplify_type, TreatParams}; use rustc_middle::ty::print::{with_crate_prefix, with_forced_trimmed_paths}; use rustc_middle::ty::IsSuggestable; use rustc_middle::ty::{self, GenericArgKind, Ty, TyCtxt, TypeVisitableExt}; +use rustc_span::def_id::DefIdSet; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::Symbol; use rustc_span::{edit_distance, source_map, ExpnKind, FileName, MacroKind, Span}; @@ -48,6 +50,15 @@ use rustc_hir::intravisit::Visitor; use std::cmp::{self, Ordering}; use std::iter; +/// After identifying that `full_expr` is a method call, we use this type to keep the expression's +/// components readily available to us to point at the right place in diagnostics. +#[derive(Debug, Clone, Copy)] +pub struct MethodCallComponents<'tcx> { + pub receiver: &'tcx hir::Expr<'tcx>, + pub args: &'tcx [hir::Expr<'tcx>], + pub full_expr: &'tcx hir::Expr<'tcx>, +} + impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn is_fn_ty(&self, ty: Ty<'tcx>, span: Span) -> bool { let tcx = self.tcx; @@ -92,7 +103,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, self.body_id, self.param_env, - poly_trait_ref.without_const(), + poly_trait_ref, ); self.predicate_may_hold(&obligation) }) @@ -113,7 +124,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { item_name: Ident, source: SelfSource<'tcx>, error: MethodError<'tcx>, - args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, + args: Option>, expected: Expectation<'tcx>, trait_missing_method: bool, ) -> Option> { @@ -151,7 +162,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { E0034, "multiple applicable items in scope" ); - err.span_label(item_name.span, format!("multiple `{}` found", item_name)); + err.span_label(item_name.span, format!("multiple `{item_name}` found")); self.note_candidates_on_method_error( rcvr_ty, @@ -175,13 +186,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { kind, item_name ); - err.span_label(item_name.span, format!("private {}", kind)); + err.span_label(item_name.span, format!("private {kind}")); let sp = self .tcx .hir() .span_if_local(def_id) .unwrap_or_else(|| self.tcx.def_span(def_id)); - err.span_label(sp, format!("private {} defined here", kind)); + err.span_label(sp, format!("private {kind} defined here")); self.suggest_valid_traits(&mut err, out_of_scope_traits); err.emit(); } @@ -211,11 +222,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if let ty::Ref(region, t_type, mutability) = rcvr_ty.kind() { if needs_mut { - let trait_type = self.tcx.mk_ref( + let trait_type = Ty::new_ref( + self.tcx, *region, ty::TypeAndMut { ty: *t_type, mutbl: mutability.invert() }, ); - let msg = format!("you need `{}` instead of `{}`", trait_type, rcvr_ty); + let msg = format!("you need `{trait_type}` instead of `{rcvr_ty}`"); let mut kind = &self_expr.kind; while let hir::ExprKind::AddrOf(_, _, expr) | hir::ExprKind::Unary(hir::UnOp::Deref, expr) = kind @@ -254,18 +266,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn suggest_missing_writer( &self, rcvr_ty: Ty<'tcx>, - args: (&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>]), + args: MethodCallComponents<'tcx>, ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { let (ty_str, _ty_file) = self.tcx.short_ty_string(rcvr_ty); - let mut err = - struct_span_err!(self.tcx.sess, args.0.span, E0599, "cannot write into `{}`", ty_str); + let mut err = struct_span_err!( + self.tcx.sess, + args.receiver.span, + E0599, + "cannot write into `{}`", + ty_str + ); err.span_note( - args.0.span, + args.receiver.span, "must implement `io::Write`, `fmt::Write`, or have a `write_fmt` method", ); - if let ExprKind::Lit(_) = args.0.kind { + if let ExprKind::Lit(_) = args.receiver.kind { err.span_help( - args.0.span.shrink_to_lo(), + args.receiver.span.shrink_to_lo(), "a writer is needed before this format string", ); }; @@ -279,7 +296,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rcvr_ty: Ty<'tcx>, item_name: Ident, source: SelfSource<'tcx>, - args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, + args: Option>, sugg_span: Span, no_match_data: &mut NoMatchData<'tcx>, expected: Expectation<'tcx>, @@ -473,6 +490,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut custom_span_label = false; let static_candidates = &mut no_match_data.static_candidates; + + // `static_candidates` may have same candidates appended by + // inherent and extension, which may result in incorrect + // diagnostic. + static_candidates.dedup(); + if !static_candidates.is_empty() { err.note( "found the following associated functions; to be used as methods, \ @@ -529,14 +552,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { )); } } else if !unsatisfied_predicates.is_empty() { - let mut type_params = FxHashMap::default(); + let mut type_params = FxIndexMap::default(); // Pick out the list of unimplemented traits on the receiver. // This is used for custom error messages with the `#[rustc_on_unimplemented]` attribute. - let mut unimplemented_traits = FxHashMap::default(); + let mut unimplemented_traits = FxIndexMap::default(); let mut unimplemented_traits_only = true; for (predicate, _parent_pred, cause) in unsatisfied_predicates { - if let (ty::PredicateKind::Clause(ty::Clause::Trait(p)), Some(cause)) = + if let (ty::PredicateKind::Clause(ty::ClauseKind::Trait(p)), Some(cause)) = (predicate.kind().skip_binder(), cause.as_ref()) { if p.trait_ref.self_ty() != rcvr_ty { @@ -563,7 +586,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // because of some non-Clone item being iterated over. for (predicate, _parent_pred, _cause) in unsatisfied_predicates { match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(p)) + ty::PredicateKind::Clause(ty::ClauseKind::Trait(p)) if unimplemented_traits.contains_key(&p.trait_ref.def_id) => {} _ => { unimplemented_traits_only = false; @@ -575,7 +598,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut collect_type_param_suggestions = |self_ty: Ty<'tcx>, parent_pred: ty::Predicate<'tcx>, obligation: &str| { // We don't care about regions here, so it's fine to skip the binder here. - if let (ty::Param(_), ty::PredicateKind::Clause(ty::Clause::Trait(p))) = + if let (ty::Param(_), ty::PredicateKind::Clause(ty::ClauseKind::Trait(p))) = (self_ty.kind(), parent_pred.kind().skip_binder()) { let hir = self.tcx.hir(); @@ -599,7 +622,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); type_params .entry(key) - .or_insert_with(FxHashSet::default) + .or_insert_with(UnordSet::default) .insert(obligation.to_owned()); return true; } @@ -628,29 +651,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Point at the closure that couldn't satisfy the bound. ty::Closure(def_id, _) => bound_spans - .push((tcx.def_span(*def_id), format!("doesn't satisfy `{}`", quiet))), + .push((tcx.def_span(*def_id), format!("doesn't satisfy `{quiet}`"))), _ => {} } }; let mut format_pred = |pred: ty::Predicate<'tcx>| { let bound_predicate = pred.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(pred)) => { let pred = bound_predicate.rebind(pred); // `::Item = String`. let projection_ty = pred.skip_binder().projection_ty; - let substs_with_infer_self = tcx.mk_substs_from_iter( - iter::once(tcx.mk_ty_var(ty::TyVid::from_u32(0)).into()) - .chain(projection_ty.substs.iter().skip(1)), + let args_with_infer_self = tcx.mk_args_from_iter( + iter::once(Ty::new_var(tcx, ty::TyVid::from_u32(0)).into()) + .chain(projection_ty.args.iter().skip(1)), ); let quiet_projection_ty = - tcx.mk_alias_ty(projection_ty.def_id, substs_with_infer_self); + tcx.mk_alias_ty(projection_ty.def_id, args_with_infer_self); let term = pred.skip_binder().term; - let obligation = format!("{} = {}", projection_ty, term); + let obligation = format!("{projection_ty} = {term}"); let quiet = with_forced_trimmed_paths!(format!( "{} = {}", quiet_projection_ty, term @@ -659,11 +682,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { bound_span_label(projection_ty.self_ty(), &obligation, &quiet); Some((obligation, projection_ty.self_ty())) } - ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_ref)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(poly_trait_ref)) => { let p = poly_trait_ref.trait_ref; let self_ty = p.self_ty(); let path = p.print_only_trait_path(); - let obligation = format!("{}: {}", self_ty, path); + let obligation = format!("{self_ty}: {path}"); let quiet = with_forced_trimmed_paths!(format!("_: {}", path)); bound_span_label(self_ty, &obligation, &quiet); Some((obligation, self_ty)) @@ -673,8 +696,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // Find all the requirements that come from a local `impl` block. - let mut skip_list: FxHashSet<_> = Default::default(); - let mut spanned_predicates = FxHashMap::default(); + let mut skip_list: UnordSet<_> = Default::default(); + let mut spanned_predicates = FxIndexMap::default(); for (p, parent_p, cause) in unsatisfied_predicates { // Extract the predicate span and parent def id of the cause, // if we have one. @@ -690,7 +713,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // Don't point out the span of `WellFormed` predicates. - if !matches!(p.kind().skip_binder(), ty::PredicateKind::Clause(_)) { + if !matches!( + p.kind().skip_binder(), + ty::PredicateKind::Clause( + ty::ClauseKind::Projection(..) | ty::ClauseKind::Trait(..) + ) + ) { continue; }; @@ -711,7 +739,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let span = self_ty.span.ctxt().outer_expn_data().call_site; let entry = spanned_predicates.entry(span); let entry = entry.or_insert_with(|| { - (FxHashSet::default(), FxHashSet::default(), Vec::new()) + (FxIndexSet::default(), FxIndexSet::default(), Vec::new()) }); entry.0.insert(span); entry.1.insert(( @@ -731,7 +759,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let sized_pred = unsatisfied_predicates.iter().any(|(pred, _, _)| { match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { Some(pred.def_id()) == self.tcx.lang_items().sized_trait() && pred.polarity == ty::ImplPolarity::Positive } @@ -759,7 +787,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { skip_list.insert(p); let entry = spanned_predicates.entry(self_ty.span); let entry = entry.or_insert_with(|| { - (FxHashSet::default(), FxHashSet::default(), Vec::new()) + (FxIndexSet::default(), FxIndexSet::default(), Vec::new()) }); entry.2.push(p); if cause_span != *item_span { @@ -794,7 +822,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { skip_list.insert(p); let entry = spanned_predicates.entry(ident.span); let entry = entry.or_insert_with(|| { - (FxHashSet::default(), FxHashSet::default(), Vec::new()) + (FxIndexSet::default(), FxIndexSet::default(), Vec::new()) }); entry.0.insert(cause_span); entry.1.insert((ident.span, "")); @@ -811,12 +839,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut preds: Vec<_> = predicates .iter() .filter_map(|pred| format_pred(**pred)) - .map(|(p, _)| format!("`{}`", p)) + .map(|(p, _)| format!("`{p}`")) .collect(); preds.sort(); preds.dedup(); let msg = if let [pred] = &preds[..] { - format!("trait bound {} was not satisfied", pred) + format!("trait bound {pred} was not satisfied") } else { format!("the following trait bounds were not satisfied:\n{}", preds.join("\n"),) }; @@ -828,7 +856,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { unsatisfied_bounds = true; } - let mut suggested_bounds = FxHashSet::default(); + let mut suggested_bounds = UnordSet::default(); // The requirements that didn't have an `impl` span to show. let mut bound_list = unsatisfied_predicates .iter() @@ -861,7 +889,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { suggested_bounds.insert(pred); } } - format!("`{}`\nwhich is required by `{}`", p, parent_p) + format!("`{p}`\nwhich is required by `{parent_p}`") } }, }, @@ -877,8 +905,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for ((span, add_where_or_comma), obligations) in type_params.into_iter() { restrict_type_params = true; // #74886: Sort here so that the output is always the same. - let mut obligations = obligations.into_iter().collect::>(); - obligations.sort(); + let obligations = obligations.to_sorted_stable_ord(); err.span_suggestion_verbose( span, format!( @@ -940,6 +967,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { unsatisfied_bounds = true; } + } else if let ty::Adt(def, targs) = rcvr_ty.kind() && let Some(args) = args { + // This is useful for methods on arbitrary self types that might have a simple + // mutability difference, like calling a method on `Pin<&mut Self>` that is on + // `Pin<&Self>`. + if targs.len() == 1 { + let mut item_segment = hir::PathSegment::invalid(); + item_segment.ident = item_name; + for t in [Ty::new_mut_ref, Ty::new_imm_ref, |_, _, t| t] { + let new_args = tcx.mk_args_from_iter( + targs + .iter() + .map(|arg| match arg.as_type() { + Some(ty) => ty::GenericArg::from( + t(tcx, tcx.lifetimes.re_erased, ty.peel_refs()), + ), + _ => arg, + }) + ); + let rcvr_ty = Ty::new_adt(tcx, *def, new_args); + if let Ok(method) = self.lookup_method_for_diagnostic( + rcvr_ty, + &item_segment, + span, + args.full_expr, + args.receiver, + ) { + err.span_note( + tcx.def_span(method.def_id), + format!("{item_kind} is available for `{rcvr_ty}`"), + ); + } + } + } } let label_span_not_found = |err: &mut Diagnostic| { @@ -981,9 +1041,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // different from the received one // So we avoid suggestion method with Box // for instance - self.tcx.at(span).type_of(*def_id).subst_identity() + self.tcx.at(span).type_of(*def_id).instantiate_identity() != rcvr_ty - && self.tcx.at(span).type_of(*def_id).subst_identity() + && self + .tcx + .at(span) + .type_of(*def_id) + .instantiate_identity() != rcvr_ty } (Mode::Path, false, _) => true, @@ -1006,7 +1070,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .map(|impl_item| { format!( "- `{}`", - self.tcx.at(span).type_of(*impl_item).subst_identity() + self.tcx.at(span).type_of(*impl_item).instantiate_identity() ) }) .collect::>() @@ -1017,9 +1081,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "".to_string() }; err.note(format!( - "the {item_kind} was found for\n{}{}", - type_candidates, additional_types + "the {item_kind} was found for\n{type_candidates}{additional_types}" )); + } else { + 'outer: for inherent_impl_did in self.tcx.inherent_impls(adt.did()) { + for inherent_method in + self.tcx.associated_items(inherent_impl_did).in_definition_order() + { + if let Some(attr) = self.tcx.get_attr(inherent_method.def_id, sym::rustc_confusables) + && let Some(candidates) = parse_confusables(attr) + && candidates.contains(&item_name.name) + { + err.span_suggestion_verbose( + item_name.span, + format!( + "you might have meant to use `{}`", + inherent_method.name.as_str() + ), + inherent_method.name.as_str(), + Applicability::MaybeIncorrect, + ); + break 'outer; + } + } + } } } } else { @@ -1073,7 +1158,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, rcvr_ty, item_name, - args.map(|(_, args)| args.len() + 1), + args.map(|MethodCallComponents { args, .. }| args.len() + 1), source, no_match_data.out_of_scope_traits.clone(), &unsatisfied_predicates, @@ -1154,7 +1239,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, rcvr_ty: Ty<'tcx>, item_name: Ident, - args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, + args: Option>, span: Span, err: &mut Diagnostic, sources: &mut Vec, @@ -1185,7 +1270,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None }; - let impl_ty = self.tcx.at(span).type_of(impl_did).subst_identity(); + let impl_ty = self.tcx.at(span).type_of(impl_did).instantiate_identity(); let insertion = match self.tcx.impl_trait_ref(impl_did) { None => String::new(), @@ -1210,8 +1295,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { ( format!( - "the candidate is defined in an impl{} for the type `{}`", - insertion, impl_ty, + "the candidate is defined in an impl{insertion} for the type `{impl_ty}`", ), None, ) @@ -1231,7 +1315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::AssocKind::Fn => self .tcx .fn_sig(item.def_id) - .subst_identity() + .instantiate_identity() .inputs() .skip_binder() .get(0) @@ -1306,7 +1390,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rcvr_ty: Ty<'tcx>, source: SelfSource<'tcx>, item_name: Ident, - args: Option<(&hir::Expr<'tcx>, &[hir::Expr<'tcx>])>, + args: Option>, sugg_span: Span, ) { let mut has_unsuggestable_args = false; @@ -1314,7 +1398,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // When the "method" is resolved through dereferencing, we really want the // original type that has the associated function for accurate suggestions. // (#61411) - let impl_ty = self.tcx.type_of(*impl_did).subst_identity(); + let impl_ty = self.tcx.type_of(*impl_did).instantiate_identity(); let target_ty = self .autoderef(sugg_span, rcvr_ty) .find(|(rcvr_ty, _)| { @@ -1323,10 +1407,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) .map_or(impl_ty, |(ty, _)| ty) .peel_refs(); - if let ty::Adt(def, substs) = target_ty.kind() { + if let ty::Adt(def, args) = target_ty.kind() { // If there are any inferred arguments, (`{integer}`), we should replace // them with underscores to allow the compiler to infer them - let infer_substs = self.tcx.mk_substs_from_iter(substs.into_iter().map(|arg| { + let infer_args = self.tcx.mk_args_from_iter(args.into_iter().map(|arg| { if !arg.is_suggestable(self.tcx, true) { has_unsuggestable_args = true; match arg.unpack() { @@ -1356,7 +1440,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } })); - self.tcx.value_path_str_with_substs(def.did(), infer_substs) + self.tcx.value_path_str_with_args(def.did(), infer_args) } else { self.ty_to_value_string(target_ty) } @@ -1368,7 +1452,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && let Some(assoc) = self.associated_value(*impl_did, item_name) && assoc.kind == ty::AssocKind::Fn { - let sig = self.tcx.fn_sig(assoc.def_id).subst_identity(); + let sig = self.tcx.fn_sig(assoc.def_id).instantiate_identity(); sig.inputs().skip_binder().get(0).and_then(|first| if first.peel_refs() == rcvr_ty.peel_refs() { None } else { @@ -1378,7 +1462,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { None }; let mut applicability = Applicability::MachineApplicable; - let args = if let Some((receiver, args)) = args { + let args = if let Some(MethodCallComponents { receiver, args, .. }) = args { // The first arg is the same kind as the receiver let explicit_args = if first_arg.is_some() { std::iter::once(receiver).chain(args.iter()).collect::>() @@ -1413,11 +1497,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_suggestion( sugg_span, "use associated function syntax instead", - format!("{}::{}{}", ty_str, item_name, args), + format!("{ty_str}::{item_name}{args}"), applicability, ); } else { - err.help(format!("try with `{}::{}`", ty_str, item_name,)); + err.help(format!("try with `{ty_str}::{item_name}`",)); } } @@ -1433,11 +1517,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) -> bool { let tcx = self.tcx; let field_receiver = self.autoderef(span, rcvr_ty).find_map(|(ty, _)| match ty.kind() { - ty::Adt(def, substs) if !def.is_enum() => { + ty::Adt(def, args) if !def.is_enum() => { let variant = &def.non_enum_variant(); tcx.find_field_index(item_name, variant).map(|index| { let field = &variant.fields[index]; - let field_ty = field.ty(tcx, substs); + let field_ty = field.ty(tcx, args); (field, field_ty) }) } @@ -1452,9 +1536,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let expr_span = expr.span.to(item_name.span); err.multipart_suggestion( format!( - "to call the function stored in `{}`, \ + "to call the function stored in `{item_name}`, \ surround the field access with parentheses", - item_name, ), vec![ (expr_span.shrink_to_lo(), '('.to_string()), @@ -1477,7 +1560,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let field_kind = if is_accessible { "field" } else { "private field" }; - err.span_label(item_name.span, format!("{}, not a method", field_kind)); + err.span_label(item_name.span, format!("{field_kind}, not a method")); return true; } false @@ -1534,7 +1617,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } let range_def_id = self.tcx.require_lang_item(lang_item.unwrap(), None); - let range_ty = self.tcx.type_of(range_def_id).subst(self.tcx, &[actual.into()]); + let range_ty = + self.tcx.type_of(range_def_id).instantiate(self.tcx, &[actual.into()]); let pick = self.lookup_probe_for_diagnostic( item_name, @@ -1597,7 +1681,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { || found_assoc(tcx.types.u64) || found_assoc(tcx.types.u128) || found_assoc(tcx.types.f32) - || found_assoc(tcx.types.f32); + || found_assoc(tcx.types.f64); if found_candidate && actual.is_numeric() && !actual.has_concrete_skeleton() @@ -1629,8 +1713,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lit.span, format!( "you must specify a concrete type for this numeric value, \ - like `{}`", - concrete_type + like `{concrete_type}`" ), format!("{snippet}_{concrete_type}"), Applicability::MaybeIncorrect, @@ -1645,8 +1728,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let parent_node = self.tcx.hir().get_parent(hir_id); let msg = format!( - "you must specify a type for this binding, like `{}`", - concrete_type, + "you must specify a type for this binding, like `{concrete_type}`", ); match (filename, parent_node) { @@ -1687,10 +1769,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// we try to suggest `rect.area()` pub(crate) fn suggest_assoc_method_call(&self, segs: &[PathSegment<'_>]) { debug!("suggest_assoc_method_call segs: {:?}", segs); - let [seg1, seg2] = segs else { return; }; + let [seg1, seg2] = segs else { + return; + }; let Some(mut diag) = - self.tcx.sess.diagnostic().steal_diagnostic(seg1.ident.span, StashKey::CallAssocMethod) - else { return }; + self.tcx.sess.diagnostic().steal_diagnostic(seg1.ident.span, StashKey::CallAssocMethod) + else { + return; + }; let map = self.infcx.tcx.hir(); let body_id = self.tcx.hir().body_owned_by(self.body_id); @@ -1754,7 +1840,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { if let SelfSource::MethodCall(expr) = source && let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id() - && let Some((fields, substs)) = + && let Some((fields, args)) = self.get_field_candidates_considering_privacy(span, actual, mod_id) { let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id)); @@ -1789,7 +1875,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) }, candidate_field, - substs, + args, vec![], mod_id, ) @@ -1827,18 +1913,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { item_name: Ident, ) { let tcx = self.tcx; - let SelfSource::MethodCall(expr) = source else { return; }; + let SelfSource::MethodCall(expr) = source else { + return; + }; let call_expr = tcx.hir().expect_expr(tcx.hir().parent_id(expr.hir_id)); - let ty::Adt(kind, substs) = actual.kind() else { return; }; + let ty::Adt(kind, args) = actual.kind() else { + return; + }; match kind.adt_kind() { ty::AdtKind::Enum => { let matching_variants: Vec<_> = kind .variants() .iter() .flat_map(|variant| { - let [field] = &variant.fields.raw[..] else { return None; }; - let field_ty = field.ty(tcx, substs); + let [field] = &variant.fields.raw[..] else { + return None; + }; + let field_ty = field.ty(tcx, args); // Skip `_`, since that'll just lead to ambiguity. if self.resolve_vars_if_possible(field_ty).is_ty_var() { @@ -1873,7 +1965,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match &matching_variants[..] { [(_, field, pick)] => { - let self_ty = field.ty(tcx, substs); + let self_ty = field.ty(tcx, args); err.span_note( tcx.def_span(pick.item.def_id), format!("the method `{item_name}` exists on the type `{self_ty}`"), @@ -1915,15 +2007,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Target wrapper types - types that wrap or pretend to wrap another type, // perhaps this inner type is meant to be called? ty::AdtKind::Struct | ty::AdtKind::Union => { - let [first] = ***substs else { return; }; - let ty::GenericArgKind::Type(ty) = first.unpack() else { return; }; + let [first] = ***args else { + return; + }; + let ty::GenericArgKind::Type(ty) = first.unpack() else { + return; + }; let Ok(pick) = self.lookup_probe_for_diagnostic( item_name, ty, call_expr, ProbeScope::TraitsInScope, None, - ) else { return; }; + ) else { + return; + }; let name = self.ty_to_value_string(actual); let inner_id = kind.did(); @@ -2003,16 +2101,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { let all_local_types_needing_impls = errors.iter().all(|e| match e.obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => match pred.self_ty().kind() { - ty::Adt(def, _) => def.did().is_local(), - _ => false, - }, + ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { + match pred.self_ty().kind() { + ty::Adt(def, _) => def.did().is_local(), + _ => false, + } + } _ => false, }); let mut preds: Vec<_> = errors .iter() .filter_map(|e| match e.obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => Some(pred), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => Some(pred), _ => None, }) .collect(); @@ -2023,7 +2123,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::Adt(def, _) => Some(def.did()), _ => None, }) - .collect::>(); + .collect::>(); let mut spans: MultiSpan = def_ids .iter() .filter_map(|def_id| { @@ -2083,10 +2183,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut derives = Vec::<(String, Span, Symbol)>::new(); let mut traits = Vec::new(); for (pred, _, _) in unsatisfied_predicates { - let Some(ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred))) = + let Some(ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred))) = pred.kind().no_bound_vars() else { - continue + continue; }; let adt = match trait_pred.self_ty().ty_adt_def() { Some(adt) if adt.did().is_local() => adt, @@ -2104,20 +2204,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | sym::Hash | sym::Debug => true, _ => false, - } && match trait_pred.trait_ref.substs.as_slice() { - // Only suggest deriving if lhs == rhs... - [lhs, rhs] => { - if let Some(lhs) = lhs.as_type() - && let Some(rhs) = rhs.as_type() - { - self.can_eq(self.param_env, lhs, rhs) - } else { - false - } - }, - // Unary ops can always be derived - [_] => true, - _ => false, }; if can_derive { let self_name = trait_pred.self_ty().to_string(); @@ -2150,7 +2236,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some((last_self_name, _, ref mut last_trait_names)) = derives_grouped.last_mut() { if last_self_name == &self_name { - last_trait_names.push_str(format!(", {}", trait_name).as_str()); + last_trait_names.push_str(format!(", {trait_name}").as_str()); continue; } } @@ -2182,8 +2268,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for (self_name, self_span, traits) in &derives_grouped { err.span_suggestion_verbose( self_span.shrink_to_lo(), - format!("consider annotating `{}` with `#[derive({})]`", self_name, traits), - format!("#[derive({})]\n", traits), + format!("consider annotating `{self_name}` with `#[derive({traits})]`"), + format!("#[derive({traits})]\n"), Applicability::MaybeIncorrect, ); } @@ -2197,7 +2283,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { item_name: Ident, expected: Expectation<'tcx>, ) { - let SelfSource::QPath(ty) = self_source else { return; }; + let SelfSource::QPath(ty) = self_source else { + return; + }; for (deref_ty, _) in self.autoderef(rustc_span::DUMMY_SP, rcvr_ty).skip(1) { if let Ok(pick) = self.probe_for_name( Mode::Path, @@ -2214,7 +2302,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // just changing the path. && pick.item.fn_has_self_parameter && let Some(self_ty) = - self.tcx.fn_sig(pick.item.def_id).subst_identity().inputs().skip_binder().get(0) + self.tcx.fn_sig(pick.item.def_id).instantiate_identity().inputs().skip_binder().get(0) && self_ty.is_ref() { let suggested_path = match deref_ty.kind() { @@ -2255,7 +2343,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Print out the type for use in value namespace. fn ty_to_value_string(&self, ty: Ty<'tcx>) -> String { match ty.kind() { - ty::Adt(def, substs) => self.tcx.def_path_str_with_substs(def.did(), substs), + ty::Adt(def, args) => self.tcx.def_path_str_with_args(def.did(), args), _ => self.ty_to_string(ty), } } @@ -2409,8 +2497,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // just this list. for (rcvr_ty, post) in &[ (rcvr_ty, ""), - (self.tcx.mk_mut_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&mut "), - (self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, rcvr_ty), "&"), + (Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_erased, rcvr_ty), "&mut "), + (Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, rcvr_ty), "&"), ] { match self.lookup_probe_for_diagnostic( item_name, @@ -2429,7 +2517,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if pick.autoderefs == 0 && !skip { err.span_label( pick.item.ident(self.tcx).span, - format!("the method is available for `{}` here", rcvr_ty), + format!("the method is available for `{rcvr_ty}` here"), ); } break; @@ -2445,10 +2533,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } for (rcvr_ty, pre) in &[ - (self.tcx.mk_lang_item(*rcvr_ty, LangItem::OwnedBox), "Box::new"), - (self.tcx.mk_lang_item(*rcvr_ty, LangItem::Pin), "Pin::new"), - (self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Arc), "Arc::new"), - (self.tcx.mk_diagnostic_item(*rcvr_ty, sym::Rc), "Rc::new"), + (Ty::new_lang_item(self.tcx, *rcvr_ty, LangItem::OwnedBox), "Box::new"), + (Ty::new_lang_item(self.tcx, *rcvr_ty, LangItem::Pin), "Pin::new"), + (Ty::new_diagnostic_item(self.tcx, *rcvr_ty, sym::Arc), "Arc::new"), + (Ty::new_diagnostic_item(self.tcx, *rcvr_ty, sym::Rc), "Rc::new"), ] { if let Some(new_rcvr_t) = *rcvr_ty && let Ok(pick) = self.lookup_probe_for_diagnostic( @@ -2475,13 +2563,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if pick.autoderefs == 0 && !skip { err.span_label( pick.item.ident(self.tcx).span, - format!("the method is available for `{}` here", new_rcvr_t), + format!("the method is available for `{new_rcvr_t}` here"), ); err.multipart_suggestion( "consider wrapping the receiver expression with the \ appropriate type", vec![ - (rcvr.span.shrink_to_lo(), format!("{}({}", pre, post)), + (rcvr.span.shrink_to_lo(), format!("{pre}({post}")), (rcvr.span.shrink_to_hi(), ")".to_string()), ], Applicability::MaybeIncorrect, @@ -2532,10 +2620,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match p.kind().skip_binder() { // Hide traits if they are present in predicates as they can be fixed without // having to implement them. - ty::PredicateKind::Clause(ty::Clause::Trait(t)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(t)) => { t.def_id() == info.def_id } - ty::PredicateKind::Clause(ty::Clause::Projection(p)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(p)) => { p.projection_ty.def_id == info.def_id } _ => false, @@ -2651,7 +2739,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Nothing, } let ast_generics = hir.get_generics(id.owner.def_id).unwrap(); - let trait_def_ids: FxHashSet = ast_generics + let trait_def_ids: DefIdSet = ast_generics .bounds_for_param(def_id) .flat_map(|bp| bp.bounds.iter()) .filter_map(|bound| bound.trait_ref()?.trait_def_id()) @@ -2721,7 +2809,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; err.span_suggestions( sp, - message(format!("add {} supertrait for", article)), + message(format!("add {article} supertrait for")), candidates.iter().map(|t| { format!("{} {}", sep, self.tcx.def_path_str(t.def_id),) }), @@ -2752,7 +2840,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.impl_polarity(*imp_did) == ty::ImplPolarity::Negative }) .any(|imp_did| { - let imp = self.tcx.impl_trait_ref(imp_did).unwrap().subst_identity(); + let imp = + self.tcx.impl_trait_ref(imp_did).unwrap().instantiate_identity(); let imp_simp = simplify_type(self.tcx, imp.self_ty(), TreatParams::ForLookup); imp_simp.is_some_and(|s| s == simp_rcvr_ty) @@ -2789,7 +2878,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { trait_infos => { let mut msg = message(param_type.map_or_else( || "implement".to_string(), // FIXME: it might only need to be imported into scope, not implemented. - |param| format!("restrict type parameter `{}` with", param), + |param| format!("restrict type parameter `{param}` with"), )); for (i, trait_info) in trait_infos.iter().enumerate() { msg.push_str(&format!( @@ -2813,8 +2902,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } trait_infos => { let mut msg = format!( - "the following traits define an item `{}`, but are explicitly unimplemented:", - item_name + "the following traits define an item `{item_name}`, but are explicitly unimplemented:" ); for trait_info in trait_infos { msg.push_str(&format!("\n{}", self.tcx.def_path_str(trait_info.def_id))); @@ -2834,9 +2922,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { found: Ty<'tcx>, expected: Ty<'tcx>, ) -> bool { - let Some((_def_id_or_name, output, _inputs)) = - self.extract_callable_info(found) else { - return false; + let Some((_def_id_or_name, output, _inputs)) = self.extract_callable_info(found) else { + return false; }; if !self.can_coerce(output, expected) { @@ -2907,7 +2994,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // This occurs for UFCS desugaring of `T::method`, where there is no // receiver expression for the method call, and thus no autoderef. if let SelfSource::QPath(_) = source { - return is_local(self.resolve_vars_with_obligations(rcvr_ty)); + return is_local(rcvr_ty); } self.autoderef(span, rcvr_ty).any(|(ty, _)| is_local(ty)) @@ -2955,7 +3042,7 @@ pub fn all_traits(tcx: TyCtxt<'_>) -> Vec { fn print_disambiguation_help<'tcx>( item_name: Ident, - args: Option<(&'tcx hir::Expr<'tcx>, &'tcx [hir::Expr<'tcx>])>, + args: Option>, err: &mut Diagnostic, trait_name: String, rcvr_ty: Ty<'_>, @@ -2967,7 +3054,11 @@ fn print_disambiguation_help<'tcx>( fn_has_self_parameter: bool, ) { let mut applicability = Applicability::MachineApplicable; - let (span, sugg) = if let (ty::AssocKind::Fn, Some((receiver, args))) = (kind, args) { + let (span, sugg) = if let ( + ty::AssocKind::Fn, + Some(MethodCallComponents { receiver, args, .. }), + ) = (kind, args) + { let args = format!( "({}{})", rcvr_ty.ref_mutability().map_or("", |mutbl| mutbl.ref_prefix_str()), @@ -2981,13 +3072,13 @@ fn print_disambiguation_help<'tcx>( .join(", "), ); let trait_name = if !fn_has_self_parameter { - format!("<{} as {}>", rcvr_ty, trait_name) + format!("<{rcvr_ty} as {trait_name}>") } else { trait_name }; - (span, format!("{}::{}{}", trait_name, item_name, args)) + (span, format!("{trait_name}::{item_name}{args}")) } else { - (span.with_hi(item_name.span.lo()), format!("<{} as {}>::", rcvr_ty, trait_name)) + (span.with_hi(item_name.span.lo()), format!("<{rcvr_ty} as {trait_name}>::")) }; err.span_suggestion_verbose( span, @@ -2995,7 +3086,7 @@ fn print_disambiguation_help<'tcx>( "disambiguate the {} for {}", def_kind_descr, if let Some(candidate) = candidate { - format!("candidate #{}", candidate) + format!("candidate #{candidate}") } else { "the candidate".to_string() }, diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index b8bf2b6912039..a283cd1abf5dd 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -4,7 +4,7 @@ use super::method::MethodCallee; use super::{has_expected_num_generic_args, FnCtxt}; use crate::Expectation; use rustc_ast as ast; -use rustc_errors::{self, struct_span_err, Applicability, Diagnostic}; +use rustc_errors::{self, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder}; use rustc_hir as hir; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::traits::ObligationCauseCode; @@ -38,7 +38,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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.mk_unit() + Ty::new_unit(self.tcx) } else { return_ty }; @@ -297,7 +297,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // error types are considered "builtin" Err(_) if lhs_ty.references_error() || rhs_ty.references_error() => { - self.tcx.ty_error_misc() + Ty::new_misc_error(self.tcx) } Err(errors) => { let (_, trait_def_id) = @@ -380,33 +380,93 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - let mut suggest_deref_binop = |lhs_deref_ty: Ty<'tcx>| { - if self - .lookup_op_method( - lhs_deref_ty, - Some((rhs_expr, rhs_ty)), - Op::Binary(op, is_assign), - 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 => "", - }, - lhs_deref_ty, - ); - err.span_suggestion_verbose( - lhs_expr.span.shrink_to_lo(), - msg, - "*", - rustc_errors::Applicability::MachineApplicable, - ); - } - }; + let suggest_deref_binop = + |err: &mut DiagnosticBuilder<'_, _>, lhs_deref_ty: Ty<'tcx>| { + if self + .lookup_op_method( + lhs_deref_ty, + Some((rhs_expr, rhs_ty)), + Op::Binary(op, is_assign), + 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 => "", + }, + lhs_deref_ty, + ); + err.span_suggestion_verbose( + lhs_expr.span.shrink_to_lo(), + msg, + "*", + rustc_errors::Applicability::MachineApplicable, + ); + } + }; + + let suggest_different_borrow = + |err: &mut DiagnosticBuilder<'_, _>, + lhs_adjusted_ty, + lhs_new_mutbl: Option, + rhs_adjusted_ty, + rhs_new_mutbl: Option| { + if self + .lookup_op_method( + lhs_adjusted_ty, + Some((rhs_expr, rhs_adjusted_ty)), + Op::Binary(op, is_assign), + expected, + ) + .is_ok() + { + let op_str = op.node.as_str(); + err.note(format!("an implementation for `{lhs_adjusted_ty} {op_str} {rhs_adjusted_ty}` exists")); + + if let Some(lhs_new_mutbl) = lhs_new_mutbl + && let Some(rhs_new_mutbl) = rhs_new_mutbl + && lhs_new_mutbl.is_not() + && rhs_new_mutbl.is_not() { + err.multipart_suggestion_verbose( + "consider reborrowing both sides", + vec![ + (lhs_expr.span.shrink_to_lo(), "&*".to_string()), + (rhs_expr.span.shrink_to_lo(), "&*".to_string()) + ], + rustc_errors::Applicability::MachineApplicable, + ); + } else { + let mut suggest_new_borrow = |new_mutbl: ast::Mutability, sp: Span| { + // Can reborrow (&mut -> &) + if new_mutbl.is_not() { + err.span_suggestion_verbose( + sp.shrink_to_lo(), + "consider reborrowing this side", + "&*", + rustc_errors::Applicability::MachineApplicable, + ); + // Works on &mut but have & + } else { + err.span_help( + sp, + "consider making this expression a mutable borrow", + ); + } + }; + + if let Some(lhs_new_mutbl) = lhs_new_mutbl { + suggest_new_borrow(lhs_new_mutbl, lhs_expr.span); + } + if let Some(rhs_new_mutbl) = rhs_new_mutbl { + suggest_new_borrow(rhs_new_mutbl, rhs_expr.span); + } + } + } + }; let is_compatible_after_call = |lhs_ty, rhs_ty| { self.lookup_op_method( @@ -429,15 +489,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if is_assign == IsAssign::Yes && let Some(lhs_deref_ty) = self.deref_once_mutably_for_diagnostic(lhs_ty) { - suggest_deref_binop(lhs_deref_ty); + suggest_deref_binop(&mut err, lhs_deref_ty); } else if is_assign == IsAssign::No - && let Ref(_, lhs_deref_ty, _) = lhs_ty.kind() + && let Ref(region, lhs_deref_ty, mutbl) = lhs_ty.kind() { if self.type_is_copy_modulo_regions( self.param_env, *lhs_deref_ty, ) { - suggest_deref_binop(*lhs_deref_ty); + suggest_deref_binop(&mut err, *lhs_deref_ty); + } else { + let lhs_inv_mutbl = mutbl.invert(); + let lhs_inv_mutbl_ty = Ty::new_ref( + self.tcx, + *region, + ty::TypeAndMut { + ty: *lhs_deref_ty, + mutbl: lhs_inv_mutbl, + }, + ); + + suggest_different_borrow( + &mut err, + lhs_inv_mutbl_ty, + Some(lhs_inv_mutbl), + rhs_ty, + None, + ); + + if let Ref(region, rhs_deref_ty, mutbl) = rhs_ty.kind() { + let rhs_inv_mutbl = mutbl.invert(); + let rhs_inv_mutbl_ty = Ty::new_ref( + self.tcx, + *region, + ty::TypeAndMut { + ty: *rhs_deref_ty, + mutbl: rhs_inv_mutbl, + }, + ); + + suggest_different_borrow( + &mut err, + lhs_ty, + None, + rhs_inv_mutbl_ty, + Some(rhs_inv_mutbl), + ); + suggest_different_borrow( + &mut err, + lhs_inv_mutbl_ty, + Some(lhs_inv_mutbl), + rhs_inv_mutbl_ty, + Some(rhs_inv_mutbl), + ); + } } } else if self.suggest_fn_call(&mut err, lhs_expr, lhs_ty, |lhs_ty| { is_compatible_after_call(lhs_ty, rhs_ty) @@ -521,8 +626,54 @@ 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_unsafe_ptr() && rhs_ty.is_integral() => { + err.multipart_suggestion( + "consider using `wrapping_add` or `add` for pointer + {integer}", + vec![ + ( + lhs_expr.span.between(rhs_expr.span), + ".wrapping_add(".to_owned(), + ), + (rhs_expr.span.shrink_to_hi(), ")".to_owned()), + ], + Applicability::MaybeIncorrect, + ); + } + hir::BinOpKind::Sub => { + if lhs_ty.is_unsafe_ptr() && rhs_ty.is_integral() { + err.multipart_suggestion( + "consider using `wrapping_sub` or `sub` for pointer - {integer}", + vec![ + (lhs_expr.span.between(rhs_expr.span), ".wrapping_sub(".to_owned()), + (rhs_expr.span.shrink_to_hi(), ")".to_owned()), + ], + Applicability::MaybeIncorrect + ); + } + + if lhs_ty.is_unsafe_ptr() && rhs_ty.is_unsafe_ptr() { + err.multipart_suggestion( + "consider using `offset_from` for pointer - pointer if the pointers point to the same allocation", + vec![ + (lhs_expr.span.shrink_to_lo(), "unsafe { ".to_owned()), + (lhs_expr.span.between(rhs_expr.span), ".offset_from(".to_owned()), + (rhs_expr.span.shrink_to_hi(), ") }".to_owned()), + ], + Applicability::MaybeIncorrect + ); + } + } + _ => {} + } + } + let reported = err.emit(); - self.tcx.ty_error(reported) + Ty::new_error(self.tcx, reported) } }; @@ -706,7 +857,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } err.emit() }); - self.tcx.ty_error(guar) + Ty::new_error(self.tcx, guar) } } } diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 5af955d313482..8fc236f46b226 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -1,3 +1,4 @@ +use crate::gather_locals::DeclOrigin; use crate::{errors, FnCtxt, RawTy}; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; @@ -77,6 +78,13 @@ struct TopInfo<'tcx> { span: Option, } +#[derive(Copy, Clone)] +struct PatInfo<'tcx, 'a> { + binding_mode: BindingMode, + top_info: TopInfo<'tcx>, + decl_origin: Option>, +} + impl<'tcx> FnCtxt<'_, 'tcx> { fn pattern_cause(&self, ti: TopInfo<'tcx>, cause_span: Span) -> ObligationCause<'tcx> { let code = @@ -135,15 +143,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// Otherwise, `Some(span)` represents the span of a type expression /// which originated the `expected` type. - pub fn check_pat_top( + pub(crate) fn check_pat_top( &self, pat: &'tcx Pat<'tcx>, expected: Ty<'tcx>, span: Option, origin_expr: Option<&'tcx hir::Expr<'tcx>>, + decl_origin: Option>, ) { let info = TopInfo { expected, origin_expr, span }; - self.check_pat(pat, expected, INITIAL_BM, info); + let pat_info = PatInfo { binding_mode: INITIAL_BM, top_info: info, decl_origin }; + self.check_pat(pat, expected, pat_info); } /// Type check the given `pat` against the `expected` type @@ -151,14 +161,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// /// Outside of this module, `check_pat_top` should always be used. /// Conversely, inside this module, `check_pat_top` should never be used. - #[instrument(level = "debug", skip(self, ti))] - fn check_pat( - &self, - pat: &'tcx Pat<'tcx>, - expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, - ) { + #[instrument(level = "debug", skip(self, pat_info))] + fn check_pat(&self, pat: &'tcx Pat<'tcx>, expected: Ty<'tcx>, pat_info: PatInfo<'tcx, '_>) { + let PatInfo { binding_mode: def_bm, top_info: ti, .. } = pat_info; let path_res = match &pat.kind { PatKind::Path(qpath) => { Some(self.resolve_ty_and_res_fully_qualified_call(qpath, pat.hir_id, pat.span)) @@ -167,38 +172,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let adjust_mode = self.calc_adjust_mode(pat, path_res.map(|(res, ..)| res)); let (expected, def_bm) = self.calc_default_binding_mode(pat, expected, def_bm, adjust_mode); + let pat_info = + PatInfo { binding_mode: def_bm, top_info: ti, decl_origin: pat_info.decl_origin }; let ty = match pat.kind { PatKind::Wild => expected, PatKind::Lit(lt) => self.check_pat_lit(pat.span, lt, expected, ti), PatKind::Range(lhs, rhs, _) => self.check_pat_range(pat.span, lhs, rhs, expected, ti), PatKind::Binding(ba, var_id, _, sub) => { - self.check_pat_ident(pat, ba, var_id, sub, expected, def_bm, ti) + self.check_pat_ident(pat, ba, var_id, sub, expected, pat_info) } PatKind::TupleStruct(ref qpath, subpats, ddpos) => { - self.check_pat_tuple_struct(pat, qpath, subpats, ddpos, expected, def_bm, ti) + self.check_pat_tuple_struct(pat, qpath, subpats, ddpos, expected, pat_info) } PatKind::Path(ref qpath) => { self.check_pat_path(pat, qpath, path_res.unwrap(), expected, ti) } PatKind::Struct(ref qpath, fields, has_rest_pat) => { - self.check_pat_struct(pat, qpath, fields, has_rest_pat, expected, def_bm, ti) + self.check_pat_struct(pat, qpath, fields, has_rest_pat, expected, pat_info) } PatKind::Or(pats) => { for pat in pats { - self.check_pat(pat, expected, def_bm, ti); + self.check_pat(pat, expected, pat_info); } expected } PatKind::Tuple(elements, ddpos) => { - self.check_pat_tuple(pat.span, elements, ddpos, expected, def_bm, ti) - } - PatKind::Box(inner) => self.check_pat_box(pat.span, inner, expected, def_bm, ti), - PatKind::Ref(inner, mutbl) => { - self.check_pat_ref(pat, inner, mutbl, expected, def_bm, ti) + self.check_pat_tuple(pat.span, elements, ddpos, expected, pat_info) } + PatKind::Box(inner) => self.check_pat_box(pat.span, inner, expected, pat_info), + PatKind::Ref(inner, mutbl) => self.check_pat_ref(pat, inner, mutbl, expected, pat_info), PatKind::Slice(before, slice, after) => { - self.check_pat_slice(pat.span, before, slice, after, expected, def_bm, ti) + self.check_pat_slice(pat.span, before, slice, after, expected, pat_info) } }; @@ -335,8 +340,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, mut def_bm: BindingMode, ) -> (Ty<'tcx>, BindingMode) { - let mut expected = self.resolve_vars_with_obligations(expected); - + let mut expected = self.try_structurally_resolve_type(pat.span, expected); // Peel off as many `&` or `&mut` from the scrutinee type as possible. For example, // for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches // the `Some(5)` which is not of type Ref. @@ -353,7 +357,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Preserve the reference type. We'll need it later during THIR lowering. pat_adjustments.push(expected); - expected = inner_ty; + expected = self.try_structurally_resolve_type(pat.span, inner_ty); def_bm = ty::BindByReference(match def_bm { // If default binding mode is by value, make it `ref` or `ref mut` // (depending on whether we observe `&` or `&mut`). @@ -393,9 +397,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // They can denote both statically and dynamically-sized byte arrays. let mut pat_ty = ty; if let hir::ExprKind::Lit(Spanned { node: ast::LitKind::ByteStr(..), .. }) = lt.kind { - let expected = self.structurally_resolved_type(span, expected); - if let ty::Ref(_, inner_ty, _) = expected.kind() - && matches!(inner_ty.kind(), ty::Slice(_)) + let expected = self.structurally_resolve_type(span, expected); + if let ty::Ref(_, inner_ty, _) = *expected.kind() + && self.try_structurally_resolve_type(span, inner_ty).is_slice() { let tcx = self.tcx; trace!(?lt.hir_id.local_id, "polymorphic byte string lit"); @@ -403,7 +407,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .borrow_mut() .treat_byte_string_as_slice .insert(lt.hir_id.local_id); - pat_ty = tcx.mk_imm_ref(tcx.lifetimes.re_static, tcx.mk_slice(tcx.types.u8)); + pat_ty = Ty::new_imm_ref(tcx,tcx.lifetimes.re_static, Ty::new_slice(tcx,tcx.types.u8)); } } @@ -412,7 +416,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let expected = self.resolve_vars_if_possible(expected); pat_ty = match expected.kind() { ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().string() => expected, - ty::Str => tcx.mk_static_str(), + ty::Str => Ty::new_static_str(tcx,), _ => pat_ty, }; } @@ -474,7 +478,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // There exists a side that didn't meet our criteria that the end-point // be of a numeric or char type, as checked in `calc_side` above. let guar = self.emit_err_pat_range(span, lhs, rhs); - return self.tcx.ty_error(guar); + return Ty::new_error(self.tcx, guar); } // Unify each side with `expected`. @@ -494,14 +498,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { demand_eqtype(&mut rhs, lhs); if let (Some((true, ..)), _) | (_, Some((true, ..))) = (lhs, rhs) { - return self.tcx.ty_error_misc(); + return Ty::new_misc_error(self.tcx); } // Find the unified type and check if it's of numeric or char type again. // This check is needed if both sides are inference variables. // We require types to be resolved here so that we emit inference failure // rather than "_ is not a char or numeric". - let ty = self.structurally_resolved_type(span, expected); + let ty = self.structurally_resolve_type(span, expected); if !(ty.is_numeric() || ty.is_char() || ty.references_error()) { if let Some((ref mut fail, _, _)) = lhs { *fail = true; @@ -510,14 +514,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { *fail = true; } let guar = self.emit_err_pat_range(span, lhs, rhs); - return self.tcx.ty_error(guar); + return Ty::new_error(self.tcx, guar); } ty } fn endpoint_has_type(&self, err: &mut Diagnostic, span: Span, ty: Ty<'_>) { if !ty.references_error() { - err.span_label(span, format!("this is of type `{}`", ty)); + err.span_label(span, format!("this is of type `{ty}`")); } } @@ -541,7 +545,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); let msg = |ty| { let ty = self.resolve_vars_if_possible(ty); - format!("this is of type `{}` but it should be `char` or numeric", ty) + format!("this is of type `{ty}` but it should be `char` or numeric") }; let mut one_side_err = |first_span, first_ty, second: Option<(bool, Ty<'tcx>, Span)>| { err.span_label(first_span, msg(first_ty)); @@ -581,9 +585,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { var_id: HirId, sub: Option<&'tcx Pat<'tcx>>, expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { + let PatInfo { binding_mode: def_bm, top_info: ti, .. } = pat_info; + // Determine the binding mode... let bm = match ba { hir::BindingAnnotation::NONE => def_bm, @@ -594,7 +599,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!("check_pat_ident: pat.hir_id={:?} bm={:?}", pat.hir_id, bm); - let local_ty = self.local_ty(pat.span, pat.hir_id).decl_ty; + let local_ty = self.local_ty(pat.span, pat.hir_id); let eq_ty = match bm { ty::BindByReference(mutbl) => { // If the binding is like `ref x | ref mut x`, @@ -621,12 +626,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if let Some(p) = sub { - self.check_pat(p, expected, def_bm, ti); + self.check_pat(p, expected, pat_info); } local_ty } + /// When a variable is bound several times in a `PatKind::Or`, it'll resolve all of the + /// subsequent bindings of the same name to the first usage. Verify that all of these + /// bindings have the same type by comparing them all against the type of that first pat. fn check_binding_alt_eq_ty( &self, ba: hir::BindingAnnotation, @@ -635,10 +643,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty: Ty<'tcx>, ti: TopInfo<'tcx>, ) { - let var_ty = self.local_ty(span, var_id).decl_ty; + let var_ty = self.local_ty(span, var_id); if let Some(mut err) = self.demand_eqtype_pat_diag(span, var_ty, ty, ti) { let hir = self.tcx.hir(); - let var_ty = self.resolve_vars_with_obligations(var_ty); + let var_ty = self.resolve_vars_if_possible(var_ty); let msg = format!("first introduced with type `{var_ty}` here"); err.span_label(hir.span(var_id), msg); let in_match = hir.parent_iter(var_id).any(|(_, n)| { @@ -651,12 +659,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }); let pre = if in_match { "in the same arm, " } else { "" }; - err.note(format!("{}a binding must have the same type in all alternatives", pre)); + err.note(format!("{pre}a binding must have the same type in all alternatives")); self.suggest_adding_missing_ref_or_removing_ref( &mut err, span, var_ty, - self.resolve_vars_with_obligations(ty), + self.resolve_vars_if_possible(ty), ba, ); err.emit(); @@ -753,7 +761,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match binding_parent { // Check that there is explicit type (ie this is not a closure param with inferred type) // so we don't suggest moving something to the type that does not exist - hir::Node::Param(hir::Param { ty_span, .. }) if binding.span != *ty_span => { + hir::Node::Param(hir::Param { ty_span, pat, .. }) if pat.span != *ty_span => { err.multipart_suggestion_verbose( format!("to take parameter `{binding}` by reference, move `&{mutability}` to the type"), vec![ @@ -841,30 +849,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fields: &'tcx [hir::PatField<'tcx>], has_rest_pat: bool, expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { // Resolve the path and check the definition for errors. let (variant, pat_ty) = match self.check_struct_path(qpath, pat.hir_id) { Ok(data) => data, Err(guar) => { - let err = self.tcx.ty_error(guar); + let err = Ty::new_error(self.tcx, guar); for field in fields { - let ti = ti; - self.check_pat(field.pat, err, def_bm, ti); + self.check_pat(field.pat, err, pat_info); } return err; } }; // Type-check the path. - self.demand_eqtype_pat(pat.span, expected, pat_ty, ti); + self.demand_eqtype_pat(pat.span, expected, pat_ty, pat_info.top_info); // Type-check subpatterns. - if self.check_struct_pat_fields(pat_ty, &pat, variant, fields, has_rest_pat, def_bm, ti) { + if self.check_struct_pat_fields(pat_ty, &pat, variant, fields, has_rest_pat, pat_info) { pat_ty } else { - self.tcx.ty_error_misc() + Ty::new_misc_error(self.tcx) } } @@ -884,12 +890,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Res::Err => { let e = tcx.sess.delay_span_bug(qpath.span(), "`Res::Err` but no error emitted"); self.set_tainted_by_errors(e); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } Res::Def(DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::Variant, _) => { let expected = "unit struct, unit variant or constant"; let e = report_unexpected_variant_res(tcx, res, qpath, pat.span, "E0533", expected); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } Res::SelfCtor(..) | Res::Def( @@ -922,7 +928,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match opt_def_id { Some(def_id) => match self.tcx.hir().get_if_local(def_id) { Some(hir::Node::Item(hir::Item { - kind: hir::ItemKind::Const(_, body_id), .. + kind: hir::ItemKind::Const(_, _, body_id), + .. })) => match self.tcx.hir().get(body_id.hir_id) { hir::Node::Expr(expr) => { if hir::is_range_literal(expr) { @@ -1026,13 +1033,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { subpats: &'tcx [Pat<'tcx>], ddpos: hir::DotDotPos, expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { + let PatInfo { binding_mode: def_bm, top_info: ti, decl_origin } = pat_info; let tcx = self.tcx; let on_error = |e| { for pat in subpats { - self.check_pat(pat, tcx.ty_error(e), def_bm, ti); + self.check_pat( + pat, + Ty::new_error(tcx, e), + PatInfo { binding_mode: def_bm, top_info: ti, decl_origin }, + ); } }; let report_unexpected_res = |res: Res| { @@ -1049,7 +1060,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let e = tcx.sess.delay_span_bug(pat.span, "`Res::Err` but no error emitted"); self.set_tainted_by_errors(e); on_error(e); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } // Type-check the path. @@ -1057,7 +1068,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.instantiate_value_path(segments, opt_ty, res, pat.span, pat.hir_id); if !pat_ty.is_fn() { let e = report_unexpected_res(res); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } let variant = match res { @@ -1065,11 +1076,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let e = tcx.sess.delay_span_bug(pat.span, "`Res::Err` but no error emitted"); self.set_tainted_by_errors(e); on_error(e); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } Res::Def(DefKind::AssocConst | DefKind::AssocFn, _) => { let e = report_unexpected_res(res); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) => tcx.expect_variant_res(res), _ => bug!("unexpected pattern resolution: {:?}", res), @@ -1092,13 +1103,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if subpats.len() == variant.fields.len() || subpats.len() < variant.fields.len() && ddpos.as_opt_usize().is_some() { - let ty::Adt(_, substs) = pat_ty.kind() else { + let ty::Adt(_, args) = pat_ty.kind() else { bug!("unexpected pattern type {:?}", pat_ty); }; for (i, subpat) in subpats.iter().enumerate_and_adjust(variant.fields.len(), ddpos) { let field = &variant.fields[FieldIdx::from_usize(i)]; - let field_ty = self.field_ty(subpat.span, field, substs); - self.check_pat(subpat, field_ty, def_bm, ti); + let field_ty = self.field_ty(subpat.span, field, args); + self.check_pat( + subpat, + field_ty, + PatInfo { binding_mode: def_bm, top_info: ti, decl_origin }, + ); self.tcx.check_stability( variant.fields[FieldIdx::from_usize(i)].did, @@ -1112,7 +1127,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let e = self.e0023(pat.span, res, qpath, subpats, &variant.fields.raw, expected, had_err); on_error(e); - return tcx.ty_error(e); + return Ty::new_error(tcx, e); } pat_ty } @@ -1180,10 +1195,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // with the subpatterns directly in the tuple variant pattern, e.g., `V_i(p_0, .., p_N)`. let missing_parentheses = match (&expected.kind(), fields, had_err) { // #67037: only do this if we could successfully type-check the expected type against - // the tuple struct pattern. Otherwise the substs could get out of range on e.g., + // the tuple struct pattern. Otherwise the args could get out of range on e.g., // `let P() = U;` where `P != U` with `struct P(T);`. - (ty::Adt(_, substs), [field], false) => { - let field_ty = self.field_ty(pat_span, field, substs); + (ty::Adt(_, args), [field], false) => { + let field_ty = self.field_ty(pat_span, field, args); match field_ty.kind() { ty::Tuple(fields) => fields.len() == subpats.len(), _ => false, @@ -1282,14 +1297,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { elements: &'tcx [Pat<'tcx>], ddpos: hir::DotDotPos, expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { let tcx = self.tcx; let mut expected_len = elements.len(); if ddpos.as_opt_usize().is_some() { // Require known type only when `..` is present. - if let ty::Tuple(tys) = self.structurally_resolved_type(span, expected).kind() { + if let ty::Tuple(tys) = self.structurally_resolve_type(span, expected).kind() { expected_len = tys.len(); } } @@ -1303,19 +1317,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) }); let element_tys = tcx.mk_type_list_from_iter(element_tys_iter); - let pat_ty = tcx.mk_tup(element_tys); - if let Some(mut err) = self.demand_eqtype_pat_diag(span, expected, pat_ty, ti) { + let pat_ty = Ty::new_tup(tcx, element_tys); + if let Some(mut err) = + self.demand_eqtype_pat_diag(span, expected, pat_ty, pat_info.top_info) + { let reported = err.emit(); // Walk subpatterns with an expected type of `err` in this case to silence // further errors being emitted when using the bindings. #50333 - let element_tys_iter = (0..max_len).map(|_| tcx.ty_error(reported)); + let element_tys_iter = (0..max_len).map(|_| Ty::new_error(tcx, reported)); for (_, elem) in elements.iter().enumerate_and_adjust(max_len, ddpos) { - self.check_pat(elem, tcx.ty_error(reported), def_bm, ti); + self.check_pat(elem, Ty::new_error(tcx, reported), pat_info); } - tcx.mk_tup_from_iter(element_tys_iter) + Ty::new_tup_from_iter(tcx, element_tys_iter) } else { for (i, elem) in elements.iter().enumerate_and_adjust(max_len, ddpos) { - self.check_pat(elem, element_tys[i], def_bm, ti); + self.check_pat(elem, element_tys[i], pat_info); } pat_ty } @@ -1328,12 +1344,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { variant: &'tcx ty::VariantDef, fields: &'tcx [hir::PatField<'tcx>], has_rest_pat: bool, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> bool { let tcx = self.tcx; - let ty::Adt(adt, substs) = adt_ty.kind() else { + let ty::Adt(adt, args) = adt_ty.kind() else { span_bug!(pat.span, "struct pattern is not an ADT"); }; @@ -1357,7 +1372,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Occupied(occupied) => { no_field_errors = false; let guar = self.error_field_already_bound(span, field.ident, *occupied.get()); - tcx.ty_error(guar) + Ty::new_error(tcx, guar) } Vacant(vacant) => { vacant.insert(span); @@ -1366,17 +1381,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .map(|(i, f)| { self.write_field_index(field.hir_id, *i); self.tcx.check_stability(f.did, Some(pat.hir_id), span, None); - self.field_ty(span, f, substs) + self.field_ty(span, f, args) }) .unwrap_or_else(|| { inexistent_fields.push(field); no_field_errors = false; - tcx.ty_error_misc() + Ty::new_misc_error(tcx) }) } }; - self.check_pat(field.pat, field_ty, def_bm, ti); + self.check_pat(field.pat, field_ty, pat_info); } let mut unmentioned_fields = variant @@ -1394,7 +1409,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &inexistent_fields, &mut unmentioned_fields, variant, - substs, + args, )) } else { None @@ -1564,7 +1579,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { inexistent_fields: &[&hir::PatField<'tcx>], unmentioned_fields: &mut Vec<(&'tcx ty::FieldDef, Ident)>, variant: &ty::VariantDef, - substs: &'tcx ty::List>, + args: &'tcx ty::List>, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { let tcx = self.tcx; let (field_names, t, plural) = if inexistent_fields.len() == 1 { @@ -1634,7 +1649,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.field_ty( unmentioned_fields[0].1.span, unmentioned_fields[0].0, - substs, + args, ), ) => {} _ => { @@ -1708,7 +1723,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err.span_suggestion_verbose( qpath.span().shrink_to_hi().to(pat.span.shrink_to_hi()), "use the tuple variant pattern syntax instead", - format!("({})", sugg), + format!("({sugg})"), appl, ); return Some(err); @@ -1810,7 +1825,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { const LIMIT: usize = 3; match witnesses { [] => bug!(), - [witness] => format!("`{}`", witness), + [witness] => format!("`{witness}`"), [head @ .., tail] if head.len() < LIMIT => { let head: Vec<_> = head.iter().map(<_>::to_string).collect(); format!("`{}` and `{}`", head.join("`, `"), tail) @@ -1832,8 +1847,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "ensure that all fields are mentioned explicitly by adding the suggested fields", ); lint.note(format!( - "the pattern is of type `{}` and the `non_exhaustive_omitted_patterns` attribute was found", - ty, + "the pattern is of type `{ty}` and the `non_exhaustive_omitted_patterns` attribute was found", )); lint @@ -1862,10 +1876,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { let fields = unmentioned_fields .iter() - .map(|(_, name)| format!("`{}`", name)) + .map(|(_, name)| format!("`{name}`")) .collect::>() .join(", "); - format!("fields {}{}", fields, inaccessible) + format!("fields {fields}{inaccessible}") }; let mut err = struct_span_err!( self.tcx.sess, @@ -1874,7 +1888,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "pattern does not mention {}", field_names ); - err.span_label(pat.span, format!("missing {}", field_names)); + err.span_label(pat.span, format!("missing {field_names}")); let len = unmentioned_fields.len(); let (prefix, postfix, sp) = match fields { [] => match &pat.kind { @@ -1907,11 +1921,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .iter() .map(|(_, name)| { let field_name = name.to_string(); - if is_number(&field_name) { - format!("{}: _", field_name) - } else { - field_name - } + if is_number(&field_name) { format!("{field_name}: _") } else { field_name } }) .collect::>() .join(", "), @@ -1928,7 +1938,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { s = pluralize!(len), them = if len == 1 { "it" } else { "them" }, ), - format!("{}..{}", prefix, postfix), + format!("{prefix}..{postfix}"), Applicability::MachineApplicable, ); err @@ -1939,8 +1949,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span: Span, inner: &'tcx Pat<'tcx>, expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { let tcx = self.tcx; let (box_ty, inner_ty) = match self.check_dereferenceable(span, expected, inner) { @@ -1951,16 +1960,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { kind: TypeVariableOriginKind::TypeInference, span: inner.span, }); - let box_ty = tcx.mk_box(inner_ty); - self.demand_eqtype_pat(span, expected, box_ty, ti); + let box_ty = Ty::new_box(tcx, inner_ty); + self.demand_eqtype_pat(span, expected, box_ty, pat_info.top_info); (box_ty, inner_ty) } Err(guar) => { - let err = tcx.ty_error(guar); + let err = Ty::new_error(tcx, guar); (err, err) } }; - self.check_pat(inner, inner_ty, def_bm, ti); + self.check_pat(inner, inner_ty, pat_info); box_ty } @@ -1971,8 +1980,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { inner: &'tcx Pat<'tcx>, mutbl: hir::Mutability, expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { let tcx = self.tcx; let expected = self.shallow_resolve(expected); @@ -1994,7 +2002,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }); let ref_ty = self.new_ref_ty(pat.span, mutbl, inner_ty); debug!("check_pat_ref: demanding {:?} = {:?}", expected, ref_ty); - let err = self.demand_eqtype_pat_diag(pat.span, expected, ref_ty, ti); + let err = self.demand_eqtype_pat_diag( + pat.span, + expected, + ref_ty, + pat_info.top_info, + ); // Look for a case like `fn foo(&foo: u32)` and suggest // `fn foo(foo: &u32)` @@ -2007,11 +2020,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } Err(guar) => { - let err = tcx.ty_error(guar); + let err = Ty::new_error(tcx, guar); (err, err) } }; - self.check_pat(inner, inner_ty, def_bm, ti); + self.check_pat(inner, inner_ty, pat_info); ref_ty } @@ -2019,7 +2032,63 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn new_ref_ty(&self, span: Span, mutbl: hir::Mutability, ty: Ty<'tcx>) -> Ty<'tcx> { let region = self.next_region_var(infer::PatternRegion(span)); let mt = ty::TypeAndMut { ty, mutbl }; - self.tcx.mk_ref(region, mt) + Ty::new_ref(self.tcx, region, mt) + } + + fn try_resolve_slice_ty_to_array_ty( + &self, + before: &'tcx [Pat<'tcx>], + slice: Option<&'tcx Pat<'tcx>>, + span: Span, + ) -> Option> { + if !slice.is_none() { + return None; + } + + let tcx = self.tcx; + let len = before.len(); + let ty_var_origin = + TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span }; + let inner_ty = self.next_ty_var(ty_var_origin); + + Some(Ty::new_array(tcx, inner_ty, len.try_into().unwrap())) + } + + /// Used to determines whether we can infer the expected type in the slice pattern to be of type array. + /// This is only possible if we're in an irrefutable pattern. If we were to allow this in refutable + /// patterns we wouldn't e.g. report ambiguity in the following situation: + /// + /// ```ignore(rust) + /// struct Zeroes; + /// const ARR: [usize; 2] = [0; 2]; + /// const ARR2: [usize; 2] = [2; 2]; + /// + /// impl Into<&'static [usize; 2]> for Zeroes { + /// fn into(self) -> &'static [usize; 2] { + /// &ARR + /// } + /// } + /// + /// impl Into<&'static [usize]> for Zeroes { + /// fn into(self) -> &'static [usize] { + /// &ARR2 + /// } + /// } + /// + /// fn main() { + /// let &[a, b]: &[usize] = Zeroes.into() else { + /// .. + /// }; + /// } + /// ``` + /// + /// If we're in an irrefutable pattern we prefer the array impl candidate given that + /// the slice impl candidate would be be rejected anyway (if no ambiguity existed). + fn pat_is_irrefutable(&self, decl_origin: Option>) -> bool { + match decl_origin { + Some(DeclOrigin::LocalDecl { els: None }) => true, + Some(DeclOrigin::LocalDecl { els: Some(_) } | DeclOrigin::LetExpr) | None => false, + } } /// Type check a slice pattern. @@ -2039,10 +2108,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { slice: Option<&'tcx Pat<'tcx>>, after: &'tcx [Pat<'tcx>], expected: Ty<'tcx>, - def_bm: BindingMode, - ti: TopInfo<'tcx>, + pat_info: PatInfo<'tcx, '_>, ) -> Ty<'tcx> { - let expected = self.structurally_resolved_type(span, expected); + let expected = self.try_structurally_resolve_type(span, expected); + + // If the pattern is irrefutable and `expected` is an infer ty, we try to equate it + // to an array if the given pattern allows it. See issue #76342 + if self.pat_is_irrefutable(pat_info.decl_origin) && expected.is_ty_var() { + if let Some(resolved_arr_ty) = + self.try_resolve_slice_ty_to_array_ty(before, slice, span) + { + debug!(?resolved_arr_ty); + self.demand_eqtype(span, expected, resolved_arr_ty); + } + } + + let expected = self.structurally_resolve_type(span, expected); + debug!(?expected); + let (element_ty, opt_slice_ty, inferred) = match *expected.kind() { // An array, so we might have something like `let [a, b, c] = [0, 1, 2];`. ty::Array(element_ty, len) => { @@ -2057,26 +2140,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::Slice(element_ty) => (element_ty, Some(expected), expected), // The expected type must be an array or slice, but was neither, so error. _ => { - let guar = expected - .error_reported() - .err() - .unwrap_or_else(|| self.error_expected_array_or_slice(span, expected, ti)); - let err = self.tcx.ty_error(guar); + let guar = expected.error_reported().err().unwrap_or_else(|| { + self.error_expected_array_or_slice(span, expected, pat_info.top_info) + }); + let err = Ty::new_error(self.tcx, guar); (err, Some(err), err) } }; // Type check all the patterns before `slice`. for elt in before { - self.check_pat(elt, element_ty, def_bm, ti); + self.check_pat(elt, element_ty, pat_info); } // Type check the `slice`, if present, against its expected type. if let Some(slice) = slice { - self.check_pat(slice, opt_slice_ty.unwrap(), def_bm, ti); + self.check_pat(slice, opt_slice_ty.unwrap(), pat_info); } // Type check the elements after `slice`, if present. for elt in after { - self.check_pat(elt, element_ty, def_bm, ti); + self.check_pat(elt, element_ty, pat_info); } inferred } @@ -2108,7 +2190,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if let Some(pat_len) = len.checked_sub(min_len) { // The variable-length pattern was there, // so it has an array type with the remaining elements left as its size... - return (Some(self.tcx.mk_array(element_ty, pat_len)), arr_ty); + return (Some(Ty::new_array(self.tcx, element_ty, pat_len)), arr_ty); } else { // ...however, in this case, there were no remaining elements. // That is, the slice pattern requires more than the array type offers. @@ -2117,7 +2199,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else if slice.is_none() { // We have a pattern with a fixed length, // which we can use to infer the length of the array. - let updated_arr_ty = self.tcx.mk_array(element_ty, min_len); + let updated_arr_ty = Ty::new_array(self.tcx, element_ty, min_len); self.demand_eqtype(span, updated_arr_ty, arr_ty); return (None, updated_arr_ty); } else { @@ -2128,7 +2210,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // If we get here, we must have emitted an error. - (Some(self.tcx.ty_error(guar)), arr_ty) + (Some(Ty::new_error(self.tcx, guar)), arr_ty) } fn error_scrutinee_inconsistent_length( diff --git a/compiler/rustc_hir_typeck/src/place_op.rs b/compiler/rustc_hir_typeck/src/place_op.rs index e2b1dc007ba6d..406434e09e68e 100644 --- a/compiler/rustc_hir_typeck/src/place_op.rs +++ b/compiler/rustc_hir_typeck/src/place_op.rs @@ -6,7 +6,7 @@ use rustc_hir as hir; use rustc_hir_analysis::autoderef::Autoderef; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::InferOk; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref, PointerCast}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref, PointerCoercion}; use rustc_middle::ty::adjustment::{AllowTwoPhase, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::{sym, Ident}; @@ -90,7 +90,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } let reported = err.emit(); - Some((self.tcx.ty_error(reported), self.tcx.ty_error(reported))) + Some((Ty::new_error(self.tcx, reported), Ty::new_error(self.tcx, reported))) } /// To type-check `base_expr[index_expr]`, we progressively autoderef @@ -107,7 +107,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { index_expr: &hir::Expr<'_>, ) -> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)> { let adjusted_ty = - self.structurally_resolved_type(autoderef.span(), autoderef.final_ty(false)); + self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false)); debug!( "try_index_step(expr={:?}, base_expr={:?}, adjusted_ty={:?}, \ index_ty={:?})", @@ -138,7 +138,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if unsize { // We only unsize arrays here. if let ty::Array(element_ty, _) = adjusted_ty.kind() { - self_ty = self.tcx.mk_slice(*element_ty); + self_ty = Ty::new_slice(self.tcx, *element_ty); } else { continue; } @@ -162,7 +162,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let ty::Ref(region, _, hir::Mutability::Not) = method.sig.inputs()[0].kind() { adjustments.push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(*region, AutoBorrowMutability::Not)), - target: self.tcx.mk_ref( + target: Ty::new_ref( + self.tcx, *region, ty::TypeAndMut { mutbl: hir::Mutability::Not, ty: adjusted_ty }, ), @@ -172,7 +173,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if unsize { adjustments.push(Adjustment { - kind: Adjust::Pointer(PointerCast::Unsize), + kind: Adjust::Pointer(PointerCoercion::Unsize), target: method.sig.inputs()[0], }); } @@ -283,7 +284,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut exprs = vec![expr]; while let hir::ExprKind::Field(ref expr, _) - | hir::ExprKind::Index(ref expr, _) + | hir::ExprKind::Index(ref expr, _, _) | hir::ExprKind::Unary(hir::UnOp::Deref, ref expr) = exprs.last().unwrap().kind { exprs.push(expr); @@ -391,7 +392,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We also could not use `expr_ty_adjusted` of index_expr because reborrowing // during coercions can also cause type of index_expr to differ from `T`, // which can potentially cause regionck failure (#74933). - Some(self.typeck_results.borrow().node_substs(expr.hir_id).type_at(1)) + Some(self.typeck_results.borrow().node_args(expr.hir_id).type_at(1)) } }; let arg_tys = arg_ty.as_slice(); @@ -427,9 +428,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { allow_two_phase_borrow: AllowTwoPhase::No, }; adjustment.kind = Adjust::Borrow(AutoBorrow::Ref(*region, mutbl)); - adjustment.target = self - .tcx - .mk_ref(*region, ty::TypeAndMut { ty: source, mutbl: mutbl.into() }); + adjustment.target = Ty::new_ref( + self.tcx, + *region, + ty::TypeAndMut { ty: source, mutbl: mutbl.into() }, + ); } source = adjustment.target; } @@ -438,7 +441,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let [ .., Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(..)), .. }, - Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), ref mut target }, + Adjustment { kind: Adjust::Pointer(PointerCoercion::Unsize), ref mut target }, ] = adjustments[..] { *target = method.sig.inputs()[0]; diff --git a/compiler/rustc_hir_typeck/src/rvalue_scopes.rs b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs index 22c9e7961070c..04d8410233633 100644 --- a/compiler/rustc_hir_typeck/src/rvalue_scopes.rs +++ b/compiler/rustc_hir_typeck/src/rvalue_scopes.rs @@ -40,7 +40,7 @@ fn record_rvalue_scope_rec( hir::ExprKind::AddrOf(_, _, subexpr) | hir::ExprKind::Unary(hir::UnOp::Deref, subexpr) | hir::ExprKind::Field(subexpr, _) - | hir::ExprKind::Index(subexpr, _) => { + | hir::ExprKind::Index(subexpr, _, _) => { expr = subexpr; } _ => { @@ -74,9 +74,7 @@ pub fn resolve_rvalue_scopes<'a, 'tcx>( debug!("start resolving rvalue scopes, def_id={def_id:?}"); debug!("rvalue_scope: rvalue_candidates={:?}", scope_tree.rvalue_candidates); for (&hir_id, candidate) in &scope_tree.rvalue_candidates { - let Some(Node::Expr(expr)) = hir_map.find(hir_id) else { - bug!("hir node does not exist") - }; + let Some(Node::Expr(expr)) = hir_map.find(hir_id) else { bug!("hir node does not exist") }; record_rvalue_scope(&mut rvalue_scopes, expr, candidate); } rvalue_scopes diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index 9458099f56ff9..1a41786d251c2 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -33,6 +33,7 @@ use super::FnCtxt; use crate::expr_use_visitor as euv; +use rustc_data_structures::unord::{ExtendUnord, UnordSet}; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; @@ -41,14 +42,14 @@ use rustc_infer::infer::UpvarRegion; use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, Projection, ProjectionKind}; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::{ - self, ClosureSizeProfileData, Ty, TyCtxt, TypeckResults, UpvarCapture, UpvarSubsts, + self, ClosureSizeProfileData, Ty, TyCtxt, TypeckResults, UpvarArgs, UpvarCapture, }; use rustc_session::lint; use rustc_span::sym; use rustc_span::{BytePos, Pos, Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_target::abi::FIRST_VARIANT; use std::iter; @@ -108,11 +109,11 @@ impl MigrationWarningReason { fn migration_message(&self) -> String { let base = "changes to closure capture in Rust 2021 will affect"; if !self.auto_traits.is_empty() && self.drop_order { - format!("{} drop order and which traits the closure implements", base) + format!("{base} drop order and which traits the closure implements") } else if self.drop_order { - format!("{} drop order", base) + format!("{base} drop order") } else { - format!("{} which traits the closure implements", base) + format!("{base} which traits the closure implements") } } } @@ -168,9 +169,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { // Extract the type of the closure. let ty = self.node_ty(closure_hir_id); - let (closure_def_id, substs) = match *ty.kind() { - ty::Closure(def_id, substs) => (def_id, UpvarSubsts::Closure(substs)), - ty::Generator(def_id, substs, _) => (def_id, UpvarSubsts::Generator(substs)), + let (closure_def_id, args) = match *ty.kind() { + ty::Closure(def_id, args) => (def_id, UpvarArgs::Closure(args)), + ty::Generator(def_id, args, _) => (def_id, UpvarArgs::Generator(args)), ty::Error(_) => { // #51714: skip analysis when we have already encountered type errors return; @@ -186,8 +187,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let closure_def_id = closure_def_id.expect_local(); - let infer_kind = if let UpvarSubsts::Closure(closure_substs) = substs { - self.closure_kind(closure_substs).is_none().then_some(closure_substs) + let infer_kind = if let UpvarArgs::Closure(closure_args) = args { + self.closure_kind(closure_args).is_none().then_some(closure_args) } else { None }; @@ -256,19 +257,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let before_feature_tys = self.final_upvar_tys(closure_def_id); - if let Some(closure_substs) = infer_kind { + if let Some(closure_args) = infer_kind { // Unify the (as yet unbound) type variable in the closure - // substs with the kind we inferred. - let closure_kind_ty = closure_substs.as_closure().kind_ty(); + // args with the kind we inferred. + let closure_kind_ty = closure_args.as_closure().kind_ty(); self.demand_eqtype(span, closure_kind.to_ty(self.tcx), closure_kind_ty); // If we have an origin, store it. - if let Some(origin) = origin { - let origin = if enable_precise_capture(span) { - (origin.0, origin.1) - } else { - (origin.0, Place { projections: vec![], ..origin.1 }) - }; + if let Some(mut origin) = origin { + if !enable_precise_capture(span) { + // Without precise captures, we just capture the base and ignore + // the projections. + origin.1.projections.clear() + } self.typeck_results .borrow_mut() @@ -293,15 +294,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Equate the type variables for the upvars with the actual types. let final_upvar_tys = self.final_upvar_tys(closure_def_id); - debug!( - "analyze_closure: id={:?} substs={:?} final_upvar_tys={:?}", - closure_hir_id, substs, final_upvar_tys - ); + debug!(?closure_hir_id, ?args, ?final_upvar_tys); // Build a tuple (U0..Un) of the final upvar types U0..Un // and unify the upvar tuple type in the closure with it: - let final_tupled_upvars_type = self.tcx.mk_tup(&final_upvar_tys); - self.demand_suptype(span, substs.tupled_upvars_ty(), final_tupled_upvars_type); + let final_tupled_upvars_type = Ty::new_tup(self.tcx, &final_upvar_tys); + self.demand_suptype(span, args.tupled_upvars_ty(), final_tupled_upvars_type); let fake_reads = delegate .fake_reads @@ -314,8 +312,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.typeck_results.borrow_mut().closure_size_eval.insert( closure_def_id, ClosureSizeProfileData { - before_feature_tys: self.tcx.mk_tup(&before_feature_tys), - after_feature_tys: self.tcx.mk_tup(&after_feature_tys), + before_feature_tys: Ty::new_tup(self.tcx, &before_feature_tys), + after_feature_tys: Ty::new_tup(self.tcx, &after_feature_tys), }, ); } @@ -337,10 +335,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let upvar_ty = captured_place.place.ty(); let capture = captured_place.info.capture_kind; - debug!( - "final_upvar_tys: place={:?} upvar_ty={:?} capture={:?}, mutability={:?}", - captured_place.place, upvar_ty, capture, captured_place.mutability, - ); + debug!(?captured_place.place, ?upvar_ty, ?capture, ?captured_place.mutability); apply_capture_kind_on_capture_ty(self.tcx, upvar_ty, capture, captured_place.region) }) @@ -644,7 +639,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { for capture in captures { match capture.info.capture_kind { ty::UpvarCapture::ByRef(_) => { - let PlaceBase::Upvar(upvar_id) = capture.place.base else { bug!("expected upvar") }; + let PlaceBase::Upvar(upvar_id) = capture.place.base else { + bug!("expected upvar") + }; let origin = UpvarRegion(upvar_id, closure_span); let upvar_region = self.next_region_var(origin); capture.region = Some(upvar_region); @@ -676,6 +673,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { match (p1.kind, p2.kind) { // Paths are the same, continue to next loop. (ProjectionKind::Deref, ProjectionKind::Deref) => {} + (ProjectionKind::OpaqueCast, ProjectionKind::OpaqueCast) => {} (ProjectionKind::Field(i1, _), ProjectionKind::Field(i2, _)) if i1 == i2 => {} @@ -698,10 +696,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { l @ (ProjectionKind::Index | ProjectionKind::Subslice | ProjectionKind::Deref + | ProjectionKind::OpaqueCast | ProjectionKind::Field(..)), r @ (ProjectionKind::Index | ProjectionKind::Subslice | ProjectionKind::Deref + | ProjectionKind::OpaqueCast | ProjectionKind::Field(..)), ) => bug!( "ProjectionKinds Index or Subslice were unexpected: ({:?}, {:?})", @@ -821,8 +821,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { lint.note("for more information, see "); let diagnostic_msg = format!( - "add a dummy let to cause {} to be fully captured", - migrated_variables_concat + "add a dummy let to cause {migrated_variables_concat} to be fully captured" ); let closure_span = self.tcx.hir().span_with_body(closure_hir_id); @@ -908,19 +907,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Combines all the reasons for 2229 migrations fn compute_2229_migrations_reasons( &self, - auto_trait_reasons: FxHashSet<&'static str>, + auto_trait_reasons: UnordSet<&'static str>, drop_order: bool, ) -> MigrationWarningReason { - let mut reasons = MigrationWarningReason::default(); - - reasons.auto_traits.extend(auto_trait_reasons); - reasons.drop_order = drop_order; - - // `auto_trait_reasons` are in hashset order, so sort them to put the - // diagnostics we emit later in a cross-platform-consistent order. - reasons.auto_traits.sort_unstable(); - - reasons + MigrationWarningReason { + auto_traits: auto_trait_reasons.to_sorted_stable_ord(), + drop_order, + } } /// Figures out the list of root variables (and their types) that aren't completely @@ -934,8 +927,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>, var_hir_id: hir::HirId, closure_clause: hir::CaptureBy, - ) -> Option>> { - let auto_traits_def_id = vec![ + ) -> Option>> { + let auto_traits_def_id = [ self.tcx.lang_items().clone_trait(), self.tcx.lang_items().sync_trait(), self.tcx.get_diagnostic_item(sym::Send), @@ -979,7 +972,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { })); } - let mut problematic_captures = FxHashMap::default(); + let mut problematic_captures = FxIndexMap::default(); // Check whether captured fields also implement the trait for capture in root_var_min_capture_list.iter() { let ty = apply_capture_kind_on_capture_ty( @@ -999,7 +992,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { })); } - let mut capture_problems = FxHashSet::default(); + let mut capture_problems = UnordSet::default(); // Checks if for any of the auto traits, one or more trait is implemented // by the root variable but not by the capture @@ -1045,7 +1038,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>, closure_clause: hir::CaptureBy, var_hir_id: hir::HirId, - ) -> Option> { + ) -> Option> { let ty = self.resolve_vars_if_possible(self.node_ty(var_hir_id)); if !ty.has_significant_drop(self.tcx, self.tcx.param_env(closure_def_id)) { @@ -1064,14 +1057,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // ``` debug!("no path starting from it is used"); - match closure_clause { // Only migrate if closure is a move closure hir::CaptureBy::Value => { - let mut diagnostics_info = FxHashSet::default(); - let upvars = self.tcx.upvars_mentioned(closure_def_id).expect("must be an upvar"); + let mut diagnostics_info = FxIndexSet::default(); + let upvars = + self.tcx.upvars_mentioned(closure_def_id).expect("must be an upvar"); let upvar = upvars[&var_hir_id]; - diagnostics_info.insert(UpvarMigrationInfo::CapturingNothing { use_span: upvar.span }); + diagnostics_info + .insert(UpvarMigrationInfo::CapturingNothing { use_span: upvar.span }); return Some(diagnostics_info); } hir::CaptureBy::Ref => {} @@ -1082,7 +1076,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug!(?root_var_min_capture_list); let mut projections_list = Vec::new(); - let mut diagnostics_info = FxHashSet::default(); + let mut diagnostics_info = FxIndexSet::default(); for captured_place in root_var_min_capture_list.iter() { match captured_place.info.capture_kind { @@ -1152,7 +1146,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let mut need_migrations = Vec::new(); - let mut auto_trait_migration_reasons = FxHashSet::default(); + let mut auto_trait_migration_reasons = UnordSet::default(); let mut drop_migration_needed = false; // Perform auto-trait analysis @@ -1164,7 +1158,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { diagnostics_info } else { - FxHashMap::default() + FxIndexMap::default() }; let drop_reorder_diagnostic = if let Some(diagnostics_info) = self @@ -1178,7 +1172,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { drop_migration_needed = true; diagnostics_info } else { - FxHashSet::default() + FxIndexSet::default() }; // Combine all the captures responsible for needing migrations into one HashSet @@ -1195,7 +1189,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(reasons) = auto_trait_diagnostic.get(&captures_info) { reasons.clone() } else { - FxHashSet::default() + UnordSet::default() }; // Check if migration is needed because of drop reorder as a result of that capture @@ -1203,7 +1197,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Combine all the reasons of why the root variable should be captured as a result of // auto trait implementation issues - auto_trait_migration_reasons.extend(capture_trait_reasons.iter().copied()); + auto_trait_migration_reasons.extend_unord(capture_trait_reasons.items().copied()); diagnostics_info.push(MigrationLintNote { captures_info, @@ -1385,7 +1379,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::Ref(..) => unreachable!(), ty::RawPtr(..) => unreachable!(), - ty::Adt(def, substs) => { + ty::Adt(def, args) => { // Multi-variant enums are captured in entirety, // which would've been handled in the case of single empty slice in `captured_by_move_projs`. assert_eq!(def.variants().len(), 1); @@ -1412,7 +1406,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) .collect(); - let after_field_ty = field.ty(self.tcx, substs); + let after_field_ty = field.ty(self.tcx, args); self.has_significant_drop_outside_of_captures( closure_def_id, closure_span, @@ -1665,9 +1659,11 @@ fn apply_capture_kind_on_capture_ty<'tcx>( ) -> Ty<'tcx> { match capture_kind { ty::UpvarCapture::ByValue => ty, - ty::UpvarCapture::ByRef(kind) => { - tcx.mk_ref(region.unwrap(), ty::TypeAndMut { ty: ty, mutbl: kind.to_mutbl_lossy() }) - } + ty::UpvarCapture::ByRef(kind) => Ty::new_ref( + tcx, + region.unwrap(), + ty::TypeAndMut { ty: ty, mutbl: kind.to_mutbl_lossy() }, + ), } } @@ -1891,6 +1887,7 @@ fn restrict_capture_precision( return (place, curr_mode); } ProjectionKind::Deref => {} + ProjectionKind::OpaqueCast => {} ProjectionKind::Field(..) => {} // ignore } } @@ -1943,10 +1940,11 @@ fn construct_place_string<'tcx>(tcx: TyCtxt<'_>, place: &Place<'tcx>) -> String let mut projections_str = String::new(); for (i, item) in place.projections.iter().enumerate() { let proj = match item.kind { - ProjectionKind::Field(a, b) => format!("({:?}, {:?})", a, b), + ProjectionKind::Field(a, b) => format!("({a:?}, {b:?})"), ProjectionKind::Deref => String::from("Deref"), ProjectionKind::Index => String::from("Index"), ProjectionKind::Subslice => String::from("Subslice"), + ProjectionKind::OpaqueCast => String::from("OpaqueCast"), }; if i != 0 { projections_str.push(','); @@ -1966,7 +1964,7 @@ fn construct_capture_kind_reason_string<'tcx>( let capture_kind_str = match capture_info.capture_kind { ty::UpvarCapture::ByValue => "ByValue".into(), - ty::UpvarCapture::ByRef(kind) => format!("{:?}", kind), + ty::UpvarCapture::ByRef(kind) => format!("{kind:?}"), }; format!("{place_str} captured as {capture_kind_str} here") @@ -1987,7 +1985,7 @@ fn construct_capture_info_string<'tcx>( let capture_kind_str = match capture_info.capture_kind { ty::UpvarCapture::ByValue => "ByValue".into(), - ty::UpvarCapture::ByRef(kind) => format!("{:?}", kind), + ty::UpvarCapture::ByRef(kind) => format!("{kind:?}"), }; format!("{place_str} -> {capture_kind_str}") } @@ -2001,7 +1999,7 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis( tcx: TyCtxt<'_>, closure_id: hir::HirId, ) -> bool { - if tcx.sess.rust_2021() { + if tcx.sess.at_least_rust_2021() { return false; } @@ -2247,5 +2245,5 @@ fn truncate_capture_for_optimization( fn enable_precise_capture(span: Span) -> bool { // We use span here to ensure that if the closure was generated by a macro with a different // edition. - span.rust_2021() + span.at_least_rust_2021() } diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index 0f21fc1e66238..603681bbc994a 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -3,24 +3,19 @@ // substitutions. use crate::FnCtxt; -use hir::def_id::LocalDefId; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::unord::ExtendUnord; use rustc_errors::{ErrorGuaranteed, StashKey}; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_infer::infer::error_reporting::TypeAnnotationNeeded::E0282; -use rustc_middle::hir::place::Place as HirPlace; -use rustc_middle::mir::FakeReadCause; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCast}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, PointerCoercion}; use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; -use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt}; -use rustc_middle::ty::TypeckResults; -use rustc_middle::ty::{self, ClosureSizeProfileData, Ty, TyCtxt}; +use rustc_middle::ty::visit::TypeVisitableExt; +use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::sym; use rustc_span::Span; use std::mem; -use std::ops::ControlFlow; /////////////////////////////////////////////////////////////////////////// // Entry point @@ -43,9 +38,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // This attribute causes us to dump some writeback information // in the form of errors, which is used for unit tests. - let rustc_dump_user_substs = self.tcx.has_attr(item_def_id, sym::rustc_dump_user_substs); + let rustc_dump_user_args = self.tcx.has_attr(item_def_id, sym::rustc_dump_user_args); - let mut wbcx = WritebackCx::new(self, body, rustc_dump_user_substs); + let mut wbcx = WritebackCx::new(self, body, rustc_dump_user_args); for param in body.params { wbcx.visit_node_id(param.pat.span, param.hir_id); } @@ -103,14 +98,14 @@ struct WritebackCx<'cx, 'tcx> { body: &'tcx hir::Body<'tcx>, - rustc_dump_user_substs: bool, + rustc_dump_user_args: bool, } impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { fn new( fcx: &'cx FnCtxt<'cx, 'tcx>, body: &'tcx hir::Body<'tcx>, - rustc_dump_user_substs: bool, + rustc_dump_user_args: bool, ) -> WritebackCx<'cx, 'tcx> { let owner = body.id().hir_id.owner; @@ -118,7 +113,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { fcx, typeck_results: ty::TypeckResults::new(owner), body, - rustc_dump_user_substs, + rustc_dump_user_args, }; // HACK: We specifically don't want the (opaque) error from tainting our @@ -137,7 +132,10 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { fn write_ty_to_typeck_results(&mut self, hir_id: hir::HirId, ty: Ty<'tcx>) { debug!("write_ty_to_typeck_results({:?}, {:?})", hir_id, ty); - assert!(!ty.has_infer() && !ty.has_placeholders() && !ty.has_free_regions()); + assert!( + !ty.has_infer() && !ty.has_placeholders() && !ty.has_free_regions(), + "{ty} can't be put into typeck results" + ); self.typeck_results.node_types_mut().insert(hir_id, ty); } @@ -148,31 +146,25 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { fn fix_scalar_builtin_expr(&mut self, e: &hir::Expr<'_>) { match e.kind { hir::ExprKind::Unary(hir::UnOp::Neg | hir::UnOp::Not, inner) => { - let inner_ty = self.fcx.node_ty(inner.hir_id); - let inner_ty = self.fcx.resolve_vars_if_possible(inner_ty); + let inner_ty = self.typeck_results.node_type(inner.hir_id); if inner_ty.is_scalar() { - let mut typeck_results = self.fcx.typeck_results.borrow_mut(); - typeck_results.type_dependent_defs_mut().remove(e.hir_id); - typeck_results.node_substs_mut().remove(e.hir_id); + self.typeck_results.type_dependent_defs_mut().remove(e.hir_id); + self.typeck_results.node_args_mut().remove(e.hir_id); } } hir::ExprKind::Binary(ref op, lhs, rhs) | hir::ExprKind::AssignOp(ref op, lhs, rhs) => { - let lhs_ty = self.fcx.node_ty(lhs.hir_id); - let lhs_ty = self.fcx.resolve_vars_if_possible(lhs_ty); - - let rhs_ty = self.fcx.node_ty(rhs.hir_id); - let rhs_ty = self.fcx.resolve_vars_if_possible(rhs_ty); + 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() { - let mut typeck_results = self.fcx.typeck_results.borrow_mut(); - typeck_results.type_dependent_defs_mut().remove(e.hir_id); - typeck_results.node_substs_mut().remove(e.hir_id); + 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 = typeck_results.adjustments_mut(); + let mut adjustments = self.typeck_results.adjustments_mut(); if let Some(a) = adjustments.get_mut(lhs.hir_id) { a.pop(); } @@ -182,7 +174,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { } } hir::ExprKind::AssignOp(..) - if let Some(a) = typeck_results.adjustments_mut().get_mut(lhs.hir_id) => + if let Some(a) = self.typeck_results.adjustments_mut().get_mut(lhs.hir_id) => { a.pop(); } @@ -200,16 +192,14 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { // if they are not we don't modify the expr, hence we bypass the ICE fn is_builtin_index( &mut self, - typeck_results: &TypeckResults<'tcx>, e: &hir::Expr<'_>, base_ty: Ty<'tcx>, index_ty: Ty<'tcx>, ) -> bool { - if let Some(elem_ty) = base_ty.builtin_index() { - let Some(exp_ty) = typeck_results.expr_ty_opt(e) else {return false;}; - let resolved_exp_ty = self.resolve(exp_ty, &e.span); - - elem_ty == resolved_exp_ty && index_ty == self.fcx.tcx.types.usize + if let Some(elem_ty) = base_ty.builtin_index() + && let Some(exp_ty) = self.typeck_results.expr_ty_opt(e) + { + elem_ty == exp_ty && index_ty == self.fcx.tcx.types.usize } else { false } @@ -220,39 +210,36 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { // to use builtin indexing because the index type is known to be // usize-ish fn fix_index_builtin_expr(&mut self, e: &hir::Expr<'_>) { - if let hir::ExprKind::Index(ref base, ref index) = e.kind { - let mut typeck_results = self.fcx.typeck_results.borrow_mut(); - + if let hir::ExprKind::Index(ref base, ref index, _) = e.kind { // All valid indexing looks like this; might encounter non-valid indexes at this point. - let base_ty = typeck_results - .expr_ty_adjusted_opt(base) - .map(|t| self.fcx.resolve_vars_if_possible(t).kind()); + let base_ty = self.typeck_results.expr_ty_adjusted_opt(base); if base_ty.is_none() { // When encountering `return [0][0]` outside of a `fn` body we can encounter a base // that isn't in the type table. We assume more relevant errors have already been // emitted, so we delay an ICE if none have. (#64638) - self.tcx().sess.delay_span_bug(e.span, format!("bad base: `{:?}`", base)); + self.tcx().sess.delay_span_bug(e.span, format!("bad base: `{base:?}`")); } - if let Some(ty::Ref(_, base_ty, _)) = base_ty { - let index_ty = typeck_results.expr_ty_adjusted_opt(index).unwrap_or_else(|| { - // When encountering `return [0][0]` outside of a `fn` body we would attempt - // to access an nonexistent index. We assume that more relevant errors will - // already have been emitted, so we only gate on this with an ICE if no - // error has been emitted. (#64638) - self.fcx.tcx.ty_error_with_message( - e.span, - format!("bad index {:?} for base: `{:?}`", index, base), - ) - }); - let index_ty = self.fcx.resolve_vars_if_possible(index_ty); - let resolved_base_ty = self.resolve(*base_ty, &base.span); - - if self.is_builtin_index(&typeck_results, e, resolved_base_ty, index_ty) { + if let Some(base_ty) = base_ty + && let ty::Ref(_, base_ty_inner, _) = *base_ty.kind() + { + let index_ty = + self.typeck_results.expr_ty_adjusted_opt(index).unwrap_or_else(|| { + // When encountering `return [0][0]` outside of a `fn` body we would attempt + // to access an nonexistent index. We assume that more relevant errors will + // already have been emitted, so we only gate on this with an ICE if no + // error has been emitted. (#64638) + Ty::new_error_with_message( + self.fcx.tcx, + e.span, + format!("bad index {index:?} for base: `{base:?}`"), + ) + }); + if self.is_builtin_index(e, base_ty_inner, index_ty) { // Remove the method call record - typeck_results.type_dependent_defs_mut().remove(e.hir_id); - typeck_results.node_substs_mut().remove(e.hir_id); + 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) = typeck_results.adjustments_mut().get_mut(base.hir_id) { + if let Some(a) = self.typeck_results.adjustments_mut().get_mut(base.hir_id) { // Discard the need for a mutable borrow // Extra adjustment made when indexing causes a drop @@ -260,7 +247,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { // Since this is "after" the other adjustment to be // discarded, we do an extra `pop()` if let Some(Adjustment { - kind: Adjust::Pointer(PointerCast::Unsize), .. + kind: Adjust::Pointer(PointerCoercion::Unsize), .. }) = a.pop() { // So the borrow discard actually happens here @@ -283,9 +270,6 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> { fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { - self.fix_scalar_builtin_expr(e); - self.fix_index_builtin_expr(e); - match e.kind { hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let body = self.fcx.tcx.hir().body(body); @@ -314,6 +298,9 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> { self.visit_node_id(e.span, e.hir_id); intravisit::walk_expr(self, e); + + self.fix_scalar_builtin_expr(e); + self.fix_index_builtin_expr(e); } fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { @@ -358,7 +345,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> { fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) { intravisit::walk_local(self, l); - let var_ty = self.fcx.local_ty(l.span, l.hir_id).decl_ty; + let var_ty = self.fcx.local_ty(l.span, l.hir_id); let var_ty = self.resolve(var_ty, &l.span); self.write_ty_to_typeck_results(l.hir_id, var_ty); } @@ -385,66 +372,75 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> { impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { fn eval_closure_size(&mut self) { - let mut res: FxHashMap> = Default::default(); - for (&closure_def_id, data) in self.fcx.typeck_results.borrow().closure_size_eval.iter() { - let closure_hir_id = self.tcx().hir().local_def_id_to_hir_id(closure_def_id); - - let data = self.resolve(*data, &closure_hir_id); - - res.insert(closure_def_id, data); - } - - self.typeck_results.closure_size_eval = res; + self.tcx().with_stable_hashing_context(|ref hcx| { + let fcx_typeck_results = self.fcx.typeck_results.borrow(); + + self.typeck_results.closure_size_eval = fcx_typeck_results + .closure_size_eval + .to_sorted(hcx, false) + .into_iter() + .map(|(&closure_def_id, data)| { + let closure_hir_id = self.tcx().hir().local_def_id_to_hir_id(closure_def_id); + let data = self.resolve(*data, &closure_hir_id); + (closure_def_id, data) + }) + .collect(); + }) } - fn visit_min_capture_map(&mut self) { - let mut min_captures_wb = ty::MinCaptureInformationMap::with_capacity_and_hasher( - self.fcx.typeck_results.borrow().closure_min_captures.len(), - Default::default(), - ); - for (&closure_def_id, root_min_captures) in - self.fcx.typeck_results.borrow().closure_min_captures.iter() - { - let mut root_var_map_wb = ty::RootVariableMinCaptureList::with_capacity_and_hasher( - root_min_captures.len(), - Default::default(), - ); - for (var_hir_id, min_list) in root_min_captures.iter() { - let min_list_wb = min_list - .iter() - .map(|captured_place| { - let locatable = captured_place.info.path_expr_id.unwrap_or_else(|| { - self.tcx().hir().local_def_id_to_hir_id(closure_def_id) - }); - - self.resolve(captured_place.clone(), &locatable) - }) - .collect(); - root_var_map_wb.insert(*var_hir_id, min_list_wb); - } - min_captures_wb.insert(closure_def_id, root_var_map_wb); - } - self.typeck_results.closure_min_captures = min_captures_wb; + fn visit_min_capture_map(&mut self) { + self.tcx().with_stable_hashing_context(|ref hcx| { + let fcx_typeck_results = self.fcx.typeck_results.borrow(); + + self.typeck_results.closure_min_captures = fcx_typeck_results + .closure_min_captures + .to_sorted(hcx, false) + .into_iter() + .map(|(&closure_def_id, root_min_captures)| { + let root_var_map_wb = root_min_captures + .iter() + .map(|(var_hir_id, min_list)| { + let min_list_wb = min_list + .iter() + .map(|captured_place| { + let locatable = + captured_place.info.path_expr_id.unwrap_or_else(|| { + self.tcx().hir().local_def_id_to_hir_id(closure_def_id) + }); + self.resolve(captured_place.clone(), &locatable) + }) + .collect(); + (*var_hir_id, min_list_wb) + }) + .collect(); + (closure_def_id, root_var_map_wb) + }) + .collect(); + }) } fn visit_fake_reads_map(&mut self) { - let mut resolved_closure_fake_reads: FxHashMap< - LocalDefId, - Vec<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>, - > = Default::default(); - for (&closure_def_id, fake_reads) in - self.fcx.typeck_results.borrow().closure_fake_reads.iter() - { - let mut resolved_fake_reads = Vec::<(HirPlace<'tcx>, FakeReadCause, hir::HirId)>::new(); - for (place, cause, hir_id) in fake_reads.iter() { - let locatable = self.tcx().hir().local_def_id_to_hir_id(closure_def_id); - - let resolved_fake_read = self.resolve(place.clone(), &locatable); - resolved_fake_reads.push((resolved_fake_read, *cause, *hir_id)); - } - resolved_closure_fake_reads.insert(closure_def_id, resolved_fake_reads); - } - self.typeck_results.closure_fake_reads = resolved_closure_fake_reads; + self.tcx().with_stable_hashing_context(move |ref hcx| { + let fcx_typeck_results = self.fcx.typeck_results.borrow(); + + self.typeck_results.closure_fake_reads = fcx_typeck_results + .closure_fake_reads + .to_sorted(hcx, true) + .into_iter() + .map(|(&closure_def_id, fake_reads)| { + let resolved_fake_reads = fake_reads + .iter() + .map(|(place, cause, hir_id)| { + let locatable = self.tcx().hir().local_def_id_to_hir_id(closure_def_id); + let resolved_fake_read = self.resolve(place.clone(), &locatable); + (resolved_fake_read, *cause, *hir_id) + }) + .collect(); + + (closure_def_id, resolved_fake_reads) + }) + .collect(); + }); } fn visit_closures(&mut self) { @@ -479,7 +475,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner); let common_hir_owner = fcx_typeck_results.hir_owner; - if self.rustc_dump_user_substs { + if self.rustc_dump_user_args { let sorted_user_provided_types = fcx_typeck_results.user_provided_types().items_in_stable_order(); @@ -487,15 +483,13 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { for (local_id, c_ty) in sorted_user_provided_types { let hir_id = hir::HirId { owner: common_hir_owner, local_id }; - if let ty::UserType::TypeOf(_, user_substs) = c_ty.value { + if let ty::UserType::TypeOf(_, user_args) = c_ty.value { // This is a unit-testing mechanism. let span = self.tcx().hir().span(hir_id); // We need to buffer the errors in order to guarantee a consistent // order when emitting them. - let err = self - .tcx() - .sess - .struct_span_err(span, format!("user substs: {:?}", user_substs)); + let err = + self.tcx().sess.struct_span_err(span, format!("user args: {user_args:?}")); err.buffer(&mut errors_buffer); } } @@ -529,7 +523,7 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { let fcx_typeck_results = self.fcx.typeck_results.borrow(); assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner); - self.typeck_results.user_provided_sigs.extend( + self.typeck_results.user_provided_sigs.extend_unord( fcx_typeck_results.user_provided_sigs.items().map(|(&def_id, c_sig)| { if cfg!(debug_assertions) && c_sig.has_infer() { span_bug!( @@ -549,10 +543,15 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner); self.typeck_results.generator_interior_types = fcx_typeck_results.generator_interior_types.clone(); - for (&expr_def_id, predicates) in fcx_typeck_results.generator_interior_predicates.iter() { - let predicates = self.resolve(predicates.clone(), &self.fcx.tcx.def_span(expr_def_id)); - self.typeck_results.generator_interior_predicates.insert(expr_def_id, predicates); - } + self.tcx().with_stable_hashing_context(move |ref hcx| { + for (&expr_def_id, predicates) in + fcx_typeck_results.generator_interior_predicates.to_sorted(hcx, false).into_iter() + { + let predicates = + self.resolve(predicates.clone(), &self.fcx.tcx.def_span(expr_def_id)); + self.typeck_results.generator_interior_predicates.insert(expr_def_id, predicates); + } + }) } #[instrument(skip(self), level = "debug")] @@ -562,40 +561,22 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { let hidden_type = self.resolve(decl.hidden_type, &decl.hidden_type.span); let opaque_type_key = self.resolve(opaque_type_key, &decl.hidden_type.span); - struct RecursionChecker { - def_id: LocalDefId, - } - impl<'tcx> ty::TypeVisitor> for RecursionChecker { - type BreakTy = (); - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { - if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) = *t.kind() { - if def_id == self.def_id.to_def_id() { - return ControlFlow::Break(()); - } - } - t.super_visit_with(self) - } - } - if hidden_type - .visit_with(&mut RecursionChecker { def_id: opaque_type_key.def_id }) - .is_break() + if let ty::Alias(ty::Opaque, alias_ty) = hidden_type.ty.kind() + && alias_ty.def_id == opaque_type_key.def_id.to_def_id() + && alias_ty.args == opaque_type_key.args { continue; } - let hidden_type = - self.tcx().erase_regions(hidden_type.remap_generic_params_to_declaration_params( - opaque_type_key, - self.tcx(), - true, - )); - + // 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.def_id, hidden_type) + .insert(opaque_type_key, hidden_type) && last_opaque_ty.ty != hidden_type.ty { + assert!(!self.fcx.next_trait_solver()); hidden_type .report_mismatch(&last_opaque_ty, opaque_type_key.def_id, self.tcx()) .stash( @@ -632,11 +613,11 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { debug!(?n_ty); // Resolve any substitutions - if let Some(substs) = self.fcx.typeck_results.borrow().node_substs_opt(hir_id) { - let substs = self.resolve(substs, &span); - debug!("write_substs_to_tcx({:?}, {:?})", hir_id, substs); - assert!(!substs.has_infer() && !substs.has_placeholders()); - self.typeck_results.node_substs_mut().insert(hir_id, substs); + if let Some(args) = self.fcx.typeck_results.borrow().node_args_opt(hir_id) { + let args = self.resolve(args, &span); + debug!("write_args_to_tcx({:?}, {:?})", hir_id, args); + assert!(!args.has_infer() && !args.has_placeholders()); + self.typeck_results.node_args_mut().insert(hir_id, args); } } @@ -816,11 +797,15 @@ impl<'cx, 'tcx> TypeFolder> for Resolver<'cx, 'tcx> { fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { match self.fcx.fully_resolve(t) { - Ok(t) if self.fcx.tcx.trait_solver_next() => { + Ok(t) if self.fcx.next_trait_solver() => { // We must normalize erasing regions here, since later lints // expect that types that show up in the typeck are fully // normalized. - self.fcx.tcx.try_normalize_erasing_regions(self.fcx.param_env, t).unwrap_or(t) + if let Ok(t) = self.fcx.tcx.try_normalize_erasing_regions(self.fcx.param_env, t) { + t + } else { + EraseEarlyRegions { tcx: self.fcx.tcx }.fold_ty(t) + } } Ok(t) => { // Do not anonymize late-bound regions @@ -833,7 +818,7 @@ impl<'cx, 'tcx> TypeFolder> for Resolver<'cx, 'tcx> { debug!("Resolver::fold_ty: input type `{:?}` not fully resolvable", t); let e = self.report_error(t); self.replaced_with_error = Some(e); - self.fcx.tcx.ty_error(e) + Ty::new_error(self.fcx.tcx, e) } } } @@ -850,7 +835,7 @@ impl<'cx, 'tcx> TypeFolder> for Resolver<'cx, 'tcx> { debug!("Resolver::fold_const: input const `{:?}` not fully resolvable", ct); let e = self.report_error(ct); self.replaced_with_error = Some(e); - self.fcx.tcx.const_error(ct.ty(), e) + ty::Const::new_error(self.fcx.tcx, e, ct.ty()) } } } diff --git a/compiler/rustc_incremental/messages.ftl b/compiler/rustc_incremental/messages.ftl index b760620e3d48b..9fa4e0fb27cf1 100644 --- a/compiler/rustc_incremental/messages.ftl +++ b/compiler/rustc_incremental/messages.ftl @@ -1,118 +1,118 @@ -incremental_unrecognized_depnode = unrecognized `DepNode` variant: {$name} +incremental_assert_loaded = + we asserted that an existing incremental cache directory should be successfully loaded, but it was not -incremental_missing_depnode = missing `DepNode` variant +incremental_assert_not_loaded = + we asserted that the incremental cache should not be loaded, but it was loaded -incremental_missing_if_this_changed = no `#[rustc_if_this_changed]` annotation detected +incremental_assertion_auto = + `except` specified DepNodes that can not be affected for "{$name}": "{$e}" -incremental_no_path = no path from `{$source}` to `{$target}` +incremental_associated_value_expected = expected an associated value -incremental_ok = OK +incremental_associated_value_expected_for = associated value expected for `{$ident}` -incremental_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified +incremental_canonicalize_path = incremental compilation: error canonicalizing path `{$path}`: {$err} -incremental_missing_query_depgraph = - found CGU-reuse attribute but `-Zquery-dep-graph` was not specified +incremental_cargo_help_1 = + incremental compilation can be disabled by setting the environment variable CARGO_INCREMENTAL=0 (see https://doc.rust-lang.org/cargo/reference/profiles.html#incremental) +incremental_cargo_help_2 = + the entire build directory can be changed to a different filesystem by setting the environment variable CARGO_TARGET_DIR to a different path (see https://doc.rust-lang.org/cargo/reference/config.html#buildtarget-dir) -incremental_malformed_cgu_name = - found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case). +incremental_copy_workproduct_to_cache = + error copying object file `{$from}` to incremental directory as `{$to}`: {$err} -incremental_no_module_named = - no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} +incremental_create_dep_graph = failed to create dependency graph at `{$path}`: {$err} -incremental_field_associated_value_expected = associated value expected for `{$name}` +incremental_create_incr_comp_dir = + could not create incremental compilation {$tag} directory `{$path}`: {$err} -incremental_no_field = no field `{$name}` +incremental_create_lock = + incremental compilation: could not create session directory lock file: {$lock_err} +incremental_create_new = failed to create {$name} at `{$path}`: {$err} -incremental_assertion_auto = - `except` specified DepNodes that can not be affected for "{$name}": "{$e}" +incremental_decode_incr_cache = could not decode incremental cache: {$err} -incremental_undefined_clean_dirty_assertions_item = - clean/dirty auto-assertions not yet defined for Node::Item.node={$kind} +incremental_delete_full = error deleting incremental compilation session directory `{$path}`: {$err} -incremental_undefined_clean_dirty_assertions = - clean/dirty auto-assertions not yet defined for {$kind} +incremental_delete_incompatible = + failed to delete invalidated or incompatible incremental compilation session directory contents `{$path}`: {$err} -incremental_repeated_depnode_label = dep-node label `{$label}` is repeated +incremental_delete_lock = + error deleting lock file for incremental compilation session directory `{$path}`: {$err} -incremental_unrecognized_depnode_label = dep-node label `{$label}` not recognized +incremental_delete_old = unable to delete old {$name} at `{$path}`: {$err} -incremental_not_dirty = `{$dep_node_str}` should be dirty but is not +incremental_delete_partial = failed to delete partly initialized session dir `{$path}`: {$err} -incremental_not_clean = `{$dep_node_str}` should be clean but is not +incremental_delete_workproduct = file-system error deleting outdated file `{$path}`: {$err} -incremental_not_loaded = `{$dep_node_str}` should have been loaded from disk but it was not +incremental_field_associated_value_expected = associated value expected for `{$name}` -incremental_unknown_item = unknown item `{$name}` +incremental_finalize = error finalizing incremental compilation session directory `{$path}`: {$err} -incremental_no_cfg = no cfg attribute +incremental_finalized_gc_failed = + failed to garbage collect finalized incremental compilation session directory `{$path}`: {$err} -incremental_associated_value_expected_for = associated value expected for `{$ident}` +incremental_hard_link_failed = + hard linking files in the incremental compilation cache failed. copying files instead. consider moving the cache directory to a file system which supports hard linking in session dir `{$path}` -incremental_associated_value_expected = expected an associated value +incremental_invalid_gc_failed = + failed to garbage collect invalid incremental compilation session directory `{$path}`: {$err} -incremental_unchecked_clean = found unchecked `#[rustc_clean]` attribute +incremental_load_dep_graph = could not load dep-graph from `{$path}`: {$err} -incremental_delete_old = unable to delete old {$name} at `{$path}`: {$err} +incremental_lock_unsupported = + the filesystem for the incremental path at {$session_dir} does not appear to support locking, consider changing the incremental path to a filesystem that supports locking or disable incremental compilation +incremental_malformed_cgu_name = + found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case). -incremental_create_new = failed to create {$name} at `{$path}`: {$err} +incremental_missing_depnode = missing `DepNode` variant -incremental_write_new = failed to write {$name} to `{$path}`: {$err} +incremental_missing_if_this_changed = no `#[rustc_if_this_changed]` annotation detected -incremental_canonicalize_path = incremental compilation: error canonicalizing path `{$path}`: {$err} +incremental_missing_query_depgraph = + found CGU-reuse attribute but `-Zquery-dep-graph` was not specified -incremental_create_incr_comp_dir = - could not create incremental compilation {$tag} directory `{$path}`: {$err} +incremental_move_dep_graph = failed to move dependency graph from `{$from}` to `{$to}`: {$err} -incremental_create_lock = - incremental compilation: could not create session directory lock file: {$lock_err} -incremental_lock_unsupported = - the filesystem for the incremental path at {$session_dir} does not appear to support locking, consider changing the incremental path to a filesystem that supports locking or disable incremental compilation -incremental_cargo_help_1 = - incremental compilation can be disabled by setting the environment variable CARGO_INCREMENTAL=0 (see https://doc.rust-lang.org/cargo/reference/profiles.html#incremental) -incremental_cargo_help_2 = - the entire build directory can be changed to a different filesystem by setting the environment variable CARGO_TARGET_DIR to a different path (see https://doc.rust-lang.org/cargo/reference/config.html#buildtarget-dir) +incremental_no_cfg = no cfg attribute -incremental_delete_lock = - error deleting lock file for incremental compilation session directory `{$path}`: {$err} +incremental_no_field = no field `{$name}` -incremental_hard_link_failed = - hard linking files in the incremental compilation cache failed. copying files instead. consider moving the cache directory to a file system which supports hard linking in session dir `{$path}` +incremental_no_module_named = + no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} -incremental_delete_partial = failed to delete partly initialized session dir `{$path}`: {$err} +incremental_no_path = no path from `{$source}` to `{$target}` -incremental_delete_full = error deleting incremental compilation session directory `{$path}`: {$err} +incremental_not_clean = `{$dep_node_str}` should be clean but is not -incremental_finalize = error finalizing incremental compilation session directory `{$path}`: {$err} +incremental_not_dirty = `{$dep_node_str}` should be dirty but is not -incremental_invalid_gc_failed = - failed to garbage collect invalid incremental compilation session directory `{$path}`: {$err} +incremental_not_loaded = `{$dep_node_str}` should have been loaded from disk but it was not -incremental_finalized_gc_failed = - failed to garbage collect finalized incremental compilation session directory `{$path}`: {$err} +incremental_ok = OK + +incremental_repeated_depnode_label = dep-node label `{$label}` is repeated incremental_session_gc_failed = failed to garbage collect incremental compilation session directory `{$path}`: {$err} -incremental_assert_not_loaded = - we asserted that the incremental cache should not be loaded, but it was loaded - -incremental_assert_loaded = - we asserted that an existing incremental cache directory should be successfully loaded, but it was not +incremental_unchecked_clean = found unchecked `#[rustc_clean]` attribute -incremental_delete_incompatible = - failed to delete invalidated or incompatible incremental compilation session directory contents `{$path}`: {$err} +incremental_undefined_clean_dirty_assertions = + clean/dirty auto-assertions not yet defined for {$kind} -incremental_load_dep_graph = could not load dep-graph from `{$path}`: {$err} +incremental_undefined_clean_dirty_assertions_item = + clean/dirty auto-assertions not yet defined for Node::Item.node={$kind} -incremental_decode_incr_cache = could not decode incremental cache: {$err} +incremental_unknown_item = unknown item `{$name}` -incremental_write_dep_graph = failed to write dependency graph to `{$path}`: {$err} +incremental_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified -incremental_move_dep_graph = failed to move dependency graph from `{$from}` to `{$to}`: {$err} +incremental_unrecognized_depnode = unrecognized `DepNode` variant: {$name} -incremental_create_dep_graph = failed to create dependency graph at `{$path}`: {$err} +incremental_unrecognized_depnode_label = dep-node label `{$label}` not recognized -incremental_copy_workproduct_to_cache = - error copying object file `{$from}` to incremental directory as `{$to}`: {$err} +incremental_write_dep_graph = failed to write dependency graph to `{$path}`: {$err} -incremental_delete_workproduct = file-system error deleting outdated file `{$path}`: {$err} +incremental_write_new = failed to write {$name} to `{$path}`: {$err} diff --git a/compiler/rustc_incremental/src/assert_dep_graph.rs b/compiler/rustc_incremental/src/assert_dep_graph.rs index 22bd12f2e6361..5e7ae3ecdb85d 100644 --- a/compiler/rustc_incremental/src/assert_dep_graph.rs +++ b/compiler/rustc_incremental/src/assert_dep_graph.rs @@ -35,7 +35,7 @@ use crate::errors; use rustc_ast as ast; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::graph::implementation::{Direction, NodeIndex, INCOMING, OUTGOING}; use rustc_graphviz as dot; use rustc_hir as hir; @@ -241,16 +241,16 @@ fn dump_graph(query: &DepGraphQuery) { { // dump a .txt file with just the edges: - let txt_path = format!("{}.txt", path); + let txt_path = format!("{path}.txt"); let mut file = BufWriter::new(File::create(&txt_path).unwrap()); for (source, target) in &edges { - write!(file, "{:?} -> {:?}\n", source, target).unwrap(); + write!(file, "{source:?} -> {target:?}\n").unwrap(); } } { // dump a .dot file in graphviz format: - let dot_path = format!("{}.dot", path); + let dot_path = format!("{path}.dot"); let mut v = Vec::new(); dot::render(&GraphvizDepGraph(nodes, edges), &mut v).unwrap(); fs::write(dot_path, v).unwrap(); @@ -258,7 +258,7 @@ fn dump_graph(query: &DepGraphQuery) { } #[allow(missing_docs)] -pub struct GraphvizDepGraph(FxHashSet, Vec<(DepKind, DepKind)>); +pub struct GraphvizDepGraph(FxIndexSet, Vec<(DepKind, DepKind)>); impl<'a> dot::GraphWalk<'a> for GraphvizDepGraph { type Node = DepKind; @@ -285,7 +285,7 @@ impl<'a> dot::Labeller<'a> for GraphvizDepGraph { dot::Id::new("DependencyGraph").unwrap() } fn node_id(&self, n: &DepKind) -> dot::Id<'_> { - let s: String = format!("{:?}", n) + let s: String = format!("{n:?}") .chars() .map(|c| if c == '_' || c.is_alphanumeric() { c } else { '_' }) .collect(); @@ -293,7 +293,7 @@ impl<'a> dot::Labeller<'a> for GraphvizDepGraph { dot::Id::new(s).unwrap() } fn node_label(&self, n: &DepKind) -> dot::LabelText<'_> { - dot::LabelText::label(format!("{:?}", n)) + dot::LabelText::label(format!("{n:?}")) } } @@ -303,7 +303,7 @@ impl<'a> dot::Labeller<'a> for GraphvizDepGraph { fn node_set<'q>( query: &'q DepGraphQuery, filter: &DepNodeFilter, -) -> Option> { +) -> Option> { debug!("node_set(filter={:?})", filter); if filter.accepts_all() { @@ -315,9 +315,9 @@ fn node_set<'q>( fn filter_nodes<'q>( query: &'q DepGraphQuery, - sources: &Option>, - targets: &Option>, -) -> FxHashSet { + sources: &Option>, + targets: &Option>, +) -> FxIndexSet { if let Some(sources) = sources { if let Some(targets) = targets { walk_between(query, sources, targets) @@ -333,10 +333,10 @@ fn filter_nodes<'q>( fn walk_nodes<'q>( query: &'q DepGraphQuery, - starts: &FxHashSet<&'q DepNode>, + starts: &FxIndexSet<&'q DepNode>, direction: Direction, -) -> FxHashSet { - let mut set = FxHashSet::default(); +) -> FxIndexSet { + let mut set = FxIndexSet::default(); for &start in starts { debug!("walk_nodes: start={:?} outgoing?={:?}", start, direction == OUTGOING); if set.insert(start.kind) { @@ -357,9 +357,9 @@ fn walk_nodes<'q>( fn walk_between<'q>( query: &'q DepGraphQuery, - sources: &FxHashSet<&'q DepNode>, - targets: &FxHashSet<&'q DepNode>, -) -> FxHashSet { + sources: &FxIndexSet<&'q DepNode>, + targets: &FxIndexSet<&'q DepNode>, +) -> FxIndexSet { // This is a bit tricky. We want to include a node only if it is: // (a) reachable from a source and (b) will reach a target. And we // have to be careful about cycles etc. Luckily efficiency is not @@ -426,8 +426,8 @@ fn walk_between<'q>( } } -fn filter_edges(query: &DepGraphQuery, nodes: &FxHashSet) -> Vec<(DepKind, DepKind)> { - let uniq: FxHashSet<_> = query +fn filter_edges(query: &DepGraphQuery, nodes: &FxIndexSet) -> Vec<(DepKind, DepKind)> { + let uniq: FxIndexSet<_> = query .edges() .into_iter() .map(|(s, t)| (s.kind, t.kind)) diff --git a/compiler/rustc_incremental/src/assert_module_sources.rs b/compiler/rustc_incremental/src/assert_module_sources.rs index c550e553bb032..8e22ab4083eef 100644 --- a/compiler/rustc_incremental/src/assert_module_sources.rs +++ b/compiler/rustc_incremental/src/assert_module_sources.rs @@ -6,6 +6,7 @@ //! //! ``` //! # #![feature(rustc_attrs)] +//! # #![allow(internal_features)] //! #![rustc_partition_reused(module="spike", cfg="rpass2")] //! #![rustc_partition_codegened(module="spike-x", cfg="rpass2")] //! ``` @@ -24,7 +25,7 @@ use crate::errors; use rustc_ast as ast; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::mir::mono::CodegenUnitNameBuilder; use rustc_middle::ty::TyCtxt; @@ -52,7 +53,7 @@ pub fn assert_module_sources(tcx: TyCtxt<'_>) { struct AssertModuleSource<'tcx> { tcx: TyCtxt<'tcx>, - available_cgus: FxHashSet, + available_cgus: UnordSet, } impl<'tcx> AssertModuleSource<'tcx> { @@ -118,9 +119,8 @@ impl<'tcx> AssertModuleSource<'tcx> { debug!("mapping '{}' to cgu name '{}'", self.field(attr, sym::module), cgu_name); if !self.available_cgus.contains(&cgu_name) { - let mut cgu_names: Vec<&str> = - self.available_cgus.iter().map(|cgu| cgu.as_str()).collect(); - cgu_names.sort(); + let cgu_names: Vec<&str> = + self.available_cgus.items().map(|cgu| cgu.as_str()).into_sorted_stable_ord(); self.tcx.sess.emit_err(errors::NoModuleNamed { span: attr.span, user_path, diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs index 11710c368cefe..b9171fad55ba3 100644 --- a/compiler/rustc_incremental/src/lib.rs +++ b/compiler/rustc_incremental/src/lib.rs @@ -4,7 +4,6 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(never_type)] #![recursion_limit = "256"] -#![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] diff --git a/compiler/rustc_incremental/src/persist/dirty_clean.rs b/compiler/rustc_incremental/src/persist/dirty_clean.rs index 43274091cb873..5dd06c6ca4f40 100644 --- a/compiler/rustc_incremental/src/persist/dirty_clean.rs +++ b/compiler/rustc_incremental/src/persist/dirty_clean.rs @@ -22,6 +22,7 @@ use crate::errors; use rustc_ast::{self as ast, Attribute, NestedMetaItem}; use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit; use rustc_hir::Node as HirNode; @@ -125,7 +126,7 @@ const LABELS_ADT: &[&[&str]] = &[BASE_HIR, BASE_STRUCT]; // // type_of for these. -type Labels = FxHashSet; +type Labels = UnordSet; /// Represents the requested configuration by rustc_clean/dirty struct Assertion { @@ -197,7 +198,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> { let (name, mut auto) = self.auto_labels(item_id, attr); let except = self.except(attr); let loaded_from_disk = self.loaded_from_disk(attr); - for e in except.iter() { + for e in except.items().into_sorted_stable_ord() { if !auto.remove(e) { self.tcx.sess.emit_fatal(errors::AssertionAuto { span: attr.span, name, e }); } @@ -299,7 +300,7 @@ impl<'tcx> DirtyCleanVisitor<'tcx> { }, _ => self.tcx.sess.emit_fatal(errors::UndefinedCleanDirty { span: attr.span, - kind: format!("{:?}", node), + kind: format!("{node:?}"), }), }; let labels = @@ -376,15 +377,15 @@ impl<'tcx> DirtyCleanVisitor<'tcx> { continue; }; self.checked_attrs.insert(attr.id); - for label in assertion.clean { + for label in assertion.clean.items().into_sorted_stable_ord() { let dep_node = DepNode::from_label_string(self.tcx, &label, def_path_hash).unwrap(); self.assert_clean(item_span, dep_node); } - for label in assertion.dirty { + for label in assertion.dirty.items().into_sorted_stable_ord() { let dep_node = DepNode::from_label_string(self.tcx, &label, def_path_hash).unwrap(); self.assert_dirty(item_span, dep_node); } - for label in assertion.loaded_from_disk { + for label in assertion.loaded_from_disk.items().into_sorted_stable_ord() { let dep_node = DepNode::from_label_string(self.tcx, &label, def_path_hash).unwrap(); self.assert_loaded_from_disk(item_span, dep_node); } diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index e3c688b3e98cb..db8ea2bfe48aa 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -104,8 +104,9 @@ //! implemented. use crate::errors; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_data_structures::svh::Svh; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_data_structures::{base_n, flock}; use rustc_errors::ErrorGuaranteed; use rustc_fs_util::{link_or_copy, try_canonicalize, LinkOrCopy}; @@ -426,13 +427,11 @@ fn copy_files(sess: &Session, target_dir: &Path, source_dir: &Path) -> Result timestamp, + Err(e) => { + debug!("unexpected incr-comp session dir: {}: {}", session_dir.display(), e); + continue; + } + }; if timestamp > best_candidate.0 { best_candidate = (timestamp, Some(session_dir.clone())); @@ -563,14 +566,14 @@ fn is_session_directory_lock_file(file_name: &str) -> bool { file_name.starts_with("s-") && file_name.ends_with(LOCK_FILE_EXT) } -fn extract_timestamp_from_session_dir(directory_name: &str) -> Result { +fn extract_timestamp_from_session_dir(directory_name: &str) -> Result { if !is_session_directory(directory_name) { - return Err(()); + return Err("not a directory"); } let dash_indices: Vec<_> = directory_name.match_indices('-').map(|(idx, _)| idx).collect(); if dash_indices.len() != 3 { - return Err(()); + return Err("not three dashes in name"); } string_to_timestamp(&directory_name[dash_indices[0] + 1..dash_indices[1]]) @@ -582,11 +585,11 @@ fn timestamp_to_string(timestamp: SystemTime) -> String { base_n::encode(micros as u128, INT_ENCODE_BASE) } -fn string_to_timestamp(s: &str) -> Result { +fn string_to_timestamp(s: &str) -> Result { let micros_since_unix_epoch = u64::from_str_radix(s, INT_ENCODE_BASE as u32); if micros_since_unix_epoch.is_err() { - return Err(()); + return Err("timestamp not an int"); } let micros_since_unix_epoch = micros_since_unix_epoch.unwrap(); @@ -603,7 +606,7 @@ fn crate_path(sess: &Session, crate_name: Symbol, stable_crate_id: StableCrateId let stable_crate_id = base_n::encode(stable_crate_id.as_u64() as u128, INT_ENCODE_BASE); - let crate_name = format!("{}-{}", crate_name, stable_crate_id); + let crate_name = format!("{crate_name}-{stable_crate_id}"); incr_dir.join(crate_name) } @@ -635,8 +638,8 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { // First do a pass over the crate directory, collecting lock files and // session directories - let mut session_directories = FxHashSet::default(); - let mut lock_files = FxHashSet::default(); + let mut session_directories = FxIndexSet::default(); + let mut lock_files = UnordSet::default(); for dir_entry in crate_directory.read_dir()? { let Ok(dir_entry) = dir_entry else { @@ -657,10 +660,11 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { // This is something we don't know, leave it alone } } + session_directories.sort(); // Now map from lock files to session directories - let lock_file_to_session_dir: FxHashMap> = lock_files - .into_iter() + let lock_file_to_session_dir: UnordMap> = lock_files + .into_items() .map(|lock_file_name| { assert!(lock_file_name.ends_with(LOCK_FILE_EXT)); let dir_prefix_end = lock_file_name.len() - LOCK_FILE_EXT.len(); @@ -670,11 +674,13 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { }; (lock_file_name, session_dir.map(String::clone)) }) - .collect(); + .into(); // Delete all lock files, that don't have an associated directory. They must // be some kind of leftover - for (lock_file_name, directory_name) in &lock_file_to_session_dir { + for (lock_file_name, directory_name) in + lock_file_to_session_dir.items().into_sorted_stable_ord() + { if directory_name.is_none() { let Ok(timestamp) = extract_timestamp_from_session_dir(lock_file_name) else { debug!( @@ -685,19 +691,19 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { continue; }; - let lock_file_path = crate_directory.join(&**lock_file_name); + let lock_file_path = crate_directory.join(&*lock_file_name); if is_old_enough_to_be_collected(timestamp) { debug!( "garbage_collect_session_directories() - deleting \ - garbage lock file: {}", + garbage lock file: {}", lock_file_path.display() ); delete_session_dir_lock_file(sess, &lock_file_path); } else { debug!( "garbage_collect_session_directories() - lock file with \ - no session dir not old enough to be collected: {}", + no session dir not old enough to be collected: {}", lock_file_path.display() ); } @@ -705,14 +711,14 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { } // Filter out `None` directories - let lock_file_to_session_dir: FxHashMap = lock_file_to_session_dir - .into_iter() + let lock_file_to_session_dir: UnordMap = lock_file_to_session_dir + .into_items() .filter_map(|(lock_file_name, directory_name)| directory_name.map(|n| (lock_file_name, n))) - .collect(); + .into(); // Delete all session directories that don't have a lock file. for directory_name in session_directories { - if !lock_file_to_session_dir.values().any(|dir| *dir == directory_name) { + if !lock_file_to_session_dir.items().any(|(_, dir)| *dir == directory_name) { let path = crate_directory.join(directory_name); if let Err(err) = safe_remove_dir_all(&path) { sess.emit_warning(errors::InvalidGcFailed { path: &path, err }); @@ -721,103 +727,103 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { } // Now garbage collect the valid session directories. - let mut deletion_candidates = vec![]; + let deletion_candidates = + lock_file_to_session_dir.items().filter_map(|(lock_file_name, directory_name)| { + debug!("garbage_collect_session_directories() - inspecting: {}", directory_name); - for (lock_file_name, directory_name) in &lock_file_to_session_dir { - debug!("garbage_collect_session_directories() - inspecting: {}", directory_name); - - let Ok(timestamp) = extract_timestamp_from_session_dir(directory_name) else { - debug!( - "found session-dir with malformed timestamp: {}", - crate_directory.join(directory_name).display() - ); - // Ignore it - continue; - }; + let Ok(timestamp) = extract_timestamp_from_session_dir(directory_name) else { + debug!( + "found session-dir with malformed timestamp: {}", + crate_directory.join(directory_name).display() + ); + // Ignore it + return None; + }; - if is_finalized(directory_name) { - let lock_file_path = crate_directory.join(lock_file_name); - match flock::Lock::new( - &lock_file_path, - false, // don't wait - false, // don't create the lock-file - true, - ) { - // get an exclusive lock - Ok(lock) => { - debug!( - "garbage_collect_session_directories() - \ + if is_finalized(directory_name) { + let lock_file_path = crate_directory.join(lock_file_name); + match flock::Lock::new( + &lock_file_path, + false, // don't wait + false, // don't create the lock-file + true, + ) { + // get an exclusive lock + Ok(lock) => { + debug!( + "garbage_collect_session_directories() - \ successfully acquired lock" - ); - debug!( - "garbage_collect_session_directories() - adding \ + ); + debug!( + "garbage_collect_session_directories() - adding \ deletion candidate: {}", - directory_name - ); - - // Note that we are holding on to the lock - deletion_candidates.push(( - timestamp, - crate_directory.join(directory_name), - Some(lock), - )); - } - Err(_) => { - debug!( - "garbage_collect_session_directories() - \ + directory_name + ); + + // Note that we are holding on to the lock + return Some(( + (timestamp, crate_directory.join(directory_name)), + Some(lock), + )); + } + Err(_) => { + debug!( + "garbage_collect_session_directories() - \ not collecting, still in use" - ); + ); + } } - } - } else if is_old_enough_to_be_collected(timestamp) { - // When cleaning out "-working" session directories, i.e. - // session directories that might still be in use by another - // compiler instance, we only look a directories that are - // at least ten seconds old. This is supposed to reduce the - // chance of deleting a directory in the time window where - // the process has allocated the directory but has not yet - // acquired the file-lock on it. - - // Try to acquire the directory lock. If we can't, it - // means that the owning process is still alive and we - // leave this directory alone. - let lock_file_path = crate_directory.join(lock_file_name); - match flock::Lock::new( - &lock_file_path, - false, // don't wait - false, // don't create the lock-file - true, - ) { - // get an exclusive lock - Ok(lock) => { - debug!( - "garbage_collect_session_directories() - \ + } else if is_old_enough_to_be_collected(timestamp) { + // When cleaning out "-working" session directories, i.e. + // session directories that might still be in use by another + // compiler instance, we only look a directories that are + // at least ten seconds old. This is supposed to reduce the + // chance of deleting a directory in the time window where + // the process has allocated the directory but has not yet + // acquired the file-lock on it. + + // Try to acquire the directory lock. If we can't, it + // means that the owning process is still alive and we + // leave this directory alone. + let lock_file_path = crate_directory.join(lock_file_name); + match flock::Lock::new( + &lock_file_path, + false, // don't wait + false, // don't create the lock-file + true, + ) { + // get an exclusive lock + Ok(lock) => { + debug!( + "garbage_collect_session_directories() - \ successfully acquired lock" - ); + ); - delete_old(sess, &crate_directory.join(directory_name)); + delete_old(sess, &crate_directory.join(directory_name)); - // Let's make it explicit that the file lock is released at this point, - // or rather, that we held on to it until here - drop(lock); - } - Err(_) => { - debug!( - "garbage_collect_session_directories() - \ + // Let's make it explicit that the file lock is released at this point, + // or rather, that we held on to it until here + drop(lock); + } + Err(_) => { + debug!( + "garbage_collect_session_directories() - \ not collecting, still in use" - ); + ); + } } - } - } else { - debug!( - "garbage_collect_session_directories() - not finalized, not \ + } else { + debug!( + "garbage_collect_session_directories() - not finalized, not \ old enough" - ); - } - } + ); + } + None + }); + let deletion_candidates = deletion_candidates.into(); // Delete all but the most recent of the candidates - for (path, lock) in all_except_most_recent(deletion_candidates) { + all_except_most_recent(deletion_candidates).into_items().all(|(path, lock)| { debug!("garbage_collect_session_directories() - deleting `{}`", path.display()); if let Err(err) = safe_remove_dir_all(&path) { @@ -829,7 +835,8 @@ pub fn garbage_collect_session_directories(sess: &Session) -> io::Result<()> { // Let's make it explicit that the file lock is released at this point, // or rather, that we held on to it until here drop(lock); - } + true + }); Ok(()) } @@ -845,18 +852,18 @@ fn delete_old(sess: &Session, path: &Path) { } fn all_except_most_recent( - deletion_candidates: Vec<(SystemTime, PathBuf, Option)>, -) -> FxHashMap> { - let most_recent = deletion_candidates.iter().map(|&(timestamp, ..)| timestamp).max(); + deletion_candidates: UnordMap<(SystemTime, PathBuf), Option>, +) -> UnordMap> { + let most_recent = deletion_candidates.items().map(|(&(timestamp, _), _)| timestamp).max(); if let Some(most_recent) = most_recent { deletion_candidates - .into_iter() - .filter(|&(timestamp, ..)| timestamp != most_recent) - .map(|(_, path, lock)| (path, lock)) + .into_items() + .filter(|&((timestamp, _), _)| timestamp != most_recent) + .map(|((_, path), lock)| (path, lock)) .collect() } else { - FxHashMap::default() + UnordMap::default() } } diff --git a/compiler/rustc_incremental/src/persist/fs/tests.rs b/compiler/rustc_incremental/src/persist/fs/tests.rs index 184796948b67d..644b8187621c9 100644 --- a/compiler/rustc_incremental/src/persist/fs/tests.rs +++ b/compiler/rustc_incremental/src/persist/fs/tests.rs @@ -2,26 +2,19 @@ use super::*; #[test] fn test_all_except_most_recent() { + let input: UnordMap<_, Option> = UnordMap::from_iter([ + ((UNIX_EPOCH + Duration::new(4, 0), PathBuf::from("4")), None), + ((UNIX_EPOCH + Duration::new(1, 0), PathBuf::from("1")), None), + ((UNIX_EPOCH + Duration::new(5, 0), PathBuf::from("5")), None), + ((UNIX_EPOCH + Duration::new(3, 0), PathBuf::from("3")), None), + ((UNIX_EPOCH + Duration::new(2, 0), PathBuf::from("2")), None), + ]); assert_eq!( - all_except_most_recent(vec![ - (UNIX_EPOCH + Duration::new(4, 0), PathBuf::from("4"), None), - (UNIX_EPOCH + Duration::new(1, 0), PathBuf::from("1"), None), - (UNIX_EPOCH + Duration::new(5, 0), PathBuf::from("5"), None), - (UNIX_EPOCH + Duration::new(3, 0), PathBuf::from("3"), None), - (UNIX_EPOCH + Duration::new(2, 0), PathBuf::from("2"), None), - ]) - .keys() - .cloned() - .collect::>(), - [PathBuf::from("1"), PathBuf::from("2"), PathBuf::from("3"), PathBuf::from("4"),] - .into_iter() - .collect::>() + all_except_most_recent(input).into_items().map(|(path, _)| path).into_sorted_stable_ord(), + vec![PathBuf::from("1"), PathBuf::from("2"), PathBuf::from("3"), PathBuf::from("4")] ); - assert_eq!( - all_except_most_recent(vec![]).keys().cloned().collect::>(), - FxHashSet::default() - ); + assert!(all_except_most_recent(UnordMap::default()).is_empty()); } #[test] diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs index a4407a93ff3ba..8d67f69256879 100644 --- a/compiler/rustc_incremental/src/persist/load.rs +++ b/compiler/rustc_incremental/src/persist/load.rs @@ -1,9 +1,9 @@ //! Code to save/load the dep-graph from files. use crate::errors; -use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::memmap::Mmap; -use rustc_middle::dep_graph::{SerializedDepGraph, WorkProduct, WorkProductId}; +use rustc_data_structures::unord::UnordMap; +use rustc_middle::dep_graph::{SerializedDepGraph, WorkProductMap}; use rustc_middle::query::on_disk_cache::OnDiskCache; use rustc_serialize::opaque::MemDecoder; use rustc_serialize::Decodable; @@ -16,8 +16,6 @@ use super::file_format; use super::fs::*; use super::work_product; -type WorkProductMap = FxHashMap; - #[derive(Debug)] /// Represents the result of an attempt to load incremental compilation data. pub enum LoadResult { @@ -147,7 +145,7 @@ pub fn load_dep_graph(sess: &Session) -> DepGraphFuture { let report_incremental_info = sess.opts.unstable_opts.incremental_info; let expected_hash = sess.opts.dep_tracking_hash(false); - let mut prev_work_products = FxHashMap::default(); + let mut prev_work_products = UnordMap::default(); // If we are only building with -Zquery-dep-graph but without an actual // incr. comp. session directory, we skip this. Otherwise we'd fail @@ -163,7 +161,7 @@ pub fn load_dep_graph(sess: &Session) -> DepGraphFuture { Decodable::decode(&mut work_product_decoder); for swp in work_products { - let all_files_exist = swp.work_product.saved_files.iter().all(|(_, path)| { + let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| { let exists = in_incr_comp_dir_sess(sess, path).exists(); if !exists && sess.opts.unstable_opts.incremental_info { eprintln!("incremental: could not find file for work product: {path}",); diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs index 7376be6be8b8a..0cfaf5837742e 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -1,7 +1,9 @@ use crate::errors; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sync::join; -use rustc_middle::dep_graph::{DepGraph, SerializedDepGraph, WorkProduct, WorkProductId}; +use rustc_middle::dep_graph::{ + DepGraph, SerializedDepGraph, WorkProduct, WorkProductId, WorkProductMap, +}; use rustc_middle::ty::TyCtxt; use rustc_serialize::opaque::{FileEncodeResult, FileEncoder}; use rustc_serialize::Encodable as RustcEncodable; @@ -79,7 +81,7 @@ pub fn save_dep_graph(tcx: TyCtxt<'_>) { pub fn save_work_product_index( sess: &Session, dep_graph: &DepGraph, - new_work_products: FxHashMap, + new_work_products: FxIndexMap, ) { if sess.opts.incremental.is_none() { return; @@ -101,11 +103,11 @@ pub fn save_work_product_index( // deleted during invalidation. Some object files don't change their // content, they are just not needed anymore. let previous_work_products = dep_graph.previous_work_products(); - for (id, wp) in previous_work_products.iter() { + for (id, wp) in previous_work_products.to_sorted_stable_ord().iter() { if !new_work_products.contains_key(id) { work_product::delete_workproduct_files(sess, wp); debug_assert!( - !wp.saved_files.iter().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists()) + !wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists()) ); } } @@ -113,13 +115,13 @@ pub fn save_work_product_index( // Check that we did not delete one of the current work-products: debug_assert!({ new_work_products.iter().all(|(_, wp)| { - wp.saved_files.iter().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists()) + wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists()) }) }); } fn encode_work_product_index( - work_products: &FxHashMap, + work_products: &FxIndexMap, encoder: &mut FileEncoder, ) { let serialized_products: Vec<_> = work_products @@ -146,7 +148,7 @@ fn encode_query_cache(tcx: TyCtxt<'_>, encoder: FileEncoder) -> FileEncodeResult pub fn build_dep_graph( sess: &Session, prev_graph: SerializedDepGraph, - prev_work_products: FxHashMap, + prev_work_products: WorkProductMap, ) -> Option { if sess.opts.incremental.is_none() { // No incremental compilation. diff --git a/compiler/rustc_incremental/src/persist/work_product.rs b/compiler/rustc_incremental/src/persist/work_product.rs index dc98fbeb0d166..bce5ca1e16bd1 100644 --- a/compiler/rustc_incremental/src/persist/work_product.rs +++ b/compiler/rustc_incremental/src/persist/work_product.rs @@ -4,7 +4,7 @@ use crate::errors; use crate::persist::fs::*; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::unord::UnordMap; use rustc_fs_util::link_or_copy; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_session::Session; @@ -20,7 +20,7 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( debug!(?cgu_name, ?files); sess.opts.incremental.as_ref()?; - let mut saved_files = FxHashMap::default(); + let mut saved_files = UnordMap::default(); for (ext, path) in files { let file_name = format!("{cgu_name}.{ext}"); let path_in_incr_dir = in_incr_comp_dir_sess(sess, &file_name); @@ -46,7 +46,7 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( /// Removes files for a given work product. pub fn delete_workproduct_files(sess: &Session, work_product: &WorkProduct) { - for (_, path) in &work_product.saved_files { + for (_, path) in work_product.saved_files.items().into_sorted_stable_ord() { let path = in_incr_comp_dir_sess(sess, path); if let Err(err) = std_fs::remove_file(&path) { sess.emit_warning(errors::DeleteWorkProduct { path: &path, err }); diff --git a/compiler/rustc_index/src/bit_set.rs b/compiler/rustc_index/src/bit_set.rs index 15bc3b4e388dc..12a7ecf813374 100644 --- a/compiler/rustc_index/src/bit_set.rs +++ b/compiler/rustc_index/src/bit_set.rs @@ -804,9 +804,7 @@ impl<'a, T: Idx> Iterator for ChunkedBitIter<'a, T> { // advance the iterator to the start of the next chunk, before proceeding in chunk sized // steps. while self.index % CHUNK_BITS != 0 { - let Some(item) = self.next() else { - return init - }; + let Some(item) = self.next() else { return init }; init = f(init, item); } let start_chunk = self.index / CHUNK_BITS; diff --git a/compiler/rustc_index/src/lib.rs b/compiler/rustc_index/src/lib.rs index 6fd9f34b29ef1..9942c70c4ae71 100644 --- a/compiler/rustc_index/src/lib.rs +++ b/compiler/rustc_index/src/lib.rs @@ -12,6 +12,7 @@ test ) )] +#![cfg_attr(all(not(bootstrap), feature = "nightly"), allow(internal_features))] #[cfg(feature = "nightly")] pub mod bit_set; diff --git a/compiler/rustc_infer/messages.ftl b/compiler/rustc_infer/messages.ftl index fdc4ff0896f5b..4d0e770636721 100644 --- a/compiler/rustc_infer/messages.ftl +++ b/compiler/rustc_infer/messages.ftl @@ -1,285 +1,72 @@ -infer_opaque_hidden_type = - opaque type's hidden type cannot be another opaque type from the same scope - .label = one of the two opaque types used here has to be outside its defining scope - .opaque_type = opaque type whose hidden type is being assigned - .hidden_type = opaque type being used as hidden type - -infer_type_annotations_needed = {$source_kind -> - [closure] type annotations needed for the closure `{$source_name}` - [normal] type annotations needed for `{$source_name}` - *[other] type annotations needed -} - .label = type must be known at this point - -infer_label_bad = {$bad_kind -> - *[other] cannot infer type - [more_info] cannot infer {$prefix_kind -> - *[type] type for {$prefix} - [const_with_param] the value of const parameter - [const] the value of the constant - } `{$name}`{$has_parent -> - [true] {" "}declared on the {$parent_prefix} `{$parent_name}` - *[false] {""} - } -} - -infer_source_kind_subdiag_let = {$kind -> - [with_pattern] consider giving `{$name}` an explicit type - [closure] consider giving this closure parameter an explicit type - *[other] consider giving this pattern a type -}{$x_kind -> - [has_name] , where the {$prefix_kind -> - *[type] type for {$prefix} - [const_with_param] value of const parameter - [const] value of the constant - } `{$arg_name}` is specified - [underscore] , where the placeholders `_` are specified - *[empty] {""} -} - -infer_source_kind_subdiag_generic_label = - cannot infer {$is_type -> - [true] type - *[false] the value - } of the {$is_type -> - [true] type - *[false] const - } {$parent_exists -> - [true] parameter `{$param_name}` declared on the {$parent_prefix} `{$parent_name}` - *[false] parameter {$param_name} - } - -infer_source_kind_subdiag_generic_suggestion = - consider specifying the generic {$arg_count -> - [one] argument - *[other] arguments - } - -infer_source_kind_fully_qualified = - try using a fully qualified path to specify the expected types - -infer_source_kind_closure_return = - try giving this closure an explicit return type - -# generator_kind may need to be translated -infer_need_type_info_in_generator = - type inside {$generator_kind -> - [async_block] `async` block - [async_closure] `async` closure - [async_fn] `async fn` body - *[generator] generator - } must be known in this context - - -infer_subtype = ...so that the {$requirement -> - [method_compat] method type is compatible with trait - [type_compat] associated type is compatible with trait - [const_compat] const is compatible with trait - [expr_assignable] expression is assignable - [if_else_different] `if` and `else` have incompatible types - [no_else] `if` missing an `else` returns `()` - [fn_main_correct_type] `main` function has the correct type - [fn_start_correct_type] `#[start]` function has the correct type - [intrinsic_correct_type] intrinsic has the correct type - [method_correct_type] method receiver has the correct type - *[other] types are compatible -} -infer_subtype_2 = ...so that {$requirement -> - [method_compat] method type is compatible with trait - [type_compat] associated type is compatible with trait - [const_compat] const is compatible with trait - [expr_assignable] expression is assignable - [if_else_different] `if` and `else` have incompatible types - [no_else] `if` missing an `else` returns `()` - [fn_main_correct_type] `main` function has the correct type - [fn_start_correct_type] `#[start]` function has the correct type - [intrinsic_correct_type] intrinsic has the correct type - [method_correct_type] method receiver has the correct type - *[other] types are compatible -} - -infer_reborrow = ...so that reference does not outlive borrowed content -infer_reborrow_upvar = ...so that closure can access `{$name}` -infer_relate_object_bound = ...so that it can be closed over into an object -infer_reference_outlives_referent = ...so that the reference type `{$name}` does not outlive the data it points at -infer_relate_param_bound = ...so that the type `{$name}` will meet its required lifetime bounds{$continues -> - [true] ... +infer_actual_impl_expl_but_actually_implemented_for_ty = ...but `{$trait_path}` is actually implemented for the type `{$ty}`{$has_lifetime -> + [true] , for some specific lifetime `'{$lifetime}` *[false] {""} } -infer_relate_param_bound_2 = ...that is required by this bound -infer_relate_region_param_bound = ...so that the declared lifetime parameter bounds are satisfied -infer_compare_impl_item_obligation = ...so that the definition in impl matches the definition from the trait -infer_ascribe_user_type_prove_predicate = ...so that the where clause holds - -infer_nothing = {""} - -infer_lifetime_mismatch = lifetime mismatch - -infer_declared_different = this parameter and the return type are declared with different lifetimes... -infer_data_returned = ...but data{$label_var1_exists -> - [true] {" "}from `{$label_var1}` - *[false] {""} -} is returned here - -infer_data_lifetime_flow = ...but data with one lifetime flows into the other here -infer_declared_multiple = this type is declared with multiple lifetimes... -infer_types_declared_different = these two types are declared with different lifetimes... -infer_data_flows = ...but data{$label_var1_exists -> - [true] {" "}from `{$label_var1}` - *[false] -> {""} -} flows{$label_var2_exists -> - [true] {" "}into `{$label_var2}` - *[false] -> {""} -} here - -infer_lifetime_param_suggestion = consider introducing a named lifetime parameter{$is_impl -> - [true] {" "}and update trait if needed +infer_actual_impl_expl_but_actually_implements_trait = ...but it actually implements `{$trait_path}`{$has_lifetime -> + [true] , for some specific lifetime `'{$lifetime}` *[false] {""} } -infer_lifetime_param_suggestion_elided = each elided lifetime in input position becomes a distinct lifetime - -infer_region_explanation = {$pref_kind -> - *[should_not_happen] [{$pref_kind}] - [ref_valid_for] ...the reference is valid for - [content_valid_for] ...but the borrowed content is only valid for - [type_obj_valid_for] object type is valid for - [source_pointer_valid_for] source pointer is only valid for - [type_satisfy] type must satisfy - [type_outlive] type must outlive - [lf_param_instantiated_with] lifetime parameter instantiated with - [lf_param_must_outlive] but lifetime parameter must outlive - [lf_instantiated_with] lifetime instantiated with - [lf_must_outlive] but lifetime must outlive - [pointer_valid_for] the pointer is valid for - [data_valid_for] but the referenced data is only valid for - [empty] {""} -}{$pref_kind -> - [empty] {""} - *[other] {" "} -}{$desc_kind -> - *[should_not_happen] [{$desc_kind}] - [restatic] the static lifetime - [revar] lifetime {$desc_arg} - [as_defined] the lifetime `{$desc_arg}` as defined here - [as_defined_anon] the anonymous lifetime as defined here - [defined_here] the anonymous lifetime defined here - [defined_here_reg] the lifetime `{$desc_arg}` as defined here -}{$suff_kind -> - *[should_not_happen] [{$suff_kind}] - [empty]{""} - [continues] ... - [req_by_binding] {" "}as required by this binding +infer_actual_impl_expl_but_actually_ty_implements = ...but `{$ty}` actually implements `{$trait_path}`{$has_lifetime -> + [true] , for some specific lifetime `'{$lifetime}` + *[false] {""} } -infer_outlives_content = lifetime of reference outlives lifetime of borrowed content... -infer_outlives_bound = lifetime of the source pointer does not outlive lifetime bound of the object type -infer_fulfill_req_lifetime = the type `{$ty}` does not fulfill the required lifetime -infer_lf_bound_not_satisfied = lifetime bound not satisfied -infer_borrowed_too_long = a value of type `{$ty}` is borrowed for too long -infer_ref_longer_than_data = in type `{$ty}`, reference has a longer lifetime than the data it references - -infer_mismatched_static_lifetime = incompatible lifetime on type -infer_does_not_outlive_static_from_impl = ...does not necessarily outlive the static lifetime introduced by the compatible `impl` -infer_implicit_static_lifetime_note = this has an implicit `'static` lifetime requirement -infer_implicit_static_lifetime_suggestion = consider relaxing the implicit `'static` requirement -infer_msl_introduces_static = introduces a `'static` lifetime requirement -infer_msl_unmet_req = because this has an unmet lifetime requirement -infer_msl_trait_note = this has an implicit `'static` lifetime requirement -infer_msl_trait_sugg = consider relaxing the implicit `'static` requirement -infer_suggest_add_let_for_letchains = consider adding `let` - -infer_explicit_lifetime_required_with_ident = explicit lifetime required in the type of `{$simple_ident}` - .label = lifetime `{$named}` required - -infer_explicit_lifetime_required_with_param_type = explicit lifetime required in parameter type - .label = lifetime `{$named}` required - -infer_explicit_lifetime_required_sugg_with_ident = add explicit lifetime `{$named}` to the type of `{$simple_ident}` - -infer_explicit_lifetime_required_sugg_with_param_type = add explicit lifetime `{$named}` to type - -infer_actual_impl_expl_expected_signature_two = {$leading_ellipsis -> - [true] ... - *[false] {""} -}closure with signature `{$ty_or_sig}` must implement `{$trait_path}`, for any two lifetimes `'{$lifetime_1}` and `'{$lifetime_2}`... -infer_actual_impl_expl_expected_signature_any = {$leading_ellipsis -> +infer_actual_impl_expl_expected_other_any = {$leading_ellipsis -> [true] ... *[false] {""} -}closure with signature `{$ty_or_sig}` must implement `{$trait_path}`, for any lifetime `'{$lifetime_1}`... -infer_actual_impl_expl_expected_signature_some = {$leading_ellipsis -> +}`{$ty_or_sig}` must implement `{$trait_path}`, for any lifetime `'{$lifetime_1}`... +infer_actual_impl_expl_expected_other_nothing = {$leading_ellipsis -> [true] ... *[false] {""} -}closure with signature `{$ty_or_sig}` must implement `{$trait_path}`, for some specific lifetime `'{$lifetime_1}`... -infer_actual_impl_expl_expected_signature_nothing = {$leading_ellipsis -> +}`{$ty_or_sig}` must implement `{$trait_path}` + +infer_actual_impl_expl_expected_other_some = {$leading_ellipsis -> [true] ... *[false] {""} -}closure with signature `{$ty_or_sig}` must implement `{$trait_path}` -infer_actual_impl_expl_expected_passive_two = {$leading_ellipsis -> +}`{$ty_or_sig}` must implement `{$trait_path}`, for some specific lifetime `'{$lifetime_1}`... +infer_actual_impl_expl_expected_other_two = {$leading_ellipsis -> [true] ... *[false] {""} -}`{$trait_path}` would have to be implemented for the type `{$ty_or_sig}`, for any two lifetimes `'{$lifetime_1}` and `'{$lifetime_2}`... +}`{$ty_or_sig}` must implement `{$trait_path}`, for any two lifetimes `'{$lifetime_1}` and `'{$lifetime_2}`... infer_actual_impl_expl_expected_passive_any = {$leading_ellipsis -> [true] ... *[false] {""} }`{$trait_path}` would have to be implemented for the type `{$ty_or_sig}`, for any lifetime `'{$lifetime_1}`... +infer_actual_impl_expl_expected_passive_nothing = {$leading_ellipsis -> + [true] ... + *[false] {""} +}`{$trait_path}` would have to be implemented for the type `{$ty_or_sig}` infer_actual_impl_expl_expected_passive_some = {$leading_ellipsis -> [true] ... *[false] {""} }`{$trait_path}` would have to be implemented for the type `{$ty_or_sig}`, for some specific lifetime `'{$lifetime_1}`... -infer_actual_impl_expl_expected_passive_nothing = {$leading_ellipsis -> +infer_actual_impl_expl_expected_passive_two = {$leading_ellipsis -> [true] ... *[false] {""} -}`{$trait_path}` would have to be implemented for the type `{$ty_or_sig}` -infer_actual_impl_expl_expected_other_two = {$leading_ellipsis -> +}`{$trait_path}` would have to be implemented for the type `{$ty_or_sig}`, for any two lifetimes `'{$lifetime_1}` and `'{$lifetime_2}`... +infer_actual_impl_expl_expected_signature_any = {$leading_ellipsis -> [true] ... *[false] {""} -}`{$ty_or_sig}` must implement `{$trait_path}`, for any two lifetimes `'{$lifetime_1}` and `'{$lifetime_2}`... -infer_actual_impl_expl_expected_other_any = {$leading_ellipsis -> +}closure with signature `{$ty_or_sig}` must implement `{$trait_path}`, for any lifetime `'{$lifetime_1}`... +infer_actual_impl_expl_expected_signature_nothing = {$leading_ellipsis -> [true] ... *[false] {""} -}`{$ty_or_sig}` must implement `{$trait_path}`, for any lifetime `'{$lifetime_1}`... -infer_actual_impl_expl_expected_other_some = {$leading_ellipsis -> +}closure with signature `{$ty_or_sig}` must implement `{$trait_path}` +infer_actual_impl_expl_expected_signature_some = {$leading_ellipsis -> [true] ... *[false] {""} -}`{$ty_or_sig}` must implement `{$trait_path}`, for some specific lifetime `'{$lifetime_1}`... -infer_actual_impl_expl_expected_other_nothing = {$leading_ellipsis -> +}closure with signature `{$ty_or_sig}` must implement `{$trait_path}`, for some specific lifetime `'{$lifetime_1}`... +infer_actual_impl_expl_expected_signature_two = {$leading_ellipsis -> [true] ... *[false] {""} -}`{$ty_or_sig}` must implement `{$trait_path}` +}closure with signature `{$ty_or_sig}` must implement `{$trait_path}`, for any two lifetimes `'{$lifetime_1}` and `'{$lifetime_2}`... +infer_ascribe_user_type_prove_predicate = ...so that the where clause holds -infer_actual_impl_expl_but_actually_implements_trait = ...but it actually implements `{$trait_path}`{$has_lifetime -> - [true] , for some specific lifetime `'{$lifetime}` - *[false] {""} -} -infer_actual_impl_expl_but_actually_implemented_for_ty = ...but `{$trait_path}` is actually implemented for the type `{$ty}`{$has_lifetime -> - [true] , for some specific lifetime `'{$lifetime}` - *[false] {""} -} -infer_actual_impl_expl_but_actually_ty_implements = ...but `{$ty}` actually implements `{$trait_path}`{$has_lifetime -> - [true] , for some specific lifetime `'{$lifetime}` - *[false] {""} -} - -infer_trait_placeholder_mismatch = implementation of `{$trait_def_id}` is not general enough - .label_satisfy = doesn't satisfy where-clause - .label_where = due to a where-clause on `{$def_id}`... - .label_dup = implementation of `{$trait_def_id}` is not general enough - -infer_trait_impl_diff = `impl` item signature doesn't match `trait` item signature - .found = found `{$found}` - .expected = expected `{$expected}` - .expected_found = expected signature `{$expected}` - {" "}found signature `{$found}` - -infer_tid_rel_help = verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output -infer_tid_consider_borrowing = consider borrowing this type parameter in the trait -infer_tid_param_help = the lifetime requirements from the `impl` do not correspond to the requirements in the `trait` - -infer_dtcs_has_lifetime_req_label = this has an implicit `'static` lifetime requirement -infer_dtcs_introduces_requirement = calling this method introduces the `impl`'s `'static` requirement -infer_dtcs_has_req_note = the used `impl` has a `'static` requirement -infer_dtcs_suggestion = consider relaxing the implicit `'static` requirement +infer_await_both_futures = consider `await`ing on both `Future`s +infer_await_future = consider `await`ing on the `Future` +infer_await_note = calling an async function returns a future +infer_borrowed_too_long = a value of type `{$ty}` is borrowed for too long infer_but_calling_introduces = {$has_param_name -> [true] `{$param_name}` *[false] `fn` parameter @@ -314,6 +101,77 @@ infer_but_needs_to_satisfy = {$has_param_name -> .used_here = ...is used here... .introduced_by_bound = `'static` lifetime requirement introduced by this bound +infer_compare_impl_item_obligation = ...so that the definition in impl matches the definition from the trait +infer_consider_specifying_length = consider specifying the actual array length +infer_data_flows = ...but data{$label_var1_exists -> + [true] {" "}from `{$label_var1}` + *[false] -> {""} +} flows{$label_var2_exists -> + [true] {" "}into `{$label_var2}` + *[false] -> {""} +} here + +infer_data_lifetime_flow = ...but data with one lifetime flows into the other here +infer_data_returned = ...but data{$label_var1_exists -> + [true] {" "}from `{$label_var1}` + *[false] {""} +} is returned here + +infer_declared_different = this parameter and the return type are declared with different lifetimes... +infer_declared_multiple = this type is declared with multiple lifetimes... +infer_does_not_outlive_static_from_impl = ...does not necessarily outlive the static lifetime introduced by the compatible `impl` +infer_dtcs_has_lifetime_req_label = this has an implicit `'static` lifetime requirement +infer_dtcs_has_req_note = the used `impl` has a `'static` requirement +infer_dtcs_introduces_requirement = calling this method introduces the `impl`'s `'static` requirement +infer_dtcs_suggestion = consider relaxing the implicit `'static` requirement + +infer_explicit_lifetime_required_sugg_with_ident = add explicit lifetime `{$named}` to the type of `{$simple_ident}` + +infer_explicit_lifetime_required_sugg_with_param_type = add explicit lifetime `{$named}` to type + +infer_explicit_lifetime_required_with_ident = explicit lifetime required in the type of `{$simple_ident}` + .label = lifetime `{$named}` required + +infer_explicit_lifetime_required_with_param_type = explicit lifetime required in parameter type + .label = lifetime `{$named}` required + +infer_fn_consider_casting = consider casting the fn item to a fn pointer: `{$casting}` + +infer_fn_uniq_types = different fn items have unique types, even if their signatures are the same +infer_fps_cast = consider casting to a fn pointer +infer_fps_cast_both = consider casting both fn items to fn pointers using `as {$expected_sig}` + +infer_fps_items_are_distinct = fn items are distinct from fn pointers +infer_fps_remove_ref = consider removing the reference +infer_fps_use_ref = consider using a reference +infer_fulfill_req_lifetime = the type `{$ty}` does not fulfill the required lifetime +infer_implicit_static_lifetime_note = this has an implicit `'static` lifetime requirement +infer_implicit_static_lifetime_suggestion = consider relaxing the implicit `'static` requirement +infer_label_bad = {$bad_kind -> + *[other] cannot infer type + [more_info] cannot infer {$prefix_kind -> + *[type] type for {$prefix} + [const_with_param] the value of const parameter + [const] the value of the constant + } `{$name}`{$has_parent -> + [true] {" "}declared on the {$parent_prefix} `{$parent_name}` + *[false] {""} + } +} + +infer_lf_bound_not_satisfied = lifetime bound not satisfied +infer_lifetime_mismatch = lifetime mismatch + +infer_lifetime_param_suggestion = consider introducing a named lifetime parameter{$is_impl -> + [true] {" "}and update trait if needed + *[false] {""} +} +infer_lifetime_param_suggestion_elided = each elided lifetime in input position becomes a distinct lifetime + +infer_meant_byte_literal = if you meant to write a byte literal, prefix with `b` +infer_meant_char_literal = if you meant to write a `char` literal, use single quotes +infer_meant_str_literal = if you meant to write a `str` literal, use double quotes +infer_mismatched_static_lifetime = incompatible lifetime on type infer_more_targeted = {$has_param_name -> [true] `{$param_name}` *[false] `fn` parameter @@ -322,72 +180,212 @@ infer_more_targeted = {$has_param_name -> *[false] an anonymous lifetime `'_` } but calling `{$ident}` introduces an implicit `'static` lifetime requirement -infer_ril_introduced_here = `'static` requirement introduced here -infer_ril_introduced_by = requirement introduced by this return type -infer_ril_because_of = because of this returned expression -infer_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type +infer_msl_introduces_static = introduces a `'static` lifetime requirement +infer_msl_trait_note = this has an implicit `'static` lifetime requirement +infer_msl_trait_sugg = consider relaxing the implicit `'static` requirement +infer_msl_unmet_req = because this has an unmet lifetime requirement +infer_need_type_info_in_generator = + type inside {$generator_kind -> + [async_block] `async` block + [async_closure] `async` closure + [async_fn] `async fn` body + *[generator] generator + } must be known in this context -infer_where_remove = remove the `where` clause -infer_where_copy_predicates = copy the `where` clause predicates from the trait -infer_srs_remove_and_box = consider removing this semicolon and boxing the expressions -infer_srs_remove = consider removing this semicolon -infer_srs_add = consider returning the local binding `{$ident}` -infer_srs_add_one = consider returning one of these bindings +infer_nothing = {""} -infer_await_both_futures = consider `await`ing on both `Future`s -infer_await_future = consider `await`ing on the `Future` -infer_await_note = calling an async function returns a future +infer_oc_cant_coerce = cannot coerce intrinsics to function pointers +infer_oc_closure_selfref = closure/generator type that references itself +infer_oc_const_compat = const not compatible with trait +infer_oc_fn_main_correct_type = `main` function has wrong type +infer_oc_fn_start_correct_type = `#[start]` function has wrong type +infer_oc_generic = mismatched types + +infer_oc_if_else_different = `if` and `else` have incompatible types +infer_oc_intrinsic_correct_type = intrinsic has wrong type +infer_oc_match_compat = `match` arms have incompatible types +infer_oc_method_compat = method not compatible with trait +infer_oc_method_correct_type = mismatched `self` parameter type +infer_oc_no_diverge = `else` clause of `let...else` does not diverge +infer_oc_no_else = `if` may be missing an `else` clause +infer_oc_try_compat = `?` operator has incompatible types +infer_oc_type_compat = type not compatible with trait +infer_opaque_captures_lifetime = hidden type for `{$opaque_ty}` captures lifetime that does not appear in bounds + .label = opaque type defined here + +infer_opaque_hidden_type = + opaque type's hidden type cannot be another opaque type from the same scope + .label = one of the two opaque types used here has to be outside its defining scope + .opaque_type = opaque type whose hidden type is being assigned + .hidden_type = opaque type being used as hidden type +infer_outlives_bound = lifetime of the source pointer does not outlive lifetime bound of the object type +infer_outlives_content = lifetime of reference outlives lifetime of borrowed content... infer_prlf_defined_with_sub = the lifetime `{$sub_symbol}` defined here... infer_prlf_defined_without_sub = the lifetime defined here... +infer_prlf_known_limitation = this is a known limitation that will be removed in the future (see issue #100013 for more information) + infer_prlf_must_outlive_with_sup = ...must outlive the lifetime `{$sup_symbol}` defined here infer_prlf_must_outlive_without_sup = ...must outlive the lifetime defined here -infer_prlf_known_limitation = this is a known limitation that will be removed in the future (see issue #100013 for more information) +infer_reborrow = ...so that reference does not outlive borrowed content +infer_reborrow_upvar = ...so that closure can access `{$name}` +infer_ref_longer_than_data = in type `{$ty}`, reference has a longer lifetime than the data it references -infer_opaque_captures_lifetime = hidden type for `{$opaque_ty}` captures lifetime that does not appear in bounds - .label = opaque type defined here +infer_reference_outlives_referent = ...so that the reference type `{$name}` does not outlive the data it points at +infer_region_explanation = {$pref_kind -> + *[should_not_happen] [{$pref_kind}] + [ref_valid_for] ...the reference is valid for + [content_valid_for] ...but the borrowed content is only valid for + [type_obj_valid_for] object type is valid for + [source_pointer_valid_for] source pointer is only valid for + [type_satisfy] type must satisfy + [type_outlive] type must outlive + [lf_param_instantiated_with] lifetime parameter instantiated with + [lf_param_must_outlive] but lifetime parameter must outlive + [lf_instantiated_with] lifetime instantiated with + [lf_must_outlive] but lifetime must outlive + [pointer_valid_for] the pointer is valid for + [data_valid_for] but the referenced data is only valid for + [empty] {""} +}{$pref_kind -> + [empty] {""} + *[other] {" "} +}{$desc_kind -> + *[should_not_happen] [{$desc_kind}] + [restatic] the static lifetime + [revar] lifetime {$desc_arg} + [as_defined] the lifetime `{$desc_arg}` as defined here + [as_defined_anon] the anonymous lifetime as defined here + [defined_here] the anonymous lifetime defined here + [defined_here_reg] the lifetime `{$desc_arg}` as defined here +}{$suff_kind -> + *[should_not_happen] [{$suff_kind}] + [empty]{""} + [continues] ... + [req_by_binding] {" "}as required by this binding +} -infer_fps_use_ref = consider using a reference -infer_fps_remove_ref = consider removing the reference -infer_fps_cast = consider casting to a fn pointer -infer_fps_items_are_distinct = fn items are distinct from fn pointers -infer_fps_cast_both = consider casting both fn items to fn pointers using `as {$expected_sig}` +infer_relate_object_bound = ...so that it can be closed over into an object +infer_relate_param_bound = ...so that the type `{$name}` will meet its required lifetime bounds{$continues -> + [true] ... + *[false] {""} +} +infer_relate_param_bound_2 = ...that is required by this bound +infer_relate_region_param_bound = ...so that the declared lifetime parameter bounds are satisfied +infer_ril_because_of = because of this returned expression +infer_ril_introduced_by = requirement introduced by this return type +infer_ril_introduced_here = `'static` requirement introduced here +infer_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type -infer_fn_uniq_types = different fn items have unique types, even if their signatures are the same -infer_fn_consider_casting = consider casting the fn item to a fn pointer: `{$casting}` +infer_sbfrit_box_return_expr = if you change the return type to expect trait objects, box the returned expressions -infer_sarwa_option = you can convert from `&Option` to `Option<&T>` using `.as_ref()` -infer_sarwa_result = you can convert from `&Result` to `Result<&T, &E>` using `.as_ref()` +infer_sbfrit_change_return_type = you could change the return type to be a boxed trait object +infer_source_kind_closure_return = + try giving this closure an explicit return type -infer_suggest_accessing_field = you might have meant to use field `{$name}` whose type is `{$ty}` +# generator_kind may need to be translated +infer_source_kind_fully_qualified = + try using a fully qualified path to specify the expected types -infer_sbfrit_change_return_type = you could change the return type to be a boxed trait object -infer_sbfrit_box_return_expr = if you change the return type to expect trait objects, box the returned expressions +infer_source_kind_subdiag_generic_label = + cannot infer {$is_type -> + [true] type + *[false] the value + } of the {$is_type -> + [true] type + *[false] const + } {$parent_exists -> + [true] parameter `{$param_name}` declared on the {$parent_prefix} `{$parent_name}` + *[false] parameter {$param_name} + } -infer_stp_wrap_one = try wrapping the pattern in `{$variant}` +infer_source_kind_subdiag_generic_suggestion = + consider specifying the generic {$arg_count -> + [one] argument + *[other] arguments + } + +infer_source_kind_subdiag_let = {$kind -> + [with_pattern] consider giving `{$name}` an explicit type + [closure] consider giving this closure parameter an explicit type + *[other] consider giving this pattern a type +}{$x_kind -> + [has_name] , where the {$prefix_kind -> + *[type] type for {$prefix} + [const_with_param] value of const parameter + [const] value of the constant + } `{$arg_name}` is specified + [underscore] , where the placeholders `_` are specified + *[empty] {""} +} + +infer_srs_add = consider returning the local binding `{$ident}` +infer_srs_add_one = consider returning one of these bindings + +infer_srs_remove = consider removing this semicolon +infer_srs_remove_and_box = consider removing this semicolon and boxing the expressions infer_stp_wrap_many = try wrapping the pattern in a variant of `{$path}` -infer_tuple_trailing_comma = use a trailing comma to create a tuple with one element +infer_stp_wrap_one = try wrapping the pattern in `{$variant}` +infer_subtype = ...so that the {$requirement -> + [method_compat] method type is compatible with trait + [type_compat] associated type is compatible with trait + [const_compat] const is compatible with trait + [expr_assignable] expression is assignable + [if_else_different] `if` and `else` have incompatible types + [no_else] `if` missing an `else` returns `()` + [fn_main_correct_type] `main` function has the correct type + [fn_start_correct_type] `#[start]` function has the correct type + [intrinsic_correct_type] intrinsic has the correct type + [method_correct_type] method receiver has the correct type + *[other] types are compatible +} +infer_subtype_2 = ...so that {$requirement -> + [method_compat] method type is compatible with trait + [type_compat] associated type is compatible with trait + [const_compat] const is compatible with trait + [expr_assignable] expression is assignable + [if_else_different] `if` and `else` have incompatible types + [no_else] `if` missing an `else` returns `()` + [fn_main_correct_type] `main` function has the correct type + [fn_start_correct_type] `#[start]` function has the correct type + [intrinsic_correct_type] intrinsic has the correct type + [method_correct_type] method receiver has the correct type + *[other] types are compatible +} -infer_oc_method_compat = method not compatible with trait -infer_oc_type_compat = type not compatible with trait -infer_oc_const_compat = const not compatible with trait -infer_oc_try_compat = `?` operator has incompatible types -infer_oc_match_compat = `match` arms have incompatible types -infer_oc_if_else_different = `if` and `else` have incompatible types -infer_oc_no_else = `if` may be missing an `else` clause -infer_oc_no_diverge = `else` clause of `let...else` does not diverge -infer_oc_fn_main_correct_type = `main` function has wrong type -infer_oc_fn_start_correct_type = `#[start]` function has wrong type -infer_oc_intrinsic_correct_type = intrinsic has wrong type -infer_oc_method_correct_type = mismatched `self` parameter type -infer_oc_closure_selfref = closure/generator type that references itself -infer_oc_cant_coerce = cannot coerce intrinsics to function pointers -infer_oc_generic = mismatched types +infer_suggest_accessing_field = you might have meant to use field `{$name}` whose type is `{$ty}` + +infer_suggest_add_let_for_letchains = consider adding `let` + +infer_tid_consider_borrowing = consider borrowing this type parameter in the trait +infer_tid_param_help = the lifetime requirements from the `impl` do not correspond to the requirements in the `trait` + +infer_tid_rel_help = verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output +infer_trait_impl_diff = `impl` item signature doesn't match `trait` item signature + .found = found `{$found}` + .expected = expected `{$expected}` + .expected_found = expected signature `{$expected}` + {" "}found signature `{$found}` + +infer_trait_placeholder_mismatch = implementation of `{$trait_def_id}` is not general enough + .label_satisfy = doesn't satisfy where-clause + .label_where = due to a where-clause on `{$def_id}`... + .label_dup = implementation of `{$trait_def_id}` is not general enough -infer_meant_byte_literal = if you meant to write a byte literal, prefix with `b` -infer_meant_char_literal = if you meant to write a `char` literal, use single quotes -infer_meant_str_literal = if you meant to write a `str` literal, use double quotes -infer_consider_specifying_length = consider specifying the actual array length infer_try_cannot_convert = `?` operator cannot convert from `{$found}` to `{$expected}` + +infer_tuple_trailing_comma = use a trailing comma to create a tuple with one element + +infer_type_annotations_needed = {$source_kind -> + [closure] type annotations needed for the closure `{$source_name}` + [normal] type annotations needed for `{$source_name}` + *[other] type annotations needed +} + .label = type must be known at this point + +infer_types_declared_different = these two types are declared with different lifetimes... +infer_where_copy_predicates = copy the `where` clause predicates from the trait + +infer_where_remove = remove the `where` clause diff --git a/compiler/rustc_infer/src/errors/mod.rs b/compiler/rustc_infer/src/errors/mod.rs index b1e819e83f19b..a7e045e1e8946 100644 --- a/compiler/rustc_infer/src/errors/mod.rs +++ b/compiler/rustc_infer/src/errors/mod.rs @@ -210,10 +210,8 @@ impl<'a> SourceKindMultiSuggestion<'a> { _ => ("", ""), }; let (start_span, start_span_code, end_span) = match should_wrap_expr { - Some(end_span) => { - (data.span(), format!("{}{}{}{{ ", arrow, ty_info, post), Some(end_span)) - } - None => (data.span(), format!("{}{}{}", arrow, ty_info, post), None), + Some(end_span) => (data.span(), format!("{arrow}{ty_info}{post}{{ "), Some(end_span)), + None => (data.span(), format!("{arrow}{ty_info}{post}"), None), }; Self::ClosureReturn { start_span, start_span_code, end_span } } @@ -363,7 +361,8 @@ impl AddToDiagnostic for AddLifetimeParamsSuggestion<'_> { let ( hir::Ty { kind: hir::TyKind::Ref(lifetime_sub, _), .. }, hir::Ty { kind: hir::TyKind::Ref(lifetime_sup, _), .. }, - ) = (self.ty_sub, self.ty_sup) else { + ) = (self.ty_sub, self.ty_sup) + else { return false; }; @@ -403,9 +402,9 @@ impl AddToDiagnostic for AddLifetimeParamsSuggestion<'_> { debug!(?lifetime_sub.ident.span); let make_suggestion = |ident: Ident| { let sugg = if ident.name == kw::Empty { - format!("{}, ", suggestion_param_name) + format!("{suggestion_param_name}, ") } else if ident.name == kw::UnderscoreLifetime && ident.span.is_empty() { - format!("{} ", suggestion_param_name) + format!("{suggestion_param_name} ") } else { suggestion_param_name.clone() }; @@ -418,9 +417,9 @@ impl AddToDiagnostic for AddLifetimeParamsSuggestion<'_> { let new_param_suggestion = if let Some(first) = generics.params.iter().find(|p| !p.name.ident().span.is_empty()) { - (first.span.shrink_to_lo(), format!("{}, ", suggestion_param_name)) + (first.span.shrink_to_lo(), format!("{suggestion_param_name}, ")) } else { - (generics.span, format!("<{}>", suggestion_param_name)) + (generics.span, format!("<{suggestion_param_name}>")) }; suggestions.push(new_param_suggestion); @@ -1246,30 +1245,6 @@ pub struct FnConsiderCasting { pub casting: String, } -#[derive(Subdiagnostic)] -pub enum SuggestAsRefWhereAppropriate<'a> { - #[suggestion( - infer_sarwa_option, - code = "{snippet}.as_ref()", - applicability = "machine-applicable" - )] - Option { - #[primary_span] - span: Span, - snippet: &'a str, - }, - #[suggestion( - infer_sarwa_result, - code = "{snippet}.as_ref()", - applicability = "machine-applicable" - )] - Result { - #[primary_span] - span: Span, - snippet: &'a str, - }, -} - #[derive(Subdiagnostic)] pub enum SuggestAccessingField<'a> { #[suggestion( @@ -1343,7 +1318,7 @@ impl AddToDiagnostic for SuggestTuplePatternMany { message, self.compatible_variants.into_iter().map(|variant| { vec![ - (self.cause_span.shrink_to_lo(), format!("{}(", variant)), + (self.cause_span.shrink_to_lo(), format!("{variant}(")), (self.cause_span.shrink_to_hi(), ")".to_string()), ] }), diff --git a/compiler/rustc_infer/src/errors/note_and_explain.rs b/compiler/rustc_infer/src/errors/note_and_explain.rs index 7328241dfbcaf..bd168f047faf5 100644 --- a/compiler/rustc_infer/src/errors/note_and_explain.rs +++ b/compiler/rustc_infer/src/errors/note_and_explain.rs @@ -80,7 +80,7 @@ impl<'a> DescriptionCtx<'a> { // We shouldn't really be having unification failures with ReVar // and ReLateBound though. ty::ReVar(_) | ty::ReLateBound(..) | ty::ReErased => { - (alt_span, "revar", format!("{:?}", region)) + (alt_span, "revar", format!("{region:?}")) } }; Some(DescriptionCtx { span, kind, arg }) diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs index 0c8854e962abb..6d5db3336cfbf 100644 --- a/compiler/rustc_infer/src/infer/at.rs +++ b/compiler/rustc_infer/src/infer/at.rs @@ -40,6 +40,7 @@ pub enum DefineOpaqueTypes { No, } +#[derive(Clone, Copy)] pub struct At<'a, 'tcx> { pub infcx: &'a InferCtxt<'tcx>, pub cause: &'a ObligationCause<'tcx>, @@ -70,8 +71,8 @@ impl<'tcx> InferCtxt<'tcx> { tcx: self.tcx, defining_use_anchor: self.defining_use_anchor, considering_regions: self.considering_regions, + skip_leak_check: self.skip_leak_check, inner: self.inner.clone(), - skip_leak_check: self.skip_leak_check.clone(), lexical_region_resolutions: self.lexical_region_resolutions.clone(), selection_cache: self.selection_cache.clone(), evaluation_cache: self.evaluation_cache.clone(), @@ -79,9 +80,9 @@ impl<'tcx> InferCtxt<'tcx> { reported_closure_mismatch: self.reported_closure_mismatch.clone(), tainted_by_errors: self.tainted_by_errors.clone(), err_count_on_creation: self.err_count_on_creation, - in_snapshot: self.in_snapshot.clone(), universe: self.universe.clone(), intercrate: self.intercrate, + next_trait_solver: self.next_trait_solver, } } } @@ -480,3 +481,31 @@ impl<'tcx> ToTrace<'tcx> for ty::FnSig<'tcx> { TypeTrace { cause: cause.clone(), values: Sigs(ExpectedFound::new(a_is_expected, a, b)) } } } + +impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialTraitRef<'tcx> { + fn to_trace( + cause: &ObligationCause<'tcx>, + a_is_expected: bool, + a: Self, + b: Self, + ) -> TypeTrace<'tcx> { + TypeTrace { + cause: cause.clone(), + values: ExistentialTraitRef(ExpectedFound::new(a_is_expected, a, b)), + } + } +} + +impl<'tcx> ToTrace<'tcx> for ty::PolyExistentialProjection<'tcx> { + fn to_trace( + cause: &ObligationCause<'tcx>, + a_is_expected: bool, + a: Self, + b: Self, + ) -> TypeTrace<'tcx> { + TypeTrace { + cause: cause.clone(), + values: ExistentialProjection(ExpectedFound::new(a_is_expected, a, b)), + } + } +} diff --git a/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs b/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs index 427d05c8b4da7..9d7a9fefd0867 100644 --- a/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs +++ b/compiler/rustc_infer/src/infer/canonical/canonicalizer.rs @@ -11,7 +11,7 @@ use crate::infer::canonical::{ use crate::infer::InferCtxt; use rustc_middle::ty::flags::FlagComputation; use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; -use rustc_middle::ty::subst::GenericArg; +use rustc_middle::ty::GenericArg; use rustc_middle::ty::{self, BoundVar, InferConst, List, Ty, TyCtxt, TypeFlags, TypeVisitableExt}; use std::sync::atomic::Ordering; @@ -205,7 +205,7 @@ impl CanonicalizeMode for CanonicalizeQueryResponse { // `delay_span_bug` to allow type error over an ICE. canonicalizer.tcx.sess.delay_span_bug( rustc_span::DUMMY_SP, - format!("unexpected region in query response: `{:?}`", r), + format!("unexpected region in query response: `{r:?}`"), ); r } @@ -382,7 +382,7 @@ impl<'cx, 'tcx> TypeFolder> for Canonicalizer<'cx, 'tcx> { // any equated inference vars correctly! let root_vid = self.infcx.root_var(vid); if root_vid != vid { - t = self.infcx.tcx.mk_ty_var(root_vid); + t = Ty::new_var(self.infcx.tcx, root_vid); vid = root_vid; } @@ -497,7 +497,7 @@ impl<'cx, 'tcx> TypeFolder> for Canonicalizer<'cx, 'tcx> { // any equated inference vars correctly! let root_vid = self.infcx.root_const_var(vid); if root_vid != vid { - ct = self.infcx.tcx.mk_const(ty::InferConst::Var(root_vid), ct.ty()); + ct = ty::Const::new_var(self.infcx.tcx, root_vid, ct.ty()); vid = root_vid; } @@ -562,15 +562,9 @@ impl<'cx, 'tcx> Canonicalizer<'cx, 'tcx> { V: TypeFoldable>, { let needs_canonical_flags = if canonicalize_region_mode.any() { - TypeFlags::HAS_INFER | - TypeFlags::HAS_FREE_REGIONS | // `HAS_RE_PLACEHOLDER` implies `HAS_FREE_REGIONS` - TypeFlags::HAS_TY_PLACEHOLDER | - TypeFlags::HAS_CT_PLACEHOLDER + TypeFlags::HAS_INFER | TypeFlags::HAS_PLACEHOLDER | TypeFlags::HAS_FREE_REGIONS } else { - TypeFlags::HAS_INFER - | TypeFlags::HAS_RE_PLACEHOLDER - | TypeFlags::HAS_TY_PLACEHOLDER - | TypeFlags::HAS_CT_PLACEHOLDER + TypeFlags::HAS_INFER | TypeFlags::HAS_PLACEHOLDER }; // Fast path: nothing that needs to be canonicalized. @@ -771,7 +765,7 @@ impl<'cx, 'tcx> Canonicalizer<'cx, 'tcx> { ) -> ty::Region<'tcx> { let var = self.canonical_var(info, r.into()); let br = ty::BoundRegion { var, kind: ty::BrAnon(None) }; - self.interner().mk_re_late_bound(self.binder_index, br) + ty::Region::new_late_bound(self.interner(), self.binder_index, br) } /// Given a type variable `ty_var` of the given kind, first check @@ -785,7 +779,7 @@ impl<'cx, 'tcx> Canonicalizer<'cx, 'tcx> { self.fold_ty(bound_to) } else { let var = self.canonical_var(info, ty_var.into()); - self.interner().mk_bound(self.binder_index, var.into()) + Ty::new_bound(self.tcx, self.binder_index, var.into()) } } @@ -804,10 +798,7 @@ impl<'cx, 'tcx> Canonicalizer<'cx, 'tcx> { self.fold_const(bound_to) } else { let var = self.canonical_var(info, const_var.into()); - self.interner().mk_const( - ty::ConstKind::Bound(self.binder_index, var), - self.fold_ty(const_var.ty()), - ) + ty::Const::new_bound(self.tcx, self.binder_index, var, self.fold_ty(const_var.ty())) } } } diff --git a/compiler/rustc_infer/src/infer/canonical/mod.rs b/compiler/rustc_infer/src/infer/canonical/mod.rs index 2abdd5b0aec81..8ca2e40304345 100644 --- a/compiler/rustc_infer/src/infer/canonical/mod.rs +++ b/compiler/rustc_infer/src/infer/canonical/mod.rs @@ -25,8 +25,8 @@ use crate::infer::{ConstVariableOrigin, ConstVariableOriginKind}; use crate::infer::{InferCtxt, RegionVariableOrigin, TypeVariableOrigin, TypeVariableOriginKind}; use rustc_index::IndexVec; use rustc_middle::ty::fold::TypeFoldable; -use rustc_middle::ty::subst::GenericArg; -use rustc_middle::ty::{self, List, TyCtxt}; +use rustc_middle::ty::GenericArg; +use rustc_middle::ty::{self, List, Ty, TyCtxt}; use rustc_span::source_map::Span; pub use rustc_middle::infer::canonical::*; @@ -88,7 +88,7 @@ impl<'tcx> InferCtxt<'tcx> { universe_map: impl Fn(ty::UniverseIndex) -> ty::UniverseIndex, ) -> CanonicalVarValues<'tcx> { CanonicalVarValues { - var_values: self.tcx.mk_substs_from_iter( + var_values: self.tcx.mk_args_from_iter( variables .iter() .map(|info| self.instantiate_canonical_var(span, info, &universe_map)), @@ -128,7 +128,7 @@ impl<'tcx> InferCtxt<'tcx> { CanonicalVarKind::PlaceholderTy(ty::PlaceholderType { universe, bound }) => { let universe_mapped = universe_map(universe); let placeholder_mapped = ty::PlaceholderType { universe: universe_mapped, bound }; - self.tcx.mk_placeholder(placeholder_mapped).into() + Ty::new_placeholder(self.tcx, placeholder_mapped).into() } CanonicalVarKind::Region(ui) => self @@ -141,7 +141,7 @@ impl<'tcx> InferCtxt<'tcx> { CanonicalVarKind::PlaceholderRegion(ty::PlaceholderRegion { universe, bound }) => { let universe_mapped = universe_map(universe); let placeholder_mapped = ty::PlaceholderRegion { universe: universe_mapped, bound }; - self.tcx.mk_re_placeholder(placeholder_mapped).into() + ty::Region::new_placeholder(self.tcx, placeholder_mapped).into() } CanonicalVarKind::Const(ui, ty) => self @@ -155,7 +155,7 @@ impl<'tcx> InferCtxt<'tcx> { CanonicalVarKind::PlaceholderConst(ty::PlaceholderConst { universe, bound }, ty) => { let universe_mapped = universe_map(universe); let placeholder_mapped = ty::PlaceholderConst { universe: universe_mapped, bound }; - self.tcx.mk_const(placeholder_mapped, ty).into() + ty::Const::new_placeholder(self.tcx, placeholder_mapped, ty).into() } } } diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs index de9afbbcaabbd..ed101082130c6 100644 --- a/compiler/rustc_infer/src/infer/canonical/query_response.rs +++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs @@ -15,7 +15,7 @@ use crate::infer::canonical::{ use crate::infer::nll_relate::{TypeRelating, TypeRelatingDelegate}; use crate::infer::region_constraints::{Constraint, RegionConstraintData}; use crate::infer::{DefineOpaqueTypes, InferCtxt, InferOk, InferResult, NllRegionVariableOrigin}; -use crate::traits::query::{Fallible, NoSolution}; +use crate::traits::query::NoSolution; use crate::traits::{Obligation, ObligationCause, PredicateObligation}; use crate::traits::{PredicateObligations, TraitEngine, TraitEngineExt}; use rustc_data_structures::captures::Captures; @@ -25,8 +25,8 @@ use rustc_middle::arena::ArenaAllocatable; use rustc_middle::mir::ConstraintCategory; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::relate::TypeRelation; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; use rustc_middle::ty::{self, BoundVar, ToPredicate, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_span::{Span, Symbol}; use std::fmt::Debug; use std::iter; @@ -57,7 +57,7 @@ impl<'tcx> InferCtxt<'tcx> { inference_vars: CanonicalVarValues<'tcx>, answer: T, fulfill_cx: &mut dyn TraitEngine<'tcx>, - ) -> Fallible> + ) -> Result, NoSolution> where T: Debug + TypeFoldable>, Canonical<'tcx, QueryResponse<'tcx, T>>: ArenaAllocatable<'tcx>, @@ -484,7 +484,7 @@ impl<'tcx> InferCtxt<'tcx> { // given variable in the loop above, use that. Otherwise, use // a fresh inference variable. let result_subst = CanonicalVarValues { - var_values: self.tcx.mk_substs_from_iter( + var_values: self.tcx.mk_args_from_iter( query_response.variables.iter().enumerate().map(|(index, info)| { if info.is_existential() { match opt_values[BoundVar::new(index)] { @@ -520,7 +520,7 @@ impl<'tcx> InferCtxt<'tcx> { self.at(cause, param_env) .eq( DefineOpaqueTypes::Yes, - self.tcx.mk_opaque(a.def_id.to_def_id(), a.substs), + Ty::new_opaque(self.tcx, a.def_id.to_def_id(), a.args), b, )? .obligations, @@ -584,12 +584,12 @@ impl<'tcx> InferCtxt<'tcx> { let ty::OutlivesPredicate(k1, r2) = predicate; let atom = match k1.unpack() { - GenericArgKind::Lifetime(r1) => { - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate(r1, r2))) - } - GenericArgKind::Type(t1) => { - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(t1, r2))) - } + GenericArgKind::Lifetime(r1) => ty::PredicateKind::Clause( + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(r1, r2)), + ), + GenericArgKind::Type(t1) => ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives( + ty::OutlivesPredicate(t1, r2), + )), GenericArgKind::Const(..) => { // Consts cannot outlive one another, so we don't expect to // encounter this branch. @@ -668,14 +668,15 @@ pub fn make_query_region_constraints<'tcx>( let constraint = match *k { // Swap regions because we are going from sub (<=) to outlives // (>=). - Constraint::VarSubVar(v1, v2) => { - ty::OutlivesPredicate(tcx.mk_re_var(v2).into(), tcx.mk_re_var(v1)) - } + Constraint::VarSubVar(v1, v2) => ty::OutlivesPredicate( + ty::Region::new_var(tcx, v2).into(), + ty::Region::new_var(tcx, v1), + ), Constraint::VarSubReg(v1, r2) => { - ty::OutlivesPredicate(r2.into(), tcx.mk_re_var(v1)) + ty::OutlivesPredicate(r2.into(), ty::Region::new_var(tcx, v1)) } Constraint::RegSubVar(r1, v2) => { - ty::OutlivesPredicate(tcx.mk_re_var(v2).into(), r1) + ty::OutlivesPredicate(ty::Region::new_var(tcx, v2).into(), r1) } Constraint::RegSubReg(r1, r2) => ty::OutlivesPredicate(r2.into(), r1), }; @@ -719,7 +720,7 @@ impl<'tcx> TypeRelatingDelegate<'tcx> for QueryTypeRelatingDelegate<'_, 'tcx> { } fn next_placeholder_region(&mut self, placeholder: ty::PlaceholderRegion) -> ty::Region<'tcx> { - self.infcx.tcx.mk_re_placeholder(placeholder) + ty::Region::new_placeholder(self.infcx.tcx, placeholder) } fn generalize_existential(&mut self, universe: ty::UniverseIndex) -> ty::Region<'tcx> { @@ -738,10 +739,8 @@ impl<'tcx> TypeRelatingDelegate<'tcx> for QueryTypeRelatingDelegate<'_, 'tcx> { self.obligations.push(Obligation { cause: self.cause.clone(), param_env: self.param_env, - predicate: ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::RegionOutlives( - ty::OutlivesPredicate(sup, sub), - ))) - .to_predicate(self.infcx.tcx), + predicate: ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(sup, sub)) + .to_predicate(self.infcx.tcx), recursion_depth: 0, }); } diff --git a/compiler/rustc_infer/src/infer/canonical/substitute.rs b/compiler/rustc_infer/src/infer/canonical/substitute.rs index cac3b40725158..f368b30fbd193 100644 --- a/compiler/rustc_infer/src/infer/canonical/substitute.rs +++ b/compiler/rustc_infer/src/infer/canonical/substitute.rs @@ -8,7 +8,7 @@ use crate::infer::canonical::{Canonical, CanonicalVarValues}; use rustc_middle::ty::fold::{FnMutDelegate, TypeFoldable}; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::GenericArgKind; use rustc_middle::ty::{self, TyCtxt}; /// FIXME(-Ztrait-solver=next): This or public because it is shared with the diff --git a/compiler/rustc_infer/src/infer/combine.rs b/compiler/rustc_infer/src/infer/combine.rs index b6b935de68c84..ddc8e7e50eb48 100644 --- a/compiler/rustc_infer/src/infer/combine.rs +++ b/compiler/rustc_infer/src/infer/combine.rs @@ -34,7 +34,7 @@ use rustc_middle::infer::unify_key::{ConstVarValue, ConstVariableValue}; use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::relate::{RelateResult, TypeRelation}; -use rustc_middle::ty::{self, AliasKind, InferConst, ToPredicate, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, InferConst, ToPredicate, Ty, TyCtxt, TypeVisitableExt}; use rustc_middle::ty::{IntType, UintType}; use rustc_span::DUMMY_SP; @@ -103,17 +103,17 @@ impl<'tcx> InferCtxt<'tcx> { // We don't expect `TyVar` or `Fresh*` vars at this point with lazy norm. ( - ty::Alias(AliasKind::Projection, _), + ty::Alias(..), ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)), ) | ( ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)), - ty::Alias(AliasKind::Projection, _), - ) if self.tcx.trait_solver_next() => { + ty::Alias(..), + ) if self.next_trait_solver() => { bug!() } - (_, ty::Alias(..)) | (ty::Alias(..), _) if self.tcx.trait_solver_next() => { + (_, ty::Alias(..)) | (ty::Alias(..), _) if self.next_trait_solver() => { relation.register_type_relate_obligation(a, b); Ok(a) } @@ -124,13 +124,10 @@ impl<'tcx> InferCtxt<'tcx> { } // During coherence, opaque types should be treated as *possibly* - // equal to each other, even if their generic params differ, as - // they could resolve to the same hidden type, even for different - // generic params. - ( - &ty::Alias(ty::Opaque, ty::AliasTy { def_id: a_def_id, .. }), - &ty::Alias(ty::Opaque, ty::AliasTy { def_id: b_def_id, .. }), - ) if self.intercrate && a_def_id == b_def_id => { + // equal to any other type (except for possibly itself). This is an + // extremely heavy hammer, but can be relaxed in a fowards-compatible + // way later. + (&ty::Alias(ty::Opaque, _), _) | (_, &ty::Alias(ty::Opaque, _)) if self.intercrate => { relation.register_predicates([ty::Binder::dummy(ty::PredicateKind::Ambiguous)]); Ok(a) } @@ -180,7 +177,7 @@ impl<'tcx> InferCtxt<'tcx> { self.tcx.check_tys_might_be_eq(canonical).map_err(|_| { self.tcx.sess.delay_span_bug( DUMMY_SP, - format!("cannot relate consts of different types (a={:?}, b={:?})", a, b,), + format!("cannot relate consts of different types (a={a:?}, b={b:?})",), ) }) }); @@ -192,11 +189,11 @@ impl<'tcx> InferCtxt<'tcx> { // HACK: equating both sides with `[const error]` eagerly prevents us // from leaving unconstrained inference vars during things like impl // matching in the solver. - let a_error = self.tcx.const_error(a.ty(), guar); + let a_error = ty::Const::new_error(self.tcx, guar, a.ty()); if let ty::ConstKind::Infer(InferConst::Var(vid)) = a.kind() { return self.unify_const_variable(vid, a_error, relation.param_env()); } - let b_error = self.tcx.const_error(b.ty(), guar); + let b_error = ty::Const::new_error(self.tcx, guar, b.ty()); if let ty::ConstKind::Infer(InferConst::Var(vid)) = b.kind() { return self.unify_const_variable(vid, b_error, relation.param_env()); } @@ -227,9 +224,20 @@ impl<'tcx> InferCtxt<'tcx> { return self.unify_const_variable(vid, a, relation.param_env()); } (ty::ConstKind::Unevaluated(..), _) | (_, ty::ConstKind::Unevaluated(..)) - if self.tcx.lazy_normalization() => + if self.tcx.features().generic_const_exprs || self.next_trait_solver() => { - relation.register_const_equate_obligation(a, b); + let (a, b) = if relation.a_is_expected() { (a, b) } else { (b, a) }; + + relation.register_predicates([ty::Binder::dummy(if self.next_trait_solver() { + ty::PredicateKind::AliasRelate( + a.into(), + b.into(), + ty::AliasRelationDirection::Equate, + ) + } else { + ty::PredicateKind::ConstEquate(a, b) + })]); + return Ok(b); } _ => {} @@ -246,7 +254,7 @@ impl<'tcx> InferCtxt<'tcx> { /// in `ct` with `ct` itself. /// /// This is especially important as unevaluated consts use their parents generics. - /// They therefore often contain unused substs, making these errors far more likely. + /// They therefore often contain unused args, making these errors far more likely. /// /// A good example of this is the following: /// @@ -264,12 +272,12 @@ impl<'tcx> InferCtxt<'tcx> { /// ``` /// /// Here `3 + 4` ends up as `ConstKind::Unevaluated` which uses the generics - /// of `fn bind` (meaning that its substs contain `N`). + /// of `fn bind` (meaning that its args contain `N`). /// /// `bind(arr)` now infers that the type of `arr` must be `[u8; N]`. /// The assignment `arr = bind(arr)` now tries to equate `N` with `3 + 4`. /// - /// As `3 + 4` contains `N` in its substs, this must not succeed. + /// As `3 + 4` contains `N` in its args, this must not succeed. /// /// See `tests/ui/const-generics/occurs-check/` for more examples where this is relevant. #[instrument(level = "debug", skip(self))] @@ -314,8 +322,8 @@ impl<'tcx> InferCtxt<'tcx> { .unify_var_value(vid, Some(val)) .map_err(|e| int_unification_error(vid_is_expected, e))?; match val { - IntType(v) => Ok(self.tcx.mk_mach_int(v)), - UintType(v) => Ok(self.tcx.mk_mach_uint(v)), + IntType(v) => Ok(Ty::new_int(self.tcx, v)), + UintType(v) => Ok(Ty::new_uint(self.tcx, v)), } } @@ -330,7 +338,7 @@ impl<'tcx> InferCtxt<'tcx> { .float_unification_table() .unify_var_value(vid, Some(ty::FloatVarValue(val))) .map_err(|e| float_unification_error(vid_is_expected, e))?; - Ok(self.tcx.mk_mach_float(val)) + Ok(Ty::new_float(self.tcx, val)) } } @@ -406,7 +414,9 @@ impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> { self.tcx(), self.trace.cause.clone(), self.param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(b_ty.into())), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + b_ty.into(), + ))), )); } @@ -453,19 +463,6 @@ pub trait ObligationEmittingRelation<'tcx>: TypeRelation<'tcx> { /// be used if control over the obligation causes is required. fn register_predicates(&mut self, obligations: impl IntoIterator>); - /// Register an obligation that both constants must be equal to each other. - /// - /// If they aren't equal then the relation doesn't hold. - fn register_const_equate_obligation(&mut self, a: ty::Const<'tcx>, b: ty::Const<'tcx>) { - let (a, b) = if self.a_is_expected() { (a, b) } else { (b, a) }; - - self.register_predicates([ty::Binder::dummy(if self.tcx().trait_solver_next() { - ty::PredicateKind::AliasRelate(a.into(), b.into(), ty::AliasRelationDirection::Equate) - } else { - ty::PredicateKind::ConstEquate(a, b) - })]); - } - /// Register an obligation that both types must be related to each other according to /// the [`ty::AliasRelationDirection`] given by [`ObligationEmittingRelation::alias_relate_direction`] fn register_type_relate_obligation(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) { diff --git a/compiler/rustc_infer/src/infer/equate.rs b/compiler/rustc_infer/src/infer/equate.rs index 42dfe4f6bb812..1dbab48fd6ceb 100644 --- a/compiler/rustc_infer/src/infer/equate.rs +++ b/compiler/rustc_infer/src/infer/equate.rs @@ -5,7 +5,7 @@ use super::combine::{CombineFields, ObligationEmittingRelation}; use super::Subtype; use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::TyVar; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; @@ -43,12 +43,12 @@ impl<'tcx> TypeRelation<'tcx> for Equate<'_, '_, 'tcx> { self.a_is_expected } - fn relate_item_substs( + fn relate_item_args( &mut self, _item_def_id: DefId, - a_subst: SubstsRef<'tcx>, - b_subst: SubstsRef<'tcx>, - ) -> RelateResult<'tcx, SubstsRef<'tcx>> { + a_arg: GenericArgsRef<'tcx>, + b_arg: GenericArgsRef<'tcx>, + ) -> RelateResult<'tcx, GenericArgsRef<'tcx>> { // N.B., once we are equating types, we don't care about // variance, so don't try to lookup the variance here. This // also avoids some cycles (e.g., #41849) since looking up @@ -56,7 +56,7 @@ impl<'tcx> TypeRelation<'tcx> for Equate<'_, '_, 'tcx> { // performing trait matching (which then performs equality // unification). - relate::relate_substs(self, a_subst, b_subst) + relate::relate_args(self, a_arg, b_arg) } fn relate_with_variance>( @@ -105,7 +105,7 @@ impl<'tcx> TypeRelation<'tcx> for Equate<'_, '_, 'tcx> { | (_, &ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. })) if self.fields.define_opaque_types == DefineOpaqueTypes::Yes && def_id.is_local() - && !self.tcx().trait_solver_next() => + && !self.fields.infcx.next_trait_solver() => { self.fields.obligations.extend( infcx diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 35c05e80badeb..ac5468f3dfd0f 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -76,6 +76,7 @@ use rustc_middle::ty::{ }; use rustc_span::{sym, symbol::kw, BytePos, DesugaringKind, Pos, Span}; use rustc_target::spec::abi; +use std::borrow::Cow; use std::ops::{ControlFlow, Deref}; use std::path::PathBuf; use std::{cmp, fmt, iter}; @@ -237,7 +238,7 @@ fn msg_span_from_named_region<'tcx>( let text = if name == kw::UnderscoreLifetime { "the anonymous lifetime as defined here".to_string() } else { - format!("the lifetime `{}` as defined here", name) + format!("the lifetime `{name}` as defined here") }; (text, Some(span)) } @@ -249,7 +250,7 @@ fn msg_span_from_named_region<'tcx>( }) ), _ => ( - format!("the lifetime `{}` as defined here", region), + format!("the lifetime `{region}` as defined here"), Some(tcx.def_span(scope)), ), } @@ -263,11 +264,11 @@ fn msg_span_from_named_region<'tcx>( ty::RePlaceholder(ty::PlaceholderRegion { bound: ty::BoundRegion { kind: ty::BoundRegionKind::BrAnon(Some(span)), .. }, .. - }) => (format!("the anonymous lifetime defined here"), Some(span)), + }) => ("the anonymous lifetime defined here".to_owned(), Some(span)), ty::RePlaceholder(ty::PlaceholderRegion { bound: ty::BoundRegion { kind: ty::BoundRegionKind::BrAnon(None), .. }, .. - }) => (format!("an anonymous lifetime"), None), + }) => ("an anonymous lifetime".to_owned(), None), _ => bug!("{:?}", region), } } @@ -279,7 +280,7 @@ fn emit_msg_span( span: Option, suffix: &str, ) { - let message = format!("{}{}{}", prefix, description, suffix); + let message = format!("{prefix}{description}{suffix}"); if let Some(span) = span { err.span_note(span, message); @@ -295,7 +296,7 @@ fn label_msg_span( span: Option, suffix: &str, ) { - let message = format!("{}{}{}", prefix, description, suffix); + let message = format!("{prefix}{description}{suffix}"); if let Some(span) = span { err.span_label(span, message); @@ -314,7 +315,7 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { let mut err = tcx.sess.create_err(errors::OpaqueCapturesLifetime { span, - opaque_ty: tcx.mk_opaque(opaque_ty_key.def_id.to_def_id(), opaque_ty_key.substs), + opaque_ty: Ty::new_opaque(tcx, opaque_ty_key.def_id.to_def_id(), opaque_ty_key.args), opaque_ty_span: tcx.def_span(opaque_ty_key.def_id), }); @@ -332,7 +333,7 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( explain_free_region( tcx, &mut err, - &format!("hidden type `{}` captures ", hidden_ty), + &format!("hidden type `{hidden_ty}` captures "), hidden_region, "", ); @@ -344,12 +345,21 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( fn_returns, hidden_region.to_string(), None, - format!("captures `{}`", hidden_region), + format!("captures `{hidden_region}`"), None, Some(reg_info.def_id), ) } } + ty::RePlaceholder(_) => { + explain_free_region( + tcx, + &mut err, + &format!("hidden type `{}` captures ", hidden_ty), + hidden_region, + "", + ); + } ty::ReError(_) => { err.delay_as_bug(); } @@ -372,7 +382,7 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( note_and_explain_region( tcx, &mut err, - &format!("hidden type `{}` captures ", hidden_ty), + &format!("hidden type `{hidden_ty}` captures "), hidden_region, "", None, @@ -385,16 +395,16 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( impl<'tcx> InferCtxt<'tcx> { pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option> { - let (def_id, substs) = match *ty.kind() { - ty::Alias(_, ty::AliasTy { def_id, substs, .. }) + let (def_id, args) = match *ty.kind() { + ty::Alias(_, ty::AliasTy { def_id, args, .. }) if matches!(self.tcx.def_kind(def_id), DefKind::OpaqueTy) => { - (def_id, substs) + (def_id, args) } - ty::Alias(_, ty::AliasTy { def_id, substs, .. }) + ty::Alias(_, ty::AliasTy { def_id, args, .. }) if self.tcx.is_impl_trait_in_trait(def_id) => { - (def_id, substs) + (def_id, args) } _ => return None, }; @@ -402,12 +412,12 @@ impl<'tcx> InferCtxt<'tcx> { let future_trait = self.tcx.require_lang_item(LangItem::Future, None); let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; - self.tcx.explicit_item_bounds(def_id).subst_iter_copied(self.tcx, substs).find_map( + self.tcx.explicit_item_bounds(def_id).iter_instantiated_copied(self.tcx, args).find_map( |(predicate, _)| { predicate .kind() .map_bound(|kind| match kind { - ty::PredicateKind::Clause(ty::Clause::Projection(projection_predicate)) + ty::ClauseKind::Projection(projection_predicate) if projection_predicate.projection_ty.def_id == item_def_id => { projection_predicate.term.ty() @@ -572,7 +582,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { use hir::def_id::CrateNum; use rustc_hir::definitions::DisambiguatedDefPathData; use ty::print::Printer; - use ty::subst::GenericArg; + use ty::GenericArg; struct AbsolutePathPrinter<'tcx> { tcx: TyCtxt<'tcx>, @@ -710,12 +720,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { { // don't show type `_` if span.desugaring_kind() == Some(DesugaringKind::ForLoop) - && let ty::Adt(def, substs) = ty.kind() + && let ty::Adt(def, args) = ty.kind() && Some(def.did()) == self.tcx.get_diagnostic_item(sym::Option) { - err.span_label(span, format!("this is an iterator with items of type `{}`", substs.type_at(0))); + err.span_label(span, format!("this is an iterator with items of type `{}`", args.type_at(0))); } else { - err.span_label(span, format!("this expression has type `{}`", ty)); + err.span_label(span, format!("this expression has type `{ty}`")); } } if let Some(ty::error::ExpectedFound { found, .. }) = exp_found @@ -725,7 +735,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { err.span_suggestion( span, "consider dereferencing the boxed value", - format!("*{}", snippet), + format!("*{snippet}"), Applicability::MachineApplicable, ); } @@ -733,6 +743,35 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { ObligationCauseCode::Pattern { origin_expr: false, span: Some(span), .. } => { err.span_label(span, "expected due to this"); } + ObligationCauseCode::BlockTailExpression( + _, + hir::MatchSource::TryDesugar(scrut_hir_id), + ) => { + if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found { + let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id); + let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind { + let arg_expr = args.first().expect("try desugaring call w/out arg"); + self.typeck_results.as_ref().and_then(|typeck_results| { + typeck_results.expr_ty_opt(arg_expr) + }) + } else { + bug!("try desugaring w/out call expr as scrutinee"); + }; + + match scrut_ty { + Some(ty) if expected == ty => { + let source_map = self.tcx.sess.source_map(); + err.span_suggestion( + source_map.end_point(cause.span()), + "try removing this `?`", + "", + Applicability::MachineApplicable, + ); + } + _ => {} + } + } + }, ObligationCauseCode::MatchExpressionArm(box MatchExpressionArmCause { arm_block_id, arm_span, @@ -742,12 +781,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { prior_arm_ty, source, ref prior_arms, - scrut_hir_id, opt_suggest_box_span, scrut_span, .. }) => match source { - hir::MatchSource::TryDesugar => { + hir::MatchSource::TryDesugar(scrut_hir_id) => { if let Some(ty::error::ExpectedFound { expected, .. }) = exp_found { let scrut_expr = self.tcx.hir().expect_expr(scrut_hir_id); let scrut_ty = if let hir::ExprKind::Call(_, args) = &scrut_expr.kind { @@ -763,7 +801,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { Some(ty) if expected == ty => { let source_map = self.tcx.sess.source_map(); err.span_suggestion( - source_map.end_point(cause.span), + source_map.end_point(cause.span()), "try removing this `?`", "", Applicability::MachineApplicable, @@ -784,13 +822,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { if prior_arms.len() <= 4 { for sp in prior_arms { any_multiline_arm |= source_map.is_multiline(*sp); - err.span_label(*sp, format!("this is found to be of type `{}`", t)); + err.span_label(*sp, format!("this is found to be of type `{t}`")); } } else if let Some(sp) = prior_arms.last() { any_multiline_arm |= source_map.is_multiline(*sp); err.span_label( *sp, - format!("this and all prior arms are found to be of type `{}`", t), + format!("this and all prior arms are found to be of type `{t}`"), ); } let outer = if any_multiline_arm || !source_map.is_multiline(cause.span) { @@ -846,7 +884,25 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { ) { err.subdiagnostic(subdiag); } - if let Some(ret_sp) = opt_suggest_box_span { + // don't suggest wrapping either blocks in `if .. {} else {}` + let is_empty_arm = |id| { + let hir::Node::Block(blk) = self.tcx.hir().get(id) + else { + return false; + }; + if blk.expr.is_some() || !blk.stmts.is_empty() { + return false; + } + let Some((_, hir::Node::Expr(expr))) = self.tcx.hir().parent_iter(id).nth(1) + else { + return false; + }; + matches!(expr.kind, hir::ExprKind::If(..)) + }; + if let Some(ret_sp) = opt_suggest_box_span + && !is_empty_arm(then_id) + && !is_empty_arm(else_id) + { self.suggest_boxing_for_return_impl_trait( err, ret_sp, @@ -889,7 +945,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { value: &mut DiagnosticStyledString, other_value: &mut DiagnosticStyledString, name: String, - sub: ty::subst::SubstsRef<'tcx>, + sub: ty::GenericArgsRef<'tcx>, pos: usize, other_ty: Ty<'tcx>, ) { @@ -967,9 +1023,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { other_path: String, other_ty: Ty<'tcx>, ) -> Option<()> { - // FIXME/HACK: Go back to `SubstsRef` to use its inherent methods, + // FIXME/HACK: Go back to `GenericArgsRef` to use its inherent methods, // ideally that shouldn't be necessary. - let sub = self.tcx.mk_substs(sub); + let sub = self.tcx.mk_args(sub); for (i, ta) in sub.types().enumerate() { if ta == other_ty { self.highlight_outer(&mut t1_out, &mut t2_out, path, sub, i, other_ty); @@ -1161,9 +1217,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let did1 = def1.did(); let did2 = def2.did(); let sub_no_defaults_1 = - self.tcx.generics_of(did1).own_substs_no_defaults(self.tcx, sub1); + self.tcx.generics_of(did1).own_args_no_defaults(self.tcx, sub1); let sub_no_defaults_2 = - self.tcx.generics_of(did2).own_substs_no_defaults(self.tcx, sub2); + self.tcx.generics_of(did2).own_args_no_defaults(self.tcx, sub2); let mut values = (DiagnosticStyledString::new(), DiagnosticStyledString::new()); let path1 = self.tcx.def_path_str(did1); let path2 = self.tcx.def_path_str(did2); @@ -1384,11 +1440,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } // When encountering tuples of the same size, highlight only the differing types - (&ty::Tuple(substs1), &ty::Tuple(substs2)) if substs1.len() == substs2.len() => { + (&ty::Tuple(args1), &ty::Tuple(args2)) if args1.len() == args2.len() => { let mut values = (DiagnosticStyledString::normal("("), DiagnosticStyledString::normal("(")); - let len = substs1.len(); - for (i, (left, right)) in substs1.iter().zip(substs2).enumerate() { + let len = args1.len(); + for (i, (left, right)) in args1.iter().zip(args2).enumerate() { let (x1, x2) = self.cmp(left, right); (values.0).0.extend(x1.0); (values.1).0.extend(x2.0); @@ -1404,35 +1460,34 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { values } - (ty::FnDef(did1, substs1), ty::FnDef(did2, substs2)) => { - let sig1 = self.tcx.fn_sig(*did1).subst(self.tcx, substs1); - let sig2 = self.tcx.fn_sig(*did2).subst(self.tcx, substs2); + (ty::FnDef(did1, args1), ty::FnDef(did2, args2)) => { + let sig1 = self.tcx.fn_sig(*did1).instantiate(self.tcx, args1); + let sig2 = self.tcx.fn_sig(*did2).instantiate(self.tcx, args2); let mut values = self.cmp_fn_sig(&sig1, &sig2); - let path1 = format!(" {{{}}}", self.tcx.def_path_str_with_substs(*did1, substs1)); - let path2 = format!(" {{{}}}", self.tcx.def_path_str_with_substs(*did2, substs2)); + let path1 = format!(" {{{}}}", self.tcx.def_path_str_with_args(*did1, args1)); + let path2 = format!(" {{{}}}", self.tcx.def_path_str_with_args(*did2, args2)); let same_path = path1 == path2; values.0.push(path1, !same_path); values.1.push(path2, !same_path); values } - (ty::FnDef(did1, substs1), ty::FnPtr(sig2)) => { - let sig1 = self.tcx.fn_sig(*did1).subst(self.tcx, substs1); + (ty::FnDef(did1, args1), ty::FnPtr(sig2)) => { + let sig1 = self.tcx.fn_sig(*did1).instantiate(self.tcx, args1); let mut values = self.cmp_fn_sig(&sig1, sig2); values.0.push_highlighted(format!( " {{{}}}", - self.tcx.def_path_str_with_substs(*did1, substs1) + self.tcx.def_path_str_with_args(*did1, args1) )); values } - (ty::FnPtr(sig1), ty::FnDef(did2, substs2)) => { - let sig2 = self.tcx.fn_sig(*did2).subst(self.tcx, substs2); + (ty::FnPtr(sig1), ty::FnDef(did2, args2)) => { + let sig2 = self.tcx.fn_sig(*did2).instantiate(self.tcx, args2); let mut values = self.cmp_fn_sig(sig1, &sig2); - values.1.push_normal(format!( - " {{{}}}", - self.tcx.def_path_str_with_substs(*did2, substs2) - )); + values + .1 + .push_normal(format!(" {{{}}}", self.tcx.def_path_str_with_args(*did2, args2))); values } @@ -1470,7 +1525,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { &self, diag: &mut Diagnostic, cause: &ObligationCause<'tcx>, - secondary_span: Option<(Span, String)>, + secondary_span: Option<(Span, Cow<'static, str>)>, mut values: Option>, terr: TypeError<'tcx>, swap_secondary_and_primary: bool, @@ -1617,6 +1672,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { (false, Mismatch::Fixed(self.tcx.def_descr(expected.def_id))) } ValuePairs::Regions(_) => (false, Mismatch::Fixed("lifetime")), + ValuePairs::ExistentialTraitRef(_) => { + (false, Mismatch::Fixed("existential trait ref")) + } + ValuePairs::ExistentialProjection(_) => { + (false, Mismatch::Fixed("existential projection")) + } }; let Some(vals) = self.values_str(values) else { // Derived error. Cancel the emitter. @@ -1629,7 +1690,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } }; - let mut label_or_note = |span: Span, msg: &str| { + let mut label_or_note = |span: Span, msg: Cow<'static, str>| { if (prefer_label && is_simple_error) || &[span] == diag.span.primary_spans() { diag.span_label(span, msg); } else { @@ -1643,15 +1704,15 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { .. })) = values { - format!("expected this to be `{}`", expected) + Cow::from(format!("expected this to be `{expected}`")) } else { - terr.to_string(self.tcx).to_string() + terr.to_string(self.tcx) }; - label_or_note(sp, &terr); - label_or_note(span, &msg); + label_or_note(sp, terr); + label_or_note(span, msg); } else { - label_or_note(span, &terr.to_string(self.tcx)); - label_or_note(sp, &msg); + label_or_note(span, terr.to_string(self.tcx)); + label_or_note(sp, msg); } } else { if let Some(values) = values @@ -1663,12 +1724,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let expected = with_forced_trimmed_paths!(e.sort_string(self.tcx)); let found = with_forced_trimmed_paths!(f.sort_string(self.tcx)); if expected == found { - label_or_note(span, &terr.to_string(self.tcx)); + label_or_note(span, terr.to_string(self.tcx)); } else { - label_or_note(span, &format!("expected {expected}, found {found}")); + label_or_note(span, Cow::from(format!("expected {expected}, found {found}"))); } } else { - label_or_note(span, &terr.to_string(self.tcx)); + label_or_note(span, terr.to_string(self.tcx)); } } @@ -1894,9 +1955,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { true }; - if should_suggest_fixes { + // FIXME(#73154): For now, we do leak check when coercing function + // pointers in typeck, instead of only during borrowck. This can lead + // to these `RegionsInsufficientlyPolymorphic` errors that aren't helpful. + if should_suggest_fixes + && !matches!(terr, TypeError::RegionsInsufficientlyPolymorphic(..)) + { self.suggest_tuple_pattern(cause, &exp_found, diag); - self.suggest_as_ref_where_appropriate(span, &exp_found, diag); self.suggest_accessing_field_where_appropriate(cause, &exp_found, diag); self.suggest_await_on_expect_found(cause, span, &exp_found, diag); self.suggest_function_pointers(cause, span, &exp_found, diag); @@ -1941,7 +2006,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { trace: &TypeTrace<'tcx>, terr: TypeError<'tcx>, ) -> Vec { - use crate::traits::ObligationCauseCode::MatchExpressionArm; + use crate::traits::ObligationCauseCode::{BlockTailExpression, MatchExpressionArm}; let mut suggestions = Vec::new(); let span = trace.cause.span(); let values = self.resolve_vars_if_possible(trace.values); @@ -1959,11 +2024,17 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { // specify a byte literal (ty::Uint(ty::UintTy::U8), ty::Char) => { if let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) - && let Some(code) = code.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) - && !code.starts_with("\\u") // forbid all Unicode escapes - && code.chars().next().is_some_and(|c| c.is_ascii()) // forbids literal Unicode characters beyond ASCII + && let Some(code) = + code.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) + // forbid all Unicode escapes + && !code.starts_with("\\u") + // forbids literal Unicode characters beyond ASCII + && code.chars().next().is_some_and(|c| c.is_ascii()) { - suggestions.push(TypeErrorAdditionalDiags::MeantByteLiteral { span, code: escape_literal(code) }) + suggestions.push(TypeErrorAdditionalDiags::MeantByteLiteral { + span, + code: escape_literal(code), + }) } } // If a character was expected and the found expression is a string literal @@ -1974,7 +2045,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { && let Some(code) = code.strip_prefix('"').and_then(|s| s.strip_suffix('"')) && code.chars().count() == 1 { - suggestions.push(TypeErrorAdditionalDiags::MeantCharLiteral { span, code: escape_literal(code) }) + suggestions.push(TypeErrorAdditionalDiags::MeantCharLiteral { + span, + code: escape_literal(code), + }) } } // If a string was expected and the found expression is a character literal, @@ -1984,7 +2058,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { if let Some(code) = code.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { - suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral { span, code: escape_literal(code) }) + suggestions.push(TypeErrorAdditionalDiags::MeantStrLiteral { + span, + code: escape_literal(code), + }) } } } @@ -1993,17 +2070,24 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { (ty::Bool, ty::Tuple(list)) => if list.len() == 0 { suggestions.extend(self.suggest_let_for_letchains(&trace.cause, span)); } - (ty::Array(_, _), ty::Array(_, _)) => suggestions.extend(self.suggest_specify_actual_length(terr, trace, span)), + (ty::Array(_, _), ty::Array(_, _)) => { + suggestions.extend(self.suggest_specify_actual_length(terr, trace, span)) + } _ => {} } } let code = trace.cause.code(); - if let &MatchExpressionArm(box MatchExpressionArmCause { source, .. }) = code - && let hir::MatchSource::TryDesugar = source - && let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values) - { - suggestions.push(TypeErrorAdditionalDiags::TryCannotConvert { found: found_ty.content(), expected: expected_ty.content() }); - } + if let &(MatchExpressionArm(box MatchExpressionArmCause { source, .. }) + | BlockTailExpression(.., source) + ) = code + && let hir::MatchSource::TryDesugar(_) = source + && let Some((expected_ty, found_ty, _, _)) = self.values_str(trace.values) + { + suggestions.push(TypeErrorAdditionalDiags::TryCannotConvert { + found: found_ty.content(), + expected: expected_ty.content(), + }); + } suggestions } @@ -2051,7 +2135,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { visitor.visit_body(body); visitor.result.map(|r| &r.peel_refs().kind) } - Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _), .. })) => { + Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(ty, _, _), .. })) => { Some(&ty.peel_refs().kind) } _ => None, @@ -2091,14 +2175,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { found: Ty<'tcx>, expected_fields: &List>, ) -> Option { - let [expected_tup_elem] = expected_fields[..] else { return None}; + let [expected_tup_elem] = expected_fields[..] else { return None }; if !self.same_type_modulo_infer(expected_tup_elem, found) { return None; } - let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) - else { return None }; + let Ok(code) = self.tcx.sess().source_map().span_to_snippet(span) else { return None }; let sugg = if code.starts_with('(') && code.ends_with(')') { let before_close = span.hi() - BytePos::from_u32(1); @@ -2123,6 +2206,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { infer::Regions(exp_found) => self.expected_found_str(exp_found), infer::Terms(exp_found) => self.expected_found_str_term(exp_found), infer::Aliases(exp_found) => self.expected_found_str(exp_found), + infer::ExistentialTraitRef(exp_found) => self.expected_found_str(exp_found), + infer::ExistentialProjection(exp_found) => self.expected_found_str(exp_found), infer::TraitRefs(exp_found) => { let pretty_exp_found = ty::error::ExpectedFound { expected: exp_found.expected.print_only_trait_path(), @@ -2338,7 +2423,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { if let Ok(snip) = self.tcx.sess.source_map().span_to_next_source(p.span) && snip.starts_with(' ') { - format!("{new_lt}") + new_lt.to_string() } else { format!("{new_lt} ") } @@ -2352,12 +2437,13 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } let labeled_user_string = match bound_kind { - GenericKind::Param(ref p) => format!("the parameter type `{}`", p), + GenericKind::Param(ref p) => format!("the parameter type `{p}`"), GenericKind::Alias(ref p) => match p.kind(self.tcx) { ty::AliasKind::Projection | ty::AliasKind::Inherent => { - format!("the associated type `{}`", p) + format!("the associated type `{p}`") } - ty::AliasKind::Opaque => format!("the opaque type `{}`", p), + ty::AliasKind::Weak => format!("the type alias `{p}`"), + ty::AliasKind::Opaque => format!("the opaque type `{p}`"), }, }; @@ -2371,7 +2457,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { span, impl_item_def_id, trait_item_def_id, - &format!("`{}: {}`", bound_kind, sub), + &format!("`{bound_kind}: {sub}`"), ); } @@ -2385,7 +2471,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let msg = "consider adding an explicit lifetime bound"; if let Some((sp, has_lifetimes)) = type_param_span { let suggestion = - if has_lifetimes { format!(" + {}", sub) } else { format!(": {}", sub) }; + if has_lifetimes { format!(" + {sub}") } else { format!(": {sub}") }; let mut suggestions = vec![(sp, suggestion)]; for add_lt_sugg in add_lt_suggs.into_iter().flatten() { suggestions.push(add_lt_sugg); @@ -2396,7 +2482,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { Applicability::MaybeIncorrect, // Issue #41966 ); } else { - let consider = format!("{} `{}: {}`...", msg, bound_kind, sub); + let consider = format!("{msg} `{bound_kind}: {sub}`..."); err.help(consider); } } @@ -2405,13 +2491,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { |err: &mut Diagnostic, type_param_span: Option<(Span, bool)>| { let msg = "consider introducing an explicit lifetime bound"; if let Some((sp, has_lifetimes)) = type_param_span { - let suggestion = if has_lifetimes { - format!(" + {}", new_lt) - } else { - format!(": {}", new_lt) - }; + let suggestion = + if has_lifetimes { format!(" + {new_lt}") } else { format!(": {new_lt}") }; let mut sugg = - vec![(sp, suggestion), (span.shrink_to_hi(), format!(" + {}", new_lt))]; + vec![(sp, suggestion), (span.shrink_to_hi(), format!(" + {new_lt}"))]; for lt in add_lt_suggs.clone().into_iter().flatten() { sugg.push(lt); sugg.rotate_right(1); @@ -2491,7 +2574,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { "{} may not live long enough", labeled_user_string ); - let pred = format!("{}: {}", bound_kind, sub); + let pred = format!("{bound_kind}: {sub}"); let suggestion = format!("{} {}", generics.add_where_or_trailing_comma(), pred,); err.span_suggestion( generics.tail_span_for_predicate_suggestion(), @@ -2547,21 +2630,19 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { note_and_explain_region( self.tcx, &mut err, - &format!("{} must be valid for ", labeled_user_string), + &format!("{labeled_user_string} must be valid for "), sub, "...", None, ); if let Some(infer::RelateParamBound(_, t, _)) = origin { - let return_impl_trait = - self.tcx.return_type_impl_trait(generic_param_scope).is_some(); let t = self.resolve_vars_if_possible(t); match t.kind() { // We've got: // fn get_later(g: G, dest: &mut T) -> impl FnOnce() + '_ // suggest: // fn get_later<'a, G: 'a, T>(g: G, dest: &mut T) -> impl FnOnce() + '_ + 'a - ty::Closure(..) | ty::Alias(ty::Opaque, ..) if return_impl_trait => { + ty::Closure(..) | ty::Alias(ty::Opaque, ..) => { new_binding_suggestion(&mut err, type_param_span); } _ => { @@ -2797,10 +2878,10 @@ impl<'tcx> InferCtxt<'tcx> { br_string(br), self.tcx.associated_item(def_id).name ), - infer::EarlyBoundRegion(_, name) => format!(" for lifetime parameter `{}`", name), + infer::EarlyBoundRegion(_, name) => format!(" for lifetime parameter `{name}`"), infer::UpvarRegion(ref upvar_id, _) => { let var_name = self.tcx.hir().name(upvar_id.var_path.hir_id); - format!(" for capture of `{}` by closure", var_name) + format!(" for capture of `{var_name}` by closure") } infer::Nll(..) => bug!("NLL variable found in lexical phase"), }; @@ -2876,8 +2957,11 @@ impl<'tcx> ObligationCauseExt<'tcx> for ObligationCause<'tcx> { CompareImplItemObligation { kind: ty::AssocKind::Const, .. } => { ObligationCauseFailureCode::ConstCompat { span, subdiags } } + BlockTailExpression(.., hir::MatchSource::TryDesugar(_)) => { + ObligationCauseFailureCode::TryCompat { span, subdiags } + } MatchExpressionArm(box MatchExpressionArmCause { source, .. }) => match source { - hir::MatchSource::TryDesugar => { + hir::MatchSource::TryDesugar(_) => { ObligationCauseFailureCode::TryCompat { span, subdiags } } _ => ObligationCauseFailureCode::MatchCompat { span, subdiags }, diff --git a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs index f3b2ec4c5e383..f2a3c47bdfe8e 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/need_type_info.rs @@ -18,7 +18,7 @@ use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKin use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::print::{FmtPrinter, PrettyPrinter, Print, Printer}; use rustc_middle::ty::{self, InferConst}; -use rustc_middle::ty::{GenericArg, GenericArgKind, SubstsRef}; +use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgsRef}; use rustc_middle::ty::{IsSuggestable, Ty, TyCtxt, TypeckResults}; use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::{BytePos, Span}; @@ -162,10 +162,18 @@ fn fmt_printer<'a, 'tcx>(infcx: &'a InferCtxt<'tcx>, ns: Namespace) -> FmtPrinte let mut infcx_inner = infcx.inner.borrow_mut(); let ty_vars = infcx_inner.type_variables(); let var_origin = ty_vars.var_origin(ty_vid); - if let TypeVariableOriginKind::TypeParameterDefinition(name, _) = var_origin.kind - && !var_origin.span.from_expansion() + if let TypeVariableOriginKind::TypeParameterDefinition(name, def_id) = var_origin.kind + && name != kw::SelfUpper && !var_origin.span.from_expansion() { - Some(name) + let generics = infcx.tcx.generics_of(infcx.tcx.parent(def_id)); + let idx = generics.param_def_id_to_index(infcx.tcx, def_id).unwrap(); + let generic_param_def = generics.param_at(idx as usize, infcx.tcx); + if let ty::GenericParamDefKind::Type { synthetic: true, .. } = generic_param_def.kind + { + None + } else { + Some(name) + } } else { None } @@ -218,8 +226,8 @@ fn ty_to_string<'tcx>( /// something users are familiar with. Directly printing the `fn_sig` of closures also /// doesn't work as they actually use the "rust-call" API. fn closure_as_fn_str<'tcx>(infcx: &InferCtxt<'tcx>, ty: Ty<'tcx>) -> String { - let ty::Closure(_, substs) = ty.kind() else { unreachable!() }; - let fn_sig = substs.as_closure().sig(); + let ty::Closure(_, args) = ty.kind() else { unreachable!() }; + let fn_sig = args.as_closure().sig(); let args = fn_sig .inputs() .skip_binder() @@ -238,7 +246,7 @@ fn closure_as_fn_str<'tcx>(infcx: &InferCtxt<'tcx>, ty: Ty<'tcx>) -> String { } else { format!(" -> {}", ty_to_string(infcx, fn_sig.output().skip_binder(), None)) }; - format!("fn({}){}", args, ret) + format!("fn({args}){ret}") } impl<'tcx> InferCtxt<'tcx> { @@ -265,9 +273,9 @@ impl<'tcx> InferCtxt<'tcx> { kind: UnderspecifiedArgKind::Type { prefix: "type parameter".into(), }, - parent: def_id.and_then(|def_id| { - InferenceDiagnosticsParentData::for_def_id(self.tcx, def_id) - }), + parent: InferenceDiagnosticsParentData::for_def_id( + self.tcx, def_id, + ), }; } } @@ -411,7 +419,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } let Some(InferSource { span, kind }) = local_visitor.infer_source else { - return self.bad_inference_failure_err(failure_span, arg_data, error_code) + return self.bad_inference_failure_err(failure_span, arg_data, error_code); }; let (source_kind, name) = kind.ty_localized_msg(self); @@ -516,9 +524,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { }); } } - InferSourceKind::FullyQualifiedMethodCall { receiver, successor, substs, def_id } => { + InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => { let printer = fmt_printer(self, Namespace::ValueNS); - let def_path = printer.print_def_path(def_id, substs).unwrap().into_buffer(); + let def_path = printer.print_def_path(def_id, args).unwrap().into_buffer(); // We only care about whether we have to add `&` or `&mut ` for now. // This is the case if the last adjustment is a borrow and the @@ -651,7 +659,7 @@ enum InferSourceKind<'tcx> { /// If the method has other arguments, this is ", " and the start of the first argument, /// while for methods without arguments this is ")" and the end of the method call. successor: (&'static str, BytePos), - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, def_id: DefId, }, ClosureReturn { @@ -702,7 +710,7 @@ impl<'tcx> InferSourceKind<'tcx> { #[derive(Debug)] struct InsertableGenericArgs<'tcx> { insert_span: Span, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, generics_def_id: DefId, def_id: DefId, have_turbofish: bool, @@ -766,11 +774,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { ty::Closure(..) => 1000, ty::FnDef(..) => 150, ty::FnPtr(..) => 30, - ty::Adt(def, substs) => { + ty::Adt(def, args) => { 5 + self .tcx .generics_of(def.did()) - .own_substs_no_defaults(self.tcx, substs) + .own_args_no_defaults(self.tcx, args) .iter() .map(|&arg| self.arg_cost(arg)) .sum::() @@ -797,8 +805,8 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { }; variant_cost + generic_args.iter().map(|&arg| ctx.arg_cost(arg)).sum::() } - InferSourceKind::FullyQualifiedMethodCall { substs, .. } => { - 20 + substs.iter().map(|arg| ctx.arg_cost(arg)).sum::() + InferSourceKind::FullyQualifiedMethodCall { args, .. } => { + 20 + args.iter().map(|arg| ctx.arg_cost(arg)).sum::() } InferSourceKind::ClosureReturn { ty, should_wrap_expr, .. } => { 30 + ctx.ty_cost(ty) + if should_wrap_expr.is_some() { 10 } else { 0 } @@ -832,9 +840,9 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { } } - fn node_substs_opt(&self, hir_id: HirId) -> Option> { - let substs = self.typeck_results.node_substs_opt(hir_id); - self.infcx.resolve_vars_if_possible(substs) + fn node_args_opt(&self, hir_id: HirId) -> Option> { + let args = self.typeck_results.node_args_opt(hir_id); + self.infcx.resolve_vars_if_possible(args) } fn opt_node_type(&self, hir_id: HirId) -> Option> { @@ -915,15 +923,15 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { false } - fn expr_inferred_subst_iter( + fn expr_inferred_arg_iter( &self, expr: &'tcx hir::Expr<'tcx>, ) -> Box> + 'a> { let tcx = self.infcx.tcx; match expr.kind { hir::ExprKind::Path(ref path) => { - if let Some(substs) = self.node_substs_opt(expr.hir_id) { - return self.path_inferred_subst_iter(expr.hir_id, substs, path); + if let Some(args) = self.node_args_opt(expr.hir_id) { + return self.path_inferred_arg_iter(expr.hir_id, args, path); } } // FIXME(#98711): Ideally we would also deal with type relative @@ -935,7 +943,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { // However, the `type_dependent_def_id` for `Self::Output` in an // impl is currently the `DefId` of `Output` in the trait definition // which makes this somewhat difficult and prevents us from just - // using `self.path_inferred_subst_iter` here. + // using `self.path_inferred_arg_iter` here. hir::ExprKind::Struct(&hir::QPath::Resolved(_self_ty, path), _, _) // FIXME(TaKO8Ki): Ideally we should support this. For that // we have to map back from the self type to the @@ -943,11 +951,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { // // See the `need_type_info/issue-103053.rs` test for // a example. - if !matches!(path.res, Res::Def(DefKind::TyAlias, _)) => { + if !matches!(path.res, Res::Def(DefKind::TyAlias { .. }, _)) => { if let Some(ty) = self.opt_node_type(expr.hir_id) - && let ty::Adt(_, substs) = ty.kind() + && let ty::Adt(_, args) = ty.kind() { - return Box::new(self.resolved_path_inferred_subst_iter(path, substs)); + return Box::new(self.resolved_path_inferred_arg_iter(path, args)); } } hir::ExprKind::MethodCall(segment, ..) => { @@ -957,12 +965,12 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { if generics.has_impl_trait() { None? } - let substs = self.node_substs_opt(expr.hir_id)?; + let args = self.node_args_opt(expr.hir_id)?; let span = tcx.hir().span(segment.hir_id); let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi()); InsertableGenericArgs { insert_span, - substs, + args, generics_def_id: def_id, def_id, have_turbofish: false, @@ -977,10 +985,10 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { Box::new(iter::empty()) } - fn resolved_path_inferred_subst_iter( + fn resolved_path_inferred_arg_iter( &self, path: &'tcx hir::Path<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> impl Iterator> + 'a { let tcx = self.infcx.tcx; let have_turbofish = path.segments.iter().any(|segment| { @@ -1001,7 +1009,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { path.segments.last().unwrap().ident.span.shrink_to_hi().with_hi(path.span.hi()); InsertableGenericArgs { insert_span, - substs, + args, generics_def_id, def_id: path.res.def_id(), have_turbofish, @@ -1021,7 +1029,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi()); Some(InsertableGenericArgs { insert_span, - substs, + args, generics_def_id, def_id: res.def_id(), have_turbofish, @@ -1030,16 +1038,16 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { .chain(last_segment_using_path_data) } - fn path_inferred_subst_iter( + fn path_inferred_arg_iter( &self, hir_id: HirId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, qpath: &'tcx hir::QPath<'tcx>, ) -> Box> + 'a> { let tcx = self.infcx.tcx; match qpath { hir::QPath::Resolved(_self_ty, path) => { - Box::new(self.resolved_path_inferred_subst_iter(path, substs)) + Box::new(self.resolved_path_inferred_arg_iter(path, args)) } hir::QPath::TypeRelative(ty, segment) => { let Some(def_id) = self.typeck_results.type_dependent_def_id(hir_id) else { @@ -1055,7 +1063,7 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { let insert_span = segment.ident.span.shrink_to_hi().with_hi(span.hi()); InsertableGenericArgs { insert_span, - substs, + args, generics_def_id: def_id, def_id, have_turbofish: false, @@ -1064,15 +1072,15 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { let parent_def_id = generics.parent.unwrap(); if let DefKind::Impl { .. } = tcx.def_kind(parent_def_id) { - let parent_ty = tcx.type_of(parent_def_id).subst(tcx, substs); + let parent_ty = tcx.type_of(parent_def_id).instantiate(tcx, args); match (parent_ty.kind(), &ty.kind) { ( - ty::Adt(def, substs), + ty::Adt(def, args), hir::TyKind::Path(hir::QPath::Resolved(_self_ty, path)), ) => { if tcx.res_generics_def_id(path.res) != Some(def.did()) { match path.res { - Res::Def(DefKind::TyAlias, _) => { + Res::Def(DefKind::TyAlias { .. }, _) => { // FIXME: Ideally we should support this. For that // we have to map back from the self type to the // type alias though. That's difficult. @@ -1084,14 +1092,13 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> { // so there's nothing for us to do here. Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } => {} _ => warn!( - "unexpected path: def={:?} substs={:?} path={:?}", - def, substs, path, + "unexpected path: def={:?} args={:?} path={:?}", + def, args, path, ), } } else { return Box::new( - self.resolved_path_inferred_subst_iter(path, substs) - .chain(segment), + self.resolved_path_inferred_arg_iter(path, args).chain(segment), ); } } @@ -1149,9 +1156,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> { continue; } - let Some(param_ty) = self.opt_node_type(param.hir_id) else { - continue - }; + let Some(param_ty) = self.opt_node_type(param.hir_id) else { continue }; if self.generic_arg_contains_target(param_ty.into()) { self.update_infer_source(InferSource { @@ -1181,27 +1186,27 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> { _ => intravisit::walk_expr(self, expr), } - for args in self.expr_inferred_subst_iter(expr) { + for args in self.expr_inferred_arg_iter(expr) { debug!(?args); let InsertableGenericArgs { insert_span, - substs, + args, generics_def_id, def_id, have_turbofish, } = args; let generics = tcx.generics_of(generics_def_id); if let Some(mut argument_index) = generics - .own_substs(substs) + .own_args(args) .iter() .position(|&arg| self.generic_arg_contains_target(arg)) { if generics.parent.is_none() && generics.has_self { argument_index += 1; } - let substs = self.infcx.resolve_vars_if_possible(substs); - let generic_args = &generics.own_substs_no_defaults(tcx, substs) - [generics.own_counts().lifetimes..]; + let args = self.infcx.resolve_vars_if_possible(args); + let generic_args = + &generics.own_args_no_defaults(tcx, args)[generics.own_counts().lifetimes..]; let span = match expr.kind { ExprKind::MethodCall(path, ..) => path.ident.span, _ => expr.span, @@ -1224,10 +1229,10 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> { if let Some(node_ty) = self.opt_node_type(expr.hir_id) { if let ( &ExprKind::Closure(&Closure { fn_decl, body, fn_decl_span, .. }), - ty::Closure(_, substs), + ty::Closure(_, args), ) = (&expr.kind, node_ty.kind()) { - let output = substs.as_closure().sig().output().skip_binder(); + let output = args.as_closure().sig().output().skip_binder(); if self.generic_arg_contains_target(output.into()) { let body = self.infcx.tcx.hir().body(body); let should_wrap_expr = if matches!(body.value.kind, ExprKind::Block(..)) { @@ -1253,22 +1258,22 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> { }) .any(|generics| generics.has_impl_trait()) }; - if let ExprKind::MethodCall(path, receiver, args, span) = expr.kind - && let Some(substs) = self.node_substs_opt(expr.hir_id) - && substs.iter().any(|arg| self.generic_arg_contains_target(arg)) + if let ExprKind::MethodCall(path, receiver, method_args, span) = expr.kind + && let Some(args) = self.node_args_opt(expr.hir_id) + && args.iter().any(|arg| self.generic_arg_contains_target(arg)) && let Some(def_id) = self.typeck_results.type_dependent_def_id(expr.hir_id) && self.infcx.tcx.trait_of_item(def_id).is_some() && !has_impl_trait(def_id) { let successor = - args.get(0).map_or_else(|| (")", span.hi()), |arg| (", ", arg.span.lo())); - let substs = self.infcx.resolve_vars_if_possible(substs); + method_args.get(0).map_or_else(|| (")", span.hi()), |arg| (", ", arg.span.lo())); + let args = self.infcx.resolve_vars_if_possible(args); self.update_infer_source(InferSource { span: path.ident.span, kind: InferSourceKind::FullyQualifiedMethodCall { receiver, successor, - substs, + args, def_id, } }) diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs index 2c63a39041072..6901955af65fd 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/mismatched_static_lifetime.rs @@ -38,8 +38,9 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { let ObligationCauseCode::MatchImpl(parent, impl_def_id) = code else { return None; }; - let (ObligationCauseCode::BindingObligation(_, binding_span) | ObligationCauseCode::ExprBindingObligation(_, binding_span, ..)) - = *parent.code() else { + let (ObligationCauseCode::BindingObligation(_, binding_span) + | ObligationCauseCode::ExprBindingObligation(_, binding_span, ..)) = *parent.code() + else { return None; }; @@ -67,12 +68,13 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { let hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(hir::Impl { self_ty: impl_self_ty, .. }), .. - }) = impl_node else { + }) = impl_node + else { bug!("Node not an impl."); }; // Next, let's figure out the set of trait objects with implicit static bounds - let ty = self.tcx().type_of(*impl_def_id).subst_identity(); + let ty = self.tcx().type_of(*impl_def_id).instantiate_identity(); let mut v = super::static_impl_trait::TraitObjectVisitor(FxIndexSet::default()); v.visit_ty(ty); let mut traits = vec![]; diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs index 4e13ec90228d6..07f04ec1e4449 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/named_anon_conflict.rs @@ -29,25 +29,15 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { // version new_ty of its type where the anonymous region is replaced // with the named one. let (named, anon, anon_param_info, region_info) = if sub.has_name() - && self.tcx().is_suitable_region(sup).is_some() - && self.find_param_with_region(sup, sub).is_some() + && let Some(region_info) = self.tcx().is_suitable_region(sup) + && let Some(anon_param_info) = self.find_param_with_region(sup, sub) { - ( - sub, - sup, - self.find_param_with_region(sup, sub).unwrap(), - self.tcx().is_suitable_region(sup).unwrap(), - ) + (sub, sup, anon_param_info, region_info) } else if sup.has_name() - && self.tcx().is_suitable_region(sub).is_some() - && self.find_param_with_region(sub, sup).is_some() + && let Some(region_info) = self.tcx().is_suitable_region(sub) + && let Some(anon_param_info) = self.find_param_with_region(sub, sup) { - ( - sup, - sub, - self.find_param_with_region(sub, sup).unwrap(), - self.tcx().is_suitable_region(sub).unwrap(), - ) + (sup, sub, anon_param_info, region_info) } else { return None; // inapplicable }; diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs index c9c1f0aeaac83..f903f7a49ef85 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/placeholder_error.rs @@ -13,7 +13,7 @@ use rustc_hir::def::Namespace; use rustc_hir::def_id::DefId; use rustc_middle::ty::error::ExpectedFound; use rustc_middle::ty::print::{FmtPrinter, Print, RegionHighlightMode}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, RePlaceholder, Region, TyCtxt}; use std::fmt; @@ -79,7 +79,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sup_placeholder @ Region(Interned(RePlaceholder(_), _)), _, )) => self.try_report_trait_placeholder_mismatch( - Some(self.tcx().mk_re_var(*vid)), + Some(ty::Region::new_var(self.tcx(), *vid)), cause, Some(*sub_placeholder), Some(*sup_placeholder), @@ -95,7 +95,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { _, _, )) => self.try_report_trait_placeholder_mismatch( - Some(self.tcx().mk_re_var(*vid)), + Some(ty::Region::new_var(self.tcx(), *vid)), cause, Some(*sub_placeholder), None, @@ -111,7 +111,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sup_placeholder @ Region(Interned(RePlaceholder(_), _)), _, )) => self.try_report_trait_placeholder_mismatch( - Some(self.tcx().mk_re_var(*vid)), + Some(ty::Region::new_var(self.tcx(), *vid)), cause, None, Some(*sup_placeholder), @@ -127,7 +127,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sup_placeholder @ Region(Interned(RePlaceholder(_), _)), _, )) => self.try_report_trait_placeholder_mismatch( - Some(self.tcx().mk_re_var(*vid)), + Some(ty::Region::new_var(self.tcx(), *vid)), cause, None, Some(*sup_placeholder), @@ -141,7 +141,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { SubregionOrigin::Subtype(box TypeTrace { cause, values }), sup_placeholder @ Region(Interned(RePlaceholder(_), _)), )) => self.try_report_trait_placeholder_mismatch( - Some(self.tcx().mk_re_var(*vid)), + Some(ty::Region::new_var(self.tcx(), *vid)), cause, None, Some(*sup_placeholder), @@ -196,11 +196,11 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sup_placeholder: Option>, value_pairs: &ValuePairs<'tcx>, ) -> Option> { - let (expected_substs, found_substs, trait_def_id) = match value_pairs { + let (expected_args, found_args, trait_def_id) = match value_pairs { ValuePairs::TraitRefs(ExpectedFound { expected, found }) if expected.def_id == found.def_id => { - (expected.substs, found.substs, expected.def_id) + (expected.args, found.args, expected.def_id) } ValuePairs::PolyTraitRefs(ExpectedFound { expected, found }) if expected.def_id() == found.def_id() => @@ -208,7 +208,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { // It's possible that the placeholders come from a binder // outside of this value pair. Use `no_bound_vars` as a // simple heuristic for that. - (expected.no_bound_vars()?.substs, found.no_bound_vars()?.substs, expected.def_id()) + (expected.no_bound_vars()?.args, found.no_bound_vars()?.args, expected.def_id()) } _ => return None, }; @@ -219,8 +219,8 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sub_placeholder, sup_placeholder, trait_def_id, - expected_substs, - found_substs, + expected_args, + found_args, )) } @@ -241,8 +241,8 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { sub_placeholder: Option>, sup_placeholder: Option>, trait_def_id: DefId, - expected_substs: SubstsRef<'tcx>, - actual_substs: SubstsRef<'tcx>, + expected_args: GenericArgsRef<'tcx>, + actual_args: GenericArgsRef<'tcx>, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { let span = cause.span(); @@ -264,12 +264,12 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { let expected_trait_ref = self.cx.resolve_vars_if_possible(ty::TraitRef::new( self.cx.tcx, trait_def_id, - expected_substs, + expected_args, )); let actual_trait_ref = self.cx.resolve_vars_if_possible(ty::TraitRef::new( self.cx.tcx, trait_def_id, - actual_substs, + actual_args, )); // Search the expected and actual trait references to see (a) @@ -413,9 +413,9 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { if self_ty.value.is_closure() && self.tcx().is_fn_trait(expected_trait_ref.value.def_id) { let closure_sig = self_ty.map(|closure| { - if let ty::Closure(_, substs) = closure.kind() { + if let ty::Closure(_, args) = closure.kind() { self.tcx().signature_unclosure( - substs.as_closure().sig(), + args.as_closure().sig(), rustc_hir::Unsafety::Normal, ) } else { diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs index aad9885827da5..3cfda0cc5c05c 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/static_impl_trait.rs @@ -146,7 +146,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = sub_origin { if let ObligationCauseCode::ReturnValue(hir_id) - | ObligationCauseCode::BlockTailExpression(hir_id) = cause.code() + | ObligationCauseCode::BlockTailExpression(hir_id, ..) = cause.code() { let parent_id = tcx.hir().get_parent_item(*hir_id); if let Some(fn_decl) = tcx.hir().fn_decl_by_hir_id(parent_id.into()) { @@ -235,10 +235,10 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { } let arg = match param.param.pat.simple_ident() { - Some(simple_ident) => format!("argument `{}`", simple_ident), + Some(simple_ident) => format!("argument `{simple_ident}`"), None => "the argument".to_string(), }; - let captures = format!("captures data from {}", arg); + let captures = format!("captures data from {arg}"); suggest_new_region_bound( tcx, &mut err, @@ -269,11 +269,11 @@ pub fn suggest_new_region_bound( // FIXME: account for the need of parens in `&(dyn Trait + '_)` let consider = "consider changing"; let declare = "to declare that"; - let explicit = format!("you can add an explicit `{}` lifetime bound", lifetime_name); + let explicit = format!("you can add an explicit `{lifetime_name}` lifetime bound"); let explicit_static = - arg.map(|arg| format!("explicit `'static` bound to the lifetime of {}", arg)); + arg.map(|arg| format!("explicit `'static` bound to the lifetime of {arg}")); let add_static_bound = "alternatively, add an explicit `'static` bound to this reference"; - let plus_lt = format!(" + {}", lifetime_name); + let plus_lt = format!(" + {lifetime_name}"); for fn_return in fn_returns { if fn_return.span.desugaring_kind().is_some() { // Skip `async` desugaring `impl Future`. @@ -288,7 +288,7 @@ pub fn suggest_new_region_bound( // Get the identity type for this RPIT let did = item_id.owner_id.to_def_id(); - let ty = tcx.mk_opaque(did, ty::InternalSubsts::identity_for_item(tcx, did)); + let ty = Ty::new_opaque(tcx, did, ty::GenericArgs::identity_for_item(tcx, did)); if let Some(span) = opaque.bounds.iter().find_map(|arg| match arg { GenericBound::Outlives(Lifetime { @@ -333,11 +333,7 @@ pub fn suggest_new_region_bound( } else { None }; - let name = if let Some(name) = &existing_lt_name { - format!("{}", name) - } else { - format!("'a") - }; + let name = if let Some(name) = &existing_lt_name { name } else { "'a" }; // if there are more than one elided lifetimes in inputs, the explicit `'_` lifetime cannot be used. // introducing a new lifetime `'a` or making use of one from existing named lifetimes if any if let Some(id) = scope_def_id @@ -350,7 +346,7 @@ pub fn suggest_new_region_bound( if p.span.hi() - p.span.lo() == rustc_span::BytePos(1) { // Ampersand (elided without '_) (p.span.shrink_to_hi(),format!("{name} ")) } else { // Underscore (elided with '_) - (p.span, format!("{name}")) + (p.span, name.to_string()) } ) .collect::>() @@ -387,12 +383,7 @@ pub fn suggest_new_region_bound( if let LifetimeName::ImplicitObjectLifetimeDefault = lt.res { err.span_suggestion_verbose( fn_return.span.shrink_to_hi(), - format!( - "{declare} the trait object {captures}, {explicit}", - declare = declare, - captures = captures, - explicit = explicit, - ), + format!("{declare} the trait object {captures}, {explicit}",), &plus_lt, Applicability::MaybeIncorrect, ); @@ -404,7 +395,7 @@ pub fn suggest_new_region_bound( if let Some(explicit_static) = &explicit_static { err.span_suggestion_verbose( lt.ident.span, - format!("{} the trait object's {}", consider, explicit_static), + format!("{consider} the trait object's {explicit_static}"), &lifetime_name, Applicability::MaybeIncorrect, ); @@ -493,7 +484,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { tcx, ctxt.param_env, ctxt.assoc_item.def_id, - self.cx.resolve_vars_if_possible(ctxt.substs), + self.cx.resolve_vars_if_possible(ctxt.args), ) else { return false; }; @@ -503,7 +494,9 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { // Get the `Ident` of the method being called and the corresponding `impl` (to point at // `Bar` in `impl Foo for dyn Bar {}` and the definition of the method being called). - let Some((ident, self_ty)) = NiceRegionError::get_impl_ident_and_self_ty_from_trait(tcx, instance.def_id(), &v.0) else { + let Some((ident, self_ty)) = + NiceRegionError::get_impl_ident_and_self_ty_from_trait(tcx, instance.def_id(), &v.0) + else { return false; }; diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs index ce70bcc5c8511..12d38ced03065 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/trait_impl_difference.rs @@ -41,8 +41,8 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { // all of the region highlighting machinery only deals with those. let guar = self.emit_err( var_origin.span(), - self.cx.tcx.mk_fn_ptr(ty::Binder::dummy(expected)), - self.cx.tcx.mk_fn_ptr(ty::Binder::dummy(found)), + Ty::new_fn_ptr(self.cx.tcx,ty::Binder::dummy(expected)), + Ty::new_fn_ptr(self.cx.tcx,ty::Binder::dummy(found)), *trait_item_def_id, ); return Some(guar); diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs index c5ef48fe3da25..be6d1a3750cdb 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs @@ -64,8 +64,8 @@ pub fn find_param_with_region<'tcx>( let body_id = hir.maybe_body_owned_by(def_id)?; let owner_id = hir.body_owner(body_id); - let fn_decl = hir.fn_decl_by_hir_id(owner_id).unwrap(); - let poly_fn_sig = tcx.fn_sig(id).subst_identity(); + let fn_decl = hir.fn_decl_by_hir_id(owner_id)?; + let poly_fn_sig = tcx.fn_sig(id).instantiate_identity(); let fn_sig = tcx.liberate_late_bound_regions(id, poly_fn_sig); let body = hir.body(body_id); @@ -123,7 +123,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { br: ty::BoundRegionKind, hir_sig: &hir::FnSig<'_>, ) -> Option { - let fn_ty = self.tcx().type_of(scope_def_id).subst_identity(); + let fn_ty = self.tcx().type_of(scope_def_id).instantiate_identity(); if let ty::FnDef(_, _) = fn_ty.kind() { let ret_ty = fn_ty.fn_sig(self.tcx()).output(); let span = hir_sig.decl.output.span(); diff --git a/compiler/rustc_infer/src/infer/error_reporting/note.rs b/compiler/rustc_infer/src/infer/error_reporting/note.rs index 07a9eff2dbefe..8d3cd23b7fa51 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/note.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/note.rs @@ -11,7 +11,7 @@ use rustc_errors::{ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::traits::ObligationCauseCode; use rustc_middle::ty::error::TypeError; -use rustc_middle::ty::{self, IsSuggestable, Region}; +use rustc_middle::ty::{self, IsSuggestable, Region, Ty}; use rustc_span::symbol::kw; use super::ObligationCauseAsDiagArg; @@ -227,7 +227,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { span, impl_item_def_id, trait_item_def_id, - &format!("`{}: {}`", sup, sub), + &format!("`{sup}: {sub}`"), ); // We should only suggest rewriting the `where` clause if the predicate is within that `where` clause if let Some(generics) = self.tcx.hir().get_generics(impl_item_def_id) @@ -243,12 +243,18 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } infer::CheckAssociatedTypeBounds { impl_item_def_id, trait_item_def_id, parent } => { let mut err = self.report_concrete_failure(*parent, sub, sup); - let trait_item_span = self.tcx.def_span(trait_item_def_id); - let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); - err.span_label( - trait_item_span, - format!("definition of `{}` from trait", item_name), - ); + + // Don't mention the item name if it's an RPITIT, since that'll just confuse + // folks. + if !self.tcx.is_impl_trait_in_trait(impl_item_def_id.to_def_id()) { + let trait_item_span = self.tcx.def_span(trait_item_def_id); + let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); + err.span_label( + trait_item_span, + format!("definition of `{item_name}` from trait"), + ); + } + self.suggest_copy_trait_method_bounds( trait_item_def_id, impl_item_def_id, @@ -295,34 +301,40 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { // but right now it's not really very smart when it comes to implicit `Sized` // predicates and bounds on the trait itself. - let Some(impl_def_id) = - self.tcx.associated_item(impl_item_def_id).impl_container(self.tcx) else { return; }; - let Some(trait_ref) = self - .tcx - .impl_trait_ref(impl_def_id) - else { return; }; - let trait_substs = trait_ref - .subst_identity() + let Some(impl_def_id) = self.tcx.associated_item(impl_item_def_id).impl_container(self.tcx) + else { + return; + }; + let Some(trait_ref) = self.tcx.impl_trait_ref(impl_def_id) else { + return; + }; + let trait_args = trait_ref + .instantiate_identity() // Replace the explicit self type with `Self` for better suggestion rendering - .with_self_ty(self.tcx, self.tcx.mk_ty_param(0, kw::SelfUpper)) - .substs; - let trait_item_substs = ty::InternalSubsts::identity_for_item(self.tcx, impl_item_def_id) - .rebase_onto(self.tcx, impl_def_id, trait_substs); + .with_self_ty(self.tcx, Ty::new_param(self.tcx, 0, kw::SelfUpper)) + .args; + let trait_item_args = ty::GenericArgs::identity_for_item(self.tcx, impl_item_def_id) + .rebase_onto(self.tcx, impl_def_id, trait_args); - let Ok(trait_predicates) = self - .tcx - .explicit_predicates_of(trait_item_def_id) - .instantiate_own(self.tcx, trait_item_substs) - .map(|(pred, _)| { - if pred.is_suggestable(self.tcx, false) { - Ok(pred.to_string()) - } else { - Err(()) - } - }) - .collect::, ()>>() else { return; }; + let Ok(trait_predicates) = + self.tcx + .explicit_predicates_of(trait_item_def_id) + .instantiate_own(self.tcx, trait_item_args) + .map(|(pred, _)| { + if pred.is_suggestable(self.tcx, false) { + Ok(pred.to_string()) + } else { + Err(()) + } + }) + .collect::, ()>>() + else { + return; + }; - let Some(generics) = self.tcx.hir().get_generics(impl_item_def_id) else { return; }; + let Some(generics) = self.tcx.hir().get_generics(impl_item_def_id) else { + return; + }; let suggestion = if trait_predicates.is_empty() { WhereClauseSuggestions::Remove { span: generics.where_clause_span } diff --git a/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs b/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs index 421eb807a141f..372539d73b130 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/note_and_explain.rs @@ -47,7 +47,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { diag.span_suggestion( sp, "use a float literal", - format!("{}.0", snippet), + format!("{snippet}.0"), MachineApplicable, ); } @@ -100,9 +100,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { { // Synthesize the associated type restriction `Add`. // FIXME: extract this logic for use in other diagnostics. - let (trait_ref, assoc_substs) = proj.trait_ref_and_own_substs(tcx); + let (trait_ref, assoc_args) = proj.trait_ref_and_own_args(tcx); let item_name = tcx.item_name(proj.def_id); - let item_args = self.format_generic_args(assoc_substs); + let item_args = self.format_generic_args(assoc_args); // Here, we try to see if there's an existing // trait implementation that matches the one that @@ -134,12 +134,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { if matched_end_of_args { // Append suggestion to the end of our args - let path = format!(", {}{} = {}",item_name, item_args, p); + let path = format!(", {item_name}{item_args} = {p}"); note = !suggest_constraining_type_param( tcx, generics, diag, - &format!("{}", proj.self_ty()), + &proj.self_ty().to_string(), &path, None, matching_span, @@ -148,12 +148,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { // Suggest adding a bound to an existing trait // or if the trait doesn't exist, add the trait // and the suggested bounds. - let path = format!("<{}{} = {}>", item_name, item_args, p); + let path = format!("<{item_name}{item_args} = {p}>"); note = !suggest_constraining_type_param( tcx, generics, diag, - &format!("{}", proj.self_ty()), + &proj.self_ty().to_string(), &path, None, matching_span, @@ -213,8 +213,7 @@ impl Trait for X { } diag.help(format!( "every closure has a distinct type and so could not always match the \ - caller-chosen type of parameter `{}`", - p + caller-chosen type of parameter `{p}`" )); } (ty::Param(p), _) | (_, ty::Param(p)) => { @@ -234,13 +233,13 @@ impl Trait for X { ); } (_, ty::Alias(ty::Projection | ty::Inherent, proj_ty)) if !tcx.is_impl_trait_in_trait(proj_ty.def_id) => { - let msg = format!( + let msg = || format!( "consider constraining the associated type `{}` to `{}`", values.found, values.expected, ); if !(self.suggest_constraining_opaque_associated_type( diag, - &msg, + msg, proj_ty, values.expected, ) || self.suggest_constraint( @@ -250,17 +249,18 @@ impl Trait for X { proj_ty, values.expected, )) { - diag.help(msg); + diag.help(msg()); diag.note( "for more information, visit \ https://doc.rust-lang.org/book/ch19-03-advanced-traits.html", ); } } - (ty::Alias(ty::Opaque, alias), _) | (_, ty::Alias(ty::Opaque, alias)) if alias.def_id.is_local() && matches!(tcx.def_kind(body_owner_def_id), DefKind::AssocFn | DefKind::AssocConst) => { + (ty::Alias(ty::Opaque, alias), _) | (_, ty::Alias(ty::Opaque, alias)) if alias.def_id.is_local() && matches!(tcx.def_kind(body_owner_def_id), DefKind::Fn | DefKind::Static(_) | DefKind::Const | DefKind::AssocFn | DefKind::AssocConst) => { if tcx.is_type_alias_impl_trait(alias.def_id) { if !tcx.opaque_types_defined_by(body_owner_def_id.expect_local()).contains(&alias.def_id.expect_local()) { - diag.span_note(tcx.def_span(body_owner_def_id), "\ + let sp = tcx.def_ident_span(body_owner_def_id).unwrap_or_else(|| tcx.def_span(body_owner_def_id)); + diag.span_note(sp, "\ this item must have the opaque type in its signature \ in order to be able to register hidden types"); } @@ -308,14 +308,14 @@ impl Trait for X { fn suggest_constraint( &self, diag: &mut Diagnostic, - msg: &str, + msg: impl Fn() -> String, body_owner_def_id: DefId, proj_ty: &ty::AliasTy<'tcx>, ty: Ty<'tcx>, ) -> bool { let tcx = self.tcx; let assoc = tcx.associated_item(proj_ty.def_id); - let (trait_ref, assoc_substs) = proj_ty.trait_ref_and_own_substs(tcx); + let (trait_ref, assoc_args) = proj_ty.trait_ref_and_own_args(tcx); if let Some(item) = tcx.hir().get_if_local(body_owner_def_id) { if let Some(hir_generics) = item.generics() { // Get the `DefId` for the type parameter corresponding to `A` in `::Foo`. @@ -338,9 +338,9 @@ impl Trait for X { &trait_ref, pred.bounds, assoc, - assoc_substs, + assoc_args, ty, - msg, + &msg, false, ) { return true; @@ -374,10 +374,18 @@ impl Trait for X { ) { let tcx = self.tcx; - let msg = format!( - "consider constraining the associated type `{}` to `{}`", - values.expected, values.found - ); + // Don't suggest constraining a projection to something containing itself + if self.tcx.erase_regions(values.found).contains(self.tcx.erase_regions(values.expected)) { + return; + } + + let msg = || { + format!( + "consider constraining the associated type `{}` to `{}`", + values.expected, values.found + ) + }; + let body_owner = tcx.hir().get_if_local(body_owner_def_id); let current_method_ident = body_owner.and_then(|n| n.ident()).map(|i| i.name); @@ -428,10 +436,11 @@ impl Trait for X { if callable_scope { diag.help(format!( "{} or calling a method that returns `{}`", - msg, values.expected + msg(), + values.expected )); } else { - diag.help(msg); + diag.help(msg()); } diag.note( "for more information, visit \ @@ -463,7 +472,7 @@ fn foo(&self) -> Self::T { String::new() } fn suggest_constraining_opaque_associated_type( &self, diag: &mut Diagnostic, - msg: &str, + msg: impl Fn() -> String, proj_ty: &ty::AliasTy<'tcx>, ty: Ty<'tcx>, ) -> bool { @@ -478,14 +487,14 @@ fn foo(&self) -> Self::T { String::new() } return false; }; - let (trait_ref, assoc_substs) = proj_ty.trait_ref_and_own_substs(tcx); + let (trait_ref, assoc_args) = proj_ty.trait_ref_and_own_args(tcx); self.constrain_generic_bound_associated_type_structured_suggestion( diag, &trait_ref, opaque_hir_ty.bounds, assoc, - assoc_substs, + assoc_args, ty, msg, true, @@ -517,7 +526,7 @@ fn foo(&self) -> Self::T { String::new() } && !tcx.is_doc_hidden(item.def_id) }) .filter_map(|item| { - let method = tcx.fn_sig(item.def_id).subst_identity(); + let method = tcx.fn_sig(item.def_id).instantiate_identity(); match *method.output().skip_binder().kind() { ty::Alias(ty::Projection, ty::AliasTy { def_id: item_def_id, .. }) if item_def_id == proj_ty_item_def_id => @@ -564,7 +573,9 @@ fn foo(&self) -> Self::T { String::new() } let Some(hir_id) = body_owner_def_id.as_local() else { return false; }; - let hir_id = tcx.hir().local_def_id_to_hir_id(hir_id); + let Some(hir_id) = tcx.opt_local_def_id_to_hir_id(hir_id) else { + return false; + }; // When `body_owner` is an `impl` or `trait` item, look in its associated types for // `expected` and point at it. let parent_id = tcx.hir().get_parent_item(hir_id); @@ -583,9 +594,9 @@ fn foo(&self) -> Self::T { String::new() } // FIXME: account for returning some type in a trait fn impl that has // an assoc type as a return type (#72076). if let hir::Defaultness::Default { has_value: true } = - tcx.impl_defaultness(item.id.owner_id) + tcx.defaultness(item.id.owner_id) { - let assoc_ty = tcx.type_of(item.id.owner_id).subst_identity(); + let assoc_ty = tcx.type_of(item.id.owner_id).instantiate_identity(); if self.infcx.can_eq(param_env, assoc_ty, found) { diag.span_label( item.span, @@ -606,7 +617,7 @@ fn foo(&self) -> Self::T { String::new() } })) => { for item in &items[..] { if let hir::AssocItemKind::Type = item.kind { - let assoc_ty = tcx.type_of(item.id.owner_id).subst_identity(); + let assoc_ty = tcx.type_of(item.id.owner_id).instantiate_identity(); if self.infcx.can_eq(param_env, assoc_ty, found) { diag.span_label(item.span, "expected this associated type"); @@ -633,9 +644,9 @@ fn foo(&self) -> Self::T { String::new() } trait_ref: &ty::TraitRef<'tcx>, bounds: hir::GenericBounds<'_>, assoc: ty::AssocItem, - assoc_substs: &[ty::GenericArg<'tcx>], + assoc_args: &[ty::GenericArg<'tcx>], ty: Ty<'tcx>, - msg: &str, + msg: impl Fn() -> String, is_bound_surely_present: bool, ) -> bool { // FIXME: we would want to call `resolve_vars_if_possible` on `ty` before suggesting. @@ -659,14 +670,7 @@ fn foo(&self) -> Self::T { String::new() } _ => return false, }; - self.constrain_associated_type_structured_suggestion( - diag, - span, - assoc, - assoc_substs, - ty, - msg, - ) + self.constrain_associated_type_structured_suggestion(diag, span, assoc, assoc_args, ty, msg) } /// Given a span corresponding to a bound, provide a structured suggestion to set an @@ -676,9 +680,9 @@ fn foo(&self) -> Self::T { String::new() } diag: &mut Diagnostic, span: Span, assoc: ty::AssocItem, - assoc_substs: &[ty::GenericArg<'tcx>], + assoc_args: &[ty::GenericArg<'tcx>], ty: Ty<'tcx>, - msg: &str, + msg: impl Fn() -> String, ) -> bool { let tcx = self.tcx; @@ -690,10 +694,10 @@ fn foo(&self) -> Self::T { String::new() } let span = Span::new(pos, pos, span.ctxt(), span.parent()); (span, format!(", {} = {}", assoc.ident(tcx), ty)) } else { - let item_args = self.format_generic_args(assoc_substs); + let item_args = self.format_generic_args(assoc_args); (span.shrink_to_hi(), format!("<{}{} = {}>", assoc.ident(tcx), item_args, ty)) }; - diag.span_suggestion_verbose(span, msg, sugg, MaybeIncorrect); + diag.span_suggestion_verbose(span, msg(), sugg, MaybeIncorrect); return true; } false diff --git a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs index d885d040707e4..f1d53cb59cd46 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/suggest.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/suggest.rs @@ -13,9 +13,9 @@ use rustc_span::{sym, BytePos, Span}; use crate::errors::{ ConsiderAddingAwait, FnConsiderCasting, FnItemsAreDistinct, FnUniqTypes, - FunctionPointerSuggestion, SuggestAccessingField, SuggestAsRefWhereAppropriate, - SuggestBoxingForReturnImplTrait, SuggestRemoveSemiOrReturnBinding, SuggestTuplePatternMany, - SuggestTuplePatternOne, TypeErrorAdditionalDiags, + FunctionPointerSuggestion, SuggestAccessingField, SuggestBoxingForReturnImplTrait, + SuggestRemoveSemiOrReturnBinding, SuggestTuplePatternMany, SuggestTuplePatternOne, + TypeErrorAdditionalDiags, }; use super::TypeErrCtxt; @@ -105,7 +105,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { // Heavily inspired by `FnCtxt::suggest_compatible_variants`, with // some modifications due to that being in typeck and this being in infer. if let ObligationCauseCode::Pattern { .. } = cause.code() { - if let ty::Adt(expected_adt, substs) = exp_found.expected.kind() { + if let ty::Adt(expected_adt, args) = exp_found.expected.kind() { let compatible_variants: Vec<_> = expected_adt .variants() .iter() @@ -114,7 +114,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { }) .filter_map(|variant| { let sole_field = &variant.single_field(); - let sole_field_ty = sole_field.ty(self.tcx, substs); + let sole_field_ty = sole_field.ty(self.tcx, args); if self.same_type_modulo_infer(sole_field_ty, exp_found.found) { let variant_path = with_no_trimmed_paths!(self.tcx.def_path_str(variant.def_id)); @@ -260,7 +260,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { "suggest_accessing_field_where_appropriate(cause={:?}, exp_found={:?})", cause, exp_found ); - if let ty::Adt(expected_def, expected_substs) = exp_found.expected.kind() { + if let ty::Adt(expected_def, expected_args) = exp_found.expected.kind() { if expected_def.is_enum() { return; } @@ -270,7 +270,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { .fields .iter() .filter(|field| field.vis.is_accessible_from(field.did, self.tcx)) - .map(|field| (field.name, field.ty(self.tcx, expected_substs))) + .map(|field| (field.name, field.ty(self.tcx, expected_args))) .find(|(_, ty)| self.same_type_modulo_infer(*ty, exp_found.found)) { if let ObligationCauseCode::Pattern { span: Some(span), .. } = *cause.code() { @@ -289,27 +289,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } - /// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate, - /// suggests it. - pub(super) fn suggest_as_ref_where_appropriate( - &self, - span: Span, - exp_found: &ty::error::ExpectedFound>, - diag: &mut Diagnostic, - ) { - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && let Some(msg) = self.should_suggest_as_ref_kind(exp_found.expected, exp_found.found) - { - // HACK: fix issue# 100605, suggesting convert from &Option to Option<&T>, remove the extra `&` - let snippet = snippet.trim_start_matches('&'); - let subdiag = match msg { - SuggestAsRefKind::Option => SuggestAsRefWhereAppropriate::Option { span, snippet }, - SuggestAsRefKind::Result => SuggestAsRefWhereAppropriate::Result { span, snippet }, - }; - diag.subdiagnostic(subdiag); - } - } - pub(super) fn suggest_function_pointers( &self, cause: &ObligationCause<'tcx>, @@ -325,12 +304,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { return; } match (&expected_inner.kind(), &found_inner.kind()) { - (ty::FnPtr(sig), ty::FnDef(did, substs)) => { + (ty::FnPtr(sig), ty::FnDef(did, args)) => { let expected_sig = &(self.normalize_fn_sig)(*sig); let found_sig = - &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).subst(self.tcx, substs)); + &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).instantiate(self.tcx, args)); - let fn_name = self.tcx.def_path_str_with_substs(*did, substs); + let fn_name = self.tcx.def_path_str_with_args(*did, args); if !self.same_type_modulo_infer(*found_sig, *expected_sig) || !sig.is_suggestable(self.tcx, true) @@ -353,11 +332,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { }; diag.subdiagnostic(sugg); } - (ty::FnDef(did1, substs1), ty::FnDef(did2, substs2)) => { + (ty::FnDef(did1, args1), ty::FnDef(did2, args2)) => { let expected_sig = - &(self.normalize_fn_sig)(self.tcx.fn_sig(*did1).subst(self.tcx, substs1)); + &(self.normalize_fn_sig)(self.tcx.fn_sig(*did1).instantiate(self.tcx, args1)); let found_sig = - &(self.normalize_fn_sig)(self.tcx.fn_sig(*did2).subst(self.tcx, substs2)); + &(self.normalize_fn_sig)(self.tcx.fn_sig(*did2).instantiate(self.tcx, args2)); if self.same_type_modulo_infer(*expected_sig, *found_sig) { diag.subdiagnostic(FnUniqTypes); @@ -372,7 +351,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { return; } - let fn_name = self.tcx.def_path_str_with_substs(*did2, substs2); + let fn_name = self.tcx.def_path_str_with_args(*did2, args2); let sug = if found.is_ref() { FunctionPointerSuggestion::CastBothRef { span, @@ -391,16 +370,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { diag.subdiagnostic(sug); } - (ty::FnDef(did, substs), ty::FnPtr(sig)) => { + (ty::FnDef(did, args), ty::FnPtr(sig)) => { let expected_sig = - &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).subst(self.tcx, substs)); + &(self.normalize_fn_sig)(self.tcx.fn_sig(*did).instantiate(self.tcx, args)); let found_sig = &(self.normalize_fn_sig)(*sig); if !self.same_type_modulo_infer(*found_sig, *expected_sig) { return; } - let fn_name = self.tcx.def_path_str_with_substs(*did, substs); + let fn_name = self.tcx.def_path_str_with_args(*did, args); let casting = if expected.is_ref() { format!("&({fn_name} as {found_sig})") @@ -421,10 +400,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { expected: Ty<'tcx>, found: Ty<'tcx>, ) -> Option { - if let (ty::Adt(exp_def, exp_substs), ty::Ref(_, found_ty, _)) = + if let (ty::Adt(exp_def, exp_args), ty::Ref(_, found_ty, _)) = (expected.kind(), found.kind()) { - if let ty::Adt(found_def, found_substs) = *found_ty.kind() { + if let ty::Adt(found_def, found_args) = *found_ty.kind() { if exp_def == &found_def { let have_as_ref = &[ (sym::Option, SuggestAsRefKind::Option), @@ -435,7 +414,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { }) { let mut show_suggestion = true; for (exp_ty, found_ty) in - std::iter::zip(exp_substs.types(), found_substs.types()) + std::iter::zip(exp_args.types(), found_args.types()) { match *exp_ty.kind() { ty::Ref(_, exp_ty, _) => { @@ -485,52 +464,53 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { span: Span, ) -> Option { let hir = self.tcx.hir(); - if let Some(node) = self.tcx.hir().find_by_def_id(cause.body_id) && - let hir::Node::Item(hir::Item { - kind: hir::ItemKind::Fn(_sig, _, body_id), .. - }) = node { - let body = hir.body(*body_id); - - /// Find the if expression with given span - struct IfVisitor { - pub result: bool, - pub found_if: bool, - pub err_span: Span, - } + if let Some(body_id) = self.tcx.hir().maybe_body_owned_by(cause.body_id) { + let body = hir.body(body_id); + + /// Find the if expression with given span + struct IfVisitor { + pub result: bool, + pub found_if: bool, + pub err_span: Span, + } - impl<'v> Visitor<'v> for IfVisitor { - fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { - if self.result { return; } - match ex.kind { - hir::ExprKind::If(cond, _, _) => { - self.found_if = true; - walk_expr(self, cond); - self.found_if = false; + impl<'v> Visitor<'v> for IfVisitor { + fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + if self.result { + return; + } + match ex.kind { + hir::ExprKind::If(cond, _, _) => { + self.found_if = true; + walk_expr(self, cond); + self.found_if = false; + } + _ => walk_expr(self, ex), } - _ => walk_expr(self, ex), } - } - fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { - if let hir::StmtKind::Local(hir::Local { - span, pat: hir::Pat{..}, ty: None, init: Some(_), .. - }) = &ex.kind - && self.found_if - && span.eq(&self.err_span) { - self.result = true; + fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { + if let hir::StmtKind::Local(hir::Local { + span, pat: hir::Pat{..}, ty: None, init: Some(_), .. + }) = &ex.kind + && self.found_if + && span.eq(&self.err_span) { + self.result = true; + } + walk_stmt(self, ex); } - walk_stmt(self, ex); - } - fn visit_body(&mut self, body: &'v hir::Body<'v>) { - hir::intravisit::walk_body(self, body); + fn visit_body(&mut self, body: &'v hir::Body<'v>) { + hir::intravisit::walk_body(self, body); + } } - } - let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; - visitor.visit_body(&body); - if visitor.result { - return Some(TypeErrorAdditionalDiags::AddLetForLetChains{span: span.shrink_to_lo()}); + let mut visitor = IfVisitor { err_span: span, found_if: false, result: false }; + visitor.visit_body(&body); + if visitor.result { + return Some(TypeErrorAdditionalDiags::AddLetForLetChains { + span: span.shrink_to_lo(), + }); } } None @@ -546,13 +526,23 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { diag: &mut Diagnostic, ) { // 0. Extract fn_decl from hir - let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(hir::Closure { body, fn_decl, .. }), .. }) = hir else { return; }; + let hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(hir::Closure { body, fn_decl, .. }), + .. + }) = hir + else { + return; + }; let hir::Body { params, .. } = self.tcx.hir().body(*body); - // 1. Get the substs of the closure. + // 1. Get the args of the closure. // 2. Assume exp_found is FnOnce / FnMut / Fn, we can extract function parameters from [1]. - let Some(expected) = exp_found.expected.skip_binder().substs.get(1) else { return; }; - let Some(found) = exp_found.found.skip_binder().substs.get(1) else { return; }; + let Some(expected) = exp_found.expected.skip_binder().args.get(1) else { + return; + }; + let Some(found) = exp_found.found.skip_binder().args.get(1) else { + return; + }; let expected = expected.unpack(); let found = found.unpack(); // 3. Extract the tuple type from Fn trait and suggest the change. @@ -585,12 +575,12 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { if param_hir.pat.span == param_hir.ty_span { // for `|x|`, `|_|`, `|x: impl Foo|` let Ok(pat) = self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span) else { return; }; - suggestion += &format!("{}: &_", pat); + suggestion += &format!("{pat}: &_"); } else { // for `|x: ty|`, `|_: ty|` let Ok(pat) = self.tcx.sess.source_map().span_to_snippet(param_hir.pat.span) else { return; }; let Ok(ty) = self.tcx.sess.source_map().span_to_snippet(param_hir.ty_span) else { return; }; - suggestion += &format!("{}: &{}", pat, ty); + suggestion += &format!("{pat}: &{ty}"); } has_suggestion = true; } else { @@ -641,8 +631,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, .. }), ) if last_def_id == exp_def_id => StatementAsExpression::CorrectType, ( - ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, substs: last_bounds, .. }), - ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, substs: exp_bounds, .. }), + ty::Alias(ty::Opaque, ty::AliasTy { def_id: last_def_id, args: last_bounds, .. }), + ty::Alias(ty::Opaque, ty::AliasTy { def_id: exp_def_id, args: exp_bounds, .. }), ) => { debug!( "both opaque, likely future {:?} {:?} {:?} {:?}", @@ -731,7 +721,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let hir = self.tcx.hir(); for stmt in blk.stmts.iter().rev() { - let hir::StmtKind::Local(local) = &stmt.kind else { continue; }; + let hir::StmtKind::Local(local) = &stmt.kind else { + continue; + }; local.pat.walk(&mut find_compatible_candidates); } match hir.find_parent(blk.hir_id) { diff --git a/compiler/rustc_infer/src/infer/freshen.rs b/compiler/rustc_infer/src/infer/freshen.rs index 0219167f6e58b..689945d644c48 100644 --- a/compiler/rustc_infer/src/infer/freshen.rs +++ b/compiler/rustc_infer/src/infer/freshen.rs @@ -11,7 +11,7 @@ //! //! To handle closures, freshened types also have to contain the signature and kind of any //! closure in the local inference context, as otherwise the cache key might be invalidated. -//! The way this is done is somewhat hacky - the closure signature is appended to the substs, +//! The way this is done is somewhat hacky - the closure signature is appended to the args, //! as well as the closure kind "encoded" as a type. Also, special handling is needed when //! the closure signature contains a reference to the original closure. //! @@ -95,7 +95,7 @@ impl<'a, 'tcx> TypeFreshener<'a, 'tcx> { Entry::Vacant(entry) => { let index = self.const_freshen_count; self.const_freshen_count += 1; - let ct = self.infcx.tcx.mk_const(freshener(index), ty); + let ct = ty::Const::new_infer(self.infcx.tcx, freshener(index), ty); entry.insert(ct); ct } @@ -188,7 +188,7 @@ impl<'a, 'tcx> TypeFreshener<'a, 'tcx> { match v { ty::TyVar(v) => { let opt_ty = self.infcx.inner.borrow_mut().type_variables().probe(v).known(); - Some(self.freshen_ty(opt_ty, ty::TyVar(v), |n| self.infcx.tcx.mk_fresh_ty(n))) + Some(self.freshen_ty(opt_ty, ty::TyVar(v), |n| Ty::new_fresh(self.infcx.tcx, n))) } ty::IntVar(v) => Some( @@ -200,7 +200,7 @@ impl<'a, 'tcx> TypeFreshener<'a, 'tcx> { .probe_value(v) .map(|v| v.to_type(self.infcx.tcx)), ty::IntVar(v), - |n| self.infcx.tcx.mk_fresh_int_ty(n), + |n| Ty::new_fresh_int(self.infcx.tcx, n), ), ), @@ -213,7 +213,7 @@ impl<'a, 'tcx> TypeFreshener<'a, 'tcx> { .probe_value(v) .map(|v| v.to_type(self.infcx.tcx)), ty::FloatVar(v), - |n| self.infcx.tcx.mk_fresh_float_ty(n), + |n| Ty::new_fresh_float(self.infcx.tcx, n), ), ), diff --git a/compiler/rustc_infer/src/infer/generalize.rs b/compiler/rustc_infer/src/infer/generalize.rs index d4a1dacde1047..cf674d5dda65d 100644 --- a/compiler/rustc_infer/src/infer/generalize.rs +++ b/compiler/rustc_infer/src/infer/generalize.rs @@ -173,21 +173,21 @@ where true } - fn relate_item_substs( + fn relate_item_args( &mut self, item_def_id: DefId, - a_subst: ty::SubstsRef<'tcx>, - b_subst: ty::SubstsRef<'tcx>, - ) -> RelateResult<'tcx, ty::SubstsRef<'tcx>> { + a_subst: ty::GenericArgsRef<'tcx>, + b_subst: ty::GenericArgsRef<'tcx>, + ) -> RelateResult<'tcx, ty::GenericArgsRef<'tcx>> { if self.ambient_variance == ty::Variance::Invariant { // Avoid fetching the variance if we are in an invariant // context; no need, and it can induce dependency cycles // (e.g., #41849). - relate::relate_substs(self, a_subst, b_subst) + relate::relate_args(self, a_subst, b_subst) } else { let tcx = self.tcx(); let opt_variances = tcx.variances_of(item_def_id); - relate::relate_substs_with_variances( + relate::relate_args_with_variances( self, item_def_id, opt_variances, @@ -277,7 +277,7 @@ where let origin = *inner.type_variables().var_origin(vid); let new_var_id = inner.type_variables().new_var(self.for_universe, origin); - let u = self.tcx().mk_ty_var(new_var_id); + let u = Ty::new_var(self.tcx(), new_var_id); // Record that we replaced `vid` with `new_var_id` as part of a generalization // operation. This is needed to detect cyclic types. To see why, see the @@ -398,21 +398,25 @@ where origin: var_value.origin, val: ConstVariableValue::Unknown { universe: self.for_universe }, }); - Ok(self.tcx().mk_const(new_var_id, c.ty())) + Ok(ty::Const::new_var(self.tcx(), new_var_id, c.ty())) } } } } // FIXME: remove this branch once `structurally_relate_consts` is fully // structural. - ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, substs }) => { - let substs = self.relate_with_variance( + ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, args }) => { + let args = self.relate_with_variance( ty::Variance::Invariant, ty::VarianceDiagInfo::default(), - substs, - substs, + args, + args, )?; - Ok(self.tcx().mk_const(ty::UnevaluatedConst { def, substs }, c.ty())) + Ok(ty::Const::new_unevaluated( + self.tcx(), + ty::UnevaluatedConst { def, args }, + c.ty(), + )) } ty::ConstKind::Placeholder(placeholder) => { if self.for_universe.can_name(placeholder.universe) { diff --git a/compiler/rustc_infer/src/infer/higher_ranked/mod.rs b/compiler/rustc_infer/src/infer/higher_ranked/mod.rs index c304cd25c9c41..510b1797d3c9b 100644 --- a/compiler/rustc_infer/src/infer/higher_ranked/mod.rs +++ b/compiler/rustc_infer/src/infer/higher_ranked/mod.rs @@ -6,7 +6,7 @@ use super::{HigherRankedType, InferCtxt}; use crate::infer::CombinedSnapshot; use rustc_middle::ty::fold::FnMutDelegate; use rustc_middle::ty::relate::{Relate, RelateResult, TypeRelation}; -use rustc_middle::ty::{self, Binder, TyCtxt, TypeFoldable}; +use rustc_middle::ty::{self, Binder, Ty, TyCtxt, TypeFoldable}; impl<'a, 'tcx> CombineFields<'a, 'tcx> { /// Checks whether `for<..> sub <: for<..> sup` holds. @@ -82,17 +82,20 @@ impl<'tcx> InferCtxt<'tcx> { let delegate = FnMutDelegate { regions: &mut |br: ty::BoundRegion| { - self.tcx - .mk_re_placeholder(ty::PlaceholderRegion { universe: next_universe, bound: br }) + ty::Region::new_placeholder( + self.tcx, + ty::PlaceholderRegion { universe: next_universe, bound: br }, + ) }, types: &mut |bound_ty: ty::BoundTy| { - self.tcx.mk_placeholder(ty::PlaceholderType { - universe: next_universe, - bound: bound_ty, - }) + Ty::new_placeholder( + self.tcx, + ty::PlaceholderType { universe: next_universe, bound: bound_ty }, + ) }, consts: &mut |bound_var: ty::BoundVar, ty| { - self.tcx.mk_const( + ty::Const::new_placeholder( + self.tcx, ty::PlaceholderConst { universe: next_universe, bound: bound_var }, ty, ) @@ -103,13 +106,15 @@ impl<'tcx> InferCtxt<'tcx> { self.tcx.replace_bound_vars_uncached(binder, delegate) } - /// See [RegionConstraintCollector::leak_check][1]. + /// See [RegionConstraintCollector::leak_check][1]. We only check placeholder + /// leaking into `outer_universe`, i.e. placeholders which cannot be named by that + /// universe. /// /// [1]: crate::infer::region_constraints::RegionConstraintCollector::leak_check pub fn leak_check( &self, - overly_polymorphic: bool, - snapshot: &CombinedSnapshot<'tcx>, + outer_universe: ty::UniverseIndex, + only_consider_snapshot: Option<&CombinedSnapshot<'tcx>>, ) -> RelateResult<'tcx, ()> { // If the user gave `-Zno-leak-check`, or we have been // configured to skip the leak check, then skip the leak check @@ -117,15 +122,15 @@ impl<'tcx> InferCtxt<'tcx> { // subtyping errors that it would have caught will now be // caught later on, during region checking. However, we // continue to use it for a transition period. - if self.tcx.sess.opts.unstable_opts.no_leak_check || self.skip_leak_check.get() { + if self.tcx.sess.opts.unstable_opts.no_leak_check || self.skip_leak_check { return Ok(()); } self.inner.borrow_mut().unwrap_region_constraints().leak_check( self.tcx, - overly_polymorphic, + outer_universe, self.universe(), - snapshot, + only_consider_snapshot, ) } } diff --git a/compiler/rustc_infer/src/infer/lattice.rs b/compiler/rustc_infer/src/infer/lattice.rs index 7190d33d299b7..9ef35429fe32c 100644 --- a/compiler/rustc_infer/src/infer/lattice.rs +++ b/compiler/rustc_infer/src/infer/lattice.rs @@ -113,7 +113,7 @@ where | (_, &ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. })) if this.define_opaque_types() == DefineOpaqueTypes::Yes && def_id.is_local() - && !this.tcx().trait_solver_next() => + && !this.infcx().next_trait_solver() => { this.register_obligations( infcx diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs index 8482ae2aa38c8..60d9d6578f54d 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs @@ -347,7 +347,7 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { // name the placeholder, then the placeholder is // larger; otherwise, the only ancestor is `'static`. Err(placeholder) if empty_ui.can_name(placeholder.universe) => { - self.tcx().mk_re_placeholder(placeholder) + ty::Region::new_placeholder(self.tcx(), placeholder) } Err(_) => self.tcx().lifetimes.re_static, }; @@ -837,9 +837,8 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { self.var_infos[node_idx].origin.span(), format!( "collect_error_for_expanding_node() could not find \ - error for var {:?} in universe {:?}, lower_bounds={:#?}, \ - upper_bounds={:#?}", - node_idx, node_universe, lower_bounds, upper_bounds + error for var {node_idx:?} in universe {node_universe:?}, lower_bounds={lower_bounds:#?}, \ + upper_bounds={upper_bounds:#?}" ), ); } @@ -943,6 +942,10 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { generic_ty: Ty<'tcx>, min: ty::Region<'tcx>, ) -> bool { + if let ty::ReError(_) = *min { + return true; + } + match bound { VerifyBound::IfEq(verify_if_eq_b) => { let verify_if_eq_b = var_values.normalize(self.region_rels.tcx, *verify_if_eq_b); diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index cd99fc312129b..aaabf1482e2d2 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -6,6 +6,7 @@ pub use self::RegionVariableOrigin::*; pub use self::SubregionOrigin::*; pub use self::ValuePairs::*; pub use combine::ObligationEmittingRelation; +use rustc_data_structures::undo_log::UndoLogs; use self::opaque_types::OpaqueTypeStorage; pub(crate) use self::undo_log::{InferCtxtUndoLogs, Snapshot, UndoLog}; @@ -29,11 +30,11 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::fold::BoundVarReplacerDelegate; use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::relate::RelateResult; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind, InternalSubsts, SubstsRef}; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; pub use rustc_middle::ty::IntVarValue; use rustc_middle::ty::{self, GenericParamDefKind, InferConst, InferTy, Ty, TyCtxt}; use rustc_middle::ty::{ConstVid, FloatVid, IntVid, TyVid}; +use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgs, GenericArgsRef}; use rustc_span::symbol::Symbol; use rustc_span::Span; @@ -251,14 +252,13 @@ pub struct InferCtxt<'tcx> { /// solving is left to borrowck instead. pub considering_regions: bool, - pub inner: RefCell>, - /// If set, this flag causes us to skip the 'leak check' during /// higher-ranked subtyping operations. This flag is a temporary one used /// to manage the removal of the leak-check: for the time being, we still run the - /// leak-check, but we issue warnings. This flag can only be set to true - /// when entering a snapshot. - skip_leak_check: Cell, + /// leak-check, but we issue warnings. + skip_leak_check: bool, + + pub inner: RefCell>, /// Once region inference is done, the values for each variable. lexical_region_resolutions: RefCell>>, @@ -298,9 +298,6 @@ pub struct InferCtxt<'tcx> { // FIXME(matthewjasper) Merge into `tainted_by_errors` err_count_on_creation: usize, - /// This flag is true while there is an active snapshot. - in_snapshot: Cell, - /// What is the innermost universe we have created? Starts out as /// `UniverseIndex::root()` but grows from there as we enter /// universal quantifiers. @@ -331,6 +328,41 @@ pub struct InferCtxt<'tcx> { /// there is no type that the user could *actually name* that /// would satisfy it. This avoids crippling inference, basically. pub intercrate: bool, + + next_trait_solver: bool, +} + +impl<'tcx> ty::InferCtxtLike> for InferCtxt<'tcx> { + fn universe_of_ty(&self, ty: ty::InferTy) -> Option { + use InferTy::*; + match ty { + // FIXME(BoxyUwU): this is kind of jank and means that printing unresolved + // ty infers will give you the universe of the var it resolved to not the universe + // it actually had. It also means that if you have a `?0.1` and infer it to `u8` then + // try to print out `?0.1` it will just print `?0`. + TyVar(ty_vid) => match self.probe_ty_var(ty_vid) { + Err(universe) => Some(universe), + Ok(_) => None, + }, + IntVar(_) | FloatVar(_) | FreshTy(_) | FreshIntTy(_) | FreshFloatTy(_) => None, + } + } + + fn universe_of_ct(&self, ct: ty::InferConst<'tcx>) -> Option { + use ty::InferConst::*; + match ct { + // Same issue as with `universe_of_ty` + Var(ct_vid) => match self.probe_const_var(ct_vid) { + Err(universe) => Some(universe), + Ok(_) => None, + }, + Fresh(_) => None, + } + } + + fn universe_of_lt(&self, lt: ty::RegionVid) -> Option { + Some(self.universe_of_region_vid(lt)) + } } /// See the `error_reporting` module for more details. @@ -342,6 +374,8 @@ pub enum ValuePairs<'tcx> { TraitRefs(ExpectedFound>), PolyTraitRefs(ExpectedFound>), Sigs(ExpectedFound>), + ExistentialTraitRef(ExpectedFound>), + ExistentialProjection(ExpectedFound>), } impl<'tcx> ValuePairs<'tcx> { @@ -543,8 +577,12 @@ pub struct InferCtxtBuilder<'tcx> { tcx: TyCtxt<'tcx>, defining_use_anchor: DefiningAnchor, considering_regions: bool, + skip_leak_check: bool, /// Whether we are in coherence mode. intercrate: bool, + /// Whether we should use the new trait solver in the local inference context, + /// which affects things like which solver is used in `predicate_may_hold`. + next_trait_solver: bool, } pub trait TyCtxtInferExt<'tcx> { @@ -557,7 +595,9 @@ impl<'tcx> TyCtxtInferExt<'tcx> for TyCtxt<'tcx> { tcx: self, defining_use_anchor: DefiningAnchor::Error, considering_regions: true, + skip_leak_check: false, intercrate: false, + next_trait_solver: self.next_trait_solver_globally(), } } } @@ -574,6 +614,11 @@ impl<'tcx> InferCtxtBuilder<'tcx> { self } + pub fn with_next_trait_solver(mut self, next_trait_solver: bool) -> Self { + self.next_trait_solver = next_trait_solver; + self + } + pub fn intercrate(mut self, intercrate: bool) -> Self { self.intercrate = intercrate; self @@ -584,6 +629,11 @@ impl<'tcx> InferCtxtBuilder<'tcx> { self } + pub fn skip_leak_check(mut self, skip_leak_check: bool) -> Self { + self.skip_leak_check = skip_leak_check; + self + } + /// Given a canonical value `C` as a starting point, create an /// inference context that contains each of the bound values /// within instantiated as a fresh variable. The `f` closure is @@ -605,11 +655,19 @@ impl<'tcx> InferCtxtBuilder<'tcx> { } pub fn build(&mut self) -> InferCtxt<'tcx> { - let InferCtxtBuilder { tcx, defining_use_anchor, considering_regions, intercrate } = *self; + let InferCtxtBuilder { + tcx, + defining_use_anchor, + considering_regions, + skip_leak_check, + intercrate, + next_trait_solver, + } = *self; InferCtxt { tcx, defining_use_anchor, considering_regions, + skip_leak_check, inner: RefCell::new(InferCtxtInner::new()), lexical_region_resolutions: RefCell::new(None), selection_cache: Default::default(), @@ -618,10 +676,9 @@ impl<'tcx> InferCtxtBuilder<'tcx> { reported_closure_mismatch: Default::default(), tainted_by_errors: Cell::new(None), err_count_on_creation: tcx.sess.err_count(), - in_snapshot: Cell::new(false), - skip_leak_check: Cell::new(false), universe: Cell::new(ty::UniverseIndex::ROOT), intercrate, + next_trait_solver, } } } @@ -654,10 +711,13 @@ pub struct CombinedSnapshot<'tcx> { undo_snapshot: Snapshot<'tcx>, region_constraints_snapshot: RegionSnapshot, universe: ty::UniverseIndex, - was_in_snapshot: bool, } impl<'tcx> InferCtxt<'tcx> { + pub fn next_trait_solver(&self) -> bool { + self.next_trait_solver + } + /// Creates a `TypeErrCtxt` for emitting various inference errors. /// During typeck, use `FnCtxt::err_ctxt` instead. pub fn err_ctxt(&self) -> TypeErrCtxt<'_, 'tcx> { @@ -673,10 +733,6 @@ impl<'tcx> InferCtxt<'tcx> { } } - pub fn is_in_snapshot(&self) -> bool { - self.in_snapshot.get() - } - pub fn freshen>>(&self, t: T) -> T { t.fold_with(&mut self.freshener()) } @@ -704,19 +760,19 @@ impl<'tcx> InferCtxt<'tcx> { .type_variables() .unsolved_variables() .into_iter() - .map(|t| self.tcx.mk_ty_var(t)) + .map(|t| Ty::new_var(self.tcx, t)) .collect(); vars.extend( (0..inner.int_unification_table().len()) .map(|i| ty::IntVid { index: i as u32 }) .filter(|&vid| inner.int_unification_table().probe_value(vid).is_none()) - .map(|v| self.tcx.mk_int_var(v)), + .map(|v| Ty::new_int_var(self.tcx, v)), ); vars.extend( (0..inner.float_unification_table().len()) .map(|i| ty::FloatVid { index: i as u32 }) .filter(|&vid| inner.float_unification_table().probe_value(vid).is_none()) - .map(|v| self.tcx.mk_float_var(v)), + .map(|v| Ty::new_float_var(self.tcx, v)), ); vars } @@ -737,31 +793,30 @@ impl<'tcx> InferCtxt<'tcx> { } } + pub fn in_snapshot(&self) -> bool { + UndoLogs::>::in_snapshot(&self.inner.borrow_mut().undo_log) + } + + pub fn num_open_snapshots(&self) -> usize { + UndoLogs::>::num_open_snapshots(&self.inner.borrow_mut().undo_log) + } + fn start_snapshot(&self) -> CombinedSnapshot<'tcx> { debug!("start_snapshot()"); - let in_snapshot = self.in_snapshot.replace(true); - let mut inner = self.inner.borrow_mut(); CombinedSnapshot { undo_snapshot: inner.undo_log.start_snapshot(), region_constraints_snapshot: inner.unwrap_region_constraints().start_snapshot(), universe: self.universe(), - was_in_snapshot: in_snapshot, } } #[instrument(skip(self, snapshot), level = "debug")] fn rollback_to(&self, cause: &str, snapshot: CombinedSnapshot<'tcx>) { - let CombinedSnapshot { - undo_snapshot, - region_constraints_snapshot, - universe, - was_in_snapshot, - } = snapshot; - - self.in_snapshot.set(was_in_snapshot); + let CombinedSnapshot { undo_snapshot, region_constraints_snapshot, universe } = snapshot; + self.universe.set(universe); let mut inner = self.inner.borrow_mut(); @@ -771,14 +826,8 @@ impl<'tcx> InferCtxt<'tcx> { #[instrument(skip(self, snapshot), level = "debug")] fn commit_from(&self, snapshot: CombinedSnapshot<'tcx>) { - let CombinedSnapshot { - undo_snapshot, - region_constraints_snapshot: _, - universe: _, - was_in_snapshot, - } = snapshot; - - self.in_snapshot.set(was_in_snapshot); + let CombinedSnapshot { undo_snapshot, region_constraints_snapshot: _, universe: _ } = + snapshot; self.inner.borrow_mut().commit(undo_snapshot); } @@ -815,32 +864,9 @@ impl<'tcx> InferCtxt<'tcx> { r } - /// If `should_skip` is true, then execute `f` then unroll any bindings it creates. - #[instrument(skip(self, f), level = "debug")] - pub fn probe_maybe_skip_leak_check(&self, should_skip: bool, f: F) -> R - where - F: FnOnce(&CombinedSnapshot<'tcx>) -> R, - { - let snapshot = self.start_snapshot(); - let was_skip_leak_check = self.skip_leak_check.get(); - if should_skip { - self.skip_leak_check.set(true); - } - let r = f(&snapshot); - self.rollback_to("probe", snapshot); - self.skip_leak_check.set(was_skip_leak_check); - r - } - - /// Scan the constraints produced since `snapshot` began and returns: - /// - /// - `None` -- if none of them involves "region outlives" constraints. - /// - `Some(true)` -- if there are `'a: 'b` constraints where `'a` or `'b` is a placeholder. - /// - `Some(false)` -- if there are `'a: 'b` constraints but none involve placeholders. - pub fn region_constraints_added_in_snapshot( - &self, - snapshot: &CombinedSnapshot<'tcx>, - ) -> Option { + /// Scan the constraints produced since `snapshot` and check whether + /// we added any region constraints. + pub fn region_constraints_added_in_snapshot(&self, snapshot: &CombinedSnapshot<'tcx>) -> bool { self.inner .borrow_mut() .unwrap_region_constraints() @@ -987,7 +1013,7 @@ impl<'tcx> InferCtxt<'tcx> { } pub fn next_ty_var(&self, origin: TypeVariableOrigin) -> Ty<'tcx> { - self.tcx.mk_ty_var(self.next_ty_var_id(origin)) + Ty::new_var(self.tcx, self.next_ty_var_id(origin)) } pub fn next_ty_var_id_in_universe( @@ -1004,11 +1030,11 @@ impl<'tcx> InferCtxt<'tcx> { universe: ty::UniverseIndex, ) -> Ty<'tcx> { let vid = self.next_ty_var_id_in_universe(origin, universe); - self.tcx.mk_ty_var(vid) + Ty::new_var(self.tcx, vid) } pub fn next_const_var(&self, ty: Ty<'tcx>, origin: ConstVariableOrigin) -> ty::Const<'tcx> { - self.tcx.mk_const(self.next_const_var_id(origin), ty) + ty::Const::new_var(self.tcx, self.next_const_var_id(origin), ty) } pub fn next_const_var_in_universe( @@ -1022,7 +1048,7 @@ impl<'tcx> InferCtxt<'tcx> { .borrow_mut() .const_unification_table() .new_key(ConstVarValue { origin, val: ConstVariableValue::Unknown { universe } }); - self.tcx.mk_const(vid, ty) + ty::Const::new_var(self.tcx, vid, ty) } pub fn next_const_var_id(&self, origin: ConstVariableOrigin) -> ConstVid<'tcx> { @@ -1037,7 +1063,7 @@ impl<'tcx> InferCtxt<'tcx> { } pub fn next_int_var(&self) -> Ty<'tcx> { - self.tcx.mk_int_var(self.next_int_var_id()) + Ty::new_int_var(self.tcx, self.next_int_var_id()) } fn next_float_var_id(&self) -> FloatVid { @@ -1045,7 +1071,7 @@ impl<'tcx> InferCtxt<'tcx> { } pub fn next_float_var(&self) -> Ty<'tcx> { - self.tcx.mk_float_var(self.next_float_var_id()) + Ty::new_float_var(self.tcx, self.next_float_var_id()) } /// Creates a fresh region variable with the next available index. @@ -1065,7 +1091,7 @@ impl<'tcx> InferCtxt<'tcx> { ) -> ty::Region<'tcx> { let region_var = self.inner.borrow_mut().unwrap_region_constraints().new_region_var(universe, origin); - self.tcx.mk_re_var(region_var) + ty::Region::new_var(self.tcx, region_var) } /// Return the universe that the region `r` was created in. For @@ -1077,6 +1103,11 @@ impl<'tcx> InferCtxt<'tcx> { self.inner.borrow_mut().unwrap_region_constraints().universe(r) } + /// Return the universe that the region variable `r` was created in. + pub fn universe_of_region_vid(&self, vid: ty::RegionVid) -> ty::UniverseIndex { + self.inner.borrow_mut().unwrap_region_constraints().var_universe(vid) + } + /// Number of region variables created so far. pub fn num_region_vars(&self) -> usize { self.inner.borrow_mut().unwrap_region_constraints().num_region_vars() @@ -1119,13 +1150,13 @@ impl<'tcx> InferCtxt<'tcx> { TypeVariableOrigin { kind: TypeVariableOriginKind::TypeParameterDefinition( param.name, - Some(param.def_id), + param.def_id, ), span, }, ); - self.tcx.mk_ty_var(ty_var_id).into() + Ty::new_var(self.tcx, ty_var_id).into() } GenericParamDefKind::Const { .. } => { let origin = ConstVariableOrigin { @@ -1140,23 +1171,23 @@ impl<'tcx> InferCtxt<'tcx> { origin, val: ConstVariableValue::Unknown { universe: self.universe() }, }); - self.tcx - .mk_const( - const_var_id, - self.tcx - .type_of(param.def_id) - .no_bound_vars() - .expect("const parameter types cannot be generic"), - ) - .into() + ty::Const::new_var( + self.tcx, + const_var_id, + self.tcx + .type_of(param.def_id) + .no_bound_vars() + .expect("const parameter types cannot be generic"), + ) + .into() } } } /// Given a set of generics defined on a type or impl, returns a substitution mapping each /// type/region parameter to a fresh inference variable. - pub fn fresh_substs_for_item(&self, span: Span, def_id: DefId) -> SubstsRef<'tcx> { - InternalSubsts::for_item(self.tcx, def_id, |param, _| self.var_for_def(span, param)) + pub fn fresh_args_for_item(&self, span: Span, def_id: DefId) -> GenericArgsRef<'tcx> { + GenericArgs::for_item(self.tcx, def_id, |param, _| self.var_for_def(span, param)) } /// Returns `true` if errors have been reported since this infcx was @@ -1206,15 +1237,15 @@ impl<'tcx> InferCtxt<'tcx> { .var_origin(vid) } - /// Takes ownership of the list of variable regions. This implies - /// that all the region constraints have already been taken, and - /// hence that `resolve_regions_and_report_errors` can never be - /// called. This is used only during NLL processing to "hand off" ownership - /// of the set of region variables into the NLL region context. + /// Clone the list of variable regions. This is used only during NLL processing + /// to put the set of region variables into the NLL region context. pub fn get_region_var_origins(&self) -> VarInfos { let mut inner = self.inner.borrow_mut(); let (var_infos, data) = inner .region_constraint_storage + // We clone instead of taking because borrowck still wants to use + // the inference context after calling this for diagnostics + // and the new trait solver. .clone() .expect("regions already resolved") .with_log(&mut inner.undo_log) @@ -1274,7 +1305,7 @@ impl<'tcx> InferCtxt<'tcx> { if let Some(value) = inner.int_unification_table().probe_value(vid) { value.to_type(self.tcx) } else { - self.tcx.mk_int_var(inner.int_unification_table().find(vid)) + Ty::new_int_var(self.tcx, inner.int_unification_table().find(vid)) } } @@ -1285,7 +1316,7 @@ impl<'tcx> InferCtxt<'tcx> { if let Some(value) = inner.float_unification_table().probe_value(vid) { value.to_type(self.tcx) } else { - self.tcx.mk_float_var(inner.float_unification_table().find(vid)) + Ty::new_float_var(self.tcx, inner.float_unification_table().find(vid)) } } @@ -1445,8 +1476,8 @@ impl<'tcx> InferCtxt<'tcx> { /// Obtains the latest type of the given closure; this may be a /// closure in the current function, in which case its /// `ClosureKind` may not yet be known. - pub fn closure_kind(&self, closure_substs: SubstsRef<'tcx>) -> Option { - let closure_kind_ty = closure_substs.as_closure().kind_ty(); + pub fn closure_kind(&self, closure_args: GenericArgsRef<'tcx>) -> Option { + let closure_kind_ty = closure_args.as_closure().kind_ty(); let closure_kind_ty = self.shallow_resolve(closure_kind_ty); closure_kind_ty.to_opt_closure_kind() } @@ -1468,6 +1499,7 @@ impl<'tcx> InferCtxt<'tcx> { /// universes. Updates `self.universe` to that new universe. pub fn create_next_universe(&self) -> ty::UniverseIndex { let u = self.universe.get().next_universe(); + debug!("create_next_universe {u:?}"); self.universe.set(u); u } @@ -1480,7 +1512,7 @@ impl<'tcx> InferCtxt<'tcx> { span: Option, ) -> Result, ErrorHandled> { match self.const_eval_resolve(param_env, unevaluated, span) { - Ok(Some(val)) => Ok(self.tcx.mk_const(val, ty)), + Ok(Some(val)) => Ok(ty::Const::new_value(self.tcx, val, ty)), Ok(None) => { let tcx = self.tcx; let def_id = unevaluated.def; @@ -1504,7 +1536,7 @@ impl<'tcx> InferCtxt<'tcx> { /// too generic for the constant to be evaluated then `Err(ErrorHandled::TooGeneric)` is /// returned. /// - /// This handles inferences variables within both `param_env` and `substs` by + /// This handles inferences variables within both `param_env` and `args` by /// performing the operation on their respective canonical forms. #[instrument(skip(self), level = "debug")] pub fn const_eval_resolve( @@ -1513,34 +1545,34 @@ impl<'tcx> InferCtxt<'tcx> { unevaluated: ty::UnevaluatedConst<'tcx>, span: Option, ) -> EvalToValTreeResult<'tcx> { - let mut substs = self.resolve_vars_if_possible(unevaluated.substs); - debug!(?substs); + let mut args = self.resolve_vars_if_possible(unevaluated.args); + debug!(?args); - // Postpone the evaluation of constants whose substs depend on inference + // Postpone the evaluation of constants whose args depend on inference // variables let tcx = self.tcx; - if substs.has_non_region_infer() { + if args.has_non_region_infer() { if let Some(ct) = tcx.thir_abstract_const(unevaluated.def)? { - let ct = tcx.expand_abstract_consts(ct.subst(tcx, substs)); + let ct = tcx.expand_abstract_consts(ct.instantiate(tcx, args)); if let Err(e) = ct.error_reported() { return Err(ErrorHandled::Reported(e.into())); } else if ct.has_non_region_infer() || ct.has_non_region_param() { return Err(ErrorHandled::TooGeneric); } else { - substs = replace_param_and_infer_substs_with_placeholder(tcx, substs); + args = replace_param_and_infer_args_with_placeholder(tcx, args); } } else { - substs = InternalSubsts::identity_for_item(tcx, unevaluated.def); + args = GenericArgs::identity_for_item(tcx, unevaluated.def); param_env = tcx.param_env(unevaluated.def); } } let param_env_erased = tcx.erase_regions(param_env); - let substs_erased = tcx.erase_regions(substs); + let args_erased = tcx.erase_regions(args); debug!(?param_env_erased); - debug!(?substs_erased); + debug!(?args_erased); - let unevaluated = ty::UnevaluatedConst { def: unevaluated.def, substs: substs_erased }; + let unevaluated = ty::UnevaluatedConst { def: unevaluated.def, args: args_erased }; // The return value is the evaluated value which doesn't contain any reference to inference // variables, thus we don't need to substitute back the original values. @@ -1929,13 +1961,13 @@ impl RegionVariableOrigin { } } -/// Replaces substs that reference param or infer variables with suitable +/// Replaces args that reference param or infer variables with suitable /// placeholders. This function is meant to remove these param and infer -/// substs when they're not actually needed to evaluate a constant. -fn replace_param_and_infer_substs_with_placeholder<'tcx>( +/// args when they're not actually needed to evaluate a constant. +fn replace_param_and_infer_args_with_placeholder<'tcx>( tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, -) -> SubstsRef<'tcx> { + args: GenericArgsRef<'tcx>, +) -> GenericArgsRef<'tcx> { struct ReplaceParamAndInferWithPlaceholder<'tcx> { tcx: TyCtxt<'tcx>, idx: u32, @@ -1953,13 +1985,16 @@ fn replace_param_and_infer_substs_with_placeholder<'tcx>( self.idx += 1; idx }; - self.tcx.mk_placeholder(ty::PlaceholderType { - universe: ty::UniverseIndex::ROOT, - bound: ty::BoundTy { - var: ty::BoundVar::from_u32(idx), - kind: ty::BoundTyKind::Anon, + Ty::new_placeholder( + self.tcx, + ty::PlaceholderType { + universe: ty::UniverseIndex::ROOT, + bound: ty::BoundTy { + var: ty::BoundVar::from_u32(idx), + kind: ty::BoundTyKind::Anon, + }, }, - }) + ) } else { t.super_fold_with(self) } @@ -1972,7 +2007,8 @@ fn replace_param_and_infer_substs_with_placeholder<'tcx>( if ty.has_non_region_param() || ty.has_non_region_infer() { bug!("const `{c}`'s type should not reference params or types"); } - self.tcx.mk_const( + ty::Const::new_placeholder( + self.tcx, ty::PlaceholderConst { universe: ty::UniverseIndex::ROOT, bound: ty::BoundVar::from_u32({ @@ -1989,5 +2025,5 @@ fn replace_param_and_infer_substs_with_placeholder<'tcx>( } } - substs.fold_with(&mut ReplaceParamAndInferWithPlaceholder { tcx, idx: 0 }) + args.fold_with(&mut ReplaceParamAndInferWithPlaceholder { tcx, idx: 0 }) } diff --git a/compiler/rustc_infer/src/infer/nll_relate/mod.rs b/compiler/rustc_infer/src/infer/nll_relate/mod.rs index d3fd01b964255..c80491643913c 100644 --- a/compiler/rustc_infer/src/infer/nll_relate/mod.rs +++ b/compiler/rustc_infer/src/infer/nll_relate/mod.rs @@ -491,12 +491,12 @@ where ( &ty::Alias(ty::Opaque, ty::AliasTy { def_id: a_def_id, .. }), &ty::Alias(ty::Opaque, ty::AliasTy { def_id: b_def_id, .. }), - ) if a_def_id == b_def_id || infcx.tcx.trait_solver_next() => { + ) if a_def_id == b_def_id || infcx.next_trait_solver() => { infcx.super_combine_tys(self, a, b).or_else(|err| { // This behavior is only there for the old solver, the new solver // shouldn't ever fail. Instead, it unconditionally emits an // alias-relate goal. - assert!(!self.tcx().trait_solver_next()); + assert!(!self.infcx.next_trait_solver()); self.tcx().sess.delay_span_bug( self.delegate.span(), "failure to relate an opaque to itself should result in an error later on", @@ -506,7 +506,7 @@ where } (&ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }), _) | (_, &ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. })) - if def_id.is_local() && !self.tcx().trait_solver_next() => + if def_id.is_local() && !self.infcx.next_trait_solver() => { self.relate_opaques(a, b) } @@ -557,7 +557,7 @@ where // Forbid inference variables in the RHS. self.infcx.tcx.sess.delay_span_bug( self.delegate.span(), - format!("unexpected inference var {:?}", b,), + format!("unexpected inference var {b:?}",), ); Ok(a) } diff --git a/compiler/rustc_infer/src/infer/opaque_types.rs b/compiler/rustc_infer/src/infer/opaque_types.rs index 9d5ec228d827b..1c3a5c3607650 100644 --- a/compiler/rustc_infer/src/infer/opaque_types.rs +++ b/compiler/rustc_infer/src/infer/opaque_types.rs @@ -33,9 +33,6 @@ pub struct OpaqueTypeDecl<'tcx> { /// There can be multiple, but they are all `lub`ed together at the end /// to obtain the canonical hidden type. pub hidden_type: OpaqueHiddenType<'tcx>, - - /// The origin of the opaque type. - pub origin: hir::OpaqueTyOrigin, } impl<'tcx> InferCtxt<'tcx> { @@ -49,7 +46,7 @@ impl<'tcx> InferCtxt<'tcx> { param_env: ty::ParamEnv<'tcx>, ) -> InferOk<'tcx, T> { // We handle opaque types differently in the new solver. - if self.tcx.trait_solver_next() { + if self.next_trait_solver() { return InferOk { value, obligations: vec![] }; } @@ -67,7 +64,7 @@ impl<'tcx> InferCtxt<'tcx> { ct_op: |ct| ct, ty_op: |ty| match *ty.kind() { ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }) - if replace_opaque_type(def_id) => + if replace_opaque_type(def_id) && !ty.has_escaping_bound_vars() => { let def_span = self.tcx.def_span(def_id); let span = if span.contains(def_span) { def_span } else { span }; @@ -106,9 +103,9 @@ impl<'tcx> InferCtxt<'tcx> { } let (a, b) = if a_is_expected { (a, b) } else { (b, a) }; let process = |a: Ty<'tcx>, b: Ty<'tcx>, a_is_expected| match *a.kind() { - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) if def_id.is_local() => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) if def_id.is_local() => { let def_id = def_id.expect_local(); - let origin = match self.defining_use_anchor { + match self.defining_use_anchor { DefiningAnchor::Bind(_) => { // Check that this is `impl Trait` type is // declared by `parent_def_id` -- i.e., one whose @@ -144,9 +141,11 @@ impl<'tcx> InferCtxt<'tcx> { // let x = || foo(); // returns the Opaque assoc with `foo` // } // ``` - self.opaque_type_origin(def_id)? + if self.opaque_type_origin(def_id).is_none() { + return None; + } } - DefiningAnchor::Bubble => self.opaque_type_origin_unchecked(def_id), + DefiningAnchor::Bubble => {} DefiningAnchor::Error => return None, }; if let ty::Alias(ty::Opaque, ty::AliasTy { def_id: b_def_id, .. }) = *b.kind() { @@ -166,11 +165,10 @@ impl<'tcx> InferCtxt<'tcx> { } } Some(self.register_hidden_type( - OpaqueTypeKey { def_id, substs }, + OpaqueTypeKey { def_id, args }, cause.clone(), param_env, b, - origin, a_is_expected, )) } @@ -216,12 +214,12 @@ impl<'tcx> InferCtxt<'tcx> { /// fn foo<'a, 'b>(..) -> (Foo1<'a>, Foo2<'b>) { .. } /// // ^^^^ ^^ /// // | | - /// // | substs + /// // | args /// // def_id /// ``` /// /// As indicating in the comments above, each of those references - /// is (in the compiler) basically a substitution (`substs`) + /// is (in the compiler) basically a substitution (`args`) /// applied to the type of a suitable `def_id` (which identifies /// `Foo1` or `Foo2`). /// @@ -280,7 +278,7 @@ impl<'tcx> InferCtxt<'tcx> { /// /// We generally prefer to make `<=` constraints, since they /// integrate best into the region solver. To do that, we find the - /// "minimum" of all the arguments that appear in the substs: that + /// "minimum" of all the arguments that appear in the args: that /// is, some region which is less than all the others. In the case /// of `Foo1<'a>`, that would be `'a` (it's the only choice, after /// all). Then we apply that as a least bound to the variables @@ -352,7 +350,7 @@ impl<'tcx> InferCtxt<'tcx> { // opaque type definition. let choice_regions: Lrc>> = Lrc::new( opaque_type_key - .substs + .args .iter() .enumerate() .filter(|(i, _)| variances[*i] == ty::Variance::Invariant) @@ -380,7 +378,7 @@ impl<'tcx> InferCtxt<'tcx> { DefiningAnchor::Bind(bind) => bind, }; - let origin = self.opaque_type_origin_unchecked(def_id); + let origin = self.tcx.opaque_type_origin(def_id); let in_definition_scope = match origin { // Async `impl Trait` hir::OpaqueTyOrigin::AsyncFn(parent) => parent == parent_def_id, @@ -397,13 +395,6 @@ impl<'tcx> InferCtxt<'tcx> { }; in_definition_scope.then_some(origin) } - - /// Returns the origin of the opaque type `def_id` even if we are not in its - /// defining scope. - #[instrument(skip(self), level = "trace", ret)] - fn opaque_type_origin_unchecked(&self, def_id: LocalDefId) -> OpaqueTyOrigin { - self.tcx.hir().expect_item(def_id).expect_opaque_ty().origin - } } /// Visitor that requires that (almost) all regions in the type visited outlive @@ -454,39 +445,32 @@ where } match ty.kind() { - ty::Closure(_, ref substs) => { + ty::Closure(_, ref args) => { // Skip lifetime parameters of the enclosing item(s) - substs.as_closure().tupled_upvars_ty().visit_with(self); - substs.as_closure().sig_as_fn_ptr_ty().visit_with(self); + for upvar in args.as_closure().upvar_tys() { + upvar.visit_with(self); + } + args.as_closure().sig_as_fn_ptr_ty().visit_with(self); } - ty::Generator(_, ref substs, _) => { + ty::Generator(_, ref args, _) => { // Skip lifetime parameters of the enclosing item(s) // Also skip the witness type, because that has no free regions. - substs.as_generator().tupled_upvars_ty().visit_with(self); - substs.as_generator().return_ty().visit_with(self); - substs.as_generator().yield_ty().visit_with(self); - substs.as_generator().resume_ty().visit_with(self); - } - - ty::Alias(ty::Opaque, ty::AliasTy { def_id, ref substs, .. }) => { - // Skip lifetime parameters that are not captures. - let variances = self.tcx.variances_of(*def_id); - - for (v, s) in std::iter::zip(variances, substs.iter()) { - if *v != ty::Variance::Bivariant { - s.visit_with(self); - } + for upvar in args.as_generator().upvar_tys() { + upvar.visit_with(self); } + args.as_generator().return_ty().visit_with(self); + args.as_generator().yield_ty().visit_with(self); + args.as_generator().resume_ty().visit_with(self); } - ty::Alias(ty::Projection, proj) if self.tcx.is_impl_trait_in_trait(proj.def_id) => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, ref args, .. }) => { // Skip lifetime parameters that are not captures. - let variances = self.tcx.variances_of(proj.def_id); + let variances = self.tcx.variances_of(*def_id); - for (v, s) in std::iter::zip(variances, proj.substs.iter()) { + for (v, s) in std::iter::zip(variances, args.iter()) { if *v != ty::Variance::Bivariant { s.visit_with(self); } @@ -524,30 +508,22 @@ impl<'tcx> InferCtxt<'tcx> { cause: ObligationCause<'tcx>, param_env: ty::ParamEnv<'tcx>, hidden_ty: Ty<'tcx>, - origin: hir::OpaqueTyOrigin, a_is_expected: bool, ) -> InferResult<'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`, - // these are the same span, but not in cases like `-> (impl - // Foo, impl Bar)`. - let span = cause.span; - let prev = self.inner.borrow_mut().opaque_types().register( + let mut obligations = Vec::new(); + + self.insert_hidden_type( opaque_type_key, - OpaqueHiddenType { ty: hidden_ty, span }, - origin, - ); - let mut obligations = if let Some(prev) = prev { - self.at(&cause, param_env) - .eq_exp(DefineOpaqueTypes::Yes, a_is_expected, prev, hidden_ty)? - .obligations - } else { - Vec::new() - }; + &cause, + param_env, + hidden_ty, + a_is_expected, + &mut obligations, + )?; self.add_item_bounds_for_hidden_type( - opaque_type_key, + opaque_type_key.def_id.to_def_id(), + opaque_type_key.args, cause, param_env, hidden_ty, @@ -557,32 +533,60 @@ impl<'tcx> InferCtxt<'tcx> { Ok(InferOk { value: (), obligations }) } - /// Registers an opaque's hidden type -- only should be used when the opaque - /// can be defined. For something more fallible -- checks the anchors, tries - /// to unify opaques in both dirs, etc. -- use `InferCtxt::handle_opaque_type`. - pub fn register_hidden_type_in_new_solver( + /// Insert a hidden type into the opaque type storage, equating it + /// with any previous entries if necessary. + /// + /// This **does not** add the item bounds of the opaque as nested + /// obligations. That is only necessary when normalizing the opaque + /// itself, not when getting the opaque type constraints from + /// somewhere else. + pub fn insert_hidden_type( &self, opaque_type_key: OpaqueTypeKey<'tcx>, + cause: &ObligationCause<'tcx>, param_env: ty::ParamEnv<'tcx>, hidden_ty: Ty<'tcx>, - ) -> InferResult<'tcx, ()> { - assert!(self.tcx.trait_solver_next()); - let origin = self - .opaque_type_origin(opaque_type_key.def_id) - .expect("should be called for defining usages only"); - self.register_hidden_type( - opaque_type_key, - ObligationCause::dummy(), - param_env, - hidden_ty, - origin, - true, - ) + a_is_expected: bool, + obligations: &mut Vec>, + ) -> Result<(), TypeError<'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`, + // these are the same span, but not in cases like `-> (impl + // Foo, impl Bar)`. + let span = cause.span; + if self.intercrate { + // During intercrate we do not define opaque types but instead always + // force ambiguity unless the hidden type is known to not implement + // our trait. + obligations.push(traits::Obligation::new( + self.tcx, + cause.clone(), + param_env, + ty::PredicateKind::Ambiguous, + )) + } else { + let prev = self + .inner + .borrow_mut() + .opaque_types() + .register(opaque_type_key, OpaqueHiddenType { ty: hidden_ty, span }); + if let Some(prev) = prev { + obligations.extend( + self.at(&cause, param_env) + .eq_exp(DefineOpaqueTypes::Yes, a_is_expected, prev, hidden_ty)? + .obligations, + ); + } + }; + + Ok(()) } pub fn add_item_bounds_for_hidden_type( &self, - OpaqueTypeKey { def_id, substs }: OpaqueTypeKey<'tcx>, + def_id: DefId, + args: ty::GenericArgsRef<'tcx>, cause: ObligationCause<'tcx>, param_env: ty::ParamEnv<'tcx>, hidden_ty: Ty<'tcx>, @@ -591,7 +595,7 @@ impl<'tcx> InferCtxt<'tcx> { let tcx = self.tcx; let item_bounds = tcx.explicit_item_bounds(def_id); - for (predicate, _) in item_bounds.subst_iter_copied(tcx, substs) { + for (predicate, _) in item_bounds.iter_instantiated_copied(tcx, args) { let predicate = predicate.fold_with(&mut BottomUpFolder { tcx, ty_op: |ty| match *ty.kind() { @@ -602,7 +606,7 @@ impl<'tcx> InferCtxt<'tcx> { ty::Alias(ty::Projection, projection_ty) if !projection_ty.has_escaping_bound_vars() && !tcx.is_impl_trait_in_trait(projection_ty.def_id) - && !tcx.trait_solver_next() => + && !self.next_trait_solver() => { self.infer_projection( param_env, @@ -614,26 +618,18 @@ impl<'tcx> InferCtxt<'tcx> { } // Replace all other mentions of the same opaque type with the hidden type, // as the bounds must hold on the hidden type after all. - ty::Alias(ty::Opaque, ty::AliasTy { def_id: def_id2, substs: substs2, .. }) - if def_id.to_def_id() == def_id2 && substs == substs2 => + ty::Alias(ty::Opaque, ty::AliasTy { def_id: def_id2, args: args2, .. }) + if def_id == def_id2 && args == args2 => { hidden_ty } - // FIXME(RPITIT): This can go away when we move to associated types - // FIXME(inherent_associated_types): Extend this to support `ty::Inherent`, too. - ty::Alias( - ty::Projection, - ty::AliasTy { def_id: def_id2, substs: substs2, .. }, - ) if def_id.to_def_id() == def_id2 && substs == substs2 => hidden_ty, _ => ty, }, lt_op: |lt| lt, ct_op: |ct| ct, }); - if let ty::PredicateKind::Clause(ty::Clause::Projection(projection)) = - predicate.kind().skip_binder() - { + if let ty::ClauseKind::Projection(projection) = predicate.kind().skip_binder() { if projection.term.references_error() { // No point on adding any obligations since there's a type error involved. obligations.clear(); diff --git a/compiler/rustc_infer/src/infer/opaque_types/table.rs b/compiler/rustc_infer/src/infer/opaque_types/table.rs index a0f6d7ecab70f..a737761ba2282 100644 --- a/compiler/rustc_infer/src/infer/opaque_types/table.rs +++ b/compiler/rustc_infer/src/infer/opaque_types/table.rs @@ -1,5 +1,4 @@ use rustc_data_structures::undo_log::UndoLogs; -use rustc_hir::OpaqueTyOrigin; use rustc_middle::ty::{self, OpaqueHiddenType, OpaqueTypeKey, Ty}; use rustc_span::DUMMY_SP; @@ -60,14 +59,13 @@ impl<'a, 'tcx> OpaqueTypeTable<'a, 'tcx> { &mut self, key: OpaqueTypeKey<'tcx>, hidden_type: OpaqueHiddenType<'tcx>, - origin: OpaqueTyOrigin, ) -> Option> { if let Some(decl) = self.storage.opaque_types.get_mut(&key) { let prev = std::mem::replace(&mut decl.hidden_type, hidden_type); self.undo_log.push(UndoLog::OpaqueTypes(key, Some(prev))); return Some(prev.ty); } - let decl = OpaqueTypeDecl { hidden_type, origin }; + let decl = OpaqueTypeDecl { hidden_type }; self.storage.opaque_types.insert(key, decl); self.undo_log.push(UndoLog::OpaqueTypes(key, None)); None diff --git a/compiler/rustc_infer/src/infer/outlives/components.rs b/compiler/rustc_infer/src/infer/outlives/components.rs index cb63d2f18b634..2ac9568f60be7 100644 --- a/compiler/rustc_infer/src/infer/outlives/components.rs +++ b/compiler/rustc_infer/src/infer/outlives/components.rs @@ -3,8 +3,8 @@ // RFC for reference. use rustc_data_structures::sso::SsoHashSet; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{GenericArg, GenericArgKind}; use smallvec::{smallvec, SmallVec}; #[derive(Debug)] @@ -71,15 +71,15 @@ fn compute_components<'tcx>( // in the `subtys` iterator (e.g., when encountering a // projection). match *ty.kind() { - ty::FnDef(_, substs) => { - // HACK(eddyb) ignore lifetimes found shallowly in `substs`. - // This is inconsistent with `ty::Adt` (including all substs) - // and with `ty::Closure` (ignoring all substs other than + ty::FnDef(_, args) => { + // HACK(eddyb) ignore lifetimes found shallowly in `args`. + // This is inconsistent with `ty::Adt` (including all args) + // and with `ty::Closure` (ignoring all args other than // upvars, of which a `ty::FnDef` doesn't have any), but // consistent with previous (accidental) behavior. // See https://github.com/rust-lang/rust/issues/70917 // for further background and discussion. - for child in substs { + for child in args { match child.unpack() { GenericArgKind::Type(ty) => { compute_components(tcx, ty, out, visited); @@ -97,14 +97,14 @@ fn compute_components<'tcx>( compute_components(tcx, element, out, visited); } - ty::Closure(_, ref substs) => { - let tupled_ty = substs.as_closure().tupled_upvars_ty(); + ty::Closure(_, ref args) => { + let tupled_ty = args.as_closure().tupled_upvars_ty(); compute_components(tcx, tupled_ty, out, visited); } - ty::Generator(_, ref substs, _) => { + ty::Generator(_, ref args, _) => { // Same as the closure case - let tupled_ty = substs.as_generator().tupled_upvars_ty(); + let tupled_ty = args.as_generator().tupled_upvars_ty(); compute_components(tcx, tupled_ty, out, visited); // We ignore regions in the generator interior as we don't @@ -189,7 +189,7 @@ fn compute_components<'tcx>( } } -/// Collect [Component]s for *all* the substs of `parent`. +/// Collect [Component]s for *all* the args of `parent`. /// /// This should not be used to get the components of `parent` itself. /// Use [push_outlives_components] instead. @@ -201,7 +201,7 @@ pub(super) fn compute_alias_components_recursive<'tcx>( ) { let ty::Alias(kind, alias_ty) = alias_ty.kind() else { bug!() }; let opt_variances = if *kind == ty::Opaque { tcx.variances_of(alias_ty.def_id) } else { &[] }; - for (index, child) in alias_ty.substs.iter().enumerate() { + for (index, child) in alias_ty.args.iter().enumerate() { if opt_variances.get(index) == Some(&ty::Bivariant) { continue; } @@ -225,7 +225,7 @@ pub(super) fn compute_alias_components_recursive<'tcx>( } } -/// Collect [Component]s for *all* the substs of `parent`. +/// Collect [Component]s for *all* the args of `parent`. /// /// This should not be used to get the components of `parent` itself. /// Use [push_outlives_components] instead. diff --git a/compiler/rustc_infer/src/infer/outlives/mod.rs b/compiler/rustc_infer/src/infer/outlives/mod.rs index 8a44d5031596f..cb92fc6ddb64a 100644 --- a/compiler/rustc_infer/src/infer/outlives/mod.rs +++ b/compiler/rustc_infer/src/infer/outlives/mod.rs @@ -20,27 +20,18 @@ pub fn explicit_outlives_bounds<'tcx>( param_env .caller_bounds() .into_iter() - .map(ty::Predicate::kind) + .map(ty::Clause::kind) .filter_map(ty::Binder::no_bound_vars) .filter_map(move |kind| match kind { - ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::Trait(..)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => None, - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate( - r_a, - r_b, - ))) => Some(OutlivesBound::RegionSubRegion(r_b, r_a)), + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(r_a, r_b)) => { + Some(OutlivesBound::RegionSubRegion(r_b, r_a)) + } + ty::ClauseKind::Trait(_) + | ty::ClauseKind::TypeOutlives(_) + | ty::ClauseKind::Projection(_) + | ty::ClauseKind::ConstArgHasType(_, _) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => None, }) } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 9c20c814b697f..f36802e128435 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -68,8 +68,8 @@ use crate::infer::{ use crate::traits::{ObligationCause, ObligationCauseCode}; use rustc_data_structures::undo_log::UndoLogs; use rustc_middle::mir::ConstraintCategory; -use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, Region, SubstsRef, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::GenericArgKind; +use rustc_middle::ty::{self, GenericArgsRef, Region, Ty, TyCtxt, TypeVisitableExt}; use smallvec::smallvec; use super::env::OutlivesEnvironment; @@ -125,10 +125,7 @@ impl<'tcx> InferCtxt<'tcx> { /// right before lexical region resolution. #[instrument(level = "debug", skip(self, outlives_env))] pub fn process_registered_region_obligations(&self, outlives_env: &OutlivesEnvironment<'tcx>) { - assert!( - !self.in_snapshot.get(), - "cannot process registered region obligations in a snapshot" - ); + assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot"); let my_region_obligations = self.take_registered_region_obligations(); @@ -256,7 +253,7 @@ where // this point it never will be self.tcx.sess.delay_span_bug( origin.span(), - format!("unresolved inference variable in outlives: {:?}", v), + format!("unresolved inference variable in outlives: {v:?}"), ); } } @@ -282,7 +279,7 @@ where alias_ty: ty::AliasTy<'tcx>, ) { // An optimization for a common case with opaque types. - if alias_ty.substs.is_empty() { + if alias_ty.args.is_empty() { return; } @@ -351,7 +348,7 @@ where { debug!("no declared bounds"); let opt_variances = is_opaque.then(|| self.tcx.variances_of(alias_ty.def_id)); - self.substs_must_outlive(alias_ty.substs, origin, region, opt_variances); + self.args_must_outlive(alias_ty.args, origin, region, opt_variances); return; } @@ -398,15 +395,15 @@ where } #[instrument(level = "debug", skip(self))] - fn substs_must_outlive( + fn args_must_outlive( &mut self, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, origin: infer::SubregionOrigin<'tcx>, region: ty::Region<'tcx>, opt_variances: Option<&[ty::Variance]>, ) { let constraint = origin.to_constraint_category(); - for (index, k) in substs.iter().enumerate() { + for (index, k) in args.iter().enumerate() { match k.unpack() { GenericArgKind::Lifetime(lt) => { let variance = if let Some(variances) = opt_variances { diff --git a/compiler/rustc_infer/src/infer/outlives/test_type_match.rs b/compiler/rustc_infer/src/infer/outlives/test_type_match.rs index cd2462d3c3123..fefa8959545ab 100644 --- a/compiler/rustc_infer/src/infer/outlives/test_type_match.rs +++ b/compiler/rustc_infer/src/infer/outlives/test_type_match.rs @@ -157,7 +157,7 @@ impl<'tcx> TypeRelation<'tcx> for Match<'tcx> { a: T, b: T, ) -> RelateResult<'tcx, T> { - // Opaque types substs have lifetime parameters. + // Opaque types args have lifetime parameters. // We must not check them to be equal, as we never insert anything to make them so. if variance != ty::Bivariant { self.relate(a, b) } else { Ok(a) } } diff --git a/compiler/rustc_infer/src/infer/outlives/verify.rs b/compiler/rustc_infer/src/infer/outlives/verify.rs index c2bf0f3db253c..4279d0ab7ab3b 100644 --- a/compiler/rustc_infer/src/infer/outlives/verify.rs +++ b/compiler/rustc_infer/src/infer/outlives/verify.rs @@ -179,7 +179,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { // this point it never will be self.tcx.sess.delay_span_bug( rustc_span::DUMMY_SP, - format!("unresolved inference variable in outlives: {:?}", v), + format!("unresolved inference variable in outlives: {v:?}"), ); // add a bound that never holds VerifyBound::AnyBound(vec![]) @@ -223,7 +223,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { // parameter environments are already elaborated, so we don't // have to worry about that. let c_b = self.param_env.caller_bounds(); - let param_bounds = self.collect_outlives_from_predicate_list(erased_ty, c_b.into_iter()); + let param_bounds = self.collect_outlives_from_clause_list(erased_ty, c_b.into_iter()); // Next, collect regions we scraped from the well-formedness // constraints in the fn signature. To do that, we walk the list @@ -293,10 +293,10 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { ) -> impl Iterator> { let tcx = self.tcx; let bounds = tcx.item_bounds(alias_ty.def_id); - trace!("{:#?}", bounds.0); + trace!("{:#?}", bounds.skip_binder()); bounds - .subst_iter(tcx, alias_ty.substs) - .filter_map(|p| p.to_opt_type_outlives()) + .iter_instantiated(tcx, alias_ty.args) + .filter_map(|p| p.as_type_outlives_clause()) .filter_map(|p| p.no_bound_vars()) .map(|OutlivesPredicate(_, r)| r) } @@ -307,15 +307,15 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { /// when comparing `ty` for equality, so `ty` must be something /// that does not involve inference variables and where you /// otherwise want a precise match. - fn collect_outlives_from_predicate_list( + fn collect_outlives_from_clause_list( &self, erased_ty: Ty<'tcx>, - predicates: impl Iterator>, + clauses: impl Iterator>, ) -> impl Iterator, ty::Region<'tcx>>>> { let tcx = self.tcx; let param_env = self.param_env; - predicates.filter_map(|p| p.to_opt_type_outlives()).filter(move |outlives_predicate| { + clauses.filter_map(|p| p.as_type_outlives_clause()).filter(move |outlives_predicate| { super::test_type_match::can_match_erased_ty( tcx, param_env, diff --git a/compiler/rustc_infer/src/infer/projection.rs b/compiler/rustc_infer/src/infer/projection.rs index fa6529dfa93ec..38e74e538687e 100644 --- a/compiler/rustc_infer/src/infer/projection.rs +++ b/compiler/rustc_infer/src/infer/projection.rs @@ -21,28 +21,18 @@ impl<'tcx> InferCtxt<'tcx> { recursion_depth: usize, obligations: &mut Vec>, ) -> Ty<'tcx> { - if self.tcx.trait_solver_next() { - // FIXME(-Ztrait-solver=next): Instead of branching here, - // completely change the normalization routine with the new solver. - // - // The new solver correctly handles projection equality so this hack - // is not necessary. if re-enabled it should emit `PredicateKind::AliasRelate` - // not `PredicateKind::Clause(Clause::Projection(..))` as in the new solver - // `Projection` is used as `normalizes-to` which will fail for `::Assoc eq ?0`. - return projection_ty.to_ty(self.tcx); - } else { - let def_id = projection_ty.def_id; - let ty_var = self.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::NormalizeProjectionType, - span: self.tcx.def_span(def_id), - }); - let projection = ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::Projection( - ty::ProjectionPredicate { projection_ty, term: ty_var.into() }, - ))); - let obligation = - Obligation::with_depth(self.tcx, cause, recursion_depth, param_env, projection); - obligations.push(obligation); - ty_var - } + debug_assert!(!self.next_trait_solver()); + let def_id = projection_ty.def_id; + let ty_var = self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::NormalizeProjectionType, + span: self.tcx.def_span(def_id), + }); + let projection = ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::Projection( + ty::ProjectionPredicate { projection_ty, term: ty_var.into() }, + ))); + let obligation = + Obligation::with_depth(self.tcx, cause, recursion_depth, param_env, projection); + obligations.push(obligation); + ty_var } } diff --git a/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs b/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs index 89cfc9ea3d140..b6ff8f2f512de 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs @@ -3,7 +3,6 @@ use crate::infer::CombinedSnapshot; use rustc_data_structures::{ fx::FxIndexMap, graph::{scc::Sccs, vec_graph::VecGraph}, - undo_log::UndoLogs, }; use rustc_index::Idx; use rustc_middle::ty::error::TypeError; @@ -13,7 +12,9 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { /// Searches new universes created during `snapshot`, looking for /// placeholders that may "leak" out from the universes they are contained /// in. If any leaking placeholders are found, then an `Err` is returned - /// (typically leading to the snapshot being reversed). + /// (typically leading to the snapshot being reversed). This algorithm + /// only looks at placeholders which cannot be named by `outer_universe`, + /// as this is the universe we're currently checking for a leak. /// /// The leak check *used* to be the only way we had to handle higher-ranked /// obligations. Now that we have integrated universes into the region @@ -55,6 +56,12 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { /// * if they must also be equal to a placeholder P, and U cannot name P, report an error, as that /// indicates `P: R` and `R` is in an incompatible universe /// + /// To improve performance and for the old trait solver caching to be sound, this takes + /// an optional snapshot in which case we only look at region constraints added in that + /// snapshot. If we were to not do that the `leak_check` during evaluation can rely on + /// region constraints added outside of that evaluation. As that is not reflected in the + /// cache key this would be unsound. + /// /// # Historical note /// /// Older variants of the leak check used to report errors for these @@ -62,36 +69,21 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { /// /// * R: P1, even if R cannot name P1, because R = 'static is a valid sol'n /// * R: P1, R: P2, as above + #[instrument(level = "debug", skip(self, tcx, only_consider_snapshot), ret)] pub fn leak_check( &mut self, tcx: TyCtxt<'tcx>, - overly_polymorphic: bool, + outer_universe: ty::UniverseIndex, max_universe: ty::UniverseIndex, - snapshot: &CombinedSnapshot<'tcx>, + only_consider_snapshot: Option<&CombinedSnapshot<'tcx>>, ) -> RelateResult<'tcx, ()> { - debug!( - "leak_check(max_universe={:?}, snapshot.universe={:?}, overly_polymorphic={:?})", - max_universe, snapshot.universe, overly_polymorphic - ); - - assert!(UndoLogs::>::in_snapshot(&self.undo_log)); - - let universe_at_start_of_snapshot = snapshot.universe; - if universe_at_start_of_snapshot == max_universe { + if outer_universe == max_universe { return Ok(()); } - let mini_graph = - &MiniGraph::new(tcx, self.undo_log.region_constraints(), &self.storage.data.verifys); + let mini_graph = &MiniGraph::new(tcx, &self, only_consider_snapshot); - let mut leak_check = LeakCheck::new( - tcx, - universe_at_start_of_snapshot, - max_universe, - overly_polymorphic, - mini_graph, - self, - ); + let mut leak_check = LeakCheck::new(tcx, outer_universe, max_universe, mini_graph, self); leak_check.assign_placeholder_values()?; leak_check.propagate_scc_value()?; Ok(()) @@ -100,9 +92,7 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { struct LeakCheck<'me, 'tcx> { tcx: TyCtxt<'tcx>, - universe_at_start_of_snapshot: ty::UniverseIndex, - /// Only used when reporting region errors. - overly_polymorphic: bool, + outer_universe: ty::UniverseIndex, mini_graph: &'me MiniGraph<'tcx>, rcc: &'me RegionConstraintCollector<'me, 'tcx>, @@ -130,17 +120,15 @@ struct LeakCheck<'me, 'tcx> { impl<'me, 'tcx> LeakCheck<'me, 'tcx> { fn new( tcx: TyCtxt<'tcx>, - universe_at_start_of_snapshot: ty::UniverseIndex, + outer_universe: ty::UniverseIndex, max_universe: ty::UniverseIndex, - overly_polymorphic: bool, mini_graph: &'me MiniGraph<'tcx>, rcc: &'me RegionConstraintCollector<'me, 'tcx>, ) -> Self { let dummy_scc_universe = SccUniverse { universe: max_universe, region: None }; Self { tcx, - universe_at_start_of_snapshot, - overly_polymorphic, + outer_universe, mini_graph, rcc, scc_placeholders: IndexVec::from_elem_n(None, mini_graph.sccs.num_sccs()), @@ -165,7 +153,7 @@ impl<'me, 'tcx> LeakCheck<'me, 'tcx> { // Detect those SCCs that directly contain a placeholder if let ty::RePlaceholder(placeholder) = **region { - if self.universe_at_start_of_snapshot.cannot_name(placeholder.universe) { + if self.outer_universe.cannot_name(placeholder.universe) { self.assign_scc_value(scc, placeholder)?; } } @@ -280,7 +268,7 @@ impl<'me, 'tcx> LeakCheck<'me, 'tcx> { placeholder1: ty::PlaceholderRegion, placeholder2: ty::PlaceholderRegion, ) -> TypeError<'tcx> { - self.error(placeholder1, self.tcx.mk_re_placeholder(placeholder2)) + self.error(placeholder1, ty::Region::new_placeholder(self.tcx, placeholder2)) } fn error( @@ -289,11 +277,7 @@ impl<'me, 'tcx> LeakCheck<'me, 'tcx> { other_region: ty::Region<'tcx>, ) -> TypeError<'tcx> { debug!("error: placeholder={:?}, other_region={:?}", placeholder, other_region); - if self.overly_polymorphic { - TypeError::RegionsOverlyPolymorphic(placeholder.bound.kind, other_region) - } else { - TypeError::RegionsInsufficientlyPolymorphic(placeholder.bound.kind, other_region) - } + TypeError::RegionsInsufficientlyPolymorphic(placeholder.bound.kind, other_region) } } @@ -379,57 +363,73 @@ struct MiniGraph<'tcx> { } impl<'tcx> MiniGraph<'tcx> { - fn new<'a>( + fn new( tcx: TyCtxt<'tcx>, - undo_log: impl Iterator>, - verifys: &[Verify<'tcx>], - ) -> Self - where - 'tcx: 'a, - { + region_constraints: &RegionConstraintCollector<'_, 'tcx>, + only_consider_snapshot: Option<&CombinedSnapshot<'tcx>>, + ) -> Self { let mut nodes = FxIndexMap::default(); let mut edges = Vec::new(); // Note that if `R2: R1`, we get a callback `r1, r2`, so `target` is first parameter. - Self::iterate_undo_log(tcx, undo_log, verifys, |target, source| { - let source_node = Self::add_node(&mut nodes, source); - let target_node = Self::add_node(&mut nodes, target); - edges.push((source_node, target_node)); - }); + Self::iterate_region_constraints( + tcx, + region_constraints, + only_consider_snapshot, + |target, source| { + let source_node = Self::add_node(&mut nodes, source); + let target_node = Self::add_node(&mut nodes, target); + edges.push((source_node, target_node)); + }, + ); let graph = VecGraph::new(nodes.len(), edges); let sccs = Sccs::new(&graph); Self { nodes, sccs } } /// Invokes `each_edge(R1, R2)` for each edge where `R2: R1` - fn iterate_undo_log<'a>( + fn iterate_region_constraints( tcx: TyCtxt<'tcx>, - undo_log: impl Iterator>, - verifys: &[Verify<'tcx>], + region_constraints: &RegionConstraintCollector<'_, 'tcx>, + only_consider_snapshot: Option<&CombinedSnapshot<'tcx>>, mut each_edge: impl FnMut(ty::Region<'tcx>, ty::Region<'tcx>), - ) where - 'tcx: 'a, - { - for undo_entry in undo_log { - match undo_entry { - &AddConstraint(Constraint::VarSubVar(a, b)) => { - each_edge(tcx.mk_re_var(a), tcx.mk_re_var(b)); - } - &AddConstraint(Constraint::RegSubVar(a, b)) => { - each_edge(a, tcx.mk_re_var(b)); - } - &AddConstraint(Constraint::VarSubReg(a, b)) => { - each_edge(tcx.mk_re_var(a), b); - } - &AddConstraint(Constraint::RegSubReg(a, b)) => { - each_edge(a, b); + ) { + let mut each_constraint = |constraint| match constraint { + &Constraint::VarSubVar(a, b) => { + each_edge(ty::Region::new_var(tcx, a), ty::Region::new_var(tcx, b)); + } + &Constraint::RegSubVar(a, b) => { + each_edge(a, ty::Region::new_var(tcx, b)); + } + &Constraint::VarSubReg(a, b) => { + each_edge(ty::Region::new_var(tcx, a), b); + } + &Constraint::RegSubReg(a, b) => { + each_edge(a, b); + } + }; + + if let Some(snapshot) = only_consider_snapshot { + for undo_entry in + region_constraints.undo_log.region_constraints_in_snapshot(&snapshot.undo_snapshot) + { + match undo_entry { + AddConstraint(constraint) => { + each_constraint(constraint); + } + &AddVerify(i) => span_bug!( + region_constraints.data().verifys[i].origin.span(), + "we never add verifications while doing higher-ranked things", + ), + &AddCombination(..) | &AddVar(..) => {} } - &AddVerify(i) => span_bug!( - verifys[i].origin.span(), - "we never add verifications while doing higher-ranked things", - ), - &AddCombination(..) | &AddVar(..) => {} } + } else { + region_constraints + .data() + .constraints + .keys() + .for_each(|constraint| each_constraint(constraint)); } } diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index c7a307b89e414..708c51cabebc6 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -400,7 +400,7 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { data } - pub(super) fn data(&self) -> &RegionConstraintData<'tcx> { + pub fn data(&self) -> &RegionConstraintData<'tcx> { &self.data } @@ -610,13 +610,13 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { let resolved = ut .probe_value(root_vid) .get_value_ignoring_universes() - .unwrap_or_else(|| tcx.mk_re_var(root_vid)); + .unwrap_or_else(|| ty::Region::new_var(tcx, root_vid)); // Don't resolve a variable to a region that it cannot name. if self.var_universe(vid).can_name(self.universe(resolved)) { resolved } else { - tcx.mk_re_var(vid) + ty::Region::new_var(tcx, vid) } } @@ -637,7 +637,7 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { ) -> Region<'tcx> { let vars = TwoRegions { a, b }; if let Some(&c) = self.combine_map(t).get(&vars) { - return tcx.mk_re_var(c); + return ty::Region::new_var(tcx, c); } let a_universe = self.universe(a); let b_universe = self.universe(b); @@ -645,7 +645,7 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { let c = self.new_region_var(c_universe, MiscVariable(origin.span())); self.combine_map(t).insert(vars, c); self.undo_log.push(AddCombination(t, vars)); - let new_r = tcx.mk_re_var(c); + let new_r = ty::Region::new_var(tcx, c); for old_r in [a, b] { match t { Glb => self.make_subregion(origin.clone(), new_r, old_r), @@ -683,15 +683,10 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> { } /// See `InferCtxt::region_constraints_added_in_snapshot`. - pub fn region_constraints_added_in_snapshot(&self, mark: &Snapshot<'tcx>) -> Option { + pub fn region_constraints_added_in_snapshot(&self, mark: &Snapshot<'tcx>) -> bool { self.undo_log .region_constraints_in_snapshot(mark) - .map(|&elt| match elt { - AddConstraint(constraint) => Some(constraint.involves_placeholders()), - _ => None, - }) - .max() - .unwrap_or(None) + .any(|&elt| matches!(elt, AddConstraint(_))) } #[inline] @@ -709,8 +704,8 @@ impl fmt::Debug for RegionSnapshot { impl<'tcx> fmt::Debug for GenericKind<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - GenericKind::Param(ref p) => write!(f, "{:?}", p), - GenericKind::Alias(ref p) => write!(f, "{:?}", p), + GenericKind::Param(ref p) => write!(f, "{p:?}"), + GenericKind::Alias(ref p) => write!(f, "{p:?}"), } } } @@ -718,8 +713,8 @@ impl<'tcx> fmt::Debug for GenericKind<'tcx> { impl<'tcx> fmt::Display for GenericKind<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - GenericKind::Param(ref p) => write!(f, "{}", p), - GenericKind::Alias(ref p) => write!(f, "{}", p), + GenericKind::Param(ref p) => write!(f, "{p}"), + GenericKind::Alias(ref p) => write!(f, "{p}"), } } } diff --git a/compiler/rustc_infer/src/infer/sub.rs b/compiler/rustc_infer/src/infer/sub.rs index ceafafb5582cd..27e1ed56f31a9 100644 --- a/compiler/rustc_infer/src/infer/sub.rs +++ b/compiler/rustc_infer/src/infer/sub.rs @@ -118,7 +118,7 @@ impl<'tcx> TypeRelation<'tcx> for Sub<'_, '_, 'tcx> { (&ty::Error(e), _) | (_, &ty::Error(e)) => { infcx.set_tainted_by_errors(e); - Ok(self.tcx().ty_error(e)) + Ok(Ty::new_error(self.tcx(), e)) } ( @@ -132,7 +132,7 @@ impl<'tcx> TypeRelation<'tcx> for Sub<'_, '_, 'tcx> { | (_, &ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. })) if self.fields.define_opaque_types == DefineOpaqueTypes::Yes && def_id.is_local() - && !self.tcx().trait_solver_next() => + && !self.fields.infcx.next_trait_solver() => { self.fields.obligations.extend( infcx diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index f7ab05b2d498a..bc83f8d3f9676 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -123,13 +123,12 @@ pub enum TypeVariableOriginKind { NormalizeProjectionType, TypeInference, OpaqueTypeInference(DefId), - TypeParameterDefinition(Symbol, Option), + TypeParameterDefinition(Symbol, DefId), - /// One of the upvars or closure kind parameters in a `ClosureSubsts` + /// One of the upvars or closure kind parameters in a `ClosureArgs` /// (before it has been determined). // FIXME(eddyb) distinguish upvar inference variables from the rest. ClosureSynthetic, - SubstitutionPlaceholder, AutoDeref, AdjustmentType, diff --git a/compiler/rustc_infer/src/infer/undo_log.rs b/compiler/rustc_infer/src/infer/undo_log.rs index 955c54e85157e..25d06b21ec84f 100644 --- a/compiler/rustc_infer/src/infer/undo_log.rs +++ b/compiler/rustc_infer/src/infer/undo_log.rs @@ -138,11 +138,9 @@ impl<'tcx> InferCtxtInner<'tcx> { } if self.undo_log.num_open_snapshots == 1 { - // The root snapshot. It's safe to clear the undo log because - // there's no snapshot further out that we might need to roll back - // to. + // After the root snapshot the undo log should be empty. assert!(snapshot.undo_len == 0); - self.undo_log.logs.clear(); + assert!(self.undo_log.logs.is_empty()); } self.undo_log.num_open_snapshots -= 1; @@ -183,15 +181,6 @@ impl<'tcx> InferCtxtUndoLogs<'tcx> { self.logs[s.undo_len..].iter().any(|log| matches!(log, UndoLog::OpaqueTypes(..))) } - pub(crate) fn region_constraints( - &self, - ) -> impl Iterator> + Clone { - self.logs.iter().filter_map(|log| match log { - UndoLog::RegionConstraintCollector(log) => Some(log), - _ => None, - }) - } - fn assert_open_snapshot(&self, snapshot: &Snapshot<'tcx>) { // Failures here may indicate a failure to follow a stack discipline. assert!(self.logs.len() >= snapshot.undo_len); diff --git a/compiler/rustc_infer/src/traits/engine.rs b/compiler/rustc_infer/src/traits/engine.rs index 11f43469400ba..64b9714c7c086 100644 --- a/compiler/rustc_infer/src/traits/engine.rs +++ b/compiler/rustc_infer/src/traits/engine.rs @@ -25,7 +25,7 @@ pub trait TraitEngine<'tcx>: 'tcx { cause, recursion_depth: 0, param_env, - predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(infcx.tcx), + predicate: ty::Binder::dummy(trait_ref).to_predicate(infcx.tcx), }, ); } diff --git a/compiler/rustc_infer/src/traits/error_reporting/mod.rs b/compiler/rustc_infer/src/traits/error_reporting/mod.rs index b5a7d0326a88d..e72a43630e9b5 100644 --- a/compiler/rustc_infer/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/traits/error_reporting/mod.rs @@ -25,12 +25,14 @@ impl<'tcx> InferCtxt<'tcx> { "impl has stricter requirements than trait" ); - if let Some(span) = self.tcx.hir().span_if_local(trait_item_def_id) { - let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); - err.span_label(span, format!("definition of `{}` from trait", item_name)); + if !self.tcx.is_impl_trait_in_trait(trait_item_def_id) { + if let Some(span) = self.tcx.hir().span_if_local(trait_item_def_id) { + let item_name = self.tcx.item_name(impl_item_def_id.to_def_id()); + err.span_label(span, format!("definition of `{item_name}` from trait")); + } } - err.span_label(error_span, format!("impl has extra requirement {}", requirement)); + err.span_label(error_span, format!("impl has extra requirement {requirement}")); err } @@ -54,7 +56,7 @@ pub fn report_object_safety_error<'tcx>( "the trait `{}` cannot be made into an object", trait_str ); - err.span_label(span, format!("`{}` cannot be made into an object", trait_str)); + err.span_label(span, format!("`{trait_str}` cannot be made into an object")); let mut reported_violations = FxIndexSet::default(); let mut multi_span = vec![]; diff --git a/compiler/rustc_infer/src/traits/mod.rs b/compiler/rustc_infer/src/traits/mod.rs index 8ce8b4e2024eb..a5b2ccce85e1e 100644 --- a/compiler/rustc_infer/src/traits/mod.rs +++ b/compiler/rustc_infer/src/traits/mod.rs @@ -9,6 +9,7 @@ mod structural_impls; pub mod util; use std::cmp; +use std::hash::{Hash, Hasher}; use hir::def_id::LocalDefId; use rustc_hir as hir; @@ -36,7 +37,7 @@ pub use rustc_middle::traits::*; /// either identifying an `impl` (e.g., `impl Eq for i32`) that /// satisfies the obligation, or else finding a bound that is in /// scope. The eventual result is usually a `Selection` (defined below). -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Clone)] pub struct Obligation<'tcx, T> { /// The reason we have to prove this thing. pub cause: ObligationCause<'tcx>, @@ -55,6 +56,27 @@ pub struct Obligation<'tcx, T> { pub recursion_depth: usize, } +impl<'tcx, T: PartialEq> PartialEq> for Obligation<'tcx, T> { + #[inline] + fn eq(&self, other: &Obligation<'tcx, T>) -> bool { + // Ignore `cause` and `recursion_depth`. This is a small performance + // win for a few crates, and a huge performance win for the crate in + // https://github.com/rust-lang/rustc-perf/pull/1680, which greatly + // stresses the trait system. + self.param_env == other.param_env && self.predicate == other.predicate + } +} + +impl Eq for Obligation<'_, T> {} + +impl Hash for Obligation<'_, T> { + fn hash(&self, state: &mut H) -> () { + // See the comment on `Obligation::eq`. + self.param_env.hash(state); + self.predicate.hash(state); + } +} + impl<'tcx, P> From> for solve::Goal<'tcx, P> { fn from(value: Obligation<'tcx, P>) -> Self { solve::Goal { param_env: value.param_env, predicate: value.predicate } @@ -62,7 +84,8 @@ impl<'tcx, P> From> for solve::Goal<'tcx, P> { } pub type PredicateObligation<'tcx> = Obligation<'tcx, ty::Predicate<'tcx>>; -pub type TraitObligation<'tcx> = Obligation<'tcx, ty::PolyTraitPredicate<'tcx>>; +pub type TraitObligation<'tcx> = Obligation<'tcx, ty::TraitPredicate<'tcx>>; +pub type PolyTraitObligation<'tcx> = Obligation<'tcx, ty::PolyTraitPredicate<'tcx>>; impl<'tcx> PredicateObligation<'tcx> { /// Flips the polarity of the inner predicate. @@ -76,25 +99,9 @@ impl<'tcx> PredicateObligation<'tcx> { recursion_depth: self.recursion_depth, }) } - - pub fn without_const(mut self, tcx: TyCtxt<'tcx>) -> PredicateObligation<'tcx> { - self.param_env = self.param_env.without_const(); - if let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = self.predicate.kind().skip_binder() && trait_pred.is_const_if_const() { - self.predicate = tcx.mk_predicate(self.predicate.kind().map_bound(|_| ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred.without_const())))); - } - self - } } -impl<'tcx> TraitObligation<'tcx> { - /// Returns `true` if the trait predicate is considered `const` in its ParamEnv. - pub fn is_const(&self) -> bool { - matches!( - (self.predicate.skip_binder().constness, self.param_env.constness()), - (ty::BoundConstness::ConstIfConst, hir::Constness::Const) - ) - } - +impl<'tcx> PolyTraitObligation<'tcx> { pub fn derived_cause( &self, variant: impl FnOnce(DerivedObligationCause<'tcx>) -> ObligationCauseCode<'tcx>, @@ -193,7 +200,7 @@ impl<'tcx> FulfillmentError<'tcx> { } } -impl<'tcx> TraitObligation<'tcx> { +impl<'tcx> PolyTraitObligation<'tcx> { pub fn polarity(&self) -> ty::ImplPolarity { self.predicate.skip_binder().polarity } diff --git a/compiler/rustc_infer/src/traits/project.rs b/compiler/rustc_infer/src/traits/project.rs index e375d611936ea..afba2e50a23c5 100644 --- a/compiler/rustc_infer/src/traits/project.rs +++ b/compiler/rustc_infer/src/traits/project.rs @@ -190,7 +190,7 @@ impl<'tcx> ProjectionCache<'_, 'tcx> { } let fresh_key = map.insert(key, ProjectionCacheEntry::NormalizedTy { ty: value, complete: None }); - assert!(!fresh_key, "never started projecting `{:?}`", key); + assert!(!fresh_key, "never started projecting `{key:?}`"); } /// Mark the relevant projection cache key as having its derived obligations @@ -229,7 +229,7 @@ impl<'tcx> ProjectionCache<'_, 'tcx> { /// be different). pub fn ambiguous(&mut self, key: ProjectionCacheKey<'tcx>) { let fresh = self.map().insert(key, ProjectionCacheEntry::Ambiguous); - assert!(!fresh, "never started projecting `{:?}`", key); + assert!(!fresh, "never started projecting `{key:?}`"); } /// Indicates that while trying to normalize `key`, `key` was required to @@ -237,14 +237,14 @@ impl<'tcx> ProjectionCache<'_, 'tcx> { /// an error here. pub fn recur(&mut self, key: ProjectionCacheKey<'tcx>) { let fresh = self.map().insert(key, ProjectionCacheEntry::Recur); - assert!(!fresh, "never started projecting `{:?}`", key); + assert!(!fresh, "never started projecting `{key:?}`"); } /// Indicates that trying to normalize `key` resulted in /// error. pub fn error(&mut self, key: ProjectionCacheKey<'tcx>) { let fresh = self.map().insert(key, ProjectionCacheEntry::Error); - assert!(!fresh, "never started projecting `{:?}`", key); + assert!(!fresh, "never started projecting `{key:?}`"); } } diff --git a/compiler/rustc_infer/src/traits/structural_impls.rs b/compiler/rustc_infer/src/traits/structural_impls.rs index 1563d92af0ea7..8a7c59da09ebb 100644 --- a/compiler/rustc_infer/src/traits/structural_impls.rs +++ b/compiler/rustc_infer/src/traits/structural_impls.rs @@ -38,17 +38,17 @@ impl<'tcx> fmt::Debug for traits::FulfillmentError<'tcx> { impl<'tcx> fmt::Debug for traits::FulfillmentErrorCode<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - super::CodeSelectionError(ref e) => write!(f, "{:?}", e), - super::CodeProjectionError(ref e) => write!(f, "{:?}", e), + super::CodeSelectionError(ref e) => write!(f, "{e:?}"), + super::CodeProjectionError(ref e) => write!(f, "{e:?}"), super::CodeSubtypeError(ref a, ref b) => { - write!(f, "CodeSubtypeError({:?}, {:?})", a, b) + write!(f, "CodeSubtypeError({a:?}, {b:?})") } super::CodeConstEquateError(ref a, ref b) => { - write!(f, "CodeConstEquateError({:?}, {:?})", a, b) + write!(f, "CodeConstEquateError({a:?}, {b:?})") } super::CodeAmbiguity { overflow: false } => write!(f, "Ambiguity"), super::CodeAmbiguity { overflow: true } => write!(f, "Overflow"), - super::CodeCycle(ref cycle) => write!(f, "Cycle({:?})", cycle), + super::CodeCycle(ref cycle) => write!(f, "Cycle({cycle:?})"), } } } diff --git a/compiler/rustc_infer/src/traits/util.rs b/compiler/rustc_infer/src/traits/util.rs index 74a78f38024e0..93dfbe63bcd1c 100644 --- a/compiler/rustc_infer/src/traits/util.rs +++ b/compiler/rustc_infer/src/traits/util.rs @@ -3,7 +3,7 @@ use smallvec::smallvec; use crate::infer::outlives::components::{push_outlives_components, Component}; use crate::traits::{self, Obligation, PredicateObligation}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; -use rustc_middle::ty::{self, ToPredicate, TyCtxt}; +use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt}; use rustc_span::symbol::Ident; use rustc_span::Span; @@ -25,6 +25,13 @@ impl<'tcx> PredicateSet<'tcx> { Self { tcx, set: Default::default() } } + /// Adds a predicate to the set. + /// + /// Returns whether the predicate was newly inserted. That is: + /// - If the set did not previously contain this predicate, `true` is returned. + /// - If the set already contained this predicate, `false` is returned, + /// and the set is not modified: original predicate is not replaced, + /// and the predicate passed as argument is dropped. pub fn insert(&mut self, pred: ty::Predicate<'tcx>) -> bool { // We have to be careful here because we want // @@ -80,14 +87,14 @@ pub struct Elaborator<'tcx, O> { pub trait Elaboratable<'tcx> { fn predicate(&self) -> ty::Predicate<'tcx>; - // Makes a new `Self` but with a different predicate. - fn child(&self, predicate: ty::Predicate<'tcx>) -> Self; + // Makes a new `Self` but with a different clause that comes from elaboration. + fn child(&self, clause: ty::Clause<'tcx>) -> Self; - // Makes a new `Self` but with a different predicate and a different cause - // code (if `Self` has one). + // Makes a new `Self` but with a different clause and a different cause + // code (if `Self` has one, such as [`PredicateObligation`]). fn child_with_derived_cause( &self, - predicate: ty::Predicate<'tcx>, + clause: ty::Clause<'tcx>, span: Span, parent_trait_pred: ty::PolyTraitPredicate<'tcx>, index: usize, @@ -99,18 +106,18 @@ impl<'tcx> Elaboratable<'tcx> for PredicateObligation<'tcx> { self.predicate } - fn child(&self, predicate: ty::Predicate<'tcx>) -> Self { + fn child(&self, clause: ty::Clause<'tcx>) -> Self { Obligation { cause: self.cause.clone(), param_env: self.param_env, recursion_depth: 0, - predicate, + predicate: clause.as_predicate(), } } fn child_with_derived_cause( &self, - predicate: ty::Predicate<'tcx>, + clause: ty::Clause<'tcx>, span: Span, parent_trait_pred: ty::PolyTraitPredicate<'tcx>, index: usize, @@ -123,7 +130,12 @@ impl<'tcx> Elaboratable<'tcx> for PredicateObligation<'tcx> { span, })) }); - Obligation { cause, param_env: self.param_env, recursion_depth: 0, predicate } + Obligation { + cause, + param_env: self.param_env, + recursion_depth: 0, + predicate: clause.as_predicate(), + } } } @@ -132,18 +144,18 @@ impl<'tcx> Elaboratable<'tcx> for ty::Predicate<'tcx> { *self } - fn child(&self, predicate: ty::Predicate<'tcx>) -> Self { - predicate + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + clause.as_predicate() } fn child_with_derived_cause( &self, - predicate: ty::Predicate<'tcx>, + clause: ty::Clause<'tcx>, _span: Span, _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, _index: usize, ) -> Self { - predicate + clause.as_predicate() } } @@ -152,18 +164,58 @@ impl<'tcx> Elaboratable<'tcx> for (ty::Predicate<'tcx>, Span) { self.0 } - fn child(&self, predicate: ty::Predicate<'tcx>) -> Self { - (predicate, self.1) + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + (clause.as_predicate(), self.1) + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + _span: Span, + _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, + _index: usize, + ) -> Self { + (clause.as_predicate(), self.1) + } +} + +impl<'tcx> Elaboratable<'tcx> for (ty::Clause<'tcx>, Span) { + fn predicate(&self) -> ty::Predicate<'tcx> { + self.0.as_predicate() + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + (clause, self.1) } fn child_with_derived_cause( &self, - predicate: ty::Predicate<'tcx>, + clause: ty::Clause<'tcx>, _span: Span, _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, _index: usize, ) -> Self { - (predicate, self.1) + (clause, self.1) + } +} + +impl<'tcx> Elaboratable<'tcx> for ty::Clause<'tcx> { + fn predicate(&self) -> ty::Predicate<'tcx> { + self.as_predicate() + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + clause + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + _span: Span, + _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, + _index: usize, + ) -> Self { + clause } } @@ -199,7 +251,7 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { let bound_predicate = elaboratable.predicate().kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => { // Negative trait bounds do not imply any supertrait bounds if data.polarity == ty::ImplPolarity::Negative { return; @@ -212,13 +264,9 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { }; let obligations = - predicates.predicates.iter().enumerate().map(|(index, &(mut pred, span))| { - // when parent predicate is non-const, elaborate it to non-const predicates. - if data.constness == ty::BoundConstness::NotConst { - pred = pred.without_const(tcx); - } + predicates.predicates.iter().enumerate().map(|(index, &(clause, span))| { elaboratable.child_with_derived_cause( - pred.subst_supertrait(tcx, &bound_predicate.rebind(data.trait_ref)), + clause.subst_supertrait(tcx, &bound_predicate.rebind(data.trait_ref)), span, bound_predicate.rebind(data), index, @@ -227,7 +275,7 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { debug!(?data, ?obligations, "super_predicates"); self.extend_deduped(obligations); } - ty::PredicateKind::WellFormed(..) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..)) => { // Currently, we do not elaborate WF predicates, // although we easily could. } @@ -243,13 +291,13 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { // Currently, we do not "elaborate" predicates like `X -> Y`, // though conceivably we might. } - ty::PredicateKind::Clause(ty::Clause::Projection(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) => { // Nothing to elaborate in a projection predicate. } ty::PredicateKind::ClosureKind(..) => { // Nothing to elaborate when waiting for a closure's kind to be inferred. } - ty::PredicateKind::ConstEvaluatable(..) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) => { // Currently, we do not elaborate const-evaluatable // predicates. } @@ -257,10 +305,10 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { // Currently, we do not elaborate const-equate // predicates. } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) => { // Nothing to elaborate from `'a: 'b`. } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( ty_max, r_min, ))) => { @@ -292,17 +340,15 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { if r.is_late_bound() { None } else { - Some(ty::PredicateKind::Clause(ty::Clause::RegionOutlives( - ty::OutlivesPredicate(r, r_min), + Some(ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate( + r, r_min, ))) } } Component::Param(p) => { - let ty = tcx.mk_ty_param(p.index, p.name); - Some(ty::PredicateKind::Clause(ty::Clause::TypeOutlives( - ty::OutlivesPredicate(ty, r_min), - ))) + let ty = Ty::new_param(tcx, p.index, p.name); + Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, r_min))) } Component::UnresolvedInferenceVariable(_) => None, @@ -310,8 +356,9 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { Component::Alias(alias_ty) => { // We might end up here if we have `Foo<::Assoc>: 'a`. // With this, we can deduce that `::Assoc: 'a`. - Some(ty::PredicateKind::Clause(ty::Clause::TypeOutlives( - ty::OutlivesPredicate(alias_ty.to_ty(tcx), r_min), + Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( + alias_ty.to_ty(tcx), + r_min, ))) } @@ -321,20 +368,16 @@ impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { None } }) - .map(|predicate_kind| { - bound_predicate.rebind(predicate_kind).to_predicate(tcx) - }) - .map(|predicate| elaboratable.child(predicate)), + .map(|clause| { + elaboratable.child(bound_predicate.rebind(clause).to_predicate(tcx)) + }), ); } - ty::PredicateKind::TypeWellFormedFromEnv(..) => { - // Nothing to elaborate - } ty::PredicateKind::Ambiguous => {} ty::PredicateKind::AliasRelate(..) => { // No } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) => { // Nothing to elaborate } } @@ -400,7 +443,7 @@ pub fn transitive_bounds_that_define_assoc_item<'tcx>( tcx.super_predicates_that_define_assoc_item((trait_ref.def_id(), assoc_name)); for (super_predicate, _) in super_predicates.predicates { let subst_predicate = super_predicate.subst_supertrait(tcx, &trait_ref); - if let Some(binder) = subst_predicate.to_opt_poly_trait_pred() { + if let Some(binder) = subst_predicate.as_trait_clause() { stack.push(binder.map_bound(|t| t.trait_ref)); } } diff --git a/compiler/rustc_interface/Cargo.toml b/compiler/rustc_interface/Cargo.toml index 2c7438ed9db43..ae008674d0127 100644 --- a/compiler/rustc_interface/Cargo.toml +++ b/compiler/rustc_interface/Cargo.toml @@ -15,6 +15,7 @@ rustc_attr = { path = "../rustc_attr" } rustc_borrowck = { path = "../rustc_borrowck" } rustc_builtin_macros = { path = "../rustc_builtin_macros" } rustc_expand = { path = "../rustc_expand" } +rustc_feature = { path = "../rustc_feature" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_fs_util = { path = "../rustc_fs_util" } rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_interface/messages.ftl b/compiler/rustc_interface/messages.ftl index 37994899a2038..bd9fad8b04296 100644 --- a/compiler/rustc_interface/messages.ftl +++ b/compiler/rustc_interface/messages.ftl @@ -1,33 +1,44 @@ -interface_ferris_identifier = - Ferris cannot be used as an identifier - .suggestion = try using their name instead +interface_cant_emit_mir = + could not emit MIR: {$error} interface_emoji_identifier = identifiers cannot contain emoji: `{$ident}` -interface_mixed_bin_crate = - cannot mix `bin` crate type with others - -interface_mixed_proc_macro_crate = - cannot mix `proc-macro` crate type with others - interface_error_writing_dependencies = error writing dependencies to `{$path}`: {$error} -interface_input_file_would_be_overwritten = - the input file "{$path}" would be overwritten by the generated executable +interface_failed_writing_file = + failed to write file {$path}: {$error}" + +interface_ferris_identifier = + Ferris cannot be used as an identifier + .suggestion = try using their name instead interface_generated_file_conflicts_with_directory = the generated executable for the input file "{$input_path}" conflicts with the existing directory "{$dir_path}" -interface_temps_dir_error = - failed to find or create the directory specified by `--temps-dir` +interface_ignoring_extra_filename = ignoring -C extra-filename flag due to -o flag + +interface_ignoring_out_dir = ignoring --out-dir flag due to -o flag + +interface_input_file_would_be_overwritten = + the input file "{$path}" would be overwritten by the generated executable +interface_mixed_bin_crate = + cannot mix `bin` crate type with others + +interface_mixed_proc_macro_crate = + cannot mix `proc-macro` crate type with others + +interface_multiple_output_types_adaption = + due to multiple output types requested, the explicitly specified output file name will be adapted for each output type + +interface_multiple_output_types_to_stdout = can't use option `-o` or `--emit` to write multiple output types to stdout interface_out_dir_error = failed to find or create the directory specified by `--out-dir` -interface_cant_emit_mir = - could not emit MIR: {$error} +interface_proc_macro_crate_panic_abort = + building proc macro crate with `panic=abort` may crash the compiler should the proc-macro panic interface_rustc_error_fatal = fatal error triggered by #[rustc_error] @@ -35,18 +46,8 @@ interface_rustc_error_fatal = interface_rustc_error_unexpected_annotation = unexpected annotation used with `#[rustc_error(...)]`! -interface_failed_writing_file = - failed to write file {$path}: {$error}" - -interface_proc_macro_crate_panic_abort = - building proc macro crate with `panic=abort` may crash the compiler should the proc-macro panic +interface_temps_dir_error = + failed to find or create the directory specified by `--temps-dir` interface_unsupported_crate_type_for_target = dropping unsupported crate type `{$crate_type}` for target `{$target_triple}` - -interface_multiple_output_types_adaption = - due to multiple output types requested, the explicitly specified output file name will be adapted for each output type - -interface_ignoring_extra_filename = ignoring -C extra-filename flag due to -o flag - -interface_ignoring_out_dir = ignoring --out-dir flag due to -o flag diff --git a/compiler/rustc_interface/src/errors.rs b/compiler/rustc_interface/src/errors.rs index 0eedee250269e..a9ab2720d89a5 100644 --- a/compiler/rustc_interface/src/errors.rs +++ b/compiler/rustc_interface/src/errors.rs @@ -108,3 +108,7 @@ pub struct IgnoringExtraFilename; #[derive(Diagnostic)] #[diag(interface_ignoring_out_dir)] pub struct IgnoringOutDir; + +#[derive(Diagnostic)] +#[diag(interface_multiple_output_types_to_stdout)] +pub struct MultipleOutputTypesToStdout; diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 39d56897999d9..5b417e008cf2c 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -14,12 +14,11 @@ use rustc_middle::{bug, ty}; use rustc_parse::maybe_new_parser_from_source_str; use rustc_query_impl::QueryCtxt; use rustc_query_system::query::print_query_stack; -use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames}; -use rustc_session::config::{CheckCfg, ExpectedValues}; -use rustc_session::lint; +use rustc_session::config::{self, CheckCfg, ExpectedValues, Input, OutFileName, OutputFilenames}; use rustc_session::parse::{CrateConfig, ParseSess}; +use rustc_session::CompilerIO; use rustc_session::Session; -use rustc_session::{early_error, CompilerIO}; +use rustc_session::{lint, EarlyErrorHandler}; use rustc_span::source_map::{FileLoader, FileName}; use rustc_span::symbol::sym; use std::path::PathBuf; @@ -36,7 +35,7 @@ pub type Result = result::Result; /// Created by passing [`Config`] to [`run_compiler`]. pub struct Compiler { pub(crate) sess: Lrc, - codegen_backend: Lrc>, + codegen_backend: Lrc, pub(crate) register_lints: Option>, pub(crate) override_queries: Option, } @@ -45,7 +44,7 @@ impl Compiler { pub fn session(&self) -> &Lrc { &self.sess } - pub fn codegen_backend(&self) -> &Lrc> { + pub fn codegen_backend(&self) -> &Lrc { &self.codegen_backend } pub fn register_lints(&self) -> &Option> { @@ -60,13 +59,11 @@ impl Compiler { } } -#[allow(rustc::bad_opt_access)] -pub fn set_thread_safe_mode(sopts: &config::UnstableOptions) { - rustc_data_structures::sync::set_dyn_thread_safe_mode(sopts.threads > 1); -} - /// Converts strings provided as `--cfg [cfgspec]` into a `crate_cfg`. -pub fn parse_cfgspecs(cfgspecs: Vec) -> FxHashSet<(String, Option)> { +pub fn parse_cfgspecs( + handler: &EarlyErrorHandler, + cfgspecs: Vec, +) -> FxHashSet<(String, Option)> { rustc_span::create_default_session_if_not_set_then(move |_| { let cfg = cfgspecs .into_iter() @@ -78,10 +75,10 @@ pub fn parse_cfgspecs(cfgspecs: Vec) -> FxHashSet<(String, Option { - early_error( - ErrorOutputType::default(), - format!(concat!("invalid `--cfg` argument: `{}` (", $reason, ")"), s), - ); + handler.early_error(format!( + concat!("invalid `--cfg` argument: `{}` (", $reason, ")"), + s + )); }; } @@ -125,7 +122,7 @@ pub fn parse_cfgspecs(cfgspecs: Vec) -> FxHashSet<(String, Option) -> CheckCfg { +pub fn parse_check_cfg(handler: &EarlyErrorHandler, specs: Vec) -> CheckCfg { rustc_span::create_default_session_if_not_set_then(move |_| { let mut check_cfg = CheckCfg::default(); @@ -137,10 +134,10 @@ pub fn parse_check_cfg(specs: Vec) -> CheckCfg { macro_rules! error { ($reason: expr) => { - early_error( - ErrorOutputType::default(), - format!(concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"), s), - ) + handler.early_error(format!( + concat!("invalid `--check-cfg` argument: `{}` (", $reason, ")"), + s + )) }; } @@ -188,7 +185,8 @@ pub fn parse_check_cfg(specs: Vec) -> CheckCfg { ExpectedValues::Some(FxHashSet::default()) }); - let ExpectedValues::Some(expected_values) = expected_values else { + let ExpectedValues::Some(expected_values) = expected_values + else { bug!("`expected_values` should be a list a values") }; @@ -252,7 +250,8 @@ pub struct Config { pub input: Input, pub output_dir: Option, - pub output_file: Option, + pub output_file: Option, + pub ice_file: Option, pub file_loader: Option>, pub locale_resources: &'static [&'static str], @@ -286,6 +285,10 @@ pub struct Config { #[allow(rustc::bad_opt_access)] pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R { trace!("run_compiler"); + + // Set parallel mode before thread pool creation, which will create `Lock`s. + rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.unstable_opts.threads > 1); + util::run_in_thread_pool_with_globals( config.opts.edition, config.opts.unstable_opts.threads, @@ -294,8 +297,11 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se let registry = &config.registry; + let handler = EarlyErrorHandler::new(config.opts.error_format); + let temps_dir = config.opts.unstable_opts.temps_dir.as_deref().map(PathBuf::from); let (mut sess, codegen_backend) = util::create_session( + &handler, config.opts, config.crate_cfg, config.crate_check_cfg, @@ -310,6 +316,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se config.lint_caps, config.make_codegen_backend, registry.clone(), + config.ice_file, ); if let Some(parse_sess_created) = config.parse_sess_created { @@ -318,7 +325,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se let compiler = Compiler { sess: Lrc::new(sess), - codegen_backend: Lrc::new(codegen_backend), + codegen_backend: Lrc::from(codegen_backend), register_lints: config.register_lints, override_queries: config.override_queries, }; @@ -333,6 +340,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se }; let prof = compiler.sess.prof.clone(); + prof.generic_activity("drop_compiler").run(move || drop(compiler)); r }) @@ -340,7 +348,11 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se ) } -pub fn try_print_query_stack(handler: &Handler, num_frames: Option) { +pub fn try_print_query_stack( + handler: &Handler, + num_frames: Option, + file: Option, +) { eprintln!("query stack during panic:"); // Be careful relying on global state here: this code is called from @@ -348,7 +360,13 @@ pub fn try_print_query_stack(handler: &Handler, num_frames: Option) { // state if it was responsible for triggering the panic. let i = ty::tls::with_context_opt(|icx| { if let Some(icx) = icx { - print_query_stack(QueryCtxt::new(icx.tcx), icx.query, handler, num_frames) + ty::print::with_no_queries!(print_query_stack( + QueryCtxt::new(icx.tcx), + icx.query, + handler, + num_frames, + file, + )) } else { 0 } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 42d8d22809178..18a669175b9ef 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -11,6 +11,7 @@ use rustc_data_structures::steal::Steal; use rustc_data_structures::sync::{Lrc, OnceCell, WorkerLocal}; use rustc_errors::PResult; use rustc_expand::base::{ExtCtxt, LintStoreExpand}; +use rustc_feature::Features; use rustc_fs_util::try_canonicalize; use rustc_hir::def_id::{StableCrateId, LOCAL_CRATE}; use rustc_lint::{unerased_lint_store, BufferedEarlyLint, EarlyCheckNode, LintStore}; @@ -24,7 +25,8 @@ use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str, validate_a use rustc_passes::{self, hir_stats, layout_test}; use rustc_plugin_impl as plugin; use rustc_resolve::Resolver; -use rustc_session::config::{CrateType, Input, OutputFilenames, OutputType}; +use rustc_session::code_stats::VTableSizeInfo; +use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; use rustc_session::cstore::{MetadataLoader, Untracked}; use rustc_session::output::filename_for_input; use rustc_session::search_paths::PathKind; @@ -71,43 +73,16 @@ fn count_nodes(krate: &ast::Crate) -> usize { counter.count } -pub fn register_plugins<'a>( - sess: &'a Session, - metadata_loader: &'a dyn MetadataLoader, - register_lints: impl Fn(&Session, &mut LintStore), +pub(crate) fn create_lint_store( + sess: &Session, + metadata_loader: &dyn MetadataLoader, + register_lints: Option, pre_configured_attrs: &[ast::Attribute], - crate_name: Symbol, -) -> Result { - // these need to be set "early" so that expansion sees `quote` if enabled. - let features = rustc_expand::config::features(sess, pre_configured_attrs); - sess.init_features(features); - - let crate_types = util::collect_crate_types(sess, pre_configured_attrs); - sess.init_crate_types(crate_types); - - let stable_crate_id = StableCrateId::new( - crate_name, - sess.crate_types().contains(&CrateType::Executable), - sess.opts.cg.metadata.clone(), - sess.cfg_version, - ); - sess.stable_crate_id.set(stable_crate_id).expect("not yet initialized"); - rustc_incremental::prepare_session_directory(sess, crate_name, stable_crate_id)?; - - if sess.opts.incremental.is_some() { - sess.time("incr_comp_garbage_collect_session_directories", || { - if let Err(e) = rustc_incremental::garbage_collect_session_directories(sess) { - warn!( - "Error while trying to garbage collect incremental \ - compilation cache directory: {}", - e - ); - } - }); - } - +) -> LintStore { let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints()); - register_lints(sess, &mut lint_store); + if let Some(register_lints) = register_lints { + register_lints(sess, &mut lint_store); + } let registrars = sess.time("plugin_loading", || { plugin::load::load_plugins(sess, metadata_loader, pre_configured_attrs) @@ -119,11 +94,12 @@ pub fn register_plugins<'a>( } }); - Ok(lint_store) + lint_store } fn pre_expansion_lint<'a>( sess: &Session, + features: &Features, lint_store: &LintStore, registered_tools: &RegisteredTools, check_node: impl EarlyCheckNode<'a>, @@ -133,6 +109,7 @@ fn pre_expansion_lint<'a>( || { rustc_lint::check_ast_node( sess, + features, true, lint_store, registered_tools, @@ -151,13 +128,14 @@ impl LintStoreExpand for LintStoreExpandImpl<'_> { fn pre_expansion_lint( &self, sess: &Session, + features: &Features, registered_tools: &RegisteredTools, node_id: ast::NodeId, attrs: &[ast::Attribute], items: &[rustc_ast::ptr::P], name: Symbol, ) { - pre_expansion_lint(sess, self.0, registered_tools, (node_id, attrs, items), name); + pre_expansion_lint(sess, features, self.0, registered_tools, (node_id, attrs, items), name); } } @@ -173,10 +151,18 @@ fn configure_and_expand( ) -> ast::Crate { let tcx = resolver.tcx(); let sess = tcx.sess; + let features = tcx.features(); let lint_store = unerased_lint_store(tcx); let crate_name = tcx.crate_name(LOCAL_CRATE); let lint_check_node = (&krate, pre_configured_attrs); - pre_expansion_lint(sess, lint_store, tcx.registered_tools(()), lint_check_node, crate_name); + pre_expansion_lint( + sess, + features, + lint_store, + tcx.registered_tools(()), + lint_check_node, + crate_name, + ); rustc_builtin_macros::register_builtin_macros(resolver); let num_standard_library_imports = sess.time("crate_injection", || { @@ -185,6 +171,7 @@ fn configure_and_expand( pre_configured_attrs, resolver, sess, + features, ) }); @@ -224,16 +211,15 @@ fn configure_and_expand( } // Create the config for macro expansion - let features = sess.features_untracked(); let recursion_limit = get_recursion_limit(pre_configured_attrs, sess); let cfg = rustc_expand::expand::ExpansionConfig { - features: Some(features), + crate_name: crate_name.to_string(), + features, recursion_limit, trace_mac: sess.opts.unstable_opts.trace_macros, should_test: sess.is_test_crate(), span_debug: sess.opts.unstable_opts.span_debug, proc_macro_backtrace: sess.opts.unstable_opts.proc_macro_backtrace, - ..rustc_expand::expand::ExpansionConfig::default(crate_name.to_string()) }; let lint_store = LintStoreExpandImpl(lint_store); @@ -267,14 +253,19 @@ fn configure_and_expand( }); sess.time("maybe_building_test_harness", || { - rustc_builtin_macros::test_harness::inject(&mut krate, sess, resolver) + rustc_builtin_macros::test_harness::inject(&mut krate, sess, features, resolver) }); let has_proc_macro_decls = sess.time("AST_validation", || { - rustc_ast_passes::ast_validation::check_crate(sess, &krate, resolver.lint_buffer()) + rustc_ast_passes::ast_validation::check_crate( + sess, + features, + &krate, + resolver.lint_buffer(), + ) }); - let crate_types = sess.crate_types(); + let crate_types = tcx.crate_types(); let is_executable_crate = crate_types.contains(&CrateType::Executable); let is_proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); @@ -296,6 +287,7 @@ fn configure_and_expand( rustc_builtin_macros::proc_macro_harness::inject( &mut krate, sess, + features, resolver, is_proc_macro_crate, has_proc_macro_decls, @@ -326,7 +318,7 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { // Needs to go *after* expansion to be able to check the results of macro expansion. sess.time("complete_gated_feature_checking", || { - rustc_ast_passes::feature_gate::check_crate(&krate, sess); + rustc_ast_passes::feature_gate::check_crate(&krate, sess, tcx.features()); }); // Add all buffered lints from the `ParseSess` to the `Session`. @@ -355,6 +347,7 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { let lint_store = unerased_lint_store(tcx); rustc_lint::check_ast_node( sess, + tcx.features(), false, lint_store, tcx.registered_tools(()), @@ -366,26 +359,31 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) { // Returns all the paths that correspond to generated files. fn generated_output_paths( - sess: &Session, + tcx: TyCtxt<'_>, outputs: &OutputFilenames, exact_name: bool, crate_name: Symbol, ) -> Vec { + let sess = tcx.sess; let mut out_filenames = Vec::new(); for output_type in sess.opts.output_types.keys() { - let file = outputs.path(*output_type); + let out_filename = outputs.path(*output_type); + let file = out_filename.as_path().to_path_buf(); match *output_type { // If the filename has been overridden using `-o`, it will not be modified // by appending `.rlib`, `.exe`, etc., so we can skip this transformation. OutputType::Exe if !exact_name => { - for crate_type in sess.crate_types().iter() { + for crate_type in tcx.crate_types().iter() { let p = filename_for_input(sess, *crate_type, crate_name, outputs); - out_filenames.push(p); + out_filenames.push(p.as_path().to_path_buf()); } } OutputType::DepInfo if sess.opts.unstable_opts.dep_info_omit_d_target => { // Don't add the dep-info output when omitting it from dep-info targets } + OutputType::DepInfo if out_filename.is_stdout() => { + // Don't add the dep-info output when it goes to stdout + } _ => { out_filenames.push(file); } @@ -452,7 +450,8 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P if !sess.opts.output_types.contains_key(&OutputType::DepInfo) { return; } - let deps_filename = outputs.path(OutputType::DepInfo); + let deps_output = outputs.path(OutputType::DepInfo); + let deps_filename = deps_output.as_path(); let result: io::Result<()> = try { // Build a list of files used to compile the output and @@ -515,33 +514,47 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P } } - let mut file = BufWriter::new(fs::File::create(&deps_filename)?); - for path in out_filenames { - writeln!(file, "{}: {}\n", path.display(), files.join(" "))?; - } + let write_deps_to_file = |file: &mut dyn Write| -> io::Result<()> { + for path in out_filenames { + writeln!(file, "{}: {}\n", path.display(), files.join(" "))?; + } - // Emit a fake target for each input file to the compilation. This - // prevents `make` from spitting out an error if a file is later - // deleted. For more info see #28735 - for path in files { - writeln!(file, "{path}:")?; - } + // Emit a fake target for each input file to the compilation. This + // prevents `make` from spitting out an error if a file is later + // deleted. For more info see #28735 + for path in files { + writeln!(file, "{path}:")?; + } - // Emit special comments with information about accessed environment variables. - let env_depinfo = sess.parse_sess.env_depinfo.borrow(); - if !env_depinfo.is_empty() { - let mut envs: Vec<_> = env_depinfo - .iter() - .map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env))) - .collect(); - envs.sort_unstable(); - writeln!(file)?; - for (k, v) in envs { - write!(file, "# env-dep:{k}")?; - if let Some(v) = v { - write!(file, "={v}")?; - } + // Emit special comments with information about accessed environment variables. + let env_depinfo = sess.parse_sess.env_depinfo.borrow(); + if !env_depinfo.is_empty() { + let mut envs: Vec<_> = env_depinfo + .iter() + .map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env))) + .collect(); + envs.sort_unstable(); writeln!(file)?; + for (k, v) in envs { + write!(file, "# env-dep:{k}")?; + if let Some(v) = v { + write!(file, "={v}")?; + } + writeln!(file)?; + } + } + + Ok(()) + }; + + match deps_output { + OutFileName::Stdout => { + let mut file = BufWriter::new(io::stdout()); + write_deps_to_file(&mut file)?; + } + OutFileName::Real(ref path) => { + let mut file = BufWriter::new(fs::File::create(path)?); + write_deps_to_file(&mut file)?; } } }; @@ -593,7 +606,7 @@ fn output_filenames(tcx: TyCtxt<'_>, (): ()) -> Arc { let outputs = util::build_output_filenames(&krate.attrs, sess); let output_paths = - generated_output_paths(sess, &outputs, sess.io.output_file.is_some(), crate_name); + generated_output_paths(tcx, &outputs, sess.io.output_file.is_some(), crate_name); // Ensure the source file isn't accidentally overwritten during compilation. if let Some(ref input_path) = sess.io.input.opt_path() { @@ -671,6 +684,8 @@ pub static DEFAULT_EXTERN_QUERY_PROVIDERS: LazyLock = LazyLock: pub fn create_global_ctxt<'tcx>( compiler: &'tcx Compiler, + crate_types: Vec, + stable_crate_id: StableCrateId, lint_store: Lrc, dep_graph: DepGraph, untracked: Untracked, @@ -703,6 +718,8 @@ pub fn create_global_ctxt<'tcx>( gcx_cell.get_or_init(move || { TyCtxt::create_global_ctxt( sess, + crate_types, + stable_crate_id, lint_store, arena, hir_arena, @@ -720,8 +737,8 @@ pub fn create_global_ctxt<'tcx>( }) } -/// Runs the resolution, type-checking, region checking and other -/// miscellaneous analysis passes on the crate. +/// Runs the type-checking, region checking and other miscellaneous analysis +/// passes on the crate. fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { rustc_passes::hir_id_validator::check_crate(tcx); @@ -826,10 +843,11 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { }, { sess.time("lint_checking", || { - rustc_lint::check_crate(tcx, || { - rustc_lint::BuiltinCombinedLateLintPass::new() - }); + rustc_lint::check_crate(tcx); }); + }, + { + tcx.ensure().clashing_extern_declarations(()); } ); }, @@ -847,6 +865,92 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { sess.time("check_lint_expectations", || tcx.check_expectations(None)); }); + if sess.opts.unstable_opts.print_vtable_sizes { + let traits = tcx.traits(LOCAL_CRATE); + + for &tr in traits { + if !tcx.check_is_object_safe(tr) { + continue; + } + + let name = ty::print::with_no_trimmed_paths!(tcx.def_path_str(tr)); + + let mut first_dsa = true; + + // Number of vtable entries, if we didn't have upcasting + let mut entries_ignoring_upcasting = 0; + // Number of vtable entries needed solely for upcasting + let mut entries_for_upcasting = 0; + + let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, tr)); + + // A slightly edited version of the code in `rustc_trait_selection::traits::vtable::vtable_entries`, + // that works without self type and just counts number of entries. + // + // Note that this is technically wrong, for traits which have associated types in supertraits: + // + // trait A: AsRef + AsRef<()> { type T; } + // + // Without self type we can't normalize `Self::T`, so we can't know if `AsRef` and + // `AsRef<()>` are the same trait, thus we assume that those are different, and potentially + // over-estimate how many vtable entries there are. + // + // Similarly this is wrong for traits that have methods with possibly-impossible bounds. + // For example: + // + // trait B { fn f(&self) where T: Copy; } + // + // Here `dyn B` will have 4 entries, while `dyn B` will only have 3. + // However, since we don't know `T`, we can't know if `T: Copy` holds or not, + // thus we lean on the bigger side and say it has 4 entries. + traits::vtable::prepare_vtable_segments(tcx, trait_ref, |segment| { + match segment { + traits::vtable::VtblSegment::MetadataDSA => { + // If this is the first dsa, it would be included either way, + // otherwise it's needed for upcasting + if std::mem::take(&mut first_dsa) { + entries_ignoring_upcasting += 3; + } else { + entries_for_upcasting += 3; + } + } + + traits::vtable::VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + // Lookup the shape of vtable for the trait. + let own_existential_entries = + tcx.own_existential_vtable_entries(trait_ref.def_id()); + + // The original code here ignores the method if its predicates are impossible. + // We can't really do that as, for example, all not trivial bounds on generic + // parameters are impossible (since we don't know the parameters...), + // see the comment above. + entries_ignoring_upcasting += own_existential_entries.len(); + + if emit_vptr { + entries_for_upcasting += 1; + } + } + } + + std::ops::ControlFlow::Continue::(()) + }); + + sess.code_stats.record_vtable_size( + tr, + &name, + VTableSizeInfo { + trait_name: name.clone(), + entries: entries_ignoring_upcasting + entries_for_upcasting, + entries_ignoring_upcasting, + entries_for_upcasting, + upcasting_cost_percent: entries_for_upcasting as f64 + / entries_ignoring_upcasting as f64 + * 100., + }, + ) + } + } + Ok(()) } diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index c441a8ffd6f94..fc71c6c7e9aa6 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -1,6 +1,6 @@ use crate::errors::{FailedWritingFile, RustcErrorFatal, RustcErrorUnexpectedAnnotation}; use crate::interface::{Compiler, Result}; -use crate::passes; +use crate::{passes, util}; use rustc_ast as ast; use rustc_codegen_ssa::traits::CodegenBackend; @@ -8,15 +8,14 @@ use rustc_codegen_ssa::CodegenResults; use rustc_data_structures::steal::Steal; use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::{AppendOnlyIndexVec, Lrc, OnceCell, RwLock, WorkerLocal}; -use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; +use rustc_hir::def_id::{StableCrateId, CRATE_DEF_ID, LOCAL_CRATE}; use rustc_hir::definitions::Definitions; use rustc_incremental::DepGraphFuture; -use rustc_lint::LintStore; use rustc_metadata::creader::CStore; use rustc_middle::arena::Arena; use rustc_middle::dep_graph::DepGraph; use rustc_middle::ty::{GlobalCtxt, TyCtxt}; -use rustc_session::config::{self, OutputFilenames, OutputType}; +use rustc_session::config::{self, CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::Untracked; use rustc_session::{output::find_crate_name, Session}; use rustc_span::symbol::sym; @@ -84,15 +83,10 @@ pub struct Queries<'tcx> { arena: WorkerLocal>, hir_arena: WorkerLocal>, - dep_graph_future: Query>, parse: Query, pre_configure: Query<(ast::Crate, ast::AttrVec)>, - crate_name: Query, - register_plugins: Query<(ast::Crate, ast::AttrVec, Lrc)>, - dep_graph: Query, // This just points to what's in `gcx_cell`. gcx: Query<&'tcx GlobalCtxt<'tcx>>, - ongoing_codegen: Query>, } impl<'tcx> Queries<'tcx> { @@ -102,31 +96,19 @@ impl<'tcx> Queries<'tcx> { gcx_cell: OnceCell::new(), arena: WorkerLocal::new(|_| Arena::default()), hir_arena: WorkerLocal::new(|_| rustc_hir::Arena::default()), - dep_graph_future: Default::default(), parse: Default::default(), pre_configure: Default::default(), - crate_name: Default::default(), - register_plugins: Default::default(), - dep_graph: Default::default(), gcx: Default::default(), - ongoing_codegen: Default::default(), } } fn session(&self) -> &Lrc { &self.compiler.sess } - fn codegen_backend(&self) -> &Lrc> { + fn codegen_backend(&self) -> &Lrc { self.compiler.codegen_backend() } - fn dep_graph_future(&self) -> Result>> { - self.dep_graph_future.compute(|| { - let sess = self.session(); - Ok(sess.opts.build_dep_graph().then(|| rustc_incremental::load_dep_graph(sess))) - }) - } - pub fn parse(&self) -> Result> { self.parse .compute(|| passes::parse(self.session()).map_err(|mut parse_error| parse_error.emit())) @@ -149,69 +131,73 @@ impl<'tcx> Queries<'tcx> { }) } - pub fn register_plugins( + fn dep_graph_future( &self, - ) -> Result)>> { - self.register_plugins.compute(|| { - let crate_name = *self.crate_name()?.borrow(); - let (krate, pre_configured_attrs) = self.pre_configure()?.steal(); - - let empty: &(dyn Fn(&Session, &mut LintStore) + Sync + Send) = &|_, _| {}; - let lint_store = passes::register_plugins( - self.session(), - &*self.codegen_backend().metadata_loader(), - self.compiler.register_lints.as_deref().unwrap_or_else(|| empty), - &pre_configured_attrs, - crate_name, - )?; - - // Compute the dependency graph (in the background). We want to do - // this as early as possible, to give the DepGraph maximum time to - // load before dep_graph() is called, but it also can't happen - // until after rustc_incremental::prepare_session_directory() is - // called, which happens within passes::register_plugins(). - self.dep_graph_future().ok(); + crate_name: Symbol, + stable_crate_id: StableCrateId, + ) -> Result> { + let sess = self.session(); + + // `load_dep_graph` can only be called after `prepare_session_directory`. + rustc_incremental::prepare_session_directory(sess, crate_name, stable_crate_id)?; + let res = sess.opts.build_dep_graph().then(|| rustc_incremental::load_dep_graph(sess)); + + if sess.opts.incremental.is_some() { + sess.time("incr_comp_garbage_collect_session_directories", || { + if let Err(e) = rustc_incremental::garbage_collect_session_directories(sess) { + warn!( + "Error while trying to garbage collect incremental \ + compilation cache directory: {}", + e + ); + } + }); + } - Ok((krate, pre_configured_attrs, Lrc::new(lint_store))) - }) + Ok(res) } - fn crate_name(&self) -> Result> { - self.crate_name.compute(|| { - Ok({ - let pre_configure_result = self.pre_configure()?; - let (_, pre_configured_attrs) = &*pre_configure_result.borrow(); - // parse `#[crate_name]` even if `--crate-name` was passed, to make sure it matches. - find_crate_name(self.session(), pre_configured_attrs) + fn dep_graph(&self, dep_graph_future: Option) -> DepGraph { + dep_graph_future + .and_then(|future| { + let sess = self.session(); + let (prev_graph, prev_work_products) = + sess.time("blocked_on_dep_graph_loading", || future.open().open(sess)); + rustc_incremental::build_dep_graph(sess, prev_graph, prev_work_products) }) - }) - } - - fn dep_graph(&self) -> Result> { - self.dep_graph.compute(|| { - let sess = self.session(); - let future_opt = self.dep_graph_future()?.steal(); - let dep_graph = future_opt - .and_then(|future| { - let (prev_graph, prev_work_products) = - sess.time("blocked_on_dep_graph_loading", || future.open().open(sess)); - - rustc_incremental::build_dep_graph(sess, prev_graph, prev_work_products) - }) - .unwrap_or_else(DepGraph::new_disabled); - Ok(dep_graph) - }) + .unwrap_or_else(DepGraph::new_disabled) } pub fn global_ctxt(&'tcx self) -> Result>> { self.gcx.compute(|| { - let crate_name = *self.crate_name()?.borrow(); - let (krate, pre_configured_attrs, lint_store) = self.register_plugins()?.steal(); - let sess = self.session(); + let (krate, pre_configured_attrs) = self.pre_configure()?.steal(); + + // parse `#[crate_name]` even if `--crate-name` was passed, to make sure it matches. + let crate_name = find_crate_name(sess, &pre_configured_attrs); + let crate_types = util::collect_crate_types(sess, &pre_configured_attrs); + let stable_crate_id = StableCrateId::new( + crate_name, + crate_types.contains(&CrateType::Executable), + sess.opts.cg.metadata.clone(), + sess.cfg_version, + ); - let cstore = RwLock::new(Box::new(CStore::new(sess)) as _); - let definitions = RwLock::new(Definitions::new(sess.local_stable_crate_id())); + // Compute the dependency graph (in the background). We want to do this as early as + // possible, to give the DepGraph maximum time to load before `dep_graph` is called. + let dep_graph_future = self.dep_graph_future(crate_name, stable_crate_id)?; + + let lint_store = Lrc::new(passes::create_lint_store( + sess, + &*self.codegen_backend().metadata_loader(), + self.compiler.register_lints.as_deref(), + &pre_configured_attrs, + )); + let cstore = RwLock::new(Box::new(CStore::new( + self.codegen_backend().metadata_loader(), + stable_crate_id, + )) as _); + let definitions = RwLock::new(Definitions::new(stable_crate_id)); let source_span = AppendOnlyIndexVec::new(); let _id = source_span.push(krate.spans.inner_span); debug_assert_eq!(_id, CRATE_DEF_ID); @@ -219,8 +205,10 @@ impl<'tcx> Queries<'tcx> { let qcx = passes::create_global_ctxt( self.compiler, + crate_types, + stable_crate_id, lint_store, - self.dep_graph()?.steal(), + self.dep_graph(dep_graph_future), untracked, &self.gcx_cell, &self.arena, @@ -232,33 +220,28 @@ impl<'tcx> Queries<'tcx> { feed.crate_name(crate_name); let feed = tcx.feed_unit_query(); - feed.crate_for_resolver(tcx.arena.alloc(Steal::new((krate, pre_configured_attrs)))); - feed.metadata_loader( - tcx.arena.alloc(Steal::new(self.codegen_backend().metadata_loader())), + feed.features_query( + tcx.arena.alloc(rustc_expand::config::features(sess, &pre_configured_attrs)), ); - feed.features_query(tcx.sess.features_untracked()); + feed.crate_for_resolver(tcx.arena.alloc(Steal::new((krate, pre_configured_attrs)))); }); Ok(qcx) }) } - pub fn ongoing_codegen(&'tcx self) -> Result>> { - self.ongoing_codegen.compute(|| { - self.global_ctxt()?.enter(|tcx| { - tcx.analysis(()).ok(); - - // Don't do code generation if there were any errors - self.session().compile_status()?; + pub fn ongoing_codegen(&'tcx self) -> Result> { + self.global_ctxt()?.enter(|tcx| { + // Don't do code generation if there were any errors + self.session().compile_status()?; - // If we have any delayed bugs, for example because we created TyKind::Error earlier, - // it's likely that codegen will only cause more ICEs, obscuring the original problem - self.session().diagnostic().flush_delayed(); + // If we have any delayed bugs, for example because we created TyKind::Error earlier, + // it's likely that codegen will only cause more ICEs, obscuring the original problem + self.session().diagnostic().flush_delayed(); - // Hook for UI tests. - Self::check_for_rustc_errors_attr(tcx); + // Hook for UI tests. + Self::check_for_rustc_errors_attr(tcx); - Ok(passes::start_codegen(&***self.codegen_backend(), tcx)) - }) + Ok(passes::start_codegen(&**self.codegen_backend(), tcx)) }) } @@ -296,18 +279,17 @@ impl<'tcx> Queries<'tcx> { } } - pub fn linker(&'tcx self) -> Result { + pub fn linker(&'tcx self, ongoing_codegen: Box) -> Result { let sess = self.session().clone(); let codegen_backend = self.codegen_backend().clone(); let (crate_hash, prepare_outputs, dep_graph) = self.global_ctxt()?.enter(|tcx| { ( - if tcx.sess.needs_crate_hash() { Some(tcx.crate_hash(LOCAL_CRATE)) } else { None }, + if tcx.needs_crate_hash() { Some(tcx.crate_hash(LOCAL_CRATE)) } else { None }, tcx.output_filenames(()).clone(), tcx.dep_graph.clone(), ) }); - let ongoing_codegen = self.ongoing_codegen()?.steal(); Ok(Linker { sess, @@ -324,7 +306,7 @@ impl<'tcx> Queries<'tcx> { pub struct Linker { // compilation inputs sess: Lrc, - codegen_backend: Lrc>, + codegen_backend: Lrc, // compilation outputs dep_graph: DepGraph, diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 28e719a40e565..e3d66d18388c6 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -8,10 +8,11 @@ use rustc_session::config::rustc_optgroups; use rustc_session::config::DebugInfo; use rustc_session::config::Input; use rustc_session::config::InstrumentXRay; +use rustc_session::config::LinkSelfContained; use rustc_session::config::TraitSolver; use rustc_session::config::{build_configuration, build_session_options, to_crate_config}; use rustc_session::config::{ - BranchProtection, Externs, OomStrategy, OutputType, OutputTypes, PAuthKey, PacRet, + BranchProtection, Externs, OomStrategy, OutFileName, OutputType, OutputTypes, PAuthKey, PacRet, ProcMacroExecutionStrategy, SymbolManglingVersion, WasiExecModel, }; use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath}; @@ -21,8 +22,8 @@ use rustc_session::config::{InstrumentCoverage, Passes}; use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; use rustc_session::utils::{CanonicalizedPath, NativeLib, NativeLibKind}; -use rustc_session::CompilerIO; use rustc_session::{build_session, getopts, Session}; +use rustc_session::{CompilerIO, EarlyErrorHandler}; use rustc_span::edition::{Edition, DEFAULT_EDITION}; use rustc_span::symbol::sym; use rustc_span::FileName; @@ -36,15 +37,18 @@ use std::path::{Path, PathBuf}; type CfgSpecs = FxHashSet<(String, Option)>; -fn build_session_options_and_crate_config(matches: getopts::Matches) -> (Options, CfgSpecs) { - let sessopts = build_session_options(&matches); - let cfg = parse_cfgspecs(matches.opt_strs("cfg")); +fn build_session_options_and_crate_config( + handler: &mut EarlyErrorHandler, + matches: getopts::Matches, +) -> (Options, CfgSpecs) { + let sessopts = build_session_options(handler, &matches); + let cfg = parse_cfgspecs(handler, matches.opt_strs("cfg")); (sessopts, cfg) } -fn mk_session(matches: getopts::Matches) -> (Session, CfgSpecs) { +fn mk_session(handler: &mut EarlyErrorHandler, matches: getopts::Matches) -> (Session, CfgSpecs) { let registry = registry::Registry::new(&[]); - let (sessopts, cfg) = build_session_options_and_crate_config(matches); + let (sessopts, cfg) = build_session_options_and_crate_config(handler, matches); let temps_dir = sessopts.unstable_opts.temps_dir.as_deref().map(PathBuf::from); let io = CompilerIO { input: Input::Str { name: FileName::Custom(String::new()), input: String::new() }, @@ -52,8 +56,19 @@ fn mk_session(matches: getopts::Matches) -> (Session, CfgSpecs) { output_file: None, temps_dir, }; - let sess = - build_session(sessopts, io, None, registry, vec![], Default::default(), None, None, ""); + let sess = build_session( + handler, + sessopts, + io, + None, + registry, + vec![], + Default::default(), + None, + None, + "", + None, + ); (sess, cfg) } @@ -120,7 +135,8 @@ fn assert_non_crate_hash_different(x: &Options, y: &Options) { fn test_switch_implies_cfg_test() { rustc_span::create_default_session_globals_then(|| { let matches = optgroups().parse(&["--test".to_string()]).unwrap(); - let (sess, cfg) = mk_session(matches); + let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let (sess, cfg) = mk_session(&mut handler, matches); let cfg = build_configuration(&sess, to_crate_config(cfg)); assert!(cfg.contains(&(sym::test, None))); }); @@ -131,7 +147,8 @@ fn test_switch_implies_cfg_test() { fn test_switch_implies_cfg_test_unless_cfg_test() { rustc_span::create_default_session_globals_then(|| { let matches = optgroups().parse(&["--test".to_string(), "--cfg=test".to_string()]).unwrap(); - let (sess, cfg) = mk_session(matches); + let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let (sess, cfg) = mk_session(&mut handler, matches); let cfg = build_configuration(&sess, to_crate_config(cfg)); let mut test_items = cfg.iter().filter(|&&(name, _)| name == sym::test); assert!(test_items.next().is_some()); @@ -143,20 +160,23 @@ fn test_switch_implies_cfg_test_unless_cfg_test() { fn test_can_print_warnings() { rustc_span::create_default_session_globals_then(|| { let matches = optgroups().parse(&["-Awarnings".to_string()]).unwrap(); - let (sess, _) = mk_session(matches); + let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let (sess, _) = mk_session(&mut handler, matches); assert!(!sess.diagnostic().can_emit_warnings()); }); rustc_span::create_default_session_globals_then(|| { let matches = optgroups().parse(&["-Awarnings".to_string(), "-Dwarnings".to_string()]).unwrap(); - let (sess, _) = mk_session(matches); + let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let (sess, _) = mk_session(&mut handler, matches); assert!(sess.diagnostic().can_emit_warnings()); }); rustc_span::create_default_session_globals_then(|| { let matches = optgroups().parse(&["-Adead_code".to_string()]).unwrap(); - let (sess, _) = mk_session(matches); + let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let (sess, _) = mk_session(&mut handler, matches); assert!(sess.diagnostic().can_emit_warnings()); }); } @@ -167,8 +187,14 @@ fn test_output_types_tracking_hash_different_paths() { let mut v2 = Options::default(); let mut v3 = Options::default(); - v1.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("./some/thing")))]); - v2.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("/some/thing")))]); + v1.output_types = OutputTypes::new(&[( + OutputType::Exe, + Some(OutFileName::Real(PathBuf::from("./some/thing"))), + )]); + v2.output_types = OutputTypes::new(&[( + OutputType::Exe, + Some(OutFileName::Real(PathBuf::from("/some/thing"))), + )]); v3.output_types = OutputTypes::new(&[(OutputType::Exe, None)]); assert_non_crate_hash_different(&v1, &v2); @@ -182,13 +208,13 @@ fn test_output_types_tracking_hash_different_construction_order() { let mut v2 = Options::default(); v1.output_types = OutputTypes::new(&[ - (OutputType::Exe, Some(PathBuf::from("./some/thing"))), - (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), + (OutputType::Exe, Some(OutFileName::Real(PathBuf::from("./some/thing")))), + (OutputType::Bitcode, Some(OutFileName::Real(PathBuf::from("./some/thing.bc")))), ]); v2.output_types = OutputTypes::new(&[ - (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))), - (OutputType::Exe, Some(PathBuf::from("./some/thing"))), + (OutputType::Bitcode, Some(OutFileName::Real(PathBuf::from("./some/thing.bc")))), + (OutputType::Exe, Some(OutFileName::Real(PathBuf::from("./some/thing")))), ]); assert_same_hash(&v1, &v2); @@ -296,35 +322,36 @@ fn test_search_paths_tracking_hash_different_order() { let mut v3 = Options::default(); let mut v4 = Options::default(); + let handler = EarlyErrorHandler::new(JSON); const JSON: ErrorOutputType = ErrorOutputType::Json { pretty: false, json_rendered: HumanReadableErrorType::Default(ColorConfig::Never), }; // Reference - v1.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); - v1.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); - v1.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); - v1.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); - v1.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); - - v2.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); - v2.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); - v2.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); - v2.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); - v2.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); - - v3.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); - v3.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); - v3.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); - v3.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); - v3.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); - - v4.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON)); - v4.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON)); - v4.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON)); - v4.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON)); - v4.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON)); + v1.search_paths.push(SearchPath::from_cli_opt(&handler, "native=abc")); + v1.search_paths.push(SearchPath::from_cli_opt(&handler, "crate=def")); + v1.search_paths.push(SearchPath::from_cli_opt(&handler, "dependency=ghi")); + v1.search_paths.push(SearchPath::from_cli_opt(&handler, "framework=jkl")); + v1.search_paths.push(SearchPath::from_cli_opt(&handler, "all=mno")); + + v2.search_paths.push(SearchPath::from_cli_opt(&handler, "native=abc")); + v2.search_paths.push(SearchPath::from_cli_opt(&handler, "dependency=ghi")); + v2.search_paths.push(SearchPath::from_cli_opt(&handler, "crate=def")); + v2.search_paths.push(SearchPath::from_cli_opt(&handler, "framework=jkl")); + v2.search_paths.push(SearchPath::from_cli_opt(&handler, "all=mno")); + + v3.search_paths.push(SearchPath::from_cli_opt(&handler, "crate=def")); + v3.search_paths.push(SearchPath::from_cli_opt(&handler, "framework=jkl")); + v3.search_paths.push(SearchPath::from_cli_opt(&handler, "native=abc")); + v3.search_paths.push(SearchPath::from_cli_opt(&handler, "dependency=ghi")); + v3.search_paths.push(SearchPath::from_cli_opt(&handler, "all=mno")); + + v4.search_paths.push(SearchPath::from_cli_opt(&handler, "all=mno")); + v4.search_paths.push(SearchPath::from_cli_opt(&handler, "native=abc")); + v4.search_paths.push(SearchPath::from_cli_opt(&handler, "crate=def")); + v4.search_paths.push(SearchPath::from_cli_opt(&handler, "dependency=ghi")); + v4.search_paths.push(SearchPath::from_cli_opt(&handler, "framework=jkl")); assert_same_hash(&v1, &v2); assert_same_hash(&v1, &v3); @@ -554,7 +581,7 @@ fn test_codegen_options_tracking_hash() { untracked!(incremental, Some(String::from("abc"))); // `link_arg` is omitted because it just forwards to `link_args`. untracked!(link_args, vec![String::from("abc"), String::from("def")]); - untracked!(link_self_contained, Some(true)); + untracked!(link_self_contained, LinkSelfContained::on()); untracked!(linker, Some(PathBuf::from("linker"))); untracked!(linker_flavor, Some(LinkerFlavorCli::Gcc)); untracked!(no_stack_check, true); @@ -679,7 +706,7 @@ fn test_unstable_options_tracking_hash() { untracked!(ls, true); untracked!(macro_backtrace, true); untracked!(meta_stats, true); - untracked!(mir_pretty_relative_line_numbers, true); + untracked!(mir_include_spans, true); untracked!(nll_facts, true); untracked!(no_analysis, true); untracked!(no_leak_check, true); @@ -688,6 +715,7 @@ fn test_unstable_options_tracking_hash() { untracked!(perf_stats, true); // `pre_link_arg` is omitted because it just forwards to `pre_link_args`. untracked!(pre_link_args, vec![String::from("abc"), String::from("def")]); + untracked!(print_codegen_stats, true); untracked!(print_llvm_passes, true); untracked!(print_mono_items, Some(String::from("abc"))); untracked!(print_type_sizes, true); @@ -712,6 +740,7 @@ fn test_unstable_options_tracking_hash() { untracked!(unstable_options, true); untracked!(validate_mir, true); untracked!(verbose, true); + untracked!(write_long_types_to_disk, false); // tidy-alphabetical-end macro_rules! tracked { @@ -815,7 +844,7 @@ fn test_unstable_options_tracking_hash() { tracked!(thir_unsafeck, true); tracked!(tiny_const_eval_limit, true); tracked!(tls_model, Some(TlsModel::GeneralDynamic)); - tracked!(trait_solver, TraitSolver::Chalk); + tracked!(trait_solver, TraitSolver::NextCoherence); tracked!(translate_remapped_path_to_local_path, false); tracked!(trap_unreachable, Some(false)); tracked!(treat_err_as_bug, NonZeroUsize::new(1)); @@ -845,7 +874,9 @@ fn test_edition_parsing() { let options = Options::default(); assert!(options.edition == DEFAULT_EDITION); + let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let matches = optgroups().parse(&["--edition=2018".to_string()]).unwrap(); - let (sessopts, _) = build_session_options_and_crate_config(matches); + let (sessopts, _) = build_session_options_and_crate_config(&mut handler, matches); assert!(sessopts.edition == Edition::Edition2018) } diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index cb19750203e85..ad35dbbc8f965 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -11,16 +11,16 @@ use rustc_parse::validate_attr; use rustc_session as session; use rustc_session::config::CheckCfg; use rustc_session::config::{self, CrateType}; -use rustc_session::config::{ErrorOutputType, OutputFilenames}; +use rustc_session::config::{OutFileName, OutputFilenames, OutputTypes}; use rustc_session::filesearch::sysroot_candidates; use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer}; use rustc_session::parse::CrateConfig; -use rustc_session::{early_error, filesearch, output, Session}; +use rustc_session::{filesearch, output, Session}; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; use rustc_span::source_map::FileLoader; use rustc_span::symbol::{sym, Symbol}; -use session::CompilerIO; +use session::{CompilerIO, EarlyErrorHandler}; use std::env; use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; use std::mem; @@ -58,6 +58,7 @@ pub fn add_configuration( } pub fn create_session( + handler: &EarlyErrorHandler, sopts: config::Options, cfg: FxHashSet<(String, Option)>, check_cfg: CheckCfg, @@ -69,11 +70,16 @@ pub fn create_session( Box Box + Send>, >, descriptions: Registry, + ice_file: Option, ) -> (Session, Box) { let codegen_backend = if let Some(make_codegen_backend) = make_codegen_backend { make_codegen_backend(&sopts) } else { - get_codegen_backend(&sopts.maybe_sysroot, sopts.unstable_opts.codegen_backend.as_deref()) + get_codegen_backend( + handler, + &sopts.maybe_sysroot, + sopts.unstable_opts.codegen_backend.as_deref(), + ) }; // target_override is documented to be called before init(), so this is okay @@ -88,7 +94,7 @@ pub fn create_session( ) { Ok(bundle) => bundle, Err(e) => { - early_error(sopts.error_format, format!("failed to load fluent bundle: {e}")); + handler.early_error(format!("failed to load fluent bundle: {e}")); } }; @@ -96,6 +102,7 @@ pub fn create_session( locale_resources.push(codegen_backend.locale_resource()); let mut sess = session::build_session( + handler, sopts, io, bundle, @@ -105,6 +112,7 @@ pub fn create_session( file_loader, target_override, rustc_version_str().unwrap_or("unknown"), + ice_file, ); codegen_backend.init(&sess); @@ -218,16 +226,16 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, R: Send>( }) } -fn load_backend_from_dylib(path: &Path) -> MakeBackendFn { +fn load_backend_from_dylib(handler: &EarlyErrorHandler, path: &Path) -> MakeBackendFn { let lib = unsafe { Library::new(path) }.unwrap_or_else(|err| { let err = format!("couldn't load codegen backend {path:?}: {err}"); - early_error(ErrorOutputType::default(), err); + handler.early_error(err); }); let backend_sym = unsafe { lib.get::(b"__rustc_codegen_backend") } .unwrap_or_else(|e| { let err = format!("couldn't load codegen backend: {e}"); - early_error(ErrorOutputType::default(), err); + handler.early_error(err); }); // Intentionally leak the dynamic library. We can't ever unload it @@ -242,6 +250,7 @@ fn load_backend_from_dylib(path: &Path) -> MakeBackendFn { /// /// A name of `None` indicates that the default backend should be used. pub fn get_codegen_backend( + handler: &EarlyErrorHandler, maybe_sysroot: &Option, backend_name: Option<&str>, ) -> Box { @@ -251,10 +260,12 @@ pub fn get_codegen_backend( let default_codegen_backend = option_env!("CFG_DEFAULT_CODEGEN_BACKEND").unwrap_or("llvm"); match backend_name.unwrap_or(default_codegen_backend) { - filename if filename.contains('.') => load_backend_from_dylib(filename.as_ref()), + filename if filename.contains('.') => { + load_backend_from_dylib(handler, filename.as_ref()) + } #[cfg(feature = "llvm")] "llvm" => rustc_codegen_llvm::LlvmCodegenBackend::new, - backend_name => get_codegen_sysroot(maybe_sysroot, backend_name), + backend_name => get_codegen_sysroot(handler, maybe_sysroot, backend_name), } }); @@ -286,7 +297,11 @@ fn get_rustc_path_inner(bin_path: &str) -> Option { }) } -fn get_codegen_sysroot(maybe_sysroot: &Option, backend_name: &str) -> MakeBackendFn { +fn get_codegen_sysroot( + handler: &EarlyErrorHandler, + maybe_sysroot: &Option, + backend_name: &str, +) -> MakeBackendFn { // For now we only allow this function to be called once as it'll dlopen a // few things, which seems to work best if we only do that once. In // general this assertion never trips due to the once guard in `get_codegen_backend`, @@ -321,7 +336,7 @@ fn get_codegen_sysroot(maybe_sysroot: &Option, backend_name: &str) -> M "failed to find a `codegen-backends` folder \ in the sysroot candidates:\n* {candidates}" ); - early_error(ErrorOutputType::default(), err); + handler.early_error(err); }); info!("probing {} for a codegen backend", sysroot.display()); @@ -332,7 +347,7 @@ fn get_codegen_sysroot(maybe_sysroot: &Option, backend_name: &str) -> M sysroot.display(), e ); - early_error(ErrorOutputType::default(), err); + handler.early_error(err); }); let mut file: Option = None; @@ -360,16 +375,16 @@ fn get_codegen_sysroot(maybe_sysroot: &Option, backend_name: &str) -> M prev.display(), path.display() ); - early_error(ErrorOutputType::default(), err); + handler.early_error(err); } file = Some(path.clone()); } match file { - Some(ref s) => load_backend_from_dylib(s), + Some(ref s) => load_backend_from_dylib(handler, s), None => { let err = format!("unsupported builtin codegen backend `{backend_name}`"); - early_error(ErrorOutputType::default(), err); + handler.early_error(err); } } } @@ -500,7 +515,37 @@ pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec bool { + use std::io::IsTerminal; + if std::io::stdout().is_terminal() { + // If stdout is a tty, check if multiple text output types are + // specified by `--emit foo=- --emit bar=-` or `-o - --emit foo,bar` + let named_text_types = output_types + .iter() + .filter(|(f, o)| f.is_text_output() && *o == &Some(OutFileName::Stdout)) + .count(); + let unnamed_text_types = + output_types.iter().filter(|(f, o)| f.is_text_output() && o.is_none()).count(); + named_text_types > 1 || unnamed_text_types > 1 && single_output_file_is_stdout + } else { + // Otherwise, all the output types should be checked + let named_types = + output_types.values().filter(|o| *o == &Some(OutFileName::Stdout)).count(); + let unnamed_types = output_types.values().filter(|o| o.is_none()).count(); + named_types > 1 || unnamed_types > 1 && single_output_file_is_stdout + } +} + pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> OutputFilenames { + if multiple_output_types_to_stdout( + &sess.opts.output_types, + sess.io.output_file == Some(OutFileName::Stdout), + ) { + sess.emit_fatal(errors::MultipleOutputTypesToStdout); + } match sess.io.output_file { None => { // "-" as input file will cause the parser to read from stdin so we @@ -544,7 +589,7 @@ pub fn build_output_filenames(attrs: &[ast::Attribute], sess: &Session) -> Outpu OutputFilenames::new( out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(), - out_file.file_stem().unwrap_or_default().to_str().unwrap().to_string(), + out_file.filestem().unwrap_or_default().to_str().unwrap().to_string(), ofile, sess.io.temps_dir.clone(), sess.opts.cg.extra_filename.clone(), diff --git a/compiler/rustc_lexer/Cargo.toml b/compiler/rustc_lexer/Cargo.toml index 23294dc2e1b6a..2211ac1c8a753 100644 --- a/compiler/rustc_lexer/Cargo.toml +++ b/compiler/rustc_lexer/Cargo.toml @@ -16,7 +16,11 @@ Rust lexer used by rustc. No stability guarantees are provided. # Note that this crate purposefully does not depend on other rustc crates [dependencies] unicode-xid = "0.2.0" -unic-emoji-char = "0.9.0" + +[dependencies.unicode-properties] +version = "0.1.0" +default-features = false +features = ["emoji"] [dev-dependencies] expect-test = "1.4.0" diff --git a/compiler/rustc_lexer/src/cursor.rs b/compiler/rustc_lexer/src/cursor.rs index eceef59802eb9..aba7f95487e9d 100644 --- a/compiler/rustc_lexer/src/cursor.rs +++ b/compiler/rustc_lexer/src/cursor.rs @@ -24,6 +24,10 @@ impl<'a> Cursor<'a> { } } + pub fn as_str(&self) -> &'a str { + self.chars.as_str() + } + /// Returns the last eaten symbol (or `'\0'` in release builds). /// (For debug assertions only.) pub(crate) fn prev(&self) -> char { diff --git a/compiler/rustc_lexer/src/lib.rs b/compiler/rustc_lexer/src/lib.rs index d511d2b1280d9..43dfd34a6ff74 100644 --- a/compiler/rustc_lexer/src/lib.rs +++ b/compiler/rustc_lexer/src/lib.rs @@ -34,6 +34,7 @@ pub use crate::cursor::Cursor; use self::LiteralKind::*; use self::TokenKind::*; use crate::cursor::EOF_CHAR; +use unicode_properties::UnicodeEmoji; /// Parsed token. /// It doesn't contain information about data that has been parsed, @@ -428,9 +429,7 @@ impl Cursor<'_> { Literal { kind, suffix_start } } // Identifier starting with an emoji. Only lexed for graceful error recovery. - c if !c.is_ascii() && unic_emoji_char::is_emoji(c) => { - self.fake_ident_or_unknown_prefix() - } + c if !c.is_ascii() && c.is_emoji_char() => self.fake_ident_or_unknown_prefix(), _ => Unknown, }; let res = Token::new(token_kind, self.pos_within_token()); @@ -514,9 +513,7 @@ impl Cursor<'_> { // we see a prefix here, it is definitely an unknown prefix. match self.first() { '#' | '"' | '\'' => UnknownPrefix, - c if !c.is_ascii() && unic_emoji_char::is_emoji(c) => { - self.fake_ident_or_unknown_prefix() - } + c if !c.is_ascii() && c.is_emoji_char() => self.fake_ident_or_unknown_prefix(), _ => Ident, } } @@ -525,7 +522,7 @@ impl Cursor<'_> { // Start is already eaten, eat the rest of identifier. self.eat_while(|c| { unicode_xid::UnicodeXID::is_xid_continue(c) - || (!c.is_ascii() && unic_emoji_char::is_emoji(c)) + || (!c.is_ascii() && c.is_emoji_char()) || c == '\u{200d}' }); // Known prefixes must have been handled earlier. So if diff --git a/compiler/rustc_lexer/src/unescape.rs b/compiler/rustc_lexer/src/unescape.rs index c9ad54d8d9806..717b042fbdabb 100644 --- a/compiler/rustc_lexer/src/unescape.rs +++ b/compiler/rustc_lexer/src/unescape.rs @@ -372,7 +372,7 @@ where callback(start..end, EscapeError::MultipleSkippedLinesWarning); } let tail = &tail[first_non_space..]; - if let Some(c) = tail.chars().nth(0) { + 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. diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index e1658d3ff82b7..c4a7f717840ce 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -5,534 +5,562 @@ lint_array_into_iter = .use_explicit_into_iter_suggestion = or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value -lint_enum_intrinsics_mem_discriminant = - the return value of `mem::discriminant` is unspecified when called with a non-enum type - .note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum. +lint_atomic_ordering_fence = memory fences cannot have `Relaxed` ordering + .help = consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst` -lint_enum_intrinsics_mem_variant = - the return value of `mem::variant_count` is unspecified when called with a non-enum type - .note = the type parameter of `variant_count` should be an enum, but it was instantiated with the type `{$ty_param}`, which is not an enum. +lint_atomic_ordering_invalid = `{$method}`'s failure ordering may not be `Release` or `AcqRel`, since a failed `{$method}` does not result in a write + .label = invalid failure ordering + .help = consider using `Acquire` or `Relaxed` failure ordering instead -lint_expectation = this lint expectation is unfulfilled - .note = the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message - .rationale = {$rationale} +lint_atomic_ordering_load = atomic loads cannot have `Release` or `AcqRel` ordering + .help = consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` -lint_for_loops_over_fallibles = - for loop over {$article} `{$ty}`. This is more readably written as an `if let` statement - .suggestion = consider using `if let` to clear intent - .remove_next = to iterate over `{$recv_snip}` remove the call to `next` - .use_while_let = to check pattern in a loop use `while let` - .use_question_mark = consider unwrapping the `Result` with `?` to iterate over its contents +lint_atomic_ordering_store = atomic stores cannot have `Acquire` or `AcqRel` ordering + .help = consider using ordering modes `Release`, `SeqCst` or `Relaxed` -lint_map_unit_fn = `Iterator::map` call that discard the iterator's values - .note = `Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated - .function_label = this function returns `()`, which is likely not what you wanted - .argument_label = called `Iterator::map` with callable that returns `()` - .map_label = after this call to map, the resulting iterator is `impl Iterator`, which means the only information carried by the iterator is the number of items - .suggestion = you might have meant to use `Iterator::for_each` +lint_bad_attribute_argument = bad attribute argument -lint_non_binding_let_on_sync_lock = - non-binding let on a synchronization lock +lint_bad_opt_access = {$msg} -lint_non_binding_let_on_drop_type = - non-binding let on a type that implements `Drop` +lint_builtin_allow_internal_unsafe = + `allow_internal_unsafe` allows defining macros using unsafe without triggering the `unsafe_code` lint at their call site -lint_non_binding_let_suggestion = - consider binding to an unused variable to avoid immediately dropping the value +lint_builtin_anonymous_params = anonymous parameters are deprecated and will be removed in the next edition + .suggestion = try naming the parameter or explicitly ignoring it -lint_non_binding_let_multi_suggestion = - consider immediately dropping the value +lint_builtin_asm_labels = avoid using named labels in inline assembly -lint_deprecated_lint_name = - lint name `{$name}` is deprecated and may not have an effect in the future. - .suggestion = change it to +lint_builtin_box_pointers = type uses owned (Box type) pointers: {$ty} -lint_renamed_or_removed_lint = {$msg} - .suggestion = use the new name +lint_builtin_clashing_extern_diff_name = `{$this}` redeclares `{$orig}` with a different signature + .previous_decl_label = `{$orig}` previously declared here + .mismatch_label = this signature doesn't match the previous declaration -lint_suspicious_double_ref_op = - using `.{$call}()` on a double reference, which returns `{$ty}` instead of {$op -> - *[should_not_happen] [{$op}] - [deref] dereferencing - [borrow] borrowing - [clone] cloning - } the inner type +lint_builtin_clashing_extern_same_name = `{$this}` redeclared with a different signature + .previous_decl_label = `{$orig}` previously declared here + .mismatch_label = this signature doesn't match the previous declaration +lint_builtin_const_no_mangle = const items should never be `#[no_mangle]` + .suggestion = try a static value -lint_unknown_lint = - unknown lint: `{$name}` - .suggestion = did you mean +lint_builtin_decl_unsafe_fn = declaration of an `unsafe` function +lint_builtin_decl_unsafe_method = declaration of an `unsafe` method +lint_builtin_deprecated_attr_default_suggestion = remove this attribute -lint_ignored_unless_crate_specified = {$level}({$name}) is ignored unless specified at crate level +lint_builtin_deprecated_attr_link = use of deprecated attribute `{$name}`: {$reason}. See {$link} + .msg_suggestion = {$msg} + .default_suggestion = remove this attribute +lint_builtin_deprecated_attr_used = use of deprecated attribute `{$name}`: no longer used. +lint_builtin_deref_nullptr = dereferencing a null pointer + .label = this code causes undefined behavior when executed -lint_unknown_gated_lint = - unknown lint: `{$name}` - .note = the `{$name}` lint is unstable +lint_builtin_ellipsis_inclusive_range_patterns = `...` range patterns are deprecated + .suggestion = use `..=` for an inclusive range -lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label} - .label = this {$label} contains {$count -> - [one] an invisible - *[other] invisible - } unicode text flow control {$count -> - [one] codepoint - *[other] codepoints +lint_builtin_explicit_outlives = outlives requirements can be inferred + .suggestion = remove {$count -> + [one] this bound + *[other] these bounds } - .note = these kind of unicode codepoints change the way text flows on applications that support them, but can cause confusion because they change the order of characters on the screen - .suggestion_remove = if their presence wasn't intentional, you can remove them - .suggestion_escape = if you want to keep them but make them visible in your source code, you can escape them - .no_suggestion_note_escape = if you want to keep them but make them visible in your source code, you can escape them: {$escaped} -lint_default_hash_types = prefer `{$preferred}` over `{$used}`, it has better performance - .note = a `use rustc_data_structures::fx::{$preferred}` may be necessary +lint_builtin_export_name_fn = declaration of a function with `export_name` +lint_builtin_export_name_method = declaration of a method with `export_name` -lint_query_instability = using `{$query}` can result in unstable query results - .note = if you believe this case to be fine, allow this lint and add a comment explaining your rationale +lint_builtin_export_name_static = declaration of a static with `export_name` +lint_builtin_impl_unsafe_method = implementation of an `unsafe` method -lint_tykind_kind = usage of `ty::TyKind::` - .suggestion = try using `ty::` directly +lint_builtin_incomplete_features = the feature `{$name}` is incomplete and may not be safe to use and/or cause compiler crashes + .note = see issue #{$n} for more information + .help = consider using `min_{$name}` instead, which is more stable and complete -lint_tykind = usage of `ty::TyKind` - .help = try using `Ty` instead +lint_builtin_internal_features = the feature `{$name}` is internal to the compiler or standard library + .note = using it is strongly discouraged -lint_ty_qualified = usage of qualified `ty::{$ty}` - .suggestion = try importing it and using it unqualified +lint_builtin_keyword_idents = `{$kw}` is a keyword in the {$next} edition + .suggestion = you can use a raw identifier to stay compatible -lint_lintpass_by_hand = implementing `LintPass` by hand - .help = try using `declare_lint_pass!` or `impl_lint_pass!` instead +lint_builtin_link_section_fn = declaration of a function with `link_section` -lint_non_existent_doc_keyword = found non-existing keyword `{$keyword}` used in `#[doc(keyword = "...")]` - .help = only existing keywords are allowed in core/std +lint_builtin_link_section_static = declaration of a static with `link_section` -lint_diag_out_of_impl = - diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls +lint_builtin_missing_copy_impl = type could implement `Copy`; consider adding `impl Copy` -lint_untranslatable_diag = diagnostics should be created using translatable messages +lint_builtin_missing_debug_impl = + type does not implement `{$debug}`; consider adding `#[derive(Debug)]` or a manual implementation -lint_trivial_untranslatable_diag = diagnostic with static strings only +lint_builtin_missing_doc = missing documentation for {$article} {$desc} -lint_bad_opt_access = {$msg} +lint_builtin_mutable_transmutes = + transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell -lint_cstring_ptr = getting the inner pointer of a temporary `CString` - .as_ptr_label = this pointer will be invalid - .unwrap_label = this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime - .note = pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned - .help = for more information, see https://doc.rust-lang.org/reference/destructors.html +lint_builtin_no_mangle_fn = declaration of a `no_mangle` function +lint_builtin_no_mangle_generic = functions generic over types or consts must be mangled + .suggestion = remove this attribute -lint_multiple_supertrait_upcastable = `{$ident}` is object-safe and has multiple supertraits +lint_builtin_no_mangle_method = declaration of a `no_mangle` method +lint_builtin_no_mangle_static = declaration of a `no_mangle` static +lint_builtin_non_shorthand_field_patterns = the `{$ident}:` in this pattern is redundant + .suggestion = use shorthand field pattern -lint_identifier_non_ascii_char = identifier contains non-ASCII characters +lint_builtin_overridden_symbol_name = + the linker's behavior with multiple libraries exporting duplicate symbol names is undefined and Rust cannot provide guarantees when you manually override them -lint_identifier_uncommon_codepoints = identifier contains uncommon Unicode codepoints +lint_builtin_overridden_symbol_section = + the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them -lint_confusable_identifier_pair = identifier pair considered confusable between `{$existing_sym}` and `{$sym}` - .label = this is where the previous identifier occurred +lint_builtin_special_module_name_used_lib = found module declaration for lib.rs + .note = lib.rs is the root of this crate's library target + .help = to refer to it from other targets, use the library's name as the path -lint_mixed_script_confusables = - the usage of Script Group `{$set}` in this crate consists solely of mixed script confusables - .includes_note = the usage includes {$includes} - .note = please recheck to make sure their usages are indeed what you want +lint_builtin_special_module_name_used_main = found module declaration for main.rs + .note = a binary crate cannot be used as library -lint_non_fmt_panic = panic message is not a string literal - .note = this usage of `{$name}!()` is deprecated; it will be a hard error in Rust 2021 - .more_info_note = for more information, see - .supports_fmt_note = the `{$name}!()` macro supports formatting, so there's no need for the `format!()` macro here - .supports_fmt_suggestion = remove the `format!(..)` macro call - .display_suggestion = add a "{"{"}{"}"}" format string to `Display` the message - .debug_suggestion = - add a "{"{"}:?{"}"}" format string to use the `Debug` implementation of `{$ty}` - .panic_suggestion = {$already_suggested -> - [true] or use - *[false] use - } std::panic::panic_any instead +lint_builtin_trivial_bounds = {$predicate_kind_name} bound {$predicate} does not depend on any type or lifetime parameters -lint_non_fmt_panic_unused = - panic message contains {$count -> - [one] an unused - *[other] unused - } formatting {$count -> - [one] placeholder - *[other] placeholders - } - .note = this message is not used as a format string when given without arguments, but will be in Rust 2021 - .add_args_suggestion = add the missing {$count -> - [one] argument - *[other] arguments - } - .add_fmt_suggestion = or add a "{"{"}{"}"}" format string to use the message literally +lint_builtin_type_alias_bounds_help = use fully disambiguated paths (i.e., `::Assoc`) to refer to associated types in type aliases -lint_non_fmt_panic_braces = - panic message contains {$count -> - [one] a brace - *[other] braces - } - .note = this message is not used as a format string, but will be in Rust 2021 - .suggestion = add a "{"{"}{"}"}" format string to use the message literally +lint_builtin_type_alias_generic_bounds = bounds on generic parameters are not enforced in type aliases + .suggestion = the bound will not be checked when the type alias is used, and should be removed -lint_non_camel_case_type = {$sort} `{$name}` should have an upper camel case name - .suggestion = convert the identifier to upper camel case - .label = should have an UpperCamelCase name +lint_builtin_type_alias_where_clause = where clauses are not enforced in type aliases + .suggestion = the clause will not be checked when the type alias is used, and should be removed -lint_non_snake_case = {$sort} `{$name}` should have a snake case name - .rename_or_convert_suggestion = rename the identifier or convert it to a snake case raw identifier - .cannot_convert_note = `{$sc}` cannot be used as a raw identifier - .rename_suggestion = rename the identifier - .convert_suggestion = convert the identifier to snake case - .help = convert the identifier to snake case: `{$sc}` - .label = should have a snake_case name +lint_builtin_unexpected_cli_config_name = unexpected `{$name}` as condition name + .help = was set with `--cfg` but isn't in the `--check-cfg` expected names -lint_non_upper_case_global = {$sort} `{$name}` should have an upper case name - .suggestion = convert the identifier to upper case - .label = should have an UPPER_CASE name +lint_builtin_unexpected_cli_config_value = unexpected condition value `{$value}` for condition name `{$name}` + .help = was set with `--cfg` but isn't in the `--check-cfg` expected values -lint_noop_method_call = call to `.{$method}()` on a reference in this situation does nothing - .label = unnecessary method call - .note = the type `{$receiver_ty}` which `{$method}` is being called on is the same as the type returned from `{$method}`, so the method call does not do anything and can be removed +lint_builtin_unpermitted_type_init_label = this code causes undefined behavior when executed +lint_builtin_unpermitted_type_init_label_suggestion = help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done -lint_pass_by_value = passing `{$ty}` by reference - .suggestion = try passing by value +lint_builtin_unpermitted_type_init_uninit = the type `{$ty}` does not permit being left uninitialized -lint_redundant_semicolons = - unnecessary trailing {$multiple -> - [true] semicolons - *[false] semicolon - } - .suggestion = remove {$multiple -> - [true] these semicolons - *[false] this semicolon - } +lint_builtin_unpermitted_type_init_zeroed = the type `{$ty}` does not permit zero-initialization +lint_builtin_unreachable_pub = unreachable `pub` {$what} + .suggestion = consider restricting its visibility + .help = or consider exporting it for use by other crates -lint_drop_trait_constraints = - bounds on `{$predicate}` are most likely incorrect, consider instead using `{$needs_drop}` to detect whether a type can be trivially dropped +lint_builtin_unsafe_block = usage of an `unsafe` block -lint_drop_glue = - types that do not implement `Drop` can still have drop glue, consider instead using `{$needs_drop}` to detect whether a type is trivially dropped +lint_builtin_unsafe_impl = implementation of an `unsafe` trait -lint_range_endpoint_out_of_range = range endpoint is out of range for `{$ty}` +lint_builtin_unsafe_trait = declaration of an `unsafe` trait -lint_range_use_inclusive_range = use an inclusive range instead +lint_builtin_unstable_features = unstable feature +lint_builtin_unused_doc_comment = unused doc comment + .label = rustdoc does not generate documentation for {$kind} + .plain_help = use `//` for a plain comment + .block_help = use `/* */` for a plain comment -lint_overflowing_bin_hex = literal out of range for `{$ty}` - .negative_note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}` - .negative_becomes_note = and the value `-{$lit}` will become `{$actually}{$ty}` - .positive_note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}` and will become `{$actually}{$ty}` - .suggestion = consider using the type `{$suggestion_ty}` instead - .help = consider using the type `{$suggestion_ty}` instead +lint_builtin_while_true = denote infinite loops with `loop {"{"} ... {"}"}` + .suggestion = use `loop` -lint_overflowing_int = literal out of range for `{$ty}` - .note = the literal `{$lit}` does not fit into the type `{$ty}` whose range is `{$min}..={$max}` - .help = consider using the type `{$suggestion_ty}` instead +lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name} -lint_only_cast_u8_to_char = only `u8` can be cast into `char` - .suggestion = use a `char` literal instead +lint_check_name_unknown = unknown lint: `{$lint_name}` + .help = did you mean: `{$suggestion}` -lint_overflowing_uint = literal out of range for `{$ty}` - .note = the literal `{$lit}` does not fit into the type `{$ty}` whose range is `{$min}..={$max}` +lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}` -lint_overflowing_literal = literal out of range for `{$ty}` - .note = the literal `{$lit}` does not fit into the type `{$ty}` and will be converted to `{$ty}::INFINITY` +lint_check_name_warning = {$msg} -lint_unused_comparisons = comparison is useless due to type limits +lint_command_line_source = `forbid` lint level was set on command line -lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe - .label = not FFI-safe - .note = the type is defined here +lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as identifiers, which look alike + .current_use = this identifier can be confused with `{$existing_sym}` + .other_use = other identifier used here -lint_improper_ctypes_opaque = opaque types have no C equivalent +lint_cstring_ptr = getting the inner pointer of a temporary `CString` + .as_ptr_label = this pointer will be invalid + .unwrap_label = this `CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime + .note = pointers do not have a lifetime; when calling `as_ptr` the `CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned + .help = for more information, see https://doc.rust-lang.org/reference/destructors.html -lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention -lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead +lint_default_hash_types = prefer `{$preferred}` over `{$used}`, it has better performance + .note = a `use rustc_data_structures::fx::{$preferred}` may be necessary -lint_improper_ctypes_tuple_reason = tuples have unspecified layout -lint_improper_ctypes_tuple_help = consider using a struct instead +lint_default_source = `forbid` lint level is the default for {$id} -lint_improper_ctypes_str_reason = string slices have no C equivalent -lint_improper_ctypes_str_help = consider using `*const u8` and a length instead +lint_deprecated_lint_name = + lint name `{$name}` is deprecated and may not have an effect in the future. + .suggestion = change it to -lint_improper_ctypes_dyn = trait objects have no C equivalent +lint_diag_out_of_impl = + diagnostics should only be created in `IntoDiagnostic`/`AddToDiagnostic` impls -lint_improper_ctypes_slice_reason = slices have no C equivalent -lint_improper_ctypes_slice_help = consider using a raw pointer instead +lint_drop_glue = + types that do not implement `Drop` can still have drop glue, consider instead using `{$needs_drop}` to detect whether a type is trivially dropped -lint_improper_ctypes_128bit = 128-bit integers don't currently have a known stable ABI +lint_drop_trait_constraints = + bounds on `{$predicate}` are most likely incorrect, consider instead using `{$needs_drop}` to detect whether a type can be trivially dropped -lint_improper_ctypes_char_reason = the `char` type has no C equivalent -lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead +lint_dropping_copy_types = calls to `std::mem::drop` with a value that implements `Copy` does nothing + .label = argument has type `{$arg_ty}` + .note = use `let _ = ...` to ignore the expression or result -lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive -lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants +lint_dropping_references = calls to `std::mem::drop` with a reference instead of an owned value does nothing + .label = argument has type `{$arg_ty}` + .note = use `let _ = ...` to ignore the expression or result -lint_improper_ctypes_enum_repr_reason = enum has no representation hint -lint_improper_ctypes_enum_repr_help = - consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum +lint_enum_intrinsics_mem_discriminant = + the return value of `mem::discriminant` is unspecified when called with a non-enum type + .note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum. -lint_improper_ctypes_struct_fieldless_reason = this struct has no fields -lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct +lint_enum_intrinsics_mem_variant = + the return value of `mem::variant_count` is unspecified when called with a non-enum type + .note = the type parameter of `variant_count` should be an enum, but it was instantiated with the type `{$ty_param}`, which is not an enum. -lint_improper_ctypes_union_fieldless_reason = this union has no fields -lint_improper_ctypes_union_fieldless_help = consider adding a member to this union +lint_expectation = this lint expectation is unfulfilled + .note = the `unfulfilled_lint_expectations` lint can't be expected and will always produce this message + .rationale = {$rationale} -lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive -lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive +lint_for_loops_over_fallibles = + for loop over {$article} `{$ty}`. This is more readably written as an `if let` statement + .suggestion = consider using `if let` to clear intent + .remove_next = to iterate over `{$recv_snip}` remove the call to `next` + .use_while_let = to check pattern in a loop use `while let` + .use_question_mark = consider unwrapping the `Result` with `?` to iterate over its contents -lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout -lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct +lint_forgetting_copy_types = calls to `std::mem::forget` with a value that implements `Copy` does nothing + .label = argument has type `{$arg_ty}` + .note = use `let _ = ...` to ignore the expression or result +lint_forgetting_references = calls to `std::mem::forget` with a reference instead of an owned value does nothing + .label = argument has type `{$arg_ty}` + .note = use `let _ = ...` to ignore the expression or result -lint_improper_ctypes_union_layout_reason = this union has unspecified layout -lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union +lint_hidden_unicode_codepoints = unicode codepoint changing visible direction of text present in {$label} + .label = this {$label} contains {$count -> + [one] an invisible + *[other] invisible + } unicode text flow control {$count -> + [one] codepoint + *[other] codepoints + } + .note = these kind of unicode codepoints change the way text flows on applications that support them, but can cause confusion because they change the order of characters on the screen + .suggestion_remove = if their presence wasn't intentional, you can remove them + .suggestion_escape = if you want to keep them but make them visible in your source code, you can escape them + .no_suggestion_note_escape = if you want to keep them but make them visible in your source code, you can escape them: {$escaped} -lint_improper_ctypes_box = box cannot be represented as a single pointer +lint_identifier_non_ascii_char = identifier contains non-ASCII characters -lint_improper_ctypes_enum_phantomdata = this enum contains a PhantomData field +lint_identifier_uncommon_codepoints = identifier contains uncommon Unicode codepoints -lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields +lint_ignored_unless_crate_specified = {$level}({$name}) is ignored unless specified at crate level + +lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe + .label = not FFI-safe + .note = the type is defined here + +lint_improper_ctypes_128bit = 128-bit integers don't currently have a known stable ABI -lint_improper_ctypes_array_reason = passing raw arrays by value is not FFI-safe lint_improper_ctypes_array_help = consider passing a pointer to the array -lint_improper_ctypes_only_phantomdata = composed only of `PhantomData` +lint_improper_ctypes_array_reason = passing raw arrays by value is not FFI-safe +lint_improper_ctypes_box = box cannot be represented as a single pointer -lint_variant_size_differences = - enum variant is more than three times larger ({$largest} bytes) than the next largest +lint_improper_ctypes_char_help = consider using `u32` or `libc::wchar_t` instead -lint_atomic_ordering_load = atomic loads cannot have `Release` or `AcqRel` ordering - .help = consider using ordering modes `Acquire`, `SeqCst` or `Relaxed` +lint_improper_ctypes_char_reason = the `char` type has no C equivalent +lint_improper_ctypes_dyn = trait objects have no C equivalent -lint_atomic_ordering_store = atomic stores cannot have `Acquire` or `AcqRel` ordering - .help = consider using ordering modes `Release`, `SeqCst` or `Relaxed` +lint_improper_ctypes_enum_repr_help = + consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum -lint_atomic_ordering_fence = memory fences cannot have `Relaxed` ordering - .help = consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst` +lint_improper_ctypes_enum_repr_reason = enum has no representation hint +lint_improper_ctypes_fnptr_help = consider using an `extern fn(...) -> ...` function pointer instead -lint_atomic_ordering_invalid = `{$method}`'s failure ordering may not be `Release` or `AcqRel`, since a failed `{$method}` does not result in a write - .label = invalid failure ordering - .help = consider using `Acquire` or `Relaxed` failure ordering instead +lint_improper_ctypes_fnptr_reason = this function pointer has Rust-specific calling convention +lint_improper_ctypes_non_exhaustive = this enum is non-exhaustive +lint_improper_ctypes_non_exhaustive_variant = this enum has non-exhaustive variants -lint_unused_op = unused {$op} that must be used - .label = the {$op} produces a value - .suggestion = use `let _ = ...` to ignore the resulting value +lint_improper_ctypes_only_phantomdata = composed only of `PhantomData` -lint_unused_result = unused result of type `{$ty}` +lint_improper_ctypes_opaque = opaque types have no C equivalent -lint_unused_closure = - unused {$pre}{$count -> - [one] closure - *[other] closures - }{$post} that must be used - .note = closures are lazy and do nothing unless called +lint_improper_ctypes_slice_help = consider using a raw pointer instead -lint_unused_generator = - unused {$pre}{$count -> - [one] generator - *[other] generator - }{$post} that must be used - .note = generators are lazy and do nothing unless resumed +lint_improper_ctypes_slice_reason = slices have no C equivalent +lint_improper_ctypes_str_help = consider using `*const u8` and a length instead -lint_unused_def = unused {$pre}`{$def}`{$post} that must be used - .suggestion = use `let _ = ...` to ignore the resulting value +lint_improper_ctypes_str_reason = string slices have no C equivalent +lint_improper_ctypes_struct_fieldless_help = consider adding a member to this struct -lint_path_statement_drop = path statement drops value - .suggestion = use `drop` to clarify the intent +lint_improper_ctypes_struct_fieldless_reason = this struct has no fields +lint_improper_ctypes_struct_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct -lint_path_statement_no_effect = path statement with no effect +lint_improper_ctypes_struct_layout_reason = this struct has unspecified layout +lint_improper_ctypes_struct_non_exhaustive = this struct is non-exhaustive +lint_improper_ctypes_struct_zst = this struct contains only zero-sized fields -lint_unused_delim = unnecessary {$delim} around {$item} - .suggestion = remove these {$delim} +lint_improper_ctypes_tuple_help = consider using a struct instead -lint_unused_import_braces = braces around {$node} is unnecessary +lint_improper_ctypes_tuple_reason = tuples have unspecified layout +lint_improper_ctypes_union_fieldless_help = consider adding a member to this union -lint_unused_allocation = unnecessary allocation, use `&` instead -lint_unused_allocation_mut = unnecessary allocation, use `&mut` instead +lint_improper_ctypes_union_fieldless_reason = this union has no fields +lint_improper_ctypes_union_layout_help = consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this union -lint_builtin_while_true = denote infinite loops with `loop {"{"} ... {"}"}` - .suggestion = use `loop` +lint_improper_ctypes_union_layout_reason = this union has unspecified layout +lint_improper_ctypes_union_non_exhaustive = this union is non-exhaustive -lint_builtin_box_pointers = type uses owned (Box type) pointers: {$ty} +# FIXME: we should ordinalize $valid_up_to when we add support for doing so +lint_invalid_from_utf8_checked = calls to `{$method}` with a invalid literal always return an error + .label = the literal was valid UTF-8 up to the {$valid_up_to} bytes -lint_builtin_non_shorthand_field_patterns = the `{$ident}:` in this pattern is redundant - .suggestion = use shorthand field pattern +# FIXME: we should ordinalize $valid_up_to when we add support for doing so +lint_invalid_from_utf8_unchecked = calls to `{$method}` with a invalid literal are undefined behavior + .label = the literal was valid UTF-8 up to the {$valid_up_to} bytes -lint_builtin_overridden_symbol_name = - the linker's behavior with multiple libraries exporting duplicate symbol names is undefined and Rust cannot provide guarantees when you manually override them +lint_invalid_nan_comparisons_eq_ne = incorrect NaN comparison, NaN cannot be directly compared to itself + .suggestion = use `f32::is_nan()` or `f64::is_nan()` instead -lint_builtin_overridden_symbol_section = - the program's behavior with overridden link sections on items is unpredictable and Rust cannot provide guarantees when you manually override them +lint_invalid_nan_comparisons_lt_le_gt_ge = incorrect NaN comparison, NaN is not orderable -lint_builtin_allow_internal_unsafe = - `allow_internal_unsafe` allows defining macros using unsafe without triggering the `unsafe_code` lint at their call site +lint_invalid_reference_casting_assign_to_ref = assigning to `&T` is undefined behavior, consider using an `UnsafeCell` + .label = casting happend here -lint_builtin_unsafe_block = usage of an `unsafe` block +lint_invalid_reference_casting_borrow_as_mut = casting `&T` to `&mut T` is undefined behavior, even if the reference is unused, consider instead using an `UnsafeCell` + .label = casting happend here -lint_builtin_unsafe_trait = declaration of an `unsafe` trait +lint_lintpass_by_hand = implementing `LintPass` by hand + .help = try using `declare_lint_pass!` or `impl_lint_pass!` instead -lint_builtin_unsafe_impl = implementation of an `unsafe` trait +lint_malformed_attribute = malformed lint attribute input + +lint_map_unit_fn = `Iterator::map` call that discard the iterator's values + .note = `Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated + .function_label = this function returns `()`, which is likely not what you wanted + .argument_label = called `Iterator::map` with callable that returns `()` + .map_label = after this call to map, the resulting iterator is `impl Iterator`, which means the only information carried by the iterator is the number of items + .suggestion = you might have meant to use `Iterator::for_each` + +lint_mixed_script_confusables = + the usage of Script Group `{$set}` in this crate consists solely of mixed script confusables + .includes_note = the usage includes {$includes} + .note = please recheck to make sure their usages are indeed what you want + +lint_multiple_supertrait_upcastable = `{$ident}` is object-safe and has multiple supertraits + +lint_node_source = `forbid` level set here + .note = {$reason} -lint_builtin_no_mangle_fn = declaration of a `no_mangle` function -lint_builtin_export_name_fn = declaration of a function with `export_name` -lint_builtin_link_section_fn = declaration of a function with `link_section` +lint_non_binding_let_multi_suggestion = + consider immediately dropping the value -lint_builtin_no_mangle_static = declaration of a `no_mangle` static -lint_builtin_export_name_static = declaration of a static with `export_name` -lint_builtin_link_section_static = declaration of a static with `link_section` +lint_non_binding_let_on_drop_type = + non-binding let on a type that implements `Drop` -lint_builtin_no_mangle_method = declaration of a `no_mangle` method -lint_builtin_export_name_method = declaration of a method with `export_name` +lint_non_binding_let_on_sync_lock = + non-binding let on a synchronization lock -lint_builtin_decl_unsafe_fn = declaration of an `unsafe` function -lint_builtin_decl_unsafe_method = declaration of an `unsafe` method -lint_builtin_impl_unsafe_method = implementation of an `unsafe` method +lint_non_binding_let_suggestion = + consider binding to an unused variable to avoid immediately dropping the value -lint_builtin_missing_doc = missing documentation for {$article} {$desc} +lint_non_camel_case_type = {$sort} `{$name}` should have an upper camel case name + .suggestion = convert the identifier to upper camel case + .label = should have an UpperCamelCase name -lint_builtin_missing_copy_impl = type could implement `Copy`; consider adding `impl Copy` +lint_non_existent_doc_keyword = found non-existing keyword `{$keyword}` used in `#[doc(keyword = "...")]` + .help = only existing keywords are allowed in core/std -lint_builtin_missing_debug_impl = - type does not implement `{$debug}`; consider adding `#[derive(Debug)]` or a manual implementation +lint_non_fmt_panic = panic message is not a string literal + .note = this usage of `{$name}!()` is deprecated; it will be a hard error in Rust 2021 + .more_info_note = for more information, see + .supports_fmt_note = the `{$name}!()` macro supports formatting, so there's no need for the `format!()` macro here + .supports_fmt_suggestion = remove the `format!(..)` macro call + .display_suggestion = add a "{"{"}{"}"}" format string to `Display` the message + .debug_suggestion = + add a "{"{"}:?{"}"}" format string to use the `Debug` implementation of `{$ty}` + .panic_suggestion = {$already_suggested -> + [true] or use + *[false] use + } std::panic::panic_any instead -lint_builtin_anonymous_params = anonymous parameters are deprecated and will be removed in the next edition - .suggestion = try naming the parameter or explicitly ignoring it +lint_non_fmt_panic_braces = + panic message contains {$count -> + [one] a brace + *[other] braces + } + .note = this message is not used as a format string, but will be in Rust 2021 + .suggestion = add a "{"{"}{"}"}" format string to use the message literally -lint_builtin_deprecated_attr_link = use of deprecated attribute `{$name}`: {$reason}. See {$link} - .msg_suggestion = {$msg} - .default_suggestion = remove this attribute -lint_builtin_deprecated_attr_used = use of deprecated attribute `{$name}`: no longer used. -lint_builtin_deprecated_attr_default_suggestion = remove this attribute +lint_non_fmt_panic_unused = + panic message contains {$count -> + [one] an unused + *[other] unused + } formatting {$count -> + [one] placeholder + *[other] placeholders + } + .note = this message is not used as a format string when given without arguments, but will be in Rust 2021 + .add_args_suggestion = add the missing {$count -> + [one] argument + *[other] arguments + } + .add_fmt_suggestion = or add a "{"{"}{"}"}" format string to use the message literally -lint_builtin_unused_doc_comment = unused doc comment - .label = rustdoc does not generate documentation for {$kind} - .plain_help = use `//` for a plain comment - .block_help = use `/* */` for a plain comment +lint_non_snake_case = {$sort} `{$name}` should have a snake case name + .rename_or_convert_suggestion = rename the identifier or convert it to a snake case raw identifier + .cannot_convert_note = `{$sc}` cannot be used as a raw identifier + .rename_suggestion = rename the identifier + .convert_suggestion = convert the identifier to snake case + .help = convert the identifier to snake case: `{$sc}` + .label = should have a snake_case name -lint_builtin_no_mangle_generic = functions generic over types or consts must be mangled - .suggestion = remove this attribute +lint_non_upper_case_global = {$sort} `{$name}` should have an upper case name + .suggestion = convert the identifier to upper case + .label = should have an UPPER_CASE name -lint_builtin_const_no_mangle = const items should never be `#[no_mangle]` - .suggestion = try a static value +lint_noop_method_call = call to `.{$method}()` on a reference in this situation does nothing + .suggestion = remove this redundant call + .note = the type `{$orig_ty}` does not implement `{$trait_}`, so calling `{$method}` on `&{$orig_ty}` copies the reference, which does not do anything and can be removed -lint_builtin_mutable_transmutes = - transmuting &T to &mut T is undefined behavior, even if the reference is unused, consider instead using an UnsafeCell +lint_only_cast_u8_to_char = only `u8` can be cast into `char` + .suggestion = use a `char` literal instead -lint_builtin_unstable_features = unstable feature +lint_opaque_hidden_inferred_bound = opaque type `{$ty}` does not satisfy its associated type bounds + .specifically = this associated type bound is unsatisfied for `{$proj_ty}` -lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op - .label = this function will not propagate the caller location +lint_opaque_hidden_inferred_bound_sugg = add this bound -lint_builtin_unreachable_pub = unreachable `pub` {$what} - .suggestion = consider restricting its visibility - .help = or consider exporting it for use by other crates +lint_overflowing_bin_hex = literal out of range for `{$ty}` + .negative_note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}` + .negative_becomes_note = and the value `-{$lit}` will become `{$actually}{$ty}` + .positive_note = the literal `{$lit}` (decimal `{$dec}`) does not fit into the type `{$ty}` and will become `{$actually}{$ty}` + .suggestion = consider using the type `{$suggestion_ty}` instead + .sign_bit_suggestion = to use as a negative number (decimal `{$negative_val}`), consider using the type `{$uint_ty}` for the literal and cast it to `{$int_ty}` + .help = consider using the type `{$suggestion_ty}` instead -lint_builtin_unexpected_cli_config_name = unexpected `{$name}` as condition name - .help = was set with `--cfg` but isn't in the `--check-cfg` expected names +lint_overflowing_int = literal out of range for `{$ty}` + .note = the literal `{$lit}` does not fit into the type `{$ty}` whose range is `{$min}..={$max}` + .help = consider using the type `{$suggestion_ty}` instead -lint_builtin_unexpected_cli_config_value = unexpected condition value `{$value}` for condition name `{$name}` - .help = was set with `--cfg` but isn't in the `--check-cfg` expected values +lint_overflowing_literal = literal out of range for `{$ty}` + .note = the literal `{$lit}` does not fit into the type `{$ty}` and will be converted to `{$ty}::INFINITY` -lint_builtin_type_alias_bounds_help = use fully disambiguated paths (i.e., `::Assoc`) to refer to associated types in type aliases +lint_overflowing_uint = literal out of range for `{$ty}` + .note = the literal `{$lit}` does not fit into the type `{$ty}` whose range is `{$min}..={$max}` -lint_builtin_type_alias_where_clause = where clauses are not enforced in type aliases - .suggestion = the clause will not be checked when the type alias is used, and should be removed +lint_overruled_attribute = {$lint_level}({$lint_source}) incompatible with previous forbid + .label = overruled by previous forbid -lint_builtin_type_alias_generic_bounds = bounds on generic parameters are not enforced in type aliases - .suggestion = the bound will not be checked when the type alias is used, and should be removed +lint_pass_by_value = passing `{$ty}` by reference + .suggestion = try passing by value -lint_builtin_trivial_bounds = {$predicate_kind_name} bound {$predicate} does not depend on any type or lifetime parameters +lint_path_statement_drop = path statement drops value + .suggestion = use `drop` to clarify the intent -lint_builtin_ellipsis_inclusive_range_patterns = `...` range patterns are deprecated - .suggestion = use `..=` for an inclusive range +lint_path_statement_no_effect = path statement with no effect -lint_builtin_unnameable_test_items = cannot test inner items +lint_ptr_null_checks_fn_ptr = function pointers are not nullable, so checking them for null will always return false + .help = wrap the function pointer inside an `Option` and use `Option::is_none` to check for null pointer value + .label = expression has type `{$orig_ty}` -lint_builtin_keyword_idents = `{$kw}` is a keyword in the {$next} edition - .suggestion = you can use a raw identifier to stay compatible +lint_ptr_null_checks_ref = references are not nullable, so checking them for null will always return false + .label = expression has type `{$orig_ty}` -lint_builtin_explicit_outlives = outlives requirements can be inferred - .suggestion = remove {$count -> - [one] this bound - *[other] these bounds - } +lint_query_instability = using `{$query}` can result in unstable query results + .note = if you believe this case to be fine, allow this lint and add a comment explaining your rationale -lint_builtin_incomplete_features = the feature `{$name}` is incomplete and may not be safe to use and/or cause compiler crashes - .note = see issue #{$n} for more information - .help = consider using `min_{$name}` instead, which is more stable and complete +lint_range_endpoint_out_of_range = range endpoint is out of range for `{$ty}` -lint_builtin_unpermitted_type_init_zeroed = the type `{$ty}` does not permit zero-initialization -lint_builtin_unpermitted_type_init_uninit = the type `{$ty}` does not permit being left uninitialized +lint_range_use_inclusive_range = use an inclusive range instead -lint_builtin_unpermitted_type_init_label = this code causes undefined behavior when executed -lint_builtin_unpermitted_type_init_label_suggestion = help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done -lint_builtin_clashing_extern_same_name = `{$this}` redeclared with a different signature - .previous_decl_label = `{$orig}` previously declared here - .mismatch_label = this signature doesn't match the previous declaration -lint_builtin_clashing_extern_diff_name = `{$this}` redeclares `{$orig}` with a different signature - .previous_decl_label = `{$orig}` previously declared here - .mismatch_label = this signature doesn't match the previous declaration +lint_reason_must_be_string_literal = reason must be a string literal -lint_builtin_deref_nullptr = dereferencing a null pointer - .label = this code causes undefined behavior when executed +lint_reason_must_come_last = reason in lint attribute must come last -lint_builtin_asm_labels = avoid using named labels in inline assembly +lint_redundant_semicolons = + unnecessary trailing {$multiple -> + [true] semicolons + *[false] semicolon + } + .suggestion = remove {$multiple -> + [true] these semicolons + *[false] this semicolon + } -lint_builtin_special_module_name_used_lib = found module declaration for lib.rs - .note = lib.rs is the root of this crate's library target - .help = to refer to it from other targets, use the library's name as the path +lint_renamed_or_removed_lint = {$msg} + .suggestion = use the new name -lint_builtin_special_module_name_used_main = found module declaration for main.rs - .note = a binary crate cannot be used as library +lint_requested_level = requested on the command line with `{$level} {$lint_name}` lint_supertrait_as_deref_target = `{$t}` implements `Deref` with supertrait `{$target_principal}` as target .label = target type is set here -lint_overruled_attribute = {$lint_level}({$lint_source}) incompatible with previous forbid - .label = overruled by previous forbid +lint_suspicious_double_ref_clone = + using `.clone()` on a double reference, which returns `{$ty}` instead of cloning the inner type -lint_default_source = `forbid` lint level is the default for {$id} +lint_suspicious_double_ref_deref = + using `.deref()` on a double reference, which returns `{$ty}` instead of dereferencing the inner type -lint_node_source = `forbid` level set here - .note = {$reason} +lint_trivial_untranslatable_diag = diagnostic with static strings only -lint_command_line_source = `forbid` lint level was set on command line +lint_ty_qualified = usage of qualified `ty::{$ty}` + .suggestion = try importing it and using it unqualified -lint_malformed_attribute = malformed lint attribute input +lint_tykind = usage of `ty::TyKind` + .help = try using `Ty` instead -lint_bad_attribute_argument = bad attribute argument +lint_tykind_kind = usage of `ty::TyKind::` + .suggestion = try using `ty::` directly -lint_reason_must_be_string_literal = reason must be a string literal +lint_undropped_manually_drops = calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of the inner value does nothing + .label = argument has type `{$arg_ty}` + .suggestion = use `std::mem::ManuallyDrop::into_inner` to get the inner value -lint_reason_must_come_last = reason in lint attribute must come last +lint_ungated_async_fn_track_caller = `#[track_caller]` on async functions is a no-op + .label = this function will not propagate the caller location + +lint_unknown_gated_lint = + unknown lint: `{$name}` + .note = the `{$name}` lint is unstable + +lint_unknown_lint = + unknown lint: `{$name}` + .suggestion = did you mean lint_unknown_tool_in_scoped_lint = unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}` .help = add `#![register_tool({$tool_name})]` to the crate root lint_unsupported_group = `{$lint_group}` lint group is not supported with ´--force-warn´ -lint_requested_level = requested on the command line with `{$level} {$lint_name}` +lint_untranslatable_diag = diagnostics should be created using translatable messages -lint_check_name_unknown = unknown lint: `{$lint_name}` - .help = did you mean: `{$suggestion}` +lint_unused_allocation = unnecessary allocation, use `&` instead +lint_unused_allocation_mut = unnecessary allocation, use `&mut` instead -lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}` +lint_unused_closure = + unused {$pre}{$count -> + [one] closure + *[other] closures + }{$post} that must be used + .note = closures are lazy and do nothing unless called -lint_check_name_warning = {$msg} +lint_unused_comparisons = comparison is useless due to type limits -lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name} +lint_unused_def = unused {$pre}`{$def}`{$post} that must be used + .suggestion = use `let _ = ...` to ignore the resulting value -lint_opaque_hidden_inferred_bound = opaque type `{$ty}` does not satisfy its associated type bounds - .specifically = this associated type bound is unsatisfied for `{$proj_ty}` +lint_unused_delim = unnecessary {$delim} around {$item} + .suggestion = remove these {$delim} -lint_opaque_hidden_inferred_bound_sugg = add this bound +lint_unused_generator = + unused {$pre}{$count -> + [one] generator + *[other] generator + }{$post} that must be used + .note = generators are lazy and do nothing unless resumed -lint_dropping_references = calls to `std::mem::drop` with a reference instead of an owned value does nothing - .label = argument has type `{$arg_ty}` - .note = use `let _ = ...` to ignore the expression or result +lint_unused_import_braces = braces around {$node} is unnecessary -lint_dropping_copy_types = calls to `std::mem::drop` with a value that implements `Copy` does nothing - .label = argument has type `{$arg_ty}` - .note = use `let _ = ...` to ignore the expression or result +lint_unused_op = unused {$op} that must be used + .label = the {$op} produces a value + .suggestion = use `let _ = ...` to ignore the resulting value -lint_forgetting_references = calls to `std::mem::forget` with a reference instead of an owned value does nothing - .label = argument has type `{$arg_ty}` - .note = use `let _ = ...` to ignore the expression or result +lint_unused_result = unused result of type `{$ty}` -lint_forgetting_copy_types = calls to `std::mem::forget` with a value that implements `Copy` does nothing - .label = argument has type `{$arg_ty}` - .note = use `let _ = ...` to ignore the expression or result +lint_variant_size_differences = + enum variant is more than three times larger ({$largest} bytes) than the next largest diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs index bccb0a94e986d..d0967ba564402 100644 --- a/compiler/rustc_lint/src/array_into_iter.rs +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else { - return + return; }; let types = diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 358d412a4d8e2..4b6917fdfdd09 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -24,24 +24,22 @@ use crate::fluent_generated as fluent; use crate::{ errors::BuiltinEllipsisInclusiveRangePatterns, lints::{ - BuiltinAnonymousParams, BuiltinBoxPointers, BuiltinClashingExtern, - BuiltinClashingExternSub, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink, - BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, BuiltinDerefNullptr, - BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, - BuiltinExplicitOutlivesSuggestion, BuiltinIncompleteFeatures, - BuiltinIncompleteFeaturesHelp, BuiltinIncompleteFeaturesNote, BuiltinKeywordIdents, + BuiltinAnonymousParams, BuiltinBoxPointers, BuiltinConstNoMangle, + BuiltinDeprecatedAttrLink, BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, + BuiltinDerefNullptr, BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, + BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures, + BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds, BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause, BuiltinUnexpectedCliConfigName, BuiltinUnexpectedCliConfigValue, - BuiltinUngatedAsyncFnTrackCaller, BuiltinUnnameableTestItems, BuiltinUnpermittedTypeInit, + BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, BuiltinWhileTrue, SuggestChangingAssocTypes, }, - types::{transparent_newtype_field, CItemKind}, - EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext, + EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext, }; use hir::IsAsync; use rustc_ast::attr; @@ -49,29 +47,29 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust::{self, expr_to_string}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{Applicability, DecorateLint, MultiSpan}; use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID}; +use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; use rustc_hir::intravisit::FnKind as HirFnKind; -use rustc_hir::{Body, FnDecl, ForeignItemKind, GenericParamKind, Node, PatKind, PredicateOrigin}; +use rustc_hir::{Body, FnDecl, GenericParamKind, Node, PatKind, PredicateOrigin}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::layout::{LayoutError, LayoutOf}; +use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::GenericArgKind; +use rustc_middle::ty::ToPredicate; use rustc_middle::ty::TypeVisitableExt; -use rustc_middle::ty::{self, Instance, Ty, TyCtxt, VariantDef}; +use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; use rustc_session::config::ExpectedValues; use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{BytePos, InnerSpan, Span}; -use rustc_target::abi::{Abi, FIRST_VARIANT}; +use rustc_target::abi::Abi; use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy}; use crate::nonstandard_style::{method_context, MethodLateContext}; @@ -181,9 +179,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxPointers { | hir::ItemKind::TyAlias(..) | hir::ItemKind::Enum(..) | hir::ItemKind::Struct(..) - | hir::ItemKind::Union(..) => { - self.check_heap_type(cx, it.span, cx.tcx.type_of(it.owner_id).subst_identity()) - } + | hir::ItemKind::Union(..) => self.check_heap_type( + cx, + it.span, + cx.tcx.type_of(it.owner_id).instantiate_identity(), + ), _ => (), } @@ -194,7 +194,7 @@ impl<'tcx> LateLintPass<'tcx> for BoxPointers { self.check_heap_type( cx, field.span, - cx.tcx.type_of(field.def_id).subst_identity(), + cx.tcx.type_of(field.def_id).instantiate_identity(), ); } } @@ -286,7 +286,9 @@ impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns { } declare_lint! { - /// The `unsafe_code` lint catches usage of `unsafe` code. + /// The `unsafe_code` lint catches usage of `unsafe` code and other + /// potentially unsound constructs like `no_mangle`, `export_name`, + /// and `link_section`. /// /// ### Example /// @@ -297,17 +299,29 @@ declare_lint! { /// /// } /// } + /// + /// #[no_mangle] + /// fn func_0() { } + /// + /// #[export_name = "exported_symbol_name"] + /// pub fn name_in_rust() { } + /// + /// #[no_mangle] + /// #[link_section = ".example_section"] + /// pub static VAR1: u32 = 1; /// ``` /// /// {{produces}} /// /// ### Explanation /// - /// This lint is intended to restrict the usage of `unsafe`, which can be - /// difficult to use correctly. + /// This lint is intended to restrict the usage of `unsafe` blocks and other + /// constructs (including, but not limited to `no_mangle`, `link_section` + /// and `export_name` attributes) wrong usage of which causes undefined + /// behavior. UNSAFE_CODE, Allow, - "usage of `unsafe` code" + "usage of `unsafe` code and other potentially unsound constructs" } declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]); @@ -445,10 +459,7 @@ declare_lint! { report_in_external_macro } -pub struct MissingDoc { - /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes. - doc_hidden_stack: Vec, -} +pub struct MissingDoc; impl_lint_pass!(MissingDoc => [MISSING_DOCS]); @@ -477,14 +488,6 @@ fn has_doc(attr: &ast::Attribute) -> bool { } impl MissingDoc { - pub fn new() -> MissingDoc { - MissingDoc { doc_hidden_stack: vec![false] } - } - - fn doc_hidden(&self) -> bool { - *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") - } - fn check_missing_docs_attrs( &self, cx: &LateContext<'_>, @@ -498,11 +501,6 @@ impl MissingDoc { return; } - // `#[doc(hidden)]` disables missing_docs check. - if self.doc_hidden() { - return; - } - // Only check publicly-visible items, using the result from the privacy pass. // It's an option so the crate root can also use this function (it doesn't // have a `NodeId`). @@ -525,31 +523,18 @@ impl MissingDoc { } impl<'tcx> LateLintPass<'tcx> for MissingDoc { - #[inline] - fn enter_lint_attrs(&mut self, _cx: &LateContext<'_>, attrs: &[ast::Attribute]) { - let doc_hidden = self.doc_hidden() - || attrs.iter().any(|attr| { - attr.has_name(sym::doc) - && match attr.meta_item_list() { - None => false, - Some(l) => attr::list_contains_name(&l, sym::hidden), - } - }); - self.doc_hidden_stack.push(doc_hidden); - } - - fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) { - self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); - } - fn check_crate(&mut self, cx: &LateContext<'_>) { self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate"); } fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { // Previously the Impl and Use types have been excluded from missing docs, - // so we will continue to exclude them for compatibility - if let hir::ItemKind::Impl(..) | hir::ItemKind::Use(..) = it.kind { + // so we will continue to exclude them for compatibility. + // + // The documentation on `ExternCrate` is not used at the moment so no need to warn for it. + if let hir::ItemKind::Impl(..) | hir::ItemKind::Use(..) | hir::ItemKind::ExternCrate(_) = + it.kind + { return; } @@ -573,7 +558,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { // If the method is an impl for an item with docs_hidden, don't doc. MethodLateContext::PlainImpl => { let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()); - let impl_ty = cx.tcx.type_of(parent).subst_identity(); + let impl_ty = cx.tcx.type_of(parent).instantiate_identity(); let outerdef = match impl_ty.kind() { ty::Adt(def, _) => Some(def.did()), ty::Foreign(def_id) => Some(*def_id), @@ -647,9 +632,7 @@ declare_lint_pass!(MissingCopyImplementations => [MISSING_COPY_IMPLEMENTATIONS]) impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { - if !(cx.effective_visibilities.is_reachable(item.owner_id.def_id) - && cx.tcx.local_visibility(item.owner_id.def_id).is_public()) - { + if !cx.effective_visibilities.is_reachable(item.owner_id.def_id) { return; } let (def, ty) = match item.kind { @@ -658,21 +641,21 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { return; } let def = cx.tcx.adt_def(item.owner_id); - (def, cx.tcx.mk_adt(def, ty::List::empty())) + (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) } hir::ItemKind::Union(_, ref ast_generics) => { if !ast_generics.params.is_empty() { return; } let def = cx.tcx.adt_def(item.owner_id); - (def, cx.tcx.mk_adt(def, ty::List::empty())) + (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) } hir::ItemKind::Enum(_, ref ast_generics) => { if !ast_generics.params.is_empty() { return; } let def = cx.tcx.adt_def(item.owner_id); - (def, cx.tcx.mk_adt(def, ty::List::empty())) + (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) } _ => return, }; @@ -684,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { // and recommending Copy might be a bad idea. for field in def.all_fields() { let did = field.did; - if cx.tcx.type_of(did).subst_identity().is_unsafe_ptr() { + if cx.tcx.type_of(did).instantiate_identity().is_unsafe_ptr() { return; } } @@ -692,6 +675,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { if ty.is_copy_modulo_regions(cx.tcx, param_env) { return; } + if type_implements_negative_copy_modulo_regions(cx.tcx, ty, param_env) { + return; + } // We shouldn't recommend implementing `Copy` on stateful things, // such as iterators. @@ -727,6 +713,24 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { } } +/// Check whether a `ty` has a negative `Copy` implementation, ignoring outlives constraints. +fn type_implements_negative_copy_modulo_regions<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> bool { + let trait_ref = ty::TraitRef::new(tcx, tcx.require_lang_item(hir::LangItem::Copy, None), [ty]); + let pred = ty::TraitPredicate { trait_ref, polarity: ty::ImplPolarity::Negative }; + let obligation = traits::Obligation { + cause: traits::ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: ty::Binder::dummy(pred).to_predicate(tcx), + }; + + tcx.infer_ctxt().build().predicate_must_hold_modulo_regions(&obligation) +} + declare_lint! { /// The `missing_debug_implementations` lint detects missing /// implementations of [`fmt::Debug`] for public types. @@ -760,17 +764,13 @@ declare_lint! { } #[derive(Default)] -pub struct MissingDebugImplementations { - impling_types: Option, -} +pub(crate) struct MissingDebugImplementations; impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]); impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { - if !(cx.effective_visibilities.is_reachable(item.owner_id.def_id) - && cx.tcx.local_visibility(item.owner_id.def_id).is_public()) - { + if !cx.effective_visibilities.is_reachable(item.owner_id.def_id) { return; } @@ -779,25 +779,20 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { _ => return, } - let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { - return - }; - - if self.impling_types.is_none() { - let mut impls = LocalDefIdSet::default(); - cx.tcx.for_each_impl(debug, |d| { - if let Some(ty_def) = cx.tcx.type_of(d).subst_identity().ty_adt_def() { - if let Some(def_id) = ty_def.did().as_local() { - impls.insert(def_id); - } - } - }); - - self.impling_types = Some(impls); - debug!("{:?}", self.impling_types); + // Avoid listing trait impls if the trait is allowed. + let (level, _) = cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()); + if level == Level::Allow { + return; } - if !self.impling_types.as_ref().unwrap().contains(&item.owner_id.def_id) { + let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { return }; + + let has_impl = cx + .tcx + .non_blanket_impls_for_ty(debug, cx.tcx.type_of(item.owner_id).instantiate_identity()) + .next() + .is_some(); + if !has_impl { cx.emit_spanned_lint( MISSING_DEBUG_IMPLEMENTATIONS, item.span, @@ -1245,8 +1240,8 @@ impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { declare_lint! { /// The `ungated_async_fn_track_caller` lint warns when the - /// `#[track_caller]` attribute is used on an async function, method, or - /// closure, without enabling the corresponding unstable feature flag. + /// `#[track_caller]` attribute is used on an async function + /// without enabling the corresponding unstable feature flag. /// /// ### Example /// @@ -1260,13 +1255,13 @@ declare_lint! { /// ### Explanation /// /// The attribute must be used in conjunction with the - /// [`closure_track_caller` feature flag]. Otherwise, the `#[track_caller]` + /// [`async_fn_track_caller` feature flag]. Otherwise, the `#[track_caller]` /// annotation will function as a no-op. /// - /// [`closure_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/closure-track-caller.html + /// [`async_fn_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/async-fn-track-caller.html UNGATED_ASYNC_FN_TRACK_CALLER, Warn, - "enabling track_caller on an async fn is a no-op unless the closure_track_caller feature is enabled" + "enabling track_caller on an async fn is a no-op unless the async_fn_track_caller feature is enabled" } declare_lint_pass!( @@ -1286,7 +1281,7 @@ impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller { def_id: LocalDefId, ) { if fn_kind.asyncness() == IsAsync::Async - && !cx.tcx.features().closure_track_caller + && !cx.tcx.features().async_fn_track_caller // Now, check if the function has the `#[track_caller]` attribute && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller) { @@ -1317,10 +1312,14 @@ declare_lint! { /// /// ### Explanation /// - /// A bare `pub` visibility may be misleading if the item is not actually - /// publicly exported from the crate. The `pub(crate)` visibility is - /// recommended to be used instead, which more clearly expresses the intent - /// that the item is only visible within its own crate. + /// The `pub` keyword both expresses an intent for an item to be publicly available, and also + /// signals to the compiler to make the item publicly accessible. The intent can only be + /// satisfied, however, if all items which contain this item are *also* publicly accessible. + /// Thus, this lint serves to identify situations where the intent does not match the reality. + /// + /// If you wish the item to be accessible elsewhere within the crate, but not outside it, the + /// `pub(crate)` visibility is recommended to be used instead. This more clearly expresses the + /// intent that the item is only visible within its own crate. /// /// This lint is "allow" by default because it will trigger for a large /// amount existing Rust code, and has some false-positives. Eventually it @@ -1440,17 +1439,20 @@ impl TypeAliasBounds { impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { - let hir::ItemKind::TyAlias(ty, type_alias_generics) = &item.kind else { - return - }; - if let hir::TyKind::OpaqueDef(..) = ty.kind { - // Bounds are respected for `type X = impl Trait` + let hir::ItemKind::TyAlias(hir_ty, type_alias_generics) = &item.kind else { return }; + + if cx.tcx.features().lazy_type_alias { + // Bounds of lazy type aliases are respected. return; } - if cx.tcx.type_of(item.owner_id).skip_binder().has_inherent_projections() { - // Bounds are respected for `type X = … Type::Inherent …` + + let ty = cx.tcx.type_of(item.owner_id).skip_binder(); + if ty.has_opaque_types() || ty.has_inherent_projections() { + // Bounds of type aliases that contain opaque types or inherent projections are respected. + // E.g: `type X = impl Trait;`, `type X = (impl Trait, Y);`, `type X = Type::Inherent;`. return; } + // There must not be a where clause if type_alias_generics.predicates.is_empty() { return; @@ -1475,7 +1477,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { if !where_spans.is_empty() { let sub = (!suggested_changing_assoc_types).then(|| { suggested_changing_assoc_types = true; - SuggestChangingAssocTypes { ty } + SuggestChangingAssocTypes { ty: hir_ty } }); cx.emit_spanned_lint( TYPE_ALIAS_BOUNDS, @@ -1491,7 +1493,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { let suggestion = BuiltinTypeAliasGenericBoundsSuggestion { suggestions: inline_sugg }; let sub = (!suggested_changing_assoc_types).then(|| { suggested_changing_assoc_types = true; - SuggestChangingAssocTypes { ty } + SuggestChangingAssocTypes { ty: hir_ty } }); cx.emit_spanned_lint( TYPE_ALIAS_BOUNDS, @@ -1513,9 +1515,10 @@ declare_lint_pass!( impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { match it.kind { - hir::ItemKind::Const(_, body_id) => { + hir::ItemKind::Const(_, _, body_id) => { let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); // trigger the query once for all constants since that will already report the errors + // FIXME(generic_const_items): Does this work properly with generic const items? cx.tcx.ensure().const_eval_poly(def_id); } hir::ItemKind::Static(_, _, body_id) => { @@ -1570,34 +1573,25 @@ declare_lint_pass!( impl<'tcx> LateLintPass<'tcx> for TrivialConstraints { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { - use rustc_middle::ty::Clause; - use rustc_middle::ty::PredicateKind::*; + use rustc_middle::ty::ClauseKind; if cx.tcx.features().trivial_bounds { let predicates = cx.tcx.predicates_of(item.owner_id); for &(predicate, span) in predicates.predicates { let predicate_kind_name = match predicate.kind().skip_binder() { - Clause(Clause::Trait(..)) => "trait", - Clause(Clause::TypeOutlives(..)) | - Clause(Clause::RegionOutlives(..)) => "lifetime", + ClauseKind::Trait(..) => "trait", + ClauseKind::TypeOutlives(..) | + ClauseKind::RegionOutlives(..) => "lifetime", // `ConstArgHasType` is never global as `ct` is always a param - Clause(Clause::ConstArgHasType(..)) | + ClauseKind::ConstArgHasType(..) // Ignore projections, as they can only be global // if the trait bound is global - Clause(Clause::Projection(..)) | - AliasRelate(..) | + | ClauseKind::Projection(..) // Ignore bounds that a user can't type - WellFormed(..) | - ObjectSafe(..) | - ClosureKind(..) | - Subtype(..) | - Coerce(..) | + | ClauseKind::WellFormed(..) // FIXME(generic_const_exprs): `ConstEvaluatable` can be written - ConstEvaluatable(..) | - ConstEquate(..) | - Ambiguous | - TypeWellFormedFromEnv(..) => continue, + | ClauseKind::ConstEvaluatable(..) => continue, }; if predicate.is_global() { cx.emit_spanned_lint( @@ -1709,7 +1703,7 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { let end = expr_to_string(&end); let replace = match start { Some(start) => format!("&({}..={})", expr_to_string(&start), end), - None => format!("&(..={})", end), + None => format!("&(..={end})"), }; if join.edition() >= Edition::Edition2021 { cx.sess().emit_err(BuiltinEllipsisInclusiveRangePatterns { @@ -1757,82 +1751,6 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { } } -declare_lint! { - /// The `unnameable_test_items` lint detects [`#[test]`][test] functions - /// that are not able to be run by the test harness because they are in a - /// position where they are not nameable. - /// - /// [test]: https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute - /// - /// ### Example - /// - /// ```rust,test - /// fn main() { - /// #[test] - /// fn foo() { - /// // This test will not fail because it does not run. - /// assert_eq!(1, 2); - /// } - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// In order for the test harness to run a test, the test function must be - /// located in a position where it can be accessed from the crate root. - /// This generally means it must be defined in a module, and not anywhere - /// else such as inside another function. The compiler previously allowed - /// this without an error, so a lint was added as an alert that a test is - /// not being used. Whether or not this should be allowed has not yet been - /// decided, see [RFC 2471] and [issue #36629]. - /// - /// [RFC 2471]: https://github.com/rust-lang/rfcs/pull/2471#issuecomment-397414443 - /// [issue #36629]: https://github.com/rust-lang/rust/issues/36629 - UNNAMEABLE_TEST_ITEMS, - Warn, - "detects an item that cannot be named being marked as `#[test_case]`", - report_in_external_macro -} - -pub struct UnnameableTestItems { - boundary: Option, // Id of the item under which things are not nameable - items_nameable: bool, -} - -impl_lint_pass!(UnnameableTestItems => [UNNAMEABLE_TEST_ITEMS]); - -impl UnnameableTestItems { - pub fn new() -> Self { - Self { boundary: None, items_nameable: true } - } -} - -impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems { - fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - if self.items_nameable { - if let hir::ItemKind::Mod(..) = it.kind { - } else { - self.items_nameable = false; - self.boundary = Some(it.owner_id); - } - return; - } - - let attrs = cx.tcx.hir().attrs(it.hir_id()); - if let Some(attr) = attr::find_by_name(attrs, sym::rustc_test_marker) { - cx.emit_spanned_lint(UNNAMEABLE_TEST_ITEMS, attr.span, BuiltinUnnameableTestItems); - } - } - - fn check_item_post(&mut self, _cx: &LateContext<'_>, it: &hir::Item<'_>) { - if !self.items_nameable && self.boundary == Some(it.owner_id) { - self.items_nameable = true; - } - } -} - declare_lint! { /// The `keyword_idents` lint detects edition keywords being used as an /// identifier. @@ -1967,8 +1885,8 @@ impl ExplicitOutlivesRequirements { ) -> Vec> { inferred_outlives .iter() - .filter_map(|(clause, _)| match *clause { - ty::Clause::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a { + .filter_map(|(clause, _)| match clause.kind().skip_binder() { + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a { ty::ReEarlyBound(ebr) if ebr.def_id == def_id => Some(b), _ => None, }, @@ -1983,8 +1901,8 @@ impl ExplicitOutlivesRequirements { ) -> Vec> { inferred_outlives .iter() - .filter_map(|(clause, _)| match *clause { - ty::Clause::TypeOutlives(ty::OutlivesPredicate(a, b)) => { + .filter_map(|(clause, _)| match clause.kind().skip_binder() { + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(a, b)) => { a.is_param(index).then_some(b) } _ => None, @@ -2102,12 +2020,16 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } let ty_generics = cx.tcx.generics_of(def_id); + let num_where_predicates = hir_generics + .predicates + .iter() + .filter(|predicate| predicate.in_where_clause()) + .count(); let mut bound_count = 0; let mut lint_spans = Vec::new(); let mut where_lint_spans = Vec::new(); - let mut dropped_predicate_count = 0; - let num_predicates = hir_generics.predicates.len(); + let mut dropped_where_predicate_count = 0; for (i, where_predicate) in hir_generics.predicates.iter().enumerate() { let (relevant_lifetimes, bounds, predicate_span, in_where_clause) = match where_predicate { @@ -2134,8 +2056,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { match predicate.bounded_ty.kind { hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { let Res::Def(DefKind::TyParam, def_id) = path.res else { - continue; - }; + continue; + }; let index = ty_generics.param_def_id_to_index[&def_id]; ( Self::lifetimes_outliving_type(inferred_outlives, index), @@ -2164,8 +2086,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { bound_count += bound_spans.len(); let drop_predicate = bound_spans.len() == bounds.len(); - if drop_predicate { - dropped_predicate_count += 1; + if drop_predicate && in_where_clause { + dropped_where_predicate_count += 1; } if drop_predicate { @@ -2174,7 +2096,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } else if predicate_span.from_expansion() { // Don't try to extend the span if it comes from a macro expansion. where_lint_spans.push(predicate_span); - } else if i + 1 < num_predicates { + } else if i + 1 < num_where_predicates { // If all the bounds on a predicate were inferable and there are // further predicates, we want to eat the trailing comma. let next_predicate_span = hir_generics.predicates[i + 1].span(); @@ -2202,9 +2124,10 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } } - // If all predicates are inferable, drop the entire clause + // If all predicates in where clause are inferable, drop the entire clause // (including the `where`) - if hir_generics.has_where_clause_predicates && dropped_predicate_count == num_predicates + if hir_generics.has_where_clause_predicates + && dropped_where_predicate_count == num_where_predicates { let where_span = hir_generics.where_clause_span; // Extend the where clause back to the closing `>` of the @@ -2283,30 +2206,63 @@ declare_lint! { "incomplete features that may function improperly in some or all cases" } +declare_lint! { + /// The `internal_features` lint detects unstable features enabled with + /// the [`feature` attribute] that are internal to the compiler or standard + /// library. + /// + /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/ + /// + /// ### Example + /// + /// ```rust + /// #![feature(rustc_attrs)] + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// These features are an implementation detail of the compiler and standard + /// library and are not supposed to be used in user code. + pub INTERNAL_FEATURES, + Warn, + "internal features are not supposed to be used" +} + declare_lint_pass!( /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/active.rs`. - IncompleteFeatures => [INCOMPLETE_FEATURES] + IncompleteInternalFeatures => [INCOMPLETE_FEATURES, INTERNAL_FEATURES] ); -impl EarlyLintPass for IncompleteFeatures { +impl EarlyLintPass for IncompleteInternalFeatures { fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { - let features = cx.sess().features_untracked(); + let features = cx.builder.features(); features .declared_lang_features .iter() .map(|(name, span, _)| (name, span)) .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span))) - .filter(|(&name, _)| features.incomplete(name)) + .filter(|(&name, _)| features.incomplete(name) || features.internal(name)) .for_each(|(&name, &span)| { let note = rustc_feature::find_feature_issue(name, GateIssue::Language) - .map(|n| BuiltinIncompleteFeaturesNote { n }); - let help = - HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); - cx.emit_spanned_lint( - INCOMPLETE_FEATURES, - span, - BuiltinIncompleteFeatures { name, note, help }, - ); + .map(|n| BuiltinFeatureIssueNote { n }); + + if features.incomplete(name) { + let help = + HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); + cx.emit_spanned_lint( + INCOMPLETE_FEATURES, + span, + BuiltinIncompleteFeatures { name, note, help }, + ); + } else { + cx.emit_spanned_lint( + INTERNAL_FEATURES, + span, + BuiltinInternalFeatures { name, note }, + ); + } }); } } @@ -2445,12 +2401,12 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { cx: &LateContext<'tcx>, ty: Ty<'tcx>, variant: &VariantDef, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, descr: &str, init: InitKind, ) -> Option { let mut field_err = variant.fields.iter().find_map(|field| { - ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|mut err| { + ty_find_init_error(cx, field.ty(cx.tcx, args), init).map(|mut err| { if !field.did.is_local() { err } else if err.span.is_none() { @@ -2527,14 +2483,14 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { Some("raw pointers must be initialized".into()) } // Recurse and checks for some compound types. (but not unions) - Adt(adt_def, substs) if !adt_def.is_union() => { + Adt(adt_def, args) if !adt_def.is_union() => { // Handle structs. if adt_def.is_struct() { return variant_find_init_error( cx, ty, adt_def.non_enum_variant(), - substs, + args, "struct field", init, ); @@ -2544,7 +2500,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { let mut potential_variants = adt_def.variants().iter().filter_map(|variant| { let definitely_inhabited = match variant .inhabited_predicate(cx.tcx, *adt_def) - .subst(cx.tcx, substs) + .instantiate(cx.tcx, args) .apply_any_module(cx.tcx, cx.param_env) { // Entirely skip uninhabited variants. @@ -2556,7 +2512,10 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { Some((variant, definitely_inhabited)) }); let Some(first_variant) = potential_variants.next() else { - return Some(InitError::from("enums with no inhabited variants have no valid value").spanned(span)); + return Some( + InitError::from("enums with no inhabited variants have no valid value") + .spanned(span), + ); }; // So we have at least one potentially inhabited variant. Might we have two? let Some(second_variant) = potential_variants.next() else { @@ -2565,7 +2524,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { cx, ty, &first_variant.0, - substs, + args, "field of the only potentially inhabited enum variant", init, ); @@ -2634,381 +2593,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { } } -declare_lint! { - /// The `clashing_extern_declarations` lint detects when an `extern fn` - /// has been declared with the same name but different types. - /// - /// ### Example - /// - /// ```rust - /// mod m { - /// extern "C" { - /// fn foo(); - /// } - /// } - /// - /// extern "C" { - /// fn foo(_: u32); - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// Because two symbols of the same name cannot be resolved to two - /// different functions at link time, and one function cannot possibly - /// have two types, a clashing extern declaration is almost certainly a - /// mistake. Check to make sure that the `extern` definitions are correct - /// and equivalent, and possibly consider unifying them in one location. - /// - /// This lint does not run between crates because a project may have - /// dependencies which both rely on the same extern function, but declare - /// it in a different (but valid) way. For example, they may both declare - /// an opaque type for one or more of the arguments (which would end up - /// distinct types), or use types that are valid conversions in the - /// language the `extern fn` is defined in. In these cases, the compiler - /// can't say that the clashing declaration is incorrect. - pub CLASHING_EXTERN_DECLARATIONS, - Warn, - "detects when an extern fn has been declared with the same name but different types" -} - -pub struct ClashingExternDeclarations { - /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls - /// contains an entry for key K, it means a symbol with name K has been seen by this lint and - /// the symbol should be reported as a clashing declaration. - // FIXME: Technically, we could just store a &'tcx str here without issue; however, the - // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime. - seen_decls: FxHashMap, -} - -/// Differentiate between whether the name for an extern decl came from the link_name attribute or -/// just from declaration itself. This is important because we don't want to report clashes on -/// symbol name if they don't actually clash because one or the other links against a symbol with a -/// different name. -enum SymbolName { - /// The name of the symbol + the span of the annotation which introduced the link name. - Link(Symbol, Span), - /// No link name, so just the name of the symbol. - Normal(Symbol), -} - -impl SymbolName { - fn get_name(&self) -> Symbol { - match self { - SymbolName::Link(s, _) | SymbolName::Normal(s) => *s, - } - } -} - -impl ClashingExternDeclarations { - pub(crate) fn new() -> Self { - ClashingExternDeclarations { seen_decls: FxHashMap::default() } - } - - /// Insert a new foreign item into the seen set. If a symbol with the same name already exists - /// for the item, return its HirId without updating the set. - fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option { - let did = fi.owner_id.to_def_id(); - let instance = Instance::new(did, ty::List::identity_for_item(tcx, did)); - let name = Symbol::intern(tcx.symbol_name(instance).name); - if let Some(&existing_id) = self.seen_decls.get(&name) { - // Avoid updating the map with the new entry when we do find a collision. We want to - // make sure we're always pointing to the first definition as the previous declaration. - // This lets us avoid emitting "knock-on" diagnostics. - Some(existing_id) - } else { - self.seen_decls.insert(name, fi.owner_id) - } - } - - /// Get the name of the symbol that's linked against for a given extern declaration. That is, - /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the - /// symbol's name. - fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName { - if let Some((overridden_link_name, overridden_link_name_span)) = - tcx.codegen_fn_attrs(fi.owner_id).link_name.map(|overridden_link_name| { - // FIXME: Instead of searching through the attributes again to get span - // information, we could have codegen_fn_attrs also give span information back for - // where the attribute was defined. However, until this is found to be a - // bottleneck, this does just fine. - (overridden_link_name, tcx.get_attr(fi.owner_id, sym::link_name).unwrap().span) - }) - { - SymbolName::Link(overridden_link_name, overridden_link_name_span) - } else { - SymbolName::Normal(fi.ident.name) - } - } - - /// Checks whether two types are structurally the same enough that the declarations shouldn't - /// clash. We need this so we don't emit a lint when two modules both declare an extern struct, - /// with the same members (as the declarations shouldn't clash). - fn structurally_same_type<'tcx>( - cx: &LateContext<'tcx>, - a: Ty<'tcx>, - b: Ty<'tcx>, - ckind: CItemKind, - ) -> bool { - fn structurally_same_type_impl<'tcx>( - seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, - cx: &LateContext<'tcx>, - a: Ty<'tcx>, - b: Ty<'tcx>, - ckind: CItemKind, - ) -> bool { - debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b); - let tcx = cx.tcx; - - // Given a transparent newtype, reach through and grab the inner - // type unless the newtype makes the type non-null. - let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> { - loop { - if let ty::Adt(def, substs) = *ty.kind() { - let is_transparent = def.repr().transparent(); - let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, def); - debug!( - "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}", - ty, is_transparent, is_non_null - ); - if is_transparent && !is_non_null { - debug_assert_eq!(def.variants().len(), 1); - let v = &def.variant(FIRST_VARIANT); - // continue with `ty`'s non-ZST field, - // otherwise `ty` is a ZST and we can return - if let Some(field) = transparent_newtype_field(tcx, v) { - ty = field.ty(tcx, substs); - continue; - } - } - } - debug!("non_transparent_ty -> {:?}", ty); - return ty; - } - }; - - let a = non_transparent_ty(a); - let b = non_transparent_ty(b); - - if !seen_types.insert((a, b)) { - // We've encountered a cycle. There's no point going any further -- the types are - // structurally the same. - true - } else if a == b { - // All nominally-same types are structurally same, too. - true - } else { - // Do a full, depth-first comparison between the two. - use rustc_type_ir::sty::TyKind::*; - let a_kind = a.kind(); - let b_kind = b.kind(); - - let compare_layouts = |a, b| -> Result> { - debug!("compare_layouts({:?}, {:?})", a, b); - let a_layout = &cx.layout_of(a)?.layout.abi(); - let b_layout = &cx.layout_of(b)?.layout.abi(); - debug!( - "comparing layouts: {:?} == {:?} = {}", - a_layout, - b_layout, - a_layout == b_layout - ); - Ok(a_layout == b_layout) - }; - - #[allow(rustc::usage_of_ty_tykind)] - let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| { - kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..)) - }; - - ensure_sufficient_stack(|| { - match (a_kind, b_kind) { - (Adt(a_def, _), Adt(b_def, _)) => { - // We can immediately rule out these types as structurally same if - // their layouts differ. - match compare_layouts(a, b) { - Ok(false) => return false, - _ => (), // otherwise, continue onto the full, fields comparison - } - - // Grab a flattened representation of all fields. - let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter()); - let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter()); - - // Perform a structural comparison for each field. - a_fields.eq_by( - b_fields, - |&ty::FieldDef { did: a_did, .. }, - &ty::FieldDef { did: b_did, .. }| { - structurally_same_type_impl( - seen_types, - cx, - tcx.type_of(a_did).subst_identity(), - tcx.type_of(b_did).subst_identity(), - ckind, - ) - }, - ) - } - (Array(a_ty, a_const), Array(b_ty, b_const)) => { - // For arrays, we also check the constness of the type. - a_const.kind() == b_const.kind() - && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) - } - (Slice(a_ty), Slice(b_ty)) => { - structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) - } - (RawPtr(a_tymut), RawPtr(b_tymut)) => { - a_tymut.mutbl == b_tymut.mutbl - && structurally_same_type_impl( - seen_types, cx, a_tymut.ty, b_tymut.ty, ckind, - ) - } - (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { - // For structural sameness, we don't need the region to be same. - a_mut == b_mut - && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind) - } - (FnDef(..), FnDef(..)) => { - let a_poly_sig = a.fn_sig(tcx); - let b_poly_sig = b.fn_sig(tcx); - - // We don't compare regions, but leaving bound regions around ICEs, so - // we erase them. - let a_sig = tcx.erase_late_bound_regions(a_poly_sig); - let b_sig = tcx.erase_late_bound_regions(b_poly_sig); - - (a_sig.abi, a_sig.unsafety, a_sig.c_variadic) - == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic) - && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { - structurally_same_type_impl(seen_types, cx, *a, *b, ckind) - }) - && structurally_same_type_impl( - seen_types, - cx, - a_sig.output(), - b_sig.output(), - ckind, - ) - } - (Tuple(a_substs), Tuple(b_substs)) => { - a_substs.iter().eq_by(b_substs.iter(), |a_ty, b_ty| { - structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind) - }) - } - // For these, it's not quite as easy to define structural-sameness quite so easily. - // For the purposes of this lint, take the conservative approach and mark them as - // not structurally same. - (Dynamic(..), Dynamic(..)) - | (Error(..), Error(..)) - | (Closure(..), Closure(..)) - | (Generator(..), Generator(..)) - | (GeneratorWitness(..), GeneratorWitness(..)) - | (Alias(ty::Projection, ..), Alias(ty::Projection, ..)) - | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..)) - | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false, - - // These definitely should have been caught above. - (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), - - // An Adt and a primitive or pointer type. This can be FFI-safe if non-null - // enum layout optimisation is being applied. - (Adt(..), other_kind) | (other_kind, Adt(..)) - if is_primitive_or_pointer(other_kind) => - { - let (primitive, adt) = - if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) }; - if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) { - ty == primitive - } else { - compare_layouts(a, b).unwrap_or(false) - } - } - // Otherwise, just compare the layouts. This may fail to lint for some - // incompatible types, but at the very least, will stop reads into - // uninitialised memory. - _ => compare_layouts(a, b).unwrap_or(false), - } - }) - } - } - let mut seen_types = FxHashSet::default(); - structurally_same_type_impl(&mut seen_types, cx, a, b, ckind) - } -} - -impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]); - -impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations { - #[instrument(level = "trace", skip(self, cx))] - fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) { - if let ForeignItemKind::Fn(..) = this_fi.kind { - let tcx = cx.tcx; - if let Some(existing_did) = self.insert(tcx, this_fi) { - let existing_decl_ty = tcx.type_of(existing_did).skip_binder(); - let this_decl_ty = tcx.type_of(this_fi.owner_id).subst_identity(); - debug!( - "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", - existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty - ); - // Check that the declarations match. - if !Self::structurally_same_type( - cx, - existing_decl_ty, - this_decl_ty, - CItemKind::Declaration, - ) { - let orig_fi = tcx.hir().expect_foreign_item(existing_did); - let orig = Self::name_of_extern_decl(tcx, orig_fi); - - // We want to ensure that we use spans for both decls that include where the - // name was defined, whether that was from the link_name attribute or not. - let get_relevant_span = - |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) { - SymbolName::Normal(_) => fi.span, - SymbolName::Link(_, annot_span) => fi.span.to(annot_span), - }; - - // Finally, emit the diagnostic. - let this = this_fi.ident.name; - let orig = orig.get_name(); - let previous_decl_label = get_relevant_span(orig_fi); - let mismatch_label = get_relevant_span(this_fi); - let sub = BuiltinClashingExternSub { - tcx, - expected: existing_decl_ty, - found: this_decl_ty, - }; - let decorator = if orig == this { - BuiltinClashingExtern::SameName { - this, - orig, - previous_decl_label, - mismatch_label, - sub, - } - } else { - BuiltinClashingExtern::DiffName { - this, - orig, - previous_decl_label, - mismatch_label, - sub, - } - }; - tcx.emit_spanned_lint( - CLASHING_EXTERN_DECLARATIONS, - this_fi.hir_id(), - get_relevant_span(this_fi), - decorator, - ); - } - } - } - } -} - declare_lint! { /// The `deref_nullptr` lint detects when an null pointer is dereferenced, /// which causes [undefined behavior]. @@ -3167,7 +2751,7 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels { let mut chars = possible_label.chars(); let Some(c) = chars.next() else { // Empty string means a leading ':' in this section, which is not a label - break + break; }; // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $ if (c.is_alphabetic() || matches!(c, '.' | '_')) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 1d0c43e95e085..f73797415bc7a 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -27,6 +27,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync; use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage}; use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle}; +use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::def_id::{CrateNum, DefId}; @@ -35,7 +36,7 @@ use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::middle::stability; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt}; +use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt}; use rustc_session::config::ExpectedValues; use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; @@ -411,7 +412,7 @@ impl LintStore { } let complete_name = if let Some(tool_name) = tool_name { - format!("{}::{}", tool_name, lint_name) + format!("{tool_name}::{lint_name}") } else { lint_name.to_string() }; @@ -424,7 +425,7 @@ impl LintStore { // 1. The tool is currently running, so this lint really doesn't exist. // FIXME: should this handle tools that never register a lint, like rustfmt? debug!("lints={:?}", self.by_name.keys().collect::>()); - let tool_prefix = format!("{}::", tool_name); + let tool_prefix = format!("{tool_name}::"); return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) { self.no_lint_suggestion(&complete_name) } else { @@ -445,11 +446,11 @@ impl LintStore { } match self.by_name.get(&complete_name) { Some(Renamed(new_name, _)) => CheckLintNameResult::Warning( - format!("lint `{}` has been renamed to `{}`", complete_name, new_name), + format!("lint `{complete_name}` has been renamed to `{new_name}`"), Some(new_name.to_owned()), ), Some(Removed(reason)) => CheckLintNameResult::Warning( - format!("lint `{}` has been removed: {}", complete_name, reason), + format!("lint `{complete_name}` has been removed: {reason}"), None, ), None => match self.lint_groups.get(&*complete_name) { @@ -503,7 +504,7 @@ impl LintStore { lint_name: &str, tool_name: &str, ) -> CheckLintNameResult<'_> { - let complete_name = format!("{}::{}", tool_name, lint_name); + let complete_name = format!("{tool_name}::{lint_name}"); match self.by_name.get(&complete_name) { None => match self.lint_groups.get(&*complete_name) { // Now we are sure, that this lint exists nowhere @@ -618,12 +619,10 @@ pub trait LintContext: Sized { _ => ("", "s"), }; db.span_label(span, format!( - "this comment contains {}invisible unicode text flow control codepoint{}", - an, - s, + "this comment contains {an}invisible unicode text flow control codepoint{s}", )); for (c, span) in &spans { - db.span_label(*span, format!("{:?}", c)); + db.span_label(*span, format!("{c:?}")); } db.note( "these kind of unicode codepoints change the way text flows on \ @@ -648,7 +647,7 @@ pub trait LintContext: Sized { let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; - (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable) + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) } Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), }; @@ -704,7 +703,7 @@ pub trait LintContext: Sized { let introduced = if is_imported { "imported" } else { "defined" }; db.span_label( span, - format!("the item `{}` is already {} here", ident, introduced), + format!("the item `{ident}` is already {introduced} here"), ); } } @@ -908,7 +907,7 @@ pub trait LintContext: Sized { BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => { db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string"); if let Some(positional_arg_for_msg) = position_sp_for_msg { - let msg = format!("this formatting argument uses named argument `{}` by position", named_arg_name); + let msg = format!("this formatting argument uses named argument `{named_arg_name}` by position"); db.span_label(positional_arg_for_msg, msg); } @@ -948,9 +947,24 @@ pub trait LintContext: Sized { Applicability::MachineApplicable, ); } + BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => { + rustc_errors::report_ambiguity_error(db, diag); + } BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => { - db.span_label(first_reexport_span, format!("the name `{}` in the {} namespace is first re-exported here", name, namespace)); - db.span_label(duplicate_reexport_span, format!("but the name `{}` in the {} namespace is also re-exported here", name, namespace)); + db.span_label(first_reexport_span, format!("the name `{name}` in the {namespace} namespace is first re-exported here")); + db.span_label(duplicate_reexport_span, format!("but the name `{name}` in the {namespace} namespace is also re-exported here")); + } + BuiltinLintDiagnostics::HiddenGlobReexports { name, namespace, glob_reexport_span, private_item_span } => { + db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here")); + db.span_note(private_item_span, "but the private item here shadows it".to_owned()); + } + BuiltinLintDiagnostics::UnusedQualifications { removal_span } => { + db.span_suggestion_verbose( + removal_span, + "remove the unnecessary path segments", + "", + Applicability::MachineApplicable + ); } } // Rewrap `db`, and pass control to the user. @@ -1058,6 +1072,7 @@ pub trait LintContext: Sized { impl<'a> EarlyContext<'a> { pub(crate) fn new( sess: &'a Session, + features: &'a Features, warn_about_weird_lints: bool, lint_store: &'a LintStore, registered_tools: &'a RegisteredTools, @@ -1066,6 +1081,7 @@ impl<'a> EarlyContext<'a> { EarlyContext { builder: LintLevelsBuilder::new( sess, + features, warn_about_weird_lints, lint_store, registered_tools, @@ -1261,16 +1277,16 @@ impl<'tcx> LateContext<'tcx> { trait_ref: Option>, ) -> Result { if trait_ref.is_none() { - if let ty::Adt(def, substs) = self_ty.kind() { - return self.print_def_path(def.did(), substs); + if let ty::Adt(def, args) = self_ty.kind() { + return self.print_def_path(def.did(), args); } } // This shouldn't ever be needed, but just in case: with_no_trimmed_paths!({ Ok(vec![match trait_ref { - Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)), - None => Symbol::intern(&format!("<{}>", self_ty)), + Some(trait_ref) => Symbol::intern(&format!("{trait_ref:?}")), + None => Symbol::intern(&format!("<{self_ty}>")), }]) }) } @@ -1294,7 +1310,7 @@ impl<'tcx> LateContext<'tcx> { ))) } None => { - with_no_trimmed_paths!(Symbol::intern(&format!("", self_ty))) + with_no_trimmed_paths!(Symbol::intern(&format!(""))) } }); @@ -1341,7 +1357,7 @@ impl<'tcx> LateContext<'tcx> { tcx.associated_items(trait_id) .find_by_name_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) .and_then(|assoc| { - let proj = tcx.mk_projection(assoc.def_id, [self_ty]); + let proj = Ty::new_projection(tcx, assoc.def_id, [self_ty]); tcx.try_normalize_erasing_regions(self.param_env, proj).ok() }) } diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs index ccf95992a6e6e..851c6493daf9b 100644 --- a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs +++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs @@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { // `Deref` is being implemented for `t` if let hir::ItemKind::Impl(impl_) = item.kind && let Some(trait_) = &impl_.of_trait - && let t = cx.tcx.type_of(item.owner_id).subst_identity() + && let t = cx.tcx.type_of(item.owner_id).instantiate_identity() && let opt_did @ Some(did) = trait_.trait_def_id() && opt_did == cx.tcx.lang_items().deref_trait() // `t` is `dyn t_principal` diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs index ed2b384805e05..467f53d445c15 100644 --- a/compiler/rustc_lint/src/drop_forget_useless.rs +++ b/compiler/rustc_lint/src/drop_forget_useless.rs @@ -1,8 +1,12 @@ use rustc_hir::{Arm, Expr, ExprKind, Node}; +use rustc_middle::ty; use rustc_span::sym; use crate::{ - lints::{DropCopyDiag, DropRefDiag, ForgetCopyDiag, ForgetRefDiag}, + lints::{ + DropCopyDiag, DropRefDiag, ForgetCopyDiag, ForgetRefDiag, UndroppedManuallyDropsDiag, + UndroppedManuallyDropsSuggestion, + }, LateContext, LateLintPass, LintContext, }; @@ -109,7 +113,29 @@ declare_lint! { "calls to `std::mem::forget` with a value that implements Copy" } -declare_lint_pass!(DropForgetUseless => [DROPPING_REFERENCES, FORGETTING_REFERENCES, DROPPING_COPY_TYPES, FORGETTING_COPY_TYPES]); +declare_lint! { + /// The `undropped_manually_drops` lint check for calls to `std::mem::drop` with + /// a value of `std::mem::ManuallyDrop` which doesn't drop. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// struct S; + /// drop(std::mem::ManuallyDrop::new(S)); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `ManuallyDrop` does not drop it's inner value so calling `std::mem::drop` will + /// not drop the inner value of the `ManuallyDrop` either. + pub UNDROPPED_MANUALLY_DROPS, + Deny, + "calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of it's inner value" +} + +declare_lint_pass!(DropForgetUseless => [DROPPING_REFERENCES, FORGETTING_REFERENCES, DROPPING_COPY_TYPES, FORGETTING_COPY_TYPES, UNDROPPED_MANUALLY_DROPS]); impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { @@ -134,6 +160,20 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { sym::mem_forget if is_copy => { cx.emit_spanned_lint(FORGETTING_COPY_TYPES, expr.span, ForgetCopyDiag { arg_ty, label: arg.span }); } + sym::mem_drop if let ty::Adt(adt, _) = arg_ty.kind() && adt.is_manually_drop() => { + cx.emit_spanned_lint( + UNDROPPED_MANUALLY_DROPS, + expr.span, + UndroppedManuallyDropsDiag { + arg_ty, + label: arg.span, + suggestion: UndroppedManuallyDropsSuggestion { + start_span: arg.span.shrink_to_lo(), + end_span: arg.span.shrink_to_hi() + } + } + ); + } _ => return, }; } @@ -154,7 +194,7 @@ fn is_single_call_in_arm<'tcx>( arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>, ) -> bool { - if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) { + if arg.can_have_side_effects() { let parent_node = cx.tcx.hir().find_parent(drop_expr.hir_id); if let Some(Node::Arm(Arm { body, .. })) = &parent_node { return body.hir_id == drop_expr.hir_id; diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 9f1f5a26ee533..211ea8f4347e3 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -20,6 +20,7 @@ use rustc_ast::ptr::P; use rustc_ast::visit::{self as ast_visit, Visitor}; use rustc_ast::{self as ast, walk_list, HasAttrs}; use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_feature::Features; use rustc_middle::ty::RegisteredTools; use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass}; use rustc_session::Session; @@ -381,6 +382,7 @@ impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [P( sess: &Session, + features: &Features, pre_expansion: bool, lint_store: &LintStore, registered_tools: &RegisteredTools, @@ -390,6 +392,7 @@ pub fn check_ast_node<'a>( ) { let context = EarlyContext::new( sess, + features, !pre_expansion, lint_store, registered_tools, diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs index 2ce28f3a049e8..05fe64830d17e 100644 --- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -51,7 +51,7 @@ fn enforce_mem_discriminant( expr_span: Span, args_span: Span, ) { - let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); if is_non_enum(ty_param) { cx.emit_spanned_lint( ENUM_INTRINSICS_NON_ENUMS, @@ -62,7 +62,7 @@ fn enforce_mem_discriminant( } fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) { - let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0); + let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); if is_non_enum(ty_param) { cx.emit_spanned_lint( ENUM_INTRINSICS_NON_ENUMS, diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index bbae3d368f43c..68167487a1bd4 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -39,7 +39,7 @@ impl AddToDiagnostic for OverruledAttributeSub { diag.span_label(span, fluent::lint_node_source); if let Some(rationale) = reason { #[allow(rustc::untranslatable_diagnostic)] - diag.note(rationale.as_str()); + diag.note(rationale.to_string()); } } OverruledAttributeSub::CommandLineSource => { diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs index 7b58bf03bbea8..c299e38842acf 100644 --- a/compiler/rustc_lint/src/for_loops_over_fallibles.rs +++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs @@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { let ty = cx.typeck_results().expr_ty(arg); - let &ty::Adt(adt, substs) = ty.kind() else { return }; + let &ty::Adt(adt, args) = ty.kind() else { return }; let (article, ty, var) = match adt.did() { did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"), @@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { } else { ForLoopsOverFalliblesLoopSub::UseWhileLet { start_span: expr.span.with_hi(pat.span.lo()), end_span: pat.span.between(arg.span), var } } ; - let question_mark = suggest_question_mark(cx, adt, substs, expr.span) + let question_mark = suggest_question_mark(cx, adt, args, expr.span) .then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() }); let suggestion = ForLoopsOverFalliblesSuggestion { var, @@ -115,11 +115,13 @@ fn extract_iterator_next_call<'tcx>( fn suggest_question_mark<'tcx>( cx: &LateContext<'tcx>, adt: ty::AdtDef<'tcx>, - substs: &List>, + args: &List>, span: Span, ) -> bool { let Some(body_id) = cx.enclosing_body else { return false }; - let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { return false }; + let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { + return false; + }; if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { return false; @@ -135,7 +137,7 @@ fn suggest_question_mark<'tcx>( } } - let ty = substs.type_at(0); + let ty = args.type_at(0); let infcx = cx.tcx.infer_ctxt().build(); let ocx = ObligationCtxt::new(&infcx); diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs new file mode 100644 index 0000000000000..7b291d558e03b --- /dev/null +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -0,0 +1,402 @@ +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_middle::query::Providers; +use rustc_middle::ty::layout::LayoutError; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; +use rustc_session::lint::{lint_array, LintArray}; +use rustc_span::{sym, Span, Symbol}; +use rustc_target::abi::FIRST_VARIANT; + +use crate::lints::{BuiltinClashingExtern, BuiltinClashingExternSub}; +use crate::types; + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { clashing_extern_declarations, ..*providers }; +} + +pub(crate) fn get_lints() -> LintArray { + lint_array!(CLASHING_EXTERN_DECLARATIONS) +} + +fn clashing_extern_declarations(tcx: TyCtxt<'_>, (): ()) { + let mut lint = ClashingExternDeclarations::new(); + for id in tcx.hir_crate_items(()).foreign_items() { + lint.check_foreign_item(tcx, id); + } +} + +declare_lint! { + /// The `clashing_extern_declarations` lint detects when an `extern fn` + /// has been declared with the same name but different types. + /// + /// ### Example + /// + /// ```rust + /// mod m { + /// extern "C" { + /// fn foo(); + /// } + /// } + /// + /// extern "C" { + /// fn foo(_: u32); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Because two symbols of the same name cannot be resolved to two + /// different functions at link time, and one function cannot possibly + /// have two types, a clashing extern declaration is almost certainly a + /// mistake. Check to make sure that the `extern` definitions are correct + /// and equivalent, and possibly consider unifying them in one location. + /// + /// This lint does not run between crates because a project may have + /// dependencies which both rely on the same extern function, but declare + /// it in a different (but valid) way. For example, they may both declare + /// an opaque type for one or more of the arguments (which would end up + /// distinct types), or use types that are valid conversions in the + /// language the `extern fn` is defined in. In these cases, the compiler + /// can't say that the clashing declaration is incorrect. + pub CLASHING_EXTERN_DECLARATIONS, + Warn, + "detects when an extern fn has been declared with the same name but different types" +} + +struct ClashingExternDeclarations { + /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls + /// contains an entry for key K, it means a symbol with name K has been seen by this lint and + /// the symbol should be reported as a clashing declaration. + // FIXME: Technically, we could just store a &'tcx str here without issue; however, the + // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime. + seen_decls: FxHashMap, +} + +/// Differentiate between whether the name for an extern decl came from the link_name attribute or +/// just from declaration itself. This is important because we don't want to report clashes on +/// symbol name if they don't actually clash because one or the other links against a symbol with a +/// different name. +enum SymbolName { + /// The name of the symbol + the span of the annotation which introduced the link name. + Link(Symbol, Span), + /// No link name, so just the name of the symbol. + Normal(Symbol), +} + +impl SymbolName { + fn get_name(&self) -> Symbol { + match self { + SymbolName::Link(s, _) | SymbolName::Normal(s) => *s, + } + } +} + +impl ClashingExternDeclarations { + pub(crate) fn new() -> Self { + ClashingExternDeclarations { seen_decls: FxHashMap::default() } + } + + /// Insert a new foreign item into the seen set. If a symbol with the same name already exists + /// for the item, return its HirId without updating the set. + fn insert(&mut self, tcx: TyCtxt<'_>, fi: hir::ForeignItemId) -> Option { + let did = fi.owner_id.to_def_id(); + let instance = Instance::new(did, ty::List::identity_for_item(tcx, did)); + let name = Symbol::intern(tcx.symbol_name(instance).name); + if let Some(&existing_id) = self.seen_decls.get(&name) { + // Avoid updating the map with the new entry when we do find a collision. We want to + // make sure we're always pointing to the first definition as the previous declaration. + // This lets us avoid emitting "knock-on" diagnostics. + Some(existing_id) + } else { + self.seen_decls.insert(name, fi.owner_id) + } + } + + #[instrument(level = "trace", skip(self, tcx))] + fn check_foreign_item<'tcx>(&mut self, tcx: TyCtxt<'tcx>, this_fi: hir::ForeignItemId) { + let DefKind::Fn = tcx.def_kind(this_fi.owner_id) else { return }; + let Some(existing_did) = self.insert(tcx, this_fi) else { return }; + + let existing_decl_ty = tcx.type_of(existing_did).skip_binder(); + let this_decl_ty = tcx.type_of(this_fi.owner_id).instantiate_identity(); + debug!( + "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", + existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty + ); + + // Check that the declarations match. + if !structurally_same_type( + tcx, + tcx.param_env(this_fi.owner_id), + existing_decl_ty, + this_decl_ty, + types::CItemKind::Declaration, + ) { + let orig = name_of_extern_decl(tcx, existing_did); + + // Finally, emit the diagnostic. + let this = tcx.item_name(this_fi.owner_id.to_def_id()); + let orig = orig.get_name(); + let previous_decl_label = get_relevant_span(tcx, existing_did); + let mismatch_label = get_relevant_span(tcx, this_fi.owner_id); + let sub = + BuiltinClashingExternSub { tcx, expected: existing_decl_ty, found: this_decl_ty }; + let decorator = if orig == this { + BuiltinClashingExtern::SameName { + this, + orig, + previous_decl_label, + mismatch_label, + sub, + } + } else { + BuiltinClashingExtern::DiffName { + this, + orig, + previous_decl_label, + mismatch_label, + sub, + } + }; + tcx.emit_spanned_lint( + CLASHING_EXTERN_DECLARATIONS, + this_fi.hir_id(), + mismatch_label, + decorator, + ); + } + } +} + +/// Get the name of the symbol that's linked against for a given extern declaration. That is, +/// the name specified in a #[link_name = ...] attribute if one was specified, else, just the +/// symbol's name. +fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> SymbolName { + if let Some((overridden_link_name, overridden_link_name_span)) = + tcx.codegen_fn_attrs(fi).link_name.map(|overridden_link_name| { + // FIXME: Instead of searching through the attributes again to get span + // information, we could have codegen_fn_attrs also give span information back for + // where the attribute was defined. However, until this is found to be a + // bottleneck, this does just fine. + (overridden_link_name, tcx.get_attr(fi, sym::link_name).unwrap().span) + }) + { + SymbolName::Link(overridden_link_name, overridden_link_name_span) + } else { + SymbolName::Normal(tcx.item_name(fi.to_def_id())) + } +} + +/// We want to ensure that we use spans for both decls that include where the +/// name was defined, whether that was from the link_name attribute or not. +fn get_relevant_span(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> Span { + match name_of_extern_decl(tcx, fi) { + SymbolName::Normal(_) => tcx.def_span(fi), + SymbolName::Link(_, annot_span) => annot_span, + } +} + +/// Checks whether two types are structurally the same enough that the declarations shouldn't +/// clash. We need this so we don't emit a lint when two modules both declare an extern struct, +/// with the same members (as the declarations shouldn't clash). +fn structurally_same_type<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: types::CItemKind, +) -> bool { + let mut seen_types = FxHashSet::default(); + structurally_same_type_impl(&mut seen_types, tcx, param_env, a, b, ckind) +} + +fn structurally_same_type_impl<'tcx>( + seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: types::CItemKind, +) -> bool { + debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b); + + // Given a transparent newtype, reach through and grab the inner + // type unless the newtype makes the type non-null. + let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> { + loop { + if let ty::Adt(def, args) = *ty.kind() { + let is_transparent = def.repr().transparent(); + let is_non_null = types::nonnull_optimization_guaranteed(tcx, def); + debug!( + "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}", + ty, is_transparent, is_non_null + ); + if is_transparent && !is_non_null { + debug_assert_eq!(def.variants().len(), 1); + let v = &def.variant(FIRST_VARIANT); + // continue with `ty`'s non-ZST field, + // otherwise `ty` is a ZST and we can return + if let Some(field) = types::transparent_newtype_field(tcx, v) { + ty = field.ty(tcx, args); + continue; + } + } + } + debug!("non_transparent_ty -> {:?}", ty); + return ty; + } + }; + + let a = non_transparent_ty(a); + let b = non_transparent_ty(b); + + if !seen_types.insert((a, b)) { + // We've encountered a cycle. There's no point going any further -- the types are + // structurally the same. + true + } else if a == b { + // All nominally-same types are structurally same, too. + true + } else { + // Do a full, depth-first comparison between the two. + use rustc_type_ir::sty::TyKind::*; + let a_kind = a.kind(); + let b_kind = b.kind(); + + let compare_layouts = |a, b| -> Result> { + debug!("compare_layouts({:?}, {:?})", a, b); + let a_layout = &tcx.layout_of(param_env.and(a))?.layout.abi(); + let b_layout = &tcx.layout_of(param_env.and(b))?.layout.abi(); + debug!( + "comparing layouts: {:?} == {:?} = {}", + a_layout, + b_layout, + a_layout == b_layout + ); + Ok(a_layout == b_layout) + }; + + #[allow(rustc::usage_of_ty_tykind)] + let is_primitive_or_pointer = + |kind: &ty::TyKind<'_>| kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..)); + + ensure_sufficient_stack(|| { + match (a_kind, b_kind) { + (Adt(a_def, _), Adt(b_def, _)) => { + // We can immediately rule out these types as structurally same if + // their layouts differ. + match compare_layouts(a, b) { + Ok(false) => return false, + _ => (), // otherwise, continue onto the full, fields comparison + } + + // Grab a flattened representation of all fields. + let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter()); + let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter()); + + // Perform a structural comparison for each field. + a_fields.eq_by( + b_fields, + |&ty::FieldDef { did: a_did, .. }, &ty::FieldDef { did: b_did, .. }| { + structurally_same_type_impl( + seen_types, + tcx, + param_env, + tcx.type_of(a_did).instantiate_identity(), + tcx.type_of(b_did).instantiate_identity(), + ckind, + ) + }, + ) + } + (Array(a_ty, a_const), Array(b_ty, b_const)) => { + // For arrays, we also check the constness of the type. + a_const.kind() == b_const.kind() + && structurally_same_type_impl( + seen_types, tcx, param_env, *a_ty, *b_ty, ckind, + ) + } + (Slice(a_ty), Slice(b_ty)) => { + structurally_same_type_impl(seen_types, tcx, param_env, *a_ty, *b_ty, ckind) + } + (RawPtr(a_tymut), RawPtr(b_tymut)) => { + a_tymut.mutbl == b_tymut.mutbl + && structurally_same_type_impl( + seen_types, tcx, param_env, a_tymut.ty, b_tymut.ty, ckind, + ) + } + (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { + // For structural sameness, we don't need the region to be same. + a_mut == b_mut + && structurally_same_type_impl( + seen_types, tcx, param_env, *a_ty, *b_ty, ckind, + ) + } + (FnDef(..), FnDef(..)) => { + let a_poly_sig = a.fn_sig(tcx); + let b_poly_sig = b.fn_sig(tcx); + + // We don't compare regions, but leaving bound regions around ICEs, so + // we erase them. + let a_sig = tcx.erase_late_bound_regions(a_poly_sig); + let b_sig = tcx.erase_late_bound_regions(b_poly_sig); + + (a_sig.abi, a_sig.unsafety, a_sig.c_variadic) + == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic) + && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { + structurally_same_type_impl(seen_types, tcx, param_env, *a, *b, ckind) + }) + && structurally_same_type_impl( + seen_types, + tcx, + param_env, + a_sig.output(), + b_sig.output(), + ckind, + ) + } + (Tuple(a_args), Tuple(b_args)) => { + a_args.iter().eq_by(b_args.iter(), |a_ty, b_ty| { + structurally_same_type_impl(seen_types, tcx, param_env, a_ty, b_ty, ckind) + }) + } + // For these, it's not quite as easy to define structural-sameness quite so easily. + // For the purposes of this lint, take the conservative approach and mark them as + // not structurally same. + (Dynamic(..), Dynamic(..)) + | (Error(..), Error(..)) + | (Closure(..), Closure(..)) + | (Generator(..), Generator(..)) + | (GeneratorWitness(..), GeneratorWitness(..)) + | (Alias(ty::Projection, ..), Alias(ty::Projection, ..)) + | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..)) + | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false, + + // These definitely should have been caught above. + (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), + + // An Adt and a primitive or pointer type. This can be FFI-safe if non-null + // enum layout optimisation is being applied. + (Adt(..), other_kind) | (other_kind, Adt(..)) + if is_primitive_or_pointer(other_kind) => + { + let (primitive, adt) = + if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) }; + if let Some(ty) = types::repr_nullable_ptr(tcx, param_env, adt, ckind) { + ty == primitive + } else { + compare_layouts(a, b).unwrap_or(false) + } + } + // Otherwise, just compare the layouts. This may fail to lint for some + // incompatible types, but at the very least, will stop reads into + // uninitialised memory. + _ => compare_layouts(a, b).unwrap_or(false), + } + }) + } +} diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 6f773e04a97d7..4b803621f71ce 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -52,20 +52,20 @@ impl LateLintPass<'_> for DefaultHashTypes { } /// Helper function for lints that check for expressions with calls and use typeck results to -/// get the `DefId` and `SubstsRef` of the function. +/// get the `DefId` and `GenericArgsRef` of the function. fn typeck_results_of_method_fn<'tcx>( cx: &LateContext<'tcx>, expr: &Expr<'_>, -) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> { +) -> Option<(Span, DefId, ty::GenericArgsRef<'tcx>)> { match expr.kind { ExprKind::MethodCall(segment, ..) if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => { - Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id))) + Some((segment.ident.span, def_id, cx.typeck_results().node_args(expr.hir_id))) }, _ => { match cx.typeck_results().node_type(expr.hir_id).kind() { - &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)), + &ty::FnDef(def_id, args) => Some((expr.span, def_id, args)), _ => None, } } @@ -89,8 +89,8 @@ declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]); impl LateLintPass<'_> for QueryStability { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; - if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) { + let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return }; + if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args) { let def_id = instance.def_id(); if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) { cx.emit_spanned_lint( @@ -232,7 +232,7 @@ fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option { } // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait. Res::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { - if let ty::Adt(adt, substs) = cx.tcx.type_of(did).subst_identity().kind() { + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() { if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did()) { // NOTE: This path is currently unreachable as `Ty<'tcx>` is @@ -241,7 +241,7 @@ fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option { // // I(@lcnr) still kept this branch in so we don't miss this // if we ever change it in the future. - return Some(format!("{}<{}>", name, substs[0])); + return Some(format!("{}<{}>", name, args[0])); } } } @@ -379,9 +379,9 @@ declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSID impl LateLintPass<'_> for Diagnostics { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return }; - debug!(?span, ?def_id, ?substs); - let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) + let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return }; + debug!(?span, ?def_id, ?args); + let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args) .ok() .flatten() .is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics)); @@ -414,7 +414,7 @@ impl LateLintPass<'_> for Diagnostics { } let mut found_diagnostic_message = false; - for ty in substs.types() { + for ty in args.types() { debug!(?ty); if let Some(adt_def) = ty.ty_adt_def() && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) && diff --git a/compiler/rustc_lint/src/invalid_from_utf8.rs b/compiler/rustc_lint/src/invalid_from_utf8.rs new file mode 100644 index 0000000000000..3291286ad679b --- /dev/null +++ b/compiler/rustc_lint/src/invalid_from_utf8.rs @@ -0,0 +1,118 @@ +use std::str::Utf8Error; + +use rustc_ast::{BorrowKind, LitKind}; +use rustc_hir::{Expr, ExprKind}; +use rustc_span::source_map::Spanned; +use rustc_span::sym; + +use crate::lints::InvalidFromUtf8Diag; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `invalid_from_utf8_unchecked` lint checks for calls to + /// `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut` + /// with an invalid UTF-8 literal. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #[allow(unused)] + /// unsafe { + /// std::str::from_utf8_unchecked(b"Ru\x82st"); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Creating such a `str` would result in undefined behavior as per documentation + /// for `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`. + pub INVALID_FROM_UTF8_UNCHECKED, + Deny, + "using a non UTF-8 literal in `std::str::from_utf8_unchecked`" +} + +declare_lint! { + /// The `invalid_from_utf8` lint checks for calls to + /// `std::str::from_utf8` and `std::str::from_utf8_mut` + /// with an invalid UTF-8 literal. + /// + /// ### Example + /// + /// ```rust + /// # #[allow(unused)] + /// std::str::from_utf8(b"Ru\x82st"); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Trying to create such a `str` would always return an error as per documentation + /// for `std::str::from_utf8` and `std::str::from_utf8_mut`. + pub INVALID_FROM_UTF8, + Warn, + "using a non UTF-8 literal in `std::str::from_utf8`" +} + +declare_lint_pass!(InvalidFromUtf8 => [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]); + +impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Call(path, [arg]) = expr.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && [sym::str_from_utf8, sym::str_from_utf8_mut, + sym::str_from_utf8_unchecked, sym::str_from_utf8_unchecked_mut].contains(&diag_item) + { + let lint = |utf8_error: Utf8Error| { + let label = arg.span; + let method = diag_item.as_str().strip_prefix("str_").unwrap(); + let method = format!("std::str::{method}"); + let valid_up_to = utf8_error.valid_up_to(); + let is_unchecked_variant = diag_item.as_str().contains("unchecked"); + + cx.emit_spanned_lint( + if is_unchecked_variant { INVALID_FROM_UTF8_UNCHECKED } else { INVALID_FROM_UTF8 }, + expr.span, + if is_unchecked_variant { + InvalidFromUtf8Diag::Unchecked { method, valid_up_to, label } + } else { + InvalidFromUtf8Diag::Checked { method, valid_up_to, label } + } + ) + }; + + match &arg.kind { + ExprKind::Lit(Spanned { node: lit, .. }) => { + if let LitKind::ByteStr(bytes, _) = &lit + && let Err(utf8_error) = std::str::from_utf8(bytes) + { + lint(utf8_error); + } + }, + ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => { + let elements = args.iter().map(|e|{ + match &e.kind { + ExprKind::Lit(Spanned { node: lit, .. }) => match lit { + LitKind::Byte(b) => Some(*b), + LitKind::Int(b, _) => Some(*b as u8), + _ => None + } + _ => None + } + }).collect::>>(); + + if let Some(elements) = elements + && let Err(utf8_error) = std::str::from_utf8(&elements) + { + lint(utf8_error); + } + } + _ => {} + } + } + } +} diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index c9781a72704f4..73af51d9e9061 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -16,9 +16,10 @@ use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore}; use rustc_ast as ast; -use rustc_data_structures::sync::{join, DynSend}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_data_structures::sync::join; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::intravisit as hir_visit; use rustc_hir::intravisit::Visitor; use rustc_middle::hir::nested_filter; @@ -157,10 +158,12 @@ impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPas } fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { - self.with_lint_attrs(e.hir_id, |cx| { - lint_callback!(cx, check_expr, e); - hir_visit::walk_expr(cx, e); - lint_callback!(cx, check_expr_post, e); + ensure_sufficient_stack(|| { + self.with_lint_attrs(e.hir_id, |cx| { + lint_callback!(cx, check_expr, e); + hir_visit::walk_expr(cx, e); + lint_callback!(cx, check_expr_post, e); + }) }) } @@ -240,8 +243,10 @@ impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPas } fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { - lint_callback!(self, check_arm, a); - hir_visit::walk_arm(self, a); + self.with_lint_attrs(a.hir_id, |cx| { + lint_callback!(cx, check_arm, a); + hir_visit::walk_arm(cx, a); + }) } fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { @@ -331,9 +336,9 @@ macro_rules! impl_late_lint_pass { crate::late_lint_methods!(impl_late_lint_pass, []); -pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( +pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( tcx: TyCtxt<'tcx>, - module_def_id: LocalDefId, + module_def_id: LocalModDefId, builtin_lints: T, ) { let context = LateContext { @@ -364,13 +369,19 @@ pub(super) fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( tcx: TyCtxt<'tcx>, - module_def_id: LocalDefId, + module_def_id: LocalModDefId, context: LateContext<'tcx>, pass: T, ) { let mut cx = LateContextAndPass { context, pass }; let (module, _span, hir_id) = tcx.hir().get_module(module_def_id); + + // There is no module lint that will have the crate itself as an item, so check it here. + if hir_id == hir::CRATE_HIR_ID { + lint_callback!(cx, check_crate,); + } + cx.process_mod(module, hir_id); // Visit the crate attributes @@ -378,10 +389,19 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( for attr in tcx.hir().attrs(hir::CRATE_HIR_ID).iter() { cx.visit_attribute(attr) } + lint_callback!(cx, check_crate_post,); } } -fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builtin_lints: T) { +fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { + // Note: `passes` is often empty. + let mut passes: Vec<_> = + unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + + if passes.is_empty() { + return; + } + let context = LateContext { tcx, enclosing_body: None, @@ -394,18 +414,8 @@ fn late_lint_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>(tcx: TyCtxt<'tcx>, builti only_module: false, }; - // Note: `passes` is often empty. In that case, it's faster to run - // `builtin_lints` directly rather than bundling it up into the - // `RuntimeCombinedLateLintPass`. - let mut passes: Vec<_> = - unerased_lint_store(tcx).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); - if passes.is_empty() { - late_lint_crate_inner(tcx, context, builtin_lints); - } else { - passes.push(Box::new(builtin_lints)); - let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; - late_lint_crate_inner(tcx, context, pass); - } + let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; + late_lint_crate_inner(tcx, context, pass); } fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>( @@ -427,15 +437,12 @@ fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>( } /// Performs lint checking on a crate. -pub fn check_crate<'tcx, T: LateLintPass<'tcx> + 'tcx>( - tcx: TyCtxt<'tcx>, - builtin_lints: impl FnOnce() -> T + Send + DynSend, -) { +pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>) { join( || { tcx.sess.time("crate_lints", || { // Run whole crate non-incremental lints - late_lint_crate(tcx, builtin_lints()); + late_lint_crate(tcx); }); }, || { diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index b92ed11f38a88..1f4e5fa4d3b35 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -1,4 +1,5 @@ use crate::{ + builtin::MISSING_DOCS, context::{CheckLintNameResult, LintStore}, fluent_generated as fluent, late::unerased_lint_store, @@ -11,6 +12,7 @@ use rustc_ast as ast; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{DecorateLint, DiagnosticBuilder, DiagnosticMessage, MultiSpan}; +use rustc_feature::Features; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::HirId; @@ -118,6 +120,7 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp let mut builder = LintLevelsBuilder { sess: tcx.sess, + features: tcx.features(), provider: QueryMapExpectationsWrapper { tcx, cur: hir::CRATE_HIR_ID, @@ -147,6 +150,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe let mut levels = LintLevelsBuilder { sess: tcx.sess, + features: tcx.features(), provider: LintLevelQueryMap { tcx, cur: owner.into(), @@ -242,7 +246,9 @@ impl LintLevelsProvider for LintLevelQueryMap<'_> { struct QueryMapExpectationsWrapper<'tcx> { tcx: TyCtxt<'tcx>, + /// HirId of the currently investigated element. cur: HirId, + /// Level map for `cur`. specs: ShallowLintLevelMap, expectations: Vec<(LintExpectationId, LintExpectation)>, unstable_to_stable_ids: FxHashMap, @@ -255,15 +261,18 @@ impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> { self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty) } fn insert(&mut self, id: LintId, lvl: LevelAndSource) { - let specs = self.specs.specs.get_mut_or_insert_default(self.cur.local_id); - specs.clear(); - specs.insert(id, lvl); + self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl); } fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { + // We cannot use `tcx.lint_level_at_node` because we want to know in which order the + // attributes have been inserted, in particular whether an `expect` follows a `forbid`. self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) } fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { - let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id else { bug!("unstable expectation id should already be mapped") }; + let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id + else { + bug!("unstable expectation id should already be mapped") + }; let key = LintExpectationId::Unstable { attr_id, lint_index: None }; self.unstable_to_stable_ids.entry(key).or_insert(LintExpectationId::Stable { @@ -355,7 +364,9 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> { fn add_id(&mut self, hir_id: HirId) { + // Change both the `HirId` and the associated specs. self.provider.cur = hir_id; + self.provider.specs.specs.clear(); self.add(self.provider.tcx.hir().attrs(hir_id), hir_id == hir::CRATE_HIR_ID, Some(hir_id)); } } @@ -427,6 +438,7 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<' pub struct LintLevelsBuilder<'s, P> { sess: &'s Session, + features: &'s Features, provider: P, warn_about_weird_lints: bool, store: &'s LintStore, @@ -440,12 +452,14 @@ pub(crate) struct BuilderPush { impl<'s> LintLevelsBuilder<'s, TopDown> { pub(crate) fn new( sess: &'s Session, + features: &'s Features, warn_about_weird_lints: bool, store: &'s LintStore, registered_tools: &'s RegisteredTools, ) -> Self { let mut builder = LintLevelsBuilder { sess, + features, provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, warn_about_weird_lints, store, @@ -518,6 +532,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { self.sess } + pub(crate) fn features(&self) -> &Features { + self.features + } + pub(crate) fn lint_store(&self) -> &LintStore { self.store } @@ -538,7 +556,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let Ok(ids) = self.store.find_lints(&lint_name) else { // errors handled in check_lint_name_cmdline above - continue + continue; }; for id in ids { // ForceWarn and Forbid cannot be overridden @@ -660,6 +678,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; } + // `#[doc(hidden)]` disables missing_docs check. + if attr.has_name(sym::doc) + && attr + .meta_item_list() + .map_or(false, |l| ast::attr::list_contains_name(&l, sym::hidden)) + { + self.insert(LintId::of(MISSING_DOCS), (Level::Allow, LintLevelSource::Default)); + continue; + } + let level = match Level::from_attr(attr) { None => continue, // This is the only lint level with a `LintExpectationId` that can be created from an attribute @@ -681,9 +709,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { Some(lvl) => lvl, }; - let Some(mut metas) = attr.meta_item_list() else { - continue - }; + let Some(mut metas) = attr.meta_item_list() else { continue }; if metas.is_empty() { // This emits the unused_attributes lint for `#[level()]` @@ -700,7 +726,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { ast::MetaItemKind::NameValue(ref name_value) => { if item.path == sym::reason { if let ast::LitKind::Str(rationale, _) = name_value.kind { - if !self.sess.features_untracked().lint_reasons { + if !self.features.lint_reasons { feature_err( &self.sess.parse_sess, sym::lint_reasons, @@ -940,7 +966,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { ); } } else { - panic!("renamed lint does not exist: {}", new_name); + panic!("renamed lint does not exist: {new_name}"); } } } @@ -952,8 +978,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; } - let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src else { - continue + let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src + else { + continue; }; self.emit_spanned_lint( @@ -972,9 +999,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// Returns `true` if the lint's feature is enabled. // FIXME only emit this once for each attribute, instead of repeating it 4 times for // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`. + #[track_caller] fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool { if let Some(feature) = lint_id.lint.feature_gate { - if !self.sess.features_untracked().enabled(feature) { + if !self.features.enabled(feature) { let lint = builtin::UNKNOWN_LINTS; let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS); struct_lint_level( @@ -1009,6 +1037,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature #[rustc_lint_diagnostics] + #[track_caller] pub(crate) fn struct_lint( &self, lint: &'static Lint, @@ -1022,6 +1051,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { struct_lint_level(self.sess, lint, level, src, span, msg, decorate) } + #[track_caller] pub fn emit_spanned_lint( &self, lint: &'static Lint, @@ -1034,6 +1064,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { }); } + #[track_caller] pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> DecorateLint<'a, ()>) { let (level, src) = self.lint_level(lint); struct_lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| { diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index dfddfe09ab3c1..585b10e79e484 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -40,6 +40,7 @@ #![recursion_limit = "256"] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_middle; @@ -58,8 +59,10 @@ mod enum_intrinsics_non_enums; mod errors; mod expect; mod for_loops_over_fallibles; +mod foreign_modules; pub mod hidden_unicode_codepoints; mod internal; +mod invalid_from_utf8; mod late; mod let_underscore; mod levels; @@ -74,7 +77,9 @@ mod noop_method_call; mod opaque_hidden_inferred_bound; mod pass_by_value; mod passes; +mod ptr_nulls; mod redundant_semicolon; +mod reference_casting; mod traits; mod types; mod unused; @@ -85,7 +90,7 @@ use rustc_ast as ast; use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_session::lint::builtin::{ @@ -102,6 +107,7 @@ use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; use for_loops_over_fallibles::*; use hidden_unicode_codepoints::*; use internal::*; +use invalid_from_utf8::*; use let_underscore::*; use map_unit_fn::*; use methods::*; @@ -112,17 +118,19 @@ use nonstandard_style::*; use noop_method_call::*; use opaque_hidden_inferred_bound::*; use pass_by_value::*; +use ptr_nulls::*; use redundant_semicolon::*; +use reference_casting::*; use traits::*; use types::*; use unused::*; /// Useful for other parts of the compiler / Clippy. -pub use builtin::SoftLints; +pub use builtin::{MissingDoc, SoftLints}; pub use context::{CheckLintNameResult, FindLintError, LintStore}; pub use context::{EarlyContext, LateContext, LintContext}; pub use early::{check_ast_node, EarlyCheckNode}; -pub use late::{check_crate, unerased_lint_store}; +pub use late::{check_crate, late_lint_mod, unerased_lint_store}; pub use passes::{EarlyLintPass, LateLintPass}; pub use rustc_session::lint::Level::{self, *}; pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId}; @@ -133,11 +141,12 @@ fluent_messages! { "../messages.ftl" } pub fn provide(providers: &mut Providers) { levels::provide(providers); expect::provide(providers); + foreign_modules::provide(providers); *providers = Providers { lint_mod, ..*providers }; } -fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { - late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); +fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { + late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); } early_lint_methods!( @@ -167,7 +176,7 @@ early_lint_methods!( WhileTrue: WhileTrue, NonAsciiIdents: NonAsciiIdents, HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, - IncompleteFeatures: IncompleteFeatures, + IncompleteInternalFeatures: IncompleteInternalFeatures, RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, UnexpectedCfgs: UnexpectedCfgs, @@ -175,27 +184,6 @@ early_lint_methods!( ] ); -// FIXME: Make a separate lint type which does not require typeck tables. - -late_lint_methods!( - declare_combined_late_lint_pass, - [ - pub BuiltinCombinedLateLintPass, - [ - // Tracks state across modules - UnnameableTestItems: UnnameableTestItems::new(), - // Tracks attributes of parents - MissingDoc: MissingDoc::new(), - // Builds a global list of all impls of `Debug`. - // FIXME: Turn the computation of types which implement Debug into a query - // and change this to a module lint pass - MissingDebugImplementations: MissingDebugImplementations::default(), - // Keeps a global list of foreign declarations. - ClashingExternDeclarations: ClashingExternDeclarations::new(), - ] - ] -); - late_lint_methods!( declare_combined_late_lint_pass, [ @@ -207,10 +195,12 @@ late_lint_methods!( HardwiredLints: HardwiredLints, ImproperCTypesDeclarations: ImproperCTypesDeclarations, ImproperCTypesDefinitions: ImproperCTypesDefinitions, + InvalidFromUtf8: InvalidFromUtf8, VariantSizeDifferences: VariantSizeDifferences, BoxPointers: BoxPointers, PathStatements: PathStatements, LetUnderscore: LetUnderscore, + InvalidReferenceCasting: InvalidReferenceCasting::default(), // Depends on referenced function signatures in expressions UnusedResults: UnusedResults, NonUpperCaseGlobals: NonUpperCaseGlobals, @@ -219,6 +209,7 @@ late_lint_methods!( // Depends on types used in type definitions MissingCopyImplementations: MissingCopyImplementations, // Depends on referenced function signatures in expressions + PtrNullChecks: PtrNullChecks, MutableTransmutes: MutableTransmutes, TypeAliasBounds: TypeAliasBounds, TrivialConstraints: TrivialConstraints, @@ -245,6 +236,8 @@ late_lint_methods!( OpaqueHiddenInferredBound: OpaqueHiddenInferredBound, MultipleSupertraitUpcastable: MultipleSupertraitUpcastable, MapUnitFn: MapUnitFn, + MissingDebugImplementations: MissingDebugImplementations, + MissingDoc: MissingDoc, ] ] ); @@ -273,7 +266,7 @@ fn register_builtins(store: &mut LintStore) { store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); - store.register_lints(&BuiltinCombinedLateLintPass::get_lints()); + store.register_lints(&foreign_modules::get_lints()); add_lint_group!( "nonstandard_style", @@ -513,20 +506,20 @@ fn register_internals(store: &mut LintStore) { store.register_lints(&LintPassImpl::get_lints()); store.register_early_pass(|| Box::new(LintPassImpl)); store.register_lints(&DefaultHashTypes::get_lints()); - store.register_late_pass(|_| Box::new(DefaultHashTypes)); + store.register_late_mod_pass(|_| Box::new(DefaultHashTypes)); store.register_lints(&QueryStability::get_lints()); - store.register_late_pass(|_| Box::new(QueryStability)); + store.register_late_mod_pass(|_| Box::new(QueryStability)); store.register_lints(&ExistingDocKeyword::get_lints()); - store.register_late_pass(|_| Box::new(ExistingDocKeyword)); + store.register_late_mod_pass(|_| Box::new(ExistingDocKeyword)); store.register_lints(&TyTyKind::get_lints()); - store.register_late_pass(|_| Box::new(TyTyKind)); + store.register_late_mod_pass(|_| Box::new(TyTyKind)); store.register_lints(&Diagnostics::get_lints()); store.register_early_pass(|| Box::new(Diagnostics)); - store.register_late_pass(|_| Box::new(Diagnostics)); + store.register_late_mod_pass(|_| Box::new(Diagnostics)); store.register_lints(&BadOptAccess::get_lints()); - store.register_late_pass(|_| Box::new(BadOptAccess)); + store.register_late_mod_pass(|_| Box::new(BadOptAccess)); store.register_lints(&PassByValue::get_lints()); - store.register_late_pass(|_| Box::new(PassByValue)); + store.register_late_mod_pass(|_| Box::new(PassByValue)); // FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and // `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and // these lints will trigger all of the time - change this once migration to diagnostic structs diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index de1c2be287576..25982a458538c 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -10,7 +10,7 @@ use rustc_errors::{ use rustc_hir::def_id::DefId; use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::{ - inhabitedness::InhabitedPredicate, PolyExistentialTraitRef, Predicate, Ty, TyCtxt, + inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt, }; use rustc_session::parse::ParseSess; use rustc_span::{edition::Edition, sym, symbol::Ident, Span, Symbol}; @@ -250,7 +250,7 @@ impl<'a> DecorateLint<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> { rustc_session::parse::add_feature_diagnostics( diag, &self.parse_sess, - sym::closure_track_caller, + sym::async_fn_track_caller, ); diag } @@ -352,7 +352,7 @@ impl AddToDiagnostic for BuiltinTypeAliasGenericBoundsSuggestion { #[diag(lint_builtin_trivial_bounds)] pub struct BuiltinTrivialBounds<'a> { pub predicate_kind_name: &'a str, - pub predicate: Predicate<'a>, + pub predicate: Clause<'a>, } #[derive(LintDiagnostic)] @@ -370,10 +370,6 @@ pub enum BuiltinEllipsisInclusiveRangePatternsLint { }, } -#[derive(LintDiagnostic)] -#[diag(lint_builtin_unnameable_test_items)] -pub struct BuiltinUnnameableTestItems; - #[derive(LintDiagnostic)] #[diag(lint_builtin_keyword_idents)] pub struct BuiltinKeywordIdents { @@ -405,18 +401,27 @@ pub struct BuiltinExplicitOutlivesSuggestion { pub struct BuiltinIncompleteFeatures { pub name: Symbol, #[subdiagnostic] - pub note: Option, + pub note: Option, #[subdiagnostic] pub help: Option, } +#[derive(LintDiagnostic)] +#[diag(lint_builtin_internal_features)] +#[note] +pub struct BuiltinInternalFeatures { + pub name: Symbol, + #[subdiagnostic] + pub note: Option, +} + #[derive(Subdiagnostic)] #[help(lint_help)] pub struct BuiltinIncompleteFeaturesHelp; #[derive(Subdiagnostic)] #[note(lint_note)] -pub struct BuiltinIncompleteFeaturesNote { +pub struct BuiltinFeatureIssueNote { pub n: NonZeroU32, } @@ -613,6 +618,24 @@ pub struct ExpectationNote { pub rationale: Symbol, } +// ptr_nulls.rs +#[derive(LintDiagnostic)] +pub enum PtrNullChecksDiag<'a> { + #[diag(lint_ptr_null_checks_fn_ptr)] + #[help(lint_help)] + FnPtr { + orig_ty: Ty<'a>, + #[label] + label: Span, + }, + #[diag(lint_ptr_null_checks_ref)] + Ref { + orig_ty: Ty<'a>, + #[label] + label: Span, + }, +} + // for_loops_over_fallibles.rs #[derive(LintDiagnostic)] #[diag(lint_for_loops_over_fallibles)] @@ -699,6 +722,59 @@ pub struct ForgetCopyDiag<'a> { pub label: Span, } +#[derive(LintDiagnostic)] +#[diag(lint_undropped_manually_drops)] +pub struct UndroppedManuallyDropsDiag<'a> { + pub arg_ty: Ty<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub suggestion: UndroppedManuallyDropsSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")] +pub struct UndroppedManuallyDropsSuggestion { + #[suggestion_part(code = "std::mem::ManuallyDrop::into_inner(")] + pub start_span: Span, + #[suggestion_part(code = ")")] + pub end_span: Span, +} + +// invalid_from_utf8.rs +#[derive(LintDiagnostic)] +pub enum InvalidFromUtf8Diag { + #[diag(lint_invalid_from_utf8_unchecked)] + Unchecked { + method: String, + valid_up_to: usize, + #[label] + label: Span, + }, + #[diag(lint_invalid_from_utf8_checked)] + Checked { + method: String, + valid_up_to: usize, + #[label] + label: Span, + }, +} + +// reference_casting.rs +#[derive(LintDiagnostic)] +pub enum InvalidReferenceCastingDiag { + #[diag(lint_invalid_reference_casting_borrow_as_mut)] + BorrowAsMut { + #[label] + orig_cast: Option, + }, + #[diag(lint_invalid_reference_casting_assign_to_ref)] + AssignToRef { + #[label] + orig_cast: Option, + }, +} + // hidden_unicode_codepoints.rs #[derive(LintDiagnostic)] #[diag(lint_hidden_unicode_codepoints)] @@ -727,7 +803,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagLabels { ) -> rustc_errors::SubdiagnosticMessage, { for (c, span) in self.spans { - diag.span_label(span, format!("{:?}", c)); + diag.span_label(span, format!("{c:?}")); } } } @@ -759,7 +835,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub { spans .into_iter() .map(|(c, span)| { - let c = format!("{:?}", c); + let c = format!("{c:?}"); (span, c[1..c.len() - 1].to_string()) }) .collect(), @@ -774,7 +850,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub { "escaped", spans .into_iter() - .map(|(c, _)| format!("{:?}", c)) + .map(|(c, _)| format!("{c:?}")) .collect::>() .join(", "), ); @@ -1007,8 +1083,10 @@ pub struct IdentifierUncommonCodepoints; pub struct ConfusableIdentifierPair { pub existing_sym: Symbol, pub sym: Symbol, - #[label] + #[label(lint_other_use)] pub label: Span, + #[label(lint_current_use)] + pub main_label: Span, } #[derive(LintDiagnostic)] @@ -1182,17 +1260,22 @@ pub enum NonUpperCaseGlobalSub { #[note] pub struct NoopMethodCallDiag<'a> { pub method: Symbol, - pub receiver_ty: Ty<'a>, - #[label] + pub orig_ty: Ty<'a>, + pub trait_: Symbol, + #[suggestion(code = "", applicability = "machine-applicable")] pub label: Span, } #[derive(LintDiagnostic)] -#[diag(lint_suspicious_double_ref_op)] -pub struct SuspiciousDoubleRefDiag<'a> { - pub call: Symbol, +#[diag(lint_suspicious_double_ref_deref)] +pub struct SuspiciousDoubleRefDerefDiag<'a> { + pub ty: Ty<'a>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_suspicious_double_ref_clone)] +pub struct SuspiciousDoubleRefCloneDiag<'a> { pub ty: Ty<'a>, - pub op: &'static str, } // pass_by_value.rs @@ -1215,7 +1298,7 @@ pub struct RedundantSemicolonsDiag { // traits.rs pub struct DropTraitConstraintsDiag<'a> { - pub predicate: Predicate<'a>, + pub predicate: Clause<'a>, pub tcx: TyCtxt<'a>, pub def_id: DefId, } @@ -1299,6 +1382,8 @@ pub struct OverflowingBinHex<'a> { pub sign: OverflowingBinHexSign, #[subdiagnostic] pub sub: Option>, + #[subdiagnostic] + pub sign_bit_sub: Option>, } pub enum OverflowingBinHexSign { @@ -1343,6 +1428,21 @@ pub enum OverflowingBinHexSub<'a> { Help { suggestion_ty: &'a str }, } +#[derive(Subdiagnostic)] +#[suggestion( + lint_sign_bit_suggestion, + code = "{lit_no_suffix}{uint_ty} as {int_ty}", + applicability = "maybe-incorrect" +)] +pub struct OverflowingBinHexSignBitSub<'a> { + #[primary_span] + pub span: Span, + pub lit_no_suffix: &'a str, + pub negative_val: String, + pub uint_ty: &'a str, + pub int_ty: &'a str, +} + #[derive(LintDiagnostic)] #[diag(lint_overflowing_int)] #[note] @@ -1391,6 +1491,36 @@ pub struct OverflowingLiteral<'a> { #[diag(lint_unused_comparisons)] pub struct UnusedComparisons; +#[derive(LintDiagnostic)] +pub enum InvalidNanComparisons { + #[diag(lint_invalid_nan_comparisons_eq_ne)] + EqNe { + #[subdiagnostic] + suggestion: Option, + }, + #[diag(lint_invalid_nan_comparisons_lt_le_gt_ge)] + LtLeGtGe, +} + +#[derive(Subdiagnostic)] +pub enum InvalidNanComparisonsSuggestion { + #[multipart_suggestion( + lint_suggestion, + style = "verbose", + applicability = "machine-applicable" + )] + Spanful { + #[suggestion_part(code = "!")] + neg: Option, + #[suggestion_part(code = ".is_nan()")] + float: Span, + #[suggestion_part(code = "")] + nan_plus_binop: Span, + }, + #[help(lint_suggestion)] + Spanless, +} + pub struct ImproperCTypes<'a> { pub ty: Ty<'a>, pub desc: &'a str, @@ -1461,8 +1591,29 @@ pub struct UnusedOp<'a> { pub op: &'a str, #[label] pub label: Span, - #[suggestion(style = "verbose", code = "let _ = ", applicability = "maybe-incorrect")] - pub suggestion: Span, + #[subdiagnostic] + pub suggestion: UnusedOpSuggestion, +} + +#[derive(Subdiagnostic)] +pub enum UnusedOpSuggestion { + #[suggestion( + lint_suggestion, + style = "verbose", + code = "let _ = ", + applicability = "maybe-incorrect" + )] + NormalExpr { + #[primary_span] + span: Span, + }, + #[multipart_suggestion(lint_suggestion, style = "verbose", applicability = "maybe-incorrect")] + BlockTailExpr { + #[suggestion_part(code = "let _ = ")] + before_span: Span, + #[suggestion_part(code = ";")] + after_span: Span, + }, } #[derive(LintDiagnostic)] @@ -1505,15 +1656,25 @@ pub struct UnusedDef<'a, 'b> { } #[derive(Subdiagnostic)] -#[suggestion( - lint_suggestion, - style = "verbose", - code = "let _ = ", - applicability = "maybe-incorrect" -)] -pub struct UnusedDefSuggestion { - #[primary_span] - pub span: Span, + +pub enum UnusedDefSuggestion { + #[suggestion( + lint_suggestion, + style = "verbose", + code = "let _ = ", + applicability = "maybe-incorrect" + )] + NormalExpr { + #[primary_span] + span: Span, + }, + #[multipart_suggestion(lint_suggestion, style = "verbose", applicability = "maybe-incorrect")] + BlockTailExpr { + #[suggestion_part(code = "let _ = ")] + before_span: Span, + #[suggestion_part(code = ";")] + after_span: Span, + }, } // Needed because of def_path_str @@ -1527,7 +1688,7 @@ impl<'a> DecorateLint<'a, ()> for UnusedDef<'_, '_> { diag.set_arg("def", self.cx.tcx.def_path_str(self.def_id)); // check for #[must_use = "..."] if let Some(note) = self.note { - diag.note(note.as_str()); + diag.note(note.to_string()); } if let Some(sugg) = self.suggestion { diag.subdiagnostic(sugg); diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs index 4c25d94a1f372..5b63b19c53c8a 100644 --- a/compiler/rustc_lint/src/methods.rs +++ b/compiler/rustc_lint/src/methods.rs @@ -53,9 +53,9 @@ fn lint_cstring_as_ptr( unwrap: &rustc_hir::Expr<'_>, ) { let source_type = cx.typeck_results().expr_ty(source); - if let ty::Adt(def, substs) = source_type.kind() { + if let ty::Adt(def, args) = source_type.kind() { if cx.tcx.is_diagnostic_item(sym::Result, def.did()) { - if let ty::Adt(adt, _) = substs.type_at(0).kind() { + if let ty::Adt(adt, _) = args.type_at(0).kind() { if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) { cx.emit_spanned_lint( TEMPORARY_CSTRING_AS_PTR, diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs index c2ed0e19f4011..84558ee1f020c 100644 --- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs +++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs @@ -10,6 +10,7 @@ declare_lint! { /// ### Example /// /// ```rust + /// #![feature(multiple_supertrait_upcastable)] /// trait A {} /// trait B {} /// @@ -45,7 +46,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable { .super_predicates_of(def_id) .predicates .into_iter() - .filter_map(|(pred, _)| pred.to_opt_poly_trait_pred()); + .filter_map(|(pred, _)| pred.as_trait_clause()); if direct_super_traits_iter.count() > 1 { cx.emit_spanned_lint( MULTIPLE_SUPERTRAIT_UPCASTABLE, diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs index 4af879b4e9128..62bb8c2c67d2b 100644 --- a/compiler/rustc_lint/src/non_ascii_idents.rs +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -222,6 +222,7 @@ impl EarlyLintPass for NonAsciiIdents { existing_sym: *existing_symbol, sym: symbol, label: *existing_span, + main_label: sp, }, ); } diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index 79253cbc8b43c..145de49483519 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -533,6 +533,10 @@ impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals { fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) { if let GenericParamKind::Const { .. } = param.kind { + // `rustc_host` params are explicitly allowed to be lowercase. + if cx.tcx.has_attr(param.def_id, sym::rustc_host) { + return; + } NonUpperCaseGlobals::check_upper_case(cx, "const parameter", ¶m.name.ident()); } } diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs index d054966459d85..bc0b9d6d81871 100644 --- a/compiler/rustc_lint/src/noop_method_call.rs +++ b/compiler/rustc_lint/src/noop_method_call.rs @@ -1,5 +1,7 @@ use crate::context::LintContext; -use crate::lints::{NoopMethodCallDiag, SuspiciousDoubleRefDiag}; +use crate::lints::{ + NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag, +}; use crate::LateContext; use crate::LateLintPass; use rustc_hir::def::DefKind; @@ -16,7 +18,6 @@ declare_lint! { /// /// ```rust /// # #![allow(unused)] - /// #![warn(noop_method_call)] /// struct Foo; /// let foo = &Foo; /// let clone: &Foo = foo.clone(); @@ -32,7 +33,7 @@ declare_lint! { /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything /// as references are copy. This lint detects these calls and warns the user about them. pub NOOP_METHOD_CALL, - Allow, + Warn, "detects the use of well-known noop methods" } @@ -76,39 +77,28 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow` // traits and ignore any other method call. - let did = match cx.typeck_results().type_dependent_def(expr.hir_id) { - // Verify we are dealing with a method/associated function. - Some((DefKind::AssocFn, did)) => match cx.tcx.trait_of_item(did) { - // Check that we're dealing with a trait method for one of the traits we care about. - Some(trait_id) - if matches!( - cx.tcx.get_diagnostic_name(trait_id), - Some(sym::Borrow | sym::Clone | sym::Deref) - ) => - { - did - } - _ => return, - }, - _ => return, + + let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id) + else { + return; + }; + + let Some(trait_id) = cx.tcx.trait_of_item(did) else { return }; + + let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return }; + + if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) { + return; }; - let substs = cx + + let args = cx .tcx - .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_substs(expr.hir_id)); + .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_args(expr.hir_id)); // Resolve the trait method instance. - let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, substs) else { - return - }; + let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, cx.param_env, did, args) else { return }; // (Re)check that it implements the noop diagnostic. let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return }; - let op = match name { - sym::noop_method_borrow => "borrow", - sym::noop_method_clone => "clone", - sym::noop_method_deref => "deref", - _ => return, - }; - let receiver_ty = cx.typeck_results().expr_ty(receiver); let expr_ty = cx.typeck_results().expr_ty_adjusted(expr); let arg_adjustments = cx.typeck_results().expr_adjustments(receiver); @@ -122,18 +112,31 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { let expr_span = expr.span; let span = expr_span.with_lo(receiver.span.hi()); + let orig_ty = expr_ty.peel_refs(); + if receiver_ty == expr_ty { cx.emit_spanned_lint( NOOP_METHOD_CALL, span, - NoopMethodCallDiag { method: call.ident.name, receiver_ty, label: span }, + NoopMethodCallDiag { method: call.ident.name, orig_ty, trait_, label: span }, ); } else { - cx.emit_spanned_lint( - SUSPICIOUS_DOUBLE_REF_OP, - span, - SuspiciousDoubleRefDiag { call: call.ident.name, ty: expr_ty, op }, - ) + match name { + // If `type_of(x) == T` and `x.borrow()` is used to get `&T`, + // then that should be allowed + sym::noop_method_borrow => return, + sym::noop_method_clone => cx.emit_spanned_lint( + SUSPICIOUS_DOUBLE_REF_OP, + span, + SuspiciousDoubleRefCloneDiag { ty: expr_ty }, + ), + sym::noop_method_deref => cx.emit_spanned_lint( + SUSPICIOUS_DOUBLE_REF_OP, + span, + SuspiciousDoubleRefDerefDiag { ty: expr_ty }, + ), + _ => return, + } } } } diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index 15715c8fca039..79b0b32bef248 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -68,17 +68,21 @@ declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]); impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { - let hir::ItemKind::OpaqueTy(opaque) = &item.kind else { return; }; + let hir::ItemKind::OpaqueTy(opaque) = &item.kind else { + return; + }; let def_id = item.owner_id.def_id.to_def_id(); let infcx = &cx.tcx.infer_ctxt().build(); // For every projection predicate in the opaque type's explicit bounds, // check that the type that we're assigning actually satisfies the bounds // of the associated type. - for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).subst_identity_iter_copied() { + for (pred, pred_span) in + cx.tcx.explicit_item_bounds(def_id).instantiate_identity_iter_copied() + { // Liberate bound regions in the predicate since we // don't actually care about lifetimes in this check. let predicate = cx.tcx.liberate_late_bound_regions(def_id, pred.kind()); - let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = predicate else { + let ty::ClauseKind::Projection(proj) = predicate else { continue; }; // Only check types, since those are the only things that may @@ -97,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { } let proj_ty = - cx.tcx.mk_projection(proj.projection_ty.def_id, proj.projection_ty.substs); + Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.args); // For every instance of the projection type in the bounds, // replace them with the term we're assigning to the associated // type in our opaque type. @@ -113,10 +117,15 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { for (assoc_pred, assoc_pred_span) in cx .tcx .explicit_item_bounds(proj.projection_ty.def_id) - .subst_iter_copied(cx.tcx, &proj.projection_ty.substs) + .iter_instantiated_copied(cx.tcx, &proj.projection_ty.args) { let assoc_pred = assoc_pred.fold_with(proj_replacer); - let Ok(assoc_pred) = traits::fully_normalize(infcx, traits::ObligationCause::dummy(), cx.param_env, assoc_pred) else { + let Ok(assoc_pred) = traits::fully_normalize( + infcx, + traits::ObligationCause::dummy(), + cx.param_env, + assoc_pred, + ) else { continue; }; // If that predicate doesn't hold modulo regions (but passed during type-check), @@ -133,7 +142,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { let add_bound = match (proj_term.kind(), assoc_pred.kind().skip_binder()) { ( ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }), - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)), + ty::ClauseKind::Trait(trait_pred), ) => Some(AddBound { suggest_span: cx.tcx.def_span(*def_id).shrink_to_hi(), trait_ref: trait_pred.print_modifiers_and_trait_path(), @@ -144,9 +153,10 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { OPAQUE_HIDDEN_INFERRED_BOUND, pred_span, OpaqueHiddenInferredBoundLint { - ty: cx.tcx.mk_opaque( + ty: Ty::new_opaque( + cx.tcx, def_id, - ty::InternalSubsts::identity_for_item(cx.tcx, def_id), + ty::GenericArgs::identity_for_item(cx.tcx, def_id), ), proj_ty: proj_term, assoc_pred_span, diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs index 2bb2a3aab5527..cad2cd7fa4c8b 100644 --- a/compiler/rustc_lint/src/pass_by_value.rs +++ b/compiler/rustc_lint/src/pass_by_value.rs @@ -50,9 +50,9 @@ fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option { - if let ty::Adt(adt, substs) = cx.tcx.type_of(did).subst_identity().kind() { + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() { if cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) { - return Some(cx.tcx.def_path_str_with_substs(adt.did(), substs)); + return Some(cx.tcx.def_path_str_with_args(adt.did(), args)); } } } diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs new file mode 100644 index 0000000000000..02aff91032fd9 --- /dev/null +++ b/compiler/rustc_lint/src/ptr_nulls.rs @@ -0,0 +1,146 @@ +use crate::{lints::PtrNullChecksDiag, LateContext, LateLintPass, LintContext}; +use rustc_ast::LitKind; +use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; + +declare_lint! { + /// The `useless_ptr_null_checks` lint checks for useless null checks against pointers + /// obtained from non-null types. + /// + /// ### Example + /// + /// ```rust + /// # fn test() {} + /// let fn_ptr: fn() = /* somehow obtained nullable function pointer */ + /// # test; + /// + /// if (fn_ptr as *const ()).is_null() { /* ... */ } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Function pointers and references are assumed to be non-null, checking them for null + /// will always return false. + USELESS_PTR_NULL_CHECKS, + Warn, + "useless checking of non-null-typed pointer" +} + +declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]); + +/// This function detects and returns the original expression from a series of consecutive casts, +/// ie. `(my_fn as *const _ as *mut _).cast_mut()` would return the expression for `my_fn`. +fn ptr_cast_chain<'a>(cx: &'a LateContext<'_>, mut e: &'a Expr<'a>) -> Option<&'a Expr<'a>> { + let mut had_at_least_one_cast = false; + loop { + e = e.peel_blocks(); + e = if let ExprKind::Cast(expr, t) = e.kind + && let TyKind::Ptr(_) = t.kind { + had_at_least_one_cast = true; + expr + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_cast | sym::ptr_cast_mut)) { + had_at_least_one_cast = true; + expr + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_from_ref | sym::ptr_from_mut)) { + had_at_least_one_cast = true; + arg + } else if had_at_least_one_cast { + return Some(e); + } else { + return None; + }; + } +} + +fn incorrect_check<'a>(cx: &LateContext<'a>, expr: &Expr<'_>) -> Option> { + let expr = ptr_cast_chain(cx, expr)?; + + let orig_ty = cx.typeck_results().expr_ty(expr); + if orig_ty.is_fn() { + Some(PtrNullChecksDiag::FnPtr { orig_ty, label: expr.span }) + } else if orig_ty.is_ref() { + Some(PtrNullChecksDiag::Ref { orig_ty, label: expr.span }) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match expr.kind { + // Catching: + // <* >::is_null(fn_ptr as * ) + ExprKind::Call(path, [arg]) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_const_is_null | sym::ptr_is_null) + ) + && let Some(diag) = incorrect_check(cx, arg) => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + // Catching: + // (fn_ptr as * ).is_null() + ExprKind::MethodCall(_, receiver, _, _) + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_const_is_null | sym::ptr_is_null) + ) + && let Some(diag) = incorrect_check(cx, receiver) => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => { + let to_check: &Expr<'_>; + let diag: PtrNullChecksDiag<'_>; + if let Some(ddiag) = incorrect_check(cx, left) { + to_check = right; + diag = ddiag; + } else if let Some(ddiag) = incorrect_check(cx, right) { + to_check = left; + diag = ddiag; + } else { + return; + } + + match to_check.kind { + // Catching: + // (fn_ptr as * ) == (0 as ) + ExprKind::Cast(cast_expr, _) + if let ExprKind::Lit(spanned) = cast_expr.kind + && let LitKind::Int(v, _) = spanned.node && v == 0 => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + }, + + // Catching: + // (fn_ptr as * ) == std::ptr::null() + ExprKind::Call(path, []) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) => + { + cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + }, + + _ => {}, + } + } + _ => {} + } + } +} diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs new file mode 100644 index 0000000000000..2577cabb3f0fb --- /dev/null +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -0,0 +1,169 @@ +use rustc_ast::Mutability; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, QPath, StmtKind, UnOp}; +use rustc_middle::ty::{self, TypeAndMut}; +use rustc_span::{sym, Span}; + +use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `invalid_reference_casting` lint checks for casts of `&T` to `&mut T` + /// without using interior mutability. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// fn x(r: &i32) { + /// unsafe { + /// *(r as *const i32 as *mut i32) += 1; + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Casting `&T` to `&mut T` without using interior mutability is undefined behavior, + /// as it's a violation of Rust reference aliasing requirements. + /// + /// `UnsafeCell` is the only way to obtain aliasable data that is considered + /// mutable. + INVALID_REFERENCE_CASTING, + Deny, + "casts of `&T` to `&mut T` without interior mutability" +} + +#[derive(Default)] +pub struct InvalidReferenceCasting { + casted: FxHashMap, +} + +impl_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]); + +impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx rustc_hir::Stmt<'tcx>) { + let StmtKind::Local(local) = stmt.kind else { + return; + }; + let Local { init: Some(init), els: None, .. } = local else { + return; + }; + + if is_cast_from_const_to_mut(cx, init) { + self.casted.insert(local.pat.hir_id, init.span); + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + // &mut + let inner = if let ExprKind::AddrOf(_, Mutability::Mut, expr) = expr.kind { + expr + // = ... + } else if let ExprKind::Assign(expr, _, _) = expr.kind { + expr + // += ... + } else if let ExprKind::AssignOp(_, expr, _) = expr.kind { + expr + } else { + return; + }; + + let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else { + return; + }; + + let orig_cast = if is_cast_from_const_to_mut(cx, e) { + None + } else if let ExprKind::Path(QPath::Resolved(_, path)) = e.kind + && let Res::Local(hir_id) = &path.res + && let Some(orig_cast) = self.casted.get(hir_id) { + Some(*orig_cast) + } else { + return; + }; + + cx.emit_spanned_lint( + INVALID_REFERENCE_CASTING, + expr.span, + if matches!(expr.kind, ExprKind::AddrOf(..)) { + InvalidReferenceCastingDiag::BorrowAsMut { orig_cast } + } else { + InvalidReferenceCastingDiag::AssignToRef { orig_cast } + }, + ); + } +} + +fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { + let e = e.peel_blocks(); + + fn from_casts<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + // as *mut ... + let mut e = if let ExprKind::Cast(e, t) = e.kind + && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() { + e + // .cast_mut() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && cx.tcx.is_diagnostic_item(sym::ptr_cast_mut, def_id) { + expr + } else { + return None; + }; + + let mut had_at_least_one_cast = false; + loop { + e = e.peel_blocks(); + // as *mut/const ... or as + e = if let ExprKind::Cast(expr, t) = e.kind + && matches!(cx.typeck_results().node_type(t.hir_id).kind(), ty::RawPtr(_) | ty::Uint(_)) { + had_at_least_one_cast = true; + expr + // .cast(), .cast_mut() or .cast_const() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const) + ) + { + had_at_least_one_cast = true; + expr + // ptr::from_ref() + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) { + return Some(arg); + } else if had_at_least_one_cast { + return Some(e); + } else { + return None; + }; + } + } + + fn from_transmute<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + ) -> Option<&'tcx Expr<'tcx>> { + // mem::transmute::<_, *mut _>() + if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::transmute, def_id) + && let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(e.hir_id).kind() { + Some(arg) + } else { + None + } + } + + let Some(e) = from_casts(cx, e).or_else(|| from_transmute(cx, e)) else { + return false; + }; + + let e = e.peel_blocks(); + matches!(cx.typeck_results().node_type(e.hir_id).kind(), ty::Ref(_, _, Mutability::Not)) +} diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs index 7ea1a138b7e60..56508a2a6ccd8 100644 --- a/compiler/rustc_lint/src/traits.rs +++ b/compiler/rustc_lint/src/traits.rs @@ -87,13 +87,12 @@ declare_lint_pass!( impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { - use rustc_middle::ty::Clause; - use rustc_middle::ty::PredicateKind::*; + use rustc_middle::ty::ClauseKind; let predicates = cx.tcx.explicit_predicates_of(item.owner_id); for &(predicate, span) in predicates.predicates { - let Clause(Clause::Trait(trait_predicate)) = predicate.kind().skip_binder() else { - continue + let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() else { + continue; }; let def_id = trait_predicate.trait_ref.def_id; if cx.tcx.lang_items().drop_trait() == Some(def_id) { @@ -101,9 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { if trait_predicate.trait_ref.self_ty().is_impl_trait() { continue; } - let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { - return - }; + let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; cx.emit_spanned_lint( DROP_BOUNDS, span, @@ -114,15 +111,11 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { } fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) { - let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else { - return - }; + let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else { return }; for bound in &bounds[..] { let def_id = bound.trait_ref.trait_def_id(); if cx.tcx.lang_items().drop_trait() == def_id { - let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { - return - }; + let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; cx.emit_spanned_lint(DYN_DROP, bound.span, DropGlue { tcx: cx.tcx, def_id }); } } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 4bf4fda8292b6..1ba746eddebd4 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -2,7 +2,8 @@ use crate::{ fluent_generated as fluent, lints::{ AtomicOrderingFence, AtomicOrderingLoad, AtomicOrderingStore, ImproperCTypes, - InvalidAtomicOrderingDiag, OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign, + InvalidAtomicOrderingDiag, InvalidNanComparisons, InvalidNanComparisonsSuggestion, + OnlyCastu8ToChar, OverflowingBinHex, OverflowingBinHexSign, OverflowingBinHexSignBitSub, OverflowingBinHexSub, OverflowingInt, OverflowingIntHelp, OverflowingLiteral, OverflowingUInt, RangeEndpointOutOfRange, UnusedComparisons, UseInclusiveRange, VariantSizeDifferencesDiag, @@ -16,7 +17,7 @@ use rustc_errors::DiagnosticMessage; use rustc_hir as hir; use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{ self, AdtKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, }; @@ -113,13 +114,35 @@ declare_lint! { "detects enums with widely varying variant sizes" } +declare_lint! { + /// The `invalid_nan_comparisons` lint checks comparison with `f32::NAN` or `f64::NAN` + /// as one of the operand. + /// + /// ### Example + /// + /// ```rust + /// let a = 2.3f32; + /// if a == f32::NAN {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// NaN does not compare meaningfully to anything – not + /// even itself – so those comparisons are always false. + INVALID_NAN_COMPARISONS, + Warn, + "detects invalid floating point NaN comparisons" +} + #[derive(Copy, Clone)] pub struct TypeLimits { /// Id of the last visited negated expression negated_expr_id: Option, } -impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]); +impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS, INVALID_NAN_COMPARISONS]); impl TypeLimits { pub fn new() -> TypeLimits { @@ -275,10 +298,50 @@ fn report_bin_hex_error( } }, ); + let sign_bit_sub = (!negative) + .then(|| { + let ty::Int(int_ty) = cx.typeck_results().node_type(expr.hir_id).kind() else { + return None; + }; + + let Some(bit_width) = int_ty.bit_width() else { + return None; // isize case + }; + + // Skip if sign bit is not set + if (val & (1 << (bit_width - 1))) == 0 { + return None; + } + + let lit_no_suffix = + if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { + repr_str.split_at(pos).0 + } else { + &repr_str + }; + + Some(OverflowingBinHexSignBitSub { + span: expr.span, + lit_no_suffix, + negative_val: actually.clone(), + int_ty: int_ty.name_str(), + uint_ty: int_ty.to_unsigned().name_str(), + }) + }) + .flatten(); + cx.emit_spanned_lint( OVERFLOWING_LITERALS, expr.span, - OverflowingBinHex { ty: t, lit: repr_str.clone(), dec: val, actually, sign, sub }, + OverflowingBinHex { + ty: t, + lit: repr_str.clone(), + dec: val, + actually, + sign, + sub, + sign_bit_sub, + }, ) } @@ -486,6 +549,75 @@ fn lint_literal<'tcx>( } } +fn lint_nan<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx hir::Expr<'tcx>, + binop: hir::BinOp, + l: &'tcx hir::Expr<'tcx>, + r: &'tcx hir::Expr<'tcx>, +) { + fn is_nan(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + let expr = expr.peel_blocks().peel_borrows(); + match expr.kind { + ExprKind::Path(qpath) => { + let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id() + else { + return false; + }; + + matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::f32_nan | sym::f64_nan)) + } + _ => false, + } + } + + fn eq_ne( + cx: &LateContext<'_>, + e: &hir::Expr<'_>, + l: &hir::Expr<'_>, + r: &hir::Expr<'_>, + f: impl FnOnce(Span, Span) -> InvalidNanComparisonsSuggestion, + ) -> InvalidNanComparisons { + // FIXME(#72505): This suggestion can be restored if `f{32,64}::is_nan` is made const. + let suggestion = (!cx.tcx.hir().is_inside_const_context(e.hir_id)).then(|| { + if let Some(l_span) = l.span.find_ancestor_inside(e.span) && + let Some(r_span) = r.span.find_ancestor_inside(e.span) + { + f(l_span, r_span) + } else { + InvalidNanComparisonsSuggestion::Spanless + } + }); + + InvalidNanComparisons::EqNe { suggestion } + } + + let lint = match binop.node { + hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => { + eq_ne(cx, 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()), + }) + } + hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => { + eq_ne(cx, 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()), + }) + } + hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge + if is_nan(cx, l) || is_nan(cx, r) => + { + InvalidNanComparisons::LtLeGtGe + } + _ => return, + }; + + cx.emit_spanned_lint(INVALID_NAN_COMPARISONS, e.span, lint); +} + impl<'tcx> LateLintPass<'tcx> for TypeLimits { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) { match e.kind { @@ -496,8 +628,12 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { } } hir::ExprKind::Binary(binop, ref l, ref r) => { - if is_comparison(binop) && !check_limits(cx, binop, &l, &r) { - cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons); + if is_comparison(binop) { + if !check_limits(cx, binop, &l, &r) { + cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons); + } else { + lint_nan(cx, e, binop, l, r); + } } } hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit), @@ -676,20 +812,19 @@ pub fn transparent_newtype_field<'a, 'tcx>( ) -> Option<&'a ty::FieldDef> { let param_env = tcx.param_env(variant.def_id); variant.fields.iter().find(|field| { - let field_ty = tcx.type_of(field.did).subst_identity(); + let field_ty = tcx.type_of(field.did).instantiate_identity(); let is_zst = tcx.layout_of(param_env.and(field_ty)).is_ok_and(|layout| layout.is_zst()); !is_zst }) } /// Is type known to be non-null? -fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { - let tcx = cx.tcx; +fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { match ty.kind() { ty::FnPtr(_) => true, ty::Ref(..) => true, ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, - ty::Adt(def, substs) if def.repr().transparent() && !def.is_union() => { + ty::Adt(def, args) if def.repr().transparent() && !def.is_union() => { let marked_non_null = nonnull_optimization_guaranteed(tcx, *def); if marked_non_null { @@ -703,8 +838,8 @@ fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKi def.variants() .iter() - .filter_map(|variant| transparent_newtype_field(cx.tcx, variant)) - .any(|field| ty_is_known_nonnull(cx, field.ty(tcx, substs), mode)) + .filter_map(|variant| transparent_newtype_field(tcx, variant)) + .any(|field| ty_is_known_nonnull(tcx, field.ty(tcx, args), mode)) } _ => false, } @@ -712,15 +847,12 @@ fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKi /// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. /// If the type passed in was not scalar, returns None. -fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { - let tcx = cx.tcx; +fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { Some(match *ty.kind() { - ty::Adt(field_def, field_substs) => { + ty::Adt(field_def, field_args) => { let inner_field_ty = { - let mut first_non_zst_ty = field_def - .variants() - .iter() - .filter_map(|v| transparent_newtype_field(cx.tcx, v)); + let mut first_non_zst_ty = + field_def.variants().iter().filter_map(|v| transparent_newtype_field(tcx, v)); debug_assert_eq!( first_non_zst_ty.clone().count(), 1, @@ -729,16 +861,16 @@ fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option tcx.mk_mach_int(ty), - ty::Uint(ty) => tcx.mk_mach_uint(ty), - ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut), + ty::Int(ty) => Ty::new_int(tcx, ty), + ty::Uint(ty) => Ty::new_uint(tcx, ty), + ty::RawPtr(ty_mut) => Ty::new_ptr(tcx, ty_mut), // As these types are always non-null, the nullable equivalent of // Option of these types are their raw pointer counterparts. - ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }), + ty::Ref(_region, ty, mutbl) => Ty::new_ptr(tcx, ty::TypeAndMut { ty, mutbl }), ty::FnPtr(..) => { // There is no nullable equivalent for Rust's function pointers -- you // must use an Option _> to represent it. @@ -763,43 +895,44 @@ fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option( - cx: &LateContext<'tcx>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>, ckind: CItemKind, ) -> Option> { - debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty); - if let ty::Adt(ty_def, substs) = ty.kind() { + debug!("is_repr_nullable_ptr(tcx, ty = {:?})", ty); + if let ty::Adt(ty_def, args) = ty.kind() { let field_ty = match &ty_def.variants().raw[..] { [var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) { - ([], [field]) | ([field], []) => field.ty(cx.tcx, substs), + ([], [field]) | ([field], []) => field.ty(tcx, args), _ => return None, }, _ => return None, }; - if !ty_is_known_nonnull(cx, field_ty, ckind) { + if !ty_is_known_nonnull(tcx, field_ty, ckind) { return None; } // At this point, the field's type is known to be nonnull and the parent enum is Option-like. // If the computed size for the field and the enum are different, the nonnull optimization isn't // being applied (and we've got a problem somewhere). - let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap(); + let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, param_env).unwrap(); if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { bug!("improper_ctypes: Option nonnull optimization not applied?"); } // Return the nullable type this Option-like enum can be safely represented with. - let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi; + let field_ty_abi = &tcx.layout_of(param_env.and(field_ty)).unwrap().abi; if let Abi::Scalar(field_ty_scalar) = field_ty_abi { - match field_ty_scalar.valid_range(cx) { + match field_ty_scalar.valid_range(&tcx) { WrappingRange { start: 0, end } - if end == field_ty_scalar.size(&cx.tcx).unsigned_int_max() - 1 => + if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 => { - return Some(get_nullable_type(cx, field_ty).unwrap()); + return Some(get_nullable_type(tcx, field_ty).unwrap()); } WrappingRange { start: 1, .. } => { - return Some(get_nullable_type(cx, field_ty).unwrap()); + return Some(get_nullable_type(tcx, field_ty).unwrap()); } WrappingRange { start, end } => { unreachable!("Unhandled start and end range: ({}, {})", start, end) @@ -831,15 +964,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { &self, cache: &mut FxHashSet>, field: &ty::FieldDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { - let field_ty = field.ty(self.cx.tcx, substs); - if field_ty.has_opaque_types() { - self.check_type_for_ffi(cache, field_ty) - } else { - let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty); - self.check_type_for_ffi(cache, field_ty) - } + let field_ty = field.ty(self.cx.tcx, args); + let field_ty = self + .cx + .tcx + .try_normalize_erasing_regions(self.cx.param_env, field_ty) + .unwrap_or(field_ty); + self.check_type_for_ffi(cache, field_ty) } /// Checks if the given `VariantDef`'s field types are "ffi-safe". @@ -849,43 +982,47 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty: Ty<'tcx>, def: ty::AdtDef<'tcx>, variant: &ty::VariantDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> FfiResult<'tcx> { use FfiResult::*; - let transparent_safety = def.repr().transparent().then(|| { - // Can assume that at most one field is not a ZST, so only check - // that field's type for FFI-safety. + let transparent_with_all_zst_fields = if def.repr().transparent() { if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { - return self.check_field_type_for_ffi(cache, field, substs); + // Transparent newtypes have at most one non-ZST field which needs to be checked.. + match self.check_field_type_for_ffi(cache, field, args) { + FfiUnsafe { ty, .. } if ty.is_unit() => (), + r => return r, + } + + false } else { - // All fields are ZSTs; this means that the type should behave - // like (), which is FFI-unsafe... except if all fields are PhantomData, - // which is tested for below - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all + // `PhantomData`). + true } - }); - // We can't completely trust repr(C) markings; make sure the fields are - // actually safe. + } else { + false + }; + + // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. let mut all_phantom = !variant.fields.is_empty(); for field in &variant.fields { - match self.check_field_type_for_ffi(cache, &field, substs) { - FfiSafe => { - all_phantom = false; - } - FfiPhantom(..) if !def.repr().transparent() && def.is_enum() => { - return FfiUnsafe { - ty, - reason: fluent::lint_improper_ctypes_enum_phantomdata, - help: None, - }; - } - FfiPhantom(..) => {} - r => return transparent_safety.unwrap_or(r), + all_phantom &= match self.check_field_type_for_ffi(cache, &field, args) { + FfiSafe => false, + // `()` fields are FFI-safe! + FfiUnsafe { ty, .. } if ty.is_unit() => false, + FfiPhantom(..) => true, + r @ FfiUnsafe { .. } => return r, } } - if all_phantom { FfiPhantom(ty) } else { transparent_safety.unwrap_or(FfiSafe) } + if all_phantom { + FfiPhantom(ty) + } else if transparent_with_all_zst_fields { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + } else { + FfiSafe + } } /// Checks if the given type is "ffi-safe" (has a stable, well-defined @@ -904,7 +1041,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match *ty.kind() { - ty::Adt(def, substs) => { + ty::Adt(def, args) => { if def.is_box() && matches!(self.mode, CItemKind::Definition) { if ty.boxed_ty().is_sized(tcx, self.cx.param_env) { return FfiSafe; @@ -967,7 +1104,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs) + self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), args) } AdtKind::Enum => { if def.variants().is_empty() { @@ -980,7 +1117,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none() { // Special-case types like `Option`. - if repr_nullable_ptr(self.cx, ty, self.mode).is_none() { + if repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode) + .is_none() + { return FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_enum_repr_reason, @@ -1008,7 +1147,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - match self.check_variant_for_ffi(cache, ty, def, variant, substs) { + match self.check_variant_for_ffi(cache, ty, def, variant, args) { FfiSafe => (), r => return r, } @@ -1088,25 +1227,19 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } let sig = tcx.erase_late_bound_regions(sig); - if !sig.output().is_unit() { - let r = self.check_type_for_ffi(cache, sig.output()); - match r { - FfiSafe => {} - _ => { - return r; - } - } - } for arg in sig.inputs() { - let r = self.check_type_for_ffi(cache, *arg); - match r { + match self.check_type_for_ffi(cache, *arg) { FfiSafe => {} - _ => { - return r; - } + r => return r, } } - FfiSafe + + let ret_ty = sig.output(); + if ret_ty.is_unit() { + return FfiSafe; + } + + self.check_type_for_ffi(cache, ret_ty) } ty::Foreign(..) => FfiSafe, @@ -1126,7 +1259,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } ty::Param(..) - | ty::Alias(ty::Projection | ty::Inherent, ..) + | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) | ty::Infer(..) | ty::Bound(..) | ty::Error(_) @@ -1188,7 +1321,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { if let Some(ty) = self .cx .tcx - .normalize_erasing_regions(self.cx.param_env, ty) + .try_normalize_erasing_regions(self.cx.param_env, ty) + .unwrap_or(ty) .visit_with(&mut ProhibitOpaqueTypes) .break_value() { @@ -1206,16 +1340,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { is_static: bool, is_return_type: bool, ) { - // We have to check for opaque types before `normalize_erasing_regions`, - // which will replace opaque types with their underlying concrete type. if self.check_for_opaque_ty(sp, ty) { // We've already emitted an error due to an opaque type. return; } - // it is only OK to use this function because extern fns cannot have - // any generic types right now: - let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); + let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.param_env, ty).unwrap_or(ty); // C doesn't really support passing arrays by value - the only way to pass an array by value // is through a struct. So, first test that the top level isn't an array, and then @@ -1225,7 +1355,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } // Don't report FFI errors for unit return types. This check exists here, and not in - // `check_foreign_fn` (where it would make more sense) so that normalization has definitely + // the caller (where it would make more sense) so that normalization has definitely // happened. if is_return_type && ty.is_unit() { return; @@ -1241,17 +1371,36 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { None, ); } - // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic - // argument, which after substitution, is `()`, then this branch can be hit. - FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {} FfiResult::FfiUnsafe { ty, reason, help } => { self.emit_ffi_unsafe_type_lint(ty, sp, reason, help); } } } - fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &hir::FnDecl<'_>) { - let sig = self.cx.tcx.fn_sig(def_id).subst_identity(); + /// Check if a function's argument types and result type are "ffi-safe". + /// + /// For a external ABI function, argument types and the result type are walked to find fn-ptr + /// types that have external ABIs, as these still need checked. + fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.erase_late_bound_regions(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { + self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false); + } + } + + if let hir::FnRetTy::Return(ref ret_hir) = decl.output { + for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) { + self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true); + } + } + } + + /// Check if a function's argument types and result type are "ffi-safe". + fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); let sig = self.cx.tcx.erase_late_bound_regions(sig); for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { @@ -1259,13 +1408,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } if let hir::FnRetTy::Return(ref ret_hir) = decl.output { - let ret_ty = sig.output(); - self.check_type_for_ffi_and_report_errors(ret_hir.span, ret_ty, false, true); + self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true); } } fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) { - let ty = self.cx.tcx.type_of(id).subst_identity(); + let ty = self.cx.tcx.type_of(id).instantiate_identity(); self.check_type_for_ffi_and_report_errors(span, ty, true, false); } @@ -1275,28 +1423,131 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic | SpecAbi::PlatformIntrinsic ) } + + /// Find any fn-ptr types with external ABIs in `ty`. + /// + /// For example, `Option` returns `extern "C" fn()` + fn find_fn_ptr_ty_with_external_abi( + &self, + hir_ty: &hir::Ty<'tcx>, + ty: Ty<'tcx>, + ) -> Vec<(Ty<'tcx>, Span)> { + struct FnPtrFinder<'parent, 'a, 'tcx> { + visitor: &'parent ImproperCTypesVisitor<'a, 'tcx>, + spans: Vec, + tys: Vec>, + } + + impl<'parent, 'a, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'parent, 'a, 'tcx> { + fn visit_ty(&mut self, ty: &'_ hir::Ty<'_>) { + debug!(?ty); + if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind + && !self.visitor.is_internal_abi(*abi) + { + self.spans.push(ty.span); + } + + hir::intravisit::walk_ty(self, ty) + } + } + + impl<'vis, 'a, 'tcx> ty::visit::TypeVisitor> for FnPtrFinder<'vis, 'a, 'tcx> { + type BreakTy = Ty<'tcx>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { + if let ty::FnPtr(sig) = ty.kind() && !self.visitor.is_internal_abi(sig.abi()) { + self.tys.push(ty); + } + + ty.super_visit_with(self) + } + } + + let mut visitor = FnPtrFinder { visitor: &*self, spans: Vec::new(), tys: Vec::new() }; + ty.visit_with(&mut visitor); + hir::intravisit::Visitor::visit_ty(&mut visitor, hir_ty); + + iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect() + } } impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { - fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) { + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); - if !vis.is_internal_abi(abi) { - match it.kind { - hir::ForeignItemKind::Fn(ref decl, _, _) => { - vis.check_foreign_fn(it.owner_id.def_id, decl); - } - hir::ForeignItemKind::Static(ref ty, _) => { - vis.check_foreign_static(it.owner_id, ty.span); - } - hir::ForeignItemKind::Type => (), + match it.kind { + hir::ForeignItemKind::Fn(ref decl, _, _) if !vis.is_internal_abi(abi) => { + vis.check_foreign_fn(it.owner_id.def_id, decl); } + hir::ForeignItemKind::Static(ref ty, _) if !vis.is_internal_abi(abi) => { + vis.check_foreign_static(it.owner_id, ty.span); + } + hir::ForeignItemKind::Fn(ref decl, _, _) => vis.check_fn(it.owner_id.def_id, decl), + hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), } } } +impl ImproperCTypesDefinitions { + fn check_ty_maybe_containing_foreign_fnptr<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + hir_ty: &'tcx hir::Ty<'_>, + ty: Ty<'tcx>, + ) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; + for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) { + vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false); + } + } +} + +/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in +/// `extern "C" { }` blocks): +/// +/// - `extern "" fn` definitions are checked in the same way as the +/// `ImproperCtypesDeclarations` visitor checks functions if `` is external (e.g. "C"). +/// - All other items which contain types (e.g. other functions, struct definitions, etc) are +/// checked for extern fn-ptrs with external ABIs. impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + match item.kind { + hir::ItemKind::Static(ty, ..) + | hir::ItemKind::Const(ty, ..) + | hir::ItemKind::TyAlias(ty, ..) => { + self.check_ty_maybe_containing_foreign_fnptr( + cx, + ty, + cx.tcx.type_of(item.owner_id).instantiate_identity(), + ); + } + // See `check_fn`.. + hir::ItemKind::Fn(..) => {} + // See `check_field_def`.. + hir::ItemKind::Union(..) | hir::ItemKind::Struct(..) | hir::ItemKind::Enum(..) => {} + // Doesn't define something that can contain a external type to be checked. + hir::ItemKind::Impl(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::Trait(..) + | hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::Mod(..) + | hir::ItemKind::Macro(..) + | hir::ItemKind::Use(..) + | hir::ItemKind::ExternCrate(..) => {} + } + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { + self.check_ty_maybe_containing_foreign_fnptr( + cx, + field.ty, + cx.tcx.type_of(field.def_id).instantiate_identity(), + ); + } + fn check_fn( &mut self, cx: &LateContext<'tcx>, @@ -1315,7 +1566,9 @@ impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { }; let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; - if !vis.is_internal_abi(abi) { + if vis.is_internal_abi(abi) { + vis.check_fn(id, decl); + } else { vis.check_foreign_fn(id, decl); } } @@ -1326,13 +1579,13 @@ declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind { - let t = cx.tcx.type_of(it.owner_id).subst_identity(); + let t = cx.tcx.type_of(it.owner_id).instantiate_identity(); let ty = cx.tcx.erase_regions(t); let Ok(layout) = cx.layout_of(ty) else { return }; - let Variants::Multiple { - tag_encoding: TagEncoding::Direct, tag, ref variants, .. - } = &layout.variants else { - return + let Variants::Multiple { tag_encoding: TagEncoding::Direct, tag, ref variants, .. } = + &layout.variants + else { + return; }; let tag_size = tag.size(&cx.tcx).bytes(); @@ -1446,7 +1699,7 @@ impl InvalidAtomicOrdering { && recognized_names.contains(&method_path.ident.name) && let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_did) = cx.tcx.impl_of_method(m_def_id) - && let Some(adt) = cx.tcx.type_of(impl_did).subst_identity().ty_adt_def() + && let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() // skip extension traits, only lint functions from the standard library && cx.tcx.trait_id_of_impl(impl_did).is_none() && let parent = cx.tcx.parent(adt.did()) @@ -1505,8 +1758,13 @@ impl InvalidAtomicOrdering { } fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak]) - else {return }; + let Some((method, args)) = Self::inherent_atomic_method_call( + cx, + expr, + &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak], + ) else { + return; + }; let fail_order_arg = match method { sym::fetch_update => &args[1], diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 8f75fa11dd9a2..6041f80753b54 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1,7 +1,8 @@ use crate::lints::{ PathStatementDrop, PathStatementDropSub, PathStatementNoEffect, UnusedAllocationDiag, UnusedAllocationMutDiag, UnusedClosure, UnusedDef, UnusedDefSuggestion, UnusedDelim, - UnusedDelimSuggestion, UnusedGenerator, UnusedImportBracesDiag, UnusedOp, UnusedResult, + UnusedDelimSuggestion, UnusedGenerator, UnusedImportBracesDiag, UnusedOp, UnusedOpSuggestion, + UnusedResult, }; use crate::Lint; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -93,7 +94,17 @@ declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); impl<'tcx> LateLintPass<'tcx> for UnusedResults { fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { - let hir::StmtKind::Semi(expr) = s.kind else { return; }; + let hir::StmtKind::Semi(mut expr) = s.kind else { + return; + }; + + let mut expr_is_from_block = false; + while let hir::ExprKind::Block(blk, ..) = expr.kind + && let hir::Block { expr: Some(e), .. } = blk + { + expr = e; + expr_is_from_block = true; + } if let hir::ExprKind::Ret(..) = expr.kind { return; @@ -113,6 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { expr.span, "output of future returned by ", "", + expr_is_from_block, ) { // We have a bare `foo().await;` on an opaque type from an async function that was @@ -125,13 +137,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let must_use_result = is_ty_must_use(cx, ty, &expr, expr.span); let type_lint_emitted_or_suppressed = match must_use_result { Some(path) => { - emit_must_use_untranslated(cx, &path, "", "", 1, false); + emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); true } None => false, }; - let fn_warned = check_fn_must_use(cx, expr); + let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block); if !fn_warned && type_lint_emitted_or_suppressed { // We don't warn about unused unit or uninhabited types. @@ -176,7 +188,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { UnusedOp { op: must_use_op, label: expr.span, - suggestion: expr.span.shrink_to_lo(), + suggestion: if expr_is_from_block { + UnusedOpSuggestion::BlockTailExpr { + before_span: expr.span.shrink_to_lo(), + after_span: expr.span.shrink_to_hi(), + } + } else { + UnusedOpSuggestion::NormalExpr { span: expr.span.shrink_to_lo() } + }, }, ); op_warned = true; @@ -186,7 +205,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { cx.emit_spanned_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); } - fn check_fn_must_use(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + fn check_fn_must_use( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + expr_is_from_block: bool, + ) -> bool { let maybe_def_id = match expr.kind { hir::ExprKind::Call(ref callee, _) => { match callee.kind { @@ -207,7 +230,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { _ => None, }; if let Some(def_id) = maybe_def_id { - check_must_use_def(cx, def_id, expr.span, "return value of ", "") + check_must_use_def( + cx, + def_id, + expr.span, + "return value of ", + "", + expr_is_from_block, + ) } else { false } @@ -256,23 +286,25 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { } ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), ty::Alias(ty::Opaque, ty::AliasTy { def_id: def, .. }) => { - elaborate(cx.tcx, cx.tcx.explicit_item_bounds(def).subst_identity_iter_copied()) - // We only care about self bounds for the impl-trait - .filter_only_self() - .find_map(|(pred, _span)| { - // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::PredicateKind::Clause(ty::Clause::Trait( - ref poly_trait_predicate, - )) = pred.kind().skip_binder() - { - let def_id = poly_trait_predicate.trait_ref.def_id; - - is_def_must_use(cx, def_id, span) - } else { - None - } - }) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) + elaborate( + cx.tcx, + cx.tcx.explicit_item_bounds(def).instantiate_identity_iter_copied(), + ) + // We only care about self bounds for the impl-trait + .filter_only_self() + .find_map(|(pred, _span)| { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = + pred.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + + is_def_must_use(cx, def_id, span) + } else { + None + } + }) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) } ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() @@ -350,6 +382,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { span: Span, descr_pre_path: &str, descr_post_path: &str, + expr_is_from_block: bool, ) -> bool { is_def_must_use(cx, def_id, span) .map(|must_use_path| { @@ -360,6 +393,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { descr_post_path, 1, false, + expr_is_from_block, ) }) .is_some() @@ -373,33 +407,64 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { descr_post: &str, plural_len: usize, is_inner: bool, + expr_is_from_block: bool, ) { let plural_suffix = pluralize!(plural_len); match path { MustUsePath::Suppressed => {} MustUsePath::Boxed(path) => { - let descr_pre = &format!("{}boxed ", descr_pre); - emit_must_use_untranslated(cx, path, descr_pre, descr_post, plural_len, true); + let descr_pre = &format!("{descr_pre}boxed "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } MustUsePath::Opaque(path) => { - let descr_pre = &format!("{}implementer{} of ", descr_pre, plural_suffix); - emit_must_use_untranslated(cx, path, descr_pre, descr_post, plural_len, true); + let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } MustUsePath::TraitObject(path) => { - let descr_post = &format!(" trait object{}{}", plural_suffix, descr_post); - emit_must_use_untranslated(cx, path, descr_pre, descr_post, plural_len, true); + let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } MustUsePath::TupleElement(elems) => { for (index, path) in elems { - let descr_post = &format!(" in tuple element {}", index); + let descr_post = &format!(" in tuple element {index}"); emit_must_use_untranslated( - cx, path, descr_pre, descr_post, plural_len, true, + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, ); } } MustUsePath::Array(path, len) => { - let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix); + let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); emit_must_use_untranslated( cx, path, @@ -407,6 +472,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { descr_post, plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), true, + expr_is_from_block, ); } MustUsePath::Closure(span) => { @@ -433,8 +499,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { cx, def_id: *def_id, note: *reason, - suggestion: (!is_inner) - .then_some(UnusedDefSuggestion { span: span.shrink_to_lo() }), + suggestion: (!is_inner).then_some(if expr_is_from_block { + UnusedDefSuggestion::BlockTailExpr { + before_span: span.shrink_to_lo(), + after_span: span.shrink_to_hi(), + } + } else { + UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() } + }), }, ); } @@ -556,6 +628,7 @@ trait UnusedDelimLint { followed_by_block: bool, left_pos: Option, right_pos: Option, + is_kw: bool, ); fn is_expr_delims_necessary( @@ -580,7 +653,7 @@ trait UnusedDelimLint { ExprKind::Call(fn_, _params) => fn_, ExprKind::Cast(expr, _ty) => expr, ExprKind::Type(expr, _ty) => expr, - ExprKind::Index(base, _subscript) => base, + ExprKind::Index(base, _subscript, _) => base, _ => break, }; if !classify::expr_requires_semi_to_be_stmt(innermost) { @@ -593,6 +666,24 @@ trait UnusedDelimLint { if !followed_by_block { return false; } + + // Check if we need parens for `match &( Struct { feild: }) {}`. + { + let mut innermost = inner; + loop { + innermost = match &innermost.kind { + ExprKind::AddrOf(_, _, expr) => expr, + _ => { + if parser::contains_exterior_struct_lit(&innermost) { + return true; + } else { + break; + } + } + } + } + } + let mut innermost = inner; loop { innermost = match &innermost.kind { @@ -624,6 +715,7 @@ trait UnusedDelimLint { ctx: UnusedDelimsCtx, left_pos: Option, right_pos: Option, + is_kw: bool, ) { // If `value` has `ExprKind::Err`, unused delim lint can be broken. // For example, the following code caused ICE. @@ -667,7 +759,7 @@ trait UnusedDelimLint { left_pos.is_some_and(|s| s >= value.span.lo()), right_pos.is_some_and(|s| s <= value.span.hi()), ); - self.emit_unused_delims(cx, value.span, spans, ctx.into(), keep_space); + self.emit_unused_delims(cx, value.span, spans, ctx.into(), keep_space, is_kw); } fn emit_unused_delims( @@ -677,6 +769,7 @@ trait UnusedDelimLint { spans: Option<(Span, Span)>, msg: &str, keep_space: (bool, bool), + is_kw: bool, ) { let primary_span = if let Some((lo, hi)) = spans { if hi.is_empty() { @@ -690,7 +783,7 @@ trait UnusedDelimLint { let suggestion = spans.map(|(lo, hi)| { let sm = cx.sess().source_map(); let lo_replace = - if keep_space.0 && + if (keep_space.0 || is_kw) && let Ok(snip) = sm.span_to_prev_source(lo) && !snip.ends_with(' ') { " " } else { @@ -720,7 +813,7 @@ trait UnusedDelimLint { fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { use rustc_ast::ExprKind::*; - let (value, ctx, followed_by_block, left_pos, right_pos) = match e.kind { + let (value, ctx, followed_by_block, left_pos, right_pos, is_kw) = match e.kind { // Do not lint `unused_braces` in `if let` expressions. If(ref cond, ref block, _) if !matches!(cond.kind, Let(_, _, _)) @@ -728,7 +821,7 @@ trait UnusedDelimLint { { let left = e.span.lo() + rustc_span::BytePos(2); let right = block.span.lo(); - (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right)) + (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right), true) } // Do not lint `unused_braces` in `while let` expressions. @@ -738,27 +831,27 @@ trait UnusedDelimLint { { let left = e.span.lo() + rustc_span::BytePos(5); let right = block.span.lo(); - (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right)) + (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right), true) } ForLoop(_, ref cond, ref block, ..) => { - (cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo())) + (cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo()), true) } Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { let left = e.span.lo() + rustc_span::BytePos(5); - (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None) + (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None, true) } Ret(Some(ref value)) => { let left = e.span.lo() + rustc_span::BytePos(3); - (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None) + (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None, true) } - Index(_, ref value) => (value, UnusedDelimsCtx::IndexExpr, false, None, None), + Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false), Assign(_, ref value, _) | AssignOp(.., ref value) => { - (value, UnusedDelimsCtx::AssignedValue, false, None, None) + (value, UnusedDelimsCtx::AssignedValue, false, None, None, false) } // either function/method call, or something this lint doesn't care about ref call_or_other => { @@ -778,12 +871,20 @@ trait UnusedDelimLint { return; } for arg in args_to_check { - self.check_unused_delims_expr(cx, arg, ctx, false, None, None); + self.check_unused_delims_expr(cx, arg, ctx, false, None, None, false); } return; } }; - self.check_unused_delims_expr(cx, &value, ctx, followed_by_block, left_pos, right_pos); + self.check_unused_delims_expr( + cx, + &value, + ctx, + followed_by_block, + left_pos, + right_pos, + is_kw, + ); } fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { @@ -794,7 +895,7 @@ trait UnusedDelimLint { None => UnusedDelimsCtx::AssignedValue, Some(_) => UnusedDelimsCtx::AssignedValueLetElse, }; - self.check_unused_delims_expr(cx, init, ctx, false, None, None); + self.check_unused_delims_expr(cx, init, ctx, false, None, None, false); } } StmtKind::Expr(ref expr) => { @@ -805,6 +906,7 @@ trait UnusedDelimLint { false, None, None, + false, ); } _ => {} @@ -824,6 +926,7 @@ trait UnusedDelimLint { false, None, None, + false, ); } } @@ -879,6 +982,7 @@ impl UnusedDelimLint for UnusedParens { followed_by_block: bool, left_pos: Option, right_pos: Option, + is_kw: bool, ) { match value.kind { ast::ExprKind::Paren(ref inner) => { @@ -893,7 +997,7 @@ impl UnusedDelimLint for UnusedParens { _, ) if node.lazy())) { - self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw) } } ast::ExprKind::Let(_, ref expr, _) => { @@ -904,6 +1008,7 @@ impl UnusedDelimLint for UnusedParens { followed_by_block, None, None, + false, ); } _ => {} @@ -942,7 +1047,7 @@ impl UnusedParens { .span .find_ancestor_inside(value.span) .map(|inner| (value.span.with_hi(inner.lo()), value.span.with_lo(inner.hi()))); - self.emit_unused_delims(cx, value.span, spans, "pattern", keep_space); + self.emit_unused_delims(cx, value.span, spans, "pattern", keep_space, false); } } } @@ -967,6 +1072,7 @@ impl EarlyLintPass for UnusedParens { true, None, None, + true, ); for stmt in &block.stmts { ::check_stmt(self, cx, stmt); @@ -985,6 +1091,7 @@ impl EarlyLintPass for UnusedParens { false, None, None, + true, ); } } @@ -1043,6 +1150,7 @@ impl EarlyLintPass for UnusedParens { false, None, None, + false, ); } ast::TyKind::Paren(r) => { @@ -1057,7 +1165,7 @@ impl EarlyLintPass for UnusedParens { .find_ancestor_inside(ty.span) .map(|r| (ty.span.with_hi(r.lo()), ty.span.with_lo(r.hi()))); - self.emit_unused_delims(cx, ty.span, spans, "type", (false, false)); + self.emit_unused_delims(cx, ty.span, spans, "type", (false, false), false); } } self.with_self_ty_parens = false; @@ -1130,6 +1238,7 @@ impl UnusedDelimLint for UnusedBraces { followed_by_block: bool, left_pos: Option, right_pos: Option, + is_kw: bool, ) { match value.kind { ast::ExprKind::Block(ref inner, None) @@ -1170,7 +1279,7 @@ impl UnusedDelimLint for UnusedBraces { && !value.span.from_expansion() && !inner.span.from_expansion() { - self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos) + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw) } } } @@ -1183,6 +1292,7 @@ impl UnusedDelimLint for UnusedBraces { followed_by_block, None, None, + false, ); } _ => {} @@ -1207,6 +1317,7 @@ impl EarlyLintPass for UnusedBraces { false, None, None, + false, ); } } @@ -1220,6 +1331,7 @@ impl EarlyLintPass for UnusedBraces { false, None, None, + false, ); } } @@ -1233,6 +1345,7 @@ impl EarlyLintPass for UnusedBraces { false, None, None, + false, ); } } @@ -1247,6 +1360,7 @@ impl EarlyLintPass for UnusedBraces { false, None, None, + false, ); } @@ -1258,6 +1372,7 @@ impl EarlyLintPass for UnusedBraces { false, None, None, + false, ); } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 6e9dc880a7dee..96c31a90da865 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -2846,6 +2846,45 @@ declare_lint! { }; } +declare_lint! { + /// The `unnameable_test_items` lint detects [`#[test]`][test] functions + /// that are not able to be run by the test harness because they are in a + /// position where they are not nameable. + /// + /// [test]: https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute + /// + /// ### Example + /// + /// ```rust,test + /// fn main() { + /// #[test] + /// fn foo() { + /// // This test will not fail because it does not run. + /// assert_eq!(1, 2); + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In order for the test harness to run a test, the test function must be + /// located in a position where it can be accessed from the crate root. + /// This generally means it must be defined in a module, and not anywhere + /// else such as inside another function. The compiler previously allowed + /// this without an error, so a lint was added as an alert that a test is + /// not being used. Whether or not this should be allowed has not yet been + /// decided, see [RFC 2471] and [issue #36629]. + /// + /// [RFC 2471]: https://github.com/rust-lang/rfcs/pull/2471#issuecomment-397414443 + /// [issue #36629]: https://github.com/rust-lang/rust/issues/36629 + pub UNNAMEABLE_TEST_ITEMS, + Warn, + "detects an item that cannot be named being marked as `#[test_case]`", + report_in_external_macro +} + declare_lint! { /// The `useless_deprecated` lint detects deprecation attributes with no effect. /// @@ -3272,6 +3311,43 @@ declare_lint! { "ambiguous glob re-exports", } +declare_lint! { + /// The `hidden_glob_reexports` lint detects cases where glob re-export items are shadowed by + /// private items. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(hidden_glob_reexports)] + /// + /// pub mod upstream { + /// mod inner { pub struct Foo {}; pub struct Bar {}; } + /// pub use self::inner::*; + /// struct Foo {} // private item shadows `inner::Foo` + /// } + /// + /// // mod downstream { + /// // fn test() { + /// // let _ = crate::upstream::Foo; // inaccessible + /// // } + /// // } + /// + /// pub fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This was previously accepted without any errors or warnings but it could silently break a + /// crate's downstream user code. If the `struct Foo` was added, `dep::inner::Foo` would + /// silently become inaccessible and trigger a "`struct `Foo` is private`" visibility error at + /// the downstream use site. + pub HIDDEN_GLOB_REEXPORTS, + Warn, + "name introduced by a private item shadows a name introduced by a public glob re-export", +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -3279,6 +3355,7 @@ declare_lint_pass! { // tidy-alphabetical-start ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_ASSOCIATED_ITEMS, + AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_GLOB_REEXPORTS, ARITHMETIC_OVERFLOW, ASM_SUB_REGISTER, @@ -3289,6 +3366,7 @@ declare_lint_pass! { BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE, CENUM_IMPL_DROP_CAST, COHERENCE_LEAK_CHECK, + COINDUCTIVE_OVERLAP_IN_COHERENCE, CONFLICTING_REPR_HINTS, CONST_EVALUATABLE_UNCHECKED, CONST_ITEM_MUTATION, @@ -3304,6 +3382,7 @@ declare_lint_pass! { FORBIDDEN_LINT_GROUPS, FUNCTION_ITEM_REFERENCES, FUZZY_PROVENANCE_CASTS, + HIDDEN_GLOB_REEXPORTS, ILL_FORMED_ATTRIBUTE_INPUT, ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, IMPLIED_BOUNDS_ENTAILMENT, @@ -3319,6 +3398,7 @@ declare_lint_pass! { LARGE_ASSIGNMENTS, LATE_BOUND_LIFETIME_ARGUMENTS, LEGACY_DERIVE_HELPERS, + LONG_RUNNING_CONST_EVAL, LOSSY_PROVENANCE_CASTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, MACRO_USE_EXTERN_CRATE, @@ -3333,7 +3413,9 @@ declare_lint_pass! { OVERLAPPING_RANGE_ENDPOINTS, PATTERNS_IN_FNS_WITHOUT_BODY, POINTER_STRUCTURAL_MATCH, + PRIVATE_BOUNDS, PRIVATE_IN_PUBLIC, + PRIVATE_INTERFACES, PROC_MACRO_BACK_COMPAT, PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, PUB_USE_OF_PRIVATE_EXTERN_CRATE, @@ -3359,7 +3441,10 @@ declare_lint_pass! { UNFULFILLED_LINT_EXPECTATIONS, UNINHABITED_STATIC, UNKNOWN_CRATE_TYPES, + UNKNOWN_DIAGNOSTIC_ATTRIBUTES, UNKNOWN_LINTS, + UNNAMEABLE_TEST_ITEMS, + UNNAMEABLE_TYPES, UNREACHABLE_CODE, UNREACHABLE_PATTERNS, UNSAFE_OP_IN_UNSAFE_FN, @@ -3367,6 +3452,7 @@ declare_lint_pass! { UNSTABLE_SYNTAX_PRE_EXPANSION, UNSUPPORTED_CALLING_CONVENTIONS, UNUSED_ASSIGNMENTS, + UNUSED_ASSOCIATED_TYPE_BOUNDS, UNUSED_ATTRIBUTES, UNUSED_CRATE_DEPENDENCIES, UNUSED_EXTERN_CRATES, @@ -3388,6 +3474,70 @@ declare_lint_pass! { ] } +declare_lint! { + /// The `long_running_const_eval` lint is emitted when const + /// eval is running for a long time to ensure rustc terminates + /// even if you accidentally wrote an infinite loop. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// const FOO: () = loop {}; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Loops allow const evaluation to compute arbitrary code, but may also + /// cause infinite loops or just very long running computations. + /// Users can enable long running computations by allowing the lint + /// on individual constants or for entire crates. + /// + /// ### Unconditional warnings + /// + /// Note that regardless of whether the lint is allowed or set to warn, + /// the compiler will issue warnings if constant evaluation runs significantly + /// longer than this lint's limit. These warnings are also shown to downstream + /// users from crates.io or similar registries. If you are above the lint's limit, + /// both you and downstream users might be exposed to these warnings. + /// They might also appear on compiler updates, as the compiler makes minor changes + /// about how complexity is measured: staying below the limit ensures that there + /// is enough room, and given that the lint is disabled for people who use your + /// dependency it means you will be the only one to get the warning and can put + /// out an update in your own time. + pub LONG_RUNNING_CONST_EVAL, + Deny, + "detects long const eval operations", + report_in_external_macro +} + +declare_lint! { + /// The `unused_associated_type_bounds` lint is emitted when an + /// associated type bound is added to a trait object, but the associated + /// type has a `where Self: Sized` bound, and is thus unavailable on the + /// trait object anyway. + /// + /// ### Example + /// + /// ```rust + /// trait Foo { + /// type Bar where Self: Sized; + /// } + /// type Mop = dyn Foo; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Just like methods with `Self: Sized` bounds are unavailable on trait + /// objects, associated types can be removed from the trait object. + pub UNUSED_ASSOCIATED_TYPE_BOUNDS, + Warn, + "detects unused `Foo = Bar` bounds in `dyn Trait`" +} + declare_lint! { /// The `unused_doc_comments` lint detects doc comments that aren't used /// by `rustdoc`. @@ -3816,7 +3966,6 @@ declare_lint! { /// /// // in crate B /// #![feature(non_exhaustive_omitted_patterns_lint)] - /// /// match Bar::A { /// Bar::A => {}, /// #[warn(non_exhaustive_omitted_patterns)] @@ -3945,12 +4094,12 @@ declare_lint! { /// /// The compiler disables the automatic implementation if an explicit one /// exists for given type constructor. The exact rules governing this - /// are currently unsound, quite subtle, and will be modified in the future. - /// This change will cause the automatic implementation to be disabled in more + /// were previously unsound, quite subtle, and have been recently modified. + /// This change caused the automatic implementation to be disabled in more /// cases, potentially breaking some code. pub SUSPICIOUS_AUTO_TRAIT_IMPLS, Warn, - "the rules governing auto traits will change in the future", + "the rules governing auto traits have recently changed resulting in potential breakage", @future_incompatible = FutureIncompatibleInfo { reason: FutureIncompatibilityReason::FutureReleaseSemanticsChange, reference: "issue #93367 ", @@ -3977,7 +4126,7 @@ declare_lint! { /// /// ### Explanation /// - /// The preferred location for where clauses on associated types in impls + /// The preferred location for where clauses on associated types /// is after the type. However, for most of generic associated types development, /// it was only accepted before the equals. To provide a transition period and /// further evaluate this change, both are currently accepted. At some point in @@ -4175,3 +4324,206 @@ declare_lint! { Warn, "\"invalid_parameter\" isn't a valid argument for `#[macro_export]`", } + +declare_lint! { + /// The `private_interfaces` lint detects types in a primary interface of an item, + /// that are more private than the item itself. Primary interface of an item is all + /// its interface except for bounds on generic parameters and where clauses. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(type_privacy_lints)] + /// # #![allow(unused)] + /// # #![allow(private_in_public)] + /// #![deny(private_interfaces)] + /// struct SemiPriv; + /// + /// mod m1 { + /// struct Priv; + /// impl crate::SemiPriv { + /// pub fn f(_: Priv) {} + /// } + /// } + /// + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Having something private in primary interface guarantees that + /// the item will be unusable from outer modules due to type privacy. + pub PRIVATE_INTERFACES, + Allow, + "private type in primary interface of an item", + @feature_gate = sym::type_privacy_lints; +} + +declare_lint! { + /// The `private_bounds` lint detects types in a secondary interface of an item, + /// that are more private than the item itself. Secondary interface of an item consists of + /// bounds on generic parameters and where clauses, including supertraits for trait items. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(type_privacy_lints)] + /// # #![allow(private_in_public)] + /// # #![allow(unused)] + /// #![deny(private_bounds)] + /// + /// struct PrivTy; + /// pub struct S + /// where PrivTy: + /// {} + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Having private types or traits in item bounds makes it less clear what interface + /// the item actually provides. + pub PRIVATE_BOUNDS, + Allow, + "private type in secondary interface of an item", + @feature_gate = sym::type_privacy_lints; +} + +declare_lint! { + /// The `unnameable_types` lint detects types for which you can get objects of that type, + /// but cannot name the type itself. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(type_privacy_lints)] + /// # #![allow(unused)] + /// #![deny(unnameable_types)] + /// mod m { + /// pub struct S; + /// } + /// + /// pub fn get_voldemort() -> m::S { m::S } + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It is often expected that if you can obtain an object of type `T`, then + /// you can name the type `T` as well, this lint attempts to enforce this rule. + pub UNNAMEABLE_TYPES, + Allow, + "effective visibility of a type is larger than the area in which it can be named", + @feature_gate = sym::type_privacy_lints; +} + +declare_lint! { + /// The `coinductive_overlap_in_coherence` lint detects impls which are currently + /// considered not overlapping, but may be considered to overlap if support for + /// coinduction is added to the trait solver. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(coinductive_overlap_in_coherence)] + /// + /// trait CyclicTrait {} + /// impl CyclicTrait for T {} + /// + /// trait Trait {} + /// impl Trait for T {} + /// // conflicting impl with the above + /// impl Trait for u8 {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// We have two choices for impl which satisfy `u8: Trait`: the blanket impl + /// for generic `T`, and the direct impl for `u8`. These two impls nominally + /// overlap, since we can infer `T = u8` in the former impl, but since the where + /// clause `u8: CyclicTrait` would end up resulting in a cycle (since it depends + /// on itself), the blanket impl is not considered to hold for `u8`. This will + /// change in a future release. + pub COINDUCTIVE_OVERLAP_IN_COHERENCE, + Warn, + "impls that are not considered to overlap may be considered to \ + overlap in the future", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #114040 ", + }; +} + +declare_lint! { + /// The `unknown_diagnostic_attributes` lint detects unrecognized diagnostic attributes. + /// + /// ### Example + /// + /// ```rust + /// #![feature(diagnostic_namespace)] + /// #[diagnostic::does_not_exist] + /// struct Foo; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It is usually a mistake to specify a diagnostic attribute that does not exist. Check + /// the spelling, and check the diagnostic attribute listing for the correct name. Also + /// consider if you are using an old version of the compiler, and the attribute + /// is only available in a newer version. + pub UNKNOWN_DIAGNOSTIC_ATTRIBUTES, + Warn, + "unrecognized diagnostic attribute" +} + +declare_lint! { + /// The `ambiguous_glob_imports` lint detects glob imports that should report ambiguity + /// errors, but previously didn't do that due to rustc bugs. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// + /// #![deny(ambiguous_glob_imports)] + /// pub fn foo() -> u32 { + /// use sub::*; + /// C + /// } + /// + /// mod sub { + /// mod mod1 { pub const C: u32 = 1; } + /// mod mod2 { pub const C: u32 = 2; } + /// + /// pub use mod1::*; + /// pub use mod2::*; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Previous versions of Rust compile it successfully because it + /// had lost the ambiguity error when resolve `use sub::mod2::*`. + /// + /// This is a [future-incompatible] lint to transition this to a + /// hard error in the future. + /// + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub AMBIGUOUS_GLOB_IMPORTS, + Warn, + "detects certain glob imports that require reporting an ambiguity error", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseError, + reference: "issue #114095 ", + }; +} diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index e27e322db8858..f350957f72f1c 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -467,6 +467,21 @@ impl ToStableHashKey for LintId { } } +#[derive(Debug)] +pub struct AmbiguityErrorDiag { + pub msg: String, + pub span: Span, + pub label_span: Span, + pub label_msg: String, + pub note_msg: String, + pub b1_span: Span, + pub b1_note_msg: String, + pub b1_help_msgs: Vec, + pub b2_span: Span, + pub b2_note_msg: String, + pub b2_help_msgs: Vec, +} + // This could be a closure, but then implementing derive trait // becomes hacky (and it gets allocated). #[derive(Debug)] @@ -530,6 +545,9 @@ pub enum BuiltinLintDiagnostics { vis_span: Span, ident_span: Span, }, + AmbiguousGlobImports { + diag: AmbiguityErrorDiag, + }, AmbiguousGlobReexports { /// The name for which collision(s) have occurred. name: String, @@ -540,6 +558,20 @@ pub enum BuiltinLintDiagnostics { /// Span where the same name is also re-exported. duplicate_reexport_span: Span, }, + HiddenGlobReexports { + /// The name of the local binding which shadows the glob re-export. + name: String, + /// The namespace for which the shadowing occurred in. + namespace: String, + /// The glob reexport that is shadowed by the local binding. + glob_reexport_span: Span, + /// The local binding that shadows the glob reexport. + private_item_span: Span, + }, + UnusedQualifications { + /// The span of the unnecessarily-qualified path to remove. + removal_span: Span, + }, } /// Lints that are buffered up early on in the `Session` before the diff --git a/compiler/rustc_llvm/build.rs b/compiler/rustc_llvm/build.rs index b0783d75d4756..4302b1618331d 100644 --- a/compiler/rustc_llvm/build.rs +++ b/compiler/rustc_llvm/build.rs @@ -12,6 +12,7 @@ const OPTIONAL_COMPONENTS: &[&str] = &[ "avr", "loongarch", "m68k", + "csky", "mips", "powerpc", "systemz", @@ -251,8 +252,11 @@ fn main() { } else if target.contains("windows-gnu") { println!("cargo:rustc-link-lib=shell32"); println!("cargo:rustc-link-lib=uuid"); - } else if target.contains("netbsd") || target.contains("haiku") || target.contains("darwin") { + } else if target.contains("haiku") || target.contains("darwin") { println!("cargo:rustc-link-lib=z"); + } else if target.contains("netbsd") { + println!("cargo:rustc-link-lib=z"); + println!("cargo:rustc-link-lib=execinfo"); } cmd.args(&components); diff --git a/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp index 87906dee4d348..d61ec0b641c9d 100644 --- a/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/CoverageMappingWrapper.cpp @@ -103,12 +103,20 @@ fromRust(LLVMRustCounterExprKind Kind) { } extern "C" void LLVMRustCoverageWriteFilenamesSectionToBuffer( - const char* const Filenames[], + const char *const Filenames[], size_t FilenamesLen, + const size_t *const Lengths, + size_t LengthsLen, RustStringRef BufferOut) { + if (FilenamesLen != LengthsLen) { + report_fatal_error( + "Mismatched lengths in LLVMRustCoverageWriteFilenamesSectionToBuffer"); + } + SmallVector FilenameRefs; + FilenameRefs.reserve(FilenamesLen); for (size_t i = 0; i < FilenamesLen; i++) { - FilenameRefs.push_back(std::string(Filenames[i])); + FilenameRefs.emplace_back(Filenames[i], Lengths[i]); } auto FilenamesWriter = coverage::CoverageFilenamesSectionWriter(ArrayRef(FilenameRefs)); @@ -153,19 +161,17 @@ extern "C" void LLVMRustCoverageWriteMappingToBuffer( CoverageMappingWriter.write(OS); } -extern "C" LLVMValueRef LLVMRustCoverageCreatePGOFuncNameVar(LLVMValueRef F, const char *FuncName) { - StringRef FuncNameRef(FuncName); +extern "C" LLVMValueRef LLVMRustCoverageCreatePGOFuncNameVar( + LLVMValueRef F, + const char *FuncName, + size_t FuncNameLen) { + StringRef FuncNameRef(FuncName, FuncNameLen); return wrap(createPGOFuncNameVar(*cast(unwrap(F)), FuncNameRef)); } -extern "C" uint64_t LLVMRustCoverageHashCString(const char *StrVal) { - StringRef StrRef(StrVal); - return IndexedInstrProf::ComputeHash(StrRef); -} - extern "C" uint64_t LLVMRustCoverageHashByteArray( const char *Bytes, - unsigned NumBytes) { + size_t NumBytes) { StringRef StrRef(Bytes, NumBytes); return IndexedInstrProf::ComputeHash(StrRef); } diff --git a/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h b/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h index 0589062837866..3f2bf2c9b444d 100644 --- a/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h +++ b/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h @@ -15,7 +15,6 @@ #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/FormattedStream.h" #include "llvm/Support/JSON.h" -#include "llvm/Support/Host.h" #include "llvm/Support/Memory.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetSelect.h" @@ -92,10 +91,9 @@ enum LLVMRustAttribute { NoCfCheck = 35, ShadowCallStack = 36, AllocSize = 37, -#if LLVM_VERSION_GE(15, 0) AllocatedPointer = 38, AllocAlign = 39, -#endif + SanitizeSafeStack = 40, }; typedef struct OpaqueRustString *RustStringRef; diff --git a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp index c43a02724773a..b566ea496dedf 100644 --- a/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -24,11 +25,11 @@ #if LLVM_VERSION_GE(17, 0) #include "llvm/Support/VirtualFileSystem.h" #endif -#include "llvm/Support/Host.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Transforms/IPO/AlwaysInliner.h" #include "llvm/Transforms/IPO/FunctionImport.h" #include "llvm/Transforms/IPO/Internalize.h" +#include "llvm/Transforms/IPO/LowerTypeTests.h" #include "llvm/Transforms/IPO/ThinLTOBitcodeWriter.h" #include "llvm/Transforms/Utils/AddDiscriminators.h" #include "llvm/Transforms/Utils/FunctionImportUtils.h" @@ -104,6 +105,12 @@ extern "C" void LLVMTimeTraceProfilerFinish(const char* FileName) { #define SUBTARGET_M68K #endif +#ifdef LLVM_COMPONENT_CSKY +#define SUBTARGET_CSKY SUBTARGET(CSKY) +#else +#define SUBTARGET_CSKY +#endif + #ifdef LLVM_COMPONENT_MIPS #define SUBTARGET_MIPS SUBTARGET(Mips) #else @@ -158,6 +165,7 @@ extern "C" void LLVMTimeTraceProfilerFinish(const char* FileName) { SUBTARGET_AARCH64 \ SUBTARGET_AVR \ SUBTARGET_M68K \ + SUBTARGET_CSKY \ SUBTARGET_MIPS \ SUBTARGET_PPC \ SUBTARGET_SYSTEMZ \ @@ -306,44 +314,53 @@ static size_t getLongestEntryLength(ArrayRef Table) { return MaxLen; } -extern "C" void LLVMRustPrintTargetCPUs(LLVMTargetMachineRef TM, const char* TargetCPU) { +using PrintBackendInfo = void(void*, const char* Data, size_t Len); + +extern "C" void LLVMRustPrintTargetCPUs(LLVMTargetMachineRef TM, + const char* TargetCPU, + PrintBackendInfo Print, + void* Out) { const TargetMachine *Target = unwrap(TM); const MCSubtargetInfo *MCInfo = Target->getMCSubtargetInfo(); const Triple::ArchType HostArch = Triple(sys::getDefaultTargetTriple()).getArch(); const Triple::ArchType TargetArch = Target->getTargetTriple().getArch(); + std::ostringstream Buf; + #if LLVM_VERSION_GE(17, 0) const ArrayRef CPUTable = MCInfo->getAllProcessorDescriptions(); -#elif defined(LLVM_RUSTLLVM) - const ArrayRef CPUTable = MCInfo->getCPUTable(); #else - printf("Full target CPU help is not supported by this LLVM version.\n\n"); + Buf << "Full target CPU help is not supported by this LLVM version.\n\n"; SubtargetSubTypeKV TargetCPUKV = { TargetCPU, {{}}, {{}} }; const ArrayRef CPUTable = TargetCPUKV; #endif unsigned MaxCPULen = getLongestEntryLength(CPUTable); - printf("Available CPUs for this target:\n"); + Buf << "Available CPUs for this target:\n"; // Don't print the "native" entry when the user specifies --target with a // different arch since that could be wrong or misleading. if (HostArch == TargetArch) { MaxCPULen = std::max(MaxCPULen, (unsigned) std::strlen("native")); const StringRef HostCPU = sys::getHostCPUName(); - printf(" %-*s - Select the CPU of the current host (currently %.*s).\n", - MaxCPULen, "native", (int)HostCPU.size(), HostCPU.data()); + Buf << " " << std::left << std::setw(MaxCPULen) << "native" + << " - Select the CPU of the current host " + "(currently " << HostCPU.str() << ").\n"; } for (auto &CPU : CPUTable) { // Compare cpu against current target to label the default if (strcmp(CPU.Key, TargetCPU) == 0) { - printf(" %-*s - This is the default target CPU" - " for the current build target (currently %s).", - MaxCPULen, CPU.Key, Target->getTargetTriple().str().c_str()); + Buf << " " << std::left << std::setw(MaxCPULen) << CPU.Key + << " - This is the default target CPU for the current build target " + "(currently " << Target->getTargetTriple().str() << ")."; } else { - printf(" %-*s", MaxCPULen, CPU.Key); + Buf << " " << CPU.Key; } - printf("\n"); + Buf << "\n"; } + + const auto &BufString = Buf.str(); + Print(Out, BufString.data(), BufString.size()); } extern "C" size_t LLVMRustGetTargetFeaturesCount(LLVMTargetMachineRef TM) { @@ -599,6 +616,8 @@ enum class LLVMRustOptStage { struct LLVMRustSanitizerOptions { bool SanitizeAddress; bool SanitizeAddressRecover; + bool SanitizeCFI; + bool SanitizeKCFI; bool SanitizeMemory; bool SanitizeMemoryRecover; int SanitizeMemoryTrackOrigins; @@ -615,6 +634,7 @@ LLVMRustOptimize( LLVMTargetMachineRef TMRef, LLVMRustPassBuilderOptLevel OptLevelRust, LLVMRustOptStage OptStage, + bool IsLinkerPluginLTO, bool NoPrepopulatePasses, bool VerifyIR, bool UseThinLTOBuffers, bool MergeFunctions, bool UnrollLoops, bool SLPVectorize, bool LoopVectorize, bool DisableSimplifyLibCalls, bool EmitLifetimeMarkers, @@ -667,6 +687,7 @@ LLVMRustOptimize( assert(!PGOUsePath && !PGOSampleUsePath); PGOOpt = PGOOptions(PGOGenPath, "", "", #if LLVM_VERSION_GE(17, 0) + "", FS, #endif PGOOptions::IRInstr, PGOOptions::NoCSAction, @@ -675,6 +696,7 @@ LLVMRustOptimize( assert(!PGOSampleUsePath); PGOOpt = PGOOptions(PGOUsePath, "", "", #if LLVM_VERSION_GE(17, 0) + "", FS, #endif PGOOptions::IRUse, PGOOptions::NoCSAction, @@ -682,6 +704,7 @@ LLVMRustOptimize( } else if (PGOSampleUsePath) { PGOOpt = PGOOptions(PGOSampleUsePath, "", "", #if LLVM_VERSION_GE(17, 0) + "", FS, #endif PGOOptions::SampleUse, PGOOptions::NoCSAction, @@ -689,6 +712,7 @@ LLVMRustOptimize( } else if (DebugInfoForProfiling) { PGOOpt = PGOOptions("", "", "", #if LLVM_VERSION_GE(17, 0) + "", FS, #endif PGOOptions::NoAction, PGOOptions::NoCSAction, @@ -722,6 +746,18 @@ LLVMRustOptimize( std::vector> OptimizerLastEPCallbacks; + if (!IsLinkerPluginLTO + && SanitizerOptions && SanitizerOptions->SanitizeCFI + && !NoPrepopulatePasses) { + PipelineStartEPCallbacks.push_back( + [](ModulePassManager &MPM, OptimizationLevel Level) { + MPM.addPass(LowerTypeTestsPass(/*ExportSummary=*/nullptr, + /*ImportSummary=*/nullptr, + /*DropTypeTests=*/false)); + } + ); + } + if (VerifyIR) { PipelineStartEPCallbacks.push_back( [VerifyIR](ModulePassManager &MPM, OptimizationLevel Level) { @@ -785,9 +821,6 @@ LLVMRustOptimize( OptimizerLastEPCallbacks.push_back( [SanitizerOptions](ModulePassManager &MPM, OptimizationLevel Level) { auto CompileKernel = SanitizerOptions->SanitizeKernelAddress; -#if LLVM_VERSION_LT(15, 0) - MPM.addPass(RequireAnalysisPass()); -#endif AddressSanitizerOptions opts = AddressSanitizerOptions{ CompileKernel, SanitizerOptions->SanitizeAddressRecover @@ -1107,9 +1140,15 @@ struct LLVMRustThinLTOData { // Not 100% sure what these are, but they impact what's internalized and // what's inlined across modules, I believe. +#if LLVM_VERSION_GE(18, 0) + DenseMap ImportLists; + DenseMap ExportLists; + DenseMap ModuleToDefinedGVSummaries; +#else StringMap ImportLists; StringMap ExportLists; StringMap ModuleToDefinedGVSummaries; +#endif StringMap> ResolvedODR; LLVMRustThinLTOData() : Index(/* HaveGVs = */ false) {} @@ -1350,6 +1389,11 @@ LLVMRustPrepareThinLTOImport(const LLVMRustThinLTOData *Data, LLVMModuleRef M, if (WasmCustomSections) WasmCustomSections->eraseFromParent(); + // `llvm.ident` named metadata also gets duplicated. + auto *llvmIdent = (*MOrErr)->getNamedMetadata("llvm.ident"); + if (llvmIdent) + llvmIdent->eraseFromParent(); + return MOrErr; }; bool ClearDSOLocal = clearDSOLocalOnDeclarations(Mod, Target); diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 49acd71b3e106..70cdf3d6d2395 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1,4 +1,5 @@ #include "LLVMWrapper.h" +#include "llvm/ADT/Statistic.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DiagnosticHandler.h" #include "llvm/IR/DiagnosticInfo.h" @@ -7,7 +8,12 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/IntrinsicsARM.h" +#include "llvm/IR/LLVMRemarkStreamer.h" #include "llvm/IR/Mangler.h" +#include "llvm/Remarks/RemarkStreamer.h" +#include "llvm/Remarks/RemarkSerializer.h" +#include "llvm/Remarks/RemarkFormat.h" +#include "llvm/Support/ToolOutputFile.h" #if LLVM_VERSION_GE(16, 0) #include "llvm/Support/ModRef.h" #endif @@ -106,9 +112,26 @@ extern "C" void LLVMRustSetNormalizedTarget(LLVMModuleRef M, unwrap(M)->setTargetTriple(Triple::normalize(Triple)); } -extern "C" void LLVMRustPrintPassTimings() { - raw_fd_ostream OS(2, false); // stderr. - TimerGroup::printAll(OS); +extern "C" const char *LLVMRustPrintPassTimings(size_t *Len) { + std::string buf; + raw_string_ostream SS(buf); + TimerGroup::printAll(SS); + SS.flush(); + *Len = buf.length(); + char *CStr = (char *)malloc(*Len); + memcpy(CStr, buf.c_str(), *Len); + return CStr; +} + +extern "C" const char *LLVMRustPrintStatistics(size_t *Len) { + std::string buf; + raw_string_ostream SS(buf); + llvm::PrintStatistics(SS); + SS.flush(); + *Len = buf.length(); + char *CStr = (char *)malloc(*Len); + memcpy(CStr, buf.c_str(), *Len); + return CStr; } extern "C" LLVMValueRef LLVMRustGetNamedValue(LLVMModuleRef M, const char *Name, @@ -116,6 +139,32 @@ extern "C" LLVMValueRef LLVMRustGetNamedValue(LLVMModuleRef M, const char *Name, return wrap(unwrap(M)->getNamedValue(StringRef(Name, NameLen))); } +enum class LLVMRustTailCallKind { + None, + Tail, + MustTail, + NoTail, +}; + +static CallInst::TailCallKind fromRust(LLVMRustTailCallKind Kind) { + switch (Kind) { + case LLVMRustTailCallKind::None: + return CallInst::TailCallKind::TCK_None; + case LLVMRustTailCallKind::Tail: + return CallInst::TailCallKind::TCK_Tail; + case LLVMRustTailCallKind::MustTail: + return CallInst::TailCallKind::TCK_MustTail; + case LLVMRustTailCallKind::NoTail: + return CallInst::TailCallKind::TCK_NoTail; + default: + report_fatal_error("bad CallInst::TailCallKind."); + } +} + +extern "C" void LLVMRustSetTailCallKind(LLVMValueRef Call, LLVMRustTailCallKind TCK) { + unwrap(Call)->setTailCallKind(fromRust(TCK)); +} + extern "C" LLVMValueRef LLVMRustGetOrInsertFunction(LLVMModuleRef M, const char *Name, size_t NameLen, @@ -228,12 +277,12 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) { return Attribute::ShadowCallStack; case AllocSize: return Attribute::AllocSize; -#if LLVM_VERSION_GE(15, 0) case AllocatedPointer: return Attribute::AllocatedPointer; case AllocAlign: return Attribute::AllocAlign; -#endif + case SanitizeSafeStack: + return Attribute::SafeStack; } report_fatal_error("bad AttributeKind"); } @@ -289,20 +338,12 @@ extern "C" LLVMAttributeRef LLVMRustCreateStructRetAttr(LLVMContextRef C, LLVMTy } extern "C" LLVMAttributeRef LLVMRustCreateElementTypeAttr(LLVMContextRef C, LLVMTypeRef Ty) { -#if LLVM_VERSION_GE(15, 0) return wrap(Attribute::get(*unwrap(C), Attribute::ElementType, unwrap(Ty))); -#else - report_fatal_error("Should not be needed on LLVM < 15"); -#endif } extern "C" LLVMAttributeRef LLVMRustCreateUWTableAttr(LLVMContextRef C, bool Async) { -#if LLVM_VERSION_LT(15, 0) - return wrap(Attribute::get(*unwrap(C), Attribute::UWTable)); -#else return wrap(Attribute::getWithUWTableKind( *unwrap(C), Async ? UWTableKind::Async : UWTableKind::Sync)); -#endif } extern "C" LLVMAttributeRef LLVMRustCreateAllocSizeAttr(LLVMContextRef C, uint32_t ElementSizeArg) { @@ -315,8 +356,6 @@ extern "C" LLVMAttributeRef LLVMRustCreateAllocSizeAttr(LLVMContextRef C, uint32 )); } -#if LLVM_VERSION_GE(15, 0) - // These values **must** match ffi::AllocKindFlags. // It _happens_ to match the LLVM values of llvm::AllocFnKind, // but that's happenstance and we do explicit conversions before @@ -360,16 +399,10 @@ static llvm::AllocFnKind allocKindFromRust(LLVMRustAllocKindFlags F) { } return AFK; } -#endif extern "C" LLVMAttributeRef LLVMRustCreateAllocKindAttr(LLVMContextRef C, uint64_t AllocKindArg) { -#if LLVM_VERSION_GE(15, 0) return wrap(Attribute::get(*unwrap(C), Attribute::AllocKind, static_cast(allocKindFromRust(static_cast(AllocKindArg))))); -#else - report_fatal_error( - "allockind attributes are new in LLVM 15 and should not be used on older LLVMs"); -#endif } // Simplified representation of `MemoryEffects` across the FFI boundary. @@ -466,14 +499,9 @@ LLVMRustInlineAsm(LLVMTypeRef Ty, char *AsmString, size_t AsmStringLen, extern "C" bool LLVMRustInlineAsmVerify(LLVMTypeRef Ty, char *Constraints, size_t ConstraintsLen) { -#if LLVM_VERSION_LT(15, 0) - return InlineAsm::Verify(unwrap(Ty), - StringRef(Constraints, ConstraintsLen)); -#else // llvm::Error converts to true if it is an error. return !llvm::errorToBool(InlineAsm::verify( unwrap(Ty), StringRef(Constraints, ConstraintsLen))); -#endif } typedef DIBuilder *LLVMRustDIBuilderRef; @@ -1583,17 +1611,6 @@ extern "C" void LLVMRustSetLinkage(LLVMValueRef V, LLVMSetLinkage(V, fromRust(RustLinkage)); } -// FIXME: replace with LLVMConstInBoundsGEP2 when bumped minimal version to llvm-14 -extern "C" LLVMValueRef LLVMRustConstInBoundsGEP2(LLVMTypeRef Ty, - LLVMValueRef ConstantVal, - LLVMValueRef *ConstantIndices, - unsigned NumIndices) { - ArrayRef IdxList(unwrap(ConstantIndices, NumIndices), - NumIndices); - Constant *Val = unwrap(ConstantVal); - return wrap(ConstantExpr::getInBoundsGetElementPtr(unwrap(Ty), Val, IdxList)); -} - extern "C" bool LLVMRustConstIntGetZExtValue(LLVMValueRef CV, uint64_t *value) { auto C = unwrap(CV); if (C->getBitWidth() > 64) @@ -1609,19 +1626,11 @@ extern "C" bool LLVMRustConstInt128Get(LLVMValueRef CV, bool sext, uint64_t *hig auto C = unwrap(CV); if (C->getBitWidth() > 128) { return false; } APInt AP; -#if LLVM_VERSION_GE(15, 0) if (sext) { AP = C->getValue().sext(128); } else { AP = C->getValue().zext(128); } -#else - if (sext) { - AP = C->getValue().sextOrSelf(128); - } else { - AP = C->getValue().zextOrSelf(128); - } -#endif *low = AP.getLoBits(64).getZExtValue(); *high = AP.getHiBits(64).getZExtValue(); return true; @@ -1853,23 +1862,52 @@ using LLVMDiagnosticHandlerTy = DiagnosticHandler::DiagnosticHandlerTy; // When RemarkAllPasses is true, remarks are enabled for all passes. Otherwise // the RemarkPasses array specifies individual passes for which remarks will be // enabled. +// +// If RemarkFilePath is not NULL, optimization remarks will be streamed directly into this file, +// bypassing the diagnostics handler. extern "C" void LLVMRustContextConfigureDiagnosticHandler( LLVMContextRef C, LLVMDiagnosticHandlerTy DiagnosticHandlerCallback, void *DiagnosticHandlerContext, bool RemarkAllPasses, - const char * const * RemarkPasses, size_t RemarkPassesLen) { + const char * const * RemarkPasses, size_t RemarkPassesLen, + const char * RemarkFilePath, + bool PGOAvailable +) { class RustDiagnosticHandler final : public DiagnosticHandler { public: - RustDiagnosticHandler(LLVMDiagnosticHandlerTy DiagnosticHandlerCallback, - void *DiagnosticHandlerContext, - bool RemarkAllPasses, - std::vector RemarkPasses) + RustDiagnosticHandler( + LLVMDiagnosticHandlerTy DiagnosticHandlerCallback, + void *DiagnosticHandlerContext, + bool RemarkAllPasses, + std::vector RemarkPasses, + std::unique_ptr RemarksFile, + std::unique_ptr RemarkStreamer, + std::unique_ptr LlvmRemarkStreamer + ) : DiagnosticHandlerCallback(DiagnosticHandlerCallback), DiagnosticHandlerContext(DiagnosticHandlerContext), RemarkAllPasses(RemarkAllPasses), - RemarkPasses(RemarkPasses) {} + RemarkPasses(std::move(RemarkPasses)), + RemarksFile(std::move(RemarksFile)), + RemarkStreamer(std::move(RemarkStreamer)), + LlvmRemarkStreamer(std::move(LlvmRemarkStreamer)) {} virtual bool handleDiagnostics(const DiagnosticInfo &DI) override { + // If this diagnostic is one of the optimization remark kinds, we can check if it's enabled + // before emitting it. This can avoid many short-lived allocations when unpacking the + // diagnostic and converting its various C++ strings into rust strings. + // FIXME: some diagnostic infos still allocate before we get here, and avoiding that would be + // good in the future. That will require changing a few call sites in LLVM. + if (auto *OptDiagBase = dyn_cast(&DI)) { + if (OptDiagBase->isEnabled()) { + if (this->LlvmRemarkStreamer) { + this->LlvmRemarkStreamer->emit(*OptDiagBase); + return true; + } + } else { + return true; + } + } if (DiagnosticHandlerCallback) { DiagnosticHandlerCallback(DI, DiagnosticHandlerContext); return true; @@ -1910,14 +1948,69 @@ extern "C" void LLVMRustContextConfigureDiagnosticHandler( bool RemarkAllPasses = false; std::vector RemarkPasses; + + // Since LlvmRemarkStreamer contains a pointer to RemarkStreamer, the ordering of the three + // members below is important. + std::unique_ptr RemarksFile; + std::unique_ptr RemarkStreamer; + std::unique_ptr LlvmRemarkStreamer; }; std::vector Passes; for (size_t I = 0; I != RemarkPassesLen; ++I) + { Passes.push_back(RemarkPasses[I]); + } + + // We need to hold onto both the streamers and the opened file + std::unique_ptr RemarkFile; + std::unique_ptr RemarkStreamer; + std::unique_ptr LlvmRemarkStreamer; + + if (RemarkFilePath != nullptr) { + if (PGOAvailable) { + // Enable PGO hotness data for remarks, if available + unwrap(C)->setDiagnosticsHotnessRequested(true); + } + + std::error_code EC; + RemarkFile = std::make_unique( + RemarkFilePath, + EC, + llvm::sys::fs::OF_TextWithCRLF + ); + if (EC) { + std::string Error = std::string("Cannot create remark file: ") + + toString(errorCodeToError(EC)); + report_fatal_error(Twine(Error)); + } + + // Do not delete the file after we gather remarks + RemarkFile->keep(); + + auto RemarkSerializer = remarks::createRemarkSerializer( + llvm::remarks::Format::YAML, + remarks::SerializerMode::Separate, + RemarkFile->os() + ); + if (Error E = RemarkSerializer.takeError()) + { + std::string Error = std::string("Cannot create remark serializer: ") + toString(std::move(E)); + report_fatal_error(Twine(Error)); + } + RemarkStreamer = std::make_unique(std::move(*RemarkSerializer)); + LlvmRemarkStreamer = std::make_unique(*RemarkStreamer); + } unwrap(C)->setDiagnosticHandler(std::make_unique( - DiagnosticHandlerCallback, DiagnosticHandlerContext, RemarkAllPasses, Passes)); + DiagnosticHandlerCallback, + DiagnosticHandlerContext, + RemarkAllPasses, + Passes, + std::move(RemarkFile), + std::move(RemarkStreamer), + std::move(LlvmRemarkStreamer) + )); } extern "C" void LLVMRustGetMangledName(LLVMValueRef V, RustStringRef Str) { @@ -1926,16 +2019,7 @@ extern "C" void LLVMRustGetMangledName(LLVMValueRef V, RustStringRef Str) { Mangler().getNameWithPrefix(OS, GV, true); } -// LLVMGetAggregateElement was added in LLVM 15. For earlier LLVM versions just -// use its implementation. -#if LLVM_VERSION_LT(15, 0) -extern "C" LLVMValueRef LLVMGetAggregateElement(LLVMValueRef C, unsigned Idx) { - return wrap(unwrap(C)->getAggregateElement(Idx)); -} -#endif - extern "C" int32_t LLVMRustGetElementTypeArgIndex(LLVMValueRef CallSite) { -#if LLVM_VERSION_GE(15, 0) auto *CB = unwrap(CallSite); switch (CB->getIntrinsicID()) { case Intrinsic::arm_ldrex: @@ -1943,10 +2027,20 @@ extern "C" int32_t LLVMRustGetElementTypeArgIndex(LLVMValueRef CallSite) { case Intrinsic::arm_strex: return 1; } -#endif return -1; } extern "C" bool LLVMRustIsBitcode(char *ptr, size_t len) { return identify_magic(StringRef(ptr, len)) == file_magic::bitcode; } + +extern "C" bool LLVMRustIsNonGVFunctionPointerTy(LLVMValueRef V) { + if (unwrap(V)->getType()->isPointerTy()) { + if (auto *GV = dyn_cast(unwrap(V))) { + if (GV->getValueType()->isFunctionTy()) + return false; + } + return true; + } + return false; +} diff --git a/compiler/rustc_llvm/llvm-wrapper/SymbolWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/SymbolWrapper.cpp index 0493d6b05d030..bf00d11edf6d8 100644 --- a/compiler/rustc_llvm/llvm-wrapper/SymbolWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/SymbolWrapper.cpp @@ -7,6 +7,7 @@ // * https://github.com/llvm/llvm-project/blob/8ef3e895ad8ab1724e2b87cabad1dacdc7a397a3/llvm/include/llvm/Object/ArchiveWriter.h // * https://github.com/llvm/llvm-project/blob/8ef3e895ad8ab1724e2b87cabad1dacdc7a397a3/llvm/lib/Object/ArchiveWriter.cpp +#include "llvm/ADT/SmallString.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Object/ObjectFile.h" diff --git a/compiler/rustc_llvm/src/lib.rs b/compiler/rustc_llvm/src/lib.rs index a49ded4fd7baa..eb70961503d1d 100644 --- a/compiler/rustc_llvm/src/lib.rs +++ b/compiler/rustc_llvm/src/lib.rs @@ -102,6 +102,14 @@ pub fn initialize_available_targets() { LLVMInitializeM68kAsmPrinter, LLVMInitializeM68kAsmParser ); + init_target!( + llvm_component = "csky", + LLVMInitializeCSKYTargetInfo, + LLVMInitializeCSKYTarget, + LLVMInitializeCSKYTargetMC, + LLVMInitializeCSKYAsmPrinter, + LLVMInitializeCSKYAsmParser + ); init_target!( llvm_component = "loongarch", LLVMInitializeLoongArchTargetInfo, diff --git a/compiler/rustc_log/Cargo.toml b/compiler/rustc_log/Cargo.toml index 7f955b0a75090..aa6e46cd8de7c 100644 --- a/compiler/rustc_log/Cargo.toml +++ b/compiler/rustc_log/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" tracing = "0.1.28" tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] } tracing-tree = "0.2.0" -tracing-core = "0.1.28" +tracing-core = "=0.1.30" # FIXME(Nilstrieb) tracing has a deadlock: https://github.com/tokio-rs/tracing/issues/2635 [dev-dependencies] rustc_span = { path = "../rustc_span" } diff --git a/compiler/rustc_log/src/lib.rs b/compiler/rustc_log/src/lib.rs index 3cbb2c21e289e..e7b80c64184bf 100644 --- a/compiler/rustc_log/src/lib.rs +++ b/compiler/rustc_log/src/lib.rs @@ -123,7 +123,7 @@ where return Ok(()); } let backtrace = std::backtrace::Backtrace::capture(); - writeln!(writer, "stack backtrace: \n{:?}", backtrace) + writeln!(writer, "stack backtrace: \n{backtrace:?}") } } diff --git a/compiler/rustc_macros/Cargo.toml b/compiler/rustc_macros/Cargo.toml index 1f1201b0035a9..17651ce959879 100644 --- a/compiler/rustc_macros/Cargo.toml +++ b/compiler/rustc_macros/Cargo.toml @@ -8,6 +8,6 @@ proc-macro = true [dependencies] synstructure = "0.13.0" -syn = { version = "2", features = ["full"] } +syn = { version = "2.0.9", features = ["full"] } proc-macro2 = "1" quote = "1" diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs index 12a954258d1de..04b7c5feebe56 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs @@ -1,5 +1,7 @@ #![deny(unused_must_use)] +use std::cell::RefCell; + use crate::diagnostics::diagnostic_builder::{DiagnosticDeriveBuilder, DiagnosticDeriveKind}; use crate::diagnostics::error::{span_err, DiagnosticDeriveError}; use crate::diagnostics::utils::SetOnce; @@ -28,6 +30,7 @@ impl<'a> DiagnosticDerive<'a> { pub(crate) fn into_tokens(self) -> TokenStream { let DiagnosticDerive { mut structure, mut builder } = self; + let slugs = RefCell::new(Vec::new()); let implementation = builder.each_variant(&mut structure, |mut builder, variant| { let preamble = builder.preamble(variant); let body = builder.body(variant); @@ -56,6 +59,7 @@ impl<'a> DiagnosticDerive<'a> { return DiagnosticDeriveError::ErrorHandled.to_compile_error(); } Some(slug) => { + slugs.borrow_mut().push(slug.clone()); quote! { let mut #diag = #handler.struct_diagnostic(crate::fluent_generated::#slug); } @@ -73,7 +77,8 @@ impl<'a> DiagnosticDerive<'a> { }); let DiagnosticDeriveKind::Diagnostic { handler } = &builder.kind else { unreachable!() }; - structure.gen_impl(quote! { + + let mut imp = structure.gen_impl(quote! { gen impl<'__diagnostic_handler_sess, G> rustc_errors::IntoDiagnostic<'__diagnostic_handler_sess, G> for @Self @@ -89,7 +94,11 @@ impl<'a> DiagnosticDerive<'a> { #implementation } } - }) + }); + for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) { + imp.extend(test); + } + imp } } @@ -124,6 +133,7 @@ impl<'a> LintDiagnosticDerive<'a> { } }); + let slugs = RefCell::new(Vec::new()); let msg = builder.each_variant(&mut structure, |mut builder, variant| { // Collect the slug by generating the preamble. let _ = builder.preamble(variant); @@ -148,6 +158,7 @@ impl<'a> LintDiagnosticDerive<'a> { DiagnosticDeriveError::ErrorHandled.to_compile_error() } Some(slug) => { + slugs.borrow_mut().push(slug.clone()); quote! { crate::fluent_generated::#slug.into() } @@ -156,7 +167,7 @@ impl<'a> LintDiagnosticDerive<'a> { }); let diag = &builder.diag; - structure.gen_impl(quote! { + let mut imp = structure.gen_impl(quote! { gen impl<'__a> rustc_errors::DecorateLint<'__a, ()> for @Self { #[track_caller] fn decorate_lint<'__b>( @@ -171,7 +182,12 @@ impl<'a> LintDiagnosticDerive<'a> { #msg } } - }) + }); + for test in slugs.borrow().iter().map(|s| generate_test(s, &structure)) { + imp.extend(test); + } + + imp } } @@ -198,3 +214,40 @@ impl Mismatch { } } } + +/// Generates a `#[test]` that verifies that all referenced variables +/// exist on this structure. +fn generate_test(slug: &syn::Path, structure: &Structure<'_>) -> TokenStream { + // FIXME: We can't identify variables in a subdiagnostic + for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) { + for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) { + if attr_name == "subdiagnostic" { + return quote!(); + } + } + } + use std::sync::atomic::{AtomicUsize, Ordering}; + // We need to make sure that the same diagnostic slug can be used multiple times without causing an + // error, so just have a global counter here. + static COUNTER: AtomicUsize = AtomicUsize::new(0); + let slug = slug.get_ident().unwrap(); + let ident = quote::format_ident!("verify_{slug}_{}", COUNTER.fetch_add(1, Ordering::Relaxed)); + let ref_slug = quote::format_ident!("{slug}_refs"); + let struct_name = &structure.ast().ident; + let variables: Vec<_> = structure + .variants() + .iter() + .flat_map(|v| v.ast().fields.iter().filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))) + .collect(); + // tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this + quote! { + #[cfg(test)] + #[test ] + fn #ident() { + let variables = [#(#variables),*]; + for vref in crate::fluent_generated::#ref_slug { + assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug)); + } + } + } +} diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index cd6e36874603f..e9a5cd9de97b0 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -14,6 +14,8 @@ use syn::Token; use syn::{parse_quote, spanned::Spanned, Attribute, Meta, Path, Type}; use synstructure::{BindingInfo, Structure, VariantInfo}; +use super::utils::SubdiagnosticVariant; + /// What kind of diagnostic is being derived - a fatal/error/warning or a lint? #[derive(Clone, PartialEq, Eq)] pub(crate) enum DiagnosticDeriveKind { @@ -150,19 +152,19 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { fn parse_subdiag_attribute( &self, attr: &Attribute, - ) -> Result, DiagnosticDeriveError> { - let Some((subdiag, slug)) = SubdiagnosticKind::from_attr(attr, self)? else { + ) -> Result, DiagnosticDeriveError> { + let Some(subdiag) = SubdiagnosticVariant::from_attr(attr, self)? else { // Some attributes aren't errors - like documentation comments - but also aren't // subdiagnostics. return Ok(None); }; - if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag { + if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag.kind { throw_invalid_attr!(attr, |diag| diag .help("consider creating a `Subdiagnostic` instead")); } - let slug = slug.unwrap_or_else(|| match subdiag { + let slug = subdiag.slug.unwrap_or_else(|| match subdiag.kind { SubdiagnosticKind::Label => parse_quote! { _subdiag::label }, SubdiagnosticKind::Note => parse_quote! { _subdiag::note }, SubdiagnosticKind::Help => parse_quote! { _subdiag::help }, @@ -171,7 +173,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(), }); - Ok(Some((subdiag, slug))) + Ok(Some((subdiag.kind, slug, subdiag.no_span))) } /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct @@ -201,14 +203,18 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { if first && (nested.input.is_empty() || nested.input.peek(Token![,])) { self.slug.set_once(path.clone(), path.span().unwrap()); first = false; - return Ok(()) + return Ok(()); } first = false; let Ok(nested) = nested.value() else { - span_err(nested.input.span().unwrap(), "diagnostic slug must be the first argument").emit(); - return Ok(()) + span_err( + nested.input.span().unwrap(), + "diagnostic slug must be the first argument", + ) + .emit(); + return Ok(()); }; if path.is_ident("code") { @@ -219,7 +225,9 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string())); }); } else { - span_err(path.span().unwrap(), "unknown argument").note("only the `code` parameter is valid after the slug").emit(); + span_err(path.span().unwrap(), "unknown argument") + .note("only the `code` parameter is valid after the slug") + .emit(); // consume the buffer so we don't have syntax errors from syn let _ = nested.parse::(); @@ -229,7 +237,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { return Ok(tokens); } - let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else { + let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else { // Some attributes aren't errors - like documentation comments - but also aren't // subdiagnostics. return Ok(quote! {}); @@ -380,7 +388,7 @@ impl<'a> DiagnosticDeriveVariantBuilder<'a> { _ => (), } - let Some((subdiag, slug)) = self.parse_subdiag_attribute(attr)? else { + let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else { // Some attributes aren't errors - like documentation comments - but also aren't // subdiagnostics. return Ok(quote! {}); diff --git a/compiler/rustc_macros/src/diagnostics/error.rs b/compiler/rustc_macros/src/diagnostics/error.rs index b37dc826d2886..84b18a6202814 100644 --- a/compiler/rustc_macros/src/diagnostics/error.rs +++ b/compiler/rustc_macros/src/diagnostics/error.rs @@ -54,7 +54,7 @@ fn path_to_string(path: &syn::Path) -> String { /// Returns an error diagnostic on span `span` with msg `msg`. #[must_use] -pub(crate) fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic { +pub(crate) fn span_err>(span: impl MultiSpan, msg: T) -> Diagnostic { Diagnostic::spanned(span, Level::Error, msg) } @@ -77,11 +77,9 @@ pub(crate) fn invalid_attr(attr: &Attribute) -> Diagnostic { let span = attr.span().unwrap(); let path = path_to_string(attr.path()); match attr.meta { - Meta::Path(_) => span_err(span, &format!("`#[{path}]` is not a valid attribute")), - Meta::NameValue(_) => { - span_err(span, &format!("`#[{path} = ...]` is not a valid attribute")) - } - Meta::List(_) => span_err(span, &format!("`#[{path}(...)]` is not a valid attribute")), + Meta::Path(_) => span_err(span, format!("`#[{path}]` is not a valid attribute")), + Meta::NameValue(_) => span_err(span, format!("`#[{path} = ...]` is not a valid attribute")), + Meta::List(_) => span_err(span, format!("`#[{path}(...)]` is not a valid attribute")), } } diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs index 374ba1a45c06e..877e9745054ed 100644 --- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs +++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs @@ -14,6 +14,8 @@ use quote::{format_ident, quote}; use syn::{spanned::Spanned, Attribute, Meta, MetaList, Path}; use synstructure::{BindingInfo, Structure, VariantInfo}; +use super::utils::SubdiagnosticVariant; + /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct. pub(crate) struct SubdiagnosticDeriveBuilder { diag: syn::Ident, @@ -180,11 +182,15 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics { } impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { - fn identify_kind(&mut self) -> Result, DiagnosticDeriveError> { + fn identify_kind( + &mut self, + ) -> Result, DiagnosticDeriveError> { let mut kind_slugs = vec![]; for attr in self.variant.ast().attrs { - let Some((kind, slug)) = SubdiagnosticKind::from_attr(attr, self)? else { + let Some(SubdiagnosticVariant { kind, slug, no_span }) = + SubdiagnosticVariant::from_attr(attr, self)? + else { // Some attributes aren't errors - like documentation comments - but also aren't // subdiagnostics. continue; @@ -196,13 +202,13 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { throw_span_err!( attr.span().unwrap(), - &format!( + format!( "diagnostic slug must be first argument of a `#[{name}(...)]` attribute" ) ); }; - kind_slugs.push((kind, slug)); + kind_slugs.push((kind, slug, no_span)); } Ok(kind_slugs) @@ -487,7 +493,8 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { } }; - let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect(); + let kind_stats: KindsStatistics = + kind_slugs.iter().map(|(kind, _slug, _no_span)| kind).collect(); let init = if kind_stats.has_multipart_suggestion { quote! { let mut suggestions = Vec::new(); } @@ -508,13 +515,17 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { let diag = &self.parent.diag; let f = &self.parent.f; let mut calls = TokenStream::new(); - for (kind, slug) in kind_slugs { + for (kind, slug, no_span) in kind_slugs { let message = format_ident!("__message"); calls.extend( quote! { let #message = #f(#diag, crate::fluent_generated::#slug.into()); }, ); - let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind); + let name = format_ident!( + "{}{}", + if span_field.is_some() && !no_span { "span_" } else { "" }, + kind + ); let call = match kind { SubdiagnosticKind::Suggestion { suggestion_kind, @@ -566,7 +577,7 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> { } } _ => { - if let Some(span) = span_field { + if let Some(span) = span_field && !no_span { quote! { #diag.#name(#span, #message); } } else { quote! { #diag.#name(#message); } diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index e2434981f8d17..125632921816b 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -347,7 +347,7 @@ pub(crate) trait HasFieldMap { None => { span_err( span.unwrap(), - &format!("`{field}` doesn't refer to a field on this type"), + format!("`{field}` doesn't refer to a field on this type"), ) .emit(); quote! { @@ -597,14 +597,20 @@ pub(super) enum SubdiagnosticKind { }, } -impl SubdiagnosticKind { - /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`, - /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the +pub(super) struct SubdiagnosticVariant { + pub(super) kind: SubdiagnosticKind, + pub(super) slug: Option, + pub(super) no_span: bool, +} + +impl SubdiagnosticVariant { + /// Constructs a `SubdiagnosticVariant` from a field or type attribute such as `#[note]`, + /// `#[error(parser::add_paren, no_span)]` or `#[suggestion(code = "...")]`. Returns the /// `SubdiagnosticKind` and the diagnostic slug, if specified. pub(super) fn from_attr( attr: &Attribute, fields: &impl HasFieldMap, - ) -> Result)>, DiagnosticDeriveError> { + ) -> Result, DiagnosticDeriveError> { // Always allow documentation comments. if is_doc_comment(attr) { return Ok(None); @@ -679,7 +685,7 @@ impl SubdiagnosticKind { | SubdiagnosticKind::Help | SubdiagnosticKind::Warn | SubdiagnosticKind::MultipartSuggestion { .. } => { - return Ok(Some((kind, None))); + return Ok(Some(SubdiagnosticVariant { kind, slug: None, no_span: false })); } SubdiagnosticKind::Suggestion { .. } => { throw_span_err!(span, "suggestion without `code = \"...\"`") @@ -696,11 +702,14 @@ impl SubdiagnosticKind { let mut first = true; let mut slug = None; + let mut no_span = false; list.parse_nested_meta(|nested| { if nested.input.is_empty() || nested.input.peek(Token![,]) { if first { slug = Some(nested.path); + } else if nested.path.is_ident("no_span") { + no_span = true; } else { span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit(); } @@ -775,19 +784,19 @@ impl SubdiagnosticKind { (_, SubdiagnosticKind::Suggestion { .. }) => { span_err(path_span, "invalid nested attribute") .help( - "only `style`, `code` and `applicability` are valid nested attributes", + "only `no_span`, `style`, `code` and `applicability` are valid nested attributes", ) .emit(); has_errors = true; } (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { span_err(path_span, "invalid nested attribute") - .help("only `style` and `applicability` are valid nested attributes") + .help("only `no_span`, `style` and `applicability` are valid nested attributes") .emit(); has_errors = true; } _ => { - span_err(path_span, "invalid nested attribute").emit(); + span_err(path_span, "only `no_span` is a valid nested attribute").emit(); has_errors = true; } } @@ -831,7 +840,7 @@ impl SubdiagnosticKind { | SubdiagnosticKind::Warn => {} } - Ok(Some((kind, slug))) + Ok(Some(SubdiagnosticVariant { kind, slug, no_span })) } } diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 904f8eb5731ec..f4593d0fe736d 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -7,6 +7,7 @@ #![allow(rustc::default_hash_types)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #![recursion_limit = "128"] use synstructure::decl_derive; diff --git a/compiler/rustc_macros/src/newtype.rs b/compiler/rustc_macros/src/newtype.rs index 415a89b0f92c7..72b47de1abc29 100644 --- a/compiler/rustc_macros/src/newtype.rs +++ b/compiler/rustc_macros/src/newtype.rs @@ -36,7 +36,8 @@ impl Parse for Newtype { false } "max" => { - let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else { + let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta + else { panic!("#[max = NUMBER] attribute requires max value"); }; @@ -47,7 +48,8 @@ impl Parse for Newtype { false } "debug_format" => { - let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta else { + let Meta::NameValue(MetaNameValue { value: Expr::Lit(lit), .. }) = &attr.meta + else { panic!("#[debug_format = FMT] attribute requires a format"); }; diff --git a/compiler/rustc_macros/src/serialize.rs b/compiler/rustc_macros/src/serialize.rs index 8d017d149f629..ba75517d7a66d 100644 --- a/compiler/rustc_macros/src/serialize.rs +++ b/compiler/rustc_macros/src/serialize.rs @@ -43,7 +43,7 @@ fn decodable_body( let ty_name = s.ast().ident.to_string(); let decode_body = match s.variants() { [] => { - let message = format!("`{}` has no variants to decode", ty_name); + let message = format!("`{ty_name}` has no variants to decode"); quote! { panic!(#message) } @@ -59,14 +59,14 @@ fn decodable_body( }) .collect(); let message = format!( - "invalid enum variant tag while decoding `{}`, expected 0..{}", + "invalid enum variant tag while decoding `{}`, expected 0..{}, actual {{}}", ty_name, variants.len() ); quote! { match ::rustc_serialize::Decoder::read_usize(__decoder) { #match_inner - _ => panic!(#message), + n => panic!(#message, n), } } } diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl index 79b8b41725704..cc58d51befdcf 100644 --- a/compiler/rustc_metadata/messages.ftl +++ b/compiler/rustc_metadata/messages.ftl @@ -1,288 +1,297 @@ -metadata_rlib_required = - crate `{$crate_name}` required to be available in rlib format, but was not found in this form +metadata_as_needed_compatibility = + linking modifier `as-needed` is only compatible with `dylib` and `framework` linking kinds -metadata_lib_required = - crate `{$crate_name}` required to be available in {$kind} format, but was not found in this form +metadata_bad_panic_strategy = + the linked panic runtime `{$runtime}` is not compiled with this crate's panic strategy `{$strategy}` -metadata_rustc_lib_required = - crate `{$crate_name}` required to be available in {$kind} format, but was not found in this form - .note = only .rmeta files are distributed for `rustc_private` crates other than `rustc_driver` - .help = try adding `extern crate rustc_driver;` at the top level of this crate +metadata_binary_output_to_tty = + option `-o` or `--emit` is used to write binary output type `metadata` to stdout, but stdout is a tty -metadata_crate_dep_multiple = - cannot satisfy dependencies so `{$crate_name}` only shows up once - .help = having upstream crates all available in one format will likely make this go away +metadata_bundle_needs_static = + linking modifier `bundle` is only compatible with `static` linking kind -metadata_two_panic_runtimes = - cannot link together two panic runtimes: {$prev_name} and {$cur_name} +metadata_cannot_find_crate = + can't find crate for `{$crate_name}`{$add_info} -metadata_bad_panic_strategy = - the linked panic runtime `{$runtime}` is not compiled with this crate's panic strategy `{$strategy}` +metadata_cant_find_crate = + can't find crate -metadata_required_panic_strategy = - the crate `{$crate_name}` requires panic strategy `{$found_strategy}` which is incompatible with this crate's strategy of `{$desired_strategy}` +metadata_compiler_missing_profiler = + the compiler may have been built without the profiler runtime -metadata_incompatible_panic_in_drop_strategy = - the crate `{$crate_name}` is compiled with the panic-in-drop strategy `{$found_strategy}` which is incompatible with this crate's strategy of `{$desired_strategy}` +metadata_conflicting_alloc_error_handler = + the `#[alloc_error_handler]` in {$other_crate_name} conflicts with allocation error handler in: {$crate_name} -metadata_multiple_names_in_link = - multiple `name` arguments in a single `#[link]` attribute +metadata_conflicting_global_alloc = + the `#[global_allocator]` in {$other_crate_name} conflicts with global allocator in: {$crate_name} -metadata_multiple_kinds_in_link = - multiple `kind` arguments in a single `#[link]` attribute +metadata_consider_adding_std = + consider adding the standard library to the sysroot with `x build library --target {$locator_triple}` -metadata_link_name_form = - link name must be of the form `name = "string"` +metadata_consider_building_std = + consider building the standard library from source with `cargo build -Zbuild-std` -metadata_link_kind_form = - link kind must be of the form `kind = "string"` +metadata_consider_downloading_target = + consider downloading the target with `rustup target add {$locator_triple}` -metadata_link_modifiers_form = - link modifiers must be of the form `modifiers = "string"` +metadata_crate_dep_multiple = + cannot satisfy dependencies so `{$crate_name}` only shows up once + .help = having upstream crates all available in one format will likely make this go away -metadata_link_cfg_form = - link cfg must be of the form `cfg(/* predicate */)` +metadata_crate_location_unknown_type = + extern location for {$crate_name} is of an unknown type: {$path} -metadata_wasm_import_form = - wasm import module must be of the form `wasm_import_module = "string"` +metadata_crate_not_panic_runtime = + the crate `{$crate_name}` is not a panic runtime + +metadata_dl_error = + {$err} metadata_empty_link_name = link name must not be empty .label = empty link name -metadata_link_framework_apple = - link kind `framework` is only supported on Apple targets +metadata_empty_renaming_target = + an empty renaming target was specified for library `{$lib_name}` -metadata_framework_only_windows = - link kind `raw-dylib` is only supported on Windows targets +metadata_extern_location_not_exist = + extern location for {$crate_name} does not exist: {$location} -metadata_unknown_link_kind = - unknown link kind `{$kind}`, expected one of: static, dylib, framework, raw-dylib - .label = unknown link kind +metadata_extern_location_not_file = + extern location for {$crate_name} is not a file: {$location} -metadata_multiple_link_modifiers = - multiple `modifiers` arguments in a single `#[link]` attribute +metadata_fail_create_file_encoder = + failed to create file encoder: {$err} -metadata_multiple_cfgs = - multiple `cfg` arguments in a single `#[link]` attribute +metadata_fail_seek_file = + failed to seek the file: {$err} -metadata_link_cfg_single_predicate = - link cfg must have a single predicate argument +metadata_fail_write_file = + failed to write to the file: {$err} -metadata_multiple_wasm_import = - multiple `wasm_import_module` arguments in a single `#[link]` attribute +metadata_failed_copy_to_stdout = + failed to copy {$filename} to stdout: {$err} -metadata_unexpected_link_arg = - unexpected `#[link]` argument, expected one of: name, kind, modifiers, cfg, wasm_import_module, import_name_type +metadata_failed_create_encoded_metadata = + failed to create encoded metadata from file: {$err} -metadata_invalid_link_modifier = - invalid linking modifier syntax, expected '+' or '-' prefix before one of: bundle, verbatim, whole-archive, as-needed +metadata_failed_create_file = + failed to create the file {$filename}: {$err} -metadata_multiple_modifiers = - multiple `{$modifier}` modifiers in a single `modifiers` argument +metadata_failed_create_tempdir = + couldn't create a temp dir: {$err} -metadata_bundle_needs_static = - linking modifier `bundle` is only compatible with `static` linking kind +metadata_failed_write_error = + failed to write {$filename}: {$err} -metadata_whole_archive_needs_static = - linking modifier `whole-archive` is only compatible with `static` linking kind +metadata_found_crate_versions = + the following crate versions were found:{$found_crates} -metadata_as_needed_compatibility = - linking modifier `as-needed` is only compatible with `dylib` and `framework` linking kinds +metadata_found_staticlib = + found staticlib `{$crate_name}` instead of rlib or dylib{$add_info} + .help = please recompile that crate using --crate-type lib -metadata_unknown_link_modifier = - unknown linking modifier `{$modifier}`, expected one of: bundle, verbatim, whole-archive, as-needed +metadata_framework_only_windows = + link kind `raw-dylib` is only supported on Windows targets + +metadata_global_alloc_required = + no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait + +metadata_import_name_type_form = + import name type must be of the form `import_name_type = "string"` + +metadata_import_name_type_raw = + import name type can only be used with link kind `raw-dylib` + +metadata_import_name_type_x86 = + import name type is only supported on x86 + +metadata_incompatible_panic_in_drop_strategy = + the crate `{$crate_name}` is compiled with the panic-in-drop strategy `{$found_strategy}` which is incompatible with this crate's strategy of `{$desired_strategy}` + +metadata_incompatible_rustc = + found crate `{$crate_name}` compiled by an incompatible version of rustc{$add_info} + .help = please recompile that crate using this compiler ({$rustc_version}) (consider running `cargo clean` first) metadata_incompatible_wasm_link = `wasm_import_module` is incompatible with other arguments in `#[link]` attributes -metadata_link_requires_name = - `#[link]` attribute requires a `name = "string"` argument - .label = missing `name` argument +metadata_install_missing_components = + maybe you need to install the missing components with: `rustup component add rust-src rustc-dev llvm-tools-preview` -metadata_raw_dylib_no_nul = - link name must not contain NUL characters if link kind is `raw-dylib` +metadata_invalid_link_modifier = + invalid linking modifier syntax, expected '+' or '-' prefix before one of: bundle, verbatim, whole-archive, as-needed -metadata_link_ordinal_raw_dylib = - `#[link_ordinal]` is only supported if link kind is `raw-dylib` +metadata_invalid_meta_files = + found invalid metadata files for crate `{$crate_name}`{$add_info} + +metadata_lib_filename_form = + file name should be lib*.rlib or {$dll_prefix}*{$dll_suffix} metadata_lib_framework_apple = library kind `framework` is only supported on Apple targets -metadata_empty_renaming_target = - an empty renaming target was specified for library `{$lib_name}` +metadata_lib_required = + crate `{$crate_name}` required to be available in {$kind} format, but was not found in this form -metadata_renaming_no_link = - renaming of the library `{$lib_name}` was specified, however this crate contains no `#[link(...)]` attributes referencing this library +metadata_link_cfg_form = + link cfg must be of the form `cfg(/* predicate */)` -metadata_multiple_renamings = - multiple renamings were specified for library `{$lib_name}` +metadata_link_cfg_single_predicate = + link cfg must have a single predicate argument -metadata_no_link_mod_override = - overriding linking modifiers from command line is not supported +metadata_link_framework_apple = + link kind `framework` is only supported on Apple targets -metadata_unsupported_abi_i686 = - ABI not supported by `#[link(kind = "raw-dylib")]` on i686 +metadata_link_kind_form = + link kind must be of the form `kind = "string"` -metadata_unsupported_abi = - ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture +metadata_link_modifiers_form = + link modifiers must be of the form `modifiers = "string"` -metadata_fail_create_file_encoder = - failed to create file encoder: {$err} +metadata_link_name_form = + link name must be of the form `name = "string"` -metadata_fail_seek_file = - failed to seek the file: {$err} +metadata_link_ordinal_raw_dylib = + `#[link_ordinal]` is only supported if link kind is `raw-dylib` -metadata_fail_write_file = - failed to write to the file: {$err} +metadata_link_requires_name = + `#[link]` attribute requires a `name = "string"` argument + .label = missing `name` argument -metadata_crate_not_panic_runtime = - the crate `{$crate_name}` is not a panic runtime +metadata_missing_native_library = + could not find native static library `{$libname}`, perhaps an -L flag is missing? -metadata_no_panic_strategy = - the crate `{$crate_name}` does not have the panic strategy `{$strategy}` +metadata_multiple_candidates = + multiple candidates for `{$flavor}` dependency `{$crate_name}` found -metadata_profiler_builtins_needs_core = - `profiler_builtins` crate (required by compiler options) is not compatible with crate attribute `#![no_core]` +metadata_multiple_cfgs = + multiple `cfg` arguments in a single `#[link]` attribute -metadata_not_profiler_runtime = - the crate `{$crate_name}` is not a profiler runtime +metadata_multiple_import_name_type = + multiple `import_name_type` arguments in a single `#[link]` attribute -metadata_no_multiple_global_alloc = - cannot define multiple global allocators - .label = cannot define a new global allocator +metadata_multiple_kinds_in_link = + multiple `kind` arguments in a single `#[link]` attribute -metadata_prev_global_alloc = - previous global allocator defined here +metadata_multiple_link_modifiers = + multiple `modifiers` arguments in a single `#[link]` attribute -metadata_no_multiple_alloc_error_handler = - cannot define multiple allocation error handlers - .label = cannot define a new allocation error handler +metadata_multiple_modifiers = + multiple `{$modifier}` modifiers in a single `modifiers` argument -metadata_prev_alloc_error_handler = - previous allocation error handler defined here +metadata_multiple_names_in_link = + multiple `name` arguments in a single `#[link]` attribute -metadata_conflicting_global_alloc = - the `#[global_allocator]` in {$other_crate_name} conflicts with global allocator in: {$crate_name} +metadata_multiple_renamings = + multiple renamings were specified for library `{$lib_name}` -metadata_conflicting_alloc_error_handler = - the `#[alloc_error_handler]` in {$other_crate_name} conflicts with allocation error handler in: {$crate_name} +metadata_multiple_wasm_import = + multiple `wasm_import_module` arguments in a single `#[link]` attribute -metadata_global_alloc_required = - no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait +metadata_newer_crate_version = + found possibly newer version of crate `{$crate_name}`{$add_info} + .note = perhaps that crate needs to be recompiled? -metadata_no_transitive_needs_dep = - the crate `{$crate_name}` cannot depend on a crate that needs {$needs_crate_name}, but it depends on `{$deps_crate_name}` +metadata_no_crate_with_triple = + couldn't find crate `{$crate_name}` with expected target triple {$locator_triple}{$add_info} -metadata_failed_write_error = - failed to write {$filename}: {$err} +metadata_no_dylib_plugin = + plugin `{$crate_name}` only found in rlib format, but must be available in dylib format -metadata_missing_native_library = - could not find native static library `{$libname}`, perhaps an -L flag is missing? +metadata_no_link_mod_override = + overriding linking modifiers from command line is not supported -metadata_only_provide_library_name = only provide the library name `{$suggested_name}`, not the full filename +metadata_no_multiple_alloc_error_handler = + cannot define multiple allocation error handlers + .label = cannot define a new allocation error handler -metadata_failed_create_tempdir = - couldn't create a temp dir: {$err} +metadata_no_multiple_global_alloc = + cannot define multiple global allocators + .label = cannot define a new global allocator -metadata_failed_create_file = - failed to create the file {$filename}: {$err} +metadata_no_panic_strategy = + the crate `{$crate_name}` does not have the panic strategy `{$strategy}` -metadata_failed_create_encoded_metadata = - failed to create encoded metadata from file: {$err} +metadata_no_transitive_needs_dep = + the crate `{$crate_name}` cannot depend on a crate that needs {$needs_crate_name}, but it depends on `{$deps_crate_name}` metadata_non_ascii_name = cannot load a crate with a non-ascii name `{$crate_name}` -metadata_extern_location_not_exist = - extern location for {$crate_name} does not exist: {$location} - -metadata_extern_location_not_file = - extern location for {$crate_name} is not a file: {$location} - -metadata_multiple_candidates = - multiple candidates for `{$flavor}` dependency `{$crate_name}` found +metadata_not_profiler_runtime = + the crate `{$crate_name}` is not a profiler runtime -metadata_symbol_conflicts_current = - the current crate is indistinguishable from one of its dependencies: it has the same crate-name `{$crate_name}` and was compiled with the same `-C metadata` arguments. This will result in symbol conflicts between the two. +metadata_only_provide_library_name = only provide the library name `{$suggested_name}`, not the full filename -metadata_symbol_conflicts_others = - found two different crates with name `{$crate_name}` that are not distinguished by differing `-C metadata`. This will result in symbol conflicts between the two. +metadata_prev_alloc_error_handler = + previous allocation error handler defined here -metadata_stable_crate_id_collision = - found crates (`{$crate_name0}` and `{$crate_name1}`) with colliding StableCrateId values. +metadata_prev_global_alloc = + previous global allocator defined here -metadata_dl_error = - {$err} +metadata_profiler_builtins_needs_core = + `profiler_builtins` crate (required by compiler options) is not compatible with crate attribute `#![no_core]` -metadata_newer_crate_version = - found possibly newer version of crate `{$crate_name}`{$add_info} - .note = perhaps that crate needs to be recompiled? +metadata_raw_dylib_no_nul = + link name must not contain NUL characters if link kind is `raw-dylib` -metadata_found_crate_versions = - the following crate versions were found:{$found_crates} +metadata_renaming_no_link = + renaming of the library `{$lib_name}` was specified, however this crate contains no `#[link(...)]` attributes referencing this library -metadata_no_crate_with_triple = - couldn't find crate `{$crate_name}` with expected target triple {$locator_triple}{$add_info} +metadata_required_panic_strategy = + the crate `{$crate_name}` requires panic strategy `{$found_strategy}` which is incompatible with this crate's strategy of `{$desired_strategy}` -metadata_found_staticlib = - found staticlib `{$crate_name}` instead of rlib or dylib{$add_info} - .help = please recompile that crate using --crate-type lib +metadata_rlib_required = + crate `{$crate_name}` required to be available in rlib format, but was not found in this form -metadata_incompatible_rustc = - found crate `{$crate_name}` compiled by an incompatible version of rustc{$add_info} - .help = please recompile that crate using this compiler ({$rustc_version}) (consider running `cargo clean` first) +metadata_rustc_lib_required = + crate `{$crate_name}` required to be available in {$kind} format, but was not found in this form + .note = only .rmeta files are distributed for `rustc_private` crates other than `rustc_driver` + .help = try adding `extern crate rustc_driver;` at the top level of this crate -metadata_invalid_meta_files = - found invalid metadata files for crate `{$crate_name}`{$add_info} +metadata_stable_crate_id_collision = + found crates (`{$crate_name0}` and `{$crate_name1}`) with colliding StableCrateId values. -metadata_cannot_find_crate = - can't find crate for `{$crate_name}`{$add_info} +metadata_std_required = + `std` is required by `{$current_crate}` because it does not declare `#![no_std]` -metadata_no_dylib_plugin = - plugin `{$crate_name}` only found in rlib format, but must be available in dylib format +metadata_symbol_conflicts_current = + the current crate is indistinguishable from one of its dependencies: it has the same crate-name `{$crate_name}` and was compiled with the same `-C metadata` arguments. This will result in symbol conflicts between the two. -metadata_target_not_installed = - the `{$locator_triple}` target may not be installed +metadata_symbol_conflicts_others = + found two different crates with name `{$crate_name}` that are not distinguished by differing `-C metadata`. This will result in symbol conflicts between the two. metadata_target_no_std_support = the `{$locator_triple}` target may not support the standard library -metadata_consider_downloading_target = - consider downloading the target with `rustup target add {$locator_triple}` - -metadata_std_required = - `std` is required by `{$current_crate}` because it does not declare `#![no_std]` - -metadata_consider_building_std = - consider building the standard library from source with `cargo build -Zbuild-std` - -metadata_compiler_missing_profiler = - the compiler may have been built without the profiler runtime +metadata_target_not_installed = + the `{$locator_triple}` target may not be installed -metadata_install_missing_components = - maybe you need to install the missing components with: `rustup component add rust-src rustc-dev llvm-tools-preview` +metadata_two_panic_runtimes = + cannot link together two panic runtimes: {$prev_name} and {$cur_name} -metadata_cant_find_crate = - can't find crate +metadata_unexpected_link_arg = + unexpected `#[link]` argument, expected one of: name, kind, modifiers, cfg, wasm_import_module, import_name_type -metadata_crate_location_unknown_type = - extern location for {$crate_name} is of an unknown type: {$path} +metadata_unknown_import_name_type = + unknown import name type `{$import_name_type}`, expected one of: decorated, noprefix, undecorated -metadata_lib_filename_form = - file name should be lib*.rlib or {$dll_prefix}*{$dll_suffix} +metadata_unknown_link_kind = + unknown link kind `{$kind}`, expected one of: static, dylib, framework, raw-dylib + .label = unknown link kind -metadata_multiple_import_name_type = - multiple `import_name_type` arguments in a single `#[link]` attribute +metadata_unknown_link_modifier = + unknown linking modifier `{$modifier}`, expected one of: bundle, verbatim, whole-archive, as-needed -metadata_import_name_type_form = - import name type must be of the form `import_name_type = "string"` +metadata_unsupported_abi = + ABI not supported by `#[link(kind = "raw-dylib")]` on this architecture -metadata_import_name_type_x86 = - import name type is only supported on x86 +metadata_unsupported_abi_i686 = + ABI not supported by `#[link(kind = "raw-dylib")]` on i686 -metadata_unknown_import_name_type = - unknown import name type `{$import_name_type}`, expected one of: decorated, noprefix, undecorated +metadata_wasm_import_form = + wasm import module must be of the form `wasm_import_module = "string"` -metadata_import_name_type_raw = - import name type can only be used with link kind `raw-dylib` +metadata_whole_archive_needs_static = + linking modifier `whole-archive` is only compatible with `static` linking kind diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 491978d7e8fdf..fce80ab37dd1b 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -4,7 +4,7 @@ use crate::errors; use crate::locator::{CrateError, CrateLocator, CratePaths}; use crate::rmeta::{CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob}; -use rustc_ast::expand::allocator::AllocatorKind; +use rustc_ast::expand::allocator::{alloc_error_handler_name, global_fn_name, AllocatorKind}; use rustc_ast::{self as ast, *}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::svh::Svh; @@ -15,12 +15,12 @@ use rustc_hir::definitions::Definitions; use rustc_index::IndexVec; use rustc_middle::ty::TyCtxt; use rustc_session::config::{self, CrateType, ExternLocation}; -use rustc_session::cstore::ExternCrateSource; -use rustc_session::cstore::{CrateDepKind, CrateSource, ExternCrate}; +use rustc_session::cstore::{ + CrateDepKind, CrateSource, ExternCrate, ExternCrateSource, MetadataLoaderDyn, +}; use rustc_session::lint; use rustc_session::output::validate_crate_name; use rustc_session::search_paths::PathKind; -use rustc_session::Session; use rustc_span::edition::Edition; use rustc_span::symbol::{sym, Symbol}; use rustc_span::{Span, DUMMY_SP}; @@ -34,6 +34,8 @@ use std::time::Duration; use std::{cmp, env, iter}; pub struct CStore { + metadata_loader: Box, + metas: IndexVec>>, injected_panic_runtime: Option, /// This crate needs an allocator and either provides it itself, or finds it in a dependency. @@ -262,10 +264,14 @@ impl CStore { } } - pub fn new(sess: &Session) -> CStore { + pub fn new( + metadata_loader: Box, + local_stable_crate_id: StableCrateId, + ) -> CStore { let mut stable_crate_ids = StableCrateIdMap::default(); - stable_crate_ids.insert(sess.local_stable_crate_id(), LOCAL_CRATE); + stable_crate_ids.insert(local_stable_crate_id, LOCAL_CRATE); CStore { + metadata_loader, // We add an empty entry for LOCAL_CRATE (which maps to zero) in // order to make array indices in `metas` match with the // corresponding `CrateNum`. This first entry will always remain @@ -365,6 +371,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { lib: Library, dep_kind: CrateDepKind, name: Symbol, + private_dep: Option, ) -> Result { let _prof_timer = self.sess.prof.generic_activity("metadata_register_crate"); @@ -372,8 +379,13 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { let crate_root = metadata.get_root(); let host_hash = host_lib.as_ref().map(|lib| lib.metadata.get_root().hash()); - let private_dep = - self.sess.opts.externs.get(name.as_str()).is_some_and(|e| e.is_private_dep); + let private_dep = self + .sess + .opts + .externs + .get(name.as_str()) + .map_or(private_dep.unwrap_or(false), |e| e.is_private_dep) + && private_dep.unwrap_or(true); // Claim this crate number and cache it let cnum = self.cstore.intern_stable_crate_id(&crate_root)?; @@ -518,25 +530,28 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { if !name.as_str().is_ascii() { return Err(CrateError::NonAsciiName(name)); } - let (root, hash, host_hash, extra_filename, path_kind) = match dep { + let (root, hash, host_hash, extra_filename, path_kind, private_dep) = match dep { Some((root, dep)) => ( Some(root), Some(dep.hash), dep.host_hash, Some(&dep.extra_filename[..]), PathKind::Dependency, + Some(dep.is_private), ), - None => (None, None, None, None, PathKind::Crate), + None => (None, None, None, None, PathKind::Crate, None), }; let result = if let Some(cnum) = self.existing_match(name, hash, path_kind) { (LoadResult::Previous(cnum), None) } else { info!("falling back to a load"); - let metadata_loader = self.tcx.metadata_loader(()).borrow(); let mut locator = CrateLocator::new( self.sess, - &**metadata_loader, + &*self.cstore.metadata_loader, name, + // The all loop is because `--crate-type=rlib --crate-type=rlib` is + // legal and produces both inside this type. + self.tcx.crate_types().iter().all(|c| *c == CrateType::Rlib), hash, extra_filename, false, // is_host @@ -562,10 +577,13 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { dep_kind = CrateDepKind::MacrosOnly; } data.update_dep_kind(|data_dep_kind| cmp::max(data_dep_kind, dep_kind)); + if let Some(private_dep) = private_dep { + data.update_and_private_dep(private_dep); + } Ok(cnum) } (LoadResult::Loaded(library), host_library) => { - self.register_crate(host_library, root, library, dep_kind, name) + self.register_crate(host_library, root, library, dep_kind, name, private_dep) } _ => panic!(), } @@ -677,7 +695,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { fn inject_panic_runtime(&mut self, krate: &ast::Crate) { // If we're only compiling an rlib, then there's no need to select a // panic runtime, so we just skip this section entirely. - let any_non_rlib = self.sess.crate_types().iter().any(|ct| *ct != CrateType::Rlib); + let any_non_rlib = self.tcx.crate_types().iter().any(|ct| *ct != CrateType::Rlib); if !any_non_rlib { info!("panic runtime injection skipped, only generating rlib"); return; @@ -731,7 +749,9 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { }; info!("panic runtime not found -- loading {}", name); - let Some(cnum) = self.resolve_crate(name, DUMMY_SP, CrateDepKind::Implicit) else { return; }; + let Some(cnum) = self.resolve_crate(name, DUMMY_SP, CrateDepKind::Implicit) else { + return; + }; let data = self.cstore.get_crate_data(cnum); // Sanity check the loaded crate to ensure it is indeed a panic runtime @@ -764,7 +784,9 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { self.sess.emit_err(errors::ProfilerBuiltinsNeedsCore); } - let Some(cnum) = self.resolve_crate(name, DUMMY_SP, CrateDepKind::Implicit) else { return; }; + let Some(cnum) = self.resolve_crate(name, DUMMY_SP, CrateDepKind::Implicit) else { + return; + }; let data = self.cstore.get_crate_data(cnum); // Sanity check the loaded crate to ensure it is indeed a profiler runtime @@ -802,7 +824,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> { // At this point we've determined that we need an allocator. Let's see // if our compilation session actually needs an allocator based on what // we're emitting. - let all_rlib = self.sess.crate_types().iter().all(|ct| matches!(*ct, CrateType::Rlib)); + let all_rlib = self.tcx.crate_types().iter().all(|ct| matches!(*ct, CrateType::Rlib)); if all_rlib { return; } @@ -1048,7 +1070,7 @@ fn global_allocator_spans(krate: &ast::Crate) -> Vec { } } - let name = Symbol::intern(&AllocatorKind::Global.fn_name(sym::alloc)); + let name = Symbol::intern(&global_fn_name(sym::alloc)); let mut f = Finder { name, spans: Vec::new() }; visit::walk_crate(&mut f, krate); f.spans @@ -1070,7 +1092,7 @@ fn alloc_error_handler_spans(krate: &ast::Crate) -> Vec { } } - let name = Symbol::intern(&AllocatorKind::Global.fn_name(sym::oom)); + let name = Symbol::intern(alloc_error_handler_name(AllocatorKind::Global)); let mut f = Finder { name, spans: Vec::new() }; visit::walk_crate(&mut f, krate); f.spans diff --git a/compiler/rustc_metadata/src/dependency_format.rs b/compiler/rustc_metadata/src/dependency_format.rs index 72b208a713276..783d35ac7e6f2 100644 --- a/compiler/rustc_metadata/src/dependency_format.rs +++ b/compiler/rustc_metadata/src/dependency_format.rs @@ -66,8 +66,7 @@ use rustc_session::cstore::CrateDepKind; use rustc_session::cstore::LinkagePreference::{self, RequireDynamic, RequireStatic}; pub(crate) fn calculate(tcx: TyCtxt<'_>) -> Dependencies { - tcx.sess - .crate_types() + tcx.crate_types() .iter() .map(|&ty| { let linkage = calculate_type(tcx, ty); diff --git a/compiler/rustc_metadata/src/errors.rs b/compiler/rustc_metadata/src/errors.rs index a44c1dd582ed3..91220629fb669 100644 --- a/compiler/rustc_metadata/src/errors.rs +++ b/compiler/rustc_metadata/src/errors.rs @@ -395,6 +395,17 @@ pub struct FailedWriteError { pub err: Error, } +#[derive(Diagnostic)] +#[diag(metadata_failed_copy_to_stdout)] +pub struct FailedCopyToStdout { + pub filename: PathBuf, + pub err: Error, +} + +#[derive(Diagnostic)] +#[diag(metadata_binary_output_to_tty)] +pub struct BinaryOutputToTty; + #[derive(Diagnostic)] #[diag(metadata_missing_native_library)] pub struct MissingNativeLibrary<'a> { @@ -612,6 +623,7 @@ pub struct CannotFindCrate { pub is_nightly_build: bool, pub profiler_runtime: Symbol, pub locator_triple: TargetTriple, + pub is_ui_testing: bool, } impl IntoDiagnostic<'_> for CannotFindCrate { @@ -635,12 +647,19 @@ impl IntoDiagnostic<'_> for CannotFindCrate { } else { diag.note(fluent::metadata_target_no_std_support); } - // NOTE: this suggests using rustup, even though the user may not have it installed. - // That's because they could choose to install it; or this may give them a hint which - // target they need to install from their distro. + if self.missing_core { - diag.help(fluent::metadata_consider_downloading_target); + if env!("CFG_RELEASE_CHANNEL") == "dev" && !self.is_ui_testing { + // Note: Emits the nicer suggestion only for the dev channel. + diag.help(fluent::metadata_consider_adding_std); + } else { + // NOTE: this suggests using rustup, even though the user may not have it installed. + // That's because they could choose to install it; or this may give them a hint which + // target they need to install from their distro. + diag.help(fluent::metadata_consider_downloading_target); + } } + // Suggest using #![no_std]. #[no_core] is unstable and not really supported anyway. // NOTE: this is a dummy span if `extern crate std` was injected by the compiler. // If it's not a dummy, that means someone added `extern crate std` explicitly and diff --git a/compiler/rustc_metadata/src/foreign_modules.rs b/compiler/rustc_metadata/src/foreign_modules.rs index d1c2f3104d072..154eb684f1197 100644 --- a/compiler/rustc_metadata/src/foreign_modules.rs +++ b/compiler/rustc_metadata/src/foreign_modules.rs @@ -1,19 +1,28 @@ +use rustc_data_structures::fx::FxIndexMap; use rustc_hir as hir; use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_middle::query::LocalCrate; use rustc_middle::ty::TyCtxt; use rustc_session::cstore::ForeignModule; -pub(crate) fn collect(tcx: TyCtxt<'_>) -> Vec { - let mut modules = Vec::new(); +pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> FxIndexMap { + let mut modules = FxIndexMap::default(); + + // We need to collect all the `ForeignMod`, even if they are empty. for id in tcx.hir().items() { if !matches!(tcx.def_kind(id.owner_id), DefKind::ForeignMod) { continue; } + + let def_id = id.owner_id.to_def_id(); let item = tcx.hir().item(id); - if let hir::ItemKind::ForeignMod { items, .. } = item.kind { + + if let hir::ItemKind::ForeignMod { abi, items } = item.kind { let foreign_items = items.iter().map(|it| it.id.owner_id.to_def_id()).collect(); - modules.push(ForeignModule { foreign_items, def_id: id.owner_id.to_def_id() }); + modules.insert(def_id, ForeignModule { def_id, abi, foreign_items }); } } + modules } diff --git a/compiler/rustc_metadata/src/fs.rs b/compiler/rustc_metadata/src/fs.rs index 08de828fbdba2..2a9662b809aef 100644 --- a/compiler/rustc_metadata/src/fs.rs +++ b/compiler/rustc_metadata/src/fs.rs @@ -1,18 +1,19 @@ use crate::errors::{ - FailedCreateEncodedMetadata, FailedCreateFile, FailedCreateTempdir, FailedWriteError, + BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile, + FailedCreateTempdir, FailedWriteError, }; use crate::{encode_metadata, EncodedMetadata}; use rustc_data_structures::temp_dir::MaybeTempDir; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::ty::TyCtxt; -use rustc_session::config::OutputType; +use rustc_session::config::{OutFileName, OutputType}; use rustc_session::output::filename_for_metadata; use rustc_session::{MetadataKind, Session}; use tempfile::Builder as TempFileBuilder; -use std::fs; use std::path::{Path, PathBuf}; +use std::{fs, io}; // FIXME(eddyb) maybe include the crate name in this? pub const METADATA_FILENAME: &str = "lib.rmeta"; @@ -55,7 +56,7 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { // Always create a file at `metadata_filename`, even if we have nothing to write to it. // This simplifies the creation of the output `out_filename` when requested. - let metadata_kind = tcx.sess.metadata_kind(); + let metadata_kind = tcx.metadata_kind(); match metadata_kind { MetadataKind::None => { std::fs::File::create(&metadata_filename).unwrap_or_else(|err| { @@ -74,16 +75,30 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) { // this file always exists. let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata); let (metadata_filename, metadata_tmpdir) = if need_metadata_file { - if let Err(err) = non_durable_rename(&metadata_filename, &out_filename) { - tcx.sess.emit_fatal(FailedWriteError { filename: out_filename, err }); - } + let filename = match out_filename { + OutFileName::Real(ref path) => { + if let Err(err) = non_durable_rename(&metadata_filename, path) { + tcx.sess.emit_fatal(FailedWriteError { filename: path.to_path_buf(), err }); + } + path.clone() + } + OutFileName::Stdout => { + if out_filename.is_tty() { + tcx.sess.emit_err(BinaryOutputToTty); + } else if let Err(err) = copy_to_stdout(&metadata_filename) { + tcx.sess + .emit_err(FailedCopyToStdout { filename: metadata_filename.clone(), err }); + } + metadata_filename + } + }; if tcx.sess.opts.json_artifact_notifications { tcx.sess .parse_sess .span_diagnostic - .emit_artifact_notification(&out_filename, "metadata"); + .emit_artifact_notification(&out_filename.as_path(), "metadata"); } - (out_filename, None) + (filename, None) } else { (metadata_filename, Some(metadata_tmpdir)) }; @@ -116,3 +131,11 @@ pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> { let _ = std::fs::remove_file(dst); std::fs::rename(src, dst) } + +pub fn copy_to_stdout(from: &Path) -> io::Result<()> { + let file = fs::File::open(from)?; + let mut reader = io::BufReader::new(file); + let mut stdout = io::stdout(); + io::copy(&mut reader, &mut stdout)?; + Ok(()) +} diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs index 9f664d0f0c8e2..87373d9974357 100644 --- a/compiler/rustc_metadata/src/lib.rs +++ b/compiler/rustc_metadata/src/lib.rs @@ -1,6 +1,6 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(decl_macro)] -#![feature(drain_filter)] +#![feature(extract_if)] #![feature(generators)] #![feature(iter_from_generator)] #![feature(let_chains)] diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index ceb348f34690e..bf6004ba86441 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -222,7 +222,7 @@ use rustc_data_structures::owned_slice::slice_owned; use rustc_data_structures::svh::Svh; use rustc_errors::{DiagnosticArgValue, FatalError, IntoDiagnosticArg}; use rustc_fs_util::try_canonicalize; -use rustc_session::config::{self, CrateType}; +use rustc_session::config; use rustc_session::cstore::{CrateSource, MetadataLoader}; use rustc_session::filesearch::FileSearch; use rustc_session::search_paths::PathKind; @@ -305,14 +305,12 @@ impl<'a> CrateLocator<'a> { sess: &'a Session, metadata_loader: &'a dyn MetadataLoader, crate_name: Symbol, + is_rlib: bool, hash: Option, extra_filename: Option<&'a str>, is_host: bool, path_kind: PathKind, ) -> CrateLocator<'a> { - // The all loop is because `--crate-type=rlib --crate-type=rlib` is - // legal and produces both inside this type. - let is_rlib = sess.crate_types().iter().all(|c| *c == CrateType::Rlib); let needs_object_code = sess.opts.output_types.should_codegen(); // If we're producing an rlib, then we don't need object code. // Or, if we're not producing object code, then we don't need it either @@ -511,7 +509,7 @@ impl<'a> CrateLocator<'a> { rlib: self.extract_one(rlibs, CrateFlavor::Rlib, &mut slot)?, dylib: self.extract_one(dylibs, CrateFlavor::Dylib, &mut slot)?, }; - Ok(slot.map(|(svh, metadata)| (svh, Library { source, metadata }))) + Ok(slot.map(|(svh, metadata, _)| (svh, Library { source, metadata }))) } fn needs_crate_flavor(&self, flavor: CrateFlavor) -> bool { @@ -535,11 +533,13 @@ impl<'a> CrateLocator<'a> { // read the metadata from it if `*slot` is `None`. If the metadata couldn't // be read, it is assumed that the file isn't a valid rust library (no // errors are emitted). + // + // The `PathBuf` in `slot` will only be used for diagnostic purposes. fn extract_one( &mut self, m: FxHashMap, flavor: CrateFlavor, - slot: &mut Option<(Svh, MetadataBlob)>, + slot: &mut Option<(Svh, MetadataBlob, PathBuf)>, ) -> Result, CrateError> { // If we are producing an rlib, and we've already loaded metadata, then // we should not attempt to discover further crate sources (unless we're @@ -550,16 +550,9 @@ impl<'a> CrateLocator<'a> { // // See also #68149 which provides more detail on why emitting the // dependency on the rlib is a bad thing. - // - // We currently do not verify that these other sources are even in sync, - // and this is arguably a bug (see #10786), but because reading metadata - // is quite slow (especially from dylibs) we currently do not read it - // from the other crate sources. if slot.is_some() { if m.is_empty() || !self.needs_crate_flavor(flavor) { return Ok(None); - } else if m.len() == 1 { - return Ok(Some(m.into_iter().next().unwrap())); } } @@ -610,8 +603,7 @@ impl<'a> CrateLocator<'a> { candidates, )); } - err_data = Some(vec![ret.as_ref().unwrap().0.clone()]); - *slot = None; + err_data = Some(vec![slot.take().unwrap().2]); } if let Some(candidates) = &mut err_data { candidates.push(lib); @@ -644,7 +636,7 @@ impl<'a> CrateLocator<'a> { continue; } } - *slot = Some((hash, metadata)); + *slot = Some((hash, metadata, lib.clone())); ret = Some((lib, kind)); } @@ -666,31 +658,30 @@ impl<'a> CrateLocator<'a> { return None; } - let root = metadata.get_root(); - if root.is_proc_macro_crate() != self.is_proc_macro { + let header = metadata.get_header(); + if header.is_proc_macro_crate != self.is_proc_macro { info!( "Rejecting via proc macro: expected {} got {}", - self.is_proc_macro, - root.is_proc_macro_crate(), + self.is_proc_macro, header.is_proc_macro_crate, ); return None; } - if self.exact_paths.is_empty() && self.crate_name != root.name() { + if self.exact_paths.is_empty() && self.crate_name != header.name { info!("Rejecting via crate name"); return None; } - if root.triple() != &self.triple { - info!("Rejecting via crate triple: expected {} got {}", self.triple, root.triple()); + if header.triple != self.triple { + info!("Rejecting via crate triple: expected {} got {}", self.triple, header.triple); self.crate_rejections.via_triple.push(CrateMismatch { path: libpath.to_path_buf(), - got: root.triple().to_string(), + got: header.triple.to_string(), }); return None; } - let hash = root.hash(); + let hash = header.hash; if let Some(expected_hash) = self.hash { if hash != expected_hash { info!("Rejecting via hash: expected {} got {}", expected_hash, hash); @@ -805,25 +796,36 @@ fn get_metadata_section<'p>( } // Length of the compressed stream - this allows linkers to pad the section if they want - let Ok(len_bytes) = <[u8; 4]>::try_from(&buf[header_len..cmp::min(data_start, buf.len())]) else { - return Err(MetadataError::LoadFailure("invalid metadata length found".to_string())); + let Ok(len_bytes) = + <[u8; 4]>::try_from(&buf[header_len..cmp::min(data_start, buf.len())]) + else { + return Err(MetadataError::LoadFailure( + "invalid metadata length found".to_string(), + )); }; let compressed_len = u32::from_be_bytes(len_bytes) as usize; // Header is okay -> inflate the actual metadata - let compressed_bytes = &buf[data_start..(data_start + compressed_len)]; - debug!("inflating {} bytes of compressed metadata", compressed_bytes.len()); - // Assume the decompressed data will be at least the size of the compressed data, so we - // don't have to grow the buffer as much. - let mut inflated = Vec::with_capacity(compressed_bytes.len()); - FrameDecoder::new(compressed_bytes).read_to_end(&mut inflated).map_err(|_| { - MetadataError::LoadFailure(format!( - "failed to decompress metadata: {}", - filename.display() - )) - })?; + let compressed_bytes = buf.slice(|buf| &buf[data_start..(data_start + compressed_len)]); + if &compressed_bytes[..cmp::min(METADATA_HEADER.len(), compressed_bytes.len())] + == METADATA_HEADER + { + // The metadata was not actually compressed. + compressed_bytes + } else { + debug!("inflating {} bytes of compressed metadata", compressed_bytes.len()); + // Assume the decompressed data will be at least the size of the compressed data, so we + // don't have to grow the buffer as much. + let mut inflated = Vec::with_capacity(compressed_bytes.len()); + FrameDecoder::new(&*compressed_bytes).read_to_end(&mut inflated).map_err(|_| { + MetadataError::LoadFailure(format!( + "failed to decompress metadata: {}", + filename.display() + )) + })?; - slice_owned(inflated, Deref::deref) + slice_owned(inflated, Deref::deref) + } } CrateFlavor::Rmeta => { // mmap the file, because only a small fraction of it is read. @@ -879,9 +881,10 @@ fn find_plugin_registrar_impl<'a>( sess, metadata_loader, name, - None, // hash - None, // extra_filename - true, // is_host + false, // is_rlib + None, // hash + None, // extra_filename + true, // is_host PathKind::Crate, ); @@ -904,7 +907,7 @@ pub fn list_file_metadata( let flavor = get_flavor_from_path(path); match get_metadata_section(target, flavor, path, metadata_loader) { Ok(metadata) => metadata.list_crate_metadata(out), - Err(msg) => write!(out, "{}\n", msg), + Err(msg) => write!(out, "{msg}\n"), } } @@ -1126,6 +1129,7 @@ impl CrateError { is_nightly_build: sess.is_nightly_build(), profiler_runtime: Symbol::intern(&sess.opts.unstable_opts.profiler_runtime), locator_triple: locator.triple, + is_ui_testing: sess.opts.unstable_opts.ui_testing, }); } } @@ -1142,6 +1146,7 @@ impl CrateError { is_nightly_build: sess.is_nightly_build(), profiler_runtime: Symbol::intern(&sess.opts.unstable_opts.profiler_runtime), locator_triple: sess.opts.target_triple.clone(), + is_ui_testing: sess.opts.unstable_opts.ui_testing, }); } } diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs index c83c47e722bf0..098c411c8d675 100644 --- a/compiler/rustc_metadata/src/native_libs.rs +++ b/compiler/rustc_metadata/src/native_libs.rs @@ -1,15 +1,17 @@ use rustc_ast::{NestedMetaItem, CRATE_NODE_ID}; use rustc_attr as attr; use rustc_data_structures::fx::FxHashSet; -use rustc_hir as hir; -use rustc_hir::def::DefKind; +use rustc_middle::query::LocalCrate; use rustc_middle::ty::{List, ParamEnv, ParamEnvAnd, Ty, TyCtxt}; use rustc_session::config::CrateType; -use rustc_session::cstore::{DllCallingConvention, DllImport, NativeLib, PeImportNameType}; +use rustc_session::cstore::{ + DllCallingConvention, DllImport, ForeignModule, NativeLib, PeImportNameType, +}; use rustc_session::parse::feature_err; use rustc_session::search_paths::PathKind; use rustc_session::utils::NativeLibKind; use rustc_session::Session; +use rustc_span::def_id::{DefId, LOCAL_CRATE}; use rustc_span::symbol::{sym, Symbol}; use rustc_target::spec::abi::Abi; @@ -50,10 +52,11 @@ fn find_bundled_library( verbatim: Option, kind: NativeLibKind, has_cfg: bool, - sess: &Session, + tcx: TyCtxt<'_>, ) -> Option { + let sess = tcx.sess; if let NativeLibKind::Static { bundle: Some(true) | None, whole_archive } = kind - && sess.crate_types().iter().any(|t| matches!(t, &CrateType::Rlib | CrateType::Staticlib)) + && tcx.crate_types().iter().any(|t| matches!(t, &CrateType::Rlib | CrateType::Staticlib)) && (sess.opts.unstable_opts.packed_bundled_libs || has_cfg || whole_archive == Some(true)) { let verbatim = verbatim.unwrap_or(false); @@ -66,10 +69,12 @@ fn find_bundled_library( None } -pub(crate) fn collect(tcx: TyCtxt<'_>) -> Vec { +pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> Vec { let mut collector = Collector { tcx, libs: Vec::new() }; - for id in tcx.hir().items() { - collector.process_item(id); + if tcx.sess.opts.unstable_opts.link_directives { + for module in tcx.foreign_modules(LOCAL_CRATE).values() { + collector.process_module(module); + } } collector.process_command_line(); collector.libs @@ -88,29 +93,20 @@ struct Collector<'tcx> { } impl<'tcx> Collector<'tcx> { - fn process_item(&mut self, id: rustc_hir::ItemId) { - if !matches!(self.tcx.def_kind(id.owner_id), DefKind::ForeignMod) { - return; - } + fn process_module(&mut self, module: &ForeignModule) { + let ForeignModule { def_id, abi, ref foreign_items } = *module; + let def_id = def_id.expect_local(); - let it = self.tcx.hir().item(id); - let hir::ItemKind::ForeignMod { abi, items: foreign_mod_items } = it.kind else { - return; - }; + let sess = self.tcx.sess; if matches!(abi, Abi::Rust | Abi::RustIntrinsic | Abi::PlatformIntrinsic) { return; } // Process all of the #[link(..)]-style arguments - let sess = self.tcx.sess; let features = self.tcx.features(); - if !sess.opts.unstable_opts.link_directives { - return; - } - - for m in self.tcx.hir().attrs(it.hir_id()).iter().filter(|a| a.has_name(sym::link)) { + for m in self.tcx.get_attrs(def_id, sym::link) { let Some(items) = m.meta_item_list() else { continue; }; @@ -340,9 +336,9 @@ impl<'tcx> Collector<'tcx> { if name.as_str().contains('\0') { sess.emit_err(errors::RawDylibNoNul { span: name_span }); } - foreign_mod_items + foreign_items .iter() - .map(|child_item| { + .map(|&child_item| { self.build_dll_import( abi, import_name_type.map(|(import_name_type, _)| import_name_type), @@ -352,21 +348,12 @@ impl<'tcx> Collector<'tcx> { .collect() } _ => { - for child_item in foreign_mod_items { - if self.tcx.def_kind(child_item.id.owner_id).has_codegen_attrs() - && self - .tcx - .codegen_fn_attrs(child_item.id.owner_id) - .link_ordinal - .is_some() + for &child_item in foreign_items { + if self.tcx.def_kind(child_item).has_codegen_attrs() + && self.tcx.codegen_fn_attrs(child_item).link_ordinal.is_some() { - let link_ordinal_attr = self - .tcx - .hir() - .attrs(child_item.id.owner_id.into()) - .iter() - .find(|a| a.has_name(sym::link_ordinal)) - .unwrap(); + let link_ordinal_attr = + self.tcx.get_attr(child_item, sym::link_ordinal).unwrap(); sess.emit_err(errors::LinkOrdinalRawDylib { span: link_ordinal_attr.span, }); @@ -378,13 +365,13 @@ impl<'tcx> Collector<'tcx> { }; let kind = kind.unwrap_or(NativeLibKind::Unspecified); - let filename = find_bundled_library(name, verbatim, kind, cfg.is_some(), sess); + let filename = find_bundled_library(name, verbatim, kind, cfg.is_some(), self.tcx); self.libs.push(NativeLib { name, filename, kind, cfg, - foreign_module: Some(it.owner_id.to_def_id()), + foreign_module: Some(def_id.to_def_id()), verbatim, dll_imports, }); @@ -425,7 +412,7 @@ impl<'tcx> Collector<'tcx> { // can move them to the end of the list below. let mut existing = self .libs - .drain_filter(|lib| { + .extract_if(|lib| { if lib.name.as_str() == passed_lib.name { // FIXME: This whole logic is questionable, whether modifiers are // involved or not, library reordering and kind overriding without @@ -456,9 +443,13 @@ impl<'tcx> Collector<'tcx> { // Add if not found let new_name: Option<&str> = passed_lib.new_name.as_deref(); let name = Symbol::intern(new_name.unwrap_or(&passed_lib.name)); - let sess = self.tcx.sess; - let filename = - find_bundled_library(name, passed_lib.verbatim, passed_lib.kind, false, sess); + let filename = find_bundled_library( + name, + passed_lib.verbatim, + passed_lib.kind, + false, + self.tcx, + ); self.libs.push(NativeLib { name, filename, @@ -476,11 +467,11 @@ impl<'tcx> Collector<'tcx> { } } - fn i686_arg_list_size(&self, item: &hir::ForeignItemRef) -> usize { + fn i686_arg_list_size(&self, item: DefId) -> usize { let argument_types: &List> = self.tcx.erase_late_bound_regions( self.tcx - .type_of(item.id.owner_id) - .subst_identity() + .type_of(item) + .instantiate_identity() .fn_sig(self.tcx) .inputs() .map_bound(|slice| self.tcx.mk_type_list(slice)), @@ -505,8 +496,10 @@ impl<'tcx> Collector<'tcx> { &self, abi: Abi, import_name_type: Option, - item: &hir::ForeignItemRef, + item: DefId, ) -> DllImport { + let span = self.tcx.def_span(item); + let calling_convention = if self.tcx.sess.target.arch == "x86" { match abi { Abi::C { .. } | Abi::Cdecl { .. } => DllCallingConvention::C, @@ -520,29 +513,29 @@ impl<'tcx> Collector<'tcx> { DllCallingConvention::Vectorcall(self.i686_arg_list_size(item)) } _ => { - self.tcx.sess.emit_fatal(errors::UnsupportedAbiI686 { span: item.span }); + self.tcx.sess.emit_fatal(errors::UnsupportedAbiI686 { span }); } } } else { match abi { Abi::C { .. } | Abi::Win64 { .. } | Abi::System { .. } => DllCallingConvention::C, _ => { - self.tcx.sess.emit_fatal(errors::UnsupportedAbi { span: item.span }); + self.tcx.sess.emit_fatal(errors::UnsupportedAbi { span }); } } }; - let codegen_fn_attrs = self.tcx.codegen_fn_attrs(item.id.owner_id); + let codegen_fn_attrs = self.tcx.codegen_fn_attrs(item); let import_name_type = codegen_fn_attrs .link_ordinal .map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord))); DllImport { - name: codegen_fn_attrs.link_name.unwrap_or(item.ident.name), + name: codegen_fn_attrs.link_name.unwrap_or(self.tcx.item_name(item)), import_name_type, calling_convention, - span: item.span, - is_fn: self.tcx.def_kind(item.id.owner_id).is_fn_like(), + span, + is_fn: self.tcx.def_kind(item).is_fn_like(), } } } diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index cc4e60cf6ac58..e8f66c36a866d 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -9,7 +9,7 @@ use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::owned_slice::OwnedSlice; use rustc_data_structures::svh::Svh; -use rustc_data_structures::sync::{AppendOnlyVec, Lock, Lrc, OnceCell}; +use rustc_data_structures::sync::{AppendOnlyVec, AtomicBool, Lock, Lrc, OnceCell}; use rustc_data_structures::unhash::UnhashMap; use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind}; use rustc_expand::proc_macro::{AttrProcMacro, BangProcMacro, DeriveProcMacro}; @@ -25,7 +25,7 @@ use rustc_middle::mir::interpret::{AllocDecodingSession, AllocDecodingState}; use rustc_middle::ty::codec::TyDecoder; use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::GeneratorDiagnosticData; -use rustc_middle::ty::{self, ParameterizedOverTcx, Predicate, Ty, TyCtxt, Visibility}; +use rustc_middle::ty::{self, ParameterizedOverTcx, Ty, TyCtxt, Visibility}; use rustc_serialize::opaque::MemDecoder; use rustc_serialize::{Decodable, Decoder}; use rustc_session::cstore::{ @@ -34,12 +34,13 @@ use rustc_session::cstore::{ use rustc_session::Session; use rustc_span::hygiene::ExpnIndex; use rustc_span::symbol::{kw, Ident, Symbol}; -use rustc_span::{self, BytePos, ExpnId, Pos, Span, SyntaxContext, DUMMY_SP}; +use rustc_span::{self, BytePos, ExpnId, Pos, Span, SpanData, SyntaxContext, DUMMY_SP}; use proc_macro::bridge::client::ProcMacro; use std::iter::TrustedLen; use std::num::NonZeroUsize; use std::path::Path; +use std::sync::atomic::Ordering; use std::{io, iter, mem}; pub(super) use cstore_impl::provide; @@ -74,6 +75,7 @@ pub(crate) struct CrateMetadata { blob: MetadataBlob, // --- Some data pre-decoded from the metadata blob, usually for performance --- + /// Data about the top-level items in a crate, as well as various crate-level metadata. root: CrateRoot, /// Trait impl data. /// FIXME: Used only from queries and can use query cache, @@ -111,9 +113,10 @@ pub(crate) struct CrateMetadata { dep_kind: Lock, /// Filesystem location of this crate. source: Lrc, - /// Whether or not this crate should be consider a private dependency - /// for purposes of the 'exported_private_dependencies' lint - private_dep: bool, + /// Whether or not this crate should be consider a private dependency. + /// Used by the 'exported_private_dependencies' lint, and for determining + /// whether to emit suggestions that reference this crate. + private_dep: AtomicBool, /// The hash for the host proc macro. Used to support `-Z dual-proc-macro`. host_hash: Option, @@ -308,8 +311,10 @@ impl<'a, 'tcx> DecodeContext<'a, 'tcx> { #[inline] fn tcx(&self) -> TyCtxt<'tcx> { let Some(tcx) = self.tcx else { - bug!("No TyCtxt found for decoding. \ - You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`."); + bug!( + "No TyCtxt found for decoding. \ + You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`." + ); }; tcx } @@ -445,11 +450,13 @@ impl<'a, 'tcx> Decodable> for SyntaxContext { let cdata = decoder.cdata(); let Some(sess) = decoder.sess else { - bug!("Cannot decode SyntaxContext without Session.\ - You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`."); + bug!( + "Cannot decode SyntaxContext without Session.\ + You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`." + ); }; - let cname = cdata.root.name; + let cname = cdata.root.name(); rustc_span::hygiene::decode_syntax_context(decoder, &cdata.hygiene_context, |_, id| { debug!("SpecializedDecoder: decoding {}", id); cdata @@ -467,8 +474,10 @@ impl<'a, 'tcx> Decodable> for ExpnId { let local_cdata = decoder.cdata(); let Some(sess) = decoder.sess else { - bug!("Cannot decode ExpnId without Session. \ - You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`."); + bug!( + "Cannot decode ExpnId without Session. \ + You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`." + ); }; let cnum = CrateNum::decode(decoder); @@ -504,11 +513,26 @@ impl<'a, 'tcx> Decodable> for ExpnId { impl<'a, 'tcx> Decodable> for Span { fn decode(decoder: &mut DecodeContext<'a, 'tcx>) -> Span { + let mode = SpanEncodingMode::decode(decoder); + let data = match mode { + SpanEncodingMode::Direct => SpanData::decode(decoder), + SpanEncodingMode::Shorthand(position) => decoder.with_position(position, |decoder| { + let mode = SpanEncodingMode::decode(decoder); + debug_assert!(matches!(mode, SpanEncodingMode::Direct)); + SpanData::decode(decoder) + }), + }; + Span::new(data.lo, data.hi, data.ctxt, data.parent) + } +} + +impl<'a, 'tcx> Decodable> for SpanData { + fn decode(decoder: &mut DecodeContext<'a, 'tcx>) -> SpanData { let ctxt = SyntaxContext::decode(decoder); let tag = u8::decode(decoder); if tag == TAG_PARTIAL_SPAN { - return DUMMY_SP.with_ctxt(ctxt); + return DUMMY_SP.with_ctxt(ctxt).data(); } debug_assert!(tag == TAG_VALID_SPAN_LOCAL || tag == TAG_VALID_SPAN_FOREIGN); @@ -518,8 +542,10 @@ impl<'a, 'tcx> Decodable> for Span { let hi = lo + len; let Some(sess) = decoder.sess else { - bug!("Cannot decode Span without Session. \ - You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`.") + bug!( + "Cannot decode Span without Session. \ + You need to explicitly pass `(crate_metadata_ref, tcx)` to `decode` instead of just `crate_metadata_ref`." + ) }; // Index of the file in the corresponding crate's list of encoded files. @@ -564,7 +590,7 @@ impl<'a, 'tcx> Decodable> for Span { let cnum = u32::decode(decoder); panic!( "Decoding of crate {:?} tried to access proc-macro dep {:?}", - decoder.cdata().root.name, + decoder.cdata().root.header.name, cnum ); } @@ -601,7 +627,7 @@ impl<'a, 'tcx> Decodable> for Span { let hi = hi + source_file.translated_source_file.start_pos; // Do not try to decode parent for foreign spans. - Span::new(lo, hi, ctxt, None) + SpanData { lo, hi, ctxt, parent: None } } } @@ -633,7 +659,7 @@ impl<'a, 'tcx> Decodable> for Symbol { } } -impl<'a, 'tcx> Decodable> for &'tcx [(ty::Predicate<'tcx>, Span)] { +impl<'a, 'tcx> Decodable> for &'tcx [(ty::Clause<'tcx>, Span)] { fn decode(d: &mut DecodeContext<'a, 'tcx>) -> Self { ty::codec::RefDecodable::decode(d) } @@ -671,6 +697,16 @@ impl MetadataBlob { .decode(self) } + pub(crate) fn get_header(&self) -> CrateHeader { + let slice = &self.blob()[..]; + let offset = METADATA_HEADER.len(); + + let pos_bytes = slice[offset..][..4].try_into().unwrap(); + let pos = u32::from_be_bytes(pos_bytes) as usize; + + LazyValue::::from_position(NonZeroUsize::new(pos).unwrap()).decode(self) + } + pub(crate) fn get_root(&self) -> CrateRoot { let slice = &self.blob()[..]; let offset = METADATA_HEADER.len(); @@ -684,18 +720,19 @@ impl MetadataBlob { pub(crate) fn list_crate_metadata(&self, out: &mut dyn io::Write) -> io::Result<()> { let root = self.get_root(); writeln!(out, "Crate info:")?; - writeln!(out, "name {}{}", root.name, root.extra_filename)?; - writeln!(out, "hash {} stable_crate_id {:?}", root.hash, root.stable_crate_id)?; + writeln!(out, "name {}{}", root.name(), root.extra_filename)?; + writeln!(out, "hash {} stable_crate_id {:?}", root.hash(), root.stable_crate_id)?; writeln!(out, "proc_macro {:?}", root.proc_macro_data.is_some())?; writeln!(out, "=External Dependencies=")?; for (i, dep) in root.crate_deps.decode(self).enumerate() { - let CrateDep { name, extra_filename, hash, host_hash, kind } = dep; + let CrateDep { name, extra_filename, hash, host_hash, kind, is_private } = dep; let number = i + 1; writeln!( out, - "{number} {name}{extra_filename} hash {hash} host_hash {host_hash:?} kind {kind:?}" + "{number} {name}{extra_filename} hash {hash} host_hash {host_hash:?} kind {kind:?} {privacy}", + privacy = if is_private { "private" } else { "public" } )?; } write!(out, "\n")?; @@ -709,21 +746,17 @@ impl CrateRoot { } pub(crate) fn name(&self) -> Symbol { - self.name + self.header.name } pub(crate) fn hash(&self) -> Svh { - self.hash + self.header.hash } pub(crate) fn stable_crate_id(&self) -> StableCrateId { self.stable_crate_id } - pub(crate) fn triple(&self) -> &TargetTriple { - &self.triple - } - pub(crate) fn decode_crate_deps<'a>( &self, metadata: &'a MetadataBlob, @@ -794,7 +827,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { bug!( "CrateMetadata::def_kind({:?}): id not found, in crate {:?} with number {}", item_id, - self.root.name, + self.root.name(), self.cnum, ) }) @@ -809,7 +842,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { .decode((self, sess)) } - fn load_proc_macro(self, id: DefIndex, sess: &Session) -> SyntaxExtension { + fn load_proc_macro(self, id: DefIndex, tcx: TyCtxt<'tcx>) -> SyntaxExtension { let (name, kind, helper_attrs) = match *self.raw_proc_macro(id) { ProcMacro::CustomDerive { trait_name, attributes, client } => { let helper_attrs = @@ -828,9 +861,11 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { } }; + let sess = tcx.sess; let attrs: Vec<_> = self.get_item_attrs(id, sess).collect(); SyntaxExtension::new( sess, + tcx.features(), kind, self.get_span(id, sess), helper_attrs, @@ -844,14 +879,14 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { self, index: DefIndex, tcx: TyCtxt<'tcx>, - ) -> ty::EarlyBinder<&'tcx [(Predicate<'tcx>, Span)]> { + ) -> ty::EarlyBinder<&'tcx [(ty::Clause<'tcx>, Span)]> { let lazy = self.root.tables.explicit_item_bounds.get(self, index); let output = if lazy.is_default() { &mut [] } else { tcx.arena.alloc_from_iter(lazy.decode((self, tcx))) }; - ty::EarlyBinder(&*output) + ty::EarlyBinder::bind(&*output) } fn get_variant( @@ -985,6 +1020,15 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { ) } + fn get_stripped_cfg_items(self, cnum: CrateNum, tcx: TyCtxt<'tcx>) -> &'tcx [StrippedCfgItem] { + let item_names = self + .root + .stripped_cfg_items + .decode((self, tcx)) + .map(|item| item.map_mod_id(|index| DefId { krate: cnum, index })); + tcx.arena.alloc_from_iter(item_names) + } + /// Iterates over the diagnostic items in the given crate. fn get_diagnostic_items(self) -> DiagnosticItems { let mut id_to_name = FxHashMap::default(); @@ -1617,7 +1661,7 @@ impl CrateMetadata { dependencies, dep_kind: Lock::new(dep_kind), source: Lrc::new(source), - private_dep, + private_dep: AtomicBool::new(private_dep), host_hash, extern_crate: Lock::new(None), hygiene_context: Default::default(), @@ -1665,6 +1709,10 @@ impl CrateMetadata { self.dep_kind.with_lock(|dep_kind| *dep_kind = f(*dep_kind)) } + pub(crate) fn update_and_private_dep(&self, private_dep: bool) { + self.private_dep.fetch_and(private_dep, Ordering::SeqCst); + } + pub(crate) fn required_panic_strategy(&self) -> Option { self.root.required_panic_strategy } @@ -1702,11 +1750,11 @@ impl CrateMetadata { } pub(crate) fn name(&self) -> Symbol { - self.root.name + self.root.header.name } pub(crate) fn hash(&self) -> Svh { - self.root.hash + self.root.header.hash } fn num_def_ids(&self) -> usize { diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 7425963d30ff8..aeda8af6d2cab 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -6,6 +6,7 @@ use crate::rmeta::AttrFlags; use rustc_ast as ast; use rustc_attr::Deprecation; +use rustc_data_structures::sync::Lrc; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LOCAL_CRATE}; use rustc_hir::definitions::{DefKey, DefPath, DefPathHash}; @@ -23,7 +24,6 @@ use rustc_span::hygiene::{ExpnHash, ExpnId}; use rustc_span::symbol::{kw, Symbol}; use rustc_span::Span; -use rustc_data_structures::sync::Lrc; use std::any::Any; use super::{Decodable, DecodeContext, DecodeIterator}; @@ -218,6 +218,7 @@ provide! { tcx, def_id, other, cdata, thir_abstract_const => { table } optimized_mir => { table } mir_for_ctfe => { table } + closure_saved_names_of_captured_variables => { table } mir_generator_witnesses => { table } promoted_mir => { table } def_span => { table } @@ -231,7 +232,7 @@ provide! { tcx, def_id, other, cdata, opt_def_kind => { table_direct } impl_parent => { table } impl_polarity => { table_direct } - impl_defaultness => { table_direct } + defaultness => { table_direct } constness => { table_direct } coerce_unsized_info => { table } mir_const_qualif => { table } @@ -245,6 +246,7 @@ provide! { tcx, def_id, other, cdata, debug_assert_eq!(tcx.def_kind(def_id), DefKind::OpaqueTy); cdata.root.tables.is_type_alias_impl_trait.get(cdata, def_id.index) } + assumed_wf_types_for_rpitit => { table } collect_return_position_impl_trait_in_trait_tys => { Ok(cdata .root @@ -285,7 +287,13 @@ provide! { tcx, def_id, other, cdata, is_ctfe_mir_available => { cdata.is_ctfe_mir_available(def_id.index) } dylib_dependency_formats => { cdata.get_dylib_dependency_formats(tcx) } - is_private_dep => { cdata.private_dep } + is_private_dep => { + // Parallel compiler needs to synchronize type checking and linting (which use this flag) + // so that they happen strictly crate loading. Otherwise, the full list of available + // impls aren't loaded yet. + use std::sync::atomic::Ordering; + cdata.private_dep.load(Ordering::Acquire) + } is_panic_runtime => { cdata.root.panic_runtime } is_compiler_builtins => { cdata.root.compiler_builtins } has_global_allocator => { cdata.root.has_global_allocator } @@ -317,9 +325,9 @@ provide! { tcx, def_id, other, cdata, } native_libraries => { cdata.get_native_libraries(tcx.sess).collect() } foreign_modules => { cdata.get_foreign_modules(tcx.sess).map(|m| (m.def_id, m)).collect() } - crate_hash => { cdata.root.hash } + crate_hash => { cdata.root.header.hash } crate_host_hash => { cdata.host_hash } - crate_name => { cdata.root.name } + crate_name => { cdata.root.header.name } extra_filename => { cdata.root.extra_filename.clone() } @@ -339,6 +347,7 @@ provide! { tcx, def_id, other, cdata, stability_implications => { cdata.get_stability_implications(tcx).iter().copied().collect() } + stripped_cfg_items => { cdata.get_stripped_cfg_items(cdata.cnum, tcx) } is_intrinsic => { cdata.get_is_intrinsic(def_id.index) } defined_lang_items => { cdata.get_lang_items(tcx) } diagnostic_items => { cdata.get_diagnostic_items() } @@ -395,10 +404,8 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { .contains(&id) }) }, - native_libraries: |tcx, LocalCrate| native_libs::collect(tcx), - foreign_modules: |tcx, LocalCrate| { - foreign_modules::collect(tcx).into_iter().map(|m| (m.def_id, m)).collect() - }, + native_libraries: native_libs::collect, + foreign_modules: foreign_modules::collect, // Returns a map from a sufficiently visible external item (i.e., an // external item that is visible from at least one local module) to a @@ -515,12 +522,13 @@ impl CStore { self.get_crate_data(def.krate).get_ctor(def.index) } - pub fn load_macro_untracked(&self, id: DefId, sess: &Session) -> LoadedMacro { + pub fn load_macro_untracked(&self, id: DefId, tcx: TyCtxt<'_>) -> LoadedMacro { + let sess = tcx.sess; let _prof_timer = sess.prof.generic_activity("metadata_load_macro"); let data = self.get_crate_data(id.krate); if data.root.is_proc_macro_crate() { - return LoadedMacro::ProcMacro(data.load_proc_macro(id.index, sess)); + return LoadedMacro::ProcMacro(data.load_proc_macro(id.index, tcx)); } let span = data.get_span(id.index, sess); @@ -581,7 +589,7 @@ impl CrateStore for CStore { } fn crate_name(&self, cnum: CrateNum) -> Symbol { - self.get_crate_data(cnum).root.name + self.get_crate_data(cnum).root.header.name } fn stable_crate_id(&self, cnum: CrateNum) -> StableCrateId { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index f067bca4b0b39..be91ad4088a26 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -3,6 +3,7 @@ use crate::rmeta::def_path_hash_map::DefPathHashMapRef; use crate::rmeta::table::TableBuilder; use crate::rmeta::*; +use rustc_ast::expand::StrippedCfgItem; use rustc_ast::Attribute; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; @@ -16,9 +17,8 @@ use rustc_hir::def_id::{ CrateNum, DefId, DefIndex, LocalDefId, CRATE_DEF_ID, CRATE_DEF_INDEX, LOCAL_CRATE, }; use rustc_hir::definitions::DefPathData; -use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::intravisit; use rustc_hir::lang_items::LangItem; -use rustc_middle::hir::nested_filter; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::{ @@ -30,14 +30,15 @@ use rustc_middle::query::Providers; use rustc_middle::traits::specialization_graph; use rustc_middle::ty::codec::TyEncoder; use rustc_middle::ty::fast_reject::{self, SimplifiedType, TreatParams}; -use rustc_middle::ty::{self, SymbolName, Ty, TyCtxt}; +use rustc_middle::ty::TypeVisitableExt; +use rustc_middle::ty::{self, AssocItemContainer, SymbolName, Ty, TyCtxt}; use rustc_middle::util::common::to_readable_str; use rustc_serialize::{opaque, Decodable, Decoder, Encodable, Encoder}; use rustc_session::config::{CrateType, OptLevel}; use rustc_session::cstore::{ForeignModule, LinkagePreference, NativeLib}; use rustc_span::hygiene::{ExpnIndex, HygieneEncodeContext, MacroKind}; use rustc_span::symbol::{sym, Symbol}; -use rustc_span::{self, ExternalSource, FileName, SourceFile, Span, SyntaxContext}; +use rustc_span::{self, ExternalSource, FileName, SourceFile, Span, SpanData, SyntaxContext}; use std::borrow::Borrow; use std::collections::hash_map::Entry; use std::hash::Hash; @@ -53,6 +54,7 @@ pub(super) struct EncodeContext<'a, 'tcx> { tables: TableBuilders, lazy_state: LazyState, + span_shorthands: FxHashMap, type_shorthands: FxHashMap, usize>, predicate_shorthands: FxHashMap, usize>, @@ -177,8 +179,20 @@ impl<'a, 'tcx> Encodable> for ExpnId { impl<'a, 'tcx> Encodable> for Span { fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) { - let span = self.data(); + match s.span_shorthands.entry(*self) { + Entry::Occupied(o) => SpanEncodingMode::Shorthand(*o.get()).encode(s), + Entry::Vacant(v) => { + let position = s.opaque.position(); + v.insert(position); + SpanEncodingMode::Direct.encode(s); + self.data().encode(s); + } + } + } +} +impl<'a, 'tcx> Encodable> for SpanData { + fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) { // Don't serialize any `SyntaxContext`s from a proc-macro crate, // since we don't load proc-macro dependencies during serialization. // This means that any hygiene information from macros used *within* @@ -213,7 +227,7 @@ impl<'a, 'tcx> Encodable> for Span { if s.is_proc_macro { SyntaxContext::root().encode(s); } else { - span.ctxt.encode(s); + self.ctxt.encode(s); } if self.is_dummy() { @@ -221,18 +235,18 @@ impl<'a, 'tcx> Encodable> for Span { } // The Span infrastructure should make sure that this invariant holds: - debug_assert!(span.lo <= span.hi); + debug_assert!(self.lo <= self.hi); - if !s.source_file_cache.0.contains(span.lo) { + if !s.source_file_cache.0.contains(self.lo) { let source_map = s.tcx.sess.source_map(); - let source_file_index = source_map.lookup_source_file_idx(span.lo); + let source_file_index = source_map.lookup_source_file_idx(self.lo); s.source_file_cache = (source_map.files()[source_file_index].clone(), source_file_index); } let (ref source_file, source_file_index) = s.source_file_cache; - debug_assert!(source_file.contains(span.lo)); + debug_assert!(source_file.contains(self.lo)); - if !source_file.contains(span.hi) { + if !source_file.contains(self.hi) { // Unfortunately, macro expansion still sometimes generates Spans // that malformed in this way. return TAG_PARTIAL_SPAN.encode(s); @@ -286,11 +300,11 @@ impl<'a, 'tcx> Encodable> for Span { // Encode the start position relative to the file start, so we profit more from the // variable-length integer encoding. - let lo = span.lo - source_file.start_pos; + let lo = self.lo - source_file.start_pos; // Encode length which is usually less than span.hi and profits more // from the variable-length integer encoding that we use. - let len = span.hi - span.lo; + let len = self.hi - self.lo; tag.encode(s); lo.encode(s); @@ -449,18 +463,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { LazyArray::from_position_and_num_elems(pos, len) } - fn encode_info_for_items(&mut self) { - self.encode_info_for_mod(CRATE_DEF_ID); - - // Proc-macro crates only export proc-macro items, which are looked - // up using `proc_macro_data` - if self.is_proc_macro { - return; - } - - self.tcx.hir().visit_all_item_likes_in_crate(self); - } - fn encode_def_path_table(&mut self) { let table = self.tcx.def_path_table(); if self.is_proc_macro { @@ -584,6 +586,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { (self.encode_lang_items(), self.encode_lang_items_missing()) }); + let stripped_cfg_items = stat!("stripped-cfg-items", || self.encode_stripped_cfg_items()); + let diagnostic_items = stat!("diagnostic-items", || self.encode_diagnostic_items()); let native_libraries = stat!("native-libs", || self.encode_native_libraries()); @@ -604,8 +608,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { _ = stat!("def-ids", || self.encode_def_ids()); - _ = stat!("items", || self.encode_info_for_items()); - let interpret_alloc_index = stat!("interpret-alloc-index", || { let mut interpret_alloc_index = Vec::new(); let mut n = 0; @@ -620,7 +622,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { trace!("encoding {} further alloc ids", new_n - n); for idx in n..new_n { let id = self.interpret_allocs[idx]; - let pos = self.position() as u32; + let pos = self.position() as u64; interpret_alloc_index.push(pos); interpret::specialized_encode_alloc_id(self, tcx, id); } @@ -662,10 +664,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let root = stat!("final", || { let attrs = tcx.hir().krate_attrs(); self.lazy(CrateRoot { - name: tcx.crate_name(LOCAL_CRATE), + header: CrateHeader { + name: tcx.crate_name(LOCAL_CRATE), + triple: tcx.sess.opts.target_triple.clone(), + hash: tcx.crate_hash(LOCAL_CRATE), + is_proc_macro_crate: proc_macro_data.is_some(), + }, extra_filename: tcx.sess.opts.cg.extra_filename.clone(), - triple: tcx.sess.opts.target_triple.clone(), - hash: tcx.crate_hash(LOCAL_CRATE), stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(), required_panic_strategy: tcx.required_panic_strategy(LOCAL_CRATE), panic_in_drop_strategy: tcx.sess.opts.unstable_opts.panic_in_drop, @@ -691,6 +696,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { lang_items, diagnostic_items, lang_items_missing, + stripped_cfg_items, native_libraries, foreign_modules, source_map, @@ -813,7 +819,7 @@ fn should_encode_span(def_kind: DefKind) -> bool { | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -832,7 +838,6 @@ fn should_encode_span(def_kind: DefKind) -> bool { | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Field | DefKind::Impl { .. } | DefKind::Closure @@ -849,7 +854,7 @@ fn should_encode_attrs(def_kind: DefKind) -> bool { | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -875,7 +880,6 @@ fn should_encode_attrs(def_kind: DefKind) -> bool { | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::LifetimeParam | DefKind::GlobalAsm | DefKind::Generator => false, @@ -891,7 +895,7 @@ fn should_encode_expn_that_defined(def_kind: DefKind) -> bool { | DefKind::Variant | DefKind::Trait | DefKind::Impl { .. } => true, - DefKind::TyAlias + DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -910,7 +914,6 @@ fn should_encode_expn_that_defined(def_kind: DefKind) -> bool { | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Field | DefKind::LifetimeParam | DefKind::GlobalAsm @@ -927,7 +930,7 @@ fn should_encode_visibility(def_kind: DefKind) -> bool { | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -947,7 +950,6 @@ fn should_encode_visibility(def_kind: DefKind) -> bool { | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::GlobalAsm | DefKind::Impl { .. } | DefKind::Closure @@ -972,9 +974,8 @@ fn should_encode_stability(def_kind: DefKind) -> bool { | DefKind::Const | DefKind::Fn | DefKind::ForeignMod - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Enum | DefKind::Union | DefKind::Impl { .. } @@ -1034,14 +1035,13 @@ fn should_encode_mir(tcx: TyCtxt<'_>, def_id: LocalDefId) -> (bool, bool) { } } -fn should_encode_variances(def_kind: DefKind) -> bool { +fn should_encode_variances<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, def_kind: DefKind) -> bool { match def_kind { DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::Variant | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn => true, @@ -1054,7 +1054,6 @@ fn should_encode_variances(def_kind: DefKind) -> bool { | DefKind::Static(..) | DefKind::Const | DefKind::ForeignMod - | DefKind::TyAlias | DefKind::Impl { .. } | DefKind::Trait | DefKind::TraitAlias @@ -1068,6 +1067,9 @@ fn should_encode_variances(def_kind: DefKind) -> bool { | DefKind::Closure | DefKind::Generator | DefKind::ExternCrate => false, + DefKind::TyAlias { lazy } => { + lazy || tcx.type_of(def_id).instantiate_identity().has_opaque_types() + } } } @@ -1078,7 +1080,7 @@ fn should_encode_generics(def_kind: DefKind) -> bool { | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -1091,7 +1093,6 @@ fn should_encode_generics(def_kind: DefKind) -> bool { | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Impl { .. } | DefKind::Field | DefKind::TyParam @@ -1119,7 +1120,7 @@ fn should_encode_type(tcx: TyCtxt<'_>, def_id: LocalDefId, def_kind: DefKind) -> | DefKind::Fn | DefKind::Const | DefKind::Static(..) - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::Impl { .. } | DefKind::AssocFn @@ -1131,8 +1132,8 @@ fn should_encode_type(tcx: TyCtxt<'_>, def_id: LocalDefId, def_kind: DefKind) -> | DefKind::InlineConst => true, DefKind::OpaqueTy => { - let opaque = tcx.hir().expect_item(def_id).expect_opaque_ty(); - if let hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id) = opaque.origin + let origin = tcx.opaque_type_origin(def_id); + if let hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id) = origin && let hir::Node::TraitItem(trait_item) = tcx.hir().get_by_def_id(fn_def_id) && let (_, hir::TraitFn::Required(..)) = trait_item.expect_fn() { @@ -1142,30 +1143,11 @@ fn should_encode_type(tcx: TyCtxt<'_>, def_id: LocalDefId, def_kind: DefKind) -> } } - DefKind::ImplTraitPlaceholder => { - let parent_def_id = tcx.impl_trait_in_trait_parent_fn(def_id.to_def_id()); - let assoc_item = tcx.associated_item(parent_def_id); - match assoc_item.container { - // Always encode an RPIT in an impl fn, since it always has a body - ty::AssocItemContainer::ImplContainer => true, - ty::AssocItemContainer::TraitContainer => { - // Encode an RPIT for a trait only if the trait has a default body - assoc_item.defaultness(tcx).has_value() - } - } - } - DefKind::AssocTy => { let assoc_item = tcx.associated_item(def_id); match assoc_item.container { ty::AssocItemContainer::ImplContainer => true, - // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty) always encode RPITITs, - // since we need to be able to "project" from an RPITIT associated item - // to an opaque when installing the default projection predicates in - // default trait methods with RPITITs. - ty::AssocItemContainer::TraitContainer => { - assoc_item.defaultness(tcx).has_value() || assoc_item.opt_rpitit_info.is_some() - } + ty::AssocItemContainer::TraitContainer => assoc_item.defaultness(tcx).has_value(), } } DefKind::TyParam => { @@ -1186,9 +1168,83 @@ fn should_encode_type(tcx: TyCtxt<'_>, def_id: LocalDefId, def_kind: DefKind) -> } } +fn should_encode_fn_sig(def_kind: DefKind) -> bool { + match def_kind { + DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn) => true, + + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Variant + | DefKind::Field + | DefKind::Const + | DefKind::Static(..) + | DefKind::Ctor(..) + | DefKind::TyAlias { .. } + | DefKind::OpaqueTy + | DefKind::ForeignTy + | DefKind::Impl { .. } + | DefKind::AssocConst + | DefKind::Closure + | DefKind::Generator + | DefKind::ConstParam + | DefKind::AnonConst + | DefKind::InlineConst + | DefKind::AssocTy + | DefKind::TyParam + | DefKind::Trait + | DefKind::TraitAlias + | DefKind::Mod + | DefKind::ForeignMod + | DefKind::Macro(..) + | DefKind::Use + | DefKind::LifetimeParam + | DefKind::GlobalAsm + | DefKind::ExternCrate => false, + } +} + +fn should_encode_constness(def_kind: DefKind) -> bool { + match def_kind { + DefKind::Fn + | DefKind::AssocFn + | DefKind::Closure + | DefKind::Impl { of_trait: true } + | DefKind::Variant + | DefKind::Ctor(..) => true, + + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Field + | DefKind::Const + | DefKind::AssocConst + | DefKind::AnonConst + | DefKind::Static(..) + | DefKind::TyAlias { .. } + | DefKind::OpaqueTy + | DefKind::Impl { of_trait: false } + | DefKind::ForeignTy + | DefKind::Generator + | DefKind::ConstParam + | DefKind::InlineConst + | DefKind::AssocTy + | DefKind::TyParam + | DefKind::Trait + | DefKind::TraitAlias + | DefKind::Mod + | DefKind::ForeignMod + | DefKind::Macro(..) + | DefKind::Use + | DefKind::LifetimeParam + | DefKind::GlobalAsm + | DefKind::ExternCrate => false, + } +} + fn should_encode_const(def_kind: DefKind) -> bool { match def_kind { - DefKind::Const | DefKind::AssocConst | DefKind::AnonConst => true, + DefKind::Const | DefKind::AssocConst | DefKind::AnonConst | DefKind::InlineConst => true, DefKind::Struct | DefKind::Union @@ -1198,16 +1254,14 @@ fn should_encode_const(def_kind: DefKind) -> bool { | DefKind::Field | DefKind::Fn | DefKind::Static(..) - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::ForeignTy | DefKind::Impl { .. } | DefKind::AssocFn | DefKind::Closure | DefKind::Generator | DefKind::ConstParam - | DefKind::InlineConst | DefKind::AssocTy | DefKind::TyParam | DefKind::Trait @@ -1222,11 +1276,8 @@ fn should_encode_const(def_kind: DefKind) -> bool { } } -// We only encode impl trait in trait when using `lower-impl-trait-in-trait-to-assoc-ty` unstable -// option. fn should_encode_fn_impl_trait_in_trait<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool { - if tcx.lower_impl_trait_in_trait_to_assoc_ty() - && let Some(assoc_item) = tcx.opt_associated_item(def_id) + if let Some(assoc_item) = tcx.opt_associated_item(def_id) && assoc_item.container == ty::AssocItemContainer::TraitContainer && assoc_item.kind == ty::AssocKind::Fn { @@ -1259,10 +1310,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } fn encode_def_ids(&mut self) { + self.encode_info_for_mod(CRATE_DEF_ID); + + // Proc-macro crates only export proc-macro items, which are looked + // up using `proc_macro_data` if self.is_proc_macro { return; } + let tcx = self.tcx; + for local_id in tcx.iter_local_def_id() { let def_id = local_id.to_def_id(); let def_kind = tcx.opt_def_kind(local_id); @@ -1295,34 +1352,85 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.encode_default_body_stability(def_id); self.encode_deprecation(def_id); } - if should_encode_variances(def_kind) { + if should_encode_variances(tcx, def_id, def_kind) { let v = self.tcx.variances_of(def_id); record_array!(self.tables.variances_of[def_id] <- v); } + if should_encode_fn_sig(def_kind) { + record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id)); + } if should_encode_generics(def_kind) { let g = tcx.generics_of(def_id); record!(self.tables.generics_of[def_id] <- g); record!(self.tables.explicit_predicates_of[def_id] <- self.tcx.explicit_predicates_of(def_id)); let inferred_outlives = self.tcx.inferred_outlives_of(def_id); record_defaulted_array!(self.tables.inferred_outlives_of[def_id] <- inferred_outlives); + + for param in &g.params { + if let ty::GenericParamDefKind::Const { has_default: true, .. } = param.kind { + let default = self.tcx.const_param_default(param.def_id); + record!(self.tables.const_param_default[param.def_id] <- default); + } + } } if should_encode_type(tcx, local_id, def_kind) { record!(self.tables.type_of[def_id] <- self.tcx.type_of(def_id)); } + if should_encode_constness(def_kind) { + self.tables.constness.set_some(def_id.index, self.tcx.constness(def_id)); + } + if let DefKind::Fn | DefKind::AssocFn = def_kind { + self.tables.asyncness.set_some(def_id.index, tcx.asyncness(def_id)); + record_array!(self.tables.fn_arg_names[def_id] <- tcx.fn_arg_names(def_id)); + self.tables.is_intrinsic.set(def_id.index, tcx.is_intrinsic(def_id)); + } if let DefKind::TyParam = def_kind { let default = self.tcx.object_lifetime_default(def_id); record!(self.tables.object_lifetime_default[def_id] <- default); } if let DefKind::Trait = def_kind { + record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); record!(self.tables.super_predicates_of[def_id] <- self.tcx.super_predicates_of(def_id)); + + let module_children = self.tcx.module_children_local(local_id); + record_array!(self.tables.module_children_non_reexports[def_id] <- + module_children.iter().map(|child| child.res.def_id().index)); } if let DefKind::TraitAlias = def_kind { + record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); record!(self.tables.super_predicates_of[def_id] <- self.tcx.super_predicates_of(def_id)); record!(self.tables.implied_predicates_of[def_id] <- self.tcx.implied_predicates_of(def_id)); } + if let DefKind::Trait | DefKind::Impl { .. } = def_kind { + let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); + record_array!(self.tables.associated_item_or_field_def_ids[def_id] <- + associated_item_def_ids.iter().map(|&def_id| { + assert!(def_id.is_local()); + def_id.index + }) + ); + for &def_id in associated_item_def_ids { + self.encode_info_for_assoc_item(def_id); + } + } + if let DefKind::Generator = def_kind { + self.encode_info_for_generator(local_id); + } if let DefKind::Enum | DefKind::Struct | DefKind::Union = def_kind { self.encode_info_for_adt(local_id); } + if let DefKind::Mod = def_kind { + self.encode_info_for_mod(local_id); + } + if let DefKind::Macro(_) = def_kind { + self.encode_info_for_macro(local_id); + } + if let DefKind::OpaqueTy = def_kind { + self.encode_explicit_item_bounds(def_id); + self.tables + .is_type_alias_impl_trait + .set(def_id.index, self.tcx.is_type_alias_impl_trait(def_id)); + } if tcx.impl_method_has_trait_impl_trait_tys(def_id) && let Ok(table) = self.tcx.collect_return_position_impl_trait_in_trait_tys(def_id) { @@ -1383,26 +1491,23 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { }; record!(self.tables.variant_data[variant.def_id] <- data); - self.tables.constness.set_some(variant.def_id.index, hir::Constness::Const); record_array!(self.tables.associated_item_or_field_def_ids[variant.def_id] <- variant.fields.iter().map(|f| { assert!(f.did.is_local()); f.did.index })); if let Some((CtorKind::Fn, ctor_def_id)) = variant.ctor { - self.tables.constness.set_some(ctor_def_id.index, hir::Constness::Const); let fn_sig = tcx.fn_sig(ctor_def_id); - record!(self.tables.fn_sig[ctor_def_id] <- fn_sig); // FIXME only encode signature for ctor_def_id record!(self.tables.fn_sig[variant.def_id] <- fn_sig); } } } + #[instrument(level = "debug", skip(self))] fn encode_info_for_mod(&mut self, local_def_id: LocalDefId) { let tcx = self.tcx; let def_id = local_def_id.to_def_id(); - debug!("EncodeContext::encode_info_for_mod({:?})", def_id); // If we are encoding a proc-macro crates, `encode_info_for_mod` will // only ever get called for the crate root. We still want to encode @@ -1430,70 +1535,34 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { record_defaulted_array!(self.tables.explicit_item_bounds[def_id] <- bounds); } - fn encode_info_for_trait_item(&mut self, def_id: DefId) { - debug!("EncodeContext::encode_info_for_trait_item({:?})", def_id); + #[instrument(level = "debug", skip(self))] + fn encode_info_for_assoc_item(&mut self, def_id: DefId) { let tcx = self.tcx; + let item = tcx.associated_item(def_id); - let impl_defaultness = tcx.impl_defaultness(def_id.expect_local()); - self.tables.impl_defaultness.set_some(def_id.index, impl_defaultness); - let trait_item = tcx.associated_item(def_id); - self.tables.assoc_container.set_some(def_id.index, trait_item.container); + self.tables.defaultness.set_some(def_id.index, item.defaultness(tcx)); + self.tables.assoc_container.set_some(def_id.index, item.container); - match trait_item.kind { - ty::AssocKind::Const => {} - ty::AssocKind::Fn => { - record_array!(self.tables.fn_arg_names[def_id] <- tcx.fn_arg_names(def_id)); - self.tables.asyncness.set_some(def_id.index, tcx.asyncness(def_id)); - self.tables.constness.set_some(def_id.index, hir::Constness::NotConst); + match item.container { + AssocItemContainer::TraitContainer => { + if let ty::AssocKind::Type = item.kind { + self.encode_explicit_item_bounds(def_id); + } } - ty::AssocKind::Type => { - self.encode_explicit_item_bounds(def_id); + AssocItemContainer::ImplContainer => { + if let Some(trait_item_def_id) = item.trait_item_def_id { + self.tables.trait_item_def_id.set_some(def_id.index, trait_item_def_id.into()); + } } } - if trait_item.kind == ty::AssocKind::Fn { - record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id)); - } - if let Some(rpitit_info) = trait_item.opt_rpitit_info { - let rpitit_info = self.lazy(rpitit_info); - self.tables.opt_rpitit_info.set_some(def_id.index, rpitit_info); - } - } - - fn encode_info_for_impl_item(&mut self, def_id: DefId) { - debug!("EncodeContext::encode_info_for_impl_item({:?})", def_id); - let tcx = self.tcx; - - let defaultness = self.tcx.impl_defaultness(def_id.expect_local()); - self.tables.impl_defaultness.set_some(def_id.index, defaultness); - let impl_item = self.tcx.associated_item(def_id); - self.tables.assoc_container.set_some(def_id.index, impl_item.container); - - match impl_item.kind { - ty::AssocKind::Fn => { - let (sig, body) = - self.tcx.hir().expect_impl_item(def_id.expect_local()).expect_fn(); - self.tables.asyncness.set_some(def_id.index, sig.header.asyncness); - record_array!(self.tables.fn_arg_names[def_id] <- self.tcx.hir().body_param_names(body)); - // Can be inside `impl const Trait`, so using sig.header.constness is not reliable - let constness = if self.tcx.is_const_fn_raw(def_id) { - hir::Constness::Const - } else { - hir::Constness::NotConst - }; - self.tables.constness.set_some(def_id.index, constness); + if let Some(rpitit_info) = item.opt_rpitit_info { + record!(self.tables.opt_rpitit_info[def_id] <- rpitit_info); + if matches!(rpitit_info, ty::ImplTraitInTraitData::Trait { .. }) { + record_array!( + self.tables.assumed_wf_types_for_rpitit[def_id] + <- self.tcx.assumed_wf_types_for_rpitit(def_id) + ); } - ty::AssocKind::Const | ty::AssocKind::Type => {} - } - if let Some(trait_item_def_id) = impl_item.trait_item_def_id { - self.tables.trait_item_def_id.set_some(def_id.index, trait_item_def_id.into()); - } - if impl_item.kind == ty::AssocKind::Fn { - record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id)); - self.tables.is_intrinsic.set(def_id.index, tcx.is_intrinsic(def_id)); - } - if let Some(rpitit_info) = impl_item.opt_rpitit_info { - let rpitit_info = self.lazy(rpitit_info); - self.tables.opt_rpitit_info.set_some(def_id.index, rpitit_info); } } @@ -1514,6 +1583,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { debug!("EntryBuilder::encode_mir({:?})", def_id); if encode_opt { record!(self.tables.optimized_mir[def_id.to_def_id()] <- tcx.optimized_mir(def_id)); + record!(self.tables.closure_saved_names_of_captured_variables[def_id.to_def_id()] + <- tcx.closure_saved_names_of_captured_variables(def_id)); if tcx.sess.opts.unstable_opts.drop_tracking_mir && let DefKind::Generator = self.tcx.def_kind(def_id) @@ -1564,9 +1635,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn encode_stability(&mut self, def_id: DefId) { - debug!("EncodeContext::encode_stability({:?})", def_id); - // The query lookup can take a measurable amount of time in crates with many items. Check if // the stability attributes are even enabled before using their queries. if self.feat.staged_api || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { @@ -1576,9 +1646,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn encode_const_stability(&mut self, def_id: DefId) { - debug!("EncodeContext::encode_const_stability({:?})", def_id); - // The query lookup can take a measurable amount of time in crates with many items. Check if // the stability attributes are even enabled before using their queries. if self.feat.staged_api || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { @@ -1588,9 +1657,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn encode_default_body_stability(&mut self, def_id: DefId) { - debug!("EncodeContext::encode_default_body_stability({:?})", def_id); - // The query lookup can take a measurable amount of time in crates with many items. Check if // the stability attributes are even enabled before using their queries. if self.feat.staged_api || self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { @@ -1600,8 +1668,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } + #[instrument(level = "debug", skip(self))] fn encode_deprecation(&mut self, def_id: DefId) { - debug!("EncodeContext::encode_deprecation({:?})", def_id); if let Some(depr) = self.tcx.lookup_deprecation(def_id) { record!(self.tables.lookup_deprecation_entry[def_id] <- depr); } @@ -1615,123 +1683,24 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { }) } - fn encode_info_for_item(&mut self, item: &'tcx hir::Item<'tcx>) { + #[instrument(level = "debug", skip(self))] + fn encode_info_for_macro(&mut self, def_id: LocalDefId) { let tcx = self.tcx; - let def_id = item.owner_id.to_def_id(); - debug!("EncodeContext::encode_info_for_item({:?})", def_id); - let record_associated_item_def_ids = |this: &mut Self, def_ids: &[DefId]| { - record_array!(this.tables.associated_item_or_field_def_ids[def_id] <- def_ids.iter().map(|&def_id| { - assert!(def_id.is_local()); - def_id.index - })) + let hir::ItemKind::Macro(ref macro_def, _) = tcx.hir().expect_item(def_id).kind else { + bug!() }; - - match item.kind { - hir::ItemKind::Fn(ref sig, .., body) => { - self.tables.asyncness.set_some(def_id.index, sig.header.asyncness); - record_array!(self.tables.fn_arg_names[def_id] <- self.tcx.hir().body_param_names(body)); - self.tables.constness.set_some(def_id.index, sig.header.constness); - record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id)); - self.tables.is_intrinsic.set(def_id.index, tcx.is_intrinsic(def_id)); - } - hir::ItemKind::Macro(ref macro_def, _) => { - self.tables.is_macro_rules.set(def_id.index, macro_def.macro_rules); - record!(self.tables.macro_definition[def_id] <- &*macro_def.body); - } - hir::ItemKind::Mod(..) => { - self.encode_info_for_mod(item.owner_id.def_id); - } - hir::ItemKind::OpaqueTy(ref opaque) => { - self.encode_explicit_item_bounds(def_id); - self.tables.is_type_alias_impl_trait.set( - def_id.index, - matches!(opaque.origin, hir::OpaqueTyOrigin::TyAlias { .. }), - ); - } - hir::ItemKind::Impl(hir::Impl { defaultness, constness, .. }) => { - self.tables.impl_defaultness.set_some(def_id.index, *defaultness); - self.tables.constness.set_some(def_id.index, *constness); - self.tables.impl_polarity.set_some(def_id.index, self.tcx.impl_polarity(def_id)); - - if let Some(trait_ref) = self.tcx.impl_trait_ref(def_id) { - record!(self.tables.impl_trait_ref[def_id] <- trait_ref); - - let trait_ref = trait_ref.skip_binder(); - let trait_def = self.tcx.trait_def(trait_ref.def_id); - if let Ok(mut an) = trait_def.ancestors(self.tcx, def_id) { - if let Some(specialization_graph::Node::Impl(parent)) = an.nth(1) { - self.tables.impl_parent.set_some(def_id.index, parent.into()); - } - } - - // if this is an impl of `CoerceUnsized`, create its - // "unsized info", else just store None - if Some(trait_ref.def_id) == self.tcx.lang_items().coerce_unsized_trait() { - let coerce_unsized_info = - self.tcx.at(item.span).coerce_unsized_info(def_id); - record!(self.tables.coerce_unsized_info[def_id] <- coerce_unsized_info); - } - } - - let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); - record_associated_item_def_ids(self, associated_item_def_ids); - for &trait_item_def_id in associated_item_def_ids { - self.encode_info_for_impl_item(trait_item_def_id); - } - } - hir::ItemKind::Trait(..) => { - record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); - - let module_children = tcx.module_children_local(item.owner_id.def_id); - record_array!(self.tables.module_children_non_reexports[def_id] <- - module_children.iter().map(|child| child.res.def_id().index)); - - let associated_item_def_ids = self.tcx.associated_item_def_ids(def_id); - record_associated_item_def_ids(self, associated_item_def_ids); - for &item_def_id in associated_item_def_ids { - self.encode_info_for_trait_item(item_def_id); - } - } - hir::ItemKind::TraitAlias(..) => { - record!(self.tables.trait_def[def_id] <- self.tcx.trait_def(def_id)); - } - hir::ItemKind::ExternCrate(_) - | hir::ItemKind::Use(..) - | hir::ItemKind::Static(..) - | hir::ItemKind::Const(..) - | hir::ItemKind::Enum(..) - | hir::ItemKind::Struct(..) - | hir::ItemKind::Union(..) - | hir::ItemKind::ForeignMod { .. } - | hir::ItemKind::GlobalAsm(..) - | hir::ItemKind::TyAlias(..) => {} - } + self.tables.is_macro_rules.set(def_id.local_def_index, macro_def.macro_rules); + record!(self.tables.macro_definition[def_id.to_def_id()] <- &*macro_def.body); } #[instrument(level = "debug", skip(self))] - fn encode_info_for_closure(&mut self, def_id: LocalDefId) { - // NOTE(eddyb) `tcx.type_of(def_id)` isn't used because it's fully generic, - // including on the signature, which is inferred in `typeck`. + fn encode_info_for_generator(&mut self, def_id: LocalDefId) { let typeck_result: &'tcx ty::TypeckResults<'tcx> = self.tcx.typeck(def_id); - let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); - let ty = typeck_result.node_type(hir_id); - match ty.kind() { - ty::Generator(..) => { - let data = self.tcx.generator_kind(def_id).unwrap(); - let generator_diagnostic_data = typeck_result.get_generator_diagnostic_data(); - record!(self.tables.generator_kind[def_id.to_def_id()] <- data); - record!(self.tables.generator_diagnostic_data[def_id.to_def_id()] <- generator_diagnostic_data); - } - - ty::Closure(_, substs) => { - let constness = self.tcx.constness(def_id.to_def_id()); - self.tables.constness.set_some(def_id.to_def_id().index, constness); - record!(self.tables.fn_sig[def_id.to_def_id()] <- ty::EarlyBinder(substs.as_closure().sig())); - } - - _ => bug!("closure that is neither generator nor closure"), - } + let data = self.tcx.generator_kind(def_id).unwrap(); + let generator_diagnostic_data = typeck_result.get_generator_diagnostic_data(); + record!(self.tables.generator_kind[def_id.to_def_id()] <- data); + record!(self.tables.generator_diagnostic_data[def_id.to_def_id()] <- generator_diagnostic_data); } fn encode_native_libraries(&mut self) -> LazyArray { @@ -1772,7 +1741,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } fn encode_proc_macros(&mut self) -> Option { - let is_proc_macro = self.tcx.sess.crate_types().contains(&CrateType::ProcMacro); + let is_proc_macro = self.tcx.crate_types().contains(&CrateType::ProcMacro); if is_proc_macro { let tcx = self.tcx; let hir = tcx.hir(); @@ -1880,6 +1849,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { host_hash: self.tcx.crate_host_hash(cnum), kind: self.tcx.dep_kind(cnum), extra_filename: self.tcx.extra_filename(cnum).clone(), + is_private: self.tcx.is_private_dep(cnum), }; (cnum, dep) }) @@ -1936,34 +1906,60 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.lazy_array(&tcx.lang_items().missing) } + fn encode_stripped_cfg_items(&mut self) -> LazyArray> { + self.lazy_array( + self.tcx + .stripped_cfg_items(LOCAL_CRATE) + .into_iter() + .map(|item| item.clone().map_mod_id(|def_id| def_id.index)), + ) + } + fn encode_traits(&mut self) -> LazyArray { empty_proc_macro!(self); self.lazy_array(self.tcx.traits(LOCAL_CRATE).iter().map(|def_id| def_id.index)) } /// Encodes an index, mapping each trait to its (local) implementations. + #[instrument(level = "debug", skip(self))] fn encode_impls(&mut self) -> LazyArray { - debug!("EncodeContext::encode_traits_and_impls()"); empty_proc_macro!(self); let tcx = self.tcx; let mut fx_hash_map: FxHashMap)>> = FxHashMap::default(); for id in tcx.hir().items() { - if matches!(tcx.def_kind(id.owner_id), DefKind::Impl { .. }) { - if let Some(trait_ref) = tcx.impl_trait_ref(id.owner_id) { - let trait_ref = trait_ref.subst_identity(); - - let simplified_self_ty = fast_reject::simplify_type( - self.tcx, - trait_ref.self_ty(), - TreatParams::AsCandidateKey, - ); - - fx_hash_map - .entry(trait_ref.def_id) - .or_default() - .push((id.owner_id.def_id.local_def_index, simplified_self_ty)); + let DefKind::Impl { of_trait } = tcx.def_kind(id.owner_id) else { + continue; + }; + let def_id = id.owner_id.to_def_id(); + + self.tables.defaultness.set_some(def_id.index, tcx.defaultness(def_id)); + self.tables.impl_polarity.set_some(def_id.index, tcx.impl_polarity(def_id)); + + if of_trait && let Some(trait_ref) = tcx.impl_trait_ref(def_id) { + record!(self.tables.impl_trait_ref[def_id] <- trait_ref); + + let trait_ref = trait_ref.instantiate_identity(); + let simplified_self_ty = + fast_reject::simplify_type(self.tcx, trait_ref.self_ty(), TreatParams::AsCandidateKey); + fx_hash_map + .entry(trait_ref.def_id) + .or_default() + .push((id.owner_id.def_id.local_def_index, simplified_self_ty)); + + let trait_def = tcx.trait_def(trait_ref.def_id); + if let Some(mut an) = trait_def.ancestors(tcx, def_id).ok() { + if let Some(specialization_graph::Node::Impl(parent)) = an.nth(1) { + self.tables.impl_parent.set_some(def_id.index, parent.into()); + } + } + + // if this is an impl of `CoerceUnsized`, create its + // "unsized info", else just store None + if Some(trait_ref.def_id) == tcx.lang_items().coerce_unsized_trait() { + let coerce_unsized_info = tcx.coerce_unsized_info(def_id); + record!(self.tables.coerce_unsized_info[def_id] <- coerce_unsized_info); } } } @@ -1991,8 +1987,8 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.lazy_array(&all_impls) } + #[instrument(level = "debug", skip(self))] fn encode_incoherent_impls(&mut self) -> LazyArray { - debug!("EncodeContext::encode_traits_and_impls()"); empty_proc_macro!(self); let tcx = self.tcx; let mut all_impls: Vec<_> = tcx.crate_inherent_impls(()).incoherent_impls.iter().collect(); @@ -2061,77 +2057,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } LazyArray::default() } - - fn encode_info_for_foreign_item(&mut self, def_id: DefId, nitem: &hir::ForeignItem<'_>) { - let tcx = self.tcx; - - debug!("EncodeContext::encode_info_for_foreign_item({:?})", def_id); - - match nitem.kind { - hir::ForeignItemKind::Fn(_, ref names, _) => { - self.tables.asyncness.set_some(def_id.index, hir::IsAsync::NotAsync); - record_array!(self.tables.fn_arg_names[def_id] <- *names); - let constness = if self.tcx.is_const_fn_raw(def_id) { - hir::Constness::Const - } else { - hir::Constness::NotConst - }; - self.tables.constness.set_some(def_id.index, constness); - record!(self.tables.fn_sig[def_id] <- tcx.fn_sig(def_id)); - } - hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => {} - } - if let hir::ForeignItemKind::Fn(..) = nitem.kind { - self.tables.is_intrinsic.set(def_id.index, tcx.is_intrinsic(def_id)); - } - } -} - -// FIXME(eddyb) make metadata encoding walk over all definitions, instead of HIR. -impl<'a, 'tcx> Visitor<'tcx> for EncodeContext<'a, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - - fn nested_visit_map(&mut self) -> Self::Map { - self.tcx.hir() - } - fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { - intravisit::walk_expr(self, ex); - self.encode_info_for_expr(ex); - } - fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { - intravisit::walk_item(self, item); - self.encode_info_for_item(item); - } - fn visit_foreign_item(&mut self, ni: &'tcx hir::ForeignItem<'tcx>) { - intravisit::walk_foreign_item(self, ni); - self.encode_info_for_foreign_item(ni.owner_id.to_def_id(), ni); - } - fn visit_generics(&mut self, generics: &'tcx hir::Generics<'tcx>) { - intravisit::walk_generics(self, generics); - self.encode_info_for_generics(generics); - } -} - -impl<'a, 'tcx> EncodeContext<'a, 'tcx> { - fn encode_info_for_generics(&mut self, generics: &hir::Generics<'tcx>) { - for param in generics.params { - match param.kind { - hir::GenericParamKind::Lifetime { .. } | hir::GenericParamKind::Type { .. } => {} - hir::GenericParamKind::Const { ref default, .. } => { - let def_id = param.def_id.to_def_id(); - if default.is_some() { - record!(self.tables.const_param_default[def_id] <- self.tcx.const_param_default(def_id)) - } - } - } - } - } - - fn encode_info_for_expr(&mut self, expr: &hir::Expr<'_>) { - if let hir::ExprKind::Closure(closure) = expr.kind { - self.encode_info_for_closure(closure.def_id); - } - } } /// Used to prefetch queries which will be needed later by metadata encoding. @@ -2273,12 +2198,13 @@ fn encode_metadata_impl(tcx: TyCtxt<'_>, path: &Path) { feat: tcx.features(), tables: Default::default(), lazy_state: LazyState::NoNode, + span_shorthands: Default::default(), type_shorthands: Default::default(), predicate_shorthands: Default::default(), source_file_cache, interpret_allocs: Default::default(), required_source_files, - is_proc_macro: tcx.sess.crate_types().contains(&CrateType::ProcMacro), + is_proc_macro: tcx.crate_types().contains(&CrateType::ProcMacro), hygiene_ctxt: &hygiene_ctxt, symbol_table: Default::default(), }; @@ -2321,13 +2247,12 @@ pub fn provide(providers: &mut Providers) { tcx.resolutions(()) .doc_link_resolutions .get(&def_id) - .expect("no resolutions for a doc link") + .unwrap_or_else(|| span_bug!(tcx.def_span(def_id), "no resolutions for a doc link")) }, doc_link_traits_in_scope: |tcx, def_id| { - tcx.resolutions(()) - .doc_link_traits_in_scope - .get(&def_id) - .expect("no traits in scope for a doc link") + tcx.resolutions(()).doc_link_traits_in_scope.get(&def_id).unwrap_or_else(|| { + span_bug!(tcx.def_span(def_id), "no traits in scope for a doc link") + }) }, traits: |tcx, LocalCrate| { let mut traits = Vec::new(); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 97e67fcf8fdd0..a89e235ff28df 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -6,6 +6,7 @@ use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use table::TableBuilder; use rustc_ast as ast; +use rustc_ast::expand::StrippedCfgItem; use rustc_attr as attr; use rustc_data_structures::svh::Svh; use rustc_hir as hir; @@ -31,7 +32,7 @@ use rustc_span::edition::Edition; use rustc_span::hygiene::{ExpnIndex, MacroKind}; use rustc_span::symbol::{Ident, Symbol}; use rustc_span::{self, ExpnData, ExpnHash, ExpnId, Span}; -use rustc_target::abi::VariantIdx; +use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_target::spec::{PanicStrategy, TargetTriple}; use std::marker::PhantomData; @@ -50,13 +51,13 @@ mod encoder; mod table; pub(crate) fn rustc_version(cfg_version: &'static str) -> String { - format!("rustc {}", cfg_version) + format!("rustc {cfg_version}") } /// Metadata encoding version. /// N.B., increment this if you change the format of metadata such that /// the rustc version can't be found to compare with `rustc_version()`. -const METADATA_VERSION: u8 = 7; +const METADATA_VERSION: u8 = 8; /// Metadata header which includes `METADATA_VERSION`. /// @@ -65,6 +66,12 @@ const METADATA_VERSION: u8 = 7; /// unsigned integer, and further followed by the rustc version string. pub const METADATA_HEADER: &[u8] = &[b'r', b'u', b's', b't', 0, 0, 0, METADATA_VERSION]; +#[derive(Encodable, Decodable)] +enum SpanEncodingMode { + Shorthand(usize), + Direct, +} + /// A value of type T referred to by its absolute position /// in the metadata, and which can be decoded lazily. /// @@ -199,7 +206,27 @@ pub(crate) struct ProcMacroData { macros: LazyArray, } -/// Serialized metadata for a crate. +/// Serialized crate metadata. +/// +/// This contains just enough information to determine if we should load the `CrateRoot` or not. +/// Prefer [`CrateRoot`] whenever possible to avoid ICEs when using `omit-git-hash` locally. +/// See #76720 for more details. +/// +/// If you do modify this struct, also bump the [`METADATA_VERSION`] constant. +#[derive(MetadataEncodable, MetadataDecodable)] +pub(crate) struct CrateHeader { + pub(crate) triple: TargetTriple, + pub(crate) hash: Svh, + pub(crate) name: Symbol, + /// Whether this is the header for a proc-macro crate. + /// + /// This is separate from [`ProcMacroData`] to avoid having to update [`METADATA_VERSION`] every + /// time ProcMacroData changes. + pub(crate) is_proc_macro_crate: bool, +} + +/// Serialized `.rmeta` data for a crate. +/// /// When compiling a proc-macro crate, we encode many of /// the `LazyArray` fields as `Lazy::empty()`. This serves two purposes: /// @@ -217,10 +244,10 @@ pub(crate) struct ProcMacroData { /// to being unused. #[derive(MetadataEncodable, MetadataDecodable)] pub(crate) struct CrateRoot { - name: Symbol, - triple: TargetTriple, + /// A header used to detect if this is the right crate to load. + header: CrateHeader, + extra_filename: String, - hash: Svh, stable_crate_id: StableCrateId, required_panic_strategy: Option, panic_in_drop_strategy: PanicStrategy, @@ -236,13 +263,14 @@ pub(crate) struct CrateRoot { stability_implications: LazyArray<(Symbol, Symbol)>, lang_items: LazyArray<(DefIndex, LangItem)>, lang_items_missing: LazyArray, + stripped_cfg_items: LazyArray>, diagnostic_items: LazyArray<(Symbol, DefIndex)>, native_libraries: LazyArray, foreign_modules: LazyArray, traits: LazyArray, impls: LazyArray, incoherent_impls: LazyArray, - interpret_alloc_index: LazyArray, + interpret_alloc_index: LazyArray, proc_macro_data: Option, tables: LazyTables, @@ -302,6 +330,7 @@ pub(crate) struct CrateDep { pub host_hash: Option, pub kind: CrateDepKind, pub extra_filename: String, + pub is_private: bool, } #[derive(MetadataEncodable, MetadataDecodable)] @@ -352,7 +381,7 @@ define_tables! { is_type_alias_impl_trait: Table, attr_flags: Table, def_path_hashes: Table, - explicit_item_bounds: Table, Span)>>, + explicit_item_bounds: Table, Span)>>, inferred_outlives_of: Table, Span)>>, inherent_impls: Table>, associated_types_for_impl_traits_in_associated_fn: Table>, @@ -393,13 +422,14 @@ define_tables! { object_lifetime_default: Table>, optimized_mir: Table>>, mir_for_ctfe: Table>>, + closure_saved_names_of_captured_variables: Table>>, mir_generator_witnesses: Table>>, promoted_mir: Table>>>, thir_abstract_const: Table>>>, impl_parent: Table, impl_polarity: Table, constness: Table, - impl_defaultness: Table, + defaultness: Table, // FIXME(eddyb) perhaps compute this on the fly if cheap enough? coerce_unsized_info: Table>, mir_const_qualif: Table>, @@ -427,6 +457,7 @@ define_tables! { trait_impl_trait_tys: Table>>>>, doc_link_resolutions: Table>, doc_link_traits_in_scope: Table>, + assumed_wf_types_for_rpitit: Table, Span)>>, } #[derive(TyEncodable, TyDecodable)] @@ -465,6 +496,7 @@ trivially_parameterized_over_tcx! { RawDefId, TraitImpls, IncoherentImpls, + CrateHeader, CrateRoot, CrateDep, AttrFlags, diff --git a/compiler/rustc_metadata/src/rmeta/table.rs b/compiler/rustc_metadata/src/rmeta/table.rs index dda30bce2c057..ea66c770b7742 100644 --- a/compiler/rustc_metadata/src/rmeta/table.rs +++ b/compiler/rustc_metadata/src/rmeta/table.rs @@ -126,7 +126,8 @@ fixed_size_enum! { ( Enum ) ( Variant ) ( Trait ) - ( TyAlias ) + ( TyAlias { lazy: false } ) + ( TyAlias { lazy: true } ) ( ForeignTy ) ( TraitAlias ) ( AssocTy ) @@ -142,7 +143,6 @@ fixed_size_enum! { ( AnonConst ) ( InlineConst ) ( OpaqueTy ) - ( ImplTraitPlaceholder ) ( Field ) ( LifetimeParam ) ( GlobalAsm ) @@ -324,7 +324,7 @@ impl FixedSizeEncoding for Option> { impl LazyArray { #[inline] fn write_to_bytes_impl(self, b: &mut [u8; 8]) { - let ([position_bytes, meta_bytes],[])= b.as_chunks_mut::<4>() else { panic!() }; + let ([position_bytes, meta_bytes], []) = b.as_chunks_mut::<4>() else { panic!() }; let position = self.position.get(); let position: u32 = position.try_into().unwrap(); @@ -347,7 +347,7 @@ impl FixedSizeEncoding for LazyArray { #[inline] fn from_bytes(b: &[u8; 8]) -> Self { - let ([position_bytes, meta_bytes],[])= b.as_chunks::<4>() else { panic!() }; + let ([position_bytes, meta_bytes], []) = b.as_chunks::<4>() else { panic!() }; if *meta_bytes == [0; 4] { return Default::default(); } @@ -366,7 +366,7 @@ impl FixedSizeEncoding for Option> { #[inline] fn from_bytes(b: &[u8; 8]) -> Self { - let ([position_bytes, meta_bytes],[])= b.as_chunks::<4>() else { panic!() }; + let ([position_bytes, meta_bytes], []) = b.as_chunks::<4>() else { panic!() }; LazyArray::from_bytes_impl(position_bytes, meta_bytes) } @@ -439,7 +439,7 @@ where /// Given the metadata, extract out the value at a particular index (if any). #[inline(never)] pub(super) fn get<'a, 'tcx, M: Metadata<'a, 'tcx>>(&self, metadata: M, i: I) -> T::Value<'tcx> { - debug!("LazyTable::lookup: index={:?} len={:?}", i, self.encoded_size); + trace!("LazyTable::lookup: index={:?} len={:?}", i, self.encoded_size); let start = self.position.get(); let bytes = &metadata.blob()[start..start + self.encoded_size]; diff --git a/compiler/rustc_middle/Cargo.toml b/compiler/rustc_middle/Cargo.toml index 7c56af1da41a5..bb8e774cea3d1 100644 --- a/compiler/rustc_middle/Cargo.toml +++ b/compiler/rustc_middle/Cargo.toml @@ -7,14 +7,13 @@ edition = "2021" [dependencies] bitflags = "1.2.1" -chalk-ir = "0.87.0" derive_more = "0.99.17" either = "1.5.0" gsgdt = "0.1.2" field-offset = "0.3.5" measureme = "10.0.0" polonius-engine = "0.13.0" -rustc_apfloat = { path = "../rustc_apfloat" } +rustc_apfloat = "0.2.0" rustc_arena = { path = "../rustc_arena" } rustc_ast = { path = "../rustc_ast" } rustc_attr = { path = "../rustc_attr" } diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl index 64d511c261aa9..108a10b506beb 100644 --- a/compiler/rustc_middle/messages.ftl +++ b/compiler/rustc_middle/messages.ftl @@ -1,45 +1,83 @@ +middle_adjust_for_foreign_abi_error = + target architecture {$arch} does not support `extern {$abi}` ABI + +middle_assert_async_resume_after_panic = `async fn` resumed after panicking + +middle_assert_async_resume_after_return = `async fn` resumed after completion + +middle_assert_divide_by_zero = + attempt to divide `{$val}` by zero + +middle_assert_generator_resume_after_panic = generator resumed after panicking + +middle_assert_generator_resume_after_return = generator resumed after completion + +middle_assert_misaligned_ptr_deref = + misaligned pointer dereference: address must be a multiple of {$required} but is {$found} + +middle_assert_op_overflow = + attempt to compute `{$left} {$op} {$right}`, which would overflow + +middle_assert_overflow_neg = + attempt to negate `{$val}`, which would overflow + +middle_assert_remainder_by_zero = + attempt to calculate the remainder of `{$val}` with a divisor of zero + +middle_assert_shl_overflow = + attempt to shift left by `{$val}`, which would overflow + +middle_assert_shr_overflow = + attempt to shift right by `{$val}`, which would overflow + +middle_bounds_check = + index out of bounds: the length is {$len} but the index is {$index} + +middle_cannot_be_normalized = + unable to determine layout for `{$ty}` because `{$failure_ty}` cannot be normalized + +middle_conflict_types = + this expression supplies two conflicting concrete types for the same opaque type + +middle_const_eval_non_int = + constant evaluation of enum discriminant resulted in non-integer + +middle_const_not_used_in_type_alias = + const parameter `{$ct}` is part of concrete type but not used in parameter list for the `impl Trait` type alias + +middle_cycle = + a cycle occurred during layout computation + middle_drop_check_overflow = overflow while adding drop-check rules for {$ty} .note = overflowed on {$overflow_ty} +middle_layout_references_error = + the type has an unknown layout + +middle_limit_invalid = + `limit` must be a non-negative integer + .label = {$error_str} + middle_opaque_hidden_type_mismatch = concrete type differs from previous defining opaque type use .label = expected `{$self_ty}`, got `{$other_ty}` -middle_conflict_types = - this expression supplies two conflicting concrete types for the same opaque type - middle_previous_use_here = previous use here -middle_limit_invalid = - `limit` must be a non-negative integer - .label = {$error_str} - middle_recursion_limit_reached = reached the recursion limit finding the struct tail for `{$ty}` .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` -middle_const_eval_non_int = - constant evaluation of enum discriminant resulted in non-integer - -middle_unknown_layout = - the type `{$ty}` has an unknown layout - -middle_values_too_big = - values of the type `{$ty}` are too big for the current architecture - -middle_cannot_be_normalized = - unable to determine layout for `{$ty}` because `{$failure_ty}` cannot be normalized - -middle_cycle = - a cycle occurred during layout computation +middle_requires_lang_item = requires `{$name}` lang_item middle_strict_coherence_needs_negative_coherence = to use `strict_coherence` on this trait, the `with_negative_coherence` feature must be enabled .label = due to this attribute -middle_requires_lang_item = requires `{$name}` lang_item +middle_unknown_layout = + the type `{$ty}` has an unknown layout -middle_const_not_used_in_type_alias = - const parameter `{$ct}` is part of concrete type but not used in parameter list for the `impl Trait` type alias +middle_values_too_big = + values of the type `{$ty}` are too big for the current architecture diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index a149a61ec136e..952c796f52e76 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -27,6 +27,11 @@ macro_rules! arena_types { rustc_middle::mir::Promoted, rustc_middle::mir::Body<'tcx> >, + [decode] closure_debuginfo: + rustc_index::IndexVec< + rustc_target::abi::FieldIdx, + rustc_span::symbol::Symbol, + >, [decode] typeck_results: rustc_middle::ty::TypeckResults<'tcx>, [decode] borrowck_result: rustc_middle::mir::BorrowCheckResult<'tcx>, @@ -35,7 +40,6 @@ macro_rules! arena_types { rustc_data_structures::sync::Lrc, )>, [] output_filenames: std::sync::Arc, - [] metadata_loader: rustc_data_structures::steal::Steal>, [] crate_for_resolver: rustc_data_structures::steal::Steal<(rustc_ast::Crate, rustc_ast::AttrVec)>, [] resolutions: rustc_middle::ty::ResolverGlobalCtxt, [decode] unsafety_check_result: rustc_middle::mir::UnsafetyCheckResult, @@ -78,9 +82,9 @@ macro_rules! arena_types { rustc_middle::infer::canonical::Canonical<'tcx, rustc_middle::infer::canonical::QueryResponse<'tcx, rustc_middle::ty::FnSig<'tcx>> >, - [] type_op_normalize_predicate: + [] type_op_normalize_clause: rustc_middle::infer::canonical::Canonical<'tcx, - rustc_middle::infer::canonical::QueryResponse<'tcx, rustc_middle::ty::Predicate<'tcx>> + rustc_middle::infer::canonical::QueryResponse<'tcx, rustc_middle::ty::Clause<'tcx>> >, [] type_op_normalize_ty: rustc_middle::infer::canonical::Canonical<'tcx, @@ -124,7 +128,9 @@ macro_rules! arena_types { [] predefined_opaques_in_body: rustc_middle::traits::solve::PredefinedOpaquesData<'tcx>, [decode] doc_link_resolutions: rustc_hir::def::DocLinkResMap, [] closure_kind_origin: (rustc_span::Span, rustc_middle::hir::place::Place<'tcx>), + [] stripped_cfg_items: rustc_ast::expand::StrippedCfgItem, [] mod_child: rustc_middle::metadata::ModChild, + [] features: rustc_feature::Features, ]); ) } diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index 2dc5b8969934f..04c09d334001b 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -60,7 +60,7 @@ use crate::mir::mono::MonoItem; use crate::ty::TyCtxt; use rustc_data_structures::fingerprint::Fingerprint; -use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; +use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, ModDefId, LOCAL_CRATE}; use rustc_hir::definitions::DefPathHash; use rustc_hir::{HirId, ItemLocalId, OwnerId}; use rustc_query_system::dep_graph::FingerprintStyle; @@ -371,7 +371,7 @@ impl<'tcx> DepNodeParams> for HirId { fn recover(tcx: TyCtxt<'tcx>, dep_node: &DepNode) -> Option { if tcx.fingerprint_style(dep_node.kind) == FingerprintStyle::HirId { let (local_hash, local_id) = Fingerprint::from(dep_node.hash).split(); - let def_path_hash = DefPathHash::new(tcx.sess.local_stable_crate_id(), local_hash); + let def_path_hash = DefPathHash::new(tcx.stable_crate_id(LOCAL_CRATE), local_hash); let def_id = tcx .def_path_hash_to_def_id(def_path_hash, &mut || { panic!("Failed to extract HirId: {:?} {}", dep_node.kind, dep_node.hash) @@ -380,10 +380,60 @@ impl<'tcx> DepNodeParams> for HirId { let local_id = local_id .as_u64() .try_into() - .unwrap_or_else(|_| panic!("local id should be u32, found {:?}", local_id)); + .unwrap_or_else(|_| panic!("local id should be u32, found {local_id:?}")); Some(HirId { owner: OwnerId { def_id }, local_id: ItemLocalId::from_u32(local_id) }) } else { None } } } + +macro_rules! impl_for_typed_def_id { + ($Name:ident, $LocalName:ident) => { + impl<'tcx> DepNodeParams> for $Name { + #[inline(always)] + fn fingerprint_style() -> FingerprintStyle { + FingerprintStyle::DefPathHash + } + + #[inline(always)] + fn to_fingerprint(&self, tcx: TyCtxt<'tcx>) -> Fingerprint { + self.to_def_id().to_fingerprint(tcx) + } + + #[inline(always)] + fn to_debug_str(&self, tcx: TyCtxt<'tcx>) -> String { + self.to_def_id().to_debug_str(tcx) + } + + #[inline(always)] + fn recover(tcx: TyCtxt<'tcx>, dep_node: &DepNode) -> Option { + DefId::recover(tcx, dep_node).map($Name::new_unchecked) + } + } + + impl<'tcx> DepNodeParams> for $LocalName { + #[inline(always)] + fn fingerprint_style() -> FingerprintStyle { + FingerprintStyle::DefPathHash + } + + #[inline(always)] + fn to_fingerprint(&self, tcx: TyCtxt<'tcx>) -> Fingerprint { + self.to_def_id().to_fingerprint(tcx) + } + + #[inline(always)] + fn to_debug_str(&self, tcx: TyCtxt<'tcx>) -> String { + self.to_def_id().to_debug_str(tcx) + } + + #[inline(always)] + fn recover(tcx: TyCtxt<'tcx>, dep_node: &DepNode) -> Option { + LocalDefId::recover(tcx, dep_node).map($LocalName::new_unchecked) + } + } + }; +} + +impl_for_typed_def_id! { ModDefId, LocalModDefId } diff --git a/compiler/rustc_middle/src/dep_graph/mod.rs b/compiler/rustc_middle/src/dep_graph/mod.rs index 0ddbe7d1c2922..f79ce08b8aed4 100644 --- a/compiler/rustc_middle/src/dep_graph/mod.rs +++ b/compiler/rustc_middle/src/dep_graph/mod.rs @@ -8,7 +8,7 @@ mod dep_node; pub use rustc_query_system::dep_graph::{ debug::DepNodeFilter, hash_result, DepContext, DepNodeColor, DepNodeIndex, - SerializedDepNodeIndex, WorkProduct, WorkProductId, + SerializedDepNodeIndex, WorkProduct, WorkProductId, WorkProductMap, }; pub use dep_node::{label_strs, DepKind, DepNode, DepNodeExt}; @@ -35,7 +35,7 @@ impl rustc_query_system::dep_graph::DepKind for DepKind { if let Some(def_id) = node.extract_def_id(tcx) { write!(f, "{}", tcx.def_path_debug_str(def_id))?; } else if let Some(ref s) = tcx.dep_graph.dep_node_debug_str(*node) { - write!(f, "{}", s)?; + write!(f, "{s}")?; } else { write!(f, "{}", node.hash)?; } diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 046186d274ca7..b346cd4539144 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -1,3 +1,7 @@ +use std::borrow::Cow; +use std::fmt; + +use rustc_errors::{DiagnosticArgValue, DiagnosticMessage}; use rustc_macros::Diagnostic; use rustc_span::{Span, Symbol}; @@ -88,3 +92,57 @@ pub(super) struct ConstNotUsedTraitAlias { #[primary_span] pub span: Span, } + +pub struct CustomSubdiagnostic<'a> { + pub msg: fn() -> DiagnosticMessage, + pub add_args: + Box, DiagnosticArgValue<'static>)) + 'a>, +} + +impl<'a> CustomSubdiagnostic<'a> { + pub fn label(x: fn() -> DiagnosticMessage) -> Self { + Self::label_and_then(x, |_| {}) + } + pub fn label_and_then< + F: FnOnce(&mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) + 'a, + >( + msg: fn() -> DiagnosticMessage, + f: F, + ) -> Self { + Self { msg, add_args: Box::new(move |x| f(x)) } + } +} + +impl fmt::Debug for CustomSubdiagnostic<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CustomSubdiagnostic").finish_non_exhaustive() + } +} + +#[derive(Diagnostic)] +pub enum LayoutError<'tcx> { + #[diag(middle_unknown_layout)] + Unknown { ty: Ty<'tcx> }, + + #[diag(middle_values_too_big)] + Overflow { ty: Ty<'tcx> }, + + #[diag(middle_cannot_be_normalized)] + NormalizationFailure { ty: Ty<'tcx>, failure_ty: String }, + + #[diag(middle_cycle)] + Cycle, + + #[diag(middle_layout_references_error)] + ReferencesError, +} + +#[derive(Diagnostic)] +#[diag(middle_adjust_for_foreign_abi_error)] +pub struct UnsupportedFnAbi { + pub arch: Symbol, + pub abi: &'static str, +} + +/// Used by `rustc_const_eval` +pub use crate::fluent_generated::middle_adjust_for_foreign_abi_error; diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs index d1ddc8fc1fd2e..467962b39bb1b 100644 --- a/compiler/rustc_middle/src/hir/map/mod.rs +++ b/compiler/rustc_middle/src/hir/map/mod.rs @@ -8,7 +8,7 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::{par_for_each_in, DynSend, DynSync}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, LOCAL_CRATE}; use rustc_hir::definitions::{DefKey, DefPath, DefPathData, DefPathHash}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::*; @@ -24,7 +24,7 @@ pub fn associated_body(node: Node<'_>) -> Option<(LocalDefId, BodyId)> { match node { Node::Item(Item { owner_id, - kind: ItemKind::Const(_, body) | ItemKind::Static(.., body) | ItemKind::Fn(.., body), + kind: ItemKind::Const(_, _, body) | ItemKind::Static(.., body) | ItemKind::Fn(.., body), .. }) | Node::TraitItem(TraitItem { @@ -44,6 +44,7 @@ pub fn associated_body(node: Node<'_>) -> Option<(LocalDefId, BodyId)> { } Node::AnonConst(constant) => Some((constant.def_id, constant.body)), + Node::ConstBlock(constant) => Some((constant.def_id, constant.body)), _ => None, } @@ -147,7 +148,7 @@ impl<'hir> Map<'hir> { } #[inline] - pub fn module_items(self, module: LocalDefId) -> impl Iterator + 'hir { + pub fn module_items(self, module: LocalModDefId) -> impl Iterator + 'hir { self.tcx.hir_module_items(module).items() } @@ -168,8 +169,8 @@ impl<'hir> Map<'hir> { } #[inline] - pub fn local_def_id_to_hir_id(self, def_id: LocalDefId) -> HirId { - self.tcx.local_def_id_to_hir_id(def_id) + pub fn local_def_id_to_hir_id(self, def_id: impl Into) -> HirId { + self.tcx.local_def_id_to_hir_id(def_id.into()) } /// Do not call this function directly. The query should be called. @@ -194,14 +195,10 @@ impl<'hir> Map<'hir> { ItemKind::Fn(..) => DefKind::Fn, ItemKind::Macro(_, macro_kind) => DefKind::Macro(macro_kind), ItemKind::Mod(..) => DefKind::Mod, - ItemKind::OpaqueTy(ref opaque) => { - if opaque.in_trait && !self.tcx.lower_impl_trait_in_trait_to_assoc_ty() { - DefKind::ImplTraitPlaceholder - } else { - DefKind::OpaqueTy - } + ItemKind::OpaqueTy(..) => DefKind::OpaqueTy, + ItemKind::TyAlias(..) => { + DefKind::TyAlias { lazy: self.tcx.features().lazy_type_alias } } - ItemKind::TyAlias(..) => DefKind::TyAlias, ItemKind::Enum(..) => DefKind::Enum, ItemKind::Struct(..) => DefKind::Struct, ItemKind::Union(..) => DefKind::Union, @@ -240,15 +237,8 @@ impl<'hir> Map<'hir> { None => bug!("constructor node without a constructor"), } } - Node::AnonConst(_) => { - let inline = match self.find_parent(hir_id) { - Some(Node::Expr(&Expr { - kind: ExprKind::ConstBlock(ref anon_const), .. - })) if anon_const.hir_id == hir_id => true, - _ => false, - }; - if inline { DefKind::InlineConst } else { DefKind::AnonConst } - } + Node::AnonConst(_) => DefKind::AnonConst, + Node::ConstBlock(_) => DefKind::InlineConst, Node::Field(_) => DefKind::Field, Node::Expr(expr) => match expr.kind { ExprKind::Closure(Closure { movability: None, .. }) => DefKind::Closure, @@ -539,20 +529,20 @@ impl<'hir> Map<'hir> { self.krate_attrs().iter().any(|attr| attr.has_name(sym::rustc_coherence_is_core)) } - pub fn get_module(self, module: LocalDefId) -> (&'hir Mod<'hir>, Span, HirId) { - let hir_id = HirId::make_owner(module); + pub fn get_module(self, module: LocalModDefId) -> (&'hir Mod<'hir>, Span, HirId) { + let hir_id = HirId::make_owner(module.to_local_def_id()); match self.tcx.hir_owner(hir_id.owner).map(|o| o.node) { Some(OwnerNode::Item(&Item { span, kind: ItemKind::Mod(ref m), .. })) => { (m, span, hir_id) } Some(OwnerNode::Crate(item)) => (item, item.spans.inner_span, hir_id), - node => panic!("not a module: {:?}", node), + node => panic!("not a module: {node:?}"), } } /// Walks the contents of the local crate. See also `visit_all_item_likes_in_crate`. pub fn walk_toplevel_module(self, visitor: &mut impl Visitor<'hir>) { - let (top_mod, span, hir_id) = self.get_module(CRATE_DEF_ID); + let (top_mod, span, hir_id) = self.get_module(LocalModDefId::CRATE_DEF_ID); visitor.visit_mod(top_mod, span, hir_id); } @@ -605,7 +595,7 @@ impl<'hir> Map<'hir> { /// This method is the equivalent of `visit_all_item_likes_in_crate` but restricted to /// item-likes in a single module. - pub fn visit_item_likes_in_module(self, module: LocalDefId, visitor: &mut V) + pub fn visit_item_likes_in_module(self, module: LocalModDefId, visitor: &mut V) where V: Visitor<'hir>, { @@ -628,17 +618,19 @@ impl<'hir> Map<'hir> { } } - pub fn for_each_module(self, mut f: impl FnMut(LocalDefId)) { + pub fn for_each_module(self, mut f: impl FnMut(LocalModDefId)) { let crate_items = self.tcx.hir_crate_items(()); for module in crate_items.submodules.iter() { - f(module.def_id) + f(LocalModDefId::new_unchecked(module.def_id)) } } #[inline] - pub fn par_for_each_module(self, f: impl Fn(LocalDefId) + DynSend + DynSync) { + pub fn par_for_each_module(self, f: impl Fn(LocalModDefId) + DynSend + DynSync) { let crate_items = self.tcx.hir_crate_items(()); - par_for_each_in(&crate_items.submodules[..], |module| f(module.def_id)) + par_for_each_in(&crate_items.submodules[..], |module| { + f(LocalModDefId::new_unchecked(module.def_id)) + }) } /// Returns an iterator for the nodes in the ancestor tree of the `current_id` @@ -747,17 +739,6 @@ impl<'hir> Map<'hir> { } } - /// Returns the `OwnerId` of `id`'s nearest module parent, or `id` itself if no - /// module parent is in this map. - pub(super) fn get_module_parent_node(self, hir_id: HirId) -> OwnerId { - for (def_id, node) in self.parent_owner_iter(hir_id) { - if let OwnerNode::Item(&Item { kind: ItemKind::Mod(_), .. }) = node { - return def_id; - } - } - CRATE_OWNER_ID - } - /// When on an if expression, a match arm tail expression or a match arm, give back /// the enclosing `if` or `match` expression. /// @@ -1060,6 +1041,7 @@ impl<'hir> Map<'hir> { Node::Variant(variant) => variant.span, Node::Field(field) => field.span, Node::AnonConst(constant) => self.body(constant.body).value.span, + Node::ConstBlock(constant) => self.body(constant.body).value.span, Node::Expr(expr) => expr.span, Node::ExprField(field) => field.span, Node::Stmt(stmt) => stmt.span, @@ -1114,6 +1096,33 @@ impl<'hir> Map<'hir> { _ => None, } } + + pub fn maybe_get_struct_pattern_shorthand_field(&self, expr: &Expr<'_>) -> Option { + let local = match expr { + Expr { + kind: + ExprKind::Path(QPath::Resolved( + None, + Path { + res: def::Res::Local(_), segments: [PathSegment { ident, .. }], .. + }, + )), + .. + } => Some(ident), + _ => None, + }?; + + match self.find_parent(expr.hir_id)? { + Node::ExprField(field) => { + if field.ident.name == local.name && field.is_shorthand { + return Some(local.name); + } + } + _ => {} + } + + None + } } impl<'hir> intravisit::Map<'hir> for Map<'hir> { @@ -1203,7 +1212,7 @@ pub(super) fn crate_hash(tcx: TyCtxt<'_>, _: LocalCrate) -> Svh { owner_spans.hash_stable(&mut hcx, &mut stable_hasher); } tcx.sess.opts.dep_tracking_hash(true).hash_stable(&mut hcx, &mut stable_hasher); - tcx.sess.local_stable_crate_id().hash_stable(&mut hcx, &mut stable_hasher); + tcx.stable_crate_id(LOCAL_CRATE).hash_stable(&mut hcx, &mut stable_hasher); // Hash visibility information since it does not appear in HIR. resolutions.visibilities.hash_stable(&mut hcx, &mut stable_hasher); resolutions.has_pub_restricted.hash_stable(&mut hcx, &mut stable_hasher); @@ -1289,6 +1298,7 @@ fn hir_id_to_string(map: Map<'_>, id: HirId) -> String { format!("{id} (field `{}` in {})", field.ident, path_str(field.def_id)) } Some(Node::AnonConst(_)) => node_str("const"), + Some(Node::ConstBlock(_)) => node_str("const"), Some(Node::Expr(_)) => node_str("expr"), Some(Node::ExprField(_)) => node_str("expr field"), Some(Node::Stmt(_)) => node_str("stmt"), @@ -1316,7 +1326,7 @@ fn hir_id_to_string(map: Map<'_>, id: HirId) -> String { } } -pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalDefId) -> ModuleItems { +pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModDefId) -> ModuleItems { let mut collector = ItemCollector::new(tcx, false); let (hir_mod, span, hir_id) = tcx.hir().get_module(module_id); @@ -1434,6 +1444,11 @@ impl<'hir> Visitor<'hir> for ItemCollector<'hir> { intravisit::walk_anon_const(self, c) } + fn visit_inline_const(&mut self, c: &'hir ConstBlock) { + self.body_owners.push(c.def_id); + intravisit::walk_inline_const(self, c) + } + fn visit_expr(&mut self, ex: &'hir Expr<'hir>) { if let ExprKind::Closure(closure) = ex.kind { self.body_owners.push(closure.def_id); diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 45a07fdd29327..0da8fe9cca72f 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -11,7 +11,7 @@ use crate::ty::{EarlyBinder, ImplSubject, TyCtxt}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::sync::{par_for_each_in, DynSend, DynSync}; use rustc_hir::def::DefKind; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::*; use rustc_query_system::ich::StableHashingContext; use rustc_span::{ExpnId, DUMMY_SP}; @@ -101,8 +101,22 @@ impl<'tcx> TyCtxt<'tcx> { map::Map { tcx: self } } - pub fn parent_module(self, id: HirId) -> LocalDefId { - self.parent_module_from_def_id(id.owner.def_id) + pub fn parent_module(self, id: HirId) -> LocalModDefId { + if !id.is_owner() && self.def_kind(id.owner) == DefKind::Mod { + LocalModDefId::new_unchecked(id.owner.def_id) + } else { + self.parent_module_from_def_id(id.owner.def_id) + } + } + + pub fn parent_module_from_def_id(self, mut id: LocalDefId) -> LocalModDefId { + while let Some(parent) = self.opt_local_parent(id) { + id = parent; + if self.def_kind(id) == DefKind::Mod { + break; + } + } + LocalModDefId::new_unchecked(id) } pub fn impl_subject(self, def_id: DefId) -> EarlyBinder> { @@ -120,10 +134,6 @@ impl<'tcx> TyCtxt<'tcx> { } pub fn provide(providers: &mut Providers) { - providers.parent_module_from_def_id = |tcx, id| { - let hir = tcx.hir(); - hir.get_module_parent_node(hir.local_def_id_to_hir_id(id)).def_id - }; providers.hir_crate_items = map::hir_crate_items; providers.crate_hash = map::crate_hash; providers.hir_module_items = map::hir_module_items; @@ -154,18 +164,15 @@ pub fn provide(providers: &mut Providers) { tcx.hir_crate(()).owners[id.def_id].as_owner().map_or(AttributeMap::EMPTY, |o| &o.attrs) }; providers.def_span = |tcx, def_id| { - let def_id = def_id; let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); tcx.hir().opt_span(hir_id).unwrap_or(DUMMY_SP) }; providers.def_ident_span = |tcx, def_id| { - let def_id = def_id; let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); tcx.hir().opt_ident_span(hir_id) }; - providers.fn_arg_names = |tcx, id| { + providers.fn_arg_names = |tcx, def_id| { let hir = tcx.hir(); - let def_id = id; let hir_id = hir.local_def_id_to_hir_id(def_id); if let Some(body_id) = hir.maybe_body_owned_by(def_id) { tcx.arena.alloc_from_iter(hir.body_param_names(body_id)) @@ -180,7 +187,7 @@ pub fn provide(providers: &mut Providers) { { idents } else { - span_bug!(hir.span(hir_id), "fn_arg_names: unexpected item {:?}", id); + span_bug!(hir.span(hir_id), "fn_arg_names: unexpected item {:?}", def_id); } }; providers.opt_def_kind = |tcx, def_id| tcx.hir().opt_def_kind(def_id); diff --git a/compiler/rustc_middle/src/hir/place.rs b/compiler/rustc_middle/src/hir/place.rs index 8a22de931c35b..32f3a177508f7 100644 --- a/compiler/rustc_middle/src/hir/place.rs +++ b/compiler/rustc_middle/src/hir/place.rs @@ -36,6 +36,10 @@ pub enum ProjectionKind { /// A subslice covering a range of values like `B[x..y]`. Subslice, + + /// A conversion from an opaque type to its hidden type so we can + /// do further projections on it. + OpaqueCast, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)] diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs index 56171314944cc..81823118ab810 100644 --- a/compiler/rustc_middle/src/infer/canonical.rs +++ b/compiler/rustc_middle/src/infer/canonical.rs @@ -23,7 +23,7 @@ use crate::infer::MemberConstraint; use crate::mir::ConstraintCategory; -use crate::ty::subst::GenericArg; +use crate::ty::GenericArg; use crate::ty::{self, BoundVar, List, Region, Ty, TyCtxt}; use rustc_macros::HashStable; use smallvec::SmallVec; @@ -63,7 +63,7 @@ impl<'tcx> ty::TypeFoldable> for CanonicalVarInfos<'tcx> { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyDecodable, TyEncodable)] #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] pub struct CanonicalVarValues<'tcx> { - pub var_values: ty::SubstsRef<'tcx>, + pub var_values: ty::GenericArgsRef<'tcx>, } impl CanonicalVarValues<'_> { @@ -82,15 +82,40 @@ impl CanonicalVarValues<'_> { } pub fn is_identity_modulo_regions(&self) -> bool { - self.var_values.iter().enumerate().all(|(bv, arg)| match arg.unpack() { - ty::GenericArgKind::Lifetime(_) => true, - ty::GenericArgKind::Type(ty) => { - matches!(*ty.kind(), ty::Bound(ty::INNERMOST, bt) if bt.var.as_usize() == bv) - } - ty::GenericArgKind::Const(ct) => { - matches!(ct.kind(), ty::ConstKind::Bound(ty::INNERMOST, bc) if bc.as_usize() == bv) + let mut var = ty::BoundVar::from_u32(0); + for arg in self.var_values { + match arg.unpack() { + ty::GenericArgKind::Lifetime(r) => { + if let ty::ReLateBound(ty::INNERMOST, br) = *r + && var == br.var + { + var = var + 1; + } else { + // It's ok if this region var isn't unique + } + }, + ty::GenericArgKind::Type(ty) => { + if let ty::Bound(ty::INNERMOST, bt) = *ty.kind() + && var == bt.var + { + var = var + 1; + } else { + return false; + } + } + ty::GenericArgKind::Const(ct) => { + if let ty::ConstKind::Bound(ty::INNERMOST, bc) = ct.kind() + && var == bc + { + var = var + 1; + } else { + return false; + } + } } - }) + } + + true } } @@ -404,26 +429,28 @@ impl<'tcx> CanonicalVarValues<'tcx> { infos: CanonicalVarInfos<'tcx>, ) -> CanonicalVarValues<'tcx> { CanonicalVarValues { - var_values: tcx.mk_substs_from_iter(infos.iter().enumerate().map( + var_values: tcx.mk_args_from_iter(infos.iter().enumerate().map( |(i, info)| -> ty::GenericArg<'tcx> { match info.kind { CanonicalVarKind::Ty(_) | CanonicalVarKind::PlaceholderTy(_) => { - tcx.mk_bound(ty::INNERMOST, ty::BoundVar::from_usize(i).into()).into() + Ty::new_bound(tcx, ty::INNERMOST, ty::BoundVar::from_usize(i).into()) + .into() } CanonicalVarKind::Region(_) | CanonicalVarKind::PlaceholderRegion(_) => { let br = ty::BoundRegion { var: ty::BoundVar::from_usize(i), kind: ty::BrAnon(None), }; - tcx.mk_re_late_bound(ty::INNERMOST, br).into() + ty::Region::new_late_bound(tcx, ty::INNERMOST, br).into() } CanonicalVarKind::Const(_, ty) - | CanonicalVarKind::PlaceholderConst(_, ty) => tcx - .mk_const( - ty::ConstKind::Bound(ty::INNERMOST, ty::BoundVar::from_usize(i)), - ty, - ) - .into(), + | CanonicalVarKind::PlaceholderConst(_, ty) => ty::Const::new_bound( + tcx, + ty::INNERMOST, + ty::BoundVar::from_usize(i), + ty, + ) + .into(), } }, )), diff --git a/compiler/rustc_middle/src/infer/mod.rs b/compiler/rustc_middle/src/infer/mod.rs index 2db59f37f4072..493bb8a682333 100644 --- a/compiler/rustc_middle/src/infer/mod.rs +++ b/compiler/rustc_middle/src/infer/mod.rs @@ -15,7 +15,7 @@ use rustc_span::Span; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] pub struct MemberConstraint<'tcx> { - /// The `DefId` and substs of the opaque type causing this constraint. + /// The `DefId` and args of the opaque type causing this constraint. /// Used for error reporting. pub key: OpaqueTypeKey<'tcx>, diff --git a/compiler/rustc_middle/src/infer/unify_key.rs b/compiler/rustc_middle/src/infer/unify_key.rs index a873854f0686a..85fb9214d9d44 100644 --- a/compiler/rustc_middle/src/infer/unify_key.rs +++ b/compiler/rustc_middle/src/infer/unify_key.rs @@ -90,15 +90,15 @@ impl<'tcx> UnifyValue for UnifiedRegion<'tcx> { impl ToType for ty::IntVarValue { fn to_type<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { match *self { - ty::IntType(i) => tcx.mk_mach_int(i), - ty::UintType(i) => tcx.mk_mach_uint(i), + ty::IntType(i) => Ty::new_int(tcx, i), + ty::UintType(i) => Ty::new_uint(tcx, i), } } } impl ToType for ty::FloatVarValue { fn to_type<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { - tcx.mk_mach_float(self.0) + Ty::new_float(tcx, self.0) } } @@ -116,7 +116,6 @@ pub enum ConstVariableOriginKind { MiscVariable, ConstInference, ConstParameterDefinition(Symbol, DefId), - SubstitutionPlaceholder, } #[derive(Copy, Clone, Debug)] diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs index 22ee2a8c5e55e..d3fc1b2850eaa 100644 --- a/compiler/rustc_middle/src/lib.rs +++ b/compiler/rustc_middle/src/lib.rs @@ -35,7 +35,6 @@ #![feature(if_let_guard)] #![feature(inline_const)] #![feature(iter_from_generator)] -#![feature(local_key_cell_methods)] #![feature(negative_impls)] #![feature(never_type)] #![feature(extern_types)] @@ -48,12 +47,13 @@ #![feature(associated_type_bounds)] #![feature(rustc_attrs)] #![feature(control_flow_enum)] +#![feature(trait_upcasting)] #![feature(trusted_step)] #![feature(try_blocks)] #![feature(try_reserve_kind)] #![feature(nonzero_ops)] #![feature(decl_macro)] -#![feature(drain_filter)] +#![feature(extract_if)] #![feature(intra_doc_pointers)] #![feature(yeet_expr)] #![feature(result_option_inspect)] @@ -63,6 +63,7 @@ #![feature(macro_metavar_expr)] #![recursion_limit = "512"] #![allow(rustc::potential_query_instability)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate bitflags; @@ -86,7 +87,7 @@ mod macros; #[macro_use] pub mod arena; -pub(crate) mod error; +pub mod error; pub mod hir; pub mod infer; pub mod lint; diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index c266584ac280f..f62e406692a85 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -169,26 +169,6 @@ impl TyCtxt<'_> { pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) { self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id) } - - /// Walks upwards from `id` to find a node which might change lint levels with attributes. - /// It stops at `bound` and just returns it if reached. - pub fn maybe_lint_level_root_bounded(self, mut id: HirId, bound: HirId) -> HirId { - let hir = self.hir(); - loop { - if id == bound { - return bound; - } - - if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) { - return id; - } - let next = hir.parent_id(id); - if next == id { - bug!("lint traversal reached the root of the crate"); - } - id = next; - } - } } /// This struct represents a lint expectation and holds all required information @@ -238,27 +218,24 @@ pub fn explain_lint_level_source( let hyphen_case_lint_name = name.replace('_', "-"); if lint_flag_val.as_str() == name { err.note_once(format!( - "requested on the command line with `{} {}`", - flag, hyphen_case_lint_name + "requested on the command line with `{flag} {hyphen_case_lint_name}`" )); } else { let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-"); err.note_once(format!( - "`{} {}` implied by `{} {}`", - flag, hyphen_case_lint_name, flag, hyphen_case_flag_val + "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`" )); } } LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => { if let Some(rationale) = reason { - err.note(rationale.as_str()); + err.note(rationale.to_string()); } err.span_note_once(span, "the lint level is defined here"); if lint_attr_name.as_str() != name { let level_str = level.as_str(); err.note_once(format!( - "`#[{}({})]` implied by `#[{}({})]`", - level_str, name, level_str, lint_attr_name + "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`" )); } } @@ -298,6 +275,7 @@ pub fn explain_lint_level_source( /// // ^^^^^^^^^^^^^^^^^^^^^ returns `&mut DiagnosticBuilder` by default /// ) /// ``` +#[track_caller] pub fn struct_lint_level( sess: &Session, lint: &'static Lint, @@ -311,6 +289,7 @@ pub fn struct_lint_level( ) { // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to // the "real" work. + #[track_caller] fn struct_lint_level_impl( sess: &Session, lint: &'static Lint, @@ -388,10 +367,11 @@ pub fn struct_lint_level( // it'll become a hard error, so we have to emit *something*. Also, // if this lint occurs in the expansion of a macro from an external crate, // allow individual lints to opt-out from being reported. - let not_future_incompatible = - future_incompatible.map(|f| f.reason.edition().is_some()).unwrap_or(true); - if not_future_incompatible && !lint.report_in_external_macro { + let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none()); + + if !incompatible && !lint.report_in_external_macro { err.cancel(); + // Don't continue further, since we don't want to have // `diag_span_note_once` called for a diagnostic that isn't emitted. return; @@ -433,12 +413,11 @@ pub fn struct_lint_level( FutureIncompatibilityReason::EditionError(edition) => { let current_edition = sess.edition(); format!( - "this is accepted in the current edition (Rust {}) but is a hard error in Rust {}!", - current_edition, edition + "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!" ) } FutureIncompatibilityReason::EditionSemanticsChange(edition) => { - format!("this changes meaning in Rust {}", edition) + format!("this changes meaning in Rust {edition}") } FutureIncompatibilityReason::Custom(reason) => reason.to_owned(), }; @@ -468,10 +447,13 @@ pub fn struct_lint_level( pub fn in_external_macro(sess: &Session, span: Span) -> bool { let expn_data = span.ctxt().outer_expn_data(); match expn_data.kind { - ExpnKind::Inlined - | ExpnKind::Root + ExpnKind::Root | ExpnKind::Desugaring( - DesugaringKind::ForLoop | DesugaringKind::WhileLoop | DesugaringKind::OpaqueTy, + DesugaringKind::ForLoop + | DesugaringKind::WhileLoop + | DesugaringKind::OpaqueTy + | DesugaringKind::Async + | DesugaringKind::Await, ) => false, ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external" ExpnKind::Macro(MacroKind::Bang, _) => { @@ -481,3 +463,12 @@ pub fn in_external_macro(sess: &Session, span: Span) -> bool { ExpnKind::Macro { .. } => true, // definitely a plugin } } + +/// Return whether `span` is generated by `async` or `await`. +pub fn is_from_async_await(span: Span) -> bool { + let expn_data = span.ctxt().outer_expn_data(); + match expn_data.kind { + ExpnKind::Desugaring(DesugaringKind::Async | DesugaringKind::Await) => true, + _ => false, + } +} diff --git a/compiler/rustc_middle/src/macros.rs b/compiler/rustc_middle/src/macros.rs index cd1c6c330bc1e..fca16d8e509f1 100644 --- a/compiler/rustc_middle/src/macros.rs +++ b/compiler/rustc_middle/src/macros.rs @@ -43,7 +43,7 @@ macro_rules! span_bug { #[macro_export] macro_rules! CloneLiftImpls { - ($($ty:ty,)+) => { + ($($ty:ty),+ $(,)?) => { $( impl<'tcx> $crate::ty::Lift<'tcx> for $ty { type Lifted = Self; @@ -59,7 +59,7 @@ macro_rules! CloneLiftImpls { /// allocated data** (i.e., don't need to be folded). #[macro_export] macro_rules! TrivialTypeTraversalImpls { - ($($ty:ty,)+) => { + ($($ty:ty),+ $(,)?) => { $( impl<'tcx> $crate::ty::fold::TypeFoldable<$crate::ty::TyCtxt<'tcx>> for $ty { fn try_fold_with>>( diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index c4601a1fb4189..02fd6ed7ba6ba 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -100,6 +100,8 @@ bitflags! { const REALLOCATOR = 1 << 18; /// `#[rustc_allocator_zeroed]`: a hint to LLVM that the function only allocates zeroed memory. const ALLOCATOR_ZEROED = 1 << 19; + /// `#[no_builtins]`: indicates that disable implicit builtin knowledge of functions for the function. + const NO_BUILTINS = 1 << 20; } } diff --git a/compiler/rustc_middle/src/middle/exported_symbols.rs b/compiler/rustc_middle/src/middle/exported_symbols.rs index 9041da9a0604d..e30b6b203d73c 100644 --- a/compiler/rustc_middle/src/middle/exported_symbols.rs +++ b/compiler/rustc_middle/src/middle/exported_symbols.rs @@ -1,4 +1,4 @@ -use crate::ty::subst::SubstsRef; +use crate::ty::GenericArgsRef; use crate::ty::{self, Ty, TyCtxt}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_macros::HashStable; @@ -41,7 +41,7 @@ pub struct SymbolExportInfo { #[derive(Eq, PartialEq, Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] pub enum ExportedSymbol<'tcx> { NonGeneric(DefId), - Generic(DefId, SubstsRef<'tcx>), + Generic(DefId, GenericArgsRef<'tcx>), DropGlue(Ty<'tcx>), ThreadLocalShim(DefId), NoDefId(ty::SymbolName<'tcx>), @@ -53,15 +53,15 @@ impl<'tcx> ExportedSymbol<'tcx> { pub fn symbol_name_for_local_instance(&self, tcx: TyCtxt<'tcx>) -> ty::SymbolName<'tcx> { match *self { ExportedSymbol::NonGeneric(def_id) => tcx.symbol_name(ty::Instance::mono(tcx, def_id)), - ExportedSymbol::Generic(def_id, substs) => { - tcx.symbol_name(ty::Instance::new(def_id, substs)) + ExportedSymbol::Generic(def_id, args) => { + tcx.symbol_name(ty::Instance::new(def_id, args)) } ExportedSymbol::DropGlue(ty) => { tcx.symbol_name(ty::Instance::resolve_drop_in_place(tcx, ty)) } ExportedSymbol::ThreadLocalShim(def_id) => tcx.symbol_name(ty::Instance { def: ty::InstanceDef::ThreadLocalShim(def_id), - substs: ty::InternalSubsts::empty(), + args: ty::GenericArgs::empty(), }), ExportedSymbol::NoDefId(symbol_name) => symbol_name, } @@ -72,6 +72,6 @@ pub fn metadata_symbol_name(tcx: TyCtxt<'_>) -> String { format!( "rust_metadata_{}_{:08x}", tcx.crate_name(LOCAL_CRATE), - tcx.sess.local_stable_crate_id(), + tcx.stable_crate_id(LOCAL_CRATE), ) } diff --git a/compiler/rustc_middle/src/middle/limits.rs b/compiler/rustc_middle/src/middle/limits.rs index bd859d4d61beb..d4f023958d6fd 100644 --- a/compiler/rustc_middle/src/middle/limits.rs +++ b/compiler/rustc_middle/src/middle/limits.rs @@ -1,8 +1,7 @@ //! Registering limits: //! * recursion_limit, -//! * move_size_limit, -//! * type_length_limit, and -//! * const_eval_limit +//! * move_size_limit, and +//! * type_length_limit //! //! There are various parts of the compiler that must impose arbitrary limits //! on how deeply they recurse to prevent stack overflow. Users can override @@ -34,12 +33,6 @@ pub fn provide(providers: &mut Providers) { sym::type_length_limit, 1048576, ), - const_eval_limit: get_limit( - tcx.hir().krate_attrs(), - tcx.sess, - sym::const_eval_limit, - 2_000_000, - ), } } diff --git a/compiler/rustc_middle/src/middle/privacy.rs b/compiler/rustc_middle/src/middle/privacy.rs index f45cf788dd91c..1913421f54c93 100644 --- a/compiler/rustc_middle/src/middle/privacy.rs +++ b/compiler/rustc_middle/src/middle/privacy.rs @@ -4,6 +4,7 @@ use crate::ty::{TyCtxt, Visibility}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_hir::def::DefKind; use rustc_macros::HashStable; use rustc_query_system::ich::StableHashingContext; use rustc_span::def_id::{LocalDefId, CRATE_DEF_ID}; @@ -148,13 +149,12 @@ impl EffectiveVisibilities { }; } - pub fn check_invariants(&self, tcx: TyCtxt<'_>, early: bool) { + pub fn check_invariants(&self, tcx: TyCtxt<'_>) { if !cfg!(debug_assertions) { return; } for (&def_id, ev) in &self.map { // More direct visibility levels can never go farther than less direct ones, - // neither of effective visibilities can go farther than nominal visibility, // and all effective visibilities are larger or equal than private visibility. let private_vis = Visibility::Restricted(tcx.parent_module_from_def_id(def_id)); let span = tcx.def_span(def_id.to_def_id()); @@ -175,17 +175,25 @@ impl EffectiveVisibilities { ev.reachable_through_impl_trait ); } - let nominal_vis = tcx.visibility(def_id); - // FIXME: `rustc_privacy` is not yet updated for the new logic and can set - // effective visibilities that are larger than the nominal one. - if !nominal_vis.is_at_least(ev.reachable_through_impl_trait, tcx) && early { - span_bug!( - span, - "{:?}: reachable_through_impl_trait {:?} > nominal {:?}", - def_id, - ev.reachable_through_impl_trait, - nominal_vis - ); + // All effective visibilities except `reachable_through_impl_trait` are limited to + // nominal visibility. For some items nominal visibility doesn't make sense so we + // don't check this condition for them. + let is_impl = matches!(tcx.def_kind(def_id), DefKind::Impl { .. }); + let is_associated_item_in_trait_impl = tcx + .impl_of_method(def_id.to_def_id()) + .and_then(|impl_id| tcx.trait_id_of_impl(impl_id)) + .is_some(); + if !is_impl && !is_associated_item_in_trait_impl { + let nominal_vis = tcx.visibility(def_id); + if !nominal_vis.is_at_least(ev.reachable, tcx) { + span_bug!( + span, + "{:?}: reachable {:?} > nominal {:?}", + def_id, + ev.reachable, + nominal_vis, + ); + } } } } @@ -212,7 +220,7 @@ impl EffectiveVisibilities { pub fn update( &mut self, id: Id, - nominal_vis: Option, + max_vis: Option, lazy_private_vis: impl FnOnce() -> Visibility, inherited_effective_vis: EffectiveVisibility, level: Level, @@ -236,8 +244,8 @@ impl EffectiveVisibilities { if !(inherited_effective_vis_at_prev_level == inherited_effective_vis_at_level && level != l) { - calculated_effective_vis = if let Some(nominal_vis) = nominal_vis && !nominal_vis.is_at_least(inherited_effective_vis_at_level, tcx) { - nominal_vis + calculated_effective_vis = if let Some(max_vis) = max_vis && !max_vis.is_at_least(inherited_effective_vis_at_level, tcx) { + max_vis } else { inherited_effective_vis_at_level } diff --git a/compiler/rustc_middle/src/middle/region.rs b/compiler/rustc_middle/src/middle/region.rs index 10712e1468679..c50c5e6f701cd 100644 --- a/compiler/rustc_middle/src/middle/region.rs +++ b/compiler/rustc_middle/src/middle/region.rs @@ -10,7 +10,7 @@ use crate::ty::TyCtxt; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_hir as hir; -use rustc_hir::Node; +use rustc_hir::{HirIdMap, Node}; use rustc_macros::HashStable; use rustc_query_system::ich::StableHashingContext; use rustc_span::{Span, DUMMY_SP}; @@ -228,7 +228,7 @@ pub struct ScopeTree { /// and not the enclosing *statement*. Expressions that are not present in this /// table are not rvalue candidates. The set of rvalue candidates is computed /// during type check based on a traversal of the AST. - pub rvalue_candidates: FxHashMap, + pub rvalue_candidates: HirIdMap, /// If there are any `yield` nested within a scope, this map /// stores the `Span` of the last one and its index in the diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index 6354c0aabde7c..908ab8b613e88 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -104,15 +104,15 @@ pub fn report_unstable( suggestion: Option<(Span, String, String, Applicability)>, is_soft: bool, span: Span, - soft_handler: impl FnOnce(&'static Lint, Span, &str), + soft_handler: impl FnOnce(&'static Lint, Span, String), ) { let msg = match reason { - Some(r) => format!("use of unstable library feature '{}': {}", feature, r), + Some(r) => format!("use of unstable library feature '{feature}': {r}"), None => format!("use of unstable library feature '{}'", &feature), }; if is_soft { - soft_handler(SOFT_UNSTABLE, span, &msg) + soft_handler(SOFT_UNSTABLE, span, msg) } else { let mut err = feature_err_issue(&sess.parse_sess, feature, span, GateIssue::Library(issue), msg); @@ -170,7 +170,7 @@ pub fn deprecation_suggestion( if let Some(suggestion) = suggestion { diag.span_suggestion_verbose( span, - format!("replace the use of the deprecated {}", kind), + format!("replace the use of the deprecated {kind}"), suggestion, Applicability::MachineApplicable, ); @@ -189,12 +189,12 @@ fn deprecation_message( path: &str, ) -> String { let message = if is_in_effect { - format!("use of deprecated {} `{}`", kind, path) + format!("use of deprecated {kind} `{path}`") } else { let since = since.as_ref().map(Symbol::as_str); if since == Some("TBD") { - format!("use of {} `{}` that will be deprecated in a future Rust version", kind, path) + format!("use of {kind} `{path}` that will be deprecated in a future Rust version") } else { format!( "use of {} `{}` that will be deprecated in future version {}", @@ -206,7 +206,7 @@ fn deprecation_message( }; match note { - Some(reason) => format!("{}: {}", message, reason), + Some(reason) => format!("{message}: {reason}"), None => message, } } @@ -225,7 +225,7 @@ pub fn deprecation_message_and_lint( pub fn early_report_deprecation( lint_buffer: &mut LintBuffer, - message: &str, + message: String, suggestion: Option, lint: &'static Lint, span: Span, @@ -241,7 +241,7 @@ pub fn early_report_deprecation( fn late_report_deprecation( tcx: TyCtxt<'_>, - message: &str, + message: String, suggestion: Option, lint: &'static Lint, span: Span, @@ -312,7 +312,7 @@ fn suggestion_for_allocator_api( return Some(( inner_types, "consider wrapping the inner types in tuple".to_string(), - format!("({})", snippet), + format!("({snippet})"), Applicability::MaybeIncorrect, )); } @@ -396,7 +396,7 @@ impl<'tcx> TyCtxt<'tcx> { late_report_deprecation( self, - &deprecation_message( + deprecation_message( is_in_effect, depr_attr.since, depr_attr.note, @@ -599,7 +599,7 @@ impl<'tcx> TyCtxt<'tcx> { |span, def_id| { // The API could be uncallable for other reasons, for example when a private module // was referenced. - self.sess.delay_span_bug(span, format!("encountered unmarked API: {:?}", def_id)); + self.sess.delay_span_bug(span, format!("encountered unmarked API: {def_id:?}")); }, ) } @@ -619,7 +619,7 @@ impl<'tcx> TyCtxt<'tcx> { allow_unstable: AllowUnstable, unmarked: impl FnOnce(Span, DefId), ) -> bool { - let soft_handler = |lint, span, msg: &_| { + let soft_handler = |lint, span, msg: String| { self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, msg, |lint| lint) }; let eval_result = diff --git a/compiler/rustc_middle/src/mir/basic_blocks.rs b/compiler/rustc_middle/src/mir/basic_blocks.rs index 9d70dbfa07221..0ad17e819c744 100644 --- a/compiler/rustc_middle/src/mir/basic_blocks.rs +++ b/compiler/rustc_middle/src/mir/basic_blocks.rs @@ -26,7 +26,7 @@ struct Cache { predecessors: OnceCell, switch_sources: OnceCell, is_cyclic: OnceCell, - postorder: OnceCell>, + reverse_postorder: OnceCell>, dominators: OnceCell>, } @@ -62,11 +62,14 @@ impl<'tcx> BasicBlocks<'tcx> { }) } - /// Returns basic blocks in a postorder. + /// Returns basic blocks in a reverse postorder. #[inline] - pub fn postorder(&self) -> &[BasicBlock] { - self.cache.postorder.get_or_init(|| { - Postorder::new(&self.basic_blocks, START_BLOCK).map(|(bb, _)| bb).collect() + pub fn reverse_postorder(&self) -> &[BasicBlock] { + self.cache.reverse_postorder.get_or_init(|| { + let mut rpo: Vec<_> = + Postorder::new(&self.basic_blocks, START_BLOCK).map(|(bb, _)| bb).collect(); + rpo.reverse(); + rpo }) } @@ -175,9 +178,7 @@ impl<'tcx> graph::WithPredecessors for BasicBlocks<'tcx> { } } -TrivialTypeTraversalAndLiftImpls! { - Cache, -} +TrivialTypeTraversalAndLiftImpls! { Cache } impl Encodable for Cache { #[inline] diff --git a/compiler/rustc_middle/src/mir/coverage.rs b/compiler/rustc_middle/src/mir/coverage.rs index db24dae11304f..1efb54bdb0872 100644 --- a/compiler/rustc_middle/src/mir/coverage.rs +++ b/compiler/rustc_middle/src/mir/coverage.rs @@ -6,69 +6,43 @@ use rustc_span::Symbol; use std::fmt::{self, Debug, Formatter}; rustc_index::newtype_index! { - /// An ExpressionOperandId value is assigned directly from either a - /// CounterValueReference.as_u32() (which ascend from 1) or an ExpressionOperandId.as_u32() - /// (which _*descend*_ from u32::MAX). Id value `0` (zero) represents a virtual counter with a - /// constant value of `0`. - #[derive(HashStable)] - #[max = 0xFFFF_FFFF] - #[debug_format = "ExpressionOperandId({})"] - pub struct ExpressionOperandId { - } -} - -impl ExpressionOperandId { - /// An expression operand for a "zero counter", as described in the following references: - /// - /// * - /// * - /// * + /// ID of a coverage counter. Values ascend from 0. /// - /// This operand can be used to count two or more separate code regions with a single counter, - /// if they run sequentially with no branches, by injecting the `Counter` in a `BasicBlock` for - /// one of the code regions, and inserting `CounterExpression`s ("add ZERO to the counter") in - /// the coverage map for the other code regions. - pub const ZERO: Self = Self::from_u32(0); -} - -rustc_index::newtype_index! { + /// Note that LLVM handles counter IDs as `uint32_t`, so there is no need + /// to use a larger representation on the Rust side. #[derive(HashStable)] #[max = 0xFFFF_FFFF] - #[debug_format = "CounterValueReference({})"] - pub struct CounterValueReference {} + #[debug_format = "CounterId({})"] + pub struct CounterId {} } -impl CounterValueReference { - /// Counters start at 1 to reserve 0 for ExpressionOperandId::ZERO. - pub const START: Self = Self::from_u32(1); +impl CounterId { + pub const START: Self = Self::from_u32(0); - /// Returns explicitly-requested zero-based version of the counter id, used - /// during codegen. LLVM expects zero-based indexes. - pub fn zero_based_index(self) -> u32 { - let one_based_index = self.as_u32(); - debug_assert!(one_based_index > 0); - one_based_index - 1 + #[inline(always)] + pub fn next_id(self) -> Self { + Self::from_u32(self.as_u32() + 1) } } rustc_index::newtype_index! { - /// InjectedExpressionId.as_u32() converts to ExpressionOperandId.as_u32() + /// ID of a coverage-counter expression. Values ascend from 0. /// - /// Values descend from u32::MAX. + /// Note that LLVM handles expression IDs as `uint32_t`, so there is no need + /// to use a larger representation on the Rust side. #[derive(HashStable)] #[max = 0xFFFF_FFFF] - #[debug_format = "InjectedExpressionId({})"] - pub struct InjectedExpressionId {} + #[debug_format = "ExpressionId({})"] + pub struct ExpressionId {} } -rustc_index::newtype_index! { - /// InjectedExpressionIndex.as_u32() translates to u32::MAX - ExpressionOperandId.as_u32() - /// - /// Values ascend from 0. - #[derive(HashStable)] - #[max = 0xFFFF_FFFF] - #[debug_format = "InjectedExpressionIndex({})"] - pub struct InjectedExpressionIndex {} +impl ExpressionId { + pub const START: Self = Self::from_u32(0); + + #[inline(always)] + pub fn next_id(self) -> Self { + Self::from_u32(self.as_u32() + 1) + } } rustc_index::newtype_index! { @@ -81,17 +55,25 @@ rustc_index::newtype_index! { pub struct MappedExpressionIndex {} } -impl From for ExpressionOperandId { - #[inline] - fn from(v: CounterValueReference) -> ExpressionOperandId { - ExpressionOperandId::from(v.as_u32()) - } +/// Operand of a coverage-counter expression. +/// +/// Operands can be a constant zero value, an actual coverage counter, or another +/// expression. Counter/expression operands are referred to by ID. +#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(TyEncodable, TyDecodable, Hash, HashStable, TypeFoldable, TypeVisitable)] +pub enum Operand { + Zero, + Counter(CounterId), + Expression(ExpressionId), } -impl From for ExpressionOperandId { - #[inline] - fn from(v: InjectedExpressionId) -> ExpressionOperandId { - ExpressionOperandId::from(v.as_u32()) +impl Debug for Operand { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Zero => write!(f, "Zero"), + Self::Counter(id) => f.debug_tuple("Counter").field(&id.as_u32()).finish(), + Self::Expression(id) => f.debug_tuple("Expression").field(&id.as_u32()).finish(), + } } } @@ -99,32 +81,21 @@ impl From for ExpressionOperandId { pub enum CoverageKind { Counter { function_source_hash: u64, - id: CounterValueReference, + /// ID of this counter within its enclosing function. + /// Expressions in the same function can refer to it as an operand. + id: CounterId, }, Expression { - id: InjectedExpressionId, - lhs: ExpressionOperandId, + /// ID of this coverage-counter expression within its enclosing function. + /// Other expressions in the same function can refer to it as an operand. + id: ExpressionId, + lhs: Operand, op: Op, - rhs: ExpressionOperandId, + rhs: Operand, }, Unreachable, } -impl CoverageKind { - pub fn as_operand_id(&self) -> ExpressionOperandId { - use CoverageKind::*; - match *self { - Counter { id, .. } => ExpressionOperandId::from(id), - Expression { id, .. } => ExpressionOperandId::from(id), - Unreachable => bug!("Unreachable coverage cannot be part of an expression"), - } - } - - pub fn is_expression(&self) -> bool { - matches!(self, Self::Expression { .. }) - } -} - impl Debug for CoverageKind { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { use CoverageKind::*; @@ -132,14 +103,14 @@ impl Debug for CoverageKind { Counter { id, .. } => write!(fmt, "Counter({:?})", id.index()), Expression { id, lhs, op, rhs } => write!( fmt, - "Expression({:?}) = {} {} {}", + "Expression({:?}) = {:?} {} {:?}", id.index(), - lhs.index(), + lhs, match op { Op::Add => "+", Op::Subtract => "-", }, - rhs.index(), + rhs, ), Unreachable => write!(fmt, "Unreachable"), } diff --git a/compiler/rustc_middle/src/mir/generic_graph.rs b/compiler/rustc_middle/src/mir/generic_graph.rs index d1f3561c02c5d..d1753427e740b 100644 --- a/compiler/rustc_middle/src/mir/generic_graph.rs +++ b/compiler/rustc_middle/src/mir/generic_graph.rs @@ -7,7 +7,7 @@ use rustc_middle::ty::TyCtxt; pub fn mir_fn_to_generic_graph<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Graph { let def_id = body.source.def_id(); let def_name = graphviz_safe_def_name(def_id); - let graph_name = format!("Mir_{}", def_name); + let graph_name = format!("Mir_{def_name}"); let dark_mode = tcx.sess.opts.unstable_opts.graphviz_dark_mode; // Nodes @@ -48,7 +48,7 @@ fn bb_to_graph_node(block: BasicBlock, body: &Body<'_>, dark_mode: bool) -> Node }; let style = NodeStyle { title_bg: Some(bgcolor.to_owned()), ..Default::default() }; - let mut stmts: Vec = data.statements.iter().map(|x| format!("{:?}", x)).collect(); + let mut stmts: Vec = data.statements.iter().map(|x| format!("{x:?}")).collect(); // add the terminator to the stmts, gsgdt can print it out separately let mut terminator_head = String::new(); diff --git a/compiler/rustc_middle/src/mir/generic_graphviz.rs b/compiler/rustc_middle/src/mir/generic_graphviz.rs index ccae7e159b1f1..299b50525cb1e 100644 --- a/compiler/rustc_middle/src/mir/generic_graphviz.rs +++ b/compiler/rustc_middle/src/mir/generic_graphviz.rs @@ -70,8 +70,8 @@ impl< writeln!(w, r#" graph [{}];"#, graph_attrs.join(" "))?; let content_attrs_str = content_attrs.join(" "); - writeln!(w, r#" node [{}];"#, content_attrs_str)?; - writeln!(w, r#" edge [{}];"#, content_attrs_str)?; + writeln!(w, r#" node [{content_attrs_str}];"#)?; + writeln!(w, r#" edge [{content_attrs_str}];"#)?; // Graph label if let Some(graph_label) = &self.graph_label { @@ -112,7 +112,7 @@ impl< // (format!("{:?}", node), color) // }; let color = if dark_mode { "dimgray" } else { "gray" }; - let (blk, bgcolor) = (format!("{:?}", node), color); + let (blk, bgcolor) = (format!("{node:?}"), color); write!( w, r#"{blk}"#, @@ -151,7 +151,7 @@ impl< } else { "".to_owned() }; - writeln!(w, r#" {} -> {} [label=<{}>];"#, src, trg, escaped_edge_label)?; + writeln!(w, r#" {src} -> {trg} [label=<{escaped_edge_label}>];"#)?; } Ok(()) } @@ -163,7 +163,7 @@ impl< W: Write, { let escaped_label = dot::escape_html(label); - writeln!(w, r#" label=<

{}



>;"#, escaped_label) + writeln!(w, r#" label=<

{escaped_label}



>;"#) } fn node(&self, node: G::Node) -> String { diff --git a/compiler/rustc_middle/src/mir/graphviz.rs b/compiler/rustc_middle/src/mir/graphviz.rs index 2de73db3a3cfb..5c7de86443008 100644 --- a/compiler/rustc_middle/src/mir/graphviz.rs +++ b/compiler/rustc_middle/src/mir/graphviz.rs @@ -127,5 +127,5 @@ fn write_graph_label<'tcx, W: std::fmt::Write>( } fn escape(t: &T) -> String { - dot::escape_html(&format!("{:?}", t)) + dot::escape_html(&format!("{t:?}")) } diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs index 1a8e48264471c..c787481bfbec7 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs @@ -18,9 +18,9 @@ use rustc_span::DUMMY_SP; use rustc_target::abi::{Align, HasDataLayout, Size}; use super::{ - read_target_uint, write_target_uint, AllocId, InterpError, InterpResult, Pointer, Provenance, - ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, UndefinedBehaviorInfo, UninitBytesAccess, - UnsupportedOpInfo, + read_target_uint, write_target_uint, AllocId, BadBytesAccess, InterpError, InterpResult, + Pointer, PointerArithmetic, Provenance, ResourceExhaustionInfo, Scalar, ScalarSizeMismatch, + UndefinedBehaviorInfo, UnsupportedOpInfo, }; use crate::ty; use init_mask::*; @@ -173,13 +173,13 @@ pub enum AllocError { /// A scalar had the wrong size. ScalarSizeMismatch(ScalarSizeMismatch), /// Encountered a pointer where we needed raw bytes. - ReadPointerAsBytes, + ReadPointerAsInt(Option), /// Partially overwriting a pointer. - PartialPointerOverwrite(Size), + OverwritePartialPointer(Size), /// Partially copying a pointer. - PartialPointerCopy(Size), + ReadPartialPointer(Size), /// Using uninitialized data where it is not allowed. - InvalidUninitBytes(Option), + InvalidUninitBytes(Option), } pub type AllocResult = Result; @@ -196,12 +196,14 @@ impl AllocError { ScalarSizeMismatch(s) => { InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ScalarSizeMismatch(s)) } - ReadPointerAsBytes => InterpError::Unsupported(UnsupportedOpInfo::ReadPointerAsBytes), - PartialPointerOverwrite(offset) => InterpError::Unsupported( - UnsupportedOpInfo::PartialPointerOverwrite(Pointer::new(alloc_id, offset)), + ReadPointerAsInt(info) => InterpError::Unsupported( + UnsupportedOpInfo::ReadPointerAsInt(info.map(|b| (alloc_id, b))), ), - PartialPointerCopy(offset) => InterpError::Unsupported( - UnsupportedOpInfo::PartialPointerCopy(Pointer::new(alloc_id, offset)), + OverwritePartialPointer(offset) => InterpError::Unsupported( + UnsupportedOpInfo::OverwritePartialPointer(Pointer::new(alloc_id, offset)), + ), + ReadPartialPointer(offset) => InterpError::Unsupported( + UnsupportedOpInfo::ReadPartialPointer(Pointer::new(alloc_id, offset)), ), InvalidUninitBytes(info) => InterpError::UndefinedBehavior( UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))), @@ -296,25 +298,13 @@ impl Allocation { Allocation::from_bytes(slice, Align::ONE, Mutability::Not) } - /// Try to create an Allocation of `size` bytes, failing if there is not enough memory - /// available to the compiler to do so. - /// - /// If `panic_on_fail` is true, this will never return `Err`. - pub fn uninit<'tcx>(size: Size, align: Align, panic_on_fail: bool) -> InterpResult<'tcx, Self> { - let bytes = Bytes::zeroed(size, align).ok_or_else(|| { - // This results in an error that can happen non-deterministically, since the memory - // available to the compiler can change between runs. Normally queries are always - // deterministic. However, we can be non-deterministic here because all uses of const - // evaluation (including ConstProp!) will make compilation fail (via hard error - // or ICE) upon encountering a `MemoryExhausted` error. - if panic_on_fail { - panic!("Allocation::uninit called with panic_on_fail had allocation failure") - } - ty::tls::with(|tcx| { - tcx.sess.delay_span_bug(DUMMY_SP, "exhausted memory during interpretation") - }); - InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted) - })?; + fn uninit_inner(size: Size, align: Align, fail: impl FnOnce() -> R) -> Result { + // This results in an error that can happen non-deterministically, since the memory + // available to the compiler can change between runs. Normally queries are always + // deterministic. However, we can be non-deterministic here because all uses of const + // evaluation (including ConstProp!) will make compilation fail (via hard error + // or ICE) upon encountering a `MemoryExhausted` error. + let bytes = Bytes::zeroed(size, align).ok_or_else(fail)?; Ok(Allocation { bytes, @@ -325,6 +315,31 @@ impl Allocation { extra: (), }) } + + /// Try to create an Allocation of `size` bytes, failing if there is not enough memory + /// available to the compiler to do so. + pub fn try_uninit<'tcx>(size: Size, align: Align) -> InterpResult<'tcx, Self> { + Self::uninit_inner(size, align, || { + ty::tls::with(|tcx| { + tcx.sess.delay_span_bug(DUMMY_SP, "exhausted memory during interpretation") + }); + InterpError::ResourceExhaustion(ResourceExhaustionInfo::MemoryExhausted).into() + }) + } + + /// Try to create an Allocation of `size` bytes, panics if there is not enough memory + /// available to the compiler to do so. + /// + /// Example use case: To obtain an Allocation filled with specific data, + /// first call this function and then call write_scalar to fill in the right data. + pub fn uninit(size: Size, align: Align) -> Self { + match Self::uninit_inner(size, align, || { + panic!("Allocation::uninit called with panic_on_fail had allocation failure"); + }) { + Ok(x) => x, + Err(x) => x, + } + } } impl Allocation { @@ -423,14 +438,26 @@ impl Allocation range: AllocRange, ) -> AllocResult<&[u8]> { self.init_mask.is_range_initialized(range).map_err(|uninit_range| { - AllocError::InvalidUninitBytes(Some(UninitBytesAccess { + AllocError::InvalidUninitBytes(Some(BadBytesAccess { access: range, - uninit: uninit_range, + bad: uninit_range, })) })?; if !Prov::OFFSET_IS_ADDR { if !self.provenance.range_empty(range, cx) { - return Err(AllocError::ReadPointerAsBytes); + // Find the provenance. + let (offset, _prov) = self + .provenance + .range_get_ptrs(range, cx) + .first() + .copied() + .expect("there must be provenance somewhere here"); + let start = offset.max(range.start); // the pointer might begin before `range`! + let end = (offset + cx.pointer_size()).min(range.end()); // the pointer might end after `range`! + return Err(AllocError::ReadPointerAsInt(Some(BadBytesAccess { + access: range, + bad: AllocRange::from(start..end), + }))); } } Ok(self.get_bytes_unchecked(range)) @@ -526,23 +553,25 @@ impl Allocation // Now use this provenance. let ptr = Pointer::new(prov, Size::from_bytes(bits)); return Ok(Scalar::from_maybe_pointer(ptr, cx)); + } else { + // Without OFFSET_IS_ADDR, the only remaining case we can handle is total absence of + // provenance. + if self.provenance.range_empty(range, cx) { + return Ok(Scalar::from_uint(bits, range.size)); + } + // Else we have mixed provenance, that doesn't work. + return Err(AllocError::ReadPartialPointer(range.start)); } } else { // We are *not* reading a pointer. - // If we can just ignore provenance, do exactly that. - if Prov::OFFSET_IS_ADDR { + // If we can just ignore provenance or there is none, that's easy. + if Prov::OFFSET_IS_ADDR || self.provenance.range_empty(range, cx) { // We just strip provenance. return Ok(Scalar::from_uint(bits, range.size)); } + // There is some provenance and we don't have OFFSET_IS_ADDR. This doesn't work. + return Err(AllocError::ReadPointerAsInt(None)); } - - // Fallback path for when we cannot treat provenance bytewise or ignore it. - assert!(!Prov::OFFSET_IS_ADDR); - if !self.provenance.range_empty(range, cx) { - return Err(AllocError::ReadPointerAsBytes); - } - // There is no provenance, we can just return the bits. - Ok(Scalar::from_uint(bits, range.size)) } /// Writes a *non-ZST* scalar. @@ -561,7 +590,7 @@ impl Allocation assert!(self.mutability == Mutability::Mut); // `to_bits_or_ptr_internal` is the right method because we just want to store this data - // as-is into memory. + // as-is into memory. This also double-checks that `val.size()` matches `range.size`. let (bytes, provenance) = match val.to_bits_or_ptr_internal(range.size)? { Right(ptr) => { let (provenance, offset) = ptr.into_parts(); diff --git a/compiler/rustc_middle/src/mir/interpret/allocation/init_mask.rs b/compiler/rustc_middle/src/mir/interpret/allocation/init_mask.rs index d4dd56a42c1ed..2c6bb908f39f6 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation/init_mask.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation/init_mask.rs @@ -542,11 +542,7 @@ impl InitMaskMaterialized { debug_assert_eq!( result, find_bit_slow(self, start, end, is_init), - "optimized implementation of find_bit is wrong for start={:?} end={:?} is_init={} init_mask={:#?}", - start, - end, - is_init, - self + "optimized implementation of find_bit is wrong for start={start:?} end={end:?} is_init={is_init} init_mask={self:#?}" ); result diff --git a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs index 318f93e12b582..0243fc4513aa6 100644 --- a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs +++ b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs @@ -66,7 +66,11 @@ impl ProvenanceMap { /// Returns all ptr-sized provenance in the given range. /// If the range has length 0, returns provenance that crosses the edge between `start-1` and /// `start`. - fn range_get_ptrs(&self, range: AllocRange, cx: &impl HasDataLayout) -> &[(Size, Prov)] { + pub(super) fn range_get_ptrs( + &self, + range: AllocRange, + cx: &impl HasDataLayout, + ) -> &[(Size, Prov)] { // We have to go back `pointer_size - 1` bytes, as that one would still overlap with // the beginning of this range. let adjusted_start = Size::from_bytes( @@ -158,7 +162,7 @@ impl ProvenanceMap { if first < start { if !Prov::OFFSET_IS_ADDR { // We can't split up the provenance into less than a pointer. - return Err(AllocError::PartialPointerOverwrite(first)); + return Err(AllocError::OverwritePartialPointer(first)); } // Insert the remaining part in the bytewise provenance. let prov = self.ptrs[&first]; @@ -171,7 +175,7 @@ impl ProvenanceMap { let begin_of_last = last - cx.data_layout().pointer_size; if !Prov::OFFSET_IS_ADDR { // We can't split up the provenance into less than a pointer. - return Err(AllocError::PartialPointerOverwrite(begin_of_last)); + return Err(AllocError::OverwritePartialPointer(begin_of_last)); } // Insert the remaining part in the bytewise provenance. let prov = self.ptrs[&begin_of_last]; @@ -246,10 +250,10 @@ impl ProvenanceMap { if !Prov::OFFSET_IS_ADDR { // There can't be any bytewise provenance, and we cannot split up the begin/end overlap. if let Some(entry) = begin_overlap { - return Err(AllocError::PartialPointerCopy(entry.0)); + return Err(AllocError::ReadPartialPointer(entry.0)); } if let Some(entry) = end_overlap { - return Err(AllocError::PartialPointerCopy(entry.0)); + return Err(AllocError::ReadPartialPointer(entry.0)); } debug_assert!(self.bytes.is_none()); } else { diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 055d8e9a352bc..e6ef5a41ee0c6 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -5,11 +5,16 @@ use crate::query::TyCtxtAt; use crate::ty::{layout, tls, Ty, ValTree}; use rustc_data_structures::sync::Lock; -use rustc_errors::{pluralize, struct_span_err, DiagnosticBuilder, ErrorGuaranteed}; +use rustc_errors::{ + struct_span_err, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed, + IntoDiagnosticArg, +}; use rustc_macros::HashStable; use rustc_session::CtfeBacktrace; use rustc_span::def_id::DefId; -use rustc_target::abi::{call, Align, Size}; +use rustc_target::abi::{call, Align, Size, VariantIdx, WrappingRange}; + +use std::borrow::Cow; use std::{any::Any, backtrace::Backtrace, fmt}; #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)] @@ -18,7 +23,7 @@ pub enum ErrorHandled { /// *guaranteed* to fail. Warnings/lints *must not* produce `Reported`. Reported(ReportedErrorInfo), /// Don't emit an error, the evaluation failed because the MIR was generic - /// and the substs didn't fully monomorphize it. + /// and the args didn't fully monomorphize it. TooGeneric, } @@ -62,9 +67,7 @@ impl Into for ReportedErrorInfo { } } -TrivialTypeTraversalAndLiftImpls! { - ErrorHandled, -} +TrivialTypeTraversalAndLiftImpls! { ErrorHandled } pub type EvalToAllocationRawResult<'tcx> = Result, ErrorHandled>; pub type EvalToConstValueResult<'tcx> = Result, ErrorHandled>; @@ -91,21 +94,50 @@ pub struct InterpErrorInfo<'tcx>(Box>); #[derive(Debug)] struct InterpErrorInfoInner<'tcx> { kind: InterpError<'tcx>, + backtrace: InterpErrorBacktrace, +} + +#[derive(Debug)] +pub struct InterpErrorBacktrace { backtrace: Option>, } -impl fmt::Display for InterpErrorInfo<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.kind) +impl InterpErrorBacktrace { + pub fn new() -> InterpErrorBacktrace { + let capture_backtrace = tls::with_opt(|tcx| { + if let Some(tcx) = tcx { + *Lock::borrow(&tcx.sess.ctfe_backtrace) + } else { + CtfeBacktrace::Disabled + } + }); + + let backtrace = match capture_backtrace { + CtfeBacktrace::Disabled => None, + CtfeBacktrace::Capture => Some(Box::new(Backtrace::force_capture())), + CtfeBacktrace::Immediate => { + // Print it now. + let backtrace = Backtrace::force_capture(); + print_backtrace(&backtrace); + None + } + }; + + InterpErrorBacktrace { backtrace } } -} -impl<'tcx> InterpErrorInfo<'tcx> { pub fn print_backtrace(&self) { - if let Some(backtrace) = self.0.backtrace.as_ref() { + if let Some(backtrace) = self.backtrace.as_ref() { print_backtrace(backtrace); } } +} + +impl<'tcx> InterpErrorInfo<'tcx> { + pub fn into_parts(self) -> (InterpError<'tcx>, InterpErrorBacktrace) { + let InterpErrorInfo(box InterpErrorInfoInner { kind, backtrace }) = self; + (kind, backtrace) + } pub fn into_kind(self) -> InterpError<'tcx> { let InterpErrorInfo(box InterpErrorInfoInner { kind, .. }) = self; @@ -119,7 +151,7 @@ impl<'tcx> InterpErrorInfo<'tcx> { } fn print_backtrace(backtrace: &Backtrace) { - eprintln!("\n\nAn error occurred in miri:\n{}", backtrace); + eprintln!("\n\nAn error occurred in the MIR interpreter:\n{backtrace}"); } impl From for InterpErrorInfo<'_> { @@ -130,32 +162,17 @@ impl From for InterpErrorInfo<'_> { impl<'tcx> From> for InterpErrorInfo<'tcx> { fn from(kind: InterpError<'tcx>) -> Self { - let capture_backtrace = tls::with_opt(|tcx| { - if let Some(tcx) = tcx { - *Lock::borrow(&tcx.sess.ctfe_backtrace) - } else { - CtfeBacktrace::Disabled - } - }); - - let backtrace = match capture_backtrace { - CtfeBacktrace::Disabled => None, - CtfeBacktrace::Capture => Some(Box::new(Backtrace::force_capture())), - CtfeBacktrace::Immediate => { - // Print it now. - let backtrace = Backtrace::force_capture(); - print_backtrace(&backtrace); - None - } - }; - - InterpErrorInfo(Box::new(InterpErrorInfoInner { kind, backtrace })) + InterpErrorInfo(Box::new(InterpErrorInfoInner { + kind, + backtrace: InterpErrorBacktrace::new(), + })) } } /// Error information for when the program we executed turned out not to actually be a valid /// program. This cannot happen in stand-alone Miri, but it can happen during CTFE/ConstProp /// where we work on generic code or execution does not have all information available. +#[derive(Debug)] pub enum InvalidProgramInfo<'tcx> { /// Resolution can fail if we are in a too generic context. TooGeneric, @@ -167,30 +184,8 @@ pub enum InvalidProgramInfo<'tcx> { /// (which unfortunately typeck does not reject). /// Not using `FnAbiError` as that contains a nested `LayoutError`. FnAbiAdjustForForeignAbi(call::AdjustForForeignAbiError), - /// SizeOf of unsized type was requested. - SizeOfUnsizedType(Ty<'tcx>), - /// An unsized local was accessed without having been initialized. - /// This is not meaningful as we can't even have backing memory for such locals. - UninitUnsizedLocal, -} - -impl fmt::Display for InvalidProgramInfo<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use InvalidProgramInfo::*; - match self { - TooGeneric => write!(f, "encountered overly generic constant"), - AlreadyReported(_) => { - write!( - f, - "an error has already been reported elsewhere (this should not usually be printed)" - ) - } - Layout(ref err) => write!(f, "{err}"), - FnAbiAdjustForForeignAbi(ref err) => write!(f, "{err}"), - SizeOfUnsizedType(ty) => write!(f, "size_of called on unsized type `{ty}`"), - UninitUnsizedLocal => write!(f, "unsized local is used while uninitialized"), - } - } + /// We are runnning into a nonsense situation due to ConstProp violating our invariants. + ConstPropNonsense, } /// Details of why a pointer had to be in-bounds. @@ -208,31 +203,30 @@ pub enum CheckInAllocMsg { InboundsTest, } -impl fmt::Display for CheckInAllocMsg { - /// When this is printed as an error the context looks like this: - /// "{msg}{pointer} is a dangling pointer". - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - match *self { - CheckInAllocMsg::DerefTest => "dereferencing pointer failed: ", - CheckInAllocMsg::MemoryAccessTest => "memory access failed: ", - CheckInAllocMsg::PointerArithmeticTest => "out-of-bounds pointer arithmetic: ", - CheckInAllocMsg::OffsetFromTest => "out-of-bounds offset_from: ", - CheckInAllocMsg::InboundsTest => "out-of-bounds pointer use: ", - } - ) +#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)] +pub enum InvalidMetaKind { + /// Size of a `[T]` is too big + SliceTooBig, + /// Size of a DST is too big + TooBig, +} + +impl IntoDiagnosticArg for InvalidMetaKind { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Borrowed(match self { + InvalidMetaKind::SliceTooBig => "slice_too_big", + InvalidMetaKind::TooBig => "too_big", + })) } } -/// Details of an access to uninitialized bytes where it is not allowed. -#[derive(Debug)] -pub struct UninitBytesAccess { +/// Details of an access to uninitialized bytes / bad pointer bytes where it is not allowed. +#[derive(Debug, Clone, Copy)] +pub struct BadBytesAccess { /// Range of the original memory access. pub access: AllocRange, - /// Range of the uninit memory that was encountered. (Might not be maximal.) - pub uninit: AllocRange, + /// Range of the bad memory that was encountered. (Might not be maximal.) + pub bad: AllocRange, } /// Information about a size mismatch. @@ -242,17 +236,32 @@ pub struct ScalarSizeMismatch { pub data_size: u64, } +macro_rules! impl_into_diagnostic_arg_through_debug { + ($($ty:ty),*$(,)?) => {$( + impl IntoDiagnosticArg for $ty { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Owned(format!("{self:?}"))) + } + } + )*} +} + +// These types have nice `Debug` output so we can just use them in diagnostics. +impl_into_diagnostic_arg_through_debug! { + AllocId, + Pointer, + AllocRange, +} + /// Error information for when the program caused Undefined Behavior. -pub enum UndefinedBehaviorInfo { - /// Free-form case. Only for errors that are never caught! +#[derive(Debug)] +pub enum UndefinedBehaviorInfo<'a> { + /// Free-form case. Only for errors that are never caught! Used by miri Ub(String), /// Unreachable code was executed. Unreachable, /// A slice/array index projection went out-of-bounds. - BoundsCheckFailed { - len: u64, - index: u64, - }, + BoundsCheckFailed { len: u64, index: u64 }, /// Something was divided by 0 (x / 0). DivisionByZero, /// Something was "remainded" by 0 (x % 0). @@ -263,12 +272,12 @@ pub enum UndefinedBehaviorInfo { RemainderOverflow, /// Overflowing inbounds pointer arithmetic. PointerArithOverflow, - /// Invalid metadata in a wide pointer (using `str` to avoid allocations). - InvalidMeta(&'static str), + /// Invalid metadata in a wide pointer + InvalidMeta(InvalidMetaKind), /// Reading a C string that does not end within its allocation. UnterminatedCString(Pointer), - /// Dereferencing a dangling pointer after it got freed. - PointerUseAfterFree(AllocId), + /// Using a pointer after it got freed. + PointerUseAfterFree(AllocId, CheckInAllocMsg), /// Used a pointer outside the bounds it is valid for. /// (If `ptr_size > 0`, determines the size of the memory range that was expected to be in-bounds.) PointerOutOfBounds { @@ -281,25 +290,13 @@ pub enum UndefinedBehaviorInfo { /// Using an integer as a pointer in the wrong way. DanglingIntPointer(u64, CheckInAllocMsg), /// Used a pointer with bad alignment. - AlignmentCheckFailed { - required: Align, - has: Align, - }, + AlignmentCheckFailed { required: Align, has: Align }, /// Writing to read-only memory. WriteToReadOnly(AllocId), - // Trying to access the data behind a function pointer. + /// Trying to access the data behind a function pointer. DerefFunctionPointer(AllocId), - // Trying to access the data behind a vtable pointer. + /// Trying to access the data behind a vtable pointer. DerefVTablePointer(AllocId), - /// The value validity check found a problem. - /// Should only be thrown by `validity.rs` and always point out which part of the value - /// is the problem. - ValidationFailure { - /// The "path" to the value in question, e.g. `.0[5].field` for a struct - /// field in the 6th element of an array that is the first element of a tuple. - path: Option, - msg: String, - }, /// Using a non-boolean `u8` as bool. InvalidBool(u8), /// Using a non-character `u32` as character. @@ -313,212 +310,164 @@ pub enum UndefinedBehaviorInfo { /// Using a string that is not valid UTF-8, InvalidStr(std::str::Utf8Error), /// Using uninitialized data where it is not allowed. - InvalidUninitBytes(Option<(AllocId, UninitBytesAccess)>), + InvalidUninitBytes(Option<(AllocId, BadBytesAccess)>), /// Working with a local that is not currently live. DeadLocal, /// Data size is not equal to target size. ScalarSizeMismatch(ScalarSizeMismatch), /// A discriminant of an uninhabited enum variant is written. - UninhabitedEnumVariantWritten, + UninhabitedEnumVariantWritten(VariantIdx), + /// An uninhabited enum variant is projected. + UninhabitedEnumVariantRead(VariantIdx), + /// Validation error. + ValidationError(ValidationErrorInfo<'a>), + // FIXME(fee1-dead) these should all be actual variants of the enum instead of dynamically + // dispatched + /// A custom (free-form) error, created by `err_ub_custom!`. + Custom(crate::error::CustomSubdiagnostic<'a>), +} + +#[derive(Debug, Clone, Copy)] +pub enum PointerKind { + Ref, + Box, +} + +impl IntoDiagnosticArg for PointerKind { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str( + match self { + Self::Ref => "ref", + Self::Box => "box", + } + .into(), + ) + } } -impl fmt::Display for UndefinedBehaviorInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use UndefinedBehaviorInfo::*; - match self { - Ub(msg) => write!(f, "{msg}"), - Unreachable => write!(f, "entering unreachable code"), - BoundsCheckFailed { ref len, ref index } => { - write!(f, "indexing out of bounds: the len is {len} but the index is {index}") - } - DivisionByZero => write!(f, "dividing by zero"), - RemainderByZero => write!(f, "calculating the remainder with a divisor of zero"), - DivisionOverflow => write!(f, "overflow in signed division (dividing MIN by -1)"), - RemainderOverflow => write!(f, "overflow in signed remainder (dividing MIN by -1)"), - PointerArithOverflow => write!(f, "overflowing in-bounds pointer arithmetic"), - InvalidMeta(msg) => write!(f, "invalid metadata in wide pointer: {msg}"), - UnterminatedCString(p) => write!( - f, - "reading a null-terminated string starting at {p:?} with no null found before end of allocation", - ), - PointerUseAfterFree(a) => { - write!(f, "pointer to {a:?} was dereferenced after this allocation got freed") - } - PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size: Size::ZERO, msg } => { - write!( - f, - "{msg}{alloc_id:?} has size {alloc_size}, so pointer at offset {ptr_offset} is out-of-bounds", - alloc_size = alloc_size.bytes(), - ) - } - PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => write!( - f, - "{msg}{alloc_id:?} has size {alloc_size}, so pointer to {ptr_size} byte{ptr_size_p} starting at offset {ptr_offset} is out-of-bounds", - alloc_size = alloc_size.bytes(), - ptr_size = ptr_size.bytes(), - ptr_size_p = pluralize!(ptr_size.bytes()), - ), - DanglingIntPointer(i, msg) => { - write!( - f, - "{msg}{pointer} is a dangling pointer (it has no provenance)", - pointer = Pointer::>::from_addr_invalid(*i), - ) - } - AlignmentCheckFailed { required, has } => write!( - f, - "accessing memory with alignment {has}, but alignment {required} is required", - has = has.bytes(), - required = required.bytes() - ), - WriteToReadOnly(a) => write!(f, "writing to {a:?} which is read-only"), - DerefFunctionPointer(a) => write!(f, "accessing {a:?} which contains a function"), - DerefVTablePointer(a) => write!(f, "accessing {a:?} which contains a vtable"), - ValidationFailure { path: None, msg } => { - write!(f, "constructing invalid value: {msg}") - } - ValidationFailure { path: Some(path), msg } => { - write!(f, "constructing invalid value at {path}: {msg}") - } - InvalidBool(b) => { - write!(f, "interpreting an invalid 8-bit value as a bool: 0x{b:02x}") - } - InvalidChar(c) => { - write!(f, "interpreting an invalid 32-bit value as a char: 0x{c:08x}") - } - InvalidTag(val) => write!(f, "enum value has invalid tag: {val:x}"), - InvalidFunctionPointer(p) => { - write!(f, "using {p:?} as function pointer but it does not point to a function") - } - InvalidVTablePointer(p) => { - write!(f, "using {p:?} as vtable pointer but it does not point to a vtable") - } - InvalidStr(err) => write!(f, "this string is not valid UTF-8: {err}"), - InvalidUninitBytes(Some((alloc, info))) => write!( - f, - "reading memory at {alloc:?}{access:?}, \ - but memory is uninitialized at {uninit:?}, \ - and this operation requires initialized memory", - access = info.access, - uninit = info.uninit, - ), - InvalidUninitBytes(None) => write!( - f, - "using uninitialized data, but this operation requires initialized memory" - ), - DeadLocal => write!(f, "accessing a dead local variable"), - ScalarSizeMismatch(self::ScalarSizeMismatch { target_size, data_size }) => write!( - f, - "scalar size mismatch: expected {target_size} bytes but got {data_size} bytes instead", - ), - UninhabitedEnumVariantWritten => { - write!(f, "writing discriminant of an uninhabited enum") - } +#[derive(Debug)] +pub struct ValidationErrorInfo<'tcx> { + pub path: Option, + pub kind: ValidationErrorKind<'tcx>, +} + +#[derive(Debug)] +pub enum ExpectedKind { + Reference, + Box, + RawPtr, + InitScalar, + Bool, + Char, + Float, + Int, + FnPtr, + EnumTag, + Str, +} + +impl From for ExpectedKind { + fn from(x: PointerKind) -> ExpectedKind { + match x { + PointerKind::Box => ExpectedKind::Box, + PointerKind::Ref => ExpectedKind::Reference, } } } +#[derive(Debug)] +pub enum ValidationErrorKind<'tcx> { + PointerAsInt { expected: ExpectedKind }, + PartialPointer, + PtrToUninhabited { ptr_kind: PointerKind, ty: Ty<'tcx> }, + PtrToStatic { ptr_kind: PointerKind }, + PtrToMut { ptr_kind: PointerKind }, + MutableRefInConst, + NullFnPtr, + NeverVal, + NullablePtrOutOfRange { range: WrappingRange, max_value: u128 }, + PtrOutOfRange { range: WrappingRange, max_value: u128 }, + OutOfRange { value: String, range: WrappingRange, max_value: u128 }, + UnsafeCell, + UninhabitedVal { ty: Ty<'tcx> }, + InvalidEnumTag { value: String }, + UninhabitedEnumVariant, + Uninit { expected: ExpectedKind }, + InvalidVTablePtr { value: String }, + InvalidMetaSliceTooLarge { ptr_kind: PointerKind }, + InvalidMetaTooLarge { ptr_kind: PointerKind }, + UnalignedPtr { ptr_kind: PointerKind, required_bytes: u64, found_bytes: u64 }, + NullPtr { ptr_kind: PointerKind }, + DanglingPtrNoProvenance { ptr_kind: PointerKind, pointer: String }, + DanglingPtrOutOfBounds { ptr_kind: PointerKind }, + DanglingPtrUseAfterFree { ptr_kind: PointerKind }, + InvalidBool { value: String }, + InvalidChar { value: String }, + InvalidFnPtr { value: String }, +} + /// Error information for when the program did something that might (or might not) be correct /// to do according to the Rust spec, but due to limitations in the interpreter, the /// operation could not be carried out. These limitations can differ between CTFE and the /// Miri engine, e.g., CTFE does not support dereferencing pointers at integral addresses. +#[derive(Debug)] pub enum UnsupportedOpInfo { /// Free-form case. Only for errors that are never caught! + // FIXME still use translatable diagnostics Unsupported(String), // // The variants below are only reachable from CTFE/const prop, miri will never emit them. // /// Overwriting parts of a pointer; without knowing absolute addresses, the resulting state /// cannot be represented by the CTFE interpreter. - PartialPointerOverwrite(Pointer), - /// Attempting to `copy` parts of a pointer to somewhere else; without knowing absolute + OverwritePartialPointer(Pointer), + /// Attempting to read or copy parts of a pointer to somewhere else; without knowing absolute /// addresses, the resulting state cannot be represented by the CTFE interpreter. - PartialPointerCopy(Pointer), - /// Encountered a pointer where we needed raw bytes. - ReadPointerAsBytes, + ReadPartialPointer(Pointer), + /// Encountered a pointer where we needed an integer. + ReadPointerAsInt(Option<(AllocId, BadBytesAccess)>), /// Accessing thread local statics ThreadLocalStatic(DefId), /// Accessing an unsupported extern static. ReadExternStatic(DefId), } -impl fmt::Display for UnsupportedOpInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use UnsupportedOpInfo::*; - match self { - Unsupported(ref msg) => write!(f, "{msg}"), - PartialPointerOverwrite(ptr) => { - write!(f, "unable to overwrite parts of a pointer in memory at {ptr:?}") - } - PartialPointerCopy(ptr) => { - write!(f, "unable to copy parts of a pointer from memory at {ptr:?}") - } - ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes"), - ThreadLocalStatic(did) => write!(f, "cannot access thread local static ({did:?})"), - ReadExternStatic(did) => write!(f, "cannot read from extern static ({did:?})"), - } - } -} - /// Error information for when the program exhausted the resources granted to it /// by the interpreter. +#[derive(Debug)] pub enum ResourceExhaustionInfo { /// The stack grew too big. StackFrameLimitReached, - /// The program ran for too long. - /// - /// The exact limit is set by the `const_eval_limit` attribute. - StepLimitReached, /// There is not enough memory (on the host) to perform an allocation. MemoryExhausted, /// The address space (of the target) is full. AddressSpaceFull, } -impl fmt::Display for ResourceExhaustionInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ResourceExhaustionInfo::*; - match self { - StackFrameLimitReached => { - write!(f, "reached the configured maximum number of stack frames") - } - StepLimitReached => { - write!(f, "exceeded interpreter step limit (see `#[const_eval_limit]`)") - } - MemoryExhausted => { - write!(f, "tried to allocate more memory than available to compiler") - } - AddressSpaceFull => { - write!(f, "there are no more free addresses in the address space") - } - } - } -} - -/// A trait to work around not having trait object upcasting. -pub trait AsAny: Any { - fn as_any(&self) -> &dyn Any; -} -impl AsAny for T { - #[inline(always)] - fn as_any(&self) -> &dyn Any { - self - } -} - /// A trait for machine-specific errors (or other "machine stop" conditions). -pub trait MachineStopType: AsAny + fmt::Display + Send {} +pub trait MachineStopType: Any + fmt::Debug + Send { + /// The diagnostic message for this error + fn diagnostic_message(&self) -> DiagnosticMessage; + /// Add diagnostic arguments by passing name and value pairs to `adder`, which are passed to + /// fluent for formatting the translated diagnostic message. + fn add_args( + self: Box, + adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>), + ); +} impl dyn MachineStopType { #[inline(always)] pub fn downcast_ref(&self) -> Option<&T> { - self.as_any().downcast_ref() + let x: &dyn Any = self; + x.downcast_ref() } } +#[derive(Debug)] pub enum InterpError<'tcx> { /// The program caused undefined behavior. - UndefinedBehavior(UndefinedBehaviorInfo), + UndefinedBehavior(UndefinedBehaviorInfo<'tcx>), /// The program did something the interpreter does not support (some of these *might* be UB /// but the interpreter is not sure). Unsupported(UnsupportedOpInfo), @@ -534,26 +483,6 @@ pub enum InterpError<'tcx> { pub type InterpResult<'tcx, T = ()> = Result>; -impl fmt::Display for InterpError<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use InterpError::*; - match *self { - Unsupported(ref msg) => write!(f, "{msg}"), - InvalidProgram(ref msg) => write!(f, "{msg}"), - UndefinedBehavior(ref msg) => write!(f, "{msg}"), - ResourceExhaustion(ref msg) => write!(f, "{msg}"), - MachineStop(ref msg) => write!(f, "{msg}"), - } - } -} - -// Forward `Debug` to `Display`, so it does not look awful. -impl fmt::Debug for InterpError<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - impl InterpError<'_> { /// Some errors do string formatting even if the error is never printed. /// To avoid performance issues, there are places where we want to be sure to never raise these formatting errors, @@ -562,7 +491,7 @@ impl InterpError<'_> { matches!( self, InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_)) - | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure { .. }) + | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationError { .. }) | InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) ) } diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index 3620385fab134..3543158bf82d2 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -89,6 +89,30 @@ macro_rules! throw_machine_stop { ($($tt:tt)*) => { do yeet err_machine_stop!($($tt)*) }; } +#[macro_export] +macro_rules! err_ub_custom { + ($msg:expr $(, $($name:ident = $value:expr),* $(,)?)?) => {{ + $( + let ($($name,)*) = ($($value,)*); + )? + err_ub!(Custom( + rustc_middle::error::CustomSubdiagnostic { + msg: || $msg, + add_args: Box::new(move |mut set_arg| { + $($( + set_arg(stringify!($name).into(), rustc_errors::IntoDiagnosticArg::into_diagnostic_arg($name)); + )*)? + }) + } + )) + }}; +} + +#[macro_export] +macro_rules! throw_ub_custom { + ($($tt:tt)*) => { do yeet err_ub_custom!($($tt)*) }; +} + mod allocation; mod error; mod pointer; @@ -114,14 +138,15 @@ use rustc_target::abi::{AddressSpace, Endian, HasDataLayout}; use crate::mir; use crate::ty::codec::{TyDecoder, TyEncoder}; -use crate::ty::subst::GenericArgKind; +use crate::ty::GenericArgKind; use crate::ty::{self, Instance, Ty, TyCtxt}; pub use self::error::{ - struct_error, CheckInAllocMsg, ErrorHandled, EvalToAllocationRawResult, EvalToConstValueResult, - EvalToValTreeResult, InterpError, InterpErrorInfo, InterpResult, InvalidProgramInfo, - MachineStopType, ReportedErrorInfo, ResourceExhaustionInfo, ScalarSizeMismatch, - UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo, + struct_error, BadBytesAccess, CheckInAllocMsg, ErrorHandled, EvalToAllocationRawResult, + EvalToConstValueResult, EvalToValTreeResult, ExpectedKind, InterpError, InterpErrorInfo, + InterpResult, InvalidMetaKind, InvalidProgramInfo, MachineStopType, PointerKind, + ReportedErrorInfo, ResourceExhaustionInfo, ScalarSizeMismatch, UndefinedBehaviorInfo, + UnsupportedOpInfo, ValidationErrorInfo, ValidationErrorKind, }; pub use self::value::{get_slice_bytes, ConstAlloc, ConstValue, Scalar}; @@ -151,7 +176,7 @@ impl<'tcx> GlobalId<'tcx> { pub fn display(self, tcx: TyCtxt<'tcx>) -> String { let instance_name = with_no_trimmed_paths!(tcx.def_path_str(self.instance.def.def_id())); if let Some(promoted) = self.promoted { - format!("{}::{:?}", instance_name, promoted) + format!("{instance_name}::{promoted:?}") } else { instance_name } @@ -249,7 +274,7 @@ pub struct AllocDecodingState { // For each `AllocId`, we keep track of which decoding state it's currently in. decoding_state: Vec>, // The offsets of each allocation in the data stream. - data_offsets: Vec, + data_offsets: Vec, } impl AllocDecodingState { @@ -264,7 +289,7 @@ impl AllocDecodingState { AllocDecodingSession { state: self, session_id } } - pub fn new(data_offsets: Vec) -> Self { + pub fn new(data_offsets: Vec) -> Self { let decoding_state = std::iter::repeat_with(|| Lock::new(State::Empty)).take(data_offsets.len()).collect(); @@ -534,7 +559,7 @@ impl<'tcx> TyCtxt<'tcx> { // However, formatting code relies on function identity (see #58320), so we only do // this for generic functions. Lifetime parameters are ignored. let is_generic = instance - .substs + .args .into_iter() .any(|kind| !matches!(kind.unpack(), GenericArgKind::Lifetime(_))); if is_generic { @@ -584,7 +609,7 @@ impl<'tcx> TyCtxt<'tcx> { /// Panics in case the `AllocId` is dangling. Since that is impossible for `AllocId`s in /// constants (as all constants must pass interning and validation that check for dangling /// ids), this function is frequently used throughout rustc, but should not be used within - /// the miri engine. + /// the interpreter. pub fn global_alloc(self, id: AllocId) -> GlobalAlloc<'tcx> { match self.try_get_global_alloc(id) { Some(alloc) => alloc, diff --git a/compiler/rustc_middle/src/mir/interpret/queries.rs b/compiler/rustc_middle/src/mir/interpret/queries.rs index f53dc8cb0ec13..fc659ce18a4d2 100644 --- a/compiler/rustc_middle/src/mir/interpret/queries.rs +++ b/compiler/rustc_middle/src/mir/interpret/queries.rs @@ -2,8 +2,8 @@ use super::{ErrorHandled, EvalToConstValueResult, EvalToValTreeResult, GlobalId} use crate::mir; use crate::query::{TyCtxtAt, TyCtxtEnsure}; -use crate::ty::subst::InternalSubsts; use crate::ty::visit::TypeVisitableExt; +use crate::ty::GenericArgs; use crate::ty::{self, TyCtxt}; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -20,8 +20,8 @@ impl<'tcx> TyCtxt<'tcx> { // to be used. So we can't use `Instance::mono`, instead we feed unresolved substitutions // into `const_eval` which will return `ErrorHandled::ToGeneric` if any of them are // encountered. - let substs = InternalSubsts::identity_for_item(self, def_id); - let instance = ty::Instance::new(def_id, substs); + let args = GenericArgs::identity_for_item(self, def_id); + let instance = ty::Instance::new(def_id, args); let cid = GlobalId { instance, promoted: None }; let param_env = self.param_env(def_id).with_reveal_all_normalized(self); self.const_eval_global_id(param_env, cid, None) @@ -48,14 +48,14 @@ impl<'tcx> TyCtxt<'tcx> { // // When trying to evaluate constants containing inference variables, // use `Infcx::const_eval_resolve` instead. - if ct.substs.has_non_region_infer() { + if ct.args.has_non_region_infer() { bug!("did not expect inference variables here"); } match ty::Instance::resolve( self, param_env, // FIXME: maybe have a separate version for resolving mir::UnevaluatedConst? - ct.def, ct.substs, + ct.def, ct.args, ) { Ok(Some(instance)) => { let cid = GlobalId { instance, promoted: ct.promoted }; @@ -79,11 +79,11 @@ impl<'tcx> TyCtxt<'tcx> { // // When trying to evaluate constants containing inference variables, // use `Infcx::const_eval_resolve` instead. - if ct.substs.has_non_region_infer() { + if ct.args.has_non_region_infer() { bug!("did not expect inference variables here"); } - match ty::Instance::resolve(self, param_env, ct.def, ct.substs) { + match ty::Instance::resolve(self, param_env, ct.def, ct.args) { Ok(Some(instance)) => { let cid = GlobalId { instance, promoted: None }; self.const_eval_global_id_for_typeck(param_env, cid, span).inspect(|_| { @@ -94,9 +94,16 @@ impl<'tcx> TyCtxt<'tcx> { // @lcnr believes that successfully evaluating even though there are // used generic parameters is a bug of evaluation, so checking for it // here does feel somewhat sensible. - if !self.features().generic_const_exprs && ct.substs.has_non_region_param() { - assert!(matches!(self.def_kind(ct.def), DefKind::AnonConst)); - let mir_body = self.mir_for_ctfe(ct.def); + if !self.features().generic_const_exprs && ct.args.has_non_region_param() { + let def_kind = self.def_kind(instance.def_id()); + assert!( + matches!( + def_kind, + DefKind::InlineConst | DefKind::AnonConst | DefKind::AssocConst + ), + "{cid:?} is {def_kind:?}", + ); + let mir_body = self.mir_for_ctfe(instance.def_id()); if mir_body.is_polymorphic { let Some(local_def_id) = ct.def.as_local() else { return }; self.struct_span_lint_hir( @@ -132,7 +139,6 @@ impl<'tcx> TyCtxt<'tcx> { cid: GlobalId<'tcx>, span: Option, ) -> EvalToConstValueResult<'tcx> { - let param_env = param_env.with_const(); // Const-eval shouldn't depend on lifetimes at all, so we can erase them, which should // improve caching of queries. let inputs = self.erase_regions(param_env.and(cid)); @@ -151,8 +157,6 @@ impl<'tcx> TyCtxt<'tcx> { cid: GlobalId<'tcx>, span: Option, ) -> EvalToValTreeResult<'tcx> { - let param_env = param_env.with_const(); - debug!(?param_env); // Const-eval shouldn't depend on lifetimes at all, so we can erase them, which should // improve caching of queries. let inputs = self.erase_regions(param_env.and(cid)); @@ -197,7 +201,6 @@ impl<'tcx> TyCtxtAt<'tcx> { gid: GlobalId<'tcx>, param_env: ty::ParamEnv<'tcx>, ) -> Result, ErrorHandled> { - let param_env = param_env.with_const(); trace!("eval_to_allocation: Need to compute {:?}", gid); let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?; Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory()) @@ -214,11 +217,10 @@ impl<'tcx> TyCtxtEnsure<'tcx> { // to be used. So we can't use `Instance::mono`, instead we feed unresolved substitutions // into `const_eval` which will return `ErrorHandled::ToGeneric` if any of them are // encountered. - let substs = InternalSubsts::identity_for_item(self.tcx, def_id); - let instance = ty::Instance::new(def_id, substs); + let args = GenericArgs::identity_for_item(self.tcx, def_id); + let instance = ty::Instance::new(def_id, args); let cid = GlobalId { instance, promoted: None }; - let param_env = - self.tcx.param_env(def_id).with_reveal_all_normalized(self.tcx).with_const(); + let param_env = self.tcx.param_env(def_id).with_reveal_all_normalized(self.tcx); // Const-eval shouldn't depend on lifetimes at all, so we can erase them, which should // improve caching of queries. let inputs = self.tcx.erase_regions(param_env.and(cid)); @@ -231,20 +233,8 @@ impl<'tcx> TyCtxtEnsure<'tcx> { assert!(self.tcx.is_static(def_id)); let instance = ty::Instance::mono(self.tcx, def_id); let gid = GlobalId { instance, promoted: None }; - let param_env = ty::ParamEnv::reveal_all().with_const(); + let param_env = ty::ParamEnv::reveal_all(); trace!("eval_to_allocation: Need to compute {:?}", gid); self.eval_to_allocation_raw(param_env.and(gid)) } } - -impl<'tcx> TyCtxt<'tcx> { - /// Destructure a mir constant ADT or array into its variant index and its field values. - /// Panics if the destructuring fails, use `try_destructure_mir_constant` for fallible version. - pub fn destructure_mir_constant( - self, - param_env: ty::ParamEnv<'tcx>, - constant: mir::ConstantKind<'tcx>, - ) -> mir::DestructuredConstant<'tcx> { - self.try_destructure_mir_constant(param_env.and(constant)).unwrap() - } -} diff --git a/compiler/rustc_middle/src/mir/interpret/value.rs b/compiler/rustc_middle/src/mir/interpret/value.rs index 36dbbe4bf7765..5345a65880328 100644 --- a/compiler/rustc_middle/src/mir/interpret/value.rs +++ b/compiler/rustc_middle/src/mir/interpret/value.rs @@ -97,6 +97,10 @@ impl<'tcx> ConstValue<'tcx> { ConstValue::Scalar(Scalar::from_u64(i)) } + pub fn from_u128(i: u128) -> Self { + ConstValue::Scalar(Scalar::from_u128(i)) + } + pub fn from_target_usize(i: u64, cx: &impl HasDataLayout) -> Self { ConstValue::Scalar(Scalar::from_target_usize(i, cx)) } @@ -131,8 +135,8 @@ static_assert_size!(Scalar, 24); impl fmt::Debug for Scalar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Scalar::Ptr(ptr, _size) => write!(f, "{:?}", ptr), - Scalar::Int(int) => write!(f, "{:?}", int), + Scalar::Ptr(ptr, _size) => write!(f, "{ptr:?}"), + Scalar::Int(int) => write!(f, "{int:?}"), } } } @@ -140,8 +144,8 @@ impl fmt::Debug for Scalar { impl fmt::Display for Scalar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr), - Scalar::Int(int) => write!(f, "{}", int), + Scalar::Ptr(ptr, _size) => write!(f, "pointer to {ptr:?}"), + Scalar::Int(int) => write!(f, "{int}"), } } } @@ -149,8 +153,8 @@ impl fmt::Display for Scalar { impl fmt::LowerHex for Scalar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Scalar::Ptr(ptr, _size) => write!(f, "pointer to {:?}", ptr), - Scalar::Int(int) => write!(f, "{:#x}", int), + Scalar::Ptr(ptr, _size) => write!(f, "pointer to {ptr:?}"), + Scalar::Int(int) => write!(f, "{int:#x}"), } } } @@ -240,6 +244,11 @@ impl Scalar { Scalar::Int(i.into()) } + #[inline] + pub fn from_u128(i: u128) -> Self { + Scalar::Int(i.into()) + } + #[inline] pub fn from_target_usize(i: u64, cx: &impl HasDataLayout) -> Self { Self::from_uint(i, cx.data_layout().pointer_size) @@ -311,6 +320,14 @@ impl Scalar { } }) } + + #[inline] + pub fn size(self) -> Size { + match self { + Scalar::Int(int) => int.size(), + Scalar::Ptr(_ptr, sz) => Size::from_bytes(sz), + } + } } impl<'tcx, Prov: Provenance> Scalar { @@ -361,21 +378,23 @@ impl<'tcx, Prov: Provenance> Scalar { #[inline] pub fn to_bits(self, target_size: Size) -> InterpResult<'tcx, u128> { assert_ne!(target_size.bytes(), 0, "you should never look at the bits of a ZST"); - self.try_to_int().map_err(|_| err_unsup!(ReadPointerAsBytes))?.to_bits(target_size).map_err( - |size| { + self.try_to_int() + .map_err(|_| err_unsup!(ReadPointerAsInt(None)))? + .to_bits(target_size) + .map_err(|size| { err_ub!(ScalarSizeMismatch(ScalarSizeMismatch { target_size: target_size.bytes(), data_size: size.bytes(), })) .into() - }, - ) + }) } #[inline(always)] #[cfg_attr(debug_assertions, track_caller)] // only in debug builds due to perf (see #98980) pub fn assert_bits(self, target_size: Size) -> u128 { - self.to_bits(target_size).unwrap() + self.to_bits(target_size) + .unwrap_or_else(|_| panic!("assertion failed: {self:?} fits {target_size:?}")) } pub fn to_bool(self) -> InterpResult<'tcx, bool> { diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 5c71910a955d8..9ef3a1b30e498 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -3,7 +3,7 @@ //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/mir/index.html use crate::mir::interpret::{ - AllocRange, ConstAllocation, ConstValue, ErrorHandled, GlobalAlloc, LitToConstInput, Scalar, + AllocRange, ConstAllocation, ConstValue, ErrorHandled, GlobalAlloc, Scalar, }; use crate::mir::visit::MirVisitable; use crate::ty::codec::{TyDecoder, TyEncoder}; @@ -12,10 +12,10 @@ use crate::ty::print::{FmtPrinter, Printer}; use crate::ty::visit::TypeVisitableExt; use crate::ty::{self, List, Ty, TyCtxt}; use crate::ty::{AdtDef, InstanceDef, ScalarInt, UserTypeAnnotationIndex}; -use crate::ty::{GenericArg, InternalSubsts, SubstsRef}; +use crate::ty::{GenericArg, GenericArgs, GenericArgsRef}; use rustc_data_structures::captures::Captures; -use rustc_errors::ErrorGuaranteed; +use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, ErrorGuaranteed, IntoDiagnosticArg}; use rustc_hir::def::{CtorKind, Namespace}; use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; use rustc_hir::{self, GeneratorKind, ImplicitSelfKind}; @@ -145,7 +145,7 @@ impl MirPhase { } "analysis" => Self::Analysis(AnalysisPhase::parse(phase)), "runtime" => Self::Runtime(RuntimePhase::parse(phase)), - _ => panic!("Unknown MIR dialect {}", dialect), + _ => bug!("Unknown MIR dialect: '{}'", dialect), } } } @@ -159,7 +159,7 @@ impl AnalysisPhase { match &*phase.to_ascii_lowercase() { "initial" => Self::Initial, "post_cleanup" | "post-cleanup" | "postcleanup" => Self::PostCleanup, - _ => panic!("Unknown analysis phase {}", phase), + _ => bug!("Unknown analysis phase: '{}'", phase), } } } @@ -174,7 +174,7 @@ impl RuntimePhase { "initial" => Self::Initial, "post_cleanup" | "post-cleanup" | "postcleanup" => Self::PostCleanup, "optimized" => Self::Optimized, - _ => panic!("Unknown runtime phase {}", phase), + _ => bug!("Unknown runtime phase: '{}'", phase), } } } @@ -476,7 +476,7 @@ impl<'tcx> Body<'tcx> { /// Returns the return type; it always return first element from `local_decls` array. #[inline] pub fn bound_return_ty(&self) -> ty::EarlyBinder> { - ty::EarlyBinder(self.local_decls[RETURN_PLACE].ty) + ty::EarlyBinder::bind(self.local_decls[RETURN_PLACE].ty) } /// Gets the location of the terminator for the given block. @@ -619,7 +619,7 @@ impl> Decodable for ClearCrossCrate { let val = T::decode(d); ClearCrossCrate::Set(val) } - tag => panic!("Invalid tag for ClearCrossCrate: {:?}", tag), + tag => panic!("Invalid tag for ClearCrossCrate: {tag:?}"), } } } @@ -706,9 +706,7 @@ pub enum BindingForm<'tcx> { RefForGuard, } -TrivialTypeTraversalAndLiftImpls! { - BindingForm<'tcx>, -} +TrivialTypeTraversalAndLiftImpls! { BindingForm<'tcx> } mod binding_form_impl { use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; @@ -1048,12 +1046,12 @@ pub enum VarDebugInfoContents<'tcx> { impl<'tcx> Debug for VarDebugInfoContents<'tcx> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match self { - VarDebugInfoContents::Const(c) => write!(fmt, "{}", c), - VarDebugInfoContents::Place(p) => write!(fmt, "{:?}", p), + VarDebugInfoContents::Const(c) => write!(fmt, "{c}"), + VarDebugInfoContents::Place(p) => write!(fmt, "{p:?}"), VarDebugInfoContents::Composite { ty, fragments } => { - write!(fmt, "{:?}{{ ", ty)?; + write!(fmt, "{ty:?}{{ ")?; for f in fragments.iter() { - write!(fmt, "{:?}, ", f)?; + write!(fmt, "{f:?}, ")?; } write!(fmt, "}}") } @@ -1111,10 +1109,6 @@ pub struct VarDebugInfo<'tcx> { /// originated from (starting from 1). Note, if MIR inlining is enabled, then this is the /// argument number in the original function before it was inlined. pub argument_index: Option, - - /// The data represents `name` dereferenced `references` times, - /// and not the direct value. - pub references: u8, } /////////////////////////////////////////////////////////////////////////// @@ -1317,109 +1311,107 @@ impl AssertKind { match self { BoundsCheck { ref len, ref index } => write!( f, - "\"index out of bounds: the length is {{}} but the index is {{}}\", {:?}, {:?}", - len, index + "\"index out of bounds: the length is {{}} but the index is {{}}\", {len:?}, {index:?}" ), OverflowNeg(op) => { - write!(f, "\"attempt to negate `{{}}`, which would overflow\", {:?}", op) + write!(f, "\"attempt to negate `{{}}`, which would overflow\", {op:?}") } - DivisionByZero(op) => write!(f, "\"attempt to divide `{{}}` by zero\", {:?}", op), + DivisionByZero(op) => write!(f, "\"attempt to divide `{{}}` by zero\", {op:?}"), RemainderByZero(op) => write!( f, - "\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {:?}", - op + "\"attempt to calculate the remainder of `{{}}` with a divisor of zero\", {op:?}" ), Overflow(BinOp::Add, l, r) => write!( f, - "\"attempt to compute `{{}} + {{}}`, which would overflow\", {:?}, {:?}", - l, r + "\"attempt to compute `{{}} + {{}}`, which would overflow\", {l:?}, {r:?}" ), Overflow(BinOp::Sub, l, r) => write!( f, - "\"attempt to compute `{{}} - {{}}`, which would overflow\", {:?}, {:?}", - l, r + "\"attempt to compute `{{}} - {{}}`, which would overflow\", {l:?}, {r:?}" ), Overflow(BinOp::Mul, l, r) => write!( f, - "\"attempt to compute `{{}} * {{}}`, which would overflow\", {:?}, {:?}", - l, r + "\"attempt to compute `{{}} * {{}}`, which would overflow\", {l:?}, {r:?}" ), Overflow(BinOp::Div, l, r) => write!( f, - "\"attempt to compute `{{}} / {{}}`, which would overflow\", {:?}, {:?}", - l, r + "\"attempt to compute `{{}} / {{}}`, which would overflow\", {l:?}, {r:?}" ), Overflow(BinOp::Rem, l, r) => write!( f, - "\"attempt to compute the remainder of `{{}} % {{}}`, which would overflow\", {:?}, {:?}", - l, r + "\"attempt to compute the remainder of `{{}} % {{}}`, which would overflow\", {l:?}, {r:?}" ), Overflow(BinOp::Shr, _, r) => { - write!(f, "\"attempt to shift right by `{{}}`, which would overflow\", {:?}", r) + write!(f, "\"attempt to shift right by `{{}}`, which would overflow\", {r:?}") } Overflow(BinOp::Shl, _, r) => { - write!(f, "\"attempt to shift left by `{{}}`, which would overflow\", {:?}", r) + write!(f, "\"attempt to shift left by `{{}}`, which would overflow\", {r:?}") } MisalignedPointerDereference { required, found } => { write!( f, - "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {:?}, {:?}", - required, found + "\"misaligned pointer dereference: address must be a multiple of {{}} but is {{}}\", {required:?}, {found:?}" ) } _ => write!(f, "\"{}\"", self.description()), } } -} -impl fmt::Debug for AssertKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + pub fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; use AssertKind::*; + match self { - BoundsCheck { ref len, ref index } => write!( - f, - "index out of bounds: the length is {:?} but the index is {:?}", - len, index - ), - OverflowNeg(op) => write!(f, "attempt to negate `{:#?}`, which would overflow", op), - DivisionByZero(op) => write!(f, "attempt to divide `{:#?}` by zero", op), - RemainderByZero(op) => write!( - f, - "attempt to calculate the remainder of `{:#?}` with a divisor of zero", - op - ), - Overflow(BinOp::Add, l, r) => { - write!(f, "attempt to compute `{:#?} + {:#?}`, which would overflow", l, r) - } - Overflow(BinOp::Sub, l, r) => { - write!(f, "attempt to compute `{:#?} - {:#?}`, which would overflow", l, r) - } - Overflow(BinOp::Mul, l, r) => { - write!(f, "attempt to compute `{:#?} * {:#?}`, which would overflow", l, r) - } - Overflow(BinOp::Div, l, r) => { - write!(f, "attempt to compute `{:#?} / {:#?}`, which would overflow", l, r) + BoundsCheck { .. } => middle_bounds_check, + Overflow(BinOp::Shl, _, _) => middle_assert_shl_overflow, + Overflow(BinOp::Shr, _, _) => middle_assert_shr_overflow, + Overflow(_, _, _) => middle_assert_op_overflow, + OverflowNeg(_) => middle_assert_overflow_neg, + DivisionByZero(_) => middle_assert_divide_by_zero, + RemainderByZero(_) => middle_assert_remainder_by_zero, + ResumedAfterReturn(GeneratorKind::Async(_)) => middle_assert_async_resume_after_return, + ResumedAfterReturn(GeneratorKind::Gen) => middle_assert_generator_resume_after_return, + ResumedAfterPanic(GeneratorKind::Async(_)) => middle_assert_async_resume_after_panic, + ResumedAfterPanic(GeneratorKind::Gen) => middle_assert_generator_resume_after_panic, + + MisalignedPointerDereference { .. } => middle_assert_misaligned_ptr_deref, + } + } + + pub fn add_args(self, adder: &mut dyn FnMut(Cow<'static, str>, DiagnosticArgValue<'static>)) + where + O: fmt::Debug, + { + use AssertKind::*; + + macro_rules! add { + ($name: expr, $value: expr) => { + adder($name.into(), $value.into_diagnostic_arg()); + }; + } + + match self { + BoundsCheck { len, index } => { + add!("len", format!("{len:?}")); + add!("index", format!("{index:?}")); } - Overflow(BinOp::Rem, l, r) => write!( - f, - "attempt to compute the remainder of `{:#?} % {:#?}`, which would overflow", - l, r - ), - Overflow(BinOp::Shr, _, r) => { - write!(f, "attempt to shift right by `{:#?}`, which would overflow", r) + Overflow(BinOp::Shl | BinOp::Shr, _, val) + | DivisionByZero(val) + | RemainderByZero(val) + | OverflowNeg(val) => { + add!("val", format!("{val:#?}")); } - Overflow(BinOp::Shl, _, r) => { - write!(f, "attempt to shift left by `{:#?}`, which would overflow", r) + Overflow(binop, left, right) => { + add!("op", binop.to_hir_binop().as_str()); + add!("left", format!("{left:#?}")); + add!("right", format!("{right:#?}")); } + ResumedAfterReturn(_) | ResumedAfterPanic(_) => {} MisalignedPointerDereference { required, found } => { - write!( - f, - "misaligned pointer dereference: address must be a multiple of {:?} but is {:?}", - required, found - ) + add!("required", format!("{required:#?}")); + add!("found", format!("{found:#?}")); } - _ => write!(f, "{}", self.description()), } } } @@ -1455,9 +1447,9 @@ impl Debug for Statement<'_> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { use self::StatementKind::*; match self.kind { - Assign(box (ref place, ref rv)) => write!(fmt, "{:?} = {:?}", place, rv), + Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), FakeRead(box (ref cause, ref place)) => { - write!(fmt, "FakeRead({:?}, {:?})", cause, place) + write!(fmt, "FakeRead({cause:?}, {place:?})") } Retag(ref kind, ref place) => write!( fmt, @@ -1470,20 +1462,20 @@ impl Debug for Statement<'_> { }, place, ), - StorageLive(ref place) => write!(fmt, "StorageLive({:?})", place), - StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place), + StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), + StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), SetDiscriminant { ref place, variant_index } => { - write!(fmt, "discriminant({:?}) = {:?}", place, variant_index) + write!(fmt, "discriminant({place:?}) = {variant_index:?}") } - Deinit(ref place) => write!(fmt, "Deinit({:?})", place), + Deinit(ref place) => write!(fmt, "Deinit({place:?})"), PlaceMention(ref place) => { - write!(fmt, "PlaceMention({:?})", place) + write!(fmt, "PlaceMention({place:?})") } AscribeUserType(box (ref place, ref c_ty), ref variance) => { - write!(fmt, "AscribeUserType({:?}, {:?}, {:?})", place, variance, c_ty) + write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") } Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn) }) => { - write!(fmt, "Coverage::{:?} for {:?}", kind, rgn) + write!(fmt, "Coverage::{kind:?} for {rgn:?}") } Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), @@ -1596,14 +1588,13 @@ impl<'tcx> Place<'tcx> { self.projection.iter().any(|elem| elem.is_indirect()) } - /// If MirPhase >= Derefered and if projection contains Deref, - /// It's guaranteed to be in the first place - pub fn has_deref(&self) -> bool { - // To make sure this is not accidentally used in wrong mir phase - debug_assert!( - self.projection.is_empty() || !self.projection[1..].contains(&PlaceElem::Deref) - ); - self.projection.first() == Some(&PlaceElem::Deref) + /// Returns `true` if this `Place`'s first projection is `Deref`. + /// + /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, + /// `Deref` projections can only occur as the first projection. In that case this method + /// is equivalent to `is_indirect`, but faster. + pub fn is_indirect_first_projection(&self) -> bool { + self.as_ref().is_indirect_first_projection() } /// Finds the innermost `Local` from this `Place`, *if* it is either a local itself or @@ -1676,9 +1667,16 @@ impl<'tcx> PlaceRef<'tcx> { self.projection.iter().any(|elem| elem.is_indirect()) } - /// If MirPhase >= Derefered and if projection contains Deref, - /// It's guaranteed to be in the first place - pub fn has_deref(&self) -> bool { + /// Returns `true` if this `Place`'s first projection is `Deref`. + /// + /// This is useful because for MIR phases `AnalysisPhase::PostCleanup` and later, + /// `Deref` projections can only occur as the first projection. In that case this method + /// is equivalent to `is_indirect`, but faster. + pub fn is_indirect_first_projection(&self) -> bool { + // To make sure this is not accidentally used in wrong mir phase + debug_assert!( + self.projection.is_empty() || !self.projection[1..].contains(&PlaceElem::Deref) + ); self.projection.first() == Some(&PlaceElem::Deref) } @@ -1763,13 +1761,13 @@ impl Debug for Place<'_> { for elem in self.projection.iter() { match elem { ProjectionElem::OpaqueCast(ty) => { - write!(fmt, " as {})", ty)?; + write!(fmt, " as {ty})")?; } ProjectionElem::Downcast(Some(name), _index) => { - write!(fmt, " as {})", name)?; + write!(fmt, " as {name})")?; } ProjectionElem::Downcast(None, index) => { - write!(fmt, " as variant#{:?})", index)?; + write!(fmt, " as variant#{index:?})")?; } ProjectionElem::Deref => { write!(fmt, ")")?; @@ -1778,25 +1776,25 @@ impl Debug for Place<'_> { write!(fmt, ".{:?}: {:?})", field.index(), ty)?; } ProjectionElem::Index(ref index) => { - write!(fmt, "[{:?}]", index)?; + write!(fmt, "[{index:?}]")?; } ProjectionElem::ConstantIndex { offset, min_length, from_end: false } => { - write!(fmt, "[{:?} of {:?}]", offset, min_length)?; + write!(fmt, "[{offset:?} of {min_length:?}]")?; } ProjectionElem::ConstantIndex { offset, min_length, from_end: true } => { - write!(fmt, "[-{:?} of {:?}]", offset, min_length)?; + write!(fmt, "[-{offset:?} of {min_length:?}]")?; } ProjectionElem::Subslice { from, to, from_end: true } if to == 0 => { - write!(fmt, "[{:?}:]", from)?; + write!(fmt, "[{from:?}:]")?; } ProjectionElem::Subslice { from, to, from_end: true } if from == 0 => { - write!(fmt, "[:-{:?}]", to)?; + write!(fmt, "[:-{to:?}]")?; } ProjectionElem::Subslice { from, to, from_end: true } => { - write!(fmt, "[{:?}:-{:?}]", from, to)?; + write!(fmt, "[{from:?}:-{to:?}]")?; } ProjectionElem::Subslice { from, to, from_end: false } => { - write!(fmt, "[{:?}..{:?}]", from, to)?; + write!(fmt, "[{from:?}..{to:?}]")?; } } } @@ -1890,24 +1888,24 @@ impl<'tcx> Debug for Operand<'tcx> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { use self::Operand::*; match *self { - Constant(ref a) => write!(fmt, "{:?}", a), - Copy(ref place) => write!(fmt, "{:?}", place), - Move(ref place) => write!(fmt, "move {:?}", place), + Constant(ref a) => write!(fmt, "{a:?}"), + Copy(ref place) => write!(fmt, "{place:?}"), + Move(ref place) => write!(fmt, "move {place:?}"), } } } impl<'tcx> Operand<'tcx> { /// Convenience helper to make a constant that refers to the fn - /// with given `DefId` and substs. Since this is used to synthesize + /// with given `DefId` and args. Since this is used to synthesize /// MIR, assumes `user_ty` is None. pub fn function_handle( tcx: TyCtxt<'tcx>, def_id: DefId, - substs: impl IntoIterator>, + args: impl IntoIterator>, span: Span, ) -> Self { - let ty = tcx.mk_fn_def(def_id, substs); + let ty = Ty::new_fn_def(tcx, def_id, args); Operand::Constant(Box::new(Constant { span, user_ty: None, @@ -1931,11 +1929,11 @@ impl<'tcx> Operand<'tcx> { let param_env_and_ty = ty::ParamEnv::empty().and(ty); let type_size = tcx .layout_of(param_env_and_ty) - .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) + .unwrap_or_else(|e| panic!("could not compute layout for {ty:?}: {e:?}")) .size; let scalar_size = match val { Scalar::Int(int) => int.size(), - _ => panic!("Invalid scalar type {:?}", val), + _ => panic!("Invalid scalar type {val:?}"), }; scalar_size == type_size }); @@ -1975,9 +1973,9 @@ impl<'tcx> Operand<'tcx> { /// /// While this is unlikely in general, it's the normal case of what you'll /// find as the `func` in a [`TerminatorKind::Call`]. - pub fn const_fn_def(&self) -> Option<(DefId, SubstsRef<'tcx>)> { + pub fn const_fn_def(&self) -> Option<(DefId, GenericArgsRef<'tcx>)> { let const_ty = self.constant()?.literal.ty(); - if let ty::FnDef(def_id, substs) = *const_ty.kind() { Some((def_id, substs)) } else { None } + if let ty::FnDef(def_id, args) = *const_ty.kind() { Some((def_id, args)) } else { None } } } @@ -2008,7 +2006,7 @@ impl<'tcx> Rvalue<'tcx> { | CastKind::IntToFloat | CastKind::FnPtrToPtr | CastKind::PtrToPtr - | CastKind::Pointer(_) + | CastKind::PointerCoercion(_) | CastKind::PointerFromExposedAddress | CastKind::DynStar | CastKind::Transmute, @@ -2029,23 +2027,19 @@ impl<'tcx> Rvalue<'tcx> { impl BorrowKind { pub fn mutability(&self) -> Mutability { match *self { - BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => Mutability::Not, + BorrowKind::Shared | BorrowKind::Shallow => Mutability::Not, BorrowKind::Mut { .. } => Mutability::Mut, } } pub fn allows_two_phase_borrow(&self) -> bool { match *self { - BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => false, - BorrowKind::Mut { allow_two_phase_borrow } => allow_two_phase_borrow, - } - } - - // FIXME: won't be used after diagnostic migration - pub fn describe_mutability(&self) -> &str { - match *self { - BorrowKind::Shared | BorrowKind::Shallow | BorrowKind::Unique => "immutable", - BorrowKind::Mut { .. } => "mutable", + BorrowKind::Shared + | BorrowKind::Shallow + | BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::ClosureCapture } => { + false + } + BorrowKind::Mut { kind: MutBorrowKind::TwoPhaseBorrow } => true, } } } @@ -2055,26 +2049,26 @@ impl<'tcx> Debug for Rvalue<'tcx> { use self::Rvalue::*; match *self { - Use(ref place) => write!(fmt, "{:?}", place), + Use(ref place) => write!(fmt, "{place:?}"), Repeat(ref a, b) => { - write!(fmt, "[{:?}; ", a)?; + write!(fmt, "[{a:?}; ")?; pretty_print_const(b, fmt, false)?; write!(fmt, "]") } - Len(ref a) => write!(fmt, "Len({:?})", a), + Len(ref a) => write!(fmt, "Len({a:?})"), Cast(ref kind, ref place, ref ty) => { - write!(fmt, "{:?} as {:?} ({:?})", place, ty, kind) + write!(fmt, "{place:?} as {ty:?} ({kind:?})") } - BinaryOp(ref op, box (ref a, ref b)) => write!(fmt, "{:?}({:?}, {:?})", op, a, b), + BinaryOp(ref op, box (ref a, ref b)) => write!(fmt, "{op:?}({a:?}, {b:?})"), CheckedBinaryOp(ref op, box (ref a, ref b)) => { - write!(fmt, "Checked{:?}({:?}, {:?})", op, a, b) + write!(fmt, "Checked{op:?}({a:?}, {b:?})") } - UnaryOp(ref op, ref a) => write!(fmt, "{:?}({:?})", op, a), - Discriminant(ref place) => write!(fmt, "discriminant({:?})", place), + UnaryOp(ref op, ref a) => write!(fmt, "{op:?}({a:?})"), + Discriminant(ref place) => write!(fmt, "discriminant({place:?})"), NullaryOp(ref op, ref t) => match op { - NullOp::SizeOf => write!(fmt, "SizeOf({:?})", t), - NullOp::AlignOf => write!(fmt, "AlignOf({:?})", t), - NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({:?}, {:?})", t, fields), + NullOp::SizeOf => write!(fmt, "SizeOf({t:?})"), + NullOp::AlignOf => write!(fmt, "AlignOf({t:?})"), + NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t:?}, {fields:?})"), }, ThreadLocalRef(did) => ty::tls::with(|tcx| { let muta = tcx.static_mutability(did).unwrap().prefix_str(); @@ -2084,7 +2078,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { let kind_str = match borrow_kind { BorrowKind::Shared => "", BorrowKind::Shallow => "shallow ", - BorrowKind::Mut { .. } | BorrowKind::Unique => "mut ", + BorrowKind::Mut { .. } => "mut ", }; // When printing regions, add trailing space if necessary. @@ -2101,10 +2095,10 @@ impl<'tcx> Debug for Rvalue<'tcx> { // Do not even print 'static String::new() }; - write!(fmt, "&{}{}{:?}", region, kind_str, place) + write!(fmt, "&{region}{kind_str}{place:?}") } - CopyForDeref(ref place) => write!(fmt, "deref_copy {:#?}", place), + CopyForDeref(ref place) => write!(fmt, "deref_copy {place:#?}"), AddressOf(mutability, ref place) => { let kind_str = match mutability { @@ -2112,7 +2106,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { Mutability::Not => "const", }; - write!(fmt, "&raw {} {:?}", kind_str, place) + write!(fmt, "&raw {kind_str} {place:?}") } Aggregate(ref kind, ref places) => { @@ -2125,7 +2119,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { }; match **kind { - AggregateKind::Array(_) => write!(fmt, "{:?}", places), + AggregateKind::Array(_) => write!(fmt, "{places:?}"), AggregateKind::Tuple => { if places.is_empty() { @@ -2135,12 +2129,12 @@ impl<'tcx> Debug for Rvalue<'tcx> { } } - AggregateKind::Adt(adt_did, variant, substs, _user_ty, _) => { + AggregateKind::Adt(adt_did, variant, args, _user_ty, _) => { ty::tls::with(|tcx| { let variant_def = &tcx.adt_def(adt_did).variant(variant); - let substs = tcx.lift(substs).expect("could not lift for printing"); + let args = tcx.lift(args).expect("could not lift for printing"); let name = FmtPrinter::new(tcx, Namespace::ValueNS) - .print_def_path(variant_def.def_id, substs)? + .print_def_path(variant_def.def_id, args)? .into_buffer(); match variant_def.ctor_kind() { @@ -2157,10 +2151,10 @@ impl<'tcx> Debug for Rvalue<'tcx> { }) } - AggregateKind::Closure(def_id, substs) => ty::tls::with(|tcx| { + AggregateKind::Closure(def_id, args) => ty::tls::with(|tcx| { let name = if tcx.sess.opts.unstable_opts.span_free_formats { - let substs = tcx.lift(substs).unwrap(); - format!("[closure@{}]", tcx.def_path_str_with_substs(def_id, substs),) + let args = tcx.lift(args).unwrap(); + format!("[closure@{}]", tcx.def_path_str_with_args(def_id, args),) } else { let span = tcx.def_span(def_id); format!( @@ -2211,7 +2205,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { } ShallowInitBox(ref place, ref ty) => { - write!(fmt, "ShallowInitBox({:?}, {:?})", place, ty) + write!(fmt, "ShallowInitBox({place:?}, {ty:?})") } } } @@ -2327,10 +2321,10 @@ impl<'tcx> ConstantKind<'tcx> { pub fn eval(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self { match self { Self::Ty(c) => { - if let Some(val) = c.kind().try_eval_for_mir(tcx, param_env) { + if let Some(val) = c.try_eval_for_mir(tcx, param_env) { match val { Ok(val) => Self::Val(val, c.ty()), - Err(guar) => Self::Ty(tcx.const_error(self.ty(), guar)), + Err(guar) => Self::Ty(ty::Const::new_error(tcx, guar, self.ty())), } } else { self @@ -2342,7 +2336,9 @@ impl<'tcx> ConstantKind<'tcx> { match tcx.const_eval_resolve(param_env, uneval, None) { Ok(val) => Self::Val(val, ty), Err(ErrorHandled::TooGeneric) => self, - Err(ErrorHandled::Reported(guar)) => Self::Ty(tcx.const_error(ty, guar.into())), + Err(ErrorHandled::Reported(guar)) => { + Self::Ty(ty::Const::new_error(tcx, guar.into(), ty)) + } } } } @@ -2461,51 +2457,6 @@ impl<'tcx> ConstantKind<'tcx> { Self::Val(val, ty) } - #[instrument(skip(tcx), level = "debug", ret)] - pub fn from_inline_const(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self { - let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); - let body_id = match tcx.hir().get(hir_id) { - hir::Node::AnonConst(ac) => ac.body, - _ => span_bug!( - tcx.def_span(def_id.to_def_id()), - "from_inline_const can only process anonymous constants" - ), - }; - let expr = &tcx.hir().body(body_id).value; - let ty = tcx.typeck(def_id).node_type(hir_id); - - let lit_input = match expr.kind { - hir::ExprKind::Lit(ref lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: false }), - hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => match expr.kind { - hir::ExprKind::Lit(ref lit) => { - Some(LitToConstInput { lit: &lit.node, ty, neg: true }) - } - _ => None, - }, - _ => None, - }; - if let Some(lit_input) = lit_input { - // If an error occurred, ignore that it's a literal and leave reporting the error up to - // mir. - match tcx.at(expr.span).lit_to_mir_constant(lit_input) { - Ok(c) => return c, - Err(_) => {} - } - } - - let typeck_root_def_id = tcx.typeck_root_def_id(def_id.to_def_id()); - let parent_substs = - tcx.erase_regions(InternalSubsts::identity_for_item(tcx, typeck_root_def_id)); - let substs = - ty::InlineConstSubsts::new(tcx, ty::InlineConstSubstsParts { parent_substs, ty }) - .substs; - - let uneval = UnevaluatedConst { def: def_id.to_def_id(), substs, promoted: None }; - debug_assert!(!uneval.has_free_regions()); - - Self::Unevaluated(uneval, ty) - } - /// Literals are converted to `ConstantKindVal`, const generic parameters are eagerly /// converted to a constant, everything else becomes `Unevaluated`. #[instrument(skip(tcx), level = "debug", ret)] @@ -2534,7 +2485,7 @@ impl<'tcx> ConstantKind<'tcx> { }; debug!("expr.kind: {:?}", expr.kind); - let ty = tcx.type_of(def).subst_identity(); + let ty = tcx.type_of(def).instantiate_identity(); debug!(?ty); // FIXME(const_generics): We currently have to special case parameters because `min_const_generics` @@ -2553,7 +2504,7 @@ impl<'tcx> ConstantKind<'tcx> { let generics = tcx.generics_of(item_def_id); let index = generics.param_def_id_to_index[&def_id]; let name = tcx.item_name(def_id); - let ty_const = tcx.mk_const(ty::ParamConst::new(index, name), ty); + let ty_const = ty::Const::new_param(tcx, ty::ParamConst::new(index, name), ty); debug!(?ty_const); return Self::Ty(ty_const); @@ -2562,23 +2513,22 @@ impl<'tcx> ConstantKind<'tcx> { } let hir_id = tcx.hir().local_def_id_to_hir_id(def); - let parent_substs = if let Some(parent_hir_id) = tcx.hir().opt_parent_id(hir_id) + let parent_args = if let Some(parent_hir_id) = tcx.hir().opt_parent_id(hir_id) && let Some(parent_did) = parent_hir_id.as_owner() { - InternalSubsts::identity_for_item(tcx, parent_did) + GenericArgs::identity_for_item(tcx, parent_did) } else { List::empty() }; - debug!(?parent_substs); + debug!(?parent_args); let did = def.to_def_id(); - let child_substs = InternalSubsts::identity_for_item(tcx, did); - let substs = - tcx.mk_substs_from_iter(parent_substs.into_iter().chain(child_substs.into_iter())); - debug!(?substs); + let child_args = GenericArgs::identity_for_item(tcx, did); + let args = tcx.mk_args_from_iter(parent_args.into_iter().chain(child_args.into_iter())); + debug!(?args); let span = tcx.def_span(def); - let uneval = UnevaluatedConst::new(did, substs); + let uneval = UnevaluatedConst::new(did, args); debug!(?span, ?param_env); match tcx.const_eval_resolve(param_env, uneval, Some(span)) { @@ -2593,7 +2543,7 @@ impl<'tcx> ConstantKind<'tcx> { Self::Unevaluated( UnevaluatedConst { def: did, - substs: InternalSubsts::identity_for_item(tcx, did), + args: GenericArgs::identity_for_item(tcx, did), promoted: None, }, ty, @@ -2619,23 +2569,22 @@ impl<'tcx> ConstantKind<'tcx> { #[derive(Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct UnevaluatedConst<'tcx> { pub def: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, pub promoted: Option, } impl<'tcx> UnevaluatedConst<'tcx> { - // FIXME: probably should get rid of this method. It's also wrong to - // shrink and then later expand a promoted. #[inline] pub fn shrink(self) -> ty::UnevaluatedConst<'tcx> { - ty::UnevaluatedConst { def: self.def, substs: self.substs } + assert_eq!(self.promoted, None); + ty::UnevaluatedConst { def: self.def, args: self.args } } } impl<'tcx> UnevaluatedConst<'tcx> { #[inline] - pub fn new(def: DefId, substs: SubstsRef<'tcx>) -> UnevaluatedConst<'tcx> { - UnevaluatedConst { def, substs, promoted: Default::default() } + pub fn new(def: DefId, args: GenericArgsRef<'tcx>) -> UnevaluatedConst<'tcx> { + UnevaluatedConst { def, args, promoted: Default::default() } } } @@ -2800,7 +2749,7 @@ rustc_index::newtype_index! { impl<'tcx> Debug for Constant<'tcx> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { - write!(fmt, "{}", self) + write!(fmt, "{self}") } } @@ -2818,7 +2767,7 @@ impl<'tcx> Display for ConstantKind<'tcx> { fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { match *self { ConstantKind::Ty(c) => pretty_print_const(c, fmt, true), - ConstantKind::Val(val, ty) => pretty_print_const_value(val, ty, fmt, true), + ConstantKind::Val(val, ty) => pretty_print_const_value(val, ty, fmt), // FIXME(valtrees): Correctly print mir constants. ConstantKind::Unevaluated(..) => { fmt.write_str("_")?; @@ -2848,13 +2797,16 @@ fn pretty_print_byte_str(fmt: &mut Formatter<'_>, byte_str: &[u8]) -> fmt::Resul write!(fmt, "b\"{}\"", byte_str.escape_ascii()) } -fn comma_sep<'tcx>(fmt: &mut Formatter<'_>, elems: Vec>) -> fmt::Result { +fn comma_sep<'tcx>( + fmt: &mut Formatter<'_>, + elems: Vec<(ConstValue<'tcx>, Ty<'tcx>)>, +) -> fmt::Result { let mut first = true; - for elem in elems { + for (ct, ty) in elems { if !first { fmt.write_str(", ")?; } - fmt.write_str(&format!("{}", elem))?; + pretty_print_const_value(ct, ty, fmt)?; first = false; } Ok(()) @@ -2865,7 +2817,6 @@ fn pretty_print_const_value<'tcx>( ct: ConstValue<'tcx>, ty: Ty<'tcx>, fmt: &mut Formatter<'_>, - print_ty: bool, ) -> fmt::Result { use crate::ty::print::PrettyPrinter; @@ -2874,7 +2825,7 @@ fn pretty_print_const_value<'tcx>( let ty = tcx.lift(ty).unwrap(); if tcx.sess.verbose() { - fmt.write_str(&format!("ConstValue({:?}: {})", ct, ty))?; + fmt.write_str(&format!("ConstValue({ct:?}: {ty})"))?; return Ok(()); } @@ -2909,7 +2860,7 @@ fn pretty_print_const_value<'tcx>( } } (ConstValue::ByRef { alloc, offset }, ty::Array(t, n)) if *t == u8_type => { - let n = n.kind().try_to_bits(tcx.data_layout.pointer_size).unwrap(); + let n = n.try_to_bits(tcx.data_layout.pointer_size).unwrap(); // cast is ok because we already checked for pointer size (32 or 64 bit) above let range = AllocRange { start: offset, size: Size::from_bytes(n) }; let byte_str = alloc.inner().get_bytes_strip_provenance(&tcx, range).unwrap(); @@ -2924,16 +2875,11 @@ fn pretty_print_const_value<'tcx>( // introducing ICEs (e.g. via `layout_of`) from missing bounds. // E.g. `transmute([0usize; 2]): (u8, *mut T)` needs to know `T: Sized` // to be able to destructure the tuple into `(0u8, *mut T)` - // - // FIXME(eddyb) for `--emit=mir`/`-Z dump-mir`, we should provide the - // correct `ty::ParamEnv` to allow printing *all* constant values. (_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_non_region_param() => { let ct = tcx.lift(ct).unwrap(); let ty = tcx.lift(ty).unwrap(); - if let Some(contents) = tcx.try_destructure_mir_constant( - ty::ParamEnv::reveal_all().and(ConstantKind::Val(ct, ty)), - ) { - let fields = contents.fields.to_vec(); + if let Some(contents) = tcx.try_destructure_mir_constant_for_diagnostics((ct, ty)) { + let fields: Vec<(ConstValue<'_>, Ty<'_>)> = contents.fields.to_vec(); match *ty.kind() { ty::Array(..) => { fmt.write_str("[")?; @@ -2949,17 +2895,17 @@ fn pretty_print_const_value<'tcx>( fmt.write_str(")")?; } ty::Adt(def, _) if def.variants().is_empty() => { - fmt.write_str(&format!("{{unreachable(): {}}}", ty))?; + fmt.write_str(&format!("{{unreachable(): {ty}}}"))?; } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let variant_idx = contents .variant .expect("destructed mir constant of adt without variant idx"); let variant_def = &def.variant(variant_idx); - let substs = tcx.lift(substs).unwrap(); + let args = tcx.lift(args).unwrap(); let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); cx.print_alloc_ids = true; - let cx = cx.print_value_path(variant_def.def_id, substs)?; + let cx = cx.print_value_path(variant_def.def_id, args)?; fmt.write_str(&cx.into_buffer())?; match variant_def.ctor_kind() { @@ -2972,12 +2918,14 @@ fn pretty_print_const_value<'tcx>( None => { fmt.write_str(" {{ ")?; let mut first = true; - for (field_def, field) in iter::zip(&variant_def.fields, fields) + for (field_def, (ct, ty)) in + iter::zip(&variant_def.fields, fields) { if !first { fmt.write_str(", ")?; } - fmt.write_str(&format!("{}: {}", field_def.name, field))?; + write!(fmt, "{}: ", field_def.name)?; + pretty_print_const_value(ct, ty, fmt)?; first = false; } fmt.write_str(" }}")?; @@ -2987,20 +2935,13 @@ fn pretty_print_const_value<'tcx>( _ => unreachable!(), } return Ok(()); - } else { - // Fall back to debug pretty printing for invalid constants. - fmt.write_str(&format!("{:?}", ct))?; - if print_ty { - fmt.write_str(&format!(": {}", ty))?; - } - return Ok(()); - }; + } } (ConstValue::Scalar(scalar), _) => { let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS); cx.print_alloc_ids = true; let ty = tcx.lift(ty).unwrap(); - cx = cx.pretty_print_const_scalar(scalar, ty, print_ty)?; + cx = cx.pretty_print_const_scalar(scalar, ty)?; fmt.write_str(&cx.into_buffer())?; return Ok(()); } @@ -3015,12 +2956,8 @@ fn pretty_print_const_value<'tcx>( // their fields instead of just dumping the memory. _ => {} } - // fallback - fmt.write_str(&format!("{:?}", ct))?; - if print_ty { - fmt.write_str(&format!(": {}", ty))?; - } - Ok(()) + // Fall back to debug pretty printing for invalid constants. + write!(fmt, "{ct:?}: {ty}") }) } diff --git a/compiler/rustc_middle/src/mir/mono.rs b/compiler/rustc_middle/src/mir/mono.rs index ff54ec56a29ac..8fd980d5a9e9c 100644 --- a/compiler/rustc_middle/src/mir/mono.rs +++ b/compiler/rustc_middle/src/mir/mono.rs @@ -1,5 +1,5 @@ use crate::dep_graph::{DepNode, WorkProduct, WorkProductId}; -use crate::ty::{subst::InternalSubsts, Instance, InstanceDef, SymbolName, TyCtxt}; +use crate::ty::{GenericArgs, Instance, InstanceDef, SymbolName, TyCtxt}; use rustc_attr::InlineAttr; use rustc_data_structures::base_n; use rustc_data_structures::fingerprint::Fingerprint; @@ -56,22 +56,31 @@ impl<'tcx> MonoItem<'tcx> { } } + // Note: if you change how item size estimates work, you might need to + // change NON_INCR_MIN_CGU_SIZE as well. pub fn size_estimate(&self, tcx: TyCtxt<'tcx>) -> usize { match *self { MonoItem::Fn(instance) => { - // Estimate the size of a function based on how many statements - // it contains. - tcx.instance_def_size_estimate(instance.def) + match instance.def { + // "Normal" functions size estimate: the number of + // statements, plus one for the terminator. + InstanceDef::Item(..) | InstanceDef::DropGlue(..) => { + let mir = tcx.instance_mir(instance.def); + mir.basic_blocks.iter().map(|bb| bb.statements.len() + 1).sum() + } + // Other compiler-generated shims size estimate: 1 + _ => 1, + } } - // Conservatively estimate the size of a static declaration - // or assembly to be 1. + // Conservatively estimate the size of a static declaration or + // assembly item to be 1. MonoItem::Static(_) | MonoItem::GlobalAsm(_) => 1, } } pub fn is_generic_fn(&self) -> bool { match *self { - MonoItem::Fn(ref instance) => instance.substs.non_erasable_generics().next().is_some(), + MonoItem::Fn(ref instance) => instance.args.non_erasable_generics().next().is_some(), MonoItem::Static(..) | MonoItem::GlobalAsm(..) => false, } } @@ -168,14 +177,14 @@ impl<'tcx> MonoItem<'tcx> { /// which will never be accessed) in its place. pub fn is_instantiable(&self, tcx: TyCtxt<'tcx>) -> bool { debug!("is_instantiable({:?})", self); - let (def_id, substs) = match *self { - MonoItem::Fn(ref instance) => (instance.def_id(), instance.substs), - MonoItem::Static(def_id) => (def_id, InternalSubsts::empty()), + let (def_id, args) = match *self { + MonoItem::Fn(ref instance) => (instance.def_id(), instance.args), + MonoItem::Static(def_id) => (def_id, GenericArgs::empty()), // global asm never has predicates MonoItem::GlobalAsm(..) => return true, }; - !tcx.subst_and_check_impossible_predicates((def_id, &substs)) + !tcx.subst_and_check_impossible_predicates((def_id, &args)) } pub fn local_span(&self, tcx: TyCtxt<'tcx>) -> Option { @@ -214,9 +223,9 @@ impl<'tcx> MonoItem<'tcx> { impl<'tcx> fmt::Display for MonoItem<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - MonoItem::Fn(instance) => write!(f, "fn {}", instance), + MonoItem::Fn(instance) => write!(f, "fn {instance}"), MonoItem::Static(def_id) => { - write!(f, "static {}", Instance::new(def_id, InternalSubsts::empty())) + write!(f, "static {}", Instance::new(def_id, GenericArgs::empty())) } MonoItem::GlobalAsm(..) => write!(f, "global_asm"), } @@ -230,14 +239,28 @@ pub struct CodegenUnit<'tcx> { /// contain something unique to this crate (e.g., a module path) /// as well as the crate name and disambiguator. name: Symbol, - items: FxHashMap, (Linkage, Visibility)>, - size_estimate: Option, + items: FxHashMap, MonoItemData>, + size_estimate: usize, primary: bool, /// True if this is CGU is used to hold code coverage information for dead code, /// false otherwise. is_code_coverage_dead_code_cgu: bool, } +/// Auxiliary info about a `MonoItem`. +#[derive(Copy, Clone, PartialEq, Debug, HashStable)] +pub struct MonoItemData { + /// A cached copy of the result of `MonoItem::instantiation_mode`, where + /// `GloballyShared` maps to `false` and `LocalCopy` maps to `true`. + pub inlined: bool, + + pub linkage: Linkage, + pub visibility: Visibility, + + /// A cached copy of the result of `MonoItem::size_estimate`. + pub size_estimate: usize, +} + /// Specifies the linkage type for a `MonoItem`. /// /// See for more details about these variants. @@ -269,7 +292,7 @@ impl<'tcx> CodegenUnit<'tcx> { CodegenUnit { name, items: Default::default(), - size_estimate: None, + size_estimate: 0, primary: false, is_code_coverage_dead_code_cgu: false, } @@ -291,11 +314,13 @@ impl<'tcx> CodegenUnit<'tcx> { self.primary = true; } - pub fn items(&self) -> &FxHashMap, (Linkage, Visibility)> { + /// The order of these items is non-determinstic. + pub fn items(&self) -> &FxHashMap, MonoItemData> { &self.items } - pub fn items_mut(&mut self) -> &mut FxHashMap, (Linkage, Visibility)> { + /// The order of these items is non-determinstic. + pub fn items_mut(&mut self) -> &mut FxHashMap, MonoItemData> { &mut self.items } @@ -318,26 +343,21 @@ impl<'tcx> CodegenUnit<'tcx> { base_n::encode(hash, base_n::CASE_INSENSITIVE) } - pub fn create_size_estimate(&mut self, tcx: TyCtxt<'tcx>) { - // Estimate the size of a codegen unit as (approximately) the number of MIR - // statements it corresponds to. - self.size_estimate = Some(self.items.keys().map(|mi| mi.size_estimate(tcx)).sum()); + pub fn compute_size_estimate(&mut self) { + // The size of a codegen unit as the sum of the sizes of the items + // within it. + self.size_estimate = self.items.values().map(|data| data.size_estimate).sum(); } - #[inline] - /// Should only be called if [`create_size_estimate`] has previously been called. + /// Should only be called if [`compute_size_estimate`] has previously been called. /// - /// [`create_size_estimate`]: Self::create_size_estimate + /// [`compute_size_estimate`]: Self::compute_size_estimate + #[inline] pub fn size_estimate(&self) -> usize { + // Items are never zero-sized, so if we have items the estimate must be + // non-zero, unless we forgot to call `compute_size_estimate` first. + assert!(self.items.is_empty() || self.size_estimate != 0); self.size_estimate - .expect("create_size_estimate must be called before getting a size_estimate") - } - - pub fn modify_size_estimate(&mut self, delta: usize) { - assert!(self.size_estimate.is_some()); - if let Some(size_estimate) = self.size_estimate { - self.size_estimate = Some(size_estimate + delta); - } } pub fn contains_item(&self, item: &MonoItem<'tcx>) -> bool { @@ -358,7 +378,7 @@ impl<'tcx> CodegenUnit<'tcx> { pub fn items_in_deterministic_order( &self, tcx: TyCtxt<'tcx>, - ) -> Vec<(MonoItem<'tcx>, (Linkage, Visibility))> { + ) -> Vec<(MonoItem<'tcx>, MonoItemData)> { // The codegen tests rely on items being process in the same order as // they appear in the file, so for local items, we sort by node_id first #[derive(PartialEq, Eq, PartialOrd, Ord)] @@ -393,7 +413,7 @@ impl<'tcx> CodegenUnit<'tcx> { ) } - let mut items: Vec<_> = self.items().iter().map(|(&i, &l)| (i, l)).collect(); + let mut items: Vec<_> = self.items().iter().map(|(&i, &data)| (i, data)).collect(); items.sort_by_cached_key(|&(i, _)| item_sort_key(tcx, i)); items } @@ -504,27 +524,27 @@ impl<'tcx> CodegenUnitNameBuilder<'tcx> { // local crate's ID. Otherwise there can be collisions between CGUs // instantiating stuff for upstream crates. let local_crate_id = if cnum != LOCAL_CRATE { - let local_stable_crate_id = tcx.sess.local_stable_crate_id(); + let local_stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); format!("-in-{}.{:08x}", tcx.crate_name(LOCAL_CRATE), local_stable_crate_id) } else { String::new() }; - let stable_crate_id = tcx.sess.local_stable_crate_id(); + let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); format!("{}.{:08x}{}", tcx.crate_name(cnum), stable_crate_id, local_crate_id) }); - write!(cgu_name, "{}", crate_prefix).unwrap(); + write!(cgu_name, "{crate_prefix}").unwrap(); // Add the components for component in components { - write!(cgu_name, "-{}", component).unwrap(); + write!(cgu_name, "-{component}").unwrap(); } if let Some(special_suffix) = special_suffix { // We add a dot in here so it cannot clash with anything in a regular // Rust identifier - write!(cgu_name, ".{}", special_suffix).unwrap(); + write!(cgu_name, ".{special_suffix}").unwrap(); } Symbol::intern(&cgu_name) diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 62c3d8cf23913..773056e8a1799 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -124,14 +124,14 @@ fn dump_matched_mir_node<'tcx, F>( let def_path = ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id())); // ignore-tidy-odd-backticks the literal below is fine - write!(file, "// MIR for `{}", def_path)?; + write!(file, "// MIR for `{def_path}")?; match body.source.promoted { None => write!(file, "`")?, - Some(promoted) => write!(file, "::{:?}`", promoted)?, + Some(promoted) => write!(file, "::{promoted:?}`")?, } - writeln!(file, " {} {}", disambiguator, pass_name)?; + writeln!(file, " {disambiguator} {pass_name}")?; if let Some(ref layout) = body.generator_layout() { - writeln!(file, "/* generator_layout = {:#?} */", layout)?; + writeln!(file, "/* generator_layout = {layout:#?} */")?; } writeln!(file)?; extra_data(PassWhere::BeforeCFG, &mut file)?; @@ -169,7 +169,7 @@ fn dump_file_basename<'tcx>( ) -> String { let source = body.source; let promotion_id = match source.promoted { - Some(id) => format!("-{:?}", id), + Some(id) => format!("-{id:?}"), None => String::new(), }; @@ -203,8 +203,7 @@ fn dump_file_basename<'tcx>( }; format!( - "{}.{}{}{}{}.{}.{}", - crate_name, item_name, shim_disambiguator, promotion_id, pass_num, pass_name, disambiguator, + "{crate_name}.{item_name}{shim_disambiguator}{promotion_id}{pass_num}.{pass_name}.{disambiguator}", ) } @@ -215,7 +214,7 @@ fn dump_path(tcx: TyCtxt<'_>, basename: &str, extension: &str) -> PathBuf { let mut file_path = PathBuf::new(); file_path.push(Path::new(&tcx.sess.opts.unstable_opts.dump_mir_dir)); - let file_name = format!("{}.{}", basename, extension,); + let file_name = format!("{basename}.{extension}",); file_path.push(&file_name); @@ -233,12 +232,12 @@ fn create_dump_file_with_basename( fs::create_dir_all(parent).map_err(|e| { io::Error::new( e.kind(), - format!("IO error creating MIR dump directory: {:?}; {}", parent, e), + format!("IO error creating MIR dump directory: {parent:?}; {e}"), ) })?; } Ok(io::BufWriter::new(fs::File::create(&file_path).map_err(|e| { - io::Error::new(e.kind(), format!("IO error creating MIR dump file: {:?}; {}", file_path, e)) + io::Error::new(e.kind(), format!("IO error creating MIR dump file: {file_path:?}; {e}")) })?)) } @@ -346,21 +345,25 @@ where // Basic block label at the top. let cleanup_text = if data.is_cleanup { " (cleanup)" } else { "" }; - writeln!(w, "{}{:?}{}: {{", INDENT, block, cleanup_text)?; + writeln!(w, "{INDENT}{block:?}{cleanup_text}: {{")?; // List of statements in the middle. let mut current_location = Location { block, statement_index: 0 }; for statement in &data.statements { extra_data(PassWhere::BeforeLocation(current_location), w)?; - let indented_body = format!("{0}{0}{1:?};", INDENT, statement); - writeln!( - w, - "{:A$} // {}{}", - indented_body, - if tcx.sess.verbose() { format!("{:?}: ", current_location) } else { String::new() }, - comment(tcx, statement.source_info, body.span), - A = ALIGN, - )?; + let indented_body = format!("{INDENT}{INDENT}{statement:?};"); + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{:A$} // {}{}", + indented_body, + if tcx.sess.verbose() { format!("{current_location:?}: ") } else { String::new() }, + comment(tcx, statement.source_info), + A = ALIGN, + )?; + } else { + writeln!(w, "{indented_body}")?; + } write_extra(tcx, w, |visitor| { visitor.visit_statement(statement, current_location); @@ -374,14 +377,18 @@ where // Terminator at the bottom. extra_data(PassWhere::BeforeLocation(current_location), w)?; let indented_terminator = format!("{0}{0}{1:?};", INDENT, data.terminator().kind); - writeln!( - w, - "{:A$} // {}{}", - indented_terminator, - if tcx.sess.verbose() { format!("{:?}: ", current_location) } else { String::new() }, - comment(tcx, data.terminator().source_info, body.span), - A = ALIGN, - )?; + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{:A$} // {}{}", + indented_terminator, + if tcx.sess.verbose() { format!("{current_location:?}: ") } else { String::new() }, + comment(tcx, data.terminator().source_info), + A = ALIGN, + )?; + } else { + writeln!(w, "{indented_terminator}")?; + } write_extra(tcx, w, |visitor| { visitor.visit_terminator(data.terminator(), current_location); @@ -390,7 +397,7 @@ where extra_data(PassWhere::AfterLocation(current_location), w)?; extra_data(PassWhere::AfterTerminator(block), w)?; - writeln!(w, "{}}}", INDENT) + writeln!(w, "{INDENT}}}") } /// After we print the main statement, we sometimes dump extra @@ -400,10 +407,12 @@ fn write_extra<'tcx, F>(tcx: TyCtxt<'tcx>, write: &mut dyn Write, mut visit_op: where F: FnMut(&mut ExtraComments<'tcx>), { - let mut extra_comments = ExtraComments { tcx, comments: vec![] }; - visit_op(&mut extra_comments); - for comment in extra_comments.comments { - writeln!(write, "{:A$} // {}", "", comment, A = ALIGN)?; + if tcx.sess.opts.unstable_opts.mir_include_spans { + let mut extra_comments = ExtraComments { tcx, comments: vec![] }; + visit_op(&mut extra_comments); + for comment in extra_comments.comments { + writeln!(write, "{:A$} // {}", "", comment, A = ALIGN)?; + } } Ok(()) } @@ -443,27 +452,27 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> { self.tcx.sess.source_map().span_to_embeddable_string(*span) )); if let Some(user_ty) = user_ty { - self.push(&format!("+ user_ty: {:?}", user_ty)); + self.push(&format!("+ user_ty: {user_ty:?}")); } // FIXME: this is a poor version of `pretty_print_const_value`. let fmt_val = |val: &ConstValue<'tcx>| match val { ConstValue::ZeroSized => "".to_string(), - ConstValue::Scalar(s) => format!("Scalar({:?})", s), + ConstValue::Scalar(s) => format!("Scalar({s:?})"), ConstValue::Slice { .. } => "Slice(..)".to_string(), ConstValue::ByRef { .. } => "ByRef(..)".to_string(), }; let fmt_valtree = |valtree: &ty::ValTree<'tcx>| match valtree { - ty::ValTree::Leaf(leaf) => format!("ValTree::Leaf({:?})", leaf), + ty::ValTree::Leaf(leaf) => format!("ValTree::Leaf({leaf:?})"), ty::ValTree::Branch(_) => "ValTree::Branch(..)".to_string(), }; let val = match literal { ConstantKind::Ty(ct) => match ct.kind() { - ty::ConstKind::Param(p) => format!("Param({})", p), + ty::ConstKind::Param(p) => format!("Param({p})"), ty::ConstKind::Unevaluated(uv) => { - format!("Unevaluated({}, {:?})", self.tcx.def_path_str(uv.def), uv.substs,) + format!("Unevaluated({}, {:?})", self.tcx.def_path_str(uv.def), uv.args,) } ty::ConstKind::Value(val) => format!("Value({})", fmt_valtree(&val)), ty::ConstKind::Error(_) => "Error".to_string(), @@ -477,7 +486,7 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> { format!( "Unevaluated({}, {:?}, {:?})", self.tcx.def_path_str(uv.def), - uv.substs, + uv.args, uv.promoted, ) } @@ -498,22 +507,22 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> { self.super_rvalue(rvalue, location); if let Rvalue::Aggregate(kind, _) = rvalue { match **kind { - AggregateKind::Closure(def_id, substs) => { + AggregateKind::Closure(def_id, args) => { self.push("closure"); - self.push(&format!("+ def_id: {:?}", def_id)); - self.push(&format!("+ substs: {:#?}", substs)); + self.push(&format!("+ def_id: {def_id:?}")); + self.push(&format!("+ args: {args:#?}")); } - AggregateKind::Generator(def_id, substs, movability) => { + AggregateKind::Generator(def_id, args, movability) => { self.push("generator"); - self.push(&format!("+ def_id: {:?}", def_id)); - self.push(&format!("+ substs: {:#?}", substs)); - self.push(&format!("+ movability: {:?}", movability)); + self.push(&format!("+ def_id: {def_id:?}")); + self.push(&format!("+ args: {args:#?}")); + self.push(&format!("+ movability: {movability:?}")); } AggregateKind::Adt(_, _, _, Some(user_ty), _) => { self.push("adt"); - self.push(&format!("+ user_ty: {:?}", user_ty)); + self.push(&format!("+ user_ty: {user_ty:?}")); } _ => {} @@ -522,13 +531,8 @@ impl<'tcx> Visitor<'tcx> for ExtraComments<'tcx> { } } -fn comment(tcx: TyCtxt<'_>, SourceInfo { span, scope }: SourceInfo, function_span: Span) -> String { - let location = if tcx.sess.opts.unstable_opts.mir_pretty_relative_line_numbers { - tcx.sess.source_map().span_to_relative_line_string(span, function_span) - } else { - tcx.sess.source_map().span_to_embeddable_string(span) - }; - +fn comment(tcx: TyCtxt<'_>, SourceInfo { span, scope }: SourceInfo) -> String { + let location = tcx.sess.source_map().span_to_embeddable_string(span); format!("scope {} at {}", scope.index(), location,) } @@ -551,22 +555,21 @@ fn write_scope_tree( } let indented_debug_info = format!( - "{0:1$}debug {2} => {3:&<4$}{5:?};", - INDENT, - indent, - var_debug_info.name, - "", - var_debug_info.references as usize, - var_debug_info.value, + "{0:1$}debug {2} => {3:?};", + INDENT, indent, var_debug_info.name, var_debug_info.value, ); - writeln!( - w, - "{0:1$} // in {2}", - indented_debug_info, - ALIGN, - comment(tcx, var_debug_info.source_info, body.span), - )?; + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{0:1$} // in {2}", + indented_debug_info, + ALIGN, + comment(tcx, var_debug_info.source_info), + )?; + } else { + writeln!(w, "{indented_debug_info}")?; + } } // Local variable types. @@ -587,21 +590,25 @@ fn write_scope_tree( format!("{0:1$}let {2}{3:?}: {4:?}", INDENT, indent, mut_str, local, local_decl.ty); if let Some(user_ty) = &local_decl.user_ty { for user_ty in user_ty.projections() { - write!(indented_decl, " as {:?}", user_ty).unwrap(); + write!(indented_decl, " as {user_ty:?}").unwrap(); } } indented_decl.push(';'); let local_name = if local == RETURN_PLACE { " return place" } else { "" }; - writeln!( - w, - "{0:1$} //{2} in {3}", - indented_decl, - ALIGN, - local_name, - comment(tcx, local_decl.source_info, body.span), - )?; + if tcx.sess.opts.unstable_opts.mir_include_spans { + writeln!( + w, + "{0:1$} //{2} in {3}", + indented_decl, + ALIGN, + local_name, + comment(tcx, local_decl.source_info), + )?; + } else { + writeln!(w, "{indented_decl}",)?; + } } let Some(children) = scope_tree.get(&parent) else { @@ -627,16 +634,20 @@ fn write_scope_tree( let indented_header = format!("{0:1$}scope {2}{3} {{", "", indent, child.index(), special); - if let Some(span) = span { - writeln!( - w, - "{0:1$} // at {2}", - indented_header, - ALIGN, - tcx.sess.source_map().span_to_embeddable_string(span), - )?; + if tcx.sess.opts.unstable_opts.mir_include_spans { + if let Some(span) = span { + writeln!( + w, + "{0:1$} // at {2}", + indented_header, + ALIGN, + tcx.sess.source_map().span_to_embeddable_string(span), + )?; + } else { + writeln!(w, "{indented_header}")?; + } } else { - writeln!(w, "{}", indented_header)?; + writeln!(w, "{indented_header}")?; } write_scope_tree(tcx, body, scope_tree, w, child, depth + 1)?; @@ -823,7 +834,7 @@ fn write_allocation_endline(w: &mut dyn std::fmt::Write, ascii: &str) -> std::fm for _ in 0..(BYTES_PER_LINE - ascii.chars().count()) { write!(w, " ")?; } - writeln!(w, " │ {}", ascii) + writeln!(w, " │ {ascii}") } /// Number of bytes to print per allocation hex dump line. @@ -846,7 +857,7 @@ fn write_allocation_newline( /// The `prefix` argument allows callers to add an arbitrary prefix before each line (even if there /// is only one line). Note that your prefix should contain a trailing space as the lines are /// printed directly after it. -fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( +pub fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( tcx: TyCtxt<'tcx>, alloc: &Allocation, w: &mut dyn std::fmt::Write, @@ -859,7 +870,7 @@ fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( if num_lines > 0 { write!(w, "{}0x{:02$x} │ ", prefix, 0, pos_width)?; } else { - write!(w, "{}", prefix)?; + write!(w, "{prefix}")?; } let mut i = Size::ZERO; @@ -892,10 +903,10 @@ fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( let offset = Size::from_bytes(offset); let provenance_width = |bytes| bytes * 3; let ptr = Pointer::new(prov, offset); - let mut target = format!("{:?}", ptr); + let mut target = format!("{ptr:?}"); if target.len() > provenance_width(ptr_size.bytes_usize() - 1) { // This is too long, try to save some space. - target = format!("{:#?}", ptr); + target = format!("{ptr:#?}"); } if ((i - line_start) + ptr_size).bytes_usize() > BYTES_PER_LINE { // This branch handles the situation where a provenance starts in the current line @@ -914,10 +925,10 @@ fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( line_start = write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?; ascii.clear(); - write!(w, "{0:─^1$}╼", target, overflow_width)?; + write!(w, "{target:─^overflow_width$}╼")?; } else { oversized_ptr(&mut target, remainder_width); - write!(w, "╾{0:─^1$}", target, remainder_width)?; + write!(w, "╾{target:─^remainder_width$}")?; line_start = write_allocation_newline(w, line_start, &ascii, pos_width, prefix)?; write!(w, "{0:─^1$}╼", "", overflow_width)?; @@ -934,7 +945,7 @@ fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( let provenance_width = provenance_width(ptr_size.bytes_usize() - 1); oversized_ptr(&mut target, provenance_width); ascii.push('╾'); - write!(w, "╾{0:─^1$}╼", target, provenance_width)?; + write!(w, "╾{target:─^provenance_width$}╼")?; for _ in 0..ptr_size.bytes() - 2 { ascii.push('─'); } @@ -951,7 +962,7 @@ fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( // Format is similar to "oversized" above. let j = i.bytes_usize(); let c = alloc.inspect_with_uninit_and_ptr_outside_interpreter(j..j + 1)[0]; - write!(w, "╾{:02x}{:#?} (1 ptr byte)╼", c, prov)?; + write!(w, "╾{c:02x}{prov:#?} (1 ptr byte)╼")?; i += Size::from_bytes(1); } else if alloc .init_mask() @@ -963,7 +974,7 @@ fn write_allocation_bytes<'tcx, Prov: Provenance, Extra, Bytes: AllocBytes>( // Checked definedness (and thus range) and provenance. This access also doesn't // influence interpreter execution but is only for debugging. let c = alloc.inspect_with_uninit_and_ptr_outside_interpreter(j..j + 1)[0]; - write!(w, "{:02x}", c)?; + write!(w, "{c:02x}")?; if c.is_ascii_control() || c >= 0x80 { ascii.push('.'); } else { @@ -997,7 +1008,7 @@ fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn Write) -> io::Res _ => tcx.is_closure(def_id), }; match (kind, body.source.promoted) { - (_, Some(i)) => write!(w, "{:?} in ", i)?, + (_, Some(i)) => write!(w, "{i:?} in ")?, (DefKind::Const | DefKind::AssocConst, _) => write!(w, "const ")?, (DefKind::Static(hir::Mutability::Not), _) => write!(w, "static ")?, (DefKind::Static(hir::Mutability::Mut), _) => write!(w, "static mut ")?, @@ -1030,7 +1041,7 @@ fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn Write) -> io::Res if let Some(yield_ty) = body.yield_ty() { writeln!(w)?; - writeln!(w, "yields {}", yield_ty)?; + writeln!(w, "yields {yield_ty}")?; } write!(w, " ")?; diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index 53fd2dd23a705..71bec49af93e6 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -1,6 +1,6 @@ //! Values computed by queries that use MIR. -use crate::mir::ConstantKind; +use crate::mir::interpret::ConstValue; use crate::ty::{self, OpaqueHiddenType, Ty, TyCtxt}; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::UnordSet; @@ -9,6 +9,7 @@ use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_index::bit_set::BitMatrix; use rustc_index::{Idx, IndexVec}; +use rustc_span::symbol::Symbol; use rustc_span::Span; use rustc_target::abi::{FieldIdx, VariantIdx}; use smallvec::SmallVec; @@ -150,6 +151,9 @@ pub struct GeneratorLayout<'tcx> { /// The type of every local stored inside the generator. pub field_tys: IndexVec>, + /// The name for debuginfo. + pub field_names: IndexVec>, + /// Which of the above fields are in each variant. Note that one field may /// be stored in multiple variants. pub variant_fields: IndexVec>, @@ -190,11 +194,11 @@ impl Debug for GeneratorLayout<'_> { } impl Debug for GenVariantPrinter { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let variant_name = ty::GeneratorSubsts::variant_name(self.0); + let variant_name = ty::GeneratorArgs::variant_name(self.0); if fmt.alternate() { write!(fmt, "{:9}({:?})", variant_name, self.0) } else { - write!(fmt, "{}", variant_name) + write!(fmt, "{variant_name}") } } } @@ -261,10 +265,10 @@ pub struct ConstQualifs { /// `UniversalRegions::closure_mapping`.) Note the free regions in the /// closure's signature and captures are erased. /// -/// Example: If type check produces a closure with the closure substs: +/// Example: If type check produces a closure with the closure args: /// /// ```text -/// ClosureSubsts = [ +/// ClosureArgs = [ /// 'a, // From the parent. /// 'b, /// i8, // the "closure kind" @@ -276,7 +280,7 @@ pub struct ConstQualifs { /// We would "renumber" each free region to a unique vid, as follows: /// /// ```text -/// ClosureSubsts = [ +/// ClosureArgs = [ /// '1, // From the parent. /// '2, /// i8, // the "closure kind" @@ -413,7 +417,7 @@ impl<'tcx> ClosureOutlivesSubjectTy<'tcx> { ty::ReVar(vid) => { let br = ty::BoundRegion { var: ty::BoundVar::new(vid.index()), kind: ty::BrAnon(None) }; - tcx.mk_re_late_bound(depth, br) + ty::Region::new_late_bound(tcx, depth, br) } _ => bug!("unexpected region in ClosureOutlivesSubjectTy: {r:?}"), }); @@ -440,7 +444,7 @@ impl<'tcx> ClosureOutlivesSubjectTy<'tcx> { #[derive(Copy, Clone, Debug, HashStable)] pub struct DestructuredConstant<'tcx> { pub variant: Option, - pub fields: &'tcx [ConstantKind<'tcx>], + pub fields: &'tcx [(ConstValue<'tcx>, Ty<'tcx>)], } /// Coverage information summarized from a MIR if instrumented for source code coverage (see diff --git a/compiler/rustc_middle/src/mir/spanview.rs b/compiler/rustc_middle/src/mir/spanview.rs index 2165403da2671..20a9e6889e40a 100644 --- a/compiler/rustc_middle/src/mir/spanview.rs +++ b/compiler/rustc_middle/src/mir/spanview.rs @@ -3,7 +3,7 @@ use rustc_middle::hir; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; use rustc_session::config::MirSpanview; -use rustc_span::{BytePos, Pos, Span, SyntaxContext}; +use rustc_span::{BytePos, Pos, Span}; use std::cmp; use std::io::{self, Write}; @@ -15,8 +15,9 @@ const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT B const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET` const NEW_LINE_SPAN: &str = "\n"; const HEADER: &str = r#" - -"#; + + +"#; const START_BODY: &str = r#" "#; const FOOTER: &str = r#" @@ -158,10 +159,10 @@ where indent_to_initial_start_col, source_map.span_to_snippet(spanview_span).expect("function should have printable source") ); - writeln!(w, "{}", HEADER)?; - writeln!(w, "{}", title)?; - writeln!(w, "{}", STYLE_SECTION)?; - writeln!(w, "{}", START_BODY)?; + writeln!(w, "{HEADER}")?; + writeln!(w, "{title}")?; + writeln!(w, "{STYLE_SECTION}")?; + writeln!(w, "{START_BODY}")?; write!( w, r#"

"#)?; - writeln!(w, "{}", FOOTER)?; + writeln!(w, "{FOOTER}")?; Ok(()) } @@ -327,7 +328,7 @@ fn compute_block_span(data: &BasicBlockData<'_>, body_span: Span) -> Span { let mut span = data.terminator().source_info.span; for statement_span in data.statements.iter().map(|statement| statement.source_info.span) { // Only combine Spans from the root context, and within the function's body_span. - if statement_span.ctxt() == SyntaxContext::root() && body_span.contains(statement_span) { + if statement_span.ctxt().is_root() && body_span.contains(statement_span) { span = span.to(statement_span); } } @@ -560,17 +561,16 @@ where } for (i, line) in html_snippet.lines().enumerate() { if i > 0 { - write!(w, "{}", NEW_LINE_SPAN)?; + write!(w, "{NEW_LINE_SPAN}")?; } write!( w, - r#"{}"#, - maybe_alt_class, layer, maybe_title_attr, line + r#"{line}"# )?; } // Check for and translate trailing newlines, because `str::lines()` ignores them if html_snippet.ends_with('\n') { - write!(w, "{}", NEW_LINE_SPAN)?; + write!(w, "{NEW_LINE_SPAN}")?; } if layer == 1 { write!(w, "
")?; diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 21faf1958e911..be27bf75dbd15 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -7,8 +7,8 @@ use super::{BasicBlock, Constant, Local, SwitchTargets, UserTypeProjection}; use crate::mir::coverage::{CodeRegion, CoverageKind}; use crate::traits::Reveal; -use crate::ty::adjustment::PointerCast; -use crate::ty::subst::SubstsRef; +use crate::ty::adjustment::PointerCoercion; +use crate::ty::GenericArgsRef; use crate::ty::{self, List, Ty}; use crate::ty::{Region, UserTypeAnnotationIndex}; @@ -182,6 +182,16 @@ pub enum BorrowKind { /// We can also report errors with this kind of borrow differently. Shallow, + /// Data is mutable and not aliasable. + Mut { kind: MutBorrowKind }, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, TyEncodable, TyDecodable)] +#[derive(Hash, HashStable)] +pub enum MutBorrowKind { + Default, + /// This borrow arose from method-call auto-ref. (i.e., `adjustment::Adjust::Borrow`) + TwoPhaseBorrow, /// Data must be immutable but not aliasable. This kind of borrow /// cannot currently be expressed by the user and is used only in /// implicit closure bindings. It is needed when the closure is @@ -216,18 +226,14 @@ pub enum BorrowKind { /// user code, if awkward, but extra weird for closures, since the /// borrow is hidden. /// - /// So we introduce a "unique imm" borrow -- the referent is - /// immutable, but not aliasable. This solves the problem. For - /// simplicity, we don't give users the way to express this + /// So we introduce a `ClosureCapture` borrow -- user will not have to mark the variable + /// containing the mutable reference as `mut`, as they didn't ever + /// intend to mutate the mutable reference itself. We still mutable capture it in order to + /// mutate the pointed value through it (but not mutating the reference itself). + /// + /// This solves the problem. For simplicity, we don't give users the way to express this /// borrow, it's just used when translating closures. - Unique, - - /// Data is mutable and not aliasable. - Mut { - /// `true` if this borrow arose from method-call auto-ref - /// (i.e., `adjustment::Adjust::Borrow`). - allow_two_phase_borrow: bool, - }, + ClosureCapture, } /////////////////////////////////////////////////////////////////////////// @@ -507,6 +513,31 @@ pub struct CopyNonOverlapping<'tcx> { pub count: Operand<'tcx>, } +/// Represents how a `TerminatorKind::Call` was constructed, used for diagnostics +#[derive(Clone, Copy, TyEncodable, TyDecodable, Debug, PartialEq, Hash, HashStable)] +#[derive(TypeFoldable, TypeVisitable)] +pub enum CallSource { + /// This came from something such as `a > b` or `a + b`. In THIR, if `from_hir_call` + /// is false then this is the desugaring. + OverloadedOperator, + /// This was from comparison generated by a match, used by const-eval for better errors + /// when the comparison cannot be done in compile time. + /// + /// (see ) + MatchCmp, + /// Other types of desugaring that did not come from the HIR, but we don't care about + /// for diagnostics (yet). + Misc, + /// Normal function call, no special source + Normal, +} + +impl CallSource { + pub fn from_hir_call(self) -> bool { + matches!(self, CallSource::Normal) + } +} + /////////////////////////////////////////////////////////////////////////// // Terminators @@ -603,7 +634,11 @@ pub enum TerminatorKind<'tcx> { /// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to /// > the place or one of its "parents" occurred more recently than a move out of it. This does not /// > consider indirect assignments. - Drop { place: Place<'tcx>, target: BasicBlock, unwind: UnwindAction }, + /// + /// The `replace` flag indicates whether this terminator was created as part of an assignment. + /// This should only be used for diagnostic purposes, and does not have any operational + /// meaning. + Drop { place: Place<'tcx>, target: BasicBlock, unwind: UnwindAction, replace: bool }, /// Roughly speaking, evaluates the `func` operand and the arguments, and starts execution of /// the referred to function. The operand types must match the argument types of the function. @@ -629,11 +664,10 @@ pub enum TerminatorKind<'tcx> { target: Option, /// Action to be taken if the call unwinds. unwind: UnwindAction, - /// `true` if this is from a call in HIR rather than from an overloaded - /// operator. True for overloaded function call. - from_hir_call: bool, + /// Where this call came from in HIR/THIR. + call_source: CallSource, /// This `Span` is the span of the function, without the dot and receiver - /// (e.g. `foo(a, b)` in `x.foo(a, b)` + /// e.g. `foo(a, b)` in `x.foo(a, b)` fn_span: Span, }, @@ -792,7 +826,8 @@ pub enum UnwindAction { } /// Information about an assertion failure. -#[derive(Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq, TypeFoldable, TypeVisitable)] +#[derive(Clone, Hash, HashStable, PartialEq, Debug)] +#[derive(TyEncodable, TyDecodable, TypeFoldable, TypeVisitable)] pub enum AssertKind { BoundsCheck { len: O, index: O }, Overflow(BinOp, O, O), @@ -1015,10 +1050,6 @@ pub type PlaceElem<'tcx> = ProjectionElem>; /// there may be other effects: if the type has a validity constraint loading the place might be UB /// if the validity constraint is not met. /// -/// **Needs clarification:** Ralf proposes that loading a place not have side-effects. -/// This is what is implemented in miri today. Are these the semantics we want for MIR? Is this -/// something we can even decide without knowing more about Rust's memory model? -/// /// **Needs clarification:** Is loading a place that has its variant index set well-formed? Miri /// currently implements it, but it seems like this may be something to check against in the /// validator. @@ -1036,6 +1067,16 @@ pub enum Operand<'tcx> { /// in [UCG#188]. You should not emit MIR that may attempt a subsequent second load of this /// place without first re-initializing it. /// + /// **Needs clarification:** The operational impact of `Move` is unclear. Currently (both in + /// Miri and codegen) it has no effect at all unless it appears in an argument to `Call`; for + /// `Call` it allows the argument to be passed to the callee "in-place", i.e. the callee might + /// just get a reference to this place instead of a full copy. Miri implements this with a + /// combination of aliasing model "protectors" and putting `uninit` into the place. Ralf + /// proposes that we don't want these semantics for `Move` in regular assignments, because + /// loading a place should not have side-effects, and the aliasing model "protectors" are + /// inherently tied to a function call. Are these the semantics we want for MIR? Is this + /// something we can even decide without knowing more about Rust's memory model? + /// /// [UCG#188]: https://github.com/rust-lang/unsafe-code-guidelines/issues/188 Move(Place<'tcx>), @@ -1195,9 +1236,9 @@ pub enum CastKind { /// An address-to-pointer cast that picks up an exposed provenance. /// See the docs on `from_exposed_addr` for more details. PointerFromExposedAddress, - /// All sorts of pointer-to-pointer casts. Note that reference-to-raw-ptr casts are + /// Pointer related casts that are done by coercions. Note that reference-to-raw-ptr casts are /// translated into `&raw mut/const *r`, i.e., they are not actually casts. - Pointer(PointerCast), + PointerCoercion(PointerCoercion), /// Cast into a dyn* object. DynStar, IntToInt, @@ -1227,10 +1268,10 @@ pub enum AggregateKind<'tcx> { /// active field number and is present only for union expressions /// -- e.g., for a union expression `SomeUnion { c: .. }`, the /// active field index would identity the field `c` - Adt(DefId, VariantIdx, SubstsRef<'tcx>, Option, Option), + Adt(DefId, VariantIdx, GenericArgsRef<'tcx>, Option, Option), - Closure(DefId, SubstsRef<'tcx>), - Generator(DefId, SubstsRef<'tcx>, hir::Movability), + Closure(DefId, GenericArgsRef<'tcx>), + Generator(DefId, GenericArgsRef<'tcx>, hir::Movability), } #[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] @@ -1257,19 +1298,30 @@ pub enum UnOp { pub enum BinOp { /// The `+` operator (addition) Add, + /// Like `Add`, but with UB on overflow. (Integers only.) + AddUnchecked, /// The `-` operator (subtraction) Sub, + /// Like `Sub`, but with UB on overflow. (Integers only.) + SubUnchecked, /// The `*` operator (multiplication) Mul, + /// Like `Mul`, but with UB on overflow. (Integers only.) + MulUnchecked, /// The `/` operator (division) /// - /// Division by zero is UB, because the compiler should have inserted checks - /// prior to this. + /// For integer types, division by zero is UB, as is `MIN / -1` for signed. + /// The compiler should have inserted checks prior to this. + /// + /// Floating-point division by zero is safe, and does not need guards. Div, /// The `%` operator (modulus) /// - /// Using zero as the modulus (second operand) is UB, because the compiler - /// should have inserted checks prior to this. + /// For integer types, using zero as the modulus (second operand) is UB, + /// as is `MIN % -1` for signed. + /// The compiler should have inserted checks prior to this. + /// + /// Floating-point remainder by zero is safe, and does not need guards. Rem, /// The `^` operator (bitwise xor) BitXor, @@ -1281,10 +1333,17 @@ pub enum BinOp { /// /// The offset is truncated to the size of the first operand before shifting. Shl, + /// Like `Shl`, but is UB if the RHS >= LHS::BITS + ShlUnchecked, /// The `>>` operator (shift right) /// /// The offset is truncated to the size of the first operand before shifting. + /// + /// This is an arithmetic shift if the LHS is signed + /// and a logical shift if the LHS is unsigned. Shr, + /// Like `Shl`, but is UB if the RHS >= LHS::BITS + ShrUnchecked, /// The `==` operator (equality) Eq, /// The `<` operator (less than) diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index 5ca8241344832..f79697936d28c 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -35,7 +35,7 @@ impl<'tcx> PlaceTy<'tcx> { #[instrument(level = "debug", skip(tcx), ret)] pub fn field_ty(self, tcx: TyCtxt<'tcx>, f: FieldIdx) -> Ty<'tcx> { match self.ty.kind() { - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { let variant_def = match self.variant_index { None => adt_def.non_enum_variant(), Some(variant_index) => { @@ -44,7 +44,7 @@ impl<'tcx> PlaceTy<'tcx> { } }; let field_def = &variant_def.fields[f]; - field_def.ty(tcx, substs) + field_def.ty(tcx, args) } ty::Tuple(tys) => tys[f.index()], _ => bug!("extracting field of non-tuple non-adt: {:?}", self), @@ -95,11 +95,13 @@ impl<'tcx> PlaceTy<'tcx> { ProjectionElem::Subslice { from, to, from_end } => { PlaceTy::from_ty(match self.ty.kind() { ty::Slice(..) => self.ty, - ty::Array(inner, _) if !from_end => tcx.mk_array(*inner, (to - from) as u64), + ty::Array(inner, _) if !from_end => { + Ty::new_array(tcx, *inner, (to - from) as u64) + } ty::Array(inner, size) if from_end => { let size = size.eval_target_usize(tcx, param_env); let len = size - from - to; - tcx.mk_array(*inner, len) + Ty::new_array(tcx, *inner, len) } _ => bug!("cannot subslice non-array type: `{:?}`", self), }) @@ -162,16 +164,16 @@ impl<'tcx> Rvalue<'tcx> { match *self { Rvalue::Use(ref operand) => operand.ty(local_decls, tcx), Rvalue::Repeat(ref operand, count) => { - tcx.mk_array_with_const_len(operand.ty(local_decls, tcx), count) + Ty::new_array_with_const_len(tcx, operand.ty(local_decls, tcx), count) } Rvalue::ThreadLocalRef(did) => tcx.thread_local_ptr_ty(did), Rvalue::Ref(reg, bk, ref place) => { let place_ty = place.ty(local_decls, tcx).ty; - tcx.mk_ref(reg, ty::TypeAndMut { ty: place_ty, mutbl: bk.to_mutbl_lossy() }) + Ty::new_ref(tcx, reg, ty::TypeAndMut { ty: place_ty, mutbl: bk.to_mutbl_lossy() }) } Rvalue::AddressOf(mutability, ref place) => { let place_ty = place.ty(local_decls, tcx).ty; - tcx.mk_ptr(ty::TypeAndMut { ty: place_ty, mutbl: mutability }) + Ty::new_ptr(tcx, ty::TypeAndMut { ty: place_ty, mutbl: mutability }) } Rvalue::Len(..) => tcx.types.usize, Rvalue::Cast(.., ty) => ty, @@ -184,7 +186,7 @@ impl<'tcx> Rvalue<'tcx> { let lhs_ty = lhs.ty(local_decls, tcx); let rhs_ty = rhs.ty(local_decls, tcx); let ty = op.ty(tcx, lhs_ty, rhs_ty); - tcx.mk_tup(&[ty, tcx.types.bool]) + Ty::new_tup(tcx, &[ty, tcx.types.bool]) } Rvalue::UnaryOp(UnOp::Not | UnOp::Neg, ref operand) => operand.ty(local_decls, tcx), Rvalue::Discriminant(ref place) => place.ty(local_decls, tcx).ty.discriminant_ty(tcx), @@ -192,17 +194,17 @@ impl<'tcx> Rvalue<'tcx> { tcx.types.usize } Rvalue::Aggregate(ref ak, ref ops) => match **ak { - AggregateKind::Array(ty) => tcx.mk_array(ty, ops.len() as u64), + AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64), AggregateKind::Tuple => { - tcx.mk_tup_from_iter(ops.iter().map(|op| op.ty(local_decls, tcx))) + Ty::new_tup_from_iter(tcx, ops.iter().map(|op| op.ty(local_decls, tcx))) } - AggregateKind::Adt(did, _, substs, _, _) => tcx.type_of(did).subst(tcx, substs), - AggregateKind::Closure(did, substs) => tcx.mk_closure(did, substs), - AggregateKind::Generator(did, substs, movability) => { - tcx.mk_generator(did, substs, movability) + AggregateKind::Adt(did, _, args, _, _) => tcx.type_of(did).instantiate(tcx, args), + AggregateKind::Closure(did, args) => Ty::new_closure(tcx, did, args), + AggregateKind::Generator(did, args, movability) => { + Ty::new_generator(tcx, did, args, movability) } }, - Rvalue::ShallowInitBox(_, ty) => tcx.mk_box(ty), + Rvalue::ShallowInitBox(_, ty) => Ty::new_box(tcx, ty), Rvalue::CopyForDeref(ref place) => place.ty(local_decls, tcx).ty, } } @@ -235,8 +237,11 @@ impl<'tcx> BinOp { // FIXME: handle SIMD correctly match self { &BinOp::Add + | &BinOp::AddUnchecked | &BinOp::Sub + | &BinOp::SubUnchecked | &BinOp::Mul + | &BinOp::MulUnchecked | &BinOp::Div | &BinOp::Rem | &BinOp::BitXor @@ -246,7 +251,11 @@ impl<'tcx> BinOp { assert_eq!(lhs_ty, rhs_ty); lhs_ty } - &BinOp::Shl | &BinOp::Shr | &BinOp::Offset => { + &BinOp::Shl + | &BinOp::ShlUnchecked + | &BinOp::Shr + | &BinOp::ShrUnchecked + | &BinOp::Offset => { lhs_ty // lhs_ty can be != rhs_ty } &BinOp::Eq | &BinOp::Lt | &BinOp::Le | &BinOp::Ne | &BinOp::Ge | &BinOp::Gt => { @@ -262,11 +271,6 @@ impl BorrowKind { BorrowKind::Mut { .. } => hir::Mutability::Mut, BorrowKind::Shared => hir::Mutability::Not, - // We have no type corresponding to a unique imm borrow, so - // use `&mut`. It gives all the capabilities of a `&uniq` - // and hence is a safe "over approximation". - BorrowKind::Unique => hir::Mutability::Mut, - // We have no type corresponding to a shallow borrow, so use // `&` as an approximation. BorrowKind::Shallow => hir::Mutability::Not, @@ -293,7 +297,14 @@ impl BinOp { BinOp::Gt => hir::BinOpKind::Gt, BinOp::Le => hir::BinOpKind::Le, BinOp::Ge => hir::BinOpKind::Ge, - BinOp::Offset => unreachable!(), + BinOp::AddUnchecked + | BinOp::SubUnchecked + | BinOp::MulUnchecked + | BinOp::ShlUnchecked + | BinOp::ShrUnchecked + | BinOp::Offset => { + unreachable!() + } } } } diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 2c6126cdd29cd..1f878d23b4433 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -10,6 +10,7 @@ use std::iter; use std::slice; pub use super::query::*; +use super::*; #[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)] pub struct SwitchTargets { @@ -105,7 +106,7 @@ pub struct Terminator<'tcx> { pub kind: TerminatorKind<'tcx>, } -pub type Successors<'a> = impl Iterator + 'a; +pub type Successors<'a> = impl DoubleEndedIterator + 'a; pub type SuccessorsMut<'a> = iter::Chain, slice::IterMut<'a, BasicBlock>>; @@ -272,14 +273,15 @@ impl<'tcx> Debug for TerminatorKind<'tcx> { let unwind = match self.unwind() { // Not needed or included in successors - None | Some(UnwindAction::Continue) | Some(UnwindAction::Cleanup(_)) => None, + None | Some(UnwindAction::Cleanup(_)) => None, + Some(UnwindAction::Continue) => Some("unwind continue"), Some(UnwindAction::Unreachable) => Some("unwind unreachable"), Some(UnwindAction::Terminate) => Some("unwind terminate"), }; match (successor_count, unwind) { (0, None) => Ok(()), - (0, Some(unwind)) => write!(fmt, " -> {}", unwind), + (0, Some(unwind)) => write!(fmt, " -> {unwind}"), (1, None) => write!(fmt, " -> {:?}", self.successors().next().unwrap()), _ => { write!(fmt, " -> [")?; @@ -306,22 +308,22 @@ impl<'tcx> TerminatorKind<'tcx> { use self::TerminatorKind::*; match self { Goto { .. } => write!(fmt, "goto"), - SwitchInt { discr, .. } => write!(fmt, "switchInt({:?})", discr), + SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), Return => write!(fmt, "return"), GeneratorDrop => write!(fmt, "generator_drop"), Resume => write!(fmt, "resume"), Terminate => write!(fmt, "abort"), - Yield { value, resume_arg, .. } => write!(fmt, "{:?} = yield({:?})", resume_arg, value), + Yield { value, resume_arg, .. } => write!(fmt, "{resume_arg:?} = yield({value:?})"), Unreachable => write!(fmt, "unreachable"), - Drop { place, .. } => write!(fmt, "drop({:?})", place), + Drop { place, .. } => write!(fmt, "drop({place:?})"), Call { func, args, destination, .. } => { - write!(fmt, "{:?} = ", destination)?; - write!(fmt, "{:?}(", func)?; + write!(fmt, "{destination:?} = ")?; + write!(fmt, "{func:?}(")?; for (index, arg) in args.iter().enumerate() { if index > 0 { write!(fmt, ", ")?; } - write!(fmt, "{:?}", arg)?; + write!(fmt, "{arg:?}")?; } write!(fmt, ")") } @@ -330,7 +332,7 @@ impl<'tcx> TerminatorKind<'tcx> { if !expected { write!(fmt, "!")?; } - write!(fmt, "{:?}, ", cond)?; + write!(fmt, "{cond:?}, ")?; msg.fmt_assert_args(fmt)?; write!(fmt, ")") } @@ -343,7 +345,7 @@ impl<'tcx> TerminatorKind<'tcx> { let print_late = |&late| if late { "late" } else { "" }; match op { InlineAsmOperand::In { reg, value } => { - write!(fmt, "in({}) {:?}", reg, value)?; + write!(fmt, "in({reg}) {value:?}")?; } InlineAsmOperand::Out { reg, late, place: Some(place) } => { write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; @@ -370,17 +372,17 @@ impl<'tcx> TerminatorKind<'tcx> { write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; } InlineAsmOperand::Const { value } => { - write!(fmt, "const {:?}", value)?; + write!(fmt, "const {value:?}")?; } InlineAsmOperand::SymFn { value } => { - write!(fmt, "sym_fn {:?}", value)?; + write!(fmt, "sym_fn {value:?}")?; } InlineAsmOperand::SymStatic { def_id } => { - write!(fmt, "sym_static {:?}", def_id)?; + write!(fmt, "sym_static {def_id:?}")?; } } } - write!(fmt, ", options({:?}))", options) + write!(fmt, ", options({options:?}))") } } } @@ -429,3 +431,108 @@ impl<'tcx> TerminatorKind<'tcx> { } } } + +#[derive(Copy, Clone, Debug)] +pub enum TerminatorEdges<'mir, 'tcx> { + /// For terminators that have no successor, like `return`. + None, + /// For terminators that a single successor, like `goto`, and `assert` without cleanup block. + Single(BasicBlock), + /// For terminators that two successors, `assert` with cleanup block and `falseEdge`. + Double(BasicBlock, BasicBlock), + /// Special action for `Yield`, `Call` and `InlineAsm` terminators. + AssignOnReturn { + return_: Option, + unwind: UnwindAction, + place: CallReturnPlaces<'mir, 'tcx>, + }, + /// Special edge for `SwitchInt`. + SwitchInt { targets: &'mir SwitchTargets, discr: &'mir Operand<'tcx> }, +} + +/// List of places that are written to after a successful (non-unwind) return +/// from a `Call`, `Yield` or `InlineAsm`. +#[derive(Copy, Clone, Debug)] +pub enum CallReturnPlaces<'a, 'tcx> { + Call(Place<'tcx>), + Yield(Place<'tcx>), + InlineAsm(&'a [InlineAsmOperand<'tcx>]), +} + +impl<'tcx> CallReturnPlaces<'_, 'tcx> { + pub fn for_each(&self, mut f: impl FnMut(Place<'tcx>)) { + match *self { + Self::Call(place) | Self::Yield(place) => f(place), + Self::InlineAsm(operands) => { + for op in operands { + match *op { + InlineAsmOperand::Out { place: Some(place), .. } + | InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place), + _ => {} + } + } + } + } + } +} + +impl<'tcx> Terminator<'tcx> { + pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { + self.kind.edges() + } +} + +impl<'tcx> TerminatorKind<'tcx> { + pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { + use TerminatorKind::*; + match *self { + Return | Resume | Terminate | GeneratorDrop | Unreachable => TerminatorEdges::None, + + Goto { target } => TerminatorEdges::Single(target), + + Assert { target, unwind, expected: _, msg: _, cond: _ } + | Drop { target, unwind, place: _, replace: _ } + | FalseUnwind { real_target: target, unwind } => match unwind { + UnwindAction::Cleanup(unwind) => TerminatorEdges::Double(target, unwind), + UnwindAction::Continue | UnwindAction::Terminate | UnwindAction::Unreachable => { + TerminatorEdges::Single(target) + } + }, + + FalseEdge { real_target, imaginary_target } => { + TerminatorEdges::Double(real_target, imaginary_target) + } + + Yield { resume: target, drop, resume_arg, value: _ } => { + TerminatorEdges::AssignOnReturn { + return_: Some(target), + unwind: drop.map_or(UnwindAction::Terminate, UnwindAction::Cleanup), + place: CallReturnPlaces::Yield(resume_arg), + } + } + + Call { unwind, destination, target, func: _, args: _, fn_span: _, call_source: _ } => { + TerminatorEdges::AssignOnReturn { + return_: target, + unwind, + place: CallReturnPlaces::Call(destination), + } + } + + InlineAsm { + template: _, + ref operands, + options: _, + line_spans: _, + destination, + unwind, + } => TerminatorEdges::AssignOnReturn { + return_: destination, + unwind, + place: CallReturnPlaces::InlineAsm(operands), + }, + + SwitchInt { ref targets, ref discr } => TerminatorEdges::SwitchInt { targets, discr }, + } + } +} diff --git a/compiler/rustc_middle/src/mir/traversal.rs b/compiler/rustc_middle/src/mir/traversal.rs index 7d247eeb656c2..ec16a8470c412 100644 --- a/compiler/rustc_middle/src/mir/traversal.rs +++ b/compiler/rustc_middle/src/mir/traversal.rs @@ -149,7 +149,7 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> { // B C // | | // | | - // D | + // | D // \ / // \ / // E @@ -159,26 +159,26 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> { // // When the first call to `traverse_successor` happens, the following happens: // - // [(B, [D]), // `B` taken from the successors of `A`, pushed to the - // // top of the stack along with the successors of `B` - // (A, [C])] + // [(C, [D]), // `C` taken from the successors of `A`, pushed to the + // // top of the stack along with the successors of `C` + // (A, [B])] // - // [(D, [E]), // `D` taken from successors of `B`, pushed to stack - // (B, []), - // (A, [C])] + // [(D, [E]), // `D` taken from successors of `C`, pushed to stack + // (C, []), + // (A, [B])] // // [(E, []), // `E` taken from successors of `D`, pushed to stack // (D, []), - // (B, []), - // (A, [C])] + // (C, []), + // (A, [B])] // // Now that the top of the stack has no successors we can traverse, each item will - // be popped off during iteration until we get back to `A`. This yields [E, D, B]. + // be popped off during iteration until we get back to `A`. This yields [E, D, C]. // - // When we yield `B` and call `traverse_successor`, we push `C` to the stack, but + // When we yield `C` and call `traverse_successor`, we push `B` to the stack, but // since we've already visited `E`, that child isn't added to the stack. The last - // two iterations yield `C` and finally `A` for a final traversal of [E, D, B, C, A] - while let Some(&mut (_, ref mut iter)) = self.visit_stack.last_mut() && let Some(bb) = iter.next() { + // two iterations yield `B` and finally `A` for a final traversal of [E, D, C, B, A] + while let Some(&mut (_, ref mut iter)) = self.visit_stack.last_mut() && let Some(bb) = iter.next_back() { if self.visited.insert(bb) { if let Some(term) = &self.basic_blocks[bb].terminator { self.visit_stack.push((bb, term.successors())); @@ -188,10 +188,6 @@ impl<'a, 'tcx> Postorder<'a, 'tcx> { } } -pub fn postorder<'a, 'tcx>(body: &'a Body<'tcx>) -> Postorder<'a, 'tcx> { - Postorder::new(&body.basic_blocks, START_BLOCK) -} - impl<'a, 'tcx> Iterator for Postorder<'a, 'tcx> { type Item = (BasicBlock, &'a BasicBlockData<'tcx>); @@ -219,6 +215,17 @@ impl<'a, 'tcx> Iterator for Postorder<'a, 'tcx> { } } +/// Creates an iterator over the `Body`'s basic blocks, that: +/// - returns basic blocks in a postorder, +/// - traverses the `BasicBlocks` CFG cache's reverse postorder backwards, and does not cache the +/// postorder itself. +pub fn postorder<'a, 'tcx>( + body: &'a Body<'tcx>, +) -> impl Iterator)> + ExactSizeIterator + DoubleEndedIterator +{ + reverse_postorder(body).rev() +} + /// Reverse postorder traversal of a graph /// /// Reverse postorder is the reverse order of a postorder traversal. @@ -295,34 +302,12 @@ pub fn reachable_as_bitset(body: &Body<'_>) -> BitSet { iter.visited } -#[derive(Clone)] -pub struct ReversePostorderIter<'a, 'tcx> { +/// Creates an iterator over the `Body`'s basic blocks, that: +/// - returns basic blocks in a reverse postorder, +/// - makes use of the `BasicBlocks` CFG cache's reverse postorder. +pub fn reverse_postorder<'a, 'tcx>( body: &'a Body<'tcx>, - blocks: &'a [BasicBlock], - idx: usize, -} - -impl<'a, 'tcx> Iterator for ReversePostorderIter<'a, 'tcx> { - type Item = (BasicBlock, &'a BasicBlockData<'tcx>); - - fn next(&mut self) -> Option<(BasicBlock, &'a BasicBlockData<'tcx>)> { - if self.idx == 0 { - return None; - } - self.idx -= 1; - - self.blocks.get(self.idx).map(|&bb| (bb, &self.body[bb])) - } - - fn size_hint(&self) -> (usize, Option) { - (self.idx, Some(self.idx)) - } -} - -impl<'a, 'tcx> ExactSizeIterator for ReversePostorderIter<'a, 'tcx> {} - -pub fn reverse_postorder<'a, 'tcx>(body: &'a Body<'tcx>) -> ReversePostorderIter<'a, 'tcx> { - let blocks = body.basic_blocks.postorder(); - let len = blocks.len(); - ReversePostorderIter { body, blocks, idx: len } +) -> impl Iterator)> + ExactSizeIterator + DoubleEndedIterator +{ + body.basic_blocks.reverse_postorder().iter().map(|&bb| (bb, &body.basic_blocks[bb])) } diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 596dd80bf4874..069b385916841 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -63,7 +63,7 @@ //! `is_cleanup` above. use crate::mir::*; -use crate::ty::subst::SubstsRef; +use crate::ty::GenericArgsRef; use crate::ty::{self, CanonicalUserTypeAnnotation, Ty}; use rustc_span::Span; @@ -245,12 +245,12 @@ macro_rules! make_mir_visitor { self.super_region(region); } - fn visit_substs( + fn visit_args( &mut self, - substs: & $($mutability)? SubstsRef<'tcx>, + args: & $($mutability)? GenericArgsRef<'tcx>, _: Location, ) { - self.super_substs(substs); + self.super_args(args); } fn visit_local_decl( @@ -335,7 +335,7 @@ macro_rules! make_mir_visitor { self.visit_span($(& $mutability)? *callsite_span); - let ty::Instance { def: callee_def, substs: callee_substs } = callee; + let ty::Instance { def: callee_def, args: callee_args } = callee; match callee_def { ty::InstanceDef::Item(_def_id) => {} @@ -355,7 +355,7 @@ macro_rules! make_mir_visitor { self.visit_ty($(& $mutability)? *ty, TyContext::Location(location)); } } - self.visit_substs(callee_substs, location); + self.visit_args(callee_args, location); } if let Some(inlined_parent_scope) = inlined_parent_scope { self.visit_source_scope($(& $mutability)? *inlined_parent_scope); @@ -504,6 +504,7 @@ macro_rules! make_mir_visitor { place, target: _, unwind: _, + replace: _, } => { self.visit_place( place, @@ -518,7 +519,7 @@ macro_rules! make_mir_visitor { destination, target: _, unwind: _, - from_hir_call: _, + call_source: _, fn_span: _ } => { self.visit_operand(func, location); @@ -649,9 +650,6 @@ macro_rules! make_mir_visitor { BorrowKind::Shallow => PlaceContext::NonMutatingUse( NonMutatingUseContext::ShallowBorrow ), - BorrowKind::Unique => PlaceContext::NonMutatingUse( - NonMutatingUseContext::UniqueBorrow - ), BorrowKind::Mut { .. } => PlaceContext::MutatingUse(MutatingUseContext::Borrow), }; @@ -723,24 +721,24 @@ macro_rules! make_mir_visitor { AggregateKind::Adt( _adt_def, _variant_index, - substs, - _user_substs, + args, + _user_args, _active_field_index ) => { - self.visit_substs(substs, location); + self.visit_args(args, location); } AggregateKind::Closure( _, - closure_substs + closure_args ) => { - self.visit_substs(closure_substs, location); + self.visit_args(closure_args, location); } AggregateKind::Generator( _, - generator_substs, + generator_args, _movability, ) => { - self.visit_substs(generator_substs, location); + self.visit_args(generator_args, location); } } @@ -842,7 +840,6 @@ macro_rules! make_mir_visitor { source_info, value, argument_index: _, - references: _, } = var_debug_info; self.visit_source_info(source_info); @@ -935,7 +932,7 @@ macro_rules! make_mir_visitor { fn super_region(&mut self, _region: $(& $mutability)? ty::Region<'tcx>) { } - fn super_substs(&mut self, _substs: & $($mutability)? SubstsRef<'tcx>) { + fn super_args(&mut self, _args: & $($mutability)? GenericArgsRef<'tcx>) { } // Convenience methods @@ -1135,13 +1132,12 @@ macro_rules! visit_place_fns { fn visit_projection_elem( &mut self, - local: Local, - proj_base: &[PlaceElem<'tcx>], + place_ref: PlaceRef<'tcx>, elem: PlaceElem<'tcx>, context: PlaceContext, location: Location, ) { - self.super_projection_elem(local, proj_base, elem, context, location); + self.super_projection_elem(place_ref, elem, context, location); } fn super_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { @@ -1170,15 +1166,13 @@ macro_rules! visit_place_fns { location: Location, ) { for (base, elem) in place_ref.iter_projections().rev() { - let base_proj = base.projection; - self.visit_projection_elem(place_ref.local, base_proj, elem, context, location); + self.visit_projection_elem(base, elem, context, location); } } fn super_projection_elem( &mut self, - _local: Local, - _proj_base: &[PlaceElem<'tcx>], + _place_ref: PlaceRef<'tcx>, elem: PlaceElem<'tcx>, _context: PlaceContext, location: Location, @@ -1264,8 +1258,6 @@ pub enum NonMutatingUseContext { SharedBorrow, /// Shallow borrow. ShallowBorrow, - /// Unique borrow. - UniqueBorrow, /// AddressOf for *const pointer. AddressOf, /// PlaceMention statement. @@ -1344,9 +1336,7 @@ impl PlaceContext { matches!( self, PlaceContext::NonMutatingUse( - NonMutatingUseContext::SharedBorrow - | NonMutatingUseContext::ShallowBorrow - | NonMutatingUseContext::UniqueBorrow + NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::ShallowBorrow ) | PlaceContext::MutatingUse(MutatingUseContext::Borrow) ) } diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index fd02a16130fc5..348f79ed6a800 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -55,6 +55,10 @@ impl EraseType for &'_ ty::List { type Result = [u8; size_of::<*const ()>()]; } +impl EraseType for &'_ rustc_index::IndexSlice { + type Result = [u8; size_of::<&'static rustc_index::IndexSlice>()]; +} + impl EraseType for Result<&'_ T, traits::query::NoSolution> { type Result = [u8; size_of::>()]; } @@ -67,8 +71,8 @@ impl EraseType for Result<&'_ T, traits::CodegenObligationError> { type Result = [u8; size_of::>()]; } -impl EraseType for Result<&'_ T, ty::layout::FnAbiError<'_>> { - type Result = [u8; size_of::>>()]; +impl EraseType for Result<&'_ T, &'_ ty::layout::FnAbiError<'_>> { + type Result = [u8; size_of::>>()]; } impl EraseType for Result<(&'_ T, rustc_middle::thir::ExprId), rustc_errors::ErrorGuaranteed> { @@ -92,15 +96,17 @@ impl EraseType for Result, traits::query::NoSolution> { type Result = [u8; size_of::, traits::query::NoSolution>>()]; } -impl EraseType for Result> { - type Result = [u8; size_of::>>()]; +impl EraseType for Result> { + type Result = [u8; size_of::>>()]; } -impl EraseType for Result>, ty::layout::LayoutError<'_>> { +impl EraseType + for Result>, &ty::layout::LayoutError<'_>> +{ type Result = [u8; size_of::< Result< rustc_target::abi::TyAndLayout<'static, Ty<'static>>, - ty::layout::LayoutError<'static>, + &'static ty::layout::LayoutError<'static>, >, >()]; } @@ -229,6 +235,7 @@ trivial! { rustc_hir::def_id::DefId, rustc_hir::def_id::DefIndex, rustc_hir::def_id::LocalDefId, + rustc_hir::def_id::LocalModDefId, rustc_hir::def::DefKind, rustc_hir::Defaultness, rustc_hir::definitions::DefKey, @@ -309,7 +316,6 @@ tcx_lifetime! { rustc_middle::mir::interpret::ConstValue, rustc_middle::mir::interpret::GlobalId, rustc_middle::mir::interpret::LitToConstInput, - rustc_middle::traits::ChalkEnvironmentAndGoal, rustc_middle::traits::query::MethodAutoderefStepsResult, rustc_middle::traits::query::type_op::AscribeUserType, rustc_middle::traits::query::type_op::Eq, @@ -317,7 +323,7 @@ tcx_lifetime! { rustc_middle::traits::query::type_op::Subtype, rustc_middle::ty::AdtDef, rustc_middle::ty::AliasTy, - rustc_middle::ty::Clause, + rustc_middle::ty::ClauseKind, rustc_middle::ty::ClosureTypeInfo, rustc_middle::ty::Const, rustc_middle::ty::DestructuredConst, diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs index fa62b7f32b1a8..01bdc4c9904e5 100644 --- a/compiler/rustc_middle/src/query/keys.rs +++ b/compiler/rustc_middle/src/query/keys.rs @@ -2,12 +2,13 @@ use crate::infer::canonical::Canonical; use crate::mir; +use crate::mir::interpret::ConstValue; use crate::traits; use crate::ty::fast_reject::SimplifiedType; use crate::ty::layout::{TyAndLayout, ValidityRequirement}; -use crate::ty::subst::{GenericArg, SubstsRef}; use crate::ty::{self, Ty, TyCtxt}; -use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; +use crate::ty::{GenericArg, GenericArgsRef}; +use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, ModDefId, LOCAL_CRATE}; use rustc_hir::hir_id::{HirId, OwnerId}; use rustc_query_system::query::{DefaultCacheSelector, SingleCacheSelector, VecCacheSelector}; use rustc_span::symbol::{Ident, Symbol}; @@ -174,6 +175,41 @@ impl AsLocalKey for DefId { } } +impl Key for LocalModDefId { + type CacheSelector = DefaultCacheSelector; + + fn default_span(&self, tcx: TyCtxt<'_>) -> Span { + tcx.def_span(*self) + } + + #[inline(always)] + fn key_as_def_id(&self) -> Option { + Some(self.to_def_id()) + } +} + +impl Key for ModDefId { + type CacheSelector = DefaultCacheSelector; + + fn default_span(&self, tcx: TyCtxt<'_>) -> Span { + tcx.def_span(*self) + } + + #[inline(always)] + fn key_as_def_id(&self) -> Option { + Some(self.to_def_id()) + } +} + +impl AsLocalKey for ModDefId { + type LocalKey = LocalModDefId; + + #[inline(always)] + fn as_local_key(&self) -> Option { + self.as_local() + } +} + impl Key for SimplifiedType { type CacheSelector = DefaultCacheSelector; @@ -285,7 +321,7 @@ impl Key for (DefId, SimplifiedType) { } } -impl<'tcx> Key for SubstsRef<'tcx> { +impl<'tcx> Key for GenericArgsRef<'tcx> { type CacheSelector = DefaultCacheSelector; fn default_span(&self, _: TyCtxt<'_>) -> Span { @@ -293,7 +329,7 @@ impl<'tcx> Key for SubstsRef<'tcx> { } } -impl<'tcx> Key for (DefId, SubstsRef<'tcx>) { +impl<'tcx> Key for (DefId, GenericArgsRef<'tcx>) { type CacheSelector = DefaultCacheSelector; fn default_span(&self, tcx: TyCtxt<'_>) -> Span { @@ -309,7 +345,7 @@ impl<'tcx> Key for (ty::UnevaluatedConst<'tcx>, ty::UnevaluatedConst<'tcx>) { } } -impl<'tcx> Key for (LocalDefId, DefId, SubstsRef<'tcx>) { +impl<'tcx> Key for (LocalDefId, DefId, GenericArgsRef<'tcx>) { type CacheSelector = DefaultCacheSelector; fn default_span(&self, tcx: TyCtxt<'_>) -> Span { @@ -317,11 +353,11 @@ impl<'tcx> Key for (LocalDefId, DefId, SubstsRef<'tcx>) { } } -impl<'tcx> Key for (ty::ParamEnv<'tcx>, ty::PolyTraitRef<'tcx>) { +impl<'tcx> Key for (ty::ParamEnv<'tcx>, ty::TraitRef<'tcx>) { type CacheSelector = DefaultCacheSelector; fn default_span(&self, tcx: TyCtxt<'_>) -> Span { - tcx.def_span(self.1.def_id()) + tcx.def_span(self.1.def_id) } } @@ -333,6 +369,14 @@ impl<'tcx> Key for (ty::Const<'tcx>, FieldIdx) { } } +impl<'tcx> Key for (ConstValue<'tcx>, Ty<'tcx>) { + type CacheSelector = DefaultCacheSelector; + + fn default_span(&self, _: TyCtxt<'_>) -> Span { + DUMMY_SP + } +} + impl<'tcx> Key for mir::interpret::ConstAlloc<'tcx> { type CacheSelector = DefaultCacheSelector; @@ -420,7 +464,7 @@ impl<'tcx> Key for (Ty<'tcx>, Ty<'tcx>) { } } -impl<'tcx> Key for &'tcx ty::List> { +impl<'tcx> Key for &'tcx ty::List> { type CacheSelector = DefaultCacheSelector; fn default_span(&self, _: TyCtxt<'_>) -> Span { @@ -478,7 +522,7 @@ impl Key for (Symbol, u32, u32) { } } -impl<'tcx> Key for (DefId, Ty<'tcx>, SubstsRef<'tcx>, ty::ParamEnv<'tcx>) { +impl<'tcx> Key for (DefId, Ty<'tcx>, GenericArgsRef<'tcx>, ty::ParamEnv<'tcx>) { type CacheSelector = DefaultCacheSelector; fn default_span(&self, _tcx: TyCtxt<'_>) -> Span { diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 1528be42f6a18..94ae0dcb51728 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -39,12 +39,11 @@ use crate::traits::query::{ }; use crate::traits::specialization_graph; use crate::traits::{ - CanonicalChalkEnvironmentAndGoal, CodegenObligationError, EvaluationResult, ImplSource, - ObjectSafetyViolation, ObligationCause, OverflowError, WellFormedLoc, + CodegenObligationError, EvaluationResult, ImplSource, ObjectSafetyViolation, ObligationCause, + OverflowError, WellFormedLoc, }; use crate::ty::fast_reject::SimplifiedType; use crate::ty::layout::ValidityRequirement; -use crate::ty::subst::{GenericArg, SubstsRef}; use crate::ty::util::AlwaysRequiresDrop; use crate::ty::GeneratorDiagnosticData; use crate::ty::TyCtxtFeed; @@ -52,9 +51,10 @@ use crate::ty::{ self, print::describe_as_module, CrateInherentImpls, ParamEnvAnd, Ty, TyCtxt, UnusedGenericParams, }; +use crate::ty::{GenericArg, GenericArgsRef}; use rustc_arena::TypedArena; use rustc_ast as ast; -use rustc_ast::expand::allocator::AllocatorKind; +use rustc_ast::expand::{allocator::AllocatorKind, StrippedCfgItem}; use rustc_attr as attr; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; @@ -67,7 +67,7 @@ use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir::def::{DefKind, DocLinkResMap}; use rustc_hir::def_id::{ - CrateNum, DefId, DefIdMap, DefIdSet, LocalDefId, LocalDefIdMap, LocalDefIdSet, + CrateNum, DefId, DefIdMap, DefIdSet, LocalDefId, LocalDefIdMap, LocalDefIdSet, LocalModDefId, }; use rustc_hir::lang_items::{LangItem, LanguageItems}; use rustc_hir::{Crate, ItemLocalId, TraitCandidate}; @@ -167,7 +167,7 @@ rustc_queries! { /// /// This can be conveniently accessed by `tcx.hir().visit_item_likes_in_module`. /// Avoid calling this query directly. - query hir_module_items(key: LocalDefId) -> &'tcx rustc_middle::hir::ModuleItems { + query hir_module_items(key: LocalModDefId) -> &'tcx rustc_middle::hir::ModuleItems { arena_cache desc { |tcx| "getting HIR module items in `{}`", tcx.def_path_str(key) } cache_on_disk_if { true } @@ -231,7 +231,7 @@ rustc_queries! { action = { use rustc_hir::def::DefKind; match tcx.def_kind(key) { - DefKind::TyAlias => "expanding type alias", + DefKind::TyAlias { .. } => "expanding type alias", DefKind::TraitAlias => "expanding trait alias", _ => "computing type of", } @@ -346,7 +346,7 @@ rustc_queries! { /// `key` is the `DefId` of the associated type or opaque type. /// /// Bounds from the parent (e.g. with nested impl trait) are not included. - query explicit_item_bounds(key: DefId) -> ty::EarlyBinder<&'tcx [(ty::Predicate<'tcx>, Span)]> { + query explicit_item_bounds(key: DefId) -> ty::EarlyBinder<&'tcx [(ty::Clause<'tcx>, Span)]> { desc { |tcx| "finding item bounds for `{}`", tcx.def_path_str(key) } cache_on_disk_if { key.is_local() } separate_provide_extern @@ -373,7 +373,7 @@ rustc_queries! { /// ``` /// /// Bounds from the parent (e.g. with nested impl trait) are not included. - query item_bounds(key: DefId) -> ty::EarlyBinder<&'tcx ty::List>> { + query item_bounds(key: DefId) -> ty::EarlyBinder<&'tcx ty::List>> { desc { |tcx| "elaborating item bounds for `{}`", tcx.def_path_str(key) } } @@ -388,7 +388,6 @@ rustc_queries! { } query shallow_lint_levels_on(key: hir::OwnerId) -> &'tcx rustc_middle::lint::ShallowLintLevelMap { - eval_always // fetches `resolutions` arena_cache desc { |tcx| "looking up lint levels for `{}`", tcx.def_path_str(key) } } @@ -398,11 +397,6 @@ rustc_queries! { desc { "computing `#[expect]`ed lints in this crate" } } - query parent_module_from_def_id(key: LocalDefId) -> LocalDefId { - eval_always - desc { |tcx| "getting the parent module of `{}`", tcx.def_path_str(key) } - } - query expn_that_defined(key: DefId) -> rustc_span::ExpnId { desc { |tcx| "getting the expansion that defined `{}`", tcx.def_path_str(key) } separate_provide_extern @@ -531,6 +525,19 @@ rustc_queries! { } } + /// Returns names of captured upvars for closures and generators. + /// + /// Here are some examples: + /// - `name__field1__field2` when the upvar is captured by value. + /// - `_ref__name__field` when the upvar is captured by reference. + /// + /// For generators this only contains upvars that are shared by all states. + query closure_saved_names_of_captured_variables(def_id: DefId) -> &'tcx IndexVec { + arena_cache + desc { |tcx| "computing debuginfo for closure `{}`", tcx.def_path_str(def_id) } + separate_provide_extern + } + query mir_generator_witnesses(key: DefId) -> &'tcx Option> { arena_cache desc { |tcx| "generator witness types for `{}`", tcx.def_path_str(key) } @@ -693,7 +700,7 @@ rustc_queries! { separate_provide_extern } - query adt_sized_constraint(key: DefId) -> &'tcx [Ty<'tcx>] { + query adt_sized_constraint(key: DefId) -> ty::EarlyBinder<&'tcx ty::List>> { desc { |tcx| "computing `Sized` constraints for `{}`", tcx.def_path_str(key) } } @@ -736,7 +743,7 @@ rustc_queries! { separate_provide_extern } - /// Gets a map with the variance of every item; use `item_variance` instead. + /// Gets a map with the variance of every item; use `variances_of` instead. query crate_variances(_: ()) -> &'tcx ty::CrateVariancesMap<'tcx> { arena_cache desc { "computing the variances for items in this crate" } @@ -868,8 +875,15 @@ rustc_queries! { /// /// Note that we've liberated the late bound regions of function signatures, so /// this can not be used to check whether these types are well formed. - query assumed_wf_types(key: DefId) -> &'tcx ty::List> { + query assumed_wf_types(key: LocalDefId) -> &'tcx [(Ty<'tcx>, Span)] { + desc { |tcx| "computing the implied bounds of `{}`", tcx.def_path_str(key) } + } + + /// We need to store the assumed_wf_types for an RPITIT so that impls of foreign + /// traits with return-position impl trait in traits can inherit the right wf types. + query assumed_wf_types_for_rpitit(key: DefId) -> &'tcx [(Ty<'tcx>, Span)] { desc { |tcx| "computing the implied bounds of `{}`", tcx.def_path_str(key) } + separate_provide_extern } /// Computes the signature of the function. @@ -881,40 +895,44 @@ rustc_queries! { } /// Performs lint checking for the module. - query lint_mod(key: LocalDefId) -> () { + query lint_mod(key: LocalModDefId) -> () { desc { |tcx| "linting {}", describe_as_module(key, tcx) } } + query check_unused_traits(_: ()) -> () { + desc { "checking unused trait imports in crate" } + } + /// Checks the attributes in the module. - query check_mod_attrs(key: LocalDefId) -> () { + query check_mod_attrs(key: LocalModDefId) -> () { desc { |tcx| "checking attributes in {}", describe_as_module(key, tcx) } } /// Checks for uses of unstable APIs in the module. - query check_mod_unstable_api_usage(key: LocalDefId) -> () { + query check_mod_unstable_api_usage(key: LocalModDefId) -> () { desc { |tcx| "checking for unstable API usage in {}", describe_as_module(key, tcx) } } /// Checks the const bodies in the module for illegal operations (e.g. `if` or `loop`). - query check_mod_const_bodies(key: LocalDefId) -> () { + query check_mod_const_bodies(key: LocalModDefId) -> () { desc { |tcx| "checking consts in {}", describe_as_module(key, tcx) } } /// Checks the loops in the module. - query check_mod_loops(key: LocalDefId) -> () { + query check_mod_loops(key: LocalModDefId) -> () { desc { |tcx| "checking loops in {}", describe_as_module(key, tcx) } } - query check_mod_naked_functions(key: LocalDefId) -> () { + query check_mod_naked_functions(key: LocalModDefId) -> () { desc { |tcx| "checking naked functions in {}", describe_as_module(key, tcx) } } - query check_mod_item_types(key: LocalDefId) -> () { + query check_mod_item_types(key: LocalModDefId) -> () { desc { |tcx| "checking item types in {}", describe_as_module(key, tcx) } } - query check_mod_privacy(key: LocalDefId) -> () { - desc { |tcx| "checking privacy in {}", describe_as_module(key, tcx) } + query check_mod_privacy(key: LocalModDefId) -> () { + desc { |tcx| "checking privacy in {}", describe_as_module(key.to_local_def_id(), tcx) } } query check_liveness(key: LocalDefId) { @@ -933,19 +951,19 @@ rustc_queries! { desc { "finding live symbols in crate" } } - query check_mod_deathness(key: LocalDefId) -> () { + query check_mod_deathness(key: LocalModDefId) -> () { desc { |tcx| "checking deathness of variables in {}", describe_as_module(key, tcx) } } - query check_mod_impl_wf(key: LocalDefId) -> () { + query check_mod_impl_wf(key: LocalModDefId) -> () { desc { |tcx| "checking that impls are well-formed in {}", describe_as_module(key, tcx) } } - query check_mod_type_wf(key: LocalDefId) -> () { + query check_mod_type_wf(key: LocalModDefId) -> () { desc { |tcx| "checking that types are well-formed in {}", describe_as_module(key, tcx) } } - query collect_mod_item_types(key: LocalDefId) -> () { + query collect_mod_item_types(key: LocalModDefId) -> () { desc { |tcx| "collecting item types in {}", describe_as_module(key, tcx) } } @@ -1019,7 +1037,7 @@ rustc_queries! { } /// Obtain all the calls into other local functions - query mir_inliner_callees(key: ty::InstanceDef<'tcx>) -> &'tcx [(DefId, SubstsRef<'tcx>)] { + query mir_inliner_callees(key: ty::InstanceDef<'tcx>) -> &'tcx [(DefId, GenericArgsRef<'tcx>)] { fatal_cycle desc { |tcx| "computing all local function calls in `{}`", @@ -1074,19 +1092,13 @@ rustc_queries! { } /// Tries to destructure an `mir::ConstantKind` ADT or array into its variant index - /// and its field values. - query try_destructure_mir_constant( - key: ty::ParamEnvAnd<'tcx, mir::ConstantKind<'tcx>> + /// and its field values. This should only be used for pretty printing. + query try_destructure_mir_constant_for_diagnostics( + key: (ConstValue<'tcx>, Ty<'tcx>) ) -> Option> { desc { "destructuring MIR constant"} - } - - /// Dereference a constant reference or raw pointer and turn the result into a constant - /// again. - query deref_mir_constant( - key: ty::ParamEnvAnd<'tcx, mir::ConstantKind<'tcx>> - ) -> mir::ConstantKind<'tcx> { - desc { "dereferencing MIR constant" } + no_hash + eval_always } query const_caller_location(key: (rustc_span::Symbol, u32, u32)) -> ConstValue<'tcx> { @@ -1100,10 +1112,6 @@ rustc_queries! { desc { "converting literal to const" } } - query lit_to_mir_constant(key: LitToConstInput<'tcx>) -> Result, LitToConstError> { - desc { "converting literal to mir constant" } - } - query check_match(key: LocalDefId) -> Result<(), rustc_errors::ErrorGuaranteed> { desc { |tcx| "match-checking `{}`", tcx.def_path_str(key) } cache_on_disk_if { true } @@ -1270,12 +1278,12 @@ rustc_queries! { query vtable_allocation(key: (Ty<'tcx>, Option>)) -> mir::interpret::AllocId { desc { |tcx| "vtable const allocation for <{} as {}>", key.0, - key.1.map(|trait_ref| format!("{}", trait_ref)).unwrap_or("_".to_owned()) + key.1.map(|trait_ref| format!("{trait_ref}")).unwrap_or("_".to_owned()) } } query codegen_select_candidate( - key: (ty::ParamEnv<'tcx>, ty::PolyTraitRef<'tcx>) + key: (ty::ParamEnv<'tcx>, ty::TraitRef<'tcx>) ) -> Result<&'tcx ImplSource<'tcx, ()>, CodegenObligationError> { cache_on_disk_if { true } desc { |tcx| "computing candidate for `{}`", key.1 } @@ -1382,7 +1390,7 @@ rustc_queries! { /// executes in "reveal all" mode, and will normalize the input type. query layout_of( key: ty::ParamEnvAnd<'tcx, Ty<'tcx>> - ) -> Result, ty::layout::LayoutError<'tcx>> { + ) -> Result, &'tcx ty::layout::LayoutError<'tcx>> { depth_limit desc { "computing layout of `{}`", key.value } } @@ -1393,7 +1401,7 @@ rustc_queries! { /// instead, where the instance is an `InstanceDef::Virtual`. query fn_abi_of_fn_ptr( key: ty::ParamEnvAnd<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List>)> - ) -> Result<&'tcx abi::call::FnAbi<'tcx, Ty<'tcx>>, ty::layout::FnAbiError<'tcx>> { + ) -> Result<&'tcx abi::call::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> { desc { "computing call ABI of `{}` function pointers", key.value.0 } } @@ -1404,7 +1412,7 @@ rustc_queries! { /// to an `InstanceDef::Virtual` instance (of `::fn`). query fn_abi_of_instance( key: ty::ParamEnvAnd<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)> - ) -> Result<&'tcx abi::call::FnAbi<'tcx, Ty<'tcx>>, ty::layout::FnAbiError<'tcx>> { + ) -> Result<&'tcx abi::call::FnAbi<'tcx, Ty<'tcx>>, &'tcx ty::layout::FnAbiError<'tcx>> { desc { "computing call ABI of `{}`", key.value.0 } } @@ -1487,8 +1495,9 @@ rustc_queries! { desc { "getting traits in scope at a block" } } - query impl_defaultness(def_id: DefId) -> hir::Defaultness { - desc { |tcx| "looking up whether `{}` is a default impl", tcx.def_path_str(def_id) } + /// Returns whether the impl or associated function has the `default` keyword. + query defaultness(def_id: DefId) -> hir::Defaultness { + desc { |tcx| "looking up whether `{}` has `default`", tcx.def_path_str(def_id) } separate_provide_extern feedable } @@ -1533,7 +1542,7 @@ rustc_queries! { /// added or removed in any upstream crate. Instead use the narrower /// `upstream_monomorphizations_for`, `upstream_drop_glue_for`, or, even /// better, `Instance::upstream_monomorphization()`. - query upstream_monomorphizations(_: ()) -> &'tcx DefIdMap, CrateNum>> { + query upstream_monomorphizations(_: ()) -> &'tcx DefIdMap, CrateNum>> { arena_cache desc { "collecting available upstream monomorphizations" } } @@ -1546,7 +1555,7 @@ rustc_queries! { /// You likely want to call `Instance::upstream_monomorphization()` /// instead of invoking this query directly. query upstream_monomorphizations_for(def_id: DefId) - -> Option<&'tcx FxHashMap, CrateNum>> + -> Option<&'tcx FxHashMap, CrateNum>> { desc { |tcx| "collecting available upstream monomorphizations for `{}`", @@ -1556,7 +1565,7 @@ rustc_queries! { } /// Returns the upstream crate that exports drop-glue for the given - /// type (`substs` is expected to be a single-item list containing the + /// type (`args` is expected to be a single-item list containing the /// type one wants drop-glue for). /// /// This is a subset of `upstream_monomorphizations_for` in order to @@ -1570,17 +1579,22 @@ rustc_queries! { /// NOTE: This query could easily be extended to also support other /// common functions that have are large set of monomorphizations /// (like `Clone::clone` for example). - query upstream_drop_glue_for(substs: SubstsRef<'tcx>) -> Option { - desc { "available upstream drop-glue for `{:?}`", substs } + query upstream_drop_glue_for(args: GenericArgsRef<'tcx>) -> Option { + desc { "available upstream drop-glue for `{:?}`", args } } /// Returns a list of all `extern` blocks of a crate. - query foreign_modules(_: CrateNum) -> &'tcx FxHashMap { + query foreign_modules(_: CrateNum) -> &'tcx FxIndexMap { arena_cache desc { "looking up the foreign modules of a linked crate" } separate_provide_extern } + /// Lint against `extern fn` declarations having incompatible types. + query clashing_extern_declarations(_: ()) { + desc { "checking `extern fn` declarations are compatible" } + } + /// Identifies the entry-point (e.g., the `main` function) for a given /// crate, returning `None` if there is no entry point (such as for library crates). query entry_fn(_: ()) -> Option<(DefId, EntryFnType)> { @@ -1914,6 +1928,16 @@ rustc_queries! { desc { "normalizing `{}`", goal.value.value } } + /// Do not call this query directly: invoke `normalize` instead. + query normalize_weak_ty( + goal: CanonicalProjectionGoal<'tcx> + ) -> Result< + &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, NormalizationResult<'tcx>>>, + NoSolution, + > { + desc { "normalizing `{}`", goal.value.value } + } + /// Do not call this query directly: invoke `normalize` instead. query normalize_inherent_projection_ty( goal: CanonicalProjectionGoal<'tcx> @@ -1959,15 +1983,6 @@ rustc_queries! { desc { "evaluating trait selection obligation `{}`", goal.value.value } } - query evaluate_goal( - goal: CanonicalChalkEnvironmentAndGoal<'tcx> - ) -> Result< - &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, ()>>, - NoSolution - > { - desc { "evaluating trait selection obligation `{}`", goal.value } - } - /// Do not call this query directly: part of the `Eq` type-op query type_op_ascribe_user_type( goal: CanonicalTypeOpAscribeUserTypeGoal<'tcx> @@ -2019,10 +2034,10 @@ rustc_queries! { } /// Do not call this query directly: part of the `Normalize` type-op - query type_op_normalize_predicate( - goal: CanonicalTypeOpNormalizeGoal<'tcx, ty::Predicate<'tcx>> + query type_op_normalize_clause( + goal: CanonicalTypeOpNormalizeGoal<'tcx, ty::Clause<'tcx>> ) -> Result< - &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, ty::Predicate<'tcx>>>, + &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, ty::Clause<'tcx>>>, NoSolution, > { desc { "normalizing `{:?}`", goal.value.value.value } @@ -2048,16 +2063,16 @@ rustc_queries! { desc { "normalizing `{:?}`", goal.value.value.value } } - query subst_and_check_impossible_predicates(key: (DefId, SubstsRef<'tcx>)) -> bool { + query subst_and_check_impossible_predicates(key: (DefId, GenericArgsRef<'tcx>)) -> bool { desc { |tcx| "checking impossible substituted predicates: `{}`", tcx.def_path_str(key.0) } } - query is_impossible_method(key: (DefId, DefId)) -> bool { + query is_impossible_associated_item(key: (DefId, DefId)) -> bool { desc { |tcx| - "checking if `{}` is impossible to call within `{}`", + "checking if `{}` is impossible to reference within `{}`", tcx.def_path_str(key.1), tcx.def_path_str(key.0), } @@ -2075,23 +2090,11 @@ rustc_queries! { desc { "looking up supported target features" } } - /// Get an estimate of the size of an InstanceDef based on its MIR for CGU partitioning. - query instance_def_size_estimate(def: ty::InstanceDef<'tcx>) - -> usize { - desc { |tcx| "estimating size for `{}`", tcx.def_path_str(def.def_id()) } - } - query features_query(_: ()) -> &'tcx rustc_feature::Features { feedable desc { "looking up enabled feature gates" } } - query metadata_loader((): ()) -> &'tcx Steal> { - feedable - no_hash - desc { "raw operations for metadata file access" } - } - query crate_for_resolver((): ()) -> &'tcx Steal<(rustc_ast::Crate, rustc_ast::AttrVec)> { feedable no_hash @@ -2099,21 +2102,21 @@ rustc_queries! { } /// Attempt to resolve the given `DefId` to an `Instance`, for the - /// given generics args (`SubstsRef`), returning one of: + /// given generics args (`GenericArgsRef`), returning one of: /// * `Ok(Some(instance))` on success - /// * `Ok(None)` when the `SubstsRef` are still too generic, + /// * `Ok(None)` when the `GenericArgsRef` are still too generic, /// and therefore don't allow finding the final `Instance` /// * `Err(ErrorGuaranteed)` when the `Instance` resolution process /// couldn't complete due to errors elsewhere - this is distinct /// from `Ok(None)` to avoid misleading diagnostics when an error /// has already been/will be emitted, for the original cause query resolve_instance( - key: ty::ParamEnvAnd<'tcx, (DefId, SubstsRef<'tcx>)> + key: ty::ParamEnvAnd<'tcx, (DefId, GenericArgsRef<'tcx>)> ) -> Result>, ErrorGuaranteed> { desc { "resolving instance `{}`", ty::Instance::new(key.value.0, key.value.1) } } - query reveal_opaque_types_in_bounds(key: &'tcx ty::List>) -> &'tcx ty::List> { + query reveal_opaque_types_in_bounds(key: &'tcx ty::List>) -> &'tcx ty::List> { desc { "revealing opaque types in `{:?}`", key } } @@ -2152,7 +2155,7 @@ rustc_queries! { separate_provide_extern } - query check_validity_requirement(key: (ValidityRequirement, ty::ParamEnvAnd<'tcx, Ty<'tcx>>)) -> Result> { + query check_validity_requirement(key: (ValidityRequirement, ty::ParamEnvAnd<'tcx, Ty<'tcx>>)) -> Result> { desc { "checking validity requirement for `{}`: {}", key.1.value, key.0 } } @@ -2185,6 +2188,19 @@ rustc_queries! { query check_tys_might_be_eq(arg: Canonical<'tcx, (ty::ParamEnv<'tcx>, Ty<'tcx>, Ty<'tcx>)>) -> Result<(), NoSolution> { desc { "check whether two const param are definitely not equal to eachother"} } + + /// Get all item paths that were stripped by a `#[cfg]` in a particular crate. + /// Should not be called for the local crate before the resolver outputs are created, as it + /// is only fed there. + query stripped_cfg_items(cnum: CrateNum) -> &'tcx [StrippedCfgItem] { + feedable + desc { "getting cfg-ed out item names" } + separate_provide_extern + } + + query generics_require_sized_self(def_id: DefId) -> bool { + desc { "check whether the item has a `where Self: Sized` bound" } + } } rustc_query_append! { define_callbacks! } diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 220118ae5ccb8..995b2140f61d5 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -104,7 +104,9 @@ struct Footer { query_result_index: EncodedDepNodeIndex, side_effects_index: EncodedDepNodeIndex, // The location of all allocations. - interpret_alloc_index: Vec, + // Most uses only need values up to u32::MAX, but benchmarking indicates that we can use a u64 + // without measurable overhead. This permits larger const allocations without ICEing. + interpret_alloc_index: Vec, // See `OnDiskCache.syntax_contexts` syntax_contexts: FxHashMap, // See `OnDiskCache.expn_data` @@ -301,7 +303,7 @@ impl<'sess> OnDiskCache<'sess> { interpret_alloc_index.reserve(new_n - n); for idx in n..new_n { let id = encoder.interpret_allocs[idx]; - let pos: u32 = encoder.position().try_into().unwrap(); + let pos: u64 = encoder.position().try_into().unwrap(); interpret_alloc_index.push(pos); interpret::specialized_encode_alloc_id(&mut encoder, tcx, id); } @@ -791,13 +793,6 @@ impl<'a, 'tcx> Decodable> } } -impl<'a, 'tcx> Decodable> for &'tcx [(ty::Predicate<'tcx>, Span)] { - #[inline] - fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { - RefDecodable::decode(d) - } -} - impl<'a, 'tcx> Decodable> for &'tcx [(ty::Clause<'tcx>, Span)] { #[inline] fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 97edfc2fca27d..a1aac2846210d 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -545,6 +545,7 @@ macro_rules! define_feedable { mod sealed { use super::{DefId, LocalDefId, OwnerId}; + use rustc_hir::def_id::{LocalModDefId, ModDefId}; /// An analogue of the `Into` trait that's intended only for query parameters. /// @@ -588,6 +589,27 @@ mod sealed { self.to_def_id() } } + + impl IntoQueryParam for ModDefId { + #[inline(always)] + fn into_query_param(self) -> DefId { + self.to_def_id() + } + } + + impl IntoQueryParam for LocalModDefId { + #[inline(always)] + fn into_query_param(self) -> DefId { + self.to_def_id() + } + } + + impl IntoQueryParam for LocalModDefId { + #[inline(always)] + fn into_query_param(self) -> LocalDefId { + self.into() + } + } } pub use sealed::IntoQueryParam; diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 813e109c41e14..ebc1c11902bcb 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -18,9 +18,9 @@ use rustc_index::IndexVec; use rustc_middle::middle::region; use rustc_middle::mir::interpret::AllocId; use rustc_middle::mir::{self, BinOp, BorrowKind, FakeReadCause, Mutability, UnOp}; -use rustc_middle::ty::adjustment::PointerCast; -use rustc_middle::ty::subst::SubstsRef; -use rustc_middle::ty::{self, AdtDef, FnSig, List, Ty, UpvarSubsts}; +use rustc_middle::ty::adjustment::PointerCoercion; +use rustc_middle::ty::GenericArgsRef; +use rustc_middle::ty::{self, AdtDef, FnSig, List, Ty, UpvarArgs}; use rustc_middle::ty::{CanonicalUserType, CanonicalUserTypeAnnotation}; use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; @@ -150,9 +150,9 @@ pub struct AdtExpr<'tcx> { pub adt_def: AdtDef<'tcx>, /// The variant of the ADT. pub variant_index: VariantIdx, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, - /// Optional user-given substs: for something like `let x = + /// Optional user-given args: for something like `let x = /// Bar:: { ... }`. pub user_ty: UserTy<'tcx>, @@ -164,7 +164,7 @@ pub struct AdtExpr<'tcx> { #[derive(Clone, Debug, HashStable)] pub struct ClosureExpr<'tcx> { pub closure_id: LocalDefId, - pub substs: UpvarSubsts<'tcx>, + pub args: UpvarArgs<'tcx>, pub upvars: Box<[ExprId]>, pub movability: Option, pub fake_reads: Vec<(ExprId, FakeReadCause, hir::HirId)>, @@ -329,9 +329,10 @@ pub enum ExprKind<'tcx> { NeverToAny { source: ExprId, }, - /// A pointer cast. More information can be found in [`PointerCast`]. - Pointer { - cast: PointerCast, + /// A pointer coercion. More information can be found in [`PointerCoercion`]. + /// Pointer casts that cannot be done by coercions are represented by [`ExprKind::Cast`]. + PointerCoercion { + cast: PointerCoercion, source: ExprId, }, /// A `loop` expression. @@ -345,6 +346,7 @@ pub enum ExprKind<'tcx> { /// A `match` expression. Match { scrutinee: ExprId, + scrutinee_hir_id: hir::HirId, arms: Box<[ArmId]>, }, /// A block. @@ -410,10 +412,14 @@ pub enum ExprKind<'tcx> { Return { value: Option, }, + /// A `become` expression. + Become { + value: ExprId, + }, /// An inline `const` block, e.g. `const {}`. ConstBlock { did: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, }, /// An array literal constructed from one repeated element, e.g. `[1; 5]`. Repeat { @@ -461,7 +467,7 @@ pub enum ExprKind<'tcx> { /// Associated constants and named constants NamedConst { def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, user_ty: UserTy<'tcx>, }, ConstParam { @@ -654,7 +660,7 @@ impl<'tcx> Pat<'tcx> { impl<'tcx> IntoDiagnosticArg for Pat<'tcx> { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - format!("{}", self).into_diagnostic_arg() + format!("{self}").into_diagnostic_arg() } } @@ -709,7 +715,7 @@ pub enum PatKind<'tcx> { /// multiple variants. Variant { adt_def: AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, variant_index: VariantIdx, subpatterns: Vec>, }, @@ -784,7 +790,7 @@ impl<'tcx> fmt::Display for Pat<'tcx> { match self.kind { PatKind::Wild => write!(f, "_"), - PatKind::AscribeUserType { ref subpattern, .. } => write!(f, "{}: _", subpattern), + PatKind::AscribeUserType { ref subpattern, .. } => write!(f, "{subpattern}: _"), PatKind::Binding { mutability, name, mode, ref subpattern, .. } => { let is_mut = match mode { BindingMode::ByValue => mutability == Mutability::Mut, @@ -796,9 +802,9 @@ impl<'tcx> fmt::Display for Pat<'tcx> { if is_mut { write!(f, "mut ")?; } - write!(f, "{}", name)?; + write!(f, "{name}")?; if let Some(ref subpattern) = *subpattern { - write!(f, " @ {}", subpattern)?; + write!(f, " @ {subpattern}")?; } Ok(()) } @@ -828,7 +834,7 @@ impl<'tcx> fmt::Display for Pat<'tcx> { }; if let Some((variant, name)) = &variant_and_name { - write!(f, "{}", name)?; + write!(f, "{name}")?; // Only for Adt we can have `S {...}`, // which we handle separately here. @@ -888,13 +894,13 @@ impl<'tcx> fmt::Display for Pat<'tcx> { } _ => bug!("{} is a bad Deref pattern type", self.ty), } - write!(f, "{}", subpattern) + write!(f, "{subpattern}") } - PatKind::Constant { value } => write!(f, "{}", value), + PatKind::Constant { value } => write!(f, "{value}"), PatKind::Range(box PatRange { lo, hi, end }) => { - write!(f, "{}", lo)?; - write!(f, "{}", end)?; - write!(f, "{}", hi) + write!(f, "{lo}")?; + write!(f, "{end}")?; + write!(f, "{hi}") } PatKind::Slice { ref prefix, ref slice, ref suffix } | PatKind::Array { ref prefix, ref slice, ref suffix } => { @@ -906,7 +912,7 @@ impl<'tcx> fmt::Display for Pat<'tcx> { write!(f, "{}", start_or_comma())?; match slice.kind { PatKind::Wild => {} - _ => write!(f, "{}", slice)?, + _ => write!(f, "{slice}")?, } write!(f, "..")?; } diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index 5c7ec31cf93d3..681400dbb9481 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -65,12 +65,12 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp Cast { source } => visitor.visit_expr(&visitor.thir()[source]), Use { source } => visitor.visit_expr(&visitor.thir()[source]), NeverToAny { source } => visitor.visit_expr(&visitor.thir()[source]), - Pointer { source, cast: _ } => visitor.visit_expr(&visitor.thir()[source]), + PointerCoercion { source, cast: _ } => visitor.visit_expr(&visitor.thir()[source]), Let { expr, .. } => { visitor.visit_expr(&visitor.thir()[expr]); } Loop { body } => visitor.visit_expr(&visitor.thir()[body]), - Match { scrutinee, ref arms } => { + Match { scrutinee, ref arms, .. } => { visitor.visit_expr(&visitor.thir()[scrutinee]); for &arm in &**arms { visitor.visit_arm(&visitor.thir()[arm]); @@ -100,7 +100,8 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp visitor.visit_expr(&visitor.thir()[value]) } } - ConstBlock { did: _, substs: _ } => {} + Become { value } => visitor.visit_expr(&visitor.thir()[value]), + ConstBlock { did: _, args: _ } => {} Repeat { value, count: _ } => { visitor.visit_expr(&visitor.thir()[value]); } @@ -114,7 +115,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp ref base, adt_def: _, variant_index: _, - substs: _, + args: _, user_ty: _, }) => { for field in &**fields { @@ -129,7 +130,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp } Closure(box ClosureExpr { closure_id: _, - substs: _, + args: _, upvars: _, movability: _, fake_reads: _, @@ -137,7 +138,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp Literal { lit: _, neg: _ } => {} NonHirLiteral { lit: _, user_ty: _ } => {} ZstLiteral { user_ty: _ } => {} - NamedConst { def_id: _, substs: _, user_ty: _ } => {} + NamedConst { def_id: _, args: _, user_ty: _ } => {} ConstParam { param: _, def_id: _ } => {} StaticRef { alloc_id: _, ty: _, def_id: _ } => {} InlineAsm(box InlineAsmExpr { ref operands, template: _, options: _, line_spans: _ }) => { @@ -226,7 +227,7 @@ pub fn walk_pat<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, pat: &Pat<' name: _, } => visitor.visit_pat(&subpattern), Binding { .. } | Wild => {} - Variant { subpatterns, adt_def: _, substs: _, variant_index: _ } | Leaf { subpatterns } => { + Variant { subpatterns, adt_def: _, args: _, variant_index: _ } | Leaf { subpatterns } => { for subpattern in subpatterns { visitor.visit_pat(&subpattern.pattern); } diff --git a/compiler/rustc_middle/src/traits/chalk.rs b/compiler/rustc_middle/src/traits/chalk.rs deleted file mode 100644 index fcc8f457a8b78..0000000000000 --- a/compiler/rustc_middle/src/traits/chalk.rs +++ /dev/null @@ -1,396 +0,0 @@ -//! Types required for Chalk-related queries -//! -//! The primary purpose of this file is defining an implementation for the -//! `chalk_ir::interner::Interner` trait. The primary purpose of this trait, as -//! its name suggest, is to provide an abstraction boundary for creating -//! interned Chalk types. - -use rustc_middle::ty::{self, AdtDef, TyCtxt}; - -use rustc_hir::def_id::DefId; -use rustc_target::spec::abi::Abi; - -use std::cmp::Ordering; -use std::fmt; -use std::hash::{Hash, Hasher}; - -#[derive(Copy, Clone)] -pub struct RustInterner<'tcx> { - pub tcx: TyCtxt<'tcx>, -} - -/// We don't ever actually need this. It's only required for derives. -impl<'tcx> Hash for RustInterner<'tcx> { - fn hash(&self, _state: &mut H) {} -} - -/// We don't ever actually need this. It's only required for derives. -impl<'tcx> Ord for RustInterner<'tcx> { - fn cmp(&self, _other: &Self) -> Ordering { - Ordering::Equal - } -} - -/// We don't ever actually need this. It's only required for derives. -impl<'tcx> PartialOrd for RustInterner<'tcx> { - fn partial_cmp(&self, _other: &Self) -> Option { - None - } -} - -/// We don't ever actually need this. It's only required for derives. -impl<'tcx> PartialEq for RustInterner<'tcx> { - fn eq(&self, _other: &Self) -> bool { - false - } -} - -/// We don't ever actually need this. It's only required for derives. -impl<'tcx> Eq for RustInterner<'tcx> {} - -impl fmt::Debug for RustInterner<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RustInterner") - } -} - -// Right now, there is no interning at all. I was running into problems with -// adding interning in `ty/context.rs` for Chalk types with -// `parallel-compiler = true`. -jackh726 -impl<'tcx> chalk_ir::interner::Interner for RustInterner<'tcx> { - type InternedType = Box>; - type InternedLifetime = Box>; - type InternedConst = Box>; - type InternedConcreteConst = ty::ValTree<'tcx>; - type InternedGenericArg = Box>; - type InternedGoal = Box>; - type InternedGoals = Vec>; - type InternedSubstitution = Vec>; - type InternedProgramClause = Box>; - type InternedProgramClauses = Vec>; - type InternedQuantifiedWhereClauses = Vec>; - type InternedVariableKinds = Vec>; - type InternedCanonicalVarKinds = Vec>; - type InternedVariances = Vec; - type InternedConstraints = Vec>>; - type DefId = DefId; - type InternedAdtId = AdtDef<'tcx>; - type Identifier = (); - type FnAbi = Abi; - - fn debug_program_clause_implication( - pci: &chalk_ir::ProgramClauseImplication, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - let mut write = || { - write!(fmt, "{:?}", pci.consequence)?; - - let conditions = pci.conditions.interned(); - let constraints = pci.constraints.interned(); - - let conds = conditions.len(); - let consts = constraints.len(); - if conds == 0 && consts == 0 { - return Ok(()); - } - - write!(fmt, " :- ")?; - - if conds != 0 { - for cond in &conditions[..conds - 1] { - write!(fmt, "{:?}, ", cond)?; - } - write!(fmt, "{:?}", conditions[conds - 1])?; - } - - if conds != 0 && consts != 0 { - write!(fmt, " ; ")?; - } - - if consts != 0 { - for constraint in &constraints[..consts - 1] { - write!(fmt, "{:?}, ", constraint)?; - } - write!(fmt, "{:?}", constraints[consts - 1])?; - } - - Ok(()) - }; - Some(write()) - } - - fn debug_substitution( - substitution: &chalk_ir::Substitution, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", substitution.interned())) - } - - fn debug_separator_trait_ref( - separator_trait_ref: &chalk_ir::SeparatorTraitRef<'_, Self>, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - let substitution = &separator_trait_ref.trait_ref.substitution; - let parameters = substitution.interned(); - Some(write!( - fmt, - "{:?}{}{:?}{:?}", - parameters[0], - separator_trait_ref.separator, - separator_trait_ref.trait_ref.trait_id, - chalk_ir::debug::Angle(¶meters[1..]) - )) - } - - fn debug_quantified_where_clauses( - clauses: &chalk_ir::QuantifiedWhereClauses, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", clauses.interned())) - } - - fn debug_ty(ty: &chalk_ir::Ty, fmt: &mut fmt::Formatter<'_>) -> Option { - match &ty.interned().kind { - chalk_ir::TyKind::Ref(chalk_ir::Mutability::Not, lifetime, ty) => { - Some(write!(fmt, "(&{:?} {:?})", lifetime, ty)) - } - chalk_ir::TyKind::Ref(chalk_ir::Mutability::Mut, lifetime, ty) => { - Some(write!(fmt, "(&{:?} mut {:?})", lifetime, ty)) - } - chalk_ir::TyKind::Array(ty, len) => Some(write!(fmt, "[{:?}; {:?}]", ty, len)), - chalk_ir::TyKind::Slice(ty) => Some(write!(fmt, "[{:?}]", ty)), - chalk_ir::TyKind::Tuple(len, substs) => Some( - try { - write!(fmt, "(")?; - for (idx, substitution) in substs.interned().iter().enumerate() { - if idx == *len && *len != 1 { - // Don't add a trailing comma if the tuple has more than one element - write!(fmt, "{:?}", substitution)?; - } else { - write!(fmt, "{:?},", substitution)?; - } - } - write!(fmt, ")")?; - }, - ), - _ => None, - } - } - - fn debug_alias( - alias_ty: &chalk_ir::AliasTy, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - match alias_ty { - chalk_ir::AliasTy::Projection(projection_ty) => { - Self::debug_projection_ty(projection_ty, fmt) - } - chalk_ir::AliasTy::Opaque(opaque_ty) => Self::debug_opaque_ty(opaque_ty, fmt), - } - } - - fn debug_projection_ty( - projection_ty: &chalk_ir::ProjectionTy, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!( - fmt, - "projection: {:?} {:?}", - projection_ty.associated_ty_id, projection_ty.substitution, - )) - } - - fn debug_opaque_ty( - opaque_ty: &chalk_ir::OpaqueTy, - fmt: &mut fmt::Formatter<'_>, - ) -> Option { - Some(write!(fmt, "{:?}", opaque_ty.opaque_ty_id)) - } - - fn intern_ty(self, ty: chalk_ir::TyKind) -> Self::InternedType { - let flags = ty.compute_flags(self); - Box::new(chalk_ir::TyData { kind: ty, flags: flags }) - } - - fn ty_data(self, ty: &Self::InternedType) -> &chalk_ir::TyData { - ty - } - - fn intern_lifetime(self, lifetime: chalk_ir::LifetimeData) -> Self::InternedLifetime { - Box::new(lifetime) - } - - fn lifetime_data(self, lifetime: &Self::InternedLifetime) -> &chalk_ir::LifetimeData { - &lifetime - } - - fn intern_const(self, constant: chalk_ir::ConstData) -> Self::InternedConst { - Box::new(constant) - } - - fn const_data(self, constant: &Self::InternedConst) -> &chalk_ir::ConstData { - &constant - } - - fn const_eq( - self, - _ty: &Self::InternedType, - c1: &Self::InternedConcreteConst, - c2: &Self::InternedConcreteConst, - ) -> bool { - c1 == c2 - } - - fn intern_generic_arg(self, data: chalk_ir::GenericArgData) -> Self::InternedGenericArg { - Box::new(data) - } - - fn generic_arg_data(self, data: &Self::InternedGenericArg) -> &chalk_ir::GenericArgData { - &data - } - - fn intern_goal(self, goal: chalk_ir::GoalData) -> Self::InternedGoal { - Box::new(goal) - } - - fn goal_data(self, goal: &Self::InternedGoal) -> &chalk_ir::GoalData { - &goal - } - - fn intern_goals( - self, - data: impl IntoIterator, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn goals_data(self, goals: &Self::InternedGoals) -> &[chalk_ir::Goal] { - goals - } - - fn intern_substitution( - self, - data: impl IntoIterator, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn substitution_data( - self, - substitution: &Self::InternedSubstitution, - ) -> &[chalk_ir::GenericArg] { - substitution - } - - fn intern_program_clause( - self, - data: chalk_ir::ProgramClauseData, - ) -> Self::InternedProgramClause { - Box::new(data) - } - - fn program_clause_data( - self, - clause: &Self::InternedProgramClause, - ) -> &chalk_ir::ProgramClauseData { - &clause - } - - fn intern_program_clauses( - self, - data: impl IntoIterator, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn program_clauses_data( - self, - clauses: &Self::InternedProgramClauses, - ) -> &[chalk_ir::ProgramClause] { - clauses - } - - fn intern_quantified_where_clauses( - self, - data: impl IntoIterator, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn quantified_where_clauses_data( - self, - clauses: &Self::InternedQuantifiedWhereClauses, - ) -> &[chalk_ir::QuantifiedWhereClause] { - clauses - } - - fn intern_generic_arg_kinds( - self, - data: impl IntoIterator, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn variable_kinds_data( - self, - parameter_kinds: &Self::InternedVariableKinds, - ) -> &[chalk_ir::VariableKind] { - parameter_kinds - } - - fn intern_canonical_var_kinds( - self, - data: impl IntoIterator, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn canonical_var_kinds_data( - self, - canonical_var_kinds: &Self::InternedCanonicalVarKinds, - ) -> &[chalk_ir::CanonicalVarKind] { - canonical_var_kinds - } - - fn intern_constraints( - self, - data: impl IntoIterator>, E>>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn constraints_data( - self, - constraints: &Self::InternedConstraints, - ) -> &[chalk_ir::InEnvironment>] { - constraints - } - - fn intern_variances( - self, - data: impl IntoIterator>, - ) -> Result { - data.into_iter().collect::, _>>() - } - - fn variances_data(self, variances: &Self::InternedVariances) -> &[chalk_ir::Variance] { - variances - } -} - -impl<'tcx> chalk_ir::interner::HasInterner for RustInterner<'tcx> { - type Interner = Self; -} - -/// A chalk environment and goal. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable, TypeFoldable, TypeVisitable)] -pub struct ChalkEnvironmentAndGoal<'tcx> { - pub environment: &'tcx ty::List>, - pub goal: ty::Predicate<'tcx>, -} - -impl<'tcx> fmt::Display for ChalkEnvironmentAndGoal<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "environment: {:?}, goal: {}", self.environment, self.goal) - } -} diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 0a903a76974da..3465759b91340 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -2,7 +2,6 @@ //! //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/traits/resolution.html -mod chalk; pub mod query; pub mod select; pub mod solve; @@ -13,7 +12,7 @@ pub mod util; use crate::infer::canonical::Canonical; use crate::mir::ConstraintCategory; use crate::ty::abstract_const::NotConstEvaluatable; -use crate::ty::subst::SubstsRef; +use crate::ty::GenericArgsRef; use crate::ty::{self, AdtKind, Ty, TyCtxt}; use rustc_data_structures::sync::Lrc; @@ -30,12 +29,8 @@ use std::hash::{Hash, Hasher}; pub use self::select::{EvaluationCache, EvaluationResult, OverflowError, SelectionCache}; -pub type CanonicalChalkEnvironmentAndGoal<'tcx> = Canonical<'tcx, ChalkEnvironmentAndGoal<'tcx>>; - pub use self::ObligationCauseCode::*; -pub use self::chalk::{ChalkEnvironmentAndGoal, RustInterner as ChalkRustInterner}; - /// Depending on the stage of compilation, we want projection to be /// more or less conservative. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, HashStable, Encodable, Decodable)] @@ -204,7 +199,7 @@ impl<'tcx> ObligationCause<'tcx> { pub struct UnifyReceiverContext<'tcx> { pub assoc_item: ty::AssocItem, pub param_env: ty::ParamEnv<'tcx>, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } #[derive(Clone, PartialEq, Eq, Lift, Default, HashStable)] @@ -407,7 +402,7 @@ pub enum ObligationCauseCode<'tcx> { OpaqueReturnType(Option<(Ty<'tcx>, Span)>), /// Block implicit return - BlockTailExpression(hir::HirId), + BlockTailExpression(hir::HirId, hir::MatchSource), /// #[feature(trivial_bounds)] is not enabled TrivialBound, @@ -445,6 +440,12 @@ pub enum ObligationCauseCode<'tcx> { /// Obligations to prove that a `std::ops::Drop` impl is not stronger than /// the ADT it's being implemented for. DropImpl, + + /// Requirement for a `const N: Ty` to implement `Ty: ConstParamTy` + ConstParam(Ty<'tcx>), + + /// Obligations emitted during the normalization of a weak type alias. + TypeAlias(InternedObligationCauseCode<'tcx>, Span, DefId), } /// The 'location' at which we try to perform HIR-based wf checking. @@ -542,7 +543,6 @@ pub struct MatchExpressionArmCause<'tcx> { pub scrut_span: Span, pub source: hir::MatchSource, pub prior_arms: Vec, - pub scrut_hir_id: hir::HirId, pub opt_suggest_box_span: Option, } @@ -587,6 +587,10 @@ pub enum SelectionError<'tcx> { /// Signaling that an error has already been emitted, to avoid /// multiple errors being shown. ErrorReporting, + /// Computing an opaque type's hidden type caused an error (e.g. a cycle error). + /// We can thus not know whether the hidden type implements an auto trait, so + /// we should not presume anything about it. + OpaqueTypeAutoTraitLeakageUnknown(DefId), } #[derive(Clone, Debug, TypeVisitable, Lift)] @@ -640,97 +644,35 @@ pub enum ImplSource<'tcx, N> { /// ImplSource identifying a particular impl. UserDefined(ImplSourceUserDefinedData<'tcx, N>), - /// ImplSource for auto trait implementations. - /// This carries the information and nested obligations with regards - /// to an auto implementation for a trait `Trait`. The nested obligations - /// ensure the trait implementation holds for all the constituent types. - AutoImpl(ImplSourceAutoImplData), - /// Successful resolution to an obligation provided by the caller /// for some type parameter. The `Vec` represents the /// obligations incurred from normalizing the where-clause (if /// any). - Param(Vec, ty::BoundConstness), - - /// Virtual calls through an object. - Object(ImplSourceObjectData<'tcx, N>), - - /// Successful resolution for a builtin trait. - Builtin(ImplSourceBuiltinData), - - /// ImplSource for trait upcasting coercion - TraitUpcasting(ImplSourceTraitUpcastingData<'tcx, N>), - - /// ImplSource automatically generated for a closure. The `DefId` is the ID - /// of the closure expression. This is an `ImplSource::UserDefined` in spirit, but the - /// impl is generated by the compiler and does not appear in the source. - Closure(ImplSourceClosureData<'tcx, N>), - - /// Same as above, but for a function pointer type with the given signature. - FnPointer(ImplSourceFnPointerData<'tcx, N>), - - /// ImplSource automatically generated for a generator. - Generator(ImplSourceGeneratorData<'tcx, N>), + Param(Vec), - /// ImplSource automatically generated for a generator backing an async future. - Future(ImplSourceFutureData<'tcx, N>), - - /// ImplSource for a trait alias. - TraitAlias(ImplSourceTraitAliasData<'tcx, N>), - - /// ImplSource for a `const Drop` implementation. - ConstDestruct(ImplSourceConstDestructData), + /// Successful resolution for a builtin impl. + Builtin(BuiltinImplSource, Vec), } impl<'tcx, N> ImplSource<'tcx, N> { pub fn nested_obligations(self) -> Vec { match self { ImplSource::UserDefined(i) => i.nested, - ImplSource::Param(n, _) => n, - ImplSource::Builtin(i) => i.nested, - ImplSource::AutoImpl(d) => d.nested, - ImplSource::Closure(c) => c.nested, - ImplSource::Generator(c) => c.nested, - ImplSource::Future(c) => c.nested, - ImplSource::Object(d) => d.nested, - ImplSource::FnPointer(d) => d.nested, - ImplSource::TraitAlias(d) => d.nested, - ImplSource::TraitUpcasting(d) => d.nested, - ImplSource::ConstDestruct(i) => i.nested, + ImplSource::Param(n) | ImplSource::Builtin(_, n) => n, } } pub fn borrow_nested_obligations(&self) -> &[N] { match self { ImplSource::UserDefined(i) => &i.nested, - ImplSource::Param(n, _) => n, - ImplSource::Builtin(i) => &i.nested, - ImplSource::AutoImpl(d) => &d.nested, - ImplSource::Closure(c) => &c.nested, - ImplSource::Generator(c) => &c.nested, - ImplSource::Future(c) => &c.nested, - ImplSource::Object(d) => &d.nested, - ImplSource::FnPointer(d) => &d.nested, - ImplSource::TraitAlias(d) => &d.nested, - ImplSource::TraitUpcasting(d) => &d.nested, - ImplSource::ConstDestruct(i) => &i.nested, + ImplSource::Param(n) | ImplSource::Builtin(_, n) => &n, } } pub fn borrow_nested_obligations_mut(&mut self) -> &mut [N] { match self { ImplSource::UserDefined(i) => &mut i.nested, - ImplSource::Param(n, _) => n, - ImplSource::Builtin(i) => &mut i.nested, - ImplSource::AutoImpl(d) => &mut d.nested, - ImplSource::Closure(c) => &mut c.nested, - ImplSource::Generator(c) => &mut c.nested, - ImplSource::Future(c) => &mut c.nested, - ImplSource::Object(d) => &mut d.nested, - ImplSource::FnPointer(d) => &mut d.nested, - ImplSource::TraitAlias(d) => &mut d.nested, - ImplSource::TraitUpcasting(d) => &mut d.nested, - ImplSource::ConstDestruct(i) => &mut i.nested, + ImplSource::Param(n) | ImplSource::Builtin(_, n) => n, } } @@ -741,57 +683,12 @@ impl<'tcx, N> ImplSource<'tcx, N> { match self { ImplSource::UserDefined(i) => ImplSource::UserDefined(ImplSourceUserDefinedData { impl_def_id: i.impl_def_id, - substs: i.substs, - nested: i.nested.into_iter().map(f).collect(), - }), - ImplSource::Param(n, ct) => ImplSource::Param(n.into_iter().map(f).collect(), ct), - ImplSource::Builtin(i) => ImplSource::Builtin(ImplSourceBuiltinData { + args: i.args, nested: i.nested.into_iter().map(f).collect(), }), - ImplSource::Object(o) => ImplSource::Object(ImplSourceObjectData { - upcast_trait_ref: o.upcast_trait_ref, - vtable_base: o.vtable_base, - nested: o.nested.into_iter().map(f).collect(), - }), - ImplSource::AutoImpl(d) => ImplSource::AutoImpl(ImplSourceAutoImplData { - trait_def_id: d.trait_def_id, - nested: d.nested.into_iter().map(f).collect(), - }), - ImplSource::Closure(c) => ImplSource::Closure(ImplSourceClosureData { - closure_def_id: c.closure_def_id, - substs: c.substs, - nested: c.nested.into_iter().map(f).collect(), - }), - ImplSource::Generator(c) => ImplSource::Generator(ImplSourceGeneratorData { - generator_def_id: c.generator_def_id, - substs: c.substs, - nested: c.nested.into_iter().map(f).collect(), - }), - ImplSource::Future(c) => ImplSource::Future(ImplSourceFutureData { - generator_def_id: c.generator_def_id, - substs: c.substs, - nested: c.nested.into_iter().map(f).collect(), - }), - ImplSource::FnPointer(p) => ImplSource::FnPointer(ImplSourceFnPointerData { - fn_ty: p.fn_ty, - nested: p.nested.into_iter().map(f).collect(), - }), - ImplSource::TraitAlias(d) => ImplSource::TraitAlias(ImplSourceTraitAliasData { - alias_def_id: d.alias_def_id, - substs: d.substs, - nested: d.nested.into_iter().map(f).collect(), - }), - ImplSource::TraitUpcasting(d) => { - ImplSource::TraitUpcasting(ImplSourceTraitUpcastingData { - upcast_trait_ref: d.upcast_trait_ref, - vtable_vptr_slot: d.vtable_vptr_slot, - nested: d.nested.into_iter().map(f).collect(), - }) - } - ImplSource::ConstDestruct(i) => { - ImplSource::ConstDestruct(ImplSourceConstDestructData { - nested: i.nested.into_iter().map(f).collect(), - }) + ImplSource::Param(n) => ImplSource::Param(n.into_iter().map(f).collect()), + ImplSource::Builtin(source, n) => { + ImplSource::Builtin(source, n.into_iter().map(f).collect()) } } } @@ -811,103 +708,35 @@ impl<'tcx, N> ImplSource<'tcx, N> { #[derive(TypeFoldable, TypeVisitable)] pub struct ImplSourceUserDefinedData<'tcx, N> { pub impl_def_id: DefId, - pub substs: SubstsRef<'tcx>, - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceGeneratorData<'tcx, N> { - pub generator_def_id: DefId, - pub substs: SubstsRef<'tcx>, - /// Nested obligations. This can be non-empty if the generator - /// signature contains associated types. - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceFutureData<'tcx, N> { - pub generator_def_id: DefId, - pub substs: SubstsRef<'tcx>, - /// Nested obligations. This can be non-empty if the generator - /// signature contains associated types. + pub args: GenericArgsRef<'tcx>, pub nested: Vec, } -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceClosureData<'tcx, N> { - pub closure_def_id: DefId, - pub substs: SubstsRef<'tcx>, - /// Nested obligations. This can be non-empty if the closure - /// signature contains associated types. - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceAutoImplData { - pub trait_def_id: DefId, - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceTraitUpcastingData<'tcx, N> { - /// `Foo` upcast to the obligation trait. This will be some supertrait of `Foo`. - pub upcast_trait_ref: ty::PolyTraitRef<'tcx>, - - /// The vtable is formed by concatenating together the method lists of - /// the base object trait and all supertraits, pointers to supertrait vtable will - /// be provided when necessary; this is the position of `upcast_trait_ref`'s vtable - /// within that vtable. - pub vtable_vptr_slot: Option, - - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceBuiltinData { - pub nested: Vec, -} - -#[derive(PartialEq, Eq, Clone, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceObjectData<'tcx, N> { - /// `Foo` upcast to the obligation trait. This will be some supertrait of `Foo`. - pub upcast_trait_ref: ty::PolyTraitRef<'tcx>, - +#[derive(Copy, Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Debug)] +pub enum BuiltinImplSource { + /// Some builtin impl we don't need to differentiate. This should be used + /// unless more specific information is necessary. + Misc, + /// A builtin impl for trait objects. + /// /// The vtable is formed by concatenating together the method lists of /// the base object trait and all supertraits, pointers to supertrait vtable will /// be provided when necessary; this is the start of `upcast_trait_ref`'s methods /// in that vtable. - pub vtable_base: usize, - - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceFnPointerData<'tcx, N> { - pub fn_ty: Ty<'tcx>, - pub nested: Vec, -} - -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceConstDestructData { - pub nested: Vec, + Object { vtable_base: usize }, + /// The vtable is formed by concatenating together the method lists of + /// the base object trait and all supertraits, pointers to supertrait vtable will + /// be provided when necessary; this is the position of `upcast_trait_ref`'s vtable + /// within that vtable. + TraitUpcasting { vtable_vptr_slot: Option }, + /// Unsizing a tuple like `(A, B, ..., X)` to `(A, B, ..., Y)` if `X` unsizes to `Y`. + /// + /// This needs to be a separate variant as it is still unstable and we need to emit + /// a feature error when using it on stable. + TupleUnsizing, } -#[derive(Clone, PartialEq, Eq, TyEncodable, TyDecodable, HashStable, Lift)] -#[derive(TypeFoldable, TypeVisitable)] -pub struct ImplSourceTraitAliasData<'tcx, N> { - pub alias_def_id: DefId, - pub substs: SubstsRef<'tcx>, - pub nested: Vec, -} +TrivialTypeTraversalAndLiftImpls! { BuiltinImplSource } #[derive(Clone, Debug, PartialEq, Eq, Hash, HashStable, PartialOrd, Ord)] pub enum ObjectSafetyViolation { @@ -947,49 +776,48 @@ impl ObjectSafetyViolation { "where clause cannot reference non-lifetime `for<...>` variables".into() } ObjectSafetyViolation::Method(name, MethodViolationCode::StaticMethod(_), _) => { - format!("associated function `{}` has no `self` parameter", name).into() + format!("associated function `{name}` has no `self` parameter").into() } ObjectSafetyViolation::Method( name, MethodViolationCode::ReferencesSelfInput(_), DUMMY_SP, - ) => format!("method `{}` references the `Self` type in its parameters", name).into(), + ) => format!("method `{name}` references the `Self` type in its parameters").into(), ObjectSafetyViolation::Method(name, MethodViolationCode::ReferencesSelfInput(_), _) => { - format!("method `{}` references the `Self` type in this parameter", name).into() + format!("method `{name}` references the `Self` type in this parameter").into() } ObjectSafetyViolation::Method(name, MethodViolationCode::ReferencesSelfOutput, _) => { - format!("method `{}` references the `Self` type in its return type", name).into() + format!("method `{name}` references the `Self` type in its return type").into() } ObjectSafetyViolation::Method( name, MethodViolationCode::ReferencesImplTraitInTrait(_), _, - ) => format!("method `{}` references an `impl Trait` type in its return type", name) - .into(), + ) => { + format!("method `{name}` references an `impl Trait` type in its return type").into() + } ObjectSafetyViolation::Method(name, MethodViolationCode::AsyncFn, _) => { - format!("method `{}` is `async`", name).into() + format!("method `{name}` is `async`").into() } ObjectSafetyViolation::Method( name, MethodViolationCode::WhereClauseReferencesSelf, _, - ) => { - format!("method `{}` references the `Self` type in its `where` clause", name).into() - } + ) => format!("method `{name}` references the `Self` type in its `where` clause").into(), ObjectSafetyViolation::Method(name, MethodViolationCode::Generic, _) => { - format!("method `{}` has generic type parameters", name).into() + format!("method `{name}` has generic type parameters").into() } ObjectSafetyViolation::Method( name, MethodViolationCode::UndispatchableReceiver(_), _, - ) => format!("method `{}`'s `self` parameter cannot be dispatched on", name).into(), + ) => format!("method `{name}`'s `self` parameter cannot be dispatched on").into(), ObjectSafetyViolation::AssocConst(name, DUMMY_SP) => { - format!("it contains associated `const` `{}`", name).into() + format!("it contains associated `const` `{name}`").into() } ObjectSafetyViolation::AssocConst(..) => "it contains this associated `const`".into(), ObjectSafetyViolation::GAT(name, _) => { - format!("it contains the generic associated type `{}`", name).into() + format!("it contains the generic associated type `{name}`").into() } } } @@ -1007,8 +835,7 @@ impl ObjectSafetyViolation { err.span_suggestion( add_self_sugg.1, format!( - "consider turning `{}` into a method by giving it a `&self` argument", - name + "consider turning `{name}` into a method by giving it a `&self` argument" ), add_self_sugg.0.to_string(), Applicability::MaybeIncorrect, @@ -1016,9 +843,8 @@ impl ObjectSafetyViolation { err.span_suggestion( make_sized_sugg.1, format!( - "alternatively, consider constraining `{}` so it does not apply to \ - trait objects", - name + "alternatively, consider constraining `{name}` so it does not apply to \ + trait objects" ), make_sized_sugg.0.to_string(), Applicability::MaybeIncorrect, @@ -1031,7 +857,7 @@ impl ObjectSafetyViolation { ) => { err.span_suggestion( *span, - format!("consider changing method `{}`'s `self` parameter to be `&self`", name), + format!("consider changing method `{name}`'s `self` parameter to be `&self`"), "&Self", Applicability::MachineApplicable, ); @@ -1039,7 +865,7 @@ impl ObjectSafetyViolation { ObjectSafetyViolation::AssocConst(name, _) | ObjectSafetyViolation::GAT(name, _) | ObjectSafetyViolation::Method(name, ..) => { - err.help(format!("consider moving `{}` to another trait", name)); + err.help(format!("consider moving `{name}` to another trait")); } } } @@ -1109,7 +935,7 @@ pub enum CodegenObligationError { FulfillmentError, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)] pub enum DefiningAnchor { /// `DefId` of the item. Bind(LocalDefId), diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs index c4f8718754f5f..950a59e9695cb 100644 --- a/compiler/rustc_middle/src/traits/query.rs +++ b/compiler/rustc_middle/src/traits/query.rs @@ -8,7 +8,7 @@ use crate::error::DropCheckOverflow; use crate::infer::canonical::{Canonical, QueryResponse}; use crate::ty::error::TypeError; -use crate::ty::subst::GenericArg; +use crate::ty::GenericArg; use crate::ty::{self, Ty, TyCtxt}; use rustc_span::source_map::Span; @@ -92,11 +92,9 @@ pub type CanonicalTypeOpProvePredicateGoal<'tcx> = pub type CanonicalTypeOpNormalizeGoal<'tcx, T> = Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize>>; -#[derive(Copy, Clone, Debug, HashStable, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, HashStable, PartialEq, Eq)] pub struct NoSolution; -pub type Fallible = Result; - impl<'tcx> From> for NoSolution { fn from(_: TypeError<'tcx>) -> NoSolution { NoSolution @@ -134,7 +132,7 @@ impl<'tcx> DropckOutlivesResult<'tcx> { pub struct DropckConstraint<'tcx> { /// Types that are required to be alive in order for this /// type to be valid for destruction. - pub outlives: Vec>, + pub outlives: Vec>, /// Types that could not be resolved: projections and params. pub dtorck_types: Vec>, diff --git a/compiler/rustc_middle/src/traits/select.rs b/compiler/rustc_middle/src/traits/select.rs index f2dda003b99c5..ffae35798895e 100644 --- a/compiler/rustc_middle/src/traits/select.rs +++ b/compiler/rustc_middle/src/traits/select.rs @@ -127,6 +127,7 @@ pub enum SelectionCandidate<'tcx> { /// an applicable bound in the trait definition. The `usize` is an index /// into the list returned by `tcx.item_bounds`. The constness is the /// constness of the bound in the trait. + // FIXME(effects) do we need this constness ProjectionCandidate(usize, ty::BoundConstness), /// Implementation of a `Fn`-family trait by one of the anonymous types @@ -304,9 +305,7 @@ impl From for OverflowError { } } -TrivialTypeTraversalAndLiftImpls! { - OverflowError, -} +TrivialTypeTraversalAndLiftImpls! { OverflowError } impl<'tcx> From for SelectionError<'tcx> { fn from(overflow_error: OverflowError) -> SelectionError<'tcx> { diff --git a/compiler/rustc_middle/src/traits/solve.rs b/compiler/rustc_middle/src/traits/solve.rs index 2c5b64a59cdb9..9d63d29185470 100644 --- a/compiler/rustc_middle/src/traits/solve.rs +++ b/compiler/rustc_middle/src/traits/solve.rs @@ -1,7 +1,6 @@ use std::ops::ControlFlow; use rustc_data_structures::intern::Interned; -use rustc_query_system::cache::Cache; use crate::infer::canonical::{CanonicalVarValues, QueryRegionConstraints}; use crate::traits::query::NoSolution; @@ -11,14 +10,17 @@ use crate::ty::{ TypeVisitor, }; -pub type EvaluationCache<'tcx> = Cache, QueryResult<'tcx>>; +mod cache; +pub mod inspect; + +pub use cache::{CacheData, EvaluationCache}; /// A goal is a statement, i.e. `predicate`, we want to prove /// given some assumptions, i.e. `param_env`. /// /// Most of the time the `param_env` contains the `where`-bounds of the function /// we're currently typechecking while the `predicate` is some trait bound. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct Goal<'tcx, P> { pub predicate: P, pub param_env: ty::ParamEnv<'tcx>, @@ -39,7 +41,7 @@ impl<'tcx, P> Goal<'tcx, P> { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct Response<'tcx> { pub certainty: Certainty, pub var_values: CanonicalVarValues<'tcx>, @@ -47,7 +49,7 @@ pub struct Response<'tcx> { pub external_constraints: ExternalConstraints<'tcx>, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)] pub enum Certainty { Yes, Maybe(MaybeCause), @@ -55,6 +57,7 @@ pub enum Certainty { impl Certainty { pub const AMBIGUOUS: Certainty = Certainty::Maybe(MaybeCause::Ambiguity); + pub const OVERFLOW: Certainty = Certainty::Maybe(MaybeCause::Overflow); /// Use this function to merge the certainty of multiple nested subgoals. /// @@ -64,7 +67,7 @@ impl Certainty { /// success, we merge these two responses. This results in ambiguity. /// /// If we unify ambiguity with overflow, we return overflow. This doesn't matter - /// inside of the solver as we distinguish ambiguity from overflow. It does + /// inside of the solver as we do not distinguish ambiguity from overflow. It does /// however matter for diagnostics. If `T: Foo` resulted in overflow and `T: Bar` /// in ambiguity without changing the inference state, we still want to tell the /// user that `T: Baz` results in overflow. @@ -86,7 +89,7 @@ impl Certainty { } /// Why we failed to evaluate a goal. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)] pub enum MaybeCause { /// We failed due to ambiguity. This ambiguity can either /// be a true ambiguity, i.e. there are multiple different answers, @@ -96,7 +99,7 @@ pub enum MaybeCause { Overflow, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct QueryInput<'tcx, T> { pub goal: Goal<'tcx, T>, pub anchor: DefiningAnchor, @@ -104,12 +107,12 @@ pub struct QueryInput<'tcx, T> { } /// Additional constraints returned on success. -#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, HashStable, Default)] pub struct PredefinedOpaquesData<'tcx> { pub opaque_types: Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)>, } -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, HashStable)] pub struct PredefinedOpaques<'tcx>(pub(crate) Interned<'tcx, PredefinedOpaquesData<'tcx>>); impl<'tcx> std::ops::Deref for PredefinedOpaques<'tcx> { @@ -132,7 +135,7 @@ pub type CanonicalResponse<'tcx> = Canonical<'tcx, Response<'tcx>>; /// solver, merge the two responses again. pub type QueryResult<'tcx> = Result, NoSolution>; -#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, HashStable)] pub struct ExternalConstraints<'tcx>(pub(crate) Interned<'tcx, ExternalConstraintsData<'tcx>>); impl<'tcx> std::ops::Deref for ExternalConstraints<'tcx> { @@ -144,7 +147,7 @@ impl<'tcx> std::ops::Deref for ExternalConstraints<'tcx> { } /// Additional constraints returned on success. -#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Hash, HashStable, Default, TypeVisitable, TypeFoldable)] pub struct ExternalConstraintsData<'tcx> { // FIXME: implement this. pub region_constraints: QueryRegionConstraints<'tcx>, @@ -226,3 +229,9 @@ impl<'tcx> TypeVisitable> for PredefinedOpaques<'tcx> { self.opaque_types.visit_with(visitor) } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, HashStable)] +pub enum IsNormalizesToHack { + Yes, + No, +} diff --git a/compiler/rustc_middle/src/traits/solve/cache.rs b/compiler/rustc_middle/src/traits/solve/cache.rs new file mode 100644 index 0000000000000..9898b0019bba2 --- /dev/null +++ b/compiler/rustc_middle/src/traits/solve/cache.rs @@ -0,0 +1,100 @@ +use super::{CanonicalInput, QueryResult}; +use crate::ty::TyCtxt; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::sync::Lock; +use rustc_query_system::cache::WithDepNode; +use rustc_query_system::dep_graph::DepNodeIndex; +use rustc_session::Limit; +/// The trait solver cache used by `-Ztrait-solver=next`. +/// +/// FIXME(@lcnr): link to some official documentation of how +/// this works. +#[derive(Default)] +pub struct EvaluationCache<'tcx> { + map: Lock, CacheEntry<'tcx>>>, +} + +pub struct CacheData<'tcx> { + pub result: QueryResult<'tcx>, + pub reached_depth: usize, + pub encountered_overflow: bool, +} + +impl<'tcx> EvaluationCache<'tcx> { + /// Insert a final result into the global cache. + pub fn insert( + &self, + key: CanonicalInput<'tcx>, + reached_depth: usize, + did_overflow: bool, + cycle_participants: FxHashSet>, + dep_node: DepNodeIndex, + result: QueryResult<'tcx>, + ) { + let mut map = self.map.borrow_mut(); + let entry = map.entry(key).or_default(); + let data = WithDepNode::new(dep_node, result); + entry.cycle_participants.extend(cycle_participants); + if did_overflow { + entry.with_overflow.insert(reached_depth, data); + } else { + entry.success = Some(Success { data, reached_depth }); + } + } + + /// Try to fetch a cached result, checking the recursion limit + /// and handling root goals of coinductive cycles. + /// + /// If this returns `Some` the cache result can be used. + pub fn get( + &self, + tcx: TyCtxt<'tcx>, + key: CanonicalInput<'tcx>, + cycle_participant_in_stack: impl FnOnce(&FxHashSet>) -> bool, + available_depth: Limit, + ) -> Option> { + let map = self.map.borrow(); + let entry = map.get(&key)?; + + if cycle_participant_in_stack(&entry.cycle_participants) { + return None; + } + + if let Some(ref success) = entry.success { + if available_depth.value_within_limit(success.reached_depth) { + return Some(CacheData { + result: success.data.get(tcx), + reached_depth: success.reached_depth, + encountered_overflow: false, + }); + } + } + + entry.with_overflow.get(&available_depth.0).map(|e| CacheData { + result: e.get(tcx), + reached_depth: available_depth.0, + encountered_overflow: true, + }) + } +} + +struct Success<'tcx> { + data: WithDepNode>, + reached_depth: usize, +} + +/// The cache entry for a goal `CanonicalInput`. +/// +/// This contains results whose computation never hit the +/// recursion limit in `success`, and all results which hit +/// the recursion limit in `with_overflow`. +#[derive(Default)] +struct CacheEntry<'tcx> { + success: Option>, + /// We have to be careful when caching roots of cycles. + /// + /// See the doc comment of `StackEntry::cycle_participants` for more + /// details. + cycle_participants: FxHashSet>, + with_overflow: FxHashMap>>, +} diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs new file mode 100644 index 0000000000000..4e2af3816ac65 --- /dev/null +++ b/compiler/rustc_middle/src/traits/solve/inspect.rs @@ -0,0 +1,90 @@ +use super::{ + CanonicalInput, Certainty, Goal, IsNormalizesToHack, NoSolution, QueryInput, QueryResult, +}; +use crate::ty; +use format::ProofTreeFormatter; +use std::fmt::{Debug, Write}; + +mod format; + +#[derive(Eq, PartialEq, Debug, Hash, HashStable)] +pub enum CacheHit { + Provisional, + Global, +} + +#[derive(Eq, PartialEq, Hash, HashStable)] +pub struct GoalEvaluation<'tcx> { + pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>, + pub canonicalized_goal: CanonicalInput<'tcx>, + + pub kind: GoalEvaluationKind<'tcx>, + pub is_normalizes_to_hack: IsNormalizesToHack, + pub returned_goals: Vec>>, + + pub result: QueryResult<'tcx>, +} + +#[derive(Eq, PartialEq, Hash, HashStable)] +pub enum GoalEvaluationKind<'tcx> { + CacheHit(CacheHit), + Uncached { revisions: Vec> }, +} +impl Debug for GoalEvaluation<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ProofTreeFormatter::new(f).format_goal_evaluation(self) + } +} + +#[derive(Eq, PartialEq, Hash, HashStable)] +pub struct AddedGoalsEvaluation<'tcx> { + pub evaluations: Vec>>, + pub result: Result, +} +impl Debug for AddedGoalsEvaluation<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ProofTreeFormatter::new(f).format_nested_goal_evaluation(self) + } +} + +#[derive(Eq, PartialEq, Hash, HashStable)] +pub struct GoalEvaluationStep<'tcx> { + pub instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>, + + pub nested_goal_evaluations: Vec>, + pub candidates: Vec>, + + pub result: QueryResult<'tcx>, +} +impl Debug for GoalEvaluationStep<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ProofTreeFormatter::new(f).format_evaluation_step(self) + } +} + +#[derive(Eq, PartialEq, Hash, HashStable)] +pub struct GoalCandidate<'tcx> { + pub nested_goal_evaluations: Vec>, + pub candidates: Vec>, + pub kind: CandidateKind<'tcx>, +} + +#[derive(Eq, PartialEq, Debug, Hash, HashStable)] +pub enum CandidateKind<'tcx> { + /// Probe entered when normalizing the self ty during candidate assembly + NormalizedSelfTyAssembly, + /// A normal candidate for proving a goal + Candidate { name: String, result: QueryResult<'tcx> }, + /// Used in the probe that wraps normalizing the non-self type for the unsize + /// trait, which is also structurally matched on. + UnsizeAssembly, + /// During upcasting from some source object to target object type, used to + /// do a probe to find out what projection type(s) may be used to prove that + /// the source type upholds all of the target type's object bounds. + UpcastProbe, +} +impl Debug for GoalCandidate<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ProofTreeFormatter::new(f).format_candidate(self) + } +} diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs new file mode 100644 index 0000000000000..8759fecb05a2d --- /dev/null +++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs @@ -0,0 +1,143 @@ +use super::*; + +pub(super) struct ProofTreeFormatter<'a, 'b> { + f: &'a mut (dyn Write + 'b), +} + +/// A formatter which adds 4 spaces of indentation to its input before +/// passing it on to its nested formatter. +/// +/// We can use this for arbitrary levels of indentation by nesting it. +struct Indentor<'a, 'b> { + f: &'a mut (dyn Write + 'b), + on_newline: bool, +} + +impl Write for Indentor<'_, '_> { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + for line in s.split_inclusive('\n') { + if self.on_newline { + self.f.write_str(" ")?; + } + self.on_newline = line.ends_with('\n'); + self.f.write_str(line)?; + } + + Ok(()) + } +} + +impl<'a, 'b> ProofTreeFormatter<'a, 'b> { + pub(super) fn new(f: &'a mut (dyn Write + 'b)) -> Self { + ProofTreeFormatter { f } + } + + fn nested(&mut self, func: F) -> R + where + F: FnOnce(&mut ProofTreeFormatter<'_, '_>) -> R, + { + func(&mut ProofTreeFormatter { f: &mut Indentor { f: self.f, on_newline: true } }) + } + + pub(super) fn format_goal_evaluation(&mut self, goal: &GoalEvaluation<'_>) -> std::fmt::Result { + let goal_text = match goal.is_normalizes_to_hack { + IsNormalizesToHack::Yes => "NORMALIZES-TO HACK GOAL", + IsNormalizesToHack::No => "GOAL", + }; + + writeln!(self.f, "{}: {:?}", goal_text, goal.uncanonicalized_goal)?; + writeln!(self.f, "CANONICALIZED: {:?}", goal.canonicalized_goal)?; + + match &goal.kind { + GoalEvaluationKind::CacheHit(CacheHit::Global) => { + writeln!(self.f, "GLOBAL CACHE HIT: {:?}", goal.result) + } + GoalEvaluationKind::CacheHit(CacheHit::Provisional) => { + writeln!(self.f, "PROVISIONAL CACHE HIT: {:?}", goal.result) + } + GoalEvaluationKind::Uncached { revisions } => { + for (n, step) in revisions.iter().enumerate() { + writeln!(self.f, "REVISION {n}: {:?}", step.result)?; + self.nested(|this| this.format_evaluation_step(step))?; + } + writeln!(self.f, "RESULT: {:?}", goal.result) + } + }?; + + if goal.returned_goals.len() > 0 { + writeln!(self.f, "NESTED GOALS ADDED TO CALLER: [")?; + self.nested(|this| { + for goal in goal.returned_goals.iter() { + writeln!(this.f, "ADDED GOAL: {goal:?},")?; + } + Ok(()) + })?; + + writeln!(self.f, "]")?; + } + + Ok(()) + } + + pub(super) fn format_evaluation_step( + &mut self, + evaluation_step: &GoalEvaluationStep<'_>, + ) -> std::fmt::Result { + writeln!(self.f, "INSTANTIATED: {:?}", evaluation_step.instantiated_goal)?; + + for candidate in &evaluation_step.candidates { + self.nested(|this| this.format_candidate(candidate))?; + } + for nested in &evaluation_step.nested_goal_evaluations { + self.nested(|this| this.format_nested_goal_evaluation(nested))?; + } + + Ok(()) + } + + pub(super) fn format_candidate(&mut self, candidate: &GoalCandidate<'_>) -> std::fmt::Result { + match &candidate.kind { + CandidateKind::NormalizedSelfTyAssembly => { + writeln!(self.f, "NORMALIZING SELF TY FOR ASSEMBLY:") + } + CandidateKind::UnsizeAssembly => { + writeln!(self.f, "ASSEMBLING CANDIDATES FOR UNSIZING:") + } + CandidateKind::UpcastProbe => { + writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:") + } + CandidateKind::Candidate { name, result } => { + writeln!(self.f, "CANDIDATE {name}: {result:?}") + } + }?; + + self.nested(|this| { + for candidate in &candidate.candidates { + this.format_candidate(candidate)?; + } + for nested in &candidate.nested_goal_evaluations { + this.format_nested_goal_evaluation(nested)?; + } + Ok(()) + }) + } + + pub(super) fn format_nested_goal_evaluation( + &mut self, + nested_goal_evaluation: &AddedGoalsEvaluation<'_>, + ) -> std::fmt::Result { + writeln!(self.f, "TRY_EVALUATE_ADDED_GOALS: {:?}", nested_goal_evaluation.result)?; + + for (n, revision) in nested_goal_evaluation.evaluations.iter().enumerate() { + writeln!(self.f, "REVISION {n}")?; + self.nested(|this| { + for goal_evaluation in revision { + this.format_goal_evaluation(goal_evaluation)?; + } + Ok(()) + })?; + } + + Ok(()) + } +} diff --git a/compiler/rustc_middle/src/traits/specialization_graph.rs b/compiler/rustc_middle/src/traits/specialization_graph.rs index c016f72275099..e48b46d12c48a 100644 --- a/compiler/rustc_middle/src/traits/specialization_graph.rs +++ b/compiler/rustc_middle/src/traits/specialization_graph.rs @@ -43,7 +43,7 @@ impl Graph { /// The parent of a given impl, which is the `DefId` of the trait when the /// impl is a "specialization root". pub fn parent(&self, child: DefId) -> DefId { - *self.parent.get(&child).unwrap_or_else(|| panic!("Failed to get parent for {:?}", child)) + *self.parent.get(&child).unwrap_or_else(|| panic!("Failed to get parent for {child:?}")) } } @@ -228,7 +228,7 @@ impl<'tcx> Ancestors<'tcx> { if let Some(item) = node.item(tcx, trait_item_def_id) { if finalizing_node.is_none() { let is_specializable = item.defaultness(tcx).is_default() - || tcx.impl_defaultness(node.def_id()).is_default(); + || tcx.defaultness(node.def_id()).is_default(); if !is_specializable { finalizing_node = Some(node); @@ -259,7 +259,9 @@ pub fn ancestors( if let Some(reported) = specialization_graph.has_errored { Err(reported) - } else if let Err(reported) = tcx.type_of(start_from_impl).subst_identity().error_reported() { + } else if let Err(reported) = + tcx.type_of(start_from_impl).instantiate_identity().error_reported() + { Err(reported) } else { Ok(Ancestors { diff --git a/compiler/rustc_middle/src/traits/structural_impls.rs b/compiler/rustc_middle/src/traits/structural_impls.rs index 6acb7745d654e..ec450cf559006 100644 --- a/compiler/rustc_middle/src/traits/structural_impls.rs +++ b/compiler/rustc_middle/src/traits/structural_impls.rs @@ -6,32 +6,16 @@ use std::fmt; impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSource<'tcx, N> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - super::ImplSource::UserDefined(ref v) => write!(f, "{:?}", v), + match self { + super::ImplSource::UserDefined(v) => write!(f, "{v:?}"), - super::ImplSource::AutoImpl(ref t) => write!(f, "{:?}", t), - - super::ImplSource::Closure(ref d) => write!(f, "{:?}", d), - - super::ImplSource::Generator(ref d) => write!(f, "{:?}", d), - - super::ImplSource::Future(ref d) => write!(f, "{:?}", d), - - super::ImplSource::FnPointer(ref d) => write!(f, "({:?})", d), - - super::ImplSource::Object(ref d) => write!(f, "{:?}", d), - - super::ImplSource::Param(ref n, ct) => { - write!(f, "ImplSourceParamData({:?}, {:?})", n, ct) + super::ImplSource::Builtin(source, d) => { + write!(f, "Builtin({source:?}, {d:?})") } - super::ImplSource::Builtin(ref d) => write!(f, "{:?}", d), - - super::ImplSource::TraitAlias(ref d) => write!(f, "{:?}", d), - - super::ImplSource::TraitUpcasting(ref d) => write!(f, "{:?}", d), - - super::ImplSource::ConstDestruct(ref d) => write!(f, "{:?}", d), + super::ImplSource::Param(n) => { + write!(f, "ImplSourceParamData({n:?})") + } } } } @@ -40,96 +24,8 @@ impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceUserDefinedData<'tcx, fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "ImplSourceUserDefinedData(impl_def_id={:?}, substs={:?}, nested={:?})", - self.impl_def_id, self.substs, self.nested - ) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceGeneratorData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceGeneratorData(generator_def_id={:?}, substs={:?}, nested={:?})", - self.generator_def_id, self.substs, self.nested - ) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceFutureData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceFutureData(generator_def_id={:?}, substs={:?}, nested={:?})", - self.generator_def_id, self.substs, self.nested - ) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceClosureData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceClosureData(closure_def_id={:?}, substs={:?}, nested={:?})", - self.closure_def_id, self.substs, self.nested + "ImplSourceUserDefinedData(impl_def_id={:?}, args={:?}, nested={:?})", + self.impl_def_id, self.args, self.nested ) } } - -impl fmt::Debug for traits::ImplSourceBuiltinData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ImplSourceBuiltinData(nested={:?})", self.nested) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceTraitUpcastingData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceTraitUpcastingData(upcast={:?}, vtable_vptr_slot={:?}, nested={:?})", - self.upcast_trait_ref, self.vtable_vptr_slot, self.nested - ) - } -} - -impl fmt::Debug for traits::ImplSourceAutoImplData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceAutoImplData(trait_def_id={:?}, nested={:?})", - self.trait_def_id, self.nested - ) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceObjectData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceObjectData(upcast={:?}, vtable_base={}, nested={:?})", - self.upcast_trait_ref, self.vtable_base, self.nested - ) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceFnPointerData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ImplSourceFnPointerData(fn_ty={:?}, nested={:?})", self.fn_ty, self.nested) - } -} - -impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSourceTraitAliasData<'tcx, N> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ImplSourceTraitAliasData(alias_def_id={:?}, substs={:?}, nested={:?})", - self.alias_def_id, self.substs, self.nested - ) - } -} - -impl fmt::Debug for traits::ImplSourceConstDestructData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ImplSourceConstDestructData(nested={:?})", self.nested) - } -} diff --git a/compiler/rustc_middle/src/traits/util.rs b/compiler/rustc_middle/src/traits/util.rs index d54b8c599d954..05c06efaf1637 100644 --- a/compiler/rustc_middle/src/traits/util.rs +++ b/compiler/rustc_middle/src/traits/util.rs @@ -25,9 +25,7 @@ impl<'tcx> Elaborator<'tcx> { .super_predicates_of(trait_ref.def_id()) .predicates .into_iter() - .flat_map(|(pred, _)| { - pred.subst_supertrait(self.tcx, &trait_ref).to_opt_poly_trait_pred() - }) + .flat_map(|(pred, _)| pred.subst_supertrait(self.tcx, &trait_ref).as_trait_clause()) .map(|t| t.map_bound(|pred| pred.trait_ref)) .filter(|supertrait_ref| self.visited.insert(*supertrait_ref)); diff --git a/compiler/rustc_middle/src/ty/_match.rs b/compiler/rustc_middle/src/ty/_match.rs index cbc68fde9d905..09517200b0ddc 100644 --- a/compiler/rustc_middle/src/ty/_match.rs +++ b/compiler/rustc_middle/src/ty/_match.rs @@ -81,7 +81,7 @@ impl<'tcx> TypeRelation<'tcx> for Match<'tcx> { Err(TypeError::Sorts(relate::expected_found(self, a, b))) } - (&ty::Error(guar), _) | (_, &ty::Error(guar)) => Ok(self.tcx().ty_error(guar)), + (&ty::Error(guar), _) | (_, &ty::Error(guar)) => Ok(Ty::new_error(self.tcx(), guar)), _ => relate::structurally_relate_tys(self, a, b), } diff --git a/compiler/rustc_middle/src/ty/abstract_const.rs b/compiler/rustc_middle/src/ty/abstract_const.rs index a39631da93666..cdd8351499b41 100644 --- a/compiler/rustc_middle/src/ty/abstract_const.rs +++ b/compiler/rustc_middle/src/ty/abstract_const.rs @@ -27,9 +27,7 @@ impl From for NotConstEvaluatable { } } -TrivialTypeTraversalAndLiftImpls! { - NotConstEvaluatable, -} +TrivialTypeTraversalAndLiftImpls! { NotConstEvaluatable } pub type BoundAbstractConst<'tcx> = Result>>, ErrorGuaranteed>; @@ -53,10 +51,10 @@ impl<'tcx> TyCtxt<'tcx> { fn fold_const(&mut self, c: Const<'tcx>) -> Const<'tcx> { let ct = match c.kind() { ty::ConstKind::Unevaluated(uv) => match self.tcx.thir_abstract_const(uv.def) { - Err(e) => self.tcx.const_error(c.ty(), e), + Err(e) => ty::Const::new_error(self.tcx, e, c.ty()), Ok(Some(bac)) => { - let substs = self.tcx.erase_regions(uv.substs); - let bac = bac.subst(self.tcx, substs); + let args = self.tcx.erase_regions(uv.args); + let bac = bac.instantiate(self.tcx, args); return bac.fold_with(self); } Ok(None) => c, diff --git a/compiler/rustc_middle/src/ty/adjustment.rs b/compiler/rustc_middle/src/ty/adjustment.rs index cd0f7e8daf1d6..76931ceaa69fa 100644 --- a/compiler/rustc_middle/src/ty/adjustment.rs +++ b/compiler/rustc_middle/src/ty/adjustment.rs @@ -6,7 +6,7 @@ use rustc_span::Span; use rustc_target::abi::FieldIdx; #[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] -pub enum PointerCast { +pub enum PointerCoercion { /// Go from a fn-item type to a fn-pointer type. ReifyFnPointer, @@ -99,7 +99,7 @@ pub enum Adjust<'tcx> { /// Take the address and produce either a `&` or `*` pointer. Borrow(AutoBorrow<'tcx>), - Pointer(PointerCast), + Pointer(PointerCoercion), /// Cast into a dyn* object. DynStar, @@ -132,7 +132,7 @@ impl<'tcx> OverloadedDeref<'tcx> { .find(|m| m.kind == ty::AssocKind::Fn) .unwrap() .def_id; - tcx.mk_fn_def(method_def_id, [source]) + Ty::new_fn_def(tcx, method_def_id, [source]) } } diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index 7c5c030c2764e..b4c6e0d970a51 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -448,7 +448,7 @@ impl<'tcx> AdtDef<'tcx> { Res::Def(DefKind::Ctor(..), cid) => self.variant_with_ctor_id(cid), Res::Def(DefKind::Struct, _) | Res::Def(DefKind::Union, _) - | Res::Def(DefKind::TyAlias, _) + | Res::Def(DefKind::TyAlias { .. }, _) | Res::Def(DefKind::AssocTy, _) | Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } @@ -562,18 +562,10 @@ impl<'tcx> AdtDef<'tcx> { tcx.adt_destructor(self.did()) } - /// Returns a list of types such that `Self: Sized` if and only - /// if that type is `Sized`, or `TyErr` if this type is recursive. - /// - /// Oddly enough, checking that the sized-constraint is `Sized` is - /// actually more expressive than checking all members: - /// the `Sized` trait is inductive, so an associated type that references - /// `Self` would prevent its containing ADT from being `Sized`. - /// - /// Due to normalization being eager, this applies even if - /// the associated type is behind a pointer (e.g., issue #31299). - pub fn sized_constraint(self, tcx: TyCtxt<'tcx>) -> ty::EarlyBinder<&'tcx [Ty<'tcx>]> { - ty::EarlyBinder(tcx.adt_sized_constraint(self.did())) + /// Returns a list of types such that `Self: Sized` if and only if that + /// type is `Sized`, or `ty::Error` if this type has a recursive layout. + pub fn sized_constraint(self, tcx: TyCtxt<'tcx>) -> ty::EarlyBinder<&'tcx ty::List>> { + tcx.adt_sized_constraint(self.did()) } } diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index 090b769323add..f77a8c6712efa 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -48,7 +48,7 @@ impl AssocItem { /// /// [`type_of`]: crate::ty::TyCtxt::type_of pub fn defaultness(&self, tcx: TyCtxt<'_>) -> hir::Defaultness { - tcx.impl_defaultness(self.def_id) + tcx.defaultness(self.def_id) } #[inline] @@ -84,14 +84,22 @@ impl AssocItem { // late-bound regions, and we don't want method signatures to show up // `as for<'r> fn(&'r MyType)`. Pretty-printing handles late-bound // regions just fine, showing `fn(&MyType)`. - tcx.fn_sig(self.def_id).subst_identity().skip_binder().to_string() + tcx.fn_sig(self.def_id).instantiate_identity().skip_binder().to_string() } ty::AssocKind::Type => format!("type {};", self.name), ty::AssocKind::Const => { - format!("const {}: {:?};", self.name, tcx.type_of(self.def_id).subst_identity()) + format!( + "const {}: {:?};", + self.name, + tcx.type_of(self.def_id).instantiate_identity() + ) } } } + + pub fn is_impl_trait_in_trait(&self) -> bool { + self.opt_rpitit_info.is_some() + } } #[derive(Copy, Clone, PartialEq, Debug, HashStable, Eq, Hash, Encodable, Decodable)] diff --git a/compiler/rustc_middle/src/ty/binding.rs b/compiler/rustc_middle/src/ty/binding.rs index a5b05a4f9b526..2fec8ac90956e 100644 --- a/compiler/rustc_middle/src/ty/binding.rs +++ b/compiler/rustc_middle/src/ty/binding.rs @@ -6,7 +6,7 @@ pub enum BindingMode { BindByValue(Mutability), } -TrivialTypeTraversalAndLiftImpls! { BindingMode, } +TrivialTypeTraversalAndLiftImpls! { BindingMode } impl BindingMode { pub fn convert(BindingAnnotation(by_ref, mutbl): BindingAnnotation) -> BindingMode { diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs index be7b2b7ec671a..74bdd07a1c946 100644 --- a/compiler/rustc_middle/src/ty/closure.rs +++ b/compiler/rustc_middle/src/ty/closure.rs @@ -6,9 +6,11 @@ use crate::{mir, ty}; use std::fmt::Write; use crate::query::Providers; -use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{self as hir, LangItem}; +use rustc_span::def_id::LocalDefIdMap; use rustc_span::symbol::Ident; use rustc_span::{Span, Symbol}; @@ -56,12 +58,9 @@ pub enum UpvarCapture { ByRef(BorrowKind), } -pub type UpvarListMap = FxHashMap>; -pub type UpvarCaptureMap = FxHashMap; - /// Given the closure DefId this map provides a map of root variables to minimum /// set of `CapturedPlace`s that need to be tracked to support all captures of that closure. -pub type MinCaptureInformationMap<'tcx> = FxHashMap>; +pub type MinCaptureInformationMap<'tcx> = LocalDefIdMap>; /// Part of `MinCaptureInformationMap`; Maps a root variable to the list of `CapturedPlace`. /// Used to track the minimum set of `Place`s that need to be captured to support all @@ -91,10 +90,18 @@ pub enum ClosureKind { FnOnce, } -impl<'tcx> ClosureKind { +impl ClosureKind { /// This is the initial value used when doing upvar inference. pub const LATTICE_BOTTOM: ClosureKind = ClosureKind::Fn; + pub const fn as_str(self) -> &'static str { + match self { + ClosureKind::Fn => "Fn", + ClosureKind::FnMut => "FnMut", + ClosureKind::FnOnce => "FnOnce", + } + } + /// Returns `true` if a type that impls this closure kind /// must also implement `other`. pub fn extends(self, other: ty::ClosureKind) -> bool { @@ -117,7 +124,7 @@ impl<'tcx> ClosureKind { /// Returns the representative scalar type for this closure kind. /// See `Ty::to_opt_closure_kind` for more details. - pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + pub fn to_ty<'tcx>(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { match self { ClosureKind::Fn => tcx.types.i8, ClosureKind::FnMut => tcx.types.i16, @@ -126,6 +133,12 @@ impl<'tcx> ClosureKind { } } +impl IntoDiagnosticArg for ClosureKind { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(self.as_str().into()) + } +} + /// A composite describing a `Place` that is captured by a closure. #[derive(PartialEq, Clone, Debug, TyEncodable, TyDecodable, HashStable)] #[derive(TypeFoldable, TypeVisitable)] @@ -176,6 +189,8 @@ impl<'tcx> CapturedPlace<'tcx> { // Ignore derefs for now, as they are likely caused by // autoderefs that don't appear in the original code. HirProjectionKind::Deref => {} + // Just change the type to the hidden type, so we can actually project. + HirProjectionKind::OpaqueCast => {} proj => bug!("Unexpected projection {:?} in captured place", proj), } ty = proj.ty; @@ -350,7 +365,7 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc for (i, proj) in place.projections.iter().enumerate() { match proj.kind { HirProjectionKind::Deref => { - curr_string = format!("*{}", curr_string); + curr_string = format!("*{curr_string}"); } HirProjectionKind::Field(idx, variant) => match place.ty_before_projection(i).kind() { ty::Adt(def, ..) => { @@ -427,6 +442,8 @@ pub enum BorrowKind { /// immutable, but not aliasable. This solves the problem. For /// simplicity, we don't give users the way to express this /// borrow, it's just used when translating closures. + /// + /// FIXME: Rename this to indicate the borrow is actually not immutable. UniqueImmBorrow, /// Data is mutable and not aliasable. diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 7fc75674da537..7c05deae90a58 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -13,7 +13,7 @@ use crate::mir::{ interpret::{AllocId, ConstAllocation}, }; use crate::traits; -use crate::ty::subst::SubstsRef; +use crate::ty::GenericArgsRef; use crate::ty::{self, AdtDef, Ty}; use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty::TyCtxt; @@ -128,6 +128,12 @@ impl<'tcx, E: TyEncoder>> Encodable for ty::Predicate<'tcx> } } +impl<'tcx, E: TyEncoder>> Encodable for ty::Clause<'tcx> { + fn encode(&self, e: &mut E) { + self.as_predicate().encode(e); + } +} + impl<'tcx, E: TyEncoder>> Encodable for ty::Region<'tcx> { fn encode(&self, e: &mut E) { self.kind().encode(e); @@ -162,7 +168,6 @@ impl<'tcx, E: TyEncoder>> Encodable for ty::ParamEnv<'tcx> { fn encode(&self, e: &mut E) { self.caller_bounds().encode(e); self.reveal().encode(e); - self.constness().encode(e); } } @@ -241,12 +246,19 @@ impl<'tcx, D: TyDecoder>> Decodable for ty::Predicate<'tcx> } } -impl<'tcx, D: TyDecoder>> Decodable for SubstsRef<'tcx> { +impl<'tcx, D: TyDecoder>> Decodable for ty::Clause<'tcx> { + fn decode(decoder: &mut D) -> ty::Clause<'tcx> { + let pred: ty::Predicate<'tcx> = Decodable::decode(decoder); + pred.expect_clause() + } +} + +impl<'tcx, D: TyDecoder>> Decodable for GenericArgsRef<'tcx> { fn decode(decoder: &mut D) -> Self { let len = decoder.read_usize(); let tcx = decoder.interner(); - tcx.mk_substs_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), + tcx.mk_args_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), ) } } @@ -264,7 +276,7 @@ impl<'tcx, D: TyDecoder>> Decodable for mir::Place<'tcx> { impl<'tcx, D: TyDecoder>> Decodable for ty::Region<'tcx> { fn decode(decoder: &mut D) -> Self { - decoder.interner().mk_region_from_kind(Decodable::decode(decoder)) + ty::Region::new_from_kind(decoder.interner(), Decodable::decode(decoder)) } } @@ -293,8 +305,7 @@ impl<'tcx, D: TyDecoder>> Decodable for ty::ParamEnv<'tcx> { fn decode(d: &mut D) -> Self { let caller_bounds = Decodable::decode(d); let reveal = Decodable::decode(d); - let constness = Decodable::decode(d); - ty::ParamEnv::new(caller_bounds, reveal, constness) + ty::ParamEnv::new(caller_bounds, reveal) } } @@ -331,7 +342,7 @@ impl<'tcx, D: TyDecoder>> RefDecodable<'tcx, D> impl<'tcx, D: TyDecoder>> Decodable for ty::Const<'tcx> { fn decode(decoder: &mut D) -> Self { let consts: ty::ConstData<'tcx> = Decodable::decode(decoder); - decoder.interner().mk_const(consts.kind, consts.ty) + decoder.interner().mk_ct_from_kind(consts.kind, consts.ty) } } @@ -355,16 +366,6 @@ impl<'tcx, D: TyDecoder>> Decodable for AdtDef<'tcx> { } } -impl<'tcx, D: TyDecoder>> RefDecodable<'tcx, D> - for [(ty::Predicate<'tcx>, Span)] -{ - fn decode(decoder: &mut D) -> &'tcx Self { - decoder.interner().arena.alloc_from_iter( - (0..decoder.read_usize()).map(|_| Decodable::decode(decoder)).collect::>(), - ) - } -} - impl<'tcx, D: TyDecoder>> RefDecodable<'tcx, D> for [(ty::Clause<'tcx>, Span)] { fn decode(decoder: &mut D) -> &'tcx Self { decoder.interner().arena.alloc_from_iter( @@ -393,11 +394,11 @@ impl<'tcx, D: TyDecoder>> RefDecodable<'tcx, D> for ty::List>> RefDecodable<'tcx, D> for ty::List> { +impl<'tcx, D: TyDecoder>> RefDecodable<'tcx, D> for ty::List> { fn decode(decoder: &mut D) -> &'tcx Self { let len = decoder.read_usize(); - decoder.interner().mk_predicates_from_iter( - (0..len).map::, _>(|_| Decodable::decode(decoder)), + decoder.interner().mk_clauses_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), ) } } @@ -421,7 +422,7 @@ impl_decodable_via_ref! { &'tcx mir::BorrowCheckResult<'tcx>, &'tcx mir::coverage::CodeRegion, &'tcx ty::List, - &'tcx ty::List>, + &'tcx ty::List>, &'tcx ty::List, } diff --git a/compiler/rustc_middle/src/ty/consts.rs b/compiler/rustc_middle/src/ty/consts.rs index 1a4bd14815f93..cce10417e1b0c 100644 --- a/compiler/rustc_middle/src/ty/consts.rs +++ b/compiler/rustc_middle/src/ty/consts.rs @@ -1,7 +1,8 @@ use crate::middle::resolve_bound_vars as rbv; -use crate::mir::interpret::LitToConstInput; -use crate::ty::{self, InternalSubsts, ParamEnv, ParamEnvAnd, Ty, TyCtxt}; +use crate::mir::interpret::{AllocId, ConstValue, LitToConstInput, Scalar}; +use crate::ty::{self, GenericArgs, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt}; use rustc_data_structures::intern::Interned; +use rustc_error_messages::MultiSpan; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::LocalDefId; @@ -13,8 +14,13 @@ mod valtree; pub use int::*; pub use kind::*; +use rustc_span::ErrorGuaranteed; +use rustc_span::DUMMY_SP; +use rustc_target::abi::Size; pub use valtree::*; +use super::sty::ConstKind; + /// Use this rather than `ConstData`, whenever possible. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, HashStable)] #[rustc_pass_by_value] @@ -30,6 +36,16 @@ pub struct ConstData<'tcx> { #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] static_assert_size!(ConstData<'_>, 40); +enum EvalMode { + Typeck, + Mir, +} + +enum EvalResult<'tcx> { + ValTree(ty::ValTree<'tcx>), + ConstVal(ConstValue<'tcx>), +} + impl<'tcx> Const<'tcx> { #[inline] pub fn ty(self) -> Ty<'tcx> { @@ -38,7 +54,98 @@ impl<'tcx> Const<'tcx> { #[inline] pub fn kind(self) -> ConstKind<'tcx> { - self.0.kind + self.0.kind.clone() + } + + #[inline] + pub fn new(tcx: TyCtxt<'tcx>, kind: ty::ConstKind<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + tcx.mk_ct_from_kind(kind, ty) + } + + #[inline] + pub fn new_param(tcx: TyCtxt<'tcx>, param: ty::ParamConst, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Param(param), ty) + } + + #[inline] + pub fn new_var(tcx: TyCtxt<'tcx>, infer: ty::ConstVid<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Infer(ty::InferConst::Var(infer)), ty) + } + + #[inline] + pub fn new_fresh(tcx: TyCtxt<'tcx>, fresh: u32, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Infer(ty::InferConst::Fresh(fresh)), ty) + } + + #[inline] + pub fn new_infer(tcx: TyCtxt<'tcx>, infer: ty::InferConst<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Infer(infer), ty) + } + + #[inline] + pub fn new_bound( + tcx: TyCtxt<'tcx>, + debruijn: ty::DebruijnIndex, + var: ty::BoundVar, + ty: Ty<'tcx>, + ) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Bound(debruijn, var), ty) + } + + #[inline] + pub fn new_placeholder( + tcx: TyCtxt<'tcx>, + placeholder: ty::PlaceholderConst<'tcx>, + ty: Ty<'tcx>, + ) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Placeholder(placeholder), ty) + } + + #[inline] + pub fn new_unevaluated( + tcx: TyCtxt<'tcx>, + uv: ty::UnevaluatedConst<'tcx>, + ty: Ty<'tcx>, + ) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Unevaluated(uv), ty) + } + + #[inline] + pub fn new_value(tcx: TyCtxt<'tcx>, val: ty::ValTree<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Value(val), ty) + } + + #[inline] + pub fn new_expr(tcx: TyCtxt<'tcx>, expr: ty::Expr<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Expr(expr), ty) + } + + #[inline] + pub fn new_error(tcx: TyCtxt<'tcx>, e: ty::ErrorGuaranteed, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new(tcx, ty::ConstKind::Error(e), ty) + } + + /// Like [Ty::new_error] but for constants. + #[track_caller] + pub fn new_misc_error(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + Const::new_error_with_message( + tcx, + ty, + DUMMY_SP, + "ty::ConstKind::Error constructed but no error reported", + ) + } + + /// Like [Ty::new_error_with_message] but for constants. + #[track_caller] + pub fn new_error_with_message>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + span: S, + msg: &'static str, + ) -> Const<'tcx> { + let reported = tcx.sess.delay_span_bug(span, msg); + Const::new_error(tcx, reported, ty) } /// Literals and const generic parameters are eagerly converted to a constant, everything else @@ -60,10 +167,11 @@ impl<'tcx> Const<'tcx> { match Self::try_eval_lit_or_param(tcx, ty, expr) { Some(v) => v, - None => tcx.mk_const( + None => ty::Const::new_unevaluated( + tcx, ty::UnevaluatedConst { def: def.to_def_id(), - substs: InternalSubsts::identity_for_item(tcx, def.to_def_id()), + args: GenericArgs::identity_for_item(tcx, def.to_def_id()), }, ty, ), @@ -104,7 +212,7 @@ impl<'tcx> Const<'tcx> { Err(e) => { tcx.sess.delay_span_bug( expr.span, - format!("Const::from_anon_const: couldn't lit_to_const {:?}", e), + format!("Const::from_anon_const: couldn't lit_to_const {e:?}"), ); } } @@ -117,7 +225,7 @@ impl<'tcx> Const<'tcx> { )) => { // Use the type from the param's definition, since we can resolve it, // not the expected parameter type from WithOptConstParam. - let param_ty = tcx.type_of(def_id).subst_identity(); + let param_ty = tcx.type_of(def_id).instantiate_identity(); match tcx.named_bound_var(expr.hir_id) { Some(rbv::ResolvedArg::EarlyBound(_)) => { // Find the name and index of the const parameter by indexing the generics of @@ -126,13 +234,19 @@ impl<'tcx> Const<'tcx> { let generics = tcx.generics_of(item_def_id); let index = generics.param_def_id_to_index[&def_id]; let name = tcx.item_name(def_id); - Some(tcx.mk_const(ty::ParamConst::new(index, name), param_ty)) + Some(ty::Const::new_param(tcx, ty::ParamConst::new(index, name), param_ty)) + } + Some(rbv::ResolvedArg::LateBound(debruijn, index, _)) => { + Some(ty::Const::new_bound( + tcx, + debruijn, + ty::BoundVar::from_u32(index), + param_ty, + )) + } + Some(rbv::ResolvedArg::Error(guar)) => { + Some(ty::Const::new_error(tcx, guar, param_ty)) } - Some(rbv::ResolvedArg::LateBound(debruijn, index, _)) => Some(tcx.mk_const( - ty::ConstKind::Bound(debruijn, ty::BoundVar::from_u32(index)), - param_ty, - )), - Some(rbv::ResolvedArg::Error(guar)) => Some(tcx.const_error(param_ty, guar)), arg => bug!("unexpected bound var resolution for {:?}: {arg:?}", expr.hir_id), } } @@ -153,9 +267,10 @@ impl<'tcx> Const<'tcx> { pub fn from_bits(tcx: TyCtxt<'tcx>, bits: u128, ty: ParamEnvAnd<'tcx, Ty<'tcx>>) -> Self { let size = tcx .layout_of(ty) - .unwrap_or_else(|e| panic!("could not compute layout for {:?}: {:?}", ty, e)) + .unwrap_or_else(|e| panic!("could not compute layout for {ty:?}: {e:?}")) .size; - tcx.mk_const( + ty::Const::new_value( + tcx, ty::ValTree::from_scalar_int(ScalarInt::try_from_uint(bits, size).unwrap()), ty.value, ) @@ -164,7 +279,7 @@ impl<'tcx> Const<'tcx> { #[inline] /// Creates an interned zst constant. pub fn zero_sized(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Self { - tcx.mk_const(ty::ValTree::zst(), ty) + ty::Const::new_value(tcx, ty::ValTree::zst(), ty) } #[inline] @@ -179,6 +294,14 @@ impl<'tcx> Const<'tcx> { Self::from_bits(tcx, n as u128, ParamEnv::empty().and(tcx.types.usize)) } + /// Attempts to convert to a `ValTree` + pub fn try_to_valtree(self) -> Option> { + match self.kind() { + ty::ConstKind::Value(valtree) => Some(valtree), + _ => None, + } + } + #[inline] /// Attempts to evaluate the given constant to bits. Can fail to evaluate in the presence of /// generics (or erroneous code) or if the value can't be represented as bits (e.g. because it @@ -192,12 +315,12 @@ impl<'tcx> Const<'tcx> { assert_eq!(self.ty(), ty); let size = tcx.layout_of(param_env.with_reveal_all_normalized(tcx).and(ty)).ok()?.size; // if `ty` does not depend on generic parameters, use an empty param_env - self.kind().eval(tcx, param_env).try_to_bits(size) + self.eval(tcx, param_env).try_to_bits(size) } #[inline] pub fn try_eval_bool(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option { - self.kind().eval(tcx, param_env).try_to_bool() + self.eval(tcx, param_env).try_to_bool() } #[inline] @@ -206,17 +329,17 @@ impl<'tcx> Const<'tcx> { tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ) -> Option { - self.kind().eval(tcx, param_env).try_to_target_usize(tcx) + self.eval(tcx, param_env).try_to_target_usize(tcx) } #[inline] /// Tries to evaluate the constant if it is `Unevaluated`. If that doesn't succeed, return the /// unevaluated constant. pub fn eval(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Const<'tcx> { - if let Some(val) = self.kind().try_eval_for_typeck(tcx, param_env) { + if let Some(val) = self.try_eval_for_typeck(tcx, param_env) { match val { - Ok(val) => tcx.mk_const(val, self.ty()), - Err(guar) => tcx.const_error(self.ty(), guar), + Ok(val) => ty::Const::new_value(tcx, val, self.ty()), + Err(guar) => ty::Const::new_error(tcx, guar, self.ty()), } } else { // Either the constant isn't evaluatable or ValTree creation failed. @@ -238,6 +361,138 @@ impl<'tcx> Const<'tcx> { .unwrap_or_else(|| bug!("expected usize, got {:#?}", self)) } + #[inline] + /// Tries to evaluate the constant if it is `Unevaluated`. If that isn't possible or necessary + /// return `None`. + // FIXME(@lcnr): Completely rework the evaluation/normalization system for `ty::Const` once valtrees are merged. + pub fn try_eval_for_mir( + self, + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ) -> Option, ErrorGuaranteed>> { + match self.try_eval_inner(tcx, param_env, EvalMode::Mir) { + Some(Ok(EvalResult::ValTree(_))) => unreachable!(), + Some(Ok(EvalResult::ConstVal(v))) => Some(Ok(v)), + Some(Err(e)) => Some(Err(e)), + None => None, + } + } + + #[inline] + /// Tries to evaluate the constant if it is `Unevaluated`. If that isn't possible or necessary + /// return `None`. + // FIXME(@lcnr): Completely rework the evaluation/normalization system for `ty::Const` once valtrees are merged. + pub fn try_eval_for_typeck( + self, + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ) -> Option, ErrorGuaranteed>> { + match self.try_eval_inner(tcx, param_env, EvalMode::Typeck) { + Some(Ok(EvalResult::ValTree(v))) => Some(Ok(v)), + Some(Ok(EvalResult::ConstVal(_))) => unreachable!(), + Some(Err(e)) => Some(Err(e)), + None => None, + } + } + + #[inline] + fn try_eval_inner( + self, + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + eval_mode: EvalMode, + ) -> Option, ErrorGuaranteed>> { + assert!(!self.has_escaping_bound_vars(), "escaping vars in {self:?}"); + if let ConstKind::Unevaluated(unevaluated) = self.kind() { + use crate::mir::interpret::ErrorHandled; + + // HACK(eddyb) this erases lifetimes even though `const_eval_resolve` + // also does later, but we want to do it before checking for + // inference variables. + // Note that we erase regions *before* calling `with_reveal_all_normalized`, + // so that we don't try to invoke this query with + // any region variables. + + // HACK(eddyb) when the query key would contain inference variables, + // attempt using identity args and `ParamEnv` instead, that will succeed + // when the expression doesn't depend on any parameters. + // FIXME(eddyb, skinny121) pass `InferCtxt` into here when it's available, so that + // we can call `infcx.const_eval_resolve` which handles inference variables. + let param_env_and = if (param_env, unevaluated).has_non_region_infer() { + tcx.param_env(unevaluated.def).and(ty::UnevaluatedConst { + def: unevaluated.def, + args: GenericArgs::identity_for_item(tcx, unevaluated.def), + }) + } else { + tcx.erase_regions(param_env) + .with_reveal_all_normalized(tcx) + .and(tcx.erase_regions(unevaluated)) + }; + + // FIXME(eddyb) maybe the `const_eval_*` methods should take + // `ty::ParamEnvAnd` instead of having them separate. + let (param_env, unevaluated) = param_env_and.into_parts(); + // try to resolve e.g. associated constants to their definition on an impl, and then + // evaluate the const. + match eval_mode { + EvalMode::Typeck => { + match tcx.const_eval_resolve_for_typeck(param_env, unevaluated, None) { + // NOTE(eddyb) `val` contains no lifetimes/types/consts, + // and we use the original type, so nothing from `args` + // (which may be identity args, see above), + // can leak through `val` into the const we return. + Ok(val) => Some(Ok(EvalResult::ValTree(val?))), + Err(ErrorHandled::TooGeneric) => None, + Err(ErrorHandled::Reported(e)) => Some(Err(e.into())), + } + } + EvalMode::Mir => { + match tcx.const_eval_resolve(param_env, unevaluated.expand(), None) { + // NOTE(eddyb) `val` contains no lifetimes/types/consts, + // and we use the original type, so nothing from `args` + // (which may be identity args, see above), + // can leak through `val` into the const we return. + Ok(val) => Some(Ok(EvalResult::ConstVal(val))), + Err(ErrorHandled::TooGeneric) => None, + Err(ErrorHandled::Reported(e)) => Some(Err(e.into())), + } + } + } + } else { + None + } + } + + #[inline] + pub fn try_to_value(self) -> Option> { + if let ConstKind::Value(val) = self.kind() { Some(val) } else { None } + } + + #[inline] + pub fn try_to_scalar(self) -> Option> { + self.try_to_value()?.try_to_scalar() + } + + #[inline] + pub fn try_to_scalar_int(self) -> Option { + self.try_to_value()?.try_to_scalar_int() + } + + #[inline] + pub fn try_to_bits(self, size: Size) -> Option { + self.try_to_scalar_int()?.to_bits(size).ok() + } + + #[inline] + pub fn try_to_bool(self) -> Option { + self.try_to_scalar_int()?.try_into().ok() + } + + #[inline] + pub fn try_to_target_usize(self, tcx: TyCtxt<'tcx>) -> Option { + self.try_to_value()?.try_to_target_usize(tcx) + } + pub fn is_ct_infer(self) -> bool { matches!(self.kind(), ty::ConstKind::Infer(_)) } @@ -254,5 +509,5 @@ pub fn const_param_default(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBind "`const_param_default` expected a generic parameter with a constant" ), }; - ty::EarlyBinder(Const::from_anon_const(tcx, default_def_id)) + ty::EarlyBinder::bind(Const::from_anon_const(tcx, default_def_id)) } diff --git a/compiler/rustc_middle/src/ty/consts/int.rs b/compiler/rustc_middle/src/ty/consts/int.rs index d1dbc531edfc1..b16163edf1448 100644 --- a/compiler/rustc_middle/src/ty/consts/int.rs +++ b/compiler/rustc_middle/src/ty/consts/int.rs @@ -1,5 +1,6 @@ use rustc_apfloat::ieee::{Double, Single}; use rustc_apfloat::Float; +use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use rustc_target::abi::Size; use std::fmt; @@ -113,6 +114,14 @@ impl std::fmt::Debug for ConstInt { } } +impl IntoDiagnosticArg for ConstInt { + // FIXME this simply uses the Debug impl, but we could probably do better by converting both + // to an inherent method that returns `Cow`. + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(format!("{self:?}").into()) + } +} + /// The raw bytes of a simple value. /// /// This is a packed struct in order to allow this type to be optimally embedded in enums @@ -409,7 +418,7 @@ impl TryFrom for char { #[inline] fn try_from(int: ScalarInt) -> Result { - let Ok(bits) = int.to_bits(Size::from_bytes(std::mem::size_of::())) else { + let Ok(bits) = int.to_bits(Size::from_bytes(std::mem::size_of::())) else { return Err(CharTryFromScalarInt); }; match char::from_u32(bits.try_into().unwrap()) { @@ -454,7 +463,7 @@ impl TryFrom for Double { impl fmt::Debug for ScalarInt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Dispatch to LowerHex below. - write!(f, "0x{:x}", self) + write!(f, "0x{self:x}") } } diff --git a/compiler/rustc_middle/src/ty/consts/kind.rs b/compiler/rustc_middle/src/ty/consts/kind.rs index 1dd4f8a243741..db4a15fbee538 100644 --- a/compiler/rustc_middle/src/ty/consts/kind.rs +++ b/compiler/rustc_middle/src/ty/consts/kind.rs @@ -1,86 +1,41 @@ use super::Const; use crate::mir; -use crate::mir::interpret::{AllocId, ConstValue, Scalar}; use crate::ty::abstract_const::CastKind; -use crate::ty::subst::{InternalSubsts, SubstsRef}; -use crate::ty::ParamEnv; -use crate::ty::{self, List, Ty, TyCtxt, TypeVisitableExt}; +use crate::ty::GenericArgsRef; +use crate::ty::{self, List, Ty}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; -use rustc_errors::ErrorGuaranteed; use rustc_hir::def_id::DefId; use rustc_macros::HashStable; -use rustc_target::abi::Size; - -use super::ScalarInt; /// An unevaluated (potentially generic) constant used in the type-system. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, TyEncodable, TyDecodable, Lift)] +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, TyEncodable, TyDecodable, Lift)] #[derive(Hash, HashStable, TypeFoldable, TypeVisitable)] pub struct UnevaluatedConst<'tcx> { pub def: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } impl rustc_errors::IntoDiagnosticArg for UnevaluatedConst<'_> { fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> { - format!("{:?}", self).into_diagnostic_arg() + format!("{self:?}").into_diagnostic_arg() } } impl<'tcx> UnevaluatedConst<'tcx> { #[inline] pub fn expand(self) -> mir::UnevaluatedConst<'tcx> { - mir::UnevaluatedConst { def: self.def, substs: self.substs, promoted: None } + mir::UnevaluatedConst { def: self.def, args: self.args, promoted: None } } } impl<'tcx> UnevaluatedConst<'tcx> { #[inline] - pub fn new(def: DefId, substs: SubstsRef<'tcx>) -> UnevaluatedConst<'tcx> { - UnevaluatedConst { def, substs } - } -} - -/// Represents a constant in Rust. -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, TyEncodable, TyDecodable)] -#[derive(Hash, HashStable, TypeFoldable, TypeVisitable)] -#[derive(derive_more::From)] -pub enum ConstKind<'tcx> { - /// A const generic parameter. - Param(ty::ParamConst), - - /// Infer the value of the const. - Infer(InferConst<'tcx>), - - /// Bound const variable, used only when preparing a trait query. - Bound(ty::DebruijnIndex, ty::BoundVar), - - /// A placeholder const - universally quantified higher-ranked const. - Placeholder(ty::PlaceholderConst<'tcx>), - - /// Used in the HIR by using `Unevaluated` everywhere and later normalizing to one of the other - /// variants when the code is monomorphic enough for that. - Unevaluated(UnevaluatedConst<'tcx>), - - /// Used to hold computed value. - Value(ty::ValTree<'tcx>), - - /// A placeholder for a const which could not be computed; this is - /// propagated to avoid useless error messages. - #[from(ignore)] - Error(ErrorGuaranteed), - - /// Expr which contains an expression which has partially evaluated items. - Expr(Expr<'tcx>), -} - -impl<'tcx> From> for ConstKind<'tcx> { - fn from(const_vid: ty::ConstVid<'tcx>) -> Self { - InferConst::Var(const_vid).into() + pub fn new(def: DefId, args: GenericArgsRef<'tcx>) -> UnevaluatedConst<'tcx> { + UnevaluatedConst { def, args } } } -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] #[derive(HashStable, TyEncodable, TyDecodable, TypeVisitable, TypeFoldable)] pub enum Expr<'tcx> { Binop(mir::BinOp, Const<'tcx>, Const<'tcx>), @@ -93,39 +48,7 @@ pub enum Expr<'tcx> { static_assert_size!(Expr<'_>, 24); #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -static_assert_size!(ConstKind<'_>, 32); - -impl<'tcx> ConstKind<'tcx> { - #[inline] - pub fn try_to_value(self) -> Option> { - if let ConstKind::Value(val) = self { Some(val) } else { None } - } - - #[inline] - pub fn try_to_scalar(self) -> Option> { - self.try_to_value()?.try_to_scalar() - } - - #[inline] - pub fn try_to_scalar_int(self) -> Option { - self.try_to_value()?.try_to_scalar_int() - } - - #[inline] - pub fn try_to_bits(self, size: Size) -> Option { - self.try_to_scalar_int()?.to_bits(size).ok() - } - - #[inline] - pub fn try_to_bool(self) -> Option { - self.try_to_scalar_int()?.try_into().ok() - } - - #[inline] - pub fn try_to_target_usize(self, tcx: TyCtxt<'tcx>) -> Option { - self.try_to_value()?.try_to_target_usize(tcx) - } -} +static_assert_size!(super::ConstKind<'_>, 32); /// An inference variable for a const, for use in const generics. #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, TyEncodable, TyDecodable, Hash)] @@ -144,124 +67,3 @@ impl HashStable for InferConst<'_> { } } } - -enum EvalMode { - Typeck, - Mir, -} - -enum EvalResult<'tcx> { - ValTree(ty::ValTree<'tcx>), - ConstVal(ConstValue<'tcx>), -} - -impl<'tcx> ConstKind<'tcx> { - #[inline] - /// Tries to evaluate the constant if it is `Unevaluated`. If that doesn't succeed, return the - /// unevaluated constant. - pub fn eval(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Self { - self.try_eval_for_typeck(tcx, param_env).and_then(Result::ok).map_or(self, ConstKind::Value) - } - - #[inline] - /// Tries to evaluate the constant if it is `Unevaluated`. If that isn't possible or necessary - /// return `None`. - // FIXME(@lcnr): Completely rework the evaluation/normalization system for `ty::Const` once valtrees are merged. - pub fn try_eval_for_mir( - self, - tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, - ) -> Option, ErrorGuaranteed>> { - match self.try_eval_inner(tcx, param_env, EvalMode::Mir) { - Some(Ok(EvalResult::ValTree(_))) => unreachable!(), - Some(Ok(EvalResult::ConstVal(v))) => Some(Ok(v)), - Some(Err(e)) => Some(Err(e)), - None => None, - } - } - - #[inline] - /// Tries to evaluate the constant if it is `Unevaluated`. If that isn't possible or necessary - /// return `None`. - // FIXME(@lcnr): Completely rework the evaluation/normalization system for `ty::Const` once valtrees are merged. - pub fn try_eval_for_typeck( - self, - tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, - ) -> Option, ErrorGuaranteed>> { - match self.try_eval_inner(tcx, param_env, EvalMode::Typeck) { - Some(Ok(EvalResult::ValTree(v))) => Some(Ok(v)), - Some(Ok(EvalResult::ConstVal(_))) => unreachable!(), - Some(Err(e)) => Some(Err(e)), - None => None, - } - } - - #[inline] - fn try_eval_inner( - self, - tcx: TyCtxt<'tcx>, - param_env: ParamEnv<'tcx>, - eval_mode: EvalMode, - ) -> Option, ErrorGuaranteed>> { - assert!(!self.has_escaping_bound_vars(), "escaping vars in {self:?}"); - if let ConstKind::Unevaluated(unevaluated) = self { - use crate::mir::interpret::ErrorHandled; - - // HACK(eddyb) this erases lifetimes even though `const_eval_resolve` - // also does later, but we want to do it before checking for - // inference variables. - // Note that we erase regions *before* calling `with_reveal_all_normalized`, - // so that we don't try to invoke this query with - // any region variables. - - // HACK(eddyb) when the query key would contain inference variables, - // attempt using identity substs and `ParamEnv` instead, that will succeed - // when the expression doesn't depend on any parameters. - // FIXME(eddyb, skinny121) pass `InferCtxt` into here when it's available, so that - // we can call `infcx.const_eval_resolve` which handles inference variables. - let param_env_and = if (param_env, unevaluated).has_non_region_infer() { - tcx.param_env(unevaluated.def).and(ty::UnevaluatedConst { - def: unevaluated.def, - substs: InternalSubsts::identity_for_item(tcx, unevaluated.def), - }) - } else { - tcx.erase_regions(param_env) - .with_reveal_all_normalized(tcx) - .and(tcx.erase_regions(unevaluated)) - }; - - // FIXME(eddyb) maybe the `const_eval_*` methods should take - // `ty::ParamEnvAnd` instead of having them separate. - let (param_env, unevaluated) = param_env_and.into_parts(); - // try to resolve e.g. associated constants to their definition on an impl, and then - // evaluate the const. - match eval_mode { - EvalMode::Typeck => { - match tcx.const_eval_resolve_for_typeck(param_env, unevaluated, None) { - // NOTE(eddyb) `val` contains no lifetimes/types/consts, - // and we use the original type, so nothing from `substs` - // (which may be identity substs, see above), - // can leak through `val` into the const we return. - Ok(val) => Some(Ok(EvalResult::ValTree(val?))), - Err(ErrorHandled::TooGeneric) => None, - Err(ErrorHandled::Reported(e)) => Some(Err(e.into())), - } - } - EvalMode::Mir => { - match tcx.const_eval_resolve(param_env, unevaluated.expand(), None) { - // NOTE(eddyb) `val` contains no lifetimes/types/consts, - // and we use the original type, so nothing from `substs` - // (which may be identity substs, see above), - // can leak through `val` into the const we return. - Ok(val) => Some(Ok(EvalResult::ConstVal(val))), - Err(ErrorHandled::TooGeneric) => None, - Err(ErrorHandled::Reported(e)) => Some(Err(e.into())), - } - } - } - } else { - None - } - } -} diff --git a/compiler/rustc_middle/src/ty/consts/valtree.rs b/compiler/rustc_middle/src/ty/consts/valtree.rs index 8b96864ddd7c9..fb7bf78bafe31 100644 --- a/compiler/rustc_middle/src/ty/consts/valtree.rs +++ b/compiler/rustc_middle/src/ty/consts/valtree.rs @@ -24,7 +24,7 @@ pub enum ValTree<'tcx> { Leaf(ScalarInt), //SliceOrStr(ValSlice<'tcx>), - // dont use SliceOrStr for now + // don't use SliceOrStr for now /// The fields of any kind of aggregate. Structs, tuples and arrays are represented by /// listing their fields' values in order. /// diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 2bde55bc4fd31..be839e03cff69 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -25,12 +25,12 @@ use crate::traits::solve::{ ExternalConstraints, ExternalConstraintsData, PredefinedOpaques, PredefinedOpaquesData, }; use crate::ty::{ - self, AdtDef, AdtDefData, AdtKind, Binder, Const, ConstData, FloatTy, FloatVar, FloatVid, - GenericParamDefKind, ImplPolarity, InferTy, IntTy, IntVar, IntVid, List, ParamConst, ParamTy, - PolyExistentialPredicate, PolyFnSig, Predicate, PredicateKind, Region, RegionKind, ReprOptions, - TraitObjectVisitor, Ty, TyKind, TyVar, TyVid, TypeAndMut, UintTy, Visibility, + self, AdtDef, AdtDefData, AdtKind, Binder, Clause, Const, ConstData, GenericParamDefKind, + ImplPolarity, InferTy, List, ParamConst, ParamTy, PolyExistentialPredicate, PolyFnSig, + Predicate, PredicateKind, Region, RegionKind, ReprOptions, TraitObjectVisitor, Ty, TyKind, + TyVid, TypeAndMut, Visibility, }; -use crate::ty::{GenericArg, InternalSubsts, SubstsRef}; +use crate::ty::{GenericArg, GenericArgs, GenericArgsRef}; use rustc_ast::{self as ast, attr}; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -50,9 +50,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; use rustc_hir::definitions::Definitions; use rustc_hir::intravisit::Visitor; use rustc_hir::lang_items::LangItem; -use rustc_hir::{ - Constness, ExprKind, HirId, ImplItemKind, ItemKind, Node, TraitCandidate, TraitItemKind, -}; +use rustc_hir::{Constness, HirId, Node, TraitCandidate}; use rustc_index::IndexVec; use rustc_macros::HashStable; use rustc_query_system::dep_graph::DepNodeIndex; @@ -61,8 +59,7 @@ use rustc_serialize::opaque::{FileEncodeResult, FileEncoder}; use rustc_session::config::CrateType; use rustc_session::cstore::{CrateStoreDyn, Untracked}; use rustc_session::lint::Lint; -use rustc_session::Limit; -use rustc_session::Session; +use rustc_session::{Limit, MetadataKind, Session}; use rustc_span::def_id::{DefPathHash, StableCrateId}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; @@ -70,10 +67,9 @@ use rustc_target::abi::{FieldIdx, Layout, LayoutS, TargetDataLayout, VariantIdx} use rustc_target::spec::abi; use rustc_type_ir::sty::TyKind::*; use rustc_type_ir::WithCachedTypeInfo; -use rustc_type_ir::{CollectAndApply, DynKind, Interner, TypeFlags}; +use rustc_type_ir::{CollectAndApply, Interner, TypeFlags}; use std::any::Any; -use std::assert_matches::debug_assert_matches; use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt; @@ -82,12 +78,10 @@ use std::iter; use std::mem; use std::ops::{Bound, Deref}; -const TINY_CONST_EVAL_LIMIT: Limit = Limit(20); - #[allow(rustc::usage_of_ty_tykind)] impl<'tcx> Interner for TyCtxt<'tcx> { type AdtDef = ty::AdtDef<'tcx>; - type SubstsRef = ty::SubstsRef<'tcx>; + type GenericArgsRef = ty::GenericArgsRef<'tcx>; type DefId = DefId; type Binder = Binder<'tcx, T>; type Ty = Ty<'tcx>; @@ -110,11 +104,29 @@ impl<'tcx> Interner for TyCtxt<'tcx> { type PredicateKind = ty::PredicateKind<'tcx>; type AllocId = crate::mir::interpret::AllocId; + type InferConst = ty::InferConst<'tcx>; + type AliasConst = ty::UnevaluatedConst<'tcx>; + type ParamConst = ty::ParamConst; + type BoundConst = ty::BoundVar; + type PlaceholderConst = ty::PlaceholderConst<'tcx>; + type ValueConst = ty::ValTree<'tcx>; + type ExprConst = ty::Expr<'tcx>; + type EarlyBoundRegion = ty::EarlyBoundRegion; type BoundRegion = ty::BoundRegion; type FreeRegion = ty::FreeRegion; type RegionVid = ty::RegionVid; type PlaceholderRegion = ty::PlaceholderRegion; + + fn ty_and_mut_to_parts( + TypeAndMut { ty, mutbl }: TypeAndMut<'tcx>, + ) -> (Self::Ty, Self::Mutability) { + (ty, mutbl) + } + + fn mutability_is_mut(mutbl: Self::Mutability) -> bool { + mutbl.is_mut() + } } type InternedSet<'tcx, T> = ShardedHashMap, ()>; @@ -127,13 +139,13 @@ pub struct CtxtInterners<'tcx> { // they're accessed quite often. type_: InternedSet<'tcx, WithCachedTypeInfo>>, const_lists: InternedSet<'tcx, List>>, - substs: InternedSet<'tcx, InternalSubsts<'tcx>>, + args: InternedSet<'tcx, GenericArgs<'tcx>>, type_lists: InternedSet<'tcx, List>>, canonical_var_infos: InternedSet<'tcx, List>>, region: InternedSet<'tcx, RegionKind<'tcx>>, poly_existential_predicates: InternedSet<'tcx, List>>, predicate: InternedSet<'tcx, WithCachedTypeInfo>>>, - predicates: InternedSet<'tcx, List>>, + clauses: InternedSet<'tcx, List>>, projs: InternedSet<'tcx, List>, place_elems: InternedSet<'tcx, List>>, const_: InternedSet<'tcx, ConstData<'tcx>>, @@ -152,13 +164,13 @@ impl<'tcx> CtxtInterners<'tcx> { arena, type_: Default::default(), const_lists: Default::default(), - substs: Default::default(), + args: Default::default(), type_lists: Default::default(), region: Default::default(), poly_existential_predicates: Default::default(), canonical_var_infos: Default::default(), predicate: Default::default(), - predicates: Default::default(), + clauses: Default::default(), projs: Default::default(), place_elems: Default::default(), const_: Default::default(), @@ -312,6 +324,8 @@ pub struct CommonLifetimes<'tcx> { pub struct CommonConsts<'tcx> { pub unit: Const<'tcx>, + pub true_: Const<'tcx>, + pub false_: Const<'tcx>, } impl<'tcx> CommonTypes<'tcx> { @@ -409,6 +423,14 @@ impl<'tcx> CommonConsts<'tcx> { kind: ty::ConstKind::Value(ty::ValTree::zst()), ty: types.unit, }), + true_: mk_const(ty::ConstData { + kind: ty::ConstKind::Value(ty::ValTree::Leaf(ty::ScalarInt::TRUE)), + ty: types.bool, + }), + false_: mk_const(ty::ConstData { + kind: ty::ConstKind::Value(ty::ValTree::Leaf(ty::ScalarInt::FALSE)), + ty: types.bool, + }), } } } @@ -470,6 +492,17 @@ impl<'tcx> TyCtxtFeed<'tcx, LocalDefId> { /// [rustc dev guide] for more details. /// /// [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/ty.html +/// +/// An implementation detail: `TyCtxt` is a wrapper type for [GlobalCtxt], +/// which is the struct that actually holds all the data. `TyCtxt` derefs to +/// `GlobalCtxt`, and in practice `TyCtxt` is passed around everywhere, and all +/// operations are done via `TyCtxt`. A `TyCtxt` is obtained for a `GlobalCtxt` +/// by calling `enter` with a closure `f`. That function creates both the +/// `TyCtxt`, and an `ImplicitCtxt` around it that is put into TLS. Within `f`: +/// - The `ImplicitCtxt` is available implicitly via TLS. +/// - The `TyCtxt` is available explicitly via the `tcx` parameter, and also +/// implicitly within the `ImplicitCtxt`. Explicit access is preferred when +/// possible. #[derive(Copy, Clone)] #[rustc_diagnostic_item = "TyCtxt"] #[rustc_pass_by_value] @@ -485,6 +518,7 @@ impl<'tcx> Deref for TyCtxt<'tcx> { } } +/// See [TyCtxt] for details about this type. pub struct GlobalCtxt<'tcx> { pub arena: &'tcx WorkerLocal>, pub hir_arena: &'tcx WorkerLocal>, @@ -492,6 +526,13 @@ pub struct GlobalCtxt<'tcx> { interners: CtxtInterners<'tcx>, pub sess: &'tcx Session, + crate_types: Vec, + /// The `stable_crate_id` is constructed out of the crate name and all the + /// `-C metadata` arguments passed to the compiler. Its value forms a unique + /// global identifier for the crate. It is used to allow multiple crates + /// with the same name to coexist. See the + /// `rustc_symbol_mangling` crate for more information. + stable_crate_id: StableCrateId, /// This only ever stores a `LintStore` but we don't want a dependency on that type here. /// @@ -532,6 +573,7 @@ pub struct GlobalCtxt<'tcx> { /// Caches the results of goal evaluation in the new solver. pub new_solver_evaluation_cache: solve::EvaluationCache<'tcx>, + pub new_solver_coherence_evaluation_cache: solve::EvaluationCache<'tcx>, /// Data layout specification for the current target. pub data_layout: TargetDataLayout, @@ -643,12 +685,16 @@ impl<'tcx> TyCtxt<'tcx> { value.lift_to_tcx(self) } - /// Creates a type context and call the closure with a `TyCtxt` reference - /// to the context. The closure enforces that the type context and any interned - /// value (types, substs, etc.) can only be used while `ty::tls` has a valid - /// reference to the context, to allow formatting values that need it. + /// Creates a type context. To use the context call `fn enter` which + /// provides a `TyCtxt`. + /// + /// By only providing the `TyCtxt` inside of the closure we enforce that the type + /// context and any interned alue (types, args, etc.) can only be used while `ty::tls` + /// has a valid reference to the context, to allow formatting values that need it. pub fn create_global_ctxt( s: &'tcx Session, + crate_types: Vec, + stable_crate_id: StableCrateId, lint_store: Lrc, arena: &'tcx WorkerLocal>, hir_arena: &'tcx WorkerLocal>, @@ -667,6 +713,8 @@ impl<'tcx> TyCtxt<'tcx> { GlobalCtxt { sess: s, + crate_types, + stable_crate_id, lint_store, arena, hir_arena, @@ -684,87 +732,12 @@ impl<'tcx> TyCtxt<'tcx> { selection_cache: Default::default(), evaluation_cache: Default::default(), new_solver_evaluation_cache: Default::default(), + new_solver_coherence_evaluation_cache: Default::default(), data_layout, alloc_map: Lock::new(interpret::AllocMap::new()), } } - /// Constructs a `TyKind::Error` type with current `ErrorGuaranteed` - #[track_caller] - pub fn ty_error(self, reported: ErrorGuaranteed) -> Ty<'tcx> { - self.mk_ty_from_kind(Error(reported)) - } - - /// Constructs a `TyKind::Error` type and registers a `delay_span_bug` to ensure it gets used. - #[track_caller] - pub fn ty_error_misc(self) -> Ty<'tcx> { - self.ty_error_with_message(DUMMY_SP, "TyKind::Error constructed but no error reported") - } - - /// Constructs a `TyKind::Error` type and registers a `delay_span_bug` with the given `msg` to - /// ensure it gets used. - #[track_caller] - pub fn ty_error_with_message>( - self, - span: S, - msg: impl Into, - ) -> Ty<'tcx> { - let reported = self.sess.delay_span_bug(span, msg); - self.mk_ty_from_kind(Error(reported)) - } - - /// Constructs a `RegionKind::ReError` lifetime. - #[track_caller] - pub fn mk_re_error(self, reported: ErrorGuaranteed) -> Region<'tcx> { - self.intern_region(ty::ReError(reported)) - } - - /// Constructs a `RegionKind::ReError` lifetime and registers a `delay_span_bug` to ensure it - /// gets used. - #[track_caller] - pub fn mk_re_error_misc(self) -> Region<'tcx> { - self.mk_re_error_with_message( - DUMMY_SP, - "RegionKind::ReError constructed but no error reported", - ) - } - - /// Constructs a `RegionKind::ReError` lifetime and registers a `delay_span_bug` with the given - /// `msg` to ensure it gets used. - #[track_caller] - pub fn mk_re_error_with_message>(self, span: S, msg: &str) -> Region<'tcx> { - let reported = self.sess.delay_span_bug(span, msg); - self.mk_re_error(reported) - } - - /// Like [TyCtxt::ty_error] but for constants, with current `ErrorGuaranteed` - #[track_caller] - pub fn const_error(self, ty: Ty<'tcx>, reported: ErrorGuaranteed) -> Const<'tcx> { - self.mk_const(ty::ConstKind::Error(reported), ty) - } - - /// Like [TyCtxt::ty_error] but for constants. - #[track_caller] - pub fn const_error_misc(self, ty: Ty<'tcx>) -> Const<'tcx> { - self.const_error_with_message( - ty, - DUMMY_SP, - "ty::ConstKind::Error constructed but no error reported", - ) - } - - /// Like [TyCtxt::ty_error_with_message] but for constants. - #[track_caller] - pub fn const_error_with_message>( - self, - ty: Ty<'tcx>, - span: S, - msg: &str, - ) -> Const<'tcx> { - let reported = self.sess.delay_span_bug(span, msg); - self.mk_const(ty::ConstKind::Error(reported), ty) - } - pub fn consider_optimizing String>(self, msg: T) -> bool { self.sess.consider_optimizing(|| self.crate_name(LOCAL_CRATE), msg) } @@ -837,10 +810,47 @@ impl<'tcx> TyCtxt<'tcx> { } } + #[inline] + pub fn crate_types(self) -> &'tcx [CrateType] { + &self.crate_types + } + + pub fn metadata_kind(self) -> MetadataKind { + self.crate_types() + .iter() + .map(|ty| match *ty { + CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib => { + MetadataKind::None + } + CrateType::Rlib => MetadataKind::Uncompressed, + CrateType::Dylib | CrateType::ProcMacro => MetadataKind::Compressed, + }) + .max() + .unwrap_or(MetadataKind::None) + } + + pub fn needs_metadata(self) -> bool { + self.metadata_kind() != MetadataKind::None + } + + pub fn needs_crate_hash(self) -> bool { + // Why is the crate hash needed for these configurations? + // - debug_assertions: for the "fingerprint the result" check in + // `rustc_query_system::query::plumbing::execute_job`. + // - incremental: for query lookups. + // - needs_metadata: for putting into crate metadata. + // - instrument_coverage: for putting into coverage data (see + // `hash_mir_source`). + cfg!(debug_assertions) + || self.sess.opts.incremental.is_some() + || self.needs_metadata() + || self.sess.instrument_coverage() + } + #[inline] pub fn stable_crate_id(self, crate_num: CrateNum) -> StableCrateId { if crate_num == LOCAL_CRATE { - self.sess.local_stable_crate_id() + self.stable_crate_id } else { self.cstore_untracked().stable_crate_id(crate_num) } @@ -850,7 +860,7 @@ impl<'tcx> TyCtxt<'tcx> { /// that the crate in question has already been loaded by the CrateStore. #[inline] pub fn stable_crate_id_to_crate_num(self, stable_crate_id: StableCrateId) -> CrateNum { - if stable_crate_id == self.sess.local_stable_crate_id() { + if stable_crate_id == self.stable_crate_id(LOCAL_CRATE) { LOCAL_CRATE } else { self.cstore_untracked().stable_crate_id_to_crate_num(stable_crate_id) @@ -867,7 +877,7 @@ impl<'tcx> TyCtxt<'tcx> { // If this is a DefPathHash from the local crate, we can look up the // DefId in the tcx's `Definitions`. - if stable_crate_id == self.sess.local_stable_crate_id() { + if stable_crate_id == self.stable_crate_id(LOCAL_CRATE) { self.untracked.definitions.read().local_def_path_hash_to_def_id(hash, err).to_def_id() } else { // If this is a DefPathHash from an upstream crate, let the CrateStore map @@ -884,7 +894,7 @@ impl<'tcx> TyCtxt<'tcx> { // statements within the query system and we'd run into endless // recursion otherwise. let (crate_name, stable_crate_id) = if def_id.is_local() { - (self.crate_name(LOCAL_CRATE), self.sess.local_stable_crate_id()) + (self.crate_name(LOCAL_CRATE), self.stable_crate_id(LOCAL_CRATE)) } else { let cstore = &*self.cstore_untracked(); (cstore.crate_name(def_id.krate), cstore.stable_crate_id(def_id.krate)) @@ -1019,20 +1029,11 @@ impl<'tcx> TyCtxt<'tcx> { self.query_system.on_disk_cache.as_ref().map_or(Ok(0), |c| c.serialize(self, encoder)) } - /// If `true`, we should use lazy normalization for constants, otherwise - /// we still evaluate them eagerly. - #[inline] - pub fn lazy_normalization(self) -> bool { - let features = self.features(); - // Note: We only use lazy normalization for generic const expressions. - features.generic_const_exprs - } - #[inline] pub fn local_crate_exports_generics(self) -> bool { debug_assert!(self.sess.opts.share_generics()); - self.sess.crate_types().iter().any(|crate_type| { + self.crate_types().iter().any(|crate_type| { match crate_type { CrateType::Executable | CrateType::Staticlib @@ -1084,7 +1085,9 @@ impl<'tcx> TyCtxt<'tcx> { scope_def_id: LocalDefId, ) -> Vec<&'tcx hir::Ty<'tcx>> { let hir_id = self.hir().local_def_id_to_hir_id(scope_def_id); - let Some(hir::FnDecl { output: hir::FnRetTy::Return(hir_output), .. }) = self.hir().fn_decl_by_hir_id(hir_id) else { + let Some(hir::FnDecl { output: hir::FnRetTy::Return(hir_output), .. }) = + self.hir().fn_decl_by_hir_id(hir_id) + else { return vec![]; }; @@ -1106,7 +1109,7 @@ impl<'tcx> TyCtxt<'tcx> { if let Some(hir::FnDecl { output: hir::FnRetTy::Return(hir_output), .. }) = self.hir().fn_decl_by_hir_id(hir_id) && let hir::TyKind::Path(hir::QPath::Resolved( None, - hir::Path { res: hir::def::Res::Def(DefKind::TyAlias, def_id), .. }, )) = hir_output.kind + hir::Path { res: hir::def::Res::Def(DefKind::TyAlias { .. }, def_id), .. }, )) = hir_output.kind && let Some(local_id) = def_id.as_local() && let Some(alias_ty) = self.hir().get_by_def_id(local_id).alias_ty() // it is type alias && let Some(alias_generics) = self.hir().get_by_def_id(local_id).generics() @@ -1119,31 +1122,6 @@ impl<'tcx> TyCtxt<'tcx> { return None; } - pub fn return_type_impl_trait(self, scope_def_id: LocalDefId) -> Option<(Ty<'tcx>, Span)> { - // `type_of()` will fail on these (#55796, #86483), so only allow `fn`s or closures. - match self.hir().get_by_def_id(scope_def_id) { - Node::Item(&hir::Item { kind: ItemKind::Fn(..), .. }) => {} - Node::TraitItem(&hir::TraitItem { kind: TraitItemKind::Fn(..), .. }) => {} - Node::ImplItem(&hir::ImplItem { kind: ImplItemKind::Fn(..), .. }) => {} - Node::Expr(&hir::Expr { kind: ExprKind::Closure { .. }, .. }) => {} - _ => return None, - } - - let ret_ty = self.type_of(scope_def_id).subst_identity(); - match ret_ty.kind() { - ty::FnDef(_, _) => { - let sig = ret_ty.fn_sig(self); - let output = self.erase_late_bound_regions(sig.output()); - output.is_impl_trait().then(|| { - let hir_id = self.hir().local_def_id_to_hir_id(scope_def_id); - let fn_decl = self.hir().fn_decl_by_hir_id(hir_id).unwrap(); - (output, fn_decl.output.span()) - }) - } - _ => None, - } - } - /// Checks if the bound region is in Impl Item. pub fn is_bound_region_in_impl_item(self, suitable_region_binding_scope: LocalDefId) -> bool { let container_id = self.parent(suitable_region_binding_scope.to_def_id()); @@ -1167,10 +1145,11 @@ impl<'tcx> TyCtxt<'tcx> { /// Returns `&'static core::panic::Location<'static>`. pub fn caller_location_ty(self) -> Ty<'tcx> { - self.mk_imm_ref( + Ty::new_imm_ref( + self, self.lifetimes.re_static, self.type_of(self.require_lang_item(LangItem::PanicLocation, None)) - .subst(self, self.mk_substs(&[self.lifetimes.re_static.into()])), + .instantiate(self, self.mk_args(&[self.lifetimes.re_static.into()])), ) } @@ -1192,14 +1171,6 @@ impl<'tcx> TyCtxt<'tcx> { self.limits(()).move_size_limit } - pub fn const_eval_limit(self) -> Limit { - if self.sess.opts.unstable_opts.tiny_const_eval_limit { - TINY_CONST_EVAL_LIMIT - } else { - self.limits(()).const_eval_limit - } - } - pub fn all_traits(self) -> impl Iterator + 'tcx { iter::once(LOCAL_CRATE) .chain(self.crates(()).iter().copied()) @@ -1210,12 +1181,18 @@ impl<'tcx> TyCtxt<'tcx> { pub fn local_visibility(self, def_id: LocalDefId) -> Visibility { self.visibility(def_id).expect_local() } + + /// Returns the origin of the opaque type `def_id`. + #[instrument(skip(self), level = "trace", ret)] + pub fn opaque_type_origin(self, def_id: LocalDefId) -> hir::OpaqueTyOrigin { + self.hir().expect_item(def_id).expect_opaque_ty().origin + } } /// A trait implemented for all `X<'a>` types that can be safely and /// efficiently converted to `X<'tcx>` as long as they are part of the /// provided `TyCtxt<'tcx>`. -/// This can be done, for example, for `Ty<'tcx>` or `SubstsRef<'tcx>` +/// This can be done, for example, for `Ty<'tcx>` or `GenericArgsRef<'tcx>` /// by looking them up in their respective interners. /// /// However, this is still not the best implementation as it does @@ -1272,16 +1249,17 @@ nop_lift! {region; Region<'a> => Region<'tcx>} nop_lift! {const_; Const<'a> => Const<'tcx>} nop_lift! {const_allocation; ConstAllocation<'a> => ConstAllocation<'tcx>} nop_lift! {predicate; Predicate<'a> => Predicate<'tcx>} +nop_lift! {predicate; Clause<'a> => Clause<'tcx>} nop_list_lift! {type_lists; Ty<'a> => Ty<'tcx>} nop_list_lift! {poly_existential_predicates; PolyExistentialPredicate<'a> => PolyExistentialPredicate<'tcx>} -nop_list_lift! {predicates; Predicate<'a> => Predicate<'tcx>} +nop_list_lift! {clauses; Clause<'a> => Clause<'tcx>} nop_list_lift! {canonical_var_infos; CanonicalVarInfo<'a> => CanonicalVarInfo<'tcx>} nop_list_lift! {projs; ProjectionKind => ProjectionKind} nop_list_lift! {bound_variable_kinds; ty::BoundVariableKind => ty::BoundVariableKind} -// This is the impl for `&'a InternalSubsts<'a>`. -nop_list_lift! {substs; GenericArg<'a> => GenericArg<'tcx>} +// This is the impl for `&'a GenericArgs<'a>`. +nop_list_lift! {args; GenericArg<'a> => GenericArg<'tcx>} CloneLiftImpls! { Constness, @@ -1393,7 +1371,7 @@ impl<'tcx> TyCtxt<'tcx> { Foreign )?; - writeln!(fmt, "InternalSubsts interner: #{}", self.0.interners.substs.len())?; + writeln!(fmt, "GenericArgs interner: #{}", self.0.interners.args.len())?; writeln!(fmt, "Region interner: #{}", self.0.interners.region.len())?; writeln!( fmt, @@ -1515,9 +1493,9 @@ macro_rules! direct_interners { // Functions with a `mk_` prefix are intended for use outside this file and // crate. Functions with an `intern_` prefix are intended for use within this -// file only, and have a corresponding `mk_` function. +// crate only, and have a corresponding `mk_` function. direct_interners! { - region: intern_region(RegionKind<'tcx>): Region -> Region<'tcx>, + region: pub(crate) intern_region(RegionKind<'tcx>): Region -> Region<'tcx>, const_: intern_const(ConstData<'tcx>): Const -> Const<'tcx>, const_allocation: pub mk_const_alloc(Allocation): ConstAllocation -> ConstAllocation<'tcx>, layout: pub mk_layout(LayoutS): Layout -> Layout<'tcx>, @@ -1549,11 +1527,11 @@ macro_rules! slice_interners { // should be used when possible, because it's faster. slice_interners!( const_lists: pub mk_const_list(Const<'tcx>), - substs: pub mk_substs(GenericArg<'tcx>), + args: pub mk_args(GenericArg<'tcx>), type_lists: pub mk_type_list(Ty<'tcx>), canonical_var_infos: pub mk_canonical_var_infos(CanonicalVarInfo<'tcx>), poly_existential_predicates: intern_poly_existential_predicates(PolyExistentialPredicate<'tcx>), - predicates: intern_predicates(Predicate<'tcx>), + clauses: intern_clauses(Clause<'tcx>), projs: pub mk_projs(ProjectionKind), place_elems: pub mk_place_elems(PlaceElem<'tcx>), bound_variable_kinds: pub mk_bound_variable_kinds(ty::BoundVariableKind), @@ -1566,7 +1544,10 @@ impl<'tcx> TyCtxt<'tcx> { /// unsafe. pub fn safe_to_unsafe_fn_ty(self, sig: PolyFnSig<'tcx>) -> Ty<'tcx> { assert_eq!(sig.unsafety(), hir::Unsafety::Normal); - self.mk_fn_ptr(sig.map_bound(|sig| ty::FnSig { unsafety: hir::Unsafety::Unsafe, ..sig })) + Ty::new_fn_ptr( + self, + sig.map_bound(|sig| ty::FnSig { unsafety: hir::Unsafety::Unsafe, ..sig }), + ) } /// Given the def_id of a Trait `trait_def_id` and the name of an associated item `assoc_name` @@ -1585,7 +1566,7 @@ impl<'tcx> TyCtxt<'tcx> { let future_trait = self.require_lang_item(LangItem::Future, None); self.explicit_item_bounds(def_id).skip_binder().iter().any(|&(predicate, _)| { - let ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) = predicate.kind().skip_binder() else { + let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() else { return false; }; trait_predicate.trait_ref.def_id == future_trait @@ -1608,9 +1589,7 @@ impl<'tcx> TyCtxt<'tcx> { let generic_predicates = self.super_predicates_of(trait_did); for (predicate, _) in generic_predicates.predicates { - if let ty::PredicateKind::Clause(ty::Clause::Trait(data)) = - predicate.kind().skip_binder() - { + if let ty::ClauseKind::Trait(data) = predicate.kind().skip_binder() { if set.insert(data.def_id()) { stack.push(data.def_id()); } @@ -1642,18 +1621,6 @@ impl<'tcx> TyCtxt<'tcx> { }) } - // Avoid this in favour of more specific `mk_*` methods, where possible. - #[allow(rustc::usage_of_ty_tykind)] - #[inline] - pub fn mk_ty_from_kind(self, st: TyKind<'tcx>) -> Ty<'tcx> { - self.interners.intern_ty( - st, - self.sess, - // This is only used to create a stable hashing context. - &self.untracked, - ) - } - #[inline] pub fn mk_predicate(self, binder: Binder<'tcx, PredicateKind<'tcx>>) -> Predicate<'tcx> { self.interners.intern_predicate( @@ -1673,181 +1640,13 @@ impl<'tcx> TyCtxt<'tcx> { if pred.kind() != binder { self.mk_predicate(binder) } else { pred } } - pub fn mk_mach_int(self, tm: IntTy) -> Ty<'tcx> { - match tm { - IntTy::Isize => self.types.isize, - IntTy::I8 => self.types.i8, - IntTy::I16 => self.types.i16, - IntTy::I32 => self.types.i32, - IntTy::I64 => self.types.i64, - IntTy::I128 => self.types.i128, - } - } - - pub fn mk_mach_uint(self, tm: UintTy) -> Ty<'tcx> { - match tm { - UintTy::Usize => self.types.usize, - UintTy::U8 => self.types.u8, - UintTy::U16 => self.types.u16, - UintTy::U32 => self.types.u32, - UintTy::U64 => self.types.u64, - UintTy::U128 => self.types.u128, - } - } - - pub fn mk_mach_float(self, tm: FloatTy) -> Ty<'tcx> { - match tm { - FloatTy::F32 => self.types.f32, - FloatTy::F64 => self.types.f64, - } - } - - #[inline] - pub fn mk_static_str(self) -> Ty<'tcx> { - self.mk_imm_ref(self.lifetimes.re_static, self.types.str_) - } - - #[inline] - pub fn mk_adt(self, def: AdtDef<'tcx>, substs: SubstsRef<'tcx>) -> Ty<'tcx> { - // Take a copy of substs so that we own the vectors inside. - self.mk_ty_from_kind(Adt(def, substs)) - } - - #[inline] - pub fn mk_foreign(self, def_id: DefId) -> Ty<'tcx> { - self.mk_ty_from_kind(Foreign(def_id)) - } - - fn mk_generic_adt(self, wrapper_def_id: DefId, ty_param: Ty<'tcx>) -> Ty<'tcx> { - let adt_def = self.adt_def(wrapper_def_id); - let substs = - InternalSubsts::for_item(self, wrapper_def_id, |param, substs| match param.kind { - GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => bug!(), - GenericParamDefKind::Type { has_default, .. } => { - if param.index == 0 { - ty_param.into() - } else { - assert!(has_default); - self.type_of(param.def_id).subst(self, substs).into() - } - } - }); - self.mk_ty_from_kind(Adt(adt_def, substs)) - } - - #[inline] - pub fn mk_box(self, ty: Ty<'tcx>) -> Ty<'tcx> { - let def_id = self.require_lang_item(LangItem::OwnedBox, None); - self.mk_generic_adt(def_id, ty) - } - - #[inline] - pub fn mk_lang_item(self, ty: Ty<'tcx>, item: LangItem) -> Option> { - let def_id = self.lang_items().get(item)?; - Some(self.mk_generic_adt(def_id, ty)) - } - - #[inline] - pub fn mk_diagnostic_item(self, ty: Ty<'tcx>, name: Symbol) -> Option> { - let def_id = self.get_diagnostic_item(name)?; - Some(self.mk_generic_adt(def_id, ty)) - } - - #[inline] - pub fn mk_maybe_uninit(self, ty: Ty<'tcx>) -> Ty<'tcx> { - let def_id = self.require_lang_item(LangItem::MaybeUninit, None); - self.mk_generic_adt(def_id, ty) - } - - #[inline] - pub fn mk_ptr(self, tm: TypeAndMut<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(RawPtr(tm)) - } - - #[inline] - pub fn mk_ref(self, r: Region<'tcx>, tm: TypeAndMut<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(Ref(r, tm.ty, tm.mutbl)) - } - - #[inline] - pub fn mk_mut_ref(self, r: Region<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - self.mk_ref(r, TypeAndMut { ty, mutbl: hir::Mutability::Mut }) - } - - #[inline] - pub fn mk_imm_ref(self, r: Region<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - self.mk_ref(r, TypeAndMut { ty, mutbl: hir::Mutability::Not }) - } - - #[inline] - pub fn mk_mut_ptr(self, ty: Ty<'tcx>) -> Ty<'tcx> { - self.mk_ptr(TypeAndMut { ty, mutbl: hir::Mutability::Mut }) - } - - #[inline] - pub fn mk_imm_ptr(self, ty: Ty<'tcx>) -> Ty<'tcx> { - self.mk_ptr(TypeAndMut { ty, mutbl: hir::Mutability::Not }) - } - - #[inline] - pub fn mk_array(self, ty: Ty<'tcx>, n: u64) -> Ty<'tcx> { - self.mk_ty_from_kind(Array(ty, ty::Const::from_target_usize(self, n))) - } - - #[inline] - pub fn mk_array_with_const_len(self, ty: Ty<'tcx>, ct: Const<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(Array(ty, ct)) - } - - #[inline] - pub fn mk_slice(self, ty: Ty<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(Slice(ty)) - } - - #[inline] - pub fn mk_tup(self, ts: &[Ty<'tcx>]) -> Ty<'tcx> { - if ts.is_empty() { - self.types.unit - } else { - self.mk_ty_from_kind(Tuple(self.mk_type_list(&ts))) - } - } - - pub fn mk_tup_from_iter(self, iter: I) -> T::Output - where - I: Iterator, - T: CollectAndApply, Ty<'tcx>>, - { - T::collect_and_apply(iter, |ts| self.mk_tup(ts)) - } - - #[inline] - pub fn mk_unit(self) -> Ty<'tcx> { - self.types.unit - } - - #[inline] - pub fn mk_diverging_default(self) -> Ty<'tcx> { - if self.features().never_type_fallback { self.types.never } else { self.types.unit } - } - - #[inline] - pub fn mk_fn_def( - self, - def_id: DefId, - substs: impl IntoIterator>>, - ) -> Ty<'tcx> { - let substs = self.check_and_mk_substs(def_id, substs); - self.mk_ty_from_kind(FnDef(def_id, substs)) - } - #[inline(always)] - pub(crate) fn check_and_mk_substs( + pub(crate) fn check_and_mk_args( self, _def_id: DefId, - substs: impl IntoIterator>>, - ) -> SubstsRef<'tcx> { - let substs = substs.into_iter().map(Into::into); + args: impl IntoIterator>>, + ) -> GenericArgsRef<'tcx> { + let args = args.into_iter().map(Into::into); #[cfg(debug_assertions)] { let generics = self.generics_of(_def_id); @@ -1863,231 +1662,45 @@ impl<'tcx> TyCtxt<'tcx> { }; assert_eq!( (n, Some(n)), - substs.size_hint(), + args.size_hint(), "wrong number of generic parameters for {_def_id:?}: {:?}", - substs.collect::>(), + args.collect::>(), ); } - self.mk_substs_from_iter(substs) - } - - #[inline] - pub fn mk_fn_ptr(self, fty: PolyFnSig<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(FnPtr(fty)) - } - - #[inline] - pub fn mk_dynamic( - self, - obj: &'tcx List>, - reg: ty::Region<'tcx>, - repr: DynKind, - ) -> Ty<'tcx> { - self.mk_ty_from_kind(Dynamic(obj, reg, repr)) - } - - #[inline] - pub fn mk_projection( - self, - item_def_id: DefId, - substs: impl IntoIterator>>, - ) -> Ty<'tcx> { - self.mk_alias(ty::Projection, self.mk_alias_ty(item_def_id, substs)) + self.mk_args_from_iter(args) } #[inline] - pub fn mk_closure(self, closure_id: DefId, closure_substs: SubstsRef<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(Closure(closure_id, closure_substs)) - } - - #[inline] - pub fn mk_generator( - self, - id: DefId, - generator_substs: SubstsRef<'tcx>, - movability: hir::Movability, - ) -> Ty<'tcx> { - self.mk_ty_from_kind(Generator(id, generator_substs, movability)) - } - - #[inline] - pub fn mk_generator_witness(self, types: ty::Binder<'tcx, &'tcx List>>) -> Ty<'tcx> { - self.mk_ty_from_kind(GeneratorWitness(types)) - } - - /// Creates a `&mut Context<'_>` [`Ty`] with erased lifetimes. - pub fn mk_task_context(self) -> Ty<'tcx> { - let context_did = self.require_lang_item(LangItem::Context, None); - let context_adt_ref = self.adt_def(context_did); - let context_substs = self.mk_substs(&[self.lifetimes.re_erased.into()]); - let context_ty = self.mk_adt(context_adt_ref, context_substs); - self.mk_mut_ref(self.lifetimes.re_erased, context_ty) - } - - #[inline] - pub fn mk_generator_witness_mir(self, id: DefId, substs: SubstsRef<'tcx>) -> Ty<'tcx> { - self.mk_ty_from_kind(GeneratorWitnessMIR(id, substs)) - } - - #[inline] - pub fn mk_const(self, kind: impl Into>, ty: Ty<'tcx>) -> Const<'tcx> { - self.intern_const(ty::ConstData { kind: kind.into(), ty }) - } - - #[inline] - pub fn mk_ty_var(self, v: TyVid) -> Ty<'tcx> { - // Use a pre-interned one when possible. - self.types - .ty_vars - .get(v.as_usize()) - .copied() - .unwrap_or_else(|| self.mk_ty_from_kind(Infer(TyVar(v)))) - } - - #[inline] - pub fn mk_int_var(self, v: IntVid) -> Ty<'tcx> { - self.mk_ty_from_kind(Infer(IntVar(v))) - } - - #[inline] - pub fn mk_float_var(self, v: FloatVid) -> Ty<'tcx> { - self.mk_ty_from_kind(Infer(FloatVar(v))) - } - - #[inline] - pub fn mk_fresh_ty(self, n: u32) -> Ty<'tcx> { - // Use a pre-interned one when possible. - self.types - .fresh_tys - .get(n as usize) - .copied() - .unwrap_or_else(|| self.mk_ty_from_kind(Infer(ty::FreshTy(n)))) - } - - #[inline] - pub fn mk_fresh_int_ty(self, n: u32) -> Ty<'tcx> { - // Use a pre-interned one when possible. - self.types - .fresh_int_tys - .get(n as usize) - .copied() - .unwrap_or_else(|| self.mk_ty_from_kind(Infer(ty::FreshIntTy(n)))) - } - - #[inline] - pub fn mk_fresh_float_ty(self, n: u32) -> Ty<'tcx> { - // Use a pre-interned one when possible. - self.types - .fresh_float_tys - .get(n as usize) - .copied() - .unwrap_or_else(|| self.mk_ty_from_kind(Infer(ty::FreshFloatTy(n)))) + pub fn mk_ct_from_kind(self, kind: ty::ConstKind<'tcx>, ty: Ty<'tcx>) -> Const<'tcx> { + self.intern_const(ty::ConstData { kind, ty }) } + // Avoid this in favour of more specific `Ty::new_*` methods, where possible. + #[allow(rustc::usage_of_ty_tykind)] #[inline] - pub fn mk_ty_param(self, index: u32, name: Symbol) -> Ty<'tcx> { - self.mk_ty_from_kind(Param(ParamTy { index, name })) + pub fn mk_ty_from_kind(self, st: TyKind<'tcx>) -> Ty<'tcx> { + self.interners.intern_ty( + st, + self.sess, + // This is only used to create a stable hashing context. + &self.untracked, + ) } pub fn mk_param_from_def(self, param: &ty::GenericParamDef) -> GenericArg<'tcx> { match param.kind { GenericParamDefKind::Lifetime => { - self.mk_re_early_bound(param.to_early_bound_region_data()).into() - } - GenericParamDefKind::Type { .. } => self.mk_ty_param(param.index, param.name).into(), - GenericParamDefKind::Const { .. } => self - .mk_const( - ParamConst { index: param.index, name: param.name }, - self.type_of(param.def_id) - .no_bound_vars() - .expect("const parameter types cannot be generic"), - ) - .into(), - } - } - - #[inline] - pub fn mk_bound(self, index: ty::DebruijnIndex, bound_ty: ty::BoundTy) -> Ty<'tcx> { - self.mk_ty_from_kind(Bound(index, bound_ty)) - } - - #[inline] - pub fn mk_placeholder(self, placeholder: ty::PlaceholderType) -> Ty<'tcx> { - self.mk_ty_from_kind(Placeholder(placeholder)) - } - - #[inline] - pub fn mk_alias(self, kind: ty::AliasKind, alias_ty: ty::AliasTy<'tcx>) -> Ty<'tcx> { - debug_assert_matches!( - (kind, self.def_kind(alias_ty.def_id)), - (ty::Opaque, DefKind::OpaqueTy) - | (ty::Projection | ty::Inherent, DefKind::AssocTy) - | (ty::Opaque | ty::Projection, DefKind::ImplTraitPlaceholder) - ); - self.mk_ty_from_kind(Alias(kind, alias_ty)) - } - - #[inline] - pub fn mk_opaque(self, def_id: DefId, substs: SubstsRef<'tcx>) -> Ty<'tcx> { - self.mk_alias(ty::Opaque, self.mk_alias_ty(def_id, substs)) - } - - #[inline] - pub fn mk_re_early_bound(self, early_bound_region: ty::EarlyBoundRegion) -> Region<'tcx> { - self.intern_region(ty::ReEarlyBound(early_bound_region)) - } - - #[inline] - pub fn mk_re_late_bound( - self, - debruijn: ty::DebruijnIndex, - bound_region: ty::BoundRegion, - ) -> Region<'tcx> { - // Use a pre-interned one when possible. - if let ty::BoundRegion { var, kind: ty::BrAnon(None) } = bound_region - && let Some(inner) = self.lifetimes.re_late_bounds.get(debruijn.as_usize()) - && let Some(re) = inner.get(var.as_usize()).copied() - { - re - } else { - self.intern_region(ty::ReLateBound(debruijn, bound_region)) - } - } - - #[inline] - pub fn mk_re_free(self, scope: DefId, bound_region: ty::BoundRegionKind) -> Region<'tcx> { - self.intern_region(ty::ReFree(ty::FreeRegion { scope, bound_region })) - } - - #[inline] - pub fn mk_re_var(self, v: ty::RegionVid) -> Region<'tcx> { - // Use a pre-interned one when possible. - self.lifetimes - .re_vars - .get(v.as_usize()) - .copied() - .unwrap_or_else(|| self.intern_region(ty::ReVar(v))) - } - - #[inline] - pub fn mk_re_placeholder(self, placeholder: ty::PlaceholderRegion) -> Region<'tcx> { - self.intern_region(ty::RePlaceholder(placeholder)) - } - - // Avoid this in favour of more specific `mk_re_*` methods, where possible, - // to avoid the cost of the `match`. - pub fn mk_region_from_kind(self, kind: ty::RegionKind<'tcx>) -> Region<'tcx> { - match kind { - ty::ReEarlyBound(region) => self.mk_re_early_bound(region), - ty::ReLateBound(debruijn, region) => self.mk_re_late_bound(debruijn, region), - ty::ReFree(ty::FreeRegion { scope, bound_region }) => { - self.mk_re_free(scope, bound_region) + ty::Region::new_early_bound(self, param.to_early_bound_region_data()).into() } - ty::ReStatic => self.lifetimes.re_static, - ty::ReVar(vid) => self.mk_re_var(vid), - ty::RePlaceholder(region) => self.mk_re_placeholder(region), - ty::ReErased => self.lifetimes.re_erased, - ty::ReError(reported) => self.mk_re_error(reported), + GenericParamDefKind::Type { .. } => Ty::new_param(self, param.index, param.name).into(), + GenericParamDefKind::Const { .. } => ty::Const::new_param( + self, + ParamConst { index: param.index, name: param.name }, + self.type_of(param.def_id) + .no_bound_vars() + .expect("const parameter types cannot be generic"), + ) + .into(), } } @@ -2146,11 +1759,11 @@ impl<'tcx> TyCtxt<'tcx> { self.intern_poly_existential_predicates(eps) } - pub fn mk_predicates(self, preds: &[Predicate<'tcx>]) -> &'tcx List> { + pub fn mk_clauses(self, clauses: &[Clause<'tcx>]) -> &'tcx List> { // FIXME consider asking the input slice to be sorted to avoid // re-interning permutations, in which case that would be asserted // here. - self.intern_predicates(preds) + self.intern_clauses(clauses) } pub fn mk_const_list_from_iter(self, iter: I) -> T::Output @@ -2196,12 +1809,12 @@ impl<'tcx> TyCtxt<'tcx> { T::collect_and_apply(iter, |xs| self.mk_poly_existential_predicates(xs)) } - pub fn mk_predicates_from_iter(self, iter: I) -> T::Output + pub fn mk_clauses_from_iter(self, iter: I) -> T::Output where I: Iterator, - T: CollectAndApply, &'tcx List>>, + T: CollectAndApply, &'tcx List>>, { - T::collect_and_apply(iter, |xs| self.mk_predicates(xs)) + T::collect_and_apply(iter, |xs| self.mk_clauses(xs)) } pub fn mk_type_list_from_iter(self, iter: I) -> T::Output @@ -2212,12 +1825,12 @@ impl<'tcx> TyCtxt<'tcx> { T::collect_and_apply(iter, |xs| self.mk_type_list(xs)) } - pub fn mk_substs_from_iter(self, iter: I) -> T::Output + pub fn mk_args_from_iter(self, iter: I) -> T::Output where I: Iterator, T: CollectAndApply, &'tcx List>>, { - T::collect_and_apply(iter, |xs| self.mk_substs(xs)) + T::collect_and_apply(iter, |xs| self.mk_args(xs)) } pub fn mk_canonical_var_infos_from_iter(self, iter: I) -> T::Output @@ -2244,21 +1857,21 @@ impl<'tcx> TyCtxt<'tcx> { T::collect_and_apply(iter, |xs| self.mk_fields(xs)) } - pub fn mk_substs_trait( + pub fn mk_args_trait( self, self_ty: Ty<'tcx>, rest: impl IntoIterator>, - ) -> SubstsRef<'tcx> { - self.mk_substs_from_iter(iter::once(self_ty.into()).chain(rest)) + ) -> GenericArgsRef<'tcx> { + self.mk_args_from_iter(iter::once(self_ty.into()).chain(rest)) } pub fn mk_alias_ty( self, def_id: DefId, - substs: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> ty::AliasTy<'tcx> { - let substs = self.check_and_mk_substs(def_id, substs); - ty::AliasTy { def_id, substs, _use_mk_alias_ty_instead: () } + let args = self.check_and_mk_args(def_id, args); + ty::AliasTy { def_id, args, _use_mk_alias_ty_instead: () } } pub fn mk_bound_variable_kinds_from_iter(self, iter: I) -> T::Output @@ -2271,6 +1884,7 @@ impl<'tcx> TyCtxt<'tcx> { /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, /// typically generated by `#[derive(LintDiagnostic)]`). + #[track_caller] pub fn emit_spanned_lint( self, lint: &'static Lint, @@ -2291,6 +1905,7 @@ impl<'tcx> TyCtxt<'tcx> { /// /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_span_lint_hir( self, lint: &'static Lint, @@ -2307,6 +1922,7 @@ impl<'tcx> TyCtxt<'tcx> { /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically /// generated by `#[derive(LintDiagnostic)]`). + #[track_caller] pub fn emit_lint( self, lint: &'static Lint, @@ -2322,6 +1938,7 @@ impl<'tcx> TyCtxt<'tcx> { /// /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_lint_node( self, lint: &'static Lint, @@ -2361,6 +1978,84 @@ impl<'tcx> TyCtxt<'tcx> { ) } + /// Given the def-id of an early-bound lifetime on an RPIT corresponding to + /// a duplicated captured lifetime, map it back to the early- or late-bound + /// lifetime of the function from which it originally as captured. If it is + /// a late-bound lifetime, this will represent the liberated (`ReFree`) lifetime + /// of the signature. + // FIXME(RPITIT): if we ever synthesize new lifetimes for RPITITs and not just + // re-use the generics of the opaque, this function will need to be tweaked slightly. + pub fn map_rpit_lifetime_to_fn_lifetime( + self, + mut rpit_lifetime_param_def_id: LocalDefId, + ) -> ty::Region<'tcx> { + debug_assert!( + matches!(self.def_kind(rpit_lifetime_param_def_id), DefKind::LifetimeParam), + "{rpit_lifetime_param_def_id:?} is a {}", + self.def_descr(rpit_lifetime_param_def_id.to_def_id()) + ); + + loop { + let parent = self.local_parent(rpit_lifetime_param_def_id); + let hir::OpaqueTy { lifetime_mapping, .. } = + self.hir().get_by_def_id(parent).expect_item().expect_opaque_ty(); + + let Some((lifetime, _)) = lifetime_mapping + .iter() + .find(|(_, duplicated_param)| *duplicated_param == rpit_lifetime_param_def_id) + else { + bug!("duplicated lifetime param should be present"); + }; + + match self.named_bound_var(lifetime.hir_id) { + Some(resolve_bound_vars::ResolvedArg::EarlyBound(ebv)) => { + let new_parent = self.parent(ebv); + + // If we map to another opaque, then it should be a parent + // of the opaque we mapped from. Continue mapping. + if matches!(self.def_kind(new_parent), DefKind::OpaqueTy) { + debug_assert_eq!(self.parent(parent.to_def_id()), new_parent); + rpit_lifetime_param_def_id = ebv.expect_local(); + continue; + } + + let generics = self.generics_of(new_parent); + return ty::Region::new_early_bound( + self, + ty::EarlyBoundRegion { + def_id: ebv, + index: generics + .param_def_id_to_index(self, ebv) + .expect("early-bound var should be present in fn generics"), + name: self.hir().name(self.local_def_id_to_hir_id(ebv.expect_local())), + }, + ); + } + Some(resolve_bound_vars::ResolvedArg::LateBound(_, _, lbv)) => { + let new_parent = self.parent(lbv); + return ty::Region::new_free( + self, + new_parent, + ty::BoundRegionKind::BrNamed( + lbv, + self.hir().name(self.local_def_id_to_hir_id(lbv.expect_local())), + ), + ); + } + Some(resolve_bound_vars::ResolvedArg::Error(guar)) => { + return ty::Region::new_error(self, guar); + } + _ => { + return ty::Region::new_error_with_message( + self, + lifetime.ident.span, + "cannot resolve lifetime", + ); + } + } + } + } + /// Whether the `def_id` counts as const fn in the current crate, considering all active /// feature gates pub fn is_const_fn(self, def_id: DefId) -> bool { @@ -2393,9 +2088,9 @@ impl<'tcx> TyCtxt<'tcx> { matches!( node, hir::Node::Item(hir::Item { - kind: hir::ItemKind::Impl(hir::Impl { constness: hir::Constness::Const, .. }), + kind: hir::ItemKind::Impl(hir::Impl { generics, .. }), .. - }) + }) if generics.params.iter().any(|p| self.has_attr(p.def_id, sym::rustc_host)) ) } @@ -2403,20 +2098,20 @@ impl<'tcx> TyCtxt<'tcx> { self.opt_local_def_id_to_hir_id(local_def_id).unwrap() } - pub fn trait_solver_next(self) -> bool { + pub fn next_trait_solver_globally(self) -> bool { self.sess.opts.unstable_opts.trait_solver == rustc_session::config::TraitSolver::Next } - pub fn lower_impl_trait_in_trait_to_assoc_ty(self) -> bool { - self.sess.opts.unstable_opts.lower_impl_trait_in_trait_to_assoc_ty + pub fn next_trait_solver_in_coherence(self) -> bool { + matches!( + self.sess.opts.unstable_opts.trait_solver, + rustc_session::config::TraitSolver::Next + | rustc_session::config::TraitSolver::NextCoherence + ) } pub fn is_impl_trait_in_trait(self, def_id: DefId) -> bool { - if self.lower_impl_trait_in_trait_to_assoc_ty() { - self.opt_rpitit_info(def_id).is_some() - } else { - self.def_kind(def_id) == DefKind::ImplTraitPlaceholder - } + self.opt_rpitit_info(def_id).is_some() } /// Named module children from all kinds of items, including imports. @@ -2433,21 +2128,6 @@ impl<'tcx> TyCtxt<'tcx> { } } -impl<'tcx> TyCtxtAt<'tcx> { - /// Constructs a `TyKind::Error` type and registers a `delay_span_bug` to ensure it gets used. - #[track_caller] - pub fn ty_error_misc(self) -> Ty<'tcx> { - self.tcx.ty_error_with_message(self.span, "TyKind::Error constructed but no error reported") - } - - /// Constructs a `TyKind::Error` type and registers a `delay_span_bug` with the given `msg` to - /// ensure it gets used. - #[track_caller] - pub fn ty_error_with_message(self, msg: impl Into) -> Ty<'tcx> { - self.tcx.ty_error_with_message(self.span, msg) - } -} - /// Parameter attributes that can only be determined by examining the body of a function instead /// of just its signature. /// diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs index 6a29063b80db3..5db9b775a0f04 100644 --- a/compiler/rustc_middle/src/ty/diagnostics.rs +++ b/compiler/rustc_middle/src/ty/diagnostics.rs @@ -1,5 +1,7 @@ //! Diagnostics related methods for `Ty`. +use std::borrow::Cow; +use std::fmt::Write; use std::ops::ControlFlow; use crate::ty::{ @@ -13,8 +15,8 @@ use rustc_errors::{Applicability, Diagnostic, DiagnosticArgValue, IntoDiagnostic use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; -use rustc_hir::WherePredicate; -use rustc_span::Span; +use rustc_hir::{PredicateOrigin, WherePredicate}; +use rustc_span::{BytePos, Span}; use rustc_type_ir::sty::TyKind::*; impl<'tcx> IntoDiagnosticArg for Ty<'tcx> { @@ -70,7 +72,7 @@ impl<'tcx> Ty<'tcx> { /// ADTs with no type arguments. pub fn is_simple_text(self) -> bool { match self.kind() { - Adt(_, substs) => substs.non_erasable_generics().next().is_none(), + Adt(_, args) => args.non_erasable_generics().next().is_none(), Ref(_, ty, _) => ty.is_simple_text(), _ => self.is_simple_ty(), } @@ -125,7 +127,7 @@ pub fn suggest_arbitrary_trait_bound<'tcx>( if constraint.ends_with('>') { constraint = format!("{}, {} = {}>", &constraint[..constraint.len() - 1], name, term); } else { - constraint.push_str(&format!("<{} = {}>", name, term)); + constraint.push_str(&format!("<{name} = {term}>")); } } @@ -155,10 +157,11 @@ enum SuggestChangingConstraintsMessage<'a> { RestrictBoundFurther, RestrictType { ty: &'a str }, RestrictTypeFurther { ty: &'a str }, - RemovingQSized, + RemoveMaybeUnsized, + ReplaceMaybeUnsizedWithSized, } -fn suggest_removing_unsized_bound( +fn suggest_changing_unsized_bound( generics: &hir::Generics<'_>, suggestions: &mut Vec<(Span, String, SuggestChangingConstraintsMessage<'_>)>, param: &hir::GenericParam<'_>, @@ -182,12 +185,25 @@ fn suggest_removing_unsized_bound( if poly.trait_ref.trait_def_id() != def_id { continue; } - let sp = generics.span_for_bound_removal(where_pos, pos); - suggestions.push(( - sp, - String::new(), - SuggestChangingConstraintsMessage::RemovingQSized, - )); + if predicate.origin == PredicateOrigin::ImplTrait && predicate.bounds.len() == 1 { + // For `impl ?Sized` with no other bounds, suggest `impl Sized` instead. + let bound_span = bound.span(); + if bound_span.can_be_used_for_suggestions() { + let question_span = bound_span.with_hi(bound_span.lo() + BytePos(1)); + suggestions.push(( + question_span, + String::new(), + SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized, + )); + } + } else { + let sp = generics.span_for_bound_removal(where_pos, pos); + suggestions.push(( + sp, + String::new(), + SuggestChangingConstraintsMessage::RemoveMaybeUnsized, + )); + } } } } @@ -236,15 +252,12 @@ pub fn suggest_constraining_type_params<'a>( { let mut sized_constraints = - constraints.drain_filter(|(_, def_id)| *def_id == tcx.lang_items().sized_trait()); - if let Some((constraint, def_id)) = sized_constraints.next() { + constraints.extract_if(|(_, def_id)| *def_id == tcx.lang_items().sized_trait()); + if let Some((_, def_id)) = sized_constraints.next() { applicability = Applicability::MaybeIncorrect; - err.span_label( - param.span, - format!("this type parameter needs to be `{}`", constraint), - ); - suggest_removing_unsized_bound(generics, &mut suggestions, param, def_id); + err.span_label(param.span, "this type parameter needs to be `Sized`"); + suggest_changing_unsized_bound(generics, &mut suggestions, param, def_id); } } @@ -262,9 +275,9 @@ pub fn suggest_constraining_type_params<'a>( if span_to_replace.is_some() { constraint.clone() } else if bound_list_non_empty { - format!(" + {}", constraint) + format!(" + {constraint}") } else { - format!(" {}", constraint) + format!(" {constraint}") }, SuggestChangingConstraintsMessage::RestrictBoundFurther, )) @@ -323,10 +336,10 @@ pub fn suggest_constraining_type_params<'a>( // - insert: `, X: Bar` suggestions.push(( generics.tail_span_for_predicate_suggestion(), - constraints - .iter() - .map(|&(constraint, _)| format!(", {}: {}", param_name, constraint)) - .collect::(), + constraints.iter().fold(String::new(), |mut string, &(constraint, _)| { + write!(string, ", {param_name}: {constraint}").unwrap(); + string + }), SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }, )); continue; @@ -346,7 +359,7 @@ pub fn suggest_constraining_type_params<'a>( // default (``), so we suggest adding `where T: Bar`. suggestions.push(( generics.tail_span_for_predicate_suggestion(), - format!(" where {}: {}", param_name, constraint), + format!(" where {param_name}: {constraint}"), SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }, )); continue; @@ -359,7 +372,7 @@ pub fn suggest_constraining_type_params<'a>( if let Some(colon_span) = param.colon_span { suggestions.push(( colon_span.shrink_to_hi(), - format!(" {}", constraint), + format!(" {constraint}"), SuggestChangingConstraintsMessage::RestrictType { ty: param_name }, )); continue; @@ -371,7 +384,7 @@ pub fn suggest_constraining_type_params<'a>( // - help: consider restricting this type parameter with `T: Foo` suggestions.push(( param.span.shrink_to_hi(), - format!(": {}", constraint), + format!(": {constraint}"), SuggestChangingConstraintsMessage::RestrictType { ty: param_name }, )); } @@ -384,22 +397,21 @@ pub fn suggest_constraining_type_params<'a>( if suggestions.len() == 1 { let (span, suggestion, msg) = suggestions.pop().unwrap(); - - let s; let msg = match msg { SuggestChangingConstraintsMessage::RestrictBoundFurther => { - "consider further restricting this bound" + Cow::from("consider further restricting this bound") } SuggestChangingConstraintsMessage::RestrictType { ty } => { - s = format!("consider restricting type parameter `{}`", ty); - &s + Cow::from(format!("consider restricting type parameter `{ty}`")) } SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => { - s = format!("consider further restricting type parameter `{}`", ty); - &s + Cow::from(format!("consider further restricting type parameter `{ty}`")) + } + SuggestChangingConstraintsMessage::RemoveMaybeUnsized => { + Cow::from("consider removing the `?Sized` bound to make the type parameter `Sized`") } - SuggestChangingConstraintsMessage::RemovingQSized => { - "consider removing the `?Sized` bound to make the type parameter `Sized`" + SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized => { + Cow::from("consider replacing `?Sized` with `Sized`") } }; @@ -480,8 +492,8 @@ impl<'tcx> TypeVisitor> for IsSuggestableVisitor<'tcx> { Alias(Opaque, AliasTy { def_id, .. }) => { let parent = self.tcx.parent(def_id); - let parent_ty = self.tcx.type_of(parent).subst_identity(); - if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent) + let parent_ty = self.tcx.type_of(parent).instantiate_identity(); + if let DefKind::TyAlias { .. } | DefKind::AssocTy = self.tcx.def_kind(parent) && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = *parent_ty.kind() && parent_opaque_def_id == def_id { @@ -547,8 +559,8 @@ impl<'tcx> FallibleTypeFolder> for MakeSuggestableFolder<'tcx> { let t = match *t.kind() { Infer(InferTy::TyVar(_)) if self.infer_suggestable => t, - FnDef(def_id, substs) => { - self.tcx.mk_fn_ptr(self.tcx.fn_sig(def_id).subst(self.tcx, substs)) + FnDef(def_id, args) => { + Ty::new_fn_ptr(self.tcx, self.tcx.fn_sig(def_id).instantiate(self.tcx, args)) } // FIXME(compiler-errors): We could replace these with infer, I guess. @@ -564,8 +576,8 @@ impl<'tcx> FallibleTypeFolder> for MakeSuggestableFolder<'tcx> { Alias(Opaque, AliasTy { def_id, .. }) => { let parent = self.tcx.parent(def_id); - let parent_ty = self.tcx.type_of(parent).subst_identity(); - if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy = self.tcx.def_kind(parent) + let parent_ty = self.tcx.type_of(parent).instantiate_identity(); + if let hir::def::DefKind::TyAlias { .. } | hir::def::DefKind::AssocTy = self.tcx.def_kind(parent) && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) = *parent_ty.kind() && parent_opaque_def_id == def_id { diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 49ab9b79e96f3..bf6f082c21c31 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -45,7 +45,6 @@ pub enum TypeError<'tcx> { RegionsDoesNotOutlive(Region<'tcx>, Region<'tcx>), RegionsInsufficientlyPolymorphic(BoundRegionKind, Region<'tcx>), - RegionsOverlyPolymorphic(BoundRegionKind, Region<'tcx>), RegionsPlaceholderMismatch, Sorts(ExpectedFound>), @@ -74,7 +73,6 @@ impl TypeError<'_> { match self { TypeError::RegionsDoesNotOutlive(_, _) | TypeError::RegionsInsufficientlyPolymorphic(_, _) - | TypeError::RegionsOverlyPolymorphic(_, _) | TypeError::RegionsPlaceholderMismatch => true, _ => false, } @@ -92,17 +90,12 @@ impl<'tcx> TypeError<'tcx> { // A naive approach to making sure that we're not reporting silly errors such as: // (expected closure, found closure). if expected == found { - format!("expected {}, found a different {}", expected, found) + format!("expected {expected}, found a different {found}") } else { - format!("expected {}, found {}", expected, found) + format!("expected {expected}, found {found}") } } - let br_string = |br: ty::BoundRegionKind| match br { - ty::BrNamed(_, name) => format!(" {}", name), - _ => String::new(), - }; - match self { CyclicTy(_) => "cyclic type of infinite size".into(), CyclicConst(_) => "encountered a self-referencing constant".into(), @@ -138,17 +131,12 @@ impl<'tcx> TypeError<'tcx> { ) .into(), ArgCount => "incorrect number of function parameters".into(), - FieldMisMatch(adt, field) => format!("field type mismatch: {}.{}", adt, field).into(), + FieldMisMatch(adt, field) => format!("field type mismatch: {adt}.{field}").into(), RegionsDoesNotOutlive(..) => "lifetime mismatch".into(), // Actually naming the region here is a bit confusing because context is lacking RegionsInsufficientlyPolymorphic(..) => { "one type is more general than the other".into() } - RegionsOverlyPolymorphic(br, _) => format!( - "expected concrete lifetime, found bound lifetime parameter{}", - br_string(br) - ) - .into(), RegionsPlaceholderMismatch => "one type is more general than the other".into(), ArgumentSorts(values, _) | Sorts(values) => { let expected = values.expected.sort_string(tcx); @@ -176,7 +164,7 @@ impl<'tcx> TypeError<'tcx> { ty::IntVarValue::IntType(ty) => ty.name_str(), ty::IntVarValue::UintType(ty) => ty.name_str(), }; - format!("expected `{}`, found `{}`", expected, found).into() + format!("expected `{expected}`, found `{found}`").into() } FloatMismatch(ref values) => format!( "expected `{}`, found `{}`", @@ -228,7 +216,6 @@ impl<'tcx> TypeError<'tcx> { | FieldMisMatch(..) | RegionsDoesNotOutlive(..) | RegionsInsufficientlyPolymorphic(..) - | RegionsOverlyPolymorphic(..) | RegionsPlaceholderMismatch | Traits(_) | ProjectionMismatched(_) @@ -313,6 +300,7 @@ impl<'tcx> Ty<'tcx> { ty::Placeholder(..) => "higher-ranked type".into(), ty::Bound(..) => "bound type variable".into(), ty::Alias(ty::Projection | ty::Inherent, _) => "associated type".into(), + ty::Alias(ty::Weak, _) => "type alias".into(), ty::Param(_) => "type parameter".into(), ty::Alias(ty::Opaque, ..) => "opaque type".into(), } @@ -351,12 +339,17 @@ impl<'tcx> TyCtxt<'tcx> { } pub fn short_ty_string(self, ty: Ty<'tcx>) -> (String, Option) { - let width = self.sess.diagnostic_width(); - let length_limit = width.saturating_sub(30); let regular = FmtPrinter::new(self, hir::def::Namespace::TypeNS) .pretty_print_type(ty) .expect("could not write to `String`") .into_buffer(); + + if !self.sess.opts.unstable_opts.write_long_types_to_disk { + return (regular, None); + } + + let width = self.sess.diagnostic_width(); + let length_limit = width.saturating_sub(30); if regular.len() <= width { return (regular, None); } diff --git a/compiler/rustc_middle/src/ty/fast_reject.rs b/compiler/rustc_middle/src/ty/fast_reject.rs index 76f61d9ac9c2d..668aa4521c101 100644 --- a/compiler/rustc_middle/src/ty/fast_reject.rs +++ b/compiler/rustc_middle/src/ty/fast_reject.rs @@ -1,40 +1,38 @@ use crate::mir::Mutability; -use crate::ty::subst::GenericArgKind; -use crate::ty::{self, SubstsRef, Ty, TyCtxt, TypeVisitableExt}; +use crate::ty::GenericArgKind; +use crate::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt}; use rustc_hir::def_id::DefId; use std::fmt::Debug; use std::hash::Hash; use std::iter; -use self::SimplifiedType::*; - /// See `simplify_type`. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable)] pub enum SimplifiedType { - BoolSimplifiedType, - CharSimplifiedType, - IntSimplifiedType(ty::IntTy), - UintSimplifiedType(ty::UintTy), - FloatSimplifiedType(ty::FloatTy), - AdtSimplifiedType(DefId), - ForeignSimplifiedType(DefId), - StrSimplifiedType, - ArraySimplifiedType, - SliceSimplifiedType, - RefSimplifiedType(Mutability), - PtrSimplifiedType(Mutability), - NeverSimplifiedType, - TupleSimplifiedType(usize), + Bool, + Char, + Int(ty::IntTy), + Uint(ty::UintTy), + Float(ty::FloatTy), + Adt(DefId), + Foreign(DefId), + Str, + Array, + Slice, + Ref(Mutability), + Ptr(Mutability), + Never, + Tuple(usize), /// A trait object, all of whose components are markers /// (e.g., `dyn Send + Sync`). - MarkerTraitObjectSimplifiedType, - TraitSimplifiedType(DefId), - ClosureSimplifiedType(DefId), - GeneratorSimplifiedType(DefId), - GeneratorWitnessSimplifiedType(usize), - GeneratorWitnessMIRSimplifiedType(DefId), - FunctionSimplifiedType(usize), - PlaceholderSimplifiedType, + MarkerTraitObject, + Trait(DefId), + Closure(DefId), + Generator(DefId), + GeneratorWitness(usize), + GeneratorWitnessMIR(DefId), + Function(usize), + Placeholder, } /// Generic parameters are pretty much just bound variables, e.g. @@ -64,6 +62,9 @@ pub enum TreatParams { /// correct mode for *lookup*, as during candidate selection. /// /// N.B. during deep rejection, this acts identically to `ForLookup`. + /// + /// FIXME(-Ztrait-solver=next): Remove this variant and cleanup + /// the code. NextSolverLookup, } @@ -110,34 +111,36 @@ pub fn simplify_type<'tcx>( treat_params: TreatParams, ) -> Option { match *ty.kind() { - ty::Bool => Some(BoolSimplifiedType), - ty::Char => Some(CharSimplifiedType), - ty::Int(int_type) => Some(IntSimplifiedType(int_type)), - ty::Uint(uint_type) => Some(UintSimplifiedType(uint_type)), - ty::Float(float_type) => Some(FloatSimplifiedType(float_type)), - ty::Adt(def, _) => Some(AdtSimplifiedType(def.did())), - ty::Str => Some(StrSimplifiedType), - ty::Array(..) => Some(ArraySimplifiedType), - ty::Slice(..) => Some(SliceSimplifiedType), - ty::RawPtr(ptr) => Some(PtrSimplifiedType(ptr.mutbl)), + ty::Bool => Some(SimplifiedType::Bool), + ty::Char => Some(SimplifiedType::Char), + ty::Int(int_type) => Some(SimplifiedType::Int(int_type)), + ty::Uint(uint_type) => Some(SimplifiedType::Uint(uint_type)), + ty::Float(float_type) => Some(SimplifiedType::Float(float_type)), + ty::Adt(def, _) => Some(SimplifiedType::Adt(def.did())), + ty::Str => Some(SimplifiedType::Str), + ty::Array(..) => Some(SimplifiedType::Array), + ty::Slice(..) => Some(SimplifiedType::Slice), + ty::RawPtr(ptr) => Some(SimplifiedType::Ptr(ptr.mutbl)), ty::Dynamic(trait_info, ..) => match trait_info.principal_def_id() { Some(principal_def_id) if !tcx.trait_is_auto(principal_def_id) => { - Some(TraitSimplifiedType(principal_def_id)) + Some(SimplifiedType::Trait(principal_def_id)) } - _ => Some(MarkerTraitObjectSimplifiedType), + _ => Some(SimplifiedType::MarkerTraitObject), }, - ty::Ref(_, _, mutbl) => Some(RefSimplifiedType(mutbl)), - ty::FnDef(def_id, _) | ty::Closure(def_id, _) => Some(ClosureSimplifiedType(def_id)), - ty::Generator(def_id, _, _) => Some(GeneratorSimplifiedType(def_id)), - ty::GeneratorWitness(tys) => Some(GeneratorWitnessSimplifiedType(tys.skip_binder().len())), - ty::GeneratorWitnessMIR(def_id, _) => Some(GeneratorWitnessMIRSimplifiedType(def_id)), - ty::Never => Some(NeverSimplifiedType), - ty::Tuple(tys) => Some(TupleSimplifiedType(tys.len())), - ty::FnPtr(f) => Some(FunctionSimplifiedType(f.skip_binder().inputs().len())), - ty::Placeholder(..) => Some(PlaceholderSimplifiedType), + ty::Ref(_, _, mutbl) => Some(SimplifiedType::Ref(mutbl)), + ty::FnDef(def_id, _) | ty::Closure(def_id, _) => Some(SimplifiedType::Closure(def_id)), + ty::Generator(def_id, _, _) => Some(SimplifiedType::Generator(def_id)), + ty::GeneratorWitness(tys) => { + Some(SimplifiedType::GeneratorWitness(tys.skip_binder().len())) + } + ty::GeneratorWitnessMIR(def_id, _) => Some(SimplifiedType::GeneratorWitnessMIR(def_id)), + ty::Never => Some(SimplifiedType::Never), + ty::Tuple(tys) => Some(SimplifiedType::Tuple(tys.len())), + ty::FnPtr(f) => Some(SimplifiedType::Function(f.skip_binder().inputs().len())), + ty::Placeholder(..) => Some(SimplifiedType::Placeholder), ty::Param(_) => match treat_params { TreatParams::ForLookup | TreatParams::NextSolverLookup => { - Some(PlaceholderSimplifiedType) + Some(SimplifiedType::Placeholder) } TreatParams::AsCandidateKey => None, }, @@ -147,11 +150,13 @@ pub fn simplify_type<'tcx>( // // We will have to be careful with lazy normalization here. // FIXME(lazy_normalization): This is probably not right... - TreatParams::ForLookup if !ty.has_non_region_infer() => Some(PlaceholderSimplifiedType), - TreatParams::NextSolverLookup => Some(PlaceholderSimplifiedType), + TreatParams::ForLookup if !ty.has_non_region_infer() => { + Some(SimplifiedType::Placeholder) + } + TreatParams::NextSolverLookup => Some(SimplifiedType::Placeholder), TreatParams::ForLookup | TreatParams::AsCandidateKey => None, }, - ty::Foreign(def_id) => Some(ForeignSimplifiedType(def_id)), + ty::Foreign(def_id) => Some(SimplifiedType::Foreign(def_id)), ty::Bound(..) | ty::Infer(_) | ty::Error(_) => None, } } @@ -159,12 +164,12 @@ pub fn simplify_type<'tcx>( impl SimplifiedType { pub fn def(self) -> Option { match self { - AdtSimplifiedType(d) - | ForeignSimplifiedType(d) - | TraitSimplifiedType(d) - | ClosureSimplifiedType(d) - | GeneratorSimplifiedType(d) - | GeneratorWitnessMIRSimplifiedType(d) => Some(d), + SimplifiedType::Adt(d) + | SimplifiedType::Foreign(d) + | SimplifiedType::Trait(d) + | SimplifiedType::Closure(d) + | SimplifiedType::Generator(d) + | SimplifiedType::GeneratorWitnessMIR(d) => Some(d), _ => None, } } @@ -188,12 +193,12 @@ pub struct DeepRejectCtxt { } impl DeepRejectCtxt { - pub fn substs_refs_may_unify<'tcx>( + pub fn args_refs_may_unify<'tcx>( self, - obligation_substs: SubstsRef<'tcx>, - impl_substs: SubstsRef<'tcx>, + obligation_args: GenericArgsRef<'tcx>, + impl_args: GenericArgsRef<'tcx>, ) -> bool { - iter::zip(obligation_substs, impl_substs).all(|(obl, imp)| { + iter::zip(obligation_args, impl_args).all(|(obl, imp)| { match (obl.unpack(), imp.unpack()) { // We don't fast reject based on regions for now. (GenericArgKind::Lifetime(_), GenericArgKind::Lifetime(_)) => true, @@ -258,9 +263,9 @@ impl DeepRejectCtxt { } _ => false, }, - ty::Adt(obl_def, obl_substs) => match k { - &ty::Adt(impl_def, impl_substs) => { - obl_def == impl_def && self.substs_refs_may_unify(obl_substs, impl_substs) + ty::Adt(obl_def, obl_args) => match k { + &ty::Adt(impl_def, impl_args) => { + obl_def == impl_def && self.args_refs_may_unify(obl_args, impl_args) } _ => false, }, diff --git a/compiler/rustc_middle/src/ty/flags.rs b/compiler/rustc_middle/src/ty/flags.rs index d64875a9f00e8..bbd4a62333098 100644 --- a/compiler/rustc_middle/src/ty/flags.rs +++ b/compiler/rustc_middle/src/ty/flags.rs @@ -1,5 +1,5 @@ -use crate::ty::subst::{GenericArg, GenericArgKind}; use crate::ty::{self, InferConst, Ty, TypeFlags}; +use crate::ty::{GenericArg, GenericArgKind}; use std::slice; #[derive(Debug)] @@ -105,48 +105,48 @@ impl FlagComputation { self.add_flags(TypeFlags::STILL_FURTHER_SPECIALIZABLE); } - ty::Generator(_, substs, _) => { - let substs = substs.as_generator(); + ty::Generator(_, args, _) => { + let args = args.as_generator(); let should_remove_further_specializable = !self.flags.contains(TypeFlags::STILL_FURTHER_SPECIALIZABLE); - self.add_substs(substs.parent_substs()); + self.add_args(args.parent_args()); if should_remove_further_specializable { self.flags -= TypeFlags::STILL_FURTHER_SPECIALIZABLE; } - self.add_ty(substs.resume_ty()); - self.add_ty(substs.return_ty()); - self.add_ty(substs.witness()); - self.add_ty(substs.yield_ty()); - self.add_ty(substs.tupled_upvars_ty()); + self.add_ty(args.resume_ty()); + self.add_ty(args.return_ty()); + self.add_ty(args.witness()); + self.add_ty(args.yield_ty()); + self.add_ty(args.tupled_upvars_ty()); } &ty::GeneratorWitness(ts) => { self.bound_computation(ts, |flags, ts| flags.add_tys(ts)); } - ty::GeneratorWitnessMIR(_, substs) => { + ty::GeneratorWitnessMIR(_, args) => { let should_remove_further_specializable = !self.flags.contains(TypeFlags::STILL_FURTHER_SPECIALIZABLE); - self.add_substs(substs); + self.add_args(args); if should_remove_further_specializable { self.flags -= TypeFlags::STILL_FURTHER_SPECIALIZABLE; } self.add_flags(TypeFlags::HAS_TY_GENERATOR); } - &ty::Closure(_, substs) => { - let substs = substs.as_closure(); + &ty::Closure(_, args) => { + let args = args.as_closure(); let should_remove_further_specializable = !self.flags.contains(TypeFlags::STILL_FURTHER_SPECIALIZABLE); - self.add_substs(substs.parent_substs()); + self.add_args(args.parent_args()); if should_remove_further_specializable { self.flags -= TypeFlags::STILL_FURTHER_SPECIALIZABLE; } - self.add_ty(substs.sig_as_fn_ptr_ty()); - self.add_ty(substs.kind_ty()); - self.add_ty(substs.tupled_upvars_ty()); + self.add_ty(args.sig_as_fn_ptr_ty()); + self.add_ty(args.kind_ty()); + self.add_ty(args.tupled_upvars_ty()); } &ty::Bound(debruijn, _) => { @@ -172,13 +172,13 @@ impl FlagComputation { } } - &ty::Adt(_, substs) => { - self.add_substs(substs); + &ty::Adt(_, args) => { + self.add_args(args); } &ty::Alias(kind, data) => { self.add_flags(match kind { - ty::Projection => TypeFlags::HAS_TY_PROJECTION, + ty::Weak | ty::Projection => TypeFlags::HAS_TY_PROJECTION, ty::Inherent => TypeFlags::HAS_TY_INHERENT, ty::Opaque => TypeFlags::HAS_TY_OPAQUE, }); @@ -189,7 +189,7 @@ impl FlagComputation { &ty::Dynamic(obj, r, _) => { for predicate in obj.iter() { self.bound_computation(predicate, |computation, predicate| match predicate { - ty::ExistentialPredicate::Trait(tr) => computation.add_substs(tr.substs), + ty::ExistentialPredicate::Trait(tr) => computation.add_args(tr.args), ty::ExistentialPredicate::Projection(p) => { computation.add_existential_projection(&p); } @@ -220,8 +220,8 @@ impl FlagComputation { self.add_tys(types); } - &ty::FnDef(_, substs) => { - self.add_substs(substs); + &ty::FnDef(_, args) => { + self.add_args(args); } &ty::FnPtr(fn_sig) => self.bound_computation(fn_sig, |computation, fn_sig| { @@ -237,21 +237,24 @@ impl FlagComputation { fn add_predicate_atom(&mut self, atom: ty::PredicateKind<'_>) { match atom { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) => { - self.add_substs(trait_pred.trait_ref.substs); + ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) => { + self.add_args(trait_pred.trait_ref.args); } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate(a, b))) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate( + a, + b, + ))) => { self.add_region(a); self.add_region(b); } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( ty, region, ))) => { self.add_ty(ty); self.add_region(region); } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { self.add_const(ct); self.add_ty(ty); } @@ -263,30 +266,27 @@ impl FlagComputation { self.add_ty(a); self.add_ty(b); } - ty::PredicateKind::Clause(ty::Clause::Projection(ty::ProjectionPredicate { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(ty::ProjectionPredicate { projection_ty, term, })) => { self.add_alias_ty(projection_ty); self.add_term(term); } - ty::PredicateKind::WellFormed(arg) => { - self.add_substs(slice::from_ref(&arg)); + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { + self.add_args(slice::from_ref(&arg)); } ty::PredicateKind::ObjectSafe(_def_id) => {} - ty::PredicateKind::ClosureKind(_def_id, substs, _kind) => { - self.add_substs(substs); + ty::PredicateKind::ClosureKind(_def_id, args, _kind) => { + self.add_args(args); } - ty::PredicateKind::ConstEvaluatable(uv) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(uv)) => { self.add_const(uv); } ty::PredicateKind::ConstEquate(expected, found) => { self.add_const(expected); self.add_const(found); } - ty::PredicateKind::TypeWellFormedFromEnv(ty) => { - self.add_ty(ty); - } ty::PredicateKind::Ambiguous => {} ty::PredicateKind::AliasRelate(t1, t2, _) => { self.add_term(t1); @@ -317,7 +317,7 @@ impl FlagComputation { self.add_ty(c.ty()); match c.kind() { ty::ConstKind::Unevaluated(uv) => { - self.add_substs(uv.substs); + self.add_args(uv.args); self.add_flags(TypeFlags::HAS_CT_PROJECTION); } ty::ConstKind::Infer(infer) => { @@ -365,7 +365,7 @@ impl FlagComputation { } fn add_existential_projection(&mut self, projection: &ty::ExistentialProjection<'_>) { - self.add_substs(projection.substs); + self.add_args(projection.args); match projection.term.unpack() { ty::TermKind::Ty(ty) => self.add_ty(ty), ty::TermKind::Const(ct) => self.add_const(ct), @@ -373,11 +373,11 @@ impl FlagComputation { } fn add_alias_ty(&mut self, alias_ty: ty::AliasTy<'_>) { - self.add_substs(alias_ty.substs); + self.add_args(alias_ty.args); } - fn add_substs(&mut self, substs: &[GenericArg<'_>]) { - for kind in substs { + fn add_args(&mut self, args: &[GenericArg<'_>]) { + for kind in args { match kind.unpack() { GenericArgKind::Type(ty) => self.add_ty(ty), GenericArgKind::Lifetime(lt) => self.add_region(lt), diff --git a/compiler/rustc_middle/src/ty/fold.rs b/compiler/rustc_middle/src/ty/fold.rs index 25890eb15cde4..77cf6bee79d64 100644 --- a/compiler/rustc_middle/src/ty/fold.rs +++ b/compiler/rustc_middle/src/ty/fold.rs @@ -213,7 +213,7 @@ where // debruijn index. Then we adjust it to the // correct depth. assert_eq!(debruijn1, ty::INNERMOST); - self.tcx.mk_re_late_bound(debruijn, br) + ty::Region::new_late_bound(self.tcx, debruijn, br) } else { region } @@ -328,7 +328,7 @@ impl<'tcx> TyCtxt<'tcx> { T: TypeFoldable>, { self.replace_late_bound_regions_uncached(value, |br| { - self.mk_re_free(all_outlive_scope, br.kind) + ty::Region::new_free(self, all_outlive_scope, br.kind) }) } @@ -341,16 +341,21 @@ impl<'tcx> TyCtxt<'tcx> { value, FnMutDelegate { regions: &mut |r: ty::BoundRegion| { - self.mk_re_late_bound( + ty::Region::new_late_bound( + self, ty::INNERMOST, ty::BoundRegion { var: shift_bv(r.var), kind: r.kind }, ) }, types: &mut |t: ty::BoundTy| { - self.mk_bound(ty::INNERMOST, ty::BoundTy { var: shift_bv(t.var), kind: t.kind }) + Ty::new_bound( + self, + ty::INNERMOST, + ty::BoundTy { var: shift_bv(t.var), kind: t.kind }, + ) }, consts: &mut |c, ty: Ty<'tcx>| { - self.mk_const(ty::ConstKind::Bound(ty::INNERMOST, shift_bv(c)), ty) + ty::Const::new_bound(self, ty::INNERMOST, shift_bv(c), ty) }, }, ) @@ -383,7 +388,7 @@ impl<'tcx> TyCtxt<'tcx> { .or_insert_with(|| ty::BoundVariableKind::Region(ty::BrAnon(None))) .expect_region(); let br = ty::BoundRegion { var, kind }; - self.tcx.mk_re_late_bound(ty::INNERMOST, br) + ty::Region::new_late_bound(self.tcx, ty::INNERMOST, br) } fn replace_ty(&mut self, bt: ty::BoundTy) -> Ty<'tcx> { let entry = self.map.entry(bt.var); @@ -392,14 +397,14 @@ impl<'tcx> TyCtxt<'tcx> { let kind = entry .or_insert_with(|| ty::BoundVariableKind::Ty(ty::BoundTyKind::Anon)) .expect_ty(); - self.tcx.mk_bound(ty::INNERMOST, BoundTy { var, kind }) + Ty::new_bound(self.tcx, ty::INNERMOST, BoundTy { var, kind }) } fn replace_const(&mut self, bv: ty::BoundVar, ty: Ty<'tcx>) -> ty::Const<'tcx> { let entry = self.map.entry(bv); let index = entry.index(); let var = ty::BoundVar::from_usize(index); let () = entry.or_insert_with(|| ty::BoundVariableKind::Const).expect_const(); - self.tcx.mk_const(ty::ConstKind::Bound(ty::INNERMOST, var), ty) + ty::Const::new_bound(self.tcx, ty::INNERMOST, var, ty) } } @@ -451,7 +456,7 @@ impl<'tcx> TypeFolder> for Shifter<'tcx> { match *r { ty::ReLateBound(debruijn, br) if debruijn >= self.current_index => { let debruijn = debruijn.shifted_in(self.amount); - self.tcx.mk_re_late_bound(debruijn, br) + ty::Region::new_late_bound(self.tcx, debruijn, br) } _ => r, } @@ -461,7 +466,7 @@ impl<'tcx> TypeFolder> for Shifter<'tcx> { match *ty.kind() { ty::Bound(debruijn, bound_ty) if debruijn >= self.current_index => { let debruijn = debruijn.shifted_in(self.amount); - self.tcx.mk_bound(debruijn, bound_ty) + Ty::new_bound(self.tcx, debruijn, bound_ty) } _ if ty.has_vars_bound_at_or_above(self.current_index) => ty.super_fold_with(self), @@ -474,7 +479,7 @@ impl<'tcx> TypeFolder> for Shifter<'tcx> { && debruijn >= self.current_index { let debruijn = debruijn.shifted_in(self.amount); - self.tcx.mk_const(ty::ConstKind::Bound(debruijn, bound_ct), ct.ty()) + ty::Const::new_bound(self.tcx, debruijn, bound_ct, ct.ty()) } else { ct.super_fold_with(self) } @@ -492,7 +497,7 @@ pub fn shift_region<'tcx>( ) -> ty::Region<'tcx> { match *region { ty::ReLateBound(debruijn, br) if amount > 0 => { - tcx.mk_re_late_bound(debruijn.shifted_in(amount), br) + ty::Region::new_late_bound(tcx, debruijn.shifted_in(amount), br) } _ => region, } diff --git a/compiler/rustc_middle/src/ty/generic_args.rs b/compiler/rustc_middle/src/ty/generic_args.rs new file mode 100644 index 0000000000000..97dab5cb47e8b --- /dev/null +++ b/compiler/rustc_middle/src/ty/generic_args.rs @@ -0,0 +1,1053 @@ +// Generic arguments. + +use crate::ty::codec::{TyDecoder, TyEncoder}; +use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable}; +use crate::ty::sty::{ClosureArgs, GeneratorArgs, InlineConstArgs}; +use crate::ty::visit::{TypeVisitable, TypeVisitableExt, TypeVisitor}; +use crate::ty::{self, Lift, List, ParamConst, Ty, TyCtxt}; + +use rustc_data_structures::intern::Interned; +use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; +use rustc_hir::def_id::DefId; +use rustc_macros::HashStable; +use rustc_serialize::{self, Decodable, Encodable}; +use rustc_span::sym; +use rustc_type_ir::WithCachedTypeInfo; +use smallvec::SmallVec; + +use core::intrinsics; +use std::cmp::Ordering; +use std::marker::PhantomData; +use std::mem; +use std::num::NonZeroUsize; +use std::ops::{ControlFlow, Deref}; + +/// An entity in the Rust type system, which can be one of +/// several kinds (types, lifetimes, and consts). +/// To reduce memory usage, a `GenericArg` is an interned pointer, +/// with the lowest 2 bits being reserved for a tag to +/// indicate the type (`Ty`, `Region`, or `Const`) it points to. +/// +/// Note: the `PartialEq`, `Eq` and `Hash` derives are only valid because `Ty`, +/// `Region` and `Const` are all interned. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct GenericArg<'tcx> { + ptr: NonZeroUsize, + marker: PhantomData<(Ty<'tcx>, ty::Region<'tcx>, ty::Const<'tcx>)>, +} + +impl<'tcx> IntoDiagnosticArg for GenericArg<'tcx> { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + self.to_string().into_diagnostic_arg() + } +} + +const TAG_MASK: usize = 0b11; +const TYPE_TAG: usize = 0b00; +const REGION_TAG: usize = 0b01; +const CONST_TAG: usize = 0b10; + +#[derive(Debug, TyEncodable, TyDecodable, PartialEq, Eq, PartialOrd, Ord, HashStable)] +pub enum GenericArgKind<'tcx> { + Lifetime(ty::Region<'tcx>), + Type(Ty<'tcx>), + Const(ty::Const<'tcx>), +} + +impl<'tcx> GenericArgKind<'tcx> { + #[inline] + fn pack(self) -> GenericArg<'tcx> { + let (tag, ptr) = match self { + GenericArgKind::Lifetime(lt) => { + // Ensure we can use the tag bits. + assert_eq!(mem::align_of_val(&*lt.0.0) & TAG_MASK, 0); + (REGION_TAG, lt.0.0 as *const ty::RegionKind<'tcx> as usize) + } + GenericArgKind::Type(ty) => { + // Ensure we can use the tag bits. + assert_eq!(mem::align_of_val(&*ty.0.0) & TAG_MASK, 0); + (TYPE_TAG, ty.0.0 as *const WithCachedTypeInfo> as usize) + } + GenericArgKind::Const(ct) => { + // Ensure we can use the tag bits. + assert_eq!(mem::align_of_val(&*ct.0.0) & TAG_MASK, 0); + (CONST_TAG, ct.0.0 as *const ty::ConstData<'tcx> as usize) + } + }; + + GenericArg { ptr: unsafe { NonZeroUsize::new_unchecked(ptr | tag) }, marker: PhantomData } + } +} + +impl<'tcx> Ord for GenericArg<'tcx> { + fn cmp(&self, other: &GenericArg<'tcx>) -> Ordering { + self.unpack().cmp(&other.unpack()) + } +} + +impl<'tcx> PartialOrd for GenericArg<'tcx> { + fn partial_cmp(&self, other: &GenericArg<'tcx>) -> Option { + Some(self.cmp(&other)) + } +} + +impl<'tcx> From> for GenericArg<'tcx> { + #[inline] + fn from(r: ty::Region<'tcx>) -> GenericArg<'tcx> { + GenericArgKind::Lifetime(r).pack() + } +} + +impl<'tcx> From> for GenericArg<'tcx> { + #[inline] + fn from(ty: Ty<'tcx>) -> GenericArg<'tcx> { + GenericArgKind::Type(ty).pack() + } +} + +impl<'tcx> From> for GenericArg<'tcx> { + #[inline] + fn from(c: ty::Const<'tcx>) -> GenericArg<'tcx> { + GenericArgKind::Const(c).pack() + } +} + +impl<'tcx> From> for GenericArg<'tcx> { + fn from(value: ty::Term<'tcx>) -> Self { + match value.unpack() { + ty::TermKind::Ty(t) => t.into(), + ty::TermKind::Const(c) => c.into(), + } + } +} + +impl<'tcx> GenericArg<'tcx> { + #[inline] + pub fn unpack(self) -> GenericArgKind<'tcx> { + let ptr = self.ptr.get(); + // SAFETY: use of `Interned::new_unchecked` here is ok because these + // pointers were originally created from `Interned` types in `pack()`, + // and this is just going in the other direction. + unsafe { + match ptr & TAG_MASK { + REGION_TAG => GenericArgKind::Lifetime(ty::Region(Interned::new_unchecked( + &*((ptr & !TAG_MASK) as *const ty::RegionKind<'tcx>), + ))), + TYPE_TAG => GenericArgKind::Type(Ty(Interned::new_unchecked( + &*((ptr & !TAG_MASK) as *const WithCachedTypeInfo>), + ))), + CONST_TAG => GenericArgKind::Const(ty::Const(Interned::new_unchecked( + &*((ptr & !TAG_MASK) as *const ty::ConstData<'tcx>), + ))), + _ => intrinsics::unreachable(), + } + } + } + + #[inline] + pub fn as_type(self) -> Option> { + match self.unpack() { + GenericArgKind::Type(ty) => Some(ty), + _ => None, + } + } + + #[inline] + pub fn as_region(self) -> Option> { + match self.unpack() { + GenericArgKind::Lifetime(re) => Some(re), + _ => None, + } + } + + #[inline] + pub fn as_const(self) -> Option> { + match self.unpack() { + GenericArgKind::Const(ct) => Some(ct), + _ => None, + } + } + + /// Unpack the `GenericArg` as a region when it is known certainly to be a region. + pub fn expect_region(self) -> ty::Region<'tcx> { + self.as_region().unwrap_or_else(|| bug!("expected a region, but found another kind")) + } + + /// Unpack the `GenericArg` as a type when it is known certainly to be a type. + /// This is true in cases where `GenericArgs` is used in places where the kinds are known + /// to be limited (e.g. in tuples, where the only parameters are type parameters). + pub fn expect_ty(self) -> Ty<'tcx> { + self.as_type().unwrap_or_else(|| bug!("expected a type, but found another kind")) + } + + /// Unpack the `GenericArg` as a const when it is known certainly to be a const. + pub fn expect_const(self) -> ty::Const<'tcx> { + self.as_const().unwrap_or_else(|| bug!("expected a const, but found another kind")) + } + + pub fn is_non_region_infer(self) -> bool { + match self.unpack() { + GenericArgKind::Lifetime(_) => false, + GenericArgKind::Type(ty) => ty.is_ty_or_numeric_infer(), + GenericArgKind::Const(ct) => ct.is_ct_infer(), + } + } +} + +impl<'a, 'tcx> Lift<'tcx> for GenericArg<'a> { + type Lifted = GenericArg<'tcx>; + + fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option { + match self.unpack() { + GenericArgKind::Lifetime(lt) => tcx.lift(lt).map(|lt| lt.into()), + GenericArgKind::Type(ty) => tcx.lift(ty).map(|ty| ty.into()), + GenericArgKind::Const(ct) => tcx.lift(ct).map(|ct| ct.into()), + } + } +} + +impl<'tcx> TypeFoldable> for GenericArg<'tcx> { + fn try_fold_with>>( + self, + folder: &mut F, + ) -> Result { + match self.unpack() { + GenericArgKind::Lifetime(lt) => lt.try_fold_with(folder).map(Into::into), + GenericArgKind::Type(ty) => ty.try_fold_with(folder).map(Into::into), + GenericArgKind::Const(ct) => ct.try_fold_with(folder).map(Into::into), + } + } +} + +impl<'tcx> TypeVisitable> for GenericArg<'tcx> { + fn visit_with>>(&self, visitor: &mut V) -> ControlFlow { + match self.unpack() { + GenericArgKind::Lifetime(lt) => lt.visit_with(visitor), + GenericArgKind::Type(ty) => ty.visit_with(visitor), + GenericArgKind::Const(ct) => ct.visit_with(visitor), + } + } +} + +impl<'tcx, E: TyEncoder>> Encodable for GenericArg<'tcx> { + fn encode(&self, e: &mut E) { + self.unpack().encode(e) + } +} + +impl<'tcx, D: TyDecoder>> Decodable for GenericArg<'tcx> { + fn decode(d: &mut D) -> GenericArg<'tcx> { + GenericArgKind::decode(d).pack() + } +} + +/// List of generic arguments that are gonna be used to replace generic parameters. +pub type GenericArgs<'tcx> = List>; + +pub type GenericArgsRef<'tcx> = &'tcx GenericArgs<'tcx>; + +impl<'tcx> GenericArgs<'tcx> { + /// Converts generic args to a type list. + /// + /// # Panics + /// + /// If any of the generic arguments are not types. + pub fn into_type_list(&self, tcx: TyCtxt<'tcx>) -> &'tcx List> { + tcx.mk_type_list_from_iter(self.iter().map(|arg| match arg.unpack() { + GenericArgKind::Type(ty) => ty, + _ => bug!("`into_type_list` called on generic arg with non-types"), + })) + } + + /// Interpret these generic args as the args of a closure type. + /// Closure args have a particular structure controlled by the + /// compiler that encodes information like the signature and closure kind; + /// see `ty::ClosureArgs` struct for more comments. + pub fn as_closure(&'tcx self) -> ClosureArgs<'tcx> { + ClosureArgs { args: self } + } + + /// Interpret these generic args as the args of a generator type. + /// Generator args have a particular structure controlled by the + /// compiler that encodes information like the signature and generator kind; + /// see `ty::GeneratorArgs` struct for more comments. + pub fn as_generator(&'tcx self) -> GeneratorArgs<'tcx> { + GeneratorArgs { args: self } + } + + /// Interpret these generic args as the args of an inline const. + /// Inline const args have a particular structure controlled by the + /// compiler that encodes information like the inferred type; + /// see `ty::InlineConstArgs` struct for more comments. + pub fn as_inline_const(&'tcx self) -> InlineConstArgs<'tcx> { + InlineConstArgs { args: self } + } + + /// Creates an `GenericArgs` that maps each generic parameter to itself. + pub fn identity_for_item(tcx: TyCtxt<'tcx>, def_id: impl Into) -> GenericArgsRef<'tcx> { + Self::for_item(tcx, def_id.into(), |param, _| tcx.mk_param_from_def(param)) + } + + /// Creates an `GenericArgs` for generic parameter definitions, + /// by calling closures to obtain each kind. + /// The closures get to observe the `GenericArgs` as they're + /// being built, which can be used to correctly + /// replace defaults of generic parameters. + pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId, mut mk_kind: F) -> GenericArgsRef<'tcx> + where + F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, + { + let defs = tcx.generics_of(def_id); + let count = defs.count(); + let mut args = SmallVec::with_capacity(count); + Self::fill_item(&mut args, tcx, defs, &mut mk_kind); + tcx.mk_args(&args) + } + + pub fn extend_to( + &self, + tcx: TyCtxt<'tcx>, + def_id: DefId, + mut mk_kind: F, + ) -> GenericArgsRef<'tcx> + where + F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, + { + Self::for_item(tcx, def_id, |param, args| { + self.get(param.index as usize).cloned().unwrap_or_else(|| mk_kind(param, args)) + }) + } + + pub fn fill_item( + args: &mut SmallVec<[GenericArg<'tcx>; 8]>, + tcx: TyCtxt<'tcx>, + defs: &ty::Generics, + mk_kind: &mut F, + ) where + F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, + { + if let Some(def_id) = defs.parent { + let parent_defs = tcx.generics_of(def_id); + Self::fill_item(args, tcx, parent_defs, mk_kind); + } + Self::fill_single(args, defs, mk_kind) + } + + pub fn fill_single( + args: &mut SmallVec<[GenericArg<'tcx>; 8]>, + defs: &ty::Generics, + mk_kind: &mut F, + ) where + F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, + { + args.reserve(defs.params.len()); + for param in &defs.params { + let kind = mk_kind(param, args); + assert_eq!(param.index as usize, args.len(), "{args:#?}, {defs:#?}"); + args.push(kind); + } + } + + // Extend an `original_args` list to the full number of args expected by `def_id`, + // filling in the missing parameters with error ty/ct or 'static regions. + pub fn extend_with_error( + tcx: TyCtxt<'tcx>, + def_id: DefId, + original_args: &[GenericArg<'tcx>], + ) -> GenericArgsRef<'tcx> { + ty::GenericArgs::for_item(tcx, def_id, |def, args| { + if let Some(arg) = original_args.get(def.index as usize) { + *arg + } else { + def.to_error(tcx, args) + } + }) + } + + #[inline] + pub fn types(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { + self.iter().filter_map(|k| k.as_type()) + } + + #[inline] + pub fn regions(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { + self.iter().filter_map(|k| k.as_region()) + } + + #[inline] + pub fn consts(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { + self.iter().filter_map(|k| k.as_const()) + } + + #[inline] + pub fn non_erasable_generics( + &'tcx self, + ) -> impl DoubleEndedIterator> + 'tcx { + self.iter().filter_map(|k| match k.unpack() { + GenericArgKind::Lifetime(_) => None, + generic => Some(generic), + }) + } + + #[inline] + #[track_caller] + pub fn type_at(&self, i: usize) -> Ty<'tcx> { + self[i].as_type().unwrap_or_else(|| bug!("expected type for param #{} in {:?}", i, self)) + } + + #[inline] + #[track_caller] + pub fn region_at(&self, i: usize) -> ty::Region<'tcx> { + self[i] + .as_region() + .unwrap_or_else(|| bug!("expected region for param #{} in {:?}", i, self)) + } + + #[inline] + #[track_caller] + pub fn const_at(&self, i: usize) -> ty::Const<'tcx> { + self[i].as_const().unwrap_or_else(|| bug!("expected const for param #{} in {:?}", i, self)) + } + + #[inline] + #[track_caller] + pub fn type_for_def(&self, def: &ty::GenericParamDef) -> GenericArg<'tcx> { + self.type_at(def.index as usize).into() + } + + /// Transform from generic args for a child of `source_ancestor` + /// (e.g., a trait or impl) to args for the same child + /// in a different item, with `target_args` as the base for + /// the target impl/trait, with the source child-specific + /// parameters (e.g., method parameters) on top of that base. + /// + /// For example given: + /// + /// ```no_run + /// trait X { fn f(); } + /// impl X for U { fn f() {} } + /// ``` + /// + /// * If `self` is `[Self, S, T]`: the identity args of `f` in the trait. + /// * If `source_ancestor` is the def_id of the trait. + /// * If `target_args` is `[U]`, the args for the impl. + /// * Then we will return `[U, T]`, the arg for `f` in the impl that + /// are needed for it to match the trait. + pub fn rebase_onto( + &self, + tcx: TyCtxt<'tcx>, + source_ancestor: DefId, + target_args: GenericArgsRef<'tcx>, + ) -> GenericArgsRef<'tcx> { + let defs = tcx.generics_of(source_ancestor); + tcx.mk_args_from_iter(target_args.iter().chain(self.iter().skip(defs.params.len()))) + } + + pub fn truncate_to(&self, tcx: TyCtxt<'tcx>, generics: &ty::Generics) -> GenericArgsRef<'tcx> { + tcx.mk_args_from_iter(self.iter().take(generics.count())) + } + + pub fn host_effect_param(&'tcx self) -> Option> { + self.consts().rfind(|x| matches!(x.kind(), ty::ConstKind::Param(p) if p.name == sym::host)) + } +} + +impl<'tcx> TypeFoldable> for GenericArgsRef<'tcx> { + fn try_fold_with>>( + self, + folder: &mut F, + ) -> Result { + // This code is hot enough that it's worth specializing for the most + // common length lists, to avoid the overhead of `SmallVec` creation. + // The match arms are in order of frequency. The 1, 2, and 0 cases are + // typically hit in 90--99.99% of cases. When folding doesn't change + // the args, it's faster to reuse the existing args rather than + // calling `mk_args`. + match self.len() { + 1 => { + let param0 = self[0].try_fold_with(folder)?; + if param0 == self[0] { Ok(self) } else { Ok(folder.interner().mk_args(&[param0])) } + } + 2 => { + let param0 = self[0].try_fold_with(folder)?; + let param1 = self[1].try_fold_with(folder)?; + if param0 == self[0] && param1 == self[1] { + Ok(self) + } else { + Ok(folder.interner().mk_args(&[param0, param1])) + } + } + 0 => Ok(self), + _ => ty::util::fold_list(self, folder, |tcx, v| tcx.mk_args(v)), + } + } +} + +impl<'tcx> TypeFoldable> for &'tcx ty::List> { + fn try_fold_with>>( + self, + folder: &mut F, + ) -> Result { + // This code is fairly hot, though not as hot as `GenericArgsRef`. + // + // When compiling stage 2, I get the following results: + // + // len | total | % + // --- | --------- | ----- + // 2 | 15083590 | 48.1 + // 3 | 7540067 | 24.0 + // 1 | 5300377 | 16.9 + // 4 | 1351897 | 4.3 + // 0 | 1256849 | 4.0 + // + // I've tried it with some private repositories and got + // close to the same result, with 4 and 0 swapping places + // sometimes. + match self.len() { + 2 => { + let param0 = self[0].try_fold_with(folder)?; + let param1 = self[1].try_fold_with(folder)?; + if param0 == self[0] && param1 == self[1] { + Ok(self) + } else { + Ok(folder.interner().mk_type_list(&[param0, param1])) + } + } + _ => ty::util::fold_list(self, folder, |tcx, v| tcx.mk_type_list(v)), + } + } +} + +impl<'tcx, T: TypeVisitable>> TypeVisitable> for &'tcx ty::List { + #[inline] + fn visit_with>>(&self, visitor: &mut V) -> ControlFlow { + self.iter().try_for_each(|t| t.visit_with(visitor)) + } +} + +/// Similar to [`super::Binder`] except that it tracks early bound generics, i.e. `struct Foo(T)` +/// needs `T` instantiated immediately. This type primarily exists to avoid forgetting to call +/// `instantiate`. +/// +/// If you don't have anything to `instantiate`, you may be looking for +/// [`instantiate_identity`](EarlyBinder::instantiate_identity) or [`skip_binder`](EarlyBinder::skip_binder). +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Encodable, Decodable, HashStable)] +pub struct EarlyBinder { + value: T, +} + +/// For early binders, you should first call `instantiate` before using any visitors. +impl<'tcx, T> !TypeFoldable> for ty::EarlyBinder {} +impl<'tcx, T> !TypeVisitable> for ty::EarlyBinder {} + +impl EarlyBinder { + pub fn bind(value: T) -> EarlyBinder { + EarlyBinder { value } + } + + pub fn as_ref(&self) -> EarlyBinder<&T> { + EarlyBinder { value: &self.value } + } + + pub fn map_bound_ref(&self, f: F) -> EarlyBinder + where + F: FnOnce(&T) -> U, + { + self.as_ref().map_bound(f) + } + + pub fn map_bound(self, f: F) -> EarlyBinder + where + F: FnOnce(T) -> U, + { + let value = f(self.value); + EarlyBinder { value } + } + + pub fn try_map_bound(self, f: F) -> Result, E> + where + F: FnOnce(T) -> Result, + { + let value = f(self.value)?; + Ok(EarlyBinder { value }) + } + + pub fn rebind(&self, value: U) -> EarlyBinder { + EarlyBinder { value } + } + + /// Skips the binder and returns the "bound" value. + /// This can be used to extract data that does not depend on generic parameters + /// (e.g., getting the `DefId` of the inner value or getting the number of + /// arguments of an `FnSig`). Otherwise, consider using + /// [`instantiate_identity`](EarlyBinder::instantiate_identity). + /// + /// To skip the binder on `x: &EarlyBinder` to obtain `&T`, leverage + /// [`EarlyBinder::as_ref`](EarlyBinder::as_ref): `x.as_ref().skip_binder()`. + /// + /// See also [`Binder::skip_binder`](super::Binder::skip_binder), which is + /// the analogous operation on [`super::Binder`]. + pub fn skip_binder(self) -> T { + self.value + } +} + +impl EarlyBinder> { + pub fn transpose(self) -> Option> { + self.value.map(|value| EarlyBinder { value }) + } +} + +impl EarlyBinder<(T, U)> { + pub fn transpose_tuple2(self) -> (EarlyBinder, EarlyBinder) { + let EarlyBinder { value: (lhs, rhs) } = self; + (EarlyBinder { value: lhs }, EarlyBinder { value: rhs }) + } +} + +impl<'tcx, 's, I: IntoIterator> EarlyBinder +where + I::Item: TypeFoldable>, +{ + pub fn iter_instantiated( + self, + tcx: TyCtxt<'tcx>, + args: &'s [GenericArg<'tcx>], + ) -> IterInstantiated<'s, 'tcx, I> { + IterInstantiated { it: self.value.into_iter(), tcx, args } + } + + /// Similar to [`instantiate_identity`](EarlyBinder::instantiate_identity), + /// but on an iterator of `TypeFoldable` values. + pub fn instantiate_identity_iter(self) -> I::IntoIter { + self.value.into_iter() + } +} + +pub struct IterInstantiated<'s, 'tcx, I: IntoIterator> { + it: I::IntoIter, + tcx: TyCtxt<'tcx>, + args: &'s [GenericArg<'tcx>], +} + +impl<'tcx, I: IntoIterator> Iterator for IterInstantiated<'_, 'tcx, I> +where + I::Item: TypeFoldable>, +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + Some(EarlyBinder { value: self.it.next()? }.instantiate(self.tcx, self.args)) + } + + fn size_hint(&self) -> (usize, Option) { + self.it.size_hint() + } +} + +impl<'tcx, I: IntoIterator> DoubleEndedIterator for IterInstantiated<'_, 'tcx, I> +where + I::IntoIter: DoubleEndedIterator, + I::Item: TypeFoldable>, +{ + fn next_back(&mut self) -> Option { + Some(EarlyBinder { value: self.it.next_back()? }.instantiate(self.tcx, self.args)) + } +} + +impl<'tcx, I: IntoIterator> ExactSizeIterator for IterInstantiated<'_, 'tcx, I> +where + I::IntoIter: ExactSizeIterator, + I::Item: TypeFoldable>, +{ +} + +impl<'tcx, 's, I: IntoIterator> EarlyBinder +where + I::Item: Deref, + ::Target: Copy + TypeFoldable>, +{ + pub fn iter_instantiated_copied( + self, + tcx: TyCtxt<'tcx>, + args: &'s [GenericArg<'tcx>], + ) -> IterInstantiatedCopied<'s, 'tcx, I> { + IterInstantiatedCopied { it: self.value.into_iter(), tcx, args } + } + + /// Similar to [`instantiate_identity`](EarlyBinder::instantiate_identity), + /// but on an iterator of values that deref to a `TypeFoldable`. + pub fn instantiate_identity_iter_copied( + self, + ) -> impl Iterator::Target> { + self.value.into_iter().map(|v| *v) + } +} + +pub struct IterInstantiatedCopied<'a, 'tcx, I: IntoIterator> { + it: I::IntoIter, + tcx: TyCtxt<'tcx>, + args: &'a [GenericArg<'tcx>], +} + +impl<'tcx, I: IntoIterator> Iterator for IterInstantiatedCopied<'_, 'tcx, I> +where + I::Item: Deref, + ::Target: Copy + TypeFoldable>, +{ + type Item = ::Target; + + fn next(&mut self) -> Option { + self.it.next().map(|value| EarlyBinder { value: *value }.instantiate(self.tcx, self.args)) + } + + fn size_hint(&self) -> (usize, Option) { + self.it.size_hint() + } +} + +impl<'tcx, I: IntoIterator> DoubleEndedIterator for IterInstantiatedCopied<'_, 'tcx, I> +where + I::IntoIter: DoubleEndedIterator, + I::Item: Deref, + ::Target: Copy + TypeFoldable>, +{ + fn next_back(&mut self) -> Option { + self.it + .next_back() + .map(|value| EarlyBinder { value: *value }.instantiate(self.tcx, self.args)) + } +} + +impl<'tcx, I: IntoIterator> ExactSizeIterator for IterInstantiatedCopied<'_, 'tcx, I> +where + I::IntoIter: ExactSizeIterator, + I::Item: Deref, + ::Target: Copy + TypeFoldable>, +{ +} + +pub struct EarlyBinderIter { + t: T, +} + +impl EarlyBinder { + pub fn transpose_iter(self) -> EarlyBinderIter { + EarlyBinderIter { t: self.value.into_iter() } + } +} + +impl Iterator for EarlyBinderIter { + type Item = EarlyBinder; + + fn next(&mut self) -> Option { + self.t.next().map(|value| EarlyBinder { value }) + } + + fn size_hint(&self) -> (usize, Option) { + self.t.size_hint() + } +} + +impl<'tcx, T: TypeFoldable>> ty::EarlyBinder { + pub fn instantiate(self, tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> T { + let mut folder = ArgFolder { tcx, args, binders_passed: 0 }; + self.value.fold_with(&mut folder) + } + + /// Makes the identity replacement `T0 => T0, ..., TN => TN`. + /// Conceptually, this converts universally bound variables into placeholders + /// when inside of a given item. + /// + /// For example, consider `for fn foo(){ .. }`: + /// - Outside of `foo`, `T` is bound (represented by the presence of `EarlyBinder`). + /// - Inside of the body of `foo`, we treat `T` as a placeholder by calling + /// `instantiate_identity` to discharge the `EarlyBinder`. + pub fn instantiate_identity(self) -> T { + self.value + } + + /// Returns the inner value, but only if it contains no bound vars. + pub fn no_bound_vars(self) -> Option { + if !self.value.has_param() { Some(self.value) } else { None } + } +} + +/////////////////////////////////////////////////////////////////////////// +// The actual substitution engine itself is a type folder. + +struct ArgFolder<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + args: &'a [GenericArg<'tcx>], + + /// Number of region binders we have passed through while doing the substitution + binders_passed: u32, +} + +impl<'a, 'tcx> TypeFolder> for ArgFolder<'a, 'tcx> { + #[inline] + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn fold_binder>>( + &mut self, + t: ty::Binder<'tcx, T>, + ) -> ty::Binder<'tcx, T> { + self.binders_passed += 1; + let t = t.super_fold_with(self); + self.binders_passed -= 1; + t + } + + fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { + #[cold] + #[inline(never)] + fn region_param_out_of_range(data: ty::EarlyBoundRegion, args: &[GenericArg<'_>]) -> ! { + bug!( + "Region parameter out of range when substituting in region {} (index={}, args = {:?})", + data.name, + data.index, + args, + ) + } + + #[cold] + #[inline(never)] + fn region_param_invalid(data: ty::EarlyBoundRegion, other: GenericArgKind<'_>) -> ! { + bug!( + "Unexpected parameter {:?} when substituting in region {} (index={})", + other, + data.name, + data.index + ) + } + + // Note: This routine only handles regions that are bound on + // type declarations and other outer declarations, not those + // bound in *fn types*. Region substitution of the bound + // regions that appear in a function signature is done using + // the specialized routine `ty::replace_late_regions()`. + match *r { + ty::ReEarlyBound(data) => { + let rk = self.args.get(data.index as usize).map(|k| k.unpack()); + match rk { + Some(GenericArgKind::Lifetime(lt)) => self.shift_region_through_binders(lt), + Some(other) => region_param_invalid(data, other), + None => region_param_out_of_range(data, self.args), + } + } + ty::ReLateBound(..) + | ty::ReFree(_) + | ty::ReStatic + | ty::RePlaceholder(_) + | ty::ReErased + | ty::ReError(_) => r, + ty::ReVar(_) => bug!("unexpected region: {r:?}"), + } + } + + fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { + if !t.has_param() { + return t; + } + + match *t.kind() { + ty::Param(p) => self.ty_for_param(p, t), + _ => t.super_fold_with(self), + } + } + + fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { + if let ty::ConstKind::Param(p) = c.kind() { + self.const_for_param(p, c) + } else { + c.super_fold_with(self) + } + } +} + +impl<'a, 'tcx> ArgFolder<'a, 'tcx> { + fn ty_for_param(&self, p: ty::ParamTy, source_ty: Ty<'tcx>) -> Ty<'tcx> { + // Look up the type in the args. It really should be in there. + let opt_ty = self.args.get(p.index as usize).map(|k| k.unpack()); + let ty = match opt_ty { + Some(GenericArgKind::Type(ty)) => ty, + Some(kind) => self.type_param_expected(p, source_ty, kind), + None => self.type_param_out_of_range(p, source_ty), + }; + + self.shift_vars_through_binders(ty) + } + + #[cold] + #[inline(never)] + fn type_param_expected(&self, p: ty::ParamTy, ty: Ty<'tcx>, kind: GenericArgKind<'tcx>) -> ! { + bug!( + "expected type for `{:?}` ({:?}/{}) but found {:?} when substituting, args={:?}", + p, + ty, + p.index, + kind, + self.args, + ) + } + + #[cold] + #[inline(never)] + fn type_param_out_of_range(&self, p: ty::ParamTy, ty: Ty<'tcx>) -> ! { + bug!( + "type parameter `{:?}` ({:?}/{}) out of range when substituting, args={:?}", + p, + ty, + p.index, + self.args, + ) + } + + fn const_for_param(&self, p: ParamConst, source_ct: ty::Const<'tcx>) -> ty::Const<'tcx> { + // Look up the const in the args. It really should be in there. + let opt_ct = self.args.get(p.index as usize).map(|k| k.unpack()); + let ct = match opt_ct { + Some(GenericArgKind::Const(ct)) => ct, + Some(kind) => self.const_param_expected(p, source_ct, kind), + None => self.const_param_out_of_range(p, source_ct), + }; + + self.shift_vars_through_binders(ct) + } + + #[cold] + #[inline(never)] + fn const_param_expected( + &self, + p: ty::ParamConst, + ct: ty::Const<'tcx>, + kind: GenericArgKind<'tcx>, + ) -> ! { + bug!( + "expected const for `{:?}` ({:?}/{}) but found {:?} when substituting args={:?}", + p, + ct, + p.index, + kind, + self.args, + ) + } + + #[cold] + #[inline(never)] + fn const_param_out_of_range(&self, p: ty::ParamConst, ct: ty::Const<'tcx>) -> ! { + bug!( + "const parameter `{:?}` ({:?}/{}) out of range when substituting args={:?}", + p, + ct, + p.index, + self.args, + ) + } + + /// It is sometimes necessary to adjust the De Bruijn indices during substitution. This occurs + /// when we are substituting a type with escaping bound vars into a context where we have + /// passed through binders. That's quite a mouthful. Let's see an example: + /// + /// ``` + /// type Func = fn(A); + /// type MetaFunc = for<'a> fn(Func<&'a i32>); + /// ``` + /// + /// The type `MetaFunc`, when fully expanded, will be + /// ```ignore (illustrative) + /// for<'a> fn(fn(&'a i32)) + /// // ^~ ^~ ^~~ + /// // | | | + /// // | | DebruijnIndex of 2 + /// // Binders + /// ``` + /// Here the `'a` lifetime is bound in the outer function, but appears as an argument of the + /// inner one. Therefore, that appearance will have a DebruijnIndex of 2, because we must skip + /// over the inner binder (remember that we count De Bruijn indices from 1). However, in the + /// definition of `MetaFunc`, the binder is not visible, so the type `&'a i32` will have a + /// De Bruijn index of 1. It's only during the substitution that we can see we must increase the + /// depth by 1 to account for the binder that we passed through. + /// + /// As a second example, consider this twist: + /// + /// ``` + /// type FuncTuple = (A,fn(A)); + /// type MetaFuncTuple = for<'a> fn(FuncTuple<&'a i32>); + /// ``` + /// + /// Here the final type will be: + /// ```ignore (illustrative) + /// for<'a> fn((&'a i32, fn(&'a i32))) + /// // ^~~ ^~~ + /// // | | + /// // DebruijnIndex of 1 | + /// // DebruijnIndex of 2 + /// ``` + /// As indicated in the diagram, here the same type `&'a i32` is substituted once, but in the + /// first case we do not increase the De Bruijn index and in the second case we do. The reason + /// is that only in the second case have we passed through a fn binder. + fn shift_vars_through_binders>>(&self, val: T) -> T { + debug!( + "shift_vars(val={:?}, binders_passed={:?}, has_escaping_bound_vars={:?})", + val, + self.binders_passed, + val.has_escaping_bound_vars() + ); + + if self.binders_passed == 0 || !val.has_escaping_bound_vars() { + return val; + } + + let result = ty::fold::shift_vars(TypeFolder::interner(self), val, self.binders_passed); + debug!("shift_vars: shifted result = {:?}", result); + + result + } + + fn shift_region_through_binders(&self, region: ty::Region<'tcx>) -> ty::Region<'tcx> { + if self.binders_passed == 0 || !region.has_escaping_bound_vars() { + return region; + } + ty::fold::shift_region(self.tcx, region, self.binders_passed) + } +} + +/// Stores the user-given args to reach some fully qualified path +/// (e.g., `::Item` or `::Item`). +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable)] +#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] +pub struct UserArgs<'tcx> { + /// The args for the item as given by the user. + pub args: GenericArgsRef<'tcx>, + + /// The self type, in the case of a `::Item` path (when applied + /// to an inherent impl). See `UserSelfTy` below. + pub user_self_ty: Option>, +} + +/// Specifies the user-given self type. In the case of a path that +/// refers to a member in an inherent impl, this self type is +/// sometimes needed to constrain the type parameters on the impl. For +/// example, in this code: +/// +/// ```ignore (illustrative) +/// struct Foo { } +/// impl Foo { fn method() { } } +/// ``` +/// +/// when you then have a path like `>::method`, +/// this struct would carry the `DefId` of the impl along with the +/// self type `Foo`. Then we can instantiate the parameters of +/// the impl (with the args from `UserArgs`) and apply those to +/// the self type, giving `Foo`. Finally, we unify that with +/// the self type here, which contains `?A` to be `&'static u32` +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable)] +#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] +pub struct UserSelfTy<'tcx> { + pub impl_def_id: DefId, + pub self_ty: Ty<'tcx>, +} diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index b0ffe78299de9..70a35f137d80c 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -1,12 +1,12 @@ use crate::ty; -use crate::ty::{EarlyBinder, SubstsRef}; +use crate::ty::{EarlyBinder, GenericArgsRef}; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_span::symbol::{kw, Symbol}; use rustc_span::Span; -use super::{EarlyBoundRegion, InstantiatedPredicates, ParamConst, ParamTy, Predicate, TyCtxt}; +use super::{Clause, EarlyBoundRegion, InstantiatedPredicates, ParamConst, ParamTy, Ty, TyCtxt}; #[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable)] pub enum GenericParamDefKind { @@ -97,14 +97,16 @@ impl GenericParamDef { pub fn to_error<'tcx>( &self, tcx: TyCtxt<'tcx>, - preceding_substs: &[ty::GenericArg<'tcx>], + preceding_args: &[ty::GenericArg<'tcx>], ) -> ty::GenericArg<'tcx> { match &self.kind { - ty::GenericParamDefKind::Lifetime => tcx.mk_re_error_misc().into(), - ty::GenericParamDefKind::Type { .. } => tcx.ty_error_misc().into(), - ty::GenericParamDefKind::Const { .. } => { - tcx.const_error_misc(tcx.type_of(self.def_id).subst(tcx, preceding_substs)).into() - } + ty::GenericParamDefKind::Lifetime => ty::Region::new_error_misc(tcx).into(), + ty::GenericParamDefKind::Type { .. } => Ty::new_misc_error(tcx).into(), + ty::GenericParamDefKind::Const { .. } => ty::Const::new_misc_error( + tcx, + tcx.type_of(self.def_id).instantiate(tcx, preceding_args), + ) + .into(), } } } @@ -133,6 +135,9 @@ pub struct Generics { pub has_self: bool, pub has_late_bound_regions: Option, + + // The index of the host effect when substituted. (i.e. might be index to parent args) + pub host_effect_index: Option, } impl<'tcx> Generics { @@ -273,14 +278,14 @@ impl<'tcx> Generics { }) } - /// Returns the substs corresponding to the generic parameters + /// Returns the args corresponding to the generic parameters /// of this item, excluding `Self`. /// /// **This should only be used for diagnostics purposes.** - pub fn own_substs_no_defaults( + pub fn own_args_no_defaults( &'tcx self, tcx: TyCtxt<'tcx>, - substs: &'tcx [ty::GenericArg<'tcx>], + args: &'tcx [ty::GenericArg<'tcx>], ) -> &'tcx [ty::GenericArg<'tcx>] { let mut own_params = self.parent_count..self.count(); if self.has_self && self.parent.is_none() { @@ -299,22 +304,22 @@ impl<'tcx> Generics { .rev() .take_while(|param| { param.default_value(tcx).is_some_and(|default| { - default.subst(tcx, substs) == substs[param.index as usize] + default.instantiate(tcx, args) == args[param.index as usize] }) }) .count(); - &substs[own_params] + &args[own_params] } - /// Returns the substs corresponding to the generic parameters of this item, excluding `Self`. + /// Returns the args corresponding to the generic parameters of this item, excluding `Self`. /// /// **This should only be used for diagnostics purposes.** - pub fn own_substs( + pub fn own_args( &'tcx self, - substs: &'tcx [ty::GenericArg<'tcx>], + args: &'tcx [ty::GenericArg<'tcx>], ) -> &'tcx [ty::GenericArg<'tcx>] { - let own = &substs[self.parent_count..][..self.params.len()]; + let own = &args[self.parent_count..][..self.params.len()]; if self.has_self && self.parent.is_none() { &own[1..] } else { &own } } } @@ -323,27 +328,26 @@ impl<'tcx> Generics { #[derive(Copy, Clone, Default, Debug, TyEncodable, TyDecodable, HashStable)] pub struct GenericPredicates<'tcx> { pub parent: Option, - pub predicates: &'tcx [(Predicate<'tcx>, Span)], + pub predicates: &'tcx [(Clause<'tcx>, Span)], } impl<'tcx> GenericPredicates<'tcx> { pub fn instantiate( &self, tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> InstantiatedPredicates<'tcx> { let mut instantiated = InstantiatedPredicates::empty(); - self.instantiate_into(tcx, &mut instantiated, substs); + self.instantiate_into(tcx, &mut instantiated, args); instantiated } pub fn instantiate_own( &self, tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, - ) -> impl Iterator, Span)> + DoubleEndedIterator + ExactSizeIterator - { - EarlyBinder(self.predicates).subst_iter_copied(tcx, substs) + args: GenericArgsRef<'tcx>, + ) -> impl Iterator, Span)> + DoubleEndedIterator + ExactSizeIterator { + EarlyBinder::bind(self.predicates).iter_instantiated_copied(tcx, args) } #[instrument(level = "debug", skip(self, tcx))] @@ -351,14 +355,14 @@ impl<'tcx> GenericPredicates<'tcx> { &self, tcx: TyCtxt<'tcx>, instantiated: &mut InstantiatedPredicates<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) { if let Some(def_id) = self.parent { - tcx.predicates_of(def_id).instantiate_into(tcx, instantiated, substs); + tcx.predicates_of(def_id).instantiate_into(tcx, instantiated, args); } - instantiated - .predicates - .extend(self.predicates.iter().map(|(p, _)| EarlyBinder(*p).subst(tcx, substs))); + instantiated.predicates.extend( + self.predicates.iter().map(|(p, _)| EarlyBinder::bind(*p).instantiate(tcx, args)), + ); instantiated.spans.extend(self.predicates.iter().map(|(_, sp)| *sp)); } diff --git a/compiler/rustc_middle/src/ty/impls_ty.rs b/compiler/rustc_middle/src/ty/impls_ty.rs index 02baa395c3c2f..b03874a90e8d5 100644 --- a/compiler/rustc_middle/src/ty/impls_ty.rs +++ b/compiler/rustc_middle/src/ty/impls_ty.rs @@ -67,7 +67,7 @@ impl<'a> ToStableHashKey> for SimplifiedType { } } -impl<'a, 'tcx> HashStable> for ty::subst::GenericArg<'tcx> { +impl<'a, 'tcx> HashStable> for ty::GenericArg<'tcx> { fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { self.unpack().hash_stable(hcx, hasher); } diff --git a/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs b/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs index ac42d6e05100f..f278cace99dc4 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs @@ -19,7 +19,7 @@ pub enum InhabitedPredicate<'tcx> { /// type has restricted visibility. NotInModule(DefId), /// Inhabited if some generic type is inhabited. - /// These are replaced by calling [`Self::subst`]. + /// These are replaced by calling [`Self::instantiate`]. GenericType(Ty<'tcx>), /// A AND B And(&'tcx [InhabitedPredicate<'tcx>; 2]), @@ -62,7 +62,18 @@ impl<'tcx> InhabitedPredicate<'tcx> { Some(1..) => Ok(false), }, Self::NotInModule(id) => in_module(id).map(|in_mod| !in_mod), - Self::GenericType(_) => Ok(true), + // `t` may be a projection, for which `inhabited_predicate` returns a `GenericType`. As + // we have a param_env available, we can do better. + Self::GenericType(t) => { + let normalized_pred = tcx + .try_normalize_erasing_regions(param_env, t) + .map_or(self, |t| t.inhabited_predicate(tcx)); + match normalized_pred { + // We don't have more information than we started with, so consider inhabited. + Self::GenericType(_) => Ok(true), + pred => pred.apply_inner(tcx, param_env, in_module), + } + } Self::And([a, b]) => try_and(a, b, |x| x.apply_inner(tcx, param_env, in_module)), Self::Or([a, b]) => try_or(a, b, |x| x.apply_inner(tcx, param_env, in_module)), } @@ -151,15 +162,15 @@ impl<'tcx> InhabitedPredicate<'tcx> { } /// Replaces generic types with its corresponding predicate - pub fn subst(self, tcx: TyCtxt<'tcx>, substs: ty::SubstsRef<'tcx>) -> Self { - self.subst_opt(tcx, substs).unwrap_or(self) + pub fn instantiate(self, tcx: TyCtxt<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Self { + self.instantiate_opt(tcx, args).unwrap_or(self) } - fn subst_opt(self, tcx: TyCtxt<'tcx>, substs: ty::SubstsRef<'tcx>) -> Option { + fn instantiate_opt(self, tcx: TyCtxt<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Option { match self { Self::ConstIsZero(c) => { - let c = ty::EarlyBinder(c).subst(tcx, substs); - let pred = match c.kind().try_to_target_usize(tcx) { + let c = ty::EarlyBinder::bind(c).instantiate(tcx, args); + let pred = match c.try_to_target_usize(tcx) { Some(0) => Self::True, Some(1..) => Self::False, None => Self::ConstIsZero(c), @@ -167,17 +178,17 @@ impl<'tcx> InhabitedPredicate<'tcx> { Some(pred) } Self::GenericType(t) => { - Some(ty::EarlyBinder(t).subst(tcx, substs).inhabited_predicate(tcx)) + Some(ty::EarlyBinder::bind(t).instantiate(tcx, args).inhabited_predicate(tcx)) } - Self::And(&[a, b]) => match a.subst_opt(tcx, substs) { - None => b.subst_opt(tcx, substs).map(|b| a.and(tcx, b)), + Self::And(&[a, b]) => match a.instantiate_opt(tcx, args) { + None => b.instantiate_opt(tcx, args).map(|b| a.and(tcx, b)), Some(InhabitedPredicate::False) => Some(InhabitedPredicate::False), - Some(a) => Some(a.and(tcx, b.subst_opt(tcx, substs).unwrap_or(b))), + Some(a) => Some(a.and(tcx, b.instantiate_opt(tcx, args).unwrap_or(b))), }, - Self::Or(&[a, b]) => match a.subst_opt(tcx, substs) { - None => b.subst_opt(tcx, substs).map(|b| a.or(tcx, b)), + Self::Or(&[a, b]) => match a.instantiate_opt(tcx, args) { + None => b.instantiate_opt(tcx, args).map(|b| a.or(tcx, b)), Some(InhabitedPredicate::True) => Some(InhabitedPredicate::True), - Some(a) => Some(a.or(tcx, b.subst_opt(tcx, substs).unwrap_or(b))), + Some(a) => Some(a.or(tcx, b.instantiate_opt(tcx, args).unwrap_or(b))), }, _ => None, } diff --git a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs index 4223502848ef8..4dac6891b3062 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs @@ -58,7 +58,7 @@ pub(crate) fn provide(providers: &mut Providers) { } /// Returns an `InhabitedPredicate` that is generic over type parameters and -/// requires calling [`InhabitedPredicate::subst`] +/// requires calling [`InhabitedPredicate::instantiate`] fn inhabited_predicate_adt(tcx: TyCtxt<'_>, def_id: DefId) -> InhabitedPredicate<'_> { if let Some(def_id) = def_id.as_local() { if matches!(tcx.representability(def_id), ty::Representability::Infinite) { @@ -87,7 +87,7 @@ impl<'tcx> VariantDef { InhabitedPredicate::all( tcx, self.fields.iter().map(|field| { - let pred = tcx.type_of(field.did).subst_identity().inhabited_predicate(tcx); + let pred = tcx.type_of(field.did).instantiate_identity().inhabited_predicate(tcx); if adt.is_enum() { return pred; } @@ -114,8 +114,8 @@ impl<'tcx> Ty<'tcx> { Never => InhabitedPredicate::False, Param(_) | Alias(ty::Projection, _) => InhabitedPredicate::GenericType(self), // FIXME(inherent_associated_types): Most likely we can just map to `GenericType` like above. - // However it's unclear if the substs passed to `InhabitedPredicate::subst` are of the correct - // format, i.e. don't contain parent substs. If you hit this case, please verify this beforehand. + // However it's unclear if the args passed to `InhabitedPredicate::instantiate` are of the correct + // format, i.e. don't contain parent args. If you hit this case, please verify this beforehand. Alias(ty::Inherent, _) => { bug!("unimplemented: inhabitedness checking for inherent projections") } @@ -189,7 +189,7 @@ impl<'tcx> Ty<'tcx> { /// N.B. this query should only be called through `Ty::inhabited_predicate` fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedPredicate<'tcx> { match *ty.kind() { - Adt(adt, substs) => tcx.inhabited_predicate_adt(adt.did()).subst(tcx, substs), + Adt(adt, args) => tcx.inhabited_predicate_adt(adt.did()).instantiate(tcx, args), Tuple(tys) => { InhabitedPredicate::all(tcx, tys.iter().map(|ty| ty.inhabited_predicate(tcx))) @@ -197,7 +197,7 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP // If we can evaluate the array length before having a `ParamEnv`, then // we can simplify the predicate. This is an optimization. - Array(ty, len) => match len.kind().try_to_target_usize(tcx) { + Array(ty, len) => match len.try_to_target_usize(tcx) { Some(0) => InhabitedPredicate::True, Some(1..) => ty.inhabited_predicate(tcx), None => ty.inhabited_predicate(tcx).or(tcx, InhabitedPredicate::ConstIsZero(len)), diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs index e641d1ef1be6f..8913bf76d347a 100644 --- a/compiler/rustc_middle/src/ty/instance.rs +++ b/compiler/rustc_middle/src/ty/instance.rs @@ -1,7 +1,7 @@ use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags; use crate::ty::print::{FmtPrinter, Printer}; use crate::ty::{self, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable}; -use crate::ty::{EarlyBinder, InternalSubsts, SubstsRef, TypeVisitableExt}; +use crate::ty::{EarlyBinder, GenericArgs, GenericArgsRef, TypeVisitableExt}; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::Namespace; use rustc_hir::def_id::{CrateNum, DefId}; @@ -16,13 +16,13 @@ use std::fmt; /// A monomorphized `InstanceDef`. /// /// Monomorphization happens on-the-fly and no monomorphized MIR is ever created. Instead, this type -/// simply couples a potentially generic `InstanceDef` with some substs, and codegen and const eval +/// simply couples a potentially generic `InstanceDef` with some args, and codegen and const eval /// will do all required substitution as they run. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, TyEncodable, TyDecodable)] #[derive(HashStable, Lift, TypeFoldable, TypeVisitable)] pub struct Instance<'tcx> { pub def: InstanceDef<'tcx>, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -115,7 +115,7 @@ impl<'tcx> Instance<'tcx> { /// lifetimes erased, allowing a `ParamEnv` to be specified for use during normalization. pub fn ty(&self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Ty<'tcx> { let ty = tcx.type_of(self.def.def_id()); - tcx.subst_and_normalize_erasing_regions(self.substs, param_env, ty) + tcx.subst_and_normalize_erasing_regions(self.args, param_env, ty) } /// Finds a crate that contains a monomorphization of this instance that @@ -139,13 +139,13 @@ impl<'tcx> Instance<'tcx> { } // If this a non-generic instance, it cannot be a shared monomorphization. - self.substs.non_erasable_generics().next()?; + self.args.non_erasable_generics().next()?; match self.def { InstanceDef::Item(def) => tcx .upstream_monomorphizations_for(def) - .and_then(|monos| monos.get(&self.substs).cloned()), - InstanceDef::DropGlue(_, Some(_)) => tcx.upstream_drop_glue_for(self.substs), + .and_then(|monos| monos.get(&self.args).cloned()), + InstanceDef::DropGlue(_, Some(_)) => tcx.upstream_drop_glue_for(self.args), _ => None, } } @@ -265,8 +265,8 @@ impl<'tcx> InstanceDef<'tcx> { } /// Returns `true` when the MIR body associated with this instance should be monomorphized - /// by its users (e.g. codegen or miri) by substituting the `substs` from `Instance` (see - /// `Instance::substs_for_mir_body`). + /// by its users (e.g. codegen or miri) by substituting the `args` from `Instance` (see + /// `Instance::args_for_mir_body`). /// /// Otherwise, returns `false` only for some kinds of shims where the construction of the MIR /// body should perform necessary substitutions. @@ -294,10 +294,10 @@ fn fmt_instance( type_length: rustc_session::Limit, ) -> fmt::Result { ty::tls::with(|tcx| { - let substs = tcx.lift(instance.substs).expect("could not lift for printing"); + let args = tcx.lift(instance.args).expect("could not lift for printing"); let s = FmtPrinter::new_with_limit(tcx, Namespace::ValueNS, type_length) - .print_def_path(instance.def_id(), substs)? + .print_def_path(instance.def_id(), args)? .into_buffer(); f.write_str(&s) })?; @@ -308,13 +308,13 @@ fn fmt_instance( InstanceDef::ReifyShim(_) => write!(f, " - shim(reify)"), InstanceDef::ThreadLocalShim(_) => write!(f, " - shim(tls)"), InstanceDef::Intrinsic(_) => write!(f, " - intrinsic"), - InstanceDef::Virtual(_, num) => write!(f, " - virtual#{}", num), - InstanceDef::FnPtrShim(_, ty) => write!(f, " - shim({})", ty), + InstanceDef::Virtual(_, num) => write!(f, " - virtual#{num}"), + InstanceDef::FnPtrShim(_, ty) => write!(f, " - shim({ty})"), InstanceDef::ClosureOnceShim { .. } => write!(f, " - shim"), InstanceDef::DropGlue(_, None) => write!(f, " - shim(None)"), - InstanceDef::DropGlue(_, Some(ty)) => write!(f, " - shim(Some({}))", ty), - InstanceDef::CloneShim(_, ty) => write!(f, " - shim({})", ty), - InstanceDef::FnPtrAddrShim(_, ty) => write!(f, " - shim({})", ty), + InstanceDef::DropGlue(_, Some(ty)) => write!(f, " - shim(Some({ty}))"), + InstanceDef::CloneShim(_, ty) => write!(f, " - shim({ty})"), + InstanceDef::FnPtrAddrShim(_, ty) => write!(f, " - shim({ty})"), } } @@ -333,18 +333,16 @@ impl<'tcx> fmt::Display for Instance<'tcx> { } impl<'tcx> Instance<'tcx> { - pub fn new(def_id: DefId, substs: SubstsRef<'tcx>) -> Instance<'tcx> { + pub fn new(def_id: DefId, args: GenericArgsRef<'tcx>) -> Instance<'tcx> { assert!( - !substs.has_escaping_bound_vars(), - "substs of instance {:?} not normalized for codegen: {:?}", - def_id, - substs + !args.has_escaping_bound_vars(), + "args of instance {def_id:?} not normalized for codegen: {args:?}" ); - Instance { def: InstanceDef::Item(def_id), substs } + Instance { def: InstanceDef::Item(def_id), args } } pub fn mono(tcx: TyCtxt<'tcx>, def_id: DefId) -> Instance<'tcx> { - let substs = InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { + let args = GenericArgs::for_item(tcx, def_id, |param, _| match param.kind { ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), ty::GenericParamDefKind::Type { .. } => { bug!("Instance::mono: {:?} has type parameters", def_id) @@ -354,7 +352,7 @@ impl<'tcx> Instance<'tcx> { } }); - Instance::new(def_id, substs) + Instance::new(def_id, args) } #[inline] @@ -362,7 +360,7 @@ impl<'tcx> Instance<'tcx> { self.def.def_id() } - /// Resolves a `(def_id, substs)` pair to an (optional) instance -- most commonly, + /// Resolves a `(def_id, args)` pair to an (optional) instance -- most commonly, /// this is used to find the precise code that will run for a trait method invocation, /// if known. /// @@ -390,29 +388,29 @@ impl<'tcx> Instance<'tcx> { tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Result>, ErrorGuaranteed> { // All regions in the result of this query are erased, so it's // fine to erase all of the input regions. - // HACK(eddyb) erase regions in `substs` first, so that `param_env.and(...)` + // HACK(eddyb) erase regions in `args` first, so that `param_env.and(...)` // below is more likely to ignore the bounds in scope (e.g. if the only - // generic parameters mentioned by `substs` were lifetime ones). - let substs = tcx.erase_regions(substs); - tcx.resolve_instance(tcx.erase_regions(param_env.and((def_id, substs)))) + // generic parameters mentioned by `args` were lifetime ones). + let args = tcx.erase_regions(args); + tcx.resolve_instance(tcx.erase_regions(param_env.and((def_id, args)))) } pub fn expect_resolve( tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Instance<'tcx> { - match ty::Instance::resolve(tcx, param_env, def_id, substs) { + match ty::Instance::resolve(tcx, param_env, def_id, args) { Ok(Some(instance)) => instance, - _ => bug!( - "failed to resolve instance for {}", - tcx.def_path_str_with_substs(def_id, substs) + instance => bug!( + "failed to resolve instance for {}: {instance:#?}", + tcx.def_path_str_with_args(def_id, args) ), } } @@ -421,12 +419,12 @@ impl<'tcx> Instance<'tcx> { tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Option> { - debug!("resolve(def_id={:?}, substs={:?})", def_id, substs); + debug!("resolve(def_id={:?}, args={:?})", def_id, args); // Use either `resolve_closure` or `resolve_for_vtable` - assert!(!tcx.is_closure(def_id), "Called `resolve_for_fn_ptr` on closure: {:?}", def_id); - Instance::resolve(tcx, param_env, def_id, substs).ok().flatten().map(|mut resolved| { + assert!(!tcx.is_closure(def_id), "Called `resolve_for_fn_ptr` on closure: {def_id:?}"); + Instance::resolve(tcx, param_env, def_id, args).ok().flatten().map(|mut resolved| { match resolved.def { InstanceDef::Item(def) if resolved.def.requires_caller_location(tcx) => { debug!(" => fn pointer created for function with #[track_caller]"); @@ -447,18 +445,18 @@ impl<'tcx> Instance<'tcx> { tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Option> { - debug!("resolve_for_vtable(def_id={:?}, substs={:?})", def_id, substs); - let fn_sig = tcx.fn_sig(def_id).subst_identity(); + debug!("resolve_for_vtable(def_id={:?}, args={:?})", def_id, args); + let fn_sig = tcx.fn_sig(def_id).instantiate_identity(); let is_vtable_shim = !fn_sig.inputs().skip_binder().is_empty() && fn_sig.input(0).skip_binder().is_param(0) && tcx.generics_of(def_id).has_self; if is_vtable_shim { debug!(" => associated item with unsizeable self: Self"); - Some(Instance { def: InstanceDef::VTableShim(def_id), substs }) + Some(Instance { def: InstanceDef::VTableShim(def_id), args }) } else { - Instance::resolve(tcx, param_env, def_id, substs).ok().flatten().map(|mut resolved| { + Instance::resolve(tcx, param_env, def_id, args).ok().flatten().map(|mut resolved| { match resolved.def { InstanceDef::Item(def) => { // We need to generate a shim when we cannot guarantee that @@ -489,12 +487,12 @@ impl<'tcx> Instance<'tcx> { { if tcx.is_closure(def) { debug!(" => vtable fn pointer created for closure with #[track_caller]: {:?} for method {:?} {:?}", - def, def_id, substs); + def, def_id, args); // Create a shim for the `FnOnce/FnMut/Fn` method we are calling // - unlike functions, invoking a closure always goes through a // trait. - resolved = Instance { def: InstanceDef::ReifyShim(def_id), substs }; + resolved = Instance { def: InstanceDef::ReifyShim(def_id), args }; } else { debug!( " => vtable fn pointer created for function with #[track_caller]: {:?}", def @@ -518,28 +516,28 @@ impl<'tcx> Instance<'tcx> { pub fn resolve_closure( tcx: TyCtxt<'tcx>, def_id: DefId, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, requested_kind: ty::ClosureKind, ) -> Option> { - let actual_kind = substs.as_closure().kind(); + let actual_kind = args.as_closure().kind(); match needs_fn_once_adapter_shim(actual_kind, requested_kind) { - Ok(true) => Instance::fn_once_adapter_instance(tcx, def_id, substs), - _ => Some(Instance::new(def_id, substs)), + Ok(true) => Instance::fn_once_adapter_instance(tcx, def_id, args), + _ => Some(Instance::new(def_id, args)), } } pub fn resolve_drop_in_place(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ty::Instance<'tcx> { let def_id = tcx.require_lang_item(LangItem::DropInPlace, None); - let substs = tcx.mk_substs(&[ty.into()]); - Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, substs) + let args = tcx.mk_args(&[ty.into()]); + Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args) } #[instrument(level = "debug", skip(tcx), ret)] pub fn fn_once_adapter_instance( tcx: TyCtxt<'tcx>, closure_did: DefId, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, ) -> Option> { let fn_once = tcx.require_lang_item(LangItem::FnOnce, None); let call_once = tcx @@ -552,30 +550,30 @@ impl<'tcx> Instance<'tcx> { tcx.codegen_fn_attrs(closure_did).flags.contains(CodegenFnAttrFlags::TRACK_CALLER); let def = ty::InstanceDef::ClosureOnceShim { call_once, track_caller }; - let self_ty = tcx.mk_closure(closure_did, substs); + let self_ty = Ty::new_closure(tcx, closure_did, args); - let sig = substs.as_closure().sig(); + let sig = args.as_closure().sig(); let sig = tcx.try_normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), sig).ok()?; assert_eq!(sig.inputs().len(), 1); - let substs = tcx.mk_substs_trait(self_ty, [sig.inputs()[0].into()]); + let args = tcx.mk_args_trait(self_ty, [sig.inputs()[0].into()]); debug!(?self_ty, ?sig); - Some(Instance { def, substs }) + Some(Instance { def, args }) } /// Depending on the kind of `InstanceDef`, the MIR body associated with an /// instance is expressed in terms of the generic parameters of `self.def_id()`, and in other /// cases the MIR body is expressed in terms of the types found in the substitution array. /// In the former case, we want to substitute those generic types and replace them with the - /// values from the substs when monomorphizing the function body. But in the latter case, we + /// values from the args when monomorphizing the function body. But in the latter case, we /// don't want to do that substitution, since it has already been done effectively. /// - /// This function returns `Some(substs)` in the former case and `None` otherwise -- i.e., if + /// This function returns `Some(args)` in the former case and `None` otherwise -- i.e., if /// this function returns `None`, then the MIR body does not require substitution during /// codegen. - fn substs_for_mir_body(&self) -> Option> { - self.def.has_polymorphic_mir_body().then_some(self.substs) + fn args_for_mir_body(&self) -> Option> { + self.def.has_polymorphic_mir_body().then_some(self.args) } pub fn subst_mir(&self, tcx: TyCtxt<'tcx>, v: EarlyBinder<&T>) -> T @@ -583,10 +581,10 @@ impl<'tcx> Instance<'tcx> { T: TypeFoldable> + Copy, { let v = v.map_bound(|v| *v); - if let Some(substs) = self.substs_for_mir_body() { - v.subst(tcx, substs) + if let Some(args) = self.args_for_mir_body() { + v.instantiate(tcx, args) } else { - v.skip_binder() + v.instantiate_identity() } } @@ -600,8 +598,8 @@ impl<'tcx> Instance<'tcx> { where T: TypeFoldable> + Clone, { - if let Some(substs) = self.substs_for_mir_body() { - tcx.subst_and_normalize_erasing_regions(substs, param_env, v) + if let Some(args) = self.args_for_mir_body() { + tcx.subst_and_normalize_erasing_regions(args, param_env, v) } else { tcx.normalize_erasing_regions(param_env, v.skip_binder()) } @@ -617,14 +615,14 @@ impl<'tcx> Instance<'tcx> { where T: TypeFoldable> + Clone, { - if let Some(substs) = self.substs_for_mir_body() { - tcx.try_subst_and_normalize_erasing_regions(substs, param_env, v) + if let Some(args) = self.args_for_mir_body() { + tcx.try_subst_and_normalize_erasing_regions(args, param_env, v) } else { tcx.try_normalize_erasing_regions(param_env, v.skip_binder()) } } - /// Returns a new `Instance` where generic parameters in `instance.substs` are replaced by + /// Returns a new `Instance` where generic parameters in `instance.args` are replaced by /// identity parameters if they are determined to be unused in `instance.def`. pub fn polymorphize(self, tcx: TyCtxt<'tcx>) -> Self { debug!("polymorphize: running polymorphization analysis"); @@ -632,18 +630,18 @@ impl<'tcx> Instance<'tcx> { return self; } - let polymorphized_substs = polymorphize(tcx, self.def, self.substs); - debug!("polymorphize: self={:?} polymorphized_substs={:?}", self, polymorphized_substs); - Self { def: self.def, substs: polymorphized_substs } + let polymorphized_args = polymorphize(tcx, self.def, self.args); + debug!("polymorphize: self={:?} polymorphized_args={:?}", self, polymorphized_args); + Self { def: self.def, args: polymorphized_args } } } fn polymorphize<'tcx>( tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>, - substs: SubstsRef<'tcx>, -) -> SubstsRef<'tcx> { - debug!("polymorphize({:?}, {:?})", instance, substs); + args: GenericArgsRef<'tcx>, +) -> GenericArgsRef<'tcx> { + debug!("polymorphize({:?}, {:?})", instance, args); let unused = tcx.unused_generic_params(instance); debug!("polymorphize: unused={:?}", unused); @@ -653,9 +651,9 @@ fn polymorphize<'tcx>( // multiple mono items (and eventually symbol clashes). let def_id = instance.def_id(); let upvars_ty = if tcx.is_closure(def_id) { - Some(substs.as_closure().tupled_upvars_ty()) + Some(args.as_closure().tupled_upvars_ty()) } else if tcx.type_of(def_id).skip_binder().is_generator() { - Some(substs.as_generator().tupled_upvars_ty()) + Some(args.as_generator().tupled_upvars_ty()) } else { None }; @@ -674,22 +672,22 @@ fn polymorphize<'tcx>( fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { debug!("fold_ty: ty={:?}", ty); match *ty.kind() { - ty::Closure(def_id, substs) => { - let polymorphized_substs = - polymorphize(self.tcx, ty::InstanceDef::Item(def_id), substs); - if substs == polymorphized_substs { + ty::Closure(def_id, args) => { + let polymorphized_args = + polymorphize(self.tcx, ty::InstanceDef::Item(def_id), args); + if args == polymorphized_args { ty } else { - self.tcx.mk_closure(def_id, polymorphized_substs) + Ty::new_closure(self.tcx, def_id, polymorphized_args) } } - ty::Generator(def_id, substs, movability) => { - let polymorphized_substs = - polymorphize(self.tcx, ty::InstanceDef::Item(def_id), substs); - if substs == polymorphized_substs { + ty::Generator(def_id, args, movability) => { + let polymorphized_args = + polymorphize(self.tcx, ty::InstanceDef::Item(def_id), args); + if args == polymorphized_args { ty } else { - self.tcx.mk_generator(def_id, polymorphized_substs, movability) + Ty::new_generator(self.tcx, def_id, polymorphized_args, movability) } } _ => ty.super_fold_with(self), @@ -697,7 +695,7 @@ fn polymorphize<'tcx>( } } - InternalSubsts::for_item(tcx, def_id, |param, _| { + GenericArgs::for_item(tcx, def_id, |param, _| { let is_unused = unused.is_unused(param.index); debug!("polymorphize: param={:?} is_unused={:?}", param, is_unused); match param.kind { @@ -706,7 +704,7 @@ fn polymorphize<'tcx>( // ..and has upvars.. has_upvars && // ..and this param has the same type as the tupled upvars.. - upvars_ty == Some(substs[param.index as usize].expect_ty()) => { + upvars_ty == Some(args[param.index as usize].expect_ty()) => { // ..then double-check that polymorphization marked it used.. debug_assert!(!is_unused); // ..and polymorphize any closures/generators captured as upvars. @@ -725,7 +723,7 @@ fn polymorphize<'tcx>( tcx.mk_param_from_def(param), // Otherwise, use the parameter as before. - _ => substs[param.index as usize], + _ => args[param.index as usize], } }) } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index b5a743cfe343e..e362b3477c9f7 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -1,20 +1,21 @@ -use crate::fluent_generated as fluent; +use crate::error::UnsupportedFnAbi; use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags; use crate::query::TyCtxtAt; use crate::ty::normalize_erasing_regions::NormalizationError; -use crate::ty::{self, ReprOptions, Ty, TyCtxt, TypeVisitableExt}; +use crate::ty::{self, ConstKind, ReprOptions, Ty, TyCtxt, TypeVisitableExt}; +use rustc_error_messages::DiagnosticMessage; use rustc_errors::{DiagnosticBuilder, Handler, IntoDiagnostic}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_index::IndexVec; use rustc_session::config::OptLevel; use rustc_span::symbol::{sym, Symbol}; -use rustc_span::{Span, DUMMY_SP}; +use rustc_span::{ErrorGuaranteed, Span, DUMMY_SP}; use rustc_target::abi::call::FnAbi; use rustc_target::abi::*; use rustc_target::spec::{abi::Abi as SpecAbi, HasTargetSpec, PanicStrategy, Target}; -use std::cmp::{self}; +use std::cmp; use std::fmt; use std::num::NonZeroUsize; use std::ops::Bound; @@ -132,7 +133,7 @@ impl PrimitiveExt for Primitive { F32 => tcx.types.f32, F64 => tcx.types.f64, // FIXME(erikdesjardins): handle non-default addrspace ptr sizes - Pointer(_) => tcx.mk_mut_ptr(tcx.mk_unit()), + Pointer(_) => Ty::new_mut_ptr(tcx, Ty::new_unit(tcx)), } } @@ -211,32 +212,35 @@ pub enum LayoutError<'tcx> { Unknown(Ty<'tcx>), SizeOverflow(Ty<'tcx>), NormalizationFailure(Ty<'tcx>, NormalizationError<'tcx>), + ReferencesError(ErrorGuaranteed), Cycle, } -impl IntoDiagnostic<'_, !> for LayoutError<'_> { - fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder<'_, !> { - let mut diag = handler.struct_fatal(""); +impl<'tcx> LayoutError<'tcx> { + pub fn diagnostic_message(&self) -> DiagnosticMessage { + use crate::fluent_generated::*; + use LayoutError::*; + match self { + Unknown(_) => middle_unknown_layout, + SizeOverflow(_) => middle_values_too_big, + NormalizationFailure(_, _) => middle_cannot_be_normalized, + Cycle => middle_cycle, + ReferencesError(_) => middle_layout_references_error, + } + } + pub fn into_diagnostic(self) -> crate::error::LayoutError<'tcx> { + use crate::error::LayoutError as E; + use LayoutError::*; match self { - LayoutError::Unknown(ty) => { - diag.set_arg("ty", ty); - diag.set_primary_message(fluent::middle_unknown_layout); - } - LayoutError::SizeOverflow(ty) => { - diag.set_arg("ty", ty); - diag.set_primary_message(fluent::middle_values_too_big); - } - LayoutError::NormalizationFailure(ty, e) => { - diag.set_arg("ty", ty); - diag.set_arg("failure_ty", e.get_type_for_failure()); - diag.set_primary_message(fluent::middle_cannot_be_normalized); - } - LayoutError::Cycle => { - diag.set_primary_message(fluent::middle_cycle); + Unknown(ty) => E::Unknown { ty }, + SizeOverflow(ty) => E::Overflow { ty }, + NormalizationFailure(ty, e) => { + E::NormalizationFailure { ty, failure_ty: e.get_type_for_failure() } } + Cycle => E::Cycle, + ReferencesError(_) => E::ReferencesError, } - diag } } @@ -245,9 +249,9 @@ impl IntoDiagnostic<'_, !> for LayoutError<'_> { impl<'tcx> fmt::Display for LayoutError<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - LayoutError::Unknown(ty) => write!(f, "the type `{}` has an unknown layout", ty), + LayoutError::Unknown(ty) => write!(f, "the type `{ty}` has an unknown layout"), LayoutError::SizeOverflow(ty) => { - write!(f, "values of the type `{}` are too big for the current architecture", ty) + write!(f, "values of the type `{ty}` are too big for the current architecture") } LayoutError::NormalizationFailure(t, e) => write!( f, @@ -256,6 +260,7 @@ impl<'tcx> fmt::Display for LayoutError<'tcx> { e.get_type_for_failure() ), LayoutError::Cycle => write!(f, "a cycle occurred during layout computation"), + LayoutError::ReferencesError(_) => write!(f, "the type has an unknown layout"), } } } @@ -309,7 +314,7 @@ impl<'tcx> SizeSkeleton<'tcx> { ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, - ) -> Result, LayoutError<'tcx>> { + ) -> Result, &'tcx LayoutError<'tcx>> { debug_assert!(!ty.has_non_region_infer()); // First try computing a static layout. @@ -317,7 +322,14 @@ impl<'tcx> SizeSkeleton<'tcx> { Ok(layout) => { return Ok(SizeSkeleton::Known(layout.size)); } - Err(err) => err, + Err(err @ LayoutError::Unknown(_)) => err, + // We can't extract SizeSkeleton info from other layout errors + Err( + e @ LayoutError::Cycle + | e @ LayoutError::SizeOverflow(_) + | e @ LayoutError::NormalizationFailure(..) + | e @ LayoutError::ReferencesError(_), + ) => return Err(e), }; match *ty.kind() { @@ -330,11 +342,8 @@ impl<'tcx> SizeSkeleton<'tcx> { Ok(SizeSkeleton::Pointer { non_zero, tail: tcx.erase_regions(tail) }) } _ => bug!( - "SizeSkeleton::compute({}): layout errored ({}), yet \ - tail `{}` is not a type parameter or a projection", - ty, - err, - tail + "SizeSkeleton::compute({ty}): layout errored ({err:?}), yet \ + tail `{tail}` is not a type parameter or a projection", ), } } @@ -349,13 +358,13 @@ impl<'tcx> SizeSkeleton<'tcx> { let size = s .bytes() .checked_mul(c) - .ok_or_else(|| LayoutError::SizeOverflow(ty))?; + .ok_or_else(|| &*tcx.arena.alloc(LayoutError::SizeOverflow(ty)))?; return Ok(SizeSkeleton::Known(Size::from_bytes(size))); } let len = tcx.expand_abstract_consts(len); let prev = ty::Const::from_target_usize(tcx, s.bytes()); let Some(gen_size) = mul_sorted_consts(tcx, param_env, len, prev) else { - return Err(LayoutError::SizeOverflow(ty)); + return Err(tcx.arena.alloc(LayoutError::SizeOverflow(ty))); }; Ok(SizeSkeleton::Generic(gen_size)) } @@ -363,14 +372,14 @@ impl<'tcx> SizeSkeleton<'tcx> { SizeSkeleton::Generic(g) => { let len = tcx.expand_abstract_consts(len); let Some(gen_size) = mul_sorted_consts(tcx, param_env, len, g) else { - return Err(LayoutError::SizeOverflow(ty)); + return Err(tcx.arena.alloc(LayoutError::SizeOverflow(ty))); }; Ok(SizeSkeleton::Generic(gen_size)) } } } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { // Only newtypes and enums w/ nullable pointer optimization. if def.is_union() || def.variants().is_empty() || def.variants().len() > 2 { return Err(err); @@ -381,7 +390,7 @@ impl<'tcx> SizeSkeleton<'tcx> { let i = VariantIdx::from_usize(i); let fields = def.variant(i).fields.iter().map(|field| { - SizeSkeleton::compute(field.ty(tcx, substs), tcx, param_env) + SizeSkeleton::compute(field.ty(tcx, args), tcx, param_env) }); let mut ptr = None; for field in fields { @@ -476,13 +485,11 @@ fn mul_sorted_consts<'tcx>( b: ty::Const<'tcx>, ) -> Option> { use crate::mir::BinOp::Mul; - use ty::ConstKind::Expr; - use ty::Expr::Binop; let mut work = vec![a, b]; let mut done = vec![]; while let Some(n) = work.pop() { - if let Expr(Binop(Mul, l, r)) = n.kind() { + if let ConstKind::Expr(ty::Expr::Binop(Mul, l, r)) = n.kind() { work.push(l); work.push(r) } else { @@ -513,7 +520,7 @@ fn mul_sorted_consts<'tcx>( done.sort_unstable(); // create a single tree from the buffer - done.into_iter().reduce(|acc, n| tcx.mk_const(Expr(Binop(Mul, n, acc)), n.ty())) + done.into_iter().reduce(|acc, n| ty::Const::new_expr(tcx, ty::Expr::Binop(Mul, n, acc), n.ty())) } pub trait HasTyCtxt<'tcx>: HasDataLayout { @@ -668,7 +675,7 @@ pub trait LayoutOf<'tcx>: LayoutOfHelpers<'tcx> { MaybeResult::from( tcx.layout_of(self.param_env().and(ty)) - .map_err(|err| self.handle_layout_err(err, span, ty)), + .map_err(|err| self.handle_layout_err(*err, span, ty)), ) } } @@ -676,16 +683,21 @@ pub trait LayoutOf<'tcx>: LayoutOfHelpers<'tcx> { impl<'tcx, C: LayoutOfHelpers<'tcx>> LayoutOf<'tcx> for C {} impl<'tcx> LayoutOfHelpers<'tcx> for LayoutCx<'tcx, TyCtxt<'tcx>> { - type LayoutOfResult = Result, LayoutError<'tcx>>; + type LayoutOfResult = Result, &'tcx LayoutError<'tcx>>; #[inline] - fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> { - err + fn handle_layout_err( + &self, + err: LayoutError<'tcx>, + _: Span, + _: Ty<'tcx>, + ) -> &'tcx LayoutError<'tcx> { + self.tcx.arena.alloc(err) } } impl<'tcx> LayoutOfHelpers<'tcx> for LayoutCx<'tcx, TyCtxtAt<'tcx>> { - type LayoutOfResult = Result, LayoutError<'tcx>>; + type LayoutOfResult = Result, &'tcx LayoutError<'tcx>>; #[inline] fn layout_tcx_at_span(&self) -> Span { @@ -693,8 +705,13 @@ impl<'tcx> LayoutOfHelpers<'tcx> for LayoutCx<'tcx, TyCtxtAt<'tcx>> { } #[inline] - fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> { - err + fn handle_layout_err( + &self, + err: LayoutError<'tcx>, + _: Span, + _: Ty<'tcx>, + ) -> &'tcx LayoutError<'tcx> { + self.tcx.arena.alloc(err) } } @@ -729,9 +746,9 @@ where let fields = match this.ty.kind() { ty::Adt(def, _) if def.variants().is_empty() => - bug!("for_variant called on zero-variant enum"), + bug!("for_variant called on zero-variant enum {}", this.ty), ty::Adt(def, _) => def.variant(variant_index).fields.len(), - _ => bug!(), + _ => bug!("`ty_and_layout_for_variant` on unexpected type {}", this.ty), }; tcx.mk_layout(LayoutS { variants: Variants::Single { index: variant_index }, @@ -743,6 +760,8 @@ where largest_niche: None, align: tcx.data_layout.i8_align, size: Size::ZERO, + max_repr_align: None, + unadjusted_abi_align: tcx.data_layout.i8_align.abi, }) } @@ -798,11 +817,11 @@ where // (which may have no non-DST form), and will work as long // as the `Abi` or `FieldsShape` is checked by users. if i == 0 { - let nil = tcx.mk_unit(); + let nil = Ty::new_unit(tcx); let unit_ptr_ty = if this.ty.is_unsafe_ptr() { - tcx.mk_mut_ptr(nil) + Ty::new_mut_ptr(tcx, nil) } else { - tcx.mk_mut_ref(tcx.lifetimes.re_static, nil) + Ty::new_mut_ref(tcx, tcx.lifetimes.re_static, nil) }; // NOTE(eddyb) using an empty `ParamEnv`, and `unwrap`-ing @@ -815,7 +834,11 @@ where } let mk_dyn_vtable = || { - tcx.mk_imm_ref(tcx.lifetimes.re_static, tcx.mk_array(tcx.types.usize, 3)) + Ty::new_imm_ref( + tcx, + tcx.lifetimes.re_static, + Ty::new_array(tcx, tcx.types.usize, 3), + ) /* FIXME: use actual fn pointers Warning: naively computing the number of entries in the vtable by counting the methods on the trait + methods on @@ -824,9 +847,9 @@ where Increase this counter if you tried to implement this but failed to do it without duplicating a lot of code from other places in the compiler: 2 - tcx.mk_tup(&[ - tcx.mk_array(tcx.types.usize, 3), - tcx.mk_array(Option), + Ty::new_tup(tcx,&[ + Ty::new_array(tcx,tcx.types.usize, 3), + Ty::new_array(tcx,Option), ]) */ }; @@ -838,16 +861,16 @@ where { let metadata = tcx.normalize_erasing_regions( cx.param_env(), - tcx.mk_projection(metadata_def_id, [pointee]), + Ty::new_projection(tcx,metadata_def_id, [pointee]), ); // Map `Metadata = DynMetadata` back to a vtable, since it // offers better information than `std::ptr::metadata::VTable`, // and we rely on this layout information to trigger a panic in // `std::mem::uninitialized::<&dyn Trait>()`, for example. - if let ty::Adt(def, substs) = metadata.kind() + if let ty::Adt(def, args) = metadata.kind() && Some(def.did()) == tcx.lang_items().dyn_metadata() - && substs.type_at(0).is_trait() + && args.type_at(0).is_trait() { mk_dyn_vtable() } else { @@ -869,16 +892,15 @@ where ty::Str => TyMaybeWithLayout::Ty(tcx.types.u8), // Tuples, generators and closures. - ty::Closure(_, ref substs) => field_ty_or_layout( - TyAndLayout { ty: substs.as_closure().tupled_upvars_ty(), ..this }, + ty::Closure(_, ref args) => field_ty_or_layout( + TyAndLayout { ty: args.as_closure().tupled_upvars_ty(), ..this }, cx, i, ), - ty::Generator(def_id, ref substs, _) => match this.variants { + ty::Generator(def_id, ref args, _) => match this.variants { Variants::Single { index } => TyMaybeWithLayout::Ty( - substs - .as_generator() + args.as_generator() .state_tys(def_id, tcx) .nth(index.as_usize()) .unwrap() @@ -889,18 +911,18 @@ where if i == tag_field { return TyMaybeWithLayout::TyAndLayout(tag_layout(tag)); } - TyMaybeWithLayout::Ty(substs.as_generator().prefix_tys().nth(i).unwrap()) + TyMaybeWithLayout::Ty(args.as_generator().prefix_tys()[i]) } }, ty::Tuple(tys) => TyMaybeWithLayout::Ty(tys[i]), // ADTs. - ty::Adt(def, substs) => { + ty::Adt(def, args) => { match this.variants { Variants::Single { index } => { let field = &def.variant(index).fields[FieldIdx::from_usize(i)]; - TyMaybeWithLayout::Ty(field.ty(tcx, substs)) + TyMaybeWithLayout::Ty(field.ty(tcx, args)) } // Discriminant field for enums (where applicable). @@ -913,15 +935,14 @@ where ty::Dynamic(_, _, ty::DynStar) => { if i == 0 { - TyMaybeWithLayout::Ty(tcx.mk_mut_ptr(tcx.types.unit)) + TyMaybeWithLayout::Ty(Ty::new_mut_ptr(tcx, tcx.types.unit)) } else if i == 1 { // FIXME(dyn-star) same FIXME as above applies here too - TyMaybeWithLayout::Ty( - tcx.mk_imm_ref( - tcx.lifetimes.re_static, - tcx.mk_array(tcx.types.usize, 3), - ), - ) + TyMaybeWithLayout::Ty(Ty::new_imm_ref( + tcx, + tcx.lifetimes.re_static, + Ty::new_array(tcx, tcx.types.usize, 3), + )) } else { bug!("no field {i} on dyn*") } @@ -940,12 +961,8 @@ where TyMaybeWithLayout::Ty(field_ty) => { cx.tcx().layout_of(cx.param_env().and(field_ty)).unwrap_or_else(|e| { bug!( - "failed to get layout for `{}`: {},\n\ - despite it being a field (#{}) of an existing layout: {:#?}", - field_ty, - e, - i, - this + "failed to get layout for `{field_ty}`: {e:?},\n\ + despite it being a field (#{i}) of an existing layout: {this:#?}", ) }) } @@ -970,10 +987,8 @@ where }) } ty::FnPtr(fn_sig) if offset.bytes() == 0 => { - tcx.layout_of(param_env.and(tcx.mk_fn_ptr(fn_sig))).ok().map(|layout| PointeeInfo { - size: layout.size, - align: layout.align.abi, - safe: None, + tcx.layout_of(param_env.and(Ty::new_fn_ptr(tcx, fn_sig))).ok().map(|layout| { + PointeeInfo { size: layout.size, align: layout.align.abi, safe: None } }) } ty::Ref(_, ty, mt) if offset.bytes() == 0 => { @@ -1101,12 +1116,11 @@ where /// /// This takes two primary parameters: /// -/// * `codegen_fn_attr_flags` - these are flags calculated as part of the -/// codegen attrs for a defined function. For function pointers this set of -/// flags is the empty set. This is only applicable for Rust-defined -/// functions, and generally isn't needed except for small optimizations where -/// we try to say a function which otherwise might look like it could unwind -/// doesn't actually unwind (such as for intrinsics and such). +/// * `fn_def_id` - the `DefId` of the function. If this is provided then we can +/// determine more precisely if the function can unwind. If this is not provided +/// then we will only infer whether the function can unwind or not based on the +/// ABI of the function. For example, a function marked with `#[rustc_nounwind]` +/// is known to not unwind even if it's using Rust ABI. /// /// * `abi` - this is the ABI that the function is defined with. This is the /// primary factor for determining whether a function can unwind or not. @@ -1138,11 +1152,6 @@ where /// aborts the process. /// * This affects whether functions have the LLVM `nounwind` attribute, which /// affects various optimizations and codegen. -/// -/// FIXME: this is actually buggy with respect to Rust functions. Rust functions -/// compiled with `-Cpanic=unwind` and referenced from another crate compiled -/// with `-Cpanic=abort` will look like they can't unwind when in fact they -/// might (from a foreign exception or similar). #[inline] #[tracing::instrument(level = "debug", skip(tcx))] pub fn fn_can_unwind(tcx: TyCtxt<'_>, fn_def_id: Option, abi: SpecAbi) -> bool { @@ -1230,6 +1239,8 @@ pub fn fn_can_unwind(tcx: TyCtxt<'_>, fn_def_id: Option, abi: SpecAbi) -> | EfiApi | AvrInterrupt | AvrNonBlockingInterrupt + | RiscvInterruptM + | RiscvInterruptS | CCmseNonSecureCall | Wasm | PlatformIntrinsic @@ -1250,33 +1261,18 @@ pub enum FnAbiError<'tcx> { AdjustForForeignAbi(call::AdjustForForeignAbiError), } -impl<'tcx> From> for FnAbiError<'tcx> { - fn from(err: LayoutError<'tcx>) -> Self { - Self::Layout(err) - } -} - -impl From for FnAbiError<'_> { - fn from(err: call::AdjustForForeignAbiError) -> Self { - Self::AdjustForForeignAbi(err) - } -} - -impl<'tcx> fmt::Display for FnAbiError<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl<'a, 'b> IntoDiagnostic<'a, !> for FnAbiError<'b> { + fn into_diagnostic(self, handler: &'a Handler) -> DiagnosticBuilder<'a, !> { match self { - Self::Layout(err) => err.fmt(f), - Self::AdjustForForeignAbi(err) => err.fmt(f), + Self::Layout(e) => e.into_diagnostic().into_diagnostic(handler), + Self::AdjustForForeignAbi(call::AdjustForForeignAbiError::Unsupported { + arch, + abi, + }) => UnsupportedFnAbi { arch, abi: abi.name() }.into_diagnostic(handler), } } } -impl IntoDiagnostic<'_, !> for FnAbiError<'_> { - fn into_diagnostic(self, handler: &Handler) -> DiagnosticBuilder<'_, !> { - handler.struct_fatal(self.to_string()) - } -} - // FIXME(eddyb) maybe use something like this for an unified `fn_abi_of`, not // just for error handling. #[derive(Debug)] @@ -1324,7 +1320,7 @@ pub trait FnAbiOf<'tcx>: FnAbiOfHelpers<'tcx> { let tcx = self.tcx().at(span); MaybeResult::from(tcx.fn_abi_of_fn_ptr(self.param_env().and((sig, extra_args))).map_err( - |err| self.handle_fn_abi_err(err, span, FnAbiRequest::OfFnPtr { sig, extra_args }), + |err| self.handle_fn_abi_err(*err, span, FnAbiRequest::OfFnPtr { sig, extra_args }), )) } @@ -1351,7 +1347,11 @@ pub trait FnAbiOf<'tcx>: FnAbiOfHelpers<'tcx> { // However, we don't do this early in order to avoid calling // `def_span` unconditionally (which may have a perf penalty). let span = if !span.is_dummy() { span } else { tcx.def_span(instance.def_id()) }; - self.handle_fn_abi_err(err, span, FnAbiRequest::OfInstance { instance, extra_args }) + self.handle_fn_abi_err( + *err, + span, + FnAbiRequest::OfInstance { instance, extra_args }, + ) }), ) } diff --git a/compiler/rustc_middle/src/ty/list.rs b/compiler/rustc_middle/src/ty/list.rs index 71911a5a61877..7a32cfb10857d 100644 --- a/compiler/rustc_middle/src/ty/list.rs +++ b/compiler/rustc_middle/src/ty/list.rs @@ -1,6 +1,7 @@ use crate::arena::Arena; use rustc_data_structures::aligned::{align_of, Aligned}; use rustc_serialize::{Encodable, Encoder}; +use rustc_type_ir::{InferCtxtLike, OptWithInfcx}; use std::alloc::Layout; use std::cmp::Ordering; use std::fmt; @@ -119,6 +120,14 @@ impl fmt::Debug for List { (**self).fmt(f) } } +impl<'tcx, T: super::DebugWithInfcx>> super::DebugWithInfcx> for List { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + fmt::Debug::fmt(&this.map(|this| this.as_slice()), f) + } +} impl> Encodable for List { #[inline] @@ -202,6 +211,8 @@ unsafe impl Sync for List {} // We need this since `List` uses extern type `OpaqueListContents`. #[cfg(parallel_compiler)] use rustc_data_structures::sync::DynSync; + +use super::TyCtxt; #[cfg(parallel_compiler)] unsafe impl DynSync for List {} diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 73f435d4840ac..1274f427e4f7d 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -28,6 +28,7 @@ use crate::ty::fast_reject::SimplifiedType; use crate::ty::util::Discr; pub use adt::*; pub use assoc::*; +pub use generic_args::*; pub use generics::*; use rustc_ast as ast; use rustc_ast::node_id::NodeMap; @@ -53,8 +54,7 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{ExpnId, ExpnKind, Span}; use rustc_target::abi::{Align, FieldIdx, Integer, IntegerType, VariantIdx}; pub use rustc_target::abi::{ReprFlags, ReprOptions}; -use rustc_type_ir::WithCachedTypeInfo; -pub use subst::*; +pub use rustc_type_ir::{DebugWithInfcx, InferCtxtLike, OptWithInfcx}; pub use vtable::*; use std::fmt::Debug; @@ -67,6 +67,10 @@ use std::{fmt, str}; pub use crate::ty::diagnostics::*; pub use rustc_type_ir::AliasKind::*; +pub use rustc_type_ir::ConstKind::{ + Bound as BoundCt, Error as ErrorCt, Expr as ExprCt, Infer as InferCt, Param as ParamCt, + Placeholder as PlaceholderCt, Unevaluated, Value, +}; pub use rustc_type_ir::DynKind::*; pub use rustc_type_ir::InferTy::*; pub use rustc_type_ir::RegionKind::*; @@ -78,11 +82,10 @@ pub use self::binding::BindingMode::*; pub use self::closure::{ is_ancestor_or_same_capture, place_to_string_for_capture, BorrowKind, CaptureInfo, CapturedPlace, ClosureKind, ClosureTypeInfo, MinCaptureInformationMap, MinCaptureList, - RootVariableMinCaptureList, UpvarCapture, UpvarCaptureMap, UpvarId, UpvarListMap, UpvarPath, - CAPTURE_STRUCT_LOCAL, + RootVariableMinCaptureList, UpvarCapture, UpvarId, UpvarPath, CAPTURE_STRUCT_LOCAL, }; pub use self::consts::{ - Const, ConstData, ConstInt, ConstKind, Expr, InferConst, ScalarInt, UnevaluatedConst, ValTree, + Const, ConstData, ConstInt, Expr, InferConst, ScalarInt, UnevaluatedConst, ValTree, }; pub use self::context::{ tls, CtxtInterners, DeducedParamAttrs, FreeRegionInfo, GlobalCtxt, Lift, TyCtxt, TyCtxtFeed, @@ -94,12 +97,12 @@ pub use self::rvalue_scopes::RvalueScopes; pub use self::sty::BoundRegionKind::*; pub use self::sty::{ AliasTy, Article, Binder, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, BoundVar, - BoundVariableKind, CanonicalPolyFnSig, ClosureSubsts, ClosureSubstsParts, ConstVid, + BoundVariableKind, CanonicalPolyFnSig, ClosureArgs, ClosureArgsParts, ConstKind, ConstVid, EarlyBoundRegion, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, FnSig, - FreeRegion, GenSig, GeneratorSubsts, GeneratorSubstsParts, InlineConstSubsts, - InlineConstSubstsParts, ParamConst, ParamTy, PolyExistentialPredicate, - PolyExistentialProjection, PolyExistentialTraitRef, PolyFnSig, PolyGenSig, PolyTraitRef, - Region, RegionKind, RegionVid, TraitRef, TyKind, TypeAndMut, UpvarSubsts, VarianceDiagInfo, + FreeRegion, GenSig, GeneratorArgs, GeneratorArgsParts, InlineConstArgs, InlineConstArgsParts, + ParamConst, ParamTy, PolyExistentialPredicate, PolyExistentialProjection, + PolyExistentialTraitRef, PolyFnSig, PolyGenSig, PolyTraitRef, Region, RegionKind, RegionVid, + TraitRef, TyKind, TypeAndMut, UpvarArgs, VarianceDiagInfo, }; pub use self::trait_def::TraitDef; pub use self::typeck_results::{ @@ -123,7 +126,6 @@ pub mod layout; pub mod normalize_erasing_regions; pub mod print; pub mod relate; -pub mod subst; pub mod trait_def; pub mod util; pub mod visit; @@ -137,6 +139,7 @@ mod consts; mod context; mod diagnostics; mod erase_regions; +mod generic_args; mod generics; mod impls_ty; mod instance; @@ -145,6 +148,7 @@ mod opaque_types; mod parameterized; mod rvalue_scopes; mod structural_impls; +#[allow(hidden_glob_reexports)] mod sty; mod typeck_results; @@ -234,7 +238,7 @@ pub struct ImplHeader<'tcx> { pub impl_def_id: DefId, pub self_ty: Ty<'tcx>, pub trait_ref: Option>, - pub predicates: Vec>, + pub predicates: Vec<(Predicate<'tcx>, Span)>, } #[derive(Copy, Clone, PartialEq, Eq, Debug, TypeFoldable, TypeVisitable)] @@ -351,8 +355,8 @@ impl TyCtxt<'_> { #[inline] #[track_caller] - pub fn local_parent(self, id: LocalDefId) -> LocalDefId { - self.parent(id.to_def_id()).expect_local() + pub fn local_parent(self, id: impl Into) -> LocalDefId { + self.parent(id.into().to_def_id()).expect_local() } pub fn is_descendant_of(self, mut descendant: DefId, ancestor: DefId) -> bool { @@ -456,6 +460,11 @@ impl ty::EarlyBoundRegion { } } +/// A statement that can be proven by a trait solver. This includes things that may +/// show up in where clauses, such as trait predicates and projection predicates, +/// and also things that are emitted as part of type checking such as `ObjectSafe` +/// predicate which is emitted when a type is coerced to a trait object. +/// /// Use this rather than `PredicateKind`, whenever possible. #[derive(Clone, Copy, PartialEq, Eq, Hash, HashStable)] #[rustc_pass_by_value] @@ -487,13 +496,11 @@ impl<'tcx> Predicate<'tcx> { let kind = self .kind() .map_bound(|kind| match kind { - PredicateKind::Clause(Clause::Trait(TraitPredicate { + PredicateKind::Clause(ClauseKind::Trait(TraitPredicate { trait_ref, - constness, polarity, - })) => Some(PredicateKind::Clause(Clause::Trait(TraitPredicate { + })) => Some(PredicateKind::Clause(ClauseKind::Trait(TraitPredicate { trait_ref, - constness, polarity: polarity.flip()?, }))), @@ -504,26 +511,13 @@ impl<'tcx> Predicate<'tcx> { Some(tcx.mk_predicate(kind)) } - pub fn without_const(mut self, tcx: TyCtxt<'tcx>) -> Self { - if let PredicateKind::Clause(Clause::Trait(TraitPredicate { trait_ref, constness, polarity })) = self.kind().skip_binder() - && constness != BoundConstness::NotConst - { - self = tcx.mk_predicate(self.kind().rebind(PredicateKind::Clause(Clause::Trait(TraitPredicate { - trait_ref, - constness: BoundConstness::NotConst, - polarity, - })))); - } - self - } - #[instrument(level = "debug", skip(tcx), ret)] pub fn is_coinductive(self, tcx: TyCtxt<'tcx>) -> bool { match self.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => { tcx.trait_is_coinductive(data.def_id()) } - ty::PredicateKind::WellFormed(_) => true, + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) => true, _ => false, } } @@ -536,21 +530,20 @@ impl<'tcx> Predicate<'tcx> { #[inline] pub fn allow_normalization(self) -> bool { match self.kind().skip_binder() { - PredicateKind::WellFormed(_) => false, - PredicateKind::Clause(Clause::Trait(_)) - | PredicateKind::Clause(Clause::RegionOutlives(_)) - | PredicateKind::Clause(Clause::TypeOutlives(_)) - | PredicateKind::Clause(Clause::Projection(_)) - | PredicateKind::Clause(Clause::ConstArgHasType(..)) + PredicateKind::Clause(ClauseKind::WellFormed(_)) => false, + PredicateKind::Clause(ClauseKind::Trait(_)) + | PredicateKind::Clause(ClauseKind::RegionOutlives(_)) + | PredicateKind::Clause(ClauseKind::TypeOutlives(_)) + | PredicateKind::Clause(ClauseKind::Projection(_)) + | PredicateKind::Clause(ClauseKind::ConstArgHasType(..)) | PredicateKind::AliasRelate(..) | PredicateKind::ObjectSafe(_) | PredicateKind::ClosureKind(_, _, _) | PredicateKind::Subtype(_) | PredicateKind::Coerce(_) - | PredicateKind::ConstEvaluatable(_) + | PredicateKind::Clause(ClauseKind::ConstEvaluatable(_)) | PredicateKind::ConstEquate(_, _) - | PredicateKind::Ambiguous - | PredicateKind::TypeWellFormedFromEnv(_) => true, + | PredicateKind::Ambiguous => true, } } } @@ -561,11 +554,73 @@ impl rustc_errors::IntoDiagnosticArg for Predicate<'_> { } } +impl rustc_errors::IntoDiagnosticArg for Clause<'_> { + fn into_diagnostic_arg(self) -> rustc_errors::DiagnosticArgValue<'static> { + rustc_errors::DiagnosticArgValue::Str(std::borrow::Cow::Owned(self.to_string())) + } +} + +/// A subset of predicates which can be assumed by the trait solver. They show up in +/// an item's where clauses, hence the name `Clause`, and may either be user-written +/// (such as traits) or may be inserted during lowering. +#[derive(Clone, Copy, PartialEq, Eq, Hash, HashStable)] +#[rustc_pass_by_value] +pub struct Clause<'tcx>(Interned<'tcx, WithCachedTypeInfo>>>); + +impl<'tcx> Clause<'tcx> { + pub fn as_predicate(self) -> Predicate<'tcx> { + Predicate(self.0) + } + + pub fn kind(self) -> Binder<'tcx, ClauseKind<'tcx>> { + self.0.internee.map_bound(|kind| match kind { + PredicateKind::Clause(clause) => clause, + _ => unreachable!(), + }) + } + + pub fn as_trait_clause(self) -> Option>> { + let clause = self.kind(); + if let ty::ClauseKind::Trait(trait_clause) = clause.skip_binder() { + Some(clause.rebind(trait_clause)) + } else { + None + } + } + + pub fn as_projection_clause(self) -> Option>> { + let clause = self.kind(); + if let ty::ClauseKind::Projection(projection_clause) = clause.skip_binder() { + Some(clause.rebind(projection_clause)) + } else { + None + } + } + + pub fn as_type_outlives_clause(self) -> Option>> { + let clause = self.kind(); + if let ty::ClauseKind::TypeOutlives(o) = clause.skip_binder() { + Some(clause.rebind(o)) + } else { + None + } + } + + pub fn as_region_outlives_clause(self) -> Option>> { + let clause = self.kind(); + if let ty::ClauseKind::RegionOutlives(o) = clause.skip_binder() { + Some(clause.rebind(o)) + } else { + None + } + } +} + #[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable)] #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] /// A clause is something that can appear in where bounds or be inferred /// by implied bounds. -pub enum Clause<'tcx> { +pub enum ClauseKind<'tcx> { /// Corresponds to `where Foo: Bar`. `Foo` here would be /// the `Self` type of the trait reference and `A`, `B`, and `C` /// would be the type parameters. @@ -584,24 +639,27 @@ pub enum Clause<'tcx> { /// Ensures that a const generic argument to a parameter `const N: u8` /// is of type `u8`. ConstArgHasType(Const<'tcx>, Ty<'tcx>), + + /// No syntax: `T` well-formed. + WellFormed(GenericArg<'tcx>), + + /// Constant initializer must evaluate successfully. + ConstEvaluatable(ty::Const<'tcx>), } #[derive(Clone, Copy, PartialEq, Eq, Hash, TyEncodable, TyDecodable)] #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] pub enum PredicateKind<'tcx> { /// Prove a clause - Clause(Clause<'tcx>), - - /// No syntax: `T` well-formed. - WellFormed(GenericArg<'tcx>), + Clause(ClauseKind<'tcx>), /// Trait must be object-safe. ObjectSafe(DefId), /// No direct syntax. May be thought of as `where T: FnFoo<...>` - /// for some substitutions `...` and `T` being a closure type. + /// for some generic args `...` and `T` being a closure type. /// Satisfied (or refuted) once we know the closure's kind. - ClosureKind(DefId, SubstsRef<'tcx>, ClosureKind), + ClosureKind(DefId, GenericArgsRef<'tcx>, ClosureKind), /// `T1 <: T2` /// @@ -620,22 +678,14 @@ pub enum PredicateKind<'tcx> { /// logic. Coerce(CoercePredicate<'tcx>), - /// Constant initializer must evaluate successfully. - ConstEvaluatable(ty::Const<'tcx>), - /// Constants must be equal. The first component is the const that is expected. ConstEquate(Const<'tcx>, Const<'tcx>), - /// Represents a type found in the environment that we can use for implied bounds. - /// - /// Only used for Chalk. - TypeWellFormedFromEnv(Ty<'tcx>), - /// A marker predicate that is always ambiguous. /// Used for coherence to mark opaque types as possibly equal to each other but ambiguous. Ambiguous, - /// Separate from `Clause::Projection` which is used for normalization in new solver. + /// Separate from `ClauseKind::Projection` which is used for normalization in new solver. /// This predicate requires two terms to be equal to eachother. /// /// Only used for new solver @@ -672,7 +722,7 @@ pub struct CratePredicatesMap<'tcx> { pub predicates: FxHashMap, Span)]>, } -impl<'tcx> Predicate<'tcx> { +impl<'tcx> Clause<'tcx> { /// Performs a substitution suitable for going from a /// poly-trait-ref to supertraits that must hold if that /// poly-trait-ref holds. This is slightly different from a normal @@ -682,7 +732,7 @@ impl<'tcx> Predicate<'tcx> { self, tcx: TyCtxt<'tcx>, trait_ref: &ty::PolyTraitRef<'tcx>, - ) -> Predicate<'tcx> { + ) -> Clause<'tcx> { // The interaction between HRTB and supertraits is not entirely // obvious. Let me walk you (and myself) through an example. // @@ -744,15 +794,15 @@ impl<'tcx> Predicate<'tcx> { // this trick achieves that). // Working through the second example: - // trait_ref: for<'x> T: Foo1<'^0.0>; substs: [T, '^0.0] - // predicate: for<'b> Self: Bar1<'a, '^0.0>; substs: [Self, 'a, '^0.0] + // trait_ref: for<'x> T: Foo1<'^0.0>; args: [T, '^0.0] + // predicate: for<'b> Self: Bar1<'a, '^0.0>; args: [Self, 'a, '^0.0] // We want to end up with: // for<'x, 'b> T: Bar1<'^0.0, '^0.1> // To do this: // 1) We must shift all bound vars in predicate by the length // of trait ref's bound vars. So, we would end up with predicate like // Self: Bar1<'a, '^0.1> - // 2) We can then apply the trait substs to this, ending up with + // 2) We can then apply the trait args to this, ending up with // T: Bar1<'^0.0, '^0.1> // 3) Finally, to create the final bound vars, we concatenate the bound // vars of the trait ref with those of the predicate: @@ -764,11 +814,17 @@ impl<'tcx> Predicate<'tcx> { let shifted_pred = tcx.shift_bound_var_indices(trait_bound_vars.len(), bound_pred.skip_binder()); // 2) Self: Bar1<'a, '^0.1> -> T: Bar1<'^0.0, '^0.1> - let new = EarlyBinder(shifted_pred).subst(tcx, trait_ref.skip_binder().substs); + let new = EarlyBinder::bind(shifted_pred).instantiate(tcx, trait_ref.skip_binder().args); // 3) ['x] + ['b] -> ['x, 'b] let bound_vars = tcx.mk_bound_variable_kinds_from_iter(trait_bound_vars.iter().chain(pred_bound_vars)); - tcx.reuse_or_mk_predicate(self, ty::Binder::bind_with_vars(new, bound_vars)) + + // FIXME: Is it really perf sensitive to use reuse_or_mk_predicate here? + tcx.reuse_or_mk_predicate( + self.as_predicate(), + ty::Binder::bind_with_vars(PredicateKind::Clause(new), bound_vars), + ) + .expect_clause() } } @@ -777,8 +833,6 @@ impl<'tcx> Predicate<'tcx> { pub struct TraitPredicate<'tcx> { pub trait_ref: TraitRef<'tcx>, - pub constness: BoundConstness, - /// If polarity is Positive: we are proving that the trait is implemented. /// /// If polarity is Negative: we are proving that a negative impl of this trait @@ -792,20 +846,6 @@ pub struct TraitPredicate<'tcx> { pub type PolyTraitPredicate<'tcx> = ty::Binder<'tcx, TraitPredicate<'tcx>>; impl<'tcx> TraitPredicate<'tcx> { - pub fn remap_constness(&mut self, param_env: &mut ParamEnv<'tcx>) { - *param_env = param_env.with_constness(self.constness.and(param_env.constness())) - } - - /// Remap the constness of this predicate before emitting it for diagnostics. - pub fn remap_constness_diag(&mut self, param_env: ParamEnv<'tcx>) { - // this is different to `remap_constness` that callees want to print this predicate - // in case of selection errors. `T: ~const Drop` bounds cannot end up here when the - // param_env is not const because it is always satisfied in non-const contexts. - if let hir::Constness::NotConst = param_env.constness() { - self.constness = ty::BoundConstness::NotConst; - } - } - pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { Self { trait_ref: self.trait_ref.with_self_ty(tcx, self_ty), ..self } } @@ -817,24 +857,6 @@ impl<'tcx> TraitPredicate<'tcx> { pub fn self_ty(self) -> Ty<'tcx> { self.trait_ref.self_ty() } - - #[inline] - pub fn is_const_if_const(self) -> bool { - self.constness == BoundConstness::ConstIfConst - } - - pub fn is_constness_satisfied_by(self, constness: hir::Constness) -> bool { - match (self.constness, constness) { - (BoundConstness::NotConst, _) - | (BoundConstness::ConstIfConst, hir::Constness::Const) => true, - (BoundConstness::ConstIfConst, hir::Constness::NotConst) => false, - } - } - - pub fn without_const(mut self) -> Self { - self.constness = BoundConstness::NotConst; - self - } } impl<'tcx> PolyTraitPredicate<'tcx> { @@ -847,19 +869,6 @@ impl<'tcx> PolyTraitPredicate<'tcx> { self.map_bound(|trait_ref| trait_ref.self_ty()) } - /// Remap the constness of this predicate before emitting it for diagnostics. - pub fn remap_constness_diag(&mut self, param_env: ParamEnv<'tcx>) { - *self = self.map_bound(|mut p| { - p.remap_constness_diag(param_env); - p - }); - } - - #[inline] - pub fn is_const_if_const(self) -> bool { - self.skip_binder().is_const_if_const() - } - #[inline] pub fn polarity(self) -> ImplPolarity { self.skip_binder().polarity @@ -905,9 +914,9 @@ pub struct Term<'tcx> { impl Debug for Term<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let data = if let Some(ty) = self.ty() { - format!("Term::Ty({:?})", ty) + format!("Term::Ty({ty:?})") } else if let Some(ct) = self.ct() { - format!("Term::Ct({:?})", ct) + format!("Term::Ct({ct:?})") } else { unreachable!() }; @@ -1004,7 +1013,7 @@ impl<'tcx> Term<'tcx> { _ => None, }, TermKind::Const(ct) => match ct.kind() { - ConstKind::Unevaluated(uv) => Some(tcx.mk_alias_ty(uv.def, uv.substs)), + ConstKind::Unevaluated(uv) => Some(tcx.mk_alias_ty(uv.def, uv.args)), _ => None, }, } @@ -1189,13 +1198,41 @@ impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, PredicateKind<'tcx>> { } } -impl<'tcx> ToPredicate<'tcx> for Clause<'tcx> { +impl<'tcx> ToPredicate<'tcx> for ClauseKind<'tcx> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { tcx.mk_predicate(ty::Binder::dummy(ty::PredicateKind::Clause(self))) } } +impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, ClauseKind<'tcx>> { + #[inline(always)] + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { + tcx.mk_predicate(self.map_bound(ty::PredicateKind::Clause)) + } +} + +impl<'tcx> ToPredicate<'tcx> for Clause<'tcx> { + #[inline(always)] + fn to_predicate(self, _tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { + self.as_predicate() + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for ClauseKind<'tcx> { + #[inline(always)] + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + tcx.mk_predicate(Binder::dummy(ty::PredicateKind::Clause(self))).expect_clause() + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for Binder<'tcx, ClauseKind<'tcx>> { + #[inline(always)] + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + tcx.mk_predicate(self.map_bound(|clause| ty::PredicateKind::Clause(clause))).expect_clause() + } +} + impl<'tcx> ToPredicate<'tcx> for TraitRef<'tcx> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { @@ -1203,6 +1240,29 @@ impl<'tcx> ToPredicate<'tcx> for TraitRef<'tcx> { } } +impl<'tcx> ToPredicate<'tcx, TraitPredicate<'tcx>> for TraitRef<'tcx> { + #[inline(always)] + fn to_predicate(self, _tcx: TyCtxt<'tcx>) -> TraitPredicate<'tcx> { + TraitPredicate { trait_ref: self, polarity: ImplPolarity::Positive } + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for TraitRef<'tcx> { + #[inline(always)] + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + let p: Predicate<'tcx> = self.to_predicate(tcx); + p.expect_clause() + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for TraitPredicate<'tcx> { + #[inline(always)] + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + let p: Predicate<'tcx> = self.to_predicate(tcx); + p.expect_clause() + } +} + impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, TraitRef<'tcx>> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { @@ -1211,12 +1271,19 @@ impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, TraitRef<'tcx>> { } } +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for Binder<'tcx, TraitRef<'tcx>> { + #[inline(always)] + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + let pred: PolyTraitPredicate<'tcx> = self.to_predicate(tcx); + pred.to_predicate(tcx) + } +} + impl<'tcx> ToPredicate<'tcx, PolyTraitPredicate<'tcx>> for Binder<'tcx, TraitRef<'tcx>> { #[inline(always)] fn to_predicate(self, _: TyCtxt<'tcx>) -> PolyTraitPredicate<'tcx> { self.map_bound(|trait_ref| TraitPredicate { trait_ref, - constness: ty::BoundConstness::NotConst, polarity: ty::ImplPolarity::Positive, }) } @@ -1236,31 +1303,70 @@ impl<'tcx> ToPredicate<'tcx, PolyTraitPredicate<'tcx>> for TraitPredicate<'tcx> impl<'tcx> ToPredicate<'tcx> for PolyTraitPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - self.map_bound(|p| PredicateKind::Clause(Clause::Trait(p))).to_predicate(tcx) + self.map_bound(|p| PredicateKind::Clause(ClauseKind::Trait(p))).to_predicate(tcx) + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for PolyTraitPredicate<'tcx> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + let p: Predicate<'tcx> = self.to_predicate(tcx); + p.expect_clause() + } +} + +impl<'tcx> ToPredicate<'tcx> for OutlivesPredicate, ty::Region<'tcx>> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { + ty::Binder::dummy(PredicateKind::Clause(ClauseKind::RegionOutlives(self))).to_predicate(tcx) } } impl<'tcx> ToPredicate<'tcx> for PolyRegionOutlivesPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - self.map_bound(|p| PredicateKind::Clause(Clause::RegionOutlives(p))).to_predicate(tcx) + self.map_bound(|p| PredicateKind::Clause(ClauseKind::RegionOutlives(p))).to_predicate(tcx) + } +} + +impl<'tcx> ToPredicate<'tcx> for OutlivesPredicate, ty::Region<'tcx>> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { + ty::Binder::dummy(PredicateKind::Clause(ClauseKind::TypeOutlives(self))).to_predicate(tcx) } } impl<'tcx> ToPredicate<'tcx> for PolyTypeOutlivesPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - self.map_bound(|p| PredicateKind::Clause(Clause::TypeOutlives(p))).to_predicate(tcx) + self.map_bound(|p| PredicateKind::Clause(ClauseKind::TypeOutlives(p))).to_predicate(tcx) + } +} + +impl<'tcx> ToPredicate<'tcx> for ProjectionPredicate<'tcx> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { + ty::Binder::dummy(PredicateKind::Clause(ClauseKind::Projection(self))).to_predicate(tcx) } } impl<'tcx> ToPredicate<'tcx> for PolyProjectionPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - self.map_bound(|p| PredicateKind::Clause(Clause::Projection(p))).to_predicate(tcx) + self.map_bound(|p| PredicateKind::Clause(ClauseKind::Projection(p))).to_predicate(tcx) + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for ProjectionPredicate<'tcx> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + let p: Predicate<'tcx> = self.to_predicate(tcx); + p.expect_clause() + } +} + +impl<'tcx> ToPredicate<'tcx, Clause<'tcx>> for PolyProjectionPredicate<'tcx> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Clause<'tcx> { + let p: Predicate<'tcx> = self.to_predicate(tcx); + p.expect_clause() } } impl<'tcx> ToPredicate<'tcx> for TraitPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { - PredicateKind::Clause(Clause::Trait(self)).to_predicate(tcx) + PredicateKind::Clause(ClauseKind::Trait(self)).to_predicate(tcx) } } @@ -1268,63 +1374,76 @@ impl<'tcx> Predicate<'tcx> { pub fn to_opt_poly_trait_pred(self) -> Option> { let predicate = self.kind(); match predicate.skip_binder() { - PredicateKind::Clause(Clause::Trait(t)) => Some(predicate.rebind(t)), - PredicateKind::Clause(Clause::Projection(..)) - | PredicateKind::Clause(Clause::ConstArgHasType(..)) + PredicateKind::Clause(ClauseKind::Trait(t)) => Some(predicate.rebind(t)), + PredicateKind::Clause(ClauseKind::Projection(..)) + | PredicateKind::Clause(ClauseKind::ConstArgHasType(..)) | PredicateKind::AliasRelate(..) | PredicateKind::Subtype(..) | PredicateKind::Coerce(..) - | PredicateKind::Clause(Clause::RegionOutlives(..)) - | PredicateKind::WellFormed(..) + | PredicateKind::Clause(ClauseKind::RegionOutlives(..)) + | PredicateKind::Clause(ClauseKind::WellFormed(..)) | PredicateKind::ObjectSafe(..) | PredicateKind::ClosureKind(..) - | PredicateKind::Clause(Clause::TypeOutlives(..)) - | PredicateKind::ConstEvaluatable(..) + | PredicateKind::Clause(ClauseKind::TypeOutlives(..)) + | PredicateKind::Clause(ClauseKind::ConstEvaluatable(..)) | PredicateKind::ConstEquate(..) - | PredicateKind::Ambiguous - | PredicateKind::TypeWellFormedFromEnv(..) => None, + | PredicateKind::Ambiguous => None, } } pub fn to_opt_poly_projection_pred(self) -> Option> { let predicate = self.kind(); match predicate.skip_binder() { - PredicateKind::Clause(Clause::Projection(t)) => Some(predicate.rebind(t)), - PredicateKind::Clause(Clause::Trait(..)) - | PredicateKind::Clause(Clause::ConstArgHasType(..)) + PredicateKind::Clause(ClauseKind::Projection(t)) => Some(predicate.rebind(t)), + PredicateKind::Clause(ClauseKind::Trait(..)) + | PredicateKind::Clause(ClauseKind::ConstArgHasType(..)) | PredicateKind::AliasRelate(..) | PredicateKind::Subtype(..) | PredicateKind::Coerce(..) - | PredicateKind::Clause(Clause::RegionOutlives(..)) - | PredicateKind::WellFormed(..) + | PredicateKind::Clause(ClauseKind::RegionOutlives(..)) + | PredicateKind::Clause(ClauseKind::WellFormed(..)) | PredicateKind::ObjectSafe(..) | PredicateKind::ClosureKind(..) - | PredicateKind::Clause(Clause::TypeOutlives(..)) - | PredicateKind::ConstEvaluatable(..) + | PredicateKind::Clause(ClauseKind::TypeOutlives(..)) + | PredicateKind::Clause(ClauseKind::ConstEvaluatable(..)) | PredicateKind::ConstEquate(..) - | PredicateKind::Ambiguous - | PredicateKind::TypeWellFormedFromEnv(..) => None, + | PredicateKind::Ambiguous => None, } } pub fn to_opt_type_outlives(self) -> Option> { let predicate = self.kind(); match predicate.skip_binder() { - PredicateKind::Clause(Clause::TypeOutlives(data)) => Some(predicate.rebind(data)), - PredicateKind::Clause(Clause::Trait(..)) - | PredicateKind::Clause(Clause::ConstArgHasType(..)) - | PredicateKind::Clause(Clause::Projection(..)) + PredicateKind::Clause(ClauseKind::TypeOutlives(data)) => Some(predicate.rebind(data)), + PredicateKind::Clause(ClauseKind::Trait(..)) + | PredicateKind::Clause(ClauseKind::ConstArgHasType(..)) + | PredicateKind::Clause(ClauseKind::Projection(..)) | PredicateKind::AliasRelate(..) | PredicateKind::Subtype(..) | PredicateKind::Coerce(..) - | PredicateKind::Clause(Clause::RegionOutlives(..)) - | PredicateKind::WellFormed(..) + | PredicateKind::Clause(ClauseKind::RegionOutlives(..)) + | PredicateKind::Clause(ClauseKind::WellFormed(..)) | PredicateKind::ObjectSafe(..) | PredicateKind::ClosureKind(..) - | PredicateKind::ConstEvaluatable(..) + | PredicateKind::Clause(ClauseKind::ConstEvaluatable(..)) | PredicateKind::ConstEquate(..) - | PredicateKind::Ambiguous - | PredicateKind::TypeWellFormedFromEnv(..) => None, + | PredicateKind::Ambiguous => None, + } + } + + /// Matches a `PredicateKind::Clause` and turns it into a `Clause`, otherwise returns `None`. + pub fn as_clause(self) -> Option> { + match self.kind().skip_binder() { + PredicateKind::Clause(..) => Some(self.expect_clause()), + _ => None, + } + } + + /// Assert that the predicate is a clause. + pub fn expect_clause(self) -> Clause<'tcx> { + match self.kind().skip_binder() { + PredicateKind::Clause(..) => Clause(self.0), + _ => bug!("{self} is not a clause"), } } } @@ -1350,7 +1469,7 @@ impl<'tcx> Predicate<'tcx> { /// [usize:Bar]]`. #[derive(Clone, Debug, TypeFoldable, TypeVisitable)] pub struct InstantiatedPredicates<'tcx> { - pub predicates: Vec>, + pub predicates: Vec>, pub spans: Vec, } @@ -1369,9 +1488,9 @@ impl<'tcx> InstantiatedPredicates<'tcx> { } impl<'tcx> IntoIterator for InstantiatedPredicates<'tcx> { - type Item = (Predicate<'tcx>, Span); + type Item = (Clause<'tcx>, Span); - type IntoIter = std::iter::Zip>, std::vec::IntoIter>; + type IntoIter = std::iter::Zip>, std::vec::IntoIter>; fn into_iter(self) -> Self::IntoIter { debug_assert_eq!(self.predicates.len(), self.spans.len()); @@ -1380,10 +1499,10 @@ impl<'tcx> IntoIterator for InstantiatedPredicates<'tcx> { } impl<'a, 'tcx> IntoIterator for &'a InstantiatedPredicates<'tcx> { - type Item = (Predicate<'tcx>, Span); + type Item = (Clause<'tcx>, Span); type IntoIter = std::iter::Zip< - std::iter::Copied>>, + std::iter::Copied>>, std::iter::Copied>, >; @@ -1397,7 +1516,7 @@ impl<'a, 'tcx> IntoIterator for &'a InstantiatedPredicates<'tcx> { #[derive(TypeFoldable, TypeVisitable)] pub struct OpaqueTypeKey<'tcx> { pub def_id: LocalDefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } #[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, HashStable, TyEncodable, TyDecodable)] @@ -1468,21 +1587,21 @@ impl<'tcx> OpaqueHiddenType<'tcx> { // typeck errors have subpar spans for opaque types, so delay error reporting until borrowck. ignore_errors: bool, ) -> Self { - let OpaqueTypeKey { def_id, substs } = opaque_type_key; + let OpaqueTypeKey { def_id, args } = opaque_type_key; - // Use substs to build up a reverse map from regions to their + // Use args to build up a reverse map from regions to their // identity mappings. This is necessary because of `impl // Trait` lifetimes are computed by replacing existing // lifetimes with 'static and remapping only those used in the // `impl Trait` return type, resulting in the parameters // shifting. - let id_substs = InternalSubsts::identity_for_item(tcx, def_id); - debug!(?id_substs); + let id_args = GenericArgs::identity_for_item(tcx, def_id); + debug!(?id_args); - // This zip may have several times the same lifetime in `substs` paired with a different - // lifetime from `id_substs`. Simply `collect`ing the iterator is the correct behaviour: + // This zip may have several times the same lifetime in `args` paired with a different + // lifetime from `id_args`. Simply `collect`ing the iterator is the correct behaviour: // it will pick the last one, which is the one we introduced in the impl-trait desugaring. - let map = substs.iter().zip(id_substs).collect(); + let map = args.iter().zip(id_args).collect(); debug!("map = {:#?}", map); // Convert the type from the function into a type valid outside @@ -1496,7 +1615,7 @@ impl<'tcx> OpaqueHiddenType<'tcx> { /// identified by both a universe, as well as a name residing within that universe. Distinct bound /// regions/types/consts within the same universe simply have an unknown relationship to one /// another. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(HashStable, TyEncodable, TyDecodable)] pub struct Placeholder { pub universe: UniverseIndex, @@ -1533,21 +1652,18 @@ pub struct ParamEnv<'tcx> { /// want `Reveal::All`. /// /// Note: This is packed, use the reveal() method to access it. - packed: CopyTaggedPtr<&'tcx List>, ParamTag, true>, + packed: CopyTaggedPtr<&'tcx List>, ParamTag, true>, } #[derive(Copy, Clone)] struct ParamTag { reveal: traits::Reveal, - constness: hir::Constness, } impl_tag! { impl Tag for ParamTag; - ParamTag { reveal: traits::Reveal::UserFacing, constness: hir::Constness::NotConst }, - ParamTag { reveal: traits::Reveal::All, constness: hir::Constness::NotConst }, - ParamTag { reveal: traits::Reveal::UserFacing, constness: hir::Constness::Const }, - ParamTag { reveal: traits::Reveal::All, constness: hir::Constness::Const }, + ParamTag { reveal: traits::Reveal::UserFacing }, + ParamTag { reveal: traits::Reveal::All }, } impl<'tcx> fmt::Debug for ParamEnv<'tcx> { @@ -1555,7 +1671,6 @@ impl<'tcx> fmt::Debug for ParamEnv<'tcx> { f.debug_struct("ParamEnv") .field("caller_bounds", &self.caller_bounds()) .field("reveal", &self.reveal()) - .field("constness", &self.constness()) .finish() } } @@ -1564,7 +1679,6 @@ impl<'a, 'tcx> HashStable> for ParamEnv<'tcx> { fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { self.caller_bounds().hash_stable(hcx, hasher); self.reveal().hash_stable(hcx, hasher); - self.constness().hash_stable(hcx, hasher); } } @@ -1576,7 +1690,6 @@ impl<'tcx> TypeFoldable> for ParamEnv<'tcx> { Ok(ParamEnv::new( self.caller_bounds().try_fold_with(folder)?, self.reveal().try_fold_with(folder)?, - self.constness(), )) } } @@ -1595,11 +1708,11 @@ impl<'tcx> ParamEnv<'tcx> { /// type-checking. #[inline] pub fn empty() -> Self { - Self::new(List::empty(), Reveal::UserFacing, hir::Constness::NotConst) + Self::new(List::empty(), Reveal::UserFacing) } #[inline] - pub fn caller_bounds(self) -> &'tcx List> { + pub fn caller_bounds(self) -> &'tcx List> { self.packed.pointer() } @@ -1608,16 +1721,6 @@ impl<'tcx> ParamEnv<'tcx> { self.packed.tag().reveal } - #[inline] - pub fn constness(self) -> hir::Constness { - self.packed.tag().constness - } - - #[inline] - pub fn is_const(self) -> bool { - self.packed.tag().constness == hir::Constness::Const - } - /// Construct a trait environment with no where-clauses in scope /// where the values of all `impl Trait` and other hidden types /// are revealed. This is suitable for monomorphized, post-typeck @@ -1627,17 +1730,13 @@ impl<'tcx> ParamEnv<'tcx> { /// or invoke `param_env.with_reveal_all()`. #[inline] pub fn reveal_all() -> Self { - Self::new(List::empty(), Reveal::All, hir::Constness::NotConst) + Self::new(List::empty(), Reveal::All) } /// Construct a trait environment with the given set of predicates. #[inline] - pub fn new( - caller_bounds: &'tcx List>, - reveal: Reveal, - constness: hir::Constness, - ) -> Self { - ty::ParamEnv { packed: CopyTaggedPtr::new(caller_bounds, ParamTag { reveal, constness }) } + pub fn new(caller_bounds: &'tcx List>, reveal: Reveal) -> Self { + ty::ParamEnv { packed: CopyTaggedPtr::new(caller_bounds, ParamTag { reveal }) } } pub fn with_user_facing(mut self) -> Self { @@ -1645,29 +1744,6 @@ impl<'tcx> ParamEnv<'tcx> { self } - #[inline] - pub fn with_constness(mut self, constness: hir::Constness) -> Self { - self.packed.set_tag(ParamTag { constness, ..self.packed.tag() }); - self - } - - #[inline] - pub fn with_const(mut self) -> Self { - self.packed.set_tag(ParamTag { constness: hir::Constness::Const, ..self.packed.tag() }); - self - } - - #[inline] - pub fn without_const(mut self) -> Self { - self.packed.set_tag(ParamTag { constness: hir::Constness::NotConst, ..self.packed.tag() }); - self - } - - #[inline] - pub fn remap_constness_with(&mut self, mut constness: ty::BoundConstness) { - *self = self.with_constness(constness.and(self.constness())) - } - /// Returns a new parameter environment with the same clauses, but /// which "reveals" the true results of projections in all cases /// (even for associated types that are specializable). This is @@ -1682,17 +1758,13 @@ impl<'tcx> ParamEnv<'tcx> { return self; } - ParamEnv::new( - tcx.reveal_opaque_types_in_bounds(self.caller_bounds()), - Reveal::All, - self.constness(), - ) + ParamEnv::new(tcx.reveal_opaque_types_in_bounds(self.caller_bounds()), Reveal::All) } /// Returns this same environment but with no caller bounds. #[inline] pub fn without_caller_bounds(self) -> Self { - Self::new(List::empty(), self.reveal(), self.constness()) + Self::new(List::empty(), self.reveal()) } /// Creates a suitable environment in which to perform trait @@ -1722,24 +1794,6 @@ impl<'tcx> ParamEnv<'tcx> { } } -// FIXME(ecstaticmorse): Audit all occurrences of `without_const().to_predicate(tcx)` to ensure that -// the constness of trait bounds is being propagated correctly. -impl<'tcx> PolyTraitRef<'tcx> { - #[inline] - pub fn with_constness(self, constness: BoundConstness) -> PolyTraitPredicate<'tcx> { - self.map_bound(|trait_ref| ty::TraitPredicate { - trait_ref, - constness, - polarity: ty::ImplPolarity::Positive, - }) - } - - #[inline] - pub fn without_const(self) -> PolyTraitPredicate<'tcx> { - self.with_constness(BoundConstness::NotConst) - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TypeFoldable, TypeVisitable)] #[derive(HashStable, Lift)] pub struct ParamEnvAnd<'tcx, T> { @@ -1874,6 +1928,22 @@ impl VariantDef { &self.fields[FieldIdx::from_u32(0)] } + + /// Returns the last field in this variant, if present. + #[inline] + pub fn tail_opt(&self) -> Option<&FieldDef> { + self.fields.raw.last() + } + + /// Returns the last field in this variant. + /// + /// # Panics + /// + /// Panics, if the variant has no fields. + #[inline] + pub fn tail(&self) -> &FieldDef { + self.tail_opt().expect("expected unsized ADT to have a tail field") + } } impl PartialEq for VariantDef { @@ -1987,10 +2057,10 @@ impl Hash for FieldDef { } impl<'tcx> FieldDef { - /// Returns the type of this field. The resulting type is not normalized. The `subst` is + /// Returns the type of this field. The resulting type is not normalized. The `arg` is /// typically obtained via the second field of [`TyKind::Adt`]. - pub fn ty(&self, tcx: TyCtxt<'tcx>, subst: SubstsRef<'tcx>) -> Ty<'tcx> { - tcx.type_of(self.did).subst(tcx, subst) + pub fn ty(&self, tcx: TyCtxt<'tcx>, arg: GenericArgsRef<'tcx>) -> Ty<'tcx> { + tcx.type_of(self.did).instantiate(tcx, arg) } /// Computes the `Ident` of this variant by looking up the `Span` @@ -2214,8 +2284,8 @@ impl<'tcx> TyCtxt<'tcx> { let impl_trait_ref2 = self.impl_trait_ref(def_id2); // If either trait impl references an error, they're allowed to overlap, // as one of them essentially doesn't exist. - if impl_trait_ref1.is_some_and(|tr| tr.subst_identity().references_error()) - || impl_trait_ref2.is_some_and(|tr| tr.subst_identity().references_error()) + if impl_trait_ref1.is_some_and(|tr| tr.instantiate_identity().references_error()) + || impl_trait_ref2.is_some_and(|tr| tr.instantiate_identity().references_error()) { return Some(ImplOverlapKind::Permitted { marker: false }); } @@ -2488,9 +2558,7 @@ impl<'tcx> TyCtxt<'tcx> { && if self.features().collapse_debuginfo { span.in_macro_expansion_with_collapse_debuginfo() } else { - // Inlined spans should not be collapsed as that leads to all of the - // inlined code being attributed to the inline callsite. - span.from_expansion() && !span.is_inlined() + span.from_expansion() } } @@ -2507,20 +2575,6 @@ impl<'tcx> TyCtxt<'tcx> { matches!(self.trait_of_item(def_id), Some(trait_id) if self.has_attr(trait_id, sym::const_trait)) } - pub fn impl_trait_in_trait_parent_fn(self, mut def_id: DefId) -> DefId { - match self.opt_rpitit_info(def_id) { - Some(ImplTraitInTraitData::Trait { fn_def_id, .. }) - | Some(ImplTraitInTraitData::Impl { fn_def_id, .. }) => fn_def_id, - None => { - while let def_kind = self.def_kind(def_id) && def_kind != DefKind::AssocFn { - debug_assert_eq!(def_kind, DefKind::ImplTraitPlaceholder); - def_id = self.parent(def_id); - } - def_id - } - } - } - /// Returns the `DefId` of the item within which the `impl Trait` is declared. /// For type-alias-impl-trait this is the `type` alias. /// For impl-trait-in-assoc-type this is the assoc type. @@ -2538,33 +2592,20 @@ impl<'tcx> TyCtxt<'tcx> { return false; } - let Some(item) = self.opt_associated_item(def_id) else { return false; }; + let Some(item) = self.opt_associated_item(def_id) else { + return false; + }; if item.container != ty::AssocItemContainer::ImplContainer { return false; } - let Some(trait_item_def_id) = item.trait_item_def_id else { return false; }; - - if self.lower_impl_trait_in_trait_to_assoc_ty() { - return !self - .associated_types_for_impl_traits_in_associated_fn(trait_item_def_id) - .is_empty(); - } + let Some(trait_item_def_id) = item.trait_item_def_id else { + return false; + }; - // FIXME(RPITIT): This does a somewhat manual walk through the signature - // of the trait fn to look for any RPITITs, but that's kinda doing a lot - // of work. We can probably remove this when we refactor RPITITs to be - // associated types. - self.fn_sig(trait_item_def_id).subst_identity().skip_binder().output().walk().any(|arg| { - if let ty::GenericArgKind::Type(ty) = arg.unpack() - && let ty::Alias(ty::Projection, data) = ty.kind() - && self.def_kind(data.def_id) == DefKind::ImplTraitPlaceholder - { - true - } else { - false - } - }) + return !self + .associated_types_for_impl_traits_in_associated_fn(trait_item_def_id) + .is_empty(); } } diff --git a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs index a0c8d299f48ba..2415d50b2784a 100644 --- a/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs +++ b/compiler/rustc_middle/src/ty/normalize_erasing_regions.rs @@ -9,7 +9,7 @@ use crate::traits::query::NoSolution; use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder}; -use crate::ty::{self, EarlyBinder, SubstsRef, Ty, TyCtxt, TypeVisitableExt}; +use crate::ty::{self, EarlyBinder, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt}; #[derive(Debug, Copy, Clone, HashStable, TyEncodable, TyDecodable)] pub enum NormalizationError<'tcx> { @@ -20,8 +20,8 @@ pub enum NormalizationError<'tcx> { impl<'tcx> NormalizationError<'tcx> { pub fn get_type_for_failure(&self) -> String { match self { - NormalizationError::Type(t) => format!("{}", t), - NormalizationError::Const(c) => format!("{}", c), + NormalizationError::Type(t) => format!("{t}"), + NormalizationError::Const(c) => format!("{c}"), } } } @@ -137,7 +137,7 @@ impl<'tcx> TyCtxt<'tcx> { /// use `try_subst_and_normalize_erasing_regions` instead. pub fn subst_and_normalize_erasing_regions( self, - param_substs: SubstsRef<'tcx>, + param_args: GenericArgsRef<'tcx>, param_env: ty::ParamEnv<'tcx>, value: EarlyBinder, ) -> T @@ -146,12 +146,12 @@ impl<'tcx> TyCtxt<'tcx> { { debug!( "subst_and_normalize_erasing_regions(\ - param_substs={:?}, \ + param_args={:?}, \ value={:?}, \ param_env={:?})", - param_substs, value, param_env, + param_args, value, param_env, ); - let substituted = value.subst(self, param_substs); + let substituted = value.instantiate(self, param_args); self.normalize_erasing_regions(param_env, substituted) } @@ -161,7 +161,7 @@ impl<'tcx> TyCtxt<'tcx> { /// not assume that normalization succeeds. pub fn try_subst_and_normalize_erasing_regions( self, - param_substs: SubstsRef<'tcx>, + param_args: GenericArgsRef<'tcx>, param_env: ty::ParamEnv<'tcx>, value: EarlyBinder, ) -> Result> @@ -170,12 +170,12 @@ impl<'tcx> TyCtxt<'tcx> { { debug!( "subst_and_normalize_erasing_regions(\ - param_substs={:?}, \ + param_args={:?}, \ value={:?}, \ param_env={:?})", - param_substs, value, param_env, + param_args, value, param_env, ); - let substituted = value.subst(self, param_substs); + let substituted = value.instantiate(self, param_args); self.try_normalize_erasing_regions(param_env, substituted) } } diff --git a/compiler/rustc_middle/src/ty/opaque_types.rs b/compiler/rustc_middle/src/ty/opaque_types.rs index 1b336b7bfc6eb..0ff5ac9030426 100644 --- a/compiler/rustc_middle/src/ty/opaque_types.rs +++ b/compiler/rustc_middle/src/ty/opaque_types.rs @@ -1,7 +1,7 @@ use crate::error::ConstNotUsedTraitAlias; use crate::ty::fold::{TypeFolder, TypeSuperFoldable}; -use crate::ty::subst::{GenericArg, GenericArgKind}; use crate::ty::{self, Ty, TyCtxt, TypeFoldable}; +use crate::ty::{GenericArg, GenericArgKind}; use rustc_data_structures::fx::FxHashMap; use rustc_span::def_id::DefId; use rustc_span::Span; @@ -49,11 +49,11 @@ impl<'tcx> ReverseMapper<'tcx> { kind.fold_with(self) } - fn fold_closure_substs( + fn fold_closure_args( &mut self, def_id: DefId, - substs: ty::SubstsRef<'tcx>, - ) -> ty::SubstsRef<'tcx> { + args: ty::GenericArgsRef<'tcx>, + ) -> ty::GenericArgsRef<'tcx> { // I am a horrible monster and I pray for death. When // we encounter a closure here, it is always a closure // from within the function that we are currently @@ -79,7 +79,7 @@ impl<'tcx> ReverseMapper<'tcx> { // during codegen. let generics = self.tcx.generics_of(def_id); - self.tcx.mk_substs_from_iter(substs.iter().enumerate().map(|(index, kind)| { + self.tcx.mk_args_from_iter(args.iter().enumerate().map(|(index, kind)| { if index < generics.parent_count { // Accommodate missing regions in the parent kinds... self.fold_kind_no_missing_regions_error(kind) @@ -124,7 +124,7 @@ impl<'tcx> TypeFolder> for ReverseMapper<'tcx> { match self.map.get(&r.into()).map(|k| k.unpack()) { Some(GenericArgKind::Lifetime(r1)) => r1, - Some(u) => panic!("region mapped to unexpected kind: {:?}", u), + Some(u) => panic!("region mapped to unexpected kind: {u:?}"), None if self.do_not_error => self.tcx.lifetimes.re_static, None => { let e = self @@ -134,33 +134,32 @@ impl<'tcx> TypeFolder> for ReverseMapper<'tcx> { .span_label( self.span, format!( - "lifetime `{}` is part of concrete type but not used in \ - parameter list of the `impl Trait` type alias", - r + "lifetime `{r}` is part of concrete type but not used in \ + parameter list of the `impl Trait` type alias" ), ) .emit(); - self.interner().mk_re_error(e) + ty::Region::new_error(self.interner(), e) } } } fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { match *ty.kind() { - ty::Closure(def_id, substs) => { - let substs = self.fold_closure_substs(def_id, substs); - self.tcx.mk_closure(def_id, substs) + ty::Closure(def_id, args) => { + let args = self.fold_closure_args(def_id, args); + Ty::new_closure(self.tcx, def_id, args) } - ty::Generator(def_id, substs, movability) => { - let substs = self.fold_closure_substs(def_id, substs); - self.tcx.mk_generator(def_id, substs, movability) + ty::Generator(def_id, args, movability) => { + let args = self.fold_closure_args(def_id, args); + Ty::new_generator(self.tcx, def_id, args, movability) } - ty::GeneratorWitnessMIR(def_id, substs) => { - let substs = self.fold_closure_substs(def_id, substs); - self.tcx.mk_generator_witness_mir(def_id, substs) + ty::GeneratorWitnessMIR(def_id, args) => { + let args = self.fold_closure_args(def_id, args); + Ty::new_generator_witness_mir(self.tcx, def_id, args) } ty::Param(param) => { @@ -169,7 +168,7 @@ impl<'tcx> TypeFolder> for ReverseMapper<'tcx> { // Found it in the substitution list; replace with the parameter from the // opaque type. Some(GenericArgKind::Type(t1)) => t1, - Some(u) => panic!("type mapped to unexpected kind: {:?}", u), + Some(u) => panic!("type mapped to unexpected kind: {u:?}"), None => { debug!(?param, ?self.map); if !self.ignore_errors { @@ -178,15 +177,14 @@ impl<'tcx> TypeFolder> for ReverseMapper<'tcx> { .struct_span_err( self.span, format!( - "type parameter `{}` is part of concrete type but not \ - used in parameter list for the `impl Trait` type alias", - ty + "type parameter `{ty}` is part of concrete type but not \ + used in parameter list for the `impl Trait` type alias" ), ) .emit(); } - self.interner().ty_error_misc() + Ty::new_misc_error(self.tcx) } } } @@ -205,7 +203,7 @@ impl<'tcx> TypeFolder> for ReverseMapper<'tcx> { // Found it in the substitution list, replace with the parameter from the // opaque type. Some(GenericArgKind::Const(c1)) => c1, - Some(u) => panic!("const mapped to unexpected kind: {:?}", u), + Some(u) => panic!("const mapped to unexpected kind: {u:?}"), None => { let guar = self .tcx @@ -216,7 +214,7 @@ impl<'tcx> TypeFolder> for ReverseMapper<'tcx> { }) .emit_unless(self.ignore_errors); - self.interner().const_error(ct.ty(), guar) + ty::Const::new_error(self.tcx, guar, ct.ty()) } } } diff --git a/compiler/rustc_middle/src/ty/parameterized.rs b/compiler/rustc_middle/src/ty/parameterized.rs index a2e77d9cdfe38..f1c38984296c7 100644 --- a/compiler/rustc_middle/src/ty/parameterized.rs +++ b/compiler/rustc_middle/src/ty/parameterized.rs @@ -52,6 +52,7 @@ trivially_parameterized_over_tcx! { usize, (), u32, + u64, bool, std::string::String, crate::metadata::ModChild, @@ -73,6 +74,7 @@ trivially_parameterized_over_tcx! { ty::fast_reject::SimplifiedType, rustc_ast::Attribute, rustc_ast::DelimArgs, + rustc_ast::expand::StrippedCfgItem, rustc_attr::ConstStability, rustc_attr::DefaultBodyStability, rustc_attr::Deprecation, @@ -128,5 +130,6 @@ parameterized_over_tcx! { ty::Const, ty::Predicate, ty::Clause, + ty::ClauseKind, ty::GeneratorDiagnosticData, } diff --git a/compiler/rustc_middle/src/ty/print/mod.rs b/compiler/rustc_middle/src/ty/print/mod.rs index 64e7480e626d7..05871d0bc39d1 100644 --- a/compiler/rustc_middle/src/ty/print/mod.rs +++ b/compiler/rustc_middle/src/ty/print/mod.rs @@ -42,19 +42,19 @@ pub trait Printer<'tcx>: Sized { fn print_def_path( self, def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], ) -> Result { - self.default_print_def_path(def_id, substs) + self.default_print_def_path(def_id, args) } fn print_impl_path( self, impl_def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], self_ty: Ty<'tcx>, trait_ref: Option>, ) -> Result { - self.default_print_impl_path(impl_def_id, substs, self_ty, trait_ref) + self.default_print_impl_path(impl_def_id, args, self_ty, trait_ref) } fn print_region(self, region: ty::Region<'tcx>) -> Result; @@ -102,7 +102,7 @@ pub trait Printer<'tcx>: Sized { fn default_print_def_path( self, def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], ) -> Result { let key = self.tcx().def_key(def_id); debug!(?key); @@ -117,25 +117,28 @@ pub trait Printer<'tcx>: Sized { let generics = self.tcx().generics_of(def_id); let self_ty = self.tcx().type_of(def_id); let impl_trait_ref = self.tcx().impl_trait_ref(def_id); - let (self_ty, impl_trait_ref) = if substs.len() >= generics.count() { + let (self_ty, impl_trait_ref) = if args.len() >= generics.count() { ( - self_ty.subst(self.tcx(), substs), - impl_trait_ref.map(|i| i.subst(self.tcx(), substs)), + self_ty.instantiate(self.tcx(), args), + impl_trait_ref.map(|i| i.instantiate(self.tcx(), args)), ) } else { - (self_ty.0, impl_trait_ref.map(|i| i.0)) + ( + self_ty.instantiate_identity(), + impl_trait_ref.map(|i| i.instantiate_identity()), + ) }; - self.print_impl_path(def_id, substs, self_ty, impl_trait_ref) + self.print_impl_path(def_id, args, self_ty, impl_trait_ref) } _ => { let parent_def_id = DefId { index: key.parent.unwrap(), ..def_id }; - let mut parent_substs = substs; + let mut parent_args = args; let mut trait_qualify_parent = false; - if !substs.is_empty() { + if !args.is_empty() { let generics = self.tcx().generics_of(def_id); - parent_substs = &substs[..generics.parent_count.min(substs.len())]; + parent_args = &args[..generics.parent_count.min(args.len())]; match key.disambiguated_data.data { // Closures' own generics are only captures, don't print them. @@ -148,10 +151,10 @@ pub trait Printer<'tcx>: Sized { // If we have any generic arguments to print, we do that // on top of the same path, but without its own generics. _ => { - if !generics.params.is_empty() && substs.len() >= generics.count() { - let args = generics.own_substs_no_defaults(self.tcx(), substs); + if !generics.params.is_empty() && args.len() >= generics.count() { + let args = generics.own_args_no_defaults(self.tcx(), args); return self.path_generic_args( - |cx| cx.print_def_path(def_id, parent_substs), + |cx| cx.print_def_path(def_id, parent_args), args, ); } @@ -162,7 +165,7 @@ pub trait Printer<'tcx>: Sized { // logic, instead of doing it when printing the child. trait_qualify_parent = generics.has_self && generics.parent == Some(parent_def_id) - && parent_substs.len() == generics.parent_count + && parent_args.len() == generics.parent_count && self.tcx().generics_of(parent_def_id).parent_count == 0; } @@ -172,11 +175,11 @@ pub trait Printer<'tcx>: Sized { let trait_ref = ty::TraitRef::new( cx.tcx(), parent_def_id, - parent_substs.iter().copied(), + parent_args.iter().copied(), ); cx.path_qualified(trait_ref.self_ty(), Some(trait_ref)) } else { - cx.print_def_path(parent_def_id, parent_substs) + cx.print_def_path(parent_def_id, parent_args) } }, &key.disambiguated_data, @@ -188,7 +191,7 @@ pub trait Printer<'tcx>: Sized { fn default_print_impl_path( self, impl_def_id: DefId, - _substs: &'tcx [GenericArg<'tcx>], + _args: &'tcx [GenericArg<'tcx>], self_ty: Ty<'tcx>, impl_trait_ref: Option>, ) -> Result { @@ -326,7 +329,8 @@ impl<'tcx, P: Printer<'tcx>> Print<'tcx, P> for ty::Const<'tcx> { } // This is only used by query descriptions -pub fn describe_as_module(def_id: LocalDefId, tcx: TyCtxt<'_>) -> String { +pub fn describe_as_module(def_id: impl Into, tcx: TyCtxt<'_>) -> String { + let def_id = def_id.into(); if def_id.is_top_level_module() { "top-level module".to_string() } else { diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index a064174e2616b..ac0c88468faa5 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -11,12 +11,13 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::sso::SsoHashSet; use rustc_hir as hir; use rustc_hir::def::{self, CtorKind, DefKind, Namespace}; -use rustc_hir::def_id::{DefId, DefIdSet, CRATE_DEF_ID, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, DefIdSet, ModDefId, CRATE_DEF_ID, LOCAL_CRATE}; use rustc_hir::definitions::{DefKey, DefPathData, DefPathDataName, DisambiguatedDefPathData}; use rustc_hir::LangItem; use rustc_session::config::TrimmedDefPaths; use rustc_session::cstore::{ExternCrate, ExternCrateSource}; use rustc_session::Limit; +use rustc_span::sym; use rustc_span::symbol::{kw, Ident, Symbol}; use rustc_span::FileNameDisplayPreference; use rustc_target::abi::Size; @@ -94,6 +95,10 @@ macro_rules! define_helper { $tl.with(|c| c.set(self.0)) } } + + pub fn $name() -> bool { + $tl.with(|c| c.get()) + } )+ } } @@ -184,7 +189,7 @@ impl<'tcx> RegionHighlightMode<'tcx> { /// Convenience wrapper for `highlighting_region`. pub fn highlighting_region_vid(&mut self, vid: ty::RegionVid, number: usize) { - self.highlighting_region(self.tcx.mk_re_var(vid), number) + self.highlighting_region(ty::Region::new_var(self.tcx, vid), number) } /// Returns `Some(n)` with the number to use for the given region, if any. @@ -220,9 +225,9 @@ pub trait PrettyPrinter<'tcx>: fn print_value_path( self, def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], ) -> Result { - self.print_def_path(def_id, substs) + self.print_def_path(def_id, args) } fn in_binder(self, value: &ty::Binder<'tcx, T>) -> Result @@ -321,7 +326,8 @@ pub trait PrettyPrinter<'tcx>: { this .tcx() - .module_children(visible_parent) + // FIXME(typed_def_id): Further propagate ModDefId + .module_children(ModDefId::new_unchecked(*visible_parent)) .iter() .filter(|child| child.res.opt_def_id() == Some(def_id)) .find(|child| child.vis.is_public() && child.ident.name != kw::Underscore) @@ -359,7 +365,7 @@ pub trait PrettyPrinter<'tcx>: self.write_str(get_local_name(&self, symbol, parent, parent_key).as_str())?; self.write_str("::")?; } else if let DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::Trait - | DefKind::TyAlias | DefKind::Fn | DefKind::Const | DefKind::Static(_) = kind + | DefKind::TyAlias { .. } | DefKind::Fn | DefKind::Const | DefKind::Static(_) = kind { } else { // If not covered above, like for example items out of `impl` blocks, fallback. @@ -546,7 +552,8 @@ pub trait PrettyPrinter<'tcx>: // that's public and whose identifier isn't `_`. let reexport = self .tcx() - .module_children(visible_parent) + // FIXME(typed_def_id): Further propagate ModDefId + .module_children(ModDefId::new_unchecked(visible_parent)) .iter() .filter(|child| child.res.opt_def_id() == Some(def_id)) .find(|child| child.vis.is_public() && child.ident.name != kw::Underscore) @@ -675,43 +682,44 @@ pub trait PrettyPrinter<'tcx>: } p!(")") } - ty::FnDef(def_id, substs) => { - if NO_QUERIES.with(|q| q.get()) { - p!(print_def_path(def_id, substs)); + ty::FnDef(def_id, args) => { + if with_no_queries() { + p!(print_def_path(def_id, args)); } else { - let sig = self.tcx().fn_sig(def_id).subst(self.tcx(), substs); - p!(print(sig), " {{", print_value_path(def_id, substs), "}}"); + let sig = self.tcx().fn_sig(def_id).instantiate(self.tcx(), args); + p!(print(sig), " {{", print_value_path(def_id, args), "}}"); } } ty::FnPtr(ref bare_fn) => p!(print(bare_fn)), ty::Infer(infer_ty) => { - let verbose = self.should_print_verbose(); + if self.should_print_verbose() { + p!(write("{:?}", ty.kind())); + return Ok(self); + } + if let ty::TyVar(ty_vid) = infer_ty { if let Some(name) = self.ty_infer_name(ty_vid) { p!(write("{}", name)) } else { - if verbose { - p!(write("{:?}", infer_ty)) - } else { - p!(write("{}", infer_ty)) - } + p!(write("{}", infer_ty)) } } else { - if verbose { p!(write("{:?}", infer_ty)) } else { p!(write("{}", infer_ty)) } + p!(write("{}", infer_ty)) } } - ty::Error(_) => p!("[type error]"), + ty::Error(_) => p!("{{type error}}"), ty::Param(ref param_ty) => p!(print(param_ty)), ty::Bound(debruijn, bound_ty) => match bound_ty.kind { - ty::BoundTyKind::Anon => debug_bound_var(&mut self, debruijn, bound_ty.var)?, + ty::BoundTyKind::Anon => { + rustc_type_ir::debug_bound_var(&mut self, debruijn, bound_ty.var)? + } ty::BoundTyKind::Param(_, s) => match self.should_print_verbose() { - true if debruijn == ty::INNERMOST => p!(write("^{}", s)), - true => p!(write("^{}_{}", debruijn.index(), s)), - false => p!(write("{}", s)), + true => p!(write("{:?}", ty.kind())), + false => p!(write("{s}")), }, }, - ty::Adt(def, substs) => { - p!(print_def_path(def.did(), substs)); + ty::Adt(def, args) => { + p!(print_def_path(def.did(), args)); } ty::Dynamic(data, r, repr) => { let print_r = self.should_print_region(r); @@ -730,22 +738,23 @@ pub trait PrettyPrinter<'tcx>: ty::Foreign(def_id) => { p!(print_def_path(def_id, &[])); } - ty::Alias(ty::Projection | ty::Inherent, ref data) => { - if !(self.should_print_verbose() || NO_QUERIES.with(|q| q.get())) + ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ref data) => { + if !(self.should_print_verbose() || with_no_queries()) && self.tcx().is_impl_trait_in_trait(data.def_id) { - return self.pretty_print_opaque_impl_type(data.def_id, data.substs); + return self.pretty_print_opaque_impl_type(data.def_id, data.args); } else { p!(print(data)) } } ty::Placeholder(placeholder) => match placeholder.bound.kind { - ty::BoundTyKind::Anon => { - debug_placeholder_var(&mut self, placeholder.universe, placeholder.bound.var)?; - } - ty::BoundTyKind::Param(_, name) => p!(write("{}", name)), + ty::BoundTyKind::Anon => p!(write("{placeholder:?}")), + ty::BoundTyKind::Param(_, name) => match self.should_print_verbose() { + true => p!(write("{:?}", ty.kind())), + false => p!(write("{name}")), + }, }, - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { // We use verbose printing in 'NO_QUERIES' mode, to // avoid needing to call `predicates_of`. This should // only affect certain debug messages (e.g. messages printed @@ -753,41 +762,41 @@ pub trait PrettyPrinter<'tcx>: // and should have no effect on any compiler output. if self.should_print_verbose() { // FIXME(eddyb) print this with `print_def_path`. - p!(write("Opaque({:?}, {:?})", def_id, substs)); + p!(write("Opaque({:?}, {:?})", def_id, args)); return Ok(self); } let parent = self.tcx().parent(def_id); match self.tcx().def_kind(parent) { - DefKind::TyAlias | DefKind::AssocTy => { + DefKind::TyAlias { .. } | DefKind::AssocTy => { // NOTE: I know we should check for NO_QUERIES here, but it's alright. // `type_of` on a type alias or assoc type should never cause a cycle. if let ty::Alias(ty::Opaque, ty::AliasTy { def_id: d, .. }) = - *self.tcx().type_of(parent).subst_identity().kind() + *self.tcx().type_of(parent).instantiate_identity().kind() { if d == def_id { // If the type alias directly starts with the `impl` of the // opaque type we're printing, then skip the `::{opaque#1}`. - p!(print_def_path(parent, substs)); + p!(print_def_path(parent, args)); return Ok(self); } } // Complex opaque type, e.g. `type Foo = (i32, impl Debug);` - p!(print_def_path(def_id, substs)); + p!(print_def_path(def_id, args)); return Ok(self); } _ => { - if NO_QUERIES.with(|q| q.get()) { + if with_no_queries() { p!(print_def_path(def_id, &[])); return Ok(self); } else { - return self.pretty_print_opaque_impl_type(def_id, substs); + return self.pretty_print_opaque_impl_type(def_id, args); } } } } ty::Str => p!("str"), - ty::Generator(did, substs, movability) => { + ty::Generator(did, args, movability) => { p!(write("[")); let generator_kind = self.tcx().generator_kind(did).unwrap(); let should_print_movability = @@ -812,20 +821,20 @@ pub trait PrettyPrinter<'tcx>: self.tcx().sess.source_map().span_to_embeddable_string(span) )); } else { - p!(write("@"), print_def_path(did, substs)); + p!(write("@"), print_def_path(did, args)); } } else { - p!(print_def_path(did, substs)); + p!(print_def_path(did, args)); p!(" upvar_tys=("); - if !substs.as_generator().is_valid() { + if !args.as_generator().is_valid() { p!("unavailable"); } else { - self = self.comma_sep(substs.as_generator().upvar_tys())?; + self = self.comma_sep(args.as_generator().upvar_tys().iter())?; } p!(")"); - if substs.as_generator().is_valid() { - p!(" ", print(substs.as_generator().witness())); + if args.as_generator().is_valid() { + p!(" ", print(args.as_generator().witness())); } } @@ -834,7 +843,7 @@ pub trait PrettyPrinter<'tcx>: ty::GeneratorWitness(types) => { p!(in_binder(&types)); } - ty::GeneratorWitnessMIR(did, substs) => { + ty::GeneratorWitnessMIR(did, args) => { p!(write("[")); if !self.tcx().sess.verbose() { p!("generator witness"); @@ -848,22 +857,22 @@ pub trait PrettyPrinter<'tcx>: self.tcx().sess.source_map().span_to_embeddable_string(span) )); } else { - p!(write("@"), print_def_path(did, substs)); + p!(write("@"), print_def_path(did, args)); } } else { - p!(print_def_path(did, substs)); + p!(print_def_path(did, args)); } p!("]") } - ty::Closure(did, substs) => { + ty::Closure(did, args) => { p!(write("[")); if !self.should_print_verbose() { p!(write("closure")); // FIXME(eddyb) should use `def_span`. if let Some(did) = did.as_local() { if self.tcx().sess.opts.unstable_opts.span_free_formats { - p!("@", print_def_path(did.to_def_id(), substs)); + p!("@", print_def_path(did.to_def_id(), args)); } else { let span = self.tcx().def_span(did); let preference = if FORCE_TRIMMED_PATH.with(|flag| flag.get()) { @@ -879,21 +888,21 @@ pub trait PrettyPrinter<'tcx>: )); } } else { - p!(write("@"), print_def_path(did, substs)); + p!(write("@"), print_def_path(did, args)); } } else { - p!(print_def_path(did, substs)); - if !substs.as_closure().is_valid() { - p!(" closure_substs=(unavailable)"); - p!(write(" substs={:?}", substs)); + p!(print_def_path(did, args)); + if !args.as_closure().is_valid() { + p!(" closure_args=(unavailable)"); + p!(write(" args={:?}", args)); } else { - p!(" closure_kind_ty=", print(substs.as_closure().kind_ty())); + p!(" closure_kind_ty=", print(args.as_closure().kind_ty())); p!( " closure_sig_as_fn_ptr_ty=", - print(substs.as_closure().sig_as_fn_ptr_ty()) + print(args.as_closure().sig_as_fn_ptr_ty()) ); p!(" upvar_tys=("); - self = self.comma_sep(substs.as_closure().upvar_tys())?; + self = self.comma_sep(args.as_closure().upvar_tys().iter())?; p!(")"); } } @@ -909,7 +918,7 @@ pub trait PrettyPrinter<'tcx>: fn pretty_print_opaque_impl_type( mut self, def_id: DefId, - substs: &'tcx ty::List>, + args: &'tcx ty::List>, ) -> Result { let tcx = self.tcx(); @@ -922,11 +931,11 @@ pub trait PrettyPrinter<'tcx>: let mut is_sized = false; let mut lifetimes = SmallVec::<[ty::Region<'tcx>; 1]>::new(); - for (predicate, _) in bounds.subst_iter_copied(tcx, substs) { + for (predicate, _) in bounds.iter_instantiated_copied(tcx, args) { let bound_predicate = predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { + ty::ClauseKind::Trait(pred) => { let trait_ref = bound_predicate.rebind(pred.trait_ref); // Don't print + Sized, but rather + ?Sized if absent. @@ -937,7 +946,7 @@ pub trait PrettyPrinter<'tcx>: self.insert_trait_and_projection(trait_ref, None, &mut traits, &mut fn_traits); } - ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { + ty::ClauseKind::Projection(pred) => { let proj_ref = bound_predicate.rebind(pred); let trait_ref = proj_ref.required_poly_trait_ref(tcx); @@ -951,7 +960,7 @@ pub trait PrettyPrinter<'tcx>: &mut fn_traits, ); } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(outlives)) => { + ty::ClauseKind::TypeOutlives(outlives) => { lifetimes.push(outlives.1); } _ => {} @@ -972,9 +981,9 @@ pub trait PrettyPrinter<'tcx>: define_scoped_cx!(cx); // Get the (single) generic ty (the args) of this FnOnce trait ref. let generics = tcx.generics_of(trait_ref.def_id); - let args = generics.own_substs_no_defaults(tcx, trait_ref.substs); + let own_args = generics.own_args_no_defaults(tcx, trait_ref.args); - match (entry.return_ty, args[0].expect_ty()) { + match (entry.return_ty, own_args[0].expect_ty()) { // We can only print `impl Fn() -> ()` if we have a tuple of args and we recorded // a return type. (Some(return_ty), arg_tys) if matches!(arg_tys.kind(), ty::Tuple(_)) => { @@ -1038,12 +1047,12 @@ pub trait PrettyPrinter<'tcx>: p!(print(trait_ref.print_only_trait_name())); let generics = tcx.generics_of(trait_ref.def_id); - let args = generics.own_substs_no_defaults(tcx, trait_ref.substs); + let own_args = generics.own_args_no_defaults(tcx, trait_ref.args); - if !args.is_empty() || !assoc_items.is_empty() { + if !own_args.is_empty() || !assoc_items.is_empty() { let mut first = true; - for ty in args { + for ty in own_args { if first { p!("<"); first = false; @@ -1062,8 +1071,8 @@ pub trait PrettyPrinter<'tcx>: && assoc.trait_container(tcx) == tcx.lang_items().gen_trait() && assoc.name == rustc_span::sym::Return { - if let ty::Generator(_, substs, _) = substs.type_at(0).kind() { - let return_ty = substs.as_generator().return_ty(); + if let ty::Generator(_, args, _) = args.type_at(0).kind() { + let return_ty = args.as_generator().return_ty(); if !return_ty.is_ty_var() { return_ty.into() } else { @@ -1176,7 +1185,7 @@ pub trait PrettyPrinter<'tcx>: &def_key.disambiguated_data, ) }, - &alias_ty.substs[1..], + &alias_ty.args[1..], ) } @@ -1205,7 +1214,7 @@ pub trait PrettyPrinter<'tcx>: // Special-case `Fn(...) -> ...` and re-sugar it. let fn_trait_kind = cx.tcx().fn_trait_kind_from_def_id(principal.def_id); if !cx.should_print_verbose() && fn_trait_kind.is_some() { - if let ty::Tuple(tys) = principal.substs.type_at(0).kind() { + if let ty::Tuple(tys) = principal.args.type_at(0).kind() { let mut projections = predicates.projection_bounds(); if let (Some(proj), None) = (projections.next(), projections.next()) { p!(pretty_fn_sig( @@ -1222,13 +1231,13 @@ pub trait PrettyPrinter<'tcx>: // in order to place the projections inside the `<...>`. if !resugared { // Use a type that can't appear in defaults of type parameters. - let dummy_cx = cx.tcx().mk_fresh_ty(0); + let dummy_cx = Ty::new_fresh(cx.tcx(), 0); let principal = principal.with_self_ty(cx.tcx(), dummy_cx); let args = cx .tcx() .generics_of(principal.def_id) - .own_substs_no_defaults(cx.tcx(), principal.substs); + .own_args_no_defaults(cx.tcx(), principal.args); let mut projections = predicates.projection_bounds(); @@ -1335,10 +1344,10 @@ pub trait PrettyPrinter<'tcx>: } match ct.kind() { - ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, substs }) => { + ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, args }) => { match self.tcx().def_kind(def) { DefKind::Const | DefKind::AssocConst => { - p!(print_value_path(def, substs)) + p!(print_value_path(def, args)) } DefKind::AnonConst => { if def.is_local() @@ -1372,15 +1381,13 @@ pub trait PrettyPrinter<'tcx>: } ty::ConstKind::Bound(debruijn, bound_var) => { - debug_bound_var(&mut self, debruijn, bound_var)? + rustc_type_ir::debug_bound_var(&mut self, debruijn, bound_var)? } - ty::ConstKind::Placeholder(placeholder) => { - debug_placeholder_var(&mut self, placeholder.universe, placeholder.bound)?; - }, + ty::ConstKind::Placeholder(placeholder) => p!(write("{placeholder:?}")), // FIXME(generic_const_exprs): // write out some legible representation of an abstract const? - ty::ConstKind::Expr(_) => p!("[const expr]"), - ty::ConstKind::Error(_) => p!("[const error]"), + ty::ConstKind::Expr(_) => p!("{{const expr}}"), + ty::ConstKind::Error(_) => p!("{{const error}}"), }; Ok(self) } @@ -1389,11 +1396,12 @@ pub trait PrettyPrinter<'tcx>: self, scalar: Scalar, ty: Ty<'tcx>, - print_ty: bool, ) -> Result { match scalar { - Scalar::Ptr(ptr, _size) => self.pretty_print_const_scalar_ptr(ptr, ty, print_ty), - Scalar::Int(int) => self.pretty_print_const_scalar_int(int, ty, print_ty), + Scalar::Ptr(ptr, _size) => self.pretty_print_const_scalar_ptr(ptr, ty), + Scalar::Int(int) => { + self.pretty_print_const_scalar_int(int, ty, /* print_ty */ true) + } } } @@ -1401,7 +1409,6 @@ pub trait PrettyPrinter<'tcx>: mut self, ptr: Pointer, ty: Ty<'tcx>, - print_ty: bool, ) -> Result { define_scoped_cx!(self); @@ -1445,7 +1452,7 @@ pub trait PrettyPrinter<'tcx>: self.tcx().try_get_global_alloc(alloc_id) { self = self.typed_value( - |this| this.print_value_path(instance.def_id(), instance.substs), + |this| this.print_value_path(instance.def_id(), instance.args), |this| this.print_type(ty), " as ", )?; @@ -1455,7 +1462,7 @@ pub trait PrettyPrinter<'tcx>: _ => {} } // Any pointer values not covered by a branch above - self = self.pretty_print_const_pointer(ptr, ty, print_ty)?; + self = self.pretty_print_const_pointer(ptr, ty)?; Ok(self) } @@ -1493,7 +1500,7 @@ pub trait PrettyPrinter<'tcx>: let data = int.assert_bits(self.tcx().data_layout.pointer_size); self = self.typed_value( |mut this| { - write!(this, "0x{:x}", data)?; + write!(this, "0x{data:x}")?; Ok(this) }, |this| this.print_type(ty), @@ -1506,7 +1513,7 @@ pub trait PrettyPrinter<'tcx>: if int.size() == Size::ZERO { write!(this, "transmute(())")?; } else { - write!(this, "transmute(0x{:x})", int)?; + write!(this, "transmute(0x{int:x})")?; } Ok(this) }; @@ -1523,24 +1530,18 @@ pub trait PrettyPrinter<'tcx>: /// This is overridden for MIR printing because we only want to hide alloc ids from users, not /// from MIR where it is actually useful. fn pretty_print_const_pointer( - mut self, + self, _: Pointer, ty: Ty<'tcx>, - print_ty: bool, ) -> Result { - if print_ty { - self.typed_value( - |mut this| { - this.write_str("&_")?; - Ok(this) - }, - |this| this.print_type(ty), - ": ", - ) - } else { - self.write_str("&_")?; - Ok(self) - } + self.typed_value( + |mut this| { + this.write_str("&_")?; + Ok(this) + }, + |this| this.print_type(ty), + ": ", + ) } fn pretty_print_byte_str(mut self, byte_str: &'tcx [u8]) -> Result { @@ -1597,7 +1598,8 @@ pub trait PrettyPrinter<'tcx>: } // Aggregates, printed as array/tuple/struct/variant construction syntax. (ty::ValTree::Branch(_), ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) => { - let contents = self.tcx().destructure_const(self.tcx().mk_const(valtree, ty)); + let contents = + self.tcx().destructure_const(ty::Const::new_value(self.tcx(), valtree, ty)); let fields = contents.fields.iter().copied(); match *ty.kind() { ty::Array(..) => { @@ -1620,11 +1622,11 @@ pub trait PrettyPrinter<'tcx>: ": ", )?; } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let variant_idx = contents.variant.expect("destructed const of adt without variant idx"); let variant_def = &def.variant(variant_idx); - p!(print_value_path(variant_def.def_id, substs)); + p!(print_value_path(variant_def.def_id, args)); match variant_def.ctor_kind() { Some(CtorKind::Const) => {} Some(CtorKind::Fn) => { @@ -1674,7 +1676,7 @@ pub trait PrettyPrinter<'tcx>: fn pretty_closure_as_impl( mut self, - closure: ty::ClosureSubsts<'tcx>, + closure: ty::ClosureArgs<'tcx>, ) -> Result { let sig = closure.sig(); let kind = closure.kind_ty().to_opt_closure_kind().unwrap_or(ty::ClosureKind::Fn); @@ -1746,7 +1748,8 @@ impl DerefMut for FmtPrinter<'_, '_> { impl<'a, 'tcx> FmtPrinter<'a, 'tcx> { pub fn new(tcx: TyCtxt<'tcx>, ns: Namespace) -> Self { - Self::new_with_limit(tcx, ns, tcx.type_length_limit()) + let limit = if with_no_queries() { Limit::new(1048576) } else { tcx.type_length_limit() }; + Self::new_with_limit(tcx, ns, limit) } pub fn new_with_limit(tcx: TyCtxt<'tcx>, ns: Namespace, type_length_limit: Limit) -> Self { @@ -1798,29 +1801,29 @@ impl<'t> TyCtxt<'t> { /// Returns a string identifying this `DefId`. This string is /// suitable for user output. pub fn def_path_str(self, def_id: impl IntoQueryParam) -> String { - self.def_path_str_with_substs(def_id, &[]) + self.def_path_str_with_args(def_id, &[]) } - pub fn def_path_str_with_substs( + pub fn def_path_str_with_args( self, def_id: impl IntoQueryParam, - substs: &'t [GenericArg<'t>], + args: &'t [GenericArg<'t>], ) -> String { let def_id = def_id.into_query_param(); let ns = guess_def_namespace(self, def_id); debug!("def_path_str: def_id={:?}, ns={:?}", def_id, ns); - FmtPrinter::new(self, ns).print_def_path(def_id, substs).unwrap().into_buffer() + FmtPrinter::new(self, ns).print_def_path(def_id, args).unwrap().into_buffer() } - pub fn value_path_str_with_substs( + pub fn value_path_str_with_args( self, def_id: impl IntoQueryParam, - substs: &'t [GenericArg<'t>], + args: &'t [GenericArg<'t>], ) -> String { let def_id = def_id.into_query_param(); let ns = guess_def_namespace(self, def_id); debug!("value_path_str: def_id={:?}, ns={:?}", def_id, ns); - FmtPrinter::new(self, ns).print_value_path(def_id, substs).unwrap().into_buffer() + FmtPrinter::new(self, ns).print_value_path(def_id, args).unwrap().into_buffer() } } @@ -1847,11 +1850,11 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> { fn print_def_path( mut self, def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], ) -> Result { define_scoped_cx!(self); - if substs.is_empty() { + if args.is_empty() { match self.try_print_trimmed_def_path(def_id)? { (cx, true) => return Ok(cx), (cx, false) => self = cx, @@ -1900,7 +1903,7 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> { } } - self.default_print_def_path(def_id, substs) + self.default_print_def_path(def_id, args) } fn print_region(self, region: ty::Region<'tcx>) -> Result { @@ -1932,7 +1935,7 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> { fn path_crate(mut self, cnum: CrateNum) -> Result { self.empty_path = true; if cnum == LOCAL_CRATE { - if self.tcx.sess.rust_2018() { + if self.tcx.sess.at_least_rust_2018() { // We add the `crate::` keyword on Rust 2018, only when desired. if SHOULD_PREFIX_WITH_CRATE.with(|flag| flag.get()) { write!(self, "{}", kw::Crate)?; @@ -2017,11 +2020,37 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> { ) -> Result { self = print_prefix(self)?; - if args.first().is_some() { + let tcx = self.tcx; + + let args = args.iter().copied(); + + let args: Vec<_> = if !tcx.sess.verbose() { + // skip host param as those are printed as `~const` + args.filter(|arg| match arg.unpack() { + // FIXME(effects) there should be a better way than just matching the name + GenericArgKind::Const(c) + if tcx.features().effects + && matches!( + c.kind(), + ty::ConstKind::Param(ty::ParamConst { name: sym::host, .. }) + ) => + { + false + } + _ => true, + }) + .collect() + } else { + // If -Zverbose is passed, we should print the host parameter instead + // of eating it. + args.collect() + }; + + if !args.is_empty() { if self.in_value { write!(self, "::")?; } - self.generic_delimiters(|cx| cx.comma_sep(args.iter().cloned())) + self.generic_delimiters(|cx| cx.comma_sep(args.into_iter())) } else { Ok(self) } @@ -2044,10 +2073,10 @@ impl<'tcx> PrettyPrinter<'tcx> for FmtPrinter<'_, 'tcx> { fn print_value_path( mut self, def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], ) -> Result { let was_in_value = std::mem::replace(&mut self.in_value, true); - self = self.print_def_path(def_id, substs)?; + self = self.print_def_path(def_id, args)?; self.in_value = was_in_value; Ok(self) @@ -2150,7 +2179,6 @@ impl<'tcx> PrettyPrinter<'tcx> for FmtPrinter<'_, 'tcx> { self, p: Pointer, ty: Ty<'tcx>, - print_ty: bool, ) -> Result { let print = |mut this: Self| { define_scoped_cx!(this); @@ -2161,11 +2189,7 @@ impl<'tcx> PrettyPrinter<'tcx> for FmtPrinter<'_, 'tcx> { } Ok(this) }; - if print_ty { - self.typed_value(print, |this| this.print_type(ty), ": ") - } else { - print(self) - } + self.typed_value(print, |this| this.print_type(ty), ": ") } } @@ -2303,7 +2327,7 @@ impl<'a, 'tcx> ty::TypeFolder> for RegionFolder<'a, 'tcx> { }; if let ty::ReLateBound(debruijn1, br) = *region { assert_eq!(debruijn1, ty::INNERMOST); - self.tcx.mk_re_late_bound(self.current_index, br) + ty::Region::new_late_bound(self.tcx, self.current_index, br) } else { region } @@ -2353,10 +2377,10 @@ impl<'tcx> FmtPrinter<'_, 'tcx> { } else { cont }; - let _ = write!(cx, "{}", w); + let _ = write!(cx, "{w}"); }; let do_continue = |cx: &mut Self, cont: Symbol| { - let _ = write!(cx, "{}", cont); + let _ = write!(cx, "{cont}"); }; define_scoped_cx!(self); @@ -2392,7 +2416,7 @@ impl<'tcx> FmtPrinter<'_, 'tcx> { let (new_value, map) = if self.should_print_verbose() { for var in value.bound_vars().iter() { start_or_continue(&mut self, "for<", ", "); - write!(self, "{:?}", var)?; + write!(self, "{var:?}")?; } start_or_continue(&mut self, "", "> "); (value.clone().skip_binder(), BTreeMap::default()) @@ -2415,7 +2439,8 @@ impl<'tcx> FmtPrinter<'_, 'tcx> { if let Some(lt_idx) = lifetime_idx { if lt_idx > binder_level_idx { let kind = ty::BrNamed(CRATE_DEF_ID.to_def_id(), name); - return tcx.mk_re_late_bound( + return ty::Region::new_late_bound( + tcx, ty::INNERMOST, ty::BoundRegion { var: br.var, kind }, ); @@ -2430,7 +2455,8 @@ impl<'tcx> FmtPrinter<'_, 'tcx> { if let Some(lt_idx) = lifetime_idx { if lt_idx > binder_level_idx { let kind = ty::BrNamed(def_id, name); - return tcx.mk_re_late_bound( + return ty::Region::new_late_bound( + tcx, ty::INNERMOST, ty::BoundRegion { var: br.var, kind }, ); @@ -2443,7 +2469,8 @@ impl<'tcx> FmtPrinter<'_, 'tcx> { if let Some(lt_idx) = lifetime_idx { if lt_idx > binder_level_idx { let kind = br.kind; - return tcx.mk_re_late_bound( + return ty::Region::new_late_bound( + tcx, ty::INNERMOST, ty::BoundRegion { var: br.var, kind }, ); @@ -2458,7 +2485,11 @@ impl<'tcx> FmtPrinter<'_, 'tcx> { start_or_continue(&mut self, "for<", ", "); do_continue(&mut self, name); } - tcx.mk_re_late_bound(ty::INNERMOST, ty::BoundRegion { var: br.var, kind }) + ty::Region::new_late_bound( + tcx, + ty::INNERMOST, + ty::BoundRegion { var: br.var, kind }, + ) }; let mut folder = RegionFolder { tcx, @@ -2693,7 +2724,7 @@ impl<'tcx> ty::PolyTraitPredicate<'tcx> { #[derive(Debug, Copy, Clone, Lift)] pub struct PrintClosureAsImpl<'tcx> { - pub closure: ty::ClosureSubsts<'tcx>, + pub closure: ty::ClosureArgs<'tcx>, } forward_display_to_print! { @@ -2705,8 +2736,9 @@ forward_display_to_print! { // HACK(eddyb) these are exhaustive instead of generic, // because `for<'tcx>` isn't possible yet. ty::PolyExistentialPredicate<'tcx>, + ty::PolyExistentialProjection<'tcx>, + ty::PolyExistentialTraitRef<'tcx>, ty::Binder<'tcx, ty::TraitRef<'tcx>>, - ty::Binder<'tcx, ty::ExistentialTraitRef<'tcx>>, ty::Binder<'tcx, TraitRefPrintOnlyTraitPath<'tcx>>, ty::Binder<'tcx, TraitRefPrintOnlyTraitName<'tcx>>, ty::Binder<'tcx, ty::FnSig<'tcx>>, @@ -2734,7 +2766,7 @@ define_print_and_forward_display! { ty::ExistentialTraitRef<'tcx> { // Use a type that can't appear in defaults of type parameters. - let dummy_self = cx.tcx().mk_fresh_ty(0); + let dummy_self = Ty::new_fresh(cx.tcx(),0); let trait_ref = self.with_self_ty(cx.tcx(), dummy_self); p!(print(trait_ref.print_only_trait_path())) } @@ -2769,7 +2801,7 @@ define_print_and_forward_display! { } TraitRefPrintOnlyTraitPath<'tcx> { - p!(print_def_path(self.0.def_id, self.0.substs)); + p!(print_def_path(self.0.def_id, self.0.args)); } TraitRefPrintOnlyTraitName<'tcx> { @@ -2777,10 +2809,7 @@ define_print_and_forward_display! { } TraitPredPrintModifiersAndPath<'tcx> { - if let ty::BoundConstness::ConstIfConst = self.0.constness { - p!("~const ") - } - + // FIXME(effects) print `~const` here if let ty::ImplPolarity::Negative = self.0.polarity { p!("!") } @@ -2814,9 +2843,12 @@ define_print_and_forward_display! { ty::TraitPredicate<'tcx> { p!(print(self.trait_ref.self_ty()), ": "); - if let ty::BoundConstness::ConstIfConst = self.constness && cx.tcx().features().const_trait_impl { - p!("~const "); + if let Some(idx) = cx.tcx().generics_of(self.trait_ref.def_id).host_effect_index { + if self.trait_ref.args.const_at(idx) != cx.tcx().consts.true_ { + p!("~const "); + } } + // FIXME(effects) print `~const` here if let ty::ImplPolarity::Negative = self.polarity { p!("!"); } @@ -2840,16 +2872,12 @@ define_print_and_forward_display! { if let DefKind::Impl { of_trait: false } = cx.tcx().def_kind(cx.tcx().parent(self.def_id)) { p!(pretty_print_inherent_projection(self)) } else { - p!(print_def_path(self.def_id, self.substs)); + p!(print_def_path(self.def_id, self.args)); } } ty::ClosureKind { - match *self { - ty::ClosureKind::Fn => p!("Fn"), - ty::ClosureKind::FnMut => p!("FnMut"), - ty::ClosureKind::FnOnce => p!("FnOnce"), - } + p!(write("{}", self.as_str())) } ty::Predicate<'tcx> { @@ -2857,37 +2885,46 @@ define_print_and_forward_display! { p!(print(binder)) } + ty::Clause<'tcx> { + p!(print(self.kind())) + } + + ty::ClauseKind<'tcx> { + match *self { + ty::ClauseKind::Trait(ref data) => { + p!(print(data)) + } + ty::ClauseKind::RegionOutlives(predicate) => p!(print(predicate)), + ty::ClauseKind::TypeOutlives(predicate) => p!(print(predicate)), + ty::ClauseKind::Projection(predicate) => p!(print(predicate)), + ty::ClauseKind::ConstArgHasType(ct, ty) => { + p!("the constant `", print(ct), "` has type `", print(ty), "`") + }, + ty::ClauseKind::WellFormed(arg) => p!(print(arg), " well-formed"), + ty::ClauseKind::ConstEvaluatable(ct) => { + p!("the constant `", print(ct), "` can be evaluated") + } + } + } + ty::PredicateKind<'tcx> { match *self { - ty::PredicateKind::Clause(ty::Clause::Trait(ref data)) => { + ty::PredicateKind::Clause(data) => { p!(print(data)) } ty::PredicateKind::Subtype(predicate) => p!(print(predicate)), ty::PredicateKind::Coerce(predicate) => p!(print(predicate)), - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => p!(print(predicate)), - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => p!(print(predicate)), - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => p!(print(predicate)), - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { - p!("the constant `", print(ct), "` has type `", print(ty), "`") - }, - ty::PredicateKind::WellFormed(arg) => p!(print(arg), " well-formed"), ty::PredicateKind::ObjectSafe(trait_def_id) => { p!("the trait `", print_def_path(trait_def_id, &[]), "` is object-safe") } - ty::PredicateKind::ClosureKind(closure_def_id, _closure_substs, kind) => p!( + ty::PredicateKind::ClosureKind(closure_def_id, _closure_args, kind) => p!( "the closure `", print_value_path(closure_def_id, &[]), write("` implements the trait `{}`", kind) ), - ty::PredicateKind::ConstEvaluatable(ct) => { - p!("the constant `", print(ct), "` can be evaluated") - } ty::PredicateKind::ConstEquate(c1, c2) => { p!("the constant `", print(c1), "` equals `", print(c2), "`") } - ty::PredicateKind::TypeWellFormedFromEnv(ty) => { - p!("the type `", print(ty), "` is found in the environment") - } ty::PredicateKind::Ambiguous => p!("ambiguous"), ty::PredicateKind::AliasRelate(t1, t2, dir) => p!(print(t1), write(" {} ", dir), print(t2)), } @@ -2949,7 +2986,7 @@ fn for_each_def(tcx: TyCtxt<'_>, mut collect_fn: impl for<'b> FnMut(&'b Ident, N match child.res { def::Res::Def(DefKind::AssocTy, _) => {} - def::Res::Def(DefKind::TyAlias, _) => {} + def::Res::Def(DefKind::TyAlias { .. }, _) => {} def::Res::Def(defkind, def_id) => { if let Some(ns) = defkind.ns() { collect_fn(&child.ident, ns, def_id); @@ -2980,7 +3017,7 @@ fn for_each_def(tcx: TyCtxt<'_>, mut collect_fn: impl for<'b> FnMut(&'b Ident, N /// /// The implementation uses similar import discovery logic to that of 'use' suggestions. /// -/// See also [`DelayDm`](rustc_error_messages::DelayDm) and [`with_no_trimmed_paths`]. +/// See also [`DelayDm`](rustc_error_messages::DelayDm) and [`with_no_trimmed_paths!`]. fn trimmed_def_paths(tcx: TyCtxt<'_>, (): ()) -> FxHashMap { let mut map: FxHashMap = FxHashMap::default(); @@ -3065,27 +3102,3 @@ pub struct OpaqueFnEntry<'tcx> { fn_trait_ref: Option>, return_ty: Option>>, } - -pub fn debug_bound_var( - fmt: &mut T, - debruijn: ty::DebruijnIndex, - var: ty::BoundVar, -) -> Result<(), std::fmt::Error> { - if debruijn == ty::INNERMOST { - write!(fmt, "^{}", var.index()) - } else { - write!(fmt, "^{}_{}", debruijn.index(), var.index()) - } -} - -pub fn debug_placeholder_var( - fmt: &mut T, - universe: ty::UniverseIndex, - bound: ty::BoundVar, -) -> Result<(), std::fmt::Error> { - if universe == ty::UniverseIndex::ROOT { - write!(fmt, "!{}", bound.index()) - } else { - write!(fmt, "!{}_{}", universe.index(), bound.index()) - } -} diff --git a/compiler/rustc_middle/src/ty/relate.rs b/compiler/rustc_middle/src/ty/relate.rs index 3bbe6a23b6628..47512d350e546 100644 --- a/compiler/rustc_middle/src/ty/relate.rs +++ b/compiler/rustc_middle/src/ty/relate.rs @@ -6,7 +6,7 @@ use crate::ty::error::{ExpectedFound, TypeError}; use crate::ty::{self, Expr, ImplSubject, Term, TermKind, Ty, TyCtxt, TypeFoldable}; -use crate::ty::{GenericArg, GenericArgKind, SubstsRef}; +use crate::ty::{GenericArg, GenericArgKind, GenericArgsRef}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_target::spec::abi; @@ -43,23 +43,23 @@ pub trait TypeRelation<'tcx>: Sized { Relate::relate(self, a, b) } - /// Relate the two substitutions for the given item. The default + /// Relate the two args for the given item. The default /// is to look up the variance for the item and proceed /// accordingly. - fn relate_item_substs( + fn relate_item_args( &mut self, item_def_id: DefId, - a_subst: SubstsRef<'tcx>, - b_subst: SubstsRef<'tcx>, - ) -> RelateResult<'tcx, SubstsRef<'tcx>> { + a_arg: GenericArgsRef<'tcx>, + b_arg: GenericArgsRef<'tcx>, + ) -> RelateResult<'tcx, GenericArgsRef<'tcx>> { debug!( - "relate_item_substs(item_def_id={:?}, a_subst={:?}, b_subst={:?})", - item_def_id, a_subst, b_subst + "relate_item_args(item_def_id={:?}, a_arg={:?}, b_arg={:?})", + item_def_id, a_arg, b_arg ); let tcx = self.tcx(); let opt_variances = tcx.variances_of(item_def_id); - relate_substs_with_variances(self, item_def_id, opt_variances, a_subst, b_subst, true) + relate_args_with_variances(self, item_def_id, opt_variances, a_arg, b_arg, true) } /// Switch variance for the purpose of relating `a` and `b`. @@ -134,31 +134,32 @@ pub fn relate_type_and_mut<'tcx, R: TypeRelation<'tcx>>( } #[inline] -pub fn relate_substs<'tcx, R: TypeRelation<'tcx>>( +pub fn relate_args<'tcx, R: TypeRelation<'tcx>>( relation: &mut R, - a_subst: SubstsRef<'tcx>, - b_subst: SubstsRef<'tcx>, -) -> RelateResult<'tcx, SubstsRef<'tcx>> { - relation.tcx().mk_substs_from_iter(iter::zip(a_subst, b_subst).map(|(a, b)| { + a_arg: GenericArgsRef<'tcx>, + b_arg: GenericArgsRef<'tcx>, +) -> RelateResult<'tcx, GenericArgsRef<'tcx>> { + relation.tcx().mk_args_from_iter(iter::zip(a_arg, b_arg).map(|(a, b)| { relation.relate_with_variance(ty::Invariant, ty::VarianceDiagInfo::default(), a, b) })) } -pub fn relate_substs_with_variances<'tcx, R: TypeRelation<'tcx>>( +pub fn relate_args_with_variances<'tcx, R: TypeRelation<'tcx>>( relation: &mut R, ty_def_id: DefId, variances: &[ty::Variance], - a_subst: SubstsRef<'tcx>, - b_subst: SubstsRef<'tcx>, + a_arg: GenericArgsRef<'tcx>, + b_arg: GenericArgsRef<'tcx>, fetch_ty_for_diag: bool, -) -> RelateResult<'tcx, SubstsRef<'tcx>> { +) -> RelateResult<'tcx, GenericArgsRef<'tcx>> { let tcx = relation.tcx(); let mut cached_ty = None; - let params = iter::zip(a_subst, b_subst).enumerate().map(|(i, (a, b))| { + let params = iter::zip(a_arg, b_arg).enumerate().map(|(i, (a, b))| { let variance = variances[i]; let variance_info = if variance == ty::Invariant && fetch_ty_for_diag { - let ty = *cached_ty.get_or_insert_with(|| tcx.type_of(ty_def_id).subst(tcx, a_subst)); + let ty = + *cached_ty.get_or_insert_with(|| tcx.type_of(ty_def_id).instantiate(tcx, a_arg)); ty::VarianceDiagInfo::Invariant { ty, param_index: i.try_into().unwrap() } } else { ty::VarianceDiagInfo::default() @@ -166,7 +167,7 @@ pub fn relate_substs_with_variances<'tcx, R: TypeRelation<'tcx>>( relation.relate_with_variance(variance, variance_info, a, b) }); - tcx.mk_substs_from_iter(params) + tcx.mk_args_from_iter(params) } impl<'tcx> Relate<'tcx> for ty::FnSig<'tcx> { @@ -272,8 +273,8 @@ impl<'tcx> Relate<'tcx> for ty::AliasTy<'tcx> { if a.def_id != b.def_id { Err(TypeError::ProjectionMismatched(expected_found(relation, a.def_id, b.def_id))) } else { - let substs = relation.relate(a.substs, b.substs)?; - Ok(relation.tcx().mk_alias_ty(a.def_id, substs)) + let args = relation.relate(a.args, b.args)?; + Ok(relation.tcx().mk_alias_ty(a.def_id, args)) } } } @@ -293,13 +294,13 @@ impl<'tcx> Relate<'tcx> for ty::ExistentialProjection<'tcx> { a.term, b.term, )?; - let substs = relation.relate_with_variance( + let args = relation.relate_with_variance( ty::Invariant, ty::VarianceDiagInfo::default(), - a.substs, - b.substs, + a.args, + b.args, )?; - Ok(ty::ExistentialProjection { def_id: a.def_id, substs, term }) + Ok(ty::ExistentialProjection { def_id: a.def_id, args, term }) } } } @@ -314,8 +315,8 @@ impl<'tcx> Relate<'tcx> for ty::TraitRef<'tcx> { if a.def_id != b.def_id { Err(TypeError::Traits(expected_found(relation, a.def_id, b.def_id))) } else { - let substs = relate_substs(relation, a.substs, b.substs)?; - Ok(ty::TraitRef::new(relation.tcx(), a.def_id, substs)) + let args = relate_args(relation, a.args, b.args)?; + Ok(ty::TraitRef::new(relation.tcx(), a.def_id, args)) } } } @@ -330,8 +331,8 @@ impl<'tcx> Relate<'tcx> for ty::ExistentialTraitRef<'tcx> { if a.def_id != b.def_id { Err(TypeError::Traits(expected_found(relation, a.def_id, b.def_id))) } else { - let substs = relate_substs(relation, a.substs, b.substs)?; - Ok(ty::ExistentialTraitRef { def_id: a.def_id, substs }) + let args = relate_args(relation, a.args, b.args)?; + Ok(ty::ExistentialTraitRef { def_id: a.def_id, args }) } } } @@ -391,13 +392,13 @@ impl<'tcx> Relate<'tcx> for Ty<'tcx> { /// Relates `a` and `b` structurally, calling the relation for all nested values. /// Any semantic equality, e.g. of projections, and inference variables have to be /// handled by the caller. +#[instrument(level = "trace", skip(relation), ret)] pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( relation: &mut R, a: Ty<'tcx>, b: Ty<'tcx>, ) -> RelateResult<'tcx, Ty<'tcx>> { let tcx = relation.tcx(); - debug!("structurally_relate_tys: a={:?} b={:?}", a, b); match (a.kind(), b.kind()) { (&ty::Infer(_), _) | (_, &ty::Infer(_)) => { // The caller should handle these cases! @@ -408,7 +409,7 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( bug!("bound types encountered in structurally_relate_tys") } - (&ty::Error(guar), _) | (_, &ty::Error(guar)) => Ok(tcx.ty_error(guar)), + (&ty::Error(guar), _) | (_, &ty::Error(guar)) => Ok(Ty::new_error(tcx, guar)), (&ty::Never, _) | (&ty::Char, _) @@ -426,12 +427,12 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( (ty::Placeholder(p1), ty::Placeholder(p2)) if p1 == p2 => Ok(a), - (&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs)) if a_def == b_def => { - let substs = relation.relate_item_substs(a_def.did(), a_substs, b_substs)?; - Ok(tcx.mk_adt(a_def, substs)) + (&ty::Adt(a_def, a_args), &ty::Adt(b_def, b_args)) if a_def == b_def => { + let args = relation.relate_item_args(a_def.did(), a_args, b_args)?; + Ok(Ty::new_adt(tcx, a_def, args)) } - (&ty::Foreign(a_id), &ty::Foreign(b_id)) if a_id == b_id => Ok(tcx.mk_foreign(a_id)), + (&ty::Foreign(a_id), &ty::Foreign(b_id)) if a_id == b_id => Ok(Ty::new_foreign(tcx, a_id)), (&ty::Dynamic(a_obj, a_region, a_repr), &ty::Dynamic(b_obj, b_region, b_repr)) if a_repr == b_repr => @@ -439,17 +440,17 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( let region_bound = relation.with_cause(Cause::ExistentialRegionBound, |relation| { relation.relate(a_region, b_region) })?; - Ok(tcx.mk_dynamic(relation.relate(a_obj, b_obj)?, region_bound, a_repr)) + Ok(Ty::new_dynamic(tcx, relation.relate(a_obj, b_obj)?, region_bound, a_repr)) } - (&ty::Generator(a_id, a_substs, movability), &ty::Generator(b_id, b_substs, _)) + (&ty::Generator(a_id, a_args, movability), &ty::Generator(b_id, b_args, _)) if a_id == b_id => { // All Generator types with the same id represent // the (anonymous) type of the same generator expression. So // all of their regions should be equated. - let substs = relation.relate(a_substs, b_substs)?; - Ok(tcx.mk_generator(a_id, substs, movability)) + let args = relation.relate(a_args, b_args)?; + Ok(Ty::new_generator(tcx, a_id, args, movability)) } (&ty::GeneratorWitness(a_types), &ty::GeneratorWitness(b_types)) => { @@ -459,30 +460,30 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( let b_types = b_types.map_bound(GeneratorWitness); // Then remove the GeneratorWitness for the result let types = relation.relate(a_types, b_types)?.map_bound(|witness| witness.0); - Ok(tcx.mk_generator_witness(types)) + Ok(Ty::new_generator_witness(tcx, types)) } - (&ty::GeneratorWitnessMIR(a_id, a_substs), &ty::GeneratorWitnessMIR(b_id, b_substs)) + (&ty::GeneratorWitnessMIR(a_id, a_args), &ty::GeneratorWitnessMIR(b_id, b_args)) if a_id == b_id => { // All GeneratorWitness types with the same id represent // the (anonymous) type of the same generator expression. So // all of their regions should be equated. - let substs = relation.relate(a_substs, b_substs)?; - Ok(tcx.mk_generator_witness_mir(a_id, substs)) + let args = relation.relate(a_args, b_args)?; + Ok(Ty::new_generator_witness_mir(tcx, a_id, args)) } - (&ty::Closure(a_id, a_substs), &ty::Closure(b_id, b_substs)) if a_id == b_id => { + (&ty::Closure(a_id, a_args), &ty::Closure(b_id, b_args)) if a_id == b_id => { // All Closure types with the same id represent // the (anonymous) type of the same closure expression. So // all of their regions should be equated. - let substs = relation.relate(a_substs, b_substs)?; - Ok(tcx.mk_closure(a_id, &substs)) + let args = relation.relate(a_args, b_args)?; + Ok(Ty::new_closure(tcx, a_id, &args)) } (&ty::RawPtr(a_mt), &ty::RawPtr(b_mt)) => { let mt = relate_type_and_mut(relation, a_mt, b_mt, a)?; - Ok(tcx.mk_ptr(mt)) + Ok(Ty::new_ptr(tcx, mt)) } (&ty::Ref(a_r, a_ty, a_mutbl), &ty::Ref(b_r, b_ty, b_mutbl)) => { @@ -490,13 +491,13 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( let a_mt = ty::TypeAndMut { ty: a_ty, mutbl: a_mutbl }; let b_mt = ty::TypeAndMut { ty: b_ty, mutbl: b_mutbl }; let mt = relate_type_and_mut(relation, a_mt, b_mt, a)?; - Ok(tcx.mk_ref(r, mt)) + Ok(Ty::new_ref(tcx, r, mt)) } (&ty::Array(a_t, sz_a), &ty::Array(b_t, sz_b)) => { let t = relation.relate(a_t, b_t)?; match relation.relate(sz_a, sz_b) { - Ok(sz) => Ok(tcx.mk_array_with_const_len(t, sz)), + Ok(sz) => Ok(Ty::new_array_with_const_len(tcx, t, sz)), Err(err) => { // Check whether the lengths are both concrete/known values, // but are unequal, for better diagnostics. @@ -519,12 +520,15 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( (&ty::Slice(a_t), &ty::Slice(b_t)) => { let t = relation.relate(a_t, b_t)?; - Ok(tcx.mk_slice(t)) + Ok(Ty::new_slice(tcx, t)) } (&ty::Tuple(as_), &ty::Tuple(bs)) => { if as_.len() == bs.len() { - Ok(tcx.mk_tup_from_iter(iter::zip(as_, bs).map(|(a, b)| relation.relate(a, b)))?) + Ok(Ty::new_tup_from_iter( + tcx, + iter::zip(as_, bs).map(|(a, b)| relation.relate(a, b)), + )?) } else if !(as_.is_empty() || bs.is_empty()) { Err(TypeError::TupleSize(expected_found(relation, as_.len(), bs.len()))) } else { @@ -532,43 +536,39 @@ pub fn structurally_relate_tys<'tcx, R: TypeRelation<'tcx>>( } } - (&ty::FnDef(a_def_id, a_substs), &ty::FnDef(b_def_id, b_substs)) - if a_def_id == b_def_id => - { - let substs = relation.relate_item_substs(a_def_id, a_substs, b_substs)?; - Ok(tcx.mk_fn_def(a_def_id, substs)) + (&ty::FnDef(a_def_id, a_args), &ty::FnDef(b_def_id, b_args)) if a_def_id == b_def_id => { + let args = relation.relate_item_args(a_def_id, a_args, b_args)?; + Ok(Ty::new_fn_def(tcx, a_def_id, args)) } (&ty::FnPtr(a_fty), &ty::FnPtr(b_fty)) => { let fty = relation.relate(a_fty, b_fty)?; - Ok(tcx.mk_fn_ptr(fty)) - } - - // these two are already handled downstream in case of lazy normalization - (&ty::Alias(ty::Projection, a_data), &ty::Alias(ty::Projection, b_data)) => { - let projection_ty = relation.relate(a_data, b_data)?; - Ok(tcx.mk_projection(projection_ty.def_id, projection_ty.substs)) - } - - (&ty::Alias(ty::Inherent, a_data), &ty::Alias(ty::Inherent, b_data)) => { - let alias_ty = relation.relate(a_data, b_data)?; - Ok(tcx.mk_alias(ty::Inherent, tcx.mk_alias_ty(alias_ty.def_id, alias_ty.substs))) + Ok(Ty::new_fn_ptr(tcx, fty)) } + // The args of opaque types may not all be invariant, so we have + // to treat them separately from other aliases. ( - &ty::Alias(ty::Opaque, ty::AliasTy { def_id: a_def_id, substs: a_substs, .. }), - &ty::Alias(ty::Opaque, ty::AliasTy { def_id: b_def_id, substs: b_substs, .. }), + &ty::Alias(ty::Opaque, ty::AliasTy { def_id: a_def_id, args: a_args, .. }), + &ty::Alias(ty::Opaque, ty::AliasTy { def_id: b_def_id, args: b_args, .. }), ) if a_def_id == b_def_id => { let opt_variances = tcx.variances_of(a_def_id); - let substs = relate_substs_with_variances( + let args = relate_args_with_variances( relation, a_def_id, opt_variances, - a_substs, - b_substs, + a_args, + b_args, false, // do not fetch `type_of(a_def_id)`, as it will cause a cycle )?; - Ok(tcx.mk_opaque(a_def_id, substs)) + Ok(Ty::new_opaque(tcx, a_def_id, args)) + } + + // Alias tend to mostly already be handled downstream due to normalization. + (&ty::Alias(a_kind, a_data), &ty::Alias(b_kind, b_data)) => { + let alias_ty = relation.relate(a_data, b_data)?; + assert_eq!(a_kind, b_kind); + Ok(Ty::new_alias(tcx, a_kind, alias_ty)) } _ => Err(TypeError::Sorts(expected_found(relation, a, b))), @@ -589,17 +589,6 @@ pub fn structurally_relate_consts<'tcx, R: TypeRelation<'tcx>>( debug!("{}.structurally_relate_consts(a = {:?}, b = {:?})", relation.tag(), a, b); let tcx = relation.tcx(); - // HACK(const_generics): We still need to eagerly evaluate consts when - // relating them because during `normalize_param_env_or_error`, - // we may relate an evaluated constant in a obligation against - // an unnormalized (i.e. unevaluated) const in the param-env. - // FIXME(generic_const_exprs): Once we always lazily unify unevaluated constants - // these `eval` calls can be removed. - if !tcx.features().generic_const_exprs { - a = a.eval(tcx, relation.param_env()); - b = b.eval(tcx, relation.param_env()); - } - if tcx.features().generic_const_exprs { a = tcx.expand_abstract_consts(a); b = tcx.expand_abstract_consts(b); @@ -628,13 +617,17 @@ pub fn structurally_relate_consts<'tcx, R: TypeRelation<'tcx>>( // be stabilized. (ty::ConstKind::Unevaluated(au), ty::ConstKind::Unevaluated(bu)) if au.def == bu.def => { assert_eq!(a.ty(), b.ty()); - let substs = relation.relate_with_variance( + let args = relation.relate_with_variance( ty::Variance::Invariant, ty::VarianceDiagInfo::default(), - au.substs, - bu.substs, + au.args, + bu.args, )?; - return Ok(tcx.mk_const(ty::UnevaluatedConst { def: au.def, substs }, a.ty())); + return Ok(ty::Const::new_unevaluated( + tcx, + ty::UnevaluatedConst { def: au.def, args }, + a.ty(), + )); } // Before calling relate on exprs, it is necessary to ensure that the nested consts // have identical types. @@ -646,7 +639,7 @@ pub fn structurally_relate_consts<'tcx, R: TypeRelation<'tcx>>( // FIXME(generic_const_exprs): relating the `ty()`s is a little weird since it is supposed to // ICE If they mismatch. Unfortunately `ConstKind::Expr` is a little special and can be thought // of as being generic over the argument types, however this is implicit so these types don't get - // related when we relate the substs of the item this const arg is for. + // related when we relate the args of the item this const arg is for. let expr = match (ae, be) { (Expr::Binop(a_op, al, ar), Expr::Binop(b_op, bl, br)) if a_op == b_op => { r.relate(al.ty(), bl.ty())?; @@ -675,8 +668,7 @@ pub fn structurally_relate_consts<'tcx, R: TypeRelation<'tcx>>( } _ => return Err(TypeError::ConstMismatch(expected_found(r, a, b))), }; - let kind = ty::ConstKind::Expr(expr); - return Ok(tcx.mk_const(kind, a.ty())); + return Ok(ty::Const::new_expr(tcx, expr, a.ty())); } _ => false, }; @@ -721,35 +713,35 @@ impl<'tcx> Relate<'tcx> for &'tcx ty::List> { } } -impl<'tcx> Relate<'tcx> for ty::ClosureSubsts<'tcx> { +impl<'tcx> Relate<'tcx> for ty::ClosureArgs<'tcx> { fn relate>( relation: &mut R, - a: ty::ClosureSubsts<'tcx>, - b: ty::ClosureSubsts<'tcx>, - ) -> RelateResult<'tcx, ty::ClosureSubsts<'tcx>> { - let substs = relate_substs(relation, a.substs, b.substs)?; - Ok(ty::ClosureSubsts { substs }) + a: ty::ClosureArgs<'tcx>, + b: ty::ClosureArgs<'tcx>, + ) -> RelateResult<'tcx, ty::ClosureArgs<'tcx>> { + let args = relate_args(relation, a.args, b.args)?; + Ok(ty::ClosureArgs { args }) } } -impl<'tcx> Relate<'tcx> for ty::GeneratorSubsts<'tcx> { +impl<'tcx> Relate<'tcx> for ty::GeneratorArgs<'tcx> { fn relate>( relation: &mut R, - a: ty::GeneratorSubsts<'tcx>, - b: ty::GeneratorSubsts<'tcx>, - ) -> RelateResult<'tcx, ty::GeneratorSubsts<'tcx>> { - let substs = relate_substs(relation, a.substs, b.substs)?; - Ok(ty::GeneratorSubsts { substs }) + a: ty::GeneratorArgs<'tcx>, + b: ty::GeneratorArgs<'tcx>, + ) -> RelateResult<'tcx, ty::GeneratorArgs<'tcx>> { + let args = relate_args(relation, a.args, b.args)?; + Ok(ty::GeneratorArgs { args }) } } -impl<'tcx> Relate<'tcx> for SubstsRef<'tcx> { +impl<'tcx> Relate<'tcx> for GenericArgsRef<'tcx> { fn relate>( relation: &mut R, - a: SubstsRef<'tcx>, - b: SubstsRef<'tcx>, - ) -> RelateResult<'tcx, SubstsRef<'tcx>> { - relate_substs(relation, a, b) + a: GenericArgsRef<'tcx>, + b: GenericArgsRef<'tcx>, + ) -> RelateResult<'tcx, GenericArgsRef<'tcx>> { + relate_args(relation, a, b) } } @@ -834,7 +826,6 @@ impl<'tcx> Relate<'tcx> for ty::TraitPredicate<'tcx> { ) -> RelateResult<'tcx, ty::TraitPredicate<'tcx>> { Ok(ty::TraitPredicate { trait_ref: relation.relate(a.trait_ref, b.trait_ref)?, - constness: relation.relate(a.constness, b.constness)?, polarity: relation.relate(a.polarity, b.polarity)?, }) } diff --git a/compiler/rustc_middle/src/ty/rvalue_scopes.rs b/compiler/rustc_middle/src/ty/rvalue_scopes.rs index e79b79a25aec9..17eabec257e93 100644 --- a/compiler/rustc_middle/src/ty/rvalue_scopes.rs +++ b/compiler/rustc_middle/src/ty/rvalue_scopes.rs @@ -1,12 +1,12 @@ use crate::middle::region::{Scope, ScopeData, ScopeTree}; -use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; +use rustc_hir::ItemLocalMap; /// `RvalueScopes` is a mapping from sub-expressions to _extended_ lifetime as determined by /// rules laid out in `rustc_hir_analysis::check::rvalue_scopes`. #[derive(TyEncodable, TyDecodable, Clone, Debug, Default, Eq, PartialEq, HashStable)] pub struct RvalueScopes { - map: FxHashMap>, + map: ItemLocalMap>, } impl RvalueScopes { diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index 16cb6c910463a..f979ddd00fa01 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -11,12 +11,15 @@ use crate::ty::{self, AliasTy, InferConst, Lift, Term, TermKind, Ty, TyCtxt}; use rustc_hir::def::Namespace; use rustc_index::{Idx, IndexVec}; use rustc_target::abi::TyAndLayout; +use rustc_type_ir::{ConstKind, DebugWithInfcx, InferCtxtLike, OptWithInfcx}; -use std::fmt; +use std::fmt::{self, Debug}; use std::ops::ControlFlow; use std::rc::Rc; use std::sync::Arc; +use super::{GenericArg, GenericArgKind, Region}; + impl fmt::Debug for ty::TraitDef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ty::tls::with(|tcx| { @@ -70,9 +73,9 @@ impl fmt::Debug for ty::BoundRegionKind { ty::BrAnon(span) => write!(f, "BrAnon({span:?})"), ty::BrNamed(did, name) => { if did.is_crate_root() { - write!(f, "BrNamed({})", name) + write!(f, "BrNamed({name})") } else { - write!(f, "BrNamed({:?}, {})", did, name) + write!(f, "BrNamed({did:?}, {name})") } } ty::BrEnv => write!(f, "BrEnv"), @@ -88,7 +91,44 @@ impl fmt::Debug for ty::FreeRegion { impl<'tcx> fmt::Debug for ty::FnSig<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "({:?}; c_variadic: {})->{:?}", self.inputs(), self.c_variadic, self.output()) + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} +impl<'tcx> DebugWithInfcx> for ty::FnSig<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + let sig = this.data; + let ty::FnSig { inputs_and_output: _, c_variadic, unsafety, abi } = sig; + + write!(f, "{}", unsafety.prefix_str())?; + match abi { + rustc_target::spec::abi::Abi::Rust => (), + abi => write!(f, "extern \"{abi:?}\" ")?, + }; + + write!(f, "fn(")?; + let inputs = sig.inputs(); + match inputs.len() { + 0 if *c_variadic => write!(f, "...)")?, + 0 => write!(f, ")")?, + _ => { + for ty in &sig.inputs()[0..(sig.inputs().len() - 1)] { + write!(f, "{:?}, ", &this.wrap(ty))?; + } + write!(f, "{:?}", &this.wrap(sig.inputs().last().unwrap()))?; + if *c_variadic { + write!(f, "...")?; + } + write!(f, ")")?; + } + } + + match sig.output().kind() { + ty::Tuple(list) if list.is_empty() => Ok(()), + _ => write!(f, " -> {:?}", &this.wrap(sig.output())), + } } } @@ -104,6 +144,14 @@ impl<'tcx> fmt::Debug for ty::TraitRef<'tcx> { } } +impl<'tcx> ty::DebugWithInfcx> for Ty<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + this.data.fmt(f) + } +} impl<'tcx> fmt::Debug for Ty<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { with_no_trimmed_paths!(fmt::Display::fmt(self, f)) @@ -124,9 +172,7 @@ impl fmt::Debug for ty::ParamConst { impl<'tcx> fmt::Debug for ty::TraitPredicate<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let ty::BoundConstness::ConstIfConst = self.constness { - write!(f, "~const ")?; - } + // FIXME(effects) printing? write!(f, "TraitPredicate({:?}, polarity:{:?})", self.trait_ref, self.polarity) } } @@ -144,13 +190,23 @@ impl<'tcx> fmt::Debug for ty::Predicate<'tcx> { } impl<'tcx> fmt::Debug for ty::Clause<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.kind()) + } +} + +impl<'tcx> fmt::Debug for ty::ClauseKind<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - ty::Clause::ConstArgHasType(ct, ty) => write!(f, "ConstArgHasType({ct:?}, {ty:?})"), - ty::Clause::Trait(ref a) => a.fmt(f), - ty::Clause::RegionOutlives(ref pair) => pair.fmt(f), - ty::Clause::TypeOutlives(ref pair) => pair.fmt(f), - ty::Clause::Projection(ref pair) => pair.fmt(f), + ty::ClauseKind::ConstArgHasType(ct, ty) => write!(f, "ConstArgHasType({ct:?}, {ty:?})"), + ty::ClauseKind::Trait(ref a) => a.fmt(f), + ty::ClauseKind::RegionOutlives(ref pair) => pair.fmt(f), + ty::ClauseKind::TypeOutlives(ref pair) => pair.fmt(f), + ty::ClauseKind::Projection(ref pair) => pair.fmt(f), + ty::ClauseKind::WellFormed(ref data) => write!(f, "WellFormed({data:?})"), + ty::ClauseKind::ConstEvaluatable(ct) => { + write!(f, "ConstEvaluatable({ct:?})") + } } } } @@ -161,20 +217,13 @@ impl<'tcx> fmt::Debug for ty::PredicateKind<'tcx> { ty::PredicateKind::Clause(ref a) => a.fmt(f), ty::PredicateKind::Subtype(ref pair) => pair.fmt(f), ty::PredicateKind::Coerce(ref pair) => pair.fmt(f), - ty::PredicateKind::WellFormed(data) => write!(f, "WellFormed({:?})", data), ty::PredicateKind::ObjectSafe(trait_def_id) => { - write!(f, "ObjectSafe({:?})", trait_def_id) - } - ty::PredicateKind::ClosureKind(closure_def_id, closure_substs, kind) => { - write!(f, "ClosureKind({:?}, {:?}, {:?})", closure_def_id, closure_substs, kind) + write!(f, "ObjectSafe({trait_def_id:?})") } - ty::PredicateKind::ConstEvaluatable(ct) => { - write!(f, "ConstEvaluatable({ct:?})") - } - ty::PredicateKind::ConstEquate(c1, c2) => write!(f, "ConstEquate({:?}, {:?})", c1, c2), - ty::PredicateKind::TypeWellFormedFromEnv(ty) => { - write!(f, "TypeWellFormedFromEnv({:?})", ty) + ty::PredicateKind::ClosureKind(closure_def_id, closure_args, kind) => { + write!(f, "ClosureKind({closure_def_id:?}, {closure_args:?}, {kind:?})") } + ty::PredicateKind::ConstEquate(c1, c2) => write!(f, "ConstEquate({c1:?}, {c2:?})"), ty::PredicateKind::Ambiguous => write!(f, "Ambiguous"), ty::PredicateKind::AliasRelate(t1, t2, dir) => { write!(f, "AliasRelate({t1:?}, {dir:?}, {t2:?})") @@ -185,9 +234,17 @@ impl<'tcx> fmt::Debug for ty::PredicateKind<'tcx> { impl<'tcx> fmt::Debug for AliasTy<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} +impl<'tcx> DebugWithInfcx> for AliasTy<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { f.debug_struct("AliasTy") - .field("substs", &self.substs) - .field("def_id", &self.def_id) + .field("args", &this.map(|data| data.args)) + .field("def_id", &this.data.def_id) .finish() } } @@ -200,36 +257,175 @@ impl<'tcx> fmt::Debug for ty::InferConst<'tcx> { } } } +impl<'tcx> DebugWithInfcx> for ty::InferConst<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + use ty::InferConst::*; + match this.infcx.and_then(|infcx| infcx.universe_of_ct(*this.data)) { + None => write!(f, "{:?}", this.data), + Some(universe) => match *this.data { + Var(vid) => write!(f, "?{}_{}c", vid.index, universe.index()), + Fresh(_) => { + unreachable!() + } + }, + } + } +} + +impl<'tcx> fmt::Debug for ty::consts::Expr<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} +impl<'tcx> DebugWithInfcx> for ty::consts::Expr<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match this.data { + ty::Expr::Binop(op, lhs, rhs) => { + write!(f, "({op:?}: {:?}, {:?})", &this.wrap(lhs), &this.wrap(rhs)) + } + ty::Expr::UnOp(op, rhs) => write!(f, "({op:?}: {:?})", &this.wrap(rhs)), + ty::Expr::FunctionCall(func, args) => { + write!(f, "{:?}(", &this.wrap(func))?; + for arg in args.as_slice().iter().rev().skip(1).rev() { + write!(f, "{:?}, ", &this.wrap(arg))?; + } + if let Some(arg) = args.last() { + write!(f, "{:?}", &this.wrap(arg))?; + } + + write!(f, ")") + } + ty::Expr::Cast(cast_kind, lhs, rhs) => { + write!(f, "({cast_kind:?}: {:?}, {:?})", &this.wrap(lhs), &this.wrap(rhs)) + } + } + } +} + +impl<'tcx> fmt::Debug for ty::UnevaluatedConst<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} +impl<'tcx> DebugWithInfcx> for ty::UnevaluatedConst<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + f.debug_struct("UnevaluatedConst") + .field("def", &this.data.def) + .field("args", &this.wrap(this.data.args)) + .finish() + } +} impl<'tcx> fmt::Debug for ty::Const<'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} +impl<'tcx> DebugWithInfcx> for ty::Const<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { // This reflects what `Const` looked liked before `Interned` was // introduced. We print it like this to avoid having to update expected // output in a lot of tests. - write!(f, "Const {{ ty: {:?}, kind: {:?} }}", self.ty(), self.kind()) + write!( + f, + "Const {{ ty: {:?}, kind: {:?} }}", + &this.map(|data| data.ty()), + &this.map(|data| data.kind()) + ) } } -impl<'tcx> fmt::Debug for ty::ConstKind<'tcx> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use ty::ConstKind::*; - match self { - Param(param) => write!(f, "{param:?}"), - Infer(var) => write!(f, "{var:?}"), - Bound(debruijn, var) => ty::print::debug_bound_var(f, *debruijn, *var), - Placeholder(placeholder) => { - ty::print::debug_placeholder_var(f, placeholder.universe, placeholder.bound) - } - Unevaluated(uv) => { - f.debug_tuple("Unevaluated").field(&uv.substs).field(&uv.def).finish() - } - Value(valtree) => write!(f, "{valtree:?}"), - Error(_) => write!(f, "[const error]"), - Expr(expr) => write!(f, "{expr:?}"), +impl fmt::Debug for ty::BoundTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + ty::BoundTyKind::Anon => write!(f, "{:?}", self.var), + ty::BoundTyKind::Param(_, sym) => write!(f, "{sym:?}"), + } + } +} + +impl fmt::Debug for ty::Placeholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.universe == ty::UniverseIndex::ROOT { + write!(f, "!{:?}", self.bound) + } else { + write!(f, "!{}_{:?}", self.universe.index(), self.bound) + } + } +} + +impl<'tcx> fmt::Debug for GenericArg<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.unpack() { + GenericArgKind::Lifetime(lt) => lt.fmt(f), + GenericArgKind::Type(ty) => ty.fmt(f), + GenericArgKind::Const(ct) => ct.fmt(f), + } + } +} +impl<'tcx> DebugWithInfcx> for GenericArg<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match this.data.unpack() { + GenericArgKind::Lifetime(lt) => write!(f, "{:?}", &this.wrap(lt)), + GenericArgKind::Const(ct) => write!(f, "{:?}", &this.wrap(ct)), + GenericArgKind::Type(ty) => write!(f, "{:?}", &this.wrap(ty)), + } + } +} + +impl<'tcx> fmt::Debug for Region<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.kind()) + } +} +impl<'tcx> DebugWithInfcx> for Region<'tcx> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + write!(f, "{:?}", &this.map(|data| data.kind())) + } +} + +impl<'tcx> DebugWithInfcx> for ty::RegionVid { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match this.infcx.and_then(|infcx| infcx.universe_of_lt(*this.data)) { + Some(universe) => write!(f, "'?{}_{}", this.data.index(), universe.index()), + None => write!(f, "{:?}", this.data), } } } +impl<'tcx, T: DebugWithInfcx>> DebugWithInfcx> for ty::Binder<'tcx, T> { + fn fmt>>( + this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + f.debug_tuple("Binder") + .field(&this.map(|data| data.as_ref().skip_binder())) + .field(&this.data.bound_vars()) + .finish() + } +} + /////////////////////////////////////////////////////////////////////////// // Atomic structs // @@ -242,7 +438,6 @@ CloneLiftImpls! { (), bool, usize, - u8, u16, u32, u64, @@ -272,10 +467,8 @@ TrivialTypeTraversalAndLiftImpls! { ::rustc_hir::Unsafety, ::rustc_target::asm::InlineAsmRegOrRegClass, ::rustc_target::spec::abi::Abi, - crate::mir::coverage::ExpressionOperandId, - crate::mir::coverage::CounterValueReference, - crate::mir::coverage::InjectedExpressionId, - crate::mir::coverage::InjectedExpressionIndex, + crate::mir::coverage::CounterId, + crate::mir::coverage::ExpressionId, crate::mir::coverage::MappedExpressionIndex, crate::mir::Local, crate::mir::Promoted, @@ -294,13 +487,14 @@ TrivialTypeTraversalAndLiftImpls! { crate::ty::AliasRelationDirection, crate::ty::Placeholder, crate::ty::Placeholder, + crate::ty::Placeholder, crate::ty::ClosureKind, crate::ty::FreeRegion, crate::ty::InferTy, crate::ty::IntVarValue, crate::ty::ParamConst, crate::ty::ParamTy, - crate::ty::adjustment::PointerCast, + crate::ty::adjustment::PointerCoercion, crate::ty::RegionVid, crate::ty::UniverseIndex, crate::ty::Variance, @@ -310,7 +504,6 @@ TrivialTypeTraversalAndLiftImpls! { interpret::Scalar, rustc_target::abi::Size, ty::BoundVar, - ty::Placeholder, } TrivialTypeTraversalAndLiftImpls! { @@ -404,7 +597,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::ParamEnv<'a> { type Lifted = ty::ParamEnv<'tcx>; fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option { tcx.lift(self.caller_bounds()) - .map(|caller_bounds| ty::ParamEnv::new(caller_bounds, self.reveal(), self.constness())) + .map(|caller_bounds| ty::ParamEnv::new(caller_bounds, self.reveal())) } } @@ -497,26 +690,26 @@ impl<'tcx> TypeSuperFoldable> for Ty<'tcx> { ty::RawPtr(tm) => ty::RawPtr(tm.try_fold_with(folder)?), ty::Array(typ, sz) => ty::Array(typ.try_fold_with(folder)?, sz.try_fold_with(folder)?), ty::Slice(typ) => ty::Slice(typ.try_fold_with(folder)?), - ty::Adt(tid, substs) => ty::Adt(tid, substs.try_fold_with(folder)?), + ty::Adt(tid, args) => ty::Adt(tid, args.try_fold_with(folder)?), ty::Dynamic(trait_ty, region, representation) => ty::Dynamic( trait_ty.try_fold_with(folder)?, region.try_fold_with(folder)?, representation, ), ty::Tuple(ts) => ty::Tuple(ts.try_fold_with(folder)?), - ty::FnDef(def_id, substs) => ty::FnDef(def_id, substs.try_fold_with(folder)?), + ty::FnDef(def_id, args) => ty::FnDef(def_id, args.try_fold_with(folder)?), ty::FnPtr(f) => ty::FnPtr(f.try_fold_with(folder)?), ty::Ref(r, ty, mutbl) => { ty::Ref(r.try_fold_with(folder)?, ty.try_fold_with(folder)?, mutbl) } - ty::Generator(did, substs, movability) => { - ty::Generator(did, substs.try_fold_with(folder)?, movability) + ty::Generator(did, args, movability) => { + ty::Generator(did, args.try_fold_with(folder)?, movability) } ty::GeneratorWitness(types) => ty::GeneratorWitness(types.try_fold_with(folder)?), - ty::GeneratorWitnessMIR(did, substs) => { - ty::GeneratorWitnessMIR(did, substs.try_fold_with(folder)?) + ty::GeneratorWitnessMIR(did, args) => { + ty::GeneratorWitnessMIR(did, args.try_fold_with(folder)?) } - ty::Closure(did, substs) => ty::Closure(did, substs.try_fold_with(folder)?), + ty::Closure(did, args) => ty::Closure(did, args.try_fold_with(folder)?), ty::Alias(kind, data) => ty::Alias(kind, data.try_fold_with(folder)?), ty::Bool @@ -550,22 +743,22 @@ impl<'tcx> TypeSuperVisitable> for Ty<'tcx> { sz.visit_with(visitor) } ty::Slice(typ) => typ.visit_with(visitor), - ty::Adt(_, substs) => substs.visit_with(visitor), + ty::Adt(_, args) => args.visit_with(visitor), ty::Dynamic(ref trait_ty, ref reg, _) => { trait_ty.visit_with(visitor)?; reg.visit_with(visitor) } ty::Tuple(ts) => ts.visit_with(visitor), - ty::FnDef(_, substs) => substs.visit_with(visitor), + ty::FnDef(_, args) => args.visit_with(visitor), ty::FnPtr(ref f) => f.visit_with(visitor), ty::Ref(r, ty, _) => { r.visit_with(visitor)?; ty.visit_with(visitor) } - ty::Generator(_did, ref substs, _) => substs.visit_with(visitor), + ty::Generator(_did, ref args, _) => args.visit_with(visitor), ty::GeneratorWitness(ref types) => types.visit_with(visitor), - ty::GeneratorWitnessMIR(_did, ref substs) => substs.visit_with(visitor), - ty::Closure(_did, ref substs) => substs.visit_with(visitor), + ty::GeneratorWitnessMIR(_did, ref args) => args.visit_with(visitor), + ty::Closure(_did, ref args) => args.visit_with(visitor), ty::Alias(_, ref data) => data.visit_with(visitor), ty::Bool @@ -609,12 +802,28 @@ impl<'tcx> TypeFoldable> for ty::Predicate<'tcx> { } } +// FIXME(clause): This is wonky +impl<'tcx> TypeFoldable> for ty::Clause<'tcx> { + fn try_fold_with>>( + self, + folder: &mut F, + ) -> Result { + Ok(folder.try_fold_predicate(self.as_predicate())?.expect_clause()) + } +} + impl<'tcx> TypeVisitable> for ty::Predicate<'tcx> { fn visit_with>>(&self, visitor: &mut V) -> ControlFlow { visitor.visit_predicate(*self) } } +impl<'tcx> TypeVisitable> for ty::Clause<'tcx> { + fn visit_with>>(&self, visitor: &mut V) -> ControlFlow { + visitor.visit_predicate(self.as_predicate()) + } +} + impl<'tcx> TypeSuperFoldable> for ty::Predicate<'tcx> { fn try_super_fold_with>>( self, @@ -634,12 +843,12 @@ impl<'tcx> TypeSuperVisitable> for ty::Predicate<'tcx> { } } -impl<'tcx> TypeFoldable> for &'tcx ty::List> { +impl<'tcx> TypeFoldable> for &'tcx ty::List> { fn try_fold_with>>( self, folder: &mut F, ) -> Result { - ty::util::fold_list(self, folder, |tcx, v| tcx.mk_predicates(v)) + ty::util::fold_list(self, folder, |tcx, v| tcx.mk_clauses(v)) } } @@ -664,9 +873,20 @@ impl<'tcx> TypeSuperFoldable> for ty::Const<'tcx> { folder: &mut F, ) -> Result { let ty = self.ty().try_fold_with(folder)?; - let kind = self.kind().try_fold_with(folder)?; + let kind = match self.kind() { + ConstKind::Param(p) => ConstKind::Param(p.try_fold_with(folder)?), + ConstKind::Infer(i) => ConstKind::Infer(i.try_fold_with(folder)?), + ConstKind::Bound(d, b) => { + ConstKind::Bound(d.try_fold_with(folder)?, b.try_fold_with(folder)?) + } + ConstKind::Placeholder(p) => ConstKind::Placeholder(p.try_fold_with(folder)?), + ConstKind::Unevaluated(uv) => ConstKind::Unevaluated(uv.try_fold_with(folder)?), + ConstKind::Value(v) => ConstKind::Value(v.try_fold_with(folder)?), + ConstKind::Error(e) => ConstKind::Error(e.try_fold_with(folder)?), + ConstKind::Expr(e) => ConstKind::Expr(e.try_fold_with(folder)?), + }; if ty != self.ty() || kind != self.kind() { - Ok(folder.interner().mk_const(kind, ty)) + Ok(folder.interner().mk_ct_from_kind(kind, ty)) } else { Ok(self) } @@ -679,7 +899,19 @@ impl<'tcx> TypeSuperVisitable> for ty::Const<'tcx> { visitor: &mut V, ) -> ControlFlow { self.ty().visit_with(visitor)?; - self.kind().visit_with(visitor) + match self.kind() { + ConstKind::Param(p) => p.visit_with(visitor), + ConstKind::Infer(i) => i.visit_with(visitor), + ConstKind::Bound(d, b) => { + d.visit_with(visitor)?; + b.visit_with(visitor) + } + ConstKind::Placeholder(p) => p.visit_with(visitor), + ConstKind::Unevaluated(uv) => uv.visit_with(visitor), + ConstKind::Value(v) => v.visit_with(visitor), + ConstKind::Error(e) => e.visit_with(visitor), + ConstKind::Expr(e) => e.visit_with(visitor), + } } } @@ -706,7 +938,7 @@ impl<'tcx> TypeSuperVisitable> for ty::UnevaluatedConst<'tcx> { &self, visitor: &mut V, ) -> ControlFlow { - self.substs.visit_with(visitor) + self.args.visit_with(visitor) } } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index e6d51c4ec9770..0291cdd6c5799 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -3,28 +3,29 @@ #![allow(rustc::usage_of_ty_tykind)] use crate::infer::canonical::Canonical; -use crate::ty::subst::{GenericArg, InternalSubsts, SubstsRef}; use crate::ty::visit::ValidateBoundVars; use crate::ty::InferTy::*; use crate::ty::{ self, AdtDef, Discr, Term, Ty, TyCtxt, TypeFlags, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, }; +use crate::ty::{GenericArg, GenericArgs, GenericArgsRef}; use crate::ty::{List, ParamEnv}; use hir::def::DefKind; use polonius_engine::Atom; use rustc_data_structures::captures::Captures; use rustc_data_structures::intern::Interned; -use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; +use rustc_errors::{DiagnosticArgValue, ErrorGuaranteed, IntoDiagnosticArg, MultiSpan}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::LangItem; use rustc_index::Idx; use rustc_macros::HashStable; use rustc_span::symbol::{kw, sym, Symbol}; -use rustc_span::Span; +use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT}; use rustc_target::spec::abi::{self, Abi}; +use std::assert_matches::debug_assert_matches; use std::borrow::Cow; use std::cmp::Ordering; use std::fmt; @@ -33,13 +34,20 @@ use std::ops::{ControlFlow, Deref, Range}; use ty::util::IntTypeExt; use rustc_type_ir::sty::TyKind::*; +use rustc_type_ir::CollectAndApply; +use rustc_type_ir::ConstKind as IrConstKind; +use rustc_type_ir::DebugWithInfcx; +use rustc_type_ir::DynKind; use rustc_type_ir::RegionKind as IrRegionKind; use rustc_type_ir::TyKind as IrTyKind; +use super::GenericParamDefKind; + // Re-export the `TyKind` from `rustc_type_ir` here for convenience #[rustc_diagnostic_item = "TyKind"] pub type TyKind<'tcx> = IrTyKind>; pub type RegionKind<'tcx> = IrRegionKind>; +pub type ConstKind<'tcx> = IrConstKind>; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)] #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] @@ -209,7 +217,7 @@ impl<'tcx> Article for TyKind<'tcx> { /// /// ## Generators /// -/// Generators are handled similarly in `GeneratorSubsts`. The set of +/// Generators are handled similarly in `GeneratorArgs`. The set of /// type parameters is similar, but `CK` and `CS` are replaced by the /// following type parameters: /// @@ -222,33 +230,30 @@ impl<'tcx> Article for TyKind<'tcx> { /// completion of the generator. /// * `GW`: The "generator witness". #[derive(Copy, Clone, PartialEq, Eq, Debug, TypeFoldable, TypeVisitable, Lift)] -pub struct ClosureSubsts<'tcx> { +pub struct ClosureArgs<'tcx> { /// Lifetime and type parameters from the enclosing function, /// concatenated with a tuple containing the types of the upvars. /// /// These are separated out because codegen wants to pass them around /// when monomorphizing. - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } /// Struct returned by `split()`. -pub struct ClosureSubstsParts<'tcx, T> { - pub parent_substs: &'tcx [GenericArg<'tcx>], +pub struct ClosureArgsParts<'tcx, T> { + pub parent_args: &'tcx [GenericArg<'tcx>], pub closure_kind_ty: T, pub closure_sig_as_fn_ptr_ty: T, pub tupled_upvars_ty: T, } -impl<'tcx> ClosureSubsts<'tcx> { - /// Construct `ClosureSubsts` from `ClosureSubstsParts`, containing `Substs` +impl<'tcx> ClosureArgs<'tcx> { + /// Construct `ClosureArgs` from `ClosureArgsParts`, containing `Args` /// for the closure parent, alongside additional closure-specific components. - pub fn new( - tcx: TyCtxt<'tcx>, - parts: ClosureSubstsParts<'tcx, Ty<'tcx>>, - ) -> ClosureSubsts<'tcx> { - ClosureSubsts { - substs: tcx.mk_substs_from_iter( - parts.parent_substs.iter().copied().chain( + pub fn new(tcx: TyCtxt<'tcx>, parts: ClosureArgsParts<'tcx, Ty<'tcx>>) -> ClosureArgs<'tcx> { + ClosureArgs { + args: tcx.mk_args_from_iter( + parts.parent_args.iter().copied().chain( [parts.closure_kind_ty, parts.closure_sig_as_fn_ptr_ty, parts.tupled_upvars_ty] .iter() .map(|&ty| ty.into()), @@ -257,53 +262,47 @@ impl<'tcx> ClosureSubsts<'tcx> { } } - /// Divides the closure substs into their respective components. - /// The ordering assumed here must match that used by `ClosureSubsts::new` above. - fn split(self) -> ClosureSubstsParts<'tcx, GenericArg<'tcx>> { - match self.substs[..] { - [ - ref parent_substs @ .., - closure_kind_ty, - closure_sig_as_fn_ptr_ty, - tupled_upvars_ty, - ] => ClosureSubstsParts { - parent_substs, - closure_kind_ty, - closure_sig_as_fn_ptr_ty, - tupled_upvars_ty, - }, - _ => bug!("closure substs missing synthetics"), + /// Divides the closure args into their respective components. + /// The ordering assumed here must match that used by `ClosureArgs::new` above. + fn split(self) -> ClosureArgsParts<'tcx, GenericArg<'tcx>> { + match self.args[..] { + [ref parent_args @ .., closure_kind_ty, closure_sig_as_fn_ptr_ty, tupled_upvars_ty] => { + ClosureArgsParts { + parent_args, + closure_kind_ty, + closure_sig_as_fn_ptr_ty, + tupled_upvars_ty, + } + } + _ => bug!("closure args missing synthetics"), } } /// Returns `true` only if enough of the synthetic types are known to - /// allow using all of the methods on `ClosureSubsts` without panicking. + /// allow using all of the methods on `ClosureArgs` without panicking. /// /// Used primarily by `ty::print::pretty` to be able to handle closure /// types that haven't had their synthetic types substituted in. pub fn is_valid(self) -> bool { - self.substs.len() >= 3 - && matches!(self.split().tupled_upvars_ty.expect_ty().kind(), Tuple(_)) + self.args.len() >= 3 && matches!(self.split().tupled_upvars_ty.expect_ty().kind(), Tuple(_)) } /// Returns the substitutions of the closure's parent. - pub fn parent_substs(self) -> &'tcx [GenericArg<'tcx>] { - self.split().parent_substs + pub fn parent_args(self) -> &'tcx [GenericArg<'tcx>] { + self.split().parent_args } /// Returns an iterator over the list of types of captured paths by the closure. /// In case there was a type error in figuring out the types of the captured path, an /// empty iterator is returned. #[inline] - pub fn upvar_tys(self) -> impl Iterator> + 'tcx { + pub fn upvar_tys(self) -> &'tcx List> { match self.tupled_upvars_ty().kind() { - TyKind::Error(_) => None, - TyKind::Tuple(..) => Some(self.tupled_upvars_ty().tuple_fields()), + TyKind::Error(_) => ty::List::empty(), + TyKind::Tuple(..) => self.tupled_upvars_ty().tuple_fields(), TyKind::Infer(_) => bug!("upvar_tys called before capture types are inferred"), ty => bug!("Unexpected representation of upvar types tuple {:?}", ty), } - .into_iter() - .flatten() } /// Returns the tuple type representing the upvars for this closure. @@ -314,7 +313,7 @@ impl<'tcx> ClosureSubsts<'tcx> { /// Returns the closure kind for this closure; may return a type /// variable during inference. To get the closure kind during - /// inference, use `infcx.closure_kind(substs)`. + /// inference, use `infcx.closure_kind(args)`. pub fn kind_ty(self) -> Ty<'tcx> { self.split().closure_kind_ty.expect_ty() } @@ -322,7 +321,7 @@ impl<'tcx> ClosureSubsts<'tcx> { /// Returns the `fn` pointer type representing the closure signature for this /// closure. // FIXME(eddyb) this should be unnecessary, as the shallowly resolved - // type is known at the time of the creation of `ClosureSubsts`, + // type is known at the time of the creation of `ClosureArgs`, // see `rustc_hir_analysis::check::closure`. pub fn sig_as_fn_ptr_ty(self) -> Ty<'tcx> { self.split().closure_sig_as_fn_ptr_ty.expect_ty() @@ -351,14 +350,14 @@ impl<'tcx> ClosureSubsts<'tcx> { } } -/// Similar to `ClosureSubsts`; see the above documentation for more. +/// Similar to `ClosureArgs`; see the above documentation for more. #[derive(Copy, Clone, PartialEq, Eq, Debug, TypeFoldable, TypeVisitable, Lift)] -pub struct GeneratorSubsts<'tcx> { - pub substs: SubstsRef<'tcx>, +pub struct GeneratorArgs<'tcx> { + pub args: GenericArgsRef<'tcx>, } -pub struct GeneratorSubstsParts<'tcx, T> { - pub parent_substs: &'tcx [GenericArg<'tcx>], +pub struct GeneratorArgsParts<'tcx, T> { + pub parent_args: &'tcx [GenericArg<'tcx>], pub resume_ty: T, pub yield_ty: T, pub return_ty: T, @@ -366,16 +365,16 @@ pub struct GeneratorSubstsParts<'tcx, T> { pub tupled_upvars_ty: T, } -impl<'tcx> GeneratorSubsts<'tcx> { - /// Construct `GeneratorSubsts` from `GeneratorSubstsParts`, containing `Substs` +impl<'tcx> GeneratorArgs<'tcx> { + /// Construct `GeneratorArgs` from `GeneratorArgsParts`, containing `Args` /// for the generator parent, alongside additional generator-specific components. pub fn new( tcx: TyCtxt<'tcx>, - parts: GeneratorSubstsParts<'tcx, Ty<'tcx>>, - ) -> GeneratorSubsts<'tcx> { - GeneratorSubsts { - substs: tcx.mk_substs_from_iter( - parts.parent_substs.iter().copied().chain( + parts: GeneratorArgsParts<'tcx, Ty<'tcx>>, + ) -> GeneratorArgs<'tcx> { + GeneratorArgs { + args: tcx.mk_args_from_iter( + parts.parent_args.iter().copied().chain( [ parts.resume_ty, parts.yield_ty, @@ -390,13 +389,13 @@ impl<'tcx> GeneratorSubsts<'tcx> { } } - /// Divides the generator substs into their respective components. - /// The ordering assumed here must match that used by `GeneratorSubsts::new` above. - fn split(self) -> GeneratorSubstsParts<'tcx, GenericArg<'tcx>> { - match self.substs[..] { - [ref parent_substs @ .., resume_ty, yield_ty, return_ty, witness, tupled_upvars_ty] => { - GeneratorSubstsParts { - parent_substs, + /// Divides the generator args into their respective components. + /// The ordering assumed here must match that used by `GeneratorArgs::new` above. + fn split(self) -> GeneratorArgsParts<'tcx, GenericArg<'tcx>> { + match self.args[..] { + [ref parent_args @ .., resume_ty, yield_ty, return_ty, witness, tupled_upvars_ty] => { + GeneratorArgsParts { + parent_args, resume_ty, yield_ty, return_ty, @@ -404,23 +403,22 @@ impl<'tcx> GeneratorSubsts<'tcx> { tupled_upvars_ty, } } - _ => bug!("generator substs missing synthetics"), + _ => bug!("generator args missing synthetics"), } } /// Returns `true` only if enough of the synthetic types are known to - /// allow using all of the methods on `GeneratorSubsts` without panicking. + /// allow using all of the methods on `GeneratorArgs` without panicking. /// /// Used primarily by `ty::print::pretty` to be able to handle generator /// types that haven't had their synthetic types substituted in. pub fn is_valid(self) -> bool { - self.substs.len() >= 5 - && matches!(self.split().tupled_upvars_ty.expect_ty().kind(), Tuple(_)) + self.args.len() >= 5 && matches!(self.split().tupled_upvars_ty.expect_ty().kind(), Tuple(_)) } /// Returns the substitutions of the generator's parent. - pub fn parent_substs(self) -> &'tcx [GenericArg<'tcx>] { - self.split().parent_substs + pub fn parent_args(self) -> &'tcx [GenericArg<'tcx>] { + self.split().parent_args } /// This describes the types that can be contained in a generator. @@ -436,15 +434,13 @@ impl<'tcx> GeneratorSubsts<'tcx> { /// In case there was a type error in figuring out the types of the captured path, an /// empty iterator is returned. #[inline] - pub fn upvar_tys(self) -> impl Iterator> + 'tcx { + pub fn upvar_tys(self) -> &'tcx List> { match self.tupled_upvars_ty().kind() { - TyKind::Error(_) => None, - TyKind::Tuple(..) => Some(self.tupled_upvars_ty().tuple_fields()), + TyKind::Error(_) => ty::List::empty(), + TyKind::Tuple(..) => self.tupled_upvars_ty().tuple_fields(), TyKind::Infer(_) => bug!("upvar_tys called before capture types are inferred"), ty => bug!("Unexpected representation of upvar types tuple {:?}", ty), } - .into_iter() - .flatten() } /// Returns the tuple type representing the upvars for this generator. @@ -489,7 +485,7 @@ impl<'tcx> GeneratorSubsts<'tcx> { } } -impl<'tcx> GeneratorSubsts<'tcx> { +impl<'tcx> GeneratorArgs<'tcx> { /// Generator has not been resumed yet. pub const UNRESUMED: usize = 0; /// Generator has returned or is completed. @@ -568,7 +564,7 @@ impl<'tcx> GeneratorSubsts<'tcx> { let layout = tcx.generator_layout(def_id).unwrap(); layout.variant_fields.iter().map(move |variant| { variant.iter().map(move |field| { - ty::EarlyBinder(layout.field_tys[*field].ty).subst(tcx, self.substs) + ty::EarlyBinder::bind(layout.field_tys[*field].ty).instantiate(tcx, self.args) }) }) } @@ -576,43 +572,41 @@ impl<'tcx> GeneratorSubsts<'tcx> { /// This is the types of the fields of a generator which are not stored in a /// variant. #[inline] - pub fn prefix_tys(self) -> impl Iterator> { + pub fn prefix_tys(self) -> &'tcx List> { self.upvar_tys() } } #[derive(Debug, Copy, Clone, HashStable)] -pub enum UpvarSubsts<'tcx> { - Closure(SubstsRef<'tcx>), - Generator(SubstsRef<'tcx>), +pub enum UpvarArgs<'tcx> { + Closure(GenericArgsRef<'tcx>), + Generator(GenericArgsRef<'tcx>), } -impl<'tcx> UpvarSubsts<'tcx> { +impl<'tcx> UpvarArgs<'tcx> { /// Returns an iterator over the list of types of captured paths by the closure/generator. /// In case there was a type error in figuring out the types of the captured path, an /// empty iterator is returned. #[inline] - pub fn upvar_tys(self) -> impl Iterator> + 'tcx { + pub fn upvar_tys(self) -> &'tcx List> { let tupled_tys = match self { - UpvarSubsts::Closure(substs) => substs.as_closure().tupled_upvars_ty(), - UpvarSubsts::Generator(substs) => substs.as_generator().tupled_upvars_ty(), + UpvarArgs::Closure(args) => args.as_closure().tupled_upvars_ty(), + UpvarArgs::Generator(args) => args.as_generator().tupled_upvars_ty(), }; match tupled_tys.kind() { - TyKind::Error(_) => None, - TyKind::Tuple(..) => Some(self.tupled_upvars_ty().tuple_fields()), + TyKind::Error(_) => ty::List::empty(), + TyKind::Tuple(..) => self.tupled_upvars_ty().tuple_fields(), TyKind::Infer(_) => bug!("upvar_tys called before capture types are inferred"), ty => bug!("Unexpected representation of upvar types tuple {:?}", ty), } - .into_iter() - .flatten() } #[inline] pub fn tupled_upvars_ty(self) -> Ty<'tcx> { match self { - UpvarSubsts::Closure(substs) => substs.as_closure().tupled_upvars_ty(), - UpvarSubsts::Generator(substs) => substs.as_generator().tupled_upvars_ty(), + UpvarArgs::Closure(args) => args.as_closure().tupled_upvars_ty(), + UpvarArgs::Generator(args) => args.as_generator().tupled_upvars_ty(), } } } @@ -629,46 +623,46 @@ impl<'tcx> UpvarSubsts<'tcx> { /// /// When the inline const is instantiated, `R` is substituted as the actual inferred /// type of the constant. The reason that `R` is represented as an extra type parameter -/// is the same reason that [`ClosureSubsts`] have `CS` and `U` as type parameters: +/// is the same reason that [`ClosureArgs`] have `CS` and `U` as type parameters: /// inline const can reference lifetimes that are internal to the creating function. #[derive(Copy, Clone, Debug)] -pub struct InlineConstSubsts<'tcx> { +pub struct InlineConstArgs<'tcx> { /// Generic parameters from the enclosing item, /// concatenated with the inferred type of the constant. - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } /// Struct returned by `split()`. -pub struct InlineConstSubstsParts<'tcx, T> { - pub parent_substs: &'tcx [GenericArg<'tcx>], +pub struct InlineConstArgsParts<'tcx, T> { + pub parent_args: &'tcx [GenericArg<'tcx>], pub ty: T, } -impl<'tcx> InlineConstSubsts<'tcx> { - /// Construct `InlineConstSubsts` from `InlineConstSubstsParts`. +impl<'tcx> InlineConstArgs<'tcx> { + /// Construct `InlineConstArgs` from `InlineConstArgsParts`. pub fn new( tcx: TyCtxt<'tcx>, - parts: InlineConstSubstsParts<'tcx, Ty<'tcx>>, - ) -> InlineConstSubsts<'tcx> { - InlineConstSubsts { - substs: tcx.mk_substs_from_iter( - parts.parent_substs.iter().copied().chain(std::iter::once(parts.ty.into())), + parts: InlineConstArgsParts<'tcx, Ty<'tcx>>, + ) -> InlineConstArgs<'tcx> { + InlineConstArgs { + args: tcx.mk_args_from_iter( + parts.parent_args.iter().copied().chain(std::iter::once(parts.ty.into())), ), } } - /// Divides the inline const substs into their respective components. - /// The ordering assumed here must match that used by `InlineConstSubsts::new` above. - fn split(self) -> InlineConstSubstsParts<'tcx, GenericArg<'tcx>> { - match self.substs[..] { - [ref parent_substs @ .., ty] => InlineConstSubstsParts { parent_substs, ty }, - _ => bug!("inline const substs missing synthetics"), + /// Divides the inline const args into their respective components. + /// The ordering assumed here must match that used by `InlineConstArgs::new` above. + fn split(self) -> InlineConstArgsParts<'tcx, GenericArg<'tcx>> { + match self.args[..] { + [ref parent_args @ .., ty] => InlineConstArgsParts { parent_args, ty }, + _ => bug!("inline const args missing synthetics"), } } /// Returns the substitutions of the inline const's parent. - pub fn parent_substs(self) -> &'tcx [GenericArg<'tcx>] { - self.split().parent_substs + pub fn parent_args(self) -> &'tcx [GenericArg<'tcx>] { + self.split().parent_args } /// Returns the type of this inline const. @@ -688,6 +682,15 @@ pub enum ExistentialPredicate<'tcx> { AutoTrait(DefId), } +impl<'tcx> DebugWithInfcx> for ExistentialPredicate<'tcx> { + fn fmt>>( + this: rustc_type_ir::OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + fmt::Debug::fmt(&this.data, f) + } +} + impl<'tcx> ExistentialPredicate<'tcx> { /// Compares via an ordering that will not change if modules are reordered or other changes are /// made to the tree. In particular, this ordering is preserved across incremental compilations. @@ -715,11 +718,11 @@ impl<'tcx> PolyExistentialPredicate<'tcx> { /// Given an existential predicate like `?Self: PartialEq` (e.g., derived from `dyn PartialEq`), /// and a concrete type `self_ty`, returns a full predicate where the existentially quantified variable `?Self` /// has been replaced with `self_ty` (e.g., `self_ty: PartialEq`, in our example). - pub fn with_self_ty(&self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> ty::Predicate<'tcx> { + pub fn with_self_ty(&self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> ty::Clause<'tcx> { use crate::ty::ToPredicate; match self.skip_binder() { ExistentialPredicate::Trait(tr) => { - self.rebind(tr).with_self_ty(tcx, self_ty).without_const().to_predicate(tcx) + self.rebind(tr).with_self_ty(tcx, self_ty).to_predicate(tcx) } ExistentialPredicate::Projection(p) => { self.rebind(p.with_self_ty(tcx, self_ty)).to_predicate(tcx) @@ -730,12 +733,11 @@ impl<'tcx> PolyExistentialPredicate<'tcx> { ty::TraitRef::new(tcx, did, [self_ty]) } else { // If this is an ill-formed auto trait, then synthesize - // new error substs for the missing generics. - let err_substs = - ty::InternalSubsts::extend_with_error(tcx, did, &[self_ty.into()]); - ty::TraitRef::new(tcx, did, err_substs) + // new error args for the missing generics. + let err_args = ty::GenericArgs::extend_with_error(tcx, did, &[self_ty.into()]); + ty::TraitRef::new(tcx, did, err_args) }; - self.rebind(trait_ref).without_const().to_predicate(tcx) + self.rebind(trait_ref).to_predicate(tcx) } } } @@ -809,7 +811,7 @@ impl<'tcx> List> { /// T: Foo /// ``` /// This would be represented by a trait-reference where the `DefId` is the -/// `DefId` for the trait `Foo` and the substs define `T` as parameter 0, +/// `DefId` for the trait `Foo` and the args define `T` as parameter 0, /// and `U` as parameter 1. /// /// Trait references also appear in object types like `Foo`, but in @@ -818,7 +820,7 @@ impl<'tcx> List> { #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] pub struct TraitRef<'tcx> { pub def_id: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, /// This field exists to prevent the creation of `TraitRef` without /// calling [`TraitRef::new`]. pub(super) _use_trait_ref_new_instead: (), @@ -828,60 +830,48 @@ impl<'tcx> TraitRef<'tcx> { pub fn new( tcx: TyCtxt<'tcx>, trait_def_id: DefId, - substs: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> Self { - let substs = tcx.check_and_mk_substs(trait_def_id, substs); - Self { def_id: trait_def_id, substs, _use_trait_ref_new_instead: () } + let args = tcx.check_and_mk_args(trait_def_id, args); + Self { def_id: trait_def_id, args, _use_trait_ref_new_instead: () } } pub fn from_lang_item( tcx: TyCtxt<'tcx>, trait_lang_item: LangItem, span: Span, - substs: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> Self { let trait_def_id = tcx.require_lang_item(trait_lang_item, Some(span)); - Self::new(tcx, trait_def_id, substs) + Self::new(tcx, trait_def_id, args) } pub fn from_method( tcx: TyCtxt<'tcx>, trait_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> ty::TraitRef<'tcx> { let defs = tcx.generics_of(trait_id); - ty::TraitRef::new(tcx, trait_id, tcx.mk_substs(&substs[..defs.params.len()])) + ty::TraitRef::new(tcx, trait_id, tcx.mk_args(&args[..defs.params.len()])) } /// Returns a `TraitRef` of the form `P0: Foo` where `Pi` /// are the parameters defined on trait. pub fn identity(tcx: TyCtxt<'tcx>, def_id: DefId) -> TraitRef<'tcx> { - ty::TraitRef::new(tcx, def_id, InternalSubsts::identity_for_item(tcx, def_id)) + ty::TraitRef::new(tcx, def_id, GenericArgs::identity_for_item(tcx, def_id)) } pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { ty::TraitRef::new( tcx, self.def_id, - [self_ty.into()].into_iter().chain(self.substs.iter().skip(1)), + [self_ty.into()].into_iter().chain(self.args.iter().skip(1)), ) } - /// Converts this trait ref to a trait predicate with a given `constness` and a positive polarity. - #[inline] - pub fn with_constness(self, constness: ty::BoundConstness) -> ty::TraitPredicate<'tcx> { - ty::TraitPredicate { trait_ref: self, constness, polarity: ty::ImplPolarity::Positive } - } - - /// Converts this trait ref to a trait predicate without `const` and a positive polarity. - #[inline] - pub fn without_const(self) -> ty::TraitPredicate<'tcx> { - self.with_constness(ty::BoundConstness::NotConst) - } - #[inline] pub fn self_ty(&self) -> Ty<'tcx> { - self.substs.type_at(0) + self.args.type_at(0) } } @@ -914,7 +904,7 @@ impl<'tcx> IntoDiagnosticArg for TraitRef<'tcx> { #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] pub struct ExistentialTraitRef<'tcx> { pub def_id: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, } impl<'tcx> ExistentialTraitRef<'tcx> { @@ -923,11 +913,11 @@ impl<'tcx> ExistentialTraitRef<'tcx> { trait_ref: ty::TraitRef<'tcx>, ) -> ty::ExistentialTraitRef<'tcx> { // Assert there is a Self. - trait_ref.substs.type_at(0); + trait_ref.args.type_at(0); ty::ExistentialTraitRef { def_id: trait_ref.def_id, - substs: tcx.mk_substs(&trait_ref.substs[1..]), + args: tcx.mk_args(&trait_ref.args[1..]), } } @@ -939,7 +929,7 @@ impl<'tcx> ExistentialTraitRef<'tcx> { // otherwise the escaping vars would be captured by the binder // debug_assert!(!self_ty.has_escaping_bound_vars()); - ty::TraitRef::new(tcx, self.def_id, [self_ty.into()].into_iter().chain(self.substs.iter())) + ty::TraitRef::new(tcx, self.def_id, [self_ty.into()].into_iter().chain(self.args.iter())) } } @@ -1007,7 +997,10 @@ impl BoundVariableKind { /// `Decodable` and `Encodable` are implemented for `Binder` using the `impl_binder_encode_decode!` macro. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(HashStable, Lift)] -pub struct Binder<'tcx, T>(T, &'tcx List); +pub struct Binder<'tcx, T> { + value: T, + bound_vars: &'tcx List, +} impl<'tcx, T> Binder<'tcx, T> where @@ -1023,15 +1016,15 @@ where !value.has_escaping_bound_vars(), "`{value:?}` has escaping bound vars, so it cannot be wrapped in a dummy binder." ); - Binder(value, ty::List::empty()) + Binder { value, bound_vars: ty::List::empty() } } - pub fn bind_with_vars(value: T, vars: &'tcx List) -> Binder<'tcx, T> { + pub fn bind_with_vars(value: T, bound_vars: &'tcx List) -> Binder<'tcx, T> { if cfg!(debug_assertions) { - let mut validator = ValidateBoundVars::new(vars); + let mut validator = ValidateBoundVars::new(bound_vars); value.visit_with(&mut validator); } - Binder(value, vars) + Binder { value, bound_vars } } } @@ -1053,30 +1046,30 @@ impl<'tcx, T> Binder<'tcx, T> { /// - comparing the self type of a PolyTraitRef to see if it is equal to /// a type parameter `X`, since the type `X` does not reference any regions pub fn skip_binder(self) -> T { - self.0 + self.value } pub fn bound_vars(&self) -> &'tcx List { - self.1 + self.bound_vars } pub fn as_ref(&self) -> Binder<'tcx, &T> { - Binder(&self.0, self.1) + Binder { value: &self.value, bound_vars: self.bound_vars } } pub fn as_deref(&self) -> Binder<'tcx, &T::Target> where T: Deref, { - Binder(&self.0, self.1) + Binder { value: &self.value, bound_vars: self.bound_vars } } pub fn map_bound_ref_unchecked(&self, f: F) -> Binder<'tcx, U> where F: FnOnce(&T) -> U, { - let value = f(&self.0); - Binder(value, self.1) + let value = f(&self.value); + Binder { value, bound_vars: self.bound_vars } } pub fn map_bound_ref>>(&self, f: F) -> Binder<'tcx, U> @@ -1090,12 +1083,13 @@ impl<'tcx, T> Binder<'tcx, T> { where F: FnOnce(T) -> U, { - let value = f(self.0); + let Binder { value, bound_vars } = self; + let value = f(value); if cfg!(debug_assertions) { - let mut validator = ValidateBoundVars::new(self.1); + let mut validator = ValidateBoundVars::new(bound_vars); value.visit_with(&mut validator); } - Binder(value, self.1) + Binder { value, bound_vars } } pub fn try_map_bound>, E>( @@ -1105,12 +1099,13 @@ impl<'tcx, T> Binder<'tcx, T> { where F: FnOnce(T) -> Result, { - let value = f(self.0)?; + let Binder { value, bound_vars } = self; + let value = f(value)?; if cfg!(debug_assertions) { - let mut validator = ValidateBoundVars::new(self.1); + let mut validator = ValidateBoundVars::new(bound_vars); value.visit_with(&mut validator); } - Ok(Binder(value, self.1)) + Ok(Binder { value, bound_vars }) } /// Wraps a `value` in a binder, using the same bound variables as the @@ -1126,11 +1121,7 @@ impl<'tcx, T> Binder<'tcx, T> { where U: TypeVisitable>, { - if cfg!(debug_assertions) { - let mut validator = ValidateBoundVars::new(self.bound_vars()); - value.visit_with(&mut validator); - } - Binder(value, self.1) + Binder::bind_with_vars(value, self.bound_vars) } /// Unwraps and returns the value within, but only if it contains @@ -1147,7 +1138,7 @@ impl<'tcx, T> Binder<'tcx, T> { where T: TypeVisitable>, { - if self.0.has_escaping_bound_vars() { None } else { Some(self.skip_binder()) } + if self.value.has_escaping_bound_vars() { None } else { Some(self.skip_binder()) } } /// Splits the contents into two things that share the same binder @@ -1160,22 +1151,23 @@ impl<'tcx, T> Binder<'tcx, T> { where F: FnOnce(T) -> (U, V), { - let (u, v) = f(self.0); - (Binder(u, self.1), Binder(v, self.1)) + let Binder { value, bound_vars } = self; + let (u, v) = f(value); + (Binder { value: u, bound_vars }, Binder { value: v, bound_vars }) } } impl<'tcx, T> Binder<'tcx, Option> { pub fn transpose(self) -> Option> { - let bound_vars = self.1; - self.0.map(|v| Binder(v, bound_vars)) + let Binder { value, bound_vars } = self; + value.map(|value| Binder { value, bound_vars }) } } impl<'tcx, T: IntoIterator> Binder<'tcx, T> { pub fn iter(self) -> impl Iterator> { - let bound_vars = self.1; - self.0.into_iter().map(|v| Binder(v, bound_vars)) + let Binder { value, bound_vars } = self; + value.into_iter().map(|value| Binder { value, bound_vars }) } } @@ -1184,7 +1176,7 @@ where T: IntoDiagnosticArg, { fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - self.0.into_diagnostic_arg() + self.value.into_diagnostic_arg() } } @@ -1206,7 +1198,7 @@ pub struct AliasTy<'tcx> { /// /// For RPIT the substitutions are for the generics of the function, /// while for TAIT it is used for the generic parameters of the alias. - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, /// The `DefId` of the `TraitItem` or `ImplItem` for the associated type `N` depending on whether /// this is a projection or an inherent projection or the `DefId` of the `OpaqueType` item if @@ -1229,25 +1221,26 @@ impl<'tcx> AliasTy<'tcx> { pub fn kind(self, tcx: TyCtxt<'tcx>) -> ty::AliasKind { match tcx.def_kind(self.def_id) { DefKind::AssocTy if let DefKind::Impl { of_trait: false } = tcx.def_kind(tcx.parent(self.def_id)) => ty::Inherent, - DefKind::AssocTy | DefKind::ImplTraitPlaceholder => ty::Projection, + DefKind::AssocTy => ty::Projection, DefKind::OpaqueTy => ty::Opaque, + DefKind::TyAlias { .. } => ty::Weak, kind => bug!("unexpected DefKind in AliasTy: {kind:?}"), } } pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { - tcx.mk_alias(self.kind(tcx), self) + Ty::new_alias(tcx, self.kind(tcx), self) } } /// The following methods work only with associated type projections. impl<'tcx> AliasTy<'tcx> { pub fn self_ty(self) -> Ty<'tcx> { - self.substs.type_at(0) + self.args.type_at(0) } pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { - tcx.mk_alias_ty(self.def_id, [self_ty.into()].into_iter().chain(self.substs.iter().skip(1))) + tcx.mk_alias_ty(self.def_id, [self_ty.into()].into_iter().chain(self.args.iter().skip(1))) } } @@ -1256,17 +1249,14 @@ impl<'tcx> AliasTy<'tcx> { pub fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId { match tcx.def_kind(self.def_id) { DefKind::AssocTy | DefKind::AssocConst => tcx.parent(self.def_id), - DefKind::ImplTraitPlaceholder => { - tcx.parent(tcx.impl_trait_in_trait_parent_fn(self.def_id)) - } kind => bug!("expected a projection AliasTy; found {kind:?}"), } } - /// Extracts the underlying trait reference and own substs from this projection. + /// Extracts the underlying trait reference and own args from this projection. /// For example, if this is a projection of `::Item<'a>`, - /// then this function would return a `T: StreamingIterator` trait reference and `['a]` as the own substs - pub fn trait_ref_and_own_substs( + /// then this function would return a `T: StreamingIterator` trait reference and `['a]` as the own args + pub fn trait_ref_and_own_args( self, tcx: TyCtxt<'tcx>, ) -> (ty::TraitRef<'tcx>, &'tcx [ty::GenericArg<'tcx>]) { @@ -1274,8 +1264,8 @@ impl<'tcx> AliasTy<'tcx> { let trait_def_id = self.trait_def_id(tcx); let trait_generics = tcx.generics_of(trait_def_id); ( - ty::TraitRef::new(tcx, trait_def_id, self.substs.truncate_to(tcx, trait_generics)), - &self.substs[trait_generics.count()..], + ty::TraitRef::new(tcx, trait_def_id, self.args.truncate_to(tcx, trait_generics)), + &self.args[trait_generics.count()..], ) } @@ -1283,18 +1273,18 @@ impl<'tcx> AliasTy<'tcx> { /// For example, if this is a projection of `::Item`, /// then this function would return a `T: Iterator` trait reference. /// - /// WARNING: This will drop the substs for generic associated types - /// consider calling [Self::trait_ref_and_own_substs] to get those + /// WARNING: This will drop the args for generic associated types + /// consider calling [Self::trait_ref_and_own_args] to get those /// as well. pub fn trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> { let def_id = self.trait_def_id(tcx); - ty::TraitRef::new(tcx, def_id, self.substs.truncate_to(tcx, tcx.generics_of(def_id))) + ty::TraitRef::new(tcx, def_id, self.args.truncate_to(tcx, tcx.generics_of(def_id))) } } /// The following methods work only with inherent associated type projections. impl<'tcx> AliasTy<'tcx> { - /// Transform the substitutions to have the given `impl` substs as the base and the GAT substs on top of that. + /// Transform the substitutions to have the given `impl` args as the base and the GAT args on top of that. /// /// Does the following transformation: /// @@ -1304,14 +1294,14 @@ impl<'tcx> AliasTy<'tcx> { /// I_i impl subst /// P_j GAT subst /// ``` - pub fn rebase_substs_onto_impl( + pub fn rebase_inherent_args_onto_impl( self, - impl_substs: ty::SubstsRef<'tcx>, + impl_args: ty::GenericArgsRef<'tcx>, tcx: TyCtxt<'tcx>, - ) -> ty::SubstsRef<'tcx> { + ) -> ty::GenericArgsRef<'tcx> { debug_assert_eq!(self.kind(tcx), ty::Inherent); - tcx.mk_substs_from_iter(impl_substs.into_iter().chain(self.substs.into_iter().skip(1))) + tcx.mk_args_from_iter(impl_args.into_iter().chain(self.args.into_iter().skip(1))) } } @@ -1427,7 +1417,7 @@ impl<'tcx> ParamTy { #[inline] pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { - tcx.mk_ty_param(self.index, self.name) + Ty::new_param(tcx, self.index, self.name) } pub fn span_from_generics(&self, tcx: TyCtxt<'tcx>, item_with_generics: DefId) -> Span { @@ -1459,6 +1449,103 @@ impl ParamConst { #[rustc_pass_by_value] pub struct Region<'tcx>(pub Interned<'tcx, RegionKind<'tcx>>); +impl<'tcx> Region<'tcx> { + #[inline] + pub fn new_early_bound( + tcx: TyCtxt<'tcx>, + early_bound_region: ty::EarlyBoundRegion, + ) -> Region<'tcx> { + tcx.intern_region(ty::ReEarlyBound(early_bound_region)) + } + + #[inline] + pub fn new_late_bound( + tcx: TyCtxt<'tcx>, + debruijn: ty::DebruijnIndex, + bound_region: ty::BoundRegion, + ) -> Region<'tcx> { + // Use a pre-interned one when possible. + if let ty::BoundRegion { var, kind: ty::BrAnon(None) } = bound_region + && let Some(inner) = tcx.lifetimes.re_late_bounds.get(debruijn.as_usize()) + && let Some(re) = inner.get(var.as_usize()).copied() + { + re + } else { + tcx.intern_region(ty::ReLateBound(debruijn, bound_region)) + } + } + + #[inline] + pub fn new_free( + tcx: TyCtxt<'tcx>, + scope: DefId, + bound_region: ty::BoundRegionKind, + ) -> Region<'tcx> { + tcx.intern_region(ty::ReFree(ty::FreeRegion { scope, bound_region })) + } + + #[inline] + pub fn new_var(tcx: TyCtxt<'tcx>, v: ty::RegionVid) -> Region<'tcx> { + // Use a pre-interned one when possible. + tcx.lifetimes + .re_vars + .get(v.as_usize()) + .copied() + .unwrap_or_else(|| tcx.intern_region(ty::ReVar(v))) + } + + #[inline] + pub fn new_placeholder(tcx: TyCtxt<'tcx>, placeholder: ty::PlaceholderRegion) -> Region<'tcx> { + tcx.intern_region(ty::RePlaceholder(placeholder)) + } + + /// Constructs a `RegionKind::ReError` region. + #[track_caller] + pub fn new_error(tcx: TyCtxt<'tcx>, reported: ErrorGuaranteed) -> Region<'tcx> { + tcx.intern_region(ty::ReError(reported)) + } + + /// Constructs a `RegionKind::ReError` region and registers a `delay_span_bug` to ensure it + /// gets used. + #[track_caller] + pub fn new_error_misc(tcx: TyCtxt<'tcx>) -> Region<'tcx> { + Region::new_error_with_message( + tcx, + DUMMY_SP, + "RegionKind::ReError constructed but no error reported", + ) + } + + /// Constructs a `RegionKind::ReError` region and registers a `delay_span_bug` with the given + /// `msg` to ensure it gets used. + #[track_caller] + pub fn new_error_with_message>( + tcx: TyCtxt<'tcx>, + span: S, + msg: &'static str, + ) -> Region<'tcx> { + let reported = tcx.sess.delay_span_bug(span, msg); + Region::new_error(tcx, reported) + } + + /// Avoid this in favour of more specific `new_*` methods, where possible, + /// to avoid the cost of the `match`. + pub fn new_from_kind(tcx: TyCtxt<'tcx>, kind: RegionKind<'tcx>) -> Region<'tcx> { + match kind { + ty::ReEarlyBound(region) => Region::new_early_bound(tcx, region), + ty::ReLateBound(debruijn, region) => Region::new_late_bound(tcx, debruijn, region), + ty::ReFree(ty::FreeRegion { scope, bound_region }) => { + Region::new_free(tcx, scope, bound_region) + } + ty::ReStatic => tcx.lifetimes.re_static, + ty::ReVar(vid) => Region::new_var(tcx, vid), + ty::RePlaceholder(region) => Region::new_placeholder(tcx, region), + ty::ReErased => tcx.lifetimes.re_erased, + ty::ReError(reported) => Region::new_error(tcx, reported), + } + } +} + impl<'tcx> Deref for Region<'tcx> { type Target = RegionKind<'tcx>; @@ -1468,12 +1555,6 @@ impl<'tcx> Deref for Region<'tcx> { } } -impl<'tcx> fmt::Debug for Region<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.kind()) - } -} - #[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, PartialOrd, Ord)] #[derive(HashStable)] pub struct EarlyBoundRegion { @@ -1484,7 +1565,7 @@ pub struct EarlyBoundRegion { impl fmt::Debug for EarlyBoundRegion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}, {}", self.index, self.name) + write!(f, "{:?}, {}, {}", self.def_id, self.index, self.name) } } @@ -1511,10 +1592,11 @@ impl Atom for RegionVid { rustc_index::newtype_index! { #[derive(HashStable)] + #[debug_format = "{}"] pub struct BoundVar {} } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, TyEncodable, TyDecodable)] #[derive(HashStable)] pub struct BoundTy { pub var: BoundVar, @@ -1539,7 +1621,7 @@ impl From for BoundTy { #[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] pub struct ExistentialProjection<'tcx> { pub def_id: DefId, - pub substs: SubstsRef<'tcx>, + pub args: GenericArgsRef<'tcx>, pub term: Term<'tcx>, } @@ -1553,8 +1635,8 @@ impl<'tcx> ExistentialProjection<'tcx> { pub fn trait_ref(&self, tcx: TyCtxt<'tcx>) -> ty::ExistentialTraitRef<'tcx> { let def_id = tcx.parent(self.def_id); let subst_count = tcx.generics_of(def_id).count() - 1; - let substs = tcx.mk_substs(&self.substs[..subst_count]); - ty::ExistentialTraitRef { def_id, substs } + let args = tcx.mk_args(&self.args[..subst_count]); + ty::ExistentialTraitRef { def_id, args } } pub fn with_self_ty( @@ -1567,7 +1649,7 @@ impl<'tcx> ExistentialProjection<'tcx> { ty::ProjectionPredicate { projection_ty: tcx - .mk_alias_ty(self.def_id, [self_ty.into()].into_iter().chain(self.substs)), + .mk_alias_ty(self.def_id, [self_ty.into()].into_iter().chain(self.args)), term: self.term, } } @@ -1577,11 +1659,11 @@ impl<'tcx> ExistentialProjection<'tcx> { projection_predicate: ty::ProjectionPredicate<'tcx>, ) -> Self { // Assert there is a Self. - projection_predicate.projection_ty.substs.type_at(0); + projection_predicate.projection_ty.args.type_at(0); Self { def_id: projection_predicate.projection_ty.def_id, - substs: tcx.mk_substs(&projection_predicate.projection_ty.substs[1..]), + args: tcx.mk_args(&projection_predicate.projection_ty.args[1..]), term: projection_predicate.term, } } @@ -1770,6 +1852,388 @@ impl<'tcx> Region<'tcx> { } } +/// Constructors for `Ty` +impl<'tcx> Ty<'tcx> { + // Avoid this in favour of more specific `new_*` methods, where possible. + #[allow(rustc::usage_of_ty_tykind)] + #[inline] + pub fn new(tcx: TyCtxt<'tcx>, st: TyKind<'tcx>) -> Ty<'tcx> { + tcx.mk_ty_from_kind(st) + } + + #[inline] + pub fn new_infer(tcx: TyCtxt<'tcx>, infer: ty::InferTy) -> Ty<'tcx> { + Ty::new(tcx, TyKind::Infer(infer)) + } + + #[inline] + pub fn new_var(tcx: TyCtxt<'tcx>, v: ty::TyVid) -> Ty<'tcx> { + // Use a pre-interned one when possible. + tcx.types + .ty_vars + .get(v.as_usize()) + .copied() + .unwrap_or_else(|| Ty::new(tcx, Infer(TyVar(v)))) + } + + #[inline] + pub fn new_int_var(tcx: TyCtxt<'tcx>, v: ty::IntVid) -> Ty<'tcx> { + Ty::new_infer(tcx, IntVar(v)) + } + + #[inline] + pub fn new_float_var(tcx: TyCtxt<'tcx>, v: ty::FloatVid) -> Ty<'tcx> { + Ty::new_infer(tcx, FloatVar(v)) + } + + #[inline] + pub fn new_fresh(tcx: TyCtxt<'tcx>, n: u32) -> Ty<'tcx> { + // Use a pre-interned one when possible. + tcx.types + .fresh_tys + .get(n as usize) + .copied() + .unwrap_or_else(|| Ty::new_infer(tcx, ty::FreshTy(n))) + } + + #[inline] + pub fn new_fresh_int(tcx: TyCtxt<'tcx>, n: u32) -> Ty<'tcx> { + // Use a pre-interned one when possible. + tcx.types + .fresh_int_tys + .get(n as usize) + .copied() + .unwrap_or_else(|| Ty::new_infer(tcx, ty::FreshIntTy(n))) + } + + #[inline] + pub fn new_fresh_float(tcx: TyCtxt<'tcx>, n: u32) -> Ty<'tcx> { + // Use a pre-interned one when possible. + tcx.types + .fresh_float_tys + .get(n as usize) + .copied() + .unwrap_or_else(|| Ty::new_infer(tcx, ty::FreshFloatTy(n))) + } + + #[inline] + pub fn new_param(tcx: TyCtxt<'tcx>, index: u32, name: Symbol) -> Ty<'tcx> { + tcx.mk_ty_from_kind(Param(ParamTy { index, name })) + } + + #[inline] + pub fn new_bound( + tcx: TyCtxt<'tcx>, + index: ty::DebruijnIndex, + bound_ty: ty::BoundTy, + ) -> Ty<'tcx> { + Ty::new(tcx, Bound(index, bound_ty)) + } + + #[inline] + pub fn new_placeholder(tcx: TyCtxt<'tcx>, placeholder: ty::PlaceholderType) -> Ty<'tcx> { + Ty::new(tcx, Placeholder(placeholder)) + } + + #[inline] + pub fn new_alias( + tcx: TyCtxt<'tcx>, + kind: ty::AliasKind, + alias_ty: ty::AliasTy<'tcx>, + ) -> Ty<'tcx> { + debug_assert_matches!( + (kind, tcx.def_kind(alias_ty.def_id)), + (ty::Opaque, DefKind::OpaqueTy) + | (ty::Projection | ty::Inherent, DefKind::AssocTy) + | (ty::Weak, DefKind::TyAlias { .. }) + ); + Ty::new(tcx, Alias(kind, alias_ty)) + } + + #[inline] + pub fn new_opaque(tcx: TyCtxt<'tcx>, def_id: DefId, args: GenericArgsRef<'tcx>) -> Ty<'tcx> { + Ty::new_alias(tcx, ty::Opaque, tcx.mk_alias_ty(def_id, args)) + } + + /// Constructs a `TyKind::Error` type with current `ErrorGuaranteed` + pub fn new_error(tcx: TyCtxt<'tcx>, reported: ErrorGuaranteed) -> Ty<'tcx> { + Ty::new(tcx, Error(reported)) + } + + /// Constructs a `TyKind::Error` type and registers a `delay_span_bug` to ensure it gets used. + #[track_caller] + pub fn new_misc_error(tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + Ty::new_error_with_message(tcx, DUMMY_SP, "TyKind::Error constructed but no error reported") + } + + /// Constructs a `TyKind::Error` type and registers a `delay_span_bug` with the given `msg` to + /// ensure it gets used. + #[track_caller] + pub fn new_error_with_message>( + tcx: TyCtxt<'tcx>, + span: S, + msg: impl Into, + ) -> Ty<'tcx> { + let reported = tcx.sess.delay_span_bug(span, msg); + Ty::new(tcx, Error(reported)) + } + + #[inline] + pub fn new_int(tcx: TyCtxt<'tcx>, i: ty::IntTy) -> Ty<'tcx> { + use ty::IntTy::*; + match i { + Isize => tcx.types.isize, + I8 => tcx.types.i8, + I16 => tcx.types.i16, + I32 => tcx.types.i32, + I64 => tcx.types.i64, + I128 => tcx.types.i128, + } + } + + #[inline] + pub fn new_uint(tcx: TyCtxt<'tcx>, ui: ty::UintTy) -> Ty<'tcx> { + use ty::UintTy::*; + match ui { + Usize => tcx.types.usize, + U8 => tcx.types.u8, + U16 => tcx.types.u16, + U32 => tcx.types.u32, + U64 => tcx.types.u64, + U128 => tcx.types.u128, + } + } + + #[inline] + pub fn new_float(tcx: TyCtxt<'tcx>, f: ty::FloatTy) -> Ty<'tcx> { + use ty::FloatTy::*; + match f { + F32 => tcx.types.f32, + F64 => tcx.types.f64, + } + } + + #[inline] + pub fn new_ref(tcx: TyCtxt<'tcx>, r: Region<'tcx>, tm: TypeAndMut<'tcx>) -> Ty<'tcx> { + Ty::new(tcx, Ref(r, tm.ty, tm.mutbl)) + } + + #[inline] + pub fn new_mut_ref(tcx: TyCtxt<'tcx>, r: Region<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + Ty::new_ref(tcx, r, TypeAndMut { ty, mutbl: hir::Mutability::Mut }) + } + + #[inline] + pub fn new_imm_ref(tcx: TyCtxt<'tcx>, r: Region<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + Ty::new_ref(tcx, r, TypeAndMut { ty, mutbl: hir::Mutability::Not }) + } + + #[inline] + pub fn new_ptr(tcx: TyCtxt<'tcx>, tm: TypeAndMut<'tcx>) -> Ty<'tcx> { + Ty::new(tcx, RawPtr(tm)) + } + + #[inline] + pub fn new_mut_ptr(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + Ty::new_ptr(tcx, TypeAndMut { ty, mutbl: hir::Mutability::Mut }) + } + + #[inline] + pub fn new_imm_ptr(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + Ty::new_ptr(tcx, TypeAndMut { ty, mutbl: hir::Mutability::Not }) + } + + #[inline] + pub fn new_adt(tcx: TyCtxt<'tcx>, def: AdtDef<'tcx>, args: GenericArgsRef<'tcx>) -> Ty<'tcx> { + Ty::new(tcx, Adt(def, args)) + } + + #[inline] + pub fn new_foreign(tcx: TyCtxt<'tcx>, def_id: DefId) -> Ty<'tcx> { + Ty::new(tcx, Foreign(def_id)) + } + + #[inline] + pub fn new_array(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, n: u64) -> Ty<'tcx> { + Ty::new(tcx, Array(ty, ty::Const::from_target_usize(tcx, n))) + } + + #[inline] + pub fn new_array_with_const_len( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + ct: ty::Const<'tcx>, + ) -> Ty<'tcx> { + Ty::new(tcx, Array(ty, ct)) + } + + #[inline] + pub fn new_slice(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + Ty::new(tcx, Slice(ty)) + } + + #[inline] + pub fn new_tup(tcx: TyCtxt<'tcx>, ts: &[Ty<'tcx>]) -> Ty<'tcx> { + if ts.is_empty() { tcx.types.unit } else { Ty::new(tcx, Tuple(tcx.mk_type_list(&ts))) } + } + + pub fn new_tup_from_iter(tcx: TyCtxt<'tcx>, iter: I) -> T::Output + where + I: Iterator, + T: CollectAndApply, Ty<'tcx>>, + { + T::collect_and_apply(iter, |ts| Ty::new_tup(tcx, ts)) + } + + #[inline] + pub fn new_fn_def( + tcx: TyCtxt<'tcx>, + def_id: DefId, + args: impl IntoIterator>>, + ) -> Ty<'tcx> { + let args = tcx.check_and_mk_args(def_id, args); + Ty::new(tcx, FnDef(def_id, args)) + } + + #[inline] + pub fn new_fn_ptr(tcx: TyCtxt<'tcx>, fty: PolyFnSig<'tcx>) -> Ty<'tcx> { + Ty::new(tcx, FnPtr(fty)) + } + + #[inline] + pub fn new_dynamic( + tcx: TyCtxt<'tcx>, + obj: &'tcx List>, + reg: ty::Region<'tcx>, + repr: DynKind, + ) -> Ty<'tcx> { + Ty::new(tcx, Dynamic(obj, reg, repr)) + } + + #[inline] + pub fn new_projection( + tcx: TyCtxt<'tcx>, + item_def_id: DefId, + args: impl IntoIterator>>, + ) -> Ty<'tcx> { + Ty::new_alias(tcx, ty::Projection, tcx.mk_alias_ty(item_def_id, args)) + } + + #[inline] + pub fn new_closure( + tcx: TyCtxt<'tcx>, + def_id: DefId, + closure_args: GenericArgsRef<'tcx>, + ) -> Ty<'tcx> { + debug_assert_eq!( + closure_args.len(), + tcx.generics_of(tcx.typeck_root_def_id(def_id)).count() + 3, + "closure constructed with incorrect substitutions" + ); + Ty::new(tcx, Closure(def_id, closure_args)) + } + + #[inline] + pub fn new_generator( + tcx: TyCtxt<'tcx>, + def_id: DefId, + generator_args: GenericArgsRef<'tcx>, + movability: hir::Movability, + ) -> Ty<'tcx> { + debug_assert_eq!( + generator_args.len(), + tcx.generics_of(tcx.typeck_root_def_id(def_id)).count() + 5, + "generator constructed with incorrect number of substitutions" + ); + Ty::new(tcx, Generator(def_id, generator_args, movability)) + } + + #[inline] + pub fn new_generator_witness( + tcx: TyCtxt<'tcx>, + types: ty::Binder<'tcx, &'tcx List>>, + ) -> Ty<'tcx> { + Ty::new(tcx, GeneratorWitness(types)) + } + + #[inline] + pub fn new_generator_witness_mir( + tcx: TyCtxt<'tcx>, + id: DefId, + args: GenericArgsRef<'tcx>, + ) -> Ty<'tcx> { + Ty::new(tcx, GeneratorWitnessMIR(id, args)) + } + + // misc + + #[inline] + pub fn new_unit(tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + tcx.types.unit + } + + #[inline] + pub fn new_static_str(tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + Ty::new_imm_ref(tcx, tcx.lifetimes.re_static, tcx.types.str_) + } + + #[inline] + pub fn new_diverging_default(tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + if tcx.features().never_type_fallback { tcx.types.never } else { tcx.types.unit } + } + + // lang and diagnostic tys + + fn new_generic_adt(tcx: TyCtxt<'tcx>, wrapper_def_id: DefId, ty_param: Ty<'tcx>) -> Ty<'tcx> { + let adt_def = tcx.adt_def(wrapper_def_id); + let args = GenericArgs::for_item(tcx, wrapper_def_id, |param, args| match param.kind { + GenericParamDefKind::Lifetime | GenericParamDefKind::Const { .. } => bug!(), + GenericParamDefKind::Type { has_default, .. } => { + if param.index == 0 { + ty_param.into() + } else { + assert!(has_default); + tcx.type_of(param.def_id).instantiate(tcx, args).into() + } + } + }); + Ty::new(tcx, Adt(adt_def, args)) + } + + #[inline] + pub fn new_lang_item(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, item: LangItem) -> Option> { + let def_id = tcx.lang_items().get(item)?; + Some(Ty::new_generic_adt(tcx, def_id, ty)) + } + + #[inline] + pub fn new_diagnostic_item(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, name: Symbol) -> Option> { + let def_id = tcx.get_diagnostic_item(name)?; + Some(Ty::new_generic_adt(tcx, def_id, ty)) + } + + #[inline] + pub fn new_box(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let def_id = tcx.require_lang_item(LangItem::OwnedBox, None); + Ty::new_generic_adt(tcx, def_id, ty) + } + + #[inline] + pub fn new_maybe_uninit(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let def_id = tcx.require_lang_item(LangItem::MaybeUninit, None); + Ty::new_generic_adt(tcx, def_id, ty) + } + + /// Creates a `&mut Context<'_>` [`Ty`] with erased lifetimes. + pub fn new_task_context(tcx: TyCtxt<'tcx>) -> Ty<'tcx> { + let context_did = tcx.require_lang_item(LangItem::Context, None); + let context_adt_ref = tcx.adt_def(context_did); + let context_args = tcx.mk_args(&[tcx.lifetimes.re_erased.into()]); + let context_ty = Ty::new_adt(tcx, context_adt_ref, context_args); + Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, context_ty) + } +} + /// Type utilities impl<'tcx> Ty<'tcx> { #[inline(always)] @@ -1889,10 +2353,10 @@ impl<'tcx> Ty<'tcx> { pub fn simd_size_and_type(self, tcx: TyCtxt<'tcx>) -> (u64, Ty<'tcx>) { match self.kind() { - Adt(def, substs) => { + Adt(def, args) => { assert!(def.repr().simd(), "`simd_size_and_type` called on non-SIMD type"); let variant = def.non_enum_variant(); - let f0_ty = variant.fields[FieldIdx::from_u32(0)].ty(tcx, substs); + let f0_ty = variant.fields[FieldIdx::from_u32(0)].ty(tcx, args); match f0_ty.kind() { // If the first field is an array, we assume it is the only field and its @@ -1953,7 +2417,7 @@ impl<'tcx> Ty<'tcx> { /// Panics if called on any type other than `Box`. pub fn boxed_ty(self) -> Ty<'tcx> { match self.kind() { - Adt(def, substs) if def.is_box() => substs.type_at(0), + Adt(def, args) if def.is_box() => args.type_at(0), _ => bug!("`boxed_ty` is called on non-box type {:?}", self), } } @@ -2117,14 +2581,14 @@ impl<'tcx> Ty<'tcx> { pub fn fn_sig(self, tcx: TyCtxt<'tcx>) -> PolyFnSig<'tcx> { match self.kind() { - FnDef(def_id, substs) => tcx.fn_sig(*def_id).subst(tcx, substs), + FnDef(def_id, args) => tcx.fn_sig(*def_id).instantiate(tcx, args), FnPtr(f) => *f, Error(_) => { // ignore errors (#54954) ty::Binder::dummy(FnSig::fake()) } Closure(..) => bug!( - "to get the signature of a closure, use `substs.as_closure().sig()` not `fn_sig()`", + "to get the signature of a closure, use `args.as_closure().sig()` not `fn_sig()`", ), _ => bug!("Ty::fn_sig() called on non-fn type: {:?}", self), } @@ -2158,7 +2622,7 @@ impl<'tcx> Ty<'tcx> { #[inline] pub fn tuple_fields(self) -> &'tcx List> { match self.kind() { - Tuple(substs) => substs, + Tuple(args) => args, _ => bug!("tuple_fields called on non-tuple"), } } @@ -2170,8 +2634,8 @@ impl<'tcx> Ty<'tcx> { pub fn variant_range(self, tcx: TyCtxt<'tcx>) -> Option> { match self.kind() { TyKind::Adt(adt, _) => Some(adt.variant_range()), - TyKind::Generator(def_id, substs, _) => { - Some(substs.as_generator().variant_range(*def_id, tcx)) + TyKind::Generator(def_id, args, _) => { + Some(args.as_generator().variant_range(*def_id, tcx)) } _ => None, } @@ -2188,16 +2652,11 @@ impl<'tcx> Ty<'tcx> { variant_index: VariantIdx, ) -> Option> { match self.kind() { - TyKind::Adt(adt, _) if adt.variants().is_empty() => { - // This can actually happen during CTFE, see - // https://github.com/rust-lang/rust/issues/89765. - None - } TyKind::Adt(adt, _) if adt.is_enum() => { Some(adt.discriminant_for_variant(tcx, variant_index)) } - TyKind::Generator(def_id, substs, _) => { - Some(substs.as_generator().discriminant_for_variant(*def_id, tcx, variant_index)) + TyKind::Generator(def_id, args, _) => { + Some(args.as_generator().discriminant_for_variant(*def_id, tcx, variant_index)) } _ => None, } @@ -2207,13 +2666,13 @@ impl<'tcx> Ty<'tcx> { pub fn discriminant_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { match self.kind() { ty::Adt(adt, _) if adt.is_enum() => adt.repr().discr_type().to_ty(tcx), - ty::Generator(_, substs, _) => substs.as_generator().discr_ty(tcx), + ty::Generator(_, args, _) => args.as_generator().discr_ty(tcx), ty::Param(_) | ty::Alias(..) | ty::Infer(ty::TyVar(_)) => { let assoc_items = tcx.associated_item_def_ids( tcx.require_lang_item(hir::LangItem::DiscriminantKind, None), ); - tcx.mk_projection(assoc_items[0], tcx.mk_substs(&[self.into()])) + Ty::new_projection(tcx, assoc_items[0], tcx.mk_args(&[self.into()])) } ty::Bool @@ -2286,7 +2745,7 @@ impl<'tcx> Ty<'tcx> { ty::Str | ty::Slice(_) => (tcx.types.usize, false), ty::Dynamic(..) => { let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None); - (tcx.type_of(dyn_metadata).subst(tcx, &[tail.into()]), false) + (tcx.type_of(dyn_metadata).instantiate(tcx, &[tail.into()]), false) }, // type parameters only have unit metadata if they're sized, so return true @@ -2303,7 +2762,7 @@ impl<'tcx> Ty<'tcx> { } /// When we create a closure, we record its kind (i.e., what trait - /// it implements) into its `ClosureSubsts` using a type + /// it implements) into its `ClosureArgs` using a type /// parameter. This is kind of a phantom type, except that the /// most convenient thing for us to are the integral types. This /// function converts such a special type into the closure @@ -2366,13 +2825,13 @@ impl<'tcx> Ty<'tcx> { ty::Tuple(tys) => tys.iter().all(|ty| ty.is_trivially_sized(tcx)), - ty::Adt(def, _substs) => def.sized_constraint(tcx).0.is_empty(), + ty::Adt(def, _args) => def.sized_constraint(tcx).skip_binder().is_empty(), - ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => false, + ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) | ty::Bound(..) => false, ty::Infer(ty::TyVar(_)) => false, - ty::Bound(..) | ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { bug!("`is_trivially_sized` applied to unexpected type: {:?}", self) } } diff --git a/compiler/rustc_middle/src/ty/subst.rs b/compiler/rustc_middle/src/ty/subst.rs deleted file mode 100644 index 43f95635ab00a..0000000000000 --- a/compiler/rustc_middle/src/ty/subst.rs +++ /dev/null @@ -1,1044 +0,0 @@ -// Type substitutions. - -use crate::ty::codec::{TyDecoder, TyEncoder}; -use crate::ty::fold::{FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable}; -use crate::ty::sty::{ClosureSubsts, GeneratorSubsts, InlineConstSubsts}; -use crate::ty::visit::{TypeVisitable, TypeVisitableExt, TypeVisitor}; -use crate::ty::{self, Lift, List, ParamConst, Ty, TyCtxt}; - -use rustc_data_structures::intern::Interned; -use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; -use rustc_hir::def_id::DefId; -use rustc_macros::HashStable; -use rustc_serialize::{self, Decodable, Encodable}; -use rustc_type_ir::WithCachedTypeInfo; -use smallvec::SmallVec; - -use core::intrinsics; -use std::cmp::Ordering; -use std::fmt; -use std::marker::PhantomData; -use std::mem; -use std::num::NonZeroUsize; -use std::ops::{ControlFlow, Deref}; - -/// An entity in the Rust type system, which can be one of -/// several kinds (types, lifetimes, and consts). -/// To reduce memory usage, a `GenericArg` is an interned pointer, -/// with the lowest 2 bits being reserved for a tag to -/// indicate the type (`Ty`, `Region`, or `Const`) it points to. -/// -/// Note: the `PartialEq`, `Eq` and `Hash` derives are only valid because `Ty`, -/// `Region` and `Const` are all interned. -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct GenericArg<'tcx> { - ptr: NonZeroUsize, - marker: PhantomData<(Ty<'tcx>, ty::Region<'tcx>, ty::Const<'tcx>)>, -} - -impl<'tcx> IntoDiagnosticArg for GenericArg<'tcx> { - fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - self.to_string().into_diagnostic_arg() - } -} - -const TAG_MASK: usize = 0b11; -const TYPE_TAG: usize = 0b00; -const REGION_TAG: usize = 0b01; -const CONST_TAG: usize = 0b10; - -#[derive(Debug, TyEncodable, TyDecodable, PartialEq, Eq, PartialOrd, Ord, HashStable)] -pub enum GenericArgKind<'tcx> { - Lifetime(ty::Region<'tcx>), - Type(Ty<'tcx>), - Const(ty::Const<'tcx>), -} - -impl<'tcx> GenericArgKind<'tcx> { - #[inline] - fn pack(self) -> GenericArg<'tcx> { - let (tag, ptr) = match self { - GenericArgKind::Lifetime(lt) => { - // Ensure we can use the tag bits. - assert_eq!(mem::align_of_val(&*lt.0.0) & TAG_MASK, 0); - (REGION_TAG, lt.0.0 as *const ty::RegionKind<'tcx> as usize) - } - GenericArgKind::Type(ty) => { - // Ensure we can use the tag bits. - assert_eq!(mem::align_of_val(&*ty.0.0) & TAG_MASK, 0); - (TYPE_TAG, ty.0.0 as *const WithCachedTypeInfo> as usize) - } - GenericArgKind::Const(ct) => { - // Ensure we can use the tag bits. - assert_eq!(mem::align_of_val(&*ct.0.0) & TAG_MASK, 0); - (CONST_TAG, ct.0.0 as *const ty::ConstData<'tcx> as usize) - } - }; - - GenericArg { ptr: unsafe { NonZeroUsize::new_unchecked(ptr | tag) }, marker: PhantomData } - } -} - -impl<'tcx> fmt::Debug for GenericArg<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.unpack() { - GenericArgKind::Lifetime(lt) => lt.fmt(f), - GenericArgKind::Type(ty) => ty.fmt(f), - GenericArgKind::Const(ct) => ct.fmt(f), - } - } -} - -impl<'tcx> Ord for GenericArg<'tcx> { - fn cmp(&self, other: &GenericArg<'tcx>) -> Ordering { - self.unpack().cmp(&other.unpack()) - } -} - -impl<'tcx> PartialOrd for GenericArg<'tcx> { - fn partial_cmp(&self, other: &GenericArg<'tcx>) -> Option { - Some(self.cmp(&other)) - } -} - -impl<'tcx> From> for GenericArg<'tcx> { - #[inline] - fn from(r: ty::Region<'tcx>) -> GenericArg<'tcx> { - GenericArgKind::Lifetime(r).pack() - } -} - -impl<'tcx> From> for GenericArg<'tcx> { - #[inline] - fn from(ty: Ty<'tcx>) -> GenericArg<'tcx> { - GenericArgKind::Type(ty).pack() - } -} - -impl<'tcx> From> for GenericArg<'tcx> { - #[inline] - fn from(c: ty::Const<'tcx>) -> GenericArg<'tcx> { - GenericArgKind::Const(c).pack() - } -} - -impl<'tcx> From> for GenericArg<'tcx> { - fn from(value: ty::Term<'tcx>) -> Self { - match value.unpack() { - ty::TermKind::Ty(t) => t.into(), - ty::TermKind::Const(c) => c.into(), - } - } -} - -impl<'tcx> GenericArg<'tcx> { - #[inline] - pub fn unpack(self) -> GenericArgKind<'tcx> { - let ptr = self.ptr.get(); - // SAFETY: use of `Interned::new_unchecked` here is ok because these - // pointers were originally created from `Interned` types in `pack()`, - // and this is just going in the other direction. - unsafe { - match ptr & TAG_MASK { - REGION_TAG => GenericArgKind::Lifetime(ty::Region(Interned::new_unchecked( - &*((ptr & !TAG_MASK) as *const ty::RegionKind<'tcx>), - ))), - TYPE_TAG => GenericArgKind::Type(Ty(Interned::new_unchecked( - &*((ptr & !TAG_MASK) as *const WithCachedTypeInfo>), - ))), - CONST_TAG => GenericArgKind::Const(ty::Const(Interned::new_unchecked( - &*((ptr & !TAG_MASK) as *const ty::ConstData<'tcx>), - ))), - _ => intrinsics::unreachable(), - } - } - } - - #[inline] - pub fn as_type(self) -> Option> { - match self.unpack() { - GenericArgKind::Type(ty) => Some(ty), - _ => None, - } - } - - #[inline] - pub fn as_region(self) -> Option> { - match self.unpack() { - GenericArgKind::Lifetime(re) => Some(re), - _ => None, - } - } - - #[inline] - pub fn as_const(self) -> Option> { - match self.unpack() { - GenericArgKind::Const(ct) => Some(ct), - _ => None, - } - } - - /// Unpack the `GenericArg` as a region when it is known certainly to be a region. - pub fn expect_region(self) -> ty::Region<'tcx> { - self.as_region().unwrap_or_else(|| bug!("expected a region, but found another kind")) - } - - /// Unpack the `GenericArg` as a type when it is known certainly to be a type. - /// This is true in cases where `Substs` is used in places where the kinds are known - /// to be limited (e.g. in tuples, where the only parameters are type parameters). - pub fn expect_ty(self) -> Ty<'tcx> { - self.as_type().unwrap_or_else(|| bug!("expected a type, but found another kind")) - } - - /// Unpack the `GenericArg` as a const when it is known certainly to be a const. - pub fn expect_const(self) -> ty::Const<'tcx> { - self.as_const().unwrap_or_else(|| bug!("expected a const, but found another kind")) - } - - pub fn is_non_region_infer(self) -> bool { - match self.unpack() { - GenericArgKind::Lifetime(_) => false, - GenericArgKind::Type(ty) => ty.is_ty_or_numeric_infer(), - GenericArgKind::Const(ct) => ct.is_ct_infer(), - } - } -} - -impl<'a, 'tcx> Lift<'tcx> for GenericArg<'a> { - type Lifted = GenericArg<'tcx>; - - fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option { - match self.unpack() { - GenericArgKind::Lifetime(lt) => tcx.lift(lt).map(|lt| lt.into()), - GenericArgKind::Type(ty) => tcx.lift(ty).map(|ty| ty.into()), - GenericArgKind::Const(ct) => tcx.lift(ct).map(|ct| ct.into()), - } - } -} - -impl<'tcx> TypeFoldable> for GenericArg<'tcx> { - fn try_fold_with>>( - self, - folder: &mut F, - ) -> Result { - match self.unpack() { - GenericArgKind::Lifetime(lt) => lt.try_fold_with(folder).map(Into::into), - GenericArgKind::Type(ty) => ty.try_fold_with(folder).map(Into::into), - GenericArgKind::Const(ct) => ct.try_fold_with(folder).map(Into::into), - } - } -} - -impl<'tcx> TypeVisitable> for GenericArg<'tcx> { - fn visit_with>>(&self, visitor: &mut V) -> ControlFlow { - match self.unpack() { - GenericArgKind::Lifetime(lt) => lt.visit_with(visitor), - GenericArgKind::Type(ty) => ty.visit_with(visitor), - GenericArgKind::Const(ct) => ct.visit_with(visitor), - } - } -} - -impl<'tcx, E: TyEncoder>> Encodable for GenericArg<'tcx> { - fn encode(&self, e: &mut E) { - self.unpack().encode(e) - } -} - -impl<'tcx, D: TyDecoder>> Decodable for GenericArg<'tcx> { - fn decode(d: &mut D) -> GenericArg<'tcx> { - GenericArgKind::decode(d).pack() - } -} - -/// List of generic arguments that are gonna be used to substitute generic parameters. -pub type InternalSubsts<'tcx> = List>; - -pub type SubstsRef<'tcx> = &'tcx InternalSubsts<'tcx>; - -impl<'tcx> InternalSubsts<'tcx> { - /// Converts substs to a type list. - /// - /// # Panics - /// - /// If any of the generic arguments are not types. - pub fn into_type_list(&self, tcx: TyCtxt<'tcx>) -> &'tcx List> { - tcx.mk_type_list_from_iter(self.iter().map(|arg| match arg.unpack() { - GenericArgKind::Type(ty) => ty, - _ => bug!("`into_type_list` called on substs with non-types"), - })) - } - - /// Interpret these substitutions as the substitutions of a closure type. - /// Closure substitutions have a particular structure controlled by the - /// compiler that encodes information like the signature and closure kind; - /// see `ty::ClosureSubsts` struct for more comments. - pub fn as_closure(&'tcx self) -> ClosureSubsts<'tcx> { - ClosureSubsts { substs: self } - } - - /// Interpret these substitutions as the substitutions of a generator type. - /// Generator substitutions have a particular structure controlled by the - /// compiler that encodes information like the signature and generator kind; - /// see `ty::GeneratorSubsts` struct for more comments. - pub fn as_generator(&'tcx self) -> GeneratorSubsts<'tcx> { - GeneratorSubsts { substs: self } - } - - /// Interpret these substitutions as the substitutions of an inline const. - /// Inline const substitutions have a particular structure controlled by the - /// compiler that encodes information like the inferred type; - /// see `ty::InlineConstSubsts` struct for more comments. - pub fn as_inline_const(&'tcx self) -> InlineConstSubsts<'tcx> { - InlineConstSubsts { substs: self } - } - - /// Creates an `InternalSubsts` that maps each generic parameter to itself. - pub fn identity_for_item(tcx: TyCtxt<'tcx>, def_id: impl Into) -> SubstsRef<'tcx> { - Self::for_item(tcx, def_id.into(), |param, _| tcx.mk_param_from_def(param)) - } - - /// Creates an `InternalSubsts` for generic parameter definitions, - /// by calling closures to obtain each kind. - /// The closures get to observe the `InternalSubsts` as they're - /// being built, which can be used to correctly - /// substitute defaults of generic parameters. - pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId, mut mk_kind: F) -> SubstsRef<'tcx> - where - F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, - { - let defs = tcx.generics_of(def_id); - let count = defs.count(); - let mut substs = SmallVec::with_capacity(count); - Self::fill_item(&mut substs, tcx, defs, &mut mk_kind); - tcx.mk_substs(&substs) - } - - pub fn extend_to(&self, tcx: TyCtxt<'tcx>, def_id: DefId, mut mk_kind: F) -> SubstsRef<'tcx> - where - F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, - { - Self::for_item(tcx, def_id, |param, substs| { - self.get(param.index as usize).cloned().unwrap_or_else(|| mk_kind(param, substs)) - }) - } - - pub fn fill_item( - substs: &mut SmallVec<[GenericArg<'tcx>; 8]>, - tcx: TyCtxt<'tcx>, - defs: &ty::Generics, - mk_kind: &mut F, - ) where - F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, - { - if let Some(def_id) = defs.parent { - let parent_defs = tcx.generics_of(def_id); - Self::fill_item(substs, tcx, parent_defs, mk_kind); - } - Self::fill_single(substs, defs, mk_kind) - } - - pub fn fill_single( - substs: &mut SmallVec<[GenericArg<'tcx>; 8]>, - defs: &ty::Generics, - mk_kind: &mut F, - ) where - F: FnMut(&ty::GenericParamDef, &[GenericArg<'tcx>]) -> GenericArg<'tcx>, - { - substs.reserve(defs.params.len()); - for param in &defs.params { - let kind = mk_kind(param, substs); - assert_eq!(param.index as usize, substs.len(), "{substs:#?}, {defs:#?}"); - substs.push(kind); - } - } - - // Extend an `original_substs` list to the full number of substs expected by `def_id`, - // filling in the missing parameters with error ty/ct or 'static regions. - pub fn extend_with_error( - tcx: TyCtxt<'tcx>, - def_id: DefId, - original_substs: &[GenericArg<'tcx>], - ) -> SubstsRef<'tcx> { - ty::InternalSubsts::for_item(tcx, def_id, |def, substs| { - if let Some(subst) = original_substs.get(def.index as usize) { - *subst - } else { - def.to_error(tcx, substs) - } - }) - } - - #[inline] - pub fn types(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { - self.iter().filter_map(|k| k.as_type()) - } - - #[inline] - pub fn regions(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { - self.iter().filter_map(|k| k.as_region()) - } - - #[inline] - pub fn consts(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { - self.iter().filter_map(|k| k.as_const()) - } - - #[inline] - pub fn non_erasable_generics( - &'tcx self, - ) -> impl DoubleEndedIterator> + 'tcx { - self.iter().filter_map(|k| match k.unpack() { - GenericArgKind::Lifetime(_) => None, - generic => Some(generic), - }) - } - - #[inline] - #[track_caller] - pub fn type_at(&self, i: usize) -> Ty<'tcx> { - self[i].as_type().unwrap_or_else(|| bug!("expected type for param #{} in {:?}", i, self)) - } - - #[inline] - #[track_caller] - pub fn region_at(&self, i: usize) -> ty::Region<'tcx> { - self[i] - .as_region() - .unwrap_or_else(|| bug!("expected region for param #{} in {:?}", i, self)) - } - - #[inline] - #[track_caller] - pub fn const_at(&self, i: usize) -> ty::Const<'tcx> { - self[i].as_const().unwrap_or_else(|| bug!("expected const for param #{} in {:?}", i, self)) - } - - #[inline] - #[track_caller] - pub fn type_for_def(&self, def: &ty::GenericParamDef) -> GenericArg<'tcx> { - self.type_at(def.index as usize).into() - } - - /// Transform from substitutions for a child of `source_ancestor` - /// (e.g., a trait or impl) to substitutions for the same child - /// in a different item, with `target_substs` as the base for - /// the target impl/trait, with the source child-specific - /// parameters (e.g., method parameters) on top of that base. - /// - /// For example given: - /// - /// ```no_run - /// trait X { fn f(); } - /// impl X for U { fn f() {} } - /// ``` - /// - /// * If `self` is `[Self, S, T]`: the identity substs of `f` in the trait. - /// * If `source_ancestor` is the def_id of the trait. - /// * If `target_substs` is `[U]`, the substs for the impl. - /// * Then we will return `[U, T]`, the subst for `f` in the impl that - /// are needed for it to match the trait. - pub fn rebase_onto( - &self, - tcx: TyCtxt<'tcx>, - source_ancestor: DefId, - target_substs: SubstsRef<'tcx>, - ) -> SubstsRef<'tcx> { - let defs = tcx.generics_of(source_ancestor); - tcx.mk_substs_from_iter(target_substs.iter().chain(self.iter().skip(defs.params.len()))) - } - - pub fn truncate_to(&self, tcx: TyCtxt<'tcx>, generics: &ty::Generics) -> SubstsRef<'tcx> { - tcx.mk_substs_from_iter(self.iter().take(generics.count())) - } -} - -impl<'tcx> TypeFoldable> for SubstsRef<'tcx> { - fn try_fold_with>>( - self, - folder: &mut F, - ) -> Result { - // This code is hot enough that it's worth specializing for the most - // common length lists, to avoid the overhead of `SmallVec` creation. - // The match arms are in order of frequency. The 1, 2, and 0 cases are - // typically hit in 90--99.99% of cases. When folding doesn't change - // the substs, it's faster to reuse the existing substs rather than - // calling `mk_substs`. - match self.len() { - 1 => { - let param0 = self[0].try_fold_with(folder)?; - if param0 == self[0] { - Ok(self) - } else { - Ok(folder.interner().mk_substs(&[param0])) - } - } - 2 => { - let param0 = self[0].try_fold_with(folder)?; - let param1 = self[1].try_fold_with(folder)?; - if param0 == self[0] && param1 == self[1] { - Ok(self) - } else { - Ok(folder.interner().mk_substs(&[param0, param1])) - } - } - 0 => Ok(self), - _ => ty::util::fold_list(self, folder, |tcx, v| tcx.mk_substs(v)), - } - } -} - -impl<'tcx> TypeFoldable> for &'tcx ty::List> { - fn try_fold_with>>( - self, - folder: &mut F, - ) -> Result { - // This code is fairly hot, though not as hot as `SubstsRef`. - // - // When compiling stage 2, I get the following results: - // - // len | total | % - // --- | --------- | ----- - // 2 | 15083590 | 48.1 - // 3 | 7540067 | 24.0 - // 1 | 5300377 | 16.9 - // 4 | 1351897 | 4.3 - // 0 | 1256849 | 4.0 - // - // I've tried it with some private repositories and got - // close to the same result, with 4 and 0 swapping places - // sometimes. - match self.len() { - 2 => { - let param0 = self[0].try_fold_with(folder)?; - let param1 = self[1].try_fold_with(folder)?; - if param0 == self[0] && param1 == self[1] { - Ok(self) - } else { - Ok(folder.interner().mk_type_list(&[param0, param1])) - } - } - _ => ty::util::fold_list(self, folder, |tcx, v| tcx.mk_type_list(v)), - } - } -} - -impl<'tcx, T: TypeVisitable>> TypeVisitable> for &'tcx ty::List { - #[inline] - fn visit_with>>(&self, visitor: &mut V) -> ControlFlow { - self.iter().try_for_each(|t| t.visit_with(visitor)) - } -} - -/// Similar to [`super::Binder`] except that it tracks early bound generics, i.e. `struct Foo(T)` -/// needs `T` substituted immediately. This type primarily exists to avoid forgetting to call -/// `subst`. -/// -/// If you don't have anything to `subst`, you may be looking for -/// [`subst_identity`](EarlyBinder::subst_identity) or [`skip_binder`](EarlyBinder::skip_binder). -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -#[derive(Encodable, Decodable, HashStable)] -pub struct EarlyBinder(pub T); - -/// For early binders, you should first call `subst` before using any visitors. -impl<'tcx, T> !TypeFoldable> for ty::EarlyBinder {} -impl<'tcx, T> !TypeVisitable> for ty::EarlyBinder {} - -impl EarlyBinder { - pub fn as_ref(&self) -> EarlyBinder<&T> { - EarlyBinder(&self.0) - } - - pub fn map_bound_ref(&self, f: F) -> EarlyBinder - where - F: FnOnce(&T) -> U, - { - self.as_ref().map_bound(f) - } - - pub fn map_bound(self, f: F) -> EarlyBinder - where - F: FnOnce(T) -> U, - { - let value = f(self.0); - EarlyBinder(value) - } - - pub fn try_map_bound(self, f: F) -> Result, E> - where - F: FnOnce(T) -> Result, - { - let value = f(self.0)?; - Ok(EarlyBinder(value)) - } - - pub fn rebind(&self, value: U) -> EarlyBinder { - EarlyBinder(value) - } - - /// Skips the binder and returns the "bound" value. - /// This can be used to extract data that does not depend on generic parameters - /// (e.g., getting the `DefId` of the inner value or getting the number of - /// arguments of an `FnSig`). Otherwise, consider using - /// [`subst_identity`](EarlyBinder::subst_identity). - /// - /// See also [`Binder::skip_binder`](super::Binder::skip_binder), which is - /// the analogous operation on [`super::Binder`]. - pub fn skip_binder(self) -> T { - self.0 - } -} - -impl EarlyBinder> { - pub fn transpose(self) -> Option> { - self.0.map(|v| EarlyBinder(v)) - } -} - -impl EarlyBinder<(T, U)> { - pub fn transpose_tuple2(self) -> (EarlyBinder, EarlyBinder) { - (EarlyBinder(self.0.0), EarlyBinder(self.0.1)) - } -} - -impl<'tcx, 's, I: IntoIterator> EarlyBinder -where - I::Item: TypeFoldable>, -{ - pub fn subst_iter( - self, - tcx: TyCtxt<'tcx>, - substs: &'s [GenericArg<'tcx>], - ) -> SubstIter<'s, 'tcx, I> { - SubstIter { it: self.0.into_iter(), tcx, substs } - } - - /// Similar to [`subst_identity`](EarlyBinder::subst_identity), - /// but on an iterator of `TypeFoldable` values. - pub fn subst_identity_iter(self) -> I::IntoIter { - self.0.into_iter() - } -} - -pub struct SubstIter<'s, 'tcx, I: IntoIterator> { - it: I::IntoIter, - tcx: TyCtxt<'tcx>, - substs: &'s [GenericArg<'tcx>], -} - -impl<'tcx, I: IntoIterator> Iterator for SubstIter<'_, 'tcx, I> -where - I::Item: TypeFoldable>, -{ - type Item = I::Item; - - fn next(&mut self) -> Option { - Some(EarlyBinder(self.it.next()?).subst(self.tcx, self.substs)) - } - - fn size_hint(&self) -> (usize, Option) { - self.it.size_hint() - } -} - -impl<'tcx, I: IntoIterator> DoubleEndedIterator for SubstIter<'_, 'tcx, I> -where - I::IntoIter: DoubleEndedIterator, - I::Item: TypeFoldable>, -{ - fn next_back(&mut self) -> Option { - Some(EarlyBinder(self.it.next_back()?).subst(self.tcx, self.substs)) - } -} - -impl<'tcx, I: IntoIterator> ExactSizeIterator for SubstIter<'_, 'tcx, I> -where - I::IntoIter: ExactSizeIterator, - I::Item: TypeFoldable>, -{ -} - -impl<'tcx, 's, I: IntoIterator> EarlyBinder -where - I::Item: Deref, - ::Target: Copy + TypeFoldable>, -{ - pub fn subst_iter_copied( - self, - tcx: TyCtxt<'tcx>, - substs: &'s [GenericArg<'tcx>], - ) -> SubstIterCopied<'s, 'tcx, I> { - SubstIterCopied { it: self.0.into_iter(), tcx, substs } - } - - /// Similar to [`subst_identity`](EarlyBinder::subst_identity), - /// but on an iterator of values that deref to a `TypeFoldable`. - pub fn subst_identity_iter_copied(self) -> impl Iterator::Target> { - self.0.into_iter().map(|v| *v) - } -} - -pub struct SubstIterCopied<'a, 'tcx, I: IntoIterator> { - it: I::IntoIter, - tcx: TyCtxt<'tcx>, - substs: &'a [GenericArg<'tcx>], -} - -impl<'tcx, I: IntoIterator> Iterator for SubstIterCopied<'_, 'tcx, I> -where - I::Item: Deref, - ::Target: Copy + TypeFoldable>, -{ - type Item = ::Target; - - fn next(&mut self) -> Option { - Some(EarlyBinder(*self.it.next()?).subst(self.tcx, self.substs)) - } - - fn size_hint(&self) -> (usize, Option) { - self.it.size_hint() - } -} - -impl<'tcx, I: IntoIterator> DoubleEndedIterator for SubstIterCopied<'_, 'tcx, I> -where - I::IntoIter: DoubleEndedIterator, - I::Item: Deref, - ::Target: Copy + TypeFoldable>, -{ - fn next_back(&mut self) -> Option { - Some(EarlyBinder(*self.it.next_back()?).subst(self.tcx, self.substs)) - } -} - -impl<'tcx, I: IntoIterator> ExactSizeIterator for SubstIterCopied<'_, 'tcx, I> -where - I::IntoIter: ExactSizeIterator, - I::Item: Deref, - ::Target: Copy + TypeFoldable>, -{ -} - -pub struct EarlyBinderIter { - t: T, -} - -impl EarlyBinder { - pub fn transpose_iter(self) -> EarlyBinderIter { - EarlyBinderIter { t: self.0.into_iter() } - } -} - -impl Iterator for EarlyBinderIter { - type Item = EarlyBinder; - - fn next(&mut self) -> Option { - self.t.next().map(|i| EarlyBinder(i)) - } - - fn size_hint(&self) -> (usize, Option) { - self.t.size_hint() - } -} - -impl<'tcx, T: TypeFoldable>> ty::EarlyBinder { - pub fn subst(self, tcx: TyCtxt<'tcx>, substs: &[GenericArg<'tcx>]) -> T { - let mut folder = SubstFolder { tcx, substs, binders_passed: 0 }; - self.0.fold_with(&mut folder) - } - - /// Makes the identity substitution `T0 => T0, ..., TN => TN`. - /// Conceptually, this converts universally bound variables into placeholders - /// when inside of a given item. - /// - /// For example, consider `for fn foo(){ .. }`: - /// - Outside of `foo`, `T` is bound (represented by the presence of `EarlyBinder`). - /// - Inside of the body of `foo`, we treat `T` as a placeholder by calling - /// `subst_identity` to discharge the `EarlyBinder`. - pub fn subst_identity(self) -> T { - self.0 - } - - /// Returns the inner value, but only if it contains no bound vars. - pub fn no_bound_vars(self) -> Option { - if !self.0.has_param() { Some(self.0) } else { None } - } -} - -/////////////////////////////////////////////////////////////////////////// -// The actual substitution engine itself is a type folder. - -struct SubstFolder<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - substs: &'a [GenericArg<'tcx>], - - /// Number of region binders we have passed through while doing the substitution - binders_passed: u32, -} - -impl<'a, 'tcx> TypeFolder> for SubstFolder<'a, 'tcx> { - #[inline] - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn fold_binder>>( - &mut self, - t: ty::Binder<'tcx, T>, - ) -> ty::Binder<'tcx, T> { - self.binders_passed += 1; - let t = t.super_fold_with(self); - self.binders_passed -= 1; - t - } - - fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { - #[cold] - #[inline(never)] - fn region_param_out_of_range(data: ty::EarlyBoundRegion, substs: &[GenericArg<'_>]) -> ! { - bug!( - "Region parameter out of range when substituting in region {} (index={}, substs = {:?})", - data.name, - data.index, - substs, - ) - } - - #[cold] - #[inline(never)] - fn region_param_invalid(data: ty::EarlyBoundRegion, other: GenericArgKind<'_>) -> ! { - bug!( - "Unexpected parameter {:?} when substituting in region {} (index={})", - other, - data.name, - data.index - ) - } - - // Note: This routine only handles regions that are bound on - // type declarations and other outer declarations, not those - // bound in *fn types*. Region substitution of the bound - // regions that appear in a function signature is done using - // the specialized routine `ty::replace_late_regions()`. - match *r { - ty::ReEarlyBound(data) => { - let rk = self.substs.get(data.index as usize).map(|k| k.unpack()); - match rk { - Some(GenericArgKind::Lifetime(lt)) => self.shift_region_through_binders(lt), - Some(other) => region_param_invalid(data, other), - None => region_param_out_of_range(data, self.substs), - } - } - ty::ReLateBound(..) - | ty::ReFree(_) - | ty::ReStatic - | ty::RePlaceholder(_) - | ty::ReErased - | ty::ReError(_) => r, - ty::ReVar(_) => bug!("unexpected region: {r:?}"), - } - } - - fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - if !t.has_param() { - return t; - } - - match *t.kind() { - ty::Param(p) => self.ty_for_param(p, t), - _ => t.super_fold_with(self), - } - } - - fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { - if let ty::ConstKind::Param(p) = c.kind() { - self.const_for_param(p, c) - } else { - c.super_fold_with(self) - } - } -} - -impl<'a, 'tcx> SubstFolder<'a, 'tcx> { - fn ty_for_param(&self, p: ty::ParamTy, source_ty: Ty<'tcx>) -> Ty<'tcx> { - // Look up the type in the substitutions. It really should be in there. - let opt_ty = self.substs.get(p.index as usize).map(|k| k.unpack()); - let ty = match opt_ty { - Some(GenericArgKind::Type(ty)) => ty, - Some(kind) => self.type_param_expected(p, source_ty, kind), - None => self.type_param_out_of_range(p, source_ty), - }; - - self.shift_vars_through_binders(ty) - } - - #[cold] - #[inline(never)] - fn type_param_expected(&self, p: ty::ParamTy, ty: Ty<'tcx>, kind: GenericArgKind<'tcx>) -> ! { - bug!( - "expected type for `{:?}` ({:?}/{}) but found {:?} when substituting, substs={:?}", - p, - ty, - p.index, - kind, - self.substs, - ) - } - - #[cold] - #[inline(never)] - fn type_param_out_of_range(&self, p: ty::ParamTy, ty: Ty<'tcx>) -> ! { - bug!( - "type parameter `{:?}` ({:?}/{}) out of range when substituting, substs={:?}", - p, - ty, - p.index, - self.substs, - ) - } - - fn const_for_param(&self, p: ParamConst, source_ct: ty::Const<'tcx>) -> ty::Const<'tcx> { - // Look up the const in the substitutions. It really should be in there. - let opt_ct = self.substs.get(p.index as usize).map(|k| k.unpack()); - let ct = match opt_ct { - Some(GenericArgKind::Const(ct)) => ct, - Some(kind) => self.const_param_expected(p, source_ct, kind), - None => self.const_param_out_of_range(p, source_ct), - }; - - self.shift_vars_through_binders(ct) - } - - #[cold] - #[inline(never)] - fn const_param_expected( - &self, - p: ty::ParamConst, - ct: ty::Const<'tcx>, - kind: GenericArgKind<'tcx>, - ) -> ! { - bug!( - "expected const for `{:?}` ({:?}/{}) but found {:?} when substituting substs={:?}", - p, - ct, - p.index, - kind, - self.substs, - ) - } - - #[cold] - #[inline(never)] - fn const_param_out_of_range(&self, p: ty::ParamConst, ct: ty::Const<'tcx>) -> ! { - bug!( - "const parameter `{:?}` ({:?}/{}) out of range when substituting substs={:?}", - p, - ct, - p.index, - self.substs, - ) - } - - /// It is sometimes necessary to adjust the De Bruijn indices during substitution. This occurs - /// when we are substituting a type with escaping bound vars into a context where we have - /// passed through binders. That's quite a mouthful. Let's see an example: - /// - /// ``` - /// type Func = fn(A); - /// type MetaFunc = for<'a> fn(Func<&'a i32>); - /// ``` - /// - /// The type `MetaFunc`, when fully expanded, will be - /// ```ignore (illustrative) - /// for<'a> fn(fn(&'a i32)) - /// // ^~ ^~ ^~~ - /// // | | | - /// // | | DebruijnIndex of 2 - /// // Binders - /// ``` - /// Here the `'a` lifetime is bound in the outer function, but appears as an argument of the - /// inner one. Therefore, that appearance will have a DebruijnIndex of 2, because we must skip - /// over the inner binder (remember that we count De Bruijn indices from 1). However, in the - /// definition of `MetaFunc`, the binder is not visible, so the type `&'a i32` will have a - /// De Bruijn index of 1. It's only during the substitution that we can see we must increase the - /// depth by 1 to account for the binder that we passed through. - /// - /// As a second example, consider this twist: - /// - /// ``` - /// type FuncTuple = (A,fn(A)); - /// type MetaFuncTuple = for<'a> fn(FuncTuple<&'a i32>); - /// ``` - /// - /// Here the final type will be: - /// ```ignore (illustrative) - /// for<'a> fn((&'a i32, fn(&'a i32))) - /// // ^~~ ^~~ - /// // | | - /// // DebruijnIndex of 1 | - /// // DebruijnIndex of 2 - /// ``` - /// As indicated in the diagram, here the same type `&'a i32` is substituted once, but in the - /// first case we do not increase the De Bruijn index and in the second case we do. The reason - /// is that only in the second case have we passed through a fn binder. - fn shift_vars_through_binders>>(&self, val: T) -> T { - debug!( - "shift_vars(val={:?}, binders_passed={:?}, has_escaping_bound_vars={:?})", - val, - self.binders_passed, - val.has_escaping_bound_vars() - ); - - if self.binders_passed == 0 || !val.has_escaping_bound_vars() { - return val; - } - - let result = ty::fold::shift_vars(TypeFolder::interner(self), val, self.binders_passed); - debug!("shift_vars: shifted result = {:?}", result); - - result - } - - fn shift_region_through_binders(&self, region: ty::Region<'tcx>) -> ty::Region<'tcx> { - if self.binders_passed == 0 || !region.has_escaping_bound_vars() { - return region; - } - ty::fold::shift_region(self.tcx, region, self.binders_passed) - } -} - -/// Stores the user-given substs to reach some fully qualified path -/// (e.g., `::Item` or `::Item`). -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable)] -#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] -pub struct UserSubsts<'tcx> { - /// The substitutions for the item as given by the user. - pub substs: SubstsRef<'tcx>, - - /// The self type, in the case of a `::Item` path (when applied - /// to an inherent impl). See `UserSelfTy` below. - pub user_self_ty: Option>, -} - -/// Specifies the user-given self type. In the case of a path that -/// refers to a member in an inherent impl, this self type is -/// sometimes needed to constrain the type parameters on the impl. For -/// example, in this code: -/// -/// ```ignore (illustrative) -/// struct Foo { } -/// impl Foo { fn method() { } } -/// ``` -/// -/// when you then have a path like `>::method`, -/// this struct would carry the `DefId` of the impl along with the -/// self type `Foo`. Then we can instantiate the parameters of -/// the impl (with the substs from `UserSubsts`) and apply those to -/// the self type, giving `Foo`. Finally, we unify that with -/// the self type here, which contains `?A` to be `&'static u32` -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TyEncodable, TyDecodable)] -#[derive(HashStable, TypeFoldable, TypeVisitable, Lift)] -pub struct UserSelfTy<'tcx> { - pub impl_def_id: DefId, - pub self_ty: Ty<'tcx>, -} diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index e61037e5ea86f..6e55e7915c92d 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -52,6 +52,16 @@ pub struct TraitDef { /// List of functions from `#[rustc_must_implement_one_of]` attribute one of which /// must be implemented. pub must_implement_one_of: Option>, + + /// Whether to add a builtin `dyn Trait: Trait` implementation. + /// This is enabled for all traits except ones marked with + /// `#[rustc_deny_explicit_impl(implement_via_object = false)]`. + pub implement_via_object: bool, + + /// Whether a trait is fully built-in, and any implementation is disallowed. + /// This only applies to built-in traits, and is marked via + /// `#[rustc_deny_explicit_impl(implement_via_object = ...)]`. + pub deny_explicit_impl: bool, } /// Whether this trait is treated specially by the standard library @@ -226,7 +236,7 @@ pub(super) fn trait_impls_of_provider(tcx: TyCtxt<'_>, trait_id: DefId) -> Trait for &impl_def_id in tcx.hir().trait_impls(trait_id) { let impl_def_id = impl_def_id.to_def_id(); - let impl_self_ty = tcx.type_of(impl_def_id).subst_identity(); + let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity(); if impl_self_ty.references_error() { continue; } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index e04dbbff9a777..327cd0a5d7b31 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -4,13 +4,12 @@ use crate::{ traits::ObligationCause, ty::{ self, tls, BindingMode, BoundVar, CanonicalPolyFnSig, ClosureSizeProfileData, - GenericArgKind, InternalSubsts, SubstsRef, Ty, UserSubsts, + GenericArgKind, GenericArgs, GenericArgsRef, Ty, UserArgs, }, }; use rustc_data_structures::{ - fx::{FxHashMap, FxIndexMap}, - sync::Lrc, - unord::{UnordItems, UnordSet}, + fx::FxIndexMap, + unord::{ExtendUnord, UnordItems, UnordSet}, }; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; @@ -54,7 +53,7 @@ pub struct TypeckResults<'tcx> { /// of this node. This only applies to nodes that refer to entities /// parameterized by type parameters, such as generic fns, types, or /// other items. - node_substs: ItemLocalMap>, + node_args: ItemLocalMap>, /// This will either store the canonicalized types provided by the user /// or the substitutions that the user explicitly gave (if any) attached @@ -145,7 +144,7 @@ pub struct TypeckResults<'tcx> { /// This is used for warning unused imports. During type /// checking, this `Lrc` should not be cloned: it must have a ref-count /// of 1 so that we can insert things into the set mutably. - pub used_trait_imports: Lrc>, + pub used_trait_imports: UnordSet, /// If any errors occurred while type-checking this body, /// this field will be set to `Some(ErrorGuaranteed)`. @@ -155,11 +154,7 @@ pub struct TypeckResults<'tcx> { /// We also store the type here, so that the compiler can use it as a hint /// for figuring out hidden types, even if they are only set in dead code /// (which doesn't show up in MIR). - /// - /// These types are mapped back to the opaque's identity substitutions - /// (with erased regions), which is why we don't associated substs with any - /// of these usages. - pub concrete_opaque_types: FxIndexMap>, + pub concrete_opaque_types: FxIndexMap, ty::OpaqueHiddenType<'tcx>>, /// Tracks the minimum captures required for a closure; /// see `MinCaptureInformationMap` for more details. @@ -187,7 +182,7 @@ pub struct TypeckResults<'tcx> { /// we never capture `t`. This becomes an issue when we build MIR as we require /// information on `t` in order to create place `t.0` and `t.1`. We can solve this /// issue by fake reading `t`. - pub closure_fake_reads: FxHashMap, FakeReadCause, hir::HirId)>>, + pub closure_fake_reads: LocalDefIdMap, FakeReadCause, hir::HirId)>>, /// Tracks the rvalue scoping rules which defines finer scoping for rvalue expressions /// by applying extended parameter rules. @@ -201,7 +196,7 @@ pub struct TypeckResults<'tcx> { /// Stores the predicates that apply on generator witness types. /// formatting modified file tests/ui/generator/retain-resume-ref.rs pub generator_interior_predicates: - FxHashMap, ObligationCause<'tcx>)>>, + LocalDefIdMap, ObligationCause<'tcx>)>>, /// We sometimes treat byte string literals (which are of type `&[u8; N]`) /// as `&[u8]`, depending on the pattern in which they are used. @@ -211,7 +206,7 @@ pub struct TypeckResults<'tcx> { /// Contains the data for evaluating the effect of feature `capture_disjoint_fields` /// on closure size. - pub closure_size_eval: FxHashMap>, + pub closure_size_eval: LocalDefIdMap>, /// Container types and field indices of `offset_of!` expressions offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec)>, @@ -269,7 +264,7 @@ impl<'tcx> TypeckResults<'tcx> { user_provided_types: Default::default(), user_provided_sigs: Default::default(), node_types: Default::default(), - node_substs: Default::default(), + node_args: Default::default(), adjustments: Default::default(), pat_binding_modes: Default::default(), pat_adjustments: Default::default(), @@ -277,7 +272,7 @@ impl<'tcx> TypeckResults<'tcx> { liberated_fn_sigs: Default::default(), fru_field_types: Default::default(), coercion_casts: Default::default(), - used_trait_imports: Lrc::new(Default::default()), + used_trait_imports: Default::default(), tainted_by_errors: None, concrete_opaque_types: Default::default(), closure_min_captures: Default::default(), @@ -389,18 +384,18 @@ impl<'tcx> TypeckResults<'tcx> { self.node_types.get(&id.local_id).cloned() } - pub fn node_substs_mut(&mut self) -> LocalTableInContextMut<'_, SubstsRef<'tcx>> { - LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.node_substs } + pub fn node_args_mut(&mut self) -> LocalTableInContextMut<'_, GenericArgsRef<'tcx>> { + LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.node_args } } - pub fn node_substs(&self, id: hir::HirId) -> SubstsRef<'tcx> { + pub fn node_args(&self, id: hir::HirId) -> GenericArgsRef<'tcx> { validate_hir_id_for_typeck_results(self.hir_owner, id); - self.node_substs.get(&id.local_id).cloned().unwrap_or_else(|| InternalSubsts::empty()) + self.node_args.get(&id.local_id).cloned().unwrap_or_else(|| GenericArgs::empty()) } - pub fn node_substs_opt(&self, id: hir::HirId) -> Option> { + pub fn node_args_opt(&self, id: hir::HirId) -> Option> { validate_hir_id_for_typeck_results(self.hir_owner, id); - self.node_substs.get(&id.local_id).cloned() + self.node_args.get(&id.local_id).cloned() } /// Returns the type of a pattern as a monotype. Like [`expr_ty`], this function @@ -640,7 +635,7 @@ impl<'a, V> LocalTableInContextMut<'a, V> { &mut self, items: UnordItems<(hir::HirId, V), impl Iterator>, ) { - self.data.extend(items.map(|(id, value)| { + self.data.extend_unord(items.map(|(id, value)| { validate_hir_id_for_typeck_results(self.hir_owner, id); (id.local_id, value) })) @@ -675,12 +670,12 @@ impl<'tcx> CanonicalUserType<'tcx> { pub fn is_identity(&self) -> bool { match self.value { UserType::Ty(_) => false, - UserType::TypeOf(_, user_substs) => { - if user_substs.user_self_ty.is_some() { + UserType::TypeOf(_, user_args) => { + if user_args.user_self_ty.is_some() { return false; } - iter::zip(user_substs.substs, BoundVar::new(0)..).all(|(kind, cvar)| { + iter::zip(user_args.args, BoundVar::new(0)..).all(|(kind, cvar)| { match kind.unpack() { GenericArgKind::Type(ty) => match ty.kind() { ty::Bound(debruijn, b) => { @@ -725,5 +720,5 @@ pub enum UserType<'tcx> { /// The canonical type is the result of `type_of(def_id)` with the /// given substitutions applied. - TypeOf(DefId, UserSubsts<'tcx>), + TypeOf(DefId, UserArgs<'tcx>), } diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index ba05135638e1f..564f982f842ed 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -1,27 +1,25 @@ //! Miscellaneous type-system utilities that are too small to deserve their own modules. use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use crate::mir; use crate::query::Providers; use crate::ty::layout::IntegerExt; use crate::ty::{ self, FallibleTypeFolder, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, }; -use crate::ty::{GenericArgKind, SubstsRef}; +use crate::ty::{GenericArgKind, GenericArgsRef}; use rustc_apfloat::Float as _; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::stable_hasher::{Hash64, HashStable, StableHasher}; +use rustc_data_structures::stable_hasher::{Hash128, HashStable, StableHasher}; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; use rustc_index::bit_set::GrowableBitSet; -use rustc_index::{Idx, IndexVec}; use rustc_macros::HashStable; use rustc_session::Limit; use rustc_span::sym; -use rustc_target::abi::{Integer, IntegerType, Size, TargetDataLayout}; +use rustc_target::abi::{Integer, IntegerType, Size}; use rustc_target::spec::abi::Abi; use smallvec::SmallVec; use std::{fmt, iter}; @@ -59,7 +57,7 @@ impl<'tcx> fmt::Display for Discr<'tcx> { let x = self.val; // sign extend the raw representation to be an i128 let x = size.sign_extend(x) as i128; - write!(fmt, "{}", x) + write!(fmt, "{x}") } _ => write!(fmt, "{}", self.val), } @@ -129,7 +127,7 @@ impl IntTypeExt for IntegerType { impl<'tcx> TyCtxt<'tcx> { /// Creates a hash of the type `Ty` which will be the same no matter what crate /// context it's calculated within. This is used by the `type_id` intrinsic. - pub fn type_id_hash(self, ty: Ty<'tcx>) -> Hash64 { + pub fn type_id_hash(self, ty: Ty<'tcx>) -> Hash128 { // We want the type_id be independent of the types free regions, so we // erase them. The erase_regions() call will also anonymize bound // regions, which is desirable too. @@ -158,7 +156,7 @@ impl<'tcx> TyCtxt<'tcx> { | DefKind::Enum | DefKind::Trait | DefKind::OpaqueTy - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -173,18 +171,6 @@ impl<'tcx> TyCtxt<'tcx> { } } - pub fn has_error_field(self, ty: Ty<'tcx>) -> bool { - if let ty::Adt(def, substs) = *ty.kind() { - for field in def.all_fields() { - let field_ty = field.ty(self, substs); - if let ty::Error(_) = field_ty.kind() { - return true; - } - } - } - false - } - /// Attempts to returns the deeply last field of nested structures, but /// does not apply any normalization in its search. Returns the same type /// if input `ty` is not a structure at all. @@ -237,17 +223,17 @@ impl<'tcx> TyCtxt<'tcx> { }; let reported = self.sess.emit_err(crate::error::RecursionLimitReached { ty, suggested_limit }); - return self.ty_error(reported); + return Ty::new_error(self, reported); } match *ty.kind() { - ty::Adt(def, substs) => { + ty::Adt(def, args) => { if !def.is_struct() { break; } - match def.non_enum_variant().fields.raw.last() { + match def.non_enum_variant().tail_opt() { Some(field) => { f(); - ty = field.ty(self, substs); + ty = field.ty(self, args); } None => break, } @@ -315,12 +301,12 @@ impl<'tcx> TyCtxt<'tcx> { let (mut a, mut b) = (source, target); loop { match (&a.kind(), &b.kind()) { - (&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs)) + (&ty::Adt(a_def, a_args), &ty::Adt(b_def, b_args)) if a_def == b_def && a_def.is_struct() => { - if let Some(f) = a_def.non_enum_variant().fields.raw.last() { - a = f.ty(self, a_substs); - b = f.ty(self, b_substs); + if let Some(f) = a_def.non_enum_variant().tail_opt() { + a = f.ty(self, a_args); + b = f.ty(self, b_args); } else { break; } @@ -363,7 +349,7 @@ impl<'tcx> TyCtxt<'tcx> { let drop_trait = self.lang_items().drop_trait()?; self.ensure().coherent_trait(drop_trait); - let ty = self.type_of(adt_did).subst_identity(); + let ty = self.type_of(adt_did).instantiate_identity(); let mut dtor_candidate = None; self.for_each_relevant_impl(drop_trait, ty, |impl_did| { if validate(self, impl_did).is_err() { @@ -372,7 +358,8 @@ impl<'tcx> TyCtxt<'tcx> { } let Some(item_id) = self.associated_item_def_ids(impl_did).first() else { - self.sess.delay_span_bug(self.def_span(impl_did), "Drop impl without drop function"); + self.sess + .delay_span_bug(self.def_span(impl_did), "Drop impl without drop function"); return; }; @@ -397,7 +384,7 @@ impl<'tcx> TyCtxt<'tcx> { /// Note that this returns only the constraints for the /// destructor of `def` itself. For the destructors of the /// contents, you need `adt_dtorck_constraint`. - pub fn destructor_constraints(self, def: ty::AdtDef<'tcx>) -> Vec> { + pub fn destructor_constraints(self, def: ty::AdtDef<'tcx>) -> Vec> { let dtor = match def.destructor(self) { None => { debug!("destructor_constraints({:?}) - no dtor", def.did()); @@ -414,7 +401,7 @@ impl<'tcx> TyCtxt<'tcx> { // must be live. // We need to return the list of parameters from the ADTs - // generics/substs that correspond to impure parameters on the + // generics/args that correspond to impure parameters on the // impl's generics. This is a bit ugly, but conceptually simple: // // Suppose our ADT looks like the following @@ -426,21 +413,21 @@ impl<'tcx> TyCtxt<'tcx> { // impl<#[may_dangle] P0, P1, P2> Drop for S // // We want to return the parameters (X, Y). For that, we match - // up the item-substs with the substs on the impl ADT, - // , and then look up which of the impl substs refer to + // up the item-args with the args on the impl ADT, + // , and then look up which of the impl args refer to // parameters marked as pure. - let impl_substs = match *self.type_of(impl_def_id).subst_identity().kind() { - ty::Adt(def_, substs) if def_ == def => substs, + let impl_args = match *self.type_of(impl_def_id).instantiate_identity().kind() { + ty::Adt(def_, args) if def_ == def => args, _ => bug!(), }; - let item_substs = match *self.type_of(def.did()).subst_identity().kind() { - ty::Adt(def_, substs) if def_ == def => substs, + let item_args = match *self.type_of(def.did()).instantiate_identity().kind() { + ty::Adt(def_, args) if def_ == def => args, _ => bug!(), }; - let result = iter::zip(item_substs, impl_substs) + let result = iter::zip(item_args, impl_args) .filter(|&(_, k)| { match k.unpack() { GenericArgKind::Lifetime(region) => match region.kind() { @@ -473,12 +460,12 @@ impl<'tcx> TyCtxt<'tcx> { /// Checks whether each generic argument is simply a unique generic parameter. pub fn uses_unique_generic_params( self, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ignore_regions: CheckRegions, ) -> Result<(), NotUniqueParam<'tcx>> { let mut seen = GrowableBitSet::default(); let mut seen_late = FxHashSet::default(); - for arg in substs { + for arg in args { match arg.unpack() { GenericArgKind::Lifetime(lt) => match (ignore_regions, lt.kind()) { (CheckRegions::Bound, ty::ReLateBound(di, reg)) => { @@ -524,10 +511,10 @@ impl<'tcx> TyCtxt<'tcx> { /// for better caching. pub fn uses_unique_placeholders_ignoring_regions( self, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Result<(), NotUniqueParam<'tcx>> { let mut seen = GrowableBitSet::default(); - for arg in substs { + for arg in args { match arg.unpack() { // Ignore regions, since we can't resolve those in a canonicalized // query in the trait solver. @@ -608,7 +595,7 @@ impl<'tcx> TyCtxt<'tcx> { def_id } - /// Given the `DefId` and substs a closure, creates the type of + /// Given the `DefId` and args a closure, creates the type of /// `self` argument that the closure expects. For example, for a /// `Fn` closure, this would return a reference type `&T` where /// `T = closure_ty`. @@ -621,15 +608,15 @@ impl<'tcx> TyCtxt<'tcx> { pub fn closure_env_ty( self, closure_def_id: DefId, - closure_substs: SubstsRef<'tcx>, + closure_args: GenericArgsRef<'tcx>, env_region: ty::Region<'tcx>, ) -> Option> { - let closure_ty = self.mk_closure(closure_def_id, closure_substs); - let closure_kind_ty = closure_substs.as_closure().kind_ty(); + let closure_ty = Ty::new_closure(self, closure_def_id, closure_args); + let closure_kind_ty = closure_args.as_closure().kind_ty(); let closure_kind = closure_kind_ty.to_opt_closure_kind()?; let env_ty = match closure_kind { - ty::ClosureKind::Fn => self.mk_imm_ref(env_region, closure_ty), - ty::ClosureKind::FnMut => self.mk_mut_ref(env_region, closure_ty), + ty::ClosureKind::Fn => Ty::new_imm_ref(self, env_region, closure_ty), + ty::ClosureKind::FnMut => Ty::new_mut_ref(self, env_region, closure_ty), ty::ClosureKind::FnOnce => closure_ty, }; Some(env_ty) @@ -668,14 +655,14 @@ impl<'tcx> TyCtxt<'tcx> { /// Returns the type a reference to the thread local takes in MIR. pub fn thread_local_ptr_ty(self, def_id: DefId) -> Ty<'tcx> { - let static_ty = self.type_of(def_id).subst_identity(); + let static_ty = self.type_of(def_id).instantiate_identity(); if self.is_mutable_static(def_id) { - self.mk_mut_ptr(static_ty) + Ty::new_mut_ptr(self, static_ty) } else if self.is_foreign_item(def_id) { - self.mk_imm_ptr(static_ty) + Ty::new_imm_ptr(self, static_ty) } else { // FIXME: These things don't *really* have 'static lifetime. - self.mk_imm_ref(self.lifetimes.re_static, static_ty) + Ty::new_imm_ref(self, self.lifetimes.re_static, static_ty) } } @@ -684,17 +671,17 @@ impl<'tcx> TyCtxt<'tcx> { // Make sure that any constants in the static's type are evaluated. let static_ty = self.normalize_erasing_regions( ty::ParamEnv::empty(), - self.type_of(def_id).subst_identity(), + self.type_of(def_id).instantiate_identity(), ); // Make sure that accesses to unsafe statics end up using raw pointers. // For thread-locals, this needs to be kept in sync with `Rvalue::ty`. if self.is_mutable_static(def_id) { - self.mk_mut_ptr(static_ty) + Ty::new_mut_ptr(self, static_ty) } else if self.is_foreign_item(def_id) { - self.mk_imm_ptr(static_ty) + Ty::new_imm_ptr(self, static_ty) } else { - self.mk_imm_ref(self.lifetimes.re_erased, static_ty) + Ty::new_imm_ref(self, self.lifetimes.re_erased, static_ty) } } @@ -709,7 +696,7 @@ impl<'tcx> TyCtxt<'tcx> { .as_ref() .map_or_else(|| [].iter(), |l| l.field_tys.iter()) .filter(|decl| !decl.ignore_for_traits) - .map(|decl| ty::EarlyBinder(decl.ty)) + .map(|decl| ty::EarlyBinder::bind(decl.ty)) } /// Normalizes all opaque types in the given value, replacing them @@ -733,7 +720,7 @@ impl<'tcx> TyCtxt<'tcx> { pub fn try_expand_impl_trait_type( self, def_id: DefId, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Result, Ty<'tcx>> { let mut visitor = OpaqueTypeExpander { seen_opaque_tys: FxHashSet::default(), @@ -746,84 +733,10 @@ impl<'tcx> TyCtxt<'tcx> { tcx: self, }; - let expanded_type = visitor.expand_opaque_ty(def_id, substs).unwrap(); + let expanded_type = visitor.expand_opaque_ty(def_id, args).unwrap(); if visitor.found_recursion { Err(expanded_type) } else { Ok(expanded_type) } } - /// Returns names of captured upvars for closures and generators. - /// - /// Here are some examples: - /// - `name__field1__field2` when the upvar is captured by value. - /// - `_ref__name__field` when the upvar is captured by reference. - /// - /// For generators this only contains upvars that are shared by all states. - pub fn closure_saved_names_of_captured_variables( - self, - def_id: DefId, - ) -> SmallVec<[String; 16]> { - let body = self.optimized_mir(def_id); - - body.var_debug_info - .iter() - .filter_map(|var| { - let is_ref = match var.value { - mir::VarDebugInfoContents::Place(place) - if place.local == mir::Local::new(1) => - { - // The projection is either `[.., Field, Deref]` or `[.., Field]`. It - // implies whether the variable is captured by value or by reference. - matches!(place.projection.last().unwrap(), mir::ProjectionElem::Deref) - } - _ => return None, - }; - let prefix = if is_ref { "_ref__" } else { "" }; - Some(prefix.to_owned() + var.name.as_str()) - }) - .collect() - } - - // FIXME(eddyb) maybe precompute this? Right now it's computed once - // per generator monomorphization, but it doesn't depend on substs. - pub fn generator_layout_and_saved_local_names( - self, - def_id: DefId, - ) -> ( - &'tcx ty::GeneratorLayout<'tcx>, - IndexVec>, - ) { - let tcx = self; - let body = tcx.optimized_mir(def_id); - let generator_layout = body.generator_layout().unwrap(); - let mut generator_saved_local_names = - IndexVec::from_elem(None, &generator_layout.field_tys); - - let state_arg = mir::Local::new(1); - for var in &body.var_debug_info { - let mir::VarDebugInfoContents::Place(place) = &var.value else { continue }; - if place.local != state_arg { - continue; - } - match place.projection[..] { - [ - // Deref of the `Pin<&mut Self>` state argument. - mir::ProjectionElem::Field(..), - mir::ProjectionElem::Deref, - // Field of a variant of the state. - mir::ProjectionElem::Downcast(_, variant), - mir::ProjectionElem::Field(field, _), - ] => { - let name = &mut generator_saved_local_names - [generator_layout.variant_fields[variant][field]]; - if name.is_none() { - name.replace(var.name); - } - } - _ => {} - } - } - (generator_layout, generator_saved_local_names) - } - /// Query and get an English description for the item's kind. pub fn def_descr(self, def_id: DefId) -> &'static str { self.def_kind_descr(self.def_kind(def_id), def_id) @@ -857,6 +770,26 @@ impl<'tcx> TyCtxt<'tcx> { _ => def_kind.article(), } } + + /// Return `true` if the supplied `CrateNum` is "user-visible," meaning either a [public] + /// dependency, or a [direct] private dependency. This is used to decide whether the crate can + /// be shown in `impl` suggestions. + /// + /// [public]: TyCtxt::is_private_dep + /// [direct]: rustc_session::cstore::ExternCrate::is_direct + pub fn is_user_visible_dep(self, key: CrateNum) -> bool { + // | Private | Direct | Visible | | + // |---------|--------|---------|--------------------| + // | Yes | Yes | Yes | !true || true | + // | No | Yes | Yes | !false || true | + // | Yes | No | No | !true || false | + // | No | No | Yes | !false || false | + !self.is_private_dep(key) + // If `extern_crate` is `None`, then the crate was injected (e.g., by the allocator). + // Treat that kind of crate as "indirect", since it's an implementation detail of + // the language. + || self.extern_crate(key.as_def_id()).map_or(false, |e| e.is_direct()) + } } struct OpaqueTypeExpander<'tcx> { @@ -867,7 +800,7 @@ struct OpaqueTypeExpander<'tcx> { seen_opaque_tys: FxHashSet, // Cache of all expansions we've seen so far. This is a critical // optimization for some large types produced by async fn trees. - expanded_cache: FxHashMap<(DefId, SubstsRef<'tcx>), Ty<'tcx>>, + expanded_cache: FxHashMap<(DefId, GenericArgsRef<'tcx>), Ty<'tcx>>, primary_def_id: Option, found_recursion: bool, found_any_recursion: bool, @@ -880,19 +813,19 @@ struct OpaqueTypeExpander<'tcx> { } impl<'tcx> OpaqueTypeExpander<'tcx> { - fn expand_opaque_ty(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) -> Option> { + fn expand_opaque_ty(&mut self, def_id: DefId, args: GenericArgsRef<'tcx>) -> Option> { if self.found_any_recursion { return None; } - let substs = substs.fold_with(self); + let args = args.fold_with(self); if !self.check_recursion || self.seen_opaque_tys.insert(def_id) { - let expanded_ty = match self.expanded_cache.get(&(def_id, substs)) { + let expanded_ty = match self.expanded_cache.get(&(def_id, args)) { Some(expanded_ty) => *expanded_ty, None => { let generic_ty = self.tcx.type_of(def_id); - let concrete_ty = generic_ty.subst(self.tcx, substs); + let concrete_ty = generic_ty.instantiate(self.tcx, args); let expanded_ty = self.fold_ty(concrete_ty); - self.expanded_cache.insert((def_id, substs), expanded_ty); + self.expanded_cache.insert((def_id, args), expanded_ty); expanded_ty } }; @@ -909,21 +842,21 @@ impl<'tcx> OpaqueTypeExpander<'tcx> { } } - fn expand_generator(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) -> Option> { + fn expand_generator(&mut self, def_id: DefId, args: GenericArgsRef<'tcx>) -> Option> { if self.found_any_recursion { return None; } - let substs = substs.fold_with(self); + let args = args.fold_with(self); if !self.check_recursion || self.seen_opaque_tys.insert(def_id) { - let expanded_ty = match self.expanded_cache.get(&(def_id, substs)) { + let expanded_ty = match self.expanded_cache.get(&(def_id, args)) { Some(expanded_ty) => *expanded_ty, None => { for bty in self.tcx.generator_hidden_types(def_id) { - let hidden_ty = bty.subst(self.tcx, substs); + let hidden_ty = bty.instantiate(self.tcx, args); self.fold_ty(hidden_ty); } - let expanded_ty = self.tcx.mk_generator_witness_mir(def_id, substs); - self.expanded_cache.insert((def_id, substs), expanded_ty); + let expanded_ty = Ty::new_generator_witness_mir(self.tcx, def_id, args); + self.expanded_cache.insert((def_id, args), expanded_ty); expanded_ty } }; @@ -947,16 +880,16 @@ impl<'tcx> TypeFolder> for OpaqueTypeExpander<'tcx> { } fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - let mut t = if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) = *t.kind() { - self.expand_opaque_ty(def_id, substs).unwrap_or(t) + let mut t = if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) = *t.kind() { + self.expand_opaque_ty(def_id, args).unwrap_or(t) } else if t.has_opaque_types() || t.has_generators() { t.super_fold_with(self) } else { t }; if self.expand_generators { - if let ty::GeneratorWitnessMIR(def_id, substs) = *t.kind() { - t = self.expand_generator(def_id, substs).unwrap_or(t); + if let ty::GeneratorWitnessMIR(def_id, args) = *t.kind() { + t = self.expand_generator(def_id, args).unwrap_or(t); } } t @@ -964,7 +897,7 @@ impl<'tcx> TypeFolder> for OpaqueTypeExpander<'tcx> { fn fold_predicate(&mut self, p: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> { if let ty::PredicateKind::Clause(clause) = p.kind().skip_binder() - && let ty::Clause::Projection(projection_pred) = clause + && let ty::ClauseKind::Projection(projection_pred) = clause { p.kind() .rebind(ty::ProjectionPredicate { @@ -1152,7 +1085,7 @@ impl<'tcx> Ty<'tcx> { #[inline] pub fn needs_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool { // Avoid querying in simple cases. - match needs_drop_components(self, &tcx.data_layout) { + match needs_drop_components(tcx, self) { Err(AlwaysRequiresDrop) => true, Ok(components) => { let query_ty = match *components { @@ -1185,7 +1118,7 @@ impl<'tcx> Ty<'tcx> { #[inline] pub fn has_significant_drop(self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool { // Avoid querying in simple cases. - match needs_drop_components(self, &tcx.data_layout) { + match needs_drop_components(tcx, self) { Err(AlwaysRequiresDrop) => true, Ok(components) => { let query_ty = match *components { @@ -1345,10 +1278,10 @@ impl<'tcx> ExplicitSelf<'tcx> { /// *any* of the returned types need drop. Returns `Err(AlwaysRequiresDrop)` if /// this type always needs drop. pub fn needs_drop_components<'tcx>( + tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, - target_layout: &TargetDataLayout, ) -> Result; 2]>, AlwaysRequiresDrop> { - match ty.kind() { + match *ty.kind() { ty::Infer(ty::FreshIntTy(_)) | ty::Infer(ty::FreshFloatTy(_)) | ty::Bool @@ -1370,11 +1303,11 @@ pub fn needs_drop_components<'tcx>( ty::Dynamic(..) | ty::Error(_) => Err(AlwaysRequiresDrop), - ty::Slice(ty) => needs_drop_components(*ty, target_layout), + ty::Slice(ty) => needs_drop_components(tcx, ty), ty::Array(elem_ty, size) => { - match needs_drop_components(*elem_ty, target_layout) { + match needs_drop_components(tcx, elem_ty) { Ok(v) if v.is_empty() => Ok(v), - res => match size.kind().try_to_bits(target_layout.pointer_size) { + res => match size.try_to_target_usize(tcx) { // Arrays of size zero don't need drop, even if their element // type does. Some(0) => Ok(SmallVec::new()), @@ -1388,7 +1321,7 @@ pub fn needs_drop_components<'tcx>( } // If any field needs drop, then the whole tuple does. ty::Tuple(fields) => fields.iter().try_fold(SmallVec::new(), move |mut acc, elem| { - acc.extend(needs_drop_components(elem, target_layout)?); + acc.extend(needs_drop_components(tcx, elem)?); Ok(acc) }), @@ -1487,8 +1420,8 @@ pub struct AlwaysRequiresDrop; /// with their underlying types. pub fn reveal_opaque_types_in_bounds<'tcx>( tcx: TyCtxt<'tcx>, - val: &'tcx ty::List>, -) -> &'tcx ty::List> { + val: &'tcx ty::List>, +) -> &'tcx ty::List> { let mut visitor = OpaqueTypeExpander { seen_opaque_tys: FxHashSet::default(), expanded_cache: FxHashMap::default(), diff --git a/compiler/rustc_middle/src/ty/visit.rs b/compiler/rustc_middle/src/ty/visit.rs index 520bb55e031c7..156eda477ade4 100644 --- a/compiler/rustc_middle/src/ty/visit.rs +++ b/compiler/rustc_middle/src/ty/visit.rs @@ -88,14 +88,10 @@ pub trait TypeVisitableExt<'tcx>: TypeVisitable> { self.has_type_flags(TypeFlags::HAS_INFER) } fn has_placeholders(&self) -> bool { - self.has_type_flags( - TypeFlags::HAS_RE_PLACEHOLDER - | TypeFlags::HAS_TY_PLACEHOLDER - | TypeFlags::HAS_CT_PLACEHOLDER, - ) + self.has_type_flags(TypeFlags::HAS_PLACEHOLDER) } fn has_non_region_placeholders(&self) -> bool { - self.has_type_flags(TypeFlags::HAS_TY_PLACEHOLDER | TypeFlags::HAS_CT_PLACEHOLDER) + self.has_type_flags(TypeFlags::HAS_PLACEHOLDER - TypeFlags::HAS_RE_PLACEHOLDER) } fn has_param(&self) -> bool { self.has_type_flags(TypeFlags::HAS_PARAM) diff --git a/compiler/rustc_middle/src/ty/vtable.rs b/compiler/rustc_middle/src/ty/vtable.rs index b9b1cd73a8b9a..97402caa0013b 100644 --- a/compiler/rustc_middle/src/ty/vtable.rs +++ b/compiler/rustc_middle/src/ty/vtable.rs @@ -29,8 +29,8 @@ impl<'tcx> fmt::Debug for VtblEntry<'tcx> { VtblEntry::MetadataSize => write!(f, "MetadataSize"), VtblEntry::MetadataAlign => write!(f, "MetadataAlign"), VtblEntry::Vacant => write!(f, "Vacant"), - VtblEntry::Method(instance) => write!(f, "Method({})", instance), - VtblEntry::TraitVPtr(trait_ref) => write!(f, "TraitVPtr({})", trait_ref), + VtblEntry::Method(instance) => write!(f, "Method({instance})"), + VtblEntry::TraitVPtr(trait_ref) => write!(f, "TraitVPtr({trait_ref})"), } } } @@ -73,7 +73,7 @@ pub(super) fn vtable_allocation_provider<'tcx>( let ptr_align = tcx.data_layout.pointer_align.abi; let vtable_size = ptr_size * u64::try_from(vtable_entries.len()).unwrap(); - let mut vtable = Allocation::uninit(vtable_size, ptr_align, /* panic_on_fail */ true).unwrap(); + let mut vtable = Allocation::uninit(vtable_size, ptr_align); // No need to do any alignment checks on the memory accesses below, because we know the // allocation is correctly aligned as we created it above. Also we're only offsetting by diff --git a/compiler/rustc_middle/src/ty/walk.rs b/compiler/rustc_middle/src/ty/walk.rs index 04a635a68034d..7c3d9ed390a22 100644 --- a/compiler/rustc_middle/src/ty/walk.rs +++ b/compiler/rustc_middle/src/ty/walk.rs @@ -1,8 +1,8 @@ //! An iterator over the type substructure. //! WARNING: this does not keep track of the region depth. -use crate::ty::subst::{GenericArg, GenericArgKind}; use crate::ty::{self, Ty}; +use crate::ty::{GenericArg, GenericArgKind}; use rustc_data_structures::sso::SsoHashSet; use smallvec::SmallVec; @@ -166,33 +166,33 @@ fn push_inner<'tcx>(stack: &mut TypeWalkerStack<'tcx>, parent: GenericArg<'tcx>) stack.push(lt.into()); } ty::Alias(_, data) => { - stack.extend(data.substs.iter().rev()); + stack.extend(data.args.iter().rev()); } ty::Dynamic(obj, lt, _) => { stack.push(lt.into()); stack.extend(obj.iter().rev().flat_map(|predicate| { - let (substs, opt_ty) = match predicate.skip_binder() { - ty::ExistentialPredicate::Trait(tr) => (tr.substs, None), - ty::ExistentialPredicate::Projection(p) => (p.substs, Some(p.term)), + let (args, opt_ty) = match predicate.skip_binder() { + ty::ExistentialPredicate::Trait(tr) => (tr.args, None), + ty::ExistentialPredicate::Projection(p) => (p.args, Some(p.term)), ty::ExistentialPredicate::AutoTrait(_) => // Empty iterator { - (ty::InternalSubsts::empty(), None) + (ty::GenericArgs::empty(), None) } }; - substs.iter().rev().chain(opt_ty.map(|term| match term.unpack() { + args.iter().rev().chain(opt_ty.map(|term| match term.unpack() { ty::TermKind::Ty(ty) => ty.into(), ty::TermKind::Const(ct) => ct.into(), })) })); } - ty::Adt(_, substs) - | ty::Closure(_, substs) - | ty::Generator(_, substs, _) - | ty::GeneratorWitnessMIR(_, substs) - | ty::FnDef(_, substs) => { - stack.extend(substs.iter().rev()); + ty::Adt(_, args) + | ty::Closure(_, args) + | ty::Generator(_, args, _) + | ty::GeneratorWitnessMIR(_, args) + | ty::FnDef(_, args) => { + stack.extend(args.iter().rev()); } ty::Tuple(ts) => stack.extend(ts.iter().rev().map(GenericArg::from)), ty::GeneratorWitness(ts) => { @@ -233,7 +233,7 @@ fn push_inner<'tcx>(stack: &mut TypeWalkerStack<'tcx>, parent: GenericArg<'tcx>) }, ty::ConstKind::Unevaluated(ct) => { - stack.extend(ct.substs.iter().rev()); + stack.extend(ct.args.iter().rev()); } } } diff --git a/compiler/rustc_middle/src/util/bug.rs b/compiler/rustc_middle/src/util/bug.rs index 43ee0343f5aa6..634ed5ec54ba4 100644 --- a/compiler/rustc_middle/src/util/bug.rs +++ b/compiler/rustc_middle/src/util/bug.rs @@ -29,7 +29,7 @@ fn opt_span_bug_fmt>( location: &Location<'_>, ) -> ! { tls::with_opt(move |tcx| { - let msg = format!("{}: {}", location, args); + let msg = format!("{location}: {args}"); match (tcx, span) { (Some(tcx), Some(span)) => tcx.sess.diagnostic().span_bug(span, msg), (Some(tcx), None) => tcx.sess.diagnostic().bug(msg), diff --git a/compiler/rustc_middle/src/util/call_kind.rs b/compiler/rustc_middle/src/util/call_kind.rs index 98d55ea6d4025..4e2a2c6ae0acc 100644 --- a/compiler/rustc_middle/src/util/call_kind.rs +++ b/compiler/rustc_middle/src/util/call_kind.rs @@ -2,7 +2,7 @@ //! as well as errors when attempting to call a non-const function in a const //! context. -use crate::ty::subst::SubstsRef; +use crate::ty::GenericArgsRef; use crate::ty::{AssocItemContainer, Instance, ParamEnv, Ty, TyCtxt}; use rustc_hir::def_id::DefId; use rustc_hir::{lang_items, LangItem}; @@ -43,7 +43,7 @@ pub enum CallKind<'tcx> { self_arg: Option, desugaring: Option<(CallDesugaringKind, Ty<'tcx>)>, method_did: DefId, - method_substs: SubstsRef<'tcx>, + method_args: GenericArgsRef<'tcx>, }, /// A call to `Fn(..)::call(..)`, desugared from `my_closure(a, b, c)` FnCall { fn_trait_id: DefId, self_ty: Ty<'tcx> }, @@ -63,7 +63,7 @@ pub fn call_kind<'tcx>( tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, method_did: DefId, - method_substs: SubstsRef<'tcx>, + method_args: GenericArgsRef<'tcx>, fn_call_span: Span, from_hir_call: bool, self_arg: Option, @@ -92,19 +92,19 @@ pub fn call_kind<'tcx>( // an FnOnce call, an operator (e.g. `<<`), or a // deref coercion. let kind = if let Some(trait_id) = fn_call { - Some(CallKind::FnCall { fn_trait_id: trait_id, self_ty: method_substs.type_at(0) }) + Some(CallKind::FnCall { fn_trait_id: trait_id, self_ty: method_args.type_at(0) }) } else if let Some(trait_id) = operator { - Some(CallKind::Operator { self_arg, trait_id, self_ty: method_substs.type_at(0) }) + Some(CallKind::Operator { self_arg, trait_id, self_ty: method_args.type_at(0) }) } else if is_deref { let deref_target = tcx.get_diagnostic_item(sym::deref_target).and_then(|deref_target| { - Instance::resolve(tcx, param_env, deref_target, method_substs).transpose() + Instance::resolve(tcx, param_env, deref_target, method_args).transpose() }); if let Some(Ok(instance)) = deref_target { let deref_target_ty = instance.ty(tcx, param_env); Some(CallKind::DerefCoercion { deref_target: tcx.def_span(instance.def_id()), deref_target_ty, - self_ty: method_substs.type_at(0), + self_ty: method_args.type_at(0), }) } else { None @@ -119,24 +119,24 @@ pub fn call_kind<'tcx>( let desugaring = if Some(method_did) == tcx.lang_items().into_iter_fn() && fn_call_span.desugaring_kind() == Some(DesugaringKind::ForLoop) { - Some((CallDesugaringKind::ForLoopIntoIter, method_substs.type_at(0))) + Some((CallDesugaringKind::ForLoopIntoIter, method_args.type_at(0))) } else if fn_call_span.desugaring_kind() == Some(DesugaringKind::QuestionMark) { if Some(method_did) == tcx.lang_items().branch_fn() { - Some((CallDesugaringKind::QuestionBranch, method_substs.type_at(0))) + Some((CallDesugaringKind::QuestionBranch, method_args.type_at(0))) } else if Some(method_did) == tcx.lang_items().from_residual_fn() { - Some((CallDesugaringKind::QuestionFromResidual, method_substs.type_at(0))) + Some((CallDesugaringKind::QuestionFromResidual, method_args.type_at(0))) } else { None } } else if Some(method_did) == tcx.lang_items().from_output_fn() && fn_call_span.desugaring_kind() == Some(DesugaringKind::TryBlock) { - Some((CallDesugaringKind::TryBlockFromOutput, method_substs.type_at(0))) + Some((CallDesugaringKind::TryBlockFromOutput, method_args.type_at(0))) } else if fn_call_span.is_desugaring(DesugaringKind::Await) { - Some((CallDesugaringKind::Await, method_substs.type_at(0))) + Some((CallDesugaringKind::Await, method_args.type_at(0))) } else { None }; - CallKind::Normal { self_arg, desugaring, method_did, method_substs } + CallKind::Normal { self_arg, desugaring, method_did, method_args } }) } diff --git a/compiler/rustc_middle/src/util/common.rs b/compiler/rustc_middle/src/util/common.rs index 08977049db02d..df101a2f6e4c6 100644 --- a/compiler/rustc_middle/src/util/common.rs +++ b/compiler/rustc_middle/src/util/common.rs @@ -17,7 +17,7 @@ pub fn to_readable_str(mut val: usize) -> String { groups.push(group.to_string()); break; } else { - groups.push(format!("{:03}", group)); + groups.push(format!("{group:03}")); } } diff --git a/compiler/rustc_middle/src/util/find_self_call.rs b/compiler/rustc_middle/src/util/find_self_call.rs index 0eab0adf07e48..1b845334c49b5 100644 --- a/compiler/rustc_middle/src/util/find_self_call.rs +++ b/compiler/rustc_middle/src/util/find_self_call.rs @@ -1,5 +1,5 @@ use crate::mir::*; -use crate::ty::subst::SubstsRef; +use crate::ty::GenericArgsRef; use crate::ty::{self, TyCtxt}; use rustc_span::def_id::DefId; @@ -11,21 +11,21 @@ pub fn find_self_call<'tcx>( body: &Body<'tcx>, local: Local, block: BasicBlock, -) -> Option<(DefId, SubstsRef<'tcx>)> { +) -> Option<(DefId, GenericArgsRef<'tcx>)> { debug!("find_self_call(local={:?}): terminator={:?}", local, &body[block].terminator); if let Some(Terminator { kind: TerminatorKind::Call { func, args, .. }, .. }) = &body[block].terminator { debug!("find_self_call: func={:?}", func); if let Operand::Constant(box Constant { literal, .. }) = func { - if let ty::FnDef(def_id, substs) = *literal.ty().kind() { + if let ty::FnDef(def_id, fn_args) = *literal.ty().kind() { if let Some(ty::AssocItem { fn_has_self_parameter: true, .. }) = tcx.opt_associated_item(def_id) { - debug!("find_self_call: args={:?}", args); + debug!("find_self_call: args={:?}", fn_args); if let [Operand::Move(self_place) | Operand::Copy(self_place), ..] = **args { if self_place.as_local() == Some(local) { - return Some((def_id, substs)); + return Some((def_id, fn_args)); } } } diff --git a/compiler/rustc_middle/src/values.rs b/compiler/rustc_middle/src/values.rs index c62c33d4dfc18..384a368434a23 100644 --- a/compiler/rustc_middle/src/values.rs +++ b/compiler/rustc_middle/src/values.rs @@ -16,7 +16,7 @@ impl<'tcx> Value, DepKind> for Ty<'_> { fn from_cycle_error(tcx: TyCtxt<'tcx>, _: &[QueryInfo]) -> Self { // SAFETY: This is never called when `Self` is not `Ty<'tcx>`. // FIXME: Represent the above fact in the trait system somehow. - unsafe { std::mem::transmute::, Ty<'_>>(tcx.ty_error_misc()) } + unsafe { std::mem::transmute::, Ty<'_>>(Ty::new_misc_error(tcx)) } } } @@ -34,7 +34,7 @@ impl<'tcx> Value, DepKind> for ty::SymbolName<'_> { impl<'tcx> Value, DepKind> for ty::Binder<'_, ty::FnSig<'_>> { fn from_cycle_error(tcx: TyCtxt<'tcx>, stack: &[QueryInfo]) -> Self { - let err = tcx.ty_error_misc(); + let err = Ty::new_misc_error(tcx); let arity = if let Some(frame) = stack.get(0) && frame.query.dep_kind == DepKind::fn_sig @@ -96,19 +96,22 @@ impl<'tcx> Value, DepKind> for Representability { impl<'tcx> Value, DepKind> for ty::EarlyBinder> { fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle: &[QueryInfo]) -> Self { - ty::EarlyBinder(Ty::from_cycle_error(tcx, cycle)) + ty::EarlyBinder::bind(Ty::from_cycle_error(tcx, cycle)) } } impl<'tcx> Value, DepKind> for ty::EarlyBinder>> { fn from_cycle_error(tcx: TyCtxt<'tcx>, cycle: &[QueryInfo]) -> Self { - ty::EarlyBinder(ty::Binder::from_cycle_error(tcx, cycle)) + ty::EarlyBinder::bind(ty::Binder::from_cycle_error(tcx, cycle)) } } -impl<'tcx, T> Value, DepKind> for Result> { +impl<'tcx, T> Value, DepKind> for Result> { fn from_cycle_error(_tcx: TyCtxt<'tcx>, _cycle: &[QueryInfo]) -> Self { - Err(ty::layout::LayoutError::Cycle) + // tcx.arena.alloc cannot be used because we are not allowed to use &'tcx LayoutError under + // min_specialization. Since this is an error path anyways, leaking doesn't matter (and really, + // tcx.arena.alloc is pretty much equal to leaking). + Err(Box::leak(Box::new(ty::layout::LayoutError::Cycle))) } } @@ -206,7 +209,7 @@ fn find_item_ty_spans( match ty.kind { hir::TyKind::Path(hir::QPath::Resolved(_, path)) => { if let Res::Def(kind, def_id) = path.res - && kind != DefKind::TyAlias { + && !matches!(kind, DefKind::TyAlias { .. }) { let check_params = def_id.as_local().map_or(true, |def_id| { if def_id == needle { spans.push(ty.span); diff --git a/compiler/rustc_mir_build/Cargo.toml b/compiler/rustc_mir_build/Cargo.toml index 58449ee9eb48d..c7e2c625ce57a 100644 --- a/compiler/rustc_mir_build/Cargo.toml +++ b/compiler/rustc_mir_build/Cargo.toml @@ -10,7 +10,7 @@ rustc_arena = { path = "../rustc_arena" } tracing = "0.1" either = "1" rustc_middle = { path = "../rustc_middle" } -rustc_apfloat = { path = "../rustc_apfloat" } +rustc_apfloat = "0.2.0" rustc_data_structures = { path = "../rustc_data_structures" } rustc_index = { path = "../rustc_index" } rustc_errors = { path = "../rustc_errors" } diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl index 0282492a261c8..938f3edd31b05 100644 --- a/compiler/rustc_mir_build/messages.ftl +++ b/compiler/rustc_mir_build/messages.ftl @@ -1,62 +1,40 @@ -mir_build_unconditional_recursion = function cannot return without recursing - .label = cannot return without recursing - .help = a `loop` may express intention better if this is on purpose - -mir_build_unconditional_recursion_call_site_label = recursive call site - -mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe = - call to unsafe function `{$function}` is unsafe and requires unsafe block (error E0133) - .note = consult the function's documentation for information on how to avoid undefined behavior - .label = call to unsafe function - -mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe_nameless = - call to unsafe function is unsafe and requires unsafe block (error E0133) - .note = consult the function's documentation for information on how to avoid undefined behavior - .label = call to unsafe function - -mir_build_unsafe_op_in_unsafe_fn_inline_assembly_requires_unsafe = - use of inline assembly is unsafe and requires unsafe block (error E0133) - .note = inline assembly is entirely unchecked and can cause undefined behavior - .label = use of inline assembly +mir_build_adt_defined_here = `{$ty}` defined here -mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_requires_unsafe = - initializing type with `rustc_layout_scalar_valid_range` attr is unsafe and requires unsafe - block (error E0133) - .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior - .label = initializing type with `rustc_layout_scalar_valid_range` attr +mir_build_already_borrowed = cannot borrow value as mutable because it is also borrowed as immutable -mir_build_unsafe_op_in_unsafe_fn_mutable_static_requires_unsafe = - use of mutable static is unsafe and requires unsafe block (error E0133) - .note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior - .label = use of mutable static +mir_build_already_mut_borrowed = cannot borrow value as immutable because it is also borrowed as mutable -mir_build_unsafe_op_in_unsafe_fn_extern_static_requires_unsafe = - use of extern static is unsafe and requires unsafe block (error E0133) - .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior - .label = use of extern static +mir_build_assoc_const_in_pattern = associated consts cannot be referenced in patterns -mir_build_unsafe_op_in_unsafe_fn_deref_raw_pointer_requires_unsafe = - dereference of raw pointer is unsafe and requires unsafe block (error E0133) - .note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior - .label = dereference of raw pointer +mir_build_bindings_with_variant_name = + pattern binding `{$name}` is named the same as one of the variants of the type `{$ty_path}` + .suggestion = to match on the variant, qualify the path -mir_build_unsafe_op_in_unsafe_fn_union_field_requires_unsafe = - access to union field is unsafe and requires unsafe block (error E0133) - .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior - .label = access to union field +mir_build_borrow = value is borrowed by `{$name}` here -mir_build_unsafe_op_in_unsafe_fn_mutation_of_layout_constrained_field_requires_unsafe = - mutation of layout constrained field is unsafe and requires unsafe block (error E0133) - .note = mutating layout constrained fields cannot statically be checked for valid values - .label = mutation of layout constrained field +mir_build_borrow_of_layout_constrained_field_requires_unsafe = + borrow of layout constrained field with interior mutability is unsafe and requires unsafe block + .note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values + .label = borrow of layout constrained field with interior mutability -mir_build_unsafe_op_in_unsafe_fn_borrow_of_layout_constrained_field_requires_unsafe = - borrow of layout constrained field with interior mutability is unsafe and requires unsafe block (error E0133) +mir_build_borrow_of_layout_constrained_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + borrow of layout constrained field with interior mutability is unsafe and requires unsafe function or block .note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values .label = borrow of layout constrained field with interior mutability -mir_build_unsafe_op_in_unsafe_fn_call_to_fn_with_requires_unsafe = - call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block (error E0133) +mir_build_borrow_of_moved_value = borrow of moved value + .label = value moved into `{$name}` here + .occurs_because_label = move occurs because `{$name}` has type `{$ty}` which does not implement the `Copy` trait + .value_borrowed_label = value borrowed here after move + .suggestion = borrow this binding in the pattern to avoid moving the value + +mir_build_call_to_fn_with_requires_unsafe = + call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block + .note = can only be called if the required target features are available + .label = call to function with `#[target_feature]` + +mir_build_call_to_fn_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe function or block .note = can only be called if the required target features are available .label = call to function with `#[target_feature]` @@ -70,55 +48,24 @@ mir_build_call_to_unsafe_fn_requires_unsafe_nameless = .note = consult the function's documentation for information on how to avoid undefined behavior .label = call to unsafe function -mir_build_call_to_unsafe_fn_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - call to unsafe function `{$function}` is unsafe and requires unsafe function or block - .note = consult the function's documentation for information on how to avoid undefined behavior - .label = call to unsafe function - mir_build_call_to_unsafe_fn_requires_unsafe_nameless_unsafe_op_in_unsafe_fn_allowed = call to unsafe function is unsafe and requires unsafe function or block .note = consult the function's documentation for information on how to avoid undefined behavior .label = call to unsafe function -mir_build_inline_assembly_requires_unsafe = - use of inline assembly is unsafe and requires unsafe block - .note = inline assembly is entirely unchecked and can cause undefined behavior - .label = use of inline assembly - -mir_build_inline_assembly_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - use of inline assembly is unsafe and requires unsafe function or block - .note = inline assembly is entirely unchecked and can cause undefined behavior - .label = use of inline assembly - -mir_build_initializing_type_with_requires_unsafe = - initializing type with `rustc_layout_scalar_valid_range` attr is unsafe and requires unsafe block - .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior - .label = initializing type with `rustc_layout_scalar_valid_range` attr - -mir_build_initializing_type_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - initializing type with `rustc_layout_scalar_valid_range` attr is unsafe and requires unsafe function or block - .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior - .label = initializing type with `rustc_layout_scalar_valid_range` attr +mir_build_call_to_unsafe_fn_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + call to unsafe function `{$function}` is unsafe and requires unsafe function or block + .note = consult the function's documentation for information on how to avoid undefined behavior + .label = call to unsafe function -mir_build_mutable_static_requires_unsafe = - use of mutable static is unsafe and requires unsafe block - .note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior - .label = use of mutable static +mir_build_confused = missing patterns are not covered because `{$variable}` is interpreted as a constant pattern, not a new variable -mir_build_mutable_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - use of mutable static is unsafe and requires unsafe function or block - .note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior - .label = use of mutable static +mir_build_const_param_in_pattern = const parameters cannot be referenced in patterns -mir_build_extern_static_requires_unsafe = - use of extern static is unsafe and requires unsafe block - .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior - .label = use of extern static +mir_build_const_pattern_depends_on_generic_parameter = + constant pattern depends on a generic parameter -mir_build_extern_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - use of extern static is unsafe and requires unsafe function or block - .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior - .label = use of extern static +mir_build_could_not_eval_const_pattern = could not evaluate constant pattern mir_build_deref_raw_pointer_requires_unsafe = dereference of raw pointer is unsafe and requires unsafe block @@ -130,117 +77,46 @@ mir_build_deref_raw_pointer_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = .note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior .label = dereference of raw pointer -mir_build_union_field_requires_unsafe = - access to union field is unsafe and requires unsafe block - .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior - .label = access to union field - -mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - access to union field is unsafe and requires unsafe function or block - .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior - .label = access to union field - -mir_build_mutation_of_layout_constrained_field_requires_unsafe = - mutation of layout constrained field is unsafe and requires unsafe block - .note = mutating layout constrained fields cannot statically be checked for valid values - .label = mutation of layout constrained field - -mir_build_mutation_of_layout_constrained_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - mutation of layout constrained field is unsafe and requires unsafe function or block - .note = mutating layout constrained fields cannot statically be checked for valid values - .label = mutation of layout constrained field - -mir_build_borrow_of_layout_constrained_field_requires_unsafe = - borrow of layout constrained field with interior mutability is unsafe and requires unsafe block - .note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values - .label = borrow of layout constrained field with interior mutability - -mir_build_borrow_of_layout_constrained_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - borrow of layout constrained field with interior mutability is unsafe and requires unsafe function or block - .note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values - .label = borrow of layout constrained field with interior mutability - -mir_build_call_to_fn_with_requires_unsafe = - call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block - .note = can only be called if the required target features are available - .label = call to function with `#[target_feature]` - -mir_build_call_to_fn_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = - call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe function or block - .note = can only be called if the required target features are available - .label = call to function with `#[target_feature]` - -mir_build_unused_unsafe = unnecessary `unsafe` block - .label = unnecessary `unsafe` block - -mir_build_unused_unsafe_enclosing_block_label = because it's nested under this `unsafe` block -mir_build_unused_unsafe_enclosing_fn_label = because it's nested under this `unsafe` fn - -mir_build_non_exhaustive_patterns_type_not_empty = non-exhaustive patterns: type `{$ty}` is non-empty - .def_note = `{$peeled_ty}` defined here - .type_note = the matched value is of type `{$ty}` - .non_exhaustive_type_note = the matched value is of type `{$ty}`, which is marked as non-exhaustive - .reference_note = references are always considered inhabited - .suggestion = ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown - .help = ensure that all possible cases are being handled by adding a match arm with a wildcard pattern - -mir_build_static_in_pattern = statics cannot be referenced in patterns - -mir_build_assoc_const_in_pattern = associated consts cannot be referenced in patterns - -mir_build_const_param_in_pattern = const parameters cannot be referenced in patterns +mir_build_extern_static_requires_unsafe = + use of extern static is unsafe and requires unsafe block + .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior + .label = use of extern static -mir_build_non_const_path = runtime values cannot be referenced in patterns +mir_build_extern_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + use of extern static is unsafe and requires unsafe function or block + .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior + .label = use of extern static -mir_build_unreachable_pattern = unreachable pattern - .label = unreachable pattern - .catchall_label = matches any value +mir_build_float_pattern = floating-point types cannot be used in patterns -mir_build_const_pattern_depends_on_generic_parameter = - constant pattern depends on a generic parameter +mir_build_indirect_structural_match = + to use a constant of type `{$non_sm_ty}` in a pattern, `{$non_sm_ty}` must be annotated with `#[derive(PartialEq, Eq)]` -mir_build_could_not_eval_const_pattern = could not evaluate constant pattern +mir_build_inform_irrefutable = `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant -mir_build_lower_range_bound_must_be_less_than_or_equal_to_upper = - lower range bound must be less than or equal to upper - .label = lower bound larger than upper bound - .teach_note = When matching against a range, the compiler verifies that the range is non-empty. Range patterns include both end-points, so this is equivalent to requiring the start of the range to be less than or equal to the end of the range. +mir_build_initializing_type_with_requires_unsafe = + initializing type with `rustc_layout_scalar_valid_range` attr is unsafe and requires unsafe block + .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior + .label = initializing type with `rustc_layout_scalar_valid_range` attr -mir_build_literal_in_range_out_of_bounds = - literal out of range for `{$ty}` - .label = this value doesn't fit in `{$ty}` whose maximum value is `{$max}` +mir_build_initializing_type_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + initializing type with `rustc_layout_scalar_valid_range` attr is unsafe and requires unsafe function or block + .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior + .label = initializing type with `rustc_layout_scalar_valid_range` attr -mir_build_lower_range_bound_must_be_less_than_upper = lower range bound must be less than upper +mir_build_inline_assembly_requires_unsafe = + use of inline assembly is unsafe and requires unsafe block + .note = inline assembly is entirely unchecked and can cause undefined behavior + .label = use of inline assembly -mir_build_leading_irrefutable_let_patterns = leading irrefutable {$count -> - [one] pattern - *[other] patterns - } in let chain - .note = {$count -> - [one] this pattern - *[other] these patterns - } will always match - .help = consider moving {$count -> - [one] it - *[other] them - } outside of the construct +mir_build_inline_assembly_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + use of inline assembly is unsafe and requires unsafe function or block + .note = inline assembly is entirely unchecked and can cause undefined behavior + .label = use of inline assembly -mir_build_trailing_irrefutable_let_patterns = trailing irrefutable {$count -> - [one] pattern - *[other] patterns - } in let chain - .note = {$count -> - [one] this pattern - *[other] these patterns - } will always match - .help = consider moving {$count -> - [one] it - *[other] them - } into the body +mir_build_interpreted_as_const = introduce a variable instead -mir_build_bindings_with_variant_name = - pattern binding `{$name}` is named the same as one of the variants of the type `{$ty_path}` - .suggestion = to match on the variant, qualify the path +mir_build_invalid_pattern = `{$non_sm_ty}` cannot be used in patterns mir_build_irrefutable_let_patterns_if_let = irrefutable `if let` {$count -> [one] pattern @@ -282,80 +158,100 @@ mir_build_irrefutable_let_patterns_while_let = irrefutable `while let` {$count - } will always match, so the loop will never exit .help = consider instead using a `loop {"{"} ... {"}"}` with a `let` inside it -mir_build_borrow_of_moved_value = borrow of moved value - .label = value moved into `{$name}` here - .occurs_because_label = move occurs because `{$name}` has type `{$ty}` which does not implement the `Copy` trait - .value_borrowed_label = value borrowed here after move - .suggestion = borrow this binding in the pattern to avoid moving the value +mir_build_leading_irrefutable_let_patterns = leading irrefutable {$count -> + [one] pattern + *[other] patterns + } in let chain + .note = {$count -> + [one] this pattern + *[other] these patterns + } will always match + .help = consider moving {$count -> + [one] it + *[other] them + } outside of the construct -mir_build_multiple_mut_borrows = cannot borrow value as mutable more than once at a time +mir_build_literal_in_range_out_of_bounds = + literal out of range for `{$ty}` + .label = this value doesn't fit in `{$ty}` whose maximum value is `{$max}` -mir_build_already_borrowed = cannot borrow value as mutable because it is also borrowed as immutable +mir_build_lower_range_bound_must_be_less_than_or_equal_to_upper = + lower range bound must be less than or equal to upper + .label = lower bound larger than upper bound + .teach_note = When matching against a range, the compiler verifies that the range is non-empty. Range patterns include both end-points, so this is equivalent to requiring the start of the range to be less than or equal to the end of the range. -mir_build_already_mut_borrowed = cannot borrow value as immutable because it is also borrowed as mutable +mir_build_lower_range_bound_must_be_less_than_upper = lower range bound must be less than upper + +mir_build_more_information = for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html + +mir_build_moved = value is moved into `{$name}` here mir_build_moved_while_borrowed = cannot move out of value because it is borrowed -mir_build_mutable_borrow = value is mutably borrowed by `{$name}` here +mir_build_multiple_mut_borrows = cannot borrow value as mutable more than once at a time -mir_build_borrow = value is borrowed by `{$name}` here +mir_build_mutable_borrow = value is mutably borrowed by `{$name}` here -mir_build_moved = value is moved into `{$name}` here +mir_build_mutable_static_requires_unsafe = + use of mutable static is unsafe and requires unsafe block + .note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior + .label = use of mutable static -mir_build_union_pattern = cannot use unions in constant patterns +mir_build_mutable_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + use of mutable static is unsafe and requires unsafe function or block + .note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior + .label = use of mutable static -mir_build_type_not_structural = - to use a constant of type `{$non_sm_ty}` in a pattern, `{$non_sm_ty}` must be annotated with `#[derive(PartialEq, Eq)]` +mir_build_mutation_of_layout_constrained_field_requires_unsafe = + mutation of layout constrained field is unsafe and requires unsafe block + .note = mutating layout constrained fields cannot statically be checked for valid values + .label = mutation of layout constrained field -mir_build_unsized_pattern = cannot use unsized non-slice type `{$non_sm_ty}` in constant patterns +mir_build_mutation_of_layout_constrained_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + mutation of layout constrained field is unsafe and requires unsafe function or block + .note = mutating layout constrained fields cannot statically be checked for valid values + .label = mutation of layout constrained field -mir_build_invalid_pattern = `{$non_sm_ty}` cannot be used in patterns +mir_build_non_const_path = runtime values cannot be referenced in patterns -mir_build_float_pattern = floating-point types cannot be used in patterns +mir_build_non_exhaustive_match_all_arms_guarded = + match arms with guards don't count towards exhaustivity -mir_build_pointer_pattern = function pointers and unsized pointers in patterns behave unpredictably and should not be relied upon. See https://github.com/rust-lang/rust/issues/70861 for details. +mir_build_non_exhaustive_omitted_pattern = some variants are not matched explicitly + .help = ensure that all variants are matched explicitly by adding the suggested match arms + .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found -mir_build_indirect_structural_match = - to use a constant of type `{$non_sm_ty}` in a pattern, `{$non_sm_ty}` must be annotated with `#[derive(PartialEq, Eq)]` +mir_build_non_exhaustive_patterns_type_not_empty = non-exhaustive patterns: type `{$ty}` is non-empty + .def_note = `{$peeled_ty}` defined here + .type_note = the matched value is of type `{$ty}` + .non_exhaustive_type_note = the matched value is of type `{$ty}`, which is marked as non-exhaustive + .reference_note = references are always considered inhabited + .suggestion = ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown + .help = ensure that all possible cases are being handled by adding a match arm with a wildcard pattern mir_build_nontrivial_structural_match = to use a constant of type `{$non_sm_ty}` in a pattern, the constant's initializer must be trivial or `{$non_sm_ty}` must be annotated with `#[derive(PartialEq, Eq)]` -mir_build_type_not_structural_tip = the traits must be derived, manual `impl`s are not sufficient - -mir_build_type_not_structural_more_info = see https://doc.rust-lang.org/stable/std/marker/trait.StructuralEq.html for details - mir_build_overlapping_range_endpoints = multiple patterns overlap on their endpoints .range = ... with this range .note = you likely meant to write mutually exclusive ranges -mir_build_non_exhaustive_omitted_pattern = some variants are not matched explicitly - .help = ensure that all variants are matched explicitly by adding the suggested match arms - .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found - -mir_build_uncovered = {$count -> - [1] pattern `{$witness_1}` - [2] patterns `{$witness_1}` and `{$witness_2}` - [3] patterns `{$witness_1}`, `{$witness_2}` and `{$witness_3}` - *[other] patterns `{$witness_1}`, `{$witness_2}`, `{$witness_3}` and {$remainder} more - } not covered - -mir_build_privately_uninhabited = pattern `{$witness_1}` is currently uninhabited, but this variant contains private fields which may become inhabited in the future - mir_build_pattern_not_covered = refutable pattern in {$origin} .pattern_ty = the matched value is of type `{$pattern_ty}` -mir_build_inform_irrefutable = `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant +mir_build_pointer_pattern = function pointers and unsized pointers in patterns behave unpredictably and should not be relied upon. See https://github.com/rust-lang/rust/issues/70861 for details. -mir_build_more_information = for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html +mir_build_privately_uninhabited = pattern `{$witness_1}` is currently uninhabited, but this variant contains private fields which may become inhabited in the future -mir_build_adt_defined_here = `{$ty}` defined here +mir_build_rustc_box_attribute_error = `#[rustc_box]` attribute used incorrectly + .attributes = no other attributes may be applied + .not_box = `#[rustc_box]` may only be applied to a `Box::new()` call + .missing_box = `#[rustc_box]` requires the `owned_box` lang item -mir_build_variant_defined_here = not covered +mir_build_static_in_pattern = statics cannot be referenced in patterns -mir_build_interpreted_as_const = introduce a variable instead +mir_build_suggest_attempted_int_lit = alternatively, you could prepend the pattern with an underscore to define a new named variable; identifiers cannot begin with digits -mir_build_confused = missing patterns are not covered because `{$variable}` is interpreted as a constant pattern, not a new variable mir_build_suggest_if_let = you might want to use `if let` to ignore the {$count -> [one] variant that isn't @@ -367,10 +263,119 @@ mir_build_suggest_let_else = you might want to use `let else` to handle the {$co *[other] variants that aren't } matched -mir_build_suggest_attempted_int_lit = alternatively, you could prepend the pattern with an underscore to define a new named variable; identifiers cannot begin with digits +mir_build_trailing_irrefutable_let_patterns = trailing irrefutable {$count -> + [one] pattern + *[other] patterns + } in let chain + .note = {$count -> + [one] this pattern + *[other] these patterns + } will always match + .help = consider moving {$count -> + [one] it + *[other] them + } into the body + +mir_build_type_not_structural = + to use a constant of type `{$non_sm_ty}` in a pattern, `{$non_sm_ty}` must be annotated with `#[derive(PartialEq, Eq)]` +mir_build_type_not_structural_more_info = see https://doc.rust-lang.org/stable/std/marker/trait.StructuralEq.html for details -mir_build_rustc_box_attribute_error = `#[rustc_box]` attribute used incorrectly - .attributes = no other attributes may be applied - .not_box = `#[rustc_box]` may only be applied to a `Box::new()` call - .missing_box = `#[rustc_box]` requires the `owned_box` lang item +mir_build_type_not_structural_tip = the traits must be derived, manual `impl`s are not sufficient + +mir_build_unconditional_recursion = function cannot return without recursing + .label = cannot return without recursing + .help = a `loop` may express intention better if this is on purpose + +mir_build_unconditional_recursion_call_site_label = recursive call site + +mir_build_uncovered = {$count -> + [1] pattern `{$witness_1}` + [2] patterns `{$witness_1}` and `{$witness_2}` + [3] patterns `{$witness_1}`, `{$witness_2}` and `{$witness_3}` + *[other] patterns `{$witness_1}`, `{$witness_2}`, `{$witness_3}` and {$remainder} more + } not covered + +mir_build_union_field_requires_unsafe = + access to union field is unsafe and requires unsafe block + .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior + .label = access to union field + +mir_build_union_field_requires_unsafe_unsafe_op_in_unsafe_fn_allowed = + access to union field is unsafe and requires unsafe function or block + .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior + .label = access to union field + +mir_build_union_pattern = cannot use unions in constant patterns + +mir_build_unreachable_pattern = unreachable pattern + .label = unreachable pattern + .catchall_label = matches any value + +mir_build_unsafe_not_inherited = items do not inherit unsafety from separate enclosing items + +mir_build_unsafe_op_in_unsafe_fn_borrow_of_layout_constrained_field_requires_unsafe = + borrow of layout constrained field with interior mutability is unsafe and requires unsafe block (error E0133) + .note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values + .label = borrow of layout constrained field with interior mutability + +mir_build_unsafe_op_in_unsafe_fn_call_to_fn_with_requires_unsafe = + call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block (error E0133) + .note = can only be called if the required target features are available + .label = call to function with `#[target_feature]` + +mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe = + call to unsafe function `{$function}` is unsafe and requires unsafe block (error E0133) + .note = consult the function's documentation for information on how to avoid undefined behavior + .label = call to unsafe function + +mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe_nameless = + call to unsafe function is unsafe and requires unsafe block (error E0133) + .note = consult the function's documentation for information on how to avoid undefined behavior + .label = call to unsafe function + +mir_build_unsafe_op_in_unsafe_fn_deref_raw_pointer_requires_unsafe = + dereference of raw pointer is unsafe and requires unsafe block (error E0133) + .note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior + .label = dereference of raw pointer + +mir_build_unsafe_op_in_unsafe_fn_extern_static_requires_unsafe = + use of extern static is unsafe and requires unsafe block (error E0133) + .note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior + .label = use of extern static + +mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_requires_unsafe = + initializing type with `rustc_layout_scalar_valid_range` attr is unsafe and requires unsafe + block (error E0133) + .note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior + .label = initializing type with `rustc_layout_scalar_valid_range` attr + +mir_build_unsafe_op_in_unsafe_fn_inline_assembly_requires_unsafe = + use of inline assembly is unsafe and requires unsafe block (error E0133) + .note = inline assembly is entirely unchecked and can cause undefined behavior + .label = use of inline assembly + +mir_build_unsafe_op_in_unsafe_fn_mutable_static_requires_unsafe = + use of mutable static is unsafe and requires unsafe block (error E0133) + .note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior + .label = use of mutable static + +mir_build_unsafe_op_in_unsafe_fn_mutation_of_layout_constrained_field_requires_unsafe = + mutation of layout constrained field is unsafe and requires unsafe block (error E0133) + .note = mutating layout constrained fields cannot statically be checked for valid values + .label = mutation of layout constrained field + +mir_build_unsafe_op_in_unsafe_fn_union_field_requires_unsafe = + access to union field is unsafe and requires unsafe block (error E0133) + .note = the field may not be properly initialized: using uninitialized data will cause undefined behavior + .label = access to union field + +mir_build_unsized_pattern = cannot use unsized non-slice type `{$non_sm_ty}` in constant patterns + +mir_build_unused_unsafe = unnecessary `unsafe` block + .label = unnecessary `unsafe` block + +mir_build_unused_unsafe_enclosing_block_label = because it's nested under this `unsafe` block +mir_build_unused_unsafe_enclosing_fn_label = because it's nested under this `unsafe` fn + +mir_build_variant_defined_here = not covered diff --git a/compiler/rustc_mir_build/src/build/custom/mod.rs b/compiler/rustc_mir_build/src/build/custom/mod.rs index 32c618828c9b2..e5c2cc6c7bbce 100644 --- a/compiler/rustc_mir_build/src/build/custom/mod.rs +++ b/compiler/rustc_mir_build/src/build/custom/mod.rs @@ -118,7 +118,11 @@ fn parse_attribute(attr: &Attribute) -> MirPhase { phase = Some(value); } other => { - panic!("Unexpected key {}", other); + span_bug!( + nested.span(), + "Unexpected key while parsing custom_mir attribute: '{}'", + other + ); } } } diff --git a/compiler/rustc_mir_build/src/build/custom/parse.rs b/compiler/rustc_mir_build/src/build/custom/parse.rs index 803207d9dc664..60c4a041696bc 100644 --- a/compiler/rustc_mir_build/src/build/custom/parse.rs +++ b/compiler/rustc_mir_build/src/build/custom/parse.rs @@ -74,7 +74,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { kind @ StmtKind::Let { pattern, .. } => { return Err(ParseError { span: pattern.span, - item_description: format!("{:?}", kind), + item_description: format!("{kind:?}"), expected: "expression".to_string(), }); } @@ -241,9 +241,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { }); } - let Some(trailing) = block.expr else { - return Err(self.expr_error(expr_id, "terminator")) - }; + let Some(trailing) = block.expr else { return Err(self.expr_error(expr_id, "terminator")) }; let span = self.thir[trailing].span; let terminator = self.parse_terminator(trailing)?; data.terminator = Some(Terminator { diff --git a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs index b74422708ce5c..26662f5de459f 100644 --- a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs +++ b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs @@ -57,14 +57,13 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { place: self.parse_place(args[0])?, target: self.parse_block(args[1])?, unwind: UnwindAction::Continue, + replace: false, }) }, @call("mir_call", args) => { - let destination = self.parse_place(args[0])?; - let target = self.parse_block(args[1])?; - self.parse_call(args[2], destination, target) + self.parse_call(args) }, - ExprKind::Match { scrutinee, arms } => { + ExprKind::Match { scrutinee, arms, .. } => { let discr = self.parse_operand(*scrutinee)?; self.parse_match(arms, expr.span).map(|t| TerminatorKind::SwitchInt { discr, targets: t }) }, @@ -77,7 +76,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { span, item_description: "no arms".to_string(), expected: "at least one arm".to_string(), - }) + }); }; let otherwise = &self.thir[*otherwise]; @@ -86,7 +85,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { span: otherwise.span, item_description: format!("{:?}", otherwise.pattern.kind), expected: "wildcard pattern".to_string(), - }) + }); }; let otherwise = self.parse_block(otherwise.body)?; @@ -99,7 +98,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { span: arm.pattern.span, item_description: format!("{:?}", arm.pattern.kind), expected: "constant pattern".to_string(), - }) + }); }; values.push(value.eval_bits(self.tcx, self.param_env, arm.pattern.ty)); targets.push(self.parse_block(arm.body)?); @@ -108,13 +107,14 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { Ok(SwitchTargets::new(values.into_iter().zip(targets), otherwise)) } - fn parse_call( - &self, - expr_id: ExprId, - destination: Place<'tcx>, - target: BasicBlock, - ) -> PResult> { - parse_by_kind!(self, expr_id, _, "function call", + fn parse_call(&self, args: &[ExprId]) -> PResult> { + let (destination, call) = parse_by_kind!(self, args[0], _, "function call", + ExprKind::Assign { lhs, rhs } => (*lhs, *rhs), + ); + let destination = self.parse_place(destination)?; + let target = self.parse_block(args[1])?; + + parse_by_kind!(self, call, _, "function call", ExprKind::Call { fun, args, from_hir_call, fn_span, .. } => { let fun = self.parse_operand(*fun)?; let args = args @@ -127,7 +127,9 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { destination, target: Some(target), unwind: UnwindAction::Continue, - from_hir_call: *from_hir_call, + call_source: if *from_hir_call { CallSource::Normal } else { + CallSource::OverloadedOperator + }, fn_span: *fn_span, }) }, @@ -189,12 +191,12 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { fields.iter().map(|e| self.parse_operand(*e)).collect::>()? )) }, - ExprKind::Adt(box AdtExpr{ adt_def, variant_index, substs, fields, .. }) => { + ExprKind::Adt(box AdtExpr{ adt_def, variant_index, args, fields, .. }) => { let is_union = adt_def.is_union(); let active_field_index = is_union.then(|| fields[0].name); Ok(Rvalue::Aggregate( - Box::new(AggregateKind::Adt(adt_def.did(), *variant_index, substs, None, active_field_index)), + Box::new(AggregateKind::Adt(adt_def.did(), *variant_index, args, None, active_field_index)), fields.iter().map(|f| self.parse_operand(f.expr)).collect::>()? )) }, diff --git a/compiler/rustc_mir_build/src/build/expr/as_constant.rs b/compiler/rustc_mir_build/src/build/expr/as_constant.rs index 4d99ab4b0ec29..aaa37446e24d3 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_constant.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_constant.rs @@ -52,7 +52,7 @@ pub fn as_constant_inner<'tcx>( match lit_to_mir_constant(tcx, LitToConstInput { lit: &lit.node, ty, neg }) { Ok(c) => c, Err(LitToConstError::Reported(guar)) => { - ConstantKind::Ty(tcx.const_error(ty, guar)) + ConstantKind::Ty(ty::Const::new_error(tcx, guar, ty)) } Err(LitToConstError::TypeError) => { bug!("encountered type error in `lit_to_mir_constant`") @@ -75,22 +75,22 @@ pub fn as_constant_inner<'tcx>( Constant { span, user_ty, literal } } - ExprKind::NamedConst { def_id, substs, ref user_ty } => { + ExprKind::NamedConst { def_id, args, ref user_ty } => { let user_ty = user_ty.as_ref().and_then(push_cuta); - let uneval = mir::UnevaluatedConst::new(def_id, substs); + let uneval = mir::UnevaluatedConst::new(def_id, args); let literal = ConstantKind::Unevaluated(uneval, ty); Constant { user_ty, span, literal } } ExprKind::ConstParam { param, def_id: _ } => { - let const_param = tcx.mk_const(ty::ConstKind::Param(param), expr.ty); + let const_param = ty::Const::new_param(tcx, param, expr.ty); let literal = ConstantKind::Ty(const_param); Constant { user_ty: None, span, literal } } - ExprKind::ConstBlock { did: def_id, substs } => { - let uneval = mir::UnevaluatedConst::new(def_id, substs); + ExprKind::ConstBlock { did: def_id, args } => { + let uneval = mir::UnevaluatedConst::new(def_id, args); let literal = ConstantKind::Unevaluated(uneval, ty); Constant { user_ty: None, span, literal } @@ -106,7 +106,7 @@ pub fn as_constant_inner<'tcx>( } #[instrument(skip(tcx, lit_input))] -pub(crate) fn lit_to_mir_constant<'tcx>( +fn lit_to_mir_constant<'tcx>( tcx: TyCtxt<'tcx>, lit_input: LitToConstInput<'tcx>, ) -> Result, LitToConstError> { diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs index 7ec57add66b59..2e7ef265a93c2 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs @@ -175,11 +175,8 @@ fn to_upvars_resolved_place_builder<'tcx>( projection: &[PlaceElem<'tcx>], ) -> Option> { let Some((capture_index, capture)) = - find_capture_matching_projections( - &cx.upvars, - var_hir_id, - &projection, - ) else { + find_capture_matching_projections(&cx.upvars, var_hir_id, &projection) + else { let closure_span = cx.tcx.def_span(closure_def_id); if !enable_precise_capture(closure_span) { bug!( @@ -189,10 +186,7 @@ fn to_upvars_resolved_place_builder<'tcx>( projection ) } else { - debug!( - "No associated capture found for {:?}[{:#?}]", - var_hir_id, projection, - ); + debug!("No associated capture found for {:?}[{:#?}]", var_hir_id, projection,); } return None; }; @@ -242,6 +236,9 @@ fn strip_prefix<'a, 'tcx>( } assert_matches!(iter.next(), Some(ProjectionElem::Field(..))); } + HirProjectionKind::OpaqueCast => { + assert_matches!(iter.next(), Some(ProjectionElem::OpaqueCast(..))); + } HirProjectionKind::Index | HirProjectionKind::Subslice => { bug!("unexpected projection kind: {:?}", projection); } @@ -535,7 +532,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Cast { .. } | ExprKind::Use { .. } | ExprKind::NeverToAny { .. } - | ExprKind::Pointer { .. } + | ExprKind::PointerCoercion { .. } | ExprKind::Repeat { .. } | ExprKind::Borrow { .. } | ExprKind::AddressOf { .. } @@ -549,6 +546,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::Become { .. } | ExprKind::Literal { .. } | ExprKind::NamedConst { .. } | ExprKind::NonHirLiteral { .. } @@ -677,21 +675,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // check that we just did stays valid. Since we can't assign to // unsized values, we only need to ensure that none of the // pointers in the base place are modified. - for (idx, elem) in base_place.projection.iter().enumerate().rev() { + for (base_place, elem) in base_place.iter_projections().rev() { match elem { ProjectionElem::Deref => { - let fake_borrow_deref_ty = Place::ty_from( - base_place.local, - &base_place.projection[..idx], - &self.local_decls, - tcx, - ) - .ty; + let fake_borrow_deref_ty = base_place.ty(&self.local_decls, tcx).ty; let fake_borrow_ty = - tcx.mk_imm_ref(tcx.lifetimes.re_erased, fake_borrow_deref_ty); + Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fake_borrow_deref_ty); let fake_borrow_temp = self.local_decls.push(LocalDecl::new(fake_borrow_ty, expr_span)); - let projection = tcx.mk_place_elems(&base_place.projection[..idx]); + let projection = tcx.mk_place_elems(&base_place.projection); self.cfg.push_assign( block, source_info, @@ -705,12 +697,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { fake_borrow_temps.push(fake_borrow_temp); } ProjectionElem::Index(_) => { - let index_ty = Place::ty_from( - base_place.local, - &base_place.projection[..idx], - &self.local_decls, - tcx, - ); + let index_ty = base_place.ty(&self.local_decls, tcx); match index_ty.ty.kind() { // The previous index expression has already // done any index expressions needed here. @@ -746,5 +733,5 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// Precise capture is enabled if user is using Rust Edition 2021 or higher. fn enable_precise_capture(closure_span: Span) -> bool { - closure_span.rust_2021() + closure_span.at_least_rust_2021() } diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs index bcab4c0d24b5f..3220a184d49ba 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs @@ -16,7 +16,7 @@ use rustc_middle::mir::*; use rustc_middle::thir::*; use rustc_middle::ty::cast::{mir_cast_kind, CastTy}; use rustc_middle::ty::layout::IntegerExt; -use rustc_middle::ty::{self, Ty, UpvarSubsts}; +use rustc_middle::ty::{self, Ty, UpvarArgs}; use rustc_span::Span; impl<'a, 'tcx> Builder<'a, 'tcx> { @@ -162,7 +162,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { [], expr_span, ); - let storage = this.temp(tcx.mk_mut_ptr(tcx.types.u8), expr_span); + let storage = this.temp(Ty::new_mut_ptr(tcx, tcx.types.u8), expr_span); let success = this.cfg.start_new_block(); this.cfg.terminate( block, @@ -173,7 +173,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { destination: storage, target: Some(success), unwind: UnwindAction::Continue, - from_hir_call: false, + call_source: CallSource::Misc, fn_span: expr_span, }, ); @@ -300,7 +300,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let cast_kind = mir_cast_kind(ty, expr.ty); block.and(Rvalue::Cast(cast_kind, source, expr.ty)) } - ExprKind::Pointer { cast, source } => { + ExprKind::PointerCoercion { cast, source } => { let source = unpack!( block = this.as_operand( block, @@ -310,7 +310,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { NeedsTemporary::No ) ); - block.and(Rvalue::Cast(CastKind::Pointer(cast), source, expr.ty)) + block.and(Rvalue::Cast(CastKind::PointerCoercion(cast), source, expr.ty)) } ExprKind::Array { ref fields } => { // (*) We would (maybe) be closer to codegen if we @@ -382,7 +382,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } ExprKind::Closure(box ClosureExpr { closure_id, - substs, + args, ref upvars, movability, ref fake_reads, @@ -442,7 +442,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { match upvar.kind { ExprKind::Borrow { borrow_kind: - BorrowKind::Mut { allow_two_phase_borrow: false }, + BorrowKind::Mut { kind: MutBorrowKind::Default }, arg, } => unpack!( block = this.limit_capture_mutability( @@ -470,19 +470,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }) .collect(); - let result = match substs { - UpvarSubsts::Generator(substs) => { + let result = match args { + UpvarArgs::Generator(args) => { // We implicitly set the discriminant to 0. See // librustc_mir/transform/deaggregator.rs for details. let movability = movability.unwrap(); - Box::new(AggregateKind::Generator( - closure_id.to_def_id(), - substs, - movability, - )) + Box::new(AggregateKind::Generator(closure_id.to_def_id(), args, movability)) } - UpvarSubsts::Closure(substs) => { - Box::new(AggregateKind::Closure(closure_id.to_def_id(), substs)) + UpvarArgs::Closure(args) => { + Box::new(AggregateKind::Closure(closure_id.to_def_id(), args)) } }; block.and(Rvalue::Aggregate(result, operands)) @@ -532,6 +528,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::Become { .. } | ExprKind::InlineAsm { .. } | ExprKind::PlaceTypeAscription { .. } | ExprKind::ValueTypeAscription { .. } => { @@ -563,7 +560,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let bool_ty = self.tcx.types.bool; let rvalue = match op { BinOp::Add | BinOp::Sub | BinOp::Mul if self.check_overflow && ty.is_integral() => { - let result_tup = self.tcx.mk_tup(&[ty, bool_ty]); + let result_tup = Ty::new_tup(self.tcx, &[ty, bool_ty]); let result_value = self.temp(result_tup, span); self.cfg.push_assign( @@ -597,7 +594,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let (unsigned_rhs, unsigned_ty) = match rhs_ty.kind() { ty::Uint(_) => (rhs.to_copy(), rhs_ty), ty::Int(int_width) => { - let uint_ty = self.tcx.mk_mach_uint(int_width.to_unsigned()); + let uint_ty = Ty::new_uint(self.tcx, int_width.to_unsigned()); let rhs_temp = self.temp(uint_ty, span); self.cfg.push_assign( block, @@ -725,6 +722,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { place: to_drop, target: success, unwind: UnwindAction::Continue, + replace: false, }, ); this.diverge_from(block); @@ -776,8 +774,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Not in a closure debug_assert!( local == ty::CAPTURE_STRUCT_LOCAL, - "Expected local to be Local(1), found {:?}", - local + "Expected local to be Local(1), found {local:?}" ); // Not in a closure debug_assert!( @@ -794,8 +791,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }; let borrow_kind = match mutability { - Mutability::Not => BorrowKind::Unique, - Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false }, + Mutability::Not => BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }, + Mutability::Mut => BorrowKind::Mut { kind: MutBorrowKind::Default }, }; let arg_place = arg_place_builder.to_place(this); diff --git a/compiler/rustc_mir_build/src/build/expr/category.rs b/compiler/rustc_mir_build/src/build/expr/category.rs index d9aa461c19d40..e07ba6b6e9383 100644 --- a/compiler/rustc_mir_build/src/build/expr/category.rs +++ b/compiler/rustc_mir_build/src/build/expr/category.rs @@ -63,7 +63,7 @@ impl Category { | ExprKind::Binary { .. } | ExprKind::Box { .. } | ExprKind::Cast { .. } - | ExprKind::Pointer { .. } + | ExprKind::PointerCoercion { .. } | ExprKind::Repeat { .. } | ExprKind::Assign { .. } | ExprKind::AssignOp { .. } @@ -82,7 +82,8 @@ impl Category { | ExprKind::Block { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } - | ExprKind::Return { .. } => + | ExprKind::Return { .. } + | ExprKind::Become { .. } => // FIXME(#27840) these probably want their own // category, like "nonterminating" { diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs index 29ff916d2cc9c..a5c86e31a2921 100644 --- a/compiler/rustc_mir_build/src/build/expr/into.rs +++ b/compiler/rustc_mir_build/src/build/expr/into.rs @@ -47,7 +47,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Block { block: ast_block } => { this.ast_block(destination, block, ast_block, source_info) } - ExprKind::Match { scrutinee, ref arms } => { + ExprKind::Match { scrutinee, ref arms, .. } => { this.match_expr(destination, expr_span, block, &this.thir[scrutinee], arms) } ExprKind::If { cond, then, else_opt, if_then_scope } => { @@ -277,7 +277,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .ty .is_inhabited_from(this.tcx, this.parent_module, this.param_env) .then_some(success), - from_hir_call, + call_source: if from_hir_call { + CallSource::Normal + } else { + CallSource::OverloadedOperator + }, fn_span, }, ); @@ -313,7 +317,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Adt(box AdtExpr { adt_def, variant_index, - substs, + args, ref user_ty, ref fields, ref base, @@ -378,7 +382,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let adt = Box::new(AggregateKind::Adt( adt_def.did(), variant_index, - substs, + args, user_ty, active_field_index, )); @@ -489,7 +493,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block.unit() } - ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Return { .. } => { + ExprKind::Continue { .. } + | ExprKind::Break { .. } + | ExprKind::Return { .. } + | ExprKind::Become { .. } => { unpack!(block = this.stmt_expr(block, expr, None)); // No assign, as these have type `!`. block.unit() @@ -549,7 +556,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Binary { .. } | ExprKind::Box { .. } | ExprKind::Cast { .. } - | ExprKind::Pointer { .. } + | ExprKind::PointerCoercion { .. } | ExprKind::Repeat { .. } | ExprKind::Array { .. } | ExprKind::Tuple { .. } diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index ea5aeb67d8576..396f82c27cd86 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -99,6 +99,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { BreakableTarget::Return, source_info, ), + // FIXME(explicit_tail_calls): properly lower tail calls here + ExprKind::Become { value } => this.break_scope( + block, + Some(&this.thir[value]), + BreakableTarget::Return, + source_info, + ), _ => { assert!( statement_scope.is_some(), diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 6df06df5c60f4..3c450740712c0 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -607,9 +607,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // }; // ``` if let Some(place) = initializer.try_to_place(self) { - let LocalInfo::User(BindingForm::Var( - VarBindingForm { opt_match_place: Some((ref mut match_place, _)), .. }, - )) = **self.local_decls[local].local_info.as_mut().assert_crate_local() else { + let LocalInfo::User(BindingForm::Var(VarBindingForm { + opt_match_place: Some((ref mut match_place, _)), + .. + })) = **self.local_decls[local].local_info.as_mut().assert_crate_local() + else { bug!("Let binding to non-user variable.") }; *match_place = Some(place); @@ -804,7 +806,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } - PatKind::Variant { adt_def, substs: _, variant_index, ref subpatterns } => { + PatKind::Variant { adt_def, args: _, variant_index, ref subpatterns } => { for subpattern in subpatterns { let subpattern_user_ty = pattern_user_ty.clone().variant(adt_def, variant_index, subpattern.field); @@ -1625,9 +1627,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // at least the first candidate ought to be tested assert!( total_candidate_count > candidates.len(), - "{}, {:#?}", - total_candidate_count, - candidates + "{total_candidate_count}, {candidates:#?}" ); debug!("tested_candidates: {}", total_candidate_count - candidates.len()); debug!("untested_candidates: {}", candidates.len()); @@ -1751,7 +1751,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { projection: tcx.mk_place_elems(matched_place_ref.projection), }; let fake_borrow_deref_ty = matched_place.ty(&self.local_decls, tcx).ty; - let fake_borrow_ty = tcx.mk_imm_ref(tcx.lifetimes.re_erased, fake_borrow_deref_ty); + let fake_borrow_ty = + Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fake_borrow_deref_ty); let mut fake_borrow_temp = LocalDecl::new(fake_borrow_ty, temp_span); fake_borrow_temp.internal = self.local_decls[matched_place.local].internal; fake_borrow_temp.local_info = ClearCrossCrate::Set(Box::new(LocalInfo::FakeBorrow)); @@ -2241,7 +2242,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.var_debug_info.push(VarDebugInfo { name, source_info: debug_source_info, - references: 0, value: VarDebugInfoContents::Place(for_arm_body.into()), argument_index: None, }); @@ -2250,7 +2250,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // This variable isn't mutated but has a name, so has to be // immutable to avoid the unused mut lint. mutability: Mutability::Not, - ty: tcx.mk_imm_ref(tcx.lifetimes.re_erased, var_ty), + ty: Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, var_ty), user_ty: None, source_info, internal: false, @@ -2261,7 +2261,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.var_debug_info.push(VarDebugInfo { name, source_info: debug_source_info, - references: 0, value: VarDebugInfoContents::Place(ref_for_guard.into()), argument_index: None, }); diff --git a/compiler/rustc_mir_build/src/build/matches/simplify.rs b/compiler/rustc_mir_build/src/build/matches/simplify.rs index f6b1955fdec4d..17ac1f4e0cea6 100644 --- a/compiler/rustc_mir_build/src/build/matches/simplify.rs +++ b/compiler/rustc_mir_build/src/build/matches/simplify.rs @@ -259,13 +259,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } - PatKind::Variant { adt_def, substs, variant_index, ref subpatterns } => { + PatKind::Variant { adt_def, args, variant_index, ref subpatterns } => { let irrefutable = adt_def.variants().iter_enumerated().all(|(i, v)| { i == variant_index || { self.tcx.features().exhaustive_patterns && !v .inhabited_predicate(self.tcx, adt_def) - .subst(self.tcx, substs) + .instantiate(self.tcx, args) .apply_ignore_module(self.tcx, self.param_env) } }) && (adt_def.did().is_local() diff --git a/compiler/rustc_mir_build/src/build/matches/test.rs b/compiler/rustc_mir_build/src/build/matches/test.rs index dbdb5b4a9a179..484e8490919d4 100644 --- a/compiler/rustc_mir_build/src/build/matches/test.rs +++ b/compiler/rustc_mir_build/src/build/matches/test.rs @@ -16,7 +16,7 @@ use rustc_middle::mir::*; use rustc_middle::thir::*; use rustc_middle::ty::util::IntTypeExt; use rustc_middle::ty::GenericArg; -use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; +use rustc_middle::ty::{self, adjustment::PointerCoercion, Ty, TyCtxt}; use rustc_span::def_id::DefId; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; @@ -30,7 +30,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// It is a bug to call this with a not-fully-simplified pattern. pub(super) fn test<'pat>(&mut self, match_pair: &MatchPair<'pat, 'tcx>) -> Test<'tcx> { match match_pair.pattern.kind { - PatKind::Variant { adt_def, substs: _, variant_index: _, subpatterns: _ } => Test { + PatKind::Variant { adt_def, args: _, variant_index: _, subpatterns: _ } => Test { span: match_pair.pattern.span, kind: TestKind::Switch { adt_def, @@ -88,7 +88,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { switch_ty: Ty<'tcx>, options: &mut FxIndexMap, u128>, ) -> bool { - let Some(match_pair) = candidate.match_pairs.iter().find(|mp| mp.place == *test_place) else { + let Some(match_pair) = candidate.match_pairs.iter().find(|mp| mp.place == *test_place) + else { return false; }; @@ -126,7 +127,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { candidate: &Candidate<'pat, 'tcx>, variants: &mut BitSet, ) -> bool { - let Some(match_pair) = candidate.match_pairs.iter().find(|mp| mp.place == *test_place) else { + let Some(match_pair) = candidate.match_pairs.iter().find(|mp| mp.place == *test_place) + else { return false; }; @@ -173,16 +175,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { debug_assert_ne!( target_blocks[idx.index()], otherwise_block, - "no candidates for tested discriminant: {:?}", - discr, + "no candidates for tested discriminant: {discr:?}", ); Some((discr.val, target_blocks[idx.index()])) } else { debug_assert_eq!( target_blocks[idx.index()], otherwise_block, - "found candidates for untested discriminant: {:?}", - discr, + "found candidates for untested discriminant: {discr:?}", ); None } @@ -244,8 +244,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { bug!("matching on `String` went through without enabling string_deref_patterns"); } let re_erased = tcx.lifetimes.re_erased; - let ref_string = self.temp(tcx.mk_imm_ref(re_erased, ty), test.span); - let ref_str_ty = tcx.mk_imm_ref(re_erased, tcx.types.str_); + let ref_string = self.temp(Ty::new_imm_ref(tcx,re_erased, ty), test.span); + let ref_str_ty = Ty::new_imm_ref(tcx,re_erased, tcx.types.str_); let ref_str = self.temp(ref_str_ty, test.span); let deref = tcx.require_lang_item(LangItem::Deref, None); let method = trait_method(tcx, deref, sym::deref, [ty]); @@ -264,7 +264,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { destination: ref_str, target: Some(eq_block), unwind: UnwindAction::Continue, - from_hir_call: false, + call_source: CallSource::Misc, fn_span: source_info.span } ); @@ -415,7 +415,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { (Some((region, elem_ty, _)), _) | (None, Some((region, elem_ty, _))) => { let tcx = self.tcx; // make both a slice - ty = tcx.mk_imm_ref(*region, tcx.mk_slice(*elem_ty)); + ty = Ty::new_imm_ref(tcx, *region, Ty::new_slice(tcx, *elem_ty)); if opt_ref_ty.is_some() { let temp = self.temp(ty, source_info.span); self.cfg.push_assign( @@ -423,7 +423,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { source_info, temp, Rvalue::Cast( - CastKind::Pointer(PointerCast::Unsize), + CastKind::PointerCoercion(PointerCoercion::Unsize), Operand::Copy(val), ty, ), @@ -436,7 +436,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, source_info, slice, - Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), expect, ty), + Rvalue::Cast( + CastKind::PointerCoercion(PointerCoercion::Unsize), + expect, + ty, + ), ); expect = Operand::Move(slice); } @@ -449,7 +453,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // non_scalar_compare called on non-reference type let temp = self.temp(ty, source_info.span); self.cfg.push_assign(block, source_info, temp, Rvalue::Use(expect)); - let ref_ty = self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, ty); + let ref_ty = Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, ty); let ref_temp = self.temp(ref_ty, source_info.span); self.cfg.push_assign( @@ -496,7 +500,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { destination: eq_result, target: Some(eq_block), unwind: UnwindAction::Continue, - from_hir_call: false, + call_source: CallSource::MatchCmp, fn_span: source_info.span, }, ); @@ -861,7 +865,7 @@ fn trait_method<'tcx>( tcx: TyCtxt<'tcx>, trait_def_id: DefId, method_name: Symbol, - substs: impl IntoIterator>>, + args: impl IntoIterator>>, ) -> ConstantKind<'tcx> { // The unhygienic comparison here is acceptable because this is only // used on known traits. @@ -871,7 +875,7 @@ fn trait_method<'tcx>( .find(|item| item.kind == ty::AssocKind::Fn) .expect("trait method not found"); - let method_ty = tcx.mk_fn_def(item.def_id, substs); + let method_ty = Ty::new_fn_def(tcx, item.def_id, args); ConstantKind::zero_sized(method_ty) } diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 4e3e98b56e799..2a23a69b58416 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -1,4 +1,3 @@ -pub(crate) use crate::build::expr::as_constant::lit_to_mir_constant; use crate::build::expr::as_place::PlaceBuilder; use crate::build::scope::DropKind; use rustc_apfloat::ieee::{Double, Single}; @@ -11,6 +10,7 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{GeneratorKind, Node}; +use rustc_index::bit_set::GrowableBitSet; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_middle::hir::place::PlaceBase as HirPlaceBase; @@ -37,6 +37,22 @@ pub(crate) fn mir_built( tcx.alloc_steal_mir(mir_build(tcx, def)) } +pub(crate) fn closure_saved_names_of_captured_variables<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: LocalDefId, +) -> IndexVec { + tcx.closure_captures(def_id) + .iter() + .map(|captured_place| { + let name = captured_place.to_symbol(); + match captured_place.info.capture_kind { + ty::UpvarCapture::ByValue => name, + ty::UpvarCapture::ByRef(..) => Symbol::intern(&format!("_ref__{name}")), + } + }) + .collect() +} + /// Construct the MIR for a given `DefId`. fn mir_build(tcx: TyCtxt<'_>, def: LocalDefId) -> Body<'_> { // Ensure unsafeck and abstract const building is ran before we steal the THIR. @@ -78,8 +94,7 @@ fn mir_build(tcx: TyCtxt<'_>, def: LocalDefId) -> Body<'_> { || body.basic_blocks.has_free_regions() || body.var_debug_info.has_free_regions() || body.yield_ty().has_free_regions()), - "Unexpected free regions in MIR: {:?}", - body, + "Unexpected free regions in MIR: {body:?}", ); body @@ -200,6 +215,14 @@ struct Builder<'a, 'tcx> { unit_temp: Option>, var_debug_info: Vec>, + + // A cache for `maybe_lint_level_roots_bounded`. That function is called + // repeatedly, and each time it effectively traces a path through a tree + // structure from a node towards the root, doing an attribute check on each + // node along the way. This cache records which nodes trace all the way to + // the root (most of them do) and saves us from retracing many sub-paths + // many times, and rechecking many nodes. + lint_level_roots_cache: GrowableBitSet, } type CaptureMap<'tcx> = SortedIndexMultiMap>; @@ -458,7 +481,7 @@ fn construct_fn<'tcx>( let (yield_ty, return_ty) = if generator_kind.is_some() { let gen_ty = arguments[thir::UPVAR_ENV_PARAM].ty; let gen_sig = match gen_ty.kind() { - ty::Generator(_, gen_substs, ..) => gen_substs.as_generator().sig(), + ty::Generator(_, gen_args, ..) => gen_args.as_generator().sig(), _ => { span_bug!(span, "generator w/o generator type: {:?}", gen_ty) } @@ -547,7 +570,7 @@ fn construct_const<'a, 'tcx>( // Figure out what primary body this item has. let (span, const_ty_span) = match tcx.hir().get(hir_id) { Node::Item(hir::Item { - kind: hir::ItemKind::Static(ty, _, _) | hir::ItemKind::Const(ty, _), + kind: hir::ItemKind::Static(ty, _, _) | hir::ItemKind::Const(ty, _, _), span, .. }) @@ -557,7 +580,7 @@ fn construct_const<'a, 'tcx>( span, .. }) => (*span, ty.span), - Node::AnonConst(_) => { + Node::AnonConst(_) | Node::ConstBlock(_) => { let span = tcx.def_span(def); (span, span) } @@ -599,15 +622,13 @@ fn construct_error(tcx: TyCtxt<'_>, def: LocalDefId, err: ErrorGuaranteed) -> Bo let generator_kind = tcx.generator_kind(def); let body_owner_kind = tcx.hir().body_owner_kind(def); - let ty = tcx.ty_error(err); + let ty = Ty::new_error(tcx, err); let num_params = match body_owner_kind { hir::BodyOwnerKind::Fn => tcx.fn_sig(def).skip_binder().inputs().skip_binder().len(), hir::BodyOwnerKind::Closure => { - let ty = tcx.type_of(def).subst_identity(); + let ty = tcx.type_of(def).instantiate_identity(); match ty.kind() { - ty::Closure(_, substs) => { - 1 + substs.as_closure().sig().inputs().skip_binder().len() - } + ty::Closure(_, args) => 1 + args.as_closure().sig().inputs().skip_binder().len(), ty::Generator(..) => 2, _ => bug!("expected closure or generator, found {ty:?}"), } @@ -710,6 +731,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { var_indices: Default::default(), unit_temp: None, var_debug_info: vec![], + lint_level_roots_cache: GrowableBitSet::new_empty(), }; assert_eq!(builder.cfg.start_new_block(), START_BLOCK); @@ -753,9 +775,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { closure_ty = *ty; } - let upvar_substs = match closure_ty.kind() { - ty::Closure(_, substs) => ty::UpvarSubsts::Closure(substs), - ty::Generator(_, substs, _) => ty::UpvarSubsts::Generator(substs), + let upvar_args = match closure_ty.kind() { + ty::Closure(_, args) => ty::UpvarArgs::Closure(args), + ty::Generator(_, args, _) => ty::UpvarArgs::Generator(args), _ => return, }; @@ -764,7 +786,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // with the closure's DefId. Here, we run through that vec of UpvarIds for // the given closure and use the necessary information to create upvar // debuginfo and to fill `self.upvars`. - let capture_tys = upvar_substs.upvar_tys(); + let capture_tys = upvar_args.upvar_tys(); let tcx = self.tcx; self.upvars = tcx @@ -798,7 +820,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }; self.var_debug_info.push(VarDebugInfo { name, - references: 0, source_info: SourceInfo::outermost(captured_place.var_ident.span), value: VarDebugInfoContents::Place(use_place), argument_index: None, @@ -829,7 +850,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.var_debug_info.push(VarDebugInfo { name, source_info, - references: 0, value: VarDebugInfoContents::Place(arg_local.into()), argument_index: Some(argument_index as u16 + 1), }); @@ -927,7 +947,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { match self.unit_temp { Some(tmp) => tmp, None => { - let ty = self.tcx.mk_unit(); + let ty = Ty::new_unit(self.tcx); let fn_span = self.fn_span; let tmp = self.temp(ty, fn_span); self.unit_temp = Some(tmp); @@ -954,9 +974,9 @@ pub(crate) fn parse_float_into_scalar( match float_ty { ty::FloatTy::F32 => { let Ok(rust_f) = num.parse::() else { return None }; - let mut f = num.parse::().unwrap_or_else(|e| { - panic!("apfloat::ieee::Single failed to parse `{}`: {:?}", num, e) - }); + let mut f = num + .parse::() + .unwrap_or_else(|e| panic!("apfloat::ieee::Single failed to parse `{num}`: {e:?}")); assert!( u128::from(rust_f.to_bits()) == f.to_bits(), @@ -977,9 +997,9 @@ pub(crate) fn parse_float_into_scalar( } ty::FloatTy::F64 => { let Ok(rust_f) = num.parse::() else { return None }; - let mut f = num.parse::().unwrap_or_else(|e| { - panic!("apfloat::ieee::Double failed to parse `{}`: {:?}", num, e) - }); + let mut f = num + .parse::() + .unwrap_or_else(|e| panic!("apfloat::ieee::Double failed to parse `{num}`: {e:?}")); assert!( u128::from(rust_f.to_bits()) == f.to_bits(), diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 7331f8ecaa965..a96288a11e58b 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -90,8 +90,9 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::middle::region; use rustc_middle::mir::*; use rustc_middle::thir::{Expr, LintLevel}; - -use rustc_span::{DesugaringKind, Span, DUMMY_SP}; +use rustc_middle::ty::Ty; +use rustc_session::lint::Level; +use rustc_span::{Span, DUMMY_SP}; #[derive(Debug)] pub struct Scopes<'tcx> { @@ -371,6 +372,7 @@ impl DropTree { // The caller will handle this if needed. unwind: UnwindAction::Terminate, place: drop_data.0.local.into(), + replace: false, }; cfg.terminate(block, drop_data.0.source_info, terminator); } @@ -723,7 +725,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue` // statement. fn add_dummy_assignment(&mut self, span: Span, block: BasicBlock, source_info: SourceInfo) { - let local_decl = LocalDecl::new(self.tcx.mk_unit(), span).internal(); + let local_decl = LocalDecl::new(Ty::new_unit(self.tcx), span).internal(); let temp_place = Place::from(self.local_decls.push(local_decl)); self.cfg.push_assign_unit(block, source_info, temp_place, self.tcx); } @@ -758,20 +760,25 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ) { let (current_root, parent_root) = if self.tcx.sess.opts.unstable_opts.maximal_hir_to_mir_coverage { - // Some consumers of rustc need to map MIR locations back to HIR nodes. Currently the - // the only part of rustc that tracks MIR -> HIR is the `SourceScopeLocalData::lint_root` - // field that tracks lint levels for MIR locations. Normally the number of source scopes - // is limited to the set of nodes with lint annotations. The -Zmaximal-hir-to-mir-coverage - // flag changes this behavior to maximize the number of source scopes, increasing the - // granularity of the MIR->HIR mapping. + // Some consumers of rustc need to map MIR locations back to HIR nodes. Currently + // the the only part of rustc that tracks MIR -> HIR is the + // `SourceScopeLocalData::lint_root` field that tracks lint levels for MIR + // locations. Normally the number of source scopes is limited to the set of nodes + // with lint annotations. The -Zmaximal-hir-to-mir-coverage flag changes this + // behavior to maximize the number of source scopes, increasing the granularity of + // the MIR->HIR mapping. (current_id, parent_id) } else { - // Use `maybe_lint_level_root_bounded` with `self.hir_id` as a bound - // to avoid adding Hir dependencies on our parents. - // We estimate the true lint roots here to avoid creating a lot of source scopes. + // Use `maybe_lint_level_root_bounded` to avoid adding Hir dependencies on our + // parents. We estimate the true lint roots here to avoid creating a lot of source + // scopes. ( - self.tcx.maybe_lint_level_root_bounded(current_id, self.hir_id), - self.tcx.maybe_lint_level_root_bounded(parent_id, self.hir_id), + self.maybe_lint_level_root_bounded(current_id), + if parent_id == self.hir_id { + parent_id // this is very common + } else { + self.maybe_lint_level_root_bounded(parent_id) + }, ) }; @@ -781,6 +788,50 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } + /// Walks upwards from `orig_id` to find a node which might change lint levels with attributes. + /// It stops at `self.hir_id` and just returns it if reached. + fn maybe_lint_level_root_bounded(&mut self, orig_id: HirId) -> HirId { + // This assertion lets us just store `ItemLocalId` in the cache, rather + // than the full `HirId`. + assert_eq!(orig_id.owner, self.hir_id.owner); + + let mut id = orig_id; + let hir = self.tcx.hir(); + loop { + if id == self.hir_id { + // This is a moderately common case, mostly hit for previously unseen nodes. + break; + } + + if hir.attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) { + // This is a rare case. It's for a node path that doesn't reach the root due to an + // intervening lint level attribute. This result doesn't get cached. + return id; + } + + let next = hir.parent_id(id); + if next == id { + bug!("lint traversal reached the root of the crate"); + } + id = next; + + // This lookup is just an optimization; it can be removed without affecting + // functionality. It might seem strange to see this at the end of this loop, but the + // `orig_id` passed in to this function is almost always previously unseen, for which a + // lookup will be a miss. So we only do lookups for nodes up the parent chain, where + // cache lookups have a very high hit rate. + if self.lint_level_roots_cache.contains(id.local_id) { + break; + } + } + + // `orig_id` traced to `self_id`; record this fact. If `orig_id` is a leaf node it will + // rarely (never?) subsequently be searched for, but it's hard to know if that is the case. + // The performance wins from the cache all come from caching non-leaf nodes. + self.lint_level_roots_cache.insert(orig_id.local_id); + self.hir_id + } + /// Creates a new source scope, nested in the current one. pub(crate) fn new_source_scope( &mut self, @@ -1128,9 +1179,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { place: Place<'tcx>, value: Rvalue<'tcx>, ) -> BlockAnd<()> { - let span = self.tcx.with_stable_hashing_context(|hcx| { - span.mark_with_reason(None, DesugaringKind::Replace, self.tcx.sess.edition(), hcx) - }); let source_info = self.source_info(span); // create the new block for the assignment @@ -1148,6 +1196,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { place, target: assign, unwind: UnwindAction::Cleanup(assign_unwind), + replace: true, }, ); self.diverge_from(block); @@ -1261,6 +1310,7 @@ fn build_scope_drops<'tcx>( place: local.into(), target: next, unwind: UnwindAction::Continue, + replace: false, }, ); block = next; diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 0506f2bf2385c..192bd4a83e3ed 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -91,7 +91,12 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> { kind.emit_unsafe_op_in_unsafe_fn_lint(self.tcx, self.hir_context, span); } SafetyContext::Safe => { - kind.emit_requires_unsafe_err(self.tcx, span, unsafe_op_in_unsafe_fn_allowed); + kind.emit_requires_unsafe_err( + self.tcx, + span, + self.hir_context, + unsafe_op_in_unsafe_fn_allowed, + ); } } } @@ -254,7 +259,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { ); }; match borrow_kind { - BorrowKind::Shallow | BorrowKind::Shared | BorrowKind::Unique => { + BorrowKind::Shallow | BorrowKind::Shared => { if !ty.is_freeze(self.tcx, self.param_env) { self.requires_unsafe(pat.span, BorrowOfLayoutConstrainedField); } @@ -303,7 +308,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { | ExprKind::NeverToAny { .. } | ExprKind::PlaceTypeAscription { .. } | ExprKind::ValueTypeAscription { .. } - | ExprKind::Pointer { .. } + | ExprKind::PointerCoercion { .. } | ExprKind::Repeat { .. } | ExprKind::StaticRef { .. } | ExprKind::ThreadLocalRef { .. } @@ -316,6 +321,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { | ExprKind::Closure { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::Become { .. } | ExprKind::Yield { .. } | ExprKind::Loop { .. } | ExprKind::Let { .. } @@ -382,7 +388,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { ExprKind::Adt(box AdtExpr { adt_def, variant_index: _, - substs: _, + args: _, user_ty: _, fields: _, base: _, @@ -392,14 +398,14 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { }, ExprKind::Closure(box ClosureExpr { closure_id, - substs: _, + args: _, upvars: _, movability: _, fake_reads: _, }) => { self.visit_inner_body(closure_id); } - ExprKind::ConstBlock { did, substs: _ } => { + ExprKind::ConstBlock { did, args: _ } => { let def_id = did.expect_local(); self.visit_inner_body(def_id); } @@ -440,7 +446,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { visit::walk_expr(&mut visitor, expr); if visitor.found { match borrow_kind { - BorrowKind::Shallow | BorrowKind::Shared | BorrowKind::Unique + BorrowKind::Shallow | BorrowKind::Shared if !self.thir[arg].ty.is_freeze(self.tcx, self.param_env) => { self.requires_unsafe(expr.span, BorrowOfLayoutConstrainedField) @@ -448,7 +454,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { BorrowKind::Mut { .. } => { self.requires_unsafe(expr.span, MutationOfLayoutConstrainedField) } - BorrowKind::Shallow | BorrowKind::Shared | BorrowKind::Unique => {} + BorrowKind::Shallow | BorrowKind::Shared => {} } } } @@ -601,98 +607,164 @@ impl UnsafeOpKind { &self, tcx: TyCtxt<'_>, span: Span, + hir_context: hir::HirId, unsafe_op_in_unsafe_fn_allowed: bool, ) { + let note_non_inherited = tcx.hir().parent_iter(hir_context).find(|(id, node)| { + if let hir::Node::Expr(block) = node + && let hir::ExprKind::Block(block, _) = block.kind + && let hir::BlockCheckMode::UnsafeBlock(_) = block.rules + { + true + } + else if let Some(sig) = tcx.hir().fn_sig_by_hir_id(*id) + && sig.header.is_unsafe() + { + true + } else { + false + } + }); + let unsafe_not_inherited_note = if let Some((id, _)) = note_non_inherited { + let span = tcx.hir().span(id); + let span = tcx.sess.source_map().guess_head_span(span); + Some(UnsafeNotInheritedNote { span }) + } else { + None + }; + match self { CallToUnsafeFunction(Some(did)) if unsafe_op_in_unsafe_fn_allowed => { tcx.sess.emit_err(CallToUnsafeFunctionRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span, + unsafe_not_inherited_note, function: &tcx.def_path_str(*did), }); } CallToUnsafeFunction(Some(did)) => { tcx.sess.emit_err(CallToUnsafeFunctionRequiresUnsafe { span, + unsafe_not_inherited_note, function: &tcx.def_path_str(*did), }); } CallToUnsafeFunction(None) if unsafe_op_in_unsafe_fn_allowed => { tcx.sess.emit_err( - CallToUnsafeFunctionRequiresUnsafeNamelessUnsafeOpInUnsafeFnAllowed { span }, + CallToUnsafeFunctionRequiresUnsafeNamelessUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }, ); } CallToUnsafeFunction(None) => { - tcx.sess.emit_err(CallToUnsafeFunctionRequiresUnsafeNameless { span }); + tcx.sess.emit_err(CallToUnsafeFunctionRequiresUnsafeNameless { + span, + unsafe_not_inherited_note, + }); } UseOfInlineAssembly if unsafe_op_in_unsafe_fn_allowed => { - tcx.sess - .emit_err(UseOfInlineAssemblyRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }); + tcx.sess.emit_err(UseOfInlineAssemblyRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); } UseOfInlineAssembly => { - tcx.sess.emit_err(UseOfInlineAssemblyRequiresUnsafe { span }); + tcx.sess.emit_err(UseOfInlineAssemblyRequiresUnsafe { + span, + unsafe_not_inherited_note, + }); } InitializingTypeWith if unsafe_op_in_unsafe_fn_allowed => { - tcx.sess - .emit_err(InitializingTypeWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }); + tcx.sess.emit_err(InitializingTypeWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); } InitializingTypeWith => { - tcx.sess.emit_err(InitializingTypeWithRequiresUnsafe { span }); + tcx.sess.emit_err(InitializingTypeWithRequiresUnsafe { + span, + unsafe_not_inherited_note, + }); } UseOfMutableStatic if unsafe_op_in_unsafe_fn_allowed => { - tcx.sess - .emit_err(UseOfMutableStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }); + tcx.sess.emit_err(UseOfMutableStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); } UseOfMutableStatic => { - tcx.sess.emit_err(UseOfMutableStaticRequiresUnsafe { span }); + tcx.sess + .emit_err(UseOfMutableStaticRequiresUnsafe { span, unsafe_not_inherited_note }); } UseOfExternStatic if unsafe_op_in_unsafe_fn_allowed => { - tcx.sess - .emit_err(UseOfExternStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }); + tcx.sess.emit_err(UseOfExternStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); } UseOfExternStatic => { - tcx.sess.emit_err(UseOfExternStaticRequiresUnsafe { span }); + tcx.sess + .emit_err(UseOfExternStaticRequiresUnsafe { span, unsafe_not_inherited_note }); } DerefOfRawPointer if unsafe_op_in_unsafe_fn_allowed => { - tcx.sess - .emit_err(DerefOfRawPointerRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }); + tcx.sess.emit_err(DerefOfRawPointerRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); } DerefOfRawPointer => { - tcx.sess.emit_err(DerefOfRawPointerRequiresUnsafe { span }); + tcx.sess + .emit_err(DerefOfRawPointerRequiresUnsafe { span, unsafe_not_inherited_note }); } AccessToUnionField if unsafe_op_in_unsafe_fn_allowed => { - tcx.sess - .emit_err(AccessToUnionFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }); + tcx.sess.emit_err(AccessToUnionFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }); } AccessToUnionField => { - tcx.sess.emit_err(AccessToUnionFieldRequiresUnsafe { span }); + tcx.sess + .emit_err(AccessToUnionFieldRequiresUnsafe { span, unsafe_not_inherited_note }); } MutationOfLayoutConstrainedField if unsafe_op_in_unsafe_fn_allowed => { tcx.sess.emit_err( MutationOfLayoutConstrainedFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span, + unsafe_not_inherited_note, }, ); } MutationOfLayoutConstrainedField => { - tcx.sess.emit_err(MutationOfLayoutConstrainedFieldRequiresUnsafe { span }); + tcx.sess.emit_err(MutationOfLayoutConstrainedFieldRequiresUnsafe { + span, + unsafe_not_inherited_note, + }); } BorrowOfLayoutConstrainedField if unsafe_op_in_unsafe_fn_allowed => { tcx.sess.emit_err( - BorrowOfLayoutConstrainedFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span }, + BorrowOfLayoutConstrainedFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { + span, + unsafe_not_inherited_note, + }, ); } BorrowOfLayoutConstrainedField => { - tcx.sess.emit_err(BorrowOfLayoutConstrainedFieldRequiresUnsafe { span }); + tcx.sess.emit_err(BorrowOfLayoutConstrainedFieldRequiresUnsafe { + span, + unsafe_not_inherited_note, + }); } CallToFunctionWith(did) if unsafe_op_in_unsafe_fn_allowed => { tcx.sess.emit_err(CallToFunctionWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed { span, + unsafe_not_inherited_note, function: &tcx.def_path_str(*did), }); } CallToFunctionWith(did) => { tcx.sess.emit_err(CallToFunctionWithRequiresUnsafe { span, + unsafe_not_inherited_note, function: &tcx.def_path_str(*did), }); } @@ -711,9 +783,7 @@ pub fn thir_check_unsafety(tcx: TyCtxt<'_>, def: LocalDefId) { return; } - let Ok((thir, expr)) = tcx.thir_body(def) else { - return - }; + let Ok((thir, expr)) = tcx.thir_body(def) else { return }; let thir = &thir.borrow(); // If `thir` is empty, a type error occurred, skip this body. if thir.exprs.is_empty() { diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 7c0df201bc25e..3ff3387a78114 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -119,6 +119,8 @@ pub struct CallToUnsafeFunctionRequiresUnsafe<'a> { #[label] pub span: Span, pub function: &'a str, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -128,6 +130,8 @@ pub struct CallToUnsafeFunctionRequiresUnsafeNameless { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -138,6 +142,8 @@ pub struct CallToUnsafeFunctionRequiresUnsafeUnsafeOpInUnsafeFnAllowed<'a> { #[label] pub span: Span, pub function: &'a str, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -150,6 +156,8 @@ pub struct CallToUnsafeFunctionRequiresUnsafeNamelessUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -159,6 +167,8 @@ pub struct UseOfInlineAssemblyRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -168,6 +178,8 @@ pub struct UseOfInlineAssemblyRequiresUnsafeUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -177,6 +189,8 @@ pub struct InitializingTypeWithRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -189,6 +203,8 @@ pub struct InitializingTypeWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -198,6 +214,8 @@ pub struct UseOfMutableStaticRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -207,6 +225,8 @@ pub struct UseOfMutableStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -216,6 +236,8 @@ pub struct UseOfExternStaticRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -225,6 +247,8 @@ pub struct UseOfExternStaticRequiresUnsafeUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -234,6 +258,8 @@ pub struct DerefOfRawPointerRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -243,6 +269,8 @@ pub struct DerefOfRawPointerRequiresUnsafeUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -252,6 +280,8 @@ pub struct AccessToUnionFieldRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -261,6 +291,8 @@ pub struct AccessToUnionFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -270,6 +302,8 @@ pub struct MutationOfLayoutConstrainedFieldRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -282,6 +316,8 @@ pub struct MutationOfLayoutConstrainedFieldRequiresUnsafeUnsafeOpInUnsafeFnAllow #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -291,6 +327,8 @@ pub struct BorrowOfLayoutConstrainedFieldRequiresUnsafe { #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -303,6 +341,8 @@ pub struct BorrowOfLayoutConstrainedFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed #[primary_span] #[label] pub span: Span, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -313,6 +353,8 @@ pub struct CallToFunctionWithRequiresUnsafe<'a> { #[label] pub span: Span, pub function: &'a str, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, } #[derive(Diagnostic)] @@ -323,6 +365,15 @@ pub struct CallToFunctionWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed<'a> { #[label] pub span: Span, pub function: &'a str, + #[subdiagnostic] + pub unsafe_not_inherited_note: Option, +} + +#[derive(Subdiagnostic)] +#[label(mir_build_unsafe_not_inherited)] +pub struct UnsafeNotInheritedNote { + #[primary_span] + pub span: Span, } #[derive(LintDiagnostic)] @@ -403,17 +454,13 @@ impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_> { if self.span.eq_ctxt(self.expr_span) { // Get the span for the empty match body `{}`. let (indentation, more) = if let Some(snippet) = sm.indentation_before(self.span) { - (format!("\n{}", snippet), " ") + (format!("\n{snippet}"), " ") } else { (" ".to_string(), "") }; suggestion = Some(( self.span.shrink_to_hi().with_hi(self.expr_span.hi()), - format!( - " {{{indentation}{more}_ => todo!(),{indentation}}}", - indentation = indentation, - more = more, - ), + format!(" {{{indentation}{more}_ => todo!(),{indentation}}}",), )); } @@ -432,6 +479,10 @@ impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_> { } } +#[derive(Subdiagnostic)] +#[note(mir_build_non_exhaustive_match_all_arms_guarded)] +pub struct NonExhaustiveMatchAllArmsGuarded; + #[derive(Diagnostic)] #[diag(mir_build_static_in_pattern, code = "E0158")] pub struct StaticInPattern { diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs index c964e62c9d0e7..099fefbf06871 100644 --- a/compiler/rustc_mir_build/src/lib.rs +++ b/compiler/rustc_mir_build/src/lib.rs @@ -19,7 +19,7 @@ extern crate rustc_middle; mod build; mod check_unsafety; mod errors; -mod lints; +pub mod lints; pub mod thir; use rustc_middle::query::Providers; @@ -32,8 +32,9 @@ fluent_messages! { "../messages.ftl" } pub fn provide(providers: &mut Providers) { providers.check_match = thir::pattern::check_match; providers.lit_to_const = thir::constant::lit_to_const; - providers.lit_to_mir_constant = build::lit_to_mir_constant; providers.mir_built = build::mir_built; + providers.closure_saved_names_of_captured_variables = + build::closure_saved_names_of_captured_variables; providers.thir_check_unsafety = check_unsafety::thir_check_unsafety; providers.thir_body = thir::cx::thir_body; providers.thir_tree = thir::print::thir_tree; diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs index 8e41957af0eba..7fb73b5c7b2f4 100644 --- a/compiler/rustc_mir_build/src/lints.rs +++ b/compiler/rustc_mir_build/src/lints.rs @@ -3,27 +3,43 @@ use rustc_data_structures::graph::iterate::{ NodeStatus, TriColorDepthFirstSearch, TriColorVisitor, }; use rustc_hir::def::DefKind; -use rustc_middle::mir::{self, BasicBlock, BasicBlocks, Body, Operand, TerminatorKind}; -use rustc_middle::ty::subst::{GenericArg, InternalSubsts}; -use rustc_middle::ty::{self, Instance, TyCtxt}; +use rustc_middle::mir::{self, BasicBlock, BasicBlocks, Body, Terminator, TerminatorKind}; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; +use rustc_middle::ty::{GenericArg, GenericArgs}; use rustc_session::lint::builtin::UNCONDITIONAL_RECURSION; use rustc_span::Span; use std::ops::ControlFlow; pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { + check_call_recursion(tcx, body); +} + +fn check_call_recursion<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { let def_id = body.source.def_id().expect_local(); if let DefKind::Fn | DefKind::AssocFn = tcx.def_kind(def_id) { - // If this is trait/impl method, extract the trait's substs. - let trait_substs = match tcx.trait_of_item(def_id.to_def_id()) { + // If this is trait/impl method, extract the trait's args. + let trait_args = match tcx.trait_of_item(def_id.to_def_id()) { Some(trait_def_id) => { - let trait_substs_count = tcx.generics_of(trait_def_id).count(); - &InternalSubsts::identity_for_item(tcx, def_id)[..trait_substs_count] + let trait_args_count = tcx.generics_of(trait_def_id).count(); + &GenericArgs::identity_for_item(tcx, def_id)[..trait_args_count] } _ => &[], }; - let mut vis = Search { tcx, body, reachable_recursive_calls: vec![], trait_substs }; + check_recursion(tcx, body, CallRecursion { trait_args }) + } +} + +fn check_recursion<'tcx>( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + classifier: impl TerminatorClassifier<'tcx>, +) { + let def_id = body.source.def_id().expect_local(); + + if let DefKind::Fn | DefKind::AssocFn = tcx.def_kind(def_id) { + let mut vis = Search { tcx, body, classifier, reachable_recursive_calls: vec![] }; if let Some(NonRecursive) = TriColorDepthFirstSearch::new(&body.basic_blocks).run_from_start(&mut vis) { @@ -46,20 +62,66 @@ pub(crate) fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { } } +/// Requires drop elaboration to have been performed first. +pub fn check_drop_recursion<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { + let def_id = body.source.def_id().expect_local(); + + // First check if `body` is an `fn drop()` of `Drop` + if let DefKind::AssocFn = tcx.def_kind(def_id) && + let Some(trait_ref) = tcx.impl_of_method(def_id.to_def_id()).and_then(|def_id| tcx.impl_trait_ref(def_id)) && + let Some(drop_trait) = tcx.lang_items().drop_trait() && drop_trait == trait_ref.instantiate_identity().def_id { + + // It was. Now figure out for what type `Drop` is implemented and then + // check for recursion. + if let ty::Ref(_, dropped_ty, _) = tcx.liberate_late_bound_regions( + def_id.to_def_id(), + tcx.fn_sig(def_id).instantiate_identity().input(0), + ).kind() { + check_recursion(tcx, body, RecursiveDrop { drop_for: *dropped_ty }); + } + } +} + +trait TerminatorClassifier<'tcx> { + fn is_recursive_terminator( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + terminator: &Terminator<'tcx>, + ) -> bool; +} + struct NonRecursive; -struct Search<'mir, 'tcx> { +struct Search<'mir, 'tcx, C: TerminatorClassifier<'tcx>> { tcx: TyCtxt<'tcx>, body: &'mir Body<'tcx>, - trait_substs: &'tcx [GenericArg<'tcx>], + classifier: C, reachable_recursive_calls: Vec, } -impl<'mir, 'tcx> Search<'mir, 'tcx> { +struct CallRecursion<'tcx> { + trait_args: &'tcx [GenericArg<'tcx>], +} + +struct RecursiveDrop<'tcx> { + /// The type that `Drop` is implemented for. + drop_for: Ty<'tcx>, +} + +impl<'tcx> TerminatorClassifier<'tcx> for CallRecursion<'tcx> { /// Returns `true` if `func` refers to the function we are searching in. - fn is_recursive_call(&self, func: &Operand<'tcx>, args: &[Operand<'tcx>]) -> bool { - let Search { tcx, body, trait_substs, .. } = *self; + fn is_recursive_terminator( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + terminator: &Terminator<'tcx>, + ) -> bool { + let TerminatorKind::Call { func, args, .. } = &terminator.kind else { + return false; + }; + // Resolving function type to a specific instance that is being called is expensive. To // avoid the cost we check the number of arguments first, which is sufficient to reject // most of calls as non-recursive. @@ -70,30 +132,46 @@ impl<'mir, 'tcx> Search<'mir, 'tcx> { let param_env = tcx.param_env(caller); let func_ty = func.ty(body, tcx); - if let ty::FnDef(callee, substs) = *func_ty.kind() { - let normalized_substs = tcx.normalize_erasing_regions(param_env, substs); - let (callee, call_substs) = if let Ok(Some(instance)) = - Instance::resolve(tcx, param_env, callee, normalized_substs) + if let ty::FnDef(callee, args) = *func_ty.kind() { + let normalized_args = tcx.normalize_erasing_regions(param_env, args); + let (callee, call_args) = if let Ok(Some(instance)) = + Instance::resolve(tcx, param_env, callee, normalized_args) { - (instance.def_id(), instance.substs) + (instance.def_id(), instance.args) } else { - (callee, normalized_substs) + (callee, normalized_args) }; // FIXME(#57965): Make this work across function boundaries - // If this is a trait fn, the substs on the trait have to match, or we might be + // If this is a trait fn, the args on the trait have to match, or we might be // calling into an entirely different method (for example, a call from the default // method in the trait to `>::method`, where `A` and/or `B` are // specific types). - return callee == caller && &call_substs[..trait_substs.len()] == trait_substs; + return callee == caller && &call_args[..self.trait_args.len()] == self.trait_args; } false } } -impl<'mir, 'tcx> TriColorVisitor> for Search<'mir, 'tcx> { +impl<'tcx> TerminatorClassifier<'tcx> for RecursiveDrop<'tcx> { + fn is_recursive_terminator( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + terminator: &Terminator<'tcx>, + ) -> bool { + let TerminatorKind::Drop { place, .. } = &terminator.kind else { return false }; + + let dropped_ty = place.ty(body, tcx).ty; + dropped_ty == self.drop_for + } +} + +impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor> + for Search<'mir, 'tcx, C> +{ type BreakVal = NonRecursive; fn node_examined( @@ -138,10 +216,8 @@ impl<'mir, 'tcx> TriColorVisitor> for Search<'mir, 'tcx> { fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow { // When we examine a node for the last time, remember it if it is a recursive call. let terminator = self.body[bb].terminator(); - if let TerminatorKind::Call { func, args, .. } = &terminator.kind { - if self.is_recursive_call(func, args) { - self.reachable_recursive_calls.push(terminator.source_info.span); - } + if self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) { + self.reachable_recursive_calls.push(terminator.source_info.span); } ControlFlow::Continue(()) @@ -149,15 +225,14 @@ impl<'mir, 'tcx> TriColorVisitor> for Search<'mir, 'tcx> { fn ignore_edge(&mut self, bb: BasicBlock, target: BasicBlock) -> bool { let terminator = self.body[bb].terminator(); - if terminator.unwind() == Some(&mir::UnwindAction::Cleanup(target)) - && terminator.successors().count() > 1 + let ignore_unwind = terminator.unwind() == Some(&mir::UnwindAction::Cleanup(target)) + && terminator.successors().count() > 1; + if ignore_unwind || self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) { return true; } - // Don't traverse successors of recursive calls or false CFG edges. - match self.body[bb].terminator().kind { - TerminatorKind::Call { ref func, ref args, .. } => self.is_recursive_call(func, args), - TerminatorKind::FalseEdge { imaginary_target, .. } => imaginary_target == target, + match &terminator.kind { + TerminatorKind::FalseEdge { imaginary_target, .. } => imaginary_target == &target, _ => false, } } diff --git a/compiler/rustc_mir_build/src/thir/constant.rs b/compiler/rustc_mir_build/src/thir/constant.rs index 57ae6a3652df5..fbb74650faaeb 100644 --- a/compiler/rustc_mir_build/src/thir/constant.rs +++ b/compiler/rustc_mir_build/src/thir/constant.rs @@ -3,6 +3,8 @@ use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput}; use rustc_middle::ty::{self, ParamEnv, ScalarInt, TyCtxt}; use rustc_span::DUMMY_SP; +use crate::build::parse_float_into_scalar; + pub(crate) fn lit_to_const<'tcx>( tcx: TyCtxt<'tcx>, lit_input: LitToConstInput<'tcx>, @@ -46,12 +48,28 @@ pub(crate) fn lit_to_const<'tcx>( (ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => { ty::ValTree::from_scalar_int((*n).into()) } + (ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().c_str()) => + { + let bytes = data as &[u8]; + ty::ValTree::from_raw_bytes(tcx, bytes) + } (ast::LitKind::Int(n, _), ty::Uint(_)) | (ast::LitKind::Int(n, _), ty::Int(_)) => { let scalar_int = trunc(if neg { (*n as i128).overflowing_neg().0 as u128 } else { *n })?; ty::ValTree::from_scalar_int(scalar_int) } (ast::LitKind::Bool(b), ty::Bool) => ty::ValTree::from_scalar_int((*b).into()), + (ast::LitKind::Float(n, _), ty::Float(fty)) => { + let bits = parse_float_into_scalar(*n, *fty, neg) + .ok_or_else(|| { + LitToConstError::Reported(tcx.sess.delay_span_bug( + DUMMY_SP, + format!("couldn't parse float literal: {:?}", lit_input.lit), + )) + })? + .assert_int(); + ty::ValTree::from_scalar_int(bits) + } (ast::LitKind::Char(c), ty::Char) => ty::ValTree::from_scalar_int((*c).into()), (ast::LitKind::Err, _) => { return Err(LitToConstError::Reported( @@ -61,5 +79,5 @@ pub(crate) fn lit_to_const<'tcx>( _ => return Err(LitToConstError::TypeError), }; - Ok(tcx.mk_const(valtree, ty)) + Ok(ty::Const::new_value(tcx, valtree, ty)) } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b20495d602e59..6c1f7d7a60667 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -13,11 +13,11 @@ use rustc_middle::middle::region; use rustc_middle::mir::{self, BinOp, BorrowKind, UnOp}; use rustc_middle::thir::*; use rustc_middle::ty::adjustment::{ - Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCast, + Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCoercion, }; -use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{ - self, AdtKind, InlineConstSubsts, InlineConstSubstsParts, ScalarInt, Ty, UpvarSubsts, UserType, + self, AdtKind, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, UpvarArgs, UserType, }; use rustc_span::{sym, Span}; use rustc_target::abi::{FieldIdx, FIRST_VARIANT}; @@ -41,11 +41,6 @@ impl<'tcx> Cx<'tcx> { let mut expr = self.make_mirror_unadjusted(hir_expr); - let adjustment_span = match self.adjustment_span { - Some((hir_id, span)) if hir_id == hir_expr.hir_id => Some(span), - _ => None, - }; - trace!(?expr.ty); // Now apply adjustments, if any. @@ -53,12 +48,7 @@ impl<'tcx> Cx<'tcx> { for adjustment in self.typeck_results.expr_adjustments(hir_expr) { trace!(?expr, ?adjustment); let span = expr.span; - expr = self.apply_adjustment( - hir_expr, - expr, - adjustment, - adjustment_span.unwrap_or(span), - ); + expr = self.apply_adjustment(hir_expr, expr, adjustment, span); } } @@ -125,11 +115,16 @@ impl<'tcx> Cx<'tcx> { }; let kind = match adjustment.kind { - Adjust::Pointer(PointerCast::Unsize) => { + Adjust::Pointer(PointerCoercion::Unsize) => { adjust_span(&mut expr); - ExprKind::Pointer { cast: PointerCast::Unsize, source: self.thir.exprs.push(expr) } + ExprKind::PointerCoercion { + cast: PointerCoercion::Unsize, + source: self.thir.exprs.push(expr), + } + } + Adjust::Pointer(cast) => { + ExprKind::PointerCoercion { cast, source: self.thir.exprs.push(expr) } } - Adjust::Pointer(cast) => ExprKind::Pointer { cast, source: self.thir.exprs.push(expr) }, Adjust::NeverToAny if adjustment.target.is_never() => return expr, Adjust::NeverToAny => ExprKind::NeverToAny { source: self.thir.exprs.push(expr) }, Adjust::Deref(None) => { @@ -143,9 +138,11 @@ impl<'tcx> Cx<'tcx> { expr = Expr { temp_lifetime, - ty: self - .tcx - .mk_ref(deref.region, ty::TypeAndMut { ty: expr.ty, mutbl: deref.mutbl }), + ty: Ty::new_ref( + self.tcx, + deref.region, + ty::TypeAndMut { ty: expr.ty, mutbl: deref.mutbl }, + ), span, kind: ExprKind::Borrow { borrow_kind: deref.mutbl.to_borrow_kind(), @@ -190,9 +187,9 @@ impl<'tcx> Cx<'tcx> { // Special cased so that we can type check that the element // type of the source matches the pointed to type of the // destination. - ExprKind::Pointer { + ExprKind::PointerCoercion { source: self.mirror_expr(source), - cast: PointerCast::ArrayToPointer, + cast: PointerCoercion::ArrayToPointer, } } else { // check whether this is casting an enum variant discriminant @@ -208,17 +205,18 @@ impl<'tcx> Cx<'tcx> { // so we wouldn't have to compute and store the actual value let hir::ExprKind::Path(ref qpath) = source.kind else { - return ExprKind::Cast { source: self.mirror_expr(source)}; + return ExprKind::Cast { source: self.mirror_expr(source) }; }; let res = self.typeck_results().qpath_res(qpath, source.hir_id); let ty = self.typeck_results().node_type(source.hir_id); - let ty::Adt(adt_def, substs) = ty.kind() else { - return ExprKind::Cast { source: self.mirror_expr(source)}; + let ty::Adt(adt_def, args) = ty.kind() else { + return ExprKind::Cast { source: self.mirror_expr(source) }; }; - let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res else { - return ExprKind::Cast { source: self.mirror_expr(source)}; + let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res + else { + return ExprKind::Cast { source: self.mirror_expr(source) }; }; let idx = adt_def.variant_index_with_ctor_id(variant_ctor_id); @@ -231,9 +229,7 @@ impl<'tcx> Cx<'tcx> { let param_env_ty = self.param_env.and(discr_ty); let size = tcx .layout_of(param_env_ty) - .unwrap_or_else(|e| { - panic!("could not compute layout for {:?}: {:?}", param_env_ty, e) - }) + .unwrap_or_else(|e| panic!("could not compute layout for {param_env_ty:?}: {e:?}")) .size; let lit = ScalarInt::try_from_uint(discr_offset as u128, size).unwrap(); @@ -244,7 +240,7 @@ impl<'tcx> Cx<'tcx> { // in case we are offsetting from a computed discriminant // and not the beginning of discriminants (which is always `0`) Some(did) => { - let kind = ExprKind::NamedConst { def_id: did, substs, user_ty: None }; + let kind = ExprKind::NamedConst { def_id: did, args, user_ty: None }; let lhs = self.thir.exprs.push(Expr { temp_lifetime, ty: discr_ty, span, kind }); let bin = ExprKind::Binary { op: BinOp::Add, lhs, rhs: offset }; @@ -266,7 +262,6 @@ impl<'tcx> Cx<'tcx> { fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { let tcx = self.tcx; let expr_ty = self.typeck_results().expr_ty(expr); - let expr_span = expr.span; let temp_lifetime = self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id); @@ -275,17 +270,11 @@ impl<'tcx> Cx<'tcx> { hir::ExprKind::MethodCall(segment, receiver, ref args, fn_span) => { // Rewrite a.b(c) into UFCS form like Trait::b(a, c) let expr = self.method_callee(expr, segment.ident.span, None); - // When we apply adjustments to the receiver, use the span of - // the overall method call for better diagnostics. args[0] - // is guaranteed to exist, since a method call always has a receiver. - let old_adjustment_span = - self.adjustment_span.replace((receiver.hir_id, expr_span)); info!("Using method span: {:?}", expr.span); let args = std::iter::once(receiver) .chain(args.iter()) .map(|expr| self.mirror_expr(expr)) .collect(); - self.adjustment_span = old_adjustment_span; ExprKind::Call { ty: expr.ty, fun: self.thir.exprs.push(expr), @@ -308,7 +297,7 @@ impl<'tcx> Cx<'tcx> { let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e)); let tupled_args = Expr { - ty: tcx.mk_tup_from_iter(arg_tys), + ty: Ty::new_tup_from_iter(tcx, arg_tys), temp_lifetime, span: expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(args) }, @@ -351,21 +340,37 @@ impl<'tcx> Cx<'tcx> { }); } } - let adt_data = - if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = fun.kind { - // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. - expr_ty.ty_adt_def().and_then(|adt_def| match path.res { - Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_id) => { + + // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. + let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind + && let Some(adt_def) = expr_ty.ty_adt_def() { + match qpath { + hir::QPath::Resolved(_, ref path) => { + match path.res { + Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_id) => { + Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id))) + } + Res::SelfCtor(..) => Some((adt_def, FIRST_VARIANT)), + _ => None, + } + } + hir::QPath::TypeRelative(_ty, _) => { + if let Some((DefKind::Ctor(_, CtorKind::Fn), ctor_id)) = + self.typeck_results().type_dependent_def(fun.hir_id) + { Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id))) + } else { + None } - Res::SelfCtor(..) => Some((adt_def, FIRST_VARIANT)), - _ => None, - }) - } else { - None - }; + + } + _ => None, + } + } else { + None + }; if let Some((adt_def, index)) = adt_data { - let substs = self.typeck_results().node_substs(fun.hir_id); + let node_args = self.typeck_results().node_args(fun.hir_id); let user_provided_types = self.typeck_results().user_provided_types(); let user_ty = user_provided_types.get(fun.hir_id).copied().map(|mut u_ty| { @@ -386,7 +391,7 @@ impl<'tcx> Cx<'tcx> { .collect(); ExprKind::Adt(Box::new(AdtExpr { adt_def, - substs, + args: node_args, variant_index: index, fields: field_refs, user_ty, @@ -440,7 +445,6 @@ impl<'tcx> Cx<'tcx> { let rhs = self.mirror_expr(rhs); self.overloaded_operator(expr, Box::new([lhs, rhs])) } else { - // FIXME overflow match op.node { hir::BinOpKind::And => ExprKind::LogicalOp { op: LogicalOp::And, @@ -464,11 +468,17 @@ impl<'tcx> Cx<'tcx> { } } - hir::ExprKind::Index(ref lhs, ref index) => { + hir::ExprKind::Index(ref lhs, ref index, brackets_span) => { if self.typeck_results().is_method_call(expr) { let lhs = self.mirror_expr(lhs); let index = self.mirror_expr(index); - self.overloaded_place(expr, expr_ty, None, Box::new([lhs, index]), expr.span) + self.overloaded_place( + expr, + expr_ty, + None, + Box::new([lhs, index]), + brackets_span, + ) } else { ExprKind::Index { lhs: self.mirror_expr(lhs), index: self.mirror_expr(index) } } @@ -504,7 +514,7 @@ impl<'tcx> Cx<'tcx> { } hir::ExprKind::Struct(ref qpath, ref fields, ref base) => match expr_ty.kind() { - ty::Adt(adt, substs) => match adt.adt_kind() { + ty::Adt(adt, args) => match adt.adt_kind() { AdtKind::Struct | AdtKind::Union => { let user_provided_types = self.typeck_results().user_provided_types(); let user_ty = user_provided_types.get(expr.hir_id).copied().map(Box::new); @@ -512,7 +522,7 @@ impl<'tcx> Cx<'tcx> { ExprKind::Adt(Box::new(AdtExpr { adt_def: *adt, variant_index: FIRST_VARIANT, - substs, + args, user_ty, fields: self.field_refs(fields), base: base.map(|base| FruInfo { @@ -539,7 +549,7 @@ impl<'tcx> Cx<'tcx> { ExprKind::Adt(Box::new(AdtExpr { adt_def: *adt, variant_index: index, - substs, + args, user_ty, fields: self.field_refs(fields), base: None, @@ -558,10 +568,10 @@ impl<'tcx> Cx<'tcx> { hir::ExprKind::Closure { .. } => { let closure_ty = self.typeck_results().expr_ty(expr); - let (def_id, substs, movability) = match *closure_ty.kind() { - ty::Closure(def_id, substs) => (def_id, UpvarSubsts::Closure(substs), None), - ty::Generator(def_id, substs, movability) => { - (def_id, UpvarSubsts::Generator(substs), Some(movability)) + let (def_id, args, movability) = match *closure_ty.kind() { + ty::Closure(def_id, args) => (def_id, UpvarArgs::Closure(args), None), + ty::Generator(def_id, args, movability) => { + (def_id, UpvarArgs::Generator(args), Some(movability)) } _ => { span_bug!(expr.span, "closure expr w/o closure type: {:?}", closure_ty); @@ -573,7 +583,7 @@ impl<'tcx> Cx<'tcx> { .tcx .closure_captures(def_id) .iter() - .zip(substs.upvar_tys()) + .zip(args.upvar_tys()) .map(|(captured_place, ty)| { let upvars = self.capture_upvar(expr, captured_place, ty); self.thir.exprs.push(upvars) @@ -594,7 +604,7 @@ impl<'tcx> Cx<'tcx> { ExprKind::Closure(Box::new(ClosureExpr { closure_id: def_id, - substs, + args, upvars, movability, fake_reads, @@ -677,13 +687,11 @@ impl<'tcx> Cx<'tcx> { let ty = self.typeck_results().node_type(anon_const.hir_id); let did = anon_const.def_id.to_def_id(); let typeck_root_def_id = tcx.typeck_root_def_id(did); - let parent_substs = - tcx.erase_regions(InternalSubsts::identity_for_item(tcx, typeck_root_def_id)); - let substs = - InlineConstSubsts::new(tcx, InlineConstSubstsParts { parent_substs, ty }) - .substs; + let parent_args = + tcx.erase_regions(GenericArgs::identity_for_item(tcx, typeck_root_def_id)); + let args = InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }).args; - ExprKind::ConstBlock { did, substs } + ExprKind::ConstBlock { did, args } } // Now comes the rote stuff: hir::ExprKind::Repeat(ref v, _) => { @@ -694,7 +702,8 @@ impl<'tcx> Cx<'tcx> { ExprKind::Repeat { value: self.mirror_expr(v), count: *count } } - hir::ExprKind::Ret(ref v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, + hir::ExprKind::Ret(v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, + hir::ExprKind::Become(call) => ExprKind::Become { value: self.mirror_expr(call) }, hir::ExprKind::Break(dest, ref value) => match dest.target_id { Ok(target_id) => ExprKind::Break { label: region::Scope { id: target_id.local_id, data: region::ScopeData::Node }, @@ -723,6 +732,7 @@ impl<'tcx> Cx<'tcx> { }, hir::ExprKind::Match(ref discr, ref arms, _) => ExprKind::Match { scrutinee: self.mirror_expr(discr), + scrutinee_hir_id: discr.hir_id, arms: arms.iter().map(|a| self.convert_arm(a)).collect(), }, hir::ExprKind::Loop(ref body, ..) => { @@ -801,12 +811,12 @@ impl<'tcx> Cx<'tcx> { Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind } } - fn user_substs_applied_to_res( + fn user_args_applied_to_res( &mut self, hir_id: hir::HirId, res: Res, ) -> Option>> { - debug!("user_substs_applied_to_res: res={:?}", res); + debug!("user_args_applied_to_res: res={:?}", res); let user_provided_type = match res { // A reference to something callable -- e.g., a fn, method, or // a tuple-struct or tuple-variant. This has the type of a @@ -824,15 +834,15 @@ impl<'tcx> Cx<'tcx> { // this variant -- but with the substitutions given by the // user. Res::Def(DefKind::Ctor(_, CtorKind::Const), _) => { - self.user_substs_applied_to_ty_of_hir_id(hir_id).map(Box::new) + self.user_args_applied_to_ty_of_hir_id(hir_id).map(Box::new) } // `Self` is used in expression as a tuple struct constructor or a unit struct constructor - Res::SelfCtor(_) => self.user_substs_applied_to_ty_of_hir_id(hir_id).map(Box::new), + Res::SelfCtor(_) => self.user_args_applied_to_ty_of_hir_id(hir_id).map(Box::new), - _ => bug!("user_substs_applied_to_res: unexpected res {:?} at {:?}", res, hir_id), + _ => bug!("user_args_applied_to_res: unexpected res {:?} at {:?}", res, hir_id), }; - debug!("user_substs_applied_to_res: user_provided_type={:?}", user_provided_type); + debug!("user_args_applied_to_res: user_provided_type={:?}", user_provided_type); user_provided_type } @@ -851,10 +861,14 @@ impl<'tcx> Cx<'tcx> { self.typeck_results().type_dependent_def(expr.hir_id).unwrap_or_else(|| { span_bug!(expr.span, "no type-dependent def for method callee") }); - let user_ty = self.user_substs_applied_to_res(expr.hir_id, Res::Def(kind, def_id)); + let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(kind, def_id)); debug!("method_callee: user_ty={:?}", user_ty); ( - self.tcx().mk_fn_def(def_id, self.typeck_results().node_substs(expr.hir_id)), + Ty::new_fn_def( + self.tcx(), + def_id, + self.typeck_results().node_args(expr.hir_id), + ), user_ty, ) } @@ -880,14 +894,14 @@ impl<'tcx> Cx<'tcx> { } fn convert_path_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, res: Res) -> ExprKind<'tcx> { - let substs = self.typeck_results().node_substs(expr.hir_id); + let args = self.typeck_results().node_args(expr.hir_id); match res { // A regular function, constructor function or a constant. Res::Def(DefKind::Fn, _) | Res::Def(DefKind::AssocFn, _) | Res::Def(DefKind::Ctor(_, CtorKind::Fn), _) | Res::SelfCtor(_) => { - let user_ty = self.user_substs_applied_to_res(expr.hir_id, res); + let user_ty = self.user_args_applied_to_res(expr.hir_id, res); ExprKind::ZstLiteral { user_ty } } @@ -902,8 +916,8 @@ impl<'tcx> Cx<'tcx> { } Res::Def(DefKind::Const, def_id) | Res::Def(DefKind::AssocConst, def_id) => { - let user_ty = self.user_substs_applied_to_res(expr.hir_id, res); - ExprKind::NamedConst { def_id, substs, user_ty } + let user_ty = self.user_args_applied_to_res(expr.hir_id, res); + ExprKind::NamedConst { def_id, args, user_ty } } Res::Def(DefKind::Ctor(_, CtorKind::Const), def_id) => { @@ -914,10 +928,10 @@ impl<'tcx> Cx<'tcx> { match ty.kind() { // A unit struct/variant which is used as a value. // We return a completely different ExprKind here to account for this special case. - ty::Adt(adt_def, substs) => ExprKind::Adt(Box::new(AdtExpr { + ty::Adt(adt_def, args) => ExprKind::Adt(Box::new(AdtExpr { adt_def: *adt_def, variant_index: adt_def.variant_index_with_ctor_id(def_id), - substs, + args, user_ty, fields: Box::new([]), base: None, @@ -1007,7 +1021,7 @@ impl<'tcx> Cx<'tcx> { let ty::Ref(region, _, mutbl) = *self.thir[args[0]].ty.kind() else { span_bug!(span, "overloaded_place: receiver is not a reference"); }; - let ref_ty = self.tcx.mk_ref(region, ty::TypeAndMut { ty: place_ty, mutbl }); + let ref_ty = Ty::new_ref(self.tcx, region, ty::TypeAndMut { ty: place_ty, mutbl }); // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type @@ -1064,6 +1078,9 @@ impl<'tcx> Cx<'tcx> { variant_index, name: field, }, + HirProjectionKind::OpaqueCast => { + ExprKind::Use { source: self.thir.exprs.push(captured_place_expr) } + } HirProjectionKind::Index | HirProjectionKind::Subslice => { // We don't capture these projections, so we can ignore them here continue; @@ -1095,8 +1112,12 @@ impl<'tcx> Cx<'tcx> { ty::UpvarCapture::ByRef(upvar_borrow) => { let borrow_kind = match upvar_borrow { ty::BorrowKind::ImmBorrow => BorrowKind::Shared, - ty::BorrowKind::UniqueImmBorrow => BorrowKind::Unique, - ty::BorrowKind::MutBorrow => BorrowKind::Mut { allow_two_phase_borrow: false }, + ty::BorrowKind::UniqueImmBorrow => { + BorrowKind::Mut { kind: mir::MutBorrowKind::ClosureCapture } + } + ty::BorrowKind::MutBorrow => { + BorrowKind::Mut { kind: mir::MutBorrowKind::Default } + } }; Expr { temp_lifetime, @@ -1132,9 +1153,9 @@ impl ToBorrowKind for AutoBorrowMutability { use rustc_middle::ty::adjustment::AllowTwoPhase; match *self { AutoBorrowMutability::Mut { allow_two_phase_borrow } => BorrowKind::Mut { - allow_two_phase_borrow: match allow_two_phase_borrow { - AllowTwoPhase::Yes => true, - AllowTwoPhase::No => false, + kind: match allow_two_phase_borrow { + AllowTwoPhase::Yes => mir::MutBorrowKind::TwoPhaseBorrow, + AllowTwoPhase::No => mir::MutBorrowKind::Default, }, }, AutoBorrowMutability::Not => BorrowKind::Shared, @@ -1145,7 +1166,7 @@ impl ToBorrowKind for AutoBorrowMutability { impl ToBorrowKind for hir::Mutability { fn to_borrow_kind(&self) -> BorrowKind { match *self { - hir::Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false }, + hir::Mutability::Mut => BorrowKind::Mut { kind: mir::MutBorrowKind::Default }, hir::Mutability::Not => BorrowKind::Shared, } } diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index 463f639defe9a..d98cc76adfbd7 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -15,8 +15,7 @@ use rustc_hir::HirId; use rustc_hir::Node; use rustc_middle::middle::region; use rustc_middle::thir::*; -use rustc_middle::ty::{self, RvalueScopes, TyCtxt}; -use rustc_span::Span; +use rustc_middle::ty::{self, RvalueScopes, Ty, TyCtxt}; pub(crate) fn thir_body( tcx: TyCtxt<'_>, @@ -40,7 +39,7 @@ pub(crate) fn thir_body( // It will always be `()` in this case. if tcx.def_kind(owner_def) == DefKind::Generator && body.params.is_empty() { cx.thir.params.push(Param { - ty: tcx.mk_unit(), + ty: Ty::new_unit(tcx), pat: None, ty_span: None, self_kind: None, @@ -62,14 +61,6 @@ struct Cx<'tcx> { typeck_results: &'tcx ty::TypeckResults<'tcx>, rvalue_scopes: &'tcx RvalueScopes, - /// When applying adjustments to the expression - /// with the given `HirId`, use the given `Span`, - /// instead of the usual span. This is used to - /// assign the span of an overall method call - /// (e.g. `my_val.foo()`) to the adjustment expressions - /// for the receiver. - adjustment_span: Option<(HirId, Span)>, - /// False to indicate that adjustments should not be applied. Only used for `custom_mir` apply_adjustments: bool, @@ -110,7 +101,6 @@ impl<'tcx> Cx<'tcx> { typeck_results, rvalue_scopes: &typeck_results.rvalue_scopes, body_owner: def.to_def_id(), - adjustment_span: None, apply_adjustments: hir .attrs(hir_id) .iter() @@ -132,7 +122,7 @@ impl<'tcx> Cx<'tcx> { DefKind::Closure => { let closure_ty = self.typeck_results.node_type(owner_id); - let ty::Closure(closure_def_id, closure_substs) = *closure_ty.kind() else { + let ty::Closure(closure_def_id, closure_args) = *closure_ty.kind() else { bug!("closure expr does not have closure type: {:?}", closure_ty); }; @@ -142,9 +132,9 @@ impl<'tcx> Cx<'tcx> { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BrEnv, }; - let env_region = self.tcx.mk_re_late_bound(ty::INNERMOST, br); + let env_region = ty::Region::new_late_bound(self.tcx, ty::INNERMOST, br); let closure_env_ty = - self.tcx.closure_env_ty(closure_def_id, closure_substs, env_region).unwrap(); + self.tcx.closure_env_ty(closure_def_id, closure_args, env_region).unwrap(); let liberated_closure_env_ty = self.tcx.erase_late_bound_regions( ty::Binder::bind_with_vars(closure_env_ty, bound_vars), ); @@ -196,7 +186,7 @@ impl<'tcx> Cx<'tcx> { self.tcx .type_of(va_list_did) - .subst(self.tcx, &[self.tcx.lifetimes.re_erased.into()]) + .instantiate(self.tcx, &[self.tcx.lifetimes.re_erased.into()]) } else { fn_sig.inputs()[index] }; diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 5559e8b3940ae..383e80851f0d7 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -90,35 +90,34 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for MatchVisitor<'a, '_, 'tcx> { #[instrument(level = "trace", skip(self))] fn visit_arm(&mut self, arm: &Arm<'tcx>) { - match arm.guard { - Some(Guard::If(expr)) => { - self.with_let_source(LetSource::IfLetGuard, |this| { - this.visit_expr(&this.thir[expr]) - }); - } - Some(Guard::IfLet(ref pat, expr)) => { - self.with_let_source(LetSource::IfLetGuard, |this| { - this.check_let(pat, expr, LetSource::IfLetGuard, pat.span); - this.visit_pat(pat); - this.visit_expr(&this.thir[expr]); - }); + self.with_lint_level(arm.lint_level, |this| { + match arm.guard { + Some(Guard::If(expr)) => { + this.with_let_source(LetSource::IfLetGuard, |this| { + this.visit_expr(&this.thir[expr]) + }); + } + Some(Guard::IfLet(ref pat, expr)) => { + this.with_let_source(LetSource::IfLetGuard, |this| { + this.check_let(pat, expr, LetSource::IfLetGuard, pat.span); + this.visit_pat(pat); + this.visit_expr(&this.thir[expr]); + }); + } + None => {} } - None => {} - } - self.visit_pat(&arm.pattern); - self.visit_expr(&self.thir[arm.body]); + this.visit_pat(&arm.pattern); + this.visit_expr(&self.thir[arm.body]); + }); } #[instrument(level = "trace", skip(self))] fn visit_expr(&mut self, ex: &Expr<'tcx>) { match ex.kind { ExprKind::Scope { value, lint_level, .. } => { - let old_lint_level = self.lint_level; - if let LintLevel::Explicit(hir_id) = lint_level { - self.lint_level = hir_id; - } - self.visit_expr(&self.thir[value]); - self.lint_level = old_lint_level; + self.with_lint_level(lint_level, |this| { + this.visit_expr(&this.thir[value]); + }); return; } ExprKind::If { cond, then, else_opt, if_then_scope: _ } => { @@ -136,10 +135,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for MatchVisitor<'a, '_, 'tcx> { }); return; } - ExprKind::Match { scrutinee, box ref arms } => { + ExprKind::Match { scrutinee, scrutinee_hir_id, box ref arms } => { let source = match ex.span.desugaring_kind() { Some(DesugaringKind::ForLoop) => hir::MatchSource::ForLoopDesugar, - Some(DesugaringKind::QuestionMark) => hir::MatchSource::TryDesugar, + Some(DesugaringKind::QuestionMark) => { + hir::MatchSource::TryDesugar(scrutinee_hir_id) + } Some(DesugaringKind::Await) => hir::MatchSource::AwaitDesugar, _ => hir::MatchSource::Normal, }; @@ -190,6 +191,17 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { self.let_source = old_let_source; } + fn with_lint_level(&mut self, new_lint_level: LintLevel, f: impl FnOnce(&mut Self)) { + if let LintLevel::Explicit(hir_id) = new_lint_level { + let old_lint_level = self.lint_level; + self.lint_level = hir_id; + f(self); + self.lint_level = old_lint_level; + } else { + f(self); + } + } + fn check_patterns(&self, pat: &Pat<'tcx>, rf: RefutableFlag) { pat.walk_always(|pat| check_borrow_conflicts_in_at_patterns(self, pat)); check_for_bindings_named_same_as_variants(self, pat, rf); @@ -236,7 +248,9 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { for &arm in arms { // Check the arm for some things unrelated to exhaustiveness. let arm = &self.thir.arms[arm]; - self.check_patterns(&arm.pattern, Refutable); + self.with_lint_level(arm.lint_level, |this| { + this.check_patterns(&arm.pattern, Refutable); + }); } let tarms: Vec<_> = arms @@ -265,7 +279,7 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { | hir::MatchSource::FormatArgs => report_arm_reachability(&cx, &report), // Unreachable patterns in try and await expressions occur when one of // the arms are an uninhabited type. Which is OK. - hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {} + hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar(_) => {} } // Check if the match is exhaustive. @@ -431,7 +445,12 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { let mut let_suggestion = None; let mut misc_suggestion = None; let mut interpreted_as_const = None; - if let PatKind::Constant { .. } = pat.kind + + if let PatKind::Constant { .. } + | PatKind::AscribeUserType { + subpattern: box Pat { kind: PatKind::Constant { .. }, .. }, + .. + } = pat.kind && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span) { // If the pattern to match is an integer literal: @@ -479,17 +498,17 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { AdtDefinedHere { adt_def_span, ty, variants } }; - // Emit an extra note if the first uncovered witness is - // visibly uninhabited anywhere in the current crate. + // Emit an extra note if the first uncovered witness would be uninhabited + // if we disregard visibility. let witness_1_is_privately_uninhabited = if cx.tcx.features().exhaustive_patterns && let Some(witness_1) = witnesses.get(0) - && let ty::Adt(adt, substs) = witness_1.ty().kind() + && let ty::Adt(adt, args) = witness_1.ty().kind() && adt.is_enum() && let Constructor::Variant(variant_index) = witness_1.ctor() { let variant = adt.variant(*variant_index); - let inhabited = variant.inhabited_predicate(cx.tcx, *adt).subst(cx.tcx, substs); + let inhabited = variant.inhabited_predicate(cx.tcx, *adt).instantiate(cx.tcx, args); assert!(inhabited.apply(cx.tcx, cx.param_env, cx.module)); !inhabited.apply_ignore_module(cx.tcx, cx.param_env) } else { @@ -674,7 +693,7 @@ fn non_exhaustive_match<'p, 'tcx>( err = create_e0004( cx.tcx.sess, sp, - format!("non-exhaustive patterns: {} not covered", joined_patterns), + format!("non-exhaustive patterns: {joined_patterns} not covered"), ); err.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns)); patterns_len = witnesses.len(); @@ -704,15 +723,13 @@ fn non_exhaustive_match<'p, 'tcx>( && matches!(witnesses[0].ctor(), Constructor::NonExhaustive) { err.note(format!( - "`{}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \ + "`{scrut_ty}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \ exhaustively", - scrut_ty, )); if cx.tcx.sess.is_nightly_build() { err.help(format!( "add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \ - enable precise `{}` matching", - scrut_ty, + enable precise `{scrut_ty}` matching", )); } } @@ -728,18 +745,13 @@ fn non_exhaustive_match<'p, 'tcx>( [] if sp.eq_ctxt(expr_span) => { // Get the span for the empty match body `{}`. let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) { - (format!("\n{}", snippet), " ") + (format!("\n{snippet}"), " ") } else { (" ".to_string(), "") }; suggestion = Some(( sp.shrink_to_hi().with_hi(expr_span.hi()), - format!( - " {{{indentation}{more}{pattern} => todo!(),{indentation}}}", - indentation = indentation, - more = more, - pattern = pattern, - ), + format!(" {{{indentation}{more}{pattern} => todo!(),{indentation}}}",), )); } [only] => { @@ -748,7 +760,7 @@ fn non_exhaustive_match<'p, 'tcx>( && let Ok(with_trailing) = sm.span_extend_while(only.span, |c| c.is_whitespace() || c == ',') && sm.is_multiline(with_trailing) { - (format!("\n{}", snippet), true) + (format!("\n{snippet}"), true) } else { (" ".to_string(), false) }; @@ -763,7 +775,7 @@ fn non_exhaustive_match<'p, 'tcx>( }; suggestion = Some(( only.span.shrink_to_hi(), - format!("{}{}{} => todo!()", comma, pre_indentation, pattern), + format!("{comma}{pre_indentation}{pattern} => todo!()"), )); } [.., prev, last] => { @@ -786,7 +798,7 @@ fn non_exhaustive_match<'p, 'tcx>( if let Some(spacing) = spacing { suggestion = Some(( last.span.shrink_to_hi(), - format!("{}{}{} => todo!()", comma, spacing, pattern), + format!("{comma}{spacing}{pattern} => todo!()"), )); } } @@ -813,6 +825,11 @@ fn non_exhaustive_match<'p, 'tcx>( _ => " or multiple match arms", }, ); + + let all_arms_have_guards = arms.iter().all(|arm_id| thir[*arm_id].guard.is_some()); + if !is_empty_match && all_arms_have_guards { + err.subdiagnostic(NonExhaustiveMatchAllArmsGuarded); + } if let Some((span, sugg)) = suggestion { err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders); } else { @@ -878,7 +895,7 @@ fn adt_defined_here<'p, 'tcx>( for pat in spans { span.push_span_label(pat, "not covered"); } - err.span_note(span, format!("`{}` defined here", ty)); + err.span_note(span, format!("`{ty}` defined here")); } } @@ -920,7 +937,9 @@ fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'a>( /// This analysis is *not* subsumed by NLL. fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, '_, 'tcx>, pat: &Pat<'tcx>) { // Extract `sub` in `binding @ sub`. - let PatKind::Binding { name, mode, ty, subpattern: Some(box ref sub), .. } = pat.kind else { return }; + let PatKind::Binding { name, mode, ty, subpattern: Some(box ref sub), .. } = pat.kind else { + return; + }; let is_binding_by_move = |ty: Ty<'tcx>| !ty.is_copy_modulo_regions(cx.tcx, cx.param_env); diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index b243f1dc8d0df..1376344cfdaea 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -1,13 +1,14 @@ use rustc_hir as hir; +use rustc_hir::def_id::DefId; use rustc_index::Idx; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::Obligation; use rustc_middle::mir; use rustc_middle::thir::{FieldPat, Pat, PatKind}; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt, ValTree}; use rustc_session::lint; use rustc_span::Span; -use rustc_target::abi::FieldIdx; +use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCause}; @@ -29,11 +30,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { cv: mir::ConstantKind<'tcx>, id: hir::HirId, span: Span, - mir_structural_match_violation: bool, + check_body_for_struct_match_violation: Option, ) -> Box> { let infcx = self.tcx.infer_ctxt().build(); let mut convert = ConstToPat::new(self, id, span, infcx); - convert.to_pat(cv, mir_structural_match_violation) + convert.to_pat(cv, check_body_for_struct_match_violation) } } @@ -104,7 +105,7 @@ impl<'tcx> ConstToPat<'tcx> { fn to_pat( &mut self, cv: mir::ConstantKind<'tcx>, - mir_structural_match_violation: bool, + check_body_for_struct_match_violation: Option, ) -> Box> { trace!(self.treat_byte_string_as_slice); // This method is just a wrapper handling a validity check; the heavy lifting is @@ -114,14 +115,44 @@ impl<'tcx> ConstToPat<'tcx> { // once indirect_structural_match is a full fledged error, this // level of indirection can be eliminated - let inlined_const_as_pat = - self.recur(cv, mir_structural_match_violation).unwrap_or_else(|_| { - Box::new(Pat { - span: self.span, - ty: cv.ty(), - kind: PatKind::Constant { value: cv }, - }) - }); + let mir_structural_match_violation = check_body_for_struct_match_violation.map(|def_id| { + // `mir_const_qualif` must be called with the `DefId` of the item where the const is + // defined, not where it is declared. The difference is significant for associated + // constants. + self.tcx().mir_const_qualif(def_id).custom_eq + }); + debug!(?check_body_for_struct_match_violation, ?mir_structural_match_violation); + + let inlined_const_as_pat = match cv { + mir::ConstantKind::Ty(c) => match c.kind() { + ty::ConstKind::Param(_) + | ty::ConstKind::Infer(_) + | ty::ConstKind::Bound(_, _) + | ty::ConstKind::Placeholder(_) + | ty::ConstKind::Unevaluated(_) + | ty::ConstKind::Error(_) + | ty::ConstKind::Expr(_) => { + span_bug!(self.span, "unexpected const in `to_pat`: {:?}", c.kind()) + } + ty::ConstKind::Value(valtree) => self + .recur(valtree, cv.ty(), mir_structural_match_violation.unwrap_or(false)) + .unwrap_or_else(|_| { + Box::new(Pat { + span: self.span, + ty: cv.ty(), + kind: PatKind::Constant { value: cv }, + }) + }), + }, + mir::ConstantKind::Unevaluated(_, _) => { + span_bug!(self.span, "unevaluated const in `to_pat`: {cv:?}") + } + mir::ConstantKind::Val(_, _) => Box::new(Pat { + span: self.span, + ty: cv.ty(), + kind: PatKind::Constant { value: cv }, + }), + }; if !self.saw_const_match_error.get() { // If we were able to successfully convert the const to some pat, @@ -141,29 +172,70 @@ impl<'tcx> ConstToPat<'tcx> { // // FIXME(#73448): Find a way to bring const qualification into parity with // `search_for_structural_match_violation`. - if structural.is_none() && mir_structural_match_violation { + if structural.is_none() && mir_structural_match_violation.unwrap_or(false) { warn!("MIR const-checker found novel structural match violation. See #73448."); return inlined_const_as_pat; } if let Some(non_sm_ty) = structural { if !self.type_may_have_partial_eq_impl(cv.ty()) { - // fatal avoids ICE from resolution of nonexistent method (rare case). - self.tcx() - .sess - .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty: non_sm_ty }); - } else if mir_structural_match_violation && !self.saw_const_match_lint.get() { - self.tcx().emit_spanned_lint( - lint::builtin::INDIRECT_STRUCTURAL_MATCH, - self.id, - self.span, - IndirectStructuralMatch { non_sm_ty }, - ); - } else { - debug!( - "`search_for_structural_match_violation` found one, but `CustomEq` was \ - not in the qualifs for that `const`" - ); + if let ty::Adt(def, ..) = non_sm_ty.kind() { + if def.is_union() { + let err = UnionPattern { span: self.span }; + self.tcx().sess.emit_err(err); + } else { + // fatal avoids ICE from resolution of nonexistent method (rare case). + self.tcx() + .sess + .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty }); + } + } else { + let err = InvalidPattern { span: self.span, non_sm_ty }; + self.tcx().sess.emit_err(err); + return Box::new(Pat { span: self.span, ty: cv.ty(), kind: PatKind::Wild }); + } + } else if !self.saw_const_match_lint.get() { + if let Some(mir_structural_match_violation) = mir_structural_match_violation { + match non_sm_ty.kind() { + ty::RawPtr(pointee) + if pointee.ty.is_sized(self.tcx(), self.param_env) => {} + ty::FnPtr(..) | ty::RawPtr(..) => { + self.tcx().emit_spanned_lint( + lint::builtin::POINTER_STRUCTURAL_MATCH, + self.id, + self.span, + PointerPattern, + ); + } + ty::Adt(..) if mir_structural_match_violation => { + self.tcx().emit_spanned_lint( + lint::builtin::INDIRECT_STRUCTURAL_MATCH, + self.id, + self.span, + IndirectStructuralMatch { non_sm_ty }, + ); + } + _ => { + debug!( + "`search_for_structural_match_violation` found one, but `CustomEq` was \ + not in the qualifs for that `const`" + ); + } + } + } + } + } else if !self.saw_const_match_lint.get() { + match cv.ty().kind() { + ty::RawPtr(pointee) if pointee.ty.is_sized(self.tcx(), self.param_env) => {} + ty::FnPtr(..) | ty::RawPtr(..) => { + self.tcx().emit_spanned_lint( + lint::builtin::POINTER_STRUCTURAL_MATCH, + self.id, + self.span, + PointerPattern, + ); + } + _ => {} } } } @@ -171,6 +243,7 @@ impl<'tcx> ConstToPat<'tcx> { inlined_const_as_pat } + #[instrument(level = "trace", skip(self), ret)] fn type_may_have_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool { // double-check there even *is* a semantic `PartialEq` to dispatch to. // @@ -187,29 +260,19 @@ impl<'tcx> ConstToPat<'tcx> { ); // FIXME: should this call a `predicate_must_hold` variant instead? - let has_impl = self.infcx.predicate_may_hold(&partial_eq_obligation); - - // Note: To fix rust-lang/rust#65466, we could just remove this type - // walk hack for function pointers, and unconditionally error - // if `PartialEq` is not implemented. However, that breaks stable - // code at the moment, because types like `for <'a> fn(&'a ())` do - // not *yet* implement `PartialEq`. So for now we leave this here. - has_impl - || ty.walk().any(|t| match t.unpack() { - ty::subst::GenericArgKind::Lifetime(_) => false, - ty::subst::GenericArgKind::Type(t) => t.is_fn_ptr(), - ty::subst::GenericArgKind::Const(_) => false, - }) + self.infcx.predicate_may_hold(&partial_eq_obligation) } fn field_pats( &self, - vals: impl Iterator>, + vals: impl Iterator, Ty<'tcx>)>, ) -> Result>, FallbackToConstRef> { vals.enumerate() - .map(|(idx, val)| { + .map(|(idx, (val, ty))| { let field = FieldIdx::new(idx); - Ok(FieldPat { field, pattern: self.recur(val, false)? }) + // Patterns can only use monomorphic types. + let ty = self.tcx().normalize_erasing_regions(self.param_env, ty); + Ok(FieldPat { field, pattern: self.recur(val, ty, false)? }) }) .collect() } @@ -218,7 +281,8 @@ impl<'tcx> ConstToPat<'tcx> { #[instrument(skip(self), level = "debug")] fn recur( &self, - cv: mir::ConstantKind<'tcx>, + cv: ValTree<'tcx>, + ty: Ty<'tcx>, mir_structural_match_violation: bool, ) -> Result>, FallbackToConstRef> { let id = self.id; @@ -226,8 +290,9 @@ impl<'tcx> ConstToPat<'tcx> { let tcx = self.tcx(); let param_env = self.param_env; - let kind = match cv.ty().kind() { + let kind = match ty.kind() { ty::Float(_) => { + self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, id, @@ -236,27 +301,6 @@ impl<'tcx> ConstToPat<'tcx> { ); return Err(FallbackToConstRef); } - ty::Adt(adt_def, _) if adt_def.is_union() => { - // Matching on union fields is unsafe, we can't hide it in constants - self.saw_const_match_error.set(true); - let err = UnionPattern { span }; - tcx.sess.emit_err(err); - PatKind::Wild - } - ty::Adt(..) - if !self.type_may_have_partial_eq_impl(cv.ty()) - // FIXME(#73448): Find a way to bring const qualification into parity with - // `search_for_structural_match_violation` and then remove this condition. - - // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we - // could get `Option`, even though `Option` is annotated with derive. - && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, cv.ty()) => - { - self.saw_const_match_error.set(true); - let err = TypeNotStructural { span, non_sm_ty }; - tcx.sess.emit_err(err); - PatKind::Wild - } // If the type is not structurally comparable, just emit the constant directly, // causing the pattern match code to treat it opaquely. // FIXME: This code doesn't emit errors itself, the caller emits the errors. @@ -266,16 +310,14 @@ impl<'tcx> ConstToPat<'tcx> { // details. // Backwards compatibility hack because we can't cause hard errors on these // types, so we compare them via `PartialEq::eq` at runtime. - ty::Adt(..) if !self.type_marked_structural(cv.ty()) && self.behind_reference.get() => { - if !self.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { + ty::Adt(..) if !self.type_marked_structural(ty) && self.behind_reference.get() => { + if !self.saw_const_match_error.get() && !self.saw_const_match_lint.get() { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, - IndirectStructuralMatch { non_sm_ty: cv.ty() }, + IndirectStructuralMatch { non_sm_ty: ty }, ); } // Since we are behind a reference, we can just bubble the error up so we get a @@ -283,108 +325,69 @@ impl<'tcx> ConstToPat<'tcx> { // `PartialEq::eq` on it. return Err(FallbackToConstRef); } - ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty()) => { - debug!( - "adt_def {:?} has !type_marked_structural for cv.ty: {:?}", - adt_def, - cv.ty() - ); + ty::FnDef(..) => { self.saw_const_match_error.set(true); - let err = TypeNotStructural { span, non_sm_ty: cv.ty() }; + tcx.sess.emit_err(InvalidPattern { span, non_sm_ty: ty }); + PatKind::Wild + } + ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => { + debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, ty,); + self.saw_const_match_error.set(true); + let err = TypeNotStructural { span, non_sm_ty: ty }; tcx.sess.emit_err(err); PatKind::Wild } - ty::Adt(adt_def, substs) if adt_def.is_enum() => { - let destructured = tcx.destructure_mir_constant(param_env, cv); - + ty::Adt(adt_def, args) if adt_def.is_enum() => { + let (&variant_index, fields) = cv.unwrap_branch().split_first().unwrap(); + let variant_index = + VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap()); PatKind::Variant { adt_def: *adt_def, - substs, - variant_index: destructured - .variant - .expect("destructed const of adt without variant id"), - subpatterns: self.field_pats(destructured.fields.iter().copied())?, + args, + variant_index, + subpatterns: self.field_pats( + fields.iter().copied().zip( + adt_def.variants()[variant_index] + .fields + .iter() + .map(|field| field.ty(self.tcx(), args)), + ), + )?, } } - ty::Tuple(_) | ty::Adt(_, _) => { - let destructured = tcx.destructure_mir_constant(param_env, cv); - PatKind::Leaf { subpatterns: self.field_pats(destructured.fields.iter().copied())? } - } - ty::Array(..) => PatKind::Array { - prefix: tcx - .destructure_mir_constant(param_env, cv) - .fields + ty::Tuple(fields) => PatKind::Leaf { + subpatterns: self + .field_pats(cv.unwrap_branch().iter().copied().zip(fields.iter()))?, + }, + ty::Adt(def, args) => PatKind::Leaf { + subpatterns: self.field_pats(cv.unwrap_branch().iter().copied().zip( + def.non_enum_variant().fields.iter().map(|field| field.ty(self.tcx(), args)), + ))?, + }, + ty::Slice(elem_ty) => PatKind::Slice { + prefix: cv + .unwrap_branch() .iter() - .map(|val| self.recur(*val, false)) + .map(|val| self.recur(*val, *elem_ty, false)) + .collect::>()?, + slice: None, + suffix: Box::new([]), + }, + ty::Array(elem_ty, _) => PatKind::Array { + prefix: cv + .unwrap_branch() + .iter() + .map(|val| self.recur(*val, *elem_ty, false)) .collect::>()?, slice: None, suffix: Box::new([]), }, ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() { - // These are not allowed and will error elsewhere anyway. - ty::Dynamic(..) => { - self.saw_const_match_error.set(true); - let err = InvalidPattern { span, non_sm_ty: cv.ty() }; - tcx.sess.emit_err(err); - PatKind::Wild - } - // `&str` is represented as `ConstValue::Slice`, let's keep using this + // `&str` is represented as a valtree, let's keep using this // optimization for now. - ty::Str => PatKind::Constant { value: cv }, - // `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when - // matching against references, you can only use byte string literals. - // The typechecker has a special case for byte string literals, by treating them - // as slices. This means we turn `&[T; N]` constants into slice patterns, which - // has no negative effects on pattern matching, even if we're actually matching on - // arrays. - ty::Array(..) if !self.treat_byte_string_as_slice => { - let old = self.behind_reference.replace(true); - let array = tcx.deref_mir_constant(self.param_env.and(cv)); - let val = PatKind::Deref { - subpattern: Box::new(Pat { - kind: PatKind::Array { - prefix: tcx - .destructure_mir_constant(param_env, array) - .fields - .iter() - .map(|val| self.recur(*val, false)) - .collect::>()?, - slice: None, - suffix: Box::new([]), - }, - span, - ty: *pointee_ty, - }), - }; - self.behind_reference.set(old); - val - } - ty::Array(elem_ty, _) | - // Cannot merge this with the catch all branch below, because the `const_deref` - // changes the type from slice to array, we need to keep the original type in the - // pattern. - ty::Slice(elem_ty) => { - let old = self.behind_reference.replace(true); - let array = tcx.deref_mir_constant(self.param_env.and(cv)); - let val = PatKind::Deref { - subpattern: Box::new(Pat { - kind: PatKind::Slice { - prefix: tcx - .destructure_mir_constant(param_env, array) - .fields - .iter() - .map(|val| self.recur(*val, false)) - .collect::>()?, - slice: None, - suffix: Box::new([]), - }, - span, - ty: tcx.mk_slice(elem_ty), - }), - }; - self.behind_reference.set(old); - val - } + ty::Str => PatKind::Constant { + value: mir::ConstantKind::Ty(ty::Const::new_value(tcx, cv, ty)), + }, // Backwards compatibility hack: support references to non-structural types, // but hard error if we aren't behind a double reference. We could just use // the fallback code path below, but that would allow *more* of this fishy @@ -392,11 +395,9 @@ impl<'tcx> ConstToPat<'tcx> { // instead of a hard error. ty::Adt(_, _) if !self.type_marked_structural(*pointee_ty) => { if self.behind_reference.get() { - if !self.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_spanned_lint( + if !self.saw_const_match_error.get() && !self.saw_const_match_lint.get() { + self.saw_const_match_lint.set(true); + tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, self.id, span, @@ -417,49 +418,41 @@ impl<'tcx> ConstToPat<'tcx> { // convert the dereferenced constant to a pattern that is the sub-pattern of the // deref pattern. _ => { - if !pointee_ty.is_sized(tcx, param_env) { - // `tcx.deref_mir_constant()` below will ICE with an unsized type - // (except slices, which are handled in a separate arm above). - + if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() { let err = UnsizedPattern { span, non_sm_ty: *pointee_ty }; tcx.sess.emit_err(err); + // FIXME: introduce PatKind::Error to silence follow up diagnostics due to unreachable patterns. PatKind::Wild } else { let old = self.behind_reference.replace(true); - let subpattern = self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false)?; + // `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when + // matching against references, you can only use byte string literals. + // The typechecker has a special case for byte string literals, by treating them + // as slices. This means we turn `&[T; N]` constants into slice patterns, which + // has no negative effects on pattern matching, even if we're actually matching on + // arrays. + let pointee_ty = match *pointee_ty.kind() { + ty::Array(elem_ty, _) if self.treat_byte_string_as_slice => { + Ty::new_slice(tcx, elem_ty) + } + _ => *pointee_ty, + }; + // References have the same valtree representation as their pointee. + let subpattern = self.recur(cv, pointee_ty, false)?; self.behind_reference.set(old); PatKind::Deref { subpattern } } } }, - ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => { - PatKind::Constant { value: cv } - } - ty::RawPtr(pointee) if pointee.ty.is_sized(tcx, param_env) => { - return Err(FallbackToConstRef); - } - // FIXME: these can have very surprising behaviour where optimization levels or other - // compilation choices change the runtime behaviour of the match. - // See https://github.com/rust-lang/rust/issues/70861 for examples. - ty::FnPtr(..) | ty::RawPtr(..) => { - if !self.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_spanned_lint( - lint::builtin::POINTER_STRUCTURAL_MATCH, - id, - span, - PointerPattern - ); - } - return Err(FallbackToConstRef); - } + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) => PatKind::Constant { + value: mir::ConstantKind::Ty(ty::Const::new_value(tcx, cv, ty)), + }, + ty::FnPtr(..) | ty::RawPtr(..) => unreachable!(), _ => { self.saw_const_match_error.set(true); - let err = InvalidPattern { span, non_sm_ty: cv.ty() }; - tcx.sess.emit_err(err); + let err = InvalidPattern { span, non_sm_ty: ty }; + tcx.sess.emit_err(err); PatKind::Wild } }; @@ -472,7 +465,7 @@ impl<'tcx> ConstToPat<'tcx> { // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we // could get `Option`, even though `Option` is annotated with derive. - && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, cv.ty()) + && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, ty) { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( @@ -483,6 +476,6 @@ impl<'tcx> ConstToPat<'tcx> { ); } - Ok(Box::new(Pat { span, ty: cv.ty(), kind })) + Ok(Box::new(Pat { span, ty, kind })) } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 6a77146138bb5..bee1c4e4614f4 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -53,11 +53,11 @@ use smallvec::{smallvec, SmallVec}; use rustc_data_structures::captures::Captures; use rustc_hir::{HirId, RangeEnd}; use rustc_index::Idx; +use rustc_middle::middle::stability::EvalResult; use rustc_middle::mir; use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; -use rustc_middle::{middle::stability::EvalResult, mir::interpret::ConstValue}; use rustc_session::lint; use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{FieldIdx, Integer, Size, VariantIdx, FIRST_VARIANT}; @@ -140,28 +140,17 @@ impl IntRange { value: mir::ConstantKind<'tcx>, ) -> Option { let ty = value.ty(); - if let Some((target_size, bias)) = Self::integral_size_and_signed_bias(tcx, ty) { - let val = if let mir::ConstantKind::Val(ConstValue::Scalar(scalar), _) = value { - // For this specific pattern we can skip a lot of effort and go - // straight to the result, after doing a bit of checking. (We - // could remove this branch and just fall through, which - // is more general but much slower.) - scalar.to_bits_or_ptr_internal(target_size).unwrap().left()? - } else { - if let mir::ConstantKind::Ty(c) = value - && let ty::ConstKind::Value(_) = c.kind() - { - bug!("encountered ConstValue in mir::ConstantKind::Ty, whereas this is expected to be in ConstantKind::Val"); - } + let (target_size, bias) = Self::integral_size_and_signed_bias(tcx, ty)?; + let val = match value { + mir::ConstantKind::Ty(c) if let ty::ConstKind::Value(valtree) = c.kind() => { + valtree.unwrap_leaf().to_bits(target_size).ok() + }, + // This is a more general form of the previous case. + _ => value.try_eval_bits(tcx, param_env, ty), + }?; - // This is a more general form of the previous case. - value.try_eval_bits(tcx, param_env, ty)? - }; - let val = val ^ bias; - Some(IntRange { range: val..=val, bias }) - } else { - None - } + let val = val ^ bias; + Some(IntRange { range: val..=val, bias }) } #[inline] @@ -317,9 +306,9 @@ impl fmt::Debug for IntRange { let (lo, hi) = self.boundaries(); let bias = self.bias; let (lo, hi) = (lo ^ bias, hi ^ bias); - write!(f, "{}", lo)?; + write!(f, "{lo}")?; write!(f, "{}", RangeEnd::Included)?; - write!(f, "{}", hi) + write!(f, "{hi}") } } @@ -933,7 +922,7 @@ impl<'tcx> SplitWildcard<'tcx> { let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; smallvec![Slice(Slice::new(None, kind))] } - ty::Adt(def, substs) if def.is_enum() => { + ty::Adt(def, args) if def.is_enum() => { // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an // additional "unknown" constructor. // There is no point in enumerating all possible variants, because the user can't @@ -961,21 +950,19 @@ impl<'tcx> SplitWildcard<'tcx> { let is_secretly_empty = def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; - let mut ctors: SmallVec<[_; 1]> = def - .variants() - .iter_enumerated() - .filter(|(_, v)| { - // If `exhaustive_patterns` is enabled, we exclude variants known to be - // uninhabited. - !is_exhaustive_pat_feature - || v.inhabited_predicate(cx.tcx, *def).subst(cx.tcx, substs).apply( - cx.tcx, - cx.param_env, - cx.module, - ) - }) - .map(|(idx, _)| Variant(idx)) - .collect(); + let mut ctors: SmallVec<[_; 1]> = + def.variants() + .iter_enumerated() + .filter(|(_, v)| { + // If `exhaustive_patterns` is enabled, we exclude variants known to be + // uninhabited. + !is_exhaustive_pat_feature + || v.inhabited_predicate(cx.tcx, *def) + .instantiate(cx.tcx, args) + .apply(cx.tcx, cx.param_env, cx.module) + }) + .map(|(idx, _)| Variant(idx)) + .collect(); if is_secretly_empty || is_declared_nonexhaustive { ctors.push(NonExhaustive); @@ -1167,12 +1154,12 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { ty: Ty<'tcx>, variant: &'a VariantDef, ) -> impl Iterator)> + Captures<'a> + Captures<'p> { - let ty::Adt(adt, substs) = ty.kind() else { bug!() }; + let ty::Adt(adt, args) = ty.kind() else { bug!() }; // Whether we must not match the fields of this variant exhaustively. let is_non_exhaustive = variant.is_field_list_non_exhaustive() && !adt.did().is_local(); variant.fields.iter().enumerate().filter_map(move |(i, field)| { - let ty = field.ty(cx.tcx, substs); + let ty = field.ty(cx.tcx, args); // `field.ty()` doesn't normalize after substituting. let ty = cx.tcx.normalize_erasing_regions(cx.param_env, ty); let is_visible = adt.is_enum() || field.vis.is_accessible_from(cx.module, cx.tcx); @@ -1194,11 +1181,11 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { Single | Variant(_) => match pcx.ty.kind() { ty::Tuple(fs) => Fields::wildcards_from_tys(pcx.cx, fs.iter(), pcx.span), ty::Ref(_, rty, _) => Fields::wildcards_from_tys(pcx.cx, once(*rty), pcx.span), - ty::Adt(adt, substs) => { + ty::Adt(adt, args) => { if adt.is_box() { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. - Fields::wildcards_from_tys(pcx.cx, once(substs.type_at(0)), pcx.span) + Fields::wildcards_from_tys(pcx.cx, once(args.type_at(0)), pcx.span) } else { let variant = &adt.variant(constructor.variant_index_for_adt(*adt)); let tys = Fields::list_variant_nonhidden_fields(pcx.cx, pcx.ty, variant) @@ -1305,7 +1292,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { } fields = Fields::from_iter(cx, wilds); } - ty::Adt(adt, substs) if adt.is_box() => { + ty::Adt(adt, args) if adt.is_box() => { // The only legal patterns of type `Box` (outside `std`) are `_` and box // patterns. If we're here we can assume this is a box pattern. // FIXME(Nadrieril): A `Box` can in theory be matched either with `Box(_, @@ -1322,7 +1309,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { let pat = if let Some(pat) = pattern { mkpat(&pat.pattern) } else { - DeconstructedPat::wildcard(substs.type_at(0), pat.span) + DeconstructedPat::wildcard(args.type_at(0), pat.span) }; ctor = Single; fields = Fields::singleton(cx, pat); @@ -1448,7 +1435,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { // the pattern is a box pattern. PatKind::Deref { subpattern: subpatterns.next().unwrap() } } - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { let variant_index = self.ctor.variant_index_for_adt(*adt_def); let variant = &adt_def.variant(variant_index); let subpatterns = Fields::list_variant_nonhidden_fields(cx, self.ty, variant) @@ -1457,7 +1444,7 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> { .collect(); if adt_def.is_enum() { - PatKind::Variant { adt_def: *adt_def, substs, variant_index, subpatterns } + PatKind::Variant { adt_def: *adt_def, args, variant_index, subpatterns } } else { PatKind::Leaf { subpatterns } } @@ -1632,7 +1619,7 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { // of `std`). So this branch is only reachable when the feature is enabled and // the pattern is a box pattern. let subpattern = self.iter_fields().next().unwrap(); - write!(f, "box {:?}", subpattern) + write!(f, "box {subpattern:?}") } ty::Adt(..) | ty::Tuple(..) => { let variant = match self.ty.kind() { @@ -1651,7 +1638,7 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { write!(f, "(")?; for p in self.iter_fields() { write!(f, "{}", start_or_comma())?; - write!(f, "{:?}", p)?; + write!(f, "{p:?}")?; } write!(f, ")") } @@ -1687,11 +1674,11 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { write!(f, "]") } &FloatRange(lo, hi, end) => { - write!(f, "{}", lo)?; - write!(f, "{}", end)?; - write!(f, "{}", hi) + write!(f, "{lo}")?; + write!(f, "{end}")?; + write!(f, "{hi}") } - IntRange(range) => write!(f, "{:?}", range), // Best-effort, will render e.g. `false` as `0..=0` + IntRange(range) => write!(f, "{range:?}"), // Best-effort, will render e.g. `false` as `0..=0` Wildcard | Missing { .. } | NonExhaustive => write!(f, "_ : {:?}", self.ty), Or => { for pat in self.iter_fields() { @@ -1699,7 +1686,7 @@ impl<'p, 'tcx> fmt::Debug for DeconstructedPat<'p, 'tcx> { } Ok(()) } - Str(value) => write!(f, "{}", value), + Str(value) => write!(f, "{value}"), Opaque => write!(f, ""), } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 1cf2f7ec0fff1..c08fe54c39c4c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -18,14 +18,15 @@ use rustc_hir::pat_util::EnumerateAndAdjustIterator; use rustc_hir::RangeEnd; use rustc_index::Idx; use rustc_middle::mir::interpret::{ - ConstValue, ErrorHandled, LitToConstError, LitToConstInput, Scalar, + ConstValue, ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar, }; -use rustc_middle::mir::{self, UserTypeProjection}; +use rustc_middle::mir::{self, ConstantKind, UserTypeProjection}; use rustc_middle::mir::{BorrowKind, Mutability}; use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange}; -use rustc_middle::ty::subst::{GenericArg, SubstsRef}; use rustc_middle::ty::CanonicalUserTypeAnnotation; -use rustc_middle::ty::{self, AdtDef, ConstKind, Region, Ty, TyCtxt, UserType}; +use rustc_middle::ty::TypeVisitableExt; +use rustc_middle::ty::{self, AdtDef, Region, Ty, TyCtxt, UserType}; +use rustc_middle::ty::{GenericArg, GenericArgsRef}; use rustc_span::{Span, Symbol}; use rustc_target::abi::FieldIdx; @@ -286,7 +287,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { ty::BindByValue(mutbl) => (mutbl, BindingMode::ByValue), ty::BindByReference(hir::Mutability::Mut) => ( Mutability::Not, - BindingMode::ByRef(BorrowKind::Mut { allow_two_phase_borrow: false }), + BindingMode::ByRef(BorrowKind::Mut { kind: mir::MutBorrowKind::Default }), ), ty::BindByReference(hir::Mutability::Not) => { (Mutability::Not, BindingMode::ByRef(BorrowKind::Shared)) @@ -415,8 +416,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let enum_id = self.tcx.parent(variant_id); let adt_def = self.tcx.adt_def(enum_id); if adt_def.is_enum() { - let substs = match ty.kind() { - ty::Adt(_, substs) | ty::FnDef(_, substs) => substs, + let args = match ty.kind() { + ty::Adt(_, args) | ty::FnDef(_, args) => args, ty::Error(_) => { // Avoid ICE (#50585) return PatKind::Wild; @@ -425,7 +426,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { }; PatKind::Variant { adt_def, - substs, + args, variant_index: adt_def.variant_index_with_id(variant_id), subpatterns, } @@ -438,7 +439,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { DefKind::Struct | DefKind::Ctor(CtorOf::Struct, ..) | DefKind::Union - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::AssocTy, _, ) @@ -459,7 +460,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } }; - if let Some(user_ty) = self.user_substs_applied_to_ty_of_hir_id(hir_id) { + if let Some(user_ty) = self.user_args_applied_to_ty_of_hir_id(hir_id) { debug!("lower_variant_or_leaf: kind={:?} user_ty={:?} span={:?}", kind, user_ty, span); let annotation = CanonicalUserTypeAnnotation { user_ty: Box::new(user_ty), @@ -495,13 +496,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { // Use `Reveal::All` here because patterns are always monomorphic even if their function // isn't. let param_env_reveal_all = self.param_env.with_reveal_all_normalized(self.tcx); - // N.B. There is no guarantee that substs collected in typeck results are fully normalized, + // N.B. There is no guarantee that args collected in typeck results are fully normalized, // so they need to be normalized in order to pass to `Instance::resolve`, which will ICE // if given unnormalized types. - let substs = self + let args = self .tcx - .normalize_erasing_regions(param_env_reveal_all, self.typeck_results.node_substs(id)); - let instance = match ty::Instance::resolve(self.tcx, param_env_reveal_all, def_id, substs) { + .normalize_erasing_regions(param_env_reveal_all, self.typeck_results.node_args(id)); + let instance = match ty::Instance::resolve(self.tcx, param_env_reveal_all, def_id, args) { Ok(Some(i)) => i, Ok(None) => { // It should be assoc consts if there's no error but we cannot resolve it. @@ -518,16 +519,24 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } }; - // `mir_const_qualif` must be called with the `DefId` of the item where the const is - // defined, not where it is declared. The difference is significant for associated - // constants. - let mir_structural_match_violation = self.tcx.mir_const_qualif(instance.def_id()).custom_eq; - debug!("mir_structural_match_violation({:?}) -> {}", qpath, mir_structural_match_violation); - - match self.tcx.const_eval_instance(param_env_reveal_all, instance, Some(span)) { - Ok(literal) => { - let const_ = mir::ConstantKind::Val(literal, ty); - let pattern = self.const_to_pat(const_, id, span, mir_structural_match_violation); + let cid = GlobalId { instance, promoted: None }; + // Prefer valtrees over opaque constants. + let const_value = self + .tcx + .const_eval_global_id_for_typeck(param_env_reveal_all, cid, Some(span)) + .map(|val| match val { + Some(valtree) => mir::ConstantKind::Ty(ty::Const::new_value(self.tcx, valtree, ty)), + None => mir::ConstantKind::Val( + self.tcx + .const_eval_global_id(param_env_reveal_all, cid, Some(span)) + .expect("const_eval_global_id_for_typeck should have already failed"), + ty, + ), + }); + + match const_value { + Ok(const_) => { + let pattern = self.const_to_pat(const_, id, span, Some(instance.def_id())); if !is_associated_const { return pattern; @@ -573,31 +582,70 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { /// Converts inline const patterns. fn lower_inline_const( &mut self, - anon_const: &'tcx hir::AnonConst, + block: &'tcx hir::ConstBlock, id: hir::HirId, span: Span, ) -> PatKind<'tcx> { - let value = mir::ConstantKind::from_inline_const(self.tcx, anon_const.def_id); - - // Evaluate early like we do in `lower_path`. - let value = value.eval(self.tcx, self.param_env); - - match value { - mir::ConstantKind::Ty(c) => match c.kind() { - ConstKind::Param(_) => { - self.tcx.sess.emit_err(ConstParamInPattern { span }); - return PatKind::Wild; + let tcx = self.tcx; + let def_id = block.def_id; + let body_id = block.body; + let expr = &tcx.hir().body(body_id).value; + let ty = tcx.typeck(def_id).node_type(block.hir_id); + + // Special case inline consts that are just literals. This is solely + // a performance optimization, as we could also just go through the regular + // const eval path below. + // FIXME: investigate the performance impact of removing this. + let lit_input = match expr.kind { + hir::ExprKind::Lit(ref lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: false }), + hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => match expr.kind { + hir::ExprKind::Lit(ref lit) => { + Some(LitToConstInput { lit: &lit.node, ty, neg: true }) } - ConstKind::Error(_) => { - return PatKind::Wild; - } - _ => bug!("Expected ConstKind::Param"), + _ => None, }, - mir::ConstantKind::Val(_, _) => self.const_to_pat(value, id, span, false).kind, - mir::ConstantKind::Unevaluated(..) => { - // If we land here it means the const can't be evaluated because it's `TooGeneric`. - self.tcx.sess.emit_err(ConstPatternDependsOnGenericParameter { span }); - return PatKind::Wild; + _ => None, + }; + if let Some(lit_input) = lit_input { + match tcx.at(expr.span).lit_to_const(lit_input) { + Ok(c) => return self.const_to_pat(ConstantKind::Ty(c), id, span, None).kind, + // If an error occurred, ignore that it's a literal + // and leave reporting the error up to const eval of + // the unevaluated constant below. + Err(_) => {} + } + } + + let typeck_root_def_id = tcx.typeck_root_def_id(def_id.to_def_id()); + let parent_args = + tcx.erase_regions(ty::GenericArgs::identity_for_item(tcx, typeck_root_def_id)); + let args = ty::InlineConstArgs::new(tcx, ty::InlineConstArgsParts { parent_args, ty }).args; + + let uneval = mir::UnevaluatedConst { def: def_id.to_def_id(), args, promoted: None }; + debug_assert!(!args.has_free_regions()); + + let ct = ty::UnevaluatedConst { def: def_id.to_def_id(), args: args }; + // First try using a valtree in order to destructure the constant into a pattern. + if let Ok(Some(valtree)) = + self.tcx.const_eval_resolve_for_typeck(self.param_env, ct, Some(span)) + { + self.const_to_pat( + ConstantKind::Ty(ty::Const::new_value(self.tcx, valtree, ty)), + id, + span, + None, + ) + .kind + } else { + // If that fails, convert it to an opaque constant pattern. + match tcx.const_eval_resolve(self.param_env, uneval, None) { + Ok(val) => self.const_to_pat(mir::ConstantKind::Val(val, ty), id, span, None).kind, + Err(ErrorHandled::TooGeneric) => { + // If we land here it means the const can't be evaluated because it's `TooGeneric`. + self.tcx.sess.emit_err(ConstPatternDependsOnGenericParameter { span }); + PatKind::Wild + } + Err(ErrorHandled::Reported(_)) => PatKind::Wild, } } } @@ -626,8 +674,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let lit_input = LitToConstInput { lit: &lit.node, ty: self.typeck_results.expr_ty(expr), neg }; - match self.tcx.at(expr.span).lit_to_mir_constant(lit_input) { - Ok(constant) => self.const_to_pat(constant, expr.hir_id, lit.span, false).kind, + match self.tcx.at(expr.span).lit_to_const(lit_input) { + Ok(constant) => { + self.const_to_pat(ConstantKind::Ty(constant), expr.hir_id, lit.span, None).kind + } Err(LitToConstError::Reported(_)) => PatKind::Wild, Err(LitToConstError::TypeError) => bug!("lower_lit: had type error"), } @@ -702,7 +752,7 @@ macro_rules! ClonePatternFoldableImpls { ClonePatternFoldableImpls! { <'tcx> Span, FieldIdx, Mutability, Symbol, LocalVarId, usize, Region<'tcx>, Ty<'tcx>, BindingMode, AdtDef<'tcx>, - SubstsRef<'tcx>, &'tcx GenericArg<'tcx>, UserType<'tcx>, + GenericArgsRef<'tcx>, &'tcx GenericArg<'tcx>, UserType<'tcx>, UserTypeProjection, CanonicalUserTypeAnnotation<'tcx> } @@ -752,10 +802,10 @@ impl<'tcx> PatternFoldable<'tcx> for PatKind<'tcx> { is_primary, } } - PatKind::Variant { adt_def, substs, variant_index, ref subpatterns } => { + PatKind::Variant { adt_def, args, variant_index, ref subpatterns } => { PatKind::Variant { adt_def: adt_def.fold_with(folder), - substs: substs.fold_with(folder), + args: args.fold_with(folder), variant_index, subpatterns: subpatterns.fold_with(folder), } @@ -806,6 +856,9 @@ pub(crate) fn compare_const_vals<'tcx>( mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(a)), _a_ty), mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(b)), _b_ty), ) => return Some(a.cmp(&b)), + (mir::ConstantKind::Ty(a), mir::ConstantKind::Ty(b)) => { + return Some(a.kind().cmp(&b.kind())); + } _ => {} }, } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index e5b6350690609..08cfe98bb68b9 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -459,7 +459,7 @@ impl<'p, 'tcx> fmt::Debug for PatStack<'p, 'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "+")?; for pat in self.iter() { - write!(f, " {:?} +", pat)?; + write!(f, " {pat:?} +")?; } Ok(()) } @@ -530,7 +530,7 @@ impl<'p, 'tcx> fmt::Debug for Matrix<'p, 'tcx> { let Matrix { patterns: m, .. } = self; let pretty_printed_matrix: Vec> = - m.iter().map(|row| row.iter().map(|pat| format!("{:?}", pat)).collect()).collect(); + m.iter().map(|row| row.iter().map(|pat| format!("{pat:?}")).collect()).collect(); let column_count = m.iter().map(|row| row.len()).next().unwrap_or(0); assert!(m.iter().all(|row| row.len() == column_count)); diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index b2f2a64e29c8c..3b6276cfeb0e6 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -301,7 +301,7 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_expr(*source, depth_lvl + 2); print_indented!(self, "}", depth_lvl); } - Pointer { cast, source } => { + PointerCoercion { cast, source } => { print_indented!(self, "Pointer {", depth_lvl); print_indented!(self, format!("cast: {:?}", cast), depth_lvl + 1); print_indented!(self, "source:", depth_lvl + 1); @@ -321,7 +321,7 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, format!("pat: {:?}", pat), depth_lvl + 1); print_indented!(self, "}", depth_lvl); } - Match { scrutinee, arms } => { + Match { scrutinee, arms, .. } => { print_indented!(self, "Match {", depth_lvl); print_indented!(self, "scrutinee:", depth_lvl + 1); self.print_expr(*scrutinee, depth_lvl + 2); @@ -421,10 +421,16 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, "}", depth_lvl); } - ConstBlock { did, substs } => { + Become { value } => { + print_indented!(self, "Become {", depth_lvl); + print_indented!(self, "value:", depth_lvl + 1); + self.print_expr(*value, depth_lvl + 2); + print_indented!(self, "}", depth_lvl); + } + ConstBlock { did, args } => { print_indented!(self, "ConstBlock {", depth_lvl); print_indented!(self, format!("did: {:?}", did), depth_lvl + 1); - print_indented!(self, format!("substs: {:?}", substs), depth_lvl + 1); + print_indented!(self, format!("args: {:?}", args), depth_lvl + 1); print_indented!(self, "}", depth_lvl); } Repeat { value, count } => { @@ -493,11 +499,11 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { ZstLiteral { user_ty } => { print_indented!(self, format!("ZstLiteral(user_ty: {:?})", user_ty), depth_lvl); } - NamedConst { def_id, substs, user_ty } => { + NamedConst { def_id, args, user_ty } => { print_indented!(self, "NamedConst {", depth_lvl); print_indented!(self, format!("def_id: {:?}", def_id), depth_lvl + 1); print_indented!(self, format!("user_ty: {:?}", user_ty), depth_lvl + 1); - print_indented!(self, format!("substs: {:?}", substs), depth_lvl + 1); + print_indented!(self, format!("args: {:?}", args), depth_lvl + 1); print_indented!(self, "}", depth_lvl); } ConstParam { param, def_id } => { @@ -554,7 +560,7 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { format!("variant_index: {:?}", adt_expr.variant_index), depth_lvl + 1 ); - print_indented!(self, format!("substs: {:?}", adt_expr.substs), depth_lvl + 1); + print_indented!(self, format!("args: {:?}", adt_expr.args), depth_lvl + 1); print_indented!(self, format!("user_ty: {:?}", adt_expr.user_ty), depth_lvl + 1); for (i, field_expr) in adt_expr.fields.iter().enumerate() { @@ -656,11 +662,11 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, "}", depth_lvl + 1); } - PatKind::Variant { adt_def, substs, variant_index, subpatterns } => { + PatKind::Variant { adt_def, args, variant_index, subpatterns } => { print_indented!(self, "Variant {", depth_lvl + 1); print_indented!(self, "adt_def: ", depth_lvl + 2); self.print_adt_def(*adt_def, depth_lvl + 3); - print_indented!(self, format!("substs: {:?}", substs), depth_lvl + 2); + print_indented!(self, format!("args: {:?}", args), depth_lvl + 2); print_indented!(self, format!("variant_index: {:?}", variant_index), depth_lvl + 2); if subpatterns.len() > 0 { @@ -778,11 +784,11 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { } fn print_closure_expr(&mut self, expr: &ClosureExpr<'tcx>, depth_lvl: usize) { - let ClosureExpr { closure_id, substs, upvars, movability, fake_reads } = expr; + let ClosureExpr { closure_id, args, upvars, movability, fake_reads } = expr; print_indented!(self, "ClosureExpr {", depth_lvl); print_indented!(self, format!("closure_id: {:?}", closure_id), depth_lvl + 1); - print_indented!(self, format!("substs: {:?}", substs), depth_lvl + 1); + print_indented!(self, format!("args: {:?}", args), depth_lvl + 1); if upvars.len() > 0 { print_indented!(self, "upvars: [", depth_lvl + 1); diff --git a/compiler/rustc_mir_build/src/thir/util.rs b/compiler/rustc_mir_build/src/thir/util.rs index c58ed1ac0b891..9106b4d33fcd1 100644 --- a/compiler/rustc_mir_build/src/thir/util.rs +++ b/compiler/rustc_mir_build/src/thir/util.rs @@ -9,7 +9,7 @@ pub(crate) trait UserAnnotatedTyHelpers<'tcx> { /// Looks up the type associated with this hir-id and applies the /// user-given substitutions; the hir-id must map to a suitable /// type. - fn user_substs_applied_to_ty_of_hir_id( + fn user_args_applied_to_ty_of_hir_id( &self, hir_id: hir::HirId, ) -> Option> { diff --git a/compiler/rustc_mir_dataflow/messages.ftl b/compiler/rustc_mir_dataflow/messages.ftl index 9885415250883..5698367e42ba5 100644 --- a/compiler/rustc_mir_dataflow/messages.ftl +++ b/compiler/rustc_mir_dataflow/messages.ftl @@ -1,29 +1,29 @@ +mir_dataflow_duplicate_values_for = + duplicate values for `{$name}` + mir_dataflow_path_must_end_in_filename = path must end in a filename -mir_dataflow_unknown_formatter = - unknown formatter +mir_dataflow_peek_argument_not_a_local = + rustc_peek: argument was not a local -mir_dataflow_duplicate_values_for = - duplicate values for `{$name}` +mir_dataflow_peek_argument_untracked = + rustc_peek: argument untracked -mir_dataflow_requires_an_argument = - `{$name}` requires an argument +mir_dataflow_peek_bit_not_set = + rustc_peek: bit not set -mir_dataflow_stop_after_dataflow_ended_compilation = - stop_after_dataflow ended compilation +mir_dataflow_peek_must_be_not_temporary = + dataflow::sanity_check cannot feed a non-temp to rustc_peek mir_dataflow_peek_must_be_place_or_ref_place = rustc_peek: argument expression must be either `place` or `&place` -mir_dataflow_peek_must_be_not_temporary = - dataflow::sanity_check cannot feed a non-temp to rustc_peek - -mir_dataflow_peek_bit_not_set = - rustc_peek: bit not set +mir_dataflow_requires_an_argument = + `{$name}` requires an argument -mir_dataflow_peek_argument_not_a_local = - rustc_peek: argument was not a local +mir_dataflow_stop_after_dataflow_ended_compilation = + stop_after_dataflow ended compilation -mir_dataflow_peek_argument_untracked = - rustc_peek: argument untracked +mir_dataflow_unknown_formatter = + unknown formatter diff --git a/compiler/rustc_mir_dataflow/src/elaborate_drops.rs b/compiler/rustc_mir_dataflow/src/elaborate_drops.rs index 18895072c3b96..9e02b027182a5 100644 --- a/compiler/rustc_mir_dataflow/src/elaborate_drops.rs +++ b/compiler/rustc_mir_dataflow/src/elaborate_drops.rs @@ -4,8 +4,8 @@ use rustc_index::Idx; use rustc_middle::mir::patch::MirPatch; use rustc_middle::mir::*; use rustc_middle::traits::Reveal; -use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::util::IntTypeExt; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT}; use std::{fmt, iter}; @@ -237,6 +237,7 @@ where place: self.place, target: self.succ, unwind: self.unwind.into_action(), + replace: false, }, ); } @@ -262,7 +263,7 @@ where base_place: Place<'tcx>, variant_path: D::Path, variant: &'tcx ty::VariantDef, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> Vec<(Place<'tcx>, Option)> { variant .fields @@ -275,7 +276,7 @@ where assert_eq!(self.elaborator.param_env().reveal(), Reveal::All); let field_ty = - tcx.normalize_erasing_regions(self.elaborator.param_env(), f.ty(tcx, substs)); + tcx.normalize_erasing_regions(self.elaborator.param_env(), f.ty(tcx, args)); (tcx.mk_place_field(base_place, field, field_ty), subpath) }) @@ -408,14 +409,21 @@ where self.drop_ladder(fields, succ, unwind).0 } + /// Drops the T contained in a `Box` if it has not been moved out of #[instrument(level = "debug", ret)] - fn open_drop_for_box(&mut self, adt: ty::AdtDef<'tcx>, substs: SubstsRef<'tcx>) -> BasicBlock { + fn open_drop_for_box_contents( + &mut self, + adt: ty::AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + succ: BasicBlock, + unwind: Unwind, + ) -> BasicBlock { // drop glue is sent straight to codegen // box cannot be directly dereferenced - let unique_ty = adt.non_enum_variant().fields[FieldIdx::new(0)].ty(self.tcx(), substs); + let unique_ty = adt.non_enum_variant().fields[FieldIdx::new(0)].ty(self.tcx(), args); let unique_variant = unique_ty.ty_adt_def().unwrap().non_enum_variant(); - let nonnull_ty = unique_variant.fields[FieldIdx::from_u32(0)].ty(self.tcx(), substs); - let ptr_ty = self.tcx().mk_imm_ptr(substs[0].expect_ty()); + let nonnull_ty = unique_variant.fields[FieldIdx::from_u32(0)].ty(self.tcx(), args); + let ptr_ty = Ty::new_imm_ptr(self.tcx(), args[0].expect_ty()); let unique_place = self.tcx().mk_place_field(self.place, FieldIdx::new(0), unique_ty); let nonnull_place = self.tcx().mk_place_field(unique_place, FieldIdx::new(0), nonnull_ty); @@ -424,15 +432,15 @@ where let interior_path = self.elaborator.deref_subpath(self.path); - let succ = self.box_free_block(adt, substs, self.succ, self.unwind); - let unwind_succ = - self.unwind.map(|unwind| self.box_free_block(adt, substs, unwind, Unwind::InCleanup)); - - self.drop_subpath(interior, interior_path, succ, unwind_succ) + self.drop_subpath(interior, interior_path, succ, unwind) } #[instrument(level = "debug", ret)] - fn open_drop_for_adt(&mut self, adt: ty::AdtDef<'tcx>, substs: SubstsRef<'tcx>) -> BasicBlock { + fn open_drop_for_adt( + &mut self, + adt: ty::AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + ) -> BasicBlock { if adt.variants().is_empty() { return self.elaborator.patch().new_block(BasicBlockData { statements: vec![], @@ -449,10 +457,18 @@ where let contents_drop = if skip_contents { (self.succ, self.unwind) } else { - self.open_drop_for_adt_contents(adt, substs) + self.open_drop_for_adt_contents(adt, args) }; - if adt.has_dtor(self.tcx()) { + if adt.is_box() { + // we need to drop the inside of the box before running the destructor + let succ = self.destructor_call_block(contents_drop); + let unwind = contents_drop + .1 + .map(|unwind| self.destructor_call_block((unwind, Unwind::InCleanup))); + + self.open_drop_for_box_contents(adt, args, succ, unwind) + } else if adt.has_dtor(self.tcx()) { self.destructor_call_block(contents_drop) } else { contents_drop.0 @@ -462,7 +478,7 @@ where fn open_drop_for_adt_contents( &mut self, adt: ty::AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, ) -> (BasicBlock, Unwind) { let (succ, unwind) = self.drop_ladder_bottom(); if !adt.is_enum() { @@ -470,18 +486,18 @@ where self.place, self.path, &adt.variant(FIRST_VARIANT), - substs, + args, ); self.drop_ladder(fields, succ, unwind) } else { - self.open_drop_for_multivariant(adt, substs, succ, unwind) + self.open_drop_for_multivariant(adt, args, succ, unwind) } } fn open_drop_for_multivariant( &mut self, adt: ty::AdtDef<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, succ: BasicBlock, unwind: Unwind, ) -> (BasicBlock, Unwind) { @@ -503,7 +519,7 @@ where self.place, ProjectionElem::Downcast(Some(variant.name), variant_index), ); - let fields = self.move_paths_for_fields(base_place, variant_path, &variant, substs); + let fields = self.move_paths_for_fields(base_place, variant_path, &variant, args); values.push(discr.val); if let Unwind::To(unwind) = unwind { // We can't use the half-ladder from the original @@ -538,7 +554,7 @@ where let have_field_with_drop_glue = variant .fields .iter() - .any(|field| field.ty(tcx, substs).needs_drop(tcx, param_env)); + .any(|field| field.ty(tcx, args).needs_drop(tcx, param_env)); if have_field_with_drop_glue { have_otherwise_with_drop_glue = true; } @@ -616,17 +632,20 @@ where let drop_fn = tcx.associated_item_def_ids(drop_trait)[0]; let ty = self.place_ty(self.place); - let ref_ty = - tcx.mk_ref(tcx.lifetimes.re_erased, ty::TypeAndMut { ty, mutbl: hir::Mutability::Mut }); + let ref_ty = Ty::new_ref( + tcx, + tcx.lifetimes.re_erased, + ty::TypeAndMut { ty, mutbl: hir::Mutability::Mut }, + ); let ref_place = self.new_temp(ref_ty); - let unit_temp = Place::from(self.new_temp(tcx.mk_unit())); + let unit_temp = Place::from(self.new_temp(Ty::new_unit(tcx))); let result = BasicBlockData { statements: vec![self.assign( Place::from(ref_place), Rvalue::Ref( tcx.lifetimes.re_erased, - BorrowKind::Mut { allow_two_phase_borrow: false }, + BorrowKind::Mut { kind: MutBorrowKind::Default }, self.place, ), )], @@ -642,14 +661,20 @@ where destination: unit_temp, target: Some(succ), unwind: unwind.into_action(), - from_hir_call: true, + call_source: CallSource::Misc, fn_span: self.source_info.span, }, source_info: self.source_info, }), is_cleanup: unwind.is_cleanup(), }; - self.elaborator.patch().new_block(result) + + let destructor_block = self.elaborator.patch().new_block(result); + + let block_start = Location { block: destructor_block, statement_index: 0 }; + self.elaborator.clear_drop_flag(block_start, self.path, DropFlagMode::Shallow); + + self.drop_flag_test_block(destructor_block, succ, unwind) } /// Create a loop that drops an array: @@ -675,7 +700,7 @@ where let move_ = |place: Place<'tcx>| Operand::Move(place); let tcx = self.tcx(); - let ptr_ty = tcx.mk_ptr(ty::TypeAndMut { ty: ety, mutbl: hir::Mutability::Mut }); + let ptr_ty = Ty::new_ptr(tcx, ty::TypeAndMut { ty: ety, mutbl: hir::Mutability::Mut }); let ptr = Place::from(self.new_temp(ptr_ty)); let can_go = Place::from(self.new_temp(tcx.types.bool)); let one = self.constant_usize(1); @@ -719,6 +744,7 @@ where place: tcx.mk_place_deref(ptr), target: loop_block, unwind: unwind.into_action(), + replace: false, }, ); @@ -834,28 +860,16 @@ where fn open_drop(&mut self) -> BasicBlock { let ty = self.place_ty(self.place); match ty.kind() { - ty::Closure(_, substs) => { - let tys: Vec<_> = substs.as_closure().upvar_tys().collect(); - self.open_drop_for_tuple(&tys) - } + ty::Closure(_, args) => self.open_drop_for_tuple(&args.as_closure().upvar_tys()), // Note that `elaborate_drops` only drops the upvars of a generator, // and this is ok because `open_drop` here can only be reached // within that own generator's resume function. // This should only happen for the self argument on the resume function. // It effectively only contains upvars until the generator transformation runs. // See librustc_body/transform/generator.rs for more details. - ty::Generator(_, substs, _) => { - let tys: Vec<_> = substs.as_generator().upvar_tys().collect(); - self.open_drop_for_tuple(&tys) - } + ty::Generator(_, args, _) => self.open_drop_for_tuple(&args.as_generator().upvar_tys()), ty::Tuple(fields) => self.open_drop_for_tuple(fields), - ty::Adt(def, substs) => { - if def.is_box() { - self.open_drop_for_box(*def, substs) - } else { - self.open_drop_for_adt(*def, substs) - } - } + ty::Adt(def, args) => self.open_drop_for_adt(*def, args), ty::Dynamic(..) => self.complete_drop(self.succ, self.unwind), ty::Array(ety, size) => { let size = size.try_eval_target_usize(self.tcx(), self.elaborator.param_env()); @@ -903,68 +917,13 @@ where blk } - /// Creates a block that frees the backing memory of a `Box` if its drop is required (either - /// statically or by checking its drop flag). - /// - /// The contained value will not be dropped. - fn box_free_block( - &mut self, - adt: ty::AdtDef<'tcx>, - substs: SubstsRef<'tcx>, - target: BasicBlock, - unwind: Unwind, - ) -> BasicBlock { - let block = self.unelaborated_free_block(adt, substs, target, unwind); - self.drop_flag_test_block(block, target, unwind) - } - - /// Creates a block that frees the backing memory of a `Box` (without dropping the contained - /// value). - fn unelaborated_free_block( - &mut self, - adt: ty::AdtDef<'tcx>, - substs: SubstsRef<'tcx>, - target: BasicBlock, - unwind: Unwind, - ) -> BasicBlock { - let tcx = self.tcx(); - let unit_temp = Place::from(self.new_temp(tcx.mk_unit())); - let free_func = tcx.require_lang_item(LangItem::BoxFree, Some(self.source_info.span)); - let args = adt - .variant(FIRST_VARIANT) - .fields - .iter() - .enumerate() - .map(|(i, f)| { - let field = FieldIdx::new(i); - let field_ty = f.ty(tcx, substs); - Operand::Move(tcx.mk_place_field(self.place, field, field_ty)) - }) - .collect(); - - let call = TerminatorKind::Call { - func: Operand::function_handle(tcx, free_func, substs, self.source_info.span), - args, - destination: unit_temp, - target: Some(target), - unwind: if unwind.is_cleanup() { - UnwindAction::Terminate - } else { - UnwindAction::Continue - }, - from_hir_call: false, - fn_span: self.source_info.span, - }; // FIXME(#43234) - let free_block = self.new_block(unwind, call); - - let block_start = Location { block: free_block, statement_index: 0 }; - self.elaborator.clear_drop_flag(block_start, self.path, DropFlagMode::Shallow); - free_block - } - fn drop_block(&mut self, target: BasicBlock, unwind: Unwind) -> BasicBlock { - let block = - TerminatorKind::Drop { place: self.place, target, unwind: unwind.into_action() }; + let block = TerminatorKind::Drop { + place: self.place, + target, + unwind: unwind.into_action(), + replace: false, + }; self.new_block(unwind, block) } diff --git a/compiler/rustc_mir_dataflow/src/framework/cursor.rs b/compiler/rustc_mir_dataflow/src/framework/cursor.rs index f3b5544aa8b9d..c978bddfef540 100644 --- a/compiler/rustc_mir_dataflow/src/framework/cursor.rs +++ b/compiler/rustc_mir_dataflow/src/framework/cursor.rs @@ -1,18 +1,60 @@ //! Random access inspection of the results of a dataflow analysis. -use crate::framework::BitSetExt; +use crate::{framework::BitSetExt, CloneAnalysis}; -use std::borrow::Borrow; +use std::borrow::{Borrow, BorrowMut}; use std::cmp::Ordering; #[cfg(debug_assertions)] use rustc_index::bit_set::BitSet; use rustc_middle::mir::{self, BasicBlock, Location}; -use super::{Analysis, Direction, Effect, EffectIndex, Results}; +use super::{Analysis, Direction, Effect, EffectIndex, EntrySets, Results, ResultsCloned}; + +// `AnalysisResults` is needed as an impl such as the following has an unconstrained type +// parameter: +// ``` +// impl<'tcx, A, E, R> ResultsCursor<'_, 'tcx, A, R> +// where +// A: Analysis<'tcx>, +// E: Borrow>, +// R: Results<'tcx, A, E>, +// {} +// ``` + +/// A type representing the analysis results consumed by a `ResultsCursor`. +pub trait AnalysisResults<'tcx, A>: BorrowMut> +where + A: Analysis<'tcx>, +{ + /// The type containing the entry sets for this `Results` type. + /// + /// Should be either `EntrySets<'tcx, A>` or `&EntrySets<'tcx, A>`. + type EntrySets: Borrow>; +} +impl<'tcx, A, E> AnalysisResults<'tcx, A> for Results<'tcx, A, E> +where + A: Analysis<'tcx>, + E: Borrow>, +{ + type EntrySets = E; +} +impl<'a, 'tcx, A, E> AnalysisResults<'tcx, A> for &'a mut Results<'tcx, A, E> +where + A: Analysis<'tcx>, + E: Borrow>, +{ + type EntrySets = E; +} /// A `ResultsCursor` that borrows the underlying `Results`. -pub type ResultsRefCursor<'a, 'mir, 'tcx, A> = ResultsCursor<'mir, 'tcx, A, &'a Results<'tcx, A>>; +pub type ResultsRefCursor<'res, 'mir, 'tcx, A> = + ResultsCursor<'mir, 'tcx, A, &'res mut Results<'tcx, A>>; + +/// A `ResultsCursor` which uses a cloned `Analysis` while borrowing the underlying `Results`. This +/// allows multiple cursors over the same `Results`. +pub type ResultsClonedCursor<'res, 'mir, 'tcx, A> = + ResultsCursor<'mir, 'tcx, A, ResultsCloned<'res, 'tcx, A>>; /// Allows random access inspection of the results of a dataflow analysis. /// @@ -45,7 +87,38 @@ where impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R> where A: Analysis<'tcx>, - R: Borrow>, +{ + /// Returns the dataflow state at the current location. + pub fn get(&self) -> &A::Domain { + &self.state + } + + /// Returns the body this analysis was run on. + pub fn body(&self) -> &'mir mir::Body<'tcx> { + self.body + } + + /// Unwraps this cursor, returning the underlying `Results`. + pub fn into_results(self) -> R { + self.results + } +} + +impl<'res, 'mir, 'tcx, A> ResultsCursor<'mir, 'tcx, A, ResultsCloned<'res, 'tcx, A>> +where + A: Analysis<'tcx> + CloneAnalysis, +{ + /// Creates a new cursor over the same `Results`. Note that the cursor's position is *not* + /// copied. + pub fn new_cursor(&self) -> Self { + Self::new(self.body, self.results.reclone_analysis()) + } +} + +impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R> +where + A: Analysis<'tcx>, + R: AnalysisResults<'tcx, A>, { /// Returns a new cursor that can inspect `results`. pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self { @@ -74,8 +147,13 @@ where } /// Returns the underlying `Results`. - pub fn results(&self) -> &Results<'tcx, A> { - &self.results.borrow() + pub fn results(&mut self) -> &Results<'tcx, A, R::EntrySets> { + self.results.borrow() + } + + /// Returns the underlying `Results`. + pub fn mut_results(&mut self) -> &mut Results<'tcx, A, R::EntrySets> { + self.results.borrow_mut() } /// Returns the `Analysis` used to generate the underlying `Results`. @@ -83,9 +161,14 @@ where &self.results.borrow().analysis } - /// Returns the dataflow state at the current location. - pub fn get(&self) -> &A::Domain { - &self.state + /// Returns the `Analysis` used to generate the underlying `Results`. + pub fn mut_analysis(&mut self) -> &mut A { + &mut self.results.borrow_mut().analysis + } + + /// Returns both the dataflow state at the current location and the `Analysis`. + pub fn get_with_analysis(&mut self) -> (&A::Domain, &mut A) { + (&self.state, &mut self.results.borrow_mut().analysis) } /// Resets the cursor to hold the entry set for the given basic block. @@ -97,7 +180,7 @@ where #[cfg(debug_assertions)] assert!(self.reachable_blocks.contains(block)); - self.state.clone_from(&self.results.borrow().entry_set_for_block(block)); + self.state.clone_from(self.results.borrow().entry_set_for_block(block)); self.pos = CursorPosition::block_entry(block); self.state_needs_reset = false; } @@ -186,7 +269,7 @@ where ) }; - let analysis = &self.results.borrow().analysis; + let analysis = &mut self.results.borrow_mut().analysis; let target_effect_index = effect.at_index(target.statement_index); A::Direction::apply_effects_in_range( @@ -205,8 +288,8 @@ where /// /// This can be used, e.g., to apply the call return effect directly to the cursor without /// creating an extra copy of the dataflow state. - pub fn apply_custom_effect(&mut self, f: impl FnOnce(&A, &mut A::Domain)) { - f(&self.results.borrow().analysis, &mut self.state); + pub fn apply_custom_effect(&mut self, f: impl FnOnce(&mut A, &mut A::Domain)) { + f(&mut self.results.borrow_mut().analysis, &mut self.state); self.state_needs_reset = true; } } @@ -215,7 +298,6 @@ impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R> where A: crate::GenKillAnalysis<'tcx>, A::Domain: BitSetExt, - R: Borrow>, { pub fn contains(&self, elem: A::Idx) -> bool { self.get().contains(elem) diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs index c8fe1af6674c8..8a9e37c5a4fda 100644 --- a/compiler/rustc_mir_dataflow/src/framework/direction.rs +++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs @@ -1,11 +1,10 @@ -use rustc_middle::mir::{self, BasicBlock, Location, SwitchTargets, UnwindAction}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::mir::{ + self, BasicBlock, CallReturnPlaces, Location, SwitchTargets, TerminatorEdges, UnwindAction, +}; use std::ops::RangeInclusive; use super::visitor::{ResultsVisitable, ResultsVisitor}; -use super::{ - Analysis, CallReturnPlaces, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget, -}; +use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget}; pub trait Direction { const IS_FORWARD: bool; @@ -16,7 +15,7 @@ pub trait Direction { /// /// `effects.start()` must precede or equal `effects.end()` in this direction. fn apply_effects_in_range<'tcx, A>( - analysis: &A, + analysis: &mut A, state: &mut A::Domain, block: BasicBlock, block_data: &mir::BasicBlockData<'tcx>, @@ -24,16 +23,18 @@ pub trait Direction { ) where A: Analysis<'tcx>; - fn apply_effects_in_block<'tcx, A>( - analysis: &A, + fn apply_effects_in_block<'mir, 'tcx, A>( + analysis: &mut A, state: &mut A::Domain, block: BasicBlock, - block_data: &mir::BasicBlockData<'tcx>, - ) where + block_data: &'mir mir::BasicBlockData<'tcx>, + statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>, + ) -> TerminatorEdges<'mir, 'tcx> + where A: Analysis<'tcx>; - fn gen_kill_effects_in_block<'tcx, A>( - analysis: &A, + fn gen_kill_statement_effects_in_block<'tcx, A>( + analysis: &mut A, trans: &mut GenKillSet, block: BasicBlock, block_data: &mir::BasicBlockData<'tcx>, @@ -44,17 +45,17 @@ pub trait Direction { state: &mut F, block: BasicBlock, block_data: &'mir mir::BasicBlockData<'tcx>, - results: &R, - vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>, + results: &mut R, + vis: &mut impl ResultsVisitor<'mir, 'tcx, R, FlowState = F>, ) where R: ResultsVisitable<'tcx, FlowState = F>; fn join_state_into_successors_of<'tcx, A>( - analysis: &A, - tcx: TyCtxt<'tcx>, + analysis: &mut A, body: &mir::Body<'tcx>, exit_state: &mut A::Domain, - block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>), + block: BasicBlock, + edges: TerminatorEdges<'_, 'tcx>, propagate: impl FnMut(BasicBlock, &A::Domain), ) where A: Analysis<'tcx>; @@ -66,39 +67,40 @@ pub struct Backward; impl Direction for Backward { const IS_FORWARD: bool = false; - fn apply_effects_in_block<'tcx, A>( - analysis: &A, + fn apply_effects_in_block<'mir, 'tcx, A>( + analysis: &mut A, state: &mut A::Domain, block: BasicBlock, - block_data: &mir::BasicBlockData<'tcx>, - ) where + block_data: &'mir mir::BasicBlockData<'tcx>, + statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>, + ) -> TerminatorEdges<'mir, 'tcx> + where A: Analysis<'tcx>, { let terminator = block_data.terminator(); let location = Location { block, statement_index: block_data.statements.len() }; analysis.apply_before_terminator_effect(state, terminator, location); - analysis.apply_terminator_effect(state, terminator, location); - - for (statement_index, statement) in block_data.statements.iter().enumerate().rev() { - let location = Location { block, statement_index }; - analysis.apply_before_statement_effect(state, statement, location); - analysis.apply_statement_effect(state, statement, location); + let edges = analysis.apply_terminator_effect(state, terminator, location); + if let Some(statement_effect) = statement_effect { + statement_effect(block, state) + } else { + for (statement_index, statement) in block_data.statements.iter().enumerate().rev() { + let location = Location { block, statement_index }; + analysis.apply_before_statement_effect(state, statement, location); + analysis.apply_statement_effect(state, statement, location); + } } + edges } - fn gen_kill_effects_in_block<'tcx, A>( - analysis: &A, + fn gen_kill_statement_effects_in_block<'tcx, A>( + analysis: &mut A, trans: &mut GenKillSet, block: BasicBlock, block_data: &mir::BasicBlockData<'tcx>, ) where A: GenKillAnalysis<'tcx>, { - let terminator = block_data.terminator(); - let location = Location { block, statement_index: block_data.statements.len() }; - analysis.before_terminator_effect(trans, terminator, location); - analysis.terminator_effect(trans, terminator, location); - for (statement_index, statement) in block_data.statements.iter().enumerate().rev() { let location = Location { block, statement_index }; analysis.before_statement_effect(trans, statement, location); @@ -107,7 +109,7 @@ impl Direction for Backward { } fn apply_effects_in_range<'tcx, A>( - analysis: &A, + analysis: &mut A, state: &mut A::Domain, block: BasicBlock, block_data: &mir::BasicBlockData<'tcx>, @@ -187,40 +189,40 @@ impl Direction for Backward { state: &mut F, block: BasicBlock, block_data: &'mir mir::BasicBlockData<'tcx>, - results: &R, - vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>, + results: &mut R, + vis: &mut impl ResultsVisitor<'mir, 'tcx, R, FlowState = F>, ) where R: ResultsVisitable<'tcx, FlowState = F>, { results.reset_to_block_entry(state, block); - vis.visit_block_end(&state, block_data, block); + vis.visit_block_end(results, &state, block_data, block); // Terminator let loc = Location { block, statement_index: block_data.statements.len() }; let term = block_data.terminator(); results.reconstruct_before_terminator_effect(state, term, loc); - vis.visit_terminator_before_primary_effect(state, term, loc); + vis.visit_terminator_before_primary_effect(results, state, term, loc); results.reconstruct_terminator_effect(state, term, loc); - vis.visit_terminator_after_primary_effect(state, term, loc); + vis.visit_terminator_after_primary_effect(results, state, term, loc); for (statement_index, stmt) in block_data.statements.iter().enumerate().rev() { let loc = Location { block, statement_index }; results.reconstruct_before_statement_effect(state, stmt, loc); - vis.visit_statement_before_primary_effect(state, stmt, loc); + vis.visit_statement_before_primary_effect(results, state, stmt, loc); results.reconstruct_statement_effect(state, stmt, loc); - vis.visit_statement_after_primary_effect(state, stmt, loc); + vis.visit_statement_after_primary_effect(results, state, stmt, loc); } - vis.visit_block_start(state, block_data, block); + vis.visit_block_start(results, state, block_data, block); } fn join_state_into_successors_of<'tcx, A>( - analysis: &A, - _tcx: TyCtxt<'tcx>, + analysis: &mut A, body: &mir::Body<'tcx>, exit_state: &mut A::Domain, - (bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>), + bb: BasicBlock, + _edges: TerminatorEdges<'_, 'tcx>, mut propagate: impl FnMut(BasicBlock, &A::Domain), ) where A: Analysis<'tcx>, @@ -254,7 +256,11 @@ impl Direction for Backward { mir::TerminatorKind::Yield { resume, resume_arg, .. } if resume == bb => { let mut tmp = exit_state.clone(); - analysis.apply_yield_resume_effect(&mut tmp, resume, resume_arg); + analysis.apply_call_return_effect( + &mut tmp, + resume, + CallReturnPlaces::Yield(resume_arg), + ); propagate(pred, &tmp); } @@ -318,28 +324,34 @@ pub struct Forward; impl Direction for Forward { const IS_FORWARD: bool = true; - fn apply_effects_in_block<'tcx, A>( - analysis: &A, + fn apply_effects_in_block<'mir, 'tcx, A>( + analysis: &mut A, state: &mut A::Domain, block: BasicBlock, - block_data: &mir::BasicBlockData<'tcx>, - ) where + block_data: &'mir mir::BasicBlockData<'tcx>, + statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>, + ) -> TerminatorEdges<'mir, 'tcx> + where A: Analysis<'tcx>, { - for (statement_index, statement) in block_data.statements.iter().enumerate() { - let location = Location { block, statement_index }; - analysis.apply_before_statement_effect(state, statement, location); - analysis.apply_statement_effect(state, statement, location); + if let Some(statement_effect) = statement_effect { + statement_effect(block, state) + } else { + for (statement_index, statement) in block_data.statements.iter().enumerate() { + let location = Location { block, statement_index }; + analysis.apply_before_statement_effect(state, statement, location); + analysis.apply_statement_effect(state, statement, location); + } } let terminator = block_data.terminator(); let location = Location { block, statement_index: block_data.statements.len() }; analysis.apply_before_terminator_effect(state, terminator, location); - analysis.apply_terminator_effect(state, terminator, location); + analysis.apply_terminator_effect(state, terminator, location) } - fn gen_kill_effects_in_block<'tcx, A>( - analysis: &A, + fn gen_kill_statement_effects_in_block<'tcx, A>( + analysis: &mut A, trans: &mut GenKillSet, block: BasicBlock, block_data: &mir::BasicBlockData<'tcx>, @@ -351,15 +363,10 @@ impl Direction for Forward { analysis.before_statement_effect(trans, statement, location); analysis.statement_effect(trans, statement, location); } - - let terminator = block_data.terminator(); - let location = Location { block, statement_index: block_data.statements.len() }; - analysis.before_terminator_effect(trans, terminator, location); - analysis.terminator_effect(trans, terminator, location); } fn apply_effects_in_range<'tcx, A>( - analysis: &A, + analysis: &mut A, state: &mut A::Domain, block: BasicBlock, block_data: &mir::BasicBlockData<'tcx>, @@ -435,123 +442,61 @@ impl Direction for Forward { state: &mut F, block: BasicBlock, block_data: &'mir mir::BasicBlockData<'tcx>, - results: &R, - vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>, + results: &mut R, + vis: &mut impl ResultsVisitor<'mir, 'tcx, R, FlowState = F>, ) where R: ResultsVisitable<'tcx, FlowState = F>, { results.reset_to_block_entry(state, block); - vis.visit_block_start(state, block_data, block); + vis.visit_block_start(results, state, block_data, block); for (statement_index, stmt) in block_data.statements.iter().enumerate() { let loc = Location { block, statement_index }; results.reconstruct_before_statement_effect(state, stmt, loc); - vis.visit_statement_before_primary_effect(state, stmt, loc); + vis.visit_statement_before_primary_effect(results, state, stmt, loc); results.reconstruct_statement_effect(state, stmt, loc); - vis.visit_statement_after_primary_effect(state, stmt, loc); + vis.visit_statement_after_primary_effect(results, state, stmt, loc); } let loc = Location { block, statement_index: block_data.statements.len() }; let term = block_data.terminator(); results.reconstruct_before_terminator_effect(state, term, loc); - vis.visit_terminator_before_primary_effect(state, term, loc); + vis.visit_terminator_before_primary_effect(results, state, term, loc); results.reconstruct_terminator_effect(state, term, loc); - vis.visit_terminator_after_primary_effect(state, term, loc); + vis.visit_terminator_after_primary_effect(results, state, term, loc); - vis.visit_block_end(state, block_data, block); + vis.visit_block_end(results, state, block_data, block); } fn join_state_into_successors_of<'tcx, A>( - analysis: &A, - _tcx: TyCtxt<'tcx>, + analysis: &mut A, _body: &mir::Body<'tcx>, exit_state: &mut A::Domain, - (bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>), + bb: BasicBlock, + edges: TerminatorEdges<'_, 'tcx>, mut propagate: impl FnMut(BasicBlock, &A::Domain), ) where A: Analysis<'tcx>, { - use mir::TerminatorKind::*; - match bb_data.terminator().kind { - Return | Resume | Terminate | GeneratorDrop | Unreachable => {} - - Goto { target } => propagate(target, exit_state), - - Assert { target, unwind, expected: _, msg: _, cond: _ } - | Drop { target, unwind, place: _ } - | FalseUnwind { real_target: target, unwind } => { - if let UnwindAction::Cleanup(unwind) = unwind { - propagate(unwind, exit_state); - } - + match edges { + TerminatorEdges::None => {} + TerminatorEdges::Single(target) => propagate(target, exit_state), + TerminatorEdges::Double(target, unwind) => { propagate(target, exit_state); + propagate(unwind, exit_state); } - - FalseEdge { real_target, imaginary_target } => { - propagate(real_target, exit_state); - propagate(imaginary_target, exit_state); - } - - Yield { resume: target, drop, resume_arg, value: _ } => { - if let Some(drop) = drop { - propagate(drop, exit_state); - } - - analysis.apply_yield_resume_effect(exit_state, target, resume_arg); - propagate(target, exit_state); - } - - Call { - unwind, - destination, - target, - func: _, - args: _, - from_hir_call: _, - fn_span: _, - } => { - if let UnwindAction::Cleanup(unwind) = unwind { - propagate(unwind, exit_state); - } - - if let Some(target) = target { - // N.B.: This must be done *last*, otherwise the unwind path will see the call - // return effect. - analysis.apply_call_return_effect( - exit_state, - bb, - CallReturnPlaces::Call(destination), - ); - propagate(target, exit_state); - } - } - - InlineAsm { - template: _, - ref operands, - options: _, - line_spans: _, - destination, - unwind, - } => { + TerminatorEdges::AssignOnReturn { return_, unwind, place } => { + // This must be done *first*, otherwise the unwind path will see the assignments. if let UnwindAction::Cleanup(unwind) = unwind { propagate(unwind, exit_state); } - - if let Some(target) = destination { - // N.B.: This must be done *last*, otherwise the unwind path will see the call - // return effect. - analysis.apply_call_return_effect( - exit_state, - bb, - CallReturnPlaces::InlineAsm(operands), - ); - propagate(target, exit_state); + if let Some(return_) = return_ { + analysis.apply_call_return_effect(exit_state, bb, place); + propagate(return_, exit_state); } } - - SwitchInt { ref targets, ref discr } => { + TerminatorEdges::SwitchInt { targets, discr } => { let mut applier = ForwardSwitchIntEdgeEffectsApplier { exit_state, targets, diff --git a/compiler/rustc_mir_dataflow/src/framework/engine.rs b/compiler/rustc_mir_dataflow/src/framework/engine.rs index 3e8f792e63442..a29962d7717a9 100644 --- a/compiler/rustc_mir_dataflow/src/framework/engine.rs +++ b/compiler/rustc_mir_dataflow/src/framework/engine.rs @@ -5,7 +5,9 @@ use crate::errors::{ }; use crate::framework::BitSetExt; +use std::borrow::Borrow; use std::ffi::OsString; +use std::marker::PhantomData; use std::path::PathBuf; use rustc_ast as ast; @@ -22,54 +24,108 @@ use rustc_span::symbol::{sym, Symbol}; use super::fmt::DebugWithContext; use super::graphviz; use super::{ - visit_results, Analysis, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice, - ResultsCursor, ResultsVisitor, + visit_results, Analysis, AnalysisDomain, CloneAnalysis, Direction, GenKill, GenKillAnalysis, + GenKillSet, JoinSemiLattice, ResultsClonedCursor, ResultsCursor, ResultsRefCursor, + ResultsVisitor, }; +pub type EntrySets<'tcx, A> = IndexVec>::Domain>; + /// A dataflow analysis that has converged to fixpoint. -pub struct Results<'tcx, A> +pub struct Results<'tcx, A, E = EntrySets<'tcx, A>> where A: Analysis<'tcx>, { pub analysis: A, - pub(super) entry_sets: IndexVec, + pub(super) entry_sets: E, + pub(super) _marker: PhantomData<&'tcx ()>, } -impl<'tcx, A> Results<'tcx, A> +/// `Results` type with a cloned `Analysis` and borrowed entry sets. +pub type ResultsCloned<'res, 'tcx, A> = Results<'tcx, A, &'res EntrySets<'tcx, A>>; + +impl<'tcx, A, E> Results<'tcx, A, E> where A: Analysis<'tcx>, + E: Borrow>, { /// Creates a `ResultsCursor` that can inspect these `Results`. pub fn into_results_cursor<'mir>( self, body: &'mir mir::Body<'tcx>, - ) -> ResultsCursor<'mir, 'tcx, A> { + ) -> ResultsCursor<'mir, 'tcx, A, Self> { ResultsCursor::new(body, self) } /// Gets the dataflow state for the given block. pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain { - &self.entry_sets[block] + &self.entry_sets.borrow()[block] } pub fn visit_with<'mir>( - &self, + &mut self, body: &'mir mir::Body<'tcx>, blocks: impl IntoIterator, - vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>, + vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, FlowState = A::Domain>, ) { visit_results(body, blocks, self, vis) } pub fn visit_reachable_with<'mir>( - &self, + &mut self, body: &'mir mir::Body<'tcx>, - vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>, + vis: &mut impl ResultsVisitor<'mir, 'tcx, Self, FlowState = A::Domain>, ) { let blocks = mir::traversal::reachable(body); visit_results(body, blocks.map(|(bb, _)| bb), self, vis) } } +impl<'tcx, A> Results<'tcx, A> +where + A: Analysis<'tcx>, +{ + /// Creates a `ResultsCursor` that can inspect these `Results`. + pub fn as_results_cursor<'a, 'mir>( + &'a mut self, + body: &'mir mir::Body<'tcx>, + ) -> ResultsRefCursor<'a, 'mir, 'tcx, A> { + ResultsCursor::new(body, self) + } +} +impl<'tcx, A> Results<'tcx, A> +where + A: Analysis<'tcx> + CloneAnalysis, +{ + /// Creates a new `Results` type with a cloned `Analysis` and borrowed entry sets. + pub fn clone_analysis(&self) -> ResultsCloned<'_, 'tcx, A> { + Results { + analysis: self.analysis.clone_analysis(), + entry_sets: &self.entry_sets, + _marker: PhantomData, + } + } + + /// Creates a `ResultsCursor` that can inspect these `Results`. + pub fn cloned_results_cursor<'mir>( + &self, + body: &'mir mir::Body<'tcx>, + ) -> ResultsClonedCursor<'_, 'mir, 'tcx, A> { + self.clone_analysis().into_results_cursor(body) + } +} +impl<'res, 'tcx, A> Results<'tcx, A, &'res EntrySets<'tcx, A>> +where + A: Analysis<'tcx> + CloneAnalysis, +{ + /// Creates a new `Results` type with a cloned `Analysis` and borrowed entry sets. + pub fn reclone_analysis(&self) -> Self { + Results { + analysis: self.analysis.clone_analysis(), + entry_sets: self.entry_sets, + _marker: PhantomData, + } + } +} /// A solver for dataflow problems. pub struct Engine<'a, 'tcx, A> @@ -88,7 +144,7 @@ where // gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade // performance in practice. I've tried a few ways to avoid this, but they have downsides. See // the message for the commit that added this FIXME for more information. - apply_trans_for_block: Option>, + apply_statement_trans_for_block: Option>, } impl<'a, 'tcx, A, D, T> Engine<'a, 'tcx, A> @@ -98,7 +154,7 @@ where T: Idx, { /// Creates a new `Engine` to solve a gen-kill dataflow problem. - pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self { + pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, mut analysis: A) -> Self { // If there are no back-edges in the control-flow graph, we only ever need to apply the // transfer function for each block exactly once (assuming that we process blocks in RPO). // @@ -109,12 +165,17 @@ where // Otherwise, compute and store the cumulative transfer function for each block. - let identity = GenKillSet::identity(analysis.bottom_value(body).domain_size()); + let identity = GenKillSet::identity(analysis.domain_size(body)); let mut trans_for_block = IndexVec::from_elem(identity, &body.basic_blocks); for (block, block_data) in body.basic_blocks.iter_enumerated() { let trans = &mut trans_for_block[block]; - A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data); + A::Direction::gen_kill_statement_effects_in_block( + &mut analysis, + trans, + block, + block_data, + ); } let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| { @@ -143,17 +204,18 @@ where tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A, - apply_trans_for_block: Option>, + apply_statement_trans_for_block: Option>, ) -> Self { - let bottom_value = analysis.bottom_value(body); - let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), &body.basic_blocks); + let mut entry_sets = + IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len()); analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]); - if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != bottom_value { + if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != analysis.bottom_value(body) + { bug!("`initialize_start_block` is not yet supported for backward dataflow analyses"); } - Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_trans_for_block } + Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_statement_trans_for_block } } /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis. @@ -171,7 +233,13 @@ where A::Domain: DebugWithContext, { let Engine { - analysis, body, mut entry_sets, tcx, apply_trans_for_block, pass_name, .. + mut analysis, + body, + mut entry_sets, + tcx, + apply_statement_trans_for_block, + pass_name, + .. } = self; let mut dirty_queue: WorkQueue = WorkQueue::with_none(body.basic_blocks.len()); @@ -201,17 +269,20 @@ where state.clone_from(&entry_sets[bb]); // Apply the block transfer function, using the cached one if it exists. - match &apply_trans_for_block { - Some(apply) => apply(bb, &mut state), - None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data), - } + let edges = A::Direction::apply_effects_in_block( + &mut analysis, + &mut state, + bb, + bb_data, + apply_statement_trans_for_block.as_deref(), + ); A::Direction::join_state_into_successors_of( - &analysis, - tcx, + &mut analysis, body, &mut state, - (bb, bb_data), + bb, + edges, |target: BasicBlock, state: &A::Domain| { let set_changed = entry_sets[target].join(state); if set_changed { @@ -221,11 +292,13 @@ where ); } - let results = Results { analysis, entry_sets }; + let mut results = Results { analysis, entry_sets, _marker: PhantomData }; - let res = write_graphviz_results(tcx, &body, &results, pass_name); - if let Err(e) = res { - error!("Failed to write graphviz dataflow results: {}", e); + if tcx.sess.opts.unstable_opts.dump_mir_dataflow { + let res = write_graphviz_results(tcx, &body, &mut results, pass_name); + if let Err(e) = res { + error!("Failed to write graphviz dataflow results: {}", e); + } } results @@ -235,11 +308,11 @@ where // Graphviz /// Writes a DOT file containing the results of a dataflow analysis if the user requested it via -/// `rustc_mir` attributes. +/// `rustc_mir` attributes and `-Z dump-mir-dataflow`. fn write_graphviz_results<'tcx, A>( tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>, - results: &Results<'tcx, A>, + results: &mut Results<'tcx, A>, pass_name: Option<&'static str>, ) -> std::io::Result<()> where @@ -264,9 +337,7 @@ where io::BufWriter::new(fs::File::create(&path)?) } - None if tcx.sess.opts.unstable_opts.dump_mir_dataflow - && dump_enabled(tcx, A::NAME, def_id) => - { + None if dump_enabled(tcx, A::NAME, def_id) => { create_dump_file(tcx, ".dot", false, A::NAME, &pass_name.unwrap_or("-----"), body)? } diff --git a/compiler/rustc_mir_dataflow/src/framework/fmt.rs b/compiler/rustc_mir_dataflow/src/framework/fmt.rs index 6a256fae3ca7a..e3a66bd952c56 100644 --- a/compiler/rustc_mir_dataflow/src/framework/fmt.rs +++ b/compiler/rustc_mir_dataflow/src/framework/fmt.rs @@ -1,6 +1,7 @@ //! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow //! analysis. +use super::lattice::MaybeReachable; use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet}; use rustc_index::Idx; use std::fmt; @@ -124,6 +125,37 @@ where } } +impl DebugWithContext for MaybeReachable +where + S: DebugWithContext, +{ + fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MaybeReachable::Unreachable => { + write!(f, "unreachable") + } + MaybeReachable::Reachable(set) => set.fmt_with(ctxt, f), + } + } + + fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match (self, old) { + (MaybeReachable::Unreachable, MaybeReachable::Unreachable) => Ok(()), + (MaybeReachable::Unreachable, MaybeReachable::Reachable(set)) => { + write!(f, "\u{001f}+")?; + set.fmt_with(ctxt, f) + } + (MaybeReachable::Reachable(set), MaybeReachable::Unreachable) => { + write!(f, "\u{001f}-")?; + set.fmt_with(ctxt, f) + } + (MaybeReachable::Reachable(this), MaybeReachable::Reachable(old)) => { + this.fmt_diff_with(old, ctxt, f) + } + } + } +} + fn fmt_diff( inserted: &HybridBitSet, removed: &HybridBitSet, diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs index 707729f8f21b7..1421d9b45cda6 100644 --- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs +++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs @@ -1,6 +1,7 @@ //! A helpful diagram for debugging dataflow problems. use std::borrow::Cow; +use std::cell::RefCell; use std::sync::OnceLock; use std::{io, ops, str}; @@ -28,23 +29,27 @@ impl OutputStyle { } } -pub struct Formatter<'a, 'tcx, A> +pub struct Formatter<'res, 'mir, 'tcx, A> where A: Analysis<'tcx>, { - body: &'a Body<'tcx>, - results: &'a Results<'tcx, A>, + body: &'mir Body<'tcx>, + results: RefCell<&'res mut Results<'tcx, A>>, style: OutputStyle, reachable: BitSet, } -impl<'a, 'tcx, A> Formatter<'a, 'tcx, A> +impl<'res, 'mir, 'tcx, A> Formatter<'res, 'mir, 'tcx, A> where A: Analysis<'tcx>, { - pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>, style: OutputStyle) -> Self { + pub fn new( + body: &'mir Body<'tcx>, + results: &'res mut Results<'tcx, A>, + style: OutputStyle, + ) -> Self { let reachable = mir::traversal::reachable_as_bitset(body); - Formatter { body, results, style, reachable } + Formatter { body, results: results.into(), style, reachable } } } @@ -64,7 +69,7 @@ fn dataflow_successors(body: &Body<'_>, bb: BasicBlock) -> Vec { .collect() } -impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, 'tcx, A> +impl<'tcx, A> dot::Labeller<'_> for Formatter<'_, '_, 'tcx, A> where A: Analysis<'tcx>, A::Domain: DebugWithContext, @@ -83,13 +88,14 @@ where fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> { let mut label = Vec::new(); + let mut results = self.results.borrow_mut(); let mut fmt = BlockFormatter { - results: ResultsRefCursor::new(self.body, self.results), + results: results.as_results_cursor(self.body), style: self.style, bg: Background::Light, }; - fmt.write_node_label(&mut label, self.body, *block).unwrap(); + fmt.write_node_label(&mut label, *block).unwrap(); dot::LabelText::html(String::from_utf8(label).unwrap()) } @@ -103,7 +109,7 @@ where } } -impl<'a, 'tcx, A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A> +impl<'mir, 'tcx, A> dot::GraphWalk<'mir> for Formatter<'_, 'mir, 'tcx, A> where A: Analysis<'tcx>, { @@ -137,16 +143,16 @@ where } } -struct BlockFormatter<'a, 'tcx, A> +struct BlockFormatter<'res, 'mir, 'tcx, A> where A: Analysis<'tcx>, { - results: ResultsRefCursor<'a, 'a, 'tcx, A>, + results: ResultsRefCursor<'res, 'mir, 'tcx, A>, bg: Background, style: OutputStyle, } -impl<'a, 'tcx, A> BlockFormatter<'a, 'tcx, A> +impl<'res, 'mir, 'tcx, A> BlockFormatter<'res, 'mir, 'tcx, A> where A: Analysis<'tcx>, A::Domain: DebugWithContext, @@ -159,12 +165,7 @@ where bg } - fn write_node_label( - &mut self, - w: &mut impl io::Write, - body: &'a Body<'tcx>, - block: BasicBlock, - ) -> io::Result<()> { + fn write_node_label(&mut self, w: &mut impl io::Write, block: BasicBlock) -> io::Result<()> { // Sample output: // +-+-----------------------------------------------+ // A | bb4 | @@ -215,11 +216,11 @@ where self.write_row_with_full_state(w, "", "(on start)")?; // D + E: Statement and terminator transfer functions - self.write_statements_and_terminator(w, body, block)?; + self.write_statements_and_terminator(w, block)?; // F: State at end of block - let terminator = body[block].terminator(); + let terminator = self.results.body()[block].terminator(); // Write the full dataflow state immediately after the terminator if it differs from the // state at block entry. @@ -268,7 +269,11 @@ where self.write_row(w, "", "(on yield resume)", |this, w, fmt| { let state_on_generator_drop = this.results.get().clone(); this.results.apply_custom_effect(|analysis, state| { - analysis.apply_yield_resume_effect(state, resume, resume_arg); + analysis.apply_call_return_effect( + state, + resume, + CallReturnPlaces::Yield(resume_arg), + ); }); write!( @@ -389,10 +394,14 @@ where fn write_statements_and_terminator( &mut self, w: &mut impl io::Write, - body: &'a Body<'tcx>, block: BasicBlock, ) -> io::Result<()> { - let diffs = StateDiffCollector::run(body, block, self.results.results(), self.style); + let diffs = StateDiffCollector::run( + self.results.body(), + block, + self.results.mut_results(), + self.style, + ); let mut diffs_before = diffs.before.map(|v| v.into_iter()); let mut diffs_after = diffs.after.into_iter(); @@ -401,7 +410,7 @@ where if A::Direction::IS_FORWARD { it.next().unwrap() } else { it.next_back().unwrap() } }; - for (i, statement) in body[block].statements.iter().enumerate() { + for (i, statement) in self.results.body()[block].statements.iter().enumerate() { let statement_str = format!("{statement:?}"); let index_str = format!("{i}"); @@ -423,7 +432,7 @@ where assert!(diffs_after.is_empty()); assert!(diffs_before.as_ref().map_or(true, ExactSizeIterator::is_empty)); - let terminator = body[block].terminator(); + let terminator = self.results.body()[block].terminator(); let mut terminator_str = String::new(); terminator.kind.fmt_head(&mut terminator_str).unwrap(); @@ -492,29 +501,24 @@ where } } -struct StateDiffCollector<'a, 'tcx, A> -where - A: Analysis<'tcx>, -{ - analysis: &'a A, - prev_state: A::Domain, +struct StateDiffCollector { + prev_state: D, before: Option>, after: Vec, } -impl<'a, 'tcx, A> StateDiffCollector<'a, 'tcx, A> -where - A: Analysis<'tcx>, - A::Domain: DebugWithContext, -{ - fn run( - body: &'a mir::Body<'tcx>, +impl StateDiffCollector { + fn run<'tcx, A>( + body: &mir::Body<'tcx>, block: BasicBlock, - results: &'a Results<'tcx, A>, + results: &mut Results<'tcx, A>, style: OutputStyle, - ) -> Self { + ) -> Self + where + A: Analysis<'tcx, Domain = D>, + D: DebugWithContext, + { let mut collector = StateDiffCollector { - analysis: &results.analysis, prev_state: results.analysis.bottom_value(body), after: vec![], before: (style == OutputStyle::BeforeAndAfter).then_some(vec![]), @@ -525,7 +529,7 @@ where } } -impl<'a, 'tcx, A> ResultsVisitor<'a, 'tcx> for StateDiffCollector<'a, 'tcx, A> +impl<'tcx, A> ResultsVisitor<'_, 'tcx, Results<'tcx, A>> for StateDiffCollector where A: Analysis<'tcx>, A::Domain: DebugWithContext, @@ -534,6 +538,7 @@ where fn visit_block_start( &mut self, + _results: &Results<'tcx, A>, state: &Self::FlowState, _block_data: &mir::BasicBlockData<'tcx>, _block: BasicBlock, @@ -545,6 +550,7 @@ where fn visit_block_end( &mut self, + _results: &Results<'tcx, A>, state: &Self::FlowState, _block_data: &mir::BasicBlockData<'tcx>, _block: BasicBlock, @@ -556,45 +562,49 @@ where fn visit_statement_before_primary_effect( &mut self, + results: &Results<'tcx, A>, state: &Self::FlowState, _statement: &mir::Statement<'tcx>, _location: Location, ) { if let Some(before) = self.before.as_mut() { - before.push(diff_pretty(state, &self.prev_state, self.analysis)); + before.push(diff_pretty(state, &self.prev_state, &results.analysis)); self.prev_state.clone_from(state) } } fn visit_statement_after_primary_effect( &mut self, + results: &Results<'tcx, A>, state: &Self::FlowState, _statement: &mir::Statement<'tcx>, _location: Location, ) { - self.after.push(diff_pretty(state, &self.prev_state, self.analysis)); + self.after.push(diff_pretty(state, &self.prev_state, &results.analysis)); self.prev_state.clone_from(state) } fn visit_terminator_before_primary_effect( &mut self, + results: &Results<'tcx, A>, state: &Self::FlowState, _terminator: &mir::Terminator<'tcx>, _location: Location, ) { if let Some(before) = self.before.as_mut() { - before.push(diff_pretty(state, &self.prev_state, self.analysis)); + before.push(diff_pretty(state, &self.prev_state, &results.analysis)); self.prev_state.clone_from(state) } } fn visit_terminator_after_primary_effect( &mut self, + results: &Results<'tcx, A>, state: &Self::FlowState, _terminator: &mir::Terminator<'tcx>, _location: Location, ) { - self.after.push(diff_pretty(state, &self.prev_state, self.analysis)); + self.after.push(diff_pretty(state, &self.prev_state, &results.analysis)); self.prev_state.clone_from(state) } } diff --git a/compiler/rustc_mir_dataflow/src/framework/lattice.rs b/compiler/rustc_mir_dataflow/src/framework/lattice.rs index 3952f44ad489d..3b89598d28993 100644 --- a/compiler/rustc_mir_dataflow/src/framework/lattice.rs +++ b/compiler/rustc_mir_dataflow/src/framework/lattice.rs @@ -187,10 +187,6 @@ impl MeetSemiLattice for ChunkedBitSet { pub struct Dual(pub T); impl BitSetExt for Dual> { - fn domain_size(&self) -> usize { - self.0.domain_size() - } - fn contains(&self, elem: T) -> bool { self.0.contains(elem) } @@ -276,3 +272,93 @@ impl HasBottom for FlatSet { impl HasTop for FlatSet { const TOP: Self = Self::Top; } + +/// Extend a lattice with a bottom value to represent an unreachable execution. +/// +/// The only useful action on an unreachable state is joining it with a reachable one to make it +/// reachable. All other actions, gen/kill for instance, are no-ops. +#[derive(PartialEq, Eq, Debug)] +pub enum MaybeReachable { + Unreachable, + Reachable(T), +} + +impl MaybeReachable { + pub fn is_reachable(&self) -> bool { + matches!(self, MaybeReachable::Reachable(_)) + } +} + +impl HasBottom for MaybeReachable { + const BOTTOM: Self = MaybeReachable::Unreachable; +} + +impl HasTop for MaybeReachable { + const TOP: Self = MaybeReachable::Reachable(T::TOP); +} + +impl MaybeReachable { + /// Return whether the current state contains the given element. If the state is unreachable, + /// it does no contain anything. + pub fn contains(&self, elem: T) -> bool + where + S: BitSetExt, + { + match self { + MaybeReachable::Unreachable => false, + MaybeReachable::Reachable(set) => set.contains(elem), + } + } +} + +impl> BitSetExt for MaybeReachable { + fn contains(&self, elem: T) -> bool { + self.contains(elem) + } + + fn union(&mut self, other: &HybridBitSet) { + match self { + MaybeReachable::Unreachable => {} + MaybeReachable::Reachable(set) => set.union(other), + } + } + + fn subtract(&mut self, other: &HybridBitSet) { + match self { + MaybeReachable::Unreachable => {} + MaybeReachable::Reachable(set) => set.subtract(other), + } + } +} + +impl Clone for MaybeReachable { + fn clone(&self) -> Self { + match self { + MaybeReachable::Reachable(x) => MaybeReachable::Reachable(x.clone()), + MaybeReachable::Unreachable => MaybeReachable::Unreachable, + } + } + + fn clone_from(&mut self, source: &Self) { + match (&mut *self, source) { + (MaybeReachable::Reachable(x), MaybeReachable::Reachable(y)) => { + x.clone_from(&y); + } + _ => *self = source.clone(), + } + } +} + +impl JoinSemiLattice for MaybeReachable { + fn join(&mut self, other: &Self) -> bool { + // Unreachable acts as a bottom. + match (&mut *self, &other) { + (_, MaybeReachable::Unreachable) => false, + (MaybeReachable::Unreachable, _) => { + *self = other.clone(); + true + } + (MaybeReachable::Reachable(this), MaybeReachable::Reachable(other)) => this.join(other), + } + } +} diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs index f2263007f6fe9..ce30c642fcc9f 100644 --- a/compiler/rustc_mir_dataflow/src/framework/mod.rs +++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs @@ -34,7 +34,7 @@ use std::cmp::Ordering; use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet}; use rustc_index::Idx; -use rustc_middle::mir::{self, BasicBlock, Location}; +use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges}; use rustc_middle::ty::TyCtxt; mod cursor; @@ -45,26 +45,21 @@ pub mod graphviz; pub mod lattice; mod visitor; -pub use self::cursor::{ResultsCursor, ResultsRefCursor}; +pub use self::cursor::{AnalysisResults, ResultsClonedCursor, ResultsCursor, ResultsRefCursor}; pub use self::direction::{Backward, Direction, Forward}; -pub use self::engine::{Engine, Results}; -pub use self::lattice::{JoinSemiLattice, MeetSemiLattice}; +pub use self::engine::{Engine, EntrySets, Results, ResultsCloned}; +pub use self::lattice::{JoinSemiLattice, MaybeReachable, MeetSemiLattice}; pub use self::visitor::{visit_results, ResultsVisitable, ResultsVisitor}; /// Analysis domains are all bitsets of various kinds. This trait holds /// operations needed by all of them. pub trait BitSetExt { - fn domain_size(&self) -> usize; fn contains(&self, elem: T) -> bool; fn union(&mut self, other: &HybridBitSet); fn subtract(&mut self, other: &HybridBitSet); } impl BitSetExt for BitSet { - fn domain_size(&self) -> usize { - self.domain_size() - } - fn contains(&self, elem: T) -> bool { self.contains(elem) } @@ -79,10 +74,6 @@ impl BitSetExt for BitSet { } impl BitSetExt for ChunkedBitSet { - fn domain_size(&self) -> usize { - self.domain_size() - } - fn contains(&self, elem: T) -> bool { self.contains(elem) } @@ -146,7 +137,7 @@ pub trait AnalysisDomain<'tcx> { pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { /// Updates the current dataflow state with the effect of evaluating a statement. fn apply_statement_effect( - &self, + &mut self, state: &mut Self::Domain, statement: &mir::Statement<'tcx>, location: Location, @@ -159,7 +150,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { /// *part* of the effect of a statement (e.g. for two-phase borrows). As a general rule, /// analyses should not implement this without also implementing `apply_statement_effect`. fn apply_before_statement_effect( - &self, + &mut self, _state: &mut Self::Domain, _statement: &mir::Statement<'tcx>, _location: Location, @@ -172,12 +163,12 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { /// in this function. That should go in `apply_call_return_effect`. For example, in the /// `InitializedPlaces` analyses, the return place for a function call is not marked as /// initialized here. - fn apply_terminator_effect( - &self, + fn apply_terminator_effect<'mir>( + &mut self, state: &mut Self::Domain, - terminator: &mir::Terminator<'tcx>, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ); + ) -> TerminatorEdges<'mir, 'tcx>; /// Updates the current dataflow state with an effect that occurs immediately *before* the /// given terminator. @@ -186,7 +177,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { /// *part* of the effect of a terminator (e.g. for two-phase borrows). As a general rule, /// analyses should not implement this without also implementing `apply_terminator_effect`. fn apply_before_terminator_effect( - &self, + &mut self, _state: &mut Self::Domain, _terminator: &mir::Terminator<'tcx>, _location: Location, @@ -201,26 +192,12 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { /// This is separate from `apply_terminator_effect` to properly track state across unwind /// edges. fn apply_call_return_effect( - &self, + &mut self, state: &mut Self::Domain, block: BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ); - /// Updates the current dataflow state with the effect of resuming from a `Yield` terminator. - /// - /// This is similar to `apply_call_return_effect` in that it only takes place after the - /// generator is resumed, not when it is dropped. - /// - /// By default, no effects happen. - fn apply_yield_resume_effect( - &self, - _state: &mut Self::Domain, - _resume_block: BasicBlock, - _resume_place: mir::Place<'tcx>, - ) { - } - /// Updates the current dataflow state with the effect of taking a particular branch in a /// `SwitchInt` terminator. /// @@ -235,7 +212,7 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { /// engine doesn't need to clone the exit state for a block unless /// `SwitchIntEdgeEffects::apply` is actually called. fn apply_switch_int_edge_effects( - &self, + &mut self, _block: BasicBlock, _discr: &mir::Operand<'tcx>, _apply_edge_effects: &mut impl SwitchIntEdgeEffects, @@ -269,6 +246,21 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { } } +/// Defines an `Analysis` which can be cloned for use in multiple `ResultsCursor`s or +/// `ResultsVisitor`s. Note this need not be a full clone, only enough of one to be used with a new +/// `ResultsCursor` or `ResultsVisitor` +pub trait CloneAnalysis { + fn clone_analysis(&self) -> Self; +} +impl<'tcx, A> CloneAnalysis for A +where + A: Analysis<'tcx> + Copy, +{ + fn clone_analysis(&self) -> Self { + *self + } +} + /// A gen/kill dataflow problem. /// /// Each method in this trait has a corresponding one in `Analysis`. However, these methods only @@ -280,9 +272,11 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> { pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> { type Idx: Idx; + fn domain_size(&self, body: &mir::Body<'tcx>) -> usize; + /// See `Analysis::apply_statement_effect`. fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, statement: &mir::Statement<'tcx>, location: Location, @@ -290,7 +284,7 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> { /// See `Analysis::apply_before_statement_effect`. fn before_statement_effect( - &self, + &mut self, _trans: &mut impl GenKill, _statement: &mir::Statement<'tcx>, _location: Location, @@ -298,16 +292,16 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> { } /// See `Analysis::apply_terminator_effect`. - fn terminator_effect( - &self, - trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ); + ) -> TerminatorEdges<'mir, 'tcx>; /// See `Analysis::apply_before_terminator_effect`. fn before_terminator_effect( - &self, + &mut self, _trans: &mut impl GenKill, _terminator: &mir::Terminator<'tcx>, _location: Location, @@ -318,24 +312,15 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> { /// See `Analysis::apply_call_return_effect`. fn call_return_effect( - &self, + &mut self, trans: &mut impl GenKill, block: BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ); - /// See `Analysis::apply_yield_resume_effect`. - fn yield_resume_effect( - &self, - _trans: &mut impl GenKill, - _resume_block: BasicBlock, - _resume_place: mir::Place<'tcx>, - ) { - } - /// See `Analysis::apply_switch_int_edge_effects`. fn switch_int_edge_effects>( - &self, + &mut self, _block: BasicBlock, _discr: &mir::Operand<'tcx>, _edge_effects: &mut impl SwitchIntEdgeEffects, @@ -349,7 +334,7 @@ where A::Domain: GenKill + BitSetExt, { fn apply_statement_effect( - &self, + &mut self, state: &mut A::Domain, statement: &mir::Statement<'tcx>, location: Location, @@ -358,7 +343,7 @@ where } fn apply_before_statement_effect( - &self, + &mut self, state: &mut A::Domain, statement: &mir::Statement<'tcx>, location: Location, @@ -366,17 +351,17 @@ where self.before_statement_effect(state, statement, location); } - fn apply_terminator_effect( - &self, + fn apply_terminator_effect<'mir>( + &mut self, state: &mut A::Domain, - terminator: &mir::Terminator<'tcx>, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ) { - self.terminator_effect(state, terminator, location); + ) -> TerminatorEdges<'mir, 'tcx> { + self.terminator_effect(state, terminator, location) } fn apply_before_terminator_effect( - &self, + &mut self, state: &mut A::Domain, terminator: &mir::Terminator<'tcx>, location: Location, @@ -387,7 +372,7 @@ where /* Edge-specific effects */ fn apply_call_return_effect( - &self, + &mut self, state: &mut A::Domain, block: BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, @@ -395,17 +380,8 @@ where self.call_return_effect(state, block, return_places); } - fn apply_yield_resume_effect( - &self, - state: &mut A::Domain, - resume_block: BasicBlock, - resume_place: mir::Place<'tcx>, - ) { - self.yield_resume_effect(state, resume_block, resume_place); - } - fn apply_switch_int_edge_effects( - &self, + &mut self, block: BasicBlock, discr: &mir::Operand<'tcx>, edge_effects: &mut impl SwitchIntEdgeEffects, @@ -516,6 +492,24 @@ impl GenKill for ChunkedBitSet { } } +impl> GenKill for MaybeReachable { + fn gen(&mut self, elem: T) { + match self { + // If the state is not reachable, adding an element does nothing. + MaybeReachable::Unreachable => {} + MaybeReachable::Reachable(set) => set.gen(elem), + } + } + + fn kill(&mut self, elem: T) { + match self { + // If the state is not reachable, killing an element does nothing. + MaybeReachable::Unreachable => {} + MaybeReachable::Reachable(set) => set.kill(elem), + } + } +} + impl GenKill for lattice::Dual> { fn gen(&mut self, elem: T) { self.0.insert(elem); @@ -597,29 +591,5 @@ pub trait SwitchIntEdgeEffects { fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget)); } -/// List of places that are written to after a successful (non-unwind) return -/// from a `Call` or `InlineAsm`. -pub enum CallReturnPlaces<'a, 'tcx> { - Call(mir::Place<'tcx>), - InlineAsm(&'a [mir::InlineAsmOperand<'tcx>]), -} - -impl<'tcx> CallReturnPlaces<'_, 'tcx> { - pub fn for_each(&self, mut f: impl FnMut(mir::Place<'tcx>)) { - match *self { - Self::Call(place) => f(place), - Self::InlineAsm(operands) => { - for op in operands { - match *op { - mir::InlineAsmOperand::Out { place: Some(place), .. } - | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place), - _ => {} - } - } - } - } - } -} - #[cfg(test)] mod tests; diff --git a/compiler/rustc_mir_dataflow/src/framework/tests.rs b/compiler/rustc_mir_dataflow/src/framework/tests.rs index 0fed305b930c4..9cce5b26cd305 100644 --- a/compiler/rustc_mir_dataflow/src/framework/tests.rs +++ b/compiler/rustc_mir_dataflow/src/framework/tests.rs @@ -40,7 +40,7 @@ fn mock_body<'tcx>() -> mir::Body<'tcx> { destination: dummy_place.clone(), target: Some(mir::START_BLOCK), unwind: mir::UnwindAction::Continue, - from_hir_call: false, + call_source: mir::CallSource::Misc, fn_span: DUMMY_SP, }, ); @@ -54,7 +54,7 @@ fn mock_body<'tcx>() -> mir::Body<'tcx> { destination: dummy_place.clone(), target: Some(mir::START_BLOCK), unwind: mir::UnwindAction::Continue, - from_hir_call: false, + call_source: mir::CallSource::Misc, fn_span: DUMMY_SP, }, ); @@ -179,7 +179,7 @@ impl<'tcx, D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> { impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> { fn apply_statement_effect( - &self, + &mut self, state: &mut Self::Domain, _statement: &mir::Statement<'tcx>, location: Location, @@ -189,7 +189,7 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> { } fn apply_before_statement_effect( - &self, + &mut self, state: &mut Self::Domain, _statement: &mir::Statement<'tcx>, location: Location, @@ -198,18 +198,19 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> { assert!(state.insert(idx)); } - fn apply_terminator_effect( - &self, + fn apply_terminator_effect<'mir>( + &mut self, state: &mut Self::Domain, - _terminator: &mir::Terminator<'tcx>, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { let idx = self.effect(Effect::Primary.at_index(location.statement_index)); assert!(state.insert(idx)); + terminator.edges() } fn apply_before_terminator_effect( - &self, + &mut self, state: &mut Self::Domain, _terminator: &mir::Terminator<'tcx>, location: Location, @@ -219,7 +220,7 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> { } fn apply_call_return_effect( - &self, + &mut self, _state: &mut Self::Domain, _block: BasicBlock, _return_places: CallReturnPlaces<'_, 'tcx>, @@ -266,7 +267,8 @@ fn test_cursor(analysis: MockAnalysis<'_, D>) { let body = analysis.body; let mut cursor = - Results { entry_sets: analysis.mock_entry_sets(), analysis }.into_results_cursor(body); + Results { entry_sets: analysis.mock_entry_sets(), analysis, _marker: PhantomData } + .into_results_cursor(body); cursor.allow_unreachable(); diff --git a/compiler/rustc_mir_dataflow/src/framework/visitor.rs b/compiler/rustc_mir_dataflow/src/framework/visitor.rs index 75b4e150a8a33..76a729827813e 100644 --- a/compiler/rustc_mir_dataflow/src/framework/visitor.rs +++ b/compiler/rustc_mir_dataflow/src/framework/visitor.rs @@ -1,16 +1,18 @@ +use std::borrow::Borrow; + use rustc_middle::mir::{self, BasicBlock, Location}; -use super::{Analysis, Direction, Results}; +use super::{Analysis, Direction, EntrySets, Results}; /// Calls the corresponding method in `ResultsVisitor` for every location in a `mir::Body` with the /// dataflow state at that location. -pub fn visit_results<'mir, 'tcx, F, V>( +pub fn visit_results<'mir, 'tcx, F, R>( body: &'mir mir::Body<'tcx>, blocks: impl IntoIterator, - results: &V, - vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>, + results: &mut R, + vis: &mut impl ResultsVisitor<'mir, 'tcx, R, FlowState = F>, ) where - V: ResultsVisitable<'tcx, FlowState = F>, + R: ResultsVisitable<'tcx, FlowState = F>, { let mut state = results.new_flow_state(body); @@ -22,15 +24,18 @@ pub fn visit_results<'mir, 'tcx, F, V>( assert!(reachable_blocks.contains(block)); let block_data = &body[block]; - V::Direction::visit_results_in_block(&mut state, block, block_data, results, vis); + R::Direction::visit_results_in_block(&mut state, block, block_data, results, vis); } } -pub trait ResultsVisitor<'mir, 'tcx> { +/// A visitor over the results of an `Analysis`. The type parameter `R` is the results type being +/// visited. +pub trait ResultsVisitor<'mir, 'tcx, R> { type FlowState; fn visit_block_start( &mut self, + _results: &R, _state: &Self::FlowState, _block_data: &'mir mir::BasicBlockData<'tcx>, _block: BasicBlock, @@ -41,6 +46,7 @@ pub trait ResultsVisitor<'mir, 'tcx> { /// its `statement_effect`. fn visit_statement_before_primary_effect( &mut self, + _results: &R, _state: &Self::FlowState, _statement: &'mir mir::Statement<'tcx>, _location: Location, @@ -51,6 +57,7 @@ pub trait ResultsVisitor<'mir, 'tcx> { /// statement applied to `state`. fn visit_statement_after_primary_effect( &mut self, + _results: &R, _state: &Self::FlowState, _statement: &'mir mir::Statement<'tcx>, _location: Location, @@ -61,6 +68,7 @@ pub trait ResultsVisitor<'mir, 'tcx> { /// its `terminator_effect`. fn visit_terminator_before_primary_effect( &mut self, + _results: &R, _state: &Self::FlowState, _terminator: &'mir mir::Terminator<'tcx>, _location: Location, @@ -73,6 +81,7 @@ pub trait ResultsVisitor<'mir, 'tcx> { /// The `call_return_effect` (if one exists) will *not* be applied to `state`. fn visit_terminator_after_primary_effect( &mut self, + _results: &R, _state: &Self::FlowState, _terminator: &'mir mir::Terminator<'tcx>, _location: Location, @@ -81,6 +90,7 @@ pub trait ResultsVisitor<'mir, 'tcx> { fn visit_block_end( &mut self, + _results: &R, _state: &Self::FlowState, _block_data: &'mir mir::BasicBlockData<'tcx>, _block: BasicBlock, @@ -105,37 +115,38 @@ pub trait ResultsVisitable<'tcx> { fn reset_to_block_entry(&self, state: &mut Self::FlowState, block: BasicBlock); fn reconstruct_before_statement_effect( - &self, + &mut self, state: &mut Self::FlowState, statement: &mir::Statement<'tcx>, location: Location, ); fn reconstruct_statement_effect( - &self, + &mut self, state: &mut Self::FlowState, statement: &mir::Statement<'tcx>, location: Location, ); fn reconstruct_before_terminator_effect( - &self, + &mut self, state: &mut Self::FlowState, terminator: &mir::Terminator<'tcx>, location: Location, ); fn reconstruct_terminator_effect( - &self, + &mut self, state: &mut Self::FlowState, terminator: &mir::Terminator<'tcx>, location: Location, ); } -impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A> +impl<'tcx, A, E> ResultsVisitable<'tcx> for Results<'tcx, A, E> where A: Analysis<'tcx>, + E: Borrow>, { type FlowState = A::Domain; @@ -146,11 +157,11 @@ where } fn reset_to_block_entry(&self, state: &mut Self::FlowState, block: BasicBlock) { - state.clone_from(&self.entry_set_for_block(block)); + state.clone_from(self.entry_set_for_block(block)); } fn reconstruct_before_statement_effect( - &self, + &mut self, state: &mut Self::FlowState, stmt: &mir::Statement<'tcx>, loc: Location, @@ -159,7 +170,7 @@ where } fn reconstruct_statement_effect( - &self, + &mut self, state: &mut Self::FlowState, stmt: &mir::Statement<'tcx>, loc: Location, @@ -168,7 +179,7 @@ where } fn reconstruct_before_terminator_effect( - &self, + &mut self, state: &mut Self::FlowState, term: &mir::Terminator<'tcx>, loc: Location, @@ -177,7 +188,7 @@ where } fn reconstruct_terminator_effect( - &self, + &mut self, state: &mut Self::FlowState, term: &mir::Terminator<'tcx>, loc: Location, diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index 92d30f254a6af..8d7b50796bbf8 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -1,19 +1,20 @@ -use super::*; - -use crate::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis}; +use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; +use crate::{AnalysisDomain, GenKill, GenKillAnalysis}; + /// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points /// to a given local. /// /// At present, this is used as a very limited form of alias analysis. For example, /// `MaybeBorrowedLocals` is used to compute which locals are live during a yield expression for /// immovable generators. +#[derive(Clone, Copy)] pub struct MaybeBorrowedLocals; impl MaybeBorrowedLocals { - fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> { + pub(super) fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> { TransferFunction { trans } } } @@ -22,12 +23,12 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals { type Domain = BitSet; const NAME: &'static str = "maybe_borrowed_locals"; - fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { + fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain { // bottom = unborrowed BitSet::new_empty(body.local_decls().len()) } - fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) { + fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) { // No locals are aliased on function entry } } @@ -35,35 +36,40 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals { impl<'tcx> GenKillAnalysis<'tcx> for MaybeBorrowedLocals { type Idx = Local; + fn domain_size(&self, body: &Body<'tcx>) -> usize { + body.local_decls.len() + } + fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, - statement: &mir::Statement<'tcx>, + statement: &Statement<'tcx>, location: Location, ) { self.transfer_function(trans).visit_statement(statement, location); } - fn terminator_effect( - &self, - trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { self.transfer_function(trans).visit_terminator(terminator, location); + terminator.edges() } fn call_return_effect( - &self, + &mut self, _trans: &mut impl GenKill, - _block: mir::BasicBlock, + _block: BasicBlock, _return_places: CallReturnPlaces<'_, 'tcx>, ) { } } /// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`. -struct TransferFunction<'a, T> { +pub(super) struct TransferFunction<'a, T> { trans: &'a mut T, } @@ -81,37 +87,37 @@ where } } - fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { self.super_rvalue(rvalue, location); match rvalue { - mir::Rvalue::AddressOf(_, borrowed_place) | mir::Rvalue::Ref(_, _, borrowed_place) => { + Rvalue::AddressOf(_, borrowed_place) | Rvalue::Ref(_, _, borrowed_place) => { if !borrowed_place.is_indirect() { self.trans.gen(borrowed_place.local); } } - mir::Rvalue::Cast(..) - | mir::Rvalue::ShallowInitBox(..) - | mir::Rvalue::Use(..) - | mir::Rvalue::ThreadLocalRef(..) - | mir::Rvalue::Repeat(..) - | mir::Rvalue::Len(..) - | mir::Rvalue::BinaryOp(..) - | mir::Rvalue::CheckedBinaryOp(..) - | mir::Rvalue::NullaryOp(..) - | mir::Rvalue::UnaryOp(..) - | mir::Rvalue::Discriminant(..) - | mir::Rvalue::Aggregate(..) - | mir::Rvalue::CopyForDeref(..) => {} + Rvalue::Cast(..) + | Rvalue::ShallowInitBox(..) + | Rvalue::Use(..) + | Rvalue::ThreadLocalRef(..) + | Rvalue::Repeat(..) + | Rvalue::Len(..) + | Rvalue::BinaryOp(..) + | Rvalue::CheckedBinaryOp(..) + | Rvalue::NullaryOp(..) + | Rvalue::UnaryOp(..) + | Rvalue::Discriminant(..) + | Rvalue::Aggregate(..) + | Rvalue::CopyForDeref(..) => {} } } - fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) { + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { self.super_terminator(terminator, location); match terminator.kind { - mir::TerminatorKind::Drop { place: dropped_place, .. } => { + TerminatorKind::Drop { place: dropped_place, .. } => { // Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut // self` as a parameter. In the general case, a drop impl could launder that // reference into the surrounding environment through a raw pointer, thus creating diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs new file mode 100644 index 0000000000000..e6d383d626ad5 --- /dev/null +++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs @@ -0,0 +1,778 @@ +use rustc_index::bit_set::{BitSet, ChunkedBitSet}; +use rustc_index::Idx; +use rustc_middle::mir::{self, Body, CallReturnPlaces, Location, TerminatorEdges}; +use rustc_middle::ty::{self, TyCtxt}; + +use crate::drop_flag_effects_for_function_entry; +use crate::drop_flag_effects_for_location; +use crate::elaborate_drops::DropFlagState; +use crate::framework::SwitchIntEdgeEffects; +use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex}; +use crate::on_lookup_result_bits; +use crate::MoveDataParamEnv; +use crate::{drop_flag_effects, on_all_children_bits, on_all_drop_children_bits}; +use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis, MaybeReachable}; + +/// `MaybeInitializedPlaces` tracks all places that might be +/// initialized upon reaching a particular point in the control flow +/// for a function. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // maybe-init: +/// // {} +/// let a = S; let mut b = S; let c; let d; // {a, b} +/// +/// if pred { +/// drop(a); // { b} +/// b = S; // { b} +/// +/// } else { +/// drop(b); // {a} +/// d = S; // {a, d} +/// +/// } // {a, b, d} +/// +/// c = S; // {a, b, c, d} +/// } +/// ``` +/// +/// To determine whether a place *must* be initialized at a +/// particular control-flow point, one can take the set-difference +/// between this data and the data from `MaybeUninitializedPlaces` at the +/// corresponding control-flow point. +/// +/// Similarly, at a given `drop` statement, the set-intersection +/// between this data and `MaybeUninitializedPlaces` yields the set of +/// places that would require a dynamic drop-flag at that statement. +pub struct MaybeInitializedPlaces<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + mdpe: &'a MoveDataParamEnv<'tcx>, + skip_unreachable_unwind: bool, +} + +impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { + MaybeInitializedPlaces { tcx, body, mdpe, skip_unreachable_unwind: false } + } + + pub fn skipping_unreachable_unwind(mut self) -> Self { + self.skip_unreachable_unwind = true; + self + } + + pub fn is_unwind_dead( + &self, + place: mir::Place<'tcx>, + state: &MaybeReachable>, + ) -> bool { + if let LookupResult::Exact(path) = self.move_data().rev_lookup.find(place.as_ref()) { + let mut maybe_live = false; + on_all_drop_children_bits(self.tcx, self.body, self.mdpe, path, |child| { + maybe_live |= state.contains(child); + }); + !maybe_live + } else { + false + } + } +} + +impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + &self.mdpe.move_data + } +} + +/// `MaybeUninitializedPlaces` tracks all places that might be +/// uninitialized upon reaching a particular point in the control flow +/// for a function. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // maybe-uninit: +/// // {a, b, c, d} +/// let a = S; let mut b = S; let c; let d; // { c, d} +/// +/// if pred { +/// drop(a); // {a, c, d} +/// b = S; // {a, c, d} +/// +/// } else { +/// drop(b); // { b, c, d} +/// d = S; // { b, c } +/// +/// } // {a, b, c, d} +/// +/// c = S; // {a, b, d} +/// } +/// ``` +/// +/// To determine whether a place *must* be uninitialized at a +/// particular control-flow point, one can take the set-difference +/// between this data and the data from `MaybeInitializedPlaces` at the +/// corresponding control-flow point. +/// +/// Similarly, at a given `drop` statement, the set-intersection +/// between this data and `MaybeInitializedPlaces` yields the set of +/// places that would require a dynamic drop-flag at that statement. +pub struct MaybeUninitializedPlaces<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + mdpe: &'a MoveDataParamEnv<'tcx>, + + mark_inactive_variants_as_uninit: bool, + skip_unreachable_unwind: BitSet, +} + +impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { + MaybeUninitializedPlaces { + tcx, + body, + mdpe, + mark_inactive_variants_as_uninit: false, + skip_unreachable_unwind: BitSet::new_empty(body.basic_blocks.len()), + } + } + + /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an + /// enum discriminant. + /// + /// This is correct in a vacuum but is not the default because it causes problems in the borrow + /// checker, where this information gets propagated along `FakeEdge`s. + pub fn mark_inactive_variants_as_uninit(mut self) -> Self { + self.mark_inactive_variants_as_uninit = true; + self + } + + pub fn skipping_unreachable_unwind( + mut self, + unreachable_unwind: BitSet, + ) -> Self { + self.skip_unreachable_unwind = unreachable_unwind; + self + } +} + +impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + &self.mdpe.move_data + } +} + +/// `DefinitelyInitializedPlaces` tracks all places that are definitely +/// initialized upon reaching a particular point in the control flow +/// for a function. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // definite-init: +/// // { } +/// let a = S; let mut b = S; let c; let d; // {a, b } +/// +/// if pred { +/// drop(a); // { b, } +/// b = S; // { b, } +/// +/// } else { +/// drop(b); // {a, } +/// d = S; // {a, d} +/// +/// } // { } +/// +/// c = S; // { c } +/// } +/// ``` +/// +/// To determine whether a place *may* be uninitialized at a +/// particular control-flow point, one can take the set-complement +/// of this data. +/// +/// Similarly, at a given `drop` statement, the set-difference between +/// this data and `MaybeInitializedPlaces` yields the set of places +/// that would require a dynamic drop-flag at that statement. +pub struct DefinitelyInitializedPlaces<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + mdpe: &'a MoveDataParamEnv<'tcx>, +} + +impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { + DefinitelyInitializedPlaces { tcx, body, mdpe } + } +} + +impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + &self.mdpe.move_data + } +} + +/// `EverInitializedPlaces` tracks all places that might have ever been +/// initialized upon reaching a particular point in the control flow +/// for a function, without an intervening `StorageDead`. +/// +/// This dataflow is used to determine if an immutable local variable may +/// be assigned to. +/// +/// For example, in code like the following, we have corresponding +/// dataflow information shown in the right-hand comments. +/// +/// ```rust +/// struct S; +/// fn foo(pred: bool) { // ever-init: +/// // { } +/// let a = S; let mut b = S; let c; let d; // {a, b } +/// +/// if pred { +/// drop(a); // {a, b, } +/// b = S; // {a, b, } +/// +/// } else { +/// drop(b); // {a, b, } +/// d = S; // {a, b, d } +/// +/// } // {a, b, d } +/// +/// c = S; // {a, b, c, d } +/// } +/// ``` +pub struct EverInitializedPlaces<'a, 'tcx> { + #[allow(dead_code)] + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + mdpe: &'a MoveDataParamEnv<'tcx>, +} + +impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { + EverInitializedPlaces { tcx, body, mdpe } + } +} + +impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> { + fn move_data(&self) -> &MoveData<'tcx> { + &self.mdpe.move_data + } +} + +impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { + fn update_bits( + trans: &mut impl GenKill, + path: MovePathIndex, + state: DropFlagState, + ) { + match state { + DropFlagState::Absent => trans.kill(path), + DropFlagState::Present => trans.gen(path), + } + } +} + +impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { + fn update_bits( + trans: &mut impl GenKill, + path: MovePathIndex, + state: DropFlagState, + ) { + match state { + DropFlagState::Absent => trans.gen(path), + DropFlagState::Present => trans.kill(path), + } + } +} + +impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> { + fn update_bits( + trans: &mut impl GenKill, + path: MovePathIndex, + state: DropFlagState, + ) { + match state { + DropFlagState::Absent => trans.kill(path), + DropFlagState::Present => trans.gen(path), + } + } +} + +impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { + type Domain = MaybeReachable>; + const NAME: &'static str = "maybe_init"; + + fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { + // bottom = uninitialized + MaybeReachable::Unreachable + } + + fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { + *state = + MaybeReachable::Reachable(ChunkedBitSet::new_empty(self.move_data().move_paths.len())); + drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| { + assert!(s == DropFlagState::Present); + state.gen(path); + }); + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { + type Idx = MovePathIndex; + + fn domain_size(&self, _: &Body<'tcx>) -> usize { + self.move_data().move_paths.len() + } + + fn statement_effect( + &mut self, + trans: &mut impl GenKill, + statement: &mir::Statement<'tcx>, + location: Location, + ) { + drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { + Self::update_bits(trans, path, s) + }); + + // Mark all places as "maybe init" if they are mutably borrowed. See #90752. + if self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration + && let Some((_, rvalue)) = statement.kind.as_assign() + && let mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place) + // FIXME: Does `&raw const foo` allow mutation? See #90413. + | mir::Rvalue::AddressOf(_, place) = rvalue + && let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) + { + on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| { + trans.gen(child); + }) + } + } + + fn terminator_effect<'mir>( + &mut self, + state: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + let mut edges = terminator.edges(); + if self.skip_unreachable_unwind + && let mir::TerminatorKind::Drop { target, unwind, place, replace: _ } = terminator.kind + && matches!(unwind, mir::UnwindAction::Cleanup(_)) + && self.is_unwind_dead(place, state) + { + edges = TerminatorEdges::Single(target); + } + drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { + Self::update_bits(state, path, s) + }); + edges + } + + fn call_return_effect( + &mut self, + trans: &mut impl GenKill, + _block: mir::BasicBlock, + return_places: CallReturnPlaces<'_, 'tcx>, + ) { + return_places.for_each(|place| { + // when a call returns successfully, that means we need to set + // the bits for that dest_place to 1 (initialized). + on_lookup_result_bits( + self.tcx, + self.body, + self.move_data(), + self.move_data().rev_lookup.find(place.as_ref()), + |mpi| { + trans.gen(mpi); + }, + ); + }); + } + + fn switch_int_edge_effects>( + &mut self, + block: mir::BasicBlock, + discr: &mir::Operand<'tcx>, + edge_effects: &mut impl SwitchIntEdgeEffects, + ) { + if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { + return; + } + + let enum_ = discr.place().and_then(|discr| { + switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr) + }); + + let Some((enum_place, enum_def)) = enum_ else { + return; + }; + + let mut discriminants = enum_def.discriminants(self.tcx); + edge_effects.apply(|trans, edge| { + let Some(value) = edge.value else { + return; + }; + + // MIR building adds discriminants to the `values` array in the same order as they + // are yielded by `AdtDef::discriminants`. We rely on this to match each + // discriminant in `values` to its corresponding variant in linear time. + let (variant, _) = discriminants + .find(|&(_, discr)| discr.val == value) + .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`"); + + // Kill all move paths that correspond to variants we know to be inactive along this + // particular outgoing edge of a `SwitchInt`. + drop_flag_effects::on_all_inactive_variants( + self.tcx, + self.body, + self.move_data(), + enum_place, + variant, + |mpi| trans.kill(mpi), + ); + }); + } +} + +impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { + type Domain = ChunkedBitSet; + + const NAME: &'static str = "maybe_uninit"; + + fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { + // bottom = initialized (start_block_effect counters this at outset) + ChunkedBitSet::new_empty(self.move_data().move_paths.len()) + } + + // sets on_entry bits for Arg places + fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { + // set all bits to 1 (uninit) before gathering counter-evidence + state.insert_all(); + + drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| { + assert!(s == DropFlagState::Present); + state.remove(path); + }); + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { + type Idx = MovePathIndex; + + fn domain_size(&self, _: &Body<'tcx>) -> usize { + self.move_data().move_paths.len() + } + + fn statement_effect( + &mut self, + trans: &mut impl GenKill, + _statement: &mir::Statement<'tcx>, + location: Location, + ) { + drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { + Self::update_bits(trans, path, s) + }); + + // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a + // mutable borrow occurs. Places cannot become uninitialized through a mutable reference. + } + + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { + Self::update_bits(trans, path, s) + }); + if self.skip_unreachable_unwind.contains(location.block) { + let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() }; + assert!(matches!(unwind, mir::UnwindAction::Cleanup(_))); + TerminatorEdges::Single(target) + } else { + terminator.edges() + } + } + + fn call_return_effect( + &mut self, + trans: &mut impl GenKill, + _block: mir::BasicBlock, + return_places: CallReturnPlaces<'_, 'tcx>, + ) { + return_places.for_each(|place| { + // when a call returns successfully, that means we need to set + // the bits for that dest_place to 0 (initialized). + on_lookup_result_bits( + self.tcx, + self.body, + self.move_data(), + self.move_data().rev_lookup.find(place.as_ref()), + |mpi| { + trans.kill(mpi); + }, + ); + }); + } + + fn switch_int_edge_effects>( + &mut self, + block: mir::BasicBlock, + discr: &mir::Operand<'tcx>, + edge_effects: &mut impl SwitchIntEdgeEffects, + ) { + if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { + return; + } + + if !self.mark_inactive_variants_as_uninit { + return; + } + + let enum_ = discr.place().and_then(|discr| { + switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr) + }); + + let Some((enum_place, enum_def)) = enum_ else { + return; + }; + + let mut discriminants = enum_def.discriminants(self.tcx); + edge_effects.apply(|trans, edge| { + let Some(value) = edge.value else { + return; + }; + + // MIR building adds discriminants to the `values` array in the same order as they + // are yielded by `AdtDef::discriminants`. We rely on this to match each + // discriminant in `values` to its corresponding variant in linear time. + let (variant, _) = discriminants + .find(|&(_, discr)| discr.val == value) + .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`"); + + // Mark all move paths that correspond to variants other than this one as maybe + // uninitialized (in reality, they are *definitely* uninitialized). + drop_flag_effects::on_all_inactive_variants( + self.tcx, + self.body, + self.move_data(), + enum_place, + variant, + |mpi| trans.gen(mpi), + ); + }); + } +} + +impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> { + /// Use set intersection as the join operator. + type Domain = lattice::Dual>; + + const NAME: &'static str = "definite_init"; + + fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { + // bottom = initialized (start_block_effect counters this at outset) + lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len())) + } + + // sets on_entry bits for Arg places + fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { + state.0.clear(); + + drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| { + assert!(s == DropFlagState::Present); + state.0.insert(path); + }); + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> { + type Idx = MovePathIndex; + + fn domain_size(&self, _: &Body<'tcx>) -> usize { + self.move_data().move_paths.len() + } + + fn statement_effect( + &mut self, + trans: &mut impl GenKill, + _statement: &mir::Statement<'tcx>, + location: Location, + ) { + drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { + Self::update_bits(trans, path, s) + }) + } + + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { + Self::update_bits(trans, path, s) + }); + terminator.edges() + } + + fn call_return_effect( + &mut self, + trans: &mut impl GenKill, + _block: mir::BasicBlock, + return_places: CallReturnPlaces<'_, 'tcx>, + ) { + return_places.for_each(|place| { + // when a call returns successfully, that means we need to set + // the bits for that dest_place to 1 (initialized). + on_lookup_result_bits( + self.tcx, + self.body, + self.move_data(), + self.move_data().rev_lookup.find(place.as_ref()), + |mpi| { + trans.gen(mpi); + }, + ); + }); + } +} + +impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> { + type Domain = ChunkedBitSet; + + const NAME: &'static str = "ever_init"; + + fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { + // bottom = no initialized variables by default + ChunkedBitSet::new_empty(self.move_data().inits.len()) + } + + fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { + for arg_init in 0..body.arg_count { + state.insert(InitIndex::new(arg_init)); + } + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { + type Idx = InitIndex; + + fn domain_size(&self, _: &Body<'tcx>) -> usize { + self.move_data().inits.len() + } + + #[instrument(skip(self, trans), level = "debug")] + fn statement_effect( + &mut self, + trans: &mut impl GenKill, + stmt: &mir::Statement<'tcx>, + location: Location, + ) { + let move_data = self.move_data(); + let init_path_map = &move_data.init_path_map; + let init_loc_map = &move_data.init_loc_map; + let rev_lookup = &move_data.rev_lookup; + + debug!("initializes move_indexes {:?}", &init_loc_map[location]); + trans.gen_all(init_loc_map[location].iter().copied()); + + if let mir::StatementKind::StorageDead(local) = stmt.kind { + // End inits for StorageDead, so that an immutable variable can + // be reinitialized on the next iteration of the loop. + let move_path_index = rev_lookup.find_local(local); + debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]); + trans.kill_all(init_path_map[move_path_index].iter().copied()); + } + } + + #[instrument(skip(self, trans, terminator), level = "debug")] + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + let (body, move_data) = (self.body, self.move_data()); + let term = body[location.block].terminator(); + let init_loc_map = &move_data.init_loc_map; + debug!(?term); + debug!("initializes move_indexes {:?}", init_loc_map[location]); + trans.gen_all( + init_loc_map[location] + .iter() + .filter(|init_index| { + move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly + }) + .copied(), + ); + terminator.edges() + } + + fn call_return_effect( + &mut self, + trans: &mut impl GenKill, + block: mir::BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + let move_data = self.move_data(); + let init_loc_map = &move_data.init_loc_map; + + let call_loc = self.body.terminator_loc(block); + for init_index in &init_loc_map[call_loc] { + trans.gen(*init_index); + } + } +} + +/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is +/// an enum discriminant. +/// +/// We expect such blocks to have a call to `discriminant` as their last statement like so: +/// +/// ```text +/// ... +/// _42 = discriminant(_1) +/// SwitchInt(_42, ..) +/// ``` +/// +/// If the basic block matches this pattern, this function returns the place corresponding to the +/// enum (`_1` in the example above) as well as the `AdtDef` of that enum. +fn switch_on_enum_discriminant<'mir, 'tcx>( + tcx: TyCtxt<'tcx>, + body: &'mir mir::Body<'tcx>, + block: &'mir mir::BasicBlockData<'tcx>, + switch_on: mir::Place<'tcx>, +) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> { + for statement in block.statements.iter().rev() { + match &statement.kind { + mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated))) + if *lhs == switch_on => + { + match discriminated.ty(body, tcx).ty.kind() { + ty::Adt(def, _) => return Some((*discriminated, *def)), + + // `Rvalue::Discriminant` is also used to get the active yield point for a + // generator, but we do not need edge-specific effects in that case. This may + // change in the future. + ty::Generator(..) => return None, + + t => bug!("`discriminant` called on unexpected type {:?}", t), + } + } + mir::StatementKind::Coverage(_) => continue, + _ => return None, + } + } + None +} diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index aeca0073304ea..5aa73c7a90699 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -1,8 +1,10 @@ use rustc_index::bit_set::{BitSet, ChunkedBitSet}; use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor}; -use rustc_middle::mir::{self, Local, Location, Place, StatementKind}; +use rustc_middle::mir::{ + self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges, +}; -use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKillAnalysis}; +use crate::{Analysis, AnalysisDomain, Backward, GenKill, GenKillAnalysis}; /// A [live-variable dataflow analysis][liveness]. /// @@ -21,6 +23,7 @@ use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKi /// [`MaybeBorrowedLocals`]: super::MaybeBorrowedLocals /// [flow-test]: https://github.com/rust-lang/rust/blob/a08c47310c7d49cbdc5d7afb38408ba519967ecd/src/test/ui/mir-dataflow/liveness-ptr.rs /// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis +#[derive(Clone, Copy)] pub struct MaybeLiveLocals; impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals { @@ -42,8 +45,12 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals { impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals { type Idx = Local; + fn domain_size(&self, body: &mir::Body<'tcx>) -> usize { + body.local_decls.len() + } + fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, statement: &mir::Statement<'tcx>, location: Location, @@ -51,43 +58,39 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals { TransferFunction(trans).visit_statement(statement, location); } - fn terminator_effect( - &self, - trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { TransferFunction(trans).visit_terminator(terminator, location); + terminator.edges() } fn call_return_effect( - &self, + &mut self, trans: &mut impl GenKill, _block: mir::BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ) { - return_places.for_each(|place| { - if let Some(local) = place.as_local() { - trans.kill(local); - } - }); - } - - fn yield_resume_effect( - &self, - trans: &mut impl GenKill, - _resume_block: mir::BasicBlock, - resume_place: mir::Place<'tcx>, - ) { - YieldResumeEffect(trans).visit_place( - &resume_place, - PlaceContext::MutatingUse(MutatingUseContext::Yield), - Location::START, - ) + if let CallReturnPlaces::Yield(resume_place) = return_places { + YieldResumeEffect(trans).visit_place( + &resume_place, + PlaceContext::MutatingUse(MutatingUseContext::Yield), + Location::START, + ) + } else { + return_places.for_each(|place| { + if let Some(local) = place.as_local() { + trans.kill(local); + } + }); + } } } -struct TransferFunction<'a, T>(&'a mut T); +pub struct TransferFunction<'a, T>(pub &'a mut T); impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, T> where @@ -96,7 +99,7 @@ where fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) { if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context { // The resume place is evaluated and assigned to only after generator resumes, so its - // effect is handled separately in `yield_resume_effect`. + // effect is handled separately in `call_resume_effect`. return; } @@ -199,8 +202,7 @@ impl DefUse { | NonMutatingUseContext::Move | NonMutatingUseContext::PlaceMention | NonMutatingUseContext::ShallowBorrow - | NonMutatingUseContext::SharedBorrow - | NonMutatingUseContext::UniqueBorrow, + | NonMutatingUseContext::SharedBorrow, ) => Some(DefUse::Use), PlaceContext::MutatingUse(MutatingUseContext::Projection) @@ -216,6 +218,7 @@ impl DefUse { /// This is basically written for dead store elimination and nothing else. /// /// All of the caveats of `MaybeLiveLocals` apply. +#[derive(Clone, Copy)] pub struct MaybeTransitiveLiveLocals<'a> { always_live: &'a BitSet, } @@ -248,7 +251,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for MaybeTransitiveLiveLocals<'a> { impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { fn apply_statement_effect( - &self, + &mut self, trans: &mut Self::Domain, statement: &mir::Statement<'tcx>, location: Location, @@ -282,38 +285,34 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { TransferFunction(trans).visit_statement(statement, location); } - fn apply_terminator_effect( - &self, + fn apply_terminator_effect<'mir>( + &mut self, trans: &mut Self::Domain, - terminator: &mir::Terminator<'tcx>, + terminator: &'mir mir::Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { TransferFunction(trans).visit_terminator(terminator, location); + terminator.edges() } fn apply_call_return_effect( - &self, + &mut self, trans: &mut Self::Domain, _block: mir::BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ) { - return_places.for_each(|place| { - if let Some(local) = place.as_local() { - trans.remove(local); - } - }); - } - - fn apply_yield_resume_effect( - &self, - trans: &mut Self::Domain, - _resume_block: mir::BasicBlock, - resume_place: mir::Place<'tcx>, - ) { - YieldResumeEffect(trans).visit_place( - &resume_place, - PlaceContext::MutatingUse(MutatingUseContext::Yield), - Location::START, - ) + if let CallReturnPlaces::Yield(resume_place) = return_places { + YieldResumeEffect(trans).visit_place( + &resume_place, + PlaceContext::MutatingUse(MutatingUseContext::Yield), + Location::START, + ) + } else { + return_places.for_each(|place| { + if let Some(local) = place.as_local() { + trans.remove(local); + } + }); + } } } diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs index 171db6965ac18..f8db18fc1f8d1 100644 --- a/compiler/rustc_mir_dataflow/src/impls/mod.rs +++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs @@ -2,763 +2,18 @@ //! bitvectors attached to each basic block, represented via a //! zero-sized structure. -use rustc_index::bit_set::{BitSet, ChunkedBitSet}; -use rustc_index::Idx; -use rustc_middle::mir::visit::{MirVisitable, Visitor}; -use rustc_middle::mir::{self, Body, Location}; -use rustc_middle::ty::{self, TyCtxt}; - -use crate::drop_flag_effects_for_function_entry; -use crate::drop_flag_effects_for_location; -use crate::elaborate_drops::DropFlagState; -use crate::framework::{CallReturnPlaces, SwitchIntEdgeEffects}; -use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex}; -use crate::on_lookup_result_bits; -use crate::MoveDataParamEnv; -use crate::{drop_flag_effects, on_all_children_bits}; -use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis}; - mod borrowed_locals; +mod initialized; mod liveness; mod storage_liveness; pub use self::borrowed_locals::borrowed_locals; pub use self::borrowed_locals::MaybeBorrowedLocals; +pub use self::initialized::{ + DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeInitializedPlaces, + MaybeUninitializedPlaces, +}; pub use self::liveness::MaybeLiveLocals; pub use self::liveness::MaybeTransitiveLiveLocals; +pub use self::liveness::TransferFunction as LivenessTransferFunction; pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive}; - -/// `MaybeInitializedPlaces` tracks all places that might be -/// initialized upon reaching a particular point in the control flow -/// for a function. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // maybe-init: -/// // {} -/// let a = S; let mut b = S; let c; let d; // {a, b} -/// -/// if pred { -/// drop(a); // { b} -/// b = S; // { b} -/// -/// } else { -/// drop(b); // {a} -/// d = S; // {a, d} -/// -/// } // {a, b, d} -/// -/// c = S; // {a, b, c, d} -/// } -/// ``` -/// -/// To determine whether a place *must* be initialized at a -/// particular control-flow point, one can take the set-difference -/// between this data and the data from `MaybeUninitializedPlaces` at the -/// corresponding control-flow point. -/// -/// Similarly, at a given `drop` statement, the set-intersection -/// between this data and `MaybeUninitializedPlaces` yields the set of -/// places that would require a dynamic drop-flag at that statement. -pub struct MaybeInitializedPlaces<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - mdpe: &'a MoveDataParamEnv<'tcx>, -} - -impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { - MaybeInitializedPlaces { tcx, body, mdpe } - } -} - -impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - &self.mdpe.move_data - } -} - -/// `MaybeUninitializedPlaces` tracks all places that might be -/// uninitialized upon reaching a particular point in the control flow -/// for a function. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // maybe-uninit: -/// // {a, b, c, d} -/// let a = S; let mut b = S; let c; let d; // { c, d} -/// -/// if pred { -/// drop(a); // {a, c, d} -/// b = S; // {a, c, d} -/// -/// } else { -/// drop(b); // { b, c, d} -/// d = S; // { b, c } -/// -/// } // {a, b, c, d} -/// -/// c = S; // {a, b, d} -/// } -/// ``` -/// -/// To determine whether a place *must* be uninitialized at a -/// particular control-flow point, one can take the set-difference -/// between this data and the data from `MaybeInitializedPlaces` at the -/// corresponding control-flow point. -/// -/// Similarly, at a given `drop` statement, the set-intersection -/// between this data and `MaybeInitializedPlaces` yields the set of -/// places that would require a dynamic drop-flag at that statement. -pub struct MaybeUninitializedPlaces<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - mdpe: &'a MoveDataParamEnv<'tcx>, - - mark_inactive_variants_as_uninit: bool, -} - -impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { - MaybeUninitializedPlaces { tcx, body, mdpe, mark_inactive_variants_as_uninit: false } - } - - /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an - /// enum discriminant. - /// - /// This is correct in a vacuum but is not the default because it causes problems in the borrow - /// checker, where this information gets propagated along `FakeEdge`s. - pub fn mark_inactive_variants_as_uninit(mut self) -> Self { - self.mark_inactive_variants_as_uninit = true; - self - } -} - -impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - &self.mdpe.move_data - } -} - -/// `DefinitelyInitializedPlaces` tracks all places that are definitely -/// initialized upon reaching a particular point in the control flow -/// for a function. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // definite-init: -/// // { } -/// let a = S; let mut b = S; let c; let d; // {a, b } -/// -/// if pred { -/// drop(a); // { b, } -/// b = S; // { b, } -/// -/// } else { -/// drop(b); // {a, } -/// d = S; // {a, d} -/// -/// } // { } -/// -/// c = S; // { c } -/// } -/// ``` -/// -/// To determine whether a place *may* be uninitialized at a -/// particular control-flow point, one can take the set-complement -/// of this data. -/// -/// Similarly, at a given `drop` statement, the set-difference between -/// this data and `MaybeInitializedPlaces` yields the set of places -/// that would require a dynamic drop-flag at that statement. -pub struct DefinitelyInitializedPlaces<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - mdpe: &'a MoveDataParamEnv<'tcx>, -} - -impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { - DefinitelyInitializedPlaces { tcx, body, mdpe } - } -} - -impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - &self.mdpe.move_data - } -} - -/// `EverInitializedPlaces` tracks all places that might have ever been -/// initialized upon reaching a particular point in the control flow -/// for a function, without an intervening `StorageDead`. -/// -/// This dataflow is used to determine if an immutable local variable may -/// be assigned to. -/// -/// For example, in code like the following, we have corresponding -/// dataflow information shown in the right-hand comments. -/// -/// ```rust -/// struct S; -/// fn foo(pred: bool) { // ever-init: -/// // { } -/// let a = S; let mut b = S; let c; let d; // {a, b } -/// -/// if pred { -/// drop(a); // {a, b, } -/// b = S; // {a, b, } -/// -/// } else { -/// drop(b); // {a, b, } -/// d = S; // {a, b, d } -/// -/// } // {a, b, d } -/// -/// c = S; // {a, b, c, d } -/// } -/// ``` -pub struct EverInitializedPlaces<'a, 'tcx> { - #[allow(dead_code)] - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - mdpe: &'a MoveDataParamEnv<'tcx>, -} - -impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> { - pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self { - EverInitializedPlaces { tcx, body, mdpe } - } -} - -impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> { - fn move_data(&self) -> &MoveData<'tcx> { - &self.mdpe.move_data - } -} - -impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> { - fn update_bits( - trans: &mut impl GenKill, - path: MovePathIndex, - state: DropFlagState, - ) { - match state { - DropFlagState::Absent => trans.kill(path), - DropFlagState::Present => trans.gen(path), - } - } -} - -impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> { - fn update_bits( - trans: &mut impl GenKill, - path: MovePathIndex, - state: DropFlagState, - ) { - match state { - DropFlagState::Absent => trans.gen(path), - DropFlagState::Present => trans.kill(path), - } - } -} - -impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> { - fn update_bits( - trans: &mut impl GenKill, - path: MovePathIndex, - state: DropFlagState, - ) { - match state { - DropFlagState::Absent => trans.kill(path), - DropFlagState::Present => trans.gen(path), - } - } -} - -impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { - type Domain = ChunkedBitSet; - const NAME: &'static str = "maybe_init"; - - fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { - // bottom = uninitialized - ChunkedBitSet::new_empty(self.move_data().move_paths.len()) - } - - fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { - drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| { - assert!(s == DropFlagState::Present); - state.insert(path); - }); - } -} - -impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> { - type Idx = MovePathIndex; - - fn statement_effect( - &self, - trans: &mut impl GenKill, - statement: &mir::Statement<'tcx>, - location: Location, - ) { - drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { - Self::update_bits(trans, path, s) - }); - - if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { - return; - } - - // Mark all places as "maybe init" if they are mutably borrowed. See #90752. - for_each_mut_borrow(statement, location, |place| { - let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else { return }; - on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| { - trans.gen(child); - }) - }) - } - - fn terminator_effect( - &self, - trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, - location: Location, - ) { - drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { - Self::update_bits(trans, path, s) - }); - - if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { - return; - } - - for_each_mut_borrow(terminator, location, |place| { - let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else { return }; - on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| { - trans.gen(child); - }) - }) - } - - fn call_return_effect( - &self, - trans: &mut impl GenKill, - _block: mir::BasicBlock, - return_places: CallReturnPlaces<'_, 'tcx>, - ) { - return_places.for_each(|place| { - // when a call returns successfully, that means we need to set - // the bits for that dest_place to 1 (initialized). - on_lookup_result_bits( - self.tcx, - self.body, - self.move_data(), - self.move_data().rev_lookup.find(place.as_ref()), - |mpi| { - trans.gen(mpi); - }, - ); - }); - } - - fn switch_int_edge_effects>( - &self, - block: mir::BasicBlock, - discr: &mir::Operand<'tcx>, - edge_effects: &mut impl SwitchIntEdgeEffects, - ) { - if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { - return; - } - - let enum_ = discr.place().and_then(|discr| { - switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr) - }); - - let Some((enum_place, enum_def)) = enum_ else { - return; - }; - - let mut discriminants = enum_def.discriminants(self.tcx); - edge_effects.apply(|trans, edge| { - let Some(value) = edge.value else { - return; - }; - - // MIR building adds discriminants to the `values` array in the same order as they - // are yielded by `AdtDef::discriminants`. We rely on this to match each - // discriminant in `values` to its corresponding variant in linear time. - let (variant, _) = discriminants - .find(|&(_, discr)| discr.val == value) - .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`"); - - // Kill all move paths that correspond to variants we know to be inactive along this - // particular outgoing edge of a `SwitchInt`. - drop_flag_effects::on_all_inactive_variants( - self.tcx, - self.body, - self.move_data(), - enum_place, - variant, - |mpi| trans.kill(mpi), - ); - }); - } -} - -impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { - type Domain = ChunkedBitSet; - - const NAME: &'static str = "maybe_uninit"; - - fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { - // bottom = initialized (start_block_effect counters this at outset) - ChunkedBitSet::new_empty(self.move_data().move_paths.len()) - } - - // sets on_entry bits for Arg places - fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { - // set all bits to 1 (uninit) before gathering counter-evidence - state.insert_all(); - - drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| { - assert!(s == DropFlagState::Present); - state.remove(path); - }); - } -} - -impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> { - type Idx = MovePathIndex; - - fn statement_effect( - &self, - trans: &mut impl GenKill, - _statement: &mir::Statement<'tcx>, - location: Location, - ) { - drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { - Self::update_bits(trans, path, s) - }); - - // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a - // mutable borrow occurs. Places cannot become uninitialized through a mutable reference. - } - - fn terminator_effect( - &self, - trans: &mut impl GenKill, - _terminator: &mir::Terminator<'tcx>, - location: Location, - ) { - drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { - Self::update_bits(trans, path, s) - }); - } - - fn call_return_effect( - &self, - trans: &mut impl GenKill, - _block: mir::BasicBlock, - return_places: CallReturnPlaces<'_, 'tcx>, - ) { - return_places.for_each(|place| { - // when a call returns successfully, that means we need to set - // the bits for that dest_place to 0 (initialized). - on_lookup_result_bits( - self.tcx, - self.body, - self.move_data(), - self.move_data().rev_lookup.find(place.as_ref()), - |mpi| { - trans.kill(mpi); - }, - ); - }); - } - - fn switch_int_edge_effects>( - &self, - block: mir::BasicBlock, - discr: &mir::Operand<'tcx>, - edge_effects: &mut impl SwitchIntEdgeEffects, - ) { - if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration { - return; - } - - if !self.mark_inactive_variants_as_uninit { - return; - } - - let enum_ = discr.place().and_then(|discr| { - switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr) - }); - - let Some((enum_place, enum_def)) = enum_ else { - return; - }; - - let mut discriminants = enum_def.discriminants(self.tcx); - edge_effects.apply(|trans, edge| { - let Some(value) = edge.value else { - return; - }; - - // MIR building adds discriminants to the `values` array in the same order as they - // are yielded by `AdtDef::discriminants`. We rely on this to match each - // discriminant in `values` to its corresponding variant in linear time. - let (variant, _) = discriminants - .find(|&(_, discr)| discr.val == value) - .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`"); - - // Mark all move paths that correspond to variants other than this one as maybe - // uninitialized (in reality, they are *definitely* uninitialized). - drop_flag_effects::on_all_inactive_variants( - self.tcx, - self.body, - self.move_data(), - enum_place, - variant, - |mpi| trans.gen(mpi), - ); - }); - } -} - -impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> { - /// Use set intersection as the join operator. - type Domain = lattice::Dual>; - - const NAME: &'static str = "definite_init"; - - fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { - // bottom = initialized (start_block_effect counters this at outset) - lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len())) - } - - // sets on_entry bits for Arg places - fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) { - state.0.clear(); - - drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| { - assert!(s == DropFlagState::Present); - state.0.insert(path); - }); - } -} - -impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> { - type Idx = MovePathIndex; - - fn statement_effect( - &self, - trans: &mut impl GenKill, - _statement: &mir::Statement<'tcx>, - location: Location, - ) { - drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { - Self::update_bits(trans, path, s) - }) - } - - fn terminator_effect( - &self, - trans: &mut impl GenKill, - _terminator: &mir::Terminator<'tcx>, - location: Location, - ) { - drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| { - Self::update_bits(trans, path, s) - }) - } - - fn call_return_effect( - &self, - trans: &mut impl GenKill, - _block: mir::BasicBlock, - return_places: CallReturnPlaces<'_, 'tcx>, - ) { - return_places.for_each(|place| { - // when a call returns successfully, that means we need to set - // the bits for that dest_place to 1 (initialized). - on_lookup_result_bits( - self.tcx, - self.body, - self.move_data(), - self.move_data().rev_lookup.find(place.as_ref()), - |mpi| { - trans.gen(mpi); - }, - ); - }); - } -} - -impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> { - type Domain = ChunkedBitSet; - - const NAME: &'static str = "ever_init"; - - fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain { - // bottom = no initialized variables by default - ChunkedBitSet::new_empty(self.move_data().inits.len()) - } - - fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { - for arg_init in 0..body.arg_count { - state.insert(InitIndex::new(arg_init)); - } - } -} - -impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> { - type Idx = InitIndex; - - #[instrument(skip(self, trans), level = "debug")] - fn statement_effect( - &self, - trans: &mut impl GenKill, - stmt: &mir::Statement<'tcx>, - location: Location, - ) { - let move_data = self.move_data(); - let init_path_map = &move_data.init_path_map; - let init_loc_map = &move_data.init_loc_map; - let rev_lookup = &move_data.rev_lookup; - - debug!("initializes move_indexes {:?}", &init_loc_map[location]); - trans.gen_all(init_loc_map[location].iter().copied()); - - if let mir::StatementKind::StorageDead(local) = stmt.kind { - // End inits for StorageDead, so that an immutable variable can - // be reinitialized on the next iteration of the loop. - let move_path_index = rev_lookup.find_local(local); - debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]); - trans.kill_all(init_path_map[move_path_index].iter().copied()); - } - } - - #[instrument(skip(self, trans, _terminator), level = "debug")] - fn terminator_effect( - &self, - trans: &mut impl GenKill, - _terminator: &mir::Terminator<'tcx>, - location: Location, - ) { - let (body, move_data) = (self.body, self.move_data()); - let term = body[location.block].terminator(); - let init_loc_map = &move_data.init_loc_map; - debug!(?term); - debug!("initializes move_indexes {:?}", init_loc_map[location]); - trans.gen_all( - init_loc_map[location] - .iter() - .filter(|init_index| { - move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly - }) - .copied(), - ); - } - - fn call_return_effect( - &self, - trans: &mut impl GenKill, - block: mir::BasicBlock, - _return_places: CallReturnPlaces<'_, 'tcx>, - ) { - let move_data = self.move_data(); - let init_loc_map = &move_data.init_loc_map; - - let call_loc = self.body.terminator_loc(block); - for init_index in &init_loc_map[call_loc] { - trans.gen(*init_index); - } - } -} - -/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is -/// an enum discriminant. -/// -/// We expect such blocks to have a call to `discriminant` as their last statement like so: -/// -/// ```text -/// ... -/// _42 = discriminant(_1) -/// SwitchInt(_42, ..) -/// ``` -/// -/// If the basic block matches this pattern, this function returns the place corresponding to the -/// enum (`_1` in the example above) as well as the `AdtDef` of that enum. -fn switch_on_enum_discriminant<'mir, 'tcx>( - tcx: TyCtxt<'tcx>, - body: &'mir mir::Body<'tcx>, - block: &'mir mir::BasicBlockData<'tcx>, - switch_on: mir::Place<'tcx>, -) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> { - for statement in block.statements.iter().rev() { - match &statement.kind { - mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated))) - if *lhs == switch_on => - { - match discriminated.ty(body, tcx).ty.kind() { - ty::Adt(def, _) => return Some((*discriminated, *def)), - - // `Rvalue::Discriminant` is also used to get the active yield point for a - // generator, but we do not need edge-specific effects in that case. This may - // change in the future. - ty::Generator(..) => return None, - - t => bug!("`discriminant` called on unexpected type {:?}", t), - } - } - mir::StatementKind::Coverage(_) => continue, - _ => return None, - } - } - None -} - -struct OnMutBorrow(F); - -impl Visitor<'_> for OnMutBorrow -where - F: FnMut(&mir::Place<'_>), -{ - fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'_>, location: Location) { - // FIXME: Does `&raw const foo` allow mutation? See #90413. - match rvalue { - mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place) - | mir::Rvalue::AddressOf(_, place) => (self.0)(place), - - _ => {} - } - - self.super_rvalue(rvalue, location) - } -} - -/// Calls `f` for each mutable borrow or raw reference in the program. -/// -/// This DOES NOT call `f` for a shared borrow of a type with interior mutability. That's okay for -/// initializedness, because we cannot move from an `UnsafeCell` (outside of `core::cell`), but -/// other analyses will likely need to check for `!Freeze`. -fn for_each_mut_borrow<'tcx>( - mir: &impl MirVisitable<'tcx>, - location: Location, - f: impl FnMut(&mir::Place<'_>), -) { - let mut vis = OnMutBorrow(f); - - mir.apply(location, &mut vis); -} diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs index 463ce083a64fd..bea23b7f7ae1f 100644 --- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs @@ -1,10 +1,11 @@ -pub use super::*; - -use crate::{CallReturnPlaces, GenKill, Results, ResultsRefCursor}; +use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; + use std::borrow::Cow; -use std::cell::RefCell; + +use super::MaybeBorrowedLocals; +use crate::{GenKill, ResultsClonedCursor}; #[derive(Clone)] pub struct MaybeStorageLive<'a> { @@ -17,17 +18,23 @@ impl<'a> MaybeStorageLive<'a> { } } +impl crate::CloneAnalysis for MaybeStorageLive<'_> { + fn clone_analysis(&self) -> Self { + self.clone() + } +} + impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> { type Domain = BitSet; const NAME: &'static str = "maybe_storage_live"; - fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { + fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain { // bottom = dead BitSet::new_empty(body.local_decls.len()) } - fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) { + fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) { assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size()); for local in self.always_live_locals.iter() { on_entry.insert(local); @@ -42,10 +49,14 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> { impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> { type Idx = Local; + fn domain_size(&self, body: &Body<'tcx>) -> usize { + body.local_decls.len() + } + fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, - stmt: &mir::Statement<'tcx>, + stmt: &Statement<'tcx>, _: Location, ) { match stmt.kind { @@ -55,17 +66,18 @@ impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> { } } - fn terminator_effect( - &self, - _trans: &mut impl GenKill, - _: &mir::Terminator<'tcx>, + fn terminator_effect<'mir>( + &mut self, + _trans: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, _: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { // Terminators have no effect + terminator.edges() } fn call_return_effect( - &self, + &mut self, _trans: &mut impl GenKill, _block: BasicBlock, _return_places: CallReturnPlaces<'_, 'tcx>, @@ -90,12 +102,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead { const NAME: &'static str = "maybe_storage_dead"; - fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { + fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain { // bottom = live BitSet::new_empty(body.local_decls.len()) } - fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) { + fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) { assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size()); // Do not iterate on return place and args, as they are trivially always live. for local in body.vars_and_temps_iter() { @@ -109,10 +121,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead { impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead { type Idx = Local; + fn domain_size(&self, body: &Body<'tcx>) -> usize { + body.local_decls.len() + } + fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, - stmt: &mir::Statement<'tcx>, + stmt: &Statement<'tcx>, _: Location, ) { match stmt.kind { @@ -122,17 +138,18 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead { } } - fn terminator_effect( - &self, - _trans: &mut impl GenKill, - _: &mir::Terminator<'tcx>, + fn terminator_effect<'mir>( + &mut self, + _: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, _: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { // Terminators have no effect + terminator.edges() } fn call_return_effect( - &self, + &mut self, _trans: &mut impl GenKill, _block: BasicBlock, _return_places: CallReturnPlaces<'_, 'tcx>, @@ -141,38 +158,38 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead { } } -type BorrowedLocalsResults<'a, 'tcx> = ResultsRefCursor<'a, 'a, 'tcx, MaybeBorrowedLocals>; +type BorrowedLocalsResults<'res, 'mir, 'tcx> = + ResultsClonedCursor<'res, 'mir, 'tcx, MaybeBorrowedLocals>; /// Dataflow analysis that determines whether each local requires storage at a /// given location; i.e. whether its storage can go away without being observed. -pub struct MaybeRequiresStorage<'mir, 'tcx> { - body: &'mir Body<'tcx>, - borrowed_locals: RefCell>, +pub struct MaybeRequiresStorage<'res, 'mir, 'tcx> { + borrowed_locals: BorrowedLocalsResults<'res, 'mir, 'tcx>, } -impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> { - pub fn new( - body: &'mir Body<'tcx>, - borrowed_locals: &'mir Results<'tcx, MaybeBorrowedLocals>, - ) -> Self { - MaybeRequiresStorage { - body, - borrowed_locals: RefCell::new(ResultsRefCursor::new(&body, borrowed_locals)), - } +impl<'res, 'mir, 'tcx> MaybeRequiresStorage<'res, 'mir, 'tcx> { + pub fn new(borrowed_locals: BorrowedLocalsResults<'res, 'mir, 'tcx>) -> Self { + MaybeRequiresStorage { borrowed_locals } } } -impl<'mir, 'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, 'tcx> { +impl crate::CloneAnalysis for MaybeRequiresStorage<'_, '_, '_> { + fn clone_analysis(&self) -> Self { + Self { borrowed_locals: self.borrowed_locals.new_cursor() } + } +} + +impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> { type Domain = BitSet; const NAME: &'static str = "requires_storage"; - fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { + fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain { // bottom = dead BitSet::new_empty(body.local_decls.len()) } - fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) { + fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) { // The resume argument is live on function entry (we don't care about // the `self` argument) for arg in body.args_iter().skip(1) { @@ -181,17 +198,21 @@ impl<'mir, 'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, 'tcx } } -impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tcx> { +impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> { type Idx = Local; + fn domain_size(&self, body: &Body<'tcx>) -> usize { + body.local_decls.len() + } + fn before_statement_effect( - &self, + &mut self, trans: &mut impl GenKill, - stmt: &mir::Statement<'tcx>, + stmt: &Statement<'tcx>, loc: Location, ) { // If a place is borrowed in a statement, it needs storage for that statement. - self.borrowed_locals.borrow().analysis().statement_effect(trans, stmt, loc); + self.borrowed_locals.mut_analysis().statement_effect(trans, stmt, loc); match &stmt.kind { StatementKind::StorageDead(l) => trans.kill(*l), @@ -218,9 +239,9 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc } fn statement_effect( - &self, + &mut self, trans: &mut impl GenKill, - _: &mir::Statement<'tcx>, + _: &Statement<'tcx>, loc: Location, ) { // If we move from a place then it only stops needing storage *after* @@ -229,13 +250,16 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc } fn before_terminator_effect( - &self, + &mut self, trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, + terminator: &Terminator<'tcx>, loc: Location, ) { // If a place is borrowed in a terminator, it needs storage for that terminator. - self.borrowed_locals.borrow().analysis().terminator_effect(trans, terminator, loc); + self.borrowed_locals + .mut_analysis() + .transfer_function(trans) + .visit_terminator(terminator, loc); match &terminator.kind { TerminatorKind::Call { destination, .. } => { @@ -281,12 +305,12 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc } } - fn terminator_effect( - &self, - trans: &mut impl GenKill, - terminator: &mir::Terminator<'tcx>, + fn terminator_effect<'t>( + &mut self, + trans: &mut Self::Domain, + terminator: &'t Terminator<'tcx>, loc: Location, - ) { + ) -> TerminatorEdges<'t, 'tcx> { match terminator.kind { // For call terminators the destination requires storage for the call // and after the call returns successfully, but not after a panic. @@ -318,49 +342,41 @@ impl<'mir, 'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tc } self.check_for_move(trans, loc); + terminator.edges() } fn call_return_effect( - &self, + &mut self, trans: &mut impl GenKill, _block: BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ) { return_places.for_each(|place| trans.gen(place.local)); } - - fn yield_resume_effect( - &self, - trans: &mut impl GenKill, - _resume_block: BasicBlock, - resume_place: mir::Place<'tcx>, - ) { - trans.gen(resume_place.local); - } } -impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> { +impl<'tcx> MaybeRequiresStorage<'_, '_, 'tcx> { /// Kill locals that are fully moved and have not been borrowed. - fn check_for_move(&self, trans: &mut impl GenKill, loc: Location) { - let mut visitor = MoveVisitor { trans, borrowed_locals: &self.borrowed_locals }; - visitor.visit_location(&self.body, loc); + fn check_for_move(&mut self, trans: &mut impl GenKill, loc: Location) { + let body = self.borrowed_locals.body(); + let mut visitor = MoveVisitor { trans, borrowed_locals: &mut self.borrowed_locals }; + visitor.visit_location(body, loc); } } -struct MoveVisitor<'a, 'mir, 'tcx, T> { - borrowed_locals: &'a RefCell>, +struct MoveVisitor<'a, 'res, 'mir, 'tcx, T> { + borrowed_locals: &'a mut BorrowedLocalsResults<'res, 'mir, 'tcx>, trans: &'a mut T, } -impl<'a, 'mir, 'tcx, T> Visitor<'tcx> for MoveVisitor<'a, 'mir, 'tcx, T> +impl<'tcx, T> Visitor<'tcx> for MoveVisitor<'_, '_, '_, 'tcx, T> where T: GenKill, { fn visit_local(&mut self, local: Local, context: PlaceContext, loc: Location) { if PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) == context { - let mut borrowed_locals = self.borrowed_locals.borrow_mut(); - borrowed_locals.seek_before_primary_effect(loc); - if !borrowed_locals.contains(local) { + self.borrowed_locals.seek_before_primary_effect(loc); + if !self.borrowed_locals.contains(local) { self.trans.kill(local); } } diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs index fc4efb943e6b7..0cdbee19d2c80 100644 --- a/compiler/rustc_mir_dataflow/src/lib.rs +++ b/compiler/rustc_mir_dataflow/src/lib.rs @@ -27,9 +27,10 @@ pub use self::drop_flag_effects::{ on_lookup_result_bits, }; pub use self::framework::{ - fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, Backward, CallReturnPlaces, - Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, Results, ResultsCursor, - ResultsRefCursor, ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects, + fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, AnalysisResults, Backward, + CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, + MaybeReachable, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor, + ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects, }; use self::move_paths::MoveData; diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 096bc0acfccb9..5052de991840e 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -1,5 +1,3 @@ -use crate::move_paths::FxHashMap; -use crate::un_derefer::UnDerefer; use rustc_index::IndexVec; use rustc_middle::mir::tcx::RvalueInitializationState; use rustc_middle::mir::*; @@ -21,7 +19,6 @@ struct MoveDataBuilder<'a, 'tcx> { param_env: ty::ParamEnv<'tcx>, data: MoveData<'tcx>, errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, - un_derefer: UnDerefer<'tcx>, } impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { @@ -35,25 +32,29 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { tcx, param_env, errors: Vec::new(), - un_derefer: UnDerefer { tcx: tcx, derefer_sidetable: Default::default() }, data: MoveData { moves: IndexVec::new(), loc_map: LocationMap::new(body), rev_lookup: MovePathLookup { locals: body .local_decls - .indices() - .map(|i| { - Self::new_move_path( - &mut move_paths, - &mut path_map, - &mut init_path_map, - None, - Place::from(i), - ) + .iter_enumerated() + .map(|(i, l)| { + if l.is_deref_temp() { + MovePathIndex::MAX + } else { + Self::new_move_path( + &mut move_paths, + &mut path_map, + &mut init_path_map, + None, + Place::from(i), + ) + } }) .collect(), projections: Default::default(), + un_derefer: Default::default(), }, move_paths, path_map, @@ -98,13 +99,10 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { /// /// Maybe we should have separate "borrowck" and "moveck" modes. fn move_path_for(&mut self, place: Place<'tcx>) -> Result> { - if let Some(new_place) = self.builder.un_derefer.derefer(place.as_ref(), self.builder.body) - { - return self.move_path_for(new_place); - } + let data = &mut self.builder.data; debug!("lookup({:?})", place); - let mut base = self.builder.data.rev_lookup.locals[place.local]; + let mut base = data.rev_lookup.find_local(place.local); // The move path index of the first union that we find. Once this is // some we stop creating child move paths, since moves from unions @@ -113,22 +111,15 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { // from `*(u.f: &_)` isn't allowed. let mut union_path = None; - for (i, elem) in place.projection.iter().enumerate() { - let proj_base = &place.projection[..i]; + for (place_ref, elem) in data.rev_lookup.un_derefer.iter_projections(place.as_ref()) { let body = self.builder.body; let tcx = self.builder.tcx; - let place_ty = Place::ty_from(place.local, proj_base, body, tcx).ty; + let place_ty = place_ref.ty(body, tcx).ty; match place_ty.kind() { ty::Ref(..) | ty::RawPtr(..) => { - let proj = &place.projection[..i + 1]; return Err(MoveError::cannot_move_out_of( self.loc, - BorrowedContent { - target_place: Place { - local: place.local, - projection: tcx.mk_place_elems(proj), - }, - }, + BorrowedContent { target_place: place_ref.project_deeper(&[elem], tcx) }, )); } ty::Adt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() => { @@ -163,10 +154,17 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { }; if union_path.is_none() { - base = self.add_move_path(base, elem, |tcx| Place { - local: place.local, - projection: tcx.mk_place_elems(&place.projection[..i + 1]), - }); + // inlined from add_move_path because of a borrowck conflict with the iterator + base = + *data.rev_lookup.projections.entry((base, elem.lift())).or_insert_with(|| { + MoveDataBuilder::new_move_path( + &mut data.move_paths, + &mut data.path_map, + &mut data.init_path_map, + Some(base), + place_ref.project_deeper(&[elem], tcx), + ) + }) } } @@ -207,10 +205,8 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { } } -pub type MoveDat<'tcx> = Result< - (FxHashMap>, MoveData<'tcx>), - (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>), ->; +pub type MoveDat<'tcx> = + Result, (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>)>; impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { fn finalize(self) -> MoveDat<'tcx> { @@ -226,11 +222,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { "done dumping moves" }); - if self.errors.is_empty() { - Ok((self.un_derefer.derefer_sidetable, self.data)) - } else { - Err((self.data, self.errors)) - } + if self.errors.is_empty() { Ok(self.data) } else { Err((self.data, self.errors)) } } } @@ -259,7 +251,7 @@ pub(super) fn gather_moves<'tcx>( impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> { fn gather_args(&mut self) { for arg in self.body.args_iter() { - let path = self.data.rev_lookup.locals[arg]; + let path = self.data.rev_lookup.find_local(arg); let init = self.data.inits.push(Init { path, @@ -293,10 +285,14 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { fn gather_statement(&mut self, stmt: &Statement<'tcx>) { match &stmt.kind { StatementKind::Assign(box (place, Rvalue::CopyForDeref(reffed))) => { - assert!(place.projection.is_empty()); - if self.builder.body.local_decls[place.local].is_deref_temp() { - self.builder.un_derefer.derefer_sidetable.insert(place.local, *reffed); - } + let local = place.as_local().unwrap(); + assert!(self.builder.body.local_decls[local].is_deref_temp()); + + let rev_lookup = &mut self.builder.data.rev_lookup; + + rev_lookup.un_derefer.insert(local, reffed.as_ref()); + let base_local = rev_lookup.un_derefer.deref_chain(local).first().unwrap().local; + rev_lookup.locals[local] = rev_lookup.locals[base_local]; } StatementKind::Assign(box (place, rval)) => { self.create_move_path(*place); @@ -317,7 +313,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { StatementKind::StorageLive(_) => {} StatementKind::StorageDead(local) => { // DerefTemp locals (results of CopyForDeref) don't actually move anything. - if !self.builder.un_derefer.derefer_sidetable.contains_key(&local) { + if !self.builder.body.local_decls[*local].is_deref_temp() { self.gather_move(Place::from(*local)); } } @@ -399,7 +395,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { destination, target, unwind: _, - from_hir_call: _, + call_source: _, fn_span: _, } => { self.gather_operand(func); @@ -459,12 +455,6 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { fn gather_move(&mut self, place: Place<'tcx>) { debug!("gather_move({:?}, {:?})", self.loc, place); - if let Some(new_place) = self.builder.un_derefer.derefer(place.as_ref(), self.builder.body) - { - self.gather_move(new_place); - return; - } - if let [ref base @ .., ProjectionElem::Subslice { from, to, from_end: false }] = **place.projection { @@ -521,11 +511,6 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { fn gather_init(&mut self, place: PlaceRef<'tcx>, kind: InitKind) { debug!("gather_init({:?}, {:?})", self.loc, place); - if let Some(new_place) = self.builder.un_derefer.derefer(place, self.builder.body) { - self.gather_init(new_place.as_ref(), kind); - return; - } - let mut place = place; // Check if we are assigning into a field of a union, if so, lookup the place diff --git a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs index ab1a6715361ed..0c7aa6676ecb0 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/mod.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/mod.rs @@ -1,4 +1,5 @@ use crate::move_paths::builder::MoveDat; +use crate::un_derefer::UnDerefer; use rustc_data_structures::fx::FxHashMap; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::*; @@ -175,7 +176,7 @@ pub struct MoveData<'tcx> { /// particular path being moved.) pub loc_map: LocationMap>, pub path_map: IndexVec>, - pub rev_lookup: MovePathLookup, + pub rev_lookup: MovePathLookup<'tcx>, pub inits: IndexVec, /// Each Location `l` is mapped to the Inits that are effects /// of executing the code at `l`. @@ -289,7 +290,7 @@ impl Init { /// Tables mapping from a place to its MovePathIndex. #[derive(Debug)] -pub struct MovePathLookup { +pub struct MovePathLookup<'tcx> { locals: IndexVec, /// projections are made from a base-place and a projection @@ -299,6 +300,8 @@ pub struct MovePathLookup { /// base-place). For the remaining lookup, we map the projection /// elem to the associated MovePathIndex. projections: FxHashMap<(MovePathIndex, AbstractElem), MovePathIndex>, + + un_derefer: UnDerefer<'tcx>, } mod builder; @@ -309,15 +312,15 @@ pub enum LookupResult { Parent(Option), } -impl MovePathLookup { +impl<'tcx> MovePathLookup<'tcx> { // Unlike the builder `fn move_path_for` below, this lookup // alternative will *not* create a MovePath on the fly for an // unknown place, but will rather return the nearest available // parent. - pub fn find(&self, place: PlaceRef<'_>) -> LookupResult { - let mut result = self.locals[place.local]; + pub fn find(&self, place: PlaceRef<'tcx>) -> LookupResult { + let mut result = self.find_local(place.local); - for elem in place.projection.iter() { + for (_, elem) in self.un_derefer.iter_projections(place) { if let Some(&subpath) = self.projections.get(&(result, elem.lift())) { result = subpath; } else { @@ -328,6 +331,7 @@ impl MovePathLookup { LookupResult::Exact(result) } + #[inline] pub fn find_local(&self, local: Local) -> MovePathIndex { self.locals[local] } diff --git a/compiler/rustc_mir_dataflow/src/rustc_peek.rs b/compiler/rustc_mir_dataflow/src/rustc_peek.rs index 7cae68efbecc3..775c522b476f6 100644 --- a/compiler/rustc_mir_dataflow/src/rustc_peek.rs +++ b/compiler/rustc_mir_dataflow/src/rustc_peek.rs @@ -17,7 +17,7 @@ use crate::impls::{ use crate::move_paths::{HasMoveData, MoveData}; use crate::move_paths::{LookupResult, MovePathIndex}; use crate::MoveDataParamEnv; -use crate::{Analysis, JoinSemiLattice, Results, ResultsCursor}; +use crate::{Analysis, JoinSemiLattice, ResultsCursor}; pub struct SanityCheck; @@ -34,7 +34,7 @@ impl<'tcx> MirPass<'tcx> for SanityCheck { } let param_env = tcx.param_env(def_id); - let (_, move_data) = MoveData::gather_moves(body, tcx, param_env).unwrap(); + let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap(); let mdpe = MoveDataParamEnv { move_data, param_env }; if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() { @@ -42,7 +42,7 @@ impl<'tcx> MirPass<'tcx> for SanityCheck { .into_engine(tcx, body) .iterate_to_fixpoint(); - sanity_check_via_rustc_peek(tcx, body, &flow_inits); + sanity_check_via_rustc_peek(tcx, flow_inits.into_results_cursor(body)); } if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_uninit).is_some() { @@ -50,7 +50,7 @@ impl<'tcx> MirPass<'tcx> for SanityCheck { .into_engine(tcx, body) .iterate_to_fixpoint(); - sanity_check_via_rustc_peek(tcx, body, &flow_uninits); + sanity_check_via_rustc_peek(tcx, flow_uninits.into_results_cursor(body)); } if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_definite_init).is_some() { @@ -58,13 +58,13 @@ impl<'tcx> MirPass<'tcx> for SanityCheck { .into_engine(tcx, body) .iterate_to_fixpoint(); - sanity_check_via_rustc_peek(tcx, body, &flow_def_inits); + sanity_check_via_rustc_peek(tcx, flow_def_inits.into_results_cursor(body)); } if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_liveness).is_some() { let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint(); - sanity_check_via_rustc_peek(tcx, body, &flow_liveness); + sanity_check_via_rustc_peek(tcx, flow_liveness.into_results_cursor(body)); } if has_rustc_mir_with(tcx, def_id, sym::stop_after_dataflow).is_some() { @@ -91,17 +91,14 @@ impl<'tcx> MirPass<'tcx> for SanityCheck { /// errors are not intended to be used for unit tests.) pub fn sanity_check_via_rustc_peek<'tcx, A>( tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - results: &Results<'tcx, A>, + mut cursor: ResultsCursor<'_, 'tcx, A>, ) where A: RustcPeekAt<'tcx>, { - let def_id = body.source.def_id(); + let def_id = cursor.body().source.def_id(); debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id); - let mut cursor = ResultsCursor::new(body, results); - - let peek_calls = body.basic_blocks.iter_enumerated().filter_map(|(bb, block_data)| { + let peek_calls = cursor.body().basic_blocks.iter_enumerated().filter_map(|(bb, block_data)| { PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call)) }); @@ -132,8 +129,8 @@ pub fn sanity_check_via_rustc_peek<'tcx, A>( ) => { let loc = Location { block: bb, statement_index }; cursor.seek_before_primary_effect(loc); - let state = cursor.get(); - results.analysis.peek_at(tcx, *place, state, call); + let (state, analysis) = cursor.get_with_analysis(); + analysis.peek_at(tcx, *place, state, call); } _ => { @@ -193,14 +190,14 @@ impl PeekCall { if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } = &terminator.kind { - if let ty::FnDef(def_id, substs) = *func.literal.ty().kind() { + if let ty::FnDef(def_id, fn_args) = *func.literal.ty().kind() { let name = tcx.item_name(def_id); if !tcx.is_intrinsic(def_id) || name != sym::rustc_peek { return None; } - assert_eq!(args.len(), 1); - let kind = PeekCallKind::from_arg_ty(substs.type_at(0)); + assert_eq!(fn_args.len(), 1); + let kind = PeekCallKind::from_arg_ty(fn_args.type_at(0)); let arg = match &args[0] { Operand::Copy(place) | Operand::Move(place) => { if let Some(local) = place.as_local() { diff --git a/compiler/rustc_mir_dataflow/src/un_derefer.rs b/compiler/rustc_mir_dataflow/src/un_derefer.rs index 7e6e25cc603ec..874d50ffd0e21 100644 --- a/compiler/rustc_mir_dataflow/src/un_derefer.rs +++ b/compiler/rustc_mir_dataflow/src/un_derefer.rs @@ -1,22 +1,100 @@ use rustc_data_structures::fx::FxHashMap; use rustc_middle::mir::*; -use rustc_middle::ty::TyCtxt; /// Used for reverting changes made by `DerefSeparator` +#[derive(Default, Debug)] pub struct UnDerefer<'tcx> { - pub tcx: TyCtxt<'tcx>, - pub derefer_sidetable: FxHashMap>, + deref_chains: FxHashMap>>, } impl<'tcx> UnDerefer<'tcx> { #[inline] - pub fn derefer(&self, place: PlaceRef<'tcx>, body: &Body<'tcx>) -> Option> { - let reffed = self.derefer_sidetable.get(&place.local)?; + pub fn insert(&mut self, local: Local, reffed: PlaceRef<'tcx>) { + let mut chain = self.deref_chains.remove(&reffed.local).unwrap_or_default(); + chain.push(reffed); + self.deref_chains.insert(local, chain); + } + + /// Returns the chain of places behind `DerefTemp` locals + #[inline] + pub fn deref_chain(&self, local: Local) -> &[PlaceRef<'tcx>] { + self.deref_chains.get(&local).map(Vec::as_slice).unwrap_or_default() + } + + /// Iterates over the projections of a place and its deref chain. + /// + /// See [`PlaceRef::iter_projections`] + #[inline] + pub fn iter_projections( + &self, + place: PlaceRef<'tcx>, + ) -> impl Iterator, PlaceElem<'tcx>)> + '_ { + ProjectionIter::new(self.deref_chain(place.local), place) + } +} + +/// The iterator returned by [`UnDerefer::iter_projections`]. +struct ProjectionIter<'a, 'tcx> { + places: SlicePlusOne<'a, PlaceRef<'tcx>>, + proj_idx: usize, +} + +impl<'a, 'tcx> ProjectionIter<'a, 'tcx> { + #[inline] + fn new(deref_chain: &'a [PlaceRef<'tcx>], place: PlaceRef<'tcx>) -> Self { + // just return an empty iterator for a bare local + let last = if place.as_local().is_none() { + Some(place) + } else { + debug_assert!(deref_chain.is_empty()); + None + }; + + ProjectionIter { places: SlicePlusOne { slice: deref_chain, last }, proj_idx: 0 } + } +} - let new_place = reffed.project_deeper(place.projection, self.tcx); - if body.local_decls[new_place.local].is_deref_temp() { - return self.derefer(new_place.as_ref(), body); +impl<'tcx> Iterator for ProjectionIter<'_, 'tcx> { + type Item = (PlaceRef<'tcx>, PlaceElem<'tcx>); + + #[inline] + fn next(&mut self) -> Option<(PlaceRef<'tcx>, PlaceElem<'tcx>)> { + let place = self.places.read()?; + + // the projection should never be empty except for a bare local which is handled in new + let partial_place = + PlaceRef { local: place.local, projection: &place.projection[..self.proj_idx] }; + let elem = place.projection[self.proj_idx]; + + if self.proj_idx == place.projection.len() - 1 { + self.proj_idx = 0; + self.places.advance(); + } else { + self.proj_idx += 1; + } + + Some((partial_place, elem)) + } +} + +struct SlicePlusOne<'a, T> { + slice: &'a [T], + last: Option, +} + +impl SlicePlusOne<'_, T> { + #[inline] + fn read(&self) -> Option { + self.slice.first().copied().or(self.last) + } + + #[inline] + fn advance(&mut self) { + match self.slice { + [_, ref remainder @ ..] => { + self.slice = remainder; + } + [] => self.last = None, } - Some(new_place) } } diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs index b74d06e5ae8dd..766e0257efdce 100644 --- a/compiler/rustc_mir_dataflow/src/value_analysis.rs +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -47,8 +47,7 @@ use rustc_target::abi::{FieldIdx, VariantIdx}; use crate::lattice::{HasBottom, HasTop}; use crate::{ - fmt::DebugWithContext, Analysis, AnalysisDomain, CallReturnPlaces, JoinSemiLattice, - SwitchIntEdgeEffects, + fmt::DebugWithContext, Analysis, AnalysisDomain, JoinSemiLattice, SwitchIntEdgeEffects, }; pub trait ValueAnalysis<'tcx> { @@ -242,11 +241,19 @@ pub trait ValueAnalysis<'tcx> { /// The effect of a successful function call return should not be /// applied here, see [`Analysis::apply_terminator_effect`]. - fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State) { + fn handle_terminator<'mir>( + &self, + terminator: &'mir Terminator<'tcx>, + state: &mut State, + ) -> TerminatorEdges<'mir, 'tcx> { self.super_terminator(terminator, state) } - fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State) { + fn super_terminator<'mir>( + &self, + terminator: &'mir Terminator<'tcx>, + state: &mut State, + ) -> TerminatorEdges<'mir, 'tcx> { match &terminator.kind { TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => { // Effect is applied by `handle_call_return`. @@ -258,8 +265,10 @@ pub trait ValueAnalysis<'tcx> { // They would have an effect, but are not allowed in this phase. bug!("encountered disallowed terminator"); } + TerminatorKind::SwitchInt { discr, targets } => { + return self.handle_switch_int(discr, targets, state); + } TerminatorKind::Goto { .. } - | TerminatorKind::SwitchInt { .. } | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return @@ -271,6 +280,7 @@ pub trait ValueAnalysis<'tcx> { // These terminators have no effect on the analysis. } } + terminator.edges() } fn handle_call_return( @@ -291,19 +301,22 @@ pub trait ValueAnalysis<'tcx> { }) } - fn handle_switch_int( + fn handle_switch_int<'mir>( &self, - discr: &Operand<'tcx>, - apply_edge_effects: &mut impl SwitchIntEdgeEffects>, - ) { - self.super_switch_int(discr, apply_edge_effects) + discr: &'mir Operand<'tcx>, + targets: &'mir SwitchTargets, + state: &mut State, + ) -> TerminatorEdges<'mir, 'tcx> { + self.super_switch_int(discr, targets, state) } - fn super_switch_int( + fn super_switch_int<'mir>( &self, - _discr: &Operand<'tcx>, - _apply_edge_effects: &mut impl SwitchIntEdgeEffects>, - ) { + discr: &'mir Operand<'tcx>, + targets: &'mir SwitchTargets, + _state: &mut State, + ) -> TerminatorEdges<'mir, 'tcx> { + TerminatorEdges::SwitchInt { discr, targets } } fn wrap(self) -> ValueAnalysisWrapper @@ -343,7 +356,7 @@ where T: ValueAnalysis<'tcx>, { fn apply_statement_effect( - &self, + &mut self, state: &mut Self::Domain, statement: &Statement<'tcx>, _location: Location, @@ -353,22 +366,24 @@ where } } - fn apply_terminator_effect( - &self, + fn apply_terminator_effect<'mir>( + &mut self, state: &mut Self::Domain, - terminator: &Terminator<'tcx>, + terminator: &'mir Terminator<'tcx>, _location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { if state.is_reachable() { - self.0.handle_terminator(terminator, state); + self.0.handle_terminator(terminator, state) + } else { + TerminatorEdges::None } } fn apply_call_return_effect( - &self, + &mut self, state: &mut Self::Domain, _block: BasicBlock, - return_places: crate::CallReturnPlaces<'_, 'tcx>, + return_places: CallReturnPlaces<'_, 'tcx>, ) { if state.is_reachable() { self.0.handle_call_return(return_places, state) @@ -376,13 +391,11 @@ where } fn apply_switch_int_edge_effects( - &self, + &mut self, _block: BasicBlock, - discr: &Operand<'tcx>, - apply_edge_effects: &mut impl SwitchIntEdgeEffects, + _discr: &Operand<'tcx>, + _apply_edge_effects: &mut impl SwitchIntEdgeEffects, ) { - // FIXME: Dataflow framework provides no access to current state here. - self.0.handle_switch_int(discr, apply_edge_effects) } } @@ -839,7 +852,7 @@ impl Map { tail_elem: Option, f: &mut impl FnMut(ValueIndex), ) { - if place.has_deref() { + if place.is_indirect_first_projection() { // We do not track indirect places. return; } @@ -999,14 +1012,14 @@ pub fn iter_fields<'tcx>( f(None, field.into(), ty); } } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { if def.is_union() { return; } for (v_index, v_def) in def.variants().iter_enumerated() { let variant = if def.is_struct() { None } else { Some(v_index) }; for (f_index, f_def) in v_def.fields.iter().enumerate() { - let field_ty = f_def.ty(tcx, substs); + let field_ty = f_def.ty(tcx, args); let field_ty = tcx .try_normalize_erasing_regions(param_env, field_ty) .unwrap_or_else(|_| tcx.erase_regions(field_ty)); @@ -1014,8 +1027,8 @@ pub fn iter_fields<'tcx>( } } } - ty::Closure(_, substs) => { - iter_fields(substs.as_closure().tupled_upvars_ty(), tcx, param_env, f); + ty::Closure(_, args) => { + iter_fields(args.as_closure().tupled_upvars_ty(), tcx, param_env, f); } _ => (), } @@ -1099,10 +1112,10 @@ fn debug_with_context_rec( let info_elem = map.places[child].proj_elem.unwrap(); let child_place_str = match info_elem { TrackElem::Discriminant => { - format!("discriminant({})", place_str) + format!("discriminant({place_str})") } TrackElem::Variant(idx) => { - format!("({} as {:?})", place_str, idx) + format!("({place_str} as {idx:?})") } TrackElem::Field(field) => { if place_str.starts_with('*') { diff --git a/compiler/rustc_mir_transform/Cargo.toml b/compiler/rustc_mir_transform/Cargo.toml index eca5f98a2c01c..f1198d9bfd375 100644 --- a/compiler/rustc_mir_transform/Cargo.toml +++ b/compiler/rustc_mir_transform/Cargo.toml @@ -18,6 +18,7 @@ rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_middle = { path = "../rustc_middle" } rustc_const_eval = { path = "../rustc_const_eval" } +rustc_mir_build = { path = "../rustc_mir_build" } rustc_mir_dataflow = { path = "../rustc_mir_dataflow" } rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index 8c85cb5f76d86..2598eb2ed0968 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -1,3 +1,8 @@ +mir_transform_arithmetic_overflow = this arithmetic operation will overflow +mir_transform_call_to_unsafe_label = call to unsafe function +mir_transform_call_to_unsafe_note = consult the function's documentation for information on how to avoid undefined behavior +mir_transform_const_defined_here = `const` item defined here + mir_transform_const_modify = attempting to modify a `const` item .note = each usage of a `const` item creates a new temporary; the original `const` item will not be modified @@ -6,50 +11,10 @@ mir_transform_const_mut_borrow = taking a mutable reference to a `const` item .note2 = the mutable reference will refer to this temporary, not the original `const` item .note3 = mutable reference created due to call to this method -mir_transform_const_defined_here = `const` item defined here - -mir_transform_unaligned_packed_ref = reference to packed field is unaligned - .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses - .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) - .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) - -mir_transform_unused_unsafe = unnecessary `unsafe` block - .label = because it's nested under this `unsafe` block - -mir_transform_requires_unsafe = {$details} is unsafe and requires unsafe {$op_in_unsafe_fn_allowed -> - [true] function or block - *[false] block - } - .not_inherited = items do not inherit unsafety from separate enclosing items - -mir_transform_call_to_unsafe_label = call to unsafe function -mir_transform_call_to_unsafe_note = consult the function's documentation for information on how to avoid undefined behavior -mir_transform_use_of_asm_label = use of inline assembly -mir_transform_use_of_asm_note = inline assembly is entirely unchecked and can cause undefined behavior -mir_transform_initializing_valid_range_label = initializing type with `rustc_layout_scalar_valid_range` attr -mir_transform_initializing_valid_range_note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior mir_transform_const_ptr2int_label = cast of pointer to int mir_transform_const_ptr2int_note = casting pointers to integers in constants -mir_transform_use_of_static_mut_label = use of mutable static -mir_transform_use_of_static_mut_note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior -mir_transform_use_of_extern_static_label = use of extern static -mir_transform_use_of_extern_static_note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior mir_transform_deref_ptr_label = dereference of raw pointer mir_transform_deref_ptr_note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior -mir_transform_union_access_label = access to union field -mir_transform_union_access_note = the field may not be properly initialized: using uninitialized data will cause undefined behavior -mir_transform_mutation_layout_constrained_label = mutation of layout constrained field -mir_transform_mutation_layout_constrained_note = mutating layout constrained fields cannot statically be checked for valid values -mir_transform_mutation_layout_constrained_borrow_label = borrow of layout constrained field with interior mutability -mir_transform_mutation_layout_constrained_borrow_note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values -mir_transform_target_feature_call_label = call to function with `#[target_feature]` -mir_transform_target_feature_call_note = can only be called if the required target features are available - -mir_transform_unsafe_op_in_unsafe_fn = {$details} is unsafe and requires unsafe block (error E0133) - -mir_transform_arithmetic_overflow = this arithmetic operation will overflow -mir_transform_operation_will_panic = this operation will panic at runtime - mir_transform_ffi_unwind_call = call to {$foreign -> [true] foreign function *[false] function pointer @@ -58,9 +23,47 @@ mir_transform_ffi_unwind_call = call to {$foreign -> mir_transform_fn_item_ref = taking a reference to a function item does not give a function pointer .suggestion = cast `{$ident}` to obtain a function pointer +mir_transform_initializing_valid_range_label = initializing type with `rustc_layout_scalar_valid_range` attr +mir_transform_initializing_valid_range_note = initializing a layout restricted type's field with a value outside the valid range is undefined behavior mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspend point, but should not be .label = the value is held across this suspend point .note = {$reason} .help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point +mir_transform_mutation_layout_constrained_borrow_label = borrow of layout constrained field with interior mutability +mir_transform_mutation_layout_constrained_borrow_note = references to fields of layout constrained fields lose the constraints. Coupled with interior mutability, the field can be changed to invalid values +mir_transform_mutation_layout_constrained_label = mutation of layout constrained field +mir_transform_mutation_layout_constrained_note = mutating layout constrained fields cannot statically be checked for valid values +mir_transform_operation_will_panic = this operation will panic at runtime + +mir_transform_requires_unsafe = {$details} is unsafe and requires unsafe {$op_in_unsafe_fn_allowed -> + [true] function or block + *[false] block + } + .not_inherited = items do not inherit unsafety from separate enclosing items + mir_transform_simd_shuffle_last_const = last argument of `simd_shuffle` is required to be a `const` item + +mir_transform_target_feature_call_label = call to function with `#[target_feature]` +mir_transform_target_feature_call_note = can only be called if the required target features are available + +mir_transform_unaligned_packed_ref = reference to packed field is unaligned + .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses + .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) + .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +mir_transform_union_access_label = access to union field +mir_transform_union_access_note = the field may not be properly initialized: using uninitialized data will cause undefined behavior +mir_transform_unsafe_op_in_unsafe_fn = {$details} is unsafe and requires unsafe block (error E0133) + .suggestion = consider wrapping the function body in an unsafe block + .note = an unsafe function restricts its caller, but its body is safe by default + +mir_transform_unused_unsafe = unnecessary `unsafe` block + .label = because it's nested under this `unsafe` block + +mir_transform_use_of_asm_label = use of inline assembly +mir_transform_use_of_asm_note = inline assembly is entirely unchecked and can cause undefined behavior +mir_transform_use_of_extern_static_label = use of extern static +mir_transform_use_of_extern_static_note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior +mir_transform_use_of_static_mut_label = use of mutable static +mir_transform_use_of_static_mut_note = mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior diff --git a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs index b29ffcc70f93f..ef2a0c790e945 100644 --- a/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs +++ b/compiler/rustc_mir_transform/src/add_moves_for_packed_drops.rs @@ -80,7 +80,7 @@ fn add_move_for_packed_drop<'tcx>( is_cleanup: bool, ) { debug!("add_move_for_packed_drop({:?} @ {:?})", terminator, loc); - let TerminatorKind::Drop { ref place, target, unwind } = terminator.kind else { + let TerminatorKind::Drop { ref place, target, unwind, replace } = terminator.kind else { unreachable!(); }; @@ -98,6 +98,11 @@ fn add_move_for_packed_drop<'tcx>( patch.add_assign(loc, Place::from(temp), Rvalue::Use(Operand::Move(*place))); patch.patch_terminator( loc.block, - TerminatorKind::Drop { place: Place::from(temp), target: storage_dead_block, unwind }, + TerminatorKind::Drop { + place: Place::from(temp), + target: storage_dead_block, + unwind, + replace, + }, ); } diff --git a/compiler/rustc_mir_transform/src/add_retag.rs b/compiler/rustc_mir_transform/src/add_retag.rs index 187d38b385be3..75473ca53fb3d 100644 --- a/compiler/rustc_mir_transform/src/add_retag.rs +++ b/compiler/rustc_mir_transform/src/add_retag.rs @@ -28,6 +28,7 @@ fn may_contain_reference<'tcx>(ty: Ty<'tcx>, depth: u32, tcx: TyCtxt<'tcx>) -> b // References ty::Ref(..) => true, ty::Adt(..) if ty.is_box() => true, + ty::Adt(adt, _) if Some(adt.did()) == tcx.lang_items().ptr_unique() => true, // Compound types: recurse ty::Array(ty, _) | ty::Slice(ty) => { // This does not branch so we keep the depth the same. @@ -59,7 +60,7 @@ impl<'tcx> MirPass<'tcx> for AddRetag { let basic_blocks = body.basic_blocks.as_mut(); let local_decls = &body.local_decls; let needs_retag = |place: &Place<'tcx>| { - !place.has_deref() // we're not really interested in stores to "outside" locations, they are hard to keep track of anyway + !place.is_indirect_first_projection() // we're not really interested in stores to "outside" locations, they are hard to keep track of anyway && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx) && !local_decls[place.local].is_deref_temp() }; diff --git a/compiler/rustc_mir_transform/src/check_alignment.rs b/compiler/rustc_mir_transform/src/check_alignment.rs index d60184e0ebef6..4892ace53e381 100644 --- a/compiler/rustc_mir_transform/src/check_alignment.rs +++ b/compiler/rustc_mir_transform/src/check_alignment.rs @@ -14,6 +14,10 @@ pub struct CheckAlignment; impl<'tcx> MirPass<'tcx> for CheckAlignment { fn is_enabled(&self, sess: &Session) -> bool { + // FIXME(#112480) MSVC and rustc disagree on minimum stack alignment on x86 Windows + if sess.target.llvm_target == "i686-pc-windows-msvc" { + return false; + } sess.opts.debug_assertions } @@ -75,6 +79,14 @@ struct PointerFinder<'tcx, 'a> { } impl<'tcx, 'a> Visitor<'tcx> for PointerFinder<'tcx, 'a> { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let Rvalue::AddressOf(..) = rvalue { + // Ignore dereferences inside of an AddressOf + return; + } + self.super_rvalue(rvalue, location); + } + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) { if let PlaceContext::NonUse(_) = context { return; @@ -143,7 +155,7 @@ fn insert_alignment_check<'tcx>( new_block: BasicBlock, ) { // Cast the pointer to a *const () - let const_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Not }); + let const_raw_ptr = Ty::new_ptr(tcx, TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Not }); let rvalue = Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(pointer), const_raw_ptr); let thin_ptr = local_decls.push(LocalDecl::with_source_info(const_raw_ptr, source_info)).into(); block_data @@ -228,7 +240,10 @@ fn insert_alignment_check<'tcx>( required: Operand::Copy(alignment), found: Operand::Copy(addr), }), - unwind: UnwindAction::Terminate, + // This calls panic_misaligned_pointer_dereference, which is #[rustc_nounwind]. + // We never want to insert an unwind into unsafe code, because unwinding could + // make a failing UB check turn into much worse UB when we start unwinding. + unwind: UnwindAction::Unreachable, }, }); } diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs index 069514d8a3bf3..58e9786ec1a54 100644 --- a/compiler/rustc_mir_transform/src/check_unsafety.rs +++ b/compiler/rustc_mir_transform/src/check_unsafety.rs @@ -1,4 +1,4 @@ -use rustc_data_structures::unord::{UnordItems, UnordSet}; +use rustc_data_structures::unord::{ExtendUnord, UnordItems, UnordSet}; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -421,10 +421,8 @@ impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_, 'tcx> { intravisit::walk_block(self, block); } - fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) { - if matches!(self.tcx.def_kind(c.def_id), DefKind::InlineConst) { - self.visit_body(self.tcx.hir().body(c.body)) - } + fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) { + self.visit_body(self.tcx.hir().body(c.body)) } fn visit_fn( @@ -527,6 +525,8 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) { } let UnsafetyCheckResult { violations, unused_unsafes, .. } = tcx.unsafety_check_result(def_id); + // Only suggest wrapping the entire function body in an unsafe block once + let mut suggest_unsafe_block = true; for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() { let details = errors::RequiresUnsafeDetail { violation: details, span: source_info.span }; @@ -561,12 +561,29 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) { op_in_unsafe_fn_allowed, }); } - UnsafetyViolationKind::UnsafeFn => tcx.emit_spanned_lint( - UNSAFE_OP_IN_UNSAFE_FN, - lint_root, - source_info.span, - errors::UnsafeOpInUnsafeFn { details }, - ), + UnsafetyViolationKind::UnsafeFn => { + tcx.emit_spanned_lint( + UNSAFE_OP_IN_UNSAFE_FN, + lint_root, + source_info.span, + errors::UnsafeOpInUnsafeFn { + details, + suggest_unsafe_block: suggest_unsafe_block.then(|| { + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); + let fn_sig = tcx + .hir() + .fn_sig_by_hir_id(hir_id) + .expect("this violation only occurs in fn"); + let body = tcx.hir().body_owned_by(def_id); + let body_span = tcx.hir().body(body).value.span; + let start = tcx.sess.source_map().start_point(body_span).shrink_to_hi(); + let end = tcx.sess.source_map().end_point(body_span).shrink_to_lo(); + (start, end, fn_sig.span) + }), + }, + ); + suggest_unsafe_block = false; + } } } diff --git a/compiler/rustc_mir_transform/src/const_goto.rs b/compiler/rustc_mir_transform/src/const_goto.rs index da101ca7ad279..e175f22d7a940 100644 --- a/compiler/rustc_mir_transform/src/const_goto.rs +++ b/compiler/rustc_mir_transform/src/const_goto.rs @@ -28,7 +28,9 @@ pub struct ConstGoto; impl<'tcx> MirPass<'tcx> for ConstGoto { fn is_enabled(&self, sess: &rustc_session::Session) -> bool { - sess.mir_opt_level() >= 4 + // This pass participates in some as-of-yet untested unsoundness found + // in https://github.com/rust-lang/rust/issues/112460 + sess.mir_opt_level() >= 2 && sess.opts.unstable_opts.unsound_mir_opts } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs index a5d18fff89bd7..7529ed8186bd7 100644 --- a/compiler/rustc_mir_transform/src/const_prop.rs +++ b/compiler/rustc_mir_transform/src/const_prop.rs @@ -4,6 +4,7 @@ use either::Right; use rustc_const_eval::const_eval::CheckAlignment; +use rustc_const_eval::ReportErrorExt; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::DefKind; use rustc_index::bit_set::BitSet; @@ -13,16 +14,15 @@ use rustc_middle::mir::visit::{ }; use rustc_middle::mir::*; use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout}; -use rustc_middle::ty::InternalSubsts; -use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, GenericArgs, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::{def_id::DefId, Span, DUMMY_SP}; use rustc_target::abi::{self, Align, HasDataLayout, Size, TargetDataLayout}; use rustc_target::spec::abi::Abi as CallAbi; use crate::MirPass; use rustc_const_eval::interpret::{ - self, compile_time_machine, AllocId, ConstAllocation, ConstValue, Frame, ImmTy, Immediate, - InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, + self, compile_time_machine, AllocId, ConstAllocation, ConstValue, FnArg, Frame, ImmTy, + Immediate, InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, PlaceTy, Pointer, Scalar, StackPopCleanup, }; @@ -37,6 +37,7 @@ macro_rules! throw_machine_stop_str { ($($tt:tt)*) => {{ // We make a new local type for it. The type itself does not carry any information, // but its vtable (for the `MachineStopType` trait) does. + #[derive(Debug)] struct Zst; // Printing this type shows the desired string. impl std::fmt::Display for Zst { @@ -44,7 +45,17 @@ macro_rules! throw_machine_stop_str { write!(f, $($tt)*) } } - impl rustc_middle::mir::interpret::MachineStopType for Zst {} + + impl rustc_middle::mir::interpret::MachineStopType for Zst { + fn diagnostic_message(&self) -> rustc_errors::DiagnosticMessage { + self.to_string().into() + } + + fn add_args( + self: Box, + _: &mut dyn FnMut(std::borrow::Cow<'static, str>, rustc_errors::DiagnosticArgValue<'static>), + ) {} + } throw_machine_stop!(Zst) }}; } @@ -75,7 +86,7 @@ impl<'tcx> MirPass<'tcx> for ConstProp { return; } - let is_generator = tcx.type_of(def_id.to_def_id()).subst_identity().is_generator(); + let is_generator = tcx.type_of(def_id.to_def_id()).instantiate_identity().is_generator(); // FIXME(welseywiser) const prop doesn't work on generators because of query cycles // computing their layout. if is_generator { @@ -103,7 +114,14 @@ impl<'tcx> MirPass<'tcx> for ConstProp { // That would require a uniform one-def no-mutation analysis // and RPO (or recursing when needing the value of a local). let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx); - optimization_finder.visit_body(body); + + // Traverse the body in reverse post-order, to ensure that `FullConstProp` locals are + // assigned before being read. + let rpo = body.basic_blocks.reverse_postorder().to_vec(); + for bb in rpo { + let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb]; + optimization_finder.visit_basic_block_data(bb, data); + } trace!("ConstProp done for {:?}", def_id); } @@ -166,7 +184,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> _ecx: &mut InterpCx<'mir, 'tcx, Self>, _instance: ty::Instance<'tcx>, _abi: CallAbi, - _args: &[OpTy<'tcx>], + _args: &[FnArg<'tcx>], _destination: &PlaceTy<'tcx>, _target: Option, _unwind: UnwindAction, @@ -319,7 +337,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { tcx: TyCtxt<'tcx>, ) -> ConstPropagator<'mir, 'tcx> { let def_id = body.source.def_id(); - let substs = &InternalSubsts::identity_for_item(tcx, def_id); + let args = &GenericArgs::identity_for_item(tcx, def_id); let param_env = tcx.param_env_reveal_all_normalized(def_id); let can_const_prop = CanConstProp::check(tcx, param_env, body); @@ -331,7 +349,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { ); let ret_layout = ecx - .layout_of(body.bound_return_ty().subst(tcx, substs)) + .layout_of(body.bound_return_ty().instantiate(tcx, args)) .ok() // Don't bother allocating memory for large values. // I don't know how return types can seem to be unsized but this happens in the @@ -347,7 +365,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { .into(); ecx.push_stack_frame( - Instance::new(def_id, substs), + Instance::new(def_id, args), dummy_body, &ret, StackPopCleanup::Root { cleanup: false }, @@ -367,7 +385,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { op } Err(e) => { - trace!("get_const failed: {}", e); + trace!("get_const failed: {:?}", e.into_kind().debug()); return None; } }; @@ -388,51 +406,9 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { ecx.machine.written_only_inside_own_block_locals.remove(&local); } - /// Returns the value, if any, of evaluating `c`. - fn eval_constant(&mut self, c: &Constant<'tcx>) -> Option> { - // FIXME we need to revisit this for #67176 - if c.has_param() { - return None; - } - - // No span, we don't want errors to be shown. - self.ecx.eval_mir_constant(&c.literal, None, None).ok() - } - - /// Returns the value, if any, of evaluating `place`. - fn eval_place(&mut self, place: Place<'tcx>) -> Option> { - trace!("eval_place(place={:?})", place); - self.ecx.eval_place_to_op(place, None).ok() - } - - /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant` - /// or `eval_place`, depending on the variant of `Operand` used. - fn eval_operand(&mut self, op: &Operand<'tcx>) -> Option> { - match *op { - Operand::Constant(ref c) => self.eval_constant(c), - Operand::Move(place) | Operand::Copy(place) => self.eval_place(place), - } - } - fn propagate_operand(&mut self, operand: &mut Operand<'tcx>) { - match *operand { - Operand::Copy(l) | Operand::Move(l) => { - if let Some(value) = self.get_const(l) && self.should_const_prop(&value) { - // FIXME(felix91gr): this code only handles `Scalar` cases. - // For now, we're not handling `ScalarPair` cases because - // doing so here would require a lot of code duplication. - // We should hopefully generalize `Operand` handling into a fn, - // and use it to do const-prop here and everywhere else - // where it makes sense. - if let interpret::Operand::Immediate(interpret::Immediate::Scalar( - scalar, - )) = *value - { - *operand = self.operand_from_scalar(scalar, value.layout.ty); - } - } - } - Operand::Constant(_) => (), + if let Some(place) = operand.place() && let Some(op) = self.replace_with_const(place) { + *operand = op; } } @@ -560,93 +536,45 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { })) } - fn replace_with_const(&mut self, place: Place<'tcx>, rval: &mut Rvalue<'tcx>) { + fn replace_with_const(&mut self, place: Place<'tcx>) -> Option> { // This will return None if the above `const_prop` invocation only "wrote" a // type whose creation requires no write. E.g. a generator whose initial state // consists solely of uninitialized memory (so it doesn't capture any locals). - let Some(ref value) = self.get_const(place) else { return }; - if !self.should_const_prop(value) { - return; - } - trace!("replacing {:?}={:?} with {:?}", place, rval, value); - - if let Rvalue::Use(Operand::Constant(c)) = rval { - match c.literal { - ConstantKind::Ty(c) if matches!(c.kind(), ConstKind::Unevaluated(..)) => {} - _ => { - trace!("skipping replace of Rvalue::Use({:?} because it is already a const", c); - return; - } - } + let value = self.get_const(place)?; + if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - {value:?}")) { + return None; } + trace!("replacing {:?} with {:?}", place, value); - trace!("attempting to replace {:?} with {:?}", rval, value); // FIXME> figure out what to do when read_immediate_raw fails - let imm = self.ecx.read_immediate_raw(value).ok(); + let imm = self.ecx.read_immediate_raw(&value).ok()?; - if let Some(Right(imm)) = imm { - match *imm { - interpret::Immediate::Scalar(scalar) => { - *rval = Rvalue::Use(self.operand_from_scalar(scalar, value.layout.ty)); - } - Immediate::ScalarPair(..) => { - // Found a value represented as a pair. For now only do const-prop if the type - // of `rvalue` is also a tuple with two scalars. - // FIXME: enable the general case stated above ^. - let ty = value.layout.ty; - // Only do it for tuples - if let ty::Tuple(types) = ty.kind() { - // Only do it if tuple is also a pair with two scalars - if let [ty1, ty2] = types[..] { - let ty_is_scalar = |ty| { - self.ecx.layout_of(ty).ok().map(|layout| layout.abi.is_scalar()) - == Some(true) - }; - let alloc = if ty_is_scalar(ty1) && ty_is_scalar(ty2) { - let alloc = self - .ecx - .intern_with_temp_alloc(value.layout, |ecx, dest| { - ecx.write_immediate(*imm, dest) - }) - .unwrap(); - Some(alloc) - } else { - None - }; - - if let Some(alloc) = alloc { - // Assign entire constant in a single statement. - // We can't use aggregates, as we run after the aggregate-lowering `MirPhase`. - let const_val = ConstValue::ByRef { alloc, offset: Size::ZERO }; - let literal = ConstantKind::Val(const_val, ty); - *rval = Rvalue::Use(Operand::Constant(Box::new(Constant { - span: DUMMY_SP, - user_ty: None, - literal, - }))); - } - } - } - } - // Scalars or scalar pairs that contain undef values are assumed to not have - // successfully evaluated and are thus not propagated. - _ => {} + let Right(imm) = imm else { return None }; + match *imm { + Immediate::Scalar(scalar) if scalar.try_to_int().is_ok() => { + Some(self.operand_from_scalar(scalar, value.layout.ty)) } - } - } - - /// Returns `true` if and only if this `op` should be const-propagated into. - fn should_const_prop(&mut self, op: &OpTy<'tcx>) -> bool { - if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - OpTy: {:?}", op)) { - return false; - } - - match **op { - interpret::Operand::Immediate(Immediate::Scalar(s)) => s.try_to_int().is_ok(), - interpret::Operand::Immediate(Immediate::ScalarPair(l, r)) => { - l.try_to_int().is_ok() && r.try_to_int().is_ok() + Immediate::ScalarPair(l, r) if l.try_to_int().is_ok() && r.try_to_int().is_ok() => { + let alloc = self + .ecx + .intern_with_temp_alloc(value.layout, |ecx, dest| { + ecx.write_immediate(*imm, dest) + }) + .ok()?; + + let literal = ConstantKind::Val( + ConstValue::ByRef { alloc, offset: Size::ZERO }, + value.layout.ty, + ); + Some(Operand::Constant(Box::new(Constant { + span: DUMMY_SP, + user_ty: None, + literal, + }))) } - _ => false, + // Scalars or scalar pairs that contain undef values are assumed to not have + // successfully evaluated and are thus not propagated. + _ => None, } } @@ -772,7 +700,6 @@ impl<'tcx> Visitor<'tcx> for CanConstProp { // mutation. | NonMutatingUse(NonMutatingUseContext::SharedBorrow) | NonMutatingUse(NonMutatingUseContext::ShallowBorrow) - | NonMutatingUse(NonMutatingUseContext::UniqueBorrow) | NonMutatingUse(NonMutatingUseContext::AddressOf) | MutatingUse(MutatingUseContext::Borrow) | MutatingUse(MutatingUseContext::AddressOf) => { @@ -790,20 +717,9 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> { self.tcx } - fn visit_body(&mut self, body: &mut Body<'tcx>) { - for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() { - self.visit_basic_block_data(bb, data); - } - } - fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) { self.super_operand(operand, location); - - // Only const prop copies and moves on `mir_opt_level=3` as doing so - // currently slightly increases compile time in some cases. - if self.tcx.sess.mir_opt_level() >= 3 { - self.propagate_operand(operand) - } + self.propagate_operand(operand) } fn process_projection_elem( @@ -813,8 +729,7 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> { ) -> Option> { if let PlaceElem::Index(local) = elem && let Some(value) = self.get_const(local.into()) - && self.should_const_prop(&value) - && let interpret::Operand::Immediate(interpret::Immediate::Scalar(scalar)) = *value + && let interpret::Operand::Immediate(Immediate::Scalar(scalar)) = *value && let Ok(offset) = scalar.to_target_usize(&self.tcx) && let Some(min_length) = offset.checked_add(1) { @@ -840,7 +755,14 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> { ConstPropMode::NoPropagation => self.ensure_not_propagated(place.local), ConstPropMode::OnlyInsideOwnBlock | ConstPropMode::FullConstProp => { if let Some(()) = self.eval_rvalue_with_identities(rvalue, *place) { - self.replace_with_const(*place, rvalue); + // If this was already an evaluated constant, keep it. + if let Rvalue::Use(Operand::Constant(c)) = rvalue + && let ConstantKind::Val(..) = c.literal + { + trace!("skipping replace of Rvalue::Use({:?} because it is already a const", c); + } else if let Some(operand) = self.replace_with_const(*place) { + *rvalue = Rvalue::Use(operand); + } } else { // Const prop failed, so erase the destination, ensuring that whatever happens // from here on, does not know about the previous value. @@ -886,54 +808,24 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> { } } StatementKind::StorageLive(local) => { - let frame = self.ecx.frame_mut(); - frame.locals[local].value = - LocalValue::Live(interpret::Operand::Immediate(interpret::Immediate::Uninit)); - } - StatementKind::StorageDead(local) => { - let frame = self.ecx.frame_mut(); - frame.locals[local].value = LocalValue::Dead; + Self::remove_const(&mut self.ecx, local); } - _ => {} - } - } - - fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) { - self.super_terminator(terminator, location); - - match &mut terminator.kind { - TerminatorKind::Assert { expected, ref mut cond, .. } => { - if let Some(ref value) = self.eval_operand(&cond) - && let Ok(value_const) = self.ecx.read_scalar(&value) - && self.should_const_prop(value) - { - trace!("assertion on {:?} should be {:?}", value, expected); - *cond = self.operand_from_scalar(value_const, self.tcx.types.bool); - } - } - TerminatorKind::SwitchInt { ref mut discr, .. } => { - // FIXME: This is currently redundant with `visit_operand`, but sadly - // always visiting operands currently causes a perf regression in LLVM codegen, so - // `visit_operand` currently only runs for propagates places for `mir_opt_level=4`. - self.propagate_operand(discr) - } - // None of these have Operands to const-propagate. - TerminatorKind::Goto { .. } - | TerminatorKind::Resume - | TerminatorKind::Terminate - | TerminatorKind::Return - | TerminatorKind::Unreachable - | TerminatorKind::Drop { .. } - | TerminatorKind::Yield { .. } - | TerminatorKind::GeneratorDrop - | TerminatorKind::FalseEdge { .. } - | TerminatorKind::FalseUnwind { .. } - | TerminatorKind::InlineAsm { .. } => {} - // Every argument in our function calls have already been propagated in `visit_operand`. + // We do not need to mark dead locals as such. For `FullConstProp` locals, + // this allows to propagate the single assigned value in this case: + // ``` + // let x = SOME_CONST; + // if a { + // f(copy x); + // StorageDead(x); + // } else { + // g(copy x); + // StorageDead(x); + // } + // ``` // - // NOTE: because LLVM codegen gives slight performance regressions with it, so this is - // gated on `mir_opt_level=3`. - TerminatorKind::Call { .. } => {} + // This may propagate a constant where the local would be uninit or dead. + // In both cases, this does not matter, as those reads would be UB anyway. + _ => {} } } diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs index 0fe49b8a1bbf0..ac07c25763bac 100644 --- a/compiler/rustc_mir_transform/src/const_prop_lint.rs +++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs @@ -9,13 +9,14 @@ use rustc_const_eval::interpret::Immediate; use rustc_const_eval::interpret::{ self, InterpCx, InterpResult, LocalValue, MemoryKind, OpTy, Scalar, StackPopCleanup, }; +use rustc_const_eval::ReportErrorExt; use rustc_hir::def::DefKind; use rustc_hir::HirId; use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout}; -use rustc_middle::ty::InternalSubsts; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{ self, ConstInt, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeVisitableExt, }; @@ -54,7 +55,7 @@ impl<'tcx> MirLint<'tcx> for ConstProp { return; } - let is_generator = tcx.type_of(def_id.to_def_id()).subst_identity().is_generator(); + let is_generator = tcx.type_of(def_id.to_def_id()).instantiate_identity().is_generator(); // FIXME(welseywiser) const prop doesn't work on generators because of query cycles // computing their layout. if is_generator { @@ -170,7 +171,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { tcx: TyCtxt<'tcx>, ) -> ConstPropagator<'mir, 'tcx> { let def_id = body.source.def_id(); - let substs = &InternalSubsts::identity_for_item(tcx, def_id); + let args = &GenericArgs::identity_for_item(tcx, def_id); let param_env = tcx.param_env_reveal_all_normalized(def_id); let can_const_prop = CanConstProp::check(tcx, param_env, body); @@ -182,7 +183,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { ); let ret_layout = ecx - .layout_of(body.bound_return_ty().subst(tcx, substs)) + .layout_of(body.bound_return_ty().instantiate(tcx, args)) .ok() // Don't bother allocating memory for large values. // I don't know how return types can seem to be unsized but this happens in the @@ -198,7 +199,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { .into(); ecx.push_stack_frame( - Instance::new(def_id, substs), + Instance::new(def_id, args), dummy_body, &ret, StackPopCleanup::Root { cleanup: false }, @@ -232,7 +233,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { op } Err(e) => { - trace!("get_const failed: {}", e); + trace!("get_const failed: {:?}", e.into_kind().debug()); return None; } }; @@ -272,8 +273,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { // dedicated error variants should be introduced instead. assert!( !error.kind().formatted_string(), - "const-prop encountered formatting error: {}", - error + "const-prop encountered formatting error: {error:?}", ); None } @@ -494,7 +494,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { trace!("assertion on {:?} should be {:?}", value, expected); let expected = Scalar::from_bool(expected); - let value_const = self.use_ecx(location, |this| this.ecx.read_scalar(&value))?; + let value_const = self.use_ecx(location, |this| this.ecx.read_scalar(value))?; if expected != value_const { // Poison all places this operand references so that further code @@ -664,7 +664,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { } TerminatorKind::SwitchInt { ref discr, ref targets } => { if let Some(ref value) = self.eval_operand(&discr, location) - && let Some(value_const) = self.use_ecx(location, |this| this.ecx.read_scalar(&value)) + && let Some(value_const) = self.use_ecx(location, |this| this.ecx.read_scalar(value)) && let Ok(constant) = value_const.try_to_int() && let Ok(constant) = constant.to_bits(constant.size()) { diff --git a/compiler/rustc_mir_transform/src/copy_prop.rs b/compiler/rustc_mir_transform/src/copy_prop.rs index 319f3a7970512..9a3798eea3b97 100644 --- a/compiler/rustc_mir_transform/src/copy_prop.rs +++ b/compiler/rustc_mir_transform/src/copy_prop.rs @@ -76,9 +76,11 @@ fn fully_moved_locals(ssa: &SsaLocals, body: &Body<'_>) -> BitSet { let mut fully_moved = BitSet::new_filled(body.local_decls.len()); for (_, rvalue, _) in ssa.assignments(body) { - let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) | Rvalue::CopyForDeref(place)) - = rvalue - else { continue }; + let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) + | Rvalue::CopyForDeref(place)) = rvalue + else { + continue; + }; let Some(rhs) = place.as_local() else { continue }; if !ssa.is_ssa(rhs) { @@ -130,7 +132,6 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> { PlaceContext::NonMutatingUse( NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::ShallowBorrow - | NonMutatingUseContext::UniqueBorrow | NonMutatingUseContext::AddressOf, ) => true, // For debuginfo, merging locals is ok. @@ -153,7 +154,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> { fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) { if let Operand::Move(place) = *operand // A move out of a projection of a copy is equivalent to a copy of the original projection. - && !place.has_deref() + && !place.is_indirect_first_projection() && !self.fully_moved.contains(place.local) { *operand = Operand::Copy(place); diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 658e01d931031..3d442e5dca9f9 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -8,55 +8,116 @@ use debug::{DebugCounters, NESTED_INDENT}; use graph::{BasicCoverageBlock, BcbBranch, CoverageGraph, TraverseCoverageGraphWithLoops}; use spans::CoverageSpan; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::graph::WithNumNodes; use rustc_index::bit_set::BitSet; +use rustc_index::IndexVec; use rustc_middle::mir::coverage::*; -/// Manages the counter and expression indexes/IDs to generate `CoverageKind` components for MIR -/// `Coverage` statements. +use std::fmt::{self, Debug}; + +/// The coverage counter or counter expression associated with a particular +/// BCB node or BCB edge. +#[derive(Clone)] +pub(super) enum BcbCounter { + Counter { id: CounterId }, + Expression { id: ExpressionId, lhs: Operand, op: Op, rhs: Operand }, +} + +impl BcbCounter { + fn is_expression(&self) -> bool { + matches!(self, Self::Expression { .. }) + } + + pub(super) fn as_operand(&self) -> Operand { + match *self { + BcbCounter::Counter { id, .. } => Operand::Counter(id), + BcbCounter::Expression { id, .. } => Operand::Expression(id), + } + } +} + +impl Debug for BcbCounter { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Counter { id, .. } => write!(fmt, "Counter({:?})", id.index()), + Self::Expression { id, lhs, op, rhs } => write!( + fmt, + "Expression({:?}) = {:?} {} {:?}", + id.index(), + lhs, + match op { + Op::Add => "+", + Op::Subtract => "-", + }, + rhs, + ), + } + } +} + +/// Generates and stores coverage counter and coverage expression information +/// associated with nodes/edges in the BCB graph. pub(super) struct CoverageCounters { - function_source_hash: u64, - next_counter_id: u32, - num_expressions: u32, + next_counter_id: CounterId, + next_expression_id: ExpressionId, + + /// Coverage counters/expressions that are associated with individual BCBs. + bcb_counters: IndexVec>, + /// Coverage counters/expressions that are associated with the control-flow + /// edge between two BCBs. + bcb_edge_counters: FxHashMap<(BasicCoverageBlock, BasicCoverageBlock), BcbCounter>, + /// Tracks which BCBs have a counter associated with some incoming edge. + /// Only used by debug assertions, to verify that BCBs with incoming edge + /// counters do not have their own physical counters (expressions are allowed). + bcb_has_incoming_edge_counters: BitSet, + /// Expression nodes that are not directly associated with any particular + /// BCB/edge, but are needed as operands to more complex expressions. + /// These are always [`BcbCounter::Expression`]. + pub(super) intermediate_expressions: Vec, + pub debug_counters: DebugCounters, } impl CoverageCounters { - pub fn new(function_source_hash: u64) -> Self { + pub(super) fn new(basic_coverage_blocks: &CoverageGraph) -> Self { + let num_bcbs = basic_coverage_blocks.num_nodes(); + Self { - function_source_hash, - next_counter_id: CounterValueReference::START.as_u32(), - num_expressions: 0, + next_counter_id: CounterId::START, + next_expression_id: ExpressionId::START, + + bcb_counters: IndexVec::from_elem_n(None, num_bcbs), + bcb_edge_counters: FxHashMap::default(), + bcb_has_incoming_edge_counters: BitSet::new_empty(num_bcbs), + intermediate_expressions: Vec::new(), + debug_counters: DebugCounters::new(), } } /// Activate the `DebugCounters` data structures, to provide additional debug formatting - /// features when formatting `CoverageKind` (counter) values. + /// features when formatting [`BcbCounter`] (counter) values. pub fn enable_debug(&mut self) { self.debug_counters.enable(); } - /// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or - /// indirectly associated with `CoverageSpans`, and returns additional `Expression`s + /// Makes [`BcbCounter`] `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or + /// indirectly associated with `CoverageSpans`, and accumulates additional `Expression`s /// representing intermediate values. pub fn make_bcb_counters( &mut self, - basic_coverage_blocks: &mut CoverageGraph, + basic_coverage_blocks: &CoverageGraph, coverage_spans: &[CoverageSpan], - ) -> Result, Error> { - let mut bcb_counters = BcbCounters::new(self, basic_coverage_blocks); - bcb_counters.make_bcb_counters(coverage_spans) + ) -> Result<(), Error> { + MakeBcbCounters::new(self, basic_coverage_blocks).make_bcb_counters(coverage_spans) } - fn make_counter(&mut self, debug_block_label_fn: F) -> CoverageKind + fn make_counter(&mut self, debug_block_label_fn: F) -> BcbCounter where F: Fn() -> Option, { - let counter = CoverageKind::Counter { - function_source_hash: self.function_source_hash, - id: self.next_counter(), - }; + let counter = BcbCounter::Counter { id: self.next_counter() }; if self.debug_counters.is_enabled() { self.debug_counters.add_counter(&counter, (debug_block_label_fn)()); } @@ -65,49 +126,120 @@ impl CoverageCounters { fn make_expression( &mut self, - lhs: ExpressionOperandId, + lhs: Operand, op: Op, - rhs: ExpressionOperandId, + rhs: Operand, debug_block_label_fn: F, - ) -> CoverageKind + ) -> BcbCounter where F: Fn() -> Option, { let id = self.next_expression(); - let expression = CoverageKind::Expression { id, lhs, op, rhs }; + let expression = BcbCounter::Expression { id, lhs, op, rhs }; if self.debug_counters.is_enabled() { self.debug_counters.add_counter(&expression, (debug_block_label_fn)()); } expression } - pub fn make_identity_counter(&mut self, counter_operand: ExpressionOperandId) -> CoverageKind { + pub fn make_identity_counter(&mut self, counter_operand: Operand) -> BcbCounter { let some_debug_block_label = if self.debug_counters.is_enabled() { self.debug_counters.some_block_label(counter_operand).cloned() } else { None }; - self.make_expression(counter_operand, Op::Add, ExpressionOperandId::ZERO, || { + self.make_expression(counter_operand, Op::Add, Operand::Zero, || { some_debug_block_label.clone() }) } /// Counter IDs start from one and go up. - fn next_counter(&mut self) -> CounterValueReference { - assert!(self.next_counter_id < u32::MAX - self.num_expressions); + fn next_counter(&mut self) -> CounterId { let next = self.next_counter_id; - self.next_counter_id += 1; - CounterValueReference::from(next) + self.next_counter_id = next.next_id(); + next + } + + /// Expression IDs start from 0 and go up. + /// (Counter IDs and Expression IDs are distinguished by the `Operand` enum.) + fn next_expression(&mut self) -> ExpressionId { + let next = self.next_expression_id; + self.next_expression_id = next.next_id(); + next + } + + fn set_bcb_counter( + &mut self, + bcb: BasicCoverageBlock, + counter_kind: BcbCounter, + ) -> Result { + debug_assert!( + // If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also + // have an expression (to be injected into an existing `BasicBlock` represented by this + // `BasicCoverageBlock`). + counter_kind.is_expression() || !self.bcb_has_incoming_edge_counters.contains(bcb), + "attempt to add a `Counter` to a BCB target with existing incoming edge counters" + ); + let operand = counter_kind.as_operand(); + if let Some(replaced) = self.bcb_counters[bcb].replace(counter_kind) { + Error::from_string(format!( + "attempt to set a BasicCoverageBlock coverage counter more than once; \ + {bcb:?} already had counter {replaced:?}", + )) + } else { + Ok(operand) + } + } + + fn set_bcb_edge_counter( + &mut self, + from_bcb: BasicCoverageBlock, + to_bcb: BasicCoverageBlock, + counter_kind: BcbCounter, + ) -> Result { + if level_enabled!(tracing::Level::DEBUG) { + // If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also + // have an expression (to be injected into an existing `BasicBlock` represented by this + // `BasicCoverageBlock`). + if self.bcb_counter(to_bcb).is_some_and(|c| !c.is_expression()) { + return Error::from_string(format!( + "attempt to add an incoming edge counter from {from_bcb:?} when the target BCB already \ + has a `Counter`" + )); + } + } + self.bcb_has_incoming_edge_counters.insert(to_bcb); + let operand = counter_kind.as_operand(); + if let Some(replaced) = self.bcb_edge_counters.insert((from_bcb, to_bcb), counter_kind) { + Error::from_string(format!( + "attempt to set an edge counter more than once; from_bcb: \ + {from_bcb:?} already had counter {replaced:?}", + )) + } else { + Ok(operand) + } } - /// Expression IDs start from u32::MAX and go down because an Expression can reference - /// (add or subtract counts) of both Counter regions and Expression regions. The counter - /// expression operand IDs must be unique across both types. - fn next_expression(&mut self) -> InjectedExpressionId { - assert!(self.next_counter_id < u32::MAX - self.num_expressions); - let next = u32::MAX - self.num_expressions; - self.num_expressions += 1; - InjectedExpressionId::from(next) + pub(super) fn bcb_counter(&self, bcb: BasicCoverageBlock) -> Option<&BcbCounter> { + self.bcb_counters[bcb].as_ref() + } + + pub(super) fn take_bcb_counter(&mut self, bcb: BasicCoverageBlock) -> Option { + self.bcb_counters[bcb].take() + } + + pub(super) fn drain_bcb_counters( + &mut self, + ) -> impl Iterator + '_ { + self.bcb_counters + .iter_enumerated_mut() + .filter_map(|(bcb, counter)| Some((bcb, counter.take()?))) + } + + pub(super) fn drain_bcb_edge_counters( + &mut self, + ) -> impl Iterator + '_ { + self.bcb_edge_counters.drain() } } @@ -115,15 +247,15 @@ impl CoverageCounters { /// injected with `CoverageSpan`s. `Expressions` have no runtime overhead, so if a viable expression /// (adding or subtracting two other counters or expressions) can compute the same result as an /// embedded counter, an `Expression` should be used. -struct BcbCounters<'a> { +struct MakeBcbCounters<'a> { coverage_counters: &'a mut CoverageCounters, - basic_coverage_blocks: &'a mut CoverageGraph, + basic_coverage_blocks: &'a CoverageGraph, } -impl<'a> BcbCounters<'a> { +impl<'a> MakeBcbCounters<'a> { fn new( coverage_counters: &'a mut CoverageCounters, - basic_coverage_blocks: &'a mut CoverageGraph, + basic_coverage_blocks: &'a CoverageGraph, ) -> Self { Self { coverage_counters, basic_coverage_blocks } } @@ -138,13 +270,9 @@ impl<'a> BcbCounters<'a> { /// Returns any non-code-span expressions created to represent intermediate values (such as to /// add two counters so the result can be subtracted from another counter), or an Error with /// message for subsequent debugging. - fn make_bcb_counters( - &mut self, - coverage_spans: &[CoverageSpan], - ) -> Result, Error> { + fn make_bcb_counters(&mut self, coverage_spans: &[CoverageSpan]) -> Result<(), Error> { debug!("make_bcb_counters(): adding a counter or expression to each BasicCoverageBlock"); let num_bcbs = self.basic_coverage_blocks.num_nodes(); - let mut collect_intermediate_expressions = Vec::with_capacity(num_bcbs); let mut bcbs_with_coverage = BitSet::new_empty(num_bcbs); for covspan in coverage_spans { @@ -165,16 +293,10 @@ impl<'a> BcbCounters<'a> { while let Some(bcb) = traversal.next(self.basic_coverage_blocks) { if bcbs_with_coverage.contains(bcb) { debug!("{:?} has at least one `CoverageSpan`. Get or make its counter", bcb); - let branching_counter_operand = - self.get_or_make_counter_operand(bcb, &mut collect_intermediate_expressions)?; + let branching_counter_operand = self.get_or_make_counter_operand(bcb)?; if self.bcb_needs_branch_counters(bcb) { - self.make_branch_counters( - &mut traversal, - bcb, - branching_counter_operand, - &mut collect_intermediate_expressions, - )?; + self.make_branch_counters(&mut traversal, bcb, branching_counter_operand)?; } } else { debug!( @@ -186,7 +308,7 @@ impl<'a> BcbCounters<'a> { } if traversal.is_complete() { - Ok(collect_intermediate_expressions) + Ok(()) } else { Error::from_string(format!( "`TraverseCoverageGraphWithLoops` missed some `BasicCoverageBlock`s: {:?}", @@ -199,8 +321,7 @@ impl<'a> BcbCounters<'a> { &mut self, traversal: &mut TraverseCoverageGraphWithLoops, branching_bcb: BasicCoverageBlock, - branching_counter_operand: ExpressionOperandId, - collect_intermediate_expressions: &mut Vec, + branching_counter_operand: Operand, ) -> Result<(), Error> { let branches = self.bcb_branches(branching_bcb); debug!( @@ -208,9 +329,7 @@ impl<'a> BcbCounters<'a> { branching_bcb, branches .iter() - .map(|branch| { - format!("{:?}: {:?}", branch, branch.counter(&self.basic_coverage_blocks)) - }) + .map(|branch| { format!("{:?}: {:?}", branch, self.branch_counter(branch)) }) .collect::>() .join("\n "), ); @@ -236,17 +355,10 @@ impl<'a> BcbCounters<'a> { counter", branch, branching_bcb ); - self.get_or_make_counter_operand( - branch.target_bcb, - collect_intermediate_expressions, - )? + self.get_or_make_counter_operand(branch.target_bcb)? } else { debug!(" {:?} has multiple incoming edges, so adding an edge counter", branch); - self.get_or_make_edge_counter_operand( - branching_bcb, - branch.target_bcb, - collect_intermediate_expressions, - )? + self.get_or_make_edge_counter_operand(branching_bcb, branch.target_bcb)? }; if let Some(sumup_counter_operand) = some_sumup_counter_operand.replace(branch_counter_operand) @@ -261,8 +373,8 @@ impl<'a> BcbCounters<'a> { " [new intermediate expression: {}]", self.format_counter(&intermediate_expression) ); - let intermediate_expression_operand = intermediate_expression.as_operand_id(); - collect_intermediate_expressions.push(intermediate_expression); + let intermediate_expression_operand = intermediate_expression.as_operand(); + self.coverage_counters.intermediate_expressions.push(intermediate_expression); some_sumup_counter_operand.replace(intermediate_expression_operand); } } @@ -282,41 +394,36 @@ impl<'a> BcbCounters<'a> { branching_counter_operand, Op::Subtract, sumup_counter_operand, - || Some(format!("{:?}", expression_branch)), + || Some(format!("{expression_branch:?}")), ); debug!("{:?} gets an expression: {}", expression_branch, self.format_counter(&expression)); let bcb = expression_branch.target_bcb; if expression_branch.is_only_path_to_target() { - self.basic_coverage_blocks[bcb].set_counter(expression)?; + self.coverage_counters.set_bcb_counter(bcb, expression)?; } else { - self.basic_coverage_blocks[bcb].set_edge_counter_from(branching_bcb, expression)?; + self.coverage_counters.set_bcb_edge_counter(branching_bcb, bcb, expression)?; } Ok(()) } - fn get_or_make_counter_operand( - &mut self, - bcb: BasicCoverageBlock, - collect_intermediate_expressions: &mut Vec, - ) -> Result { - self.recursive_get_or_make_counter_operand(bcb, collect_intermediate_expressions, 1) + fn get_or_make_counter_operand(&mut self, bcb: BasicCoverageBlock) -> Result { + self.recursive_get_or_make_counter_operand(bcb, 1) } fn recursive_get_or_make_counter_operand( &mut self, bcb: BasicCoverageBlock, - collect_intermediate_expressions: &mut Vec, debug_indent_level: usize, - ) -> Result { + ) -> Result { // If the BCB already has a counter, return it. - if let Some(counter_kind) = self.basic_coverage_blocks[bcb].counter() { + if let Some(counter_kind) = &self.coverage_counters.bcb_counters[bcb] { debug!( "{}{:?} already has a counter: {}", NESTED_INDENT.repeat(debug_indent_level), bcb, self.format_counter(counter_kind), ); - return Ok(counter_kind.as_operand_id()); + return Ok(counter_kind.as_operand()); } // A BCB with only one incoming edge gets a simple `Counter` (via `make_counter()`). @@ -324,7 +431,7 @@ impl<'a> BcbCounters<'a> { // program results in a tight infinite loop, but it should still compile. let one_path_to_target = self.bcb_has_one_path_to_target(bcb); if one_path_to_target || self.bcb_predecessors(bcb).contains(&bcb) { - let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{:?}", bcb))); + let counter_kind = self.coverage_counters.make_counter(|| Some(format!("{bcb:?}"))); if one_path_to_target { debug!( "{}{:?} gets a new counter: {}", @@ -342,7 +449,7 @@ impl<'a> BcbCounters<'a> { self.format_counter(&counter_kind), ); } - return self.basic_coverage_blocks[bcb].set_counter(counter_kind); + return self.coverage_counters.set_bcb_counter(bcb, counter_kind); } // A BCB with multiple incoming edges can compute its count by `Expression`, summing up the @@ -358,7 +465,6 @@ impl<'a> BcbCounters<'a> { let first_edge_counter_operand = self.recursive_get_or_make_edge_counter_operand( predecessors.next().unwrap(), bcb, - collect_intermediate_expressions, debug_indent_level + 1, )?; let mut some_sumup_edge_counter_operand = None; @@ -366,7 +472,6 @@ impl<'a> BcbCounters<'a> { let edge_counter_operand = self.recursive_get_or_make_edge_counter_operand( predecessor, bcb, - collect_intermediate_expressions, debug_indent_level + 1, )?; if let Some(sumup_edge_counter_operand) = @@ -383,8 +488,8 @@ impl<'a> BcbCounters<'a> { NESTED_INDENT.repeat(debug_indent_level), self.format_counter(&intermediate_expression) ); - let intermediate_expression_operand = intermediate_expression.as_operand_id(); - collect_intermediate_expressions.push(intermediate_expression); + let intermediate_expression_operand = intermediate_expression.as_operand(); + self.coverage_counters.intermediate_expressions.push(intermediate_expression); some_sumup_edge_counter_operand.replace(intermediate_expression_operand); } } @@ -392,7 +497,7 @@ impl<'a> BcbCounters<'a> { first_edge_counter_operand, Op::Add, some_sumup_edge_counter_operand.unwrap(), - || Some(format!("{:?}", bcb)), + || Some(format!("{bcb:?}")), ); debug!( "{}{:?} gets a new counter (sum of predecessor counters): {}", @@ -400,43 +505,34 @@ impl<'a> BcbCounters<'a> { bcb, self.format_counter(&counter_kind) ); - self.basic_coverage_blocks[bcb].set_counter(counter_kind) + self.coverage_counters.set_bcb_counter(bcb, counter_kind) } fn get_or_make_edge_counter_operand( &mut self, from_bcb: BasicCoverageBlock, to_bcb: BasicCoverageBlock, - collect_intermediate_expressions: &mut Vec, - ) -> Result { - self.recursive_get_or_make_edge_counter_operand( - from_bcb, - to_bcb, - collect_intermediate_expressions, - 1, - ) + ) -> Result { + self.recursive_get_or_make_edge_counter_operand(from_bcb, to_bcb, 1) } fn recursive_get_or_make_edge_counter_operand( &mut self, from_bcb: BasicCoverageBlock, to_bcb: BasicCoverageBlock, - collect_intermediate_expressions: &mut Vec, debug_indent_level: usize, - ) -> Result { + ) -> Result { // If the source BCB has only one successor (assumed to be the given target), an edge // counter is unnecessary. Just get or make a counter for the source BCB. let successors = self.bcb_successors(from_bcb).iter(); if successors.len() == 1 { - return self.recursive_get_or_make_counter_operand( - from_bcb, - collect_intermediate_expressions, - debug_indent_level + 1, - ); + return self.recursive_get_or_make_counter_operand(from_bcb, debug_indent_level + 1); } // If the edge already has a counter, return it. - if let Some(counter_kind) = self.basic_coverage_blocks[to_bcb].edge_counter_from(from_bcb) { + if let Some(counter_kind) = + self.coverage_counters.bcb_edge_counters.get(&(from_bcb, to_bcb)) + { debug!( "{}Edge {:?}->{:?} already has a counter: {}", NESTED_INDENT.repeat(debug_indent_level), @@ -444,12 +540,12 @@ impl<'a> BcbCounters<'a> { to_bcb, self.format_counter(counter_kind) ); - return Ok(counter_kind.as_operand_id()); + return Ok(counter_kind.as_operand()); } // Make a new counter to count this edge. let counter_kind = - self.coverage_counters.make_counter(|| Some(format!("{:?}->{:?}", from_bcb, to_bcb))); + self.coverage_counters.make_counter(|| Some(format!("{from_bcb:?}->{to_bcb:?}"))); debug!( "{}Edge {:?}->{:?} gets a new counter: {}", NESTED_INDENT.repeat(debug_indent_level), @@ -457,7 +553,7 @@ impl<'a> BcbCounters<'a> { to_bcb, self.format_counter(&counter_kind) ); - self.basic_coverage_blocks[to_bcb].set_edge_counter_from(from_bcb, counter_kind) + self.coverage_counters.set_bcb_edge_counter(from_bcb, to_bcb, counter_kind) } /// Select a branch for the expression, either the recommended `reloop_branch`, or if none was @@ -467,8 +563,7 @@ impl<'a> BcbCounters<'a> { traversal: &TraverseCoverageGraphWithLoops, branches: &[BcbBranch], ) -> BcbBranch { - let branch_needs_a_counter = - |branch: &BcbBranch| branch.counter(&self.basic_coverage_blocks).is_none(); + let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch); let some_reloop_branch = self.find_some_reloop_branch(traversal, &branches); if let Some(reloop_branch_without_counter) = @@ -481,10 +576,8 @@ impl<'a> BcbCounters<'a> { ); reloop_branch_without_counter } else { - let &branch_without_counter = branches - .iter() - .find(|&&branch| branch.counter(&self.basic_coverage_blocks).is_none()) - .expect( + let &branch_without_counter = + branches.iter().find(|&branch| self.branch_has_no_counter(branch)).expect( "needs_branch_counters was `true` so there should be at least one \ branch", ); @@ -511,8 +604,7 @@ impl<'a> BcbCounters<'a> { traversal: &TraverseCoverageGraphWithLoops, branches: &[BcbBranch], ) -> Option { - let branch_needs_a_counter = - |branch: &BcbBranch| branch.counter(&self.basic_coverage_blocks).is_none(); + let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch); let mut some_reloop_branch: Option = None; for context in traversal.context_stack.iter().rev() { @@ -523,7 +615,7 @@ impl<'a> BcbCounters<'a> { self.bcb_dominates(branch.target_bcb, backedge_from_bcb) }) { if let Some(reloop_branch) = some_reloop_branch { - if reloop_branch.counter(&self.basic_coverage_blocks).is_none() { + if self.branch_has_no_counter(&reloop_branch) { // we already found a candidate reloop_branch that still // needs a counter continue; @@ -589,12 +681,24 @@ impl<'a> BcbCounters<'a> { } fn bcb_needs_branch_counters(&self, bcb: BasicCoverageBlock) -> bool { - let branch_needs_a_counter = - |branch: &BcbBranch| branch.counter(&self.basic_coverage_blocks).is_none(); + let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch); let branches = self.bcb_branches(bcb); branches.len() > 1 && branches.iter().any(branch_needs_a_counter) } + fn branch_has_no_counter(&self, branch: &BcbBranch) -> bool { + self.branch_counter(branch).is_none() + } + + fn branch_counter(&self, branch: &BcbBranch) -> Option<&BcbCounter> { + let to_bcb = branch.target_bcb; + if let Some(from_bcb) = branch.edge_from_bcb { + self.coverage_counters.bcb_edge_counters.get(&(from_bcb, to_bcb)) + } else { + self.coverage_counters.bcb_counters[to_bcb].as_ref() + } + } + /// Returns true if the BasicCoverageBlock has zero or one incoming edge. (If zero, it should be /// the entry point for the function.) #[inline] @@ -608,7 +712,7 @@ impl<'a> BcbCounters<'a> { } #[inline] - fn format_counter(&self, counter_kind: &CoverageKind) -> String { + fn format_counter(&self, counter_kind: &BcbCounter) -> String { self.coverage_counters.debug_counters.format_counter(counter_kind) } } diff --git a/compiler/rustc_mir_transform/src/coverage/debug.rs b/compiler/rustc_mir_transform/src/coverage/debug.rs index 6a3d42511ac1b..af616c498fd3a 100644 --- a/compiler/rustc_mir_transform/src/coverage/debug.rs +++ b/compiler/rustc_mir_transform/src/coverage/debug.rs @@ -108,6 +108,7 @@ //! recursively, generating labels with nested operations, enclosed in parentheses //! (for example: `bcb2 + (bcb0 - bcb1)`). +use super::counters::{BcbCounter, CoverageCounters}; use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph}; use super::spans::CoverageSpan; @@ -198,9 +199,9 @@ impl DebugOptions { fn bool_option_val(option: &str, some_strval: Option<&str>) -> bool { if let Some(val) = some_strval { - if vec!["yes", "y", "on", "true"].contains(&val) { + if ["yes", "y", "on", "true"].contains(&val) { true - } else if vec!["no", "n", "off", "false"].contains(&val) { + } else if ["no", "n", "off", "false"].contains(&val) { false } else { bug!( @@ -246,11 +247,11 @@ impl Default for ExpressionFormat { } } -/// If enabled, this struct maintains a map from `CoverageKind` IDs (as `ExpressionOperandId`) to -/// the `CoverageKind` data and optional label (normally, the counter's associated +/// If enabled, this struct maintains a map from `BcbCounter` IDs (as `Operand`) to +/// the `BcbCounter` data and optional label (normally, the counter's associated /// `BasicCoverageBlock` format string, if any). /// -/// Use `format_counter` to convert one of these `CoverageKind` counters to a debug output string, +/// Use `format_counter` to convert one of these `BcbCounter` counters to a debug output string, /// as directed by the `DebugOptions`. This allows the format of counter labels in logs and dump /// files (including the `CoverageGraph` graphviz file) to be changed at runtime, via environment /// variable. @@ -258,7 +259,7 @@ impl Default for ExpressionFormat { /// `DebugCounters` supports a recursive rendering of `Expression` counters, so they can be /// presented as nested expressions such as `(bcb3 - (bcb0 + bcb1))`. pub(super) struct DebugCounters { - some_counters: Option>, + some_counters: Option>, } impl DebugCounters { @@ -275,43 +276,35 @@ impl DebugCounters { self.some_counters.is_some() } - pub fn add_counter(&mut self, counter_kind: &CoverageKind, some_block_label: Option) { + pub fn add_counter(&mut self, counter_kind: &BcbCounter, some_block_label: Option) { if let Some(counters) = &mut self.some_counters { - let id: ExpressionOperandId = match *counter_kind { - CoverageKind::Counter { id, .. } => id.into(), - CoverageKind::Expression { id, .. } => id.into(), - _ => bug!( - "the given `CoverageKind` is not an counter or expression: {:?}", - counter_kind - ), - }; + let id = counter_kind.as_operand(); counters .try_insert(id, DebugCounter::new(counter_kind.clone(), some_block_label)) .expect("attempt to add the same counter_kind to DebugCounters more than once"); } } - pub fn some_block_label(&self, operand: ExpressionOperandId) -> Option<&String> { + pub fn some_block_label(&self, operand: Operand) -> Option<&String> { self.some_counters.as_ref().and_then(|counters| { counters.get(&operand).and_then(|debug_counter| debug_counter.some_block_label.as_ref()) }) } - pub fn format_counter(&self, counter_kind: &CoverageKind) -> String { + pub fn format_counter(&self, counter_kind: &BcbCounter) -> String { match *counter_kind { - CoverageKind::Counter { .. } => { + BcbCounter::Counter { .. } => { format!("Counter({})", self.format_counter_kind(counter_kind)) } - CoverageKind::Expression { .. } => { + BcbCounter::Expression { .. } => { format!("Expression({})", self.format_counter_kind(counter_kind)) } - CoverageKind::Unreachable { .. } => "Unreachable".to_owned(), } } - fn format_counter_kind(&self, counter_kind: &CoverageKind) -> String { + fn format_counter_kind(&self, counter_kind: &BcbCounter) -> String { let counter_format = &debug_options().counter_format; - if let CoverageKind::Expression { id, lhs, op, rhs } = *counter_kind { + if let BcbCounter::Expression { id, lhs, op, rhs } = *counter_kind { if counter_format.operation { return format!( "{}{} {} {}", @@ -330,35 +323,29 @@ impl DebugCounters { } } - let id: ExpressionOperandId = match *counter_kind { - CoverageKind::Counter { id, .. } => id.into(), - CoverageKind::Expression { id, .. } => id.into(), - _ => { - bug!("the given `CoverageKind` is not an counter or expression: {:?}", counter_kind) - } - }; + let id = counter_kind.as_operand(); if self.some_counters.is_some() && (counter_format.block || !counter_format.id) { let counters = self.some_counters.as_ref().unwrap(); if let Some(DebugCounter { some_block_label: Some(block_label), .. }) = counters.get(&id) { return if counter_format.id { - format!("{}#{}", block_label, id.index()) + format!("{}#{:?}", block_label, id) } else { block_label.to_string() }; } } - format!("#{}", id.index()) + format!("#{:?}", id) } - fn format_operand(&self, operand: ExpressionOperandId) -> String { - if operand.index() == 0 { + fn format_operand(&self, operand: Operand) -> String { + if matches!(operand, Operand::Zero) { return String::from("0"); } if let Some(counters) = &self.some_counters { if let Some(DebugCounter { counter_kind, some_block_label }) = counters.get(&operand) { - if let CoverageKind::Expression { .. } = counter_kind { + if let BcbCounter::Expression { .. } = counter_kind { if let Some(label) = some_block_label && debug_options().counter_format.block { return format!( "{}:({})", @@ -371,19 +358,19 @@ impl DebugCounters { return self.format_counter_kind(counter_kind); } } - format!("#{}", operand.index()) + format!("#{:?}", operand) } } /// A non-public support class to `DebugCounters`. #[derive(Debug)] struct DebugCounter { - counter_kind: CoverageKind, + counter_kind: BcbCounter, some_block_label: Option, } impl DebugCounter { - fn new(counter_kind: CoverageKind, some_block_label: Option) -> Self { + fn new(counter_kind: BcbCounter, some_block_label: Option) -> Self { Self { counter_kind, some_block_label } } } @@ -392,9 +379,9 @@ impl DebugCounter { /// a Graphviz (.dot file) representation of the `CoverageGraph`, for debugging purposes. pub(super) struct GraphvizData { some_bcb_to_coverage_spans_with_counters: - Option>>, - some_bcb_to_dependency_counters: Option>>, - some_edge_to_counter: Option>, + Option>>, + some_bcb_to_dependency_counters: Option>>, + some_edge_to_counter: Option>, } impl GraphvizData { @@ -421,7 +408,7 @@ impl GraphvizData { &mut self, bcb: BasicCoverageBlock, coverage_span: &CoverageSpan, - counter_kind: &CoverageKind, + counter_kind: &BcbCounter, ) { if let Some(bcb_to_coverage_spans_with_counters) = self.some_bcb_to_coverage_spans_with_counters.as_mut() @@ -436,7 +423,7 @@ impl GraphvizData { pub fn get_bcb_coverage_spans_with_counters( &self, bcb: BasicCoverageBlock, - ) -> Option<&[(CoverageSpan, CoverageKind)]> { + ) -> Option<&[(CoverageSpan, BcbCounter)]> { if let Some(bcb_to_coverage_spans_with_counters) = self.some_bcb_to_coverage_spans_with_counters.as_ref() { @@ -449,7 +436,7 @@ impl GraphvizData { pub fn add_bcb_dependency_counter( &mut self, bcb: BasicCoverageBlock, - counter_kind: &CoverageKind, + counter_kind: &BcbCounter, ) { if let Some(bcb_to_dependency_counters) = self.some_bcb_to_dependency_counters.as_mut() { bcb_to_dependency_counters @@ -459,7 +446,7 @@ impl GraphvizData { } } - pub fn get_bcb_dependency_counters(&self, bcb: BasicCoverageBlock) -> Option<&[CoverageKind]> { + pub fn get_bcb_dependency_counters(&self, bcb: BasicCoverageBlock) -> Option<&[BcbCounter]> { if let Some(bcb_to_dependency_counters) = self.some_bcb_to_dependency_counters.as_ref() { bcb_to_dependency_counters.get(&bcb).map(Deref::deref) } else { @@ -471,7 +458,7 @@ impl GraphvizData { &mut self, from_bcb: BasicCoverageBlock, to_bb: BasicBlock, - counter_kind: &CoverageKind, + counter_kind: &BcbCounter, ) { if let Some(edge_to_counter) = self.some_edge_to_counter.as_mut() { edge_to_counter @@ -484,7 +471,7 @@ impl GraphvizData { &self, from_bcb: BasicCoverageBlock, to_bb: BasicBlock, - ) -> Option<&CoverageKind> { + ) -> Option<&BcbCounter> { if let Some(edge_to_counter) = self.some_edge_to_counter.as_ref() { edge_to_counter.get(&(from_bcb, to_bb)) } else { @@ -498,10 +485,9 @@ impl GraphvizData { /// _not_ used are retained in the `unused_expressions` Vec, to be included in debug output (logs /// and/or a `CoverageGraph` graphviz output). pub(super) struct UsedExpressions { - some_used_expression_operands: - Option>>, + some_used_expression_operands: Option>>, some_unused_expressions: - Option, BasicCoverageBlock)>>, + Option, BasicCoverageBlock)>>, } impl UsedExpressions { @@ -519,18 +505,18 @@ impl UsedExpressions { self.some_used_expression_operands.is_some() } - pub fn add_expression_operands(&mut self, expression: &CoverageKind) { + pub fn add_expression_operands(&mut self, expression: &BcbCounter) { if let Some(used_expression_operands) = self.some_used_expression_operands.as_mut() { - if let CoverageKind::Expression { id, lhs, rhs, .. } = *expression { + if let BcbCounter::Expression { id, lhs, rhs, .. } = *expression { used_expression_operands.entry(lhs).or_insert_with(Vec::new).push(id); used_expression_operands.entry(rhs).or_insert_with(Vec::new).push(id); } } } - pub fn expression_is_used(&self, expression: &CoverageKind) -> bool { + pub fn expression_is_used(&self, expression: &BcbCounter) -> bool { if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() { - used_expression_operands.contains_key(&expression.as_operand_id()) + used_expression_operands.contains_key(&expression.as_operand()) } else { false } @@ -538,12 +524,12 @@ impl UsedExpressions { pub fn add_unused_expression_if_not_found( &mut self, - expression: &CoverageKind, + expression: &BcbCounter, edge_from_bcb: Option, target_bcb: BasicCoverageBlock, ) { if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() { - if !used_expression_operands.contains_key(&expression.as_operand_id()) { + if !used_expression_operands.contains_key(&expression.as_operand()) { self.some_unused_expressions.as_mut().unwrap().push(( expression.clone(), edge_from_bcb, @@ -553,11 +539,11 @@ impl UsedExpressions { } } - /// Return the list of unused counters (if any) as a tuple with the counter (`CoverageKind`), + /// Return the list of unused counters (if any) as a tuple with the counter (`BcbCounter`), /// optional `from_bcb` (if it was an edge counter), and `target_bcb`. pub fn get_unused_expressions( &self, - ) -> Vec<(CoverageKind, Option, BasicCoverageBlock)> { + ) -> Vec<(BcbCounter, Option, BasicCoverageBlock)> { if let Some(unused_expressions) = self.some_unused_expressions.as_ref() { unused_expressions.clone() } else { @@ -573,7 +559,7 @@ impl UsedExpressions { bcb_counters_without_direct_coverage_spans: &[( Option, BasicCoverageBlock, - CoverageKind, + BcbCounter, )], ) { if self.is_enabled() { @@ -643,7 +629,7 @@ pub(super) fn dump_coverage_spanview<'tcx>( .expect("Unexpected error creating MIR spanview HTML file"); let crate_name = tcx.crate_name(def_id.krate); let item_name = tcx.def_path(def_id).to_filename_friendly_no_crate(); - let title = format!("{}.{} - Coverage Spans", crate_name, item_name); + let title = format!("{crate_name}.{item_name} - Coverage Spans"); spanview::write_document(tcx, body_span, span_viewables, &title, &mut file) .expect("Unexpected IO error dumping coverage spans as HTML"); } @@ -673,18 +659,21 @@ pub(super) fn dump_coverage_graphviz<'tcx>( mir_body: &mir::Body<'tcx>, pass_name: &str, basic_coverage_blocks: &CoverageGraph, - debug_counters: &DebugCounters, + coverage_counters: &CoverageCounters, graphviz_data: &GraphvizData, - intermediate_expressions: &[CoverageKind], + intermediate_expressions: &[BcbCounter], debug_used_expressions: &UsedExpressions, ) { + let debug_counters = &coverage_counters.debug_counters; + let mir_source = mir_body.source; let def_id = mir_source.def_id(); let node_content = |bcb| { bcb_to_string_sections( tcx, mir_body, - debug_counters, + coverage_counters, + bcb, &basic_coverage_blocks[bcb], graphviz_data.get_bcb_coverage_spans_with_counters(bcb), graphviz_data.get_bcb_dependency_counters(bcb), @@ -750,12 +739,15 @@ pub(super) fn dump_coverage_graphviz<'tcx>( fn bcb_to_string_sections<'tcx>( tcx: TyCtxt<'tcx>, mir_body: &mir::Body<'tcx>, - debug_counters: &DebugCounters, + coverage_counters: &CoverageCounters, + bcb: BasicCoverageBlock, bcb_data: &BasicCoverageBlockData, - some_coverage_spans_with_counters: Option<&[(CoverageSpan, CoverageKind)]>, - some_dependency_counters: Option<&[CoverageKind]>, - some_intermediate_expressions: Option<&[CoverageKind]>, + some_coverage_spans_with_counters: Option<&[(CoverageSpan, BcbCounter)]>, + some_dependency_counters: Option<&[BcbCounter]>, + some_intermediate_expressions: Option<&[BcbCounter]>, ) -> Vec { + let debug_counters = &coverage_counters.debug_counters; + let len = bcb_data.basic_blocks.len(); let mut sections = Vec::new(); if let Some(collect_intermediate_expressions) = some_intermediate_expressions { @@ -791,8 +783,8 @@ fn bcb_to_string_sections<'tcx>( .join(" \n"), )); } - if let Some(counter_kind) = &bcb_data.counter_kind { - sections.push(format!("{:?}", counter_kind)); + if let Some(counter_kind) = coverage_counters.bcb_counter(bcb) { + sections.push(format!("{counter_kind:?}")); } let non_term_blocks = bcb_data.basic_blocks[0..len - 1] .iter() diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index ea1223fbca642..59b01ffec0f12 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -1,12 +1,8 @@ -use super::Error; - use itertools::Itertools; -use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::graph::dominators::{self, Dominators}; use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode}; use rustc_index::bit_set::BitSet; use rustc_index::{IndexSlice, IndexVec}; -use rustc_middle::mir::coverage::*; use rustc_middle::mir::{self, BasicBlock, BasicBlockData, Terminator, TerminatorKind}; use std::cmp::Ordering; @@ -15,10 +11,7 @@ use std::ops::{Index, IndexMut}; const ID_SEPARATOR: &str = ","; /// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s -/// nodes are `BasicCoverageBlock`s, which encompass one or more MIR `BasicBlock`s, plus a -/// `CoverageKind` counter (to be added by `CoverageCounters::make_bcb_counters`), and an optional -/// set of additional counters--if needed--to count incoming edges, if there are more than one. -/// (These "edge counters" are eventually converted into new MIR `BasicBlock`s.) +/// nodes are `BasicCoverageBlock`s, which encompass one or more MIR `BasicBlock`s. #[derive(Debug)] pub(super) struct CoverageGraph { bcbs: IndexVec, @@ -195,13 +188,6 @@ impl CoverageGraph { self.bcbs.iter_enumerated() } - #[inline(always)] - pub fn iter_enumerated_mut( - &mut self, - ) -> impl Iterator { - self.bcbs.iter_enumerated_mut() - } - #[inline(always)] pub fn bcb_from_bb(&self, bb: BasicBlock) -> Option { if bb.index() < self.bb_to_bcb.len() { self.bb_to_bcb[bb] } else { None } @@ -320,14 +306,12 @@ rustc_index::newtype_index! { #[derive(Debug, Clone)] pub(super) struct BasicCoverageBlockData { pub basic_blocks: Vec, - pub counter_kind: Option, - edge_from_bcbs: Option>, } impl BasicCoverageBlockData { pub fn from(basic_blocks: Vec) -> Self { assert!(basic_blocks.len() > 0); - Self { basic_blocks, counter_kind: None, edge_from_bcbs: None } + Self { basic_blocks } } #[inline(always)] @@ -345,86 +329,6 @@ impl BasicCoverageBlockData { &mir_body[self.last_bb()].terminator() } - pub fn set_counter( - &mut self, - counter_kind: CoverageKind, - ) -> Result { - debug_assert!( - // If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also - // have an expression (to be injected into an existing `BasicBlock` represented by this - // `BasicCoverageBlock`). - self.edge_from_bcbs.is_none() || counter_kind.is_expression(), - "attempt to add a `Counter` to a BCB target with existing incoming edge counters" - ); - let operand = counter_kind.as_operand_id(); - if let Some(replaced) = self.counter_kind.replace(counter_kind) { - Error::from_string(format!( - "attempt to set a BasicCoverageBlock coverage counter more than once; \ - {:?} already had counter {:?}", - self, replaced, - )) - } else { - Ok(operand) - } - } - - #[inline(always)] - pub fn counter(&self) -> Option<&CoverageKind> { - self.counter_kind.as_ref() - } - - #[inline(always)] - pub fn take_counter(&mut self) -> Option { - self.counter_kind.take() - } - - pub fn set_edge_counter_from( - &mut self, - from_bcb: BasicCoverageBlock, - counter_kind: CoverageKind, - ) -> Result { - if level_enabled!(tracing::Level::DEBUG) { - // If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also - // have an expression (to be injected into an existing `BasicBlock` represented by this - // `BasicCoverageBlock`). - if !self.counter_kind.as_ref().map_or(true, |c| c.is_expression()) { - return Error::from_string(format!( - "attempt to add an incoming edge counter from {:?} when the target BCB already \ - has a `Counter`", - from_bcb - )); - } - } - let operand = counter_kind.as_operand_id(); - if let Some(replaced) = - self.edge_from_bcbs.get_or_insert_default().insert(from_bcb, counter_kind) - { - Error::from_string(format!( - "attempt to set an edge counter more than once; from_bcb: \ - {:?} already had counter {:?}", - from_bcb, replaced, - )) - } else { - Ok(operand) - } - } - - #[inline] - pub fn edge_counter_from(&self, from_bcb: BasicCoverageBlock) -> Option<&CoverageKind> { - if let Some(edge_from_bcbs) = &self.edge_from_bcbs { - edge_from_bcbs.get(&from_bcb) - } else { - None - } - } - - #[inline] - pub fn take_edge_counters( - &mut self, - ) -> Option> { - self.edge_from_bcbs.take().map(|m| m.into_iter()) - } - pub fn id(&self) -> String { format!("@{}", self.basic_blocks.iter().map(|bb| bb.index().to_string()).join(ID_SEPARATOR)) } @@ -454,17 +358,6 @@ impl BcbBranch { Self { edge_from_bcb, target_bcb: to_bcb } } - pub fn counter<'a>( - &self, - basic_coverage_blocks: &'a CoverageGraph, - ) -> Option<&'a CoverageKind> { - if let Some(from_bcb) = self.edge_from_bcb { - basic_coverage_blocks[self.target_bcb].edge_counter_from(from_bcb) - } else { - basic_coverage_blocks[self.target_bcb].counter() - } - } - pub fn is_only_path_to_target(&self) -> bool { self.edge_from_bcb.is_none() } @@ -612,7 +505,7 @@ impl TraverseCoverageGraphWithLoops { the {}", successor_to_add, if let Some(loop_header) = some_loop_header { - format!("worklist for the loop headed by {:?}", loop_header) + format!("worklist for the loop headed by {loop_header:?}") } else { String::from("non-loop worklist") }, @@ -623,7 +516,7 @@ impl TraverseCoverageGraphWithLoops { "{:?} successor is non-branching. Defer it to the end of the {}", successor_to_add, if let Some(loop_header) = some_loop_header { - format!("worklist for the loop headed by {:?}", loop_header) + format!("worklist for the loop headed by {loop_header:?}") } else { String::from("non-loop worklist") }, diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 076e714d70370..8c9eae508b4ae 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -8,9 +8,9 @@ mod spans; #[cfg(test)] mod tests; -use counters::CoverageCounters; -use graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph}; -use spans::{CoverageSpan, CoverageSpans}; +use self::counters::{BcbCounter, CoverageCounters}; +use self::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph}; +use self::spans::{CoverageSpan, CoverageSpans}; use crate::MirPass; @@ -106,6 +106,7 @@ struct Instrumentor<'a, 'tcx> { source_file: Lrc, fn_sig_span: Span, body_span: Span, + function_source_hash: u64, basic_coverage_blocks: CoverageGraph, coverage_counters: CoverageCounters, } @@ -137,6 +138,8 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { let function_source_hash = hash_mir_source(tcx, hir_body); let basic_coverage_blocks = CoverageGraph::from_mir(mir_body); + let coverage_counters = CoverageCounters::new(&basic_coverage_blocks); + Self { pass_name, tcx, @@ -144,8 +147,9 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { source_file, fn_sig_span, body_span, + function_source_hash, basic_coverage_blocks, - coverage_counters: CoverageCounters::new(function_source_hash), + coverage_counters, } } @@ -199,52 +203,47 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { // `BasicCoverageBlock`s not already associated with a `CoverageSpan`. // // Intermediate expressions (used to compute other `Expression` values), which have no - // direct associate to any `BasicCoverageBlock`, are returned in the method `Result`. - let intermediate_expressions_or_error = self + // direct association with any `BasicCoverageBlock`, are accumulated inside `coverage_counters`. + let result = self .coverage_counters .make_bcb_counters(&mut self.basic_coverage_blocks, &coverage_spans); - let (result, intermediate_expressions) = match intermediate_expressions_or_error { - Ok(intermediate_expressions) => { - // If debugging, add any intermediate expressions (which are not associated with any - // BCB) to the `debug_used_expressions` map. - if debug_used_expressions.is_enabled() { - for intermediate_expression in &intermediate_expressions { - debug_used_expressions.add_expression_operands(intermediate_expression); - } + if let Ok(()) = result { + // If debugging, add any intermediate expressions (which are not associated with any + // BCB) to the `debug_used_expressions` map. + if debug_used_expressions.is_enabled() { + for intermediate_expression in &self.coverage_counters.intermediate_expressions { + debug_used_expressions.add_expression_operands(intermediate_expression); } - - //////////////////////////////////////////////////// - // Remove the counter or edge counter from of each `CoverageSpan`s associated - // `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR. - // - // `Coverage` statements injected from `CoverageSpan`s will include the code regions - // (source code start and end positions) to be counted by the associated counter. - // - // These `CoverageSpan`-associated counters are removed from their associated - // `BasicCoverageBlock`s so that the only remaining counters in the `CoverageGraph` - // are indirect counters (to be injected next, without associated code regions). - self.inject_coverage_span_counters( - coverage_spans, - &mut graphviz_data, - &mut debug_used_expressions, - ); - - //////////////////////////////////////////////////// - // For any remaining `BasicCoverageBlock` counters (that were not associated with - // any `CoverageSpan`), inject `Coverage` statements (_without_ code region `Span`s) - // to ensure `BasicCoverageBlock` counters that other `Expression`s may depend on - // are in fact counted, even though they don't directly contribute to counting - // their own independent code region's coverage. - self.inject_indirect_counters(&mut graphviz_data, &mut debug_used_expressions); - - // Intermediate expressions will be injected as the final step, after generating - // debug output, if any. - //////////////////////////////////////////////////// - - (Ok(()), intermediate_expressions) } - Err(e) => (Err(e), Vec::new()), + + //////////////////////////////////////////////////// + // Remove the counter or edge counter from of each `CoverageSpan`s associated + // `BasicCoverageBlock`, and inject a `Coverage` statement into the MIR. + // + // `Coverage` statements injected from `CoverageSpan`s will include the code regions + // (source code start and end positions) to be counted by the associated counter. + // + // These `CoverageSpan`-associated counters are removed from their associated + // `BasicCoverageBlock`s so that the only remaining counters in the `CoverageGraph` + // are indirect counters (to be injected next, without associated code regions). + self.inject_coverage_span_counters( + coverage_spans, + &mut graphviz_data, + &mut debug_used_expressions, + ); + + //////////////////////////////////////////////////// + // For any remaining `BasicCoverageBlock` counters (that were not associated with + // any `CoverageSpan`), inject `Coverage` statements (_without_ code region `Span`s) + // to ensure `BasicCoverageBlock` counters that other `Expression`s may depend on + // are in fact counted, even though they don't directly contribute to counting + // their own independent code region's coverage. + self.inject_indirect_counters(&mut graphviz_data, &mut debug_used_expressions); + + // Intermediate expressions will be injected as the final step, after generating + // debug output, if any. + //////////////////////////////////////////////////// }; if graphviz_data.is_enabled() { @@ -255,9 +254,9 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { self.mir_body, self.pass_name, &self.basic_coverage_blocks, - &self.coverage_counters.debug_counters, + &self.coverage_counters, &graphviz_data, - &intermediate_expressions, + &self.coverage_counters.intermediate_expressions, &debug_used_expressions, ); } @@ -273,8 +272,11 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { //////////////////////////////////////////////////// // Finally, inject the intermediate expressions collected along the way. - for intermediate_expression in intermediate_expressions { - inject_intermediate_expression(self.mir_body, intermediate_expression); + for intermediate_expression in &self.coverage_counters.intermediate_expressions { + inject_intermediate_expression( + self.mir_body, + self.make_mir_coverage_kind(intermediate_expression), + ); } } @@ -303,8 +305,8 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { let span = covspan.span; let counter_kind = if let Some(&counter_operand) = bcb_counters[bcb].as_ref() { self.coverage_counters.make_identity_counter(counter_operand) - } else if let Some(counter_kind) = self.bcb_data_mut(bcb).take_counter() { - bcb_counters[bcb] = Some(counter_kind.as_operand_id()); + } else if let Some(counter_kind) = self.coverage_counters.take_bcb_counter(bcb) { + bcb_counters[bcb] = Some(counter_kind.as_operand()); debug_used_expressions.add_expression_operands(&counter_kind); counter_kind } else { @@ -312,19 +314,14 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { }; graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind); - debug!( - "Calling make_code_region(file_name={}, source_file={:?}, span={}, body_span={})", - file_name, - self.source_file, - source_map.span_to_diagnostic_string(span), - source_map.span_to_diagnostic_string(body_span) - ); + let code_region = + make_code_region(source_map, file_name, &self.source_file, span, body_span); inject_statement( self.mir_body, - counter_kind, + self.make_mir_coverage_kind(&counter_kind), self.bcb_leader_bb(bcb), - Some(make_code_region(source_map, file_name, &self.source_file, span, body_span)), + Some(code_region), ); } } @@ -343,19 +340,17 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { debug_used_expressions: &mut debug::UsedExpressions, ) { let mut bcb_counters_without_direct_coverage_spans = Vec::new(); - for (target_bcb, target_bcb_data) in self.basic_coverage_blocks.iter_enumerated_mut() { - if let Some(counter_kind) = target_bcb_data.take_counter() { - bcb_counters_without_direct_coverage_spans.push((None, target_bcb, counter_kind)); - } - if let Some(edge_counters) = target_bcb_data.take_edge_counters() { - for (from_bcb, counter_kind) in edge_counters { - bcb_counters_without_direct_coverage_spans.push(( - Some(from_bcb), - target_bcb, - counter_kind, - )); - } - } + for (target_bcb, counter_kind) in self.coverage_counters.drain_bcb_counters() { + bcb_counters_without_direct_coverage_spans.push((None, target_bcb, counter_kind)); + } + for ((from_bcb, target_bcb), counter_kind) in + self.coverage_counters.drain_bcb_edge_counters() + { + bcb_counters_without_direct_coverage_spans.push(( + Some(from_bcb), + target_bcb, + counter_kind, + )); } // If debug is enabled, validate that every BCB or edge counter not directly associated @@ -372,7 +367,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { ); match counter_kind { - CoverageKind::Counter { .. } => { + BcbCounter::Counter { .. } => { let inject_to_bb = if let Some(from_bcb) = edge_from_bcb { // The MIR edge starts `from_bb` (the outgoing / last BasicBlock in // `from_bcb`) and ends at `to_bb` (the incoming / first BasicBlock in the @@ -405,12 +400,17 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { target_bb }; - inject_statement(self.mir_body, counter_kind, inject_to_bb, None); + inject_statement( + self.mir_body, + self.make_mir_coverage_kind(&counter_kind), + inject_to_bb, + None, + ); } - CoverageKind::Expression { .. } => { - inject_intermediate_expression(self.mir_body, counter_kind) - } - _ => bug!("CoverageKind should be a counter"), + BcbCounter::Expression { .. } => inject_intermediate_expression( + self.mir_body, + self.make_mir_coverage_kind(&counter_kind), + ), } } } @@ -431,13 +431,19 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> { } #[inline] - fn bcb_data_mut(&mut self, bcb: BasicCoverageBlock) -> &mut BasicCoverageBlockData { - &mut self.basic_coverage_blocks[bcb] + fn format_counter(&self, counter_kind: &BcbCounter) -> String { + self.coverage_counters.debug_counters.format_counter(counter_kind) } - #[inline] - fn format_counter(&self, counter_kind: &CoverageKind) -> String { - self.coverage_counters.debug_counters.format_counter(counter_kind) + fn make_mir_coverage_kind(&self, counter_kind: &BcbCounter) -> CoverageKind { + match *counter_kind { + BcbCounter::Counter { id } => { + CoverageKind::Counter { function_source_hash: self.function_source_hash, id } + } + BcbCounter::Expression { id, lhs, op, rhs } => { + CoverageKind::Expression { id, lhs, op, rhs } + } + } } } @@ -508,6 +514,14 @@ fn make_code_region( span: Span, body_span: Span, ) -> CodeRegion { + debug!( + "Called make_code_region(file_name={}, source_file={:?}, span={}, body_span={})", + file_name, + source_file, + source_map.span_to_diagnostic_string(span), + source_map.span_to_diagnostic_string(body_span) + ); + let (start_line, mut start_col) = source_file.lookup_file_pos(span.lo()); let (end_line, end_col) = if span.hi() == span.lo() { let (end_line, mut end_col) = (start_line, start_col); diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs index 74b4b4a07c550..aa205655f9dab 100644 --- a/compiler/rustc_mir_transform/src/coverage/query.rs +++ b/compiler/rustc_mir_transform/src/coverage/query.rs @@ -43,43 +43,25 @@ struct CoverageVisitor { } impl CoverageVisitor { - /// Updates `num_counters` to the maximum encountered zero-based counter_id plus 1. Note the - /// final computed number of counters should be the number of all `CoverageKind::Counter` - /// statements in the MIR *plus one* for the implicit `ZERO` counter. + /// Updates `num_counters` to the maximum encountered counter ID plus 1. #[inline(always)] - fn update_num_counters(&mut self, counter_id: u32) { + fn update_num_counters(&mut self, counter_id: CounterId) { + let counter_id = counter_id.as_u32(); self.info.num_counters = std::cmp::max(self.info.num_counters, counter_id + 1); } - /// Computes an expression index for each expression ID, and updates `num_expressions` to the - /// maximum encountered index plus 1. + /// Updates `num_expressions` to the maximum encountered expression ID plus 1. #[inline(always)] - fn update_num_expressions(&mut self, expression_id: u32) { - let expression_index = u32::MAX - expression_id; - self.info.num_expressions = std::cmp::max(self.info.num_expressions, expression_index + 1); + fn update_num_expressions(&mut self, expression_id: ExpressionId) { + let expression_id = expression_id.as_u32(); + self.info.num_expressions = std::cmp::max(self.info.num_expressions, expression_id + 1); } - fn update_from_expression_operand(&mut self, operand_id: u32) { - if operand_id >= self.info.num_counters { - let operand_as_expression_index = u32::MAX - operand_id; - if operand_as_expression_index >= self.info.num_expressions { - // The operand ID is outside the known range of counter IDs and also outside the - // known range of expression IDs. In either case, the result of a missing operand - // (if and when used in an expression) will be zero, so from a computation - // perspective, it doesn't matter whether it is interpreted as a counter or an - // expression. - // - // However, the `num_counters` and `num_expressions` query results are used to - // allocate arrays when generating the coverage map (during codegen), so choose - // the type that grows either `num_counters` or `num_expressions` the least. - if operand_id - self.info.num_counters - < operand_as_expression_index - self.info.num_expressions - { - self.update_num_counters(operand_id) - } else { - self.update_num_expressions(operand_id) - } - } + fn update_from_expression_operand(&mut self, operand: Operand) { + match operand { + Operand::Counter(id) => self.update_num_counters(id), + Operand::Expression(id) => self.update_num_expressions(id), + Operand::Zero => {} } } @@ -100,19 +82,15 @@ impl CoverageVisitor { if self.add_missing_operands { match coverage.kind { CoverageKind::Expression { lhs, rhs, .. } => { - self.update_from_expression_operand(u32::from(lhs)); - self.update_from_expression_operand(u32::from(rhs)); + self.update_from_expression_operand(lhs); + self.update_from_expression_operand(rhs); } _ => {} } } else { match coverage.kind { - CoverageKind::Counter { id, .. } => { - self.update_num_counters(u32::from(id)); - } - CoverageKind::Expression { id, .. } => { - self.update_num_expressions(u32::from(id)); - } + CoverageKind::Counter { id, .. } => self.update_num_counters(id), + CoverageKind::Expression { id, .. } => self.update_num_expressions(id), _ => {} } } @@ -123,8 +101,7 @@ fn coverageinfo<'tcx>(tcx: TyCtxt<'tcx>, instance_def: ty::InstanceDef<'tcx>) -> let mir_body = tcx.instance_mir(instance_def); let mut coverage_visitor = CoverageVisitor { - // num_counters always has at least the `ZERO` counter. - info: CoverageInfo { num_counters: 1, num_expressions: 0 }, + info: CoverageInfo { num_counters: 0, num_expressions: 0 }, add_missing_operands: false, }; diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index d27200419e2cb..deebf5345bac9 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -11,7 +11,7 @@ use rustc_middle::ty::TyCtxt; use rustc_span::source_map::original_sp; use rustc_span::{BytePos, ExpnKind, MacroKind, Span, Symbol}; -use std::cell::RefCell; +use std::cell::OnceCell; use std::cmp::Ordering; #[derive(Debug, Copy, Clone)] @@ -67,7 +67,7 @@ impl CoverageStatement { pub(super) struct CoverageSpan { pub span: Span, pub expn_span: Span, - pub current_macro_or_none: RefCell>>, + pub current_macro_or_none: OnceCell>, pub bcb: BasicCoverageBlock, pub coverage_statements: Vec, pub is_closure: bool, @@ -175,8 +175,7 @@ impl CoverageSpan { /// If the span is part of a macro, returns the macro name symbol. pub fn current_macro(&self) -> Option { self.current_macro_or_none - .borrow_mut() - .get_or_insert_with(|| { + .get_or_init(|| { if let ExpnKind::Macro(MacroKind::Bang, current_macro) = self.expn_span.ctxt().outer_expn_data().kind { @@ -480,9 +479,10 @@ impl<'a, 'tcx> CoverageSpans<'a, 'tcx> { fn check_invoked_macro_name_span(&mut self) { if let Some(visible_macro) = self.curr().visible_macro(self.body_span) { - if self.prev_expn_span.map_or(true, |prev_expn_span| { - self.curr().expn_span.ctxt() != prev_expn_span.ctxt() - }) { + if !self + .prev_expn_span + .is_some_and(|prev_expn_span| self.curr().expn_span.ctxt() == prev_expn_span.ctxt()) + { let merged_prefix_len = self.curr_original_span.lo() - self.curr().span.lo(); let after_macro_bang = merged_prefix_len + BytePos(visible_macro.as_str().len() as u32 + 1); diff --git a/compiler/rustc_mir_transform/src/coverage/test_macros/src/lib.rs b/compiler/rustc_mir_transform/src/coverage/test_macros/src/lib.rs index 3d6095d2738cb..f41adf667ec55 100644 --- a/compiler/rustc_mir_transform/src/coverage/test_macros/src/lib.rs +++ b/compiler/rustc_mir_transform/src/coverage/test_macros/src/lib.rs @@ -2,5 +2,5 @@ use proc_macro::TokenStream; #[proc_macro] pub fn let_bcb(item: TokenStream) -> TokenStream { - format!("let bcb{} = graph::BasicCoverageBlock::from_usize({});", item, item).parse().unwrap() + format!("let bcb{item} = graph::BasicCoverageBlock::from_usize({item});").parse().unwrap() } diff --git a/compiler/rustc_mir_transform/src/coverage/tests.rs b/compiler/rustc_mir_transform/src/coverage/tests.rs index 90b58933df7c0..4a066ed3abdea 100644 --- a/compiler/rustc_mir_transform/src/coverage/tests.rs +++ b/compiler/rustc_mir_transform/src/coverage/tests.rs @@ -34,7 +34,6 @@ use itertools::Itertools; use rustc_data_structures::graph::WithNumNodes; use rustc_data_structures::graph::WithSuccessors; use rustc_index::{Idx, IndexVec}; -use rustc_middle::mir::coverage::CoverageKind; use rustc_middle::mir::*; use rustc_middle::ty; use rustc_span::{self, BytePos, Pos, Span, DUMMY_SP}; @@ -140,7 +139,7 @@ impl<'tcx> MockBlocks<'tcx> { destination: self.dummy_place.clone(), target: Some(TEMP_BLOCK), unwind: UnwindAction::Continue, - from_hir_call: false, + call_source: CallSource::Misc, fn_span: DUMMY_SP, }, ) @@ -675,17 +674,17 @@ fn test_make_bcb_counters() { )); } } - let mut coverage_counters = counters::CoverageCounters::new(0); - let intermediate_expressions = coverage_counters + let mut coverage_counters = counters::CoverageCounters::new(&basic_coverage_blocks); + coverage_counters .make_bcb_counters(&mut basic_coverage_blocks, &coverage_spans) .expect("should be Ok"); - assert_eq!(intermediate_expressions.len(), 0); + assert_eq!(coverage_counters.intermediate_expressions.len(), 0); let_bcb!(1); assert_eq!( - 1, // coincidentally, bcb1 has a `Counter` with id = 1 - match basic_coverage_blocks[bcb1].counter().expect("should have a counter") { - CoverageKind::Counter { id, .. } => id, + 0, // bcb1 has a `Counter` with id = 0 + match coverage_counters.bcb_counter(bcb1).expect("should have a counter") { + counters::BcbCounter::Counter { id, .. } => id, _ => panic!("expected a Counter"), } .as_u32() @@ -693,9 +692,9 @@ fn test_make_bcb_counters() { let_bcb!(2); assert_eq!( - 2, // coincidentally, bcb2 has a `Counter` with id = 2 - match basic_coverage_blocks[bcb2].counter().expect("should have a counter") { - CoverageKind::Counter { id, .. } => id, + 1, // bcb2 has a `Counter` with id = 1 + match coverage_counters.bcb_counter(bcb2).expect("should have a counter") { + counters::BcbCounter::Counter { id, .. } => id, _ => panic!("expected a Counter"), } .as_u32() diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index 7adfc9dff2ae9..8f4dc9f69e9ab 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -10,8 +10,10 @@ use rustc_middle::mir::visit::{MutVisitor, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_mir_dataflow::value_analysis::{Map, State, TrackElem, ValueAnalysis, ValueOrPlace}; -use rustc_mir_dataflow::{lattice::FlatSet, Analysis, ResultsVisitor, SwitchIntEdgeEffects}; +use rustc_mir_dataflow::value_analysis::{ + Map, State, TrackElem, ValueAnalysis, ValueAnalysisWrapper, ValueOrPlace, +}; +use rustc_mir_dataflow::{lattice::FlatSet, Analysis, Results, ResultsVisitor}; use rustc_span::DUMMY_SP; use rustc_target::abi::{Align, FieldIdx, VariantIdx}; @@ -52,11 +54,11 @@ impl<'tcx> MirPass<'tcx> for DataflowConstProp { // Perform the actual dataflow analysis. let analysis = ConstAnalysis::new(tcx, body, map); - let results = debug_span!("analyze") + let mut results = debug_span!("analyze") .in_scope(|| analysis.wrap().into_engine(tcx, body).iterate_to_fixpoint()); // Collect results and patch the body afterwards. - let mut visitor = CollectAndPatch::new(tcx, &results.analysis.0.map); + let mut visitor = CollectAndPatch::new(tcx); debug_span!("collect").in_scope(|| results.visit_reachable_with(body, &mut visitor)); debug_span!("patch").in_scope(|| visitor.visit_body(body)); } @@ -245,49 +247,27 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> { .unwrap_or(FlatSet::Top) } - fn handle_switch_int( + fn handle_switch_int<'mir>( &self, - discr: &Operand<'tcx>, - apply_edge_effects: &mut impl SwitchIntEdgeEffects>, - ) { - // FIXME: The dataflow framework only provides the state if we call `apply()`, which makes - // this more inefficient than it has to be. - let mut discr_value = None; - let mut handled = false; - apply_edge_effects.apply(|state, target| { - let discr_value = match discr_value { - Some(value) => value, - None => { - let value = match self.handle_operand(discr, state) { - ValueOrPlace::Value(value) => value, - ValueOrPlace::Place(place) => state.get_idx(place, self.map()), - }; - let result = match value { - FlatSet::Top => FlatSet::Top, - FlatSet::Elem(ScalarTy(scalar, _)) => { - let int = scalar.assert_int(); - FlatSet::Elem(int.assert_bits(int.size())) - } - FlatSet::Bottom => FlatSet::Bottom, - }; - discr_value = Some(result); - result - } - }; - - let FlatSet::Elem(choice) = discr_value else { - // Do nothing if we don't know which branch will be taken. - return - }; - - if target.value.map(|n| n == choice).unwrap_or(!handled) { - // Branch is taken. Has no effect on state. - handled = true; - } else { - // Branch is not taken. - state.mark_unreachable(); + discr: &'mir Operand<'tcx>, + targets: &'mir SwitchTargets, + state: &mut State, + ) -> TerminatorEdges<'mir, 'tcx> { + let value = match self.handle_operand(discr, state) { + ValueOrPlace::Value(value) => value, + ValueOrPlace::Place(place) => state.get_idx(place, self.map()), + }; + match value { + // We are branching on uninitialized data, this is UB, treat it as unreachable. + // This allows the set of visited edges to grow monotonically with the lattice. + FlatSet::Bottom => TerminatorEdges::None, + FlatSet::Elem(ScalarTy(scalar, _)) => { + let int = scalar.assert_int(); + let choice = int.assert_bits(int.size()); + TerminatorEdges::Single(targets.target_for_value(choice)) } - }) + FlatSet::Top => TerminatorEdges::SwitchInt { discr, targets }, + } } } @@ -387,9 +367,8 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> { } } -struct CollectAndPatch<'tcx, 'map> { +struct CollectAndPatch<'tcx> { tcx: TyCtxt<'tcx>, - map: &'map Map, /// For a given MIR location, this stores the values of the operands used by that location. In /// particular, this is before the effect, such that the operands of `_1 = _1 + _2` are @@ -400,9 +379,9 @@ struct CollectAndPatch<'tcx, 'map> { assignments: FxHashMap>, } -impl<'tcx, 'map> CollectAndPatch<'tcx, 'map> { - fn new(tcx: TyCtxt<'tcx>, map: &'map Map) -> Self { - Self { tcx, map, before_effect: FxHashMap::default(), assignments: FxHashMap::default() } +impl<'tcx> CollectAndPatch<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> Self { + Self { tcx, before_effect: FxHashMap::default(), assignments: FxHashMap::default() } } fn make_operand(&self, scalar: ScalarTy<'tcx>) -> Operand<'tcx> { @@ -414,18 +393,23 @@ impl<'tcx, 'map> CollectAndPatch<'tcx, 'map> { } } -impl<'mir, 'tcx, 'map> ResultsVisitor<'mir, 'tcx> for CollectAndPatch<'tcx, 'map> { +impl<'mir, 'tcx> + ResultsVisitor<'mir, 'tcx, Results<'tcx, ValueAnalysisWrapper>>> + for CollectAndPatch<'tcx> +{ type FlowState = State>>; fn visit_statement_before_primary_effect( &mut self, + results: &Results<'tcx, ValueAnalysisWrapper>>, state: &Self::FlowState, statement: &'mir Statement<'tcx>, location: Location, ) { match &statement.kind { StatementKind::Assign(box (_, rvalue)) => { - OperandCollector { state, visitor: self }.visit_rvalue(rvalue, location); + OperandCollector { state, visitor: self, map: &results.analysis.0.map } + .visit_rvalue(rvalue, location); } _ => (), } @@ -433,6 +417,7 @@ impl<'mir, 'tcx, 'map> ResultsVisitor<'mir, 'tcx> for CollectAndPatch<'tcx, 'map fn visit_statement_after_primary_effect( &mut self, + results: &Results<'tcx, ValueAnalysisWrapper>>, state: &Self::FlowState, statement: &'mir Statement<'tcx>, location: Location, @@ -441,30 +426,34 @@ impl<'mir, 'tcx, 'map> ResultsVisitor<'mir, 'tcx> for CollectAndPatch<'tcx, 'map StatementKind::Assign(box (_, Rvalue::Use(Operand::Constant(_)))) => { // Don't overwrite the assignment if it already uses a constant (to keep the span). } - StatementKind::Assign(box (place, _)) => match state.get(place.as_ref(), self.map) { - FlatSet::Top => (), - FlatSet::Elem(value) => { - self.assignments.insert(location, value); - } - FlatSet::Bottom => { - // This assignment is either unreachable, or an uninitialized value is assigned. + StatementKind::Assign(box (place, _)) => { + match state.get(place.as_ref(), &results.analysis.0.map) { + FlatSet::Top => (), + FlatSet::Elem(value) => { + self.assignments.insert(location, value); + } + FlatSet::Bottom => { + // This assignment is either unreachable, or an uninitialized value is assigned. + } } - }, + } _ => (), } } fn visit_terminator_before_primary_effect( &mut self, + results: &Results<'tcx, ValueAnalysisWrapper>>, state: &Self::FlowState, terminator: &'mir Terminator<'tcx>, location: Location, ) { - OperandCollector { state, visitor: self }.visit_terminator(terminator, location); + OperandCollector { state, visitor: self, map: &results.analysis.0.map } + .visit_terminator(terminator, location); } } -impl<'tcx, 'map> MutVisitor<'tcx> for CollectAndPatch<'tcx, 'map> { +impl<'tcx> MutVisitor<'tcx> for CollectAndPatch<'tcx> { fn tcx<'a>(&'a self) -> TyCtxt<'tcx> { self.tcx } @@ -496,14 +485,15 @@ impl<'tcx, 'map> MutVisitor<'tcx> for CollectAndPatch<'tcx, 'map> { struct OperandCollector<'tcx, 'map, 'a> { state: &'a State>>, - visitor: &'a mut CollectAndPatch<'tcx, 'map>, + visitor: &'a mut CollectAndPatch<'tcx>, + map: &'map Map, } impl<'tcx, 'map, 'a> Visitor<'tcx> for OperandCollector<'tcx, 'map, 'a> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { match operand { Operand::Copy(place) | Operand::Move(place) => { - match self.state.get(place.as_ref(), self.visitor.map) { + match self.state.get(place.as_ref(), self.map) { FlatSet::Top => (), FlatSet::Elem(value) => { self.visitor.before_effect.insert((location, *place), value); @@ -518,7 +508,7 @@ impl<'tcx, 'map, 'a> Visitor<'tcx> for OperandCollector<'tcx, 'map, 'a> { struct DummyMachine; -impl<'mir, 'tcx> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachine { +impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachine { rustc_const_eval::interpret::compile_time_machine!(<'mir, 'tcx>); type MemoryKind = !; const PANIC_ON_ALLOC_FAIL: bool = true; @@ -543,7 +533,7 @@ impl<'mir, 'tcx> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachi _ecx: &mut InterpCx<'mir, 'tcx, Self>, _instance: ty::Instance<'tcx>, _abi: rustc_target::spec::abi::Abi, - _args: &[rustc_const_eval::interpret::OpTy<'tcx, Self::Provenance>], + _args: &[rustc_const_eval::interpret::FnArg<'tcx, Self::Provenance>], _destination: &rustc_const_eval::interpret::PlaceTy<'tcx, Self::Provenance>, _target: Option, _unwind: UnwindAction, diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs index 7bc5183a00a82..3f988930b5e66 100644 --- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs +++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs @@ -13,9 +13,12 @@ //! use rustc_index::bit_set::BitSet; +use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; -use rustc_mir_dataflow::impls::{borrowed_locals, MaybeTransitiveLiveLocals}; +use rustc_mir_dataflow::impls::{ + borrowed_locals, LivenessTransferFunction, MaybeTransitiveLiveLocals, +}; use rustc_mir_dataflow::Analysis; /// Performs the optimization on the body @@ -28,8 +31,33 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS .iterate_to_fixpoint() .into_results_cursor(body); + // For blocks with a call terminator, if an argument copy can be turned into a move, + // record it as (block, argument index). + let mut call_operands_to_move = Vec::new(); let mut patch = Vec::new(); + for (bb, bb_data) in traversal::preorder(body) { + if let TerminatorKind::Call { ref args, .. } = bb_data.terminator().kind { + let loc = Location { block: bb, statement_index: bb_data.statements.len() }; + + // Position ourselves between the evaluation of `args` and the write to `destination`. + live.seek_to_block_end(bb); + let mut state = live.get().clone(); + + for (index, arg) in args.iter().enumerate().rev() { + if let Operand::Copy(place) = *arg + && !place.is_indirect() + && !borrowed.contains(place.local) + && !state.contains(place.local) + { + call_operands_to_move.push((bb, index)); + } + + // Account that `arg` is read from, so we don't promote another argument to a move. + LivenessTransferFunction(&mut state).visit_operand(arg, loc); + } + } + for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { let loc = Location { block: bb, statement_index }; if let StatementKind::Assign(assign) = &statement.kind { @@ -64,7 +92,7 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS } } - if patch.is_empty() { + if patch.is_empty() && call_operands_to_move.is_empty() { return; } @@ -72,6 +100,14 @@ pub fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>, borrowed: &BitS for Location { block, statement_index } in patch { bbs[block].statements[statement_index].make_nop(); } + for (block, argument_index) in call_operands_to_move { + let TerminatorKind::Call { ref mut args, .. } = bbs[block].terminator_mut().kind else { + bug!() + }; + let arg = &mut args[argument_index]; + let Operand::Copy(place) = *arg else { bug!() }; + *arg = Operand::Move(place); + } crate::simplify::simplify_locals(body, tcx) } diff --git a/compiler/rustc_mir_transform/src/deduce_param_attrs.rs b/compiler/rustc_mir_transform/src/deduce_param_attrs.rs index a133c9d4782c8..79645310a3987 100644 --- a/compiler/rustc_mir_transform/src/deduce_param_attrs.rs +++ b/compiler/rustc_mir_transform/src/deduce_param_attrs.rs @@ -166,7 +166,7 @@ pub fn deduced_param_attrs<'tcx>( // Codegen won't use this information for anything if all the function parameters are passed // directly. Detect that and bail, for compilation speed. - let fn_ty = tcx.type_of(def_id).subst_identity(); + let fn_ty = tcx.type_of(def_id).instantiate_identity(); if matches!(fn_ty.kind(), ty::FnDef(..)) { if fn_ty .fn_sig(tcx) @@ -203,7 +203,12 @@ pub fn deduced_param_attrs<'tcx>( body.local_decls.iter().skip(1).take(body.arg_count).enumerate().map( |(arg_index, local_decl)| DeducedParamAttrs { read_only: !deduce_read_only.mutable_args.contains(arg_index) - && local_decl.ty.is_freeze(tcx, param_env), + // We must normalize here to reveal opaques and normalize + // their substs, otherwise we'll see exponential blow-up in + // compile times: #113372 + && tcx + .normalize_erasing_regions(param_env, local_decl.ty) + .is_freeze(tcx, param_env), }, ), ); diff --git a/compiler/rustc_mir_transform/src/deref_separator.rs b/compiler/rustc_mir_transform/src/deref_separator.rs index a39026751a783..95898b5b73cdb 100644 --- a/compiler/rustc_mir_transform/src/deref_separator.rs +++ b/compiler/rustc_mir_transform/src/deref_separator.rs @@ -8,13 +8,13 @@ use rustc_middle::ty::TyCtxt; pub struct Derefer; -pub struct DerefChecker<'tcx> { +pub struct DerefChecker<'a, 'tcx> { tcx: TyCtxt<'tcx>, patcher: MirPatch<'tcx>, - local_decls: IndexVec>, + local_decls: &'a IndexVec>, } -impl<'tcx> MutVisitor<'tcx> for DerefChecker<'tcx> { +impl<'a, 'tcx> MutVisitor<'tcx> for DerefChecker<'a, 'tcx> { fn tcx(&self) -> TyCtxt<'tcx> { self.tcx } @@ -36,7 +36,7 @@ impl<'tcx> MutVisitor<'tcx> for DerefChecker<'tcx> { for (idx, (p_ref, p_elem)) in place.iter_projections().enumerate() { if !p_ref.projection.is_empty() && p_elem == ProjectionElem::Deref { - let ty = p_ref.ty(&self.local_decls, self.tcx).ty; + let ty = p_ref.ty(self.local_decls, self.tcx).ty; let temp = self.patcher.new_internal_with_info( ty, self.local_decls[p_ref.local].source_info.span, @@ -70,7 +70,7 @@ impl<'tcx> MutVisitor<'tcx> for DerefChecker<'tcx> { pub fn deref_finder<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let patch = MirPatch::new(body); - let mut checker = DerefChecker { tcx, patcher: patch, local_decls: body.local_decls.clone() }; + let mut checker = DerefChecker { tcx, patcher: patch, local_decls: &body.local_decls }; for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() { checker.visit_basic_block_data(bb, data); diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index 78758e2db28ab..b73b72c3192ea 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -37,6 +37,10 @@ //! if they do not consistently refer to the same place in memory. This is satisfied if they do //! not contain any indirection through a pointer or any indexing projections. //! +//! * `p` and `q` must have the **same type**. If we replace a local with a subtype or supertype, +//! we may end up with a differnet vtable for that local. See the `subtyping-impacts-selection` +//! tests for an example where that causes issues. +//! //! * We need to make sure that the goal of "merging the memory" is actually structurally possible //! in MIR. For example, even if all the other conditions are satisfied, there is no way to //! "merge" `_5.foo` and `_6.bar`. For now, we ensure this by requiring that both `p` and `q` are @@ -134,6 +138,7 @@ use crate::MirPass; use rustc_data_structures::fx::FxHashMap; use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; +use rustc_middle::mir::HasLocalDecls; use rustc_middle::mir::{dump_mir, PassWhere}; use rustc_middle::mir::{ traversal, Body, InlineAsmOperand, Local, LocalKind, Location, Operand, Place, Rvalue, @@ -213,9 +218,9 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation { if merged_locals.contains(*src) { continue; } - let Some(dest) = - candidates.iter().find(|dest| !merged_locals.contains(**dest)) else { - continue; + let Some(dest) = candidates.iter().find(|dest| !merged_locals.contains(**dest)) + else { + continue; }; if !tcx.consider_optimizing(|| { format!("{} round {}", tcx.def_path_str(def_id), round_count) @@ -596,9 +601,7 @@ impl WriteInfo { rhs: &Operand<'tcx>, body: &Body<'tcx>, ) { - let Some(rhs) = rhs.place() else { - return - }; + let Some(rhs) = rhs.place() else { return }; if let Some(pair) = places_to_candidate_pair(lhs, rhs, body) { self.skip_pair = Some(pair); } @@ -763,12 +766,22 @@ impl<'tcx> Visitor<'tcx> for FindAssignments<'_, '_, 'tcx> { return; }; - // As described at the top of the file, we do not go near things that have their address - // taken. + // As described at the top of the file, we do not go near things that have + // their address taken. if self.borrowed.contains(src) || self.borrowed.contains(dest) { return; } + // As described at the top of this file, we do not touch locals which have + // different types. + let src_ty = self.body.local_decls()[src].ty; + let dest_ty = self.body.local_decls()[dest].ty; + if src_ty != dest_ty { + // FIXME(#112651): This can be removed afterwards. Also update the module description. + trace!("skipped `{src:?} = {dest:?}` due to subtyping: {src_ty} != {dest_ty}"); + return; + } + // Also, we need to make sure that MIR actually allows the `src` to be removed if is_local_required(src, self.body) { return; diff --git a/compiler/rustc_mir_transform/src/dump_mir.rs b/compiler/rustc_mir_transform/src/dump_mir.rs index 746e3d9652db6..13841be494cf0 100644 --- a/compiler/rustc_mir_transform/src/dump_mir.rs +++ b/compiler/rustc_mir_transform/src/dump_mir.rs @@ -7,7 +7,7 @@ use crate::MirPass; use rustc_middle::mir::write_mir_pretty; use rustc_middle::mir::Body; use rustc_middle::ty::TyCtxt; -use rustc_session::config::OutputType; +use rustc_session::config::{OutFileName, OutputType}; pub struct Marker(pub &'static str); @@ -20,8 +20,15 @@ impl<'tcx> MirPass<'tcx> for Marker { } pub fn emit_mir(tcx: TyCtxt<'_>) -> io::Result<()> { - let path = tcx.output_filenames(()).path(OutputType::Mir); - let mut f = io::BufWriter::new(File::create(&path)?); - write_mir_pretty(tcx, None, &mut f)?; + match tcx.output_filenames(()).path(OutputType::Mir) { + OutFileName::Stdout => { + let mut f = io::stdout(); + write_mir_pretty(tcx, None, &mut f)?; + } + OutFileName::Real(path) => { + let mut f = io::BufWriter::new(File::create(&path)?); + write_mir_pretty(tcx, None, &mut f)?; + } + } Ok(()) } diff --git a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs index 8a7b027ddda7e..319fb4eaf3eca 100644 --- a/compiler/rustc_mir_transform/src/early_otherwise_branch.rs +++ b/compiler/rustc_mir_transform/src/early_otherwise_branch.rs @@ -107,9 +107,7 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch { for i in 0..body.basic_blocks.len() { let bbs = &*body.basic_blocks; let parent = BasicBlock::from_usize(i); - let Some(opt_data) = evaluate_candidate(tcx, body, parent) else { - continue - }; + let Some(opt_data) = evaluate_candidate(tcx, body, parent) else { continue }; if !tcx.consider_optimizing(|| format!("EarlyOtherwiseBranch {:?}", &opt_data)) { break; @@ -119,10 +117,9 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch { should_cleanup = true; - let TerminatorKind::SwitchInt { - discr: parent_op, - targets: parent_targets - } = &bbs[parent].terminator().kind else { + let TerminatorKind::SwitchInt { discr: parent_op, targets: parent_targets } = + &bbs[parent].terminator().kind + else { unreachable!() }; // Always correct since we can only switch on `Copy` types @@ -168,7 +165,8 @@ impl<'tcx> MirPass<'tcx> for EarlyOtherwiseBranch { ); let eq_new_targets = parent_targets.iter().map(|(value, child)| { - let TerminatorKind::SwitchInt{ targets, .. } = &bbs[child].terminator().kind else { + let TerminatorKind::SwitchInt { targets, .. } = &bbs[child].terminator().kind + else { unreachable!() }; (value, targets.target_for_value(value)) @@ -311,11 +309,9 @@ fn evaluate_candidate<'tcx>( parent: BasicBlock, ) -> Option> { let bbs = &body.basic_blocks; - let TerminatorKind::SwitchInt { - targets, - discr: parent_discr, - } = &bbs[parent].terminator().kind else { - return None + let TerminatorKind::SwitchInt { targets, discr: parent_discr } = &bbs[parent].terminator().kind + else { + return None; }; let parent_ty = parent_discr.ty(body.local_decls(), tcx); let parent_dest = { @@ -332,18 +328,16 @@ fn evaluate_candidate<'tcx>( }; let (_, child) = targets.iter().next()?; let child_terminator = &bbs[child].terminator(); - let TerminatorKind::SwitchInt { - targets: child_targets, - discr: child_discr, - } = &child_terminator.kind else { - return None + let TerminatorKind::SwitchInt { targets: child_targets, discr: child_discr } = + &child_terminator.kind + else { + return None; }; let child_ty = child_discr.ty(body.local_decls(), tcx); if child_ty != parent_ty { return None; } - let Some(StatementKind::Assign(boxed)) - = &bbs[child].statements.first().map(|x| &x.kind) else { + let Some(StatementKind::Assign(boxed)) = &bbs[child].statements.first().map(|x| &x.kind) else { return None; }; let (_, Rvalue::Discriminant(child_place)) = &**boxed else { @@ -383,12 +377,8 @@ fn verify_candidate_branch<'tcx>( return false; } // ...assign the discriminant of `place` in that statement - let StatementKind::Assign(boxed) = &branch.statements[0].kind else { - return false - }; - let (discr_place, Rvalue::Discriminant(from_place)) = &**boxed else { - return false - }; + let StatementKind::Assign(boxed) = &branch.statements[0].kind else { return false }; + let (discr_place, Rvalue::Discriminant(from_place)) = &**boxed else { return false }; if *from_place != place { return false; } @@ -397,8 +387,9 @@ fn verify_candidate_branch<'tcx>( return false; } // ...terminate on a `SwitchInt` that invalidates that local - let TerminatorKind::SwitchInt{ discr: switch_op, targets, .. } = &branch.terminator().kind else { - return false + let TerminatorKind::SwitchInt { discr: switch_op, targets, .. } = &branch.terminator().kind + else { + return false; }; if *switch_op != Operand::Move(*discr_place) { return false; diff --git a/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs b/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs index f31653caa4972..e51f771e00dbe 100644 --- a/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs +++ b/compiler/rustc_mir_transform/src/elaborate_box_derefs.rs @@ -18,10 +18,10 @@ pub fn build_ptr_tys<'tcx>( unique_did: DefId, nonnull_did: DefId, ) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) { - let substs = tcx.mk_substs(&[pointee.into()]); - let unique_ty = tcx.type_of(unique_did).subst(tcx, substs); - let nonnull_ty = tcx.type_of(nonnull_did).subst(tcx, substs); - let ptr_ty = tcx.mk_imm_ptr(pointee); + let args = tcx.mk_args(&[pointee.into()]); + let unique_ty = tcx.type_of(unique_did).instantiate(tcx, args); + let nonnull_ty = tcx.type_of(nonnull_did).instantiate(tcx, args); + let ptr_ty = Ty::new_imm_ptr(tcx, pointee); (unique_ty, nonnull_ty, ptr_ty) } @@ -95,7 +95,8 @@ impl<'tcx> MirPass<'tcx> for ElaborateBoxDerefs { let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[FieldIdx::from_u32(0)].did; - let Some(nonnull_def) = tcx.type_of(unique_did).subst_identity().ty_adt_def() else { + let Some(nonnull_def) = tcx.type_of(unique_did).instantiate_identity().ty_adt_def() + else { span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique") }; diff --git a/compiler/rustc_mir_transform/src/elaborate_drops.rs b/compiler/rustc_mir_transform/src/elaborate_drops.rs index 98e7a519c2013..b6b1ae6d3c37e 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drops.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drops.rs @@ -10,11 +10,10 @@ use rustc_mir_dataflow::elaborate_drops::{DropElaborator, DropFlagMode, DropStyl use rustc_mir_dataflow::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces}; use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex}; use rustc_mir_dataflow::on_lookup_result_bits; -use rustc_mir_dataflow::un_derefer::UnDerefer; use rustc_mir_dataflow::MoveDataParamEnv; use rustc_mir_dataflow::{on_all_children_bits, on_all_drop_children_bits}; use rustc_mir_dataflow::{Analysis, ResultsCursor}; -use rustc_span::{DesugaringKind, Span}; +use rustc_span::Span; use rustc_target::abi::{FieldIdx, VariantIdx}; use std::fmt; @@ -49,41 +48,41 @@ use std::fmt; pub struct ElaborateDrops; impl<'tcx> MirPass<'tcx> for ElaborateDrops { + #[instrument(level = "trace", skip(self, tcx, body))] fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { debug!("elaborate_drops({:?} @ {:?})", body.source, body.span); let def_id = body.source.def_id(); let param_env = tcx.param_env_reveal_all_normalized(def_id); - let (side_table, move_data) = match MoveData::gather_moves(body, tcx, param_env) { + let move_data = match MoveData::gather_moves(body, tcx, param_env) { Ok(move_data) => move_data, Err((move_data, _)) => { tcx.sess.delay_span_bug( body.span, "No `move_errors` should be allowed in MIR borrowck", ); - (Default::default(), move_data) + move_data } }; - let un_derefer = UnDerefer { tcx: tcx, derefer_sidetable: side_table }; let elaborate_patch = { let env = MoveDataParamEnv { move_data, param_env }; - remove_dead_unwinds(tcx, body, &env, &un_derefer); - let inits = MaybeInitializedPlaces::new(tcx, body, &env) + let mut inits = MaybeInitializedPlaces::new(tcx, body, &env) + .skipping_unreachable_unwind() .into_engine(tcx, body) .pass_name("elaborate_drops") .iterate_to_fixpoint() .into_results_cursor(body); + let dead_unwinds = compute_dead_unwinds(&body, &mut inits); let uninits = MaybeUninitializedPlaces::new(tcx, body, &env) .mark_inactive_variants_as_uninit() + .skipping_unreachable_unwind(dead_unwinds) .into_engine(tcx, body) .pass_name("elaborate_drops") .iterate_to_fixpoint() .into_results_cursor(body); - let reachable = traversal::reachable_as_bitset(body); - let drop_flags = IndexVec::from_elem(None, &env.move_data.move_paths); ElaborateDropsCtxt { tcx, @@ -92,8 +91,6 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops { init_data: InitializationData { inits, uninits }, drop_flags, patch: MirPatch::new(body), - un_derefer: un_derefer, - reachable, } .elaborate() }; @@ -102,68 +99,30 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops { } } -/// Removes unwind edges which are known to be unreachable, because they are in `drop` terminators +/// Records unwind edges which are known to be unreachable, because they are in `drop` terminators /// that can't drop anything. -fn remove_dead_unwinds<'tcx>( - tcx: TyCtxt<'tcx>, - body: &mut Body<'tcx>, - env: &MoveDataParamEnv<'tcx>, - und: &UnDerefer<'tcx>, -) { - debug!("remove_dead_unwinds({:?})", body.span); +#[instrument(level = "trace", skip(body, flow_inits), ret)] +fn compute_dead_unwinds<'mir, 'tcx>( + body: &'mir Body<'tcx>, + flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>, +) -> BitSet { // We only need to do this pass once, because unwind edges can only // reach cleanup blocks, which can't have unwind edges themselves. - let mut dead_unwinds = Vec::new(); - let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &env) - .into_engine(tcx, body) - .pass_name("remove_dead_unwinds") - .iterate_to_fixpoint() - .into_results_cursor(body); + let mut dead_unwinds = BitSet::new_empty(body.basic_blocks.len()); for (bb, bb_data) in body.basic_blocks.iter_enumerated() { - let place = match bb_data.terminator().kind { - TerminatorKind::Drop { ref place, unwind: UnwindAction::Cleanup(_), .. } => { - und.derefer(place.as_ref(), body).unwrap_or(*place) - } - _ => continue, - }; - - debug!("remove_dead_unwinds @ {:?}: {:?}", bb, bb_data); - - let LookupResult::Exact(path) = env.move_data.rev_lookup.find(place.as_ref()) else { - debug!("remove_dead_unwinds: has parent; skipping"); + let TerminatorKind::Drop { place, unwind: UnwindAction::Cleanup(_), .. } = + bb_data.terminator().kind + else { continue; }; flow_inits.seek_before_primary_effect(body.terminator_loc(bb)); - debug!( - "remove_dead_unwinds @ {:?}: path({:?})={:?}; init_data={:?}", - bb, - place, - path, - flow_inits.get() - ); - - let mut maybe_live = false; - on_all_drop_children_bits(tcx, body, &env, path, |child| { - maybe_live |= flow_inits.contains(child); - }); - - debug!("remove_dead_unwinds @ {:?}: maybe_live={}", bb, maybe_live); - if !maybe_live { - dead_unwinds.push(bb); + if flow_inits.analysis().is_unwind_dead(place, flow_inits.get()) { + dead_unwinds.insert(bb); } } - if dead_unwinds.is_empty() { - return; - } - - let basic_blocks = body.basic_blocks.as_mut(); - for &bb in dead_unwinds.iter() { - if let Some(unwind) = basic_blocks[bb].terminator_mut().unwind_mut() { - *unwind = UnwindAction::Unreachable; - } - } + dead_unwinds } struct InitializationData<'mir, 'tcx> { @@ -296,8 +255,6 @@ struct ElaborateDropsCtxt<'a, 'tcx> { init_data: InitializationData<'a, 'tcx>, drop_flags: IndexVec>, patch: MirPatch<'tcx>, - un_derefer: UnDerefer<'tcx>, - reachable: BitSet, } impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { @@ -337,14 +294,9 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { fn collect_drop_flags(&mut self) { for (bb, data) in self.body.basic_blocks.iter_enumerated() { - if !self.reachable.contains(bb) { - continue; - } let terminator = data.terminator(); let place = match terminator.kind { - TerminatorKind::Drop { ref place, .. } => { - self.un_derefer.derefer(place.as_ref(), self.body).unwrap_or(*place) - } + TerminatorKind::Drop { ref place, .. } => place, _ => continue, }; @@ -367,8 +319,7 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { self.tcx.sess.delay_span_bug( terminator.source_info.span, format!( - "drop of untracked, uninitialized value {:?}, place {:?} ({:?})", - bb, place, path + "drop of untracked, uninitialized value {bb:?}, place {place:?} ({path:?})" ), ); } @@ -394,18 +345,11 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { fn elaborate_drops(&mut self) { for (bb, data) in self.body.basic_blocks.iter_enumerated() { - if !self.reachable.contains(bb) { - continue; - } let loc = Location { block: bb, statement_index: data.statements.len() }; let terminator = data.terminator(); match terminator.kind { - TerminatorKind::Drop { mut place, target, unwind } => { - if let Some(new_place) = self.un_derefer.derefer(place.as_ref(), self.body) { - place = new_place; - } - + TerminatorKind::Drop { place, target, unwind, replace } => { self.init_data.seek_before(loc); match self.move_data().rev_lookup.find(place.as_ref()) { LookupResult::Exact(path) => { @@ -434,13 +378,10 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { ) } LookupResult::Parent(..) => { - if !matches!( - terminator.source_info.span.desugaring_kind(), - Some(DesugaringKind::Replace), - ) { + if !replace { self.tcx.sess.delay_span_bug( terminator.source_info.span, - format!("drop of untracked value {:?}", bb), + format!("drop of untracked value {bb:?}"), ); } // A drop and replace behind a pointer/array/whatever. @@ -482,9 +423,6 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { fn drop_flags_for_fn_rets(&mut self) { for (bb, data) in self.body.basic_blocks.iter_enumerated() { - if !self.reachable.contains(bb) { - continue; - } if let TerminatorKind::Call { destination, target: Some(tgt), @@ -523,9 +461,6 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> { // clobbered before they are read. for (bb, data) in self.body.basic_blocks.iter_enumerated() { - if !self.reachable.contains(bb) { - continue; - } debug!("drop_flags_for_locs({:?})", data); for i in 0..(data.statements.len() + 1) { debug!("drop_flag_for_locs: stmt {}", i); diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 602e40d513104..4b796d79ef69a 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -1,5 +1,6 @@ use rustc_errors::{ - DecorateLint, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler, IntoDiagnostic, + Applicability, DecorateLint, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler, + IntoDiagnostic, }; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::mir::{AssertKind, UnsafetyViolationDetails}; @@ -130,6 +131,12 @@ impl RequiresUnsafeDetail { pub(crate) struct UnsafeOpInUnsafeFn { pub details: RequiresUnsafeDetail, + + /// These spans point to: + /// 1. the start of the function body + /// 2. the end of the function body + /// 3. the function signature + pub suggest_unsafe_block: Option<(Span, Span, Span)>, } impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn { @@ -138,13 +145,21 @@ impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn { self, diag: &'b mut DiagnosticBuilder<'a, ()>, ) -> &'b mut DiagnosticBuilder<'a, ()> { - let desc = diag - .handler() - .expect("lint should not yet be emitted") - .eagerly_translate_to_string(self.details.label(), [].into_iter()); + let handler = diag.handler().expect("lint should not yet be emitted"); + let desc = handler.eagerly_translate_to_string(self.details.label(), [].into_iter()); diag.set_arg("details", desc); diag.span_label(self.details.span, self.details.label()); diag.note(self.details.note()); + + if let Some((start, end, fn_sig)) = self.suggest_unsafe_block { + diag.span_note(fn_sig, crate::fluent_generated::mir_transform_note); + diag.tool_only_multipart_suggestion( + crate::fluent_generated::mir_transform_suggestion, + vec![(start, " unsafe {".into()), (end, "}".into())], + Applicability::MaybeIncorrect, + ); + } + diag } @@ -163,7 +178,14 @@ impl<'a, P: std::fmt::Debug> DecorateLint<'a, ()> for AssertLint

{ self, diag: &'b mut DiagnosticBuilder<'a, ()>, ) -> &'b mut DiagnosticBuilder<'a, ()> { - diag.span_label(self.span(), format!("{:?}", self.panic())); + let span = self.span(); + let assert_kind = self.panic(); + let message = assert_kind.diagnostic_message(); + assert_kind.add_args(&mut |name, value| { + diag.set_arg(name, value); + }); + diag.span_label(span, message); + diag } @@ -191,7 +213,7 @@ impl

AssertLint

{ AssertLint::ArithmeticOverflow(sp, _) | AssertLint::UnconditionalPanic(sp, _) => *sp, } } - pub fn panic(&self) -> &AssertKind

{ + pub fn panic(self) -> AssertKind

(&mut self, predicate: P) -> Option + where + P: FnOnce(&mut T) -> bool, + { + if self.as_mut().map_or(false, predicate) { self.take() } else { None } + } + /// Replaces the actual value in the option by the value given in parameter, /// returning the old value if present, /// leaving a [`Some`] in its place without deinitializing either one. diff --git a/library/core/src/panic/panic_info.rs b/library/core/src/panic/panic_info.rs index 5576adde84b03..c7f04f11ef618 100644 --- a/library/core/src/panic/panic_info.rs +++ b/library/core/src/panic/panic_info.rs @@ -147,16 +147,18 @@ impl<'a> PanicInfo<'a> { impl fmt::Display for PanicInfo<'_> { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("panicked at ")?; + self.location.fmt(formatter)?; if let Some(message) = self.message { - write!(formatter, "'{}', ", message)? + formatter.write_str(":\n")?; + formatter.write_fmt(*message)?; } else if let Some(payload) = self.payload.downcast_ref::<&'static str>() { - write!(formatter, "'{}', ", payload)? + formatter.write_str(":\n")?; + formatter.write_str(payload)?; } // NOTE: we cannot use downcast_ref::() here // since String is not available in core! // The payload is a String when `std::panic!` is called with multiple arguments, // but in that case the message is also available. - - self.location.fmt(formatter) + Ok(()) } } diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index 81be3fb22eec4..7b6249207fe6f 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -166,14 +166,15 @@ fn panic_bounds_check(index: usize, len: usize) -> ! { #[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))] #[track_caller] #[lang = "panic_misaligned_pointer_dereference"] // needed by codegen for panic on misaligned pointer deref +#[rustc_nounwind] // `CheckAlignment` MIR pass requires this function to never unwind fn panic_misaligned_pointer_dereference(required: usize, found: usize) -> ! { if cfg!(feature = "panic_immediate_abort") { super::intrinsics::abort() } - panic!( + panic_nounwind_fmt(format_args!( "misaligned pointer dereference: address must be a multiple of {required:#x} but is {found:#x}" - ) + )) } /// Panic because we cannot unwind out of a function. @@ -266,16 +267,14 @@ fn assert_failed_inner( match args { Some(args) => panic!( - r#"assertion failed: `(left {} right)` - left: `{:?}`, - right: `{:?}`: {}"#, - op, left, right, args + r#"assertion `left {op} right` failed: {args} + left: {left:?} + right: {right:?}"# ), None => panic!( - r#"assertion failed: `(left {} right)` - left: `{:?}`, - right: `{:?}`"#, - op, left, right, + r#"assertion `left {op} right` failed + left: {left:?} + right: {right:?}"# ), } } diff --git a/library/core/src/primitive_docs.rs b/library/core/src/primitive_docs.rs index 8266e899011cb..80289ca08c3fc 100644 --- a/library/core/src/primitive_docs.rs +++ b/library/core/src/primitive_docs.rs @@ -308,7 +308,7 @@ mod prim_never {} /// /// ```no_run /// // Undefined behaviour -/// unsafe { char::from_u32_unchecked(0x110000) }; +/// let _ = unsafe { char::from_u32_unchecked(0x110000) }; /// ``` /// /// USVs are also the exact set of values that may be encoded in UTF-8. Because diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 5ee1b5e4afc78..ee69d89a4b720 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -1,7 +1,7 @@ use super::*; use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::intrinsics::{self, const_eval_select}; -use crate::mem; +use crate::mem::{self, SizedTypeProperties}; use crate::slice::{self, SliceIndex}; impl *const T { @@ -30,6 +30,7 @@ impl *const T { /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")] + #[rustc_diagnostic_item = "ptr_const_is_null"] #[inline] pub const fn is_null(self) -> bool { #[inline] @@ -54,6 +55,7 @@ impl *const T { /// Casts to a pointer of another type. #[stable(feature = "ptr_cast", since = "1.38.0")] #[rustc_const_stable(feature = "const_ptr_cast", since = "1.38.0")] + #[rustc_diagnostic_item = "const_ptr_cast"] #[inline(always)] pub const fn cast(self) -> *const U { self as _ @@ -104,6 +106,7 @@ impl *const T { /// refactored. #[stable(feature = "ptr_const_cast", since = "1.65.0")] #[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")] + #[rustc_diagnostic_item = "ptr_cast_mut"] #[inline(always)] pub const fn cast_mut(self) -> *mut T { self as _ @@ -916,16 +919,8 @@ impl *const T { where T: Sized, { - #[cfg(bootstrap)] // SAFETY: the caller must uphold the safety contract for `offset`. - unsafe { - self.offset(count as isize) - } - #[cfg(not(bootstrap))] - // SAFETY: the caller must uphold the safety contract for `offset`. - unsafe { - intrinsics::offset(self, count) - } + unsafe { intrinsics::offset(self, count) } } /// Calculates the offset from a pointer in bytes (convenience for `.byte_offset(count as isize)`). @@ -1001,14 +996,23 @@ impl *const T { #[stable(feature = "pointer_methods", since = "1.26.0")] #[must_use = "returns a new pointer rather than modifying its argument"] #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] + // We could always go back to wrapping if unchecked becomes unacceptable + #[rustc_allow_const_fn_unstable(const_int_unchecked_arith)] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn sub(self, count: usize) -> Self where T: Sized, { - // SAFETY: the caller must uphold the safety contract for `offset`. - unsafe { self.offset((count as isize).wrapping_neg()) } + if T::IS_ZST { + // Pointer arithmetic does nothing when the pointee is a ZST. + self + } else { + // SAFETY: the caller must uphold the safety contract for `offset`. + // Because the pointee is *not* a ZST, that means that `count` is + // at most `isize::MAX`, and thus the negation cannot overflow. + unsafe { self.offset(intrinsics::unchecked_sub(0, count as isize)) } + } } /// Calculates the offset from a pointer in bytes (convenience for @@ -1195,7 +1199,7 @@ impl *const T { /// /// [`ptr::read`]: crate::ptr::read() #[stable(feature = "pointer_methods", since = "1.26.0")] - #[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[inline] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn read(self) -> T @@ -1236,7 +1240,7 @@ impl *const T { /// /// [`ptr::read_unaligned`]: crate::ptr::read_unaligned() #[stable(feature = "pointer_methods", since = "1.26.0")] - #[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[inline] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn read_unaligned(self) -> T diff --git a/library/core/src/ptr/metadata.rs b/library/core/src/ptr/metadata.rs index 2ea032d4affe0..040aa06978748 100644 --- a/library/core/src/ptr/metadata.rs +++ b/library/core/src/ptr/metadata.rs @@ -50,7 +50,7 @@ use crate::hash::{Hash, Hasher}; /// /// [`to_raw_parts`]: *const::to_raw_parts #[lang = "pointee_trait"] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] pub trait Pointee { /// The type for metadata in pointers and references to `Self`. #[lang = "metadata_type"] diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index ff9fa48f31136..5f094ac4e7e64 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -698,6 +698,7 @@ where #[inline(always)] #[must_use] #[unstable(feature = "ptr_from_ref", issue = "106116")] +#[rustc_diagnostic_item = "ptr_from_ref"] pub const fn from_ref(r: &T) -> *const T { r } @@ -709,6 +710,7 @@ pub const fn from_ref(r: &T) -> *const T { #[inline(always)] #[must_use] #[unstable(feature = "ptr_from_ref", issue = "106116")] +#[rustc_diagnostic_item = "ptr_from_mut"] pub const fn from_mut(r: &mut T) -> *mut T { r } @@ -1139,7 +1141,7 @@ pub const unsafe fn replace(dst: *mut T, mut src: T) -> T { /// [valid]: self#safety #[inline] #[stable(feature = "rust1", since = "1.0.0")] -#[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] +#[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[rustc_allow_const_fn_unstable(const_mut_refs, const_maybe_uninit_as_mut_ptr)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn read(src: *const T) -> T { @@ -1256,7 +1258,7 @@ pub const unsafe fn read(src: *const T) -> T { /// ``` #[inline] #[stable(feature = "ptr_unaligned", since = "1.17.0")] -#[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] +#[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[rustc_allow_const_fn_unstable(const_mut_refs, const_maybe_uninit_as_mut_ptr)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn read_unaligned(src: *const T) -> T { diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index 5edd291fb76aa..9dbb3f9d322c0 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -1,6 +1,7 @@ use super::*; use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::intrinsics::{self, const_eval_select}; +use crate::mem::SizedTypeProperties; use crate::slice::{self, SliceIndex}; impl *mut T { @@ -29,6 +30,7 @@ impl *mut T { /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")] + #[rustc_diagnostic_item = "ptr_is_null"] #[inline] pub const fn is_null(self) -> bool { #[inline] @@ -53,6 +55,7 @@ impl *mut T { /// Casts to a pointer of another type. #[stable(feature = "ptr_cast", since = "1.38.0")] #[rustc_const_stable(feature = "const_ptr_cast", since = "1.38.0")] + #[rustc_diagnostic_item = "ptr_cast"] #[inline(always)] pub const fn cast(self) -> *mut U { self as _ @@ -109,6 +112,7 @@ impl *mut T { /// [`cast_mut`]: #method.cast_mut #[stable(feature = "ptr_const_cast", since = "1.65.0")] #[rustc_const_stable(feature = "ptr_const_cast", since = "1.65.0")] + #[rustc_diagnostic_item = "ptr_cast_const"] #[inline(always)] pub const fn cast_const(self) -> *const T { self as _ @@ -473,20 +477,10 @@ impl *mut T { where T: Sized, { - #[cfg(bootstrap)] // SAFETY: the caller must uphold the safety contract for `offset`. // The obtained pointer is valid for writes since the caller must // guarantee that it points to the same allocated object as `self`. - unsafe { - intrinsics::offset(self, count) as *mut T - } - #[cfg(not(bootstrap))] - // SAFETY: the caller must uphold the safety contract for `offset`. - // The obtained pointer is valid for writes since the caller must - // guarantee that it points to the same allocated object as `self`. - unsafe { - intrinsics::offset(self, count) - } + unsafe { intrinsics::offset(self, count) } } /// Calculates the offset from a pointer in bytes. @@ -1026,16 +1020,8 @@ impl *mut T { where T: Sized, { - #[cfg(bootstrap)] // SAFETY: the caller must uphold the safety contract for `offset`. - unsafe { - self.offset(count as isize) - } - #[cfg(not(bootstrap))] - // SAFETY: the caller must uphold the safety contract for `offset`. - unsafe { - intrinsics::offset(self, count) - } + unsafe { intrinsics::offset(self, count) } } /// Calculates the offset from a pointer in bytes (convenience for `.byte_offset(count as isize)`). @@ -1111,14 +1097,23 @@ impl *mut T { #[stable(feature = "pointer_methods", since = "1.26.0")] #[must_use = "returns a new pointer rather than modifying its argument"] #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] + // We could always go back to wrapping if unchecked becomes unacceptable + #[rustc_allow_const_fn_unstable(const_int_unchecked_arith)] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn sub(self, count: usize) -> Self where T: Sized, { - // SAFETY: the caller must uphold the safety contract for `offset`. - unsafe { self.offset((count as isize).wrapping_neg()) } + if T::IS_ZST { + // Pointer arithmetic does nothing when the pointee is a ZST. + self + } else { + // SAFETY: the caller must uphold the safety contract for `offset`. + // Because the pointee is *not* a ZST, that means that `count` is + // at most `isize::MAX`, and thus the negation cannot overflow. + unsafe { self.offset(intrinsics::unchecked_sub(0, count as isize)) } + } } /// Calculates the offset from a pointer in bytes (convenience for @@ -1305,7 +1300,7 @@ impl *mut T { /// /// [`ptr::read`]: crate::ptr::read() #[stable(feature = "pointer_methods", since = "1.26.0")] - #[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn read(self) -> T @@ -1346,7 +1341,7 @@ impl *mut T { /// /// [`ptr::read_unaligned`]: crate::ptr::read_unaligned() #[stable(feature = "pointer_methods", since = "1.26.0")] - #[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn read_unaligned(self) -> T diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index b492d2f07bc13..e0fd347a04950 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -367,13 +367,14 @@ impl NonNull { /// /// [the module documentation]: crate::ptr#safety #[stable(feature = "nonnull", since = "1.25.0")] - #[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")] + #[rustc_const_stable(feature = "const_nonnull_as_ref", since = "1.73.0")] #[must_use] #[inline(always)] pub const unsafe fn as_ref<'a>(&self) -> &'a T { // SAFETY: the caller must guarantee that `self` meets all the // requirements for a reference. - unsafe { &*self.as_ptr() } + // `cast_const` avoids a mutable raw pointer deref. + unsafe { &*self.as_ptr().cast_const() } } /// Returns a unique reference to the value. If the value may be uninitialized, [`as_uninit_mut`] @@ -462,6 +463,30 @@ impl NonNull { // And the caller promised the `delta` is sound to add. unsafe { NonNull { pointer: self.pointer.add(delta) } } } + + /// See [`pointer::sub`] for semantics and safety requirements. + #[inline] + pub(crate) const unsafe fn sub(self, delta: usize) -> Self + where + T: Sized, + { + // SAFETY: We require that the delta stays in-bounds of the object, and + // thus it cannot become null, as no legal objects can be allocated + // in such as way that the null address is part of them. + // And the caller promised the `delta` is sound to subtract. + unsafe { NonNull { pointer: self.pointer.sub(delta) } } + } + + /// See [`pointer::sub_ptr`] for semantics and safety requirements. + #[inline] + pub(crate) const unsafe fn sub_ptr(self, subtrahend: Self) -> usize + where + T: Sized, + { + // SAFETY: The caller promised that this is safe to do, and + // the non-nullness is irrelevant to the operation. + unsafe { self.pointer.sub_ptr(subtrahend.pointer) } + } } impl NonNull<[T]> { diff --git a/library/core/src/ptr/unique.rs b/library/core/src/ptr/unique.rs index a853f15edb7e0..bf8b86677d568 100644 --- a/library/core/src/ptr/unique.rs +++ b/library/core/src/ptr/unique.rs @@ -32,6 +32,8 @@ use crate::ptr::NonNull; )] #[doc(hidden)] #[repr(transparent)] +// Lang item used experimentally by Miri to define the semantics of `Unique`. +#[lang = "ptr_unique"] pub struct Unique { pointer: NonNull, // NOTE: this marker has no consequences for variance, but is necessary diff --git a/library/core/src/result.rs b/library/core/src/result.rs index 1ee270f4c0398..6981abc9be19d 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -749,7 +749,7 @@ impl Result { } /// Returns the provided default (if [`Err`]), or - /// applies a function to the contained value (if [`Ok`]), + /// applies a function to the contained value (if [`Ok`]). /// /// Arguments passed to `map_or` are eagerly evaluated; if you are passing /// the result of a function call, it is recommended to use [`map_or_else`], @@ -768,6 +768,7 @@ impl Result { /// ``` #[inline] #[stable(feature = "result_map_or", since = "1.41.0")] + #[must_use = "if you don't need the returned value, use `if let` instead"] pub fn map_or U>(self, default: U, f: F) -> U { match self { Ok(t) => f(t), diff --git a/library/core/src/slice/cmp.rs b/library/core/src/slice/cmp.rs index 7601dd3c75608..075347b80d031 100644 --- a/library/core/src/slice/cmp.rs +++ b/library/core/src/slice/cmp.rs @@ -1,22 +1,12 @@ //! Comparison traits for `[T]`. use crate::cmp::{self, BytewiseEq, Ordering}; -use crate::ffi; +use crate::intrinsics::compare_bytes; use crate::mem; use super::from_raw_parts; use super::memchr; -extern "C" { - /// Calls implementation provided memcmp. - /// - /// Interprets the data as u8. - /// - /// Returns 0 for equal, < 0 for less than and > 0 for greater - /// than. - fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> ffi::c_int; -} - #[stable(feature = "rust1", since = "1.0.0")] impl PartialEq<[B]> for [A] where @@ -74,7 +64,8 @@ where } } -// Use memcmp for bytewise equality when the types allow +// When each element can be compared byte-wise, we can compare all the bytes +// from the whole size in one call to the intrinsics. impl SlicePartialEq for [A] where A: BytewiseEq, @@ -88,7 +79,7 @@ where // The two slices have been checked to have the same size above. unsafe { let size = mem::size_of_val(self); - memcmp(self.as_ptr() as *const u8, other.as_ptr() as *const u8, size) == 0 + compare_bytes(self.as_ptr() as *const u8, other.as_ptr() as *const u8, size) == 0 } } } @@ -183,7 +174,7 @@ impl SliceOrd for A { } } -// memcmp compares a sequence of unsigned bytes lexicographically. +// `compare_bytes` compares a sequence of unsigned bytes lexicographically. // this matches the order we want for [u8], but no others (not even [i8]). impl SliceOrd for u8 { #[inline] @@ -195,7 +186,7 @@ impl SliceOrd for u8 { // SAFETY: `left` and `right` are references and are thus guaranteed to be valid. // We use the minimum of both lengths which guarantees that both regions are // valid for reads in that interval. - let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize }; + let mut order = unsafe { compare_bytes(left.as_ptr(), right.as_ptr(), len) as isize }; if order == 0 { order = diff; } diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index 6ef9f9c95e843..d313e8e012f20 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -152,7 +152,10 @@ mod private_slice_index { #[rustc_on_unimplemented( on(T = "str", label = "string indices are ranges of `usize`",), on( - all(any(T = "str", T = "&str", T = "std::string::String"), _Self = "{integer}"), + all( + any(T = "str", T = "&str", T = "alloc::string::String", T = "std::string::String"), + _Self = "{integer}" + ), note = "you can use `.chars().nth()` or `.bytes().nth()`\n\ for more information, see chapter 8 in The Book: \ " @@ -724,7 +727,7 @@ where } /// Convert pair of `ops::Bound`s into `ops::Range` without performing any bounds checking and (in debug) overflow checking -fn into_range_unchecked( +pub(crate) fn into_range_unchecked( len: usize, (start, end): (ops::Bound, ops::Bound), ) -> ops::Range { @@ -744,7 +747,7 @@ fn into_range_unchecked( /// Convert pair of `ops::Bound`s into `ops::Range`. /// Returns `None` on overflowing indices. -fn into_range( +pub(crate) fn into_range( len: usize, (start, end): (ops::Bound, ops::Bound), ) -> Option> { @@ -769,7 +772,7 @@ fn into_range( /// Convert pair of `ops::Bound`s into `ops::Range`. /// Panics on overflowing indices. -fn into_slice_range( +pub(crate) fn into_slice_range( len: usize, (start, end): (ops::Bound, ops::Bound), ) -> ops::Range { diff --git a/library/core/src/slice/iter.rs b/library/core/src/slice/iter.rs index 5369fe0a9a9aa..cc9313553183c 100644 --- a/library/core/src/slice/iter.rs +++ b/library/core/src/slice/iter.rs @@ -13,7 +13,7 @@ use crate::iter::{ use crate::marker::{PhantomData, Send, Sized, Sync}; use crate::mem::{self, SizedTypeProperties}; use crate::num::NonZeroUsize; -use crate::ptr::{invalid, invalid_mut, NonNull}; +use crate::ptr::{self, invalid, invalid_mut, NonNull}; use super::{from_raw_parts, from_raw_parts_mut}; @@ -68,7 +68,7 @@ pub struct Iter<'a, T: 'a> { /// For non-ZSTs, the non-null pointer to the past-the-end element. /// /// For ZSTs, this is `ptr::invalid(len)`. - end: *const T, + end_or_len: *const T, _marker: PhantomData<&'a T>, } @@ -90,9 +90,9 @@ impl<'a, T> Iter<'a, T> { let ptr = slice.as_ptr(); // SAFETY: Similar to `IterMut::new`. unsafe { - let end = if T::IS_ZST { invalid(slice.len()) } else { ptr.add(slice.len()) }; + let end_or_len = if T::IS_ZST { invalid(slice.len()) } else { ptr.add(slice.len()) }; - Self { ptr: NonNull::new_unchecked(ptr as *mut T), end, _marker: PhantomData } + Self { ptr: NonNull::new_unchecked(ptr as *mut T), end_or_len, _marker: PhantomData } } } @@ -128,7 +128,7 @@ impl<'a, T> Iter<'a, T> { } } -iterator! {struct Iter -> *const T, &'a T, const, {/* no mut */}, { +iterator! {struct Iter -> *const T, &'a T, const, {/* no mut */}, as_ref, { fn is_sorted_by(self, mut compare: F) -> bool where Self: Sized, @@ -142,7 +142,7 @@ iterator! {struct Iter -> *const T, &'a T, const, {/* no mut */}, { impl Clone for Iter<'_, T> { #[inline] fn clone(&self) -> Self { - Iter { ptr: self.ptr, end: self.end, _marker: self._marker } + Iter { ptr: self.ptr, end_or_len: self.end_or_len, _marker: self._marker } } } @@ -189,7 +189,7 @@ pub struct IterMut<'a, T: 'a> { /// For non-ZSTs, the non-null pointer to the past-the-end element. /// /// For ZSTs, this is `ptr::invalid_mut(len)`. - end: *mut T, + end_or_len: *mut T, _marker: PhantomData<&'a mut T>, } @@ -220,15 +220,16 @@ impl<'a, T> IterMut<'a, T> { // for direct pointer equality with `ptr` to check if the iterator is // done. // - // In the case of a ZST, the end pointer is just the start pointer plus - // the length, to also allows for the fast `ptr == end` check. + // In the case of a ZST, the end pointer is just the length. It's never + // used as a pointer at all, and thus it's fine to have no provenance. // // See the `next_unchecked!` and `is_empty!` macros as well as the // `post_inc_start` method for more information. unsafe { - let end = if T::IS_ZST { invalid_mut(slice.len()) } else { ptr.add(slice.len()) }; + let end_or_len = + if T::IS_ZST { invalid_mut(slice.len()) } else { ptr.add(slice.len()) }; - Self { ptr: NonNull::new_unchecked(ptr), end, _marker: PhantomData } + Self { ptr: NonNull::new_unchecked(ptr), end_or_len, _marker: PhantomData } } } @@ -360,7 +361,7 @@ impl AsRef<[T]> for IterMut<'_, T> { // } // } -iterator! {struct IterMut -> *mut T, &'a mut T, mut, {mut}, {}} +iterator! {struct IterMut -> *mut T, &'a mut T, mut, {mut}, as_mut, {}} /// An internal abstraction over the splitting iterators, so that /// splitn, splitn_mut etc can be implemented once. diff --git a/library/core/src/slice/iter/macros.rs b/library/core/src/slice/iter/macros.rs index 3462c0e020a3d..95bcd123b824f 100644 --- a/library/core/src/slice/iter/macros.rs +++ b/library/core/src/slice/iter/macros.rs @@ -1,45 +1,62 @@ //! Macros used by iterators of slice. -// Shrinks the iterator when T is a ZST, setting the length to `new_len`. -// `new_len` must not exceed `self.len()`. -macro_rules! zst_set_len { - ($self: ident, $new_len: expr) => {{ +/// Convenience & performance macro for consuming the `end_or_len` field, by +/// giving a `(&mut) usize` or `(&mut) NonNull` depending whether `T` is +/// or is not a ZST respectively. +/// +/// Internally, this reads the `end` through a pointer-to-`NonNull` so that +/// it'll get the appropriate non-null metadata in the backend without needing +/// to call `assume` manually. +macro_rules! if_zst { + (mut $this:ident, $len:ident => $zst_body:expr, $end:ident => $other_body:expr,) => {{ #![allow(unused_unsafe)] // we're sometimes used within an unsafe block - // SAFETY: same as `invalid(_mut)`, but the macro doesn't know - // which versions of that function to call, so open-code it. - $self.end = unsafe { mem::transmute::($new_len) }; + if T::IS_ZST { + // SAFETY: for ZSTs, the pointer is storing a provenance-free length, + // so consuming and updating it as a `usize` is fine. + let $len = unsafe { &mut *ptr::addr_of_mut!($this.end_or_len).cast::() }; + $zst_body + } else { + // SAFETY: for non-ZSTs, the type invariant ensures it cannot be null + let $end = unsafe { &mut *ptr::addr_of_mut!($this.end_or_len).cast::>() }; + $other_body + } }}; -} + ($this:ident, $len:ident => $zst_body:expr, $end:ident => $other_body:expr,) => {{ + #![allow(unused_unsafe)] // we're sometimes used within an unsafe block -// Shrinks the iterator when T is a ZST, reducing the length by `n`. -// `n` must not exceed `self.len()`. -macro_rules! zst_shrink { - ($self: ident, $n: ident) => { - let new_len = $self.end.addr() - $n; - zst_set_len!($self, new_len); - }; + if T::IS_ZST { + let $len = $this.end_or_len.addr(); + $zst_body + } else { + // SAFETY: for non-ZSTs, the type invariant ensures it cannot be null + let $end = unsafe { *ptr::addr_of!($this.end_or_len).cast::>() }; + $other_body + } + }}; } // Inlining is_empty and len makes a huge performance difference macro_rules! is_empty { ($self: ident) => { - if T::IS_ZST { $self.end.addr() == 0 } else { $self.ptr.as_ptr() as *const _ == $self.end } + if_zst!($self, + len => len == 0, + end => $self.ptr == end, + ) }; } macro_rules! len { ($self: ident) => {{ - #![allow(unused_unsafe)] // we're sometimes used within an unsafe block - - if T::IS_ZST { - $self.end.addr() - } else { - // To get rid of some bounds checks (see `position`), we use ptr_sub instead of - // offset_from (Tested by `codegen/slice-position-bounds-check`.) - // SAFETY: by the type invariant pointers are aligned and `start <= end` - unsafe { $self.end.sub_ptr($self.ptr.as_ptr()) } - } + if_zst!($self, + len => len, + end => { + // To get rid of some bounds checks (see `position`), we use ptr_sub instead of + // offset_from (Tested by `codegen/slice-position-bounds-check`.) + // SAFETY: by the type invariant pointers are aligned and `start <= end` + unsafe { end.sub_ptr($self.ptr) } + }, + ) }}; } @@ -50,20 +67,21 @@ macro_rules! iterator { $elem:ty, $raw_mut:tt, {$( $mut_:tt )?}, + $into_ref:ident, {$($extra:tt)*} ) => { // Returns the first element and moves the start of the iterator forwards by 1. // Greatly improves performance compared to an inlined function. The iterator // must not be empty. macro_rules! next_unchecked { - ($self: ident) => {& $( $mut_ )? *$self.post_inc_start(1)} + ($self: ident) => { $self.post_inc_start(1).$into_ref() } } // Returns the last element and moves the end of the iterator backwards by 1. // Greatly improves performance compared to an inlined function. The iterator // must not be empty. macro_rules! next_back_unchecked { - ($self: ident) => {& $( $mut_ )? *$self.pre_dec_end(1)} + ($self: ident) => { $self.pre_dec_end(1).$into_ref() } } impl<'a, T> $name<'a, T> { @@ -80,33 +98,40 @@ macro_rules! iterator { // returning the old start. // Unsafe because the offset must not exceed `self.len()`. #[inline(always)] - unsafe fn post_inc_start(&mut self, offset: usize) -> * $raw_mut T { + unsafe fn post_inc_start(&mut self, offset: usize) -> NonNull { let old = self.ptr; - if T::IS_ZST { - zst_shrink!(self, offset); - } else { - // SAFETY: the caller guarantees that `offset` doesn't exceed `self.len()`, - // so this new pointer is inside `self` and thus guaranteed to be non-null. - self.ptr = unsafe { self.ptr.add(offset) }; + + // SAFETY: the caller guarantees that `offset` doesn't exceed `self.len()`, + // so this new pointer is inside `self` and thus guaranteed to be non-null. + unsafe { + if_zst!(mut self, + len => *len = len.unchecked_sub(offset), + _end => self.ptr = self.ptr.add(offset), + ); } - old.as_ptr() + old } // Helper function for moving the end of the iterator backwards by `offset` elements, // returning the new end. // Unsafe because the offset must not exceed `self.len()`. #[inline(always)] - unsafe fn pre_dec_end(&mut self, offset: usize) -> * $raw_mut T { - if T::IS_ZST { - zst_shrink!(self, offset); - self.ptr.as_ptr() - } else { + unsafe fn pre_dec_end(&mut self, offset: usize) -> NonNull { + if_zst!(mut self, + // SAFETY: By our precondition, `offset` can be at most the + // current length, so the subtraction can never overflow. + len => unsafe { + *len = len.unchecked_sub(offset); + self.ptr + }, // SAFETY: the caller guarantees that `offset` doesn't exceed `self.len()`, // which is guaranteed to not overflow an `isize`. Also, the resulting pointer // is in bounds of `slice`, which fulfills the other requirements for `offset`. - self.end = unsafe { self.end.sub(offset) }; - self.end - } + end => unsafe { + *end = end.sub(offset); + *end + }, + ) } } @@ -131,13 +156,9 @@ macro_rules! iterator { fn next(&mut self) -> Option<$elem> { // could be implemented with slices, but this avoids bounds checks - // SAFETY: `assume` call is safe because slices over non-ZSTs must - // have a non-null end pointer. The call to `next_unchecked!` is + // SAFETY: The call to `next_unchecked!` is // safe since we check if the iterator is empty first. unsafe { - if !::IS_ZST { - assume(!self.end.is_null()); - } if is_empty!(self) { None } else { @@ -161,14 +182,10 @@ macro_rules! iterator { fn nth(&mut self, n: usize) -> Option<$elem> { if n >= len!(self) { // This iterator is now empty. - if T::IS_ZST { - zst_set_len!(self, 0); - } else { - // SAFETY: end can't be 0 if T isn't ZST because ptr isn't 0 and end >= ptr - unsafe { - self.ptr = NonNull::new_unchecked(self.end as *mut T); - } - } + if_zst!(mut self, + len => *len = 0, + end => self.ptr = *end, + ); return None; } // SAFETY: We are in bounds. `post_inc_start` does the right thing even for ZSTs. @@ -191,6 +208,39 @@ macro_rules! iterator { self.next_back() } + #[inline] + fn fold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + // this implementation consists of the following optimizations compared to the + // default implementation: + // - do-while loop, as is llvm's preferred loop shape, + // see https://releases.llvm.org/16.0.0/docs/LoopTerminology.html#more-canonical-loops + // - bumps an index instead of a pointer since the latter case inhibits + // some optimizations, see #111603 + // - avoids Option wrapping/matching + if is_empty!(self) { + return init; + } + let mut acc = init; + let mut i = 0; + let len = len!(self); + loop { + // SAFETY: the loop iterates `i in 0..len`, which always is in bounds of + // the slice allocation + acc = f(acc, unsafe { & $( $mut_ )? *self.ptr.add(i).as_ptr() }); + // SAFETY: `i` can't overflow since it'll only reach usize::MAX if the + // slice had that length, in which case we'll break out of the loop + // after the increment + i = unsafe { i.unchecked_add(1) }; + if i == len { + break; + } + } + acc + } + // We override the default implementation, which uses `try_fold`, // because this simple implementation generates less LLVM IR and is // faster to compile. @@ -342,13 +392,9 @@ macro_rules! iterator { fn next_back(&mut self) -> Option<$elem> { // could be implemented with slices, but this avoids bounds checks - // SAFETY: `assume` call is safe because slices over non-ZSTs must - // have a non-null end pointer. The call to `next_back_unchecked!` + // SAFETY: The call to `next_back_unchecked!` // is safe since we check if the iterator is empty first. unsafe { - if !::IS_ZST { - assume(!self.end.is_null()); - } if is_empty!(self) { None } else { @@ -361,11 +407,10 @@ macro_rules! iterator { fn nth_back(&mut self, n: usize) -> Option<$elem> { if n >= len!(self) { // This iterator is now empty. - if T::IS_ZST { - zst_set_len!(self, 0); - } else { - self.end = self.ptr.as_ptr(); - } + if_zst!(mut self, + len => *len = 0, + end => *end = self.ptr, + ); return None; } // SAFETY: We are in bounds. `pre_dec_end` does the right thing even for ZSTs. diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index c787b7288b060..d95662afddd46 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -38,10 +38,11 @@ pub mod sort; mod ascii; mod cmp; -mod index; +pub(crate) mod index; mod iter; mod raw; mod rotate; +mod select; mod specialize; #[unstable(feature = "str_internals", issue = "none")] @@ -850,6 +851,8 @@ impl [T] { /// Swaps two elements in the slice. /// + /// If `a` equals to `b`, it's guaranteed that elements won't change value. + /// /// # Arguments /// /// * a - The index of the first element @@ -1853,7 +1856,7 @@ impl [T] { /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_stable(feature = "const_slice_split_at_not_mut", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_slice_split_at_not_mut", since = "1.71.0")] #[rustc_allow_const_fn_unstable(slice_split_at_unchecked)] #[inline] #[track_caller] @@ -2954,7 +2957,7 @@ impl [T] { /// elements. /// /// This sort is unstable (i.e., may reorder equal elements), in-place - /// (i.e., does not allocate), and *O*(m \* *n* \* log(*n*)) worst-case, where the key function is + /// (i.e., does not allocate), and *O*(*m* \* *n* \* log(*n*)) worst-case, where the key function is /// *O*(*m*). /// /// # Current implementation @@ -2994,7 +2997,7 @@ impl [T] { /// This reordering has the additional property that any value at position `i < index` will be /// less than or equal to any value at a position `j > index`. Additionally, this reordering is /// unstable (i.e. any number of equal elements may end up at position `index`), in-place - /// (i.e. does not allocate), and *O*(*n*) on average. The worst-case performance is *O*(*n* log *n*). + /// (i.e. does not allocate), and runs in *O*(*n*) time. /// This function is also known as "kth element" in other libraries. /// /// It returns a triplet of the following from the reordered slice: @@ -3004,8 +3007,9 @@ impl [T] { /// /// # Current implementation /// - /// The current algorithm is based on the quickselect portion of the same quicksort algorithm - /// used for [`sort_unstable`]. + /// The current algorithm is an introselect implementation based on Pattern Defeating Quicksort, which is also + /// the basis for [`sort_unstable`]. The fallback algorithm is Median of Medians using Tukey's Ninther for + /// pivot selection, which guarantees linear runtime for all inputs. /// /// [`sort_unstable`]: slice::sort_unstable /// @@ -3034,7 +3038,7 @@ impl [T] { where T: Ord, { - sort::partition_at_index(self, index, T::lt) + select::partition_at_index(self, index, T::lt) } /// Reorder the slice with a comparator function such that the element at `index` is at its @@ -3043,9 +3047,8 @@ impl [T] { /// This reordering has the additional property that any value at position `i < index` will be /// less than or equal to any value at a position `j > index` using the comparator function. /// Additionally, this reordering is unstable (i.e. any number of equal elements may end up at - /// position `index`), in-place (i.e. does not allocate), and *O*(*n*) on average. - /// The worst-case performance is *O*(*n* log *n*). This function is also known as - /// "kth element" in other libraries. + /// position `index`), in-place (i.e. does not allocate), and runs in *O*(*n*) time. + /// This function is also known as "kth element" in other libraries. /// /// It returns a triplet of the following from /// the slice reordered according to the provided comparator function: the subslice prior to @@ -3055,8 +3058,9 @@ impl [T] { /// /// # Current implementation /// - /// The current algorithm is based on the quickselect portion of the same quicksort algorithm - /// used for [`sort_unstable`]. + /// The current algorithm is an introselect implementation based on Pattern Defeating Quicksort, which is also + /// the basis for [`sort_unstable`]. The fallback algorithm is Median of Medians using Tukey's Ninther for + /// pivot selection, which guarantees linear runtime for all inputs. /// /// [`sort_unstable`]: slice::sort_unstable /// @@ -3089,7 +3093,7 @@ impl [T] { where F: FnMut(&T, &T) -> Ordering, { - sort::partition_at_index(self, index, |a: &T, b: &T| compare(a, b) == Less) + select::partition_at_index(self, index, |a: &T, b: &T| compare(a, b) == Less) } /// Reorder the slice with a key extraction function such that the element at `index` is at its @@ -3098,8 +3102,7 @@ impl [T] { /// This reordering has the additional property that any value at position `i < index` will be /// less than or equal to any value at a position `j > index` using the key extraction function. /// Additionally, this reordering is unstable (i.e. any number of equal elements may end up at - /// position `index`), in-place (i.e. does not allocate), and *O*(*n*) on average. - /// The worst-case performance is *O*(*n* log *n*). + /// position `index`), in-place (i.e. does not allocate), and runs in *O*(*n*) time. /// This function is also known as "kth element" in other libraries. /// /// It returns a triplet of the following from @@ -3110,8 +3113,9 @@ impl [T] { /// /// # Current implementation /// - /// The current algorithm is based on the quickselect portion of the same quicksort algorithm - /// used for [`sort_unstable`]. + /// The current algorithm is an introselect implementation based on Pattern Defeating Quicksort, which is also + /// the basis for [`sort_unstable`]. The fallback algorithm is Median of Medians using Tukey's Ninther for + /// pivot selection, which guarantees linear runtime for all inputs. /// /// [`sort_unstable`]: slice::sort_unstable /// @@ -3145,7 +3149,7 @@ impl [T] { F: FnMut(&T) -> K, K: Ord, { - sort::partition_at_index(self, index, |a: &T, b: &T| f(a).lt(&f(b))) + select::partition_at_index(self, index, |a: &T, b: &T| f(a).lt(&f(b))) } /// Moves all consecutive repeated elements to the end of the slice according to the diff --git a/library/core/src/slice/raw.rs b/library/core/src/slice/raw.rs index 052fd34d0b6b7..48a6eb03b5e1c 100644 --- a/library/core/src/slice/raw.rs +++ b/library/core/src/slice/raw.rs @@ -32,7 +32,8 @@ use crate::ptr; /// * The memory referenced by the returned slice must not be mutated for the duration /// of lifetime `'a`, except inside an `UnsafeCell`. /// -/// * The total size `len * mem::size_of::()` of the slice must be no larger than `isize::MAX`. +/// * The total size `len * mem::size_of::()` of the slice must be no larger than `isize::MAX`, +/// and adding that size to `data` must not "wrap around" the address space. /// See the safety documentation of [`pointer::offset`]. /// /// # Caveat @@ -125,7 +126,8 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] /// (not derived from the return value) for the duration of lifetime `'a`. /// Both read and write accesses are forbidden. /// -/// * The total size `len * mem::size_of::()` of the slice must be no larger than `isize::MAX`. +/// * The total size `len * mem::size_of::()` of the slice must be no larger than `isize::MAX`, +/// and adding that size to `data` must not "wrap around" the address space. /// See the safety documentation of [`pointer::offset`]. /// /// [valid]: ptr#safety @@ -179,15 +181,16 @@ pub const fn from_mut(s: &mut T) -> &mut [T] { /// the last element, such that the offset from the end to the start pointer is /// the length of the slice. /// -/// * The range must contain `N` consecutive properly initialized values of type `T`: +/// * The entire memory range of this slice must be contained within a single allocated object! +/// Slices can never span across multiple allocated objects. /// -/// * The entire memory range of this slice must be contained within a single allocated object! -/// Slices can never span across multiple allocated objects. +/// * The range must contain `N` consecutive properly initialized values of type `T`. /// /// * The memory referenced by the returned slice must not be mutated for the duration /// of lifetime `'a`, except inside an `UnsafeCell`. /// -/// * The total length of the range must be no larger than `isize::MAX`. +/// * The total length of the range must be no larger than `isize::MAX`, +/// and adding that size to `data` must not "wrap around" the address space. /// See the safety documentation of [`pointer::offset`]. /// /// Note that a range created from [`slice::as_ptr_range`] fulfills these requirements. @@ -247,16 +250,17 @@ pub const unsafe fn from_ptr_range<'a, T>(range: Range<*const T>) -> &'a [T] { /// the last element, such that the offset from the end to the start pointer is /// the length of the slice. /// -/// * The range must contain `N` consecutive properly initialized values of type `T`: +/// * The entire memory range of this slice must be contained within a single allocated object! +/// Slices can never span across multiple allocated objects. /// -/// * The entire memory range of this slice must be contained within a single allocated object! -/// Slices can never span across multiple allocated objects. +/// * The range must contain `N` consecutive properly initialized values of type `T`. /// /// * The memory referenced by the returned slice must not be accessed through any other pointer /// (not derived from the return value) for the duration of lifetime `'a`. /// Both read and write accesses are forbidden. /// -/// * The total length of the range must be no larger than `isize::MAX`. +/// * The total length of the range must be no larger than `isize::MAX`, +/// and adding that size to `data` must not "wrap around" the address space. /// See the safety documentation of [`pointer::offset`]. /// /// Note that a range created from [`slice::as_mut_ptr_range`] fulfills these requirements. diff --git a/library/core/src/slice/select.rs b/library/core/src/slice/select.rs new file mode 100644 index 0000000000000..ffc193578e075 --- /dev/null +++ b/library/core/src/slice/select.rs @@ -0,0 +1,302 @@ +//! Slice selection +//! +//! This module contains the implementation for `slice::select_nth_unstable`. +//! It uses an introselect algorithm based on Orson Peters' pattern-defeating quicksort, +//! published at: +//! +//! The fallback algorithm used for introselect is Median of Medians using Tukey's Ninther +//! for pivot selection. Using this as a fallback ensures O(n) worst case running time with +//! better performance than one would get using heapsort as fallback. + +use crate::cmp; +use crate::mem::{self, SizedTypeProperties}; +use crate::slice::sort::{ + break_patterns, choose_pivot, insertion_sort_shift_left, partition, partition_equal, +}; + +// For slices of up to this length it's probably faster to simply sort them. +// Defined at the module scope because it's used in multiple functions. +const MAX_INSERTION: usize = 10; + +fn partition_at_index_loop<'a, T, F>( + mut v: &'a mut [T], + mut index: usize, + is_less: &mut F, + mut pred: Option<&'a T>, +) where + F: FnMut(&T, &T) -> bool, +{ + // Limit the amount of iterations and fall back to fast deterministic selection + // to ensure O(n) worst case running time. This limit needs to be constant, because + // using `ilog2(len)` like in `sort` would result in O(n log n) time complexity. + // The exact value of the limit is chosen somewhat arbitrarily, but for most inputs bad pivot + // selections should be relatively rare, so the limit usually shouldn't be reached + // anyways. + let mut limit = 16; + + // True if the last partitioning was reasonably balanced. + let mut was_balanced = true; + + loop { + if v.len() <= MAX_INSERTION { + if v.len() > 1 { + insertion_sort_shift_left(v, 1, is_less); + } + return; + } + + if limit == 0 { + median_of_medians(v, is_less, index); + return; + } + + // If the last partitioning was imbalanced, try breaking patterns in the slice by shuffling + // some elements around. Hopefully we'll choose a better pivot this time. + if !was_balanced { + break_patterns(v); + limit -= 1; + } + + // Choose a pivot + let (pivot, _) = choose_pivot(v, is_less); + + // If the chosen pivot is equal to the predecessor, then it's the smallest element in the + // slice. Partition the slice into elements equal to and elements greater than the pivot. + // This case is usually hit when the slice contains many duplicate elements. + if let Some(p) = pred { + if !is_less(p, &v[pivot]) { + let mid = partition_equal(v, pivot, is_less); + + // If we've passed our index, then we're good. + if mid > index { + return; + } + + // Otherwise, continue sorting elements greater than the pivot. + v = &mut v[mid..]; + index = index - mid; + pred = None; + continue; + } + } + + let (mid, _) = partition(v, pivot, is_less); + was_balanced = cmp::min(mid, v.len() - mid) >= v.len() / 8; + + // Split the slice into `left`, `pivot`, and `right`. + let (left, right) = v.split_at_mut(mid); + let (pivot, right) = right.split_at_mut(1); + let pivot = &pivot[0]; + + if mid < index { + v = right; + index = index - mid - 1; + pred = Some(pivot); + } else if mid > index { + v = left; + } else { + // If mid == index, then we're done, since partition() guaranteed that all elements + // after mid are greater than or equal to mid. + return; + } + } +} + +/// Helper function that returns the index of the minimum element in the slice using the given +/// comparator function +fn min_index bool>(slice: &[T], is_less: &mut F) -> Option { + slice + .iter() + .enumerate() + .reduce(|acc, t| if is_less(t.1, acc.1) { t } else { acc }) + .map(|(i, _)| i) +} + +/// Helper function that returns the index of the maximum element in the slice using the given +/// comparator function +fn max_index bool>(slice: &[T], is_less: &mut F) -> Option { + slice + .iter() + .enumerate() + .reduce(|acc, t| if is_less(acc.1, t.1) { t } else { acc }) + .map(|(i, _)| i) +} + +/// Reorder the slice such that the element at `index` is at its final sorted position. +pub fn partition_at_index( + v: &mut [T], + index: usize, + mut is_less: F, +) -> (&mut [T], &mut T, &mut [T]) +where + F: FnMut(&T, &T) -> bool, +{ + if index >= v.len() { + panic!("partition_at_index index {} greater than length of slice {}", index, v.len()); + } + + if T::IS_ZST { + // Sorting has no meaningful behavior on zero-sized types. Do nothing. + } else if index == v.len() - 1 { + // Find max element and place it in the last position of the array. We're free to use + // `unwrap()` here because we know v must not be empty. + let max_idx = max_index(v, &mut is_less).unwrap(); + v.swap(max_idx, index); + } else if index == 0 { + // Find min element and place it in the first position of the array. We're free to use + // `unwrap()` here because we know v must not be empty. + let min_idx = min_index(v, &mut is_less).unwrap(); + v.swap(min_idx, index); + } else { + partition_at_index_loop(v, index, &mut is_less, None); + } + + let (left, right) = v.split_at_mut(index); + let (pivot, right) = right.split_at_mut(1); + let pivot = &mut pivot[0]; + (left, pivot, right) +} + +/// Selection algorithm to select the k-th element from the slice in guaranteed O(n) time. +/// This is essentially a quickselect that uses Tukey's Ninther for pivot selection +fn median_of_medians bool>(mut v: &mut [T], is_less: &mut F, mut k: usize) { + // Since this function isn't public, it should never be called with an out-of-bounds index. + debug_assert!(k < v.len()); + + // If T is as ZST, `partition_at_index` will already return early. + debug_assert!(!T::IS_ZST); + + // We now know that `k < v.len() <= isize::MAX` + loop { + if v.len() <= MAX_INSERTION { + if v.len() > 1 { + insertion_sort_shift_left(v, 1, is_less); + } + return; + } + + // `median_of_{minima,maxima}` can't handle the extreme cases of the first/last element, + // so we catch them here and just do a linear search. + if k == v.len() - 1 { + // Find max element and place it in the last position of the array. We're free to use + // `unwrap()` here because we know v must not be empty. + let max_idx = max_index(v, is_less).unwrap(); + v.swap(max_idx, k); + return; + } else if k == 0 { + // Find min element and place it in the first position of the array. We're free to use + // `unwrap()` here because we know v must not be empty. + let min_idx = min_index(v, is_less).unwrap(); + v.swap(min_idx, k); + return; + } + + let p = median_of_ninthers(v, is_less); + + if p == k { + return; + } else if p > k { + v = &mut v[..p]; + } else { + // Since `p < k < v.len()`, `p + 1` doesn't overflow and is + // a valid index into the slice. + v = &mut v[p + 1..]; + k -= p + 1; + } + } +} + +// Optimized for when `k` lies somewhere in the middle of the slice. Selects a pivot +// as close as possible to the median of the slice. For more details on how the algorithm +// operates, refer to the paper . +fn median_of_ninthers bool>(v: &mut [T], is_less: &mut F) -> usize { + // use `saturating_mul` so the multiplication doesn't overflow on 16-bit platforms. + let frac = if v.len() <= 1024 { + v.len() / 12 + } else if v.len() <= 128_usize.saturating_mul(1024) { + v.len() / 64 + } else { + v.len() / 1024 + }; + + let pivot = frac / 2; + let lo = v.len() / 2 - pivot; + let hi = frac + lo; + let gap = (v.len() - 9 * frac) / 4; + let mut a = lo - 4 * frac - gap; + let mut b = hi + gap; + for i in lo..hi { + ninther(v, is_less, a, i - frac, b, a + 1, i, b + 1, a + 2, i + frac, b + 2); + a += 3; + b += 3; + } + + median_of_medians(&mut v[lo..lo + frac], is_less, pivot); + partition(v, lo + pivot, is_less).0 +} + +/// Moves around the 9 elements at the indices a..i, such that +/// `v[d]` contains the median of the 9 elements and the other +/// elements are partitioned around it. +fn ninther bool>( + v: &mut [T], + is_less: &mut F, + a: usize, + mut b: usize, + c: usize, + mut d: usize, + e: usize, + mut f: usize, + g: usize, + mut h: usize, + i: usize, +) { + b = median_idx(v, is_less, a, b, c); + h = median_idx(v, is_less, g, h, i); + if is_less(&v[h], &v[b]) { + mem::swap(&mut b, &mut h); + } + if is_less(&v[f], &v[d]) { + mem::swap(&mut d, &mut f); + } + if is_less(&v[e], &v[d]) { + // do nothing + } else if is_less(&v[f], &v[e]) { + d = f; + } else { + if is_less(&v[e], &v[b]) { + v.swap(e, b); + } else if is_less(&v[h], &v[e]) { + v.swap(e, h); + } + return; + } + if is_less(&v[d], &v[b]) { + d = b; + } else if is_less(&v[h], &v[d]) { + d = h; + } + + v.swap(d, e); +} + +/// returns the index pointing to the median of the 3 +/// elements `v[a]`, `v[b]` and `v[c]` +fn median_idx bool>( + v: &[T], + is_less: &mut F, + mut a: usize, + b: usize, + mut c: usize, +) -> usize { + if is_less(&v[c], &v[a]) { + mem::swap(&mut a, &mut c); + } + if is_less(&v[c], &v[b]) { + return c; + } + if is_less(&v[b], &v[a]) { + return a; + } + b +} diff --git a/library/core/src/slice/sort.rs b/library/core/src/slice/sort.rs index eb8595ca90d56..db76d26257aac 100644 --- a/library/core/src/slice/sort.rs +++ b/library/core/src/slice/sort.rs @@ -145,7 +145,7 @@ where /// Never inline this function to avoid code bloat. It still optimizes nicely and has practically no /// performance impact. Even improving performance in some cases. #[inline(never)] -fn insertion_sort_shift_left(v: &mut [T], offset: usize, is_less: &mut F) +pub(super) fn insertion_sort_shift_left(v: &mut [T], offset: usize, is_less: &mut F) where F: FnMut(&T, &T) -> bool, { @@ -557,7 +557,7 @@ where /// /// 1. Number of elements smaller than `v[pivot]`. /// 2. True if `v` was already partitioned. -fn partition(v: &mut [T], pivot: usize, is_less: &mut F) -> (usize, bool) +pub(super) fn partition(v: &mut [T], pivot: usize, is_less: &mut F) -> (usize, bool) where F: FnMut(&T, &T) -> bool, { @@ -612,7 +612,7 @@ where /// /// Returns the number of elements equal to the pivot. It is assumed that `v` does not contain /// elements smaller than the pivot. -fn partition_equal(v: &mut [T], pivot: usize, is_less: &mut F) -> usize +pub(super) fn partition_equal(v: &mut [T], pivot: usize, is_less: &mut F) -> usize where F: FnMut(&T, &T) -> bool, { @@ -670,7 +670,7 @@ where /// Scatters some elements around in an attempt to break patterns that might cause imbalanced /// partitions in quicksort. #[cold] -fn break_patterns(v: &mut [T]) { +pub(super) fn break_patterns(v: &mut [T]) { let len = v.len(); if len >= 8 { let mut seed = len; @@ -719,7 +719,7 @@ fn break_patterns(v: &mut [T]) { /// Chooses a pivot in `v` and returns the index and `true` if the slice is likely already sorted. /// /// Elements in `v` might be reordered in the process. -fn choose_pivot(v: &mut [T], is_less: &mut F) -> (usize, bool) +pub(super) fn choose_pivot(v: &mut [T], is_less: &mut F) -> (usize, bool) where F: FnMut(&T, &T) -> bool, { @@ -897,138 +897,6 @@ where recurse(v, &mut is_less, None, limit); } -fn partition_at_index_loop<'a, T, F>( - mut v: &'a mut [T], - mut index: usize, - is_less: &mut F, - mut pred: Option<&'a T>, -) where - F: FnMut(&T, &T) -> bool, -{ - // Limit the amount of iterations and fall back to heapsort, similarly to `slice::sort_unstable`. - // This lowers the worst case running time from O(n^2) to O(n log n). - // FIXME: Investigate whether it would be better to use something like Median of Medians - // or Fast Deterministic Selection to guarantee O(n) worst case. - let mut limit = usize::BITS - v.len().leading_zeros(); - - // True if the last partitioning was reasonably balanced. - let mut was_balanced = true; - - loop { - let len = v.len(); - - // For slices of up to this length it's probably faster to simply sort them. - const MAX_INSERTION: usize = 10; - if len <= MAX_INSERTION { - if len >= 2 { - insertion_sort_shift_left(v, 1, is_less); - } - return; - } - - if limit == 0 { - heapsort(v, is_less); - return; - } - - // If the last partitioning was imbalanced, try breaking patterns in the slice by shuffling - // some elements around. Hopefully we'll choose a better pivot this time. - if !was_balanced { - break_patterns(v); - limit -= 1; - } - - // Choose a pivot - let (pivot, _) = choose_pivot(v, is_less); - - // If the chosen pivot is equal to the predecessor, then it's the smallest element in the - // slice. Partition the slice into elements equal to and elements greater than the pivot. - // This case is usually hit when the slice contains many duplicate elements. - if let Some(p) = pred { - if !is_less(p, &v[pivot]) { - let mid = partition_equal(v, pivot, is_less); - - // If we've passed our index, then we're good. - if mid > index { - return; - } - - // Otherwise, continue sorting elements greater than the pivot. - v = &mut v[mid..]; - index = index - mid; - pred = None; - continue; - } - } - - let (mid, _) = partition(v, pivot, is_less); - was_balanced = cmp::min(mid, len - mid) >= len / 8; - - // Split the slice into `left`, `pivot`, and `right`. - let (left, right) = v.split_at_mut(mid); - let (pivot, right) = right.split_at_mut(1); - let pivot = &pivot[0]; - - if mid < index { - v = right; - index = index - mid - 1; - pred = Some(pivot); - } else if mid > index { - v = left; - } else { - // If mid == index, then we're done, since partition() guaranteed that all elements - // after mid are greater than or equal to mid. - return; - } - } -} - -/// Reorder the slice such that the element at `index` is at its final sorted position. -pub fn partition_at_index( - v: &mut [T], - index: usize, - mut is_less: F, -) -> (&mut [T], &mut T, &mut [T]) -where - F: FnMut(&T, &T) -> bool, -{ - use cmp::Ordering::Greater; - use cmp::Ordering::Less; - - if index >= v.len() { - panic!("partition_at_index index {} greater than length of slice {}", index, v.len()); - } - - if T::IS_ZST { - // Sorting has no meaningful behavior on zero-sized types. Do nothing. - } else if index == v.len() - 1 { - // Find max element and place it in the last position of the array. We're free to use - // `unwrap()` here because we know v must not be empty. - let (max_index, _) = v - .iter() - .enumerate() - .max_by(|&(_, x), &(_, y)| if is_less(x, y) { Less } else { Greater }) - .unwrap(); - v.swap(max_index, index); - } else if index == 0 { - // Find min element and place it in the first position of the array. We're free to use - // `unwrap()` here because we know v must not be empty. - let (min_index, _) = v - .iter() - .enumerate() - .min_by(|&(_, x), &(_, y)| if is_less(x, y) { Less } else { Greater }) - .unwrap(); - v.swap(min_index, index); - } else { - partition_at_index_loop(v, index, &mut is_less, None); - } - - let (left, right) = v.split_at_mut(index); - let (pivot, right) = right.split_at_mut(1); - let pivot = &mut pivot[0]; - (left, pivot, right) -} - /// Merges non-decreasing runs `v[..mid]` and `v[mid..]` using `buf` as temporary storage, and /// stores the result into `v[..]`. /// diff --git a/library/core/src/str/converts.rs b/library/core/src/str/converts.rs index 5f8748206d764..0f23cf7ae239f 100644 --- a/library/core/src/str/converts.rs +++ b/library/core/src/str/converts.rs @@ -84,6 +84,7 @@ use super::Utf8Error; #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_str_from_utf8_shared", since = "1.63.0")] #[rustc_allow_const_fn_unstable(str_internals)] +#[rustc_diagnostic_item = "str_from_utf8"] pub const fn from_utf8(v: &[u8]) -> Result<&str, Utf8Error> { // FIXME: This should use `?` again, once it's `const` match run_utf8_validation(v) { @@ -127,6 +128,7 @@ pub const fn from_utf8(v: &[u8]) -> Result<&str, Utf8Error> { /// errors that can be returned. #[stable(feature = "str_mut_extras", since = "1.20.0")] #[rustc_const_unstable(feature = "const_str_from_utf8", issue = "91006")] +#[rustc_diagnostic_item = "str_from_utf8_mut"] pub const fn from_utf8_mut(v: &mut [u8]) -> Result<&mut str, Utf8Error> { // This should use `?` again, once it's `const` match run_utf8_validation(v) { @@ -167,6 +169,7 @@ pub const fn from_utf8_mut(v: &mut [u8]) -> Result<&mut str, Utf8Error> { #[must_use] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_str_from_utf8_unchecked", since = "1.55.0")] +#[rustc_diagnostic_item = "str_from_utf8_unchecked"] pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str { // SAFETY: the caller must guarantee that the bytes `v` are valid UTF-8. // Also relies on `&str` and `&[u8]` having the same layout. @@ -194,6 +197,7 @@ pub const unsafe fn from_utf8_unchecked(v: &[u8]) -> &str { #[must_use] #[stable(feature = "str_mut_extras", since = "1.20.0")] #[rustc_const_unstable(feature = "const_str_from_utf8_unchecked_mut", issue = "91005")] +#[rustc_diagnostic_item = "str_from_utf8_unchecked_mut"] pub const unsafe fn from_utf8_unchecked_mut(v: &mut [u8]) -> &mut str { // SAFETY: the caller must guarantee that the bytes `v` // are valid UTF-8, thus the cast to `*mut str` is safe. diff --git a/library/core/src/str/iter.rs b/library/core/src/str/iter.rs index 772c3605562cf..cd16810c4dde7 100644 --- a/library/core/src/str/iter.rs +++ b/library/core/src/str/iter.rs @@ -1439,11 +1439,22 @@ impl<'a> Iterator for EncodeUtf16<'a> { #[inline] fn size_hint(&self) -> (usize, Option) { - let (low, high) = self.chars.size_hint(); - // every char gets either one u16 or two u16, - // so this iterator is between 1 or 2 times as - // long as the underlying iterator. - (low, high.and_then(|n| n.checked_mul(2))) + let len = self.chars.iter.len(); + // The highest bytes:code units ratio occurs for 3-byte sequences, + // since a 4-byte sequence results in 2 code units. The lower bound + // is therefore determined by assuming the remaining bytes contain as + // many 3-byte sequences as possible. The highest bytes:code units + // ratio is for 1-byte sequences, so use this for the upper bound. + // `(len + 2)` can't overflow, because we know that the `slice::Iter` + // belongs to a slice in memory which has a maximum length of + // `isize::MAX` (that's well below `usize::MAX`) + if self.extra == 0 { + ((len + 2) / 3, Some(len)) + } else { + // We're in the middle of a surrogate pair, so add the remaining + // surrogate to the bounds. + ((len + 2) / 3 + 1, Some(len + 1)) + } } } diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index ef05b25fdd06c..e5f34952c7d40 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -144,8 +144,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let len = "foo".len(); /// assert_eq!(3, len); @@ -165,8 +163,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = ""; /// assert!(s.is_empty()); @@ -271,14 +267,13 @@ impl str { /// Finds the closest `x` not below `index` where `is_char_boundary(x)` is `true`. /// + /// If `index` is greater than the length of the string, this returns the length of the string. + /// /// This method is the natural complement to [`floor_char_boundary`]. See that method /// for more details. /// /// [`floor_char_boundary`]: str::floor_char_boundary /// - /// # Panics - /// - /// Panics if `index > self.len()`. /// /// # Examples /// @@ -296,7 +291,7 @@ impl str { #[inline] pub fn ceil_char_boundary(&self, index: usize) -> usize { if index > self.len() { - slice_error_fail(self, index, index) + self.len() } else { let upper_bound = Ord::min(index + 4, self.len()); self.as_bytes()[index..upper_bound] @@ -311,8 +306,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let bytes = "bors".as_bytes(); /// assert_eq!(b"bors", bytes); @@ -387,8 +380,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = "Hello"; /// let ptr = s.as_ptr(); @@ -570,8 +561,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = "Löwe 老虎 Léopard"; /// @@ -649,8 +638,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = "Per Martin-Löf"; /// @@ -691,8 +678,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = "Per Martin-Löf".to_string(); /// { @@ -840,8 +825,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut bytes = "bors".bytes(); /// @@ -968,6 +951,10 @@ impl str { /// /// Line terminators are not included in the lines returned by the iterator. /// + /// Note that any carriage return (`\r`) not immediately followed by a + /// line feed (`\n`) does not split a line. These carriage returns are + /// thereby included in the produced lines. + /// /// The final line ending is optional. A string that ends with a final line /// ending will return the same lines as an otherwise identical string /// without a final line ending. @@ -977,18 +964,19 @@ impl str { /// Basic usage: /// /// ``` - /// let text = "foo\r\nbar\n\nbaz\n"; + /// let text = "foo\r\nbar\n\nbaz\r"; /// let mut lines = text.lines(); /// /// assert_eq!(Some("foo"), lines.next()); /// assert_eq!(Some("bar"), lines.next()); /// assert_eq!(Some(""), lines.next()); - /// assert_eq!(Some("baz"), lines.next()); + /// // Trailing carriage return is included in the last line + /// assert_eq!(Some("baz\r"), lines.next()); /// /// assert_eq!(None, lines.next()); /// ``` /// - /// The final line ending isn't required: + /// The final line does not require any ending: /// /// ``` /// let text = "foo\nbar\n\r\nbaz"; @@ -1020,8 +1008,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let text = "Zażółć gęślą jaźń"; /// @@ -1050,8 +1036,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let bananas = "bananas"; /// @@ -1077,8 +1061,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let bananas = "bananas"; /// @@ -1103,8 +1085,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let bananas = "bananas"; /// @@ -1463,8 +1443,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let v: Vec<&str> = "A.B.".split_terminator('.').collect(); /// assert_eq!(v, ["A", "B"]); @@ -1692,12 +1670,10 @@ impl str { /// If the pattern allows a reverse search but its results might differ /// from a forward search, the [`rmatches`] method can be used. /// - /// [`rmatches`]: str::matches + /// [`rmatches`]: str::rmatches /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect(); /// assert_eq!(v, ["abc", "abc", "abc"]); @@ -1732,8 +1708,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect(); /// assert_eq!(v, ["abc", "abc", "abc"]); @@ -1775,8 +1749,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect(); /// assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]); @@ -1817,8 +1789,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect(); /// assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]); @@ -1845,8 +1815,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = "\n Hello\tworld\t\n"; /// @@ -2085,8 +2053,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11"); /// assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123"); @@ -2232,8 +2198,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11"); /// assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123"); diff --git a/library/core/src/str/pattern.rs b/library/core/src/str/pattern.rs index 91ee2903aab43..d5d6d60acf61d 100644 --- a/library/core/src/str/pattern.rs +++ b/library/core/src/str/pattern.rs @@ -1750,7 +1750,9 @@ fn simd_contains(needle: &str, haystack: &str) -> Option { 1 } else { // try a few bytes in case first and last byte of the needle are the same - let Some(second_probe_offset) = (needle.len().saturating_sub(4)..needle.len()).rfind(|&idx| needle[idx] != first_probe) else { + let Some(second_probe_offset) = + (needle.len().saturating_sub(4)..needle.len()).rfind(|&idx| needle[idx] != first_probe) + else { // fall back to other search methods if we can't find any different bytes // since we could otherwise hit some degenerate cases return None; diff --git a/library/core/src/str/traits.rs b/library/core/src/str/traits.rs index 1d52335f28ebf..2b37af66bd6af 100644 --- a/library/core/src/str/traits.rs +++ b/library/core/src/str/traits.rs @@ -252,6 +252,58 @@ unsafe impl SliceIndex for ops::Range { } } +/// Implements substring slicing for arbitrary bounds. +/// +/// Returns a slice of the given string bounded by the byte indices +/// provided by each bound. +/// +/// This operation is *O*(1). +/// +/// # Panics +/// +/// Panics if `begin` or `end` (if it exists and once adjusted for +/// inclusion/exclusion) does not point to the starting byte offset of +/// a character (as defined by `is_char_boundary`), if `begin > end`, or if +/// `end > len`. +#[stable(feature = "slice_index_str_with_ops_bound_pair", since = "1.73.0")] +unsafe impl SliceIndex for (ops::Bound, ops::Bound) { + type Output = str; + + #[inline] + fn get(self, slice: &str) -> Option<&str> { + crate::slice::index::into_range(slice.len(), self)?.get(slice) + } + + #[inline] + fn get_mut(self, slice: &mut str) -> Option<&mut str> { + crate::slice::index::into_range(slice.len(), self)?.get_mut(slice) + } + + #[inline] + unsafe fn get_unchecked(self, slice: *const str) -> *const str { + let len = (slice as *const [u8]).len(); + // SAFETY: the caller has to uphold the safety contract for `get_unchecked`. + unsafe { crate::slice::index::into_range_unchecked(len, self).get_unchecked(slice) } + } + + #[inline] + unsafe fn get_unchecked_mut(self, slice: *mut str) -> *mut str { + let len = (slice as *mut [u8]).len(); + // SAFETY: the caller has to uphold the safety contract for `get_unchecked_mut`. + unsafe { crate::slice::index::into_range_unchecked(len, self).get_unchecked_mut(slice) } + } + + #[inline] + fn index(self, slice: &str) -> &str { + crate::slice::index::into_slice_range(slice.len(), self).index(slice) + } + + #[inline] + fn index_mut(self, slice: &mut str) -> &mut str { + crate::slice::index::into_slice_range(slice.len(), self).index_mut(slice) + } +} + /// Implements substring slicing with syntax `&self[.. end]` or `&mut /// self[.. end]`. /// diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index 236b7f423d6be..22a1c09782ce8 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -131,6 +131,17 @@ use crate::intrinsics; use crate::hint::spin_loop; +// Some architectures don't have byte-sized atomics, which results in LLVM +// emulating them using a LL/SC loop. However for AtomicBool we can take +// advantage of the fact that it only ever contains 0 or 1 and use atomic OR/AND +// instead, which LLVM can emulate using a larger atomic OR/AND operation. +// +// This list should only contain architectures which have word-sized atomic-or/ +// atomic-and instructions but don't natively support byte-sized atomics. +#[cfg(target_has_atomic = "8")] +const EMULATE_ATOMIC_BOOL: bool = + cfg!(any(target_arch = "riscv32", target_arch = "riscv64", target_arch = "loongarch64")); + /// A boolean type which can be safely shared between threads. /// /// This type has the same in-memory representation as a [`bool`]. @@ -553,8 +564,12 @@ impl AtomicBool { #[cfg(target_has_atomic = "8")] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub fn swap(&self, val: bool, order: Ordering) -> bool { - // SAFETY: data races are prevented by atomic intrinsics. - unsafe { atomic_swap(self.v.get(), val as u8, order) != 0 } + if EMULATE_ATOMIC_BOOL { + if val { self.fetch_or(true, order) } else { self.fetch_and(false, order) } + } else { + // SAFETY: data races are prevented by atomic intrinsics. + unsafe { atomic_swap(self.v.get(), val as u8, order) != 0 } + } } /// Stores a value into the [`bool`] if the current value is the same as the `current` value. @@ -664,12 +679,39 @@ impl AtomicBool { success: Ordering, failure: Ordering, ) -> Result { - // SAFETY: data races are prevented by atomic intrinsics. - match unsafe { - atomic_compare_exchange(self.v.get(), current as u8, new as u8, success, failure) - } { - Ok(x) => Ok(x != 0), - Err(x) => Err(x != 0), + if EMULATE_ATOMIC_BOOL { + // Pick the strongest ordering from success and failure. + let order = match (success, failure) { + (SeqCst, _) => SeqCst, + (_, SeqCst) => SeqCst, + (AcqRel, _) => AcqRel, + (_, AcqRel) => { + panic!("there is no such thing as an acquire-release failure ordering") + } + (Release, Acquire) => AcqRel, + (Acquire, _) => Acquire, + (_, Acquire) => Acquire, + (Release, Relaxed) => Release, + (_, Release) => panic!("there is no such thing as a release failure ordering"), + (Relaxed, Relaxed) => Relaxed, + }; + let old = if current == new { + // This is a no-op, but we still need to perform the operation + // for memory ordering reasons. + self.fetch_or(false, order) + } else { + // This sets the value to the new one and returns the old one. + self.swap(new, order) + }; + if old == current { Ok(old) } else { Err(old) } + } else { + // SAFETY: data races are prevented by atomic intrinsics. + match unsafe { + atomic_compare_exchange(self.v.get(), current as u8, new as u8, success, failure) + } { + Ok(x) => Ok(x != 0), + Err(x) => Err(x != 0), + } } } @@ -719,6 +761,10 @@ impl AtomicBool { success: Ordering, failure: Ordering, ) -> Result { + if EMULATE_ATOMIC_BOOL { + return self.compare_exchange(current, new, success, failure); + } + // SAFETY: data races are prevented by atomic intrinsics. match unsafe { atomic_compare_exchange_weak(self.v.get(), current as u8, new as u8, success, failure) @@ -1958,14 +2004,12 @@ macro_rules! atomic_int { $stable_from:meta, $stable_nand:meta, $const_stable:meta, - $stable_init_const:meta, $diagnostic_item:meta, $s_int_type:literal, $extra_feature:expr, $min_fn:ident, $max_fn:ident, $align:expr, - $atomic_new:expr, - $int_type:ident $atomic_type:ident $atomic_init:ident) => { + $int_type:ident $atomic_type:ident) => { /// An integer type which can be safely shared between threads. /// /// This type has the same in-memory representation as the underlying @@ -1988,15 +2032,6 @@ macro_rules! atomic_int { v: UnsafeCell<$int_type>, } - /// An atomic integer initialized to `0`. - #[$stable_init_const] - #[deprecated( - since = "1.34.0", - note = "the `new` function is now preferred", - suggestion = $atomic_new, - )] - pub const $atomic_init: $atomic_type = $atomic_type::new(0); - #[$stable] impl Default for $atomic_type { #[inline] @@ -2874,14 +2909,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicI8"), "i8", "", atomic_min, atomic_max, 1, - "AtomicI8::new(0)", - i8 AtomicI8 ATOMIC_I8_INIT + i8 AtomicI8 } #[cfg(target_has_atomic_load_store = "8")] atomic_int! { @@ -2894,14 +2927,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicU8"), "u8", "", atomic_umin, atomic_umax, 1, - "AtomicU8::new(0)", - u8 AtomicU8 ATOMIC_U8_INIT + u8 AtomicU8 } #[cfg(target_has_atomic_load_store = "16")] atomic_int! { @@ -2914,14 +2945,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicI16"), "i16", "", atomic_min, atomic_max, 2, - "AtomicI16::new(0)", - i16 AtomicI16 ATOMIC_I16_INIT + i16 AtomicI16 } #[cfg(target_has_atomic_load_store = "16")] atomic_int! { @@ -2934,14 +2963,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicU16"), "u16", "", atomic_umin, atomic_umax, 2, - "AtomicU16::new(0)", - u16 AtomicU16 ATOMIC_U16_INIT + u16 AtomicU16 } #[cfg(target_has_atomic_load_store = "32")] atomic_int! { @@ -2954,14 +2981,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicI32"), "i32", "", atomic_min, atomic_max, 4, - "AtomicI32::new(0)", - i32 AtomicI32 ATOMIC_I32_INIT + i32 AtomicI32 } #[cfg(target_has_atomic_load_store = "32")] atomic_int! { @@ -2974,14 +2999,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicU32"), "u32", "", atomic_umin, atomic_umax, 4, - "AtomicU32::new(0)", - u32 AtomicU32 ATOMIC_U32_INIT + u32 AtomicU32 } #[cfg(target_has_atomic_load_store = "64")] atomic_int! { @@ -2994,14 +3017,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicI64"), "i64", "", atomic_min, atomic_max, 8, - "AtomicI64::new(0)", - i64 AtomicI64 ATOMIC_I64_INIT + i64 AtomicI64 } #[cfg(target_has_atomic_load_store = "64")] atomic_int! { @@ -3014,14 +3035,12 @@ atomic_int! { stable(feature = "integer_atomics_stable", since = "1.34.0"), stable(feature = "integer_atomics_stable", since = "1.34.0"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicU64"), "u64", "", atomic_umin, atomic_umax, 8, - "AtomicU64::new(0)", - u64 AtomicU64 ATOMIC_U64_INIT + u64 AtomicU64 } #[cfg(target_has_atomic_load_store = "128")] atomic_int! { @@ -3034,14 +3053,12 @@ atomic_int! { unstable(feature = "integer_atomics", issue = "99069"), unstable(feature = "integer_atomics", issue = "99069"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"), "i128", "#![feature(integer_atomics)]\n\n", atomic_min, atomic_max, 16, - "AtomicI128::new(0)", - i128 AtomicI128 ATOMIC_I128_INIT + i128 AtomicI128 } #[cfg(target_has_atomic_load_store = "128")] atomic_int! { @@ -3054,19 +3071,17 @@ atomic_int! { unstable(feature = "integer_atomics", issue = "99069"), unstable(feature = "integer_atomics", issue = "99069"), rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), - unstable(feature = "integer_atomics", issue = "99069"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"), "u128", "#![feature(integer_atomics)]\n\n", atomic_umin, atomic_umax, 16, - "AtomicU128::new(0)", - u128 AtomicU128 ATOMIC_U128_INIT + u128 AtomicU128 } +#[cfg(target_has_atomic_load_store = "ptr")] macro_rules! atomic_int_ptr_sized { ( $($target_pointer_width:literal $align:literal)* ) => { $( - #[cfg(target_has_atomic_load_store = "ptr")] #[cfg(target_pointer_width = $target_pointer_width)] atomic_int! { cfg(target_has_atomic = "ptr"), @@ -3078,16 +3093,13 @@ macro_rules! atomic_int_ptr_sized { stable(feature = "atomic_from", since = "1.23.0"), stable(feature = "atomic_nand", since = "1.27.0"), rustc_const_stable(feature = "const_ptr_sized_atomics", since = "1.24.0"), - stable(feature = "rust1", since = "1.0.0"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicIsize"), "isize", "", atomic_min, atomic_max, $align, - "AtomicIsize::new(0)", - isize AtomicIsize ATOMIC_ISIZE_INIT + isize AtomicIsize } - #[cfg(target_has_atomic_load_store = "ptr")] #[cfg(target_pointer_width = $target_pointer_width)] atomic_int! { cfg(target_has_atomic = "ptr"), @@ -3099,18 +3111,37 @@ macro_rules! atomic_int_ptr_sized { stable(feature = "atomic_from", since = "1.23.0"), stable(feature = "atomic_nand", since = "1.27.0"), rustc_const_stable(feature = "const_ptr_sized_atomics", since = "1.24.0"), - stable(feature = "rust1", since = "1.0.0"), cfg_attr(not(test), rustc_diagnostic_item = "AtomicUsize"), "usize", "", atomic_umin, atomic_umax, $align, - "AtomicUsize::new(0)", - usize AtomicUsize ATOMIC_USIZE_INIT + usize AtomicUsize } + + /// An [`AtomicIsize`] initialized to `0`. + #[cfg(target_pointer_width = $target_pointer_width)] + #[stable(feature = "rust1", since = "1.0.0")] + #[deprecated( + since = "1.34.0", + note = "the `new` function is now preferred", + suggestion = "AtomicIsize::new(0)", + )] + pub const ATOMIC_ISIZE_INIT: AtomicIsize = AtomicIsize::new(0); + + /// An [`AtomicUsize`] initialized to `0`. + #[cfg(target_pointer_width = $target_pointer_width)] + #[stable(feature = "rust1", since = "1.0.0")] + #[deprecated( + since = "1.34.0", + note = "the `new` function is now preferred", + suggestion = "AtomicUsize::new(0)", + )] + pub const ATOMIC_USIZE_INIT: AtomicUsize = AtomicUsize::new(0); )* }; } +#[cfg(target_has_atomic_load_store = "ptr")] atomic_int_ptr_sized! { "16" 2 "32" 4 diff --git a/library/core/src/task/wake.rs b/library/core/src/task/wake.rs index 7043ab5ff2b41..b63fd5c9095fc 100644 --- a/library/core/src/task/wake.rs +++ b/library/core/src/task/wake.rs @@ -2,6 +2,7 @@ use crate::fmt; use crate::marker::{PhantomData, Unpin}; +use crate::ptr; /// A `RawWaker` allows the implementor of a task executor to create a [`Waker`] /// which provides customized wakeup behavior. @@ -322,6 +323,45 @@ impl Waker { Waker { waker } } + /// Creates a new `Waker` that does nothing when `wake` is called. + /// + /// This is mostly useful for writing tests that need a [`Context`] to poll + /// some futures, but are not expecting those futures to wake the waker or + /// do not need to do anything specific if it happens. + /// + /// # Examples + /// + /// ``` + /// #![feature(noop_waker)] + /// + /// use std::future::Future; + /// use std::task; + /// + /// let waker = task::Waker::noop(); + /// let mut cx = task::Context::from_waker(&waker); + /// + /// let mut future = Box::pin(async { 10 }); + /// assert_eq!(future.as_mut().poll(&mut cx), task::Poll::Ready(10)); + /// ``` + #[inline] + #[must_use] + #[unstable(feature = "noop_waker", issue = "98286")] + pub const fn noop() -> Waker { + const VTABLE: RawWakerVTable = RawWakerVTable::new( + // Cloning just returns a new no-op raw waker + |_| RAW, + // `wake` does nothing + |_| {}, + // `wake_by_ref` does nothing + |_| {}, + // Dropping does nothing as we don't allocate anything + |_| {}, + ); + const RAW: RawWaker = RawWaker::new(ptr::null(), &VTABLE); + + Waker { waker: RAW } + } + /// Get a reference to the underlying [`RawWaker`]. #[inline] #[must_use] diff --git a/library/core/src/tuple.rs b/library/core/src/tuple.rs index 172e5fccb61aa..7782ace69c1c1 100644 --- a/library/core/src/tuple.rs +++ b/library/core/src/tuple.rs @@ -1,6 +1,8 @@ // See src/libstd/primitive_docs.rs for documentation. use crate::cmp::Ordering::{self, *}; +use crate::marker::ConstParamTy; +use crate::marker::{StructuralEq, StructuralPartialEq}; // Recursive macro for implementing n-ary tuple functions and operations // @@ -45,6 +47,27 @@ macro_rules! tuple_impls { {} } + maybe_tuple_doc! { + $($T)+ @ + #[unstable(feature = "structural_match", issue = "31434")] + impl<$($T: ConstParamTy),+> ConstParamTy for ($($T,)+) + {} + } + + maybe_tuple_doc! { + $($T)+ @ + #[unstable(feature = "structural_match", issue = "31434")] + impl<$($T),+> StructuralPartialEq for ($($T,)+) + {} + } + + maybe_tuple_doc! { + $($T)+ @ + #[unstable(feature = "structural_match", issue = "31434")] + impl<$($T),+> StructuralEq for ($($T,)+) + {} + } + maybe_tuple_doc! { $($T)+ @ #[stable(feature = "rust1", since = "1.0.0")] @@ -100,7 +123,7 @@ macro_rules! tuple_impls { } } - #[stable(feature = "array_tuple_conv", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "array_tuple_conv", since = "1.71.0")] impl From<[T; ${count(T)}]> for ($(${ignore(T)} T,)+) { #[inline] #[allow(non_snake_case)] @@ -110,7 +133,7 @@ macro_rules! tuple_impls { } } - #[stable(feature = "array_tuple_conv", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "array_tuple_conv", since = "1.71.0")] impl From<($(${ignore(T)} T,)+)> for [T; ${count(T)}] { #[inline] #[allow(non_snake_case)] diff --git a/library/core/src/unicode/printable.py b/library/core/src/unicode/printable.py index 7c37f5f099c7f..4d39ace066c46 100755 --- a/library/core/src/unicode/printable.py +++ b/library/core/src/unicode/printable.py @@ -119,7 +119,7 @@ def print_singletons(uppers, lowers, uppersname, lowersname): print("#[rustfmt::skip]") print("const {}: &[u8] = &[".format(lowersname)) for i in range(0, len(lowers), 8): - print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8]))) + print(" {}".format(" ".join("{:#04x},".format(x) for x in lowers[i:i+8]))) print("];") def print_normal(normal, normalname): diff --git a/library/core/tests/any.rs b/library/core/tests/any.rs index a8f6b7ebb9250..8d2d31b64310f 100644 --- a/library/core/tests/any.rs +++ b/library/core/tests/any.rs @@ -147,65 +147,3 @@ fn dyn_type_name() { std::any::type_name:: + Send + Sync>() ); } - -// Test the `Provider` API. - -struct SomeConcreteType { - some_string: String, -} - -impl Provider for SomeConcreteType { - fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - demand - .provide_ref::(&self.some_string) - .provide_ref::(&self.some_string) - .provide_value_with::(|| "bye".to_owned()); - } -} - -// Test the provide and request mechanisms with a by-reference trait object. -#[test] -fn test_provider() { - let obj: &dyn Provider = &SomeConcreteType { some_string: "hello".to_owned() }; - - assert_eq!(&**request_ref::(obj).unwrap(), "hello"); - assert_eq!(&*request_value::(obj).unwrap(), "bye"); - assert_eq!(request_value::(obj), None); -} - -// Test the provide and request mechanisms with a boxed trait object. -#[test] -fn test_provider_boxed() { - let obj: Box = Box::new(SomeConcreteType { some_string: "hello".to_owned() }); - - assert_eq!(&**request_ref::(&*obj).unwrap(), "hello"); - assert_eq!(&*request_value::(&*obj).unwrap(), "bye"); - assert_eq!(request_value::(&*obj), None); -} - -// Test the provide and request mechanisms with a concrete object. -#[test] -fn test_provider_concrete() { - let obj = SomeConcreteType { some_string: "hello".to_owned() }; - - assert_eq!(&**request_ref::(&obj).unwrap(), "hello"); - assert_eq!(&*request_value::(&obj).unwrap(), "bye"); - assert_eq!(request_value::(&obj), None); -} - -trait OtherTrait: Provider {} - -impl OtherTrait for SomeConcreteType {} - -impl dyn OtherTrait { - fn get_ref(&self) -> Option<&T> { - request_ref::(self) - } -} - -// Test the provide and request mechanisms via an intermediate trait. -#[test] -fn test_provider_intermediate() { - let obj: &dyn OtherTrait = &SomeConcreteType { some_string: "hello".to_owned() }; - assert_eq!(obj.get_ref::().unwrap(), "hello"); -} diff --git a/library/core/tests/array.rs b/library/core/tests/array.rs index 0869644c040f5..982d7853f6936 100644 --- a/library/core/tests/array.rs +++ b/library/core/tests/array.rs @@ -257,14 +257,8 @@ fn iterator_drops() { assert_eq!(i.get(), 5); } -// This test does not work on targets without panic=unwind support. -// To work around this problem, test is marked is should_panic, so it will -// be automagically skipped on unsuitable targets, such as -// wasm32-unknown-unknown. -// -// It means that we use panic for indicating success. -#[test] -#[should_panic(expected = "test succeeded")] +#[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn array_default_impl_avoids_leaks_on_panic() { use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; static COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -296,7 +290,6 @@ fn array_default_impl_avoids_leaks_on_panic() { assert_eq!(*panic_msg, "bomb limit exceeded"); // check that all bombs are successfully dropped assert_eq!(COUNTER.load(Relaxed), 0); - panic!("test succeeded") } #[test] @@ -317,9 +310,8 @@ fn array_map() { assert_eq!(b, [1, 2, 3]); } -// See note on above test for why `should_panic` is used. #[test] -#[should_panic(expected = "test succeeded")] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn array_map_drop_safety() { static DROPPED: AtomicUsize = AtomicUsize::new(0); struct DropCounter; @@ -341,7 +333,6 @@ fn array_map_drop_safety() { }); assert!(success.is_err()); assert_eq!(DROPPED.load(Ordering::SeqCst), num_to_create); - panic!("test succeeded") } #[test] diff --git a/library/core/tests/clone.rs b/library/core/tests/clone.rs index aafe5ced2e97f..64193e1155890 100644 --- a/library/core/tests/clone.rs +++ b/library/core/tests/clone.rs @@ -1,5 +1,5 @@ #[test] -#[cfg_attr(not(bootstrap), allow(suspicious_double_ref_op))] +#[allow(suspicious_double_ref_op)] fn test_borrowed_clone() { let x = 5; let y: &i32 = &x; diff --git a/library/core/tests/error.rs b/library/core/tests/error.rs new file mode 100644 index 0000000000000..cb7cb5441d1d8 --- /dev/null +++ b/library/core/tests/error.rs @@ -0,0 +1,66 @@ +use core::error::{request_value, request_ref, Request}; + +// Test the `Request` API. +#[derive(Debug)] +struct SomeConcreteType { + some_string: String, +} + +impl std::fmt::Display for SomeConcreteType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "A") + } +} + +impl std::error::Error for SomeConcreteType { + fn provide<'a>(&'a self, request: &mut Request<'a>) { + request + .provide_ref::(&self.some_string) + .provide_ref::(&self.some_string) + .provide_value_with::(|| "bye".to_owned()); + } +} + +// Test the Error.provide and request mechanisms with a by-reference trait object. +#[test] +fn test_error_generic_member_access() { + let obj = &SomeConcreteType { some_string: "hello".to_owned() }; + + assert_eq!(request_ref::(&*obj).unwrap(), "hello"); + assert_eq!(request_value::(&*obj).unwrap(), "bye"); + assert_eq!(request_value::(&obj), None); +} + +// Test the Error.provide and request mechanisms with a by-reference trait object. +#[test] +fn test_request_constructor() { + let obj: &dyn std::error::Error = &SomeConcreteType { some_string: "hello".to_owned() }; + + assert_eq!(request_ref::(&*obj).unwrap(), "hello"); + assert_eq!(request_value::(&*obj).unwrap(), "bye"); + assert_eq!(request_value::(&obj), None); +} + +// Test the Error.provide and request mechanisms with a boxed trait object. +#[test] +fn test_error_generic_member_access_boxed() { + let obj: Box = + Box::new(SomeConcreteType { some_string: "hello".to_owned() }); + + assert_eq!(request_ref::(&*obj).unwrap(), "hello"); + assert_eq!(request_value::(&*obj).unwrap(), "bye"); + + // NOTE: Box only implements Error when E: Error + Sized, which means we can't pass a + // Box to request_value. + //assert_eq!(request_value::(&obj).unwrap(), "bye"); +} + +// Test the Error.provide and request mechanisms with a concrete object. +#[test] +fn test_error_generic_member_access_concrete() { + let obj = SomeConcreteType { some_string: "hello".to_owned() }; + + assert_eq!(request_ref::(&obj).unwrap(), "hello"); + assert_eq!(request_value::(&obj).unwrap(), "bye"); + assert_eq!(request_value::(&obj), None); +} diff --git a/library/core/tests/future.rs b/library/core/tests/future.rs index 74b6f74e4013c..db417256dd01e 100644 --- a/library/core/tests/future.rs +++ b/library/core/tests/future.rs @@ -30,7 +30,6 @@ fn poll_n(val: usize, num: usize) -> PollN { } #[test] -#[cfg_attr(miri, ignore)] // self-referential generators do not work with Miri's aliasing checks fn test_join() { block_on(async move { let x = join!(async { 0 }).await; diff --git a/library/core/tests/iter/adapters/map_windows.rs b/library/core/tests/iter/adapters/map_windows.rs new file mode 100644 index 0000000000000..7fb2408f8acb7 --- /dev/null +++ b/library/core/tests/iter/adapters/map_windows.rs @@ -0,0 +1,283 @@ +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + +#[cfg(not(panic = "abort"))] +mod drop_checks { + //! These tests mainly make sure the elements are correctly dropped. + use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; + + #[derive(Debug)] + struct DropInfo { + dropped_twice: AtomicBool, + alive_count: AtomicUsize, + } + + impl DropInfo { + const fn new() -> Self { + Self { dropped_twice: AtomicBool::new(false), alive_count: AtomicUsize::new(0) } + } + + #[track_caller] + fn check(&self) { + assert!(!self.dropped_twice.load(SeqCst), "a value was dropped twice"); + assert_eq!(self.alive_count.load(SeqCst), 0); + } + } + + #[derive(Debug)] + struct DropCheck<'a> { + info: &'a DropInfo, + was_dropped: bool, + } + + impl<'a> DropCheck<'a> { + fn new(info: &'a DropInfo) -> Self { + info.alive_count.fetch_add(1, SeqCst); + + Self { info, was_dropped: false } + } + } + + impl Drop for DropCheck<'_> { + fn drop(&mut self) { + if self.was_dropped { + self.info.dropped_twice.store(true, SeqCst); + } + self.was_dropped = true; + + self.info.alive_count.fetch_sub(1, SeqCst); + } + } + + fn iter(info: &DropInfo, len: usize, panic_at: usize) -> impl Iterator> { + (0..len).map(move |i| { + if i == panic_at { + panic!("intended panic"); + } + DropCheck::new(info) + }) + } + + #[track_caller] + fn check(len: usize, panic_at: usize) { + check_drops(|info| { + iter(info, len, panic_at).map_windows(|_: &[_; N]| {}).last(); + }); + } + + #[track_caller] + fn check_drops(f: impl FnOnce(&DropInfo)) { + let info = DropInfo::new(); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + f(&info); + })); + info.check(); + } + + #[test] + fn no_iter_panic_n1() { + check::<1>(0, 100); + check::<1>(1, 100); + check::<1>(2, 100); + check::<1>(13, 100); + } + + #[test] + fn no_iter_panic_n2() { + check::<2>(0, 100); + check::<2>(1, 100); + check::<2>(2, 100); + check::<2>(3, 100); + check::<2>(13, 100); + } + + #[test] + fn no_iter_panic_n5() { + check::<5>(0, 100); + check::<5>(1, 100); + check::<5>(2, 100); + check::<5>(13, 100); + check::<5>(30, 100); + } + + #[test] + fn panic_in_first_batch() { + check::<1>(7, 0); + + check::<2>(7, 0); + check::<2>(7, 1); + + check::<3>(7, 0); + check::<3>(7, 1); + check::<3>(7, 2); + } + + #[test] + fn panic_in_middle() { + check::<1>(7, 1); + check::<1>(7, 5); + check::<1>(7, 6); + + check::<2>(7, 2); + check::<2>(7, 5); + check::<2>(7, 6); + + check::<5>(13, 5); + check::<5>(13, 8); + check::<5>(13, 12); + } + + #[test] + fn len_equals_n() { + check::<1>(1, 100); + check::<1>(1, 0); + + check::<2>(2, 100); + check::<2>(2, 0); + check::<2>(2, 1); + + check::<5>(5, 100); + check::<5>(5, 0); + check::<5>(5, 1); + check::<5>(5, 4); + } +} + +#[test] +fn output_n1() { + assert_eq!("".chars().map_windows(|[c]| *c).collect::>(), vec![]); + assert_eq!("x".chars().map_windows(|[c]| *c).collect::>(), vec!['x']); + assert_eq!("abcd".chars().map_windows(|[c]| *c).collect::>(), vec!['a', 'b', 'c', 'd']); +} + +#[test] +fn output_n2() { + assert_eq!( + "".chars().map_windows(|a: &[_; 2]| *a).collect::>(), + >::new(), + ); + assert_eq!("ab".chars().map_windows(|a: &[_; 2]| *a).collect::>(), vec![['a', 'b']]); + assert_eq!( + "abcd".chars().map_windows(|a: &[_; 2]| *a).collect::>(), + vec![['a', 'b'], ['b', 'c'], ['c', 'd']], + ); +} + +#[test] +fn test_case_from_pr_82413_comment() { + for () in std::iter::repeat("0".to_owned()).map_windows(|_: &[_; 3]| {}).take(4) {} +} + +#[test] +#[should_panic = "array in `Iterator::map_windows` must contain more than 0 elements"] +fn check_zero_window() { + let _ = std::iter::repeat(0).map_windows(|_: &[_; 0]| ()); +} + +#[test] +fn test_zero_sized_type() { + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct Data; + let data: Vec<_> = + std::iter::repeat(Data).take(10).map_windows(|arr: &[Data; 5]| *arr).collect(); + assert_eq!(data, [[Data; 5]; 6]); +} + +#[test] +#[should_panic = "array size of `Iterator::map_windows` is too large"] +fn test_too_large_array_size() { + let _ = std::iter::repeat(()).map_windows(|arr: &[(); usize::MAX]| *arr); +} + +#[test] +fn test_laziness() { + let counter = AtomicUsize::new(0); + let mut iter = (0..5) + .inspect(|_| { + counter.fetch_add(1, SeqCst); + }) + .map_windows(|arr: &[i32; 2]| *arr); + assert_eq!(counter.load(SeqCst), 0); + + assert_eq!(iter.next(), Some([0, 1])); + // The first iteration consumes N items (N = 2). + assert_eq!(counter.load(SeqCst), 2); + + assert_eq!(iter.next(), Some([1, 2])); + assert_eq!(counter.load(SeqCst), 3); + + assert_eq!(iter.next(), Some([2, 3])); + assert_eq!(counter.load(SeqCst), 4); + + assert_eq!(iter.next(), Some([3, 4])); + assert_eq!(counter.load(SeqCst), 5); + + assert_eq!(iter.next(), None); + assert_eq!(counter.load(SeqCst), 5); +} + +#[test] +fn test_size_hint() { + struct SizeHintCheckHelper((usize, Option)); + + impl Iterator for SizeHintCheckHelper { + type Item = i32; + + fn next(&mut self) -> Option { + let (ref mut lo, ref mut hi) = self.0; + let next = (*hi != Some(0)).then_some(0); + *lo = lo.saturating_sub(1); + if let Some(hi) = hi { + *hi = hi.saturating_sub(1); + } + next + } + + fn size_hint(&self) -> (usize, Option) { + self.0 + } + } + + fn check_size_hint( + size_hint: (usize, Option), + mut mapped_size_hint: (usize, Option), + ) { + let mut iter = SizeHintCheckHelper(size_hint); + let mut mapped_iter = iter.by_ref().map_windows(|_: &[_; N]| ()); + while mapped_iter.size_hint().0 > 0 { + assert_eq!(mapped_iter.size_hint(), mapped_size_hint); + assert!(mapped_iter.next().is_some()); + mapped_size_hint.0 -= 1; + mapped_size_hint.1 = mapped_size_hint.1.map(|hi| hi.saturating_sub(1)); + } + } + + check_size_hint::<1>((0, None), (0, None)); + check_size_hint::<1>((0, Some(0)), (0, Some(0))); + check_size_hint::<1>((0, Some(2)), (0, Some(2))); + check_size_hint::<1>((1, None), (1, None)); + check_size_hint::<1>((1, Some(1)), (1, Some(1))); + check_size_hint::<1>((1, Some(4)), (1, Some(4))); + check_size_hint::<1>((5, None), (5, None)); + check_size_hint::<1>((5, Some(5)), (5, Some(5))); + check_size_hint::<1>((5, Some(10)), (5, Some(10))); + + check_size_hint::<2>((0, None), (0, None)); + check_size_hint::<2>((0, Some(0)), (0, Some(0))); + check_size_hint::<2>((0, Some(2)), (0, Some(1))); + check_size_hint::<2>((1, None), (0, None)); + check_size_hint::<2>((1, Some(1)), (0, Some(0))); + check_size_hint::<2>((1, Some(4)), (0, Some(3))); + check_size_hint::<2>((5, None), (4, None)); + check_size_hint::<2>((5, Some(5)), (4, Some(4))); + check_size_hint::<2>((5, Some(10)), (4, Some(9))); + + check_size_hint::<5>((0, None), (0, None)); + check_size_hint::<5>((0, Some(0)), (0, Some(0))); + check_size_hint::<5>((0, Some(2)), (0, Some(0))); + check_size_hint::<5>((1, None), (0, None)); + check_size_hint::<5>((1, Some(1)), (0, Some(0))); + check_size_hint::<5>((1, Some(4)), (0, Some(0))); + check_size_hint::<5>((5, None), (1, None)); + check_size_hint::<5>((5, Some(5)), (1, Some(1))); + check_size_hint::<5>((5, Some(10)), (1, Some(6))); +} diff --git a/library/core/tests/iter/adapters/mod.rs b/library/core/tests/iter/adapters/mod.rs index ca3463aa7f782..dedb4c0a9dd5f 100644 --- a/library/core/tests/iter/adapters/mod.rs +++ b/library/core/tests/iter/adapters/mod.rs @@ -13,6 +13,7 @@ mod fuse; mod inspect; mod intersperse; mod map; +mod map_windows; mod peekable; mod scan; mod skip; diff --git a/library/core/tests/iter/adapters/step_by.rs b/library/core/tests/iter/adapters/step_by.rs index 94f2fa8c25e2d..4c5b1dd9a6bd1 100644 --- a/library/core/tests/iter/adapters/step_by.rs +++ b/library/core/tests/iter/adapters/step_by.rs @@ -244,3 +244,58 @@ fn test_step_by_skip() { assert_eq!((0..=50).step_by(10).nth(3), Some(30)); assert_eq!((200..=255u8).step_by(10).nth(3), Some(230)); } + + +struct DeOpt(I); + +impl Iterator for DeOpt { + type Item = I::Item; + + fn next(&mut self) -> core::option::Option { + self.0.next() + } +} + +impl DoubleEndedIterator for DeOpt { + fn next_back(&mut self) -> core::option::Option { + self.0.next_back() + } +} + +#[test] +fn test_step_by_fold_range_specialization() { + macro_rules! t { + ($range:expr, $var: ident, $body:tt) => { + { + // run the same tests for the non-optimized version + let mut $var = DeOpt($range); + $body + } + { + let mut $var = $range; + $body + } + } + } + + t!((1usize..5).step_by(1), r, { + assert_eq!(r.next_back(), Some(4)); + assert_eq!(r.sum::(), 6); + }); + + t!((0usize..4).step_by(2), r, { + assert_eq!(r.next(), Some(0)); + assert_eq!(r.sum::(), 2); + }); + + + t!((0usize..5).step_by(2), r, { + assert_eq!(r.next(), Some(0)); + assert_eq!(r.sum::(), 6); + }); + + t!((usize::MAX - 6 .. usize::MAX).step_by(5), r, { + assert_eq!(r.next(), Some(usize::MAX - 6)); + assert_eq!(r.sum::(), usize::MAX - 1); + }); +} diff --git a/library/core/tests/iter/traits/iterator.rs b/library/core/tests/iter/traits/iterator.rs index 9eebfb1f1f359..995bbf0e26156 100644 --- a/library/core/tests/iter/traits/iterator.rs +++ b/library/core/tests/iter/traits/iterator.rs @@ -1,3 +1,4 @@ +use core::cmp::Ordering; use core::num::NonZeroUsize; /// A wrapper struct that implements `Eq` and `Ord` based on the wrapped @@ -371,11 +372,39 @@ fn test_by_ref() { #[test] fn test_is_sorted() { + // Tests on integers assert!([1, 2, 2, 9].iter().is_sorted()); assert!(![1, 3, 2].iter().is_sorted()); assert!([0].iter().is_sorted()); - assert!(std::iter::empty::().is_sorted()); + assert!([0, 0].iter().is_sorted()); + assert!(core::iter::empty::().is_sorted()); + + // Tests on floats + assert!([1.0f32, 2.0, 2.0, 9.0].iter().is_sorted()); + assert!(![1.0f32, 3.0f32, 2.0f32].iter().is_sorted()); + assert!([0.0f32].iter().is_sorted()); + assert!([0.0f32, 0.0f32].iter().is_sorted()); + // Test cases with NaNs + assert!([f32::NAN].iter().is_sorted()); + assert!(![f32::NAN, f32::NAN].iter().is_sorted()); assert!(![0.0, 1.0, f32::NAN].iter().is_sorted()); + // Tests from + assert!(![f32::NAN, f32::NAN, f32::NAN].iter().is_sorted()); + assert!(![1.0, f32::NAN, 2.0].iter().is_sorted()); + assert!(![2.0, f32::NAN, 1.0].iter().is_sorted()); + assert!(![2.0, f32::NAN, 1.0, 7.0].iter().is_sorted()); + assert!(![2.0, f32::NAN, 1.0, 0.0].iter().is_sorted()); + assert!(![-f32::NAN, -1.0, 0.0, 1.0, f32::NAN].iter().is_sorted()); + assert!(![f32::NAN, -f32::NAN, -1.0, 0.0, 1.0].iter().is_sorted()); + assert!(![1.0, f32::NAN, -f32::NAN, -1.0, 0.0].iter().is_sorted()); + assert!(![0.0, 1.0, f32::NAN, -f32::NAN, -1.0].iter().is_sorted()); + assert!(![-1.0, 0.0, 1.0, f32::NAN, -f32::NAN].iter().is_sorted()); + + // Tests for is_sorted_by + assert!(![6, 2, 8, 5, 1, -60, 1337].iter().is_sorted()); + assert!([6, 2, 8, 5, 1, -60, 1337].iter().is_sorted_by(|_, _| Some(Ordering::Less))); + + // Tests for is_sorted_by_key assert!([-2, -1, 0, 3].iter().is_sorted()); assert!(![-2i32, -1, 0, 3].iter().is_sorted_by_key(|n| n.abs())); assert!(!["c", "bb", "aaa"].iter().is_sorted()); diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 3933e3289514c..7a6def37a5522 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -93,7 +93,7 @@ #![feature(const_option)] #![feature(const_option_ext)] #![feature(const_result)] -#![feature(integer_atomics)] +#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))] #![feature(int_roundings)] #![feature(slice_group_by)] #![feature(split_array)] @@ -105,11 +105,14 @@ #![feature(const_slice_from_ref)] #![feature(waker_getters)] #![feature(slice_flatten)] -#![feature(provide_any)] +#![feature(error_generic_member_access)] +#![feature(error_in_core)] +#![feature(trait_upcasting)] #![feature(utf8_chunks)] #![feature(is_ascii_octdigit)] #![feature(get_many_mut)] -#![cfg_attr(not(bootstrap), feature(offset_of))] +#![feature(offset_of)] +#![feature(iter_map_windows)] #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] diff --git a/library/core/tests/manually_drop.rs b/library/core/tests/manually_drop.rs index 9eac279733af9..bbf444471ad2a 100644 --- a/library/core/tests/manually_drop.rs +++ b/library/core/tests/manually_drop.rs @@ -1,3 +1,5 @@ +#![allow(undropped_manually_drops)] + use core::mem::ManuallyDrop; #[test] diff --git a/library/core/tests/mem.rs b/library/core/tests/mem.rs index aee9c89b59544..5c2e18745ea21 100644 --- a/library/core/tests/mem.rs +++ b/library/core/tests/mem.rs @@ -366,7 +366,6 @@ fn const_maybe_uninit() { } #[test] -#[cfg(not(bootstrap))] fn offset_of() { #[repr(C)] struct Foo { @@ -391,7 +390,7 @@ fn offset_of() { struct Generic { x: u8, y: u32, - z: T + z: T, } trait Trait {} @@ -409,7 +408,6 @@ fn offset_of() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_union() { #[repr(C)] union Foo { @@ -429,7 +427,6 @@ fn offset_of_union() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_dst() { #[repr(C)] struct Alpha { @@ -469,7 +466,6 @@ fn offset_of_dst() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_packed() { #[repr(C, packed)] struct Foo { @@ -482,7 +478,6 @@ fn offset_of_packed() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_projection() { #[repr(C)] struct Foo { @@ -503,7 +498,6 @@ fn offset_of_projection() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_alias() { #[repr(C)] struct Foo { @@ -518,7 +512,6 @@ fn offset_of_alias() { } #[test] -#[cfg(not(bootstrap))] fn const_offset_of() { #[repr(C)] struct Foo { @@ -534,7 +527,6 @@ fn const_offset_of() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_without_const_promotion() { #[repr(C)] struct Foo { @@ -555,7 +547,6 @@ fn offset_of_without_const_promotion() { } #[test] -#[cfg(not(bootstrap))] fn offset_of_addr() { #[repr(C)] struct Foo { diff --git a/library/core/tests/net/ip_addr.rs b/library/core/tests/net/ip_addr.rs index 5a6ac08c08815..7530fc084874d 100644 --- a/library/core/tests/net/ip_addr.rs +++ b/library/core/tests/net/ip_addr.rs @@ -139,7 +139,7 @@ fn ipv6_addr_to_string() { // ipv4-compatible address let a1 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x280); - assert_eq!(a1.to_string(), "::192.0.2.128"); + assert_eq!(a1.to_string(), "::c000:280"); // v6 address with no zero segments assert_eq!(Ipv6Addr::new(8, 9, 10, 11, 12, 13, 14, 15).to_string(), "8:9:a:b:c:d:e:f"); @@ -316,7 +316,7 @@ fn ip_properties() { check!("::", unspec); check!("::1", loopback); - check!("::0.0.0.2", global); + check!("::2", global); check!("1::", global); check!("fc00::"); check!("fdff:ffff::"); @@ -607,7 +607,7 @@ fn ipv6_properties() { check!("::1", &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], loopback); - check!("::0.0.0.2", &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], global | unicast_global); + check!("::2", &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2], global | unicast_global); check!("1::", &[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], global | unicast_global); diff --git a/library/core/tests/net/socket_addr.rs b/library/core/tests/net/socket_addr.rs index 68c7cd94d322f..35a69cead4826 100644 --- a/library/core/tests/net/socket_addr.rs +++ b/library/core/tests/net/socket_addr.rs @@ -34,7 +34,7 @@ fn ipv6_socket_addr_to_string() { // IPv4-compatible address. assert_eq!( SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x280), 8080, 0, 0).to_string(), - "[::192.0.2.128]:8080" + "[::c000:280]:8080" ); // IPv6 address with no zero segments. diff --git a/library/core/tests/num/flt2dec/random.rs b/library/core/tests/num/flt2dec/random.rs index 0084c1c814e2d..ec0ab2202ca36 100644 --- a/library/core/tests/num/flt2dec/random.rs +++ b/library/core/tests/num/flt2dec/random.rs @@ -1,4 +1,4 @@ -#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(target_family = "wasm"))] use std::mem::MaybeUninit; use std::str; diff --git a/library/core/tests/ptr.rs b/library/core/tests/ptr.rs index c02cd99cc4477..ee885adfeee61 100644 --- a/library/core/tests/ptr.rs +++ b/library/core/tests/ptr.rs @@ -1001,7 +1001,7 @@ fn nonnull_tagged_pointer_with_provenance() { assert_eq!(p.tag(), 3); assert_eq!(unsafe { *p.pointer().as_ptr() }, 10); - unsafe { Box::from_raw(p.pointer().as_ptr()) }; + unsafe { drop(Box::from_raw(p.pointer().as_ptr())) }; /// A non-null pointer type which carries several bits of metadata and maintains provenance. #[repr(transparent)] diff --git a/library/core/tests/slice.rs b/library/core/tests/slice.rs index 88f54591bb4a4..12aab5251ef2c 100644 --- a/library/core/tests/slice.rs +++ b/library/core/tests/slice.rs @@ -1802,7 +1802,7 @@ fn brute_force_rotate_test_1() { } #[test] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] fn sort_unstable() { use core::cmp::Ordering::{Equal, Greater, Less}; use core::slice::heapsort; @@ -1876,7 +1876,7 @@ fn sort_unstable() { } #[test] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] #[cfg_attr(miri, ignore)] // Miri is too slow fn select_nth_unstable() { use core::cmp::Ordering::{Equal, Greater, Less}; @@ -2278,11 +2278,39 @@ fn test_copy_within_panics_src_out_of_bounds() { fn test_is_sorted() { let empty: [i32; 0] = []; + // Tests on integers assert!([1, 2, 2, 9].is_sorted()); assert!(![1, 3, 2].is_sorted()); assert!([0].is_sorted()); + assert!([0, 0].is_sorted()); assert!(empty.is_sorted()); + + // Tests on floats + assert!([1.0f32, 2.0, 2.0, 9.0].is_sorted()); + assert!(![1.0f32, 3.0f32, 2.0f32].is_sorted()); + assert!([0.0f32].is_sorted()); + assert!([0.0f32, 0.0f32].is_sorted()); + // Test cases with NaNs + assert!([f32::NAN].is_sorted()); + assert!(![f32::NAN, f32::NAN].is_sorted()); assert!(![0.0, 1.0, f32::NAN].is_sorted()); + // Tests from + assert!(![f32::NAN, f32::NAN, f32::NAN].is_sorted()); + assert!(![1.0, f32::NAN, 2.0].is_sorted()); + assert!(![2.0, f32::NAN, 1.0].is_sorted()); + assert!(![2.0, f32::NAN, 1.0, 7.0].is_sorted()); + assert!(![2.0, f32::NAN, 1.0, 0.0].is_sorted()); + assert!(![-f32::NAN, -1.0, 0.0, 1.0, f32::NAN].is_sorted()); + assert!(![f32::NAN, -f32::NAN, -1.0, 0.0, 1.0].is_sorted()); + assert!(![1.0, f32::NAN, -f32::NAN, -1.0, 0.0].is_sorted()); + assert!(![0.0, 1.0, f32::NAN, -f32::NAN, -1.0].is_sorted()); + assert!(![-1.0, 0.0, 1.0, f32::NAN, -f32::NAN].is_sorted()); + + // Tests for is_sorted_by + assert!(![6, 2, 8, 5, 1, -60, 1337].is_sorted()); + assert!([6, 2, 8, 5, 1, -60, 1337].is_sorted_by(|_, _| Some(Ordering::Less))); + + // Tests for is_sorted_by_key assert!([-2, -1, 0, 3].is_sorted()); assert!(![-2i32, -1, 0, 3].is_sorted_by_key(|n| n.abs())); assert!(!["c", "bb", "aaa"].is_sorted()); diff --git a/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs index b193d79b0e1a8..76b3591965851 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -14,6 +14,7 @@ #![feature(staged_api)] #![feature(rustc_attrs)] #![feature(c_unwind)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[cfg(target_os = "android")] mod android; diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index ce78ab82ef942..009014de5c2c4 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -19,13 +19,14 @@ #![feature(panic_unwind)] #![feature(staged_api)] #![feature(std_internals)] -#![feature(abi_thiscall)] +#![cfg_attr(bootstrap, feature(abi_thiscall))] #![feature(rustc_attrs)] #![panic_runtime] #![feature(panic_runtime)] #![feature(c_unwind)] // `real_imp` is unused with Miri, so silence warnings. #![cfg_attr(miri, allow(dead_code))] +#![cfg_attr(not(bootstrap), allow(internal_features))] use alloc::boxed::Box; use core::any::Any; diff --git a/library/portable-simd/.github/workflows/ci.yml b/library/portable-simd/.github/workflows/ci.yml index acd47a3da72b2..1ff377fce3467 100644 --- a/library/portable-simd/.github/workflows/ci.yml +++ b/library/portable-simd/.github/workflows/ci.yml @@ -38,8 +38,9 @@ jobs: - i586-unknown-linux-gnu - aarch64-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - - mips-unknown-linux-gnu - - mips64-unknown-linux-gnuabi64 + # non-nightly since https://github.com/rust-lang/rust/pull/113274 + # - mips-unknown-linux-gnu + # - mips64-unknown-linux-gnuabi64 - powerpc-unknown-linux-gnu - powerpc64-unknown-linux-gnu - riscv64gc-unknown-linux-gnu @@ -191,8 +192,8 @@ jobs: # Note: The issue above means neither of these mips targets will use # MSA (mips simd) but MIPS uses a nonstandard binary representation # for NaNs which makes it worth testing on despite that. - - mips-unknown-linux-gnu - - mips64-unknown-linux-gnuabi64 + # - mips-unknown-linux-gnu + # - mips64-unknown-linux-gnuabi64 - riscv64gc-unknown-linux-gnu # TODO this test works, but it appears to time out # - powerpc-unknown-linux-gnu diff --git a/library/portable-simd/Cargo.toml b/library/portable-simd/Cargo.toml index 9802386e4566d..d1732aaec2f92 100644 --- a/library/portable-simd/Cargo.toml +++ b/library/portable-simd/Cargo.toml @@ -1,5 +1,5 @@ [workspace] - +resolver = "1" members = [ "crates/core_simd", "crates/std_float", diff --git a/library/portable-simd/crates/core_simd/Cargo.toml b/library/portable-simd/crates/core_simd/Cargo.toml index d1a3a515a7e81..56d8ead86ab72 100644 --- a/library/portable-simd/crates/core_simd/Cargo.toml +++ b/library/portable-simd/crates/core_simd/Cargo.toml @@ -15,7 +15,7 @@ std = [] generic_const_exprs = [] all_lane_counts = [] -[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +[target.'cfg(target_family = "wasm")'.dev-dependencies] wasm-bindgen = "0.2" wasm-bindgen-test = "0.3" diff --git a/library/portable-simd/crates/core_simd/examples/dot_product.rs b/library/portable-simd/crates/core_simd/examples/dot_product.rs index 391f08f55a07a..a7973ec740411 100644 --- a/library/portable-simd/crates/core_simd/examples/dot_product.rs +++ b/library/portable-simd/crates/core_simd/examples/dot_product.rs @@ -130,7 +130,7 @@ pub fn dot_prod_simd_4(a: &[f32], b: &[f32]) -> f32 { } // This version allocates a single `XMM` register for accumulation, and the folds don't allocate on top of that. -// Notice the the use of `mul_add`, which can do a multiply and an add operation ber iteration. +// Notice the use of `mul_add`, which can do a multiply and an add operation ber iteration. pub fn dot_prod_simd_5(a: &[f32], b: &[f32]) -> f32 { a.array_chunks::<4>() .map(|&a| f32x4::from_array(a)) diff --git a/library/portable-simd/crates/core_simd/src/cast.rs b/library/portable-simd/crates/core_simd/src/cast.rs index 65a3f845ffca7..1c3592f807578 100644 --- a/library/portable-simd/crates/core_simd/src/cast.rs +++ b/library/portable-simd/crates/core_simd/src/cast.rs @@ -1,55 +1,51 @@ use crate::simd::SimdElement; +mod sealed { + /// Cast vector elements to other types. + /// + /// # Safety + /// Implementing this trait asserts that the type is a valid vector element for the `simd_cast` + /// or `simd_as` intrinsics. + pub unsafe trait Sealed {} +} +use sealed::Sealed; + /// Supporting trait for `Simd::cast`. Typically doesn't need to be used directly. -/// -/// # Safety -/// Implementing this trait asserts that the type is a valid vector element for the `simd_cast` or -/// `simd_as` intrinsics. -pub unsafe trait SimdCast: SimdElement {} +pub trait SimdCast: Sealed + SimdElement {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i8 {} +unsafe impl Sealed for i8 {} +impl SimdCast for i8 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i16 {} +unsafe impl Sealed for i16 {} +impl SimdCast for i16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i32 {} +unsafe impl Sealed for i32 {} +impl SimdCast for i32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i64 {} +unsafe impl Sealed for i64 {} +impl SimdCast for i64 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for isize {} +unsafe impl Sealed for isize {} +impl SimdCast for isize {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u8 {} +unsafe impl Sealed for u8 {} +impl SimdCast for u8 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u16 {} +unsafe impl Sealed for u16 {} +impl SimdCast for u16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u32 {} +unsafe impl Sealed for u32 {} +impl SimdCast for u32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u64 {} +unsafe impl Sealed for u64 {} +impl SimdCast for u64 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for usize {} +unsafe impl Sealed for usize {} +impl SimdCast for usize {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for f32 {} +unsafe impl Sealed for f32 {} +impl SimdCast for f32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for f64 {} - -/// Supporting trait for `Simd::cast_ptr`. Typically doesn't need to be used directly. -/// -/// # Safety -/// Implementing this trait asserts that the type is a valid vector element for the `simd_cast_ptr` -/// intrinsic. -pub unsafe trait SimdCastPtr {} - -// Safety: pointers can be cast to other pointer types -unsafe impl SimdCastPtr for *const U -where - U: core::ptr::Pointee, - T: core::ptr::Pointee, -{ -} -// Safety: pointers can be cast to other pointer types -unsafe impl SimdCastPtr for *mut U -where - U: core::ptr::Pointee, - T: core::ptr::Pointee, -{ -} +unsafe impl Sealed for f64 {} +impl SimdCast for f64 {} diff --git a/library/portable-simd/crates/core_simd/src/elements/const_ptr.rs b/library/portable-simd/crates/core_simd/src/elements/const_ptr.rs index 0ef9802b5e219..f215f9a61d02e 100644 --- a/library/portable-simd/crates/core_simd/src/elements/const_ptr.rs +++ b/library/portable-simd/crates/core_simd/src/elements/const_ptr.rs @@ -1,5 +1,5 @@ use super::sealed::Sealed; -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SimdUint, SupportedLaneCount}; /// Operations on SIMD vectors of constant pointers. pub trait SimdConstPtr: Copy + Sealed { @@ -9,6 +9,9 @@ pub trait SimdConstPtr: Copy + Sealed { /// Vector of `isize` with the same number of lanes. type Isize; + /// Vector of const pointers with the same number of lanes. + type CastPtr; + /// Vector of mutable pointers to the same type. type MutPtr; @@ -18,6 +21,11 @@ pub trait SimdConstPtr: Copy + Sealed { /// Returns `true` for each lane that is null. fn is_null(self) -> Self::Mask; + /// Casts to a pointer of another type. + /// + /// Equivalent to calling [`pointer::cast`] on each lane. + fn cast(self) -> Self::CastPtr; + /// Changes constness without changing the type. /// /// Equivalent to calling [`pointer::cast_mut`] on each lane. @@ -78,6 +86,7 @@ where { type Usize = Simd; type Isize = Simd; + type CastPtr = Simd<*const U, LANES>; type MutPtr = Simd<*mut T, LANES>; type Mask = Mask; @@ -86,9 +95,22 @@ where Simd::splat(core::ptr::null()).simd_eq(self) } + #[inline] + fn cast(self) -> Self::CastPtr { + // SimdElement currently requires zero-sized metadata, so this should never fail. + // If this ever changes, `simd_cast_ptr` should produce a post-mono error. + use core::{mem::size_of, ptr::Pointee}; + assert_eq!(size_of::<::Metadata>(), 0); + assert_eq!(size_of::<::Metadata>(), 0); + + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } + } + #[inline] fn cast_mut(self) -> Self::MutPtr { - self.cast_ptr() + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } } #[inline] @@ -106,9 +128,9 @@ where // In the mean-time, this operation is defined to be "as if" it was // a wrapping_offset, so we can emulate it as such. This should properly // restore pointer provenance even under today's compiler. - self.cast_ptr::<*const u8>() + self.cast::() .wrapping_offset(addr.cast::() - self.addr().cast::()) - .cast_ptr() + .cast() } #[inline] diff --git a/library/portable-simd/crates/core_simd/src/elements/float.rs b/library/portable-simd/crates/core_simd/src/elements/float.rs index d602232705560..501c1c5ddd3f2 100644 --- a/library/portable-simd/crates/core_simd/src/elements/float.rs +++ b/library/portable-simd/crates/core_simd/src/elements/float.rs @@ -1,6 +1,6 @@ use super::sealed::Sealed; use crate::simd::{ - intrinsics, LaneCount, Mask, Simd, SimdElement, SimdPartialEq, SimdPartialOrd, + intrinsics, LaneCount, Mask, Simd, SimdCast, SimdElement, SimdPartialEq, SimdPartialOrd, SupportedLaneCount, }; @@ -15,6 +15,53 @@ pub trait SimdFloat: Copy + Sealed { /// Bit representation of this SIMD vector type. type Bits; + /// A SIMD vector with a different element type. + type Cast; + + /// Performs elementwise conversion of this vector's elements to another SIMD-valid type. + /// + /// This follows the semantics of Rust's `as` conversion for floats (truncating or saturating + /// at the limits) for each element. + /// + /// # Example + /// ``` + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "as_crate")] use core_simd::simd; + /// # #[cfg(not(feature = "as_crate"))] use core::simd; + /// # use simd::{SimdFloat, SimdInt, Simd}; + /// let floats: Simd = Simd::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); + /// let ints = floats.cast::(); + /// assert_eq!(ints, Simd::from_array([1, -4, i32::MAX, 0])); + /// + /// // Formally equivalent, but `Simd::cast` can optimize better. + /// assert_eq!(ints, Simd::from_array(floats.to_array().map(|x| x as i32))); + /// + /// // The float conversion does not round-trip. + /// let floats_again = ints.cast(); + /// assert_ne!(floats, floats_again); + /// assert_eq!(floats_again, Simd::from_array([1.0, -4.0, 2147483647.0, 0.0])); + /// ``` + #[must_use] + fn cast(self) -> Self::Cast; + + /// Rounds toward zero and converts to the same-width integer type, assuming that + /// the value is finite and fits in that type. + /// + /// # Safety + /// The value must: + /// + /// * Not be NaN + /// * Not be infinite + /// * Be representable in the return type, after truncating off its fractional part + /// + /// If these requirements are infeasible or costly, consider using the safe function [cast], + /// which saturates on conversion. + /// + /// [cast]: Simd::cast + unsafe fn to_int_unchecked(self) -> Self::Cast + where + Self::Scalar: core::convert::FloatToInt; + /// Raw transmutation to an unsigned integer vector type with the /// same size and number of lanes. #[must_use = "method returns a new vector and does not mutate the original value"] @@ -206,6 +253,24 @@ macro_rules! impl_trait { type Mask = Mask<<$mask_ty as SimdElement>::Mask, LANES>; type Scalar = $ty; type Bits = Simd<$bits_ty, LANES>; + type Cast = Simd; + + #[inline] + fn cast(self) -> Self::Cast + { + // Safety: supported types are guaranteed by SimdCast + unsafe { intrinsics::simd_as(self) } + } + + #[inline] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + unsafe fn to_int_unchecked(self) -> Self::Cast + where + Self::Scalar: core::convert::FloatToInt, + { + // Safety: supported types are guaranteed by SimdCast, the caller is responsible for the extra invariants + unsafe { intrinsics::simd_cast(self) } + } #[inline] fn to_bits(self) -> Simd<$bits_ty, LANES> { diff --git a/library/portable-simd/crates/core_simd/src/elements/int.rs b/library/portable-simd/crates/core_simd/src/elements/int.rs index 9b8c37ed466ec..6db89ff9a659e 100644 --- a/library/portable-simd/crates/core_simd/src/elements/int.rs +++ b/library/portable-simd/crates/core_simd/src/elements/int.rs @@ -1,6 +1,6 @@ use super::sealed::Sealed; use crate::simd::{ - intrinsics, LaneCount, Mask, Simd, SimdElement, SimdPartialOrd, SupportedLaneCount, + intrinsics, LaneCount, Mask, Simd, SimdCast, SimdElement, SimdPartialOrd, SupportedLaneCount, }; /// Operations on SIMD vectors of signed integers. @@ -11,6 +11,16 @@ pub trait SimdInt: Copy + Sealed { /// Scalar type contained by this SIMD vector type. type Scalar; + /// A SIMD vector with a different element type. + type Cast; + + /// Performs elementwise conversion of this vector's elements to another SIMD-valid type. + /// + /// This follows the semantics of Rust's `as` conversion for casting integers (wrapping to + /// other integer types, and saturating to float types). + #[must_use] + fn cast(self) -> Self::Cast; + /// Lanewise saturating add. /// /// # Examples @@ -198,6 +208,13 @@ macro_rules! impl_trait { { type Mask = Mask<<$ty as SimdElement>::Mask, LANES>; type Scalar = $ty; + type Cast = Simd; + + #[inline] + fn cast(self) -> Self::Cast { + // Safety: supported types are guaranteed by SimdCast + unsafe { intrinsics::simd_as(self) } + } #[inline] fn saturating_add(self, second: Self) -> Self { diff --git a/library/portable-simd/crates/core_simd/src/elements/mut_ptr.rs b/library/portable-simd/crates/core_simd/src/elements/mut_ptr.rs index d87986b4a091c..4bdc6a14ce4a6 100644 --- a/library/portable-simd/crates/core_simd/src/elements/mut_ptr.rs +++ b/library/portable-simd/crates/core_simd/src/elements/mut_ptr.rs @@ -1,5 +1,5 @@ use super::sealed::Sealed; -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SimdUint, SupportedLaneCount}; /// Operations on SIMD vectors of mutable pointers. pub trait SimdMutPtr: Copy + Sealed { @@ -9,6 +9,9 @@ pub trait SimdMutPtr: Copy + Sealed { /// Vector of `isize` with the same number of lanes. type Isize; + /// Vector of const pointers with the same number of lanes. + type CastPtr; + /// Vector of constant pointers to the same type. type ConstPtr; @@ -18,6 +21,11 @@ pub trait SimdMutPtr: Copy + Sealed { /// Returns `true` for each lane that is null. fn is_null(self) -> Self::Mask; + /// Casts to a pointer of another type. + /// + /// Equivalent to calling [`pointer::cast`] on each lane. + fn cast(self) -> Self::CastPtr; + /// Changes constness without changing the type. /// /// Equivalent to calling [`pointer::cast_const`] on each lane. @@ -73,6 +81,7 @@ where { type Usize = Simd; type Isize = Simd; + type CastPtr = Simd<*mut U, LANES>; type ConstPtr = Simd<*const T, LANES>; type Mask = Mask; @@ -81,9 +90,22 @@ where Simd::splat(core::ptr::null_mut()).simd_eq(self) } + #[inline] + fn cast(self) -> Self::CastPtr { + // SimdElement currently requires zero-sized metadata, so this should never fail. + // If this ever changes, `simd_cast_ptr` should produce a post-mono error. + use core::{mem::size_of, ptr::Pointee}; + assert_eq!(size_of::<::Metadata>(), 0); + assert_eq!(size_of::<::Metadata>(), 0); + + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } + } + #[inline] fn cast_const(self) -> Self::ConstPtr { - self.cast_ptr() + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } } #[inline] @@ -101,9 +123,9 @@ where // In the mean-time, this operation is defined to be "as if" it was // a wrapping_offset, so we can emulate it as such. This should properly // restore pointer provenance even under today's compiler. - self.cast_ptr::<*mut u8>() + self.cast::() .wrapping_offset(addr.cast::() - self.addr().cast::()) - .cast_ptr() + .cast() } #[inline] diff --git a/library/portable-simd/crates/core_simd/src/elements/uint.rs b/library/portable-simd/crates/core_simd/src/elements/uint.rs index 21e7e76eb3de5..3926c395ec9ac 100644 --- a/library/portable-simd/crates/core_simd/src/elements/uint.rs +++ b/library/portable-simd/crates/core_simd/src/elements/uint.rs @@ -1,11 +1,21 @@ use super::sealed::Sealed; -use crate::simd::{intrinsics, LaneCount, Simd, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Simd, SimdCast, SimdElement, SupportedLaneCount}; /// Operations on SIMD vectors of unsigned integers. pub trait SimdUint: Copy + Sealed { /// Scalar type contained by this SIMD vector type. type Scalar; + /// A SIMD vector with a different element type. + type Cast; + + /// Performs elementwise conversion of this vector's elements to another SIMD-valid type. + /// + /// This follows the semantics of Rust's `as` conversion for casting integers (wrapping to + /// other integer types, and saturating to float types). + #[must_use] + fn cast(self) -> Self::Cast; + /// Lanewise saturating add. /// /// # Examples @@ -77,6 +87,13 @@ macro_rules! impl_trait { LaneCount: SupportedLaneCount, { type Scalar = $ty; + type Cast = Simd; + + #[inline] + fn cast(self) -> Self::Cast { + // Safety: supported types are guaranteed by SimdCast + unsafe { intrinsics::simd_as(self) } + } #[inline] fn saturating_add(self, second: Self) -> Self { diff --git a/library/portable-simd/crates/core_simd/src/iter.rs b/library/portable-simd/crates/core_simd/src/iter.rs index 3275b4db8e49f..328c995b81ddd 100644 --- a/library/portable-simd/crates/core_simd/src/iter.rs +++ b/library/portable-simd/crates/core_simd/src/iter.rs @@ -10,6 +10,7 @@ macro_rules! impl_traits { where LaneCount: SupportedLaneCount, { + #[inline] fn sum>(iter: I) -> Self { iter.fold(Simd::splat(0 as $type), Add::add) } @@ -19,6 +20,7 @@ macro_rules! impl_traits { where LaneCount: SupportedLaneCount, { + #[inline] fn product>(iter: I) -> Self { iter.fold(Simd::splat(1 as $type), Mul::mul) } @@ -28,6 +30,7 @@ macro_rules! impl_traits { where LaneCount: SupportedLaneCount, { + #[inline] fn sum>(iter: I) -> Self { iter.fold(Simd::splat(0 as $type), Add::add) } @@ -37,6 +40,7 @@ macro_rules! impl_traits { where LaneCount: SupportedLaneCount, { + #[inline] fn product>(iter: I) -> Self { iter.fold(Simd::splat(1 as $type), Mul::mul) } diff --git a/library/portable-simd/crates/core_simd/src/lib.rs b/library/portable-simd/crates/core_simd/src/lib.rs index e5307de215520..fde406bda7060 100644 --- a/library/portable-simd/crates/core_simd/src/lib.rs +++ b/library/portable-simd/crates/core_simd/src/lib.rs @@ -16,7 +16,7 @@ )] #![cfg_attr(feature = "generic_const_exprs", feature(generic_const_exprs))] #![cfg_attr(feature = "generic_const_exprs", allow(incomplete_features))] -#![warn(missing_docs)] +#![warn(missing_docs, clippy::missing_inline_in_public_items)] // basically all items, really #![deny(unsafe_op_in_unsafe_fn, clippy::undocumented_unsafe_blocks)] #![unstable(feature = "portable_simd", issue = "86656")] //! Portable SIMD module. diff --git a/library/portable-simd/crates/core_simd/src/masks.rs b/library/portable-simd/crates/core_simd/src/masks.rs index e0f3c7beef689..fea687bdc1aef 100644 --- a/library/portable-simd/crates/core_simd/src/masks.rs +++ b/library/portable-simd/crates/core_simd/src/masks.rs @@ -179,6 +179,7 @@ where /// Panics if any lane is not 0 or -1. #[inline] #[must_use = "method returns a new mask and does not mutate the original value"] + #[track_caller] pub fn from_int(value: Simd) -> Self { assert!(T::valid(value), "all values must be either 0 or -1",); // Safety: the validity has been checked @@ -217,6 +218,7 @@ where /// Panics if `lane` is greater than or equal to the number of lanes in the vector. #[inline] #[must_use = "method returns a new bool and does not mutate the original value"] + #[track_caller] pub fn test(&self, lane: usize) -> bool { assert!(lane < LANES, "lane index out of range"); // Safety: the lane index has been checked @@ -240,6 +242,7 @@ where /// # Panics /// Panics if `lane` is greater than or equal to the number of lanes in the vector. #[inline] + #[track_caller] pub fn set(&mut self, lane: usize, value: bool) { assert!(lane < LANES, "lane index out of range"); // Safety: the lane index has been checked @@ -327,6 +330,7 @@ where T: MaskElement + fmt::Debug, LaneCount: SupportedLaneCount, { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list() .entries((0..LANES).map(|lane| self.test(lane))) diff --git a/library/portable-simd/crates/core_simd/src/mod.rs b/library/portable-simd/crates/core_simd/src/mod.rs index 35c659b7a429a..f9891a3b7c1d7 100644 --- a/library/portable-simd/crates/core_simd/src/mod.rs +++ b/library/portable-simd/crates/core_simd/src/mod.rs @@ -23,6 +23,8 @@ mod vendor; #[doc = include_str!("core_simd_docs.md")] pub mod simd { + pub mod prelude; + pub(crate) use crate::core_simd::intrinsics; pub use crate::core_simd::alias::*; diff --git a/library/portable-simd/crates/core_simd/src/ops.rs b/library/portable-simd/crates/core_simd/src/ops.rs index fc1e0bc426df3..b007456cf2cc5 100644 --- a/library/portable-simd/crates/core_simd/src/ops.rs +++ b/library/portable-simd/crates/core_simd/src/ops.rs @@ -15,6 +15,7 @@ where I: core::slice::SliceIndex<[T]>, { type Output = I::Output; + #[inline] fn index(&self, index: I) -> &Self::Output { &self.as_array()[index] } @@ -26,6 +27,7 @@ where LaneCount: SupportedLaneCount, I: core::slice::SliceIndex<[T]>, { + #[inline] fn index_mut(&mut self, index: I) -> &mut Self::Output { &mut self.as_mut_array()[index] } @@ -118,10 +120,14 @@ macro_rules! for_base_types { #[inline] #[must_use = "operator returns a new vector without mutating the inputs"] + // TODO: only useful for int Div::div, but we hope that this + // will essentially always always get inlined anyway. + #[track_caller] fn $call(self, rhs: Self) -> Self::Output { $macro_impl!(self, rhs, $inner, $scalar) } - })* + } + )* } } diff --git a/library/portable-simd/crates/core_simd/src/ord.rs b/library/portable-simd/crates/core_simd/src/ord.rs index 1ae9cd061fb2d..b2455190e8231 100644 --- a/library/portable-simd/crates/core_simd/src/ord.rs +++ b/library/portable-simd/crates/core_simd/src/ord.rs @@ -94,6 +94,7 @@ macro_rules! impl_integer { } #[inline] + #[track_caller] fn simd_clamp(self, min: Self, max: Self) -> Self { assert!( min.simd_le(max).all(), @@ -200,6 +201,7 @@ macro_rules! impl_mask { } #[inline] + #[track_caller] fn simd_clamp(self, min: Self, max: Self) -> Self { assert!( min.simd_le(max).all(), @@ -254,6 +256,7 @@ where } #[inline] + #[track_caller] fn simd_clamp(self, min: Self, max: Self) -> Self { assert!( min.simd_le(max).all(), @@ -303,6 +306,7 @@ where } #[inline] + #[track_caller] fn simd_clamp(self, min: Self, max: Self) -> Self { assert!( min.simd_le(max).all(), diff --git a/library/portable-simd/crates/core_simd/src/simd/prelude.rs b/library/portable-simd/crates/core_simd/src/simd/prelude.rs new file mode 100644 index 0000000000000..e8fdc932d490f --- /dev/null +++ b/library/portable-simd/crates/core_simd/src/simd/prelude.rs @@ -0,0 +1,80 @@ +//! The portable SIMD prelude. +//! +//! Includes important traits and types to be imported with a glob: +//! ```ignore +//! use std::simd::prelude::*; +//! ``` + +#[doc(no_inline)] +pub use super::{ + simd_swizzle, Mask, Simd, SimdConstPtr, SimdFloat, SimdInt, SimdMutPtr, SimdOrd, SimdPartialEq, + SimdPartialOrd, SimdUint, +}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{f32x1, f32x2, f32x4, f32x8, f32x16, f32x32, f32x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{f64x1, f64x2, f64x4, f64x8, f64x16, f64x32, f64x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{i8x1, i8x2, i8x4, i8x8, i8x16, i8x32, i8x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{i16x1, i16x2, i16x4, i16x8, i16x16, i16x32, i16x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{i32x1, i32x2, i32x4, i32x8, i32x16, i32x32, i32x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{i64x1, i64x2, i64x4, i64x8, i64x16, i64x32, i64x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{isizex1, isizex2, isizex4, isizex8, isizex16, isizex32, isizex64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{u8x1, u8x2, u8x4, u8x8, u8x16, u8x32, u8x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{u16x1, u16x2, u16x4, u16x8, u16x16, u16x32, u16x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{u32x1, u32x2, u32x4, u32x8, u32x16, u32x32, u32x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{u64x1, u64x2, u64x4, u64x8, u64x16, u64x32, u64x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{usizex1, usizex2, usizex4, usizex8, usizex16, usizex32, usizex64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{mask8x1, mask8x2, mask8x4, mask8x8, mask8x16, mask8x32, mask8x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{mask16x1, mask16x2, mask16x4, mask16x8, mask16x16, mask16x32, mask16x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{mask32x1, mask32x2, mask32x4, mask32x8, mask32x16, mask32x32, mask32x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{mask64x1, mask64x2, mask64x4, mask64x8, mask64x16, mask64x32, mask64x64}; + +#[rustfmt::skip] +#[doc(no_inline)] +pub use super::{masksizex1, masksizex2, masksizex4, masksizex8, masksizex16, masksizex32, masksizex64}; diff --git a/library/portable-simd/crates/core_simd/src/swizzle_dyn.rs b/library/portable-simd/crates/core_simd/src/swizzle_dyn.rs index 6065d6459378e..ce621792534e1 100644 --- a/library/portable-simd/crates/core_simd/src/swizzle_dyn.rs +++ b/library/portable-simd/crates/core_simd/src/swizzle_dyn.rs @@ -16,9 +16,14 @@ where #[inline] pub fn swizzle_dyn(self, idxs: Simd) -> Self { #![allow(unused_imports, unused_unsafe)] - #[cfg(target_arch = "aarch64")] + #[cfg(all(target_arch = "aarch64", target_endian = "little"))] use core::arch::aarch64::{uint8x8_t, vqtbl1q_u8, vtbl1_u8}; - #[cfg(all(target_arch = "arm", target_feature = "v7", target_feature = "neon"))] + #[cfg(all( + target_arch = "arm", + target_feature = "v7", + target_feature = "neon", + target_endian = "little" + ))] use core::arch::arm::{uint8x8_t, vtbl1_u8}; #[cfg(target_arch = "wasm32")] use core::arch::wasm32 as wasm; @@ -29,13 +34,24 @@ where // SAFETY: Intrinsics covered by cfg unsafe { match N { - #[cfg(target_feature = "neon")] + #[cfg(all( + any( + target_arch = "aarch64", + all(target_arch = "arm", target_feature = "v7") + ), + target_feature = "neon", + target_endian = "little" + ))] 8 => transize(vtbl1_u8, self, idxs), #[cfg(target_feature = "ssse3")] 16 => transize(x86::_mm_shuffle_epi8, self, idxs), #[cfg(target_feature = "simd128")] 16 => transize(wasm::i8x16_swizzle, self, idxs), - #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] + #[cfg(all( + target_arch = "aarch64", + target_feature = "neon", + target_endian = "little" + ))] 16 => transize(vqtbl1q_u8, self, idxs), #[cfg(all(target_feature = "avx2", not(target_feature = "avx512vbmi")))] 32 => transize_raw(avx2_pshufb, self, idxs), diff --git a/library/portable-simd/crates/core_simd/src/vector.rs b/library/portable-simd/crates/core_simd/src/vector.rs index 3809cc961515b..9aa7bacfce981 100644 --- a/library/portable-simd/crates/core_simd/src/vector.rs +++ b/library/portable-simd/crates/core_simd/src/vector.rs @@ -1,6 +1,6 @@ use crate::simd::{ - intrinsics, LaneCount, Mask, MaskElement, SimdCast, SimdCastPtr, SimdConstPtr, SimdMutPtr, - SimdPartialOrd, SupportedLaneCount, Swizzle, + intrinsics, LaneCount, Mask, MaskElement, SimdConstPtr, SimdMutPtr, SimdPartialOrd, + SupportedLaneCount, Swizzle, }; use core::convert::{TryFrom, TryInto}; @@ -122,6 +122,7 @@ where /// let v = u32x4::splat(0); /// assert_eq!(v.lanes(), 4); /// ``` + #[inline] pub const fn lanes(&self) -> usize { Self::LANES } @@ -136,6 +137,7 @@ where /// let v = u32x4::splat(8); /// assert_eq!(v.as_array(), &[8, 8, 8, 8]); /// ``` + #[inline] pub fn splat(value: T) -> Self { // This is preferred over `[value; N]`, since it's explicitly a splat: // https://github.com/rust-lang/rust/issues/97804 @@ -156,6 +158,7 @@ where /// let v: u64x4 = Simd::from_array([0, 1, 2, 3]); /// assert_eq!(v.as_array(), &[0, 1, 2, 3]); /// ``` + #[inline] pub const fn as_array(&self) -> &[T; N] { // SAFETY: `Simd` is just an overaligned `[T; N]` with // potential padding at the end, so pointer casting to a @@ -167,6 +170,7 @@ where } /// Returns a mutable array reference containing the entire SIMD vector. + #[inline] pub fn as_mut_array(&mut self) -> &mut [T; N] { // SAFETY: `Simd` is just an overaligned `[T; N]` with // potential padding at the end, so pointer casting to a @@ -184,6 +188,7 @@ where /// /// # Safety /// Reading `ptr` must be safe, as if by `<*const [T; N]>::read_unaligned`. + #[inline] const unsafe fn load(ptr: *const [T; N]) -> Self { // There are potentially simpler ways to write this function, but this should result in // LLVM `load ` @@ -204,6 +209,7 @@ where /// /// # Safety /// Writing to `ptr` must be safe, as if by `<*mut [T; N]>::write_unaligned`. + #[inline] const unsafe fn store(self, ptr: *mut [T; N]) { // There are potentially simpler ways to write this function, but this should result in // LLVM `store ` @@ -216,6 +222,7 @@ where } /// Converts an array to a SIMD vector. + #[inline] pub const fn from_array(array: [T; N]) -> Self { // SAFETY: `&array` is safe to read. // @@ -228,6 +235,7 @@ where } /// Converts a SIMD vector to an array. + #[inline] pub const fn to_array(self) -> [T; N] { let mut tmp = core::mem::MaybeUninit::uninit(); // SAFETY: writing to `tmp` is safe and initializes it. @@ -259,6 +267,8 @@ where /// assert_eq!(v.as_array(), &[1, 2, 3, 4]); /// ``` #[must_use] + #[inline] + #[track_caller] pub const fn from_slice(slice: &[T]) -> Self { assert!( slice.len() >= Self::LANES, @@ -287,6 +297,8 @@ where /// v.copy_to_slice(&mut dest); /// assert_eq!(&dest, &[1, 2, 3, 4, 0, 0]); /// ``` + #[inline] + #[track_caller] pub fn copy_to_slice(self, slice: &mut [T]) { assert!( slice.len() >= Self::LANES, @@ -297,76 +309,6 @@ where unsafe { self.store(slice.as_mut_ptr().cast()) } } - /// Performs elementwise conversion of a SIMD vector's elements to another SIMD-valid type. - /// - /// This follows the semantics of Rust's `as` conversion for casting integers between - /// signed and unsigned (interpreting integers as 2s complement, so `-1` to `U::MAX` and - /// `1 << (U::BITS -1)` becoming `I::MIN` ), and from floats to integers (truncating, - /// or saturating at the limits) for each element. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # use core::simd::Simd; - /// let floats: Simd = Simd::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); - /// let ints = floats.cast::(); - /// assert_eq!(ints, Simd::from_array([1, -4, i32::MAX, 0])); - /// - /// // Formally equivalent, but `Simd::cast` can optimize better. - /// assert_eq!(ints, Simd::from_array(floats.to_array().map(|x| x as i32))); - /// - /// // The float conversion does not round-trip. - /// let floats_again = ints.cast(); - /// assert_ne!(floats, floats_again); - /// assert_eq!(floats_again, Simd::from_array([1.0, -4.0, 2147483647.0, 0.0])); - /// ``` - #[must_use] - #[inline] - pub fn cast(self) -> Simd - where - T: SimdCast, - { - // Safety: supported types are guaranteed by SimdCast - unsafe { intrinsics::simd_as(self) } - } - - /// Casts a vector of pointers to another pointer type. - #[must_use] - #[inline] - pub fn cast_ptr(self) -> Simd - where - T: SimdCastPtr, - U: SimdElement, - { - // Safety: supported types are guaranteed by SimdCastPtr - unsafe { intrinsics::simd_cast_ptr(self) } - } - - /// Rounds toward zero and converts to the same-width integer type, assuming that - /// the value is finite and fits in that type. - /// - /// # Safety - /// The value must: - /// - /// * Not be NaN - /// * Not be infinite - /// * Be representable in the return type, after truncating off its fractional part - /// - /// If these requirements are infeasible or costly, consider using the safe function [cast], - /// which saturates on conversion. - /// - /// [cast]: Simd::cast - #[inline] - #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces - pub unsafe fn to_int_unchecked(self) -> Simd - where - T: core::convert::FloatToInt + SimdCast, - I: SimdCast, - { - // Safety: supported types are guaranteed by SimdCast, the caller is responsible for the extra invariants - unsafe { intrinsics::simd_cast(self) } - } - /// Reads from potentially discontiguous indices in `slice` to construct a SIMD vector. /// If an index is out-of-bounds, the element is instead selected from the `or` vector. /// @@ -717,6 +659,7 @@ where LaneCount: SupportedLaneCount, T: SimdElement, { + #[inline] fn clone(&self) -> Self { *self } @@ -861,6 +804,7 @@ where LaneCount: SupportedLaneCount, T: SimdElement, { + #[inline] fn from(array: [T; N]) -> Self { Self::from_array(array) } @@ -871,6 +815,7 @@ where LaneCount: SupportedLaneCount, T: SimdElement, { + #[inline] fn from(vector: Simd) -> Self { vector.to_array() } @@ -883,6 +828,7 @@ where { type Error = core::array::TryFromSliceError; + #[inline] fn try_from(slice: &[T]) -> Result { Ok(Self::from_array(slice.try_into()?)) } @@ -895,6 +841,7 @@ where { type Error = core::array::TryFromSliceError; + #[inline] fn try_from(slice: &mut [T]) -> Result { Ok(Self::from_array(slice.try_into()?)) } diff --git a/library/portable-simd/crates/core_simd/src/vendor.rs b/library/portable-simd/crates/core_simd/src/vendor.rs index 9fb70218c9543..8e2370f7de54c 100644 --- a/library/portable-simd/crates/core_simd/src/vendor.rs +++ b/library/portable-simd/crates/core_simd/src/vendor.rs @@ -21,8 +21,8 @@ macro_rules! from_transmute { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86; -#[cfg(any(target_arch = "wasm32"))] -mod wasm32; +#[cfg(any(target_family = "wasm"))] +mod wasm; #[cfg(any(target_arch = "aarch64", target_arch = "arm",))] mod arm; diff --git a/library/portable-simd/crates/core_simd/src/vendor/wasm.rs b/library/portable-simd/crates/core_simd/src/vendor/wasm.rs new file mode 100644 index 0000000000000..80368ea97d78a --- /dev/null +++ b/library/portable-simd/crates/core_simd/src/vendor/wasm.rs @@ -0,0 +1,30 @@ +use crate::simd::*; +use core::arch::wasm::v128; + +from_transmute! { unsafe u8x16 => v128 } +from_transmute! { unsafe i8x16 => v128 } + +from_transmute! { unsafe u16x8 => v128 } +from_transmute! { unsafe i16x8 => v128 } + +from_transmute! { unsafe u32x4 => v128 } +from_transmute! { unsafe i32x4 => v128 } +from_transmute! { unsafe f32x4 => v128 } + +from_transmute! { unsafe u64x2 => v128 } +from_transmute! { unsafe i64x2 => v128 } +from_transmute! { unsafe f64x2 => v128 } + +#[cfg(target_pointer_width = "32")] +mod p32 { + use super::*; + from_transmute! { unsafe usizex4 => v128 } + from_transmute! { unsafe isizex4 => v128 } +} + +#[cfg(target_pointer_width = "64")] +mod p64 { + use super::*; + from_transmute! { unsafe usizex2 => v128 } + from_transmute! { unsafe isizex2 => v128 } +} diff --git a/library/portable-simd/crates/core_simd/src/vendor/wasm32.rs b/library/portable-simd/crates/core_simd/src/vendor/wasm32.rs deleted file mode 100644 index ef3baf885b0fb..0000000000000 --- a/library/portable-simd/crates/core_simd/src/vendor/wasm32.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::simd::*; -use core::arch::wasm32::v128; - -from_transmute! { unsafe u8x16 => v128 } -from_transmute! { unsafe i8x16 => v128 } - -from_transmute! { unsafe u16x8 => v128 } -from_transmute! { unsafe i16x8 => v128 } - -from_transmute! { unsafe u32x4 => v128 } -from_transmute! { unsafe i32x4 => v128 } -from_transmute! { unsafe f32x4 => v128 } - -from_transmute! { unsafe u64x2 => v128 } -from_transmute! { unsafe i64x2 => v128 } -from_transmute! { unsafe f64x2 => v128 } - -#[cfg(target_pointer_width = "32")] -mod p32 { - use super::*; - from_transmute! { unsafe usizex4 => v128 } - from_transmute! { unsafe isizex4 => v128 } -} - -#[cfg(target_pointer_width = "64")] -mod p64 { - use super::*; - from_transmute! { unsafe usizex2 => v128 } - from_transmute! { unsafe isizex2 => v128 } -} diff --git a/library/portable-simd/crates/core_simd/tests/cast.rs b/library/portable-simd/crates/core_simd/tests/cast.rs index ab5650f071323..00545936ea2a6 100644 --- a/library/portable-simd/crates/core_simd/tests/cast.rs +++ b/library/portable-simd/crates/core_simd/tests/cast.rs @@ -2,7 +2,8 @@ macro_rules! cast_types { ($start:ident, $($target:ident),*) => { mod $start { - use core_simd::simd::Simd; + #[allow(unused)] + use core_simd::simd::{Simd, SimdInt, SimdUint, SimdFloat}; type Vector = Simd<$start, N>; $( mod $target { diff --git a/library/portable-simd/crates/core_simd/tests/round.rs b/library/portable-simd/crates/core_simd/tests/round.rs index 8b9638ad46671..aacf7bd3bcc2c 100644 --- a/library/portable-simd/crates/core_simd/tests/round.rs +++ b/library/portable-simd/crates/core_simd/tests/round.rs @@ -53,6 +53,7 @@ macro_rules! float_rounding_test { test_helpers::test_lanes! { fn to_int_unchecked() { + use core_simd::simd::SimdFloat; // The maximum integer that can be represented by the equivalently sized float has // all of the mantissa digits set to 1, pushed up to the MSB. const ALL_MANTISSA_BITS: IntScalar = ((1 << ::MANTISSA_DIGITS) - 1); diff --git a/library/proc_macro/src/bridge/mod.rs b/library/proc_macro/src/bridge/mod.rs index caecda1bc63fd..86ce5d9c6d5fe 100644 --- a/library/proc_macro/src/bridge/mod.rs +++ b/library/proc_macro/src/bridge/mod.rs @@ -8,7 +8,7 @@ #![deny(unsafe_code)] -use crate::{Delimiter, Level, LineColumn, Spacing}; +use crate::{Delimiter, Level, Spacing}; use std::fmt; use std::hash::Hash; use std::marker; @@ -95,10 +95,10 @@ macro_rules! with_api { fn parent($self: $S::Span) -> Option<$S::Span>; fn source($self: $S::Span) -> $S::Span; fn byte_range($self: $S::Span) -> Range; - fn start($self: $S::Span) -> LineColumn; - fn end($self: $S::Span) -> LineColumn; - fn before($self: $S::Span) -> $S::Span; - fn after($self: $S::Span) -> $S::Span; + fn start($self: $S::Span) -> $S::Span; + fn end($self: $S::Span) -> $S::Span; + fn line($self: $S::Span) -> usize; + fn column($self: $S::Span) -> usize; fn join($self: $S::Span, other: $S::Span) -> Option<$S::Span>; fn subspan($self: $S::Span, start: Bound, end: Bound) -> Option<$S::Span>; fn resolved_at($self: $S::Span, at: $S::Span) -> $S::Span; @@ -299,7 +299,6 @@ mark_noop! { Delimiter, LitKind, Level, - LineColumn, Spacing, } @@ -319,7 +318,6 @@ rpc_encode_decode!( Help, } ); -rpc_encode_decode!(struct LineColumn { line, column }); rpc_encode_decode!( enum Spacing { Alone, diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index c64665b6ae0e8..83d637b685ac6 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -24,7 +24,6 @@ #![feature(staged_api)] #![feature(allow_internal_unstable)] #![feature(decl_macro)] -#![feature(local_key_cell_methods)] #![feature(maybe_uninit_write_slice)] #![feature(negative_impls)] #![feature(new_uninit)] @@ -33,6 +32,7 @@ #![feature(min_specialization)] #![feature(strict_provenance)] #![recursion_limit = "256"] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[unstable(feature = "proc_macro_internals", issue = "27812")] #[doc(hidden)] @@ -43,7 +43,6 @@ mod diagnostic; #[unstable(feature = "proc_macro_diagnostic", issue = "54140")] pub use diagnostic::{Diagnostic, Level, MultiSpan}; -use std::cmp::Ordering; use std::ops::{Range, RangeBounds}; use std::path::PathBuf; use std::str::FromStr; @@ -178,6 +177,7 @@ impl FromStr for TokenStream { // N.B., the bridge only provides `to_string`, implement `fmt::Display` // based on it (the reverse of the usual relationship between the two). +#[doc(hidden)] #[stable(feature = "proc_macro_lib", since = "1.15.0")] impl ToString for TokenStream { fn to_string(&self) -> String { @@ -494,28 +494,32 @@ impl Span { self.0.byte_range() } - /// Gets the starting line/column in the source file for this span. + /// Creates an empty span pointing to directly before this span. #[unstable(feature = "proc_macro_span", issue = "54725")] - pub fn start(&self) -> LineColumn { - self.0.start().add_1_to_column() + pub fn start(&self) -> Span { + Span(self.0.start()) } - /// Gets the ending line/column in the source file for this span. + /// Creates an empty span pointing to directly after this span. #[unstable(feature = "proc_macro_span", issue = "54725")] - pub fn end(&self) -> LineColumn { - self.0.end().add_1_to_column() + pub fn end(&self) -> Span { + Span(self.0.end()) } - /// Creates an empty span pointing to directly before this span. - #[unstable(feature = "proc_macro_span_shrink", issue = "87552")] - pub fn before(&self) -> Span { - Span(self.0.before()) + /// The one-indexed line of the source file where the span starts. + /// + /// To obtain the line of the span's end, use `span.end().line()`. + #[unstable(feature = "proc_macro_span", issue = "54725")] + pub fn line(&self) -> usize { + self.0.line() } - /// Creates an empty span pointing to directly after this span. - #[unstable(feature = "proc_macro_span_shrink", issue = "87552")] - pub fn after(&self) -> Span { - Span(self.0.after()) + /// The one-indexed column of the source file where the span starts. + /// + /// To obtain the column of the span's end, use `span.end().column()`. + #[unstable(feature = "proc_macro_span", issue = "54725")] + pub fn column(&self) -> usize { + self.0.column() } /// Creates a new span encompassing `self` and `other`. @@ -586,44 +590,6 @@ impl fmt::Debug for Span { } } -/// A line-column pair representing the start or end of a `Span`. -#[unstable(feature = "proc_macro_span", issue = "54725")] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct LineColumn { - /// The 1-indexed line in the source file on which the span starts or ends (inclusive). - #[unstable(feature = "proc_macro_span", issue = "54725")] - pub line: usize, - /// The 1-indexed column (number of bytes in UTF-8 encoding) in the source - /// file on which the span starts or ends (inclusive). - #[unstable(feature = "proc_macro_span", issue = "54725")] - pub column: usize, -} - -impl LineColumn { - fn add_1_to_column(self) -> Self { - LineColumn { line: self.line, column: self.column + 1 } - } -} - -#[unstable(feature = "proc_macro_span", issue = "54725")] -impl !Send for LineColumn {} -#[unstable(feature = "proc_macro_span", issue = "54725")] -impl !Sync for LineColumn {} - -#[unstable(feature = "proc_macro_span", issue = "54725")] -impl Ord for LineColumn { - fn cmp(&self, other: &Self) -> Ordering { - self.line.cmp(&other.line).then(self.column.cmp(&other.column)) - } -} - -#[unstable(feature = "proc_macro_span", issue = "54725")] -impl PartialOrd for LineColumn { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - /// The source file of a given `Span`. #[unstable(feature = "proc_macro_span", issue = "54725")] #[derive(Clone)] @@ -773,6 +739,7 @@ impl From for TokenTree { // N.B., the bridge only provides `to_string`, implement `fmt::Display` // based on it (the reverse of the usual relationship between the two). +#[doc(hidden)] #[stable(feature = "proc_macro_lib", since = "1.15.0")] impl ToString for TokenTree { fn to_string(&self) -> String { @@ -907,6 +874,7 @@ impl Group { // N.B., the bridge only provides `to_string`, implement `fmt::Display` // based on it (the reverse of the usual relationship between the two). +#[doc(hidden)] #[stable(feature = "proc_macro_lib", since = "1.15.0")] impl ToString for Group { fn to_string(&self) -> String { @@ -948,21 +916,34 @@ impl !Send for Punct {} #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl !Sync for Punct {} -/// Describes whether a `Punct` is followed immediately by another `Punct` ([`Spacing::Joint`]) or -/// by a different token or whitespace ([`Spacing::Alone`]). +/// Indicates whether a `Punct` token can join with the following token +/// to form a multi-character operator. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[stable(feature = "proc_macro_lib2", since = "1.29.0")] pub enum Spacing { - /// A `Punct` is not immediately followed by another `Punct`. - /// E.g. `+` is `Alone` in `+ =`, `+ident` and `+()`. - #[stable(feature = "proc_macro_lib2", since = "1.29.0")] - Alone, - /// A `Punct` is immediately followed by another `Punct`. - /// E.g. `+` is `Joint` in `+=` and `++`. + /// A `Punct` token can join with the following token to form a multi-character operator. + /// + /// In token streams constructed using proc macro interfaces `Joint` punctuation tokens can be + /// followed by any other tokens. \ + /// However, in token streams parsed from source code compiler will only set spacing to `Joint` + /// in the following cases: + /// - A `Punct` is immediately followed by another `Punct` without a whitespace. \ + /// E.g. `+` is `Joint` in `+=` and `++`. + /// - A single quote `'` is immediately followed by an identifier without a whitespace. \ + /// E.g. `'` is `Joint` in `'lifetime`. /// - /// Additionally, single quote `'` can join with identifiers to form lifetimes: `'ident`. + /// This list may be extended in the future to enable more token combinations. #[stable(feature = "proc_macro_lib2", since = "1.29.0")] Joint, + /// A `Punct` token cannot join with the following token to form a multi-character operator. + /// + /// `Alone` punctuation tokens can be followed by any other tokens. \ + /// In token streams parsed from source code compiler will set spacing to `Alone` in all cases + /// not covered by the conditions for `Joint` above. \ + /// E.g. `+` is `Alone` in `+ =`, `+ident` and `+()`. + /// In particular, token not followed by anything will also be marked as `Alone`. + #[stable(feature = "proc_macro_lib2", since = "1.29.0")] + Alone, } impl Punct { @@ -994,10 +975,9 @@ impl Punct { self.0.ch as char } - /// Returns the spacing of this punctuation character, indicating whether it's immediately - /// followed by another `Punct` in the token stream, so they can potentially be combined into - /// a multi-character operator (`Joint`), or it's followed by some other token or whitespace - /// (`Alone`) so the operator has certainly ended. + /// Returns the spacing of this punctuation character, indicating whether it can be potentially + /// combined into a multi-character operator with the following token (`Joint`), or the operator + /// has certainly ended (`Alone`). #[stable(feature = "proc_macro_lib2", since = "1.29.0")] pub fn spacing(&self) -> Spacing { if self.0.joint { Spacing::Joint } else { Spacing::Alone } @@ -1016,6 +996,7 @@ impl Punct { } } +#[doc(hidden)] #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl ToString for Punct { fn to_string(&self) -> String { @@ -1118,8 +1099,7 @@ impl Ident { } } -/// Converts the identifier to a string that should be losslessly convertible -/// back into the same identifier. +#[doc(hidden)] #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl ToString for Ident { fn to_string(&self) -> String { @@ -1458,6 +1438,7 @@ impl FromStr for Literal { } } +#[doc(hidden)] #[stable(feature = "proc_macro_lib2", since = "1.29.0")] impl ToString for Literal { fn to_string(&self) -> String { diff --git a/library/profiler_builtins/build.rs b/library/profiler_builtins/build.rs index 1b1f11798d74d..d14d0b82229a1 100644 --- a/library/profiler_builtins/build.rs +++ b/library/profiler_builtins/build.rs @@ -6,6 +6,12 @@ use std::env; use std::path::Path; fn main() { + println!("cargo:rerun-if-env-changed=LLVM_PROFILER_RT_LIB"); + if let Ok(rt) = env::var("LLVM_PROFILER_RT_LIB") { + println!("cargo:rustc-link-lib=static:+verbatim={rt}"); + return; + } + let target = env::var("TARGET").expect("TARGET was not set"); let cfg = &mut cc::Build::new(); diff --git a/library/profiler_builtins/src/lib.rs b/library/profiler_builtins/src/lib.rs index 0c83bcee06ff4..a81d0a63547f8 100644 --- a/library/profiler_builtins/src/lib.rs +++ b/library/profiler_builtins/src/lib.rs @@ -7,4 +7,5 @@ issue = "none" )] #![allow(unused_features)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #![feature(staged_api)] diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 824abc72a223d..f0f86462491e4 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["public-dependency"] + [package] name = "std" version = "0.0.0" @@ -10,44 +12,48 @@ edition = "2021" crate-type = ["dylib", "rlib"] [dependencies] -alloc = { path = "../alloc" } +alloc = { path = "../alloc", public = true } cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] } panic_unwind = { path = "../panic_unwind", optional = true } panic_abort = { path = "../panic_abort" } -core = { path = "../core" } -libc = { version = "0.2.143", default-features = false, features = ['rustc-dep-of-std'] } -compiler_builtins = { version = "0.1.92" } +core = { path = "../core", public = true } +libc = { git = "https://github.com/wasix-org/libc.git", branch = "main", version = "0.2.147", default-features = false, features = ['rustc-dep-of-std'], public = true } +compiler_builtins = { version = "0.1.100" } profiler_builtins = { path = "../profiler_builtins", optional = true } unwind = { path = "../unwind" } -hashbrown = { version = "0.13", default-features = false, features = ['rustc-dep-of-std'] } +hashbrown = { version = "0.14", default-features = false, features = ['rustc-dep-of-std'] } std_detect = { path = "../stdarch/crates/std_detect", default-features = false, features = ['rustc-dep-of-std'] } # Dependencies of the `backtrace` crate -addr2line = { version = "0.19.0", optional = true, default-features = false } rustc-demangle = { version = "0.1.21", features = ['rustc-dep-of-std'] } -miniz_oxide = { version = "0.6.0", optional = true, default-features = false } -[dependencies.object] -version = "0.30.0" -optional = true -default-features = false -features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive'] + +[target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] +miniz_oxide = { version = "0.7.0", optional = true, default-features = false } +addr2line = { version = "0.21.0", optional = true, default-features = false } +object = { version = "0.32.0", default-features = false, optional = true, features = ['read_core', 'elf', 'macho', 'pe', 'unaligned', 'archive'] } [dev-dependencies] rand = { version = "0.8.5", default-features = false, features = ["alloc"] } rand_xorshift = "0.3.0" -[target.'cfg(any(all(target_family = "wasm", not(target_os = "emscripten")), all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] +[target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] dlmalloc = { version = "0.2.3", features = ['rustc-dep-of-std'] } [target.x86_64-fortanix-unknown-sgx.dependencies] -fortanix-sgx-abi = { version = "0.5.0", features = ['rustc-dep-of-std'] } +fortanix-sgx-abi = { version = "0.5.0", features = ['rustc-dep-of-std'], public = true } [target.'cfg(target_os = "hermit")'.dependencies] -hermit-abi = { version = "0.3.0", features = ['rustc-dep-of-std'] } +hermit-abi = { version = "0.3.2", features = ['rustc-dep-of-std'], public = true } -[target.wasm32-wasi.dependencies] +[target.wasm32-unknown-wasi.dependencies] wasi = { version = "0.11.0", features = ['rustc-dep-of-std'], default-features = false } +[target.wasm32-wasmer-wasi.dependencies] +wasi = { package = "wasix", version = "0.12.9", features = ['rustc-dep-of-std'], default-features = false } + +[target.wasm64-wasmer-wasi.dependencies] +wasi = { package = "wasix", version = "0.12.9", features = ['rustc-dep-of-std'], default-features = false } + [features] backtrace = [ "gimli-symbolize", @@ -63,6 +69,7 @@ compiler-builtins-c = ["alloc/compiler-builtins-c"] compiler-builtins-mem = ["alloc/compiler-builtins-mem"] compiler-builtins-no-asm = ["alloc/compiler-builtins-no-asm"] compiler-builtins-mangled-names = ["alloc/compiler-builtins-mangled-names"] +compiler-builtins-weak-intrinsics = ["alloc/compiler-builtins-weak-intrinsics"] llvm-libunwind = ["unwind/llvm-libunwind"] system-llvm-libunwind = ["unwind/system-llvm-libunwind"] diff --git a/library/std/build.rs b/library/std/build.rs index 0fb03c8e88af5..ddf6e84d8d035 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -18,6 +18,7 @@ fn main() { || target.contains("illumos") || target.contains("apple-darwin") || target.contains("apple-ios") + || target.contains("apple-tvos") || target.contains("apple-watchos") || target.contains("uwp") || target.contains("windows") @@ -36,6 +37,8 @@ fn main() { || target.contains("nintendo-3ds") || target.contains("vita") || target.contains("nto") + // See src/bootstrap/synthetic_targets.rs + || env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok() { // These platforms don't have any special requirements. } else { @@ -46,7 +49,6 @@ fn main() { // - mipsel-sony-psp // - nvptx64-nvidia-cuda // - arch=avr - // - tvos (aarch64-apple-tvos, x86_64-apple-tvos) // - uefi (x86_64-unknown-uefi, i686-unknown-uefi) // - JSON targets // - Any new targets that have not been explicitly added above. diff --git a/library/std/src/alloc.rs b/library/std/src/alloc.rs index ec774e62debbf..1eae7fa6a9549 100644 --- a/library/std/src/alloc.rs +++ b/library/std/src/alloc.rs @@ -336,7 +336,6 @@ fn default_alloc_error_hook(layout: Layout) { static __rust_alloc_error_handler_should_panic: u8; } - #[allow(unused_unsafe)] if unsafe { __rust_alloc_error_handler_should_panic != 0 } { panic!("memory allocation of {} bytes failed", layout.size()); } else { diff --git a/library/std/src/backtrace.rs b/library/std/src/backtrace.rs index 7543ffadd4140..e7110aebdea16 100644 --- a/library/std/src/backtrace.rs +++ b/library/std/src/backtrace.rs @@ -89,12 +89,12 @@ mod tests; // a backtrace or actually symbolizing it. use crate::backtrace_rs::{self, BytesOrWideString}; -use crate::cell::UnsafeCell; use crate::env; use crate::ffi::c_void; use crate::fmt; +use crate::panic::UnwindSafe; use crate::sync::atomic::{AtomicUsize, Ordering::Relaxed}; -use crate::sync::Once; +use crate::sync::LazyLock; use crate::sys_common::backtrace::{lock, output_filename}; use crate::vec::Vec; @@ -133,12 +133,11 @@ pub enum BacktraceStatus { enum Inner { Unsupported, Disabled, - Captured(LazilyResolvedCapture), + Captured(LazyLock), } struct Capture { actual_start: usize, - resolved: bool, frames: Vec, } @@ -179,7 +178,7 @@ impl fmt::Debug for Backtrace { let capture = match &self.inner { Inner::Unsupported => return fmt.write_str(""), Inner::Disabled => return fmt.write_str(""), - Inner::Captured(c) => c.force(), + Inner::Captured(c) => &**c, }; let frames = &capture.frames[capture.actual_start..]; @@ -347,11 +346,10 @@ impl Backtrace { let inner = if frames.is_empty() { Inner::Unsupported } else { - Inner::Captured(LazilyResolvedCapture::new(Capture { + Inner::Captured(LazyLock::new(lazy_resolve(Capture { actual_start: actual_start.unwrap_or(0), frames, - resolved: false, - })) + }))) }; Backtrace { inner } @@ -376,7 +374,7 @@ impl<'a> Backtrace { #[must_use] #[unstable(feature = "backtrace_frames", issue = "79676")] pub fn frames(&'a self) -> &'a [BacktraceFrame] { - if let Inner::Captured(c) = &self.inner { &c.force().frames } else { &[] } + if let Inner::Captured(c) = &self.inner { &c.frames } else { &[] } } } @@ -386,7 +384,7 @@ impl fmt::Display for Backtrace { let capture = match &self.inner { Inner::Unsupported => return fmt.write_str("unsupported backtrace"), Inner::Disabled => return fmt.write_str("disabled backtrace"), - Inner::Captured(c) => c.force(), + Inner::Captured(c) => &**c, }; let full = fmt.alternate(); @@ -430,46 +428,15 @@ impl fmt::Display for Backtrace { } } -struct LazilyResolvedCapture { - sync: Once, - capture: UnsafeCell, -} - -impl LazilyResolvedCapture { - fn new(capture: Capture) -> Self { - LazilyResolvedCapture { sync: Once::new(), capture: UnsafeCell::new(capture) } - } - - fn force(&self) -> &Capture { - self.sync.call_once(|| { - // SAFETY: This exclusive reference can't overlap with any others - // `Once` guarantees callers will block until this closure returns - // `Once` also guarantees only a single caller will enter this closure - unsafe { &mut *self.capture.get() }.resolve(); - }); - - // SAFETY: This shared reference can't overlap with the exclusive reference above - unsafe { &*self.capture.get() } - } -} - -// SAFETY: Access to the inner value is synchronized using a thread-safe `Once` -// So long as `Capture` is `Sync`, `LazilyResolvedCapture` is too -unsafe impl Sync for LazilyResolvedCapture where Capture: Sync {} - -impl Capture { - fn resolve(&mut self) { - // If we're already resolved, nothing to do! - if self.resolved { - return; - } - self.resolved = true; +type LazyResolve = impl (FnOnce() -> Capture) + Send + Sync + UnwindSafe; +fn lazy_resolve(mut capture: Capture) -> LazyResolve { + move || { // Use the global backtrace lock to synchronize this as it's a // requirement of the `backtrace` crate, and then actually resolve // everything. let _lock = lock(); - for frame in self.frames.iter_mut() { + for frame in capture.frames.iter_mut() { let symbols = &mut frame.symbols; let frame = match &frame.frame { RawFrame::Actual(frame) => frame, @@ -490,6 +457,8 @@ impl Capture { }); } } + + capture } } diff --git a/library/std/src/backtrace/tests.rs b/library/std/src/backtrace/tests.rs index 4dfbf88e83ebc..73543a3af548f 100644 --- a/library/std/src/backtrace/tests.rs +++ b/library/std/src/backtrace/tests.rs @@ -1,4 +1,5 @@ use super::*; +use crate::panic::{RefUnwindSafe, UnwindSafe}; fn generate_fake_frames() -> Vec { vec![ @@ -43,9 +44,8 @@ fn generate_fake_frames() -> Vec { #[test] fn test_debug() { let backtrace = Backtrace { - inner: Inner::Captured(LazilyResolvedCapture::new(Capture { + inner: Inner::Captured(LazyLock::preinit(Capture { actual_start: 1, - resolved: true, frames: generate_fake_frames(), })), }; @@ -66,9 +66,8 @@ fn test_debug() { #[test] fn test_frames() { let backtrace = Backtrace { - inner: Inner::Captured(LazilyResolvedCapture::new(Capture { + inner: Inner::Captured(LazyLock::preinit(Capture { actual_start: 1, - resolved: true, frames: generate_fake_frames(), })), }; @@ -93,3 +92,9 @@ fn test_frames() { assert!(iter.all(|(f, e)| format!("{f:#?}") == *e)); } + +#[test] +fn backtrace_unwind_safe() { + fn assert_unwind_safe() {} + assert_unwind_safe::(); +} diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index c722bad2e4f63..be173a7ace6d8 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -49,12 +49,14 @@ use crate::sys; /// ``` /// /// In other words, if two keys are equal, their hashes must be equal. +/// Violating this property is a logic error. /// -/// It is a logic error for a key to be modified in such a way that the key's +/// It is also a logic error for a key to be modified in such a way that the key's /// hash, as determined by the [`Hash`] trait, or its equality, as determined by /// the [`Eq`] trait, changes while it is in the map. This is normally only /// possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. -/// The behavior resulting from such a logic error is not specified, but will +/// +/// The behavior resulting from either logic error is not specified, but will /// be encapsulated to the `HashMap` that observed the logic error and not /// result in undefined behavior. This could include panics, incorrect results, /// aborts, memory leaks, and non-termination. @@ -623,28 +625,27 @@ impl HashMap { /// If the closure returns false, or panics, the element remains in the map and will not be /// yielded. /// - /// Note that `drain_filter` lets you mutate every value in the filter closure, regardless of + /// Note that `extract_if` lets you mutate every value in the filter closure, regardless of /// whether you choose to keep or remove it. /// - /// If the iterator is only partially consumed or not consumed at all, each of the remaining - /// elements will still be subjected to the closure and removed and dropped if it returns true. + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// Use [`retain`] with a negated predicate if you do not need the returned iterator. /// - /// It is unspecified how many more elements will be subjected to the closure - /// if a panic occurs in the closure, or a panic occurs while dropping an element, - /// or if the `DrainFilter` value is leaked. + /// [`retain`]: HashMap::retain /// /// # Examples /// /// Splitting a map into even and odd keys, reusing the original map: /// /// ``` - /// #![feature(hash_drain_filter)] + /// #![feature(hash_extract_if)] /// use std::collections::HashMap; /// /// let mut map: HashMap = (0..8).map(|x| (x, x)).collect(); - /// let drained: HashMap = map.drain_filter(|k, _v| k % 2 == 0).collect(); + /// let extracted: HashMap = map.extract_if(|k, _v| k % 2 == 0).collect(); /// - /// let mut evens = drained.keys().copied().collect::>(); + /// let mut evens = extracted.keys().copied().collect::>(); /// let mut odds = map.keys().copied().collect::>(); /// evens.sort(); /// odds.sort(); @@ -654,12 +655,12 @@ impl HashMap { /// ``` #[inline] #[rustc_lint_query_instability] - #[unstable(feature = "hash_drain_filter", issue = "59618")] - pub fn drain_filter(&mut self, pred: F) -> DrainFilter<'_, K, V, F> + #[unstable(feature = "hash_extract_if", issue = "59618")] + pub fn extract_if(&mut self, pred: F) -> ExtractIf<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool, { - DrainFilter { base: self.base.drain_filter(pred) } + ExtractIf { base: self.base.extract_if(pred) } } /// Retains only the elements specified by the predicate. @@ -1578,28 +1579,29 @@ impl<'a, K, V> Drain<'a, K, V> { /// A draining, filtering iterator over the entries of a `HashMap`. /// -/// This `struct` is created by the [`drain_filter`] method on [`HashMap`]. +/// This `struct` is created by the [`extract_if`] method on [`HashMap`]. /// -/// [`drain_filter`]: HashMap::drain_filter +/// [`extract_if`]: HashMap::extract_if /// /// # Example /// /// ``` -/// #![feature(hash_drain_filter)] +/// #![feature(hash_extract_if)] /// /// use std::collections::HashMap; /// /// let mut map = HashMap::from([ /// ("a", 1), /// ]); -/// let iter = map.drain_filter(|_k, v| *v % 2 == 0); +/// let iter = map.extract_if(|_k, v| *v % 2 == 0); /// ``` -#[unstable(feature = "hash_drain_filter", issue = "59618")] -pub struct DrainFilter<'a, K, V, F> +#[unstable(feature = "hash_extract_if", issue = "59618")] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ExtractIf<'a, K, V, F> where F: FnMut(&K, &mut V) -> bool, { - base: base::DrainFilter<'a, K, V, F>, + base: base::ExtractIf<'a, K, V, F>, } /// A mutable iterator over the values of a `HashMap`. @@ -2479,8 +2481,8 @@ where } } -#[unstable(feature = "hash_drain_filter", issue = "59618")] -impl Iterator for DrainFilter<'_, K, V, F> +#[unstable(feature = "hash_extract_if", issue = "59618")] +impl Iterator for ExtractIf<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool, { @@ -2496,16 +2498,16 @@ where } } -#[unstable(feature = "hash_drain_filter", issue = "59618")] -impl FusedIterator for DrainFilter<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool {} +#[unstable(feature = "hash_extract_if", issue = "59618")] +impl FusedIterator for ExtractIf<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool {} -#[unstable(feature = "hash_drain_filter", issue = "59618")] -impl<'a, K, V, F> fmt::Debug for DrainFilter<'a, K, V, F> +#[unstable(feature = "hash_extract_if", issue = "59618")] +impl<'a, K, V, F> fmt::Debug for ExtractIf<'a, K, V, F> where F: FnMut(&K, &mut V) -> bool, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DrainFilter").finish_non_exhaustive() + f.debug_struct("ExtractIf").finish_non_exhaustive() } } @@ -2543,12 +2545,12 @@ impl<'a, K, V> Entry<'a, K, V> { /// ``` /// use std::collections::HashMap; /// - /// let mut map: HashMap<&str, String> = HashMap::new(); - /// let s = "hoho".to_string(); + /// let mut map = HashMap::new(); + /// let value = "hoho"; /// - /// map.entry("poneyland").or_insert_with(|| s); + /// map.entry("poneyland").or_insert_with(|| value); /// - /// assert_eq!(map["poneyland"], "hoho".to_string()); + /// assert_eq!(map["poneyland"], "hoho"); /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/collections/hash/map/tests.rs b/library/std/src/collections/hash/map/tests.rs index 6b89518e2e26c..91a3776e7be84 100644 --- a/library/std/src/collections/hash/map/tests.rs +++ b/library/std/src/collections/hash/map/tests.rs @@ -944,7 +944,7 @@ fn test_raw_entry() { } } -mod test_drain_filter { +mod test_extract_if { use super::*; use crate::panic::{catch_unwind, AssertUnwindSafe}; @@ -968,7 +968,7 @@ mod test_drain_filter { #[test] fn empty() { let mut map: HashMap = HashMap::new(); - map.drain_filter(|_, _| unreachable!("there's nothing to decide on")); + map.extract_if(|_, _| unreachable!("there's nothing to decide on")).for_each(drop); assert!(map.is_empty()); } @@ -976,7 +976,7 @@ mod test_drain_filter { fn consuming_nothing() { let pairs = (0..3).map(|i| (i, i)); let mut map: HashMap<_, _> = pairs.collect(); - assert!(map.drain_filter(|_, _| false).eq_sorted(crate::iter::empty())); + assert!(map.extract_if(|_, _| false).eq_sorted(crate::iter::empty())); assert_eq!(map.len(), 3); } @@ -984,7 +984,7 @@ mod test_drain_filter { fn consuming_all() { let pairs = (0..3).map(|i| (i, i)); let mut map: HashMap<_, _> = pairs.clone().collect(); - assert!(map.drain_filter(|_, _| true).eq_sorted(pairs)); + assert!(map.extract_if(|_, _| true).eq_sorted(pairs)); assert!(map.is_empty()); } @@ -993,7 +993,7 @@ mod test_drain_filter { let pairs = (0..3).map(|i| (i, i)); let mut map: HashMap<_, _> = pairs.collect(); assert!( - map.drain_filter(|_, v| { + map.extract_if(|_, v| { *v += 6; false }) @@ -1008,7 +1008,7 @@ mod test_drain_filter { let pairs = (0..3).map(|i| (i, i)); let mut map: HashMap<_, _> = pairs.collect(); assert!( - map.drain_filter(|_, v| { + map.extract_if(|_, v| { *v += 6; true }) @@ -1034,14 +1034,15 @@ mod test_drain_filter { let mut map = (0..3).map(|i| (i, D)).collect::>(); catch_unwind(move || { - drop(map.drain_filter(|_, _| { + map.extract_if(|_, _| { PREDS.fetch_add(1, Ordering::SeqCst); true - })) + }) + .for_each(drop) }) .unwrap_err(); - assert_eq!(PREDS.load(Ordering::SeqCst), 3); + assert_eq!(PREDS.load(Ordering::SeqCst), 2); assert_eq!(DROPS.load(Ordering::SeqCst), 3); } @@ -1060,10 +1061,11 @@ mod test_drain_filter { let mut map = (0..3).map(|i| (i, D)).collect::>(); catch_unwind(AssertUnwindSafe(|| { - drop(map.drain_filter(|_, _| match PREDS.fetch_add(1, Ordering::SeqCst) { + map.extract_if(|_, _| match PREDS.fetch_add(1, Ordering::SeqCst) { 0 => true, _ => panic!(), - })) + }) + .for_each(drop) })) .unwrap_err(); @@ -1088,7 +1090,7 @@ mod test_drain_filter { let mut map = (0..3).map(|i| (i, D)).collect::>(); { - let mut it = map.drain_filter(|_, _| match PREDS.fetch_add(1, Ordering::SeqCst) { + let mut it = map.extract_if(|_, _| match PREDS.fetch_add(1, Ordering::SeqCst) { 0 => true, _ => panic!(), }); diff --git a/library/std/src/collections/hash/set.rs b/library/std/src/collections/hash/set.rs index ac906e682d5f0..6d85b26af5fa2 100644 --- a/library/std/src/collections/hash/set.rs +++ b/library/std/src/collections/hash/set.rs @@ -24,13 +24,14 @@ use super::map::{map_try_reserve_error, RandomState}; /// ``` /// /// In other words, if two keys are equal, their hashes must be equal. +/// Violating this property is a logic error. /// -/// -/// It is a logic error for a key to be modified in such a way that the key's +/// It is also a logic error for a key to be modified in such a way that the key's /// hash, as determined by the [`Hash`] trait, or its equality, as determined by /// the [`Eq`] trait, changes while it is in the map. This is normally only /// possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. -/// The behavior resulting from such a logic error is not specified, but will +/// +/// The behavior resulting from either logic error is not specified, but will /// be encapsulated to the `HashSet` that observed the logic error and not /// result in undefined behavior. This could include panics, incorrect results, /// aborts, memory leaks, and non-termination. @@ -65,8 +66,8 @@ use super::map::{map_try_reserve_error, RandomState}; /// ``` /// /// The easiest way to use `HashSet` with a custom type is to derive -/// [`Eq`] and [`Hash`]. We must also derive [`PartialEq`], this will in the -/// future be implied by [`Eq`]. +/// [`Eq`] and [`Hash`]. We must also derive [`PartialEq`], +/// which is required if [`Eq`] is derived. /// /// ``` /// use std::collections::HashSet; @@ -262,25 +263,24 @@ impl HashSet { /// If the closure returns false, the value will remain in the list and will not be yielded /// by the iterator. /// - /// If the iterator is only partially consumed or not consumed at all, each of the remaining - /// values will still be subjected to the closure and removed and dropped if it returns true. + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// Use [`retain`] with a negated predicate if you do not need the returned iterator. /// - /// It is unspecified how many more values will be subjected to the closure - /// if a panic occurs in the closure, or if a panic occurs while dropping a value, or if the - /// `DrainFilter` itself is leaked. + /// [`retain`]: HashSet::retain /// /// # Examples /// /// Splitting a set into even and odd values, reusing the original set: /// /// ``` - /// #![feature(hash_drain_filter)] + /// #![feature(hash_extract_if)] /// use std::collections::HashSet; /// /// let mut set: HashSet = (0..8).collect(); - /// let drained: HashSet = set.drain_filter(|v| v % 2 == 0).collect(); + /// let extracted: HashSet = set.extract_if(|v| v % 2 == 0).collect(); /// - /// let mut evens = drained.into_iter().collect::>(); + /// let mut evens = extracted.into_iter().collect::>(); /// let mut odds = set.into_iter().collect::>(); /// evens.sort(); /// odds.sort(); @@ -290,12 +290,12 @@ impl HashSet { /// ``` #[inline] #[rustc_lint_query_instability] - #[unstable(feature = "hash_drain_filter", issue = "59618")] - pub fn drain_filter(&mut self, pred: F) -> DrainFilter<'_, T, F> + #[unstable(feature = "hash_extract_if", issue = "59618")] + pub fn extract_if(&mut self, pred: F) -> ExtractIf<'_, T, F> where F: FnMut(&T) -> bool, { - DrainFilter { base: self.base.drain_filter(pred) } + ExtractIf { base: self.base.extract_if(pred) } } /// Retains only the elements specified by the predicate. @@ -868,7 +868,9 @@ where /// Returns whether the value was newly inserted. That is: /// /// - If the set did not previously contain this value, `true` is returned. - /// - If the set already contained this value, `false` is returned. + /// - If the set already contained this value, `false` is returned, + /// and the set is not modified: original value is not replaced, + /// and the value passed as argument is dropped. /// /// # Examples /// @@ -1310,27 +1312,27 @@ pub struct Drain<'a, K: 'a> { /// A draining, filtering iterator over the items of a `HashSet`. /// -/// This `struct` is created by the [`drain_filter`] method on [`HashSet`]. +/// This `struct` is created by the [`extract_if`] method on [`HashSet`]. /// -/// [`drain_filter`]: HashSet::drain_filter +/// [`extract_if`]: HashSet::extract_if /// /// # Examples /// /// ``` -/// #![feature(hash_drain_filter)] +/// #![feature(hash_extract_if)] /// /// use std::collections::HashSet; /// /// let mut a = HashSet::from([1, 2, 3]); /// -/// let mut drain_filtered = a.drain_filter(|v| v % 2 == 0); +/// let mut extract_ifed = a.extract_if(|v| v % 2 == 0); /// ``` -#[unstable(feature = "hash_drain_filter", issue = "59618")] -pub struct DrainFilter<'a, K, F> +#[unstable(feature = "hash_extract_if", issue = "59618")] +pub struct ExtractIf<'a, K, F> where F: FnMut(&K) -> bool, { - base: base::DrainFilter<'a, K, F>, + base: base::ExtractIf<'a, K, F>, } /// A lazy iterator producing elements in the intersection of `HashSet`s. @@ -1576,8 +1578,8 @@ impl fmt::Debug for Drain<'_, K> { } } -#[unstable(feature = "hash_drain_filter", issue = "59618")] -impl Iterator for DrainFilter<'_, K, F> +#[unstable(feature = "hash_extract_if", issue = "59618")] +impl Iterator for ExtractIf<'_, K, F> where F: FnMut(&K) -> bool, { @@ -1593,16 +1595,16 @@ where } } -#[unstable(feature = "hash_drain_filter", issue = "59618")] -impl FusedIterator for DrainFilter<'_, K, F> where F: FnMut(&K) -> bool {} +#[unstable(feature = "hash_extract_if", issue = "59618")] +impl FusedIterator for ExtractIf<'_, K, F> where F: FnMut(&K) -> bool {} -#[unstable(feature = "hash_drain_filter", issue = "59618")] -impl<'a, K, F> fmt::Debug for DrainFilter<'a, K, F> +#[unstable(feature = "hash_extract_if", issue = "59618")] +impl<'a, K, F> fmt::Debug for ExtractIf<'a, K, F> where F: FnMut(&K) -> bool, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DrainFilter").finish_non_exhaustive() + f.debug_struct("ExtractIf").finish_non_exhaustive() } } diff --git a/library/std/src/collections/hash/set/tests.rs b/library/std/src/collections/hash/set/tests.rs index 941a0450cc770..e0cd80b44f8e6 100644 --- a/library/std/src/collections/hash/set/tests.rs +++ b/library/std/src/collections/hash/set/tests.rs @@ -3,6 +3,7 @@ use super::HashSet; use crate::panic::{catch_unwind, AssertUnwindSafe}; use crate::sync::atomic::{AtomicU32, Ordering}; +use crate::sync::Arc; #[test] fn test_zero_capacities() { @@ -418,18 +419,18 @@ fn test_retain() { } #[test] -fn test_drain_filter() { +fn test_extract_if() { let mut x: HashSet<_> = [1].iter().copied().collect(); let mut y: HashSet<_> = [1].iter().copied().collect(); - x.drain_filter(|_| true); - y.drain_filter(|_| false); + x.extract_if(|_| true).for_each(drop); + y.extract_if(|_| false).for_each(drop); assert_eq!(x.len(), 0); assert_eq!(y.len(), 1); } #[test] -fn test_drain_filter_drop_panic_leak() { +fn test_extract_if_drop_panic_leak() { static PREDS: AtomicU32 = AtomicU32::new(0); static DROPS: AtomicU32 = AtomicU32::new(0); @@ -446,19 +447,20 @@ fn test_drain_filter_drop_panic_leak() { let mut set = (0..3).map(|i| D(i)).collect::>(); catch_unwind(move || { - drop(set.drain_filter(|_| { + set.extract_if(|_| { PREDS.fetch_add(1, Ordering::SeqCst); true - })) + }) + .for_each(drop) }) .ok(); - assert_eq!(PREDS.load(Ordering::SeqCst), 3); + assert_eq!(PREDS.load(Ordering::SeqCst), 2); assert_eq!(DROPS.load(Ordering::SeqCst), 3); } #[test] -fn test_drain_filter_pred_panic_leak() { +fn test_extract_if_pred_panic_leak() { static PREDS: AtomicU32 = AtomicU32::new(0); static DROPS: AtomicU32 = AtomicU32::new(0); @@ -473,10 +475,11 @@ fn test_drain_filter_pred_panic_leak() { let mut set: HashSet<_> = (0..3).map(|_| D).collect(); catch_unwind(AssertUnwindSafe(|| { - drop(set.drain_filter(|_| match PREDS.fetch_add(1, Ordering::SeqCst) { + set.extract_if(|_| match PREDS.fetch_add(1, Ordering::SeqCst) { 0 => true, _ => panic!(), - })) + }) + .for_each(drop) })) .ok(); @@ -502,3 +505,22 @@ fn const_with_hasher() { const X: HashSet<(), ()> = HashSet::with_hasher(()); assert_eq!(X.len(), 0); } + +#[test] +fn test_insert_does_not_overwrite_the_value() { + let first_value = Arc::new(17); + let second_value = Arc::new(17); + + let mut set = HashSet::new(); + let inserted = set.insert(first_value.clone()); + assert!(inserted); + + let inserted = set.insert(second_value); + assert!(!inserted); + + assert!( + Arc::ptr_eq(set.iter().next().unwrap(), &first_value), + "Insert must not overwrite the value, so the contained value pointer \ + must be the same as first value pointer we inserted" + ); +} diff --git a/library/std/src/env.rs b/library/std/src/env.rs index d372fa64065f5..f67f6034d3412 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -178,7 +178,8 @@ impl Iterator for Vars { #[stable(feature = "std_debug", since = "1.16.0")] impl fmt::Debug for Vars { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Vars").finish_non_exhaustive() + let Self { inner: VarsOs { inner } } = self; + f.debug_struct("Vars").field("inner", &inner.str_debug()).finish() } } @@ -196,7 +197,8 @@ impl Iterator for VarsOs { #[stable(feature = "std_debug", since = "1.16.0")] impl fmt::Debug for VarsOs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("VarOs").finish_non_exhaustive() + let Self { inner } = self; + f.debug_struct("VarsOs").field("inner", inner).finish() } } @@ -829,7 +831,8 @@ impl DoubleEndedIterator for Args { #[stable(feature = "std_debug", since = "1.16.0")] impl fmt::Debug for Args { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Args").field("inner", &self.inner.inner).finish() + let Self { inner: ArgsOs { inner } } = self; + f.debug_struct("Args").field("inner", inner).finish() } } @@ -870,7 +873,8 @@ impl DoubleEndedIterator for ArgsOs { #[stable(feature = "std_debug", since = "1.16.0")] impl fmt::Debug for ArgsOs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ArgsOs").field("inner", &self.inner).finish() + let Self { inner } = self; + f.debug_struct("ArgsOs").field("inner", inner).finish() } } @@ -890,6 +894,7 @@ pub mod consts { /// - aarch64 /// - loongarch64 /// - m68k + /// - csky /// - mips /// - mips64 /// - powerpc diff --git a/library/std/src/env/tests.rs b/library/std/src/env/tests.rs index 94cace03af64e..558692295815d 100644 --- a/library/std/src/env/tests.rs +++ b/library/std/src/env/tests.rs @@ -95,8 +95,28 @@ fn args_debug() { format!("Args {{ inner: {:?} }}", args().collect::>()), format!("{:?}", args()) ); +} + +#[test] +fn args_os_debug() { assert_eq!( format!("ArgsOs {{ inner: {:?} }}", args_os().collect::>()), format!("{:?}", args_os()) ); } + +#[test] +fn vars_debug() { + assert_eq!( + format!("Vars {{ inner: {:?} }}", vars().collect::>()), + format!("{:?}", vars()) + ); +} + +#[test] +fn vars_os_debug() { + assert_eq!( + format!("VarsOs {{ inner: {:?} }}", vars_os().collect::>()), + format!("{:?}", vars_os()) + ); +} diff --git a/library/std/src/error.rs b/library/std/src/error.rs index 05f8fd8de327f..375ff2d245044 100644 --- a/library/std/src/error.rs +++ b/library/std/src/error.rs @@ -9,6 +9,8 @@ use crate::fmt::{self, Write}; #[stable(feature = "rust1", since = "1.0.0")] pub use core::error::Error; +#[unstable(feature = "error_generic_member_access", issue = "99301")] +pub use core::error::{request_ref, request_value, Request}; mod private { // This is a hack to prevent `type_id` from being overridden by `Error` @@ -121,7 +123,8 @@ mod private { /// This example produces the following output: /// /// ```console -/// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: SuperError is here!: SuperErrorSideKick is here!', src/error.rs:34:40 +/// thread 'main' panicked at src/error.rs:34:40: +/// called `Result::unwrap()` on an `Err` value: SuperError is here!: SuperErrorSideKick is here! /// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace /// ``` /// @@ -370,11 +373,10 @@ impl Report { /// /// ```rust /// #![feature(error_reporter)] - /// #![feature(provide_any)] /// #![feature(error_generic_member_access)] /// # use std::error::Error; /// # use std::fmt; - /// use std::any::Demand; + /// use std::error::Request; /// use std::error::Report; /// use std::backtrace::Backtrace; /// @@ -404,8 +406,8 @@ impl Report { /// } /// /// impl Error for SuperErrorSideKick { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand.provide_ref::(&self.backtrace); + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_ref::(&self.backtrace); /// } /// } /// @@ -458,11 +460,11 @@ where fn backtrace(&self) -> Option<&Backtrace> { // have to grab the backtrace on the first error directly since that error may not be // 'static - let backtrace = (&self.error as &dyn Error).request_ref(); + let backtrace = request_ref(&self.error); let backtrace = backtrace.or_else(|| { self.error .source() - .map(|source| source.sources().find_map(|source| source.request_ref())) + .map(|source| source.sources().find_map(|source| request_ref(source))) .flatten() }); backtrace diff --git a/library/std/src/error/tests.rs b/library/std/src/error/tests.rs index ee999bd65c3c9..ed070a26b0cf0 100644 --- a/library/std/src/error/tests.rs +++ b/library/std/src/error/tests.rs @@ -1,6 +1,6 @@ use super::Error; use crate::fmt; -use core::any::Demand; +use core::error::Request; #[derive(Debug, PartialEq)] struct A; @@ -199,7 +199,7 @@ where self.source.as_deref() } - fn provide<'a>(&'a self, req: &mut Demand<'a>) { + fn provide<'a>(&'a self, req: &mut Request<'a>) { self.backtrace.as_ref().map(|bt| req.provide_ref::(bt)); } } diff --git a/library/std/src/f32.rs b/library/std/src/f32.rs index 408244b2ce9eb..a659b552f4730 100644 --- a/library/std/src/f32.rs +++ b/library/std/src/f32.rs @@ -61,6 +61,7 @@ impl f32 { /// assert_eq!(f.ceil(), 4.0); /// assert_eq!(g.ceil(), 4.0); /// ``` + #[doc(alias = "ceiling")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -135,6 +136,7 @@ impl f32 { /// assert_eq!(g.trunc(), 3.0); /// assert_eq!(h.trunc(), -3.0); /// ``` + #[doc(alias = "truncate")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -321,6 +323,7 @@ impl f32 { /// // limitation due to round-off error /// assert!((-f32::EPSILON).rem_euclid(3.0) != 0.0); /// ``` + #[doc(alias = "modulo", alias = "mod")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[inline] @@ -500,10 +503,7 @@ impl f32 { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn log2(self) -> f32 { - #[cfg(target_os = "android")] - return crate::sys::android::log2f32(self); - #[cfg(not(target_os = "android"))] - return unsafe { intrinsics::log2f32(self) }; + crate::sys::log2f32(self) } /// Returns the base 10 logarithm of the number. @@ -528,7 +528,7 @@ impl f32 { /// The positive difference of two numbers. /// - /// * If `self <= other`: `0:0` + /// * If `self <= other`: `0.0` /// * Else: `self - other` /// /// # Examples @@ -675,6 +675,7 @@ impl f32 { /// /// assert!(abs_difference <= f32::EPSILON); /// ``` + #[doc(alias = "arcsin")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -697,6 +698,7 @@ impl f32 { /// /// assert!(abs_difference <= f32::EPSILON); /// ``` + #[doc(alias = "arccos")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -718,6 +720,7 @@ impl f32 { /// /// assert!(abs_difference <= f32::EPSILON); /// ``` + #[doc(alias = "arctan")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -775,6 +778,7 @@ impl f32 { /// assert!(abs_difference_0 <= f32::EPSILON); /// assert!(abs_difference_1 <= f32::EPSILON); /// ``` + #[doc(alias = "sincos")] #[rustc_allow_incoherent_impl] #[stable(feature = "rust1", since = "1.0.0")] #[inline] @@ -907,6 +911,7 @@ impl f32 { /// /// assert!(abs_difference <= f32::EPSILON); /// ``` + #[doc(alias = "arcsinh")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -929,6 +934,7 @@ impl f32 { /// /// assert!(abs_difference <= f32::EPSILON); /// ``` + #[doc(alias = "arccosh")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -953,6 +959,7 @@ impl f32 { /// /// assert!(abs_difference <= 1e-5); /// ``` + #[doc(alias = "arctanh")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -960,4 +967,46 @@ impl f32 { pub fn atanh(self) -> f32 { 0.5 * ((2.0 * self) / (1.0 - self)).ln_1p() } + + /// Gamma function. + /// + /// # Examples + /// + /// ``` + /// #![feature(float_gamma)] + /// let x = 5.0f32; + /// + /// let abs_difference = (x.gamma() - 24.0).abs(); + /// + /// assert!(abs_difference <= f32::EPSILON); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[unstable(feature = "float_gamma", issue = "99842")] + #[inline] + pub fn gamma(self) -> f32 { + unsafe { cmath::tgammaf(self) } + } + + /// Returns the natural logarithm of the gamma function. + /// + /// # Examples + /// + /// ``` + /// #![feature(float_gamma)] + /// let x = 2.0f32; + /// + /// let abs_difference = (x.ln_gamma().0 - 0.0).abs(); + /// + /// assert!(abs_difference <= f32::EPSILON); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[unstable(feature = "float_gamma", issue = "99842")] + #[inline] + pub fn ln_gamma(self) -> (f32, i32) { + let mut signgamp: i32 = 0; + let x = unsafe { cmath::lgammaf_r(self, &mut signgamp) }; + (x, signgamp) + } } diff --git a/library/std/src/f32/tests.rs b/library/std/src/f32/tests.rs index e949def00bb1f..9ca4e8f2f45fe 100644 --- a/library/std/src/f32/tests.rs +++ b/library/std/src/f32/tests.rs @@ -652,6 +652,38 @@ fn test_atanh() { assert_approx_eq!((-0.5f32).atanh(), -0.54930614433405484569762261846126285f32); } +#[test] +fn test_gamma() { + // precision can differ between platforms + assert_approx_eq!(1.0f32.gamma(), 1.0f32); + assert_approx_eq!(2.0f32.gamma(), 1.0f32); + assert_approx_eq!(3.0f32.gamma(), 2.0f32); + assert_approx_eq!(4.0f32.gamma(), 6.0f32); + assert_approx_eq!(5.0f32.gamma(), 24.0f32); + assert_approx_eq!(0.5f32.gamma(), consts::PI.sqrt()); + assert_approx_eq!((-0.5f32).gamma(), -2.0 * consts::PI.sqrt()); + assert_eq!(0.0f32.gamma(), f32::INFINITY); + assert_eq!((-0.0f32).gamma(), f32::NEG_INFINITY); + assert!((-1.0f32).gamma().is_nan()); + assert!((-2.0f32).gamma().is_nan()); + assert!(f32::NAN.gamma().is_nan()); + assert!(f32::NEG_INFINITY.gamma().is_nan()); + assert_eq!(f32::INFINITY.gamma(), f32::INFINITY); + assert_eq!(171.71f32.gamma(), f32::INFINITY); +} + +#[test] +fn test_ln_gamma() { + assert_approx_eq!(1.0f32.ln_gamma().0, 0.0f32); + assert_eq!(1.0f32.ln_gamma().1, 1); + assert_approx_eq!(2.0f32.ln_gamma().0, 0.0f32); + assert_eq!(2.0f32.ln_gamma().1, 1); + assert_approx_eq!(3.0f32.ln_gamma().0, 2.0f32.ln()); + assert_eq!(3.0f32.ln_gamma().1, 1); + assert_approx_eq!((-0.5f32).ln_gamma().0, (2.0 * consts::PI.sqrt()).ln()); + assert_eq!((-0.5f32).ln_gamma().1, -1); +} + #[test] fn test_real_consts() { use super::consts; diff --git a/library/std/src/f64.rs b/library/std/src/f64.rs index 6782b861f110b..721e1fb754e10 100644 --- a/library/std/src/f64.rs +++ b/library/std/src/f64.rs @@ -61,6 +61,7 @@ impl f64 { /// assert_eq!(f.ceil(), 4.0); /// assert_eq!(g.ceil(), 4.0); /// ``` + #[doc(alias = "ceiling")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -135,6 +136,7 @@ impl f64 { /// assert_eq!(g.trunc(), 3.0); /// assert_eq!(h.trunc(), -3.0); /// ``` + #[doc(alias = "truncate")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -321,6 +323,7 @@ impl f64 { /// // limitation due to round-off error /// assert!((-f64::EPSILON).rem_euclid(3.0) != 0.0); /// ``` + #[doc(alias = "modulo", alias = "mod")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[inline] @@ -456,7 +459,7 @@ impl f64 { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn ln(self) -> f64 { - self.log_wrapper(|n| unsafe { intrinsics::logf64(n) }) + crate::sys::log_wrapper(self, |n| unsafe { intrinsics::logf64(n) }) } /// Returns the logarithm of the number with respect to an arbitrary base. @@ -500,12 +503,7 @@ impl f64 { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn log2(self) -> f64 { - self.log_wrapper(|n| { - #[cfg(target_os = "android")] - return crate::sys::android::log2f64(n); - #[cfg(not(target_os = "android"))] - return unsafe { intrinsics::log2f64(n) }; - }) + crate::sys::log_wrapper(self, crate::sys::log2f64) } /// Returns the base 10 logarithm of the number. @@ -525,12 +523,12 @@ impl f64 { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn log10(self) -> f64 { - self.log_wrapper(|n| unsafe { intrinsics::log10f64(n) }) + crate::sys::log_wrapper(self, |n| unsafe { intrinsics::log10f64(n) }) } /// The positive difference of two numbers. /// - /// * If `self <= other`: `0:0` + /// * If `self <= other`: `0.0` /// * Else: `self - other` /// /// # Examples @@ -677,6 +675,7 @@ impl f64 { /// /// assert!(abs_difference < 1e-10); /// ``` + #[doc(alias = "arcsin")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -699,6 +698,7 @@ impl f64 { /// /// assert!(abs_difference < 1e-10); /// ``` + #[doc(alias = "arccos")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -720,6 +720,7 @@ impl f64 { /// /// assert!(abs_difference < 1e-10); /// ``` + #[doc(alias = "arctan")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -777,6 +778,7 @@ impl f64 { /// assert!(abs_difference_0 < 1e-10); /// assert!(abs_difference_1 < 1e-10); /// ``` + #[doc(alias = "sincos")] #[rustc_allow_incoherent_impl] #[stable(feature = "rust1", since = "1.0.0")] #[inline] @@ -909,6 +911,7 @@ impl f64 { /// /// assert!(abs_difference < 1.0e-10); /// ``` + #[doc(alias = "arcsinh")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -931,6 +934,7 @@ impl f64 { /// /// assert!(abs_difference < 1.0e-10); /// ``` + #[doc(alias = "arccosh")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -955,6 +959,7 @@ impl f64 { /// /// assert!(abs_difference < 1.0e-10); /// ``` + #[doc(alias = "arctanh")] #[rustc_allow_incoherent_impl] #[must_use = "method returns a new number and does not mutate the original value"] #[stable(feature = "rust1", since = "1.0.0")] @@ -963,27 +968,45 @@ impl f64 { 0.5 * ((2.0 * self) / (1.0 - self)).ln_1p() } - // Solaris/Illumos requires a wrapper around log, log2, and log10 functions - // because of their non-standard behavior (e.g., log(-n) returns -Inf instead - // of expected NaN). - #[rustc_allow_incoherent_impl] - fn log_wrapper f64>(self, log_fn: F) -> f64 { - if !cfg!(any(target_os = "solaris", target_os = "illumos")) { - log_fn(self) - } else if self.is_finite() { - if self > 0.0 { - log_fn(self) - } else if self == 0.0 { - Self::NEG_INFINITY // log(0) = -Inf - } else { - Self::NAN // log(-n) = NaN - } - } else if self.is_nan() { - self // log(NaN) = NaN - } else if self > 0.0 { - self // log(Inf) = Inf - } else { - Self::NAN // log(-Inf) = NaN - } + /// Gamma function. + /// + /// # Examples + /// + /// ``` + /// #![feature(float_gamma)] + /// let x = 5.0f64; + /// + /// let abs_difference = (x.gamma() - 24.0).abs(); + /// + /// assert!(abs_difference <= f64::EPSILON); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[unstable(feature = "float_gamma", issue = "99842")] + #[inline] + pub fn gamma(self) -> f64 { + unsafe { cmath::tgamma(self) } + } + + /// Returns the natural logarithm of the gamma function. + /// + /// # Examples + /// + /// ``` + /// #![feature(float_gamma)] + /// let x = 2.0f64; + /// + /// let abs_difference = (x.ln_gamma().0 - 0.0).abs(); + /// + /// assert!(abs_difference <= f64::EPSILON); + /// ``` + #[rustc_allow_incoherent_impl] + #[must_use = "method returns a new number and does not mutate the original value"] + #[unstable(feature = "float_gamma", issue = "99842")] + #[inline] + pub fn ln_gamma(self) -> (f64, i32) { + let mut signgamp: i32 = 0; + let x = unsafe { cmath::lgamma_r(self, &mut signgamp) }; + (x, signgamp) } } diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs index 53d351cceef5c..f88d01593b5e4 100644 --- a/library/std/src/f64/tests.rs +++ b/library/std/src/f64/tests.rs @@ -635,6 +635,38 @@ fn test_atanh() { assert_approx_eq!((-0.5f64).atanh(), -0.54930614433405484569762261846126285f64); } +#[test] +fn test_gamma() { + // precision can differ between platforms + assert_approx_eq!(1.0f64.gamma(), 1.0f64); + assert_approx_eq!(2.0f64.gamma(), 1.0f64); + assert_approx_eq!(3.0f64.gamma(), 2.0f64); + assert_approx_eq!(4.0f64.gamma(), 6.0f64); + assert_approx_eq!(5.0f64.gamma(), 24.0f64); + assert_approx_eq!(0.5f64.gamma(), consts::PI.sqrt()); + assert_approx_eq!((-0.5f64).gamma(), -2.0 * consts::PI.sqrt()); + assert_eq!(0.0f64.gamma(), f64::INFINITY); + assert_eq!((-0.0f64).gamma(), f64::NEG_INFINITY); + assert!((-1.0f64).gamma().is_nan()); + assert!((-2.0f64).gamma().is_nan()); + assert!(f64::NAN.gamma().is_nan()); + assert!(f64::NEG_INFINITY.gamma().is_nan()); + assert_eq!(f64::INFINITY.gamma(), f64::INFINITY); + assert_eq!(171.71f64.gamma(), f64::INFINITY); +} + +#[test] +fn test_ln_gamma() { + assert_approx_eq!(1.0f64.ln_gamma().0, 0.0f64); + assert_eq!(1.0f64.ln_gamma().1, 1); + assert_approx_eq!(2.0f64.ln_gamma().0, 0.0f64); + assert_eq!(2.0f64.ln_gamma().1, 1); + assert_approx_eq!(3.0f64.ln_gamma().0, 2.0f64.ln()); + assert_eq!(3.0f64.ln_gamma().1, 1); + assert_approx_eq!((-0.5f64).ln_gamma().0, (2.0 * consts::PI.sqrt()).ln()); + assert_eq!((-0.5f64).ln_gamma().1, -1); +} + #[test] fn test_real_consts() { use super::consts; diff --git a/library/std/src/ffi/mod.rs b/library/std/src/ffi/mod.rs index d987bf69b2576..ee9f6ed087c99 100644 --- a/library/std/src/ffi/mod.rs +++ b/library/std/src/ffi/mod.rs @@ -127,6 +127,14 @@ //! trait, which provides a [`from_wide`] method to convert a native Windows //! string (without the terminating nul character) to an [`OsString`]. //! +//! ## On all platforms +//! +//! On all platforms, [`OsStr`] consists of a sequence of bytes that is encoded as a superset of +//! UTF-8; see [`OsString`] for more details on its encoding on different platforms. +//! +//! For limited, inexpensive conversions from and to bytes, see [`OsStr::as_os_str_bytes`] and +//! [`OsStr::from_os_str_bytes_unchecked`]. +//! //! [Unicode scalar value]: https://www.unicode.org/glossary/#unicode_scalar_value //! [Unicode code point]: https://www.unicode.org/glossary/#code_point //! [`env::set_var()`]: crate::env::set_var "env::set_var" @@ -148,6 +156,8 @@ #[stable(feature = "alloc_c_string", since = "1.64.0")] pub use alloc::ffi::{CString, FromVecWithNulError, IntoStringError, NulError}; +#[stable(feature = "cstr_from_bytes_until_nul", since = "1.73.0")] +pub use core::ffi::FromBytesUntilNulError; #[stable(feature = "core_c_str", since = "1.64.0")] pub use core::ffi::{CStr, FromBytesWithNulError}; diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs index 5c0541d3caf33..43cecb19b148e 100644 --- a/library/std/src/ffi/os_str.rs +++ b/library/std/src/ffi/os_str.rs @@ -110,12 +110,12 @@ impl crate::sealed::Sealed for OsString {} /// [conversions]: super#conversions #[cfg_attr(not(test), rustc_diagnostic_item = "OsStr")] #[stable(feature = "rust1", since = "1.0.0")] -// FIXME: // `OsStr::from_inner` current implementation relies // on `OsStr` being layout-compatible with `Slice`. -// When attribute privacy is implemented, `OsStr` should be annotated as `#[repr(transparent)]`. -// Anyway, `OsStr` representation and layout are considered implementation details, are -// not documented and must not be relied upon. +// However, `OsStr` layout is considered an implementation detail and must not be relied upon. We +// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under +// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. +#[cfg_attr(not(doc), repr(transparent))] pub struct OsStr { inner: Slice, } @@ -141,6 +141,51 @@ impl OsString { OsString { inner: Buf::from_string(String::new()) } } + /// Converts bytes to an `OsString` without checking that the bytes contains + /// valid [`OsStr`]-encoded data. + /// + /// The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8. + /// By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit + /// ASCII. + /// + /// See the [module's toplevel documentation about conversions][conversions] for safe, + /// cross-platform [conversions] from/to native representations. + /// + /// # Safety + /// + /// As the encoding is unspecified, callers must pass in bytes that originated as a mixture of + /// validated UTF-8 and bytes from [`OsStr::as_os_str_bytes`] from within the same rust version + /// built for the same target platform. For example, reconstructing an `OsString` from bytes sent + /// over the network or stored in a file will likely violate these safety rules. + /// + /// Due to the encoding being self-synchronizing, the bytes from [`OsStr::as_os_str_bytes`] can be + /// split either immediately before or immediately after any valid non-empty UTF-8 substring. + /// + /// # Example + /// + /// ``` + /// #![feature(os_str_bytes)] + /// + /// use std::ffi::OsStr; + /// + /// let os_str = OsStr::new("Mary had a little lamb"); + /// let bytes = os_str.as_os_str_bytes(); + /// let words = bytes.split(|b| *b == b' '); + /// let words: Vec<&OsStr> = words.map(|word| { + /// // SAFETY: + /// // - Each `word` only contains content that originated from `OsStr::as_os_str_bytes` + /// // - Only split with ASCII whitespace which is a non-empty UTF-8 substring + /// unsafe { OsStr::from_os_str_bytes_unchecked(word) } + /// }).collect(); + /// ``` + /// + /// [conversions]: super#conversions + #[inline] + #[unstable(feature = "os_str_bytes", issue = "111544")] + pub unsafe fn from_os_str_bytes_unchecked(bytes: Vec) -> Self { + OsString { inner: Buf::from_os_str_bytes_unchecked(bytes) } + } + /// Converts to an [`OsStr`] slice. /// /// # Examples @@ -159,6 +204,26 @@ impl OsString { self } + /// Converts the `OsString` into a byte slice. To convert the byte slice back into an + /// `OsString`, use the [`OsStr::from_os_str_bytes_unchecked`] function. + /// + /// The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8. + /// By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit + /// ASCII. + /// + /// Note: As the encoding is unspecified, any sub-slice of bytes that is not valid UTF-8 should + /// be treated as opaque and only comparable within the same rust version built for the same + /// target platform. For example, sending the bytes over the network or storing it in a file + /// will likely result in incompatible data. See [`OsString`] for more encoding details + /// and [`std::ffi`] for platform-specific, specified conversions. + /// + /// [`std::ffi`]: crate::ffi + #[inline] + #[unstable(feature = "os_str_bytes", issue = "111544")] + pub fn into_os_str_bytes(self) -> Vec { + self.inner.into_os_str_bytes() + } + /// Converts the `OsString` into a [`String`] if it contains valid Unicode data. /// /// On failure, ownership of the original `OsString` is returned. @@ -667,6 +732,51 @@ impl OsStr { s.as_ref() } + /// Converts a slice of bytes to an OS string slice without checking that the string contains + /// valid `OsStr`-encoded data. + /// + /// The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8. + /// By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit + /// ASCII. + /// + /// See the [module's toplevel documentation about conversions][conversions] for safe, + /// cross-platform [conversions] from/to native representations. + /// + /// # Safety + /// + /// As the encoding is unspecified, callers must pass in bytes that originated as a mixture of + /// validated UTF-8 and bytes from [`OsStr::as_os_str_bytes`] from within the same rust version + /// built for the same target platform. For example, reconstructing an `OsStr` from bytes sent + /// over the network or stored in a file will likely violate these safety rules. + /// + /// Due to the encoding being self-synchronizing, the bytes from [`OsStr::as_os_str_bytes`] can be + /// split either immediately before or immediately after any valid non-empty UTF-8 substring. + /// + /// # Example + /// + /// ``` + /// #![feature(os_str_bytes)] + /// + /// use std::ffi::OsStr; + /// + /// let os_str = OsStr::new("Mary had a little lamb"); + /// let bytes = os_str.as_os_str_bytes(); + /// let words = bytes.split(|b| *b == b' '); + /// let words: Vec<&OsStr> = words.map(|word| { + /// // SAFETY: + /// // - Each `word` only contains content that originated from `OsStr::as_os_str_bytes` + /// // - Only split with ASCII whitespace which is a non-empty UTF-8 substring + /// unsafe { OsStr::from_os_str_bytes_unchecked(word) } + /// }).collect(); + /// ``` + /// + /// [conversions]: super#conversions + #[inline] + #[unstable(feature = "os_str_bytes", issue = "111544")] + pub unsafe fn from_os_str_bytes_unchecked(bytes: &[u8]) -> &Self { + Self::from_inner(Slice::from_os_str_bytes_unchecked(bytes)) + } + #[inline] fn from_inner(inner: &Slice) -> &OsStr { // SAFETY: OsStr is just a wrapper of Slice, @@ -700,7 +810,7 @@ impl OsStr { without modifying the original"] #[inline] pub fn to_str(&self) -> Option<&str> { - self.inner.to_str() + self.inner.to_str().ok() } /// Converts an `OsStr` to a [Cow]<[str]>. @@ -837,13 +947,24 @@ impl OsStr { OsString { inner: Buf::from_box(boxed) } } - /// Gets the underlying byte representation. + /// Converts an OS string slice to a byte slice. To convert the byte slice back into an OS + /// string slice, use the [`OsStr::from_os_str_bytes_unchecked`] function. /// - /// Note: it is *crucial* that this API is not externally public, to avoid - /// revealing the internal, platform-specific encodings. + /// The byte encoding is an unspecified, platform-specific, self-synchronizing superset of UTF-8. + /// By being a self-synchronizing superset of UTF-8, this encoding is also a superset of 7-bit + /// ASCII. + /// + /// Note: As the encoding is unspecified, any sub-slice of bytes that is not valid UTF-8 should + /// be treated as opaque and only comparable within the same rust version built for the same + /// target platform. For example, sending the slice over the network or storing it in a file + /// will likely result in incompatible byte slices. See [`OsString`] for more encoding details + /// and [`std::ffi`] for platform-specific, specified conversions. + /// + /// [`std::ffi`]: crate::ffi #[inline] - pub(crate) fn bytes(&self) -> &[u8] { - unsafe { &*(&self.inner as *const _ as *const [u8]) } + #[unstable(feature = "os_str_bytes", issue = "111544")] + pub fn as_os_str_bytes(&self) -> &[u8] { + self.inner.as_os_str_bytes() } /// Converts this string to its ASCII lower case equivalent in-place. @@ -1109,6 +1230,24 @@ impl<'a> From> for OsString { } } +#[stable(feature = "str_tryfrom_osstr_impl", since = "1.72.0")] +impl<'a> TryFrom<&'a OsStr> for &'a str { + type Error = crate::str::Utf8Error; + + /// Tries to convert an `&OsStr` to a `&str`. + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// let os_str = OsStr::new("foo"); + /// let as_str = <&str>::try_from(os_str).unwrap(); + /// assert_eq!(as_str, "foo"); + /// ``` + fn try_from(value: &'a OsStr) -> Result { + value.inner.to_str() + } +} + #[stable(feature = "box_default_extra", since = "1.17.0")] impl Default for Box { #[inline] @@ -1131,7 +1270,7 @@ impl Default for &OsStr { impl PartialEq for OsStr { #[inline] fn eq(&self, other: &OsStr) -> bool { - self.bytes().eq(other.bytes()) + self.as_os_str_bytes().eq(other.as_os_str_bytes()) } } @@ -1158,23 +1297,23 @@ impl Eq for OsStr {} impl PartialOrd for OsStr { #[inline] fn partial_cmp(&self, other: &OsStr) -> Option { - self.bytes().partial_cmp(other.bytes()) + self.as_os_str_bytes().partial_cmp(other.as_os_str_bytes()) } #[inline] fn lt(&self, other: &OsStr) -> bool { - self.bytes().lt(other.bytes()) + self.as_os_str_bytes().lt(other.as_os_str_bytes()) } #[inline] fn le(&self, other: &OsStr) -> bool { - self.bytes().le(other.bytes()) + self.as_os_str_bytes().le(other.as_os_str_bytes()) } #[inline] fn gt(&self, other: &OsStr) -> bool { - self.bytes().gt(other.bytes()) + self.as_os_str_bytes().gt(other.as_os_str_bytes()) } #[inline] fn ge(&self, other: &OsStr) -> bool { - self.bytes().ge(other.bytes()) + self.as_os_str_bytes().ge(other.as_os_str_bytes()) } } @@ -1193,7 +1332,7 @@ impl PartialOrd for OsStr { impl Ord for OsStr { #[inline] fn cmp(&self, other: &OsStr) -> cmp::Ordering { - self.bytes().cmp(other.bytes()) + self.as_os_str_bytes().cmp(other.as_os_str_bytes()) } } @@ -1243,7 +1382,7 @@ impl_cmp!(Cow<'a, OsStr>, OsString); impl Hash for OsStr { #[inline] fn hash(&self, state: &mut H) { - self.bytes().hash(state) + self.as_os_str_bytes().hash(state) } } diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 89dfdfafdb179..4094e378034e9 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -16,6 +16,7 @@ use crate::fmt; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write}; use crate::path::{Path, PathBuf}; use crate::sealed::Sealed; +use crate::sync::Arc; use crate::sys::fs as fs_imp; use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; @@ -743,7 +744,7 @@ fn buffer_capacity_required(mut file: &File) -> Option { } #[stable(feature = "rust1", since = "1.0.0")] -impl Read for File { +impl Read for &File { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.inner.read(buf) } @@ -776,7 +777,7 @@ impl Read for File { } } #[stable(feature = "rust1", since = "1.0.0")] -impl Write for File { +impl Write for &File { fn write(&mut self, buf: &[u8]) -> io::Result { self.inner.write(buf) } @@ -790,72 +791,107 @@ impl Write for File { self.inner.is_write_vectored() } + #[inline] fn flush(&mut self) -> io::Result<()> { self.inner.flush() } } #[stable(feature = "rust1", since = "1.0.0")] -impl Seek for File { +impl Seek for &File { fn seek(&mut self, pos: SeekFrom) -> io::Result { self.inner.seek(pos) } } + #[stable(feature = "rust1", since = "1.0.0")] -impl Read for &File { +impl Read for File { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.inner.read(buf) + (&*self).read(buf) + } + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + (&*self).read_vectored(bufs) } - fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { - self.inner.read_buf(cursor) + (&*self).read_buf(cursor) + } + #[inline] + fn is_read_vectored(&self) -> bool { + (&&*self).is_read_vectored() + } + fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + (&*self).read_to_end(buf) + } + fn read_to_string(&mut self, buf: &mut String) -> io::Result { + (&*self).read_to_string(buf) + } +} +#[stable(feature = "rust1", since = "1.0.0")] +impl Write for File { + fn write(&mut self, buf: &[u8]) -> io::Result { + (&*self).write(buf) + } + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + (&*self).write_vectored(bufs) + } + #[inline] + fn is_write_vectored(&self) -> bool { + (&&*self).is_write_vectored() + } + #[inline] + fn flush(&mut self) -> io::Result<()> { + (&*self).flush() + } +} +#[stable(feature = "rust1", since = "1.0.0")] +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + (&*self).seek(pos) } +} +#[stable(feature = "io_traits_arc", since = "1.73.0")] +impl Read for Arc { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + (&**self).read(buf) + } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { - self.inner.read_vectored(bufs) + (&**self).read_vectored(bufs) + } + fn read_buf(&mut self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + (&**self).read_buf(cursor) } - #[inline] fn is_read_vectored(&self) -> bool { - self.inner.is_read_vectored() + (&**self).is_read_vectored() } - - // Reserves space in the buffer based on the file size when available. fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - let size = buffer_capacity_required(self); - buf.reserve(size.unwrap_or(0)); - io::default_read_to_end(self, buf, size) + (&**self).read_to_end(buf) } - - // Reserves space in the buffer based on the file size when available. fn read_to_string(&mut self, buf: &mut String) -> io::Result { - let size = buffer_capacity_required(self); - buf.reserve(size.unwrap_or(0)); - io::default_read_to_string(self, buf, size) + (&**self).read_to_string(buf) } } -#[stable(feature = "rust1", since = "1.0.0")] -impl Write for &File { +#[stable(feature = "io_traits_arc", since = "1.73.0")] +impl Write for Arc { fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) + (&**self).write(buf) } - fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { - self.inner.write_vectored(bufs) + (&**self).write_vectored(bufs) } - #[inline] fn is_write_vectored(&self) -> bool { - self.inner.is_write_vectored() + (&**self).is_write_vectored() } - + #[inline] fn flush(&mut self) -> io::Result<()> { - self.inner.flush() + (&**self).flush() } } -#[stable(feature = "rust1", since = "1.0.0")] -impl Seek for &File { +#[stable(feature = "io_traits_arc", since = "1.73.0")] +impl Seek for Arc { fn seek(&mut self, pos: SeekFrom) -> io::Result { - self.inner.seek(pos) + (&**self).seek(pos) } } @@ -1755,8 +1791,14 @@ impl DirEntry { self.0.file_type().map(FileType) } - /// Returns the bare file name of this directory entry without any other - /// leading path component. + /// Returns the file name of this directory entry without any + /// leading path component(s). + /// + /// As an example, + /// the output of the function will result in "foo" for all the following paths: + /// - "./foo" + /// - "/the/foo" + /// - "../../foo" /// /// # Examples /// diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index e2480bcbbc729..d74f0f00e46f4 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -957,6 +957,7 @@ fn readlink_not_symlink() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating hardlinks fn links_work() { let tmpdir = tmpdir(); let input = tmpdir.join("in.txt"); @@ -1453,6 +1454,7 @@ fn metadata_access_times() { /// Test creating hard links to symlinks. #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating hardlinks fn symlink_hard_link() { let tmpdir = tmpdir(); if !got_symlink_permission(&tmpdir) { @@ -1640,6 +1642,10 @@ fn test_file_times() { use crate::os::ios::fs::FileTimesExt; #[cfg(target_os = "macos")] use crate::os::macos::fs::FileTimesExt; + #[cfg(target_os = "tvos")] + use crate::os::tvos::fs::FileTimesExt; + #[cfg(target_os = "tvos")] + use crate::os::tvos::fs::FileTimesExt; #[cfg(target_os = "watchos")] use crate::os::watchos::fs::FileTimesExt; #[cfg(windows)] @@ -1651,9 +1657,21 @@ fn test_file_times() { let accessed = SystemTime::UNIX_EPOCH + Duration::from_secs(12345); let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(54321); times = times.set_accessed(accessed).set_modified(modified); - #[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any( + windows, + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos", + ))] let created = SystemTime::UNIX_EPOCH + Duration::from_secs(32123); - #[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any( + windows, + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos", + ))] { times = times.set_created(created); } @@ -1678,7 +1696,13 @@ fn test_file_times() { let metadata = file.metadata().unwrap(); assert_eq!(metadata.accessed().unwrap(), accessed); assert_eq!(metadata.modified().unwrap(), modified); - #[cfg(any(windows, target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any( + windows, + target_os = "macos", + target_os = "ios", + target_os = "watchos", + target_os = "tvos", + ))] { assert_eq!(metadata.created().unwrap(), created); } diff --git a/library/std/src/io/buffered/bufreader.rs b/library/std/src/io/buffered/bufreader.rs index 4f339a18a480e..7097dfef88d4e 100644 --- a/library/std/src/io/buffered/bufreader.rs +++ b/library/std/src/io/buffered/bufreader.rs @@ -47,13 +47,13 @@ use buffer::Buffer; /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] -pub struct BufReader { - inner: R, +pub struct BufReader { buf: Buffer, + inner: R, } impl BufReader { - /// Creates a new `BufReader` with a default buffer capacity. The default is currently 8 KB, + /// Creates a new `BufReader` with a default buffer capacity. The default is currently 8 KiB, /// but may change in the future. /// /// # Examples @@ -95,7 +95,7 @@ impl BufReader { } } -impl BufReader { +impl BufReader { /// Gets a reference to the underlying reader. /// /// It is inadvisable to directly read from the underlying reader. @@ -213,26 +213,29 @@ impl BufReader { /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] - pub fn into_inner(self) -> R { + pub fn into_inner(self) -> R + where + R: Sized, + { self.inner } /// Invalidates all data in the internal buffer. #[inline] - fn discard_buffer(&mut self) { + pub(in crate::io) fn discard_buffer(&mut self) { self.buf.discard_buffer() } } // This is only used by a test which asserts that the initialization-tracking is correct. #[cfg(test)] -impl BufReader { +impl BufReader { pub fn initialized(&self) -> usize { self.buf.initialized() } } -impl BufReader { +impl BufReader { /// Seeks relative to the current position. If the new position lies within the buffer, /// the buffer will not be flushed, allowing for more efficient seeks. /// This method does not return the location of the underlying reader, so the caller @@ -257,7 +260,7 @@ impl BufReader { } #[stable(feature = "rust1", since = "1.0.0")] -impl Read for BufReader { +impl Read for BufReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { // If we don't have any buffered data and we're doing a massive read // (larger than our internal buffer), bypass our internal buffer @@ -371,7 +374,7 @@ impl Read for BufReader { } #[stable(feature = "rust1", since = "1.0.0")] -impl BufRead for BufReader { +impl BufRead for BufReader { fn fill_buf(&mut self) -> io::Result<&[u8]> { self.buf.fill_buf(&mut self.inner) } @@ -384,11 +387,11 @@ impl BufRead for BufReader { #[stable(feature = "rust1", since = "1.0.0")] impl fmt::Debug for BufReader where - R: fmt::Debug, + R: ?Sized + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("BufReader") - .field("reader", &self.inner) + .field("reader", &&self.inner) .field( "buffer", &format_args!("{}/{}", self.buf.filled() - self.buf.pos(), self.capacity()), @@ -398,7 +401,7 @@ where } #[stable(feature = "rust1", since = "1.0.0")] -impl Seek for BufReader { +impl Seek for BufReader { /// Seek to an offset, in bytes, in the underlying reader. /// /// The position used for seeking with [SeekFrom::Current]\(_) is the @@ -491,7 +494,7 @@ impl Seek for BufReader { } } -impl SizeHint for BufReader { +impl SizeHint for BufReader { #[inline] fn lower_bound(&self) -> usize { SizeHint::lower_bound(self.get_ref()) + self.buffer().len() diff --git a/library/std/src/io/buffered/bufwriter.rs b/library/std/src/io/buffered/bufwriter.rs index 14c455d4fa3c9..0f04f29111793 100644 --- a/library/std/src/io/buffered/bufwriter.rs +++ b/library/std/src/io/buffered/bufwriter.rs @@ -67,8 +67,7 @@ use crate::ptr; /// [`TcpStream`]: crate::net::TcpStream /// [`flush`]: BufWriter::flush #[stable(feature = "rust1", since = "1.0.0")] -pub struct BufWriter { - inner: W, +pub struct BufWriter { // The buffer. Avoid using this like a normal `Vec` in common code paths. // That is, don't use `buf.push`, `buf.extend_from_slice`, or any other // methods that require bounds checking or the like. This makes an enormous @@ -78,10 +77,11 @@ pub struct BufWriter { // write the buffered data a second time in BufWriter's destructor. This // flag tells the Drop impl if it should skip the flush. panicked: bool, + inner: W, } impl BufWriter { - /// Creates a new `BufWriter` with a default buffer capacity. The default is currently 8 KB, + /// Creates a new `BufWriter` with a default buffer capacity. The default is currently 8 KiB, /// but may change in the future. /// /// # Examples @@ -115,6 +115,69 @@ impl BufWriter { BufWriter { inner, buf: Vec::with_capacity(capacity), panicked: false } } + /// Unwraps this `BufWriter`, returning the underlying writer. + /// + /// The buffer is written out before returning the writer. + /// + /// # Errors + /// + /// An [`Err`] will be returned if an error occurs while flushing the buffer. + /// + /// # Examples + /// + /// ```no_run + /// use std::io::BufWriter; + /// use std::net::TcpStream; + /// + /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); + /// + /// // unwrap the TcpStream and flush the buffer + /// let stream = buffer.into_inner().unwrap(); + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn into_inner(mut self) -> Result>> { + match self.flush_buf() { + Err(e) => Err(IntoInnerError::new(self, e)), + Ok(()) => Ok(self.into_parts().0), + } + } + + /// Disassembles this `BufWriter`, returning the underlying writer, and any buffered but + /// unwritten data. + /// + /// If the underlying writer panicked, it is not known what portion of the data was written. + /// In this case, we return `WriterPanicked` for the buffered data (from which the buffer + /// contents can still be recovered). + /// + /// `into_parts` makes no attempt to flush data and cannot fail. + /// + /// # Examples + /// + /// ``` + /// use std::io::{BufWriter, Write}; + /// + /// let mut buffer = [0u8; 10]; + /// let mut stream = BufWriter::new(buffer.as_mut()); + /// write!(stream, "too much data").unwrap(); + /// stream.flush().expect_err("it doesn't fit"); + /// let (recovered_writer, buffered_data) = stream.into_parts(); + /// assert_eq!(recovered_writer.len(), 0); + /// assert_eq!(&buffered_data.unwrap(), b"ata"); + /// ``` + #[stable(feature = "bufwriter_into_parts", since = "1.56.0")] + pub fn into_parts(mut self) -> (W, Result, WriterPanicked>) { + let buf = mem::take(&mut self.buf); + let buf = if !self.panicked { Ok(buf) } else { Err(WriterPanicked { buf }) }; + + // SAFETY: forget(self) prevents double dropping inner + let inner = unsafe { ptr::read(&self.inner) }; + mem::forget(self); + + (inner, buf) + } +} + +impl BufWriter { /// Send data in our local buffer into the inner writer, looping as /// necessary until either it's all been sent or an error occurs. /// @@ -284,67 +347,6 @@ impl BufWriter { self.buf.capacity() } - /// Unwraps this `BufWriter`, returning the underlying writer. - /// - /// The buffer is written out before returning the writer. - /// - /// # Errors - /// - /// An [`Err`] will be returned if an error occurs while flushing the buffer. - /// - /// # Examples - /// - /// ```no_run - /// use std::io::BufWriter; - /// use std::net::TcpStream; - /// - /// let mut buffer = BufWriter::new(TcpStream::connect("127.0.0.1:34254").unwrap()); - /// - /// // unwrap the TcpStream and flush the buffer - /// let stream = buffer.into_inner().unwrap(); - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn into_inner(mut self) -> Result>> { - match self.flush_buf() { - Err(e) => Err(IntoInnerError::new(self, e)), - Ok(()) => Ok(self.into_parts().0), - } - } - - /// Disassembles this `BufWriter`, returning the underlying writer, and any buffered but - /// unwritten data. - /// - /// If the underlying writer panicked, it is not known what portion of the data was written. - /// In this case, we return `WriterPanicked` for the buffered data (from which the buffer - /// contents can still be recovered). - /// - /// `into_parts` makes no attempt to flush data and cannot fail. - /// - /// # Examples - /// - /// ``` - /// use std::io::{BufWriter, Write}; - /// - /// let mut buffer = [0u8; 10]; - /// let mut stream = BufWriter::new(buffer.as_mut()); - /// write!(stream, "too much data").unwrap(); - /// stream.flush().expect_err("it doesn't fit"); - /// let (recovered_writer, buffered_data) = stream.into_parts(); - /// assert_eq!(recovered_writer.len(), 0); - /// assert_eq!(&buffered_data.unwrap(), b"ata"); - /// ``` - #[stable(feature = "bufwriter_into_parts", since = "1.56.0")] - pub fn into_parts(mut self) -> (W, Result, WriterPanicked>) { - let buf = mem::take(&mut self.buf); - let buf = if !self.panicked { Ok(buf) } else { Err(WriterPanicked { buf }) }; - - // SAFETY: forget(self) prevents double dropping inner - let inner = unsafe { ptr::read(&self.inner) }; - mem::forget(self); - - (inner, buf) - } - // Ensure this function does not get inlined into `write`, so that it // remains inlineable and its common path remains as short as possible. // If this function ends up being called frequently relative to `write`, @@ -511,7 +513,7 @@ impl fmt::Debug for WriterPanicked { } #[stable(feature = "rust1", since = "1.0.0")] -impl Write for BufWriter { +impl Write for BufWriter { #[inline] fn write(&mut self, buf: &[u8]) -> io::Result { // Use < instead of <= to avoid a needless trip through the buffer in some cases. @@ -640,20 +642,20 @@ impl Write for BufWriter { } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Debug for BufWriter +impl fmt::Debug for BufWriter where W: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("BufWriter") - .field("writer", &self.inner) + .field("writer", &&self.inner) .field("buffer", &format_args!("{}/{}", self.buf.len(), self.buf.capacity())) .finish() } } #[stable(feature = "rust1", since = "1.0.0")] -impl Seek for BufWriter { +impl Seek for BufWriter { /// Seek to the offset, in bytes, in the underlying writer. /// /// Seeking always writes out the internal buffer before seeking. @@ -664,7 +666,7 @@ impl Seek for BufWriter { } #[stable(feature = "rust1", since = "1.0.0")] -impl Drop for BufWriter { +impl Drop for BufWriter { fn drop(&mut self) { if !self.panicked { // dtors should not panic, so we ignore a failed flush diff --git a/library/std/src/io/buffered/linewriter.rs b/library/std/src/io/buffered/linewriter.rs index a26a4ab330e7a..3d4ae70419322 100644 --- a/library/std/src/io/buffered/linewriter.rs +++ b/library/std/src/io/buffered/linewriter.rs @@ -64,7 +64,7 @@ use crate::io::{self, buffered::LineWriterShim, BufWriter, IntoInnerError, IoSli /// } /// ``` #[stable(feature = "rust1", since = "1.0.0")] -pub struct LineWriter { +pub struct LineWriter { inner: BufWriter, } @@ -109,27 +109,6 @@ impl LineWriter { LineWriter { inner: BufWriter::with_capacity(capacity, inner) } } - /// Gets a reference to the underlying writer. - /// - /// # Examples - /// - /// ```no_run - /// use std::fs::File; - /// use std::io::LineWriter; - /// - /// fn main() -> std::io::Result<()> { - /// let file = File::create("poem.txt")?; - /// let file = LineWriter::new(file); - /// - /// let reference = file.get_ref(); - /// Ok(()) - /// } - /// ``` - #[stable(feature = "rust1", since = "1.0.0")] - pub fn get_ref(&self) -> &W { - self.inner.get_ref() - } - /// Gets a mutable reference to the underlying writer. /// /// Caution must be taken when calling methods on the mutable reference @@ -184,8 +163,31 @@ impl LineWriter { } } +impl LineWriter { + /// Gets a reference to the underlying writer. + /// + /// # Examples + /// + /// ```no_run + /// use std::fs::File; + /// use std::io::LineWriter; + /// + /// fn main() -> std::io::Result<()> { + /// let file = File::create("poem.txt")?; + /// let file = LineWriter::new(file); + /// + /// let reference = file.get_ref(); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn get_ref(&self) -> &W { + self.inner.get_ref() + } +} + #[stable(feature = "rust1", since = "1.0.0")] -impl Write for LineWriter { +impl Write for LineWriter { fn write(&mut self, buf: &[u8]) -> io::Result { LineWriterShim::new(&mut self.inner).write(buf) } @@ -216,7 +218,7 @@ impl Write for LineWriter { } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Debug for LineWriter +impl fmt::Debug for LineWriter where W: fmt::Debug, { diff --git a/library/std/src/io/buffered/linewritershim.rs b/library/std/src/io/buffered/linewritershim.rs index 0175d2693e894..f2a55da05b22e 100644 --- a/library/std/src/io/buffered/linewritershim.rs +++ b/library/std/src/io/buffered/linewritershim.rs @@ -11,11 +11,11 @@ use crate::sys_common::memchr; /// `BufWriters` to be temporarily given line-buffering logic; this is what /// enables Stdout to be alternately in line-buffered or block-buffered mode. #[derive(Debug)] -pub struct LineWriterShim<'a, W: Write> { +pub struct LineWriterShim<'a, W: ?Sized + Write> { buffer: &'a mut BufWriter, } -impl<'a, W: Write> LineWriterShim<'a, W> { +impl<'a, W: ?Sized + Write> LineWriterShim<'a, W> { pub fn new(buffer: &'a mut BufWriter) -> Self { Self { buffer } } @@ -49,7 +49,7 @@ impl<'a, W: Write> LineWriterShim<'a, W> { } } -impl<'a, W: Write> Write for LineWriterShim<'a, W> { +impl<'a, W: ?Sized + Write> Write for LineWriterShim<'a, W> { /// Write some data into this BufReader with line buffering. This means /// that, if any newlines are present in the data, the data up to the last /// newline is sent directly to the underlying writer, and data after it diff --git a/library/std/src/io/copy.rs b/library/std/src/io/copy.rs index 1d9d93f5b64ec..3322940d2452f 100644 --- a/library/std/src/io/copy.rs +++ b/library/std/src/io/copy.rs @@ -1,6 +1,13 @@ -use super::{BorrowedBuf, BufWriter, ErrorKind, Read, Result, Write, DEFAULT_BUF_SIZE}; +use super::{BorrowedBuf, BufReader, BufWriter, ErrorKind, Read, Result, Write, DEFAULT_BUF_SIZE}; +use crate::alloc::Allocator; +use crate::cmp; +use crate::collections::VecDeque; +use crate::io::IoSlice; use crate::mem::MaybeUninit; +#[cfg(test)] +mod tests; + /// Copies the entire contents of a reader into a writer. /// /// This function will continuously read data from `reader` and then @@ -71,32 +78,145 @@ where R: Read, W: Write, { - BufferedCopySpec::copy_to(reader, writer) + let read_buf = BufferedReaderSpec::buffer_size(reader); + let write_buf = BufferedWriterSpec::buffer_size(writer); + + if read_buf >= DEFAULT_BUF_SIZE && read_buf >= write_buf { + return BufferedReaderSpec::copy_to(reader, writer); + } + + BufferedWriterSpec::copy_from(writer, reader) +} + +/// Specialization of the read-write loop that reuses the internal +/// buffer of a BufReader. If there's no buffer then the writer side +/// should be used instead. +trait BufferedReaderSpec { + fn buffer_size(&self) -> usize; + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result; +} + +impl BufferedReaderSpec for T +where + Self: Read, + T: ?Sized, +{ + #[inline] + default fn buffer_size(&self) -> usize { + 0 + } + + default fn copy_to(&mut self, _to: &mut (impl Write + ?Sized)) -> Result { + unreachable!("only called from specializations") + } +} + +impl BufferedReaderSpec for &[u8] { + fn buffer_size(&self) -> usize { + // prefer this specialization since the source "buffer" is all we'll ever need, + // even if it's small + usize::MAX + } + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { + let len = self.len(); + to.write_all(self)?; + *self = &self[len..]; + Ok(len as u64) + } +} + +impl BufferedReaderSpec for VecDeque { + fn buffer_size(&self) -> usize { + // prefer this specialization since the source "buffer" is all we'll ever need, + // even if it's small + usize::MAX + } + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { + let len = self.len(); + let (front, back) = self.as_slices(); + let bufs = &mut [IoSlice::new(front), IoSlice::new(back)]; + to.write_all_vectored(bufs)?; + self.clear(); + Ok(len as u64) + } +} + +impl BufferedReaderSpec for BufReader +where + Self: Read, + I: ?Sized, +{ + fn buffer_size(&self) -> usize { + self.capacity() + } + + fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result { + let mut len = 0; + + loop { + // Hack: this relies on `impl Read for BufReader` always calling fill_buf + // if the buffer is empty, even for empty slices. + // It can't be called directly here since specialization prevents us + // from adding I: Read + match self.read(&mut []) { + Ok(_) => {} + Err(e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + } + let buf = self.buffer(); + if self.buffer().len() == 0 { + return Ok(len); + } + + // In case the writer side is a BufWriter then its write_all + // implements an optimization that passes through large + // buffers to the underlying writer. That code path is #[cold] + // but we're still avoiding redundant memcopies when doing + // a copy between buffered inputs and outputs. + to.write_all(buf)?; + len += buf.len() as u64; + self.discard_buffer(); + } + } } /// Specialization of the read-write loop that either uses a stack buffer /// or reuses the internal buffer of a BufWriter -trait BufferedCopySpec: Write { - fn copy_to(reader: &mut R, writer: &mut Self) -> Result; +trait BufferedWriterSpec: Write { + fn buffer_size(&self) -> usize; + + fn copy_from(&mut self, reader: &mut R) -> Result; } -impl BufferedCopySpec for W { - default fn copy_to(reader: &mut R, writer: &mut Self) -> Result { - stack_buffer_copy(reader, writer) +impl BufferedWriterSpec for W { + #[inline] + default fn buffer_size(&self) -> usize { + 0 + } + + default fn copy_from(&mut self, reader: &mut R) -> Result { + stack_buffer_copy(reader, self) } } -impl BufferedCopySpec for BufWriter { - fn copy_to(reader: &mut R, writer: &mut Self) -> Result { - if writer.capacity() < DEFAULT_BUF_SIZE { - return stack_buffer_copy(reader, writer); +impl BufferedWriterSpec for BufWriter { + fn buffer_size(&self) -> usize { + self.capacity() + } + + fn copy_from(&mut self, reader: &mut R) -> Result { + if self.capacity() < DEFAULT_BUF_SIZE { + return stack_buffer_copy(reader, self); } let mut len = 0; let mut init = 0; loop { - let buf = writer.buffer_mut(); + let buf = self.buffer_mut(); let mut read_buf: BorrowedBuf<'_> = buf.spare_capacity_mut().into(); unsafe { @@ -127,13 +247,54 @@ impl BufferedCopySpec for BufWriter { Err(e) => return Err(e), } } else { - writer.flush_buf()?; + self.flush_buf()?; init = 0; } } } } +impl BufferedWriterSpec for Vec { + fn buffer_size(&self) -> usize { + cmp::max(DEFAULT_BUF_SIZE, self.capacity() - self.len()) + } + + fn copy_from(&mut self, reader: &mut R) -> Result { + let mut bytes = 0; + + // avoid allocating before we have determined that there's anything to read + if self.capacity() == 0 { + bytes = stack_buffer_copy(&mut reader.take(DEFAULT_BUF_SIZE as u64), self)?; + if bytes == 0 { + return Ok(0); + } + } + + loop { + self.reserve(DEFAULT_BUF_SIZE); + let mut buf: BorrowedBuf<'_> = self.spare_capacity_mut().into(); + match reader.read_buf(buf.unfilled()) { + Ok(()) => {} + Err(e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + + let read = buf.filled().len(); + if read == 0 { + break; + } + + // SAFETY: BorrowedBuf guarantees all of its filled bytes are init + // and the number of read bytes can't exceed the spare capacity since + // that's what the buffer is borrowing from. + unsafe { self.set_len(self.len() + read) }; + bytes += read as u64; + } + + Ok(bytes) + } +} + fn stack_buffer_copy( reader: &mut R, writer: &mut W, diff --git a/library/std/src/io/copy/tests.rs b/library/std/src/io/copy/tests.rs new file mode 100644 index 0000000000000..af137eaf856f5 --- /dev/null +++ b/library/std/src/io/copy/tests.rs @@ -0,0 +1,144 @@ +use crate::cmp::{max, min}; +use crate::collections::VecDeque; +use crate::io; +use crate::io::*; + +#[test] +fn copy_copies() { + let mut r = repeat(0).take(4); + let mut w = sink(); + assert_eq!(copy(&mut r, &mut w).unwrap(), 4); + + let mut r = repeat(0).take(1 << 17); + assert_eq!(copy(&mut r as &mut dyn Read, &mut w as &mut dyn Write).unwrap(), 1 << 17); +} + +struct ShortReader { + cap: usize, + read_size: usize, + observed_buffer: usize, +} + +impl Read for ShortReader { + fn read(&mut self, buf: &mut [u8]) -> Result { + let bytes = min(self.cap, self.read_size).min(buf.len()); + self.cap -= bytes; + self.observed_buffer = max(self.observed_buffer, buf.len()); + Ok(bytes) + } +} + +struct WriteObserver { + observed_buffer: usize, +} + +impl Write for WriteObserver { + fn write(&mut self, buf: &[u8]) -> Result { + self.observed_buffer = max(self.observed_buffer, buf.len()); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<()> { + Ok(()) + } +} + +#[test] +fn copy_specializes_bufwriter() { + let cap = 117 * 1024; + let buf_sz = 16 * 1024; + let mut r = ShortReader { cap, observed_buffer: 0, read_size: 1337 }; + let mut w = BufWriter::with_capacity(buf_sz, WriteObserver { observed_buffer: 0 }); + assert_eq!( + copy(&mut r, &mut w).unwrap(), + cap as u64, + "expected the whole capacity to be copied" + ); + assert_eq!(r.observed_buffer, buf_sz, "expected a large buffer to be provided to the reader"); + assert!(w.get_mut().observed_buffer > DEFAULT_BUF_SIZE, "expected coalesced writes"); +} + +#[test] +fn copy_specializes_bufreader() { + let mut source = vec![0; 768 * 1024]; + source[1] = 42; + let mut buffered = BufReader::with_capacity(256 * 1024, Cursor::new(&mut source)); + + let mut sink = Vec::new(); + assert_eq!(crate::io::copy(&mut buffered, &mut sink).unwrap(), source.len() as u64); + assert_eq!(source.as_slice(), sink.as_slice()); + + let buf_sz = 71 * 1024; + assert!(buf_sz > DEFAULT_BUF_SIZE, "test precondition"); + + let mut buffered = BufReader::with_capacity(buf_sz, Cursor::new(&mut source)); + let mut sink = WriteObserver { observed_buffer: 0 }; + assert_eq!(crate::io::copy(&mut buffered, &mut sink).unwrap(), source.len() as u64); + assert_eq!( + sink.observed_buffer, buf_sz, + "expected a large buffer to be provided to the writer" + ); +} + +#[test] +fn copy_specializes_to_vec() { + let cap = 123456; + let mut source = ShortReader { cap, observed_buffer: 0, read_size: 1337 }; + let mut sink = Vec::new(); + assert_eq!(cap as u64, io::copy(&mut source, &mut sink).unwrap()); + assert!( + source.observed_buffer > DEFAULT_BUF_SIZE, + "expected a large buffer to be provided to the reader" + ); +} + +#[test] +fn copy_specializes_from_vecdeque() { + let mut source = VecDeque::with_capacity(100 * 1024); + for _ in 0..20 * 1024 { + source.push_front(0); + } + for _ in 0..20 * 1024 { + source.push_back(0); + } + let mut sink = WriteObserver { observed_buffer: 0 }; + assert_eq!(40 * 1024u64, io::copy(&mut source, &mut sink).unwrap()); + assert_eq!(20 * 1024, sink.observed_buffer); +} + +#[test] +fn copy_specializes_from_slice() { + let mut source = [1; 60 * 1024].as_slice(); + let mut sink = WriteObserver { observed_buffer: 0 }; + assert_eq!(60 * 1024u64, io::copy(&mut source, &mut sink).unwrap()); + assert_eq!(60 * 1024, sink.observed_buffer); +} + +#[cfg(unix)] +mod io_benches { + use crate::fs::File; + use crate::fs::OpenOptions; + use crate::io::prelude::*; + use crate::io::BufReader; + + use test::Bencher; + + #[bench] + fn bench_copy_buf_reader(b: &mut Bencher) { + let mut file_in = File::open("/dev/zero").expect("opening /dev/zero failed"); + // use dyn to avoid specializations unrelated to readbuf + let dyn_in = &mut file_in as &mut dyn Read; + let mut reader = BufReader::with_capacity(256 * 1024, dyn_in.take(0)); + let mut writer = + OpenOptions::new().write(true).open("/dev/null").expect("opening /dev/null failed"); + + const BYTES: u64 = 1024 * 1024; + + b.bytes = BYTES; + + b.iter(|| { + reader.get_mut().set_limit(BYTES); + crate::io::copy(&mut reader, &mut writer).unwrap() + }); + } +} diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 9e09ce337bc0e..71d91f21362eb 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -593,7 +593,8 @@ pub trait Read { /// This may happen for example because fewer bytes are actually available right now /// (e. g. being close to end-of-file) or because read() was interrupted by a signal. /// - /// As this trait is safe to implement, callers cannot rely on `n <= buf.len()` for safety. + /// As this trait is safe to implement, callers in unsafe code cannot rely on + /// `n <= buf.len()` for safety. /// Extra care needs to be taken when `unsafe` functions are used to access the read bytes. /// Callers have to ensure that no unchecked out-of-bounds accesses are possible even if /// `n > buf.len()`. @@ -603,8 +604,8 @@ pub trait Read { /// contents of `buf` being true. It is recommended that *implementations* /// only write data to `buf` instead of reading its contents. /// - /// Correspondingly, however, *callers* of this method must not assume any guarantees - /// about how the implementation uses `buf`. The trait is safe to implement, + /// Correspondingly, however, *callers* of this method in unsafe code must not assume + /// any guarantees about how the implementation uses `buf`. The trait is safe to implement, /// so it is possible that the code that's supposed to write to the buffer might also read /// from it. It is your responsibility to make sure that `buf` is initialized /// before calling `read`. Calling `read` with an uninitialized `buf` (of the kind one @@ -1415,17 +1416,18 @@ pub trait Write { /// /// This function will attempt to write the entire contents of `buf`, but /// the entire write might not succeed, or the write may also generate an - /// error. A call to `write` represents *at most one* attempt to write to + /// error. Typically, a call to `write` represents one attempt to write to /// any wrapped object. /// /// Calls to `write` are not guaranteed to block waiting for data to be /// written, and a write which would otherwise block can be indicated through /// an [`Err`] variant. /// - /// If the return value is [`Ok(n)`] then it must be guaranteed that - /// `n <= buf.len()`. A return value of `0` typically means that the - /// underlying object is no longer able to accept bytes and will likely not - /// be able to in the future as well, or that the buffer provided is empty. + /// If this method consumed `n > 0` bytes of `buf` it must return [`Ok(n)`]. + /// If the return value is `Ok(n)` then `n` must satisfy `n <= buf.len()`. + /// A return value of `Ok(0)` typically means that the underlying object is + /// no longer able to accept bytes and will likely not be able to in the + /// future as well, or that the buffer provided is empty. /// /// # Errors /// @@ -2753,7 +2755,7 @@ trait SizeHint { } } -impl SizeHint for T { +impl SizeHint for T { #[inline] default fn lower_bound(&self) -> usize { 0 diff --git a/library/std/src/io/readbuf.rs b/library/std/src/io/readbuf.rs index 4800eeda022bb..034ddd8df9aed 100644 --- a/library/std/src/io/readbuf.rs +++ b/library/std/src/io/readbuf.rs @@ -99,6 +99,13 @@ impl<'data> BorrowedBuf<'data> { unsafe { MaybeUninit::slice_assume_init_ref(&self.buf[0..self.filled]) } } + /// Returns a mutable reference to the filled portion of the buffer. + #[inline] + pub fn filled_mut(&mut self) -> &mut [u8] { + // SAFETY: We only slice the filled part of the buffer, which is always valid + unsafe { MaybeUninit::slice_assume_init_mut(&mut self.buf[0..self.filled]) } + } + /// Returns a cursor over the unfilled part of the buffer. #[inline] pub fn unfilled<'this>(&'this mut self) -> BorrowedCursor<'this> { @@ -303,6 +310,7 @@ impl<'a> Write for BorrowedCursor<'a> { Ok(buf.len()) } + #[inline] fn flush(&mut self) -> Result<()> { Ok(()) } diff --git a/library/std/src/io/util.rs b/library/std/src/io/util.rs index f076ee0923c80..6bc8f181c905f 100644 --- a/library/std/src/io/util.rs +++ b/library/std/src/io/util.rs @@ -8,24 +8,41 @@ use crate::io::{ self, BorrowedCursor, BufRead, IoSlice, IoSliceMut, Read, Seek, SeekFrom, SizeHint, Write, }; -/// A reader which is always at EOF. +/// `Empty` ignores any data written via [`Write`], and will always be empty +/// (returning zero bytes) when read via [`Read`]. /// -/// This struct is generally created by calling [`empty()`]. Please see -/// the documentation of [`empty()`] for more details. +/// This struct is generally created by calling [`empty()`]. Please +/// see the documentation of [`empty()`] for more details. #[stable(feature = "rust1", since = "1.0.0")] #[non_exhaustive] -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Debug, Default)] pub struct Empty; -/// Constructs a new handle to an empty reader. +/// Creates a value that is always at EOF for reads, and ignores all data written. /// -/// All reads from the returned reader will return [Ok]\(0). +/// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] +/// and the contents of the buffer will not be inspected. +/// +/// All calls to [`read`] from the returned reader will return [`Ok(0)`]. +/// +/// [`Ok(buf.len())`]: Ok +/// [`Ok(0)`]: Ok +/// +/// [`write`]: Write::write +/// [`read`]: Read::read /// /// # Examples /// -/// A slightly sad example of not reading anything into a buffer: +/// ```rust +/// use std::io::{self, Write}; /// +/// let buffer = vec![1, 2, 3, 5, 8]; +/// let num_bytes = io::empty().write(&buffer).unwrap(); +/// assert_eq!(num_bytes, 5); /// ``` +/// +/// +/// ```rust /// use std::io::{self, Read}; /// /// let mut buffer = String::new(); @@ -76,13 +93,6 @@ impl Seek for Empty { } } -#[stable(feature = "std_debug", since = "1.16.0")] -impl fmt::Debug for Empty { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Empty").finish_non_exhaustive() - } -} - impl SizeHint for Empty { #[inline] fn upper_bound(&self) -> Option { @@ -90,6 +100,54 @@ impl SizeHint for Empty { } } +#[stable(feature = "empty_write", since = "1.73.0")] +impl Write for Empty { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "empty_write", since = "1.73.0")] +impl Write for &Empty { + #[inline] + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + + #[inline] + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + let total_len = bufs.iter().map(|b| b.len()).sum(); + Ok(total_len) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + true + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + /// A reader which yields one byte over and over and over and over and over and... /// /// This struct is generally created by calling [`repeat()`]. Please @@ -182,19 +240,20 @@ impl fmt::Debug for Repeat { /// A writer which will move data into the void. /// -/// This struct is generally created by calling [`sink`]. Please +/// This struct is generally created by calling [`sink()`]. Please /// see the documentation of [`sink()`] for more details. #[stable(feature = "rust1", since = "1.0.0")] #[non_exhaustive] -#[derive(Copy, Clone, Default)] +#[derive(Copy, Clone, Debug, Default)] pub struct Sink; /// Creates an instance of a writer which will successfully consume all data. /// -/// All calls to [`write`] on the returned instance will return `Ok(buf.len())` +/// All calls to [`write`] on the returned instance will return [`Ok(buf.len())`] /// and the contents of the buffer will not be inspected. /// /// [`write`]: Write::write +/// [`Ok(buf.len())`]: Ok /// /// # Examples /// @@ -259,10 +318,3 @@ impl Write for &Sink { Ok(()) } } - -#[stable(feature = "std_debug", since = "1.16.0")] -impl fmt::Debug for Sink { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Sink").finish_non_exhaustive() - } -} diff --git a/library/std/src/io/util/tests.rs b/library/std/src/io/util/tests.rs index ce5e2c9da1dbf..6de91e29c7707 100644 --- a/library/std/src/io/util/tests.rs +++ b/library/std/src/io/util/tests.rs @@ -1,67 +1,8 @@ -use crate::cmp::{max, min}; use crate::io::prelude::*; -use crate::io::{ - copy, empty, repeat, sink, BorrowedBuf, BufWriter, Empty, Repeat, Result, SeekFrom, Sink, - DEFAULT_BUF_SIZE, -}; +use crate::io::{empty, repeat, sink, BorrowedBuf, Empty, Repeat, SeekFrom, Sink}; use crate::mem::MaybeUninit; -#[test] -fn copy_copies() { - let mut r = repeat(0).take(4); - let mut w = sink(); - assert_eq!(copy(&mut r, &mut w).unwrap(), 4); - - let mut r = repeat(0).take(1 << 17); - assert_eq!(copy(&mut r as &mut dyn Read, &mut w as &mut dyn Write).unwrap(), 1 << 17); -} - -struct ShortReader { - cap: usize, - read_size: usize, - observed_buffer: usize, -} - -impl Read for ShortReader { - fn read(&mut self, buf: &mut [u8]) -> Result { - let bytes = min(self.cap, self.read_size); - self.cap -= bytes; - self.observed_buffer = max(self.observed_buffer, buf.len()); - Ok(bytes) - } -} - -struct WriteObserver { - observed_buffer: usize, -} - -impl Write for WriteObserver { - fn write(&mut self, buf: &[u8]) -> Result { - self.observed_buffer = max(self.observed_buffer, buf.len()); - Ok(buf.len()) - } - - fn flush(&mut self) -> Result<()> { - Ok(()) - } -} - -#[test] -fn copy_specializes_bufwriter() { - let cap = 117 * 1024; - let buf_sz = 16 * 1024; - let mut r = ShortReader { cap, observed_buffer: 0, read_size: 1337 }; - let mut w = BufWriter::with_capacity(buf_sz, WriteObserver { observed_buffer: 0 }); - assert_eq!( - copy(&mut r, &mut w).unwrap(), - cap as u64, - "expected the whole capacity to be copied" - ); - assert_eq!(r.observed_buffer, buf_sz, "expected a large buffer to be provided to the reader"); - assert!(w.get_mut().observed_buffer > DEFAULT_BUF_SIZE, "expected coalesced writes"); -} - #[test] fn sink_sinks() { let mut s = sink(); @@ -77,7 +18,7 @@ fn empty_reads() { assert_eq!(e.read(&mut []).unwrap(), 0); assert_eq!(e.read(&mut [0]).unwrap(), 0); assert_eq!(e.read(&mut [0; 1024]).unwrap(), 0); - assert_eq!(e.by_ref().read(&mut [0; 1024]).unwrap(), 0); + assert_eq!(Read::by_ref(&mut e).read(&mut [0; 1024]).unwrap(), 0); let buf: &mut [MaybeUninit<_>] = &mut []; let mut buf: BorrowedBuf<'_> = buf.into(); @@ -99,7 +40,7 @@ fn empty_reads() { let buf: &mut [_] = &mut [MaybeUninit::uninit(); 1024]; let mut buf: BorrowedBuf<'_> = buf.into(); - e.by_ref().read_buf(buf.unfilled()).unwrap(); + Read::by_ref(&mut e).read_buf(buf.unfilled()).unwrap(); assert_eq!(buf.len(), 0); assert_eq!(buf.init_len(), 0); } @@ -124,6 +65,15 @@ fn empty_seeks() { assert!(matches!(e.seek(SeekFrom::Current(i64::MAX)), Ok(0))); } +#[test] +fn empty_sinks() { + let mut e = empty(); + assert_eq!(e.write(&[]).unwrap(), 0); + assert_eq!(e.write(&[0]).unwrap(), 1); + assert_eq!(e.write(&[0; 1024]).unwrap(), 1024); + assert_eq!(Write::by_ref(&mut e).write(&[0; 1024]).unwrap(), 1024); +} + #[test] fn repeat_repeats() { let mut r = repeat(4); diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 318a46d1b637e..0ccbb16b1f2a4 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -188,6 +188,13 @@ //! [array]: prim@array //! [slice]: prim@slice +// To run std tests without x.py without ending up with two copies of std, Miri needs to be +// able to "empty" this crate. See . +// rustc itself never sets the feature, so this line has no effect there. +#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))] +// miri-test-libstd also prefers to make std use the sysroot versions of the dependencies. +#![cfg_attr(feature = "miri-test-libstd", feature(rustc_private))] +// #![cfg_attr(not(feature = "restricted-std"), stable(feature = "rust1", since = "1.0.0"))] #![cfg_attr(feature = "restricted-std", unstable(feature = "restricted_std", issue = "none"))] #![doc( @@ -202,12 +209,6 @@ no_global_oom_handling, not(no_global_oom_handling) ))] -// To run std tests without x.py without ending up with two copies of std, Miri needs to be -// able to "empty" this crate. See . -// rustc itself never sets the feature, so this line has no affect there. -#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))] -// miri-test-libstd also prefers to make std use the sysroot versions of the dependencies. -#![cfg_attr(feature = "miri-test-libstd", feature(rustc_private))] // Don't link to std. We are std. #![no_std] // Tell the compiler to link to either panic_abort or panic_unwind @@ -219,8 +220,10 @@ #![warn(missing_debug_implementations)] #![allow(explicit_outlives_requirements)] #![allow(unused_lifetimes)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #![deny(rustc::existing_doc_keyword)] #![deny(fuzzy_provenance_casts)] +#![cfg_attr(not(bootstrap), allow(rustdoc::redundant_explicit_links))] // Ensure that std can be linked against panic_abort despite compiled with `-C panic=unwind` #![deny(ffi_unwind_calls)] // std may use features in a platform-specific way @@ -271,6 +274,7 @@ #![feature(staged_api)] #![feature(thread_local)] #![feature(try_blocks)] +#![feature(type_alias_impl_trait)] #![feature(utf8_chunks)] // tidy-alphabetical-end // @@ -285,16 +289,17 @@ #![feature(exact_size_is_empty)] #![feature(exclusive_wrapper)] #![feature(extend_one)] +#![feature(float_gamma)] #![feature(float_minimum_maximum)] #![feature(float_next_up_down)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] -#![feature(int_roundings)] #![feature(ip)] #![feature(ip_in_core)] #![feature(maybe_uninit_slice)] #![feature(maybe_uninit_uninit_array)] #![feature(maybe_uninit_write_slice)] +#![feature(offset_of)] #![feature(panic_can_unwind)] #![feature(panic_info_message)] #![feature(panic_internals)] @@ -302,7 +307,6 @@ #![feature(pointer_is_aligned)] #![feature(portable_simd)] #![feature(prelude_2024)] -#![feature(provide_any)] #![feature(ptr_as_uninit)] #![feature(raw_os_nonzero)] #![feature(round_ties_even)] @@ -392,9 +396,15 @@ extern crate libc; #[allow(unused_extern_crates)] extern crate unwind; +// FIXME: #94122 this extern crate definition only exist here to stop +// miniz_oxide docs leaking into std docs. Find better way to do it. +// Remove exclusion from tidy platform check when this removed. #[doc(masked)] #[allow(unused_extern_crates)] -#[cfg(feature = "miniz_oxide")] +#[cfg(all( + not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))), + feature = "miniz_oxide" +))] extern crate miniz_oxide; // During testing, this crate is not actually the "real" std library, but rather @@ -541,7 +551,7 @@ pub mod time; // Pull in `std_float` crate into std. The contents of // `std_float` are in a different repository: rust-lang/portable-simd. #[path = "../../portable-simd/crates/std_float/src/lib.rs"] -#[allow(missing_debug_implementations, dead_code, unsafe_op_in_unsafe_fn, unused_unsafe)] +#[allow(missing_debug_implementations, dead_code, unsafe_op_in_unsafe_fn)] #[allow(rustdoc::bare_urls)] #[unstable(feature = "portable_simd", issue = "86656")] mod std_float; @@ -602,7 +612,6 @@ pub mod alloc; // Private support modules mod panicking; -mod personality; #[path = "../../backtrace/src/lib.rs"] #[allow(dead_code, unused_attributes, fuzzy_provenance_casts)] diff --git a/library/std/src/macros.rs b/library/std/src/macros.rs index fcc5cfafd808d..ba1b8cbfa56df 100644 --- a/library/std/src/macros.rs +++ b/library/std/src/macros.rs @@ -154,7 +154,7 @@ macro_rules! println { /// /// Panics if writing to `io::stderr` fails. /// -/// Writing to non-blocking stdout can cause an error, which will lead +/// Writing to non-blocking stderr can cause an error, which will lead /// this macro to panic. /// /// # Examples @@ -189,7 +189,7 @@ macro_rules! eprint { /// /// Panics if writing to `io::stderr` fails. /// -/// Writing to non-blocking stdout can cause an error, which will lead +/// Writing to non-blocking stderr can cause an error, which will lead /// this macro to panic. /// /// # Examples diff --git a/library/std/src/net/socket_addr/tests.rs b/library/std/src/net/socket_addr/tests.rs index dfc6dabbed1ed..6a065cfba2105 100644 --- a/library/std/src/net/socket_addr/tests.rs +++ b/library/std/src/net/socket_addr/tests.rs @@ -85,7 +85,7 @@ fn ipv6_socket_addr_to_string() { // IPv4-compatible address. assert_eq!( SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x280), 8080, 0, 0).to_string(), - "[::192.0.2.128]:8080" + "[::c000:280]:8080" ); // IPv6 address with no zero segments. diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index 141a18a42dde6..865cc7f649d1a 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -435,7 +435,7 @@ impl TcpStream { /// .expect("Couldn't connect to the server..."); /// stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger call failed"); /// ``` - #[unstable(feature = "tcp_linger", issue = "88494")] + #[stable(feature = "tcp_linger", since = "1.18.0")] pub fn set_linger(&self, linger: Option) -> io::Result<()> { self.0.set_linger(linger) } @@ -457,7 +457,7 @@ impl TcpStream { /// stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger call failed"); /// assert_eq!(stream.linger().unwrap(), Some(Duration::from_secs(0))); /// ``` - #[unstable(feature = "tcp_linger", issue = "88494")] + #[stable(feature = "tcp_linger", since = "1.18.0")] pub fn linger(&self) -> io::Result> { self.0.linger() } @@ -647,6 +647,7 @@ impl Write for TcpStream { self.0.is_write_vectored() } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } @@ -685,6 +686,7 @@ impl Write for &TcpStream { self.0.is_write_vectored() } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } @@ -826,9 +828,33 @@ impl TcpListener { pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { // On WASM, `TcpStream` is uninhabited (as it's unsupported) and so // the `a` variable here is technically unused. - #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] + #[cfg_attr(target_family = "wasm", allow(unused_variables))] self.0.accept().map(|(a, b)| (TcpStream(a), b)) } + + /// Accept a new incoming connection from this listener (or times out). + /// + /// Unlike other methods on `TcpStream`, this does not correspond to a + /// single system call. It instead uses an OS-specific mechanism to await the + /// a completion request request, then calls `accept`. + /// + /// # Examples + /// + /// ```no_run + /// use std::net::TcpListener; + /// use std::time::Duration; + /// + /// let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + /// match listener.accept_timeout(Duration::from_secs(10)) { + /// Ok((_socket, addr)) => println!("new client: {addr:?}"), + /// Err(e) => println!("couldn't get client: {e:?}"), + /// } + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn accept_timeout(&self, timeout: crate::time::Duration) -> io::Result<(TcpStream, SocketAddr)> { + #[cfg_attr(target_family = "wasm", allow(unused_variables))] + self.0.accept_timeout(timeout).map(|(a, b)| (TcpStream(a), b)) + } /// Returns an iterator over the connections being received on this /// listener. diff --git a/library/std/src/net/tcp/tests.rs b/library/std/src/net/tcp/tests.rs index 7a3c66e450456..db367cfa0f7a1 100644 --- a/library/std/src/net/tcp/tests.rs +++ b/library/std/src/net/tcp/tests.rs @@ -46,6 +46,17 @@ fn connect_error() { } } +#[test] +#[cfg_attr(target_env = "sgx", ignore)] // FIXME: https://github.com/fortanix/rust-sgx/issues/31 +fn connect_timeout_error() { + let socket_addr = next_test_ip4(); + let result = TcpStream::connect_timeout(&socket_addr, Duration::MAX); + assert!(!matches!(result, Err(e) if e.kind() == ErrorKind::TimedOut)); + + let _listener = TcpListener::bind(&socket_addr).unwrap(); + assert!(TcpStream::connect_timeout(&socket_addr, Duration::MAX).is_ok()); +} + #[test] fn listen_localhost() { let socket_addr = next_test_ip4(); diff --git a/library/std/src/os/android/raw.rs b/library/std/src/os/android/raw.rs index a255d03208623..175f8eac97176 100644 --- a/library/std/src/os/android/raw.rs +++ b/library/std/src/os/android/raw.rs @@ -21,26 +21,26 @@ pub use self::arch::{blkcnt_t, blksize_t, dev_t, ino_t, mode_t, nlink_t, off_t, #[cfg(any(target_arch = "arm", target_arch = "x86"))] mod arch { - use crate::os::raw::{c_longlong, c_uchar, c_uint, c_ulong, c_ulonglong}; + use crate::os::raw::{c_long, c_longlong, c_uchar, c_uint, c_ulong, c_ulonglong}; use crate::os::unix::raw::{gid_t, uid_t}; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type dev_t = u64; + pub type dev_t = u32; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type mode_t = u32; + pub type mode_t = c_uint; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; + pub type blkcnt_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; + pub type blksize_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; + pub type ino_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; + pub type nlink_t = u32; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; + pub type off_t = i32; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; + pub type time_t = c_long; #[repr(C)] #[derive(Clone)] @@ -70,45 +70,47 @@ mod arch { pub st_blksize: u32, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_blocks: c_ulonglong, + #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: c_ulong, + pub st_atime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_ulong, + pub st_atime_nsec: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: c_ulong, + pub st_mtime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_ulong, + pub st_mtime_nsec: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: c_ulong, + pub st_ctime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_ulong, + pub st_ctime_nsec: c_long, + #[stable(feature = "raw_ext", since = "1.1.0")] pub st_ino: c_ulonglong, } } -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] mod arch { - use crate::os::raw::{c_uchar, c_ulong}; + use crate::os::raw::{c_int, c_long, c_uint, c_ulong}; use crate::os::unix::raw::{gid_t, uid_t}; #[stable(feature = "raw_ext", since = "1.1.0")] pub type dev_t = u64; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type mode_t = u32; + pub type mode_t = c_uint; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; + pub type blkcnt_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; + pub type blksize_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; + pub type ino_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; + pub type nlink_t = u32; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; + pub type off_t = i64; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; + pub type time_t = c_long; #[repr(C)] #[derive(Clone)] @@ -117,9 +119,7 @@ mod arch { #[stable(feature = "raw_ext", since = "1.1.0")] pub st_dev: dev_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad0: [c_uchar; 4], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __st_ino: ino_t, + pub st_ino: ino_t, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_mode: mode_t, #[stable(feature = "raw_ext", since = "1.1.0")] @@ -131,27 +131,33 @@ mod arch { #[stable(feature = "raw_ext", since = "1.1.0")] pub st_rdev: dev_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad3: [c_uchar; 4], + pub __pad1: c_ulong, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_size: off_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: blksize_t, + pub st_blksize: c_int, + #[stable(feature = "raw_ext", since = "1.1.0")] + pub __pad2: c_int, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: blkcnt_t, + pub st_blocks: c_long, + #[stable(feature = "raw_ext", since = "1.1.0")] pub st_atime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_ulong, + pub st_atime_nsec: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_mtime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_ulong, + pub st_mtime_nsec: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_ctime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_ulong, + pub st_ctime_nsec: c_long, + #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: ino_t, + pub __unused4: c_uint, + #[stable(feature = "raw_ext", since = "1.1.0")] + pub __unused5: c_uint, } } @@ -163,20 +169,20 @@ mod arch { #[stable(feature = "raw_ext", since = "1.1.0")] pub type dev_t = u64; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type mode_t = u32; + pub type mode_t = c_uint; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; + pub type blkcnt_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; + pub type blksize_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; + pub type ino_t = c_ulong; #[stable(feature = "raw_ext", since = "1.1.0")] pub type nlink_t = u32; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; + pub type off_t = i64; #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; + pub type time_t = c_long; #[repr(C)] #[derive(Clone)] @@ -195,25 +201,30 @@ mod arch { #[stable(feature = "raw_ext", since = "1.1.0")] pub st_gid: gid_t, #[stable(feature = "raw_ext", since = "1.1.0")] + pub __pad0: c_uint, + #[stable(feature = "raw_ext", since = "1.1.0")] pub st_rdev: dev_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, + pub st_size: off_t, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_blksize: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] pub st_blocks: c_long, + + #[stable(feature = "raw_ext", since = "1.1.0")] + pub st_atime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: c_ulong, + pub st_atime_nsec: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_ulong, + pub st_mtime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: c_ulong, + pub st_mtime_nsec: c_long, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_ulong, + pub st_ctime: time_t, #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: c_ulong, + pub st_ctime_nsec: c_long, + #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_ulong, - __unused: [c_long; 3], + pub __pad3: [c_long; 3], } } diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 2180d2974d5ae..d73e88e65fe3a 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -9,7 +9,7 @@ use crate::fs; use crate::io; use crate::marker::PhantomData; use crate::mem::forget; -#[cfg(not(any(target_arch = "wasm32", target_env = "sgx", target_os = "hermit")))] +#[cfg(not(any(target_family = "wasm", target_env = "sgx", target_os = "hermit")))] use crate::sys::cvt; use crate::sys_common::{AsInner, FromInner, IntoInner}; @@ -89,7 +89,7 @@ impl OwnedFd { impl BorrowedFd<'_> { /// Creates a new `OwnedFd` instance that shares the same underlying file /// description as the existing `BorrowedFd` instance. - #[cfg(not(any(target_arch = "wasm32", target_os = "hermit")))] + #[cfg(not(any(target_family = "wasm", target_os = "hermit")))] #[stable(feature = "io_safety", since = "1.63.0")] pub fn try_clone_to_owned(&self) -> crate::io::Result { // We want to atomically duplicate this file descriptor and set the @@ -112,7 +112,7 @@ impl BorrowedFd<'_> { /// Creates a new `OwnedFd` instance that shares the same underlying file /// description as the existing `BorrowedFd` instance. - #[cfg(any(target_arch = "wasm32", target_os = "hermit"))] + #[cfg(any(target_family = "wasm", target_os = "hermit"))] #[stable(feature = "io_safety", since = "1.63.0")] pub fn try_clone_to_owned(&self) -> crate::io::Result { Err(crate::io::const_io_error!( diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 592e072ad908a..40a95bd51a478 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -8,7 +8,7 @@ use crate::io; use crate::os::hermit::io::OwnedFd; #[cfg(not(target_os = "hermit"))] use crate::os::raw; -#[cfg(all(doc, not(target_arch = "wasm32")))] +#[cfg(all(doc, not(target_family = "wasm")))] use crate::os::unix::io::AsFd; #[cfg(unix)] use crate::os::unix::io::OwnedFd; diff --git a/library/std/src/os/ios/fs.rs b/library/std/src/os/ios/fs.rs index 6d4d54b7c78c5..b319527a541f5 100644 --- a/library/std/src/os/ios/fs.rs +++ b/library/std/src/os/ios/fs.rs @@ -6,7 +6,7 @@ use crate::sys_common::{AsInner, AsInnerMut, IntoInner}; use crate::time::SystemTime; #[allow(deprecated)] -use crate::os::ios::raw; +use super::raw; /// OS-specific extensions to [`fs::Metadata`]. /// diff --git a/library/std/src/os/l4re/raw.rs b/library/std/src/os/l4re/raw.rs index b3f7439f8cdc0..12c0293285a5e 100644 --- a/library/std/src/os/l4re/raw.rs +++ b/library/std/src/os/l4re/raw.rs @@ -27,6 +27,7 @@ pub use self::arch::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; #[cfg(any( target_arch = "x86", target_arch = "m68k", + target_arch = "csky", target_arch = "powerpc", target_arch = "sparc", target_arch = "arm", diff --git a/library/std/src/os/linux/raw.rs b/library/std/src/os/linux/raw.rs index c55ca8ba26e2f..d57267e6b3479 100644 --- a/library/std/src/os/linux/raw.rs +++ b/library/std/src/os/linux/raw.rs @@ -27,11 +27,12 @@ pub use self::arch::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; #[cfg(any( target_arch = "x86", target_arch = "m68k", + target_arch = "csky", target_arch = "powerpc", target_arch = "sparc", target_arch = "arm", target_arch = "asmjs", - target_arch = "wasm32" + target_family = "wasm", ))] mod arch { use crate::os::raw::{c_long, c_short, c_uint}; @@ -94,7 +95,7 @@ mod arch { } } -#[cfg(target_arch = "mips")] +#[cfg(any(target_arch = "mips", target_arch = "mips32r6"))] mod arch { use crate::os::raw::{c_long, c_ulong}; @@ -233,6 +234,7 @@ mod arch { #[cfg(any( target_arch = "loongarch64", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "s390x", target_arch = "sparc64", target_arch = "riscv64", diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index 5b54cc5f2e491..cd275969c9ee4 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -18,7 +18,7 @@ pub mod raw; #[cfg(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] @@ -27,7 +27,7 @@ pub mod unix {} #[cfg(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] @@ -36,7 +36,7 @@ pub mod linux {} #[cfg(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] @@ -45,7 +45,7 @@ pub mod wasi {} #[cfg(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) ))] @@ -56,7 +56,7 @@ pub mod windows {} #[cfg(not(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) )))] @@ -67,29 +67,38 @@ pub mod unix; #[cfg(not(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) )))] #[cfg(any(target_os = "linux", doc))] pub mod linux; -// wasi +// wasi (re-exported as unix when using 'wasmer' for compatibility) #[cfg(not(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) )))] -#[cfg(any(target_os = "wasi", doc))] +#[cfg(any(all(target_os = "wasi", not(target_vendor = "wasmer")), doc))] pub mod wasi; +#[cfg(any(all(target_os = "wasi", target_vendor = "wasmer")))] +#[path = "wasix/mod.rs"] +pub mod wasi; +#[cfg(any(all(target_os = "wasi", target_vendor = "wasmer")))] +#[stable(feature = "rust1", since = "1.0.0")] +pub mod unix { + #[stable(feature = "rust1", since = "1.0.0")] + pub use super::wasi::*; +} // windows #[cfg(not(all( doc, any( - all(target_arch = "wasm32", not(target_os = "wasi")), + all(target_family = "wasm", not(target_os = "wasi")), all(target_vendor = "fortanix", target_env = "sgx") ) )))] @@ -137,6 +146,9 @@ pub mod redox; pub mod solaris; #[cfg(target_os = "solid_asp3")] pub mod solid; +#[cfg(target_os = "tvos")] +#[path = "ios/mod.rs"] +pub(crate) mod tvos; #[cfg(target_os = "vita")] pub mod vita; #[cfg(target_os = "vxworks")] diff --git a/library/std/src/os/raw/mod.rs b/library/std/src/os/raw/mod.rs index 19d0ffb2e39cb..5b302e3c2c824 100644 --- a/library/std/src/os/raw/mod.rs +++ b/library/std/src/os/raw/mod.rs @@ -9,11 +9,6 @@ macro_rules! alias_core_ffi { ($($t:ident)*) => {$( #[stable(feature = "raw_os", since = "1.1.0")] #[doc = include_str!(concat!("../../../../core/src/ffi/", stringify!($t), ".md"))] - // Make this type alias appear cfg-dependent so that Clippy does not suggest - // replacing expressions like `0 as c_char` with `0_i8`/`0_u8`. This #[cfg(all())] can be - // removed after the false positive in https://github.com/rust-lang/rust-clippy/issues/8093 - // is fixed. - #[cfg(all())] #[doc(cfg(all()))] pub type $t = core::ffi::$t; )*} diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs index 1e1c369310547..029de8fbf7602 100644 --- a/library/std/src/os/unix/fs.rs +++ b/library/std/src/os/unix/fs.rs @@ -149,7 +149,36 @@ pub trait FileExt { /// Note that similar to [`File::write`], it is not an error to return a /// short write. /// + /// # Bug + /// On some systems, `write_at` utilises [`pwrite64`] to write to files. + /// However, this syscall has a [bug] where files opened with the `O_APPEND` + /// flag fail to respect the offset parameter, always appending to the end + /// of the file instead. + /// + /// It is possible to inadvertantly set this flag, like in the example below. + /// Therefore, it is important to be vigilant while changing options to mitigate + /// unexpected behaviour. + /// + /// ```no_run + /// use std::fs::File; + /// use std::io; + /// use std::os::unix::prelude::FileExt; + /// + /// fn main() -> io::Result<()> { + /// // Open a file with the append option (sets the `O_APPEND` flag) + /// let file = File::options().append(true).open("foo.txt")?; + /// + /// // We attempt to write at offset 10; instead appended to EOF + /// file.write_at(b"sushi", 10)?; + /// + /// // foo.txt is 5 bytes long instead of 15 + /// Ok(()) + /// } + /// ``` + /// /// [`File::write`]: fs::File::write + /// [`pwrite64`]: https://man7.org/linux/man-pages/man2/pwrite.2.html + /// [bug]: https://man7.org/linux/man-pages/man2/pwrite.2.html#BUGS /// /// # Examples /// @@ -159,7 +188,7 @@ pub trait FileExt { /// use std::os::unix::prelude::FileExt; /// /// fn main() -> io::Result<()> { - /// let file = File::open("foo.txt")?; + /// let file = File::create("foo.txt")?; /// /// // We now write at the offset 10. /// file.write_at(b"sushi", 10)?; @@ -971,7 +1000,6 @@ impl DirBuilderExt for fs::DirBuilder { /// # Examples /// /// ```no_run -/// #![feature(unix_chown)] /// use std::os::unix::fs; /// /// fn main() -> std::io::Result<()> { @@ -979,7 +1007,7 @@ impl DirBuilderExt for fs::DirBuilder { /// Ok(()) /// } /// ``` -#[unstable(feature = "unix_chown", issue = "88989")] +#[stable(feature = "unix_chown", since = "1.73.0")] pub fn chown>(dir: P, uid: Option, gid: Option) -> io::Result<()> { sys::fs::chown(dir.as_ref(), uid.unwrap_or(u32::MAX), gid.unwrap_or(u32::MAX)) } @@ -991,7 +1019,6 @@ pub fn chown>(dir: P, uid: Option, gid: Option) -> io:: /// # Examples /// /// ```no_run -/// #![feature(unix_chown)] /// use std::os::unix::fs; /// /// fn main() -> std::io::Result<()> { @@ -1000,7 +1027,7 @@ pub fn chown>(dir: P, uid: Option, gid: Option) -> io:: /// Ok(()) /// } /// ``` -#[unstable(feature = "unix_chown", issue = "88989")] +#[stable(feature = "unix_chown", since = "1.73.0")] pub fn fchown(fd: F, uid: Option, gid: Option) -> io::Result<()> { sys::fs::fchown(fd.as_fd().as_raw_fd(), uid.unwrap_or(u32::MAX), gid.unwrap_or(u32::MAX)) } @@ -1013,7 +1040,6 @@ pub fn fchown(fd: F, uid: Option, gid: Option) -> io::Result< /// # Examples /// /// ```no_run -/// #![feature(unix_chown)] /// use std::os::unix::fs; /// /// fn main() -> std::io::Result<()> { @@ -1021,7 +1047,7 @@ pub fn fchown(fd: F, uid: Option, gid: Option) -> io::Result< /// Ok(()) /// } /// ``` -#[unstable(feature = "unix_chown", issue = "88989")] +#[stable(feature = "unix_chown", since = "1.73.0")] pub fn lchown>(dir: P, uid: Option, gid: Option) -> io::Result<()> { sys::fs::lchown(dir.as_ref(), uid.unwrap_or(u32::MAX), gid.unwrap_or(u32::MAX)) } diff --git a/library/std/src/os/unix/mod.rs b/library/std/src/os/unix/mod.rs index 6fe1111188aa8..401ec1e7a0130 100644 --- a/library/std/src/os/unix/mod.rs +++ b/library/std/src/os/unix/mod.rs @@ -73,6 +73,8 @@ mod platform { pub use crate::os::redox::*; #[cfg(target_os = "solaris")] pub use crate::os::solaris::*; + #[cfg(target_os = "tvos")] + pub use crate::os::tvos::*; #[cfg(target_os = "vita")] pub use crate::os::vita::*; #[cfg(target_os = "vxworks")] @@ -96,6 +98,7 @@ pub mod thread; target_os = "dragonfly", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "macos", target_os = "netbsd", diff --git a/library/std/src/os/unix/net/ancillary.rs b/library/std/src/os/unix/net/ancillary.rs index 7565fbc0d099c..218536689fdbe 100644 --- a/library/std/src/os/unix/net/ancillary.rs +++ b/library/std/src/os/unix/net/ancillary.rs @@ -11,12 +11,19 @@ use crate::slice::from_raw_parts; use crate::sys::net::Socket; // FIXME(#43348): Make libc adapt #[doc(cfg(...))] so we don't need these fake definitions here? -#[cfg(all(doc, not(target_os = "linux"), not(target_os = "android"), not(target_os = "netbsd")))] +#[cfg(all( + doc, + not(target_os = "linux"), + not(target_os = "android"), + not(target_os = "netbsd"), + not(target_os = "freebsd") +))] #[allow(non_camel_case_types)] mod libc { pub use libc::c_int; pub struct ucred; pub struct cmsghdr; + pub struct sockcred2; pub type pid_t = i32; pub type gid_t = u32; pub type uid_t = u32; diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index 5be8aebc70fd5..622aa939fc8f6 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -156,6 +156,34 @@ impl UnixListener { Ok((UnixStream(sock), addr)) } + /// Accepts a new incoming connection to this listener. + /// + /// Unlike other methods, this does not correspond to a + /// single system call. It instead uses an OS-specific mechanism to await the + /// a completion request request, then calls `accept`. + /// + /// [`UnixStream`]: crate::os::unix::net::UnixStream + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixListener; + /// + /// fn main() -> std::io::Result<()> { + /// let listener = UnixListener::bind("/path/to/the/socket")?; + /// + /// match listener.accept() { + /// Ok((socket, addr)) => println!("Got a client: {addr:?}"), + /// Err(e) => println!("accept function failed: {e:?}"), + /// } + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn accept_timeout(&self, _timeout: crate::time::Duration) -> io::Result<(UnixStream, SocketAddr)> { + self.accept() + } + /// Creates a new independently owned handle to the underlying socket. /// /// The returned `UnixListener` is a reference to the same socket that this diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index bf2a51b5edb88..41290e0017ac3 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -11,6 +11,7 @@ use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Owned target_os = "dragonfly", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "macos", target_os = "watchos", target_os = "netbsd", @@ -30,6 +31,7 @@ use crate::time::Duration; target_os = "dragonfly", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "macos", target_os = "watchos", target_os = "netbsd", @@ -238,6 +240,7 @@ impl UnixStream { target_os = "dragonfly", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "macos", target_os = "watchos", target_os = "netbsd", @@ -709,6 +712,7 @@ impl<'a> io::Write for &'a UnixStream { self.0.is_write_vectored() } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } diff --git a/library/std/src/os/unix/net/tests.rs b/library/std/src/os/unix/net/tests.rs index 39f10c50dc421..3d4302e665c20 100644 --- a/library/std/src/os/unix/net/tests.rs +++ b/library/std/src/os/unix/net/tests.rs @@ -23,6 +23,7 @@ macro_rules! or_panic { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn basic() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -93,6 +94,7 @@ fn pair() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn try_clone() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -119,6 +121,7 @@ fn try_clone() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn iter() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -168,6 +171,7 @@ fn long_path() { #[test] #[cfg(not(target_os = "nto"))] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn timeouts() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -195,6 +199,7 @@ fn timeouts() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_read_timeout() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -214,6 +219,7 @@ fn test_read_timeout() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_read_with_timeout() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -241,6 +247,7 @@ fn test_read_with_timeout() { // Ensure the `set_read_timeout` and `set_write_timeout` calls return errors // when passed zero Durations #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_stream_timeout_zero_duration() { let dir = tmpdir(); let socket_path = dir.path().join("sock"); @@ -260,6 +267,7 @@ fn test_unix_stream_timeout_zero_duration() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_datagram() { let dir = tmpdir(); let path1 = dir.path().join("sock1"); @@ -276,6 +284,7 @@ fn test_unix_datagram() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unnamed_unix_datagram() { let dir = tmpdir(); let path1 = dir.path().join("sock1"); @@ -293,6 +302,7 @@ fn test_unnamed_unix_datagram() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_datagram_connect_to_recv_addr() { let dir = tmpdir(); let path1 = dir.path().join("sock1"); @@ -317,6 +327,7 @@ fn test_unix_datagram_connect_to_recv_addr() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_connect_unix_datagram() { let dir = tmpdir(); let path1 = dir.path().join("sock1"); @@ -343,6 +354,7 @@ fn test_connect_unix_datagram() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_datagram_recv() { let dir = tmpdir(); let path1 = dir.path().join("sock1"); @@ -385,6 +397,7 @@ fn datagram_pair() { // Ensure the `set_read_timeout` and `set_write_timeout` calls return errors // when passed zero Durations #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_datagram_timeout_zero_duration() { let dir = tmpdir(); let path = dir.path().join("sock"); @@ -529,6 +542,7 @@ fn test_abstract_no_pathname_and_not_unnamed() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_stream_peek() { let (txdone, rxdone) = crate::sync::mpsc::channel(); @@ -561,6 +575,7 @@ fn test_unix_stream_peek() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_datagram_peek() { let dir = tmpdir(); let path1 = dir.path().join("sock"); @@ -585,6 +600,7 @@ fn test_unix_datagram_peek() { } #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_unix_datagram_peek_from() { let dir = tmpdir(); let path1 = dir.path().join("sock"); @@ -648,6 +664,7 @@ fn test_send_vectored_fds_unix_stream() { #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_send_vectored_with_ancillary_to_unix_datagram() { fn getpid() -> libc::pid_t { unsafe { libc::getpid() } @@ -715,6 +732,7 @@ fn test_send_vectored_with_ancillary_to_unix_datagram() { #[cfg(any(target_os = "android", target_os = "linux"))] #[test] +#[cfg_attr(target_os = "android", ignore)] // Android SELinux rules prevent creating Unix sockets fn test_send_vectored_with_ancillary_unix_datagram() { let dir = tmpdir(); let path1 = dir.path().join("sock1"); diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 729c63d184f2c..2b40b672d9f0a 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -15,7 +15,7 @@ use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; use cfg_if::cfg_if; cfg_if! { - if #[cfg(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon"))] { + if #[cfg(any(target_os = "vxworks", target_os = "espidf", target_os = "horizon", target_os = "vita"))] { type UserId = u16; type GroupId = u16; } else if #[cfg(target_os = "nto")] { diff --git a/library/std/src/os/unix/ucred.rs b/library/std/src/os/unix/ucred.rs index 95967eac29520..6a0cc2d2c48ff 100644 --- a/library/std/src/os/unix/ucred.rs +++ b/library/std/src/os/unix/ucred.rs @@ -36,7 +36,7 @@ pub use self::impl_linux::peer_cred; ))] pub use self::impl_bsd::peer_cred; -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] pub use self::impl_mac::peer_cred; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -98,7 +98,7 @@ pub mod impl_bsd { } } -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] pub mod impl_mac { use super::UCred; use crate::os::unix::io::AsRawFd; diff --git a/library/std/src/os/unix/ucred/tests.rs b/library/std/src/os/unix/ucred/tests.rs index e63a2fc248eb0..dd99ecdd819a7 100644 --- a/library/std/src/os/unix/ucred/tests.rs +++ b/library/std/src/os/unix/ucred/tests.rs @@ -8,6 +8,7 @@ use libc::{getegid, geteuid, getpid}; target_os = "dragonfly", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "macos", target_os = "watchos", target_os = "openbsd" @@ -26,7 +27,13 @@ fn test_socket_pair() { } #[test] -#[cfg(any(target_os = "linux", target_os = "ios", target_os = "macos", target_os = "watchos"))] +#[cfg(any( + target_os = "linux", + target_os = "ios", + target_os = "macos", + target_os = "watchos", + target_os = "tvos", +))] fn test_socket_pair_pids(arg: Type) -> RetType { // Create two connected sockets and get their peer credentials. let (sock_a, sock_b) = UnixStream::pair().unwrap(); diff --git a/library/std/src/os/wasi/ffi.rs b/library/std/src/os/wasi/ffi.rs index 41dd8702e98e9..d31dffc83e1c1 100644 --- a/library/std/src/os/wasi/ffi.rs +++ b/library/std/src/os/wasi/ffi.rs @@ -9,3 +9,36 @@ mod os_str; #[stable(feature = "rust1", since = "1.0.0")] pub use self::os_str::{OsStrExt, OsStringExt}; + +#[cfg(any(all(target_arch = "wasm64", target_os = "wasi")))] +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +#[cfg(any(all(target_arch = "wasm64", target_os = "wasi")))] +impl crate::ffi::OsStr +{ + #[inline] + #[stable(feature = "rust1", since = "1.0.0")] + pub fn from_bytes(slice: &[u8]) -> &crate::ffi::OsStr { + unsafe { crate::mem::transmute(slice) } + } + #[inline] + #[stable(feature = "rust1", since = "1.0.0")] + pub fn as_bytes(&self) -> &[u8] { + &self.as_inner().inner + } +} + +#[cfg(any(all(target_arch = "wasm64", target_os = "wasi")))] +impl crate::ffi::OsString +{ + #[inline] + #[stable(feature = "rust1", since = "1.0.0")] + pub fn from_vec(vec: Vec) -> crate::ffi::OsString { + FromInner::from_inner(crate::sys::os_str::Buf { inner: vec }) + } + #[inline] + #[stable(feature = "rust1", since = "1.0.0")] + pub fn into_vec(self) -> Vec { + self.into_inner().inner + } +} diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs index 160c8f1eca251..3a76384b25142 100644 --- a/library/std/src/os/wasi/fs.rs +++ b/library/std/src/os/wasi/fs.rs @@ -472,16 +472,16 @@ pub trait FileTypeExt { impl FileTypeExt for fs::FileType { fn is_block_device(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_BLOCK_DEVICE + self.as_inner().bits().raw() == wasi::FILETYPE_BLOCK_DEVICE.raw() } fn is_char_device(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_CHARACTER_DEVICE + self.as_inner().bits().raw() == wasi::FILETYPE_CHARACTER_DEVICE.raw() } fn is_socket_dgram(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_SOCKET_DGRAM + self.as_inner().bits().raw() == wasi::FILETYPE_SOCKET_DGRAM.raw() } fn is_socket_stream(&self) -> bool { - self.as_inner().bits() == wasi::FILETYPE_SOCKET_STREAM + self.as_inner().bits().raw() == wasi::FILETYPE_SOCKET_STREAM.raw() } } diff --git a/library/std/src/os/wasi/mod.rs b/library/std/src/os/wasi/mod.rs index bbaf328f457e4..4a89df888e5d0 100644 --- a/library/std/src/os/wasi/mod.rs +++ b/library/std/src/os/wasi/mod.rs @@ -36,6 +36,7 @@ pub mod ffi; pub mod fs; pub mod io; pub mod net; +pub mod process; /// A prelude for conveniently writing platform-specific code. /// diff --git a/library/std/src/os/wasi/process.rs b/library/std/src/os/wasi/process.rs new file mode 100644 index 0000000000000..4b8716a4626bf --- /dev/null +++ b/library/std/src/os/wasi/process.rs @@ -0,0 +1,121 @@ +//! Wasi-specific extensions to primitives in the [`std::process`] module. +//! +//! [`std::process`]: crate::process + +#![stable(feature = "rust1", since = "1.0.0")] +#![allow(dead_code, unused)] + +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::process; + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl FromRawFd for process::Stdio { + #[inline] + unsafe fn from_raw_fd(fd: RawFd) -> process::Stdio { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for process::Stdio { + #[inline] + fn from(fd: OwnedFd) -> process::Stdio { + unimplemented!() + } +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl AsRawFd for process::ChildStdin { + #[inline] + fn as_raw_fd(&self) -> RawFd { + unimplemented!() + } +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl AsRawFd for process::ChildStdout { + #[inline] + fn as_raw_fd(&self) -> RawFd { + unimplemented!() + } +} + +#[stable(feature = "process_extensions", since = "1.2.0")] +impl AsRawFd for process::ChildStderr { + #[inline] + fn as_raw_fd(&self) -> RawFd { + unimplemented!() + } +} + +#[stable(feature = "into_raw_os", since = "1.4.0")] +impl IntoRawFd for process::ChildStdin { + #[inline] + fn into_raw_fd(self) -> RawFd { + unimplemented!() + } +} + +#[stable(feature = "into_raw_os", since = "1.4.0")] +impl IntoRawFd for process::ChildStdout { + #[inline] + fn into_raw_fd(self) -> RawFd { + unimplemented!() + } +} + +#[stable(feature = "into_raw_os", since = "1.4.0")] +impl IntoRawFd for process::ChildStderr { + #[inline] + fn into_raw_fd(self) -> RawFd { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for crate::process::ChildStdin { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + #[inline] + fn from(child_stdin: crate::process::ChildStdin) -> OwnedFd { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for crate::process::ChildStdout { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + #[inline] + fn from(child_stdout: crate::process::ChildStdout) -> OwnedFd { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for crate::process::ChildStderr { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + unimplemented!() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + #[inline] + fn from(child_stderr: crate::process::ChildStderr) -> OwnedFd { + unimplemented!() + } +} diff --git a/library/std/src/os/wasix/mod.rs b/library/std/src/os/wasix/mod.rs new file mode 100644 index 0000000000000..797ad15bb7404 --- /dev/null +++ b/library/std/src/os/wasix/mod.rs @@ -0,0 +1,62 @@ +//! Platform-specific extensions to `std` for the WebAssembly System Interface (WASI). +//! +//! Provides access to platform-level information on WASI, and exposes +//! WASI-specific functions that would otherwise be inappropriate as +//! part of the core `std` library. +//! +//! It exposes more ways to deal with platform-specific strings (`OsStr`, +//! `OsString`), allows to set permissions more granularly, extract low-level +//! file descriptors from files and sockets, and has platform-specific helpers +//! for spawning processes. +//! +//! # Examples +//! +//! ```no_run +//! use std::fs::File; +//! use std::os::wasi::prelude::*; +//! +//! fn main() -> std::io::Result<()> { +//! let f = File::create("foo.txt")?; +//! let fd = f.as_raw_fd(); +//! +//! // use fd with native WASI bindings +//! +//! Ok(()) +//! } +//! ``` +//! +//! [`OsStr`]: crate::ffi::OsStr +//! [`OsString`]: crate::ffi::OsString + +#![stable(feature = "rust1", since = "1.0.0")] +#![deny(unsafe_op_in_unsafe_fn)] +#![doc(cfg(target_os = "wasi"))] + +#[path = "../wasi/ffi.rs"] +pub mod ffi; +#[path = "../wasi/fs.rs"] +pub mod fs; +#[path = "../wasi/io/mod.rs"] +pub mod io; +pub mod net; +#[path = "../wasi/process.rs"] +pub mod process; + +/// A prelude for conveniently writing platform-specific code. +/// +/// Includes all extension traits, and some important type definitions. +#[stable(feature = "rust1", since = "1.0.0")] +pub mod prelude { + #[doc(no_inline)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use super::ffi::{OsStrExt, OsStringExt}; + #[doc(no_inline)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use super::fs::FileTypeExt; + #[doc(no_inline)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use super::fs::{DirEntryExt, FileExt, MetadataExt, OpenOptionsExt}; + #[doc(no_inline)] + #[stable(feature = "rust1", since = "1.0.0")] + pub use super::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +} diff --git a/library/std/src/os/wasix/net/addr.rs b/library/std/src/os/wasix/net/addr.rs new file mode 100644 index 0000000000000..0f9467ab71f1f --- /dev/null +++ b/library/std/src/os/wasix/net/addr.rs @@ -0,0 +1,123 @@ +#![allow(unused_variables, dead_code)] +use crate::path::Path; +use crate::sys::cvt; +use crate::{fmt, io, mem}; + +pub(super) fn sockaddr_un(path: &Path) -> io::Result<(libc::sockaddr_un, libc::socklen_t)> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) +} + +fn sun_path_offset(addr: &libc::sockaddr_un) -> usize { + // Work with an actual instance of the type since using a null pointer is UB + let base = (addr as *const libc::sockaddr_un).addr(); + let path = (&addr.sun_path as *const libc::c_char).addr(); + path - base +} + +/// An address associated with a Unix socket. +/// +/// Not currently supported on this platform +#[derive(Clone)] +#[stable(feature = "unix_socket", since = "1.10.0")] +pub struct SocketAddr { + pub(super) addr: libc::sockaddr_un, + pub(super) len: libc::socklen_t, +} + +impl SocketAddr { + pub(super) fn new(f: F) -> io::Result + where + F: FnOnce(*mut libc::sockaddr, *mut libc::socklen_t) -> libc::c_int, + { + unsafe { + let mut addr: libc::sockaddr_un = mem::zeroed(); + let mut len = mem::size_of::() as libc::socklen_t; + cvt(f(&mut addr as *mut _ as *mut _, &mut len))?; + SocketAddr::from_parts(addr, len) + } + } + + pub(super) fn from_parts( + addr: libc::sockaddr_un, + mut len: libc::socklen_t, + ) -> io::Result { + if len == 0 { + // When there is a datagram from unnamed unix socket + // linux returns zero bytes of address + len = sun_path_offset(&addr) as libc::socklen_t; // i.e., zero-length address + } else if addr.sun_family != libc::AF_UNIX as libc::sa_family_t { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + "file descriptor did not correspond to a Unix socket", + )); + } + + Ok(SocketAddr { addr, len }) + } + + /// Constructs a `SockAddr` with the family `AF_UNIX` and the provided path. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket_creation", since = "1.61.0")] + pub fn from_pathname

(path: P) -> io::Result + where + P: AsRef, + { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns `true` if the address is unnamed. + /// + /// Not currently supported on this platform + #[must_use] + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn is_unnamed(&self) -> bool { + true + } + + /// Returns the contents of this address if it is a `pathname` address. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + #[must_use] + pub fn as_pathname(&self) -> Option<&Path> { + None + } + + /// Returns the contents of this address if it is an abstract namespace + /// without the leading null byte. + /// + /// Not currently supported on this platform + #[doc(cfg(any(target_os = "android", target_os = "linux")))] + #[cfg(any(doc, target_os = "android", target_os = "linux",))] + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn as_abstract_namespace(&self) -> Option<&[u8]> { + None + } + + /// Creates an abstract domain socket address from a namespace + /// + /// Not currently supported on this platform + #[doc(cfg(any(target_os = "android", target_os = "linux")))] + #[cfg(any(doc, target_os = "android", target_os = "linux",))] + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn from_abstract_namespace(_namespace: &[u8]) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )); + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl fmt::Debug for SocketAddr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "(unnamed)") + } +} diff --git a/library/std/src/os/wasix/net/datagram.rs b/library/std/src/os/wasix/net/datagram.rs new file mode 100644 index 0000000000000..b5693ae374df0 --- /dev/null +++ b/library/std/src/os/wasix/net/datagram.rs @@ -0,0 +1,806 @@ +use super::{sockaddr_un, SocketAddr}; +use crate::net::Shutdown; +use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::path::Path; +use crate::sys::cvt; +use crate::sys::net::Socket; +use crate::sys_common::{AsInner, FromInner, IntoInner}; +use crate::time::Duration; +use crate::{fmt, io}; + +use libc::MSG_NOSIGNAL; + +/// A Unix datagram socket. +/// +/// # Examples +/// +/// ```no_run +/// use std::os::unix::net::UnixDatagram; +/// +/// fn main() -> std::io::Result<()> { +/// let socket = UnixDatagram::bind("/path/to/my/socket")?; +/// socket.send_to(b"hello world", "/path/to/other/socket")?; +/// let mut buf = [0; 100]; +/// let (count, address) = socket.recv_from(&mut buf)?; +/// println!("socket {:?} sent {:?}", address, &buf[..count]); +/// Ok(()) +/// } +/// ``` +#[stable(feature = "unix_socket", since = "1.10.0")] +pub struct UnixDatagram(Socket); + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl fmt::Debug for UnixDatagram { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("UnixDatagram"); + builder.field("fd", self.0.as_inner()); + if let Ok(addr) = self.local_addr() { + builder.field("local", &addr); + } + if let Ok(addr) = self.peer_addr() { + builder.field("peer", &addr); + } + builder.finish() + } +} + +impl UnixDatagram { + /// Creates a Unix datagram socket bound to the given path. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// let sock = match UnixDatagram::bind("/path/to/the/socket") { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't bind: {e:?}"); + /// return + /// } + /// }; + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn bind>(path: P) -> io::Result { + unsafe { + let socket = UnixDatagram::unbound()?; + let (addr, len) = sockaddr_un(path.as_ref())?; + + cvt(libc::bind(socket.as_raw_fd(), &addr as *const _ as *const _, len as _))?; + + Ok(socket) + } + } + + /// Creates a Unix datagram socket bound to an address. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(unix_socket_abstract)] + /// use std::os::unix::net::{UnixDatagram}; + /// + /// fn main() -> std::io::Result<()> { + /// let sock1 = UnixDatagram::bind("path/to/socket")?; + /// let addr = sock1.local_addr()?; + /// + /// let sock2 = match UnixDatagram::bind_addr(&addr) { + /// Ok(sock) => sock, + /// Err(err) => { + /// println!("Couldn't bind: {err:?}"); + /// return Err(err); + /// } + /// }; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result { + unsafe { + let socket = UnixDatagram::unbound()?; + cvt(libc::bind( + socket.as_raw_fd(), + &socket_addr.addr as *const _ as *const _, + socket_addr.len as _, + ))?; + Ok(socket) + } + } + + /// Creates a Unix Datagram socket which is not bound to any address. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// let sock = match UnixDatagram::unbound() { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't unbound: {e:?}"); + /// return + /// } + /// }; + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn unbound() -> io::Result { + let inner = Socket::new_raw(libc::AF_UNIX, libc::SOCK_DGRAM)?; + Ok(UnixDatagram(inner)) + } + + /// Creates an unnamed pair of connected sockets. + /// + /// Returns two `UnixDatagrams`s which are connected to each other. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// let (sock1, sock2) = match UnixDatagram::pair() { + /// Ok((sock1, sock2)) => (sock1, sock2), + /// Err(e) => { + /// println!("Couldn't unbound: {e:?}"); + /// return + /// } + /// }; + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn pair() -> io::Result<(UnixDatagram, UnixDatagram)> { + let (i1, i2) = Socket::new_pair(libc::AF_UNIX, libc::SOCK_DGRAM)?; + Ok((UnixDatagram(i1), UnixDatagram(i2))) + } + + /// Connects the socket to the specified path address. + /// + /// The [`send`] method may be used to send data to the specified address. + /// [`recv`] and [`recv_from`] will only receive data from that address. + /// + /// [`send`]: UnixDatagram::send + /// [`recv`]: UnixDatagram::recv + /// [`recv_from`]: UnixDatagram::recv_from + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// match sock.connect("/path/to/the/socket") { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't connect: {e:?}"); + /// return Err(e) + /// } + /// }; + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn connect>(&self, path: P) -> io::Result<()> { + unsafe { + let (addr, len) = sockaddr_un(path.as_ref())?; + + cvt(libc::connect(self.as_raw_fd(), &addr as *const _ as *const _, len))?; + } + Ok(()) + } + + /// Connects the socket to an address. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(unix_socket_abstract)] + /// use std::os::unix::net::{UnixDatagram}; + /// + /// fn main() -> std::io::Result<()> { + /// let bound = UnixDatagram::bind("/path/to/socket")?; + /// let addr = bound.local_addr()?; + /// + /// let sock = UnixDatagram::unbound()?; + /// match sock.connect_addr(&addr) { + /// Ok(sock) => sock, + /// Err(e) => { + /// println!("Couldn't connect: {e:?}"); + /// return Err(e) + /// } + /// }; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn connect_addr(&self, socket_addr: &SocketAddr) -> io::Result<()> { + unsafe { + cvt(libc::connect( + self.as_raw_fd(), + &socket_addr.addr as *const _ as *const _, + socket_addr.len, + ))?; + } + Ok(()) + } + + /// Creates a new independently owned handle to the underlying socket. + /// + /// The returned `UnixDatagram` is a reference to the same socket that this + /// object references. Both handles can be used to accept incoming + /// connections and options set on one side will affect the other. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::bind("/path/to/the/socket")?; + /// let sock_copy = sock.try_clone().expect("try_clone failed"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn try_clone(&self) -> io::Result { + self.0.duplicate().map(UnixDatagram) + } + + /// Returns the address of this socket. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::bind("/path/to/the/socket")?; + /// let addr = sock.local_addr().expect("Couldn't get local address"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn local_addr(&self) -> io::Result { + SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) }) + } + + /// Returns the address of this socket's peer. + /// + /// The [`connect`] method will connect the socket to a peer. + /// + /// [`connect`]: UnixDatagram::connect + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.connect("/path/to/the/socket")?; + /// + /// let addr = sock.peer_addr().expect("Couldn't get peer address"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn peer_addr(&self) -> io::Result { + SocketAddr::new(|addr, len| unsafe { libc::getpeername(self.as_raw_fd(), addr, len) }) + } + + fn recv_from_flags( + &self, + buf: &mut [u8], + flags: libc::c_int, + ) -> io::Result<(usize, SocketAddr)> { + let mut count = 0; + let addr = SocketAddr::new(|addr, len| unsafe { + count = libc::recvfrom( + self.as_raw_fd(), + buf.as_mut_ptr() as *mut _, + buf.len(), + flags, + addr, + len, + ); + if count > 0 { + 1 + } else if count == 0 { + 0 + } else { + -1 + } + })?; + + Ok((count as usize, addr)) + } + + /// Receives data from the socket. + /// + /// On success, returns the number of bytes read and the address from + /// whence the data came. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// let mut buf = vec![0; 10]; + /// let (size, sender) = sock.recv_from(buf.as_mut_slice())?; + /// println!("received {size} bytes from {sender:?}"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.recv_from_flags(buf, 0) + } + + /// Receives data from the socket. + /// + /// On success, returns the number of bytes read. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::bind("/path/to/the/socket")?; + /// let mut buf = vec![0; 10]; + /// sock.recv(buf.as_mut_slice()).expect("recv function failed"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + + /// Sends data on the socket to the specified address. + /// + /// On success, returns the number of bytes written. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.send_to(b"omelette au fromage", "/some/sock").expect("send_to function failed"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn send_to>(&self, buf: &[u8], path: P) -> io::Result { + unsafe { + let (addr, len) = sockaddr_un(path.as_ref())?; + + let count = cvt(libc::sendto( + self.as_raw_fd(), + buf.as_ptr() as *const _, + buf.len(), + MSG_NOSIGNAL, + &addr as *const _ as *const _, + len, + ))?; + Ok(count as usize) + } + } + + /// Sends data on the socket to the specified [SocketAddr]. + /// + /// On success, returns the number of bytes written. + /// + /// [SocketAddr]: crate::os::unix::net::SocketAddr + /// + /// # Examples + /// + /// ```no_run + /// #![feature(unix_socket_abstract)] + /// use std::os::unix::net::{UnixDatagram}; + /// + /// fn main() -> std::io::Result<()> { + /// let bound = UnixDatagram::bind("/path/to/socket")?; + /// let addr = bound.local_addr()?; + /// + /// let sock = UnixDatagram::unbound()?; + /// sock.send_to_addr(b"bacon egg and cheese", &addr).expect("send_to_addr function failed"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn send_to_addr(&self, buf: &[u8], socket_addr: &SocketAddr) -> io::Result { + unsafe { + let count = cvt(libc::sendto( + self.as_raw_fd(), + buf.as_ptr() as *const _, + buf.len(), + MSG_NOSIGNAL, + &socket_addr.addr as *const _ as *const _, + socket_addr.len, + ))?; + Ok(count as usize) + } + } + + /// Sends data on the socket to the socket's peer. + /// + /// The peer address may be set by the `connect` method, and this method + /// will return an error if the socket has not already been connected. + /// + /// On success, returns the number of bytes written. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.connect("/some/sock").expect("Couldn't connect"); + /// sock.send(b"omelette au fromage").expect("send_to function failed"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn send(&self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + /// Sets the read timeout for the socket. + /// + /// If the provided value is [`None`], then [`recv`] and [`recv_from`] calls will + /// block indefinitely. An [`Err`] is returned if the zero [`Duration`] + /// is passed to this method. + /// + /// [`recv`]: UnixDatagram::recv + /// [`recv_from`]: UnixDatagram::recv_from + /// + /// # Examples + /// + /// ``` + /// use std::os::unix::net::UnixDatagram; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_read_timeout(Some(Duration::new(1, 0))) + /// .expect("set_read_timeout function failed"); + /// Ok(()) + /// } + /// ``` + /// + /// An [`Err`] is returned if the zero [`Duration`] is passed to this + /// method: + /// + /// ```no_run + /// use std::io; + /// use std::os::unix::net::UnixDatagram; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixDatagram::unbound()?; + /// let result = socket.set_read_timeout(Some(Duration::new(0, 0))); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + self.0.set_timeout(timeout, libc::SO_RCVTIMEO) + } + + /// Sets the write timeout for the socket. + /// + /// If the provided value is [`None`], then [`send`] and [`send_to`] calls will + /// block indefinitely. An [`Err`] is returned if the zero [`Duration`] is passed to this + /// method. + /// + /// [`send`]: UnixDatagram::send + /// [`send_to`]: UnixDatagram::send_to + /// + /// # Examples + /// + /// ``` + /// use std::os::unix::net::UnixDatagram; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_write_timeout(Some(Duration::new(1, 0))) + /// .expect("set_write_timeout function failed"); + /// Ok(()) + /// } + /// ``` + /// + /// An [`Err`] is returned if the zero [`Duration`] is passed to this + /// method: + /// + /// ```no_run + /// use std::io; + /// use std::os::unix::net::UnixDatagram; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixDatagram::unbound()?; + /// let result = socket.set_write_timeout(Some(Duration::new(0, 0))); + /// let err = result.unwrap_err(); + /// assert_eq!(err.kind(), io::ErrorKind::InvalidInput); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + self.0.set_timeout(timeout, libc::SO_SNDTIMEO) + } + + /// Returns the read timeout of this socket. + /// + /// # Examples + /// + /// ``` + /// use std::os::unix::net::UnixDatagram; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_read_timeout(Some(Duration::new(1, 0))) + /// .expect("set_read_timeout function failed"); + /// assert_eq!(sock.read_timeout()?, Some(Duration::new(1, 0))); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn read_timeout(&self) -> io::Result> { + self.0.timeout(libc::SO_RCVTIMEO) + } + + /// Returns the write timeout of this socket. + /// + /// # Examples + /// + /// ``` + /// use std::os::unix::net::UnixDatagram; + /// use std::time::Duration; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_write_timeout(Some(Duration::new(1, 0))) + /// .expect("set_write_timeout function failed"); + /// assert_eq!(sock.write_timeout()?, Some(Duration::new(1, 0))); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn write_timeout(&self) -> io::Result> { + self.0.timeout(libc::SO_SNDTIMEO) + } + + /// Moves the socket into or out of nonblocking mode. + /// + /// # Examples + /// + /// ``` + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_nonblocking(true).expect("set_nonblocking function failed"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + self.0.set_nonblocking(nonblocking) + } + + /// Moves the socket to pass unix credentials as control message in [`SocketAncillary`]. + /// + /// Set the socket option `SO_PASSCRED`. + /// + /// # Examples + /// + #[cfg_attr(any(target_os = "android", target_os = "linux"), doc = "```no_run")] + #[cfg_attr(not(any(target_os = "android", target_os = "linux")), doc = "```ignore")] + /// #![feature(unix_socket_ancillary_data)] + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_passcred(true).expect("set_passcred function failed"); + /// Ok(()) + /// } + /// ``` + #[cfg(any(doc, target_os = "android", target_os = "linux", target_os = "netbsd",))] + #[unstable(feature = "unix_socket_ancillary_data", issue = "76915")] + pub fn set_passcred(&self, passcred: bool) -> io::Result<()> { + self.0.set_passcred(passcred) + } + + /// Get the current value of the socket for passing unix credentials in [`SocketAncillary`]. + /// This value can be change by [`set_passcred`]. + /// + /// Get the socket option `SO_PASSCRED`. + /// + /// [`set_passcred`]: UnixDatagram::set_passcred + #[cfg(any(doc, target_os = "android", target_os = "linux", target_os = "netbsd",))] + #[unstable(feature = "unix_socket_ancillary_data", issue = "76915")] + pub fn passcred(&self) -> io::Result { + self.0.passcred() + } + + /// Set the id of the socket for network filtering purpose + /// + #[cfg_attr( + any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"), + doc = "```no_run" + )] + #[cfg_attr( + not(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd")), + doc = "```ignore" + )] + /// #![feature(unix_set_mark)] + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.set_mark(32)?; + /// Ok(()) + /// } + /// ``` + #[cfg(any(doc, target_os = "linux", target_os = "freebsd", target_os = "openbsd",))] + #[unstable(feature = "unix_set_mark", issue = "96467")] + pub fn set_mark(&self, mark: u32) -> io::Result<()> { + self.0.set_mark(mark) + } + + /// Returns the value of the `SO_ERROR` option. + /// + /// # Examples + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// if let Ok(Some(err)) = sock.take_error() { + /// println!("Got error: {err:?}"); + /// } + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn take_error(&self) -> io::Result> { + self.0.take_error() + } + + /// Shut down the read, write, or both halves of this connection. + /// + /// This function will cause all pending and future I/O calls on the + /// specified portions to immediately return with an appropriate value + /// (see the documentation of [`Shutdown`]). + /// + /// ```no_run + /// use std::os::unix::net::UnixDatagram; + /// use std::net::Shutdown; + /// + /// fn main() -> std::io::Result<()> { + /// let sock = UnixDatagram::unbound()?; + /// sock.shutdown(Shutdown::Both).expect("shutdown function failed"); + /// Ok(()) + /// } + /// ``` + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + self.0.shutdown(how) + } + + /// Receives data on the socket from the remote address to which it is + /// connected, without removing that data from the queue. On success, + /// returns the number of bytes peeked. + /// + /// Successive calls return the same data. This is accomplished by passing + /// `MSG_PEEK` as a flag to the underlying `recv` system call. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(unix_socket_peek)] + /// + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixDatagram::bind("/tmp/sock")?; + /// let mut buf = [0; 10]; + /// let len = socket.peek(&mut buf).expect("peek failed"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "unix_socket_peek", issue = "76923")] + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + self.0.peek(buf) + } + + /// Receives a single datagram message on the socket, without removing it from the + /// queue. On success, returns the number of bytes read and the origin. + /// + /// The function must be called with valid byte array `buf` of sufficient size to + /// hold the message bytes. If a message is too long to fit in the supplied buffer, + /// excess bytes may be discarded. + /// + /// Successive calls return the same data. This is accomplished by passing + /// `MSG_PEEK` as a flag to the underlying `recvfrom` system call. + /// + /// Do not use this function to implement busy waiting, instead use `libc::poll` to + /// synchronize IO events on one or more sockets. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(unix_socket_peek)] + /// + /// use std::os::unix::net::UnixDatagram; + /// + /// fn main() -> std::io::Result<()> { + /// let socket = UnixDatagram::bind("/tmp/sock")?; + /// let mut buf = [0; 10]; + /// let (len, addr) = socket.peek_from(&mut buf).expect("peek failed"); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "unix_socket_peek", issue = "76923")] + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.recv_from_flags(buf, libc::MSG_PEEK) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl AsRawFd for UnixDatagram { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.0.as_inner().as_raw_fd() + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl FromRawFd for UnixDatagram { + #[inline] + unsafe fn from_raw_fd(fd: RawFd) -> UnixDatagram { + UnixDatagram(Socket::from_inner(FromInner::from_inner(unsafe { OwnedFd::from_raw_fd(fd) }))) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl IntoRawFd for UnixDatagram { + #[inline] + fn into_raw_fd(self) -> RawFd { + self.0.into_inner().into_inner().into_raw_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for UnixDatagram { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_inner().as_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + #[inline] + fn from(unix_datagram: UnixDatagram) -> OwnedFd { + unsafe { OwnedFd::from_raw_fd(unix_datagram.into_raw_fd()) } + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for UnixDatagram { + #[inline] + fn from(owned: OwnedFd) -> Self { + unsafe { Self::from_raw_fd(owned.into_raw_fd()) } + } +} diff --git a/library/std/src/os/wasix/net/listener.rs b/library/std/src/os/wasix/net/listener.rs new file mode 100644 index 0000000000000..e3f1088019e2d --- /dev/null +++ b/library/std/src/os/wasix/net/listener.rs @@ -0,0 +1,211 @@ +//! WASI-specific networking functionality + +#![unstable(feature = "wasi_ext", issue = "71213")] +#![allow(unused_variables, dead_code)] + +use crate::io; +use crate::fmt; +use crate::path::Path; +use super::{SocketAddr, UnixStream}; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys::net::Socket; +use crate::sys_common::{AsInner, FromInner, IntoInner}; + +/// A structure representing a Unix domain socket server. +/// +/// Not currently supported on this platform +#[stable(feature = "unix_socket", since = "1.10.0")] +pub struct UnixListener(Socket); + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl fmt::Debug for UnixListener { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("UnixListener"); + builder.finish() + } +} + +impl UnixListener { + /// Creates a new `UnixListener` bound to the specified socket. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn bind>(_path: P) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Creates a new `UnixListener` bound to the specified [`socket address`]. + /// + /// [`socket address`]: crate::os::wasi::net::SocketAddr + /// + /// Not currently supported on this platform + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Accepts a new incoming connection to this listener. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Accepts a new incoming connection to this listener (or times out). + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn accept_timeout(&self, _timeout: crate::time::Duration) -> io::Result<(UnixStream, SocketAddr)> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Creates a new independently owned handle to the underlying socket. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn try_clone(&self) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns the local socket address of this listener. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn local_addr(&self) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Moves the socket into or out of nonblocking mode. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns the value of the `SO_ERROR` option. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn take_error(&self) -> io::Result> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns an iterator over incoming connections. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn incoming(&self) -> Incoming<'_> { + Incoming { listener: self } + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl AsRawFd for UnixListener { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.0.as_inner().as_raw_fd() + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl FromRawFd for UnixListener { + #[inline] + unsafe fn from_raw_fd(fd: RawFd) -> UnixListener { + unsafe { + UnixListener(Socket::from_inner(FromInner::from_inner(OwnedFd::from_raw_fd(fd)))) + } + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl IntoRawFd for UnixListener { + #[inline] + fn into_raw_fd(self) -> RawFd { + self.0.into_inner().into_inner().into_raw_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for UnixListener { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_inner().as_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for UnixListener { + #[inline] + fn from(fd: OwnedFd) -> UnixListener { + UnixListener(Socket::from_inner(FromInner::from_inner(fd))) + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + #[inline] + fn from(listener: UnixListener) -> OwnedFd { + listener.0.into_inner().into_inner() + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl<'a> IntoIterator for &'a UnixListener { + type Item = io::Result; + type IntoIter = Incoming<'a>; + + fn into_iter(self) -> Incoming<'a> { + self.incoming() + } +} + +/// An iterator over incoming connections to a [`UnixListener`]. +/// +/// It will never return [`None`]. +/// +/// Not currently supported on this platform +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +#[stable(feature = "unix_socket", since = "1.10.0")] +pub struct Incoming<'a> { + listener: &'a UnixListener, +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl<'a> Iterator for Incoming<'a> { + type Item = io::Result; + + fn next(&mut self) -> Option> { + Some(self.listener.accept().map(|s| s.0)) + } + + fn size_hint(&self) -> (usize, Option) { + (usize::MAX, None) + } +} diff --git a/library/std/src/os/wasix/net/mod.rs b/library/std/src/os/wasix/net/mod.rs new file mode 100644 index 0000000000000..a735053d52ad1 --- /dev/null +++ b/library/std/src/os/wasix/net/mod.rs @@ -0,0 +1,17 @@ +//! WASI-specific networking functionality + +#![stable(feature = "rust1", since = "1.0.0")] + +mod addr; +mod datagram; +mod listener; +mod stream; + +#[stable(feature = "unix_socket", since = "1.10.0")] +pub use self::addr::*; +#[stable(feature = "unix_socket", since = "1.10.0")] +pub use self::datagram::*; +#[stable(feature = "unix_socket", since = "1.10.0")] +pub use self::listener::*; +#[stable(feature = "unix_socket", since = "1.10.0")] +pub use self::stream::*; diff --git a/library/std/src/os/wasix/net/stream.rs b/library/std/src/os/wasix/net/stream.rs new file mode 100644 index 0000000000000..1e75692e3bc85 --- /dev/null +++ b/library/std/src/os/wasix/net/stream.rs @@ -0,0 +1,315 @@ +#![allow(unused_variables, dead_code)] +use super::SocketAddr; +use crate::fmt; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::net::Shutdown; +use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::path::Path; +use crate::time::Duration; + +/// A Unix stream socket. +/// +/// Not supported on this platform +#[stable(feature = "unix_socket", since = "1.10.0")] +pub struct UnixStream(crate::sys::fs::File); + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl fmt::Debug for UnixStream { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = fmt.debug_struct("UnixStream"); + builder.finish() + } +} + +impl UnixStream { + /// Connects to the socket named by `path`. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn connect>(path: P) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Connects to the socket specified by [`address`]. + /// + /// Not currently supported on this platform + #[unstable(feature = "unix_socket_abstract", issue = "85410")] + pub fn connect_addr(socket_addr: &SocketAddr) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Creates an unnamed pair of connected sockets. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn pair() -> io::Result<(UnixStream, UnixStream)> { + use crate::sys::err2io; + use crate::sys::fd::WasiFd; + + let (fd1, fd2) = unsafe { wasi::fd_pipe().map_err(err2io)? }; + unsafe { + let fd1 = UnixStream(crate::sys::fs::File { fd: WasiFd::from_raw_fd(fd1 as RawFd) }); + let fd2 = UnixStream(crate::sys::fs::File { fd: WasiFd::from_raw_fd(fd2 as RawFd) }); + Ok((fd1, fd2)) + } + } + + /// Creates a new independently owned handle to the underlying socket. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn try_clone(&self) -> io::Result { + self.0.duplicate().map(UnixStream) + } + + /// Returns the socket address of the local half of this connection. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn local_addr(&self) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns the socket address of the remote half of this connection. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn peer_addr(&self) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Sets the read timeout for the socket. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Sets the write timeout for the socket. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns the read timeout of this socket. + /// + /// Not currently supported on this platforn + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn read_timeout(&self) -> io::Result> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Returns the write timeout of this socket. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn write_timeout(&self) -> io::Result> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Moves the socket into or out of nonblocking mode. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + use crate::sys::err2io; + + let fdstat = unsafe { wasi::fd_fdstat_get(self.0.fd.as_raw_fd() as u32).map_err(err2io)? }; + + let mut flags = fdstat.fs_flags; + + if nonblocking { + flags |= wasi::FDFLAGS_NONBLOCK; + } else { + flags &= !wasi::FDFLAGS_NONBLOCK; + } + + unsafe { + wasi::fd_fdstat_set_flags(self.0.fd.as_raw_fd() as u32, flags).map_err(err2io)?; + } + + Ok(()) + } + + /// Returns the value of the `SO_ERROR` option. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn take_error(&self) -> io::Result> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Shuts down the read, write, or both halves of this connection. + /// + /// Not currently supported on this platform + #[stable(feature = "unix_socket", since = "1.10.0")] + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } + + /// Receives data on the socket from the remote address to which it is + /// connected, without removing that data from the queue. On success, + /// returns the number of bytes peeked. + /// + /// Not currently supported on this platform + #[unstable(feature = "unix_socket_peek", issue = "76923")] + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + Err(crate::io::const_io_error!( + crate::io::ErrorKind::Unsupported, + "unix sockets are not supported on this platform", + )) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl io::Read for UnixStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + io::Read::read(&mut &*self, buf) + } + + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + io::Read::read_vectored(&mut &*self, bufs) + } + + #[inline] + fn is_read_vectored(&self) -> bool { + io::Read::is_read_vectored(&&*self) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl<'a> io::Read for &'a UnixStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.0.read_vectored(bufs) + } + + #[inline] + fn is_read_vectored(&self) -> bool { + self.0.is_read_vectored() + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl io::Write for UnixStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + io::Write::write(&mut &*self, buf) + } + + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + io::Write::write_vectored(&mut &*self, bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + io::Write::is_write_vectored(&&*self) + } + + fn flush(&mut self) -> io::Result<()> { + io::Write::flush(&mut &*self) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl<'a> io::Write for &'a UnixStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result { + self.0.write_vectored(bufs) + } + + #[inline] + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl AsRawFd for UnixStream { + #[inline] + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl FromRawFd for UnixStream { + #[inline] + unsafe fn from_raw_fd(fd: RawFd) -> UnixStream { + Self(unsafe { FromRawFd::from_raw_fd(fd) }) + } +} + +#[stable(feature = "unix_socket", since = "1.10.0")] +impl IntoRawFd for UnixStream { + #[inline] + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl AsFd for UnixStream { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for OwnedFd { + #[inline] + fn from(unix_stream: UnixStream) -> OwnedFd { + unsafe { OwnedFd::from_raw_fd(unix_stream.into_raw_fd()) } + } +} + +#[stable(feature = "io_safety", since = "1.63.0")] +impl From for UnixStream { + #[inline] + fn from(owned: OwnedFd) -> Self { + unsafe { Self::from_raw_fd(owned.into_raw_fd()) } + } +} diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index cbf8209a5ad43..274af08a3881c 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -437,7 +437,7 @@ impl AsHandle for &mut T { } } -#[stable(feature = "as_windows_ptrs", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "as_windows_ptrs", since = "1.71.0")] /// This impl allows implementing traits that require `AsHandle` on Arc. /// ``` /// # #[cfg(windows)] mod group_cfg { @@ -457,7 +457,7 @@ impl AsHandle for crate::sync::Arc { } } -#[stable(feature = "as_windows_ptrs", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsHandle for crate::rc::Rc { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { @@ -465,7 +465,7 @@ impl AsHandle for crate::rc::Rc { } } -#[stable(feature = "as_windows_ptrs", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsHandle for Box { #[inline] fn as_handle(&self) -> BorrowedHandle<'_> { diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs index 0c90d55c024fb..6359835cad5d9 100644 --- a/library/std/src/os/windows/io/socket.rs +++ b/library/std/src/os/windows/io/socket.rs @@ -254,7 +254,7 @@ impl AsSocket for &mut T { } } -#[stable(feature = "as_windows_ptrs", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "as_windows_ptrs", since = "1.71.0")] /// This impl allows implementing traits that require `AsSocket` on Arc. /// ``` /// # #[cfg(windows)] mod group_cfg { @@ -274,7 +274,7 @@ impl AsSocket for crate::sync::Arc { } } -#[stable(feature = "as_windows_ptrs", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsSocket for crate::rc::Rc { #[inline] fn as_socket(&self) -> BorrowedSocket<'_> { @@ -282,7 +282,7 @@ impl AsSocket for crate::rc::Rc { } } -#[stable(feature = "as_windows_ptrs", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "as_windows_ptrs", since = "1.71.0")] impl AsSocket for Box { #[inline] fn as_socket(&self) -> BorrowedSocket<'_> { diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index 6d59266b6f838..a0c21f7045409 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -234,6 +234,7 @@ where *hook = Hook::Custom(Box::new(move |info| hook_fn(&prev, info))); } +/// The default panic handler. fn default_hook(info: &PanicInfo<'_>) { // If this is a double panic, make sure that we print a backtrace // for this panic. Otherwise only print it if logging is enabled. @@ -257,7 +258,7 @@ fn default_hook(info: &PanicInfo<'_>) { let name = thread.as_ref().and_then(|t| t.name()).unwrap_or(""); let write = |err: &mut dyn crate::io::Write| { - let _ = writeln!(err, "thread '{name}' panicked at '{msg}', {location}"); + let _ = writeln!(err, "thread '{name}' panicked at {location}:\n{msg}"); static FIRST_PANIC: AtomicBool = AtomicBool::new(true); @@ -272,7 +273,8 @@ fn default_hook(info: &PanicInfo<'_>) { if FIRST_PANIC.swap(false, Ordering::SeqCst) { let _ = writeln!( err, - "note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + "note: run with `RUST_BACKTRACE=1` environment variable to display a \ + backtrace" ); } } @@ -298,8 +300,18 @@ pub mod panic_count { pub const ALWAYS_ABORT_FLAG: usize = 1 << (usize::BITS - 1); - // Panic count for the current thread. - thread_local! { static LOCAL_PANIC_COUNT: Cell = const { Cell::new(0) } } + /// A reason for forcing an immediate abort on panic. + #[derive(Debug)] + pub enum MustAbort { + AlwaysAbort, + PanicInHook, + } + + // Panic count for the current thread and whether a panic hook is currently + // being executed.. + thread_local! { + static LOCAL_PANIC_COUNT: Cell<(usize, bool)> = const { Cell::new((0, false)) } + } // Sum of panic counts from all threads. The purpose of this is to have // a fast path in `count_is_zero` (which is used by `panicking`). In any particular @@ -328,34 +340,39 @@ pub mod panic_count { // panicking thread consumes at least 2 bytes of address space. static GLOBAL_PANIC_COUNT: AtomicUsize = AtomicUsize::new(0); - // Return the state of the ALWAYS_ABORT_FLAG and number of panics. + // Increases the global and local panic count, and returns whether an + // immediate abort is required. // - // If ALWAYS_ABORT_FLAG is not set, the number is determined on a per-thread - // base (stored in LOCAL_PANIC_COUNT), i.e. it is the amount of recursive calls - // of the calling thread. - // If ALWAYS_ABORT_FLAG is set, the number equals the *global* number of panic - // calls. See above why LOCAL_PANIC_COUNT is not used. - pub fn increase() -> (bool, usize) { + // This also updates thread-local state to keep track of whether a panic + // hook is currently executing. + pub fn increase(run_panic_hook: bool) -> Option { let global_count = GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed); - let must_abort = global_count & ALWAYS_ABORT_FLAG != 0; - let panics = if must_abort { - global_count & !ALWAYS_ABORT_FLAG - } else { - LOCAL_PANIC_COUNT.with(|c| { - let next = c.get() + 1; - c.set(next); - next - }) - }; - (must_abort, panics) + if global_count & ALWAYS_ABORT_FLAG != 0 { + return Some(MustAbort::AlwaysAbort); + } + + LOCAL_PANIC_COUNT.with(|c| { + let (count, in_panic_hook) = c.get(); + if in_panic_hook { + return Some(MustAbort::PanicInHook); + } + c.set((count + 1, run_panic_hook)); + None + }) + } + + pub fn finished_panic_hook() { + LOCAL_PANIC_COUNT.with(|c| { + let (count, _) = c.get(); + c.set((count, false)); + }); } pub fn decrease() { GLOBAL_PANIC_COUNT.fetch_sub(1, Ordering::Relaxed); LOCAL_PANIC_COUNT.with(|c| { - let next = c.get() - 1; - c.set(next); - next + let (count, _) = c.get(); + c.set((count - 1, false)); }); } @@ -366,7 +383,7 @@ pub mod panic_count { // Disregards ALWAYS_ABORT_FLAG #[must_use] pub fn get_count() -> usize { - LOCAL_PANIC_COUNT.with(|c| c.get()) + LOCAL_PANIC_COUNT.with(|c| c.get().0) } // Disregards ALWAYS_ABORT_FLAG @@ -394,7 +411,7 @@ pub mod panic_count { #[inline(never)] #[cold] fn is_zero_slow_path() -> bool { - LOCAL_PANIC_COUNT.with(|c| c.get() == 0) + LOCAL_PANIC_COUNT.with(|c| c.get().0 == 0) } } @@ -655,23 +672,22 @@ fn rust_panic_with_hook( location: &Location<'_>, can_unwind: bool, ) -> ! { - let (must_abort, panics) = panic_count::increase(); - - // If this is the third nested call (e.g., panics == 2, this is 0-indexed), - // the panic hook probably triggered the last panic, otherwise the - // double-panic check would have aborted the process. In this case abort the - // process real quickly as we don't want to try calling it again as it'll - // probably just panic again. - if must_abort || panics > 2 { - if panics > 2 { - // Don't try to print the message in this case - // - perhaps that is causing the recursive panics. - rtprintpanic!("thread panicked while processing panic. aborting.\n"); - } else { - // Unfortunately, this does not print a backtrace, because creating - // a `Backtrace` will allocate, which we must to avoid here. - let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind); - rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n"); + let must_abort = panic_count::increase(true); + + // Check if we need to abort immediately. + if let Some(must_abort) = must_abort { + match must_abort { + panic_count::MustAbort::PanicInHook => { + // Don't try to print the message in this case + // - perhaps that is causing the recursive panics. + rtprintpanic!("thread panicked while processing panic. aborting.\n"); + } + panic_count::MustAbort::AlwaysAbort => { + // Unfortunately, this does not print a backtrace, because creating + // a `Backtrace` will allocate, which we must to avoid here. + let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind); + rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n"); + } } crate::sys::abort_internal(); } @@ -697,16 +713,16 @@ fn rust_panic_with_hook( }; drop(hook); - if panics > 1 || !can_unwind { - // If a thread panics while it's already unwinding then we - // have limited options. Currently our preference is to - // just abort. In the future we may consider resuming - // unwinding or otherwise exiting the thread cleanly. - if !can_unwind { - rtprintpanic!("thread caused non-unwinding panic. aborting.\n"); - } else { - rtprintpanic!("thread panicked while panicking. aborting.\n"); - } + // Indicate that we have finished executing the panic hook. After this point + // it is fine if there is a panic while executing destructors, as long as it + // it contained within a `catch_unwind`. + panic_count::finished_panic_hook(); + + if !can_unwind { + // If a thread panics while running destructors or tries to unwind + // through a nounwind function (e.g. extern "C") then we cannot continue + // unwinding and have to abort immediately. + rtprintpanic!("thread caused non-unwinding panic. aborting.\n"); crate::sys::abort_internal(); } @@ -716,7 +732,7 @@ fn rust_panic_with_hook( /// This is the entry point for `resume_unwind`. /// It just forwards the payload to the panic runtime. pub fn rust_panic_without_hook(payload: Box) -> ! { - panic_count::increase(); + panic_count::increase(false); struct RewrapBox(Box); diff --git a/library/std/src/path.rs b/library/std/src/path.rs index febdeb514634c..5842c096f1ab7 100644 --- a/library/std/src/path.rs +++ b/library/std/src/path.rs @@ -193,7 +193,7 @@ impl<'a> Prefix<'a> { fn len(&self) -> usize { use self::Prefix::*; fn os_str_len(s: &OsStr) -> usize { - s.bytes().len() + s.as_os_str_bytes().len() } match *self { Verbatim(x) => 4 + os_str_len(x), @@ -299,20 +299,6 @@ where } } -unsafe fn u8_slice_as_os_str(s: &[u8]) -> &OsStr { - // SAFETY: See note at the top of this module to understand why this and - // `OsStr::bytes` are used: - // - // This casts are safe as OsStr is internally a wrapper around [u8] on all - // platforms. - // - // Note that currently this relies on the special knowledge that std has; - // these types are single-element structs but are not marked - // repr(transparent) or repr(C) which would make these casts not allowable - // outside std. - unsafe { &*(s as *const [u8] as *const OsStr) } -} - // Detect scheme on Redox fn has_redox_scheme(s: &[u8]) -> bool { cfg!(target_os = "redox") && s.contains(&b':') @@ -330,7 +316,7 @@ fn has_physical_root(s: &[u8], prefix: Option>) -> bool { // basic workhorse for splitting stem and extension fn rsplit_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) { - if file.bytes() == b".." { + if file.as_os_str_bytes() == b".." { return (Some(file), None); } @@ -338,18 +324,23 @@ fn rsplit_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) { // and back. This is safe to do because (1) we only look at ASCII // contents of the encoding and (2) new &OsStr values are produced // only from ASCII-bounded slices of existing &OsStr values. - let mut iter = file.bytes().rsplitn(2, |b| *b == b'.'); + let mut iter = file.as_os_str_bytes().rsplitn(2, |b| *b == b'.'); let after = iter.next(); let before = iter.next(); if before == Some(b"") { (Some(file), None) } else { - unsafe { (before.map(|s| u8_slice_as_os_str(s)), after.map(|s| u8_slice_as_os_str(s))) } + unsafe { + ( + before.map(|s| OsStr::from_os_str_bytes_unchecked(s)), + after.map(|s| OsStr::from_os_str_bytes_unchecked(s)), + ) + } } } fn split_file_at_dot(file: &OsStr) -> (&OsStr, Option<&OsStr>) { - let slice = file.bytes(); + let slice = file.as_os_str_bytes(); if slice == b".." { return (file, None); } @@ -364,7 +355,12 @@ fn split_file_at_dot(file: &OsStr) -> (&OsStr, Option<&OsStr>) { }; let before = &slice[..i]; let after = &slice[i + 1..]; - unsafe { (u8_slice_as_os_str(before), Some(u8_slice_as_os_str(after))) } + unsafe { + ( + OsStr::from_os_str_bytes_unchecked(before), + Some(OsStr::from_os_str_bytes_unchecked(after)), + ) + } } //////////////////////////////////////////////////////////////////////////////// @@ -743,7 +739,7 @@ impl<'a> Components<'a> { // separately via `include_cur_dir` b".." => Some(Component::ParentDir), b"" => None, - _ => Some(Component::Normal(unsafe { u8_slice_as_os_str(comp) })), + _ => Some(Component::Normal(unsafe { OsStr::from_os_str_bytes_unchecked(comp) })), } } @@ -900,7 +896,7 @@ impl<'a> Iterator for Components<'a> { let raw = &self.path[..self.prefix_len()]; self.path = &self.path[self.prefix_len()..]; return Some(Component::Prefix(PrefixComponent { - raw: unsafe { u8_slice_as_os_str(raw) }, + raw: unsafe { OsStr::from_os_str_bytes_unchecked(raw) }, parsed: self.prefix.unwrap(), })); } @@ -972,7 +968,7 @@ impl<'a> DoubleEndedIterator for Components<'a> { State::Prefix if self.prefix_len() > 0 => { self.back = State::Done; return Some(Component::Prefix(PrefixComponent { - raw: unsafe { u8_slice_as_os_str(self.path) }, + raw: unsafe { OsStr::from_os_str_bytes_unchecked(self.path) }, parsed: self.prefix.unwrap(), })); } @@ -1162,12 +1158,12 @@ impl FusedIterator for Ancestors<'_> {} /// Which method works best depends on what kind of situation you're in. #[cfg_attr(not(test), rustc_diagnostic_item = "PathBuf")] #[stable(feature = "rust1", since = "1.0.0")] -// FIXME: // `PathBuf::as_mut_vec` current implementation relies // on `PathBuf` being layout-compatible with `Vec`. -// When attribute privacy is implemented, `PathBuf` should be annotated as `#[repr(transparent)]`. -// Anyway, `PathBuf` representation and layout are considered implementation detail, are -// not documented and must not be relied upon. +// However, `PathBuf` layout is considered an implementation detail and must not be relied upon. We +// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under +// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. +#[cfg_attr(not(doc), repr(transparent))] pub struct PathBuf { inner: OsString, } @@ -1481,17 +1477,17 @@ impl PathBuf { fn _set_extension(&mut self, extension: &OsStr) -> bool { let file_stem = match self.file_stem() { None => return false, - Some(f) => f.bytes(), + Some(f) => f.as_os_str_bytes(), }; // truncate until right after the file stem let end_file_stem = file_stem[file_stem.len()..].as_ptr().addr(); - let start = self.inner.bytes().as_ptr().addr(); + let start = self.inner.as_os_str_bytes().as_ptr().addr(); let v = self.as_mut_vec(); v.truncate(end_file_stem.wrapping_sub(start)); // add the new extension, if any - let new = extension.bytes(); + let new = extension.as_os_str_bytes(); if !new.is_empty() { v.reserve_exact(new.len() + 1); v.push(b'.'); @@ -1987,12 +1983,12 @@ impl AsRef for PathBuf { /// ``` #[cfg_attr(not(test), rustc_diagnostic_item = "Path")] #[stable(feature = "rust1", since = "1.0.0")] -// FIXME: // `Path::new` current implementation relies // on `Path` being layout-compatible with `OsStr`. -// When attribute privacy is implemented, `Path` should be annotated as `#[repr(transparent)]`. -// Anyway, `Path` representation and layout are considered implementation detail, are -// not documented and must not be relied upon. +// However, `Path` layout is considered an implementation detail and must not be relied upon. We +// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under +// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. +#[cfg_attr(not(doc), repr(transparent))] pub struct Path { inner: OsStr, } @@ -2011,11 +2007,11 @@ impl Path { // The following (private!) function allows construction of a path from a u8 // slice, which is only safe when it is known to follow the OsStr encoding. unsafe fn from_u8_slice(s: &[u8]) -> &Path { - unsafe { Path::new(u8_slice_as_os_str(s)) } + unsafe { Path::new(OsStr::from_os_str_bytes_unchecked(s)) } } // The following (private!) function reveals the byte encoding used for OsStr. fn as_u8_slice(&self) -> &[u8] { - self.inner.bytes() + self.inner.as_os_str_bytes() } /// Directly wraps a string slice as a `Path` slice. @@ -2612,9 +2608,27 @@ impl Path { } fn _with_extension(&self, extension: &OsStr) -> PathBuf { - let mut buf = self.to_path_buf(); - buf.set_extension(extension); - buf + let self_len = self.as_os_str().len(); + let self_bytes = self.as_os_str().as_os_str_bytes(); + + let (new_capacity, slice_to_copy) = match self.extension() { + None => { + // Enough capacity for the extension and the dot + let capacity = self_len + extension.len() + 1; + let whole_path = self_bytes.iter(); + (capacity, whole_path) + } + Some(previous_extension) => { + let capacity = self_len + extension.len() - previous_extension.len(); + let path_till_dot = self_bytes[..self_len - previous_extension.len()].iter(); + (capacity, path_till_dot) + } + }; + + let mut new_path = PathBuf::with_capacity(new_capacity); + new_path.as_mut_vec().extend(slice_to_copy); + new_path.set_extension(extension); + new_path } /// Produces an iterator over the [`Component`]s of the path. diff --git a/library/std/src/path/tests.rs b/library/std/src/path/tests.rs index dd307022c6d05..f12ffbf2e0166 100644 --- a/library/std/src/path/tests.rs +++ b/library/std/src/path/tests.rs @@ -1183,7 +1183,7 @@ pub fn test_prefix_ext() { #[test] pub fn test_push() { macro_rules! tp ( - ($path:expr, $push:expr, $expected:expr) => ( { + ($path:expr, $push:expr, $expected:expr) => ({ let mut actual = PathBuf::from($path); actual.push($push); assert!(actual.to_str() == Some($expected), @@ -1281,7 +1281,7 @@ pub fn test_push() { #[test] pub fn test_pop() { macro_rules! tp ( - ($path:expr, $expected:expr, $output:expr) => ( { + ($path:expr, $expected:expr, $output:expr) => ({ let mut actual = PathBuf::from($path); let output = actual.pop(); assert!(actual.to_str() == Some($expected) && output == $output, @@ -1335,7 +1335,7 @@ pub fn test_pop() { #[test] pub fn test_set_file_name() { macro_rules! tfn ( - ($path:expr, $file:expr, $expected:expr) => ( { + ($path:expr, $file:expr, $expected:expr) => ({ let mut p = PathBuf::from($path); p.set_file_name($file); assert!(p.to_str() == Some($expected), @@ -1369,7 +1369,7 @@ pub fn test_set_file_name() { #[test] pub fn test_set_extension() { macro_rules! tfe ( - ($path:expr, $ext:expr, $expected:expr, $output:expr) => ( { + ($path:expr, $ext:expr, $expected:expr, $output:expr) => ({ let mut p = PathBuf::from($path); let output = p.set_extension($ext); assert!(p.to_str() == Some($expected) && output == $output, @@ -1394,6 +1394,46 @@ pub fn test_set_extension() { tfe!("/", "foo", "/", false); } +#[test] +pub fn test_with_extension() { + macro_rules! twe ( + ($input:expr, $extension:expr, $expected:expr) => ({ + let input = Path::new($input); + let output = input.with_extension($extension); + + assert!( + output.to_str() == Some($expected), + "calling Path::new({:?}).with_extension({:?}): Expected {:?}, got {:?}", + $input, $extension, $expected, output, + ); + }); + ); + + twe!("foo", "txt", "foo.txt"); + twe!("foo.bar", "txt", "foo.txt"); + twe!("foo.bar.baz", "txt", "foo.bar.txt"); + twe!(".test", "txt", ".test.txt"); + twe!("foo.txt", "", "foo"); + twe!("foo", "", "foo"); + twe!("", "foo", ""); + twe!(".", "foo", "."); + twe!("foo/", "bar", "foo.bar"); + twe!("foo/.", "bar", "foo.bar"); + twe!("..", "foo", ".."); + twe!("foo/..", "bar", "foo/.."); + twe!("/", "foo", "/"); + + // New extension is smaller than file name + twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb"); + // New extension is greater than file name + twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa"); + + // New extension is smaller than previous extension + twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.bbb_bbb"); + // New extension is greater than previous extension + twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.aaa_aaa_aaa"); +} + #[test] fn test_eq_receivers() { use crate::borrow::Cow; @@ -1669,7 +1709,7 @@ fn into_rc() { #[test] fn test_ord() { macro_rules! ord( - ($ord:ident, $left:expr, $right:expr) => ( { + ($ord:ident, $left:expr, $right:expr) => ({ use core::cmp::Ordering; let left = Path::new($left); diff --git a/library/std/src/personality.rs b/library/std/src/personality.rs deleted file mode 100644 index 63f0ad4f16e34..0000000000000 --- a/library/std/src/personality.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! This module contains the implementation of the `eh_personality` lang item. -//! -//! The actual implementation is heavily dependent on the target since Rust -//! tries to use the native stack unwinding mechanism whenever possible. -//! -//! This personality function is still required with `-C panic=abort` because -//! it is used to catch foreign exceptions from `extern "C-unwind"` and turn -//! them into aborts. -//! -//! Additionally, ARM EHABI uses the personality function when generating -//! backtraces. - -mod dwarf; - -#[cfg(not(test))] -cfg_if::cfg_if! { - if #[cfg(target_os = "emscripten")] { - mod emcc; - } else if #[cfg(target_env = "msvc")] { - // This is required by the compiler to exist (e.g., it's a lang item), - // but it's never actually called by the compiler because - // _CxxFrameHandler3 is the personality function that is always used. - // Hence this is just an aborting stub. - #[lang = "eh_personality"] - fn rust_eh_personality() { - core::intrinsics::abort() - } - } else if #[cfg(any( - all(target_family = "windows", target_env = "gnu"), - target_os = "psp", - target_os = "solid_asp3", - all(target_family = "unix", not(target_os = "espidf")), - all(target_vendor = "fortanix", target_env = "sgx"), - ))] { - mod gcc; - } else { - // Targets that don't support unwinding. - // - family=wasm - // - os=none ("bare metal" targets) - // - os=uefi - // - os=espidf - // - os=hermit - // - nvptx64-nvidia-cuda - // - arch=avr - } -} diff --git a/library/std/src/primitive_docs.rs b/library/std/src/primitive_docs.rs index 8266e899011cb..80289ca08c3fc 100644 --- a/library/std/src/primitive_docs.rs +++ b/library/std/src/primitive_docs.rs @@ -308,7 +308,7 @@ mod prim_never {} /// /// ```no_run /// // Undefined behaviour -/// unsafe { char::from_u32_unchecked(0x110000) }; +/// let _ = unsafe { char::from_u32_unchecked(0x110000) }; /// ``` /// /// USVs are also the exact set of values that may be encoded in UTF-8. Because diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 9da74a5ddb574..7380b45b00fea 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -280,6 +280,7 @@ impl Write for ChildStdin { io::Write::is_write_vectored(&&*self) } + #[inline] fn flush(&mut self) -> io::Result<()> { (&*self).flush() } @@ -299,6 +300,7 @@ impl Write for &ChildStdin { self.inner.is_write_vectored() } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } @@ -558,6 +560,14 @@ impl Command { /// but this has some implementation limitations on Windows /// (see issue #37519). /// + /// # Platform-specific behavior + /// + /// Note on Windows: For executable files with the .exe extension, + /// it can be omitted when specifying the program for this Command. + /// However, if the file has a different extension, + /// a filename including the extension needs to be provided, + /// otherwise the file won't be found. + /// /// # Examples /// /// Basic usage: @@ -1524,6 +1534,15 @@ impl From for Stdio { #[stable(feature = "process", since = "1.0.0")] pub struct ExitStatus(imp::ExitStatus); +/// The default value is one which indicates successful completion. +#[stable(feature = "process-exitcode-default", since = "1.73.0")] +impl Default for ExitStatus { + fn default() -> Self { + // Ideally this would be done by ExitCode::default().into() but that is complicated. + ExitStatus::from_inner(imp::ExitStatus::default()) + } +} + /// Allows extension traits within `std`. #[unstable(feature = "sealed", issue = "none")] impl crate::sealed::Sealed for ExitStatus {} @@ -1904,8 +1923,8 @@ impl FromInner for ExitCode { } impl Child { - /// Forces the child process to exit. If the child has already exited, an [`InvalidInput`] - /// error is returned. + /// Forces the child process to exit. If the child has already exited, `Ok(())` + /// is returned. /// /// The mapping to [`ErrorKind`]s is not part of the compatibility contract of the function. /// @@ -1920,7 +1939,7 @@ impl Child { /// /// let mut command = Command::new("yes"); /// if let Ok(mut child) = command.spawn() { - /// child.kill().expect("command wasn't running"); + /// child.kill().expect("command couldn't be killed"); /// } else { /// println!("yes command didn't start"); /// } diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index d7f4d335de3e3..366b591466c07 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -582,3 +582,18 @@ fn run_canonical_bat_script() { assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!"); } + +#[test] +fn terminate_exited_process() { + let mut cmd = if cfg!(target_os = "android") { + let mut p = shell_cmd(); + p.args(&["-c", "true"]); + p + } else { + known_command() + }; + let mut p = cmd.stdout(Stdio::null()).spawn().unwrap(); + p.wait().unwrap(); + assert!(p.kill().is_ok()); + assert!(p.kill().is_ok()); +} diff --git a/library/std/src/rt.rs b/library/std/src/rt.rs index f1eeb75be7c4d..ca24494bae28a 100644 --- a/library/std/src/rt.rs +++ b/library/std/src/rt.rs @@ -15,6 +15,7 @@ #![doc(hidden)] #![deny(unsafe_op_in_unsafe_fn)] #![allow(unused_macros)] +#![cfg_attr(target_vendor = "wasmer", allow(dead_code))] use crate::ffi::CString; diff --git a/library/std/src/sync/barrier.rs b/library/std/src/sync/barrier.rs index 11836b7b694b3..ed3c55120847f 100644 --- a/library/std/src/sync/barrier.rs +++ b/library/std/src/sync/barrier.rs @@ -13,9 +13,10 @@ use crate::sync::{Condvar, Mutex}; /// use std::sync::{Arc, Barrier}; /// use std::thread; /// -/// let mut handles = Vec::with_capacity(10); -/// let barrier = Arc::new(Barrier::new(10)); -/// for _ in 0..10 { +/// let n = 10; +/// let mut handles = Vec::with_capacity(n); +/// let barrier = Arc::new(Barrier::new(n)); +/// for _ in 0..n { /// let c = Arc::clone(&barrier); /// // The same messages will be printed together. /// // You will NOT see any interleaving. @@ -105,9 +106,10 @@ impl Barrier { /// use std::sync::{Arc, Barrier}; /// use std::thread; /// - /// let mut handles = Vec::with_capacity(10); - /// let barrier = Arc::new(Barrier::new(10)); - /// for _ in 0..10 { + /// let n = 10; + /// let mut handles = Vec::with_capacity(n); + /// let barrier = Arc::new(Barrier::new(n)); + /// for _ in 0..n { /// let c = Arc::clone(&barrier); /// // The same messages will be printed together. /// // You will NOT see any interleaving. @@ -128,11 +130,8 @@ impl Barrier { let local_gen = lock.generation_id; lock.count += 1; if lock.count < self.num_threads { - // We need a while loop to guard against spurious wakeups. - // https://en.wikipedia.org/wiki/Spurious_wakeup - while local_gen == lock.generation_id { - lock = self.cvar.wait(lock).unwrap(); - } + let _guard = + self.cvar.wait_while(lock, |state| local_gen == state.generation_id).unwrap(); BarrierWaitResult(false) } else { lock.count = 0; diff --git a/library/std/src/sync/condvar.rs b/library/std/src/sync/condvar.rs index 76a1b4a2a86cd..9c4b926b7ecdc 100644 --- a/library/std/src/sync/condvar.rs +++ b/library/std/src/sync/condvar.rs @@ -21,11 +21,11 @@ impl WaitTimeoutResult { /// /// # Examples /// - /// This example spawns a thread which will update the boolean value and - /// then wait 100 milliseconds before notifying the condvar. + /// This example spawns a thread which will sleep 20 milliseconds before + /// updating a boolean value and then notifying the condvar. /// - /// The main thread will wait with a timeout on the condvar and then leave - /// once the boolean has been updated and notified. + /// The main thread will wait with a 10 millisecond timeout on the condvar + /// and will leave the loop upon timeout. /// /// ``` /// use std::sync::{Arc, Condvar, Mutex}; @@ -49,14 +49,12 @@ impl WaitTimeoutResult { /// /// // Wait for the thread to start up. /// let (lock, cvar) = &*pair; - /// let mut started = lock.lock().unwrap(); /// loop { /// // Let's put a timeout on the condvar's wait. - /// let result = cvar.wait_timeout(started, Duration::from_millis(10)).unwrap(); - /// // 10 milliseconds have passed, or maybe the value changed! - /// started = result.0; - /// if *started == true { - /// // We received the notification and the value has been updated, we can leave. + /// let result = cvar.wait_timeout(lock.lock().unwrap(), Duration::from_millis(10)).unwrap(); + /// // 10 milliseconds have passed. + /// if result.1.timed_out() { + /// // timed out now and we can leave. /// break /// } /// } diff --git a/library/std/src/sync/lazy_lock.rs b/library/std/src/sync/lazy_lock.rs index a6bc468b09281..3598598cfa02a 100644 --- a/library/std/src/sync/lazy_lock.rs +++ b/library/std/src/sync/lazy_lock.rs @@ -25,6 +25,8 @@ union Data { /// /// # Examples /// +/// Initialize static variables with `LazyLock`. +/// /// ``` /// #![feature(lazy_cell)] /// @@ -54,6 +56,24 @@ union Data { /// // Some("Hoyten") /// } /// ``` +/// Initialize fields with `LazyLock`. +/// ``` +/// #![feature(lazy_cell)] +/// +/// use std::sync::LazyLock; +/// +/// #[derive(Debug)] +/// struct UseCellLock { +/// number: LazyLock, +/// } +/// fn main() { +/// let lock: LazyLock = LazyLock::new(|| 0u32); +/// +/// let data = UseCellLock { number: lock }; +/// println!("{}", *data.number); +/// } +/// ``` + #[unstable(feature = "lazy_cell", issue = "109736")] pub struct LazyLock T> { once: Once, @@ -69,6 +89,15 @@ impl T> LazyLock { LazyLock { once: Once::new(), data: UnsafeCell::new(Data { f: ManuallyDrop::new(f) }) } } + /// Creates a new lazy value that is already initialized. + #[inline] + #[cfg(test)] + pub(crate) fn preinit(value: T) -> LazyLock { + let once = Once::new(); + once.call_once(|| {}); + LazyLock { once, data: UnsafeCell::new(Data { value: ManuallyDrop::new(value) }) } + } + /// Consumes this `LazyLock` returning the stored value. /// /// Returns `Ok(value)` if `Lazy` is initialized and `Err(f)` otherwise. @@ -193,10 +222,12 @@ impl Default for LazyLock { #[unstable(feature = "lazy_cell", issue = "109736")] impl fmt::Debug for LazyLock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_tuple("LazyLock"); match self.get() { - Some(v) => f.debug_tuple("LazyLock").field(v).finish(), - None => f.write_str("LazyLock(Uninit)"), - } + Some(v) => d.field(v), + None => d.field(&format_args!("")), + }; + d.finish() } } diff --git a/library/std/src/sync/mpmc/utils.rs b/library/std/src/sync/mpmc/utils.rs index d053d69e26eee..0cbc61160f7ee 100644 --- a/library/std/src/sync/mpmc/utils.rs +++ b/library/std/src/sync/mpmc/utils.rs @@ -35,7 +35,9 @@ use crate::ops::{Deref, DerefMut}; any( target_arch = "arm", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "riscv64", ), repr(align(32)) @@ -59,7 +61,9 @@ use crate::ops::{Deref, DerefMut}; target_arch = "powerpc64", target_arch = "arm", target_arch = "mips", + target_arch = "mips32r6", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "riscv64", target_arch = "s390x", )), diff --git a/library/std/src/sync/mpmc/waker.rs b/library/std/src/sync/mpmc/waker.rs index 4912ca4f8150b..9aab1b9417edb 100644 --- a/library/std/src/sync/mpmc/waker.rs +++ b/library/std/src/sync/mpmc/waker.rs @@ -66,26 +66,32 @@ impl Waker { /// Attempts to find another thread's entry, select the operation, and wake it up. #[inline] pub(crate) fn try_select(&mut self) -> Option { - self.selectors - .iter() - .position(|selector| { - // Does the entry belong to a different thread? - selector.cx.thread_id() != current_thread_id() - && selector // Try selecting this operation. - .cx - .try_select(Selected::Operation(selector.oper)) - .is_ok() - && { - // Provide the packet. - selector.cx.store_packet(selector.packet); - // Wake the thread up. - selector.cx.unpark(); - true - } - }) - // Remove the entry from the queue to keep it clean and improve - // performance. - .map(|pos| self.selectors.remove(pos)) + if self.selectors.is_empty() { + None + } else { + let thread_id = current_thread_id(); + + self.selectors + .iter() + .position(|selector| { + // Does the entry belong to a different thread? + selector.cx.thread_id() != thread_id + && selector // Try selecting this operation. + .cx + .try_select(Selected::Operation(selector.oper)) + .is_ok() + && { + // Provide the packet. + selector.cx.store_packet(selector.packet); + // Wake the thread up. + selector.cx.unpark(); + true + } + }) + // Remove the entry from the queue to keep it clean and improve + // performance. + .map(|pos| self.selectors.remove(pos)) + } } /// Notifies all operations waiting to be ready. diff --git a/library/std/src/sync/mpsc/mod.rs b/library/std/src/sync/mpsc/mod.rs index 0e0c87d1c748e..f92bb1a4b1f7d 100644 --- a/library/std/src/sync/mpsc/mod.rs +++ b/library/std/src/sync/mpsc/mod.rs @@ -303,12 +303,11 @@ pub struct IntoIter { rx: Receiver, } -/// The sending-half of Rust's asynchronous [`channel`] type. This half can only be -/// owned by one thread, but it can be cloned to send to other threads. +/// The sending-half of Rust's asynchronous [`channel`] type. /// /// Messages can be sent through this channel with [`send`]. /// -/// Note: all senders (the original and the clones) need to be dropped for the receiver +/// Note: all senders (the original and its clones) need to be dropped for the receiver /// to stop blocking to receive messages with [`Receiver::recv`]. /// /// [`send`]: Sender::send @@ -347,8 +346,8 @@ pub struct Sender { #[stable(feature = "rust1", since = "1.0.0")] unsafe impl Send for Sender {} -#[stable(feature = "rust1", since = "1.0.0")] -impl !Sync for Sender {} +#[stable(feature = "mpsc_sender_sync", since = "1.72.0")] +unsafe impl Sync for Sender {} /// The sending-half of Rust's synchronous [`sync_channel`] type. /// diff --git a/library/std/src/sync/mutex.rs b/library/std/src/sync/mutex.rs index b8fec6902a08c..b4ae6b7e07ebc 100644 --- a/library/std/src/sync/mutex.rs +++ b/library/std/src/sync/mutex.rs @@ -490,13 +490,7 @@ impl fmt::Debug for Mutex { d.field("data", &&**err.get_ref()); } Err(TryLockError::WouldBlock) => { - struct LockedPlaceholder; - impl fmt::Debug for LockedPlaceholder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("") - } - } - d.field("data", &LockedPlaceholder); + d.field("data", &format_args!("")); } } d.field("poisoned", &self.poison.get()); diff --git a/library/std/src/sync/once.rs b/library/std/src/sync/once.rs index 1b17c31089ff2..8c46080e43b38 100644 --- a/library/std/src/sync/once.rs +++ b/library/std/src/sync/once.rs @@ -91,7 +91,7 @@ impl Once { /// return). /// /// If the given closure recursively invokes `call_once` on the same [`Once`] - /// instance the exact behavior is not specified, allowed outcomes are + /// instance, the exact behavior is not specified: allowed outcomes are /// a panic or a deadlock. /// /// # Examples diff --git a/library/std/src/sync/once_lock.rs b/library/std/src/sync/once_lock.rs index e83bc35ee9846..e2b7b893cb5a5 100644 --- a/library/std/src/sync/once_lock.rs +++ b/library/std/src/sync/once_lock.rs @@ -365,10 +365,12 @@ impl Default for OnceLock { #[stable(feature = "once_cell", since = "1.70.0")] impl fmt::Debug for OnceLock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_tuple("OnceLock"); match self.get() { - Some(v) => f.debug_tuple("Once").field(v).finish(), - None => f.write_str("Once(Uninit)"), - } + Some(v) => d.field(v), + None => d.field(&format_args!("")), + }; + d.finish() } } diff --git a/library/std/src/sync/rwlock.rs b/library/std/src/sync/rwlock.rs index 7c409cb3e9776..26aaa2414c979 100644 --- a/library/std/src/sync/rwlock.rs +++ b/library/std/src/sync/rwlock.rs @@ -485,13 +485,7 @@ impl fmt::Debug for RwLock { d.field("data", &&**err.get_ref()); } Err(TryLockError::WouldBlock) => { - struct LockedPlaceholder; - impl fmt::Debug for LockedPlaceholder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("") - } - } - d.field("data", &LockedPlaceholder); + d.field("data", &format_args!("")); } } d.field("poisoned", &self.poison.get()); diff --git a/library/std/src/sys/common/alloc.rs b/library/std/src/sys/common/alloc.rs index a5fcbdf39c6c6..d58aa6c27b8cd 100644 --- a/library/std/src/sys/common/alloc.rs +++ b/library/std/src/sys/common/alloc.rs @@ -8,7 +8,9 @@ use crate::ptr; target_arch = "x86", target_arch = "arm", target_arch = "m68k", + target_arch = "csky", target_arch = "mips", + target_arch = "mips32r6", target_arch = "powerpc", target_arch = "powerpc64", target_arch = "sparc", @@ -24,6 +26,7 @@ pub const MIN_ALIGN: usize = 8; target_arch = "aarch64", target_arch = "loongarch64", target_arch = "mips64", + target_arch = "mips64r6", target_arch = "s390x", target_arch = "sparc64", target_arch = "riscv64", diff --git a/library/std/src/sys/common/cvt.rs b/library/std/src/sys/common/cvt.rs new file mode 100644 index 0000000000000..4a5fbe4351a46 --- /dev/null +++ b/library/std/src/sys/common/cvt.rs @@ -0,0 +1,31 @@ +#[doc(hidden)] +pub trait IsMinusOne { + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) +} + +impl_is_minus_one! { i8 i16 i32 i64 isize } + +pub fn cvt(t: T) -> crate::io::Result { + if t.is_minus_one() { Err(crate::io::Error::last_os_error()) } else { Ok(t) } +} + +pub fn cvt_r(mut f: F) -> crate::io::Result +where + T: IsMinusOne, + F: FnMut() -> T, +{ + loop { + match cvt(f()) { + Err(ref e) if e.kind() == crate::io::ErrorKind::Interrupted => {} + other => return other, + } + } +} diff --git a/library/std/src/sys/common/mod.rs b/library/std/src/sys/common/mod.rs index 2b8782ddf4482..01d926ceb0aac 100644 --- a/library/std/src/sys/common/mod.rs +++ b/library/std/src/sys/common/mod.rs @@ -11,6 +11,8 @@ #![allow(dead_code)] pub mod alloc; + +pub mod cvt; pub mod small_c_string; pub mod thread_local; diff --git a/library/std/src/sys/common/small_c_string.rs b/library/std/src/sys/common/small_c_string.rs index 01acd5191351c..963d17a47e4c0 100644 --- a/library/std/src/sys/common/small_c_string.rs +++ b/library/std/src/sys/common/small_c_string.rs @@ -19,7 +19,7 @@ pub fn run_path_with_cstr(path: &Path, f: F) -> io::Result where F: FnOnce(&CStr) -> io::Result, { - run_with_cstr(path.as_os_str().bytes(), f) + run_with_cstr(path.as_os_str().as_os_str_bytes(), f) } #[inline] diff --git a/library/std/src/sys/common/tests.rs b/library/std/src/sys/common/tests.rs index fb6f5d6af8371..0a1cbcbe8ef37 100644 --- a/library/std/src/sys/common/tests.rs +++ b/library/std/src/sys/common/tests.rs @@ -8,7 +8,7 @@ use core::iter::repeat; fn stack_allocation_works() { let path = Path::new("abc"); let result = run_path_with_cstr(path, |p| { - assert_eq!(p, &*CString::new(path.as_os_str().bytes()).unwrap()); + assert_eq!(p, &*CString::new(path.as_os_str().as_os_str_bytes()).unwrap()); Ok(42) }); assert_eq!(result.unwrap(), 42); @@ -25,7 +25,7 @@ fn heap_allocation_works() { let path = repeat("a").take(384).collect::(); let path = Path::new(&path); let result = run_path_with_cstr(path, |p| { - assert_eq!(p, &*CString::new(path.as_os_str().bytes()).unwrap()); + assert_eq!(p, &*CString::new(path.as_os_str().as_os_str_bytes()).unwrap()); Ok(42) }); assert_eq!(result.unwrap(), 42); diff --git a/library/std/src/sys/common/thread_local/fast_local.rs b/library/std/src/sys/common/thread_local/fast_local.rs index 447044a798ba6..c0a9619bf7bfa 100644 --- a/library/std/src/sys/common/thread_local/fast_local.rs +++ b/library/std/src/sys/common/thread_local/fast_local.rs @@ -33,20 +33,21 @@ pub macro thread_local_inner { // 1 == dtor registered, dtor not run // 2 == dtor registered and is running or has run #[thread_local] - static mut STATE: $crate::primitive::u8 = 0; + static STATE: $crate::cell::Cell<$crate::primitive::u8> = $crate::cell::Cell::new(0); + // Safety: Performs `drop_in_place(ptr as *mut $t)`, and requires + // all that comes with it. unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { - let ptr = ptr as *mut $t; - - unsafe { - $crate::debug_assert_eq!(STATE, 1); - STATE = 2; - $crate::ptr::drop_in_place(ptr); - } + $crate::thread::local_impl::abort_on_dtor_unwind(|| { + let old_state = STATE.replace(2); + $crate::debug_assert_eq!(old_state, 1); + // Safety: safety requirement is passed on to caller. + unsafe { $crate::ptr::drop_in_place(ptr.cast::<$t>()); } + }); } unsafe { - match STATE { + match STATE.get() { // 0 == we haven't registered a destructor, so do // so now. 0 => { @@ -54,7 +55,7 @@ pub macro thread_local_inner { $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, destroy, ); - STATE = 1; + STATE.set(1); $crate::option::Option::Some(&VAL) } // 1 == the destructor is registered and the value @@ -86,10 +87,6 @@ pub macro thread_local_inner { static __KEY: $crate::thread::local_impl::Key<$t> = $crate::thread::local_impl::Key::<$t>::new(); - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] unsafe { __KEY.get(move || { if let $crate::option::Option::Some(init) = init { @@ -148,7 +145,6 @@ impl fmt::Debug for Key { f.debug_struct("Key").finish_non_exhaustive() } } - impl Key { pub const fn new() -> Key { Key { inner: LazyKeyInner::new(), dtor_state: Cell::new(DtorState::Unregistered) } diff --git a/library/std/src/sys/common/thread_local/mod.rs b/library/std/src/sys/common/thread_local/mod.rs index 77f645883102c..975509bd412b0 100644 --- a/library/std/src/sys/common/thread_local/mod.rs +++ b/library/std/src/sys/common/thread_local/mod.rs @@ -101,3 +101,24 @@ mod lazy { } } } + +/// Run a callback in a scenario which must not unwind (such as a `extern "C" +/// fn` declared in a user crate). If the callback unwinds anyway, then +/// `rtabort` with a message about thread local panicking on drop. +#[inline] +pub fn abort_on_dtor_unwind(f: impl FnOnce()) { + // Using a guard like this is lower cost. + let guard = DtorUnwindGuard; + f(); + core::mem::forget(guard); + + struct DtorUnwindGuard; + impl Drop for DtorUnwindGuard { + #[inline] + fn drop(&mut self) { + // This is not terribly descriptive, but it doesn't need to be as we'll + // already have printed a panic message at this point. + rtabort!("thread local panicked on drop"); + } + } +} diff --git a/library/std/src/sys/common/thread_local/os_local.rs b/library/std/src/sys/common/thread_local/os_local.rs index 5d48ce1e03bc3..7cf291921228b 100644 --- a/library/std/src/sys/common/thread_local/os_local.rs +++ b/library/std/src/sys/common/thread_local/os_local.rs @@ -24,7 +24,6 @@ pub macro thread_local_inner { const fn __init() -> $t { INIT_EXPR } static __KEY: $crate::thread::local_impl::Key<$t> = $crate::thread::local_impl::Key::new(); - #[allow(unused_unsafe)] unsafe { __KEY.get(move || { if let $crate::option::Option::Some(init) = _init { @@ -59,10 +58,6 @@ pub macro thread_local_inner { static __KEY: $crate::thread::local_impl::Key<$t> = $crate::thread::local_impl::Key::new(); - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] unsafe { __KEY.get(move || { if let $crate::option::Option::Some(init) = init { diff --git a/library/std/src/sys/common/thread_local/static_local.rs b/library/std/src/sys/common/thread_local/static_local.rs index 80322a978646b..5cb6c541a0ec5 100644 --- a/library/std/src/sys/common/thread_local/static_local.rs +++ b/library/std/src/sys/common/thread_local/static_local.rs @@ -43,10 +43,6 @@ pub macro thread_local_inner { static __KEY: $crate::thread::local_impl::Key<$t> = $crate::thread::local_impl::Key::new(); - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] unsafe { __KEY.get(move || { if let $crate::option::Option::Some(init) = init { diff --git a/library/std/src/sys/hermit/fs.rs b/library/std/src/sys/hermit/fs.rs index 4bb735668d24c..6aa4ea7f5b4fb 100644 --- a/library/std/src/sys/hermit/fs.rs +++ b/library/std/src/sys/hermit/fs.rs @@ -335,6 +335,7 @@ impl File { false } + #[inline] pub fn flush(&self) -> io::Result<()> { Ok(()) } diff --git a/library/std/src/sys/hermit/os.rs b/library/std/src/sys/hermit/os.rs index e53dbae611981..c79197a9ad1fa 100644 --- a/library/std/src/sys/hermit/os.rs +++ b/library/std/src/sys/hermit/os.rs @@ -112,6 +112,34 @@ pub struct Env { iter: vec::IntoIter<(OsString, OsString)>, } +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + slice: &'a [(OsString, OsString)], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap()))) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + f.debug_list().entries(iter.as_slice()).finish() + } +} + impl !Send for Env {} impl !Sync for Env {} diff --git a/library/std/src/sys/hermit/thread.rs b/library/std/src/sys/hermit/thread.rs index 2507f70695173..97384d1a29a6b 100644 --- a/library/std/src/sys/hermit/thread.rs +++ b/library/std/src/sys/hermit/thread.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] -use super::unsupported; use crate::ffi::CStr; use crate::io; use crate::mem; @@ -60,6 +59,11 @@ impl Thread { Thread::new_with_coreid(stack, p, -1 /* = no specific core */) } + pub unsafe fn new_reactor(p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + unsupported() + } + #[inline] pub fn yield_now() { unsafe { @@ -99,7 +103,7 @@ impl Thread { } pub fn available_parallelism() -> io::Result { - unsupported() + unsafe { Ok(NonZeroUsize::new_unchecked(abi::get_processor_count())) } } pub mod guard { diff --git a/library/std/src/sys/hermit/time.rs b/library/std/src/sys/hermit/time.rs index 5440d85df4adf..7d91460aba3ee 100644 --- a/library/std/src/sys/hermit/time.rs +++ b/library/std/src/sys/hermit/time.rs @@ -40,7 +40,7 @@ impl Timespec { } fn checked_add_duration(&self, other: &Duration) -> Option { - let mut secs = self.tv_sec.checked_add_unsigned(other.as_secs())?; + let mut secs = self.t.tv_sec.checked_add_unsigned(other.as_secs())?; // Nano calculations can't overflow because nanos are <1B which fit // in a u32. @@ -53,7 +53,7 @@ impl Timespec { } fn checked_sub_duration(&self, other: &Duration) -> Option { - let mut secs = self.tv_sec.checked_sub_unsigned(other.as_secs())?; + let mut secs = self.t.tv_sec.checked_sub_unsigned(other.as_secs())?; // Similar to above, nanos can't overflow. let mut nsec = self.t.tv_nsec as i32 - other.subsec_nanos() as i32; diff --git a/library/std/src/sys/itron/thread.rs b/library/std/src/sys/itron/thread.rs index ae0f718535b26..8f9eb37f64f27 100644 --- a/library/std/src/sys/itron/thread.rs +++ b/library/std/src/sys/itron/thread.rs @@ -195,6 +195,11 @@ impl Thread { Ok(Self { p_inner, task: new_task }) } + pub unsafe fn new_reactor(p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + unsupported() + } + pub fn yield_now() { expect_success(unsafe { abi::rot_rdq(abi::TPRI_SELF) }, &"rot_rdq"); } diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index c72be13804d20..8a4ac6c646633 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -23,6 +23,7 @@ #![allow(missing_debug_implementations)] pub mod common; +mod personality; cfg_if::cfg_if! { if #[cfg(unix)] { @@ -37,7 +38,11 @@ cfg_if::cfg_if! { } else if #[cfg(target_os = "hermit")] { mod hermit; pub use self::hermit::*; - } else if #[cfg(target_os = "wasi")] { + } else if #[cfg(all(target_vendor = "wasmer", target_os = "wasi"))] { + #[path = "wasix/mod.rs"] + mod wasi; + pub use self::wasi::*; + } else if #[cfg(all(target_family = "wasm", target_os = "wasi"))] { mod wasi; pub use self::wasi::*; } else if #[cfg(target_family = "wasm")] { @@ -60,3 +65,52 @@ cfg_if::cfg_if! { pub const FULL_BACKTRACE_DEFAULT: bool = false; } } + +#[cfg(not(test))] +cfg_if::cfg_if! { + if #[cfg(target_os = "android")] { + pub use self::android::log2f32; + pub use self::android::log2f64; + } else { + #[inline] + pub fn log2f32(n: f32) -> f32 { + unsafe { crate::intrinsics::log2f32(n) } + } + + #[inline] + pub fn log2f64(n: f64) -> f64 { + unsafe { crate::intrinsics::log2f64(n) } + } + } +} + +// Solaris/Illumos requires a wrapper around log, log2, and log10 functions +// because of their non-standard behavior (e.g., log(-n) returns -Inf instead +// of expected NaN). +#[cfg(not(test))] +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +#[inline] +pub fn log_wrapper f64>(n: f64, log_fn: F) -> f64 { + if n.is_finite() { + if n > 0.0 { + log_fn(n) + } else if n == 0.0 { + f64::NEG_INFINITY // log(0) = -Inf + } else { + f64::NAN // log(-n) = NaN + } + } else if n.is_nan() { + n // log(NaN) = NaN + } else if n > 0.0 { + n // log(Inf) = Inf + } else { + f64::NAN // log(-Inf) = NaN + } +} + +#[cfg(not(test))] +#[cfg(not(any(target_os = "solaris", target_os = "illumos")))] +#[inline] +pub fn log_wrapper f64>(n: f64, log_fn: F) -> f64 { + log_fn(n) +} diff --git a/library/std/src/personality/dwarf/eh.rs b/library/std/src/sys/personality/dwarf/eh.rs similarity index 100% rename from library/std/src/personality/dwarf/eh.rs rename to library/std/src/sys/personality/dwarf/eh.rs diff --git a/library/std/src/personality/dwarf/mod.rs b/library/std/src/sys/personality/dwarf/mod.rs similarity index 100% rename from library/std/src/personality/dwarf/mod.rs rename to library/std/src/sys/personality/dwarf/mod.rs diff --git a/library/std/src/personality/dwarf/tests.rs b/library/std/src/sys/personality/dwarf/tests.rs similarity index 100% rename from library/std/src/personality/dwarf/tests.rs rename to library/std/src/sys/personality/dwarf/tests.rs diff --git a/library/std/src/personality/emcc.rs b/library/std/src/sys/personality/emcc.rs similarity index 100% rename from library/std/src/personality/emcc.rs rename to library/std/src/sys/personality/emcc.rs diff --git a/library/std/src/personality/gcc.rs b/library/std/src/sys/personality/gcc.rs similarity index 97% rename from library/std/src/personality/gcc.rs rename to library/std/src/sys/personality/gcc.rs index 82edb11cbd146..e477a0cd7ab81 100644 --- a/library/std/src/personality/gcc.rs +++ b/library/std/src/sys/personality/gcc.rs @@ -59,9 +59,17 @@ const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 / X0, X1 #[cfg(target_arch = "m68k")] const UNWIND_DATA_REG: (i32, i32) = (0, 1); // D0, D1 -#[cfg(any(target_arch = "mips", target_arch = "mips64"))] +#[cfg(any( + target_arch = "mips", + target_arch = "mips32r6", + target_arch = "mips64", + target_arch = "mips64r6" +))] const UNWIND_DATA_REG: (i32, i32) = (4, 5); // A0, A1 +#[cfg(target_arch = "csky")] +const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 + #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4 / X3, X4 @@ -85,7 +93,7 @@ const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c cfg_if::cfg_if! { - if #[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "watchos"), not(target_os = "netbsd")))] { + if #[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "tvos"), not(target_os = "watchos"), not(target_os = "netbsd")))] { // ARM EHABI personality routine. // https://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf // diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs new file mode 100644 index 0000000000000..386a399f53228 --- /dev/null +++ b/library/std/src/sys/personality/mod.rs @@ -0,0 +1,46 @@ +//! This module contains the implementation of the `eh_personality` lang item. +//! +//! The actual implementation is heavily dependent on the target since Rust +//! tries to use the native stack unwinding mechanism whenever possible. +//! +//! This personality function is still required with `-C panic=abort` because +//! it is used to catch foreign exceptions from `extern "C-unwind"` and turn +//! them into aborts. +//! +//! Additionally, ARM EHABI uses the personality function when generating +//! backtraces. + +mod dwarf; + +#[cfg(not(test))] +cfg_if::cfg_if! { + if #[cfg(target_os = "emscripten")] { + mod emcc; + } else if #[cfg(target_env = "msvc")] { + // This is required by the compiler to exist (e.g., it's a lang item), + // but it's never actually called by the compiler because + // _CxxFrameHandler3 is the personality function that is always used. + // Hence this is just an aborting stub. + #[lang = "eh_personality"] + fn rust_eh_personality() { + core::intrinsics::abort() + } + } else if #[cfg(any( + all(target_family = "windows", target_env = "gnu"), + target_os = "psp", + target_os = "solid_asp3", + all(target_family = "unix", not(target_os = "espidf"), not(target_os = "l4re")), + all(target_vendor = "fortanix", target_env = "sgx"), + ))] { + mod gcc; + } else { + // Targets that don't support unwinding. + // - family=wasm + // - os=none ("bare metal" targets) + // - os=uefi + // - os=espidf + // - os=hermit + // - nvptx64-nvidia-cuda + // - arch=avr + } +} diff --git a/library/std/src/sys/sgx/net.rs b/library/std/src/sys/sgx/net.rs index 03620a08f2c03..9cfeb347b3625 100644 --- a/library/std/src/sys/sgx/net.rs +++ b/library/std/src/sys/sgx/net.rs @@ -276,6 +276,10 @@ impl TcpListener { Ok((TcpStream { inner: Socket::new(fd, local_addr), peer_addr }, ret_peer)) } + pub fn accept_timeout(&self, _timeout: crate::time::Duration) -> io::Result<(TcpStream, SocketAddr)> { + self.accept() + } + pub fn duplicate(&self) -> io::Result { Ok(self.clone()) } diff --git a/library/std/src/sys/sgx/os.rs b/library/std/src/sys/sgx/os.rs index 5da0257f35de5..86f4c7d3d56d6 100644 --- a/library/std/src/sys/sgx/os.rs +++ b/library/std/src/sys/sgx/os.rs @@ -96,14 +96,61 @@ fn create_env_store() -> &'static EnvStore { unsafe { &*(ENV.load(Ordering::Relaxed) as *const EnvStore) } } -pub type Env = vec::IntoIter<(OsString, OsString)>; +pub struct Env { + iter: vec::IntoIter<(OsString, OsString)>, +} + +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + slice: &'a [(OsString, OsString)], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap()))) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + f.debug_list().entries(iter.as_slice()).finish() + } +} + +impl !Send for Env {} +impl !Sync for Env {} + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} pub fn env() -> Env { let clone_to_vec = |map: &HashMap| -> Vec<_> { map.iter().map(|(k, v)| (k.clone(), v.clone())).collect() }; - get_env_store().map(|env| clone_to_vec(&env.lock().unwrap())).unwrap_or_default().into_iter() + let iter = get_env_store() + .map(|env| clone_to_vec(&env.lock().unwrap())) + .unwrap_or_default() + .into_iter(); + Env { iter } } pub fn getenv(k: &OsStr) -> Option { diff --git a/library/std/src/sys/sgx/thread.rs b/library/std/src/sys/sgx/thread.rs index 1608b8cb642dc..ce99ad9f974ed 100644 --- a/library/std/src/sys/sgx/thread.rs +++ b/library/std/src/sys/sgx/thread.rs @@ -109,6 +109,11 @@ impl Thread { Ok(Thread(handle)) } + pub unsafe fn new_reactor(p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + unsupported() + } + pub(super) fn entry() -> JoinNotifier { let mut pending_tasks = task_queue::lock(); let task = rtunwrap!(Some, pending_tasks.pop()); @@ -121,8 +126,16 @@ impl Thread { rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock); } + /// SGX should protect in-enclave data from the outside (attacker), + /// so there should be no data leakage to the OS, + /// and therefore also no 1-1 mapping between SGX thread names and OS thread names. + /// + /// This is why the method is intentionally No-Op. pub fn set_name(_name: &CStr) { - // FIXME: could store this pointer in TLS somewhere + // Note that the internally visible SGX thread name is already provided + // by the platform-agnostic (target-agnostic) Rust thread code. + // This can be observed in the [`std::thread::tests::test_named_thread`] test, + // which succeeds as-is with the SGX target. } pub fn sleep(dur: Duration) { diff --git a/library/std/src/sys/solid/os.rs b/library/std/src/sys/solid/os.rs index 6135921f0b5a8..9f4e66d628b4b 100644 --- a/library/std/src/sys/solid/os.rs +++ b/library/std/src/sys/solid/os.rs @@ -81,10 +81,42 @@ pub fn current_exe() -> io::Result { static ENV_LOCK: RwLock<()> = RwLock::new(()); +pub fn env_read_lock() -> impl Drop { + ENV_LOCK.read().unwrap_or_else(PoisonError::into_inner) +} + pub struct Env { iter: vec::IntoIter<(OsString, OsString)>, } +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + slice: &'a [(OsString, OsString)], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap()))) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + f.debug_list().entries(iter.as_slice()).finish() + } +} + impl !Send for Env {} impl !Sync for Env {} @@ -106,7 +138,7 @@ pub fn env() -> Env { } unsafe { - let _guard = ENV_LOCK.read(); + let _guard = env_read_lock(); let mut result = Vec::new(); if !environ.is_null() { while !(*environ).is_null() { @@ -140,17 +172,21 @@ pub fn env() -> Env { pub fn getenv(k: &OsStr) -> Option { // environment variables with a nul byte can't be set, so their value is // always None as well - let s = run_with_cstr(k.as_bytes(), |k| { - let _guard = ENV_LOCK.read(); - Ok(unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char) - }) - .ok()?; + run_with_cstr(k.as_bytes(), |k| { + let _guard = env_read_lock(); + let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char; - if s.is_null() { - None - } else { - Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) - } + if v.is_null() { + Ok(None) + } else { + // SAFETY: `v` cannot be mutated while executing this line since we've a read lock + let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec(); + + Ok(Some(OsStringExt::from_vec(bytes))) + } + }) + .ok() + .flatten() } pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { diff --git a/library/std/src/sys/unix/args.rs b/library/std/src/sys/unix/args.rs index 9ed4d9c1e0dd3..eafd6821f540a 100644 --- a/library/std/src/sys/unix/args.rs +++ b/library/std/src/sys/unix/args.rs @@ -168,7 +168,7 @@ mod imp { } } -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] mod imp { use super::Args; use crate::ffi::CStr; @@ -209,7 +209,7 @@ mod imp { // for i in (0..[args count]) // res.push([args objectAtIndex:i]) // res - #[cfg(any(target_os = "ios", target_os = "watchos"))] + #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos"))] pub fn args() -> Args { use crate::ffi::OsString; use crate::mem; diff --git a/library/std/src/sys/unix/cmath.rs b/library/std/src/sys/unix/cmath.rs index 2bf80d7a4cbe2..5346d229116f7 100644 --- a/library/std/src/sys/unix/cmath.rs +++ b/library/std/src/sys/unix/cmath.rs @@ -30,4 +30,8 @@ extern "C" { pub fn tanf(n: f32) -> f32; pub fn tanh(n: f64) -> f64; pub fn tanhf(n: f32) -> f32; + pub fn tgamma(n: f64) -> f64; + pub fn tgammaf(n: f32) -> f32; + pub fn lgamma_r(n: f64, s: &mut i32) -> f64; + pub fn lgammaf_r(n: f32, s: &mut i32) -> f32; } diff --git a/library/std/src/sys/unix/env.rs b/library/std/src/sys/unix/env.rs index 8c3ef88d8f8e0..929e9dae73868 100644 --- a/library/std/src/sys/unix/env.rs +++ b/library/std/src/sys/unix/env.rs @@ -31,6 +31,17 @@ pub mod os { pub const EXE_EXTENSION: &str = ""; } +#[cfg(target_os = "tvos")] +pub mod os { + pub const FAMILY: &str = "unix"; + pub const OS: &str = "tvos"; + pub const DLL_PREFIX: &str = "lib"; + pub const DLL_SUFFIX: &str = ".dylib"; + pub const DLL_EXTENSION: &str = "dylib"; + pub const EXE_SUFFIX: &str = ""; + pub const EXE_EXTENSION: &str = ""; +} + #[cfg(target_os = "watchos")] pub mod os { pub const FAMILY: &str = "unix"; diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs index cb630eede6da0..85e020ae41328 100644 --- a/library/std/src/sys/unix/fd.rs +++ b/library/std/src/sys/unix/fd.rs @@ -44,6 +44,7 @@ const READ_LIMIT: usize = libc::ssize_t::MAX as usize; target_os = "dragonfly", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "macos", target_os = "netbsd", target_os = "openbsd", @@ -69,6 +70,7 @@ const fn max_iov() -> usize { target_os = "emscripten", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "linux", target_os = "macos", target_os = "netbsd", @@ -181,6 +183,7 @@ impl FileDesc { target_os = "fuchsia", target_os = "illumos", target_os = "ios", + target_os = "tvos", target_os = "linux", target_os = "macos", target_os = "netbsd", @@ -222,6 +225,7 @@ impl FileDesc { #[cfg(any( all(target_os = "android", target_pointer_width = "32"), target_os = "ios", + target_os = "tvos", target_os = "macos", ))] pub fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { @@ -320,6 +324,7 @@ impl FileDesc { target_os = "fuchsia", target_os = "illumos", target_os = "ios", + target_os = "tvos", target_os = "linux", target_os = "macos", target_os = "netbsd", @@ -361,6 +366,7 @@ impl FileDesc { #[cfg(any( all(target_os = "android", target_pointer_width = "32"), target_os = "ios", + target_os = "tvos", target_os = "macos", ))] pub fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { @@ -402,7 +408,10 @@ impl FileDesc { } } #[cfg(any( - all(target_env = "newlib", not(any(target_os = "espidf", target_os = "horizon"))), + all( + target_env = "newlib", + not(any(target_os = "espidf", target_os = "horizon", target_os = "vita")) + ), target_os = "solaris", target_os = "illumos", target_os = "emscripten", @@ -424,10 +433,10 @@ impl FileDesc { Ok(()) } } - #[cfg(any(target_os = "espidf", target_os = "horizon"))] + #[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita"))] pub fn set_cloexec(&self) -> io::Result<()> { - // FD_CLOEXEC is not supported in ESP-IDF and Horizon OS but there's no need to, - // because neither supports spawning processes. + // FD_CLOEXEC is not supported in ESP-IDF, Horizon OS and Vita but there's no need to, + // because none of them supports spawning processes. Ok(()) } diff --git a/library/std/src/sys/unix/fs.rs b/library/std/src/sys/unix/fs.rs index 09e9ae2720f5b..a5604c92a80ba 100644 --- a/library/std/src/sys/unix/fs.rs +++ b/library/std/src/sys/unix/fs.rs @@ -7,16 +7,6 @@ use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::mem; -#[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "solaris", - target_os = "fuchsia", - target_os = "redox", - target_os = "illumos", - target_os = "nto", -))] -use crate::mem::MaybeUninit; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; use crate::path::{Path, PathBuf}; use crate::ptr; @@ -31,6 +21,7 @@ use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; all(target_os = "linux", target_env = "gnu"), target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", ))] use crate::sys::weak::syscall; @@ -42,6 +33,7 @@ use libc::{c_int, mode_t}; #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "solaris", all(target_os = "linux", target_env = "gnu") @@ -58,6 +50,7 @@ use libc::fstatat64; target_os = "redox", target_os = "illumos", target_os = "nto", + target_os = "vita", ))] use libc::readdir as readdir64; #[cfg(target_os = "linux")] @@ -74,6 +67,7 @@ use libc::readdir64_r; target_os = "fuchsia", target_os = "redox", target_os = "nto", + target_os = "vita", )))] use libc::readdir_r as readdir64_r; #[cfg(target_os = "android")] @@ -283,6 +277,7 @@ unsafe impl Sync for Dir {} target_os = "fuchsia", target_os = "redox", target_os = "nto", + target_os = "vita" ))] pub struct DirEntry { dir: Arc, @@ -304,10 +299,16 @@ pub struct DirEntry { target_os = "fuchsia", target_os = "redox", target_os = "nto", + target_os = "vita", ))] struct dirent64_min { d_ino: u64, - #[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "nto")))] + #[cfg(not(any( + target_os = "solaris", + target_os = "illumos", + target_os = "nto", + target_os = "vita" + )))] d_type: u8, } @@ -319,6 +320,7 @@ struct dirent64_min { target_os = "fuchsia", target_os = "redox", target_os = "nto", + target_os = "vita", )))] pub struct DirEntry { dir: Arc, @@ -349,7 +351,7 @@ pub struct FilePermissions { pub struct FileTimes { accessed: Option, modified: Option, - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))] created: Option, } @@ -508,6 +510,7 @@ impl FileAttr { target_os = "openbsd", target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", ))] pub fn created(&self) -> io::Result { @@ -519,7 +522,9 @@ impl FileAttr { target_os = "openbsd", target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", + target_os = "vita", )))] pub fn created(&self) -> io::Result { cfg_has_statx! { @@ -541,6 +546,11 @@ impl FileAttr { currently", )) } + + #[cfg(target_os = "vita")] + pub fn created(&self) -> io::Result { + Ok(SystemTime::new(self.stat.st_ctime as i64, 0)) + } } #[cfg(target_os = "nto")] @@ -594,7 +604,7 @@ impl FileTimes { self.modified = Some(t); } - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos", target_os = "tvos"))] pub fn set_created(&mut self, t: SystemTime) { self.created = Some(t); } @@ -645,6 +655,7 @@ impl Iterator for ReadDir { target_os = "redox", target_os = "illumos", target_os = "nto", + target_os = "vita", ))] fn next(&mut self) -> Option> { if self.end_of_stream { @@ -690,22 +701,10 @@ impl Iterator for ReadDir { // requires the full extent of *entry_ptr to be in bounds of the same // allocation, which is not necessarily the case here. // - // Absent any other way to obtain a pointer to `(*entry_ptr).d_name` - // legally in Rust analogously to how it would be done in C, we instead - // need to make our own non-libc allocation that conforms to the weird - // imaginary definition of dirent64, and use that for a field offset - // computation. + // Instead we must access fields individually through their offsets. macro_rules! offset_ptr { ($entry_ptr:expr, $field:ident) => {{ - const OFFSET: isize = { - let delusion = MaybeUninit::::uninit(); - let entry_ptr = delusion.as_ptr(); - unsafe { - ptr::addr_of!((*entry_ptr).$field) - .cast::() - .offset_from(entry_ptr.cast::()) - } - }; + const OFFSET: isize = mem::offset_of!(dirent64, $field) as isize; if true { // Cast to the same type determined by the else branch. $entry_ptr.byte_offset(OFFSET).cast::<_>() @@ -725,6 +724,7 @@ impl Iterator for ReadDir { continue; } + #[cfg(not(target_os = "vita"))] let entry = dirent64_min { d_ino: *offset_ptr!(entry_ptr, d_ino) as u64, #[cfg(not(any( @@ -735,6 +735,9 @@ impl Iterator for ReadDir { d_type: *offset_ptr!(entry_ptr, d_type) as u8, }; + #[cfg(target_os = "vita")] + let entry = dirent64_min { d_ino: 0u64 }; + return Some(Ok(DirEntry { entry, name: name.to_owned(), @@ -752,6 +755,7 @@ impl Iterator for ReadDir { target_os = "redox", target_os = "illumos", target_os = "nto", + target_os = "vita", )))] fn next(&mut self) -> Option> { if self.end_of_stream { @@ -842,6 +846,7 @@ impl DirEntry { target_os = "haiku", target_os = "vxworks", target_os = "nto", + target_os = "vita", ))] pub fn file_type(&self) -> io::Result { self.metadata().map(|m| m.file_type()) @@ -853,6 +858,7 @@ impl DirEntry { target_os = "haiku", target_os = "vxworks", target_os = "nto", + target_os = "vita", )))] pub fn file_type(&self) -> io::Result { match self.entry.d_type { @@ -870,6 +876,7 @@ impl DirEntry { #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "linux", target_os = "emscripten", @@ -903,6 +910,7 @@ impl DirEntry { #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "netbsd", target_os = "openbsd", @@ -921,6 +929,7 @@ impl DirEntry { #[cfg(not(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "netbsd", target_os = "openbsd", @@ -939,6 +948,7 @@ impl DirEntry { target_os = "fuchsia", target_os = "redox", target_os = "nto", + target_os = "vita", )))] fn name_cstr(&self) -> &CStr { unsafe { CStr::from_ptr(self.entry.d_name.as_ptr()) } @@ -951,6 +961,7 @@ impl DirEntry { target_os = "fuchsia", target_os = "redox", target_os = "nto", + target_os = "vita", ))] fn name_cstr(&self) -> &CStr { &self.name @@ -1080,11 +1091,21 @@ impl File { cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?; return Ok(()); - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + ))] unsafe fn os_fsync(fd: c_int) -> c_int { libc::fcntl(fd, libc::F_FULLFSYNC) } - #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "watchos")))] + #[cfg(not(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + )))] unsafe fn os_fsync(fd: c_int) -> c_int { libc::fsync(fd) } @@ -1094,7 +1115,12 @@ impl File { cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?; return Ok(()); - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", + ))] unsafe fn os_datasync(fd: c_int) -> c_int { libc::fcntl(fd, libc::F_FULLFSYNC) } @@ -1113,6 +1139,7 @@ impl File { target_os = "android", target_os = "freebsd", target_os = "ios", + target_os = "tvos", target_os = "linux", target_os = "macos", target_os = "netbsd", @@ -1177,6 +1204,7 @@ impl File { self.0.write_vectored_at(bufs, offset) } + #[inline] pub fn flush(&self) -> io::Result<()> { Ok(()) } @@ -1222,7 +1250,7 @@ impl File { io::ErrorKind::Unsupported, "setting file times not supported", )) - } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] { + } else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] { let mut buf = [mem::MaybeUninit::::uninit(); 3]; let mut num_times = 0; let mut attrlist: libc::attrlist = unsafe { mem::zeroed() }; @@ -1543,7 +1571,7 @@ pub fn link(original: &Path, link: &Path) -> io::Result<()> { run_path_with_cstr(original, |original| { run_path_with_cstr(link, |link| { cfg_if::cfg_if! { - if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon"))] { + if #[cfg(any(target_os = "vxworks", target_os = "redox", target_os = "android", target_os = "espidf", target_os = "horizon", target_os = "vita"))] { // VxWorks, Redox and ESP-IDF lack `linkat`, so use `link` instead. POSIX leaves // it implementation-defined whether `link` follows symlinks, so rely on the // `symlink_hard_link` test in library/std/src/fs/tests.rs to check the behavior. @@ -1666,6 +1694,8 @@ fn open_to_and_set_permissions( .truncate(true) .open(to)?; let writer_metadata = writer.metadata()?; + // fchmod is broken on vita + #[cfg(not(target_os = "vita"))] if writer_metadata.is_file() { // Set the correct file permissions, in case the file already existed. // Don't set the permissions on already existing non-files like @@ -1680,6 +1710,7 @@ fn open_to_and_set_permissions( target_os = "android", target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", )))] pub fn copy(from: &Path, to: &Path) -> io::Result { @@ -1707,7 +1738,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { } } -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] pub fn copy(from: &Path, to: &Path) -> io::Result { use crate::sync::atomic::{AtomicBool, Ordering}; @@ -1844,11 +1875,12 @@ pub fn chroot(dir: &Path) -> io::Result<()> { pub use remove_dir_impl::remove_dir_all; -// Fallback for REDOX, ESP-ID, Horizon, and Miri +// Fallback for REDOX, ESP-ID, Horizon, Vita and Miri #[cfg(any( target_os = "redox", target_os = "espidf", target_os = "horizon", + target_os = "vita", target_os = "nto", miri ))] @@ -1861,6 +1893,7 @@ mod remove_dir_impl { target_os = "redox", target_os = "espidf", target_os = "horizon", + target_os = "vita", target_os = "nto", miri )))] diff --git a/library/std/src/sys/unix/kernel_copy.rs b/library/std/src/sys/unix/kernel_copy.rs index 16c8e0c0ebfc5..4d17a1b000229 100644 --- a/library/std/src/sys/unix/kernel_copy.rs +++ b/library/std/src/sys/unix/kernel_copy.rs @@ -89,6 +89,12 @@ enum FdMeta { NoneObtained, } +#[derive(PartialEq)] +enum FdHandle { + Input, + Output, +} + impl FdMeta { fn maybe_fifo(&self) -> bool { match self { @@ -114,12 +120,14 @@ impl FdMeta { } } - fn copy_file_range_candidate(&self) -> bool { + fn copy_file_range_candidate(&self, f: FdHandle) -> bool { match self { // copy_file_range will fail on empty procfs files. `read` can determine whether EOF has been reached // without extra cost and skip the write, thus there is no benefit in attempting copy_file_range - FdMeta::Metadata(meta) if meta.is_file() && meta.len() > 0 => true, - FdMeta::NoneObtained => true, + FdMeta::Metadata(meta) if f == FdHandle::Input && meta.is_file() && meta.len() > 0 => { + true + } + FdMeta::Metadata(meta) if f == FdHandle::Output && meta.is_file() => true, _ => false, } } @@ -197,7 +205,9 @@ impl SpecCopy for Copier<'_, '_, R, W> { written += flush()?; let max_write = reader.min_limit(); - if input_meta.copy_file_range_candidate() && output_meta.copy_file_range_candidate() { + if input_meta.copy_file_range_candidate(FdHandle::Input) + && output_meta.copy_file_range_candidate(FdHandle::Output) + { let result = copy_regular_files(readfd, writefd, max_write); result.update_take(reader); @@ -466,7 +476,7 @@ impl CopyRead for Take { } } -impl CopyRead for BufReader { +impl CopyRead for BufReader { fn drain_to(&mut self, writer: &mut W, outer_limit: u64) -> Result { let buf = self.buffer(); let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))]; @@ -495,7 +505,7 @@ impl CopyRead for BufReader { } } -impl CopyWrite for BufWriter { +impl CopyWrite for BufWriter { fn properties(&self) -> CopyParams { self.get_ref().properties() } diff --git a/library/std/src/sys/unix/l4re.rs b/library/std/src/sys/unix/l4re.rs index ee016887e7021..2db4e13f7d31d 100644 --- a/library/std/src/sys/unix/l4re.rs +++ b/library/std/src/sys/unix/l4re.rs @@ -10,7 +10,7 @@ macro_rules! unimpl { pub mod net { #![allow(warnings)] use crate::fmt; - use crate::io::{self, IoSlice, IoSliceMut}; + use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::sys::fd::FileDesc; @@ -218,6 +218,10 @@ pub mod net { unimpl!(); } + pub fn read_buf(&self, _: BorrowedCursor<'_>) -> io::Result<()> { + unimpl!(); + } + pub fn read_vectored(&self, _: &mut [IoSliceMut<'_>]) -> io::Result { unimpl!(); } @@ -325,6 +329,10 @@ pub mod net { unimpl!(); } + pub fn accept_timeout(&self, _timeout: crate::time::Duration) -> io::Result<(TcpStream, SocketAddr)> { + unimpl!(); + } + pub fn duplicate(&self) -> io::Result { unimpl!(); } diff --git a/library/std/src/sys/unix/locks/pthread_condvar.rs b/library/std/src/sys/unix/locks/pthread_condvar.rs index 192fa216dfaf5..2dc1b0c601e78 100644 --- a/library/std/src/sys/unix/locks/pthread_condvar.rs +++ b/library/std/src/sys/unix/locks/pthread_condvar.rs @@ -32,6 +32,7 @@ impl LazyInit for AllocatedCondvar { if #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "l4re", target_os = "android", @@ -124,6 +125,7 @@ impl Condvar { #[cfg(not(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "android", target_os = "espidf", @@ -158,6 +160,7 @@ impl Condvar { #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "android", target_os = "espidf", diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs index bb9e65e68e5e8..f126737250ffa 100644 --- a/library/std/src/sys/unix/mod.rs +++ b/library/std/src/sys/unix/mod.rs @@ -88,6 +88,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { // The poll on Darwin doesn't set POLLNVAL for closed fds. target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "redox", target_os = "l4re", @@ -109,6 +110,11 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 { match errno() { libc::EINTR => continue, + #[cfg(target_vendor = "unikraft")] + libc::ENOSYS => { + // Not all configurations of Unikraft enable `LIBPOSIX_EVENT`. + break 'poll; + } libc::EINVAL | libc::EAGAIN | libc::ENOMEM => { // RLIMIT_NOFILE or temporary allocation failures // may be preventing use of poll(), fall back to fcntl @@ -168,7 +174,9 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { target_os = "emscripten", target_os = "fuchsia", target_os = "horizon", - target_os = "vita" + // Unikraft's `signal` implementation is currently broken: + // https://github.com/unikraft/lib-musl/issues/57 + target_vendor = "unikraft", )))] { // We don't want to add this as a public type to std, nor do we @@ -207,7 +215,6 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { target_os = "emscripten", target_os = "fuchsia", target_os = "horizon", - target_os = "vita" )))] static UNIX_SIGPIPE_ATTR_SPECIFIED: crate::sync::atomic::AtomicBool = crate::sync::atomic::AtomicBool::new(false); @@ -217,7 +224,6 @@ static UNIX_SIGPIPE_ATTR_SPECIFIED: crate::sync::atomic::AtomicBool = target_os = "emscripten", target_os = "fuchsia", target_os = "horizon", - target_os = "vita", )))] pub(crate) fn unix_sigpipe_attr_specified() -> bool { UNIX_SIGPIPE_ATTR_SPECIFIED.load(crate::sync::atomic::Ordering::Relaxed) @@ -283,37 +289,11 @@ pub fn decode_error_kind(errno: i32) -> ErrorKind { } } -#[doc(hidden)] -pub trait IsMinusOne { - fn is_minus_one(&self) -> bool; -} - -macro_rules! impl_is_minus_one { - ($($t:ident)*) => ($(impl IsMinusOne for $t { - fn is_minus_one(&self) -> bool { - *self == -1 - } - })*) -} - -impl_is_minus_one! { i8 i16 i32 i64 isize } - -pub fn cvt(t: T) -> crate::io::Result { - if t.is_minus_one() { Err(crate::io::Error::last_os_error()) } else { Ok(t) } -} - -pub fn cvt_r(mut f: F) -> crate::io::Result -where - T: IsMinusOne, - F: FnMut() -> T, -{ - loop { - match cvt(f()) { - Err(ref e) if e.kind() == ErrorKind::Interrupted => {} - other => return other, - } - } -} +pub use super::common::cvt::{ + IsMinusOne, + cvt, + cvt_r +}; #[allow(dead_code)] // Not used on all platforms. pub fn cvt_nz(error: libc::c_int) -> crate::io::Result<()> { @@ -395,7 +375,7 @@ cfg_if::cfg_if! { } else if #[cfg(target_os = "macos")] { #[link(name = "System")] extern "C" {} - } else if #[cfg(any(target_os = "ios", target_os = "watchos"))] { + } else if #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos"))] { #[link(name = "System")] #[link(name = "objc")] #[link(name = "Security", kind = "framework")] @@ -408,6 +388,9 @@ cfg_if::cfg_if! { } else if #[cfg(all(target_os = "linux", target_env = "uclibc"))] { #[link(name = "dl")] extern "C" {} + } else if #[cfg(target_os = "vita")] { + #[link(name = "pthread", kind = "static", modifiers = "-bundle")] + extern "C" {} } } diff --git a/library/std/src/sys/unix/net.rs b/library/std/src/sys/unix/net.rs index 39edb136c24fd..410aeb1dce5f5 100644 --- a/library/std/src/sys/unix/net.rs +++ b/library/std/src/sys/unix/net.rs @@ -238,6 +238,56 @@ impl Socket { } } + pub fn accept_timeout(&self, storage: *mut sockaddr, len: *mut socklen_t, timeout: Duration) -> io::Result { + let mut pollfd = libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLIN, revents: 0 }; + + if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { + return self.accept(storage, len); + } + + self.set_nonblocking(true)?; + let start = Instant::now(); + loop { + let elapsed = start.elapsed(); + if elapsed >= timeout { + return Err(io::const_io_error!(io::ErrorKind::TimedOut, "connection timed out")); + } + + let timeout = timeout - elapsed; + let mut timeout = timeout + .as_secs() + .saturating_mul(1_000) + .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000); + if timeout == 0 { + timeout = 1; + } + + let timeout = cmp::min(timeout, c_int::MAX as u64) as c_int; + match unsafe { libc::poll(&mut pollfd, 1, timeout) } { + -1 => { + let err = io::Error::last_os_error(); + return Err(err); + } + 0 => { + crate::thread::yield_now(); + } + _ => { + let socket = match self.accept(storage, len) { + Ok(a) => a, + Err(err) => { + if err.kind() == io::ErrorKind::WouldBlock { + crate::thread::sleep(Duration::from_millis(1)); + continue; + } + return Err(err); + } + }; + return Ok(socket); + } + } + } + } + pub fn duplicate(&self) -> io::Result { self.0.duplicate().map(Socket) } @@ -454,12 +504,18 @@ impl Socket { Ok(passcred != 0) } - #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + #[cfg(not(any(target_os = "solaris", target_os = "illumos", target_os = "vita")))] pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { let mut nonblocking = nonblocking as libc::c_int; cvt(unsafe { libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &mut nonblocking) }).map(drop) } + #[cfg(target_os = "vita")] + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + let option = nonblocking as libc::c_int; + setsockopt(self, libc::SOL_SOCKET, libc::SO_NONBLOCK, option) + } + #[cfg(any(target_os = "solaris", target_os = "illumos"))] pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { // FIONBIO is inadequate for sockets on illumos/Solaris, so use the diff --git a/library/std/src/sys/unix/os.rs b/library/std/src/sys/unix/os.rs index 8edfd33130442..57e1a36dace9b 100644 --- a/library/std/src/sys/unix/os.rs +++ b/library/std/src/sys/unix/os.rs @@ -63,7 +63,13 @@ extern "C" { #[cfg_attr(any(target_os = "solaris", target_os = "illumos"), link_name = "___errno")] #[cfg_attr(target_os = "nto", link_name = "__get_errno_ptr")] #[cfg_attr( - any(target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "watchos"), + any( + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "freebsd", + target_os = "watchos" + ), link_name = "__error" )] #[cfg_attr(target_os = "haiku", link_name = "_errnop")] @@ -375,7 +381,7 @@ pub fn current_exe() -> io::Result { Ok(PathBuf::from(OsString::from_vec(e))) } -#[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] pub fn current_exe() -> io::Result { unsafe { let mut sz: u32 = 0; @@ -489,6 +495,34 @@ pub struct Env { iter: vec::IntoIter<(OsString, OsString)>, } +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + slice: &'a [(OsString, OsString)], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap()))) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + f.debug_list().entries(iter.as_slice()).finish() + } +} + impl !Send for Env {} impl !Sync for Env {} @@ -560,16 +594,21 @@ pub fn env() -> Env { pub fn getenv(k: &OsStr) -> Option { // environment variables with a nul byte can't be set, so their value is // always None as well - let s = run_with_cstr(k.as_bytes(), |k| { + run_with_cstr(k.as_bytes(), |k| { let _guard = env_read_lock(); - Ok(unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char) + let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char; + + if v.is_null() { + Ok(None) + } else { + // SAFETY: `v` cannot be mutated while executing this line since we've a read lock + let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec(); + + Ok(Some(OsStringExt::from_vec(bytes))) + } }) - .ok()?; - if s.is_null() { - None - } else { - Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) - } + .ok() + .flatten() } pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { @@ -609,6 +648,7 @@ pub fn home_dir() -> Option { #[cfg(any( target_os = "android", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "emscripten", target_os = "redox", @@ -623,6 +663,7 @@ pub fn home_dir() -> Option { #[cfg(not(any( target_os = "android", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "emscripten", target_os = "redox", diff --git a/library/std/src/sys/unix/os_str.rs b/library/std/src/sys/unix/os_str.rs index 488217f39413f..463b0a275157a 100644 --- a/library/std/src/sys/unix/os_str.rs +++ b/library/std/src/sys/unix/os_str.rs @@ -96,6 +96,16 @@ impl AsInner<[u8]> for Buf { } impl Buf { + #[inline] + pub fn into_os_str_bytes(self) -> Vec { + self.inner + } + + #[inline] + pub unsafe fn from_os_str_bytes_unchecked(s: Vec) -> Self { + Self { inner: s } + } + pub fn from_string(s: String) -> Buf { Buf { inner: s.into_bytes() } } @@ -193,17 +203,22 @@ impl Buf { impl Slice { #[inline] - fn from_u8_slice(s: &[u8]) -> &Slice { + pub fn as_os_str_bytes(&self) -> &[u8] { + &self.inner + } + + #[inline] + pub unsafe fn from_os_str_bytes_unchecked(s: &[u8]) -> &Slice { unsafe { mem::transmute(s) } } #[inline] pub fn from_str(s: &str) -> &Slice { - Slice::from_u8_slice(s.as_bytes()) + unsafe { Slice::from_os_str_bytes_unchecked(s.as_bytes()) } } - pub fn to_str(&self) -> Option<&str> { - str::from_utf8(&self.inner).ok() + pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> { + str::from_utf8(&self.inner) } pub fn to_string_lossy(&self) -> Cow<'_, str> { diff --git a/library/std/src/sys/unix/os_str/tests.rs b/library/std/src/sys/unix/os_str/tests.rs index 22ba0c9235041..91bc0e61a4a5b 100644 --- a/library/std/src/sys/unix/os_str/tests.rs +++ b/library/std/src/sys/unix/os_str/tests.rs @@ -2,7 +2,7 @@ use super::*; #[test] fn slice_debug_output() { - let input = Slice::from_u8_slice(b"\xF0hello,\tworld"); + let input = unsafe { Slice::from_os_str_bytes_unchecked(b"\xF0hello,\tworld") }; let expected = r#""\xF0hello,\tworld""#; let output = format!("{input:?}"); @@ -11,8 +11,7 @@ fn slice_debug_output() { #[test] fn display() { - assert_eq!( - "Hello\u{FFFD}\u{FFFD} There\u{FFFD} Goodbye", - Slice::from_u8_slice(b"Hello\xC0\x80 There\xE6\x83 Goodbye").to_string(), - ); + assert_eq!("Hello\u{FFFD}\u{FFFD} There\u{FFFD} Goodbye", unsafe { + Slice::from_os_str_bytes_unchecked(b"Hello\xC0\x80 There\xE6\x83 Goodbye").to_string() + },); } diff --git a/library/std/src/sys/unix/path.rs b/library/std/src/sys/unix/path.rs index a98a69e2db8e1..935245f637b86 100644 --- a/library/std/src/sys/unix/path.rs +++ b/library/std/src/sys/unix/path.rs @@ -30,7 +30,7 @@ pub(crate) fn absolute(path: &Path) -> io::Result { // Get the components, skipping the redundant leading "." component if it exists. let mut components = path.strip_prefix(".").unwrap_or(path).components(); - let path_os = path.as_os_str().bytes(); + let path_os = path.as_os_str().as_os_str_bytes(); let mut normalized = if path.is_absolute() { // "If a pathname begins with two successive characters, the diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs index afd03d79c0ba6..e0eeddb634d2d 100644 --- a/library/std/src/sys/unix/process/process_common.rs +++ b/library/std/src/sys/unix/process/process_common.rs @@ -1,4 +1,6 @@ -#[cfg(all(test, not(target_os = "emscripten")))] +#![cfg_attr(target_os = "wasi", allow(unused, dead_code))] + +#[cfg(all(test, not(any(target_os = "emscripten", target_os = "wasi"))))] mod tests; use crate::os::unix::prelude::*; @@ -63,7 +65,7 @@ cfg_if::cfg_if! { let bit = (signum - 1) as usize; if set.is_null() || bit >= (8 * size_of::()) { - crate::sys::unix::os::set_errno(libc::EINVAL); + crate::sys::os::set_errno(libc::EINVAL); return -1; } let raw = slice::from_raw_parts_mut( @@ -164,9 +166,9 @@ pub enum ProgramKind { impl ProgramKind { fn new(program: &OsStr) -> Self { - if program.bytes().starts_with(b"/") { + if program.as_os_str_bytes().starts_with(b"/") { Self::Absolute - } else if program.bytes().contains(&b'/') { + } else if program.as_os_str_bytes().contains(&b'/') { // If the program has more than one component in it, it is a relative path. Self::Relative } else { diff --git a/library/std/src/sys/unix/process/process_fuchsia.rs b/library/std/src/sys/unix/process/process_fuchsia.rs index e45c380a0bb84..9931c2af2f1e0 100644 --- a/library/std/src/sys/unix/process/process_fuchsia.rs +++ b/library/std/src/sys/unix/process/process_fuchsia.rs @@ -235,7 +235,7 @@ impl Process { } } -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] pub struct ExitStatus(i64); impl ExitStatus { diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 612d43fe20414..3963e7f52d552 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -10,11 +10,10 @@ use core::ffi::NonZero_c_int; #[cfg(target_os = "linux")] use crate::os::linux::process::PidFd; -#[cfg(target_os = "linux")] -use crate::sys::weak::raw_syscall; - #[cfg(any( target_os = "macos", + target_os = "watchos", + target_os = "tvos", target_os = "freebsd", all(target_os = "linux", target_env = "gnu"), all(target_os = "linux", target_env = "musl"), @@ -28,15 +27,38 @@ use libc::RTP_ID as pid_t; #[cfg(not(target_os = "vxworks"))] use libc::{c_int, pid_t}; -#[cfg(not(any(target_os = "vxworks", target_os = "l4re")))] +#[cfg(not(any( + target_os = "vxworks", + target_os = "l4re", + target_os = "tvos", + target_os = "watchos", +)))] use libc::{gid_t, uid_t}; cfg_if::cfg_if! { if #[cfg(all(target_os = "nto", target_env = "nto71"))] { use crate::thread; use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t}; - // arbitrary number of tries: - const MAX_FORKSPAWN_TRIES: u32 = 4; + use crate::time::Duration; + use crate::sync::LazyLock; + // Get smallest amount of time we can sleep. + // Return a common value if it cannot be determined. + fn get_clock_resolution() -> Duration { + static MIN_DELAY: LazyLock Duration> = LazyLock::new(|| { + let mut mindelay = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + if unsafe { libc::clock_getres(libc::CLOCK_MONOTONIC, &mut mindelay) } == 0 + { + Duration::from_nanos(mindelay.tv_nsec as u64) + } else { + Duration::from_millis(1) + } + }); + *MIN_DELAY + } + // Arbitrary minimum sleep duration for retrying fork/spawn + const MIN_FORKSPAWN_SLEEP: Duration = Duration::from_nanos(1); + // Maximum duration of sleeping before giving up and returning an error + const MAX_FORKSPAWN_SLEEP: Duration = Duration::from_millis(1000); } } @@ -67,6 +89,10 @@ impl Command { return Ok((ret, ours)); } + #[cfg(target_os = "linux")] + let (input, output) = sys::net::Socket::new_pair(libc::AF_UNIX, libc::SOCK_SEQPACKET)?; + + #[cfg(not(target_os = "linux"))] let (input, output) = sys::pipe::anon_pipe()?; // Whatever happens after the fork is almost for sure going to touch or @@ -80,12 +106,16 @@ impl Command { // The child calls `mem::forget` to leak the lock, which is crucial because // releasing a lock is not async-signal-safe. let env_lock = sys::os::env_read_lock(); - let (pid, pidfd) = unsafe { self.do_fork()? }; + let pid = unsafe { self.do_fork()? }; if pid == 0 { crate::panic::always_abort(); mem::forget(env_lock); // avoid non-async-signal-safe unlocking drop(input); + #[cfg(target_os = "linux")] + if self.get_create_pidfd() { + self.send_pidfd(&output); + } let Err(err) = unsafe { self.do_exec(theirs, envp.as_ref()) }; let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32; let errno = errno.to_be_bytes(); @@ -109,6 +139,12 @@ impl Command { drop(env_lock); drop(output); + #[cfg(target_os = "linux")] + let pidfd = if self.get_create_pidfd() { self.recv_pidfd(&input) } else { -1 }; + + #[cfg(not(target_os = "linux"))] + let pidfd = -1; + // Safety: We obtained the pidfd from calling `clone3` with // `CLONE_PIDFD` so it's valid an otherwise unowned. let mut p = unsafe { Process::new(pid, pidfd) }; @@ -136,6 +172,7 @@ impl Command { } Ok(..) => { // pipe I/O up to PIPE_BUF bytes should be atomic + // similarly SOCK_SEQPACKET messages should arrive whole assert!(p.wait().is_ok(), "wait() should either return Ok or panic"); panic!("short read on the CLOEXEC pipe") } @@ -148,11 +185,32 @@ impl Command { crate::sys_common::process::wait_with_output(proc, pipes) } + // WatchOS and TVOS headers mark the `fork`/`exec*` functions with + // `__WATCHOS_PROHIBITED __TVOS_PROHIBITED`, and indicate that the + // `posix_spawn*` functions should be used instead. It isn't entirely clear + // what `PROHIBITED` means here (e.g. if calls to these functions are + // allowed to exist in dead code), but it sounds bad, so we go out of our + // way to avoid that all-together. + #[cfg(any(target_os = "tvos", target_os = "watchos"))] + const ERR_APPLE_TV_WATCH_NO_FORK_EXEC: Error = io::const_io_error!( + ErrorKind::Unsupported, + "`fork`+`exec`-based process spawning is not supported on this target", + ); + + #[cfg(any(target_os = "tvos", target_os = "watchos"))] + unsafe fn do_fork(&mut self) -> Result { + return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); + } + // Attempts to fork the process. If successful, returns Ok((0, -1)) // in the child, and Ok((child_pid, -1)) in the parent. - #[cfg(not(any(target_os = "linux", all(target_os = "nto", target_env = "nto71"))))] - unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { - cvt(libc::fork()).map(|res| (res, -1)) + #[cfg(not(any( + target_os = "watchos", + target_os = "tvos", + all(target_os = "nto", target_env = "nto71"), + )))] + unsafe fn do_fork(&mut self) -> Result { + cvt(libc::fork()) } // On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened @@ -160,99 +218,32 @@ impl Command { // Documentation says "... or try calling fork() again". This is what we do here. // See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html #[cfg(all(target_os = "nto", target_env = "nto71"))] - unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { + unsafe fn do_fork(&mut self) -> Result { use crate::sys::os::errno; - let mut tries_left = MAX_FORKSPAWN_TRIES; + let mut delay = MIN_FORKSPAWN_SLEEP; + loop { let r = libc::fork(); - if r == -1 as libc::pid_t && tries_left > 0 && errno() as libc::c_int == libc::EBADF { - thread::yield_now(); - tries_left -= 1; + if r == -1 as libc::pid_t && errno() as libc::c_int == libc::EBADF { + if delay < get_clock_resolution() { + // We cannot sleep this short (it would be longer). + // Yield instead. + thread::yield_now(); + } else if delay < MAX_FORKSPAWN_SLEEP { + thread::sleep(delay); + } else { + return Err(io::const_io_error!( + ErrorKind::WouldBlock, + "forking returned EBADF too often", + )); + } + delay *= 2; + continue; } else { - return cvt(r).map(|res| (res, -1)); - } - } - } - - // Attempts to fork the process. If successful, returns Ok((0, -1)) - // in the child, and Ok((child_pid, child_pidfd)) in the parent. - #[cfg(target_os = "linux")] - unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> { - use crate::sync::atomic::{AtomicBool, Ordering}; - - static HAS_CLONE3: AtomicBool = AtomicBool::new(true); - const CLONE_PIDFD: u64 = 0x00001000; - - #[repr(C)] - struct clone_args { - flags: u64, - pidfd: u64, - child_tid: u64, - parent_tid: u64, - exit_signal: u64, - stack: u64, - stack_size: u64, - tls: u64, - set_tid: u64, - set_tid_size: u64, - cgroup: u64, - } - - raw_syscall! { - fn clone3(cl_args: *mut clone_args, len: libc::size_t) -> libc::c_long - } - - // Bypassing libc for `clone3` can make further libc calls unsafe, - // so we use it sparingly for now. See #89522 for details. - // Some tools (e.g. sandboxing tools) may also expect `fork` - // rather than `clone3`. - let want_clone3_pidfd = self.get_create_pidfd(); - - // If we fail to create a pidfd for any reason, this will - // stay as -1, which indicates an error. - let mut pidfd: pid_t = -1; - - // Attempt to use the `clone3` syscall, which supports more arguments - // (in particular, the ability to create a pidfd). If this fails, - // we will fall through this block to a call to `fork()` - if want_clone3_pidfd && HAS_CLONE3.load(Ordering::Relaxed) { - let mut args = clone_args { - flags: CLONE_PIDFD, - pidfd: &mut pidfd as *mut pid_t as u64, - child_tid: 0, - parent_tid: 0, - exit_signal: libc::SIGCHLD as u64, - stack: 0, - stack_size: 0, - tls: 0, - set_tid: 0, - set_tid_size: 0, - cgroup: 0, - }; - - let args_ptr = &mut args as *mut clone_args; - let args_size = crate::mem::size_of::(); - - let res = cvt(clone3(args_ptr, args_size)); - match res { - Ok(n) => return Ok((n as pid_t, pidfd)), - Err(e) => match e.raw_os_error() { - // Multiple threads can race to execute this store, - // but that's fine - that just means that multiple threads - // will have tried and failed to execute the same syscall, - // with no other side effects. - Some(libc::ENOSYS) => HAS_CLONE3.store(false, Ordering::Relaxed), - // Fallback to fork if `EPERM` is returned. (e.g. blocked by seccomp) - Some(libc::EPERM) => {} - _ => return Err(e), - }, + return cvt(r); } } - - // Generally, we just call `fork`. If we get here after wanting `clone3`, - // then the syscall does not exist or we do not have permission to call it. - cvt(libc::fork()).map(|res| (res, pidfd)) } pub fn exec(&mut self, default: Stdio) -> io::Error { @@ -308,6 +299,7 @@ impl Command { // allocation). Instead we just close it manually. This will never // have the drop glue anyway because this code never returns (the // child will either exec() or invoke libc::exit) + #[cfg(not(any(target_os = "tvos", target_os = "watchos")))] unsafe fn do_exec( &mut self, stdio: ChildPipes, @@ -414,8 +406,19 @@ impl Command { Err(io::Error::last_os_error()) } + #[cfg(any(target_os = "tvos", target_os = "watchos"))] + unsafe fn do_exec( + &mut self, + _stdio: ChildPipes, + _maybe_envp: Option<&CStringArray>, + ) -> Result { + return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); + } + #[cfg(not(any( target_os = "macos", + target_os = "tvos", + target_os = "watchos", target_os = "freebsd", all(target_os = "linux", target_env = "gnu"), all(target_os = "linux", target_env = "musl"), @@ -433,6 +436,9 @@ impl Command { // directly. #[cfg(any( target_os = "macos", + // FIXME: `target_os = "ios"`? + target_os = "tvos", + target_os = "watchos", target_os = "freebsd", all(target_os = "linux", target_env = "gnu"), all(target_os = "linux", target_env = "musl"), @@ -480,17 +486,28 @@ impl Command { attrp: *const posix_spawnattr_t, argv: *const *mut c_char, envp: *const *mut c_char, - ) -> i32 { - let mut tries_left = MAX_FORKSPAWN_TRIES; + ) -> io::Result { + let mut delay = MIN_FORKSPAWN_SLEEP; loop { match libc::posix_spawnp(pid, file, file_actions, attrp, argv, envp) { - libc::EBADF if tries_left > 0 => { - thread::yield_now(); - tries_left -= 1; + libc::EBADF => { + if delay < get_clock_resolution() { + // We cannot sleep this short (it would be longer). + // Yield instead. + thread::yield_now(); + } else if delay < MAX_FORKSPAWN_SLEEP { + thread::sleep(delay); + } else { + return Err(io::const_io_error!( + ErrorKind::WouldBlock, + "posix_spawnp returned EBADF too often", + )); + } + delay *= 2; continue; } r => { - return r; + return Ok(r); } } } @@ -508,7 +525,7 @@ impl Command { } let addchdir = match self.get_cwd() { Some(cwd) => { - if cfg!(target_os = "macos") { + if cfg!(any(target_os = "macos", target_os = "tvos", target_os = "watchos")) { // There is a bug in macOS where a relative executable // path like "../myprogram" will cause `posix_spawn` to // successfully launch the program, but erroneously return @@ -620,17 +637,132 @@ impl Command { let spawn_fn = libc::posix_spawnp; #[cfg(target_os = "nto")] let spawn_fn = retrying_libc_posix_spawnp; - cvt_nz(spawn_fn( + + let spawn_res = spawn_fn( &mut p.pid, self.get_program_cstr().as_ptr(), file_actions.0.as_ptr(), attrs.0.as_ptr(), self.get_argv().as_ptr() as *const _, envp as *const _, - ))?; + ); + + #[cfg(target_os = "nto")] + let spawn_res = spawn_res?; + + cvt_nz(spawn_res)?; Ok(Some(p)) } } + + #[cfg(target_os = "linux")] + fn send_pidfd(&self, sock: &crate::sys::net::Socket) { + use crate::io::IoSlice; + use crate::os::fd::RawFd; + use crate::sys::cvt_r; + use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET}; + + unsafe { + let child_pid = libc::getpid(); + // pidfd_open sets CLOEXEC by default + let pidfd = libc::syscall(libc::SYS_pidfd_open, child_pid, 0); + + let fds: [c_int; 1] = [pidfd as RawFd]; + + const SCM_MSG_LEN: usize = mem::size_of::<[c_int; 1]>(); + + #[repr(C)] + union Cmsg { + buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }], + _align: libc::cmsghdr, + } + + let mut cmsg: Cmsg = mem::zeroed(); + + // 0-length message to send through the socket so we can pass along the fd + let mut iov = [IoSlice::new(b"")]; + let mut msg: libc::msghdr = mem::zeroed(); + + msg.msg_iov = &mut iov as *mut _ as *mut _; + msg.msg_iovlen = 1; + msg.msg_controllen = mem::size_of_val(&cmsg.buf) as _; + msg.msg_control = &mut cmsg.buf as *mut _ as *mut _; + + // only attach cmsg if we successfully acquired the pidfd + if pidfd >= 0 { + let hdr = CMSG_FIRSTHDR(&mut msg as *mut _ as *mut _); + (*hdr).cmsg_level = SOL_SOCKET; + (*hdr).cmsg_type = SCM_RIGHTS; + (*hdr).cmsg_len = CMSG_LEN(SCM_MSG_LEN as _) as _; + let data = CMSG_DATA(hdr); + crate::ptr::copy_nonoverlapping( + fds.as_ptr().cast::(), + data as *mut _, + SCM_MSG_LEN, + ); + } + + // we send the 0-length message even if we failed to acquire the pidfd + // so we get a consistent SEQPACKET order + match cvt_r(|| libc::sendmsg(sock.as_raw(), &msg, 0)) { + Ok(0) => {} + _ => rtabort!("failed to communicate with parent process"), + } + } + } + + #[cfg(target_os = "linux")] + fn recv_pidfd(&self, sock: &crate::sys::net::Socket) -> pid_t { + use crate::io::IoSliceMut; + use crate::sys::cvt_r; + + use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET}; + + unsafe { + const SCM_MSG_LEN: usize = mem::size_of::<[c_int; 1]>(); + + #[repr(C)] + union Cmsg { + _buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }], + _align: libc::cmsghdr, + } + let mut cmsg: Cmsg = mem::zeroed(); + // 0-length read to get the fd + let mut iov = [IoSliceMut::new(&mut [])]; + + let mut msg: libc::msghdr = mem::zeroed(); + + msg.msg_iov = &mut iov as *mut _ as *mut _; + msg.msg_iovlen = 1; + msg.msg_controllen = mem::size_of::() as _; + msg.msg_control = &mut cmsg as *mut _ as *mut _; + + match cvt_r(|| libc::recvmsg(sock.as_raw(), &mut msg, 0)) { + Err(_) => return -1, + Ok(_) => {} + } + + let hdr = CMSG_FIRSTHDR(&mut msg as *mut _ as *mut _); + if hdr.is_null() + || (*hdr).cmsg_level != SOL_SOCKET + || (*hdr).cmsg_type != SCM_RIGHTS + || (*hdr).cmsg_len != CMSG_LEN(SCM_MSG_LEN as _) as _ + { + return -1; + } + let data = CMSG_DATA(hdr); + + let mut fds = [-1 as c_int]; + + crate::ptr::copy_nonoverlapping( + data as *const _, + fds.as_mut_ptr().cast::(), + SCM_MSG_LEN, + ); + + fds[0] + } + } } //////////////////////////////////////////////////////////////////////////////// @@ -671,12 +803,9 @@ impl Process { pub fn kill(&mut self) -> io::Result<()> { // If we've already waited on this process then the pid can be recycled // and used for another process, and we probably shouldn't be killing - // random processes, so just return an error. + // random processes, so return Ok because the process has exited already. if self.status.is_some() { - Err(io::const_io_error!( - ErrorKind::InvalidInput, - "invalid argument: can't kill an exited process", - )) + Ok(()) } else { cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) } @@ -712,7 +841,7 @@ impl Process { // // This is not actually an "exit status" in Unix terminology. Rather, it is a "wait status". // See the discussion in comments and doc comments for `std::process::ExitStatus`. -#[derive(PartialEq, Eq, Clone, Copy)] +#[derive(PartialEq, Eq, Clone, Copy, Default)] pub struct ExitStatus(c_int); impl fmt::Debug for ExitStatus { @@ -788,31 +917,47 @@ fn signal_string(signal: i32) -> &'static str { libc::SIGILL => " (SIGILL)", libc::SIGTRAP => " (SIGTRAP)", libc::SIGABRT => " (SIGABRT)", + #[cfg(not(target_os = "l4re"))] libc::SIGBUS => " (SIGBUS)", libc::SIGFPE => " (SIGFPE)", libc::SIGKILL => " (SIGKILL)", + #[cfg(not(target_os = "l4re"))] libc::SIGUSR1 => " (SIGUSR1)", libc::SIGSEGV => " (SIGSEGV)", + #[cfg(not(target_os = "l4re"))] libc::SIGUSR2 => " (SIGUSR2)", libc::SIGPIPE => " (SIGPIPE)", libc::SIGALRM => " (SIGALRM)", libc::SIGTERM => " (SIGTERM)", + #[cfg(not(target_os = "l4re"))] libc::SIGCHLD => " (SIGCHLD)", + #[cfg(not(target_os = "l4re"))] libc::SIGCONT => " (SIGCONT)", + #[cfg(not(target_os = "l4re"))] libc::SIGSTOP => " (SIGSTOP)", + #[cfg(not(target_os = "l4re"))] libc::SIGTSTP => " (SIGTSTP)", + #[cfg(not(target_os = "l4re"))] libc::SIGTTIN => " (SIGTTIN)", + #[cfg(not(target_os = "l4re"))] libc::SIGTTOU => " (SIGTTOU)", + #[cfg(not(target_os = "l4re"))] libc::SIGURG => " (SIGURG)", + #[cfg(not(target_os = "l4re"))] libc::SIGXCPU => " (SIGXCPU)", + #[cfg(not(target_os = "l4re"))] libc::SIGXFSZ => " (SIGXFSZ)", + #[cfg(not(target_os = "l4re"))] libc::SIGVTALRM => " (SIGVTALRM)", + #[cfg(not(target_os = "l4re"))] libc::SIGPROF => " (SIGPROF)", + #[cfg(not(target_os = "l4re"))] libc::SIGWINCH => " (SIGWINCH)", - #[cfg(not(target_os = "haiku"))] + #[cfg(not(any(target_os = "haiku", target_os = "l4re")))] libc::SIGIO => " (SIGIO)", #[cfg(target_os = "haiku")] libc::SIGPOLL => " (SIGPOLL)", + #[cfg(not(target_os = "l4re"))] libc::SIGSYS => " (SIGSYS)", // For information on Linux signals, run `man 7 signal` #[cfg(all( diff --git a/library/std/src/sys/unix/process/process_unix/tests.rs b/library/std/src/sys/unix/process/process_unix/tests.rs index e5e1f956bc351..6aa79e7f9e710 100644 --- a/library/std/src/sys/unix/process/process_unix/tests.rs +++ b/library/std/src/sys/unix/process/process_unix/tests.rs @@ -60,3 +60,28 @@ fn test_command_fork_no_unwind() { || signal == libc::SIGSEGV ); } + +#[test] +#[cfg(target_os = "linux")] +fn test_command_pidfd() { + use crate::os::fd::RawFd; + use crate::os::linux::process::{ChildExt, CommandExt}; + use crate::process::Command; + + let our_pid = crate::process::id(); + let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) }; + let pidfd_open_available = if pidfd >= 0 { + unsafe { libc::close(pidfd as RawFd) }; + true + } else { + false + }; + + // always exercise creation attempts + let child = Command::new("echo").create_pidfd(true).spawn().unwrap(); + + // but only check if we know that the kernel supports pidfds + if pidfd_open_available { + assert!(child.pidfd().is_ok()) + } +} diff --git a/library/std/src/sys/unix/process/process_unsupported.rs b/library/std/src/sys/unix/process/process_unsupported.rs index f28ca58d02038..8e0b971af7316 100644 --- a/library/std/src/sys/unix/process/process_unsupported.rs +++ b/library/std/src/sys/unix/process/process_unsupported.rs @@ -55,7 +55,7 @@ impl Process { } } -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] pub struct ExitStatus(c_int); impl ExitStatus { diff --git a/library/std/src/sys/unix/process/process_vxworks.rs b/library/std/src/sys/unix/process/process_vxworks.rs index c40e7ada03cbd..1ff2b2fb3833f 100644 --- a/library/std/src/sys/unix/process/process_vxworks.rs +++ b/library/std/src/sys/unix/process/process_vxworks.rs @@ -144,12 +144,9 @@ impl Process { pub fn kill(&mut self) -> io::Result<()> { // If we've already waited on this process then the pid can be recycled // and used for another process, and we probably shouldn't be killing - // random processes, so just return an error. + // random processes, so return Ok because the process has exited already. if self.status.is_some() { - Err(io::const_io_error!( - ErrorKind::InvalidInput, - "invalid argument: can't kill an exited process", - )) + Ok(()) } else { cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) } @@ -182,7 +179,7 @@ impl Process { } /// Unix exit statuses -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] pub struct ExitStatus(c_int); impl ExitStatus { diff --git a/library/std/src/sys/unix/rand.rs b/library/std/src/sys/unix/rand.rs index d8b63546b9ed1..fbf158f56fcc0 100644 --- a/library/std/src/sys/unix/rand.rs +++ b/library/std/src/sys/unix/rand.rs @@ -14,9 +14,9 @@ pub fn hashmap_random_keys() -> (u64, u64) { unix, not(target_os = "macos"), not(target_os = "ios"), + not(target_os = "tvos"), not(target_os = "watchos"), not(target_os = "openbsd"), - not(target_os = "freebsd"), not(target_os = "netbsd"), not(target_os = "fuchsia"), not(target_os = "redox"), @@ -67,11 +67,25 @@ mod imp { unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) } } + #[cfg(target_os = "freebsd")] + fn getrandom(buf: &mut [u8]) -> libc::ssize_t { + // FIXME: using the above when libary std's libc is updated + extern "C" { + fn getrandom( + buffer: *mut libc::c_void, + length: libc::size_t, + flags: libc::c_uint, + ) -> libc::ssize_t; + } + unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) } + } + #[cfg(not(any( target_os = "linux", target_os = "android", target_os = "espidf", - target_os = "horizon" + target_os = "horizon", + target_os = "freebsd" )))] fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool { false @@ -81,7 +95,8 @@ mod imp { target_os = "linux", target_os = "android", target_os = "espidf", - target_os = "horizon" + target_os = "horizon", + target_os = "freebsd" ))] fn getrandom_fill_bytes(v: &mut [u8]) -> bool { use crate::sync::atomic::{AtomicBool, Ordering}; @@ -198,7 +213,7 @@ mod imp { // once per thread in `hashmap_random_keys`. Therefore `SecRandomCopyBytes` is // only used on iOS where direct access to `/dev/urandom` is blocked by the // sandbox. -#[cfg(any(target_os = "ios", target_os = "watchos"))] +#[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos"))] mod imp { use crate::io; use crate::ptr; @@ -221,7 +236,7 @@ mod imp { } } -#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] +#[cfg(target_os = "netbsd")] mod imp { use crate::ptr; diff --git a/library/std/src/sys/unix/stack_overflow.rs b/library/std/src/sys/unix/stack_overflow.rs index b59d4ba26afb9..138f35e9e1196 100644 --- a/library/std/src/sys/unix/stack_overflow.rs +++ b/library/std/src/sys/unix/stack_overflow.rs @@ -1,4 +1,5 @@ #![cfg_attr(test, allow(dead_code))] +#![cfg_attr(target_vendor = "wasmer", allow(dead_code))] use self::imp::{drop_handler, make_handler}; diff --git a/library/std/src/sys/unix/stdio.rs b/library/std/src/sys/unix/stdio.rs index a26f20795a191..97e75f1b5b669 100644 --- a/library/std/src/sys/unix/stdio.rs +++ b/library/std/src/sys/unix/stdio.rs @@ -54,6 +54,7 @@ impl io::Write for Stdout { true } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } @@ -81,6 +82,7 @@ impl io::Write for Stderr { true } + #[inline] fn flush(&mut self) -> io::Result<()> { Ok(()) } diff --git a/library/std/src/sys/unix/thread.rs b/library/std/src/sys/unix/thread.rs index 7307d9b2c8616..721e744c19164 100644 --- a/library/std/src/sys/unix/thread.rs +++ b/library/std/src/sys/unix/thread.rs @@ -1,9 +1,11 @@ +#![cfg_attr(target_vendor = "wasmer", allow(dead_code))] use crate::cmp; use crate::ffi::CStr; use crate::io; use crate::mem; use crate::num::NonZeroUsize; use crate::ptr; +use crate::sync::Arc; use crate::sys::{os, stack_overflow}; use crate::time::Duration; @@ -111,6 +113,43 @@ impl Thread { } } + pub unsafe fn new_reactor(p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + let p = Arc::new(p); + let p = Arc::into_raw(p); + let mut native: libc::pthread_t = mem::zeroed(); + let mut attr: libc::pthread_attr_t = mem::zeroed(); + assert_eq!(libc::pthread_attr_init(&mut attr), 0); + + let ret = libc::pthread_create(&mut native, &attr, reactor_start, p as *mut _); + // Note: if the thread creation fails and this assert fails, then p will + // be leaked. However, an alternative design could cause double-free + // which is clearly worse. + assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + + return if ret != 0 { + // The thread failed to start and as a result p was not consumed. Therefore, it is + // safe to reconstruct the box so that it gets deallocated. + drop(Arc::from_raw(p)); + Err(io::Error::from_raw_os_error(ret)) + } else { + Ok(Thread { id: native }) + }; + + extern "C" fn reactor_start(main: *mut libc::c_void) -> *mut libc::c_void { + unsafe { + // Next, set up our stack overflow handler which may get triggered if we run + // out of stack. + let _handler = stack_overflow::Handler::new(); + // Finally, let's run some code. + let f = Arc::from_raw(main as *mut Arc); + f(); + crate::mem::forget(f); + } + ptr::null_mut() + } + } + pub fn yield_now() { let ret = unsafe { libc::sched_yield() }; debug_assert_eq!(ret, 0); @@ -150,7 +189,7 @@ impl Thread { } } - #[cfg(any(target_os = "macos", target_os = "ios", target_os = "watchos"))] + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))] pub fn set_name(name: &CStr) { unsafe { let name = truncate_cstr::<{ libc::MAXTHREADNAMESIZE }>(name); @@ -212,6 +251,7 @@ impl Thread { target_env = "newlib", target_os = "l4re", target_os = "emscripten", + target_os = "wasi", target_os = "redox", target_os = "vxworks" ))] @@ -284,7 +324,13 @@ impl Drop for Thread { } } -#[cfg(any(target_os = "linux", target_os = "macos", target_os = "ios", target_os = "watchos"))] +#[cfg(any( + target_os = "linux", + target_os = "macos", + target_os = "ios", + target_os = "tvos", + target_os = "watchos", +))] fn truncate_cstr(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] { let mut result = [0; MAX_WITH_NUL]; for (src, dst) in cstr.to_bytes().iter().zip(&mut result[..MAX_WITH_NUL - 1]) { @@ -298,8 +344,10 @@ pub fn available_parallelism() -> io::Result { if #[cfg(any( target_os = "android", target_os = "emscripten", + target_os = "wasi", target_os = "fuchsia", target_os = "ios", + target_os = "tvos", target_os = "linux", target_os = "macos", target_os = "solaris", @@ -345,6 +393,29 @@ pub fn available_parallelism() -> io::Result { } } + #[cfg(target_os = "netbsd")] + { + unsafe { + let set = libc::_cpuset_create(); + if !set.is_null() { + let mut count: usize = 0; + if libc::pthread_getaffinity_np(libc::pthread_self(), libc::_cpuset_size(set), set) == 0 { + for i in 0..u64::MAX { + match libc::_cpuset_isset(i, set) { + -1 => break, + 0 => continue, + _ => count = count + 1, + } + } + } + libc::_cpuset_destroy(set); + if let Some(count) = NonZeroUsize::new(count) { + return Ok(count); + } + } + } + } + let mut cpus: libc::c_uint = 0; let mut cpus_size = crate::mem::size_of_val(&cpus); diff --git a/library/std/src/sys/unix/thread_parking/pthread.rs b/library/std/src/sys/unix/thread_parking/pthread.rs index 43046ed07b82c..ae805d8439945 100644 --- a/library/std/src/sys/unix/thread_parking/pthread.rs +++ b/library/std/src/sys/unix/thread_parking/pthread.rs @@ -46,6 +46,7 @@ unsafe fn wait_timeout( #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "espidf", target_os = "horizon", @@ -73,6 +74,7 @@ unsafe fn wait_timeout( #[cfg(not(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "espidf", target_os = "horizon", @@ -120,10 +122,12 @@ impl Parker { if #[cfg(any( target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos", target_os = "l4re", target_os = "android", - target_os = "redox" + target_os = "redox", + target_os = "vita", ))] { addr_of_mut!((*parker).cvar).write(UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER)); } else if #[cfg(any(target_os = "espidf", target_os = "horizon"))] { diff --git a/library/std/src/sys/unix/time.rs b/library/std/src/sys/unix/time.rs index a9fbc7ab108a4..17b4130c20241 100644 --- a/library/std/src/sys/unix/time.rs +++ b/library/std/src/sys/unix/time.rs @@ -219,7 +219,8 @@ impl From<__timespec64> for Timespec { #[cfg(any( all(target_os = "macos", any(not(target_arch = "aarch64"))), target_os = "ios", - target_os = "watchos" + target_os = "watchos", + target_os = "tvos" ))] mod inner { use crate::sync::atomic::{AtomicU64, Ordering}; @@ -339,7 +340,8 @@ mod inner { #[cfg(not(any( all(target_os = "macos", any(not(target_arch = "aarch64"))), target_os = "ios", - target_os = "watchos" + target_os = "watchos", + target_os = "tvos" )))] mod inner { use crate::fmt; diff --git a/library/std/src/sys/unix/weak.rs b/library/std/src/sys/unix/weak.rs index 62ffee70becc3..61088ff16eddf 100644 --- a/library/std/src/sys/unix/weak.rs +++ b/library/std/src/sys/unix/weak.rs @@ -28,7 +28,7 @@ use crate::ptr; use crate::sync::atomic::{self, AtomicPtr, Ordering}; // We can use true weak linkage on ELF targets. -#[cfg(not(any(target_os = "macos", target_os = "ios")))] +#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "tvos")))] pub(crate) macro weak { (fn $name:ident($($t:ty),*) -> $ret:ty) => ( let ref $name: ExternWeak $ret> = { @@ -43,7 +43,7 @@ pub(crate) macro weak { } // On non-ELF targets, use the dlsym approximation of weak linkage. -#[cfg(any(target_os = "macos", target_os = "ios"))] +#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))] pub(crate) use self::dlsym as weak; pub(crate) struct ExternWeak { diff --git a/library/std/src/sys/unsupported/net.rs b/library/std/src/sys/unsupported/net.rs index bbc52703f9632..dac53494bd831 100644 --- a/library/std/src/sys/unsupported/net.rs +++ b/library/std/src/sys/unsupported/net.rs @@ -133,6 +133,10 @@ impl TcpListener { self.0 } + pub fn accept_timeout(&self, _timeout: crate::time::Duration) -> io::Result<(TcpStream, SocketAddr)> { + unsupported() + } + pub fn duplicate(&self) -> io::Result { self.0 } diff --git a/library/std/src/sys/unsupported/os.rs b/library/std/src/sys/unsupported/os.rs index e150ae143ad99..248b34829f2ee 100644 --- a/library/std/src/sys/unsupported/os.rs +++ b/library/std/src/sys/unsupported/os.rs @@ -65,10 +65,26 @@ pub fn current_exe() -> io::Result { pub struct Env(!); +impl Env { + // FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self(inner) = self; + match *inner {} + } +} + +impl fmt::Debug for Env { + fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + match *inner {} + } +} + impl Iterator for Env { type Item = (OsString, OsString); fn next(&mut self) -> Option<(OsString, OsString)> { - self.0 + let Self(inner) = self; + match *inner {} } } diff --git a/library/std/src/sys/unsupported/process.rs b/library/std/src/sys/unsupported/process.rs index a494f2d6b4c15..77b675aaa4e49 100644 --- a/library/std/src/sys/unsupported/process.rs +++ b/library/std/src/sys/unsupported/process.rs @@ -99,58 +99,59 @@ impl fmt::Debug for Command { } } -pub struct ExitStatus(!); +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +#[non_exhaustive] +pub struct ExitStatus(); impl ExitStatus { pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - self.0 + Ok(()) } pub fn code(&self) -> Option { - self.0 + Some(0) } } -impl Clone for ExitStatus { - fn clone(&self) -> ExitStatus { - self.0 +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") } } -impl Copy for ExitStatus {} +pub struct ExitStatusError(!); -impl PartialEq for ExitStatus { - fn eq(&self, _other: &ExitStatus) -> bool { +impl Clone for ExitStatusError { + fn clone(&self) -> ExitStatusError { self.0 } } -impl Eq for ExitStatus {} +impl Copy for ExitStatusError {} -impl fmt::Debug for ExitStatus { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl PartialEq for ExitStatusError { + fn eq(&self, _other: &ExitStatusError) -> bool { self.0 } } -impl fmt::Display for ExitStatus { +impl Eq for ExitStatusError {} + +impl fmt::Debug for ExitStatusError { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0 } } -#[derive(PartialEq, Eq, Clone, Copy, Debug)] -pub struct ExitStatusError(ExitStatus); - impl Into for ExitStatusError { fn into(self) -> ExitStatus { - self.0.0 + self.0 } } impl ExitStatusError { pub fn code(self) -> Option { - self.0.0 + self.0 } } diff --git a/library/std/src/sys/unsupported/thread.rs b/library/std/src/sys/unsupported/thread.rs index a8db251de2017..fac3d0a4a0aae 100644 --- a/library/std/src/sys/unsupported/thread.rs +++ b/library/std/src/sys/unsupported/thread.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use super::unsupported; use crate::ffi::CStr; use crate::io; @@ -14,6 +15,11 @@ impl Thread { unsupported() } + pub unsafe fn new_reactor(p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + unsupported() + } + pub fn yield_now() { // do nothing } diff --git a/library/std/src/sys/unsupported/thread_local_dtor.rs b/library/std/src/sys/unsupported/thread_local_dtor.rs index 84660ea588156..7bfc62861c2e5 100644 --- a/library/std/src/sys/unsupported/thread_local_dtor.rs +++ b/library/std/src/sys/unsupported/thread_local_dtor.rs @@ -1,4 +1,5 @@ #![unstable(feature = "thread_local_internals", issue = "none")] +#![allow(dead_code)] #[cfg_attr(target_family = "wasm", allow(unused))] // unused on wasm32-unknown-unknown pub unsafe fn register_dtor(_t: *mut u8, _dtor: unsafe extern "C" fn(*mut u8)) { diff --git a/library/std/src/sys/unsupported/thread_local_key.rs b/library/std/src/sys/unsupported/thread_local_key.rs index b6e5e4cd2e197..5082caf5cc8be 100644 --- a/library/std/src/sys/unsupported/thread_local_key.rs +++ b/library/std/src/sys/unsupported/thread_local_key.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] pub type Key = usize; #[inline] diff --git a/library/std/src/sys/wasi/args.rs b/library/std/src/sys/wasi/args.rs index c42c310e3a254..a975282267018 100644 --- a/library/std/src/sys/wasi/args.rs +++ b/library/std/src/sys/wasi/args.rs @@ -2,6 +2,7 @@ use crate::ffi::{CStr, OsStr, OsString}; use crate::fmt; +#[allow(unused_imports)] use crate::os::wasi::ffi::OsStrExt; use crate::vec; @@ -21,7 +22,7 @@ fn maybe_args() -> Option> { unsafe { let (argc, buf_size) = wasi::args_sizes_get().ok()?; let mut argv = Vec::with_capacity(argc); - let mut buf = Vec::with_capacity(buf_size); + let mut buf = Vec::with_capacity(buf_size as usize); wasi::args_get(argv.as_mut_ptr(), buf.as_mut_ptr()).ok()?; argv.set_len(argc); let mut ret = Vec::with_capacity(argc); diff --git a/library/std/src/sys/wasi/fd.rs b/library/std/src/sys/wasi/fd.rs index 9a8b2a0be5b00..35c76a938b3ec 100644 --- a/library/std/src/sys/wasi/fd.rs +++ b/library/std/src/sys/wasi/fd.rs @@ -16,14 +16,20 @@ pub struct WasiFd { fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] { assert_eq!(mem::size_of::>(), mem::size_of::()); assert_eq!(mem::align_of::>(), mem::align_of::()); - // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout + // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout. + // We decorate our `IoSliceMut` with `repr(transparent)` (see `io.rs`), and + // `crate::io::IoSliceMut` is a `repr(transparent)` wrapper around our type, so this is + // guaranteed. unsafe { mem::transmute(a) } } fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] { assert_eq!(mem::size_of::>(), mem::size_of::()); assert_eq!(mem::align_of::>(), mem::align_of::()); - // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout + // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout. + // We decorate our `IoSlice` with `repr(transparent)` (see `io.rs`), and + // `crate::io::IoSlice` is a `repr(transparent)` wrapper around our type, so this is + // guaranteed. unsafe { mem::transmute(a) } } @@ -96,7 +102,7 @@ impl WasiFd { unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } } - pub fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { + pub(crate) fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { unsafe { wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) } @@ -179,7 +185,7 @@ impl WasiFd { } } - pub fn filestat_get(&self) -> io::Result { + pub(crate) fn filestat_get(&self) -> io::Result { unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) } } @@ -199,7 +205,7 @@ impl WasiFd { unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) } } - pub fn path_filestat_get( + pub(crate) fn path_filestat_get( &self, flags: wasi::Lookupflags, path: &str, @@ -245,7 +251,7 @@ impl WasiFd { } pub fn sock_accept(&self, flags: wasi::Fdflags) -> io::Result { - unsafe { wasi::sock_accept(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } + unsafe { wasi::sock_accept_v2(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } } pub fn sock_recv( diff --git a/library/std/src/sys/wasi/fs.rs b/library/std/src/sys/wasi/fs.rs index 8d1dbf59155a4..c7e3e43b95f8d 100644 --- a/library/std/src/sys/wasi/fs.rs +++ b/library/std/src/sys/wasi/fs.rs @@ -1,4 +1,5 @@ #![deny(unsafe_op_in_unsafe_fn)] +#![allow(unused_imports)] use super::fd::WasiFd; use crate::ffi::{CStr, OsStr, OsString}; @@ -7,6 +8,7 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::iter; use crate::mem::{self, ManuallyDrop}; use crate::os::raw::c_int; +#[allow(unused_imports)] use crate::os::wasi::ffi::{OsStrExt, OsStringExt}; use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::{Path, PathBuf}; @@ -20,7 +22,7 @@ use crate::sys_common::{AsInner, FromInner, IntoInner}; pub use crate::sys_common::fs::try_exists; pub struct File { - fd: WasiFd, + pub(crate) fd: WasiFd, } #[derive(Clone)] @@ -89,7 +91,7 @@ impl FileAttr { } pub fn file_type(&self) -> FileType { - FileType { bits: self.meta.filetype } + FileType { bits: self.meta.filetype.into() } } pub fn modified(&self) -> io::Result { @@ -104,7 +106,7 @@ impl FileAttr { Ok(SystemTime::from_wasi_timestamp(self.meta.ctim)) } - pub fn as_wasi(&self) -> &wasi::Filestat { + pub(crate) fn as_wasi(&self) -> &wasi::Filestat { &self.meta } } @@ -131,18 +133,18 @@ impl FileTimes { impl FileType { pub fn is_dir(&self) -> bool { - self.bits == wasi::FILETYPE_DIRECTORY + self.bits.raw() == wasi::FILETYPE_DIRECTORY.raw() } pub fn is_file(&self) -> bool { - self.bits == wasi::FILETYPE_REGULAR_FILE + self.bits.raw() == wasi::FILETYPE_REGULAR_FILE.raw() } pub fn is_symlink(&self) -> bool { - self.bits == wasi::FILETYPE_SYMBOLIC_LINK + self.bits.raw() == wasi::FILETYPE_SYMBOLIC_LINK.raw() } - pub fn bits(&self) -> wasi::Filetype { + pub(crate) fn bits(&self) -> wasi::Filetype { self.bits } } @@ -256,7 +258,7 @@ impl DirEntry { } pub fn file_type(&self) -> io::Result { - Ok(FileType { bits: self.meta.d_type }) + Ok(FileType { bits: self.meta.d_type.into() }) } pub fn ino(&self) -> wasi::Inode { @@ -403,6 +405,10 @@ impl File { open_at(&dir, &file, opts) } + pub fn open_c(path: &CStr, opts: &OpenOptions) -> io::Result { + Self::open(Path::new(path.to_str().map_err(|_| io::const_io_error!(io::ErrorKind::InvalidInput, "failed to convert CStr to path"))?), opts) + } + pub fn open_at(&self, path: &Path, opts: &OpenOptions) -> io::Result { open_at(&self.fd, path, opts) } diff --git a/library/std/src/sys/wasi/io.rs b/library/std/src/sys/wasi/io.rs index 2cd45df88fad1..8f4c9bf74d9d2 100644 --- a/library/std/src/sys/wasi/io.rs +++ b/library/std/src/sys/wasi/io.rs @@ -31,7 +31,7 @@ impl<'a> IoSlice<'a> { #[inline] pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len) } + unsafe { slice::from_raw_parts(self.vec.buf as *const u8, self.vec.buf_len as usize) } } } diff --git a/library/std/src/sys/wasi/mod.rs b/library/std/src/sys/wasi/mod.rs index a222370802480..77971cb7517ed 100644 --- a/library/std/src/sys/wasi/mod.rs +++ b/library/std/src/sys/wasi/mod.rs @@ -29,8 +29,7 @@ pub mod fs; #[path = "../wasm/atomics/futex.rs"] pub mod futex; pub mod io; -#[path = "../unsupported/locks/mod.rs"] -pub mod locks; + pub mod net; pub mod os; #[path = "../unix/os_str.rs"] @@ -39,7 +38,6 @@ pub mod os_str; pub mod path; #[path = "../unsupported/pipe.rs"] pub mod pipe; -#[path = "../unsupported/process.rs"] pub mod process; pub mod stdio; pub mod thread; @@ -47,14 +45,27 @@ pub mod thread; pub mod thread_local_dtor; #[path = "../unsupported/thread_local_key.rs"] pub mod thread_local_key; -#[path = "../unsupported/thread_parking.rs"] -pub mod thread_parking; pub mod time; cfg_if::cfg_if! { - if #[cfg(not(target_feature = "atomics"))] { + if #[cfg(target_feature = "atomics")] { + #[path = "../unix/locks"] + pub mod locks { + #![allow(unsafe_op_in_unsafe_fn)] + mod futex_condvar; + mod futex_mutex; + mod futex_rwlock; + pub(crate) use futex_condvar::Condvar; + pub(crate) use futex_mutex::Mutex; + pub(crate) use futex_rwlock::RwLock; + } + } else { + #[path = "../unsupported/locks/mod.rs"] + pub mod locks; #[path = "../unsupported/once.rs"] pub mod once; + #[path = "../unsupported/thread_parking.rs"] + pub mod thread_parking; } } @@ -107,6 +118,12 @@ pub fn hashmap_random_keys() -> (u64, u64) { return ret; } +pub use super::common::cvt::{ + IsMinusOne, + cvt, + cvt_r +}; + fn err2io(err: wasi::Errno) -> std_io::Error { std_io::Error::from_raw_os_error(err.raw().into()) } diff --git a/library/std/src/sys/wasi/net.rs b/library/std/src/sys/wasi/net.rs index 2239880ffbef4..19b3d53e831ed 100644 --- a/library/std/src/sys/wasi/net.rs +++ b/library/std/src/sys/wasi/net.rs @@ -223,7 +223,7 @@ impl TcpListener { pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { let fd = unsafe { - wasi::sock_accept(self.as_inner().as_inner().as_raw_fd() as _, 0).map_err(err2io)? + wasi::sock_accept_v2(self.as_inner().as_inner().as_raw_fd() as _, 0).map_err(err2io)? }; Ok(( @@ -234,6 +234,10 @@ impl TcpListener { )) } + pub fn accept_timeout(&self, _timeout: crate::time::Duration) -> io::Result<(TcpStream, SocketAddr)> { + unsupported() + } + pub fn duplicate(&self) -> io::Result { unsupported() } diff --git a/library/std/src/sys/wasi/os.rs b/library/std/src/sys/wasi/os.rs index 9919dc7087ec1..322ee730acb52 100644 --- a/library/std/src/sys/wasi/os.rs +++ b/library/std/src/sys/wasi/os.rs @@ -62,9 +62,10 @@ pub fn error_string(errno: i32) -> String { let p = buf.as_mut_ptr(); unsafe { if libc::strerror_r(errno as libc::c_int, p, buf.len()) < 0 { - panic!("strerror_r failure"); + format!("unknown error (errno={})", errno) + } else { + str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() } - str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() } } @@ -142,10 +143,39 @@ impl StdError for JoinPathsError { pub fn current_exe() -> io::Result { unsupported() } + pub struct Env { iter: vec::IntoIter<(OsString, OsString)>, } +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + slice: &'a [(OsString, OsString)], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap()))) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + f.debug_list().entries(iter.as_slice()).finish() + } +} + impl !Send for Env {} impl !Sync for Env {} @@ -196,16 +226,23 @@ pub fn env() -> Env { } pub fn getenv(k: &OsStr) -> Option { - let s = run_with_cstr(k.as_bytes(), |k| unsafe { + // environment variables with a nul byte can't be set, so their value is + // always None as well + run_with_cstr(k.as_bytes(), |k| { let _guard = env_read_lock(); - Ok(libc::getenv(k.as_ptr()) as *const libc::c_char) + let v = unsafe { libc::getenv(k.as_ptr()) } as *const libc::c_char; + + if v.is_null() { + Ok(None) + } else { + // SAFETY: `v` cannot be mutated while executing this line since we've a read lock + let bytes = unsafe { CStr::from_ptr(v) }.to_bytes().to_vec(); + + Ok(Some(OsStringExt::from_vec(bytes))) + } }) - .ok()?; - if s.is_null() { - None - } else { - Some(OsStringExt::from_vec(unsafe { CStr::from_ptr(s) }.to_bytes().to_vec())) - } + .ok() + .flatten() } pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { @@ -224,6 +261,11 @@ pub fn unsetenv(n: &OsStr) -> io::Result<()> { }) } +#[allow(dead_code)] +pub fn page_size() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } +} + pub fn temp_dir() -> PathBuf { panic!("no filesystem on wasm") } diff --git a/library/std/src/sys/wasi/thread.rs b/library/std/src/sys/wasi/thread.rs index e7a6ab4be826f..376e6a353a229 100644 --- a/library/std/src/sys/wasi/thread.rs +++ b/library/std/src/sys/wasi/thread.rs @@ -1,5 +1,3 @@ -#![deny(unsafe_op_in_unsafe_fn)] - use crate::ffi::CStr; use crate::io; use crate::mem; @@ -7,13 +5,128 @@ use crate::num::NonZeroUsize; use crate::sys::unsupported; use crate::time::Duration; -pub struct Thread(!); +cfg_if::cfg_if! { + if #[cfg(target_feature = "atomics")] { + use crate::cmp; + use crate::ptr; + use crate::sys::os; + // Add a few symbols not in upstream `libc` just yet. + mod libc { + pub use crate::ffi; + pub use crate::mem; + pub use libc::*; + + // defined in wasi-libc + // https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-top-half/musl/include/alltypes.h.in#L108 + #[repr(C)] + union pthread_attr_union { + __i: [ffi::c_int; if mem::size_of::() == 8 { 14 } else { 9 }], + __vi: [ffi::c_int; if mem::size_of::() == 8 { 14 } else { 9 }], + __s: [ffi::c_ulong; if mem::size_of::() == 8 { 7 } else { 9 }], + } + + #[repr(C)] + pub struct pthread_attr_t { + __u: pthread_attr_union, + } + + #[allow(non_camel_case_types)] + pub type pthread_t = *mut ffi::c_void; + + extern "C" { + pub fn pthread_create( + native: *mut pthread_t, + attr: *const pthread_attr_t, + f: extern "C" fn(*mut ffi::c_void) -> *mut ffi::c_void, + value: *mut ffi::c_void, + ) -> ffi::c_int; + pub fn pthread_join(native: pthread_t, value: *mut *mut ffi::c_void) -> ffi::c_int; + pub fn pthread_attr_init(attrp: *mut pthread_attr_t) -> ffi::c_int; + pub fn pthread_attr_setstacksize( + attr: *mut pthread_attr_t, + stack_size: libc::size_t, + ) -> ffi::c_int; + pub fn pthread_attr_destroy(attr: *mut pthread_attr_t) -> ffi::c_int; + pub fn pthread_detach(thread: pthread_t) -> ffi::c_int; + } + } + + pub struct Thread { + id: libc::pthread_t, + } + + impl Drop for Thread { + fn drop(&mut self) { + let ret = unsafe { libc::pthread_detach(self.id) }; + debug_assert_eq!(ret, 0); + } + } + } else { + pub struct Thread(!); + } +} pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; impl Thread { // unsafe: see thread::Builder::spawn_unchecked for safety requirements - pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + cfg_if::cfg_if! { + if #[cfg(target_feature = "atomics")] { + pub unsafe fn new(stack: usize, p: Box) -> io::Result { + let p = Box::into_raw(Box::new(p)); + let mut native: libc::pthread_t = mem::zeroed(); + let mut attr: libc::pthread_attr_t = mem::zeroed(); + assert_eq!(libc::pthread_attr_init(&mut attr), 0); + + let stack_size = cmp::max(stack, DEFAULT_MIN_STACK_SIZE); + + match libc::pthread_attr_setstacksize(&mut attr, stack_size) { + 0 => {} + n => { + assert_eq!(n, libc::EINVAL); + // EINVAL means |stack_size| is either too small or not a + // multiple of the system page size. Because it's definitely + // >= PTHREAD_STACK_MIN, it must be an alignment issue. + // Round up to the nearest page and try again. + let page_size = os::page_size(); + let stack_size = + (stack_size + page_size - 1) & (-(page_size as isize - 1) as usize - 1); + assert_eq!(libc::pthread_attr_setstacksize(&mut attr, stack_size), 0); + } + }; + + let ret = libc::pthread_create(&mut native, &attr, thread_start, p as *mut _); + // Note: if the thread creation fails and this assert fails, then p will + // be leaked. However, an alternative design could cause double-free + // which is clearly worse. + assert_eq!(libc::pthread_attr_destroy(&mut attr), 0); + + return if ret != 0 { + // The thread failed to start and as a result p was not consumed. Therefore, it is + // safe to reconstruct the box so that it gets deallocated. + drop(Box::from_raw(p)); + Err(io::Error::from_raw_os_error(ret)) + } else { + Ok(Thread { id: native }) + }; + + extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void { + unsafe { + // Finally, let's run some code. + Box::from_raw(main as *mut Box)(); + } + ptr::null_mut() + } + } + } else { + pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + unsupported() + } + } + } + + pub unsafe fn new_reactor(_p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { unsupported() } @@ -62,7 +175,19 @@ impl Thread { } pub fn join(self) { - self.0 + cfg_if::cfg_if! { + if #[cfg(target_feature = "atomics")] { + unsafe { + let ret = libc::pthread_join(self.id, ptr::null_mut()); + mem::forget(self); + if ret != 0 { + rtabort!("failed to join thread: {}", io::Error::from_raw_os_error(ret)); + } + } + } else { + self.0 + } + } } } @@ -72,6 +197,7 @@ pub fn available_parallelism() -> io::Result { pub mod guard { pub type Guard = !; + #[allow(dead_code)] pub unsafe fn current() -> Option { None } diff --git a/library/std/src/sys/wasix/atomics/futex.rs b/library/std/src/sys/wasix/atomics/futex.rs new file mode 100644 index 0000000000000..8f2967300ffb8 --- /dev/null +++ b/library/std/src/sys/wasix/atomics/futex.rs @@ -0,0 +1,64 @@ +use crate::sync::atomic::AtomicU32; +use crate::time::Duration; + +/// Wait for a futex_wake operation to wake us. +/// +/// Returns directly if the futex doesn't hold the expected value. +/// +/// Returns false on timeout, and true in all other cases. +pub fn futex_wait(futex: &AtomicU32, expected: u32, timeout: Option) -> bool { + let timeout = match timeout { + Some(timeout) => wasi::OptionTimestamp { + tag: wasi::OPTION_SOME.raw(), + u: wasi::OptionTimestampU { + some: timeout.as_nanos() as u64, + } + }, + None => wasi::OptionTimestamp { + tag: wasi::OPTION_NONE.raw(), + u: wasi::OptionTimestampU { + none: 0, + } + } + }; + + unsafe { + match wasi::futex_wait( + futex as *const AtomicU32 as *mut u32, + expected, + &timeout as *const wasi::OptionTimestamp + ) { + Ok(wasi::BOOL_FALSE) => false, + _ => true + } + } +} + +/// Wake up one thread that's blocked on futex_wait on this futex. +/// +/// Returns true if this actually woke up such a thread, +/// or false if no thread was waiting on this futex. +pub fn futex_wake(futex: &AtomicU32) -> bool { + unsafe { + match wasi::futex_wake( + futex as *const AtomicU32 as *mut u32, + ) { + Ok(wasi::BOOL_TRUE) => true, + Ok(_) => false, + Err(_) => false + } + } +} + +/// Wake up all threads that are waiting on futex_wait on this futex. +pub fn futex_wake_all(futex: &AtomicU32) -> bool { + unsafe { + match wasi::futex_wake_all( + futex as *const AtomicU32 as *mut u32, + ) { + Ok(wasi::BOOL_TRUE) => true, + Ok(_) => false, + Err(_) => false + } + } +} diff --git a/library/std/src/sys/wasix/atomics/thread_local_key.rs b/library/std/src/sys/wasix/atomics/thread_local_key.rs new file mode 100644 index 0000000000000..29b4efb282976 --- /dev/null +++ b/library/std/src/sys/wasix/atomics/thread_local_key.rs @@ -0,0 +1,60 @@ +#![allow(dead_code, unused, fuzzy_provenance_casts)] + +use crate::mem; + +pub type Key = u32; + +// Callback invoked when a thread local variable is destroyed (either when the thread +// exits or when the thread local variable is destroyed) +#[no_mangle] +#[inline(never)] +pub extern "C" fn _thread_local_destroy(user_data_low: i32, user_data_high: i32, val_low: i32, val_high: i32) { + let user_data_low: u64 = (user_data_low as u64) & 0xFFFFFFFF; + let user_data_high: u64 = (user_data_high as u64) << 32; + let user_data = user_data_low + user_data_high; + + let val_low: u64 = (val_low as u64) & 0xFFFFFFFF; + let val_high: u64 = (val_high as u64) << 32; + let val = val_low + val_high; + + if user_data != 0 { + unsafe { + #[cfg(target_arch = "wasm32")] + let user_data = user_data as u32; + let dtor: unsafe extern "C" fn(*mut u8) = mem::transmute(user_data); + let value = val as *mut u8; + dtor(value); + } + } +} + +#[inline] +pub unsafe fn create(dtor: Option) -> Key { + let user_data = match dtor { + Some(a) => a as u64, + None => 0, + }; + wasi::thread_local_create(user_data).unwrap() +} + +#[inline] +pub unsafe fn set(key: Key, value: *mut u8) { + let value = value as u64; + wasi::thread_local_set(key, value).unwrap(); +} + +#[inline] +pub unsafe fn get(key: Key) -> *mut u8 { + let value = wasi::thread_local_get(key).unwrap(); + value as *mut u8 +} + +#[inline] +pub unsafe fn destroy(key: Key) { + let _ = wasi::thread_local_destroy(key); +} + +#[inline] +pub fn requires_synchronized_create() -> bool { + false +} diff --git a/library/std/src/sys/wasix/fd.rs b/library/std/src/sys/wasix/fd.rs new file mode 100644 index 0000000000000..bffd2396f348f --- /dev/null +++ b/library/std/src/sys/wasix/fd.rs @@ -0,0 +1,377 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(dead_code)] + +use super::err2io; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::mem; +use crate::net::Shutdown; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner}; +use crate::io::Read; + +#[stable(feature = "rust1", since = "1.0.0")] +#[derive(Debug)] +pub struct WasiFd { + pub(super) fd: OwnedFd, +} + +pub use WasiFd as FileDesc; + +pub(super) fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] { + assert_eq!(mem::size_of::>(), mem::size_of::()); + assert_eq!(mem::align_of::>(), mem::align_of::()); + // SAFETY: `IoSliceMut` and `IoVec` have exactly the same memory layout + unsafe { mem::transmute(a) } +} + +pub(super) fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] { + assert_eq!(mem::size_of::>(), mem::size_of::()); + assert_eq!(mem::align_of::>(), mem::align_of::()); + // SAFETY: `IoSlice` and `CIoVec` have exactly the same memory layout + unsafe { mem::transmute(a) } +} + +impl WasiFd { + pub(crate) fn datasync(&self) -> io::Result<()> { + unsafe { wasi::fd_datasync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub(crate) fn pread(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result { + unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, iovec(bufs), offset).map_err(err2io) } + } + + pub(crate) fn pwrite(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result { + unsafe { + wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, ciovec(bufs), offset).map_err(err2io) + } + } + + pub(crate) fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + unsafe { wasi::fd_read(self.as_raw_fd() as wasi::Fd, iovec(bufs)).map_err(err2io) } + } + + pub(crate) fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + unsafe { + let bufs = [wasi::Iovec { + buf: buf.as_mut().as_mut_ptr() as *mut u8, + buf_len: buf.capacity(), + }]; + match wasi::fd_read(self.as_raw_fd() as wasi::Fd, &bufs) { + Ok(n) => { + buf.advance(n); + Ok(()) + } + Err(e) => Err(err2io(e)), + } + } + } + + pub(crate) fn write(&self, bufs: &[IoSlice<'_>]) -> io::Result { + unsafe { wasi::fd_write(self.as_raw_fd() as wasi::Fd, ciovec(bufs)).map_err(err2io) } + } + + pub(crate) fn seek(&self, pos: SeekFrom) -> io::Result { + let (whence, offset) = match pos { + SeekFrom::Start(pos) => (wasi::WHENCE_SET, pos as i64), + SeekFrom::End(pos) => (wasi::WHENCE_END, pos), + SeekFrom::Current(pos) => (wasi::WHENCE_CUR, pos), + }; + unsafe { wasi::fd_seek(self.as_raw_fd() as wasi::Fd, offset, whence).map_err(err2io) } + } + + pub(crate) fn tell(&self) -> io::Result { + unsafe { wasi::fd_tell(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + // FIXME: __wasi_fd_fdstat_get + + pub(crate) fn set_flags(&self, flags: wasi::Fdflags) -> io::Result<()> { + unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) } + } + + pub(crate) fn set_rights(&self, base: wasi::Rights, inheriting: wasi::Rights) -> io::Result<()> { + unsafe { + wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, base, inheriting) + .map_err(err2io) + } + } + + pub(crate) fn sync(&self) -> io::Result<()> { + unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub(crate) fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> { + unsafe { + wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io) + } + } + + pub(crate) fn allocate(&self, offset: u64, len: u64) -> io::Result<()> { + unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) } + } + + pub(crate) fn create_directory(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub(crate) fn link( + &self, + old_flags: wasi::Lookupflags, + old_path: &str, + new_fd: &WasiFd, + new_path: &str, + ) -> io::Result<()> { + unsafe { + wasi::path_link( + self.as_raw_fd() as wasi::Fd, + old_flags, + old_path, + new_fd.as_raw_fd() as wasi::Fd, + new_path, + ) + .map_err(err2io) + } + } + + pub(crate) fn open( + &self, + dirflags: wasi::Lookupflags, + path: &str, + oflags: wasi::Oflags, + fs_rights_base: wasi::Rights, + fs_rights_inheriting: wasi::Rights, + fs_flags: wasi::Fdflags, + ) -> io::Result { + unsafe { + wasi::path_open( + self.as_raw_fd() as wasi::Fd, + dirflags, + path, + oflags, + fs_rights_base, + fs_rights_inheriting, + fs_flags, + ) + .map(|fd| WasiFd::from_raw_fd(fd as RawFd)) + .map_err(err2io) + } + } + + pub(crate) fn readdir(&self, buf: &mut [u8], cookie: wasi::Dircookie) -> io::Result { + unsafe { + wasi::fd_readdir(self.as_raw_fd() as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie) + .map_err(err2io) + } + } + + pub(crate) fn readlink(&self, path: &str, buf: &mut [u8]) -> io::Result { + unsafe { + wasi::path_readlink(self.as_raw_fd() as wasi::Fd, path, buf.as_mut_ptr(), buf.len()) + .map_err(err2io) + } + } + + pub(crate) fn rename(&self, old_path: &str, new_fd: &WasiFd, new_path: &str) -> io::Result<()> { + unsafe { + wasi::path_rename( + self.as_raw_fd() as wasi::Fd, + old_path, + new_fd.as_raw_fd() as wasi::Fd, + new_path, + ) + .map_err(err2io) + } + } + + pub(crate) fn filestat_get(&self) -> io::Result { + unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) } + } + + pub(crate) fn filestat_set_times( + &self, + atim: wasi::Timestamp, + mtim: wasi::Timestamp, + fstflags: wasi::Fstflags, + ) -> io::Result<()> { + unsafe { + wasi::fd_filestat_set_times(self.as_raw_fd() as wasi::Fd, atim, mtim, fstflags) + .map_err(err2io) + } + } + + pub(crate) fn filestat_set_size(&self, size: u64) -> io::Result<()> { + unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) } + } + + pub(crate) fn path_filestat_get( + &self, + flags: wasi::Lookupflags, + path: &str, + ) -> io::Result { + unsafe { + wasi::path_filestat_get(self.as_raw_fd() as wasi::Fd, flags, path).map_err(err2io) + } + } + + pub(crate) fn path_filestat_set_times( + &self, + flags: wasi::Lookupflags, + path: &str, + atim: wasi::Timestamp, + mtim: wasi::Timestamp, + fstflags: wasi::Fstflags, + ) -> io::Result<()> { + unsafe { + wasi::path_filestat_set_times( + self.as_raw_fd() as wasi::Fd, + flags, + path, + atim, + mtim, + fstflags, + ) + .map_err(err2io) + } + } + + pub(crate) fn symlink(&self, old_path: &str, new_path: &str) -> io::Result<()> { + unsafe { + wasi::path_symlink(old_path, self.as_raw_fd() as wasi::Fd, new_path).map_err(err2io) + } + } + + pub(crate) fn unlink_file(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub(crate) fn remove_directory(&self, path: &str) -> io::Result<()> { + unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) } + } + + pub(crate) fn sock_accept(&self, flags: wasi::Fdflags) -> io::Result { + let ret = unsafe { + wasi::sock_accept_v2(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io)? + }; + Ok(ret.0) + } + + pub(crate) fn sock_recv( + &self, + ri_data: &mut [IoSliceMut<'_>], + ri_flags: wasi::Riflags, + ) -> io::Result<(usize, wasi::Roflags)> { + let (amt, flags) = unsafe { + wasi::sock_recv(self.as_raw_fd() as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io)? + }; + Ok((amt as usize, flags)) + } + + pub(crate) fn sock_send(&self, si_data: &[IoSlice<'_>], si_flags: wasi::Siflags) -> io::Result { + unsafe { + wasi::sock_send(self.as_raw_fd() as wasi::Fd, ciovec(si_data), si_flags).map(|a| a as usize).map_err(err2io) + } + } + + pub(crate) fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> { + let how = match how { + Shutdown::Read => wasi::SDFLAGS_RD, + Shutdown::Write => wasi::SDFLAGS_WR, + Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD, + }; + unsafe { wasi::sock_shutdown(self.as_raw_fd() as wasi::Fd, how).map_err(err2io) } + } + + pub(crate) fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + let fdstat = unsafe { + wasi::fd_fdstat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io)? + }; + + let mut flags = fdstat.fs_flags; + + if nonblocking { + flags |= wasi::FDFLAGS_NONBLOCK; + } else { + flags &= !wasi::FDFLAGS_NONBLOCK; + } + + unsafe { + wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags) + .map_err(err2io)?; + } + + Ok(()) + } + + #[inline] + pub(crate) fn duplicate(&self) -> io::Result { + let raw_fd = unsafe { + wasi::fd_dup(self.as_raw_fd() as wasi::Fd).map_err(err2io)? + } as RawFd; + Ok( + unsafe { Self { fd: OwnedFd::from_raw_fd(raw_fd) } } + ) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl<'a> Read for &'a WasiFd { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + WasiFd::read(self, &mut [IoSliceMut::new(buf)]) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl AsInner for WasiFd { + fn as_inner(&self) -> &OwnedFd { + &self.fd + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl AsInnerMut for WasiFd { + fn as_inner_mut(&mut self) -> &mut OwnedFd { + &mut self.fd + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl IntoInner for WasiFd { + fn into_inner(self) -> OwnedFd { + self.fd + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl FromInner for WasiFd { + fn from_inner(owned_fd: OwnedFd) -> Self { + Self { fd: owned_fd } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl AsFd for WasiFd { + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl AsRawFd for WasiFd { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl IntoRawFd for WasiFd { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl FromRawFd for WasiFd { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + unsafe { Self { fd: OwnedFd::from_raw_fd(raw_fd) } } + } +} diff --git a/library/std/src/sys/wasix/mod.rs b/library/std/src/sys/wasix/mod.rs new file mode 100644 index 0000000000000..46bbe54ccb4c7 --- /dev/null +++ b/library/std/src/sys/wasix/mod.rs @@ -0,0 +1,117 @@ +//! System bindings for the wasm/web platform on WASIX + +use crate::io as std_io; +use crate::mem; + +#[path = "../unix/alloc.rs"] +pub mod alloc; +#[path = "../wasi/args.rs"] +pub mod args; +#[path = "../unix/cmath.rs"] +pub mod cmath; +#[path = "../wasi/env.rs"] +pub mod env; +pub mod fd; +#[path = "../wasi/fs.rs"] +pub mod fs; +#[path = "../wasi/io.rs"] +pub mod io; +pub mod net; +pub mod os; +#[path = "../unix/os_str.rs"] +pub mod os_str; +#[path = "../unix/path.rs"] +pub mod path; +pub mod pipe; +pub mod process; +#[path = "../wasi/stdio.rs"] +pub mod stdio; +#[path = "../unsupported/thread_local_dtor.rs"] +pub mod thread_local_dtor; +#[path = "../unix/thread_local_key.rs"] +//#[path = "atomics/thread_local_key.rs"] +pub mod thread_local_key; +#[path = "../wasi/time.rs"] +pub mod time; + +#[path = "../unix/locks"] +pub mod locks { + // FIXME: still needed? + #![allow(unsafe_op_in_unsafe_fn)] + + mod futex_mutex; + mod futex_rwlock; + mod futex_condvar; + pub(crate) use futex_condvar::Condvar; + pub(crate) use futex_mutex::Mutex; + pub(crate) use futex_rwlock::RwLock; +} +//#[cfg_attr(target_pointer_width = "32", path = "../wasm/atomics/futex.rs")] +#[cfg_attr(target_pointer_width = "32", path = "atomics/futex.rs")] +#[cfg_attr(target_pointer_width = "64", path = "atomics/futex.rs")] +pub mod futex; +#[path = "../unix/thread.rs"] +pub mod thread; +#[path = "../unix/stack_overflow.rs"] +pub mod stack_overflow; + +#[path = "../unsupported/common.rs"] +#[deny(unsafe_op_in_unsafe_fn)] +#[allow(unused)] +mod common; +pub use common::*; + +pub fn decode_error_kind(errno: i32) -> std_io::ErrorKind { + use std_io::ErrorKind::*; + if errno > u16::MAX as i32 || errno < 0 { + return Uncategorized; + } + + match errno { + e if e == wasi::ERRNO_CONNREFUSED.raw().into() => ConnectionRefused, + e if e == wasi::ERRNO_CONNRESET.raw().into() => ConnectionReset, + e if e == wasi::ERRNO_PERM.raw().into() || e == wasi::ERRNO_ACCES.raw().into() => { + PermissionDenied + } + e if e == wasi::ERRNO_PIPE.raw().into() => BrokenPipe, + e if e == wasi::ERRNO_NOTCONN.raw().into() => NotConnected, + e if e == wasi::ERRNO_CONNABORTED.raw().into() => ConnectionAborted, + e if e == wasi::ERRNO_ADDRNOTAVAIL.raw().into() => AddrNotAvailable, + e if e == wasi::ERRNO_ADDRINUSE.raw().into() => AddrInUse, + e if e == wasi::ERRNO_NOENT.raw().into() => NotFound, + e if e == wasi::ERRNO_INTR.raw().into() => Interrupted, + e if e == wasi::ERRNO_INVAL.raw().into() => InvalidInput, + e if e == wasi::ERRNO_TIMEDOUT.raw().into() => TimedOut, + e if e == wasi::ERRNO_EXIST.raw().into() => AlreadyExists, + e if e == wasi::ERRNO_AGAIN.raw().into() => WouldBlock, + e if e == wasi::ERRNO_NOSYS.raw().into() => Unsupported, + e if e == wasi::ERRNO_NOMEM.raw().into() => OutOfMemory, + _ => Uncategorized, + } +} + +pub fn abort_internal() -> ! { + unsafe { libc::abort() } +} + +pub fn hashmap_random_keys() -> (u64, u64) { + let mut ret = (0u64, 0u64); + unsafe { + let base = &mut ret as *mut (u64, u64) as *mut u8; + let len = mem::size_of_val(&ret); + wasi::random_get(base, len as usize).expect("random_get failure"); + } + return ret; +} + +pub use super::common::cvt::{ + IsMinusOne, + cvt, + cvt_r +}; +pub use os::cvt_nz; + +pub(crate) fn err2io(err: I) -> std_io::Error +where I: Into { + std_io::Error::from_raw_os_error(err.into().raw().into()) +} diff --git a/library/std/src/sys/wasix/net.rs b/library/std/src/sys/wasix/net.rs new file mode 100644 index 0000000000000..93f4f5e12cf3a --- /dev/null +++ b/library/std/src/sys/wasix/net.rs @@ -0,0 +1,1373 @@ +#![deny(unsafe_op_in_unsafe_fn)] +#![allow(dead_code)] + +use super::err2io; +use super::fd::WasiFd; +use crate::collections::VecDeque; +use crate::fmt; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; + +use crate::sync::{Arc, Mutex}; +use crate::sys_common::{AsInner, FromInner, IntoInner}; +use crate::time::Duration; +use crate::time::Instant; +use libc::c_int; + +pub use crate::sys::{cvt, cvt_r}; + +pub struct Socket { + fd: Option, + addr: SocketAddr, + peer: Arc>>, +} + +impl Socket { + pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result { + let fam = match *addr { + SocketAddr::V4(..) => AF_INET, + SocketAddr::V6(..) => AF_INET6, + }; + let mut sock = Self::new_raw(fam, ty)?; + sock.addr = addr.clone(); + unsafe { + let addr = to_wasi_addr_port(addr.clone()); + wasi::sock_bind(sock.fd(), &addr).map_err(err2io)?; + } + Ok(sock) + } + + pub fn new_raw(fam: c_int, ty: c_int) -> io::Result { + unsafe { + let wasi_fam = match fam { + AF_INET6 => wasi::ADDRESS_FAMILY_INET6, + AF_INET => wasi::ADDRESS_FAMILY_INET4, + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid address family" + )); + } + }; + let wasi_ty = match ty { + SOCK_DGRAM => wasi::SOCK_TYPE_SOCKET_DGRAM, + SOCK_STREAM => wasi::SOCK_TYPE_SOCKET_STREAM, + SOCK_RAW => wasi::SOCK_TYPE_SOCKET_RAW, + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid socket type" + )); + } + }; + let wasi_proto = match ty { + SOCK_DGRAM => wasi::SOCK_PROTO_UDP, + SOCK_STREAM => wasi::SOCK_PROTO_TCP, + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid socket protocol" + )); + } + }; + let ip = match fam { + AF_INET6 => IpAddr::V6(Ipv6Addr::UNSPECIFIED), + AF_INET => IpAddr::V4(Ipv4Addr::UNSPECIFIED), + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid address family" + )); + } + }; + let fd = wasi::sock_open(wasi_fam, wasi_ty, wasi_proto).map_err(err2io)?; + Ok(Socket { + fd: Some(WasiFd::from_raw_fd(fd as RawFd)), + addr: SocketAddr::new(ip, 0), + peer: Arc::new(Mutex::new(None)), + }) + } + } + + pub fn new_pair(fam: c_int, _ty: c_int) -> io::Result<(Socket, Socket)> { + let ip = match fam { + AF_INET6 => IpAddr::V6(Ipv6Addr::UNSPECIFIED), + AF_INET => IpAddr::V4(Ipv4Addr::UNSPECIFIED), + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid address family" + )); + } + }; + let (fd1, fd2) = unsafe { wasi::fd_pipe().map_err(err2io)? }; + let socket1 = Socket { + fd: Some(unsafe { WasiFd::from_raw_fd(fd1 as RawFd) }), + addr: SocketAddr::new(ip, 0), + peer: Arc::new(Mutex::new(None)), + }; + let socket2 = Socket { + fd: Some(unsafe { WasiFd::from_raw_fd(fd2 as RawFd) }), + addr: SocketAddr::new(ip, 0), + peer: Arc::new(Mutex::new(None)), + }; + Ok((socket1, socket2)) + } + + pub fn connect(&self, addr: &SocketAddr) -> io::Result<()> { + let timeout = self + .timeout_internal(wasi::SOCK_OPTION_CONNECT_TIMEOUT)? + .unwrap_or_else(|| Duration::from_secs(20)); + self.connect_timeout(addr, timeout) + } + + pub fn connect_timeout(&self, addr: &SocketAddr, timeout: Duration) -> io::Result<()> { + let r = unsafe { + let addr = to_wasi_addr_port(*addr); + wasi::sock_connect(self.fd(), &addr).map_err(err2io) + }; + + match r { + Ok(_) => return Ok(()), + Err(ref e) if e.raw_os_error() == Some(wasi::ERRNO_INPROGRESS.raw() as i32) => {} + Err(e) => return Err(e), + } + + { + let mut peer = self.peer.lock().unwrap(); + peer.replace(addr.clone()); + } + + let mut pollfd = libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLOUT, revents: 0 }; + + if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + "cannot set a 0 duration timeout", + )); + } + + let start = Instant::now(); + + loop { + let elapsed = start.elapsed(); + if elapsed >= timeout { + return Err(io::const_io_error!(io::ErrorKind::TimedOut, "connection timed out")); + } + + let timeout = timeout - elapsed; + let mut timeout = timeout + .as_secs() + .saturating_mul(1_000) + .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000); + if timeout == 0 { + timeout = 1; + } + + let timeout = timeout.min(libc::c_int::MAX as u64) as c_int; + + match unsafe { libc::poll(&mut pollfd, 1, timeout) } { + -1 => { + let err = io::Error::last_os_error(); + if err.kind() != io::ErrorKind::Interrupted { + return Err(err); + } + } + 0 => {} + _ => { + // linux returns POLLOUT|POLLERR|POLLHUP for refused connections (!), so look + // for POLLHUP rather than read readiness + if pollfd.revents & libc::POLLHUP != 0 { + let e = self.take_error()?.unwrap_or_else(|| { + io::const_io_error!( + io::ErrorKind::Uncategorized, + "no error set after POLLHUP", + ) + }); + return Err(e); + } + + return Ok(()); + } + } + } + } + + pub fn accept(&self) -> io::Result { + let (fd, addr) = unsafe { wasi::sock_accept_v2(self.fd(), 0).map_err(err2io)? }; + let addr = conv_addr_port(addr); + Ok(Socket { + fd: Some(unsafe { WasiFd::from_raw_fd(fd as RawFd) }), + addr, + peer: Arc::new(Mutex::new(Some(addr))), + }) + } + + pub fn accept_timeout(&self, timeout: Duration) -> io::Result { + self.set_nonblocking(true)?; + loop { + let mut pollfd = + libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLIN, revents: 0 }; + let start = Instant::now(); + loop { + let elapsed = start.elapsed(); + if elapsed >= timeout { + return Err(io::const_io_error!( + io::ErrorKind::TimedOut, + "connection timed out" + )); + } + + let timeout = timeout - elapsed; + let mut timeout = timeout + .as_secs() + .saturating_mul(1_000) + .saturating_add(timeout.subsec_nanos() as u64 / 1_000_000); + if timeout == 0 { + timeout = 1; + } + + let timeout = timeout.min(libc::c_int::MAX as u64) as c_int; + + match unsafe { libc::poll(&mut pollfd, 1, timeout) } { + -1 => { + let err = io::Error::last_os_error(); + if err.kind() != io::ErrorKind::Interrupted { + return Err(err); + } + } + 0 => {} + _ => { + break; + } + } + } + + unsafe { + match wasi::sock_accept_v2(self.fd(), 0) { + Ok((fd, addr)) => { + let addr = conv_addr_port(addr); + return Ok(Socket { + fd: Some(WasiFd::from_raw_fd(fd as RawFd)), + addr, + peer: Arc::new(Mutex::new(Some(addr))), + }); + } + Err(wasi::x::ERRNO_AGAIN) => { + crate::thread::yield_now(); + continue; + } + Err(err) => return Err(err2io(err)), + } + } + } + } + + pub fn duplicate(&self) -> io::Result { + let peer = self.peer.lock().unwrap(); + let fd = unsafe { wasi::fd_dup(self.fd()).map_err(err2io)? }; + Ok(Socket { + fd: Some(unsafe { WasiFd::from_raw_fd(fd as RawFd) }), + addr: self.addr.clone(), + peer: Arc::new(Mutex::new(peer.clone())), + }) + } + + fn recv_with_flags( + &self, + ri_data: &mut [IoSliceMut<'_>], + ri_flags: wasi::Riflags, + ) -> io::Result<(usize, wasi::Roflags)> { + let (amt, flags) = unsafe { + wasi::sock_recv(self.fd(), super::fd::iovec(ri_data), ri_flags).map_err(err2io)? + }; + Ok((amt as usize, flags)) + } + + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + let mut data = [IoSliceMut::new(buf)]; + let ret = self.recv_with_flags(&mut data, 0)?; + Ok(ret.0) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.recv(buf) + } + + pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + unsafe { + let slice = crate::slice::from_raw_parts_mut( + buf.as_mut().as_mut_ptr() as *mut u8, + buf.capacity(), + ); + match self.recv(slice) { + Ok(n) => { + buf.advance(n); + Ok(()) + } + Err(e) => Err(e), + } + } + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + let mut data = [IoSliceMut::new(buf)]; + let ret = self.recv_with_flags(&mut data, MSG_PEEK as u16)?; + Ok(ret.0) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.recv_vectored(bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + self.is_recv_vectored() + } + + pub fn recv_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + let ret = self.recv_with_flags(bufs, 0)?; + Ok(ret.0) + } + + #[inline] + pub fn is_recv_vectored(&self) -> bool { + true + } + + fn recv_from_with_flags( + &self, + ri_data: &mut [IoSliceMut<'_>], + ri_flags: wasi::Riflags, + ) -> io::Result<(usize, wasi::Roflags, SocketAddr)> { + let ret = unsafe { + wasi::sock_recv_from(self.fd(), super::fd::iovec(ri_data), ri_flags).map_err(err2io)? + }; + Ok((ret.0 as usize, ret.1, conv_addr_port(ret.2))) + } + + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + let mut data = [IoSliceMut::new(buf)]; + let ret = self.recv_from_with_flags(&mut data, 0)?; + Ok((ret.0, ret.2)) + } + + pub fn recv_msg(&self, msg: &mut libc::msghdr) -> io::Result { + let n = cvt(unsafe { libc::recvmsg(self.as_raw_fd(), msg, libc::MSG_CMSG_CLOEXEC) })?; + Ok(n as usize) + } + + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + let mut data = [IoSliceMut::new(buf)]; + let ret = self.recv_from_with_flags(&mut data, MSG_PEEK as u16)?; + Ok((ret.0, ret.2)) + } + + fn send_with_flags( + &self, + si_data: &[IoSlice<'_>], + si_flags: wasi::Siflags, + ) -> io::Result { + unsafe { + wasi::sock_send(self.fd(), super::fd::ciovec(si_data), si_flags) + .map(|a| a as usize) + .map_err(err2io) + } + } + + pub fn send(&self, buf: &[u8]) -> io::Result { + let data = [IoSlice::new(buf)]; + self.send_with_flags(&data, 0) + } + + pub fn send_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + self.send_with_flags(bufs, 0) + } + + #[inline] + pub fn is_send_vectored(&self) -> bool { + true + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.send(buf) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + self.send_vectored(bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + self.is_send_vectored() + } + + fn send_to_with_flags( + &self, + si_data: &[IoSlice<'_>], + si_flags: wasi::Siflags, + addr: SocketAddr, + ) -> io::Result { + let addr = to_wasi_addr_port(addr); + unsafe { + wasi::sock_send_to(self.fd(), super::fd::ciovec(si_data), si_flags, &addr) + .map(|a| a as usize) + .map_err(err2io) + } + } + + pub fn send_to(&self, buf: &[u8], addr: SocketAddr) -> io::Result { + let data = [IoSlice::new(buf)]; + self.send_to_with_flags(&data, 0, addr) + } + + pub fn send_to_vectored(&self, bufs: &[IoSlice<'_>], addr: SocketAddr) -> io::Result { + self.send_to_with_flags(bufs, 0, addr) + } + + pub fn send_msg(&self, msg: &mut libc::msghdr) -> io::Result { + let n = cvt(unsafe { libc::sendmsg(self.as_raw_fd(), msg, 0) })?; + Ok(n as usize) + } + + pub fn set_timeout(&self, dur: Option, kind: libc::c_int) -> io::Result<()> { + let option = match kind { + SO_RCVTIMEO => wasi::SOCK_OPTION_RECV_TIMEOUT, + SO_SNDTIMEO => wasi::SOCK_OPTION_SEND_TIMEOUT, + SO_CONNTIMEO => wasi::SOCK_OPTION_CONNECT_TIMEOUT, + SO_ACCPTIMEO => wasi::SOCK_OPTION_ACCEPT_TIMEOUT, + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid timeout type" + )); + } + }; + self.set_timeout_internal(dur, option) + } + + pub fn timeout(&self, kind: libc::c_int) -> io::Result> { + let option = match kind { + SO_RCVTIMEO => wasi::SOCK_OPTION_RECV_TIMEOUT, + SO_SNDTIMEO => wasi::SOCK_OPTION_SEND_TIMEOUT, + SO_CONNTIMEO => wasi::SOCK_OPTION_CONNECT_TIMEOUT, + SO_ACCPTIMEO => wasi::SOCK_OPTION_ACCEPT_TIMEOUT, + _ => { + return Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "invalid timeout type" + )); + } + }; + self.timeout_internal(option) + } + + fn set_timeout_internal( + &self, + dur: Option, + option: wasi::SockOption, + ) -> io::Result<()> { + let dur = match dur { + Some(dur) => wasi::OptionTimestamp { + tag: wasi::OPTION_SOME.raw(), + u: wasi::OptionTimestampU { some: dur.as_nanos() as u64 }, + }, + None => wasi::OptionTimestamp { + tag: wasi::OPTION_NONE.raw(), + u: wasi::OptionTimestampU { none: 0 }, + }, + }; + unsafe { wasi::sock_set_opt_time(self.fd(), option, &dur).map_err(err2io) } + } + + fn timeout_internal(&self, option: wasi::SockOption) -> io::Result> { + let ret = unsafe { wasi::sock_get_opt_time(self.fd(), option).map_err(err2io)? }; + Ok(match ret.tag { + a if a == wasi::OPTION_SOME.raw() => unsafe { + Some(Duration::from_nanos(ret.u.some as u64)) + }, + a if a == wasi::OPTION_NONE.raw() => None, + _ => { + return Err(io::const_io_error!(io::ErrorKind::Uncategorized, "invalid response")); + } + }) + } + + pub fn peer_addr(&self) -> io::Result { + use crate::ops::Deref; + let peer = self.peer.lock().unwrap(); + if let Some(peer) = peer.deref() { + Ok(peer.clone()) + } else { + Ok(match self.addr { + SocketAddr::V4(..) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), + SocketAddr::V6(..) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), + }) + } + } + + pub fn socket_addr(&self) -> io::Result { + Ok(self.addr.clone()) + } + + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + let how = match how { + Shutdown::Read => wasi::SDFLAGS_RD, + Shutdown::Write => wasi::SDFLAGS_WR, + Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD, + }; + unsafe { wasi::sock_shutdown(self.fd(), how).map_err(err2io) } + } + + pub fn set_reuse_addr(&self, reuse: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_REUSE_ADDR, reuse) + } + + pub fn reuse_addr(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_REUSE_ADDR) + } + + pub fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_REUSE_PORT, reuse) + } + + pub fn reuse_port(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_REUSE_PORT) + } + + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_NO_DELAY, nodelay) + } + + pub fn nodelay(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_NO_DELAY) + } + + pub fn set_only_v6(&self, only_v6: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_ONLY_V6, only_v6) + } + + pub fn only_v6(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_ONLY_V6) + } + + pub fn set_broadcast(&self, broadcast: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_BROADCAST, broadcast) + } + + pub fn broadcast(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_BROADCAST) + } + pub fn set_multicast_loop_v4(&self, val: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_MULTICAST_LOOP_V4, val) + } + + pub fn multicast_loop_v4(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_MULTICAST_LOOP_V4) + } + + pub fn set_multicast_loop_v6(&self, val: bool) -> io::Result<()> { + self.set_opt_flag(wasi::SOCK_OPTION_MULTICAST_LOOP_V6, val) + } + + pub fn multicast_loop_v6(&self) -> io::Result { + self.get_opt_flag(wasi::SOCK_OPTION_MULTICAST_LOOP_V6) + } + + fn set_opt_flag(&self, sockopt: wasi::SockOption, val: bool) -> io::Result<()> { + unsafe { + let val = match val { + false => wasi::BOOL_FALSE, + true => wasi::BOOL_TRUE, + }; + wasi::sock_set_opt_flag(self.fd(), sockopt, val).map_err(err2io) + } + } + + fn get_opt_flag(&self, sockopt: wasi::SockOption) -> io::Result { + let val = unsafe { wasi::sock_get_opt_flag(self.fd(), sockopt).map_err(err2io)? }; + Ok(match val { + wasi::BOOL_TRUE => true, + wasi::BOOL_FALSE | _ => false, + }) + } + + pub fn set_linger(&self, val: Option) -> io::Result<()> { + let val = match val { + Some(dur) => wasi::OptionTimestamp { + tag: wasi::OPTION_SOME.raw(), + u: wasi::OptionTimestampU { some: dur.as_nanos() as u64 }, + }, + None => wasi::OptionTimestamp { + tag: wasi::OPTION_NONE.raw(), + u: wasi::OptionTimestampU { none: 0 }, + }, + }; + unsafe { + wasi::sock_set_opt_time(self.fd(), wasi::SOCK_OPTION_LINGER, &val).map_err(err2io) + } + } + + pub fn linger(&self) -> io::Result> { + let ret = unsafe { + wasi::sock_get_opt_time(self.fd(), wasi::SOCK_OPTION_LINGER).map_err(err2io)? + }; + Ok(match ret.tag { + a if a == wasi::OPTION_SOME.raw() => unsafe { + Some(Duration::from_nanos(ret.u.some as u64)) + }, + a if a == wasi::OPTION_NONE.raw() => None, + _ => { + return Err(io::const_io_error!(io::ErrorKind::Uncategorized, "invalid response")); + } + }) + } + + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + let fdstat = unsafe { wasi::fd_fdstat_get(self.fd()).map_err(err2io)? }; + + let mut flags = fdstat.fs_flags; + + if nonblocking { + flags |= wasi::FDFLAGS_NONBLOCK; + } else { + flags &= !wasi::FDFLAGS_NONBLOCK; + } + + unsafe { + wasi::fd_fdstat_set_flags(self.fd(), flags).map_err(err2io)?; + } + + Ok(()) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + unsafe { + wasi::sock_set_opt_size(self.fd(), wasi::SOCK_OPTION_TTL, ttl as wasi::Filesize) + .map_err(err2io) + } + } + + pub fn ttl(&self) -> io::Result { + let ttl = unsafe { + wasi::sock_get_opt_size(self.fd(), wasi::SOCK_OPTION_TTL).map_err(err2io)? as u32 + }; + Ok(ttl) + } + + pub fn set_multicast_ttl_v4(&self, ttl: u32) -> io::Result<()> { + unsafe { + wasi::sock_set_opt_size( + self.fd(), + wasi::SOCK_OPTION_MULTICAST_TTL_V4, + ttl as wasi::Filesize, + ) + .map_err(err2io) + } + } + + pub fn multicast_ttl_v4(&self) -> io::Result { + let ttl = unsafe { + wasi::sock_get_opt_size(self.fd(), wasi::SOCK_OPTION_MULTICAST_TTL_V4) + .map_err(err2io)? as u32 + }; + Ok(ttl) + } + + pub fn join_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { + let multiaddr = to_wasi_addr_v4(*multiaddr); + let interface = to_wasi_addr_v4(*interface); + unsafe { wasi::sock_join_multicast_v4(self.fd(), &multiaddr, &interface).map_err(err2io) } + } + + pub fn join_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { + let multiaddr = to_wasi_addr_v6(*multiaddr); + unsafe { wasi::sock_join_multicast_v6(self.fd(), &multiaddr, interface).map_err(err2io) } + } + + pub fn leave_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { + let multiaddr = to_wasi_addr_v4(*multiaddr); + let interface = to_wasi_addr_v4(*interface); + unsafe { wasi::sock_leave_multicast_v4(self.fd(), &multiaddr, &interface).map_err(err2io) } + } + + pub fn leave_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { + let multiaddr = to_wasi_addr_v6(*multiaddr); + unsafe { wasi::sock_leave_multicast_v6(self.fd(), &multiaddr, interface).map_err(err2io) } + } + + pub fn take_error(&self) -> io::Result> { + Ok(None) + } + + pub fn as_raw(&self) -> RawFd { + self.as_raw_fd() + } + + fn fd(&self) -> wasi::Fd { + self.as_raw_fd() as wasi::Fd + } +} + +impl Drop for Socket { + fn drop(&mut self) { + unsafe { + if let Some(fd) = self.fd.take() { + let _ = wasi::fd_close(fd.as_raw_fd() as wasi::Fd); + } + } + } +} + +fn conv_addr_port(addr: wasi::AddrPort) -> SocketAddr { + unsafe { + match addr.tag { + a if a == wasi::ADDRESS_FAMILY_INET6.raw() => { + let u = &addr.u.inet6.addr; + let ip = IpAddr::V6(Ipv6Addr::new(u.n0, u.n1, u.n2, u.n3, u.h0, u.h1, u.h2, u.h3)); + SocketAddr::new(ip, addr.u.inet6.port) + } + _ => { + let u = &addr.u.inet4.addr; + let ip = IpAddr::V4(Ipv4Addr::new(u.n0, u.n1, u.h0, u.h1)); + SocketAddr::new(ip, addr.u.inet4.port) + } + } + } +} + +fn conv_addr_v4(u: wasi::AddrIp4) -> Ipv4Addr { + Ipv4Addr::new(u.n0, u.n1, u.h0, u.h1) +} + +fn conv_addr_v6(u: wasi::AddrIp6) -> Ipv6Addr { + Ipv6Addr::new(u.n0, u.n1, u.n2, u.n3, u.h0, u.h1, u.h2, u.h3) +} + +fn conv_addr(addr: wasi::Addr) -> IpAddr { + unsafe { + match addr.tag { + a if a == wasi::ADDRESS_FAMILY_INET6.raw() => IpAddr::V6(conv_addr_v6(addr.u.inet6)), + _ => IpAddr::V4(conv_addr_v4(addr.u.inet4)), + } + } +} + +fn to_wasi_addr_port(addr: SocketAddr) -> wasi::AddrPort { + let ip = to_wasi_addr(addr.ip()); + unsafe { + wasi::AddrPort { + tag: ip.tag, + u: match ip.tag { + a if a == wasi::ADDRESS_FAMILY_INET6.raw() => wasi::AddrPortU { + inet6: wasi::AddrIp6Port { addr: ip.u.inet6, port: addr.port() }, + }, + _ => wasi::AddrPortU { + inet4: wasi::AddrIp4Port { addr: ip.u.inet4, port: addr.port() }, + }, + }, + } + } +} + +fn to_wasi_addr_v4(ip: Ipv4Addr) -> wasi::AddrIp4 { + let octs = ip.octets(); + wasi::AddrIp4 { n0: octs[0], n1: octs[1], h0: octs[2], h1: octs[3] } +} + +fn to_wasi_addr_v6(ip: Ipv6Addr) -> wasi::AddrIp6 { + let segs = ip.segments(); + wasi::AddrIp6 { + n0: segs[0], + n1: segs[1], + n2: segs[2], + n3: segs[3], + h0: segs[4], + h1: segs[5], + h2: segs[6], + h3: segs[7], + } +} + +fn to_wasi_addr(addr: IpAddr) -> wasi::Addr { + match addr { + IpAddr::V4(ip) => wasi::Addr { + tag: wasi::ADDRESS_FAMILY_INET4.raw(), + u: wasi::AddrU { inet4: to_wasi_addr_v4(ip) }, + }, + IpAddr::V6(ip) => wasi::Addr { + tag: wasi::ADDRESS_FAMILY_INET6.raw(), + u: wasi::AddrU { inet6: to_wasi_addr_v6(ip) }, + }, + } +} + +impl AsInner for Socket { + fn as_inner(&self) -> &WasiFd { + self.fd.as_ref().unwrap() + } +} + +impl IntoInner for Socket { + fn into_inner(mut self) -> WasiFd { + self.fd.take().unwrap() + } +} + +impl FromInner for Socket { + fn from_inner(inner: WasiFd) -> Socket { + Socket { + fd: Some(inner), + addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), + peer: Arc::new(Mutex::new(None)), + } + } +} + +impl AsFd for Socket { + fn as_fd(&self) -> BorrowedFd<'_> { + let fd = self.as_raw_fd(); + unsafe { BorrowedFd::borrow_raw(fd) } + } +} + +impl AsRawFd for Socket { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_ref().map(|fd| fd.as_raw_fd()).unwrap_or_default() + } +} + +impl IntoRawFd for Socket { + fn into_raw_fd(mut self) -> RawFd { + self.fd.take().map(|fd| fd.as_raw_fd()).unwrap_or_default() + } +} + +impl FromRawFd for Socket { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + Self { + fd: Some(unsafe { WasiFd::from_raw_fd(raw_fd) }), + addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), + peer: Arc::new(Mutex::new(None)), + } + } +} + +pub struct TcpStream { + inner: Socket, +} + +impl TcpStream { + pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result { + let addr = addr?; + let fam = match *addr { + SocketAddr::V4(..) => AF_INET, + SocketAddr::V6(..) => AF_INET6, + }; + let sock = Socket::new_raw(fam, SOCK_STREAM)?; + sock.connect(addr)?; + Ok(TcpStream { inner: sock }) + } + + pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result { + let fam = match *addr { + SocketAddr::V4(..) => AF_INET, + SocketAddr::V6(..) => AF_INET6, + }; + let sock = Socket::new_raw(fam, SOCK_STREAM)?; + sock.connect_timeout(addr, timeout)?; + Ok(TcpStream { inner: sock }) + } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + self.inner.set_timeout_internal(timeout, wasi::SOCK_OPTION_RECV_TIMEOUT) + } + + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + self.inner.set_timeout_internal(timeout, wasi::SOCK_OPTION_SEND_TIMEOUT) + } + + pub fn read_timeout(&self) -> io::Result> { + self.inner.timeout_internal(wasi::SOCK_OPTION_RECV_TIMEOUT) + } + + pub fn write_timeout(&self) -> io::Result> { + self.inner.timeout_internal(wasi::SOCK_OPTION_SEND_TIMEOUT) + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + self.inner.peek(buf) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.inner.recv(buf) + } + + pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> { + self.inner.read_buf(buf) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.inner.recv_vectored(bufs) + } + + pub fn is_read_vectored(&self) -> bool { + self.inner.is_recv_vectored() + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.inner.send(buf) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + self.inner.send_vectored(bufs) + } + + pub fn is_write_vectored(&self) -> bool { + self.inner.is_send_vectored() + } + + pub fn peer_addr(&self) -> io::Result { + self.inner.peer_addr() + } + + pub fn socket_addr(&self) -> io::Result { + self.inner.socket_addr() + } + + pub fn shutdown(&self, shutdown: Shutdown) -> io::Result<()> { + self.inner.shutdown(shutdown) + } + + pub fn duplicate(&self) -> io::Result { + Ok(TcpStream { inner: self.inner.duplicate()? }) + } + + pub fn set_linger(&self, linger: Option) -> io::Result<()> { + self.inner.set_linger(linger) + } + + pub fn linger(&self) -> io::Result> { + self.inner.linger() + } + + pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { + self.inner.set_nodelay(nodelay) + } + + pub fn nodelay(&self) -> io::Result { + self.inner.nodelay() + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + self.inner.set_ttl(ttl) + } + + pub fn ttl(&self) -> io::Result { + self.inner.ttl() + } + + pub fn take_error(&self) -> io::Result> { + self.inner.take_error() + } + + pub fn set_nonblocking(&self, state: bool) -> io::Result<()> { + self.inner.set_nonblocking(state) + } + + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } +} + +impl FromInner for TcpStream { + fn from_inner(socket: Socket) -> TcpStream { + TcpStream { inner: socket } + } +} + +impl fmt::Debug for TcpStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TcpStream").field("fd", &self.inner.as_raw_fd()).finish() + } +} + +pub struct TcpListener { + inner: Socket, +} + +impl TcpListener { + pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result { + let addr = addr?; + let sock = Socket::new(addr, SOCK_STREAM)?; + + sock.set_reuse_addr(true)?; + + unsafe { + wasi::sock_listen(sock.fd(), 128).map_err(err2io)?; + } + + Ok(TcpListener { inner: sock }) + } + + pub fn socket_addr(&self) -> io::Result { + self.inner.socket_addr() + } + + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + let socket = self.inner.accept()?; + let addr = socket.socket_addr()?; + Ok((TcpStream { inner: socket }, addr)) + } + + pub fn accept_timeout( + &self, + timeout: crate::time::Duration, + ) -> io::Result<(TcpStream, SocketAddr)> { + let socket = self.inner.accept_timeout(timeout)?; + let addr = socket.socket_addr()?; + Ok((TcpStream { inner: socket }, addr)) + } + + pub fn duplicate(&self) -> io::Result { + Ok(TcpListener { inner: self.inner.duplicate()? }) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + self.inner.set_ttl(ttl) + } + + pub fn ttl(&self) -> io::Result { + self.inner.ttl() + } + + pub fn set_only_v6(&self, only_v6: bool) -> io::Result<()> { + self.inner.set_only_v6(only_v6) + } + + pub fn only_v6(&self) -> io::Result { + self.inner.only_v6() + } + + pub fn take_error(&self) -> io::Result> { + self.inner.take_error() + } + + pub fn set_nonblocking(&self, state: bool) -> io::Result<()> { + self.inner.set_nonblocking(state) + } + + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } +} + +impl AsInner for TcpListener { + fn as_inner(&self) -> &Socket { + &self.inner + } +} + +impl IntoInner for TcpListener { + fn into_inner(self) -> Socket { + self.inner + } +} + +impl FromInner for TcpListener { + fn from_inner(inner: Socket) -> TcpListener { + TcpListener { inner } + } +} + +impl fmt::Debug for TcpListener { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TcpListener").field("fd", &self.inner.as_raw_fd()).finish() + } +} + +pub struct UdpSocket { + inner: Socket, +} + +impl UdpSocket { + pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result { + let addr = addr?; + let sock = Socket::new(addr, SOCK_DGRAM)?; + Ok(UdpSocket { inner: sock }) + } + + pub fn peer_addr(&self) -> io::Result { + self.inner.peer_addr() + } + + pub fn socket_addr(&self) -> io::Result { + self.inner.socket_addr() + } + + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.inner.recv_from(buf) + } + + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + self.inner.peek_from(buf) + } + + pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> io::Result { + self.inner.send_to(buf, *addr) + } + + pub fn duplicate(&self) -> io::Result { + let sock = self.inner.duplicate()?; + Ok(UdpSocket { inner: sock }) + } + + pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { + self.inner.set_timeout(dur, SO_RCVTIMEO) + } + + pub fn set_write_timeout(&self, dur: Option) -> io::Result<()> { + self.inner.set_timeout(dur, SO_SNDTIMEO) + } + + pub fn read_timeout(&self) -> io::Result> { + self.inner.timeout(SO_RCVTIMEO) + } + + pub fn write_timeout(&self) -> io::Result> { + self.inner.timeout(SO_SNDTIMEO) + } + + pub fn set_broadcast(&self, broadcast: bool) -> io::Result<()> { + self.inner.set_broadcast(broadcast) + } + + pub fn broadcast(&self) -> io::Result { + self.inner.broadcast() + } + + pub fn set_multicast_loop_v4(&self, val: bool) -> io::Result<()> { + self.inner.set_multicast_loop_v4(val) + } + + pub fn multicast_loop_v4(&self) -> io::Result { + self.inner.multicast_loop_v4() + } + + pub fn set_multicast_ttl_v4(&self, ttl: u32) -> io::Result<()> { + self.inner.set_multicast_ttl_v4(ttl) + } + + pub fn multicast_ttl_v4(&self) -> io::Result { + self.inner.multicast_ttl_v4() + } + + pub fn set_multicast_loop_v6(&self, val: bool) -> io::Result<()> { + self.inner.set_multicast_loop_v6(val) + } + + pub fn multicast_loop_v6(&self) -> io::Result { + self.inner.multicast_loop_v6() + } + + pub fn join_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { + self.inner.join_multicast_v4(multiaddr, interface) + } + + pub fn join_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { + self.inner.join_multicast_v6(multiaddr, interface) + } + + pub fn leave_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { + self.inner.leave_multicast_v4(multiaddr, interface) + } + + pub fn leave_multicast_v6(&self, multiaddr: &Ipv6Addr, interface: u32) -> io::Result<()> { + self.inner.leave_multicast_v6(multiaddr, interface) + } + + pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { + self.inner.set_ttl(ttl) + } + + pub fn ttl(&self) -> io::Result { + self.inner.ttl() + } + + pub fn take_error(&self) -> io::Result> { + self.inner.take_error() + } + + pub fn set_nonblocking(&self, val: bool) -> io::Result<()> { + self.inner.set_nonblocking(val) + } + + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + self.inner.recv(buf) + } + + pub fn peek(&self, buf: &mut [u8]) -> io::Result { + self.inner.peek(buf) + } + + pub fn send(&self, buf: &[u8]) -> io::Result { + self.inner.send(buf) + } + + pub fn connect(&self, addr: io::Result<&SocketAddr>) -> io::Result<()> { + let addr = addr?; + self.inner.connect(addr) + } + + pub fn socket(&self) -> &Socket { + &self.inner + } + + pub fn into_socket(self) -> Socket { + self.inner + } +} + +impl AsInner for UdpSocket { + fn as_inner(&self) -> &Socket { + &self.inner + } +} + +impl IntoInner for UdpSocket { + fn into_inner(self) -> Socket { + self.inner + } +} + +impl FromInner for UdpSocket { + fn from_inner(inner: Socket) -> UdpSocket { + UdpSocket { inner } + } +} + +impl fmt::Debug for UdpSocket { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UdpSocket").field("fd", &self.inner.as_raw_fd()).finish() + } +} + +pub struct LookupHost(VecDeque, u16); + +impl LookupHost { + pub fn port(&self) -> u16 { + self.1 + } +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option { + self.0.pop_front().map(|ip| SocketAddr::new(ip, self.port())) + } +} + +impl<'a> TryFrom<&'a str> for LookupHost { + type Error = io::Error; + + fn try_from(v: &'a str) -> io::Result { + macro_rules! try_opt { + ($e:expr, $msg:expr) => { + match $e { + Some(r) => r, + None => return Err(io::const_io_error!(io::ErrorKind::InvalidInput, $msg)), + } + }; + } + + // split the string by ':' and convert the second part to u16 + let (host, port_str) = try_opt!(v.rsplit_once(':'), "invalid socket address"); + let port: u16 = try_opt!(port_str.parse().ok(), "invalid port value"); + TryFrom::try_from((host, port)) + } +} + +impl<'a> TryFrom<(&'a str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(v: (&'a str, u16)) -> io::Result { + let host = v.0; + let port = v.1; + let mut ret = VecDeque::new(); + unsafe { + let mut ips = [crate::mem::MaybeUninit::::zeroed(); 50]; + let cnt = wasi::resolve(host, port, ips.as_mut_ptr() as *mut wasi::Addr, ips.len()) + .map_err(err2io)?; + for n in 0..cnt { + let ip = ips[n].assume_init(); + let ip = conv_addr(ip); + ret.push_back(ip); + } + } + Ok(LookupHost(ret, port)) + } +} + +#[allow(nonstandard_style)] +pub mod netc { + pub const AF_UNSPEC: i32 = 0; + pub const AF_INET: i32 = 1; + pub const AF_INET6: i32 = 2; + pub const AF_UNIX: i32 = 3; + pub type sa_family_t = u8; + + pub const SOCK_STREAM: i32 = 0; + pub const SOCK_DGRAM: i32 = 1; + pub const SOCK_RAW: i32 = 2; + + pub const SO_RCVTIMEO: i32 = 19; + pub const SO_SNDTIMEO: i32 = 20; + pub const SO_CONNTIMEO: i32 = 21; + pub const SO_ACCPTIMEO: i32 = 22; + + pub const MSG_PEEK: u32 = 1; + pub const MSG_WAITALL: u32 = 2; + pub const MSG_TRUNC: u32 = 4; + + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in { + pub sin_family: sa_family_t, + pub sin_port: u16, + pub sin_addr: in_addr, + } + + #[derive(Copy, Clone)] + pub struct in6_addr { + pub s6_addr: [u8; 16], + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in6 { + pub sin6_family: sa_family_t, + pub sin6_port: u16, + pub sin6_addr: in6_addr, + pub sin6_flowinfo: u32, + pub sin6_scope_id: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr {} + + pub type socklen_t = usize; +} + +use netc::*; diff --git a/library/std/src/sys/wasix/os.rs b/library/std/src/sys/wasix/os.rs new file mode 100644 index 0000000000000..5d6d7b8f81e5f --- /dev/null +++ b/library/std/src/sys/wasix/os.rs @@ -0,0 +1,316 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::error::Error as StdError; +use crate::ffi::{CStr, CString, OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::iter; +use crate::os::wasi::prelude::*; +use crate::path::{self, PathBuf}; +use crate::slice; +use crate::str; +use crate::sync::{LazyLock, Mutex, MutexGuard}; +use crate::sys::memchr; +use crate::vec; + +use libc::c_int; + +use super::err2io; + +const PATH_SEPARATOR: u8 = b':'; + +static ENV_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +pub fn env_lock<'a>() -> MutexGuard<'a, ()> { + ENV_LOCK.lock().unwrap() +} + +#[allow(unused, dead_code)] +pub fn errno() -> i32 { + extern "C" { + #[thread_local] + static errno: libc::c_int; + } + + unsafe { errno as i32 } +} + +#[allow(unused, dead_code)] +pub fn set_errno(e: i32) { + extern "C" { + #[thread_local] + static mut errno: c_int; + } + + unsafe { + errno = e; + } +} + +pub fn error_string(errno: i32) -> String { + let mut buf = [0 as libc::c_char; 1024]; + + let p = buf.as_mut_ptr(); + unsafe { + if libc::strerror_r(errno as libc::c_int, p, buf.len()) < 0 { + format!("unknown error (errno={})", errno) + } else { + str::from_utf8(CStr::from_ptr(p).to_bytes()).unwrap().to_owned() + } + } +} + +pub fn getcwd() -> io::Result { + let mut buf = Vec::::with_capacity(1024); + for _ in 0..2 { + unsafe { + let mut len = buf.capacity() as usize; + let ptr_buf = buf.as_mut_ptr() as *mut u8; + let ptr_len = &mut len as *mut usize; + match wasi::getcwd(ptr_buf, ptr_len) { + Ok(()) => { + buf.set_len(len as usize); + buf.shrink_to_fit(); + return Ok(PathBuf::from(OsString::from_vec(buf))); + } + Err(wasi::ERRNO_OVERFLOW) => { + buf = Vec::with_capacity(len as usize); + continue; + } + Err(err) => { + return Err(err2io(err)); + } + } + } + } + Err(err2io(wasi::ERRNO_INVAL)) +} + +pub fn chdir(p: &path::Path) -> io::Result<()> { + let p = p.to_str().ok_or_else(|| err2io(wasi::ERRNO_INVAL))?; + unsafe { wasi::chdir(p).map_err(err2io) } +} + +pub struct SplitPaths<'a> { + iter: iter::Map bool>, fn(&'a [u8]) -> PathBuf>, +} + +pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> { + fn bytes_to_path(b: &[u8]) -> PathBuf { + PathBuf::from(::from_bytes(b)) + } + fn is_separator(b: &u8) -> bool { + *b == PATH_SEPARATOR + } + let unparsed = unparsed.as_bytes(); + SplitPaths { + iter: unparsed + .split(is_separator as fn(&u8) -> bool) + .map(bytes_to_path as fn(&[u8]) -> PathBuf), + } +} + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + fn next(&mut self) -> Option { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths(paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + let mut joined = Vec::new(); + + for (i, path) in paths.enumerate() { + let path = path.as_ref().as_bytes(); + if i > 0 { + joined.push(PATH_SEPARATOR) + } + if path.contains(&PATH_SEPARATOR) { + return Err(JoinPathsError); + } + joined.extend_from_slice(path); + } + Ok(OsStringExt::from_vec(joined)) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "path segment contains separator `{}`", char::from(PATH_SEPARATOR)) + } +} + +impl StdError for JoinPathsError { + #[allow(deprecated)] + fn description(&self) -> &str { + "failed to join paths" + } +} + +pub fn current_exe() -> io::Result { + use crate::io::ErrorKind; + Err(io::const_io_error!(ErrorKind::Unsupported, "Not yet implemented!")) +} + +pub struct EnvStrDebug<'a> { + slice: &'a [(OsString, OsString)], +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { slice } = self; + f.debug_list() + .entries(slice.iter().map(|(a, b)| (a.to_str().unwrap(), b.to_str().unwrap()))) + .finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { iter } = self; + EnvStrDebug { slice: iter.as_slice() } + } +} + +#[derive(Debug)] +pub struct Env { + iter: vec::IntoIter<(OsString, OsString)>, +} + +impl !Send for Env {} +impl !Sync for Env {} + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + self.iter.next() + } + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +pub fn env() -> Env { + unsafe { + let _guard = env_lock(); + let mut environ = libc::environ; + let mut result = Vec::new(); + if !environ.is_null() { + while !(*environ).is_null() { + if let Some(key_value) = parse(CStr::from_ptr(*environ).to_bytes()) { + result.push(key_value); + } + environ = environ.add(1); + } + } + return Env { iter: result.into_iter() }; + } + + // See src/libstd/sys/unix/os.rs, same as that + fn parse(input: &[u8]) -> Option<(OsString, OsString)> { + if input.is_empty() { + return None; + } + let pos = memchr::memchr(b'=', &input[1..]).map(|p| p + 1); + pos.map(|p| { + ( + OsStringExt::from_vec(input[..p].to_vec()), + OsStringExt::from_vec(input[p + 1..].to_vec()), + ) + }) + } +} + +pub fn getenv(k: &OsStr) -> Option { + let k = CString::new(k.as_bytes()).ok()?; + unsafe { + let _guard = env_lock(); + let s = libc::getenv(k.as_ptr()) as *const libc::c_char; + if s.is_null() { + None + } else { + Some(OsStringExt::from_vec(CStr::from_ptr(s).to_bytes().to_vec())) + } + } +} + +pub fn setenv(k: &OsStr, v: &OsStr) -> io::Result<()> { + let k = CString::new(k.as_bytes())?; + let v = CString::new(v.as_bytes())?; + + unsafe { + let _guard = env_lock(); + cvt(libc::setenv(k.as_ptr(), v.as_ptr(), 1)).map(drop) + } +} + +pub fn unsetenv(n: &OsStr) -> io::Result<()> { + let nbuf = CString::new(n.as_bytes())?; + + unsafe { + let _guard = env_lock(); + cvt(libc::unsetenv(nbuf.as_ptr())).map(drop) + } +} + +#[allow(dead_code)] +pub fn page_size() -> usize { + unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } +} + +pub fn temp_dir() -> PathBuf { + crate::env::var_os("TMPDIR").map(PathBuf::from).unwrap_or_else(|| PathBuf::from("/tmp")) +} + +pub fn home_dir() -> Option { + None +} + +pub fn exit(code: i32) -> ! { + unsafe { libc::exit(code) } +} + +pub fn getpid() -> u32 { + unsafe { wasi::proc_id().map(|a| a as u32).map_err(err2io).unwrap_or_else(|_| 0u32) } +} + +#[doc(hidden)] +pub trait IsMinusOne { + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) +} + +impl_is_minus_one! { i8 i16 i32 i64 isize } + +fn cvt(t: T) -> io::Result { + if t.is_minus_one() { + Err(io::Error::last_os_error()) + } else { + Ok(t) + } +} + +#[allow(dead_code)] // Not used on all platforms. +pub fn cvt_nz(error: libc::c_int) -> crate::io::Result<()> { + if error == 0 { + Ok(()) + } else { + Err(crate::io::Error::from_raw_os_error(error)) + } +} diff --git a/library/std/src/sys/wasix/pipe.rs b/library/std/src/sys/wasix/pipe.rs new file mode 100644 index 0000000000000..4dd8459c00d47 --- /dev/null +++ b/library/std/src/sys/wasix/pipe.rs @@ -0,0 +1,141 @@ +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; +use crate::mem; +use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; +use crate::sys::fd::FileDesc; +use crate::sys_common::IntoInner; +use crate::sys::err2io; +use crate::io::Read; + +pub use crate::sys::{cvt, cvt_r}; + +//////////////////////////////////////////////////////////////////////////////// +// Anonymous pipes +//////////////////////////////////////////////////////////////////////////////// + +pub struct AnonPipe(FileDesc); + +pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> { + let (fd1, fd2) = unsafe { + wasi::fd_pipe().map_err(err2io)? + }; + let fd1 = fd1 as RawFd; + let fd2 = fd2 as RawFd; + + unsafe { + Ok((AnonPipe(FileDesc::from_raw_fd(fd1)), AnonPipe(FileDesc::from_raw_fd(fd2)))) + } +} + +impl AnonPipe { + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.0.read(&mut [IoSliceMut::new(buf)]) + } + + pub fn read_buf(&self, buf: BorrowedCursor<'_>) -> io::Result<()> { + self.0.read_buf(buf) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.0.read(bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn read_to_end(&self, buf: &mut Vec) -> io::Result { + (&self.0).read_to_end(buf) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.write_vectored(&[IoSlice::new(buf)]) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + self.0.write(bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + true + } +} + +impl IntoInner for AnonPipe { + fn into_inner(self) -> FileDesc { + self.0 + } +} + +pub fn read2(p1: AnonPipe, v1: &mut Vec, p2: AnonPipe, v2: &mut Vec) -> io::Result<()> { + // Set both pipes into nonblocking mode as we're gonna be reading from both + // in the `select` loop below, and we wouldn't want one to block the other! + let p1 = p1.into_inner(); + let p2 = p2.into_inner(); + p1.set_nonblocking(true)?; + p2.set_nonblocking(true)?; + + let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() }; + fds[0].fd = p1.as_raw_fd(); + fds[0].events = libc::POLLIN; + fds[1].fd = p2.as_raw_fd(); + fds[1].events = libc::POLLIN; + loop { + // wait for either pipe to become readable using `poll` + cvt_r(|| unsafe { libc::poll(fds.as_mut_ptr(), 2, -1) })?; + + if fds[0].revents != 0 && read(&p1, v1)? { + p2.set_nonblocking(false)?; + return (&p2).read_to_end(v2).map(drop); + } + if fds[1].revents != 0 && read(&p2, v2)? { + p1.set_nonblocking(false)?; + return (&p1).read_to_end(v1).map(drop); + } + } + + // Read as much as we can from each pipe, ignoring EWOULDBLOCK or + // EAGAIN. If we hit EOF, then this will happen because the underlying + // reader will return Ok(0), in which case we'll see `Ok` ourselves. In + // this case we flip the other fd back into blocking mode and read + // whatever's leftover on that file descriptor. + fn read(mut fd: &FileDesc, dst: &mut Vec) -> Result { + match fd.read_to_end(dst) { + Ok(_) => Ok(true), + Err(e) => { + if e.raw_os_error() == Some(libc::EWOULDBLOCK) + || e.raw_os_error() == Some(libc::EAGAIN) + { + Ok(false) + } else { + Err(e) + } + } + } + } +} + +impl AsRawFd for AnonPipe { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} + +impl AsFd for AnonPipe { + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl IntoRawFd for AnonPipe { + fn into_raw_fd(self) -> RawFd { + self.0.into_raw_fd() + } +} + +impl FromRawFd for AnonPipe { + unsafe fn from_raw_fd(raw_fd: RawFd) -> Self { + Self(FromRawFd::from_raw_fd(raw_fd)) + } +} diff --git a/library/std/src/sys/wasix/process/mod.rs b/library/std/src/sys/wasix/process/mod.rs new file mode 100644 index 0000000000000..043d93dc5a544 --- /dev/null +++ b/library/std/src/sys/wasix/process/mod.rs @@ -0,0 +1,11 @@ +pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes}; +pub use self::process_inner::{ExitStatus, ExitStatusError, Process}; +pub use crate::ffi::OsString as EnvKey; +pub use crate::sys_common::process::CommandEnvs; + +#[path = "../../unix/process/process_common.rs"] +#[cfg_attr(any(target_os = "espidf", target_os = "horizon"), allow(unused))] +mod process_common; + +#[path = "process_wasix.rs"] +mod process_inner; diff --git a/library/std/src/sys/wasix/process/process_wasix.rs b/library/std/src/sys/wasix/process/process_wasix.rs new file mode 100644 index 0000000000000..a3c58e43c84b6 --- /dev/null +++ b/library/std/src/sys/wasix/process/process_wasix.rs @@ -0,0 +1,319 @@ +#![allow(unused, dead_code)] +use crate::fmt; +use crate::io; +use crate::num::NonZeroI32; +use crate::sys::process::process_common::*; +use crate::sys::{unsupported, unsupported_err}; +use core::ffi::NonZero_c_int; + +use crate::io::ErrorKind; + +use libc::{c_int, pid_t}; + +pub use crate::sys::{cvt, cvt_nz, cvt_r}; + +//////////////////////////////////////////////////////////////////////////////// +// Command +//////////////////////////////////////////////////////////////////////////////// + +impl Command { + pub fn spawn( + &mut self, + default: Stdio, + needs_stdin: bool, + ) -> io::Result<(Process, StdioPipes)> { + let envp = self.capture_env(); + + if self.saw_nul() { + return Err(io::const_io_error!( + ErrorKind::InvalidInput, + "nul byte found in provided data", + )); + } + + let (ours, theirs) = self.setup_io(default, needs_stdin)?; + + let ret = self.posix_spawn(&theirs, envp.as_ref())?; + Ok((ret, ours)) + } + + pub fn output(&mut self) -> io::Result<(ExitStatus, Vec, Vec)> { + let (proc, pipes) = self.spawn(Stdio::MakePipe, false)?; + crate::sys_common::process::wait_with_output(proc, pipes) + } + + pub fn exec(&mut self, default: Stdio) -> io::Error { + let envp = self.capture_env(); + + if self.saw_nul() { + return io::const_io_error!(ErrorKind::InvalidInput, "nul byte found in provided data",); + } + + match self.setup_io(default, true) { + Ok((_, theirs)) => unsafe { + let Err(e) = self.do_exec(theirs, envp.as_ref()); + e + }, + Err(e) => e, + } + } + + unsafe fn do_exec( + &mut self, + stdio: ChildPipes, + maybe_envp: Option<&CStringArray>, + ) -> Result { + use crate::sys::{self, cvt_r}; + + if let Some(fd) = stdio.stdin.fd() { + cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))?; + } + if let Some(fd) = stdio.stdout.fd() { + cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))?; + } + if let Some(fd) = stdio.stderr.fd() { + cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))?; + } + + if let Some(ref cwd) = *self.get_cwd() { + cvt(libc::chdir(cwd.as_ptr()))?; + } + + for callback in self.get_closures().iter_mut() { + callback()?; + } + + libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr()); + Err(io::Error::last_os_error()) + } + + fn posix_spawn( + &mut self, + stdio: &ChildPipes, + envp: Option<&CStringArray>, + ) -> io::Result { + use crate::mem::MaybeUninit; + use crate::sys; + + let pgroup = self.get_pgroup(); + + // Safety: -1 indicates we don't have a pidfd. + let mut p = unsafe { Process::new(0, -1) }; + + struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit); + + impl Drop for PosixSpawnFileActions<'_> { + fn drop(&mut self) { + unsafe { + libc::posix_spawn_file_actions_destroy(self.0.as_mut_ptr()); + } + } + } + + struct PosixSpawnattr<'a>(&'a mut MaybeUninit); + + impl Drop for PosixSpawnattr<'_> { + fn drop(&mut self) { + unsafe { + libc::posix_spawnattr_destroy(self.0.as_mut_ptr()); + } + } + } + + unsafe { + let mut attrs = MaybeUninit::uninit(); + cvt_nz(libc::posix_spawnattr_init(attrs.as_mut_ptr()))?; + let attrs = PosixSpawnattr(&mut attrs); + + let mut flags = 0; + + let mut file_actions = MaybeUninit::uninit(); + cvt_nz(libc::posix_spawn_file_actions_init(file_actions.as_mut_ptr()))?; + let file_actions = PosixSpawnFileActions(&mut file_actions); + + if let Some(fd) = stdio.stdin.fd() { + cvt_nz(libc::posix_spawn_file_actions_adddup2( + file_actions.0.as_mut_ptr(), + fd, + libc::STDIN_FILENO, + ))?; + } + if let Some(fd) = stdio.stdout.fd() { + cvt_nz(libc::posix_spawn_file_actions_adddup2( + file_actions.0.as_mut_ptr(), + fd, + libc::STDOUT_FILENO, + ))?; + } + if let Some(fd) = stdio.stderr.fd() { + cvt_nz(libc::posix_spawn_file_actions_adddup2( + file_actions.0.as_mut_ptr(), + fd, + libc::STDERR_FILENO, + ))?; + } + + if let Some(pgroup) = pgroup { + flags |= libc::POSIX_SPAWN_SETPGROUP; + cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?; + } + + cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?; + + // Make sure we synchronize access to the global `environ` resource + let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| libc::environ as *const _); + + let spawn_fn = libc::posix_spawnp; + cvt_nz(spawn_fn( + &mut p.pid, + self.get_program_cstr().as_ptr(), + file_actions.0.as_ptr(), + attrs.0.as_ptr(), + self.get_argv().as_ptr() as *const _, + envp as *const _, + ))?; + Ok(p) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Processes +//////////////////////////////////////////////////////////////////////////////// + +pub struct Process { + pid: pid_t, + status: Option, +} + +impl Process { + unsafe fn new(pid: pid_t, _pidfd: pid_t) -> Self { + Process { pid, status: None } + } + + pub fn id(&self) -> u32 { + self.pid as u32 + } + + pub fn kill(&mut self) -> io::Result<()> { + // If we've already waited on this process then the pid can be recycled + // and used for another process, and we probably shouldn't be killing + // random processes, so just return an error. + if self.status.is_some() { + Err(io::const_io_error!( + ErrorKind::InvalidInput, + "invalid argument: can't kill an exited process", + )) + } else { + cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) + } + } + + pub fn wait(&mut self) -> io::Result { + use crate::sys::cvt_r; + if let Some(status) = self.status { + return Ok(status); + } + let mut status = 0 as c_int; + cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?; + self.status = Some(ExitStatus::new(status)); + Ok(ExitStatus::new(status)) + } + + pub fn try_wait(&mut self) -> io::Result> { + if let Some(status) = self.status { + return Ok(Some(status)); + } + let mut status = 0 as c_int; + let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?; + if pid == 0 { + Ok(None) + } else { + self.status = Some(ExitStatus::new(status)); + Ok(Some(ExitStatus::new(status))) + } + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Default)] +pub struct ExitStatus(c_int); + +impl fmt::Debug for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("wasix_wait_status").field(&self.0).finish() + } +} + +impl ExitStatus { + pub fn new(status: c_int) -> ExitStatus { + ExitStatus(status) + } + + fn exited(&self) -> bool { + libc::WIFEXITED(self.0) + } + + pub fn exit_ok(&self) -> Result<(), ExitStatusError> { + // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is + // true on all actual versions of Unix, is widely assumed, and is specified in SuS + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not + // true for a platform pretending to be Unix, the tests (our doctests, and also + // procsss_unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too. + match NonZero_c_int::try_from(self.0) { + /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)), + /* was zero, couldn't convert */ Err(_) => Ok(()), + } + } + + pub fn code(&self) -> Option { + self.exited().then(|| libc::WEXITSTATUS(self.0)) + } + + pub fn signal(&self) -> Option { + libc::WIFSIGNALED(self.0).then(|| libc::WTERMSIG(self.0)) + } + + pub fn core_dumped(&self) -> bool { + libc::WIFSIGNALED(self.0) && libc::WCOREDUMP(self.0) + } + + pub fn stopped_signal(&self) -> Option { + libc::WIFSTOPPED(self.0).then(|| libc::WSTOPSIG(self.0)) + } + + pub fn continued(&self) -> bool { + libc::WIFCONTINUED(self.0) + } + + pub fn into_raw(&self) -> c_int { + self.0 + } +} + +/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. +impl From for ExitStatus { + fn from(a: c_int) -> ExitStatus { + ExitStatus(a as i32) + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "exit code: {}", self.0) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitStatusError(NonZero_c_int); + +impl Into for ExitStatusError { + fn into(self) -> ExitStatus { + ExitStatus(self.0.into()) + } +} + +impl ExitStatusError { + pub fn code(self) -> Option { + ExitStatus(self.0.into()).code().map(|st| st.try_into().unwrap()) + } +} diff --git a/library/std/src/sys/wasm/alloc.rs b/library/std/src/sys/wasm/alloc.rs index 6dceb1689a8b7..553762dea0e17 100644 --- a/library/std/src/sys/wasm/alloc.rs +++ b/library/std/src/sys/wasm/alloc.rs @@ -74,7 +74,7 @@ mod lock { // form of `i32.atomic.wait` like so: // // unsafe { - // let r = core::arch::wasm32::i32_atomic_wait( + // let r = core::arch::wasm::i32_atomic_wait( // LOCKED.as_mut_ptr(), // 1, // expected value // -1, // timeout @@ -150,7 +150,7 @@ mod lock { // anyone up, but if we did it'd likely look something like this: // // unsafe { - // core::arch::wasm32::atomic_notify( + // core::arch::wasm::atomic_notify( // LOCKED.as_mut_ptr(), // 1, // only one thread // ); diff --git a/library/std/src/sys/wasm/atomics/mod.rs b/library/std/src/sys/wasm/atomics/mod.rs new file mode 100644 index 0000000000000..8fd99abb364ac --- /dev/null +++ b/library/std/src/sys/wasm/atomics/mod.rs @@ -0,0 +1,21 @@ +cfg_if::cfg_if! { + if #[cfg(any( + target_os = "linux", + target_os = "android", + all(target_arch = "wasm32", target_feature = "atomics"), + all(target_vendor = "wasmer", target_os = "wasi"), + target_os = "freebsd", + target_os = "openbsd", + target_os = "dragonfly", + ))] { + mod futex; + pub use futex::Parker; + } else if #[cfg(windows)] { + pub use crate::sys::thread_parker::Parker; + } else if #[cfg(target_family = "unix")] { + pub use crate::sys::thread_parker::Parker; + } else { + mod generic; + pub use generic::Parker; + } +} diff --git a/library/std/src/sys/wasm/atomics/thread.rs b/library/std/src/sys/wasm/atomics/thread.rs index 714b704922794..92278c3398493 100644 --- a/library/std/src/sys/wasm/atomics/thread.rs +++ b/library/std/src/sys/wasm/atomics/thread.rs @@ -14,6 +14,11 @@ impl Thread { unsupported() } + pub unsafe fn new_reactor(p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + unsupported() + } + pub fn yield_now() {} pub fn set_name(_name: &CStr) {} diff --git a/library/std/src/sys/windows/args.rs b/library/std/src/sys/windows/args.rs index 5bfd8b52ed08d..6b597f499bcdc 100644 --- a/library/std/src/sys/windows/args.rs +++ b/library/std/src/sys/windows/args.rs @@ -226,7 +226,7 @@ pub(crate) fn append_arg(cmd: &mut Vec, arg: &Arg, force_quotes: bool) -> i // that it actually gets passed through on the command line or otherwise // it will be dropped entirely when parsed on the other end. ensure_no_nuls(arg)?; - let arg_bytes = arg.bytes(); + let arg_bytes = arg.as_os_str_bytes(); let (quote, escape) = match quote { Quote::Always => (true, true), Quote::Auto => { @@ -297,7 +297,9 @@ pub(crate) fn make_bat_command_line( // * `|<>` pipe/redirect characters. const SPECIAL: &[u8] = b"\t &()[]{}^=;!'+,`~%|<>"; let force_quotes = match arg { - Arg::Regular(arg) if !force_quotes => arg.bytes().iter().any(|c| SPECIAL.contains(c)), + Arg::Regular(arg) if !force_quotes => { + arg.as_os_str_bytes().iter().any(|c| SPECIAL.contains(c)) + } _ => force_quotes, }; append_arg(&mut cmd, arg, force_quotes)?; diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs index 2bc40c4748a35..d9ccba0e9da76 100644 --- a/library/std/src/sys/windows/c.rs +++ b/library/std/src/sys/windows/c.rs @@ -12,13 +12,13 @@ use crate::os::windows::io::{AsRawHandle, BorrowedHandle}; use crate::ptr; use core::ffi::NonZero_c_ulong; -#[path = "c/windows_sys.rs"] // c.rs is included from two places so we need to specify this mod windows_sys; pub use windows_sys::*; pub type DWORD = c_ulong; pub type NonZeroDWORD = NonZero_c_ulong; pub type LARGE_INTEGER = c_longlong; +#[cfg_attr(target_vendor = "uwp", allow(unused))] pub type LONG = c_long; pub type UINT = c_uint; pub type WCHAR = u16; @@ -267,6 +267,8 @@ pub unsafe fn getaddrinfo( windows_sys::getaddrinfo(node.cast::(), service.cast::(), hints, res) } +cfg_if::cfg_if! { +if #[cfg(not(target_vendor = "uwp"))] { pub unsafe fn NtReadFile( filehandle: BorrowedHandle<'_>, event: HANDLE, @@ -313,6 +315,8 @@ pub unsafe fn NtWriteFile( key.map(|k| k as *const u32).unwrap_or(ptr::null()), ) } +} +} // Functions that aren't available on every version of Windows that we support, // but we still use them and just provide some form of a fallback implementation. @@ -376,4 +380,98 @@ compat_fn_with_fallback! { ) -> NTSTATUS { panic!("keyed events not available") } + + // These functions are available on UWP when lazily loaded. They will fail WACK if loaded statically. + #[cfg(target_vendor = "uwp")] + pub fn NtCreateFile( + filehandle: *mut HANDLE, + desiredaccess: FILE_ACCESS_RIGHTS, + objectattributes: *const OBJECT_ATTRIBUTES, + iostatusblock: *mut IO_STATUS_BLOCK, + allocationsize: *const i64, + fileattributes: FILE_FLAGS_AND_ATTRIBUTES, + shareaccess: FILE_SHARE_MODE, + createdisposition: NTCREATEFILE_CREATE_DISPOSITION, + createoptions: NTCREATEFILE_CREATE_OPTIONS, + eabuffer: *const ::core::ffi::c_void, + ealength: u32 + ) -> NTSTATUS { + STATUS_NOT_IMPLEMENTED + } + #[cfg(target_vendor = "uwp")] + pub fn NtReadFile( + filehandle: BorrowedHandle<'_>, + event: HANDLE, + apcroutine: PIO_APC_ROUTINE, + apccontext: *mut c_void, + iostatusblock: &mut IO_STATUS_BLOCK, + buffer: *mut crate::mem::MaybeUninit, + length: ULONG, + byteoffset: Option<&LARGE_INTEGER>, + key: Option<&ULONG> + ) -> NTSTATUS { + STATUS_NOT_IMPLEMENTED + } + #[cfg(target_vendor = "uwp")] + pub fn NtWriteFile( + filehandle: BorrowedHandle<'_>, + event: HANDLE, + apcroutine: PIO_APC_ROUTINE, + apccontext: *mut c_void, + iostatusblock: &mut IO_STATUS_BLOCK, + buffer: *const u8, + length: ULONG, + byteoffset: Option<&LARGE_INTEGER>, + key: Option<&ULONG> + ) -> NTSTATUS { + STATUS_NOT_IMPLEMENTED + } + #[cfg(target_vendor = "uwp")] + pub fn RtlNtStatusToDosError(Status: NTSTATUS) -> u32 { + Status as u32 + } +} + +// # Arm32 shim +// +// AddVectoredExceptionHandler and WSAStartup use platform-specific types. +// However, Microsoft no longer supports thumbv7a so definitions for those targets +// are not included in the win32 metadata. We work around that by defining them here. +// +// Where possible, these definitions should be kept in sync with https://docs.rs/windows-sys +cfg_if::cfg_if! { +if #[cfg(not(target_vendor = "uwp"))] { + #[link(name = "kernel32")] + extern "system" { + pub fn AddVectoredExceptionHandler( + first: u32, + handler: PVECTORED_EXCEPTION_HANDLER, + ) -> *mut c_void; + } + pub type PVECTORED_EXCEPTION_HANDLER = Option< + unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32, + >; + #[repr(C)] + pub struct EXCEPTION_POINTERS { + pub ExceptionRecord: *mut EXCEPTION_RECORD, + pub ContextRecord: *mut CONTEXT, + } + #[cfg(target_arch = "arm")] + pub enum CONTEXT {} +}} + +#[link(name = "ws2_32")] +extern "system" { + pub fn WSAStartup(wversionrequested: u16, lpwsadata: *mut WSADATA) -> i32; +} +#[cfg(target_arch = "arm")] +#[repr(C)] +pub struct WSADATA { + pub wVersion: u16, + pub wHighVersion: u16, + pub szDescription: [u8; 257], + pub szSystemStatus: [u8; 129], + pub iMaxSockets: u16, + pub iMaxUdpDg: u16, + pub lpVendorInfo: PSTR, } diff --git a/library/std/src/sys/windows/c/windows_sys.lst b/library/std/src/sys/windows/c/windows_sys.lst index 3e454199f13b1..631aedd26b4ea 100644 --- a/library/std/src/sys/windows/c/windows_sys.lst +++ b/library/std/src/sys/windows/c/windows_sys.lst @@ -1930,6 +1930,7 @@ Windows.Win32.Foundation.SetLastError Windows.Win32.Foundation.STATUS_DELETE_PENDING Windows.Win32.Foundation.STATUS_END_OF_FILE Windows.Win32.Foundation.STATUS_INVALID_PARAMETER +Windows.Win32.Foundation.STATUS_NOT_IMPLEMENTED Windows.Win32.Foundation.STATUS_PENDING Windows.Win32.Foundation.STATUS_SUCCESS Windows.Win32.Foundation.TRUE @@ -2170,7 +2171,6 @@ Windows.Win32.Networking.WinSock.WSARecv Windows.Win32.Networking.WinSock.WSASend Windows.Win32.Networking.WinSock.WSASERVICE_NOT_FOUND Windows.Win32.Networking.WinSock.WSASocketW -Windows.Win32.Networking.WinSock.WSAStartup Windows.Win32.Networking.WinSock.WSASYSCALLFAILURE Windows.Win32.Networking.WinSock.WSASYSNOTREADY Windows.Win32.Networking.WinSock.WSATRY_AGAIN @@ -2418,12 +2418,10 @@ Windows.Win32.System.Console.STD_HANDLE Windows.Win32.System.Console.STD_INPUT_HANDLE Windows.Win32.System.Console.STD_OUTPUT_HANDLE Windows.Win32.System.Console.WriteConsoleW -Windows.Win32.System.Diagnostics.Debug.AddVectoredExceptionHandler Windows.Win32.System.Diagnostics.Debug.ARM64_NT_NEON128 Windows.Win32.System.Diagnostics.Debug.CONTEXT Windows.Win32.System.Diagnostics.Debug.CONTEXT Windows.Win32.System.Diagnostics.Debug.CONTEXT -Windows.Win32.System.Diagnostics.Debug.EXCEPTION_POINTERS Windows.Win32.System.Diagnostics.Debug.EXCEPTION_RECORD Windows.Win32.System.Diagnostics.Debug.FACILITY_CODE Windows.Win32.System.Diagnostics.Debug.FACILITY_NT_BIT @@ -2436,7 +2434,6 @@ Windows.Win32.System.Diagnostics.Debug.FORMAT_MESSAGE_IGNORE_INSERTS Windows.Win32.System.Diagnostics.Debug.FORMAT_MESSAGE_OPTIONS Windows.Win32.System.Diagnostics.Debug.FormatMessageW Windows.Win32.System.Diagnostics.Debug.M128A -Windows.Win32.System.Diagnostics.Debug.PVECTORED_EXCEPTION_HANDLER Windows.Win32.System.Diagnostics.Debug.XSAVE_FORMAT Windows.Win32.System.Diagnostics.Debug.XSAVE_FORMAT Windows.Win32.System.Environment.FreeEnvironmentStringsW diff --git a/library/std/src/sys/windows/c/windows_sys.rs b/library/std/src/sys/windows/c/windows_sys.rs index 36a30f6ba5652..02377087173a7 100644 --- a/library/std/src/sys/windows/c/windows_sys.rs +++ b/library/std/src/sys/windows/c/windows_sys.rs @@ -39,13 +39,6 @@ extern "system" { pub fn AcquireSRWLockShared(srwlock: *mut RTL_SRWLOCK) -> (); } #[link(name = "kernel32")] -extern "system" { - pub fn AddVectoredExceptionHandler( - first: u32, - handler: PVECTORED_EXCEPTION_HANDLER, - ) -> *mut ::core::ffi::c_void; -} -#[link(name = "kernel32")] extern "system" { pub fn CancelIo(hfile: HANDLE) -> BOOL; } @@ -711,10 +704,6 @@ extern "system" { ) -> SOCKET; } #[link(name = "ws2_32")] -extern "system" { - pub fn WSAStartup(wversionrequested: u16, lpwsadata: *mut WSADATA) -> i32; -} -#[link(name = "ws2_32")] extern "system" { pub fn accept(s: SOCKET, addr: *mut SOCKADDR, addrlen: *mut i32) -> SOCKET; } @@ -3029,17 +3018,6 @@ pub const ERROR_XML_PARSE_ERROR: WIN32_ERROR = 1465u32; pub type EXCEPTION_DISPOSITION = i32; pub const EXCEPTION_MAXIMUM_PARAMETERS: u32 = 15u32; #[repr(C)] -pub struct EXCEPTION_POINTERS { - pub ExceptionRecord: *mut EXCEPTION_RECORD, - pub ContextRecord: *mut CONTEXT, -} -impl ::core::marker::Copy for EXCEPTION_POINTERS {} -impl ::core::clone::Clone for EXCEPTION_POINTERS { - fn clone(&self) -> Self { - *self - } -} -#[repr(C)] pub struct EXCEPTION_RECORD { pub ExceptionCode: NTSTATUS, pub ExceptionFlags: u32, @@ -3748,9 +3726,6 @@ pub const PROFILE_SERVER: PROCESS_CREATION_FLAGS = 1073741824u32; pub const PROFILE_USER: PROCESS_CREATION_FLAGS = 268435456u32; pub const PROGRESS_CONTINUE: u32 = 0u32; pub type PSTR = *mut u8; -pub type PVECTORED_EXCEPTION_HANDLER = ::core::option::Option< - unsafe extern "system" fn(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32, ->; pub type PWSTR = *mut u16; pub const READ_CONTROL: FILE_ACCESS_RIGHTS = 131072u32; pub const REALTIME_PRIORITY_CLASS: PROCESS_CREATION_FLAGS = 256u32; @@ -3888,6 +3863,7 @@ pub type STARTUPINFOW_FLAGS = u32; pub const STATUS_DELETE_PENDING: NTSTATUS = -1073741738i32; pub const STATUS_END_OF_FILE: NTSTATUS = -1073741807i32; pub const STATUS_INVALID_PARAMETER: NTSTATUS = -1073741811i32; +pub const STATUS_NOT_IMPLEMENTED: NTSTATUS = -1073741822i32; pub const STATUS_PENDING: NTSTATUS = 259i32; pub const STATUS_SUCCESS: NTSTATUS = 0i32; pub const STD_ERROR_HANDLE: STD_HANDLE = 4294967284u32; diff --git a/library/std/src/sys/windows/cmath.rs b/library/std/src/sys/windows/cmath.rs index 43ab8c7ee659f..1b2a86f3c0eb1 100644 --- a/library/std/src/sys/windows/cmath.rs +++ b/library/std/src/sys/windows/cmath.rs @@ -1,6 +1,6 @@ #![cfg(not(test))] -use libc::{c_double, c_float}; +use libc::{c_double, c_float, c_int}; extern "C" { pub fn acos(n: c_double) -> c_double; @@ -23,6 +23,10 @@ extern "C" { pub fn sinh(n: c_double) -> c_double; pub fn tan(n: c_double) -> c_double; pub fn tanh(n: c_double) -> c_double; + pub fn tgamma(n: c_double) -> c_double; + pub fn tgammaf(n: c_float) -> c_float; + pub fn lgamma_r(n: c_double, s: &mut c_int) -> c_double; + pub fn lgammaf_r(n: c_float, s: &mut c_int) -> c_float; } pub use self::shims::*; diff --git a/library/std/src/sys/windows/compat.rs b/library/std/src/sys/windows/compat.rs index 4fe95d41116b5..e28dd49353678 100644 --- a/library/std/src/sys/windows/compat.rs +++ b/library/std/src/sys/windows/compat.rs @@ -69,10 +69,7 @@ unsafe extern "C" fn init() { /// Helper macro for creating CStrs from literals and symbol names. macro_rules! ansi_str { - (sym $ident:ident) => {{ - #[allow(unused_unsafe)] - crate::sys::compat::const_cstr_from_bytes(concat!(stringify!($ident), "\0").as_bytes()) - }}; + (sym $ident:ident) => {{ crate::sys::compat::const_cstr_from_bytes(concat!(stringify!($ident), "\0").as_bytes()) }}; ($lit:literal) => {{ crate::sys::compat::const_cstr_from_bytes(concat!($lit, "\0").as_bytes()) }}; } diff --git a/library/std/src/sys/windows/mod.rs b/library/std/src/sys/windows/mod.rs index bcc172b0fae36..54a17fb2f0cb8 100644 --- a/library/std/src/sys/windows/mod.rs +++ b/library/std/src/sys/windows/mod.rs @@ -44,6 +44,19 @@ cfg_if::cfg_if! { } } +mod unsupported { + use crate::io; + + pub fn unsupported() -> io::Result { + Err(unsupported_err()) + } + + pub fn unsupported_err() -> io::Error { + io::const_io_error!(io::ErrorKind::Unsupported, "operation not supported on this platform",) + } +} + + // SAFETY: must be called only once during runtime initialization. // NOTE: this is not guaranteed to run, for example when Rust code is called externally. pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) { diff --git a/library/std/src/sys/windows/net.rs b/library/std/src/sys/windows/net.rs index 2404bbe2b893a..342ed42a2973c 100644 --- a/library/std/src/sys/windows/net.rs +++ b/library/std/src/sys/windows/net.rs @@ -159,7 +159,7 @@ impl Socket { } let mut timeout = c::timeval { - tv_sec: timeout.as_secs() as c_long, + tv_sec: cmp::min(timeout.as_secs(), c_long::MAX as u64) as c_long, tv_usec: (timeout.subsec_nanos() / 1000) as c_long, }; @@ -210,6 +210,10 @@ impl Socket { } } + pub fn accept_timeout(&self, _storage: *mut c::SOCKADDR, _len: *mut c_int, _timeout: crate::time::Duration) -> io::Result { + super::unsupported::unsupported() + } + pub fn duplicate(&self) -> io::Result { Ok(Self(self.0.try_clone()?)) } diff --git a/library/std/src/sys/windows/os.rs b/library/std/src/sys/windows/os.rs index d7adeb266ed93..58afca088ef9a 100644 --- a/library/std/src/sys/windows/os.rs +++ b/library/std/src/sys/windows/os.rs @@ -25,10 +25,6 @@ pub fn errno() -> i32 { /// Gets a detailed string description for the given error number. pub fn error_string(mut errnum: i32) -> String { - // This value is calculated from the macro - // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) - let langId = 0x0800 as c::DWORD; - let mut buf = [0 as c::WCHAR; 2048]; unsafe { @@ -56,13 +52,13 @@ pub fn error_string(mut errnum: i32) -> String { flags | c::FORMAT_MESSAGE_FROM_SYSTEM | c::FORMAT_MESSAGE_IGNORE_INSERTS, module, errnum as c::DWORD, - langId, + 0, buf.as_mut_ptr(), buf.len() as c::DWORD, ptr::null(), ) as usize; if res == 0 { - // Sometimes FormatMessageW can fail e.g., system doesn't like langId, + // Sometimes FormatMessageW can fail e.g., system doesn't like 0 as langId, let fm_err = errno(); return format!("OS Error {errnum} (FormatMessageW() returned error {fm_err})"); } @@ -85,25 +81,69 @@ pub fn error_string(mut errnum: i32) -> String { pub struct Env { base: c::LPWCH, - cur: c::LPWCH, + iter: EnvIterator, +} + +// FIXME(https://github.com/rust-lang/rust/issues/114583): Remove this when ::fmt matches ::fmt. +pub struct EnvStrDebug<'a> { + iter: &'a EnvIterator, +} + +impl fmt::Debug for EnvStrDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { iter } = self; + let iter: EnvIterator = (*iter).clone(); + let mut list = f.debug_list(); + for (a, b) in iter { + list.entry(&(a.to_str().unwrap(), b.to_str().unwrap())); + } + list.finish() + } +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self { base: _, iter } = self; + EnvStrDebug { iter } + } +} + +impl fmt::Debug for Env { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { base: _, iter } = self; + f.debug_list().entries(iter.clone()).finish() + } } impl Iterator for Env { type Item = (OsString, OsString); fn next(&mut self) -> Option<(OsString, OsString)> { + let Self { base: _, iter } = self; + iter.next() + } +} + +#[derive(Clone)] +struct EnvIterator(c::LPWCH); + +impl Iterator for EnvIterator { + type Item = (OsString, OsString); + + fn next(&mut self) -> Option<(OsString, OsString)> { + let Self(cur) = self; loop { unsafe { - if *self.cur == 0 { + if **cur == 0 { return None; } - let p = self.cur as *const u16; + let p = *cur as *const u16; let mut len = 0; while *p.add(len) != 0 { len += 1; } let s = slice::from_raw_parts(p, len); - self.cur = self.cur.add(len + 1); + *cur = cur.add(len + 1); // Windows allows environment variables to start with an equals // symbol (in any other position, this is the separator between @@ -137,7 +177,7 @@ pub fn env() -> Env { if ch.is_null() { panic!("failure getting env string from OS: {}", io::Error::last_os_error()); } - Env { base: ch, cur: ch } + Env { base: ch, iter: EnvIterator(ch) } } } diff --git a/library/std/src/sys/windows/os_str.rs b/library/std/src/sys/windows/os_str.rs index 2f2b0e56e0889..4708657a907f5 100644 --- a/library/std/src/sys/windows/os_str.rs +++ b/library/std/src/sys/windows/os_str.rs @@ -63,6 +63,16 @@ impl fmt::Display for Slice { } impl Buf { + #[inline] + pub fn into_os_str_bytes(self) -> Vec { + self.inner.into_bytes() + } + + #[inline] + pub unsafe fn from_os_str_bytes_unchecked(s: Vec) -> Self { + Self { inner: Wtf8Buf::from_bytes_unchecked(s) } + } + pub fn with_capacity(capacity: usize) -> Buf { Buf { inner: Wtf8Buf::with_capacity(capacity) } } @@ -151,12 +161,22 @@ impl Buf { } impl Slice { + #[inline] + pub fn as_os_str_bytes(&self) -> &[u8] { + self.inner.as_bytes() + } + + #[inline] + pub unsafe fn from_os_str_bytes_unchecked(s: &[u8]) -> &Slice { + mem::transmute(Wtf8::from_bytes_unchecked(s)) + } + #[inline] pub fn from_str(s: &str) -> &Slice { unsafe { mem::transmute(Wtf8::from_str(s)) } } - pub fn to_str(&self) -> Option<&str> { + pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> { self.inner.as_str() } diff --git a/library/std/src/sys/windows/path.rs b/library/std/src/sys/windows/path.rs index c3573d14c7f92..c9c2d10e6c444 100644 --- a/library/std/src/sys/windows/path.rs +++ b/library/std/src/sys/windows/path.rs @@ -1,7 +1,6 @@ use super::{c, fill_utf16_buf, to_u16s}; use crate::ffi::{OsStr, OsString}; use crate::io; -use crate::mem; use crate::path::{Path, PathBuf, Prefix}; use crate::ptr; @@ -11,16 +10,6 @@ mod tests; pub const MAIN_SEP_STR: &str = "\\"; pub const MAIN_SEP: char = '\\'; -/// # Safety -/// -/// `bytes` must be a valid wtf8 encoded slice -#[inline] -unsafe fn bytes_as_os_str(bytes: &[u8]) -> &OsStr { - // &OsStr is layout compatible with &Slice, which is compatible with &Wtf8, - // which is compatible with &[u8]. - mem::transmute(bytes) -} - #[inline] pub fn is_sep_byte(b: u8) -> bool { b == b'/' || b == b'\\' @@ -33,12 +22,12 @@ pub fn is_verbatim_sep(b: u8) -> bool { /// Returns true if `path` looks like a lone filename. pub(crate) fn is_file_name(path: &OsStr) -> bool { - !path.bytes().iter().copied().any(is_sep_byte) + !path.as_os_str_bytes().iter().copied().any(is_sep_byte) } pub(crate) fn has_trailing_slash(path: &OsStr) -> bool { - let is_verbatim = path.bytes().starts_with(br"\\?\"); + let is_verbatim = path.as_os_str_bytes().starts_with(br"\\?\"); let is_separator = if is_verbatim { is_verbatim_sep } else { is_sep_byte }; - if let Some(&c) = path.bytes().last() { is_separator(c) } else { false } + if let Some(&c) = path.as_os_str_bytes().last() { is_separator(c) } else { false } } /// Appends a suffix to a path. @@ -60,7 +49,7 @@ impl<'a, const LEN: usize> PrefixParser<'a, LEN> { fn get_prefix(path: &OsStr) -> [u8; LEN] { let mut prefix = [0; LEN]; // SAFETY: Only ASCII characters are modified. - for (i, &ch) in path.bytes().iter().take(LEN).enumerate() { + for (i, &ch) in path.as_os_str_bytes().iter().take(LEN).enumerate() { prefix[i] = if ch == b'/' { b'\\' } else { ch }; } prefix @@ -93,7 +82,7 @@ impl<'a> PrefixParserSlice<'a, '_> { } fn prefix_bytes(&self) -> &'a [u8] { - &self.path.bytes()[..self.index] + &self.path.as_os_str_bytes()[..self.index] } fn finish(self) -> &'a OsStr { @@ -101,7 +90,7 @@ impl<'a> PrefixParserSlice<'a, '_> { // &[u8] and back. This is safe to do because (1) we only look at ASCII // contents of the encoding and (2) new &OsStr values are produced only // from ASCII-bounded slices of existing &OsStr values. - unsafe { bytes_as_os_str(&self.path.bytes()[self.index..]) } + unsafe { OsStr::from_os_str_bytes_unchecked(&self.path.as_os_str_bytes()[self.index..]) } } } @@ -173,7 +162,7 @@ fn parse_drive(path: &OsStr) -> Option { drive.is_ascii_alphabetic() } - match path.bytes() { + match path.as_os_str_bytes() { [drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()), _ => None, } @@ -182,7 +171,7 @@ fn parse_drive(path: &OsStr) -> Option { // Parses a drive prefix exactly, e.g. "C:" fn parse_drive_exact(path: &OsStr) -> Option { // only parse two bytes: the drive letter and the drive separator - if path.bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) { + if path.as_os_str_bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) { parse_drive(path) } else { None @@ -196,21 +185,26 @@ fn parse_drive_exact(path: &OsStr) -> Option { fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) { let separator = if verbatim { is_verbatim_sep } else { is_sep_byte }; - match path.bytes().iter().position(|&x| separator(x)) { + match path.as_os_str_bytes().iter().position(|&x| separator(x)) { Some(separator_start) => { let separator_end = separator_start + 1; - let component = &path.bytes()[..separator_start]; + let component = &path.as_os_str_bytes()[..separator_start]; // Panic safe // The max `separator_end` is `bytes.len()` and `bytes[bytes.len()..]` is a valid index. - let path = &path.bytes()[separator_end..]; + let path = &path.as_os_str_bytes()[separator_end..]; // SAFETY: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\') // is encoded in a single byte, therefore `bytes[separator_start]` and // `bytes[separator_end]` must be code point boundaries and thus // `bytes[..separator_start]` and `bytes[separator_end..]` are valid wtf8 slices. - unsafe { (bytes_as_os_str(component), bytes_as_os_str(path)) } + unsafe { + ( + OsStr::from_os_str_bytes_unchecked(component), + OsStr::from_os_str_bytes_unchecked(path), + ) + } } None => (path, OsStr::new("")), } @@ -329,7 +323,7 @@ pub(crate) fn absolute(path: &Path) -> io::Result { // Verbatim paths should not be modified. if prefix.map(|x| x.is_verbatim()).unwrap_or(false) { // NULs in verbatim paths are rejected for consistency. - if path.bytes().contains(&0) { + if path.as_os_str_bytes().contains(&0) { return Err(io::const_io_error!( io::ErrorKind::InvalidInput, "strings passed to WinAPI cannot contain NULs", diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index df3667c0fd788..2dd0c67acdb4e 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -395,7 +395,7 @@ fn resolve_exe<'a>( // Test if the file name has the `exe` extension. // This does a case-insensitive `ends_with`. let has_exe_suffix = if exe_path.len() >= EXE_SUFFIX.len() { - exe_path.bytes()[exe_path.len() - EXE_SUFFIX.len()..] + exe_path.as_os_str_bytes()[exe_path.len() - EXE_SUFFIX.len()..] .eq_ignore_ascii_case(EXE_SUFFIX.as_bytes()) } else { false @@ -425,7 +425,7 @@ fn resolve_exe<'a>( // From the `CreateProcessW` docs: // > If the file name does not contain an extension, .exe is appended. // Note that this rule only applies when searching paths. - let has_extension = exe_path.bytes().contains(&b'.'); + let has_extension = exe_path.as_os_str_bytes().contains(&b'.'); // Search the directories given by `search_paths`. let result = search_paths(parent_paths, child_paths, |mut path| { @@ -595,7 +595,16 @@ pub struct Process { impl Process { pub fn kill(&mut self) -> io::Result<()> { - cvt(unsafe { c::TerminateProcess(self.handle.as_raw_handle(), 1) })?; + let result = unsafe { c::TerminateProcess(self.handle.as_raw_handle(), 1) }; + if result == c::FALSE { + let error = unsafe { c::GetLastError() }; + // TerminateProcess returns ERROR_ACCESS_DENIED if the process has already been + // terminated (by us, or for any other reason). So check if the process was actually + // terminated, and if so, do not return an error. + if error != c::ERROR_ACCESS_DENIED || self.try_wait().is_err() { + return Err(crate::io::Error::from_raw_os_error(error as i32)); + } + } Ok(()) } @@ -643,7 +652,7 @@ impl Process { } } -#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] pub struct ExitStatus(c::DWORD); impl ExitStatus { diff --git a/library/std/src/sys/windows/rand.rs b/library/std/src/sys/windows/rand.rs index bca4e38d9f62c..5d8fd13785a09 100644 --- a/library/std/src/sys/windows/rand.rs +++ b/library/std/src/sys/windows/rand.rs @@ -1,5 +1,3 @@ -use crate::ffi::c_void; -use crate::io; use crate::mem; use crate::ptr; use crate::sys::c; @@ -25,6 +23,9 @@ pub fn hashmap_random_keys() -> (u64, u64) { #[cfg(not(target_vendor = "uwp"))] #[inline(never)] fn fallback_rng() -> (u64, u64) { + use crate::ffi::c_void; + use crate::io; + let mut v = (0, 0); let ret = unsafe { c::RtlGenRandom(&mut v as *mut _ as *mut c_void, mem::size_of_val(&v) as c::ULONG) diff --git a/library/std/src/sys/windows/stdio.rs b/library/std/src/sys/windows/stdio.rs index 2e3e0859dc18e..3fcaaa508e3c8 100644 --- a/library/std/src/sys/windows/stdio.rs +++ b/library/std/src/sys/windows/stdio.rs @@ -11,6 +11,9 @@ use crate::sys::cvt; use crate::sys::handle::Handle; use core::str::utf8_char_width; +#[cfg(test)] +mod tests; + // Don't cache handles but get them fresh for every read/write. This allows us to track changes to // the value over time (such as if a process calls `SetStdHandle` while it's running). See #40490. pub struct Stdin { @@ -383,6 +386,10 @@ fn utf16_to_utf8(utf16: &[u16], utf8: &mut [u8]) -> io::Result { debug_assert!(utf16.len() <= c::c_int::MAX as usize); debug_assert!(utf8.len() <= c::c_int::MAX as usize); + if utf16.is_empty() { + return Ok(0); + } + let result = unsafe { c::WideCharToMultiByte( c::CP_UTF8, // CodePage diff --git a/library/std/src/sys/windows/stdio/tests.rs b/library/std/src/sys/windows/stdio/tests.rs new file mode 100644 index 0000000000000..1e53e0bee6369 --- /dev/null +++ b/library/std/src/sys/windows/stdio/tests.rs @@ -0,0 +1,6 @@ +use super::utf16_to_utf8; + +#[test] +fn zero_size_read() { + assert_eq!(utf16_to_utf8(&[], &mut []).unwrap(), 0); +} diff --git a/library/std/src/sys/windows/thread.rs b/library/std/src/sys/windows/thread.rs index 18cecb65681d2..369a4409b8c81 100644 --- a/library/std/src/sys/windows/thread.rs +++ b/library/std/src/sys/windows/thread.rs @@ -60,6 +60,11 @@ impl Thread { } } + pub unsafe fn new_reactor(_p: F) -> io::Result + where F: Fn() + Send + Sync + 'static { + super::unsupported::unsupported() + } + pub fn set_name(name: &CStr) { if let Ok(utf8) = name.to_str() { if let Ok(utf16) = to_u16s(utf8) { diff --git a/library/std/src/sys/windows/thread_local_dtor.rs b/library/std/src/sys/windows/thread_local_dtor.rs index 9707a95dff21b..cf542d2bfb838 100644 --- a/library/std/src/sys/windows/thread_local_dtor.rs +++ b/library/std/src/sys/windows/thread_local_dtor.rs @@ -4,29 +4,4 @@ #![unstable(feature = "thread_local_internals", issue = "none")] #![cfg(target_thread_local)] -// Using a per-thread list avoids the problems in synchronizing global state. -#[thread_local] -static mut DESTRUCTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new(); - -// Ensure this can never be inlined because otherwise this may break in dylibs. -// See #44391. -#[inline(never)] -pub unsafe fn register_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { - DESTRUCTORS.push((t, dtor)); -} - -#[inline(never)] // See comment above -/// Runs destructors. This should not be called until thread exit. -pub unsafe fn run_keyless_dtors() { - // Drop all the destructors. - // - // Note: While this is potentially an infinite loop, it *should* be - // the case that this loop always terminates because we provide the - // guarantee that a TLS key cannot be set after it is flagged for - // destruction. - while let Some((ptr, dtor)) = DESTRUCTORS.pop() { - (dtor)(ptr); - } - // We're done so free the memory. - DESTRUCTORS = Vec::new(); -} +pub use super::thread_local_key::register_keyless_dtor as register_dtor; diff --git a/library/std/src/sys/windows/thread_local_key.rs b/library/std/src/sys/windows/thread_local_key.rs index 17628b7579b8d..036d96596e9c0 100644 --- a/library/std/src/sys/windows/thread_local_key.rs +++ b/library/std/src/sys/windows/thread_local_key.rs @@ -1,7 +1,7 @@ use crate::cell::UnsafeCell; use crate::ptr; use crate::sync::atomic::{ - AtomicPtr, AtomicU32, + AtomicBool, AtomicPtr, AtomicU32, Ordering::{AcqRel, Acquire, Relaxed, Release}, }; use crate::sys::c; @@ -9,6 +9,41 @@ use crate::sys::c; #[cfg(test)] mod tests; +/// An optimization hint. The compiler is often smart enough to know if an atomic +/// is never set and can remove dead code based on that fact. +static HAS_DTORS: AtomicBool = AtomicBool::new(false); + +// Using a per-thread list avoids the problems in synchronizing global state. +#[thread_local] +#[cfg(target_thread_local)] +static mut DESTRUCTORS: Vec<(*mut u8, unsafe extern "C" fn(*mut u8))> = Vec::new(); + +// Ensure this can never be inlined because otherwise this may break in dylibs. +// See #44391. +#[inline(never)] +#[cfg(target_thread_local)] +pub unsafe fn register_keyless_dtor(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { + DESTRUCTORS.push((t, dtor)); + HAS_DTORS.store(true, Relaxed); +} + +#[inline(never)] // See comment above +#[cfg(target_thread_local)] +/// Runs destructors. This should not be called until thread exit. +unsafe fn run_keyless_dtors() { + // Drop all the destructors. + // + // Note: While this is potentially an infinite loop, it *should* be + // the case that this loop always terminates because we provide the + // guarantee that a TLS key cannot be set after it is flagged for + // destruction. + while let Some((ptr, dtor)) = DESTRUCTORS.pop() { + (dtor)(ptr); + } + // We're done so free the memory. + DESTRUCTORS = Vec::new(); +} + type Key = c::DWORD; type Dtor = unsafe extern "C" fn(*mut u8); @@ -156,6 +191,8 @@ static DTORS: AtomicPtr = AtomicPtr::new(ptr::null_mut()); /// Should only be called once per key, otherwise loops or breaks may occur in /// the linked list. unsafe fn register_dtor(key: &'static StaticKey) { + // Ensure this is never run when native thread locals are available. + assert_eq!(false, cfg!(target_thread_local)); let this = <*const StaticKey>::cast_mut(key); // Use acquire ordering to pass along the changes done by the previously // registered keys when we store the new head with release ordering. @@ -167,6 +204,7 @@ unsafe fn register_dtor(key: &'static StaticKey) { Err(new) => head = new, } } + HAS_DTORS.store(true, Release); } // ------------------------------------------------------------------------- @@ -240,10 +278,14 @@ pub static p_thread_callback: unsafe extern "system" fn(c::LPVOID, c::DWORD, c:: #[allow(dead_code, unused_variables)] unsafe extern "system" fn on_tls_callback(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID) { + if !HAS_DTORS.load(Acquire) { + return; + } if dwReason == c::DLL_THREAD_DETACH || dwReason == c::DLL_PROCESS_DETACH { + #[cfg(not(target_thread_local))] run_dtors(); #[cfg(target_thread_local)] - super::thread_local_dtor::run_keyless_dtors(); + run_keyless_dtors(); } // See comments above for what this is doing. Note that we don't need this diff --git a/library/std/src/sys/windows/thread_local_key/tests.rs b/library/std/src/sys/windows/thread_local_key/tests.rs index c95f383fb90e3..c739f0caf3ec0 100644 --- a/library/std/src/sys/windows/thread_local_key/tests.rs +++ b/library/std/src/sys/windows/thread_local_key/tests.rs @@ -1,3 +1,7 @@ +// This file only tests the thread local key fallback. +// Windows targets with native thread local support do not use this. +#![cfg(not(target_thread_local))] + use super::StaticKey; use crate::ptr; diff --git a/library/std/src/sys_common/backtrace.rs b/library/std/src/sys_common/backtrace.rs index 6f020940df12f..84e2c5d8d7f70 100644 --- a/library/std/src/sys_common/backtrace.rs +++ b/library/std/src/sys_common/backtrace.rs @@ -60,6 +60,8 @@ unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt:: bt_fmt.add_context()?; let mut idx = 0; let mut res = Ok(()); + let mut omitted_count: usize = 0; + let mut first_omit = true; // Start immediately if we're not using a short backtrace. let mut start = print_fmt != PrintFmt::Short; backtrace_rs::trace_unsynchronized(|frame| { @@ -85,10 +87,27 @@ unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt:: start = true; return; } + if !start { + omitted_count += 1; + } } } if start { + if omitted_count > 0 { + debug_assert!(print_fmt == PrintFmt::Short); + // only print the message between the middle of frames + if !first_omit { + let _ = writeln!( + bt_fmt.formatter(), + " [... omitted {} frame{} ...]", + omitted_count, + if omitted_count > 1 { "s" } else { "" } + ); + } + first_omit = false; + omitted_count = 0; + } res = bt_fmt.frame().symbol(frame, symbol); } }); diff --git a/library/std/src/sys_common/net.rs b/library/std/src/sys_common/net.rs index 652c695fc57b0..4a0b863d71d17 100644 --- a/library/std/src/sys_common/net.rs +++ b/library/std/src/sys_common/net.rs @@ -18,7 +18,7 @@ use crate::ffi::{c_int, c_void}; cfg_if::cfg_if! { if #[cfg(any( target_os = "dragonfly", target_os = "freebsd", - target_os = "ios", target_os = "macos", target_os = "watchos", + target_os = "ios", target_os = "tvos", target_os = "macos", target_os = "watchos", target_os = "openbsd", target_os = "netbsd", target_os = "illumos", target_os = "solaris", target_os = "haiku", target_os = "l4re", target_os = "nto"))] { use crate::sys::net::netc::IPV6_JOIN_GROUP as IPV6_ADD_MEMBERSHIP; @@ -450,6 +450,14 @@ impl TcpListener { Ok((TcpStream { inner: sock }, addr)) } + pub fn accept_timeout(&self, timeout: crate::time::Duration) -> io::Result<(TcpStream, SocketAddr)> { + let mut storage: c::sockaddr_storage = unsafe { mem::zeroed() }; + let mut len = mem::size_of_val(&storage) as c::socklen_t; + let sock = self.inner.accept_timeout(&mut storage as *mut _ as *mut _, &mut len, timeout)?; + let addr = sockaddr_to_addr(&storage, len as usize)?; + Ok((TcpStream { inner: sock }, addr)) + } + pub fn duplicate(&self) -> io::Result { self.inner.duplicate().map(|s| TcpListener { inner: s }) } diff --git a/library/std/src/sys_common/once/mod.rs b/library/std/src/sys_common/once/mod.rs index 359697d831317..e948ea0cd8a48 100644 --- a/library/std/src/sys_common/once/mod.rs +++ b/library/std/src/sys_common/once/mod.rs @@ -11,7 +11,7 @@ cfg_if::cfg_if! { if #[cfg(any( target_os = "linux", target_os = "android", - all(target_arch = "wasm32", target_feature = "atomics"), + all(target_family = "wasm", target_feature = "atomics"), target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", diff --git a/library/std/src/sys_common/thread_info.rs b/library/std/src/sys_common/thread_info.rs index 38c9e50009af5..88d937a7db153 100644 --- a/library/std/src/sys_common/thread_info.rs +++ b/library/std/src/sys_common/thread_info.rs @@ -1,5 +1,4 @@ #![allow(dead_code)] // stack_guard isn't used right now on all platforms -#![allow(unused_unsafe)] // thread_local with `const {}` triggers this liny use crate::cell::RefCell; use crate::sys::thread::guard::Guard; diff --git a/library/std/src/sys_common/thread_parking/id.rs b/library/std/src/sys_common/thread_parking/id.rs index 15042fc3beecb..0466743966034 100644 --- a/library/std/src/sys_common/thread_parking/id.rs +++ b/library/std/src/sys_common/thread_parking/id.rs @@ -56,18 +56,14 @@ impl Parker { self.init_tid(); // Changes NOTIFIED to EMPTY and EMPTY to PARKED. - let mut state = self.state.fetch_sub(1, Acquire).wrapping_sub(1); - if state == PARKED { + let state = self.state.fetch_sub(1, Acquire); + if state == EMPTY { // Loop to guard against spurious wakeups. - while state == PARKED { + // The state must be reset with acquire ordering to ensure that all + // calls to `unpark` synchronize with this thread. + while self.state.compare_exchange(NOTIFIED, EMPTY, Acquire, Relaxed).is_err() { park(self.state.as_ptr().addr()); - state = self.state.load(Acquire); } - - // Since the state change has already been observed with acquire - // ordering, the state can be reset with a relaxed store instead - // of a swap. - self.state.store(EMPTY, Relaxed); } } @@ -78,8 +74,7 @@ impl Parker { if state == PARKED { park_timeout(dur, self.state.as_ptr().addr()); // Swap to ensure that we observe all state changes with acquire - // ordering, even if the state has been changed after the timeout - // occurred. + // ordering. self.state.swap(EMPTY, Acquire); } } diff --git a/library/std/src/sys_common/thread_parking/mod.rs b/library/std/src/sys_common/thread_parking/mod.rs index c4d3f9ea2f427..0fb78617fdcf8 100644 --- a/library/std/src/sys_common/thread_parking/mod.rs +++ b/library/std/src/sys_common/thread_parking/mod.rs @@ -3,6 +3,7 @@ cfg_if::cfg_if! { target_os = "linux", target_os = "android", all(target_arch = "wasm32", target_feature = "atomics"), + all(target_vendor = "wasmer", target_os = "wasi"), target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs index ff96c35fb0ba6..67db5ebd89cfc 100644 --- a/library/std/src/sys_common/wtf8.rs +++ b/library/std/src/sys_common/wtf8.rs @@ -182,6 +182,15 @@ impl Wtf8Buf { Wtf8Buf { bytes: Vec::with_capacity(capacity), is_known_utf8: true } } + /// Creates a WTF-8 string from a WTF-8 byte vec. + /// + /// Since the byte vec is not checked for valid WTF-8, this functions is + /// marked unsafe. + #[inline] + pub unsafe fn from_bytes_unchecked(value: Vec) -> Wtf8Buf { + Wtf8Buf { bytes: value, is_known_utf8: false } + } + /// Creates a WTF-8 string from a UTF-8 `String`. /// /// This takes ownership of the `String` and does not copy. @@ -402,6 +411,12 @@ impl Wtf8Buf { self.bytes.truncate(new_len) } + /// Consumes the WTF-8 string and tries to convert it to a vec of bytes. + #[inline] + pub fn into_bytes(self) -> Vec { + self.bytes + } + /// Consumes the WTF-8 string and tries to convert it to UTF-8. /// /// This does not copy the data. @@ -444,6 +459,7 @@ impl Wtf8Buf { /// Converts this `Wtf8Buf` into a boxed `Wtf8`. #[inline] pub fn into_box(self) -> Box { + // SAFETY: relies on `Wtf8` being `repr(transparent)`. unsafe { mem::transmute(self.bytes.into_boxed_slice()) } } @@ -496,6 +512,7 @@ impl Extend for Wtf8Buf { /// Similar to `&str`, but can additionally contain surrogate code points /// if they’re not in a surrogate pair. #[derive(Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] pub struct Wtf8 { bytes: [u8], } @@ -570,7 +587,7 @@ impl Wtf8 { /// Since the byte slice is not checked for valid WTF-8, this functions is /// marked unsafe. #[inline] - unsafe fn from_bytes_unchecked(value: &[u8]) -> &Wtf8 { + pub unsafe fn from_bytes_unchecked(value: &[u8]) -> &Wtf8 { mem::transmute(value) } @@ -614,19 +631,20 @@ impl Wtf8 { Wtf8CodePoints { bytes: self.bytes.iter() } } + /// Access raw bytes of WTF-8 data + #[inline] + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + /// Tries to convert the string to UTF-8 and return a `&str` slice. /// /// Returns `None` if the string contains surrogates. /// /// This does not copy the data. #[inline] - pub fn as_str(&self) -> Option<&str> { - // Well-formed WTF-8 is also well-formed UTF-8 - // if and only if it contains no surrogate. - match self.next_surrogate(0) { - None => Some(unsafe { str::from_utf8_unchecked(&self.bytes) }), - Some(_) => None, - } + pub fn as_str(&self) -> Result<&str, str::Utf8Error> { + str::from_utf8(&self.bytes) } /// Creates an owned `Wtf8Buf` from a borrowed `Wtf8`. diff --git a/library/std/src/sys_common/wtf8/tests.rs b/library/std/src/sys_common/wtf8/tests.rs index 1a302d646941b..a07bbe6d7e425 100644 --- a/library/std/src/sys_common/wtf8/tests.rs +++ b/library/std/src/sys_common/wtf8/tests.rs @@ -521,11 +521,11 @@ fn wtf8_code_points() { #[test] fn wtf8_as_str() { - assert_eq!(Wtf8::from_str("").as_str(), Some("")); - assert_eq!(Wtf8::from_str("aé 💩").as_str(), Some("aé 💩")); + assert_eq!(Wtf8::from_str("").as_str(), Ok("")); + assert_eq!(Wtf8::from_str("aé 💩").as_str(), Ok("aé 💩")); let mut string = Wtf8Buf::new(); string.push(CodePoint::from_u32(0xD800).unwrap()); - assert_eq!(string.as_str(), None); + assert!(string.as_str().is_err()); } #[test] diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index 1b86d898cc7a3..09994e47f0a69 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -313,7 +313,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::Cell; /// /// thread_local! { @@ -326,7 +325,7 @@ impl LocalKey> { /// /// assert_eq!(X.get(), 123); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn set(&'static self, value: T) { self.initialize_with(Cell::new(value), |value, cell| { if let Some(value) = value { @@ -351,7 +350,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::Cell; /// /// thread_local! { @@ -360,7 +358,7 @@ impl LocalKey> { /// /// assert_eq!(X.get(), 1); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn get(&'static self) -> T where T: Copy, @@ -381,7 +379,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::Cell; /// /// thread_local! { @@ -391,7 +388,7 @@ impl LocalKey> { /// assert_eq!(X.take(), Some(1)); /// assert_eq!(X.take(), None); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn take(&'static self) -> T where T: Default, @@ -412,7 +409,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::Cell; /// /// thread_local! { @@ -422,7 +418,7 @@ impl LocalKey> { /// assert_eq!(X.replace(2), 1); /// assert_eq!(X.replace(3), 2); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn replace(&'static self, value: T) -> T { self.with(|cell| cell.replace(value)) } @@ -444,7 +440,6 @@ impl LocalKey> { /// # Example /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::RefCell; /// /// thread_local! { @@ -453,7 +448,7 @@ impl LocalKey> { /// /// X.with_borrow(|v| assert!(v.is_empty())); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn with_borrow(&'static self, f: F) -> R where F: FnOnce(&T) -> R, @@ -476,7 +471,6 @@ impl LocalKey> { /// # Example /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::RefCell; /// /// thread_local! { @@ -487,7 +481,7 @@ impl LocalKey> { /// /// X.with_borrow(|v| assert_eq!(*v, vec![1])); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn with_borrow_mut(&'static self, f: F) -> R where F: FnOnce(&mut T) -> R, @@ -511,7 +505,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::RefCell; /// /// thread_local! { @@ -524,7 +517,7 @@ impl LocalKey> { /// /// X.with_borrow(|v| assert_eq!(*v, vec![1, 2, 3])); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn set(&'static self, value: T) { self.initialize_with(RefCell::new(value), |value, cell| { if let Some(value) = value { @@ -551,7 +544,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::RefCell; /// /// thread_local! { @@ -566,7 +558,7 @@ impl LocalKey> { /// /// X.with_borrow(|v| assert!(v.is_empty())); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn take(&'static self) -> T where T: Default, @@ -586,7 +578,6 @@ impl LocalKey> { /// # Examples /// /// ``` - /// #![feature(local_key_cell_methods)] /// use std::cell::RefCell; /// /// thread_local! { @@ -598,7 +589,7 @@ impl LocalKey> { /// /// X.with_borrow(|v| assert_eq!(*v, vec![1, 2, 3])); /// ``` - #[unstable(feature = "local_key_cell_methods", issue = "92122")] + #[stable(feature = "local_key_cell_methods", since = "1.73.0")] pub fn replace(&'static self, value: T) -> T { self.with(|cell| cell.replace(value)) } diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index f712c872708ac..947fa9d039743 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -152,9 +152,8 @@ #![stable(feature = "rust1", since = "1.0.0")] #![deny(unsafe_op_in_unsafe_fn)] -// Under `test`, `__FastLocalKeyInner` seems unused. +#![cfg_attr(all(target_os = "wasi", target_vendor = "wasmer"), allow(unused_imports))] #![cfg_attr(test, allow(dead_code))] - #[cfg(all(test, not(target_os = "emscripten")))] mod tests; @@ -191,11 +190,11 @@ pub use scoped::{scope, Scope, ScopedJoinHandle}; //////////////////////////////////////////////////////////////////////////////// #[macro_use] -mod local; +pub(crate) mod local; cfg_if::cfg_if! { if #[cfg(test)] { - // Avoid duplicating the global state assoicated with thread-locals between this crate and + // Avoid duplicating the global state associated with thread-locals between this crate and // realstd. Miri relies on this. pub use realstd::thread::{local_impl, AccessError, LocalKey}; } else { @@ -206,7 +205,7 @@ cfg_if::cfg_if! { #[doc(hidden)] #[unstable(feature = "thread_local_internals", issue = "none")] pub mod local_impl { - pub use crate::sys::common::thread_local::{thread_local_inner, Key}; + pub use crate::sys::common::thread_local::{thread_local_inner, Key, abort_on_dtor_unwind}; } } } @@ -262,9 +261,9 @@ cfg_if::cfg_if! { #[derive(Debug)] pub struct Builder { // A name for the thread-to-be, for identification in panic messages - name: Option, + pub(crate) name: Option, // The size of the stack for the spawned thread in bytes - stack_size: Option, + pub(crate) stack_size: Option, } impl Builder { @@ -389,6 +388,46 @@ impl Builder { unsafe { self.spawn_unchecked(f) } } + /// Spawns a new reactor by taking ownership of the `Builder`, and returns an + /// [`io::Result`]. + /// + /// The spawned reactor may outlive the caller (unless the caller thread + /// is the main thread; the whole process is terminated when the main + /// thread finishes). + /// + /// # Errors + /// + /// Unlike the [`spawn`] free function, this method yields an + /// [`io::Result`] to capture any failure to create the thread at + /// the OS level. + /// + /// [`io::Result`]: crate::io::Result + /// + /// # Panics + /// + /// Panics if a reactor name was set and it contained null bytes. + /// + /// # Examples + /// + /// ``` + /// use std::thread; + /// + /// let builder = thread::Builder::new(); + /// + /// builder.reactor(|| { + /// // reactor code + /// }).unwrap(); + /// ``` + #[stable(feature = "rust1", since = "1.0.0")] + pub fn reactor(self, f: F) -> io::Result<()> + where + F: Fn(), + F: Send + Sync + 'static, + { + unsafe { imp::Thread::new_reactor(f)? }; + Ok(()) + } + /// Spawns a new thread without any lifetime restrictions by taking ownership /// of the `Builder`, and returns an [`io::Result`] to its [`JoinHandle`]. /// @@ -528,6 +567,7 @@ impl Builder { let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| { crate::sys_common::backtrace::__rust_begin_short_backtrace(f) })); + // SAFETY: `their_packet` as been built just above and moved by the // closure (it is an Arc<...>) and `my_packet` will be stored in the // same `JoinInner` as this closure meaning the mutation will be @@ -686,6 +726,89 @@ where Builder::new().spawn(f).expect("failed to spawn thread") } +//////////////////////////////////////////////////////////////////////////////// +// Free functions +//////////////////////////////////////////////////////////////////////////////// + +/// Spawns a new reactor +/// +/// This call will create a reactor using default parameters of [`Builder`], if you +/// want to specify the stack size or the name of the reactor, use this API +/// instead. +/// +/// As you can see in the signature of `reactor` there are two constraints on +/// the closure given to `reactor`, let's explain them: +/// +/// - The `'static` constraint means that the closure must have a lifetime of the +/// whole program execution. The reason for this is that reactors will be +/// continuously invoked by outside of the program until it exists +/// +/// Indeed if the reactor, can outlive their caller, we need to make sure that +/// they will be valid afterwards, thus is until the end of the program, hence +/// he `'static` lifetime. +/// - The [`Send`] constraint is because the closure will need to be passed +/// *by value* from the thread where it is spawned to the new thread. Its +/// return value will need to be passed from the new thread to the thread +/// where it is `join`ed. +/// As a reminder, the [`Send`] marker trait expresses that it is safe to be +/// passed from thread to thread. [`Sync`] expresses that it is safe to have a +/// reference be passed from thread to thread. +/// +/// # Panics +/// +/// Panics if the OS fails to create a thread; use [`Builder::reactor`] +/// to recover from such errors. +/// +/// # Examples +/// +/// Creating a thread. +/// +/// ``` +/// use std::thread; +/// +/// thread::reactor(|| { +/// // reactor code +/// }); +/// ``` +/// +/// As mentioned in the module documentation, reactors are usually made to +/// communicate using [`channels`], here is how it usually looks. +/// +/// This example also shows how to use `move`, in order to give ownership +/// of values to a reactor. +/// +/// ``` +/// use std::thread; +/// use std::sync::{Arc, Mutex}; +/// use std::sync::mpsc::channel; +/// +/// let (tx, rx) = channel(); +/// let tx = Arc::new(Mutex::new(tx)); +/// +/// let sender = thread::reactor(move || { +/// tx.lock().unwrap().send("Hello, thread".to_owned()) +/// .expect("Unable to send on channel"); +/// }); +/// +/// let receiver = thread::thread(move || { +/// let value = rx.recv().expect("Unable to receive from channel"); +/// println!("{value}"); +/// }); +/// +/// receiver.join().expect("The receiver thread has panicked"); +/// ``` +/// +/// [`channels`]: crate::sync::mpsc +/// [`Err`]: crate::result::Result::Err +#[stable(feature = "rust1", since = "1.0.0")] +pub fn reactor(f: F) +where + F: Fn(), + F: Send + Sync + 'static, +{ + Builder::new().reactor(f).expect("failed to spawn reactor") +} + /// Gets a handle to the thread that invokes it. /// /// # Examples @@ -889,7 +1012,7 @@ impl Drop for PanicGuard { /// it is guaranteed that this function will not panic (it may abort the /// process if the implementation encounters some rare errors). /// -/// # park and unpark +/// # `park` and `unpark` /// /// Every thread is equipped with some basic low-level blocking support, via the /// [`thread::park`][`park`] function and [`thread::Thread::unpark`][`unpark`] @@ -910,14 +1033,6 @@ impl Drop for PanicGuard { /// if it wasn't already. Because the token is initially absent, [`unpark`] /// followed by [`park`] will result in the second call returning immediately. /// -/// In other words, each [`Thread`] acts a bit like a spinlock that can be -/// locked and unlocked using `park` and `unpark`. -/// -/// Notice that being unblocked does not imply any synchronization with someone -/// that unparked this thread, it could also be spurious. -/// For example, it would be a valid, but inefficient, implementation to make both [`park`] and -/// [`unpark`] return immediately without doing anything. -/// /// The API is typically used by acquiring a handle to the current thread, /// placing that handle in a shared data structure so that other threads can /// find it, and then `park`ing in a loop. When some desired condition is met, another @@ -931,6 +1046,23 @@ impl Drop for PanicGuard { /// /// * It can be implemented very efficiently on many platforms. /// +/// # Memory Ordering +/// +/// Calls to `park` _synchronize-with_ calls to `unpark`, meaning that memory +/// operations performed before a call to `unpark` are made visible to the thread that +/// consumes the token and returns from `park`. Note that all `park` and `unpark` +/// operations for a given thread form a total order and `park` synchronizes-with +/// _all_ prior `unpark` operations. +/// +/// In atomic ordering terms, `unpark` performs a `Release` operation and `park` +/// performs the corresponding `Acquire` operation. Calls to `unpark` for the same +/// thread form a [release sequence]. +/// +/// Note that being unblocked does not imply a call was made to `unpark`, because +/// wakeups can also be spurious. For example, a valid, but inefficient, +/// implementation could have `park` and `unpark` return immediately without doing anything, +/// making *all* wakeups spurious. +/// /// # Examples /// /// ``` @@ -944,7 +1076,7 @@ impl Drop for PanicGuard { /// let parked_thread = thread::spawn(move || { /// // We want to wait until the flag is set. We *could* just spin, but using /// // park/unpark is more efficient. -/// while !flag2.load(Ordering::Acquire) { +/// while !flag2.load(Ordering::Relaxed) { /// println!("Parking thread"); /// thread::park(); /// // We *could* get here spuriously, i.e., way before the 10ms below are over! @@ -961,7 +1093,7 @@ impl Drop for PanicGuard { /// // There is no race condition here, if `unpark` /// // happens first, `park` will return immediately. /// // Hence there is no risk of a deadlock. -/// flag.store(true, Ordering::Release); +/// flag.store(true, Ordering::Relaxed); /// println!("Unpark the thread"); /// parked_thread.thread().unpark(); /// @@ -970,6 +1102,7 @@ impl Drop for PanicGuard { /// /// [`unpark`]: Thread::unpark /// [`thread::park_timeout`]: park_timeout +/// [release sequence]: https://en.cppreference.com/w/cpp/atomic/memory_order#Release_sequence #[stable(feature = "rust1", since = "1.0.0")] pub fn park() { let guard = PanicGuard; @@ -1145,14 +1278,14 @@ impl ThreadId { //////////////////////////////////////////////////////////////////////////////// /// The internal representation of a `Thread` handle -struct Inner { +pub(crate) struct Inner { name: Option, // Guaranteed to be UTF-8 id: ThreadId, parker: Parker, } impl Inner { - fn parker(self: Pin<&Self>) -> Pin<&Parker> { + pub(crate) fn parker(self: Pin<&Self>) -> Pin<&Parker> { unsafe { Pin::map_unchecked(self, |inner| &inner.parker) } } } @@ -1301,7 +1434,7 @@ impl Thread { self.cname().map(|s| unsafe { str::from_utf8_unchecked(s.to_bytes()) }) } - fn cname(&self) -> Option<&CStr> { + pub(crate) fn cname(&self) -> Option<&CStr> { self.inner.name.as_deref() } } @@ -1372,7 +1505,7 @@ pub type Result = crate::result::Result>; // // An Arc to the packet is stored into a `JoinInner` which in turns is placed // in `JoinHandle`. -struct Packet<'scope, T> { +pub(crate) struct Packet<'scope, T> { scope: Option>, result: UnsafeCell>>, _marker: PhantomData>, @@ -1417,14 +1550,14 @@ impl<'scope, T> Drop for Packet<'scope, T> { } /// Inner representation for JoinHandle -struct JoinInner<'scope, T> { - native: imp::Thread, - thread: Thread, - packet: Arc>, +pub(crate) struct JoinInner<'scope, T> { + pub(crate) native: imp::Thread, + pub(crate) thread: Thread, + pub(crate) packet: Arc>, } impl<'scope, T> JoinInner<'scope, T> { - fn join(mut self) -> Result { + pub(crate) fn join(mut self) -> Result { self.native.join(); Arc::get_mut(&mut self.packet).unwrap().result.get_mut().take().unwrap() } @@ -1493,7 +1626,7 @@ impl<'scope, T> JoinInner<'scope, T> { /// [`thread::Builder::spawn`]: Builder::spawn /// [`thread::spawn`]: spawn #[stable(feature = "rust1", since = "1.0.0")] -pub struct JoinHandle(JoinInner<'static, T>); +pub struct JoinHandle(pub(crate) JoinInner<'static, T>); #[stable(feature = "joinhandle_impl_send_sync", since = "1.29.0")] unsafe impl Send for JoinHandle {} @@ -1596,7 +1729,7 @@ impl fmt::Debug for JoinHandle { } } -fn _assert_sync_and_send() { +pub(crate) fn _assert_sync_and_send() { fn _assert_both() {} _assert_both::>(); _assert_both::(); diff --git a/library/std/src/thread/scoped.rs b/library/std/src/thread/scoped.rs index ada69aa8269f6..429f62f87cb50 100644 --- a/library/std/src/thread/scoped.rs +++ b/library/std/src/thread/scoped.rs @@ -35,14 +35,14 @@ pub struct Scope<'scope, 'env: 'scope> { #[stable(feature = "scoped_threads", since = "1.63.0")] pub struct ScopedJoinHandle<'scope, T>(JoinInner<'scope, T>); -pub(super) struct ScopeData { +pub(crate) struct ScopeData { num_running_threads: AtomicUsize, a_thread_panicked: AtomicBool, main_thread: Thread, } impl ScopeData { - pub(super) fn increment_num_running_threads(&self) { + pub(crate) fn increment_num_running_threads(&self) { // We check for 'overflow' with usize::MAX / 2, to make sure there's no // chance it overflows to 0, which would result in unsoundness. if self.num_running_threads.fetch_add(1, Ordering::Relaxed) > usize::MAX / 2 { @@ -51,7 +51,7 @@ impl ScopeData { panic!("too many running threads in thread scope"); } } - pub(super) fn decrement_num_running_threads(&self, panic: bool) { + pub(crate) fn decrement_num_running_threads(&self, panic: bool) { if panic { self.a_thread_panicked.store(true, Ordering::Relaxed); } diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index b65e2572cc5e4..5d6b9e94ee91b 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -42,6 +42,7 @@ fn test_named_thread() { all(target_os = "linux", target_env = "gnu"), target_os = "macos", target_os = "ios", + target_os = "tvos", target_os = "watchos" ))] #[test] diff --git a/library/std/src/time/tests.rs b/library/std/src/time/tests.rs index 6ed84806e6d37..da174a72352dd 100644 --- a/library/std/src/time/tests.rs +++ b/library/std/src/time/tests.rs @@ -1,6 +1,6 @@ use super::{Duration, Instant, SystemTime, UNIX_EPOCH}; use core::fmt::Debug; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] use test::{black_box, Bencher}; macro_rules! assert_almost_eq { @@ -26,7 +26,7 @@ fn instant_monotonic() { } #[test] -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] fn instant_monotonic_concurrent() -> crate::thread::Result<()> { let threads: Vec<_> = (0..8) .map(|_| { @@ -231,7 +231,7 @@ fn big_math() { macro_rules! bench_instant_threaded { ($bench_name:ident, $thread_count:expr) => { #[bench] - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(target_family = "wasm"))] fn $bench_name(b: &mut Bencher) -> crate::thread::Result<()> { use crate::sync::atomic::{AtomicBool, Ordering}; use crate::sync::Arc; diff --git a/library/std/tests/common/mod.rs b/library/std/tests/common/mod.rs index fce220223a055..358c2c3f9b2f3 100644 --- a/library/std/tests/common/mod.rs +++ b/library/std/tests/common/mod.rs @@ -20,15 +20,15 @@ pub(crate) fn test_rng() -> rand_xorshift::XorShiftRng { } // Copied from std::sys_common::io -pub struct TempDir(PathBuf); +pub(crate) struct TempDir(PathBuf); impl TempDir { - pub fn join(&self, path: &str) -> PathBuf { + pub(crate) fn join(&self, path: &str) -> PathBuf { let TempDir(ref p) = *self; p.join(path) } - pub fn path(&self) -> &Path { + pub(crate) fn path(&self) -> &Path { let TempDir(ref p) = *self; p } @@ -49,7 +49,7 @@ impl Drop for TempDir { } #[track_caller] // for `test_rng` -pub fn tmpdir() -> TempDir { +pub(crate) fn tmpdir() -> TempDir { let p = env::temp_dir(); let mut r = test_rng(); let ret = p.join(&format!("rust-{}", r.next_u32())); diff --git a/library/std/tests/process_spawning.rs b/library/std/tests/process_spawning.rs new file mode 100644 index 0000000000000..46dc9ff00bd0d --- /dev/null +++ b/library/std/tests/process_spawning.rs @@ -0,0 +1,38 @@ +#![cfg(not(target_env="sgx"))] + +use std::env; +use std::fs; +use std::process; +use std::str; + +mod common; + +#[test] +fn issue_15149() { + // If we're the parent, copy our own binary to a new directory. + let my_path = env::current_exe().unwrap(); + + let temp = common::tmpdir(); + let child_dir = temp.join("issue-15140-child"); + fs::create_dir_all(&child_dir).unwrap(); + + let child_path = child_dir.join(&format!("mytest{}", env::consts::EXE_SUFFIX)); + fs::copy(&my_path, &child_path).unwrap(); + + // Append the new directory to our own PATH. + let path = { + let mut paths: Vec<_> = env::split_paths(&env::var_os("PATH").unwrap()).collect(); + paths.push(child_dir.to_path_buf()); + env::join_paths(paths).unwrap() + }; + + let child_output = + process::Command::new("mytest").env("PATH", &path).arg("child").output().unwrap(); + + assert!( + child_output.status.success(), + "child assertion failed\n child stdout:\n {}\n child stderr:\n {}", + str::from_utf8(&child_output.stdout).unwrap(), + str::from_utf8(&child_output.stderr).unwrap() + ); +} diff --git a/library/std/tests/switch-stdout.rs b/library/std/tests/switch-stdout.rs new file mode 100644 index 0000000000000..2605664d28971 --- /dev/null +++ b/library/std/tests/switch-stdout.rs @@ -0,0 +1,53 @@ +#![cfg(any(target_family = "unix", target_family = "windows"))] + +use std::fs::File; +use std::io::{Read, Write}; + +mod common; + +#[cfg(unix)] +fn switch_stdout_to(file: File) { + use std::os::unix::prelude::*; + + extern "C" { + fn dup2(old: i32, new: i32) -> i32; + } + + unsafe { + assert_eq!(dup2(file.as_raw_fd(), 1), 1); + } +} + +#[cfg(windows)] +fn switch_stdout_to(file: File) { + use std::os::windows::prelude::*; + + extern "system" { + fn SetStdHandle(nStdHandle: u32, handle: *mut u8) -> i32; + } + + const STD_OUTPUT_HANDLE: u32 = (-11i32) as u32; + + unsafe { + let rc = SetStdHandle(STD_OUTPUT_HANDLE, file.into_raw_handle() as *mut _); + assert!(rc != 0); + } +} + +#[test] +fn switch_stdout() { + let temp = common::tmpdir(); + let path = temp.join("switch-stdout-output"); + let f = File::create(&path).unwrap(); + + let mut stdout = std::io::stdout(); + stdout.write(b"foo\n").unwrap(); + stdout.flush().unwrap(); + switch_stdout_to(f); + stdout.write(b"bar\n").unwrap(); + stdout.flush().unwrap(); + + let mut contents = String::new(); + File::open(&path).unwrap().read_to_string(&mut contents).unwrap(); + assert_eq!(contents, "bar\n"); +} diff --git a/library/stdarch b/library/stdarch index 7e2cdc675b921..d77878b7299dd 160000 --- a/library/stdarch +++ b/library/stdarch @@ -1 +1 @@ -Subproject commit 7e2cdc675b92165c5f8c4c794620252be4605e74 +Subproject commit d77878b7299dd7e286799a6e8447048b65d2a861 diff --git a/library/sysroot/Cargo.toml b/library/sysroot/Cargo.toml index 5356ee277cc2c..6ff24a8db59c3 100644 --- a/library/sysroot/Cargo.toml +++ b/library/sysroot/Cargo.toml @@ -17,6 +17,7 @@ compiler-builtins-c = ["std/compiler-builtins-c"] compiler-builtins-mem = ["std/compiler-builtins-mem"] compiler-builtins-no-asm = ["std/compiler-builtins-no-asm"] compiler-builtins-mangled-names = ["std/compiler-builtins-mangled-names"] +compiler-builtins-weak-intrinsics = ["std/compiler-builtins-weak-intrinsics"] llvm-libunwind = ["std/llvm-libunwind"] system-llvm-libunwind = ["std/system-llvm-libunwind"] panic-unwind = ["std/panic_unwind"] diff --git a/library/test/src/console.rs b/library/test/src/console.rs index 7eee4ca236190..bbeb944e8b11b 100644 --- a/library/test/src/console.rs +++ b/library/test/src/console.rs @@ -199,7 +199,7 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec) -> io::Res let TestDescAndFn { desc, testfn } = test; let fntype = match testfn { - StaticTestFn(..) | DynTestFn(..) => { + StaticTestFn(..) | DynTestFn(..) | StaticBenchAsTestFn(..) | DynBenchAsTestFn(..) => { st.tests += 1; "test" } diff --git a/library/test/src/formatters/junit.rs b/library/test/src/formatters/junit.rs index 9f5bf24367eaa..a211ebf1ded16 100644 --- a/library/test/src/formatters/junit.rs +++ b/library/test/src/formatters/junit.rs @@ -32,7 +32,7 @@ fn str_to_cdata(s: &str) -> String { let escaped_output = s.replace("]]>", "]]]]>"); let escaped_output = escaped_output.replace(" ", ""); format!("", escaped_output) diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index e76d6716b94fe..64d10dd5712ab 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -21,6 +21,7 @@ #![feature(process_exitcode_internals)] #![feature(panic_can_unwind)] #![feature(test)] +#![cfg_attr(not(bootstrap), allow(internal_features))] // Public reexports pub use self::bench::{black_box, Bencher}; @@ -92,6 +93,7 @@ use time::TestExecTime; const ERROR_EXIT_CODE: i32 = 101; const SECONDARY_TEST_INVOKER_VAR: &str = "__RUST_TEST_INVOKE"; +const SECONDARY_TEST_BENCH_BENCHMARKS_VAR: &str = "__RUST_TEST_BENCH_BENCHMARKS"; // The default console test runner. It accepts the command line // arguments and a vector of test_descs. @@ -171,18 +173,31 @@ pub fn test_main_static_abort(tests: &[&TestDescAndFn]) { // will then exit the process. if let Ok(name) = env::var(SECONDARY_TEST_INVOKER_VAR) { env::remove_var(SECONDARY_TEST_INVOKER_VAR); + + // Convert benchmarks to tests if we're not benchmarking. + let mut tests = tests.iter().map(make_owned_test).collect::>(); + if env::var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR).is_ok() { + env::remove_var(SECONDARY_TEST_BENCH_BENCHMARKS_VAR); + } else { + tests = convert_benchmarks_to_tests(tests); + }; + let test = tests - .iter() - .filter(|test| test.desc.name.as_slice() == name) - .map(make_owned_test) - .next() + .into_iter() + .find(|test| test.desc.name.as_slice() == name) .unwrap_or_else(|| panic!("couldn't find a test with the provided name '{name}'")); let TestDescAndFn { desc, testfn } = test; - let testfn = match testfn { - StaticTestFn(f) => f, - _ => panic!("only static tests are supported"), - }; - run_test_in_spawned_subprocess(desc, Box::new(testfn)); + match testfn.into_runnable() { + Runnable::Test(runnable_test) => { + if runnable_test.is_dynamic() { + panic!("only static tests are supported"); + } + run_test_in_spawned_subprocess(desc, runnable_test); + } + Runnable::Bench(_) => { + panic!("benchmarks should not be executed into child processes") + } + } } let args = env::args().collect::>(); @@ -234,16 +249,6 @@ impl FilteredTests { self.tests.push((TestId(self.next_id), test)); self.next_id += 1; } - fn add_bench_as_test( - &mut self, - desc: TestDesc, - benchfn: impl Fn(&mut Bencher) -> Result<(), String> + Send + 'static, - ) { - let testfn = DynTestFn(Box::new(move || { - bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b))) - })); - self.add_test(desc, testfn); - } fn total_len(&self) -> usize { self.tests.len() + self.benches.len() } @@ -301,14 +306,14 @@ where if opts.bench_benchmarks { filtered.add_bench(desc, DynBenchFn(benchfn)); } else { - filtered.add_bench_as_test(desc, benchfn); + filtered.add_test(desc, DynBenchAsTestFn(benchfn)); } } StaticBenchFn(benchfn) => { if opts.bench_benchmarks { filtered.add_bench(desc, StaticBenchFn(benchfn)); } else { - filtered.add_bench_as_test(desc, benchfn); + filtered.add_test(desc, StaticBenchAsTestFn(benchfn)); } } testfn => { @@ -519,12 +524,8 @@ pub fn convert_benchmarks_to_tests(tests: Vec) -> Vec DynTestFn(Box::new(move || { - bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b))) - })), - StaticBenchFn(benchfn) => DynTestFn(Box::new(move || { - bench::run_once(|b| __rust_begin_short_backtrace(|| benchfn(b))) - })), + DynBenchFn(benchfn) => DynBenchAsTestFn(benchfn), + StaticBenchFn(benchfn) => StaticBenchAsTestFn(benchfn), f => f, }; TestDescAndFn { desc: x.desc, testfn } @@ -553,99 +554,69 @@ pub fn run_test( return None; } - struct TestRunOpts { - pub strategy: RunStrategy, - pub nocapture: bool, - pub time: Option, - } + match testfn.into_runnable() { + Runnable::Test(runnable_test) => { + if runnable_test.is_dynamic() { + match strategy { + RunStrategy::InProcess => (), + _ => panic!("Cannot run dynamic test fn out-of-process"), + }; + } - fn run_test_inner( - id: TestId, - desc: TestDesc, - monitor_ch: Sender, - testfn: Box Result<(), String> + Send>, - opts: TestRunOpts, - ) -> Option> { - let name = desc.name.clone(); - - let runtest = move || match opts.strategy { - RunStrategy::InProcess => run_test_in_process( - id, - desc, - opts.nocapture, - opts.time.is_some(), - testfn, - monitor_ch, - opts.time, - ), - RunStrategy::SpawnPrimary => spawn_test_subprocess( - id, - desc, - opts.nocapture, - opts.time.is_some(), - monitor_ch, - opts.time, - ), - }; + let name = desc.name.clone(); + let nocapture = opts.nocapture; + let time_options = opts.time_options; + let bench_benchmarks = opts.bench_benchmarks; + + let runtest = move || match strategy { + RunStrategy::InProcess => run_test_in_process( + id, + desc, + nocapture, + time_options.is_some(), + runnable_test, + monitor_ch, + time_options, + ), + RunStrategy::SpawnPrimary => spawn_test_subprocess( + id, + desc, + nocapture, + time_options.is_some(), + monitor_ch, + time_options, + bench_benchmarks, + ), + }; - // If the platform is single-threaded we're just going to run - // the test synchronously, regardless of the concurrency - // level. - let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_family = "wasm"); - if supports_threads { - let cfg = thread::Builder::new().name(name.as_slice().to_owned()); - let mut runtest = Arc::new(Mutex::new(Some(runtest))); - let runtest2 = runtest.clone(); - match cfg.spawn(move || runtest2.lock().unwrap().take().unwrap()()) { - Ok(handle) => Some(handle), - Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - // `ErrorKind::WouldBlock` means hitting the thread limit on some - // platforms, so run the test synchronously here instead. - Arc::get_mut(&mut runtest).unwrap().get_mut().unwrap().take().unwrap()(); - None + // If the platform is single-threaded we're just going to run + // the test synchronously, regardless of the concurrency + // level. + let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_family = "wasm"); + if supports_threads { + let cfg = thread::Builder::new().name(name.as_slice().to_owned()); + let mut runtest = Arc::new(Mutex::new(Some(runtest))); + let runtest2 = runtest.clone(); + match cfg.spawn(move || runtest2.lock().unwrap().take().unwrap()()) { + Ok(handle) => Some(handle), + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // `ErrorKind::WouldBlock` means hitting the thread limit on some + // platforms, so run the test synchronously here instead. + Arc::get_mut(&mut runtest).unwrap().get_mut().unwrap().take().unwrap()(); + None + } + Err(e) => panic!("failed to spawn thread to run test: {e}"), } - Err(e) => panic!("failed to spawn thread to run test: {e}"), + } else { + runtest(); + None } - } else { - runtest(); - None } - } - - let test_run_opts = - TestRunOpts { strategy, nocapture: opts.nocapture, time: opts.time_options }; - - match testfn { - DynBenchFn(benchfn) => { + Runnable::Bench(runnable_bench) => { // Benchmarks aren't expected to panic, so we run them all in-process. - crate::bench::benchmark(id, desc, monitor_ch, opts.nocapture, benchfn); + runnable_bench.run(id, &desc, &monitor_ch, opts.nocapture); None } - StaticBenchFn(benchfn) => { - // Benchmarks aren't expected to panic, so we run them all in-process. - crate::bench::benchmark(id, desc, monitor_ch, opts.nocapture, benchfn); - None - } - DynTestFn(f) => { - match strategy { - RunStrategy::InProcess => (), - _ => panic!("Cannot run dynamic test fn out-of-process"), - }; - run_test_inner( - id, - desc, - monitor_ch, - Box::new(move || __rust_begin_short_backtrace(f)), - test_run_opts, - ) - } - StaticTestFn(f) => run_test_inner( - id, - desc, - monitor_ch, - Box::new(move || __rust_begin_short_backtrace(f)), - test_run_opts, - ), } } @@ -663,7 +634,7 @@ fn run_test_in_process( desc: TestDesc, nocapture: bool, report_time: bool, - testfn: Box Result<(), String> + Send>, + runnable_test: RunnableTest, monitor_ch: Sender, time_opts: Option, ) { @@ -675,7 +646,7 @@ fn run_test_in_process( } let start = report_time.then(Instant::now); - let result = fold_err(catch_unwind(AssertUnwindSafe(testfn))); + let result = fold_err(catch_unwind(AssertUnwindSafe(|| runnable_test.run()))); let exec_time = start.map(|start| { let duration = start.elapsed(); TestExecTime(duration) @@ -712,6 +683,7 @@ fn spawn_test_subprocess( report_time: bool, monitor_ch: Sender, time_opts: Option, + bench_benchmarks: bool, ) { let (result, test_output, exec_time) = (|| { let args = env::args().collect::>(); @@ -719,6 +691,9 @@ fn spawn_test_subprocess( let mut command = Command::new(current_exe); command.env(SECONDARY_TEST_INVOKER_VAR, desc.name.as_slice()); + if bench_benchmarks { + command.env(SECONDARY_TEST_BENCH_BENCHMARKS_VAR, "1"); + } if nocapture { command.stdout(process::Stdio::inherit()); command.stderr(process::Stdio::inherit()); @@ -760,10 +735,7 @@ fn spawn_test_subprocess( monitor_ch.send(message).unwrap(); } -fn run_test_in_spawned_subprocess( - desc: TestDesc, - testfn: Box Result<(), String> + Send>, -) -> ! { +fn run_test_in_spawned_subprocess(desc: TestDesc, runnable_test: RunnableTest) -> ! { let builtin_panic_hook = panic::take_hook(); let record_result = Arc::new(move |panic_info: Option<&'_ PanicInfo<'_>>| { let test_result = match panic_info { @@ -789,7 +761,7 @@ fn run_test_in_spawned_subprocess( }); let record_result2 = record_result.clone(); panic::set_hook(Box::new(move |info| record_result2(Some(info)))); - if let Err(message) = testfn() { + if let Err(message) = runnable_test.run() { panic!("{}", message); } record_result(None); diff --git a/library/test/src/options.rs b/library/test/src/options.rs index 75ec0b616e193..3eaad59474a12 100644 --- a/library/test/src/options.rs +++ b/library/test/src/options.rs @@ -16,19 +16,21 @@ pub enum ShouldPanic { } /// Whether should console output be colored or not -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Default, Debug)] pub enum ColorConfig { + #[default] AutoColor, AlwaysColor, NeverColor, } /// Format of the test results output -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub enum OutputFormat { /// Verbose output Pretty, /// Quiet output + #[default] Terse, /// JSON output Json, diff --git a/library/test/src/term/terminfo/searcher/tests.rs b/library/test/src/term/terminfo/searcher/tests.rs index 4227a585e2f59..e1edd3b25cf4b 100644 --- a/library/test/src/term/terminfo/searcher/tests.rs +++ b/library/test/src/term/terminfo/searcher/tests.rs @@ -6,14 +6,12 @@ fn test_get_dbpath_for_term() { // woefully inadequate test coverage // note: current tests won't work with non-standard terminfo hierarchies (e.g., macOS's) use std::env; - // FIXME (#9639): This needs to handle non-utf8 paths - fn x(t: &str) -> String { - let p = get_dbpath_for_term(t).expect("no terminfo entry found"); - p.to_str().unwrap().to_string() + fn x(t: &str) -> PathBuf { + get_dbpath_for_term(t).expect(&format!("no terminfo entry found for {t:?}")) } - assert!(x("screen") == "/usr/share/terminfo/s/screen"); - assert!(get_dbpath_for_term("") == None); + assert_eq!(x("screen"), PathBuf::from("/usr/share/terminfo/s/screen")); + assert_eq!(get_dbpath_for_term(""), None); env::set_var("TERMINFO_DIRS", ":"); - assert!(x("screen") == "/usr/share/terminfo/s/screen"); + assert_eq!(x("screen"), PathBuf::from("/usr/share/terminfo/s/screen")); env::remove_var("TERMINFO_DIRS"); } diff --git a/library/test/src/tests.rs b/library/test/src/tests.rs index c34583e695974..4ef18b14f4cdd 100644 --- a/library/test/src/tests.rs +++ b/library/test/src/tests.rs @@ -154,6 +154,7 @@ pub fn ignored_tests_result_in_ignored() { // FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251) #[test] #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_should_panic() { fn f() -> Result<(), String> { panic!(); @@ -184,6 +185,7 @@ fn test_should_panic() { // FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251) #[test] #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_should_panic_good_message() { fn f() -> Result<(), String> { panic!("an error message"); @@ -214,6 +216,7 @@ fn test_should_panic_good_message() { // FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251) #[test] #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_should_panic_bad_message() { use crate::tests::TrFailedMsg; fn f() -> Result<(), String> { @@ -249,6 +252,7 @@ fn test_should_panic_bad_message() { // FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251) #[test] #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_should_panic_non_string_message_type() { use crate::tests::TrFailedMsg; use std::any::TypeId; @@ -288,6 +292,7 @@ fn test_should_panic_non_string_message_type() { // FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251) #[test] #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_should_panic_but_succeeds() { let should_panic_variants = [ShouldPanic::Yes, ShouldPanic::YesWithMessage("error message")]; diff --git a/library/test/src/types.rs b/library/test/src/types.rs index e79914dbf4b21..1a8ae889c8c1b 100644 --- a/library/test/src/types.rs +++ b/library/test/src/types.rs @@ -2,8 +2,11 @@ use std::borrow::Cow; use std::fmt; +use std::sync::mpsc::Sender; +use super::__rust_begin_short_backtrace; use super::bench::Bencher; +use super::event::CompletedTest; use super::options; pub use NamePadding::*; @@ -82,8 +85,10 @@ impl fmt::Display for TestName { pub enum TestFn { StaticTestFn(fn() -> Result<(), String>), StaticBenchFn(fn(&mut Bencher) -> Result<(), String>), + StaticBenchAsTestFn(fn(&mut Bencher) -> Result<(), String>), DynTestFn(Box Result<(), String> + Send>), DynBenchFn(Box Result<(), String> + Send>), + DynBenchAsTestFn(Box Result<(), String> + Send>), } impl TestFn { @@ -91,8 +96,21 @@ impl TestFn { match *self { StaticTestFn(..) => PadNone, StaticBenchFn(..) => PadOnRight, + StaticBenchAsTestFn(..) => PadNone, DynTestFn(..) => PadNone, DynBenchFn(..) => PadOnRight, + DynBenchAsTestFn(..) => PadNone, + } + } + + pub(crate) fn into_runnable(self) -> Runnable { + match self { + StaticTestFn(f) => Runnable::Test(RunnableTest::Static(f)), + StaticBenchFn(f) => Runnable::Bench(RunnableBench::Static(f)), + StaticBenchAsTestFn(f) => Runnable::Test(RunnableTest::StaticBenchAsTest(f)), + DynTestFn(f) => Runnable::Test(RunnableTest::Dynamic(f)), + DynBenchFn(f) => Runnable::Bench(RunnableBench::Dynamic(f)), + DynBenchAsTestFn(f) => Runnable::Test(RunnableTest::DynamicBenchAsTest(f)), } } } @@ -102,12 +120,74 @@ impl fmt::Debug for TestFn { f.write_str(match *self { StaticTestFn(..) => "StaticTestFn(..)", StaticBenchFn(..) => "StaticBenchFn(..)", + StaticBenchAsTestFn(..) => "StaticBenchAsTestFn(..)", DynTestFn(..) => "DynTestFn(..)", DynBenchFn(..) => "DynBenchFn(..)", + DynBenchAsTestFn(..) => "DynBenchAsTestFn(..)", }) } } +pub(crate) enum Runnable { + Test(RunnableTest), + Bench(RunnableBench), +} + +pub(crate) enum RunnableTest { + Static(fn() -> Result<(), String>), + Dynamic(Box Result<(), String> + Send>), + StaticBenchAsTest(fn(&mut Bencher) -> Result<(), String>), + DynamicBenchAsTest(Box Result<(), String> + Send>), +} + +impl RunnableTest { + pub(crate) fn run(self) -> Result<(), String> { + match self { + RunnableTest::Static(f) => __rust_begin_short_backtrace(f), + RunnableTest::Dynamic(f) => __rust_begin_short_backtrace(f), + RunnableTest::StaticBenchAsTest(f) => { + crate::bench::run_once(|b| __rust_begin_short_backtrace(|| f(b))) + } + RunnableTest::DynamicBenchAsTest(f) => { + crate::bench::run_once(|b| __rust_begin_short_backtrace(|| f(b))) + } + } + } + + pub(crate) fn is_dynamic(&self) -> bool { + match self { + RunnableTest::Static(_) => false, + RunnableTest::StaticBenchAsTest(_) => false, + RunnableTest::Dynamic(_) => true, + RunnableTest::DynamicBenchAsTest(_) => true, + } + } +} + +pub(crate) enum RunnableBench { + Static(fn(&mut Bencher) -> Result<(), String>), + Dynamic(Box Result<(), String> + Send>), +} + +impl RunnableBench { + pub(crate) fn run( + self, + id: TestId, + desc: &TestDesc, + monitor_ch: &Sender, + nocapture: bool, + ) { + match self { + RunnableBench::Static(f) => { + crate::bench::benchmark(id, desc.clone(), monitor_ch.clone(), nocapture, f) + } + RunnableBench::Dynamic(f) => { + crate::bench::benchmark(id, desc.clone(), monitor_ch.clone(), nocapture, f) + } + } + } +} + // A unique integer associated with each test. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct TestId(pub usize); @@ -144,7 +224,7 @@ impl TestDesc { } } - /// Returns None for ignored test or that that are just run, otherwise give a description of the type of test. + /// Returns None for ignored test or tests that are just run, otherwise returns a description of the type of test. /// Descriptions include "should panic", "compile fail" and "compile". pub fn test_mode(&self) -> Option<&'static str> { if self.ignore { diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index b655bae967372..0b4daeafe46df 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -5,6 +5,7 @@ #![feature(c_unwind)] #![feature(cfg_target_abi)] #![cfg_attr(not(target_env = "msvc"), feature(libc))] +#![cfg_attr(not(bootstrap), allow(internal_features))] cfg_if::cfg_if! { if #[cfg(target_env = "msvc")] { diff --git a/library/unwind/src/libunwind.rs b/library/unwind/src/libunwind.rs index f6a68073b2f7e..a2bfa8e96dd22 100644 --- a/library/unwind/src/libunwind.rs +++ b/library/unwind/src/libunwind.rs @@ -51,10 +51,13 @@ pub const unwinder_private_data_size: usize = 5; #[cfg(target_arch = "m68k")] pub const unwinder_private_data_size: usize = 2; -#[cfg(target_arch = "mips")] +#[cfg(any(target_arch = "mips", target_arch = "mips32r6"))] pub const unwinder_private_data_size: usize = 2; -#[cfg(target_arch = "mips64")] +#[cfg(target_arch = "csky")] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(any(target_arch = "mips64", target_arch = "mips64r6"))] pub const unwinder_private_data_size: usize = 2; #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] @@ -117,7 +120,7 @@ extern "C" { } cfg_if::cfg_if! { -if #[cfg(any(target_os = "ios", target_os = "watchos", target_os = "netbsd", not(target_arch = "arm")))] { +if #[cfg(any(target_os = "ios", target_os = "tvos", target_os = "watchos", target_os = "netbsd", not(target_arch = "arm")))] { // Not ARM EHABI #[repr(C)] #[derive(Copy, Clone, PartialEq)] diff --git a/rustfmt.toml b/rustfmt.toml index 828d492a3d19c..88700779e87be 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -16,6 +16,8 @@ ignore = [ "tests", # do not format submodules + # FIXME: sync submodule list with tidy/bootstrap/etc + # tidy/src/walk.rs:filter_dirs "library/backtrace", "library/portable-simd", "library/stdarch", @@ -31,13 +33,10 @@ ignore = [ "src/tools/cargo", "src/tools/clippy", "src/tools/miri", - "src/tools/rls", "src/tools/rust-analyzer", "src/tools/rustfmt", - "src/tools/rust-installer", # these are ignored by a standard cargo fmt run "compiler/rustc_codegen_cranelift/y.rs", # running rustfmt breaks this file - "compiler/rustc_codegen_cranelift/example", "compiler/rustc_codegen_cranelift/scripts", ] diff --git a/src/bootstrap/CHANGELOG.md b/src/bootstrap/CHANGELOG.md index d6924cf2cfb23..1aba0713850ae 100644 --- a/src/bootstrap/CHANGELOG.md +++ b/src/bootstrap/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `x.py fmt` now formats only files modified between the merge-base of HEAD and the last commit in the master branch of the rust-lang repository and the current working directory. To restore old behaviour, use `x.py fmt .`. The check mode is not affected by this change. [#105702](https://github.com/rust-lang/rust/pull/105702) - The `llvm.version-check` config option has been removed. Older versions were never supported. If you still need to support older versions (e.g. you are applying custom patches), patch `check_llvm_version` in bootstrap to change the minimum version. [#108619](https://github.com/rust-lang/rust/pull/108619) - The `rust.ignore-git` option has been renamed to `rust.omit-git-hash`. [#110059](https://github.com/rust-lang/rust/pull/110059) +- `--exclude` no longer accepts a `Kind` as part of a Step; instead it uses the top-level Kind of the subcommand. If this matches how you were already using --exclude (e.g. `x test --exclude test::std`), simply remove the kind: `--exclude std`. If you were using a kind that did not match the top-level subcommand, please open an issue explaining why you wanted this feature. ### Non-breaking changes diff --git a/src/bootstrap/Cargo.lock b/src/bootstrap/Cargo.lock index 8f8778efee796..ecb58a0e9a5f6 100644 --- a/src/bootstrap/Cargo.lock +++ b/src/bootstrap/Cargo.lock @@ -51,7 +51,6 @@ dependencies = [ "filetime", "hex", "ignore", - "is-terminal", "junction", "libc", "object", @@ -86,6 +85,10 @@ dependencies = [ [[package]] name = "build_helper" version = "0.1.0" +dependencies = [ + "serde", + "serde_derive", +] [[package]] name = "cc" @@ -386,18 +389,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "is-terminal" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" -dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys", -] - [[package]] name = "itoa" version = "1.0.2" @@ -488,9 +479,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] @@ -540,9 +531,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index 367c6190967c6..74b9a23fa4e17 100644 --- a/src/bootstrap/Cargo.toml +++ b/src/bootstrap/Cargo.toml @@ -30,14 +30,13 @@ path = "bin/sccache-plus-cl.rs" test = false [dependencies] -is-terminal = "0.4" build_helper = { path = "../tools/build_helper" } cmake = "0.1.38" filetime = "0.2" cc = "1.0.69" libc = "0.2" hex = "0.4" -object = { version = "0.31.1", default-features = false, features = ["archive", "coff", "read_core", "unaligned"] } +object = { version = "0.32.0", default-features = false, features = ["archive", "coff", "read_core", "unaligned"] } serde = "1.0.137" # Directly use serde_derive rather than through the derive feature of serde to allow building both # in parallel and to allow serde_json and toml to start building as soon as serde has been built. diff --git a/src/bootstrap/bin/main.rs b/src/bootstrap/bin/main.rs index a80379e85c193..c497cabbd6922 100644 --- a/src/bootstrap/bin/main.rs +++ b/src/bootstrap/bin/main.rs @@ -5,7 +5,11 @@ //! parent directory, and otherwise documentation can be found throughout the `build` //! directory in each respective module. -use std::env; +#[cfg(all(any(unix, windows), not(target_os = "solaris")))] +use std::io::Write; +#[cfg(all(any(unix, windows), not(target_os = "solaris")))] +use std::process; +use std::{env, fs}; #[cfg(all(any(unix, windows), not(target_os = "solaris")))] use bootstrap::t; @@ -20,22 +24,32 @@ fn main() { #[cfg(all(any(unix, windows), not(target_os = "solaris")))] let _build_lock_guard; #[cfg(all(any(unix, windows), not(target_os = "solaris")))] + // Display PID of process holding the lock + // PID will be stored in a lock file { let path = config.out.join("lock"); - build_lock = fd_lock::RwLock::new(t!(std::fs::File::create(&path))); + let pid = match fs::read_to_string(&path) { + Ok(contents) => contents, + Err(_) => String::new(), + }; + + build_lock = + fd_lock::RwLock::new(t!(fs::OpenOptions::new().write(true).create(true).open(&path))); _build_lock_guard = match build_lock.try_write() { - Ok(lock) => lock, + Ok(mut lock) => { + t!(lock.write(&process::id().to_string().as_ref())); + lock + } err => { drop(err); - if let Some(pid) = get_lock_owner(&path) { - println!("warning: build directory locked by process {pid}, waiting for lock"); - } else { - println!("warning: build directory locked, waiting for lock"); - } - t!(build_lock.write()) + println!("warning: build directory locked by process {pid}, waiting for lock"); + let mut lock = t!(build_lock.write()); + t!(lock.write(&process::id().to_string().as_ref())); + lock } }; } + #[cfg(any(not(any(unix, windows)), target_os = "solaris"))] println!("warning: file locking not supported for target, not locking build directory"); @@ -53,7 +67,7 @@ fn main() { `cp config.example.toml config.toml`" ); } else if let Some(suggestion) = &changelog_suggestion { - println!("{}", suggestion); + println!("{suggestion}"); } let pre_commit = config.src.join(".git").join("hooks").join("pre-commit"); @@ -66,14 +80,14 @@ fn main() { `cp config.example.toml config.toml`" ); } else if let Some(suggestion) = &changelog_suggestion { - println!("{}", suggestion); + println!("{suggestion}"); } // Give a warning if the pre-commit script is in pre-commit and not pre-push. // HACK: Since the commit script uses hard links, we can't actually tell if it was installed by x.py setup or not. // We could see if it's identical to src/etc/pre-push.sh, but pre-push may have been modified in the meantime. // Instead, look for this comment, which is almost certainly not in any custom hook. - if std::fs::read_to_string(pre_commit).map_or(false, |contents| { + if fs::read_to_string(pre_commit).map_or(false, |contents| { contents.contains("https://github.com/rust-lang/rust/issues/77620#issuecomment-705144570") }) { println!( @@ -93,13 +107,13 @@ fn check_version(config: &Config) -> Option { let suggestion = if let Some(seen) = config.changelog_seen { if seen != VERSION { msg.push_str("warning: there have been changes to x.py since you last updated.\n"); - format!("update `config.toml` to use `changelog-seen = {}` instead", VERSION) + format!("update `config.toml` to use `changelog-seen = {VERSION}` instead") } else { return None; } } else { msg.push_str("warning: x.py has made several changes recently you may want to look at\n"); - format!("add `changelog-seen = {}` at the top of `config.toml`", VERSION) + format!("add `changelog-seen = {VERSION}` at the top of `config.toml`") }; msg.push_str("help: consider looking at the changes in `src/bootstrap/CHANGELOG.md`\n"); @@ -108,30 +122,3 @@ fn check_version(config: &Config) -> Option { Some(msg) } - -/// Get the PID of the process which took the write lock by -/// parsing `/proc/locks`. -#[cfg(target_os = "linux")] -fn get_lock_owner(f: &std::path::Path) -> Option { - use std::fs::File; - use std::io::{BufRead, BufReader}; - use std::os::unix::fs::MetadataExt; - - let lock_inode = std::fs::metadata(f).ok()?.ino(); - let lockfile = File::open("/proc/locks").ok()?; - BufReader::new(lockfile).lines().find_map(|line| { - // pid--vvvvvv vvvvvvv--- inode - // 21: FLOCK ADVISORY WRITE 359238 08:02:3719774 0 EOF - let line = line.ok()?; - let parts = line.split_whitespace().collect::>(); - let (pid, inode) = (parts[4].parse::().ok()?, &parts[5]); - let inode = inode.rsplit_once(':')?.1.parse::().ok()?; - if inode == lock_inode { Some(pid) } else { None } - }) -} - -#[cfg(not(any(target_os = "linux", target_os = "solaris")))] -fn get_lock_owner(_: &std::path::Path) -> Option { - // FIXME: Implement on other OS's - None -} diff --git a/src/bootstrap/bin/rustc.rs b/src/bootstrap/bin/rustc.rs index e87125a49a6a0..10718aeb89f3d 100644 --- a/src/bootstrap/bin/rustc.rs +++ b/src/bootstrap/bin/rustc.rs @@ -120,7 +120,7 @@ fn main() { // Override linker if necessary. if let Ok(host_linker) = env::var("RUSTC_HOST_LINKER") { - cmd.arg(format!("-Clinker={}", host_linker)); + cmd.arg(format!("-Clinker={host_linker}")); } if env::var_os("RUSTC_HOST_FUSE_LD_LLD").is_some() { cmd.arg("-Clink-args=-fuse-ld=lld"); @@ -206,11 +206,11 @@ fn main() { env::vars().filter(|(k, _)| k.starts_with("RUST") || k.starts_with("CARGO")); let prefix = if is_test { "[RUSTC-SHIM] rustc --test" } else { "[RUSTC-SHIM] rustc" }; let prefix = match crate_name { - Some(crate_name) => format!("{} {}", prefix, crate_name), + Some(crate_name) => format!("{prefix} {crate_name}"), None => prefix.to_string(), }; for (i, (k, v)) in rust_env_vars.enumerate() { - eprintln!("{} env[{}]: {:?}={:?}", prefix, i, k, v); + eprintln!("{prefix} env[{i}]: {k:?}={v:?}"); } eprintln!("{} working directory: {}", prefix, env::current_dir().unwrap().display()); eprintln!( @@ -220,13 +220,13 @@ fn main() { env::join_paths(&dylib_path).unwrap(), cmd, ); - eprintln!("{} sysroot: {:?}", prefix, sysroot); - eprintln!("{} libdir: {:?}", prefix, libdir); + eprintln!("{prefix} sysroot: {sysroot:?}"); + eprintln!("{prefix} libdir: {libdir:?}"); } let start = Instant::now(); let (child, status) = { - let errmsg = format!("\nFailed to run:\n{:?}\n-------------", cmd); + let errmsg = format!("\nFailed to run:\n{cmd:?}\n-------------"); let mut child = cmd.spawn().expect(&errmsg); let status = child.wait().expect(&errmsg); (child, status) @@ -259,7 +259,7 @@ fn main() { // should run on success, after this block. } if verbose > 0 { - println!("\nDid not run successfully: {}\n{:?}\n-------------", status, cmd); + println!("\nDid not run successfully: {status}\n{cmd:?}\n-------------"); } if let Some(mut on_fail) = on_fail { @@ -271,7 +271,7 @@ fn main() { match status.code() { Some(i) => std::process::exit(i), None => { - eprintln!("rustc exited with {}", status); + eprintln!("rustc exited with {status}"); std::process::exit(0xfe); } } @@ -396,21 +396,20 @@ fn format_rusage_data(_child: Child) -> Option { let minflt = rusage.ru_minflt; let majflt = rusage.ru_majflt; if minflt != 0 || majflt != 0 { - init_str.push_str(&format!(" page reclaims: {} page faults: {}", minflt, majflt)); + init_str.push_str(&format!(" page reclaims: {minflt} page faults: {majflt}")); } let inblock = rusage.ru_inblock; let oublock = rusage.ru_oublock; if inblock != 0 || oublock != 0 { - init_str.push_str(&format!(" fs block inputs: {} fs block outputs: {}", inblock, oublock)); + init_str.push_str(&format!(" fs block inputs: {inblock} fs block outputs: {oublock}")); } let nvcsw = rusage.ru_nvcsw; let nivcsw = rusage.ru_nivcsw; if nvcsw != 0 || nivcsw != 0 { init_str.push_str(&format!( - " voluntary ctxt switches: {} involuntary ctxt switches: {}", - nvcsw, nivcsw + " voluntary ctxt switches: {nvcsw} involuntary ctxt switches: {nivcsw}" )); } diff --git a/src/bootstrap/bin/rustdoc.rs b/src/bootstrap/bin/rustdoc.rs index d2b85f7a6297b..4ecb3349816ee 100644 --- a/src/bootstrap/bin/rustdoc.rs +++ b/src/bootstrap/bin/rustdoc.rs @@ -62,7 +62,7 @@ fn main() { } if let Ok(no_threads) = env::var("RUSTDOC_LLD_NO_THREADS") { cmd.arg("-Clink-arg=-fuse-ld=lld"); - cmd.arg(format!("-Clink-arg=-Wl,{}", no_threads)); + cmd.arg(format!("-Clink-arg=-Wl,{no_threads}")); } // Cargo doesn't pass RUSTDOCFLAGS to proc_macros: // https://github.com/rust-lang/cargo/issues/4423 @@ -82,12 +82,12 @@ fn main() { env::join_paths(&dylib_path).unwrap(), cmd, ); - eprintln!("sysroot: {:?}", sysroot); - eprintln!("libdir: {:?}", libdir); + eprintln!("sysroot: {sysroot:?}"); + eprintln!("libdir: {libdir:?}"); } std::process::exit(match cmd.status() { Ok(s) => s.code().unwrap_or(1), - Err(e) => panic!("\n\nfailed to run {:?}: {}\n\n", cmd, e), + Err(e) => panic!("\n\nfailed to run {cmd:?}: {e}\n\n"), }) } diff --git a/src/bootstrap/bolt.rs b/src/bootstrap/bolt.rs deleted file mode 100644 index 5384181ea687d..0000000000000 --- a/src/bootstrap/bolt.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::path::Path; -use std::process::Command; - -/// Uses the `llvm-bolt` binary to instrument the artifact at the given `path` with BOLT. -/// When the instrumented artifact is executed, it will generate BOLT profiles into -/// `/tmp/prof.fdata..fdata`. -/// Creates the instrumented artifact at `output_path`. -pub fn instrument_with_bolt(path: &Path, output_path: &Path) { - let status = Command::new("llvm-bolt") - .arg("-instrument") - .arg(&path) - // Make sure that each process will write its profiles into a separate file - .arg("--instrumentation-file-append-pid") - .arg("-o") - .arg(output_path) - .status() - .expect("Could not instrument artifact using BOLT"); - - if !status.success() { - panic!("Could not instrument {} with BOLT, exit code {:?}", path.display(), status.code()); - } -} - -/// Uses the `llvm-bolt` binary to optimize the artifact at the given `path` with BOLT, -/// using merged profiles from `profile_path`. -/// -/// The recorded profiles have to be merged using the `merge-fdata` tool from LLVM and the merged -/// profile path should be then passed to this function. -/// -/// Creates the optimized artifact at `output_path`. -pub fn optimize_with_bolt(path: &Path, profile_path: &Path, output_path: &Path) { - let status = Command::new("llvm-bolt") - .arg(&path) - .arg("-data") - .arg(&profile_path) - .arg("-o") - .arg(output_path) - // Reorder basic blocks within functions - .arg("-reorder-blocks=ext-tsp") - // Reorder functions within the binary - .arg("-reorder-functions=hfsort+") - // Split function code into hot and code regions - .arg("-split-functions") - // Split as many basic blocks as possible - .arg("-split-all-cold") - // Move jump tables to a separate section - .arg("-jump-tables=move") - // Fold functions with identical code - .arg("-icf=1") - // Update DWARF debug info in the final binary - .arg("-update-debug-sections") - // Print optimization statistics - .arg("-dyno-stats") - .status() - .expect("Could not optimize artifact using BOLT"); - - if !status.success() { - panic!("Could not optimize {} with BOLT, exit code {:?}", path.display(), status.code()); - } -} diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py index 50ace987193a2..f44a05a6b281c 100644 --- a/src/bootstrap/bootstrap.py +++ b/src/bootstrap/bootstrap.py @@ -97,7 +97,7 @@ def _download(path, url, probably_big, verbose, exception): print("downloading {}".format(url), file=sys.stderr) try: - if probably_big or verbose: + if (probably_big or verbose) and "GITHUB_ACTIONS" not in os.environ: option = "-#" else: option = "-s" @@ -226,16 +226,13 @@ def format_build_time(duration): def default_build_triple(verbose): """Build triple as in LLVM""" - # If the user already has a host build triple with an existing `rustc` - # install, use their preference. This fixes most issues with Windows builds - # being detected as GNU instead of MSVC. + # If we're on Windows and have an existing `rustc` toolchain, use `rustc --version --verbose` + # to find our host target triple. This fixes an issue with Windows builds being detected + # as GNU instead of MSVC. + # Otherwise, detect it via `uname` default_encoding = sys.getdefaultencoding() - if sys.platform == 'darwin': - if verbose: - print("not using rustc detection as it is unreliable on macOS", file=sys.stderr) - print("falling back to auto-detect", file=sys.stderr) - else: + if platform_is_win32(): try: version = subprocess.check_output(["rustc", "--version", "--verbose"], stderr=subprocess.DEVNULL) @@ -253,19 +250,17 @@ def default_build_triple(verbose): print("falling back to auto-detect", file=sys.stderr) required = not platform_is_win32() - ostype = require(["uname", "-s"], exit=required) - cputype = require(['uname', '-m'], exit=required) + uname = require(["uname", "-smp"], exit=required) # If we do not have `uname`, assume Windows. - if ostype is None or cputype is None: + if uname is None: return 'x86_64-pc-windows-msvc' - ostype = ostype.decode(default_encoding) - cputype = cputype.decode(default_encoding) + kernel, cputype, processor = uname.decode(default_encoding).split(maxsplit=2) # The goal here is to come up with the same triple as LLVM would, # at least for the subset of platforms we're willing to target. - ostype_mapper = { + kerneltype_mapper = { 'Darwin': 'apple-darwin', 'DragonFly': 'unknown-dragonfly', 'FreeBSD': 'unknown-freebsd', @@ -275,17 +270,18 @@ def default_build_triple(verbose): } # Consider the direct transformation first and then the special cases - if ostype in ostype_mapper: - ostype = ostype_mapper[ostype] - elif ostype == 'Linux': - os_from_sp = subprocess.check_output( - ['uname', '-o']).strip().decode(default_encoding) - if os_from_sp == 'Android': - ostype = 'linux-android' + if kernel in kerneltype_mapper: + kernel = kerneltype_mapper[kernel] + elif kernel == 'Linux': + # Apple doesn't support `-o` so this can't be used in the combined + # uname invocation above + ostype = require(["uname", "-o"], exit=required).decode(default_encoding) + if ostype == 'Android': + kernel = 'linux-android' else: - ostype = 'unknown-linux-gnu' - elif ostype == 'SunOS': - ostype = 'pc-solaris' + kernel = 'unknown-linux-gnu' + elif kernel == 'SunOS': + kernel = 'pc-solaris' # On Solaris, uname -m will return a machine classification instead # of a cpu type, so uname -p is recommended instead. However, the # output from that option is too generic for our purposes (it will @@ -294,39 +290,40 @@ def default_build_triple(verbose): cputype = require(['isainfo', '-k']).decode(default_encoding) # sparc cpus have sun as a target vendor if 'sparc' in cputype: - ostype = 'sun-solaris' - elif ostype.startswith('MINGW'): + kernel = 'sun-solaris' + elif kernel.startswith('MINGW'): # msys' `uname` does not print gcc configuration, but prints msys # configuration. so we cannot believe `uname -m`: # msys1 is always i686 and msys2 is always x86_64. # instead, msys defines $MSYSTEM which is MINGW32 on i686 and # MINGW64 on x86_64. - ostype = 'pc-windows-gnu' + kernel = 'pc-windows-gnu' cputype = 'i686' if os.environ.get('MSYSTEM') == 'MINGW64': cputype = 'x86_64' - elif ostype.startswith('MSYS'): - ostype = 'pc-windows-gnu' - elif ostype.startswith('CYGWIN_NT'): + elif kernel.startswith('MSYS'): + kernel = 'pc-windows-gnu' + elif kernel.startswith('CYGWIN_NT'): cputype = 'i686' - if ostype.endswith('WOW64'): + if kernel.endswith('WOW64'): cputype = 'x86_64' - ostype = 'pc-windows-gnu' - elif sys.platform == 'win32': + kernel = 'pc-windows-gnu' + elif platform_is_win32(): # Some Windows platforms might have a `uname` command that returns a # non-standard string (e.g. gnuwin32 tools returns `windows32`). In # these cases, fall back to using sys.platform. return 'x86_64-pc-windows-msvc' else: - err = "unknown OS type: {}".format(ostype) + err = "unknown OS type: {}".format(kernel) sys.exit(err) - if cputype in ['powerpc', 'riscv'] and ostype == 'unknown-freebsd': + if cputype in ['powerpc', 'riscv'] and kernel == 'unknown-freebsd': cputype = subprocess.check_output( ['uname', '-p']).strip().decode(default_encoding) cputype_mapper = { 'BePC': 'i686', 'aarch64': 'aarch64', + 'aarch64eb': 'aarch64', 'amd64': 'x86_64', 'arm64': 'aarch64', 'i386': 'i686', @@ -335,6 +332,7 @@ def default_build_triple(verbose): 'i786': 'i686', 'loongarch64': 'loongarch64', 'm68k': 'm68k', + 'csky': 'csky', 'powerpc': 'powerpc', 'powerpc64': 'powerpc64', 'powerpc64le': 'powerpc64le', @@ -354,24 +352,23 @@ def default_build_triple(verbose): cputype = cputype_mapper[cputype] elif cputype in {'xscale', 'arm'}: cputype = 'arm' - if ostype == 'linux-android': - ostype = 'linux-androideabi' - elif ostype == 'unknown-freebsd': - cputype = subprocess.check_output( - ['uname', '-p']).strip().decode(default_encoding) - ostype = 'unknown-freebsd' + if kernel == 'linux-android': + kernel = 'linux-androideabi' + elif kernel == 'unknown-freebsd': + cputype = processor + kernel = 'unknown-freebsd' elif cputype == 'armv6l': cputype = 'arm' - if ostype == 'linux-android': - ostype = 'linux-androideabi' + if kernel == 'linux-android': + kernel = 'linux-androideabi' else: - ostype += 'eabihf' + kernel += 'eabihf' elif cputype in {'armv7l', 'armv8l'}: cputype = 'armv7' - if ostype == 'linux-android': - ostype = 'linux-androideabi' + if kernel == 'linux-android': + kernel = 'linux-androideabi' else: - ostype += 'eabihf' + kernel += 'eabihf' elif cputype == 'mips': if sys.byteorder == 'big': cputype = 'mips' @@ -387,14 +384,14 @@ def default_build_triple(verbose): else: raise ValueError('unknown byteorder: {}'.format(sys.byteorder)) # only the n64 ABI is supported, indicate it - ostype += 'abi64' + kernel += 'abi64' elif cputype == 'sparc' or cputype == 'sparcv9' or cputype == 'sparc64': pass else: err = "unknown cpu type: {}".format(cputype) sys.exit(err) - return "{}-{}".format(cputype, ostype) + return "{}-{}".format(cputype, kernel) @contextlib.contextmanager @@ -463,23 +460,53 @@ def unpack_component(download_info): verbose=download_info.verbose, ) -class RustBuild(object): - """Provide all the methods required to build Rust""" +class FakeArgs: + """Used for unit tests to avoid updating all call sites""" def __init__(self): - self.checksums_sha256 = {} - self.stage0_compiler = None - self.download_url = '' self.build = '' self.build_dir = '' self.clean = False - self.config_toml = '' - self.rust_root = '' - self.use_locked_deps = False - self.use_vendored_sources = False self.verbose = False + self.json_output = False + self.color = 'auto' + self.warnings = 'default' + +class RustBuild(object): + """Provide all the methods required to build Rust""" + def __init__(self, config_toml="", args=None): + if args is None: + args = FakeArgs() self.git_version = None self.nix_deps_dir = None self._should_fix_bins_and_dylibs = None + self.rust_root = os.path.abspath(os.path.join(__file__, '../../..')) + + self.config_toml = config_toml + + self.clean = args.clean + self.json_output = args.json_output + self.verbose = args.verbose + self.color = args.color + self.warnings = args.warnings + + config_verbose_count = self.get_toml('verbose', 'build') + if config_verbose_count is not None: + self.verbose = max(self.verbose, int(config_verbose_count)) + + self.use_vendored_sources = self.get_toml('vendor', 'build') == 'true' + self.use_locked_deps = self.get_toml('locked-deps', 'build') == 'true' + + build_dir = args.build_dir or self.get_toml('build-dir', 'build') or 'build' + self.build_dir = os.path.abspath(build_dir) + + with open(os.path.join(self.rust_root, "src", "stage0.json")) as f: + data = json.load(f) + self.checksums_sha256 = data["checksums_sha256"] + self.stage0_compiler = Stage0Toolchain(data["compiler"]) + self.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"] + + self.build = args.build or self.build_triple() + def download_toolchain(self): """Fetch the build system for Rust, written in Rust @@ -625,7 +652,7 @@ def get_answer(): # The latter one does not exist on NixOS when using tmpfs as root. try: with open("/etc/os-release", "r") as f: - if not any(l.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"') for l in f): + if not any(ln.strip() in ("ID=nixos", "ID='nixos'", 'ID="nixos"') for ln in f): return False except FileNotFoundError: return False @@ -709,9 +736,10 @@ def rustc_stamp(self): """Return the path for .rustc-stamp at the given stage >>> rb = RustBuild() + >>> rb.build = "host" >>> rb.build_dir = "build" - >>> rb.rustc_stamp() == os.path.join("build", "stage0", ".rustc-stamp") - True + >>> expected = os.path.join("build", "host", "stage0", ".rustc-stamp") + >>> assert rb.rustc_stamp() == expected, rb.rustc_stamp() """ return os.path.join(self.bin_root(), '.rustc-stamp') @@ -726,15 +754,9 @@ def bin_root(self): """Return the binary root directory for the given stage >>> rb = RustBuild() - >>> rb.build_dir = "build" - >>> rb.bin_root() == os.path.join("build", "stage0") - True - - When the 'build' property is given should be a nested directory: - >>> rb.build = "devel" - >>> rb.bin_root() == os.path.join("build", "devel", "stage0") - True + >>> expected = os.path.abspath(os.path.join("build", "devel", "stage0")) + >>> assert rb.bin_root() == expected, rb.bin_root() """ subdir = "stage0" return os.path.join(self.build_dir, self.build, subdir) @@ -766,9 +788,12 @@ def get_toml(self, key, section=None): >>> rb.get_toml("key1") 'true' """ + return RustBuild.get_toml_static(self.config_toml, key, section) + @staticmethod + def get_toml_static(config_toml, key, section=None): cur_section = None - for line in self.config_toml.splitlines(): + for line in config_toml.splitlines(): section_match = re.match(r'^\s*\[(.*)\]\s*$', line) if section_match is not None: cur_section = section_match.group(1) @@ -777,7 +802,7 @@ def get_toml(self, key, section=None): if match is not None: value = match.group(1) if section is None or section == cur_section: - return self.get_string(value) or value.strip() + return RustBuild.get_string(value) or value.strip() return None def cargo(self): @@ -840,13 +865,23 @@ def bootstrap_binary(self): """ return os.path.join(self.build_dir, "bootstrap", "debug", "bootstrap") - def build_bootstrap(self, color, verbose_count): + def build_bootstrap(self): """Build bootstrap""" env = os.environ.copy() if "GITHUB_ACTIONS" in env: print("::group::Building bootstrap") else: print("Building bootstrap", file=sys.stderr) + + args = self.build_bootstrap_cmd(env) + # Run this from the source directory so cargo finds .cargo/config + run(args, env=env, verbose=self.verbose, cwd=self.rust_root) + + if "GITHUB_ACTIONS" in env: + print("::endgroup::") + + def build_bootstrap_cmd(self, env): + """For tests.""" build_dir = os.path.join(self.build_dir, "bootstrap") if self.clean and os.path.exists(build_dir): shutil.rmtree(build_dir) @@ -877,11 +912,12 @@ def build_bootstrap(self, color, verbose_count): } for var_name, toml_key in var_data.items(): toml_val = self.get_toml(toml_key, build_section) - if toml_val != None: + if toml_val is not None: env["{}_{}".format(var_name, host_triple_sanitized)] = toml_val # preserve existing RUSTFLAGS env.setdefault("RUSTFLAGS", "") + target_features = [] if self.get_toml("crt-static", build_section) == "true": target_features += ["+crt-static"] @@ -893,7 +929,11 @@ def build_bootstrap(self, color, verbose_count): if target_linker is not None: env["RUSTFLAGS"] += " -C linker=" + target_linker env["RUSTFLAGS"] += " -Wrust_2018_idioms -Wunused_lifetimes" - if self.get_toml("deny-warnings", "rust") != "false": + if self.warnings == "default": + deny_warnings = self.get_toml("deny-warnings", "rust") != "false" + else: + deny_warnings = self.warnings == "deny" + if deny_warnings: env["RUSTFLAGS"] += " -Dwarnings" env["PATH"] = os.path.join(self.bin_root(), "bin") + \ @@ -903,7 +943,7 @@ def build_bootstrap(self, color, verbose_count): self.cargo())) args = [self.cargo(), "build", "--manifest-path", os.path.join(self.rust_root, "src/bootstrap/Cargo.toml")] - args.extend("--verbose" for _ in range(verbose_count)) + args.extend("--verbose" for _ in range(self.verbose)) if self.use_locked_deps: args.append("--locked") if self.use_vendored_sources: @@ -913,16 +953,16 @@ def build_bootstrap(self, color, verbose_count): args.append("build-metrics") if self.json_output: args.append("--message-format=json") - if color == "always": + if self.color == "always": args.append("--color=always") - elif color == "never": + elif self.color == "never": args.append("--color=never") + try: + args += env["CARGOFLAGS"].split() + except KeyError: + pass - # Run this from the source directory so cargo finds .cargo/config - run(args, env=env, verbose=self.verbose, cwd=self.rust_root) - - if "GITHUB_ACTIONS" in env: - print("::endgroup::") + return args def build_triple(self): """Build triple as in LLVM @@ -972,7 +1012,7 @@ def check_vendored_status(self): if os.path.exists(cargo_dir): shutil.rmtree(cargo_dir) -def parse_args(): +def parse_args(args): """Parse the command line arguments that the python script needs.""" parser = argparse.ArgumentParser(add_help=False) parser.add_argument('-h', '--help', action='store_true') @@ -982,18 +1022,14 @@ def parse_args(): parser.add_argument('--color', choices=['always', 'never', 'auto']) parser.add_argument('--clean', action='store_true') parser.add_argument('--json-output', action='store_true') + parser.add_argument('--warnings', choices=['deny', 'warn', 'default'], default='default') parser.add_argument('-v', '--verbose', action='count', default=0) - return parser.parse_known_args(sys.argv)[0] + return parser.parse_known_args(args)[0] def bootstrap(args): """Configure, fetch, build and run the initial bootstrap""" - # Configure initial bootstrap - build = RustBuild() - build.rust_root = os.path.abspath(os.path.join(__file__, '../../..')) - build.verbose = args.verbose != 0 - build.clean = args.clean - build.json_output = args.json_output + rust_root = os.path.abspath(os.path.join(__file__, '../../..')) # Read from `--config`, then `RUST_BOOTSTRAP_CONFIG`, then `./config.toml`, # then `config.toml` in the root directory. @@ -1002,52 +1038,43 @@ def bootstrap(args): if using_default_path: toml_path = 'config.toml' if not os.path.exists(toml_path): - toml_path = os.path.join(build.rust_root, toml_path) + toml_path = os.path.join(rust_root, toml_path) # Give a hard error if `--config` or `RUST_BOOTSTRAP_CONFIG` are set to a missing path, # but not if `config.toml` hasn't been created. if not using_default_path or os.path.exists(toml_path): with open(toml_path) as config: - build.config_toml = config.read() + config_toml = config.read() + else: + config_toml = '' - profile = build.get_toml('profile') + profile = RustBuild.get_toml_static(config_toml, 'profile') if profile is not None: - include_file = 'config.{}.toml'.format(profile) - include_dir = os.path.join(build.rust_root, 'src', 'bootstrap', 'defaults') + # Allows creating alias for profile names, allowing + # profiles to be renamed while maintaining back compatibility + # Keep in sync with `profile_aliases` in config.rs + profile_aliases = { + "user": "dist" + } + include_file = 'config.{}.toml'.format(profile_aliases.get(profile) or profile) + include_dir = os.path.join(rust_root, 'src', 'bootstrap', 'defaults') include_path = os.path.join(include_dir, include_file) - # HACK: This works because `build.get_toml()` returns the first match it finds for a + # HACK: This works because `self.get_toml()` returns the first match it finds for a # specific key, so appending our defaults at the end allows the user to override them with open(include_path) as included_toml: - build.config_toml += os.linesep + included_toml.read() - - verbose_count = args.verbose - config_verbose_count = build.get_toml('verbose', 'build') - if config_verbose_count is not None: - verbose_count = max(args.verbose, int(config_verbose_count)) - - build.use_vendored_sources = build.get_toml('vendor', 'build') == 'true' - build.use_locked_deps = build.get_toml('locked-deps', 'build') == 'true' + config_toml += os.linesep + included_toml.read() + # Configure initial bootstrap + build = RustBuild(config_toml, args) build.check_vendored_status() - build_dir = args.build_dir or build.get_toml('build-dir', 'build') or 'build' - build.build_dir = os.path.abspath(build_dir) - - with open(os.path.join(build.rust_root, "src", "stage0.json")) as f: - data = json.load(f) - build.checksums_sha256 = data["checksums_sha256"] - build.stage0_compiler = Stage0Toolchain(data["compiler"]) - build.download_url = os.getenv("RUSTUP_DIST_SERVER") or data["config"]["dist_server"] - - build.build = args.build or build.build_triple() - if not os.path.exists(build.build_dir): os.makedirs(build.build_dir) # Fetch/build the bootstrap build.download_toolchain() sys.stdout.flush() - build.build_bootstrap(args.color, verbose_count) + build.build_bootstrap() sys.stdout.flush() # Run the bootstrap @@ -1067,7 +1094,7 @@ def main(): if len(sys.argv) > 1 and sys.argv[1] == 'help': sys.argv[1] = '-h' - args = parse_args() + args = parse_args(sys.argv) help_triggered = args.help or len(sys.argv) == 1 # If the user is asking for help, let them know that the whole download-and-build diff --git a/src/bootstrap/bootstrap_test.py b/src/bootstrap/bootstrap_test.py index 5ecda83ee66b1..dc06a4c97343d 100644 --- a/src/bootstrap/bootstrap_test.py +++ b/src/bootstrap/bootstrap_test.py @@ -1,8 +1,9 @@ -"""Bootstrap tests""" +"""Bootstrap tests + +Run these with `x test bootstrap`, or `python -m unittest src/bootstrap/bootstrap_test.py`.""" from __future__ import absolute_import, division, print_function import os -import doctest import unittest import tempfile import hashlib @@ -10,8 +11,31 @@ from shutil import rmtree -import bootstrap -import configure +# Allow running this from the top-level directory. +bootstrap_dir = os.path.dirname(os.path.abspath(__file__)) +# For the import below, have Python search in src/bootstrap first. +sys.path.insert(0, bootstrap_dir) +import bootstrap # noqa: E402 +import configure # noqa: E402 + +def serialize_and_parse(configure_args, bootstrap_args=None): + from io import StringIO + + if bootstrap_args is None: + bootstrap_args = bootstrap.FakeArgs() + + section_order, sections, targets = configure.parse_args(configure_args) + buffer = StringIO() + configure.write_config_toml(buffer, section_order, targets, sections) + build = bootstrap.RustBuild(config_toml=buffer.getvalue(), args=bootstrap_args) + + try: + import tomllib + # Verify this is actually valid TOML. + tomllib.loads(build.config_toml) + except ImportError: + print("warning: skipping TOML validation, need at least python 3.11", file=sys.stderr) + return build class VerifyTestCase(unittest.TestCase): @@ -77,58 +101,70 @@ def test_same_dates(self): class GenerateAndParseConfig(unittest.TestCase): """Test that we can serialize and deserialize a config.toml file""" - def serialize_and_parse(self, args): - from io import StringIO - - section_order, sections, targets = configure.parse_args(args) - buffer = StringIO() - configure.write_config_toml(buffer, section_order, targets, sections) - build = bootstrap.RustBuild() - build.config_toml = buffer.getvalue() - - try: - import tomllib - # Verify this is actually valid TOML. - tomllib.loads(build.config_toml) - except ImportError: - print("warning: skipping TOML validation, need at least python 3.11", file=sys.stderr) - return build - def test_no_args(self): - build = self.serialize_and_parse([]) + build = serialize_and_parse([]) self.assertEqual(build.get_toml("changelog-seen"), '2') - self.assertEqual(build.get_toml("profile"), 'user') + self.assertEqual(build.get_toml("profile"), 'dist') self.assertIsNone(build.get_toml("llvm.download-ci-llvm")) def test_set_section(self): - build = self.serialize_and_parse(["--set", "llvm.download-ci-llvm"]) + build = serialize_and_parse(["--set", "llvm.download-ci-llvm"]) self.assertEqual(build.get_toml("download-ci-llvm", section="llvm"), 'true') def test_set_target(self): - build = self.serialize_and_parse(["--set", "target.x86_64-unknown-linux-gnu.cc=gcc"]) + build = serialize_and_parse(["--set", "target.x86_64-unknown-linux-gnu.cc=gcc"]) self.assertEqual(build.get_toml("cc", section="target.x86_64-unknown-linux-gnu"), 'gcc') def test_set_top_level(self): - build = self.serialize_and_parse(["--set", "profile=compiler"]) + build = serialize_and_parse(["--set", "profile=compiler"]) self.assertEqual(build.get_toml("profile"), 'compiler') def test_set_codegen_backends(self): - build = self.serialize_and_parse(["--set", "rust.codegen-backends=cranelift"]) + build = serialize_and_parse(["--set", "rust.codegen-backends=cranelift"]) self.assertNotEqual(build.config_toml.find("codegen-backends = ['cranelift']"), -1) - build = self.serialize_and_parse(["--set", "rust.codegen-backends=cranelift,llvm"]) + build = serialize_and_parse(["--set", "rust.codegen-backends=cranelift,llvm"]) self.assertNotEqual(build.config_toml.find("codegen-backends = ['cranelift', 'llvm']"), -1) - build = self.serialize_and_parse(["--enable-full-tools"]) + build = serialize_and_parse(["--enable-full-tools"]) self.assertNotEqual(build.config_toml.find("codegen-backends = ['llvm']"), -1) -if __name__ == '__main__': - SUITE = unittest.TestSuite() - TEST_LOADER = unittest.TestLoader() - SUITE.addTest(doctest.DocTestSuite(bootstrap)) - SUITE.addTests([ - TEST_LOADER.loadTestsFromTestCase(VerifyTestCase), - TEST_LOADER.loadTestsFromTestCase(GenerateAndParseConfig), - TEST_LOADER.loadTestsFromTestCase(ProgramOutOfDate)]) - - RUNNER = unittest.TextTestRunner(stream=sys.stdout, verbosity=2) - result = RUNNER.run(SUITE) - sys.exit(0 if result.wasSuccessful() else 1) + +class BuildBootstrap(unittest.TestCase): + """Test that we generate the appropriate arguments when building bootstrap""" + + def build_args(self, configure_args=None, args=None, env=None): + if configure_args is None: + configure_args = [] + if args is None: + args = [] + if env is None: + env = {} + + env = env.copy() + env["PATH"] = os.environ["PATH"] + + parsed = bootstrap.parse_args(args) + build = serialize_and_parse(configure_args, parsed) + # Make these optional so that `python -m unittest` works when run manually. + build_dir = os.environ.get("BUILD_DIR") + if build_dir is not None: + build.build_dir = build_dir + build_platform = os.environ.get("BUILD_PLATFORM") + if build_platform is not None: + build.build = build_platform + return build.build_bootstrap_cmd(env), env + + def test_cargoflags(self): + args, _ = self.build_args(env={"CARGOFLAGS": "--timings"}) + self.assertTrue("--timings" in args) + + def test_warnings(self): + for toml_warnings in ['false', 'true', None]: + configure_args = [] + if toml_warnings is not None: + configure_args = ["--set", "rust.deny-warnings=" + toml_warnings] + + _, env = self.build_args(configure_args, args=["--warnings=warn"]) + self.assertFalse("-Dwarnings" in env["RUSTFLAGS"]) + + _, env = self.build_args(configure_args, args=["--warnings=deny"]) + self.assertTrue("-Dwarnings" in env["RUSTFLAGS"]) diff --git a/src/bootstrap/build.rs b/src/bootstrap/build.rs index cd1f418028c62..e0e32d3135354 100644 --- a/src/bootstrap/build.rs +++ b/src/bootstrap/build.rs @@ -3,5 +3,5 @@ use std::env; fn main() { let host = env::var("HOST").unwrap(); println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rustc-env=BUILD_TRIPLE={}", host); + println!("cargo:rustc-env=BUILD_TRIPLE={host}"); } diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index cf7c6596c0238..b366619285338 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -8,12 +8,12 @@ use std::fs::{self, File}; use std::hash::Hash; use std::io::{BufRead, BufReader}; use std::ops::Deref; -use std::path::{Component, Path, PathBuf}; +use std::path::{Path, PathBuf}; use std::process::Command; use std::time::{Duration, Instant}; use crate::cache::{Cache, Interned, INTERNER}; -use crate::config::{SplitDebuginfo, TargetSelection}; +use crate::config::{DryRun, SplitDebuginfo, TargetSelection}; use crate::doc; use crate::flags::{Color, Subcommand}; use crate::install; @@ -103,15 +103,55 @@ impl RunConfig<'_> { } /// Return a list of crate names selected by `run.paths`. + #[track_caller] pub fn cargo_crates_in_set(&self) -> Interned> { let mut crates = Vec::new(); for krate in &self.paths { let path = krate.assert_single_path(); - let crate_name = self.builder.crate_paths[&path.path]; + let Some(crate_name) = self.builder.crate_paths.get(&path.path) else { + panic!("missing crate for path {}", path.path.display()) + }; crates.push(crate_name.to_string()); } INTERNER.intern_list(crates) } + + /// Given an `alias` selected by the `Step` and the paths passed on the command line, + /// return a list of the crates that should be built. + /// + /// Normally, people will pass *just* `library` if they pass it. + /// But it's possible (although strange) to pass something like `library std core`. + /// Build all crates anyway, as if they hadn't passed the other args. + pub fn make_run_crates(&self, alias: Alias) -> Interned> { + let has_alias = + self.paths.iter().any(|set| set.assert_single_path().path.ends_with(alias.as_str())); + if !has_alias { + return self.cargo_crates_in_set(); + } + + let crates = match alias { + Alias::Library => self.builder.in_tree_crates("sysroot", Some(self.target)), + Alias::Compiler => self.builder.in_tree_crates("rustc-main", Some(self.target)), + }; + + let crate_names = crates.into_iter().map(|krate| krate.name.to_string()).collect(); + INTERNER.intern_list(crate_names) + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Alias { + Library, + Compiler, +} + +impl Alias { + fn as_str(self) -> &'static str { + match self { + Alias::Library => "library", + Alias::Compiler => "compiler", + } + } } /// A description of the crates in this set, suitable for passing to `builder.info`. @@ -147,29 +187,6 @@ pub struct TaskPath { pub kind: Option, } -impl TaskPath { - pub fn parse(path: impl Into) -> TaskPath { - let mut kind = None; - let mut path = path.into(); - - let mut components = path.components(); - if let Some(Component::Normal(os_str)) = components.next() { - if let Some(str) = os_str.to_str() { - if let Some((found_kind, found_prefix)) = str.split_once("::") { - if found_kind.is_empty() { - panic!("empty kind in task path {}", path.display()); - } - kind = Kind::parse(found_kind); - assert!(kind.is_some()); - path = Path::new(found_prefix).join(components.as_path()); - } - } - } - - TaskPath { path, kind } - } -} - impl Debug for TaskPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(kind) = &self.kind { @@ -213,7 +230,7 @@ impl PathSet { PathSet::Set(set) } - fn has(&self, needle: &Path, module: Option) -> bool { + fn has(&self, needle: &Path, module: Kind) -> bool { match self { PathSet::Set(set) => set.iter().any(|p| Self::check(p, needle, module)), PathSet::Suite(suite) => Self::check(suite, needle, module), @@ -221,9 +238,9 @@ impl PathSet { } // internal use only - fn check(p: &TaskPath, needle: &Path, module: Option) -> bool { - if let (Some(p_kind), Some(kind)) = (&p.kind, module) { - p.path.ends_with(needle) && *p_kind == kind + fn check(p: &TaskPath, needle: &Path, module: Kind) -> bool { + if let Some(p_kind) = &p.kind { + p.path.ends_with(needle) && *p_kind == module } else { p.path.ends_with(needle) } @@ -235,11 +252,7 @@ impl PathSet { /// This is used for `StepDescription::krate`, which passes all matching crates at once to /// `Step::make_run`, rather than calling it many times with a single crate. /// See `tests.rs` for examples. - fn intersection_removing_matches( - &self, - needles: &mut Vec<&Path>, - module: Option, - ) -> PathSet { + fn intersection_removing_matches(&self, needles: &mut Vec<&Path>, module: Kind) -> PathSet { let mut check = |p| { for (i, n) in needles.iter().enumerate() { let matched = Self::check(p, n, module); @@ -264,7 +277,7 @@ impl PathSet { /// A convenience wrapper for Steps which know they have no aliases and all their sets contain only a single path. /// - /// This can be used with [`ShouldRun::krate`], [`ShouldRun::path`], or [`ShouldRun::alias`]. + /// This can be used with [`ShouldRun::crate_or_deps`], [`ShouldRun::path`], or [`ShouldRun::alias`]. #[track_caller] pub fn assert_single_path(&self) -> &TaskPath { match self { @@ -304,15 +317,17 @@ impl StepDescription { } fn is_excluded(&self, builder: &Builder<'_>, pathset: &PathSet) -> bool { - if builder.config.exclude.iter().any(|e| pathset.has(&e.path, e.kind)) { - println!("Skipping {:?} because it is excluded", pathset); + if builder.config.skip.iter().any(|e| pathset.has(&e, builder.kind)) { + if !matches!(builder.config.dry_run, DryRun::SelfCheck) { + println!("Skipping {pathset:?} because it is excluded"); + } return true; } - if !builder.config.exclude.is_empty() { + if !builder.config.skip.is_empty() && !matches!(builder.config.dry_run, DryRun::SelfCheck) { builder.verbose(&format!( "{:?} not skipped for {:?} -- not in {:?}", - pathset, self.name, builder.config.exclude + pathset, self.name, builder.config.skip )); } false @@ -378,7 +393,7 @@ impl StepDescription { eprintln!( "note: if you are adding a new Step to bootstrap itself, make sure you register it with `describe!`" ); - crate::detail_exit(1); + crate::exit!(1); } } } @@ -427,25 +442,6 @@ impl<'a> ShouldRun<'a> { } } - /// Indicates it should run if the command-line selects the given crate or - /// any of its (local) dependencies. - /// - /// Compared to `krate`, this treats the dependencies as aliases for the - /// same job. Generally it is preferred to use `krate`, and treat each - /// individual path separately. For example `./x.py test src/liballoc` - /// (which uses `krate`) will test just `liballoc`. However, `./x.py check - /// src/liballoc` (which uses `all_krates`) will check all of `libtest`. - /// `all_krates` should probably be removed at some point. - pub fn all_krates(mut self, name: &str) -> Self { - let mut set = BTreeSet::new(); - for krate in self.builder.in_tree_crates(name, None) { - let path = krate.local_path(self.builder); - set.insert(TaskPath { path, kind: Some(self.kind) }); - } - self.paths.insert(PathSet::Set(set)); - self - } - /// Indicates it should run if the command-line selects the given crate or /// any of its (local) dependencies. /// @@ -458,6 +454,8 @@ impl<'a> ShouldRun<'a> { /// Indicates it should run if the command-line selects any of the given crates. /// /// `make_run` will be called a single time with all matching command-line paths. + /// + /// Prefer [`ShouldRun::crate_or_deps`] to this function where possible. pub(crate) fn crates(mut self, crates: Vec<&Crate>) -> Self { for krate in crates { let path = krate.local_path(self.builder); @@ -473,8 +471,7 @@ impl<'a> ShouldRun<'a> { // `compiler` and `library` folders respectively. assert!( self.kind == Kind::Setup || !self.builder.src.join(alias).exists(), - "use `builder.path()` for real paths: {}", - alias + "use `builder.path()` for real paths: {alias}" ); self.paths.insert(PathSet::Set( std::iter::once(TaskPath { path: alias.into(), kind: Some(self.kind) }).collect(), @@ -487,7 +484,15 @@ impl<'a> ShouldRun<'a> { self.paths(&[path]) } - // multiple aliases for the same job + /// Multiple aliases for the same job. + /// + /// This differs from [`path`] in that multiple calls to path will end up calling `make_run` + /// multiple times, whereas a single call to `paths` will only ever generate a single call to + /// `paths`. + /// + /// This is analogous to `all_krates`, although `all_krates` is gone now. Prefer [`path`] where possible. + /// + /// [`path`]: ShouldRun::path pub fn paths(mut self, paths: &[&str]) -> Self { static SUBMODULES_PATHS: OnceCell> = OnceCell::new(); @@ -568,7 +573,7 @@ impl<'a> ShouldRun<'a> { ) -> Vec { let mut sets = vec![]; for pathset in &self.paths { - let subset = pathset.intersection_removing_matches(paths, Some(kind)); + let subset = pathset.intersection_removing_matches(paths, kind); if subset != PathSet::empty() { sets.push(subset); } @@ -577,7 +582,7 @@ impl<'a> ShouldRun<'a> { } } -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, ValueEnum)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum Kind { #[clap(alias = "b")] Build, @@ -641,12 +646,19 @@ impl Kind { } } - pub fn test_description(&self) -> &'static str { + pub fn description(&self) -> String { match self { Kind::Test => "Testing", Kind::Bench => "Benchmarking", - _ => panic!("not a test command: {}!", self.as_str()), + Kind::Doc => "Documenting", + Kind::Run => "Running", + Kind::Suggest => "Suggesting", + _ => { + let title_letter = self.as_str()[0..1].to_ascii_uppercase(); + return format!("{title_letter}{}ing", &self.as_str()[1..]); + } } + .to_owned() } } @@ -689,7 +701,9 @@ impl<'a> Builder<'a> { tool::Miri, tool::CargoMiri, llvm::Lld, - llvm::CrtBeginEnd + llvm::CrtBeginEnd, + tool::RustdocGUITest, + tool::OptimizedDist ), Kind::Check | Kind::Clippy | Kind::Fix => describe!( check::Std, @@ -701,8 +715,8 @@ impl<'a> Builder<'a> { check::CargoMiri, check::MiroptTestTools, check::Rls, - check::RustAnalyzer, check::Rustfmt, + check::RustAnalyzer, check::Bootstrap ), Kind::Test => describe!( @@ -711,6 +725,7 @@ impl<'a> Builder<'a> { test::Tidy, test::Ui, test::RunPassValgrind, + test::RunCoverage, test::MirOpt, test::Codegen, test::CodegenUnits, @@ -719,6 +734,7 @@ impl<'a> Builder<'a> { test::Debuginfo, test::UiFullDeps, test::Rustdoc, + test::RunCoverageRustdoc, test::Pretty, test::Crate, test::CrateLibrustc, @@ -787,6 +803,7 @@ impl<'a> Builder<'a> { doc::EditionGuide, doc::StyleGuide, doc::Tidy, + doc::Bootstrap, ), Kind::Dist => describe!( dist::Docs, @@ -919,21 +936,6 @@ impl<'a> Builder<'a> { Self::new_internal(build, kind, paths.to_owned()) } - /// Creates a new standalone builder for use outside of the normal process - pub fn new_standalone( - build: &mut Build, - kind: Kind, - paths: Vec, - stage: Option, - ) -> Builder<'_> { - // FIXME: don't mutate `build` - if let Some(stage) = stage { - build.config.stage = stage; - } - - Self::new_internal(build, kind, paths.to_owned()) - } - pub fn execute_cli(&self) { self.run_step_descriptions(&Builder::get_step_descriptions(self.kind), &self.paths); } @@ -992,7 +994,7 @@ impl<'a> Builder<'a> { } pub fn sysroot(&self, compiler: Compiler) -> Interned { - self.ensure(compile::Sysroot { compiler }) + self.ensure(compile::Sysroot::new(compiler)) } /// Returns the libdir where the standard library and other artifacts are @@ -1229,7 +1231,7 @@ impl<'a> Builder<'a> { assert_eq!(target, compiler.host); } - if self.config.rust_optimize { + if self.config.rust_optimize.is_release() { // FIXME: cargo bench/install do not accept `--release` if cmd != "bench" && cmd != "install" { cargo.arg("--release"); @@ -1278,14 +1280,14 @@ impl<'a> Builder<'a> { out_dir.join(target.triple).join("doc") } } - _ => panic!("doc mode {:?} not expected", mode), + _ => panic!("doc mode {mode:?} not expected"), }; let rustdoc = self.rustdoc(compiler); self.clear_if_dirty(&my_out, &rustdoc); } let profile_var = |name: &str| { - let profile = if self.config.rust_optimize { "RELEASE" } else { "DEV" }; + let profile = if self.config.rust_optimize.is_release() { "RELEASE" } else { "DEV" }; format!("CARGO_PROFILE_{}_{}", profile, name) }; @@ -1355,7 +1357,7 @@ impl<'a> Builder<'a> { "error: `x.py clippy` requires a host `rustc` toolchain with the `clippy` component" ); eprintln!("help: try `rustup component add clippy`"); - crate::detail_exit(1); + crate::exit!(1); }); if !t!(std::str::from_utf8(&output.stdout)).contains("nightly") { rustflags.arg("--cfg=bootstrap"); @@ -1624,6 +1626,7 @@ impl<'a> Builder<'a> { // fun to pass a flag to a tool to pass a flag to pass a flag to a tool // to change a flag in a binary? if self.config.rpath_enabled(target) && util::use_host_linker(target) { + let libdir = self.sysroot_libdir_relative(compiler).to_str().unwrap(); let rpath = if target.contains("apple") { // Note that we need to take one extra step on macOS to also pass // `-Wl,-instal_name,@rpath/...` to get things to work right. To @@ -1631,15 +1634,15 @@ impl<'a> Builder<'a> { // so. Note that this is definitely a hack, and we should likely // flesh out rpath support more fully in the future. rustflags.arg("-Zosx-rpath-install-name"); - Some("-Wl,-rpath,@loader_path/../lib") - } else if !target.contains("windows") { + Some(format!("-Wl,-rpath,@loader_path/../{libdir}")) + } else if !target.contains("windows") && !target.contains("aix") { rustflags.arg("-Clink-args=-Wl,-z,origin"); - Some("-Wl,-rpath,$ORIGIN/../lib") + Some(format!("-Wl,-rpath,$ORIGIN/../{libdir}")) } else { None }; if let Some(rpath) = rpath { - rustflags.arg(&format!("-Clink-args={}", rpath)); + rustflags.arg(&format!("-Clink-args={rpath}")); } } @@ -1653,7 +1656,7 @@ impl<'a> Builder<'a> { if let Some(target_linker) = self.linker(target) { let target = crate::envify(&target.triple); - cargo.env(&format!("CARGO_TARGET_{}_LINKER", target), target_linker); + cargo.env(&format!("CARGO_TARGET_{target}_LINKER"), target_linker); } if self.is_fuse_ld_lld(target) { rustflags.arg("-Clink-args=-fuse-ld=lld"); @@ -1674,6 +1677,13 @@ impl<'a> Builder<'a> { } }; cargo.env(profile_var("DEBUG"), debuginfo_level.to_string()); + if let Some(opt_level) = &self.config.rust_optimize.get_opt_level() { + cargo.env(profile_var("OPT_LEVEL"), opt_level); + } + if !self.config.dry_run() && self.cc.borrow()[&target].args().iter().any(|arg| arg == "-gz") + { + rustflags.arg("-Clink-arg=-gz"); + } cargo.env( profile_var("DEBUG_ASSERTIONS"), if mode == Mode::Std { @@ -1783,7 +1793,10 @@ impl<'a> Builder<'a> { cargo.env("RUSTC_TLS_MODEL_INITIAL_EXEC", "1"); } - if self.config.incremental { + // Ignore incremental modes except for stage0, since we're + // not guaranteeing correctness across builds if the compiler + // is changing under your feet. + if self.config.incremental && compiler.stage == 0 { cargo.env("CARGO_INCREMENTAL", "1"); } else { // Don't rely on any default setting for incr. comp. in Cargo @@ -1879,24 +1892,24 @@ impl<'a> Builder<'a> { }; let triple_underscored = target.triple.replace("-", "_"); let cc = ccacheify(&self.cc(target)); - cargo.env(format!("CC_{}", triple_underscored), &cc); + cargo.env(format!("CC_{triple_underscored}"), &cc); let cflags = self.cflags(target, GitRepo::Rustc, CLang::C).join(" "); - cargo.env(format!("CFLAGS_{}", triple_underscored), &cflags); + cargo.env(format!("CFLAGS_{triple_underscored}"), &cflags); if let Some(ar) = self.ar(target) { let ranlib = format!("{} s", ar.display()); cargo - .env(format!("AR_{}", triple_underscored), ar) - .env(format!("RANLIB_{}", triple_underscored), ranlib); + .env(format!("AR_{triple_underscored}"), ar) + .env(format!("RANLIB_{triple_underscored}"), ranlib); } if let Ok(cxx) = self.cxx(target) { let cxx = ccacheify(&cxx); let cxxflags = self.cflags(target, GitRepo::Rustc, CLang::Cxx).join(" "); cargo - .env(format!("CXX_{}", triple_underscored), &cxx) - .env(format!("CXXFLAGS_{}", triple_underscored), cxxflags); + .env(format!("CXX_{triple_underscored}"), &cxx) + .env(format!("CXXFLAGS_{triple_underscored}"), cxxflags); } } @@ -1915,10 +1928,10 @@ impl<'a> Builder<'a> { } // For `cargo doc` invocations, make rustdoc print the Rust version into the docs - // This replaces spaces with newlines because RUSTDOCFLAGS does not + // This replaces spaces with tabs because RUSTDOCFLAGS does not // support arguments with regular spaces. Hopefully someday Cargo will // have space support. - let rust_version = self.rust_version().replace(' ', "\n"); + let rust_version = self.rust_version().replace(' ', "\t"); rustdocflags.arg("--crate-version").arg(&rust_version); // Environment variables *required* throughout the build @@ -2009,7 +2022,7 @@ impl<'a> Builder<'a> { if let Some(limit) = limit { if stage == 0 || self.config.default_codegen_backend().unwrap_or_default() == "llvm" { - rustflags.arg(&format!("-Cllvm-args=-import-instr-limit={}", limit)); + rustflags.arg(&format!("-Cllvm-args=-import-instr-limit={limit}")); } } } @@ -2017,7 +2030,7 @@ impl<'a> Builder<'a> { if matches!(mode, Mode::Std) { if let Some(mir_opt_level) = self.config.rust_validate_mir_opts { rustflags.arg("-Zvalidate-mir"); - rustflags.arg(&format!("-Zmir-opt-level={}", mir_opt_level)); + rustflags.arg(&format!("-Zmir-opt-level={mir_opt_level}")); } // Always enable inlining MIR when building the standard library. // Without this flag, MIR inlining is disabled when incremental compilation is enabled. @@ -2027,6 +2040,13 @@ impl<'a> Builder<'a> { rustflags.arg("-Zinline-mir"); } + // set rustc args passed from command line + let rustc_args = + self.config.cmd.rustc_args().iter().map(|s| s.to_string()).collect::>(); + if !rustc_args.is_empty() { + cargo.env("RUSTFLAGS", &rustc_args.join(" ")); + } + Cargo { command: cargo, rustflags, rustdocflags, allow_features } } @@ -2042,9 +2062,9 @@ impl<'a> Builder<'a> { continue; } let mut out = String::new(); - out += &format!("\n\nCycle in build detected when adding {:?}\n", step); + out += &format!("\n\nCycle in build detected when adding {step:?}\n"); for el in stack.iter().rev() { - out += &format!("\t{:?}\n", el); + out += &format!("\t{el:?}\n"); } panic!("{}", out); } @@ -2071,7 +2091,7 @@ impl<'a> Builder<'a> { }; if self.config.print_step_timings && !self.config.dry_run() { - let step_string = format!("{:?}", step); + let step_string = format!("{step:?}"); let brace_index = step_string.find("{").unwrap_or(0); let type_string = type_name::(); println!( @@ -2107,7 +2127,7 @@ impl<'a> Builder<'a> { let desc = StepDescription::from::(kind); let should_run = (desc.should_run)(ShouldRun::new(self, desc.kind)); - // Avoid running steps contained in --exclude + // Avoid running steps contained in --skip for pathset in &should_run.paths { if desc.is_excluded(self, pathset) { return None; @@ -2124,7 +2144,7 @@ impl<'a> Builder<'a> { let should_run = (desc.should_run)(ShouldRun::new(self, desc.kind)); for path in &self.paths { - if should_run.paths.iter().any(|s| s.has(path, Some(desc.kind))) + if should_run.paths.iter().any(|s| s.has(path, desc.kind)) && !desc.is_excluded( self, &PathSet::Suite(TaskPath { path: path.clone(), kind: Some(desc.kind) }), @@ -2151,7 +2171,7 @@ impl<'a> Builder<'a> { let path = path.as_ref(); self.info(&format!("Opening doc {}", path.display())); if let Err(err) = opener::open(path) { - self.info(&format!("{}\n", err)); + self.info(&format!("{err}\n")); } } } diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs index edca8fe9b13af..43b4a34fe5b62 100644 --- a/src/bootstrap/builder/tests.rs +++ b/src/bootstrap/builder/tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::config::{Config, DryRun, TargetSelection}; +use crate::doc::DocumentationFormat; use std::thread; fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config { @@ -66,6 +67,20 @@ macro_rules! std { }; } +macro_rules! doc_std { + ($host:ident => $target:ident, stage = $stage:literal) => {{ + let config = configure("doc", &["A"], &["A"]); + let build = Build::new(config); + let builder = Builder::new(&build); + doc::Std::new( + $stage, + TargetSelection::from_user(stringify!($target)), + &builder, + DocumentationFormat::HTML, + ) + }}; +} + macro_rules! rustc { ($host:ident => $target:ident, stage = $stage:literal) => { compile::Rustc::new( @@ -90,23 +105,21 @@ fn test_invalid() { #[test] fn test_intersection() { - let set = PathSet::Set( - ["library/core", "library/alloc", "library/std"].into_iter().map(TaskPath::parse).collect(), - ); + let set = |paths: &[&str]| { + PathSet::Set(paths.into_iter().map(|p| TaskPath { path: p.into(), kind: None }).collect()) + }; + let library_set = set(&["library/core", "library/alloc", "library/std"]); let mut command_paths = vec![Path::new("library/core"), Path::new("library/alloc"), Path::new("library/stdarch")]; - let subset = set.intersection_removing_matches(&mut command_paths, None); - assert_eq!( - subset, - PathSet::Set(["library/core", "library/alloc"].into_iter().map(TaskPath::parse).collect()) - ); + let subset = library_set.intersection_removing_matches(&mut command_paths, Kind::Build); + assert_eq!(subset, set(&["library/core", "library/alloc"]),); assert_eq!(command_paths, vec![Path::new("library/stdarch")]); } #[test] fn test_exclude() { let mut config = configure("test", &["A"], &["A"]); - config.exclude = vec![TaskPath::parse("src/tools/tidy")]; + config.skip = vec!["src/tools/tidy".into()]; let cache = run_build(&[], config); // Ensure we have really excluded tidy @@ -118,21 +131,16 @@ fn test_exclude() { #[test] fn test_exclude_kind() { - let path = PathBuf::from("src/tools/cargotest"); - let exclude = TaskPath::parse("test::src/tools/cargotest"); - assert_eq!(exclude, TaskPath { kind: Some(Kind::Test), path: path.clone() }); + let path = PathBuf::from("compiler/rustc_data_structures"); let mut config = configure("test", &["A"], &["A"]); - // Ensure our test is valid, and `test::Cargotest` would be run without the exclude. - assert!(run_build(&[path.clone()], config.clone()).contains::()); - // Ensure tests for cargotest are skipped. - config.exclude = vec![exclude.clone()]; - assert!(!run_build(&[path.clone()], config).contains::()); - - // Ensure builds for cargotest are not skipped. - let mut config = configure("build", &["A"], &["A"]); - config.exclude = vec![exclude]; - assert!(run_build(&[path], config).contains::()); + // Ensure our test is valid, and `test::Rustc` would be run without the exclude. + assert!(run_build(&[], config.clone()).contains::()); + // Ensure tests for rustc are skipped. + config.skip = vec![path.clone()]; + assert!(!run_build(&[], config.clone()).contains::()); + // Ensure builds for rustc are not skipped. + assert!(run_build(&[], config).contains::()); } /// Ensure that if someone passes both a single crate and `library`, all library crates get built. @@ -144,6 +152,9 @@ fn alias_and_path_for_library() { first(cache.all::()), &[std!(A => A, stage = 0), std!(A => A, stage = 1)] ); + + let mut cache = run_build(&["library".into(), "core".into()], configure("doc", &["A"], &["A"])); + assert_eq!(first(cache.all::()), &[doc_std!(A => A, stage = 0)]); } #[test] @@ -576,6 +587,7 @@ mod dist { run: None, only_modified: false, skip: vec![], + extra_checks: None, }; let build = Build::new(config); @@ -647,6 +659,7 @@ mod dist { pass: None, run: None, only_modified: false, + extra_checks: None, }; // Make sure rustfmt binary not being found isn't an error. config.channel = "beta".to_string(); diff --git a/src/bootstrap/cache.rs b/src/bootstrap/cache.rs index 5376c4ec9c325..53e4ff03431f5 100644 --- a/src/bootstrap/cache.rs +++ b/src/bootstrap/cache.rs @@ -75,7 +75,7 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s: &U = &*self; - f.write_fmt(format_args!("{:?}", s)) + f.write_fmt(format_args!("{s:?}")) } } @@ -236,7 +236,7 @@ impl Cache { .or_insert_with(|| Box::new(HashMap::::new())) .downcast_mut::>() .expect("invalid type mapped"); - assert!(!stepcache.contains_key(&step), "processing {:?} a second time", step); + assert!(!stepcache.contains_key(&step), "processing {step:?} a second time"); stepcache.insert(step, value); } diff --git a/src/bootstrap/cc_detect.rs b/src/bootstrap/cc_detect.rs index 65c882fb801e5..2496c2a9db520 100644 --- a/src/bootstrap/cc_detect.rs +++ b/src/bootstrap/cc_detect.rs @@ -69,6 +69,8 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { .opt_level(2) .warnings(false) .debug(false) + // Compress debuginfo + .flag_if_supported("-gz") .target(&target.triple) .host(&build.build.triple); match build.crt_static(target) { @@ -87,7 +89,7 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { cfg } -pub fn find(build: &mut Build) { +pub fn find(build: &Build) { // For all targets we're going to need a C compiler for building some shims // and such as well as for being a linker for Rust code. let targets = build @@ -98,60 +100,64 @@ pub fn find(build: &mut Build) { .chain(iter::once(build.build)) .collect::>(); for target in targets.into_iter() { - let mut cfg = new_cc_build(build, target); - let config = build.config.target_config.get(&target); - if let Some(cc) = config.and_then(|c| c.cc.as_ref()) { - cfg.compiler(cc); - } else { - set_compiler(&mut cfg, Language::C, target, config, build); - } + find_target(build, target); + } +} - let compiler = cfg.get_compiler(); - let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) { - ar - } else { - cc2ar(compiler.path(), target) - }; +pub fn find_target(build: &Build, target: TargetSelection) { + let mut cfg = new_cc_build(build, target); + let config = build.config.target_config.get(&target); + if let Some(cc) = config.and_then(|c| c.cc.as_ref()) { + cfg.compiler(cc); + } else { + set_compiler(&mut cfg, Language::C, target, config, build); + } - build.cc.insert(target, compiler.clone()); - let cflags = build.cflags(target, GitRepo::Rustc, CLang::C); + let compiler = cfg.get_compiler(); + let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) { + ar + } else { + cc2ar(compiler.path(), target) + }; - // If we use llvm-libunwind, we will need a C++ compiler as well for all targets - // We'll need one anyways if the target triple is also a host triple - let mut cfg = new_cc_build(build, target); - cfg.cpp(true); - let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) { - cfg.compiler(cxx); - true - } else if build.hosts.contains(&target) || build.build == target { - set_compiler(&mut cfg, Language::CPlusPlus, target, config, build); - true - } else { - // Use an auto-detected compiler (or one configured via `CXX_target_triple` env vars). - cfg.try_get_compiler().is_ok() - }; + build.cc.borrow_mut().insert(target, compiler.clone()); + let cflags = build.cflags(target, GitRepo::Rustc, CLang::C); - // for VxWorks, record CXX compiler which will be used in lib.rs:linker() - if cxx_configured || target.contains("vxworks") { - let compiler = cfg.get_compiler(); - build.cxx.insert(target, compiler); - } + // If we use llvm-libunwind, we will need a C++ compiler as well for all targets + // We'll need one anyways if the target triple is also a host triple + let mut cfg = new_cc_build(build, target); + cfg.cpp(true); + let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) { + cfg.compiler(cxx); + true + } else if build.hosts.contains(&target) || build.build == target { + set_compiler(&mut cfg, Language::CPlusPlus, target, config, build); + true + } else { + // Use an auto-detected compiler (or one configured via `CXX_target_triple` env vars). + cfg.try_get_compiler().is_ok() + }; - build.verbose(&format!("CC_{} = {:?}", &target.triple, build.cc(target))); - build.verbose(&format!("CFLAGS_{} = {:?}", &target.triple, cflags)); - if let Ok(cxx) = build.cxx(target) { - let cxxflags = build.cflags(target, GitRepo::Rustc, CLang::Cxx); - build.verbose(&format!("CXX_{} = {:?}", &target.triple, cxx)); - build.verbose(&format!("CXXFLAGS_{} = {:?}", &target.triple, cxxflags)); - } - if let Some(ar) = ar { - build.verbose(&format!("AR_{} = {:?}", &target.triple, ar)); - build.ar.insert(target, ar); - } + // for VxWorks, record CXX compiler which will be used in lib.rs:linker() + if cxx_configured || target.contains("vxworks") { + let compiler = cfg.get_compiler(); + build.cxx.borrow_mut().insert(target, compiler); + } - if let Some(ranlib) = config.and_then(|c| c.ranlib.clone()) { - build.ranlib.insert(target, ranlib); - } + build.verbose(&format!("CC_{} = {:?}", &target.triple, build.cc(target))); + build.verbose(&format!("CFLAGS_{} = {:?}", &target.triple, cflags)); + if let Ok(cxx) = build.cxx(target) { + let cxxflags = build.cflags(target, GitRepo::Rustc, CLang::Cxx); + build.verbose(&format!("CXX_{} = {:?}", &target.triple, cxx)); + build.verbose(&format!("CXXFLAGS_{} = {:?}", &target.triple, cxxflags)); + } + if let Some(ar) = ar { + build.verbose(&format!("AR_{} = {:?}", &target.triple, ar)); + build.ar.borrow_mut().insert(target, ar); + } + + if let Some(ranlib) = config.and_then(|c| c.ranlib.clone()) { + build.ranlib.borrow_mut().insert(target, ranlib); } } @@ -190,7 +196,7 @@ fn set_compiler( '0'..='6' => {} _ => return, } - let alternative = format!("e{}", gnu_compiler); + let alternative = format!("e{gnu_compiler}"); if Command::new(&alternative).output().is_ok() { cfg.compiler(alternative); } diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs index b11be96cefe62..bdefc41c9c7cd 100644 --- a/src/bootstrap/check.rs +++ b/src/bootstrap/check.rs @@ -1,6 +1,6 @@ //! Implementation of compiling the compiler and standard library, in "check"-based modes. -use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step}; +use crate::builder::{crate_description, Alias, Builder, Kind, RunConfig, ShouldRun, Step}; use crate::cache::Interned; use crate::compile::{add_to_sysroot, run_cargo, rustc_cargo, rustc_cargo_env, std_cargo}; use crate::config::TargetSelection; @@ -12,6 +12,12 @@ use std::path::{Path, PathBuf}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Std { pub target: TargetSelection, + /// Whether to build only a subset of crates. + /// + /// This shouldn't be used from other steps; see the comment on [`compile::Rustc`]. + /// + /// [`compile::Rustc`]: crate::compile::Rustc + crates: Interned>, } /// Returns args for the subcommand itself (not for cargo) @@ -66,16 +72,23 @@ fn cargo_subcommand(kind: Kind) -> &'static str { } } +impl Std { + pub fn new(target: TargetSelection) -> Self { + Self { target, crates: INTERNER.intern_list(vec![]) } + } +} + impl Step for Std { type Output = (); const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.all_krates("sysroot").path("library") + run.crate_or_deps("sysroot").path("library") } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Std { target: run.target }); + let crates = run.make_run_crates(Alias::Library); + run.builder.ensure(Std { target: run.target, crates }); } fn run(self, builder: &Builder<'_>) { @@ -97,7 +110,14 @@ impl Step for Std { cargo.arg("--lib"); } - let _guard = builder.msg_check("library artifacts", target); + for krate in &*self.crates { + cargo.arg("-p").arg(krate); + } + + let _guard = builder.msg_check( + format_args!("library artifacts{}", crate_description(&self.crates)), + target, + ); run_cargo( builder, cargo, @@ -115,9 +135,11 @@ impl Step for Std { let hostdir = builder.sysroot_libdir(compiler, compiler.host); add_to_sysroot(&builder, &libdir, &hostdir, &libstd_stamp(builder, compiler, target)); } + drop(_guard); // don't run on std twice with x.py clippy - if builder.kind == Kind::Clippy { + // don't check test dependencies if we haven't built libtest + if builder.kind == Kind::Clippy || !self.crates.iter().any(|krate| krate == "test") { return; } @@ -147,8 +169,8 @@ impl Step for Std { // Explicitly pass -p for all dependencies krates -- this will force cargo // to also check the tests/benches/examples for these crates, rather // than just the leaf crate. - for krate in builder.in_tree_crates("test", Some(target)) { - cargo.arg("-p").arg(krate.name); + for krate in &*self.crates { + cargo.arg("-p").arg(krate); } let _guard = builder.msg_check("library test/bench/example targets", target); @@ -167,6 +189,23 @@ impl Step for Std { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Rustc { pub target: TargetSelection, + /// Whether to build only a subset of crates. + /// + /// This shouldn't be used from other steps; see the comment on [`compile::Rustc`]. + /// + /// [`compile::Rustc`]: crate::compile::Rustc + crates: Interned>, +} + +impl Rustc { + pub fn new(target: TargetSelection, builder: &Builder<'_>) -> Self { + let crates = builder + .in_tree_crates("rustc-main", Some(target)) + .into_iter() + .map(|krate| krate.name.to_string()) + .collect(); + Self { target, crates: INTERNER.intern_list(crates) } + } } impl Step for Rustc { @@ -175,11 +214,12 @@ impl Step for Rustc { const DEFAULT: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.all_krates("rustc-main").path("compiler") + run.crate_or_deps("rustc-main").path("compiler") } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Rustc { target: run.target }); + let crates = run.make_run_crates(Alias::Compiler); + run.builder.ensure(Rustc { target: run.target, crates }); } /// Builds the compiler. @@ -200,7 +240,7 @@ impl Step for Rustc { builder.ensure(crate::compile::Std::new(compiler, compiler.host)); builder.ensure(crate::compile::Std::new(compiler, target)); } else { - builder.ensure(Std { target }); + builder.ensure(Std::new(target)); } let mut cargo = builder.cargo( @@ -218,14 +258,17 @@ impl Step for Rustc { cargo.arg("--all-targets"); } - // Explicitly pass -p for all compiler krates -- this will force cargo + // Explicitly pass -p for all compiler crates -- this will force cargo // to also check the tests/benches/examples for these crates, rather // than just the leaf crate. - for krate in builder.in_tree_crates("rustc-main", Some(target)) { - cargo.arg("-p").arg(krate.name); + for krate in &*self.crates { + cargo.arg("-p").arg(krate); } - let _guard = builder.msg_check("compiler artifacts", target); + let _guard = builder.msg_check( + format_args!("compiler artifacts{}", crate_description(&self.crates)), + target, + ); run_cargo( builder, cargo, @@ -264,11 +307,17 @@ impl Step for CodegenBackend { } fn run(self, builder: &Builder<'_>) { + // FIXME: remove once https://github.com/rust-lang/rust/issues/112393 is resolved + if builder.build.config.vendor && &self.backend == "gcc" { + println!("Skipping checking of `rustc_codegen_gcc` with vendoring enabled."); + return; + } + let compiler = builder.compiler(builder.top_stage, builder.config.build); let target = self.target; let backend = self.backend; - builder.ensure(Rustc { target }); + builder.ensure(Rustc::new(target, builder)); let mut cargo = builder.cargo( compiler, @@ -279,7 +328,7 @@ impl Step for CodegenBackend { ); cargo .arg("--manifest-path") - .arg(builder.src.join(format!("compiler/rustc_codegen_{}/Cargo.toml", backend))); + .arg(builder.src.join(format!("compiler/rustc_codegen_{backend}/Cargo.toml"))); rustc_cargo_env(builder, &mut cargo, target, compiler.stage); let _guard = builder.msg_check(&backend, target); @@ -304,7 +353,7 @@ pub struct RustAnalyzer { impl Step for RustAnalyzer { type Output = (); const ONLY_HOSTS: bool = true; - const DEFAULT: bool = true; + const DEFAULT: bool = false; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { run.path("src/tools/rust-analyzer") @@ -318,7 +367,7 @@ impl Step for RustAnalyzer { let compiler = builder.compiler(builder.top_stage, builder.config.build); let target = self.target; - builder.ensure(Std { target }); + builder.ensure(Std::new(target)); let mut cargo = prepare_tool_cargo( builder, @@ -386,7 +435,7 @@ macro_rules! tool_check_step { let compiler = builder.compiler(builder.top_stage, builder.config.build); let target = self.target; - builder.ensure(Rustc { target }); + builder.ensure(Rustc::new(target, builder)); let mut cargo = prepare_tool_cargo( builder, @@ -482,5 +531,5 @@ fn codegen_backend_stamp( ) -> PathBuf { builder .cargo_out(compiler, Mode::Codegen, target) - .join(format!(".librustc_codegen_{}-check.stamp", backend)) + .join(format!(".librustc_codegen_{backend}-check.stamp")) } diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs index 0d9fd56b03814..7389816b44c78 100644 --- a/src/bootstrap/clean.rs +++ b/src/bootstrap/clean.rs @@ -26,8 +26,15 @@ impl Step for CleanAll { } fn run(self, builder: &Builder<'_>) -> Self::Output { - let Subcommand::Clean { all, .. } = builder.config.cmd else { unreachable!("wrong subcommand?") }; - clean_default(builder.build, all) + let Subcommand::Clean { all, stage } = builder.config.cmd else { + unreachable!("wrong subcommand?") + }; + + if all && stage.is_some() { + panic!("--all and --stage can't be used at the same time for `x clean`"); + } + + clean(builder.build, all, stage) } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -84,31 +91,70 @@ clean_crate_tree! { Std, Mode::Std, "sysroot"; } -fn clean_default(build: &Build, all: bool) { +fn clean(build: &Build, all: bool, stage: Option) { + if build.config.dry_run() { + return; + } + rm_rf("tmp".as_ref()); + // Clean the entire build directory if all { rm_rf(&build.out); - } else { - rm_rf(&build.out.join("tmp")); - rm_rf(&build.out.join("dist")); - rm_rf(&build.out.join("bootstrap")); - rm_rf(&build.out.join("rustfmt.stamp")); - - for host in &build.hosts { - let entries = match build.out.join(host.triple).read_dir() { - Ok(iter) => iter, - Err(_) => continue, - }; - - for entry in entries { - let entry = t!(entry); - if entry.file_name().to_str() == Some("llvm") { - continue; - } - let path = t!(entry.path().canonicalize()); - rm_rf(&path); + return; + } + + // Clean the target stage artifacts + if let Some(stage) = stage { + clean_specific_stage(build, stage); + return; + } + + // Follow the default behaviour + clean_default(build); +} + +fn clean_specific_stage(build: &Build, stage: u32) { + for host in &build.hosts { + let entries = match build.out.join(host.triple).read_dir() { + Ok(iter) => iter, + Err(_) => continue, + }; + + for entry in entries { + let entry = t!(entry); + let stage_prefix = format!("stage{}", stage); + + // if current entry is not related with the target stage, continue + if !entry.file_name().to_str().unwrap_or("").contains(&stage_prefix) { + continue; + } + + let path = t!(entry.path().canonicalize()); + rm_rf(&path); + } + } +} + +fn clean_default(build: &Build) { + rm_rf(&build.out.join("tmp")); + rm_rf(&build.out.join("dist")); + rm_rf(&build.out.join("bootstrap")); + rm_rf(&build.out.join("rustfmt.stamp")); + + for host in &build.hosts { + let entries = match build.out.join(host.triple).read_dir() { + Ok(iter) => iter, + Err(_) => continue, + }; + + for entry in entries { + let entry = t!(entry); + if entry.file_name().to_str() == Some("llvm") { + continue; } + let path = t!(entry.path().canonicalize()); + rm_rf(&path); } } } diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index 33addb90da372..392618528751b 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -23,7 +23,7 @@ use crate::builder::crate_description; use crate::builder::Cargo; use crate::builder::{Builder, Kind, PathSet, RunConfig, ShouldRun, Step, TaskPath}; use crate::cache::{Interned, INTERNER}; -use crate::config::{LlvmLibunwind, RustcLto, TargetSelection}; +use crate::config::{DebuginfoLevel, LlvmLibunwind, RustcLto, TargetSelection}; use crate::dist; use crate::llvm; use crate::tool::SourceType; @@ -31,6 +31,7 @@ use crate::util::get_clang_cl_resource_dir; use crate::util::{exe, is_debug_info, is_dylib, output, symlink_dir, t, up_to_date}; use crate::LLVM_TOOLS; use crate::{CLang, Compiler, DependencyType, GitRepo, Mode}; +use filetime::FileTime; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Std { @@ -40,11 +41,18 @@ pub struct Std { /// /// This shouldn't be used from other steps; see the comment on [`Rustc`]. crates: Interned>, + /// When using download-rustc, we need to use a new build of `std` for running unit tests of Std itself, + /// but we need to use the downloaded copy of std for linking to rustdoc. Allow this to be overriden by `builder.ensure` from other steps. + force_recompile: bool, } impl Std { pub fn new(compiler: Compiler, target: TargetSelection) -> Self { - Self { target, compiler, crates: Default::default() } + Self { target, compiler, crates: Default::default(), force_recompile: false } + } + + pub fn force_recompile(compiler: Compiler, target: TargetSelection) -> Self { + Self { target, compiler, crates: Default::default(), force_recompile: true } } } @@ -62,16 +70,16 @@ impl Step for Std { } fn make_run(run: RunConfig<'_>) { - // Normally, people will pass *just* library if they pass it. - // But it's possible (although strange) to pass something like `library std core`. - // Build all crates anyway, as if they hadn't passed the other args. - let has_library = + // If the paths include "library", build the entire standard library. + let has_alias = run.paths.iter().any(|set| set.assert_single_path().path.ends_with("library")); - let crates = if has_library { Default::default() } else { run.cargo_crates_in_set() }; + let crates = if has_alias { Default::default() } else { run.cargo_crates_in_set() }; + run.builder.ensure(Std { compiler: run.builder.compiler(run.builder.top_stage, run.build_triple()), target: run.target, crates, + force_recompile: false, }); } @@ -84,11 +92,20 @@ impl Step for Std { let target = self.target; let compiler = self.compiler; - // When using `download-rustc`, we already have artifacts for the host available - // (they were copied in `impl Step for Sysroot`). Don't recompile them. - // NOTE: the ABI of the beta compiler is different from the ABI of the downloaded compiler, - // so its artifacts can't be reused. - if builder.download_rustc() && compiler.stage != 0 && target == builder.build.build { + // When using `download-rustc`, we already have artifacts for the host available. Don't + // recompile them. + if builder.download_rustc() && target == builder.build.build + // NOTE: the beta compiler may generate different artifacts than the downloaded compiler, so + // its artifacts can't be reused. + && compiler.stage != 0 + // This check is specific to testing std itself; see `test::Std` for more details. + && !self.force_recompile + { + cp_rustc_component_to_ci_sysroot( + builder, + compiler, + builder.config.ci_rust_std_contents(), + ); return; } @@ -96,6 +113,10 @@ impl Step for Std { || builder.config.keep_stage_std.contains(&compiler.stage) { builder.info("Warning: Using a potentially old libstd. This may not behave well."); + + copy_third_party_objects(builder, &compiler, target); + copy_self_contained_objects(builder, &compiler, target); + builder.ensure(StdLink::from_std(self, compiler)); return; } @@ -143,6 +164,11 @@ impl Step for Std { cargo.arg("-p").arg(krate); } + // See src/bootstrap/synthetic_targets.rs + if target.is_synthetic() { + cargo.env("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET", "1"); + } + let _guard = builder.msg( Kind::Build, compiler.stage, @@ -197,13 +223,6 @@ fn copy_third_party_objects( ) -> Vec<(PathBuf, DependencyType)> { let mut target_deps = vec![]; - // FIXME: remove this in 2021 - if target == "x86_64-fortanix-unknown-sgx" { - if env::var_os("X86_FORTANIX_SGX_LIBS").is_some() { - builder.info("Warning: X86_FORTANIX_SGX_LIBS environment variable is ignored, libunwind is now compiled as part of rustbuild"); - } - } - if builder.config.sanitizers_enabled(target) && compiler.stage != 0 { // The sanitizers are only copied in stage1 or above, // to avoid creating dependency on LLVM. @@ -243,7 +262,7 @@ fn copy_self_contained_objects( // to using gcc from a glibc-targeting toolchain for linking. // To do that we have to distribute musl startup objects as a part of Rust toolchain // and link with them manually in the self-contained mode. - if target.contains("musl") { + if target.contains("musl") && !target.contains("unikraft") { let srcdir = builder.musl_libdir(target).unwrap_or_else(|| { panic!("Target {:?} does not have a \"musl-libdir\" key", target.triple) }); @@ -270,12 +289,24 @@ fn copy_self_contained_objects( target_deps.push((libunwind_path, DependencyType::TargetSelfContained)); } } else if target.ends_with("-wasi") { - let srcdir = builder - .wasi_root(target) - .unwrap_or_else(|| { - panic!("Target {:?} does not have a \"wasi-root\" key", target.triple) - }) - .join("lib/wasm32-wasi"); + let srcdir = { + let target_dir = if target.contains("-preview1") { + target.to_string().replace("-preview1", "") + } else if target.starts_with("wasm32-") { + "wasm32-wasi".to_string() + } else if target.starts_with("wasm64-") { + "wasm64-wasi".to_string() + } else { + panic!("Target {:?} is not supported", target.triple) + }; + builder + .wasi_root(target) + .unwrap_or_else(|| { + panic!("Target {:?} does not have a \"wasi-root\" key", target.triple) + }) + .join("lib") + .join(target_dir) + }; for &obj in &["libc.a", "crt1-command.o", "crt1-reactor.o"] { copy_and_stamp( builder, @@ -288,7 +319,7 @@ fn copy_self_contained_objects( } } else if target.ends_with("windows-gnu") { for obj in ["crt2.o", "dllcrt2.o"].iter() { - let src = compiler_file(builder, builder.cc(target), target, CLang::C, obj); + let src = compiler_file(builder, &builder.cc(target), target, CLang::C, obj); let target = libdir_self_contained.join(obj); builder.copy(&src, &target); target_deps.push((target, DependencyType::TargetSelfContained)); @@ -305,6 +336,10 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car cargo.env("MACOSX_DEPLOYMENT_TARGET", target); } + if let Some(path) = builder.config.profiler_path(target) { + cargo.env("LLVM_PROFILER_RT_LIB", path); + } + // Determine if we're going to compile in optimized C intrinsics to // the `compiler-builtins` crate. These intrinsics live in LLVM's // `compiler-rt` repository, but our `src/llvm-project` submodule isn't @@ -375,9 +410,13 @@ pub fn std_cargo(builder: &Builder<'_>, target: TargetSelection, stage: u32, car } } - if target.ends_with("-wasi") { + if target.contains("-wasi") { if let Some(p) = builder.wasi_root(target) { - let root = format!("native={}/lib/wasm32-wasi", p.to_str().unwrap()); + let root = format!( + "native={}/lib/{}", + p.to_str().unwrap(), + target.to_string().replace("-preview1", "") + ); cargo.rustflag("-L").rustflag(&root); } } @@ -423,6 +462,8 @@ struct StdLink { pub target: TargetSelection, /// Not actually used; only present to make sure the cache invalidation is correct. crates: Interned>, + /// See [`Std::force_recompile`]. + force_recompile: bool, } impl StdLink { @@ -432,6 +473,7 @@ impl StdLink { target_compiler: std.compiler, target: std.target, crates: std.crates, + force_recompile: std.force_recompile, } } } @@ -455,9 +497,68 @@ impl Step for StdLink { let compiler = self.compiler; let target_compiler = self.target_compiler; let target = self.target; - let libdir = builder.sysroot_libdir(target_compiler, target); - let hostdir = builder.sysroot_libdir(target_compiler, compiler.host); + + // NOTE: intentionally does *not* check `target == builder.build` to avoid having to add the same check in `test::Crate`. + let (libdir, hostdir) = if self.force_recompile && builder.download_rustc() { + // NOTE: copies part of `sysroot_libdir` to avoid having to add a new `force_recompile` argument there too + let lib = builder.sysroot_libdir_relative(self.compiler); + let sysroot = builder.ensure(crate::compile::Sysroot { + compiler: self.compiler, + force_recompile: self.force_recompile, + }); + let libdir = sysroot.join(lib).join("rustlib").join(target.triple).join("lib"); + let hostdir = sysroot.join(lib).join("rustlib").join(compiler.host.triple).join("lib"); + (INTERNER.intern_path(libdir), INTERNER.intern_path(hostdir)) + } else { + let libdir = builder.sysroot_libdir(target_compiler, target); + let hostdir = builder.sysroot_libdir(target_compiler, compiler.host); + (libdir, hostdir) + }; + add_to_sysroot(builder, &libdir, &hostdir, &libstd_stamp(builder, compiler, target)); + + // Special case for stage0, to make `rustup toolchain link` and `x dist --stage 0` + // work for stage0-sysroot. We only do this if the stage0 compiler comes from beta, + // and is not set to a custom path. + if compiler.stage == 0 + && builder + .build + .config + .initial_rustc + .starts_with(builder.out.join(&compiler.host.triple).join("stage0/bin")) + { + // Copy bin files from stage0/bin to stage0-sysroot/bin + let sysroot = builder.out.join(&compiler.host.triple).join("stage0-sysroot"); + + let host = compiler.host.triple; + let stage0_bin_dir = builder.out.join(&host).join("stage0/bin"); + let sysroot_bin_dir = sysroot.join("bin"); + t!(fs::create_dir_all(&sysroot_bin_dir)); + builder.cp_r(&stage0_bin_dir, &sysroot_bin_dir); + + // Copy all *.so files from stage0/lib to stage0-sysroot/lib + let stage0_lib_dir = builder.out.join(&host).join("stage0/lib"); + if let Ok(files) = fs::read_dir(&stage0_lib_dir) { + for file in files { + let file = t!(file); + let path = file.path(); + if path.is_file() && is_dylib(&file.file_name().into_string().unwrap()) { + builder.copy(&path, &sysroot.join("lib").join(path.file_name().unwrap())); + } + } + } + + // Copy codegen-backends from stage0 + let sysroot_codegen_backends = builder.sysroot_codegen_backends(compiler); + t!(fs::create_dir_all(&sysroot_codegen_backends)); + let stage0_codegen_backends = builder + .out + .join(&host) + .join("stage0/lib/rustlib") + .join(&host) + .join("codegen-backends"); + builder.cp_r(&stage0_codegen_backends, &sysroot_codegen_backends); + } } } @@ -589,6 +690,25 @@ impl Step for StartupObjects { } } +fn cp_rustc_component_to_ci_sysroot( + builder: &Builder<'_>, + compiler: Compiler, + contents: Vec, +) { + let sysroot = builder.ensure(Sysroot { compiler, force_recompile: false }); + let ci_rustc_dir = builder.config.ci_rustc_dir(); + + for file in contents { + let src = ci_rustc_dir.join(&file); + let dst = sysroot.join(file); + if src.is_dir() { + t!(fs::create_dir_all(dst)); + } else { + builder.copy(&src, &dst); + } + } +} + #[derive(Debug, PartialOrd, Ord, Copy, Clone, PartialEq, Eq, Hash)] pub struct Rustc { pub target: TargetSelection, @@ -615,6 +735,8 @@ impl Step for Rustc { fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let mut crates = run.builder.in_tree_crates("rustc-main", None); for (i, krate) in crates.iter().enumerate() { + // We can't allow `build rustc` as an alias for this Step, because that's reserved by `Assemble`. + // Ideally Assemble would use `build compiler` instead, but that seems too confusing to be worth the breaking change. if krate.name == "rustc-main" { crates.swap_remove(i); break; @@ -646,18 +768,11 @@ impl Step for Rustc { if builder.download_rustc() && compiler.stage != 0 { // Copy the existing artifacts instead of rebuilding them. // NOTE: this path is only taken for tools linking to rustc-dev (including ui-fulldeps tests). - let sysroot = builder.ensure(Sysroot { compiler }); - - let ci_rustc_dir = builder.out.join(&*builder.build.build.triple).join("ci-rustc"); - for file in builder.config.rustc_dev_contents() { - let src = ci_rustc_dir.join(&file); - let dst = sysroot.join(file); - if src.is_dir() { - t!(fs::create_dir_all(dst)); - } else { - builder.copy(&src, &dst); - } - } + cp_rustc_component_to_ci_sysroot( + builder, + compiler, + builder.config.ci_rustc_dev_contents(), + ); return; } @@ -721,7 +836,7 @@ impl Step for Rustc { let is_collecting = if let Some(path) = &builder.config.rust_profile_generate { if compiler.stage == 1 { - cargo.rustflag(&format!("-Cprofile-generate={}", path)); + cargo.rustflag(&format!("-Cprofile-generate={path}")); // Apparently necessary to avoid overflowing the counters during // a Cargo build profile cargo.rustflag("-Cllvm-args=-vp-counters-per-site=4"); @@ -731,7 +846,7 @@ impl Step for Rustc { } } else if let Some(path) = &builder.config.rust_profile_use { if compiler.stage == 1 { - cargo.rustflag(&format!("-Cprofile-use={}", path)); + cargo.rustflag(&format!("-Cprofile-use={path}")); cargo.rustflag("-Cllvm-args=-pgo-warn-missing-function"); true } else { @@ -764,7 +879,7 @@ impl Step for Rustc { RustcLto::Fat => "fat", _ => unreachable!(), }; - cargo.rustflag(&format!("-Clto={}", lto_type)); + cargo.rustflag(&format!("-Clto={lto_type}")); cargo.rustflag("-Cembed-bitcode=yes"); } RustcLto::ThinLocal => { /* Do nothing, this is the default */ } @@ -789,16 +904,30 @@ impl Step for Rustc { compiler.host, target, ); + let stamp = librustc_stamp(builder, compiler, target); run_cargo( builder, cargo, vec![], - &librustc_stamp(builder, compiler, target), + &stamp, vec![], false, true, // Only ship rustc_driver.so and .rmeta files, not all intermediate .rlib files. ); + // When building `librustc_driver.so` (like `libLLVM.so`) on linux, it can contain + // unexpected debuginfo from dependencies, for example from the C++ standard library used in + // our LLVM wrapper. Unless we're explicitly requesting `librustc_driver` to be built with + // debuginfo (via the debuginfo level of the executables using it): strip this debuginfo + // away after the fact. + if builder.config.rust_debuginfo_level_rustc == DebuginfoLevel::None + && builder.config.rust_debuginfo_level_tools == DebuginfoLevel::None + { + let target_root_dir = stamp.parent().unwrap(); + let rustc_driver = target_root_dir.join("librustc_driver.so"); + strip_debug(builder, target, &rustc_driver); + } + builder.ensure(RustcLink::from_rustc( self, builder.compiler(compiler.stage, builder.config.build), @@ -936,8 +1065,13 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect && !target.contains("apple") && !target.contains("solaris") { - let file = - compiler_file(builder, builder.cxx(target).unwrap(), target, CLang::Cxx, "libstdc++.a"); + let file = compiler_file( + builder, + &builder.cxx(target).unwrap(), + target, + CLang::Cxx, + "libstdc++.a", + ); cargo.env("LLVM_STATIC_STDCPP", file); } if builder.llvm_link_shared() { @@ -1010,7 +1144,7 @@ fn needs_codegen_config(run: &RunConfig<'_>) -> bool { needs_codegen_cfg } -const CODEGEN_BACKEND_PREFIX: &str = "rustc_codegen_"; +pub(crate) const CODEGEN_BACKEND_PREFIX: &str = "rustc_codegen_"; fn is_codegen_cfg_needed(path: &TaskPath, run: &RunConfig<'_>) -> bool { if path.path.to_str().unwrap().contains(&CODEGEN_BACKEND_PREFIX) { @@ -1093,7 +1227,7 @@ impl Step for CodegenBackend { let mut cargo = builder.cargo(compiler, Mode::Codegen, SourceType::InTree, target, "build"); cargo .arg("--manifest-path") - .arg(builder.src.join(format!("compiler/rustc_codegen_{}/Cargo.toml", backend))); + .arg(builder.src.join(format!("compiler/rustc_codegen_{backend}/Cargo.toml"))); rustc_cargo_env(builder, &mut cargo, target, compiler.stage); let tmp_stamp = out_dir.join(".tmp.stamp"); @@ -1198,7 +1332,7 @@ fn codegen_backend_stamp( ) -> PathBuf { builder .cargo_out(compiler, Mode::Codegen, target) - .join(format!(".librustc_codegen_{}.stamp", backend)) + .join(format!(".librustc_codegen_{backend}.stamp")) } pub fn compiler_file( @@ -1208,9 +1342,12 @@ pub fn compiler_file( c: CLang, file: &str, ) -> PathBuf { + if builder.config.dry_run() { + return PathBuf::new(); + } let mut cmd = Command::new(compiler); cmd.args(builder.cflags(target, GitRepo::Rustc, c)); - cmd.arg(format!("-print-file-name={}", file)); + cmd.arg(format!("-print-file-name={file}")); let out = output(&mut cmd); PathBuf::from(out.trim()) } @@ -1218,6 +1355,14 @@ pub fn compiler_file( #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Sysroot { pub compiler: Compiler, + /// See [`Std::force_recompile`]. + force_recompile: bool, +} + +impl Sysroot { + pub(crate) fn new(compiler: Compiler) -> Self { + Sysroot { compiler, force_recompile: false } + } } impl Step for Sysroot { @@ -1240,6 +1385,8 @@ impl Step for Sysroot { let sysroot_dir = |stage| { if stage == 0 { host_dir.join("stage0-sysroot") + } else if self.force_recompile && stage == compiler.stage { + host_dir.join(format!("stage{stage}-test-sysroot")) } else if builder.download_rustc() && compiler.stage != builder.top_stage { host_dir.join("ci-rustc-sysroot") } else { @@ -1252,6 +1399,16 @@ impl Step for Sysroot { let _ = fs::remove_dir_all(&sysroot); t!(fs::create_dir_all(&sysroot)); + // In some cases(see https://github.com/rust-lang/rust/issues/109314), when the stage0 + // compiler relies on more recent version of LLVM than the beta compiler, it may not + // be able to locate the correct LLVM in the sysroot. This situation typically occurs + // when we upgrade LLVM version while the beta compiler continues to use an older version. + // + // Make sure to add the correct version of LLVM into the stage0 sysroot. + if compiler.stage == 0 { + dist::maybe_install_llvm_target(builder, compiler.host, &sysroot); + } + // If we're downloading a compiler from CI, we can use the same compiler for all stages other than 0. if builder.download_rustc() && compiler.stage != 0 { assert_eq!( @@ -1279,17 +1436,27 @@ impl Step for Sysroot { // 2. The sysroot is deleted and recreated between each invocation, so running `x test // ui-fulldeps && x test ui` can't cause failures. let mut filtered_files = Vec::new(); - // Don't trim directories or files that aren't loaded per-target; they can't cause conflicts. - let suffix = format!("lib/rustlib/{}/lib", compiler.host); - for path in builder.config.rustc_dev_contents() { - let path = Path::new(&path); - if path.parent().map_or(false, |parent| parent.ends_with(&suffix)) { - filtered_files.push(path.file_name().unwrap().to_owned()); + let mut add_filtered_files = |suffix, contents| { + for path in contents { + let path = Path::new(&path); + if path.parent().map_or(false, |parent| parent.ends_with(&suffix)) { + filtered_files.push(path.file_name().unwrap().to_owned()); + } } - } - - let filtered_extensions = [OsStr::new("rmeta"), OsStr::new("rlib"), OsStr::new("so")]; - let ci_rustc_dir = builder.ci_rustc_dir(builder.config.build); + }; + let suffix = format!("lib/rustlib/{}/lib", compiler.host); + add_filtered_files(suffix.as_str(), builder.config.ci_rustc_dev_contents()); + // NOTE: we can't copy std eagerly because `stage2-test-sysroot` needs to have only the + // newly compiled std, not the downloaded std. + add_filtered_files("lib", builder.config.ci_rust_std_contents()); + + let filtered_extensions = [ + OsStr::new("rmeta"), + OsStr::new("rlib"), + // FIXME: this is wrong when compiler.host != build, but we don't support that today + OsStr::new(std::env::consts::DLL_EXTENSION), + ]; + let ci_rustc_dir = builder.config.ci_rustc_dir(); builder.cp_filtered(&ci_rustc_dir, &sysroot, &|path| { if path.extension().map_or(true, |ext| !filtered_extensions.contains(&ext)) { return true; @@ -1404,10 +1571,15 @@ impl Step for Assemble { // If we're downloading a compiler from CI, we can use the same compiler for all stages other than 0. if builder.download_rustc() { - let sysroot = builder.ensure(Sysroot { compiler: target_compiler }); + let sysroot = + builder.ensure(Sysroot { compiler: target_compiler, force_recompile: false }); // Ensure that `libLLVM.so` ends up in the newly created target directory, // so that tools using `rustc_private` can use it. dist::maybe_install_llvm_target(builder, target_compiler.host, &sysroot); + // Lower stages use `ci-rustc-sysroot`, not stageN + if target_compiler.stage == builder.top_stage { + builder.info(&format!("Creating a sysroot for stage{stage} compiler (use `rustup toolchain link 'name' build/host/stage{stage}`)", stage=target_compiler.stage)); + } return target_compiler; } @@ -1445,11 +1617,18 @@ impl Step for Assemble { let stage = target_compiler.stage; let host = target_compiler.host; - let msg = if build_compiler.host == host { - format!("Assembling stage{} compiler", stage) + let (host_info, dir_name) = if build_compiler.host == host { + ("".into(), "host".into()) } else { - format!("Assembling stage{} compiler ({})", stage, host) + (format!(" ({host})"), host.to_string()) }; + // NOTE: "Creating a sysroot" is somewhat inconsistent with our internal terminology, since + // sysroots can temporarily be empty until we put the compiler inside. However, + // `ensure(Sysroot)` isn't really something that's user facing, so there shouldn't be any + // ambiguity. + let msg = format!( + "Creating a sysroot for stage{stage} compiler{host_info} (use `rustup toolchain link 'name' build/{dir_name}/stage{stage}`)" + ); builder.info(&msg); // Link in all dylibs to the libdir @@ -1679,7 +1858,7 @@ pub fn run_cargo( }); if !ok { - crate::detail_exit(1); + crate::exit!(1); } // Ok now we need to actually find all the files listed in `toplevel`. We've @@ -1702,10 +1881,10 @@ pub fn run_cargo( }); let path_to_add = match max { Some(triple) => triple.0.to_str().unwrap(), - None => panic!("no output generated for {:?} {:?}", prefix, extension), + None => panic!("no output generated for {prefix:?} {extension:?}"), }; if is_dylib(path_to_add) { - let candidate = format!("{}.lib", path_to_add); + let candidate = format!("{path_to_add}.lib"); let candidate = PathBuf::from(candidate); if candidate.exists() { deps.push((candidate, DependencyType::Target)); @@ -1757,10 +1936,10 @@ pub fn stream_cargo( cargo.arg(arg); } - builder.verbose(&format!("running: {:?}", cargo)); + builder.verbose(&format!("running: {cargo:?}")); let mut child = match cargo.spawn() { Ok(child) => child, - Err(e) => panic!("failed to execute command: {:?}\nerror: {}", cargo, e), + Err(e) => panic!("failed to execute command: {cargo:?}\nerror: {e}"), }; // Spawn Cargo slurping up its JSON output. We'll start building up the @@ -1773,12 +1952,12 @@ pub fn stream_cargo( Ok(msg) => { if builder.config.json_output { // Forward JSON to stdout. - println!("{}", line); + println!("{line}"); } cb(msg) } // If this was informational, just print it out and continue - Err(_) => println!("{}", line), + Err(_) => println!("{line}"), } } @@ -1786,9 +1965,8 @@ pub fn stream_cargo( let status = t!(child.wait()); if builder.is_verbose() && !status.success() { eprintln!( - "command did not execute successfully: {:?}\n\ - expected success, got: {}", - cargo, status + "command did not execute successfully: {cargo:?}\n\ + expected success, got: {status}" ); } status.success() @@ -1815,3 +1993,30 @@ pub enum CargoMessage<'a> { success: bool, }, } + +pub fn strip_debug(builder: &Builder<'_>, target: TargetSelection, path: &Path) { + // FIXME: to make things simpler for now, limit this to the host and target where we know + // `strip -g` is both available and will fix the issue, i.e. on a x64 linux host that is not + // cross-compiling. Expand this to other appropriate targets in the future. + if target != "x86_64-unknown-linux-gnu" || target != builder.config.build || !path.exists() { + return; + } + + let previous_mtime = FileTime::from_last_modification_time(&path.metadata().unwrap()); + // Note: `output` will propagate any errors here. + output(Command::new("strip").arg("--strip-debug").arg(path)); + + // After running `strip`, we have to set the file modification time to what it was before, + // otherwise we risk Cargo invalidating its fingerprint and rebuilding the world next time + // bootstrap is invoked. + // + // An example of this is if we run this on librustc_driver.so. In the first invocation: + // - Cargo will build librustc_driver.so (mtime of 1) + // - Cargo will build rustc-main (mtime of 2) + // - Bootstrap will strip librustc_driver.so (changing the mtime to 3). + // + // In the second invocation of bootstrap, Cargo will see that the mtime of librustc_driver.so + // is greater than the mtime of rustc-main, and will rebuild rustc-main. That will then cause + // everything else (standard library, future stages...) to be rebuilt. + t!(filetime::set_file_mtime(path, previous_mtime)); +} diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index e192cda9a9a71..4821d20a89890 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -10,19 +10,21 @@ use std::cell::{Cell, RefCell}; use std::cmp; use std::collections::{HashMap, HashSet}; use std::env; -use std::fmt; +use std::fmt::{self, Display}; use std::fs; +use std::io::IsTerminal; use std::path::{Path, PathBuf}; use std::process::Command; use std::str::FromStr; -use crate::builder::TaskPath; use crate::cache::{Interned, INTERNER}; use crate::cc_detect::{ndk_compiler, Language}; use crate::channel::{self, GitInfo}; +use crate::compile::CODEGEN_BACKEND_PREFIX; pub use crate::flags::Subcommand; use crate::flags::{Color, Flags, Warnings}; use crate::util::{exe, output, t}; +use build_helper::exit; use once_cell::sync::OnceCell; use semver::Version; use serde::{Deserialize, Deserializer}; @@ -49,6 +51,57 @@ pub enum DryRun { UserSelected, } +#[derive(Copy, Clone, Default, PartialEq, Eq)] +pub enum DebuginfoLevel { + #[default] + None, + LineTablesOnly, + Limited, + Full, +} + +// NOTE: can't derive(Deserialize) because the intermediate trip through toml::Value only +// deserializes i64, and derive() only generates visit_u64 +impl<'de> Deserialize<'de> for DebuginfoLevel { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde::de::Error; + + Ok(match Deserialize::deserialize(deserializer)? { + StringOrInt::String("none") | StringOrInt::Int(0) => DebuginfoLevel::None, + StringOrInt::String("line-tables-only") => DebuginfoLevel::LineTablesOnly, + StringOrInt::String("limited") | StringOrInt::Int(1) => DebuginfoLevel::Limited, + StringOrInt::String("full") | StringOrInt::Int(2) => DebuginfoLevel::Full, + StringOrInt::Int(n) => { + let other = serde::de::Unexpected::Signed(n); + return Err(D::Error::invalid_value(other, &"expected 0, 1, or 2")); + } + StringOrInt::String(s) => { + let other = serde::de::Unexpected::Str(s); + return Err(D::Error::invalid_value( + other, + &"expected none, line-tables-only, limited, or full", + )); + } + }) + } +} + +/// Suitable for passing to `-C debuginfo` +impl Display for DebuginfoLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use DebuginfoLevel::*; + f.write_str(match self { + None => "0", + LineTablesOnly => "line-tables-only", + Limited => "1", + Full => "2", + }) + } +} + /// Global configuration for the entire build and/or bootstrap. /// /// This structure is parsed from `config.toml`, and some of the fields are inferred from `git` or build-time parameters. @@ -78,7 +131,7 @@ pub struct Config { pub sanitizers: bool, pub profiler: bool, pub omit_git_hash: bool, - pub exclude: Vec, + pub skip: Vec, pub include_default_paths: bool, pub rustc_error_format: Option, pub json_output: bool, @@ -150,7 +203,7 @@ pub struct Config { pub llvm_use_libcxx: bool, // rust codegen options - pub rust_optimize: bool, + pub rust_optimize: RustOptimize, pub rust_codegen_units: Option, pub rust_codegen_units_std: Option, pub rust_debug_assertions: bool, @@ -158,10 +211,10 @@ pub struct Config { pub rust_overflow_checks: bool, pub rust_overflow_checks_std: bool, pub rust_debug_logging: bool, - pub rust_debuginfo_level_rustc: u32, - pub rust_debuginfo_level_std: u32, - pub rust_debuginfo_level_tools: u32, - pub rust_debuginfo_level_tests: u32, + pub rust_debuginfo_level_rustc: DebuginfoLevel, + pub rust_debuginfo_level_std: DebuginfoLevel, + pub rust_debuginfo_level_tools: DebuginfoLevel, + pub rust_debuginfo_level_tests: DebuginfoLevel, pub rust_split_debuginfo: SplitDebuginfo, pub rust_rpath: bool, pub rustc_parallel: bool, @@ -180,8 +233,8 @@ pub struct Config { pub llvm_profile_use: Option, pub llvm_profile_generate: bool, pub llvm_libunwind_default: Option, - pub llvm_bolt_profile_generate: bool, - pub llvm_bolt_profile_use: Option, + + pub reproducible_artifacts: Vec, pub build: TargetSelection, pub hosts: Vec, @@ -304,7 +357,7 @@ impl FromStr for LlvmLibunwind { "no" => Ok(Self::No), "in-tree" => Ok(Self::InTree), "system" => Ok(Self::System), - invalid => Err(format!("Invalid value '{}' for rust.llvm-libunwind config.", invalid)), + invalid => Err(format!("Invalid value '{invalid}' for rust.llvm-libunwind config.")), } } } @@ -368,7 +421,7 @@ impl std::str::FromStr for RustcLto { "thin" => Ok(RustcLto::Thin), "fat" => Ok(RustcLto::Fat), "off" => Ok(RustcLto::Off), - _ => Err(format!("Invalid value for rustc LTO: {}", s)), + _ => Err(format!("Invalid value for rustc LTO: {s}")), } } } @@ -377,6 +430,7 @@ impl std::str::FromStr for RustcLto { pub struct TargetSelection { pub triple: Interned, file: Option>, + synthetic: bool, } /// Newtype over `Vec` so we can implement custom parsing logic @@ -408,7 +462,15 @@ impl TargetSelection { let triple = INTERNER.intern_str(triple); let file = file.map(|f| INTERNER.intern_str(f)); - Self { triple, file } + Self { triple, file, synthetic: false } + } + + pub fn create_synthetic(triple: &str, file: &str) -> Self { + Self { + triple: INTERNER.intern_str(triple), + file: Some(INTERNER.intern_str(file)), + synthetic: true, + } } pub fn rustc_target_arg(&self) -> &str { @@ -426,13 +488,18 @@ impl TargetSelection { pub fn ends_with(&self, needle: &str) -> bool { self.triple.ends_with(needle) } + + // See src/bootstrap/synthetic_targets.rs + pub fn is_synthetic(&self) -> bool { + self.synthetic + } } impl fmt::Display for TargetSelection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.triple)?; if let Some(file) = self.file { - write!(f, "({})", file)?; + write!(f, "({file})")?; } Ok(()) } @@ -440,7 +507,7 @@ impl fmt::Display for TargetSelection { impl fmt::Debug for TargetSelection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self) + write!(f, "{self}") } } @@ -467,7 +534,7 @@ pub struct Target { pub linker: Option, pub ndk: Option, pub sanitizers: Option, - pub profiler: Option, + pub profiler: Option, pub rpath: Option, pub crt_static: Option, pub musl_root: Option, @@ -580,7 +647,7 @@ macro_rules! define_config { panic!("overriding existing option") } else { eprintln!("overriding existing option: `{}`", stringify!($field)); - crate::detail_exit(2); + exit!(2); } } else { self.$field = other.$field; @@ -679,7 +746,7 @@ impl Merge for Option { panic!("overriding existing option") } else { eprintln!("overriding existing option"); - crate::detail_exit(2); + exit!(2); } } else { *self = other; @@ -796,9 +863,9 @@ define_config! { } } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(untagged)] -enum StringOrBool { +pub enum StringOrBool { String(String), Bool(bool), } @@ -809,10 +876,107 @@ impl Default for StringOrBool { } } +impl StringOrBool { + fn is_string_or_true(&self) -> bool { + matches!(self, Self::String(_) | Self::Bool(true)) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RustOptimize { + String(String), + Int(u8), + Bool(bool), +} + +impl Default for RustOptimize { + fn default() -> RustOptimize { + RustOptimize::Bool(false) + } +} + +impl<'de> Deserialize<'de> for RustOptimize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(OptimizeVisitor) + } +} + +struct OptimizeVisitor; + +impl<'de> serde::de::Visitor<'de> for OptimizeVisitor { + type Value = RustOptimize; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str(r#"one of: 0, 1, 2, 3, "s", "z", true, false"#) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if ["s", "z"].iter().find(|x| **x == value).is_some() { + Ok(RustOptimize::String(value.to_string())) + } else { + Err(format_optimize_error_msg(value)).map_err(serde::de::Error::custom) + } + } + + fn visit_i64(self, value: i64) -> Result + where + E: serde::de::Error, + { + if matches!(value, 0..=3) { + Ok(RustOptimize::Int(value as u8)) + } else { + Err(format_optimize_error_msg(value)).map_err(serde::de::Error::custom) + } + } + + fn visit_bool(self, value: bool) -> Result + where + E: serde::de::Error, + { + Ok(RustOptimize::Bool(value)) + } +} + +fn format_optimize_error_msg(v: impl std::fmt::Display) -> String { + format!( + r#"unrecognized option for rust optimize: "{v}", expected one of 0, 1, 2, 3, "s", "z", true, false"# + ) +} + +impl RustOptimize { + pub(crate) fn is_release(&self) -> bool { + match &self { + RustOptimize::Bool(true) | RustOptimize::String(_) => true, + RustOptimize::Int(i) => *i > 0, + RustOptimize::Bool(false) => false, + } + } + + pub(crate) fn get_opt_level(&self) -> Option { + match &self { + RustOptimize::String(s) => Some(s.clone()), + RustOptimize::Int(i) => Some(i.to_string()), + RustOptimize::Bool(_) => None, + } + } +} + +#[derive(Deserialize)] +#[serde(untagged)] +enum StringOrInt<'a> { + String(&'a str), + Int(i64), +} define_config! { /// TOML representation of how the Rust build is configured. struct Rust { - optimize: Option = "optimize", + optimize: Option = "optimize", debug: Option = "debug", codegen_units: Option = "codegen-units", codegen_units_std: Option = "codegen-units-std", @@ -821,11 +985,11 @@ define_config! { overflow_checks: Option = "overflow-checks", overflow_checks_std: Option = "overflow-checks-std", debug_logging: Option = "debug-logging", - debuginfo_level: Option = "debuginfo-level", - debuginfo_level_rustc: Option = "debuginfo-level-rustc", - debuginfo_level_std: Option = "debuginfo-level-std", - debuginfo_level_tools: Option = "debuginfo-level-tools", - debuginfo_level_tests: Option = "debuginfo-level-tests", + debuginfo_level: Option = "debuginfo-level", + debuginfo_level_rustc: Option = "debuginfo-level-rustc", + debuginfo_level_std: Option = "debuginfo-level-std", + debuginfo_level_tools: Option = "debuginfo-level-tools", + debuginfo_level_tests: Option = "debuginfo-level-tests", split_debuginfo: Option = "split-debuginfo", run_dsymutil: Option = "run-dsymutil", backtrace: Option = "backtrace", @@ -880,7 +1044,7 @@ define_config! { llvm_libunwind: Option = "llvm-libunwind", android_ndk: Option = "android-ndk", sanitizers: Option = "sanitizers", - profiler: Option = "profiler", + profiler: Option = "profiler", rpath: Option = "rpath", crt_static: Option = "crt-static", musl_root: Option = "musl-root", @@ -893,14 +1057,12 @@ define_config! { impl Config { pub fn default_opts() -> Config { - use is_terminal::IsTerminal; - let mut config = Config::default(); config.llvm_optimize = true; config.ninja_in_file = true; config.llvm_static_stdcpp = false; config.backtrace = true; - config.rust_optimize = true; + config.rust_optimize = RustOptimize::Bool(true); config.rust_optimize_tests = true; config.submodules = None; config.docs = true; @@ -945,7 +1107,7 @@ impl Config { .and_then(|table: toml::Value| TomlConfig::deserialize(table)) .unwrap_or_else(|err| { eprintln!("failed to parse TOML configuration '{}': {err}", file.display()); - crate::detail_exit(2); + exit!(2); }) } Self::parse_inner(args, get_toml) @@ -957,7 +1119,7 @@ impl Config { // Set flags. config.paths = std::mem::take(&mut flags.paths); - config.exclude = flags.exclude.into_iter().map(|path| TaskPath::parse(path)).collect(); + config.skip = flags.skip.into_iter().chain(flags.exclude).collect(); config.include_default_paths = flags.include_default_paths; config.rustc_error_format = flags.rustc_error_format; config.json_output = flags.json_output; @@ -972,15 +1134,6 @@ impl Config { config.free_args = std::mem::take(&mut flags.free_args); config.llvm_profile_use = flags.llvm_profile_use; config.llvm_profile_generate = flags.llvm_profile_generate; - config.llvm_bolt_profile_generate = flags.llvm_bolt_profile_generate; - config.llvm_bolt_profile_use = flags.llvm_bolt_profile_use; - - if config.llvm_bolt_profile_generate && config.llvm_bolt_profile_use.is_some() { - eprintln!( - "Cannot use both `llvm_bolt_profile_generate` and `llvm_bolt_profile_use` at the same time" - ); - crate::detail_exit(1); - } // Infer the rest of the configuration. @@ -1058,11 +1211,19 @@ impl Config { }; if let Some(include) = &toml.profile { + // Allows creating alias for profile names, allowing + // profiles to be renamed while maintaining back compatibility + // Keep in sync with `profile_aliases` in bootstrap.py + let profile_aliases = HashMap::from([("user", "dist")]); + let include = match profile_aliases.get(include.as_str()) { + Some(alias) => alias, + None => include.as_str(), + }; let mut include_path = config.src.clone(); include_path.push("src"); include_path.push("bootstrap"); include_path.push("defaults"); - include_path.push(format!("config.{}.toml", include)); + include_path.push(format!("config.{include}.toml")); let included_toml = get_toml(&include_path); toml.merge(included_toml, ReplaceOpt::IgnoreDuplicate); } @@ -1095,7 +1256,7 @@ impl Config { } } eprintln!("failed to parse override `{option}`: `{err}"); - crate::detail_exit(2) + exit!(2) } toml.merge(override_toml, ReplaceOpt::Override); @@ -1114,10 +1275,13 @@ impl Config { config.out = crate::util::absolute(&config.out); } - config.initial_rustc = build.rustc.map(PathBuf::from).unwrap_or_else(|| { + config.initial_rustc = if let Some(rustc) = build.rustc { + config.check_build_rustc_version(&rustc); + PathBuf::from(rustc) + } else { config.download_beta_toolchain(); config.out.join(config.build.triple).join("stage0/bin/rustc") - }); + }; config.initial_cargo = build .cargo @@ -1208,6 +1372,25 @@ impl Config { let mut omit_git_hash = None; if let Some(rust) = toml.rust { + set(&mut config.channel, rust.channel); + + config.download_rustc_commit = config.download_ci_rustc_commit(rust.download_rustc); + // This list is incomplete, please help by expanding it! + if config.download_rustc_commit.is_some() { + // We need the channel used by the downloaded compiler to match the one we set for rustdoc; + // otherwise rustdoc-ui tests break. + let ci_channel = t!(fs::read_to_string(config.src.join("src/ci/channel"))); + let ci_channel = ci_channel.trim_end(); + if config.channel != ci_channel + && !(config.channel == "dev" && ci_channel == "nightly") + { + panic!( + "setting rust.channel={} is incompatible with download-rustc", + config.channel + ); + } + } + debug = rust.debug; debug_assertions = rust.debug_assertions; debug_assertions_std = rust.debug_assertions_std; @@ -1219,6 +1402,7 @@ impl Config { debuginfo_level_std = rust.debuginfo_level_std; debuginfo_level_tools = rust.debuginfo_level_tools; debuginfo_level_tests = rust.debuginfo_level_tests; + config.rust_split_debuginfo = rust .split_debuginfo .as_deref() @@ -1234,7 +1418,6 @@ impl Config { set(&mut config.jemalloc, rust.jemalloc); set(&mut config.test_compare_mode, rust.test_compare_mode); set(&mut config.backtrace, rust.backtrace); - set(&mut config.channel, rust.channel); config.description = rust.description; set(&mut config.rust_dist_src, rust.dist_src); set(&mut config.verbose_tests, rust.verbose_tests); @@ -1267,16 +1450,27 @@ impl Config { .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); if let Some(ref backends) = rust.codegen_backends { - config.rust_codegen_backends = - backends.iter().map(|s| INTERNER.intern_str(s)).collect(); + let available_backends = vec!["llvm", "cranelift", "gcc"]; + + config.rust_codegen_backends = backends.iter().map(|s| { + if let Some(backend) = s.strip_prefix(CODEGEN_BACKEND_PREFIX) { + if available_backends.contains(&backend) { + panic!("Invalid value '{s}' for 'rust.codegen-backends'. Instead, please use '{backend}'."); + } else { + println!("help: '{s}' for 'rust.codegen-backends' might fail. \ + Codegen backends are mostly defined without the '{CODEGEN_BACKEND_PREFIX}' prefix. \ + In this case, it would be referred to as '{backend}'."); + } + } + + INTERNER.intern_str(s) + }).collect(); } config.rust_codegen_units = rust.codegen_units.map(threads_from_config); config.rust_codegen_units_std = rust.codegen_units_std.map(threads_from_config); config.rust_profile_use = flags.rust_profile_use.or(rust.profile_use); config.rust_profile_generate = flags.rust_profile_generate.or(rust.profile_generate); - config.download_rustc_commit = config.download_ci_rustc_commit(rust.download_rustc); - config.rust_lto = rust .lto .as_deref() @@ -1288,6 +1482,8 @@ impl Config { config.rust_profile_generate = flags.rust_profile_generate; } + config.reproducible_artifacts = flags.reproducible_artifact; + // rust_info must be set before is_ci_llvm_available() is called. let default = config.channel == "dev"; config.omit_git_hash = omit_git_hash.unwrap_or(default); @@ -1332,7 +1528,7 @@ impl Config { let asserts = llvm_assertions.unwrap_or(false); config.llvm_from_ci = match llvm.download_ci_llvm { Some(StringOrBool::String(s)) => { - assert!(s == "if-available", "unknown option `{}` for download-ci-llvm", s); + assert!(s == "if-available", "unknown option `{s}` for download-ci-llvm"); crate::llvm::is_ci_llvm_available(&config, asserts) } Some(StringOrBool::Bool(b)) => b, @@ -1388,6 +1584,11 @@ impl Config { let mut target = Target::from_triple(&triple); if let Some(ref s) = cfg.llvm_config { + if config.download_rustc_commit.is_some() && triple == &*config.build.triple { + panic!( + "setting llvm_config for the host is incompatible with download-rustc" + ); + } target.llvm_config = Some(config.src.join(s)); } target.llvm_has_rust_patches = cfg.llvm_has_rust_patches; @@ -1464,7 +1665,7 @@ impl Config { config.llvm_assertions = llvm_assertions.unwrap_or(false); config.llvm_tests = llvm_tests.unwrap_or(false); config.llvm_plugins = llvm_plugins.unwrap_or(false); - config.rust_optimize = optimize.unwrap_or(true); + config.rust_optimize = optimize.unwrap_or(RustOptimize::Bool(true)); let default = debug == Some(true); config.rust_debug_assertions = debug_assertions.unwrap_or(default); @@ -1476,17 +1677,17 @@ impl Config { config.rust_debug_logging = debug_logging.unwrap_or(config.rust_debug_assertions); - let with_defaults = |debuginfo_level_specific: Option| { + let with_defaults = |debuginfo_level_specific: Option<_>| { debuginfo_level_specific.or(debuginfo_level).unwrap_or(if debug == Some(true) { - 1 + DebuginfoLevel::Limited } else { - 0 + DebuginfoLevel::None }) }; config.rust_debuginfo_level_rustc = with_defaults(debuginfo_level_rustc); config.rust_debuginfo_level_std = with_defaults(debuginfo_level_std); config.rust_debuginfo_level_tools = with_defaults(debuginfo_level_tools); - config.rust_debuginfo_level_tests = debuginfo_level_tests.unwrap_or(0); + config.rust_debuginfo_level_tests = debuginfo_level_tests.unwrap_or(DebuginfoLevel::None); let download_rustc = config.download_rustc_commit.is_some(); // See https://github.com/rust-lang/compiler-team/issues/326 @@ -1553,6 +1754,18 @@ impl Config { } } + /// Runs a command, printing out nice contextual information if it fails. + /// Exits if the command failed to execute at all, otherwise returns its + /// `status.success()`. + #[deprecated = "use `Builder::try_run` instead where possible"] + pub(crate) fn try_run(&self, cmd: &mut Command) -> Result<(), ()> { + if self.dry_run() { + return Ok(()); + } + self.verbose(&format!("running: {cmd:?}")); + build_helper::util::try_run(cmd, self.is_verbose()) + } + /// A git invocation which runs inside the source directory. /// /// Use this rather than `Command::new("git")` in order to support out-of-tree builds. @@ -1589,10 +1802,10 @@ impl Config { pub(crate) fn artifact_version_part(&self, commit: &str) -> String { let (channel, version) = if self.rust_info.is_managed_git_subrepository() { let mut channel = self.git(); - channel.arg("show").arg(format!("{}:src/ci/channel", commit)); + channel.arg("show").arg(format!("{commit}:src/ci/channel")); let channel = output(&mut channel); let mut version = self.git(); - version.arg("show").arg(format!("{}:src/version", commit)); + version.arg("show").arg(format!("{commit}:src/version")); let version = output(&mut version); (channel.trim().to_owned(), version.trim().to_owned()) } else { @@ -1609,10 +1822,10 @@ impl Config { "help: consider using a git checkout or ensure these files are readable" ); if let Err(channel) = channel { - eprintln!("reading {}/src/ci/channel failed: {:?}", src, channel); + eprintln!("reading {src}/src/ci/channel failed: {channel:?}"); } if let Err(version) = version { - eprintln!("reading {}/src/version failed: {:?}", src, version); + eprintln!("reading {src}/src/version failed: {version:?}"); } panic!(); } @@ -1658,6 +1871,12 @@ impl Config { self.out.join(&*self.build.triple).join("ci-llvm") } + /// Directory where the extracted `rustc-dev` component is stored. + pub(crate) fn ci_rustc_dir(&self) -> PathBuf { + assert!(self.download_rustc()); + self.out.join(self.build.triple).join("ci-rustc") + } + /// Determine whether llvm should be linked dynamically. /// /// If `false`, llvm should be linked statically. @@ -1693,11 +1912,11 @@ impl Config { self.download_rustc_commit().is_some() } - pub(crate) fn download_rustc_commit(&self) -> Option<&'static str> { + pub(crate) fn download_rustc_commit(&self) -> Option<&str> { static DOWNLOAD_RUSTC: OnceCell> = OnceCell::new(); if self.dry_run() && DOWNLOAD_RUSTC.get().is_none() { // avoid trying to actually download the commit - return None; + return self.download_rustc_commit.as_deref(); } DOWNLOAD_RUSTC @@ -1732,7 +1951,7 @@ impl Config { pub fn verbose(&self, msg: &str) { if self.verbose > 0 { - println!("{}", msg); + println!("{msg}"); } } @@ -1744,12 +1963,24 @@ impl Config { self.target_config.values().any(|t| t.sanitizers == Some(true)) || self.sanitizers } + pub fn profiler_path(&self, target: TargetSelection) -> Option<&str> { + match self.target_config.get(&target)?.profiler.as_ref()? { + StringOrBool::String(s) => Some(s), + StringOrBool::Bool(_) => None, + } + } + pub fn profiler_enabled(&self, target: TargetSelection) -> bool { - self.target_config.get(&target).map(|t| t.profiler).flatten().unwrap_or(self.profiler) + self.target_config + .get(&target) + .and_then(|t| t.profiler.as_ref()) + .map(StringOrBool::is_string_or_true) + .unwrap_or(self.profiler) } pub fn any_profiler_enabled(&self) -> bool { - self.target_config.values().any(|t| t.profiler == Some(true)) || self.profiler + self.target_config.values().any(|t| matches!(&t.profiler, Some(p) if p.is_string_or_true())) + || self.profiler } pub fn rpath_enabled(&self, target: TargetSelection) -> bool { @@ -1780,13 +2011,13 @@ impl Config { self.rust_codegen_backends.get(0).cloned() } - pub fn check_build_rustc_version(&self) { + pub fn check_build_rustc_version(&self, rustc_path: &str) { if self.dry_run() { return; } // check rustc version is same or lower with 1 apart from the building one - let mut cmd = Command::new(&self.initial_rustc); + let mut cmd = Command::new(rustc_path); cmd.arg("--version"); let rustc_output = output(&mut cmd) .lines() @@ -1805,14 +2036,14 @@ impl Config { .unwrap(); if !(source_version == rustc_version || (source_version.major == rustc_version.major - && source_version.minor == rustc_version.minor + 1)) + && (source_version.minor == rustc_version.minor + || source_version.minor == rustc_version.minor + 1))) { let prev_version = format!("{}.{}.x", source_version.major, source_version.minor - 1); eprintln!( - "Unexpected rustc version: {}, we should use {}/{} to build source with {}", - rustc_version, prev_version, source_version, source_version + "Unexpected rustc version: {rustc_version}, we should use {prev_version}/{source_version} to build source with {source_version}" ); - crate::detail_exit(1); + exit!(1); } } @@ -1824,7 +2055,7 @@ impl Config { Some(StringOrBool::Bool(true)) => false, Some(StringOrBool::String(s)) if s == "if-unchanged" => true, Some(StringOrBool::String(other)) => { - panic!("unrecognized option for download-rustc: {}", other) + panic!("unrecognized option for download-rustc: {other}") } }; @@ -1848,7 +2079,7 @@ impl Config { println!("help: maybe your repository history is too shallow?"); println!("help: consider disabling `download-rustc`"); println!("help: or fetch enough history to include one upstream commit"); - crate::detail_exit(1); + crate::exit!(1); } // Warn if there were changes to the compiler or standard library since the ancestor commit. diff --git a/src/bootstrap/config/tests.rs b/src/bootstrap/config/tests.rs index 4de84b543ed96..c340bb2982a2b 100644 --- a/src/bootstrap/config/tests.rs +++ b/src/bootstrap/config/tests.rs @@ -1,5 +1,8 @@ +use crate::config::TomlConfig; + use super::{Config, Flags}; use clap::CommandFactory; +use serde::Deserialize; use std::{env, path::Path}; fn parse(config: &str) -> Config { @@ -159,3 +162,37 @@ fn override_toml_duplicate() { |&_| toml::from_str("changelog-seen = 0").unwrap(), ); } + +#[test] +fn profile_user_dist() { + fn get_toml(file: &Path) -> TomlConfig { + let contents = if file.ends_with("config.toml") { + "profile = \"user\"".to_owned() + } else { + assert!(file.ends_with("config.dist.toml")); + std::fs::read_to_string(dbg!(file)).unwrap() + }; + toml::from_str(&contents) + .and_then(|table: toml::Value| TomlConfig::deserialize(table)) + .unwrap() + } + Config::parse_inner(&["check".to_owned()], get_toml); +} + +#[test] +fn rust_optimize() { + assert_eq!(parse("").rust_optimize.is_release(), true); + assert_eq!(parse("rust.optimize = false").rust_optimize.is_release(), false); + assert_eq!(parse("rust.optimize = true").rust_optimize.is_release(), true); + assert_eq!(parse("rust.optimize = 0").rust_optimize.is_release(), false); + assert_eq!(parse("rust.optimize = 1").rust_optimize.is_release(), true); + assert_eq!(parse("rust.optimize = 1").rust_optimize.get_opt_level(), Some("1".to_string())); + assert_eq!(parse("rust.optimize = \"s\"").rust_optimize.is_release(), true); + assert_eq!(parse("rust.optimize = \"s\"").rust_optimize.get_opt_level(), Some("s".to_string())); +} + +#[test] +#[should_panic] +fn invalid_rust_optimize() { + parse("rust.optimize = \"a\""); +} diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index 571062a3a6fd0..15e8c1eb9f8d9 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -9,7 +9,7 @@ rust_dir = os.path.dirname(rust_dir) rust_dir = os.path.dirname(rust_dir) sys.path.append(os.path.join(rust_dir, "src", "bootstrap")) -import bootstrap +import bootstrap # noqa: E402 class Option(object): @@ -45,7 +45,6 @@ def v(*args): o("llvm-link-shared", "llvm.link-shared", "prefer shared linking to LLVM (llvm-config --link-shared)") o("rpath", "rust.rpath", "build rpaths into rustc itself") o("codegen-tests", "rust.codegen-tests", "run the tests/codegen tests") -o("option-checking", None, "complain about unrecognized options in this configure script") o("ninja", "llvm.ninja", "build LLVM using the Ninja generator (for MSVC, requires building in the correct environment)") o("locked-deps", "build.locked-deps", "force Cargo.lock to be up to date") o("vendor", "build.vendor", "enable usage of vendored Rust crates") @@ -170,6 +169,9 @@ def v(*args): v("host", None, "List of GNUs ./configure syntax LLVM host triples") v("target", None, "List of GNUs ./configure syntax LLVM target triples") +# Options specific to this configure script +o("option-checking", None, "complain about unrecognized options in this configure script") +o("verbose-configure", None, "don't truncate options when printing them in this configure script") v("set", None, "set arbitrary key/value pairs in TOML configuration") @@ -178,7 +180,7 @@ def p(msg): def err(msg): - print("configure: error: " + msg) + print("\nconfigure: ERROR: " + msg + "\n") sys.exit(1) def is_value_list(key): @@ -211,6 +213,8 @@ def is_value_list(key): print('be passed with `--disable-foo` to forcibly disable the option') sys.exit(0) +VERBOSE = False + # Parse all command line arguments into one of these three lists, handling # boolean and value-based options separately def parse_args(args): @@ -271,9 +275,12 @@ def parse_args(args): if len(need_value_args) > 0: err("Option '{0}' needs a value ({0}=val)".format(need_value_args[0])) + global VERBOSE + VERBOSE = 'verbose-configure' in known_args + config = {} - set('build.configure-args', sys.argv[1:], config) + set('build.configure-args', args, config) apply_args(known_args, option_checking, config) return parse_example_config(known_args, config) @@ -290,7 +297,7 @@ def set(key, value, config): value = [v for v in value if v] s = "{:20} := {}".format(key, value) - if len(s) < 70: + if len(s) < 70 or VERBOSE: p(s) else: p(s[:70] + " ...") @@ -312,7 +319,7 @@ def apply_args(known_args, option_checking, config): for key in known_args: # The `set` option is special and can be passed a bunch of times if key == 'set': - for option, value in known_args[key]: + for _option, value in known_args[key]: keyval = value.split('=', 1) if len(keyval) == 1 or keyval[1] == "true": value = True @@ -371,7 +378,7 @@ def apply_args(known_args, option_checking, config): set('rust.lld', True, config) set('rust.llvm-tools', True, config) set('build.extended', True, config) - elif option.name == 'option-checking': + elif option.name in ['option-checking', 'verbose-configure']: # this was handled above pass elif option.name == 'dist-compression-formats': @@ -393,8 +400,10 @@ def parse_example_config(known_args, config): targets = {} top_level_keys = [] - for line in open(rust_dir + '/config.example.toml').read().split("\n"): - if cur_section == None: + with open(rust_dir + '/config.example.toml') as example_config: + example_lines = example_config.read().split("\n") + for line in example_lines: + if cur_section is None: if line.count('=') == 1: top_level_key = line.split('=')[0] top_level_key = top_level_key.strip(' #') @@ -428,7 +437,7 @@ def parse_example_config(known_args, config): targets[target][0] = targets[target][0].replace("x86_64-unknown-linux-gnu", "'{}'".format(target) if "." in target else target) if 'profile' not in config: - set('profile', 'user', config) + set('profile', 'dist', config) configure_file(sections, top_level_keys, targets, config) return section_order, sections, targets @@ -516,8 +525,8 @@ def write_uncommented(target, f): block.append(line) if len(line) == 0: if not is_comment: - for l in block: - f.write(l + "\n") + for ln in block: + f.write(ln + "\n") block = [] is_comment = True continue @@ -535,12 +544,21 @@ def write_config_toml(writer, section_order, targets, sections): def quit_if_file_exists(file): if os.path.isfile(file): - err("Existing '" + file + "' detected.") + msg = "Existing '{}' detected. Exiting".format(file) + + # If the output object directory isn't empty, we can get these errors + host_objdir = os.environ.get("OBJDIR_ON_HOST") + if host_objdir is not None: + msg += "\nIs objdir '{}' clean?".format(host_objdir) + + err(msg) if __name__ == "__main__": # If 'config.toml' already exists, exit the script at this point quit_if_file_exists('config.toml') + if "GITHUB_ACTIONS" in os.environ: + print("::group::Configure the build") p("processing command line") # Parse all known arguments into a configuration structure that reflects the # TOML we're going to write out @@ -563,3 +581,5 @@ def quit_if_file_exists(file): p("") p("run `python {}/x.py --help`".format(rust_dir)) + if "GITHUB_ACTIONS" in os.environ: + print("::endgroup::") diff --git a/src/bootstrap/defaults/config.dist.toml b/src/bootstrap/defaults/config.dist.toml new file mode 100644 index 0000000000000..44efdf50b965f --- /dev/null +++ b/src/bootstrap/defaults/config.dist.toml @@ -0,0 +1,22 @@ +# These defaults are meant for users and distro maintainers building from source, without intending to make multiple changes. +[build] +# When compiling from source, you almost always want a full stage 2 build, +# which has all the latest optimizations from nightly. +build-stage = 2 +test-stage = 2 +doc-stage = 2 +# When compiling from source, you usually want all tools. +extended = true + +# Most users installing from source want to build all parts of the project from source. +[llvm] +download-ci-llvm = false +[rust] +# We have several defaults in bootstrap that depend on whether the channel is `dev` (e.g. `omit-git-hash` and `download-ci-llvm`). +# Make sure they don't get set when installing from source. +channel = "nightly" +download-rustc = false + +[dist] +# Use better compression when preparing tarballs. +compression-profile = "balanced" diff --git a/src/bootstrap/defaults/config.tools.toml b/src/bootstrap/defaults/config.tools.toml index 6b6625342a67e..79424f28d27b7 100644 --- a/src/bootstrap/defaults/config.tools.toml +++ b/src/bootstrap/defaults/config.tools.toml @@ -9,6 +9,8 @@ debug-logging = true incremental = true # Download rustc from CI instead of building it from source. # This cuts compile times by almost 60x, but means you can't modify the compiler. +# Using these defaults will download the stage2 compiler (see `download-rustc` +# setting) and the stage2 toolchain should therefore be used for these defaults. download-rustc = "if-unchanged" [build] diff --git a/src/bootstrap/defaults/config.user.toml b/src/bootstrap/defaults/config.user.toml deleted file mode 100644 index 25d9e649f23c7..0000000000000 --- a/src/bootstrap/defaults/config.user.toml +++ /dev/null @@ -1,19 +0,0 @@ -# These defaults are meant for users and distro maintainers building from source, without intending to make multiple changes. -[build] -# When compiling from source, you almost always want a full stage 2 build, -# which has all the latest optimizations from nightly. -build-stage = 2 -test-stage = 2 -doc-stage = 2 -# When compiling from source, you usually want all tools. -extended = true - -# Most users installing from source want to build all parts of the project from source. -[llvm] -download-ci-llvm = false -[rust] -download-rustc = false - -[dist] -# Use better compression when preparing tarballs. -compression-profile = "balanced" diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 9cead7adc8c35..32da4ac29a463 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -18,9 +18,7 @@ use std::process::Command; use object::read::archive::ArchiveFile; use object::BinaryFormat; -use sha2::Digest; -use crate::bolt::{instrument_with_bolt, optimize_with_bolt}; use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step}; use crate::cache::{Interned, INTERNER}; use crate::channel; @@ -106,11 +104,12 @@ impl Step for JsonDocs { /// Builds the `rust-docs-json` installer component. fn run(self, builder: &Builder<'_>) -> Option { let host = self.host; - builder.ensure(crate::doc::Std { - stage: builder.top_stage, - target: host, - format: DocumentationFormat::JSON, - }); + builder.ensure(crate::doc::Std::new( + builder.top_stage, + host, + builder, + DocumentationFormat::JSON, + )); let dest = "share/doc/rust/json"; @@ -161,7 +160,7 @@ fn find_files(files: &[&str], path: &[PathBuf]) -> Vec { if let Some(file_path) = file_path { found.push(file_path); } else { - panic!("Could not find '{}' in {:?}", file, path); + panic!("Could not find '{file}' in {path:?}"); } } @@ -174,6 +173,10 @@ fn make_win_dist( target: TargetSelection, builder: &Builder<'_>, ) { + if builder.config.dry_run() { + return; + } + //Ask gcc where it keeps its stuff let mut cmd = Command::new(builder.cc(target)); cmd.arg("-print-search-dirs"); @@ -897,7 +900,9 @@ impl Step for Src { /// Creates the `rust-src` installer component fn run(self, builder: &Builder<'_>) -> GeneratedTarball { - builder.update_submodule(&Path::new("src/llvm-project")); + if !builder.config.dry_run() { + builder.update_submodule(&Path::new("src/llvm-project")); + } let tarball = Tarball::new_targetless(builder, "rust-src"); @@ -1013,6 +1018,9 @@ impl Step for PlainSourceTarball { .arg(builder.src.join("./compiler/rustc_codegen_cranelift/Cargo.toml")) .arg("--sync") .arg(builder.src.join("./src/bootstrap/Cargo.toml")) + // Will read the libstd Cargo.toml + // which uses the unstable `public-dependency` feature. + .env("RUSTC_BOOTSTRAP", "1") .current_dir(&plain_dst_src); let config = if !builder.config.dry_run() { @@ -1075,13 +1083,6 @@ impl Step for Cargo { tarball.add_dir(etc.join("man"), "share/man/man1"); tarball.add_legal_and_readme_to("share/doc/cargo"); - for dirent in fs::read_dir(cargo.parent().unwrap()).expect("read_dir") { - let dirent = dirent.expect("read dir entry"); - if dirent.file_name().to_str().expect("utf8").starts_with("cargo-credential-") { - tarball.add_file(&dirent.path(), "libexec", 0o755); - } - } - Some(tarball.generate()) } } @@ -1477,8 +1478,8 @@ impl Step for Extended { rtf.push('}'); fn filter(contents: &str, marker: &str) -> String { - let start = format!("tool-{}-start", marker); - let end = format!("tool-{}-end", marker); + let start = format!("tool-{marker}-start"); + let end = format!("tool-{marker}-end"); let mut lines = Vec::new(); let mut omitted = false; for line in contents.lines() { @@ -1594,9 +1595,7 @@ impl Step for Extended { prepare("cargo"); prepare("rust-analysis"); prepare("rust-std"); - prepare("clippy"); - prepare("rust-analyzer"); - for tool in &["rust-docs", "rust-demangler", "miri"] { + for tool in &["clippy", "rust-analyzer", "rust-docs", "rust-demangler", "miri"] { if built_tools.contains(tool) { prepare(tool); } @@ -1682,40 +1681,44 @@ impl Step for Extended { .arg("-out") .arg(exe.join("StdGroup.wxs")), ); - builder.run( - Command::new(&heat) - .current_dir(&exe) - .arg("dir") - .arg("rust-analyzer") - .args(&heat_flags) - .arg("-cg") - .arg("RustAnalyzerGroup") - .arg("-dr") - .arg("RustAnalyzer") - .arg("-var") - .arg("var.RustAnalyzerDir") - .arg("-out") - .arg(exe.join("RustAnalyzerGroup.wxs")) - .arg("-t") - .arg(etc.join("msi/remove-duplicates.xsl")), - ); - builder.run( - Command::new(&heat) - .current_dir(&exe) - .arg("dir") - .arg("clippy") - .args(&heat_flags) - .arg("-cg") - .arg("ClippyGroup") - .arg("-dr") - .arg("Clippy") - .arg("-var") - .arg("var.ClippyDir") - .arg("-out") - .arg(exe.join("ClippyGroup.wxs")) - .arg("-t") - .arg(etc.join("msi/remove-duplicates.xsl")), - ); + if built_tools.contains("rust-analyzer") { + builder.run( + Command::new(&heat) + .current_dir(&exe) + .arg("dir") + .arg("rust-analyzer") + .args(&heat_flags) + .arg("-cg") + .arg("RustAnalyzerGroup") + .arg("-dr") + .arg("RustAnalyzer") + .arg("-var") + .arg("var.RustAnalyzerDir") + .arg("-out") + .arg(exe.join("RustAnalyzerGroup.wxs")) + .arg("-t") + .arg(etc.join("msi/remove-duplicates.xsl")), + ); + } + if built_tools.contains("clippy") { + builder.run( + Command::new(&heat) + .current_dir(&exe) + .arg("dir") + .arg("clippy") + .args(&heat_flags) + .arg("-cg") + .arg("ClippyGroup") + .arg("-dr") + .arg("Clippy") + .arg("-var") + .arg("var.ClippyDir") + .arg("-out") + .arg(exe.join("ClippyGroup.wxs")) + .arg("-t") + .arg(etc.join("msi/remove-duplicates.xsl")), + ); + } if built_tools.contains("rust-demangler") { builder.run( Command::new(&heat) @@ -1799,7 +1802,6 @@ impl Step for Extended { .arg("-dCargoDir=cargo") .arg("-dStdDir=rust-std") .arg("-dAnalysisDir=rust-analysis") - .arg("-dClippyDir=clippy") .arg("-arch") .arg(&arch) .arg("-out") @@ -1807,6 +1809,9 @@ impl Step for Extended { .arg(&input); add_env(builder, &mut cmd, target); + if built_tools.contains("clippy") { + cmd.arg("-dClippyDir=clippy"); + } if built_tools.contains("rust-docs") { cmd.arg("-dDocsDir=rust-docs"); } @@ -1833,7 +1838,9 @@ impl Step for Extended { } candle("CargoGroup.wxs".as_ref()); candle("StdGroup.wxs".as_ref()); - candle("ClippyGroup.wxs".as_ref()); + if built_tools.contains("clippy") { + candle("ClippyGroup.wxs".as_ref()); + } if built_tools.contains("miri") { candle("MiriGroup.wxs".as_ref()); } @@ -1853,7 +1860,7 @@ impl Step for Extended { builder.install(&etc.join("gfx/banner.bmp"), &exe, 0o644); builder.install(&etc.join("gfx/dialogbg.bmp"), &exe, 0o644); - builder.info(&format!("building `msi` installer with {:?}", light)); + builder.info(&format!("building `msi` installer with {light:?}")); let filename = format!("{}-{}.msi", pkgname(builder, "rust"), target.triple); let mut cmd = Command::new(&light); cmd.arg("-nologo") @@ -1870,9 +1877,11 @@ impl Step for Extended { .arg("CargoGroup.wixobj") .arg("StdGroup.wixobj") .arg("AnalysisGroup.wixobj") - .arg("ClippyGroup.wixobj") .current_dir(&exe); + if built_tools.contains("clippy") { + cmd.arg("ClippyGroup.wixobj"); + } if built_tools.contains("miri") { cmd.arg("MiriGroup.wixobj"); } @@ -1930,19 +1939,7 @@ fn install_llvm_file(builder: &Builder<'_>, source: &Path, destination: &Path) { return; } - // After LLVM is built, we modify (instrument or optimize) the libLLVM.so library file. - // This is not done in-place so that the built LLVM files are not "tainted" with BOLT. - // We perform the instrumentation/optimization here, on the fly, just before they are being - // packaged into some destination directory. - let postprocessed = if builder.config.llvm_bolt_profile_generate { - builder.ensure(BoltInstrument::new(source.to_path_buf())) - } else if let Some(path) = &builder.config.llvm_bolt_profile_use { - builder.ensure(BoltOptimize::new(source.to_path_buf(), path.into())) - } else { - source.to_path_buf() - }; - - builder.install(&postprocessed, destination, 0o644); + builder.install(&source, destination, 0o644); } /// Maybe add LLVM object files to the given destination lib-dir. Allows either static or dynamic linking. @@ -1985,7 +1982,7 @@ fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir { let mut cmd = Command::new(llvm_config); cmd.arg("--libfiles"); - builder.verbose(&format!("running {:?}", cmd)); + builder.verbose(&format!("running {cmd:?}")); let files = if builder.config.dry_run() { "".into() } else { output(&mut cmd) }; let build_llvm_out = &builder.llvm_out(builder.config.build); let target_llvm_out = &builder.llvm_out(target); @@ -2027,117 +2024,6 @@ pub fn maybe_install_llvm_runtime(builder: &Builder<'_>, target: TargetSelection } } -/// Creates an output path to a BOLT-manipulated artifact for the given `file`. -/// The hash of the file is used to make sure that we don't mix BOLT artifacts amongst different -/// files with the same name. -/// -/// We need to keep the file-name the same though, to make sure that copying the manipulated file -/// to a directory will not change the final file path. -fn create_bolt_output_path(builder: &Builder<'_>, file: &Path, hash: &str) -> PathBuf { - let directory = builder.out.join("bolt").join(hash); - t!(fs::create_dir_all(&directory)); - directory.join(file.file_name().unwrap()) -} - -/// Instrument the provided file with BOLT. -/// Returns a path to the instrumented artifact. -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct BoltInstrument { - file: PathBuf, - hash: String, -} - -impl BoltInstrument { - fn new(file: PathBuf) -> Self { - let mut hasher = sha2::Sha256::new(); - hasher.update(t!(fs::read(&file))); - let hash = hex::encode(hasher.finalize().as_slice()); - - Self { file, hash } - } -} - -impl Step for BoltInstrument { - type Output = PathBuf; - - const ONLY_HOSTS: bool = false; - const DEFAULT: bool = false; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.never() - } - - fn run(self, builder: &Builder<'_>) -> PathBuf { - if builder.build.config.dry_run() { - return self.file.clone(); - } - - if builder.build.config.llvm_from_ci { - println!("warning: trying to use BOLT with LLVM from CI, this will probably not work"); - } - - println!("Instrumenting {} with BOLT", self.file.display()); - - let output_path = create_bolt_output_path(builder, &self.file, &self.hash); - if !output_path.is_file() { - instrument_with_bolt(&self.file, &output_path); - } - output_path - } -} - -/// Optimize the provided file with BOLT. -/// Returns a path to the optimized artifact. -/// -/// The hash is stored in the step to make sure that we don't optimize the same file -/// twice (even under different file paths). -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct BoltOptimize { - file: PathBuf, - profile: PathBuf, - hash: String, -} - -impl BoltOptimize { - fn new(file: PathBuf, profile: PathBuf) -> Self { - let mut hasher = sha2::Sha256::new(); - hasher.update(t!(fs::read(&file))); - hasher.update(t!(fs::read(&profile))); - let hash = hex::encode(hasher.finalize().as_slice()); - - Self { file, profile, hash } - } -} - -impl Step for BoltOptimize { - type Output = PathBuf; - - const ONLY_HOSTS: bool = false; - const DEFAULT: bool = false; - - fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.never() - } - - fn run(self, builder: &Builder<'_>) -> PathBuf { - if builder.build.config.dry_run() { - return self.file.clone(); - } - - if builder.build.config.llvm_from_ci { - println!("warning: trying to use BOLT with LLVM from CI, this will probably not work"); - } - - println!("Optimizing {} with BOLT", self.file.display()); - - let output_path = create_bolt_output_path(builder, &self.file, &self.hash); - if !output_path.is_file() { - optimize_with_bolt(&self.file, &self.profile, &output_path); - } - output_path - } -} - #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct LlvmTools { pub target: TargetSelection, @@ -2164,7 +2050,7 @@ impl Step for LlvmTools { /* run only if llvm-config isn't used */ if let Some(config) = builder.config.target_config.get(&target) { if let Some(ref _s) = config.llvm_config { - builder.info(&format!("Skipping LlvmTools ({}): external LLVM", target)); + builder.info(&format!("Skipping LlvmTools ({target}): external LLVM")); return None; } } @@ -2220,7 +2106,7 @@ impl Step for RustDev { /* run only if llvm-config isn't used */ if let Some(config) = builder.config.target_config.get(&target) { if let Some(ref _s) = config.llvm_config { - builder.info(&format!("Skipping RustDev ({}): external LLVM", target)); + builder.info(&format!("Skipping RustDev ({target}): external LLVM")); return None; } } @@ -2379,8 +2265,8 @@ impl Step for ReproducibleArtifacts { tarball.add_file(path, ".", 0o644); added_anything = true; } - if let Some(path) = builder.config.llvm_bolt_profile_use.as_ref() { - tarball.add_file(path, ".", 0o644); + for profile in &builder.config.reproducible_artifacts { + tarball.add_file(profile, ".", 0o644); added_anything = true; } if added_anything { Some(tarball.generate()) } else { None } diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs index 8f5d9bb66e103..505f06ed12d74 100644 --- a/src/bootstrap/doc.rs +++ b/src/bootstrap/doc.rs @@ -7,18 +7,16 @@ //! Everything here is basically just a shim around calling either `rustbook` or //! `rustdoc`. -use std::ffi::OsStr; use std::fs; -use std::io; use std::path::{Path, PathBuf}; use crate::builder::crate_description; -use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step}; +use crate::builder::{Alias, Builder, Compiler, Kind, RunConfig, ShouldRun, Step}; use crate::cache::{Interned, INTERNER}; use crate::compile; use crate::config::{Config, TargetSelection}; use crate::tool::{self, prepare_tool_cargo, SourceType, Tool}; -use crate::util::{symlink_dir, t, up_to_date}; +use crate::util::{dir_is_empty, symlink_dir, t, up_to_date}; use crate::Mode; macro_rules! submodule_helper { @@ -87,15 +85,6 @@ book!( StyleGuide, "src/doc/style-guide", "style-guide"; ); -// "library/std" -> ["library", "std"] -// -// Used for deciding whether a particular step is one requested by the user on -// the `x.py doc` command line, which determines whether `--open` will open that -// page. -pub(crate) fn components_simplified(path: &PathBuf) -> Vec<&str> { - path.iter().map(|component| component.to_str().unwrap_or("???")).collect() -} - #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct UnstableBook { target: TargetSelection, @@ -158,7 +147,7 @@ impl Step for RustbookSrc

{ if !builder.config.dry_run() && !(up_to_date(&src, &index) || up_to_date(&rustbook, &index)) { - builder.info(&format!("Rustbook ({}) - {}", target, name)); + builder.info(&format!("Rustbook ({target}) - {name}")); let _ = fs::remove_dir_all(&out); builder.run(rustbook_cmd.arg("build").arg(&src).arg("-d").arg(out)); @@ -208,11 +197,21 @@ impl Step for TheBook { let compiler = self.compiler; let target = self.target; + let absolute_path = builder.src.join(&relative_path); + let redirect_path = absolute_path.join("redirects"); + if !absolute_path.exists() + || !redirect_path.exists() + || dir_is_empty(&absolute_path) + || dir_is_empty(&redirect_path) + { + eprintln!("Please checkout submodule: {}", relative_path.display()); + crate::exit!(1); + } // build book builder.ensure(RustbookSrc { target, name: INTERNER.intern_str("book"), - src: INTERNER.intern_path(builder.src.join(&relative_path)), + src: INTERNER.intern_path(absolute_path.clone()), parent: Some(self), }); @@ -220,8 +219,8 @@ impl Step for TheBook { for edition in &["first-edition", "second-edition", "2018-edition"] { builder.ensure(RustbookSrc { target, - name: INTERNER.intern_string(format!("book/{}", edition)), - src: INTERNER.intern_path(builder.src.join(&relative_path).join(edition)), + name: INTERNER.intern_string(format!("book/{edition}")), + src: INTERNER.intern_path(absolute_path.join(edition)), // There should only be one book that is marked as the parent for each target, so // treat the other editions as not having a parent. parent: Option::::None, @@ -231,9 +230,12 @@ impl Step for TheBook { // build the version info page and CSS let shared_assets = builder.ensure(SharedAssets { target }); + // build the command first so we don't nest GHA groups + builder.rustdoc_cmd(compiler); + // build the redirect pages - builder.info(&format!("Documenting book redirect pages ({})", target)); - for file in t!(fs::read_dir(builder.src.join(&relative_path).join("redirects"))) { + let _guard = builder.msg_doc(compiler, "book redirect pages", target); + for file in t!(fs::read_dir(redirect_path)) { let file = t!(file); let path = file.path(); let path = path.to_str().unwrap(); @@ -316,7 +318,7 @@ impl Step for Standalone { fn run(self, builder: &Builder<'_>) { let target = self.target; let compiler = self.compiler; - builder.info(&format!("Documenting standalone ({})", target)); + let _guard = builder.msg_doc(compiler, "standalone", target); let out = builder.doc_out(target); t!(fs::create_dir_all(&out)); @@ -425,11 +427,28 @@ impl Step for SharedAssets { } } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Std { pub stage: u32, pub target: TargetSelection, pub format: DocumentationFormat, + crates: Interned>, +} + +impl Std { + pub(crate) fn new( + stage: u32, + target: TargetSelection, + builder: &Builder<'_>, + format: DocumentationFormat, + ) -> Self { + let crates = builder + .in_tree_crates("sysroot", Some(target)) + .into_iter() + .map(|krate| krate.name.to_string()) + .collect(); + Std { stage, target, format, crates: INTERNER.intern_list(crates) } + } } impl Step for Std { @@ -438,7 +457,7 @@ impl Step for Std { fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { let builder = run.builder; - run.all_krates("sysroot").path("library").default_condition(builder.config.docs) + run.crate_or_deps("sysroot").path("library").default_condition(builder.config.docs) } fn make_run(run: RunConfig<'_>) { @@ -450,6 +469,7 @@ impl Step for Std { } else { DocumentationFormat::HTML }, + crates: run.make_run_crates(Alias::Library), }); } @@ -471,57 +491,41 @@ impl Step for Std { builder.ensure(SharedAssets { target: self.target }); } - let index_page = builder.src.join("src/doc/index.md").into_os_string(); + let index_page = builder + .src + .join("src/doc/index.md") + .into_os_string() + .into_string() + .expect("non-utf8 paths are unsupported"); let mut extra_args = match self.format { - DocumentationFormat::HTML => vec![ - OsStr::new("--markdown-css"), - OsStr::new("rust.css"), - OsStr::new("--markdown-no-toc"), - OsStr::new("--index-page"), - &index_page, - ], - DocumentationFormat::JSON => vec![OsStr::new("--output-format"), OsStr::new("json")], + DocumentationFormat::HTML => { + vec!["--markdown-css", "rust.css", "--markdown-no-toc", "--index-page", &index_page] + } + DocumentationFormat::JSON => vec!["--output-format", "json"], }; if !builder.config.docs_minification { - extra_args.push(OsStr::new("--disable-minification")); + extra_args.push("--disable-minification"); } - let requested_crates = builder - .paths - .iter() - .map(components_simplified) - .filter_map(|path| { - if path.len() >= 2 && path.get(0) == Some(&"library") { - // single crate - Some(path[1].to_owned()) - } else if !path.is_empty() { - // ?? - Some(path[0].to_owned()) - } else { - // all library crates - None - } - }) - .collect::>(); - - doc_std(builder, self.format, stage, target, &out, &extra_args, &requested_crates); + doc_std(builder, self.format, stage, target, &out, &extra_args, &self.crates); // Don't open if the format is json if let DocumentationFormat::JSON = self.format { return; } - // Look for library/std, library/core etc in the `x.py doc` arguments and - // open the corresponding rendered docs. - for requested_crate in requested_crates { - if requested_crate == "library" { - // For `x.py doc library --open`, open `std` by default. - let index = out.join("std").join("index.html"); - builder.open_in_browser(index); - } else if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) { - let index = out.join(requested_crate).join("index.html"); - builder.open_in_browser(index); + if builder.paths.iter().any(|path| path.ends_with("library")) { + // For `x.py doc library --open`, open `std` by default. + let index = out.join("std").join("index.html"); + builder.open_in_browser(index); + } else { + for requested_crate in &*self.crates { + if STD_PUBLIC_CRATES.iter().any(|&k| k == requested_crate) { + let index = out.join(requested_crate).join("index.html"); + builder.open_in_browser(index); + break; + } } } } @@ -538,7 +542,7 @@ impl Step for Std { /// or remote link. const STD_PUBLIC_CRATES: [&str; 5] = ["core", "alloc", "std", "proc_macro", "test"]; -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum DocumentationFormat { HTML, JSON, @@ -554,31 +558,22 @@ impl DocumentationFormat { } /// Build the documentation for public standard library crates. -/// -/// `requested_crates` can be used to build only a subset of the crates. If empty, all crates will -/// be built. fn doc_std( builder: &Builder<'_>, format: DocumentationFormat, stage: u32, target: TargetSelection, out: &Path, - extra_args: &[&OsStr], + extra_args: &[&str], requested_crates: &[String], ) { - builder.info(&format!( - "Documenting{} stage{} library ({}) in {} format", - crate_description(requested_crates), - stage, - target, - format.as_str() - )); if builder.no_std(target) == Some(true) { panic!( "building std documentation for no_std target {target} is not supported\n\ - Set `docs = false` in the config to disable documentation." + Set `docs = false` in the config to disable documentation, or pass `--skip library`." ); } + let compiler = builder.compiler(stage, builder.config.build); let target_doc_dir_name = if format == DocumentationFormat::JSON { "json-doc" } else { "doc" }; @@ -590,35 +585,38 @@ fn doc_std( // as a function parameter. let out_dir = target_dir.join(target.triple).join("doc"); - let run_cargo_rustdoc_for = |package: &str| { - let mut cargo = builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "rustdoc"); - compile::std_cargo(builder, target, compiler.stage, &mut cargo); - cargo - .arg("--target-dir") - .arg(&*target_dir.to_string_lossy()) - .arg("-p") - .arg(package) - .arg("-Zskip-rustdoc-fingerprint") - .arg("--") - .arg("-Z") - .arg("unstable-options") - .arg("--resource-suffix") - .arg(&builder.version) - .args(extra_args); - if builder.config.library_docs_private_items { - cargo.arg("--document-private-items").arg("--document-hidden-items"); - } - builder.run(&mut cargo.into()); - }; + let mut cargo = builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "doc"); + compile::std_cargo(builder, target, compiler.stage, &mut cargo); + cargo + .arg("--no-deps") + .arg("--target-dir") + .arg(&*target_dir.to_string_lossy()) + .arg("-Zskip-rustdoc-fingerprint") + .rustdocflag("-Z") + .rustdocflag("unstable-options") + .rustdocflag("--resource-suffix") + .rustdocflag(&builder.version); + for arg in extra_args { + cargo.rustdocflag(arg); + } + + if builder.config.library_docs_private_items { + cargo.rustdocflag("--document-private-items").rustdocflag("--document-hidden-items"); + } - for krate in STD_PUBLIC_CRATES { - run_cargo_rustdoc_for(krate); - if requested_crates.iter().any(|p| p == krate) { - // No need to document more of the libraries if we have the one we want. - break; + for krate in requested_crates { + if krate == "sysroot" { + // The sysroot crate is an implementation detail, don't include it in public docs. + continue; } + cargo.arg("-p").arg(krate); } + let description = + format!("library{} in {} format", crate_description(&requested_crates), format.as_str()); + let _guard = builder.msg_doc(compiler, &description, target); + + builder.run(&mut cargo.into()); builder.cp_r(&out_dir, &out); } @@ -626,6 +624,18 @@ fn doc_std( pub struct Rustc { pub stage: u32, pub target: TargetSelection, + crates: Interned>, +} + +impl Rustc { + pub(crate) fn new(stage: u32, target: TargetSelection, builder: &Builder<'_>) -> Self { + let crates = builder + .in_tree_crates("rustc-main", Some(target)) + .into_iter() + .map(|krate| krate.name.to_string()) + .collect(); + Self { stage, target, crates: INTERNER.intern_list(crates) } + } } impl Step for Rustc { @@ -641,7 +651,11 @@ impl Step for Rustc { } fn make_run(run: RunConfig<'_>) { - run.builder.ensure(Rustc { stage: run.builder.top_stage, target: run.target }); + run.builder.ensure(Rustc { + stage: run.builder.top_stage, + target: run.target, + crates: run.make_run_crates(Alias::Compiler), + }); } /// Generates compiler documentation. @@ -654,15 +668,6 @@ impl Step for Rustc { let stage = self.stage; let target = self.target; - let paths = builder - .paths - .iter() - .filter(|path| { - let components = components_simplified(path); - components.len() >= 2 && components[0] == "compiler" - }) - .collect::>(); - // This is the intended out directory for compiler documentation. let out = builder.compiler_doc_out(target); t!(fs::create_dir_all(&out)); @@ -672,7 +677,13 @@ impl Step for Rustc { let compiler = builder.compiler(stage, builder.config.build); builder.ensure(compile::Std::new(compiler, builder.config.build)); - builder.info(&format!("Documenting stage{} compiler ({})", stage, target)); + let _guard = builder.msg_sysroot_tool( + Kind::Doc, + stage, + &format!("compiler{}", crate_description(&self.crates)), + compiler.host, + target, + ); // This uses a shared directory so that librustdoc documentation gets // correctly built and merged with the rustc documentation. This is @@ -680,11 +691,12 @@ impl Step for Rustc { // rustc. rustdoc needs to be able to see everything, for example when // merging the search index, or generating local (relative) links. let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target.triple).join("doc"); - t!(symlink_dir_force(&builder.config, &out, &out_dir)); + t!(fs::create_dir_all(out_dir.parent().unwrap())); + symlink_dir_force(&builder.config, &out, &out_dir); // Cargo puts proc macros in `target/doc` even if you pass `--target` // explicitly (https://github.com/rust-lang/cargo/issues/7677). let proc_macro_out_dir = builder.stage_out(compiler, Mode::Rustc).join("doc"); - t!(symlink_dir_force(&builder.config, &out, &proc_macro_out_dir)); + symlink_dir_force(&builder.config, &out, &proc_macro_out_dir); // Build cargo command. let mut cargo = builder.cargo(compiler, Mode::Rustc, SourceType::InTree, target, "doc"); @@ -710,35 +722,28 @@ impl Step for Rustc { cargo.rustdocflag("--extern-html-root-url"); cargo.rustdocflag("ena=https://docs.rs/ena/latest/"); - let root_crates = if paths.is_empty() { - vec![ - INTERNER.intern_str("rustc_driver"), - INTERNER.intern_str("rustc_codegen_llvm"), - INTERNER.intern_str("rustc_codegen_ssa"), - ] - } else { - paths.into_iter().map(|p| builder.crate_paths[p]).collect() - }; - // Find dependencies for top level crates. - let compiler_crates = root_crates.iter().flat_map(|krate| { - builder.in_tree_crates(krate, Some(target)).into_iter().map(|krate| krate.name) - }); - let mut to_open = None; - for krate in compiler_crates { + + for krate in &*self.crates { // Create all crate output directories first to make sure rustdoc uses // relative links. // FIXME: Cargo should probably do this itself. - t!(fs::create_dir_all(out_dir.join(krate))); + let dir_name = krate.replace("-", "_"); + t!(fs::create_dir_all(out_dir.join(&*dir_name))); cargo.arg("-p").arg(krate); if to_open.is_none() { - to_open = Some(krate); + to_open = Some(dir_name); } } builder.run(&mut cargo.into()); - // Let's open the first crate documentation page: - if let Some(krate) = to_open { + + if builder.paths.iter().any(|path| path.ends_with("compiler")) { + // For `x.py doc compiler --open`, open `rustc_middle` by default. + let index = out.join("rustc_middle").join("index.html"); + builder.open_in_browser(index); + } else if let Some(krate) = to_open { + // Let's open the first crate documentation page: let index = out.join(krate).join("index.html"); builder.open_in_browser(index); } @@ -746,7 +751,15 @@ impl Step for Rustc { } macro_rules! tool_doc { - ($tool: ident, $should_run: literal, $path: literal, $(rustc_tool = $rustc_tool:literal, )? $(in_tree = $in_tree:literal, )? [$($krate: literal),+ $(,)?] $(,)?) => { + ( + $tool: ident, + $should_run: literal, + $path: literal, + $(rustc_tool = $rustc_tool:literal, )? + $(in_tree = $in_tree:literal, )? + [$($extra_arg: literal),+ $(,)?] + $(,)? + ) => { #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct $tool { target: TargetSelection, @@ -785,7 +798,7 @@ macro_rules! tool_doc { if true $(&& $rustc_tool)? { // Build rustc docs so that we generate relative links. - builder.ensure(Rustc { stage, target }); + builder.ensure(Rustc::new(stage, target, builder)); // Rustdoc needs the rustc sysroot available to build. // FIXME: is there a way to only ensure `check::Rustc` here? Last time I tried it failed @@ -799,15 +812,6 @@ macro_rules! tool_doc { SourceType::Submodule }; - builder.info( - &format!( - "Documenting stage{} {} ({})", - stage, - stringify!($tool).to_lowercase(), - target, - ), - ); - // Symlink compiler docs to the output directory of rustdoc documentation. let out_dirs = [ builder.stage_out(compiler, Mode::ToolRustc).join(target.triple).join("doc"), @@ -816,7 +820,7 @@ macro_rules! tool_doc { ]; for out_dir in out_dirs { t!(fs::create_dir_all(&out_dir)); - t!(symlink_dir_force(&builder.config, &out, &out_dir)); + symlink_dir_force(&builder.config, &out, &out_dir); } // Build cargo command. @@ -834,30 +838,40 @@ macro_rules! tool_doc { cargo.arg("-Zskip-rustdoc-fingerprint"); // Only include compiler crates, no dependencies of those, such as `libc`. cargo.arg("--no-deps"); + $( - cargo.arg("-p").arg($krate); + cargo.arg($extra_arg); )+ cargo.rustdocflag("--document-private-items"); + // Since we always pass --document-private-items, there's no need to warn about linking to private items. + cargo.rustdocflag("-Arustdoc::private-intra-doc-links"); cargo.rustdocflag("--enable-index-page"); cargo.rustdocflag("--show-type-layout"); cargo.rustdocflag("--generate-link-to-definition"); cargo.rustdocflag("-Zunstable-options"); + + let _guard = builder.msg_doc(compiler, stringify!($tool).to_lowercase(), target); builder.run(&mut cargo.into()); } } } } -tool_doc!(Rustdoc, "rustdoc-tool", "src/tools/rustdoc", ["rustdoc", "rustdoc-json-types"],); +tool_doc!( + Rustdoc, + "rustdoc-tool", + "src/tools/rustdoc", + ["-p", "rustdoc", "-p", "rustdoc-json-types"] +); tool_doc!( Rustfmt, "rustfmt-nightly", "src/tools/rustfmt", - ["rustfmt-nightly", "rustfmt-config_proc_macro"], + ["-p", "rustfmt-nightly", "-p", "rustfmt-config_proc_macro"], ); -tool_doc!(Clippy, "clippy", "src/tools/clippy", ["clippy_utils"]); -tool_doc!(Miri, "miri", "src/tools/miri", ["miri"]); +tool_doc!(Clippy, "clippy", "src/tools/clippy", ["-p", "clippy_utils"]); +tool_doc!(Miri, "miri", "src/tools/miri", ["-p", "miri"]); tool_doc!( Cargo, "cargo", @@ -865,24 +879,35 @@ tool_doc!( rustc_tool = false, in_tree = false, [ + "-p", "cargo", + "-p", "cargo-platform", + "-p", "cargo-util", + "-p", "crates-io", + "-p", "cargo-test-macro", + "-p", "cargo-test-support", + "-p", "cargo-credential", - "cargo-credential-1password", + "-p", "mdman", // FIXME: this trips a license check in tidy. + // "-p", // "resolver-tests", - // FIXME: we should probably document these, but they're different per-platform so we can't use `tool_doc`. - // "cargo-credential-gnome-secret", - // "cargo-credential-macos-keychain", - // "cargo-credential-wincred", ] ); -tool_doc!(Tidy, "tidy", "src/tools/tidy", ["tidy"]); +tool_doc!(Tidy, "tidy", "src/tools/tidy", rustc_tool = false, ["-p", "tidy"]); +tool_doc!( + Bootstrap, + "bootstrap", + "src/bootstrap", + rustc_tool = false, + ["--lib", "-p", "bootstrap"] +); #[derive(Ord, PartialOrd, Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct ErrorIndex { @@ -941,7 +966,7 @@ impl Step for UnstableBookGen { fn run(self, builder: &Builder<'_>) { let target = self.target; - builder.info(&format!("Generating unstable book md files ({})", target)); + builder.info(&format!("Generating unstable book md files ({target})")); let out = builder.md_doc_out(target).join("unstable-book"); builder.create_dir(&out); builder.remove_dir(&out); @@ -955,21 +980,24 @@ impl Step for UnstableBookGen { } } -fn symlink_dir_force(config: &Config, src: &Path, dst: &Path) -> io::Result<()> { +fn symlink_dir_force(config: &Config, original: &Path, link: &Path) { if config.dry_run() { - return Ok(()); + return; } - if let Ok(m) = fs::symlink_metadata(dst) { + if let Ok(m) = fs::symlink_metadata(link) { if m.file_type().is_dir() { - fs::remove_dir_all(dst)?; + t!(fs::remove_dir_all(link)); } else { // handle directory junctions on windows by falling back to // `remove_dir`. - fs::remove_file(dst).or_else(|_| fs::remove_dir(dst))?; + t!(fs::remove_file(link).or_else(|_| fs::remove_dir(link))); } } - symlink_dir(config, src, dst) + t!( + symlink_dir(config, original, link), + format!("failed to create link from {} -> {}", link.display(), original.display()) + ); } #[derive(Ord, PartialOrd, Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -1036,7 +1064,16 @@ impl Step for RustcBook { // config.toml), then this needs to explicitly update the dylib search // path. builder.add_rustc_lib_path(self.compiler, &mut cmd); + let doc_generator_guard = builder.msg( + Kind::Run, + self.compiler.stage, + "lint-docs", + self.compiler.host, + self.target, + ); builder.run(&mut cmd); + drop(doc_generator_guard); + // Run rustbook/mdbook to generate the HTML pages. builder.ensure(RustbookSrc { target: self.target, diff --git a/src/bootstrap/download-ci-llvm-stamp b/src/bootstrap/download-ci-llvm-stamp index 4111b7cc0d3f9..ffc38057900ad 100644 --- a/src/bootstrap/download-ci-llvm-stamp +++ b/src/bootstrap/download-ci-llvm-stamp @@ -1,4 +1,4 @@ Change this file to make users of the `download-ci-llvm` configuration download a new version of LLVM from CI, even if the LLVM submodule hasn’t changed. -Last change is for: https://github.com/rust-lang/rust/pull/96971 +Last change is for: https://github.com/rust-lang/rust/pull/113996 diff --git a/src/bootstrap/download.rs b/src/bootstrap/download.rs index 25df5b2573b96..a4135b06e9dea 100644 --- a/src/bootstrap/download.rs +++ b/src/bootstrap/download.rs @@ -7,6 +7,7 @@ use std::{ process::{Command, Stdio}, }; +use build_helper::ci::CiEnv; use once_cell::sync::OnceCell; use xz2::bufread::XzDecoder; @@ -14,12 +15,18 @@ use crate::{ config::RustfmtMetadata, llvm::detect_llvm_sha, t, - util::{check_run, exe, program_out_of_date, try_run}, + util::{check_run, exe, program_out_of_date}, Config, }; static SHOULD_FIX_BINS_AND_DYLIBS: OnceCell = OnceCell::new(); +/// `Config::try_run` wrapper for this module to avoid warnings on `try_run`, since we don't have access to a `builder` yet. +fn try_run(config: &Config, cmd: &mut Command) -> Result<(), ()> { + #[allow(deprecated)] + config.try_run(cmd) +} + /// Generic helpers that are useful anywhere in bootstrap. impl Config { pub fn is_verbose(&self) -> bool { @@ -50,17 +57,6 @@ impl Config { tmp } - /// Runs a command, printing out nice contextual information if it fails. - /// Exits if the command failed to execute at all, otherwise returns its - /// `status.success()`. - pub(crate) fn try_run(&self, cmd: &mut Command) -> bool { - if self.dry_run() { - return true; - } - self.verbose(&format!("running: {:?}", cmd)); - try_run(cmd, self.is_verbose()) - } - /// Runs a command, printing out nice contextual information if it fails. /// Returns false if do not execute at all, otherwise returns its /// `status.success()`. @@ -68,7 +64,7 @@ impl Config { if self.dry_run() { return true; } - self.verbose(&format!("running: {:?}", cmd)); + self.verbose(&format!("running: {cmd:?}")); check_run(cmd, self.is_verbose()) } @@ -123,7 +119,7 @@ impl Config { /// This is only required on NixOS and uses the PatchELF utility to /// change the interpreter/RPATH of ELF executables. /// - /// Please see https://nixos.org/patchelf.html for more information + /// Please see for more information fn fix_bin_or_dylib(&self, fname: &Path) { assert_eq!(SHOULD_FIX_BINS_AND_DYLIBS.get(), Some(&true)); println!("attempting to patch {}", fname.display()); @@ -155,12 +151,16 @@ impl Config { ]; } "; - nix_build_succeeded = self.try_run(Command::new("nix-build").args(&[ - Path::new("-E"), - Path::new(NIX_EXPR), - Path::new("-o"), - &nix_deps_dir, - ])); + nix_build_succeeded = try_run( + self, + Command::new("nix-build").args(&[ + Path::new("-E"), + Path::new(NIX_EXPR), + Path::new("-o"), + &nix_deps_dir, + ]), + ) + .is_ok(); nix_deps_dir }); if !nix_build_succeeded { @@ -185,7 +185,7 @@ impl Config { patchelf.args(&["--set-interpreter", dynamic_linker.trim_end()]); } - self.try_run(patchelf.arg(fname)); + let _ = try_run(self, patchelf.arg(fname)); } fn download_file(&self, url: &str, dest_path: &Path, help_on_error: &str) { @@ -206,11 +206,10 @@ impl Config { } fn download_http_with_retries(&self, tempfile: &Path, url: &str, help_on_error: &str) { - println!("downloading {}", url); + println!("downloading {url}"); // Try curl. If that fails and we are on windows, fallback to PowerShell. let mut curl = Command::new("curl"); curl.args(&[ - "-#", "-y", "30", "-Y", @@ -221,6 +220,12 @@ impl Config { "3", "-SRf", ]); + // Don't print progress in CI; the \r wrapping looks bad and downloads don't take long enough for progress to be useful. + if CiEnv::is_ci() { + curl.arg("-s"); + } else { + curl.arg("--progress-bar"); + } curl.arg(url); let f = File::create(tempfile).unwrap(); curl.stdout(Stdio::from(f)); @@ -228,7 +233,7 @@ impl Config { if self.build.contains("windows-msvc") { eprintln!("Fallback to PowerShell"); for _ in 0..3 { - if self.try_run(Command::new("PowerShell.exe").args(&[ + if try_run(self, Command::new("PowerShell.exe").args(&[ "/nologo", "-Command", "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;", @@ -236,16 +241,16 @@ impl Config { "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')", url, tempfile.to_str().expect("invalid UTF-8 not supported with powershell downloads"), ), - ])) { + ])).is_err() { return; } eprintln!("\nspurious failure, trying again"); } } if !help_on_error.is_empty() { - eprintln!("{}", help_on_error); + eprintln!("{help_on_error}"); } - crate::detail_exit(1); + crate::exit!(1); } } @@ -270,11 +275,8 @@ impl Config { // `compile::Sysroot` needs to know the contents of the `rustc-dev` tarball to avoid adding // it to the sysroot unless it was explicitly requested. But parsing the 100 MB tarball is slow. // Cache the entries when we extract it so we only have to read it once. - let mut recorded_entries = if dst.ends_with("ci-rustc") && pattern == "rustc-dev" { - Some(BufWriter::new(t!(File::create(dst.join(".rustc-dev-contents"))))) - } else { - None - }; + let mut recorded_entries = + if dst.ends_with("ci-rustc") { recorded_entries(dst, pattern) } else { None }; for member in t!(tar.entries()) { let mut member = t!(member); @@ -331,6 +333,17 @@ impl Config { } } +fn recorded_entries(dst: &Path, pattern: &str) -> Option> { + let name = if pattern == "rustc-dev" { + ".rustc-dev-contents" + } else if pattern.starts_with("rust-std") { + ".rust-std-contents" + } else { + return None; + }; + Some(BufWriter::new(t!(File::create(dst.join(name))))) +} + enum DownloadSource { CI, Dist, @@ -381,11 +394,24 @@ impl Config { Some(rustfmt_path) } - pub(crate) fn rustc_dev_contents(&self) -> Vec { + pub(crate) fn ci_rust_std_contents(&self) -> Vec { + self.ci_component_contents(".rust-std-contents") + } + + pub(crate) fn ci_rustc_dev_contents(&self) -> Vec { + self.ci_component_contents(".rustc-dev-contents") + } + + fn ci_component_contents(&self, stamp_file: &str) -> Vec { assert!(self.download_rustc()); - let ci_rustc_dir = self.out.join(&*self.build.triple).join("ci-rustc"); - let rustc_dev_contents_file = t!(File::open(ci_rustc_dir.join(".rustc-dev-contents"))); - t!(BufReader::new(rustc_dev_contents_file).lines().collect()) + if self.dry_run() { + return vec![]; + } + + let ci_rustc_dir = self.ci_rustc_dir(); + let stamp_file = ci_rustc_dir.join(stamp_file); + let contents_file = t!(File::open(&stamp_file), stamp_file.display().to_string()); + t!(BufReader::new(contents_file).lines().collect()) } pub(crate) fn download_ci_rustc(&self, commit: &str) { @@ -399,7 +425,7 @@ impl Config { self.download_toolchain( &version, "ci-rustc", - commit, + &format!("{commit}-{}", self.llvm_assertions), &extra_components, Self::download_ci_component, ); @@ -475,8 +501,15 @@ impl Config { /// Download a single component of a CI-built toolchain (not necessarily a published nightly). // NOTE: intentionally takes an owned string to avoid downloading multiple times by accident - fn download_ci_component(&self, filename: String, prefix: &str, commit: &str) { - Self::download_component(self, DownloadSource::CI, filename, prefix, commit, "ci-rustc") + fn download_ci_component(&self, filename: String, prefix: &str, commit_with_assertions: &str) { + Self::download_component( + self, + DownloadSource::CI, + filename, + prefix, + commit_with_assertions, + "ci-rustc", + ) } fn download_component( @@ -496,11 +529,18 @@ impl Config { let bin_root = self.out.join(self.build.triple).join(destination); let tarball = cache_dir.join(&filename); let (base_url, url, should_verify) = match mode { - DownloadSource::CI => ( - self.stage0_metadata.config.artifacts_server.clone(), - format!("{key}/{filename}"), - false, - ), + DownloadSource::CI => { + let dist_server = if self.llvm_assertions { + self.stage0_metadata.config.artifacts_with_llvm_assertions_server.clone() + } else { + self.stage0_metadata.config.artifacts_server.clone() + }; + let url = format!( + "{}/{filename}", + key.strip_suffix(&format!("-{}", self.llvm_assertions)).unwrap() + ); + (dist_server, url, false) + } DownloadSource::Dist => { let dist_server = env::var("RUSTUP_DIST_SERVER") .unwrap_or(self.stage0_metadata.config.dist_server.to_string()); @@ -606,7 +646,7 @@ download-rustc = false fn download_ci_llvm(&self, llvm_sha: &str) { let llvm_assertions = self.llvm_assertions; - let cache_prefix = format!("llvm-{}-{}", llvm_sha, llvm_assertions); + let cache_prefix = format!("llvm-{llvm_sha}-{llvm_assertions}"); let cache_dst = self.out.join("cache"); let rustc_cache = cache_dst.join(cache_prefix); if !rustc_cache.exists() { diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs index 80e715777984a..e0291e407b34a 100644 --- a/src/bootstrap/flags.rs +++ b/src/bootstrap/flags.rs @@ -68,7 +68,10 @@ pub struct Flags { #[arg(global(true), long, value_name = "PATH")] /// build paths to exclude - pub exclude: Vec, + pub exclude: Vec, // keeping for client backward compatibility + #[arg(global(true), long, value_name = "PATH")] + /// build paths to skip + pub skip: Vec, #[arg(global(true), long)] /// include default paths in addition to the provided ones pub include_default_paths: bool, @@ -149,12 +152,9 @@ pub struct Flags { /// generate PGO profile with llvm built for rustc #[arg(global(true), long)] pub llvm_profile_generate: bool, - /// generate BOLT profile for LLVM build + /// Additional reproducible artifacts that should be added to the reproducible artifacts archive. #[arg(global(true), long)] - pub llvm_bolt_profile_generate: bool, - /// use BOLT profile for LLVM build - #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub llvm_bolt_profile_use: Option, + pub reproducible_artifact: Vec, #[arg(global(true))] /// paths for the subcommand pub paths: Vec, @@ -189,11 +189,11 @@ impl Flags { let build = Build::new(config); let paths = Builder::get_help(&build, subcommand); if let Some(s) = paths { - println!("{}", s); + println!("{s}"); } else { panic!("No paths available for subcommand `{}`", subcommand.as_str()); } - crate::detail_exit(0); + crate::exit!(0); } Flags::parse_from(it) @@ -304,7 +304,7 @@ pub enum Subcommand { ./x.py test library/std --test-args hash_map ./x.py test library/std --stage 0 --no-doc ./x.py test tests/ui --bless - ./x.py test tests/ui --compare-mode chalk + ./x.py test tests/ui --compare-mode next-solver Note that `test tests/* --stage N` does NOT depend on `build compiler/rustc --stage N`; just like `build library/std --stage N` it tests the compiler produced by the previous stage. @@ -321,7 +321,7 @@ pub enum Subcommand { no_fail_fast: bool, #[arg(long, value_name = "SUBSTRING")] /// skips tests matching SUBSTRING, if supported by test tool. May be passed multiple times - skip: Vec, + skip: Vec, #[arg(long, value_name = "ARGS", allow_hyphen_values(true))] /// extra arguments to be passed for the test tool being used /// (e.g. libtest, compiletest or rustdoc) @@ -339,6 +339,10 @@ pub enum Subcommand { /// whether to automatically update stderr/stdout files bless: bool, #[arg(long)] + /// comma-separated list of other files types to check (accepts py, py:lint, + /// py:fmt, shell) + extra_checks: Option, + #[arg(long)] /// rerun tests even if the inputs are unchanged force_rerun: bool, #[arg(long)] @@ -366,7 +370,11 @@ pub enum Subcommand { /// Clean out build directories Clean { #[arg(long)] + /// Clean the entire build directory (not used by default) all: bool, + #[arg(long, value_name = "N")] + /// Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used. + stage: Option, }, /// Build distribution artifacts Dist, @@ -472,6 +480,13 @@ impl Subcommand { } } + pub fn extra_checks(&self) -> Option<&str> { + match *self { + Subcommand::Test { ref extra_checks, .. } => extra_checks.as_ref().map(String::as_str), + _ => None, + } + } + pub fn only_modified(&self) -> bool { match *self { Subcommand::Test { only_modified, .. } => only_modified, @@ -538,7 +553,7 @@ pub fn get_completion(shell: G, path: &Path) -> Opt } else { std::fs::read_to_string(path).unwrap_or_else(|_| { eprintln!("couldn't read {}", path.display()); - crate::detail_exit(1) + crate::exit!(1) }) }; let mut buf = Vec::new(); diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs index d8d3f300a3500..d658be0b8eb1c 100644 --- a/src/bootstrap/format.rs +++ b/src/bootstrap/format.rs @@ -22,7 +22,7 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F cmd.arg("--check"); } cmd.args(paths); - let cmd_debug = format!("{:?}", cmd); + let cmd_debug = format!("{cmd:?}"); let mut cmd = cmd.spawn().expect("running rustfmt"); // poor man's async: return a closure that'll wait for rustfmt's completion move |block: bool| -> bool { @@ -40,7 +40,7 @@ fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl F code, run `./x.py fmt` instead.", cmd_debug, ); - crate::detail_exit(1); + crate::exit!(1); } true } @@ -66,13 +66,17 @@ fn get_rustfmt_version(build: &Builder<'_>) -> Option<(String, PathBuf)> { /// Return whether the format cache can be reused. fn verify_rustfmt_version(build: &Builder<'_>) -> bool { - let Some((version, stamp_file)) = get_rustfmt_version(build) else {return false;}; + let Some((version, stamp_file)) = get_rustfmt_version(build) else { + return false; + }; !program_out_of_date(&stamp_file, &version) } /// Updates the last rustfmt version used fn update_rustfmt_version(build: &Builder<'_>) { - let Some((version, stamp_file)) = get_rustfmt_version(build) else {return;}; + let Some((version, stamp_file)) = get_rustfmt_version(build) else { + return; + }; t!(std::fs::write(stamp_file, version)) } @@ -109,9 +113,9 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { } let rustfmt_config = t!(std::fs::read_to_string(&rustfmt_config)); let rustfmt_config: RustfmtConfig = t!(toml::from_str(&rustfmt_config)); - let mut ignore_fmt = ignore::overrides::OverrideBuilder::new(&build.src); + let mut fmt_override = ignore::overrides::OverrideBuilder::new(&build.src); for ignore in rustfmt_config.ignore { - ignore_fmt.add(&format!("!{}", ignore)).expect(&ignore); + fmt_override.add(&format!("!{ignore}")).expect(&ignore); } let git_available = match Command::new("git") .arg("--version") @@ -148,14 +152,16 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { .map(|entry| { entry.split(' ').nth(1).expect("every git status entry should list a path") }); + let mut untracked_count = 0; for untracked_path in untracked_paths { - println!("skip untracked path {} during rustfmt invocations", untracked_path); + println!("skip untracked path {untracked_path} during rustfmt invocations"); // The leading `/` makes it an exact match against the // repository root, rather than a glob. Without that, if you // have `foo.rs` in the repository root it will also match // against anything like `compiler/rustc_foo/src/foo.rs`, // preventing the latter from being formatted. - ignore_fmt.add(&format!("!/{}", untracked_path)).expect(&untracked_path); + untracked_count += 1; + fmt_override.add(&format!("!/{untracked_path}")).expect(&untracked_path); } // Only check modified files locally to speed up runtime. // We still check all files in CI to avoid bugs in `get_modified_rs_files` letting regressions slip through; @@ -168,10 +174,25 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { println!("formatting modified file {file}"); } } else { - println!("formatting {} modified files", files.len()); + let pluralized = |count| if count > 1 { "files" } else { "file" }; + let untracked_msg = if untracked_count == 0 { + "".to_string() + } else { + format!( + ", skipped {} untracked {}", + untracked_count, + pluralized(untracked_count), + ) + }; + println!( + "formatting {} modified {}{}", + files.len(), + pluralized(files.len()), + untracked_msg + ); } for file in files { - ignore_fmt.add(&format!("/{file}")).expect(&file); + fmt_override.add(&format!("/{file}")).expect(&file); } } Ok(None) => {} @@ -192,11 +213,11 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { println!("Could not find usable git. Skipping git-aware format checks"); } - let ignore_fmt = ignore_fmt.build().unwrap(); + let fmt_override = fmt_override.build().unwrap(); let rustfmt_path = build.initial_rustfmt().unwrap_or_else(|| { eprintln!("./x.py fmt is not supported on this channel"); - crate::detail_exit(1); + crate::exit!(1); }); assert!(rustfmt_path.exists(), "{}", rustfmt_path.display()); let src = build.src.clone(); @@ -248,7 +269,7 @@ pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) { None => WalkBuilder::new(src.clone()), } .types(matcher) - .overrides(ignore_fmt) + .overrides(fmt_override) .build_parallel(); // there is a lot of blocking involved in spawning a child process and reading files to format. diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 6ee50ee657399..a5f3870de7cb1 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -27,6 +27,7 @@ use std::process::{Command, Stdio}; use std::str; use build_helper::ci::{gha, CiEnv}; +use build_helper::exit; use channel::GitInfo; use config::{DryRun, Target}; use filetime::FileTime; @@ -35,10 +36,9 @@ use once_cell::sync::OnceCell; use crate::builder::Kind; use crate::config::{LlvmLibunwind, TargetSelection}; use crate::util::{ - exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed, + dir_is_empty, exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed, }; -mod bolt; mod builder; mod cache; mod cc_detect; @@ -60,6 +60,7 @@ mod run; mod sanity; mod setup; mod suggest; +mod synthetic_targets; mod tarball; mod test; mod tool; @@ -129,9 +130,16 @@ const EXTRA_CHECK_CFGS: &[(Option, &'static str, Option<&[&'static str]>)] (Some(Mode::Std), "freebsd13", None), (Some(Mode::Std), "backtrace_in_libstd", None), /* Extra values not defined in the built-in targets yet, but used in std */ + // #[cfg(bootstrap)] + (Some(Mode::Std), "target_vendor", Some(&["unikraft"])), (Some(Mode::Std), "target_env", Some(&["libnx"])), - // (Some(Mode::Std), "target_os", Some(&[])), - (Some(Mode::Std), "target_arch", Some(&["asmjs", "spirv", "nvptx", "xtensa"])), + (Some(Mode::Std), "target_os", Some(&["teeos"])), + // #[cfg(bootstrap)] mips32r6, mips64r6 + ( + Some(Mode::Std), + "target_arch", + Some(&["asmjs", "spirv", "nvptx", "xtensa", "mips32r6", "mips64r6", "csky"]), + ), /* Extra names used by dependencies */ // FIXME: Used by serde_json, but we should not be triggering on external dependencies. (Some(Mode::Rustc), "no_btreemap_remove_entry", None), @@ -142,6 +150,9 @@ const EXTRA_CHECK_CFGS: &[(Option, &'static str, Option<&[&'static str]>)] // FIXME: Used by proc-macro2, but we should not be triggering on external dependencies. (Some(Mode::Rustc), "span_locations", None), (Some(Mode::ToolRustc), "span_locations", None), + // Can be passed in RUSTFLAGS to prevent direct syscalls in rustix. + (None, "rustix_use_libc", None), + (Some(Mode::Std), "target_vendor", Some(&["wasmer"])), // FIXME: Used by rustix, but we should not be triggering on external dependencies. (Some(Mode::Rustc), "rustix_use_libc", None), (Some(Mode::ToolRustc), "rustix_use_libc", None), @@ -189,7 +200,7 @@ pub enum GitRepo { /// although most functions are implemented as free functions rather than /// methods specifically on this structure itself (to make it easier to /// organize). -#[cfg_attr(not(feature = "build-metrics"), derive(Clone))] +#[derive(Clone)] pub struct Build { /// User-specified configuration from `config.toml`. config: Config, @@ -221,13 +232,14 @@ pub struct Build { initial_cargo: PathBuf, initial_lld: PathBuf, initial_libdir: PathBuf, + initial_sysroot: PathBuf, // Runtime state filled in later on // C/C++ compilers and archiver for all targets - cc: HashMap, - cxx: HashMap, - ar: HashMap, - ranlib: HashMap, + cc: RefCell>, + cxx: RefCell>, + ar: RefCell>, + ranlib: RefCell>, // Miscellaneous // allow bidirectional lookups: both name -> path and path -> name crates: HashMap, Crate>, @@ -330,7 +342,6 @@ forward! { create(path: &Path, s: &str), remove(f: &Path), tempdir() -> PathBuf, - try_run(cmd: &mut Command) -> bool, llvm_link_shared() -> bool, download_rustc() -> bool, initial_rustfmt() -> Option, @@ -388,13 +399,16 @@ impl Build { "/dummy".to_string() } else { output(Command::new(&config.initial_rustc).arg("--print").arg("sysroot")) - }; + } + .trim() + .to_string(); + let initial_libdir = initial_target_dir .parent() .unwrap() .parent() .unwrap() - .strip_prefix(initial_sysroot.trim()) + .strip_prefix(&initial_sysroot) .unwrap() .to_path_buf(); @@ -414,7 +428,6 @@ impl Build { bootstrap_out.display() ) } - config.check_build_rustc_version(); if rust_info.is_from_tarball() && config.description.is_none() { config.description = Some("built from a source tarball".to_owned()); @@ -425,6 +438,7 @@ impl Build { initial_cargo: config.initial_cargo.clone(), initial_lld, initial_libdir, + initial_sysroot: initial_sysroot.into(), local_rebuild: config.local_rebuild, fail_fast: config.cmd.fail_fast(), doc_tests: config.cmd.doc_tests(), @@ -446,10 +460,10 @@ impl Build { miri_info, rustfmt_info, in_tree_llvm_info, - cc: HashMap::new(), - cxx: HashMap::new(), - ar: HashMap::new(), - ranlib: HashMap::new(), + cc: RefCell::new(HashMap::new()), + cxx: RefCell::new(HashMap::new()), + ar: RefCell::new(HashMap::new()), + ranlib: RefCell::new(HashMap::new()), crates: HashMap::new(), crate_paths: HashMap::new(), is_sudo, @@ -472,12 +486,12 @@ impl Build { .unwrap() .trim(); if local_release.split('.').take(2).eq(version.split('.').take(2)) { - build.verbose(&format!("auto-detected local-rebuild {}", local_release)); + build.verbose(&format!("auto-detected local-rebuild {local_release}")); build.local_rebuild = true; } build.verbose("finding compilers"); - cc_detect::find(&mut build); + cc_detect::find(&build); // When running `setup`, the profile is about to change, so any requirements we have now may // be different on the next invocation. Don't check for them until the next time x.py is // run. This is ok because `setup` never runs any build commands, so it won't fail if commands are missing. @@ -525,10 +539,6 @@ impl Build { /// /// `relative_path` should be relative to the root of the git repository, not an absolute path. pub(crate) fn update_submodule(&self, relative_path: &Path) { - fn dir_is_empty(dir: &Path) -> bool { - t!(std::fs::read_dir(dir)).next().is_none() - } - if !self.config.submodules(&self.rust_info()) { return; } @@ -593,6 +603,9 @@ impl Build { let mut git = self.config.git(); if let Some(branch) = current_branch { + // If there is a tag named after the current branch, git will try to disambiguate by prepending `heads/` to the branch name. + // This syntax isn't accepted by `branch.{branch}`. Strip it. + let branch = branch.strip_prefix("heads/").unwrap_or(&branch); git.arg("-c").arg(format!("branch.{branch}.remote=origin")); } git.args(&["submodule", "update", "--init", "--recursive", "--depth=1"]); @@ -608,11 +621,15 @@ impl Build { } // Save any local changes, but avoid running `git stash pop` if there are none (since it will exit with an error). - let has_local_modifications = !self.try_run( - Command::new("git") - .args(&["diff-index", "--quiet", "HEAD"]) - .current_dir(&absolute_path), - ); + #[allow(deprecated)] // diff-index reports the modifications through the exit status + let has_local_modifications = self + .config + .try_run( + Command::new("git") + .args(&["diff-index", "--quiet", "HEAD"]) + .current_dir(&absolute_path), + ) + .is_err(); if has_local_modifications { self.run(Command::new("git").args(&["stash", "push"]).current_dir(&absolute_path)); } @@ -698,9 +715,9 @@ impl Build { if failures.len() > 0 { eprintln!("\n{} command(s) did not execute successfully:\n", failures.len()); for failure in failures.iter() { - eprintln!(" - {}\n", failure); + eprintln!(" - {failure}\n"); } - detail_exit(1); + exit!(1); } #[cfg(feature = "build-metrics")] @@ -777,7 +794,7 @@ impl Build { /// Component directory that Cargo will produce output into (e.g. /// release/debug) fn cargo_dir(&self) -> &'static str { - if self.config.rust_optimize { "release" } else { "debug" } + if self.config.rust_optimize.is_release() { "release" } else { "debug" } } fn tools_dir(&self, compiler: Compiler) -> PathBuf { @@ -811,11 +828,6 @@ impl Build { self.stage_out(compiler, mode).join(&*target.triple).join(self.cargo_dir()) } - /// Directory where the extracted `rustc-dev` component is stored. - fn ci_rustc_dir(&self, target: TargetSelection) -> PathBuf { - self.out.join(&*target.triple).join("ci-rustc") - } - /// Root output directory for LLVM compiled for `target` /// /// Note that if LLVM is configured externally then the directory returned @@ -949,7 +961,7 @@ impl Build { if self.config.dry_run() { return; } - self.verbose(&format!("running: {:?}", cmd)); + self.verbose(&format!("running: {cmd:?}")); run(cmd, self.is_verbose()) } @@ -958,19 +970,43 @@ impl Build { if self.config.dry_run() { return; } - self.verbose(&format!("running: {:?}", cmd)); + self.verbose(&format!("running: {cmd:?}")); run_suppressed(cmd) } /// Runs a command, printing out nice contextual information if it fails. /// Exits if the command failed to execute at all, otherwise returns its /// `status.success()`. - fn try_run_quiet(&self, cmd: &mut Command) -> bool { + fn run_quiet_delaying_failure(&self, cmd: &mut Command) -> bool { if self.config.dry_run() { return true; } - self.verbose(&format!("running: {:?}", cmd)); - try_run_suppressed(cmd) + if !self.fail_fast { + self.verbose(&format!("running: {cmd:?}")); + if !try_run_suppressed(cmd) { + let mut failures = self.delayed_failures.borrow_mut(); + failures.push(format!("{cmd:?}")); + return false; + } + } else { + self.run_quiet(cmd); + } + true + } + + /// Runs a command, printing out contextual info if it fails, and delaying errors until the build finishes. + pub(crate) fn run_delaying_failure(&self, cmd: &mut Command) -> bool { + if !self.fail_fast { + #[allow(deprecated)] // can't use Build::try_run, that's us + if self.config.try_run(cmd).is_err() { + let mut failures = self.delayed_failures.borrow_mut(); + failures.push(format!("{cmd:?}")); + return false; + } + } else { + self.run(cmd); + } + true } pub fn is_verbose_than(&self, level: usize) -> bool { @@ -980,7 +1016,7 @@ impl Build { /// Prints a message if this build is configured in more verbose mode than `level`. fn verbose_than(&self, level: usize, msg: &str) { if self.is_verbose_than(level) { - println!("{}", msg); + println!("{msg}"); } } @@ -988,11 +1024,13 @@ impl Build { match self.config.dry_run { DryRun::SelfCheck => return, DryRun::Disabled | DryRun::UserSelected => { - println!("{}", msg); + println!("{msg}"); } } } + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] fn msg_check( &self, what: impl Display, @@ -1001,6 +1039,19 @@ impl Build { self.msg(Kind::Check, self.config.stage, what, self.config.build, target) } + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] + fn msg_doc( + &self, + compiler: Compiler, + what: impl Display, + target: impl Into> + Copy, + ) -> Option { + self.msg(Kind::Doc, compiler.stage, what, compiler.host, target.into()) + } + + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] fn msg_build( &self, compiler: Compiler, @@ -1011,6 +1062,10 @@ impl Build { } /// Return a `Group` guard for a [`Step`] that is built for each `--stage`. + /// + /// [`Step`]: crate::builder::Step + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] fn msg( &self, action: impl Into, @@ -1019,8 +1074,8 @@ impl Build { host: impl Into>, target: impl Into>, ) -> Option { - let action = action.into(); - let msg = |fmt| format!("{action:?}ing stage{stage} {what}{fmt}"); + let action = action.into().description(); + let msg = |fmt| format!("{action} stage{stage} {what}{fmt}"); let msg = if let Some(target) = target.into() { let host = host.into().unwrap(); if host == target { @@ -1035,17 +1090,23 @@ impl Build { } /// Return a `Group` guard for a [`Step`] that is only built once and isn't affected by `--stage`. + /// + /// [`Step`]: crate::builder::Step + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] fn msg_unstaged( &self, action: impl Into, what: impl Display, target: TargetSelection, ) -> Option { - let action = action.into(); - let msg = format!("{action:?}ing {what} for {target}"); + let action = action.into().description(); + let msg = format!("{action} {what} for {target}"); self.group(&msg) } + #[must_use = "Groups should not be dropped until the Step finishes running"] + #[track_caller] fn msg_sysroot_tool( &self, action: impl Into, @@ -1054,8 +1115,8 @@ impl Build { host: TargetSelection, target: TargetSelection, ) -> Option { - let action = action.into(); - let msg = |fmt| format!("{action:?}ing {what} {fmt}"); + let action = action.into().description(); + let msg = |fmt| format!("{action} {what} {fmt}"); let msg = if host == target { msg(format_args!("(stage{stage} -> stage{}, {target})", stage + 1)) } else { @@ -1064,8 +1125,8 @@ impl Build { self.group(&msg) } + #[track_caller] fn group(&self, msg: &str) -> Option { - self.info(&msg); match self.config.dry_run { DryRun::SelfCheck => None, DryRun::Disabled | DryRun::UserSelected => Some(gha::group(&msg)), @@ -1088,23 +1149,29 @@ impl Build { match which { GitRepo::Rustc => { let sha = self.rust_sha().unwrap_or(&self.version); - Some(format!("/rustc/{}", sha)) + Some(format!("/rustc/{sha}")) } GitRepo::Llvm => Some(String::from("/rustc/llvm")), } } /// Returns the path to the C compiler for the target specified. - fn cc(&self, target: TargetSelection) -> &Path { - self.cc[&target].path() + fn cc(&self, target: TargetSelection) -> PathBuf { + if self.config.dry_run() { + return PathBuf::new(); + } + self.cc.borrow()[&target].path().into() } /// Returns a list of flags to pass to the C compiler for the target /// specified. fn cflags(&self, target: TargetSelection, which: GitRepo, c: CLang) -> Vec { + if self.config.dry_run() { + return Vec::new(); + } let base = match c { - CLang::C => &self.cc[&target], - CLang::Cxx => &self.cxx[&target], + CLang::C => self.cc.borrow()[&target].clone(), + CLang::Cxx => self.cxx.borrow()[&target].clone(), }; // Filter out -O and /O (the optimization flags) that we picked up from @@ -1135,51 +1202,61 @@ impl Build { let map = format!("{}={}", self.src.display(), map_to); let cc = self.cc(target); if cc.ends_with("clang") || cc.ends_with("gcc") { - base.push(format!("-fdebug-prefix-map={}", map)); + base.push(format!("-fdebug-prefix-map={map}")); } else if cc.ends_with("clang-cl.exe") { base.push("-Xclang".into()); - base.push(format!("-fdebug-prefix-map={}", map)); + base.push(format!("-fdebug-prefix-map={map}")); } } base } /// Returns the path to the `ar` archive utility for the target specified. - fn ar(&self, target: TargetSelection) -> Option<&Path> { - self.ar.get(&target).map(|p| &**p) + fn ar(&self, target: TargetSelection) -> Option { + if self.config.dry_run() { + return None; + } + self.ar.borrow().get(&target).cloned() } /// Returns the path to the `ranlib` utility for the target specified. - fn ranlib(&self, target: TargetSelection) -> Option<&Path> { - self.ranlib.get(&target).map(|p| &**p) + fn ranlib(&self, target: TargetSelection) -> Option { + if self.config.dry_run() { + return None; + } + self.ranlib.borrow().get(&target).cloned() } /// Returns the path to the C++ compiler for the target specified. - fn cxx(&self, target: TargetSelection) -> Result<&Path, String> { - match self.cxx.get(&target) { - Some(p) => Ok(p.path()), - None => { - Err(format!("target `{}` is not configured as a host, only as a target", target)) - } + fn cxx(&self, target: TargetSelection) -> Result { + if self.config.dry_run() { + return Ok(PathBuf::new()); + } + match self.cxx.borrow().get(&target) { + Some(p) => Ok(p.path().into()), + None => Err(format!("target `{target}` is not configured as a host, only as a target")), } } /// Returns the path to the linker for the given target if it needs to be overridden. - fn linker(&self, target: TargetSelection) -> Option<&Path> { - if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.as_ref()) + fn linker(&self, target: TargetSelection) -> Option { + if self.config.dry_run() { + return Some(PathBuf::new()); + } + if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone()) { Some(linker) } else if target.contains("vxworks") { // need to use CXX compiler as linker to resolve the exception functions // that are only existed in CXX libraries - Some(self.cxx[&target].path()) + Some(self.cxx.borrow()[&target].path().into()) } else if target != self.config.build && util::use_host_linker(target) && !target.contains("msvc") { Some(self.cc(target)) } else if self.config.use_lld && !self.is_fuse_ld_lld(target) && self.build == target { - Some(&self.initial_lld) + Some(self.initial_lld.clone()) } else { None } @@ -1200,7 +1277,7 @@ impl Build { } let no_threads = util::lld_flag_no_threads(target.contains("windows")); - options[1] = Some(format!("-Clink-arg=-Wl,{}", no_threads)); + options[1] = Some(format!("-Clink-arg=-Wl,{no_threads}")); } IntoIterator::into_iter(options).flatten() @@ -1327,11 +1404,11 @@ impl Build { if !self.config.omit_git_hash { format!("{}-beta.{}", num, self.beta_prerelease_version()) } else { - format!("{}-beta", num) + format!("{num}-beta") } } - "nightly" => format!("{}-nightly", num), - _ => format!("{}-dev", num), + "nightly" => format!("{num}-nightly"), + _ => format!("{num}-dev"), } } @@ -1379,7 +1456,7 @@ impl Build { "stable" => num.to_string(), "beta" => "beta".to_string(), "nightly" => "nightly".to_string(), - _ => format!("{}-dev", num), + _ => format!("{num}-dev"), } } @@ -1410,7 +1487,7 @@ impl Build { /// Returns the `a.b.c` version that the given package is at. fn release_num(&self, package: &str) -> String { - let toml_file_name = self.src.join(&format!("src/tools/{}/Cargo.toml", package)); + let toml_file_name = self.src.join(&format!("src/tools/{package}/Cargo.toml")); let toml = t!(fs::read_to_string(&toml_file_name)); for line in toml.lines() { if let Some(stripped) = @@ -1420,7 +1497,7 @@ impl Build { } } - panic!("failed to find version in {}'s Cargo.toml", package) + panic!("failed to find version in {package}'s Cargo.toml") } /// Returns `true` if unstable features should be enabled for the compiler @@ -1466,6 +1543,7 @@ impl Build { } } } + ret.sort_unstable_by_key(|krate| krate.name); // reproducible order needed for tests ret } @@ -1479,7 +1557,7 @@ impl Build { "Error: Unable to find the stamp file {}, did you try to keep a nonexistent build stage?", stamp.display() ); - crate::detail_exit(1); + crate::exit!(1); } let mut paths = Vec::new(); @@ -1511,12 +1589,12 @@ impl Build { if self.config.dry_run() { return; } - self.verbose_than(1, &format!("Copy {:?} to {:?}", src, dst)); + self.verbose_than(1, &format!("Copy {src:?} to {dst:?}")); if src == dst { return; } let _ = fs::remove_file(&dst); - let metadata = t!(src.symlink_metadata()); + let metadata = t!(src.symlink_metadata().map_err(|err| { eprintln!("Copy {:?} to {:?}", src, dst); err })); let mut src = src.to_path_buf(); if metadata.file_type().is_symlink() { if dereference_symlinks { @@ -1602,7 +1680,7 @@ impl Build { return; } let dst = dstdir.join(src.file_name().unwrap()); - self.verbose_than(1, &format!("Install {:?} to {:?}", src, dst)); + self.verbose_than(1, &format!("Install {src:?} to {dst:?}")); t!(fs::create_dir_all(dstdir)); if !src.exists() { panic!("Error: File \"{}\" not found!", src.display()); @@ -1636,7 +1714,7 @@ impl Build { let iter = match fs::read_dir(dir) { Ok(v) => v, Err(_) if self.config.dry_run() => return vec![].into_iter(), - Err(err) => panic!("could not read dir {:?}: {:?}", dir, err), + Err(err) => panic!("could not read dir {dir:?}: {err:?}"), }; iter.map(|e| t!(e)).collect::>().into_iter() } @@ -1671,7 +1749,7 @@ Alternatively, set `download-ci-llvm = true` in that `[llvm]` section to download LLVM rather than building it. " ); - detail_exit(1); + exit!(1); } } @@ -1736,18 +1814,6 @@ fn chmod(path: &Path, perms: u32) { #[cfg(windows)] fn chmod(_path: &Path, _perms: u32) {} -/// If code is not 0 (successful exit status), exit status is 101 (rust's default error code.) -/// If the test is running and code is an error code, it will cause a panic. -fn detail_exit(code: i32) -> ! { - // if in test and code is an error code, panic with status code provided - if cfg!(test) { - panic!("status code: {}", code); - } else { - // otherwise,exit with provided status code - std::process::exit(code); - } -} - impl Compiler { pub fn with_stage(mut self, stage: u32) -> Compiler { self.stage = stage; diff --git a/src/bootstrap/llvm.rs b/src/bootstrap/llvm.rs index 040a12f5d10a4..c841cb34036ec 100644 --- a/src/bootstrap/llvm.rs +++ b/src/bootstrap/llvm.rs @@ -299,7 +299,7 @@ impl Step for Llvm { let llvm_exp_targets = match builder.config.llvm_experimental_targets { Some(ref s) => s, - None => "AVR;M68k", + None => "AVR;M68k;CSKY", }; let assertions = if builder.config.llvm_assertions { "ON" } else { "OFF" }; @@ -342,17 +342,11 @@ impl Step for Llvm { if let Some(path) = builder.config.llvm_profile_use.as_ref() { cfg.define("LLVM_PROFDATA_FILE", &path); } - if builder.config.llvm_bolt_profile_generate - || builder.config.llvm_bolt_profile_use.is_some() - { - // Relocations are required for BOLT to work. - ldflags.push_all("-Wl,-q"); - } // Disable zstd to avoid a dependency on libzstd.so. cfg.define("LLVM_ENABLE_ZSTD", "OFF"); - if target != "aarch64-apple-darwin" && !target.contains("windows") { + if !target.contains("windows") { cfg.define("LLVM_ENABLE_ZLIB", "ON"); } else { cfg.define("LLVM_ENABLE_ZLIB", "OFF"); @@ -380,9 +374,12 @@ impl Step for Llvm { cfg.define("LLVM_LINK_LLVM_DYLIB", "ON"); } - if target.starts_with("riscv") && !target.contains("freebsd") && !target.contains("openbsd") + if (target.starts_with("riscv") || target.starts_with("csky")) + && !target.contains("freebsd") + && !target.contains("openbsd") + && !target.contains("netbsd") { - // RISC-V GCC erroneously requires linking against + // RISC-V and CSKY GCC erroneously requires linking against // `libatomic` when using 1-byte and 2-byte C++ // atomics but the LLVM build system check cannot // detect this. Therefore it is set manually here. @@ -488,25 +485,50 @@ impl Step for Llvm { cfg.build(); - // When building LLVM with LLVM_LINK_LLVM_DYLIB for macOS, an unversioned - // libLLVM.dylib will be built. However, llvm-config will still look - // for a versioned path like libLLVM-14.dylib. Manually create a symbolic - // link to make llvm-config happy. - if builder.llvm_link_shared() && target.contains("apple-darwin") { + // Helper to find the name of LLVM's shared library on darwin and linux. + let find_llvm_lib_name = |extension| { let mut cmd = Command::new(&res.llvm_config); let version = output(cmd.arg("--version")); let major = version.split('.').next().unwrap(); - let lib_name = match llvm_version_suffix { - Some(s) => format!("libLLVM-{}{}.dylib", major, s), - None => format!("libLLVM-{}.dylib", major), + let lib_name = match &llvm_version_suffix { + Some(version_suffix) => format!("libLLVM-{major}{version_suffix}.{extension}"), + None => format!("libLLVM-{major}.{extension}"), }; + lib_name + }; + // When building LLVM with LLVM_LINK_LLVM_DYLIB for macOS, an unversioned + // libLLVM.dylib will be built. However, llvm-config will still look + // for a versioned path like libLLVM-14.dylib. Manually create a symbolic + // link to make llvm-config happy. + if builder.llvm_link_shared() && target.contains("apple-darwin") { + let lib_name = find_llvm_lib_name("dylib"); let lib_llvm = out_dir.join("build").join("lib").join(lib_name); if !lib_llvm.exists() { t!(builder.symlink_file("libLLVM.dylib", &lib_llvm)); } } + // When building LLVM as a shared library on linux, it can contain unexpected debuginfo: + // some can come from the C++ standard library. Unless we're explicitly requesting LLVM to + // be built with debuginfo, strip it away after the fact, to make dist artifacts smaller. + if builder.llvm_link_shared() + && builder.config.llvm_optimize + && !builder.config.llvm_release_debuginfo + { + // Find the name of the LLVM shared library that we just built. + let lib_name = find_llvm_lib_name("so"); + + // If the shared library exists in LLVM's `/build/lib/` or `/lib/` folders, strip its + // debuginfo. + crate::compile::strip_debug(builder, target, &out_dir.join("lib").join(&lib_name)); + crate::compile::strip_debug( + builder, + target, + &out_dir.join("build").join("lib").join(&lib_name), + ); + } + t!(stamp.write()); res @@ -522,11 +544,11 @@ fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) { let version = output(cmd.arg("--version")); let mut parts = version.split('.').take(2).filter_map(|s| s.parse::().ok()); if let (Some(major), Some(_minor)) = (parts.next(), parts.next()) { - if major >= 14 { + if major >= 15 { return; } } - panic!("\n\nbad LLVM version: {}, need >=14.0\n\n", version) + panic!("\n\nbad LLVM version: {version}, need >=15.0\n\n") } fn configure_cmake( @@ -556,6 +578,8 @@ fn configure_cmake( if target.contains("netbsd") { cfg.define("CMAKE_SYSTEM_NAME", "NetBSD"); + } else if target.contains("dragonfly") { + cfg.define("CMAKE_SYSTEM_NAME", "DragonFly"); } else if target.contains("freebsd") { cfg.define("CMAKE_SYSTEM_NAME", "FreeBSD"); } else if target.contains("windows") { @@ -566,7 +590,12 @@ fn configure_cmake( cfg.define("CMAKE_SYSTEM_NAME", "SunOS"); } else if target.contains("linux") { cfg.define("CMAKE_SYSTEM_NAME", "Linux"); + } else { + builder.info( + "could not determine CMAKE_SYSTEM_NAME from the target `{target}`, build may fail", + ); } + // When cross-compiling we should also set CMAKE_SYSTEM_VERSION, but in // that case like CMake we cannot easily determine system version either. // @@ -605,7 +634,7 @@ fn configure_cmake( } let (cc, cxx) = match builder.config.llvm_clang_cl { - Some(ref cl) => (cl.as_ref(), cl.as_ref()), + Some(ref cl) => (cl.into(), cl.into()), None => (builder.cc(target), builder.cxx(target).unwrap()), }; @@ -656,9 +685,9 @@ fn configure_cmake( .define("CMAKE_CXX_COMPILER_LAUNCHER", ccache); } } - cfg.define("CMAKE_C_COMPILER", sanitize_cc(cc)) - .define("CMAKE_CXX_COMPILER", sanitize_cc(cxx)) - .define("CMAKE_ASM_COMPILER", sanitize_cc(cc)); + cfg.define("CMAKE_C_COMPILER", sanitize_cc(&cc)) + .define("CMAKE_CXX_COMPILER", sanitize_cc(&cxx)) + .define("CMAKE_ASM_COMPILER", sanitize_cc(&cc)); } cfg.build_arg("-j").build_arg(builder.jobs().to_string()); @@ -676,10 +705,10 @@ fn configure_cmake( } } if builder.config.llvm_clang_cl.is_some() { - cflags.push(&format!(" --target={}", target)); + cflags.push(&format!(" --target={target}")); } for flag in extra_compiler_flags { - cflags.push(&format!(" {}", flag)); + cflags.push(&format!(" {flag}")); } cfg.define("CMAKE_C_FLAGS", cflags); let mut cxxflags: OsString = builder.cflags(target, GitRepo::Llvm, CLang::Cxx).join(" ").into(); @@ -688,17 +717,17 @@ fn configure_cmake( cxxflags.push(s); } if builder.config.llvm_clang_cl.is_some() { - cxxflags.push(&format!(" --target={}", target)); + cxxflags.push(&format!(" --target={target}")); } for flag in extra_compiler_flags { - cxxflags.push(&format!(" {}", flag)); + cxxflags.push(&format!(" {flag}")); } cfg.define("CMAKE_CXX_FLAGS", cxxflags); if let Some(ar) = builder.ar(target) { if ar.is_absolute() { // LLVM build breaks if `CMAKE_AR` is a relative path, for some reason it // tries to resolve this path in the LLVM build directory. - cfg.define("CMAKE_AR", sanitize_cc(ar)); + cfg.define("CMAKE_AR", sanitize_cc(&ar)); } } @@ -706,7 +735,7 @@ fn configure_cmake( if ranlib.is_absolute() { // LLVM build breaks if `CMAKE_RANLIB` is a relative path, for some reason it // tries to resolve this path in the LLVM build directory. - cfg.define("CMAKE_RANLIB", sanitize_cc(ranlib)); + cfg.define("CMAKE_RANLIB", sanitize_cc(&ranlib)); } } @@ -762,7 +791,7 @@ fn configure_llvm(builder: &Builder<'_>, target: TargetSelection, cfg: &mut cmak fn get_var(var_base: &str, host: &str, target: &str) -> Option { let kind = if host == target { "HOST" } else { "TARGET" }; let target_u = target.replace("-", "_"); - env::var_os(&format!("{}_{}", var_base, target)) + env::var_os(&format!("{var_base}_{target}")) .or_else(|| env::var_os(&format!("{}_{}", var_base, target_u))) .or_else(|| env::var_os(&format!("{}_{}", kind, var_base))) .or_else(|| env::var_os(var_base)) @@ -834,6 +863,31 @@ impl Step for Lld { } } + // LLD is built as an LLVM tool, but is distributed outside of the `llvm-tools` component, + // which impacts where it expects to find LLVM's shared library. This causes #80703. + // + // LLD is distributed at "$root/lib/rustlib/$host/bin/rust-lld", but the `libLLVM-*.so` it + // needs is distributed at "$root/lib". The default rpath of "$ORIGIN/../lib" points at the + // lib path for LLVM tools, not the one for rust binaries. + // + // (The `llvm-tools` component copies the .so there for the other tools, and with that + // component installed, one can successfully invoke `rust-lld` directly without rustup's + // `LD_LIBRARY_PATH` overrides) + // + if builder.config.rpath_enabled(target) + && util::use_host_linker(target) + && builder.config.llvm_link_shared() + && target.contains("linux") + { + // So we inform LLD where it can find LLVM's libraries by adding an rpath entry to the + // expected parent `lib` directory. + // + // Be careful when changing this path, we need to ensure it's quoted or escaped: + // `$ORIGIN` would otherwise be expanded when the `LdFlags` are passed verbatim to + // cmake. + ldflags.push_all("-Wl,-rpath,'$ORIGIN/../../../'"); + } + configure_cmake(builder, target, &mut cfg, true, ldflags, &[]); configure_llvm(builder, target, &mut cfg); @@ -1017,7 +1071,7 @@ fn supported_sanitizers( "x86_64-unknown-illumos" => common_libs("illumos", "x86_64", &["asan"]), "x86_64-pc-solaris" => common_libs("solaris", "x86_64", &["asan"]), "x86_64-unknown-linux-gnu" => { - common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"]) + common_libs("linux", "x86_64", &["asan", "lsan", "msan", "safestack", "tsan"]) } "x86_64-unknown-linux-musl" => { common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"]) @@ -1028,6 +1082,9 @@ fn supported_sanitizers( "s390x-unknown-linux-musl" => { common_libs("linux", "s390x", &["asan", "lsan", "msan", "tsan"]) } + "x86_64-unknown-linux-ohos" => { + common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"]) + } _ => Vec::new(), } } @@ -1096,8 +1153,8 @@ impl Step for CrtBeginEnd { return out_dir; } - let crtbegin_src = builder.src.join("src/llvm-project/compiler-rt/lib/crt/crtbegin.c"); - let crtend_src = builder.src.join("src/llvm-project/compiler-rt/lib/crt/crtend.c"); + let crtbegin_src = builder.src.join("src/llvm-project/compiler-rt/lib/builtins/crtbegin.c"); + let crtend_src = builder.src.join("src/llvm-project/compiler-rt/lib/builtins/crtend.c"); if up_to_date(&crtbegin_src, &out_dir.join("crtbegin.o")) && up_to_date(&crtend_src, &out_dir.join("crtendS.o")) { diff --git a/src/bootstrap/metadata.rs b/src/bootstrap/metadata.rs index 8f2c3faca3a48..3b20ceac8759f 100644 --- a/src/bootstrap/metadata.rs +++ b/src/bootstrap/metadata.rs @@ -74,6 +74,9 @@ fn workspace_members(build: &Build) -> impl Iterator { let collect_metadata = |manifest_path| { let mut cargo = Command::new(&build.initial_cargo); cargo + // Will read the libstd Cargo.toml + // which uses the unstable `public-dependency` feature. + .env("RUSTC_BOOTSTRAP", "1") .arg("metadata") .arg("--format-version") .arg("1") diff --git a/src/bootstrap/metrics.rs b/src/bootstrap/metrics.rs index e19d56ccd6adc..cf8d33dfcb013 100644 --- a/src/bootstrap/metrics.rs +++ b/src/bootstrap/metrics.rs @@ -7,17 +7,46 @@ use crate::builder::{Builder, Step}; use crate::util::t; use crate::Build; -use serde_derive::{Deserialize, Serialize}; +use build_helper::metrics::{ + JsonInvocation, JsonInvocationSystemStats, JsonNode, JsonRoot, JsonStepSystemStats, Test, + TestOutcome, TestSuite, TestSuiteMetadata, +}; use std::cell::RefCell; use std::fs::File; use std::io::BufWriter; use std::time::{Duration, Instant, SystemTime}; use sysinfo::{CpuExt, System, SystemExt}; +// Update this number whenever a breaking change is made to the build metrics. +// +// The output format is versioned for two reasons: +// +// - The metadata is intended to be consumed by external tooling, and exposing a format version +// helps the tools determine whether they're compatible with a metrics file. +// +// - If a developer enables build metrics in their local checkout, making a breaking change to the +// metrics format would result in a hard-to-diagnose error message when an existing metrics file +// is not compatible with the new changes. With a format version number, bootstrap can discard +// incompatible metrics files instead of appending metrics to them. +// +// Version changelog: +// +// - v0: initial version +// - v1: replaced JsonNode::Test with JsonNode::TestSuite +// +const CURRENT_FORMAT_VERSION: usize = 1; + pub(crate) struct BuildMetrics { state: RefCell, } +/// NOTE: this isn't really cloning anything, but `x suggest` doesn't need metrics so this is probably ok. +impl Clone for BuildMetrics { + fn clone(&self) -> Self { + Self::init() + } +} + impl BuildMetrics { pub(crate) fn init() -> Self { let state = RefCell::new(MetricsState { @@ -57,7 +86,7 @@ impl BuildMetrics { duration_excluding_children_sec: Duration::ZERO, children: Vec::new(), - tests: Vec::new(), + test_suites: Vec::new(), }); } @@ -84,6 +113,17 @@ impl BuildMetrics { } } + pub(crate) fn begin_test_suite(&self, metadata: TestSuiteMetadata, builder: &Builder<'_>) { + // Do not record dry runs, as they'd be duplicates of the actual steps. + if builder.config.dry_run() { + return; + } + + let mut state = self.state.borrow_mut(); + let step = state.running_steps.last_mut().unwrap(); + step.test_suites.push(TestSuite { metadata, tests: Vec::new() }); + } + pub(crate) fn record_test(&self, name: &str, outcome: TestOutcome, builder: &Builder<'_>) { // Do not record dry runs, as they'd be duplicates of the actual steps. if builder.config.dry_run() { @@ -91,12 +131,13 @@ impl BuildMetrics { } let mut state = self.state.borrow_mut(); - state - .running_steps - .last_mut() - .unwrap() - .tests - .push(Test { name: name.to_string(), outcome }); + let step = state.running_steps.last_mut().unwrap(); + + if let Some(test_suite) = step.test_suites.last_mut() { + test_suite.tests.push(Test { name: name.to_string(), outcome }); + } else { + panic!("metrics.record_test() called without calling metrics.begin_test_suite() first"); + } } fn collect_stats(&self, state: &mut MetricsState) { @@ -131,7 +172,20 @@ impl BuildMetrics { // Some of our CI builds consist of multiple independent CI invocations. Ensure all the // previous invocations are still present in the resulting file. let mut invocations = match std::fs::read(&dest) { - Ok(contents) => t!(serde_json::from_slice::(&contents)).invocations, + Ok(contents) => { + // We first parse just the format_version field to have the check succeed even if + // the rest of the contents are not valid anymore. + let version: OnlyFormatVersion = t!(serde_json::from_slice(&contents)); + if version.format_version == CURRENT_FORMAT_VERSION { + t!(serde_json::from_slice::(&contents)).invocations + } else { + println!( + "warning: overriding existing build/metrics.json, as it's not \ + compatible with build metrics format version {CURRENT_FORMAT_VERSION}." + ); + Vec::new() + } + } Err(err) => { if err.kind() != std::io::ErrorKind::NotFound { panic!("failed to open existing metrics file at {}: {err}", dest.display()); @@ -149,7 +203,7 @@ impl BuildMetrics { children: steps.into_iter().map(|step| self.prepare_json_step(step)).collect(), }); - let json = JsonRoot { system_stats, invocations }; + let json = JsonRoot { format_version: CURRENT_FORMAT_VERSION, system_stats, invocations }; t!(std::fs::create_dir_all(dest.parent().unwrap())); let mut file = BufWriter::new(t!(File::create(&dest))); @@ -159,11 +213,7 @@ impl BuildMetrics { fn prepare_json_step(&self, step: StepMetrics) -> JsonNode { let mut children = Vec::new(); children.extend(step.children.into_iter().map(|child| self.prepare_json_step(child))); - children.extend( - step.tests - .into_iter() - .map(|test| JsonNode::Test { name: test.name, outcome: test.outcome }), - ); + children.extend(step.test_suites.into_iter().map(JsonNode::TestSuite)); JsonNode::RustbuildStep { type_: step.type_, @@ -198,71 +248,11 @@ struct StepMetrics { duration_excluding_children_sec: Duration, children: Vec, - tests: Vec, -} - -struct Test { - name: String, - outcome: TestOutcome, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -struct JsonRoot { - system_stats: JsonInvocationSystemStats, - invocations: Vec, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -struct JsonInvocation { - // Unix timestamp in seconds - // - // This is necessary to easily correlate this invocation with logs or other data. - start_time: u64, - duration_including_children_sec: f64, - children: Vec, -} - -#[derive(Serialize, Deserialize)] -#[serde(tag = "kind", rename_all = "snake_case")] -enum JsonNode { - RustbuildStep { - #[serde(rename = "type")] - type_: String, - debug_repr: String, - - duration_excluding_children_sec: f64, - system_stats: JsonStepSystemStats, - - children: Vec, - }, - Test { - name: String, - #[serde(flatten)] - outcome: TestOutcome, - }, -} - -#[derive(Serialize, Deserialize)] -#[serde(tag = "outcome", rename_all = "snake_case")] -pub(crate) enum TestOutcome { - Passed, - Failed, - Ignored { ignore_reason: Option }, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -struct JsonInvocationSystemStats { - cpu_threads_count: usize, - cpu_model: String, - - memory_total_bytes: u64, + test_suites: Vec, } -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -struct JsonStepSystemStats { - cpu_utilization_percent: f64, +#[derive(serde_derive::Deserialize)] +struct OnlyFormatVersion { + #[serde(default)] // For version 0 the field was not present. + format_version: usize, } diff --git a/src/bootstrap/mk/Makefile.in b/src/bootstrap/mk/Makefile.in index d54a21b9f1641..a9865e262fece 100644 --- a/src/bootstrap/mk/Makefile.in +++ b/src/bootstrap/mk/Makefile.in @@ -57,27 +57,22 @@ tidy: prepare: $(Q)$(BOOTSTRAP) build --stage 2 nonexistent/path/to/trigger/cargo/metadata -TESTS_IN_2 := \ - tests/ui \ - src/tools/linkchecker - ## MSVC native builders -# these intentionally don't use `$(BOOTSTRAP)` so we can test the shebang on Windows -ci-subset-1: - $(Q)$(CFG_SRC_DIR)/x.py test --stage 2 $(TESTS_IN_2:%=--exclude %) -ci-subset-2: - $(Q)$(CFG_SRC_DIR)/x.ps1 test --stage 2 $(TESTS_IN_2) +# this intentionally doesn't use `$(BOOTSTRAP)` so we can test the shebang on Windows +ci-msvc-py: + $(Q)$(CFG_SRC_DIR)/x.py test --stage 2 tidy +ci-msvc-ps1: + $(Q)$(CFG_SRC_DIR)/x.ps1 test --stage 2 --skip tidy +ci-msvc: ci-msvc-py ci-msvc-ps1 ## MingW native builders -TESTS_IN_MINGW_2 := \ - tests/ui - -ci-mingw-subset-1: - $(Q)$(CFG_SRC_DIR)/x test --stage 2 $(TESTS_IN_MINGW_2:%=--exclude %) -ci-mingw-subset-2: - $(Q)$(BOOTSTRAP) test --stage 2 $(TESTS_IN_MINGW_2) - +# test both x and bootstrap entrypoints +ci-mingw-x: + $(Q)$(CFG_SRC_DIR)/x test --stage 2 tidy +ci-mingw-bootstrap: + $(Q)$(BOOTSTRAP) test --stage 2 --skip tidy +ci-mingw: ci-mingw-x ci-mingw-bootstrap .PHONY: dist diff --git a/src/bootstrap/render_tests.rs b/src/bootstrap/render_tests.rs index fa0a4806618b7..6802bf4511bcc 100644 --- a/src/bootstrap/render_tests.rs +++ b/src/bootstrap/render_tests.rs @@ -7,7 +7,7 @@ //! to reimplement all the rendering logic in this module because of that. use crate::builder::Builder; -use std::io::{BufRead, BufReader, Write}; +use std::io::{BufRead, BufReader, Read, Write}; use std::process::{ChildStdout, Command, Stdio}; use std::time::Duration; use termcolor::{Color, ColorSpec, WriteColor}; @@ -20,17 +20,17 @@ pub(crate) fn add_flags_and_try_run_tests(builder: &Builder<'_>, cmd: &mut Comma } cmd.args(&["-Z", "unstable-options", "--format", "json"]); - try_run_tests(builder, cmd) + try_run_tests(builder, cmd, false) } -pub(crate) fn try_run_tests(builder: &Builder<'_>, cmd: &mut Command) -> bool { +pub(crate) fn try_run_tests(builder: &Builder<'_>, cmd: &mut Command, stream: bool) -> bool { if builder.config.dry_run() { return true; } - if !run_tests(builder, cmd) { + if !run_tests(builder, cmd, stream) { if builder.fail_fast { - crate::detail_exit(1); + crate::exit!(1); } else { let mut failures = builder.delayed_failures.borrow_mut(); failures.push(format!("{cmd:?}")); @@ -41,7 +41,7 @@ pub(crate) fn try_run_tests(builder: &Builder<'_>, cmd: &mut Command) -> bool { } } -fn run_tests(builder: &Builder<'_>, cmd: &mut Command) -> bool { +fn run_tests(builder: &Builder<'_>, cmd: &mut Command, stream: bool) -> bool { cmd.stdout(Stdio::piped()); builder.verbose(&format!("running: {cmd:?}")); @@ -50,7 +50,12 @@ fn run_tests(builder: &Builder<'_>, cmd: &mut Command) -> bool { // This runs until the stdout of the child is closed, which means the child exited. We don't // run this on another thread since the builder is not Sync. - Renderer::new(process.stdout.take().unwrap(), builder).render_all(); + let renderer = Renderer::new(process.stdout.take().unwrap(), builder); + if stream { + renderer.stream_all(); + } else { + renderer.render_all(); + } let result = process.wait_with_output().unwrap(); if !result.status.success() && builder.is_verbose() { @@ -88,10 +93,10 @@ impl<'a> Renderer<'a> { } fn render_all(mut self) { - let mut line = String::new(); + let mut line = Vec::new(); loop { line.clear(); - match self.stdout.read_line(&mut line) { + match self.stdout.read_until(b'\n', &mut line) { Ok(_) => {} Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break, Err(err) => panic!("failed to read output of test runner: {err}"), @@ -100,13 +105,31 @@ impl<'a> Renderer<'a> { break; } - match serde_json::from_str(&line) { + match serde_json::from_slice(&line) { Ok(parsed) => self.render_message(parsed), Err(_err) => { // Handle non-JSON output, for example when --nocapture is passed. - print!("{line}"); - let _ = std::io::stdout().flush(); + let mut stdout = std::io::stdout(); + stdout.write_all(&line).unwrap(); + let _ = stdout.flush(); + } + } + } + } + + /// Renders the stdout characters one by one + fn stream_all(mut self) { + let mut buffer = [0; 1]; + loop { + match self.stdout.read(&mut buffer) { + Ok(0) => break, + Ok(_) => { + let mut stdout = std::io::stdout(); + stdout.write_all(&buffer).unwrap(); + let _ = stdout.flush(); } + Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break, + Err(err) => panic!("failed to read output of test runner: {err}"), } } } @@ -118,9 +141,9 @@ impl<'a> Renderer<'a> { self.builder.metrics.record_test( &test.name, match outcome { - Outcome::Ok | Outcome::BenchOk => crate::metrics::TestOutcome::Passed, - Outcome::Failed => crate::metrics::TestOutcome::Failed, - Outcome::Ignored { reason } => crate::metrics::TestOutcome::Ignored { + Outcome::Ok | Outcome::BenchOk => build_helper::metrics::TestOutcome::Passed, + Outcome::Failed => build_helper::metrics::TestOutcome::Failed, + Outcome::Ignored { reason } => build_helper::metrics::TestOutcome::Ignored { ignore_reason: reason.map(|s| s.to_string()), }, }, diff --git a/src/bootstrap/run.rs b/src/bootstrap/run.rs index ec01f744b8250..4082f5bb9b1ee 100644 --- a/src/bootstrap/run.rs +++ b/src/bootstrap/run.rs @@ -24,8 +24,7 @@ impl Step for ExpandYamlAnchors { /// anchors in them, since GitHub Actions doesn't support them. fn run(self, builder: &Builder<'_>) { builder.info("Expanding YAML anchors in the GitHub Actions configuration"); - try_run( - builder, + builder.run_delaying_failure( &mut builder.tool_cmd(Tool::ExpandYamlAnchors).arg("generate").arg(&builder.src), ); } @@ -39,19 +38,6 @@ impl Step for ExpandYamlAnchors { } } -fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool { - if !builder.fail_fast { - if !builder.try_run(cmd) { - let mut failures = builder.delayed_failures.borrow_mut(); - failures.push(format!("{:?}", cmd)); - return false; - } - } else { - builder.run(cmd); - } - true -} - #[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] pub struct BuildManifest; diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs index 140259b02135f..7e83b508e94c1 100644 --- a/src/bootstrap/sanity.rs +++ b/src/bootstrap/sanity.rs @@ -104,7 +104,7 @@ You should install cmake, or set `download-ci-llvm = true` in the than building it. " ); - crate::detail_exit(1); + crate::exit!(1); } } @@ -188,7 +188,7 @@ than building it. // Externally configured LLVM requires FileCheck to exist let filecheck = build.llvm_filecheck(build.build); if !filecheck.starts_with(&build.out) && !filecheck.exists() && build.config.codegen_tests { - panic!("FileCheck executable {:?} does not exist", filecheck); + panic!("FileCheck executable {filecheck:?} does not exist"); } } @@ -206,7 +206,7 @@ than building it. } // Make sure musl-root is valid - if target.contains("musl") { + if target.contains("musl") && !target.contains("unikraft") { // If this is a native target (host is also musl) and no musl-root is given, // fall back to the system toolchain in /usr before giving up if build.musl_root(*target).is_none() && build.config.build == *target { diff --git a/src/bootstrap/setup.rs b/src/bootstrap/setup.rs index 09f26862b4ab2..30730f50491fe 100644 --- a/src/bootstrap/setup.rs +++ b/src/bootstrap/setup.rs @@ -20,7 +20,7 @@ pub enum Profile { Codegen, Library, Tools, - User, + Dist, None, } @@ -31,6 +31,8 @@ static SETTINGS_HASHES: &[&str] = &[ "ea67e259dedf60d4429b6c349a564ffcd1563cf41c920a856d1f5b16b4701ac8", "56e7bf011c71c5d81e0bf42e84938111847a810eee69d906bba494ea90b51922", "af1b5efe196aed007577899db9dae15d6dbc923d6fa42fa0934e68617ba9bbe0", + "3468fea433c25fff60be6b71e8a215a732a7b1268b6a83bf10d024344e140541", + "47d227f424bf889b0d899b9cc992d5695e1b78c406e183cd78eafefbe5488923", ]; static RUST_ANALYZER_SETTINGS: &str = include_str!("../etc/rust_analyzer_settings.json"); @@ -42,7 +44,7 @@ impl Profile { pub fn all() -> impl Iterator { use Profile::*; // N.B. these are ordered by how they are displayed, not alphabetically - [Library, Compiler, Codegen, Tools, User, None].iter().copied() + [Library, Compiler, Codegen, Tools, Dist, None].iter().copied() } pub fn purpose(&self) -> String { @@ -52,7 +54,7 @@ impl Profile { Compiler => "Contribute to the compiler itself", Codegen => "Contribute to the compiler, and also modify LLVM or codegen", Tools => "Contribute to tools which depend on the compiler, but do not modify it directly (e.g. rustdoc, clippy, miri)", - User => "Install Rust from source", + Dist => "Install Rust from source", None => "Do not modify `config.toml`" } .to_string() @@ -72,7 +74,7 @@ impl Profile { Profile::Codegen => "codegen", Profile::Library => "library", Profile::Tools => "tools", - Profile::User => "user", + Profile::Dist => "dist", Profile::None => "none", } } @@ -86,12 +88,12 @@ impl FromStr for Profile { "lib" | "library" => Ok(Profile::Library), "compiler" => Ok(Profile::Compiler), "llvm" | "codegen" => Ok(Profile::Codegen), - "maintainer" | "user" => Ok(Profile::User), + "maintainer" | "dist" | "user" => Ok(Profile::Dist), "tools" | "tool" | "rustdoc" | "clippy" | "miri" | "rustfmt" | "rls" => { Ok(Profile::Tools) } "none" => Ok(Profile::None), - _ => Err(format!("unknown profile: '{}'", s)), + _ => Err(format!("unknown profile: '{s}'")), } } } @@ -159,22 +161,30 @@ pub fn setup(config: &Config, profile: Profile) { "test src/tools/rustfmt", ], Profile::Library => &["check", "build", "test library/std", "doc"], - Profile::User => &["dist", "build"], + Profile::Dist => &["dist", "build"], }; println!(); println!("To get started, try one of the following commands:"); for cmd in suggestions { - println!("- `x.py {}`", cmd); + println!("- `x.py {cmd}`"); } - if profile != Profile::User { + if profile != Profile::Dist { println!( "For more suggestions, see https://rustc-dev-guide.rust-lang.org/building/suggested.html" ); } + if profile == Profile::Tools { + eprintln!(); + eprintln!( + "note: the `tools` profile sets up the `stage2` toolchain (use \ + `rustup toolchain link 'name' host/build/stage2` to use rustc)" + ) + } + let path = &config.config.clone().unwrap_or(PathBuf::from("config.toml")); setup_config_toml(path, profile, config); } @@ -194,14 +204,13 @@ fn setup_config_toml(path: &PathBuf, profile: Profile, config: &Config) { "note: this will use the configuration in {}", profile.include_path(&config.src).display() ); - crate::detail_exit(1); + crate::exit!(1); } let settings = format!( "# Includes one of the default files in src/bootstrap/defaults\n\ - profile = \"{}\"\n\ - changelog-seen = {}\n", - profile, VERSION + profile = \"{profile}\"\n\ + changelog-seen = {VERSION}\n" ); t!(fs::write(path, settings)); @@ -332,7 +341,7 @@ fn ensure_stage1_toolchain_placeholder_exists(stage_path: &str) -> bool { return false; }; - let pathbuf = pathbuf.join(format!("rustc{}", EXE_SUFFIX)); + let pathbuf = pathbuf.join(format!("rustc{EXE_SUFFIX}")); if pathbuf.exists() { return true; @@ -380,12 +389,12 @@ pub fn interactive_path() -> io::Result { io::stdin().read_line(&mut input)?; if input.is_empty() { eprintln!("EOF on stdin, when expecting answer to question. Giving up."); - crate::detail_exit(1); + crate::exit!(1); } break match parse_with_abbrev(&input) { Ok(profile) => profile, Err(err) => { - eprintln!("error: {}", err); + eprintln!("error: {err}"); eprintln!("note: press Ctrl+C to exit"); continue; } @@ -573,7 +582,7 @@ fn create_vscode_settings_maybe(config: &Config) -> io::Result<()> { Some(false) => { // exists and is not current version or outdated, so back it up let mut backup = vscode_settings.clone(); - backup.set_extension("bak"); + backup.set_extension("json.bak"); eprintln!("warning: copying `settings.json` to `settings.json.bak`"); fs::copy(&vscode_settings, &backup)?; "Updated" diff --git a/src/bootstrap/suggest.rs b/src/bootstrap/suggest.rs index ff20ebec26772..f225104bda925 100644 --- a/src/bootstrap/suggest.rs +++ b/src/bootstrap/suggest.rs @@ -4,18 +4,11 @@ use std::str::FromStr; use std::path::PathBuf; -use crate::{ - builder::{Builder, Kind}, - tool::Tool, -}; +use clap::Parser; -#[cfg(feature = "build-metrics")] -pub fn suggest(builder: &Builder<'_>, run: bool) { - panic!("`x suggest` is not supported with `build-metrics`") -} +use crate::{builder::Builder, tool::Tool}; /// Suggests a list of possible `x.py` commands to run based on modified files in branch. -#[cfg(not(feature = "build-metrics"))] pub fn suggest(builder: &Builder<'_>, run: bool) { let suggestions = builder.tool_cmd(Tool::SuggestTests).output().expect("failed to run `suggest-tests` tool"); @@ -67,12 +60,13 @@ pub fn suggest(builder: &Builder<'_>, run: bool) { if run { for sug in suggestions { - let mut build = builder.build.clone(); - - let builder = - Builder::new_standalone(&mut build, Kind::parse(&sug.0).unwrap(), sug.2, sug.1); - - builder.execute_cli() + let mut build: crate::Build = builder.build.clone(); + build.config.paths = sug.2; + build.config.cmd = crate::flags::Flags::parse_from(["x.py", sug.0]).cmd; + if let Some(stage) = sug.1 { + build.config.stage = stage; + } + build.build(); } } else { println!("help: consider using the `--run` flag to automatically run suggested tests"); diff --git a/src/bootstrap/synthetic_targets.rs b/src/bootstrap/synthetic_targets.rs new file mode 100644 index 0000000000000..7eeac9025c9b7 --- /dev/null +++ b/src/bootstrap/synthetic_targets.rs @@ -0,0 +1,82 @@ +//! In some cases, parts of bootstrap need to change part of a target spec just for one or a few +//! steps. Adding these targets to rustc proper would "leak" this implementation detail of +//! bootstrap, and would make it more complex to apply additional changes if the need arises. +//! +//! To address that problem, this module implements support for "synthetic targets". Synthetic +//! targets are custom target specs generated using builtin target specs as their base. You can use +//! one of the target specs already defined in this module, or create new ones by adding a new step +//! that calls create_synthetic_target. + +use crate::builder::{Builder, ShouldRun, Step}; +use crate::config::TargetSelection; +use crate::Compiler; +use std::process::{Command, Stdio}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct MirOptPanicAbortSyntheticTarget { + pub(crate) compiler: Compiler, + pub(crate) base: TargetSelection, +} + +impl Step for MirOptPanicAbortSyntheticTarget { + type Output = TargetSelection; + const DEFAULT: bool = true; + const ONLY_HOSTS: bool = false; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.never() + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + create_synthetic_target(builder, self.compiler, "miropt-abort", self.base, |spec| { + spec.insert("panic-strategy".into(), "abort".into()); + }) + } +} + +fn create_synthetic_target( + builder: &Builder<'_>, + compiler: Compiler, + suffix: &str, + base: TargetSelection, + customize: impl FnOnce(&mut serde_json::Map), +) -> TargetSelection { + if base.contains("synthetic") { + // This check is not strictly needed, but nothing currently needs recursive synthetic + // targets. If the need arises, removing this in the future *SHOULD* be safe. + panic!("cannot create synthetic targets with other synthetic targets as their base"); + } + + let name = format!("{base}-synthetic-{suffix}"); + let path = builder.out.join("synthetic-target-specs").join(format!("{name}.json")); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + + if builder.config.dry_run() { + std::fs::write(&path, b"dry run\n").unwrap(); + return TargetSelection::create_synthetic(&name, path.to_str().unwrap()); + } + + let mut cmd = Command::new(builder.rustc(compiler)); + cmd.arg("--target").arg(base.rustc_target_arg()); + cmd.args(["-Zunstable-options", "--print", "target-spec-json"]); + cmd.stdout(Stdio::piped()); + + let output = cmd.spawn().unwrap().wait_with_output().unwrap(); + if !output.status.success() { + panic!("failed to gather the target spec for {base}"); + } + + let mut spec: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + let spec_map = spec.as_object_mut().unwrap(); + + // The `is-builtin` attribute of a spec needs to be removed, otherwise rustc will complain. + spec_map.remove("is-builtin"); + + customize(spec_map); + + std::fs::write(&path, &serde_json::to_vec_pretty(&spec).unwrap()).unwrap(); + let target = TargetSelection::create_synthetic(&name, path.to_str().unwrap()); + crate::cc_detect::find_target(builder, target); + + target +} diff --git a/src/bootstrap/tarball.rs b/src/bootstrap/tarball.rs index 7fa8a4d9d7f8a..95d909c5730f6 100644 --- a/src/bootstrap/tarball.rs +++ b/src/bootstrap/tarball.rs @@ -309,7 +309,7 @@ impl<'a> Tarball<'a> { let mut cmd = self.builder.tool_cmd(crate::tool::Tool::RustInstaller); let package_name = self.package_name(); - self.builder.info(&format!("Dist {}", package_name)); + self.builder.info(&format!("Dist {package_name}")); let _time = crate::util::timeit(self.builder); build_cli(&self, &mut cmd); @@ -344,7 +344,7 @@ impl<'a> Tarball<'a> { .unwrap_or("gz"); GeneratedTarball { - path: crate::dist::distdir(self.builder).join(format!("{}.tar.{}", package_name, ext)), + path: crate::dist::distdir(self.builder).join(format!("{package_name}.tar.{ext}")), decompressed_output, work: self.temp_dir, } diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index 2b72d6c48eb75..d0d62db08071a 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -4,6 +4,7 @@ //! our CI. use std::env; +use std::ffi::OsStr; use std::ffi::OsString; use std::fs; use std::iter; @@ -23,6 +24,7 @@ use crate::doc::DocumentationFormat; use crate::flags::Subcommand; use crate::llvm; use crate::render_tests::add_flags_and_try_run_tests; +use crate::synthetic_targets::MirOptPanicAbortSyntheticTarget; use crate::tool::{self, SourceType, Tool}; use crate::toolstate::ToolState; use crate::util::{self, add_link_lib_path, dylib_path, dylib_path_var, output, t, up_to_date}; @@ -30,31 +32,22 @@ use crate::{envify, CLang, DocTests, GitRepo, Mode}; const ADB_TEST_DIR: &str = "/data/local/tmp/work"; -fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool { - if !builder.fail_fast { - if !builder.try_run(cmd) { - let mut failures = builder.delayed_failures.borrow_mut(); - failures.push(format!("{:?}", cmd)); - return false; - } - } else { - builder.run(cmd); - } - true -} - -fn try_run_quiet(builder: &Builder<'_>, cmd: &mut Command) -> bool { - if !builder.fail_fast { - if !builder.try_run_quiet(cmd) { - let mut failures = builder.delayed_failures.borrow_mut(); - failures.push(format!("{:?}", cmd)); - return false; - } - } else { - builder.run_quiet(cmd); - } - true -} +// mir-opt tests have different variants depending on whether a target is 32bit or 64bit, and +// blessing them requires blessing with each target. To aid developers, when blessing the mir-opt +// test suite the corresponding target of the opposite pointer size is also blessed. +// +// This array serves as the known mappings between 32bit and 64bit targets. If you're developing on +// a target where a target with the opposite pointer size exists, feel free to add it here. +const MIR_OPT_BLESS_TARGET_MAPPING: &[(&str, &str)] = &[ + // (32bit, 64bit) + ("i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu"), + ("i686-unknown-linux-musl", "x86_64-unknown-linux-musl"), + ("i686-pc-windows-msvc", "x86_64-pc-windows-msvc"), + ("i686-pc-windows-gnu", "x86_64-pc-windows-gnu"), + ("i686-apple-darwin", "x86_64-apple-darwin"), + // ARM Macs don't have a corresponding 32-bit target that they can (easily) + // build for, so there is no entry for "aarch64-apple-darwin" here. +]; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct CrateBootstrap { @@ -99,14 +92,8 @@ impl Step for CrateBootstrap { SourceType::InTree, &[], ); - builder.info(&format!( - "{} {} stage0 ({})", - builder.kind.test_description(), - path, - bootstrap_host, - )); let crate_name = path.rsplit_once('/').unwrap().1; - run_cargo_test(cargo, &[], &[], crate_name, compiler, bootstrap_host, builder); + run_cargo_test(cargo, &[], &[], crate_name, crate_name, compiler, bootstrap_host, builder); } } @@ -136,15 +123,16 @@ impl Step for Linkcheck { if (hosts != targets) && !hosts.is_empty() && !targets.is_empty() { panic!( "Linkcheck currently does not support builds with different hosts and targets. -You can skip linkcheck with --exclude src/tools/linkchecker" +You can skip linkcheck with --skip src/tools/linkchecker" ); } - builder.info(&format!("Linkcheck ({})", host)); + builder.info(&format!("Linkcheck ({host})")); // Test the linkchecker itself. let bootstrap_host = builder.config.build; let compiler = builder.compiler(0, bootstrap_host); + let cargo = tool::prepare_tool_cargo( builder, compiler, @@ -155,7 +143,16 @@ You can skip linkcheck with --exclude src/tools/linkchecker" SourceType::InTree, &[], ); - run_cargo_test(cargo, &[], &[], "linkchecker", compiler, bootstrap_host, builder); + run_cargo_test( + cargo, + &[], + &[], + "linkchecker", + "linkchecker self tests", + compiler, + bootstrap_host, + builder, + ); if builder.doc_tests == DocTests::No { return; @@ -164,12 +161,14 @@ You can skip linkcheck with --exclude src/tools/linkchecker" // Build all the default documentation. builder.default_doc(&[]); + // Build the linkchecker before calling `msg`, since GHA doesn't support nested groups. + let mut linkchecker = builder.tool_cmd(Tool::Linkchecker); + // Run the linkchecker. + let _guard = + builder.msg(Kind::Test, compiler.stage, "Linkcheck", bootstrap_host, bootstrap_host); let _time = util::timeit(&builder); - try_run( - builder, - builder.tool_cmd(Tool::Linkchecker).arg(builder.out.join(host.triple).join("doc")), - ); + builder.run_delaying_failure(linkchecker.arg(builder.out.join(host.triple).join("doc"))); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -220,9 +219,11 @@ impl Step for HtmlCheck { } // Ensure that a few different kinds of documentation are available. builder.default_doc(&[]); - builder.ensure(crate::doc::Rustc { target: self.target, stage: builder.top_stage }); + builder.ensure(crate::doc::Rustc::new(builder.top_stage, self.target, builder)); - try_run(builder, builder.tool_cmd(Tool::HtmlChecker).arg(builder.doc_out(self.target))); + builder.run_delaying_failure( + builder.tool_cmd(Tool::HtmlChecker).arg(builder.doc_out(self.target)), + ); } } @@ -261,8 +262,7 @@ impl Step for Cargotest { let _time = util::timeit(&builder); let mut cmd = builder.tool_cmd(Tool::CargoTest); - try_run( - builder, + builder.run_delaying_failure( cmd.arg(&cargo) .arg(&out_dir) .args(builder.config.test_args()) @@ -317,6 +317,17 @@ impl Step for Cargo { cargo.env("CARGO_TEST_DISABLE_NIGHTLY", "1"); cargo.env("PATH", &path_for_cargo(builder, compiler)); + #[cfg(feature = "build-metrics")] + builder.metrics.begin_test_suite( + build_helper::metrics::TestSuiteMetadata::CargoPackage { + crates: vec!["cargo".into()], + target: self.host.triple.to_string(), + host: self.host.triple.to_string(), + stage: self.stage, + }, + builder, + ); + let _time = util::timeit(&builder); add_flags_and_try_run_tests(builder, &mut cargo); } @@ -347,7 +358,9 @@ impl Step for RustAnalyzer { let host = self.host; let compiler = builder.compiler(stage, host); - builder.ensure(tool::RustAnalyzer { compiler, target: self.host }).expect("in-tree tool"); + // We don't need to build the whole Rust Analyzer for the proc-macro-srv test suite, + // but we do need the standard library to be present. + builder.ensure(compile::Std::new(compiler, host)); let workspace_path = "src/tools/rust-analyzer"; // until the whole RA test suite runs on `i686`, we only run @@ -375,7 +388,7 @@ impl Step for RustAnalyzer { cargo.env("SKIP_SLOW_TESTS", "1"); cargo.add_rustc_lib_path(builder, compiler); - run_cargo_test(cargo, &[], &[], "rust-analyzer", compiler, host, builder); + run_cargo_test(cargo, &[], &[], "rust-analyzer", "rust-analyzer", compiler, host, builder); } } @@ -424,7 +437,7 @@ impl Step for Rustfmt { cargo.add_rustc_lib_path(builder, compiler); - run_cargo_test(cargo, &[], &[], "rustfmt", compiler, host, builder); + run_cargo_test(cargo, &[], &[], "rustfmt", "rustfmt", compiler, host, builder); } } @@ -472,7 +485,16 @@ impl Step for RustDemangler { cargo.env("RUST_DEMANGLER_DRIVER_PATH", rust_demangler); cargo.add_rustc_lib_path(builder, compiler); - run_cargo_test(cargo, &[], &[], "rust-demangler", compiler, host, builder); + run_cargo_test( + cargo, + &[], + &[], + "rust-demangler", + "rust-demangler", + compiler, + host, + builder, + ); } } @@ -516,6 +538,13 @@ impl Miri { cargo.env("RUST_BACKTRACE", "1"); let mut cargo = Command::from(cargo); + let _guard = builder.msg( + Kind::Build, + compiler.stage + 1, + "miri sysroot", + compiler.host, + compiler.host, + ); builder.run(&mut cargo); // # Determine where Miri put its sysroot. @@ -529,7 +558,7 @@ impl Miri { if builder.config.dry_run() { String::new() } else { - builder.verbose(&format!("running: {:?}", cargo)); + builder.verbose(&format!("running: {cargo:?}")); let out = cargo.output().expect("We already ran `cargo miri setup` before and that worked"); assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code"); @@ -537,7 +566,7 @@ impl Miri { let stdout = String::from_utf8(out.stdout) .expect("`cargo miri setup` stdout is not valid UTF-8"); let sysroot = stdout.trim_end(); - builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot)); + builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {sysroot:?}")); sysroot.to_owned() } } @@ -593,16 +622,14 @@ impl Step for Miri { SourceType::InTree, &[], ); + let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "miri", host, host); + cargo.add_rustc_lib_path(builder, compiler); // miri tests need to know about the stage sysroot cargo.env("MIRI_SYSROOT", &miri_sysroot); cargo.env("MIRI_HOST_SYSROOT", sysroot); cargo.env("MIRI", &miri); - // propagate --bless - if builder.config.cmd.bless() { - cargo.env("MIRI_BLESS", "Gesundheit"); - } // Set the target. cargo.env("MIRI_TEST_TARGET", target.rustc_target_arg()); @@ -620,8 +647,8 @@ impl Step for Miri { cargo.env("MIRIFLAGS", "-O -Zmir-opt-level=4 -Cdebug-assertions=yes"); // Optimizations can change backtraces cargo.env("MIRI_SKIP_UI_CHECKS", "1"); - // `MIRI_SKIP_UI_CHECKS` and `MIRI_BLESS` are incompatible - cargo.env_remove("MIRI_BLESS"); + // `MIRI_SKIP_UI_CHECKS` and `RUSTC_BLESS` are incompatible + cargo.env_remove("RUSTC_BLESS"); // Optimizations can change error locations and remove UB so don't run `fail` tests. cargo.args(&["tests/pass", "tests/panic"]); @@ -689,7 +716,7 @@ impl Step for CompiletestTest { /// Runs `cargo test` for compiletest. fn run(self, builder: &Builder<'_>) { let host = self.host; - let compiler = builder.compiler(1, host); + let compiler = builder.compiler(builder.top_stage, host); // We need `ToolStd` for the locally-built sysroot because // compiletest uses unstable features of the `test` crate. @@ -705,7 +732,16 @@ impl Step for CompiletestTest { &[], ); cargo.allow_features("test"); - run_cargo_test(cargo, &[], &[], "compiletest", compiler, host, builder); + run_cargo_test( + cargo, + &[], + &[], + "compiletest", + "compiletest self test", + compiler, + host, + builder, + ); } } @@ -756,27 +792,17 @@ impl Step for Clippy { cargo.add_rustc_lib_path(builder, compiler); let mut cargo = prepare_cargo_test(cargo, &[], &[], "clippy", compiler, host, builder); - if builder.try_run(&mut cargo) { + let _guard = builder.msg_sysroot_tool(Kind::Test, compiler.stage, "clippy", host, host); + + #[allow(deprecated)] // Clippy reports errors if it blessed the outputs + if builder.config.try_run(&mut cargo).is_ok() { // The tests succeeded; nothing to do. return; } if !builder.config.cmd.bless() { - crate::detail_exit(1); - } - - let mut cargo = builder.cargo(compiler, Mode::ToolRustc, SourceType::InTree, host, "run"); - cargo.arg("-p").arg("clippy_dev"); - // clippy_dev gets confused if it can't find `clippy/Cargo.toml` - cargo.current_dir(&builder.src.join("src").join("tools").join("clippy")); - if builder.config.rust_optimize { - cargo.env("PROFILE", "release"); - } else { - cargo.env("PROFILE", "debug"); + crate::exit!(1); } - cargo.arg("--"); - cargo.arg("bless"); - builder.run(&mut cargo.into()); } } @@ -829,7 +855,7 @@ impl Step for RustdocTheme { util::lld_flag_no_threads(self.compiler.host.contains("windows")), ); } - try_run(builder, &mut cmd); + builder.run_delaying_failure(&mut cmd); } } @@ -844,7 +870,8 @@ impl Step for RustdocJSStd { const ONLY_HOSTS: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.suite_path("tests/rustdoc-js-std") + let default = run.builder.config.nodejs.is_some(); + run.suite_path("tests/rustdoc-js-std").default_condition(default) } fn make_run(run: RunConfig<'_>) { @@ -852,38 +879,42 @@ impl Step for RustdocJSStd { } fn run(self, builder: &Builder<'_>) { - if let Some(ref nodejs) = builder.config.nodejs { - let mut command = Command::new(nodejs); - command - .arg(builder.src.join("src/tools/rustdoc-js/tester.js")) - .arg("--crate-name") - .arg("std") - .arg("--resource-suffix") - .arg(&builder.version) - .arg("--doc-folder") - .arg(builder.doc_out(self.target)) - .arg("--test-folder") - .arg(builder.src.join("tests/rustdoc-js-std")); - for path in &builder.paths { - if let Some(p) = - util::is_valid_test_suite_arg(path, "tests/rustdoc-js-std", builder) - { - if !p.ends_with(".js") { - eprintln!("A non-js file was given: `{}`", path.display()); - panic!("Cannot run rustdoc-js-std tests"); - } - command.arg("--test-file").arg(path); + let nodejs = + builder.config.nodejs.as_ref().expect("need nodejs to run rustdoc-js-std tests"); + let mut command = Command::new(nodejs); + command + .arg(builder.src.join("src/tools/rustdoc-js/tester.js")) + .arg("--crate-name") + .arg("std") + .arg("--resource-suffix") + .arg(&builder.version) + .arg("--doc-folder") + .arg(builder.doc_out(self.target)) + .arg("--test-folder") + .arg(builder.src.join("tests/rustdoc-js-std")); + for path in &builder.paths { + if let Some(p) = util::is_valid_test_suite_arg(path, "tests/rustdoc-js-std", builder) { + if !p.ends_with(".js") { + eprintln!("A non-js file was given: `{}`", path.display()); + panic!("Cannot run rustdoc-js-std tests"); } + command.arg("--test-file").arg(path); } - builder.ensure(crate::doc::Std { - target: self.target, - stage: builder.top_stage, - format: DocumentationFormat::HTML, - }); - builder.run(&mut command); - } else { - builder.info("No nodejs found, skipping \"tests/rustdoc-js-std\" tests"); } + builder.ensure(crate::doc::Std::new( + builder.top_stage, + self.target, + builder, + DocumentationFormat::HTML, + )); + let _guard = builder.msg( + Kind::Test, + builder.top_stage, + "rustdoc-js-std", + builder.config.build, + self.target, + ); + builder.run(&mut command); } } @@ -899,7 +930,8 @@ impl Step for RustdocJSNotStd { const ONLY_HOSTS: bool = true; fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { - run.suite_path("tests/rustdoc-js") + let default = run.builder.config.nodejs.is_some(); + run.suite_path("tests/rustdoc-js").default_condition(default) } fn make_run(run: RunConfig<'_>) { @@ -908,18 +940,14 @@ impl Step for RustdocJSNotStd { } fn run(self, builder: &Builder<'_>) { - if builder.config.nodejs.is_some() { - builder.ensure(Compiletest { - compiler: self.compiler, - target: self.target, - mode: "js-doc-test", - suite: "rustdoc-js", - path: "tests/rustdoc-js", - compare_mode: None, - }); - } else { - builder.info("No nodejs found, skipping \"tests/rustdoc-js\" tests"); - } + builder.ensure(Compiletest { + compiler: self.compiler, + target: self.target, + mode: "js-doc-test", + suite: "rustdoc-js", + path: "tests/rustdoc-js", + compare_mode: None, + }); } } @@ -944,28 +972,6 @@ fn get_browser_ui_test_version(npm: &Path) -> Option { .or_else(|| get_browser_ui_test_version_inner(npm, true)) } -fn compare_browser_ui_test_version(installed_version: &str, src: &Path) { - match fs::read_to_string( - src.join("src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version"), - ) { - Ok(v) => { - if v.trim() != installed_version { - eprintln!( - "⚠️ Installed version of browser-ui-test (`{}`) is different than the \ - one used in the CI (`{}`)", - installed_version, v - ); - eprintln!( - "You can install this version using `npm update browser-ui-test` or by using \ - `npm install browser-ui-test@{}`", - v, - ); - } - } - Err(e) => eprintln!("Couldn't find the CI browser-ui-test version: {:?}", e), - } -} - #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub struct RustdocGUI { pub target: TargetSelection, @@ -997,79 +1003,30 @@ impl Step for RustdocGUI { } fn run(self, builder: &Builder<'_>) { - let nodejs = builder.config.nodejs.as_ref().expect("nodejs isn't available"); - let npm = builder.config.npm.as_ref().expect("npm isn't available"); - builder.ensure(compile::Std::new(self.compiler, self.target)); - // The goal here is to check if the necessary packages are installed, and if not, we - // panic. - match get_browser_ui_test_version(&npm) { - Some(version) => { - // We also check the version currently used in CI and emit a warning if it's not the - // same one. - compare_browser_ui_test_version(&version, &builder.build.src); - } - None => { - eprintln!( - "error: rustdoc-gui test suite cannot be run because npm `browser-ui-test` \ - dependency is missing", - ); - eprintln!( - "If you want to install the `{0}` dependency, run `npm install {0}`", - "browser-ui-test", - ); - panic!("Cannot run rustdoc-gui tests"); - } - } + let mut cmd = builder.tool_cmd(Tool::RustdocGUITest); let out_dir = builder.test_out(self.target).join("rustdoc-gui"); - - // We remove existing folder to be sure there won't be artifacts remaining. builder.clear_if_dirty(&out_dir, &builder.rustdoc(self.compiler)); - let src_path = builder.build.src.join("tests/rustdoc-gui/src"); - // We generate docs for the libraries present in the rustdoc-gui's src folder. - for entry in src_path.read_dir().expect("read_dir call failed") { - if let Ok(entry) = entry { - let path = entry.path(); + if let Some(src) = builder.config.src.to_str() { + cmd.arg("--rust-src").arg(src); + } - if !path.is_dir() { - continue; - } + if let Some(out_dir) = out_dir.to_str() { + cmd.arg("--out-dir").arg(out_dir); + } - let mut cargo = Command::new(&builder.initial_cargo); - cargo - .arg("doc") - .arg("--target-dir") - .arg(&out_dir) - .env("RUSTC_BOOTSTRAP", "1") - .env("RUSTDOC", builder.rustdoc(self.compiler)) - .env("RUSTC", builder.rustc(self.compiler)) - .current_dir(path); - // FIXME: implement a `// compile-flags` command or similar - // instead of hard-coding this test - if entry.file_name() == "link_to_definition" { - cargo.env("RUSTDOCFLAGS", "-Zunstable-options --generate-link-to-definition"); - } else if entry.file_name() == "scrape_examples" { - cargo.arg("-Zrustdoc-scrape-examples"); - } else if entry.file_name() == "extend_css" { - cargo.env("RUSTDOCFLAGS", &format!("--extend-css extra.css")); - } - builder.run(&mut cargo); - } + if let Some(initial_cargo) = builder.config.initial_cargo.to_str() { + cmd.arg("--initial-cargo").arg(initial_cargo); } - // We now run GUI tests. - let mut command = Command::new(&nodejs); - command - .arg(builder.build.src.join("src/tools/rustdoc-gui/tester.js")) - .arg("--jobs") - .arg(&builder.jobs().to_string()) - .arg("--doc-folder") - .arg(out_dir.join("doc")) - .arg("--tests-folder") - .arg(builder.build.src.join("tests/rustdoc-gui")); + cmd.arg("--jobs").arg(builder.jobs().to_string()); + + cmd.env("RUSTDOC", builder.rustdoc(self.compiler)) + .env("RUSTC", builder.rustc(self.compiler)); + for path in &builder.paths { if let Some(p) = util::is_valid_test_suite_arg(path, "tests/rustdoc-gui", builder) { if !p.ends_with(".goml") { @@ -1077,14 +1034,32 @@ impl Step for RustdocGUI { panic!("Cannot run rustdoc-gui tests"); } if let Some(name) = path.file_name().and_then(|f| f.to_str()) { - command.arg("--file").arg(name); + cmd.arg("--goml-file").arg(name); } } } + for test_arg in builder.config.test_args() { - command.arg(test_arg); + cmd.arg("--test-arg").arg(test_arg); } - builder.run(&mut command); + + if let Some(ref nodejs) = builder.config.nodejs { + cmd.arg("--nodejs").arg(nodejs); + } + + if let Some(ref npm) = builder.config.npm { + cmd.arg("--npm").arg(npm); + } + + let _time = util::timeit(&builder); + let _guard = builder.msg_sysroot_tool( + Kind::Test, + self.compiler.stage, + "rustdoc-gui", + self.compiler.host, + self.target, + ); + crate::render_tests::try_run_tests(builder, &mut cmd, true); } } @@ -1120,6 +1095,14 @@ impl Step for Tidy { if builder.config.cmd.bless() { cmd.arg("--bless"); } + if let Some(s) = builder.config.cmd.extra_checks() { + cmd.arg(format!("--extra-checks={s}")); + } + let mut args = std::env::args_os(); + if let Some(_) = args.find(|arg| arg == OsStr::new("--")) { + cmd.arg("--"); + cmd.args(args); + } if builder.config.channel == "dev" || builder.config.channel == "nightly" { builder.info("fmt check"); @@ -1130,17 +1113,17 @@ impl Step for Tidy { error: no `rustfmt` binary found in {PATH} info: `rust.channel` is currently set to \"{CHAN}\" help: if you are testing a beta branch, set `rust.channel` to \"beta\" in the `config.toml` file -help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy` to `x.py test`", +help: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to `x.py test`", PATH = inferred_rustfmt_dir.display(), CHAN = builder.config.channel, ); - crate::detail_exit(1); + crate::exit!(1); } crate::format::format(&builder, !builder.config.cmd.bless(), &[]); } builder.info("tidy check"); - try_run(builder, &mut cmd); + builder.run_delaying_failure(&mut cmd); builder.ensure(ExpandYamlAnchors); @@ -1157,7 +1140,7 @@ help: to skip test's attempt to check tidiness, pass `--exclude src/tools/tidy` eprintln!( "x.py completions were changed; run `x.py run generate-completions` to update them" ); - crate::detail_exit(1); + crate::exit!(1); } } } @@ -1185,8 +1168,7 @@ impl Step for ExpandYamlAnchors { /// by the user before committing CI changes. fn run(self, builder: &Builder<'_>) { builder.info("Ensuring the YAML anchors in the GitHub Actions config were expanded"); - try_run( - builder, + builder.run_delaying_failure( &mut builder.tool_cmd(Tool::ExpandYamlAnchors).arg("check").arg(&builder.src), ); } @@ -1310,8 +1292,6 @@ default_test!(RunPassValgrind { suite: "run-pass-valgrind" }); -default_test!(MirOpt { path: "tests/mir-opt", mode: "mir-opt", suite: "mir-opt" }); - default_test!(Codegen { path: "tests/codegen", mode: "codegen", suite: "codegen" }); default_test!(CodegenUnits { @@ -1348,6 +1328,98 @@ host_test!(RunMakeFullDeps { default_test!(Assembly { path: "tests/assembly", mode: "assembly", suite: "assembly" }); +host_test!(RunCoverage { path: "tests/run-coverage", mode: "run-coverage", suite: "run-coverage" }); +host_test!(RunCoverageRustdoc { + path: "tests/run-coverage-rustdoc", + mode: "run-coverage", + suite: "run-coverage-rustdoc" +}); + +// For the mir-opt suite we do not use macros, as we need custom behavior when blessing. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct MirOpt { + pub compiler: Compiler, + pub target: TargetSelection, +} + +impl Step for MirOpt { + type Output = (); + const DEFAULT: bool = true; + const ONLY_HOSTS: bool = false; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.suite_path("tests/mir-opt") + } + + fn make_run(run: RunConfig<'_>) { + let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple()); + run.builder.ensure(MirOpt { compiler, target: run.target }); + } + + fn run(self, builder: &Builder<'_>) { + let run = |target| { + builder.ensure(Compiletest { + compiler: self.compiler, + target: target, + mode: "mir-opt", + suite: "mir-opt", + path: "tests/mir-opt", + compare_mode: None, + }) + }; + + // We use custom logic to bless the mir-opt suite: mir-opt tests have multiple variants + // (32bit vs 64bit, and panic=abort vs panic=unwind), and all of them needs to be blessed. + // When blessing, we try best-effort to also bless the other variants, to aid developers. + if builder.config.cmd.bless() { + let targets = MIR_OPT_BLESS_TARGET_MAPPING + .iter() + .filter(|(target_32bit, target_64bit)| { + *target_32bit == &*self.target.triple || *target_64bit == &*self.target.triple + }) + .next() + .map(|(target_32bit, target_64bit)| { + let target_32bit = TargetSelection::from_user(target_32bit); + let target_64bit = TargetSelection::from_user(target_64bit); + + // Running compiletest requires a C compiler to be available, but it might not + // have been detected by bootstrap if the target we're testing wasn't in the + // --target flags. + if !builder.cc.borrow().contains_key(&target_32bit) { + crate::cc_detect::find_target(builder, target_32bit); + } + if !builder.cc.borrow().contains_key(&target_64bit) { + crate::cc_detect::find_target(builder, target_64bit); + } + + vec![target_32bit, target_64bit] + }) + .unwrap_or_else(|| { + eprintln!( + "\ +Note that not all variants of mir-opt tests are going to be blessed, as no mapping between +a 32bit and a 64bit target was found for {target}. +You can add that mapping by changing MIR_OPT_BLESS_TARGET_MAPPING in src/bootstrap/test.rs", + target = self.target, + ); + vec![self.target] + }); + + for target in targets { + run(target); + + let panic_abort_target = builder.ensure(MirOptPanicAbortSyntheticTarget { + compiler: self.compiler, + base: target, + }); + run(panic_abort_target); + } + } else { + run(self.target); + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] struct Compiletest { compiler: Compiler, @@ -1378,7 +1450,7 @@ help: to test the compiler, use `--stage 1` instead help: to test the standard library, use `--stage 0 library/std` instead note: if you're sure you want to do this, please open an issue as to why. In the meantime, you can override this with `COMPILETEST_FORCE_STAGE0=1`." ); - crate::detail_exit(1); + crate::exit!(1); } let mut compiler = self.compiler; @@ -1447,6 +1519,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the || (mode == "ui" && is_rustdoc) || mode == "js-doc-test" || mode == "rustdoc-json" + || suite == "run-coverage-rustdoc" { cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler)); } @@ -1460,7 +1533,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the .arg(builder.ensure(tool::JsonDocLint { compiler: json_compiler, target })); } - if mode == "run-make" { + if mode == "run-make" || mode == "run-coverage" { let rust_demangler = builder .ensure(tool::RustDemangler { compiler, @@ -1473,7 +1546,15 @@ note: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--src-base").arg(builder.src.join("tests").join(suite)); cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite)); - cmd.arg("--sysroot-base").arg(builder.sysroot(compiler)); + + // When top stage is 0, that means that we're testing an externally provided compiler. + // In that case we need to use its specific sysroot for tests to pass. + let sysroot = if builder.top_stage == 0 { + builder.initial_sysroot.clone() + } else { + builder.sysroot(compiler).to_path_buf() + }; + cmd.arg("--sysroot-base").arg(sysroot); cmd.arg("--stage-id").arg(stage_id); cmd.arg("--suite").arg(suite); cmd.arg("--mode").arg(mode); @@ -1506,6 +1587,8 @@ note: if you're sure you want to do this, please open an issue as to why. In the if let Some(ref nodejs) = builder.config.nodejs { cmd.arg("--nodejs").arg(nodejs); + } else if mode == "js-doc-test" { + panic!("need nodejs to run js-doc-test suite"); } if let Some(ref npm) = builder.config.npm { cmd.arg("--npm").arg(npm); @@ -1576,9 +1659,9 @@ note: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--run-clang-based-tests-with").arg(clang_exe); } - for exclude in &builder.config.exclude { + for exclude in &builder.config.skip { cmd.arg("--skip"); - cmd.arg(&exclude.path); + cmd.arg(&exclude); } // Get paths from cmd args @@ -1639,17 +1722,21 @@ note: if you're sure you want to do this, please open an issue as to why. In the add_link_lib_path(vec![llvm_libdir.trim().into()], &mut cmd); } - // Only pass correct values for these flags for the `run-make` suite as it - // requires that a C++ compiler was configured which isn't always the case. - if !builder.config.dry_run() && matches!(suite, "run-make" | "run-make-fulldeps") { + if !builder.config.dry_run() + && (matches!(suite, "run-make" | "run-make-fulldeps") || mode == "run-coverage") + { // The llvm/bin directory contains many useful cross-platform // tools. Pass the path to run-make tests so they can use them. + // (The run-coverage tests also need these tools to process + // coverage reports.) let llvm_bin_path = llvm_config .parent() .expect("Expected llvm-config to be contained in directory"); assert!(llvm_bin_path.is_dir()); cmd.arg("--llvm-bin-dir").arg(llvm_bin_path); + } + if !builder.config.dry_run() && matches!(suite, "run-make" | "run-make-fulldeps") { // If LLD is available, add it to the PATH if builder.config.lld_enabled { let lld_install_root = @@ -1707,8 +1794,8 @@ note: if you're sure you want to do this, please open an issue as to why. In the // // Note that if we encounter `PATH` we make sure to append to our own `PATH` // rather than stomp over it. - if target.contains("msvc") { - for &(ref k, ref v) in builder.cc[&target].env() { + if !builder.config.dry_run() && target.contains("msvc") { + for &(ref k, ref v) in builder.cc.borrow()[&target].env() { if k != "PATH" { cmd.env(k, v); } @@ -1733,7 +1820,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--adb-path").arg("adb"); cmd.arg("--adb-test-dir").arg(ADB_TEST_DIR); - if target.contains("android") { + if target.contains("android") && !builder.config.dry_run() { // Assume that cc for this target comes from the android sysroot cmd.arg("--android-cross-path") .arg(builder.cc(target).parent().unwrap().parent().unwrap()); @@ -1753,27 +1840,52 @@ note: if you're sure you want to do this, please open an issue as to why. In the cmd.arg("--git-hash"); } - if let Some(commit) = builder.config.download_rustc_commit() { - cmd.env("FAKE_DOWNLOAD_RUSTC_PREFIX", format!("/rustc/{commit}")); - } - builder.ci_env.force_coloring_in_ci(&mut cmd); - builder.info(&format!( - "Check compiletest suite={} mode={} ({} -> {})", - suite, mode, &compiler.host, target - )); - let _time = util::timeit(&builder); - crate::render_tests::try_run_tests(builder, &mut cmd); + #[cfg(feature = "build-metrics")] + builder.metrics.begin_test_suite( + build_helper::metrics::TestSuiteMetadata::Compiletest { + suite: suite.into(), + mode: mode.into(), + compare_mode: None, + target: self.target.triple.to_string(), + host: self.compiler.host.triple.to_string(), + stage: self.compiler.stage, + }, + builder, + ); + + let _group = builder.msg( + Kind::Test, + compiler.stage, + &format!("compiletest suite={suite} mode={mode}"), + compiler.host, + target, + ); + crate::render_tests::try_run_tests(builder, &mut cmd, false); if let Some(compare_mode) = compare_mode { cmd.arg("--compare-mode").arg(compare_mode); + + #[cfg(feature = "build-metrics")] + builder.metrics.begin_test_suite( + build_helper::metrics::TestSuiteMetadata::Compiletest { + suite: suite.into(), + mode: mode.into(), + compare_mode: Some(compare_mode.into()), + target: self.target.triple.to_string(), + host: self.compiler.host.triple.to_string(), + stage: self.compiler.stage, + }, + builder, + ); + builder.info(&format!( "Check compiletest suite={} mode={} compare_mode={} ({} -> {})", suite, mode, compare_mode, &compiler.host, target )); let _time = util::timeit(&builder); - crate::render_tests::try_run_tests(builder, &mut cmd); + crate::render_tests::try_run_tests(builder, &mut cmd, false); } } } @@ -1840,12 +1952,12 @@ impl BookTest { let _guard = builder.msg( Kind::Test, compiler.stage, - format_args!("rustbook {}", self.path.display()), + format_args!("mdbook {}", self.path.display()), compiler.host, compiler.host, ); let _time = util::timeit(&builder); - let toolstate = if try_run(builder, &mut rustbook_cmd) { + let toolstate = if builder.run_delaying_failure(&mut rustbook_cmd) { ToolState::TestPass } else { ToolState::TestFail @@ -1856,8 +1968,12 @@ impl BookTest { /// This runs `rustdoc --test` on all `.md` files in the path. fn run_local_doc(self, builder: &Builder<'_>) { let compiler = self.compiler; + let host = self.compiler.host; - builder.ensure(compile::Std::new(compiler, compiler.host)); + builder.ensure(compile::Std::new(compiler, host)); + + let _guard = + builder.msg(Kind::Test, compiler.stage, &format!("book {}", self.name), host, host); // Do a breadth-first traversal of the `src/doc` directory and just run // tests for all files that end in `*.md` @@ -1971,10 +2087,11 @@ impl Step for ErrorIndex { let mut tool = tool::ErrorIndex::command(builder); tool.arg("markdown").arg(&output); - let _guard = + let guard = builder.msg(Kind::Test, compiler.stage, "error-index", compiler.host, compiler.host); let _time = util::timeit(&builder); builder.run_quiet(&mut tool); + drop(guard); // The tests themselves need to link to std, so make sure it is // available. builder.ensure(compile::Std::new(compiler, compiler.host)); @@ -1989,7 +2106,7 @@ fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) -> } } - builder.info(&format!("doc tests for: {}", markdown.display())); + builder.verbose(&format!("doc tests for: {}", markdown.display())); let mut cmd = builder.rustdoc_cmd(compiler); builder.add_rust_test_threads(&mut cmd); // allow for unstable options such as new editions @@ -2003,9 +2120,9 @@ fn markdown_test(builder: &Builder<'_>, compiler: Compiler, markdown: &Path) -> cmd.arg("--test-args").arg(test_args); if builder.config.verbose_tests { - try_run(builder, &mut cmd) + builder.run_delaying_failure(&mut cmd) } else { - try_run_quiet(builder, &mut cmd) + builder.run_quiet_delaying_failure(&mut cmd) } } @@ -2031,7 +2148,7 @@ impl Step for RustcGuide { let src = builder.src.join(relative_path); let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook); - let toolstate = if try_run(builder, rustbook_cmd.arg("linkcheck").arg(&src)) { + let toolstate = if builder.run_delaying_failure(rustbook_cmd.arg("linkcheck").arg(&src)) { ToolState::TestPass } else { ToolState::TestFail @@ -2082,11 +2199,12 @@ impl Step for CrateLibrustc { /// Given a `cargo test` subcommand, add the appropriate flags and run it. /// /// Returns whether the test succeeded. -fn run_cargo_test( +fn run_cargo_test<'a>( cargo: impl Into, libtest_args: &[&str], crates: &[Interned], primary_crate: &str, + description: impl Into>, compiler: Compiler, target: TargetSelection, builder: &Builder<'_>, @@ -2094,6 +2212,20 @@ fn run_cargo_test( let mut cargo = prepare_cargo_test(cargo, libtest_args, crates, primary_crate, compiler, target, builder); let _time = util::timeit(&builder); + let _group = description.into().and_then(|what| { + builder.msg_sysroot_tool(Kind::Test, compiler.stage, what, compiler.host, target) + }); + + #[cfg(feature = "build-metrics")] + builder.metrics.begin_test_suite( + build_helper::metrics::TestSuiteMetadata::CargoPackage { + crates: crates.iter().map(|c| c.to_string()).collect(), + target: target.triple.to_string(), + host: compiler.host.triple.to_string(), + stage: compiler.stage, + }, + builder, + ); add_flags_and_try_run_tests(builder, &mut cargo) } @@ -2109,6 +2241,13 @@ fn prepare_cargo_test( ) -> Command { let mut cargo = cargo.into(); + // Propegate `--bless` if it has not already been set/unset + // Any tools that want to use this should bless if `RUSTC_BLESS` is set to + // anything other than `0`. + if builder.config.cmd.bless() && !cargo.get_envs().any(|v| v.0 == "RUSTC_BLESS") { + cargo.env("RUSTC_BLESS", "Gesundheit"); + } + // Pass in some standard flags then iterate over the graph we've discovered // in `cargo metadata` with the maps above and figure out what `-p` // arguments need to get passed. @@ -2211,7 +2350,8 @@ impl Step for Crate { let target = self.target; let mode = self.mode; - builder.ensure(compile::Std::new(compiler, target)); + // See [field@compile::Std::force_recompile]. + builder.ensure(compile::Std::force_recompile(compiler, target)); builder.ensure(RemoteCopyLibs { compiler, target }); // If we're not doing a full bootstrap but we're testing a stage2 @@ -2225,6 +2365,16 @@ impl Step for Crate { match mode { Mode::Std => { compile::std_cargo(builder, target, compiler.stage, &mut cargo); + // `std_cargo` actually does the wrong thing: it passes `--sysroot build/host/stage2`, + // but we want to use the force-recompile std we just built in `build/host/stage2-test-sysroot`. + // Override it. + if builder.download_rustc() && compiler.stage > 0 { + let sysroot = builder + .out + .join(compiler.host.triple) + .join(format!("stage{}-test-sysroot", compiler.stage)); + cargo.env("RUSTC_SYSROOT", sysroot); + } } Mode::Rustc => { compile::rustc_cargo(builder, &mut cargo, target, compiler.stage); @@ -2232,14 +2382,16 @@ impl Step for Crate { _ => panic!("can only test libraries"), }; - let _guard = builder.msg( - builder.kind, - compiler.stage, - crate_description(&self.crates), - compiler.host, + run_cargo_test( + cargo, + &[], + &self.crates, + &self.crates[0], + &*crate_description(&self.crates), + compiler, target, + builder, ); - run_cargo_test(cargo, &[], &self.crates, &self.crates[0], compiler, target, builder); } } @@ -2276,6 +2428,11 @@ impl Step for CrateRustdoc { // isn't really necessary. builder.compiler_for(builder.top_stage, target, target) }; + // NOTE: normally `ensure(Rustc)` automatically runs `ensure(Std)` for us. However, when + // using `download-rustc`, the rustc_private artifacts may be in a *different sysroot* from + // the target rustdoc (`ci-rustc-sysroot` vs `stage2`). In that case, we need to ensure this + // explicitly to make sure it ends up in the stage2 sysroot. + builder.ensure(compile::Std::new(compiler, target)); builder.ensure(compile::Rustc::new(compiler, target)); let mut cargo = tool::prepare_tool_cargo( @@ -2327,12 +2484,12 @@ impl Step for CrateRustdoc { dylib_path.insert(0, PathBuf::from(&*libdir)); cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap()); - let _guard = builder.msg(builder.kind, compiler.stage, "rustdoc", compiler.host, target); run_cargo_test( cargo, &[], &[INTERNER.intern_str("rustdoc:0.0.0")], "rustdoc", + "rustdoc", compiler, target, builder, @@ -2388,13 +2545,12 @@ impl Step for CrateRustdocJsonTypes { &[] }; - let _guard = - builder.msg(builder.kind, compiler.stage, "rustdoc-json-types", compiler.host, target); run_cargo_test( cargo, libtest_args, &[INTERNER.intern_str("rustdoc-json-types")], "rustdoc-json-types", + "rustdoc-json-types", compiler, target, builder, @@ -2433,7 +2589,7 @@ impl Step for RemoteCopyLibs { builder.ensure(compile::Std::new(compiler, target)); - builder.info(&format!("REMOTE copy libs to emulator ({})", target)); + builder.info(&format!("REMOTE copy libs to emulator ({target})")); let server = builder.ensure(tool::RemoteTestServer { compiler, target }); @@ -2514,6 +2670,9 @@ impl Step for Distcheck { let toml = dir.join("rust-src/lib/rustlib/src/rust/library/std/Cargo.toml"); builder.run( Command::new(&builder.initial_cargo) + // Will read the libstd Cargo.toml + // which uses the unstable `public-dependency` feature. + .env("RUSTC_BOOTSTRAP", "1") .arg("generate-lockfile") .arg("--manifest-path") .arg(&toml) @@ -2532,12 +2691,20 @@ impl Step for Bootstrap { /// Tests the build system itself. fn run(self, builder: &Builder<'_>) { - let mut check_bootstrap = Command::new(&builder.python()); - check_bootstrap.arg("bootstrap_test.py").current_dir(builder.src.join("src/bootstrap/")); - try_run(builder, &mut check_bootstrap); - let host = builder.config.build; let compiler = builder.compiler(0, host); + let _guard = builder.msg(Kind::Test, 0, "bootstrap", host, host); + + let mut check_bootstrap = Command::new(&builder.python()); + check_bootstrap + .args(["-m", "unittest", "bootstrap_test.py"]) + .env("BUILD_DIR", &builder.out) + .env("BUILD_PLATFORM", &builder.build.build.triple) + .current_dir(builder.src.join("src/bootstrap/")); + // NOTE: we intentionally don't pass test_args here because the args for unittest and cargo test are mutually incompatible. + // Use `python -m unittest` manually if you want to pass arguments. + builder.run_delaying_failure(&mut check_bootstrap); + let mut cmd = Command::new(&builder.initial_cargo); cmd.arg("test") .current_dir(builder.src.join("src/bootstrap")) @@ -2554,7 +2721,7 @@ impl Step for Bootstrap { } // rustbuild tests are racy on directory creation so just run them one at a time. // Since there's not many this shouldn't be a problem. - run_cargo_test(cmd, &["--test-threads=1"], &[], "bootstrap", compiler, host, builder); + run_cargo_test(cmd, &["--test-threads=1"], &[], "bootstrap", None, compiler, host, builder); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { @@ -2605,8 +2772,14 @@ impl Step for TierCheck { cargo.arg("--verbose"); } - builder.info("platform support check"); - try_run(builder, &mut cargo.into()); + let _guard = builder.msg( + Kind::Test, + self.compiler.stage, + "platform support check", + self.compiler.host, + self.compiler.host, + ); + builder.run_delaying_failure(&mut cargo.into()); } } @@ -2653,8 +2826,6 @@ impl Step for RustInstaller { /// Ensure the version placeholder replacement tool builds fn run(self, builder: &Builder<'_>) { - builder.info("test rust-installer"); - let bootstrap_host = builder.config.build; let compiler = builder.compiler(0, bootstrap_host); let cargo = tool::prepare_tool_cargo( @@ -2667,7 +2838,15 @@ impl Step for RustInstaller { SourceType::InTree, &[], ); - run_cargo_test(cargo, &[], &[], "installer", compiler, bootstrap_host, builder); + + let _guard = builder.msg( + Kind::Test, + compiler.stage, + "rust-installer", + bootstrap_host, + bootstrap_host, + ); + run_cargo_test(cargo, &[], &[], "installer", None, compiler, bootstrap_host, builder); // We currently don't support running the test.sh script outside linux(?) environments. // Eventually this should likely migrate to #[test]s in rust-installer proper rather than a @@ -2686,7 +2865,7 @@ impl Step for RustInstaller { cmd.env("CARGO", &builder.initial_cargo); cmd.env("RUSTC", &builder.initial_rustc); cmd.env("TMP_DIR", &tmpdir); - try_run(builder, &mut cmd); + builder.run_delaying_failure(&mut cmd); } fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index f13d365e3754d..e6d27757ac661 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -34,6 +34,7 @@ struct ToolBuild { } impl Builder<'_> { + #[track_caller] fn msg_tool( &self, mode: Mode, @@ -107,7 +108,8 @@ impl Step for ToolBuild { ); let mut cargo = Command::from(cargo); - let is_expected = builder.try_run(&mut cargo); + #[allow(deprecated)] // we check this in `is_optional_tool` in a second + let is_expected = builder.config.try_run(&mut cargo).is_ok(); builder.save_toolstate( tool, @@ -116,7 +118,7 @@ impl Step for ToolBuild { if !is_expected { if !is_optional_tool { - crate::detail_exit(1); + crate::exit!(1); } else { None } @@ -289,7 +291,7 @@ bootstrap_tool!( Compiletest, "src/tools/compiletest", "compiletest", is_unstable_tool = true, allow_features = "test"; BuildManifest, "src/tools/build-manifest", "build-manifest"; RemoteTestClient, "src/tools/remote-test-client", "remote-test-client"; - RustInstaller, "src/tools/rust-installer", "rust-installer", is_external_tool = true; + RustInstaller, "src/tools/rust-installer", "rust-installer"; RustdocTheme, "src/tools/rustdoc-themes", "rustdoc-themes"; ExpandYamlAnchors, "src/tools/expand-yaml-anchors", "expand-yaml-anchors"; LintDocs, "src/tools/lint-docs", "lint-docs"; @@ -302,6 +304,8 @@ bootstrap_tool!( GenerateCopyright, "src/tools/generate-copyright", "generate-copyright"; SuggestTests, "src/tools/suggest-tests", "suggest-tests"; GenerateWindowsSys, "src/tools/generate-windows-sys", "generate-windows-sys"; + RustdocGUITest, "src/tools/rustdoc-gui-test", "rustdoc-gui-test", is_unstable_tool = true, allow_features = "test"; + OptimizedDist, "src/tools/opt-dist", "opt-dist"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] @@ -554,39 +558,6 @@ impl Step for Cargo { allow_features: "", }) .expect("expected to build -- essential tool"); - - let build_cred = |name, path| { - // These credential helpers are currently experimental. - // Any build failures will be ignored. - let _ = builder.ensure(ToolBuild { - compiler: self.compiler, - target: self.target, - tool: name, - mode: Mode::ToolRustc, - path, - is_optional_tool: true, - source_type: SourceType::Submodule, - extra_features: Vec::new(), - allow_features: "", - }); - }; - - if self.target.contains("windows") { - build_cred( - "cargo-credential-wincred", - "src/tools/cargo/credential/cargo-credential-wincred", - ); - } - if self.target.contains("apple-darwin") { - build_cred( - "cargo-credential-macos-keychain", - "src/tools/cargo/credential/cargo-credential-macos-keychain", - ); - } - build_cred( - "cargo-credential-1password", - "src/tools/cargo/credential/cargo-credential-1password", - ); cargo_bin_path } } @@ -710,7 +681,7 @@ impl Step for RustAnalyzerProcMacroSrv { tool: "rust-analyzer-proc-macro-srv", mode: Mode::ToolStd, path: "src/tools/rust-analyzer/crates/proc-macro-srv-cli", - extra_features: vec!["proc-macro-srv/sysroot-abi".to_owned()], + extra_features: vec!["sysroot-abi".to_owned()], is_optional_tool: false, source_type: SourceType::InTree, allow_features: RustAnalyzer::ALLOW_FEATURES, @@ -854,7 +825,7 @@ impl<'a> Builder<'a> { if compiler.host.contains("msvc") { let curpaths = env::var_os("PATH").unwrap_or_default(); let curpaths = env::split_paths(&curpaths).collect::>(); - for &(ref k, ref v) in self.cc[&compiler.host].env() { + for &(ref k, ref v) in self.cc.borrow()[&compiler.host].env() { if k != "PATH" { continue; } diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs index 7aab88a1a7364..308023537d58d 100644 --- a/src/bootstrap/toolstate.rs +++ b/src/bootstrap/toolstate.rs @@ -83,15 +83,15 @@ static NIGHTLY_TOOLS: &[(&str, &str)] = &[ fn print_error(tool: &str, submodule: &str) { eprintln!(); - eprintln!("We detected that this PR updated '{}', but its tests failed.", tool); + eprintln!("We detected that this PR updated '{tool}', but its tests failed."); eprintln!(); - eprintln!("If you do intend to update '{}', please check the error messages above and", tool); + eprintln!("If you do intend to update '{tool}', please check the error messages above and"); eprintln!("commit another update."); eprintln!(); - eprintln!("If you do NOT intend to update '{}', please ensure you did not accidentally", tool); - eprintln!("change the submodule at '{}'. You may ask your reviewer for the", submodule); + eprintln!("If you do NOT intend to update '{tool}', please ensure you did not accidentally"); + eprintln!("change the submodule at '{submodule}'. You may ask your reviewer for the"); eprintln!("proper steps."); - crate::detail_exit(3); + crate::exit!(3); } fn check_changed_files(toolstates: &HashMap, ToolState>) { @@ -105,8 +105,8 @@ fn check_changed_files(toolstates: &HashMap, ToolState>) { let output = match output { Ok(o) => o, Err(e) => { - eprintln!("Failed to get changed files: {:?}", e); - crate::detail_exit(1); + eprintln!("Failed to get changed files: {e:?}"); + crate::exit!(1); } }; @@ -114,12 +114,12 @@ fn check_changed_files(toolstates: &HashMap, ToolState>) { for (tool, submodule) in STABLE_TOOLS.iter().chain(NIGHTLY_TOOLS.iter()) { let changed = output.lines().any(|l| l.starts_with('M') && l.ends_with(submodule)); - eprintln!("Verifying status of {}...", tool); + eprintln!("Verifying status of {tool}..."); if !changed { continue; } - eprintln!("This PR updated '{}', verifying if status is 'test-pass'...", submodule); + eprintln!("This PR updated '{submodule}', verifying if status is 'test-pass'..."); if toolstates[*tool] != ToolState::TestPass { print_error(tool, submodule); } @@ -172,12 +172,12 @@ impl Step for ToolStateCheck { for (tool, _) in STABLE_TOOLS.iter().chain(NIGHTLY_TOOLS.iter()) { if !toolstates.contains_key(*tool) { did_error = true; - eprintln!("error: Tool `{}` was not recorded in tool state.", tool); + eprintln!("error: Tool `{tool}` was not recorded in tool state."); } } if did_error { - crate::detail_exit(1); + crate::exit!(1); } check_changed_files(&toolstates); @@ -190,7 +190,7 @@ impl Step for ToolStateCheck { if state != ToolState::TestPass { if !is_nightly { did_error = true; - eprintln!("error: Tool `{}` should be test-pass but is {}", tool, state); + eprintln!("error: Tool `{tool}` should be test-pass but is {state}"); } else if in_beta_week { let old_state = old_toolstate .iter() @@ -200,17 +200,15 @@ impl Step for ToolStateCheck { if state < old_state { did_error = true; eprintln!( - "error: Tool `{}` has regressed from {} to {} during beta week.", - tool, old_state, state + "error: Tool `{tool}` has regressed from {old_state} to {state} during beta week." ); } else { // This warning only appears in the logs, which most // people won't read. It's mostly here for testing and // debugging. eprintln!( - "warning: Tool `{}` is not test-pass (is `{}`), \ - this should be fixed before beta is branched.", - tool, state + "warning: Tool `{tool}` is not test-pass (is `{state}`), \ + this should be fixed before beta is branched." ); } } @@ -223,7 +221,7 @@ impl Step for ToolStateCheck { } if did_error { - crate::detail_exit(1); + crate::exit!(1); } if builder.config.channel == "nightly" && env::var_os("TOOLSTATE_PUBLISH").is_some() { @@ -262,6 +260,8 @@ impl Builder<'_> { /// `rust.save-toolstates` in `config.toml`. If unspecified, nothing will be /// done. The file is updated immediately after this function completes. pub fn save_toolstate(&self, tool: &str, state: ToolState) { + use std::io::Write; + // If we're in a dry run setting we don't want to save toolstates as // that means if we e.g. panic down the line it'll look like we tested // everything (but we actually haven't). @@ -286,7 +286,8 @@ impl Builder<'_> { current_toolstates.insert(tool.into(), state); t!(file.seek(SeekFrom::Start(0))); t!(file.set_len(0)); - t!(serde_json::to_writer(file, ¤t_toolstates)); + t!(serde_json::to_writer(&file, ¤t_toolstates)); + t!(writeln!(file)); // make sure this ends in a newline } } } @@ -320,7 +321,7 @@ fn checkout_toolstate_repo() { Err(_) => false, }; if !success { - panic!("git clone unsuccessful (status: {:?})", status); + panic!("git clone unsuccessful (status: {status:?})"); } } @@ -333,7 +334,7 @@ fn prepare_toolstate_config(token: &str) { Err(_) => false, }; if !success { - panic!("git config key={} value={} failed (status: {:?})", key, value, status); + panic!("git config key={key} value={value} failed (status: {status:?})"); } } @@ -343,7 +344,7 @@ fn prepare_toolstate_config(token: &str) { git_config("user.name", "Rust Toolstate Update"); git_config("credential.helper", "store"); - let credential = format!("https://{}:x-oauth-basic@github.com\n", token,); + let credential = format!("https://{token}:x-oauth-basic@github.com\n",); let git_credential_path = PathBuf::from(t!(env::var("HOME"))).join(".git-credentials"); t!(fs::write(&git_credential_path, credential)); } diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs index 2e1adbf63bb10..3c4a21434c00b 100644 --- a/src/bootstrap/util.rs +++ b/src/bootstrap/util.rs @@ -3,6 +3,7 @@ //! Simple things like testing the various filesystem operations here and there, //! not a lot of interesting happenings here unfortunately. +use build_helper::util::{fail, try_run}; use std::env; use std::fs; use std::io; @@ -45,9 +46,9 @@ pub use t; /// executable for a particular target. pub fn exe(name: &str, target: TargetSelection) -> String { if target.contains("windows") { - format!("{}.exe", name) + format!("{name}.exe") } else if target.contains("uefi") { - format!("{}.efi", name) + format!("{name}.efi") } else { name.to_string() } @@ -133,17 +134,17 @@ pub(crate) fn program_out_of_date(stamp: &Path, key: &str) -> bool { /// Symlinks two directories, using junctions on Windows and normal symlinks on /// Unix. -pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> { +pub fn symlink_dir(config: &Config, original: &Path, link: &Path) -> io::Result<()> { if config.dry_run() { return Ok(()); } - let _ = fs::remove_dir(dest); - return symlink_dir_inner(src, dest); + let _ = fs::remove_dir(link); + return symlink_dir_inner(original, link); #[cfg(not(windows))] - fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> { + fn symlink_dir_inner(original: &Path, link: &Path) -> io::Result<()> { use std::os::unix::fs; - fs::symlink(src, dest) + fs::symlink(original, link) } #[cfg(windows)] @@ -152,18 +153,6 @@ pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> { } } -/// The CI environment rustbuild is running in. This mainly affects how the logs -/// are printed. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum CiEnv { - /// Not a CI environment. - None, - /// The Azure Pipelines environment, for Linux (including Docker), Windows, and macOS builds. - AzurePipelines, - /// The GitHub Actions environment, for Linux (including Docker), Windows and macOS builds. - GitHubActions, -} - pub fn forcing_clang_based_tests() -> bool { if let Some(var) = env::var_os("RUSTBUILD_FORCE_CLANG_BASED_TESTS") { match &var.to_string_lossy().to_lowercase()[..] { @@ -172,9 +161,8 @@ pub fn forcing_clang_based_tests() -> bool { other => { // Let's make sure typos don't go unnoticed panic!( - "Unrecognized option '{}' set in \ - RUSTBUILD_FORCE_CLANG_BASED_TESTS", - other + "Unrecognized option '{other}' set in \ + RUSTBUILD_FORCE_CLANG_BASED_TESTS" ) } } @@ -229,39 +217,23 @@ pub fn is_valid_test_suite_arg<'a, P: AsRef>( } pub fn run(cmd: &mut Command, print_cmd_on_fail: bool) { - if !try_run(cmd, print_cmd_on_fail) { - crate::detail_exit(1); - } -} - -pub fn try_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool { - let status = match cmd.status() { - Ok(status) => status, - Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), - }; - if !status.success() && print_cmd_on_fail { - println!( - "\n\ncommand did not execute successfully: {:?}\n\ - expected success, got: {}\n\n", - cmd, status - ); + if try_run(cmd, print_cmd_on_fail).is_err() { + crate::exit!(1); } - status.success() } pub fn check_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool { let status = match cmd.status() { Ok(status) => status, Err(e) => { - println!("failed to execute command: {:?}\nerror: {}", cmd, e); + println!("failed to execute command: {cmd:?}\nerror: {e}"); return false; } }; if !status.success() && print_cmd_on_fail { println!( - "\n\ncommand did not execute successfully: {:?}\n\ - expected success, got: {}\n\n", - cmd, status + "\n\ncommand did not execute successfully: {cmd:?}\n\ + expected success, got: {status}\n\n" ); } status.success() @@ -269,14 +241,14 @@ pub fn check_run(cmd: &mut Command, print_cmd_on_fail: bool) -> bool { pub fn run_suppressed(cmd: &mut Command) { if !try_run_suppressed(cmd) { - crate::detail_exit(1); + crate::exit!(1); } } pub fn try_run_suppressed(cmd: &mut Command) -> bool { let output = match cmd.output() { Ok(status) => status, - Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), + Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")), }; if !output.status.success() { println!( @@ -309,7 +281,7 @@ pub fn make(host: &str) -> PathBuf { pub fn output(cmd: &mut Command) -> String { let output = match cmd.stderr(Stdio::inherit()).output() { Ok(status) => status, - Err(e) => fail(&format!("failed to execute command: {:?}\nerror: {}", cmd, e)), + Err(e) => fail(&format!("failed to execute command: {cmd:?}\nerror: {e}")), }; if !output.status.success() { panic!( @@ -324,7 +296,7 @@ pub fn output(cmd: &mut Command) -> String { pub fn output_result(cmd: &mut Command) -> Result { let output = match cmd.stderr(Stdio::inherit()).output() { Ok(status) => status, - Err(e) => return Err(format!("failed to run command: {:?}: {}", cmd, e)), + Err(e) => return Err(format!("failed to run command: {cmd:?}: {e}")), }; if !output.status.success() { return Err(format!( @@ -354,7 +326,7 @@ pub fn up_to_date(src: &Path, dst: &Path) -> bool { let threshold = mtime(dst); let meta = match fs::metadata(src) { Ok(meta) => meta, - Err(e) => panic!("source {:?} failed to get metadata: {}", src, e), + Err(e) => panic!("source {src:?} failed to get metadata: {e}"), }; if meta.is_dir() { dir_up_to_date(src, threshold) @@ -374,11 +346,6 @@ fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool { }) } -fn fail(s: &str) -> ! { - eprintln!("\n\n{}\n\n", s); - crate::detail_exit(1); -} - /// Copied from `std::path::absolute` until it stabilizes. /// /// FIXME: this shouldn't exist. @@ -488,7 +455,7 @@ fn absolute_windows(path: &std::path::Path) -> std::io::Result /// /// When `clang-cl` is used with instrumentation, we need to add clang's runtime library resource /// directory to the linker flags, otherwise there will be linker errors about the profiler runtime @@ -524,3 +491,7 @@ pub fn lld_flag_no_threads(is_windows: bool) -> &'static str { }); if is_windows { windows } else { other } } + +pub fn dir_is_empty(dir: &Path) -> bool { + t!(std::fs::read_dir(dir)).next().is_none() +} diff --git a/src/ci/channel b/src/ci/channel index bf867e0ae5b6c..2bf5ad0447d33 100644 --- a/src/ci/channel +++ b/src/ci/channel @@ -1 +1 @@ -nightly +stable diff --git a/src/ci/cpu-usage-over-time.py b/src/ci/cpu-usage-over-time.py old mode 100644 new mode 100755 diff --git a/src/ci/docker/README.md b/src/ci/docker/README.md index e799d7c968893..b83b198780ba7 100644 --- a/src/ci/docker/README.md +++ b/src/ci/docker/README.md @@ -14,7 +14,8 @@ for example: ./src/ci/docker/run.sh x86_64-gnu ``` -Images will output artifacts in an `obj` dir at the root of a repository. +Images will output artifacts in an `obj` dir at the root of a repository. Note +that the script will overwrite the contents of this directory. To match conditions in rusts CI, also set the environment variable `DEPLOY=1`, e.g.: ``` @@ -270,7 +271,7 @@ For targets: `loongarch64-unknown-linux-gnu` - Operating System > Linux kernel version = 5.19.16 - Binary utilities > Version of binutils = 2.40 - C-library > glibc version = 2.36 -- C compiler > gcc version = 12.2.0 +- C compiler > gcc version = 13.1.0 - C compiler > C++ = ENABLE -- to cross compile LLVM ### `mips-linux-gnu.defconfig` diff --git a/src/ci/docker/host-aarch64/aarch64-gnu/Dockerfile b/src/ci/docker/host-aarch64/aarch64-gnu/Dockerfile index 699938c371842..85b0f3b10818e 100644 --- a/src/ci/docker/host-aarch64/aarch64-gnu/Dockerfile +++ b/src/ci/docker/host-aarch64/aarch64-gnu/Dockerfile @@ -1,6 +1,7 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ make \ ninja-build \ diff --git a/src/ci/docker/host-x86_64/arm-android/Dockerfile b/src/ci/docker/host-x86_64/arm-android/Dockerfile index b6b4fdc67a949..db11700af2815 100644 --- a/src/ci/docker/host-x86_64/arm-android/Dockerfile +++ b/src/ci/docker/host-x86_64/arm-android/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.10 +FROM ubuntu:23.04 ARG DEBIAN_FRONTEND=noninteractive COPY scripts/android-base-apt-get.sh /scripts/ diff --git a/src/ci/docker/host-x86_64/armhf-gnu/Dockerfile b/src/ci/docker/host-x86_64/armhf-gnu/Dockerfile index 57e63cd39d2f8..015caafc5bd66 100644 --- a/src/ci/docker/host-x86_64/armhf-gnu/Dockerfile +++ b/src/ci/docker/host-x86_64/armhf-gnu/Dockerfile @@ -1,6 +1,7 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 -RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update -y && apt-get install -y --no-install-recommends \ bc \ bzip2 \ ca-certificates \ @@ -13,6 +14,7 @@ RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y --no- git \ libc6-dev \ libc6-dev-armhf-cross \ + libssl-dev \ make \ ninja-build \ python3 \ @@ -33,14 +35,14 @@ WORKDIR /build # the kernel. This file was generated by running `make vexpress_defconfig` # followed by `make menuconfig` and then enabling the IPv6 protocol page. COPY host-x86_64/armhf-gnu/vexpress_config /build/.config -RUN curl https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.4.253.tar.xz | \ +RUN curl https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.14.320.tar.xz | \ tar xJf - && \ - cd /build/linux-4.4.253 && \ + cd /build/linux-4.14.320 && \ cp /build/.config . && \ make -j$(nproc) all && \ cp arch/arm/boot/zImage /tmp && \ cd /build && \ - rm -rf linux-4.4.253 + rm -rf linux-4.14.320 # Compile an instance of busybox as this provides a lightweight system and init # binary which we will boot into. Only trick here is configuring busybox to @@ -58,7 +60,7 @@ RUN curl https://www.busybox.net/downloads/busybox-1.32.1.tar.bz2 | tar xjf - && # Download the ubuntu rootfs, which we'll use as a chroot for all our tests. WORKDIR /tmp RUN mkdir rootfs/ubuntu -RUN curl https://cdimage.ubuntu.com/ubuntu-base/releases/20.04/release/ubuntu-base-20.04.1-base-armhf.tar.gz | \ +RUN curl https://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04.2-base-armhf.tar.gz | \ tar xzf - -C rootfs/ubuntu && \ cd rootfs && mkdir proc sys dev etc etc/init.d diff --git a/src/ci/docker/host-x86_64/armhf-gnu/vexpress_config b/src/ci/docker/host-x86_64/armhf-gnu/vexpress_config index b39e5dcf38d70..54c3ae18d60f9 100644 --- a/src/ci/docker/host-x86_64/armhf-gnu/vexpress_config +++ b/src/ci/docker/host-x86_64/armhf-gnu/vexpress_config @@ -1,6 +1,6 @@ # # Automatically generated file; DO NOT EDIT. -# Linux/arm 4.4.253 Kernel Configuration +# Linux/arm 4.14.320 Kernel Configuration # CONFIG_ARM=y CONFIG_ARM_HAS_SG_CHAIN=y @@ -49,8 +49,8 @@ CONFIG_SYSVIPC=y CONFIG_SYSVIPC_SYSCTL=y # CONFIG_POSIX_MQUEUE is not set CONFIG_CROSS_MEMORY_ATTACH=y -# CONFIG_FHANDLE is not set -CONFIG_USELIB=y +CONFIG_FHANDLE=y +# CONFIG_USELIB is not set # CONFIG_AUDIT is not set CONFIG_HAVE_ARCH_AUDITSYSCALL=y @@ -60,6 +60,7 @@ CONFIG_HAVE_ARCH_AUDITSYSCALL=y CONFIG_GENERIC_IRQ_PROBE=y CONFIG_GENERIC_IRQ_SHOW=y CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y CONFIG_GENERIC_IRQ_MIGRATION=y CONFIG_HARDIRQS_SW_RESEND=y CONFIG_IRQ_DOMAIN=y @@ -68,6 +69,8 @@ CONFIG_HANDLE_DOMAIN_IRQ=y # CONFIG_IRQ_DOMAIN_DEBUG is not set CONFIG_IRQ_FORCED_THREADING=y CONFIG_SPARSE_IRQ=y +# CONFIG_GENERIC_IRQ_DEBUGFS is not set +CONFIG_ARCH_CLOCKSOURCE_DATA=y CONFIG_GENERIC_TIME_VSYSCALL=y CONFIG_GENERIC_CLOCKEVENTS=y CONFIG_ARCH_HAS_TICK_BROADCAST=y @@ -97,28 +100,31 @@ CONFIG_TICK_CPU_ACCOUNTING=y CONFIG_TREE_RCU=y # CONFIG_RCU_EXPERT is not set CONFIG_SRCU=y +CONFIG_TREE_SRCU=y # CONFIG_TASKS_RCU is not set CONFIG_RCU_STALL_COMMON=y -# CONFIG_TREE_RCU_TRACE is not set -# CONFIG_RCU_EXPEDITE_BOOT is not set +CONFIG_RCU_NEED_SEGCBLIST=y CONFIG_BUILD_BIN2C=y CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y CONFIG_LOG_BUF_SHIFT=14 CONFIG_LOG_CPU_MAX_BUF_SHIFT=12 +CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13 CONFIG_GENERIC_SCHED_CLOCK=y CONFIG_CGROUPS=y -# CONFIG_CGROUP_DEBUG is not set -# CONFIG_CGROUP_FREEZER is not set +# CONFIG_MEMCG is not set +# CONFIG_BLK_CGROUP is not set +# CONFIG_CGROUP_SCHED is not set # CONFIG_CGROUP_PIDS is not set -# CONFIG_CGROUP_DEVICE is not set +# CONFIG_CGROUP_RDMA is not set +# CONFIG_CGROUP_FREEZER is not set CONFIG_CPUSETS=y CONFIG_PROC_PID_CPUSET=y +# CONFIG_CGROUP_DEVICE is not set # CONFIG_CGROUP_CPUACCT is not set -# CONFIG_MEMCG is not set # CONFIG_CGROUP_PERF is not set -# CONFIG_CGROUP_SCHED is not set -# CONFIG_BLK_CGROUP is not set +# CONFIG_CGROUP_DEBUG is not set +# CONFIG_SOCK_CGROUP_DATA is not set # CONFIG_CHECKPOINT_RESTORE is not set CONFIG_NAMESPACES=y # CONFIG_UTS_NS is not set @@ -149,13 +155,19 @@ CONFIG_MULTIUSER=y # CONFIG_SGETMASK_SYSCALL is not set CONFIG_SYSFS_SYSCALL=y # CONFIG_SYSCTL_SYSCALL is not set +CONFIG_POSIX_TIMERS=y CONFIG_KALLSYMS=y # CONFIG_KALLSYMS_ALL is not set +# CONFIG_KALLSYMS_ABSOLUTE_PERCPU is not set +CONFIG_KALLSYMS_BASE_RELATIVE=y CONFIG_PRINTK=y +CONFIG_PRINTK_NMI=y CONFIG_BUG=y CONFIG_ELF_CORE=y CONFIG_BASE_FULL=y CONFIG_FUTEX=y +CONFIG_FUTEX_PI=y +CONFIG_HAVE_FUTEX_CMPXCHG=y CONFIG_EPOLL=y CONFIG_SIGNALFD=y CONFIG_TIMERFD=y @@ -169,6 +181,7 @@ CONFIG_MEMBARRIER=y # CONFIG_EMBEDDED is not set CONFIG_HAVE_PERF_EVENTS=y CONFIG_PERF_USE_VMALLOC=y +# CONFIG_PC104 is not set # # Kernel Performance Events And Counters @@ -180,25 +193,30 @@ CONFIG_SLUB_DEBUG=y CONFIG_COMPAT_BRK=y # CONFIG_SLAB is not set CONFIG_SLUB=y +CONFIG_SLAB_MERGE_DEFAULT=y +# CONFIG_SLAB_FREELIST_RANDOM is not set +# CONFIG_SLAB_FREELIST_HARDENED is not set CONFIG_SLUB_CPU_PARTIAL=y # CONFIG_SYSTEM_DATA_VERIFICATION is not set CONFIG_PROFILING=y +CONFIG_TRACEPOINTS=y CONFIG_OPROFILE=y CONFIG_HAVE_OPROFILE=y # CONFIG_KPROBES is not set # CONFIG_JUMP_LABEL is not set -# CONFIG_UPROBES is not set +CONFIG_UPROBES=y # CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y CONFIG_ARCH_USE_BUILTIN_BSWAP=y CONFIG_HAVE_KPROBES=y CONFIG_HAVE_KRETPROBES=y CONFIG_HAVE_OPTPROBES=y +CONFIG_HAVE_NMI=y CONFIG_HAVE_ARCH_TRACEHOOK=y -CONFIG_HAVE_DMA_ATTRS=y CONFIG_HAVE_DMA_CONTIGUOUS=y CONFIG_GENERIC_SMP_IDLE_THREAD=y CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_ARCH_HAS_SET_MEMORY=y CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y CONFIG_HAVE_CLK=y CONFIG_HAVE_DMA_API_DEBUG=y @@ -208,20 +226,39 @@ CONFIG_HAVE_PERF_USER_STACK_DUMP=y CONFIG_HAVE_ARCH_JUMP_LABEL=y CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_HAVE_GCC_PLUGINS=y +# CONFIG_GCC_PLUGINS is not set CONFIG_HAVE_CC_STACKPROTECTOR=y # CONFIG_CC_STACKPROTECTOR is not set CONFIG_CC_STACKPROTECTOR_NONE=y # CONFIG_CC_STACKPROTECTOR_REGULAR is not set # CONFIG_CC_STACKPROTECTOR_STRONG is not set +CONFIG_THIN_ARCHIVES=y CONFIG_HAVE_CONTEXT_TRACKING=y CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y CONFIG_HAVE_MOD_ARCH_SPECIFIC=y CONFIG_MODULES_USE_ELF_REL=y CONFIG_ARCH_HAS_ELF_RANDOMIZE=y +CONFIG_HAVE_ARCH_MMAP_RND_BITS=y +CONFIG_HAVE_EXIT_THREAD=y +CONFIG_ARCH_MMAP_RND_BITS_MIN=8 +CONFIG_ARCH_MMAP_RND_BITS_MAX=15 +CONFIG_ARCH_MMAP_RND_BITS=8 +# CONFIG_HAVE_ARCH_HASH is not set +# CONFIG_ISA_BUS_API is not set CONFIG_CLONE_BACKWARDS=y CONFIG_OLD_SIGSUSPEND3=y CONFIG_OLD_SIGACTION=y +# CONFIG_CPU_NO_EFFICIENT_FFS is not set +# CONFIG_HAVE_ARCH_VMAP_STACK is not set +CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y +CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT=y +CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y +CONFIG_STRICT_KERNEL_RWX=y +CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y +CONFIG_STRICT_MODULE_RWX=y +# CONFIG_REFCOUNT_FULL is not set # # GCOV-based kernel profiling @@ -240,13 +277,19 @@ CONFIG_MODULE_UNLOAD=y # CONFIG_MODULE_SRCVERSION_ALL is not set # CONFIG_MODULE_SIG is not set # CONFIG_MODULE_COMPRESS is not set +# CONFIG_TRIM_UNUSED_KSYMS is not set CONFIG_MODULES_TREE_LOOKUP=y CONFIG_BLOCK=y # CONFIG_LBDAF is not set +CONFIG_BLK_SCSI_REQUEST=y # CONFIG_BLK_DEV_BSG is not set # CONFIG_BLK_DEV_BSGLIB is not set # CONFIG_BLK_DEV_INTEGRITY is not set +# CONFIG_BLK_DEV_ZONED is not set # CONFIG_BLK_CMDLINE_PARSER is not set +# CONFIG_BLK_WBT is not set +CONFIG_BLK_DEBUG_FS=y +# CONFIG_BLK_SED_OPAL is not set # # Partition Types @@ -254,6 +297,7 @@ CONFIG_BLOCK=y # CONFIG_PARTITION_ADVANCED is not set CONFIG_MSDOS_PARTITION=y CONFIG_EFI_PARTITION=y +CONFIG_BLK_MQ_VIRTIO=y # # IO Schedulers @@ -263,6 +307,9 @@ CONFIG_IOSCHED_NOOP=y # CONFIG_IOSCHED_CFQ is not set CONFIG_DEFAULT_NOOP=y CONFIG_DEFAULT_IOSCHED="noop" +CONFIG_MQ_IOSCHED_DEADLINE=y +CONFIG_MQ_IOSCHED_KYBER=y +# CONFIG_IOSCHED_BFQ is not set CONFIG_INLINE_SPIN_UNLOCK_IRQ=y CONFIG_INLINE_READ_UNLOCK=y CONFIG_INLINE_READ_UNLOCK_IRQ=y @@ -279,10 +326,6 @@ CONFIG_FREEZER=y # CONFIG_MMU=y CONFIG_ARCH_MULTIPLATFORM=y -# CONFIG_ARCH_REALVIEW is not set -# CONFIG_ARCH_VERSATILE is not set -# CONFIG_ARCH_CLPS711X is not set -# CONFIG_ARCH_GEMINI is not set # CONFIG_ARCH_EBSA110 is not set # CONFIG_ARCH_EP93XX is not set # CONFIG_ARCH_FOOTBRIDGE is not set @@ -292,9 +335,6 @@ CONFIG_ARCH_MULTIPLATFORM=y # CONFIG_ARCH_IOP33X is not set # CONFIG_ARCH_IXP4XX is not set # CONFIG_ARCH_DOVE is not set -# CONFIG_ARCH_MV78XX0 is not set -# CONFIG_ARCH_ORION5X is not set -# CONFIG_ARCH_MMP is not set # CONFIG_ARCH_KS8695 is not set # CONFIG_ARCH_W90X900 is not set # CONFIG_ARCH_LPC32XX is not set @@ -302,7 +342,6 @@ CONFIG_ARCH_MULTIPLATFORM=y # CONFIG_ARCH_RPC is not set # CONFIG_ARCH_SA1100 is not set # CONFIG_ARCH_S3C24XX is not set -# CONFIG_ARCH_S3C64XX is not set # CONFIG_ARCH_DAVINCI is not set # CONFIG_ARCH_OMAP1 is not set @@ -319,7 +358,9 @@ CONFIG_ARCH_MULTI_V6_V7=y # CONFIG_ARCH_MULTI_CPU_AUTO is not set # CONFIG_ARCH_VIRT is not set # CONFIG_ARCH_MVEBU is not set +# CONFIG_ARCH_ACTIONS is not set # CONFIG_ARCH_ALPINE is not set +# CONFIG_ARCH_ARTPEC is not set # CONFIG_ARCH_AT91 is not set # CONFIG_ARCH_BCM is not set # CONFIG_ARCH_BERLIN is not set @@ -340,16 +381,19 @@ CONFIG_ARCH_MULTI_V6_V7=y # CONFIG_SOC_AM33XX is not set # CONFIG_SOC_AM43XX is not set # CONFIG_SOC_DRA7XX is not set +# CONFIG_ARCH_MMP is not set # CONFIG_ARCH_QCOM is not set +# CONFIG_ARCH_REALVIEW is not set # CONFIG_ARCH_ROCKCHIP is not set # CONFIG_ARCH_SOCFPGA is not set # CONFIG_PLAT_SPEAR is not set # CONFIG_ARCH_STI is not set # CONFIG_ARCH_S5PV210 is not set # CONFIG_ARCH_EXYNOS is not set -# CONFIG_ARCH_SHMOBILE_MULTI is not set +# CONFIG_ARCH_RENESAS is not set # CONFIG_ARCH_SUNXI is not set # CONFIG_ARCH_SIRF is not set +# CONFIG_ARCH_TANGO is not set # CONFIG_ARCH_TEGRA is not set # CONFIG_ARCH_UNIPHIER is not set # CONFIG_ARCH_U8500 is not set @@ -367,6 +411,7 @@ CONFIG_PLAT_VERSATILE=y # Processor Type # CONFIG_CPU_V7=y +CONFIG_CPU_THUMB_CAPABLE=y CONFIG_CPU_32v6K=y CONFIG_CPU_32v7=y CONFIG_CPU_ABRT_EV7=y @@ -393,12 +438,14 @@ CONFIG_SWP_EMULATE=y # CONFIG_CPU_BPREDICT_DISABLE is not set CONFIG_CPU_SPECTRE=y CONFIG_HARDEN_BRANCH_PREDICTOR=y +CONFIG_HARDEN_BRANCH_HISTORY=y CONFIG_KUSER_HELPERS=y CONFIG_VDSO=y CONFIG_OUTER_CACHE=y CONFIG_OUTER_CACHE_SYNC=y CONFIG_MIGHT_HAVE_CACHE_L2X0=y CONFIG_CACHE_L2X0=y +# CONFIG_CACHE_L2X0_PMU is not set # CONFIG_PL310_ERRATA_588369 is not set # CONFIG_PL310_ERRATA_727915 is not set CONFIG_PL310_ERRATA_753970=y @@ -408,7 +455,7 @@ CONFIG_ARM_L1_CACHE_SHIFT=6 CONFIG_ARM_DMA_MEM_BUFFERABLE=y CONFIG_ARM_HEAVY_MB=y CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y -# CONFIG_ARM_KERNMEM_PERMS is not set +CONFIG_DEBUG_ALIGN_RODATA=y CONFIG_MULTI_IRQ_HANDLER=y # CONFIG_ARM_ERRATA_430973 is not set CONFIG_ARM_ERRATA_643719=y @@ -419,7 +466,11 @@ CONFIG_ARM_ERRATA_720789=y # CONFIG_ARM_ERRATA_775420 is not set # CONFIG_ARM_ERRATA_798181 is not set # CONFIG_ARM_ERRATA_773022 is not set -CONFIG_ICST=y +# CONFIG_ARM_ERRATA_818325_852422 is not set +# CONFIG_ARM_ERRATA_821420 is not set +# CONFIG_ARM_ERRATA_825619 is not set +# CONFIG_ARM_ERRATA_852421 is not set +# CONFIG_ARM_ERRATA_852423 is not set # # Bus support @@ -427,6 +478,15 @@ CONFIG_ICST=y # CONFIG_PCI is not set # CONFIG_PCI_DOMAINS_GENERIC is not set # CONFIG_PCI_SYSCALL is not set + +# +# DesignWare PCI Core Support +# + +# +# PCI Endpoint +# +# CONFIG_PCI_ENDPOINT is not set # CONFIG_PCCARD is not set # @@ -465,6 +525,7 @@ CONFIG_HZ_100=y CONFIG_HZ=100 # CONFIG_SCHED_HRTICK is not set # CONFIG_THUMB2_KERNEL is not set +CONFIG_ARM_PATCH_IDIV=y CONFIG_AEABI=y # CONFIG_OABI_COMPAT is not set # CONFIG_ARCH_SPARSEMEM_DEFAULT is not set @@ -487,9 +548,9 @@ CONFIG_BALLOON_COMPACTION=y CONFIG_COMPACTION=y CONFIG_MIGRATION=y # CONFIG_PHYS_ADDR_T_64BIT is not set -CONFIG_ZONE_DMA_FLAG=0 # CONFIG_KSM is not set CONFIG_DEFAULT_MMAP_MIN_ADDR=4096 +# CONFIG_ARCH_WANTS_THP_SWAP is not set # CONFIG_CLEANCACHE is not set # CONFIG_FRONTSWAP is not set CONFIG_CMA=y @@ -499,13 +560,17 @@ CONFIG_CMA_AREAS=7 # CONFIG_ZPOOL is not set # CONFIG_ZBUD is not set # CONFIG_ZSMALLOC is not set +CONFIG_GENERIC_EARLY_IOREMAP=y # CONFIG_IDLE_PAGE_TRACKING is not set +# CONFIG_PERCPU_STATS is not set CONFIG_FORCE_MAX_ZONEORDER=11 CONFIG_ALIGNMENT_TRAP=y # CONFIG_UACCESS_WITH_MEMCPY is not set # CONFIG_SECCOMP is not set CONFIG_SWIOTLB=y CONFIG_IOMMU_HELPER=y +# CONFIG_PARAVIRT is not set +# CONFIG_PARAVIRT_TIME_ACCOUNTING is not set # CONFIG_XEN is not set # @@ -524,6 +589,7 @@ CONFIG_CMDLINE_FROM_BOOTLOADER=y # CONFIG_KEXEC is not set # CONFIG_CRASH_DUMP is not set CONFIG_AUTO_ZRELADDR=y +# CONFIG_EFI is not set # # CPU Power Management @@ -539,7 +605,7 @@ CONFIG_AUTO_ZRELADDR=y # CONFIG_CPU_IDLE=y CONFIG_CPU_IDLE_GOV_LADDER=y -CONFIG_CPU_IDLE_GOV_MENU=y +# CONFIG_CPU_IDLE_GOV_MENU is not set # # ARM CPU Idle Drivers @@ -565,8 +631,10 @@ CONFIG_NEON=y # Userspace binary formats # CONFIG_BINFMT_ELF=y +CONFIG_ELFCORE=y # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set CONFIG_BINFMT_SCRIPT=y +# CONFIG_BINFMT_FLAT is not set # CONFIG_HAVE_AOUT is not set # CONFIG_BINFMT_MISC is not set CONFIG_COREDUMP=y @@ -599,7 +667,9 @@ CONFIG_NET=y CONFIG_PACKET=y # CONFIG_PACKET_DIAG is not set CONFIG_UNIX=y +CONFIG_UNIX_SCM=y # CONFIG_UNIX_DIAG is not set +# CONFIG_TLS is not set CONFIG_XFRM=y # CONFIG_XFRM_USER is not set # CONFIG_XFRM_SUB_POLICY is not set @@ -624,15 +694,17 @@ CONFIG_NET_IP_TUNNEL=y # CONFIG_INET_AH is not set # CONFIG_INET_ESP is not set # CONFIG_INET_IPCOMP is not set +CONFIG_INET_TABLE_PERTURB_ORDER=16 # CONFIG_INET_XFRM_TUNNEL is not set CONFIG_INET_TUNNEL=y CONFIG_INET_XFRM_MODE_TRANSPORT=y CONFIG_INET_XFRM_MODE_TUNNEL=y CONFIG_INET_XFRM_MODE_BEET=y -# CONFIG_INET_LRO is not set CONFIG_INET_DIAG=y CONFIG_INET_TCP_DIAG=y # CONFIG_INET_UDP_DIAG is not set +# CONFIG_INET_RAW_DIAG is not set +# CONFIG_INET_DIAG_DESTROY is not set # CONFIG_TCP_CONG_ADVANCED is not set CONFIG_TCP_CONG_CUBIC=y CONFIG_DEFAULT_TCP_CONG="cubic" @@ -644,7 +716,6 @@ CONFIG_IPV6=y # CONFIG_INET6_ESP is not set # CONFIG_INET6_IPCOMP is not set # CONFIG_IPV6_MIP6 is not set -# CONFIG_IPV6_ILA is not set # CONFIG_INET6_XFRM_TUNNEL is not set # CONFIG_INET6_TUNNEL is not set CONFIG_INET6_XFRM_MODE_TRANSPORT=y @@ -656,9 +727,12 @@ CONFIG_IPV6_SIT=y # CONFIG_IPV6_SIT_6RD is not set CONFIG_IPV6_NDISC_NODETYPE=y # CONFIG_IPV6_TUNNEL is not set -# CONFIG_IPV6_GRE is not set +# CONFIG_IPV6_FOU is not set +# CONFIG_IPV6_FOU_TUNNEL is not set # CONFIG_IPV6_MULTIPLE_TABLES is not set # CONFIG_IPV6_MROUTE is not set +# CONFIG_IPV6_SEG6_LWTUNNEL is not set +# CONFIG_IPV6_SEG6_HMAC is not set # CONFIG_NETWORK_SECMARK is not set # CONFIG_NET_PTP_CLASSIFY is not set # CONFIG_NETWORK_PHY_TIMESTAMPING is not set @@ -673,7 +747,6 @@ CONFIG_IPV6_NDISC_NODETYPE=y CONFIG_HAVE_NET_DSA=y # CONFIG_NET_DSA is not set # CONFIG_VLAN_8021Q is not set -# CONFIG_DECNET is not set # CONFIG_LLC2 is not set # CONFIG_IPX is not set # CONFIG_ATALK is not set @@ -689,9 +762,11 @@ CONFIG_HAVE_NET_DSA=y # CONFIG_VSOCKETS is not set # CONFIG_NETLINK_DIAG is not set # CONFIG_MPLS is not set +# CONFIG_NET_NSH is not set # CONFIG_HSR is not set # CONFIG_NET_SWITCHDEV is not set # CONFIG_NET_L3_MASTER_DEV is not set +# CONFIG_NET_NCSI is not set CONFIG_RPS=y CONFIG_RFS_ACCEL=y CONFIG_XPS=y @@ -706,30 +781,35 @@ CONFIG_NET_FLOW_LIMIT=y # Network testing # # CONFIG_NET_PKTGEN is not set +# CONFIG_NET_DROP_MONITOR is not set # CONFIG_HAMRADIO is not set # CONFIG_CAN is not set -# CONFIG_IRDA is not set # CONFIG_BT is not set # CONFIG_AF_RXRPC is not set +# CONFIG_AF_KCM is not set +# CONFIG_STREAM_PARSER is not set # CONFIG_WIRELESS is not set # CONFIG_WIMAX is not set # CONFIG_RFKILL is not set -# CONFIG_RFKILL_REGULATOR is not set CONFIG_NET_9P=y CONFIG_NET_9P_VIRTIO=y # CONFIG_NET_9P_DEBUG is not set # CONFIG_CAIF is not set # CONFIG_CEPH_LIB is not set # CONFIG_NFC is not set +# CONFIG_PSAMPLE is not set +# CONFIG_NET_IFE is not set # CONFIG_LWTUNNEL is not set CONFIG_DST_CACHE=y -CONFIG_HAVE_BPF_JIT=y +CONFIG_GRO_CELLS=y +# CONFIG_NET_DEVLINK is not set +CONFIG_MAY_USE_DEVLINK=y +CONFIG_HAVE_EBPF_JIT=y # # Device Drivers # CONFIG_ARM_AMBA=y -# CONFIG_TEGRA_AHB is not set # # Generic Driver Options @@ -747,12 +827,18 @@ CONFIG_EXTRA_FIRMWARE="" CONFIG_ALLOW_DEV_COREDUMP=y # CONFIG_DEBUG_DRIVER is not set # CONFIG_DEBUG_DEVRES is not set +# CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set +# CONFIG_TEST_ASYNC_DRIVER_PROBE is not set # CONFIG_SYS_HYPERVISOR is not set # CONFIG_GENERIC_CPU_DEVICES is not set +CONFIG_GENERIC_CPU_AUTOPROBE=y +CONFIG_GENERIC_CPU_VULNERABILITIES=y CONFIG_REGMAP=y +CONFIG_REGMAP_I2C=y CONFIG_REGMAP_MMIO=y # CONFIG_DMA_SHARED_BUFFER is not set # CONFIG_DMA_CMA is not set +CONFIG_GENERIC_ARCH_TOPOLOGY=y # # Bus devices @@ -761,9 +847,10 @@ CONFIG_ARM_CCI=y CONFIG_ARM_CCI400_COMMON=y # CONFIG_ARM_CCI400_PMU is not set CONFIG_ARM_CCI400_PORT_CTRL=y -# CONFIG_ARM_CCI500_PMU is not set +# CONFIG_ARM_CCI5xx_PMU is not set # CONFIG_ARM_CCN is not set # CONFIG_BRCMSTB_GISB_ARB is not set +# CONFIG_SIMPLE_PM_BUS is not set CONFIG_VEXPRESS_CONFIG=y # CONFIG_CONNECTOR is not set CONFIG_MTD=y @@ -774,6 +861,10 @@ CONFIG_MTD_CMDLINE_PARTS=y CONFIG_MTD_OF_PARTS=y # CONFIG_MTD_AR7_PARTS is not set +# +# Partition parsers +# + # # User Modules And Translation Layers # @@ -821,6 +912,8 @@ CONFIG_MTD_RAM=y CONFIG_MTD_PHYSMAP=y # CONFIG_MTD_PHYSMAP_COMPAT is not set CONFIG_MTD_PHYSMAP_OF=y +# CONFIG_MTD_PHYSMAP_OF_VERSATILE is not set +# CONFIG_MTD_PHYSMAP_OF_GEMINI is not set CONFIG_MTD_PLATRAM=y # @@ -859,7 +952,6 @@ CONFIG_OF_ADDRESS=y CONFIG_OF_IRQ=y CONFIG_OF_NET=y CONFIG_OF_MDIO=y -CONFIG_OF_MTD=y CONFIG_OF_RESERVED_MEM=y # CONFIG_OF_OVERLAY is not set CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y @@ -873,9 +965,10 @@ CONFIG_BLK_DEV=y # CONFIG_BLK_DEV_RAM is not set # CONFIG_CDROM_PKTCDVD is not set # CONFIG_ATA_OVER_ETH is not set -# CONFIG_MG_DISK is not set CONFIG_VIRTIO_BLK=y +# CONFIG_VIRTIO_BLK_SCSI is not set # CONFIG_BLK_DEV_RBD is not set +# CONFIG_NVME_FC is not set # # Misc devices @@ -889,13 +982,10 @@ CONFIG_VIRTIO_BLK=y # CONFIG_ISL29003 is not set # CONFIG_ISL29020 is not set # CONFIG_SENSORS_TSL2550 is not set -# CONFIG_SENSORS_BH1780 is not set # CONFIG_SENSORS_BH1770 is not set # CONFIG_SENSORS_APDS990X is not set # CONFIG_HMC6352 is not set # CONFIG_DS1682 is not set -# CONFIG_ARM_CHARLCD is not set -# CONFIG_BMP085_I2C is not set # CONFIG_USB_SWITCH_FSA9480 is not set # CONFIG_SRAM is not set CONFIG_VEXPRESS_SYSCFG=y @@ -908,6 +998,7 @@ CONFIG_VEXPRESS_SYSCFG=y # CONFIG_EEPROM_LEGACY is not set # CONFIG_EEPROM_MAX6875 is not set # CONFIG_EEPROM_93CX6 is not set +# CONFIG_EEPROM_IDT_89HPESX is not set # # Texas Instruments shared transport line discipline @@ -928,6 +1019,10 @@ CONFIG_VEXPRESS_SYSCFG=y # SCIF Bus Driver # +# +# VOP Bus Driver +# + # # Intel MIC Host Driver # @@ -943,10 +1038,14 @@ CONFIG_VEXPRESS_SYSCFG=y # # Intel MIC Coprocessor State Management (COSM) Drivers # + +# +# VOP Driver +# # CONFIG_ECHO is not set # CONFIG_CXL_BASE is not set -# CONFIG_CXL_KERNEL_API is not set -# CONFIG_CXL_EEH is not set +# CONFIG_CXL_AFU_DRIVER_OPS is not set +# CONFIG_CXL_LIB is not set # # SCSI device support @@ -1034,8 +1133,9 @@ CONFIG_NET_CORE=y # CONFIG_EQUALIZER is not set # CONFIG_NET_TEAM is not set # CONFIG_MACVLAN is not set -# CONFIG_IPVLAN is not set # CONFIG_VXLAN is not set +# CONFIG_GTP is not set +# CONFIG_MACSEC is not set # CONFIG_NETCONSOLE is not set # CONFIG_NETPOLL is not set # CONFIG_NET_POLL_CONTROLLER is not set @@ -1052,13 +1152,12 @@ CONFIG_VIRTIO_NET=y # # Distributed Switch Architecture drivers # -# CONFIG_NET_DSA_MV88E6XXX is not set -# CONFIG_NET_DSA_MV88E6XXX_NEED_PPU is not set CONFIG_ETHERNET=y +CONFIG_NET_VENDOR_ALACRITECH=y # CONFIG_ALTERA_TSE is not set +CONFIG_NET_VENDOR_AMAZON=y +CONFIG_NET_VENDOR_AQUANTIA=y CONFIG_NET_VENDOR_ARC=y -# CONFIG_ARC_EMAC is not set -# CONFIG_EMAC_ROCKCHIP is not set # CONFIG_NET_VENDOR_AURORA is not set CONFIG_NET_CADENCE=y # CONFIG_MACB is not set @@ -1077,26 +1176,36 @@ CONFIG_NET_VENDOR_FARADAY=y # CONFIG_FTGMAC100 is not set CONFIG_NET_VENDOR_HISILICON=y # CONFIG_HIX5HD2_GMAC is not set +# CONFIG_HISI_FEMAC is not set # CONFIG_HIP04_ETH is not set # CONFIG_HNS is not set # CONFIG_HNS_DSAF is not set # CONFIG_HNS_ENET is not set +CONFIG_NET_VENDOR_HUAWEI=y CONFIG_NET_VENDOR_INTEL=y CONFIG_NET_VENDOR_I825XX=y CONFIG_NET_VENDOR_MARVELL=y # CONFIG_MVMDIO is not set +# CONFIG_MVNETA_BM is not set +CONFIG_NET_VENDOR_MELLANOX=y +# CONFIG_MLXSW_CORE is not set +# CONFIG_MLXFW is not set CONFIG_NET_VENDOR_MICREL=y # CONFIG_KS8851_MLL is not set CONFIG_NET_VENDOR_NATSEMI=y +CONFIG_NET_VENDOR_NETRONOME=y CONFIG_NET_VENDOR_8390=y # CONFIG_AX88796 is not set # CONFIG_ETHOC is not set CONFIG_NET_VENDOR_QUALCOMM=y +# CONFIG_QCOM_EMAC is not set +# CONFIG_RMNET is not set CONFIG_NET_VENDOR_RENESAS=y CONFIG_NET_VENDOR_ROCKER=y CONFIG_NET_VENDOR_SAMSUNG=y # CONFIG_SXGBE_ETH is not set CONFIG_NET_VENDOR_SEEQ=y +CONFIG_NET_VENDOR_SOLARFLARE=y CONFIG_NET_VENDOR_SMSC=y CONFIG_SMC91X=y # CONFIG_SMC911X is not set @@ -1104,47 +1213,58 @@ CONFIG_SMSC911X=y # CONFIG_SMSC911X_ARCH_HOOKS is not set CONFIG_NET_VENDOR_STMICRO=y # CONFIG_STMMAC_ETH is not set -CONFIG_NET_VENDOR_SYNOPSYS=y -# CONFIG_SYNOPSYS_DWC_ETH_QOS is not set CONFIG_NET_VENDOR_VIA=y # CONFIG_VIA_RHINE is not set # CONFIG_VIA_VELOCITY is not set CONFIG_NET_VENDOR_WIZNET=y # CONFIG_WIZNET_W5100 is not set # CONFIG_WIZNET_W5300 is not set +CONFIG_NET_VENDOR_SYNOPSYS=y +# CONFIG_DWC_XLGMAC is not set +CONFIG_MDIO_DEVICE=y +CONFIG_MDIO_BUS=y +# CONFIG_MDIO_BCM_UNIMAC is not set +# CONFIG_MDIO_BITBANG is not set +# CONFIG_MDIO_BUS_MUX_GPIO is not set +# CONFIG_MDIO_BUS_MUX_MMIOREG is not set +# CONFIG_MDIO_HISI_FEMAC is not set CONFIG_PHYLIB=y +CONFIG_SWPHY=y +# CONFIG_LED_TRIGGER_PHY is not set # # MII PHY device drivers # +# CONFIG_AMD_PHY is not set # CONFIG_AQUANTIA_PHY is not set # CONFIG_AT803X_PHY is not set -# CONFIG_AMD_PHY is not set -# CONFIG_MARVELL_PHY is not set -# CONFIG_DAVICOM_PHY is not set -# CONFIG_QSEMI_PHY is not set -# CONFIG_LXT_PHY is not set -# CONFIG_CICADA_PHY is not set -# CONFIG_VITESSE_PHY is not set -# CONFIG_TERANETICS_PHY is not set -# CONFIG_SMSC_PHY is not set -# CONFIG_BROADCOM_PHY is not set # CONFIG_BCM7XXX_PHY is not set # CONFIG_BCM87XX_PHY is not set +# CONFIG_BROADCOM_PHY is not set +# CONFIG_CICADA_PHY is not set +# CONFIG_CORTINA_PHY is not set +# CONFIG_DAVICOM_PHY is not set +# CONFIG_DP83848_PHY is not set +# CONFIG_DP83867_PHY is not set +CONFIG_FIXED_PHY=y # CONFIG_ICPLUS_PHY is not set -# CONFIG_REALTEK_PHY is not set -# CONFIG_NATIONAL_PHY is not set -# CONFIG_STE10XP is not set +# CONFIG_INTEL_XWAY_PHY is not set # CONFIG_LSI_ET1011C_PHY is not set +# CONFIG_LXT_PHY is not set +# CONFIG_MARVELL_PHY is not set +# CONFIG_MARVELL_10G_PHY is not set # CONFIG_MICREL_PHY is not set -# CONFIG_DP83848_PHY is not set -# CONFIG_DP83867_PHY is not set # CONFIG_MICROCHIP_PHY is not set -# CONFIG_FIXED_PHY is not set -# CONFIG_MDIO_BITBANG is not set -# CONFIG_MDIO_BUS_MUX_GPIO is not set -# CONFIG_MDIO_BUS_MUX_MMIOREG is not set -# CONFIG_MDIO_BCM_UNIMAC is not set +# CONFIG_MICROSEMI_PHY is not set +# CONFIG_NATIONAL_PHY is not set +# CONFIG_QSEMI_PHY is not set +# CONFIG_REALTEK_PHY is not set +# CONFIG_ROCKCHIP_PHY is not set +# CONFIG_SMSC_PHY is not set +# CONFIG_STE10XP is not set +# CONFIG_TERANETICS_PHY is not set +# CONFIG_VITESSE_PHY is not set +# CONFIG_XILINX_GMII2RGMII is not set # CONFIG_PPP is not set # CONFIG_SLIP is not set CONFIG_USB_NET_DRIVERS=y @@ -1163,7 +1283,6 @@ CONFIG_USB_NET_DRIVERS=y # # CONFIG_WAN is not set # CONFIG_ISDN is not set -# CONFIG_NVM is not set # # Input device support @@ -1178,10 +1297,7 @@ CONFIG_INPUT_LEDS=y # # Userland interfaces # -CONFIG_INPUT_MOUSEDEV=y -CONFIG_INPUT_MOUSEDEV_PSAUX=y -CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024 -CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768 +# CONFIG_INPUT_MOUSEDEV is not set # CONFIG_INPUT_JOYDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_INPUT_EVBUG is not set @@ -1195,6 +1311,7 @@ CONFIG_INPUT_KEYBOARD=y CONFIG_KEYBOARD_ATKBD=y # CONFIG_KEYBOARD_QT1070 is not set # CONFIG_KEYBOARD_QT2160 is not set +# CONFIG_KEYBOARD_DLINK_DIR685 is not set # CONFIG_KEYBOARD_LKKBD is not set # CONFIG_KEYBOARD_GPIO is not set # CONFIG_KEYBOARD_GPIO_POLLED is not set @@ -1212,20 +1329,24 @@ CONFIG_KEYBOARD_ATKBD=y # CONFIG_KEYBOARD_STOWAWAY is not set # CONFIG_KEYBOARD_SUNKBD is not set # CONFIG_KEYBOARD_OMAP4 is not set +# CONFIG_KEYBOARD_TM2_TOUCHKEY is not set # CONFIG_KEYBOARD_XTKBD is not set # CONFIG_KEYBOARD_CAP11XX is not set # CONFIG_KEYBOARD_BCM is not set CONFIG_INPUT_MOUSE=y CONFIG_MOUSE_PS2=y CONFIG_MOUSE_PS2_ALPS=y +CONFIG_MOUSE_PS2_BYD=y CONFIG_MOUSE_PS2_LOGIPS2PP=y CONFIG_MOUSE_PS2_SYNAPTICS=y +CONFIG_MOUSE_PS2_SYNAPTICS_SMBUS=y CONFIG_MOUSE_PS2_CYPRESS=y CONFIG_MOUSE_PS2_TRACKPOINT=y # CONFIG_MOUSE_PS2_ELANTECH is not set # CONFIG_MOUSE_PS2_SENTELIC is not set # CONFIG_MOUSE_PS2_TOUCHKIT is not set CONFIG_MOUSE_PS2_FOCALTECH=y +CONFIG_MOUSE_PS2_SMBUS=y # CONFIG_MOUSE_SERIAL is not set # CONFIG_MOUSE_APPLETOUCH is not set # CONFIG_MOUSE_BCM5974 is not set @@ -1239,6 +1360,7 @@ CONFIG_MOUSE_PS2_FOCALTECH=y # CONFIG_INPUT_TABLET is not set # CONFIG_INPUT_TOUCHSCREEN is not set # CONFIG_INPUT_MISC is not set +# CONFIG_RMI4_CORE is not set # # Hardware I/O ports @@ -1252,6 +1374,7 @@ CONFIG_SERIO_LIBPS2=y # CONFIG_SERIO_PS2MULT is not set # CONFIG_SERIO_ARC_PS2 is not set # CONFIG_SERIO_APBPS2 is not set +# CONFIG_SERIO_GPIO_PS2 is not set # CONFIG_USERIO is not set # CONFIG_GAMEPORT is not set @@ -1266,7 +1389,6 @@ CONFIG_VT_CONSOLE_SLEEP=y CONFIG_HW_CONSOLE=y CONFIG_VT_HW_CONSOLE_BINDING=y CONFIG_UNIX98_PTYS=y -# CONFIG_DEVPTS_MULTIPLE_INSTANCES is not set CONFIG_LEGACY_PTYS=y CONFIG_LEGACY_PTY_COUNT=16 # CONFIG_SERIAL_NONSTANDARD is not set @@ -1274,7 +1396,7 @@ CONFIG_LEGACY_PTY_COUNT=16 # CONFIG_TRACE_SINK is not set CONFIG_LDISC_AUTOLOAD=y CONFIG_DEVMEM=y -CONFIG_DEVKMEM=y +# CONFIG_DEVKMEM is not set # # Serial drivers @@ -1302,7 +1424,7 @@ CONFIG_SERIAL_CORE_CONSOLE=y # CONFIG_SERIAL_FSL_LPUART is not set # CONFIG_SERIAL_CONEXANT_DIGICOLOR is not set # CONFIG_SERIAL_ST_ASC is not set -# CONFIG_SERIAL_STM32 is not set +# CONFIG_SERIAL_DEV_BUS is not set CONFIG_HVC_DRIVER=y # CONFIG_HVC_DCC is not set CONFIG_VIRTIO_CONSOLE=y @@ -1313,6 +1435,7 @@ CONFIG_HW_RANDOM_VIRTIO=y # CONFIG_RAW_DRIVER is not set # CONFIG_TCG_TPM is not set # CONFIG_XILLYBUS is not set +CONFIG_RANDOM_TRUST_BOOTLOADER=y # # I2C support @@ -1365,16 +1488,8 @@ CONFIG_I2C_VERSATILE=y # CONFIG_SPI is not set # CONFIG_SPMI is not set # CONFIG_HSI is not set - -# -# PPS support -# # CONFIG_PPS is not set -# -# PPS generators support -# - # # PTP clock support # @@ -1384,10 +1499,7 @@ CONFIG_I2C_VERSATILE=y # Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks. # CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y -CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y -CONFIG_ARCH_REQUIRE_GPIOLIB=y CONFIG_GPIOLIB=y -CONFIG_GPIO_DEVRES=y CONFIG_OF_GPIO=y # CONFIG_DEBUG_GPIO is not set # CONFIG_GPIO_SYSFS is not set @@ -1399,14 +1511,15 @@ CONFIG_GPIO_GENERIC=y # CONFIG_GPIO_74XX_MMIO is not set # CONFIG_GPIO_ALTERA is not set # CONFIG_GPIO_DWAPB is not set -# CONFIG_GPIO_EM is not set +# CONFIG_GPIO_FTGPIO010 is not set CONFIG_GPIO_GENERIC_PLATFORM=y # CONFIG_GPIO_GRGPIO is not set +# CONFIG_GPIO_MOCKUP is not set +# CONFIG_GPIO_MPC8XXX is not set # CONFIG_GPIO_PL061 is not set # CONFIG_GPIO_SYSCON is not set # CONFIG_GPIO_XILINX is not set # CONFIG_GPIO_ZEVIO is not set -# CONFIG_GPIO_ZX is not set # # I2C GPIO expanders @@ -1417,21 +1530,30 @@ CONFIG_GPIO_GENERIC_PLATFORM=y # CONFIG_GPIO_MAX732X is not set # CONFIG_GPIO_PCA953X is not set # CONFIG_GPIO_PCF857X is not set -# CONFIG_GPIO_SX150X is not set +# CONFIG_GPIO_TPIC2810 is not set # # MFD GPIO expanders # - -# -# SPI or I2C GPIO expanders -# -# CONFIG_GPIO_MCP23S08 is not set +# CONFIG_HTC_EGPIO is not set # # USB GPIO expanders # # CONFIG_W1 is not set +# CONFIG_POWER_AVS is not set +CONFIG_POWER_RESET=y +# CONFIG_POWER_RESET_BRCMKONA is not set +# CONFIG_POWER_RESET_BRCMSTB is not set +# CONFIG_POWER_RESET_GPIO is not set +# CONFIG_POWER_RESET_GPIO_RESTART is not set +# CONFIG_POWER_RESET_LTC2952 is not set +# CONFIG_POWER_RESET_RESTART is not set +# CONFIG_POWER_RESET_VERSATILE is not set +CONFIG_POWER_RESET_VEXPRESS=y +# CONFIG_POWER_RESET_SYSCON is not set +# CONFIG_POWER_RESET_SYSCON_POWEROFF is not set +# CONFIG_SYSCON_REBOOT_MODE is not set CONFIG_POWER_SUPPLY=y # CONFIG_POWER_SUPPLY_DEBUG is not set # CONFIG_PDA_POWER is not set @@ -1440,6 +1562,7 @@ CONFIG_POWER_SUPPLY=y # CONFIG_BATTERY_DS2781 is not set # CONFIG_BATTERY_DS2782 is not set # CONFIG_BATTERY_SBS is not set +# CONFIG_CHARGER_SBS is not set # CONFIG_BATTERY_BQ27XXX is not set # CONFIG_BATTERY_MAX17040 is not set # CONFIG_BATTERY_MAX17042 is not set @@ -1447,24 +1570,16 @@ CONFIG_POWER_SUPPLY=y # CONFIG_CHARGER_LP8727 is not set # CONFIG_CHARGER_GPIO is not set # CONFIG_CHARGER_MANAGER is not set +# CONFIG_CHARGER_LTC3651 is not set +# CONFIG_CHARGER_DETECTOR_MAX14656 is not set # CONFIG_CHARGER_BQ2415X is not set -# CONFIG_CHARGER_BQ24190 is not set +# CONFIG_CHARGER_BQ24257 is not set # CONFIG_CHARGER_BQ24735 is not set # CONFIG_CHARGER_BQ25890 is not set # CONFIG_CHARGER_SMB347 is not set # CONFIG_BATTERY_GAUGE_LTC2941 is not set +# CONFIG_BATTERY_RT5033 is not set # CONFIG_CHARGER_RT9455 is not set -CONFIG_POWER_RESET=y -# CONFIG_POWER_RESET_BRCMSTB is not set -# CONFIG_POWER_RESET_GPIO is not set -# CONFIG_POWER_RESET_GPIO_RESTART is not set -# CONFIG_POWER_RESET_LTC2952 is not set -# CONFIG_POWER_RESET_RESTART is not set -# CONFIG_POWER_RESET_VERSATILE is not set -CONFIG_POWER_RESET_VEXPRESS=y -# CONFIG_POWER_RESET_SYSCON is not set -# CONFIG_POWER_RESET_SYSCON_POWEROFF is not set -# CONFIG_POWER_AVS is not set CONFIG_HWMON=y # CONFIG_HWMON_VID is not set # CONFIG_HWMON_DEBUG_CHIP is not set @@ -1486,6 +1601,7 @@ CONFIG_HWMON=y # CONFIG_SENSORS_ADT7470 is not set # CONFIG_SENSORS_ADT7475 is not set # CONFIG_SENSORS_ASC7621 is not set +# CONFIG_SENSORS_ASPEED is not set # CONFIG_SENSORS_ATXP1 is not set # CONFIG_SENSORS_DS620 is not set # CONFIG_SENSORS_DS1621 is not set @@ -1503,6 +1619,7 @@ CONFIG_HWMON=y # CONFIG_SENSORS_POWR1220 is not set # CONFIG_SENSORS_LINEAGE is not set # CONFIG_SENSORS_LTC2945 is not set +# CONFIG_SENSORS_LTC2990 is not set # CONFIG_SENSORS_LTC4151 is not set # CONFIG_SENSORS_LTC4215 is not set # CONFIG_SENSORS_LTC4222 is not set @@ -1518,8 +1635,8 @@ CONFIG_HWMON=y # CONFIG_SENSORS_MAX6650 is not set # CONFIG_SENSORS_MAX6697 is not set # CONFIG_SENSORS_MAX31790 is not set -# CONFIG_SENSORS_HTU21 is not set # CONFIG_SENSORS_MCP3021 is not set +# CONFIG_SENSORS_TC654 is not set # CONFIG_SENSORS_LM63 is not set # CONFIG_SENSORS_LM73 is not set # CONFIG_SENSORS_LM75 is not set @@ -1546,6 +1663,7 @@ CONFIG_HWMON=y # CONFIG_PMBUS is not set # CONFIG_SENSORS_SHT15 is not set # CONFIG_SENSORS_SHT21 is not set +# CONFIG_SENSORS_SHT3x is not set # CONFIG_SENSORS_SHTC1 is not set # CONFIG_SENSORS_DME1737 is not set # CONFIG_SENSORS_EMC1403 is not set @@ -1555,6 +1673,7 @@ CONFIG_HWMON=y # CONFIG_SENSORS_SMSC47M192 is not set # CONFIG_SENSORS_SMSC47B397 is not set # CONFIG_SENSORS_SCH56XX_COMMON is not set +# CONFIG_SENSORS_STTS751 is not set # CONFIG_SENSORS_SMM665 is not set # CONFIG_SENSORS_ADC128D818 is not set # CONFIG_SENSORS_ADS1015 is not set @@ -1562,10 +1681,12 @@ CONFIG_HWMON=y # CONFIG_SENSORS_AMC6821 is not set # CONFIG_SENSORS_INA209 is not set # CONFIG_SENSORS_INA2XX is not set +# CONFIG_SENSORS_INA3221 is not set # CONFIG_SENSORS_TC74 is not set # CONFIG_SENSORS_THMC50 is not set # CONFIG_SENSORS_TMP102 is not set # CONFIG_SENSORS_TMP103 is not set +# CONFIG_SENSORS_TMP108 is not set # CONFIG_SENSORS_TMP401 is not set # CONFIG_SENSORS_TMP421 is not set CONFIG_SENSORS_VEXPRESS=y @@ -1588,16 +1709,13 @@ CONFIG_SSB_POSSIBLE=y # # CONFIG_SSB is not set CONFIG_BCMA_POSSIBLE=y - -# -# Broadcom specific AMBA -# # CONFIG_BCMA is not set # # Multifunction device drivers # CONFIG_MFD_CORE=y +# CONFIG_MFD_ACT8945A is not set # CONFIG_MFD_AS3711 is not set # CONFIG_MFD_AS3722 is not set # CONFIG_PMIC_ADP5520 is not set @@ -1605,7 +1723,8 @@ CONFIG_MFD_CORE=y # CONFIG_MFD_ATMEL_FLEXCOM is not set # CONFIG_MFD_ATMEL_HLCDC is not set # CONFIG_MFD_BCM590XX is not set -# CONFIG_MFD_AXP20X is not set +# CONFIG_MFD_BD9571MWV is not set +# CONFIG_MFD_AXP20X_I2C is not set # CONFIG_MFD_CROS_EC is not set # CONFIG_MFD_ASIC3 is not set # CONFIG_PMIC_DA903X is not set @@ -1617,15 +1736,14 @@ CONFIG_MFD_CORE=y # CONFIG_MFD_DLN2 is not set # CONFIG_MFD_MC13XXX_I2C is not set # CONFIG_MFD_HI6421_PMIC is not set -# CONFIG_HTC_EGPIO is not set # CONFIG_HTC_PASIC3 is not set # CONFIG_HTC_I2CPLD is not set -# CONFIG_INTEL_SOC_PMIC is not set # CONFIG_MFD_KEMPLD is not set # CONFIG_MFD_88PM800 is not set # CONFIG_MFD_88PM805 is not set # CONFIG_MFD_88PM860X is not set # CONFIG_MFD_MAX14577 is not set +# CONFIG_MFD_MAX77620 is not set # CONFIG_MFD_MAX77686 is not set # CONFIG_MFD_MAX77693 is not set # CONFIG_MFD_MAX77843 is not set @@ -1639,7 +1757,7 @@ CONFIG_MFD_CORE=y # CONFIG_MFD_RETU is not set # CONFIG_MFD_PCF50633 is not set # CONFIG_UCB1400_CORE is not set -# CONFIG_MFD_PM8921_CORE is not set +# CONFIG_MFD_PM8XXX is not set # CONFIG_MFD_RT5033 is not set # CONFIG_MFD_RTSX_USB is not set # CONFIG_MFD_RC5T583 is not set @@ -1656,16 +1774,19 @@ CONFIG_MFD_SYSCON=y # CONFIG_MFD_TI_AM335X_TSCADC is not set # CONFIG_MFD_LP3943 is not set # CONFIG_MFD_LP8788 is not set +# CONFIG_MFD_TI_LMU is not set # CONFIG_MFD_PALMAS is not set # CONFIG_TPS6105X is not set # CONFIG_TPS65010 is not set # CONFIG_TPS6507X is not set +# CONFIG_MFD_TPS65086 is not set # CONFIG_MFD_TPS65090 is not set # CONFIG_MFD_TPS65217 is not set +# CONFIG_MFD_TI_LP873X is not set +# CONFIG_MFD_TI_LP87565 is not set # CONFIG_MFD_TPS65218 is not set # CONFIG_MFD_TPS6586X is not set # CONFIG_MFD_TPS65910 is not set -# CONFIG_MFD_TPS65912 is not set # CONFIG_MFD_TPS65912_I2C is not set # CONFIG_MFD_TPS80031 is not set # CONFIG_TWL4030_CORE is not set @@ -1702,31 +1823,57 @@ CONFIG_REGULATOR_FIXED_VOLTAGE=y # CONFIG_REGULATOR_LP872X is not set # CONFIG_REGULATOR_LP8755 is not set # CONFIG_REGULATOR_LTC3589 is not set +# CONFIG_REGULATOR_LTC3676 is not set # CONFIG_REGULATOR_MAX1586 is not set # CONFIG_REGULATOR_MAX8649 is not set # CONFIG_REGULATOR_MAX8660 is not set # CONFIG_REGULATOR_MAX8952 is not set -# CONFIG_REGULATOR_MAX8973 is not set # CONFIG_REGULATOR_MT6311 is not set # CONFIG_REGULATOR_PFUZE100 is not set +# CONFIG_REGULATOR_PV88060 is not set +# CONFIG_REGULATOR_PV88080 is not set +# CONFIG_REGULATOR_PV88090 is not set # CONFIG_REGULATOR_TPS51632 is not set # CONFIG_REGULATOR_TPS62360 is not set # CONFIG_REGULATOR_TPS65023 is not set # CONFIG_REGULATOR_TPS6507X is not set +# CONFIG_REGULATOR_TPS65132 is not set +# CONFIG_REGULATOR_VCTRL is not set CONFIG_REGULATOR_VEXPRESS=y +CONFIG_RC_CORE=y +CONFIG_RC_MAP=y +CONFIG_RC_DECODERS=y +# CONFIG_LIRC is not set +CONFIG_IR_NEC_DECODER=y +CONFIG_IR_RC5_DECODER=y +CONFIG_IR_RC6_DECODER=y +CONFIG_IR_JVC_DECODER=y +CONFIG_IR_SONY_DECODER=y +CONFIG_IR_SANYO_DECODER=y +CONFIG_IR_SHARP_DECODER=y +CONFIG_IR_MCE_KBD_DECODER=y +CONFIG_IR_XMP_DECODER=y +# CONFIG_RC_DEVICES is not set # CONFIG_MEDIA_SUPPORT is not set # # Graphics support # +# CONFIG_IMX_IPUV3_CORE is not set # CONFIG_DRM is not set +# +# ACP (Audio CoProcessor) Configuration +# +# CONFIG_DRM_LIB_RANDOM is not set + # # Frame buffer Devices # CONFIG_FB=y # CONFIG_FIRMWARE_EDID is not set CONFIG_FB_CMDLINE=y +CONFIG_FB_NOTIFY=y # CONFIG_FB_DDC is not set # CONFIG_FB_BOOT_VESA_SUPPORT is not set CONFIG_FB_CFB_FILLRECT=y @@ -1736,6 +1883,7 @@ CONFIG_FB_CFB_IMAGEBLIT=y # CONFIG_FB_SYS_FILLRECT is not set # CONFIG_FB_SYS_COPYAREA is not set # CONFIG_FB_SYS_IMAGEBLIT is not set +# CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA is not set # CONFIG_FB_FOREIGN_ENDIAN is not set # CONFIG_FB_SYS_FOPS is not set # CONFIG_FB_SVGALIB is not set @@ -1760,7 +1908,19 @@ CONFIG_PLAT_VERSATILE_CLCD=y # CONFIG_FB_AUO_K190X is not set # CONFIG_FB_SIMPLE is not set # CONFIG_FB_SSD1307 is not set -# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=m +# CONFIG_LCD_PLATFORM is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_BACKLIGHT_GENERIC=y +# CONFIG_BACKLIGHT_PM8941_WLED is not set +# CONFIG_BACKLIGHT_ADP8860 is not set +# CONFIG_BACKLIGHT_ADP8870 is not set +# CONFIG_BACKLIGHT_LM3639 is not set +# CONFIG_BACKLIGHT_GPIO is not set +# CONFIG_BACKLIGHT_LV5207LP is not set +# CONFIG_BACKLIGHT_BD6107 is not set +# CONFIG_BACKLIGHT_ARCXCNN is not set # CONFIG_VGASTATE is not set CONFIG_VIDEOMODE_HELPERS=y @@ -1776,16 +1936,11 @@ CONFIG_LOGO=y # CONFIG_LOGO_LINUX_VGA16 is not set CONFIG_LOGO_LINUX_CLUT224=y CONFIG_SOUND=y -CONFIG_SOUND_OSS_CORE=y -CONFIG_SOUND_OSS_CORE_PRECLAIM=y +# CONFIG_SOUND_OSS_CORE is not set CONFIG_SND=y CONFIG_SND_TIMER=y CONFIG_SND_PCM=y -# CONFIG_SND_SEQUENCER is not set -CONFIG_SND_OSSEMUL=y -CONFIG_SND_MIXER_OSS=y -CONFIG_SND_PCM_OSS=y -CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_OSSEMUL is not set CONFIG_SND_PCM_TIMER=y # CONFIG_SND_DYNAMIC_MINORS is not set CONFIG_SND_SUPPORT_OLD_API=y @@ -1794,11 +1949,9 @@ CONFIG_SND_VERBOSE_PROCFS=y # CONFIG_SND_VERBOSE_PRINTK is not set # CONFIG_SND_DEBUG is not set CONFIG_SND_VMASTER=y -# CONFIG_SND_RAWMIDI_SEQ is not set +# CONFIG_SND_SEQUENCER is not set # CONFIG_SND_OPL3_LIB_SEQ is not set # CONFIG_SND_OPL4_LIB_SEQ is not set -# CONFIG_SND_SBAWE_SEQ is not set -# CONFIG_SND_EMU10K1_SEQ is not set CONFIG_SND_AC97_CODEC=y # CONFIG_SND_DRIVERS is not set @@ -1820,7 +1973,6 @@ CONFIG_SND_USB=y # CONFIG_SND_USB_TONEPORT is not set # CONFIG_SND_USB_VARIAX is not set # CONFIG_SND_SOC is not set -# CONFIG_SOUND_PRIME is not set CONFIG_AC97_BUS=y # @@ -1836,9 +1988,11 @@ CONFIG_HID_GENERIC=y # Special HID drivers # CONFIG_HID_A4TECH=y +# CONFIG_HID_ACCUTOUCH is not set # CONFIG_HID_ACRUX is not set CONFIG_HID_APPLE=y # CONFIG_HID_APPLEIR is not set +# CONFIG_HID_ASUS is not set # CONFIG_HID_AUREAL is not set CONFIG_HID_BELKIN=y # CONFIG_HID_BETOP_FF is not set @@ -1846,7 +2000,7 @@ CONFIG_HID_CHERRY=y CONFIG_HID_CHICONY=y # CONFIG_HID_CORSAIR is not set # CONFIG_HID_PRODIKEYS is not set -# CONFIG_HID_CP2112 is not set +# CONFIG_HID_CMEDIA is not set CONFIG_HID_CYPRESS=y CONFIG_HID_DRAGONRISE=y # CONFIG_DRAGONRISE_FF is not set @@ -1864,9 +2018,11 @@ CONFIG_HID_EZKEY=y # CONFIG_HID_WALTOP is not set CONFIG_HID_GYRATION=y # CONFIG_HID_ICADE is not set +CONFIG_HID_ITE=y CONFIG_HID_TWINHAN=y CONFIG_HID_KENSINGTON=y # CONFIG_HID_LCPOWER is not set +# CONFIG_HID_LED is not set # CONFIG_HID_LENOVO is not set CONFIG_HID_LOGITECH=y # CONFIG_HID_LOGITECH_HIDPP is not set @@ -1875,9 +2031,11 @@ CONFIG_HID_LOGITECH=y # CONFIG_LOGIG940_FF is not set # CONFIG_LOGIWHEELS_FF is not set # CONFIG_HID_MAGICMOUSE is not set +# CONFIG_HID_MAYFLASH is not set CONFIG_HID_MICROSOFT=y CONFIG_HID_MONTEREY=y # CONFIG_HID_MULTITOUCH is not set +# CONFIG_HID_NTI is not set CONFIG_HID_NTRIG=y # CONFIG_HID_ORTEK is not set CONFIG_HID_PANTHERLORD=y @@ -1887,6 +2045,7 @@ CONFIG_HID_PETALYNX=y # CONFIG_HID_PICOLCD is not set # CONFIG_HID_PLANTRONICS is not set # CONFIG_HID_PRIMAX is not set +# CONFIG_HID_RETRODE is not set # CONFIG_HID_ROCCAT is not set # CONFIG_HID_SAITEK is not set CONFIG_HID_SAMSUNG=y @@ -1905,6 +2064,7 @@ CONFIG_HID_TOPSEED=y # CONFIG_HID_THINGM is not set CONFIG_HID_THRUSTMASTER=y # CONFIG_THRUSTMASTER_FF is not set +# CONFIG_HID_UDRAW_PS3 is not set # CONFIG_HID_WACOM is not set # CONFIG_HID_WIIMOTE is not set # CONFIG_HID_XINMO is not set @@ -1912,6 +2072,7 @@ CONFIG_HID_ZEROPLUS=y # CONFIG_ZEROPLUS_FF is not set # CONFIG_HID_ZYDACRON is not set # CONFIG_HID_SENSOR_HUB is not set +# CONFIG_HID_ALPS is not set # # USB HID support @@ -1938,7 +2099,7 @@ CONFIG_USB_DEFAULT_PERSIST=y # CONFIG_USB_DYNAMIC_MINORS is not set # CONFIG_USB_OTG is not set # CONFIG_USB_OTG_WHITELIST is not set -# CONFIG_USB_ULPI_BUS is not set +# CONFIG_USB_LEDS_TRIGGER_USBPORT is not set CONFIG_USB_MON=y # CONFIG_USB_WUSB_CBAF is not set @@ -2016,7 +2177,6 @@ CONFIG_USB_ISP1760_HOST_ROLE=y # CONFIG_USB_SEVSEG is not set # CONFIG_USB_LEGOTOWER is not set # CONFIG_USB_LCD is not set -# CONFIG_USB_LED is not set # CONFIG_USB_CYPRESS_CY7C63 is not set # CONFIG_USB_CYTHERM is not set # CONFIG_USB_IDMOUSE is not set @@ -2030,7 +2190,9 @@ CONFIG_USB_ISP1760_HOST_ROLE=y # CONFIG_USB_ISIGHTFW is not set # CONFIG_USB_YUREX is not set # CONFIG_USB_EZUSB_FX2 is not set +# CONFIG_USB_HUB_USB251XB is not set # CONFIG_USB_HSIC_USB3503 is not set +# CONFIG_USB_HSIC_USB4604 is not set # CONFIG_USB_LINK_LAYER_TEST is not set # CONFIG_USB_CHAOSKEY is not set @@ -2039,28 +2201,30 @@ CONFIG_USB_ISP1760_HOST_ROLE=y # # CONFIG_USB_PHY is not set # CONFIG_NOP_USB_XCEIV is not set -# CONFIG_AM335X_PHY_USB is not set # CONFIG_USB_GPIO_VBUS is not set # CONFIG_USB_ISP1301 is not set # CONFIG_USB_ULPI is not set # CONFIG_USB_GADGET is not set -# CONFIG_USB_LED_TRIG is not set -# CONFIG_UWB is not set -CONFIG_MMC=y -# CONFIG_MMC_DEBUG is not set # -# MMC/SD/SDIO Card Drivers +# USB Power Delivery and Type-C drivers # +# CONFIG_TYPEC_UCSI is not set +# CONFIG_USB_LED_TRIG is not set +# CONFIG_USB_ULPI_BUS is not set +# CONFIG_UWB is not set +CONFIG_MMC=y +CONFIG_PWRSEQ_EMMC=y +CONFIG_PWRSEQ_SIMPLE=y CONFIG_MMC_BLOCK=y CONFIG_MMC_BLOCK_MINORS=8 -CONFIG_MMC_BLOCK_BOUNCE=y # CONFIG_SDIO_UART is not set # CONFIG_MMC_TEST is not set # # MMC/SD/SDIO Host Controller Drivers # +# CONFIG_MMC_DEBUG is not set CONFIG_MMC_ARMMMCI=y # CONFIG_MMC_SDHCI is not set # CONFIG_MMC_DW is not set @@ -2072,6 +2236,7 @@ CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y # CONFIG_LEDS_CLASS_FLASH is not set +# CONFIG_LEDS_BRIGHTNESS_HW_CHANGED is not set # # LED drivers @@ -2083,6 +2248,7 @@ CONFIG_LEDS_CLASS=y # CONFIG_LEDS_PCA9532 is not set CONFIG_LEDS_GPIO=y # CONFIG_LEDS_LP3944 is not set +# CONFIG_LEDS_LP3952 is not set # CONFIG_LEDS_LP5521 is not set # CONFIG_LEDS_LP5523 is not set # CONFIG_LEDS_LP5562 is not set @@ -2096,12 +2262,15 @@ CONFIG_LEDS_GPIO=y # CONFIG_LEDS_TCA6507 is not set # CONFIG_LEDS_TLC591XX is not set # CONFIG_LEDS_LM355x is not set +# CONFIG_LEDS_IS31FL319X is not set +# CONFIG_LEDS_IS31FL32XX is not set # # LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM) # # CONFIG_LEDS_BLINKM is not set # CONFIG_LEDS_SYSCON is not set +# CONFIG_LEDS_USER is not set # # LED Triggers @@ -2109,6 +2278,8 @@ CONFIG_LEDS_GPIO=y CONFIG_LEDS_TRIGGERS=y # CONFIG_LEDS_TRIGGER_TIMER is not set # CONFIG_LEDS_TRIGGER_ONESHOT is not set +# CONFIG_LEDS_TRIGGER_DISK is not set +# CONFIG_LEDS_TRIGGER_MTD is not set CONFIG_LEDS_TRIGGER_HEARTBEAT=y # CONFIG_LEDS_TRIGGER_BACKLIGHT is not set CONFIG_LEDS_TRIGGER_CPU=y @@ -2120,10 +2291,10 @@ CONFIG_LEDS_TRIGGER_CPU=y # # CONFIG_LEDS_TRIGGER_TRANSIENT is not set # CONFIG_LEDS_TRIGGER_CAMERA is not set +# CONFIG_LEDS_TRIGGER_PANIC is not set # CONFIG_ACCESSIBILITY is not set CONFIG_EDAC_ATOMIC_SCRUB=y CONFIG_EDAC_SUPPORT=y -# CONFIG_EDAC is not set CONFIG_RTC_LIB=y CONFIG_RTC_CLASS=y CONFIG_RTC_HCTOSYS=y @@ -2131,6 +2302,7 @@ CONFIG_RTC_HCTOSYS_DEVICE="rtc0" CONFIG_RTC_SYSTOHC=y CONFIG_RTC_SYSTOHC_DEVICE="rtc0" # CONFIG_RTC_DEBUG is not set +CONFIG_RTC_NVMEM=y # # RTC interfaces @@ -2149,32 +2321,37 @@ CONFIG_RTC_INTF_DEV=y # CONFIG_RTC_DRV_DS1307 is not set # CONFIG_RTC_DRV_DS1374 is not set # CONFIG_RTC_DRV_DS1672 is not set -# CONFIG_RTC_DRV_DS3232 is not set # CONFIG_RTC_DRV_HYM8563 is not set # CONFIG_RTC_DRV_MAX6900 is not set # CONFIG_RTC_DRV_RS5C372 is not set # CONFIG_RTC_DRV_ISL1208 is not set # CONFIG_RTC_DRV_ISL12022 is not set -# CONFIG_RTC_DRV_ISL12057 is not set # CONFIG_RTC_DRV_X1205 is not set -# CONFIG_RTC_DRV_PCF2127 is not set # CONFIG_RTC_DRV_PCF8523 is not set -# CONFIG_RTC_DRV_PCF8563 is not set # CONFIG_RTC_DRV_PCF85063 is not set +# CONFIG_RTC_DRV_PCF8563 is not set # CONFIG_RTC_DRV_PCF8583 is not set # CONFIG_RTC_DRV_M41T80 is not set # CONFIG_RTC_DRV_BQ32K is not set # CONFIG_RTC_DRV_S35390A is not set # CONFIG_RTC_DRV_FM3130 is not set +# CONFIG_RTC_DRV_RX8010 is not set # CONFIG_RTC_DRV_RX8581 is not set # CONFIG_RTC_DRV_RX8025 is not set # CONFIG_RTC_DRV_EM3027 is not set -# CONFIG_RTC_DRV_RV3029C2 is not set # CONFIG_RTC_DRV_RV8803 is not set # # SPI RTC drivers # +CONFIG_RTC_I2C_AND_SPI=y + +# +# SPI and I2C RTC drivers +# +# CONFIG_RTC_DRV_DS3232 is not set +# CONFIG_RTC_DRV_PCF2127 is not set +# CONFIG_RTC_DRV_RV3029C2 is not set # # Platform RTC drivers @@ -2201,13 +2378,20 @@ CONFIG_RTC_INTF_DEV=y # # CONFIG_RTC_DRV_PL030 is not set CONFIG_RTC_DRV_PL031=y +# CONFIG_RTC_DRV_FTRTC010 is not set # CONFIG_RTC_DRV_SNVS is not set +# CONFIG_RTC_DRV_R7301 is not set # # HID Sensor RTC drivers # # CONFIG_RTC_DRV_HID_SENSOR_TIME is not set # CONFIG_DMADEVICES is not set + +# +# DMABUF options +# +# CONFIG_SYNC_FILE is not set # CONFIG_AUXDISPLAY is not set # CONFIG_UIO is not set # CONFIG_VIRT_DRIVERS is not set @@ -2224,7 +2408,9 @@ CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y # # Microsoft Hyper-V guest support # +# CONFIG_HYPERV_TSCPAGE is not set # CONFIG_STAGING is not set +# CONFIG_GOLDFISH is not set # CONFIG_CHROME_PLATFORMS is not set CONFIG_CLKDEV_LOOKUP=y CONFIG_HAVE_CLK_PREPARE=y @@ -2233,26 +2419,29 @@ CONFIG_COMMON_CLK=y # # Common Clock Framework # +CONFIG_ICST=y CONFIG_COMMON_CLK_VERSATILE=y CONFIG_CLK_SP810=y CONFIG_CLK_VEXPRESS_OSC=y +# CONFIG_CLK_HSDK is not set # CONFIG_COMMON_CLK_SI5351 is not set # CONFIG_COMMON_CLK_SI514 is not set # CONFIG_COMMON_CLK_SI570 is not set +# CONFIG_COMMON_CLK_CDCE706 is not set # CONFIG_COMMON_CLK_CDCE925 is not set +# CONFIG_COMMON_CLK_CS2000_CP is not set # CONFIG_CLK_QORIQ is not set +# CONFIG_COMMON_CLK_NXP is not set # CONFIG_COMMON_CLK_PXA is not set -# CONFIG_COMMON_CLK_CDCE706 is not set - -# -# Hardware Spinlock drivers -# +# CONFIG_COMMON_CLK_PIC32 is not set +# CONFIG_COMMON_CLK_VC5 is not set +# CONFIG_HWSPINLOCK is not set # # Clock Source drivers # -CONFIG_CLKSRC_OF=y -CONFIG_CLKSRC_PROBE=y +CONFIG_TIMER_OF=y +CONFIG_TIMER_PROBE=y CONFIG_CLKSRC_MMIO=y CONFIG_ARM_ARCH_TIMER=y CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y @@ -2272,12 +2461,13 @@ CONFIG_IOMMU_SUPPORT=y # Generic IOMMU Pagetable Support # # CONFIG_IOMMU_IO_PGTABLE_LPAE is not set +# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set # CONFIG_ARM_SMMU is not set # # Remoteproc drivers # -# CONFIG_STE_MODEM_RPROC is not set +# CONFIG_REMOTEPROC is not set # # Rpmsg drivers @@ -2286,7 +2476,23 @@ CONFIG_IOMMU_SUPPORT=y # # SOC (System On Chip) specific Drivers # + +# +# Amlogic SoC drivers +# + +# +# Broadcom SoC drivers +# # CONFIG_SOC_BRCMSTB is not set + +# +# i.MX SoC drivers +# + +# +# Qualcomm SoC drivers +# # CONFIG_SUNXI_SRAM is not set # CONFIG_SOC_TI is not set # CONFIG_PM_DEVFREQ is not set @@ -2296,6 +2502,7 @@ CONFIG_IOMMU_SUPPORT=y # CONFIG_PWM is not set CONFIG_IRQCHIP=y CONFIG_ARM_GIC=y +CONFIG_ARM_GIC_MAX_NR=1 # CONFIG_IPACK_BUS is not set # CONFIG_RESET_CONTROLLER is not set # CONFIG_FMC is not set @@ -2304,9 +2511,9 @@ CONFIG_ARM_GIC=y # PHY Subsystem # # CONFIG_GENERIC_PHY is not set +# CONFIG_BCM_KONA_USB2_PHY is not set # CONFIG_PHY_PXA_28NM_HSIC is not set # CONFIG_PHY_PXA_28NM_USB2 is not set -# CONFIG_BCM_KONA_USB2_PHY is not set # CONFIG_POWERCAP is not set # CONFIG_MCB is not set @@ -2320,21 +2527,30 @@ CONFIG_ARM_PMU=y # Android # # CONFIG_ANDROID is not set -# CONFIG_NVMEM is not set +# CONFIG_DAX is not set +CONFIG_NVMEM=y # CONFIG_STM is not set # CONFIG_INTEL_TH is not set +# CONFIG_FPGA is not set # -# FPGA Configuration Support +# FSI support # -# CONFIG_FPGA is not set +# CONFIG_FSI is not set +# CONFIG_TEE is not set # # Firmware Drivers # CONFIG_ARM_PSCI_FW=y +# CONFIG_ARM_PSCI_CHECKER is not set # CONFIG_FIRMWARE_MEMMAP is not set CONFIG_HAVE_ARM_SMCCC=y +# CONFIG_GOOGLE_FIRMWARE is not set + +# +# Tegra firmware driver +# # # File systems @@ -2359,7 +2575,11 @@ CONFIG_FS_MBCACHE=y # CONFIG_NILFS2_FS is not set # CONFIG_F2FS_FS is not set # CONFIG_FS_POSIX_ACL is not set +CONFIG_EXPORTFS=y +# CONFIG_EXPORTFS_BLOCK_OPS is not set CONFIG_FILE_LOCKING=y +CONFIG_MANDATORY_FILE_LOCKING=y +# CONFIG_FS_ENCRYPTION is not set CONFIG_FSNOTIFY=y CONFIG_DNOTIFY=y CONFIG_INOTIFY_USER=y @@ -2389,6 +2609,7 @@ CONFIG_FAT_FS=y CONFIG_VFAT_FS=y CONFIG_FAT_DEFAULT_CODEPAGE=437 CONFIG_FAT_DEFAULT_IOCHARSET="iso8859-1" +# CONFIG_FAT_DEFAULT_UTF8 is not set # CONFIG_NTFS_FS is not set # @@ -2406,6 +2627,7 @@ CONFIG_TMPFS=y # CONFIG_HUGETLB_PAGE is not set # CONFIG_CONFIGFS_FS is not set CONFIG_MISC_FILESYSTEMS=y +# CONFIG_ORANGEFS_FS is not set # CONFIG_ADFS_FS is not set # CONFIG_AFFS_FS is not set # CONFIG_HFS_FS is not set @@ -2429,7 +2651,8 @@ CONFIG_UBIFS_FS=y CONFIG_UBIFS_FS_LZO=y CONFIG_UBIFS_FS_ZLIB=y # CONFIG_UBIFS_ATIME_SUPPORT is not set -# CONFIG_LOGFS is not set +# CONFIG_UBIFS_FS_ENCRYPTION is not set +CONFIG_UBIFS_FS_SECURITY=y CONFIG_CRAMFS=y CONFIG_SQUASHFS=y CONFIG_SQUASHFS_FILE_CACHE=y @@ -2442,6 +2665,7 @@ CONFIG_SQUASHFS_ZLIB=y # CONFIG_SQUASHFS_LZ4 is not set CONFIG_SQUASHFS_LZO=y # CONFIG_SQUASHFS_XZ is not set +# CONFIG_SQUASHFS_ZSTD is not set # CONFIG_SQUASHFS_4K_DEVBLK_SIZE is not set # CONFIG_SQUASHFS_EMBEDDED is not set CONFIG_SQUASHFS_FRAGMENT_CACHE_SIZE=3 @@ -2538,6 +2762,7 @@ CONFIG_NLS_ISO8859_1=y # printk and dmesg options # # CONFIG_PRINTK_TIME is not set +CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7 CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4 # CONFIG_BOOT_PRINTK_DELAY is not set # CONFIG_DYNAMIC_DEBUG is not set @@ -2564,6 +2789,7 @@ CONFIG_SECTION_MISMATCH_WARN_ONLY=y # CONFIG_DEBUG_FORCE_WEAK_PER_CPU is not set CONFIG_MAGIC_SYSRQ=y CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1 +CONFIG_MAGIC_SYSRQ_SERIAL=y CONFIG_DEBUG_KERNEL=y # @@ -2571,6 +2797,9 @@ CONFIG_DEBUG_KERNEL=y # # CONFIG_PAGE_EXTENSION is not set # CONFIG_DEBUG_PAGEALLOC is not set +# CONFIG_PAGE_POISONING is not set +# CONFIG_DEBUG_PAGE_REF is not set +# CONFIG_DEBUG_RODATA_TEST is not set # CONFIG_DEBUG_OBJECTS is not set # CONFIG_SLUB_DEBUG_ON is not set # CONFIG_SLUB_STATS is not set @@ -2578,6 +2807,8 @@ CONFIG_HAVE_DEBUG_KMEMLEAK=y # CONFIG_DEBUG_KMEMLEAK is not set # CONFIG_DEBUG_STACK_USAGE is not set # CONFIG_DEBUG_VM is not set +CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y +# CONFIG_DEBUG_VIRTUAL is not set CONFIG_DEBUG_MEMORY_INIT=y # CONFIG_DEBUG_PER_CPU_MAPS is not set # CONFIG_DEBUG_SHIRQ is not set @@ -2585,11 +2816,12 @@ CONFIG_DEBUG_MEMORY_INIT=y # # Debug Lockups and Hangs # -# CONFIG_LOCKUP_DETECTOR is not set +# CONFIG_SOFTLOCKUP_DETECTOR is not set CONFIG_DETECT_HUNG_TASK=y CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 # CONFIG_BOOTPARAM_HUNG_TASK_PANIC is not set CONFIG_BOOTPARAM_HUNG_TASK_PANIC_VALUE=0 +# CONFIG_WQ_WATCHDOG is not set # CONFIG_PANIC_ON_OOPS is not set CONFIG_PANIC_ON_OOPS_VALUE=0 CONFIG_PANIC_TIMEOUT=0 @@ -2598,7 +2830,6 @@ CONFIG_PANIC_TIMEOUT=0 # CONFIG_SCHEDSTATS is not set # CONFIG_SCHED_STACK_END_CHECK is not set # CONFIG_DEBUG_TIMEKEEPING is not set -# CONFIG_TIMER_STATS is not set # # Lock Debugging (spinlocks, mutexes, etc...) @@ -2613,7 +2844,9 @@ CONFIG_PANIC_TIMEOUT=0 # CONFIG_DEBUG_ATOMIC_SLEEP is not set # CONFIG_DEBUG_LOCKING_API_SELFTESTS is not set # CONFIG_LOCK_TORTURE_TEST is not set -# CONFIG_STACKTRACE is not set +# CONFIG_WW_MUTEX_SELFTEST is not set +CONFIG_STACKTRACE=y +# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set # CONFIG_DEBUG_KOBJECT is not set CONFIG_DEBUG_BUGVERBOSE=y # CONFIG_DEBUG_LIST is not set @@ -2626,29 +2859,38 @@ CONFIG_DEBUG_BUGVERBOSE=y # RCU Debugging # # CONFIG_PROVE_RCU is not set -# CONFIG_SPARSE_RCU_POINTER is not set # CONFIG_TORTURE_TEST is not set +# CONFIG_RCU_PERF_TEST is not set # CONFIG_RCU_TORTURE_TEST is not set CONFIG_RCU_CPU_STALL_TIMEOUT=21 -# CONFIG_RCU_TRACE is not set +CONFIG_RCU_TRACE=y # CONFIG_RCU_EQS_DEBUG is not set +# CONFIG_DEBUG_WQ_FORCE_RR_CPU is not set # CONFIG_DEBUG_BLOCK_EXT_DEVT is not set +# CONFIG_CPU_HOTPLUG_STATE_CONTROL is not set # CONFIG_NOTIFIER_ERROR_INJECTION is not set # CONFIG_FAULT_INJECTION is not set +# CONFIG_LATENCYTOP is not set +CONFIG_NOP_TRACER=y CONFIG_HAVE_FUNCTION_TRACER=y CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y CONFIG_HAVE_SYSCALL_TRACEPOINTS=y CONFIG_HAVE_C_RECORDMCOUNT=y CONFIG_TRACE_CLOCK=y CONFIG_RING_BUFFER=y +CONFIG_EVENT_TRACING=y +CONFIG_CONTEXT_SWITCH_TRACER=y CONFIG_RING_BUFFER_ALLOW_SWAP=y +CONFIG_TRACING=y CONFIG_TRACING_SUPPORT=y CONFIG_FTRACE=y # CONFIG_FUNCTION_TRACER is not set # CONFIG_IRQSOFF_TRACER is not set # CONFIG_SCHED_TRACER is not set +# CONFIG_HWLAT_TRACER is not set # CONFIG_ENABLE_DEFAULT_TRACERS is not set # CONFIG_FTRACE_SYSCALLS is not set # CONFIG_TRACER_SNAPSHOT is not set @@ -2657,18 +2899,21 @@ CONFIG_BRANCH_PROFILE_NONE=y # CONFIG_PROFILE_ALL_BRANCHES is not set # CONFIG_STACK_TRACER is not set # CONFIG_BLK_DEV_IO_TRACE is not set -# CONFIG_UPROBE_EVENT is not set -# CONFIG_PROBE_EVENTS is not set +CONFIG_UPROBE_EVENTS=y +CONFIG_PROBE_EVENTS=y # CONFIG_TRACEPOINT_BENCHMARK is not set # CONFIG_RING_BUFFER_BENCHMARK is not set # CONFIG_RING_BUFFER_STARTUP_TEST is not set +# CONFIG_TRACE_EVAL_MAP_FILE is not set CONFIG_TRACING_EVENTS_GPIO=y +# CONFIG_DMA_API_DEBUG is not set # # Runtime Testing # # CONFIG_LKDTM is not set # CONFIG_TEST_LIST_SORT is not set +# CONFIG_TEST_SORT is not set # CONFIG_BACKTRACE_SELF_TEST is not set # CONFIG_RBTREE_TEST is not set # CONFIG_INTERVAL_TREE_TEST is not set @@ -2678,21 +2923,29 @@ CONFIG_TRACING_EVENTS_GPIO=y # CONFIG_TEST_STRING_HELPERS is not set # CONFIG_TEST_KSTRTOX is not set # CONFIG_TEST_PRINTF is not set +# CONFIG_TEST_BITMAP is not set +# CONFIG_TEST_UUID is not set # CONFIG_TEST_RHASHTABLE is not set # CONFIG_TEST_HASH is not set -# CONFIG_DMA_API_DEBUG is not set # CONFIG_TEST_LKM is not set # CONFIG_TEST_USER_COPY is not set # CONFIG_TEST_BPF is not set # CONFIG_TEST_FIRMWARE is not set +# CONFIG_TEST_SYSCTL is not set # CONFIG_TEST_UDELAY is not set -# CONFIG_MEMTEST is not set # CONFIG_TEST_STATIC_KEYS is not set +# CONFIG_MEMTEST is not set +# CONFIG_BUG_ON_DATA_CORRUPTION is not set # CONFIG_SAMPLES is not set CONFIG_HAVE_ARCH_KGDB=y # CONFIG_KGDB is not set -# CONFIG_ARM_PTDUMP is not set +# CONFIG_ARCH_WANTS_UBSAN_NO_NULL is not set +# CONFIG_UBSAN is not set +CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set +# CONFIG_ARM_PTDUMP is not set +# CONFIG_UNWINDER_FRAME_POINTER is not set +CONFIG_UNWINDER_ARM=y CONFIG_ARM_UNWIND=y CONFIG_DEBUG_USER=y # CONFIG_DEBUG_LL is not set @@ -2700,7 +2953,6 @@ CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" # CONFIG_DEBUG_UART_8250 is not set CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" # CONFIG_PID_IN_CONTEXTIDR is not set -# CONFIG_DEBUG_SET_MODULE_RONX is not set # CONFIG_CORESIGHT is not set # @@ -2710,6 +2962,9 @@ CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" # CONFIG_SECURITY_DMESG_RESTRICT is not set # CONFIG_SECURITY is not set # CONFIG_SECURITYFS is not set +CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR=y +# CONFIG_HARDENED_USERCOPY is not set +# CONFIG_STATIC_USERMODEHELPER is not set CONFIG_DEFAULT_SECURITY_DAC=y CONFIG_DEFAULT_SECURITY="" CONFIG_CRYPTO=y @@ -2727,9 +2982,12 @@ CONFIG_CRYPTO_HASH2=y CONFIG_CRYPTO_RNG=m CONFIG_CRYPTO_RNG2=y CONFIG_CRYPTO_RNG_DEFAULT=m -CONFIG_CRYPTO_PCOMP2=y CONFIG_CRYPTO_AKCIPHER2=y +CONFIG_CRYPTO_KPP2=y +CONFIG_CRYPTO_ACOMP2=y # CONFIG_CRYPTO_RSA is not set +# CONFIG_CRYPTO_DH is not set +# CONFIG_CRYPTO_ECDH is not set CONFIG_CRYPTO_MANAGER=m CONFIG_CRYPTO_MANAGER2=y # CONFIG_CRYPTO_USER is not set @@ -2791,6 +3049,7 @@ CONFIG_CRYPTO_CRC32C=y # CONFIG_CRYPTO_SHA1 is not set CONFIG_CRYPTO_SHA256=m # CONFIG_CRYPTO_SHA512 is not set +# CONFIG_CRYPTO_SHA3 is not set # CONFIG_CRYPTO_TGR192 is not set # CONFIG_CRYPTO_WP512 is not set @@ -2798,6 +3057,7 @@ CONFIG_CRYPTO_SHA256=m # Ciphers # CONFIG_CRYPTO_AES=y +# CONFIG_CRYPTO_AES_TI is not set # CONFIG_CRYPTO_ANUBIS is not set # CONFIG_CRYPTO_ARC4 is not set # CONFIG_CRYPTO_BLOWFISH is not set @@ -2818,7 +3078,6 @@ CONFIG_CRYPTO_AES=y # Compression # CONFIG_CRYPTO_DEFLATE=y -# CONFIG_CRYPTO_ZLIB is not set CONFIG_CRYPTO_LZO=y # CONFIG_CRYPTO_842 is not set # CONFIG_CRYPTO_LZ4 is not set @@ -2831,7 +3090,6 @@ CONFIG_CRYPTO_LZO=y CONFIG_CRYPTO_DRBG_MENU=m CONFIG_CRYPTO_DRBG_HMAC=y # CONFIG_CRYPTO_DRBG_HASH is not set -# CONFIG_CRYPTO_DRBG_CTR is not set CONFIG_CRYPTO_DRBG=m CONFIG_CRYPTO_JITTERENTROPY=m # CONFIG_CRYPTO_USER_API_HASH is not set @@ -2844,7 +3102,7 @@ CONFIG_CRYPTO_JITTERENTROPY=m # Certificates for signature checking # # CONFIG_ARM_CRYPTO is not set -# CONFIG_BINARY_PRINTF is not set +CONFIG_BINARY_PRINTF=y # # Library routines @@ -2868,6 +3126,7 @@ CONFIG_CRC32_SLICEBY8=y # CONFIG_CRC32_SLICEBY4 is not set # CONFIG_CRC32_SARWATE is not set # CONFIG_CRC32_BIT is not set +# CONFIG_CRC4 is not set # CONFIG_CRC7 is not set # CONFIG_LIBCRC32C is not set # CONFIG_CRC8 is not set @@ -2896,19 +3155,25 @@ CONFIG_DECOMPRESS_LZ4=y CONFIG_GENERIC_ALLOCATOR=y CONFIG_HAS_IOMEM=y CONFIG_HAS_DMA=y +# CONFIG_SGL_ALLOC is not set +# CONFIG_DMA_NOOP_OPS is not set +# CONFIG_DMA_VIRT_OPS is not set CONFIG_CPU_RMAP=y CONFIG_DQL=y CONFIG_GLOB=y # CONFIG_GLOB_SELFTEST is not set CONFIG_NLATTR=y -CONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE=y # CONFIG_CORDIC is not set # CONFIG_DDR is not set +# CONFIG_IRQ_POLL is not set CONFIG_LIBFDT=y CONFIG_FONT_SUPPORT=y # CONFIG_FONTS is not set CONFIG_FONT_8x8=y CONFIG_FONT_8x16=y # CONFIG_SG_SPLIT is not set +CONFIG_SG_POOL=y CONFIG_ARCH_HAS_SG_CHAIN=y +CONFIG_SBITMAP=y +# CONFIG_STRING_SELFTEST is not set # CONFIG_VIRTUALIZATION is not set diff --git a/src/ci/docker/host-x86_64/disabled/dist-m68k-linux/Dockerfile b/src/ci/docker/host-x86_64/disabled/dist-m68k-linux/Dockerfile index 17203994cdff0..cbac2310d0dfd 100644 --- a/src/ci/docker/host-x86_64/disabled/dist-m68k-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/disabled/dist-m68k-linux/Dockerfile @@ -16,6 +16,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libssl-dev \ pkg-config +COPY scripts/cmake.sh /scripts/ +RUN /scripts/cmake.sh COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh diff --git a/src/ci/docker/host-x86_64/disabled/riscv64gc-linux/Dockerfile b/src/ci/docker/host-x86_64/disabled/riscv64gc-linux/Dockerfile index 4377608700b0c..07260be35877a 100644 --- a/src/ci/docker/host-x86_64/disabled/riscv64gc-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/disabled/riscv64gc-linux/Dockerfile @@ -19,6 +19,7 @@ RUN apt-get update -y && apt-get install -y --no-install-recommends \ g++ \ libc6-dev \ libc6-dev-riscv64-cross \ + libssl-dev \ make \ ninja-build \ patch \ @@ -94,6 +95,9 @@ RUN mkdir build && cd build && \ WORKDIR /tmp RUN rm -rf /tmp/riscv-pk +COPY scripts/cmake.sh /scripts/ +RUN /scripts/cmake.sh + COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh diff --git a/src/ci/docker/host-x86_64/dist-android/Dockerfile b/src/ci/docker/host-x86_64/dist-android/Dockerfile index 9c6f648896b51..b09b6edb01a6d 100644 --- a/src/ci/docker/host-x86_64/dist-android/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-android/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.10 +FROM ubuntu:23.04 COPY scripts/android-base-apt-get.sh /scripts/ RUN sh /scripts/android-base-apt-get.sh diff --git a/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile index ea4a2a2427dbf..f5274104d60ac 100644 --- a/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-i686-linux/Dockerfile @@ -12,7 +12,6 @@ RUN yum upgrade -y && \ automake \ bzip2 \ file \ - cmake3 \ gcc \ gcc-c++ \ git \ @@ -35,7 +34,7 @@ RUN yum upgrade -y && \ zlib-devel.x86_64 \ && yum clean all -RUN mkdir -p /rustroot/bin && ln -s /usr/bin/cmake3 /rustroot/bin/cmake +RUN mkdir -p /rustroot/bin ENV PATH=/rustroot/bin:$PATH ENV LD_LIBRARY_PATH=/rustroot/lib64:/rustroot/lib32:/rustroot/lib @@ -48,6 +47,9 @@ COPY host-x86_64/dist-x86_64-linux/shared.sh /tmp/ COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ RUN ./build-gcc.sh && yum remove -y gcc gcc-c++ +COPY scripts/cmake.sh /tmp/ +RUN ./cmake.sh + # Now build LLVM+Clang, afterwards configuring further compilations to use the # clang/clang++ compilers. COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ diff --git a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile index 6b7b32a8bc77f..5dc282403be9c 100644 --- a/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-powerpc64le-linux/Dockerfile @@ -1,15 +1,12 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 COPY scripts/cross-apt-packages.sh /scripts/ RUN sh /scripts/cross-apt-packages.sh COPY scripts/rustbuild-setup.sh /scripts/ RUN sh /scripts/rustbuild-setup.sh -USER rustbuild WORKDIR /tmp -USER root - RUN apt-get install -y --no-install-recommends rpm2cpio cpio COPY host-x86_64/dist-powerpc64le-linux/shared.sh host-x86_64/dist-powerpc64le-linux/build-powerpc64le-toolchain.sh /tmp/ RUN ./build-powerpc64le-toolchain.sh diff --git a/src/ci/docker/host-x86_64/dist-riscv64-linux/riscv64-unknown-linux-gnu.defconfig b/src/ci/docker/host-x86_64/dist-riscv64-linux/riscv64-unknown-linux-gnu.defconfig index 10075907beb00..470cef1a84e18 100644 --- a/src/ci/docker/host-x86_64/dist-riscv64-linux/riscv64-unknown-linux-gnu.defconfig +++ b/src/ci/docker/host-x86_64/dist-riscv64-linux/riscv64-unknown-linux-gnu.defconfig @@ -10,7 +10,7 @@ CT_ARCH_64=y CT_ARCH_ARCH="rv64gc" CT_KERNEL_LINUX=y CT_LINUX_V_4_20=y -CT_BINUTILS_V_2_32=y +CT_BINUTILS_V_2_36=y CT_GLIBC_V_2_29=y CT_GCC_V_8=y CT_CC_LANG_CXX=y diff --git a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile index 4576e6d4fa256..8f4ad0f4e755c 100644 --- a/src/ci/docker/host-x86_64/dist-various-1/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-1/Dockerfile @@ -1,6 +1,7 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ automake \ bison \ @@ -73,8 +74,8 @@ RUN env \ CXX=arm-linux-gnueabihf-g++ CXXFLAGS="-march=armv6 -marm -mfpu=vfp" \ bash musl.sh armhf && \ env \ - CC=arm-linux-gnueabihf-gcc CFLAGS="-march=armv7-a" \ - CXX=arm-linux-gnueabihf-g++ CXXFLAGS="-march=armv7-a" \ + CC=arm-linux-gnueabihf-gcc CFLAGS="-march=armv7-a+fp" \ + CXX=arm-linux-gnueabihf-g++ CXXFLAGS="-march=armv7-a+fp" \ bash musl.sh armv7hf && \ env \ CC=mips-openwrt-linux-gcc \ @@ -147,7 +148,7 @@ ENV TARGETS=$TARGETS,armv7a-none-eabi ENV CFLAGS_armv5te_unknown_linux_musleabi="-march=armv5te -marm -mfloat-abi=soft" \ CFLAGS_arm_unknown_linux_musleabi="-march=armv6 -marm" \ CFLAGS_arm_unknown_linux_musleabihf="-march=armv6 -marm -mfpu=vfp" \ - CFLAGS_armv7_unknown_linux_musleabihf="-march=armv7-a" \ + CFLAGS_armv7_unknown_linux_musleabihf="-march=armv7-a+fp" \ CC_mipsel_unknown_linux_musl=mipsel-openwrt-linux-gcc \ CC_mips_unknown_linux_musl=mips-openwrt-linux-gcc \ CC_mips64el_unknown_linux_muslabi64=mips64el-linux-gnuabi64-gcc \ diff --git a/src/ci/docker/host-x86_64/dist-various-1/install-x86_64-redox.sh b/src/ci/docker/host-x86_64/dist-various-1/install-x86_64-redox.sh index dad9792233847..f86402b0180bb 100755 --- a/src/ci/docker/host-x86_64/dist-various-1/install-x86_64-redox.sh +++ b/src/ci/docker/host-x86_64/dist-various-1/install-x86_64-redox.sh @@ -2,5 +2,5 @@ set -ex -curl https://static.redox-os.org/toolchain/x86_64-unknown-redox/relibc-install.tar.gz | \ +curl https://ci-mirrors.rust-lang.org/rustc/2022-11-27-relibc-install.tar.gz | \ tar --extract --gzip --directory /usr/local diff --git a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile index 0f5df95a0dd4b..4777ae558602a 100644 --- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 COPY scripts/cross-apt-packages.sh /scripts/ RUN sh /scripts/cross-apt-packages.sh @@ -9,7 +9,7 @@ RUN sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list RUN apt-get update && apt-get build-dep -y clang llvm && apt-get install -y --no-install-recommends \ build-essential \ # gcc-multilib can not be installed together with gcc-arm-linux-gnueabi - g++-8-multilib \ + g++-9-multilib \ libedit-dev \ libgmp-dev \ libisl-dev \ @@ -24,7 +24,7 @@ RUN apt-get update && apt-get build-dep -y clang llvm && apt-get install -y --no # Needed for apt-key to work: dirmngr \ gpg-agent \ - g++-8-arm-linux-gnueabi + g++-9-arm-linux-gnueabi RUN apt-key adv --batch --yes --keyserver keyserver.ubuntu.com --recv-keys 74DA7924C5513486 RUN add-apt-repository -y 'deb https://apt.dilos.org/dilos dilos2 main' @@ -51,8 +51,8 @@ ENV \ AR_x86_64_sun_solaris=x86_64-sun-solaris2.10-ar \ CC_x86_64_sun_solaris=x86_64-sun-solaris2.10-gcc \ CXX_x86_64_sun_solaris=x86_64-sun-solaris2.10-g++ \ - CC_armv7_unknown_linux_gnueabi=arm-linux-gnueabi-gcc-8 \ - CXX_armv7_unknown_linux_gnueabi=arm-linux-gnueabi-g++-8 \ + CC_armv7_unknown_linux_gnueabi=arm-linux-gnueabi-gcc-9 \ + CXX_armv7_unknown_linux_gnueabi=arm-linux-gnueabi-g++-9 \ AR_x86_64_fortanix_unknown_sgx=ar \ CC_x86_64_fortanix_unknown_sgx=clang-11 \ CFLAGS_x86_64_fortanix_unknown_sgx="-D__ELF__ -isystem/usr/include/x86_64-linux-gnu -mlvi-hardening -mllvm -x86-experimental-lvi-inline-asm-hardening" \ @@ -67,14 +67,14 @@ ENV \ CXX_i686_unknown_uefi=clang++-11 \ CC_x86_64_unknown_uefi=clang-11 \ CXX_x86_64_unknown_uefi=clang++-11 \ - CC=gcc-8 \ - CXX=g++-8 + CC=gcc-9 \ + CXX=g++-9 WORKDIR /build COPY scripts/musl.sh /build RUN env \ - CC=arm-linux-gnueabi-gcc-8 CFLAGS="-march=armv7-a" \ - CXX=arm-linux-gnueabi-g++-8 CXXFLAGS="-march=armv7-a" \ + CC=arm-linux-gnueabi-gcc-9 CFLAGS="-march=armv7-a" \ + CXX=arm-linux-gnueabi-g++-9 CXXFLAGS="-march=armv7-a" \ bash musl.sh armv7 && \ rm -rf /build/* @@ -93,6 +93,9 @@ RUN /tmp/build-x86_64-fortanix-unknown-sgx-toolchain.sh COPY host-x86_64/dist-various-2/build-wasi-toolchain.sh /tmp/ RUN /tmp/build-wasi-toolchain.sh +COPY host-x86_64/dist-various-2/build-wasi-threads-toolchain.sh /tmp/ +RUN /tmp/build-wasi-threads-toolchain.sh + COPY scripts/freebsd-toolchain.sh /tmp/ RUN /tmp/freebsd-toolchain.sh i686 @@ -113,7 +116,10 @@ ENV CARGO_TARGET_AARCH64_UNKNOWN_FUCHSIA_RUSTFLAGS \ ENV TARGETS=x86_64-unknown-fuchsia ENV TARGETS=$TARGETS,aarch64-unknown-fuchsia ENV TARGETS=$TARGETS,wasm32-unknown-unknown -ENV TARGETS=$TARGETS,wasm32-wasi +ENV TARGETS=$TARGETS,wasm32-unknown-wasi +ENV TARGETS=$TARGETS,wasm32-wasi-preview1-threads +ENV TARGETS=$TARGETS,wasm32-wasmer-wasi +ENV TARGETS=$TARGETS,wasm64-wasmer-wasi ENV TARGETS=$TARGETS,sparcv9-sun-solaris ENV TARGETS=$TARGETS,x86_64-pc-solaris ENV TARGETS=$TARGETS,x86_64-sun-solaris @@ -129,13 +135,14 @@ ENV TARGETS=$TARGETS,i686-unknown-uefi ENV TARGETS=$TARGETS,x86_64-unknown-uefi # As per https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1300211 -# we need asm in the search path for gcc-8 (for gnux32) but not in the search path of the +# we need asm in the search path for gcc-9 (for gnux32) but not in the search path of the # cross compilers. -# Luckily one of the folders is /usr/local/include so symlink /usr/include/asm-generic there -RUN ln -s /usr/include/asm-generic /usr/local/include/asm +# Luckily one of the folders is /usr/local/include so symlink /usr/include/x86_64-linux-gnu/asm there +RUN ln -s /usr/include/x86_64-linux-gnu/asm /usr/local/include/asm ENV RUST_CONFIGURE_ARGS --enable-extended --enable-lld --disable-docs \ --set target.wasm32-wasi.wasi-root=/wasm32-wasi \ + --set target.wasm32-wasi-preview1-threads.wasi-root=/wasm32-wasi-preview1-threads \ --musl-root-armv7=/musl-armv7 ENV SCRIPT python3 ../x.py dist --host='' --target $TARGETS diff --git a/src/ci/docker/host-x86_64/dist-various-2/build-wasi-threads-toolchain.sh b/src/ci/docker/host-x86_64/dist-various-2/build-wasi-threads-toolchain.sh new file mode 100755 index 0000000000000..689fe52863e07 --- /dev/null +++ b/src/ci/docker/host-x86_64/dist-various-2/build-wasi-threads-toolchain.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +set -ex + +# Originally from https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz +curl https://ci-mirrors.rust-lang.org/rustc/2023-05-17-clang%2Bllvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz | \ + tar xJf - +bin="$PWD/clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04/bin" + +git clone https://github.com/WebAssembly/wasi-libc + +cd wasi-libc +git reset --hard ec4566beae84e54952637f0bf61bee4b4cacc087 +make -j$(nproc) \ + CC="$bin/clang" \ + NM="$bin/llvm-nm" \ + AR="$bin/llvm-ar" \ + THREAD_MODEL=posix \ + INSTALL_DIR=/wasm32-wasi-preview1-threads \ + install + +cd .. +rm -rf wasi-libc +rm -rf clang+llvm* diff --git a/src/ci/docker/host-x86_64/dist-various-2/build-wasi-toolchain.sh b/src/ci/docker/host-x86_64/dist-various-2/build-wasi-toolchain.sh index 5fbce36c39d28..4b0d360686f12 100755 --- a/src/ci/docker/host-x86_64/dist-various-2/build-wasi-toolchain.sh +++ b/src/ci/docker/host-x86_64/dist-various-2/build-wasi-toolchain.sh @@ -2,15 +2,15 @@ set -ex -# Originally from https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.6/clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz -curl https://ci-mirrors.rust-lang.org/rustc/2022-12-06-clang%2Bllvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04.tar.xz | \ +# Originally from https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz +curl https://ci-mirrors.rust-lang.org/rustc/2023-05-17-clang%2Bllvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04.tar.xz | \ tar xJf - -bin="$PWD/clang+llvm-15.0.6-x86_64-linux-gnu-ubuntu-18.04/bin" +bin="$PWD/clang+llvm-16.0.4-x86_64-linux-gnu-ubuntu-22.04/bin" git clone https://github.com/WebAssembly/wasi-libc cd wasi-libc -git reset --hard 4362b1885fd369e042a7c0ecd8df3b6cd47fb4e8 +git reset --hard ec4566beae84e54952637f0bf61bee4b4cacc087 make -j$(nproc) \ CC="$bin/clang" \ NM="$bin/llvm-nm" \ diff --git a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile index 04fdb15f5acb2..319989df33460 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-linux/Dockerfile @@ -12,7 +12,6 @@ RUN yum upgrade -y && \ automake \ bzip2 \ file \ - cmake3 \ gcc \ gcc-c++ \ git \ @@ -35,7 +34,7 @@ RUN yum upgrade -y && \ zlib-devel.x86_64 \ && yum clean all -RUN mkdir -p /rustroot/bin && ln -s /usr/bin/cmake3 /rustroot/bin/cmake +RUN mkdir -p /rustroot/bin ENV PATH=/rustroot/bin:$PATH ENV LD_LIBRARY_PATH=/rustroot/lib64:/rustroot/lib32:/rustroot/lib @@ -48,14 +47,19 @@ COPY host-x86_64/dist-x86_64-linux/shared.sh /tmp/ COPY host-x86_64/dist-x86_64-linux/build-gcc.sh /tmp/ RUN ./build-gcc.sh && yum remove -y gcc gcc-c++ +# LLVM 17 needs cmake 3.20 or higher. +COPY scripts/cmake.sh /tmp/ +RUN ./cmake.sh + # Now build LLVM+Clang, afterwards configuring further compilations to use the # clang/clang++ compilers. COPY host-x86_64/dist-x86_64-linux/build-clang.sh /tmp/ RUN ./build-clang.sh ENV CC=clang CXX=clang++ -# rustc-perf version from 2023-03-15 -ENV PERF_COMMIT 9dfaa35193154b690922347ee1141a06ec87a199 +# rustc-perf version from 2023-05-30 +# Should also be changed in the opt-dist tool for other environments. +ENV PERF_COMMIT 8b2ac3042e1ff2c0074455a0a3618adef97156b1 RUN curl -LS -o perf.zip https://github.com/rust-lang/rustc-perf/archive/$PERF_COMMIT.zip && \ unzip perf.zip && \ mv rustc-perf-$PERF_COMMIT rustc-perf && \ @@ -81,7 +85,9 @@ ENV RUST_CONFIGURE_ARGS \ --set rust.jemalloc \ --set rust.use-lld=true \ --set rust.lto=thin -ENV SCRIPT python3 ../src/ci/stage-build.py python3 ../x.py dist \ + +ENV SCRIPT python3 ../x.py build --set rust.debug=true opt-dist && \ + ./build/$HOSTS/stage0-tools-bin/opt-dist python3 ../x.py dist \ --host $HOSTS --target $HOSTS \ --include-default-paths \ build-manifest bootstrap diff --git a/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile index 6f04dcad9a54f..c9a6a2dd069e2 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-musl/Dockerfile @@ -1,6 +1,7 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ make \ ninja-build \ @@ -11,6 +12,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins python3 \ git \ cmake \ + bzip2 \ xz-utils \ sudo \ gdb \ @@ -21,10 +23,6 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins WORKDIR /build/ -# Build cmake before musl toolchain, as we replace the compiler during that step. -COPY scripts/cmake.sh /scripts/ -RUN /scripts/cmake.sh - COPY scripts/musl-toolchain.sh /build/ # We need to mitigate rust-lang/rust#34978 when compiling musl itself as well RUN CFLAGS="-Wa,-mrelax-relocations=no -Wa,--compress-debug-sections=none -Wl,--compress-debug-sections=none" \ diff --git a/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile b/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile index d03c364547e03..effdc99d9a650 100644 --- a/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-x86_64-netbsd/Dockerfile @@ -1,11 +1,14 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 +ARG DEBIAN_FRONTEND=noninteractive COPY scripts/cross-apt-packages.sh /scripts/ RUN sh /scripts/cross-apt-packages.sh -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y zlib1g-dev +RUN apt-get install -y zlib1g-dev COPY host-x86_64/dist-x86_64-netbsd/build-netbsd-toolchain.sh /tmp/ -RUN /tmp/build-netbsd-toolchain.sh +# GCC 10 changed the default to -fno-common, which causes errors with the NetBSD-9.0 sources like: +# /usr/bin/ld: buf.o:(.bss+0x0): multiple definition of `debug_file'; arch.o:(.bss+0x0): first defined here +RUN env HOST_CFLAGS="-O -fcommon" /tmp/build-netbsd-toolchain.sh COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh diff --git a/src/ci/docker/host-x86_64/i686-gnu/Dockerfile b/src/ci/docker/host-x86_64/i686-gnu/Dockerfile index b5abf6564a633..61811c41904c4 100644 --- a/src/ci/docker/host-x86_64/i686-gnu/Dockerfile +++ b/src/ci/docker/host-x86_64/i686-gnu/Dockerfile @@ -24,10 +24,10 @@ COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh ENV RUST_CONFIGURE_ARGS --build=i686-unknown-linux-gnu -# Exclude some tests that are unlikely to be platform specific, to speed up +# Skip some tests that are unlikely to be platform specific, to speed up # this slow job. ENV SCRIPT python3 ../x.py --stage 2 test \ - --exclude src/bootstrap \ - --exclude tests/rustdoc-js \ - --exclude src/tools/error_index_generator \ - --exclude src/tools/linkchecker + --skip src/bootstrap \ + --skip tests/rustdoc-js \ + --skip src/tools/error_index_generator \ + --skip src/tools/linkchecker diff --git a/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile b/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile index 34b93be412e5f..0a49eab4d5028 100644 --- a/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile @@ -26,11 +26,13 @@ COPY scripts/sccache.sh /scripts/ RUN sh /scripts/sccache.sh COPY host-x86_64/mingw-check/reuse-requirements.txt /tmp/ -RUN pip3 install --no-deps --no-cache-dir --require-hashes -r /tmp/reuse-requirements.txt +RUN pip3 install --no-deps --no-cache-dir --require-hashes -r /tmp/reuse-requirements.txt \ + && pip3 install virtualenv COPY host-x86_64/mingw-check/validate-toolstate.sh /scripts/ COPY host-x86_64/mingw-check/validate-error-codes.sh /scripts/ # NOTE: intentionally uses python2 for x.py so we can test it still works. # validate-toolstate only runs in our CI, so it's ok for it to only support python3. -ENV SCRIPT python2.7 ../x.py test --stage 0 src/tools/tidy tidyselftest +ENV SCRIPT TIDY_PRINT_DIFF=1 python2.7 ../x.py test \ + --stage 0 src/tools/tidy tidyselftest --extra-checks=py:lint diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile index 515890aef8df8..85a9a5d33752f 100644 --- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile @@ -45,6 +45,9 @@ ENV SCRIPT python3 ../x.py --stage 2 test src/tools/expand-yaml-anchors && \ python3 ../x.py test --stage 0 src/tools/compiletest && \ python3 ../x.py test --stage 0 core alloc std test proc_macro && \ # Build both public and internal documentation. + RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 library && \ + mkdir -p /checkout/obj/staging/doc && \ + cp -r build/x86_64-unknown-linux-gnu/doc /checkout/obj/staging && \ RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 compiler && \ RUSTDOCFLAGS=\"--document-private-items --document-hidden-items\" python3 ../x.py doc --stage 0 library/test && \ /scripts/validate-toolstate.sh && \ diff --git a/src/ci/docker/host-x86_64/test-various/Dockerfile b/src/ci/docker/host-x86_64/test-various/Dockerfile index 1dc7b79872438..4fe66014c17cd 100644 --- a/src/ci/docker/host-x86_64/test-various/Dockerfile +++ b/src/ci/docker/host-x86_64/test-various/Dockerfile @@ -1,6 +1,7 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ clang-11 \ g++ \ make \ @@ -15,23 +16,20 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins gdb \ libssl-dev \ pkg-config \ + bzip2 \ xz-utils \ wget \ patch \ ovmf \ + ovmf-ia32 \ qemu-efi-aarch64 \ qemu-system-arm \ qemu-system-x86 \ && rm -rf /var/lib/apt/lists/* -RUN curl -sL https://nodejs.org/dist/v15.14.0/node-v15.14.0-linux-x64.tar.xz | \ +RUN curl -sL https://nodejs.org/dist/v18.12.0/node-v18.12.0-linux-x64.tar.xz | \ tar -xJ -# Install 32-bit OVMF files for the i686-unknown-uefi test. This package -# is not available in ubuntu 20.04, so download a 22.04 package. -RUN curl -sL --output ovmf-ia32.deb http://mirrors.kernel.org/ubuntu/pool/universe/e/edk2/ovmf-ia32_2022.02-3_all.deb -RUN dpkg -i ovmf-ia32.deb && rm ovmf-ia32.deb - WORKDIR /build/ COPY scripts/musl-toolchain.sh /build/ RUN bash musl-toolchain.sh x86_64 && rm -rf build @@ -42,7 +40,7 @@ RUN sh /scripts/sccache.sh ENV RUST_CONFIGURE_ARGS \ --musl-root-x86_64=/usr/local/x86_64-linux-musl \ - --set build.nodejs=/node-v15.14.0-linux-x64/bin/node \ + --set build.nodejs=/node-v18.12.0-linux-x64/bin/node \ --set rust.lld # Some run-make tests have assertions about code size, and enabling debug @@ -58,6 +56,8 @@ ENV WASM_SCRIPT python3 /checkout/x.py --stage 2 test --host='' --target $WASM_T tests/ui \ tests/mir-opt \ tests/codegen-units \ + tests/codegen \ + tests/assembly \ library/core ENV NVPTX_TARGETS=nvptx64-nvidia-cuda diff --git a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml index fa8e5b3d08060..2d17cf7d47a8b 100644 --- a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml +++ b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2021" [workspace] +resolver = "1" [dependencies] r-efi = "4.1.0" diff --git a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py old mode 100644 new mode 100755 index ffae7b0d4ac27..3577643ca550a --- a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py +++ b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py @@ -109,12 +109,7 @@ def build_and_run(tmp_dir, target): '-drive', f'format=raw,file=fat:rw:{esp}', capture=True, - # Ubuntu 20.04 (which is what the Dockerfile currently - # uses) provides QEMU 4.2.1, which segfaults on - # shutdown under some circumstances. That has been - # fixed in newer versions of QEMU, but for now just - # don't check the exit status. - check=False, + check=True, # Set a timeout to kill the VM in case something goes wrong. timeout=60).stdout diff --git a/src/ci/docker/host-x86_64/wasm32/Dockerfile b/src/ci/docker/host-x86_64/wasm32/Dockerfile index ef1fde1c3b924..02b4664eb557c 100644 --- a/src/ci/docker/host-x86_64/wasm32/Dockerfile +++ b/src/ci/docker/host-x86_64/wasm32/Dockerfile @@ -1,6 +1,7 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ make \ ninja-build \ @@ -13,6 +14,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins sudo \ gdb \ xz-utils \ + libssl-dev \ bzip2 \ && rm -rf /var/lib/apt/lists/* @@ -53,7 +55,8 @@ COPY static/gitconfig /etc/gitconfig # Emscripten installation is user-specific ENV NO_CHANGE_USER=1 +RUN chown 10719 -R /emsdk-portable/ # Exclude library/alloc due to OOM in benches. ENV SCRIPT python3 ../x.py test --stage 2 --host='' --target $TARGETS \ - --exclude library/alloc + --skip library/alloc diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile index 735d4d4dfcfa1..e4534d0f8408c 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-debug/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14-stage1/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14-stage1/Dockerfile deleted file mode 100644 index d45ef0a7d07e3..0000000000000 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14-stage1/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -FROM ubuntu:22.04 - -ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ - g++ \ - gcc-multilib \ - make \ - ninja-build \ - file \ - curl \ - ca-certificates \ - python3 \ - git \ - cmake \ - sudo \ - gdb \ - llvm-14-tools \ - llvm-14-dev \ - libedit-dev \ - libssl-dev \ - pkg-config \ - zlib1g-dev \ - xz-utils \ - nodejs \ - mingw-w64 \ - && rm -rf /var/lib/apt/lists/* - -COPY scripts/sccache.sh /scripts/ -RUN sh /scripts/sccache.sh - -# We are disabling CI LLVM since this builder is intentionally using a host -# LLVM, rather than the typical src/llvm-project LLVM. -ENV NO_DOWNLOAD_CI_LLVM 1 - -# This is not the latest LLVM version, so some components required by tests may -# be missing. -ENV IS_NOT_LATEST_LLVM 1 - -# Using llvm-link-shared due to libffi issues -- see #34486 -ENV RUST_CONFIGURE_ARGS \ - --build=x86_64-unknown-linux-gnu \ - --llvm-root=/usr/lib/llvm-14 \ - --enable-llvm-link-shared \ - --set rust.thin-lto-import-instr-limit=10 - -ENV SCRIPT ../x.py --stage 1 test --exclude src/tools/tidy && \ - # Run the `mir-opt` tests again but this time for a 32-bit target. - # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have - # both 32-bit and 64-bit outputs updated by the PR author, before - # the PR is approved and tested for merging. - # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, - # despite having different output on 32-bit vs 64-bit targets. - ../x.py --stage 1 test tests/mir-opt \ - --host='' --target=i686-unknown-linux-gnu diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile deleted file mode 100644 index 1f28b93977800..0000000000000 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-14/Dockerfile +++ /dev/null @@ -1,68 +0,0 @@ -FROM ubuntu:22.04 - -ARG DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y --no-install-recommends \ - g++ \ - gcc-multilib \ - make \ - ninja-build \ - file \ - curl \ - ca-certificates \ - python3.11 \ - git \ - cmake \ - sudo \ - gdb \ - llvm-14-tools \ - llvm-14-dev \ - libedit-dev \ - libssl-dev \ - pkg-config \ - zlib1g-dev \ - xz-utils \ - nodejs \ - mingw-w64 \ - && rm -rf /var/lib/apt/lists/* - -# Install powershell (universal package) so we can test x.ps1 on Linux -RUN curl -sL "https://github.com/PowerShell/PowerShell/releases/download/v7.3.1/powershell_7.3.1-1.deb_amd64.deb" > powershell.deb && \ - dpkg -i powershell.deb && \ - rm -f powershell.deb - -COPY scripts/sccache.sh /scripts/ -RUN sh /scripts/sccache.sh - -# We are disabling CI LLVM since this builder is intentionally using a host -# LLVM, rather than the typical src/llvm-project LLVM. -ENV NO_DOWNLOAD_CI_LLVM 1 - -# This is not the latest LLVM version, so some components required by tests may -# be missing. -ENV IS_NOT_LATEST_LLVM 1 - -# Using llvm-link-shared due to libffi issues -- see #34486 -ENV RUST_CONFIGURE_ARGS \ - --build=x86_64-unknown-linux-gnu \ - --llvm-root=/usr/lib/llvm-14 \ - --enable-llvm-link-shared \ - --set rust.thin-lto-import-instr-limit=10 - -# NOTE: intentionally uses all of `x.py`, `x`, and `x.ps1` to make sure they all work on Linux. -ENV SCRIPT ../x.py --stage 2 test --exclude src/tools/tidy && \ - # Run the `mir-opt` tests again but this time for a 32-bit target. - # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have - # both 32-bit and 64-bit outputs updated by the PR author, before - # the PR is approved and tested for merging. - # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, - # despite having different output on 32-bit vs 64-bit targets. - ../x --stage 2 test tests/mir-opt \ - --host='' --target=i686-unknown-linux-gnu && \ - # Run the UI test suite again, but in `--pass=check` mode - # - # This is intended to make sure that both `--pass=check` continues to - # work. - # - ../x.ps1 --stage 2 test tests/ui --pass=check \ - --host='' --target=i686-unknown-linux-gnu diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/Dockerfile index 960683b92bdd5..444e0275d48f4 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.10 +FROM ubuntu:22.04 ARG DEBIAN_FRONTEND=noninteractive @@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ file \ curl \ ca-certificates \ - python3 \ + python3.11 \ git \ cmake \ sudo \ @@ -49,20 +49,6 @@ ENV RUST_CONFIGURE_ARGS \ --enable-llvm-link-shared \ --set rust.thin-lto-import-instr-limit=10 -# NOTE: intentionally uses all of `x.py`, `x`, and `x.ps1` to make sure they all work on Linux. -ENV SCRIPT ../x.py --stage 2 test --exclude src/tools/tidy && \ - # Run the `mir-opt` tests again but this time for a 32-bit target. - # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have - # both 32-bit and 64-bit outputs updated by the PR author, before - # the PR is approved and tested for merging. - # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, - # despite having different output on 32-bit vs 64-bit targets. - ../x --stage 2 test tests/mir-opt \ - --host='' --target=i686-unknown-linux-gnu && \ - # Run the UI test suite again, but in `--pass=check` mode - # - # This is intended to make sure that both `--pass=check` continues to - # work. - # - ../x.ps1 --stage 2 test tests/ui --pass=check \ - --host='' --target=i686-unknown-linux-gnu +COPY host-x86_64/x86_64-gnu-llvm-15/script.sh /tmp/ + +ENV SCRIPT /tmp/script.sh diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh new file mode 100755 index 0000000000000..390304b6ad5a1 --- /dev/null +++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-15/script.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -ex + +# Only run the stage 1 tests on merges, not on PR CI jobs. +if [[ -z "${PR_CI_JOB}" ]]; then +../x.py --stage 1 test --skip src/tools/tidy && \ + # Run the `mir-opt` tests again but this time for a 32-bit target. + # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have + # both 32-bit and 64-bit outputs updated by the PR author, before + # the PR is approved and tested for merging. + # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, + # despite having different output on 32-bit vs 64-bit targets. + ../x.py --stage 1 test tests/mir-opt \ + --host='' --target=i686-unknown-linux-gnu +fi + +# NOTE: intentionally uses all of `x.py`, `x`, and `x.ps1` to make sure they all work on Linux. +../x.py --stage 2 test --skip src/tools/tidy && \ + # Run the `mir-opt` tests again but this time for a 32-bit target. + # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have + # both 32-bit and 64-bit outputs updated by the PR author, before + # the PR is approved and tested for merging. + # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, + # despite having different output on 32-bit vs 64-bit targets. + ../x --stage 2 test tests/mir-opt \ + --host='' --target=i686-unknown-linux-gnu && \ + # Run the UI test suite again, but in `--pass=check` mode + # + # This is intended to make sure that both `--pass=check` continues to + # work. + # + ../x.ps1 --stage 2 test tests/ui --pass=check \ + --host='' --target=i686-unknown-linux-gnu diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-16/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-16/Dockerfile index 7c75d0df59083..1e2b802e64e39 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-llvm-16/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-llvm-16/Dockerfile @@ -45,20 +45,6 @@ ENV RUST_CONFIGURE_ARGS \ --enable-llvm-link-shared \ --set rust.thin-lto-import-instr-limit=10 -# NOTE: intentionally uses all of `x.py`, `x`, and `x.ps1` to make sure they all work on Linux. -ENV SCRIPT ../x.py --stage 2 test --exclude src/tools/tidy && \ - # Run the `mir-opt` tests again but this time for a 32-bit target. - # This enforces that tests using `// EMIT_MIR_FOR_EACH_BIT_WIDTH` have - # both 32-bit and 64-bit outputs updated by the PR author, before - # the PR is approved and tested for merging. - # It will also detect tests lacking `// EMIT_MIR_FOR_EACH_BIT_WIDTH`, - # despite having different output on 32-bit vs 64-bit targets. - ../x --stage 2 test tests/mir-opt \ - --host='' --target=i686-unknown-linux-gnu && \ - # Run the UI test suite again, but in `--pass=check` mode - # - # This is intended to make sure that both `--pass=check` continues to - # work. - # - ../x.ps1 --stage 2 test tests/ui --pass=check \ - --host='' --target=i686-unknown-linux-gnu +COPY host-x86_64/x86_64-gnu-llvm-15/script.sh /tmp/ + +ENV SCRIPT /tmp/script.sh diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-nopt/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu-nopt/Dockerfile index 9fdc78406fbd1..d8113e0672375 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-nopt/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu-nopt/Dockerfile @@ -1,7 +1,8 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 # Avoid interactive prompts while installing `tzdata` dependency with `DEBIAN_FRONTEND`. -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ make \ ninja-build \ diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version index 806935b827ff5..b31629ad6050b 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version +++ b/src/ci/docker/host-x86_64/x86_64-gnu-tools/browser-ui-test.version @@ -1 +1 @@ -0.16.4 \ No newline at end of file +0.16.8 \ No newline at end of file diff --git a/src/ci/docker/host-x86_64/x86_64-gnu/Dockerfile b/src/ci/docker/host-x86_64/x86_64-gnu/Dockerfile index fbec368c9ee55..9025e9bb0a3af 100644 --- a/src/ci/docker/host-x86_64/x86_64-gnu/Dockerfile +++ b/src/ci/docker/host-x86_64/x86_64-gnu/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/src/ci/docker/run.sh b/src/ci/docker/run.sh index 8bea8cd4c878c..8bd8beb873b63 100755 --- a/src/ci/docker/run.sh +++ b/src/ci/docker/run.sh @@ -63,6 +63,11 @@ if [ -f "$docker_dir/$image/Dockerfile" ]; then uname -m >> $hash_key docker --version >> $hash_key + + # Include cache version. Currently it is needed to bust Docker + # cache key after opting in into the old Docker build backend. + echo "1" >> $hash_key + cksum=$(sha512sum $hash_key | \ awk '{print $1}') @@ -79,7 +84,7 @@ if [ -f "$docker_dir/$image/Dockerfile" ]; then loaded_images=$(/usr/bin/timeout -k 720 600 docker load -i /tmp/rustci_docker_cache \ | sed 's/.* sha/sha/') set -e - echo "Downloaded containers:\n$loaded_images" + printf "Downloaded containers:\n$loaded_images\n" fi dockerfile="$docker_dir/$image/Dockerfile" @@ -89,12 +94,20 @@ if [ -f "$docker_dir/$image/Dockerfile" ]; then else context="$script_dir" fi + echo "::group::Building docker image for $image" + + # As of August 2023, Github Actions have updated Docker to 23.X, + # which uses the BuildKit by default. It currently throws aways all + # intermediate layers, which breaks our usage of S3 layer caching. + # Therefore we opt-in to the old build backend for now. + export DOCKER_BUILDKIT=0 retry docker \ build \ --rm \ -t rust-ci \ -f "$dockerfile" \ "$context" + echo "::endgroup::" if [ "$CI" != "" ]; then s3url="s3://$SCCACHE_BUCKET/docker/$cksum" @@ -254,8 +267,6 @@ docker \ --env DEPLOY \ --env DEPLOY_ALT \ --env CI \ - --env TF_BUILD \ - --env BUILD_SOURCEBRANCHNAME \ --env GITHUB_ACTIONS \ --env GITHUB_REF \ --env TOOLSTATE_REPO_ACCESS_TOKEN \ @@ -264,6 +275,9 @@ docker \ --env RUST_CI_OVERRIDE_RELEASE_CHANNEL \ --env CI_JOB_NAME="${CI_JOB_NAME-$IMAGE}" \ --env BASE_COMMIT="$BASE_COMMIT" \ + --env DIST_TRY_BUILD \ + --env PR_CI_JOB \ + --env OBJDIR_ON_HOST="$objdir" \ --init \ --rm \ rust-ci \ diff --git a/src/ci/docker/scripts/android-sdk-manager.py b/src/ci/docker/scripts/android-sdk-manager.py index c9e2961f6eb15..66cba58427bad 100755 --- a/src/ci/docker/scripts/android-sdk-manager.py +++ b/src/ci/docker/scripts/android-sdk-manager.py @@ -2,6 +2,14 @@ # Simpler reimplementation of Android's sdkmanager # Extra features of this implementation are pinning and mirroring +import argparse +import hashlib +import os +import subprocess +import tempfile +import urllib.request +import xml.etree.ElementTree as ET + # These URLs are the Google repositories containing the list of available # packages and their versions. The list has been generated by listing the URLs # fetched while executing `tools/bin/sdkmanager --list` @@ -27,15 +35,6 @@ MIRROR_BUCKET_REGION = "us-west-1" MIRROR_BASE_DIR = "rustc/android/" -import argparse -import hashlib -import os -import subprocess -import sys -import tempfile -import urllib.request -import xml.etree.ElementTree as ET - class Package: def __init__(self, path, url, sha1, deps=None): if deps is None: diff --git a/src/ci/docker/scripts/cmake.sh b/src/ci/docker/scripts/cmake.sh index f124dbdaa6d3b..822666c89530d 100755 --- a/src/ci/docker/scripts/cmake.sh +++ b/src/ci/docker/scripts/cmake.sh @@ -18,9 +18,9 @@ exit 1 set -x } -# LLVM 12 requires CMake 3.13.4 or higher. -# This script is not necessary for images using Ubuntu 20.04 or newer. -CMAKE=3.13.4 +# LLVM 17 requires CMake 3.20 or higher. +# This script is not necessary for images using Ubuntu 22.04 or newer. +CMAKE=3.20.3 curl -L https://github.com/Kitware/CMake/releases/download/v$CMAKE/cmake-$CMAKE.tar.gz | tar xzf - mkdir cmake-build diff --git a/src/ci/docker/scripts/crosstool-ng-git.sh b/src/ci/docker/scripts/crosstool-ng-git.sh index 449cc476f9ab5..b8d3991532717 100644 --- a/src/ci/docker/scripts/crosstool-ng-git.sh +++ b/src/ci/docker/scripts/crosstool-ng-git.sh @@ -2,7 +2,7 @@ set -ex URL=https://github.com/crosstool-ng/crosstool-ng -REV=943364711a650d9b9e84c1b42c91cc0265b6ab5c +REV=227d99d7f3115f3a078595a580d2b307dcd23e93 mkdir crosstool-ng cd crosstool-ng diff --git a/src/ci/docker/scripts/fuchsia-test-runner.py b/src/ci/docker/scripts/fuchsia-test-runner.py index ecef56f56f1d5..f78d446c8b7ca 100755 --- a/src/ci/docker/scripts/fuchsia-test-runner.py +++ b/src/ci/docker/scripts/fuchsia-test-runner.py @@ -15,23 +15,17 @@ import json import os import platform -import re import shutil -import signal import subprocess import sys -from typing import ClassVar, List, Optional +from typing import ClassVar, List @dataclass class TestEnvironment: - rust_dir: str + rust_build_dir: str sdk_dir: str target: str - package_server_pid: Optional[int] = None - emu_addr: Optional[str] = None - libstd_name: Optional[str] = None - libtest_name: Optional[str] = None verbose: bool = False @staticmethod @@ -57,7 +51,7 @@ def env_file_path(cls): @classmethod def from_args(cls, args): return cls( - os.path.abspath(args.rust), + os.path.abspath(args.rust_build), os.path.abspath(args.sdk), args.target, verbose=args.verbose, @@ -68,13 +62,9 @@ def read_from_file(cls): with open(cls.env_file_path(), encoding="utf-8") as f: test_env = json.loads(f.read()) return cls( - test_env["rust_dir"], + test_env["rust_build_dir"], test_env["sdk_dir"], test_env["target"], - libstd_name=test_env["libstd_name"], - libtest_name=test_env["libtest_name"], - emu_addr=test_env["emu_addr"], - package_server_pid=test_env["package_server_pid"], verbose=test_env["verbose"], ) @@ -82,18 +72,6 @@ def write_to_file(self): with open(self.env_file_path(), "w", encoding="utf-8") as f: f.write(json.dumps(self.__dict__)) - def ssh_dir(self): - return os.path.join(self.tmp_dir(), "ssh") - - def ssh_keyfile_path(self): - return os.path.join(self.ssh_dir(), "fuchsia_ed25519") - - def ssh_authfile_path(self): - return os.path.join(self.ssh_dir(), "fuchsia_authorized_keys") - - def vdl_output_path(self): - return os.path.join(self.tmp_dir(), "vdl_output") - def package_server_log_path(self): return os.path.join(self.tmp_dir(), "package_server_log") @@ -113,7 +91,9 @@ def repo_dir(self): def libs_dir(self): return os.path.join( - self.rust_dir, + self.rust_build_dir, + "host", + "stage2", "lib", ) @@ -171,7 +151,6 @@ def ffx_isolate_dir(self): def home_dir(self): return os.path.join(self.tmp_dir(), "user-home") - def start_ffx_isolation(self): # Most of this is translated directly from ffx's isolate library os.mkdir(self.ffx_isolate_dir()) @@ -213,21 +192,19 @@ def start_ffx_isolation(self): # Set configs configs = { "log.enabled": "true", - "ssh.pub": self.ssh_authfile_path(), - "ssh.priv": self.ssh_keyfile_path(), "test.is_isolated": "true", "test.experimental_structured_output": "true", } for key, value in configs.items(): subprocess.check_call( [ - self.tool_path("ffx"), + ffx_path, "config", "set", key, value, ], - env=self.ffx_cmd_env(), + env=ffx_env, stdout=self.subprocess_output(), stderr=self.subprocess_output(), ) @@ -249,6 +226,7 @@ def stop_ffx_isolation(self): self.tool_path("ffx"), "daemon", "stop", + "-w", ], env=self.ffx_cmd_env(), stdout=self.subprocess_output(), @@ -276,87 +254,62 @@ def start(self): elif len(os.listdir(self.tmp_dir())) != 0: raise Exception(f"Temp directory is not clean (in {self.tmp_dir()})") - os.mkdir(self.ssh_dir()) os.mkdir(self.output_dir()) - # Find libstd and libtest - libstd_paths = glob.glob(os.path.join(self.rustlibs_dir(), "libstd-*.so")) - libtest_paths = glob.glob(os.path.join(self.rustlibs_dir(), "libtest-*.so")) - - if not libstd_paths: - raise Exception(f"Failed to locate libstd (in {self.rustlibs_dir()})") - - if not libtest_paths: - raise Exception(f"Failed to locate libtest (in {self.rustlibs_dir()})") + ffx_path = self.tool_path("ffx") + ffx_env = self.ffx_cmd_env() - self.libstd_name = os.path.basename(libstd_paths[0]) - self.libtest_name = os.path.basename(libtest_paths[0]) + # Start ffx isolation + self.log_info("Starting ffx isolation...") + self.start_ffx_isolation() - # Generate SSH keys for the emulator to use - self.log_info("Generating SSH keys...") + # Stop any running emulators (there shouldn't be any) subprocess.check_call( [ - "ssh-keygen", - "-N", - "", - "-t", - "ed25519", - "-f", - self.ssh_keyfile_path(), - "-C", - "Generated by fuchsia-test-runner.py", + ffx_path, + "emu", + "stop", + "--all", ], + env=ffx_env, stdout=self.subprocess_output(), stderr=self.subprocess_output(), ) - authfile_contents = subprocess.check_output( + + # Start emulator + self.log_info("Starting emulator...") + product_bundle = "terminal.qemu-" + self.triple_to_arch(self.target) + subprocess.check_call( [ - "ssh-keygen", - "-y", - "-f", - self.ssh_keyfile_path(), + ffx_path, + "product-bundle", + "get", + product_bundle, ], + env=ffx_env, + stdout=self.subprocess_output(), stderr=self.subprocess_output(), ) - with open(self.ssh_authfile_path(), "wb") as authfile: - authfile.write(authfile_contents) - - # Start ffx isolation - self.log_info("Starting ffx isolation...") - self.start_ffx_isolation() - - # Start emulator (this will generate the vdl output) - self.log_info("Starting emulator...") + # FIXME: condition --accel hyper on target arch matching host arch subprocess.check_call( [ - self.tool_path("fvdl"), - "--sdk", + ffx_path, + "emu", "start", - "--tuntap", + product_bundle, "--headless", - "--nointeractive", - "--ssh", - self.ssh_dir(), - "--vdl-output", - self.vdl_output_path(), - "--emulator-log", + "--log", self.emulator_log_path(), - "--image-name", - "qemu-" + self.triple_to_arch(self.target), + "--net", + "tap", + "--accel", + "hyper", ], + env=ffx_env, stdout=self.subprocess_output(), stderr=self.subprocess_output(), ) - # Parse vdl output for relevant information - with open(self.vdl_output_path(), encoding="utf-8") as f: - vdl_content = f.read() - matches = re.search( - r'network_address:\s+"\[([0-9a-f]{1,4}:(:[0-9a-f]{1,4}){4}%qemu)\]"', - vdl_content, - ) - self.emu_addr = matches.group(1) - # Create new package repo self.log_info("Creating package repo...") subprocess.check_call( @@ -370,61 +323,46 @@ def start(self): stderr=self.subprocess_output(), ) - # Start package server - self.log_info("Starting package server...") - with open( - self.package_server_log_path(), "w", encoding="utf-8" - ) as package_server_log: - # We want this to be a long-running process that persists after the script finishes - # pylint: disable=consider-using-with - self.package_server_pid = subprocess.Popen( - [ - self.tool_path("pm"), - "serve", - "-vt", - "-repo", - self.repo_dir(), - "-l", - ":8084", - ], - stdout=package_server_log, - stderr=package_server_log, - ).pid - - # Register package server with emulator - self.log_info("Registering package server...") - ssh_client = subprocess.check_output( + # Add repo + subprocess.check_call( [ - "ssh", - "-i", - self.ssh_keyfile_path(), - "-o", - "StrictHostKeyChecking=accept-new", - self.emu_addr, - "-f", - "echo $SSH_CLIENT", + ffx_path, + "repository", + "add-from-pm", + self.repo_dir(), + "--repository", + self.TEST_REPO_NAME, ], - text=True, + env=ffx_env, + stdout=self.subprocess_output(), + stderr=self.subprocess_output(), ) - repo_addr = ssh_client.split()[0].replace("%", "%25") - repo_url = f"http://[{repo_addr}]:8084/config.json" + + # Start repository server + subprocess.check_call( + [ffx_path, "repository", "server", "start", "--address", "[::]:0"], + env=ffx_env, + stdout=self.subprocess_output(), + stderr=self.subprocess_output(), + ) + + # Register with newly-started emulator subprocess.check_call( [ - "ssh", - "-i", - self.ssh_keyfile_path(), - "-o", - "StrictHostKeyChecking=accept-new", - self.emu_addr, - "-f", - f"pkgctl repo add url -f 1 -n {self.TEST_REPO_NAME} {repo_url}", + ffx_path, + "target", + "repository", + "register", + "--repository", + self.TEST_REPO_NAME, ], + env=ffx_env, stdout=self.subprocess_output(), stderr=self.subprocess_output(), ) # Create lockfiles - open(self.pm_lockfile_path(), 'a').close() + open(self.pm_lockfile_path(), "a").close() # Write to file self.write_to_file() @@ -458,6 +396,7 @@ def start(self): ], use: [ {{ storage: "data", path: "/data" }}, + {{ storage: "tmp", path: "/tmp" }}, {{ protocol: [ "fuchsia.process.Launcher" ] }}, {{ protocol: [ "fuchsia.posix.socket.Provider" ] }} ], @@ -471,8 +410,8 @@ def start(self): meta/package={package_dir}/meta/package meta/{package_name}.cm={package_dir}/meta/{package_name}.cm bin/{exe_name}={bin_path} - lib/{libstd_name}={rust_dir}/lib/rustlib/{rustlib_dir}/lib/{libstd_name} - lib/{libtest_name}={rust_dir}/lib/rustlib/{rustlib_dir}/lib/{libtest_name} + lib/{libstd_name}={libstd_path} + lib/{libtest_name}={libtest_path} lib/ld.so.1={sdk_dir}/arch/{target_arch}/sysroot/dist/lib/ld.so.1 lib/libfdio.so={sdk_dir}/arch/{target_arch}/dist/libfdio.so """ @@ -502,6 +441,16 @@ def run(self, args): bin_path = os.path.abspath(args.bin_path) + # Find libstd and libtest + libstd_paths = glob.glob(os.path.join(self.rustlibs_dir(), "libstd-*.so")) + libtest_paths = glob.glob(os.path.join(self.rustlibs_dir(), "libtest-*.so")) + + if not libstd_paths: + raise Exception(f"Failed to locate libstd (in {self.rustlibs_dir()})") + + if not libtest_paths: + raise Exception(f"Failed to locate libtest (in {self.rustlibs_dir()})") + # Build a unique, deterministic name for the test using the name of the # binary and the last 6 hex digits of the hash of the full path def path_checksum(path): @@ -568,8 +517,11 @@ def log(msg): env_vars += f'\n "{var_name}={var_value}",' # Default to no backtrace for test suite - if os.getenv("RUST_BACKTRACE") == None: - env_vars += f'\n "RUST_BACKTRACE=0",' + if os.getenv("RUST_BACKTRACE") is None: + env_vars += '\n "RUST_BACKTRACE=0",' + + # Use /tmp as the test temporary directory + env_vars += '\n "RUST_TEST_TMPDIR=/tmp",' cml.write( self.CML_TEMPLATE.format(env_vars=env_vars, exe_name=exe_name) @@ -601,11 +553,12 @@ def log(msg): exe_name=exe_name, package_dir=package_dir, package_name=package_name, - rust_dir=self.rust_dir, - rustlib_dir=self.target, + target=self.target, sdk_dir=self.sdk_dir, - libstd_name=self.libstd_name, - libtest_name=self.libtest_name, + libstd_name=os.path.basename(libstd_paths[0]), + libtest_name=os.path.basename(libtest_paths[0]), + libstd_path=libstd_paths[0], + libtest_path=libtest_paths[0], target_arch=self.triple_to_arch(self.target), ) ) @@ -642,7 +595,7 @@ def log(msg): log("Publishing package to repo...") # Publish package to repo - with open(self.pm_lockfile_path(), 'w') as pm_lockfile: + with open(self.pm_lockfile_path(), "w") as pm_lockfile: fcntl.lockf(pm_lockfile.fileno(), fcntl.LOCK_EX) subprocess.check_call( [ @@ -776,20 +729,15 @@ def stop(self): else: self.log_debug("No ffx daemon log found") - # Stop package server - self.log_info("Stopping package server...") - os.kill(self.package_server_pid, signal.SIGTERM) - # Shut down the emulator self.log_info("Stopping emulator...") subprocess.check_call( [ - self.tool_path("fvdl"), - "--sdk", - "kill", - "--launched-proto", - self.vdl_output_path(), + self.tool_path("ffx"), + "emu", + "stop", ], + env=self.ffx_cmd_env(), stdout=self.subprocess_output(), stderr=self.subprocess_output(), ) @@ -966,8 +914,8 @@ def print_help(args): "start", help="initializes the testing environment" ) start_parser.add_argument( - "--rust", - help="the directory of the installed Rust compiler for Fuchsia", + "--rust-build", + help="the current compiler build directory (`$RUST_SRC/build` by default)", required=True, ) start_parser.add_argument( @@ -1045,9 +993,7 @@ def print_help(args): ) debug_parser.set_defaults(func=debug) - syslog_parser = subparsers.add_parser( - "syslog", help="prints the device syslog" - ) + syslog_parser = subparsers.add_parser("syslog", help="prints the device syslog") syslog_parser.set_defaults(func=syslog) args = parser.parse_args() diff --git a/src/ci/github-actions/ci.yml b/src/ci/github-actions/ci.yml index fd619467fc153..2cc0bfd9db96a 100644 --- a/src/ci/github-actions/ci.yml +++ b/src/ci/github-actions/ci.yml @@ -14,7 +14,6 @@ # step CI will fail. --- - ############################### # YAML Anchors Definition # ############################### @@ -30,10 +29,11 @@ # The expand-yaml-anchors tool will automatically remove this block from the # output YAML file. x--expand-yaml-anchors--remove: - - &shared-ci-variables CI_JOB_NAME: ${{ matrix.name }} CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + # commit of PR sha or commit sha. `GITHUB_SHA` is not accurate for PRs. + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - &public-variables SCCACHE_BUCKET: rust-lang-ci-sccache2 @@ -88,7 +88,7 @@ x--expand-yaml-anchors--remove: <<: *base-job - &job-macos-xl - os: macos-latest # We use the standard runner for now + os: macos-13 # We use the standard runner for now <<: *base-job - &job-windows-8c @@ -145,13 +145,6 @@ x--expand-yaml-anchors--remove: run: src/ci/scripts/verify-channel.sh <<: *step - - name: configure GitHub Actions to kill the build when outdated - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master - with: - github_token: "${{ secrets.github_token }}" - if: success() && !env.SKIP_JOB && github.ref != 'refs/heads/try' && github.ref != 'refs/heads/try-perf' - <<: *step - - name: collect CPU statistics run: src/ci/scripts/collect-cpu-stats.sh <<: *step @@ -229,6 +222,20 @@ x--expand-yaml-anchors--remove: TOOLSTATE_REPO_ACCESS_TOKEN: ${{ secrets.TOOLSTATE_REPO_ACCESS_TOKEN }} <<: *step + - name: create github artifacts + run: src/ci/scripts/create-doc-artifacts.sh + <<: *step + + - name: upload artifacts to github + uses: actions/upload-artifact@v3 + with: + # name is set in previous step + name: ${{ env.DOC_ARTIFACT_NAME }} + path: obj/artifacts/doc + if-no-files-found: ignore + retention-days: 5 + <<: *step + - name: upload artifacts to S3 run: src/ci/scripts/upload-artifacts.sh env: @@ -289,34 +296,38 @@ defaults: # shell is PowerShell.) shell: bash +concurrency: + # For a given workflow, if we push to the same branch, cancel all previous builds on that branch. + # We add an exception for try builds (try branch) and unrolled rollup builds (try-perf), which + # are all triggered on the same branch, but which should be able to run concurrently. + group: ${{ github.workflow }}-${{ ((github.ref == 'refs/heads/try' || github.ref == 'refs/heads/try-perf') && github.sha) || github.ref }} + cancel-in-progress: true + jobs: pr: - permissions: - actions: write # for rust-lang/simpleinfra/github-actions/cancel-outdated-builds <<: *base-ci-job name: PR - ${{ matrix.name }} env: <<: [*shared-ci-variables, *public-variables] + PR_CI_JOB: 1 if: github.event_name == 'pull_request' continue-on-error: ${{ matrix.name == 'mingw-check-tidy' }} strategy: matrix: include: - name: mingw-check - <<: *job-linux-16c + <<: *job-linux-4c - name: mingw-check-tidy - <<: *job-linux-16c + <<: *job-linux-4c - - name: x86_64-gnu-llvm-14 + - name: x86_64-gnu-llvm-15 <<: *job-linux-16c - name: x86_64-gnu-tools <<: *job-linux-16c auto: - permissions: - actions: write # for rust-lang/simpleinfra/github-actions/cancel-outdated-builds <<: *base-ci-job name: auto - ${{ matrix.name }} env: @@ -362,18 +373,6 @@ jobs: - name: dist-loongarch64-linux <<: *job-linux-8c - - name: dist-mips-linux - <<: *job-linux-8c - - - name: dist-mips64-linux - <<: *job-linux-8c - - - name: dist-mips64el-linux - <<: *job-linux-8c - - - name: dist-mipsel-linux - <<: *job-linux-8c - - name: dist-powerpc-linux <<: *job-linux-8c @@ -468,16 +467,6 @@ jobs: RUST_BACKTRACE: 1 <<: *job-linux-8c - - name: x86_64-gnu-llvm-14 - env: - RUST_BACKTRACE: 1 - <<: *job-linux-8c - - - name: x86_64-gnu-llvm-14-stage1 - env: - RUST_BACKTRACE: 1 - <<: *job-linux-8c - - name: x86_64-gnu-nopt <<: *job-linux-4c @@ -529,7 +518,7 @@ jobs: - name: x86_64-apple-1 env: &env-x86_64-apple-tests - SCRIPT: ./x.py --stage 2 test --exclude tests/ui --exclude tests/rustdoc --exclude tests/run-make-fulldeps + SCRIPT: ./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc --skip tests/run-make-fulldeps RUST_CONFIGURE_ARGS: --build=x86_64-apple-darwin --enable-sanitizers --enable-profiler --set rust.jemalloc --set llvm.ninja=false RUSTC_RETRY_LINKER_ON_SEGFAULT: 1 MACOSX_DEPLOYMENT_TARGET: 10.8 @@ -582,40 +571,22 @@ jobs: # Windows Builders # ###################### - - name: x86_64-msvc-1 - env: - RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-profiler - SCRIPT: make ci-subset-1 - <<: *job-windows-8c - - - name: x86_64-msvc-2 + - name: x86_64-msvc env: RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-profiler - SCRIPT: make ci-subset-2 + SCRIPT: make ci-msvc <<: *job-windows-8c - - name: i686-msvc-1 + - name: i686-msvc env: RUST_CONFIGURE_ARGS: --build=i686-pc-windows-msvc - SCRIPT: make ci-subset-1 + SCRIPT: make ci-msvc <<: *job-windows-8c - - name: i686-msvc-2 + - name: x86_64-msvc-ext env: - RUST_CONFIGURE_ARGS: --build=i686-pc-windows-msvc - SCRIPT: make ci-subset-2 - <<: *job-windows-8c - - - name: x86_64-msvc-cargo - env: - SCRIPT: python x.py --stage 2 test src/tools/cargotest src/tools/cargo - RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-lld - <<: *job-windows-8c - - - name: x86_64-msvc-tools - env: - SCRIPT: src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh x.py /tmp/toolstate/toolstates.json windows - RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --save-toolstates=/tmp/toolstate/toolstates.json + SCRIPT: python x.py --stage 2 test src/tools/cargotest src/tools/cargo && src/ci/docker/host-x86_64/x86_64-gnu-tools/checktools.sh x.py /tmp/toolstate/toolstates.json windows + RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-lld --save-toolstates=/tmp/toolstate/toolstates.json DEPLOY_TOOLSTATES_JSON: toolstates-windows.json <<: *job-windows-8c @@ -635,41 +606,19 @@ jobs: # came from the mingw-w64 SourceForge download site. Unfortunately # SourceForge is notoriously flaky, so we mirror it on our own infrastructure. - - name: i686-mingw-1 - env: - RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu - SCRIPT: make ci-mingw-subset-1 - # We are intentionally allowing an old toolchain on this builder (and that's - # incompatible with LLVM downloads today). - NO_DOWNLOAD_CI_LLVM: 1 - CUSTOM_MINGW: 1 - <<: *job-windows-8c - - - name: i686-mingw-2 + - name: i686-mingw env: RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu - SCRIPT: make ci-mingw-subset-2 - # We are intentionally allowing an old toolchain on this builder (and that's - # incompatible with LLVM downloads today). - NO_DOWNLOAD_CI_LLVM: 1 - CUSTOM_MINGW: 1 - <<: *job-windows-8c - - - name: x86_64-mingw-1 - env: - SCRIPT: make ci-mingw-subset-1 - RUST_CONFIGURE_ARGS: >- - --build=x86_64-pc-windows-gnu - --enable-profiler + SCRIPT: make ci-mingw # We are intentionally allowing an old toolchain on this builder (and that's # incompatible with LLVM downloads today). NO_DOWNLOAD_CI_LLVM: 1 CUSTOM_MINGW: 1 <<: *job-windows-8c - - name: x86_64-mingw-2 + - name: x86_64-mingw env: - SCRIPT: make ci-mingw-subset-2 + SCRIPT: make ci-mingw RUST_CONFIGURE_ARGS: >- --build=x86_64-pc-windows-gnu --enable-profiler @@ -687,7 +636,7 @@ jobs: --target=x86_64-pc-windows-msvc --enable-full-tools --enable-profiler - SCRIPT: PGO_HOST=x86_64-pc-windows-msvc python src/ci/stage-build.py python x.py dist bootstrap --include-default-paths + SCRIPT: python x.py build --set rust.debug=true opt-dist && PGO_HOST=x86_64-pc-windows-msvc ./build/x86_64-pc-windows-msvc/stage0-tools-bin/opt-dist python x.py dist bootstrap --include-default-paths DIST_REQUIRE_ALL_TOOLS: 1 <<: *job-windows-8c @@ -712,9 +661,6 @@ jobs: --enable-profiler SCRIPT: python x.py dist bootstrap --include-default-paths DIST_REQUIRE_ALL_TOOLS: 1 - # Hack around this SDK version, because it doesn't work with clang. - # See https://github.com/rust-lang/rust/issues/88796 - WINDOWS_SDK_20348_HACK: 1 <<: *job-windows-8c - name: dist-i686-mingw @@ -752,11 +698,10 @@ jobs: <<: *job-windows-8c try: - permissions: - actions: write # for rust-lang/simpleinfra/github-actions/cancel-outdated-builds <<: *base-ci-job name: try - ${{ matrix.name }} env: + DIST_TRY_BUILD: 1 <<: [*shared-ci-variables, *prod-variables] if: github.event_name == 'push' && (github.ref == 'refs/heads/try' || github.ref == 'refs/heads/try-perf') && github.repository == 'rust-lang-ci/rust' strategy: diff --git a/src/ci/github-actions/problem_matchers.json b/src/ci/github-actions/problem_matchers.json index 37561924b7d4f..b6c7ace841e59 100644 --- a/src/ci/github-actions/problem_matchers.json +++ b/src/ci/github-actions/problem_matchers.json @@ -10,6 +10,46 @@ "message": 3 } ] + }, + { + "owner": "cargo-common", + "pattern": [ + { + "regexp": "^(warning|warn|error)(\\[(\\S*)\\])?: (.*)$", + "severity": 1, + "message": 4, + "code": 3 + }, + { + "regexp": "^\\s+-->\\s(\\S+):(\\d+):(\\d+)$", + "file": 1, + "line": 2, + "column": 3 + } + ] + }, + { + "owner": "compiler-panic", + "pattern": [ + { + "regexp": "error: internal compiler error: (.*):(\\d+):(\\d+): (.*)$", + "message": 4, + "file": 1, + "line": 2, + "column": 3 + } + ] + }, + { + "owner": "cargo-fmt", + "pattern": [ + { + "regexp": "^(Diff in (\\S+)) at line (\\d+):", + "message": 1, + "file": 2, + "line": 3 + } + ] } ] } diff --git a/src/ci/run.sh b/src/ci/run.sh index 966af3abc6f1c..b8cb758bf40c9 100755 --- a/src/ci/run.sh +++ b/src/ci/run.sh @@ -8,7 +8,7 @@ fi if [ "$NO_CHANGE_USER" = "" ]; then if [ "$LOCAL_USER_ID" != "" ]; then - useradd --shell /bin/bash -u $LOCAL_USER_ID -o -c "" -m user + id -u user &>/dev/null || useradd --shell /bin/bash -u $LOCAL_USER_ID -o -c "" -m user export HOME=/home/user unset LOCAL_USER_ID @@ -53,6 +53,7 @@ if ! isCI || isCiBranch auto || isCiBranch beta || isCiBranch try || isCiBranch HAS_METRICS=1 fi +RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-verbose-configure" RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-sccache" RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --disable-manage-submodules" RUST_CONFIGURE_ARGS="$RUST_CONFIGURE_ARGS --enable-locked-deps" @@ -153,13 +154,25 @@ fi # check for clock drifts. An HTTP URL is used instead of HTTPS since on Azure # Pipelines it happened that the certificates were marked as expired. datecheck() { - echo "== clock drift check ==" + # If an error has happened, we do not want to start a new group, because that will collapse + # a previous group that might have contained the error log. + exit_code=$? + + if [ $exit_code -eq 0 ] + then + echo "::group::Clock drift check" + fi + echo -n " local time: " date echo -n " network time: " curl -fs --head http://ci-caches.rust-lang.org | grep ^Date: \ | sed 's/Date: //g' || true - echo "== end clock drift check ==" + + if [ $exit_code -eq 0 ] + then + echo "::endgroup::" + fi } datecheck trap datecheck EXIT @@ -170,12 +183,16 @@ trap datecheck EXIT # sccache server at the start of the build, but no need to worry if this fails. SCCACHE_IDLE_TIMEOUT=10800 sccache --start-server || true +# Our build may overwrite config.toml, so we remove it here +rm -f config.toml + $SRC/configure $RUST_CONFIGURE_ARGS retry make prepare # Display the CPU and memory information. This helps us know why the CI timing # is fluctuating. +echo "::group::Display CPU and Memory information" if isMacOS; then system_profiler SPHardwareDataType || true sysctl hw || true @@ -185,8 +202,10 @@ else cat /proc/meminfo || true ncpus=$(grep processor /proc/cpuinfo | wc -l) fi +echo "::endgroup::" if [ ! -z "$SCRIPT" ]; then + echo "Executing ${SCRIPT}" sh -x -c "$SCRIPT" else do_make() { @@ -216,4 +235,6 @@ if [ "$RUN_CHECK_WITH_PARALLEL_QUERIES" != "" ]; then CARGO_INCREMENTAL=0 ../x check fi +echo "::group::sccache stats" sccache --show-stats || true +echo "::endgroup::" diff --git a/src/ci/scripts/create-doc-artifacts.sh b/src/ci/scripts/create-doc-artifacts.sh new file mode 100755 index 0000000000000..2516b0d850594 --- /dev/null +++ b/src/ci/scripts/create-doc-artifacts.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Compress doc artifacts and name them based on the commit, or the date if +# commit is not available. + +set -euox pipefail + +# Try to get short commit hash, fallback to date +if [ -n "$HEAD_SHA" ]; then + short_rev=$(echo "${HEAD_SHA}" | cut -c1-8) +else + short_rev=$(git rev-parse --short HEAD || date -u +'%Y-%m-%dT%H%M%SZ') +fi + +# Try to get branch, fallback to none +branch=$(git branch --show-current || echo) + +if [ -n "$branch" ]; then + branch="${branch}-" +fi + +if [ "${GITHUB_EVENT_NAME:=none}" = "pull_request" ]; then + pr_num=$(echo "$GITHUB_REF_NAME" | cut -d'/' -f1) + name="doc-${pr_num}-${short_rev}" +else + name="doc-${branch}${short_rev}" +fi + + +if [ -d "obj/staging/doc" ]; then + mkdir -p obj/artifacts/doc + + # Level 12 seems to give a good tradeoff of time vs. space savings + ZSTD_CLEVEL=12 ZSTD_NBTHREADS=4 \ + tar --zstd -cf "obj/artifacts/doc/${name}.tar.zst" -C obj/staging/doc . + + ls -lh obj/artifacts/doc +fi + +# Set this environment variable for future use if running in CI +if [ -n "$GITHUB_ENV" ]; then + echo "DOC_ARTIFACT_NAME=${name}" >> "$GITHUB_ENV" +fi diff --git a/src/ci/scripts/install-clang.sh b/src/ci/scripts/install-clang.sh index 02b72625d6eb5..cbb4d5765ca3f 100755 --- a/src/ci/scripts/install-clang.sh +++ b/src/ci/scripts/install-clang.sh @@ -39,11 +39,6 @@ if isMacOS; then ciCommandSetEnv AR "ar" elif isWindows && [[ ${CUSTOM_MINGW-0} -ne 1 ]]; then - if [[ ${WINDOWS_SDK_20348_HACK-0} -eq 1 ]]; then - rm -rf '/c/Program Files (x86)/Windows Kits/10/include/10.0.20348.0' - mv '/c/Program Files (x86)/Windows Kits/10/include/'10.0.{19041,20348}.0 - fi - # If we're compiling for MSVC then we, like most other distribution builders, # switch to clang as the compiler. This'll allow us eventually to enable LTO # amongst LLVM and rustc. Note that we only do this on MSVC as I don't think diff --git a/src/ci/scripts/setup-environment.sh b/src/ci/scripts/setup-environment.sh index 0bc35f932832c..d3c55a4d6b4aa 100755 --- a/src/ci/scripts/setup-environment.sh +++ b/src/ci/scripts/setup-environment.sh @@ -10,7 +10,7 @@ source "$(cd "$(dirname "$0")" && pwd)/../shared.sh" # Load extra environment variables vars="${EXTRA_VARIABLES-}" -echo "${vars}" | jq '' >/dev/null # Validate JSON and exit on errors +echo "${vars}" | jq '.' >/dev/null # Validate JSON and exit on errors for key in $(echo "${vars}" | jq "keys[]" -r); do # On Windows, for whatever reason, $key contains the BOM character in it, # and that messes up `jq ".${key}"`. This line strips the BOM from the key. diff --git a/src/ci/stage-build.py b/src/ci/stage-build.py deleted file mode 100644 index 8d03d3759bf00..0000000000000 --- a/src/ci/stage-build.py +++ /dev/null @@ -1,870 +0,0 @@ -#!/usr/bin/env python3 -# ignore-tidy-linelength - -# Compatible with Python 3.6+ - -import contextlib -import getpass -import glob -import json -import logging -import os -import pprint -import shutil -import subprocess -import sys -import time -import traceback -import urllib.request -from io import StringIO -from pathlib import Path -from typing import Callable, ContextManager, Dict, Iterable, Iterator, List, Optional, \ - Tuple, Union - -PGO_HOST = os.environ["PGO_HOST"] - -LOGGER = logging.getLogger("stage-build") - -LLVM_PGO_CRATES = [ - "syn-1.0.89", - "cargo-0.60.0", - "serde-1.0.136", - "ripgrep-13.0.0", - "regex-1.5.5", - "clap-3.1.6", - "hyper-0.14.18" -] - -RUSTC_PGO_CRATES = [ - "externs", - "ctfe-stress-5", - "cargo-0.60.0", - "token-stream-stress", - "match-stress", - "tuple-stress", - "diesel-1.4.8", - "bitmaps-3.1.0" -] - -LLVM_BOLT_CRATES = LLVM_PGO_CRATES - -class Pipeline: - # Paths - def checkout_path(self) -> Path: - """ - The root checkout, where the source is located. - """ - raise NotImplementedError - - def downloaded_llvm_dir(self) -> Path: - """ - Directory where the host LLVM is located. - """ - raise NotImplementedError - - def build_root(self) -> Path: - """ - The main directory where the build occurs. - """ - raise NotImplementedError - - def build_artifacts(self) -> Path: - return self.build_root() / "build" / PGO_HOST - - def rustc_stage_0(self) -> Path: - return self.build_artifacts() / "stage0" / "bin" / "rustc" - - def cargo_stage_0(self) -> Path: - return self.build_artifacts() / "stage0" / "bin" / "cargo" - - def rustc_stage_2(self) -> Path: - return self.build_artifacts() / "stage2" / "bin" / "rustc" - - def opt_artifacts(self) -> Path: - raise NotImplementedError - - def llvm_profile_dir_root(self) -> Path: - return self.opt_artifacts() / "llvm-pgo" - - def llvm_profile_merged_file(self) -> Path: - return self.opt_artifacts() / "llvm-pgo.profdata" - - def rustc_perf_dir(self) -> Path: - return self.opt_artifacts() / "rustc-perf" - - def build_rustc_perf(self): - raise NotImplementedError() - - def rustc_profile_dir_root(self) -> Path: - return self.opt_artifacts() / "rustc-pgo" - - def rustc_profile_merged_file(self) -> Path: - return self.opt_artifacts() / "rustc-pgo.profdata" - - def rustc_profile_template_path(self) -> Path: - """ - The profile data is written into a single filepath that is being repeatedly merged when each - rustc invocation ends. Empirically, this can result in some profiling data being lost. That's - why we override the profile path to include the PID. This will produce many more profiling - files, but the resulting profile will produce a slightly faster rustc binary. - """ - return self.rustc_profile_dir_root() / "default_%m_%p.profraw" - - def supports_bolt(self) -> bool: - raise NotImplementedError - - def llvm_bolt_profile_merged_file(self) -> Path: - return self.opt_artifacts() / "bolt.profdata" - - def metrics_path(self) -> Path: - return self.build_root() / "build" / "metrics.json" - - -class LinuxPipeline(Pipeline): - def checkout_path(self) -> Path: - return Path("/checkout") - - def downloaded_llvm_dir(self) -> Path: - return Path("/rustroot") - - def build_root(self) -> Path: - return self.checkout_path() / "obj" - - def opt_artifacts(self) -> Path: - return Path("/tmp/tmp-multistage/opt-artifacts") - - def build_rustc_perf(self): - # /tmp/rustc-perf comes from the Dockerfile - shutil.copytree("/tmp/rustc-perf", self.rustc_perf_dir()) - cmd(["chown", "-R", f"{getpass.getuser()}:", self.rustc_perf_dir()]) - - with change_cwd(self.rustc_perf_dir()): - cmd([self.cargo_stage_0(), "build", "-p", "collector"], env=dict( - RUSTC=str(self.rustc_stage_0()), - RUSTC_BOOTSTRAP="1" - )) - - def supports_bolt(self) -> bool: - return True - - -class WindowsPipeline(Pipeline): - def __init__(self): - self.checkout_dir = Path(os.getcwd()) - - def checkout_path(self) -> Path: - return self.checkout_dir - - def downloaded_llvm_dir(self) -> Path: - return self.checkout_path() / "citools" / "clang-rust" - - def build_root(self) -> Path: - return self.checkout_path() - - def opt_artifacts(self) -> Path: - return self.checkout_path() / "opt-artifacts" - - def rustc_stage_0(self) -> Path: - return super().rustc_stage_0().with_suffix(".exe") - - def cargo_stage_0(self) -> Path: - return super().cargo_stage_0().with_suffix(".exe") - - def rustc_stage_2(self) -> Path: - return super().rustc_stage_2().with_suffix(".exe") - - def build_rustc_perf(self): - # rustc-perf version from 2023-03-15 - perf_commit = "9dfaa35193154b690922347ee1141a06ec87a199" - rustc_perf_zip_path = self.opt_artifacts() / "perf.zip" - - def download_rustc_perf(): - download_file( - f"https://github.com/rust-lang/rustc-perf/archive/{perf_commit}.zip", - rustc_perf_zip_path - ) - with change_cwd(self.opt_artifacts()): - unpack_archive(rustc_perf_zip_path) - move_path(Path(f"rustc-perf-{perf_commit}"), self.rustc_perf_dir()) - delete_file(rustc_perf_zip_path) - - retry_action(download_rustc_perf, "Download rustc-perf") - - with change_cwd(self.rustc_perf_dir()): - cmd([self.cargo_stage_0(), "build", "-p", "collector"], env=dict( - RUSTC=str(self.rustc_stage_0()), - RUSTC_BOOTSTRAP="1" - )) - - def rustc_profile_template_path(self) -> Path: - """ - On Windows, we don't have enough space to use separate files for each rustc invocation. - Therefore, we use a single file for the generated profiles. - """ - return self.rustc_profile_dir_root() / "default_%m.profraw" - - def supports_bolt(self) -> bool: - return False - - -def get_timestamp() -> float: - return time.time() - - -Duration = float - - -def iterate_timers(timer: "Timer", name: str, level: int = 0) -> Iterator[ - Tuple[int, str, Duration]]: - """ - Hierarchically iterate the children of a timer, in a depth-first order. - """ - yield (level, name, timer.total_duration()) - for (child_name, child_timer) in timer.children: - yield from iterate_timers(child_timer, child_name, level=level + 1) - - -class Timer: - def __init__(self, parent_names: Tuple[str, ...] = ()): - self.children: List[Tuple[str, Timer]] = [] - self.section_active = False - self.parent_names = parent_names - self.duration_excluding_children: Duration = 0 - - @contextlib.contextmanager - def section(self, name: str) -> ContextManager["Timer"]: - assert not self.section_active - self.section_active = True - - start = get_timestamp() - exc = None - - child_timer = Timer(parent_names=self.parent_names + (name,)) - full_name = " > ".join(child_timer.parent_names) - try: - LOGGER.info(f"Section `{full_name}` starts") - yield child_timer - except BaseException as exception: - exc = exception - raise - finally: - end = get_timestamp() - duration = end - start - - child_timer.duration_excluding_children = duration - child_timer.total_duration() - self.add_child(name, child_timer) - if exc is None: - LOGGER.info(f"Section `{full_name}` ended: OK ({duration:.2f}s)") - else: - LOGGER.info(f"Section `{full_name}` ended: FAIL ({duration:.2f}s)") - self.section_active = False - - def total_duration(self) -> Duration: - return self.duration_excluding_children + sum( - c.total_duration() for (_, c) in self.children) - - def has_children(self) -> bool: - return len(self.children) > 0 - - def print_stats(self): - rows = [] - for (child_name, child_timer) in self.children: - for (level, name, duration) in iterate_timers(child_timer, child_name, level=0): - label = f"{' ' * level}{name}:" - rows.append((label, duration)) - - # Empty row - rows.append(("", "")) - - total_duration_label = "Total duration:" - total_duration = self.total_duration() - rows.append((total_duration_label, humantime(total_duration))) - - space_after_label = 2 - max_label_length = max(16, max(len(label) for (label, _) in rows)) + space_after_label - - table_width = max_label_length + 23 - divider = "-" * table_width - - with StringIO() as output: - print(divider, file=output) - for (label, duration) in rows: - if isinstance(duration, Duration): - pct = (duration / total_duration) * 100 - value = f"{duration:>12.2f}s ({pct:>5.2f}%)" - else: - value = f"{duration:>{len(total_duration_label) + 7}}" - print(f"{label:<{max_label_length}} {value}", file=output) - print(divider, file=output, end="") - LOGGER.info(f"Timer results\n{output.getvalue()}") - - def add_child(self, name: str, timer: "Timer"): - self.children.append((name, timer)) - - def add_duration(self, name: str, duration: Duration): - timer = Timer(parent_names=self.parent_names + (name,)) - timer.duration_excluding_children = duration - self.add_child(name, timer) - - -class BuildStep: - def __init__(self, type: str, children: List["BuildStep"], duration: float): - self.type = type - self.children = children - self.duration = duration - - def find_all_by_type(self, type: str) -> Iterator["BuildStep"]: - if type == self.type: - yield self - for child in self.children: - yield from child.find_all_by_type(type) - - def __repr__(self): - return f"BuildStep(type={self.type}, duration={self.duration}, children={len(self.children)})" - - -def load_last_metrics(path: Path) -> BuildStep: - """ - Loads the metrics of the most recent bootstrap execution from a metrics.json file. - """ - with open(path, "r") as f: - metrics = json.load(f) - invocation = metrics["invocations"][-1] - - def parse(entry) -> Optional[BuildStep]: - if "kind" not in entry or entry["kind"] != "rustbuild_step": - return None - type = entry.get("type", "") - duration = entry.get("duration_excluding_children_sec", 0) - children = [] - - for child in entry.get("children", ()): - step = parse(child) - if step is not None: - children.append(step) - duration += step.duration - return BuildStep(type=type, children=children, duration=duration) - - children = [parse(child) for child in invocation.get("children", ())] - return BuildStep( - type="root", - children=children, - duration=invocation.get("duration_including_children_sec", 0) - ) - - -@contextlib.contextmanager -def change_cwd(dir: Path): - """ - Temporarily change working directory to `dir`. - """ - cwd = os.getcwd() - LOGGER.debug(f"Changing working dir from `{cwd}` to `{dir}`") - os.chdir(dir) - try: - yield - finally: - LOGGER.debug(f"Reverting working dir to `{cwd}`") - os.chdir(cwd) - - -def humantime(time_s: float) -> str: - hours = time_s // 3600 - time_s = time_s % 3600 - minutes = time_s // 60 - seconds = time_s % 60 - - result = "" - if hours > 0: - result += f"{int(hours)}h " - if minutes > 0: - result += f"{int(minutes)}m " - result += f"{round(seconds)}s" - return result - - -def move_path(src: Path, dst: Path): - LOGGER.info(f"Moving `{src}` to `{dst}`") - shutil.move(src, dst) - - -def delete_file(path: Path): - LOGGER.info(f"Deleting file `{path}`") - os.unlink(path) - - -def delete_directory(path: Path): - LOGGER.info(f"Deleting directory `{path}`") - shutil.rmtree(path) - - -def unpack_archive(archive: Path): - LOGGER.info(f"Unpacking archive `{archive}`") - shutil.unpack_archive(archive) - - -def download_file(src: str, target: Path): - LOGGER.info(f"Downloading `{src}` into `{target}`") - urllib.request.urlretrieve(src, str(target)) - - -def retry_action(action, name: str, max_fails: int = 5): - LOGGER.info(f"Attempting to perform action `{name}` with retry") - for iteration in range(max_fails): - LOGGER.info(f"Attempt {iteration + 1}/{max_fails}") - try: - action() - return - except: - LOGGER.error(f"Action `{name}` has failed\n{traceback.format_exc()}") - - raise Exception(f"Action `{name}` has failed after {max_fails} attempts") - - -def cmd( - args: List[Union[str, Path]], - env: Optional[Dict[str, str]] = None, - output_path: Optional[Path] = None -): - args = [str(arg) for arg in args] - - environment = os.environ.copy() - - cmd_str = "" - if env is not None: - environment.update(env) - cmd_str += " ".join(f"{k}={v}" for (k, v) in (env or {}).items()) - cmd_str += " " - cmd_str += " ".join(args) - if output_path is not None: - cmd_str += f" > {output_path}" - LOGGER.info(f"Executing `{cmd_str}`") - - if output_path is not None: - with open(output_path, "w") as f: - return subprocess.run( - args, - env=environment, - check=True, - stdout=f - ) - return subprocess.run(args, env=environment, check=True) - -class BenchmarkRunner: - def run_rustc(self, pipeline: Pipeline): - raise NotImplementedError - - def run_llvm(self, pipeline: Pipeline): - raise NotImplementedError - - def run_bolt(self, pipeline: Pipeline): - raise NotImplementedError - -class DefaultBenchmarkRunner(BenchmarkRunner): - def run_rustc(self, pipeline: Pipeline): - # Here we're profiling the `rustc` frontend, so we also include `Check`. - # The benchmark set includes various stress tests that put the frontend under pressure. - run_compiler_benchmarks( - pipeline, - profiles=["Check", "Debug", "Opt"], - scenarios=["All"], - crates=RUSTC_PGO_CRATES, - env=dict( - LLVM_PROFILE_FILE=str(pipeline.rustc_profile_template_path()) - ) - ) - def run_llvm(self, pipeline: Pipeline): - run_compiler_benchmarks( - pipeline, - profiles=["Debug", "Opt"], - scenarios=["Full"], - crates=LLVM_PGO_CRATES - ) - - def run_bolt(self, pipeline: Pipeline): - run_compiler_benchmarks( - pipeline, - profiles=["Check", "Debug", "Opt"], - scenarios=["Full"], - crates=LLVM_BOLT_CRATES - ) - -def run_compiler_benchmarks( - pipeline: Pipeline, - profiles: List[str], - scenarios: List[str], - crates: List[str], - env: Optional[Dict[str, str]] = None -): - env = env if env is not None else {} - - # Compile libcore, both in opt-level=0 and opt-level=3 - with change_cwd(pipeline.build_root()): - cmd([ - pipeline.rustc_stage_2(), - "--edition", "2021", - "--crate-type", "lib", - str(pipeline.checkout_path() / "library/core/src/lib.rs"), - "--out-dir", pipeline.opt_artifacts() - ], env=dict(RUSTC_BOOTSTRAP="1", **env)) - - cmd([ - pipeline.rustc_stage_2(), - "--edition", "2021", - "--crate-type", "lib", - "-Copt-level=3", - str(pipeline.checkout_path() / "library/core/src/lib.rs"), - "--out-dir", pipeline.opt_artifacts() - ], env=dict(RUSTC_BOOTSTRAP="1", **env)) - - # Run rustc-perf benchmarks - # Benchmark using profile_local with eprintln, which essentially just means - # don't actually benchmark -- just make sure we run rustc a bunch of times. - with change_cwd(pipeline.rustc_perf_dir()): - cmd([ - pipeline.cargo_stage_0(), - "run", - "-p", "collector", "--bin", "collector", "--", - "profile_local", "eprintln", - pipeline.rustc_stage_2(), - "--id", "Test", - "--cargo", pipeline.cargo_stage_0(), - "--profiles", ",".join(profiles), - "--scenarios", ",".join(scenarios), - "--include", ",".join(crates) - ], env=dict( - RUST_LOG="collector=debug", - RUSTC=str(pipeline.rustc_stage_0()), - RUSTC_BOOTSTRAP="1", - **env - )) - - -# https://stackoverflow.com/a/31631711/1107768 -def format_bytes(size: int) -> str: - """Return the given bytes as a human friendly KiB, MiB or GiB string.""" - KB = 1024 - MB = KB ** 2 # 1,048,576 - GB = KB ** 3 # 1,073,741,824 - TB = KB ** 4 # 1,099,511,627,776 - - if size < KB: - return f"{size} B" - elif KB <= size < MB: - return f"{size / KB:.2f} KiB" - elif MB <= size < GB: - return f"{size / MB:.2f} MiB" - elif GB <= size < TB: - return f"{size / GB:.2f} GiB" - else: - return str(size) - - -# https://stackoverflow.com/a/63307131/1107768 -def count_files(path: Path) -> int: - return sum(1 for p in path.rglob("*") if p.is_file()) - - -def count_files_with_prefix(path: Path) -> int: - return sum(1 for p in glob.glob(f"{path}*") if Path(p).is_file()) - - -# https://stackoverflow.com/a/55659577/1107768 -def get_path_size(path: Path) -> int: - if path.is_dir(): - return sum(p.stat().st_size for p in path.rglob("*")) - return path.stat().st_size - - -def get_path_prefix_size(path: Path) -> int: - """ - Get size of all files beginning with the prefix `path`. - Alternative to shell `du -sh *`. - """ - return sum(Path(p).stat().st_size for p in glob.glob(f"{path}*")) - - -def get_files(directory: Path, filter: Optional[Callable[[Path], bool]] = None) -> Iterable[Path]: - for file in os.listdir(directory): - path = directory / file - if filter is None or filter(path): - yield path - - -def build_rustc( - pipeline: Pipeline, - args: List[str], - env: Optional[Dict[str, str]] = None -): - arguments = [ - sys.executable, - pipeline.checkout_path() / "x.py", - "build", - "--target", PGO_HOST, - "--host", PGO_HOST, - "--stage", "2", - "library/std" - ] + args - cmd(arguments, env=env) - - -def create_pipeline() -> Pipeline: - if sys.platform == "linux": - return LinuxPipeline() - elif sys.platform in ("cygwin", "win32"): - return WindowsPipeline() - else: - raise Exception(f"Optimized build is not supported for platform {sys.platform}") - - -def gather_llvm_profiles(pipeline: Pipeline, runner: BenchmarkRunner): - LOGGER.info("Running benchmarks with PGO instrumented LLVM") - - runner.run_llvm(pipeline) - - profile_path = pipeline.llvm_profile_merged_file() - LOGGER.info(f"Merging LLVM PGO profiles to {profile_path}") - cmd([ - pipeline.downloaded_llvm_dir() / "bin" / "llvm-profdata", - "merge", - "-o", profile_path, - pipeline.llvm_profile_dir_root() - ]) - - LOGGER.info("LLVM PGO statistics") - LOGGER.info(f"{profile_path}: {format_bytes(get_path_size(profile_path))}") - LOGGER.info( - f"{pipeline.llvm_profile_dir_root()}: {format_bytes(get_path_size(pipeline.llvm_profile_dir_root()))}") - LOGGER.info(f"Profile file count: {count_files(pipeline.llvm_profile_dir_root())}") - - # We don't need the individual .profraw files now that they have been merged - # into a final .profdata - delete_directory(pipeline.llvm_profile_dir_root()) - - -def gather_rustc_profiles(pipeline: Pipeline, runner: BenchmarkRunner): - LOGGER.info("Running benchmarks with PGO instrumented rustc") - - - runner.run_rustc(pipeline) - - - profile_path = pipeline.rustc_profile_merged_file() - LOGGER.info(f"Merging Rustc PGO profiles to {profile_path}") - cmd([ - pipeline.build_artifacts() / "llvm" / "bin" / "llvm-profdata", - "merge", - "-o", profile_path, - pipeline.rustc_profile_dir_root() - ]) - - LOGGER.info("Rustc PGO statistics") - LOGGER.info(f"{profile_path}: {format_bytes(get_path_size(profile_path))}") - LOGGER.info( - f"{pipeline.rustc_profile_dir_root()}: {format_bytes(get_path_size(pipeline.rustc_profile_dir_root()))}") - LOGGER.info(f"Profile file count: {count_files(pipeline.rustc_profile_dir_root())}") - - # We don't need the individual .profraw files now that they have been merged - # into a final .profdata - delete_directory(pipeline.rustc_profile_dir_root()) - - -def gather_llvm_bolt_profiles(pipeline: Pipeline, runner: BenchmarkRunner): - LOGGER.info("Running benchmarks with BOLT instrumented LLVM") - - runner.run_bolt(pipeline) - - merged_profile_path = pipeline.llvm_bolt_profile_merged_file() - profile_files_path = Path("/tmp/prof.fdata") - LOGGER.info(f"Merging LLVM BOLT profiles to {merged_profile_path}") - - profile_files = sorted(glob.glob(f"{profile_files_path}*")) - cmd([ - "merge-fdata", - *profile_files, - ], output_path=merged_profile_path) - - LOGGER.info("LLVM BOLT statistics") - LOGGER.info(f"{merged_profile_path}: {format_bytes(get_path_size(merged_profile_path))}") - LOGGER.info( - f"{profile_files_path}: {format_bytes(get_path_prefix_size(profile_files_path))}") - LOGGER.info(f"Profile file count: {count_files_with_prefix(profile_files_path)}") - - -def clear_llvm_files(pipeline: Pipeline): - """ - Rustbuild currently doesn't support rebuilding LLVM when PGO options - change (or any other llvm-related options); so just clear out the relevant - directories ourselves. - """ - LOGGER.info("Clearing LLVM build files") - delete_directory(pipeline.build_artifacts() / "llvm") - delete_directory(pipeline.build_artifacts() / "lld") - - -def print_binary_sizes(pipeline: Pipeline): - bin_dir = pipeline.build_artifacts() / "stage2" / "bin" - binaries = get_files(bin_dir) - - lib_dir = pipeline.build_artifacts() / "stage2" / "lib" - libraries = get_files(lib_dir, lambda p: p.suffix == ".so") - - paths = sorted(binaries) + sorted(libraries) - with StringIO() as output: - for path in paths: - path_str = f"{path.name}:" - print(f"{path_str:<50}{format_bytes(path.stat().st_size):>14}", file=output) - LOGGER.info(f"Rustc binary size\n{output.getvalue()}") - - -def print_free_disk_space(pipeline: Pipeline): - usage = shutil.disk_usage(pipeline.opt_artifacts()) - total = usage.total - used = usage.used - free = usage.free - - logging.info( - f"Free disk space: {format_bytes(free)} out of total {format_bytes(total)} ({(used / total) * 100:.2f}% used)") - - -def log_metrics(step: BuildStep): - substeps: List[Tuple[int, BuildStep]] = [] - - def visit(step: BuildStep, level: int): - substeps.append((level, step)) - for child in step.children: - visit(child, level=level + 1) - - visit(step, 0) - - output = StringIO() - for (level, step) in substeps: - label = f"{'.' * level}{step.type}" - print(f"{label:<65}{step.duration:>8.2f}s", file=output) - logging.info(f"Build step durations\n{output.getvalue()}") - - -def record_metrics(pipeline: Pipeline, timer: Timer): - metrics = load_last_metrics(pipeline.metrics_path()) - if metrics is None: - return - llvm_steps = tuple(metrics.find_all_by_type("bootstrap::llvm::Llvm")) - assert len(llvm_steps) > 0 - llvm_duration = sum(step.duration for step in llvm_steps) - - rustc_steps = tuple(metrics.find_all_by_type("bootstrap::compile::Rustc")) - assert len(rustc_steps) > 0 - rustc_duration = sum(step.duration for step in rustc_steps) - - # The LLVM step is part of the Rustc step - rustc_duration -= llvm_duration - - timer.add_duration("LLVM", llvm_duration) - timer.add_duration("Rustc", rustc_duration) - - log_metrics(metrics) - - -def execute_build_pipeline(timer: Timer, pipeline: Pipeline, runner: BenchmarkRunner, final_build_args: List[str]): - # Clear and prepare tmp directory - shutil.rmtree(pipeline.opt_artifacts(), ignore_errors=True) - os.makedirs(pipeline.opt_artifacts(), exist_ok=True) - - pipeline.build_rustc_perf() - - # Stage 1: Build rustc + PGO instrumented LLVM - with timer.section("Stage 1 (LLVM PGO)") as stage1: - with stage1.section("Build rustc and LLVM") as rustc_build: - build_rustc(pipeline, args=[ - "--llvm-profile-generate" - ], env=dict( - LLVM_PROFILE_DIR=str(pipeline.llvm_profile_dir_root() / "prof-%p") - )) - record_metrics(pipeline, rustc_build) - - with stage1.section("Gather profiles"): - gather_llvm_profiles(pipeline, runner) - print_free_disk_space(pipeline) - - clear_llvm_files(pipeline) - final_build_args += [ - "--llvm-profile-use", - pipeline.llvm_profile_merged_file() - ] - - # Stage 2: Build PGO instrumented rustc + LLVM - with timer.section("Stage 2 (rustc PGO)") as stage2: - with stage2.section("Build rustc and LLVM") as rustc_build: - build_rustc(pipeline, args=[ - "--rust-profile-generate", - pipeline.rustc_profile_dir_root() - ]) - record_metrics(pipeline, rustc_build) - - with stage2.section("Gather profiles"): - gather_rustc_profiles(pipeline, runner) - print_free_disk_space(pipeline) - - clear_llvm_files(pipeline) - final_build_args += [ - "--rust-profile-use", - pipeline.rustc_profile_merged_file() - ] - - # Stage 3: Build rustc + BOLT instrumented LLVM - if pipeline.supports_bolt(): - with timer.section("Stage 3 (LLVM BOLT)") as stage3: - with stage3.section("Build rustc and LLVM") as rustc_build: - build_rustc(pipeline, args=[ - "--llvm-profile-use", - pipeline.llvm_profile_merged_file(), - "--llvm-bolt-profile-generate", - "--rust-profile-use", - pipeline.rustc_profile_merged_file() - ]) - record_metrics(pipeline, rustc_build) - - with stage3.section("Gather profiles"): - gather_llvm_bolt_profiles(pipeline, runner) - - # LLVM is not being cleared here, we want to reuse the previous build - print_free_disk_space(pipeline) - final_build_args += [ - "--llvm-bolt-profile-use", - pipeline.llvm_bolt_profile_merged_file() - ] - - # Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM - with timer.section("Stage 4 (final build)") as stage4: - cmd(final_build_args) - record_metrics(pipeline, stage4) - - -def run(runner: BenchmarkRunner): - logging.basicConfig( - level=logging.DEBUG, - format="%(name)s %(levelname)-4s: %(message)s", - ) - - LOGGER.info(f"Running multi-stage build using Python {sys.version}") - LOGGER.info(f"Environment values\n{pprint.pformat(dict(os.environ), indent=2)}") - - build_args = sys.argv[1:] - - timer = Timer() - pipeline = create_pipeline() - - try: - execute_build_pipeline(timer, pipeline, runner, build_args) - except BaseException as e: - LOGGER.error("The multi-stage build has failed") - raise e - finally: - timer.print_stats() - print_free_disk_space(pipeline) - - print_binary_sizes(pipeline) - -if __name__ == "__main__": - runner = DefaultBenchmarkRunner() - run(runner) diff --git a/src/doc/book b/src/doc/book index 8fa6b854d5155..72187f5cd0bea 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit 8fa6b854d515506d825390fe0d817f5ef0c89350 +Subproject commit 72187f5cd0beaaa9c6f584156bcd88f921871e83 diff --git a/src/doc/edition-guide b/src/doc/edition-guide index f63e578b92ff4..2751bdcef1254 160000 --- a/src/doc/edition-guide +++ b/src/doc/edition-guide @@ -1 +1 @@ -Subproject commit f63e578b92ff43e8cc38fcaa257b660f45c8a8c2 +Subproject commit 2751bdcef125468ea2ee006c11992cd1405aebe5 diff --git a/src/doc/embedded-book b/src/doc/embedded-book index f2aed2fe8e9f5..99ad2847b865e 160000 --- a/src/doc/embedded-book +++ b/src/doc/embedded-book @@ -1 +1 @@ -Subproject commit f2aed2fe8e9f55508c86ba3aa4b6789b18a08a22 +Subproject commit 99ad2847b865e96d8ae7b333d3ee96963557e621 diff --git a/src/doc/nomicon b/src/doc/nomicon index b5f018fb5930c..388750b081c08 160000 --- a/src/doc/nomicon +++ b/src/doc/nomicon @@ -1 +1 @@ -Subproject commit b5f018fb5930cb733b0a8aaf2eed975d4771e74d +Subproject commit 388750b081c0893c275044d37203f97709e058ba diff --git a/src/doc/reference b/src/doc/reference index 553d99b02a53b..d43038932adeb 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit 553d99b02a53b4133a40d5bd2e19958c67487c00 +Subproject commit d43038932adeb16ada80e206d4c073d851298101 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index 8ee9528b72b92..07e0df2f006e5 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit 8ee9528b72b927cff8fd32346db8bbd1198816f0 +Subproject commit 07e0df2f006e59d171c6bf3cafa9d61dbeb520d8 diff --git a/src/doc/rustc-dev-guide b/src/doc/rustc-dev-guide index f1e637883fafe..b123ab4754127 160000 --- a/src/doc/rustc-dev-guide +++ b/src/doc/rustc-dev-guide @@ -1 +1 @@ -Subproject commit f1e637883fafeb83bdd5906ee7f467e4d35b7337 +Subproject commit b123ab4754127d822ffb38349ce0fbf561f1b2fd diff --git a/src/doc/rustc/book.toml b/src/doc/rustc/book.toml index cea6033ede208..167aece0ed6a3 100644 --- a/src/doc/rustc/book.toml +++ b/src/doc/rustc/book.toml @@ -6,3 +6,9 @@ title = "The rustc book" [output.html] git-repository-url = "https://github.com/rust-lang/rust/tree/master/src/doc/rustc" edit-url-template = "https://github.com/rust-lang/rust/edit/master/src/doc/rustc/{path}" + +[output.html.search] +use-boolean-and = true + +[output.html.playground] +runnable = false diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 73343ba9df51b..94605e2a21707 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -16,6 +16,7 @@ - [Target Tier Policy](target-tier-policy.md) - [Template for Target-specific Documentation](platform-support/TEMPLATE.md) - [aarch64-apple-ios-sim](platform-support/aarch64-apple-ios-sim.md) + - [\*-apple-tvos](platform-support/apple-tvos.md) - [\*-apple-watchos\*](platform-support/apple-watchos.md) - [aarch64-nintendo-switch-freestanding](platform-support/aarch64-nintendo-switch-freestanding.md) - [armeb-unknown-linux-gnueabi](platform-support/armeb-unknown-linux-gnueabi.md) @@ -27,19 +28,28 @@ - [armv7-unknown-linux-uclibceabihf](platform-support/armv7-unknown-linux-uclibceabihf.md) - [\*-android and \*-androideabi](platform-support/android.md) - [\*-linux-ohos](platform-support/openharmony.md) + - [aarch64-unknown-teeos](platform-support/aarch64-unknown-teeos.md) - [\*-esp-espidf](platform-support/esp-idf.md) - [\*-unknown-fuchsia](platform-support/fuchsia.md) - [\*-kmc-solid_\*](platform-support/kmc-solid.md) + - [csky-unknown-linux-gnuabiv2](platform-support/csky-unknown-linux-gnuabiv2.md) - [loongarch\*-unknown-linux-\*](platform-support/loongarch-linux.md) + - [loongarch\*-unknown-none\*](platform-support/loongarch-none.md) - [m68k-unknown-linux-gnu](platform-support/m68k-unknown-linux-gnu.md) - [mips64-openwrt-linux-musl](platform-support/mips64-openwrt-linux-musl.md) - [mipsel-sony-psx](platform-support/mipsel-sony-psx.md) + - [mipsisa\*r6\*-unknown-linux-gnu\*](platform-support/mips-release-6.md) - [nvptx64-nvidia-cuda](platform-support/nvptx64-nvidia-cuda.md) - [riscv32imac-unknown-xous-elf](platform-support/riscv32imac-unknown-xous-elf.md) + - [sparc-unknown-none-elf](./platform-support/sparc-unknown-none-elf.md) - [*-pc-windows-gnullvm](platform-support/pc-windows-gnullvm.md) - [\*-nto-qnx-\*](platform-support/nto-qnx.md) + - [*-unikraft-linux-musl](platform-support/unikraft-linux-musl.md) + - [*-unknown-hermit](platform-support/hermit.md) + - [\*-unknown-netbsd\*](platform-support/netbsd.md) - [*-unknown-openbsd](platform-support/openbsd.md) - [\*-unknown-uefi](platform-support/unknown-uefi.md) + - [wasm32-wasi-preview1-threads](platform-support/wasm32-wasi-preview1-threads.md) - [wasm64-unknown-unknown](platform-support/wasm64-unknown-unknown.md) - [x86_64-fortanix-unknown-sgx](platform-support/x86_64-fortanix-unknown-sgx.md) - [x86_64-unknown-none](platform-support/x86_64-unknown-none.md) @@ -52,4 +62,6 @@ - [Instrumentation-based Code Coverage](instrument-coverage.md) - [Linker-plugin-based LTO](linker-plugin-lto.md) - [Exploit Mitigations](exploit-mitigations.md) +- [Symbol Mangling](symbol-mangling/index.md) + - [v0 Symbol Format](symbol-mangling/v0.md) - [Contributing to `rustc`](contributing.md) diff --git a/src/doc/rustc/src/codegen-options/index.md b/src/doc/rustc/src/codegen-options/index.md index 1041d5026690f..f882a31de5a8b 100644 --- a/src/doc/rustc/src/codegen-options/index.md +++ b/src/doc/rustc/src/codegen-options/index.md @@ -31,8 +31,8 @@ Supported values can also be discovered by running `rustc --print code-models`. ## codegen-units -This flag controls how many code generation units the crate is split into. It -takes an integer greater than 0. +This flag controls the maximum number of code generation units the crate is +split into. It takes an integer greater than 0. When a crate is split into multiple codegen units, LLVM is able to process them in parallel. Increasing parallelism may speed up compile times, but may @@ -569,20 +569,22 @@ for the purpose of generating object code and linking. Supported values for this option are: -* `v0` — The "v0" mangling scheme. The specific format is not specified at - this time. +* `v0` — The "v0" mangling scheme. The default, if not specified, will use a compiler-chosen default which may change in the future. +See the [Symbol Mangling] chapter for details on symbol mangling and the mangling format. + [name mangling]: https://en.wikipedia.org/wiki/Name_mangling +[Symbol Mangling]: ../symbol-mangling/index.md ## target-cpu This instructs `rustc` to generate code specifically for a particular processor. You can run `rustc --print target-cpus` to see the valid options to pass -and the default target CPU for the current buid target. +and the default target CPU for the current build target. Each target has a default base CPU. Special values include: * `native` can be passed to use the processor of the host machine. diff --git a/src/doc/rustc/src/command-line-arguments.md b/src/doc/rustc/src/command-line-arguments.md index 3be4382b0a3aa..2c7c05c0c4b88 100644 --- a/src/doc/rustc/src/command-line-arguments.md +++ b/src/doc/rustc/src/command-line-arguments.md @@ -58,8 +58,8 @@ Example: `-l static:+whole-archive=mylib`. The kind of library and the modifiers can also be specified in a [`#[link]` attribute][link-attribute]. If the kind is not specified in the `link` -attribute or on the command-line, it will link a dynamic library if available, -otherwise it will use a static library. If the kind is specified on the +attribute or on the command-line, it will link a dynamic library by default, +except when building a static executable. If the kind is specified on the command-line, it will override the kind specified in a `link` attribute. The name used in a `link` attribute may be overridden using the form `-l @@ -202,6 +202,12 @@ flag](codegen-options/index.md#extra-filename). The files are written to the current directory unless the [`--out-dir` flag](#option-out-dir) is used. Each emission type may also specify the output filename with the form `KIND=PATH`, which takes precedence over the `-o` flag. +Specifying `-o -` or `--emit KIND=-` asks rustc to emit to stdout. +Text output types (`asm`, `dep-info`, `llvm-ir` and `mir`) can be written to +stdout despite it being a tty or not. This will result in an error if any +binary output type is written to stdout that is a tty. +This will also result in an error if multiple output types +would be written to stdout, because they would be all mixed together. [LLVM bitcode]: https://llvm.org/docs/BitCodeFormat.html [LLVM IR]: https://llvm.org/docs/LangRef.html diff --git a/src/doc/rustc/src/exploit-mitigations.md b/src/doc/rustc/src/exploit-mitigations.md index a82a53248d485..172048704f48d 100644 --- a/src/doc/rustc/src/exploit-mitigations.md +++ b/src/doc/rustc/src/exploit-mitigations.md @@ -55,88 +55,18 @@ Table I \ Summary of exploit mitigations supported by the Rust compiler when building programs for the Linux operating system on the AMD64 architecture and equivalent. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Exploit mitigation - Supported and enabled by default - Since -
Position-independent executable - Yes - 0.12.0 (2014-10-09) -
Integer overflow checks - Yes (enabled when debug assertions are enabled, and disabled when debug assertions are disabled) - 1.1.0 (2015-06-25) -
Non-executable memory regions - Yes - 1.8.0 (2016-04-14) -
Stack clashing protection - Yes - 1.20.0 (2017-08-31) -
Read-only relocations and immediate binding - Yes - 1.21.0 (2017-10-12) -
Heap corruption protection - Yes - 1.32.0 (2019-01-17) (via operating system default or specified allocator) -
Stack smashing protection - Yes - Nightly -
Forward-edge control flow protection - Yes - Nightly -
Backward-edge control flow protection (e.g., shadow and safe stack) - No - -
+ +| Exploit mitigation | Supported and enabled by default | Since | +| - | - | - | +| Position-independent executable | Yes | 0.12.0 (2014-10-09) | +| Integer overflow checks | Yes (enabled when debug assertions are enabled, and disabled when debug assertions are disabled) | 1.1.0 (2015-06-25) | +| Non-executable memory regions | Yes | 1.8.0 (2016-04-14) | +| Stack clashing protection | Yes | 1.20.0 (2017-08-31) | +| Read-only relocations and immediate binding | Yes | 1.21.0 (2017-10-12) | +| Heap corruption protection | Yes | 1.32.0 (2019-01-17) (via operating system default or specified allocator) | +| Stack smashing protection | Yes | Nightly | +| Forward-edge control flow protection | Yes | Nightly | +| Backward-edge control flow protection (e.g., shadow and safe stack) | Yes | Nightly | 1\. See @@ -513,20 +443,21 @@ Newer processors provide hardware assistance for backward-edge control flow protection, such as ARM Pointer Authentication, and Intel Shadow Stack as part of Intel CET. -The Rust compiler does not support shadow or safe stack. There is work -currently ongoing to add support for the sanitizers[40], which may or may -not include support for safe stack
7. +The Rust compiler supports shadow stack for aarch64 only +7 +on nightly Rust compilers [43]-[44]. Safe stack is available on nightly +Rust compilers [45]-[46]. ```text $ readelf -s target/release/hello-rust | grep __safestack_init + 1177: 00000000000057b0 444 FUNC GLOBAL DEFAULT 9 __safestack_init ``` Fig. 16. Checking if LLVM SafeStack is enabled for a given binary. The presence of the `__safestack_init` symbol indicates that LLVM SafeStack -is enabled for a given binary. Conversely, the absence of the +is enabled for a given binary (see Fig. 16). Conversely, the absence of the `__safestack_init` symbol indicates that LLVM SafeStack is not enabled for a -given binary (see Fig. 16). +given binary. 7\. The shadow stack implementation for the AMD64 architecture and equivalent in LLVM was removed due to performance and @@ -698,3 +629,15 @@ defaults (unrelated to `READ_IMPLIES_EXEC`). 42. bbjornse. “add codegen option for using LLVM stack smash protection #84197.” GitHub. + +43. ivanloz. “Add support for LLVM ShadowCallStack. #98208.” GitHub. + . + +44. “ShadowCallStack.” The Rust Unstable Book. + [https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#shadowcallstack](../unstable-book/compiler-flags/sanitizer.html#shadowcallstack). + +45. W. Wiser. “Add support for LLVM SafeStack #112000” GitHub. + + +46. “SafeStack.” The Rust Unstable Book. + [https://doc.rust-lang/org/unstable-book/compiler-flags/sanitizer.html#safestack](../unstable-book/compiler-flags/sanitizer.html#safestack). diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 3b2463aa5b2dd..a2ca925fb5f52 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -87,7 +87,7 @@ target | notes `aarch64-unknown-linux-musl` | ARM64 Linux with MUSL `arm-unknown-linux-gnueabi` | ARMv6 Linux (kernel 3.2, glibc 2.17) `arm-unknown-linux-gnueabihf` | ARMv6 Linux, hardfloat (kernel 3.2, glibc 2.17) -`armv7-unknown-linux-gnueabihf` | ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) +`armv7-unknown-linux-gnueabihf` | ARMv7-A Linux, hardfloat (kernel 3.2, glibc 2.17) [`loongarch64-unknown-linux-gnu`](platform-support/loongarch-linux.md) | LoongArch64 Linux, LP64D ABI (kernel 5.19, glibc 2.36) `mips-unknown-linux-gnu` | MIPS Linux (kernel 4.4, glibc 2.23) `mips64-unknown-linux-gnuabi64` | MIPS64 Linux, n64 ABI (kernel 4.4, glibc 2.23) @@ -101,7 +101,7 @@ target | notes `x86_64-unknown-freebsd` | 64-bit FreeBSD `x86_64-unknown-illumos` | illumos `x86_64-unknown-linux-musl` | 64-bit Linux with MUSL -`x86_64-unknown-netbsd` | NetBSD/amd64 +[`x86_64-unknown-netbsd`](platform-support/netbsd.md) | NetBSD/amd64 ## Tier 2 @@ -128,22 +128,22 @@ target | std | notes `aarch64-apple-ios` | ✓ | ARM64 iOS [`aarch64-apple-ios-sim`](platform-support/aarch64-apple-ios-sim.md) | ✓ | Apple iOS Simulator on ARM64 `aarch64-fuchsia` | ✓ | Alias for `aarch64-unknown-fuchsia` -`aarch64-unknown-fuchsia` | ✓ | ARM64 Fuchsia +[`aarch64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | ARM64 Fuchsia [`aarch64-linux-android`](platform-support/android.md) | ✓ | ARM64 Android `aarch64-unknown-none-softfloat` | * | Bare ARM64, softfloat `aarch64-unknown-none` | * | Bare ARM64, hardfloat [`aarch64-unknown-uefi`](platform-support/unknown-uefi.md) | * | ARM64 UEFI -[`arm-linux-androideabi`](platform-support/android.md) | ✓ | ARMv7 Android +[`arm-linux-androideabi`](platform-support/android.md) | ✓ | ARMv6 Android `arm-unknown-linux-musleabi` | ✓ | ARMv6 Linux with MUSL `arm-unknown-linux-musleabihf` | ✓ | ARMv6 Linux with MUSL, hardfloat `armebv7r-none-eabi` | * | Bare ARMv7-R, Big Endian `armebv7r-none-eabihf` | * | Bare ARMv7-R, Big Endian, hardfloat `armv5te-unknown-linux-gnueabi` | ✓ | ARMv5TE Linux (kernel 4.4, glibc 2.23) `armv5te-unknown-linux-musleabi` | ✓ | ARMv5TE Linux with MUSL -[`armv7-linux-androideabi`](platform-support/android.md) | ✓ | ARMv7a Android -`armv7-unknown-linux-gnueabi` | ✓ |ARMv7 Linux (kernel 4.15, glibc 2.27) -`armv7-unknown-linux-musleabi` | ✓ |ARMv7 Linux with MUSL -`armv7-unknown-linux-musleabihf` | ✓ | ARMv7 Linux with MUSL, hardfloat +[`armv7-linux-androideabi`](platform-support/android.md) | ✓ | ARMv7-A Android +`armv7-unknown-linux-gnueabi` | ✓ | ARMv7-A Linux (kernel 4.15, glibc 2.27) +`armv7-unknown-linux-musleabi` | ✓ | ARMv7-A Linux with MUSL +`armv7-unknown-linux-musleabihf` | ✓ | ARMv7-A Linux with MUSL, hardfloat `armv7a-none-eabi` | * | Bare ARMv7-A `armv7r-none-eabi` | * | Bare ARMv7-R `armv7r-none-eabihf` | * | Bare ARMv7-R, hardfloat @@ -159,7 +159,7 @@ target | std | notes `mips64-unknown-linux-muslabi64` | ✓ | MIPS64 Linux, n64 ABI, MUSL `mips64el-unknown-linux-muslabi64` | ✓ | MIPS64 (LE) Linux, n64 ABI, MUSL `mipsel-unknown-linux-musl` | ✓ | MIPS (LE) Linux with MUSL -`nvptx64-nvidia-cuda` | * | --emit=asm generates PTX code that [runs on NVIDIA GPUs] +[`nvptx64-nvidia-cuda`](platform-support/nvptx64-nvidia-cuda.md) | * | --emit=asm generates PTX code that [runs on NVIDIA GPUs] `riscv32i-unknown-none-elf` | * | Bare RISC-V (RV32I ISA) `riscv32imac-unknown-none-elf` | * | Bare RISC-V (RV32IMAC ISA) `riscv32imc-unknown-none-elf` | * | Bare RISC-V (RV32IMC ISA) @@ -167,22 +167,26 @@ target | std | notes `riscv64imac-unknown-none-elf` | * | Bare RISC-V (RV64IMAC ISA) `sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4, glibc 2.23) `sparcv9-sun-solaris` | ✓ | SPARC Solaris 10/11, illumos -`thumbv6m-none-eabi` | * | Bare Cortex-M0, M0+, M1 -`thumbv7em-none-eabi` | * | Bare Cortex-M4, M7 -`thumbv7em-none-eabihf` | * | Bare Cortex-M4F, M7F, FPU, hardfloat -`thumbv7m-none-eabi` | * | Bare Cortex-M3 -[`thumbv7neon-linux-androideabi`](platform-support/android.md) | ✓ | Thumb2-mode ARMv7a Android with NEON -`thumbv7neon-unknown-linux-gnueabihf` | ✓ | Thumb2-mode ARMv7a Linux with NEON (kernel 4.4, glibc 2.23) -`thumbv8m.base-none-eabi` | * | ARMv8-M Baseline -`thumbv8m.main-none-eabi` | * | ARMv8-M Mainline -`thumbv8m.main-none-eabihf` | * | ARMv8-M Mainline, hardfloat +`thumbv6m-none-eabi` | * | Bare ARMv6-M +`thumbv7em-none-eabi` | * | Bare ARMv7E-M +`thumbv7em-none-eabihf` | * | Bare ARMV7E-M, hardfloat +`thumbv7m-none-eabi` | * | Bare ARMv7-M +[`thumbv7neon-linux-androideabi`](platform-support/android.md) | ✓ | Thumb2-mode ARMv7-A Android with NEON +`thumbv7neon-unknown-linux-gnueabihf` | ✓ | Thumb2-mode ARMv7-A Linux with NEON (kernel 4.4, glibc 2.23) +`thumbv8m.base-none-eabi` | * | Bare ARMv8-M Baseline +`thumbv8m.main-none-eabi` | * | Bare ARMv8-M Mainline +`thumbv8m.main-none-eabihf` | * | Bare ARMv8-M Mainline, hardfloat +[`sparc-unknown-none-elf`](./platform-support/sparc-unknown-none-elf.md) | * | Bare 32-bit SPARC V7+ `wasm32-unknown-emscripten` | ✓ | WebAssembly via Emscripten `wasm32-unknown-unknown` | ✓ | WebAssembly -`wasm32-wasi` | ✓ | WebAssembly with WASI +`wasm32-unknown-wasi` | ✓ | WebAssembly with WASI +[`wasm32-wasi-preview1-threads`](platform-support/wasm32-wasi-preview1-threads.md) | ✓ | WebAssembly with WASI Preview 1 and threads +`wasm32-wasmer-wasi` | ✓ | WebAssembly with WASIX +`wasm64-wasmer-wasi` | ✓ | WebAssembly with WASIX for 64-bit `x86_64-apple-ios` | ✓ | 64-bit x86 iOS [`x86_64-fortanix-unknown-sgx`](platform-support/x86_64-fortanix-unknown-sgx.md) | ✓ | [Fortanix ABI] for 64-bit Intel SGX `x86_64-fuchsia` | ✓ | Alias for `x86_64-unknown-fuchsia` -`x86_64-unknown-fuchsia` | ✓ | 64-bit Fuchsia +[`x86_64-unknown-fuchsia`](platform-support/fuchsia.md) | ✓ | 64-bit x86 Fuchsia [`x86_64-linux-android`](platform-support/android.md) | ✓ | 64-bit x86 Android `x86_64-pc-solaris` | ✓ | 64-bit Solaris 10/11, illumos `x86_64-unknown-linux-gnux32` | ✓ | 64-bit Linux (x32 ABI) (kernel 4.15, glibc 2.27) @@ -214,59 +218,64 @@ host tools. target | std | host | notes -------|:---:|:----:|------- `aarch64-apple-ios-macabi` | ? | | Apple Catalyst on ARM64 -`aarch64-apple-tvos` | * | | ARM64 tvOS +[`aarch64-apple-tvos`](platform-support/apple-tvos.md) | ? | | ARM64 tvOS [`aarch64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | | ARM64 Apple WatchOS Simulator [`aarch64-kmc-solid_asp3`](platform-support/kmc-solid.md) | ✓ | | ARM64 SOLID with TOPPERS/ASP3 [`aarch64-nintendo-switch-freestanding`](platform-support/aarch64-nintendo-switch-freestanding.md) | * | | ARM64 Nintendo Switch, Horizon [`aarch64-pc-windows-gnullvm`](platform-support/pc-windows-gnullvm.md) | ✓ | ✓ | [`aarch64-unknown-linux-ohos`](platform-support/openharmony.md) | ✓ | | ARM64 OpenHarmony | +[`aarch64-unknown-teeos`](platform-support/aarch64-unknown-teeos.md) | ? | | ARM64 TEEOS | [`aarch64-unknown-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | ARM64 QNX Neutrino 7.1 RTOS | `aarch64-unknown-freebsd` | ✓ | ✓ | ARM64 FreeBSD -`aarch64-unknown-hermit` | ✓ | | ARM64 HermitCore +[`aarch64-unknown-hermit`](platform-support/hermit.md) | ✓ | | ARM64 Hermit `aarch64-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (ILP32 ABI) -`aarch64-unknown-netbsd` | ✓ | ✓ | +[`aarch64-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD [`aarch64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | ARM64 OpenBSD `aarch64-unknown-redox` | ? | | ARM64 Redox OS `aarch64-uwp-windows-msvc` | ? | | `aarch64-wrs-vxworks` | ? | | `aarch64_be-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (big-endian, ILP32 ABI) `aarch64_be-unknown-linux-gnu` | ✓ | ✓ | ARM64 Linux (big-endian) +[`aarch64_be-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD (big-endian) [`arm64_32-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARM Apple WatchOS 64-bit with 32-bit pointers [`armeb-unknown-linux-gnueabi`](platform-support/armeb-unknown-linux-gnueabi.md) | ✓ | ? | ARM BE8 the default ARM big-endian architecture since [ARMv6](https://developer.arm.com/documentation/101754/0616/armlink-Reference/armlink-Command-line-Options/--be8?lang=en). -`armv4t-none-eabi` | * | | ARMv4T A32 -`armv4t-unknown-linux-gnueabi` | ? | | -[`armv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | ARMv5TE A32 +`armv4t-none-eabi` | * | | Bare ARMv4T +`armv4t-unknown-linux-gnueabi` | ? | | ARMv4T Linux +[`armv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | Bare ARMv5TE `armv5te-unknown-linux-uclibceabi` | ? | | ARMv5TE Linux with uClibc `armv6-unknown-freebsd` | ✓ | ✓ | ARMv6 FreeBSD -`armv6-unknown-netbsd-eabihf` | ? | | +[`armv6-unknown-netbsd-eabihf`](platform-support/netbsd.md) | ✓ | ✓ | ARMv6 NetBSD w/hard-float [`armv6k-nintendo-3ds`](platform-support/armv6k-nintendo-3ds.md) | ? | | ARMv6K Nintendo 3DS, Horizon (Requires devkitARM toolchain) -`armv7-apple-ios` | ✓ | | ARMv7 iOS, Cortex-a8 -[`armv7-sony-vita-newlibeabihf`](platform-support/armv7-sony-vita-newlibeabihf.md) | ? | | ARM Cortex-A9 Sony PlayStation Vita (requires VITASDK toolchain) -[`armv7-unknown-linux-ohos`](platform-support/openharmony.md) | ✓ | | ARMv7 OpenHarmony | -[`armv7-unknown-linux-uclibceabi`](platform-support/armv7-unknown-linux-uclibceabi.md) | ✓ | ✓ | ARMv7 Linux with uClibc, softfloat -[`armv7-unknown-linux-uclibceabihf`](platform-support/armv7-unknown-linux-uclibceabihf.md) | ✓ | ? | ARMv7 Linux with uClibc, hardfloat -`armv7-unknown-freebsd` | ✓ | ✓ | ARMv7 FreeBSD -`armv7-unknown-netbsd-eabihf` | ✓ | ✓ | -`armv7-wrs-vxworks-eabihf` | ? | | +`armv7-apple-ios` | ✓ | | ARMv7-A Cortex-A8 iOS +[`armv7-sony-vita-newlibeabihf`](platform-support/armv7-sony-vita-newlibeabihf.md) | ? | | ARMv7-A Cortex-A9 Sony PlayStation Vita (requires VITASDK toolchain) +[`armv7-unknown-linux-ohos`](platform-support/openharmony.md) | ✓ | | ARMv7-A OpenHarmony | +[`armv7-unknown-linux-uclibceabi`](platform-support/armv7-unknown-linux-uclibceabi.md) | ✓ | ✓ | ARMv7-A Linux with uClibc, softfloat +[`armv7-unknown-linux-uclibceabihf`](platform-support/armv7-unknown-linux-uclibceabihf.md) | ✓ | ? | ARMv7-A Linux with uClibc, hardfloat +`armv7-unknown-freebsd` | ✓ | ✓ | ARMv7-A FreeBSD +[`armv7-unknown-netbsd-eabihf`](platform-support/netbsd.md) | ✓ | ✓ | ARMv7-A NetBSD w/hard-float +`armv7-wrs-vxworks-eabihf` | ? | | ARMv7-A for VxWorks [`armv7a-kmc-solid_asp3-eabi`](platform-support/kmc-solid.md) | ✓ | | ARM SOLID with TOPPERS/ASP3 [`armv7a-kmc-solid_asp3-eabihf`](platform-support/kmc-solid.md) | ✓ | | ARM SOLID with TOPPERS/ASP3, hardfloat -`armv7a-none-eabihf` | * | | ARM Cortex-A, hardfloat -[`armv7k-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARM Apple WatchOS -`armv7s-apple-ios` | ✓ | | +`armv7a-none-eabihf` | * | | Bare ARMv7-A, hardfloat +[`armv7k-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | ARMv7-A Apple WatchOS +`armv7s-apple-ios` | ✓ | | ARMv7-A Apple-A6 Apple iOS `avr-unknown-gnu-atmega328` | * | | AVR. Requires `-Z build-std=core` `bpfeb-unknown-none` | * | | BPF (big endian) `bpfel-unknown-none` | * | | BPF (little endian) +`csky-unknown-linux-gnuabiv2` | ✓ | | C-SKY abiv2 Linux(little endian) `hexagon-unknown-linux-musl` | ? | | `i386-apple-ios` | ✓ | | 32-bit x86 iOS [`i586-pc-nto-qnx700`](platform-support/nto-qnx.md) | * | | 32-bit x86 QNX Neutrino 7.0 RTOS | `i686-apple-darwin` | ✓ | ✓ | 32-bit macOS (10.7+, Lion+) `i686-pc-windows-msvc` | * | | 32-bit Windows XP support `i686-unknown-haiku` | ✓ | ✓ | 32-bit Haiku -`i686-unknown-netbsd` | ✓ | ✓ | NetBSD/i386 with SSE2 +[`i686-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | NetBSD/i386 with SSE2 [`i686-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | 32-bit OpenBSD `i686-uwp-windows-gnu` | ? | | `i686-uwp-windows-msvc` | ? | | `i686-wrs-vxworks` | ? | | +[`loongarch64-unknown-none`](platform-support/loongarch-none.md) | * | LoongArch64 Bare-metal (LP64D ABI) +[`loongarch64-unknown-none-softfloat`](platform-support/loongarch-none.md) | * | LoongArch64 Bare-metal (LP64S ABI) [`m68k-unknown-linux-gnu`](platform-support/m68k-unknown-linux-gnu.md) | ? | | Motorola 680x0 Linux `mips-unknown-linux-uclibc` | ✓ | | MIPS Linux with uClibc [`mips64-openwrt-linux-musl`](platform-support/mips64-openwrt-linux-musl.md) | ? | | MIPS64 for OpenWrt Linux MUSL @@ -274,14 +283,14 @@ target | std | host | notes [`mipsel-sony-psx`](platform-support/mipsel-sony-psx.md) | * | | MIPS (LE) Sony PlayStation 1 (PSX) `mipsel-unknown-linux-uclibc` | ✓ | | MIPS (LE) Linux with uClibc `mipsel-unknown-none` | * | | Bare MIPS (LE) softfloat -`mipsisa32r6-unknown-linux-gnu` | ? | | -`mipsisa32r6el-unknown-linux-gnu` | ? | | -`mipsisa64r6-unknown-linux-gnuabi64` | ? | | -`mipsisa64r6el-unknown-linux-gnuabi64` | ? | | +[`mipsisa32r6-unknown-linux-gnu`](platform-support/mips-release-6.md) | ? | | 32-bit MIPS Release 6 Big Endian +[`mipsisa32r6el-unknown-linux-gnu`](platform-support/mips-release-6.md) | ? | | 32-bit MIPS Release 6 Little Endian +[`mipsisa64r6-unknown-linux-gnuabi64`](platform-support/mips-release-6.md) | ? | | 64-bit MIPS Release 6 Big Endian +[`mipsisa64r6el-unknown-linux-gnuabi64`](platform-support/mips-release-6.md) | ✓ | ✓ | 64-bit MIPS Release 6 Little Endian `msp430-none-elf` | * | | 16-bit MSP430 microcontrollers `powerpc-unknown-linux-gnuspe` | ✓ | | PowerPC SPE Linux `powerpc-unknown-linux-musl` | ? | | -`powerpc-unknown-netbsd` | ✓ | ✓ | +[`powerpc-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | NetBSD 32-bit powerpc systems `powerpc-unknown-openbsd` | ? | | `powerpc-wrs-vxworks-spe` | ? | | `powerpc-wrs-vxworks` | ? | | @@ -298,35 +307,41 @@ target | std | host | notes `riscv32im-unknown-none-elf` | * | | Bare RISC-V (RV32IM ISA) [`riscv32imac-unknown-xous-elf`](platform-support/riscv32imac-unknown-xous-elf.md) | ? | | RISC-V Xous (RV32IMAC ISA) [`riscv32imc-esp-espidf`](platform-support/esp-idf.md) | ✓ | | RISC-V ESP-IDF +[`riscv32imac-esp-espidf`](platform-support/esp-idf.md) | ✓ | | RISC-V ESP-IDF +[`riscv64gc-unknown-hermit`](platform-support/hermit.md) | ✓ | | RISC-V Hermit `riscv64gc-unknown-freebsd` | | | RISC-V FreeBSD `riscv64gc-unknown-fuchsia` | | | RISC-V Fuchsia `riscv64gc-unknown-linux-musl` | | | RISC-V Linux (kernel 4.20, musl 1.2.0) +[`riscv64gc-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | RISC-V NetBSD [`riscv64gc-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | OpenBSD/riscv64 +[`riscv64-linux-android`](platform-support/android.md) | | | RISC-V 64-bit Android `s390x-unknown-linux-musl` | | | S390x Linux (kernel 3.2, MUSL) `sparc-unknown-linux-gnu` | ✓ | | 32-bit SPARC Linux -`sparc64-unknown-netbsd` | ✓ | ✓ | NetBSD/sparc64 +[`sparc64-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | NetBSD/sparc64 [`sparc64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | OpenBSD/sparc64 -`thumbv4t-none-eabi` | * | | ARMv4T T32 -[`thumbv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | ARMv5TE T32 +`thumbv4t-none-eabi` | * | | Thumb-mode Bare ARMv4T +[`thumbv5te-none-eabi`](platform-support/armv5te-none-eabi.md) | * | | Thumb-mode Bare ARMv5TE `thumbv7a-pc-windows-msvc` | ? | | `thumbv7a-uwp-windows-msvc` | ✓ | | -`thumbv7neon-unknown-linux-musleabihf` | ? | | Thumb2-mode ARMv7a Linux with NEON, MUSL +`thumbv7neon-unknown-linux-musleabihf` | ? | | Thumb2-mode ARMv7-A Linux with NEON, MUSL [`wasm64-unknown-unknown`](platform-support/wasm64-unknown-unknown.md) | ? | | WebAssembly `x86_64-apple-ios-macabi` | ✓ | | Apple Catalyst on x86_64 -`x86_64-apple-tvos` | * | | x86 64-bit tvOS +[`x86_64-apple-tvos`](platform-support/apple-tvos.md) | ? | | x86 64-bit tvOS [`x86_64-apple-watchos-sim`](platform-support/apple-watchos.md) | ✓ | | x86 64-bit Apple WatchOS simulator [`x86_64-pc-nto-qnx710`](platform-support/nto-qnx.md) | ✓ | | x86 64-bit QNX Neutrino 7.1 RTOS | [`x86_64-pc-windows-gnullvm`](platform-support/pc-windows-gnullvm.md) | ✓ | ✓ | `x86_64-pc-windows-msvc` | * | | 64-bit Windows XP support `x86_64-sun-solaris` | ? | | Deprecated target for 64-bit Solaris 10/11, illumos +[`x86_64-unikraft-linux-musl`](platform-support/unikraft-linux-musl.md) | ✓ | | 64-bit Unikraft with musl `x86_64-unknown-dragonfly` | ✓ | ✓ | 64-bit DragonFlyBSD `x86_64-unknown-haiku` | ✓ | ✓ | 64-bit Haiku -`x86_64-unknown-hermit` | ✓ | | HermitCore +[`x86_64-unknown-hermit`](platform-support/hermit.md) | ✓ | | x86_64 Hermit `x86_64-unknown-l4re-uclibc` | ? | | +[`x86_64-unknown-linux-ohos`](platform-support/openharmony.md) | ✓ | | x86_64 OpenHarmony | [`x86_64-unknown-openbsd`](platform-support/openbsd.md) | ✓ | ✓ | 64-bit OpenBSD `x86_64-uwp-windows-gnu` | ✓ | | `x86_64-uwp-windows-msvc` | ✓ | | `x86_64-wrs-vxworks` | ? | | -`x86_64h-apple-darwin` | ✓ | ✓ | macOS with late-gen Intel (at least Haswell) +[`x86_64h-apple-darwin`](platform-support/x86_64h-apple-darwin.md) | ✓ | ✓ | macOS with late-gen Intel (at least Haswell) [runs on NVIDIA GPUs]: https://github.com/japaric-archived/nvptx#targets diff --git a/src/doc/rustc/src/platform-support/aarch64-unknown-teeos.md b/src/doc/rustc/src/platform-support/aarch64-unknown-teeos.md new file mode 100644 index 0000000000000..8bc9381342dd6 --- /dev/null +++ b/src/doc/rustc/src/platform-support/aarch64-unknown-teeos.md @@ -0,0 +1,100 @@ +# `aarch64-unknown-teeos` + +**Tier: 3** + +Target for the TEEOS operating system. + +TEEOS is a mini os run in TrustZone, for trusted/security apps. The kernel of TEEOS is HongMeng/ChCore micro kernel. The libc for TEEOS is a part of musl. +It's very small that there is no RwLock, no network, no stdin, and no file system for apps in TEEOS. + +Some abbreviation: +| Abbreviation | The full text | Description | +| ---- | ---- | ---- | +| TEE | Trusted Execution Environment | ARM TrustZone divides the system into two worlds/modes -- the secure world/mode and the normal world/mode. TEE is in the secure world/mode. | +| REE | Rich Execution Environment | The normal world. for example, Linux for Android phone is in REE side. | +| TA | Trusted Application | The app run in TEE side system. | +| CA | Client Application | The progress run in REE side system. | + +TEEOS is open source in progress. [MORE about](https://gitee.com/opentrustee-group) + +## Target maintainers + +- Petrochenkov Vadim +- Sword-Destiny + +## Setup +We use OpenHarmony SDK for TEEOS. + +The OpenHarmony SDK doesn't currently support Rust compilation directly, so +some setup is required. + +First, you must obtain the OpenHarmony SDK from [this page](https://gitee.com/openharmony/docs/tree/master/en/release-notes). +Select the version of OpenHarmony you are developing for and download the "Public SDK package for the standard system". + +Create the following shell scripts that wrap Clang from the OpenHarmony SDK: + +`aarch64-unknown-teeos-clang.sh` + +```sh +#!/bin/sh +exec /path/to/ohos-sdk/linux/native/llvm/bin/clang \ + --target aarch64-linux-gnu \ + "$@" +``` + +`aarch64-unknown-teeos-clang++.sh` + +```sh +#!/bin/sh +exec /path/to/ohos-sdk/linux/native/llvm/bin/clang++ \ + --target aarch64-linux-gnu \ + "$@" +``` + +## Building the target + +To build a rust toolchain, create a `config.toml` with the following contents: + +```toml +profile = "compiler" +changelog-seen = 2 + +[build] +sanitizers = true +profiler = true +target = ["x86_64-unknown-linux-gnu", "aarch64-unknown-teeos"] +submodules = false +compiler-docs = false +extended = true + +[install] +bindir = "bin" +libdir = "lib" + +[target.aarch64-unknown-teeos] +cc = "/path/to/scripts/aarch64-unknown-teeos-clang.sh" +cxx = "/path/to/scripts/aarch64-unknown-teeos-clang.sh" +linker = "/path/to/scripts/aarch64-unknown-teeos-clang.sh" +ar = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ar" +ranlib = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ranlib" +llvm-config = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-config" +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will either need to build Rust with the target enabled (see +"Building the target" above), or build your own copy of `core` by using +`build-std` or similar. + +You will need to configure the linker to use in `~/.cargo/config`: +```toml +[target.aarch64-unknown-teeos] +linker = "/path/to/aarch64-unknown-teeos-clang.sh" +``` + +## Testing + +Running the Rust testsuite is not possible now. + +More information about how to test CA/TA. [See here](https://gitee.com/openharmony-sig/tee_tee_dev_kit/tree/master/docs) diff --git a/src/doc/rustc/src/platform-support/android.md b/src/doc/rustc/src/platform-support/android.md index e351cfaf89c2c..4ef74295e0fdf 100644 --- a/src/doc/rustc/src/platform-support/android.md +++ b/src/doc/rustc/src/platform-support/android.md @@ -39,6 +39,8 @@ edition of the [Android NDK]. Supported Android targets are: * thumbv7neon-linux-androideabi * x86_64-linux-android +The riscv64-linux-android target is supported as a Tier 3 target. + [Android NDK]: https://developer.android.com/ndk/downloads A list of all supported targets can be found diff --git a/src/doc/rustc/src/platform-support/apple-tvos.md b/src/doc/rustc/src/platform-support/apple-tvos.md new file mode 100644 index 0000000000000..d87fd1959b49c --- /dev/null +++ b/src/doc/rustc/src/platform-support/apple-tvos.md @@ -0,0 +1,85 @@ +# `*-apple-tvos` +- aarch64-apple-tvos +- x86_64-apple-tvos + +**Tier: 3** + +Apple tvOS targets: +- Apple tvOS on aarch64 +- Apple tvOS Simulator on x86_64 + +## Target maintainers + +* [@thomcc](https://github.com/thomcc) + +## Requirements + +These targets are cross-compiled. You will need appropriate versions of Xcode +and the SDKs for tvOS (`AppleTVOS.sdk`) and/or the tvOS Simulator +(`AppleTVSimulator.sdk`) to build a toolchain and target these platforms. + +The targets support most (see below) of the standard library including the +allocator to the best of my knowledge, however they are very new, not yet +well-tested, and it is possible that there are various bugs. + +In theory we support back to tvOS version 7.0, although the actual minimum +version you can target may be newer than this, for example due to the versions +of Xcode and your SDKs. + +As with the other Apple targets, `rustc` respects the common environment +variables used by Xcode to configure this, in this case +`TVOS_DEPLOYMENT_TARGET`. + +#### Incompletely supported library functionality + +As mentioned, "most" of the standard library is supported, which means that some portions +are known to be unsupported. The following APIs are currently known to have +missing or incomplete support: + +- `std::process::Command`'s API will return an error if it is configured in a + manner which cannot be performed using `posix_spawn` -- this is because the + more flexible `fork`/`exec`-based approach is prohibited on these platforms in + favor of `posix_spawn{,p}` (which still probably will get you rejected from + app stores, so is likely sideloading-only). A concrete set of cases where this + will occur is difficult to enumerate (and would quickly become stale), but in + some cases it may be worked around by tweaking the manner in which `Command` + is invoked. + +## Building the target + +The targets can be built by enabling them for a `rustc` build in `config.toml`, by adding, for example: + +```toml +[build] +build-stage = 1 +target = ["aarch64-apple-tvos", "x86_64-apple-tvos"] +``` + +It's possible that cargo under `-Zbuild-std` may also be used to target them. + +## Building Rust programs + +*Note: Building for this target requires the corresponding TVOS SDK, as provided by Xcode.* + +Rust programs can be built for these targets + +```text +$ rustc --target aarch64-apple-tvos your-code.rs +... +$ rustc --target x86_64-apple-tvos your-code.rs +``` + +## Testing + +There is no support for running the Rust or standard library testsuite on tvOS +or the simulators at the moment. Testing has mostly been done manually with +builds of static libraries called from Xcode or a simulator. + +It hopefully will be possible to improve this in the future. + +## Cross-compilation toolchains and C code + +This target can be cross-compiled from x86_64 or aarch64 macOS hosts. + +Other hosts are not supported for cross-compilation, but might work when also +providing the required Xcode SDK. diff --git a/src/doc/rustc/src/platform-support/armv7-sony-vita-newlibeabihf.md b/src/doc/rustc/src/platform-support/armv7-sony-vita-newlibeabihf.md index d75bd92beda73..49eed366dacf0 100644 --- a/src/doc/rustc/src/platform-support/armv7-sony-vita-newlibeabihf.md +++ b/src/doc/rustc/src/platform-support/armv7-sony-vita-newlibeabihf.md @@ -7,7 +7,7 @@ This tier supports the ARM Cortex A9 processor running on a PlayStation Vita con Rust support for this target is not affiliated with Sony, and is not derived from nor used with any official Sony SDK. -## Designated Developers +## Target maintainers * [@amg98](https://github.com/amg98) * [@nikarh](https://github.com/nikarh) @@ -27,9 +27,9 @@ In order to support some APIs, binaries must be linked against `libc` written for the target, using a linker for the target. These are provided by the VITASDK toolchain. -This target generates binaries in the ELF format. +This target generates binaries in the ELF format with thumb ISA. -## Building +## Building the target Rust does not ship pre-compiled artifacts for this target. You can use `build-std` flag to build binaries with `std`: @@ -37,43 +37,9 @@ Rust does not ship pre-compiled artifacts for this target. You can use `build-st cargo build -Z build-std=std,panic_abort --target=armv7-sony-vita-newlibeabihf --release ``` -## Cross-compilation - -This target can be cross-compiled from `x86_64` on either Windows, MacOS or Linux systems. Other hosts are not supported for cross-compilation. - -## Testing - -Currently there is no support to run the rustc test suite for this target. - -## Building and Running Rust Programs +## Building Rust programs -`std` support for this target relies on newlib. In order to work, newlib must be initialized correctly. The easiest way to achieve this with VITASDK newlib implementation is by compiling your program as a staticlib with and exposing your main function from rust to `_init` function in `crt0`. - -Add this to your `Cargo.toml`: - -```toml -[lib] -crate-type = ["staticlib"] - -[profile.release] -panic = 'abort' -lto = true -opt-level = 3 -``` - -Your entrypoint should look roughly like this, `src/lib.rs`: -```rust,ignore,no_run -#[used] -#[export_name = "_newlib_heap_size_user"] -pub static _NEWLIB_HEAP_SIZE_USER: u32 = 100 * 1024 * 1024; // Default heap size is only 32mb, increase it to something suitable for your application - -#[no_mangle] -pub extern "C" fn main() { - println!("Hello, world!"); -} -``` - -To test your developed rust programs on PlayStation Vita, first you must correctly link and package your rust staticlib. These steps can be preformed using tools available in VITASDK, and can be automated using tools like `cargo-make`. +To test your developed rust programs on PlayStation Vita, first you must correctly package your elf. These steps can be preformed using tools available in VITASDK, and can be automated using a tool like `cargo-make`. First, set up environment variables for `VITASDK`, and it's binaries: @@ -88,40 +54,21 @@ Use the example below as a template for your project: [env] TITLE = "Rust Hello World" TITLEID = "RUST00001" -# Add other libs required by your project here -LINKER_LIBS = "-lpthread -lm -lmathneon" # At least a "sce_sys" folder should be place there for app metadata (title, icons, description...) # You can find sample assets for that on $VITASDK/share/gcc-arm-vita-eabi/samples/hello_world/sce_sys/ STATIC_DIR = "static" # Folder where static assets should be placed (sce_sys folder is at $STATIC_DIR/sce_sys) CARGO_TARGET_DIR = { script = ["echo ${CARGO_TARGET_DIR:=target}"] } -RUST_TARGET = "armv7-sony-vita-newlibeabihf" CARGO_OUT_DIR = "${CARGO_TARGET_DIR}/${RUST_TARGET}/release" -TARGET_LINKER = "arm-vita-eabi-gcc" -TARGET_LINKER_FLAGS = "-Wl,-q" - [tasks.build] -description = "Build the project using `cargo` as a static lib." +description = "Build the project using `cargo`." command = "cargo" args = ["build", "-Z", "build-std=std,panic_abort", "--target=armv7-sony-vita-newlibeabihf", "--release"] -[tasks.link] -description = "Build an ELF executable using the `vitasdk` linker." -dependencies = ["build"] -script = [ - """ - ${TARGET_LINKER} ${TARGET_LINKER_FLAGS} \ - -L"${CARGO_OUT_DIR}" \ - -l"${CARGO_MAKE_CRATE_FS_NAME}" \ - ${LINKER_LIBS} \ - -o"${CARGO_OUT_DIR}/${CARGO_MAKE_CRATE_NAME}.elf" - """ -] - [tasks.strip] description = "Strip the produced ELF executable." -dependencies = ["link"] +dependencies = ["build"] command = "arm-vita-eabi-strip" args = ["-g", '${CARGO_OUT_DIR}/${CARGO_MAKE_CRATE_FS_NAME}.elf'] @@ -194,3 +141,11 @@ script = [ ``` After running the above script, you should be able to get a *.vpk file in the same folder your *.elf executable resides. Now you can pick it and install it on your own PlayStation Vita using, or you can use an [Vita3K](https://vita3k.org/) emulator. + +## Testing + +Currently there is no support to run the rustc test suite for this target. + +## Cross-compilation + +This target can be cross-compiled from `x86_64` on either Windows, MacOS or Linux systems. Other hosts are not supported for cross-compilation. diff --git a/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md b/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md new file mode 100644 index 0000000000000..e73598be0d9d7 --- /dev/null +++ b/src/doc/rustc/src/platform-support/csky-unknown-linux-gnuabiv2.md @@ -0,0 +1,70 @@ +# `csky-unknown-linux-gnuabiv2` + +**Tier: 3** + +This target supports [C-SKY](https://github.com/c-sky) CPUs with `abi` v2 and `glibc`. + +https://c-sky.github.io/ +https://gitlab.com/c-sky/ + +## Target maintainers + +* [@Dirreke](https://github.com/Dirreke) + +## Requirements + + +## Building the target + +### Get a C toolchain + +Compiling rust for this target has been tested on `x86_64` linux hosts. Other host types have not been tested, but may work, if you can find a suitable cross compilation toolchain for them. + +If you don't already have a suitable toolchain, you can download from [here](https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource/1356021/1619528643136/csky-linux-gnuabiv2-tools-x86_64-glibc-linux-4.9.56-20210423.tar.gz), and unpack it into a directory. + +### Configure rust + +The target can be built by enabling it for a `rustc` build, by placing the following in `config.toml`: + +```toml +[build] +target = ["x86_64-unknown-linux-gnu", "csky-unknown-linux-gnuabiv2"] +stage = 2 + +[target.csky-unknown-linux-gnuabiv2] +# ADJUST THIS PATH TO POINT AT YOUR TOOLCHAIN +cc = "${TOOLCHAIN_PATH}/bin/csky-linux-gnuabiv2-gcc" + +### Build + +```sh +# in rust dir +./x.py build --stage 2 +``` + +## Building and Running Rust programs + +To test cross-compiled binaries on a `x86_64` system, you can use the `qemu-cskyv2`. This avoids having a full emulated ARM system by doing dynamic binary translation and dynamic system call translation. It lets you run CSKY programs directly on your `x86_64` kernel. It's very convenient! + +To use: + +* Install `qemu-cskyv2` (If you don't already have a qemu, you can download from [here](https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1689324918932/xuantie-qemu-x86_64-Ubuntu-18.04-20230714-0202.tar.gz"), and unpack it into a directory.) +* Link your built toolchain via: + * `rustup toolchain link stage2 ${RUST}/build/x86_64-unknown-linux-gnu/stage2` +* Create a test program + +```sh +cargo new hello_world +cd hello_world +``` + +* Build and run + +```sh +CARGO_TARGET_CSKY_UNKNOWN_LINUX_GNUABIV2_RUNNER=${QEMU_PATH}/bin/qemu-cskyv2 -L ${TOOLCHAIN_PATH}/csky-linux-gnuabiv2/libc \ +CARGO_TARGET_CSKY_UNKNOWN_LINUX_GNUABIV2_LINKER=${TOOLCHAIN_PATH}/bin/csky-linux-gnuabiv2-gcc \ +RUSTFLAGS="-C target-features=+crt-static" \ +cargo +stage2 run --target csky-unknown-linux-gnuabiv2 +``` + +Attention: The dynamic-linked program may nor be run by `qemu-cskyv2` but can be run on the target. diff --git a/src/doc/rustc/src/platform-support/esp-idf.md b/src/doc/rustc/src/platform-support/esp-idf.md index 8a4ca347e22f5..8f630fa152c49 100644 --- a/src/doc/rustc/src/platform-support/esp-idf.md +++ b/src/doc/rustc/src/platform-support/esp-idf.md @@ -13,11 +13,14 @@ Targets for the [ESP-IDF](https://github.com/espressif/esp-idf) development fram The target names follow this format: `$ARCH-esp-espidf`, where `$ARCH` specifies the target processor architecture. The following targets are currently defined: -| Target name | Target CPU(s) | -|--------------------------------|-----------------------| -| `riscv32imc-esp-espidf` | [ESP32-C3](https://www.espressif.com/en/products/socs/esp32-c3) | - -The minimum supported ESP-IDF version is `v4.3`, though it is recommended to use the latest stable release if possible. +| Target name | Target CPU(s) | Minimum ESP-IDF version | +| ------------------------ | --------------------------------------------------------------- | ----------------------- | +| `riscv32imc-esp-espidf` | [ESP32-C2](https://www.espressif.com/en/products/socs/esp32-c2) | `v5.0` | +| `riscv32imc-esp-espidf` | [ESP32-C3](https://www.espressif.com/en/products/socs/esp32-c3) | `v4.3` | +| `riscv32imac-esp-espidf` | [ESP32-C6](https://www.espressif.com/en/products/socs/esp32-c6) | `v5.1` | +| `riscv32imac-esp-espidf` | [ESP32-H2](https://www.espressif.com/en/products/socs/esp32-h2) | `v5.1` | + +It is recommended to use the latest ESP-IDF stable release if possible. ## Building the target diff --git a/src/doc/rustc/src/platform-support/fuchsia.md b/src/doc/rustc/src/platform-support/fuchsia.md index 4d97b8c6cb90b..f7cce35b1232b 100644 --- a/src/doc/rustc/src/platform-support/fuchsia.md +++ b/src/doc/rustc/src/platform-support/fuchsia.md @@ -681,12 +681,9 @@ local Rust source checkout: cd ${RUST_SRC_PATH} ``` -To run the Rust test suite on an emulated Fuchsia device, you must install the -Rust compiler locally. See "[Targeting Fuchsia with a compiler built from source](#targeting-fuchsia-with-a-compiler-built-from-source)" -for the steps to build locally. - -You'll also need to download a copy of the Fuchsia SDK. The current minimum -supported SDK version is [10.20221207.2.89][minimum_supported_sdk_version]. +To run the Rust test suite on an emulated Fuchsia device, you'll also need to +download a copy of the Fuchsia SDK. The current minimum supported SDK version is +[10.20221207.2.89][minimum_supported_sdk_version]. [minimum_supported_sdk_version]: https://chrome-infra-packages.appspot.com/p/fuchsia/sdk/core/linux-amd64/+/version:10.20221207.2.89 @@ -695,13 +692,13 @@ Fuchsia's test runner interacts with the Fuchsia emulator and is located at test environment with: ```sh -src/ci/docker/scripts/fuchsia-test-runner.py start - --rust ${RUST_SRC_PATH}/install - --sdk ${SDK_PATH} - --target {x86_64-unknown-fuchsia|aarch64-unknown-fuchsia} +src/ci/docker/scripts/fuchsia-test-runner.py start \ + --rust-build ${RUST_SRC_PATH}/build \ + --sdk ${SDK_PATH} \ + --target {x86_64-unknown-fuchsia|aarch64-unknown-fuchsia} \ ``` -Where `${RUST_SRC_PATH}/install` is the `prefix` set in `config.toml` and +Where `${RUST_SRC_PATH}/build` is the `build-dir` set in `config.toml` and `${SDK_PATH}` is the path to the downloaded and unzipped SDK. Once our environment is started, we can run our tests using `x.py` as usual. The diff --git a/src/doc/rustc/src/platform-support/hermit.md b/src/doc/rustc/src/platform-support/hermit.md new file mode 100644 index 0000000000000..146079e36f44c --- /dev/null +++ b/src/doc/rustc/src/platform-support/hermit.md @@ -0,0 +1,75 @@ +# `*-unknown-hermit` + +**Tier: 3** + +The [Hermit] unikernel target allows compiling your applications into self-contained, specialized unikernel images that can be run in small virtual machines. + +[Hermit]: https://github.com/hermitcore + +Target triplets available so far: + +- `x86_64-unknown-hermit` +- `aarch64-unknown-hermit` +- `riscv64gc-unknown-hermit` + +## Target maintainers + +- Stefan Lankes ([@stlankes](https://github.com/stlankes)) +- Martin Kröning ([@mkroening](https://github.com/mkroening)) + +## Requirements + +These targets only support cross-compilation. +The targets do support std. + +When building binaries for this target, the Hermit unikernel is built from scratch. +The application developer themselves specializes the target and sets corresponding expectations. + +The Hermit targets follow Linux's `extern "C"` calling convention. + +Hermit binaries have the ELF format. + +## Building the target + +You can build Rust with support for the targets by adding it to the `target` list in `config.toml`. +To run the Hermit build scripts, you also have to enable your host target. +The build scripts rely on `llvm-tools` and binaries are linked using `rust-lld`, so those have to be enabled as well. + +```toml +[build] +build-stage = 1 +target = [ + "", + "x86_64-unknown-hermit", + "aarch64-unknown-hermit", + "riscv64gc-unknown-hermit", +] + +[rust] +lld = true +llvm-tools = true +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for these targets. +To compile for these targets, you will either need to build Rust with the targets enabled +(see “Building the targets” above), or build your own copy of `core` by using `build-std` or similar. + +Building Rust programs can be done by following the tutorial in our starter application [rusty-demo]. + +[rusty-demo]: https://github.com/hermitcore/rusty-demo + +## Testing + +The targets support running binaries in the form of self-contained unikernel images. +These images can be chainloaded by Hermit's [loader] or hypervisor ([Uhyve]). +QEMU can be used to boot Hermit binaries using the loader on any architecture. +The targets do not support running the Rust test suite. + +[loader]: https://github.com/hermitcore/rusty-loader +[Uhyve]: https://github.com/hermitcore/uhyve + +## Cross-compilation toolchains and C code + +The targets do not yet support C code and Rust code at the same time. diff --git a/src/doc/rustc/src/platform-support/loongarch-linux.md b/src/doc/rustc/src/platform-support/loongarch-linux.md index 999e71f80285a..e8f55b8bfce10 100644 --- a/src/doc/rustc/src/platform-support/loongarch-linux.md +++ b/src/doc/rustc/src/platform-support/loongarch-linux.md @@ -28,9 +28,9 @@ While the integer base ABI is implied by the machine field, the floating po ## Target maintainers -- [ZHAI Xiaojuan](https://github.com/zhaixiaojuan) `zhaixiaojuan@loongson.cn` - [WANG Rui](https://github.com/heiher) `wangrui@loongson.cn` - [ZHAI Xiang](https://github.com/xiangzhai) `zhaixiang@loongson.cn` +- [ZHAI Xiaojuan](https://github.com/zhaixiaojuan) `zhaixiaojuan@loongson.cn` - [WANG Xuerui](https://github.com/xen0n) `git@xen0n.name` ## Requirements @@ -71,7 +71,7 @@ CXX_loongarch64_unknown_linux_gnu=/TOOLCHAIN_PATH/bin/loongarch64-unknown-linux- AR_loongarch64_unknown_linux_gnu=/TOOLCHAIN_PATH/bin/loongarch64-unknown-linux-gnu-gcc-ar \ CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNUN_LINKER=/TOOLCHAIN_PATH/bin/loongarch64-unknown-linux-gnu-gcc \ # SET TARGET SYSTEM LIBRARY PATH -CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNUN_RUNNER="qemu-loongarch64 -L /TOOLCHAIN_PATH/TARGET_LIBRAY_PATH" \ +CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNUN_RUNNER="qemu-loongarch64 -L /TOOLCHAIN_PATH/TARGET_LIBRARY_PATH" \ cargo run --target loongarch64-unknown-linux-gnu --release ``` Tested on x86 architecture, other architectures not tested. diff --git a/src/doc/rustc/src/platform-support/loongarch-none.md b/src/doc/rustc/src/platform-support/loongarch-none.md new file mode 100644 index 0000000000000..d0ae3425fa8eb --- /dev/null +++ b/src/doc/rustc/src/platform-support/loongarch-none.md @@ -0,0 +1,79 @@ +# `loongarch*-unknown-none*` + +**Tier: 3** + +Freestanding/bare-metal LoongArch64 binaries in ELF format: firmware, kernels, etc. + +| Target | Descriptions | +|------------------------------------|-------------------------------------------------------| +| loongarch64-unknown-none | LoongArch 64-bit, LP64D ABI (freestanding, hardfloat) | +| loongarch64-unknown-none-softfloat | LoongArch 64-bit, LP64S ABI (freestanding, softfloat) | + +## Target maintainers + +- [WANG Rui](https://github.com/heiher) `wangrui@loongson.cn` +- [WANG Xuerui](https://github.com/xen0n) `git@xen0n.name` + +## Requirements + +This target is cross-compiled. There is no support for `std`. There is no +default allocator, but it's possible to use `alloc` by supplying an allocator. + +This allows the generated code to run in environments, such as kernels, which +may need to avoid the use of such registers or which may have special considerations +about the use of such registers (e.g. saving and restoring them to avoid breaking +userspace code using the same registers). You can change code generation to use +additional CPU features via the `-C target-feature=` codegen options to rustc, or +via the `#[target_feature]` mechanism within Rust code. + +By default, code generated with this target should run on any `loongarch` +hardware; enabling additional target features may raise this baseline. + +Code generated with this target will use the `small` code model by default. +You can change this using the `-C code-model=` option to rustc. + +On `loongarch64-unknown-none*`, `extern "C"` uses the [standard calling +convention](https://loongson.github.io/LoongArch-Documentation/LoongArch-ELF-ABI-EN.html). + +This target generates binaries in the ELF format. Any alternate formats or +special considerations for binary layout will require linker options or linker +scripts. + +## Building the target + +You can build Rust with support for the target by adding it to the `target` +list in `config.toml`: + +```toml +[build] +build-stage = 1 +target = ["loongarch64-unknown-none"] +``` + +## Building Rust programs + +```text +# target flag may be used with any cargo or rustc command +cargo build --target loongarch64-unknown-none +``` + +## Testing + +As `loongarch64-unknown-none*` supports a variety of different environments and does +not support `std`, this target does not support running the Rust test suite. + +## Cross-compilation toolchains and C code + +If you want to compile C code along with Rust (such as for Rust crates with C +dependencies), you will need an appropriate `loongarch` toolchain. + +Rust *may* be able to use an `loongarch64-unknown-linux-gnu-` toolchain with +appropriate standalone flags to build for this toolchain (depending on the assumptions +of that toolchain, see below), or you may wish to use a separate +`loongarch64-unknown-none` toolchain. + +On some `loongarch` hosts that use ELF binaries, you *may* be able to use the host +C toolchain, if it does not introduce assumptions about the host environment +that don't match the expectations of a standalone environment. Otherwise, you +may need a separate toolchain for standalone/freestanding development, just as +when cross-compiling from a non-`loongarch` platform. diff --git a/src/doc/rustc/src/platform-support/mips-release-6.md b/src/doc/rustc/src/platform-support/mips-release-6.md new file mode 100644 index 0000000000000..3f1912fc6f951 --- /dev/null +++ b/src/doc/rustc/src/platform-support/mips-release-6.md @@ -0,0 +1,181 @@ +# mipsisa\*r6\*-unknown-linux-gnu\* + +**Tier: 3** + +[MIPS Release 6](https://s3-eu-west-1.amazonaws.com/downloads-mips/documents/MD00083-2B-MIPS64INT-AFP-06.01.pdf), or simply MIPS R6, is the latest iteration of the MIPS instruction set architecture (ISA). + +MIPS R6 is experimental in nature, as there is not yet real hardware. However, Qemu emulation is available and we have two Linux distros maintained for development and evaluation purposes. This documentation describes the Rust support for MIPS R6 targets under `mipsisa*r6*-unknown-linux-gnu*`. + +The target name follow this format: `--`, where `` specifies the CPU family/model, `` specifies the vendor and `` the operating system name. The `` denotes the base ABI (32/n32/64/o64). + +| ABI suffix | Description | +|------------|------------------------------------| +| abi64 | Uses the 64-bit (64) ABI | +| abin32 | Uses the n32 ABI | +| N/A | Uses the (assumed) 32-bit (32) ABI | + +## Target Maintainers + +- [Xuan Chen](https://github.com/chenx97) +- [Walter Ji](https://github.com/709924470) +- [Xinhui Yang](https://github.com/Cyanoxygen) +- [Lain Yang](https://github.com/Fearyncess) + +## Requirements + +### C/C++ Toolchain + +A GNU toolchain for one of the MIPS R6 target is required. [AOSC OS](https://aosc.io/) provides working native and cross-compiling build environments. You may also supply your own a toolchain consisting of recent versions of GCC and Binutils. + +### Target libraries + +A minimum set of libraries is required to perform dynamic linking: + +- GNU glibc +- OpenSSL +- Zlib +- Linux API Headers + +This set of libraries should be installed to make up minimal target sysroot. + +For AOSC OS, You may install such a sysroot with the following commands: + +```sh +cd /tmp + +# linux+api, glibc, and file system structure are included in the toolchain. +sudo apt install gcc+cross-mips64r6el binutils+cross-mips64r6el + +# Download and extract required libraries. +wget https://repo.aosc.io/debs/pool/stable/main/z/zlib_1.2.13-0_mips64r6el.deb -O zlib.deb +wget https://repo.aosc.io/debs/pool/stable/main/o/openssl_1.1.1q-1_mips64r6el.deb -O openssl.deb + +# Extract them to your desired location. +for i in zlib openssl ; do + sudo dpkg-deb -vx $i.deb /var/ab/cross-root/mips64r6el +done + +# Workaround a possible ld bug when using -Wl,-Bdynamic. +sudo sed -i 's|/usr|=/usr|g' /var/ab/cross-root/mips64r6el/usr/lib/libc.so +``` + +For other distros, you may build them manually. + +## Building + +The following procedure outlines the build process for the MIPS64 R6 target with 64-bit (64) ABI (`mipsisa64r6el-unknown-linux-gnuabi64`). + +### Prerequisite: Disable debuginfo + +A LLVM bug makes rustc crash if debug or debug info generation is enabled. You need to edit `config.toml` to disable this: + +```toml +[rust] +debug = false +debug-info-level = 0 +``` + +### Prerequisite: Enable rustix's libc backend + +The crate `rustix` may try to link itself against MIPS R2 assembly, resulting in linkage error. To avoid this, you may force `rustix` to use its fallback `libc` backend by setting relevant `RUSTFLAGS`: + +```sh +export RUSTFLAGS="--cfg rustix_use_libc" +``` + +This will trigger warnings during build, as `-D warnings` is enabled by default. Disable `-D warnings` by editing `config.toml` to append the following: + +```toml +[rust] +deny-warnings = false +``` + +### Prerequisite: Supplying OpenSSL + +As a Tier 3 target, `openssl_sys` lacks the vendored OpenSSL library for this target. You will need to provide a prebuilt OpenSSL library to link `cargo`. Since we have a pre-configured sysroot, we can point to it directly: + +```sh +export MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_OPENSSL_NO_VENDOR=y +export MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_OPENSSL_DIR="/var/ab/cross-root/mips64r6el/usr" +``` + +On Debian, you may need to provide library path and include path separately: + +```sh +export MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_OPENSSL_NO_VENDOR=y +export MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_OPENSSL_LIB_DIR="/usr/lib/mipsisa64r6el-linux-gnuabi64/" +export MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_OPENSSL_INCLUDE_DIR="/usr/include" +``` + +### Launching `x.py` + +```toml +[build] +target = ["mipsisa64r6el-unknown-linux-gnuabi64"] +``` + +Make sure that `mipsisa64r6el-unknown-linux-gnuabi64-gcc` is available from your executable search path (`$PATH`). + +Alternatively, you can specify the directories to all necessary toolchain executables in `config.toml`: + +```toml +[target.mipsisa64r6el-unknown-linux-gnuabi64] +# Adjust the paths below to point to your toolchain installation prefix. +cc = "/toolchain_prefix/bin/mipsisa64r6el-unknown-linux-gnuabi64-gcc" +cxx = "/toolchain_prefix/bin/mipsisa64r6el-unknown-linux-gnuabi64-g++" +ar = "/toolchain_prefix/bin/mipsisa64r6el-unknown-linux-gnuabi64-gcc-ar" +ranlib = "/toolchain_prefix/bin/mipsisa64r6el-unknown-linux-gnuabi64-ranlib" +linker = "/toolchain_prefix/bin/mipsisa64r6el-unknown-linux-gnuabi64-gcc" +``` + +Or, you can specify your cross compiler toolchain with an environment variable: + +```sh +export CROSS_COMPILE="/opt/abcross/mips64r6el/bin/mipsisa64r6el-aosc-linux-gnuabi64-" +``` + +Finally, launch the build script: + +```sh +./x.py build +``` + +### Tips + +- Avoid setting `cargo-native-static` to `false`, as this will result in a redundant artifact error while building clippy: + ```text + duplicate artifacts found when compiling a tool, this typically means that something was recompiled because a transitive dependency has different features activated than in a previous build: + + the following dependencies have different features: + syn 2.0.8 (registry+https://github.com/rust-lang/crates.io-index) + `clippy-driver` additionally enabled features {"full"} at ... + `cargo` additionally enabled features {} at ... + + to fix this you will probably want to edit the local src/tools/rustc-workspace-hack/Cargo.toml crate, as that will update the dependency graph to ensure that these crates all share the same feature set + thread 'main' panicked at 'tools should not compile multiple copies of the same crate', tool.rs:250:13 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + ``` + +## Building Rust programs + +To build Rust programs for MIPS R6 targets, for instance, the `mipsisa64r6el-unknown-linux-gnuabi64` target: + +```bash +cargo build --target mipsisa64r6el-unknown-linux-gnuabi64 +``` + +## Testing + +To test a cross-compiled binary on your build system, install the Qemu user emulator that support the MIPS R6 architecture (`qemu-user-mipsel` or `qemu-user-mips64el`). GCC runtime libraries (`libgcc_s`) for the target architecture should be present in target sysroot to run the program. + +```sh +env \ + CARGO_TARGET_MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_LINKER="/opt/abcross/mips64r6el/bin/mipsisa64r6el-aosc-linux-gnuabi64-gcc" \ + CARGO_TARGET_MIPSISA64R6EL_UNKNOWN_LINUX_GNUABI64_RUNNER="qemu-mips64el-static -L /var/ab/cross-root/mips64r6el" \ + cargo run --release \ + --target mipsisa64r6el-unknown-linux-gnuabi64 +``` + +## Tips for building Rust programs for MIPS R6 + +- Until we finalize a fix, please make sure the aforementioned workarounds for `rustix` crate and LLVM are always applied. This can be achieved by setting the relevant environment variables, and editing `Cargo.toml` before building. diff --git a/src/doc/rustc/src/platform-support/netbsd.md b/src/doc/rustc/src/platform-support/netbsd.md new file mode 100644 index 0000000000000..3891d6d3148da --- /dev/null +++ b/src/doc/rustc/src/platform-support/netbsd.md @@ -0,0 +1,108 @@ +# \*-unknown-netbsd + +**Tier: 3** + +[NetBSD] multi-platform 4.4BSD-based UNIX-like operating system. + +[NetBSD]: https://www.NetBSD.org/ + +The target names follow this format: `$ARCH-unknown-netbsd{-$SUFFIX}`, +where `$ARCH` specifies the target processor architecture and +`-$SUFFIX` (optional) might indicate the ABI. The following targets +are currently defined running NetBSD: + +| Target name | NetBSD Platform | +|--------------------------------|-----------------| +| `amd64-unknown-netbsd` | [amd64 / x86_64 systems](https://wiki.netbsd.org/ports/amd64/) | +| `armv7-unknown-netbsd-eabihf` | [32-bit ARMv7 systems with hard-float](https://wiki.netbsd.org/ports/evbarm/) | +| `armv6-unknown-netbsd-eabihf` | [32-bit ARMv6 systems with hard-float](https://wiki.netbsd.org/ports/evbarm/) | +| `aarch64-unknown-netbsd` | [64-bit ARM systems, little-endian](https://wiki.netbsd.org/ports/evbarm/) | +| `aarch64_be-unknown-netbsd` | [64-bit ARM systems, big-endian](https://wiki.netbsd.org/ports/evbarm/) | +| `i586-unknown-netbsd` | [32-bit i386, restricted to Pentium](https://wiki.netbsd.org/ports/i386/) | +| `i686-unknown-netbsd` | [32-bit i386 with SSE](https://wiki.netbsd.org/ports/i386/) | +| `mipsel-unknown-netbsd` | [32-bit mips, requires mips32 cpu support](https://wiki.netbsd.org/ports/evbmips/) | +| `powerpc-unknown-netbsd` | [Various 32-bit PowerPC systems, e.g. MacPPC](https://wiki.netbsd.org/ports/macppc/) | +| `riscv64gc-unknown-netbsd` | [64-bit RISC-V](https://wiki.netbsd.org/ports/riscv/) +| `sparc64-unknown-netbsd` | [Sun UltraSPARC systems](https://wiki.netbsd.org/ports/sparc64/) | + +All use the "native" `stdc++` library which goes along with the natively +supplied GNU C++ compiler for the given OS version. Many of the bootstraps +are built for NetBSD 9.x, although some exceptions exist (some +are built for NetBSD 8.x but also work on newer OS versions). + + +## Designated Developers + +- [@he32](https://github.com/he32), `he@NetBSD.org` +- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust +- [NetBSD's pkgsrc lang/rust](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust) for the "proper" package in pkgsrc. +- [NetBSD's pkgsrc lang/rust-bin](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust-bin) which re-uses the bootstrap kit as a binary distribution and therefore avoids the rather protracted native build time of rust itself + +Fallback to pkgsrc-users@NetBSD.org, or fault reporting via NetBSD's +bug reporting system. + +## Requirements + +The `amd64-unknown-netbsd` artifacts is being distributed by the +rust project. + +The other targets are built by the designated developers (see above), +and the targets are initially cross-compiled, but many if not most +of them are also built natively as part of testing. + + +## Building + +The default build mode for the packages is a native build. + + +## Cross-compilation + +These targets can be cross-compiled, and we do that via the pkgsrc +package(s). + +Cross-compilation typically requires the "tools" and "dest" trees +resulting from a normal cross-build of NetBSD itself, ref. our main +build script, `build.sh`. + +See e.g. [do-cross.mk +Makefile](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust/do-cross.mk) +for the Makefile used to cross-build all the above NetBSD targets +(except for the `amd64` target). + +The major option for the rust build is whether to build rust with +the LLVM rust carries in its distribution, or use the LLVM package +installed from pkgsrc. The `PKG_OPTIONS.rust` option is +`rust-internal-llvm`, ref. [the rust package's options.mk make +fragment](https://github.com/NetBSD/pkgsrc/blob/trunk/lang/rust/options.mk). +It defaults to being set for a few of the above platforms, for +various reasons (see comments), but is otherwise unset and therefore +indicates use of the pkgsrc LLVM. + + +## Testing + +The Rust testsuite could presumably be run natively. + +For the systems where the maintainer can build natively, the rust +compiler itself is re-built natively. This involves the rust compiler +being re-built with the newly self-built rust compiler, so exercises +the result quite extensively. + +Additionally, for some systems we build `librsvg`, and for the more +capable systems we build and test `firefox` (amd64, i386, aarch64). + + +## Building Rust programs + +Rust ships pre-compiled artifacts for the `amd64-unknown-netbsd` +target. + +For the other systems mentioned above, using the `pkgsrc` route is +probably the easiest, possibly via the `rust-bin` package to save +time, see the `RUST_TYPE` variable from the `rust.mk` Makefile +fragment. + +The pkgsrc rust package has a few files to assist with building +pkgsrc packages written in rust, ref. the `rust.mk` and `cargo.mk` +Makefile fragments in the `lang/rust` package. diff --git a/src/doc/rustc/src/platform-support/nto-qnx.md b/src/doc/rustc/src/platform-support/nto-qnx.md index 0d815c9b59807..b376c4a84ac9e 100644 --- a/src/doc/rustc/src/platform-support/nto-qnx.md +++ b/src/doc/rustc/src/platform-support/nto-qnx.md @@ -164,18 +164,12 @@ export exclude_tests=' --exclude tests/run-make-fulldeps' env $build_env \ - ./x.py test -j 1 \ + ./x.py test \ $exclude_tests \ --stage 1 \ --target x86_64-pc-nto-qnx710 ``` -Currently, only one thread can be used when testing due to limitations in `libc::fork` and `libc::posix_spawnp`. -See [fork documentation](https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html) -(error section) for more information. -This can be achieved by using the `-j 1` parameter in the `x.py` call. -This issue is being researched and we will try to allow parallelism in the future. - ## Building Rust programs Rust does not yet ship pre-compiled artifacts for this target. diff --git a/src/doc/rustc/src/platform-support/openharmony.md b/src/doc/rustc/src/platform-support/openharmony.md index a8dcc6443461e..89539f3888c83 100644 --- a/src/doc/rustc/src/platform-support/openharmony.md +++ b/src/doc/rustc/src/platform-support/openharmony.md @@ -71,6 +71,28 @@ exec /path/to/ohos-sdk/linux/native/llvm/bin/clang++ \ "$@" ``` +`x86_64-unknown-linux-ohos-clang.sh` + +```sh +#!/bin/sh +exec /path/to/ohos-sdk/linux/native/llvm/bin/clang \ + -target x86_64-linux-ohos \ + --sysroot=/path/to/ohos-sdk/linux/native/sysroot \ + -D__MUSL__ \ + "$@" +``` + +`x86_64-unknown-linux-ohos-clang++.sh` + +```sh +#!/bin/sh +exec /path/to/ohos-sdk/linux/native/llvm/bin/clang++ \ + -target x86_64-linux-ohos \ + --sysroot=/path/to/ohos-sdk/linux/native/sysroot \ + -D__MUSL__ \ + "$@" +``` + Future versions of the OpenHarmony SDK will avoid the need for this process. ## Building the target @@ -98,6 +120,13 @@ cxx = "/path/to/armv7-unknown-linux-ohos-clang++.sh" ar = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ar" ranlib = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ranlib" linker = "/path/to/armv7-unknown-linux-ohos-clang.sh" + +[target.x86_64-unknown-linux-ohos] +cc = "/path/to/x86_64-unknown-linux-ohos-clang.sh" +cxx = "/path/to/x86_64-unknown-linux-ohos-clang++.sh" +ar = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ar" +ranlib = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ranlib" +linker = "/path/to/x86_64-unknown-linux-ohos-clang.sh" ``` ## Building Rust programs @@ -116,6 +145,10 @@ linker = "/path/to/aarch64-unknown-linux-ohos-clang.sh" [target.armv7-unknown-linux-ohos] ar = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ar" linker = "/path/to/armv7-unknown-linux-ohos-clang.sh" + +[target.x86_64-unknown-linux-ohos] +ar = "/path/to/ohos-sdk/linux/native/llvm/bin/llvm-ar" +linker = "/path/to/x86_64-unknown-linux-ohos-clang.sh" ``` ## Testing diff --git a/src/doc/rustc/src/platform-support/sparc-unknown-none-elf.md b/src/doc/rustc/src/platform-support/sparc-unknown-none-elf.md new file mode 100644 index 0000000000000..f579b1fb8d487 --- /dev/null +++ b/src/doc/rustc/src/platform-support/sparc-unknown-none-elf.md @@ -0,0 +1,188 @@ +# `sparc-unknown-none-elf` + +**Tier: 3** + +Rust for bare-metal 32-bit SPARC V7 and V8 systems, e.g. the Gaisler LEON3. + +| Target | Descriptions | +| ---------------------- | ----------------------------------------- | +| sparc-unknown-none-elf | SPARC V7 32-bit (freestanding, hardfloat) | + +## Target maintainers + +- Jonathan Pallant, , https://ferrous-systems.com + +## Requirements + +This target is cross-compiled. There is no support for `std`. There is no +default allocator, but it's possible to use `alloc` by supplying an allocator. + +By default, code generated with this target should run on any `SPARC` hardware; +enabling additional target features may raise this baseline. + +- `-Ctarget-cpu=v8` adds the extra SPARC V8 instructions. + +- `-Ctarget-cpu=leon3` adds the SPARC V8 instructions and sets up scheduling to + suit the Gaisler Leon3. + +Functions marked `extern "C"` use the [standard SPARC architecture calling +convention](https://sparc.org/technical-documents/). + +This target generates ELF binaries. Any alternate formats or special +considerations for binary layout will require linker options or linker scripts. + +## Building the target + +You can build Rust with support for the target by adding it to the `target` +list in `config.toml`: + +```toml +[build] +build-stage = 1 +host = [""] +target = ["", "sparc-unknown-none-elf"] +``` + +Replace `` with `x86_64-unknown-linux-gnu` or whatever +else is appropriate for your host machine. + +## Building Rust programs + +To build with this target, pass it to the `--target` argument, like: + +```console +cargo build --target sparc-unknown-none-elf +``` + +This target uses GCC as a linker, and so you will need an appropriate GCC +compatible `sparc-unknown-none` toolchain. The default linker binary is +`sparc-elf-gcc`, but you can override this in your project configuration, as +follows: + +`.cargo/config.toml`: +```toml +[target.sparc-unknown-none-elf] +linker = "sparc-custom-elf-gcc" +``` + +## Testing + +As `sparc-unknown-none-elf` supports a variety of different environments and does +not support `std`, this target does not support running the Rust test suite. + +## Cross-compilation toolchains and C code + +This target was initially tested using [BCC2] from Gaisler, along with the TSIM +Leon3 processor simulator. Both [BCC2] GCC and [BCC2] Clang have been shown to +work. To work with these tools, your project configuration should contain +something like: + +[BCC2]: https://www.gaisler.com/index.php/downloads/compilers + +`.cargo/config.toml`: +```toml +[target.sparc-unknown-none-elf] +linker = "sparc-gaisler-elf-gcc" +runner = "tsim-leon3" + +[build] +target = ["sparc-unknown-none-elf"] +rustflags = "-Ctarget-cpu=leon3" +``` + +With this configuration, running `cargo run` will compile your code for the +SPARC V8 compatible Gaisler Leon3 processor and then start the `tsim-leon3` +simulator. The `libcore` was pre-compiled as part of the `rustc` compilation +process using the SPARC V7 baseline, but if you are using a nightly toolchain +you can use the +[`-Z build-std=core`](https://doc.rust-lang.org/cargo/reference/unstable.html#build-std) +option to rebuild `libcore` from source. This may be useful if you want to +compile it for SPARC V8 and take advantage of the extra instructions. + +`.cargo/config.toml`: +```toml +[target.sparc-unknown-none-elf] +linker = "sparc-gaisler-elf-gcc" +runner = "tsim-leon3" + +[build] +target = ["sparc-unknown-none-elf"] +rustflags = "-Ctarget-cpu=leon3" + +[unstable] +build-std = ["core"] +``` + +Either way, once the simulator is running, simply enter the command `run` to +start the code executing in the simulator. + +The default C toolchain libraries are linked in, so with the Gaisler [BCC2] +toolchain, and using its default Leon3 BSP, you can use call the C `putchar` +function and friends to output to the simulator console. The default linker +script is also appropriate for the Leon3 simulator, so no linker script is +required. + +Here's a complete example using the above config file: + +```rust,ignore (cannot-test-this-because-it-assumes-special-libc-functions) +#![no_std] +#![no_main] + +extern "C" { + fn putchar(ch: i32); + fn _exit(code: i32) -> !; +} + +#[no_mangle] +extern "C" fn main() -> i32 { + let message = "Hello, this is Rust!"; + for b in message.bytes() { + unsafe { + putchar(b as i32); + } + } + 0 +} + +#[panic_handler] +fn panic(_panic: &core::panic::PanicInfo) -> ! { + unsafe { + _exit(1); + } +} +``` + +```console +$ cargo run --target=sparc-unknown-none-elf + Compiling sparc-demo-rust v0.1.0 (/work/sparc-demo-rust) + Finished dev [unoptimized + debuginfo] target(s) in 3.44s + Running `tsim-leon3 target/sparc-unknown-none-elf/debug/sparc-demo-rust` + + TSIM3 LEON3 SPARC simulator, version 3.1.9 (evaluation version) + + Copyright (C) 2023, Frontgrade Gaisler - all rights reserved. + This software may only be used with a valid license. + For latest updates, go to https://www.gaisler.com/ + Comments or bug-reports to support@gaisler.com + + This TSIM evaluation version will expire 2023-11-28 + +Number of CPUs: 2 +system frequency: 50.000 MHz +icache: 1 * 4 KiB, 16 bytes/line (4 KiB total) +dcache: 1 * 4 KiB, 16 bytes/line (4 KiB total) +Allocated 8192 KiB SRAM memory, in 1 bank at 0x40000000 +Allocated 32 MiB SDRAM memory, in 1 bank at 0x60000000 +Allocated 8192 KiB ROM memory at 0x00000000 +section: .text, addr: 0x40000000, size: 20528 bytes +section: .rodata, addr: 0x40005030, size: 128 bytes +section: .data, addr: 0x400050b0, size: 1176 bytes +read 347 symbols + +tsim> run + Initializing and starting from 0x40000000 +Hello, this is Rust! + + Program exited normally on CPU 0. +tsim> +``` diff --git a/src/doc/rustc/src/platform-support/unikraft-linux-musl.md b/src/doc/rustc/src/platform-support/unikraft-linux-musl.md new file mode 100644 index 0000000000000..90fa18b985783 --- /dev/null +++ b/src/doc/rustc/src/platform-support/unikraft-linux-musl.md @@ -0,0 +1,67 @@ +# `*-unikraft-linux-musl` + +**Tier: 3** + +Targets for the [Unikraft] Unikernel Development Kit (with musl). + +[Unikraft]: https://unikraft.org/ + +Target triplets available so far: + +- `x86_64-unikraft-linux-musl` + +## Target maintainers + +- Martin Kröning ([@mkroening](https://github.com/mkroening)) + +## Requirements + +These targets only support cross-compilation. +The targets do support std. + +Unikraft pretends to behave exactly like Linux. +How much of that functionality is available depends on the individual unikernel configuration. +For example, the basic Unikraft + musl config does not support `poll` or networking out of the box. +That functionality requires enabling [`LIBPOSIX_EVENT`] or [lwIP] respectively. + +[`LIBPOSIX_EVENT`]: https://github.com/unikraft/unikraft/blob/RELEASE-0.13.1/lib/posix-event/Config.uk +[lwIP]: https://github.com/unikraft/lib-lwip + +The Unikraft targets follow Linux's `extern "C"` calling convention. + +For these targets, `rustc` does not perform the final linking step. +Instead, the Unikraft build system will produce the final Unikernel image for the selected platform (e.g., KVM, Linux user space, and Xen). + +## Building the targets + +You can build Rust with support for the targets by adding it to the `target` list in `config.toml`: + +```toml +[build] +build-stage = 1 +target = [ "x86_64-unikraft-linux-musl" ] +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for these targets. +To compile for these targets, you will either need to build Rust with the targets enabled +(see “Building the targets” above), or build your own copy of `core` by using `build-std` or similar. + +Linking requires a [KraftKit] shim. +See [unikraft/kraftkit#612] for more information. + +[KraftKit]: https://github.com/unikraft/kraftkit +[unikraft/kraftkit#612]: https://github.com/unikraft/kraftkit/issues/612 + +## Testing + +The targets do support running binaries in the form of unikernel images. +How the unikernel image is run depends on the specific platform (e.g., KVM, Linux user space, and Xen). +The targets do not support running the Rust test suite. + +## Cross-compilation toolchains and C code + +The targets do support C code. +To build compatible C code, you have to use the same compiler and flags as does the Unikraft build system for your specific configuration. +The easiest way to achieve that, is to build the C code with the Unikraft build system when building your unikernel image. diff --git a/src/doc/rustc/src/platform-support/wasm32-wasi-preview1-threads.md b/src/doc/rustc/src/platform-support/wasm32-wasi-preview1-threads.md new file mode 100644 index 0000000000000..23b9992489973 --- /dev/null +++ b/src/doc/rustc/src/platform-support/wasm32-wasi-preview1-threads.md @@ -0,0 +1,148 @@ +# `wasm32-wasi-preview1-threads` + +**Tier: 2** + +The `wasm32-wasi-preview1-threads` target is a new and still (as of July 2023) an +experimental target. This target is an extension to `wasm32-wasi-preview1` target, +originally known as `wasm32-wasi`. It extends the original target with a +standardized set of syscalls that are intended to empower WebAssembly binaries with +native multi threading capabilities. + +[wasi-threads]: https://github.com/WebAssembly/wasi-threads +[threads]: https://github.com/WebAssembly/threads + + +## Target maintainers + +- Georgii Rylov, https://github.com/g0djan +- Alex Crichton, https://github.com/alexcrichton +- Andrew Brown, https://github.com/abrown +- Marcin Kolny, https://github.com/loganek + +## Requirements + +This target is cross-compiled. The target supports `std` fully. + +The Rust target definition here is interesting in a few ways. We want to +serve two use cases here with this target: +* First, we want Rust usage of the target to be as hassle-free as possible, + ideally avoiding the need to configure and install a local wasm32-wasi-preview1-threads + toolchain. +* Second, one of the primary use cases of LLVM's new wasm backend and the + wasm support in LLD is that any compiled language can interoperate with + any other. The `wasm32-wasi-preview1-threads` target is the first with a viable C + standard library and sysroot common definition, so we want Rust and C/C++ + code to interoperate when compiled to `wasm32-unknown-unknown`. + + +You'll note, however, that the two goals above are somewhat at odds with one +another. To attempt to solve both use cases in one go we define a target +that (ab)uses the `crt-static` target feature to indicate which one you're +in. +### No interop with C required +By default the `crt-static` target feature is enabled, and when enabled +this means that the bundled version of `libc.a` found in `liblibc.rlib` +is used. This isn't intended really for interoperation with a C because it +may be the case that Rust's bundled C library is incompatible with a +foreign-compiled C library. In this use case, though, we use `rust-lld` and +some copied crt startup object files to ensure that you can download the +wasi target for Rust and you're off to the races, no further configuration +necessary. +All in all, by default, no external dependencies are required. You can +compile `wasm32-wasi-preview1-threads` binaries straight out of the box. You can't, however, +reliably interoperate with C code in this mode (yet). +### Interop with C required +For the second goal we repurpose the `target-feature` flag, meaning that +you'll need to do a few things to have C/Rust code interoperate. +1. All Rust code needs to be compiled with `-C target-feature=-crt-static`, + indicating that the bundled C standard library in the Rust sysroot will + not be used. +2. If you're using rustc to build a linked artifact then you'll need to + specify `-C linker` to a `clang` binary that supports + `wasm32-wasi-preview1-threads` and is configured with the `wasm32-wasi-preview1-threads` sysroot. This + will cause Rust code to be linked against the libc.a that the specified + `clang` provides. +3. If you're building a staticlib and integrating Rust code elsewhere, then + compiling with `-C target-feature=-crt-static` is all you need to do. + +All in all, by default, no external dependencies are required. You can +compile `wasm32-wasi-preview1-threads` binaries straight out of the box. You can't, however, +reliably interoperate with C code in this mode (yet). + + +Also note that at this time the `wasm32-wasi-preview1-threads` target assumes the +presence of other merged wasm proposals such as (with their LLVM feature flags): + +* [Bulk memory] - `+bulk-memory` +* Mutable imported globals - `+mutable-globals` +* Atomics - `+atomics` + +[Bulk memory]: https://github.com/WebAssembly/spec/blob/main/proposals/bulk-memory-operations/Overview.md + +LLVM 16 is required for this target. The reason is related to linker flags: prior to LLVM 16, --import-memory and --export-memory were not allowed together. The reason both are needed is an artifact of how WASI currently does things; see https://github.com/WebAssembly/WASI/issues/502 for more details. + +The target intends to match the corresponding Clang target for its `"C"` ABI. + +> **Note**: due to the relatively early-days nature of this target when working +> with this target you may encounter LLVM bugs. If an assertion hit or a bug is +> found it's recommended to open an issue either with rust-lang/rust or ideally +> with LLVM itself. + +## Platform requirements + +The runtime should support the same set of APIs as any other supported wasi target for interacting with the host environment through the WASI standard. The runtime also should have implemetation of [wasi-threads proposal](https://github.com/WebAssembly/wasi-threads). + +This target is not a stable target. This means that there are a few engines +which implement the `wasi-threads` feature and if they do they're likely behind a +flag, for example: + +* Wasmtime - `--wasm-features=threads --wasi-modules=experimental-wasi-threads` +* [WAMR](https://github.com/bytecodealliance/wasm-micro-runtime) - needs to be built with WAMR_BUILD_LIB_WASI_THREADS=1 + +## Building the target + +Users need to install or built wasi-sdk since release 20.0 +https://github.com/WebAssembly/wasi-sdk/releases/tag/wasi-sdk-20 +and specify path to *wasi-root* `.cargo/config.toml` + +```toml +[target.wasm32-wasi-preview1-threads] +wasi-root = ".../wasi-libc/sysroot" +``` + +After that users can build this by adding it to the `target` list in +`config.toml`, or with `-Zbuild-std`. + +## Building Rust programs + +From Rust Nightly 1.71.1 (2023-08-03) on the artifacts are shipped pre-compiled: + +```text +rustup target add wasm32-wasi-preview1-threads --toolchain nightly +``` + +Rust programs can be built for that target: + +```text +rustc --target wasm32-wasi-preview1-threads your-code.rs +``` + +## Cross-compilation + +This target can be cross-compiled from any hosts. + +## Testing + +Currently testing is not well supported for `wasm32-wasi-preview1-threads` and the +Rust project doesn't run any tests for this target. However the UI testsuite can be run +manually following this instructions: + +0. Ensure [wamr](https://github.com/bytecodealliance/wasm-micro-runtime), [wasmtime](https://github.com/bytecodealliance/wasmtime) +or another engine that supports `wasi-threads` is installed and can be found in the `$PATH` env variable. +1. Clone master branch. +2. Apply such [a change](https://github.com/g0djan/rust/compare/godjan/wasi-threads...g0djan:rust:godjan/wasi-run-ui-tests?expand=1) with an engine from the step 1. +3. Run `./x.py test --target wasm32-wasi-preview1-threads tests/ui` and save the list of failed tests. +4. Checkout branch with your changes. +5. Apply such [a change](https://github.com/g0djan/rust/compare/godjan/wasi-threads...g0djan:rust:godjan/wasi-run-ui-tests?expand=1) with an engine from the step 1. +6. Run `./x.py test --target wasm32-wasi-preview1-threads tests/ui` and save the list of failed tests. +7. For both lists of failed tests run `cat list | sort > sorted_list` and compare it with `diff sorted_list1 sorted_list2`. diff --git a/src/doc/rustc/src/platform-support/x86_64h-apple-darwin.md b/src/doc/rustc/src/platform-support/x86_64h-apple-darwin.md index 1a6f7bb834cf5..0fe9d4edaca10 100644 --- a/src/doc/rustc/src/platform-support/x86_64h-apple-darwin.md +++ b/src/doc/rustc/src/platform-support/x86_64h-apple-darwin.md @@ -20,7 +20,7 @@ will fail to load on machines that do not support this. It should support the full standard library (`std` and `alloc` either with default or user-defined allocators). This target is probably most useful when -targetted via cross-compilation (including from `x86_64-apple-darwin`), but if +targeted via cross-compilation (including from `x86_64-apple-darwin`), but if built manually, the host tools work. It is similar to `x86_64-apple-darwin` in nearly all respects, although the @@ -49,7 +49,7 @@ suite seems to work. Cross-compilation to this target from Apple hosts should generally work without much configuration, so long as XCode and the CommandLineTools are installed. -Targetting it from non-Apple hosts is difficult, but no moreso than targetting +Targeting it from non-Apple hosts is difficult, but no more so than targeting `x86_64-apple-darwin`. When compiling C code for this target, either the "`x86_64h-apple-macosx*`" LLVM diff --git a/src/doc/rustc/src/symbol-mangling/index.md b/src/doc/rustc/src/symbol-mangling/index.md new file mode 100644 index 0000000000000..be58f2b41b8dd --- /dev/null +++ b/src/doc/rustc/src/symbol-mangling/index.md @@ -0,0 +1,52 @@ +# Symbol Mangling + +[Symbol name mangling] is used by `rustc` to encode a unique name for symbols that are used during code generation. +The encoded names are used by the linker to associate the name with the thing it refers to. + +The method for mangling the names can be controlled with the [`-C symbol-mangling-version`] option. + +[Symbol name mangling]: https://en.wikipedia.org/wiki/Name_mangling +[`-C symbol-mangling-version`]: ../codegen-options/index.md#symbol-mangling-version + +## Per-item control + +The [`#[no_mangle]` attribute][reference-no_mangle] can be used on items to disable name mangling on that item. + +The [`#[export_name]`attribute][reference-export_name] can be used to specify the exact name that will be used for a function or static. + +Items listed in an [`extern` block][reference-extern-block] use the identifier of the item without mangling to refer to the item. +The [`#[link_name]` attribute][reference-link_name] can be used to change that name. + + + +[reference-no_mangle]: ../../reference/abi.html#the-no_mangle-attribute +[reference-export_name]: ../../reference/abi.html#the-export_name-attribute +[reference-link_name]: ../../reference/items/external-blocks.html#the-link_name-attribute +[reference-extern-block]: ../../reference/items/external-blocks.html + +## Decoding + +The encoded names may need to be decoded in some situations. +For example, debuggers and other tooling may need to demangle the name so that it is more readable to the user. +Recent versions of `gdb` and `lldb` have built-in support for demangling Rust identifiers. +In situations where you need to do your own demangling, the [`rustc-demangle`] crate can be used to programmatically demangle names. +[`rustfilt`] is a CLI tool which can demangle names. + +An example of running rustfilt: + +```text +$ rustfilt _RNvCskwGfYPst2Cb_3foo16example_function +foo::example_function +``` + +[`rustc-demangle`]: https://crates.io/crates/rustc-demangle +[`rustfilt`]: https://crates.io/crates/rustfilt + +## Mangling versions + +`rustc` supports different mangling versions which encode the names in different ways. +The legacy version (which is currently the default) is not described here. +The "v0" mangling scheme addresses several limitations of the legacy format, +and is described in the [v0 Symbol Format](v0.md) chapter. diff --git a/src/doc/rustc/src/symbol-mangling/v0.md b/src/doc/rustc/src/symbol-mangling/v0.md new file mode 100644 index 0000000000000..61f747fac837c --- /dev/null +++ b/src/doc/rustc/src/symbol-mangling/v0.md @@ -0,0 +1,1222 @@ +# v0 Symbol Format + +The v0 mangling format was introduced in [RFC 2603]. +It has the following properties: + +- It provides an unambiguous string encoding for everything that can end up in a binary's symbol table. +- It encodes information about generic parameters in a reversible way. +- The mangled symbols are *decodable* such that the demangled form should be easily identifiable as some concrete instance of e.g. a polymorphic function. +- It has a consistent definition that does not rely on pretty-printing certain language constructs. +- Symbols can be restricted to only consist of the characters `A-Z`, `a-z`, `0-9`, and `_`. + This helps ensure that it is platform-independent, + where other characters might have special meaning in some context (e.g. `.` for MSVC `DEF` files). + Unicode symbols are optionally supported. +- It tries to stay efficient, avoiding unnecessarily long names, + and avoiding computationally expensive operations to demangle. + +The v0 format is not intended to be compatible with other mangling schemes (such as C++). + +The v0 format is not presented as a stable ABI for Rust. +This format is currently intended to be well-defined enough that a demangler can produce a reasonable human-readable form of the symbol. +There are several implementation-defined portions that result in it not being possible to entirely predict how a given Rust entity will be encoded. + +The sections below define the encoding of a v0 symbol. +There is no standardized demangled form of the symbols, +though suggestions are provided for how to demangle a symbol. +Implementers may choose to demangle in different ways. + +## Extensions + +This format may be extended in the future to add new tags as Rust is extended with new language items. +To be forward compatible, demanglers should gracefully handle symbols that have encodings where it encounters a tag character not described in this document. +For example, they may fall back to displaying the mangled symbol. +The format may be extended anywhere there is a tag character, such as the [type] rule. +The meaning of existing tags and encodings will not be changed. + +## Grammar notation + +The format of an encoded symbol is illustrated as a context free grammar in an extended BNF-like syntax. +A consolidated summary can be found in the [Symbol grammar summary][summary]. + +| Name | Syntax | Example | Description | +|------|--------|---------|-------------| +| Rule | → | A → *B* *C* | A production. | +| Concatenation | whitespace | A → *B* *C* *D* | Individual elements in sequence left-to-right. | +| Alternative | \| | A → *B* \| *C* | Matches either one or the other. | +| Grouping | () | A → *B* (*C* \| *D*) *E* | Groups multiple elements as one. | +| Repetition | {} | A → {*B*} | Repeats the enclosed zero or more times. | +| Option | opt | A → *B*opt *C* | An optional element. | +| Literal | `monospace` | A → `G` | A terminal matching the exact characters case-sensitive. | + +## Symbol name +[symbol-name]: #symbol-name + +> symbol-name → `_R` *[decimal-number]*opt *[path]* *[instantiating-crate]*opt *[vendor-specific-suffix]*opt + +A mangled symbol starts with the two characters `_R` which is a prefix to identify the symbol as a Rust symbol. +The prefix can optionally be followed by a *[decimal-number]* which specifies the encoding version. +This number is currently not used, and is never present in the current encoding. +Following that is a *[path]* which encodes the path to an entity. +The path is followed by an optional *[instantiating-crate]* which helps to disambiguate entities which may be instantiated multiple times in separate crates. +The final part is an optional *[vendor-specific-suffix]*. + +> **Recommended Demangling** +> +> A *symbol-name* should be displayed as the *[path]*. +> The *[instantiating-crate]* and the *[vendor-specific-suffix]* usually need not be displayed. + +> Example: +> ```rust +> std::path::PathBuf::new(); +> ``` +> +> The symbol for `PathBuf::new` in crate `mycrate` is: +> +> ```text +> _RNvMsr_NtCs3ssYzQotkvD_3std4pathNtB5_7PathBuf3newCs15kBYyAo9fc_7mycrate +> ├┘└───────────────────────┬──────────────────────┘└──────────┬─────────┘ +> │ │ │ +> │ │ └── instantiating-crate path "mycrate" +> │ └───────────────────────────────────── path to std::path::PathBuf::new +> └─────────────────────────────────────────────────────────────── `_R` symbol prefix +> ``` +> +> Recommended demangling: `::new` + +## Symbol path +[path]: #symbol-path + +> path → \ +>       *[crate-root]* \ +>    | *[inherent-impl]* \ +>    | *[trait-impl]* \ +>    | *[trait-definition]* \ +>    | *[nested-path]* \ +>    | *[generic-args]* \ +>    | *[backref]* + +A *path* represents a variant of a [Rust path][reference-paths] to some entity. +In addition to typical Rust path segments using identifiers, +it uses extra elements to represent unnameable entities (like an `impl`) or generic arguments for monomorphized items. + +The initial tag character can be used to determine which kind of path it represents: + +| Tag | Rule | Description | +|-----|------|-------------| +| `C` | *[crate-root]* | The root of a crate path. | +| `M` | *[inherent-impl]* | An inherent implementation. | +| `X` | *[trait-impl]* | A trait implementation. | +| `Y` | *[trait-definition]* | A trait definition. | +| `N` | *[nested-path]* | A nested path. | +| `I` | *[generic-args]* | Generic arguments. | +| `B` | *[backref]* | A back reference. | + +### Path: Crate root +[crate-root]: #path-crate-root + +> crate-root → `C` *[identifier]* + +A *crate-root* indicates a path referring to the root of a crate's module tree. +It consists of the character `C` followed by the crate name as an *[identifier]*. + +The crate name is the name as seen from the defining crate. +Since Rust supports linking multiple crates with the same name, +the *[disambiguator]* is used to make the name unique across the crate graph. + +> **Recommended Demangling** +> +> A *crate-root* can be displayed as the identifier such as `mycrate`. +> +> Usually the disambiguator in the identifier need not be displayed, +> but as an alternate form the disambiguator can be shown in hex such as +> `mycrate[ca63f166dbe9294]`. + +> Example: +> ```rust +> fn example() {} +> ``` +> +> The symbol for `example` in crate `mycrate` is: +> +> ```text +> _RNvCs15kBYyAo9fc_7mycrate7example +> │└────┬─────┘││└──┬──┘ +> │ │ ││ │ +> │ │ ││ └── crate-root identifier "mycrate" +> │ │ │└────── length 7 of "mycrate" +> │ │ └─────── end of base-62-number +> │ └────────────── disambiguator for crate-root "mycrate" 0xca63f166dbe9293 + 1 +> └──────────────────── crate-root +> ``` +> +> Recommended demangling: `mycrate::example` + +### Path: Inherent impl +[inherent-impl]: #path-inherent-impl + +> inherent-impl → `M` *[impl-path]* *[type]* + +An *inherent-impl* indicates a path to an [inherent implementation][reference-inherent-impl]. +It consists of the character `M` followed by an *[impl-path]*, which uniquely identifies the impl block the item is defined in. +Following that is a *[type]* representing the `Self` type of the impl. + +> **Recommended Demangling** +> +> An *inherent-impl* can be displayed as a qualified path segment to the *[type]* within angled brackets. +> The *[impl-path]* usually need not be displayed. + +> Example: +> ```rust +> struct Example; +> impl Example { +> fn foo() {} +> } +> ``` +> +> The symbol for `foo` in the impl for `Example` is: +> +> ```text +> _RNvMs_Cs4Cv8Wi1oAIB_7mycrateNtB4_7Example3foo +> │├┘└─────────┬──────────┘└────┬──────┘ +> ││ │ │ +> ││ │ └── Self type "Example" +> ││ └─────────────────── path to the impl's parent "mycrate" +> │└─────────────────────────────── disambiguator 1 +> └──────────────────────────────── inherent-impl +> ``` +> +> Recommended demangling: `::foo` + +### Path: Trait impl +[trait-impl]: #path-trait-impl + +> trait-impl → `X` *[impl-path]* *[type]* *[path]* + +A *trait-impl* indicates a path to a [trait implementation][reference-trait-impl]. +It consists of the character `X` followed by an *[impl-path]* to the impl's parent followed by the *[type]* representing the `Self` type of the impl followed by a *[path]* to the trait. + +> **Recommended Demangling** +> +> A *trait-impl* can be displayed as a qualified path segment using the `<` *type* `as` *path* `>` syntax. +> The *[impl-path]* usually need not be displayed. + +> Example: +> ```rust +> struct Example; +> trait Trait { +> fn foo(); +> } +> impl Trait for Example { +> fn foo() {} +> } +> ``` +> +> The symbol for `foo` in the trait impl for `Example` is: +> +> ```text +> _RNvXCs15kBYyAo9fc_7mycrateNtB2_7ExampleNtB2_5Trait3foo +> │└─────────┬──────────┘└─────┬─────┘└────┬────┘ +> │ │ │ │ +> │ │ │ └── path to the trait "Trait" +> │ │ └────────────── Self type "Example" +> │ └──────────────────────────────── path to the impl's parent "mycrate" +> └─────────────────────────────────────────── trait-impl +> ``` +> +> Recommended demangling: `::foo` + +### Path: Impl +[impl-path]: #path-impl + +> impl-path → *[disambiguator]*opt *[path]* + +An *impl-path* is a path used for *[inherent-impl]* and *[trait-impl]* to indicate the path to parent of an [implementation][reference-implementations]. +It consists of an optional *[disambiguator]* followed by a *[path]*. +The *[path]* is the path to the parent that contains the impl. +The *[disambiguator]* can be used to distinguish between multiple impls within the same parent. + +> **Recommended Demangling** +> +> An *impl-path* usually need not be displayed (unless the location of the impl is desired). + +> Example: +> ```rust +> struct Example; +> impl Example { +> fn foo() {} +> } +> impl Example { +> fn bar() {} +> } +> ``` +> +> The symbol for `foo` in the impl for `Example` is: +> +> ```text +> _RNvMCs7qp2U7fqm6G_7mycrateNtB2_7Example3foo +> └─────────┬──────────┘ +> │ +> └── path to the impl's parent crate-root "mycrate" +> ``` +> +> The symbol for `bar` is similar, though it has a disambiguator to indicate it is in a different impl block. +> +> ```text +> _RNvMs_Cs7qp2U7fqm6G_7mycrateNtB4_7Example3bar +> ├┘└─────────┬──────────┘ +> │ │ +> │ └── path to the impl's parent crate-root "mycrate" +> └────────────── disambiguator 1 +> ``` +> +> Recommended demangling: +> * `foo`: `::foo` +> * `bar`: `::bar` + +### Path: Trait definition +[trait-definition]: #path-trait-definition + +> trait-definition → `Y` *[type]* *[path]* + +A *trait-definition* is a path to a [trait definition][reference-traits]. +It consists of the character `Y` followed by the *[type]* which is the `Self` type of the referrer, followed by the *[path]* to the trait definition. + +> **Recommended Demangling** +> +> A *trait-definition* can be displayed as a qualified path segment using the `<` *type* `as` *path* `>` syntax. + +> Example: +> ```rust +> trait Trait { +> fn example() {} +> } +> struct Example; +> impl Trait for Example {} +> ``` +> +> The symbol for `example` in the trait `Trait` implemented for `Example` is: +> +> ```text +> _RNvYNtCs15kBYyAo9fc_7mycrate7ExampleNtB4_5Trait7exampleB4_ +> │└──────────────┬───────────────┘└────┬────┘ +> │ │ │ +> │ │ └── path to the trait "Trait" +> │ └──────────────────────── path to the implementing type "mycrate::Example" +> └──────────────────────────────────────── trait-definition +> ``` +> +> Recommended demangling: `::example` + +### Path: Nested path +[nested-path]: #path-nested-path + +> nested-path → `N` *[namespace]* *[path]* *[identifier]* + +A *nested-path* is a path representing an optionally named entity. +It consists of the character `N` followed by a *[namespace]* indicating the namespace of the entity, +followed by a *[path]* which is a path representing the parent of the entity, +followed by an *[identifier]* of the entity. + +The identifier of the entity may have a length of 0 when the entity is not named. +For example, entities like closures, tuple-like struct constructors, and anonymous constants may not have a name. +The identifier may still have a disambiguator unless the disambiguator is 0. + +> **Recommended Demangling** +> +> A *nested-path* can be displayed by first displaying the *[path]* followed by a `::` separator followed by the *[identifier]*. +> If the *[identifier]* is empty, then the separating `::` should not be displayed. +> +> If a *[namespace]* is specified, then extra context may be added such as: \ +> *[path]* `::{` *[namespace]* (`:` *[identifier]*)opt `#` *disambiguator*as base-10 number `}` +> +> Here the namespace `C` may be printed as `closure` and `S` as `shim`. +> Others may be printed by their character tag. +> The `:` *name* portion may be skipped if the name is empty. +> +> The *[disambiguator]* in the *[identifier]* may be displayed if a *[namespace]* is specified. +> In other situations, it is usually not necessary to display the *[disambiguator]*. +> If it is displayed, it is recommended to place it in brackets, for example `[284a76a8b41a7fd3]`. +> If the *[disambiguator]* is not present, then its value is 0 and it can always be omitted from display. + +> Example: +> ```rust +> fn main() { +> let x = || {}; +> let y = || {}; +> x(); +> y(); +> } +> ``` +> +> The symbol for the closure `x` in crate `mycrate` is: +> +> ```text +> _RNCNvCsgStHSCytQ6I_7mycrate4main0B3_ +> ││└─────────────┬─────────────┘│ +> ││ │ │ +> ││ │ └── identifier with length 0 +> ││ └───────────────── path to "mycrate::main" +> │└──────────────────────────────── closure namespace +> └───────────────────────────────── nested-path +> ``` +> +> The symbol for the closure `y` is similar, with a disambiguator: +> +> ```text +> _RNCNvCsgStHSCytQ6I_7mycrate4mains_0B3_ +> ││ +> │└── base-62-number 0 +> └─── disambiguator 1 (base-62-number+1) +> ``` +> +> Recommended demangling: +> * `x`: `mycrate::main::{closure#0}` +> * `y`: `mycrate::main::{closure#1}` + +### Path: Generic arguments +[generic-args]: #path-generic-arguments +[generic-arg]: #path-generic-arguments + +> generic-args → `I` *[path]* {*[generic-arg]*} `E` +> +> generic-arg → \ +>       *[lifetime]* \ +>    | *[type]* \ +>    | `K` *[const]* + +A *generic-args* is a path representing a list of generic arguments. +It consists of the character `I` followed by a *[path]* to the defining entity, followed by zero or more [generic-arg]s terminated by the character `E`. + +Each *[generic-arg]* is either a *[lifetime]* (starting with the character `L`), a *[type]*, or the character `K` followed by a *[const]* representing a const argument. + +> **Recommended Demangling** +> +> A *generic-args* may be printed as: *[path]* `::`opt `<` comma-separated list of args `>` +> The `::` separator may be elided for type paths (similar to Rust's rules). + +> > Example: +> ```rust +> fn main() { +> example([123]); +> } +> +> fn example(x: [T; N]) {} +> ``` +> +> The symbol for the function `example` is: +> +> ```text +> _RINvCsgStHSCytQ6I_7mycrate7examplelKj1_EB2_ +> │└──────────────┬───────────────┘││││││ +> │ │ │││││└── end of generic-args +> │ │ ││││└─── end of const-data +> │ │ │││└──── const value `1` +> │ │ ││└───── const type `usize` +> │ │ │└────── const generic +> │ │ └─────── generic type i32 +> │ └──────────────────────── path to "mycrate::example" +> └──────────────────────────────────────── generic-args +> ``` +> +> Recommended demangling: `mycrate::example::` + +### Namespace +[namespace]: #namespace + +> namespace → *[lower]* | *[upper]* + +A *namespace* is used to segregate names into separate logical groups, allowing identical names to otherwise avoid collisions. +It consists of a single character of an upper or lowercase ASCII letter. +Lowercase letters are reserved for implementation-internal disambiguation categories (and demanglers should never show them). +Uppercase letters are used for special namespaces which demanglers may display in a special way. + +Uppercase namespaces are: + +* `C` — A closure. +* `S` — A shim. Shims are added by the compiler in some situations where an intermediate is needed. + For example, a `fn()` pointer to a function with the [`#[track_caller]` attribute][reference-track_caller] needs a shim to deal with the implicit caller location. + +> **Recommended Demangling** +> +> See *[nested-path]* for recommended demangling. + +## Identifier +[identifier]: #identifier +[undisambiguated-identifier]: #identifier +[bytes]: #identifier + +> identifier → *[disambiguator]*opt *[undisambiguated-identifier]* +> +> undisambiguated-identifier → `u`opt *[decimal-number]* `_`opt *[bytes]* +> +> bytes → {*UTF-8 bytes*} + +An *identifier* is a named label used in a *[path]* to refer to an entity. +It consists of an optional *[disambiguator]* followed by an *[undisambiguated-identifier]*. + +The disambiguator is used to disambiguate identical identifiers that should not otherwise be considered the same. +For example, closures have no name, so the disambiguator is the only differentiating element between two different closures in the same parent path. + +The undisambiguated-identifier starts with an optional `u` character, +which indicates that the identifier is encoded in [Punycode][Punycode identifiers]. +The next part is a *[decimal-number]* which indicates the length of the *bytes*. + +Following the identifier size is an optional `_` character which is used to separate the length value from the identifier itself. +The `_` is mandatory if the *bytes* starts with a decimal digit or `_` in order to keep it unambiguous where the *decimal-number* ends and the *bytes* starts. + +*bytes* is the identifier itself encoded in UTF-8. + +> **Recommended Demangling** +> +> The display of an *identifier* can depend on its context. +> If it is Punycode-encoded, then it may first be decoded before being displayed. +> +> The *[disambiguator]* may or may not be displayed; see recommendations for rules that use *identifier*. + +### Punycode identifiers +[Punycode identifiers]: #punycode-identifiers + +Because some environments are restricted to ASCII alphanumerics and `_`, +Rust's [Unicode identifiers][reference-identifiers] may be encoded using a modified version of [Punycode]. + +For example, the function: + +```rust +mod gödel { + mod escher { + fn bach() {} + } +} +``` + +would be mangled as: + +```text +_RNvNtNtCsgOH4LzxkuMq_7mycrateu8gdel_5qa6escher4bach + ││└───┬──┘ + ││ │ + ││ └── gdel_5qa translates to gödel + │└─────── 8 is the length + └──────── `u` indicates it is a Unicode identifier +``` + +Standard Punycode generates strings of the form `([[:ascii:]]+-)?[[:alnum:]]+`. +This is problematic because the `-` character +(which is used to separate the ASCII part from the base-36 encoding) +is not in the supported character set for symbols. +For this reason, `-` characters in the Punycode encoding are replaced with `_`. + +Here are some examples: + +| Original | Punycode | Punycode + Encoding | +|-----------------|-----------------|---------------------| +| føø | f-5gaa | f_5gaa | +| α_ω | _-ylb7e | __ylb7e | +| 铁锈 | n84amf | n84amf | +| 🤦 | fq9h | fq9h | +| ρυστ | 2xaedc | 2xaedc | + +> Note: It is up to the compiler to decide whether or not to encode identifiers using Punycode or not. +> Some platforms may have native support for UTF-8 symbols, +> and the compiler may decide to use the UTF-8 encoding directly. +> Demanglers should be prepared to support either form. + +[Punycode]: https://tools.ietf.org/html/rfc3492 + +## Disambiguator +[disambiguator]: #disambiguator + +> disambiguator → `s` *[base-62-number]* + +A *disambiguator* is used in various parts of a symbol *[path]* to uniquely identify path elements that would otherwise be identical but should not be considered the same. +It starts with the character `s` and is followed by a *[base-62-number]*. + +If the *disambiguator* is not specified, then its value can be assumed to be zero. +Otherwise, when demangling, the value 1 should be added to the *[base-62-number]* +(thus a *base-62-number* of zero encoded as `_` has a value of 1). +This allows disambiguators that are encoded sequentially to use minimal bytes. + +> **Recommended Demangling** +> +> The *disambiguator* may or may not be displayed; see recommendations for rules that use *disambiguator*. + +## Lifetime +[lifetime]: #lifetime + +> lifetime → `L` *[base-62-number]* + +A *lifetime* is used to encode an anonymous (numbered) lifetime, either erased or [higher-ranked](#binder). +It starts with the character `L` and is followed by a *[base-62-number]*. +Index 0 is always erased. +Indices starting from 1 refer (as de Bruijn indices) to a higher-ranked lifetime bound by one of the enclosing [binder]s. + +> **Recommended Demangling** +> +> A *lifetime* may be displayed like a Rust lifetime using a single quote. +> +> Index 0 should be displayed as `'_`. +> Index 0 should not be displayed for lifetimes in a *[ref-type]*, *[mut-ref-type]*, or *[dyn-trait-type]*. +> +> A lifetime can be displayed by converting the De Bruijn index to a De Bruijn level +> (level = number of bound lifetimes - index) and selecting a unique name for each level. +> For example, starting with single lowercase letters such as `'a` for level 0. +> Levels over 25 may consider printing the numeric lifetime as in `'_123`. +> See *[binder]* for more on lifetime indexes and ordering. + +> Example: +> ```rust +> fn main() { +> example::(); +> } +> +> pub fn example() {} +> ``` +> +> The symbol for the function `example` is: +> +> ```text +> _RINvCs7qp2U7fqm6G_7mycrate7exampleFG0_RL1_hRL0_tEuEB2_ +> │└┬┘│└┬┘││└┬┘││ +> │ │ │ │ ││ │ │└── end of input types +> │ │ │ │ ││ │ └─── type u16 +> │ │ │ │ ││ └───── lifetime #1 'b +> │ │ │ │ │└─────── reference type +> │ │ │ │ └──────── type u8 +> │ │ │ └────────── lifetime #2 'a +> │ │ └──────────── reference type +> │ └────────────── binder with 2 lifetimes +> └──────────────── function type +> ``` +> +> Recommended demangling: `mycrate::example:: fn(&'a u8, &'b u16)>` + +## Const +[const]: #const +[const-data]: #const +[hex-digit]: #const + +> const → \ +>       *[type]* *[const-data]* \ +>    | `p` \ +>    | *[backref]* +> +> const-data → `n`opt {*[hex-digit]*} `_` +> +> [hex-digit] → *[digit]* | `a` | `b` | `c` | `d` | `e` | `f` + +A *const* is used to encode a const value used in generics and types. +It has the following forms: + +* A constant value encoded as a *[type]* which represents the type of the constant and *[const-data]* which is the constant value, followed by `_` to terminate the *const*. +* The character `p` which represents a [placeholder]. +* A *[backref]* to a previously encoded *const* of the same value. + +The encoding of the *const-data* depends on the type: + +* `bool` — The value `false` is encoded as `0_`, the value true is encoded as `1_`. +* `char` — The Unicode scalar value of the character is encoded in hexadecimal. +* Unsigned integers — The value is encoded in hexadecimal. +* Signed integers — The character `n` is a prefix to indicate that it is negative, + followed by the absolute value encoded in hexadecimal. + +> **Recommended Demangling** +> +> A *const* may be displayed by the const value depending on the type. +> +> The `p` placeholder should be displayed as the `_` character. +> +> For specific types: +> * `b` (bool) — Display as `true` or `false`. +> * `c` (char) — Display the character in as a Rust character (such as `'A'` or `'\n'`). +> * integers — Display the integer (either in decimal or hex). + +> Example: +> ```rust +> fn main() { +> example::<0x12345678>(); +> } +> +> pub fn example() {} +> ``` +> +> The symbol for function `example` is: +> +> ```text +> _RINvCs7qp2U7fqm6G_7mycrate7exampleKy12345678_EB2_ +> ││└───┬───┘ +> ││ │ +> ││ └── const-data 0x12345678 +> │└─────── const type u64 +> └──────── const generic arg +> ``` +> +> Recommended demangling: `mycrate::example::<305419896>` + +### Placeholders +[placeholder]: #placeholders + +A *placeholder* may occur in circumstances where a type or const value is not relevant. + +> Example: +> ```rust +> pub struct Example([T; N]); +> +> impl Example { +> pub fn foo() -> &'static () { +> static EXAMPLE_STATIC: () = (); +> &EXAMPLE_STATIC +> } +> } +> ``` +> +> In this example, the static `EXAMPLE_STATIC` would not be monomorphized by the type or const parameters `T` and `N`. +> Those will use the placeholder for those generic arguments. +> Its symbol is: +> +> ```text +> _RNvNvMCsd9PVOYlP1UU_7mycrateINtB4_7ExamplepKpE3foo14EXAMPLE_STATIC +> │ │││ +> │ ││└── const placeholder +> │ │└─── const generic argument +> │ └──── type placeholder +> └────────────────── generic-args +> ``` +> +> Recommended demangling: `>::foo::EXAMPLE_STATIC` + + +## Type +[type]: #type +[basic-type]: #basic-type +[array-type]: #array-type +[slice-type]: #slice-type +[tuple-type]: #tuple-type +[ref-type]: #ref-type +[mut-ref-type]: #mut-ref-type +[const-ptr-type]: #const-ptr-type +[mut-ptr-type]: #mut-ptr-type +[fn-type]: #fn-type +[dyn-trait-type]: #dyn-trait-type + +> type → \ +>       *[basic-type]* \ +>    | *[array-type]* \ +>    | *[slice-type]* \ +>    | *[tuple-type]* \ +>    | *[ref-type]* \ +>    | *[mut-ref-type]* \ +>    | *[const-ptr-type]* \ +>    | *[mut-ptr-type]* \ +>    | *[fn-type]* \ +>    | *[dyn-trait-type]* \ +>    | *[path]* \ +>    | *[backref]* + +A *type* represents a Rust [type][reference-types]. +The initial character can be used to distinguish which type is encoded. +The type encodings based on the initial tag character are: + +* A *basic-type* is encoded as a single character: + * `a` — `i8` + * `b` — `bool` + * `c` — `char` + * `d` — `f64` + * `e` — `str` + * `f` — `f32` + * `h` — `u8` + * `i` — `isize` + * `j` — `usize` + * `l` — `i32` + * `m` — `u32` + * `n` — `i128` + * `o` — `u128` + * `s` — `i16` + * `t` — `u16` + * `u` — unit `()` + * `v` — variadic `...` + * `x` — `i64` + * `y` — `u64` + * `z` — `!` + * `p` — [placeholder] `_` + +* `A` — An [array][reference-array] `[T; N]`. + + > array-type → `A` *[type]* *[const]* + + The tag `A` is followed by the *[type]* of the array followed by a *[const]* for the array size. + +* `S` — A [slice][reference-slice] `[T]`. + + > slice-type → `S` *[type]* + + The tag `S` is followed by the *[type]* of the slice. + +* `T` — A [tuple][reference-tuple] `(T1, T2, T3, ...)`. + + > tuple-type → `T` {*[type]*} `E` + + The tag `T` is followed by one or more [type]s indicating the type of each field, followed by a terminating `E` character. + + Note that a zero-length tuple (unit) is encoded with the `u` *[basic-type]*. + +* `R` — A [reference][reference-shared-reference] `&T`. + + > ref-type → `R` *[lifetime]*opt *[type]* + + The tag `R` is followed by an optional *[lifetime]* followed by the *[type]* of the reference. + The lifetime is not included if it has been erased. + +* `Q` — A [mutable reference][reference-mutable-reference] `&mut T`. + + > mut-ref-type → `Q` *[lifetime]*opt *[type]* + + The tag `Q` is followed by an optional *[lifetime]* followed by the *[type]* of the mutable reference. + The lifetime is not included if it has been erased. + +* `P` — A [constant raw pointer][reference-raw-pointer] `*const T`. + + The tag `P` is followed by the *[type]* of the pointer. + + > const-ptr-type → `P` *[type]* + +* `O` — A [mutable raw pointer][reference-raw-pointer] `*mut T`. + + > mut-ptr-type → `O` *[type]* + + The tag `O` is followed by the *[type]* of the pointer. + +* `F` — A [function pointer][reference-fn-pointer] `fn(…) -> …`. + + > fn-type → `F` *[fn-sig]* + > + > fn-sig → *[binder]*opt `U`opt (`K` *[abi]*)opt {*[type]*} `E` *[type]* + > + > abi → \ + >       `C` \ + >    | *[undisambiguated-identifier]* + + The tag `F` is followed by a *[fn-sig]* of the function signature. + A *fn-sig* is the signature for a function pointer. + + It starts with an optional *[binder]* which represents the higher-ranked trait bounds (`for<…>`). + + Following that is an optional `U` character which is present for an `unsafe` function. + + Following that is an optional `K` character which indicates that an *[abi]* is specified. + If the ABI is not specified, it is assumed to be the `"Rust"` ABI. + + The *[abi]* can be the letter `C` to indicate it is the `"C"` ABI. + Otherwise it is an *[undisambiguated-identifier]* of the ABI string with dashes converted to underscores. + + Following that is zero or more [type]s which indicate the input parameters of the function. + + Following that is the character `E` and then the *[type]* of the return value. + +[fn-sig]: #fn-sig +[abi]: #abi + +* `D` — A [trait object][reference-trait-object] `dyn Trait + Send + 'a`. + + > dyn-trait-type → `D` *[dyn-bounds]* *[lifetime]* + > + > dyn-bounds → *[binder]*opt {*[dyn-trait]*} `E` + > + > dyn-trait → *[path]* {*[dyn-trait-assoc-binding]*} + > + > dyn-trait-assoc-binding → `p` *[undisambiguated-identifier]* *[type]* + + The tag `D` is followed by a *[dyn-bounds]* which encodes the trait bounds, + followed by a *[lifetime]* of the trait object lifetime bound. + + A *dyn-bounds* starts with an optional *[binder]* which represents the higher-ranked trait bounds (`for<…>`). + Following that is a sequence of *[dyn-trait]* terminated by the character `E`. + + Each *[dyn-trait]* represents a trait bound, which consists of a *[path]* to the trait followed by zero or more *[dyn-trait-assoc-binding]* which list the associated types. + + Each *[dyn-trait-assoc-binding]* consists of a character `p` followed a *[undisambiguated-identifier]* representing the associated binding name, and finally a *[type]*. + +[dyn-bounds]: #dyn-bounds +[dyn-trait]: #dyn-trait +[dyn-trait-assoc-binding]: #dyn-trait-assoc-binding + + +* A *[path]* to a named type. + +* A *[backref]* to refer to a previously encoded type. + +> **Recommended Demangling** +> +> A *[type]* may be displayed as the type it represents, using typical Rust syntax to represent the type. + +> Example: +> ```rust +> fn main() { +> example::<[u16; 8]>(); +> } +> +> pub fn example() {} +> ``` +> +> The symbol for function `example` is: +> +> ```text +> _RINvCs7qp2U7fqm6G_7mycrate7exampleAtj8_EB2_ +> │││├┘│ +> ││││ └─── end of generic args +> │││└───── const data 8 +> ││└────── const type usize +> │└─────── array element type u16 +> └──────── array type +> ``` +> +> Recommended demangling: `mycrate::example::<[u16; 8]>` + +## Binder +[binder]: #binder + +> binder → `G` *[base-62-number]* + +A *binder* represents the number of [higher-ranked trait bound][reference-hrtb] lifetimes to bind. +It consists of the character `G` followed by a *[base-62-number]*. +The value 1 should be added to the *[base-62-number]* when decoding +(such that the *base-62-number* encoding of `_` is interpreted as having 1 binder). + +A *lifetime* rule can then refer to these numbered lifetimes. +The lowest indices represent the innermost lifetimes. +The number of bound lifetimes is the value of *[base-62-number]* plus one. + +For example, in `for<'a, 'b> fn(for<'c> fn (...))`, any [lifetime]s in `...` +(but not inside more binders) will observe the indices 1, 2, and 3 to refer to `'c`, `'b`, and `'a`, respectively. + +> **Recommended Demangling** +> +> A *binder* may be printed using `for<…>` syntax listing the lifetimes as recommended in *[lifetime]*. +> See *[lifetime]* for an example. + +## Backref +[backref]: #backref + +> backref → `B` *[base-62-number]* + +A *backref* is used to refer to a previous part of the mangled symbol. +This provides a simple form of compression to reduce the length of the mangled symbol. +This can help reduce the amount of work and resources needed by the compiler, linker, and loader. + +It consists of the character `B` followed by a *[base-62-number]*. +The number indicates the 0-based offset in bytes starting from just after the `_R` prefix of the symbol. +The *backref* represents the corresponding element starting at that position. + +backrefs always refer to a position before the *backref* itself. + +The *backref* compression relies on the fact that all substitutable symbol elements have a self-terminating mangled form. +Given the start position of the encoded node, the grammar guarantees that it is always unambiguous where the node ends. +This is ensured by not allowing optional or repeating elements at the end of substitutable productions. + +> **Recommended Demangling** +> +> A *backref* should be demangled by rendering the element that it points to. +> Care should be considered when handling deeply nested backrefs to avoid using too much stack. + +> Example: +> ```rust +> fn main() { +> example::(); +> } +> +> struct Example; +> +> pub fn example() {} +> ``` +> +> The symbol for function `example` is: +> +> ```text +> _RINvCs7qp2U7fqm6G_7mycrate7exampleNtB2_7ExampleBw_EB2_ +> │├┘ │├┘ │├┘ +> ││ ││ ││ +> ││ ││ │└── backref to offset 3 (crate-root) +> ││ ││ └─── backref for instantiating-crate path +> ││ │└────── backref to offset 33 (path to Example) +> ││ └─────── backref for second generic-arg +> │└───────────────── backref to offset 3 (crate-root) +> └────────────────── backref for first generic-arg (first segment of Example path) +> ``` +> +> Recommended demangling: `mycrate::example::` + +## Instantiating crate +[instantiating-crate]: #instantiating-crate + +> instantiating-crate → *[path]* + +The *instantiating-crate* is an optional element of the *[symbol-name]* which can be used to indicate which crate is instantiating the symbol. +It consists of a single *[path]*. + +This helps differentiate symbols that would otherwise be identical, +for example the monomorphization of a function from an external crate may result in a duplicate if another crate is also instantiating the same generic function with the same types. + +In practice, the instantiating crate is also often the crate where the symbol is defined, +so it is usually encoded as a *[backref]* to the *[crate-root]* encoded elsewhere in the symbol. + +> **Recommended Demangling** +> +> The *instantiating-crate* usually need not be displayed. + +> Example: +> ```rust +> std::path::Path::new("example"); +> ``` +> +> The symbol for `Path::new::` instantiated from the `mycrate` crate is: +> +> ```text +> _RINvMsY_NtCseXNvpPnDBDp_3std4pathNtB6_4Path3neweECs7qp2U7fqm6G_7mycrate +> └──┬───┘ +> │ +> └── instantiating crate identifier `mycrate` +> ``` +> +> Recommended demangling: `::new::` + +## Vendor-specific suffix +[vendor-specific-suffix]: #vendor-specific-suffix +[suffix]: #vendor-specific-suffix + +> vendor-specific-suffix → (`.` | `$`) *[suffix]* +> +> suffix → {*byte*} + +The *vendor-specific-suffix* is an optional element at the end of the *[symbol-name]*. +It consists of either a `.` or `$` character followed by zero or more bytes. +There are no restrictions on the characters following the period or dollar sign. + +This suffix is added as needed by the implementation. +One example where this can happen is when locally unique names need to become globally unique. +LLVM can append a `.llvm.` suffix during LTO to ensure a unique name, +and `$` can be used for thread-local data on Mach-O. +In these situations it's generally fine to ignore the suffix; +the suffixed name has the same semantics as the original. + +> **Recommended Demangling** +> +> The *vendor-specific-suffix* usually need not be displayed. + +> Example: +> ```rust +> # use std::cell::RefCell; +> thread_local! { +> pub static EXAMPLE: RefCell = RefCell::new(1); +> } +> ``` +> +> The symbol for `EXAMPLE` on macOS may have the following for thread-local data: +> +> ```text +> _RNvNvNvCs7qp2U7fqm6G_7mycrate7EXAMPLE7___getit5___KEY$tlv$init +> └───┬───┘ +> │ +> └── vendor-specific-suffix +> ``` +> +> Recommended demangling: `mycrate::EXAMPLE::__getit::__KEY` + +## Common rules +[decimal-number]: #common-rules +[digit]: #common-rules +[non-zero-digit]: #common-rules +[lower]: #common-rules +[upper]: #common-rules + +> [decimal-number] → \ +>       `0` \ +>    | *[non-zero-digit]* {*[digit]*} +> +> [non-zero-digit] → `1` | `2` | `3` | `4` | `5` | `6` | `7` | `8` | `9` \ +> [digit] → `0` | *[non-zero-digit]* +> +> [lower] → `a` |`b` |`c` |`d` |`e` |`f` |`g` |`h` |`i` |`j` |`k` |`l` |`m` |`n` |`o` |`p` |`q` |`r` |`s` |`t` |`u` |`v` |`w` |`x` |`y` |`z` +> +> [upper] → `A` | `B` | `C` | `D` | `E` | `F` | `G` | `H` | `I` | `J` | `K` | `L` | `M` | `N` | `O` | `P` | `Q` | `R` | `S` | `T` | `U` | `V` | `W` | `X` | `Y` | `Z` + +A *decimal-number* is encoded as one or more [digit]s indicating a numeric value in decimal. + +The value zero is encoded as a single byte `0`. +Beware that there are situations where `0` may be followed by another digit that should not be decoded as part of the decimal-number. +For example, a zero-length *[identifier]* within a *[nested-path]* which is in turn inside another *[nested-path]* will result in two identifiers in a row, where the first one only has the encoding of `0`. + +A *digit* is an ASCII number. + +A *lower* and *upper* is an ASCII lower and uppercase letter respectively. + +## base-62-number +[base-62-number]: #base-62-number + +> [base-62-number] → { *[digit]* | *[lower]* | *[upper]* } `_` + +A *base-62-number* is an encoding of a numeric value. +It uses ASCII numbers and lowercase and uppercase letters. +The value is terminated with the `_` character. +If the value is 0, then the encoding is the `_` character without any digits. +Otherwise, one is subtracted from the value, and it is encoded with the mapping: + +* `0`-`9` maps to 0-9 +* `a`-`z` maps to 10 to 35 +* `A`-`Z` maps to 36 to 61 + +The number is repeatedly divided by 62 (with integer division round towards zero) +to choose the next character in the sequence. +The remainder of each division is used in the mapping to choose the next character. +This is repeated until the number is 0. +The final sequence of characters is then reversed. + +Decoding is a similar process in reverse. + +Examples: + +| Value | Encoding | +|-------|----------| +| 0 | `_` | +| 1 | `0_` | +| 11 | `a_` | +| 62 | `Z_` | +| 63 | `10_` | +| 1000 | `g7_` | + +## Symbol grammar summary +[summary]: #symbol-grammar-summary + +The following is a summary of all of the productions of the symbol grammar. + +> [symbol-name] → `_R` *[decimal-number]*opt *[path]* *[instantiating-crate]*opt *[vendor-specific-suffix]*opt +> +> [path] → \ +>       *[crate-root]* \ +>    | *[inherent-impl]* \ +>    | *[trait-impl]* \ +>    | *[trait-definition]* \ +>    | *[nested-path]* \ +>    | *[generic-args]* \ +>    | *[backref]* +> +> [crate-root] → `C` *[identifier]* \ +> [inherent-impl] → `M` *[impl-path]* *[type]* \ +> [trait-impl] → `X` *[impl-path]* *[type]* *[path]* \ +> [trait-definition] → `Y` *[type]* *[path]* \ +> [nested-path] → `N` *[namespace]* *[path]* *[identifier]* \ +> [generic-args] → `I` *[path]* {*[generic-arg]*} `E` +> +> [identifier] → *[disambiguator]*opt *[undisambiguated-identifier]* \ +> [undisambiguated-identifier] → `u`opt *[decimal-number]* `_`opt *[bytes]* \ +> [bytes] → {*UTF-8 bytes*} +> +> [disambiguator] → `s` *[base-62-number]* +> +> [impl-path] → *[disambiguator]*opt *[path]* +> +> [type] → \ +>       *[basic-type]* \ +>    | *[array-type]* \ +>    | *[slice-type]* \ +>    | *[tuple-type]* \ +>    | *[ref-type]* \ +>    | *[mut-ref-type]* \ +>    | *[const-ptr-type]* \ +>    | *[mut-ptr-type]* \ +>    | *[fn-type]* \ +>    | *[dyn-trait-type]* \ +>    | *[path]* \ +>    | *[backref]* +> +> [basic-type] → *[lower]* \ +> [array-type] → `A` *[type]* *[const]* \ +> [slice-type] → `S` *[type]* \ +> [tuple-type] → `T` {*[type]*} `E` \ +> [ref-type] → `R` *[lifetime]*opt *[type]* \ +> [mut-ref-type] → `Q` *[lifetime]*opt *[type]* \ +> [const-ptr-type] → `P` *[type]* \ +> [mut-ptr-type] → `O` *[type]* \ +> [fn-type] → `F` *[fn-sig]* \ +> [dyn-trait-type] → `D` *[dyn-bounds]* *[lifetime]* +> +> [namespace] → *[lower]* | *[upper]* +> +> [generic-arg] → \ +>       *[lifetime]* \ +>    | *[type]* \ +>    | `K` *[const]* +> +> [lifetime] → `L` *[base-62-number]* +> +> [const] → \ +>       *[type]* *[const-data]* \ +>    | `p` \ +>    | *[backref]* +> +> [const-data] → `n`opt {*[hex-digit]*} `_` +> +> [hex-digit] → *[digit]* | `a` | `b` | `c` | `d` | `e` | `f` +> +> [fn-sig] → *[binder]*opt `U`opt (`K` *[abi]*)opt {*[type]*} `E` *[type]* +> +> [abi] → \ +>       `C` \ +>    | *[undisambiguated-identifier]* +> +> [dyn-bounds] → *[binder]*opt {*[dyn-trait]*} `E` \ +> [dyn-trait] → *[path]* {*[dyn-trait-assoc-binding]*} \ +> [dyn-trait-assoc-binding] → `p` *[undisambiguated-identifier]* *[type]* +> +> [binder] → `G` *[base-62-number]* +> +> [backref] → `B` *[base-62-number]* +> +> [instantiating-crate] → *[path]* +> +> [vendor-specific-suffix] → (`.` | `$`) *[suffix]* \ +> [suffix] → {*byte*} +> +> [decimal-number] → \ +>       `0` \ +>    | *[non-zero-digit]* {*[digit]*} +> +> [base-62-number] → { *[digit]* | *[lower]* | *[upper]* } `_` +> +> [non-zero-digit] → `1` | `2` | `3` | `4` | `5` | `6` | `7` | `8` | `9` \ +> [digit] → `0` | *[non-zero-digit]* \ +> [lower] → `a` |`b` |`c` |`d` |`e` |`f` |`g` |`h` |`i` |`j` |`k` |`l` |`m` |`n` |`o` |`p` |`q` |`r` |`s` |`t` |`u` |`v` |`w` |`x` |`y` |`z` \ +> [upper] → `A` | `B` | `C` | `D` | `E` | `F` | `G` | `H` | `I` | `J` | `K` | `L` | `M` | `N` | `O` | `P` | `Q` | `R` | `S` | `T` | `U` | `V` | `W` | `X` | `Y` | `Z` + +## Encoding of Rust entities + +The following are guidelines for how Rust entities are encoded in a symbol. +The compiler has some latitude in how an entity is encoded as long as the symbol is unambiguous. + +* Named functions, methods, and statics shall be represented by a *[path]* production. + +* Paths should be rooted at the inner-most entity that can act as a path root. + Roots can be crate-ids, inherent impls, trait impls, and (for items within default methods) trait definitions. + +* The compiler is free to choose disambiguation indices and namespace tags from + the reserved ranges as long as it ascertains identifier unambiguity. + +* Generic arguments that are equal to the default should not be encoded in order to save space. + + +[RFC 2603]: https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html +[reference-array]: ../../reference/types/array.html +[reference-fn-pointer]: ../../reference/types/function-pointer.html +[reference-hrtb]: ../../reference/trait-bounds.html#higher-ranked-trait-bounds +[reference-identifiers]: ../../reference/identifiers.html +[reference-implementations]: ../../reference/items/implementations.html +[reference-inherent-impl]: ../../reference/items/implementations.html#inherent-implementations +[reference-mutable-reference]: ../../reference/types/pointer.html#mutable-references-mut +[reference-paths]: ../../reference/paths.html +[reference-raw-pointer]: ../../reference/types/pointer.html#raw-pointers-const-and-mut +[reference-shared-reference]: ../../reference/types/pointer.html#shared-references- +[reference-slice]: ../../reference/types/slice.html +[reference-track_caller]: ../../reference/attributes/codegen.html#the-track_caller-attribute +[reference-trait-impl]: ../../reference/items/implementations.html#trait-implementations +[reference-trait-object]: ../../reference/types/trait-object.html +[reference-traits]: ../../reference/items/traits.html +[reference-tuple]: ../../reference/types/tuple.html +[reference-types]: ../../reference/types.html diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md index b512135d92770..12a8b2b8db4b6 100644 --- a/src/doc/rustdoc/src/SUMMARY.md +++ b/src/doc/rustdoc/src/SUMMARY.md @@ -7,6 +7,7 @@ - [How to write documentation](how-to-write-documentation.md) - [What to include (and exclude)](write-documentation/what-to-include.md) - [The `#[doc]` attribute](write-documentation/the-doc-attribute.md) + - [Re-exports](write-documentation/re-exports.md) - [Linking to items by name](write-documentation/linking-to-items-by-name.md) - [Documentation tests](write-documentation/documentation-tests.md) - [Rustdoc-specific lints](lints.md) diff --git a/src/doc/rustdoc/src/how-to-read-rustdoc.md b/src/doc/rustdoc/src/how-to-read-rustdoc.md index ccd77fb17e938..9deb7009cfe00 100644 --- a/src/doc/rustdoc/src/how-to-read-rustdoc.md +++ b/src/doc/rustdoc/src/how-to-read-rustdoc.md @@ -105,6 +105,16 @@ will match these queries: But it *does not* match `Result` or `Result>`. +Function signature searches also support arrays and slices. The explicit name +`primitive:slice` and `primitive:array` can be used to match a slice +or array of bytes, while square brackets `[u8]` will match either one. Empty +square brackets, `[]`, will match any slice regardless of what it contains. + +Paths are supported as well, you can look for `Vec::new` or `Option::Some` or +even `module::module_child::another_child::struct::field`. Whitespace characters +are considered the same as `::`, so if you write `Vec new`, it will be +considered the same as `Vec::new`. + ### Shortcuts Pressing `S` while focused elsewhere on the page will move focus to the diff --git a/src/doc/rustdoc/src/how-to-write-documentation.md b/src/doc/rustdoc/src/how-to-write-documentation.md index 38fd1db5c21e8..1fa9f81447607 100644 --- a/src/doc/rustdoc/src/how-to-write-documentation.md +++ b/src/doc/rustdoc/src/how-to-write-documentation.md @@ -165,15 +165,15 @@ extensions: ### Strikethrough Text may be rendered with a horizontal line through the center by wrapping the -text with two tilde characters on each side: +text with one or two tilde characters on each side: ```text -An example of ~~strikethrough text~~. +An example of ~~strikethrough text~~. You can also use ~single tildes~. ``` This example will render as: -> An example of ~~strikethrough text~~. +> An example of ~~strikethrough text~~. You can also use ~single tildes~. This follows the [GitHub Strikethrough extension][strikethrough]. diff --git a/src/doc/rustdoc/src/lints.md b/src/doc/rustdoc/src/lints.md index fd57b07964481..f15e6e451e757 100644 --- a/src/doc/rustdoc/src/lints.md +++ b/src/doc/rustdoc/src/lints.md @@ -412,3 +412,37 @@ help: if you meant to use a literal backtick, escape it warning: 1 warning emitted ``` + +## `redundant_explicit_links` + +This lint is **warned by default**. It detects explicit links that are same +as computed automatic links. +This usually means the explicit links is removeable. For example: + +```rust +#![warn(rustdoc::redundant_explicit_links)] // note: unnecessary - warns by default. + +/// add takes 2 [`usize`](usize) and performs addition +/// on them, then returns result. +pub fn add(left: usize, right: usize) -> usize { + left + right +} +``` + +Which will give: + +```text +error: redundant explicit rustdoc link + --> src/lib.rs:3:27 + | +3 | /// add takes 2 [`usize`](usize) and performs addition + | ^^^^^ + | + = note: Explicit link does not affect the original link +note: the lint level is defined here + --> src/lib.rs:1:9 + | +1 | #![deny(rustdoc::redundant_explicit_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = help: Remove explicit link instead +``` diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index ae180439d23d4..f69156b7c05e2 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -200,6 +200,7 @@ To do so, the `#[doc(keyword = "...")]` attribute is used. Example: ```rust #![feature(rustdoc_internals)] +#![allow(internal_features)] /// Some documentation about the keyword. #[doc(keyword = "keyword")] @@ -310,6 +311,8 @@ the source. ### `--show-type-layout`: add a section to each type's docs describing its memory layout +* Tracking issue: [#113248](https://github.com/rust-lang/rust/issues/113248) + Using this flag looks like this: ```bash diff --git a/src/doc/rustdoc/src/what-is-rustdoc.md b/src/doc/rustdoc/src/what-is-rustdoc.md index 7a444d77c09d1..7179ee0cf0370 100644 --- a/src/doc/rustdoc/src/what-is-rustdoc.md +++ b/src/doc/rustdoc/src/what-is-rustdoc.md @@ -37,7 +37,7 @@ top, with no contents. ## Configuring rustdoc There are two problems with this: first, why does it -think that our package is named "lib"? Second, why does it not have any +think that our crate is named "lib"? Second, why does it not have any contents? The first problem is due to `rustdoc` trying to be helpful; like `rustc`, diff --git a/src/doc/rustdoc/src/write-documentation/re-exports.md b/src/doc/rustdoc/src/write-documentation/re-exports.md new file mode 100644 index 0000000000000..593428b8a70a5 --- /dev/null +++ b/src/doc/rustdoc/src/write-documentation/re-exports.md @@ -0,0 +1,172 @@ +# Re-exports + +Let's start by explaining what are re-exports. To do so, we will use an example where we are +writing a library (named `lib`) with some types dispatched in sub-modules: + +```rust +pub mod sub_module1 { + pub struct Foo; +} +pub mod sub_module2 { + pub struct AnotherFoo; +} +``` + +Users can import them like this: + +```rust,ignore (inline) +use lib::sub_module1::Foo; +use lib::sub_module2::AnotherFoo; +``` + +But what if you want the types to be available directly at the crate root or if we don't want the +modules to be visible for users? That's where re-exports come in: + +```rust,ignore (inline) +// `sub_module1` and `sub_module2` are not visible outside. +mod sub_module1 { + pub struct Foo; +} +mod sub_module2 { + pub struct AnotherFoo; +} +// We re-export both types: +pub use crate::sub_module1::Foo; +pub use crate::sub_module2::AnotherFoo; +``` + +And now users will be able to do: + +```rust,ignore (inline) +use lib::{Foo, AnotherFoo}; +``` + +And since both `sub_module1` and `sub_module2` are private, users won't be able to import them. + +Now what's interesting is that the generated documentation for this crate will show both `Foo` and +`AnotherFoo` directly at the crate root, meaning they have been inlined. There are a few rules to +know whether or not a re-exported item will be inlined. + +## Inlining rules + +If a public item comes from a private module, it will be inlined: + +```rust,ignore (inline) +mod private_module { + pub struct Public; +} +pub mod public_mod { + // `Public` will inlined here since `private_module` is private. + pub use super::private_module::Public; +} +// `Public` will not be inlined here since `public_mod` is public. +pub use self::public_mod::Public; +``` + +Likewise, if an item inherits `#[doc(hidden)]` from any of its ancestors, it will be inlined: + +```rust,ignore (inline) +#[doc(hidden)] +pub mod public_mod { + pub struct Public; +} +// `Public` be inlined since its parent (`public_mod`) has `#[doc(hidden)]`. +pub use self::public_mod::Public; +``` + +If an item has `#[doc(hidden)]`, it won't be inlined (nor visible in the generated documentation): + +```rust,ignore (inline) +// This struct won't be visible. +#[doc(hidden)] +pub struct Hidden; + +// This re-export won't be visible. +pub use self::Hidden as InlinedHidden; +``` + +The same applies on re-exports themselves: if you have multiple re-exports and some of them have +`#[doc(hidden)]`, then these ones (and only these) own't appear in the documentation: + +```rust,ignore (inline) +mod private_mod { + /// First + pub struct InPrivate; +} + +/// Second +#[doc(hidden)] +pub use self::private_mod::InPrivate as Hidden; +/// Third +pub use self::Hidden as Visible; +``` + +In this case, `InPrivate` will be inlined as `Visible`. However, its documentation will be +`First Third` and not `First Second Third` because the re-export with `Second` as documentation has +`#[doc(hidden)]`, therefore, all its attributes are ignored. + +## Inlining with `#[doc(inline)]` + +You can use the `#[doc(inline)]` attribute if you want to force an item to be inlined: + +```rust,ignore (inline) +pub mod public_mod { + pub struct Public; +} +#[doc(inline)] +pub use self::public_mod::Public; +``` + +With this code, even though `public_mod::Public` is public and present in the documentation, the +`Public` type will be present both at the crate root and in the `public_mod` module. + +## Preventing inlining with `#[doc(no_inline)]` + +On the opposite of the `#[doc(inline)]` attribute, if you want to prevent an item from being +inlined, you can use `#[doc(no_inline)]`: + +```rust,ignore (inline) +mod private_mod { + pub struct Public; +} +#[doc(no_inline)] +pub use self::private_mod::Public; +``` + +In the generated documentation, you will see a re-export at the crate root and not the type +directly. + +## Attributes + +When an item is inlined, its doc comments and most of its attributes will be inlined along with it: + +```rust,ignore (inline) +mod private_mod { + /// First + #[cfg(a)] + pub struct InPrivate; + /// Second + #[cfg(b)] + pub use self::InPrivate as Second; +} + +/// Third +#[doc(inline)] +#[cfg(c)] +pub use self::private_mod::Second as Visible; +``` + +In this case, `Visible` will have as documentation `First Second Third` and will also have as `cfg`: +`#[cfg(a, b, c)]`. + +[Intra-doc links](./linking-to-items-by-name.md) are resolved relative to where the doc comment is +defined. + +There are a few attributes which are not inlined though: + * `#[doc(alias="")]` + * `#[doc(inline)]` + * `#[doc(no_inline)]` + * `#[doc(hidden)]` (because the re-export itself and its attributes are ignored). + +All other attributes are inherited when inlined, so that the documentation matches the behavior if +the inlined item was directly defined at the spot where it's shown. diff --git a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md index 8ecf05f0e121f..046d018543f38 100644 --- a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md +++ b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md @@ -223,12 +223,18 @@ Now we'll have a `Re-exports` line, and `Bar` will not link to anywhere. One special case: In Rust 2018 and later, if you `pub use` one of your dependencies, `rustdoc` will not eagerly inline it as a module unless you add `#[doc(inline)]`. +If you want to know more about inlining rules, take a look at the +[`re-exports` chapter](./re-exports.md). + ### `hidden` Any item annotated with `#[doc(hidden)]` will not appear in the documentation, unless -the `strip-hidden` pass is removed. +the `strip-hidden` pass is removed. Re-exported items where one of its ancestors has +`#[doc(hidden)]` will be considered the same as private. + +You can find more information in the [`re-exports` chapter](./re-exports.md). ### `alias` diff --git a/src/doc/style-guide/src/README.md b/src/doc/style-guide/src/README.md index adb73a7eef6e0..f4d759673700d 100644 --- a/src/doc/style-guide/src/README.md +++ b/src/doc/style-guide/src/README.md @@ -16,20 +16,71 @@ Rust code has similar formatting, less mental effort is required to comprehend a new project, lowering the barrier to entry for new developers. Thus, there are productivity benefits to using a formatting tool (such as -rustfmt), and even larger benefits by using a community-consistent formatting, -typically by using a formatting tool's default settings. +`rustfmt`), and even larger benefits by using a community-consistent +formatting, typically by using a formatting tool's default settings. +## The default Rust style + +The Rust Style Guide defines the default Rust style, and *recommends* that +developers and tools follow the default Rust style. Tools such as `rustfmt` use +the style guide as a reference for the default style. Everything in this style +guide, whether or not it uses language such as "must" or the imperative mood +such as "insert a space ..." or "break the line after ...", refers to the +default style. + +This should not be interpreted as forbidding developers from following a +non-default style, or forbidding tools from adding any particular configuration +options. ## Formatting conventions ### Indentation and line width -* Use spaces, not tabs. -* Each level of indentation must be four spaces (that is, all indentation - outside of string literals and comments must be a multiple of four). -* The maximum width for a line is 100 characters. -* A tool should be configurable for all three of these variables. +- Use spaces, not tabs. +- Each level of indentation must be 4 spaces (that is, all indentation + outside of string literals and comments must be a multiple of 4). +- The maximum width for a line is 100 characters. + +#### Block indent +Prefer block indent over visual indent: + +```rust +// Block indent +a_function_call( + foo, + bar, +); + +// Visual indent +a_function_call(foo, + bar); +``` + +This makes for smaller diffs (e.g., if `a_function_call` is renamed in the above +example) and less rightward drift. + +### Trailing commas + +In comma-separated lists of any kind, use a trailing comma when followed by a +newline: + +```rust +function_call( + argument, + another_argument, +); + +let array = [ + element, + another_element, + yet_another_element, +]; +``` + +This makes moving code (e.g., by copy and paste) easier, and makes diffs +smaller, as appending or removing items does not require modifying another line +to add or remove a comma. ### Blank lines @@ -48,17 +99,13 @@ fn bar() {} fn baz() {} ``` -Formatting tools should make the bounds on blank lines configurable: there -should be separate minimum and maximum numbers of newlines between both -statements and (top-level) items (i.e., four options). As described above, the -defaults for both statements and items should be minimum: 1, maximum: 2. - - ### [Module-level items](items.md) + ### [Statements](statements.md) + ### [Expressions](expressions.md) -### [Types](types.md) +### [Types](types.md) ### Comments @@ -67,17 +114,17 @@ formatter might skip formatting of comments. Prefer line comments (`//`) to block comments (`/* ... */`). -When using line comments there should be a single space after the opening sigil. +When using line comments, put a single space after the opening sigil. -When using single-line block comments there should be a single space after the -opening sigil and before the closing sigil. Multi-line block comments should -have a newline after the opening sigil and before the closing sigil. +When using single-line block comments, put a single space after the opening +sigil and before the closing sigil. For multi-line block comments, put a +newline after the opening sigil, and a newline before the closing sigil. -Prefer to put a comment on its own line. Where a comment follows code, there -should be a single space before it. Where a block comment is inline, there -should be surrounding whitespace as if it were an identifier or keyword. There -should be no trailing whitespace after a comment or at the end of any line in a -multi-line comment. Examples: +Prefer to put a comment on its own line. Where a comment follows code, put a +single space before it. Where a block comment appears inline, use surrounding +whitespace as if it were an identifier or keyword. Do not include trailing +whitespace after a comment or at the end of any line in a multi-line comment. +Examples: ```rust // A comment on an item. @@ -126,7 +173,7 @@ Prefer line comments (`///`) to block comments (`/** ... */`). Prefer outer doc comments (`///` or `/** ... */`), only use inner doc comments (`//!` and `/*! ... */`) to write module-level or crate-level documentation. -Doc comments should come before attributes. +Put doc comments before attributes. ### Attributes @@ -139,6 +186,11 @@ For attributes with argument lists, format like functions. ```rust #[repr(C)] #[foo(foo, bar)] +#[long_multi_line_attribute( + split, + across, + lines, +)] struct CRepr { #![repr(C)] x: f32, @@ -146,18 +198,20 @@ struct CRepr { } ``` -For attributes with an equal sign, there should be a single space before and -after the `=`, e.g., `#[foo = 42]`. +For attributes with an equal sign, put a single space before and after the `=`, +e.g., `#[foo = 42]`. There must only be a single `derive` attribute. Note for tool authors: if combining multiple `derive` attributes into a single attribute, the ordering of -the derived names should be preserved. E.g., `#[derive(bar)] #[derive(foo)] -struct Baz;` should be formatted to `#[derive(bar, foo)] struct Baz;`. +the derived names must generally be preserved for correctness: +`#[derive(Foo)] #[derive(Bar)] struct Baz;` must be formatted to +`#[derive(Foo, Bar)] struct Baz;`. ### *small* items -In many places in this guide we specify that a formatter may format an item -differently if it is *small*, for example struct literals: +In many places in this guide we specify formatting that depends on a code +construct being *small*. For example, single-line vs multi-line struct +literals: ```rust // Normal formatting @@ -166,7 +220,7 @@ Foo { f2: another_expression(), } -// *small* formatting +// "small" formatting Foo { f1, f2 } ``` @@ -179,10 +233,6 @@ complexity of an item (for example, that all components must be simple names, not more complex sub-expressions). For more discussion on suitable heuristics, see [this issue](https://github.com/rust-lang-nursery/fmt-rfcs/issues/47). -Tools should give the user an option to ignore such heuristics and always use -the normal formatting. - - ## [Non-formatting conventions](advice.md) ## [Cargo.toml conventions](cargo.md) diff --git a/src/doc/style-guide/src/SUMMARY.md b/src/doc/style-guide/src/SUMMARY.md index 004692fa6a22b..64540c3997cda 100644 --- a/src/doc/style-guide/src/SUMMARY.md +++ b/src/doc/style-guide/src/SUMMARY.md @@ -2,10 +2,12 @@ [Introduction](README.md) -- [Module-level items](items.md) +- [Items](items.md) - [Statements](statements.md) - [Expressions](expressions.md) -- [Types](types.md) -- [Non-formatting conventions](advice.md) +- [Types and Bounds](types.md) +- [Other style advice](advice.md) - [`Cargo.toml` conventions](cargo.md) -- [Principles used for deciding these guidelines](principles.md) +- [Guiding principles and rationale](principles.md) +- [Rust style editions](editions.md) +- [Nightly-only syntax](nightly.md) diff --git a/src/doc/style-guide/src/advice.md b/src/doc/style-guide/src/advice.md index ab4b92b0a2478..65cf8cb6e909c 100644 --- a/src/doc/style-guide/src/advice.md +++ b/src/doc/style-guide/src/advice.md @@ -18,16 +18,16 @@ if y { ## Names - * Types shall be `UpperCamelCase`, - * Enum variants shall be `UpperCamelCase`, - * Struct fields shall be `snake_case`, - * Function and method names shall be `snake_case`, - * Local variables shall be `snake_case`, - * Macro names shall be `snake_case`, - * Constants (`const`s and immutable `static`s) shall be `SCREAMING_SNAKE_CASE`. - * When a name is forbidden because it is a reserved word (e.g., `crate`), use a - trailing underscore to make the name legal (e.g., `crate_`), or use raw - identifiers if possible. +- Types shall be `UpperCamelCase`, +- Enum variants shall be `UpperCamelCase`, +- Struct fields shall be `snake_case`, +- Function and method names shall be `snake_case`, +- Local variables shall be `snake_case`, +- Macro names shall be `snake_case`, +- Constants (`const`s and immutable `static`s) shall be `SCREAMING_SNAKE_CASE`. +- When a name is forbidden because it is a reserved word (such as `crate`), + either use a raw identifier (`r#crate`) or use a trailing underscore + (`crate_`). Don't misspell the word (`krate`). ### Modules diff --git a/src/doc/style-guide/src/cargo.md b/src/doc/style-guide/src/cargo.md index 13b96ca8c5e9d..d3b67ae45825d 100644 --- a/src/doc/style-guide/src/cargo.md +++ b/src/doc/style-guide/src/cargo.md @@ -1,4 +1,4 @@ -# Cargo.toml conventions +# `Cargo.toml` conventions ## Formatting conventions @@ -25,16 +25,17 @@ not indent any key names; start all key names at the start of a line. Use multi-line strings (rather than newline escape sequences) for any string values that include multiple lines, such as the crate description. -For array values, such as a list of authors, put the entire list on the same +For array values, such as a list of features, put the entire list on the same line as the key, if it fits. Otherwise, use block indentation: put a newline after the opening square bracket, indent each item by one indentation level, put a comma after each item (including the last), and put the closing square bracket at the start of a line by itself after the last item. ```rust -authors = [ - "A Uthor ", - "Another Author ", +some_feature = [ + "another_feature", + "yet_another_feature", + "some_dependency?/some_feature", ] ``` @@ -54,11 +55,11 @@ version = "4.5.6" ## Metadata conventions -The authors list should consist of strings that each contain an author name -followed by an email address in angle brackets: `Full Name `. -It should not contain bare email addresses, or names without email addresses. -(The authors list may also include a mailing list address without an associated -name.) +The authors list, if present, should consist of strings that each contain an +author name followed by an email address in angle brackets: `Full Name +`. It should not contain bare email addresses, or names without +email addresses. (The authors list may also include a mailing list address +without an associated name.) The license field must contain a valid [SPDX expression](https://spdx.org/spdx-specification-21-web-version#h.jxpfx0ykyb60), diff --git a/src/doc/style-guide/src/editions.md b/src/doc/style-guide/src/editions.md new file mode 100644 index 0000000000000..5c67a185b8ffa --- /dev/null +++ b/src/doc/style-guide/src/editions.md @@ -0,0 +1,46 @@ +# Rust style editions + +The default Rust style evolves over time, as Rust does. However, to avoid +breaking established code style, and CI jobs checking code style, changes to +the default Rust style only appear in *style editions*. + +Code written in a given +[Rust edition](https://doc.rust-lang.org/edition-guide/) +uses the corresponding Rust style edition by default. To make it easier to +migrate code style separately from the semantic changes between Rust editions, +formatting tools such as `rustfmt` allow updating the style edition separately +from the Rust edition. + +The current version of the style guide describes the latest Rust style edition. +Each distinct past style will have a corresponding archived version of the +style guide. + +Note that archived versions of the style guide do not document formatting for +newer Rust constructs that did not exist at the time that version of the style +guide was archived. However, each style edition will still format all +constructs valid in that Rust edition, with the style of newer constructs +coming from the first subsequent style edition providing formatting rules for +that construct (without any of the systematic/global changes from that style +edition). + +Not all Rust editions have corresponding changes to the Rust style. For +instance, Rust 2015, Rust 2018, and Rust 2021 all use the same style edition. + +## Rust 2024 style edition + +This style guide describes the Rust 2024 style edition. The Rust 2024 style +edition is currently nightly-only and may change before the release of Rust +2024. + +For a full history of changes in the Rust 2024 style edition, see the git +history of the style guide. Notable changes in the Rust 2024 style edition +include: + +- Miscellaneous `rustfmt` bugfixes. + +## Rust 2015/2018/2021 style edition + +The archived version of the style guide at + +describes the style edition corresponding to Rust 2015, Rust 2018, and Rust +2021. diff --git a/src/doc/style-guide/src/expressions.md b/src/doc/style-guide/src/expressions.md index 96f66c89c259f..32c604f9f3e10 100644 --- a/src/doc/style-guide/src/expressions.md +++ b/src/doc/style-guide/src/expressions.md @@ -1,11 +1,14 @@ -## Expressions +# Expressions -### Blocks +## Blocks -A block expression should have a newline after the initial `{` and before the -terminal `}`. Any qualifier before the block (e.g., `unsafe`) should always be -on the same line as the opening brace, and separated with a single space. The -contents of the block should be block indented: +A block expression must have a newline after the initial `{` and before the +terminal `}`, unless it qualifies to be written as a single line based on +another style rule. + +A keyword before the block (such as `unsafe` or `async`) must be on the same +line as the opening brace, with a single space between the keyword and the +opening brace. Indent the contents of the block. ```rust fn block_as_stmt() { @@ -40,7 +43,7 @@ fn unsafe_block_as_stmt() { } ``` -If a block has an attribute, it should be on its own line: +If a block has an attribute, put it on its own line before the block: ```rust fn block_as_stmt() { @@ -54,18 +57,18 @@ fn block_as_stmt() { } ``` -Avoid writing comments on the same line as the braces. +Avoid writing comments on the same lines as either of the braces. -An empty block should be written as `{}`. +Write an empty block as `{}`. -A block may be written on a single line if: +Write a block on a single line if: -* it is either used in expression position (not statement position) or is an - unsafe block in statement position -* contains a single-line expression and no statements -* contains no comments +- it is either used in expression position (not statement position) or is an + unsafe block in statement position, +- it contains a single-line expression and no statements, and +- it contains no comments -A single line block should have spaces after the opening brace and before the +For a single-line block, put spaces after the opening brace and before the closing brace. Examples: @@ -113,18 +116,17 @@ fn main() { } ``` - -### Closures +## Closures Don't put any extra spaces before the first `|` (unless the closure is prefixed -by `move`); put a space between the second `|` and the expression of the -closure. Between the `|`s, you should use function definition syntax, however, -elide types where possible. +by a keyword such as `move`); put a space between the second `|` and the +expression of the closure. Between the `|`s, use function definition syntax, +but elide types where possible. Use closures without the enclosing `{}`, if possible. Add the `{}` when you have -a return type, when there are statements, there are comments in the body, or the -body expression spans multiple lines and is a control-flow expression. If using -braces, follow the rules above for blocks. Examples: +a return type, when there are statements, when there are comments inside the +closure, or when the body expression is a control-flow expression that spans +multiple lines. If using braces, follow the rules above for blocks. Examples: ```rust |arg1, arg2| expr @@ -152,16 +154,16 @@ move |arg1: i32, arg2: i32| -> i32 { } ``` +## Struct literals -### Struct literals +If a struct literal is *small*, format it on a single line, and do not use a +trailing comma. If not, split it across multiple lines, with each field on its +own block-indented line, and use a trailing comma. -If a struct literal is *small* it may be formatted on a single line. If not, -each field should be on it's own, block-indented line. There should be a -trailing comma in the multi-line form only. There should be a space after the -colon only. +For each `field: value` entry, put a space after the colon only. -There should be a space before the opening brace. In the single-line form there -should be spaces after the opening brace and before the closing brace. +Put a space before the opening brace. In the single-line form, put spaces after +the opening brace and before the closing brace. ```rust Foo { field1, field2: 0 } @@ -172,19 +174,24 @@ let f = Foo { ``` Functional record update syntax is treated like a field, but it must never have -a trailing comma. There should be no space after `..`. +a trailing comma. Do not put a space after `..`. +```rust let f = Foo { field1, ..an_expr }; +``` +## Tuple literals -### Tuple literals +Use a single-line form where possible. Do not put spaces between the opening +parenthesis and the first element, or between the last element and the closing +parenthesis. Separate elements with a comma followed by a space. -Use a single-line form where possible. There should not be spaces around the -parentheses. Where a single-line form is not possible, each element of the tuple -should be on its own block-indented line and there should be a trailing comma. +Where a single-line form is not possible, write the tuple across +multiple lines, with each element of the tuple on its own block-indented line, +and use a trailing comma. ```rust (a, b, c) @@ -195,17 +202,24 @@ let x = ( ); ``` +## Tuple struct literals -### Tuple struct literals +Do not put space between the identifier and the opening parenthesis. Otherwise, +follow the rules for tuple literals: -There should be no space between the identifier and the opening parenthesis. -Otherwise, follow the rules for tuple literals, e.g., `Foo(a, b)`. +```rust +Foo(a, b, c) +let x = Foo( + a_long_expr, + another_very_long_expr, +); +``` -### Enum literals +## Enum literals Follow the formatting rules for the various struct literals. Prefer using the -name of the enum as a qualifying name, unless the enum is in the prelude. E.g., +name of the enum as a qualifying name, unless the enum is in the prelude: ```rust Foo::Bar(a, b) @@ -216,27 +230,31 @@ Foo::Baz { Ok(an_expr) ``` +## Array literals -### Array literals +Write small array literals on a single line. Do not put spaces between the opening +square bracket and the first element, or between the last element and the closing +square bracket. Separate elements with a comma followed by a space. -For simple array literals, avoid line breaking, no spaces around square -brackets, contents of the array should be separated by commas and spaces. If -using the repeating initialiser, there should be a space after the semicolon -only. Apply the same rules if using the `vec!` or similar macros (always use -square brackets here). Examples: +If using the repeating initializer, put a space after the semicolon +only. + +Apply the same rules if using `vec!` or similar array-like macros; always use +square brackets with such macros. Examples: ```rust fn main() { - [1, 2, 3]; - vec![a, b, c, d]; + let x = [1, 2, 3]; + let y = vec![a, b, c, d]; let a = [42; 10]; } ``` -If a line must be broken, prefer breaking only after the `;`, if possible. -Otherwise, follow the rules below for function calls. In any case, the contents -of the initialiser should be block indented and there should be line breaks -after the opening bracket and before the closing bracket: +For arrays that have to be broken across lines, if using the repeating +initializer, break after the `;`, not before. Otherwise, follow the rules below +for function calls. In any case, block-indent the contents of the initializer, +and put line breaks after the opening square bracket and before the closing +square bracket: ```rust fn main() { @@ -252,14 +270,14 @@ fn main() { } ``` +## Array accesses, indexing, and slicing -### Array accesses, indexing, and slicing. - -No spaces around the square brackets, avoid breaking lines if possible, never -break a line between the target expression and the opening bracket. If the -indexing expression covers multiple lines, then it should be block indented and -there should be newlines after the opening brackets and before the closing -bracket. However, this should be avoided where possible. +Don't put spaces around the square brackets. Avoid breaking lines if possible. +Never break a line between the target expression and the opening square +bracket. If the indexing expression must be broken onto a subsequent line, or +spans multiple lines itself, then block-indent the indexing expression, and put +newlines after the opening square bracket and before the closing square +bracket: Examples: @@ -275,27 +293,29 @@ fn main() { } ``` -### Unary operations +## Unary operations Do not include a space between a unary op and its operand (i.e., `!x`, not `! x`). However, there must be a space after `&mut`. Avoid line-breaking between a unary operator and its operand. -### Binary operations +## Binary operations Do include spaces around binary ops (i.e., `x + 1`, not `x+1`) (including `=` and other assignment operators such as `+=` or `*=`). For comparison operators, because for `T op U`, `&T op &U` is also implemented: if you have `t: &T`, and `u: U`, prefer `*t op u` to `t op &u`. In general, -within expressions, prefer dereferencing to taking references. +within expressions, prefer dereferencing to taking references, unless necessary +(e.g. to avoid an unnecessarily expensive operation). -Use parentheses liberally, do not necessarily elide them due to precedence. +Use parentheses liberally; do not necessarily elide them due to precedence. Tools should not automatically insert or remove parentheses. Do not use spaces to indicate precedence. -If line-breaking, put the operator on a new line and block indent. Put each -sub-expression on its own line. E.g., +If line-breaking, block-indent each subsequent line. For assignment operators, +break after the operator; for all other operators, put the operator on the +subsequent line. Put each sub-expression on its own line: ```rust foo_bar @@ -308,7 +328,7 @@ foo_bar Prefer line-breaking at an assignment operator (either `=` or `+=`, etc.) rather than at other binary operators. -### Control flow +## Control flow Do not include extraneous parentheses for `if` and `while` expressions. @@ -327,7 +347,7 @@ if (true) { Do include extraneous parentheses if it makes an arithmetic or logic expression easier to understand (`(x * 15) + (y * 20)` is fine) -### Function calls +## Function calls Do not put a space between the function name, and the opening parenthesis. @@ -337,7 +357,7 @@ Do put a space between an argument, and the comma which precedes it. Prefer not to break a line in the callee expression. -#### Single-line calls +### Single-line calls Do not put a space between the function name and open paren, between the open paren and the first argument, or between the last argument and the close paren. @@ -348,13 +368,13 @@ Do not put a comma after the last argument. foo(x, y, z) ``` -#### Multi-line calls +### Multi-line calls If the function call is not *small*, it would otherwise over-run the max width, -or any argument or the callee is multi-line, then the call should be formatted -across multiple lines. In this case, each argument should be on it's own block- -indented line, there should be a newline after the opening parenthesis and -before the closing parenthesis, and there should be a trailing comma. E.g., +or any argument or the callee is multi-line, then format the call across +multiple lines. In this case, put each argument on its own block-indented line, +break after the opening parenthesis and before the closing parenthesis, +and use a trailing comma: ```rust a_function_call( @@ -363,8 +383,7 @@ a_function_call( ) ``` - -### Method calls +## Method calls Follow the function rules for calling. @@ -374,20 +393,20 @@ Do not put any spaces around the `.`. x.foo().bar().baz(x, y, z); ``` +## Macro uses -### Macro uses - -Macros which can be parsed like other constructs should be formatted like those +If a macro can be parsed like other constructs, format it like those constructs. For example, a macro use `foo!(a, b, c)` can be parsed like a -function call (ignoring the `!`), therefore it should be formatted following the -rules for function calls. +function call (ignoring the `!`), so format it using the rules for function +calls. -#### Special case macros +### Special case macros -Macros which take a format string and where all other arguments are *small* may -be formatted with arguments before and after the format string on a single line -and the format string on its own line, rather than putting each argument on its -own line. For example, +For macros which take a format string, if all other arguments are *small*, +format the arguments before the format string on a single line if they fit, and +format the arguments after the format string on a single line if they fit, with +the format string on its own line. If the arguments are not small or do not +fit, put each on its own line as with a function. For example: ```rust println!( @@ -402,8 +421,7 @@ assert_eq!( ); ``` - -### Casts (`as`) +## Casts (`as`) Put spaces before and after `as`: @@ -411,16 +429,15 @@ Put spaces before and after `as`: let cstr = "Hi\0" as *const str as *const [u8] as *const std::os::raw::c_char; ``` +## Chains of fields and method calls -### Chains of fields and method calls +A chain is a sequence of field accesses, method calls, and/or uses of the try +operator `?`. E.g., `a.b.c().d` or `foo?.bar().baz?`. -A chain is a sequence of field accesses and/or method calls. A chain may also -include the try operator ('?'). E.g., `a.b.c().d` or `foo?.bar().baz?`. - -Prefer formatting on one line if possible, and the chain is *small*. If -formatting on multiple lines, each field access or method call in the chain -should be on its own line with the line-break before the `.` and after any `?`. -Each line should be block-indented. E.g., +Format the chain on one line if it is "small" and otherwise possible to do so. +If formatting on multiple lines, put each field access or method call in the +chain on its own line, with the line-break before the `.` and after any `?`. +Block-indent each subsequent line: ```rust let foo = bar @@ -429,13 +446,16 @@ let foo = bar ``` If the length of the last line of the first element plus its indentation is -less than or equal to the indentation of the second line (and there is space), -then combine the first and second lines, e.g., +less than or equal to the indentation of the second line, then combine the +first and second lines if they fit. Apply this rule recursively. ```rust x.baz? .qux() +x.y.z + .qux() + let foo = x .baz? .qux(); @@ -447,14 +467,13 @@ foo( .qux(); ``` -#### Multi-line elements +### Multi-line elements -If any element in a chain is formatted across multiple lines, then that element -and any later elements must be on their own line. Earlier elements may be kept -on a single line. E.g., +If any element in a chain is formatted across multiple lines, put that element +and any later elements on their own lines. ```rust -a.b.c()?.d +a.b.c()? .foo( an_expr, another_expr, @@ -483,18 +502,18 @@ self.pre_comment.as_ref().map_or( ) ``` -### Control flow expressions +## Control flow expressions This section covers `if`, `if let`, `loop`, `while`, `while let`, and `for` expressions. -The keyword, any initial clauses, and the opening brace of the block should be -on a single line. The usual rules for [block formatting](#blocks) should be -applied to the block. +Put the keyword, any initial clauses, and the opening brace of the block all on +a single line, if they fit. Apply the usual rules for [block +formatting](#blocks) to the block. -If there is an `else` component, then the closing brace, `else`, any following -clause, and the opening brace should all be on the same line. There should be a -single space before and after the `else` keyword. For example: +If there is an `else` component, then put the closing brace, `else`, any +following clause, and the opening brace all on the same line, with a single +space before and after the `else` keyword: ```rust if ... { @@ -512,10 +531,10 @@ if let ... { } ``` -If the control line needs to be broken, then prefer to break before the `=` in -`* let` expressions and before `in` in a `for` expression; the following line -should be block indented. If the control line is broken for any reason, then the -opening brace should be on its own line and not indented. Examples: +If the control line needs to be broken, prefer to break before the `=` in `* +let` expressions and before `in` in a `for` expression; block-indent the +following line. If the control line is broken for any reason, put the opening +brace on its own line, not indented. Examples: ```rust while let Some(foo) @@ -538,10 +557,10 @@ if a_long_expression } ``` -Where the initial clause is multi-lined and ends with one or more closing -parentheses, square brackets, or braces, and there is nothing else on that line, -and that line is not indented beyond the indent on the first line of the control -flow expression, then the opening brace of the block should be put on the same +Where the initial clause spans multiple lines and ends with one or more closing +parentheses, square brackets, or braces, and there is nothing else on that +line, and that line is not indented beyond the indent on the first line of the +control flow expression, then put the opening brace of the block on the same line with a preceding space. For example: ```rust @@ -554,12 +573,11 @@ if !self.config.file_lines().intersects( } ``` +### Single line `if else` -#### Single line `if else` - -Formatters may place an `if else` or `if let else` on a single line if it occurs -in expression context (i.e., is not a standalone statement), it contains a -single `else` clause, and is *small*. For example: +Put an `if else` or `if let else` on a single line if it occurs in expression +context (i.e., is not a standalone statement), it contains a single `else` +clause, and is *small*: ```rust let y = if x { 0 } else { 1 }; @@ -578,12 +596,11 @@ if x { } ``` +## Match -### Match - -Prefer not to line-break inside the discriminant expression. There must always -be a line break after the opening brace and before the closing brace. The match -arms must be block indented once: +Prefer not to line-break inside the discriminant expression. Always break after +the opening brace and before the closing brace. Block-indent the match arms +once: ```rust match foo { @@ -597,7 +614,7 @@ let x = match foo.bar.baz() { Use a trailing comma for a match arm if and only if not using a block. -Never start a match arm pattern with `|`, e.g., +Never start a match arm pattern with `|`: ```rust match foo { @@ -607,14 +624,13 @@ match foo { | a_very_long_pattern | another_pattern | yet_another_pattern - | a_forth_pattern => { + | a_fourth_pattern => { ... } } ``` -Prefer - +Prefer: ```rust match foo { @@ -622,7 +638,7 @@ match foo { a_very_long_pattern | another_pattern | yet_another_pattern - | a_forth_pattern => { + | a_fourth_pattern => { ... } } @@ -632,11 +648,12 @@ Avoid splitting the left-hand side (before the `=>`) of a match arm where possible. If the right-hand side of the match arm is kept on the same line, never use a block (unless the block is empty). -If the right-hand side consists of multiple statements or has line comments or -the start of the line cannot be fit on the same line as the left-hand side, use -a block. +If the right-hand side consists of multiple statements, or has line comments, +or the start of the line does not fit on the same line as the left-hand side, +use a block. Do not flatten a right-hand side block containing a single macro call +because its expanded form could contain a trailing semicolon. -The body of a block arm should be block indented once. +Block-indent the body of a block arm. Examples: @@ -657,12 +674,16 @@ match foo { bar => {} // Trailing comma on last item. foo => bar, + baz => qux!(), + lorem => { + ipsum!() + } } ``` If the body is a single expression with no line comments and not a control flow -expression, then it may be started on the same line as the right-hand side. If -not, then it must be in a block. Example, +expression, start it on the same line as the left-hand side. If not, then it +must be in a block. Example: ```rust match foo { @@ -684,13 +705,13 @@ match foo { } ``` -#### Line-breaking +### Line-breaking -Where it is possible to use a block form on the right-hand side and avoid -breaking the left-hand side, do that. E.g. +If using a block form on the right-hand side of a match arm makes it possible +to avoid breaking on the left-hand side, do that: ```rust - // Assuming the following line does done fit in the max width + // Assuming the following line does not fit in the max width a_very_long_pattern | another_pattern => ALongStructName { ... }, @@ -719,7 +740,7 @@ body on a new line: If required to break the pattern, put each clause of the pattern on its own line with no additional indent, breaking before the `|`. If there is an `if` -clause, then you must use the above form: +clause, use the above form: ```rust a_very_long_pattern @@ -739,7 +760,7 @@ clause, then you must use the above form: ``` If the pattern is multi-line, and the last line is less wide than the indent, do -not put the `if` clause on a newline. E.g., +not put the `if` clause on a new line. E.g., ```rust Token::Dimension { @@ -751,9 +772,9 @@ not put the `if` clause on a newline. E.g., } ``` -If every clause in a pattern is *small*, but does not fit on one line, then the -pattern may be formatted across multiple lines with as many clauses per line as -possible. Again break before a `|`: +If every clause in a pattern is *small*, but the whole pattern does not fit on +one line, then format the pattern across multiple lines with as many clauses +per line as possible. Again, break before a `|`: ```rust foo | bar | baz @@ -762,27 +783,27 @@ possible. Again break before a `|`: } ``` -We define a pattern clause to be *small* if it matches the following grammar: +We define a pattern clause to be *small* if it fits on a single line and +matches "small" in the following grammar: ``` -[small, ntp]: - - single token - - `&[single-line, ntp]` +small: + - small_no_tuple + - unary tuple constructor: `(` small_no_tuple `,` `)` + - `&` small -[small]: - - `[small, ntp]` - - unary tuple constructor `([small, ntp])` - - `&[small]` +small_no_tuple: + - single token + - `&` small_no_tuple ``` E.g., `&&Some(foo)` matches, `Foo(4, Bar)` does not. - -### Combinable expressions +## Combinable expressions Where a function call has a single argument, and that argument is formatted -across multiple-lines, the outer call may be formatted as if it were a single- -line call. The same combining behaviour may be applied to any similar +across multiple-lines, format the outer call as if it were a single-line call, +if the result fits. Apply the same combining behaviour to any similar expressions which have multi-line, block-indented lists of sub-expressions delimited by parentheses (e.g., macros or tuple struct literals). E.g., @@ -800,15 +821,24 @@ foo(|param| { action(); foo(param) }) + +let x = combinable([ + an_expr, + another_expr, +]); + +let arr = [combinable( + an_expr, + another_expr, +)]; ``` -Such behaviour should extend recursively, however, tools may choose to limit the -depth of nesting. +Apply this behavior recursively. -Only where the multi-line sub-expression is a closure with an explicit block, -this combining behaviour may be used where there are other arguments, as long as -all the arguments and the first line of the closure fit on the first line, the -closure is the last argument, and there is only one closure argument: +For a function with multiple arguments, if the last argument is a multi-line +closure with an explicit block, there are no other closure arguments, and all +the arguments and the first line of the closure fit on the first line, use the +same combining behavior: ```rust foo(first_arg, x, |param| { @@ -817,34 +847,30 @@ foo(first_arg, x, |param| { }) ``` - -### Ranges +## Ranges Do not put spaces in ranges, e.g., `0..10`, `x..=y`, `..x.len()`, `foo..`. When writing a range with both upper and lower bounds, if the line must be -broken, break before the range operator and block indent the second line: +broken within the range, break before the range operator and block indent the +second line: ```rust a_long_expression ..another_long_expression ``` -For the sake of indicating precedence, we recommend that if either bound is a -compound expression, then use parentheses around it, e.g., `..(x + 1)`, -`(x.f)..(x.f.len())`, or `0..(x - 10)`. +For the sake of indicating precedence, if either bound is a compound +expression, use parentheses around it, e.g., `..(x + 1)`, `(x.f)..(x.f.len())`, +or `0..(x - 10)`. - -### Hexadecimal literals +## Hexadecimal literals Hexadecimal literals may use upper- or lower-case letters, but they must not be mixed within the same literal. Projects should use the same case for all literals, but we do not make a recommendation for either lower- or upper-case. -Tools should have an option to convert mixed case literals to upper-case, and -may have an option to convert all literals to either lower- or upper-case. - ## Patterns -Patterns should be formatted like their corresponding expressions. See the -section on `match` for additional formatting for patterns in match arms. +Format patterns like their corresponding expressions. See the section on +`match` for additional formatting for patterns in match arms. diff --git a/src/doc/style-guide/src/items.md b/src/doc/style-guide/src/items.md index 2835975355fca..a6d941f6d0454 100644 --- a/src/doc/style-guide/src/items.md +++ b/src/doc/style-guide/src/items.md @@ -1,22 +1,25 @@ -## Items +# Items + +Items consist of the set of things permitted at the top level of a module. +However, Rust also allows some items to appear within some other types of +items, such as within a function. The same formatting conventions apply whether +an item appears at module level or within another item. `extern crate` statements must be first in a file. They must be ordered alphabetically. `use` statements, and module *declarations* (`mod foo;`, not `mod { ... }`) -must come before other items. We recommend that imports come before module -declarations; if imports and modules are separated, then they should be ordered -alphabetically. When sorting, `self` and `super` must come before any other -names. Module declarations should not be moved if they are annotated with -`#[macro_use]`, since that may be semantics changing. - -Tools should make the above ordering optional. +must come before other items. Put imports before module declarations. Sort each +alphabetically, except that `self` and `super` must come before any other +names. +Don't automatically move module declarations annotated with `#[macro_use]`, +since that might change semantics. -### Function definitions +## Function definitions -In Rust, one finds functions by searching for `fn [function-name]`; It's -important that you style your code so that it's very searchable in this way. +In Rust, people often find functions by searching for `fn [function-name]`, so +the formatting of function definitions must enable this. The proper ordering and spacing is: @@ -43,14 +46,13 @@ fn foo( Note the trailing comma on the last argument. - -### Tuples and tuple structs +## Tuples and tuple structs Write the type list as you would a parameter list to a function. Build a tuple or tuple struct as you would call a function. -#### Single-line +### Single-line ```rust struct Bar(Type1, Type2); @@ -59,12 +61,13 @@ let x = Bar(11, 22); let y = (11, 22, 33); ``` -### Enums +## Enums In the declaration, put each variant on its own line, block indented. -Format each variant accordingly as either a struct, tuple struct, or identifier, -which doesn't require special formatting (but without the `struct` keyword. +Format each variant accordingly as either a struct (but without the `struct` +keyword), a tuple struct, or an identifier (which doesn't require special +formatting): ```rust enum FooBar { @@ -77,9 +80,9 @@ enum FooBar { } ``` -If a struct variant is [*small*](index.html#small-items), it may be formatted on -one line. In this case, do not use a trailing comma for the field list, but do -put spaces around each brace: +If a struct variant is [*small*](index.html#small-items), format it on one +line. In this case, do not use a trailing comma for the field list, but do put +spaces around each brace: ```rust enum FooBar { @@ -88,12 +91,11 @@ enum FooBar { ``` In an enum with multiple struct variants, if any struct variant is written on -multiple lines, then the multi-line formatting should be used for all struct -variants. However, such a situation might be an indication that you should -factor out the fields of the variant into their own struct. +multiple lines, use the multi-line formatting for all struct variants. However, +such a situation might be an indication that you should factor out the fields +of the variant into their own struct. - -### Structs and Unions +## Structs and Unions Struct names follow on the same line as the `struct` keyword, with the opening brace on the same line when it fits within the right margin. All struct fields @@ -134,12 +136,11 @@ union Foo { } ``` +## Tuple structs -### Tuple structs - -Put the whole struct on one line if possible. Types in the parentheses should be -separated by a comma and space with no trailing comma. No spaces around the -parentheses or semi-colon: +Put the whole struct on one line if possible. Separate types within the +parentheses using a comma and space. Don't use a trailing comma for a +single-line tuple struct. Don't put spaces around the parentheses or semicolon: ```rust pub struct Foo(String, u8); @@ -148,9 +149,11 @@ pub struct Foo(String, u8); Prefer unit structs to empty tuple structs (these only exist to simplify code generation), e.g., `struct Foo;` rather than `struct Foo();`. -For more than a few fields, prefer a proper struct with named fields. Given -this, a tuple struct should always fit on one line. If it does not, block format -the fields with a field on each line and a trailing comma: +For more than a few fields (in particular if the tuple struct does not fit on +one line), prefer a proper struct with named fields. + +For a multi-line tuple struct, block-format the fields with a field on each +line and a trailing comma: ```rust pub struct Foo( @@ -159,12 +162,11 @@ pub struct Foo( ); ``` +## Traits -### Traits - -Trait items should be block-indented. If there are no items, the trait may be -formatted on a single line. Otherwise there should be line-breaks after the -opening brace and before the closing brace: +Use block-indent for trait items. If there are no items, format the trait (including its `{}`) +on a single line. Otherwise, break after the opening brace and before +the closing brace: ```rust trait Foo {} @@ -174,8 +176,8 @@ pub trait Bar { } ``` -If the trait has bounds, there should be a space after the colon but not before -and before and after each `+`, e.g., +If the trait has bounds, put a space after the colon but not before, +and put spaces around each `+`, e.g., ```rust trait Foo: Debug + Bar {} @@ -198,12 +200,11 @@ pub trait IndexRanges: } ``` +## Impls -### Impls - -Impl items should be block indented. If there are no items, the impl may be -formatted on a single line. Otherwise there should be line-breaks after the -opening brace and before the closing brace: +Use block-indent for impl items. If there are no items, format the impl +(including its `{}`) on a single line. Otherwise, break after the opening brace +and before the closing brace: ```rust impl Foo {} @@ -225,15 +226,13 @@ impl Bar } ``` - -### Extern crate +## Extern crate `extern crate foo;` -Use spaces around keywords, no spaces around the semi-colon. +Use spaces around keywords, no spaces around the semicolon. - -### Modules +## Modules ```rust mod foo { @@ -245,9 +244,9 @@ mod foo; ``` Use spaces around keywords and before the opening brace, no spaces around the -semi-colon. +semicolon. -### macro\_rules! +## `macro_rules!` Use `{}` for the full definition of the macro. @@ -256,17 +255,16 @@ macro_rules! foo { } ``` - -### Generics +## Generics Prefer to put a generics clause on one line. Break other parts of an item declaration rather than line-breaking a generics clause. If a generics clause is -large enough to require line-breaking, you should prefer to use a `where` clause -instead. +large enough to require line-breaking, prefer a `where` clause instead. -Do not put spaces before or after `<` nor before `>`. Only put a space after `>` -if it is followed by a word or opening brace, not an opening parenthesis. There -should be a space after each comma and no trailing comma. +Do not put spaces before or after `<` nor before `>`. Only put a space after +`>` if it is followed by a word or opening brace, not an opening parenthesis. +Put a space after each comma. Do not use a trailing comma for a single-line +generics clause. ```rust fn foo(x: Vec, y: Vec) ... @@ -274,10 +272,9 @@ fn foo(x: Vec, y: Vec) ... impl SomeType { ... ``` -If the generics clause must be formatted across multiple lines, each parameter -should have its own block-indented line, there should be newlines after the -opening bracket and before the closing bracket, and the should be a trailing -comma. +If the generics clause must be formatted across multiple lines, put each +parameter on its own block-indented line, break after the opening `<` and +before the closing `>`, and use a trailing comma. ```rust fn foo< @@ -286,8 +283,7 @@ fn foo< >(x: Vec, y: Vec) ... ``` -If an associated type is bound in a generic type, then there should be spaces on -either side of the `=`: +If an associated type is bound in a generic type, put spaces around the `=`: ```rust > @@ -295,17 +291,18 @@ either side of the `=`: Prefer to use single-letter names for generic parameters. - -### `where` clauses +## `where` clauses These rules apply for `where` clauses on any item. -A `where` clause may immediately follow a closing bracket of any kind. -Otherwise, it must start a new line, with no indent. Each component of a `where` -clause must be on its own line and be block indented. There should be a trailing +If immediately following a closing bracket of any kind, write the keyword +`where` on the same line, with a space before it. + +Otherwise, put `where` on a new line at the same indentation level. Put each +component of a `where` clause on its own line, block-indented. Use a trailing comma, unless the clause is terminated with a semicolon. If the `where` clause -is followed by a block (or assignment), the block should be started on a new -line. Examples: +is followed by a block (or assignment), start that block on a new line. +Examples: ```rust fn function(args) @@ -349,12 +346,12 @@ where = Bar; ``` -If a `where` clause is very short, we recommend using an inline bound on the -type parameter. - +If a `where` clause is very short, prefer using an inline bound on the type +parameter. -If a component of a `where` clause is long, it may be broken before `+` and -further block indented. Each bound should go on its own line. E.g., +If a component of a `where` clause does not fit and contains `+`, break it +before each `+` and block-indent the continuation lines. Put each bound on its +own line. E.g., ```rust impl IndexRanges for T @@ -363,40 +360,14 @@ where + Index, Output = Self::Output> + Index, Output = Self::Output> + Index, Output = Self::Output> - + Index, Output = Self::Output> + Index -``` - -#### Option - `where_single_line` - -`where_single_line` is `false` by default. If `true`, then a where clause with -exactly one component may be formatted on a single line if the rest of the -item's signature is also kept on one line. In this case, there is no need for a -trailing comma and if followed by a block, no need for a newline before the -block. E.g., - -```rust -// May be single-lined. -fn foo(args) -> ReturnType -where T: Bound { - body -} - -// Must be multi-lined. -fn foo( - args -) -> ReturnType -where - T: Bound, -{ - body -} + + Index, Output = Self::Output> + + Index, ``` +## Type aliases -### Type aliases - -Type aliases should generally be kept on one line. If necessary to break the -line, do so after the `=`; the right-hand-side should be block indented: +Keep type aliases on one line when they fit. If necessary to break the line, do +so after the `=`, and block-indent the right-hand side: ```rust pub type Foo = Bar; @@ -418,29 +389,24 @@ where = AnEvenLongerType>; ``` +## Associated types -### Associated types - -Associated types should follow the guidelines above for type aliases. Where an -associated type has a bound, there should be a space after the colon but not -before: +Format associated types like type aliases. Where an associated type has a +bound, put a space after the colon but not before: ```rust pub type Foo: Bar; ``` +## extern items -### extern items - -When writing extern items (such as `extern "C" fn`), always be explicit about -the ABI. For example, write `extern "C" fn foo ...`, not `extern fn foo ...`, or +When writing extern items (such as `extern "C" fn`), always specify the ABI. +For example, write `extern "C" fn foo ...`, not `extern fn foo ...`, or `extern "C" { ... }`. +## Imports (`use` statements) -### Imports (`use` statements) - -If an import can be formatted on one line, do so. There should be no spaces -around braces. +Format imports on one line where possible. Don't put spaces around braces. ```rust use a::b::c; @@ -448,18 +414,16 @@ use a::b::d::*; use a::b::{foo, bar, baz}; ``` - -#### Large list imports +### Large list imports Prefer to use multiple imports rather than a multi-line import. However, tools -should not split imports by default (they may offer this as an option). +should not split imports by default. If an import does require multiple lines (either because a list of single names does not fit within the max width, or because of the rules for nested imports below), then break after the opening brace and before the closing brace, use a trailing comma, and block indent the names. - ```rust // Prefer foo::{long, list, of, imports}; @@ -472,15 +436,13 @@ foo::{ }; ``` - -#### Ordering of imports +### Ordering of imports A *group* of imports is a set of imports on the same or sequential lines. One or more blank lines or other items (e.g., a function) separate groups of imports. -Within a group of imports, imports must be sorted ascii-betically. Groups of -imports must not be merged or re-ordered. - +Within a group of imports, imports must be sorted ASCIIbetically (uppercase +before lowercase). Groups of imports must not be merged or re-ordered. E.g., input: @@ -505,33 +467,25 @@ use b; Because of `macro_use`, attributes must also start a new group and prevent re-ordering. -Note that tools which only have access to syntax (such as Rustfmt) cannot tell -which imports are from an external crate or the std lib, etc. - +### Ordering list import -#### Ordering list import - -Names in a list import must be sorted ascii-betically, but with `self` and +Names in a list import must be sorted ASCIIbetically, but with `self` and `super` first, and groups and glob imports last. This applies recursively. For example, `a::*` comes before `b::a` but `a::b` comes before `a::*`. E.g., `use foo::bar::{a, b::c, b::d, b::d::{x, y, z}, b::{self, r, s}};`. +### Normalisation -#### Normalisation - -Tools must make the following normalisations: - -* `use a::self;` -> `use a;` -* `use a::{};` -> (nothing) -* `use a::{b};` -> `use a::b;` +Tools must make the following normalisations, recursively: -And must apply these recursively. +- `use a::self;` -> `use a;` +- `use a::{};` -> (nothing) +- `use a::{b};` -> `use a::b;` Tools must not otherwise merge or un-merge import lists or adjust glob imports (without an explicit option). - -#### Nested imports +### Nested imports If there are any nested imports in a list import, then use the multi-line form, even if the import fits on one line. Each nested import must be on its own line, @@ -547,8 +501,7 @@ use a::b::{ }; ``` - -#### Merging/un-merging imports +### Merging/un-merging imports An example: diff --git a/src/doc/style-guide/src/nightly.md b/src/doc/style-guide/src/nightly.md new file mode 100644 index 0000000000000..66e7fa3c9f89c --- /dev/null +++ b/src/doc/style-guide/src/nightly.md @@ -0,0 +1,7 @@ +# Nightly + +This chapter documents style and formatting for nightly-only syntax. The rest of the style guide documents style for stable Rust syntax; nightly syntax only appears in this chapter. Each section here includes the name of the feature gate, so that searches (e.g. `git grep`) for a nightly feature in the Rust repository also turn up the style guide section. + +Style and formatting for nightly-only syntax should be removed from this chapter and integrated into the appropriate sections of the style guide at the time of stabilization. + +There is no guarantee of the stability of this chapter in contrast to the rest of the style guide. Refer to the style team policy for nightly formatting procedure regarding breaking changes to this chapter. diff --git a/src/doc/style-guide/src/principles.md b/src/doc/style-guide/src/principles.md index 2d203f264e623..ce57c649a2d0b 100644 --- a/src/doc/style-guide/src/principles.md +++ b/src/doc/style-guide/src/principles.md @@ -1,53 +1,29 @@ # Guiding principles and rationale -When deciding on style guidelines, the style team tried to be guided by the -following principles (in rough priority order): - -* readability - - scan-ability - - avoiding misleading formatting - - accessibility - readable and editable by users using the widest - variety of hardware, including non-visual accessibility interfaces - - readability of code in contexts without syntax highlighting or IDE - assistance, such as rustc error messages, diffs, grep, and other - plain-text contexts - -* aesthetics - - sense of 'beauty' - - consistent with other languages/tools - -* specifics - - compatibility with version control practices - preserving diffs, - merge-friendliness, etc. - - preventing right-ward drift - - minimising vertical space - -* application - - ease of manual application - - ease of implementation (in Rustfmt, and in other tools/editors/code generators) - - internal consistency - - simplicity of formatting rules - - -## Overarching guidelines - -Prefer block indent over visual indent. E.g., - -```rust -// Block indent -a_function_call( - foo, - bar, -); - -// Visual indent -a_function_call(foo, - bar); -``` - -This makes for smaller diffs (e.g., if `a_function_call` is renamed in the above -example) and less rightward drift. - -Lists should have a trailing comma when followed by a newline, see the block -indent example above. This choice makes moving code (e.g., by copy and paste) -easier and makes smaller diffs. +When deciding on style guidelines, the style team follows these guiding +principles (in rough priority order): + +- readability + - scan-ability + - avoiding misleading formatting + - accessibility - readable and editable by users using the widest + variety of hardware, including non-visual accessibility interfaces + - readability of code in contexts without syntax highlighting or IDE + assistance, such as rustc error messages, diffs, grep, and other + plain-text contexts + +- aesthetics + - sense of 'beauty' + - consistent with other languages/tools + +- specifics + - compatibility with version control practices - preserving diffs, + merge-friendliness, etc. + - preventing rightward drift + - minimising vertical space + +- application + - ease of manual application + - ease of implementation (in `rustfmt`, and in other tools/editors/code generators) + - internal consistency + - simplicity of formatting rules diff --git a/src/doc/style-guide/src/statements.md b/src/doc/style-guide/src/statements.md index 671e6d31a5775..6f322b3d65b54 100644 --- a/src/doc/style-guide/src/statements.md +++ b/src/doc/style-guide/src/statements.md @@ -1,7 +1,9 @@ -### Let statements +# Statements -There should be spaces after the `:` and on both sides of the `=` (if they are -present). No space before the semi-colon. +## Let statements + +Put a space after the `:` and on both sides of the `=` (if they are present). +Don't put a space before the semicolon. ```rust // A comment. @@ -12,20 +14,19 @@ let pattern: Type; let pattern = expr; ``` -If possible the declaration should be formatted on a single line. If this is not -possible, then try splitting after the `=`, if the declaration can fit on two -lines. The expression should be block indented. +If possible, format the declaration on a single line. If not possible, then try +splitting after the `=`, if the declaration fits on two lines. Block-indent the +expression. ```rust let pattern: Type = expr; ``` -If the first line does not fit on a single line, then split after the colon, -using block indentation. If the type covers multiple lines, even after line- -breaking after the `:`, then the first line may be placed on the same line as -the `:`, subject to the [combining rules](https://github.com/rust-lang-nursery/fmt-rfcs/issues/61) (WIP). - +If the first line still does not fit on a single line, split after the `:`, and +use block indentation. If the type requires multiple lines, even after +line-breaking after the `:`, then place the first line on the same line as the +`:`, subject to the [combining rules](expressions.html#combinable-expressions). ```rust let pattern: @@ -49,12 +50,12 @@ let (abcd, ``` If the expression covers multiple lines, if the first line of the expression -fits in the remaining space, it stays on the same line as the `=`, the rest of the -expression is not indented. If the first line does not fit, then it should start -on the next lines, and should be block indented. If the expression is a block -and the type or pattern cover multiple lines, then the opening brace should be -on a new line and not indented (this provides separation for the interior of the -block from the type), otherwise the opening brace follows the `=`. +fits in the remaining space, it stays on the same line as the `=`, and the rest +of the expression is not further indented. If the first line does not fit, then +put the expression on subsequent lines, block indented. If the expression is a +block and the type or pattern cover multiple lines, put the opening brace on a +new line and not indented (this provides separation for the interior of the +block from the type); otherwise, the opening brace follows the `=`. Examples: @@ -99,24 +100,68 @@ let Foo { ); ``` -#### else blocks (let-else statements) +### else blocks (let-else statements) + +A let statement can contain an `else` component, making it a let-else statement. +In this case, always apply the same formatting rules to the components preceding +the `else` block (i.e. the `let pattern: Type = initializer_expr` portion) +as described [for other let statements](#let-statements). + +Format the entire let-else statement on a single line if all the following are +true: + +* the entire statement is *short* +* the `else` block contains only a single-line expression and no statements +* the `else` block contains no comments +* the let statement components preceding the `else` block can be formatted on a single line + +```rust +let Some(1) = opt else { return }; +``` + +Otherwise, the let-else statement requires some line breaks. + +If breaking a let-else statement across multiple lines, never break between the +`else` and the `{`, and always break before the `}`. -If a let statement contains an `else` component, also known as a let-else statement, -then the `else` component should be formatted according to the same rules as the `else` block -in [control flow expressions (i.e. if-else, and if-let-else expressions)](./expressions.md#control-flow-expressions). -Apply the same formatting rules to the components preceding -the `else` block (i.e. the `let pattern: Type = initializer_expr ...` portion) -as described [above](#let-statements) +If the let statement components preceding the `else` can be formatted on a +single line, but the let-else does not qualify to be placed entirely on a +single line, put the `else {` on the same line as the initializer expression, +with a space between them, then break the line after the `{`. Indent the +closing `}` to match the `let`, and indent the contained block one step +further. + +```rust +let Some(1) = opt else { + return; +}; + +let Some(1) = opt else { + // nope + return +}; +``` + +If the let statement components preceding the `else` can be formatted on a +single line, but the `else {` does not fit on the same line, break the line +before the `else`. + +```rust + let Some(x) = some_really_really_really_really_really_really_really_really_really_long_name + else { + return; + }; +``` -Similarly to if-else expressions, if the initializer -expression is multi-lined, then the `else` keyword and opening brace of the block (i.e. `else {`) -should be put on the same line as the end of the initializer -expression with a preceding space if all the following are true: +If the initializer expression is multi-line, put the `else` keyword and opening +brace of the block (i.e. `else {`) on the same line as the end of the +initializer expression, with a space between them, if and only if all the +following are true: * The initializer expression ends with one or more closing parentheses, square brackets, and/or braces * There is nothing else on that line -* That line is not indented beyond the indent of the first line containing the `let` keyword +* That line has the same indentation level as the initial `let` keyword. For example: @@ -133,7 +178,9 @@ let Some(x) = y.foo( } ``` -Otherwise, the `else` keyword and opening brace should be placed on the next line after the end of the initializer expression, and should not be indented (the `else` keyword should be aligned with the `let` keyword). +Otherwise, put the `else` keyword and opening brace on the next line after the +end of the initializer expression, with the `else` keyword at the same +indentation level as the `let` keyword. For example: @@ -153,66 +200,57 @@ fn main() { return }; - let Some(x) = some_really_really_really_really_really_really_really_really_really_long_name + let Some(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) = + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb else { return; }; - let Some(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) = - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + let LongStructName(AnotherStruct { + multi, + line, + pattern, + }) = slice.as_ref() else { return; }; -} -``` - -##### Single line let-else statements - -The entire let-else statement may be formatted on a single line if all the following are true: - -* the entire statement is *short* -* the `else` block contains a single-line expression and no statements -* the `else` block contains no comments -* the let statement components preceding the `else` block can be formatted on a single line -```rust -let Some(1) = opt else { return }; - -let Some(1) = opt else { - return; -}; - -let Some(1) = opt else { - // nope - return -}; + let LongStructName(AnotherStruct { + multi, + line, + pattern, + }) = multi_line_function_call( + arg1, + arg2, + arg3, + arg4, + ) else { + return; + }; +} ``` -Formatters may allow users to configure the value of the threshold -used to determine whether a let-else statement is *short*. - -### Macros in statement position +## Macros in statement position -A macro use in statement position should use parentheses or square brackets as -delimiters and should be terminated with a semi-colon. There should be no spaces -between the name, `!`, the delimiters, or the `;`. +For a macro use in statement position, use parentheses or square brackets as +delimiters, and terminate it with a semicolon. Do not put spaces around the +name, `!`, the delimiters, or the `;`. ```rust // A comment. a_macro!(...); ``` +## Expressions in statement position -### Expressions in statement position - -There should be no space between the expression and the semi-colon. +Do not put space between the expression and the semicolon. ``` ; ``` -All expressions in statement position should be terminated with a semi-colon, -unless they end with a block or are used as the value for a block. +Terminate all expressions in statement position with a semicolon, unless they +end with a block or are used as the value for a block. E.g., @@ -229,7 +267,7 @@ loop { } ``` -Use a semi-colon where an expression has void type, even if it could be +Use a semicolon where an expression has void type, even if it could be propagated. E.g., ```rust diff --git a/src/doc/style-guide/src/types.md b/src/doc/style-guide/src/types.md index ae456ef21c8d4..b7921c8914e45 100644 --- a/src/doc/style-guide/src/types.md +++ b/src/doc/style-guide/src/types.md @@ -1,23 +1,22 @@ -## Types and Bounds +# Types and Bounds -### Single line formatting +## Single line formatting -* `[T]` no spaces -* `[T; expr]`, e.g., `[u32; 42]`, `[Vec; 10 * 2 + foo()]` (space after colon, no spaces around square brackets) -* `*const T`, `*mut T` (no space after `*`, space before type) -* `&'a T`, `&T`, `&'a mut T`, `&mut T` (no space after `&`, single spaces separating other words) -* `unsafe extern "C" fn<'a, 'b, 'c>(T, U, V) -> W` or `fn()` (single spaces around keywords and sigils, and after commas, no trailing commas, no spaces around brackets) -* `!` should be treated like any other type name, `Name` -* `(A, B, C, D)` (spaces after commas, no spaces around parens, no trailing comma unless it is a one-tuple) -* ` as SomeTrait>::Foo::Bar` or `Foo::Bar` or `::Foo::Bar` (no spaces around `::` or angle brackets, single spaces around `as`) -* `Foo::Bar` (spaces after commas, no trailing comma, no spaces around angle brackets) -* `T + T + T` (single spaces between types, and `+`). -* `impl T + T + T` (single spaces between keyword, types, and `+`). +- `[T]` no spaces +- `[T; expr]`, e.g., `[u32; 42]`, `[Vec; 10 * 2 + foo()]` (space after colon, no spaces around square brackets) +- `*const T`, `*mut T` (no space after `*`, space before type) +- `&'a T`, `&T`, `&'a mut T`, `&mut T` (no space after `&`, single spaces separating other words) +- `unsafe extern "C" fn<'a, 'b, 'c>(T, U, V) -> W` or `fn()` (single spaces around keywords and sigils, and after commas, no trailing commas, no spaces around brackets) +- `!` gets treated like any other type name, `Name` +- `(A, B, C, D)` (spaces after commas, no spaces around parens, no trailing comma unless it is a one-tuple) +- ` as SomeTrait>::Foo::Bar` or `Foo::Bar` or `::Foo::Bar` (no spaces around `::` or angle brackets, single spaces around `as`) +- `Foo::Bar` (spaces after commas, no trailing comma, no spaces around angle brackets) +- `T + T + T` (single spaces between types, and `+`). +- `impl T + T + T` (single spaces between keyword, types, and `+`). -Parentheses used in types should not be surrounded by whitespace, e.g., `(Foo)` +Do not put space around parentheses used in types, e.g., `(Foo)` - -### Line breaks +## Line breaks Avoid breaking lines in types where possible. Prefer breaking at outermost scope, e.g., prefer @@ -37,13 +36,17 @@ Foo> ``` -`[T; expr]` may be broken after the `;` if necessary. +If a type requires line-breaks in order to fit, this section outlines where to +break such types if necessary. + +Break `[T; expr]` after the `;` if necessary. -Function types may be broken following the rules for function declarations. +Break function types following the rules for function declarations. -Generic types may be broken following the rules for generics. +Break generic types following the rules for generics. -Types with `+` may be broken after any `+` using block indent and breaking before the `+`. When breaking such a type, all `+`s should be line broken, e.g., +Break types with `+` by breaking before the `+` and block-indenting the +subsequent lines. When breaking such a type, break before *every* `+`: ```rust impl Clone diff --git a/src/doc/unstable-book/src/compiler-flags/no-parallel-llvm.md b/src/doc/unstable-book/src/compiler-flags/no-parallel-llvm.md new file mode 100644 index 0000000000000..f19ba16b6f70d --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/no-parallel-llvm.md @@ -0,0 +1,8 @@ +# `no-parallel-llvm` + +--------------------- + +This flag disables parallelization of codegen and linking, while otherwise preserving +behavior with regard to codegen units and LTO. + +This flag is not useful for regular users, but it can be useful for debugging the backend. Codegen issues commonly only manifest under specific circumstances, e.g. if multiple codegen units are used and ThinLTO is enabled. Serialization of these threaded configurations makes the use of LLVM debugging facilities easier, by avoiding the interleaving of output. diff --git a/src/doc/unstable-book/src/compiler-flags/path-options.md b/src/doc/unstable-book/src/compiler-flags/path-options.md new file mode 100644 index 0000000000000..0786ef1f16612 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/path-options.md @@ -0,0 +1,11 @@ +# `--print` Options + +The behavior of the `--print` flag can be modified by optionally be specifying a filepath +for each requested information kind, in the format `--print KIND=PATH`, just like for +`--emit`. When a path is specified, information will be written there instead of to stdout. + +This is unstable feature, so you have to provide `-Zunstable-options` to enable it. + +## Examples + +`rustc main.rs -Z unstable-options --print cfg=cfgs.txt` diff --git a/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md b/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md index ce894ce6ac7f1..2dd1f6f8e1a3a 100644 --- a/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md +++ b/src/doc/unstable-book/src/compiler-flags/profile_sample_use.md @@ -1,4 +1,4 @@ -# `profile-sample-use +# `profile-sample-use` --- diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md index aa776daf09db6..49389b28c8fc7 100644 --- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md +++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md @@ -21,7 +21,8 @@ This feature allows for use of one of following sanitizers: * [MemorySanitizer](#memorysanitizer) a detector of uninitialized reads. * [MemTagSanitizer](#memtagsanitizer) fast memory error detector based on Armv8.5-A Memory Tagging Extension. -* [ShadowCallStack](#shadowcallstack) provides backward-edge control flow protection. +* [SafeStack](#safestack) provides backward-edge control flow protection by separating the stack into safe and unsafe regions. +* [ShadowCallStack](#shadowcallstack) provides backward-edge control flow protection (aarch64 only). * [ThreadSanitizer](#threadsanitizer) a fast data race detector. To enable a sanitizer compile with `-Zsanitizer=address`,`-Zsanitizer=cfi`, @@ -712,6 +713,16 @@ To enable this target feature compile with `-C target-feature="+mte"`. See the [LLVM MemTagSanitizer documentation][llvm-memtag] for more details. +# SafeStack + +SafeStack provides backward edge control flow protection by separating the stack into data which is only accessed safely (the safe stack) and all other data (the unsafe stack). + +SafeStack can be enabled with the `-Zsanitizer=safestack` option and is supported on the following targets: + +* `x86_64-unknown-linux-gnu` + +See the [Clang SafeStack documentation][clang-safestack] for more details. + # ShadowCallStack ShadowCallStack provides backward edge control flow protection by storing a function's return address in a separately allocated 'shadow call stack' and loading the return address from that shadow call stack. @@ -828,6 +839,7 @@ Sanitizers produce symbolized stacktraces when llvm-symbolizer binary is in `PAT [clang-kcfi]: https://clang.llvm.org/docs/ControlFlowIntegrity.html#fsanitize-kcfi [clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html [clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html +[clang-safestack]: https://clang.llvm.org/docs/SafeStack.html [clang-scs]: https://clang.llvm.org/docs/ShadowCallStack.html [clang-tsan]: https://clang.llvm.org/docs/ThreadSanitizer.html [linux-kasan]: https://www.kernel.org/doc/html/latest/dev-tools/kasan.html diff --git a/src/doc/unstable-book/src/language-features/abi-thiscall.md b/src/doc/unstable-book/src/language-features/abi-thiscall.md deleted file mode 100644 index 73bc6eacf42ce..0000000000000 --- a/src/doc/unstable-book/src/language-features/abi-thiscall.md +++ /dev/null @@ -1,12 +0,0 @@ -# `abi_thiscall` - -The tracking issue for this feature is: [#42202] - -[#42202]: https://github.com/rust-lang/rust/issues/42202 - ------------------------- - -The MSVC ABI on x86 Windows uses the `thiscall` calling convention for C++ -instance methods by default; it is identical to the usual (C) calling -convention on x86 Windows except that the first parameter of the method, -the `this` pointer, is passed in the ECX register. diff --git a/src/doc/unstable-book/src/language-features/asm-experimental-arch.md b/src/doc/unstable-book/src/language-features/asm-experimental-arch.md index 532cb9eea1107..968c9bb4ebb86 100644 --- a/src/doc/unstable-book/src/language-features/asm-experimental-arch.md +++ b/src/doc/unstable-book/src/language-features/asm-experimental-arch.md @@ -17,7 +17,7 @@ This feature tracks `asm!` and `global_asm!` support for the following architect - AVR - MSP430 - M68k -- LoongArch +- CSKY - s390x ## Register classes @@ -47,8 +47,8 @@ This feature tracks `asm!` and `global_asm!` support for the following architect | M68k | `reg` | `d[0-7]`, `a[0-7]` | `r` | | M68k | `reg_data` | `d[0-7]` | `d` | | M68k | `reg_addr` | `a[0-3]` | `a` | -| LoongArch | `reg` | `$r1`, `$r[4-20]`, `$r[23,30]` | `r` | -| LoongArch | `freg` | `$f[0-31]` | `f` | +| CSKY | `reg` | `r[0-31]` | `r` | +| CSKY | `freg` | `f[0-31]` | `f` | | s390x | `reg` | `r[0-10]`, `r[12-14]` | `r` | | s390x | `freg` | `f[0-15]` | `f` | @@ -82,8 +82,8 @@ This feature tracks `asm!` and `global_asm!` support for the following architect | MSP430 | `reg` | None | `i8`, `i16` | | M68k | `reg`, `reg_addr` | None | `i16`, `i32` | | M68k | `reg_data` | None | `i8`, `i16`, `i32` | -| LoongArch64 | `reg` | None | `i8`, `i16`, `i32`, `i64`, `f32`, `f64` | -| LoongArch64 | `freg` | None | `f32`, `f64` | +| CSKY | `reg` | None | `i8`, `i16`, `i32` | +| CSKY | `freg` | None | `f32`, | | s390x | `reg` | None | `i8`, `i16`, `i32`, `i64` | | s390x | `freg` | None | `f32`, `f64` | @@ -107,10 +107,17 @@ This feature tracks `asm!` and `global_asm!` support for the following architect | M68k | `a5` | `bp` | | M68k | `a6` | `fp` | | M68k | `a7` | `sp`, `usp`, `ssp`, `isp` | -| LoongArch | `$r0` | `zero` | -| LoongArch | `$r2` | `tp` | -| LoongArch | `$r3` | `sp` | -| LoongArch | `$r22` | `fp` | +| CSKY | `r[0-3]` | `a[0-3]` | +| CSKY | `r[4-11]` | `l[0-7]` | +| CSKY | `r[12-13]` | `t[0-1]` | +| CSKY | `r14` | `sp` | +| CSKY | `r15` | `lr` | +| CSKY | `r[16-17]` | `l[8-9]` | +| CSKY | `r[18-25]` | `t[2-9]` | +| CSKY | `r28` | `rgb` | +| CSKY | `r29` | `rtb` | +| CSKY | `r30` | `svbr` | +| CSKY | `r31` | `tls` | > **Notes**: > - TI does not mandate a frame pointer for MSP430, but toolchains are allowed @@ -121,7 +128,7 @@ This feature tracks `asm!` and `global_asm!` support for the following architect | Architecture | Unsupported register | Reason | | ------------ | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | All | `sp`, `r15` (s390x) | The stack pointer must be restored to its original value at the end of an asm code block. | -| All | `fr` (Hexagon), `$fp` (MIPS), `Y` (AVR), `r4` (MSP430), `a6` (M68k), `$fp` (LoongArch), `r11` (s390x) | The frame pointer cannot be used as an input or output. | +| All | `fr` (Hexagon), `$fp` (MIPS), `Y` (AVR), `r4` (MSP430), `a6` (M68k), `r11` (s390x) | The frame pointer cannot be used as an input or output. | | All | `r19` (Hexagon) | This is used internally by LLVM as a "base pointer" for functions with complex stack frames. | | MIPS | `$0` or `$zero` | This is a constant zero register which can't be modified. | | MIPS | `$1` or `$at` | Reserved for assembler. | @@ -132,10 +139,13 @@ This feature tracks `asm!` and `global_asm!` support for the following architect | AVR | `r0`, `r1`, `r1r0` | Due to an issue in LLVM, the `r0` and `r1` registers cannot be used as inputs or outputs. If modified, they must be restored to their original values before the end of the block. | |MSP430 | `r0`, `r2`, `r3` | These are the program counter, status register, and constant generator respectively. Neither the status register nor constant generator can be written to. | | M68k | `a4`, `a5` | Used internally by LLVM for the base pointer and global base pointer. | -| LoongArch | `$r0` or `$zero` | This is a constant zero register which can't be modified. | -| LoongArch | `$r2` or `$tp` | This is reserved for TLS. | -| LoongArch | `$r21` | This is reserved by the ABI. | -| LoongArch | `$r31` or `$s8` | This is used internally by LLVM. | +| CSKY | `r7`, `r28` | Used internally by LLVM for the base pointer and global base pointer. | +| CSKY | `r8` | Used internally by LLVM for the frame pointer. | +| CSKY | `r14` | Used internally by LLVM for the stack pointer. | +| CSKY | `r15` | This is the link register. | +| CSKY | `r[26-30]` | Reserved by its ABI. | +| CSKY | `r31` | This is the TLS register. | + ## Template modifiers @@ -150,10 +160,10 @@ This feature tracks `asm!` and `global_asm!` support for the following architect | PowerPC | `reg` | None | `0` | None | | PowerPC | `reg_nonzero` | None | `3` | `b` | | PowerPC | `freg` | None | `0` | None | -| LoongArch | `reg` | None | `$r2` | None | -| LoongArch | `freg` | None | `$f0` | None | | s390x | `reg` | None | `%r0` | None | | s390x | `freg` | None | `%f0` | None | +| CSKY | `reg` | None | `r0` | None | +| CSKY | `freg` | None | `f0` | None | # Flags covered by `preserves_flags` diff --git a/src/doc/unstable-book/src/language-features/const-eval-limit.md b/src/doc/unstable-book/src/language-features/const-eval-limit.md deleted file mode 100644 index df68e83bcac74..0000000000000 --- a/src/doc/unstable-book/src/language-features/const-eval-limit.md +++ /dev/null @@ -1,7 +0,0 @@ -# `const_eval_limit` - -The tracking issue for this feature is: [#67217] - -[#67217]: https://github.com/rust-lang/rust/issues/67217 - -The `const_eval_limit` allows someone to limit the evaluation steps the CTFE undertakes to evaluate a `const fn`. diff --git a/src/doc/unstable-book/src/language-features/intrinsics.md b/src/doc/unstable-book/src/language-features/intrinsics.md index ea9bace6dd47f..8fa8f567d7eed 100644 --- a/src/doc/unstable-book/src/language-features/intrinsics.md +++ b/src/doc/unstable-book/src/language-features/intrinsics.md @@ -17,6 +17,7 @@ via a declaration like ```rust #![feature(intrinsics)] +#![allow(internal_features)] # fn main() {} extern "rust-intrinsic" { diff --git a/src/doc/unstable-book/src/language-features/lang-items.md b/src/doc/unstable-book/src/language-features/lang-items.md index 6adb3506e646a..9e20662fff370 100644 --- a/src/doc/unstable-book/src/language-features/lang-items.md +++ b/src/doc/unstable-book/src/language-features/lang-items.md @@ -9,25 +9,60 @@ functionality that isn't hard-coded into the language, but is implemented in libraries, with a special marker to tell the compiler it exists. The marker is the attribute `#[lang = "..."]` and there are various different values of `...`, i.e. various different 'lang -items'. +items'. Most of them can only be defined once. -For example, `Box` pointers require two lang items, one for allocation -and one for deallocation. A freestanding program that uses the `Box` -sugar for dynamic allocations via `malloc` and `free`: +Lang items are loaded lazily by the compiler; e.g. if one never uses `Box` +then there is no need to define a function for `exchange_malloc`. +`rustc` will emit an error when an item is needed but not found in the current +crate or any that it depends on. + +Some features provided by lang items: + +- overloadable operators via traits: the traits corresponding to the + `==`, `<`, dereferencing (`*`) and `+` (etc.) operators are all + marked with lang items; those specific four are `eq`, `partial_ord`, + `deref`/`deref_mut`, and `add` respectively. +- panicking: the `panic` and `panic_impl` lang items, among others. +- stack unwinding: the lang item `eh_personality` is a function used by the + failure mechanisms of the compiler. This is often mapped to GCC's personality + function (see the [`std` implementation][personality] for more information), + but programs which don't trigger a panic can be assured that this function is + never called. Additionally, a `eh_catch_typeinfo` static is needed for certain + targets which implement Rust panics on top of C++ exceptions. +- the traits in `core::marker` used to indicate types of + various kinds; e.g. lang items `sized`, `sync` and `copy`. +- memory allocation, see below. + +Most lang items are defined by `core`, but if you're trying to build +an executable without the `std` crate, you might run into the need +for lang item definitions. + +[personality]: https://github.com/rust-lang/rust/blob/master/library/std/src/personality/gcc.rs + +## Example: Implementing a `Box` + +`Box` pointers require two lang items: one for the type itself and one for +allocation. A freestanding program that uses the `Box` sugar for dynamic +allocations via `malloc` and `free`: ```rust,ignore (libc-is-finicky) -#![feature(lang_items, start, libc, core_intrinsics, rustc_private, rustc_attrs)] +#![feature(lang_items, start, core_intrinsics, rustc_private, panic_unwind, rustc_attrs)] +#![allow(internal_features)] #![no_std] + +extern crate libc; +extern crate unwind; + +use core::ffi::c_void; use core::intrinsics; use core::panic::PanicInfo; use core::ptr::NonNull; -extern crate libc; - +pub struct Global; // the global allocator struct Unique(NonNull); #[lang = "owned_box"] -pub struct Box(Unique); +pub struct Box(Unique, A); impl Box { pub fn new(x: T) -> Self { @@ -36,23 +71,26 @@ impl Box { } } +impl Drop for Box { + fn drop(&mut self) { + unsafe { + libc::free(self.0.0.as_ptr() as *mut c_void); + } + } +} + #[lang = "exchange_malloc"] unsafe fn allocate(size: usize, _align: usize) -> *mut u8 { - let p = libc::malloc(size as libc::size_t) as *mut u8; + let p = libc::malloc(size) as *mut u8; // Check if `malloc` failed: - if p as usize == 0 { + if p.is_null() { intrinsics::abort(); } p } -#[lang = "box_free"] -unsafe fn box_free(ptr: *mut T) { - libc::free(ptr as *mut libc::c_void) -} - #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { let _x = Box::new(1); @@ -60,246 +98,18 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { 0 } -#[lang = "eh_personality"] extern fn rust_eh_personality() {} -#[lang = "panic_impl"] extern fn rust_begin_panic(_info: &PanicInfo) -> ! { intrinsics::abort() } -#[no_mangle] pub extern fn rust_eh_register_frames () {} -#[no_mangle] pub extern fn rust_eh_unregister_frames () {} -``` - -Note the use of `abort`: the `exchange_malloc` lang item is assumed to -return a valid pointer, and so needs to do the check internally. - -Other features provided by lang items include: - -- overloadable operators via traits: the traits corresponding to the - `==`, `<`, dereferencing (`*`) and `+` (etc.) operators are all - marked with lang items; those specific four are `eq`, `ord`, - `deref`, and `add` respectively. -- stack unwinding and general failure; the `eh_personality`, - `panic` and `panic_bounds_check` lang items. -- the traits in `std::marker` used to indicate types of - various kinds; lang items `send`, `sync` and `copy`. -- the marker types and variance indicators found in - `std::marker`; lang items `covariant_type`, - `contravariant_lifetime`, etc. - -Lang items are loaded lazily by the compiler; e.g. if one never uses -`Box` then there is no need to define functions for `exchange_malloc` -and `box_free`. `rustc` will emit an error when an item is needed -but not found in the current crate or any that it depends on. - -Most lang items are defined by `libcore`, but if you're trying to build -an executable without the standard library, you'll run into the need -for lang items. The rest of this page focuses on this use-case, even though -lang items are a bit broader than that. - -### Using libc - -In order to build a `#[no_std]` executable we will need libc as a dependency. -We can specify this using our `Cargo.toml` file: - -```toml -[dependencies] -libc = { version = "0.2.14", default-features = false } -``` - -Note that the default features have been disabled. This is a critical step - -**the default features of libc include the standard library and so must be -disabled.** - -### Writing an executable without stdlib - -Controlling the entry point is possible in two ways: the `#[start]` attribute, -or overriding the default shim for the C `main` function with your own. - -The function marked `#[start]` is passed the command line parameters -in the same format as C: - -```rust,ignore (libc-is-finicky) -#![feature(lang_items, core_intrinsics, rustc_private)] -#![feature(start)] -#![no_std] -use core::intrinsics; -use core::panic::PanicInfo; - -// Pull in the system libc library for what crt0.o likely requires. -extern crate libc; - -// Entry point for this program. -#[start] -fn start(_argc: isize, _argv: *const *const u8) -> isize { - 0 -} - -// These functions are used by the compiler, but not -// for a bare-bones hello world. These are normally -// provided by libstd. #[lang = "eh_personality"] -#[no_mangle] -pub extern fn rust_eh_personality() { -} +fn rust_eh_personality() {} -#[lang = "panic_impl"] -#[no_mangle] -pub extern fn rust_begin_panic(info: &PanicInfo) -> ! { - unsafe { intrinsics::abort() } -} +#[panic_handler] +fn panic_handler(_info: &PanicInfo) -> ! { intrinsics::abort() } ``` -To override the compiler-inserted `main` shim, one has to disable it -with `#![no_main]` and then create the appropriate symbol with the -correct ABI and the correct name, which requires overriding the -compiler's name mangling too: - -```rust,ignore (libc-is-finicky) -#![feature(lang_items, core_intrinsics, rustc_private)] -#![feature(start)] -#![no_std] -#![no_main] -use core::intrinsics; -use core::panic::PanicInfo; - -// Pull in the system libc library for what crt0.o likely requires. -extern crate libc; - -// Entry point for this program. -#[no_mangle] // ensure that this symbol is called `main` in the output -pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 { - 0 -} - -// These functions are used by the compiler, but not -// for a bare-bones hello world. These are normally -// provided by libstd. -#[lang = "eh_personality"] -#[no_mangle] -pub extern fn rust_eh_personality() { -} - -#[lang = "panic_impl"] -#[no_mangle] -pub extern fn rust_begin_panic(info: &PanicInfo) -> ! { - unsafe { intrinsics::abort() } -} -``` - -In many cases, you may need to manually link to the `compiler_builtins` crate -when building a `no_std` binary. You may observe this via linker error messages -such as "```undefined reference to `__rust_probestack'```". - -## More about the language items - -The compiler currently makes a few assumptions about symbols which are -available in the executable to call. Normally these functions are provided by -the standard library, but without it you must define your own. These symbols -are called "language items", and they each have an internal name, and then a -signature that an implementation must conform to. - -The first of these functions, `rust_eh_personality`, is used by the failure -mechanisms of the compiler. This is often mapped to GCC's personality function -(see the [libstd implementation][unwind] for more information), but crates -which do not trigger a panic can be assured that this function is never -called. The language item's name is `eh_personality`. - -[unwind]: https://github.com/rust-lang/rust/blob/master/library/panic_unwind/src/gcc.rs - -The second function, `rust_begin_panic`, is also used by the failure mechanisms of the -compiler. When a panic happens, this controls the message that's displayed on -the screen. While the language item's name is `panic_impl`, the symbol name is -`rust_begin_panic`. - -Finally, a `eh_catch_typeinfo` static is needed for certain targets which -implement Rust panics on top of C++ exceptions. +Note the use of `abort`: the `exchange_malloc` lang item is assumed to +return a valid pointer, and so needs to do the check internally. ## List of all language items -This is a list of all language items in Rust along with where they are located in -the source code. +An up-to-date list of all language items can be found [here] in the compiler code. -- Primitives - - `i8`: `libcore/num/mod.rs` - - `i16`: `libcore/num/mod.rs` - - `i32`: `libcore/num/mod.rs` - - `i64`: `libcore/num/mod.rs` - - `i128`: `libcore/num/mod.rs` - - `isize`: `libcore/num/mod.rs` - - `u8`: `libcore/num/mod.rs` - - `u16`: `libcore/num/mod.rs` - - `u32`: `libcore/num/mod.rs` - - `u64`: `libcore/num/mod.rs` - - `u128`: `libcore/num/mod.rs` - - `usize`: `libcore/num/mod.rs` - - `f32`: `libstd/f32.rs` - - `f64`: `libstd/f64.rs` - - `char`: `libcore/char.rs` - - `slice`: `liballoc/slice.rs` - - `str`: `liballoc/str.rs` - - `const_ptr`: `libcore/ptr.rs` - - `mut_ptr`: `libcore/ptr.rs` - - `unsafe_cell`: `libcore/cell.rs` -- Runtime - - `start`: `libstd/rt.rs` - - `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC) - - `eh_personality`: `libpanic_unwind/gcc.rs` (GNU) - - `eh_personality`: `libpanic_unwind/seh.rs` (SEH) - - `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC) - - `panic`: `libcore/panicking.rs` - - `panic_bounds_check`: `libcore/panicking.rs` - - `panic_impl`: `libcore/panicking.rs` - - `panic_impl`: `libstd/panicking.rs` -- Allocations - - `owned_box`: `liballoc/boxed.rs` - - `exchange_malloc`: `liballoc/heap.rs` - - `box_free`: `liballoc/heap.rs` -- Operands - - `not`: `libcore/ops/bit.rs` - - `bitand`: `libcore/ops/bit.rs` - - `bitor`: `libcore/ops/bit.rs` - - `bitxor`: `libcore/ops/bit.rs` - - `shl`: `libcore/ops/bit.rs` - - `shr`: `libcore/ops/bit.rs` - - `bitand_assign`: `libcore/ops/bit.rs` - - `bitor_assign`: `libcore/ops/bit.rs` - - `bitxor_assign`: `libcore/ops/bit.rs` - - `shl_assign`: `libcore/ops/bit.rs` - - `shr_assign`: `libcore/ops/bit.rs` - - `deref`: `libcore/ops/deref.rs` - - `deref_mut`: `libcore/ops/deref.rs` - - `index`: `libcore/ops/index.rs` - - `index_mut`: `libcore/ops/index.rs` - - `add`: `libcore/ops/arith.rs` - - `sub`: `libcore/ops/arith.rs` - - `mul`: `libcore/ops/arith.rs` - - `div`: `libcore/ops/arith.rs` - - `rem`: `libcore/ops/arith.rs` - - `neg`: `libcore/ops/arith.rs` - - `add_assign`: `libcore/ops/arith.rs` - - `sub_assign`: `libcore/ops/arith.rs` - - `mul_assign`: `libcore/ops/arith.rs` - - `div_assign`: `libcore/ops/arith.rs` - - `rem_assign`: `libcore/ops/arith.rs` - - `eq`: `libcore/cmp.rs` - - `ord`: `libcore/cmp.rs` -- Functions - - `fn`: `libcore/ops/function.rs` - - `fn_mut`: `libcore/ops/function.rs` - - `fn_once`: `libcore/ops/function.rs` - - `generator_state`: `libcore/ops/generator.rs` - - `generator`: `libcore/ops/generator.rs` -- Other - - `coerce_unsized`: `libcore/ops/unsize.rs` - - `drop`: `libcore/ops/drop.rs` - - `drop_in_place`: `libcore/ptr.rs` - - `clone`: `libcore/clone.rs` - - `copy`: `libcore/marker.rs` - - `send`: `libcore/marker.rs` - - `sized`: `libcore/marker.rs` - - `unsize`: `libcore/marker.rs` - - `sync`: `libcore/marker.rs` - - `phantom_data`: `libcore/marker.rs` - - `discriminant_kind`: `libcore/marker.rs` - - `freeze`: `libcore/marker.rs` - - `debug_trait`: `libcore/fmt/mod.rs` - - `non_zero`: `libcore/nonzero.rs` - - `arc`: `liballoc/sync.rs` - - `rc`: `liballoc/rc.rs` +[here]: https://github.com/rust-lang/rust/blob/master/compiler/rustc_hir/src/lang_items.rs diff --git a/src/doc/unstable-book/src/language-features/start.md b/src/doc/unstable-book/src/language-features/start.md new file mode 100644 index 0000000000000..09e4875a2e4f7 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/start.md @@ -0,0 +1,59 @@ +# `start` + +The tracking issue for this feature is: [#29633] + +[#29633]: https://github.com/rust-lang/rust/issues/29633 + +------------------------ + +Allows you to mark a function as the entry point of the executable, which is +necessary in `#![no_std]` environments. + +The function marked `#[start]` is passed the command line parameters in the same +format as the C main function (aside from the integer types being used). +It has to be non-generic and have the following signature: + +```rust,ignore (only-for-syntax-highlight) +# let _: +fn(isize, *const *const u8) -> isize +# ; +``` + +This feature should not be confused with the `start` *lang item* which is +defined by the `std` crate and is written `#[lang = "start"]`. + +## Usage together with the `std` crate + +`#[start]` can be used in combination with the `std` crate, in which case the +normal `main` function (which would get called from the `std` crate) won't be +used as an entry point. +The initialization code in `std` will be skipped this way. + +Example: + +```rust +#![feature(start)] + +#[start] +fn start(_argc: isize, _argv: *const *const u8) -> isize { + 0 +} +``` + +Unwinding the stack past the `#[start]` function is currently considered +Undefined Behavior (for any unwinding implementation): + +```rust,ignore (UB) +#![feature(start)] + +#[start] +fn start(_argc: isize, _argv: *const *const u8) -> isize { + std::panic::catch_unwind(|| { + panic!(); // panic safely gets caught or safely aborts execution + }); + + panic!(); // UB! + + 0 +} +``` diff --git a/src/doc/unstable-book/src/library-features/default-free-fn.md b/src/doc/unstable-book/src/library-features/default-free-fn.md deleted file mode 100644 index bafc9ac4d0d96..0000000000000 --- a/src/doc/unstable-book/src/library-features/default-free-fn.md +++ /dev/null @@ -1,47 +0,0 @@ -# `default_free_fn` - -The tracking issue for this feature is: [#73014] - -[#73014]: https://github.com/rust-lang/rust/issues/73014 - ------------------------- - -Adds a free `default()` function to the `std::default` module. This function -just forwards to [`Default::default()`], but may remove repetition of the word -"default" from the call site. - -[`Default::default()`]: ../../std/default/trait.Default.html#tymethod.default - -Here is an example: - -```rust -#![feature(default_free_fn)] -use std::default::default; - -#[derive(Default)] -struct AppConfig { - foo: FooConfig, - bar: BarConfig, -} - -#[derive(Default)] -struct FooConfig { - foo: i32, -} - -#[derive(Default)] -struct BarConfig { - bar: f32, - baz: u8, -} - -fn main() { - let options = AppConfig { - foo: default(), - bar: BarConfig { - bar: 10.1, - ..default() - }, - }; -} -``` diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish index 9f65f1eeeb7e5..151c4120ddfbc 100644 --- a/src/etc/completions/x.py.fish +++ b/src/etc/completions/x.py.fish @@ -4,6 +4,7 @@ complete -c x.py -n "__fish_use_subcommand" -l build -d 'build target of the sta complete -c x.py -n "__fish_use_subcommand" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_use_subcommand" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_use_subcommand" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_use_subcommand" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_use_subcommand" -l rustc-error-format -r -f complete -c x.py -n "__fish_use_subcommand" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_use_subcommand" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -18,7 +19,7 @@ complete -c x.py -n "__fish_use_subcommand" -l llvm-skip-rebuild -d 'whether reb complete -c x.py -n "__fish_use_subcommand" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_use_subcommand" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_use_subcommand" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_use_subcommand" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_use_subcommand" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_use_subcommand" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_use_subcommand" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_use_subcommand" -s i -l incremental -d 'use incremental compilation' @@ -26,7 +27,6 @@ complete -c x.py -n "__fish_use_subcommand" -l include-default-paths -d 'include complete -c x.py -n "__fish_use_subcommand" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_use_subcommand" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_use_subcommand" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_use_subcommand" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_use_subcommand" -s h -l help -d 'Print help' complete -c x.py -n "__fish_use_subcommand" -f -a "build" -d 'Compile either the compiler or libraries' complete -c x.py -n "__fish_use_subcommand" -f -a "check" -d 'Compile either the compiler or libraries, using cargo check' @@ -48,6 +48,7 @@ complete -c x.py -n "__fish_seen_subcommand_from build" -l build -d 'build targe complete -c x.py -n "__fish_seen_subcommand_from build" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from build" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from build" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from build" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from build" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from build" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from build" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -62,7 +63,7 @@ complete -c x.py -n "__fish_seen_subcommand_from build" -l llvm-skip-rebuild -d complete -c x.py -n "__fish_seen_subcommand_from build" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from build" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from build" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from build" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from build" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from build" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from build" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from build" -s i -l incremental -d 'use incremental compilation' @@ -70,7 +71,6 @@ complete -c x.py -n "__fish_seen_subcommand_from build" -l include-default-paths complete -c x.py -n "__fish_seen_subcommand_from build" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from build" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from build" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from build" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from build" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from check" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from check" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -78,6 +78,7 @@ complete -c x.py -n "__fish_seen_subcommand_from check" -l build -d 'build targe complete -c x.py -n "__fish_seen_subcommand_from check" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from check" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from check" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from check" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from check" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from check" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from check" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -92,7 +93,7 @@ complete -c x.py -n "__fish_seen_subcommand_from check" -l llvm-skip-rebuild -d complete -c x.py -n "__fish_seen_subcommand_from check" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from check" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from check" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from check" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from check" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from check" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from check" -l all-targets -d 'Check all targets' complete -c x.py -n "__fish_seen_subcommand_from check" -s v -l verbose -d 'use verbose output (-vv for very verbose)' @@ -101,7 +102,6 @@ complete -c x.py -n "__fish_seen_subcommand_from check" -l include-default-paths complete -c x.py -n "__fish_seen_subcommand_from check" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from check" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from check" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from check" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from check" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from clippy" -s A -d 'clippy lints to allow' -r complete -c x.py -n "__fish_seen_subcommand_from clippy" -s D -d 'clippy lints to deny' -r @@ -113,6 +113,7 @@ complete -c x.py -n "__fish_seen_subcommand_from clippy" -l build -d 'build targ complete -c x.py -n "__fish_seen_subcommand_from clippy" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from clippy" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from clippy" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from clippy" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from clippy" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from clippy" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from clippy" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -127,7 +128,7 @@ complete -c x.py -n "__fish_seen_subcommand_from clippy" -l llvm-skip-rebuild -d complete -c x.py -n "__fish_seen_subcommand_from clippy" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from clippy" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from clippy" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from clippy" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from clippy" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from clippy" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from clippy" -l fix complete -c x.py -n "__fish_seen_subcommand_from clippy" -s v -l verbose -d 'use verbose output (-vv for very verbose)' @@ -136,7 +137,6 @@ complete -c x.py -n "__fish_seen_subcommand_from clippy" -l include-default-path complete -c x.py -n "__fish_seen_subcommand_from clippy" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from clippy" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from clippy" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from clippy" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from clippy" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from fix" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from fix" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -144,6 +144,7 @@ complete -c x.py -n "__fish_seen_subcommand_from fix" -l build -d 'build target complete -c x.py -n "__fish_seen_subcommand_from fix" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from fix" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from fix" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from fix" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from fix" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from fix" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from fix" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -158,7 +159,7 @@ complete -c x.py -n "__fish_seen_subcommand_from fix" -l llvm-skip-rebuild -d 'w complete -c x.py -n "__fish_seen_subcommand_from fix" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from fix" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from fix" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from fix" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from fix" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from fix" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from fix" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from fix" -s i -l incremental -d 'use incremental compilation' @@ -166,7 +167,6 @@ complete -c x.py -n "__fish_seen_subcommand_from fix" -l include-default-paths - complete -c x.py -n "__fish_seen_subcommand_from fix" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from fix" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from fix" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from fix" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from fix" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from fmt" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from fmt" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -174,6 +174,7 @@ complete -c x.py -n "__fish_seen_subcommand_from fmt" -l build -d 'build target complete -c x.py -n "__fish_seen_subcommand_from fmt" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from fmt" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from fmt" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from fmt" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from fmt" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from fmt" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from fmt" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -188,7 +189,7 @@ complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-skip-rebuild -d 'w complete -c x.py -n "__fish_seen_subcommand_from fmt" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from fmt" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from fmt" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from fmt" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from fmt" -l check -d 'check formatting instead of applying' complete -c x.py -n "__fish_seen_subcommand_from fmt" -s v -l verbose -d 'use verbose output (-vv for very verbose)' @@ -197,7 +198,6 @@ complete -c x.py -n "__fish_seen_subcommand_from fmt" -l include-default-paths - complete -c x.py -n "__fish_seen_subcommand_from fmt" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from fmt" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from fmt" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from fmt" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from doc" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from doc" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -205,6 +205,7 @@ complete -c x.py -n "__fish_seen_subcommand_from doc" -l build -d 'build target complete -c x.py -n "__fish_seen_subcommand_from doc" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from doc" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from doc" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from doc" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from doc" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from doc" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from doc" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -219,7 +220,7 @@ complete -c x.py -n "__fish_seen_subcommand_from doc" -l llvm-skip-rebuild -d 'w complete -c x.py -n "__fish_seen_subcommand_from doc" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from doc" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from doc" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from doc" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from doc" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from doc" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from doc" -l open -d 'open the docs in a browser' complete -c x.py -n "__fish_seen_subcommand_from doc" -l json -d 'render the documentation in JSON format in addition to the usual HTML format' @@ -229,11 +230,11 @@ complete -c x.py -n "__fish_seen_subcommand_from doc" -l include-default-paths - complete -c x.py -n "__fish_seen_subcommand_from doc" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from doc" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from doc" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from doc" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from doc" -s h -l help -d 'Print help (see more with \'--help\')' -complete -c x.py -n "__fish_seen_subcommand_from test" -l skip -d 'skips tests matching SUBSTRING, if supported by test tool. May be passed multiple times' -r +complete -c x.py -n "__fish_seen_subcommand_from test" -l skip -d 'skips tests matching SUBSTRING, if supported by test tool. May be passed multiple times' -r -F complete -c x.py -n "__fish_seen_subcommand_from test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r complete -c x.py -n "__fish_seen_subcommand_from test" -l rustc-args -d 'extra options to pass the compiler when running tests' -r +complete -c x.py -n "__fish_seen_subcommand_from test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)' -r complete -c x.py -n "__fish_seen_subcommand_from test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r complete -c x.py -n "__fish_seen_subcommand_from test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r complete -c x.py -n "__fish_seen_subcommand_from test" -l run -d 'whether to execute run-* tests' -r @@ -257,7 +258,7 @@ complete -c x.py -n "__fish_seen_subcommand_from test" -l llvm-skip-rebuild -d ' complete -c x.py -n "__fish_seen_subcommand_from test" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from test" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from test" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from test" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from test" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from test" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from test" -l no-fail-fast -d 'run all tests regardless of failure' complete -c x.py -n "__fish_seen_subcommand_from test" -l no-doc -d 'do not run doc tests' @@ -272,7 +273,6 @@ complete -c x.py -n "__fish_seen_subcommand_from test" -l include-default-paths complete -c x.py -n "__fish_seen_subcommand_from test" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from test" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from test" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from test" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from test" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from bench" -l test-args -r complete -c x.py -n "__fish_seen_subcommand_from bench" -l config -d 'TOML configuration file for build' -r -F @@ -281,6 +281,7 @@ complete -c x.py -n "__fish_seen_subcommand_from bench" -l build -d 'build targe complete -c x.py -n "__fish_seen_subcommand_from bench" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from bench" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from bench" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from bench" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from bench" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from bench" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from bench" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -295,7 +296,7 @@ complete -c x.py -n "__fish_seen_subcommand_from bench" -l llvm-skip-rebuild -d complete -c x.py -n "__fish_seen_subcommand_from bench" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from bench" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from bench" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from bench" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from bench" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from bench" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from bench" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from bench" -s i -l incremental -d 'use incremental compilation' @@ -303,17 +304,17 @@ complete -c x.py -n "__fish_seen_subcommand_from bench" -l include-default-paths complete -c x.py -n "__fish_seen_subcommand_from bench" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from bench" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from bench" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from bench" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from bench" -s h -l help -d 'Print help' +complete -c x.py -n "__fish_seen_subcommand_from clean" -l stage -d 'Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used' -r complete -c x.py -n "__fish_seen_subcommand_from clean" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from clean" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" complete -c x.py -n "__fish_seen_subcommand_from clean" -l build -d 'build target of the stage0 compiler' -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from clean" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from clean" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" -complete -c x.py -n "__fish_seen_subcommand_from clean" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l keep-stage -d 'stage(s) to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)' -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l keep-stage-std -d 'stage(s) of the standard library to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)' -r -f complete -c x.py -n "__fish_seen_subcommand_from clean" -l src -d 'path to the root of the rust checkout' -r -f -a "(__fish_complete_directories)" @@ -325,16 +326,15 @@ complete -c x.py -n "__fish_seen_subcommand_from clean" -l llvm-skip-rebuild -d complete -c x.py -n "__fish_seen_subcommand_from clean" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from clean" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from clean" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from clean" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from clean" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from clean" -l set -d 'override options in config.toml' -r -f -complete -c x.py -n "__fish_seen_subcommand_from clean" -l all +complete -c x.py -n "__fish_seen_subcommand_from clean" -l all -d 'Clean the entire build directory (not used by default)' complete -c x.py -n "__fish_seen_subcommand_from clean" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from clean" -s i -l incremental -d 'use incremental compilation' complete -c x.py -n "__fish_seen_subcommand_from clean" -l include-default-paths -d 'include default paths in addition to the provided ones' complete -c x.py -n "__fish_seen_subcommand_from clean" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from clean" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from clean" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from clean" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from clean" -s h -l help -d 'Print help' complete -c x.py -n "__fish_seen_subcommand_from dist" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from dist" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -342,6 +342,7 @@ complete -c x.py -n "__fish_seen_subcommand_from dist" -l build -d 'build target complete -c x.py -n "__fish_seen_subcommand_from dist" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from dist" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from dist" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from dist" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from dist" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from dist" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from dist" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -356,7 +357,7 @@ complete -c x.py -n "__fish_seen_subcommand_from dist" -l llvm-skip-rebuild -d ' complete -c x.py -n "__fish_seen_subcommand_from dist" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from dist" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from dist" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from dist" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from dist" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from dist" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from dist" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from dist" -s i -l incremental -d 'use incremental compilation' @@ -364,7 +365,6 @@ complete -c x.py -n "__fish_seen_subcommand_from dist" -l include-default-paths complete -c x.py -n "__fish_seen_subcommand_from dist" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from dist" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from dist" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from dist" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from dist" -s h -l help -d 'Print help' complete -c x.py -n "__fish_seen_subcommand_from install" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from install" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -372,6 +372,7 @@ complete -c x.py -n "__fish_seen_subcommand_from install" -l build -d 'build tar complete -c x.py -n "__fish_seen_subcommand_from install" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from install" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from install" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from install" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from install" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from install" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from install" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -386,7 +387,7 @@ complete -c x.py -n "__fish_seen_subcommand_from install" -l llvm-skip-rebuild - complete -c x.py -n "__fish_seen_subcommand_from install" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from install" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from install" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from install" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from install" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from install" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from install" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from install" -s i -l incremental -d 'use incremental compilation' @@ -394,7 +395,6 @@ complete -c x.py -n "__fish_seen_subcommand_from install" -l include-default-pat complete -c x.py -n "__fish_seen_subcommand_from install" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from install" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from install" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from install" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from install" -s h -l help -d 'Print help' complete -c x.py -n "__fish_seen_subcommand_from run" -l args -d 'arguments for the tool' -r complete -c x.py -n "__fish_seen_subcommand_from run" -l config -d 'TOML configuration file for build' -r -F @@ -403,6 +403,7 @@ complete -c x.py -n "__fish_seen_subcommand_from run" -l build -d 'build target complete -c x.py -n "__fish_seen_subcommand_from run" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from run" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from run" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from run" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from run" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from run" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from run" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -417,7 +418,7 @@ complete -c x.py -n "__fish_seen_subcommand_from run" -l llvm-skip-rebuild -d 'w complete -c x.py -n "__fish_seen_subcommand_from run" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from run" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from run" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from run" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from run" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from run" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from run" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from run" -s i -l incremental -d 'use incremental compilation' @@ -425,7 +426,6 @@ complete -c x.py -n "__fish_seen_subcommand_from run" -l include-default-paths - complete -c x.py -n "__fish_seen_subcommand_from run" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from run" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from run" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from run" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from run" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from setup" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from setup" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -433,6 +433,7 @@ complete -c x.py -n "__fish_seen_subcommand_from setup" -l build -d 'build targe complete -c x.py -n "__fish_seen_subcommand_from setup" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from setup" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from setup" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from setup" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from setup" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from setup" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from setup" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -447,7 +448,7 @@ complete -c x.py -n "__fish_seen_subcommand_from setup" -l llvm-skip-rebuild -d complete -c x.py -n "__fish_seen_subcommand_from setup" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from setup" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from setup" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from setup" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from setup" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from setup" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from setup" -s v -l verbose -d 'use verbose output (-vv for very verbose)' complete -c x.py -n "__fish_seen_subcommand_from setup" -s i -l incremental -d 'use incremental compilation' @@ -455,7 +456,6 @@ complete -c x.py -n "__fish_seen_subcommand_from setup" -l include-default-paths complete -c x.py -n "__fish_seen_subcommand_from setup" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from setup" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from setup" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from setup" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from setup" -s h -l help -d 'Print help (see more with \'--help\')' complete -c x.py -n "__fish_seen_subcommand_from suggest" -l config -d 'TOML configuration file for build' -r -F complete -c x.py -n "__fish_seen_subcommand_from suggest" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)" @@ -463,6 +463,7 @@ complete -c x.py -n "__fish_seen_subcommand_from suggest" -l build -d 'build tar complete -c x.py -n "__fish_seen_subcommand_from suggest" -l host -d 'host targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from suggest" -l target -d 'target targets to build' -r -f complete -c x.py -n "__fish_seen_subcommand_from suggest" -l exclude -d 'build paths to exclude' -r -F +complete -c x.py -n "__fish_seen_subcommand_from suggest" -l skip -d 'build paths to skip' -r -F complete -c x.py -n "__fish_seen_subcommand_from suggest" -l rustc-error-format -r -f complete -c x.py -n "__fish_seen_subcommand_from suggest" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)" complete -c x.py -n "__fish_seen_subcommand_from suggest" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f @@ -477,7 +478,7 @@ complete -c x.py -n "__fish_seen_subcommand_from suggest" -l llvm-skip-rebuild - complete -c x.py -n "__fish_seen_subcommand_from suggest" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from suggest" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F complete -c x.py -n "__fish_seen_subcommand_from suggest" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F -complete -c x.py -n "__fish_seen_subcommand_from suggest" -l llvm-bolt-profile-use -d 'use BOLT profile for LLVM build' -r -F +complete -c x.py -n "__fish_seen_subcommand_from suggest" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r complete -c x.py -n "__fish_seen_subcommand_from suggest" -l set -d 'override options in config.toml' -r -f complete -c x.py -n "__fish_seen_subcommand_from suggest" -l run -d 'run suggested tests' complete -c x.py -n "__fish_seen_subcommand_from suggest" -s v -l verbose -d 'use verbose output (-vv for very verbose)' @@ -486,5 +487,4 @@ complete -c x.py -n "__fish_seen_subcommand_from suggest" -l include-default-pat complete -c x.py -n "__fish_seen_subcommand_from suggest" -l dry-run -d 'dry run; don\'t build anything' complete -c x.py -n "__fish_seen_subcommand_from suggest" -l json-output -d 'use message-format=json' complete -c x.py -n "__fish_seen_subcommand_from suggest" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc' -complete -c x.py -n "__fish_seen_subcommand_from suggest" -l llvm-bolt-profile-generate -d 'generate BOLT profile for LLVM build' complete -c x.py -n "__fish_seen_subcommand_from suggest" -s h -l help -d 'Print help (see more with \'--help\')' diff --git a/src/etc/completions/x.py.ps1 b/src/etc/completions/x.py.ps1 index 569c186555cc2..cafb8eed12d49 100644 --- a/src/etc/completions/x.py.ps1 +++ b/src/etc/completions/x.py.ps1 @@ -27,6 +27,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -42,7 +43,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -52,7 +53,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('build', 'build', [CompletionResultType]::ParameterValue, 'Compile either the compiler or libraries') @@ -78,6 +78,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -93,7 +94,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -103,7 +104,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -115,6 +115,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -130,7 +131,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--all-targets', 'all-targets', [CompletionResultType]::ParameterName, 'Check all targets') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -141,7 +142,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -157,6 +157,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -172,7 +173,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--fix', 'fix', [CompletionResultType]::ParameterName, 'fix') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -183,7 +184,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -195,6 +195,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -210,7 +211,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -220,7 +221,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -232,6 +232,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -247,7 +248,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'check formatting instead of applying') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -258,7 +259,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -270,6 +270,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -285,7 +286,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--open', 'open', [CompletionResultType]::ParameterName, 'open the docs in a browser') [CompletionResult]::new('--json', 'json', [CompletionResultType]::ParameterName, 'render the documentation in JSON format in addition to the usual HTML format') @@ -297,7 +298,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -306,6 +306,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'skips tests matching SUBSTRING, if supported by test tool. May be passed multiple times') [CompletionResult]::new('--test-args', 'test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)') [CompletionResult]::new('--rustc-args', 'rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running tests') + [CompletionResult]::new('--extra-checks', 'extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)') [CompletionResult]::new('--compare-mode', 'compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to') [CompletionResult]::new('--pass', 'pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode') [CompletionResult]::new('--run', 'run', [CompletionResultType]::ParameterName, 'whether to execute run-* tests') @@ -330,7 +331,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--no-fail-fast', 'no-fail-fast', [CompletionResultType]::ParameterName, 'run all tests regardless of failure') [CompletionResult]::new('--no-doc', 'no-doc', [CompletionResultType]::ParameterName, 'do not run doc tests') @@ -347,7 +348,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -360,6 +360,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -375,7 +376,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -385,21 +386,21 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break } 'x.py;clean' { + [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'Clean a specific stage without touching other artifacts. By default, every stage is cleaned if this option is not used') [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'TOML configuration file for build') [CompletionResult]::new('--build-dir', 'build-dir', [CompletionResultType]::ParameterName, 'Build directory, overrides `build.build-dir` in `config.toml`') [CompletionResult]::new('--build', 'build', [CompletionResultType]::ParameterName, 'build target of the stage0 compiler') [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') - [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') [CompletionResult]::new('--keep-stage', 'keep-stage', [CompletionResultType]::ParameterName, 'stage(s) to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)') [CompletionResult]::new('--keep-stage-std', 'keep-stage-std', [CompletionResultType]::ParameterName, 'stage(s) of the standard library to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)') [CompletionResult]::new('--src', 'src', [CompletionResultType]::ParameterName, 'path to the root of the rust checkout') @@ -412,9 +413,9 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') - [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'all') + [CompletionResult]::new('--all', 'all', [CompletionResultType]::ParameterName, 'Clean the entire build directory (not used by default)') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'use incremental compilation') @@ -423,7 +424,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break @@ -435,6 +435,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -450,7 +451,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -460,7 +461,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break @@ -472,6 +472,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -487,7 +488,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -497,7 +498,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') break @@ -510,6 +510,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -525,7 +526,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -535,7 +536,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -547,6 +547,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -562,7 +563,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -572,7 +573,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break @@ -584,6 +584,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build') [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build') [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude') + [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip') [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format') [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure') [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)') @@ -599,7 +600,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build') [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build') [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build') - [CompletionResult]::new('--llvm-bolt-profile-use', 'llvm-bolt-profile-use', [CompletionResultType]::ParameterName, 'use BOLT profile for LLVM build') + [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive') [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml') [CompletionResult]::new('--run', 'run', [CompletionResultType]::ParameterName, 'run suggested tests') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)') @@ -610,7 +611,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock { [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything') [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json') [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc') - [CompletionResult]::new('--llvm-bolt-profile-generate', 'llvm-bolt-profile-generate', [CompletionResultType]::ParameterName, 'generate BOLT profile for LLVM build') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')') break diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh index 322afdb28835f..3c57e71bdb7c9 100644 --- a/src/etc/completions/x.py.sh +++ b/src/etc/completions/x.py.sh @@ -61,7 +61,7 @@ _x.py() { case "${cmd}" in x.py) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]... build check clippy fix fmt doc test bench clean dist install run setup suggest" + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]... build check clippy fix fmt doc test bench clean dist install run setup suggest" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -91,6 +91,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -151,7 +155,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -167,7 +171,7 @@ _x.py() { return 0 ;; x.py__bench) - opts="-v -i -j -h --test-args --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --test-args --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -201,6 +205,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -261,7 +269,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -277,7 +285,7 @@ _x.py() { return 0 ;; x.py__build) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -307,6 +315,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -367,7 +379,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -383,7 +395,7 @@ _x.py() { return 0 ;; x.py__check) - opts="-v -i -j -h --all-targets --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --all-targets --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -413,6 +425,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -473,7 +489,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -489,12 +505,16 @@ _x.py() { return 0 ;; x.py__clean) - opts="-v -i -j -h --all --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --all --stage --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --stage) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --config) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -519,6 +539,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -527,10 +551,6 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --stage) - COMPREPLY=("${cur}") - return 0 - ;; --keep-stage) COMPREPLY=("${cur}") return 0 @@ -579,7 +599,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -595,7 +615,7 @@ _x.py() { return 0 ;; x.py__clippy) - opts="-A -D -W -F -v -i -j -h --fix --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-A -D -W -F -v -i -j -h --fix --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -641,6 +661,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -701,7 +725,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -717,7 +741,7 @@ _x.py() { return 0 ;; x.py__dist) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -747,6 +771,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -807,7 +835,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -823,7 +851,7 @@ _x.py() { return 0 ;; x.py__doc) - opts="-v -i -j -h --open --json --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --open --json --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -853,6 +881,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -913,7 +945,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -929,7 +961,7 @@ _x.py() { return 0 ;; x.py__fix) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -959,6 +991,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -1019,7 +1055,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -1035,7 +1071,7 @@ _x.py() { return 0 ;; x.py__fmt) - opts="-v -i -j -h --check --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --check --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -1065,6 +1101,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -1125,7 +1165,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -1141,7 +1181,7 @@ _x.py() { return 0 ;; x.py__install) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -1171,6 +1211,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -1231,7 +1275,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -1247,7 +1291,7 @@ _x.py() { return 0 ;; x.py__run) - opts="-v -i -j -h --args --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --args --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -1281,6 +1325,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -1341,7 +1389,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -1357,7 +1405,7 @@ _x.py() { return 0 ;; x.py__setup) - opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [|hook|vscode|link] [PATHS]... [ARGS]..." + opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [|hook|vscode|link] [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -1387,6 +1435,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -1447,7 +1499,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -1463,7 +1515,7 @@ _x.py() { return 0 ;; x.py__suggest) - opts="-v -i -j -h --run --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --run --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -1493,6 +1545,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --skip) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --rustc-error-format) COMPREPLY=("${cur}") return 0 @@ -1553,7 +1609,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; @@ -1569,7 +1625,7 @@ _x.py() { return 0 ;; x.py__test) - opts="-v -i -j -h --no-fail-fast --skip --test-args --rustc-args --no-doc --doc --bless --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --llvm-bolt-profile-generate --llvm-bolt-profile-use --set --help [PATHS]... [ARGS]..." + opts="-v -i -j -h --no-fail-fast --skip --test-args --rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --verbose --incremental --config --build-dir --build --host --target --exclude --include-default-paths --rustc-error-format --on-fail --dry-run --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --reproducible-artifact --set --help [PATHS]... [ARGS]..." if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -1587,6 +1643,10 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --extra-checks) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --compare-mode) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -1683,7 +1743,7 @@ _x.py() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --llvm-bolt-profile-use) + --reproducible-artifact) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; diff --git a/src/etc/dec2flt_table.py b/src/etc/dec2flt_table.py old mode 100644 new mode 100755 index aa5188d96c3e7..9264a8439bb71 --- a/src/etc/dec2flt_table.py +++ b/src/etc/dec2flt_table.py @@ -14,8 +14,7 @@ available here: . """ from __future__ import print_function -from math import ceil, floor, log, log2 -from fractions import Fraction +from math import ceil, floor, log from collections import deque HEADER = """ @@ -97,7 +96,6 @@ def print_proper_powers(min_exp, max_exp, bias): print('#[rustfmt::skip]') typ = '[(u64, u64); N_POWERS_OF_FIVE]' print('pub static POWER_OF_FIVE_128: {} = ['.format(typ)) - lo_mask = (1 << 64) - 1 for c, exp in powers: hi = '0x{:x}'.format(c // (1 << 64)) lo = '0x{:x}'.format(c % (1 << 64)) diff --git a/src/etc/gdb_load_rust_pretty_printers.py b/src/etc/gdb_load_rust_pretty_printers.py index 856b5df2de70b..e05039ce47447 100644 --- a/src/etc/gdb_load_rust_pretty_printers.py +++ b/src/etc/gdb_load_rust_pretty_printers.py @@ -1,3 +1,15 @@ +# Add this folder to the python sys path; GDB Python-interpreter will now find modules in this path +import sys +from os import path +self_dir = path.dirname(path.realpath(__file__)) +sys.path.append(self_dir) + +# ruff: noqa: E402 import gdb import gdb_lookup -gdb_lookup.register_printers(gdb.current_objfile()) + +# current_objfile can be none; even with `gdb foo-app`; sourcing this file after gdb init now works +try: + gdb_lookup.register_printers(gdb.current_objfile()) +except Exception: + gdb_lookup.register_printers(gdb.selected_inferior().progspace) diff --git a/src/etc/generate-deriving-span-tests.py b/src/etc/generate-deriving-span-tests.py index d38f5add7474d..d61693460bc1f 100755 --- a/src/etc/generate-deriving-span-tests.py +++ b/src/etc/generate-deriving-span-tests.py @@ -102,7 +102,9 @@ def write_file(name, string): traits[trait] = (ALL, supers, errs) for (trait, (types, super_traits, error_count)) in traits.items(): - mk = lambda ty: create_test_case(ty, trait, super_traits, error_count) + def mk(ty, t=trait, st=super_traits, ec=error_count): + return create_test_case(ty, t, st, ec) + if types & ENUM: write_file(trait + '-enum', mk(ENUM_TUPLE)) write_file(trait + '-enum-struct-variant', mk(ENUM_STRUCT)) diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py old mode 100644 new mode 100755 index c97fb4b805473..2e0f832192fdc --- a/src/etc/htmldocck.py +++ b/src/etc/htmldocck.py @@ -110,6 +110,9 @@ * `@has-dir PATH` checks for the existence of the given directory. +* `@files FOLDER_PATH [ENTRIES]`, checks that `FOLDER_PATH` contains exactly + `[ENTRIES]`. + All conditions can be negated with `!`. `@!has foo/type.NoSuch.html` checks if the given file does not exist, for example. @@ -144,7 +147,7 @@ # Python 2 -> 3 compatibility try: - unichr + unichr # noqa: B018 FIXME: py2 except NameError: unichr = chr @@ -271,6 +274,8 @@ def get_commands(template): args = shlex.split(args) except UnicodeEncodeError: args = [arg.decode('utf-8') for arg in shlex.split(args.encode('utf-8'))] + except Exception as exc: + raise Exception("line {}: {}".format(lineno + 1, exc)) from None yield Command(negated=negated, cmd=cmd, args=args, lineno=lineno+1, context=line) @@ -321,12 +326,15 @@ def resolve_path(self, path): else: return self.last_path + def get_absolute_path(self, path): + return os.path.join(self.root, path) + def get_file(self, path): path = self.resolve_path(path) if path in self.files: return self.files[path] - abspath = os.path.join(self.root, path) + abspath = self.get_absolute_path(path) if not(os.path.exists(abspath) and os.path.isfile(abspath)): raise FailedCheck('File does not exist {!r}'.format(path)) @@ -340,7 +348,7 @@ def get_tree(self, path): if path in self.trees: return self.trees[path] - abspath = os.path.join(self.root, path) + abspath = self.get_absolute_path(path) if not(os.path.exists(abspath) and os.path.isfile(abspath)): raise FailedCheck('File does not exist {!r}'.format(path)) @@ -348,13 +356,15 @@ def get_tree(self, path): try: tree = ET.fromstringlist(f.readlines(), CustomHTMLParser()) except Exception as e: - raise RuntimeError('Cannot parse an HTML file {!r}: {}'.format(path, e)) + raise RuntimeError( # noqa: B904 FIXME: py2 + 'Cannot parse an HTML file {!r}: {}'.format(path, e) + ) self.trees[path] = tree return self.trees[path] def get_dir(self, path): path = self.resolve_path(path) - abspath = os.path.join(self.root, path) + abspath = self.get_absolute_path(path) if not(os.path.exists(abspath) and os.path.isdir(abspath)): raise FailedCheck('Directory does not exist {!r}'.format(path)) @@ -422,7 +432,7 @@ def check_snapshot(snapshot_name, actual_tree, normalize_to_text): if bless: expected_str = None else: - raise FailedCheck('No saved snapshot value') + raise FailedCheck('No saved snapshot value') # noqa: B904 FIXME: py2 if not normalize_to_text: actual_str = ET.tostring(actual_tree).decode('utf-8') @@ -536,6 +546,41 @@ def get_nb_matching_elements(cache, c, regexp, stop_at_first): return check_tree_text(cache.get_tree(c.args[0]), pat, c.args[2], regexp, stop_at_first) +def check_files_in_folder(c, cache, folder, files): + files = files.strip() + if not files.startswith('[') or not files.endswith(']'): + raise InvalidCheck("Expected list as second argument of @{} (ie '[]')".format(c.cmd)) + + folder = cache.get_absolute_path(folder) + + # First we create a set of files to check if there are duplicates. + files = shlex.split(files[1:-1].replace(",", "")) + files_set = set() + for file in files: + if file in files_set: + raise InvalidCheck("Duplicated file `{}` in @{}".format(file, c.cmd)) + files_set.add(file) + folder_set = set([f for f in os.listdir(folder) if f != "." and f != ".."]) + + # Then we remove entries from both sets (we clone `folder_set` so we can iterate it while + # removing its elements). + for entry in set(folder_set): + if entry in files_set: + files_set.remove(entry) + folder_set.remove(entry) + + error = 0 + if len(files_set) != 0: + print_err(c.lineno, c.context, "Entries not found in folder `{}`: `{}`".format( + folder, files_set)) + error += 1 + if len(folder_set) != 0: + print_err(c.lineno, c.context, "Extra entries in folder `{}`: `{}`".format( + folder, folder_set)) + error += 1 + return error == 0 + + ERR_COUNT = 0 @@ -564,6 +609,13 @@ def check_command(c, cache): else: raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd)) + elif c.cmd == 'files': # check files in given folder + if len(c.args) != 2: # @files + raise InvalidCheck("Invalid number of @{} arguments".format(c.cmd)) + elif c.negated: + raise InvalidCheck("@{} doesn't support negative check".format(c.cmd)) + ret = check_files_in_folder(c, cache, c.args[0], c.args[1]) + elif c.cmd == 'count': # count test if len(c.args) == 3: # @count = count test expected = int(c.args[2]) diff --git a/src/etc/lldb_batchmode.py b/src/etc/lldb_batchmode.py index fc355c87b52a2..db1e0035ea063 100644 --- a/src/etc/lldb_batchmode.py +++ b/src/etc/lldb_batchmode.py @@ -124,7 +124,7 @@ def listen(): breakpoint = lldb.SBBreakpoint.GetBreakpointFromEvent(event) print_debug("breakpoint added, id = " + str(breakpoint.id)) new_breakpoints.append(breakpoint.id) - except: + except BaseException: # explicitly catch ctrl+c/sysexit print_debug("breakpoint listener shutting down") # Start the listener and let it run as a daemon diff --git a/src/etc/lldb_providers.py b/src/etc/lldb_providers.py index c4381e202b945..4c86b2146463d 100644 --- a/src/etc/lldb_providers.py +++ b/src/etc/lldb_providers.py @@ -1,6 +1,6 @@ import sys -from lldb import SBValue, SBData, SBError, eBasicTypeLong, eBasicTypeUnsignedLong, \ +from lldb import SBData, SBError, eBasicTypeLong, eBasicTypeUnsignedLong, \ eBasicTypeUnsignedChar # from lldb.formatters import Logger diff --git a/src/etc/natvis/liballoc.natvis b/src/etc/natvis/liballoc.natvis index c4ad98ec1d3a9..00c17d83322a8 100644 --- a/src/etc/natvis/liballoc.natvis +++ b/src/etc/natvis/liballoc.natvis @@ -66,7 +66,10 @@ dyn pointees. Rc<[T]> and Arc<[T]> are handled separately altogether so we can actually show - the slice values. + the slice values. These visualizers have a second wildcard `foo<slice2$<*>, *>` + which accounts for the allocator parameter. This isn't needed for the other visualizers since + their inner `*` eats the type parameter but since the slice ones match part of the type params + it is necessary for them. --> @@ -84,7 +87,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -114,7 +117,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -143,7 +146,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length @@ -172,7 +175,7 @@ - + {{ len={ptr.pointer.length} }} ptr.pointer.length diff --git a/src/etc/pre-push.sh b/src/etc/pre-push.sh index ff17931115cd0..c9e1a2733fdc5 100755 --- a/src/etc/pre-push.sh +++ b/src/etc/pre-push.sh @@ -5,7 +5,7 @@ # and remove it from .git/hooks to deactivate. # -set -Eeuo pipefail +set -Euo pipefail # https://github.com/rust-lang/rust/issues/77620#issuecomment-705144570 unset GIT_DIR @@ -14,4 +14,8 @@ ROOT_DIR="$(git rev-parse --show-toplevel)" echo "Running pre-push script $ROOT_DIR/x test tidy" cd "$ROOT_DIR" -CARGOFLAGS="--locked" ./x test tidy +./x test tidy --set build.locked-deps=true +if [ $? -ne 0 ]; then + echo "You may use \`git push --no-verify\` to skip this check." + exit 1 +fi diff --git a/src/etc/rust_analyzer_settings.json b/src/etc/rust_analyzer_settings.json index dd01bfaa7252d..6e5e2c35005a6 100644 --- a/src/etc/rust_analyzer_settings.json +++ b/src/etc/rust_analyzer_settings.json @@ -7,7 +7,14 @@ "check", "--json-output" ], - "rust-analyzer.linkedProjects": ["src/bootstrap/Cargo.toml", "Cargo.toml"], + "rust-analyzer.linkedProjects": [ + "Cargo.toml", + "src/tools/x/Cargo.toml", + "src/bootstrap/Cargo.toml", + "src/tools/rust-analyzer/Cargo.toml", + "compiler/rustc_codegen_cranelift/Cargo.toml", + "compiler/rustc_codegen_gcc/Cargo.toml" + ], "rust-analyzer.rustfmt.overrideCommand": [ "./build/host/rustfmt/bin/rustfmt", "--edition=2021" @@ -24,5 +31,8 @@ "--json-output" ], "rust-analyzer.cargo.sysrootSrc": "./library", - "rust-analyzer.rustc.source": "./Cargo.toml" + "rust-analyzer.rustc.source": "./Cargo.toml", + "rust-analyzer.cargo.extraEnv": { + "RUSTC_BOOTSTRAP": "1" + } } diff --git a/src/etc/test-float-parse/Cargo.toml b/src/etc/test-float-parse/Cargo.toml index 7ee19a0b6d913..6d7b227d0ad31 100644 --- a/src/etc/test-float-parse/Cargo.toml +++ b/src/etc/test-float-parse/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" publish = false [workspace] +resolver = "1" [dependencies] rand = "0.4" diff --git a/src/etc/test-float-parse/runtests.py b/src/etc/test-float-parse/runtests.py old mode 100644 new mode 100755 index cf7279534dc86..cc5e31a051fc2 --- a/src/etc/test-float-parse/runtests.py +++ b/src/etc/test-float-parse/runtests.py @@ -127,7 +127,7 @@ def write_errors(): if not have_seen_error: have_seen_error = True msg("Something is broken:", *args) - msg("Future errors logged to errors.txt") + msg("Future errors will be logged to errors.txt") exit_status = 101 diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index baf2b0a858529..a06f31a932919 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -46,7 +46,7 @@ where let tcx = self.cx.tcx; let trait_ref = ty::Binder::dummy(ty::TraitRef::new(tcx, trait_def_id, [ty])); if !self.cx.generated_synthetics.insert((ty, trait_def_id)) { - debug!("get_auto_trait_impl_for({:?}): already generated, aborting", trait_ref); + debug!("get_auto_trait_impl_for({trait_ref:?}): already generated, aborting"); return None; } @@ -124,7 +124,7 @@ where unsafety: hir::Unsafety::Normal, generics: new_generics, trait_: Some(clean_trait_ref_with_bindings(self.cx, trait_ref, ThinVec::new())), - for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None), + for_: clean_middle_ty(ty::Binder::dummy(ty), self.cx, None, None), items: Vec::new(), polarity, kind: ImplKind::Auto, @@ -137,10 +137,10 @@ where pub(crate) fn get_auto_trait_impls(&mut self, item_def_id: DefId) -> Vec { let tcx = self.cx.tcx; let param_env = tcx.param_env(item_def_id); - let ty = tcx.type_of(item_def_id).subst_identity(); + let ty = tcx.type_of(item_def_id).instantiate_identity(); let f = auto_trait::AutoTraitFinder::new(tcx); - debug!("get_auto_trait_impls({:?})", ty); + debug!("get_auto_trait_impls({ty:?})"); let auto_traits: Vec<_> = self.cx.auto_traits.to_vec(); let mut auto_traits: Vec = auto_traits .into_iter() @@ -163,9 +163,9 @@ where fn get_lifetime(region: Region<'_>, names_map: &FxHashMap) -> Lifetime { region_name(region) .map(|name| { - names_map.get(&name).unwrap_or_else(|| { - panic!("Missing lifetime with name {:?} for {:?}", name.as_str(), region) - }) + names_map + .get(&name) + .unwrap_or_else(|| panic!("Missing lifetime with name {name:?} for {region:?}")) }) .unwrap_or(&Lifetime::statik()) .clone() @@ -317,14 +317,14 @@ where lifetime_predicates } - fn extract_for_generics(&self, pred: ty::Predicate<'tcx>) -> FxHashSet { + fn extract_for_generics(&self, pred: ty::Clause<'tcx>) -> FxHashSet { let bound_predicate = pred.kind(); let tcx = self.cx.tcx; let regions = match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_pred)) => { + ty::ClauseKind::Trait(poly_trait_pred) => { tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_trait_pred)) } - ty::PredicateKind::Clause(ty::Clause::Projection(poly_proj_pred)) => { + ty::ClauseKind::Projection(poly_proj_pred) => { tcx.collect_referenced_late_bound_regions(&bound_predicate.rebind(poly_proj_pred)) } _ => return FxHashSet::default(), @@ -372,7 +372,7 @@ where let output = output.as_ref().cloned().map(Box::new); if old_output.is_some() && old_output != output { - panic!("Output mismatch for {:?} {:?} {:?}", ty, old_output, output); + panic!("Output mismatch for {ty:?} {old_output:?} {output:?}"); } let new_params = GenericArgs::Parenthesized { inputs: old_input, output }; @@ -449,9 +449,7 @@ where .filter(|p| { !orig_bounds.contains(p) || match p.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { - pred.def_id() == sized_trait - } + ty::ClauseKind::Trait(pred) => pred.def_id() == sized_trait, _ => false, } }) @@ -464,7 +462,7 @@ where ); let mut generic_params = raw_generics.params; - debug!("param_env_to_generics({:?}): generic_params={:?}", item_def_id, generic_params); + debug!("param_env_to_generics({item_def_id:?}): generic_params={generic_params:?}"); let mut has_sized = FxHashSet::default(); let mut ty_to_bounds: FxHashMap<_, FxHashSet<_>> = Default::default(); @@ -625,7 +623,7 @@ where // loop ty_to_traits.entry(ty.clone()).or_default().insert(trait_.clone()); } - _ => panic!("Unexpected LHS {:?} for {:?}", lhs, item_def_id), + _ => panic!("Unexpected LHS {lhs:?} for {item_def_id:?}"), } } }; @@ -712,7 +710,7 @@ where /// involved (impls rarely have more than a few bounds) means that it /// shouldn't matter in practice. fn unstable_debug_sort(&self, vec: &mut [T]) { - vec.sort_by_cached_key(|x| format!("{:?}", x)) + vec.sort_by_cached_key(|x| format!("{x:?}")) } fn is_fn_trait(&self, path: &Path) -> bool { diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index e4c05b5737835..dad2aa4061d6c 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -17,11 +17,11 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { let param_env = cx.tcx.param_env(item_def_id); let ty = cx.tcx.type_of(item_def_id); - trace!("get_blanket_impls({:?})", ty); + trace!("get_blanket_impls({ty:?})"); let mut impls = Vec::new(); for trait_def_id in cx.tcx.all_traits() { if !cx.cache.effective_visibilities.is_reachable(cx.tcx, trait_def_id) - || cx.generated_synthetics.get(&(ty.0, trait_def_id)).is_some() + || cx.generated_synthetics.get(&(ty.skip_binder(), trait_def_id)).is_some() { continue; } @@ -34,22 +34,26 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { impl_def_id ); let trait_ref = cx.tcx.impl_trait_ref(impl_def_id).unwrap(); - if !matches!(trait_ref.0.self_ty().kind(), ty::Param(_)) { + if !matches!(trait_ref.skip_binder().self_ty().kind(), ty::Param(_)) { continue; } let infcx = cx.tcx.infer_ctxt().build(); - let substs = infcx.fresh_substs_for_item(DUMMY_SP, item_def_id); - let impl_ty = ty.subst(infcx.tcx, substs); - let param_env = EarlyBinder(param_env).subst(infcx.tcx, substs); + let args = infcx.fresh_args_for_item(DUMMY_SP, item_def_id); + let impl_ty = ty.instantiate(infcx.tcx, args); + let param_env = EarlyBinder::bind(param_env).instantiate(infcx.tcx, args); - let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); - let impl_trait_ref = trait_ref.subst(infcx.tcx, impl_substs); + let impl_args = infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); + let impl_trait_ref = trait_ref.instantiate(infcx.tcx, impl_args); // Require the type the impl is implemented on to match // our type, and ignore the impl if there was a mismatch. - let Ok(eq_result) = infcx.at(&traits::ObligationCause::dummy(), param_env).eq(DefineOpaqueTypes::No, impl_trait_ref.self_ty(), impl_ty) else { - continue - }; + let Ok(eq_result) = infcx.at(&traits::ObligationCause::dummy(), param_env).eq( + DefineOpaqueTypes::No, + impl_trait_ref.self_ty(), + impl_ty, + ) else { + continue; + }; let InferOk { value: (), obligations } = eq_result; // FIXME(eddyb) ignoring `obligations` might cause false positives. drop(obligations); @@ -63,12 +67,12 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { let predicates = cx .tcx .predicates_of(impl_def_id) - .instantiate(cx.tcx, impl_substs) + .instantiate(cx.tcx, impl_args) .predicates .into_iter() .chain(Some(ty::Binder::dummy(impl_trait_ref).to_predicate(infcx.tcx))); for predicate in predicates { - debug!("testing predicate {:?}", predicate); + debug!("testing predicate {predicate:?}"); let obligation = traits::Obligation::new( infcx.tcx, traits::ObligationCause::dummy(), @@ -87,7 +91,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { trait_ref, ty ); - cx.generated_synthetics.insert((ty.0, trait_def_id)); + cx.generated_synthetics.insert((ty.skip_binder(), trait_def_id)); impls.push(Item { name: None, @@ -104,21 +108,28 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { // the post-inference `trait_ref`, as it's more accurate. trait_: Some(clean_trait_ref_with_bindings( cx, - ty::Binder::dummy(trait_ref.0), + ty::Binder::dummy(trait_ref.instantiate_identity()), ThinVec::new(), )), - for_: clean_middle_ty(ty::Binder::dummy(ty.0), cx, None), + for_: clean_middle_ty( + ty::Binder::dummy(ty.instantiate_identity()), + cx, + None, + None, + ), items: cx .tcx .associated_items(impl_def_id) .in_definition_order() - .map(|x| clean_middle_assoc_item(x, cx)) + .filter(|item| !item.is_impl_trait_in_trait()) + .map(|item| clean_middle_assoc_item(item, cx)) .collect::>(), polarity: ty::ImplPolarity::Positive, kind: ImplKind::Blanket(Box::new(clean_middle_ty( - ty::Binder::dummy(trait_ref.0.self_ty()), + ty::Binder::dummy(trait_ref.instantiate_identity().self_ty()), cx, None, + None, ))), }))), cfg: None, diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index 5177cffe6bae4..ab5aec12fe7cb 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -434,9 +434,9 @@ impl<'a> fmt::Display for Display<'a> { } if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) { if self.1.is_html() { - write!(fmt, "{}", feat)?; + write!(fmt, "{feat}")?; } else { - write!(fmt, "`{}`", feat)?; + write!(fmt, "`{feat}`")?; } } else { write_with_opt_paren(fmt, !sub_cfg.is_all(), Display(sub_cfg, self.1))?; @@ -471,9 +471,9 @@ impl<'a> fmt::Display for Display<'a> { } if let (true, Cfg::Cfg(_, Some(feat))) = (short_longhand, sub_cfg) { if self.1.is_html() { - write!(fmt, "{}", feat)?; + write!(fmt, "{feat}")?; } else { - write!(fmt, "`{}`", feat)?; + write!(fmt, "`{feat}`")?; } } else { write_with_opt_paren(fmt, !sub_cfg.is_simple(), Display(sub_cfg, self.1))?; @@ -519,8 +519,11 @@ impl<'a> fmt::Display for Display<'a> { "asmjs" => "JavaScript", "loongarch64" => "LoongArch LA64", "m68k" => "M68k", + "csky" => "CSKY", "mips" => "MIPS", + "mips32r6" => "MIPS Release 6", "mips64" => "MIPS-64", + "mips64r6" => "MIPS-64 Release 6", "msp430" => "MSP430", "powerpc" => "PowerPC", "powerpc64" => "PowerPC-64", @@ -549,21 +552,21 @@ impl<'a> fmt::Display for Display<'a> { "sgx" => "SGX", _ => "", }, - (sym::target_endian, Some(endian)) => return write!(fmt, "{}-endian", endian), - (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{}-bit", bits), + (sym::target_endian, Some(endian)) => return write!(fmt, "{endian}-endian"), + (sym::target_pointer_width, Some(bits)) => return write!(fmt, "{bits}-bit"), (sym::target_feature, Some(feat)) => match self.1 { Format::LongHtml => { - return write!(fmt, "target feature {}", feat); + return write!(fmt, "target feature {feat}"); } - Format::LongPlain => return write!(fmt, "target feature `{}`", feat), - Format::ShortHtml => return write!(fmt, "{}", feat), + Format::LongPlain => return write!(fmt, "target feature `{feat}`"), + Format::ShortHtml => return write!(fmt, "{feat}"), }, (sym::feature, Some(feat)) => match self.1 { Format::LongHtml => { - return write!(fmt, "crate feature {}", feat); + return write!(fmt, "crate feature {feat}"); } - Format::LongPlain => return write!(fmt, "crate feature `{}`", feat), - Format::ShortHtml => return write!(fmt, "{}", feat), + Format::LongPlain => return write!(fmt, "crate feature `{feat}`"), + Format::ShortHtml => return write!(fmt, "{feat}"), }, _ => "", }; @@ -578,12 +581,12 @@ impl<'a> fmt::Display for Display<'a> { Escape(v.as_str()) ) } else { - write!(fmt, r#"`{}="{}"`"#, name, v) + write!(fmt, r#"`{name}="{v}"`"#) } } else if self.1.is_html() { write!(fmt, "{}", Escape(name.as_str())) } else { - write!(fmt, "`{}`", name) + write!(fmt, "`{name}`") } } } diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index c852f9cca2bfd..cac2113074042 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -9,9 +9,10 @@ use rustc_ast as ast; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, DefIdSet, LocalDefId}; +use rustc_hir::def_id::{DefId, DefIdSet, LocalModDefId}; use rustc_hir::Mutability; use rustc_metadata::creader::{CStore, LoadedMacro}; +use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Symbol}; @@ -49,7 +50,7 @@ pub(crate) fn try_inline( } let mut ret = Vec::new(); - debug!("attrs={:?}", attrs); + debug!("attrs={attrs:?}"); let attrs_without_docs = attrs.map(|(attrs, def_id)| { (attrs.into_iter().filter(|a| a.doc_str().is_none()).cloned().collect::>(), def_id) @@ -78,7 +79,7 @@ pub(crate) fn try_inline( build_impls(cx, did, attrs_without_docs, &mut ret); clean::UnionItem(build_union(cx, did)) } - Res::Def(DefKind::TyAlias, did) => { + Res::Def(DefKind::TyAlias { .. }, did) => { record_extern_fqn(cx, did, ItemType::Typedef); build_impls(cx, did, attrs_without_docs, &mut ret); clean::TypedefItem(build_type_alias(cx, did)) @@ -137,9 +138,10 @@ pub(crate) fn try_inline( pub(crate) fn try_inline_glob( cx: &mut DocContext<'_>, res: Res, - current_mod: LocalDefId, + current_mod: LocalModDefId, visited: &mut DefIdSet, inlined_names: &mut FxHashSet<(ItemType, Symbol)>, + import: &hir::Item<'_>, ) -> Option> { let did = res.opt_def_id()?; if did.is_local() { @@ -152,19 +154,27 @@ pub(crate) fn try_inline_glob( // reexported by the glob, e.g. because they are shadowed by something else. let reexports = cx .tcx - .module_children_local(current_mod) + .module_children_local(current_mod.to_local_def_id()) .iter() .filter(|child| !child.reexport_chain.is_empty()) .filter_map(|child| child.res.opt_def_id()) .collect(); - let mut items = build_module_items(cx, did, visited, inlined_names, Some(&reexports)); - items.drain_filter(|item| { + let attrs = cx.tcx.hir().attrs(import.hir_id()); + let mut items = build_module_items( + cx, + did, + visited, + inlined_names, + Some(&reexports), + Some((attrs, Some(import.owner_id.def_id.to_def_id()))), + ); + items.retain(|item| { if let Some(name) = item.name { // If an item with the same type and name already exists, // it takes priority over the inlined stuff. - !inlined_names.insert((item.type_(), name)) + inlined_names.insert((item.type_(), name)) } else { - false + true } }); Some(items) @@ -190,7 +200,7 @@ pub(crate) fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemT let fqn = if let ItemType::Macro = kind { // Check to see if it is a macro 2.0 or built-in macro if matches!( - CStore::from_tcx(cx.tcx).load_macro_untracked(did, cx.sess()), + CStore::from_tcx(cx.tcx).load_macro_untracked(did, cx.tcx), LoadedMacro::MacroDef(def, _) if matches!(&def.kind, ast::ItemKind::MacroDef(ast_def) if !ast_def.macro_rules) @@ -215,6 +225,7 @@ pub(crate) fn build_external_trait(cx: &mut DocContext<'_>, did: DefId) -> clean .tcx .associated_items(did) .in_definition_order() + .filter(|item| !item.is_impl_trait_in_trait()) .map(|item| clean_middle_assoc_item(item, cx)) .collect(); @@ -226,7 +237,7 @@ pub(crate) fn build_external_trait(cx: &mut DocContext<'_>, did: DefId) -> clean } fn build_external_function<'tcx>(cx: &mut DocContext<'tcx>, did: DefId) -> Box { - let sig = cx.tcx.fn_sig(did).subst_identity(); + let sig = cx.tcx.fn_sig(did).instantiate_identity(); let late_bound_regions = sig.bound_vars().into_iter().filter_map(|var| match var { ty::BoundVariableKind::Region(ty::BrNamed(_, name)) if name != kw::UnderscoreLifetime => { @@ -278,8 +289,12 @@ fn build_union(cx: &mut DocContext<'_>, did: DefId) -> clean::Union { fn build_type_alias(cx: &mut DocContext<'_>, did: DefId) -> Box { let predicates = cx.tcx.explicit_predicates_of(did); - let type_ = - clean_middle_ty(ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()), cx, Some(did)); + let type_ = clean_middle_ty( + ty::Binder::dummy(cx.tcx.type_of(did).instantiate_identity()), + cx, + Some(did), + None, + ); Box::new(clean::Typedef { type_, @@ -310,9 +325,8 @@ pub(crate) fn build_impls( // * https://github.com/rust-lang/rust/pull/99917 — where the feature got used // * https://github.com/rust-lang/rust/issues/53487 — overall tracking issue for Error if tcx.has_attr(did, sym::rustc_has_incoherent_inherent_impls) { - use rustc_middle::ty::fast_reject::SimplifiedType::*; let type_ = - if tcx.is_trait(did) { TraitSimplifiedType(did) } else { AdtSimplifiedType(did) }; + if tcx.is_trait(did) { SimplifiedType::Trait(did) } else { SimplifiedType::Adt(did) }; for &did in tcx.incoherent_impls(type_) { build_impl(cx, did, attrs, ret); } @@ -355,9 +369,9 @@ pub(crate) fn build_impl( return; } - let _prof_timer = cx.tcx.sess.prof.generic_activity("build_impl"); - let tcx = cx.tcx; + let _prof_timer = tcx.sess.prof.generic_activity("build_impl"); + let associated_trait = tcx.impl_trait_ref(did).map(ty::EarlyBinder::skip_binder); // Only inline impl if the implemented trait is @@ -386,9 +400,12 @@ pub(crate) fn build_impl( let for_ = match &impl_item { Some(impl_) => clean_ty(impl_.self_ty, cx), - None => { - clean_middle_ty(ty::Binder::dummy(tcx.type_of(did).subst_identity()), cx, Some(did)) - } + None => clean_middle_ty( + ty::Binder::dummy(tcx.type_of(did).instantiate_identity()), + cx, + Some(did), + None, + ), }; // Only inline impl if the implementing type is @@ -452,6 +469,7 @@ pub(crate) fn build_impl( None => ( tcx.associated_items(did) .in_definition_order() + .filter(|item| !item.is_impl_trait_in_trait()) .filter(|item| { // If this is a trait impl, filter out associated items whose corresponding item // in the associated trait is marked `doc(hidden)`. @@ -466,7 +484,7 @@ pub(crate) fn build_impl( associated_trait.def_id, ) .unwrap(); // corresponding associated item has to exist - !tcx.is_doc_hidden(trait_item.def_id) + document_hidden || !tcx.is_doc_hidden(trait_item.def_id) } else { item.visibility(tcx).is_public() } @@ -489,7 +507,7 @@ pub(crate) fn build_impl( let mut stack: Vec<&Type> = vec![&for_]; if let Some(did) = trait_.as_ref().map(|t| t.def_id()) { - if tcx.is_doc_hidden(did) { + if !document_hidden && tcx.is_doc_hidden(did) { return; } } @@ -498,7 +516,7 @@ pub(crate) fn build_impl( } while let Some(ty) = stack.pop() { - if let Some(did) = ty.def_id(&cx.cache) && tcx.is_doc_hidden(did) { + if let Some(did) = ty.def_id(&cx.cache) && !document_hidden && tcx.is_doc_hidden(did) { return; } if let Some(generics) = ty.generics() { @@ -511,7 +529,7 @@ pub(crate) fn build_impl( } let (merged_attrs, cfg) = merge_attrs(cx, load_attrs(cx, did), attrs); - trace!("merged_attrs={:?}", merged_attrs); + trace!("merged_attrs={merged_attrs:?}"); trace!( "build_impl: impl {:?} for {:?}", @@ -540,7 +558,7 @@ pub(crate) fn build_impl( } fn build_module(cx: &mut DocContext<'_>, did: DefId, visited: &mut DefIdSet) -> clean::Module { - let items = build_module_items(cx, did, visited, &mut FxHashSet::default(), None); + let items = build_module_items(cx, did, visited, &mut FxHashSet::default(), None, None); let span = clean::Span::new(cx.tcx.def_span(did)); clean::Module { items, span } @@ -552,6 +570,7 @@ fn build_module_items( visited: &mut DefIdSet, inlined_names: &mut FxHashSet<(ItemType, Symbol)>, allowed_def_ids: Option<&DefIdSet>, + attrs: Option<(&[ast::Attribute], Option)>, ) -> Vec { let mut items = Vec::new(); @@ -606,7 +625,7 @@ fn build_module_items( cfg: None, inline_stmt_id: None, }); - } else if let Some(i) = try_inline(cx, res, item.ident.name, None, visited) { + } else if let Some(i) = try_inline(cx, res, item.ident.name, attrs, visited) { items.extend(i) } } @@ -625,12 +644,18 @@ pub(crate) fn print_inlined_const(tcx: TyCtxt<'_>, did: DefId) -> String { } fn build_const(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant { + let mut generics = + clean_ty_generics(cx, cx.tcx.generics_of(def_id), cx.tcx.explicit_predicates_of(def_id)); + clean::simplify::move_bounds_to_generic_parameters(&mut generics); + clean::Constant { type_: clean_middle_ty( - ty::Binder::dummy(cx.tcx.type_of(def_id).subst_identity()), + ty::Binder::dummy(cx.tcx.type_of(def_id).instantiate_identity()), cx, Some(def_id), + None, ), + generics: Box::new(generics), kind: clean::ConstantKind::Extern { def_id }, } } @@ -638,9 +663,10 @@ fn build_const(cx: &mut DocContext<'_>, def_id: DefId) -> clean::Constant { fn build_static(cx: &mut DocContext<'_>, did: DefId, mutable: bool) -> clean::Static { clean::Static { type_: clean_middle_ty( - ty::Binder::dummy(cx.tcx.type_of(did).subst_identity()), + ty::Binder::dummy(cx.tcx.type_of(did).instantiate_identity()), cx, Some(did), + None, ), mutability: if mutable { Mutability::Mut } else { Mutability::Not }, expr: None, @@ -654,7 +680,7 @@ fn build_macro( import_def_id: Option, macro_kind: MacroKind, ) -> clean::ItemKind { - match CStore::from_tcx(cx.tcx).load_macro_untracked(def_id, cx.sess()) { + match CStore::from_tcx(cx.tcx).load_macro_untracked(def_id, cx.tcx) { LoadedMacro::MacroDef(item_def, _) => match macro_kind { MacroKind::Bang => { if let ast::ItemKind::MacroDef(ref def) = item_def.kind { @@ -755,7 +781,7 @@ pub(crate) fn record_extern_trait(cx: &mut DocContext<'_>, did: DefId) { cx.active_extern_traits.insert(did); } - debug!("record_extern_trait: {:?}", did); + debug!("record_extern_trait: {did:?}"); let trait_ = build_external_trait(cx, did); cx.external_traits.borrow_mut().insert(did, trait_); diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 59a3e63172406..c9a05460b70db 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -24,13 +24,13 @@ use rustc_infer::infer::region_constraints::{Constraint, RegionConstraintData}; use rustc_middle::metadata::Reexport; use rustc_middle::middle::resolve_bound_vars as rbv; use rustc_middle::ty::fold::TypeFolder; -use rustc_middle::ty::InternalSubsts; use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::{self, AdtKind, EarlyBinder, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::hygiene::{AstPass, MacroKind}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{self, ExpnKind}; +use rustc_trait_selection::traits::wf::object_region_bounds; use std::borrow::Cow; use std::collections::hash_map::Entry; @@ -53,7 +53,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< let mut inserted = FxHashSet::default(); items.extend(doc.foreigns.iter().map(|(item, renamed)| { let item = clean_maybe_renamed_foreign_item(cx, item, *renamed); - if let Some(name) = item.name && !item.attrs.lists(sym::doc).has_word(sym::hidden) { + if let Some(name) = item.name && (cx.render_options.document_hidden || !item.is_doc_hidden()) { inserted.insert((item.type_(), name)); } item @@ -63,7 +63,7 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< return None; } let item = clean_doc_module(x, cx); - if item.attrs.lists(sym::doc).has_word(sym::hidden) { + if !cx.render_options.document_hidden && item.is_doc_hidden() { // Hidden modules are stripped at a later stage. // If a hidden module has the same name as a visible one, we want // to keep both of them around. @@ -84,12 +84,25 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< } let v = clean_maybe_renamed_item(cx, item, *renamed, *import_id); for item in &v { - if let Some(name) = item.name && !item.attrs.lists(sym::doc).has_word(sym::hidden) { + if let Some(name) = item.name && (cx.render_options.document_hidden || !item.is_doc_hidden()) { inserted.insert((item.type_(), name)); } } v })); + items.extend(doc.inlined_foreigns.iter().flat_map(|((_, renamed), (res, local_import_id))| { + let Some(def_id) = res.opt_def_id() else { return Vec::new() }; + let name = renamed.unwrap_or_else(|| cx.tcx.item_name(def_id)); + let import = cx.tcx.hir().expect_item(*local_import_id); + match import.kind { + hir::ItemKind::Use(path, kind) => { + let hir::UsePath { segments, span, .. } = *path; + let path = hir::Path { segments, res: *res, span }; + clean_use_statement_inner(import, name, &path, kind, cx, &mut Default::default()) + } + _ => unreachable!(), + } + })); items.extend(doc.items.values().flat_map(|(item, renamed, _)| { // Now we actually lower the imports, skipping everything else. if let hir::ItemKind::Use(path, hir::UseKind::Glob) = item.kind { @@ -119,25 +132,31 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< }); let kind = ModuleItem(Module { items, span }); - generate_item_with_correct_attrs(cx, kind, doc.def_id, doc.name, doc.import_id, doc.renamed) + generate_item_with_correct_attrs( + cx, + kind, + doc.def_id.to_def_id(), + doc.name, + doc.import_id, + doc.renamed, + ) } fn generate_item_with_correct_attrs( cx: &mut DocContext<'_>, kind: ItemKind, - local_def_id: LocalDefId, + def_id: DefId, name: Symbol, import_id: Option, renamed: Option, ) -> Item { - let def_id = local_def_id.to_def_id(); let target_attrs = inline::load_attrs(cx, def_id); let attrs = if let Some(import_id) = import_id { let is_inline = inline::load_attrs(cx, import_id.to_def_id()) .lists(sym::doc) .get_word_attr(sym::inline) .is_some(); - let mut attrs = get_all_import_attributes(cx, import_id, local_def_id, is_inline); + let mut attrs = get_all_import_attributes(cx, import_id, def_id, is_inline); add_without_unwanted_attributes(&mut attrs, target_attrs, is_inline, None); attrs } else { @@ -166,8 +185,7 @@ fn clean_generic_bound<'tcx>( let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(cx.tcx, def_id)); let generic_args = clean_generic_args(generic_args, cx); - let GenericArgs::AngleBracketed { bindings, .. } = generic_args - else { + let GenericArgs::AngleBracketed { bindings, .. } = generic_args else { bug!("clean: parenthesized `GenericBound::LangItemTrait`"); }; @@ -197,11 +215,11 @@ pub(crate) fn clean_trait_ref_with_bindings<'tcx>( ) -> Path { let kind = cx.tcx.def_kind(trait_ref.def_id()).into(); if !matches!(kind, ItemType::Trait | ItemType::TraitAlias) { - span_bug!(cx.tcx.def_span(trait_ref.def_id()), "`TraitRef` had unexpected kind {:?}", kind); + span_bug!(cx.tcx.def_span(trait_ref.def_id()), "`TraitRef` had unexpected kind {kind:?}"); } inline::record_extern_fqn(cx, trait_ref.def_id(), kind); let path = - external_path(cx, trait_ref.def_id(), true, bindings, trait_ref.map_bound(|tr| tr.substs)); + external_path(cx, trait_ref.def_id(), true, bindings, trait_ref.map_bound(|tr| tr.args)); debug!(?trait_ref); @@ -239,7 +257,7 @@ fn clean_lifetime<'tcx>(lifetime: &hir::Lifetime, cx: &mut DocContext<'tcx>) -> | rbv::ResolvedArg::Free(_, node_id), ) = def { - if let Some(lt) = cx.substs.get(&node_id).and_then(|p| p.as_lt()).cloned() { + if let Some(lt) = cx.args.get(&node_id).and_then(|p| p.as_lt()).cloned() { return lt; } } @@ -250,10 +268,12 @@ pub(crate) fn clean_const<'tcx>(constant: &hir::ConstArg, cx: &mut DocContext<'t let def_id = cx.tcx.hir().body_owner_def_id(constant.value.body).to_def_id(); Constant { type_: clean_middle_ty( - ty::Binder::dummy(cx.tcx.type_of(def_id).subst_identity()), + ty::Binder::dummy(cx.tcx.type_of(def_id).instantiate_identity()), cx, Some(def_id), + None, ), + generics: Box::new(Generics::default()), kind: ConstantKind::Anonymous { body: constant.value.body }, } } @@ -264,7 +284,8 @@ pub(crate) fn clean_middle_const<'tcx>( ) -> Constant { // FIXME: instead of storing the stringified expression, store `self` directly instead. Constant { - type_: clean_middle_ty(constant.map_bound(|c| c.ty()), cx, None), + type_: clean_middle_ty(constant.map_bound(|c| c.ty()), cx, None, None), + generics: Box::new(Generics::default()), kind: ConstantKind::TyConst { expr: constant.skip_binder().to_string().into() }, } } @@ -283,7 +304,7 @@ pub(crate) fn clean_middle_region<'tcx>(region: ty::Region<'tcx>) -> Option { - debug!("cannot clean region {:?}", region); + debug!("cannot clean region {region:?}"); None } } @@ -324,36 +345,23 @@ fn clean_where_predicate<'tcx>( } pub(crate) fn clean_predicate<'tcx>( - predicate: ty::Predicate<'tcx>, + predicate: ty::Clause<'tcx>, cx: &mut DocContext<'tcx>, ) -> Option { let bound_predicate = predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { - clean_poly_trait_predicate(bound_predicate.rebind(pred), cx) - } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(pred)) => { - clean_region_outlives_predicate(pred) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(pred)) => { + ty::ClauseKind::Trait(pred) => clean_poly_trait_predicate(bound_predicate.rebind(pred), cx), + ty::ClauseKind::RegionOutlives(pred) => clean_region_outlives_predicate(pred), + ty::ClauseKind::TypeOutlives(pred) => { clean_type_outlives_predicate(bound_predicate.rebind(pred), cx) } - ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => { + ty::ClauseKind::Projection(pred) => { Some(clean_projection_predicate(bound_predicate.rebind(pred), cx)) } // FIXME(generic_const_exprs): should this do something? - ty::PredicateKind::ConstEvaluatable(..) => None, - ty::PredicateKind::WellFormed(..) => None, - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => None, - - ty::PredicateKind::Subtype(..) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => panic!("not user writable"), + ty::ClauseKind::ConstEvaluatable(..) + | ty::ClauseKind::WellFormed(..) + | ty::ClauseKind::ConstArgHasType(..) => None, } } @@ -362,15 +370,14 @@ fn clean_poly_trait_predicate<'tcx>( cx: &mut DocContext<'tcx>, ) -> Option { // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op. - if pred.skip_binder().constness == ty::BoundConstness::ConstIfConst - && Some(pred.skip_binder().def_id()) == cx.tcx.lang_items().destruct_trait() - { + // FIXME(effects) check constness + if Some(pred.skip_binder().def_id()) == cx.tcx.lang_items().destruct_trait() { return None; } let poly_trait_ref = pred.map_bound(|pred| pred.trait_ref); Some(WherePredicate::BoundPredicate { - ty: clean_middle_ty(poly_trait_ref.self_ty(), cx, None), + ty: clean_middle_ty(poly_trait_ref.self_ty(), cx, None, None), bounds: vec![clean_poly_trait_ref_with_bindings(cx, poly_trait_ref, ThinVec::new())], bound_params: Vec::new(), }) @@ -396,7 +403,7 @@ fn clean_type_outlives_predicate<'tcx>( let ty::OutlivesPredicate(ty, lt) = pred.skip_binder(); Some(WherePredicate::BoundPredicate { - ty: clean_middle_ty(pred.rebind(ty), cx, None), + ty: clean_middle_ty(pred.rebind(ty), cx, None, None), bounds: vec![GenericBound::Outlives( clean_middle_region(lt).expect("failed to clean lifetimes"), )], @@ -409,7 +416,7 @@ fn clean_middle_term<'tcx>( cx: &mut DocContext<'tcx>, ) -> Term { match term.skip_binder().unpack() { - ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None)), + ty::TermKind::Ty(ty) => Term::Type(clean_middle_ty(term.rebind(ty), cx, None, None)), ty::TermKind::Const(c) => Term::Constant(clean_middle_const(term.rebind(c), cx)), } } @@ -454,7 +461,7 @@ fn clean_projection<'tcx>( let bounds = cx .tcx .explicit_item_bounds(ty.skip_binder().def_id) - .subst_iter_copied(cx.tcx, ty.skip_binder().substs) + .iter_instantiated_copied(cx.tcx, ty.skip_binder().args) .map(|(pred, _)| pred) .collect::>(); return clean_middle_opaque_bounds(cx, bounds); @@ -462,7 +469,7 @@ fn clean_projection<'tcx>( let trait_ = clean_trait_ref_with_bindings(cx, ty.map_bound(|ty| ty.trait_ref(cx.tcx)), ThinVec::new()); - let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None); + let self_type = clean_middle_ty(ty.map_bound(|ty| ty.self_ty()), cx, None, None); let self_def_id = if let Some(def_id) = def_id { cx.tcx.opt_parent(def_id).or(Some(def_id)) } else { @@ -493,8 +500,13 @@ fn projection_to_path_segment<'tcx>( PathSegment { name: item.name, args: GenericArgs::AngleBracketed { - args: substs_to_args(cx, ty.map_bound(|ty| &ty.substs[generics.parent_count..]), false) - .into(), + args: ty_args_to_args( + cx, + ty.map_bound(|ty| &ty.args[generics.parent_count..]), + false, + None, + ) + .into(), bindings: Default::default(), }, } @@ -511,9 +523,10 @@ fn clean_generic_param_def<'tcx>( ty::GenericParamDefKind::Type { has_default, synthetic, .. } => { let default = if has_default { Some(clean_middle_ty( - ty::Binder::dummy(cx.tcx.type_of(def.def_id).subst_identity()), + ty::Binder::dummy(cx.tcx.type_of(def.def_id).instantiate_identity()), cx, Some(def.def_id), + None, )) } else { None @@ -540,10 +553,11 @@ fn clean_generic_param_def<'tcx>( ), cx, Some(def.def_id), + None, )), default: match has_default { true => Some(Box::new( - cx.tcx.const_param_default(def.def_id).subst_identity().to_string(), + cx.tcx.const_param_default(def.def_id).instantiate_identity().to_string(), )), false => None, }, @@ -782,34 +796,31 @@ fn clean_ty_generics<'tcx>( }) .collect::>(); - // param index -> [(trait DefId, associated type name & generics, type, higher-ranked params)] + // param index -> [(trait DefId, associated type name & generics, term, higher-ranked params)] let mut impl_trait_proj = FxHashMap::< u32, - Vec<(DefId, PathSegment, ty::Binder<'_, Ty<'_>>, Vec)>, + Vec<(DefId, PathSegment, ty::Binder<'_, ty::Term<'_>>, Vec)>, >::default(); let where_predicates = preds .predicates .iter() - .flat_map(|(p, _)| { + .flat_map(|(pred, _)| { let mut projection = None; let param_idx = (|| { - let bound_p = p.kind(); + let bound_p = pred.kind(); match bound_p.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { + ty::ClauseKind::Trait(pred) => { if let ty::Param(param) = pred.self_ty().kind() { return Some(param.index); } } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - ty, - _reg, - ))) => { + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, _reg)) => { if let ty::Param(param) = ty.kind() { return Some(param.index); } } - ty::PredicateKind::Clause(ty::Clause::Projection(p)) => { + ty::ClauseKind::Projection(p) => { if let ty::Param(param) = p.projection_ty.self_ty().kind() { projection = Some(bound_p.rebind(p)); return Some(param.index); @@ -822,34 +833,26 @@ fn clean_ty_generics<'tcx>( })(); if let Some(param_idx) = param_idx - && let Some(b) = impl_trait.get_mut(¶m_idx.into()) + && let Some(bounds) = impl_trait.get_mut(¶m_idx.into()) { - let p: WherePredicate = clean_predicate(*p, cx)?; + let pred = clean_predicate(*pred, cx)?; - b.extend( - p.get_bounds() + bounds.extend( + pred.get_bounds() .into_iter() .flatten() .cloned() - .filter(|b| !b.is_sized_bound(cx)), ); - let proj = projection.map(|p| { - ( - clean_projection(p.map_bound(|p| p.projection_ty), cx, None), - p.map_bound(|p| p.term), - ) - }); - if let Some(((_, trait_did, name), rhs)) = proj - .as_ref() - .and_then(|(lhs, rhs): &(Type, _)| Some((lhs.projection()?, rhs))) + if let Some(proj) = projection + && let lhs = clean_projection(proj.map_bound(|p| p.projection_ty), cx, None) + && let Some((_, trait_did, name)) = lhs.projection() { - // FIXME(...): Remove this unwrap() impl_trait_proj.entry(param_idx).or_default().push(( trait_did, name, - rhs.map_bound(|rhs| rhs.ty().unwrap()), - p.get_bound_params() + proj.map_bound(|p| p.term), + pred.get_bound_params() .into_iter() .flatten() .cloned() @@ -860,26 +863,38 @@ fn clean_ty_generics<'tcx>( return None; } - Some(p) + Some(pred) }) .collect::>(); for (param, mut bounds) in impl_trait { + let mut has_sized = false; + bounds.retain(|b| { + if b.is_sized_bound(cx) { + has_sized = true; + false + } else { + true + } + }); + if !has_sized { + bounds.push(GenericBound::maybe_sized(cx)); + } + // Move trait bounds to the front. - bounds.sort_by_key(|b| !matches!(b, GenericBound::TraitBound(..))); + bounds.sort_by_key(|b| !b.is_trait_bound()); + + // Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`). + // Since all potential trait bounds are at the front we can just check the first bound. + if bounds.first().map_or(true, |b| !b.is_trait_bound()) { + bounds.insert(0, GenericBound::sized(cx)); + } let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() }; if let Some(proj) = impl_trait_proj.remove(&idx) { for (trait_did, name, rhs, bound_params) in proj { - let rhs = clean_middle_ty(rhs, cx, None); - simplify::merge_bounds( - cx, - &mut bounds, - bound_params, - trait_did, - name, - &Term::Type(rhs), - ); + let rhs = clean_middle_term(rhs, cx); + simplify::merge_bounds(cx, &mut bounds, bound_params, trait_did, name, &rhs); } } @@ -895,7 +910,7 @@ fn clean_ty_generics<'tcx>( // implicit `Sized` bound unless removed with `?Sized`. // However, in the list of where-predicates below, `Sized` appears like a // normal bound: It's either present (the type is sized) or - // absent (the type is unsized) but never *maybe* (i.e. `?Sized`). + // absent (the type might be unsized) but never *maybe* (i.e. `?Sized`). // // This is unsuitable for rendering. // Thus, as a first step remove all `Sized` bounds that should be implicit. @@ -906,8 +921,8 @@ fn clean_ty_generics<'tcx>( let mut sized_params = FxHashSet::default(); where_predicates.retain(|pred| { if let WherePredicate::BoundPredicate { ty: Generic(g), bounds, .. } = pred - && *g != kw::SelfUpper - && bounds.iter().any(|b| b.is_sized_bound(cx)) + && *g != kw::SelfUpper + && bounds.iter().any(|b| b.is_sized_bound(cx)) { sized_params.insert(*g); false @@ -1111,8 +1126,8 @@ fn clean_fn_decl_with_args<'tcx>( args: Arguments, ) -> FnDecl { let output = match decl.output { - hir::FnRetTy::Return(typ) => Return(clean_ty(typ, cx)), - hir::FnRetTy::DefaultReturn(..) => DefaultReturn, + hir::FnRetTy::Return(typ) => clean_ty(typ, cx), + hir::FnRetTy::DefaultReturn(..) => Type::Tuple(Vec::new()), }; FnDecl { inputs: args, output, c_variadic: decl.c_variadic } } @@ -1126,10 +1141,7 @@ fn clean_fn_decl_from_did_and_sig<'tcx>( // We assume all empty tuples are default return type. This theoretically can discard `-> ()`, // but shouldn't change any code meaning. - let output = match clean_middle_ty(sig.output(), cx, None) { - Type::Tuple(inner) if inner.is_empty() => DefaultReturn, - ty => Return(ty), - }; + let output = clean_middle_ty(sig.output(), cx, None, None); FnDecl { output, @@ -1139,7 +1151,7 @@ fn clean_fn_decl_from_did_and_sig<'tcx>( .inputs() .iter() .map(|t| Argument { - type_: clean_middle_ty(t.map_bound(|t| *t), cx, None), + type_: clean_middle_ty(t.map_bound(|t| *t), cx, None, None), name: names .next() .map(|i| i.name) @@ -1177,11 +1189,18 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext let local_did = trait_item.owner_id.to_def_id(); cx.with_param_env(local_did, |cx| { let inner = match trait_item.kind { - hir::TraitItemKind::Const(ty, Some(default)) => AssocConstItem( - clean_ty(ty, cx), - ConstantKind::Local { def_id: local_did, body: default }, - ), - hir::TraitItemKind::Const(ty, None) => TyAssocConstItem(clean_ty(ty, cx)), + hir::TraitItemKind::Const(ty, Some(default)) => { + let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); + AssocConstItem( + Box::new(generics), + clean_ty(ty, cx), + ConstantKind::Local { def_id: local_did, body: default }, + ) + } + hir::TraitItemKind::Const(ty, None) => { + let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); + TyAssocConstItem(Box::new(generics), clean_ty(ty, cx)) + } hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => { let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Body(body)); MethodItem(m, None) @@ -1193,8 +1212,12 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext hir::TraitItemKind::Type(bounds, Some(default)) => { let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx)); let bounds = bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(); - let item_type = - clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, default)), cx, None); + let item_type = clean_middle_ty( + ty::Binder::dummy(hir_ty_to_ty(cx.tcx, default)), + cx, + None, + None, + ); AssocTypeItem( Box::new(Typedef { type_: clean_ty(default, cx), @@ -1222,19 +1245,24 @@ pub(crate) fn clean_impl_item<'tcx>( cx.with_param_env(local_did, |cx| { let inner = match impl_.kind { hir::ImplItemKind::Const(ty, expr) => { + let generics = clean_generics(impl_.generics, cx); let default = ConstantKind::Local { def_id: local_did, body: expr }; - AssocConstItem(clean_ty(ty, cx), default) + AssocConstItem(Box::new(generics), clean_ty(ty, cx), default) } hir::ImplItemKind::Fn(ref sig, body) => { let m = clean_function(cx, sig, impl_.generics, FunctionArgs::Body(body)); - let defaultness = cx.tcx.impl_defaultness(impl_.owner_id); + let defaultness = cx.tcx.defaultness(impl_.owner_id); MethodItem(m, Some(defaultness)) } hir::ImplItemKind::Type(hir_ty) => { let type_ = clean_ty(hir_ty, cx); let generics = clean_generics(impl_.generics, cx); - let item_type = - clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None); + let item_type = clean_middle_ty( + ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), + cx, + None, + None, + ); AssocTypeItem( Box::new(Typedef { type_, generics, item_type: Some(item_type) }), Vec::new(), @@ -1254,23 +1282,31 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( let kind = match assoc_item.kind { ty::AssocKind::Const => { let ty = clean_middle_ty( - ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()), + ty::Binder::dummy(tcx.type_of(assoc_item.def_id).instantiate_identity()), cx, Some(assoc_item.def_id), + None, ); + let mut generics = Box::new(clean_ty_generics( + cx, + tcx.generics_of(assoc_item.def_id), + tcx.explicit_predicates_of(assoc_item.def_id), + )); + simplify::move_bounds_to_generic_parameters(&mut generics); + let provided = match assoc_item.container { ty::ImplContainer => true, - ty::TraitContainer => tcx.impl_defaultness(assoc_item.def_id).has_value(), + ty::TraitContainer => tcx.defaultness(assoc_item.def_id).has_value(), }; if provided { - AssocConstItem(ty, ConstantKind::Extern { def_id: assoc_item.def_id }) + AssocConstItem(generics, ty, ConstantKind::Extern { def_id: assoc_item.def_id }) } else { - TyAssocConstItem(ty) + TyAssocConstItem(generics, ty) } } ty::AssocKind::Fn => { - let sig = tcx.fn_sig(assoc_item.def_id).subst_identity(); + let sig = tcx.fn_sig(assoc_item.def_id).instantiate_identity(); let late_bound_regions = sig.bound_vars().into_iter().filter_map(|var| match var { ty::BoundVariableKind::Region(ty::BrNamed(_, name)) @@ -1293,7 +1329,9 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( if assoc_item.fn_has_self_parameter { let self_ty = match assoc_item.container { - ty::ImplContainer => tcx.type_of(assoc_item.container_id(tcx)).subst_identity(), + ty::ImplContainer => { + tcx.type_of(assoc_item.container_id(tcx)).instantiate_identity() + } ty::TraitContainer => tcx.types.self_param, }; let self_arg_ty = sig.input(0).skip_binder(); @@ -1346,19 +1384,24 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( } } + let mut predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates; if let ty::TraitContainer = assoc_item.container { let bounds = - tcx.explicit_item_bounds(assoc_item.def_id).subst_identity_iter_copied(); - let predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates; - let predicates = - tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied())); - let mut generics = clean_ty_generics( - cx, - tcx.generics_of(assoc_item.def_id), - ty::GenericPredicates { parent: None, predicates }, - ); - // Filter out the bounds that are (likely?) directly attached to the associated type, - // as opposed to being located in the where clause. + tcx.explicit_item_bounds(assoc_item.def_id).instantiate_identity_iter_copied(); + predicates = tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied())); + } + let mut generics = clean_ty_generics( + cx, + tcx.generics_of(assoc_item.def_id), + ty::GenericPredicates { parent: None, predicates }, + ); + simplify::move_bounds_to_generic_parameters(&mut generics); + + if let ty::TraitContainer = assoc_item.container { + // Move bounds that are (likely) directly attached to the associated type + // from the where-clause to the associated type. + // There is no guarantee that this is what the user actually wrote but we have + // no way of knowing. let mut bounds: Vec = Vec::new(); generics.where_predicates.retain_mut(|pred| match *pred { WherePredicate::BoundPredicate { @@ -1415,44 +1458,19 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( } None => bounds.push(GenericBound::maybe_sized(cx)), } - // Move bounds that are (likely) directly attached to the parameters of the - // (generic) associated type from the where clause to the respective parameter. - // There is no guarantee that this is what the user actually wrote but we have - // no way of knowing. - let mut where_predicates = ThinVec::new(); - for mut pred in generics.where_predicates { - if let WherePredicate::BoundPredicate { ty: Generic(arg), bounds, .. } = &mut pred - && let Some(GenericParamDef { - kind: GenericParamDefKind::Type { bounds: param_bounds, .. }, - .. - }) = generics.params.iter_mut().find(|param| ¶m.name == arg) - { - param_bounds.append(bounds); - } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = &mut pred - && let Some(GenericParamDef { - kind: GenericParamDefKind::Lifetime { outlives: param_bounds }, - .. - }) = generics.params.iter_mut().find(|param| ¶m.name == arg) { - param_bounds.extend(bounds.drain(..).map(|bound| match bound { - GenericBound::Outlives(lifetime) => lifetime, - _ => unreachable!(), - })); - } else { - where_predicates.push(pred); - } - } - generics.where_predicates = where_predicates; - if tcx.impl_defaultness(assoc_item.def_id).has_value() { + if tcx.defaultness(assoc_item.def_id).has_value() { AssocTypeItem( Box::new(Typedef { type_: clean_middle_ty( - ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()), + ty::Binder::dummy( + tcx.type_of(assoc_item.def_id).instantiate_identity(), + ), cx, Some(assoc_item.def_id), + None, ), generics, - // FIXME: should we obtain the Type from HIR and pass it on here? item_type: None, }), bounds, @@ -1461,20 +1479,21 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( TyAssocTypeItem(generics, bounds) } } else { - // FIXME: when could this happen? Associated items in inherent impls? AssocTypeItem( Box::new(Typedef { type_: clean_middle_ty( - ty::Binder::dummy(tcx.type_of(assoc_item.def_id).subst_identity()), + ty::Binder::dummy( + tcx.type_of(assoc_item.def_id).instantiate_identity(), + ), cx, Some(assoc_item.def_id), + None, ), - generics: Generics { - params: ThinVec::new(), - where_predicates: ThinVec::new(), - }, + generics, item_type: None, }), + // Associated types inside trait or inherent impls are not allowed to have + // item bounds. Thus we don't attempt to move any bounds there. Vec::new(), ) } @@ -1484,14 +1503,127 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( Item::from_def_id_and_parts(assoc_item.def_id, Some(assoc_item.name), kind, cx) } +fn first_non_private_clean_path<'tcx>( + cx: &mut DocContext<'tcx>, + path: &hir::Path<'tcx>, + new_path_segments: &'tcx [hir::PathSegment<'tcx>], + new_path_span: rustc_span::Span, +) -> Path { + let new_hir_path = + hir::Path { segments: new_path_segments, res: path.res, span: new_path_span }; + let mut new_clean_path = clean_path(&new_hir_path, cx); + // In here we need to play with the path data one last time to provide it the + // missing `args` and `res` of the final `Path` we get, which, since it comes + // from a re-export, doesn't have the generics that were originally there, so + // we add them by hand. + if let Some(path_last) = path.segments.last().as_ref() + && let Some(new_path_last) = new_clean_path.segments[..].last_mut() + && let Some(path_last_args) = path_last.args.as_ref() + && path_last.args.is_some() + { + assert!(new_path_last.args.is_empty()); + new_path_last.args = clean_generic_args(path_last_args, cx); + } + new_clean_path +} + +/// The goal of this function is to return the first `Path` which is not private (ie not private +/// or `doc(hidden)`). If it's not possible, it'll return the "end type". +/// +/// If the path is not a re-export or is public, it'll return `None`. +fn first_non_private<'tcx>( + cx: &mut DocContext<'tcx>, + hir_id: hir::HirId, + path: &hir::Path<'tcx>, +) -> Option { + let target_def_id = path.res.opt_def_id()?; + let (parent_def_id, ident) = match &path.segments[..] { + [] => return None, + // Relative paths are available in the same scope as the owner. + [leaf] => (cx.tcx.local_parent(hir_id.owner.def_id), leaf.ident), + // So are self paths. + [parent, leaf] if parent.ident.name == kw::SelfLower => { + (cx.tcx.local_parent(hir_id.owner.def_id), leaf.ident) + } + // Crate paths are not. We start from the crate root. + [parent, leaf] if matches!(parent.ident.name, kw::Crate | kw::PathRoot) => { + (LOCAL_CRATE.as_def_id().as_local()?, leaf.ident) + } + [parent, leaf] if parent.ident.name == kw::Super => { + let parent_mod = cx.tcx.parent_module(hir_id); + if let Some(super_parent) = cx.tcx.opt_local_parent(parent_mod.to_local_def_id()) { + (super_parent, leaf.ident) + } else { + // If we can't find the parent of the parent, then the parent is already the crate. + (LOCAL_CRATE.as_def_id().as_local()?, leaf.ident) + } + } + // Absolute paths are not. We start from the parent of the item. + [.., parent, leaf] => (parent.res.opt_def_id()?.as_local()?, leaf.ident), + }; + let hir = cx.tcx.hir(); + // First we try to get the `DefId` of the item. + for child in + cx.tcx.module_children_local(parent_def_id).iter().filter(move |c| c.ident == ident) + { + if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = child.res { + continue; + } + + if let Some(def_id) = child.res.opt_def_id() && target_def_id == def_id { + let mut last_path_res = None; + 'reexps: for reexp in child.reexport_chain.iter() { + if let Some(use_def_id) = reexp.id() && + let Some(local_use_def_id) = use_def_id.as_local() && + let Some(hir::Node::Item(item)) = hir.find_by_def_id(local_use_def_id) && + !item.ident.name.is_empty() && + let hir::ItemKind::Use(path, _) = item.kind + { + for res in &path.res { + if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res { + continue; + } + if (cx.render_options.document_hidden || + !cx.tcx.is_doc_hidden(use_def_id)) && + // We never check for "cx.render_options.document_private" + // because if a re-export is not fully public, it's never + // documented. + cx.tcx.local_visibility(local_use_def_id).is_public() { + break 'reexps; + } + last_path_res = Some((path, res)); + continue 'reexps; + } + } + } + if !child.reexport_chain.is_empty() { + // So in here, we use the data we gathered from iterating the reexports. If + // `last_path_res` is set, it can mean two things: + // + // 1. We found a public reexport. + // 2. We didn't find a public reexport so it's the "end type" path. + if let Some((new_path, _)) = last_path_res { + return Some(first_non_private_clean_path(cx, path, new_path.segments, new_path.span)); + } + // If `last_path_res` is `None`, it can mean two things: + // + // 1. The re-export is public, no need to change anything, just use the path as is. + // 2. Nothing was found, so let's just return the original path. + return None; + } + } + } + None +} + fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type { - let hir::Ty { hir_id: _, span, ref kind } = *hir_ty; + let hir::Ty { hir_id, span, ref kind } = *hir_ty; let hir::TyKind::Path(qpath) = kind else { unreachable!() }; match qpath { hir::QPath::Resolved(None, path) => { if let Res::Def(DefKind::TyParam, did) = path.res { - if let Some(new_ty) = cx.substs.get(&did).and_then(|p| p.as_ty()).cloned() { + if let Some(new_ty) = cx.args.get(&did).and_then(|p| p.as_ty()).cloned() { return new_ty; } if let Some(bounds) = cx.impl_trait_bounds.remove(&did.into()) { @@ -1502,7 +1634,12 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type if let Some(expanded) = maybe_expand_private_type_alias(cx, path) { expanded } else { - let path = clean_path(path, cx); + // First we check if it's a private re-export. + let path = if let Some(path) = first_non_private(cx, hir_id, &path) { + path + } else { + clean_path(path, cx) + }; resolve_type(cx, path) } } @@ -1513,7 +1650,7 @@ fn clean_qpath<'tcx>(hir_ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type if !ty.has_escaping_bound_vars() && let Some(normalized_value) = normalize(cx, ty::Binder::dummy(ty)) { - return clean_middle_ty(normalized_value, cx, None); + return clean_middle_ty(normalized_value, cx, None, None); } let trait_segments = &p.segments[..p.segments.len() - 1]; @@ -1569,7 +1706,7 @@ fn maybe_expand_private_type_alias<'tcx>( cx: &mut DocContext<'tcx>, path: &hir::Path<'tcx>, ) -> Option { - let Res::Def(DefKind::TyAlias, def_id) = path.res else { return None }; + let Res::Def(DefKind::TyAlias { .. }, def_id) = path.res else { return None }; // Substitute private type aliases let def_id = def_id.as_local()?; let alias = if !cx.cache.effective_visibilities.is_exported(cx.tcx, def_id.to_def_id()) @@ -1582,7 +1719,7 @@ fn maybe_expand_private_type_alias<'tcx>( let hir::ItemKind::TyAlias(ty, generics) = alias else { return None }; let provided_params = &path.segments.last().expect("segments were empty"); - let mut substs = DefIdMap::default(); + let mut args = DefIdMap::default(); let generic_args = provided_params.args(); let mut indices: hir::GenericParamCount = Default::default(); @@ -1606,7 +1743,7 @@ fn maybe_expand_private_type_alias<'tcx>( } else { Lifetime::elided() }; - substs.insert(param.def_id.to_def_id(), SubstParam::Lifetime(cleaned)); + args.insert(param.def_id.to_def_id(), SubstParam::Lifetime(cleaned)); } indices.lifetimes += 1; } @@ -1623,10 +1760,9 @@ fn maybe_expand_private_type_alias<'tcx>( _ => None, }); if let Some(ty) = type_ { - substs.insert(param.def_id.to_def_id(), SubstParam::Type(clean_ty(ty, cx))); + args.insert(param.def_id.to_def_id(), SubstParam::Type(clean_ty(ty, cx))); } else if let Some(default) = *default { - substs - .insert(param.def_id.to_def_id(), SubstParam::Type(clean_ty(default, cx))); + args.insert(param.def_id.to_def_id(), SubstParam::Type(clean_ty(default, cx))); } indices.types += 1; } @@ -1643,7 +1779,7 @@ fn maybe_expand_private_type_alias<'tcx>( _ => None, }); if let Some(ct) = const_ { - substs.insert( + args.insert( param.def_id.to_def_id(), SubstParam::Constant(clean_const(ct, cx)), ); @@ -1654,7 +1790,7 @@ fn maybe_expand_private_type_alias<'tcx>( } } - Some(cx.enter_alias(substs, def_id.to_def_id(), |cx| clean_ty(ty, cx))) + Some(cx.enter_alias(args, def_id.to_def_id(), |cx| clean_ty(&ty, cx))) } pub(crate) fn clean_ty<'tcx>(ty: &hir::Ty<'tcx>, cx: &mut DocContext<'tcx>) -> Type { @@ -1731,21 +1867,163 @@ fn normalize<'tcx>( .map(|resolved| infcx.resolve_vars_if_possible(resolved.value)); match normalized { Ok(normalized_value) => { - debug!("normalized {:?} to {:?}", ty, normalized_value); + debug!("normalized {ty:?} to {normalized_value:?}"); Some(normalized_value) } Err(err) => { - debug!("failed to normalize {:?}: {:?}", ty, err); + debug!("failed to normalize {ty:?}: {err:?}"); None } } } +fn clean_trait_object_lifetime_bound<'tcx>( + region: ty::Region<'tcx>, + container: Option>, + preds: &'tcx ty::List>, + tcx: TyCtxt<'tcx>, +) -> Option { + if can_elide_trait_object_lifetime_bound(region, container, preds, tcx) { + return None; + } + + // Since there is a semantic difference between an implicitly elided (i.e. "defaulted") object + // lifetime and an explicitly elided object lifetime (`'_`), we intentionally don't hide the + // latter contrary to `clean_middle_region`. + match *region { + ty::ReStatic => Some(Lifetime::statik()), + ty::ReEarlyBound(region) if region.name != kw::Empty => Some(Lifetime(region.name)), + ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) + if name != kw::Empty => + { + Some(Lifetime(name)) + } + ty::ReEarlyBound(_) + | ty::ReLateBound(..) + | ty::ReFree(_) + | ty::ReVar(_) + | ty::RePlaceholder(_) + | ty::ReErased + | ty::ReError(_) => None, + } +} + +fn can_elide_trait_object_lifetime_bound<'tcx>( + region: ty::Region<'tcx>, + container: Option>, + preds: &'tcx ty::List>, + tcx: TyCtxt<'tcx>, +) -> bool { + // Below we quote extracts from https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes + + // > If the trait object is used as a type argument of a generic type then the containing type is + // > first used to try to infer a bound. + let default = container + .map_or(ObjectLifetimeDefault::Empty, |container| container.object_lifetime_default(tcx)); + + // > If there is a unique bound from the containing type then that is the default + // If there is a default object lifetime and the given region is lexically equal to it, elide it. + match default { + ObjectLifetimeDefault::Static => return *region == ty::ReStatic, + // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly. + ObjectLifetimeDefault::Arg(default) => return region.get_name() == default.get_name(), + // > If there is more than one bound from the containing type then an explicit bound must be specified + // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible. + // Don't elide the lifetime. + ObjectLifetimeDefault::Ambiguous => return false, + // There is no meaningful bound. Further processing is needed... + ObjectLifetimeDefault::Empty => {} + } + + // > If neither of those rules apply, then the bounds on the trait are used: + match *object_region_bounds(tcx, preds) { + // > If the trait has no lifetime bounds, then the lifetime is inferred in expressions + // > and is 'static outside of expressions. + // FIXME: If we are in an expression context (i.e. fn bodies and const exprs) then the default is + // `'_` and not `'static`. Only if we are in a non-expression one, the default is `'static`. + // Note however that at the time of this writing it should be fine to disregard this subtlety + // as we neither render const exprs faithfully anyway (hiding them in some places or using `_` instead) + // nor show the contents of fn bodies. + [] => *region == ty::ReStatic, + // > If the trait is defined with a single lifetime bound then that bound is used. + // > If 'static is used for any lifetime bound then 'static is used. + // FIXME(fmease): Don't compare lexically but respect de Bruijn indices etc. to handle shadowing correctly. + [object_region] => object_region.get_name() == region.get_name(), + // There are several distinct trait regions and none are `'static`. + // Due to ambiguity there is no default trait-object lifetime and thus elision is impossible. + // Don't elide the lifetime. + _ => false, + } +} + +#[derive(Debug)] +pub(crate) enum ContainerTy<'tcx> { + Ref(ty::Region<'tcx>), + Regular { + ty: DefId, + args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>, + has_self: bool, + arg: usize, + }, +} + +impl<'tcx> ContainerTy<'tcx> { + fn object_lifetime_default(self, tcx: TyCtxt<'tcx>) -> ObjectLifetimeDefault<'tcx> { + match self { + Self::Ref(region) => ObjectLifetimeDefault::Arg(region), + Self::Regular { ty: container, args, has_self, arg: index } => { + let (DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::TyAlias { .. } + | DefKind::Trait) = tcx.def_kind(container) + else { + return ObjectLifetimeDefault::Empty; + }; + + let generics = tcx.generics_of(container); + debug_assert_eq!(generics.parent_count, 0); + + // If the container is a trait object type, the arguments won't contain the self type but the + // generics of the corresponding trait will. In such a case, offset the index by one. + // For comparison, if the container is a trait inside a bound, the arguments do contain the + // self type. + let offset = + if !has_self && generics.parent.is_none() && generics.has_self { 1 } else { 0 }; + let param = generics.params[index + offset].def_id; + + let default = tcx.object_lifetime_default(param); + match default { + rbv::ObjectLifetimeDefault::Param(lifetime) => { + // The index is relative to the parent generics but since we don't have any, + // we don't need to translate it. + let index = generics.param_def_id_to_index[&lifetime]; + let arg = args.skip_binder()[index as usize].expect_region(); + ObjectLifetimeDefault::Arg(arg) + } + rbv::ObjectLifetimeDefault::Empty => ObjectLifetimeDefault::Empty, + rbv::ObjectLifetimeDefault::Static => ObjectLifetimeDefault::Static, + rbv::ObjectLifetimeDefault::Ambiguous => ObjectLifetimeDefault::Ambiguous, + } + } + } + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum ObjectLifetimeDefault<'tcx> { + Empty, + Static, + Ambiguous, + Arg(ty::Region<'tcx>), +} + #[instrument(level = "trace", skip(cx), ret)] pub(crate) fn clean_middle_ty<'tcx>( bound_ty: ty::Binder<'tcx, Ty<'tcx>>, cx: &mut DocContext<'tcx>, parent_def_id: Option, + container: Option>, ) -> Type { let bound_ty = normalize(cx, bound_ty).unwrap_or(bound_ty); match *bound_ty.skip_binder().kind() { @@ -1756,19 +2034,24 @@ pub(crate) fn clean_middle_ty<'tcx>( ty::Uint(uint_ty) => Primitive(uint_ty.into()), ty::Float(float_ty) => Primitive(float_ty.into()), ty::Str => Primitive(PrimitiveType::Str), - ty::Slice(ty) => Slice(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None))), + ty::Slice(ty) => Slice(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None))), ty::Array(ty, mut n) => { n = n.eval(cx.tcx, ty::ParamEnv::reveal_all()); let n = print_const(cx, n); - Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None)), n.into()) + Array(Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None, None)), n.into()) } ty::RawPtr(mt) => { - RawPointer(mt.mutbl, Box::new(clean_middle_ty(bound_ty.rebind(mt.ty), cx, None))) + RawPointer(mt.mutbl, Box::new(clean_middle_ty(bound_ty.rebind(mt.ty), cx, None, None))) } ty::Ref(r, ty, mutbl) => BorrowedRef { lifetime: clean_middle_region(r), mutability: mutbl, - type_: Box::new(clean_middle_ty(bound_ty.rebind(ty), cx, None)), + type_: Box::new(clean_middle_ty( + bound_ty.rebind(ty), + cx, + None, + Some(ContainerTy::Ref(r)), + )), }, ty::FnDef(..) | ty::FnPtr(_) => { // FIXME: should we merge the outer and inner binders somehow? @@ -1781,7 +2064,7 @@ pub(crate) fn clean_middle_ty<'tcx>( abi: sig.abi(), })) } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let did = def.did(); let kind = match def.adt_kind() { AdtKind::Struct => ItemType::Struct, @@ -1789,7 +2072,7 @@ pub(crate) fn clean_middle_ty<'tcx>( AdtKind::Enum => ItemType::Enum, }; inline::record_extern_fqn(cx, did, kind); - let path = external_path(cx, did, false, ThinVec::new(), bound_ty.rebind(substs)); + let path = external_path(cx, did, false, ThinVec::new(), bound_ty.rebind(args)); Type::Path { path } } ty::Foreign(did) => { @@ -1799,7 +2082,7 @@ pub(crate) fn clean_middle_ty<'tcx>( did, false, ThinVec::new(), - ty::Binder::dummy(InternalSubsts::empty()), + ty::Binder::dummy(ty::GenericArgs::empty()), ); Type::Path { path } } @@ -1812,21 +2095,19 @@ pub(crate) fn clean_middle_ty<'tcx>( .principal_def_id() .or_else(|| dids.next()) .unwrap_or_else(|| panic!("found trait object `{bound_ty:?}` with no traits?")); - let substs = match obj.principal() { - Some(principal) => principal.map_bound(|p| p.substs), - // marker traits have no substs. - _ => ty::Binder::dummy(InternalSubsts::empty()), + let args = match obj.principal() { + Some(principal) => principal.map_bound(|p| p.args), + // marker traits have no args. + _ => ty::Binder::dummy(ty::GenericArgs::empty()), }; inline::record_extern_fqn(cx, did, ItemType::Trait); - // FIXME(fmease): Hide the trait-object lifetime bound if it coincides with its default - // to partially address #44306. Follow the rules outlined at - // https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes - let lifetime = clean_middle_region(*reg); + let lifetime = clean_trait_object_lifetime_bound(*reg, container, obj, cx.tcx); + let mut bounds = dids .map(|did| { - let empty = ty::Binder::dummy(InternalSubsts::empty()); + let empty = ty::Binder::dummy(ty::GenericArgs::empty()); let path = external_path(cx, did, false, ThinVec::new(), empty); inline::record_extern_fqn(cx, did, ItemType::Trait); PolyTrait { trait_: path, generic_params: Vec::new() } @@ -1840,7 +2121,7 @@ pub(crate) fn clean_middle_ty<'tcx>( pb.map_bound(|pb| { pb // HACK(compiler-errors): Doesn't actually matter what self - // type we put here, because we're only using the GAT's substs. + // type we put here, because we're only using the GAT's args. .with_self_ty(cx.tcx, cx.tcx.types.self_param) .projection_ty }), @@ -1866,31 +2147,32 @@ pub(crate) fn clean_middle_ty<'tcx>( .collect(); let late_bound_regions = late_bound_regions.into_iter().collect(); - let path = external_path(cx, did, false, bindings, substs); + let path = external_path(cx, did, false, bindings, args); bounds.insert(0, PolyTrait { trait_: path, generic_params: late_bound_regions }); DynTrait(bounds, lifetime) } ty::Tuple(t) => { - Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None)).collect()) + Tuple(t.iter().map(|t| clean_middle_ty(bound_ty.rebind(t), cx, None, None)).collect()) } - ty::Alias(ty::Projection, ref data) => { - clean_projection(bound_ty.rebind(*data), cx, parent_def_id) + ty::Alias(ty::Projection, data) => { + clean_projection(bound_ty.rebind(data), cx, parent_def_id) } ty::Alias(ty::Inherent, alias_ty) => { let alias_ty = bound_ty.rebind(alias_ty); - let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None); + let self_type = clean_middle_ty(alias_ty.map_bound(|ty| ty.self_ty()), cx, None, None); Type::QPath(Box::new(QPathData { assoc: PathSegment { name: cx.tcx.associated_item(alias_ty.skip_binder().def_id).name, args: GenericArgs::AngleBracketed { - args: substs_to_args( + args: ty_args_to_args( cx, - alias_ty.map_bound(|ty| ty.substs.as_slice()), + alias_ty.map_bound(|ty| ty.args.as_slice()), true, + None, ) .into(), bindings: Default::default(), @@ -1902,6 +2184,24 @@ pub(crate) fn clean_middle_ty<'tcx>( })) } + ty::Alias(ty::Weak, data) => { + if cx.tcx.features().lazy_type_alias { + // Weak type alias `data` represents the `type X` in `type X = Y`. If we need `Y`, + // we need to use `type_of`. + let path = external_path( + cx, + data.def_id, + false, + ThinVec::new(), + bound_ty.rebind(data.args), + ); + Type::Path { path } + } else { + let ty = cx.tcx.type_of(data.def_id).instantiate(cx.tcx, data.args); + clean_middle_ty(bound_ty.rebind(ty), cx, None, None) + } + } + ty::Param(ref p) => { if let Some(bounds) = cx.impl_trait_bounds.remove(&p.index.into()) { ImplTrait(bounds) @@ -1910,11 +2210,10 @@ pub(crate) fn clean_middle_ty<'tcx>( } } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { // If it's already in the same alias, don't get an infinite loop. if cx.current_type_aliases.contains_key(&def_id) { - let path = - external_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(substs)); + let path = external_path(cx, def_id, false, ThinVec::new(), bound_ty.rebind(args)); Type::Path { path } } else { *cx.current_type_aliases.entry(def_id).or_insert(0) += 1; @@ -1923,7 +2222,7 @@ pub(crate) fn clean_middle_ty<'tcx>( let bounds = cx .tcx .explicit_item_bounds(def_id) - .subst_iter_copied(cx.tcx, substs) + .iter_instantiated_copied(cx.tcx, args) .map(|(bound, _)| bound) .collect::>(); let ty = clean_middle_opaque_bounds(cx, bounds); @@ -1950,26 +2249,17 @@ pub(crate) fn clean_middle_ty<'tcx>( fn clean_middle_opaque_bounds<'tcx>( cx: &mut DocContext<'tcx>, - bounds: Vec>, + bounds: Vec>, ) -> Type { - let mut regions = vec![]; let mut has_sized = false; let mut bounds = bounds .iter() .filter_map(|bound| { let bound_predicate = bound.kind(); let trait_ref = match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(tr)) => { - bound_predicate.rebind(tr.trait_ref) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - _ty, - reg, - ))) => { - if let Some(r) = clean_middle_region(reg) { - regions.push(GenericBound::Outlives(r)); - } - return None; + ty::ClauseKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref), + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => { + return clean_middle_region(reg).map(GenericBound::Outlives); } _ => return None, }; @@ -1982,9 +2272,7 @@ fn clean_middle_opaque_bounds<'tcx>( let bindings: ThinVec<_> = bounds .iter() .filter_map(|bound| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = - bound.kind().skip_binder() - { + if let ty::ClauseKind::Projection(proj) = bound.kind().skip_binder() { if proj.projection_ty.trait_ref(cx.tcx) == trait_ref.skip_binder() { Some(TypeBinding { assoc: projection_to_path_segment( @@ -2007,10 +2295,20 @@ fn clean_middle_opaque_bounds<'tcx>( Some(clean_poly_trait_ref_with_bindings(cx, trait_ref, bindings)) }) .collect::>(); - bounds.extend(regions); - if !has_sized && !bounds.is_empty() { - bounds.insert(0, GenericBound::maybe_sized(cx)); + + if !has_sized { + bounds.push(GenericBound::maybe_sized(cx)); } + + // Move trait bounds to the front. + bounds.sort_by_key(|b| !b.is_trait_bound()); + + // Add back a `Sized` bound if there are no *trait* bounds remaining (incl. `?Sized`). + // Since all potential trait bounds are at the front we can just check the first bound. + if bounds.first().map_or(true, |b| !b.is_trait_bound()) { + bounds.insert(0, GenericBound::sized(cx)); + } + ImplTrait(bounds) } @@ -2023,9 +2321,10 @@ pub(crate) fn clean_middle_field<'tcx>(field: &ty::FieldDef, cx: &mut DocContext field.did, field.name, clean_middle_ty( - ty::Binder::dummy(cx.tcx.type_of(field.did).subst_identity()), + ty::Binder::dummy(cx.tcx.type_of(field.did).instantiate_identity()), cx, Some(field.did), + None, ), cx, ) @@ -2153,10 +2452,10 @@ fn clean_bare_fn_ty<'tcx>( pub(crate) fn reexport_chain<'tcx>( tcx: TyCtxt<'tcx>, import_def_id: LocalDefId, - target_def_id: LocalDefId, + target_def_id: DefId, ) -> &'tcx [Reexport] { for child in tcx.module_children_local(tcx.local_parent(import_def_id)) { - if child.res.opt_def_id() == Some(target_def_id.to_def_id()) + if child.res.opt_def_id() == Some(target_def_id) && child.reexport_chain.first().and_then(|r| r.id()) == Some(import_def_id.to_def_id()) { return &child.reexport_chain; @@ -2169,7 +2468,7 @@ pub(crate) fn reexport_chain<'tcx>( fn get_all_import_attributes<'hir>( cx: &mut DocContext<'hir>, import_def_id: LocalDefId, - target_def_id: LocalDefId, + target_def_id: DefId, is_inline: bool, ) -> Vec<(Cow<'hir, ast::Attribute>, Option)> { let mut attrs = Vec::new(); @@ -2183,7 +2482,8 @@ fn get_all_import_attributes<'hir>( // This is the "original" reexport so we get all its attributes without filtering them. attrs = import_attrs.iter().map(|attr| (Cow::Borrowed(attr), Some(def_id))).collect(); first = false; - } else { + // We don't add attributes of an intermediate re-export if it has `#[doc(hidden)]`. + } else if cx.render_options.document_hidden || !cx.tcx.is_doc_hidden(def_id) { add_without_unwanted_attributes(&mut attrs, import_attrs, is_inline, Some(def_id)); } } @@ -2191,19 +2491,19 @@ fn get_all_import_attributes<'hir>( } fn filter_tokens_from_list( - args_tokens: TokenStream, + args_tokens: &TokenStream, should_retain: impl Fn(&TokenTree) -> bool, ) -> Vec { let mut tokens = Vec::with_capacity(args_tokens.len()); let mut skip_next_comma = false; - for token in args_tokens.into_trees() { + for token in args_tokens.trees() { match token { TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) if skip_next_comma => { skip_next_comma = false; } - token if should_retain(&token) => { + token if should_retain(token) => { skip_next_comma = false; - tokens.push(token); + tokens.push(token.clone()); } _ => { skip_next_comma = true; @@ -2213,7 +2513,7 @@ fn filter_tokens_from_list( tokens } -/// When inlining items, we merge its attributes (and all the reexports attributes too) with the +/// When inlining items, we merge their attributes (and all the reexports attributes too) with the /// final reexport. For example: /// /// ```ignore (just an example) @@ -2261,7 +2561,7 @@ fn add_without_unwanted_attributes<'hir>( match normal.item.args { ast::AttrArgs::Delimited(ref mut args) => { let tokens = - filter_tokens_from_list(args.tokens.clone(), |token| { + filter_tokens_from_list(&args.tokens, |token| { !matches!( token, TokenTree::Token( @@ -2305,8 +2605,9 @@ fn clean_maybe_renamed_item<'tcx>( ItemKind::Static(ty, mutability, body_id) => { StaticItem(Static { type_: clean_ty(ty, cx), mutability, expr: Some(body_id) }) } - ItemKind::Const(ty, body_id) => ConstantItem(Constant { + ItemKind::Const(ty, generics, body_id) => ConstantItem(Constant { type_: clean_ty(ty, cx), + generics: Box::new(clean_generics(generics, cx)), kind: ConstantKind::Local { body: body_id, def_id }, }), ItemKind::OpaqueTy(ref ty) => OpaqueTyItem(OpaqueTy { @@ -2316,7 +2617,12 @@ fn clean_maybe_renamed_item<'tcx>( ItemKind::TyAlias(hir_ty, generics) => { *cx.current_type_aliases.entry(def_id).or_insert(0) += 1; let rustdoc_ty = clean_ty(hir_ty, cx); - let ty = clean_middle_ty(ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), cx, None); + let ty = clean_middle_ty( + ty::Binder::dummy(hir_ty_to_ty(cx.tcx, hir_ty)), + cx, + None, + None, + ); let generics = clean_generics(generics, cx); if let Some(count) = cx.current_type_aliases.get_mut(&def_id) { *count -= 1; @@ -2380,7 +2686,7 @@ fn clean_maybe_renamed_item<'tcx>( vec![generate_item_with_correct_attrs( cx, kind, - item.owner_id.def_id, + item.owner_id.def_id.to_def_id(), name, import_id, renamed, @@ -2416,10 +2722,11 @@ fn clean_impl<'tcx>( let for_ = clean_ty(impl_.self_ty, cx); let type_alias = for_.def_id(&cx.cache).and_then(|alias_def_id: DefId| match tcx.def_kind(alias_def_id) { - DefKind::TyAlias => Some(clean_middle_ty( - ty::Binder::dummy(tcx.type_of(def_id).subst_identity()), + DefKind::TyAlias { .. } => Some(clean_middle_ty( + ty::Binder::dummy(tcx.type_of(def_id).instantiate_identity()), cx, Some(def_id.to_def_id()), + None, )), _ => None, }); @@ -2481,15 +2788,12 @@ fn clean_extern_crate<'tcx>( } } - // FIXME: using `from_def_id_and_kind` breaks `rustdoc/masked` for some reason - vec![Item { - name: Some(name), - attrs: Box::new(Attributes::from_ast(attrs)), - item_id: crate_def_id.into(), - kind: Box::new(ExternCrateItem { src: orig_name }), - cfg: attrs.cfg(cx.tcx, &cx.cache.hidden_cfg), - inline_stmt_id: Some(krate_owner_def_id), - }] + vec![Item::from_def_id_and_parts( + krate_owner_def_id, + Some(name), + ExternCrateItem { src: orig_name }, + cx, + )] } fn clean_use_statement<'tcx>( @@ -2503,9 +2807,6 @@ fn clean_use_statement<'tcx>( let mut items = Vec::new(); let hir::UsePath { segments, ref res, span } = *path; for &res in res { - if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res { - continue; - } let path = hir::Path { segments, res, span }; items.append(&mut clean_use_statement_inner(import, name, &path, kind, cx, inlined_names)); } @@ -2520,6 +2821,9 @@ fn clean_use_statement_inner<'tcx>( cx: &mut DocContext<'tcx>, inlined_names: &mut FxHashSet<(ItemType, Symbol)>, ) -> Vec { + if should_ignore_res(path.res) { + return Vec::new(); + } // We need this comparison because some imports (for std types for example) // are "inserted" as well but directly by the compiler and they should not be // taken into account. @@ -2532,11 +2836,12 @@ fn clean_use_statement_inner<'tcx>( let inline_attr = attrs.lists(sym::doc).get_word_attr(sym::inline); let pub_underscore = visibility.is_public() && name == kw::Underscore; let current_mod = cx.tcx.parent_module_from_def_id(import.owner_id.def_id); + let import_def_id = import.owner_id.def_id.to_def_id(); // The parent of the module in which this import resides. This // is the same as `current_mod` if that's already the top // level module. - let parent_mod = cx.tcx.parent_module_from_def_id(current_mod); + let parent_mod = cx.tcx.parent_module_from_def_id(current_mod.to_local_def_id()); // This checks if the import can be seen from a higher level module. // In other words, it checks if the visibility is the equivalent of @@ -2582,9 +2887,14 @@ fn clean_use_statement_inner<'tcx>( let inner = if kind == hir::UseKind::Glob { if !denied { let mut visited = DefIdSet::default(); - if let Some(items) = - inline::try_inline_glob(cx, path.res, current_mod, &mut visited, inlined_names) - { + if let Some(items) = inline::try_inline_glob( + cx, + path.res, + current_mod, + &mut visited, + inlined_names, + import, + ) { return items; } } @@ -2592,14 +2902,14 @@ fn clean_use_statement_inner<'tcx>( } else { if inline_attr.is_none() && let Res::Def(DefKind::Mod, did) = path.res - && !did.is_local() && did.is_crate_root() + && !did.is_local() + && did.is_crate_root() { // if we're `pub use`ing an extern crate root, don't inline it unless we // were specifically asked for it denied = true; } if !denied { - let import_def_id = import.owner_id.to_def_id(); if let Some(mut items) = inline::try_inline( cx, path.res, @@ -2619,7 +2929,7 @@ fn clean_use_statement_inner<'tcx>( Import::new_simple(name, resolve_use_source(cx, path), true) }; - vec![Item::from_def_id_and_parts(import.owner_id.to_def_id(), None, ImportItem(inner), cx)] + vec![Item::from_def_id_and_parts(import_def_id, None, ImportItem(inner), cx)] } fn clean_maybe_renamed_foreign_item<'tcx>( diff --git a/src/librustdoc/clean/render_macro_matchers.rs b/src/librustdoc/clean/render_macro_matchers.rs index ef38ca3c16c1a..66d10f2368b22 100644 --- a/src/librustdoc/clean/render_macro_matchers.rs +++ b/src/librustdoc/clean/render_macro_matchers.rs @@ -76,17 +76,13 @@ fn snippet_equal_to_token(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Option reparsed_trees, - Err(diagnostic) => { - diagnostic.cancel(); - return None; - } - }; - if reparsed_trees.len() != 1 { + if parser.token == token::Eof { + return None; + } + let reparsed_tree = parser.parse_token_tree(); + if parser.token != token::Eof { return None; } - let reparsed_tree = reparsed_trees.pop().unwrap(); // Compare against the original tree. if reparsed_tree.eq_unspanned(matcher) { Some(snippet) } else { None } diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index 3c72b0bf9f2f6..7b8f20326ed47 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -47,7 +47,9 @@ pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: Vec) -> ThinVec, child: DefId, trait_: DefId) .predicates .iter() .filter_map(|(pred, _)| { - if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = pred.kind().skip_binder() { + if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder() { if pred.trait_ref.self_ty() == self_ty { Some(pred.def_id()) } else { None } } else { None @@ -136,3 +138,38 @@ fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId) }) .any(|did| trait_is_same_or_supertrait(cx, did, trait_)) } + +/// Move bounds that are (likely) directly attached to generic parameters from the where-clause to +/// the respective parameter. +/// +/// There is no guarantee that this is what the user actually wrote but we have no way of knowing. +// FIXME(fmease): It'd make a lot of sense to just incorporate this logic into `clean_ty_generics` +// making every of its users benefit from it. +pub(crate) fn move_bounds_to_generic_parameters(generics: &mut clean::Generics) { + use clean::types::*; + + let mut where_predicates = ThinVec::new(); + for mut pred in generics.where_predicates.drain(..) { + if let WherePredicate::BoundPredicate { ty: Generic(arg), bounds, .. } = &mut pred + && let Some(GenericParamDef { + kind: GenericParamDefKind::Type { bounds: param_bounds, .. }, + .. + }) = generics.params.iter_mut().find(|param| ¶m.name == arg) + { + param_bounds.append(bounds); + } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = &mut pred + && let Some(GenericParamDef { + kind: GenericParamDefKind::Lifetime { outlives: param_bounds }, + .. + }) = generics.params.iter_mut().find(|param| ¶m.name == arg) + { + param_bounds.extend(bounds.drain(..).map(|bound| match bound { + GenericBound::Outlives(lifetime) => lifetime, + _ => unreachable!(), + })); + } else { + where_predicates.push(pred); + } + } + generics.where_predicates = where_predicates; +} diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 38664c3e359a6..49bde1d315266 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -42,7 +42,6 @@ use crate::formats::item_type::ItemType; use crate::html::render::Context; use crate::passes::collect_intra_doc_links::UrlFragment; -pub(crate) use self::FnRetTy::*; pub(crate) use self::ItemKind::*; pub(crate) use self::SelfTy::*; pub(crate) use self::Type::{ @@ -79,7 +78,7 @@ impl ItemId { #[track_caller] pub(crate) fn expect_def_id(self) -> DefId { self.as_def_id() - .unwrap_or_else(|| panic!("ItemId::expect_def_id: `{:?}` isn't a DefId", self)) + .unwrap_or_else(|| panic!("ItemId::expect_def_id: `{self:?}` isn't a DefId")) } #[inline] @@ -353,21 +352,21 @@ fn is_field_vis_inherited(tcx: TyCtxt<'_>, def_id: DefId) -> bool { match tcx.def_kind(parent) { DefKind::Struct | DefKind::Union => false, DefKind::Variant => true, - parent_kind => panic!("unexpected parent kind: {:?}", parent_kind), + parent_kind => panic!("unexpected parent kind: {parent_kind:?}"), } } impl Item { pub(crate) fn stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option { - self.item_id.as_def_id().and_then(|did| tcx.lookup_stability(did)) + self.def_id().and_then(|did| tcx.lookup_stability(did)) } pub(crate) fn const_stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option { - self.item_id.as_def_id().and_then(|did| tcx.lookup_const_stability(did)) + self.def_id().and_then(|did| tcx.lookup_const_stability(did)) } pub(crate) fn deprecation(&self, tcx: TyCtxt<'_>) -> Option { - self.item_id.as_def_id().and_then(|did| tcx.lookup_deprecation(did)) + self.def_id().and_then(|did| tcx.lookup_deprecation(did)) } pub(crate) fn inner_docs(&self, tcx: TyCtxt<'_>) -> bool { @@ -392,7 +391,7 @@ impl Item { panic!("blanket impl item has non-blanket ID") } } - _ => self.item_id.as_def_id().map(|did| rustc_span(did, tcx)), + _ => self.def_id().map(|did| rustc_span(did, tcx)), } } @@ -401,12 +400,18 @@ impl Item { .unwrap_or_else(|| self.span(tcx).map_or(rustc_span::DUMMY_SP, |span| span.inner())) } - /// Finds the `doc` attribute as a NameValue and returns the corresponding - /// value found. - pub(crate) fn doc_value(&self) -> Option { + /// Combine all doc strings into a single value handling indentation and newlines as needed. + pub(crate) fn doc_value(&self) -> String { self.attrs.doc_value() } + /// Combine all doc strings into a single value handling indentation and newlines as needed. + /// Returns `None` is there's no documentation at all, and `Some("")` if there is some + /// documentation but it is empty (e.g. `#[doc = ""]`). + pub(crate) fn opt_doc_value(&self) -> Option { + self.attrs.opt_doc_value() + } + pub(crate) fn from_def_id_and_parts( def_id: DefId, name: Option, @@ -431,7 +436,7 @@ impl Item { attrs: Box, cfg: Option>, ) -> Item { - trace!("name={:?}, def_id={:?} cfg={:?}", name, def_id, cfg); + trace!("name={name:?}, def_id={def_id:?} cfg={cfg:?}"); Item { item_id: def_id.into(), @@ -443,20 +448,10 @@ impl Item { } } - /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined - /// with newlines. - pub(crate) fn collapsed_doc_value(&self) -> Option { - self.attrs.collapsed_doc_value() - } - pub(crate) fn links(&self, cx: &Context<'_>) -> Vec { use crate::html::format::{href, link_tooltip}; - let Some(links) = cx.cache() - .intra_doc_links - .get(&self.item_id) else { - return vec![] - }; + let Some(links) = cx.cache().intra_doc_links.get(&self.item_id) else { return vec![] }; links .iter() .filter_map(|ItemLink { link: s, link_text, page_id: id, ref fragment }| { @@ -485,11 +480,9 @@ impl Item { /// the link text, but does need to know which `[]`-bracketed names /// are actually links. pub(crate) fn link_names(&self, cache: &Cache) -> Vec { - let Some(links) = cache - .intra_doc_links - .get(&self.item_id) else { - return vec![]; - }; + let Some(links) = cache.intra_doc_links.get(&self.item_id) else { + return vec![]; + }; links .iter() .map(|ItemLink { link: s, link_text, .. }| RenderedLink { @@ -502,7 +495,7 @@ impl Item { } pub(crate) fn is_crate(&self) -> bool { - self.is_mod() && self.item_id.as_def_id().map_or(false, |did| did.is_crate_root()) + self.is_mod() && self.def_id().map_or(false, |did| did.is_crate_root()) } pub(crate) fn is_mod(&self) -> bool { self.type_() == ItemType::Module @@ -639,11 +632,11 @@ impl Item { } let header = match *self.kind { ItemKind::ForeignFunctionItem(_) => { - let def_id = self.item_id.as_def_id().unwrap(); + let def_id = self.def_id().unwrap(); let abi = tcx.fn_sig(def_id).skip_binder().abi(); hir::FnHeader { unsafety: if abi == Abi::RustIntrinsic { - intrinsic_operation_unsafety(tcx, self.item_id.as_def_id().unwrap()) + intrinsic_operation_unsafety(tcx, self.def_id().unwrap()) } else { hir::Unsafety::Unsafe }, @@ -660,7 +653,7 @@ impl Item { } } ItemKind::FunctionItem(_) | ItemKind::MethodItem(_, _) | ItemKind::TyMethodItem(_) => { - let def_id = self.item_id.as_def_id().unwrap(); + let def_id = self.def_id().unwrap(); build_fn_header(def_id, tcx, tcx.asyncness(def_id)) } _ => return None, @@ -739,7 +732,7 @@ impl Item { } }) .collect(); - if let Some(def_id) = self.item_id.as_def_id() && + if let Some(def_id) = self.def_id() && !def_id.is_local() && // This check is needed because `adt_def` will panic if not a compatible type otherwise... matches!(self.type_(), ItemType::Struct | ItemType::Enum | ItemType::Union) @@ -784,6 +777,14 @@ impl Item { } attrs } + + pub fn is_doc_hidden(&self) -> bool { + self.attrs.is_doc_hidden() + } + + pub fn def_id(&self) -> Option { + self.item_id.as_def_id() + } } #[derive(Clone, Debug)] @@ -823,9 +824,9 @@ pub(crate) enum ItemKind { ProcMacroItem(ProcMacro), PrimitiveItem(PrimitiveType), /// A required associated constant in a trait declaration. - TyAssocConstItem(Type), + TyAssocConstItem(Box, Type), /// An associated constant in a trait impl or a provided one in a trait declaration. - AssocConstItem(Type, ConstantKind), + AssocConstItem(Box, Type, ConstantKind), /// A required associated type in a trait declaration. /// /// The bounds may be non-empty if there is a `where` clause. @@ -870,8 +871,8 @@ impl ItemKind { | MacroItem(_) | ProcMacroItem(_) | PrimitiveItem(_) - | TyAssocConstItem(_) - | AssocConstItem(_, _) + | TyAssocConstItem(..) + | AssocConstItem(..) | TyAssocTypeItem(..) | AssocTypeItem(..) | StrippedItem(_) @@ -949,6 +950,8 @@ pub(crate) trait AttributesExt { .filter_map(|attr| Cfg::parse(attr.meta_item()?).ok()) .fold(Cfg::True, |cfg, new_cfg| cfg & new_cfg) } else if doc_auto_cfg_active { + // If there is no `doc(cfg())`, then we retrieve the `cfg()` attributes (because + // `doc(cfg())` overrides `cfg()`). self.iter() .filter(|attr| attr.has_name(sym::cfg)) .filter_map(|attr| single(attr.meta_item_list()?)) @@ -1068,17 +1071,6 @@ impl> NestedAttributesExt for I { } } -/// Collapse a collection of [`DocFragment`]s into one string, -/// handling indentation and newlines as needed. -pub(crate) fn collapse_doc_fragments(doc_strings: &[DocFragment]) -> String { - let mut acc = String::new(); - for frag in doc_strings { - add_doc_fragment(&mut acc, frag); - } - acc.pop(); - acc -} - /// A link that has not yet been rendered. /// /// This link will be turned into a rendered link by [`Item::links`]. @@ -1141,6 +1133,10 @@ impl Attributes { false } + fn is_doc_hidden(&self) -> bool { + self.has_doc_flag(sym::hidden) + } + pub(crate) fn from_ast(attrs: &[ast::Attribute]) -> Attributes { Attributes::from_ast_iter(attrs.iter().map(|attr| (attr, None)), false) } @@ -1163,29 +1159,23 @@ impl Attributes { Attributes { doc_strings, other_attrs } } - /// Finds the `doc` attribute as a NameValue and returns the corresponding - /// value found. - pub(crate) fn doc_value(&self) -> Option { - let mut iter = self.doc_strings.iter(); - - let ori = iter.next()?; - let mut out = String::new(); - add_doc_fragment(&mut out, ori); - for new_frag in iter { - add_doc_fragment(&mut out, new_frag); - } - out.pop(); - if out.is_empty() { None } else { Some(out) } + /// Combine all doc strings into a single value handling indentation and newlines as needed. + pub(crate) fn doc_value(&self) -> String { + self.opt_doc_value().unwrap_or_default() } - /// Finds all `doc` attributes as NameValues and returns their corresponding values, joined - /// with newlines. - pub(crate) fn collapsed_doc_value(&self) -> Option { - if self.doc_strings.is_empty() { - None - } else { - Some(collapse_doc_fragments(&self.doc_strings)) - } + /// Combine all doc strings into a single value handling indentation and newlines as needed. + /// Returns `None` is there's no documentation at all, and `Some("")` if there is some + /// documentation but it is empty (e.g. `#[doc = ""]`). + pub(crate) fn opt_doc_value(&self) -> Option { + (!self.doc_strings.is_empty()).then(|| { + let mut res = String::new(); + for frag in &self.doc_strings { + add_doc_fragment(&mut res, frag); + } + res.pop(); + res + }) } pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> { @@ -1229,15 +1219,24 @@ pub(crate) enum GenericBound { } impl GenericBound { + pub(crate) fn sized(cx: &mut DocContext<'_>) -> GenericBound { + Self::sized_with(cx, hir::TraitBoundModifier::None) + } + pub(crate) fn maybe_sized(cx: &mut DocContext<'_>) -> GenericBound { + Self::sized_with(cx, hir::TraitBoundModifier::Maybe) + } + + fn sized_with(cx: &mut DocContext<'_>, modifier: hir::TraitBoundModifier) -> GenericBound { let did = cx.tcx.require_lang_item(LangItem::Sized, None); - let empty = ty::Binder::dummy(ty::InternalSubsts::empty()); + let empty = ty::Binder::dummy(ty::GenericArgs::empty()); let path = external_path(cx, did, false, ThinVec::new(), empty); inline::record_extern_fqn(cx, did, ItemType::Trait); - GenericBound::TraitBound( - PolyTrait { trait_: path, generic_params: Vec::new() }, - hir::TraitBoundModifier::Maybe, - ) + GenericBound::TraitBound(PolyTrait { trait_: path, generic_params: Vec::new() }, modifier) + } + + pub(crate) fn is_trait_bound(&self) -> bool { + matches!(self, Self::TraitBound(..)) } pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool { @@ -1279,7 +1278,7 @@ impl Lifetime { } } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] pub(crate) enum WherePredicate { BoundPredicate { ty: Type, bounds: Vec, bound_params: Vec }, RegionPredicate { lifetime: Lifetime, bounds: Vec }, @@ -1349,7 +1348,7 @@ impl GenericParamDef { } // maybe use a Generic enum and use Vec? -#[derive(Clone, Debug, Default)] +#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)] pub(crate) struct Generics { pub(crate) params: ThinVec, pub(crate) where_predicates: ThinVec, @@ -1370,7 +1369,7 @@ pub(crate) struct Function { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) struct FnDecl { pub(crate) inputs: Arguments, - pub(crate) output: FnRetTy, + pub(crate) output: Type, pub(crate) c_variadic: bool, } @@ -1388,18 +1387,16 @@ impl FnDecl { /// /// This function will panic if the return type does not match the expected sugaring for async /// functions. - pub(crate) fn sugared_async_return_type(&self) -> FnRetTy { - match &self.output { - FnRetTy::Return(Type::ImplTrait(bounds)) => match &bounds[0] { - GenericBound::TraitBound(PolyTrait { trait_, .. }, ..) => { - let bindings = trait_.bindings().unwrap(); - let ret_ty = bindings[0].term(); - let ty = ret_ty.ty().expect("Unexpected constant return term"); - FnRetTy::Return(ty.clone()) - } - _ => panic!("unexpected desugaring of async function"), - }, - _ => panic!("unexpected desugaring of async function"), + pub(crate) fn sugared_async_return_type(&self) -> Type { + if let Type::ImplTrait(v) = &self.output && + let [GenericBound::TraitBound(PolyTrait { trait_, .. }, _ )] = &v[..] + { + let bindings = trait_.bindings().unwrap(); + let ret_ty = bindings[0].term(); + let ty = ret_ty.ty().expect("Unexpected constant return term"); + ty.clone() + } else { + panic!("unexpected desugaring of async function") } } } @@ -1442,21 +1439,6 @@ impl Argument { } } -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub(crate) enum FnRetTy { - Return(Type), - DefaultReturn, -} - -impl FnRetTy { - pub(crate) fn as_return(&self) -> Option<&Type> { - match self { - Return(ret) => Some(ret), - DefaultReturn => None, - } - } -} - #[derive(Clone, Debug)] pub(crate) struct Trait { pub(crate) def_id: DefId, @@ -1658,6 +1640,10 @@ impl Type { matches!(self, Type::ImplTrait(_)) } + pub(crate) fn is_unit(&self) -> bool { + matches!(self, Type::Tuple(v) if v.is_empty()) + } + pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> { if let QPath(box QPathData { self_type, trait_, assoc, .. }) = self { Some((self_type, trait_.as_ref()?.def_id(), assoc.clone())) @@ -1799,7 +1785,6 @@ impl PrimitiveType { } pub(crate) fn simplified_types() -> &'static SimplifiedTypes { - use ty::fast_reject::SimplifiedType::*; use ty::{FloatTy, IntTy, UintTy}; use PrimitiveType::*; static CELL: OnceCell = OnceCell::new(); @@ -1807,38 +1792,38 @@ impl PrimitiveType { let single = |x| iter::once(x).collect(); CELL.get_or_init(move || { map! { - Isize => single(IntSimplifiedType(IntTy::Isize)), - I8 => single(IntSimplifiedType(IntTy::I8)), - I16 => single(IntSimplifiedType(IntTy::I16)), - I32 => single(IntSimplifiedType(IntTy::I32)), - I64 => single(IntSimplifiedType(IntTy::I64)), - I128 => single(IntSimplifiedType(IntTy::I128)), - Usize => single(UintSimplifiedType(UintTy::Usize)), - U8 => single(UintSimplifiedType(UintTy::U8)), - U16 => single(UintSimplifiedType(UintTy::U16)), - U32 => single(UintSimplifiedType(UintTy::U32)), - U64 => single(UintSimplifiedType(UintTy::U64)), - U128 => single(UintSimplifiedType(UintTy::U128)), - F32 => single(FloatSimplifiedType(FloatTy::F32)), - F64 => single(FloatSimplifiedType(FloatTy::F64)), - Str => single(StrSimplifiedType), - Bool => single(BoolSimplifiedType), - Char => single(CharSimplifiedType), - Array => single(ArraySimplifiedType), - Slice => single(SliceSimplifiedType), + Isize => single(SimplifiedType::Int(IntTy::Isize)), + I8 => single(SimplifiedType::Int(IntTy::I8)), + I16 => single(SimplifiedType::Int(IntTy::I16)), + I32 => single(SimplifiedType::Int(IntTy::I32)), + I64 => single(SimplifiedType::Int(IntTy::I64)), + I128 => single(SimplifiedType::Int(IntTy::I128)), + Usize => single(SimplifiedType::Uint(UintTy::Usize)), + U8 => single(SimplifiedType::Uint(UintTy::U8)), + U16 => single(SimplifiedType::Uint(UintTy::U16)), + U32 => single(SimplifiedType::Uint(UintTy::U32)), + U64 => single(SimplifiedType::Uint(UintTy::U64)), + U128 => single(SimplifiedType::Uint(UintTy::U128)), + F32 => single(SimplifiedType::Float(FloatTy::F32)), + F64 => single(SimplifiedType::Float(FloatTy::F64)), + Str => single(SimplifiedType::Str), + Bool => single(SimplifiedType::Bool), + Char => single(SimplifiedType::Char), + Array => single(SimplifiedType::Array), + Slice => single(SimplifiedType::Slice), // FIXME: If we ever add an inherent impl for tuples // with different lengths, they won't show in rustdoc. // // Either manually update this arrayvec at this point // or start with a more complex refactoring. - Tuple => [TupleSimplifiedType(1), TupleSimplifiedType(2), TupleSimplifiedType(3)].into(), - Unit => single(TupleSimplifiedType(0)), - RawPointer => [PtrSimplifiedType(Mutability::Not), PtrSimplifiedType(Mutability::Mut)].into_iter().collect(), - Reference => [RefSimplifiedType(Mutability::Not), RefSimplifiedType(Mutability::Mut)].into_iter().collect(), + Tuple => [SimplifiedType::Tuple(1), SimplifiedType::Tuple(2), SimplifiedType::Tuple(3)].into(), + Unit => single(SimplifiedType::Tuple(0)), + RawPointer => [SimplifiedType::Ptr(Mutability::Not), SimplifiedType::Ptr(Mutability::Mut)].into_iter().collect(), + Reference => [SimplifiedType::Ref(Mutability::Not), SimplifiedType::Ref(Mutability::Mut)].into_iter().collect(), // FIXME: This will be wrong if we ever add inherent impls // for function pointers. - Fn => single(FunctionSimplifiedType(1)), - Never => single(NeverSimplifiedType), + Fn => single(SimplifiedType::Function(1)), + Never => single(SimplifiedType::Never), } }) } @@ -2227,6 +2212,17 @@ pub(crate) enum GenericArgs { Parenthesized { inputs: Box<[Type]>, output: Option> }, } +impl GenericArgs { + pub(crate) fn is_empty(&self) -> bool { + match self { + GenericArgs::AngleBracketed { args, bindings } => { + args.is_empty() && bindings.is_empty() + } + GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(), + } + } +} + #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) struct PathSegment { pub(crate) name: Symbol, @@ -2270,6 +2266,7 @@ pub(crate) struct Static { #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub(crate) struct Constant { pub(crate) type_: Type, + pub(crate) generics: Box, pub(crate) kind: ConstantKind, } @@ -2406,6 +2403,7 @@ impl ImplKind { #[derive(Clone, Debug)] pub(crate) struct Import { pub(crate) kind: ImportKind, + /// The item being re-exported. pub(crate) source: ImportSource, pub(crate) should_be_displayed: bool, } @@ -2518,7 +2516,8 @@ mod size_asserts { static_assert_size!(GenericParamDef, 56); static_assert_size!(Generics, 16); static_assert_size!(Item, 56); - static_assert_size!(ItemKind, 64); + // FIXME(generic_const_items): Further reduce the size. + static_assert_size!(ItemKind, 72); static_assert_size!(PathSegment, 40); static_assert_size!(Type, 32); // tidy-alphabetical-end diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index d8c91a9680479..4907a55270b8e 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -1,7 +1,5 @@ use super::*; -use crate::clean::collapse_doc_fragments; - use rustc_resolve::rustdoc::{unindent_doc_fragments, DocFragment, DocFragmentKind}; use rustc_span::create_default_session_globals_then; use rustc_span::source_map::DUMMY_SP; @@ -22,7 +20,8 @@ fn run_test(input: &str, expected: &str) { create_default_session_globals_then(|| { let mut s = create_doc_fragment(input); unindent_doc_fragments(&mut s); - assert_eq!(collapse_doc_fragments(&s), expected); + let attrs = Attributes { doc_strings: s, other_attrs: Default::default() }; + assert_eq!(attrs.doc_value(), expected); }); } @@ -74,7 +73,7 @@ fn should_not_trim() { fn is_same_generic() { use crate::clean::types::{PrimitiveType, Type}; use crate::formats::cache::Cache; - let cache = Cache::new(false); + let cache = Cache::new(false, false); let generic = Type::Generic(rustc_span::symbol::sym::Any); let unit = Type::Primitive(PrimitiveType::Unit); assert!(!generic.is_doc_subtype_of(&unit, &cache)); diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 17aa6b38e389c..a86bbcc7679b3 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -13,14 +13,14 @@ use rustc_ast as ast; use rustc_ast::tokenstream::TokenTree; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, LocalDefId, LOCAL_CRATE}; use rustc_middle::mir; use rustc_middle::mir::interpret::ConstValue; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, TyCtxt}; use rustc_span::symbol::{kw, sym, Symbol}; use std::fmt::Write as _; use std::mem; +use std::sync::LazyLock as Lazy; use thin_vec::{thin_vec, ThinVec}; #[cfg(test)] @@ -38,11 +38,15 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { for it in &module.items { // `compiler_builtins` should be masked too, but we can't apply // `#[doc(masked)]` to the injected `extern crate` because it's unstable. - if it.is_extern_crate() - && (it.attrs.has_doc_flag(sym::masked) - || cx.tcx.is_compiler_builtins(it.item_id.krate())) - { + if cx.tcx.is_compiler_builtins(it.item_id.krate()) { cx.cache.masked_crates.insert(it.item_id.krate()); + } else if it.is_extern_crate() + && it.attrs.has_doc_flag(sym::masked) + && let Some(def_id) = it.item_id.as_def_id() + && let Some(local_def_id) = def_id.as_local() + && let Some(cnum) = cx.tcx.extern_mod_stmt_cnum(local_def_id) + { + cx.cache.masked_crates.insert(cnum); } } } @@ -53,8 +57,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { let primitives = local_crate.primitives(cx.tcx); let keywords = local_crate.keywords(cx.tcx); { - let ItemKind::ModuleItem(ref mut m) = *module.kind - else { unreachable!() }; + let ItemKind::ModuleItem(ref mut m) = *module.kind else { unreachable!() }; m.items.extend(primitives.iter().map(|&(def_id, prim)| { Item::from_def_id_and_parts( def_id, @@ -71,30 +74,39 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate { Crate { module, external_traits: cx.external_traits.clone() } } -pub(crate) fn substs_to_args<'tcx>( +pub(crate) fn ty_args_to_args<'tcx>( cx: &mut DocContext<'tcx>, - substs: ty::Binder<'tcx, &[ty::subst::GenericArg<'tcx>]>, - mut skip_first: bool, + args: ty::Binder<'tcx, &'tcx [ty::GenericArg<'tcx>]>, + has_self: bool, + container: Option, ) -> Vec { + let mut skip_first = has_self; let mut ret_val = - Vec::with_capacity(substs.skip_binder().len().saturating_sub(if skip_first { - 1 - } else { - 0 - })); - ret_val.extend(substs.iter().filter_map(|kind| match kind.skip_binder().unpack() { - GenericArgKind::Lifetime(lt) => { - Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided()))) - } - GenericArgKind::Type(_) if skip_first => { - skip_first = false; - None - } - GenericArgKind::Type(ty) => { - Some(GenericArg::Type(clean_middle_ty(kind.rebind(ty), cx, None))) - } - GenericArgKind::Const(ct) => { - Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx)))) + Vec::with_capacity(args.skip_binder().len().saturating_sub(if skip_first { 1 } else { 0 })); + + ret_val.extend(args.iter().enumerate().filter_map(|(index, kind)| { + match kind.skip_binder().unpack() { + GenericArgKind::Lifetime(lt) => { + Some(GenericArg::Lifetime(clean_middle_region(lt).unwrap_or(Lifetime::elided()))) + } + GenericArgKind::Type(_) if skip_first => { + skip_first = false; + None + } + GenericArgKind::Type(ty) => Some(GenericArg::Type(clean_middle_ty( + kind.rebind(ty), + cx, + None, + container.map(|container| crate::clean::ContainerTy::Regular { + ty: container, + args, + has_self, + arg: index, + }), + ))), + GenericArgKind::Const(ct) => { + Some(GenericArg::Const(Box::new(clean_middle_const(kind.rebind(ct), cx)))) + } } })); ret_val @@ -105,12 +117,12 @@ fn external_generic_args<'tcx>( did: DefId, has_self: bool, bindings: ThinVec, - substs: ty::Binder<'tcx, SubstsRef<'tcx>>, + ty_args: ty::Binder<'tcx, GenericArgsRef<'tcx>>, ) -> GenericArgs { - let args = substs_to_args(cx, substs.map_bound(|substs| &substs[..]), has_self); + let args = ty_args_to_args(cx, ty_args.map_bound(|args| &args[..]), has_self, Some(did)); if cx.tcx.fn_trait_kind_from_def_id(did).is_some() { - let ty = substs + let ty = ty_args .iter() .nth(if has_self { 1 } else { 0 }) .unwrap() @@ -118,7 +130,7 @@ fn external_generic_args<'tcx>( let inputs = // The trait's first substitution is the one after self, if there is one. match ty.skip_binder().kind() { - ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(ty.rebind(t), cx, None)).collect::>().into(), + ty::Tuple(tys) => tys.iter().map(|t| clean_middle_ty(ty.rebind(t), cx, None, None)).collect::>().into(), _ => return GenericArgs::AngleBracketed { args: args.into(), bindings }, }; let output = bindings.into_iter().next().and_then(|binding| match binding.kind { @@ -138,7 +150,7 @@ pub(super) fn external_path<'tcx>( did: DefId, has_self: bool, bindings: ThinVec, - substs: ty::Binder<'tcx, SubstsRef<'tcx>>, + args: ty::Binder<'tcx, GenericArgsRef<'tcx>>, ) -> Path { let def_kind = cx.tcx.def_kind(did); let name = cx.tcx.item_name(did); @@ -146,7 +158,7 @@ pub(super) fn external_path<'tcx>( res: Res::Def(def_kind, did), segments: thin_vec![PathSegment { name, - args: external_generic_args(cx, did, has_self, bindings, substs), + args: external_generic_args(cx, did, has_self, bindings, args), }], } } @@ -193,7 +205,7 @@ pub(crate) fn build_deref_target_impls( }; if let Some(prim) = target.primitive_type() { - let _prof_timer = cx.tcx.sess.prof.generic_activity("build_primitive_inherent_impls"); + let _prof_timer = tcx.sess.prof.generic_activity("build_primitive_inherent_impls"); for did in prim.impls(tcx).filter(|did| !did.is_local()) { inline::build_impl(cx, did, None, ret); } @@ -208,7 +220,7 @@ pub(crate) fn build_deref_target_impls( pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { use rustc_hir::*; - debug!("trying to get a name from pattern: {:?}", p); + debug!("trying to get a name from pattern: {p:?}"); Symbol::intern(&match p.kind { PatKind::Wild | PatKind::Struct(..) => return kw::Underscore, @@ -241,7 +253,7 @@ pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { pub(crate) fn print_const(cx: &DocContext<'_>, n: ty::Const<'_>) -> String { match n.kind() { - ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, substs: _ }) => { + ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, args: _ }) => { let s = if let Some(def) = def.as_local() { print_const_expr(cx.tcx, cx.tcx.hir().body_owned_by(def)) } else { @@ -266,7 +278,7 @@ pub(crate) fn print_evaluated_const( underscores_and_type: bool, ) -> Option { tcx.const_eval_poly(def_id).ok().and_then(|val| { - let ty = tcx.type_of(def_id).subst_identity(); + let ty = tcx.type_of(def_id).instantiate_identity(); match (val, ty.kind()) { (_, &ty::Ref(..)) => None, (ConstValue::Scalar(_), &ty::Adt(_, _)) => None, @@ -451,7 +463,7 @@ pub(crate) fn print_const_expr(tcx: TyCtxt<'_>, body: hir::BodyId) -> String { /// Given a type Path, resolve it to a Type using the TyCtxt pub(crate) fn resolve_type(cx: &mut DocContext<'_>, path: Path) -> Type { - debug!("resolve_type({:?})", path); + debug!("resolve_type({path:?})"); match path.res { Res::PrimTy(p) => Primitive(PrimitiveType::from(p)), @@ -470,12 +482,6 @@ pub(crate) fn get_auto_trait_and_blanket_impls( cx: &mut DocContext<'_>, item_def_id: DefId, ) -> impl Iterator { - // FIXME: To be removed once `parallel_compiler` bugs are fixed! - // More information in . - if cfg!(parallel_compiler) { - return vec![].into_iter().chain(vec![].into_iter()); - } - let auto_impls = cx .sess() .prof @@ -496,16 +502,30 @@ pub(crate) fn get_auto_trait_and_blanket_impls( /// [`href()`]: crate::html::format::href pub(crate) fn register_res(cx: &mut DocContext<'_>, res: Res) -> DefId { use DefKind::*; - debug!("register_res({:?})", res); + debug!("register_res({res:?})"); let (kind, did) = match res { Res::Def( - kind @ (AssocTy | AssocFn | AssocConst | Variant | Fn | TyAlias | Enum | Trait | Struct - | Union | Mod | ForeignTy | Const | Static(_) | Macro(..) | TraitAlias), + kind @ (AssocTy + | AssocFn + | AssocConst + | Variant + | Fn + | TyAlias { .. } + | Enum + | Trait + | Struct + | Union + | Mod + | ForeignTy + | Const + | Static(_) + | Macro(..) + | TraitAlias), did, ) => (kind.into(), did), - _ => panic!("register_res: unexpected {:?}", res), + _ => panic!("register_res: unexpected {res:?}"), }; if did.is_local() { return did; @@ -571,6 +591,8 @@ pub(crate) fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool { /// /// Set by `bootstrap::Builder::doc_rust_lang_org_channel` in order to keep tests passing on beta/stable. pub(crate) const DOC_RUST_LANG_ORG_CHANNEL: &str = env!("DOC_RUST_LANG_ORG_CHANNEL"); +pub(crate) static DOC_CHANNEL: Lazy<&'static str> = + Lazy::new(|| DOC_RUST_LANG_ORG_CHANNEL.rsplit("/").filter(|c| !c.is_empty()).next().unwrap()); /// Render a sequence of macro arms in a format suitable for displaying to the user /// as part of an item declaration. @@ -581,8 +603,12 @@ pub(super) fn render_macro_arms<'a>( ) -> String { let mut out = String::new(); for matcher in matchers { - writeln!(out, " {} => {{ ... }}{}", render_macro_matcher(tcx, matcher), arm_delim) - .unwrap(); + writeln!( + out, + " {matcher} => {{ ... }}{arm_delim}", + matcher = render_macro_matcher(tcx, matcher), + ) + .unwrap(); } out } @@ -598,22 +624,54 @@ pub(super) fn display_macro_source( let matchers = def.body.tokens.chunks(4).map(|arm| &arm[0]); if def.macro_rules { - format!("macro_rules! {} {{\n{}}}", name, render_macro_arms(cx.tcx, matchers, ";")) + format!("macro_rules! {name} {{\n{arms}}}", arms = render_macro_arms(cx.tcx, matchers, ";")) } else { if matchers.len() <= 1 { format!( - "{}macro {}{} {{\n ...\n}}", - visibility_to_src_with_space(Some(vis), cx.tcx, def_id), - name, - matchers.map(|matcher| render_macro_matcher(cx.tcx, matcher)).collect::(), + "{vis}macro {name}{matchers} {{\n ...\n}}", + vis = visibility_to_src_with_space(Some(vis), cx.tcx, def_id), + matchers = matchers + .map(|matcher| render_macro_matcher(cx.tcx, matcher)) + .collect::(), ) } else { format!( - "{}macro {} {{\n{}}}", - visibility_to_src_with_space(Some(vis), cx.tcx, def_id), - name, - render_macro_arms(cx.tcx, matchers, ","), + "{vis}macro {name} {{\n{arms}}}", + vis = visibility_to_src_with_space(Some(vis), cx.tcx, def_id), + arms = render_macro_arms(cx.tcx, matchers, ","), + ) + } + } +} + +pub(crate) fn inherits_doc_hidden( + tcx: TyCtxt<'_>, + mut def_id: LocalDefId, + stop_at: Option, +) -> bool { + let hir = tcx.hir(); + while let Some(id) = tcx.opt_local_parent(def_id) { + if let Some(stop_at) = stop_at && id == stop_at { + return false; + } + def_id = id; + if tcx.is_doc_hidden(def_id.to_def_id()) { + return true; + } else if let Some(node) = hir.find_by_def_id(def_id) && + matches!( + node, + hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(_), .. }), ) + { + // `impl` blocks stand a bit on their own: unless they have `#[doc(hidden)]` directly + // on them, they don't inherit it from the parent context. + return false; } } + false +} + +#[inline] +pub(crate) fn should_ignore_res(res: Res) -> bool { + matches!(res, Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..)) } diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index dfa4b091b0164..81fb13f4166bc 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -15,6 +15,7 @@ use rustc_session::config::{ use rustc_session::getopts; use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; +use rustc_session::EarlyErrorHandler; use rustc_span::edition::Edition; use rustc_target::spec::TargetTriple; @@ -49,7 +50,7 @@ impl TryFrom<&str> for OutputFormat { match value { "json" => Ok(OutputFormat::Json), "html" => Ok(OutputFormat::Html), - _ => Err(format!("unknown output format `{}`", value)), + _ => Err(format!("unknown output format `{value}`")), } } } @@ -311,33 +312,33 @@ impl Options { /// Parses the given command-line for options. If an error message or other early-return has /// been printed, returns `Err` with the exit code. pub(crate) fn from_matches( + handler: &mut EarlyErrorHandler, matches: &getopts::Matches, args: Vec, ) -> Result<(Options, RenderOptions), i32> { - let args = &args[1..]; // Check for unstable options. - nightly_options::check_nightly_options(matches, &opts()); + nightly_options::check_nightly_options(handler, matches, &opts()); if args.is_empty() || matches.opt_present("h") || matches.opt_present("help") { crate::usage("rustdoc"); return Err(0); } else if matches.opt_present("version") { - rustc_driver::version!("rustdoc", matches); + rustc_driver::version!(&handler, "rustdoc", matches); return Err(0); } - if rustc_driver::describe_flag_categories(&matches) { + if rustc_driver::describe_flag_categories(handler, &matches) { return Err(0); } - let color = config::parse_color(matches); + let color = config::parse_color(handler, matches); let config::JsonConfig { json_rendered, json_unused_externs, .. } = - config::parse_json(matches); - let error_format = config::parse_error_format(matches, color, json_rendered); + config::parse_json(handler, matches); + let error_format = config::parse_error_format(handler, matches, color, json_rendered); let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default(); - let codegen_options = CodegenOptions::build(matches, error_format); - let unstable_opts = UnstableOptions::build(matches, error_format); + let codegen_options = CodegenOptions::build(handler, matches); + let unstable_opts = UnstableOptions::build(handler, matches); let diag = new_handler(error_format, None, diagnostic_width, &unstable_opts); @@ -382,7 +383,7 @@ impl Options { match kind.parse() { Ok(kind) => emit.push(kind), Err(()) => { - diag.err(format!("unrecognized emission type: {}", kind)); + diag.err(format!("unrecognized emission type: {kind}")); return Err(1); } } @@ -394,8 +395,7 @@ impl Options { && !matches.opt_present("show-coverage") && !nightly_options::is_unstable_enabled(matches) { - rustc_session::early_error( - error_format, + handler.early_error( "the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)", ); } @@ -415,7 +415,7 @@ impl Options { println!("rustdoc: [check-theme] Starting tests! (Ignoring all other arguments)"); for theme_file in to_check.iter() { - print!(" - Checking \"{}\"...", theme_file); + print!(" - Checking \"{theme_file}\"..."); let (success, differences) = theme::test_theme_against(theme_file, &paths, &diag); if !differences.is_empty() || !success { println!(" FAILED"); @@ -433,7 +433,7 @@ impl Options { return Err(0); } - let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format); + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(handler, matches); let input = PathBuf::from(if describe_lints { "" // dummy, this won't be used @@ -447,12 +447,9 @@ impl Options { &matches.free[0] }); - let libs = matches - .opt_strs("L") - .iter() - .map(|s| SearchPath::from_cli_opt(s, error_format)) - .collect(); - let externs = parse_externs(matches, &unstable_opts, error_format); + let libs = + matches.opt_strs("L").iter().map(|s| SearchPath::from_cli_opt(handler, s)).collect(); + let externs = parse_externs(handler, matches, &unstable_opts); let extern_html_root_urls = match parse_extern_html_roots(matches) { Ok(ex) => ex, Err(err) => { @@ -559,30 +556,28 @@ impl Options { matches.opt_strs("theme").iter().map(|s| (PathBuf::from(&s), s.to_owned())) { if !theme_file.is_file() { - diag.struct_err(format!("invalid argument: \"{}\"", theme_s)) + diag.struct_err(format!("invalid argument: \"{theme_s}\"")) .help("arguments to --theme must be files") .emit(); return Err(1); } if theme_file.extension() != Some(OsStr::new("css")) { - diag.struct_err(format!("invalid argument: \"{}\"", theme_s)) + diag.struct_err(format!("invalid argument: \"{theme_s}\"")) .help("arguments to --theme must have a .css extension") .emit(); return Err(1); } let (success, ret) = theme::test_theme_against(&theme_file, &paths, &diag); if !success { - diag.struct_err(format!("error loading theme file: \"{}\"", theme_s)).emit(); + diag.struct_err(format!("error loading theme file: \"{theme_s}\"")).emit(); return Err(1); } else if !ret.is_empty() { diag.struct_warn(format!( - "theme file \"{}\" is missing CSS rules from the default theme", - theme_s + "theme file \"{theme_s}\" is missing CSS rules from the default theme", )) .warn("the theme may appear incorrect when loaded") .help(format!( - "to see what rules are missing, call `rustdoc --check-theme \"{}\"`", - theme_s + "to see what rules are missing, call `rustdoc --check-theme \"{theme_s}\"`", )) .emit(); } @@ -590,7 +585,7 @@ impl Options { } } - let edition = config::parse_crate_edition(matches); + let edition = config::parse_crate_edition(handler, matches); let mut id_map = html::markdown::IdMap::new(); let Some(external_html) = ExternalHtml::load( @@ -611,7 +606,7 @@ impl Options { match matches.opt_str("r").as_deref() { Some("rust") | None => {} Some(s) => { - diag.struct_err(format!("unknown input format: {}", s)).emit(); + diag.struct_err(format!("unknown input format: {s}")).emit(); return Err(1); } } @@ -624,14 +619,14 @@ impl Options { } } - let target = parse_target_triple(matches, error_format); + let target = parse_target_triple(handler, matches); let show_coverage = matches.opt_present("show-coverage"); let crate_types = match parse_crate_types_from_list(matches.opt_strs("crate-type")) { Ok(types) => types, Err(e) => { - diag.struct_err(format!("unknown crate type: {}", e)).emit(); + diag.struct_err(format!("unknown crate type: {e}")).emit(); return Err(1); } }; @@ -790,7 +785,7 @@ fn check_deprecated_options(matches: &getopts::Matches, diag: &rustc_errors::Han for &flag in deprecated_flags.iter() { if matches.opt_present(flag) { - diag.struct_warn(format!("the `{}` flag is deprecated", flag)) + diag.struct_warn(format!("the `{flag}` flag is deprecated")) .note( "see issue #44136 \ for more information", @@ -803,7 +798,7 @@ fn check_deprecated_options(matches: &getopts::Matches, diag: &rustc_errors::Han for &flag in removed_flags.iter() { if matches.opt_present(flag) { - let mut err = diag.struct_warn(format!("the `{}` flag no longer functions", flag)); + let mut err = diag.struct_warn(format!("the `{flag}` flag no longer functions")); err.note( "see issue #44136 \ for more information", diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index a6be132337eb6..4c8dab61f0cbf 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -10,11 +10,12 @@ use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{HirId, Path}; use rustc_interface::interface; +use rustc_lint::{late_lint_mod, MissingDoc}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks}; -use rustc_session::lint; use rustc_session::Session; +use rustc_session::{lint, EarlyErrorHandler}; use rustc_span::symbol::sym; use rustc_span::{source_map, Span}; @@ -45,7 +46,7 @@ pub(crate) struct DocContext<'tcx> { // The current set of parameter substitutions, // for expanding type aliases at the HIR level: /// Table `DefId` of type, lifetime, or const parameter -> substituted type, lifetime, or const - pub(crate) substs: DefIdMap, + pub(crate) args: DefIdMap, pub(crate) current_type_aliases: DefIdMap, /// Table synthetic type parameter for `impl Trait` in argument position -> bounds pub(crate) impl_trait_bounds: FxHashMap>, @@ -85,17 +86,17 @@ impl<'tcx> DocContext<'tcx> { /// the substitutions for a type alias' RHS. pub(crate) fn enter_alias( &mut self, - substs: DefIdMap, + args: DefIdMap, def_id: DefId, f: F, ) -> R where F: FnOnce(&mut Self) -> R, { - let old_substs = mem::replace(&mut self.substs, substs); + let old_args = mem::replace(&mut self.args, args); *self.current_type_aliases.entry(def_id).or_insert(0) += 1; let r = f(self); - self.substs = old_substs; + self.args = old_args; if let Some(count) = self.current_type_aliases.get_mut(&def_id) { *count -= 1; if *count == 0 { @@ -136,19 +137,13 @@ pub(crate) fn new_handler( ErrorOutputType::HumanReadable(kind) => { let (short, color_config) = kind.unzip(); Box::new( - EmitterWriter::stderr( - color_config, - source_map.map(|sm| sm as _), - None, - fallback_bundle, - short, - unstable_opts.teach, - diagnostic_width, - false, - unstable_opts.track_diagnostics, - TerminalUrl::No, - ) - .ui_testing(unstable_opts.ui_testing), + EmitterWriter::stderr(color_config, fallback_bundle) + .sm(source_map.map(|sm| sm as _)) + .short_message(short) + .teach(unstable_opts.teach) + .diagnostic_width(diagnostic_width) + .track_diagnostics(unstable_opts.track_diagnostics) + .ui_testing(unstable_opts.ui_testing), ) } ErrorOutputType::Json { pretty, json_rendered } => { @@ -173,14 +168,13 @@ pub(crate) fn new_handler( } }; - rustc_errors::Handler::with_emitter_and_flags( - emitter, - unstable_opts.diagnostic_handler_flags(true), - ) + rustc_errors::Handler::with_emitter(emitter) + .with_flags(unstable_opts.diagnostic_handler_flags(true)) } /// Parse, resolve, and typecheck the given crate. pub(crate) fn create_config( + handler: &EarlyErrorHandler, RustdocOptions { input, crate_name, @@ -258,8 +252,8 @@ pub(crate) fn create_config( interface::Config { opts: sessopts, - crate_cfg: interface::parse_cfgspecs(cfgs), - crate_check_cfg: interface::parse_check_cfg(check_cfgs), + crate_cfg: interface::parse_cfgspecs(handler, cfgs), + crate_check_cfg: interface::parse_check_cfg(handler, check_cfgs), input, output_file: None, output_dir: None, @@ -269,8 +263,9 @@ pub(crate) fn create_config( parse_sess_created: None, register_lints: Some(Box::new(crate::lint::register_lints)), override_queries: Some(|_sess, providers, _external_providers| { + // We do not register late module lints, so this only runs `MissingDoc`. // Most lints will require typechecking, so just don't run them. - providers.lint_mod = |_, _| {}; + providers.lint_mod = |tcx, module_def_id| late_lint_mod(tcx, module_def_id, MissingDoc); // hack so that `used_trait_imports` won't try to call typeck providers.used_trait_imports = |_, _| { static EMPTY_SET: LazyLock> = LazyLock::new(UnordSet::default); @@ -288,13 +283,14 @@ pub(crate) fn create_config( let hir = tcx.hir(); let body = hir.body(hir.body_owned_by(def_id)); - debug!("visiting body for {:?}", def_id); + debug!("visiting body for {def_id:?}"); EmitIgnoredResolutionErrors::new(tcx).visit_body(body); (rustc_interface::DEFAULT_QUERY_PROVIDERS.typeck)(tcx, def_id) }; }), make_codegen_backend: None, registry: rustc_driver::diagnostics_registry(), + ice_file: None, } } @@ -323,9 +319,7 @@ pub(crate) fn run_global_ctxt( tcx.hir().for_each_module(|module| tcx.ensure().check_mod_item_types(module)) }); tcx.sess.abort_if_errors(); - tcx.sess.time("missing_docs", || { - rustc_lint::check_crate(tcx, rustc_lint::builtin::MissingDoc::new); - }); + tcx.sess.time("missing_docs", || rustc_lint::check_crate(tcx)); tcx.sess.time("check_mod_attrs", || { tcx.hir().for_each_module(|module| tcx.ensure().check_mod_attrs(module)) }); @@ -339,12 +333,12 @@ pub(crate) fn run_global_ctxt( param_env: ParamEnv::empty(), external_traits: Default::default(), active_extern_traits: Default::default(), - substs: Default::default(), + args: Default::default(), current_type_aliases: Default::default(), impl_trait_bounds: Default::default(), generated_synthetics: Default::default(), auto_traits, - cache: Cache::new(render_options.document_private), + cache: Cache::new(render_options.document_private, render_options.document_hidden), inlined: FxHashSet::default(), output_format, render_options, @@ -367,7 +361,7 @@ pub(crate) fn run_global_ctxt( let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt)); - if krate.module.doc_value().map(|d| d.is_empty()).unwrap_or(true) { + if krate.module.doc_value().is_empty() { let help = format!( "The following guide may be of use:\n\ {}/rustdoc/how-to-write-documentation.html", @@ -383,7 +377,7 @@ pub(crate) fn run_global_ctxt( fn report_deprecated_attr(name: &str, diag: &rustc_errors::Handler, sp: Span) { let mut msg = - diag.struct_span_warn(sp, format!("the `#![doc({})]` attribute is deprecated", name)); + diag.struct_span_warn(sp, format!("the `#![doc({name})]` attribute is deprecated")); msg.note( "see issue #44136 \ for more information", @@ -476,7 +470,7 @@ impl<'tcx> Visitor<'tcx> for EmitIgnoredResolutionErrors<'tcx> { } fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) { - debug!("visiting path {:?}", path); + debug!("visiting path {path:?}"); if path.res == Res::Err { // We have less context here than in rustc_resolve, // so we can only emit the name and span. @@ -493,8 +487,7 @@ impl<'tcx> Visitor<'tcx> for EmitIgnoredResolutionErrors<'tcx> { self.tcx.sess, path.span, E0433, - "failed to resolve: {}", - label + "failed to resolve: {label}", ); err.span_label(path.span, label); err.note("this error was originally ignored because you are running `rustdoc`"); diff --git a/src/librustdoc/docfs.rs b/src/librustdoc/docfs.rs index d58b8dc6ad4a4..82c1a503924c0 100644 --- a/src/librustdoc/docfs.rs +++ b/src/librustdoc/docfs.rs @@ -72,9 +72,9 @@ impl DocFS { let sender = self.errors.clone().expect("can't write after closing"); self.pool.execute(move || { fs::write(&path, contents).unwrap_or_else(|e| { - sender.send(format!("\"{}\": {}", path.display(), e)).unwrap_or_else(|_| { - panic!("failed to send error on \"{}\"", path.display()) - }) + sender.send(format!("\"{path}\": {e}", path = path.display())).unwrap_or_else( + |_| panic!("failed to send error on \"{}\"", path.display()), + ) }); }); } else { diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 575d8ee65b7ba..36d5adb63044a 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -1,7 +1,7 @@ use rustc_ast as ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lrc; -use rustc_errors::{ColorConfig, ErrorGuaranteed, FatalError, TerminalUrl}; +use rustc_errors::{ColorConfig, ErrorGuaranteed, FatalError}; use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID, LOCAL_CRATE}; use rustc_hir::{self as hir, intravisit, CRATE_HIR_ID}; use rustc_interface::interface; @@ -12,7 +12,7 @@ use rustc_parse::maybe_new_parser_from_source_str; use rustc_parse::parser::attr::InnerAttrPolicy; use rustc_session::config::{self, CrateType, ErrorOutputType}; use rustc_session::parse::ParseSess; -use rustc_session::{lint, Session}; +use rustc_session::{lint, EarlyErrorHandler, Session}; use rustc_span::edition::Edition; use rustc_span::source_map::SourceMap; use rustc_span::symbol::sym; @@ -85,13 +85,18 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { ..config::Options::default() }; + let early_error_handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let mut cfgs = options.cfgs.clone(); cfgs.push("doc".to_owned()); cfgs.push("doctest".to_owned()); let config = interface::Config { opts: sessopts, - crate_cfg: interface::parse_cfgspecs(cfgs), - crate_check_cfg: interface::parse_check_cfg(options.check_cfgs.clone()), + crate_cfg: interface::parse_cfgspecs(&early_error_handler, cfgs), + crate_check_cfg: interface::parse_check_cfg( + &early_error_handler, + options.check_cfgs.clone(), + ), input, output_file: None, output_dir: None, @@ -103,6 +108,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { override_queries: None, make_codegen_backend: None, registry: rustc_driver::diagnostics_registry(), + ice_file: None, }; let test_args = options.test_args.clone(); @@ -186,7 +192,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { // The allow lint level is not expected, // as if allow is specified, no message // is to be emitted. - v => unreachable!("Invalid lint level '{}'", v), + v => unreachable!("Invalid lint level '{v}'"), }) .unwrap_or("warn") .to_string(); @@ -398,7 +404,7 @@ fn run_test( compiler.stdin(Stdio::piped()); compiler.stderr(Stdio::piped()); - debug!("compiler invocation for doctest: {:?}", compiler); + debug!("compiler invocation for doctest: {compiler:?}"); let mut child = compiler.spawn().expect("Failed to spawn rustc process"); { @@ -463,7 +469,9 @@ fn run_test( // Run the code! let mut cmd; + let output_file = make_maybe_absolute_path(output_file); if let Some(tool) = runtool { + let tool = make_maybe_absolute_path(tool.into()); cmd = Command::new(tool); cmd.args(runtool_args); cmd.arg(output_file); @@ -497,6 +505,20 @@ fn run_test( Ok(()) } +/// Converts a path intended to use as a command to absolute if it is +/// relative, and not a single component. +/// +/// This is needed to deal with relative paths interacting with +/// `Command::current_dir` in a platform-specific way. +fn make_maybe_absolute_path(path: PathBuf) -> PathBuf { + if path.components().count() == 1 { + // Look up process via PATH. + path + } else { + std::env::current_dir().map(|c| c.join(&path)).unwrap_or_else(|_| path) + } +} + /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of /// lines before the test code begins as well as if the output stream supports colors or not. pub(crate) fn make_test( @@ -552,36 +574,14 @@ pub(crate) fn make_test( rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false, ); - supports_color = EmitterWriter::stderr( - ColorConfig::Auto, - None, - None, - fallback_bundle.clone(), - false, - false, - Some(80), - false, - false, - TerminalUrl::No, - ) - .supports_color(); + supports_color = EmitterWriter::stderr(ColorConfig::Auto, fallback_bundle.clone()) + .diagnostic_width(Some(80)) + .supports_color(); - let emitter = EmitterWriter::new( - Box::new(io::sink()), - None, - None, - fallback_bundle, - false, - false, - false, - None, - false, - false, - TerminalUrl::No, - ); + let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser - let handler = Handler::with_emitter(false, None, Box::new(emitter)); + let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); let sess = ParseSess::with_span_handler(handler, sm); let mut found_main = false; @@ -648,8 +648,7 @@ pub(crate) fn make_test( (found_main, found_extern_crate, found_macro) }) }); - let Ok((already_has_main, already_has_extern_crate, found_macro)) = result - else { + let Ok((already_has_main, already_has_extern_crate, found_macro)) = result else { // If the parser panicked due to a fatal error, pass the test code through unchanged. // The error will be reported during compilation. return (s.to_owned(), 0, false); @@ -755,21 +754,9 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool { false, ); - let emitter = EmitterWriter::new( - Box::new(io::sink()), - None, - None, - fallback_bundle, - false, - false, - false, - None, - false, - false, - TerminalUrl::No, - ); + let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); - let handler = Handler::with_emitter(false, None, Box::new(emitter)); + let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); let sess = ParseSess::with_span_handler(handler, sm); let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source.to_owned()) { @@ -962,7 +949,7 @@ impl Collector { if !item_path.is_empty() { item_path.push(' '); } - format!("{} - {}(line {})", filename.prefer_local(), item_path, line) + format!("{} - {item_path}(line {line})", filename.prefer_local()) } pub(crate) fn set_position(&mut self, position: Span) { @@ -1039,7 +1026,7 @@ impl Tester for Collector { path.push(&test_id); if let Err(err) = std::fs::create_dir_all(&path) { - eprintln!("Couldn't create directory for doctest executables: {}", err); + eprintln!("Couldn't create directory for doctest executables: {err}"); panic::resume_unwind(Box::new(())); } @@ -1108,7 +1095,7 @@ impl Tester for Collector { eprint!("Test executable succeeded, but it's marked `should_panic`."); } TestFailure::MissingErrorCodes(codes) => { - eprint!("Some expected error codes were not found: {:?}", codes); + eprint!("Some expected error codes were not found: {codes:?}"); } TestFailure::ExecutionError(err) => { eprint!("Couldn't run the test: {err}"); @@ -1237,7 +1224,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> { // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with // anything else, this will combine them for us. let attrs = Attributes::from_ast(ast_attrs); - if let Some(doc) = attrs.collapsed_doc_value() { + if let Some(doc) = attrs.opt_doc_value() { // Use the outermost invocation, so that doctest names come from where the docs were written. let span = ast_attrs .iter() diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index 88049c4ca0051..f0ebb8e5a3905 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -37,8 +37,7 @@ impl ExternalHtml { let bc = load_external_files(before_content, diag)?; let m_bc = load_external_files(md_before_content, diag)?; let bc = format!( - "{}{}", - bc, + "{bc}{}", Markdown { content: &m_bc, links: &[], @@ -53,8 +52,7 @@ impl ExternalHtml { let ac = load_external_files(after_content, diag)?; let m_ac = load_external_files(md_after_content, diag)?; let ac = format!( - "{}{}", - ac, + "{ac}{}", Markdown { content: &m_ac, links: &[], @@ -83,7 +81,11 @@ pub(crate) fn load_string>( let contents = match fs::read(file_path) { Ok(bytes) => bytes, Err(e) => { - diag.struct_err(format!("error reading `{}`: {}", file_path.display(), e)).emit(); + diag.struct_err(format!( + "error reading `{file_path}`: {e}", + file_path = file_path.display() + )) + .emit(); return Err(LoadStringError::ReadFail); } }; diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index c0730e90740eb..d1deda0c71646 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -86,6 +86,9 @@ pub(crate) struct Cache { /// Whether to document private items. /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. pub(crate) document_private: bool, + /// Whether to document hidden items. + /// This is stored in `Cache` so it doesn't need to be passed through all rustdoc functions. + pub(crate) document_hidden: bool, /// Crates marked with [`#[doc(masked)]`][doc_masked]. /// @@ -121,6 +124,11 @@ pub(crate) struct Cache { pub(crate) intra_doc_links: FxHashMap>, /// Cfg that have been hidden via #![doc(cfg_hide(...))] pub(crate) hidden_cfg: FxHashSet, + + /// Contains the list of `DefId`s which have been inlined. It is used when generating files + /// to check if a stripped item should get its file generated or not: if it's inside a + /// `#[doc(hidden)]` item or a private one and not inlined, it shouldn't get a file. + pub(crate) inlined_items: DefIdSet, } /// This struct is used to wrap the `cache` and `tcx` in order to run `DocFolder`. @@ -132,8 +140,8 @@ struct CacheBuilder<'a, 'tcx> { } impl Cache { - pub(crate) fn new(document_private: bool) -> Self { - Cache { document_private, ..Cache::default() } + pub(crate) fn new(document_private: bool, document_hidden: bool) -> Self { + Cache { document_private, document_hidden, ..Cache::default() } } /// Populates the `Cache` with more data. The returned `Crate` will be missing some data that was @@ -147,7 +155,7 @@ impl Cache { // Cache where all our extern crates are located // FIXME: this part is specific to HTML so it'd be nice to remove it from the common code - for &crate_num in cx.tcx.crates(()) { + for &crate_num in tcx.crates(()) { let e = ExternalCrate { crate_num }; let name = e.name(tcx); @@ -327,9 +335,8 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // which should not be indexed. The crate-item itself is // inserted later on when serializing the search-index. if item.item_id.as_def_id().map_or(false, |idx| !idx.is_crate_root()) { - let desc = item.doc_value().map_or_else(String::new, |x| { - short_markdown_summary(x.as_str(), &item.link_names(self.cache)) - }); + let desc = + short_markdown_summary(&item.doc_value(), &item.link_names(self.cache)); let ty = item.type_(); if ty != ItemType::StructField || u16::from_str_radix(s.as_str(), 10).is_err() @@ -469,7 +476,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { | clean::BorrowedRef { type_: box clean::Type::Path { ref path }, .. } => { dids.insert(path.def_id()); if let Some(generics) = path.generics() && - let ty::Adt(adt, _) = self.tcx.type_of(path.def_id()).subst_identity().kind() && + let ty::Adt(adt, _) = self.tcx.type_of(path.def_id()).instantiate_identity().kind() && adt.is_fundamental() { for ty in generics { if let Some(did) = ty.def_id(self.cache) { diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index 452e14918faf7..a788935581fa9 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -115,7 +115,7 @@ impl From for ItemType { DefKind::Struct => Self::Struct, DefKind::Union => Self::Union, DefKind::Trait => Self::Trait, - DefKind::TyAlias => Self::Typedef, + DefKind::TyAlias { .. } => Self::Typedef, DefKind::TraitAlias => Self::TraitAlias, DefKind::Macro(kind) => match kind { MacroKind::Bang => ItemType::Macro, @@ -136,7 +136,6 @@ impl From for ItemType { | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Field | DefKind::LifetimeParam | DefKind::GlobalAsm diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index 6f9cc026675b6..c49f1a4d37ec1 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -77,8 +77,11 @@ pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>( prof.generic_activity_with_arg("render_mod_item", item.name.unwrap().to_string()); cx.mod_item_in(&item)?; - let (clean::StrippedItem(box clean::ModuleItem(module)) | clean::ModuleItem(module)) = *item.kind - else { unreachable!() }; + let (clean::StrippedItem(box clean::ModuleItem(module)) | clean::ModuleItem(module)) = + *item.kind + else { + unreachable!() + }; for it in module.items { debug!("Adding {:?} to worklist", it.name); work.push((cx.make_child_renderer(), it)); diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index d963d6092c48f..2f611c31a0746 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -18,7 +18,7 @@ use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::DefKind; -use rustc_hir::def_id::DefId; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_metadata::creader::{CStore, LoadedMacro}; use rustc_middle::ty; use rustc_middle::ty::TyCtxt; @@ -109,6 +109,10 @@ impl Buffer { self.buffer } + pub(crate) fn push(&mut self, c: char) { + self.buffer.push(c); + } + pub(crate) fn push_str(&mut self, s: &str) { self.buffer.push_str(s); } @@ -228,9 +232,9 @@ impl clean::GenericParamDef { if let Some(default) = default { if f.alternate() { - write!(f, " = {:#}", default)?; + write!(f, " = {default:#}")?; } else { - write!(f, " = {}", default)?; + write!(f, " = {default}")?; } } @@ -347,13 +351,19 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( } } else { let mut br_with_padding = String::with_capacity(6 * indent + 28); - br_with_padding.push_str("\n"); + br_with_padding.push('\n'); - let padding_amount = - if ending == Ending::Newline { indent + 4 } else { indent + "fn where ".len() }; + let where_indent = 3; + let padding_amount = if ending == Ending::Newline { + indent + 4 + } else if indent == 0 { + 4 + } else { + indent + where_indent + "where ".len() + }; for _ in 0..padding_amount { - br_with_padding.push_str(" "); + br_with_padding.push(' '); } let where_preds = where_preds.to_string().replace('\n', &br_with_padding); @@ -370,7 +380,8 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( let where_preds = where_preds.replacen(&br_with_padding, " ", 1); let mut clause = br_with_padding; - clause.truncate(clause.len() - "where ".len()); + // +1 is for `\n`. + clause.truncate(indent + 1 + where_indent); write!(clause, "where{where_preds}")?; clause @@ -444,9 +455,9 @@ impl clean::GenericBound { hir::TraitBoundModifier::MaybeConst => "", }; if f.alternate() { - write!(f, "{}{:#}", modifier_str, ty.print(cx)) + write!(f, "{modifier_str}{ty:#}", ty = ty.print(cx)) } else { - write!(f, "{}{}", modifier_str, ty.print(cx)) + write!(f, "{modifier_str}{ty}", ty = ty.print(cx)) } } }) @@ -592,12 +603,12 @@ fn generate_macro_def_id_path( let cstore = CStore::from_tcx(tcx); // We need this to prevent a `panic` when this function is used from intra doc links... if !cstore.has_crate_data(def_id.krate) { - debug!("No data for crate {}", crate_name); + debug!("No data for crate {crate_name}"); return Err(HrefError::NotInExternalCache); } // Check to see if it is a macro 2.0 or built-in macro. // More information in . - let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx.sess) { + let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx) { LoadedMacro::MacroDef(def, _) => { // If `ast_def.macro_rules` is `true`, then it's not a macro 2.0. matches!(&def.kind, ast::ItemKind::MacroDef(ast_def) if !ast_def.macro_rules) @@ -624,19 +635,18 @@ fn generate_macro_def_id_path( let url = match cache.extern_locations[&def_id.krate] { ExternalLocation::Remote(ref s) => { // `ExternalLocation::Remote` always end with a `/`. - format!("{}{}", s, path.iter().map(|p| p.as_str()).join("/")) + format!("{s}{path}", path = path.iter().map(|p| p.as_str()).join("/")) } ExternalLocation::Local => { // `root_path` always end with a `/`. format!( - "{}{}/{}", - root_path.unwrap_or(""), - crate_name, - path.iter().map(|p| p.as_str()).join("/") + "{root_path}{crate_name}/{path}", + root_path = root_path.unwrap_or(""), + path = path.iter().map(|p| p.as_str()).join("/") ) } ExternalLocation::Unknown => { - debug!("crate {} not in cache when linkifying macros", crate_name); + debug!("crate {crate_name} not in cache when linkifying macros"); return Err(HrefError::NotInExternalCache); } }; @@ -655,6 +665,14 @@ pub(crate) fn href_with_root_path( // documented on their parent's page tcx.parent(did) } + DefKind::ExternCrate => { + // Link to the crate itself, not the `extern crate` item. + if let Some(local_did) = did.as_local() { + tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id() + } else { + did + } + } _ => did, }; let cache = cx.cache(); @@ -717,7 +735,7 @@ pub(crate) fn href_with_root_path( _ => { let prefix = shortty.as_str(); let last = fqp.last().unwrap(); - url_parts.push_fmt(format_args!("{}.{}.html", prefix, last)); + url_parts.push_fmt(format_args!("{prefix}.{last}.html")); } } Ok((url_parts.finish(), shortty, fqp.to_vec())) @@ -764,9 +782,10 @@ pub(crate) fn href_relative_parts<'fqp>( pub(crate) fn link_tooltip(did: DefId, fragment: &Option, cx: &Context<'_>) -> String { let cache = cx.cache(); - let Some((fqp, shortty)) = cache.paths.get(&did) - .or_else(|| cache.external_paths.get(&did)) - else { return String::new() }; + let Some((fqp, shortty)) = cache.paths.get(&did).or_else(|| cache.external_paths.get(&did)) + else { + return String::new(); + }; let mut buf = Buffer::new(); let fqp = if *shortty == ItemType::Primitive { // primitives are documented in a crate, but not actually part of it @@ -812,9 +831,9 @@ fn resolved_path<'cx>( let path = if use_absolute { if let Ok((_, _, fqp)) = href(did, cx) { format!( - "{}::{}", - join_with_double_colon(&fqp[..fqp.len() - 1]), - anchor(did, *fqp.last().unwrap(), cx) + "{path}::{anchor}", + path = join_with_double_colon(&fqp[..fqp.len() - 1]), + anchor = anchor(did, *fqp.last().unwrap(), cx) ) } else { last.name.to_string() @@ -822,7 +841,7 @@ fn resolved_path<'cx>( } else { anchor(did, last.name, cx).to_string() }; - write!(w, "{}{}", path, last.args.print(cx))?; + write!(w, "{path}{args}", args = last.args.print(cx))?; } Ok(()) } @@ -890,7 +909,7 @@ fn primitive_link_fragment( None => {} } } - write!(f, "{}", name)?; + f.write_str(name)?; if needs_termination { write!(f, "")?; } @@ -930,15 +949,11 @@ pub(crate) fn anchor<'a, 'cx: 'a>( if let Ok((url, short_ty, fqp)) = parts { write!( f, - r#"{}"#, - short_ty, - url, - short_ty, - join_with_double_colon(&fqp), - text.as_str() + r#"{text}"#, + path = join_with_double_colon(&fqp), ) } else { - write!(f, "{}", text) + f.write_str(text.as_str()) } }) } @@ -949,10 +964,10 @@ fn fmt_type<'cx>( use_absolute: bool, cx: &'cx Context<'_>, ) -> fmt::Result { - trace!("fmt_type(t = {:?})", t); + trace!("fmt_type(t = {t:?})"); match *t { - clean::Generic(name) => write!(f, "{}", name), + clean::Generic(name) => f.write_str(name.as_str()), clean::Type::Path { ref path } => { // Paths like `T::Output` and `Self::Output` should be rendered with all segments. let did = path.def_id(); @@ -1069,13 +1084,13 @@ fn fmt_type<'cx>( if matches!(**t, clean::Generic(_)) || t.is_assoc_ty() { let text = if f.alternate() { - format!("*{} {:#}", m, t.print(cx)) + format!("*{m} {ty:#}", ty = t.print(cx)) } else { - format!("*{} {}", m, t.print(cx)) + format!("*{m} {ty}", ty = t.print(cx)) }; primitive_link(f, clean::PrimitiveType::RawPointer, &text, cx) } else { - primitive_link(f, clean::PrimitiveType::RawPointer, &format!("*{} ", m), cx)?; + primitive_link(f, clean::PrimitiveType::RawPointer, &format!("*{m} "), cx)?; fmt::Display::fmt(&t.print(cx), f) } } @@ -1086,22 +1101,35 @@ fn fmt_type<'cx>( }; let m = mutability.print_with_space(); let amp = if f.alternate() { "&" } else { "&" }; - match **ty { + + if let clean::Generic(name) = **ty { + return primitive_link( + f, + PrimitiveType::Reference, + &format!("{amp}{lt}{m}{name}"), + cx, + ); + } + + write!(f, "{amp}{lt}{m}")?; + + let needs_parens = match **ty { clean::DynTrait(ref bounds, ref trait_lt) if bounds.len() > 1 || trait_lt.is_some() => { - write!(f, "{}{}{}(", amp, lt, m)?; - fmt_type(ty, f, use_absolute, cx)?; - write!(f, ")") - } - clean::Generic(name) => { - primitive_link(f, PrimitiveType::Reference, &format!("{amp}{lt}{m}{name}"), cx) - } - _ => { - write!(f, "{}{}{}", amp, lt, m)?; - fmt_type(ty, f, use_absolute, cx) + true } + clean::ImplTrait(ref bounds) if bounds.len() > 1 => true, + _ => false, + }; + if needs_parens { + f.write_str("(")?; + } + fmt_type(ty, f, use_absolute, cx)?; + if needs_parens { + f.write_str(")")?; } + Ok(()) } clean::ImplTrait(ref bounds) => { if f.alternate() { @@ -1257,9 +1285,9 @@ impl clean::Impl { }; primitive_link_fragment(f, PrimitiveType::Tuple, &format!("fn ({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), "#trait-implementations-1", cx)?; // Write output. - if let clean::FnRetTy::Return(ty) = &bare_fn.decl.output { + if !bare_fn.decl.output.is_unit() { write!(f, " -> ")?; - fmt_type(ty, f, use_absolute, cx)?; + fmt_type(&bare_fn.decl.output, f, use_absolute, cx)?; } } else if let Some(ty) = self.kind.as_blanket_ty() { fmt_type(ty, f, use_absolute, cx)?; @@ -1296,22 +1324,6 @@ impl clean::Arguments { } } -impl clean::FnRetTy { - pub(crate) fn print<'a, 'tcx: 'a>( - &'a self, - cx: &'a Context<'tcx>, - ) -> impl fmt::Display + 'a + Captures<'tcx> { - display_fn(move |f| match self { - clean::Return(clean::Tuple(tys)) if tys.is_empty() => Ok(()), - clean::Return(ty) if f.alternate() => { - write!(f, " -> {:#}", ty.print(cx)) - } - clean::Return(ty) => write!(f, " -> {}", ty.print(cx)), - clean::DefaultReturn => Ok(()), - }) - } -} - impl clean::BareFunctionDecl { fn print_hrtb_with_space<'a, 'tcx: 'a>( &'a self, @@ -1366,7 +1378,7 @@ impl clean::FnDecl { "({args:#}{ellipsis}){arrow:#}", args = self.inputs.print(cx), ellipsis = ellipsis, - arrow = self.output.print(cx) + arrow = self.print_output(cx) ) } else { write!( @@ -1374,7 +1386,7 @@ impl clean::FnDecl { "({args}{ellipsis}){arrow}", args = self.inputs.print(cx), ellipsis = ellipsis, - arrow = self.output.print(cx) + arrow = self.print_output(cx) ) } }) @@ -1417,7 +1429,7 @@ impl clean::FnDecl { let amp = if f.alternate() { "&" } else { "&" }; write!(f, "(")?; - if let Some(n) = line_wrapping_indent { + if let Some(n) = line_wrapping_indent && !self.inputs.values.is_empty() { write!(f, "\n{}", Indent(n + 4))?; } for (i, input) in self.inputs.values.iter().enumerate() { @@ -1432,11 +1444,20 @@ impl clean::FnDecl { clean::SelfValue => { write!(f, "self")?; } - clean::SelfBorrowed(Some(ref lt), mtbl) => { - write!(f, "{}{} {}self", amp, lt.print(), mtbl.print_with_space())?; + clean::SelfBorrowed(Some(ref lt), mutability) => { + write!( + f, + "{amp}{lifetime} {mutability}self", + lifetime = lt.print(), + mutability = mutability.print_with_space(), + )?; } - clean::SelfBorrowed(None, mtbl) => { - write!(f, "{}{}self", amp, mtbl.print_with_space())?; + clean::SelfBorrowed(None, mutability) => { + write!( + f, + "{amp}{mutability}self", + mutability = mutability.print_with_space(), + )?; } clean::SelfExplicit(ref typ) => { write!(f, "self: ")?; @@ -1464,9 +1485,22 @@ impl clean::FnDecl { Some(n) => write!(f, "\n{})", Indent(n))?, }; - fmt::Display::fmt(&self.output.print(cx), f)?; + fmt::Display::fmt(&self.print_output(cx), f)?; Ok(()) } + + fn print_output<'a, 'tcx: 'a>( + &'a self, + cx: &'a Context<'tcx>, + ) -> impl fmt::Display + 'a + Captures<'tcx> { + display_fn(move |f| match &self.output { + clean::Tuple(tys) if tys.is_empty() => Ok(()), + ty if f.alternate() => { + write!(f, " -> {:#}", ty.print(cx)) + } + ty => write!(f, " -> {}", ty.print(cx)), + }) + } } pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( @@ -1497,7 +1531,7 @@ pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( "pub(super) ".into() } else { let path = cx.tcx().def_path(vis_did); - debug!("path={:?}", path); + debug!("path={path:?}"); // modified from `resolved_path()` to work with `DefPathData` let last_name = path.data.last().unwrap().data.get_opt_name().unwrap(); let anchor = anchor(vis_did, last_name, cx); @@ -1506,12 +1540,12 @@ pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>( for seg in &path.data[..path.data.len() - 1] { let _ = write!(s, "{}::", seg.data.get_opt_name().unwrap()); } - let _ = write!(s, "{}) ", anchor); + let _ = write!(s, "{anchor}) "); s.into() } } }; - display_fn(move |f| write!(f, "{}", to_print)) + display_fn(move |f| f.write_str(&to_print)) } /// This function is the same as print_with_space, except that it renders no links. @@ -1606,7 +1640,7 @@ impl clean::Import { if name == self.source.path.last() { write!(f, "use {};", self.source.print(cx)) } else { - write!(f, "use {} as {};", self.source.print(cx), name) + write!(f, "use {source} as {name};", source = self.source.print(cx)) } } clean::ImportKind::Glob => { @@ -1635,7 +1669,7 @@ impl clean::ImportSource { if let hir::def::Res::PrimTy(p) = self.path.res { primitive_link(f, PrimitiveType::from(p), name.as_str(), cx)?; } else { - write!(f, "{}", name)?; + f.write_str(name.as_str())?; } Ok(()) } diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index c94968b4817cb..039e8cdb98738 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -928,18 +928,16 @@ fn string_without_closing_tag( href_context: &Option>, open_tag: bool, ) -> Option<&'static str> { - let Some(klass) = klass - else { - write!(out, "{}", text).unwrap(); + let Some(klass) = klass else { + write!(out, "{text}").unwrap(); return None; }; - let Some(def_span) = klass.get_span() - else { + let Some(def_span) = klass.get_span() else { if !open_tag { - write!(out, "{}", text).unwrap(); + write!(out, "{text}").unwrap(); return None; } - write!(out, "{}", klass.as_html(), text).unwrap(); + write!(out, "{text}", klass = klass.as_html()).unwrap(); return Some(""); }; @@ -949,14 +947,17 @@ fn string_without_closing_tag( match t { "self" | "Self" => write!( &mut path, - "{}", - Class::Self_(DUMMY_SP).as_html(), - t + "{t}", + klass = Class::Self_(DUMMY_SP).as_html(), ), "crate" | "super" => { - write!(&mut path, "{}", Class::KeyWord.as_html(), t) + write!( + &mut path, + "{t}", + klass = Class::KeyWord.as_html(), + ) } - t => write!(&mut path, "{}", t), + t => write!(&mut path, "{t}"), } .expect("Failed to build source HTML path"); path @@ -988,19 +989,24 @@ fn string_without_closing_tag( ) .ok() .map(|(url, _, _)| url), + LinkFromSrc::Doc(def_id) => { + format::href_with_root_path(*def_id, context, Some(&href_context.root_path)) + .ok() + .map(|(doc_link, _, _)| doc_link) + } } }) { if !open_tag { // We're already inside an element which has the same klass, no need to give it // again. - write!(out, "{}", href, text_s).unwrap(); + write!(out, "{text_s}").unwrap(); } else { let klass_s = klass.as_html(); if klass_s.is_empty() { - write!(out, "{}", href, text_s).unwrap(); + write!(out, "{text_s}").unwrap(); } else { - write!(out, "{}", klass_s, href, text_s).unwrap(); + write!(out, "{text_s}").unwrap(); } } return Some(""); @@ -1015,7 +1021,7 @@ fn string_without_closing_tag( out.write_str(&text_s).unwrap(); Some("") } else { - write!(out, "{}", klass_s, text_s).unwrap(); + write!(out, "{text_s}").unwrap(); Some("") } } diff --git a/src/librustdoc/html/highlight/tests.rs b/src/librustdoc/html/highlight/tests.rs index 2c93b9a097f40..4c0874a686fe9 100644 --- a/src/librustdoc/html/highlight/tests.rs +++ b/src/librustdoc/html/highlight/tests.rs @@ -23,7 +23,7 @@ fn test_html_highlighting() { let html = { let mut out = Buffer::new(); write_code(&mut out, src, None, None); - format!("{}

{}
\n", STYLE, out.into_inner()) + format!("{STYLE}
{}
\n", out.into_inner()) }; expect_file!["fixtures/sample.html"].assert_eq(&html); }); diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs index 6ab849c92a076..8c5871d912647 100644 --- a/src/librustdoc/html/layout.rs +++ b/src/librustdoc/html/layout.rs @@ -55,6 +55,7 @@ struct PageLayout<'a> { sidebar: String, content: String, krate_with_trailing_slash: String, + rust_channel: &'static str, pub(crate) rustdoc_version: &'a str, } @@ -82,6 +83,7 @@ pub(crate) fn render( sidebar, content, krate_with_trailing_slash, + rust_channel: *crate::clean::utils::DOC_CHANNEL, rustdoc_version, } .render() diff --git a/src/librustdoc/html/length_limit.rs b/src/librustdoc/html/length_limit.rs index 4c8db2c6784a5..8562e103dc17e 100644 --- a/src/librustdoc/html/length_limit.rs +++ b/src/librustdoc/html/length_limit.rs @@ -78,8 +78,7 @@ impl HtmlWithLimit { pub(super) fn open_tag(&mut self, tag_name: &'static str) { assert!( tag_name.chars().all(|c| ('a'..='z').contains(&c)), - "tag_name contained non-alphabetic chars: {:?}", - tag_name + "tag_name contained non-alphabetic chars: {tag_name:?}", ); self.queued_tags.push(tag_name); } @@ -88,7 +87,7 @@ impl HtmlWithLimit { pub(super) fn close_tag(&mut self) { match self.unclosed_tags.pop() { // Close the most recently opened tag. - Some(tag_name) => write!(self.buf, "", tag_name).unwrap(), + Some(tag_name) => write!(self.buf, "").unwrap(), // There are valid cases where `close_tag()` is called without // there being any tags to close. For example, this occurs when // a tag is opened after the length limit is exceeded; @@ -101,7 +100,7 @@ impl HtmlWithLimit { /// Write all queued tags and add them to the `unclosed_tags` list. fn flush_queue(&mut self) { for tag_name in self.queued_tags.drain(..) { - write!(self.buf, "<{}>", tag_name).unwrap(); + write!(self.buf, "<{tag_name}>").unwrap(); self.unclosed_tags.push(tag_name); } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 09e7ed293d473..98cc38a10d44d 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -50,7 +50,7 @@ use crate::html::render::small_url_encode; use crate::html::toc::TocBuilder; use pulldown_cmark::{ - html, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, + html, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, OffsetIter, Options, Parser, Tag, }; #[cfg(test)] @@ -246,10 +246,9 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { return Some(Event::Html( format!( "
\ -
{}
\ +
{text}
\
", - lang, - Escape(&original_text), + text = Escape(&original_text), ) .into(), )); @@ -288,8 +287,9 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { let test_escaped = small_url_encode(test); Some(format!( - r#"Run"#, - url, test_escaped, channel, edition, + "Run", )) }); @@ -308,7 +308,7 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { // insert newline to clearly separate it from the // previous block so we can shorten the html output let mut s = Buffer::new(); - s.push_str("\n"); + s.push('\n'); highlight::render_example_with_highlighting( &text, @@ -349,7 +349,7 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { dest, title, ))) => { - debug!("saw start of shortcut link to {} with title {}", dest, title); + debug!("saw start of shortcut link to {dest} with title {title}"); // If this is a shortcut link, it was resolved by the broken_link_callback. // So the URL will already be updated properly. let link = self.links.iter().find(|&link| *link.href == **dest); @@ -370,7 +370,7 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { dest, _, ))) => { - debug!("saw end of shortcut link to {}", dest); + debug!("saw end of shortcut link to {dest}"); if self.links.iter().any(|link| *link.href == **dest) { assert!(self.shortcut_link.is_some(), "saw closing link without opening tag"); self.shortcut_link = None; @@ -379,9 +379,8 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { // Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link. // [`fn@f`] Some(Event::Code(text)) => { - trace!("saw code {}", text); + trace!("saw code {text}"); if let Some(link) = self.shortcut_link { - trace!("original text was {}", link.original_text); // NOTE: this only replaces if the code block is the *entire* text. // If only part of the link has code highlighting, the disambiguator will not be removed. // e.g. [fn@`f`] @@ -390,9 +389,12 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { // So we could never be sure we weren't replacing too much: // [fn@my_`f`unc] is treated the same as [my_func()] in that pass. // - // NOTE: &[1..len() - 1] is to strip the backticks - if **text == link.original_text[1..link.original_text.len() - 1] { - debug!("replacing {} with {}", text, link.new_text); + // NOTE: .get(1..len() - 1) is to strip the backticks + if let Some(link) = self.links.iter().find(|l| { + l.href == link.href + && Some(&**text) == l.original_text.get(1..l.original_text.len() - 1) + }) { + debug!("replacing {text} with {new_text}", new_text = link.new_text); *text = CowStr::Borrowed(&link.new_text); } } @@ -400,12 +402,15 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { // Replace plain text in links, but only in the middle of a shortcut link. // [fn@f] Some(Event::Text(text)) => { - trace!("saw text {}", text); + trace!("saw text {text}"); if let Some(link) = self.shortcut_link { - trace!("original text was {}", link.original_text); // NOTE: same limitations as `Event::Code` - if **text == *link.original_text { - debug!("replacing {} with {}", text, link.new_text); + if let Some(link) = self + .links + .iter() + .find(|l| l.href == link.href && **text == *l.original_text) + { + debug!("replacing {text} with {new_text}", new_text = link.new_text); *text = CowStr::Borrowed(&link.new_text); } } @@ -517,18 +522,16 @@ impl<'a, 'b, 'ids, I: Iterator>> Iterator let mut html_header = String::new(); html::push_html(&mut html_header, self.buf.iter().map(|(ev, _)| ev.clone())); let sec = builder.push(level as u32, html_header, id.clone()); - self.buf.push_front((Event::Html(format!("{} ", sec).into()), 0..0)); + self.buf.push_front((Event::Html(format!("{sec} ").into()), 0..0)); } let level = std::cmp::min(level as u32 + (self.heading_offset as u32), MAX_HEADER_LEVEL); - self.buf.push_back((Event::Html(format!("", level).into()), 0..0)); + self.buf.push_back((Event::Html(format!("").into()), 0..0)); let start_tags = format!( "\ ", - id = id, - level = level ); return Some((Event::Html(start_tags.into()), 0..0)); } @@ -678,14 +681,14 @@ impl<'a, I: Iterator>> Iterator for Footnotes<'a, I> { v.sort_by(|a, b| a.1.cmp(&b.1)); let mut ret = String::from("

    "); for (mut content, id) in v { - write!(ret, "
  1. ", id).unwrap(); + write!(ret, "
  2. ").unwrap(); let mut is_paragraph = false; if let Some(&Event::End(Tag::Paragraph)) = content.last() { content.pop(); is_paragraph = true; } html::push_html(&mut ret, content.into_iter()); - write!(ret, " ", id).unwrap(); + write!(ret, " ").unwrap(); if is_paragraph { ret.push_str("

    "); } @@ -781,7 +784,7 @@ impl<'tcx> ExtraInfo<'tcx> { ExtraInfo { def_id, sp, tcx } } - fn error_invalid_codeblock_attr(&self, msg: String, help: &str) { + fn error_invalid_codeblock_attr(&self, msg: String, help: &'static str) { if let Some(def_id) = self.def_id.as_local() { self.tcx.struct_span_lint_hir( crate::lint::INVALID_CODEBLOCK_ATTRIBUTES, @@ -956,7 +959,7 @@ impl LangString { } { if let Some(extra) = extra { extra.error_invalid_codeblock_attr( - format!("unknown attribute `{}`. Did you mean `{}`?", x, flag), + format!("unknown attribute `{x}`. Did you mean `{flag}`?"), help, ); } @@ -1035,7 +1038,7 @@ impl MarkdownWithToc<'_> { html::push_html(&mut s, p); } - format!("{}", toc.into_toc().print(), s) + format!("{s}", toc = toc.into_toc().print()) } } @@ -1237,11 +1240,32 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin pub(crate) struct MarkdownLink { pub kind: LinkType, pub link: String, - pub range: Range, + pub display_text: Option, + pub range: MarkdownLinkRange, } -pub(crate) fn markdown_links( - md: &str, +#[derive(Clone, Debug)] +pub(crate) enum MarkdownLinkRange { + /// Normally, markdown link warnings point only at the destination. + Destination(Range), + /// In some cases, it's not possible to point at the destination. + /// Usually, this happens because backslashes `\\` are used. + /// When that happens, point at the whole link, and don't provide structured suggestions. + WholeLink(Range), +} + +impl MarkdownLinkRange { + /// Extracts the inner range. + pub fn inner_range(&self) -> &Range { + match self { + MarkdownLinkRange::Destination(range) => range, + MarkdownLinkRange::WholeLink(range) => range, + } + } +} + +pub(crate) fn markdown_links<'md, R>( + md: &'md str, preprocess_link: impl Fn(MarkdownLink) -> Option, ) -> Vec { if md.is_empty() { @@ -1257,9 +1281,9 @@ pub(crate) fn markdown_links( if md_start <= s_start && s_end <= md_end { let start = s_start.offset_from(md_start) as usize; let end = s_end.offset_from(md_start) as usize; - start..end + MarkdownLinkRange::Destination(start..end) } else { - fallback + MarkdownLinkRange::WholeLink(fallback) } }; @@ -1267,6 +1291,7 @@ pub(crate) fn markdown_links( // For diagnostics, we want to underline the link's definition but `span` will point at // where the link is used. This is a problem for reference-style links, where the definition // is separate from the usage. + match link { // `Borrowed` variant means the string (the link's destination) may come directly from // the markdown text and we can locate the original link destination. @@ -1275,27 +1300,166 @@ pub(crate) fn markdown_links( CowStr::Borrowed(s) => locate(s, span), // For anything else, we can only use the provided range. - CowStr::Boxed(_) | CowStr::Inlined(_) => span, + CowStr::Boxed(_) | CowStr::Inlined(_) => MarkdownLinkRange::WholeLink(span), + } + }; + + let span_for_offset_backward = |span: Range, open: u8, close: u8| { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate().rev() { + let i = i + span.start; + if b == close { + close_brace = i; + break; + } + } + if close_brace < span.start || close_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + let mut nesting = 1; + for (i, b) in md.as_bytes()[span.start..close_brace].iter().copied().enumerate().rev() { + let i = i + span.start; + if b == close { + nesting += 1; + } + if b == open { + nesting -= 1; + } + if nesting == 0 { + open_brace = i; + break; + } + } + assert!(open_brace != close_brace); + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + // do not actually include braces in the span + let range = (open_brace + 1)..close_brace; + MarkdownLinkRange::Destination(range.clone()) + }; + + let span_for_offset_forward = |span: Range, open: u8, close: u8| { + let mut open_brace = !0; + let mut close_brace = !0; + for (i, b) in md.as_bytes()[span.clone()].iter().copied().enumerate() { + let i = i + span.start; + if b == open { + open_brace = i; + break; + } + } + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); + } + let mut nesting = 0; + for (i, b) in md.as_bytes()[open_brace..span.end].iter().copied().enumerate() { + let i = i + open_brace; + if b == close { + nesting -= 1; + } + if b == open { + nesting += 1; + } + if nesting == 0 { + close_brace = i; + break; + } + } + assert!(open_brace != close_brace); + if open_brace < span.start || open_brace >= span.end { + return MarkdownLinkRange::WholeLink(span); } + // do not actually include braces in the span + let range = (open_brace + 1)..close_brace; + MarkdownLinkRange::Destination(range.clone()) }; - Parser::new_with_broken_link_callback( + let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); + let mut event_iter = Parser::new_with_broken_link_callback( md, main_body_opts(), - Some(&mut |link: BrokenLink<'_>| Some((link.reference, "".into()))), + Some(&mut broken_link_callback), ) - .into_offset_iter() - .filter_map(|(event, span)| match event { - Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { - preprocess_link(MarkdownLink { - kind: link_type, - range: span_for_link(&dest, span), - link: dest.into_string(), - }) + .into_offset_iter(); + let mut links = Vec::new(); + + while let Some((event, span)) = event_iter.next() { + match event { + Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { + let range = match link_type { + // Link is pulled from the link itself. + LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => { + span_for_offset_backward(span, b'[', b']') + } + LinkType::CollapsedUnknown => span_for_offset_forward(span, b'[', b']'), + LinkType::Inline => span_for_offset_backward(span, b'(', b')'), + // Link is pulled from elsewhere in the document. + LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => { + span_for_link(&dest, span) + } + LinkType::Autolink | LinkType::Email => unreachable!(), + }; + + let display_text = if matches!( + link_type, + LinkType::Inline + | LinkType::ReferenceUnknown + | LinkType::Reference + | LinkType::Shortcut + | LinkType::ShortcutUnknown + ) { + collect_link_data(&mut event_iter) + } else { + None + }; + + if let Some(link) = preprocess_link(MarkdownLink { + kind: link_type, + link: dest.into_string(), + display_text, + range, + }) { + links.push(link); + } + } + _ => {} + } + } + + links +} + +/// Collects additional data of link. +fn collect_link_data<'input, 'callback>( + event_iter: &mut OffsetIter<'input, 'callback>, +) -> Option { + let mut display_text: Option = None; + let mut append_text = |text: CowStr<'_>| { + if let Some(display_text) = &mut display_text { + display_text.push_str(&text); + } else { + display_text = Some(text.to_string()); } - _ => None, - }) - .collect() + }; + + while let Some((event, _span)) = event_iter.next() { + match event { + Event::Text(text) => { + append_text(text); + } + Event::Code(code) => { + append_text(code); + } + Event::End(_) => { + break; + } + _ => {} + } + } + + display_text } #[derive(Debug)] @@ -1418,7 +1582,6 @@ fn init_id_map() -> FxHashMap, usize> { map.insert("toggle-all-docs".into(), 1); map.insert("all-types".into(), 1); map.insert("default-settings".into(), 1); - map.insert("rustdoc-vars".into(), 1); map.insert("sidebar-vars".into(), 1); map.insert("copy-path".into(), 1); map.insert("TOC".into(), 1); diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index e05635a020756..db8504d15c753 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -38,7 +38,7 @@ fn test_unique_id() { ]; let mut map = IdMap::new(); - let actual: Vec = input.iter().map(|s| map.derive(s.to_string())).collect(); + let actual: Vec = input.iter().map(|s| map.derive(s)).collect(); assert_eq!(&actual[..], expected); } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 01a92f6df6a0c..d7ff248a9bff5 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -73,6 +73,8 @@ pub(crate) struct Context<'tcx> { pub(crate) include_sources: bool, /// Collection of all types with notable traits referenced in the current module. pub(crate) types_with_notable_traits: FxHashSet, + /// Field used during rendering, to know if we're inside an inlined item. + pub(crate) is_inside_inlined_module: bool, } // `Context` is cloned a lot, so we don't want the size to grow unexpectedly. @@ -160,7 +162,7 @@ impl<'tcx> Context<'tcx> { self.shared.tcx.sess } - pub(super) fn derive_id(&mut self, id: String) -> String { + pub(super) fn derive_id + ToString>(&mut self, id: S) -> String { self.id_map.derive(id) } @@ -171,6 +173,19 @@ impl<'tcx> Context<'tcx> { } fn render_item(&mut self, it: &clean::Item, is_module: bool) -> String { + let mut render_redirect_pages = self.render_redirect_pages; + // If the item is stripped but inlined, links won't point to the item so no need to generate + // a file for it. + if it.is_stripped() && + let Some(def_id) = it.def_id() && + def_id.is_local() + { + if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) { + // For now we're forced to generate a redirect page for stripped items until + // `record_extern_fqn` correctly points to external items. + render_redirect_pages = true; + } + } let mut title = String::new(); if !is_module { title.push_str(it.name.unwrap().as_str()); @@ -184,31 +199,27 @@ impl<'tcx> Context<'tcx> { }; title.push_str(" - Rust"); let tyname = it.type_(); - let desc = it - .doc_value() - .as_ref() - .map(|doc| plain_text_summary(doc, &it.link_names(&self.cache()))); - let desc = if let Some(desc) = desc { + let desc = plain_text_summary(&it.doc_value(), &it.link_names(&self.cache())); + let desc = if !desc.is_empty() { desc } else if it.is_crate() { format!("API documentation for the Rust `{}` crate.", self.shared.layout.krate) } else { format!( - "API documentation for the Rust `{}` {} in crate `{}`.", - it.name.as_ref().unwrap(), - tyname, - self.shared.layout.krate + "API documentation for the Rust `{name}` {tyname} in crate `{krate}`.", + name = it.name.as_ref().unwrap(), + krate = self.shared.layout.krate, ) }; let name; let tyname_s = if it.is_crate() { - name = format!("{} crate", tyname); + name = format!("{tyname} crate"); name.as_str() } else { tyname.as_str() }; - if !self.render_redirect_pages { + if !render_redirect_pages { let clone_shared = Rc::clone(&self.shared); let page = layout::Page { css_class: tyname_s, @@ -252,7 +263,12 @@ impl<'tcx> Context<'tcx> { current_path.push_str(&item_path(ty, names.last().unwrap().as_str())); redirections.borrow_mut().insert(current_path, path); } - None => return layout::redirect(&format!("{}{}", self.root_path(), path)), + None => { + return layout::redirect(&format!( + "{root}{path}", + root = self.root_path() + )); + } } } } @@ -370,11 +386,7 @@ impl<'tcx> Context<'tcx> { let hiline = span.hi(self.sess()).line; format!( "#{}", - if loline == hiline { - loline.to_string() - } else { - format!("{}-{}", loline, hiline) - } + if loline == hiline { loline.to_string() } else { format!("{loline}-{hiline}") } ) } else { "".to_string() @@ -548,6 +560,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { shared: Rc::new(scx), include_sources, types_with_notable_traits: FxHashSet::default(), + is_inside_inlined_module: false, }; if emit_crate { @@ -577,6 +590,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { shared: Rc::clone(&self.shared), include_sources: self.include_sources, types_with_notable_traits: FxHashSet::default(), + is_inside_inlined_module: self.is_inside_inlined_module, } } @@ -771,18 +785,31 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { info!("Recursing into {}", self.dst.display()); - let buf = self.render_item(item, true); - // buf will be empty if the module is stripped and there is no redirect for it - if !buf.is_empty() { - self.shared.ensure_dir(&self.dst)?; - let joint_dst = self.dst.join("index.html"); - self.shared.fs.write(joint_dst, buf)?; + if !item.is_stripped() { + let buf = self.render_item(item, true); + // buf will be empty if the module is stripped and there is no redirect for it + if !buf.is_empty() { + self.shared.ensure_dir(&self.dst)?; + let joint_dst = self.dst.join("index.html"); + self.shared.fs.write(joint_dst, buf)?; + } + } + if !self.is_inside_inlined_module { + if let Some(def_id) = item.def_id() && self.cache().inlined_items.contains(&def_id) { + self.is_inside_inlined_module = true; + } + } else if !self.cache().document_hidden && item.is_doc_hidden() { + // We're not inside an inlined module anymore since this one cannot be re-exported. + self.is_inside_inlined_module = false; } // Render sidebar-items.js used throughout this module. if !self.render_redirect_pages { - let (clean::StrippedItem(box clean::ModuleItem(ref module)) | clean::ModuleItem(ref module)) = *item.kind - else { unreachable!() }; + let (clean::StrippedItem(box clean::ModuleItem(ref module)) + | clean::ModuleItem(ref module)) = *item.kind + else { + unreachable!() + }; let items = self.build_sidebar_items(module); let js_dst = self.dst.join(&format!("sidebar-items{}.js", self.shared.resource_suffix)); let v = format!("window.SIDEBAR_ITEMS = {};", serde_json::to_string(&items).unwrap()); @@ -828,12 +855,12 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { // If the item is a macro, redirect from the old macro URL (with !) // to the new one (without). if item_type == ItemType::Macro { - let redir_name = format!("{}.{}!.html", item_type, name); + let redir_name = format!("{item_type}.{name}!.html"); if let Some(ref redirections) = self.shared.redirections { let crate_name = &self.shared.layout.krate; redirections.borrow_mut().insert( - format!("{}/{}", crate_name, redir_name), - format!("{}/{}", crate_name, file_name), + format!("{crate_name}/{redir_name}"), + format!("{crate_name}/{file_name}"), ); } else { let v = layout::redirect(file_name); diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 0a2f5f6653cfd..ac9c180a6a867 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -85,7 +85,7 @@ use crate::DOC_RUST_LANG_ORG_CHANNEL; pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { crate::html::format::display_fn(move |f| { - if !v.ends_with('/') && !v.is_empty() { write!(f, "{}/", v) } else { f.write_str(v) } + if !v.ends_with('/') && !v.is_empty() { write!(f, "{v}/") } else { f.write_str(v) } }) } @@ -268,7 +268,7 @@ impl AllTypes { fn append(&mut self, item_name: String, item_type: &ItemType) { let mut url: Vec<_> = item_name.split("::").skip(1).collect(); if let Some(name) = url.pop() { - let new_url = format!("{}/{}.{}.html", url.join("/"), item_type, name); + let new_url = format!("{}/{item_type}.{name}.html", url.join("/")); url.push(name); let name = url.join("::"); match *item_type { @@ -385,16 +385,17 @@ impl AllTypes { fn scrape_examples_help(shared: &SharedContext<'_>) -> String { let mut content = SCRAPE_EXAMPLES_HELP_MD.to_owned(); content.push_str(&format!( - "## More information\n\n\ - If you want more information about this feature, please read the [corresponding chapter in the Rustdoc book]({}/rustdoc/scraped-examples.html).", - DOC_RUST_LANG_ORG_CHANNEL)); + "## More information\n\n\ + If you want more information about this feature, please read the [corresponding chapter in \ + the Rustdoc book]({DOC_RUST_LANG_ORG_CHANNEL}/rustdoc/scraped-examples.html)." + )); let mut ids = IdMap::default(); format!( "
    \ -

    About scraped examples

    \ -
    \ -
    {}
    ", +

    About scraped examples

    \ +
\ +
{}
", Markdown { content: &content, links: &[], @@ -415,17 +416,16 @@ fn document<'a, 'cx: 'a>( heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx> { if let Some(ref name) = item.name { - info!("Documenting {}", name); + info!("Documenting {name}"); } display_fn(move |f| { document_item_info(cx, item, parent).render_into(f).unwrap(); if parent.is_none() { - write!(f, "{}", document_full_collapsible(item, cx, heading_offset))?; + write!(f, "{}", document_full_collapsible(item, cx, heading_offset)) } else { - write!(f, "{}", document_full(item, cx, heading_offset))?; + write!(f, "{}", document_full(item, cx, heading_offset)) } - Ok(()) }) } @@ -468,12 +468,13 @@ fn document_short<'a, 'cx: 'a>( if !show_def_docs { return Ok(()); } - if let Some(s) = item.doc_value() { + let s = item.doc_value(); + if !s.is_empty() { let (mut summary_html, has_more_content) = MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content(); if has_more_content { - let link = format!(r#" Read more"#, assoc_href_attr(item, link, cx)); + let link = format!(" Read more", assoc_href_attr(item, link, cx)); if let Some(idx) = summary_html.rfind("

") { summary_html.insert_str(idx, &link); @@ -482,7 +483,7 @@ fn document_short<'a, 'cx: 'a>( } } - write!(f, "
{}
", summary_html)?; + write!(f, "
{summary_html}
")?; } Ok(()) }) @@ -511,15 +512,15 @@ fn document_full_inner<'a, 'cx: 'a>( heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { - if let Some(s) = item.collapsed_doc_value() { - debug!("Doc block: =====\n{}\n=====", s); + if let Some(s) = item.opt_doc_value() { + debug!("Doc block: =====\n{s}\n====="); if is_collapsible { write!( f, "
\ - \ + \ Expand description\ - {}
", + {}", render_markdown(cx, &s, item.links(cx), heading_offset) )?; } else { @@ -564,12 +565,10 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option, cx: &Context<'_>) let item_type = it.type_(); let href = match link { - AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)), - AssocItemLink::Anchor(None) => Some(format!("#{}.{}", item_type, name)), + AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{id}")), + AssocItemLink::Anchor(None) => Some(format!("#{item_type}.{name}")), AssocItemLink::GotoSource(did, provided_methods) => { // We're creating a link from the implementation of an associated item to its // declaration in the trait declaration. @@ -722,7 +721,7 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) }; match href(did.expect_def_id(), cx) { - Ok((url, ..)) => Some(format!("{}#{}.{}", url, item_type, name)), + Ok((url, ..)) => Some(format!("{url}#{item_type}.{name}")), // The link is broken since it points to an external crate that wasn't documented. // Do not create any link in such case. This is better than falling back to a // dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item @@ -735,37 +734,39 @@ fn assoc_href_attr(it: &clean::Item, link: AssocItemLink<'_>, cx: &Context<'_>) // In this scenario, the actual `id` of this impl item would be // `#{item_type}.{name}-{n}` for some number `n` (a disambiguator). Err(HrefError::DocumentationNotBuilt) => None, - Err(_) => Some(format!("#{}.{}", item_type, name)), + Err(_) => Some(format!("#{item_type}.{name}")), } } }; // If there is no `href` for the reason explained above, simply do not render it which is valid: // https://html.spec.whatwg.org/multipage/links.html#links-created-by-a-and-area-elements - href.map(|href| format!(" href=\"{}\"", href)).unwrap_or_default() + href.map(|href| format!(" href=\"{href}\"")).unwrap_or_default() } fn assoc_const( w: &mut Buffer, it: &clean::Item, + generics: &clean::Generics, ty: &clean::Type, default: Option<&clean::ConstantKind>, link: AssocItemLink<'_>, - extra: &str, + indent: usize, cx: &Context<'_>, ) { let tcx = cx.tcx(); write!( w, - "{extra}{vis}const {name}: {ty}", - extra = extra, + "{indent}{vis}const {name}{generics}: {ty}", + indent = " ".repeat(indent), vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), href = assoc_href_attr(it, link, cx), name = it.name.as_ref().unwrap(), + generics = generics.print(cx), ty = ty.print(cx), ); if let Some(default) = default { - write!(w, " = "); + w.write_str(" = "); // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the // hood which adds noisy underscores and a type suffix to number literals. @@ -774,6 +775,7 @@ fn assoc_const( // Find a way to print constants here without all that jazz. write!(w, "{}", Escape(&default.value(tcx).unwrap_or_else(|| default.expr(tcx)))); } + write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline)); } fn assoc_type( @@ -786,10 +788,12 @@ fn assoc_type( indent: usize, cx: &Context<'_>, ) { + let tcx = cx.tcx(); write!( w, - "{indent}type {name}{generics}", + "{indent}{vis}type {name}{generics}", indent = " ".repeat(indent), + vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), href = assoc_href_attr(it, link, cx), name = it.name.as_ref().unwrap(), generics = generics.print(cx), @@ -797,10 +801,11 @@ fn assoc_type( if !bounds.is_empty() { write!(w, ": {}", print_generic_bounds(bounds, cx)) } - write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline)); + // Render the default before the where-clause which aligns with the new recommended style. See #89122. if let Some(default) = default { write!(w, " = {}", default.print(cx)) } + write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline)); } fn assoc_method( @@ -817,6 +822,7 @@ fn assoc_method( let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item"); let name = meth.name.as_ref().unwrap(); let vis = visibility_print_with_space(meth.visibility(tcx), meth.item_id, cx).to_string(); + let defaultness = print_default_space(meth.is_default()); // FIXME: Once https://github.com/rust-lang/rust/issues/67792 is implemented, we can remove // this condition. let constness = match render_mode { @@ -827,7 +833,6 @@ fn assoc_method( }; let asyncness = header.asyncness.print_with_space(); let unsafety = header.unsafety.print_with_space(); - let defaultness = print_default_space(meth.is_default()); let abi = print_abi_with_space(header.abi).to_string(); let href = assoc_href_attr(meth, link, cx); @@ -835,15 +840,15 @@ fn assoc_method( let generics_len = format!("{:#}", g.print(cx)).len(); let mut header_len = "fn ".len() + vis.len() + + defaultness.len() + constness.len() + asyncness.len() + unsafety.len() - + defaultness.len() + abi.len() + name.as_str().len() + generics_len; - let notable_traits = d.output.as_return().and_then(|output| notable_traits_button(output, cx)); + let notable_traits = notable_traits_button(&d.output, cx); let (indent, indent_str, end_newline) = if parent == ItemType::Trait { header_len += 4; @@ -857,14 +862,14 @@ fn assoc_method( w.reserve(header_len + "{".len() + "".len()); write!( w, - "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn {name}\ - {generics}{decl}{notable_traits}{where_clause}", + "{indent}{vis}{defaultness}{constness}{asyncness}{unsafety}{abi}fn \ + {name}{generics}{decl}{notable_traits}{where_clause}", indent = indent_str, vis = vis, + defaultness = defaultness, constness = constness, asyncness = asyncness, unsafety = unsafety, - defaultness = defaultness, abi = abi, href = href, name = name, @@ -904,39 +909,41 @@ fn render_stability_since_raw_with_extra( if let Some(ver) = stable_version { stability.push_str(ver.as_str()); - title.push_str(&format!("Stable since Rust version {}", ver)); + title.push_str(&format!("Stable since Rust version {ver}")); } let const_title_and_stability = match const_stability { Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. }) if Some(since) != containing_const_ver => { - Some((format!("const since {}", since), format!("const: {}", since))) + Some((format!("const since {since}"), format!("const: {since}"))) } Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => { let unstable = if let Some(n) = issue { format!( - r#"unstable"#, - n, feature + "unstable" ) } else { String::from("unstable") }; - Some((String::from("const unstable"), format!("const: {}", unstable))) + Some((String::from("const unstable"), format!("const: {unstable}"))) } _ => None, }; if let Some((const_title, const_stability)) = const_title_and_stability { if !title.is_empty() { - title.push_str(&format!(", {}", const_title)); + title.push_str(&format!(", {const_title}")); } else { title.push_str(&const_title); } if !stability.is_empty() { - stability.push_str(&format!(" ({})", const_stability)); + stability.push_str(&format!(" ({const_stability})")); } else { stability.push_str(&const_stability); } @@ -983,19 +990,22 @@ fn render_assoc_item( clean::MethodItem(m, _) => { assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode) } - kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => assoc_const( - w, - item, - ty, - match kind { - clean::TyAssocConstItem(_) => None, - clean::AssocConstItem(_, default) => Some(default), - _ => unreachable!(), - }, - link, - if parent == ItemType::Trait { " " } else { "" }, - cx, - ), + kind @ (clean::TyAssocConstItem(generics, ty) | clean::AssocConstItem(generics, ty, _)) => { + assoc_const( + w, + item, + generics, + ty, + match kind { + clean::TyAssocConstItem(..) => None, + clean::AssocConstItem(.., default) => Some(default), + _ => unreachable!(), + }, + link, + if parent == ItemType::Trait { 4 } else { 0 }, + cx, + ) + } clean::TyAssocTypeItem(ref generics, ref bounds) => assoc_type( w, item, @@ -1029,7 +1039,7 @@ fn render_attributes_in_pre<'a, 'b: 'a>( ) -> impl fmt::Display + Captures<'a> + Captures<'b> { crate::html::format::display_fn(move |f| { for a in it.attributes(tcx, false) { - writeln!(f, "{}{}", prefix, a)?; + writeln!(f, "{prefix}{a}")?; } Ok(()) }) @@ -1037,9 +1047,9 @@ fn render_attributes_in_pre<'a, 'b: 'a>( // When an attribute is rendered inside a tag, it is formatted using // a div to produce a newline after it. -fn render_attributes_in_code(w: &mut Buffer, it: &clean::Item, tcx: TyCtxt<'_>) { - for a in it.attributes(tcx, false) { - write!(w, "
{}
", a); +fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, tcx: TyCtxt<'_>) { + for attr in it.attributes(tcx, false) { + write!(w, "
{attr}
").unwrap(); } } @@ -1082,7 +1092,7 @@ pub(crate) fn render_all_impls( let impls = impls.into_inner(); if !impls.is_empty() { write_impl_section_heading(&mut w, "Trait Implementations", "trait-implementations"); - write!(w, "
{}
", impls).unwrap(); + write!(w, "
{impls}
").unwrap(); } if !synthetic.is_empty() { @@ -1141,9 +1151,7 @@ fn render_assoc_items_inner( AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { let id = cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx)))); - if let Some(def_id) = type_.def_id(cx.cache()) { - cx.deref_id_map.insert(def_id, id.clone()); - } + let derived_id = cx.derive_id(&id); write_impl_section_heading( &mut tmp_buf, &format!( @@ -1153,11 +1161,10 @@ fn render_assoc_items_inner( ), &id, ); - ( - RenderMode::ForDeref { mut_: deref_mut_ }, - cx.derive_id(id), - r#" class="impl-items""#, - ) + if let Some(def_id) = type_.def_id(cx.cache()) { + cx.deref_id_map.insert(def_id, id); + } + (RenderMode::ForDeref { mut_: deref_mut_ }, derived_id, r#" class="impl-items""#) } }; let mut impls_buf = Buffer::html(); @@ -1180,10 +1187,13 @@ fn render_assoc_items_inner( ); } if !impls_buf.is_empty() { - write!(w, "{}", tmp_buf.into_inner()).unwrap(); - write!(w, "
").unwrap(); - write!(w, "{}", impls_buf.into_inner()).unwrap(); - w.write_str("
").unwrap(); + write!( + w, + "{}
{}
", + tmp_buf.into_inner(), + impls_buf.into_inner() + ) + .unwrap(); } } @@ -1233,7 +1243,10 @@ fn render_deref_methods( _ => None, }) .expect("Expected associated type binding"); - debug!("Render deref methods for {:#?}, target {:#?}", impl_.inner_impl().for_, target); + debug!( + "Render deref methods for {for_:#?}, target {target:#?}", + for_ = impl_.inner_impl().for_ + ); let what = AssocItemRender::DerefFor { trait_: deref_type, type_: real_target, deref_mut_: deref_mut }; if let Some(did) = target.def_id(cache) { @@ -1281,6 +1294,11 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> Option { let mut has_notable_trait = false; + if ty.is_unit() { + // Very common fast path. + return None; + } + let did = ty.def_id(cx.cache())?; // Box has pass-through impls for Read, Write, Iterator, and Future when the @@ -1378,7 +1396,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { } } if out.is_empty() { - write!(&mut out, "
",); + out.write_str(""); } (format!("{:#}", ty.print(cx)), out.into_inner()) @@ -1476,7 +1494,7 @@ fn render_impl( if let Some(it) = t.items.iter().find(|i| i.name == item.name) { // We need the stability of the item from the trait // because impls can't have a stability. - if item.doc_value().is_some() { + if !item.doc_value().is_empty() { document_item_info(cx, it, Some(parent)) .render_into(&mut info_buffer) .unwrap(); @@ -1524,25 +1542,25 @@ fn render_impl( let toggled = !doc_buffer.is_empty(); if toggled { let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; - write!(w, "
", method_toggle_class); + write!(w, "
"); } match &*item.kind { clean::MethodItem(..) | clean::TyMethodItem(_) => { // Only render when the method is not static or we allow static methods if render_method_item { - let id = cx.derive_id(format!("{}.{}", item_type, name)); + let id = cx.derive_id(format!("{item_type}.{name}")); let source_id = trait_ .and_then(|trait_| { trait_.items.iter().find(|item| { item.name.map(|n| n.as_str().eq(name.as_str())).unwrap_or(false) }) }) - .map(|item| format!("{}.{}", item.type_(), name)); - write!(w, "
", id, item_type, in_trait_class,); + .map(|item| format!("{}.{name}", item.type_())); + write!(w, "
"); render_rightside(w, cx, item, containing_item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "§", id); + write!(w, "§"); } w.write_str("

"); render_assoc_item( @@ -1553,43 +1571,43 @@ fn render_impl( cx, render_mode, ); - w.write_str("

"); - w.write_str("
"); + w.write_str("
"); } } - kind @ (clean::TyAssocConstItem(ty) | clean::AssocConstItem(ty, _)) => { - let source_id = format!("{}.{}", item_type, name); - let id = cx.derive_id(source_id.clone()); - write!(w, "
", id, item_type, in_trait_class); + kind @ (clean::TyAssocConstItem(generics, ty) + | clean::AssocConstItem(generics, ty, _)) => { + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); + write!(w, "
"); render_rightside(w, cx, item, containing_item, render_mode); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "§", id); + write!(w, "§"); } w.write_str("

"); assoc_const( w, item, + generics, ty, match kind { - clean::TyAssocConstItem(_) => None, - clean::AssocConstItem(_, default) => Some(default), + clean::TyAssocConstItem(..) => None, + clean::AssocConstItem(.., default) => Some(default), _ => unreachable!(), }, link.anchor(if trait_.is_some() { &source_id } else { &id }), - "", + 0, cx, ); - w.write_str("

"); - w.write_str("
"); + w.write_str("
"); } clean::TyAssocTypeItem(generics, bounds) => { - let source_id = format!("{}.{}", item_type, name); - let id = cx.derive_id(source_id.clone()); - write!(w, "
", id, item_type, in_trait_class); + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); + write!(w, "
"); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "§", id); + write!(w, "§"); } w.write_str("

"); assoc_type( @@ -1602,16 +1620,15 @@ fn render_impl( 0, cx, ); - w.write_str("

"); - w.write_str("
"); + w.write_str("
"); } clean::AssocTypeItem(tydef, _bounds) => { - let source_id = format!("{}.{}", item_type, name); - let id = cx.derive_id(source_id.clone()); - write!(w, "
", id, item_type, in_trait_class); + let source_id = format!("{item_type}.{name}"); + let id = cx.derive_id(&source_id); + write!(w, "
"); if trait_.is_some() { // Anchors are only used on trait impls. - write!(w, "§", id); + write!(w, "§"); } w.write_str("

"); assoc_type( @@ -1624,8 +1641,7 @@ fn render_impl( 0, cx, ); - w.write_str("

"); - w.write_str("
"); + w.write_str("
"); } clean::StrippedItem(..) => return, _ => panic!("can't make docs for trait item with name {:?}", item.name), @@ -1670,11 +1686,11 @@ fn render_impl( rendering_params: ImplRenderingParameters, ) { for trait_item in &t.items { - // Skip over any default trait items that are impossible to call + // Skip over any default trait items that are impossible to reference // (e.g. if it has a `Self: Sized` bound on an unsized type). if let Some(impl_def_id) = parent.item_id.as_def_id() && let Some(trait_item_def_id) = trait_item.item_id.as_def_id() - && cx.tcx().is_impossible_method((impl_def_id, trait_item_def_id)) + && cx.tcx().is_impossible_associated_item((impl_def_id, trait_item_def_id)) { continue; } @@ -1728,10 +1744,10 @@ fn render_impl( close_tags.insert_str(0, "
"); write!( w, - "
", + "
\ + ", if rendering_params.toggle_open_by_default { " open" } else { "" } ); - write!(w, "") } render_impl_summary( w, @@ -1744,15 +1760,15 @@ fn render_impl( aliases, ); if toggled { - write!(w, "") + w.write_str(""); } - if let Some(ref dox) = i.impl_item.collapsed_doc_value() { + if let Some(ref dox) = i.impl_item.opt_doc_value() { if trait_.is_none() && i.inner_impl().items.is_empty() { w.write_str( "
\ -
This impl block contains no items.
\ -
", +
This impl block contains no items.
\ + ", ); } write!( @@ -1811,11 +1827,11 @@ fn render_rightside( const_stable_since, if has_src_ref { "" } else { " rightside" }, ); - if let Some(l) = src_href { + if let Some(link) = src_href { if has_stability { - write!(rightside, " · source", l) + write!(rightside, " · source") } else { - write!(rightside, "source", l) + write!(rightside, "source") } } if has_stability && has_src_ref { @@ -1844,10 +1860,13 @@ pub(crate) fn render_impl_summary( } else { format!(" data-aliases=\"{}\"", aliases.join(",")) }; - write!(w, "
", id, aliases); + write!(w, "
"); render_rightside(w, cx, &i.impl_item, containing_item, RenderMode::Normal); - write!(w, "§", id); - write!(w, "

"); + write!( + w, + "§\ +

" + ); if let Some(use_absolute) = use_absolute { write!(w, "{}", inner_impl.print(use_absolute, cx)); @@ -1872,15 +1891,16 @@ pub(crate) fn render_impl_summary( } else { write!(w, "{}", inner_impl.print(false, cx)); } - write!(w, "

"); + w.write_str(""); let is_trait = inner_impl.trait_.is_some(); if is_trait { if let Some(portability) = portability(&i.impl_item, Some(parent)) { write!( w, - "
{}
", - portability + "\ +
{portability}
\ +
", ); } } @@ -1932,10 +1952,8 @@ pub(crate) fn small_url_encode(s: String) -> String { // While the same is not true for hashes, rustdoc only needs to be // consistent with itself when encoding them. st += "+"; - } else if b == b'%' { - st += "%%"; } else { - write!(st, "%{:02X}", b).unwrap(); + write!(st, "%{b:02X}").unwrap(); } // Invariant: if the current byte is not at the start of a multi-byte character, // we need to get down here so that when the next turn of the loop comes around, @@ -1982,7 +2000,9 @@ pub(crate) fn get_filtered_impls_for_reference<'a>( ) -> (Vec<&'a Impl>, Vec<&'a Impl>, Vec<&'a Impl>) { let def_id = it.item_id.expect_def_id(); // If the reference primitive is somehow not defined, exit early. - let Some(v) = shared.cache.impls.get(&def_id) else { return (Vec::new(), Vec::new(), Vec::new()) }; + let Some(v) = shared.cache.impls.get(&def_id) else { + return (Vec::new(), Vec::new(), Vec::new()); + }; // Since there is no "direct implementation" on the reference primitive type, we filter out // every implementation which isn't a trait implementation. let traits = v.iter().filter(|i| i.inner_impl().trait_.is_some()); @@ -2248,7 +2268,7 @@ fn render_call_locations(mut w: W, cx: &mut Context<'_>, item: &c format!("lines {}-{}", line_lo + 1, line_hi + 1), ) }; - let url = format!("{}{}#{}", cx.root_path(), call_data.url, anchor); + let url = format!("{}{}#{anchor}", cx.root_path(), call_data.url); (url, title) }; @@ -2258,7 +2278,7 @@ fn render_call_locations(mut w: W, cx: &mut Context<'_>, item: &c Ok(contents) => contents, Err(err) => { let span = item.span(tcx).map_or(rustc_span::DUMMY_SP, |span| span.inner()); - tcx.sess.span_err(span, format!("failed to read file {}: {}", path.display(), err)); + tcx.sess.span_err(span, format!("failed to read file {}: {err}", path.display())); return false; } }; @@ -2317,7 +2337,7 @@ fn render_call_locations(mut w: W, cx: &mut Context<'_>, item: &c .unwrap(); if line_ranges.len() > 1 { - write!(w, r#" "#) + w.write_str(r#" "#) .unwrap(); } @@ -2353,7 +2373,7 @@ fn render_call_locations(mut w: W, cx: &mut Context<'_>, item: &c highlight::DecorationInfo(decoration_info), sources::SourceContext::Embedded { offset: line_min, needs_expansion }, ); - write!(w, "").unwrap(); + w.write_str("").unwrap(); true }; @@ -2420,8 +2440,10 @@ fn render_call_locations(mut w: W, cx: &mut Context<'_>, item: &c // For the remaining examples, generate a
    containing links to the source files. if it.peek().is_some() { - write!(w, r#"").unwrap(); } - write!(w, "
").unwrap(); + w.write_str("
").unwrap(); } - write!(w, "").unwrap(); + w.write_str("").unwrap(); } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 4cc81e860f09a..6cab349862233 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -9,6 +9,7 @@ use rustc_middle::middle::stability; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Symbol}; +use std::cell::{RefCell, RefMut}; use std::cmp::Ordering; use std::fmt; use std::rc::Rc; @@ -38,6 +39,110 @@ use crate::html::{highlight, static_files}; use askama::Template; use itertools::Itertools; +/// Generates an Askama template struct for rendering items with common methods. +/// +/// Usage: +/// ```ignore (illustrative) +/// item_template!( +/// #[template(path = "", /* additional values */)] +/// /* additional meta items */ +/// struct MyItem<'a, 'cx> { +/// cx: RefCell<&'a mut Context<'cx>>, +/// it: &'a clean::Item, +/// /* additional fields */ +/// }, +/// methods = [ /* method names (comma separated; refer to macro definition of `item_template_methods!()`) */ ] +/// ) +/// ``` +/// +/// NOTE: ensure that the generic lifetimes (`'a`, `'cx`) and +/// required fields (`cx`, `it`) are identical (in terms of order and definition). +macro_rules! item_template { + ( + $(#[$meta:meta])* + struct $name:ident<'a, 'cx> { + cx: RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + $($field_name:ident: $field_ty:ty),*, + }, + methods = [$($methods:tt),* $(,)?] + ) => { + #[derive(Template)] + $(#[$meta])* + struct $name<'a, 'cx> { + cx: RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + $($field_name: $field_ty),* + } + + impl<'a, 'cx: 'a> ItemTemplate<'a, 'cx> for $name<'a, 'cx> { + fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>) { + (&self.it, self.cx.borrow_mut()) + } + } + + impl<'a, 'cx: 'a> $name<'a, 'cx> { + item_template_methods!($($methods)*); + } + }; +} + +/// Implement common methods for item template structs generated by `item_template!()`. +/// +/// NOTE: this macro is intended to be used only by `item_template!()`. +macro_rules! item_template_methods { + () => {}; + (document $($rest:tt)*) => { + fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, mut cx) = self.item_and_mut_cx(); + let v = document(*cx, item, None, HeadingOffset::H2); + write!(f, "{v}") + }) + } + item_template_methods!($($rest)*); + }; + (document_type_layout $($rest:tt)*) => { + fn document_type_layout<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, cx) = self.item_and_mut_cx(); + let def_id = item.item_id.expect_def_id(); + let v = document_type_layout(*cx, def_id); + write!(f, "{v}") + }) + } + item_template_methods!($($rest)*); + }; + (render_attributes_in_pre $($rest:tt)*) => { + fn render_attributes_in_pre<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, cx) = self.item_and_mut_cx(); + let tcx = cx.tcx(); + let v = render_attributes_in_pre(item, "", tcx); + write!(f, "{v}") + }) + } + item_template_methods!($($rest)*); + }; + (render_assoc_items $($rest:tt)*) => { + fn render_assoc_items<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { + display_fn(move |f| { + let (item, mut cx) = self.item_and_mut_cx(); + let def_id = item.item_id.expect_def_id(); + let v = render_assoc_items(*cx, item, def_id, AssocItemRender::All); + write!(f, "{v}") + }) + } + item_template_methods!($($rest)*); + }; + ($method:ident $($rest:tt)*) => { + compile_error!(concat!("unknown method: ", stringify!($method))); + }; + ($token:tt $($rest:tt)*) => { + compile_error!(concat!("unexpected token: ", stringify!($token))); + }; +} + const ITEM_TABLE_OPEN: &str = "
    "; const ITEM_TABLE_CLOSE: &str = "
"; const ITEM_TABLE_ROW_OPEN: &str = "
  • "; @@ -205,9 +310,8 @@ fn toggle_open(mut w: impl fmt::Write, text: impl fmt::Display) { w, "
    \ \ - Show {}\ + Show {text}\ ", - text ) .unwrap(); } @@ -216,6 +320,10 @@ fn toggle_close(mut w: impl fmt::Write) { w.write_str("
    ").unwrap(); } +trait ItemTemplate<'a, 'cx: 'a>: askama::Template + fmt::Display { + fn item_and_mut_cx(&self) -> (&'a clean::Item, RefMut<'_, &'a mut Context<'cx>>); +} + fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) { write!(w, "{}", document(cx, item, None, HeadingOffset::H2)); @@ -303,7 +411,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: ) }); - debug!("{:?}", indices); + debug!("{indices:?}"); let mut last_section = None; for &idx in &indices { @@ -322,9 +430,8 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: w, "

    \ {name}\ -

    {}", - ITEM_TABLE_OPEN, - id = cx.derive_id(my_section.id().to_owned()), + {ITEM_TABLE_OPEN}", + id = cx.derive_id(my_section.id()), name = my_section.name(), ); } @@ -356,18 +463,18 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: clean::ImportItem(ref import) => { let stab_tags = if let Some(import_def_id) = import.source.did { - let ast_attrs = cx.tcx().get_attrs_unchecked(import_def_id); + let ast_attrs = tcx.get_attrs_unchecked(import_def_id); let import_attrs = Box::new(clean::Attributes::from_ast(ast_attrs)); // Just need an item with the correct def_id and attrs let import_item = clean::Item { item_id: import_def_id.into(), attrs: import_attrs, - cfg: ast_attrs.cfg(cx.tcx(), &cx.cache().hidden_cfg), + cfg: ast_attrs.cfg(tcx, &cx.cache().hidden_cfg), ..myitem.clone() }; - let stab_tags = Some(extra_info_tags(&import_item, item, cx.tcx()).to_string()); + let stab_tags = Some(extra_info_tags(&import_item, item, tcx).to_string()); stab_tags } else { None @@ -376,7 +483,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: w.write_str(ITEM_TABLE_ROW_OPEN); let id = match import.kind { clean::ImportKind::Simple(s) => { - format!(" id=\"{}\"", cx.derive_id(format!("reexport.{}", s))) + format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) } clean::ImportKind::Glob => String::new(), }; @@ -405,8 +512,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: let unsafety_flag = match *myitem.kind { clean::FunctionItem(_) | clean::ForeignFunctionItem(_) - if myitem.fn_header(cx.tcx()).unwrap().unsafety - == hir::Unsafety::Unsafe => + if myitem.fn_header(tcx).unwrap().unsafety == hir::Unsafety::Unsafe => { "" } @@ -420,9 +526,9 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: _ => "", }; - let doc_value = myitem.doc_value().unwrap_or_default(); w.write_str(ITEM_TABLE_ROW_OPEN); - let docs = MarkdownSummaryLine(&doc_value, &myitem.links(cx)).into_string(); + let docs = + MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); let (docs_before, docs_after) = if docs.is_empty() { ("", "") } else { @@ -439,7 +545,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: {docs_before}{docs}{docs_after}", name = myitem.name.unwrap(), visibility_emoji = visibility_emoji, - stab_tags = extra_info_tags(myitem, item, cx.tcx()), + stab_tags = extra_info_tags(myitem, item, tcx), class = myitem.type_(), unsafety_flag = unsafety_flag, href = item_path(myitem.type_(), myitem.name.unwrap().as_str()), @@ -475,10 +581,8 @@ fn extra_info_tags<'a, 'tcx: 'a>( display_fn(move |f| { write!( f, - r#"{}"#, - class, - Escape(title), - contents + r#"{contents}"#, + title = Escape(title), ) }) } @@ -506,7 +610,12 @@ fn extra_info_tags<'a, 'tcx: 'a>( (cfg, _) => cfg.as_deref().cloned(), }; - debug!("Portability name={:?} {:?} - {:?} = {:?}", item.name, item.cfg, parent.cfg, cfg); + debug!( + "Portability name={name:?} {cfg:?} - {parent_cfg:?} = {cfg:?}", + name = item.name, + cfg = item.cfg, + parent_cfg = parent.cfg + ); if let Some(ref cfg) = cfg { write!( f, @@ -539,8 +648,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle + name.as_str().len() + generics_len; - let notable_traits = - f.decl.output.as_return().and_then(|output| notable_traits_button(output, cx)); + let notable_traits = notable_traits_button(&f.decl.output, cx); wrap_item(w, |w| { w.reserve(header_len); @@ -582,14 +690,13 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: wrap_item(w, |mut w| { write!( w, - "{attrs}{}{}{}trait {}{}{}", - visibility_print_with_space(it.visibility(tcx), it.item_id, cx), - t.unsafety(tcx).print_with_space(), - if t.is_auto(tcx) { "auto " } else { "" }, - it.name.unwrap(), - t.generics.print(cx), - bounds, + "{attrs}{vis}{unsafety}{is_auto}trait {name}{generics}{bounds}", attrs = render_attributes_in_pre(it, "", tcx), + vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), + unsafety = t.unsafety(tcx).print_with_space(), + is_auto = if t.is_auto(tcx) { "auto " } else { "" }, + name = it.name.unwrap(), + generics = t.generics.print(cx), ); if !t.generics.where_predicates.is_empty() { @@ -635,11 +742,10 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: toggle_open( &mut w, format_args!( - "{} associated constant{} and {} method{}", - count_consts, - pluralize(count_consts), - count_methods, - pluralize(count_methods), + "{count_consts} associated constant{plural_const} and \ + {count_methods} method{plural_method}", + plural_const = pluralize(count_consts), + plural_method = pluralize(count_methods), ), ); } @@ -661,7 +767,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } if !toggle && should_hide_fields(count_methods) { toggle = true; - toggle_open(&mut w, format_args!("{} methods", count_methods)); + toggle_open(&mut w, format_args!("{count_methods} methods")); } if count_consts != 0 && count_methods != 0 { w.write_str("\n"); @@ -730,9 +836,9 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::Item) { let name = m.name.unwrap(); - info!("Documenting {} on {:?}", name, t.name); + info!("Documenting {name} on {ty_name:?}", ty_name = t.name); let item_type = m.type_(); - let id = cx.derive_id(format!("{}.{}", item_type, name)); + let id = cx.derive_id(format!("{item_type}.{name}")); let mut content = Buffer::empty_from(w); write!(&mut content, "{}", document(cx, m, Some(t), HeadingOffset::H5)); let toggled = !content.is_empty(); @@ -740,7 +846,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" }; write!(w, "
    "); } - write!(w, "
    ", id); + write!(w, "
    "); render_rightside(w, cx, m, t, RenderMode::Normal); write!(w, "

    "); render_assoc_item( @@ -886,7 +992,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: write_small_section_header(w, "foreign-impls", "Implementations on Foreign Types", ""); for implementor in foreign { - let provided_methods = implementor.inner_impl().provided_trait_methods(cx.tcx()); + let provided_methods = implementor.inner_impl().provided_trait_methods(tcx); let assoc_link = AssocItemLink::GotoSource(implementor.impl_item.item_id, &provided_methods); render_impl( @@ -919,7 +1025,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: } w.write_str(""); - if t.is_auto(cx.tcx()) { + if t.is_auto(tcx) { write_small_section_header( w, "synthetic-implementors", @@ -948,7 +1054,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: "
    ", ); - if t.is_auto(cx.tcx()) { + if t.is_auto(tcx) { write_small_section_header( w, "synthetic-implementors", @@ -1054,48 +1160,61 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: ); } -fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TraitAlias) { +fn item_trait_alias( + w: &mut impl fmt::Write, + cx: &mut Context<'_>, + it: &clean::Item, + t: &clean::TraitAlias, +) { wrap_item(w, |w| { write!( w, - "{attrs}trait {}{}{} = {};", - it.name.unwrap(), - t.generics.print(cx), - print_where_clause(&t.generics, cx, 0, Ending::Newline), - bounds(&t.bounds, true, cx), + "{attrs}trait {name}{generics}{where_b} = {bounds};", attrs = render_attributes_in_pre(it, "", cx.tcx()), - ); + name = it.name.unwrap(), + generics = t.generics.print(cx), + where_b = print_where_clause(&t.generics, cx, 0, Ending::Newline), + bounds = bounds(&t.bounds, true, cx), + ) + .unwrap(); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); - + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); // Render any items associated directly to this alias, as otherwise they // won't be visible anywhere in the docs. It would be nice to also show // associated items from the aliased type (see discussion in #32077), but // we need #14072 to make sense of the generics. write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) + .unwrap(); } -fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::OpaqueTy) { +fn item_opaque_ty( + w: &mut impl fmt::Write, + cx: &mut Context<'_>, + it: &clean::Item, + t: &clean::OpaqueTy, +) { wrap_item(w, |w| { write!( w, - "{attrs}type {}{}{where_clause} = impl {bounds};", - it.name.unwrap(), - t.generics.print(cx), + "{attrs}type {name}{generics}{where_clause} = impl {bounds};", + attrs = render_attributes_in_pre(it, "", cx.tcx()), + name = it.name.unwrap(), + generics = t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), bounds = bounds(&t.bounds, false, cx), - attrs = render_attributes_in_pre(it, "", cx.tcx()), - ); + ) + .unwrap(); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); // Render any items associated directly to this alias, as otherwise they // won't be visible anywhere in the docs. It would be nice to also show // associated items from the aliased type (see discussion in #32077), but // we need #14072 to make sense of the generics. write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) + .unwrap(); } fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Typedef) { @@ -1103,13 +1222,13 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea wrap_item(w, |w| { write!( w, - "{attrs}{}type {}{}{where_clause} = {type_};", - visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), - it.name.unwrap(), - t.generics.print(cx), + "{attrs}{vis}type {name}{generics}{where_clause} = {type_};", + attrs = render_attributes_in_pre(it, "", cx.tcx()), + vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), + name = it.name.unwrap(), + generics = t.generics.print(cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), type_ = t.type_.print(cx), - attrs = render_attributes_in_pre(it, "", cx.tcx()), ); }); } @@ -1128,35 +1247,17 @@ fn item_typedef(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clea } fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Union) { - #[derive(Template)] - #[template(path = "item_union.html")] - struct ItemUnion<'a, 'cx> { - cx: std::cell::RefCell<&'a mut Context<'cx>>, - it: &'a clean::Item, - s: &'a clean::Union, - } + item_template!( + #[template(path = "item_union.html")] + struct ItemUnion<'a, 'cx> { + cx: RefCell<&'a mut Context<'cx>>, + it: &'a clean::Item, + s: &'a clean::Union, + }, + methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items] + ); impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { - fn render_assoc_items<'b>( - &'b self, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let def_id = self.it.item_id.expect_def_id(); - let mut cx = self.cx.borrow_mut(); - let v = render_assoc_items(*cx, self.it, def_id, AssocItemRender::All); - write!(f, "{v}") - }) - } - fn document_type_layout<'b>( - &'b self, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let def_id = self.it.item_id.expect_def_id(); - let cx = self.cx.borrow_mut(); - let v = document_type_layout(*cx, def_id); - write!(f, "{v}") - }) - } fn render_union<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { display_fn(move |f| { let cx = self.cx.borrow_mut(); @@ -1164,22 +1265,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: write!(f, "{v}") }) } - fn render_attributes_in_pre<'b>( - &'b self, - ) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let tcx = self.cx.borrow().tcx(); - let v = render_attributes_in_pre(self.it, "", tcx); - write!(f, "{v}") - }) - } - fn document<'b>(&'b self) -> impl fmt::Display + Captures<'a> + 'b + Captures<'cx> { - display_fn(move |f| { - let mut cx = self.cx.borrow_mut(); - let v = document(*cx, self.it, None, HeadingOffset::H2); - write!(f, "{v}") - }) - } + fn document_field<'b>( &'b self, field: &'a clean::Item, @@ -1190,10 +1276,12 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: write!(f, "{v}") }) } + fn stability_field(&self, field: &clean::Item) -> Option { let cx = self.cx.borrow(); field.stability_class(cx.tcx()) } + fn print_ty<'b>( &'b self, ty: &'a clean::Type, @@ -1219,7 +1307,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean: } } - ItemUnion { cx: std::cell::RefCell::new(cx), it, s }.render_into(w).unwrap(); + ItemUnion { cx: RefCell::new(cx), it, s }.render_into(w).unwrap(); } fn print_tuple_struct_fields<'a, 'cx: 'a>( @@ -1265,7 +1353,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: w.write_str("{\n"); let toggle = should_hide_fields(count_variants); if toggle { - toggle_open(&mut w, format_args!("{} variants", count_variants)); + toggle_open(&mut w, format_args!("{count_variants} variants")); } for v in e.variants() { w.write_str(" "); @@ -1273,7 +1361,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: match *v.kind { // FIXME(#101337): Show discriminant clean::VariantItem(ref var) => match var.kind { - clean::VariantKind::CLike => write!(w, "{}", name), + clean::VariantKind::CLike => w.write_str(name.as_str()), clean::VariantKind::Tuple(ref s) => { write!(w, "{name}({})", print_tuple_struct_fields(cx, s),); } @@ -1329,7 +1417,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() }; if let clean::VariantKind::Tuple(ref s) = variant_data.kind { - write!(w, "({})", print_tuple_struct_fields(cx, s),); + write!(w, "({})", print_tuple_struct_fields(cx, s)); } w.write_str("

    "); @@ -1338,7 +1426,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean:: clean::VariantKind::Tuple(fields) => { // Documentation on tuple variant fields is rare, so to reduce noise we only emit // the section if at least one field is documented. - if fields.iter().any(|f| f.doc_value().is_some()) { + if fields.iter().any(|f| !f.doc_value().is_empty()) { Some(("Tuple Fields", fields)) } else { None @@ -1402,37 +1490,41 @@ fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) } -fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) { - wrap_item(w, |w| { +fn item_proc_macro( + w: &mut impl fmt::Write, + cx: &mut Context<'_>, + it: &clean::Item, + m: &clean::ProcMacro, +) { + wrap_item(w, |buffer| { let name = it.name.expect("proc-macros always have names"); match m.kind { MacroKind::Bang => { - write!(w, "{}!() {{ /* proc-macro */ }}", name); + write!(buffer, "{name}!() {{ /* proc-macro */ }}").unwrap(); } MacroKind::Attr => { - write!(w, "#[{}]", name); + write!(buffer, "#[{name}]").unwrap(); } MacroKind::Derive => { - write!(w, "#[derive({})]", name); + write!(buffer, "#[derive({name})]").unwrap(); if !m.helpers.is_empty() { - w.push_str("\n{\n"); - w.push_str(" // Attributes available to this derive:\n"); + buffer.write_str("\n{\n // Attributes available to this derive:\n").unwrap(); for attr in &m.helpers { - writeln!(w, " #[{}]", attr); + writeln!(buffer, " #[{attr}]").unwrap(); } - w.push_str("}\n"); + buffer.write_str("}\n").unwrap(); } } } }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); } -fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { +fn item_primitive(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) { let def_id = it.item_id.expect_def_id(); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)); + write!(w, "{}", render_assoc_items(cx, it, def_id, AssocItemRender::All)).unwrap(); } else { // We handle the "reference" primitive type on its own because we only want to list // implementations on generic types. @@ -1450,10 +1542,12 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle write!( w, - "{vis}const {name}: {typ}", + "{vis}const {name}{generics}: {typ}{where_clause}", vis = visibility_print_with_space(it.visibility(tcx), it.item_id, cx), name = it.name.unwrap(), + generics = c.generics.print(cx), typ = c.type_.print(cx), + where_clause = print_where_clause(&c.generics, cx, 0, Ending::NoNewline), ); // FIXME: The code below now prints @@ -1522,7 +1616,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean for (index, (field, ty)) in fields.enumerate() { let field_name = field.name.map_or_else(|| index.to_string(), |sym| sym.as_str().to_string()); - let id = cx.derive_id(format!("{}.{}", ItemType::StructField, field_name)); + let id = cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField)); write!( w, "\ @@ -1541,36 +1635,39 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean write!(w, "{}", document_type_layout(cx, def_id)); } -fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { - wrap_item(w, |w| { - render_attributes_in_code(w, it, cx.tcx()); +fn item_static(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Static) { + wrap_item(w, |buffer| { + render_attributes_in_code(buffer, it, cx.tcx()); write!( - w, + buffer, "{vis}static {mutability}{name}: {typ}", vis = visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), mutability = s.mutability.print_with_space(), name = it.name.unwrap(), typ = s.type_.print(cx) - ); + ) + .unwrap(); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) + + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); } -fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { - wrap_item(w, |w| { - w.write_str("extern {\n"); - render_attributes_in_code(w, it, cx.tcx()); +fn item_foreign_type(w: &mut impl fmt::Write, cx: &mut Context<'_>, it: &clean::Item) { + wrap_item(w, |buffer| { + buffer.write_str("extern {\n").unwrap(); + render_attributes_in_code(buffer, it, cx.tcx()); write!( - w, + buffer, " {}type {};\n}}", visibility_print_with_space(it.visibility(cx.tcx()), it.item_id, cx), it.name.unwrap(), - ); + ) + .unwrap(); }); - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)); - + write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) + .unwrap(); } fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) { @@ -1624,7 +1721,7 @@ pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { pub(super) fn item_path(ty: ItemType, name: &str) -> String { match ty { ItemType::Module => format!("{}index.html", ensure_trailing_slash(name)), - _ => format!("{}.{}.html", ty, name), + _ => format!("{ty}.{name}.html"), } } @@ -1644,13 +1741,14 @@ fn bounds(t_bounds: &[clean::GenericBound], trait_alias: bool, cx: &Context<'_>) bounds } -fn wrap_item(w: &mut Buffer, f: F) +fn wrap_item(w: &mut W, f: F) where - F: FnOnce(&mut Buffer), + W: fmt::Write, + F: FnOnce(&mut W), { - w.write_str(r#"
    "#);
    +    write!(w, r#"
    "#).unwrap();
         f(w);
    -    w.write_str("
    "); + write!(w, "
    ").unwrap(); } #[derive(PartialEq, Eq)] @@ -1746,7 +1844,7 @@ fn render_union<'a, 'cx: 'a>( fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count(); let toggle = should_hide_fields(count_fields); if toggle { - toggle_open(&mut f, format_args!("{} fields", count_fields)); + toggle_open(&mut f, format_args!("{count_fields} fields")); } for field in fields { @@ -1809,26 +1907,25 @@ fn render_struct( let has_visible_fields = count_fields > 0; let toggle = should_hide_fields(count_fields); if toggle { - toggle_open(&mut w, format_args!("{} fields", count_fields)); + toggle_open(&mut w, format_args!("{count_fields} fields")); } for field in fields { if let clean::StructFieldItem(ref ty) = *field.kind { write!( w, - "\n{} {}{}: {},", - tab, - visibility_print_with_space(field.visibility(tcx), field.item_id, cx), - field.name.unwrap(), - ty.print(cx), + "\n{tab} {vis}{name}: {ty},", + vis = visibility_print_with_space(field.visibility(tcx), field.item_id, cx), + name = field.name.unwrap(), + ty = ty.print(cx), ); } } if has_visible_fields { if it.has_stripped_entries().unwrap() { - write!(w, "\n{} /* private fields */", tab); + write!(w, "\n{tab} /* private fields */"); } - write!(w, "\n{}", tab); + write!(w, "\n{tab}"); } else if it.has_stripped_entries().unwrap() { write!(w, " /* private fields */ "); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index a3be6dd526909..f34be120d292b 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -7,7 +7,7 @@ use rustc_span::symbol::Symbol; use serde::ser::{Serialize, SerializeStruct, Serializer}; use crate::clean; -use crate::clean::types::{FnRetTy, Function, Generics, ItemId, Type, WherePredicate}; +use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate}; use crate::formats::cache::{Cache, OrphanImplItem}; use crate::formats::item_type::ItemType; use crate::html::format::join_with_double_colon; @@ -28,9 +28,7 @@ pub(crate) fn build_index<'tcx>( // has since been learned. for &OrphanImplItem { parent, ref item, ref impl_generics } in &cache.orphan_impl_items { if let Some((fqp, _)) = cache.paths.get(&parent) { - let desc = item - .doc_value() - .map_or_else(String::new, |s| short_markdown_summary(&s, &item.link_names(cache))); + let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache)); cache.search_index.push(IndexItem { ty: item.type_(), name: item.name.unwrap(), @@ -45,10 +43,8 @@ pub(crate) fn build_index<'tcx>( } } - let crate_doc = krate - .module - .doc_value() - .map_or_else(String::new, |s| short_markdown_summary(&s, &krate.module.link_names(cache))); + let crate_doc = + short_markdown_summary(&krate.module.doc_value(), &krate.module.link_names(cache)); // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias, // we need the alias element to have an array of items. @@ -660,22 +656,9 @@ fn get_fn_inputs_and_outputs<'tcx>( } let mut ret_types = Vec::new(); - match decl.output { - FnRetTy::Return(ref return_type) => { - add_generics_and_bounds_as_types( - self_, - generics, - return_type, - tcx, - 0, - &mut ret_types, - cache, - ); - if ret_types.is_empty() { - ret_types.push(get_index_type(return_type, vec![])); - } - } - _ => {} - }; + add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache); + if ret_types.is_empty() { + ret_types.push(get_index_type(&decl.output, vec![])); + } (all_types, ret_types) } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 455b4e9aefe55..f3da610565c44 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -330,7 +330,7 @@ fn sidebar_deref_methods<'a>( ) { let c = cx.cache(); - debug!("found Deref: {:?}", impl_); + debug!("found Deref: {impl_:?}"); if let Some((target, real_target)) = impl_.inner_impl().items.iter().find_map(|item| match *item.kind { clean::AssocTypeItem(box ref t, _) => Some(match *t { @@ -340,7 +340,7 @@ fn sidebar_deref_methods<'a>( _ => None, }) { - debug!("found target, real_target: {:?} {:?}", target, real_target); + debug!("found target, real_target: {target:?} {real_target:?}"); if let Some(did) = target.def_id(c) && let Some(type_did) = impl_.inner_impl().for_.def_id(c) && // `impl Deref for S` @@ -357,7 +357,7 @@ fn sidebar_deref_methods<'a>( }) .and_then(|did| c.impls.get(&did)); if let Some(impls) = inner_impl { - debug!("found inner_impl: {:?}", impls); + debug!("found inner_impl: {impls:?}"); let mut ret = impls .iter() .filter(|i| i.inner_impl().trait_.is_none()) @@ -510,10 +510,10 @@ fn get_next_url(used_links: &mut FxHashSet, url: String) -> String { return url; } let mut add = 1; - while !used_links.insert(format!("{}-{}", url, add)) { + while !used_links.insert(format!("{url}-{add}")) { add += 1; } - format!("{}-{}", url, add) + format!("{url}-{add}") } fn get_methods<'a>( @@ -529,7 +529,7 @@ fn get_methods<'a>( Some(ref name) if !name.is_empty() && item.is_method() => { if !for_deref || super::should_render_item(item, deref_mut, tcx) { Some(Link::new( - get_next_url(used_links, format!("{}.{}", ItemType::Method, name)), + get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::Method)), name.as_str(), )) } else { @@ -549,7 +549,7 @@ fn get_associated_constants<'a>( .iter() .filter_map(|item| match item.name { Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new( - get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)), + get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocConst)), name.as_str(), )), _ => None, diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index eb9262f472b67..5f130f1875aae 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -1,11 +1,11 @@ -use crate::clean::{self, PrimitiveType}; +use crate::clean::{self, rustc_span, PrimitiveType}; use crate::html::sources; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::DefId; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{ExprKind, HirId, Mod, Node}; +use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::hygiene::MacroKind; @@ -25,6 +25,7 @@ pub(crate) enum LinkFromSrc { Local(clean::Span), External(DefId), Primitive(PrimitiveType), + Doc(DefId), } /// This function will do at most two things: @@ -65,24 +66,43 @@ struct SpanMapVisitor<'tcx> { impl<'tcx> SpanMapVisitor<'tcx> { /// This function is where we handle `hir::Path` elements and add them into the "span map". fn handle_path(&mut self, path: &rustc_hir::Path<'_>) { - let info = match path.res { + match path.res { // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`. // Would be nice to support them too alongside the other `DefKind` // (such as primitive types!). - Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id), - Res::Local(_) => None, + Res::Def(kind, def_id) if kind != DefKind::TyParam => { + let link = if def_id.as_local().is_some() { + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) + } else { + LinkFromSrc::External(def_id) + }; + self.matches.insert(path.span, link); + } + Res::Local(_) => { + if let Some(span) = self.tcx.hir().res_span(path.res) { + self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span))); + } + } Res::PrimTy(p) => { // FIXME: Doesn't handle "path-like" primitives like arrays or tuples. self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p))); - return; } - Res::Err => return, - _ => return, - }; - if let Some(span) = self.tcx.hir().res_span(path.res) { - self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span))); - } else if let Some(def_id) = info { - self.matches.insert(path.span, LinkFromSrc::External(def_id)); + Res::Err => {} + _ => {} + } + } + + /// Used to generate links on items' definition to go to their documentation page. + pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) { + if let Some(Node::Item(item)) = self.tcx.hir().find(hir_id) { + if let Some(span) = self.tcx.def_ident_span(item.owner_id) { + let cspan = clean::Span::new(span); + // If the span isn't from the current crate, we ignore it. + if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE { + return; + } + self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id())); + } } } @@ -117,10 +137,13 @@ impl<'tcx> SpanMapVisitor<'tcx> { _ => return true, }; let link_from_src = match data.macro_def_id { - Some(macro_def_id) if macro_def_id.is_local() => { - LinkFromSrc::Local(clean::Span::new(data.def_site)) + Some(macro_def_id) => { + if macro_def_id.is_local() { + LinkFromSrc::Local(clean::Span::new(data.def_site)) + } else { + LinkFromSrc::External(macro_def_id) + } } - Some(macro_def_id) => LinkFromSrc::External(macro_def_id), None => return true, }; let new_span = data.call_site; @@ -160,6 +183,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)), ); } + } else { + // If it's a "mod foo {}", we want to look to its documentation page. + self.extract_info_from_hir_id(id); } intravisit::walk_mod(self, m, id); } @@ -176,13 +202,12 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { .tcx .typeck_body(hir.maybe_body_owned_by(body_id).expect("a body which isn't a body")); if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) { - self.matches.insert( - segment.ident.span, - match hir.span_if_local(def_id) { - Some(span) => LinkFromSrc::Local(clean::Span::new(span)), - None => LinkFromSrc::External(def_id), - }, - ); + let link = if def_id.as_local().is_some() { + LinkFromSrc::Local(rustc_span(def_id, self.tcx)) + } else { + LinkFromSrc::External(def_id) + }; + self.matches.insert(segment.ident.span, link); } } else if self.handle_macro(expr.span) { // We don't want to go deeper into the macro. @@ -190,4 +215,28 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } intravisit::walk_expr(self, expr); } + + fn visit_item(&mut self, item: &'tcx Item<'tcx>) { + match item.kind { + ItemKind::Static(_, _, _) + | ItemKind::Const(_, _, _) + | ItemKind::Fn(_, _, _) + | ItemKind::Macro(_, _) + | ItemKind::TyAlias(_, _) + | ItemKind::Enum(_, _) + | ItemKind::Struct(_, _) + | ItemKind::Union(_, _) + | ItemKind::Trait(_, _, _, _, _) + | ItemKind::TraitAlias(_, _) => self.extract_info_from_hir_id(item.hir_id()), + ItemKind::Impl(_) + | ItemKind::Use(_, _) + | ItemKind::ExternCrate(_) + | ItemKind::ForeignMod { .. } + | ItemKind::GlobalAsm(_) + | ItemKind::OpaqueTy(_) + // We already have "visit_mod" above so no need to check it here. + | ItemKind::Mod(_) => {} + } + intravisit::walk_item(self, item); + } } diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs index 22aec623335e8..377daaeb9d487 100644 --- a/src/librustdoc/html/render/type_layout.rs +++ b/src/librustdoc/html/render/type_layout.rs @@ -17,7 +17,7 @@ use crate::html::render::Context; #[template(path = "type_layout.html")] struct TypeLayout<'cx> { variants: Vec<(Symbol, TypeLayoutSize)>, - type_layout_size: Result>, + type_layout_size: Result>, } #[derive(Template)] @@ -39,7 +39,7 @@ pub(crate) fn document_type_layout<'a, 'cx: 'a>( let tcx = cx.tcx(); let param_env = tcx.param_env(ty_def_id); - let ty = tcx.type_of(ty_def_id).subst_identity(); + let ty = tcx.type_of(ty_def_id).instantiate_identity(); let type_layout = tcx.layout_of(param_env.and(ty)); let variants = @@ -54,13 +54,13 @@ pub(crate) fn document_type_layout<'a, 'cx: 'a>( } else if let Primitive::Int(i, _) = tag.primitive() { i.size().bytes() } else { - span_bug!(cx.tcx().def_span(ty_def_id), "tag is neither niche nor int") + span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") }; variants .iter_enumerated() .map(|(variant_idx, variant_layout)| { let Adt(adt, _) = type_layout.ty.kind() else { - span_bug!(cx.tcx().def_span(ty_def_id), "not an adt") + span_bug!(tcx.def_span(ty_def_id), "not an adt") }; let name = adt.variant(variant_idx).name; let is_unsized = variant_layout.abi.is_unsized(); diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 54749e9a31718..e824651e727df 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -73,7 +73,7 @@ pub(super) fn write_shared( } let bytes = try_err!(fs::read(&entry.path), &entry.path); - let filename = format!("{}{}.{}", theme, cx.shared.resource_suffix, extension); + let filename = format!("{theme}{suffix}.{extension}", suffix = cx.shared.resource_suffix); cx.shared.fs.write(cx.dst.join(filename), bytes)?; } @@ -112,7 +112,7 @@ pub(super) fn write_shared( let mut krates = Vec::new(); if path.exists() { - let prefix = format!("\"{}\"", krate); + let prefix = format!("\"{krate}\""); for line in BufReader::new(File::open(path)?).lines() { let line = line?; if !line.starts_with('"') { @@ -157,7 +157,7 @@ pub(super) fn write_shared( let mut krates = Vec::new(); if path.exists() { - let prefix = format!("\"{}\"", krate); + let prefix = format!("\"{krate}\""); for line in BufReader::new(File::open(path)?).lines() { let line = line?; if !line.starts_with('"') { @@ -213,10 +213,10 @@ pub(super) fn write_shared( let dirs = if subs.is_empty() && files.is_empty() { String::new() } else { - format!(",[{}]", subs) + format!(",[{subs}]") }; let files = files.join(","); - let files = if files.is_empty() { String::new() } else { format!(",[{}]", files) }; + let files = if files.is_empty() { String::new() } else { format!(",[{files}]") }; format!( "[\"{name}\"{dirs}{files}]", name = self.elem.to_str().expect("invalid osstring conversion"), @@ -270,7 +270,7 @@ pub(super) fn write_shared( hierarchy.add_path(source); } let hierarchy = Rc::try_unwrap(hierarchy).unwrap(); - let dst = cx.dst.join(&format!("source-files{}.js", cx.shared.resource_suffix)); + let dst = cx.dst.join(&format!("src-files{}.js", cx.shared.resource_suffix)); let make_sources = || { let (mut all_sources, _krates) = try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); @@ -286,12 +286,12 @@ pub(super) fn write_shared( .replace("\\\"", "\\\\\"") )); all_sources.sort(); - let mut v = String::from("var sourcesIndex = JSON.parse('{\\\n"); + let mut v = String::from("var srcIndex = JSON.parse('{\\\n"); v.push_str(&all_sources.join(",\\\n")); - v.push_str("\\\n}');\ncreateSourceSidebar();\n"); + v.push_str("\\\n}');\ncreateSrcSidebar();\n"); Ok(v.into_bytes()) }; - write_invocation_specific("source-files.js", &make_sources)?; + write_invocation_specific("src-files.js", &make_sources)?; } // Update the search index and crate list. @@ -319,8 +319,8 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; })?; write_invocation_specific("crates.js", &|| { - let krates = krates.iter().map(|k| format!("\"{}\"", k)).join(","); - Ok(format!("window.ALL_CRATES = [{}];", krates).into_bytes()) + let krates = krates.iter().map(|k| format!("\"{k}\"")).join(","); + Ok(format!("window.ALL_CRATES = [{krates}];").into_bytes()) })?; if options.enable_index_page { @@ -349,9 +349,8 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; .iter() .map(|s| { format!( - "
  • {}
  • ", - ensure_trailing_slash(s), - s + "
  • {s}
  • ", + trailing_slash = ensure_trailing_slash(s), ) }) .collect::() @@ -444,7 +443,7 @@ if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; mydst.push(part.to_string()); } cx.shared.ensure_dir(&mydst)?; - mydst.push(&format!("{}.{}.js", remote_item_type, remote_path[remote_path.len() - 1])); + mydst.push(&format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1])); let (mut all_implementors, _) = try_err!(collect(&mydst, krate.name(cx.tcx()).as_str()), &mydst); diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index a26fa37491296..c4a1ebbec02ea 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -146,9 +146,8 @@ impl DocVisitor for SourceCollector<'_, '_> { self.cx.shared.tcx.sess.span_err( span, format!( - "failed to render source code for `{}`: {}", - filename.prefer_local(), - e, + "failed to render source code for `{filename}`: {e}", + filename = filename.prefer_local(), ), ); false @@ -227,7 +226,7 @@ impl SourceCollector<'_, '_> { let desc = format!("Source of the Rust file `{}`.", filename.prefer_remapped()); let page = layout::Page { title: &title, - css_class: "source", + css_class: "src", root_path: &root_path, static_root_path: shared.static_root_path.as_deref(), description: &desc, diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index 54e8b6561f34f..93aa11a58528b 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -19,7 +19,7 @@ nav.sub { display: none; } -.source .sidebar { +.src .sidebar { display: none; } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index a7d5f497756b5..b1de8c1529e78 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -8,6 +8,7 @@ :root { --nav-sub-mobile-padding: 8px; + --search-typename-width: 6.75rem; } /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @@ -193,7 +194,7 @@ h1, h2, h3, h4, h5, h6, .item-name > a, .out-of-band, span.since, -a.srclink, +a.src, #help-button > a, summary.hideme, .scraped-example-list, @@ -205,7 +206,7 @@ ul.all-items { #toggle-all-docs, a.anchor, .small-section-header a, -#source-sidebar a, +#src-sidebar a, .rust a, .sidebar h2 a, .sidebar h3 a, @@ -213,7 +214,7 @@ a.anchor, h1 a, .search-results a, .stab, -.result-name .primitive > i, .result-name .keyword > i { +.result-name i { color: var(--main-color); } @@ -314,7 +315,7 @@ main { min-width: 0; /* avoid growing beyond the size limit */ } -.source main { +.src main { padding: 15px; } @@ -349,10 +350,10 @@ pre.item-decl { contain: initial; } -.source .content pre { +.src .content pre { padding: 20px; } -.rustdoc.source .example-wrap pre.src-line-numbers { +.rustdoc.src .example-wrap pre.src-line-numbers { padding: 20px 0 20px 4px; } @@ -391,7 +392,7 @@ img { left: 0; } -.rustdoc.source .sidebar { +.rustdoc.src .sidebar { flex-basis: 50px; border-right: 1px solid; overflow-x: hidden; @@ -401,7 +402,7 @@ img { } .sidebar, .mobile-topbar, .sidebar-menu-toggle, -#src-sidebar-toggle, #source-sidebar { +#src-sidebar-toggle, #src-sidebar { background-color: var(--sidebar-background-color); } @@ -409,16 +410,16 @@ img { background-color: var(--sidebar-background-color-hover); } -.source .sidebar > *:not(#src-sidebar-toggle) { +.src .sidebar > *:not(#src-sidebar-toggle) { visibility: hidden; } -.source-sidebar-expanded .source .sidebar { +.src-sidebar-expanded .src .sidebar { overflow-y: auto; flex-basis: 300px; } -.source-sidebar-expanded .source .sidebar > *:not(#src-sidebar-toggle) { +.src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { visibility: visible; } @@ -543,7 +544,7 @@ ul.block, .block li { flex-grow: 1; } -.rustdoc:not(.source) .example-wrap pre { +.rustdoc:not(.src) .example-wrap pre { overflow: auto hidden; } @@ -618,7 +619,7 @@ ul.block, .block li { } .docblock code, .docblock-short code, -pre, .rustdoc.source .example-wrap { +pre, .rustdoc.src .example-wrap { background-color: var(--code-block-background-color); } @@ -675,7 +676,7 @@ nav.sub { height: 34px; flex-grow: 1; } -.source nav.sub { +.src nav.sub { margin: 0 0 15px 0; } @@ -775,7 +776,6 @@ table, } #crate-search { min-width: 115px; - /* keep these two in sync with "@-moz-document url-prefix()" below */ padding: 0 23px 0 4px; /* prevents the s */ -@-moz-document url-prefix() { - #crate-search { - padding-left: 0px; /* == 4px - 4px */ - padding-right: 19px; /* == 23px - 4px */ - } -} /* pseudo-element for holding the dropdown-arrow image; needs to be a separate thing so that we can apply CSS-filters to change the arrow color in themes */ #crate-search-div::after { @@ -869,14 +861,11 @@ so that we can apply CSS-filters to change the arrow color in themes */ gap: 1em; } -.search-results > a > div { - flex: 1; -} - .search-results > a > div.desc { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; + flex: 2; } .search-results a:hover, @@ -884,11 +873,30 @@ so that we can apply CSS-filters to change the arrow color in themes */ background-color: var(--search-result-link-focus-background-color); } -.search-results .result-name span.alias { +.search-results .result-name { + display: flex; + align-items: center; + justify-content: start; + flex: 3; +} +.search-results .result-name .alias { color: var(--search-results-alias-color); } -.search-results .result-name span.grey { +.search-results .result-name .grey { + color: var(--search-results-grey-color); +} +.search-results .result-name .typename { color: var(--search-results-grey-color); + font-size: 0.875rem; + width: var(--search-typename-width); +} +.search-results .result-name .path { + word-break: break-all; + max-width: calc(100% - var(--search-typename-width)); + display: inline-block; +} +.search-results .result-name .path > * { + display: inline; } .popover { @@ -957,6 +965,8 @@ so that we can apply CSS-filters to change the arrow color in themes */ display: flex; padding: 3px; margin-bottom: 5px; + align-items: center; + vertical-align: text-bottom; } .item-name .stab { margin-left: 0.3125em; @@ -968,11 +978,9 @@ so that we can apply CSS-filters to change the arrow color in themes */ color: var(--main-color); background-color: var(--stab-background-color); width: fit-content; - align-items: center; white-space: pre-wrap; border-radius: 3px; - display: inline-flex; - vertical-align: text-bottom; + display: inline; } .stab.portability > code { @@ -1060,7 +1068,7 @@ pre.rust .doccomment { color: var(--code-highlight-doc-comment-color); } -.rustdoc.source .example-wrap pre.rust a { +.rustdoc.src .example-wrap pre.rust a { background: var(--codeblock-link-background); } @@ -1179,6 +1187,10 @@ a.test-arrow:hover { position: relative; } +.code-header a.tooltip:hover { + color: var(--link-color); +} + /* placeholder thunk so that the mouse can easily travel from "(i)" to popover the resulting "hover tunnel" is a stepped triangle, approximating https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */ @@ -1191,6 +1203,14 @@ a.tooltip:hover::after { content: "\00a0"; } +/* This animation is layered onto the mistake-proofing delay for dismissing + a hovered tooltip, to ensure it feels responsive even with the delay. + */ +.fade-out { + opacity: 0; + transition: opacity 0.45s cubic-bezier(0, 0, 0.1, 1.0); +} + .popover.tooltip .content { margin: 0.25em 0.5em; } @@ -1275,22 +1295,22 @@ a.tooltip:hover::after { align-items: stretch; z-index: 10; } -#source-sidebar { +#src-sidebar { width: 100%; overflow: auto; } -#source-sidebar > .title { +#src-sidebar > .title { font-size: 1.5rem; text-align: center; border-bottom: 1px solid var(--border-color); margin-bottom: 6px; } -#source-sidebar div.files > a:hover, details.dir-entry summary:hover, -#source-sidebar div.files > a:focus, details.dir-entry summary:focus { - background-color: var(--source-sidebar-background-hover); +#src-sidebar div.files > a:hover, details.dir-entry summary:hover, +#src-sidebar div.files > a:focus, details.dir-entry summary:focus { + background-color: var(--src-sidebar-background-hover); } -#source-sidebar div.files > a.selected { - background-color: var(--source-sidebar-background-selected); +#src-sidebar div.files > a.selected { + background-color: var(--src-sidebar-background-selected); } #src-sidebar-toggle > button { font-size: inherit; @@ -1536,7 +1556,7 @@ However, it's not needed with smaller screen width because the doc/code block is /* WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY If you update this line, then you also need to update the line with the same warning -in source-script.js +in src-script.js */ @media (max-width: 700px) { /* When linking to an item with an `id` (for instance, by clicking a link in the sidebar, @@ -1593,8 +1613,8 @@ in source-script.js /* The source view uses a different design for the sidebar toggle, and doesn't have a topbar, so don't bump down the main content or the sidebar. */ - .source main, - .rustdoc.source .sidebar { + .src main, + .rustdoc.src .sidebar { top: 0; padding: 0; height: 100vh; @@ -1602,8 +1622,8 @@ in source-script.js } .sidebar.shown, - .source-sidebar-expanded .source .sidebar, - .rustdoc:not(.source) .sidebar:focus-within { + .src-sidebar-expanded .src .sidebar, + .rustdoc:not(.src) .sidebar:focus-within { left: 0; } @@ -1683,7 +1703,7 @@ in source-script.js border-left: 0; } - .source-sidebar-expanded #src-sidebar-toggle { + .src-sidebar-expanded #src-sidebar-toggle { left: unset; top: unset; width: unset; @@ -1712,8 +1732,18 @@ in source-script.js .search-results > a > div.desc, .item-table > li > div.desc { padding-left: 2em; } + .search-results .result-name { + display: block; + } + .search-results .result-name .typename { + width: initial; + margin-right: 0; + } + .search-results .result-name .typename, .search-results .result-name .path { + display: inline; + } - .source-sidebar-expanded .source .sidebar { + .src-sidebar-expanded .src .sidebar { max-width: 100vw; width: 100vw; } @@ -1733,7 +1763,7 @@ in source-script.js margin-left: 34px; } - .source nav.sub { + .src nav.sub { margin: 0; padding: var(--nav-sub-mobile-padding); } @@ -1756,7 +1786,7 @@ in source-script.js } @media print { - nav.sidebar, nav.sub, .out-of-band, a.srclink, #copy-path, + nav.sidebar, nav.sub, .out-of-band, a.src, #copy-path, details.toggle[open] > summary::before, details.toggle > summary::before, details.toggle.top-doc > summary { display: none; diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 7145baad25673..d8dae51eb1bff 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -89,8 +89,8 @@ Original by Dempfi (https://github.com/dempfi/ayu) --crate-search-div-hover-filter: invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%); --crate-search-hover-border: #e0e0e0; - --source-sidebar-background-selected: #14191f; - --source-sidebar-background-hover: #14191f; + --src-sidebar-background-selected: #14191f; + --src-sidebar-background-hover: #14191f; --table-alt-row-background-color: #191f26; --codeblock-link-background: #333; --scrape-example-toggle-line-background: #999; @@ -107,7 +107,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) h1, h2, h3, h4, h1 a, .sidebar h2 a, .sidebar h3 a, -#source-sidebar > .title { +#src-sidebar > .title { color: #fff; } h4 { @@ -124,15 +124,15 @@ h4 { .docblock pre > code, pre, pre > code, .item-info code, -.rustdoc.source .example-wrap { +.rustdoc.src .example-wrap { color: #e6e1cf; } .sidebar .current, .sidebar a:hover, -#source-sidebar div.files > a:hover, details.dir-entry summary:hover, -#source-sidebar div.files > a:focus, details.dir-entry summary:focus, -#source-sidebar div.files > a.selected { +#src-sidebar div.files > a:hover, details.dir-entry summary:hover, +#src-sidebar div.files > a:focus, details.dir-entry summary:focus, +#src-sidebar div.files > a.selected { color: #ffb44c; } diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 3c1186a5649f1..2b302988734fa 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -68,7 +68,7 @@ --test-arrow-color: #dedede; --test-arrow-background-color: rgba(78, 139, 202, 0.2); --test-arrow-hover-color: #dedede; - --test-arrow-hover-background-color: #4e8bca; + --test-arrow-hover-background-color: rgb(78, 139, 202); --target-background-color: #494a3d; --target-border-color: #bb7410; --kbd-color: #000; @@ -84,8 +84,8 @@ --crate-search-div-hover-filter: invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%); --crate-search-hover-border: #2196f3; - --source-sidebar-background-selected: #333; - --source-sidebar-background-hover: #444; + --src-sidebar-background-selected: #333; + --src-sidebar-background-hover: #444; --table-alt-row-background-color: #2A2A2A; --codeblock-link-background: #333; --scrape-example-toggle-line-background: #999; diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index f8c287137deac..56fd8cbef121c 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -68,7 +68,7 @@ --test-arrow-color: #f5f5f5; --test-arrow-background-color: rgba(78, 139, 202, 0.2); --test-arrow-hover-color: #f5f5f5; - --test-arrow-hover-background-color: #4e8bca; + --test-arrow-hover-background-color: rgb(78, 139, 202); --target-background-color: #fdffd3; --target-border-color: #ad7c37; --kbd-color: #000; @@ -81,8 +81,8 @@ --crate-search-div-hover-filter: invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%); --crate-search-hover-border: #717171; - --source-sidebar-background-selected: #fff; - --source-sidebar-background-hover: #e0e0e0; + --src-sidebar-background-selected: #fff; + --src-sidebar-background-hover: #e0e0e0; --table-alt-row-background-color: #F5F5F5; --codeblock-link-background: #eee; --scrape-example-toggle-line-background: #ccc; diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index 8b931f74e600a..f697abd07765a 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -53,7 +53,7 @@ let ParsedQuery; * parent: (Object|null|undefined), * path: string, * ty: (Number|null|number), - * type: (Array|null) + * type: FunctionSearchType? * }} */ let Row; @@ -135,7 +135,7 @@ let RawFunctionType; /** * @typedef {{ * inputs: Array, - * outputs: Array, + * output: Array, * }} */ let FunctionSearchType; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index bccf675c14b5e..254b0d8bf5a43 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -4,6 +4,13 @@ "use strict"; +// The amount of time that the cursor must remain still over a hover target before +// revealing a tooltip. +// +// https://www.nngroup.com/articles/timing-exposing-content/ +window.RUSTDOC_TOOLTIP_HOVER_MS = 300; +window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS = 450; + // Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL // for a resource under the root-path, with the resource-suffix. function resourcePath(basename, extension) { @@ -270,14 +277,18 @@ function preLoadCss(cssUrl) { searchState.mouseMovedAfterSearch = false; document.title = searchState.title; }, - hideResults: () => { - switchDisplayedElement(null); + removeQueryParameters: () => { + // We change the document title. document.title = searchState.titleBeforeSearch; - // We also remove the query parameter from the URL. if (browserSupportsHistoryApi()) { history.replaceState(null, "", getNakedUrl() + window.location.hash); } }, + hideResults: () => { + switchDisplayedElement(null); + // We also remove the query parameter from the URL. + searchState.removeQueryParameters(); + }, getQueryStringParams: () => { const params = {}; window.location.search.substring(1).split("&"). @@ -772,6 +783,13 @@ function preLoadCss(cssUrl) { }); }); + /** + * Show a tooltip immediately. + * + * @param {DOMElement} e - The tooltip's anchor point. The DOM is consulted to figure + * out what the tooltip should contain, and where it should be + * positioned. + */ function showTooltip(e) { const notable_ty = e.getAttribute("data-notable-ty"); if (!window.NOTABLE_TRAITS && notable_ty) { @@ -782,8 +800,10 @@ function preLoadCss(cssUrl) { throw new Error("showTooltip() called with notable without any notable traits!"); } } + // Make this function idempotent. If the tooltip is already shown, avoid doing extra work + // and leave it alone. if (window.CURRENT_TOOLTIP_ELEMENT && window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE === e) { - // Make this function idempotent. + clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT); return; } window.hideAllModals(false); @@ -791,11 +811,18 @@ function preLoadCss(cssUrl) { if (notable_ty) { wrapper.innerHTML = "
    " + window.NOTABLE_TRAITS[notable_ty] + "
    "; - } else if (e.getAttribute("title") !== undefined) { - const titleContent = document.createElement("div"); - titleContent.className = "content"; - titleContent.appendChild(document.createTextNode(e.getAttribute("title"))); - wrapper.appendChild(titleContent); + } else { + // Replace any `title` attribute with `data-title` to avoid double tooltips. + if (e.getAttribute("title") !== null) { + e.setAttribute("data-title", e.getAttribute("title")); + e.removeAttribute("title"); + } + if (e.getAttribute("data-title") !== null) { + const titleContent = document.createElement("div"); + titleContent.className = "content"; + titleContent.appendChild(document.createTextNode(e.getAttribute("data-title"))); + wrapper.appendChild(titleContent); + } } wrapper.className = "tooltip popover"; const focusCatcher = document.createElement("div"); @@ -824,17 +851,77 @@ function preLoadCss(cssUrl) { wrapper.style.visibility = ""; window.CURRENT_TOOLTIP_ELEMENT = wrapper; window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE = e; + clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT); + wrapper.onpointerenter = function(ev) { + // If this is a synthetic touch event, ignore it. A click event will be along shortly. + if (ev.pointerType !== "mouse") { + return; + } + clearTooltipHoverTimeout(e); + }; wrapper.onpointerleave = function(ev) { // If this is a synthetic touch event, ignore it. A click event will be along shortly. if (ev.pointerType !== "mouse") { return; } - if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(event.relatedTarget, e)) { - hideTooltip(true); + if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(ev.relatedTarget, e)) { + // See "Tooltip pointer leave gesture" below. + setTooltipHoverTimeout(e, false); + addClass(wrapper, "fade-out"); } }; } + /** + * Show or hide the tooltip after a timeout. If a timeout was already set before this function + * was called, that timeout gets cleared. If the tooltip is already in the requested state, + * this function will still clear any pending timeout, but otherwise do nothing. + * + * @param {DOMElement} element - The tooltip's anchor point. The DOM is consulted to figure + * out what the tooltip should contain, and where it should be + * positioned. + * @param {boolean} show - If true, the tooltip will be made visible. If false, it will + * be hidden. + */ + function setTooltipHoverTimeout(element, show) { + clearTooltipHoverTimeout(element); + if (!show && !window.CURRENT_TOOLTIP_ELEMENT) { + // To "hide" an already hidden element, just cancel its timeout. + return; + } + if (show && window.CURRENT_TOOLTIP_ELEMENT) { + // To "show" an already visible element, just cancel its timeout. + return; + } + if (window.CURRENT_TOOLTIP_ELEMENT && + window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE !== element) { + // Don't do anything if another tooltip is already visible. + return; + } + element.TOOLTIP_HOVER_TIMEOUT = setTimeout(() => { + if (show) { + showTooltip(element); + } else if (!element.TOOLTIP_FORCE_VISIBLE) { + hideTooltip(false); + } + }, show ? window.RUSTDOC_TOOLTIP_HOVER_MS : window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS); + } + + /** + * If a show/hide timeout was set by `setTooltipHoverTimeout`, cancel it. If none exists, + * do nothing. + * + * @param {DOMElement} element - The tooltip's anchor point, + * as passed to `setTooltipHoverTimeout`. + */ + function clearTooltipHoverTimeout(element) { + if (element.TOOLTIP_HOVER_TIMEOUT !== undefined) { + removeClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out"); + clearTimeout(element.TOOLTIP_HOVER_TIMEOUT); + delete element.TOOLTIP_HOVER_TIMEOUT; + } + } + function tooltipBlurHandler(event) { if (window.CURRENT_TOOLTIP_ELEMENT && !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT) && @@ -854,6 +941,12 @@ function preLoadCss(cssUrl) { } } + /** + * Hide the current tooltip immediately. + * + * @param {boolean} focus - If set to `true`, move keyboard focus to the tooltip anchor point. + * If set to `false`, leave keyboard focus alone. + */ function hideTooltip(focus) { if (window.CURRENT_TOOLTIP_ELEMENT) { if (window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE) { @@ -864,6 +957,7 @@ function preLoadCss(cssUrl) { } const body = document.getElementsByTagName("body")[0]; body.removeChild(window.CURRENT_TOOLTIP_ELEMENT); + clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT); window.CURRENT_TOOLTIP_ELEMENT = null; } } @@ -886,7 +980,14 @@ function preLoadCss(cssUrl) { if (ev.pointerType !== "mouse") { return; } - showTooltip(this); + setTooltipHoverTimeout(this, true); + }; + e.onpointermove = function(ev) { + // If this is a synthetic touch event, ignore it. A click event will be along shortly. + if (ev.pointerType !== "mouse") { + return; + } + setTooltipHoverTimeout(this, true); }; e.onpointerleave = function(ev) { // If this is a synthetic touch event, ignore it. A click event will be along shortly. @@ -895,7 +996,38 @@ function preLoadCss(cssUrl) { } if (!this.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) { - hideTooltip(true); + // Tooltip pointer leave gesture: + // + // Designing a good hover microinteraction is a matter of guessing user + // intent from what are, literally, vague gestures. In this case, guessing if + // hovering in or out of the tooltip base is intentional or not. + // + // To figure this out, a few different techniques are used: + // + // * When the mouse pointer enters a tooltip anchor point, its hitbox is grown + // on the bottom, where the popover is/will appear. Search "hover tunnel" in + // rustdoc.css for the implementation. + // * There's a delay when the mouse pointer enters the popover base anchor, in + // case the mouse pointer was just passing through and the user didn't want + // to open it. + // * Similarly, a delay is added when exiting the anchor, or the popover + // itself, before hiding it. + // * A fade-out animation is layered onto the pointer exit delay to immediately + // inform the user that they successfully dismissed the popover, while still + // providing a way for them to cancel it if it was a mistake and they still + // wanted to interact with it. + // * No animation is used for revealing it, because we don't want people to try + // to interact with an element while it's in the middle of fading in: either + // they're allowed to interact with it while it's fading in, meaning it can't + // serve as mistake-proofing for the popover, or they can't, but + // they might try and be frustrated. + // + // See also: + // * https://www.nngroup.com/articles/timing-exposing-content/ + // * https://www.nngroup.com/articles/tooltip-guidelines/ + // * https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown + setTooltipHoverTimeout(e, false); + addClass(window.CURRENT_TOOLTIP_ELEMENT, "fade-out"); } }; }); @@ -918,9 +1050,10 @@ function preLoadCss(cssUrl) { function buildHelpMenu() { const book_info = document.createElement("span"); + const channel = getVar("channel"); book_info.className = "top"; - book_info.innerHTML = "You can find more information in \ - the rustdoc book."; + book_info.innerHTML = `You can find more information in \ +the rustdoc book.`; const shortcuts = [ ["?", "Show this help dialog"], @@ -940,6 +1073,9 @@ function preLoadCss(cssUrl) { div_shortcuts.innerHTML = "

    Keyboard Shortcuts

    " + shortcuts + "
    "; const infos = [ + `For a full list of all search features, take a look here.`, "Prefix searches with a type followed by a colon (e.g., fn:) to \ restrict the search to a given item kind.", "Accepted kinds are: fn, mod, struct, \ @@ -949,6 +1085,10 @@ function preLoadCss(cssUrl) { -> vec or String, enum:Cow -> bool)", "You can look for items with an exact name by putting double quotes around \ your request: \"string\"", + "Look for functions that accept or return \ + slices and \ + arrays by writing \ + square brackets (e.g., -> [u8] or [] -> Option)", "Look for items inside another one by searching for a path: vec::Vec", ].map(x => "

    " + x + "

    ").join(""); const div_infos = document.createElement("div"); diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 62afe40bb3155..42088e7355440 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -35,6 +35,35 @@ const itemTypes = [ "traitalias", ]; +const longItemTypes = [ + "module", + "extern crate", + "re-export", + "struct", + "enum", + "function", + "type alias", + "static", + "trait", + "", + "trait method", + "method", + "struct field", + "enum variant", + "macro", + "primitive type", + "assoc type", + "constant", + "assoc const", + "union", + "foreign type", + "keyword", + "existential type", + "attribute macro", + "derive macro", + "trait alias", +]; + // used for special search precedence const TY_PRIMITIVE = itemTypes.indexOf("primitive"); const TY_KEYWORD = itemTypes.indexOf("keyword"); @@ -208,6 +237,46 @@ function initSearch(rawSearchIndex) { let typeNameIdMap; const ALIASES = new Map(); + /** + * Special type name IDs for searching by array. + */ + let typeNameIdOfArray; + /** + * Special type name IDs for searching by slice. + */ + let typeNameIdOfSlice; + /** + * Special type name IDs for searching by both array and slice (`[]` syntax). + */ + let typeNameIdOfArrayOrSlice; + + /** + * Add an item to the type Name->ID map, or, if one already exists, use it. + * Returns the number. If name is "" or null, return -1 (pure generic). + * + * This is effectively string interning, so that function matching can be + * done more quickly. Two types with the same name but different item kinds + * get the same ID. + * + * @param {string} name + * + * @returns {integer} + */ + function buildTypeMapIndex(name) { + + if (name === "" || name === null) { + return -1; + } + + if (typeNameIdMap.has(name)) { + return typeNameIdMap.get(name); + } else { + const id = typeNameIdMap.size; + typeNameIdMap.set(name, id); + return id; + } + } + function isWhitespace(c) { return " \t\n\r".indexOf(c) !== -1; } @@ -217,11 +286,11 @@ function initSearch(rawSearchIndex) { } function isEndCharacter(c) { - return ",>-".indexOf(c) !== -1; + return ",>-]".indexOf(c) !== -1; } function isStopCharacter(c) { - return isWhitespace(c) || isEndCharacter(c); + return isEndCharacter(c); } function isErrorCharacter(c) { @@ -317,18 +386,69 @@ function initSearch(rawSearchIndex) { * @return {boolean} */ function isSeparatorCharacter(c) { - return c === "," || isWhitespaceCharacter(c); + return c === ","; } - /** - * Returns `true` if the given `c` character is a whitespace. +/** + * Returns `true` if the given `c` character is a path separator. For example + * `:` in `a::b` or a whitespace in `a b`. * * @param {string} c * * @return {boolean} */ - function isWhitespaceCharacter(c) { - return c === " " || c === "\t"; + function isPathSeparator(c) { + return c === ":" || isWhitespace(c); + } + + /** + * Returns `true` if the previous character is `lookingFor`. + * + * @param {ParserState} parserState + * @param {String} lookingFor + * + * @return {boolean} + */ + function prevIs(parserState, lookingFor) { + let pos = parserState.pos; + while (pos > 0) { + const c = parserState.userQuery[pos - 1]; + if (c === lookingFor) { + return true; + } else if (!isWhitespace(c)) { + break; + } + pos -= 1; + } + return false; + } + + /** + * Returns `true` if the last element in the `elems` argument has generics. + * + * @param {Array} elems + * @param {ParserState} parserState + * + * @return {boolean} + */ + function isLastElemGeneric(elems, parserState) { + return (elems.length > 0 && elems[elems.length - 1].generics.length > 0) || + prevIs(parserState, ">"); + } + + /** + * Increase current parser position until it doesn't find a whitespace anymore. + * + * @param {ParserState} parserState + */ + function skipWhitespace(parserState) { + while (parserState.pos < parserState.userQuery.length) { + const c = parserState.userQuery[parserState.pos]; + if (!isWhitespace(c)) { + break; + } + parserState.pos += 1; + } } /** @@ -340,39 +460,78 @@ function initSearch(rawSearchIndex) { * @return {QueryElement} - The newly created `QueryElement`. */ function createQueryElement(query, parserState, name, generics, isInGenerics) { - if (name === "*" || (name.length === 0 && generics.length === 0)) { - return; + const path = name.trim(); + if (path.length === 0 && generics.length === 0) { + throw ["Unexpected ", parserState.userQuery[parserState.pos]]; + } else if (path === "*") { + throw ["Unexpected ", "*"]; } if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) { - throw ["You cannot have more than one element if you use quotes"]; - } - const pathSegments = name.split("::"); - if (pathSegments.length > 1) { - for (let i = 0, len = pathSegments.length; i < len; ++i) { - const pathSegment = pathSegments[i]; - - if (pathSegment.length === 0) { - if (i === 0) { - throw ["Paths cannot start with ", "::"]; - } else if (i + 1 === len) { - throw ["Paths cannot end with ", "::"]; - } - throw ["Unexpected ", "::::"]; - } + throw ["Cannot have more than one element if you use quotes"]; + } + const typeFilter = parserState.typeFilter; + parserState.typeFilter = null; + if (name === "!") { + if (typeFilter !== null && typeFilter !== "primitive") { + throw [ + "Invalid search type: primitive never type ", + "!", + " and ", + typeFilter, + " both specified", + ]; + } + if (generics.length !== 0) { + throw [ + "Never type ", + "!", + " does not accept generic parameters", + ]; } + return { + name: "never", + id: -1, + fullPath: ["never"], + pathWithoutLast: [], + pathLast: "never", + generics: [], + typeFilter: "primitive", + }; } + if (path.startsWith("::")) { + throw ["Paths cannot start with ", "::"]; + } else if (path.endsWith("::")) { + throw ["Paths cannot end with ", "::"]; + } else if (path.includes("::::")) { + throw ["Unexpected ", "::::"]; + } else if (path.includes(" ::")) { + throw ["Unexpected ", " ::"]; + } else if (path.includes(":: ")) { + throw ["Unexpected ", ":: "]; + } + const pathSegments = path.split(/::|\s+/); // In case we only have something like `

    `, there is no name. if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) { - throw ["Found generics without a path"]; + if (generics.length > 0 || prevIs(parserState, ">")) { + throw ["Found generics without a path"]; + } else { + throw ["Unexpected ", parserState.userQuery[parserState.pos]]; + } + } + for (const [i, pathSegment] of pathSegments.entries()) { + if (pathSegment === "!") { + if (i !== 0) { + throw ["Never type ", "!", " is not associated item"]; + } + pathSegments[i] = "never"; + } } parserState.totalElems += 1; if (isInGenerics) { parserState.genericsElems += 1; } - const typeFilter = parserState.typeFilter; - parserState.typeFilter = null; return { - name: name, + name: name.trim(), id: -1, fullPath: pathSegments, pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1), @@ -408,28 +567,40 @@ function initSearch(rawSearchIndex) { foundExclamation = parserState.pos; } else if (isErrorCharacter(c)) { throw ["Unexpected ", c]; - } else if ( - isStopCharacter(c) || - isSpecialStartCharacter(c) || - isSeparatorCharacter(c) - ) { - break; - } else if (c === ":") { // If we allow paths ("str::string" for example). - if (!isPathStart(parserState)) { - break; + } else if (isPathSeparator(c)) { + if (c === ":") { + if (!isPathStart(parserState)) { + break; + } + // Skip current ":". + parserState.pos += 1; + } else { + while (parserState.pos + 1 < parserState.length) { + const next_c = parserState.userQuery[parserState.pos + 1]; + if (!isWhitespace(next_c)) { + break; + } + parserState.pos += 1; + } } if (foundExclamation !== -1) { - if (start <= (end - 2)) { + if (foundExclamation !== start && + isIdentCharacter(parserState.userQuery[foundExclamation - 1]) + ) { throw ["Cannot have associated items in macros"]; } else { - // if start == end - 1, we got the never type // while the never type has no associated macros, we still // can parse a path like that foundExclamation = -1; } } - // Skip current ":". - parserState.pos += 1; + } else if ( + c === "[" || + isStopCharacter(c) || + isSpecialStartCharacter(c) || + isSeparatorCharacter(c) + ) { + break; } else { throw ["Unexpected ", c]; } @@ -438,7 +609,10 @@ function initSearch(rawSearchIndex) { end = parserState.pos; } // if start == end - 1, we got the never type - if (foundExclamation !== -1 && start <= (end - 2)) { + if (foundExclamation !== -1 && + foundExclamation !== start && + isIdentCharacter(parserState.userQuery[foundExclamation - 1]) + ) { if (parserState.typeFilter === null) { parserState.typeFilter = "macro"; } else if (parserState.typeFilter !== "macro") { @@ -464,37 +638,71 @@ function initSearch(rawSearchIndex) { function getNextElem(query, parserState, elems, isInGenerics) { const generics = []; + skipWhitespace(parserState); let start = parserState.pos; let end; - // We handle the strings on their own mostly to make code easier to follow. - if (parserState.userQuery[parserState.pos] === "\"") { - start += 1; - getStringElem(query, parserState, isInGenerics); - end = parserState.pos - 1; + if (parserState.userQuery[parserState.pos] === "[") { + parserState.pos += 1; + getItemsBefore(query, parserState, generics, "]"); + const typeFilter = parserState.typeFilter; + if (typeFilter !== null && typeFilter !== "primitive") { + throw [ + "Invalid search type: primitive ", + "[]", + " and ", + typeFilter, + " both specified", + ]; + } + parserState.typeFilter = null; + parserState.totalElems += 1; + if (isInGenerics) { + parserState.genericsElems += 1; + } + elems.push({ + name: "[]", + id: -1, + fullPath: ["[]"], + pathWithoutLast: [], + pathLast: "[]", + generics, + typeFilter: "primitive", + }); } else { - end = getIdentEndPosition(parserState); - } - if (parserState.pos < parserState.length && - parserState.userQuery[parserState.pos] === "<" - ) { - if (start >= end) { - throw ["Found generics without a path"]; + const isStringElem = parserState.userQuery[start] === "\""; + // We handle the strings on their own mostly to make code easier to follow. + if (isStringElem) { + start += 1; + getStringElem(query, parserState, isInGenerics); + end = parserState.pos - 1; + } else { + end = getIdentEndPosition(parserState); } - parserState.pos += 1; - getItemsBefore(query, parserState, generics, ">"); - } - if (start >= end && generics.length === 0) { - return; + if (parserState.pos < parserState.length && + parserState.userQuery[parserState.pos] === "<" + ) { + if (start >= end) { + throw ["Found generics without a path"]; + } + parserState.pos += 1; + getItemsBefore(query, parserState, generics, ">"); + } + if (isStringElem) { + skipWhitespace(parserState); + } + if (start >= end && generics.length === 0) { + return; + } + elems.push( + createQueryElement( + query, + parserState, + parserState.userQuery.slice(start, end), + generics, + isInGenerics + ) + ); } - elems.push( - createQueryElement( - query, - parserState, - parserState.userQuery.slice(start, end), - generics, - isInGenerics - ) - ); } /** @@ -518,6 +726,17 @@ function initSearch(rawSearchIndex) { const oldTypeFilter = parserState.typeFilter; parserState.typeFilter = null; + let extra = ""; + if (endChar === ">") { + extra = "<"; + } else if (endChar === "]") { + extra = "["; + } else if (endChar === "") { + extra = "->"; + } else { + extra = endChar; + } + while (parserState.pos < parserState.length) { const c = parserState.userQuery[parserState.pos]; if (c === endChar) { @@ -535,7 +754,7 @@ function initSearch(rawSearchIndex) { if (elems.length === 0) { throw ["Expected type filter before ", ":"]; } else if (query.literalSearch) { - throw ["You cannot use quotes on type filter"]; + throw ["Cannot use quotes on type filter"]; } // The type filter doesn't count as an element since it's a modifier. const typeFilterElem = elems.pop(); @@ -547,43 +766,39 @@ function initSearch(rawSearchIndex) { foundStopChar = true; continue; } else if (isEndCharacter(c)) { - let extra = ""; - if (endChar === ">") { - extra = "<"; - } else if (endChar === "") { - extra = "->"; - } else { - extra = endChar; - } throw ["Unexpected ", c, " after ", extra]; } if (!foundStopChar) { + let extra = []; + if (isLastElemGeneric(query.elems, parserState)) { + extra = [" after ", ">"]; + } else if (prevIs(parserState, "\"")) { + throw ["Cannot have more than one element if you use quotes"]; + } if (endChar !== "") { throw [ "Expected ", - ",", // comma - ", ", - " ", // whitespace + ",", " or ", endChar, + ...extra, ", found ", c, ]; } throw [ "Expected ", - ",", // comma - " or ", - " ", // whitespace + ",", + ...extra, ", found ", c, ]; } const posBefore = parserState.pos; start = parserState.pos; - getNextElem(query, parserState, elems, endChar === ">"); + getNextElem(query, parserState, elems, endChar !== ""); if (endChar !== "" && parserState.pos >= parserState.length) { - throw ["Unclosed ", "<"]; + throw ["Unclosed ", extra]; } // This case can be encountered if `getNextElem` encountered a "stop character" right // from the start. For example if you have `,,` or `<>`. In this case, we simply move up @@ -594,7 +809,7 @@ function initSearch(rawSearchIndex) { foundStopChar = false; } if (parserState.pos >= parserState.length && endChar !== "") { - throw ["Unclosed ", "<"]; + throw ["Unclosed ", extra]; } // We are either at the end of the string or on the `endChar` character, let's move forward // in any case. @@ -610,11 +825,17 @@ function initSearch(rawSearchIndex) { * @param {ParserState} parserState */ function checkExtraTypeFilterCharacters(start, parserState) { - const query = parserState.userQuery; + const query = parserState.userQuery.slice(start, parserState.pos).trim(); - for (let pos = start; pos < parserState.pos; ++pos) { - if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) { - throw ["Unexpected ", query[pos], " in type filter"]; + for (const c in query) { + if (!isIdentCharacter(query[c])) { + throw [ + "Unexpected ", + query[c], + " in type filter (before ", + ":", + ")", + ]; } } } @@ -646,12 +867,17 @@ function initSearch(rawSearchIndex) { throw ["Unexpected ", c]; } else if (c === ":" && !isPathStart(parserState)) { if (parserState.typeFilter !== null) { - throw ["Unexpected ", ":"]; - } - if (query.elems.length === 0) { + throw [ + "Unexpected ", + ":", + " (expected path after type filter ", + parserState.typeFilter + ":", + ")", + ]; + } else if (query.elems.length === 0) { throw ["Expected type filter before ", ":"]; } else if (query.literalSearch) { - throw ["You cannot use quotes on type filter"]; + throw ["Cannot use quotes on type filter"]; } // The type filter doesn't count as an element since it's a modifier. const typeFilterElem = query.elems.pop(); @@ -662,29 +888,36 @@ function initSearch(rawSearchIndex) { query.literalSearch = false; foundStopChar = true; continue; + } else if (isWhitespace(c)) { + skipWhitespace(parserState); + continue; } if (!foundStopChar) { + let extra = ""; + if (isLastElemGeneric(query.elems, parserState)) { + extra = [" after ", ">"]; + } else if (prevIs(parserState, "\"")) { + throw ["Cannot have more than one element if you use quotes"]; + } if (parserState.typeFilter !== null) { throw [ "Expected ", - ",", // comma - ", ", - " ", // whitespace + ",", " or ", - "->", // arrow + "->", + ...extra, ", found ", c, ]; } throw [ "Expected ", - ",", // comma - ", ", - " ", // whitespace + ",", ", ", - ":", // colon + ":", " or ", - "->", // arrow + "->", + ...extra, ", found ", c, ]; @@ -699,11 +932,18 @@ function initSearch(rawSearchIndex) { foundStopChar = false; } if (parserState.typeFilter !== null) { - throw ["Unexpected ", ":", " (expected path after type filter)"]; + throw [ + "Unexpected ", + ":", + " (expected path after type filter ", + parserState.typeFilter + ":", + ")", + ]; } while (parserState.pos < parserState.length) { if (isReturnArrow(parserState)) { parserState.pos += 2; + skipWhitespace(parserState); // Get returned elements. getItemsBefore(query, parserState, query.returned, ""); // Nothing can come afterward! @@ -778,9 +1018,10 @@ function initSearch(rawSearchIndex) { * The supported syntax by this parser is as follow: * * ident = *(ALPHA / DIGIT / "_") - * path = ident *(DOUBLE-COLON ident) [!] - * arg = [type-filter *WS COLON *WS] path [generics] - * type-sep = COMMA/WS *(COMMA/WS) + * path = ident *(DOUBLE-COLON/{WS} ident) [!] + * slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET + * arg = [type-filter *WS COLON *WS] (path [generics] / slice) + * type-sep = *WS COMMA *(COMMA) * nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep) * CLOSE-ANGLE-BRACKET @@ -821,6 +1062,8 @@ function initSearch(rawSearchIndex) { * * OPEN-ANGLE-BRACKET = "<" * CLOSE-ANGLE-BRACKET = ">" + * OPEN-SQUARE-BRACKET = "[" + * CLOSE-SQUARE-BRACKET = "]" * COLON = ":" * DOUBLE-COLON = "::" * QUOTE = %x22 @@ -1103,98 +1346,182 @@ function initSearch(rawSearchIndex) { } /** - * This function checks if the object (`row`) generics match the given type (`elem`) - * generics. If there are no generics on `row`, `defaultDistance` is returned. + * This function checks generics in search query `queryElem` can all be found in the + * search index (`fnType`), * - * @param {Row} row - The object to check. - * @param {QueryElement} elem - The element from the parsed query. + * @param {FunctionType} fnType - The object to check. + * @param {QueryElement} queryElem - The element from the parsed query. * - * @return {boolean} - Returns true if a match, false otherwise. + * @return {boolean} - Returns true if a match, false otherwise. */ - function checkGenerics(row, elem) { - if (row.generics.length === 0 || elem.generics.length === 0) { - return false; - } - // This function is called if the names match, but we need to make - // sure that all generics match as well. - // + function checkGenerics(fnType, queryElem) { + return unifyFunctionTypes(fnType.generics, queryElem.generics); + } + /** + * This function checks if a list of search query `queryElems` can all be found in the + * search index (`fnTypes`). + * + * @param {Array} fnTypes - The objects to check. + * @param {Array} queryElems - The elements from the parsed query. + * + * @return {boolean} - Returns true if a match, false otherwise. + */ + function unifyFunctionTypes(fnTypes, queryElems) { // This search engine implements order-agnostic unification. There // should be no missing duplicates (generics have "bag semantics"), // and the row is allowed to have extras. - if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) { - const elems = new Map(); - const addEntryToElems = function addEntryToElems(entry) { - if (entry.id === -1) { - // Pure generic, needs to check into it. - for (const inner_entry of entry.generics) { - addEntryToElems(inner_entry); - } - return; + if (queryElems.length === 0) { + return true; + } + if (!fnTypes || fnTypes.length === 0) { + return false; + } + /** + * @type Map + */ + const queryElemSet = new Map(); + const addQueryElemToQueryElemSet = function addQueryElemToQueryElemSet(queryElem) { + let currentQueryElemList; + if (queryElemSet.has(queryElem.id)) { + currentQueryElemList = queryElemSet.get(queryElem.id); + } else { + currentQueryElemList = []; + queryElemSet.set(queryElem.id, currentQueryElemList); + } + currentQueryElemList.push(queryElem); + }; + for (const queryElem of queryElems) { + addQueryElemToQueryElemSet(queryElem); + } + /** + * @type Map + */ + const fnTypeSet = new Map(); + const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) { + // Pure generic, or an item that's not matched by any query elems. + // Try [unboxing] it. + // + // [unboxing]: + // http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf + const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice); + if (fnType.id === -1 || !( + queryElemSet.has(fnType.id) || + (fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) || + (fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem) + )) { + for (const innerFnType of fnType.generics) { + addFnTypeToFnTypeSet(innerFnType); } - let currentEntryElems; - if (elems.has(entry.id)) { - currentEntryElems = elems.get(entry.id); - } else { - currentEntryElems = []; - elems.set(entry.id, currentEntryElems); + return; + } + let currentQueryElemList = queryElemSet.get(fnType.id) || []; + let matchIdx = currentQueryElemList.findIndex(queryElem => { + return typePassesFilter(queryElem.typeFilter, fnType.ty) && + checkGenerics(fnType, queryElem); + }); + if (matchIdx === -1 && + (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) && + queryContainsArrayOrSliceElem + ) { + currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || []; + matchIdx = currentQueryElemList.findIndex(queryElem => { + return typePassesFilter(queryElem.typeFilter, fnType.ty) && + checkGenerics(fnType, queryElem); + }); + } + // None of the query elems match the function type. + // Try [unboxing] it. + if (matchIdx === -1) { + for (const innerFnType of fnType.generics) { + addFnTypeToFnTypeSet(innerFnType); } - currentEntryElems.push(entry); - }; - for (const entry of row.generics) { - addEntryToElems(entry); - } - // We need to find the type that matches the most to remove it in order - // to move forward. - const handleGeneric = generic => { - if (!elems.has(generic.id)) { - return false; + return; + } + let currentFnTypeList; + if (fnTypeSet.has(fnType.id)) { + currentFnTypeList = fnTypeSet.get(fnType.id); + } else { + currentFnTypeList = []; + fnTypeSet.set(fnType.id, currentFnTypeList); + } + currentFnTypeList.push(fnType); + }; + for (const fnType of fnTypes) { + addFnTypeToFnTypeSet(fnType); + } + const doHandleQueryElemList = (currentFnTypeList, queryElemList) => { + if (queryElemList.length === 0) { + return true; + } + // Multiple items in one list might match multiple items in another. + // Since an item with fewer generics can match an item with more, we + // need to check all combinations for a potential match. + const queryElem = queryElemList.pop(); + const l = currentFnTypeList.length; + for (let i = 0; i < l; i += 1) { + const fnType = currentFnTypeList[i]; + if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) { + continue; } - const matchElems = elems.get(generic.id); - const matchIdx = matchElems.findIndex(tmp_elem => { - if (generic.generics.length > 0 && !checkGenerics(tmp_elem, generic)) { - return false; + if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) { + currentFnTypeList.splice(i, 1); + const result = doHandleQueryElemList(currentFnTypeList, queryElemList); + if (result) { + return true; } - return typePassesFilter(generic.typeFilter, tmp_elem.ty); - }); - if (matchIdx === -1) { - return false; + currentFnTypeList.splice(i, 0, fnType); } - matchElems.splice(matchIdx, 1); - if (matchElems.length === 0) { - elems.delete(generic.id); + } + return false; + }; + const handleQueryElemList = (id, queryElemList) => { + if (!fnTypeSet.has(id)) { + if (id === typeNameIdOfArrayOrSlice) { + return handleQueryElemList(typeNameIdOfSlice, queryElemList) || + handleQueryElemList(typeNameIdOfArray, queryElemList); } - return true; - }; - // To do the right thing with type filters, we first process generics - // that have them, removing matching ones from the "bag," then do the - // ones with no type filter, which can match any entry regardless of its - // own type. - for (const generic of elem.generics) { - if (generic.typeFilter !== -1 && !handleGeneric(generic)) { - return false; + return false; + } + const currentFnTypeList = fnTypeSet.get(id); + if (currentFnTypeList.length < queryElemList.length) { + // It's not possible for all the query elems to find a match. + return false; + } + const result = doHandleQueryElemList(currentFnTypeList, queryElemList); + if (result) { + // Found a solution. + // Any items that weren't used for it can be unboxed, and might form + // part of the solution for another item. + for (const innerFnType of currentFnTypeList) { + addFnTypeToFnTypeSet(innerFnType); } + fnTypeSet.delete(id); } - for (const generic of elem.generics) { - if (generic.typeFilter === -1 && !handleGeneric(generic)) { - return false; + return result; + }; + let queryElemSetSize = -1; + while (queryElemSetSize !== queryElemSet.size) { + queryElemSetSize = queryElemSet.size; + for (const [id, queryElemList] of queryElemSet) { + if (handleQueryElemList(id, queryElemList)) { + queryElemSet.delete(id); } } - return true; } - return false; + return queryElemSetSize === 0; } /** * This function checks if the object (`row`) matches the given type (`elem`) and its * generics (if any). * - * @param {Row} row + * @param {Array} list * @param {QueryElement} elem - The element from the parsed query. * * @return {boolean} - Returns true if found, false otherwise. */ - function checkIfInGenerics(row, elem) { - for (const entry of row.generics) { + function checkIfInList(list, elem) { + for (const entry of list) { if (checkType(entry, elem)) { return true; } @@ -1214,10 +1541,15 @@ function initSearch(rawSearchIndex) { function checkType(row, elem) { if (row.id === -1) { // This is a pure "generic" search, no need to run other checks. - return row.generics.length > 0 ? checkIfInGenerics(row, elem) : false; + return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false; } - if (row.id === elem.id && typePassesFilter(elem.typeFilter, row.ty)) { + const matchesExact = row.id === elem.id; + const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice && + (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray); + + if ((matchesExact || matchesArrayOrSlice) && + typePassesFilter(elem.typeFilter, row.ty)) { if (elem.generics.length > 0) { return checkGenerics(row, elem); } @@ -1227,59 +1559,7 @@ function initSearch(rawSearchIndex) { // If the current item does not match, try [unboxing] the generic. // [unboxing]: // https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf - return checkIfInGenerics(row, elem); - } - - /** - * This function checks if the object (`row`) has an argument with the given type (`elem`). - * - * @param {Row} row - * @param {QueryElement} elem - The element from the parsed query. - * @param {Array} skipPositions - Do not return one of these positions. - * - * @return {integer} - Returns the position of the match, or -1 if none. - */ - function findArg(row, elem, skipPositions) { - if (row && row.type && row.type.inputs && row.type.inputs.length > 0) { - let i = 0; - for (const input of row.type.inputs) { - if (skipPositions.indexOf(i) !== -1) { - i += 1; - continue; - } - if (checkType(input, elem)) { - return i; - } - i += 1; - } - } - return -1; - } - - /** - * This function checks if the object (`row`) returns the given type (`elem`). - * - * @param {Row} row - * @param {QueryElement} elem - The element from the parsed query. - * @param {Array} skipPositions - Do not return one of these positions. - * - * @return {integer} - Returns the position of the matching item, or -1 if none. - */ - function checkReturned(row, elem, skipPositions) { - if (row && row.type && row.type.output.length > 0) { - let i = 0; - for (const ret_ty of row.type.output) { - if (skipPositions.indexOf(i) !== -1) { - i += 1; - continue; - } - if (checkType(ret_ty, elem)) { - return i; - } - i += 1; - } - } - return -1; + return checkIfInList(row.generics, elem); } function checkPath(contains, ty, maxEditDistance) { @@ -1480,14 +1760,14 @@ function initSearch(rawSearchIndex) { const fullId = row.id; const searchWord = searchWords[pos]; - const in_args = findArg(row, elem, []); - if (in_args !== -1) { + const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem); + if (in_args) { // path_dist is 0 because no parent path information is currently stored // in the search index addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance); } - const returned = checkReturned(row, elem, []); - if (returned !== -1) { + const returned = row.type && row.type.output && checkIfInList(row.type.output, elem); + if (returned) { addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance); } @@ -1543,32 +1823,15 @@ function initSearch(rawSearchIndex) { * @param {Object} results */ function handleArgs(row, pos, results) { - if (!row || (filterCrates !== null && row.crate !== filterCrates)) { + if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) { return; } // If the result is too "bad", we return false and it ends this search. - function checkArgs(elems, callback) { - const skipPositions = []; - for (const elem of elems) { - // There is more than one parameter to the query so all checks should be "exact" - const position = callback( - row, - elem, - skipPositions - ); - if (position !== -1) { - skipPositions.push(position); - } else { - return false; - } - } - return true; - } - if (!checkArgs(parsedQuery.elems, findArg)) { + if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) { return; } - if (!checkArgs(parsedQuery.returned, checkReturned)) { + if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) { return; } @@ -1655,12 +1918,9 @@ function initSearch(rawSearchIndex) { elem = parsedQuery.returned[0]; for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { row = searchIndex[i]; - in_returned = checkReturned( - row, - elem, - [] - ); - if (in_returned !== -1) { + in_returned = row.type && + unifyFunctionTypes(row.type.output, parsedQuery.returned); + if (in_returned) { addIntoResults( results_others, row.id, @@ -1836,16 +2096,11 @@ function initSearch(rawSearchIndex) { array.forEach(item => { const name = item.name; const type = itemTypes[item.ty]; + const longType = longItemTypes[item.ty]; + const typeName = longType.length !== 0 ? `${longType}` : "?"; length += 1; - let extra = ""; - if (type === "primitive") { - extra = " (primitive type)"; - } else if (type === "keyword") { - extra = " (keyword)"; - } - const link = document.createElement("a"); link.className = "result-" + type; link.href = item.href; @@ -1853,24 +2108,22 @@ function initSearch(rawSearchIndex) { const resultName = document.createElement("div"); resultName.className = "result-name"; - if (item.is_alias) { - const alias = document.createElement("span"); - alias.className = "alias"; - - const bold = document.createElement("b"); - bold.innerText = item.alias; - alias.appendChild(bold); - - alias.insertAdjacentHTML( - "beforeend", - " - see "); + resultName.insertAdjacentHTML( + "beforeend", + `${typeName}`); + link.appendChild(resultName); - resultName.appendChild(alias); + let alias = " "; + if (item.is_alias) { + alias = `

    \ +${item.alias} - see \ +
    `; } resultName.insertAdjacentHTML( "beforeend", - item.displayPath + "" + name + extra + ""); - link.appendChild(resultName); + `
    ${alias}\ +${item.displayPath}${name}\ +
    `); const description = document.createElement("div"); description.className = "desc"; @@ -1916,6 +2169,20 @@ function initSearch(rawSearchIndex) { if (go_to_first || (results.others.length === 1 && getSettingValue("go-to-only-result") === "true") ) { + // Needed to force re-execution of JS when coming back to a page. Let's take this + // scenario as example: + // + // 1. You have the "Directly go to item in search if there is only one result" option + // enabled. + // 2. You make a search which results only one result, leading you automatically to + // this result. + // 3. You go back to previous page. + // + // Now, without the call below, the JS will not be re-executed and the previous state + // will be used, starting search again since the search input is not empty, leading you + // back to the previous page again. + window.onunload = () => {}; + searchState.removeQueryParameters(); const elem = document.createElement("a"); elem.href = results.others[0].href; removeClass(elem, "active"); @@ -1967,7 +2234,7 @@ function initSearch(rawSearchIndex) { error.forEach((value, index) => { value = value.split("<").join("<").split(">").join(">"); if (index % 2 !== 0) { - error[index] = `${value}`; + error[index] = `${value.replaceAll(" ", " ")}`; } else { error[index] = value; } @@ -2030,6 +2297,18 @@ function initSearch(rawSearchIndex) { printTab(currentTab); } + function updateSearchHistory(url) { + if (!browserSupportsHistoryApi()) { + return; + } + const params = searchState.getQueryStringParams(); + if (!history.state && !params.search) { + history.pushState(null, "", url); + } else { + history.replaceState(null, "", url); + } + } + /** * Perform a search based on the current state of the search input element * and display the results. @@ -2040,7 +2319,6 @@ function initSearch(rawSearchIndex) { if (e) { e.preventDefault(); } - const query = parseQuery(searchState.input.value.trim()); let filterCrates = getFilterCrates(); @@ -2066,15 +2344,7 @@ function initSearch(rawSearchIndex) { // Because searching is incremental by character, only the most // recent search query is added to the browser history. - if (browserSupportsHistoryApi()) { - const newURL = buildUrl(query.original, filterCrates); - - if (!history.state && !params.search) { - history.pushState(null, "", newURL); - } else { - history.replaceState(null, "", newURL); - } - } + updateSearchHistory(buildUrl(query.original, filterCrates)); showResults( execQuery(query, searchWords, filterCrates, window.currentCrate), @@ -2082,34 +2352,6 @@ function initSearch(rawSearchIndex) { filterCrates); } - /** - * Add an item to the type Name->ID map, or, if one already exists, use it. - * Returns the number. If name is "" or null, return -1 (pure generic). - * - * This is effectively string interning, so that function matching can be - * done more quickly. Two types with the same name but different item kinds - * get the same ID. - * - * @param {Map} typeNameIdMap - * @param {string} name - * - * @returns {integer} - */ - function buildTypeMapIndex(typeNameIdMap, name) { - - if (name === "" || name === null) { - return -1; - } - - if (typeNameIdMap.has(name)) { - return typeNameIdMap.get(name); - } else { - const id = typeNameIdMap.size; - typeNameIdMap.set(name, id); - return id; - } - } - /** * Convert a list of RawFunctionType / ID to object-based FunctionType. * @@ -2128,7 +2370,7 @@ function initSearch(rawSearchIndex) { * * @return {Array} */ - function buildItemSearchTypeAll(types, lowercasePaths, typeNameIdMap) { + function buildItemSearchTypeAll(types, lowercasePaths) { const PATH_INDEX_DATA = 0; const GENERICS_DATA = 1; return types.map(type => { @@ -2140,15 +2382,14 @@ function initSearch(rawSearchIndex) { pathIndex = type[PATH_INDEX_DATA]; generics = buildItemSearchTypeAll( type[GENERICS_DATA], - lowercasePaths, - typeNameIdMap + lowercasePaths ); } return { // `0` is used as a sentinel because it's fewer bytes than `null` id: pathIndex === 0 ? -1 - : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name), + : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name), ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, generics: generics, }; @@ -2171,7 +2412,7 @@ function initSearch(rawSearchIndex) { * * @return {null|FunctionSearchType} */ - function buildFunctionSearchType(functionSearchType, lowercasePaths, typeNameIdMap) { + function buildFunctionSearchType(functionSearchType, lowercasePaths) { const INPUTS_DATA = 0; const OUTPUT_DATA = 1; // `0` is used as a sentinel because it's fewer bytes than `null` @@ -2184,15 +2425,14 @@ function initSearch(rawSearchIndex) { inputs = [{ id: pathIndex === 0 ? -1 - : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name), + : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name), ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, generics: [], }]; } else { inputs = buildItemSearchTypeAll( functionSearchType[INPUTS_DATA], - lowercasePaths, - typeNameIdMap + lowercasePaths ); } if (functionSearchType.length > 1) { @@ -2201,15 +2441,14 @@ function initSearch(rawSearchIndex) { output = [{ id: pathIndex === 0 ? -1 - : buildTypeMapIndex(typeNameIdMap, lowercasePaths[pathIndex - 1].name), + : buildTypeMapIndex(lowercasePaths[pathIndex - 1].name), ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty, generics: [], }]; } else { output = buildItemSearchTypeAll( functionSearchType[OUTPUT_DATA], - lowercasePaths, - typeNameIdMap + lowercasePaths ); } } else { @@ -2233,6 +2472,12 @@ function initSearch(rawSearchIndex) { let currentIndex = 0; let id = 0; + // Initialize type map indexes for primitive list types + // that can be searched using `[]` syntax. + typeNameIdOfArray = buildTypeMapIndex("array"); + typeNameIdOfSlice = buildTypeMapIndex("slice"); + typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]"); + for (const crate in rawSearchIndex) { if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) { continue; @@ -2363,8 +2608,7 @@ function initSearch(rawSearchIndex) { parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, type: buildFunctionSearchType( itemFunctionSearchTypes[i], - lowercasePaths, - typeNameIdMap + lowercasePaths ), id: id, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), @@ -2566,13 +2810,8 @@ function initSearch(rawSearchIndex) { function updateCrate(ev) { if (ev.target.value === "all crates") { // If we don't remove it from the URL, it'll be picked up again by the search. - const params = searchState.getQueryStringParams(); const query = searchState.input.value.trim(); - if (!history.state && !params.search) { - history.pushState(null, "", buildUrl(query, null)); - } else { - history.replaceState(null, "", buildUrl(query, null)); - } + updateSearchHistory(buildUrl(query, null)); } // In case you "cut" the entry from the search input, then change the crate filter // before paste back the previous search, you get the old search results without diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js deleted file mode 100644 index d999f3b36fd82..0000000000000 --- a/src/librustdoc/html/static/js/source-script.js +++ /dev/null @@ -1,235 +0,0 @@ -// From rust: -/* global sourcesIndex */ - -// Local js definitions: -/* global addClass, getCurrentValue, onEachLazy, removeClass, browserSupportsHistoryApi */ -/* global updateLocalStorage */ - -"use strict"; - -(function() { - -const rootPath = document.getElementById("rustdoc-vars").attributes["data-root-path"].value; - -const NAME_OFFSET = 0; -const DIRS_OFFSET = 1; -const FILES_OFFSET = 2; - -// WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY -// If you update this line, then you also need to update the media query with the same -// warning in rustdoc.css -const RUSTDOC_MOBILE_BREAKPOINT = 700; - -function closeSidebarIfMobile() { - if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) { - updateLocalStorage("source-sidebar-show", "false"); - } -} - -function createDirEntry(elem, parent, fullPath, hasFoundFile) { - const dirEntry = document.createElement("details"); - const summary = document.createElement("summary"); - - dirEntry.className = "dir-entry"; - - fullPath += elem[NAME_OFFSET] + "/"; - - summary.innerText = elem[NAME_OFFSET]; - dirEntry.appendChild(summary); - - const folders = document.createElement("div"); - folders.className = "folders"; - if (elem[DIRS_OFFSET]) { - for (const dir of elem[DIRS_OFFSET]) { - if (createDirEntry(dir, folders, fullPath, false)) { - dirEntry.open = true; - hasFoundFile = true; - } - } - } - dirEntry.appendChild(folders); - - const files = document.createElement("div"); - files.className = "files"; - if (elem[FILES_OFFSET]) { - const w = window.location.href.split("#")[0]; - for (const file_text of elem[FILES_OFFSET]) { - const file = document.createElement("a"); - file.innerText = file_text; - file.href = rootPath + "src/" + fullPath + file_text + ".html"; - file.addEventListener("click", closeSidebarIfMobile); - if (!hasFoundFile && w === file.href) { - file.className = "selected"; - dirEntry.open = true; - hasFoundFile = true; - } - files.appendChild(file); - } - } - dirEntry.appendChild(files); - parent.appendChild(dirEntry); - return hasFoundFile; -} - -function toggleSidebar() { - const child = this.parentNode.children[0]; - if (child.innerText === ">") { - addClass(document.documentElement, "source-sidebar-expanded"); - child.innerText = "<"; - updateLocalStorage("source-sidebar-show", "true"); - } else { - removeClass(document.documentElement, "source-sidebar-expanded"); - child.innerText = ">"; - updateLocalStorage("source-sidebar-show", "false"); - } -} - -function createSidebarToggle() { - const sidebarToggle = document.createElement("div"); - sidebarToggle.id = "src-sidebar-toggle"; - - const inner = document.createElement("button"); - - if (getCurrentValue("source-sidebar-show") === "true") { - inner.innerText = "<"; - } else { - inner.innerText = ">"; - } - inner.onclick = toggleSidebar; - - sidebarToggle.appendChild(inner); - return sidebarToggle; -} - -// This function is called from "source-files.js", generated in `html/render/write_shared.rs`. -// eslint-disable-next-line no-unused-vars -function createSourceSidebar() { - const container = document.querySelector("nav.sidebar"); - - const sidebarToggle = createSidebarToggle(); - container.insertBefore(sidebarToggle, container.firstChild); - - const sidebar = document.createElement("div"); - sidebar.id = "source-sidebar"; - - let hasFoundFile = false; - - const title = document.createElement("div"); - title.className = "title"; - title.innerText = "Files"; - sidebar.appendChild(title); - Object.keys(sourcesIndex).forEach(key => { - sourcesIndex[key][NAME_OFFSET] = key; - hasFoundFile = createDirEntry(sourcesIndex[key], sidebar, "", hasFoundFile); - }); - - container.appendChild(sidebar); - // Focus on the current file in the source files sidebar. - const selected_elem = sidebar.getElementsByClassName("selected")[0]; - if (typeof selected_elem !== "undefined") { - selected_elem.focus(); - } -} - -const lineNumbersRegex = /^#?(\d+)(?:-(\d+))?$/; - -function highlightSourceLines(match) { - if (typeof match === "undefined") { - match = window.location.hash.match(lineNumbersRegex); - } - if (!match) { - return; - } - let from = parseInt(match[1], 10); - let to = from; - if (typeof match[2] !== "undefined") { - to = parseInt(match[2], 10); - } - if (to < from) { - const tmp = to; - to = from; - from = tmp; - } - let elem = document.getElementById(from); - if (!elem) { - return; - } - const x = document.getElementById(from); - if (x) { - x.scrollIntoView(); - } - onEachLazy(document.getElementsByClassName("src-line-numbers"), e => { - onEachLazy(e.getElementsByTagName("a"), i_e => { - removeClass(i_e, "line-highlighted"); - }); - }); - for (let i = from; i <= to; ++i) { - elem = document.getElementById(i); - if (!elem) { - break; - } - addClass(elem, "line-highlighted"); - } -} - -const handleSourceHighlight = (function() { - let prev_line_id = 0; - - const set_fragment = name => { - const x = window.scrollX, - y = window.scrollY; - if (browserSupportsHistoryApi()) { - history.replaceState(null, null, "#" + name); - highlightSourceLines(); - } else { - location.replace("#" + name); - } - // Prevent jumps when selecting one or many lines - window.scrollTo(x, y); - }; - - return ev => { - let cur_line_id = parseInt(ev.target.id, 10); - // This event handler is attached to the entire line number column, but it should only - // be run if one of the anchors is clicked. It also shouldn't do anything if the anchor - // is clicked with a modifier key (to open a new browser tab). - if (isNaN(cur_line_id) || - ev.ctrlKey || - ev.altKey || - ev.metaKey) { - return; - } - ev.preventDefault(); - - if (ev.shiftKey && prev_line_id) { - // Swap selection if needed - if (prev_line_id > cur_line_id) { - const tmp = prev_line_id; - prev_line_id = cur_line_id; - cur_line_id = tmp; - } - - set_fragment(prev_line_id + "-" + cur_line_id); - } else { - prev_line_id = cur_line_id; - - set_fragment(cur_line_id); - } - }; -}()); - -window.addEventListener("hashchange", () => { - const match = window.location.hash.match(lineNumbersRegex); - if (match) { - return highlightSourceLines(match); - } -}); - -onEachLazy(document.getElementsByClassName("src-line-numbers"), el => { - el.addEventListener("click", handleSourceHighlight); -}); - -highlightSourceLines(); - -window.createSourceSidebar = createSourceSidebar; -})(); diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js new file mode 100644 index 0000000000000..679c2341f02ba --- /dev/null +++ b/src/librustdoc/html/static/js/src-script.js @@ -0,0 +1,235 @@ +// From rust: +/* global srcIndex */ + +// Local js definitions: +/* global addClass, getCurrentValue, onEachLazy, removeClass, browserSupportsHistoryApi */ +/* global updateLocalStorage, getVar */ + +"use strict"; + +(function() { + +const rootPath = getVar("root-path"); + +const NAME_OFFSET = 0; +const DIRS_OFFSET = 1; +const FILES_OFFSET = 2; + +// WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY +// If you update this line, then you also need to update the media query with the same +// warning in rustdoc.css +const RUSTDOC_MOBILE_BREAKPOINT = 700; + +function closeSidebarIfMobile() { + if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) { + updateLocalStorage("source-sidebar-show", "false"); + } +} + +function createDirEntry(elem, parent, fullPath, hasFoundFile) { + const dirEntry = document.createElement("details"); + const summary = document.createElement("summary"); + + dirEntry.className = "dir-entry"; + + fullPath += elem[NAME_OFFSET] + "/"; + + summary.innerText = elem[NAME_OFFSET]; + dirEntry.appendChild(summary); + + const folders = document.createElement("div"); + folders.className = "folders"; + if (elem[DIRS_OFFSET]) { + for (const dir of elem[DIRS_OFFSET]) { + if (createDirEntry(dir, folders, fullPath, false)) { + dirEntry.open = true; + hasFoundFile = true; + } + } + } + dirEntry.appendChild(folders); + + const files = document.createElement("div"); + files.className = "files"; + if (elem[FILES_OFFSET]) { + const w = window.location.href.split("#")[0]; + for (const file_text of elem[FILES_OFFSET]) { + const file = document.createElement("a"); + file.innerText = file_text; + file.href = rootPath + "src/" + fullPath + file_text + ".html"; + file.addEventListener("click", closeSidebarIfMobile); + if (!hasFoundFile && w === file.href) { + file.className = "selected"; + dirEntry.open = true; + hasFoundFile = true; + } + files.appendChild(file); + } + } + dirEntry.appendChild(files); + parent.appendChild(dirEntry); + return hasFoundFile; +} + +function toggleSidebar() { + const child = this.parentNode.children[0]; + if (child.innerText === ">") { + addClass(document.documentElement, "src-sidebar-expanded"); + child.innerText = "<"; + updateLocalStorage("source-sidebar-show", "true"); + } else { + removeClass(document.documentElement, "src-sidebar-expanded"); + child.innerText = ">"; + updateLocalStorage("source-sidebar-show", "false"); + } +} + +function createSidebarToggle() { + const sidebarToggle = document.createElement("div"); + sidebarToggle.id = "src-sidebar-toggle"; + + const inner = document.createElement("button"); + + if (getCurrentValue("source-sidebar-show") === "true") { + inner.innerText = "<"; + } else { + inner.innerText = ">"; + } + inner.onclick = toggleSidebar; + + sidebarToggle.appendChild(inner); + return sidebarToggle; +} + +// This function is called from "src-files.js", generated in `html/render/write_shared.rs`. +// eslint-disable-next-line no-unused-vars +function createSrcSidebar() { + const container = document.querySelector("nav.sidebar"); + + const sidebarToggle = createSidebarToggle(); + container.insertBefore(sidebarToggle, container.firstChild); + + const sidebar = document.createElement("div"); + sidebar.id = "src-sidebar"; + + let hasFoundFile = false; + + const title = document.createElement("div"); + title.className = "title"; + title.innerText = "Files"; + sidebar.appendChild(title); + Object.keys(srcIndex).forEach(key => { + srcIndex[key][NAME_OFFSET] = key; + hasFoundFile = createDirEntry(srcIndex[key], sidebar, "", hasFoundFile); + }); + + container.appendChild(sidebar); + // Focus on the current file in the source files sidebar. + const selected_elem = sidebar.getElementsByClassName("selected")[0]; + if (typeof selected_elem !== "undefined") { + selected_elem.focus(); + } +} + +const lineNumbersRegex = /^#?(\d+)(?:-(\d+))?$/; + +function highlightSrcLines(match) { + if (typeof match === "undefined") { + match = window.location.hash.match(lineNumbersRegex); + } + if (!match) { + return; + } + let from = parseInt(match[1], 10); + let to = from; + if (typeof match[2] !== "undefined") { + to = parseInt(match[2], 10); + } + if (to < from) { + const tmp = to; + to = from; + from = tmp; + } + let elem = document.getElementById(from); + if (!elem) { + return; + } + const x = document.getElementById(from); + if (x) { + x.scrollIntoView(); + } + onEachLazy(document.getElementsByClassName("src-line-numbers"), e => { + onEachLazy(e.getElementsByTagName("a"), i_e => { + removeClass(i_e, "line-highlighted"); + }); + }); + for (let i = from; i <= to; ++i) { + elem = document.getElementById(i); + if (!elem) { + break; + } + addClass(elem, "line-highlighted"); + } +} + +const handleSrcHighlight = (function() { + let prev_line_id = 0; + + const set_fragment = name => { + const x = window.scrollX, + y = window.scrollY; + if (browserSupportsHistoryApi()) { + history.replaceState(null, null, "#" + name); + highlightSrcLines(); + } else { + location.replace("#" + name); + } + // Prevent jumps when selecting one or many lines + window.scrollTo(x, y); + }; + + return ev => { + let cur_line_id = parseInt(ev.target.id, 10); + // This event handler is attached to the entire line number column, but it should only + // be run if one of the anchors is clicked. It also shouldn't do anything if the anchor + // is clicked with a modifier key (to open a new browser tab). + if (isNaN(cur_line_id) || + ev.ctrlKey || + ev.altKey || + ev.metaKey) { + return; + } + ev.preventDefault(); + + if (ev.shiftKey && prev_line_id) { + // Swap selection if needed + if (prev_line_id > cur_line_id) { + const tmp = prev_line_id; + prev_line_id = cur_line_id; + cur_line_id = tmp; + } + + set_fragment(prev_line_id + "-" + cur_line_id); + } else { + prev_line_id = cur_line_id; + + set_fragment(cur_line_id); + } + }; +}()); + +window.addEventListener("hashchange", () => { + const match = window.location.hash.match(lineNumbersRegex); + if (match) { + return highlightSrcLines(match); + } +}); + +onEachLazy(document.getElementsByClassName("src-line-numbers"), el => { + el.addEventListener("click", handleSrcHighlight); +}); + +highlightSrcLines(); + +window.createSrcSidebar = createSrcSidebar; +})(); diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index 93979a944183b..af3ca42a6c078 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -108,7 +108,7 @@ function getCurrentValue(name) { // Get a value from the rustdoc-vars div, which is used to convey data from // Rust to the JS. If there is no such element, return null. const getVar = (function getVar(name) { - const el = document.getElementById("rustdoc-vars"); + const el = document.querySelector("head > meta[name='rustdoc-vars']"); return el ? el.attributes["data-" + name].value : null; }); @@ -185,7 +185,7 @@ updateTheme(); if (getSettingValue("source-sidebar-show") === "true") { // At this point in page load, `document.body` is not available yet. // Set a class on the `` element instead. - addClass(document.documentElement, "source-sidebar-expanded"); + addClass(document.documentElement, "src-sidebar-expanded"); } // If we navigate away (for example to a settings page), and then use the back or diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs index 767b974cc9109..a27aa2b58d245 100644 --- a/src/librustdoc/html/static_files.rs +++ b/src/librustdoc/html/static_files.rs @@ -53,7 +53,7 @@ pub(crate) fn suffix_path(filename: &str, suffix: &str) -> PathBuf { // which would result in `style.min-suffix.css` which isn't what we // want. let (base, ext) = filename.split_once('.').unwrap(); - let filename = format!("{}{}.{}", base, suffix, ext); + let filename = format!("{base}{suffix}.{ext}"); filename.into() } @@ -97,7 +97,7 @@ static_files! { main_js => "static/js/main.js", search_js => "static/js/search.js", settings_js => "static/js/settings.js", - source_script_js => "static/js/source-script.js", + src_script_js => "static/js/src-script.js", storage_js => "static/js/storage.js", scrape_examples_js => "static/js/scrape-examples.js", wheel_svg => "static/images/wheel.svg", diff --git a/src/librustdoc/html/templates/STYLE.md b/src/librustdoc/html/templates/STYLE.md index 0281b1c47f8ce..38aac2a60e94d 100644 --- a/src/librustdoc/html/templates/STYLE.md +++ b/src/librustdoc/html/templates/STYLE.md @@ -32,7 +32,7 @@ Askama templates support quite sophisticated control flow. To keep our templates simple and understandable, we use only a subset: `if` and `for`. In particular we avoid [assignments in the template logic][assignments] and [Askama macros][macros]. This also may make things easier if we switch to a different -Jinja-style template system, like Askama, in the future. +Jinja-style template system in the future. [assignments]: https://djc.github.io/askama/template_syntax.html#assignments [macros]: https://djc.github.io/askama/template_syntax.html#macros diff --git a/src/librustdoc/html/templates/item_info.html b/src/librustdoc/html/templates/item_info.html index d2ea9bdae9c65..9e65ae95e15d2 100644 --- a/src/librustdoc/html/templates/item_info.html +++ b/src/librustdoc/html/templates/item_info.html @@ -1,5 +1,5 @@ {% if !items.is_empty() %} - {# #} + {% for item in items %} {{item|safe}} {# #} {% endfor %} diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html index a01457971c178..f6d2fa3489081 100644 --- a/src/librustdoc/html/templates/item_union.html +++ b/src/librustdoc/html/templates/item_union.html @@ -4,14 +4,15 @@ {{ self.document() | safe }} {% if self.fields_iter().peek().is_some() %} -

    - Fields§ +

    {# #} + Fields§ {# #}

    {% for (field, ty) in self.fields_iter() %} {% let name = field.name.expect("union field name") %} - - § - {{ name }}: {{ self.print_ty(ty) | safe }} + {# #} + § {# #} + {{ name }}: {{+ self.print_ty(ty) | safe }} {# #} {% if let Some(stability_class) = self.stability_field(field) %} diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index 9133f899af60d..60ccfe4da4418 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -24,13 +24,14 @@ {% endfor %} > {# #} {% endif %} -
    {# #} -
    {# #} {# #} {% if page.css_class.contains("crate") %} {# #} - {% else if page.css_class == "source" %} - {# #} - {# #} + {% else if page.css_class == "src" %} + {# #} + {# #} {% else if !page.css_class.contains("mod") %} {# #} {% endif %} @@ -85,7 +85,7 @@ {# #} {# #} {{ layout.external_html.before_content|safe }} - {% if page.css_class != "source" %} + {% if page.css_class != "src" %} {# #} {% endif %} {# #}
    {# #} - {% if page.css_class != "source" %}{% endif %}
    {# #} {{ layout.external_html.after_content|safe }} {# #} diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index edabac9a08231..1d215c26968d8 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -1,5 +1,5 @@
    {# #} -

    {# #} +

    {{typ}} {# The breadcrumbs of the item path, like std::string #} {% for component in path_components %} @@ -12,13 +12,13 @@

    {# #} alt="Copy item path"> {# #} {# #}

    {# #} - {# #} + {% if !stability_since_raw.is_empty() %} {{ stability_since_raw|safe +}} · {#+ #} {% endif %} {% match src_href %} {% when Some with (href) %} -
    source · {#+ #} + source · {#+ #} {% else %} {% endmatch %} @@ -517,7 +519,8 @@

    Clippy Lints

    {{lint.id}} - + 📋 diff --git a/src/tools/clippy/util/gh-pages/script.js b/src/tools/clippy/util/gh-pages/script.js index 1c16ecd6b0b1f..f59245e556cd2 100644 --- a/src/tools/clippy/util/gh-pages/script.js +++ b/src/tools/clippy/util/gh-pages/script.js @@ -24,9 +24,9 @@ target.scrollIntoView(); } - function scrollToLintByURL($scope) { - var removeListener = $scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) { - scrollToLint(window.location.hash.slice(1)); + function scrollToLintByURL($scope, $location) { + var removeListener = $scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) { + scrollToLint($location.path().substring(1)); removeListener(); }); } @@ -106,10 +106,10 @@ } }; }) - .controller("lintList", function ($scope, $http, $timeout) { + .controller("lintList", function ($scope, $http, $location, $timeout) { // Level filter var LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; - $scope.levels = LEVEL_FILTERS_DEFAULT; + $scope.levels = { ...LEVEL_FILTERS_DEFAULT }; $scope.byLevels = function (lint) { return $scope.levels[lint.level]; }; @@ -146,6 +146,165 @@ "=": {enabled: false, minorVersion: null }, }; + // Map the versionFilters to the query parameters in a way that is easier to work with in a URL + const versionFilterKeyMap = { + "≥": "gte", + "≤": "lte", + "=": "eq" + }; + const reverseVersionFilterKeyMap = Object.fromEntries( + Object.entries(versionFilterKeyMap).map(([key, value]) => [value, key]) + ); + + // loadFromURLParameters retrieves filter settings from the URL parameters and assigns them + // to corresponding $scope variables. + function loadFromURLParameters() { + // Extract parameters from URL + const urlParameters = $location.search(); + + // Define a helper function that assigns URL parameters to a provided scope variable + const handleParameter = (parameter, scopeVariable, defaultValues) => { + if (urlParameters[parameter]) { + const items = urlParameters[parameter].split(','); + for (const key in scopeVariable) { + if (scopeVariable.hasOwnProperty(key)) { + scopeVariable[key] = items.includes(key); + } + } + } else if (defaultValues) { + for (const key in defaultValues) { + if (scopeVariable.hasOwnProperty(key)) { + scopeVariable[key] = defaultValues[key]; + } + } + } + }; + + handleParameter('levels', $scope.levels, LEVEL_FILTERS_DEFAULT); + handleParameter('groups', $scope.groups, GROUPS_FILTER_DEFAULT); + + // Handle 'versions' parameter separately because it needs additional processing + if (urlParameters.versions) { + const versionFilters = urlParameters.versions.split(','); + for (const versionFilter of versionFilters) { + const [key, minorVersion] = versionFilter.split(':'); + const parsedMinorVersion = parseInt(minorVersion); + + // Map the key from the URL parameter to its original form + const originalKey = reverseVersionFilterKeyMap[key]; + + if (originalKey in $scope.versionFilters && !isNaN(parsedMinorVersion)) { + $scope.versionFilters[originalKey].enabled = true; + $scope.versionFilters[originalKey].minorVersion = parsedMinorVersion; + } + } + } + + // Load the search parameter from the URL path + const searchParameter = $location.path().substring(1); // Remove the leading slash + if (searchParameter) { + $scope.search = searchParameter; + $scope.open[searchParameter] = true; + scrollToLintByURL($scope, $location); + } + } + + // updateURLParameter updates the URL parameter with the given key to the given value + function updateURLParameter(filterObj, urlKey, defaultValue = {}, processFilter = filter => filter) { + const parameter = Object.keys(filterObj) + .filter(filter => filterObj[filter]) + .sort() + .map(processFilter) + .filter(Boolean) // Filters out any falsy values, including null + .join(','); + + const defaultParameter = Object.keys(defaultValue) + .filter(filter => defaultValue[filter]) + .sort() + .map(processFilter) + .filter(Boolean) // Filters out any falsy values, including null + .join(','); + + // if we ended up back at the defaults, just remove it from the URL + if (parameter === defaultParameter) { + $location.search(urlKey, null); + } else { + $location.search(urlKey, parameter || null); + } + } + + // updateVersionURLParameter updates the version URL parameter with the given version filters + function updateVersionURLParameter(versionFilters) { + updateURLParameter( + versionFilters, + 'versions', {}, + versionFilter => versionFilters[versionFilter].enabled && versionFilters[versionFilter].minorVersion != null + ? `${versionFilterKeyMap[versionFilter]}:${versionFilters[versionFilter].minorVersion}` + : null + ); + } + + // updateAllURLParameters updates all the URL parameters with the current filter settings + function updateAllURLParameters() { + updateURLParameter($scope.levels, 'levels', LEVEL_FILTERS_DEFAULT); + updateURLParameter($scope.groups, 'groups', GROUPS_FILTER_DEFAULT); + updateVersionURLParameter($scope.versionFilters); + } + + // Add $watches to automatically update URL parameters when the data changes + $scope.$watch('levels', function (newVal, oldVal) { + if (newVal !== oldVal) { + updateURLParameter(newVal, 'levels', LEVEL_FILTERS_DEFAULT); + } + }, true); + + $scope.$watch('groups', function (newVal, oldVal) { + if (newVal !== oldVal) { + updateURLParameter(newVal, 'groups', GROUPS_FILTER_DEFAULT); + } + }, true); + + $scope.$watch('versionFilters', function (newVal, oldVal) { + if (newVal !== oldVal) { + updateVersionURLParameter(newVal); + } + }, true); + + // Watch for changes in the URL path and update the search and lint display + $scope.$watch(function () { return $location.path(); }, function (newPath) { + const searchParameter = newPath.substring(1); + if ($scope.search !== searchParameter) { + $scope.search = searchParameter; + $scope.open[searchParameter] = true; + scrollToLintByURL($scope, $location); + } + }); + + let debounceTimeout; + $scope.$watch('search', function (newVal, oldVal) { + if (newVal !== oldVal) { + if (debounceTimeout) { + $timeout.cancel(debounceTimeout); + } + + debounceTimeout = $timeout(function () { + $location.path(newVal); + }, 1000); + } + }); + + $scope.$watch(function () { return $location.search(); }, function (newParameters) { + loadFromURLParameters(); + }, true); + + $scope.updatePath = function () { + if (debounceTimeout) { + $timeout.cancel(debounceTimeout); + } + + $location.path($scope.search); + } + $scope.selectTheme = function (theme) { setTheme(theme, true); } @@ -169,10 +328,9 @@ }; $scope.resetGroupsToDefault = function () { - const groups = $scope.groups; - for (const [key, value] of Object.entries(GROUPS_FILTER_DEFAULT)) { - groups[key] = value; - } + $scope.groups = { + ...GROUPS_FILTER_DEFAULT + }; }; $scope.selectedValuesCount = function (obj) { @@ -272,6 +430,12 @@ return true; } + // Show details for one lint + $scope.openLint = function (lint) { + $scope.open[lint.id] = true; + $location.path(lint.id); + }; + $scope.copyToClipboard = function (lint) { const clipboard = document.getElementById("clipboard-" + lint.id); if (clipboard) { @@ -296,14 +460,12 @@ // Get data $scope.open = {}; $scope.loading = true; + // This will be used to jump into the source code of the version that this documentation is for. $scope.docVersion = window.location.pathname.split('/')[2] || "master"; - if (window.location.hash.length > 1) { - $scope.search = window.location.hash.slice(1); - $scope.open[window.location.hash.slice(1)] = true; - scrollToLintByURL($scope); - } + // Set up the filters from the URL parameters before we start loading the data + loadFromURLParameters(); $http.get('./lints.json') .success(function (data) { @@ -315,7 +477,7 @@ selectGroup($scope, selectedGroup.toLowerCase()); } - scrollToLintByURL($scope); + scrollToLintByURL($scope, $location); setTimeout(function () { var el = document.getElementById('filter-input'); @@ -326,18 +488,6 @@ $scope.error = data; $scope.loading = false; }); - - window.addEventListener('hashchange', function () { - // trigger re-render - $timeout(function () { - $scope.levels = LEVEL_FILTERS_DEFAULT; - $scope.search = window.location.hash.slice(1); - $scope.open[window.location.hash.slice(1)] = true; - - scrollToLintByURL($scope); - }); - return true; - }, false); }); })(); diff --git a/src/tools/clippy/util/gh-pages/versions.html b/src/tools/clippy/util/gh-pages/versions.html index 6e810a349bfcc..31ce881932952 100644 --- a/src/tools/clippy/util/gh-pages/versions.html +++ b/src/tools/clippy/util/gh-pages/versions.html @@ -36,7 +36,7 @@

    @@ -54,18 +54,15 @@

    .controller('docVersions', function ($scope, $http) { $scope.loading = true; - $scope.normalizeVersionDisplay = function(v) { - return v.replace(/^v/, ''); - }; - $scope.normalizeVersion = function(v) { - return v.replace(/^v/, '').replace(/^rust-/, ''); + return v.replace(/^rust-/, ''); }; $scope.versionOrder = function(v) { if (v === 'master') { return Infinity; } if (v === 'stable') { return Number.MAX_VALUE; } if (v === 'beta') { return Number.MAX_VALUE - 1; } + if (v === 'pre-1.29.0') { return Number.MIN_VALUE; } return $scope.normalizeVersion(v) .split('.') diff --git a/src/tools/clippy/util/versions.py b/src/tools/clippy/util/versions.py index 0cfa007d1b271..c041fc606a8f3 100755 --- a/src/tools/clippy/util/versions.py +++ b/src/tools/clippy/util/versions.py @@ -1,24 +1,27 @@ #!/usr/bin/env python import json +import logging as log import os import sys -import logging as log -log.basicConfig(level=log.INFO, format='%(levelname)s: %(message)s') + +log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") def key(v): - if v == 'master': - return float('inf') - if v == 'stable': + if v == "master": + return float("inf") + if v == "stable": return sys.maxsize - if v == 'beta': + if v == "beta": return sys.maxsize - 1 + if v == "pre-1.29.0": + return -1 - v = v.replace('v', '').replace('rust-', '') + v = v.replace("rust-", "") s = 0 - for i, val in enumerate(v.split('.')[::-1]): + for i, val in enumerate(v.split(".")[::-1]): s += int(val) * 100**i return s @@ -31,7 +34,11 @@ def main(): outdir = sys.argv[1] versions = [ - dir for dir in os.listdir(outdir) if not dir.startswith(".") and os.path.isdir(os.path.join(outdir, dir)) + dir + for dir in os.listdir(outdir) + if not dir.startswith(".") + and not dir.startswith("v") + and os.path.isdir(os.path.join(outdir, dir)) ] versions.sort(key=key) diff --git a/src/tools/collect-license-metadata/src/path_tree.rs b/src/tools/collect-license-metadata/src/path_tree.rs index 68b6cef643272..709d91897e662 100644 --- a/src/tools/collect-license-metadata/src/path_tree.rs +++ b/src/tools/collect-license-metadata/src/path_tree.rs @@ -155,7 +155,10 @@ impl Node { name: child_name, children: child_children, license: child_license, - } = child else { continue }; + } = child + else { + continue; + }; if child_license != license { continue; diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml index e5297d41a6119..ff1d5cecb7223 100644 --- a/src/tools/compiletest/Cargo.toml +++ b/src/tools/compiletest/Cargo.toml @@ -3,6 +3,9 @@ name = "compiletest" version = "0.0.0" edition = "2021" +[lib] +doctest = false + [dependencies] colored = "2" diff = "0.1.10" @@ -26,7 +29,7 @@ anyhow = "1" libc = "0.2" [target.'cfg(windows)'.dependencies] -miow = "0.5" +miow = "0.6" [target.'cfg(windows)'.dependencies.windows] version = "0.48.0" diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index ba68b5ee9d5b4..7c17e92d0dfe2 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -66,6 +66,13 @@ string_enum! { JsDocTest => "js-doc-test", MirOpt => "mir-opt", Assembly => "assembly", + RunCoverage => "run-coverage", + } +} + +impl Default for Mode { + fn default() -> Self { + Mode::Ui } } @@ -100,8 +107,8 @@ string_enum! { #[derive(Clone, Debug, PartialEq)] pub enum CompareMode { Polonius => "polonius", - Chalk => "chalk", NextSolver => "next-solver", + NextSolverCoherence => "next-solver-coherence", SplitDwarf => "split-dwarf", SplitDwarfSingle => "split-dwarf-single", } @@ -124,8 +131,17 @@ pub enum PanicStrategy { Abort, } +impl PanicStrategy { + pub(crate) fn for_miropt_test_tools(&self) -> miropt_test_tools::PanicStrategy { + match self { + PanicStrategy::Unwind => miropt_test_tools::PanicStrategy::Unwind, + PanicStrategy::Abort => miropt_test_tools::PanicStrategy::Abort, + } + } +} + /// Configuration for compiletest -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub struct Config { /// `true` to overwrite stderr/stdout files instead of complaining about changes in output. pub bless: bool, @@ -422,7 +438,7 @@ pub struct TargetCfgs { impl TargetCfgs { fn new(config: &Config) -> TargetCfgs { - let targets: HashMap = serde_json::from_str(&rustc_output( + let mut targets: HashMap = serde_json::from_str(&rustc_output( config, &["--print=all-target-specs-json", "-Zunstable-options"], )) @@ -437,7 +453,19 @@ impl TargetCfgs { let mut all_families = HashSet::new(); let mut all_pointer_widths = HashSet::new(); - for (target, cfg) in targets.into_iter() { + // Handle custom target specs, which are not included in `--print=all-target-specs-json`. + if config.target.ends_with(".json") { + targets.insert( + config.target.clone(), + serde_json::from_str(&rustc_output( + config, + &["--print=target-spec-json", "-Zunstable-options", "--target", &config.target], + )) + .unwrap(), + ); + } + + for (target, cfg) in targets.iter() { all_archs.insert(cfg.arch.clone()); all_oses.insert(cfg.os.clone()); all_oses_and_envs.insert(cfg.os_and_env()); @@ -448,11 +476,11 @@ impl TargetCfgs { } all_pointer_widths.insert(format!("{}bit", cfg.pointer_width)); - all_targets.insert(target.into()); + all_targets.insert(target.clone()); } Self { - current: Self::get_current_target_config(config), + current: Self::get_current_target_config(config, &targets), all_targets, all_archs, all_oses, @@ -464,16 +492,20 @@ impl TargetCfgs { } } - fn get_current_target_config(config: &Config) -> TargetCfg { - let mut arch = None; - let mut os = None; - let mut env = None; - let mut abi = None; - let mut families = Vec::new(); - let mut pointer_width = None; - let mut endian = None; - let mut panic = None; - + fn get_current_target_config( + config: &Config, + targets: &HashMap, + ) -> TargetCfg { + let mut cfg = targets[&config.target].clone(); + + // To get the target information for the current target, we take the target spec obtained + // from `--print=all-target-specs-json`, and then we enrich it with the information + // gathered from `--print=cfg --target=$target`. + // + // This is done because some parts of the target spec can be overridden with `-C` flags, + // which are respected for `--print=cfg` but not for `--print=all-target-specs-json`. The + // code below extracts them from `--print=cfg`: make sure to only override fields that can + // actually be changed with `-C` flags. for config in rustc_output(config, &["--print=cfg", "--target", &config.target]).trim().lines() { @@ -491,60 +523,16 @@ impl TargetCfgs { }) .unwrap_or_else(|| (config, None)); - match name { - "target_arch" => { - arch = Some(value.expect("target_arch should be a key-value pair").to_string()); - } - "target_os" => { - os = Some(value.expect("target_os sould be a key-value pair").to_string()); - } - "target_env" => { - env = Some(value.expect("target_env should be a key-value pair").to_string()); - } - "target_abi" => { - abi = Some(value.expect("target_abi should be a key-value pair").to_string()); - } - "target_family" => { - families - .push(value.expect("target_family should be a key-value pair").to_string()); - } - "target_pointer_width" => { - pointer_width = Some( - value - .expect("target_pointer_width should be a key-value pair") - .parse::() - .expect("target_pointer_width should be a valid u32"), - ); - } - "target_endian" => { - endian = Some(match value.expect("target_endian should be a key-value pair") { - "big" => Endian::Big, - "little" => Endian::Little, - _ => panic!("target_endian should be either 'big' or 'little'"), - }); - } - "panic" => { - panic = Some(match value.expect("panic should be a key-value pair") { - "abort" => PanicStrategy::Abort, - "unwind" => PanicStrategy::Unwind, - _ => panic!("panic should be either 'abort' or 'unwind'"), - }); - } - _ => (), + match (name, value) { + // Can be overridden with `-C panic=$strategy`. + ("panic", Some("abort")) => cfg.panic = PanicStrategy::Abort, + ("panic", Some("unwind")) => cfg.panic = PanicStrategy::Unwind, + ("panic", other) => panic!("unexpected value for panic cfg: {other:?}"), + _ => {} } } - TargetCfg { - arch: arch.expect("target configuration should specify target_arch"), - os: os.expect("target configuration should specify target_os"), - env: env.expect("target configuration should specify target_env"), - abi: abi.expect("target configuration should specify target_abi"), - families, - pointer_width: pointer_width - .expect("target configuration should specify target_pointer_width"), - endian: endian.expect("target configuration should specify target_endian"), - panic: panic.expect("target configuration should specify panic"), - } + cfg } } @@ -565,7 +553,9 @@ pub struct TargetCfg { #[serde(rename = "target-endian", default)] endian: Endian, #[serde(rename = "panic-strategy", default)] - panic: PanicStrategy, + pub(crate) panic: PanicStrategy, + #[serde(default)] + pub(crate) dynamic_linking: bool, } impl TargetCfg { @@ -648,6 +638,7 @@ pub const UI_EXTENSIONS: &[&str] = &[ UI_STDERR_64, UI_STDERR_32, UI_STDERR_16, + UI_COVERAGE, ]; pub const UI_STDERR: &str = "stderr"; pub const UI_STDOUT: &str = "stdout"; @@ -657,6 +648,7 @@ pub const UI_RUN_STDOUT: &str = "run.stdout"; pub const UI_STDERR_64: &str = "64bit.stderr"; pub const UI_STDERR_32: &str = "32bit.stderr"; pub const UI_STDERR_16: &str = "16bit.stderr"; +pub const UI_COVERAGE: &str = "coverage"; /// Absolute path to the directory where all output for all tests in the given /// `relative_dir` group should reside. Example: diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 8cc935e54d117..269d9384376f9 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -6,7 +6,6 @@ use std::io::BufReader; use std::path::{Path, PathBuf}; use std::process::Command; -use build_helper::ci::CiEnv; use tracing::*; use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; @@ -161,7 +160,7 @@ pub struct TestProps { // customized normalization rules pub normalize_stdout: Vec<(String, String)>, pub normalize_stderr: Vec<(String, String)>, - pub failure_status: i32, + pub failure_status: Option, // For UI tests, allows compiler to exit with arbitrary failure status pub dont_check_failure_status: bool, // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the @@ -232,7 +231,7 @@ impl TestProps { aux_builds: vec![], aux_crates: vec![], revisions: vec![], - rustc_env: vec![], + rustc_env: vec![("RUSTC_ICE".to_string(), "0".to_string())], unset_rustc_env: vec![], exec_env: vec![], unset_exec_env: vec![], @@ -257,7 +256,7 @@ impl TestProps { check_test_line_numbers_match: false, normalize_stdout: vec![], normalize_stderr: vec![], - failure_status: -1, + failure_status: None, dont_check_failure_status: false, run_rustfix: false, rustfix_only_machine_applicable: false, @@ -298,13 +297,6 @@ impl TestProps { /// `//[foo]`), then the property is ignored unless `cfg` is /// `Some("foo")`. fn load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config) { - // In CI, we've sometimes encountered non-determinism related to truncating very long paths. - // Set a consistent (short) prefix to avoid issues, but only in CI to avoid regressing the - // contributor experience. - if CiEnv::is_ci() { - self.remap_src_base = config.mode == Mode::Ui && !config.suite.contains("rustdoc"); - } - let mut has_edition = false; if !testfile.is_dir() { let file = File::open(testfile).unwrap(); @@ -428,7 +420,7 @@ impl TestProps { .parse_name_value_directive(ln, FAILURE_STATUS) .and_then(|code| code.trim().parse::().ok()) { - self.failure_status = code; + self.failure_status = Some(code); } config.set_name_directive( @@ -491,11 +483,8 @@ impl TestProps { }); } - if self.failure_status == -1 { - self.failure_status = 1; - } if self.should_ice { - self.failure_status = 101; + self.failure_status = Some(101); } if config.mode == Mode::Incremental { @@ -544,16 +533,15 @@ impl TestProps { } fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) { - let check_no_run = |s| { - if config.mode != Mode::Ui && config.mode != Mode::Incremental { - panic!("`{}` header is only supported in UI and incremental tests", s); - } - if config.mode == Mode::Incremental - && !revision.map_or(false, |r| r.starts_with("cfail")) - && !self.revisions.iter().all(|r| r.starts_with("cfail")) - { - panic!("`{}` header is only supported in `cfail` incremental tests", s); + let check_no_run = |s| match (config.mode, s) { + (Mode::Ui, _) => (), + (Mode::Codegen, "build-pass") => (), + (Mode::Incremental, _) => { + if revision.is_some() && !self.revisions.iter().all(|r| r.starts_with("cfail")) { + panic!("`{s}` header is only supported in `cfail` incremental tests") + } } + (mode, _) => panic!("`{s}` header is not supported in `{mode}` tests"), }; let pass_mode = if config.parse_name_directive(ln, "check-pass") { check_no_run("check-pass"); @@ -562,9 +550,7 @@ impl TestProps { check_no_run("build-pass"); Some(PassMode::Build) } else if config.parse_name_directive(ln, "run-pass") { - if config.mode != Mode::Ui { - panic!("`run-pass` header is only supported in UI tests") - } + check_no_run("run-pass"); Some(PassMode::Run) } else { None @@ -591,21 +577,25 @@ impl TestProps { } } +/// Extract a `(Option, directive)` directive from a line if comment is present. pub fn line_directive<'line>( comment: &str, ln: &'line str, ) -> Option<(Option<&'line str>, &'line str)> { + let ln = ln.trim_start(); if ln.starts_with(comment) { let ln = ln[comment.len()..].trim_start(); if ln.starts_with('[') { // A comment like `//[foo]` is specific to revision `foo` - if let Some(close_brace) = ln.find(']') { - let lncfg = &ln[1..close_brace]; + let Some(close_brace) = ln.find(']') else { + panic!( + "malformed condition directive: expected `{}[foo]`, found `{}`", + comment, ln + ); + }; - Some((Some(lncfg), ln[(close_brace + 1)..].trim_start())) - } else { - panic!("malformed condition directive: expected `{}[foo]`, found `{}`", comment, ln) - } + let lncfg = &ln[1..close_brace]; + Some((Some(lncfg), ln[(close_brace + 1)..].trim_start())) } else { Some((None, ln)) } @@ -615,10 +605,25 @@ pub fn line_directive<'line>( } fn iter_header(testfile: &Path, rdr: R, it: &mut dyn FnMut(Option<&str>, &str, usize)) { + iter_header_extra(testfile, rdr, &[], it) +} + +fn iter_header_extra( + testfile: &Path, + rdr: impl Read, + extra_directives: &[&str], + it: &mut dyn FnMut(Option<&str>, &str, usize), +) { if testfile.is_dir() { return; } + // Process any extra directives supplied by the caller (e.g. because they + // are implied by the test mode), with a dummy line number of 0. + for directive in extra_directives { + it(None, directive, 0); + } + let comment = if testfile.extension().map(|e| e == "rs") == Some(true) { "//" } else { "#" }; let mut rdr = BufReader::new(rdr); @@ -897,7 +902,27 @@ pub fn make_test_description( let mut ignore_message = None; let mut should_fail = false; - iter_header(path, src, &mut |revision, ln, line_number| { + let extra_directives: &[&str] = match config.mode { + // The run-coverage tests are treated as having these extra directives, + // without needing to specify them manually in every test file. + // (Some of the comments below have been copied over from + // `tests/run-make/coverage-reports/Makefile`, which no longer exists.) + Mode::RunCoverage => { + &[ + "needs-profiler-support", + // FIXME(mati865): MinGW GCC miscompiles compiler-rt profiling library but with Clang it works + // properly. Since we only have GCC on the CI ignore the test for now. + "ignore-windows-gnu", + // FIXME(pietroalbini): this test currently does not work on cross-compiled + // targets because remote-test is not capable of sending back the *.profraw + // files generated by the LLVM instrumentation. + "ignore-cross-compile", + ] + } + _ => &[], + }; + + iter_header_extra(path, src, extra_directives, &mut |revision, ln, line_number| { if revision.is_some() && revision != cfg { return; } diff --git a/src/tools/compiletest/src/header/cfg.rs b/src/tools/compiletest/src/header/cfg.rs index 86a749b935d82..77c2866b366a6 100644 --- a/src/tools/compiletest/src/header/cfg.rs +++ b/src/tools/compiletest/src/header/cfg.rs @@ -112,7 +112,7 @@ pub(super) fn parse_cfg_name_directive<'a>( (config.target == "wasm32-unknown-unknown").then_some("emscripten"), ], allowed_names: &target_cfgs.all_oses, - message: "when the operative system is {name}" + message: "when the operating system is {name}" } condition! { name: &target_cfg.env, @@ -122,7 +122,7 @@ pub(super) fn parse_cfg_name_directive<'a>( condition! { name: &target_cfg.os_and_env(), allowed_names: &target_cfgs.all_oses_and_envs, - message: "when the operative system and target environment are {name}" + message: "when the operating system and target environment are {name}" } condition! { name: &target_cfg.abi, diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs index 4a57c61406ce4..62364ede47b38 100644 --- a/src/tools/compiletest/src/header/needs.rs +++ b/src/tools/compiletest/src/header/needs.rs @@ -70,6 +70,11 @@ pub(super) fn handle_needs( condition: cache.sanitizer_shadow_call_stack, ignore_reason: "ignored on targets without shadow call stacks", }, + Need { + name: "needs-sanitizer-safestack", + condition: cache.sanitizer_safestack, + ignore_reason: "ignored on targets without SafeStack support", + }, Need { name: "needs-run-enabled", condition: config.run_enabled(), @@ -82,7 +87,7 @@ pub(super) fn handle_needs( }, Need { name: "needs-profiler-support", - condition: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(), + condition: cache.profiler_support, ignore_reason: "ignored when profiler support is disabled", }, Need { @@ -125,6 +130,11 @@ pub(super) fn handle_needs( condition: config.git_hash, ignore_reason: "ignored when git hashes have been omitted for building", }, + Need { + name: "needs-dynamic-linking", + condition: config.target_cfg().dynamic_linking, + ignore_reason: "ignored on targets without dynamic linking", + }, ]; let (name, comment) = match ln.split_once([':', ' ']) { @@ -184,6 +194,8 @@ pub(super) struct CachedNeedsConditions { sanitizer_hwaddress: bool, sanitizer_memtag: bool, sanitizer_shadow_call_stack: bool, + sanitizer_safestack: bool, + profiler_support: bool, xray: bool, rust_lld: bool, i686_dlltool: bool, @@ -220,6 +232,8 @@ impl CachedNeedsConditions { sanitizer_hwaddress: util::HWASAN_SUPPORTED_TARGETS.contains(target), sanitizer_memtag: util::MEMTAG_SUPPORTED_TARGETS.contains(target), sanitizer_shadow_call_stack: util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(target), + sanitizer_safestack: util::SAFESTACK_SUPPORTED_TARGETS.contains(target), + profiler_support: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(), xray: util::XRAY_SUPPORTED_TARGETS.contains(target), // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs new file mode 100644 index 0000000000000..1a765477fe501 --- /dev/null +++ b/src/tools/compiletest/src/lib.rs @@ -0,0 +1,1136 @@ +#![crate_name = "compiletest"] +// The `test` crate is the only unstable feature +// allowed here, just to share similar code. +#![feature(test)] + +extern crate test; + +#[cfg(test)] +mod tests; + +pub mod common; +pub mod compute_diff; +pub mod errors; +pub mod header; +mod json; +mod raise_fd_limit; +mod read2; +pub mod runtest; +pub mod util; + +use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}; +use crate::common::{Config, Debugger, Mode, PassMode, TestPaths}; +use crate::util::logv; +use build_helper::git::{get_git_modified_files, get_git_untracked_files}; +use core::panic; +use getopts::Options; +use lazycell::AtomicLazyCell; +use std::collections::BTreeSet; +use std::ffi::OsString; +use std::fs; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::SystemTime; +use std::{env, vec}; +use test::ColorConfig; +use tracing::*; +use walkdir::WalkDir; + +use self::header::{make_test_description, EarlyProps}; +use crate::header::HeadersCache; +use std::sync::Arc; + +pub fn parse_config(args: Vec) -> Config { + let mut opts = Options::new(); + opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH") + .reqopt("", "run-lib-path", "path to target shared libraries", "PATH") + .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH") + .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH") + .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH") + .reqopt("", "python", "path to python to use for doc tests", "PATH") + .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH") + .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH") + .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM") + .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind") + .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH") + .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR") + .reqopt("", "src-base", "directory to scan for test files", "PATH") + .reqopt("", "build-base", "directory to deposit test outputs", "PATH") + .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH") + .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET") + .reqopt( + "", + "mode", + "which sort of compile tests to run", + "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \ + | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly", + ) + .reqopt( + "", + "suite", + "which suite of compile tests to run. used for nicer error reporting.", + "SUITE", + ) + .optopt( + "", + "pass", + "force {check,build,run}-pass tests to this mode.", + "check | build | run", + ) + .optopt("", "run", "whether to execute run-* tests", "auto | always | never") + .optflag("", "ignored", "run tests marked as ignored") + .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING") + .optflag("", "exact", "filters match exactly") + .optopt( + "", + "runtool", + "supervisor program to run tests under \ + (eg. emulator, valgrind)", + "PROGRAM", + ) + .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS") + .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS") + .optflag("", "optimize-tests", "run tests with optimizations enabled") + .optflag("", "verbose", "run tests verbosely, showing all output") + .optflag( + "", + "bless", + "overwrite stderr/stdout files instead of complaining about a mismatch", + ) + .optflag("", "quiet", "print one character per test instead of one line") + .optopt("", "color", "coloring: auto, always, never", "WHEN") + .optflag("", "json", "emit json output instead of plaintext output") + .optopt("", "logfile", "file to log test execution to", "FILE") + .optopt("", "target", "the target to build for", "TARGET") + .optopt("", "host", "the host to build for", "HOST") + .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH") + .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH") + .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING") + .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING") + .optflag("", "system-llvm", "is LLVM the system LLVM") + .optopt("", "android-cross-path", "Android NDK standalone path", "PATH") + .optopt("", "adb-path", "path to the android debugger", "PATH") + .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH") + .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH") + .reqopt("", "cc", "path to a C compiler", "PATH") + .reqopt("", "cxx", "path to a C++ compiler", "PATH") + .reqopt("", "cflags", "flags for the C compiler", "FLAGS") + .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS") + .optopt("", "ar", "path to an archiver", "PATH") + .optopt("", "target-linker", "path to a linker for the target", "PATH") + .optopt("", "host-linker", "path to a linker for the host", "PATH") + .reqopt("", "llvm-components", "list of LLVM components built in", "LIST") + .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH") + .optopt("", "nodejs", "the name of nodejs", "PATH") + .optopt("", "npm", "the name of npm", "PATH") + .optopt("", "remote-test-client", "path to the remote test client", "PATH") + .optopt( + "", + "compare-mode", + "mode describing what file the actual ui output will be compared to", + "COMPARE MODE", + ) + .optflag( + "", + "rustfix-coverage", + "enable this to generate a Rustfix coverage file, which is saved in \ + `.//rustfix_missing_coverage.txt`", + ) + .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged") + .optflag("", "only-modified", "only run tests that result been modified") + .optflag("", "nocapture", "") + .optflag("h", "help", "show this message") + .reqopt("", "channel", "current Rust channel", "CHANNEL") + .optflag("", "git-hash", "run tests which rely on commit version being compiled into the binaries") + .optopt("", "edition", "default Rust edition", "EDITION"); + + let (argv0, args_) = args.split_first().unwrap(); + if args.len() == 1 || args[1] == "-h" || args[1] == "--help" { + let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); + println!("{}", opts.usage(&message)); + println!(); + panic!() + } + + let matches = &match opts.parse(args_) { + Ok(m) => m, + Err(f) => panic!("{:?}", f), + }; + + if matches.opt_present("h") || matches.opt_present("help") { + let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); + println!("{}", opts.usage(&message)); + println!(); + panic!() + } + + fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf { + match m.opt_str(nm) { + Some(s) => PathBuf::from(&s), + None => panic!("no option (=path) found for {}", nm), + } + } + + fn make_absolute(path: PathBuf) -> PathBuf { + if path.is_relative() { env::current_dir().unwrap().join(path) } else { path } + } + + let target = opt_str2(matches.opt_str("target")); + let android_cross_path = opt_path(matches, "android-cross-path"); + let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target); + let (gdb, gdb_version, gdb_native_rust) = + analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path); + let (lldb_version, lldb_native_rust) = matches + .opt_str("lldb-version") + .as_deref() + .and_then(extract_lldb_version) + .map(|(v, b)| (Some(v), b)) + .unwrap_or((None, false)); + let color = match matches.opt_str("color").as_deref() { + Some("auto") | None => ColorConfig::AutoColor, + Some("always") => ColorConfig::AlwaysColor, + Some("never") => ColorConfig::NeverColor, + Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x), + }; + let llvm_version = + matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version).or_else( + || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?), + ); + + let src_base = opt_path(matches, "src-base"); + let run_ignored = matches.opt_present("ignored"); + let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode"); + let has_tidy = if mode == Mode::Rustdoc { + Command::new("tidy") + .arg("--version") + .stdout(Stdio::null()) + .status() + .map_or(false, |status| status.success()) + } else { + // Avoid spawning an external command when we know tidy won't be used. + false + }; + Config { + bless: matches.opt_present("bless"), + compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")), + run_lib_path: make_absolute(opt_path(matches, "run-lib-path")), + rustc_path: opt_path(matches, "rustc-path"), + rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from), + rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from), + python: matches.opt_str("python").unwrap(), + jsondocck_path: matches.opt_str("jsondocck-path"), + jsondoclint_path: matches.opt_str("jsondoclint-path"), + valgrind_path: matches.opt_str("valgrind-path"), + force_valgrind: matches.opt_present("force-valgrind"), + run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"), + llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from), + llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from), + src_base, + build_base: opt_path(matches, "build-base"), + sysroot_base: opt_path(matches, "sysroot-base"), + stage_id: matches.opt_str("stage-id").unwrap(), + mode, + suite: matches.opt_str("suite").unwrap(), + debugger: None, + run_ignored, + filters: matches.free.clone(), + skip: matches.opt_strs("skip"), + filter_exact: matches.opt_present("exact"), + force_pass_mode: matches.opt_str("pass").map(|mode| { + mode.parse::() + .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode)) + }), + run: matches.opt_str("run").and_then(|mode| match mode.as_str() { + "auto" => None, + "always" => Some(true), + "never" => Some(false), + _ => panic!("unknown `--run` option `{}` given", mode), + }), + logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)), + runtool: matches.opt_str("runtool"), + host_rustcflags: matches.opt_strs("host-rustcflags"), + target_rustcflags: matches.opt_strs("target-rustcflags"), + optimize_tests: matches.opt_present("optimize-tests"), + target, + host: opt_str2(matches.opt_str("host")), + cdb, + cdb_version, + gdb, + gdb_version, + gdb_native_rust, + lldb_version, + lldb_native_rust, + llvm_version, + system_llvm: matches.opt_present("system-llvm"), + android_cross_path, + adb_path: opt_str2(matches.opt_str("adb-path")), + adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")), + adb_device_status: opt_str2(matches.opt_str("target")).contains("android") + && "(none)" != opt_str2(matches.opt_str("adb-test-dir")) + && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(), + lldb_python_dir: matches.opt_str("lldb-python-dir"), + verbose: matches.opt_present("verbose"), + format: match (matches.opt_present("quiet"), matches.opt_present("json")) { + (true, true) => panic!("--quiet and --json are incompatible"), + (true, false) => test::OutputFormat::Terse, + (false, true) => test::OutputFormat::Json, + (false, false) => test::OutputFormat::Pretty, + }, + only_modified: matches.opt_present("only-modified"), + color, + remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from), + compare_mode: matches + .opt_str("compare-mode") + .map(|s| s.parse().expect("invalid --compare-mode provided")), + rustfix_coverage: matches.opt_present("rustfix-coverage"), + has_tidy, + channel: matches.opt_str("channel").unwrap(), + git_hash: matches.opt_present("git-hash"), + edition: matches.opt_str("edition"), + + cc: matches.opt_str("cc").unwrap(), + cxx: matches.opt_str("cxx").unwrap(), + cflags: matches.opt_str("cflags").unwrap(), + cxxflags: matches.opt_str("cxxflags").unwrap(), + ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")), + target_linker: matches.opt_str("target-linker"), + host_linker: matches.opt_str("host-linker"), + llvm_components: matches.opt_str("llvm-components").unwrap(), + nodejs: matches.opt_str("nodejs"), + npm: matches.opt_str("npm"), + + force_rerun: matches.opt_present("force-rerun"), + + target_cfgs: AtomicLazyCell::new(), + + nocapture: matches.opt_present("nocapture"), + } +} + +pub fn log_config(config: &Config) { + let c = config; + logv(c, "configuration:".to_string()); + logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path)); + logv(c, format!("run_lib_path: {:?}", config.run_lib_path)); + logv(c, format!("rustc_path: {:?}", config.rustc_path.display())); + logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path)); + logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path)); + logv(c, format!("src_base: {:?}", config.src_base.display())); + logv(c, format!("build_base: {:?}", config.build_base.display())); + logv(c, format!("stage_id: {}", config.stage_id)); + logv(c, format!("mode: {}", config.mode)); + logv(c, format!("run_ignored: {}", config.run_ignored)); + logv(c, format!("filters: {:?}", config.filters)); + logv(c, format!("skip: {:?}", config.skip)); + logv(c, format!("filter_exact: {}", config.filter_exact)); + logv( + c, + format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),), + ); + logv(c, format!("runtool: {}", opt_str(&config.runtool))); + logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags)); + logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags)); + logv(c, format!("target: {}", config.target)); + logv(c, format!("host: {}", config.host)); + logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display())); + logv(c, format!("adb_path: {:?}", config.adb_path)); + logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir)); + logv(c, format!("adb_device_status: {}", config.adb_device_status)); + logv(c, format!("ar: {}", config.ar)); + logv(c, format!("target-linker: {:?}", config.target_linker)); + logv(c, format!("host-linker: {:?}", config.host_linker)); + logv(c, format!("verbose: {}", config.verbose)); + logv(c, format!("format: {:?}", config.format)); + logv(c, "\n".to_string()); +} + +pub fn opt_str(maybestr: &Option) -> &str { + match *maybestr { + None => "(none)", + Some(ref s) => s, + } +} + +pub fn opt_str2(maybestr: Option) -> String { + match maybestr { + None => "(none)".to_owned(), + Some(s) => s, + } +} + +pub fn run_tests(config: Arc) { + // If we want to collect rustfix coverage information, + // we first make sure that the coverage file does not exist. + // It will be created later on. + if config.rustfix_coverage { + let mut coverage_file_path = config.build_base.clone(); + coverage_file_path.push("rustfix_missing_coverage.txt"); + if coverage_file_path.exists() { + if let Err(e) = fs::remove_file(&coverage_file_path) { + panic!("Could not delete {} due to {}", coverage_file_path.display(), e) + } + } + } + + // sadly osx needs some file descriptor limits raised for running tests in + // parallel (especially when we have lots and lots of child processes). + // For context, see #8904 + unsafe { + raise_fd_limit::raise_fd_limit(); + } + // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows + // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary + env::set_var("__COMPAT_LAYER", "RunAsInvoker"); + + // Let tests know which target they're running as + env::set_var("TARGET", &config.target); + + let opts = test_opts(&config); + + let mut configs = Vec::new(); + if let Mode::DebugInfo = config.mode { + // Debugging emscripten code doesn't make sense today + if !config.target.contains("emscripten") { + configs.extend(configure_cdb(&config)); + configs.extend(configure_gdb(&config)); + configs.extend(configure_lldb(&config)); + } + } else { + configs.push(config.clone()); + }; + + let mut tests = Vec::new(); + for c in configs { + let mut found_paths = BTreeSet::new(); + make_tests(c, &mut tests, &mut found_paths); + check_overlapping_tests(&found_paths); + } + + tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice())); + + let res = test::run_tests_console(&opts, tests); + match res { + Ok(true) => {} + Ok(false) => { + // We want to report that the tests failed, but we also want to give + // some indication of just what tests we were running. Especially on + // CI, where there can be cross-compiled tests for a lot of + // architectures, without this critical information it can be quite + // easy to miss which tests failed, and as such fail to reproduce + // the failure locally. + + println!( + "Some tests failed in compiletest suite={}{} mode={} host={} target={}", + config.suite, + config + .compare_mode + .as_ref() + .map(|c| format!(" compare_mode={:?}", c)) + .unwrap_or_default(), + config.mode, + config.host, + config.target + ); + + std::process::exit(1); + } + Err(e) => { + // We don't know if tests passed or not, but if there was an error + // during testing we don't want to just succeed (we may not have + // tested something), so fail. + // + // This should realistically "never" happen, so don't try to make + // this a pretty error message. + panic!("I/O failure during tests: {:?}", e); + } + } +} + +fn configure_cdb(config: &Config) -> Option> { + config.cdb.as_ref()?; + + Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() })) +} + +fn configure_gdb(config: &Config) -> Option> { + config.gdb_version?; + + if config.matches_env("msvc") { + return None; + } + + if config.remote_test_client.is_some() && !config.target.contains("android") { + println!( + "WARNING: debuginfo tests are not available when \ + testing with remote" + ); + return None; + } + + if config.target.contains("android") { + println!( + "{} debug-info test uses tcp 5039 port.\ + please reserve it", + config.target + ); + + // android debug-info test uses remote debugger so, we test 1 thread + // at once as they're all sharing the same TCP port to communicate + // over. + // + // we should figure out how to lift this restriction! (run them all + // on different ports allocated dynamically). + env::set_var("RUST_TEST_THREADS", "1"); + } + + Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() })) +} + +fn configure_lldb(config: &Config) -> Option> { + config.lldb_python_dir.as_ref()?; + + if let Some(350) = config.lldb_version { + println!( + "WARNING: The used version of LLDB (350) has a \ + known issue that breaks debuginfo tests. See \ + issue #32520 for more information. Skipping all \ + LLDB-based tests!", + ); + return None; + } + + Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() })) +} + +pub fn test_opts(config: &Config) -> test::TestOpts { + if env::var("RUST_TEST_NOCAPTURE").is_ok() { + eprintln!( + "WARNING: RUST_TEST_NOCAPTURE is no longer used. \ + Use the `--nocapture` flag instead." + ); + } + + test::TestOpts { + exclude_should_panic: false, + filters: config.filters.clone(), + filter_exact: config.filter_exact, + run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No }, + format: config.format, + logfile: config.logfile.clone(), + run_tests: true, + bench_benchmarks: true, + nocapture: config.nocapture, + color: config.color, + shuffle: false, + shuffle_seed: None, + test_threads: None, + skip: config.skip.clone(), + list: false, + options: test::Options::new(), + time_options: None, + force_run_in_process: false, + fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(), + } +} + +pub fn make_tests( + config: Arc, + tests: &mut Vec, + found_paths: &mut BTreeSet, +) { + debug!("making tests from {:?}", config.src_base.display()); + let inputs = common_inputs_stamp(&config); + let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { + panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) + }); + + let cache = HeadersCache::load(&config); + let mut poisoned = false; + collect_tests_from_dir( + config.clone(), + &cache, + &config.src_base, + &PathBuf::new(), + &inputs, + tests, + found_paths, + &modified_tests, + &mut poisoned, + ) + .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display())); + + if poisoned { + eprintln!(); + panic!("there are errors in tests"); + } +} + +/// Returns a stamp constructed from input files common to all test cases. +fn common_inputs_stamp(config: &Config) -> Stamp { + let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root"); + + let mut stamp = Stamp::from_path(&config.rustc_path); + + // Relevant pretty printer files + let pretty_printer_files = [ + "src/etc/rust_types.py", + "src/etc/gdb_load_rust_pretty_printers.py", + "src/etc/gdb_lookup.py", + "src/etc/gdb_providers.py", + "src/etc/lldb_batchmode.py", + "src/etc/lldb_lookup.py", + "src/etc/lldb_providers.py", + ]; + for file in &pretty_printer_files { + let path = rust_src_dir.join(file); + stamp.add_path(&path); + } + + stamp.add_dir(&rust_src_dir.join("src/etc/natvis")); + + stamp.add_dir(&config.run_lib_path); + + if let Some(ref rustdoc_path) = config.rustdoc_path { + stamp.add_path(&rustdoc_path); + stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py")); + } + + // Compiletest itself. + stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/")); + + stamp +} + +fn modified_tests(config: &Config, dir: &Path) -> Result, String> { + if !config.only_modified { + return Ok(vec![]); + } + let files = + get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]); + // Add new test cases to the list, it will be convenient in daily development. + let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]); + + let all_paths = [&files[..], &untracked_files[..]].concat(); + let full_paths = { + let mut full_paths: Vec = all_paths + .into_iter() + .map(|f| PathBuf::from(f).with_extension("").with_extension("rs")) + .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None }) + .collect(); + full_paths.dedup(); + full_paths.sort_unstable(); + full_paths + }; + Ok(full_paths) +} + +fn collect_tests_from_dir( + config: Arc, + cache: &HeadersCache, + dir: &Path, + relative_dir_path: &Path, + inputs: &Stamp, + tests: &mut Vec, + found_paths: &mut BTreeSet, + modified_tests: &Vec, + poisoned: &mut bool, +) -> io::Result<()> { + // Ignore directories that contain a file named `compiletest-ignore-dir`. + if dir.join("compiletest-ignore-dir").exists() { + return Ok(()); + } + + if config.mode == Mode::RunMake && dir.join("Makefile").exists() { + let paths = TestPaths { + file: dir.to_path_buf(), + relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), + }; + tests.extend(make_test(config, cache, &paths, inputs, poisoned)); + return Ok(()); + } + + // If we find a test foo/bar.rs, we have to build the + // output directory `$build/foo` so we can write + // `$build/foo/bar` into it. We do this *now* in this + // sequential loop because otherwise, if we do it in the + // tests themselves, they race for the privilege of + // creating the directories and sometimes fail randomly. + let build_dir = output_relative_path(&config, relative_dir_path); + fs::create_dir_all(&build_dir).unwrap(); + + // Add each `.rs` file as a test, and recurse further on any + // subdirectories we find, except for `aux` directories. + for file in fs::read_dir(dir)? { + let file = file?; + let file_path = file.path(); + let file_name = file.file_name(); + if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) { + debug!("found test file: {:?}", file_path.display()); + let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap()); + found_paths.insert(rel_test_path); + let paths = + TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; + + tests.extend(make_test(config.clone(), cache, &paths, inputs, poisoned)) + } else if file_path.is_dir() { + let relative_file_path = relative_dir_path.join(file.file_name()); + if &file_name != "auxiliary" { + debug!("found directory: {:?}", file_path.display()); + collect_tests_from_dir( + config.clone(), + cache, + &file_path, + &relative_file_path, + inputs, + tests, + found_paths, + modified_tests, + poisoned, + )?; + } + } else { + debug!("found other file/directory: {:?}", file_path.display()); + } + } + Ok(()) +} + +/// Returns true if `file_name` looks like a proper test file name. +pub fn is_test(file_name: &OsString) -> bool { + let file_name = file_name.to_str().unwrap(); + + if !file_name.ends_with(".rs") { + return false; + } + + // `.`, `#`, and `~` are common temp-file prefixes. + let invalid_prefixes = &[".", "#", "~"]; + !invalid_prefixes.iter().any(|p| file_name.starts_with(p)) +} + +fn make_test( + config: Arc, + cache: &HeadersCache, + testpaths: &TestPaths, + inputs: &Stamp, + poisoned: &mut bool, +) -> Vec { + let test_path = if config.mode == Mode::RunMake { + // Parse directives in the Makefile + testpaths.file.join("Makefile") + } else { + PathBuf::from(&testpaths.file) + }; + let early_props = EarlyProps::from_file(&config, &test_path); + + // Incremental tests are special, they inherently cannot be run in parallel. + // `runtest::run` will be responsible for iterating over revisions. + let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental { + vec![None] + } else { + early_props.revisions.iter().map(Some).collect() + }; + + revisions + .into_iter() + .map(|revision| { + let src_file = + std::fs::File::open(&test_path).expect("open test file to parse ignores"); + let cfg = revision.map(|v| &**v); + let test_name = crate::make_test_name(&config, testpaths, revision); + let mut desc = make_test_description( + &config, cache, test_name, &test_path, src_file, cfg, poisoned, + ); + // Ignore tests that already run and are up to date with respect to inputs. + if !config.force_rerun { + desc.ignore |= is_up_to_date( + &config, + testpaths, + &early_props, + revision.map(|s| s.as_str()), + inputs, + ); + } + test::TestDescAndFn { + desc, + testfn: make_test_closure(config.clone(), testpaths, revision), + } + }) + .collect() +} + +fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { + output_base_dir(config, testpaths, revision).join("stamp") +} + +fn files_related_to_test( + config: &Config, + testpaths: &TestPaths, + props: &EarlyProps, + revision: Option<&str>, +) -> Vec { + let mut related = vec![]; + + if testpaths.file.is_dir() { + // run-make tests use their individual directory + for entry in WalkDir::new(&testpaths.file) { + let path = entry.unwrap().into_path(); + if path.is_file() { + related.push(path); + } + } + } else { + related.push(testpaths.file.clone()); + } + + for aux in &props.aux { + let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux); + related.push(path); + } + + // UI test files. + for extension in UI_EXTENSIONS { + let path = expected_output_path(testpaths, revision, &config.compare_mode, extension); + related.push(path); + } + + related +} + +fn is_up_to_date( + config: &Config, + testpaths: &TestPaths, + props: &EarlyProps, + revision: Option<&str>, + inputs: &Stamp, +) -> bool { + let stamp_name = stamp(config, testpaths, revision); + // Check hash. + let contents = match fs::read_to_string(&stamp_name) { + Ok(f) => f, + Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"), + Err(_) => return false, + }; + let expected_hash = runtest::compute_stamp_hash(config); + if contents != expected_hash { + return false; + } + + // Check timestamps. + let mut inputs = inputs.clone(); + for path in files_related_to_test(config, testpaths, props, revision) { + inputs.add_path(&path); + } + + inputs < Stamp::from_path(&stamp_name) +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct Stamp { + time: SystemTime, +} + +impl Stamp { + fn from_path(path: &Path) -> Self { + let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH }; + stamp.add_path(path); + stamp + } + + fn add_path(&mut self, path: &Path) { + let modified = fs::metadata(path) + .and_then(|metadata| metadata.modified()) + .unwrap_or(SystemTime::UNIX_EPOCH); + self.time = self.time.max(modified); + } + + fn add_dir(&mut self, path: &Path) { + for entry in WalkDir::new(path) { + let entry = entry.unwrap(); + if entry.file_type().is_file() { + let modified = entry + .metadata() + .ok() + .and_then(|metadata| metadata.modified().ok()) + .unwrap_or(SystemTime::UNIX_EPOCH); + self.time = self.time.max(modified); + } + } + } +} + +fn make_test_name( + config: &Config, + testpaths: &TestPaths, + revision: Option<&String>, +) -> test::TestName { + // Print the name of the file, relative to the repository root. + // `src_base` looks like `/path/to/rust/tests/ui` + let root_directory = config.src_base.parent().unwrap().parent().unwrap(); + let path = testpaths.file.strip_prefix(root_directory).unwrap(); + let debugger = match config.debugger { + Some(d) => format!("-{}", d), + None => String::new(), + }; + let mode_suffix = match config.compare_mode { + Some(ref mode) => format!(" ({})", mode.to_str()), + None => String::new(), + }; + + test::DynTestName(format!( + "[{}{}{}] {}{}", + config.mode, + debugger, + mode_suffix, + path.display(), + revision.map_or("".to_string(), |rev| format!("#{}", rev)) + )) +} + +fn make_test_closure( + config: Arc, + testpaths: &TestPaths, + revision: Option<&String>, +) -> test::TestFn { + let config = config.clone(); + let testpaths = testpaths.clone(); + let revision = revision.cloned(); + test::DynTestFn(Box::new(move || { + runtest::run(config, &testpaths, revision.as_deref()); + Ok(()) + })) +} + +/// Returns `true` if the given target is an Android target for the +/// purposes of GDB testing. +fn is_android_gdb_target(target: &str) -> bool { + matches!( + &target[..], + "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" + ) +} + +/// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing. +fn is_pc_windows_msvc_target(target: &str) -> bool { + target.ends_with("-pc-windows-msvc") +} + +fn find_cdb(target: &str) -> Option { + if !(cfg!(windows) && is_pc_windows_msvc_target(target)) { + return None; + } + + let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?; + let cdb_arch = if cfg!(target_arch = "x86") { + "x86" + } else if cfg!(target_arch = "x86_64") { + "x64" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else if cfg!(target_arch = "arm") { + "arm" + } else { + return None; // No compatible CDB.exe in the Windows 10 SDK + }; + + let mut path = PathBuf::new(); + path.push(pf86); + path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too? + path.push(cdb_arch); + path.push(r"cdb.exe"); + + if !path.exists() { + return None; + } + + Some(path.into_os_string()) +} + +/// Returns Path to CDB +fn analyze_cdb(cdb: Option, target: &str) -> (Option, Option<[u16; 4]>) { + let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target)); + + let mut version = None; + if let Some(cdb) = cdb.as_ref() { + if let Ok(output) = Command::new(cdb).arg("/version").output() { + if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { + version = extract_cdb_version(&first_line); + } + } + } + + (cdb, version) +} + +fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> { + // Example full_version_line: "cdb version 10.0.18362.1" + let version = full_version_line.rsplit(' ').next()?; + let mut components = version.split('.'); + let major: u16 = components.next().unwrap().parse().unwrap(); + let minor: u16 = components.next().unwrap().parse().unwrap(); + let patch: u16 = components.next().unwrap_or("0").parse().unwrap(); + let build: u16 = components.next().unwrap_or("0").parse().unwrap(); + Some([major, minor, patch, build]) +} + +/// Returns (Path to GDB, GDB Version, GDB has Rust Support) +fn analyze_gdb( + gdb: Option, + target: &str, + android_cross_path: &PathBuf, +) -> (Option, Option, bool) { + #[cfg(not(windows))] + const GDB_FALLBACK: &str = "gdb"; + #[cfg(windows)] + const GDB_FALLBACK: &str = "gdb.exe"; + + const MIN_GDB_WITH_RUST: u32 = 7011010; + + let fallback_gdb = || { + if is_android_gdb_target(target) { + let mut gdb_path = match android_cross_path.to_str() { + Some(x) => x.to_owned(), + None => panic!("cannot find android cross path"), + }; + gdb_path.push_str("/bin/gdb"); + gdb_path + } else { + GDB_FALLBACK.to_owned() + } + }; + + let gdb = match gdb { + None => fallback_gdb(), + Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb + Some(ref s) => s.to_owned(), + }; + + let mut version_line = None; + if let Ok(output) = Command::new(&gdb).arg("--version").output() { + if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { + version_line = Some(first_line.to_string()); + } + } + + let version = match version_line { + Some(line) => extract_gdb_version(&line), + None => return (None, None, false), + }; + + let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST); + + (Some(gdb), version, gdb_native_rust) +} + +fn extract_gdb_version(full_version_line: &str) -> Option { + let full_version_line = full_version_line.trim(); + + // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both + // of the ? sections being optional + + // We will parse up to 3 digits for each component, ignoring the date + + // We skip text in parentheses. This avoids accidentally parsing + // the openSUSE version, which looks like: + // GNU gdb (GDB; openSUSE Leap 15.0) 8.1 + // This particular form is documented in the GNU coding standards: + // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion + + let unbracketed_part = full_version_line.split('[').next().unwrap(); + let mut splits = unbracketed_part.trim_end().rsplit(' '); + let version_string = splits.next().unwrap(); + + let mut splits = version_string.split('.'); + let major = splits.next().unwrap(); + let minor = splits.next().unwrap(); + let patch = splits.next(); + + let major: u32 = major.parse().unwrap(); + let (minor, patch): (u32, u32) = match minor.find(not_a_digit) { + None => { + let minor = minor.parse().unwrap(); + let patch: u32 = match patch { + Some(patch) => match patch.find(not_a_digit) { + None => patch.parse().unwrap(), + Some(idx) if idx > 3 => 0, + Some(idx) => patch[..idx].parse().unwrap(), + }, + None => 0, + }; + (minor, patch) + } + // There is no patch version after minor-date (e.g. "4-2012"). + Some(idx) => { + let minor = minor[..idx].parse().unwrap(); + (minor, 0) + } + }; + + Some(((major * 1000) + minor) * 1000 + patch) +} + +/// Returns (LLDB version, LLDB is rust-enabled) +fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> { + // Extract the major LLDB version from the given version string. + // LLDB version strings are different for Apple and non-Apple platforms. + // The Apple variant looks like this: + // + // LLDB-179.5 (older versions) + // lldb-300.2.51 (new versions) + // + // We are only interested in the major version number, so this function + // will return `Some(179)` and `Some(300)` respectively. + // + // Upstream versions look like: + // lldb version 6.0.1 + // + // There doesn't seem to be a way to correlate the Apple version + // with the upstream version, and since the tests were originally + // written against Apple versions, we make a fake Apple version by + // multiplying the first number by 100. This is a hack, but + // normally fine because the only non-Apple version we test is + // rust-enabled. + + let full_version_line = full_version_line.trim(); + + if let Some(apple_ver) = + full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-")) + { + if let Some(idx) = apple_ver.find(not_a_digit) { + let version: u32 = apple_ver[..idx].parse().unwrap(); + return Some((version, full_version_line.contains("rust-enabled"))); + } + } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") { + if let Some(idx) = lldb_ver.find(not_a_digit) { + let version: u32 = lldb_ver[..idx].parse().ok()?; + return Some((version * 100, full_version_line.contains("rust-enabled"))); + } + } + None +} + +fn not_a_digit(c: char) -> bool { + !c.is_digit(10) +} + +fn check_overlapping_tests(found_paths: &BTreeSet) { + let mut collisions = Vec::new(); + for path in found_paths { + for ancestor in path.ancestors().skip(1) { + if found_paths.contains(ancestor) { + collisions.push((path, ancestor)); + } + } + } + if !collisions.is_empty() { + let collisions: String = collisions + .into_iter() + .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n")) + .collect(); + panic!( + "{collisions}\n\ + Tests cannot have overlapping names. Make sure they use unique prefixes." + ); + } +} diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index c4bef998f3171..34d48559c378d 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -1,45 +1,6 @@ -#![crate_name = "compiletest"] -// The `test` crate is the only unstable feature -// allowed here, just to share similar code. -#![feature(test)] +use std::{env, sync::Arc}; -extern crate test; - -use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}; -use crate::common::{Config, Debugger, Mode, PassMode, TestPaths}; -use crate::util::logv; -use build_helper::git::{get_git_modified_files, get_git_untracked_files}; -use core::panic; -use getopts::Options; -use lazycell::AtomicLazyCell; -use std::collections::BTreeSet; -use std::ffi::OsString; -use std::fs; -use std::io::{self, ErrorKind}; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use std::time::SystemTime; -use std::{env, vec}; -use test::ColorConfig; -use tracing::*; -use walkdir::WalkDir; - -use self::header::{make_test_description, EarlyProps}; -use crate::header::HeadersCache; -use std::sync::Arc; - -#[cfg(test)] -mod tests; - -pub mod common; -pub mod compute_diff; -pub mod errors; -pub mod header; -mod json; -mod raise_fd_limit; -mod read2; -pub mod runtest; -pub mod util; +use compiletest::{common::Mode, log_config, parse_config, run_tests}; fn main() { tracing_subscriber::fmt::init(); @@ -57,1097 +18,3 @@ fn main() { log_config(&config); run_tests(config); } - -pub fn parse_config(args: Vec) -> Config { - let mut opts = Options::new(); - opts.reqopt("", "compile-lib-path", "path to host shared libraries", "PATH") - .reqopt("", "run-lib-path", "path to target shared libraries", "PATH") - .reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH") - .optopt("", "rustdoc-path", "path to rustdoc to use for compiling", "PATH") - .optopt("", "rust-demangler-path", "path to rust-demangler to use in tests", "PATH") - .reqopt("", "python", "path to python to use for doc tests", "PATH") - .optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH") - .optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH") - .optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM") - .optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind") - .optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH") - .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR") - .reqopt("", "src-base", "directory to scan for test files", "PATH") - .reqopt("", "build-base", "directory to deposit test outputs", "PATH") - .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH") - .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET") - .reqopt( - "", - "mode", - "which sort of compile tests to run", - "run-pass-valgrind | pretty | debug-info | codegen | rustdoc \ - | rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly", - ) - .reqopt( - "", - "suite", - "which suite of compile tests to run. used for nicer error reporting.", - "SUITE", - ) - .optopt( - "", - "pass", - "force {check,build,run}-pass tests to this mode.", - "check | build | run", - ) - .optopt("", "run", "whether to execute run-* tests", "auto | always | never") - .optflag("", "ignored", "run tests marked as ignored") - .optmulti("", "skip", "skip tests matching SUBSTRING. Can be passed multiple times", "SUBSTRING") - .optflag("", "exact", "filters match exactly") - .optopt( - "", - "runtool", - "supervisor program to run tests under \ - (eg. emulator, valgrind)", - "PROGRAM", - ) - .optmulti("", "host-rustcflags", "flags to pass to rustc for host", "FLAGS") - .optmulti("", "target-rustcflags", "flags to pass to rustc for target", "FLAGS") - .optflag("", "optimize-tests", "run tests with optimizations enabled") - .optflag("", "verbose", "run tests verbosely, showing all output") - .optflag( - "", - "bless", - "overwrite stderr/stdout files instead of complaining about a mismatch", - ) - .optflag("", "quiet", "print one character per test instead of one line") - .optopt("", "color", "coloring: auto, always, never", "WHEN") - .optflag("", "json", "emit json output instead of plaintext output") - .optopt("", "logfile", "file to log test execution to", "FILE") - .optopt("", "target", "the target to build for", "TARGET") - .optopt("", "host", "the host to build for", "HOST") - .optopt("", "cdb", "path to CDB to use for CDB debuginfo tests", "PATH") - .optopt("", "gdb", "path to GDB to use for GDB debuginfo tests", "PATH") - .optopt("", "lldb-version", "the version of LLDB used", "VERSION STRING") - .optopt("", "llvm-version", "the version of LLVM used", "VERSION STRING") - .optflag("", "system-llvm", "is LLVM the system LLVM") - .optopt("", "android-cross-path", "Android NDK standalone path", "PATH") - .optopt("", "adb-path", "path to the android debugger", "PATH") - .optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH") - .optopt("", "lldb-python-dir", "directory containing LLDB's python module", "PATH") - .reqopt("", "cc", "path to a C compiler", "PATH") - .reqopt("", "cxx", "path to a C++ compiler", "PATH") - .reqopt("", "cflags", "flags for the C compiler", "FLAGS") - .reqopt("", "cxxflags", "flags for the CXX compiler", "FLAGS") - .optopt("", "ar", "path to an archiver", "PATH") - .optopt("", "target-linker", "path to a linker for the target", "PATH") - .optopt("", "host-linker", "path to a linker for the host", "PATH") - .reqopt("", "llvm-components", "list of LLVM components built in", "LIST") - .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH") - .optopt("", "nodejs", "the name of nodejs", "PATH") - .optopt("", "npm", "the name of npm", "PATH") - .optopt("", "remote-test-client", "path to the remote test client", "PATH") - .optopt( - "", - "compare-mode", - "mode describing what file the actual ui output will be compared to", - "COMPARE MODE", - ) - .optflag( - "", - "rustfix-coverage", - "enable this to generate a Rustfix coverage file, which is saved in \ - `.//rustfix_missing_coverage.txt`", - ) - .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged") - .optflag("", "only-modified", "only run tests that result been modified") - .optflag("", "nocapture", "") - .optflag("h", "help", "show this message") - .reqopt("", "channel", "current Rust channel", "CHANNEL") - .optflag("", "git-hash", "run tests which rely on commit version being compiled into the binaries") - .optopt("", "edition", "default Rust edition", "EDITION"); - - let (argv0, args_) = args.split_first().unwrap(); - if args.len() == 1 || args[1] == "-h" || args[1] == "--help" { - let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); - println!("{}", opts.usage(&message)); - println!(); - panic!() - } - - let matches = &match opts.parse(args_) { - Ok(m) => m, - Err(f) => panic!("{:?}", f), - }; - - if matches.opt_present("h") || matches.opt_present("help") { - let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); - println!("{}", opts.usage(&message)); - println!(); - panic!() - } - - fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf { - match m.opt_str(nm) { - Some(s) => PathBuf::from(&s), - None => panic!("no option (=path) found for {}", nm), - } - } - - fn make_absolute(path: PathBuf) -> PathBuf { - if path.is_relative() { env::current_dir().unwrap().join(path) } else { path } - } - - let target = opt_str2(matches.opt_str("target")); - let android_cross_path = opt_path(matches, "android-cross-path"); - let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target); - let (gdb, gdb_version, gdb_native_rust) = - analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path); - let (lldb_version, lldb_native_rust) = matches - .opt_str("lldb-version") - .as_deref() - .and_then(extract_lldb_version) - .map(|(v, b)| (Some(v), b)) - .unwrap_or((None, false)); - let color = match matches.opt_str("color").as_deref() { - Some("auto") | None => ColorConfig::AutoColor, - Some("always") => ColorConfig::AlwaysColor, - Some("never") => ColorConfig::NeverColor, - Some(x) => panic!("argument for --color must be auto, always, or never, but found `{}`", x), - }; - let llvm_version = - matches.opt_str("llvm-version").as_deref().and_then(header::extract_llvm_version).or_else( - || header::extract_llvm_version_from_binary(&matches.opt_str("llvm-filecheck")?), - ); - - let src_base = opt_path(matches, "src-base"); - let run_ignored = matches.opt_present("ignored"); - let mode = matches.opt_str("mode").unwrap().parse().expect("invalid mode"); - let has_tidy = if mode == Mode::Rustdoc { - Command::new("tidy") - .arg("--version") - .stdout(Stdio::null()) - .status() - .map_or(false, |status| status.success()) - } else { - // Avoid spawning an external command when we know tidy won't be used. - false - }; - Config { - bless: matches.opt_present("bless"), - compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")), - run_lib_path: make_absolute(opt_path(matches, "run-lib-path")), - rustc_path: opt_path(matches, "rustc-path"), - rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from), - rust_demangler_path: matches.opt_str("rust-demangler-path").map(PathBuf::from), - python: matches.opt_str("python").unwrap(), - jsondocck_path: matches.opt_str("jsondocck-path"), - jsondoclint_path: matches.opt_str("jsondoclint-path"), - valgrind_path: matches.opt_str("valgrind-path"), - force_valgrind: matches.opt_present("force-valgrind"), - run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"), - llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from), - llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from), - src_base, - build_base: opt_path(matches, "build-base"), - sysroot_base: opt_path(matches, "sysroot-base"), - stage_id: matches.opt_str("stage-id").unwrap(), - mode, - suite: matches.opt_str("suite").unwrap(), - debugger: None, - run_ignored, - filters: matches.free.clone(), - skip: matches.opt_strs("skip"), - filter_exact: matches.opt_present("exact"), - force_pass_mode: matches.opt_str("pass").map(|mode| { - mode.parse::() - .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode)) - }), - run: matches.opt_str("run").and_then(|mode| match mode.as_str() { - "auto" => None, - "always" => Some(true), - "never" => Some(false), - _ => panic!("unknown `--run` option `{}` given", mode), - }), - logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)), - runtool: matches.opt_str("runtool"), - host_rustcflags: matches.opt_strs("host-rustcflags"), - target_rustcflags: matches.opt_strs("target-rustcflags"), - optimize_tests: matches.opt_present("optimize-tests"), - target, - host: opt_str2(matches.opt_str("host")), - cdb, - cdb_version, - gdb, - gdb_version, - gdb_native_rust, - lldb_version, - lldb_native_rust, - llvm_version, - system_llvm: matches.opt_present("system-llvm"), - android_cross_path, - adb_path: opt_str2(matches.opt_str("adb-path")), - adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")), - adb_device_status: opt_str2(matches.opt_str("target")).contains("android") - && "(none)" != opt_str2(matches.opt_str("adb-test-dir")) - && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(), - lldb_python_dir: matches.opt_str("lldb-python-dir"), - verbose: matches.opt_present("verbose"), - format: match (matches.opt_present("quiet"), matches.opt_present("json")) { - (true, true) => panic!("--quiet and --json are incompatible"), - (true, false) => test::OutputFormat::Terse, - (false, true) => test::OutputFormat::Json, - (false, false) => test::OutputFormat::Pretty, - }, - only_modified: matches.opt_present("only-modified"), - color, - remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from), - compare_mode: matches - .opt_str("compare-mode") - .map(|s| s.parse().expect("invalid --compare-mode provided")), - rustfix_coverage: matches.opt_present("rustfix-coverage"), - has_tidy, - channel: matches.opt_str("channel").unwrap(), - git_hash: matches.opt_present("git-hash"), - edition: matches.opt_str("edition"), - - cc: matches.opt_str("cc").unwrap(), - cxx: matches.opt_str("cxx").unwrap(), - cflags: matches.opt_str("cflags").unwrap(), - cxxflags: matches.opt_str("cxxflags").unwrap(), - ar: matches.opt_str("ar").unwrap_or_else(|| String::from("ar")), - target_linker: matches.opt_str("target-linker"), - host_linker: matches.opt_str("host-linker"), - llvm_components: matches.opt_str("llvm-components").unwrap(), - nodejs: matches.opt_str("nodejs"), - npm: matches.opt_str("npm"), - - force_rerun: matches.opt_present("force-rerun"), - - target_cfgs: AtomicLazyCell::new(), - - nocapture: matches.opt_present("nocapture"), - } -} - -pub fn log_config(config: &Config) { - let c = config; - logv(c, "configuration:".to_string()); - logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path)); - logv(c, format!("run_lib_path: {:?}", config.run_lib_path)); - logv(c, format!("rustc_path: {:?}", config.rustc_path.display())); - logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path)); - logv(c, format!("rust_demangler_path: {:?}", config.rust_demangler_path)); - logv(c, format!("src_base: {:?}", config.src_base.display())); - logv(c, format!("build_base: {:?}", config.build_base.display())); - logv(c, format!("stage_id: {}", config.stage_id)); - logv(c, format!("mode: {}", config.mode)); - logv(c, format!("run_ignored: {}", config.run_ignored)); - logv(c, format!("filters: {:?}", config.filters)); - logv(c, format!("skip: {:?}", config.skip)); - logv(c, format!("filter_exact: {}", config.filter_exact)); - logv( - c, - format!("force_pass_mode: {}", opt_str(&config.force_pass_mode.map(|m| format!("{}", m))),), - ); - logv(c, format!("runtool: {}", opt_str(&config.runtool))); - logv(c, format!("host-rustcflags: {:?}", config.host_rustcflags)); - logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags)); - logv(c, format!("target: {}", config.target)); - logv(c, format!("host: {}", config.host)); - logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display())); - logv(c, format!("adb_path: {:?}", config.adb_path)); - logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir)); - logv(c, format!("adb_device_status: {}", config.adb_device_status)); - logv(c, format!("ar: {}", config.ar)); - logv(c, format!("target-linker: {:?}", config.target_linker)); - logv(c, format!("host-linker: {:?}", config.host_linker)); - logv(c, format!("verbose: {}", config.verbose)); - logv(c, format!("format: {:?}", config.format)); - logv(c, "\n".to_string()); -} - -pub fn opt_str(maybestr: &Option) -> &str { - match *maybestr { - None => "(none)", - Some(ref s) => s, - } -} - -pub fn opt_str2(maybestr: Option) -> String { - match maybestr { - None => "(none)".to_owned(), - Some(s) => s, - } -} - -pub fn run_tests(config: Arc) { - // If we want to collect rustfix coverage information, - // we first make sure that the coverage file does not exist. - // It will be created later on. - if config.rustfix_coverage { - let mut coverage_file_path = config.build_base.clone(); - coverage_file_path.push("rustfix_missing_coverage.txt"); - if coverage_file_path.exists() { - if let Err(e) = fs::remove_file(&coverage_file_path) { - panic!("Could not delete {} due to {}", coverage_file_path.display(), e) - } - } - } - - // sadly osx needs some file descriptor limits raised for running tests in - // parallel (especially when we have lots and lots of child processes). - // For context, see #8904 - unsafe { - raise_fd_limit::raise_fd_limit(); - } - // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows - // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary - env::set_var("__COMPAT_LAYER", "RunAsInvoker"); - - // Let tests know which target they're running as - env::set_var("TARGET", &config.target); - - let opts = test_opts(&config); - - let mut configs = Vec::new(); - if let Mode::DebugInfo = config.mode { - // Debugging emscripten code doesn't make sense today - if !config.target.contains("emscripten") { - configs.extend(configure_cdb(&config)); - configs.extend(configure_gdb(&config)); - configs.extend(configure_lldb(&config)); - } - } else { - configs.push(config.clone()); - }; - - let mut tests = Vec::new(); - for c in configs { - let mut found_paths = BTreeSet::new(); - make_tests(c, &mut tests, &mut found_paths); - check_overlapping_tests(&found_paths); - } - - tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice())); - - let res = test::run_tests_console(&opts, tests); - match res { - Ok(true) => {} - Ok(false) => { - // We want to report that the tests failed, but we also want to give - // some indication of just what tests we were running. Especially on - // CI, where there can be cross-compiled tests for a lot of - // architectures, without this critical information it can be quite - // easy to miss which tests failed, and as such fail to reproduce - // the failure locally. - - println!( - "Some tests failed in compiletest suite={}{} mode={} host={} target={}", - config.suite, - config - .compare_mode - .as_ref() - .map(|c| format!(" compare_mode={:?}", c)) - .unwrap_or_default(), - config.mode, - config.host, - config.target - ); - - std::process::exit(1); - } - Err(e) => { - // We don't know if tests passed or not, but if there was an error - // during testing we don't want to just succeed (we may not have - // tested something), so fail. - // - // This should realistically "never" happen, so don't try to make - // this a pretty error message. - panic!("I/O failure during tests: {:?}", e); - } - } -} - -fn configure_cdb(config: &Config) -> Option> { - config.cdb.as_ref()?; - - Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() })) -} - -fn configure_gdb(config: &Config) -> Option> { - config.gdb_version?; - - if config.matches_env("msvc") { - return None; - } - - if config.remote_test_client.is_some() && !config.target.contains("android") { - println!( - "WARNING: debuginfo tests are not available when \ - testing with remote" - ); - return None; - } - - if config.target.contains("android") { - println!( - "{} debug-info test uses tcp 5039 port.\ - please reserve it", - config.target - ); - - // android debug-info test uses remote debugger so, we test 1 thread - // at once as they're all sharing the same TCP port to communicate - // over. - // - // we should figure out how to lift this restriction! (run them all - // on different ports allocated dynamically). - env::set_var("RUST_TEST_THREADS", "1"); - } - - Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() })) -} - -fn configure_lldb(config: &Config) -> Option> { - config.lldb_python_dir.as_ref()?; - - if let Some(350) = config.lldb_version { - println!( - "WARNING: The used version of LLDB (350) has a \ - known issue that breaks debuginfo tests. See \ - issue #32520 for more information. Skipping all \ - LLDB-based tests!", - ); - return None; - } - - Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() })) -} - -pub fn test_opts(config: &Config) -> test::TestOpts { - if env::var("RUST_TEST_NOCAPTURE").is_ok() { - eprintln!( - "WARNING: RUST_TEST_NOCAPTURE is no longer used. \ - Use the `--nocapture` flag instead." - ); - } - - test::TestOpts { - exclude_should_panic: false, - filters: config.filters.clone(), - filter_exact: config.filter_exact, - run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No }, - format: config.format, - logfile: config.logfile.clone(), - run_tests: true, - bench_benchmarks: true, - nocapture: config.nocapture, - color: config.color, - shuffle: false, - shuffle_seed: None, - test_threads: None, - skip: config.skip.clone(), - list: false, - options: test::Options::new(), - time_options: None, - force_run_in_process: false, - fail_fast: std::env::var_os("RUSTC_TEST_FAIL_FAST").is_some(), - } -} - -pub fn make_tests( - config: Arc, - tests: &mut Vec, - found_paths: &mut BTreeSet, -) { - debug!("making tests from {:?}", config.src_base.display()); - let inputs = common_inputs_stamp(&config); - let modified_tests = modified_tests(&config, &config.src_base).unwrap_or_else(|err| { - panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) - }); - - let cache = HeadersCache::load(&config); - let mut poisoned = false; - collect_tests_from_dir( - config.clone(), - &cache, - &config.src_base, - &PathBuf::new(), - &inputs, - tests, - found_paths, - &modified_tests, - &mut poisoned, - ) - .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display())); - - if poisoned { - eprintln!(); - panic!("there are errors in tests"); - } -} - -/// Returns a stamp constructed from input files common to all test cases. -fn common_inputs_stamp(config: &Config) -> Stamp { - let rust_src_dir = config.find_rust_src_root().expect("Could not find Rust source root"); - - let mut stamp = Stamp::from_path(&config.rustc_path); - - // Relevant pretty printer files - let pretty_printer_files = [ - "src/etc/rust_types.py", - "src/etc/gdb_load_rust_pretty_printers.py", - "src/etc/gdb_lookup.py", - "src/etc/gdb_providers.py", - "src/etc/lldb_batchmode.py", - "src/etc/lldb_lookup.py", - "src/etc/lldb_providers.py", - ]; - for file in &pretty_printer_files { - let path = rust_src_dir.join(file); - stamp.add_path(&path); - } - - stamp.add_dir(&rust_src_dir.join("src/etc/natvis")); - - stamp.add_dir(&config.run_lib_path); - - if let Some(ref rustdoc_path) = config.rustdoc_path { - stamp.add_path(&rustdoc_path); - stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py")); - } - - // Compiletest itself. - stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/")); - - stamp -} - -fn modified_tests(config: &Config, dir: &Path) -> Result, String> { - if !config.only_modified { - return Ok(vec![]); - } - let files = - get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]); - // Add new test cases to the list, it will be convenient in daily development. - let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]); - - let all_paths = [&files[..], &untracked_files[..]].concat(); - let full_paths = { - let mut full_paths: Vec = all_paths - .into_iter() - .map(|f| PathBuf::from(f).with_extension("").with_extension("rs")) - .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None }) - .collect(); - full_paths.dedup(); - full_paths.sort_unstable(); - full_paths - }; - Ok(full_paths) -} - -fn collect_tests_from_dir( - config: Arc, - cache: &HeadersCache, - dir: &Path, - relative_dir_path: &Path, - inputs: &Stamp, - tests: &mut Vec, - found_paths: &mut BTreeSet, - modified_tests: &Vec, - poisoned: &mut bool, -) -> io::Result<()> { - // Ignore directories that contain a file named `compiletest-ignore-dir`. - if dir.join("compiletest-ignore-dir").exists() { - return Ok(()); - } - - if config.mode == Mode::RunMake && dir.join("Makefile").exists() { - let paths = TestPaths { - file: dir.to_path_buf(), - relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), - }; - tests.extend(make_test(config, cache, &paths, inputs, poisoned)); - return Ok(()); - } - - // If we find a test foo/bar.rs, we have to build the - // output directory `$build/foo` so we can write - // `$build/foo/bar` into it. We do this *now* in this - // sequential loop because otherwise, if we do it in the - // tests themselves, they race for the privilege of - // creating the directories and sometimes fail randomly. - let build_dir = output_relative_path(&config, relative_dir_path); - fs::create_dir_all(&build_dir).unwrap(); - - // Add each `.rs` file as a test, and recurse further on any - // subdirectories we find, except for `aux` directories. - for file in fs::read_dir(dir)? { - let file = file?; - let file_path = file.path(); - let file_name = file.file_name(); - if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) { - debug!("found test file: {:?}", file_path.display()); - let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap()); - found_paths.insert(rel_test_path); - let paths = - TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; - - tests.extend(make_test(config.clone(), cache, &paths, inputs, poisoned)) - } else if file_path.is_dir() { - let relative_file_path = relative_dir_path.join(file.file_name()); - if &file_name != "auxiliary" { - debug!("found directory: {:?}", file_path.display()); - collect_tests_from_dir( - config.clone(), - cache, - &file_path, - &relative_file_path, - inputs, - tests, - found_paths, - modified_tests, - poisoned, - )?; - } - } else { - debug!("found other file/directory: {:?}", file_path.display()); - } - } - Ok(()) -} - -/// Returns true if `file_name` looks like a proper test file name. -pub fn is_test(file_name: &OsString) -> bool { - let file_name = file_name.to_str().unwrap(); - - if !file_name.ends_with(".rs") { - return false; - } - - // `.`, `#`, and `~` are common temp-file prefixes. - let invalid_prefixes = &[".", "#", "~"]; - !invalid_prefixes.iter().any(|p| file_name.starts_with(p)) -} - -fn make_test( - config: Arc, - cache: &HeadersCache, - testpaths: &TestPaths, - inputs: &Stamp, - poisoned: &mut bool, -) -> Vec { - let test_path = if config.mode == Mode::RunMake { - // Parse directives in the Makefile - testpaths.file.join("Makefile") - } else { - PathBuf::from(&testpaths.file) - }; - let early_props = EarlyProps::from_file(&config, &test_path); - - // Incremental tests are special, they inherently cannot be run in parallel. - // `runtest::run` will be responsible for iterating over revisions. - let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental { - vec![None] - } else { - early_props.revisions.iter().map(Some).collect() - }; - - revisions - .into_iter() - .map(|revision| { - let src_file = - std::fs::File::open(&test_path).expect("open test file to parse ignores"); - let cfg = revision.map(|v| &**v); - let test_name = crate::make_test_name(&config, testpaths, revision); - let mut desc = make_test_description( - &config, cache, test_name, &test_path, src_file, cfg, poisoned, - ); - // Ignore tests that already run and are up to date with respect to inputs. - if !config.force_rerun { - desc.ignore |= is_up_to_date( - &config, - testpaths, - &early_props, - revision.map(|s| s.as_str()), - inputs, - ); - } - test::TestDescAndFn { - desc, - testfn: make_test_closure(config.clone(), testpaths, revision), - } - }) - .collect() -} - -fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { - output_base_dir(config, testpaths, revision).join("stamp") -} - -fn files_related_to_test( - config: &Config, - testpaths: &TestPaths, - props: &EarlyProps, - revision: Option<&str>, -) -> Vec { - let mut related = vec![]; - - if testpaths.file.is_dir() { - // run-make tests use their individual directory - for entry in WalkDir::new(&testpaths.file) { - let path = entry.unwrap().into_path(); - if path.is_file() { - related.push(path); - } - } - } else { - related.push(testpaths.file.clone()); - } - - for aux in &props.aux { - let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux); - related.push(path); - } - - // UI test files. - for extension in UI_EXTENSIONS { - let path = expected_output_path(testpaths, revision, &config.compare_mode, extension); - related.push(path); - } - - related -} - -fn is_up_to_date( - config: &Config, - testpaths: &TestPaths, - props: &EarlyProps, - revision: Option<&str>, - inputs: &Stamp, -) -> bool { - let stamp_name = stamp(config, testpaths, revision); - // Check hash. - let contents = match fs::read_to_string(&stamp_name) { - Ok(f) => f, - Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"), - Err(_) => return false, - }; - let expected_hash = runtest::compute_stamp_hash(config); - if contents != expected_hash { - return false; - } - - // Check timestamps. - let mut inputs = inputs.clone(); - for path in files_related_to_test(config, testpaths, props, revision) { - inputs.add_path(&path); - } - - inputs < Stamp::from_path(&stamp_name) -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct Stamp { - time: SystemTime, -} - -impl Stamp { - fn from_path(path: &Path) -> Self { - let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH }; - stamp.add_path(path); - stamp - } - - fn add_path(&mut self, path: &Path) { - let modified = fs::metadata(path) - .and_then(|metadata| metadata.modified()) - .unwrap_or(SystemTime::UNIX_EPOCH); - self.time = self.time.max(modified); - } - - fn add_dir(&mut self, path: &Path) { - for entry in WalkDir::new(path) { - let entry = entry.unwrap(); - if entry.file_type().is_file() { - let modified = entry - .metadata() - .ok() - .and_then(|metadata| metadata.modified().ok()) - .unwrap_or(SystemTime::UNIX_EPOCH); - self.time = self.time.max(modified); - } - } - } -} - -fn make_test_name( - config: &Config, - testpaths: &TestPaths, - revision: Option<&String>, -) -> test::TestName { - // Print the name of the file, relative to the repository root. - // `src_base` looks like `/path/to/rust/tests/ui` - let root_directory = config.src_base.parent().unwrap().parent().unwrap(); - let path = testpaths.file.strip_prefix(root_directory).unwrap(); - let debugger = match config.debugger { - Some(d) => format!("-{}", d), - None => String::new(), - }; - let mode_suffix = match config.compare_mode { - Some(ref mode) => format!(" ({})", mode.to_str()), - None => String::new(), - }; - - test::DynTestName(format!( - "[{}{}{}] {}{}", - config.mode, - debugger, - mode_suffix, - path.display(), - revision.map_or("".to_string(), |rev| format!("#{}", rev)) - )) -} - -fn make_test_closure( - config: Arc, - testpaths: &TestPaths, - revision: Option<&String>, -) -> test::TestFn { - let config = config.clone(); - let testpaths = testpaths.clone(); - let revision = revision.cloned(); - test::DynTestFn(Box::new(move || { - runtest::run(config, &testpaths, revision.as_deref()); - Ok(()) - })) -} - -/// Returns `true` if the given target is an Android target for the -/// purposes of GDB testing. -fn is_android_gdb_target(target: &str) -> bool { - matches!( - &target[..], - "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" - ) -} - -/// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing. -fn is_pc_windows_msvc_target(target: &str) -> bool { - target.ends_with("-pc-windows-msvc") -} - -fn find_cdb(target: &str) -> Option { - if !(cfg!(windows) && is_pc_windows_msvc_target(target)) { - return None; - } - - let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?; - let cdb_arch = if cfg!(target_arch = "x86") { - "x86" - } else if cfg!(target_arch = "x86_64") { - "x64" - } else if cfg!(target_arch = "aarch64") { - "arm64" - } else if cfg!(target_arch = "arm") { - "arm" - } else { - return None; // No compatible CDB.exe in the Windows 10 SDK - }; - - let mut path = PathBuf::new(); - path.push(pf86); - path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too? - path.push(cdb_arch); - path.push(r"cdb.exe"); - - if !path.exists() { - return None; - } - - Some(path.into_os_string()) -} - -/// Returns Path to CDB -fn analyze_cdb(cdb: Option, target: &str) -> (Option, Option<[u16; 4]>) { - let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target)); - - let mut version = None; - if let Some(cdb) = cdb.as_ref() { - if let Ok(output) = Command::new(cdb).arg("/version").output() { - if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { - version = extract_cdb_version(&first_line); - } - } - } - - (cdb, version) -} - -fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> { - // Example full_version_line: "cdb version 10.0.18362.1" - let version = full_version_line.rsplit(' ').next()?; - let mut components = version.split('.'); - let major: u16 = components.next().unwrap().parse().unwrap(); - let minor: u16 = components.next().unwrap().parse().unwrap(); - let patch: u16 = components.next().unwrap_or("0").parse().unwrap(); - let build: u16 = components.next().unwrap_or("0").parse().unwrap(); - Some([major, minor, patch, build]) -} - -/// Returns (Path to GDB, GDB Version, GDB has Rust Support) -fn analyze_gdb( - gdb: Option, - target: &str, - android_cross_path: &PathBuf, -) -> (Option, Option, bool) { - #[cfg(not(windows))] - const GDB_FALLBACK: &str = "gdb"; - #[cfg(windows)] - const GDB_FALLBACK: &str = "gdb.exe"; - - const MIN_GDB_WITH_RUST: u32 = 7011010; - - let fallback_gdb = || { - if is_android_gdb_target(target) { - let mut gdb_path = match android_cross_path.to_str() { - Some(x) => x.to_owned(), - None => panic!("cannot find android cross path"), - }; - gdb_path.push_str("/bin/gdb"); - gdb_path - } else { - GDB_FALLBACK.to_owned() - } - }; - - let gdb = match gdb { - None => fallback_gdb(), - Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb - Some(ref s) => s.to_owned(), - }; - - let mut version_line = None; - if let Ok(output) = Command::new(&gdb).arg("--version").output() { - if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { - version_line = Some(first_line.to_string()); - } - } - - let version = match version_line { - Some(line) => extract_gdb_version(&line), - None => return (None, None, false), - }; - - let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST); - - (Some(gdb), version, gdb_native_rust) -} - -fn extract_gdb_version(full_version_line: &str) -> Option { - let full_version_line = full_version_line.trim(); - - // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both - // of the ? sections being optional - - // We will parse up to 3 digits for each component, ignoring the date - - // We skip text in parentheses. This avoids accidentally parsing - // the openSUSE version, which looks like: - // GNU gdb (GDB; openSUSE Leap 15.0) 8.1 - // This particular form is documented in the GNU coding standards: - // https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion - - let unbracketed_part = full_version_line.split('[').next().unwrap(); - let mut splits = unbracketed_part.trim_end().rsplit(' '); - let version_string = splits.next().unwrap(); - - let mut splits = version_string.split('.'); - let major = splits.next().unwrap(); - let minor = splits.next().unwrap(); - let patch = splits.next(); - - let major: u32 = major.parse().unwrap(); - let (minor, patch): (u32, u32) = match minor.find(not_a_digit) { - None => { - let minor = minor.parse().unwrap(); - let patch: u32 = match patch { - Some(patch) => match patch.find(not_a_digit) { - None => patch.parse().unwrap(), - Some(idx) if idx > 3 => 0, - Some(idx) => patch[..idx].parse().unwrap(), - }, - None => 0, - }; - (minor, patch) - } - // There is no patch version after minor-date (e.g. "4-2012"). - Some(idx) => { - let minor = minor[..idx].parse().unwrap(); - (minor, 0) - } - }; - - Some(((major * 1000) + minor) * 1000 + patch) -} - -/// Returns (LLDB version, LLDB is rust-enabled) -fn extract_lldb_version(full_version_line: &str) -> Option<(u32, bool)> { - // Extract the major LLDB version from the given version string. - // LLDB version strings are different for Apple and non-Apple platforms. - // The Apple variant looks like this: - // - // LLDB-179.5 (older versions) - // lldb-300.2.51 (new versions) - // - // We are only interested in the major version number, so this function - // will return `Some(179)` and `Some(300)` respectively. - // - // Upstream versions look like: - // lldb version 6.0.1 - // - // There doesn't seem to be a way to correlate the Apple version - // with the upstream version, and since the tests were originally - // written against Apple versions, we make a fake Apple version by - // multiplying the first number by 100. This is a hack, but - // normally fine because the only non-Apple version we test is - // rust-enabled. - - let full_version_line = full_version_line.trim(); - - if let Some(apple_ver) = - full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-")) - { - if let Some(idx) = apple_ver.find(not_a_digit) { - let version: u32 = apple_ver[..idx].parse().unwrap(); - return Some((version, full_version_line.contains("rust-enabled"))); - } - } else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") { - if let Some(idx) = lldb_ver.find(not_a_digit) { - let version: u32 = lldb_ver[..idx].parse().ok()?; - return Some((version * 100, full_version_line.contains("rust-enabled"))); - } - } - None -} - -fn not_a_digit(c: char) -> bool { - !c.is_digit(10) -} - -fn check_overlapping_tests(found_paths: &BTreeSet) { - let mut collisions = Vec::new(); - for path in found_paths { - for ancestor in path.ancestors().skip(1) { - if found_paths.contains(ancestor) { - collisions.push((path, ancestor.clone())); - } - } - } - if !collisions.is_empty() { - let collisions: String = collisions - .into_iter() - .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n")) - .collect(); - panic!( - "{collisions}\n\ - Tests cannot have overlapping names. Make sure they use unique prefixes." - ); - } -} diff --git a/src/tools/compiletest/src/read2.rs b/src/tools/compiletest/src/read2.rs index 725f7a1515c62..a455a1badc053 100644 --- a/src/tools/compiletest/src/read2.rs +++ b/src/tools/compiletest/src/read2.rs @@ -83,7 +83,7 @@ impl ProcOutput { } let new_len = bytes.len(); - if *filtered_len <= HEAD_LEN + TAIL_LEN { + if (*filtered_len).min(new_len) <= HEAD_LEN + TAIL_LEN { return; } diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 5bc4d16426551..4ef79af3124a8 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -6,8 +6,8 @@ use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJs use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc}; use crate::common::{CompareMode, FailMode, PassMode}; use crate::common::{Config, TestPaths}; -use crate::common::{Pretty, RunPassValgrind}; -use crate::common::{UI_RUN_STDERR, UI_RUN_STDOUT}; +use crate::common::{Pretty, RunCoverage, RunPassValgrind}; +use crate::common::{UI_COVERAGE, UI_RUN_STDERR, UI_RUN_STDOUT}; use crate::compute_diff::{write_diff, write_filtered_diff}; use crate::errors::{self, Error, ErrorKind}; use crate::header::TestProps; @@ -18,6 +18,7 @@ use crate::ColorConfig; use regex::{Captures, Regex}; use rustfix::{apply_suggestions, get_suggestions_from_json, Filter}; +use std::borrow::Cow; use std::collections::hash_map::DefaultHasher; use std::collections::{HashMap, HashSet}; use std::env; @@ -41,7 +42,7 @@ use crate::extract_gdb_version; use crate::is_android_gdb_target; mod debugger; -use debugger::{check_debugger_output, DebuggerCommands}; +use debugger::DebuggerCommands; #[cfg(test)] mod tests; @@ -253,6 +254,7 @@ impl<'test> TestCx<'test> { MirOpt => self.run_mir_opt_test(), Assembly => self.run_assembly_test(), JsDocTest => self.run_js_doc_test(), + RunCoverage => self.run_coverage_test(), } } @@ -384,7 +386,7 @@ impl<'test> TestCx<'test> { } fn check_correct_failure_status(&self, proc_res: &ProcRes) { - let expected_status = Some(self.props.failure_status); + let expected_status = Some(self.props.failure_status.unwrap_or(1)); let received_status = proc_res.status.code(); if expected_status != received_status { @@ -465,6 +467,312 @@ impl<'test> TestCx<'test> { } } + fn run_coverage_test(&self) { + let should_run = self.run_if_enabled(); + let proc_res = self.compile_test(should_run, Emit::None); + + if !proc_res.status.success() { + self.fatal_proc_rec("compilation failed!", &proc_res); + } + drop(proc_res); + + if let WillExecute::Disabled = should_run { + return; + } + + let profraw_path = self.output_base_dir().join("default.profraw"); + let profdata_path = self.output_base_dir().join("default.profdata"); + + // Delete any existing profraw/profdata files to rule out unintended + // interference between repeated test runs. + if profraw_path.exists() { + std::fs::remove_file(&profraw_path).unwrap(); + } + if profdata_path.exists() { + std::fs::remove_file(&profdata_path).unwrap(); + } + + let proc_res = self.exec_compiled_test_general( + &[("LLVM_PROFILE_FILE", &profraw_path.to_str().unwrap())], + false, + ); + if self.props.failure_status.is_some() { + self.check_correct_failure_status(&proc_res); + } else if !proc_res.status.success() { + self.fatal_proc_rec("test run failed!", &proc_res); + } + drop(proc_res); + + let mut profraw_paths = vec![profraw_path]; + let mut bin_paths = vec![self.make_exe_name()]; + + if self.config.suite == "run-coverage-rustdoc" { + self.run_doctests_for_coverage(&mut profraw_paths, &mut bin_paths); + } + + // Run `llvm-profdata merge` to index the raw coverage output. + let proc_res = self.run_llvm_tool("llvm-profdata", |cmd| { + cmd.args(["merge", "--sparse", "--output"]); + cmd.arg(&profdata_path); + cmd.args(&profraw_paths); + }); + if !proc_res.status.success() { + self.fatal_proc_rec("llvm-profdata merge failed!", &proc_res); + } + drop(proc_res); + + // Run `llvm-cov show` to produce a coverage report in text format. + let proc_res = self.run_llvm_tool("llvm-cov", |cmd| { + cmd.args(["show", "--format=text", "--show-line-counts-or-regions"]); + + cmd.arg("--Xdemangler"); + cmd.arg(self.config.rust_demangler_path.as_ref().unwrap()); + + cmd.arg("--instr-profile"); + cmd.arg(&profdata_path); + + for bin in &bin_paths { + cmd.arg("--object"); + cmd.arg(bin); + } + }); + if !proc_res.status.success() { + self.fatal_proc_rec("llvm-cov show failed!", &proc_res); + } + + let kind = UI_COVERAGE; + + let expected_coverage = self.load_expected_output(kind); + let normalized_actual_coverage = + self.normalize_coverage_output(&proc_res.stdout).unwrap_or_else(|err| { + self.fatal_proc_rec(&err, &proc_res); + }); + + let coverage_errors = self.compare_output( + kind, + &normalized_actual_coverage, + &expected_coverage, + self.props.compare_output_lines_by_subset, + ); + + if coverage_errors > 0 { + self.fatal_proc_rec( + &format!("{} errors occurred comparing coverage output.", coverage_errors), + &proc_res, + ); + } + } + + /// Run any doctests embedded in this test file, and add any resulting + /// `.profraw` files and doctest executables to the given vectors. + fn run_doctests_for_coverage( + &self, + profraw_paths: &mut Vec, + bin_paths: &mut Vec, + ) { + // Put .profraw files and doctest executables in dedicated directories, + // to make it easier to glob them all later. + let profraws_dir = self.output_base_dir().join("doc_profraws"); + let bins_dir = self.output_base_dir().join("doc_bins"); + + // Remove existing directories to prevent cross-run interference. + if profraws_dir.try_exists().unwrap() { + std::fs::remove_dir_all(&profraws_dir).unwrap(); + } + if bins_dir.try_exists().unwrap() { + std::fs::remove_dir_all(&bins_dir).unwrap(); + } + + let mut rustdoc_cmd = + Command::new(self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed")); + + // In general there will be multiple doctest binaries running, so we + // tell the profiler runtime to write their coverage data into separate + // profraw files. + rustdoc_cmd.env("LLVM_PROFILE_FILE", profraws_dir.join("%p-%m.profraw")); + + rustdoc_cmd.args(["--test", "-Cinstrument-coverage"]); + + // Without this, the doctests complain about not being able to find + // their enclosing file's crate for some reason. + rustdoc_cmd.args(["--crate-name", "workaround_for_79771"]); + + // Persist the doctest binaries so that `llvm-cov show` can read their + // embedded coverage mappings later. + rustdoc_cmd.arg("-Zunstable-options"); + rustdoc_cmd.arg("--persist-doctests"); + rustdoc_cmd.arg(&bins_dir); + + rustdoc_cmd.arg("-L"); + rustdoc_cmd.arg(self.aux_output_dir_name()); + + rustdoc_cmd.arg(&self.testpaths.file); + + let proc_res = self.compose_and_run_compiler(rustdoc_cmd, None); + if !proc_res.status.success() { + self.fatal_proc_rec("rustdoc --test failed!", &proc_res) + } + + fn glob_iter(path: impl AsRef) -> impl Iterator { + let path_str = path.as_ref().to_str().unwrap(); + let iter = glob(path_str).unwrap(); + iter.map(Result::unwrap) + } + + // Find all profraw files in the profraw directory. + for p in glob_iter(profraws_dir.join("*.profraw")) { + profraw_paths.push(p); + } + // Find all executables in the `--persist-doctests` directory, while + // avoiding other file types (e.g. `.pdb` on Windows). This doesn't + // need to be perfect, as long as it can handle the files actually + // produced by `rustdoc --test`. + for p in glob_iter(bins_dir.join("**/*")) { + let is_bin = p.is_file() + && match p.extension() { + None => true, + Some(ext) => ext == OsStr::new("exe"), + }; + if is_bin { + bin_paths.push(p); + } + } + } + + fn run_llvm_tool(&self, name: &str, configure_cmd_fn: impl FnOnce(&mut Command)) -> ProcRes { + let tool_path = self + .config + .llvm_bin_dir + .as_ref() + .expect("this test expects the LLVM bin dir to be available") + .join(name); + + let mut cmd = Command::new(tool_path); + configure_cmd_fn(&mut cmd); + + let output = cmd.output().unwrap_or_else(|_| panic!("failed to exec `{cmd:?}`")); + + let proc_res = ProcRes { + status: output.status, + stdout: String::from_utf8(output.stdout).unwrap(), + stderr: String::from_utf8(output.stderr).unwrap(), + cmdline: format!("{cmd:?}"), + }; + self.dump_output(&proc_res.stdout, &proc_res.stderr); + + proc_res + } + + fn normalize_coverage_output(&self, coverage: &str) -> Result { + let normalized = self.normalize_output(coverage, &[]); + let normalized = Self::anonymize_coverage_line_numbers(&normalized); + + let mut lines = normalized.lines().collect::>(); + + Self::sort_coverage_file_sections(&mut lines)?; + Self::sort_coverage_subviews(&mut lines)?; + + let joined_lines = lines.iter().flat_map(|line| [line, "\n"]).collect::(); + Ok(joined_lines) + } + + /// Replace line numbers in coverage reports with the placeholder `LL`, + /// so that the tests are less sensitive to lines being added/removed. + fn anonymize_coverage_line_numbers(coverage: &str) -> Cow<'_, str> { + // The coverage reporter prints line numbers at the start of a line. + // They are truncated or left-padded to occupy exactly 5 columns. + // (`LineNumberColumnWidth` in `SourceCoverageViewText.cpp`.) + // A pipe character `|` appears immediately after the final digit. + // + // Line numbers that appear inside expansion/instantiation subviews + // have an additional prefix of ` |` for each nesting level. + static LINE_NUMBER_RE: Lazy = + Lazy::new(|| Regex::new(r"(?m:^)(?(?: \|)*) *[0-9]+\|").unwrap()); + LINE_NUMBER_RE.replace_all(coverage, "$prefix LL|") + } + + /// Coverage reports can describe multiple source files, separated by + /// blank lines. The order of these files is unpredictable (since it + /// depends on implementation details), so we need to sort the file + /// sections into a consistent order before comparing against a snapshot. + fn sort_coverage_file_sections(coverage_lines: &mut Vec<&str>) -> Result<(), String> { + // Group the lines into file sections, separated by blank lines. + let mut sections = coverage_lines.split(|line| line.is_empty()).collect::>(); + + // The last section should be empty, representing an extra trailing blank line. + if !sections.last().is_some_and(|last| last.is_empty()) { + return Err("coverage report should end with an extra blank line".to_owned()); + } + + // Sort the file sections (not including the final empty "section"). + let except_last = sections.len() - 1; + (&mut sections[..except_last]).sort(); + + // Join the file sections back into a flat list of lines, with + // sections separated by blank lines. + let joined = sections.join(&[""] as &[_]); + assert_eq!(joined.len(), coverage_lines.len()); + *coverage_lines = joined; + + Ok(()) + } + + fn sort_coverage_subviews(coverage_lines: &mut Vec<&str>) -> Result<(), String> { + let mut output_lines = Vec::new(); + + // We accumulate a list of zero or more "subviews", where each + // subview is a list of one or more lines. + let mut subviews: Vec> = Vec::new(); + + fn flush<'a>(subviews: &mut Vec>, output_lines: &mut Vec<&'a str>) { + if subviews.is_empty() { + return; + } + + // Take and clear the list of accumulated subviews. + let mut subviews = std::mem::take(subviews); + + // The last "subview" should be just a boundary line on its own, + // so exclude it when sorting the other subviews. + let except_last = subviews.len() - 1; + (&mut subviews[..except_last]).sort(); + + for view in subviews { + for line in view { + output_lines.push(line); + } + } + } + + for (line, line_num) in coverage_lines.iter().zip(1..) { + if line.starts_with(" ------------------") { + // This is a subview boundary line, so start a new subview. + subviews.push(vec![line]); + } else if line.starts_with(" |") { + // Add this line to the current subview. + subviews + .last_mut() + .ok_or(format!( + "unexpected subview line outside of a subview on line {line_num}" + ))? + .push(line); + } else { + // This line is not part of a subview, so sort and print any + // accumulated subviews, and then print the line as-is. + flush(&mut subviews, &mut output_lines); + output_lines.push(line); + } + } + + flush(&mut subviews, &mut output_lines); + assert!(subviews.is_empty()); + + assert_eq!(output_lines.len(), coverage_lines.len()); + *coverage_lines = output_lines; + + Ok(()) + } + fn run_pretty_test(&self) { if self.props.pp_exact.is_some() { logv(self.config, "testing for exact pretty-printing".to_owned()); @@ -577,6 +885,8 @@ impl<'test> TestCx<'test> { .args(&["--target", &self.config.target]) .arg("-L") .arg(&aux_dir) + .arg("-A") + .arg("internal_features") .args(&self.props.compile_flags) .envs(self.props.rustc_env.clone()); self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags); @@ -645,7 +955,9 @@ impl<'test> TestCx<'test> { .arg("-L") .arg(&self.config.build_base) .arg("-L") - .arg(aux_dir); + .arg(aux_dir) + .arg("-A") + .arg("internal_features"); self.set_revision_flags(&mut rustc); self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags); rustc.args(&self.props.compile_flags); @@ -706,16 +1018,13 @@ impl<'test> TestCx<'test> { }; // Parse debugger commands etc from test files - let DebuggerCommands { commands, check_lines, breakpoint_lines, .. } = - match DebuggerCommands::parse_from( - &self.testpaths.file, - self.config, - prefixes, - self.revision, - ) { - Ok(cmds) => cmds, - Err(e) => self.fatal(&e), - }; + let dbg_cmds = DebuggerCommands::parse_from( + &self.testpaths.file, + self.config, + prefixes, + self.revision, + ) + .unwrap_or_else(|e| self.fatal(&e)); // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-commands let mut script_str = String::with_capacity(2048); @@ -732,12 +1041,12 @@ impl<'test> TestCx<'test> { // Set breakpoints on every line that contains the string "#break" let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy(); - for line in &breakpoint_lines { + for line in &dbg_cmds.breakpoint_lines { script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line)); } // Append the other `cdb-command:`s - for line in &commands { + for line in &dbg_cmds.commands { script_str.push_str(line); script_str.push_str("\n"); } @@ -767,7 +1076,7 @@ impl<'test> TestCx<'test> { self.fatal_proc_rec("Error while running CDB", &debugger_run_result); } - if let Err(e) = check_debugger_output(&debugger_run_result, &check_lines) { + if let Err(e) = dbg_cmds.check_output(&debugger_run_result) { self.fatal_proc_rec(&e, &debugger_run_result); } } @@ -797,17 +1106,14 @@ impl<'test> TestCx<'test> { PREFIXES }; - let DebuggerCommands { commands, check_lines, breakpoint_lines } = - match DebuggerCommands::parse_from( - &self.testpaths.file, - self.config, - prefixes, - self.revision, - ) { - Ok(cmds) => cmds, - Err(e) => self.fatal(&e), - }; - let mut cmds = commands.join("\n"); + let dbg_cmds = DebuggerCommands::parse_from( + &self.testpaths.file, + self.config, + prefixes, + self.revision, + ) + .unwrap_or_else(|e| self.fatal(&e)); + let mut cmds = dbg_cmds.commands.join("\n"); // compile test file (it should have 'compile-flags:-g' in the header) let should_run = self.run_if_enabled(); @@ -841,13 +1147,14 @@ impl<'test> TestCx<'test> { ./{}/stage2/lib/rustlib/{}/lib/\n", self.config.host, self.config.target )); - for line in &breakpoint_lines { + for line in &dbg_cmds.breakpoint_lines { script_str.push_str( - &format!( + format!( "break {:?}:{}\n", self.testpaths.file.file_name().unwrap().to_string_lossy(), *line - )[..], + ) + .as_str(), ); } script_str.push_str(&cmds); @@ -988,7 +1295,7 @@ impl<'test> TestCx<'test> { } // Add line breakpoints - for line in &breakpoint_lines { + for line in &dbg_cmds.breakpoint_lines { script_str.push_str(&format!( "break '{}':{}\n", self.testpaths.file.file_name().unwrap().to_string_lossy(), @@ -1024,7 +1331,7 @@ impl<'test> TestCx<'test> { self.fatal_proc_rec("gdb failed to execute", &debugger_run_result); } - if let Err(e) = check_debugger_output(&debugger_run_result, &check_lines) { + if let Err(e) = dbg_cmds.check_output(&debugger_run_result) { self.fatal_proc_rec(&e, &debugger_run_result); } } @@ -1081,16 +1388,13 @@ impl<'test> TestCx<'test> { }; // Parse debugger commands etc from test files - let DebuggerCommands { commands, check_lines, breakpoint_lines, .. } = - match DebuggerCommands::parse_from( - &self.testpaths.file, - self.config, - prefixes, - self.revision, - ) { - Ok(cmds) => cmds, - Err(e) => self.fatal(&e), - }; + let dbg_cmds = DebuggerCommands::parse_from( + &self.testpaths.file, + self.config, + prefixes, + self.revision, + ) + .unwrap_or_else(|e| self.fatal(&e)); // Write debugger script: // We don't want to hang when calling `quit` while the process is still running @@ -1139,7 +1443,7 @@ impl<'test> TestCx<'test> { // Set breakpoints on every line that contains the string "#break" let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy(); - for line in &breakpoint_lines { + for line in &dbg_cmds.breakpoint_lines { script_str.push_str(&format!( "breakpoint set --file '{}' --line {}\n", source_file_name, line @@ -1147,7 +1451,7 @@ impl<'test> TestCx<'test> { } // Append the other commands - for line in &commands { + for line in &dbg_cmds.commands { script_str.push_str(line); script_str.push_str("\n"); } @@ -1167,7 +1471,7 @@ impl<'test> TestCx<'test> { self.fatal_proc_rec("Error while running LLDB", &debugger_run_result); } - if let Err(e) = check_debugger_output(&debugger_run_result, &check_lines) { + if let Err(e) = dbg_cmds.check_output(&debugger_run_result) { self.fatal_proc_rec(&e, &debugger_run_result); } } @@ -1358,7 +1662,7 @@ impl<'test> TestCx<'test> { if self.props.known_bug { if !expected_errors.is_empty() { self.fatal_proc_rec( - "`known_bug` tests should not have an expected errors", + "`known_bug` tests should not have an expected error", proc_res, ); } @@ -1584,6 +1888,8 @@ impl<'test> TestCx<'test> { .arg("--deny") .arg("warnings") .arg(&self.testpaths.file) + .arg("-A") + .arg("internal_features") .args(&self.props.compile_flags); if self.config.mode == RustdocJson { @@ -1598,7 +1904,26 @@ impl<'test> TestCx<'test> { } fn exec_compiled_test(&self) -> ProcRes { - let env = &self.props.exec_env; + self.exec_compiled_test_general(&[], true) + } + + fn exec_compiled_test_general( + &self, + env_extra: &[(&str, &str)], + delete_after_success: bool, + ) -> ProcRes { + let prepare_env = |cmd: &mut Command| { + for key in &self.props.unset_exec_env { + cmd.env_remove(key); + } + + for (key, val) in &self.props.exec_env { + cmd.env(key, val); + } + for (key, val) in env_extra { + cmd.env(key, val); + } + }; let proc_res = match &*self.config.target { // This is pretty similar to below, we're transforming: @@ -1631,14 +1956,12 @@ impl<'test> TestCx<'test> { let mut test_client = Command::new(self.config.remote_test_client.as_ref().unwrap()); test_client - .args(&["run", &support_libs.len().to_string(), &prog]) + .args(&["run", &support_libs.len().to_string()]) + .arg(&prog) .args(support_libs) .args(args); - for key in &self.props.unset_exec_env { - test_client.env_remove(key); - } - test_client.envs(env.clone()); + prepare_env(&mut test_client); self.compose_and_run( test_client, @@ -1653,10 +1976,7 @@ impl<'test> TestCx<'test> { let mut wr_run = Command::new("wr-run"); wr_run.args(&[&prog]).args(args); - for key in &self.props.unset_exec_env { - wr_run.env_remove(key); - } - wr_run.envs(env.clone()); + prepare_env(&mut wr_run); self.compose_and_run( wr_run, @@ -1671,10 +1991,7 @@ impl<'test> TestCx<'test> { let mut program = Command::new(&prog); program.args(args).current_dir(&self.output_base_dir()); - for key in &self.props.unset_exec_env { - program.env_remove(key); - } - program.envs(env.clone()); + prepare_env(&mut program); self.compose_and_run( program, @@ -1685,7 +2002,7 @@ impl<'test> TestCx<'test> { } }; - if proc_res.status.success() { + if delete_after_success && proc_res.status.success() { // delete the executable after running it to save space. // it is ok if the deletion failed. let _ = fs::remove_file(self.make_exe_name()); @@ -1810,8 +2127,9 @@ impl<'test> TestCx<'test> { || self.config.target.contains("wasm32") || self.config.target.contains("nvptx") || self.is_vxworks_pure_static() - || self.config.target.contains("sgx") || self.config.target.contains("bpf") + || !self.config.target_cfg().dynamic_linking + || self.config.mode == RunCoverage { // We primarily compile all auxiliary libraries as dynamic libraries // to avoid code size bloat and large binaries as much as possible @@ -1822,6 +2140,10 @@ impl<'test> TestCx<'test> { // dynamic libraries so we just go back to building a normal library. Note, // however, that for MUSL if the library is built with `force_host` then // it's ok to be a dylib as the host should always support dylibs. + // + // Coverage tests want static linking by default so that coverage + // mappings in auxiliary libraries can be merged into the final + // executable. (false, Some("lib")) } else { (true, Some("dylib")) @@ -1939,8 +2261,21 @@ impl<'test> TestCx<'test> { // Use a single thread for efficiency and a deterministic error message order rustc.arg("-Zthreads=1"); + // Hide libstd sources from ui tests to make sure we generate the stderr + // output that users will see. + // Without this, we may be producing good diagnostics in-tree but users + // will not see half the information. + // + // This also has the benefit of more effectively normalizing output between different + // compilers, so that we don't have to know the `/rustc/$sha` output to normalize after the + // fact. + rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX"); + rustc.arg("-Ztranslate-remapped-path-to-local-path=no"); + // Optionally prevent default --sysroot if specified in test compile-flags. - if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot")) { + if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot")) + && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot") + { // In stage 0, make sure we use `stage0-sysroot` instead of the bootstrap sysroot. rustc.arg("--sysroot").arg(&self.config.sysroot_base); } @@ -1986,6 +2321,10 @@ impl<'test> TestCx<'test> { } } DebugInfo => { /* debuginfo tests must be unoptimized */ } + RunCoverage => { + // Coverage reports are affected by optimization level, and + // the current snapshots assume no optimization by default. + } _ => { rustc.arg("-O"); } @@ -2014,14 +2353,15 @@ impl<'test> TestCx<'test> { rustc.arg("-Ccodegen-units=1"); // Hide line numbers to reduce churn rustc.arg("-Zui-testing"); - // Hide libstd sources from ui tests to make sure we generate the stderr - // output that users will see. - // Without this, we may be producing good diagnostics in-tree but users - // will not see half the information. - rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX"); - rustc.arg("-Ztranslate-remapped-path-to-local-path=no"); - rustc.arg("-Zdeduplicate-diagnostics=no"); + // #[cfg(not(bootstrap)] unconditionally pass flag after beta bump + // since `ui-fulldeps --stage=1` builds using the stage 0 compiler, + // which doesn't have this flag. + if !(self.config.stage_id.starts_with("stage1-") + && self.config.suite == "ui-fulldeps") + { + rustc.arg("-Zwrite-long-types-to-disk=no"); + } // FIXME: use this for other modes too, for perf? rustc.arg("-Cstrip=debuginfo"); } @@ -2040,12 +2380,14 @@ impl<'test> TestCx<'test> { &zdump_arg, "-Zvalidate-mir", "-Zdump-mir-exclude-pass-number", - "-Zmir-pretty-relative-line-numbers=yes", ]); if let Some(pass) = &self.props.mir_unit_test { rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]); } else { - rustc.arg("-Zmir-opt-level=4"); + rustc.args(&[ + "-Zmir-opt-level=4", + "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals", + ]); } let mir_dump_dir = self.get_mir_dump_dir(); @@ -2057,6 +2399,9 @@ impl<'test> TestCx<'test> { rustc.arg(dir_opt); } + RunCoverage => { + rustc.arg("-Cinstrument-coverage"); + } RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RustdocJson | RunMake | CodegenUnits | JsDocTest | Assembly => { // do not use JSON output @@ -2114,12 +2459,12 @@ impl<'test> TestCx<'test> { Some(CompareMode::Polonius) => { rustc.args(&["-Zpolonius"]); } - Some(CompareMode::Chalk) => { - rustc.args(&["-Ztrait-solver=chalk"]); - } Some(CompareMode::NextSolver) => { rustc.args(&["-Ztrait-solver=next"]); } + Some(CompareMode::NextSolverCoherence) => { + rustc.args(&["-Ztrait-solver=next-coherence"]); + } Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => { rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]); } @@ -2138,6 +2483,14 @@ impl<'test> TestCx<'test> { rustc.args(&["-A", "unused"]); } + // #[cfg(not(bootstrap)] unconditionally pass flag after beta bump + // since `ui-fulldeps --stage=1` builds using the stage 0 compiler, + // which doesn't have this lint. + if !(self.config.stage_id.starts_with("stage1-") && self.config.suite == "ui-fulldeps") { + // Allow tests to use internal features. + rustc.args(&["-A", "internal_features"]); + } + if self.props.force_host { self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags); if !is_rustdoc { @@ -2195,7 +2548,7 @@ impl<'test> TestCx<'test> { // If this is emscripten, then run tests under nodejs if self.config.target.contains("emscripten") { if let Some(ref p) = self.config.nodejs { - args.push(p.clone()); + args.push(p.into()); } else { self.fatal("emscripten target requested and no NodeJS binary found (--nodejs)"); } @@ -2203,7 +2556,7 @@ impl<'test> TestCx<'test> { // shim } else if self.config.target.contains("wasm32") { if let Some(ref p) = self.config.nodejs { - args.push(p.clone()); + args.push(p.into()); } else { self.fatal("wasm32 target requested and no NodeJS binary found (--nodejs)"); } @@ -2215,13 +2568,12 @@ impl<'test> TestCx<'test> { .unwrap() // chop off `ui` .parent() .unwrap(); // chop off `tests` - args.push(src.join("src/etc/wasm32-shim.js").display().to_string()); + args.push(src.join("src/etc/wasm32-shim.js").into_os_string()); } let exe_file = self.make_exe_name(); - // FIXME (#9639): This needs to handle non-utf8 paths - args.push(exe_file.to_str().unwrap().to_owned()); + args.push(exe_file.into_os_string()); // Add the arguments in the run_flags directive args.extend(self.split_maybe_args(&self.props.run_flags)); @@ -2230,12 +2582,16 @@ impl<'test> TestCx<'test> { ProcArgs { prog, args } } - fn split_maybe_args(&self, argstr: &Option) -> Vec { + fn split_maybe_args(&self, argstr: &Option) -> Vec { match *argstr { Some(ref s) => s .split(' ') .filter_map(|s| { - if s.chars().all(|c| c.is_whitespace()) { None } else { Some(s.to_owned()) } + if s.chars().all(|c| c.is_whitespace()) { + None + } else { + Some(OsString::from(s)) + } }) .collect(), None => Vec::new(), @@ -2439,6 +2795,10 @@ impl<'test> TestCx<'test> { self.fatal_proc_rec("compilation failed!", &proc_res); } + if let Some(PassMode::Build) = self.pass_mode() { + return; + } + let output_path = self.output_base_name().with_extension("ll"); let proc_res = self.verify_with_filecheck(&output_path); if !proc_res.status.success() { @@ -3555,6 +3915,7 @@ impl<'test> TestCx<'test> { let files = miropt_test_tools::files_for_miropt_test( &self.testpaths.file, self.config.get_pointer_width(), + self.config.target_cfg().panic.for_miropt_test_tools(), ); let mut out = Vec::new(); @@ -3572,25 +3933,24 @@ impl<'test> TestCx<'test> { } fn check_mir_dump(&self) { - let test_file_contents = fs::read_to_string(&self.testpaths.file).unwrap(); - let test_dir = self.testpaths.file.parent().unwrap(); let test_crate = self.testpaths.file.file_stem().unwrap().to_str().unwrap().replace("-", "_"); - let mut bit_width = String::new(); - if test_file_contents.lines().any(|l| l == "// EMIT_MIR_FOR_EACH_BIT_WIDTH") { - bit_width = format!(".{}bit", self.config.get_pointer_width()); - } + let suffix = miropt_test_tools::output_file_suffix( + &self.testpaths.file, + self.config.get_pointer_width(), + self.config.target_cfg().panic.for_miropt_test_tools(), + ); if self.config.bless { for e in - glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, bit_width)).unwrap() + glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, suffix)).unwrap() { std::fs::remove_file(e.unwrap()).unwrap(); } for e in - glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, bit_width)).unwrap() + glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, suffix)).unwrap() { std::fs::remove_file(e.unwrap()).unwrap(); } @@ -3599,6 +3959,7 @@ impl<'test> TestCx<'test> { let files = miropt_test_tools::files_for_miropt_test( &self.testpaths.file, self.config.get_pointer_width(), + self.config.target_cfg().panic.for_miropt_test_tools(), ); for miropt_test_tools::MiroptTestFiles { from_file, to_file, expected_file, passes: _ } in files @@ -3700,8 +4061,11 @@ impl<'test> TestCx<'test> { } fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String { + let rflags = self.props.run_flags.as_ref(); let cflags = self.props.compile_flags.join(" "); - let json = cflags.contains("--error-format json") + let json = rflags + .map_or(false, |s| s.contains("--format json") || s.contains("--format=json")) + || cflags.contains("--error-format json") || cflags.contains("--error-format pretty-json") || cflags.contains("--error-format=json") || cflags.contains("--error-format=pretty-json") @@ -3729,28 +4093,13 @@ impl<'test> TestCx<'test> { normalize_path(&remapped_parent_dir, "$DIR"); } - let source_bases = &[ - // Source base on the current filesystem (calculated as parent of `tests/$suite`): - Some(self.config.src_base.parent().unwrap().parent().unwrap().into()), - // Source base on the sysroot (from the src components downloaded by `download-rustc`): - Some(self.config.sysroot_base.join("lib").join("rustlib").join("src").join("rust")), - // Virtual `/rustc/$sha` remapped paths (if `remap-debuginfo` is enabled): - option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR").map(PathBuf::from), - // Virtual `/rustc/$sha` coming from download-rustc: - std::env::var_os("FAKE_DOWNLOAD_RUSTC_PREFIX").map(PathBuf::from), - // Tests using -Zsimulate-remapped-rust-src-base should use this fake path - Some("/rustc/FAKE_PREFIX".into()), - ]; - for base_dir in source_bases { - if let Some(base_dir) = base_dir { - // Paths into the libstd/libcore - normalize_path(&base_dir.join("library"), "$SRC_DIR"); - // `ui-fulldeps` tests can show paths to the compiler source when testing macros from - // `rustc_macros` - // eg. /home/user/rust/compiler - normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR"); - } - } + let base_dir = Path::new("/rustc/FAKE_PREFIX"); + // Paths into the libstd/libcore + normalize_path(&base_dir.join("library"), "$SRC_DIR"); + // `ui-fulldeps` tests can show paths to the compiler source when testing macros from + // `rustc_macros` + // eg. /home/user/rust/compiler + normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR"); // Paths into the build directory let test_build_dir = &self.config.build_base; @@ -3843,8 +4192,8 @@ impl<'test> TestCx<'test> { # Match paths that don't include spaces. (?:\\[\pL\pN\.\-_']+)+\.\pL+ | - # If the path starts with a well-known root, then allow spaces. - \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_' ]+)+ + # If the path starts with a well-known root, then allow spaces and no file extension. + \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+ )"#, ) .unwrap() @@ -4049,8 +4398,8 @@ impl<'test> TestCx<'test> { } struct ProcArgs { - prog: String, - args: Vec, + prog: OsString, + args: Vec, } pub struct ProcRes { @@ -4064,7 +4413,7 @@ impl ProcRes { pub fn print_info(&self) { fn render(name: &str, contents: &str) -> String { let contents = json::extract_rendered(contents); - let contents = contents.trim(); + let contents = contents.trim_end(); if contents.is_empty() { format!("{name}: none") } else { diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs index 379ff0bab408a..eebe5f3580b30 100644 --- a/src/tools/compiletest/src/runtest/debugger.rs +++ b/src/tools/compiletest/src/runtest/debugger.rs @@ -2,18 +2,25 @@ use crate::common::Config; use crate::header::line_directive; use crate::runtest::ProcRes; +use std::fmt::Write; use std::fs::File; use std::io::{BufRead, BufReader}; -use std::path::Path; +use std::path::{Path, PathBuf}; +/// Representation of information to invoke a debugger and check its output pub(super) struct DebuggerCommands { + /// Commands for the debuuger pub commands: Vec, - pub check_lines: Vec, + /// Lines to insert breakpoints at pub breakpoint_lines: Vec, + /// Contains the source line number to check and the line itself + check_lines: Vec<(usize, String)>, + /// Source file name + file: PathBuf, } impl DebuggerCommands { - pub(super) fn parse_from( + pub fn parse_from( file: &Path, config: &Config, debugger_prefixes: &[&str], @@ -21,7 +28,7 @@ impl DebuggerCommands { ) -> Result { let directives = debugger_prefixes .iter() - .map(|prefix| (format!("{}-command", prefix), format!("{}-check", prefix))) + .map(|prefix| (format!("{prefix}-command"), format!("{prefix}-check"))) .collect::>(); let mut breakpoint_lines = vec![]; @@ -29,63 +36,88 @@ impl DebuggerCommands { let mut check_lines = vec![]; let mut counter = 0; let reader = BufReader::new(File::open(file).unwrap()); - for line in reader.lines() { + for (line_no, line) in reader.lines().enumerate() { counter += 1; - match line { - Ok(line) => { - let (lnrev, line) = line_directive("//", &line).unwrap_or((None, &line)); - - // Skip any revision specific directive that doesn't match the current - // revision being tested - if lnrev.is_some() && lnrev != rev { - continue; - } - - if line.contains("#break") { - breakpoint_lines.push(counter); - } - - for &(ref command_directive, ref check_directive) in &directives { - config - .parse_name_value_directive(&line, command_directive) - .map(|cmd| commands.push(cmd)); - - config - .parse_name_value_directive(&line, check_directive) - .map(|cmd| check_lines.push(cmd)); - } - } - Err(e) => return Err(format!("Error while parsing debugger commands: {}", e)), + let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?; + let (lnrev, line) = line_directive("//", &line).unwrap_or((None, &line)); + + // Skip any revision specific directive that doesn't match the current + // revision being tested + if lnrev.is_some() && lnrev != rev { + continue; + } + + if line.contains("#break") { + breakpoint_lines.push(counter); + } + + for &(ref command_directive, ref check_directive) in &directives { + config + .parse_name_value_directive(&line, command_directive) + .map(|cmd| commands.push(cmd)); + + config + .parse_name_value_directive(&line, check_directive) + .map(|cmd| check_lines.push((line_no, cmd))); } } - Ok(Self { commands, check_lines, breakpoint_lines }) + Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_owned() }) } -} -pub(super) fn check_debugger_output( - debugger_run_result: &ProcRes, - check_lines: &[String], -) -> Result<(), String> { - let num_check_lines = check_lines.len(); - - let mut check_line_index = 0; - for line in debugger_run_result.stdout.lines() { - if check_line_index >= num_check_lines { - break; + /// Given debugger output and lines to check, ensure that every line is + /// contained in the debugger output. The check lines need to be found in + /// order, but there can be extra lines between. + pub fn check_output(&self, debugger_run_result: &ProcRes) -> Result<(), String> { + // (src_lineno, ck_line) that we did find + let mut found = vec![]; + // (src_lineno, ck_line) that we couldn't find + let mut missing = vec![]; + // We can find our any current match anywhere after our last match + let mut last_idx = 0; + let dbg_lines: Vec<&str> = debugger_run_result.stdout.lines().collect(); + + for (src_lineno, ck_line) in &self.check_lines { + if let Some(offset) = dbg_lines + .iter() + .skip(last_idx) + .position(|out_line| check_single_line(out_line, &ck_line)) + { + last_idx += offset; + found.push((src_lineno, dbg_lines[last_idx])); + } else { + missing.push((src_lineno, ck_line)); + } } - if check_single_line(line, &(check_lines[check_line_index])[..]) { - check_line_index += 1; + if missing.is_empty() { + Ok(()) + } else { + let fname = self.file.file_name().unwrap().to_string_lossy(); + let mut msg = format!( + "check directive(s) from `{}` not found in debugger output. errors:", + self.file.display() + ); + + for (src_lineno, err_line) in missing { + write!(msg, "\n ({fname}:{num}) `{err_line}`", num = src_lineno + 1).unwrap(); + } + + if !found.is_empty() { + let init = "\nthe following subset of check directive(s) was found successfully:"; + msg.push_str(init); + for (src_lineno, found_line) in found { + write!(msg, "\n ({fname}:{num}) `{found_line}`", num = src_lineno + 1) + .unwrap(); + } + } + + Err(msg) } } - if check_line_index != num_check_lines && num_check_lines > 0 { - Err(format!("line not found in debugger output: {}", check_lines[check_line_index])) - } else { - Ok(()) - } } +/// Check that the pattern in `check_line` applies to `line`. Returns `true` if they do match. fn check_single_line(line: &str, check_line: &str) -> bool { // Allow check lines to leave parts unspecified (e.g., uninitialized // bits in the wrong case of an enum) with the notation "[...]". @@ -101,21 +133,19 @@ fn check_single_line(line: &str, check_line: &str) -> bool { } let (mut rest, first_fragment) = if can_start_anywhere { - match line.find(check_fragments[0]) { - Some(pos) => (&line[pos + check_fragments[0].len()..], 1), - None => return false, - } + let Some(pos) = line.find(check_fragments[0]) else { + return false; + }; + (&line[pos + check_fragments[0].len()..], 1) } else { (line, 0) }; for current_fragment in &check_fragments[first_fragment..] { - match rest.find(current_fragment) { - Some(pos) => { - rest = &rest[pos + current_fragment.len()..]; - } - None => return false, - } + let Some(pos) = rest.find(current_fragment) else { + return false; + }; + rest = &rest[pos + current_fragment.len()..]; } if !can_end_anywhere && !rest.is_empty() { false } else { true } diff --git a/src/tools/compiletest/src/runtest/tests.rs b/src/tools/compiletest/src/runtest/tests.rs index 51105111175a9..fb3dd326a4c80 100644 --- a/src/tools/compiletest/src/runtest/tests.rs +++ b/src/tools/compiletest/src/runtest/tests.rs @@ -8,8 +8,8 @@ fn normalize_platform_differences() { "$BUILD_DIR/../parser.rs" ); assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\bar.rs hello\nworld"), - r"$DIR/bar.rs hello\nworld" + TestCx::normalize_platform_differences(r"$DIR\bar.rs: hello\nworld"), + r"$DIR/bar.rs: hello\nworld" ); assert_eq!( TestCx::normalize_platform_differences(r"either bar\baz.rs or bar\baz\mod.rs"), @@ -27,8 +27,8 @@ fn normalize_platform_differences() { ); assert_eq!(TestCx::normalize_platform_differences(r"$DIR\foo.rs:12:11"), "$DIR/foo.rs:12:11",); assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\path with spaces 'n' quotes"), - "$DIR/path with spaces 'n' quotes", + TestCx::normalize_platform_differences(r"$DIR\path with\spaces 'n' quotes"), + "$DIR/path with/spaces 'n' quotes", ); assert_eq!( TestCx::normalize_platform_differences(r"$DIR\file_with\no_extension"), diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index 748240cc94bc6..17bed38b65e88 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -104,6 +104,8 @@ pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[ "x86_64-unknown-openbsd", ]; +pub const SAFESTACK_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"]; + pub fn make_new_path(path: &str) -> String { assert!(cfg!(windows)); // Windows just uses PATH as the library search path, so we have to diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs index f984275b164cc..62a58576da555 100644 --- a/src/tools/error_index_generator/main.rs +++ b/src/tools/error_index_generator/main.rs @@ -1,6 +1,7 @@ #![feature(rustc_private)] extern crate rustc_driver; +extern crate rustc_session; use std::env; use std::error::Error; @@ -170,7 +171,9 @@ fn parse_args() -> (OutputFormat, PathBuf) { } fn main() { - rustc_driver::init_env_logger("RUST_LOG"); + let handler = + rustc_session::EarlyErrorHandler::new(rustc_session::config::ErrorOutputType::default()); + rustc_driver::init_env_logger(&handler, "RUST_LOG"); let (format, dst) = parse_args(); let result = main_with_result(format, &dst); if let Err(e) = result { diff --git a/src/tools/jsondocck/src/cache.rs b/src/tools/jsondocck/src/cache.rs index f9e54232750b6..50697d46b8c70 100644 --- a/src/tools/jsondocck/src/cache.rs +++ b/src/tools/jsondocck/src/cache.rs @@ -15,8 +15,10 @@ impl Cache { /// Create a new cache, used to read files only once and otherwise store their contents. pub fn new(config: &Config) -> Cache { let root = Path::new(&config.doc_dir); - let filename = Path::new(&config.template).file_stem().unwrap(); - let file_path = root.join(&Path::with_extension(Path::new(filename), "json")); + // `filename` needs to replace `-` with `_` to be sure the JSON path will always be valid. + let filename = + Path::new(&config.template).file_stem().unwrap().to_str().unwrap().replace('-', "_"); + let file_path = root.join(&Path::with_extension(Path::new(&filename), "json")); let content = fs::read_to_string(&file_path).expect("failed to read JSON file"); Cache { diff --git a/src/tools/jsondoclint/src/main.rs b/src/tools/jsondoclint/src/main.rs index ee163ddfdd9a8..aaaba78cb46d1 100644 --- a/src/tools/jsondoclint/src/main.rs +++ b/src/tools/jsondoclint/src/main.rs @@ -1,4 +1,5 @@ use std::io::{BufWriter, Write}; +use std::path::{Path, PathBuf}; use anyhow::{bail, Result}; use clap::Parser; @@ -25,7 +26,7 @@ enum ErrorKind { #[derive(Debug, Serialize)] struct JsonOutput { - path: String, + path: PathBuf, errors: Vec, } @@ -45,6 +46,12 @@ struct Cli { fn main() -> Result<()> { let Cli { path, verbose, json_output } = Cli::parse(); + // We convert `-` into `_` for the file name to be sure the JSON path will always be correct. + let path = Path::new(&path); + let filename = path.file_name().unwrap().to_str().unwrap().replace('-', "_"); + let parent = path.parent().unwrap(); + let path = parent.join(&filename); + let contents = fs::read_to_string(&path)?; let krate: Crate = serde_json::from_str(&contents)?; assert_eq!(krate.format_version, FORMAT_VERSION); @@ -101,7 +108,7 @@ fn main() -> Result<()> { ErrorKind::Custom(msg) => eprintln!("{}: {}", err.id.0, msg), } } - bail!("Errors validating json {path}"); + bail!("Errors validating json {}", path.display()); } Ok(()) diff --git a/src/tools/linkchecker/main.rs b/src/tools/linkchecker/main.rs index c8a370085a045..7f73cac63cbd2 100644 --- a/src/tools/linkchecker/main.rs +++ b/src/tools/linkchecker/main.rs @@ -368,7 +368,6 @@ impl Checker { return; } // Search for intra-doc links that rustdoc didn't warn about - // FIXME(#77199, 77200) Rustdoc should just warn about these directly. // NOTE: only looks at one line at a time; in practice this should find most links for (i, line) in source.lines().enumerate() { for broken_link in BROKEN_INTRA_DOC_LINK.captures_iter(line) { diff --git a/src/tools/lint-docs/src/groups.rs b/src/tools/lint-docs/src/groups.rs index b11fb287cf4dd..5be8ef7996bb2 100644 --- a/src/tools/lint-docs/src/groups.rs +++ b/src/tools/lint-docs/src/groups.rs @@ -39,7 +39,6 @@ impl<'a> LintExtractor<'a> { fn collect_groups(&self) -> Result> { let mut result = BTreeMap::new(); let mut cmd = Command::new(self.rustc_path); - cmd.env_remove("LD_LIBRARY_PATH"); cmd.arg("-Whelp"); let output = cmd.output().map_err(|e| format!("failed to run command {:?}\n{}", cmd, e))?; if !output.status.success() { diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index fe29b9abda39a..b7c8b9ed2e318 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -403,12 +403,6 @@ impl<'a> LintExtractor<'a> { fs::write(&tempfile, source) .map_err(|e| format!("failed to write {}: {}", tempfile.display(), e))?; let mut cmd = Command::new(self.rustc_path); - // NOTE: bootstrap sets `LD_LIBRARY_PATH` for building lint-docs itself. - // Unfortunately, lint-docs is a bootstrap tool while rustc is built from source, - // and sometimes the paths conflict. In particular, when using `download-rustc`, - // the LLVM versions can differ between `ci-llvm` and `ci-rustc-sysroot`. - // Unset LD_LIBRARY_PATH here so it doesn't interfere with running the compiler. - cmd.env_remove("LD_LIBRARY_PATH"); if options.contains(&"edition2015") { cmd.arg("--edition=2015"); } else { diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index da1c2f770ac4f..8ced9fa86bee4 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -10,17 +10,14 @@ on: branches: - 'master' schedule: - - cron: '6 6 * * *' # At 6:06 UTC every day. + - cron: '11 5 * * *' # At 5:11 UTC every day. -env: - CARGO_UNSTABLE_SPARSE_REGISTRY: 'true' +defaults: + run: + shell: bash jobs: build: - runs-on: ${{ matrix.os }} - env: - RUST_BACKTRACE: 1 - HOST_TARGET: ${{ matrix.host_target }} strategy: fail-fast: false matrix: @@ -31,6 +28,10 @@ jobs: host_target: x86_64-apple-darwin - os: windows-latest host_target: i686-pc-windows-msvc + runs-on: ${{ matrix.os }} + env: + RUST_BACKTRACE: 1 + HOST_TARGET: ${{ matrix.host_target }} steps: - uses: actions/checkout@v3 @@ -59,12 +60,9 @@ jobs: - name: Install rustup-toolchain-install-master if: ${{ steps.cache.outputs.cache-hit != 'true' }} - shell: bash - run: | - cargo install -f rustup-toolchain-install-master + run: cargo install -f rustup-toolchain-install-master - name: Install "master" toolchain - shell: bash run: | if [[ ${{ github.event_name }} == 'schedule' ]]; then echo "Building against latest rustc git version" @@ -79,7 +77,7 @@ jobs: cargo -V - name: Test - run: bash ./ci.sh + run: ./ci.sh style: name: style checks @@ -111,13 +109,14 @@ jobs: - name: Install rustup-toolchain-install-master if: ${{ steps.cache.outputs.cache-hit != 'true' }} - shell: bash - run: | - cargo install -f rustup-toolchain-install-master + run: cargo install -f rustup-toolchain-install-master - name: Install "master" toolchain - shell: bash run: | + if [[ ${{ github.event_name }} == 'schedule' ]]; then + echo "Building against latest rustc git version" + git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1 > rust-version + fi ./miri toolchain - name: Show Rust version @@ -131,14 +130,13 @@ jobs: - name: clippy run: ./miri clippy -- -D warnings - name: rustdoc - run: RUSTDOCFLAGS="-Dwarnings" cargo doc --document-private-items + run: RUSTDOCFLAGS="-Dwarnings" ./miri cargo doc --document-private-items # These jobs doesn't actually test anything, but they're only used to tell # bors the build completed, as there is no practical way to detect when a # workflow is successful listening to webhooks only. # # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - # (`fmt` is deliberately not listed, we want bors to ignore it.) end-success: name: bors build finished runs-on: ubuntu-latest @@ -156,22 +154,22 @@ jobs: - name: mark the job as a failure run: exit 1 - # Send a Zulip notification when a cron job fails cron-fail-notify: name: cronjob failure notification runs-on: ubuntu-latest needs: [build, style] if: github.event_name == 'schedule' && (failure() || cancelled()) steps: + # Send a Zulip notification - name: Install zulip-send run: pip3 install zulip - name: Send Zulip notification - shell: bash env: ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }} ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }} run: | - ~/.local/bin/zulip-send --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \ + ~/.local/bin/zulip-send --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com \ + --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \ --message 'Dear @*T-miri*, It would appear that the [Miri cron job build]('"https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"') failed. @@ -183,5 +181,40 @@ jobs: Thanks in advance! Sincerely, - The Miri Cronjobs Bot' \ - --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com + The Miri Cronjobs Bot' + + # Attempt to auto-sync with rustc + - uses: actions/checkout@v3 + with: + fetch-depth: 256 # get a bit more of the history + - name: install josh-proxy + run: cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r22.12.06 + - name: start josh-proxy + run: josh-proxy --local=$HOME/.cache/josh --remote=https://github.com --no-background & + - name: setup bot git name and email + run: | + git config --global user.name 'The Miri Conjob Bot' + git config --global user.email 'miri@cron.bot' + - name: get changes from rustc + run: ./miri rustc-pull + - name: Install rustup-toolchain-install-master + run: cargo install -f rustup-toolchain-install-master + - name: format changes (if any) + run: | + ./miri toolchain + ./miri fmt --check || (./miri fmt && git commit -am "fmt") + - name: Push changes to a branch + run: | + BRANCH="rustup-$(date -u +%Y-%m-%d)" + git switch -c $BRANCH + git push -u origin $BRANCH + - name: Create Pull Request + run: | + PR=$(gh pr create -B master --title 'Automatic sync from rustc' --body '') + ~/.local/bin/zulip-send --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com \ + --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \ + --message "A PR doing a rustc-pull [has been automatically created]($PR) for your convenience." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }} + ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }} diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md index bcdb623b090e4..b67e7103fd095 100644 --- a/src/tools/miri/CONTRIBUTING.md +++ b/src/tools/miri/CONTRIBUTING.md @@ -107,7 +107,7 @@ evaluation error was originally raised. ### UI testing We use ui-testing in Miri, meaning we generate `.stderr` and `.stdout` files for the output -produced by Miri. You can use `./miri bless` to automatically (re)generate these files when +produced by Miri. You can use `./miri test --bless` to automatically (re)generate these files when you add new tests or change how Miri presents certain output. Note that when you also use `MIRIFLAGS` to change optimizations and similar, the ui output diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock index 7bedb5d48f64e..4232d7fda78f1 100644 --- a/src/tools/miri/Cargo.lock +++ b/src/tools/miri/Cargo.lock @@ -442,7 +442,6 @@ dependencies = [ "measureme", "rand", "regex", - "rustc-workspace-hack", "rustc_version", "smallvec", "ui_test", @@ -529,9 +528,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -628,12 +627,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-workspace-hack" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" - [[package]] name = "rustc_version" version = "0.4.0" @@ -849,9 +842,9 @@ dependencies = [ [[package]] name = "ui_test" -version = "0.10.0" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191a442639ea102fa62671026047e51d574bfda44b7fdf32151d7314624c1cd2" +checksum = "c21899b59f53717dfad29e4f46e5b21a200a1b6888ab86532a07cfc8b48dd78c" dependencies = [ "bstr", "cargo-platform", diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml index 683f99ca4de44..a625e1696e143 100644 --- a/src/tools/miri/Cargo.toml +++ b/src/tools/miri/Cargo.toml @@ -24,10 +24,6 @@ log = "0.4" rand = "0.8" smallvec = "1.7" -# A noop dependency that changes in the Rust repository, it's a bit of a hack. -# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` -# for more information. -rustc-workspace-hack = "1.0.0" measureme = "10.0.0" ctrlc = "3.2.5" @@ -40,7 +36,7 @@ libloading = "0.7" [dev-dependencies] colored = "2" -ui_test = "0.10" +ui_test = "0.11.7" rustc_version = "0.4" # Features chosen to match those required by env_logger, to avoid rebuilds regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] } diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 79b0daf9e82b8..4483ae242d5e9 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -53,7 +53,7 @@ behavior** in your program, and cannot run all programs: positives here, so if your program runs fine in Miri right now that is by no means a guarantee that it is UB-free when these questions get answered. - In particular, Miri does currently not check that references point to valid data. + In particular, Miri does not check that references point to valid data. * If the program relies on unspecified details of how data is laid out, it will still run fine in Miri -- but might break (including causing UB) on different compiler versions or different platforms. @@ -74,12 +74,19 @@ behavior** in your program, and cannot run all programs: unobservable by compiled programs running on real hardware when `SeqCst` fences are used, and it cannot produce all behaviors possibly observable on real hardware. +Moreover, Miri fundamentally cannot tell you whether your code is *sound*. [Soundness] is the property +of never causing undefined behavior when invoked from arbitrary safe code, even in combination with +other sound code. In contrast, Miri can just tell you if *a particular way of interacting with your +code* (e.g., a test suite) causes any undefined behavior. It is up to you to ensure sufficient +coverage. + [rust]: https://www.rust-lang.org/ [mir]: https://github.com/rust-lang/rfcs/blob/master/text/1211-mir.md [`unreachable_unchecked`]: https://doc.rust-lang.org/stable/std/hint/fn.unreachable_unchecked.html [`copy_nonoverlapping`]: https://doc.rust-lang.org/stable/std/ptr/fn.copy_nonoverlapping.html [Stacked Borrows]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md [Tree Borrows]: https://perso.crans.org/vanille/treebor/ +[Soundness]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#soundness-of-code--of-a-library ## Using Miri @@ -400,15 +407,11 @@ to Miri failing to detect cases of undefined behavior in a program. application instead of raising an error within the context of Miri (and halting execution). Note that code might not expect these operations to ever panic, so this flag can lead to strange (mis)behavior. -* `-Zmiri-retag-fields` changes Stacked Borrows retagging to recurse into *all* fields. - This means that references in fields of structs/enums/tuples/arrays/... are retagged, - and in particular, they are protected when passed as function arguments. - (The default is to recurse only in cases where rustc would actually emit a `noalias` attribute.) -* `-Zmiri-retag-fields=` controls when Stacked Borrows retagging recurses into - fields. `all` means it always recurses (like `-Zmiri-retag-fields`), `none` means it never - recurses, `scalar` (the default) means it only recurses for types where we would also emit - `noalias` annotations in the generated LLVM IR (types passed as individual scalars or pairs of - scalars). Setting this to `none` is **unsound**. +* `-Zmiri-retag-fields[=]` controls when Stacked Borrows retagging recurses into + fields. `all` means it always recurses (the default, and equivalent to `-Zmiri-retag-fields` + without an explicit value), `none` means it never recurses, `scalar` means it only recurses for + types where we would also emit `noalias` annotations in the generated LLVM IR (types passed as + individual scalars or pairs of scalars). Setting this to `none` is **unsound**. * `-Zmiri-tag-gc=` configures how often the pointer tag garbage collector runs. The default is to search for and remove unreachable tags once every `10000` basic blocks. Setting this to `0` disables the garbage collector, which causes some programs to have explosive memory usage @@ -435,6 +438,9 @@ to Miri failing to detect cases of undefined behavior in a program. so with this flag. * `-Zmiri-force-page-size=` overrides the default page size for an architecture, in multiples of 1k. `4` is default for most targets. This value should always be a power of 2 and nonzero. +* `-Zmiri-unique-is-unique` performs additional aliasing checks for `core::ptr::Unique` to ensure + that it could theoretically be considered `noalias`. This flag is experimental and has + an effect only when used with `-Zmiri-tree-borrows`. [function ABI]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier @@ -476,7 +482,7 @@ Moreover, Miri recognizes some environment variables: purpose. * `MIRI_NO_STD` (recognized by `cargo miri` and the test suite) makes sure that the target's sysroot is built without libstd. This allows testing and running no_std programs. -* `MIRI_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all +* `RUSTC_BLESS` (recognized by the test suite and `cargo-miri-test/run-test.py`): overwrite all `stderr` and `stdout` files instead of checking whether the output matches. * `MIRI_SKIP_UI_CHECKS` (recognized by the test suite): don't check whether the `stderr` or `stdout` files match the actual output. @@ -575,6 +581,7 @@ Definite bugs found: * [`crossbeam-epoch` calling `assume_init` on a partly-initialized `MaybeUninit`](https://github.com/crossbeam-rs/crossbeam/pull/779) * [`integer-encoding` dereferencing a misaligned pointer](https://github.com/dermesser/integer-encoding-rs/pull/23) * [`rkyv` constructing a `Box<[u8]>` from an overaligned allocation](https://github.com/rkyv/rkyv/commit/a9417193a34757e12e24263178be8b2eebb72456) +* [Data race in `arc-swap`](https://github.com/vorner/arc-swap/issues/76) * [Data race in `thread::scope`](https://github.com/rust-lang/rust/issues/98498) * [`regex` incorrectly handling unaligned `Vec` buffers](https://www.reddit.com/r/rust/comments/vq3mmu/comment/ienc7t0?context=3) * [Incorrect use of `compare_exchange_weak` in `once_cell`](https://github.com/matklad/once_cell/issues/186) diff --git a/src/tools/miri/cargo-miri/Cargo.lock b/src/tools/miri/cargo-miri/Cargo.lock index 727e46a028db0..47fdc65fa9618 100644 --- a/src/tools/miri/cargo-miri/Cargo.lock +++ b/src/tools/miri/cargo-miri/Cargo.lock @@ -30,7 +30,6 @@ dependencies = [ "cargo_metadata", "directories", "rustc-build-sysroot", - "rustc-workspace-hack", "rustc_tools_util", "rustc_version", "serde", @@ -179,9 +178,9 @@ checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -235,12 +234,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "rustc-workspace-hack" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" - [[package]] name = "rustc_tools_util" version = "0.3.0" diff --git a/src/tools/miri/cargo-miri/Cargo.toml b/src/tools/miri/cargo-miri/Cargo.toml index 09079dbb818be..cfa5ac5281df0 100644 --- a/src/tools/miri/cargo-miri/Cargo.toml +++ b/src/tools/miri/cargo-miri/Cargo.toml @@ -20,11 +20,6 @@ serde_json = "1.0.40" cargo_metadata = "0.15.0" rustc-build-sysroot = "0.4.1" -# A noop dependency that changes in the Rust repository, it's a bit of a hack. -# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` -# for more information. -rustc-workspace-hack = "1.0.0" - # Enable some feature flags that dev-dependencies need but dependencies # do not. This makes `./miri install` after `./miri build` faster. serde = { version = "*", features = ["derive"] } diff --git a/src/tools/miri/cargo-miri/src/main.rs b/src/tools/miri/cargo-miri/src/main.rs index 85c9cdad7dfd2..c5fada6fe55b8 100644 --- a/src/tools/miri/cargo-miri/src/main.rs +++ b/src/tools/miri/cargo-miri/src/main.rs @@ -56,7 +56,7 @@ fn main() { return; } - // The way rustdoc invokes rustc is indistuingishable from the way cargo invokes rustdoc by the + // The way rustdoc invokes rustc is indistinguishable from the way cargo invokes rustdoc by the // arguments alone. `phase_cargo_rustdoc` sets this environment variable to let us disambiguate. if env::var_os("MIRI_CALLED_FROM_RUSTDOC").is_some() { // ...however, we then also see this variable when rustdoc invokes us as the testrunner! @@ -80,7 +80,11 @@ fn main() { match first.as_str() { "miri" => phase_cargo_miri(args), "runner" => phase_runner(args, RunnerPhase::Cargo), - arg if arg == env::var("RUSTC").unwrap() => { + arg if arg == env::var("RUSTC").unwrap_or_else(|_| { + show_error!( + "`cargo-miri` called without RUSTC set; please only invoke this binary through `cargo miri`" + ) + }) => { // If the first arg is equal to the RUSTC env variable (which should be set at this // point), then we need to behave as rustc. This is the somewhat counter-intuitive // behavior of having both RUSTC and RUSTC_WRAPPER set diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs index 465e4a1b2d2b2..80ce63255826e 100644 --- a/src/tools/miri/cargo-miri/src/phases.rs +++ b/src/tools/miri/cargo-miri/src/phases.rs @@ -94,7 +94,7 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { let target = target.as_ref().unwrap_or(host); // We always setup. - setup(&subcommand, target, &rustc_version, verbose); + let miri_sysroot = setup(&subcommand, target, &rustc_version, verbose); // Invoke actual cargo for the job, but with different flags. // We re-use `cargo test` and `cargo run`, which makes target and binary handling very easy but @@ -159,6 +159,8 @@ pub fn phase_cargo_miri(mut args: impl Iterator) { // Forward all further arguments (not consumed by `ArgSplitFlagValue`) to cargo. cmd.args(args); + // Let it know where the Miri sysroot lives. + cmd.env("MIRI_SYSROOT", miri_sysroot); // Set `RUSTC_WRAPPER` to ourselves. Cargo will prepend that binary to its usual invocation, // i.e., the first argument is `rustc` -- which is what we use in `main` to distinguish // the two codepaths. (That extra argument is why we prefer this over setting `RUSTC`.) @@ -519,7 +521,7 @@ pub fn phase_runner(mut binary_args: impl Iterator, phase: Runner // `.rmeta`. // We also need to remove `--error-format` as cargo specifies that to be JSON, // but when we run here, cargo does not interpret the JSON any more. `--json` - // then also nees to be dropped. + // then also needs to be dropped. let mut args = info.args.into_iter(); let error_format_flag = "--error-format"; let json_flag = "--json"; @@ -538,8 +540,7 @@ pub fn phase_runner(mut binary_args: impl Iterator, phase: Runner } // Respect `MIRIFLAGS`. if let Ok(a) = env::var("MIRIFLAGS") { - // This code is taken from `RUSTFLAGS` handling in cargo. - let args = a.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string); + let args = flagsplit(&a); cmd.args(args); } diff --git a/src/tools/miri/cargo-miri/src/setup.rs b/src/tools/miri/cargo-miri/src/setup.rs index 2e4f0a71013c0..77cecddcb8b14 100644 --- a/src/tools/miri/cargo-miri/src/setup.rs +++ b/src/tools/miri/cargo-miri/src/setup.rs @@ -13,13 +13,20 @@ use crate::util::*; /// Performs the setup required to make `cargo miri` work: Getting a custom-built libstd. Then sets /// `MIRI_SYSROOT`. Skipped if `MIRI_SYSROOT` is already set, in which case we expect the user has /// done all this already. -pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta, verbose: usize) { +pub fn setup( + subcommand: &MiriCommand, + target: &str, + rustc_version: &VersionMeta, + verbose: usize, +) -> PathBuf { let only_setup = matches!(subcommand, MiriCommand::Setup); let ask_user = !only_setup; let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path - if !only_setup && std::env::var_os("MIRI_SYSROOT").is_some() { - // Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`. - return; + if !only_setup { + if let Some(sysroot) = std::env::var_os("MIRI_SYSROOT") { + // Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`. + return sysroot.into(); + } } // Determine where the rust sources are located. The env var trumps auto-detection. @@ -92,6 +99,8 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta command.env("RUSTC", &cargo_miri_path); } command.env("MIRI_CALLED_FROM_SETUP", "1"); + // Miri expects `MIRI_SYSROOT` to be set when invoked in target mode. Even if that directory is empty. + command.env("MIRI_SYSROOT", &sysroot_dir); // Make sure there are no other wrappers getting in our way (Cc // https://github.com/rust-lang/miri/issues/1421, // https://github.com/rust-lang/miri/issues/2429). Looks like setting @@ -105,7 +114,7 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta command.arg("-v"); } } else { - // Supress output. + // Suppress output. command.stdout(process::Stdio::null()); command.stderr(process::Stdio::null()); } @@ -117,8 +126,6 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta // the user might have set, which is consistent with normal `cargo build` that does // not apply `RUSTFLAGS` to the sysroot either. let rustflags = &["-Cdebug-assertions=off", "-Coverflow-checks=on"]; - // Make sure all target-level Miri invocations know their sysroot. - std::env::set_var("MIRI_SYSROOT", &sysroot_dir); // Do the build. if print_sysroot { @@ -159,4 +166,6 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta // Print just the sysroot and nothing else to stdout; this way we do not need any escaping. println!("{}", sysroot_dir.display()); } + + sysroot_dir } diff --git a/src/tools/miri/cargo-miri/src/util.rs b/src/tools/miri/cargo-miri/src/util.rs index 60f39cb36abaa..6381a4db86177 100644 --- a/src/tools/miri/cargo-miri/src/util.rs +++ b/src/tools/miri/cargo-miri/src/util.rs @@ -1,7 +1,5 @@ -use std::collections::HashMap; use std::env; use std::ffi::OsString; -use std::fmt::Write as _; use std::fs::File; use std::io::{self, BufWriter, Read, Write}; use std::ops::Not; @@ -82,7 +80,7 @@ pub enum MiriCommand { pub fn escape_for_toml(s: &str) -> String { // We want to surround this string in quotes `"`. So we first escape all quotes, // and also all backslashes (that are used to escape quotes). - let s = s.replace('\\', r#"\\"#).replace('"', r#"\""#); + let s = s.replace('\\', r"\\").replace('"', r#"\""#); format!("\"{s}\"") } @@ -114,6 +112,11 @@ pub fn cargo() -> Command { Command::new(env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"))) } +pub fn flagsplit(flags: &str) -> Vec { + // This code is taken from `RUSTFLAGS` handling in cargo. + flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect() +} + /// Execute the `Command`, where possible by replacing the current process with a new process /// described by the `Command`. Then exit this process with the exit code of the new process. pub fn exec(mut cmd: Command) -> ! { @@ -130,7 +133,7 @@ pub fn exec(mut cmd: Command) -> ! { { use std::os::unix::process::CommandExt; let error = cmd.exec(); - Err(error).expect("failed to run command") + panic!("failed to run command: {error}") } } @@ -242,46 +245,10 @@ pub fn local_crates(metadata: &Metadata) -> String { local_crates } -fn env_vars_from_cmd(cmd: &Command) -> Vec<(String, String)> { - let mut envs = HashMap::new(); - for (key, value) in std::env::vars() { - envs.insert(key, value); - } - for (key, value) in cmd.get_envs() { - if let Some(value) = value { - envs.insert(key.to_string_lossy().to_string(), value.to_string_lossy().to_string()); - } else { - envs.remove(&key.to_string_lossy().to_string()); - } - } - let mut envs: Vec<_> = envs.into_iter().collect(); - envs.sort(); - envs -} - /// Debug-print a command that is going to be run. pub fn debug_cmd(prefix: &str, verbose: usize, cmd: &Command) { if verbose == 0 { return; } - // We only do a single `eprintln!` call to minimize concurrency interactions. - let mut out = prefix.to_string(); - writeln!(out, " running command: env \\").unwrap(); - if verbose > 1 { - // Print the full environment this will be called in. - for (key, value) in env_vars_from_cmd(cmd) { - writeln!(out, "{key}={value:?} \\").unwrap(); - } - } else { - // Print only what has been changed for this `cmd`. - for (var, val) in cmd.get_envs() { - if let Some(val) = val { - writeln!(out, "{}={val:?} \\", var.to_string_lossy()).unwrap(); - } else { - writeln!(out, "--unset={}", var.to_string_lossy()).unwrap(); - } - } - } - write!(out, "{cmd:?}").unwrap(); - eprintln!("{out}"); + eprintln!("{prefix} running command: {cmd:?}"); } diff --git a/src/tools/miri/ci.sh b/src/tools/miri/ci.sh index b5b3b211b05f0..a8aae524e7101 100755 --- a/src/tools/miri/ci.sh +++ b/src/tools/miri/ci.sh @@ -17,11 +17,11 @@ begingroup "Building Miri" echo "Installing release version of Miri" export RUSTFLAGS="-D warnings" export CARGO_INCREMENTAL=0 -./miri install # implicitly locked +export CARGO_EXTRA_FLAGS="--locked" +./miri install # Prepare debug build for direct `./miri` invocations echo "Building debug version of Miri" -export CARGO_EXTRA_FLAGS="--locked" ./miri check --no-default-features # make sure this can be built ./miri check --all-features # and this, too ./miri build --all-targets # the build that all the `./miri test` below will use @@ -39,8 +39,11 @@ function run_tests { ## ui test suite ./miri test if [ -z "${MIRI_TEST_TARGET+exists}" ]; then - # Only for host architecture: tests with optimizations (`-O` is what cargo passes, but crank MIR - # optimizations up all the way, too). + # Host-only tests: running these on all targets is unlikely to catch more problems and would + # cost a lot of CI time. + + # Tests with optimizations (`-O` is what cargo passes, but crank MIR optimizations up all the + # way, too). # Optimizations change diagnostics (mostly backtraces), so we don't check # them. Also error locations change so we don't run the failing tests. # We explicitly enable debug-assertions here, they are disabled by -O but we have tests @@ -51,6 +54,9 @@ function run_tests { for FILE in tests/many-seeds/*.rs; do MIRI_SEEDS=64 CARGO_EXTRA_FLAGS="$CARGO_EXTRA_FLAGS -q" ./miri many-seeds ./miri run "$FILE" done + + # Check that the benchmarks build and run, but without actually benchmarking. + HYPERFINE="bash -c" ./miri bench fi ## test-cargo-miri @@ -75,13 +81,6 @@ function run_tests { unset RUSTC MIRI rm -rf .cargo - # Ensure that our benchmarks all work, but only on Linux hosts. - if [ -z "${MIRI_TEST_TARGET+exists}" ] && [ "$HOST_TARGET" = x86_64-unknown-linux-gnu ] ; then - for BENCH in $(ls "bench-cargo-miri"); do - cargo miri run --manifest-path bench-cargo-miri/$BENCH/Cargo.toml - done - fi - endgroup } diff --git a/src/tools/miri/miri b/src/tools/miri/miri index 48a46a76a1297..c816a4bb06b16 100755 --- a/src/tools/miri/miri +++ b/src/tools/miri/miri @@ -1,355 +1,6 @@ #!/bin/bash set -e -USAGE=$(cat <<"EOF" - COMMANDS - -./miri install : -Installs the miri driver and cargo-miri. are passed to `cargo -install`. Sets up the rpath such that the installed binary should work in any -working directory. Note that the binaries are placed in the `miri` toolchain -sysroot, to prevent conflicts with other toolchains. - -./miri build : -Just build miri. are passed to `cargo build`. - -./miri check : -Just check miri. are passed to `cargo check`. - -./miri test : -Build miri, set up a sysroot and then run the test suite. are passed -to the final `cargo test` invocation. - -./miri run : -Build miri, set up a sysroot and then run the driver with the given . -(Also respects MIRIFLAGS environment variable.) - -./miri fmt : -Format all sources and tests. are passed to `rustfmt`. - -./miri clippy : -Runs clippy on all sources. are passed to `cargo clippy`. - -./miri cargo : -Runs just `cargo ` with the Miri-specific environment variables. -Mainly meant to be invoked by rust-analyzer. - -./miri many-seeds : -Runs over and over again with different seeds for Miri. The MIRIFLAGS -variable is set to its original value appended with ` -Zmiri-seed=$SEED` for -many different seeds. The MIRI_SEEDS variable controls how many seeds are being -tried; MIRI_SEED_START controls the first seed to try. - -./miri bench : -Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. - can explicitly list the benchmarks to run; by default, all of them are run. - -./miri toolchain : -Update and activate the rustup toolchain 'miri' to the commit given in the -`rust-version` file. -`rustup-toolchain-install-master` must be installed for this to work. Any extra -flags are passed to `rustup-toolchain-install-master`. - -./miri rustc-pull : -Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest -rustc commit. The fetched commit is stored in the `rust-version` file, so the -next `./miri toolchain` will install the rustc that just got pulled. - -./miri rustc-push : -Push Miri changes back to the rustc repo. This will pull a copy of the rustc -history into the Miri repo, unless you set the RUSTC_GIT env var to an existing -clone of the rustc repo. - - ENVIRONMENT VARIABLES - -MIRI_SYSROOT: -If already set, the "sysroot setup" step is skipped. - -CARGO_EXTRA_FLAGS: -Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.) -EOF -) - -## We need to know which command to run and some global constants. -COMMAND="$1" -if [ -z "$COMMAND" ]; then - echo "$USAGE" - exit 1 -fi -shift -# macOS does not have a useful readlink/realpath so we have to use Python instead... -MIRIDIR=$(python3 -c 'import os, sys; print(os.path.dirname(os.path.realpath(sys.argv[1])))' "$0") -# Used for rustc syncs. -JOSH_FILTER=":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri" -# Needed for `./miri bench`. -TOOLCHAIN=$(cd "$MIRIDIR"; rustup show active-toolchain | head -n 1 | cut -d ' ' -f 1) - -## Early commands, that don't do auto-things and don't want the environment-altering things happening below. -case "$COMMAND" in -toolchain) - cd "$MIRIDIR" - NEW_COMMIT=$(cat rust-version) - # Make sure rustup-toolchain-install-master is installed. - if ! which rustup-toolchain-install-master >/dev/null; then - echo "Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'" - exit 1 - fi - # Check if we already are at that commit. - CUR_COMMIT=$(rustc +miri --version -v 2>/dev/null | grep "^commit-hash: " | cut -d " " -f 2) - if [[ "$CUR_COMMIT" == "$NEW_COMMIT" ]]; then - echo "miri toolchain is already at commit $CUR_COMMIT." - rustup override set miri - exit 0 - fi - # Install and setup new toolchain. - rustup toolchain uninstall miri - rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy "$@" -- "$NEW_COMMIT" - rustup override set miri - # Cleanup. - cargo clean - # Call 'cargo metadata' on the sources in case that changes the lockfile - # (which fails under some setups when it is done from inside vscode). - cargo metadata --format-version 1 --manifest-path "$(rustc --print sysroot)/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml" >/dev/null - # Done! - exit 0 - ;; -rustc-pull) - cd "$MIRIDIR" - FETCH_COMMIT="$1" - if [ -z "$FETCH_COMMIT" ]; then - FETCH_COMMIT=$(git ls-remote https://github.com/rust-lang/rust/ HEAD | cut -f 1) - fi - # Update rust-version file. As a separate commit, since making it part of - # the merge has confused the heck out of josh in the past. - echo "$FETCH_COMMIT" > rust-version - git commit rust-version -m "Preparing for merge from rustc" || (echo "FAILED to commit rust-version file, something went wrong"; exit 1) - # Fetch given rustc commit and note down which one that was - git fetch http://localhost:8000/rust-lang/rust.git@$FETCH_COMMIT$JOSH_FILTER.git || (echo "FAILED to fetch new commits, something went wrong"; exit 1) - git merge FETCH_HEAD --no-ff -m "Merge from rustc" || (echo "FAILED to merge new commits, something went wrong"; exit 1) - exit 0 - ;; -rustc-push) - USER="$1" - BRANCH="$2" - if [ -z "$USER" ] || [ -z "$BRANCH" ]; then - echo "Usage: $0 rustc-push " - exit 1 - fi - if [ -n "$RUSTC_GIT" ]; then - # Use an existing fork for the branch updates. - cd "$RUSTC_GIT" - else - # Do this in the local Miri repo. - echo "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB." - read -r -p "To avoid that, abort now and set the RUSTC_GIT environment variable to an existing rustc checkout. Proceed? [y/N] " - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - cd "$MIRIDIR" - fi - # Prepare the branch. Pushing works much better if we use as base exactly - # the commit that we pulled from last time, so we use the `rust-version` - # file as a good approximation of that. - BASE=$(cat "$MIRIDIR/rust-version") - echo "Preparing $USER/rust (base: $BASE)..." - if git fetch "https://github.com/$USER/rust" "$BRANCH" &>/dev/null; then - echo "The branch '$BRANCH' seems to already exist in 'https://github.com/$USER/rust'. Please delete it and try again." - exit 1 - fi - git fetch https://github.com/rust-lang/rust $BASE - git push https://github.com/$USER/rust $BASE:refs/heads/$BRANCH -f - echo - # Do the actual push. - cd "$MIRIDIR" - echo "Pushing Miri changes..." - git push http://localhost:8000/$USER/rust.git$JOSH_FILTER.git HEAD:$BRANCH - # Do a round-trip check to make sure the push worked as expected. - echo - git fetch http://localhost:8000/$USER/rust.git@$JOSH_FILTER.git $BRANCH &>/dev/null - if [[ $(git rev-parse HEAD) != $(git rev-parse FETCH_HEAD) ]]; then - echo "ERROR: Josh created a non-roundtrip push! Do NOT merge this into rustc!" - exit 1 - else - echo "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:" - echo " https://github.com/$USER/rust/pull/new/$BRANCH" - exit 0 - fi - ;; -many-seeds) - MIRI_SEED_START=${MIRI_SEED_START:-0} # default to 0 - MIRI_SEEDS=${MIRI_SEEDS:-256} # default to 256 - for SEED in $(seq $MIRI_SEED_START $(( $MIRI_SEED_START + $MIRI_SEEDS - 1 )) ); do - echo "Trying seed: $SEED" - MIRIFLAGS="$MIRIFLAGS -Zlayout-seed=$SEED -Zmiri-seed=$SEED" $@ || { echo "Failing seed: $SEED"; break; } - done - exit 0 - ;; -bench) - # Make sure we have an up-to-date Miri installed - "$0" install - # Run the requested benchmarks - if [ -z "${1+exists}" ]; then - BENCHES=( $(ls "$MIRIDIR/bench-cargo-miri" ) ) - else - BENCHES=("$@") - fi - for BENCH in "${BENCHES[@]}"; do - hyperfine -w 1 -m 5 --shell=none "cargo +$TOOLCHAIN miri run --manifest-path $MIRIDIR/bench-cargo-miri/$BENCH/Cargo.toml" - done - exit 0 - ;; -esac - -## Run the auto-things. -if [ -z "$MIRI_AUTO_OPS" ]; then - export MIRI_AUTO_OPS=42 - - # Run this first, so that the toolchain doesn't change after - # other code has run. - if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-toolchain" ] ; then - $0 toolchain - # Let's make sure to actually use that toolchain, too. - TOOLCHAIN=miri - fi - - if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-fmt" ] ; then - $0 fmt - fi - - if [ -f "$MIRIDIR/.auto-everything" ] || [ -f "$MIRIDIR/.auto-clippy" ] ; then - $0 clippy -- -D warnings - fi -fi - -## Prepare the environment -# Determine some toolchain properties -TARGET=$(rustc +$TOOLCHAIN --version --verbose | grep "^host:" | cut -d ' ' -f 2) -SYSROOT=$(rustc +$TOOLCHAIN --print sysroot) -LIBDIR=$SYSROOT/lib/rustlib/$TARGET/lib -if ! test -d "$LIBDIR"; then - echo "Something went wrong determining the library dir." - echo "I got $LIBDIR but that does not exist." - echo "Please report a bug at https://github.com/rust-lang/miri/issues." - exit 2 -fi - -# Prepare flags for cargo and rustc. -CARGO="cargo +$TOOLCHAIN" -# Share target dir between `miri` and `cargo-miri`. -if [ -z "$CARGO_TARGET_DIR" ]; then - export CARGO_TARGET_DIR="$MIRIDIR/target" -fi -# We configure dev builds to not be unusably slow. -if [ -z "$CARGO_PROFILE_DEV_OPT_LEVEL" ]; then - export CARGO_PROFILE_DEV_OPT_LEVEL=2 -fi -# Enable rustc-specific lints (ignored without `-Zunstable-options`). -export RUSTFLAGS="-Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros $RUSTFLAGS" -# We set the rpath so that Miri finds the private rustc libraries it needs. -export RUSTFLAGS="-C link-args=-Wl,-rpath,$LIBDIR $RUSTFLAGS" - -## Helper functions - -# Build a sysroot and set MIRI_SYSROOT to use it. Arguments are passed to `cargo miri setup`. -build_sysroot() { - if ! MIRI_SYSROOT="$($CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup --print-sysroot "$@")"; then - # Run it again so the user can see the error. - $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml -- miri setup "$@" - echo "'cargo miri setup' failed" - exit 1 - fi - export MIRI_SYSROOT -} - -# Prepare and set MIRI_SYSROOT. Respects `MIRI_TEST_TARGET` and takes into account -# locally built vs. distributed rustc. -find_sysroot() { - if [ -n "$MIRI_SYSROOT" ]; then - # Sysroot already set, use that. - return 0 - fi - # We need to build a sysroot. - if [ -n "$MIRI_TEST_TARGET" ]; then - build_sysroot --target "$MIRI_TEST_TARGET" - else - build_sysroot - fi -} - -## Main - -# Run command. -case "$COMMAND" in -install) - # "--locked" to respect the Cargo.lock file if it exists. - # Install binaries to the miri toolchain's sysroot so they do not interact with other toolchains. - $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR" --force --locked --root "$SYSROOT" "$@" - $CARGO install $CARGO_EXTRA_FLAGS --path "$MIRIDIR"/cargo-miri --force --locked --root "$SYSROOT" "$@" - ;; -check) - # Check, and let caller control flags. - $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@" - $CARGO check $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" - ;; -build) - # Build, and let caller control flags. - $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@" - $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" - ;; -test|bless) - # First build and get a sysroot. - $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml - find_sysroot - if [ "$COMMAND" = "bless" ]; then - export MIRI_BLESS="Gesundheit" - fi - # Then test, and let caller control flags. - # Only in root project as `cargo-miri` has no tests. - $CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@" - ;; -run|run-dep) - # Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so - # that we set the MIRI_SYSROOT up the right way. - FOUND_TARGET_OPT=0 - for ARG in "$@"; do - if [ "$LAST_ARG" = "--target" ]; then - # Found it! - export MIRI_TEST_TARGET="$ARG" - FOUND_TARGET_OPT=1 - break - fi - LAST_ARG="$ARG" - done - if [ "$FOUND_TARGET_OPT" = "0" ] && [ -n "$MIRI_TEST_TARGET" ]; then - # Make sure Miri actually uses this target. - MIRIFLAGS="$MIRIFLAGS --target $MIRI_TEST_TARGET" - fi - - # First build and get a sysroot. - $CARGO build $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml - find_sysroot - # Then run the actual command. - - if [ "$COMMAND" = "run-dep" ]; then - exec $CARGO test --test compiletest $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- --miri-run-dep-mode $MIRIFLAGS "$@" - else - exec $CARGO run $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml -- $MIRIFLAGS "$@" - fi - ;; -fmt) - find "$MIRIDIR" -not \( -name target -prune \) -name '*.rs' \ - | xargs rustfmt +$TOOLCHAIN --edition=2021 --config-path "$MIRIDIR/rustfmt.toml" "$@" - ;; -clippy) - $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@" - $CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@" - ;; -cargo) - # We carefully kept the working dir intact, so this will run cargo *on the workspace in the - # current working dir*, not on the main Miri workspace. That is exactly what RA needs. - $CARGO "$@" - ;; -*) - echo "Unknown command: $COMMAND" - exit 1 - ;; -esac +# Instead of doing just `cargo run --manifest-path .. $@`, we invoke miri-script binary directly. Invoking `cargo run` goes through +# rustup (that sets it's own environmental variables), which is undesirable. +cargo build -q --manifest-path "$(dirname "$0")"/miri-script/Cargo.toml +"$(dirname "$0")"/miri-script/target/debug/miri-script "$@" diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock new file mode 100644 index 0000000000000..cf6062d7d7f58 --- /dev/null +++ b/src/tools/miri/miri-script/Cargo.lock @@ -0,0 +1,160 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "miri-script" +version = "0.1.0" +dependencies = [ + "anyhow", + "dunce", + "itertools", + "path_macro", + "rustc_version", + "shell-words", + "walkdir", + "which", + "xshell", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "path_macro" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e819bbd49d5939f682638fa54826bf1650abddcd65d000923de8ad63cc7d15" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xshell" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c" diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml new file mode 100644 index 0000000000000..c0414a2fe374a --- /dev/null +++ b/src/tools/miri/miri-script/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = ["Miri Team"] +description = "Helpers for miri maintenance" +license = "MIT OR Apache-2.0" +name = "miri-script" +repository = "https://github.com/rust-lang/miri" +version = "0.1.0" +default-run = "miri-script" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +which = "4.4" +walkdir = "2.3" +itertools = "0.10" +path_macro = "1.0" +shell-words = "1.1" +anyhow = "1.0" +xshell = "0.2" +rustc_version = "0.4" +dunce = "1.0.4" diff --git a/src/tools/miri/miri-script/miri b/src/tools/miri/miri-script/miri new file mode 100755 index 0000000000000..cf3ad06788ab1 --- /dev/null +++ b/src/tools/miri/miri-script/miri @@ -0,0 +1,4 @@ +#!/bin/sh +# RA invokes `./miri cargo ...` for each workspace, so we need to forward that to the main `miri` +# script. See . +exec "$(dirname "$0")"/../miri "$@" diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs new file mode 100644 index 0000000000000..ed78f80c02335 --- /dev/null +++ b/src/tools/miri/miri-script/src/commands.rs @@ -0,0 +1,468 @@ +use std::env; +use std::ffi::OsString; +use std::io::Write; +use std::ops::Not; + +use anyhow::{anyhow, bail, Context, Result}; +use path_macro::path; +use walkdir::WalkDir; +use xshell::{cmd, Shell}; + +use crate::util::*; +use crate::Command; + +/// Used for rustc syncs. +const JOSH_FILTER: &str = + ":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri"; + +impl MiriEnv { + fn build_miri_sysroot(&mut self, quiet: bool) -> Result<()> { + if self.sh.var("MIRI_SYSROOT").is_ok() { + // Sysroot already set, use that. + return Ok(()); + } + let manifest_path = path!(self.miri_dir / "cargo-miri" / "Cargo.toml"); + let Self { toolchain, cargo_extra_flags, .. } = &self; + + // Make sure everything is built. Also Miri itself. + self.build(path!(self.miri_dir / "Cargo.toml"), &[], quiet)?; + self.build(&manifest_path, &[], quiet)?; + + let target = &match self.sh.var("MIRI_TEST_TARGET") { + Ok(target) => vec!["--target".into(), target], + Err(_) => vec![], + }; + if !quiet { + eprintln!("$ (building Miri sysroot)"); + } + let output = cmd!(self.sh, + "cargo +{toolchain} --quiet run {cargo_extra_flags...} --manifest-path {manifest_path} -- + miri setup --print-sysroot {target...}" + ).read(); + let Ok(output) = output else { + // Run it again (without `--print-sysroot` or `--quiet`) so the user can see the error. + cmd!( + self.sh, + "cargo +{toolchain} run {cargo_extra_flags...} --manifest-path {manifest_path} -- + miri setup {target...}" + ) + .run() + .with_context(|| "`cargo miri setup` failed")?; + panic!("`cargo miri setup` didn't fail again the 2nd time?"); + }; + self.sh.set_var("MIRI_SYSROOT", output); + Ok(()) + } +} + +impl Command { + fn auto_actions() -> Result<()> { + let miri_dir = miri_dir()?; + let auto_everything = path!(miri_dir / ".auto-everything").exists(); + let auto_toolchain = auto_everything || path!(miri_dir / ".auto-toolchain").exists(); + let auto_fmt = auto_everything || path!(miri_dir / ".auto-fmt").exists(); + let auto_clippy = auto_everything || path!(miri_dir / ".auto-clippy").exists(); + + // `toolchain` goes first as it could affect the others + if auto_toolchain { + Self::toolchain(vec![])?; + } + if auto_fmt { + Self::fmt(vec![])?; + } + if auto_clippy { + Self::clippy(vec![])?; + } + + Ok(()) + } + + pub fn exec(self) -> Result<()> { + match &self { + Command::Install { .. } + | Command::Build { .. } + | Command::Check { .. } + | Command::Test { .. } + | Command::Run { .. } + | Command::Fmt { .. } + | Command::Clippy { .. } + | Command::Cargo { .. } => Self::auto_actions()?, + | Command::ManySeeds { .. } + | Command::Toolchain { .. } + | Command::RustcPull { .. } + | Command::Bench { .. } + | Command::RustcPush { .. } => {} + } + match self { + Command::Install { flags } => Self::install(flags), + Command::Build { flags } => Self::build(flags), + Command::Check { flags } => Self::check(flags), + Command::Test { bless, flags } => Self::test(bless, flags), + Command::Run { dep, flags } => Self::run(dep, flags), + Command::Fmt { flags } => Self::fmt(flags), + Command::Clippy { flags } => Self::clippy(flags), + Command::Cargo { flags } => Self::cargo(flags), + Command::ManySeeds { command } => Self::many_seeds(command), + Command::Bench { benches } => Self::bench(benches), + Command::Toolchain { flags } => Self::toolchain(flags), + Command::RustcPull { commit } => Self::rustc_pull(commit.clone()), + Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch), + } + } + + fn toolchain(flags: Vec) -> Result<()> { + // Make sure rustup-toolchain-install-master is installed. + which::which("rustup-toolchain-install-master") + .context("Please install rustup-toolchain-install-master by running 'cargo install rustup-toolchain-install-master'")?; + let sh = Shell::new()?; + sh.change_dir(miri_dir()?); + let new_commit = Some(sh.read_file("rust-version")?.trim().to_owned()); + let current_commit = { + let rustc_info = cmd!(sh, "rustc +miri --version -v").read(); + if rustc_info.is_err() { + None + } else { + let metadata = rustc_version::version_meta_for(&rustc_info.unwrap())?; + Some( + metadata + .commit_hash + .ok_or_else(|| anyhow!("rustc metadata did not contain commit hash"))?, + ) + } + }; + // Check if we already are at that commit. + if current_commit == new_commit { + if active_toolchain()? != "miri" { + cmd!(sh, "rustup override set miri").run()?; + } + return Ok(()); + } + // Install and setup new toolchain. + cmd!(sh, "rustup toolchain uninstall miri").run()?; + + cmd!(sh, "rustup-toolchain-install-master -n miri -c cargo -c rust-src -c rustc-dev -c llvm-tools -c rustfmt -c clippy {flags...} -- {new_commit...}").run()?; + cmd!(sh, "rustup override set miri").run()?; + // Cleanup. + cmd!(sh, "cargo clean").run()?; + // Call `cargo metadata` on the sources in case that changes the lockfile + // (which fails under some setups when it is done from inside vscode). + let sysroot = cmd!(sh, "rustc --print sysroot").read()?; + let sysroot = sysroot.trim(); + cmd!(sh, "cargo metadata --format-version 1 --manifest-path {sysroot}/lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml").ignore_stdout().run()?; + Ok(()) + } + + fn rustc_pull(commit: Option) -> Result<()> { + let sh = Shell::new()?; + sh.change_dir(miri_dir()?); + let commit = commit.map(Result::Ok).unwrap_or_else(|| { + let rust_repo_head = + cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?; + rust_repo_head + .split_whitespace() + .next() + .map(|front| front.trim().to_owned()) + .ok_or_else(|| anyhow!("Could not obtain Rust repo HEAD from remote.")) + })?; + // Make sure the repo is clean. + if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() { + bail!("working directory must be clean before running `./miri rustc-pull`"); + } + + // Update rust-version file. As a separate commit, since making it part of + // the merge has confused the heck out of josh in the past. + // We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn + // trigger auto-actions. + sh.write_file("rust-version", format!("{commit}\n"))?; + const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc"; + cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}") + .run() + .context("FAILED to commit rust-version file, something went wrong")?; + + // Fetch given rustc commit. + cmd!(sh, "git fetch http://localhost:8000/rust-lang/rust.git@{commit}{JOSH_FILTER}.git") + .run() + .map_err(|e| { + // Try to un-do the previous `git commit`, to leave the repo in the state we found it it. + cmd!(sh, "git reset --hard HEAD^") + .run() + .expect("FAILED to clean up again after failed `git fetch`, sorry for that"); + e + }) + .context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?; + + // Merge the fetched commit. + const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc"; + cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}") + .run() + .context("FAILED to merge new commits, something went wrong")?; + Ok(()) + } + + fn rustc_push(github_user: String, branch: String) -> Result<()> { + let sh = Shell::new()?; + sh.change_dir(miri_dir()?); + let base = sh.read_file("rust-version")?.trim().to_owned(); + // Make sure the repo is clean. + if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() { + bail!("working directory must be clean before running `./miri rustc-push`"); + } + + // Find a repo we can do our preparation in. + if let Ok(rustc_git) = env::var("RUSTC_GIT") { + // If rustc_git is `Some`, we'll use an existing fork for the branch updates. + sh.change_dir(rustc_git); + } else { + // Otherwise, do this in the local Miri repo. + println!( + "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB." + ); + print!( + "To avoid that, abort now and set the `--rustc-git` flag to an existing rustc checkout. Proceed? [y/N] " + ); + std::io::stdout().flush()?; + let mut answer = String::new(); + std::io::stdin().read_line(&mut answer)?; + if answer.trim().to_lowercase() != "y" { + std::process::exit(1); + } + }; + // Prepare the branch. Pushing works much better if we use as base exactly + // the commit that we pulled from last time, so we use the `rust-version` + // file as a good approximation of that. + println!("Preparing {github_user}/rust (base: {base})..."); + if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}") + .ignore_stderr() + .read() + .is_ok() + { + println!( + "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again." + ); + std::process::exit(1); + } + cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?; + cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}") + .run()?; + println!(); + + // Do the actual push. + sh.change_dir(miri_dir()?); + println!("Pushing miri changes..."); + cmd!( + sh, + "git push http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git HEAD:{branch}" + ) + .run()?; + println!(); + + // Do a round-trip check to make sure the push worked as expected. + cmd!( + sh, + "git fetch http://localhost:8000/{github_user}/rust.git{JOSH_FILTER}.git {branch}" + ) + .ignore_stderr() + .read()?; + let head = cmd!(sh, "git rev-parse HEAD").read()?; + let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?; + if head != fetch_head { + bail!("Josh created a non-roundtrip push! Do NOT merge this into rustc!"); + } + println!( + "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:" + ); + println!(" https://github.com/{github_user}/rust/pull/new/{branch}"); + Ok(()) + } + + fn many_seeds(command: Vec) -> Result<()> { + let seed_start: u64 = env::var("MIRI_SEED_START") + .unwrap_or_else(|_| "0".into()) + .parse() + .context("failed to parse MIRI_SEED_START")?; + let seed_count: u64 = env::var("MIRI_SEEDS") + .unwrap_or_else(|_| "256".into()) + .parse() + .context("failed to parse MIRI_SEEDS")?; + let seed_end = seed_start + seed_count; + let Some((command_name, trailing_args)) = command.split_first() else { + bail!("expected many-seeds command to be non-empty"); + }; + let sh = Shell::new()?; + for seed in seed_start..seed_end { + println!("Trying seed: {seed}"); + let mut miriflags = env::var_os("MIRIFLAGS").unwrap_or_default(); + miriflags.push(format!(" -Zlayout-seed={seed} -Zmiri-seed={seed}")); + let status = cmd!(sh, "{command_name} {trailing_args...}") + .env("MIRIFLAGS", miriflags) + .quiet() + .run(); + if status.is_err() { + println!("Failing seed: {seed}"); + break; + } + } + Ok(()) + } + + fn bench(benches: Vec) -> Result<()> { + // The hyperfine to use + let hyperfine = env::var("HYPERFINE"); + let hyperfine = hyperfine.as_deref().unwrap_or("hyperfine -w 1 -m 5 --shell=none"); + let hyperfine = shell_words::split(hyperfine)?; + let Some((program_name, args)) = hyperfine.split_first() else { + bail!("expected HYPERFINE environment variable to be non-empty"); + }; + // Make sure we have an up-to-date Miri installed and selected the right toolchain. + Self::install(vec![])?; + + let sh = Shell::new()?; + sh.change_dir(miri_dir()?); + let benches_dir = "bench-cargo-miri"; + let benches = if benches.is_empty() { + sh.read_dir(benches_dir)? + .into_iter() + .filter(|path| path.is_dir()) + .map(Into::into) + .collect() + } else { + benches.to_owned() + }; + // Run the requested benchmarks + for bench in benches { + let current_bench = path!(benches_dir / bench / "Cargo.toml"); + // We don't attempt to escape `current_bench`, but we wrap it in quotes. + // That seems to make Windows CI happy. + cmd!( + sh, + "{program_name} {args...} 'cargo miri run --manifest-path \"'{current_bench}'\"'" + ) + .run()?; + } + Ok(()) + } + + fn install(flags: Vec) -> Result<()> { + let e = MiriEnv::new()?; + e.install_to_sysroot(e.miri_dir.clone(), &flags)?; + e.install_to_sysroot(path!(e.miri_dir / "cargo-miri"), &flags)?; + Ok(()) + } + + fn build(flags: Vec) -> Result<()> { + let e = MiriEnv::new()?; + e.build(path!(e.miri_dir / "Cargo.toml"), &flags, /* quiet */ false)?; + e.build(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags, /* quiet */ false)?; + Ok(()) + } + + fn check(flags: Vec) -> Result<()> { + let e = MiriEnv::new()?; + e.check(path!(e.miri_dir / "Cargo.toml"), &flags)?; + e.check(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?; + Ok(()) + } + + fn clippy(flags: Vec) -> Result<()> { + let e = MiriEnv::new()?; + e.clippy(path!(e.miri_dir / "Cargo.toml"), &flags)?; + e.clippy(path!(e.miri_dir / "cargo-miri" / "Cargo.toml"), &flags)?; + e.clippy(path!(e.miri_dir / "miri-script" / "Cargo.toml"), &flags)?; + Ok(()) + } + + fn cargo(flags: Vec) -> Result<()> { + let e = MiriEnv::new()?; + let toolchain = &e.toolchain; + // We carefully kept the working dir intact, so this will run cargo *on the workspace in the + // current working dir*, not on the main Miri workspace. That is exactly what RA needs. + cmd!(e.sh, "cargo +{toolchain} {flags...}").run()?; + Ok(()) + } + + fn test(bless: bool, flags: Vec) -> Result<()> { + let mut e = MiriEnv::new()?; + // Prepare a sysroot. + e.build_miri_sysroot(/* quiet */ false)?; + + // Then test, and let caller control flags. + // Only in root project as `cargo-miri` has no tests. + if bless { + e.sh.set_var("RUSTC_BLESS", "Gesundheit"); + } + e.test(path!(e.miri_dir / "Cargo.toml"), &flags)?; + Ok(()) + } + + fn run(dep: bool, flags: Vec) -> Result<()> { + let mut e = MiriEnv::new()?; + // Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so + // that we set the MIRI_SYSROOT up the right way. + use itertools::Itertools; + let target = flags.iter().tuple_windows().find(|(first, _)| first == &"--target"); + if let Some((_, target)) = target { + // Found it! + e.sh.set_var("MIRI_TEST_TARGET", target); + } else if let Ok(target) = std::env::var("MIRI_TEST_TARGET") { + // Make sure miri actually uses this target. + let miriflags = e.sh.var("MIRIFLAGS").unwrap_or_default(); + e.sh.set_var("MIRIFLAGS", format!("{miriflags} --target {target}")); + } + // Prepare a sysroot. + e.build_miri_sysroot(/* quiet */ true)?; + + // Then run the actual command. + let miri_manifest = path!(e.miri_dir / "Cargo.toml"); + let miri_flags = e.sh.var("MIRIFLAGS").unwrap_or_default(); + let miri_flags = flagsplit(&miri_flags); + let toolchain = &e.toolchain; + let extra_flags = &e.cargo_extra_flags; + if dep { + cmd!( + e.sh, + "cargo +{toolchain} --quiet test --test compiletest {extra_flags...} --manifest-path {miri_manifest} -- --miri-run-dep-mode {miri_flags...} {flags...}" + ).quiet().run()?; + } else { + cmd!( + e.sh, + "cargo +{toolchain} --quiet run {extra_flags...} --manifest-path {miri_manifest} -- {miri_flags...} {flags...}" + ).quiet().run()?; + } + Ok(()) + } + + fn fmt(flags: Vec) -> Result<()> { + let e = MiriEnv::new()?; + let toolchain = &e.toolchain; + let config_path = path!(e.miri_dir / "rustfmt.toml"); + + let mut cmd = cmd!( + e.sh, + "rustfmt +{toolchain} --edition=2021 --config-path {config_path} {flags...}" + ); + eprintln!("$ {cmd} ..."); + + // Add all the filenames to the command. + // FIXME: `rustfmt` will follow the `mod` statements in these files, so we get a bunch of + // duplicate diffs. + for item in WalkDir::new(&e.miri_dir).into_iter().filter_entry(|entry| { + let name = entry.file_name().to_string_lossy(); + let ty = entry.file_type(); + if ty.is_file() { + name.ends_with(".rs") + } else { + // dir or symlink. skip `target` and `.git`. + &name != "target" && &name != ".git" + } + }) { + let item = item?; + if item.file_type().is_file() { + cmd = cmd.arg(item.into_path()); + } + } + + // We want our own error message, repeating the command is too much. + cmd.quiet().run().map_err(|_| anyhow!("`rustfmt` failed"))?; + Ok(()) + } +} diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs new file mode 100644 index 0000000000000..849a9168028f2 --- /dev/null +++ b/src/tools/miri/miri-script/src/main.rs @@ -0,0 +1,210 @@ +mod commands; +mod util; + +use std::env; +use std::ffi::OsString; + +use anyhow::{anyhow, bail, Result}; + +#[derive(Clone, Debug)] +pub enum Command { + /// Installs the miri driver and cargo-miri. + /// Sets up the rpath such that the installed binary should work in any + /// working directory. Note that the binaries are placed in the `miri` toolchain + /// sysroot, to prevent conflicts with other toolchains. + Install { + /// Flags that are passed through to `cargo install`. + flags: Vec, + }, + /// Just build miri. + Build { + /// Flags that are passed through to `cargo build`. + flags: Vec, + }, + /// Just check miri. + Check { + /// Flags that are passed through to `cargo check`. + flags: Vec, + }, + /// Build miri, set up a sysroot and then run the test suite. + Test { + bless: bool, + /// Flags that are passed through to `cargo test`. + flags: Vec, + }, + /// Build miri, set up a sysroot and then run the driver with the given . + /// (Also respects MIRIFLAGS environment variable.) + Run { + dep: bool, + /// Flags that are passed through to `miri`. + flags: Vec, + }, + /// Format all sources and tests. + Fmt { + /// Flags that are passed through to `rustfmt`. + flags: Vec, + }, + /// Runs clippy on all sources. + Clippy { + /// Flags that are passed through to `cargo clippy`. + flags: Vec, + }, + /// Runs just `cargo ` with the Miri-specific environment variables. + /// Mainly meant to be invoked by rust-analyzer. + Cargo { flags: Vec }, + /// Runs over and over again with different seeds for Miri. The MIRIFLAGS + /// variable is set to its original value appended with ` -Zmiri-seed=$SEED` for + /// many different seeds. + ManySeeds { command: Vec }, + /// Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. + Bench { + /// List of benchmarks to run. By default all benchmarks are run. + benches: Vec, + }, + /// Update and activate the rustup toolchain 'miri' to the commit given in the + /// `rust-version` file. + /// `rustup-toolchain-install-master` must be installed for this to work. Any extra + /// flags are passed to `rustup-toolchain-install-master`. + Toolchain { flags: Vec }, + /// Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest + /// rustc commit. The fetched commit is stored in the `rust-version` file, so the + /// next `./miri toolchain` will install the rustc that just got pulled. + RustcPull { commit: Option }, + /// Push Miri changes back to the rustc repo. This will pull a copy of the rustc + /// history into the Miri repo, unless you set the RUSTC_GIT env var to an existing + /// clone of the rustc repo. + RustcPush { github_user: String, branch: String }, +} + +const HELP: &str = r#" COMMANDS + +./miri build : +Just build miri. are passed to `cargo build`. + +./miri check : +Just check miri. are passed to `cargo check`. + +./miri test [--bless] : +Build miri, set up a sysroot and then run the test suite. are passed +to the final `cargo test` invocation. + +./miri run [--dep] : +Build miri, set up a sysroot and then run the driver with the given . +(Also respects MIRIFLAGS environment variable.) + +./miri fmt : +Format all sources and tests. are passed to `rustfmt`. + +./miri clippy : +Runs clippy on all sources. are passed to `cargo clippy`. + +./miri cargo : +Runs just `cargo ` with the Miri-specific environment variables. +Mainly meant to be invoked by rust-analyzer. + +./miri install : +Installs the miri driver and cargo-miri. are passed to `cargo +install`. Sets up the rpath such that the installed binary should work in any +working directory. Note that the binaries are placed in the `miri` toolchain +sysroot, to prevent conflicts with other toolchains. + +./miri many-seeds : +Runs over and over again with different seeds for Miri. The MIRIFLAGS +variable is set to its original value appended with ` -Zmiri-seed=$SEED` for +many different seeds. The MIRI_SEEDS variable controls how many seeds are being +tried; MIRI_SEED_START controls the first seed to try. + +./miri bench : +Runs the benchmarks from bench-cargo-miri in hyperfine. hyperfine needs to be installed. + can explicitly list the benchmarks to run; by default, all of them are run. + +./miri toolchain : +Update and activate the rustup toolchain 'miri' to the commit given in the +`rust-version` file. +`rustup-toolchain-install-master` must be installed for this to work. Any extra +flags are passed to `rustup-toolchain-install-master`. + +./miri rustc-pull : +Pull and merge Miri changes from the rustc repo. Defaults to fetching the latest +rustc commit. The fetched commit is stored in the `rust-version` file, so the +next `./miri toolchain` will install the rustc that just got pulled. + +./miri rustc-push : +Push Miri changes back to the rustc repo. This will pull a copy of the rustc +history into the Miri repo, unless you set the RUSTC_GIT env var to an existing +clone of the rustc repo. + + ENVIRONMENT VARIABLES + +MIRI_SYSROOT: +If already set, the "sysroot setup" step is skipped. + +CARGO_EXTRA_FLAGS: +Pass extra flags to all cargo invocations. (Ignored by `./miri cargo`.)"#; + +fn main() -> Result<()> { + // We are hand-rolling our own argument parser, since `clap` can't express what we need + // (https://github.com/clap-rs/clap/issues/5055). + let mut args = env::args_os().peekable(); + args.next().unwrap(); // skip program name + let command = match args.next().and_then(|s| s.into_string().ok()).as_deref() { + Some("build") => Command::Build { flags: args.collect() }, + Some("check") => Command::Check { flags: args.collect() }, + Some("test") => { + let bless = args.peek().is_some_and(|a| a.to_str() == Some("--bless")); + if bless { + // Consume the flag. + args.next().unwrap(); + } + Command::Test { bless, flags: args.collect() } + } + Some("run") => { + let dep = args.peek().is_some_and(|a| a.to_str() == Some("--dep")); + if dep { + // Consume the flag. + args.next().unwrap(); + } + Command::Run { dep, flags: args.collect() } + } + Some("fmt") => Command::Fmt { flags: args.collect() }, + Some("clippy") => Command::Clippy { flags: args.collect() }, + Some("cargo") => Command::Cargo { flags: args.collect() }, + Some("install") => Command::Install { flags: args.collect() }, + Some("many-seeds") => Command::ManySeeds { command: args.collect() }, + Some("bench") => Command::Bench { benches: args.collect() }, + Some("toolchain") => Command::Toolchain { flags: args.collect() }, + Some("rustc-pull") => { + let commit = args.next().map(|a| a.to_string_lossy().into_owned()); + if args.next().is_some() { + bail!("Too many arguments for `./miri rustc-pull`"); + } + Command::RustcPull { commit } + } + Some("rustc-push") => { + let github_user = args + .next() + .ok_or_else(|| { + anyhow!("Missing first argument for `./miri rustc-push GITHUB_USER BRANCH`") + })? + .to_string_lossy() + .into_owned(); + let branch = args + .next() + .ok_or_else(|| { + anyhow!("Missing second argument for `./miri rustc-push GITHUB_USER BRANCH`") + })? + .to_string_lossy() + .into_owned(); + if args.next().is_some() { + bail!("Too many arguments for `./miri rustc-push GITHUB_USER BRANCH`"); + } + Command::RustcPush { github_user, branch } + } + _ => { + eprintln!("Unknown or missing command. Usage:\n\n{HELP}"); + std::process::exit(1); + } + }; + command.exec()?; + Ok(()) +} diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs new file mode 100644 index 0000000000000..64e780b61a7c7 --- /dev/null +++ b/src/tools/miri/miri-script/src/util.rs @@ -0,0 +1,144 @@ +use std::ffi::{OsStr, OsString}; +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use dunce::canonicalize; +use path_macro::path; +use xshell::{cmd, Shell}; + +pub fn miri_dir() -> std::io::Result { + const MIRI_SCRIPT_ROOT_DIR: &str = env!("CARGO_MANIFEST_DIR"); + Ok(canonicalize(MIRI_SCRIPT_ROOT_DIR)?.parent().unwrap().into()) +} + +/// Queries the active toolchain for the Miri dir. +pub fn active_toolchain() -> Result { + let sh = Shell::new()?; + sh.change_dir(miri_dir()?); + let stdout = cmd!(sh, "rustup show active-toolchain").read()?; + Ok(stdout.split_whitespace().next().context("Could not obtain active Rust toolchain")?.into()) +} + +pub fn flagsplit(flags: &str) -> Vec { + // This code is taken from `RUSTFLAGS` handling in cargo. + flags.split(' ').map(str::trim).filter(|s| !s.is_empty()).map(str::to_string).collect() +} + +/// Some extra state we track for building Miri, such as the right RUSTFLAGS. +pub struct MiriEnv { + /// miri_dir is the root of the miri repository checkout we are working in. + pub miri_dir: PathBuf, + /// active_toolchain is passed as `+toolchain` argument to cargo/rustc invocations. + pub toolchain: String, + /// Extra flags to pass to cargo. + pub cargo_extra_flags: Vec, + /// The rustc sysroot + pub sysroot: PathBuf, + /// The shell we use. + pub sh: Shell, +} + +impl MiriEnv { + pub fn new() -> Result { + let toolchain = active_toolchain()?; + let sh = Shell::new()?; // we are preserving the current_dir on this one, so paths resolve properly! + let miri_dir = miri_dir()?; + + let sysroot = cmd!(sh, "rustc +{toolchain} --print sysroot").read()?.into(); + let target_output = cmd!(sh, "rustc +{toolchain} --version --verbose").read()?; + let rustc_meta = rustc_version::version_meta_for(&target_output)?; + let libdir = path!(sysroot / "lib" / "rustlib" / rustc_meta.host / "lib"); + + // Determine some toolchain properties + if !libdir.exists() { + println!("Something went wrong determining the library dir."); + println!("I got {} but that does not exist.", libdir.display()); + println!("Please report a bug at https://github.com/rust-lang/miri/issues."); + std::process::exit(2); + } + // Share target dir between `miri` and `cargo-miri`. + let target_dir = std::env::var_os("CARGO_TARGET_DIR") + .unwrap_or_else(|| path!(miri_dir / "target").into()); + sh.set_var("CARGO_TARGET_DIR", target_dir); + + // We configure dev builds to not be unusably slow. + let devel_opt_level = + std::env::var_os("CARGO_PROFILE_DEV_OPT_LEVEL").unwrap_or_else(|| "2".into()); + sh.set_var("CARGO_PROFILE_DEV_OPT_LEVEL", devel_opt_level); + + // Compute rustflags. + let rustflags = { + let mut flags = OsString::new(); + // We set the rpath so that Miri finds the private rustc libraries it needs. + flags.push("-C link-args=-Wl,-rpath,"); + flags.push(libdir); + // Enable rustc-specific lints (ignored without `-Zunstable-options`). + flags.push(" -Zunstable-options -Wrustc::internal -Wrust_2018_idioms -Wunused_lifetimes -Wsemicolon_in_expressions_from_macros"); + // Add user-defined flags. + if let Some(value) = std::env::var_os("RUSTFLAGS") { + flags.push(" "); + flags.push(value); + } + flags + }; + sh.set_var("RUSTFLAGS", rustflags); + + // Get extra flags for cargo. + let cargo_extra_flags = std::env::var("CARGO_EXTRA_FLAGS").unwrap_or_default(); + let cargo_extra_flags = flagsplit(&cargo_extra_flags); + + Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags }) + } + + pub fn install_to_sysroot( + &self, + path: impl AsRef, + args: impl IntoIterator>, + ) -> Result<()> { + let MiriEnv { sysroot, toolchain, cargo_extra_flags, .. } = self; + // Install binaries to the miri toolchain's `sysroot` so they do not interact with other toolchains. + cmd!(self.sh, "cargo +{toolchain} install {cargo_extra_flags...} --path {path} --force --root {sysroot} {args...}").run()?; + Ok(()) + } + + pub fn build( + &self, + manifest_path: impl AsRef, + args: &[OsString], + quiet: bool, + ) -> Result<()> { + let MiriEnv { toolchain, cargo_extra_flags, .. } = self; + let quiet_flag = if quiet { Some("--quiet") } else { None }; + let mut cmd = cmd!( + self.sh, + "cargo +{toolchain} build {cargo_extra_flags...} --manifest-path {manifest_path} {quiet_flag...} {args...}" + ); + cmd.set_quiet(quiet); + cmd.run()?; + Ok(()) + } + + pub fn check(&self, manifest_path: impl AsRef, args: &[OsString]) -> Result<()> { + let MiriEnv { toolchain, cargo_extra_flags, .. } = self; + cmd!(self.sh, "cargo +{toolchain} check {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}") + .run()?; + Ok(()) + } + + pub fn clippy(&self, manifest_path: impl AsRef, args: &[OsString]) -> Result<()> { + let MiriEnv { toolchain, cargo_extra_flags, .. } = self; + cmd!(self.sh, "cargo +{toolchain} clippy {cargo_extra_flags...} --manifest-path {manifest_path} --all-targets {args...}") + .run()?; + Ok(()) + } + + pub fn test(&self, manifest_path: impl AsRef, args: &[OsString]) -> Result<()> { + let MiriEnv { toolchain, cargo_extra_flags, .. } = self; + cmd!( + self.sh, + "cargo +{toolchain} test {cargo_extra_flags...} --manifest-path {manifest_path} {args...}" + ) + .run()?; + Ok(()) + } +} diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 0bff100dc140e..30123b92f6caa 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -8b4b20836b832e91aa605a2faf5e2a55190202c8 +656ee47db32e882fb02913f6204e09cc7a41a50e diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 083dd4800d93e..97718f1f4a9f7 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -31,9 +31,9 @@ use rustc_middle::{ query::{ExternProviders, LocalCrate}, ty::TyCtxt, }; -use rustc_session::config::OptLevel; - -use rustc_session::{config::CrateType, search_paths::PathKind, CtfeBacktrace}; +use rustc_session::config::{CrateType, ErrorOutputType, OptLevel}; +use rustc_session::search_paths::PathKind; +use rustc_session::{CtfeBacktrace, EarlyErrorHandler}; use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields}; @@ -59,6 +59,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { fn after_analysis<'tcx>( &mut self, + handler: &EarlyErrorHandler, _: &rustc_interface::interface::Compiler, queries: &'tcx rustc_interface::Queries<'tcx>, ) -> Compilation { @@ -67,8 +68,8 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { tcx.sess.fatal("miri cannot be run on programs that fail compilation"); } - init_late_loggers(tcx); - if !tcx.sess.crate_types().contains(&CrateType::Executable) { + init_late_loggers(handler, tcx); + if !tcx.crate_types().contains(&CrateType::Executable) { tcx.sess.fatal("miri only makes sense on bin crates"); } @@ -181,7 +182,7 @@ macro_rules! show_error { ($($tt:tt)*) => { show_error(&format_args!($($tt)*)) }; } -fn init_early_loggers() { +fn init_early_loggers(handler: &EarlyErrorHandler) { // Note that our `extern crate log` is *not* the same as rustc's; as a result, we have to // initialize them both, and we always initialize `miri`'s first. let env = env_logger::Env::new().filter("MIRI_LOG").write_style("MIRI_LOG_STYLE"); @@ -195,11 +196,11 @@ fn init_early_loggers() { // later with our custom settings, and *not* log anything for what happens before // `miri` gets started. if env::var_os("RUSTC_LOG").is_some() { - rustc_driver::init_rustc_env_logger(); + rustc_driver::init_rustc_env_logger(handler); } } -fn init_late_loggers(tcx: TyCtxt<'_>) { +fn init_late_loggers(handler: &EarlyErrorHandler, tcx: TyCtxt<'_>) { // We initialize loggers right before we start evaluation. We overwrite the `RUSTC_LOG` // env var if it is not set, control it based on `MIRI_LOG`. // (FIXME: use `var_os`, but then we need to manually concatenate instead of `format!`.) @@ -218,7 +219,7 @@ fn init_late_loggers(tcx: TyCtxt<'_>) { } else { env::set_var("RUSTC_LOG", &var); } - rustc_driver::init_rustc_env_logger(); + rustc_driver::init_rustc_env_logger(handler); } } @@ -284,6 +285,8 @@ fn parse_comma_list(input: &str) -> Result, T::Err> { } fn main() { + let handler = EarlyErrorHandler::new(ErrorOutputType::default()); + // Snapshot a copy of the environment before `rustc` starts messing with it. // (`install_ice_hook` might change `RUST_BACKTRACE`.) let env_snapshot = env::vars_os().collect::>(); @@ -292,7 +295,7 @@ fn main() { if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") { // Earliest rustc setup. rustc_driver::install_ice_hook(rustc_driver::DEFAULT_BUG_REPORT_URL, |_| ()); - rustc_driver::init_rustc_env_logger(); + rustc_driver::init_rustc_env_logger(&handler); let target_crate = if crate_kind == "target" { true @@ -314,7 +317,7 @@ fn main() { rustc_driver::install_ice_hook("https://github.com/rust-lang/miri/issues/new", |_| ()); // Init loggers the Miri way. - init_early_loggers(); + init_early_loggers(&handler); // Parse our arguments and split them across `rustc` and `miri`. let mut miri_config = miri::MiriConfig::default(); @@ -340,6 +343,8 @@ fn main() { miri_config.borrow_tracker = None; } else if arg == "-Zmiri-tree-borrows" { miri_config.borrow_tracker = Some(BorrowTrackerMethod::TreeBorrows); + } else if arg == "-Zmiri-unique-is-unique" { + miri_config.unique_is_unique = true; } else if arg == "-Zmiri-disable-data-race-detector" { miri_config.data_race_detector = false; miri_config.weak_memory_emulation = false; @@ -557,6 +562,14 @@ fn main() { rustc_args.push(arg); } } + // `-Zmiri-unique-is-unique` should only be used with `-Zmiri-tree-borrows` + if miri_config.unique_is_unique + && !matches!(miri_config.borrow_tracker, Some(BorrowTrackerMethod::TreeBorrows)) + { + show_error!( + "-Zmiri-unique-is-unique only has an effect when -Zmiri-tree-borrows is also used" + ); + } debug!("rustc arguments: {:?}", rustc_args); debug!("crate arguments: {:?}", miri_config.args); diff --git a/src/tools/miri/src/borrow_tracker/mod.rs b/src/tools/miri/src/borrow_tracker/mod.rs index ffc49eedb5a98..b6dfd9944eef3 100644 --- a/src/tools/miri/src/borrow_tracker/mod.rs +++ b/src/tools/miri/src/borrow_tracker/mod.rs @@ -74,7 +74,7 @@ pub struct FrameState { impl VisitTags for FrameState { fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) { - // `protected_tags` are fine to GC. + // `protected_tags` are already recorded by `GlobalStateInner`. } } @@ -103,12 +103,17 @@ pub struct GlobalStateInner { pub tracked_call_ids: FxHashSet, /// Whether to recurse into datatypes when searching for pointers to retag. pub retag_fields: RetagFields, + /// Whether `core::ptr::Unique` gets special (`Box`-like) handling. + pub unique_is_unique: bool, } impl VisitTags for GlobalStateInner { - fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) { - // The only candidate is base_ptr_tags, and that does not need visiting since we don't ever - // GC the bottommost tag. + fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { + for &tag in self.protected_tags.keys() { + visit(tag); + } + // The only other candidate is base_ptr_tags, and that does not need visiting since we don't ever + // GC the bottommost/root tag. } } @@ -170,6 +175,7 @@ impl GlobalStateInner { tracked_pointer_tags: FxHashSet, tracked_call_ids: FxHashSet, retag_fields: RetagFields, + unique_is_unique: bool, ) -> Self { GlobalStateInner { borrow_tracker_method, @@ -180,6 +186,7 @@ impl GlobalStateInner { tracked_pointer_tags, tracked_call_ids, retag_fields, + unique_is_unique, } } @@ -244,6 +251,7 @@ impl BorrowTrackerMethod { config.tracked_pointer_tags.clone(), config.tracked_call_ids.clone(), config.retag_fields, + config.unique_is_unique, )) } } @@ -297,12 +305,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } - fn retag_return_place(&mut self) -> InterpResult<'tcx> { + fn protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method; match method { - BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(), - BorrowTrackerMethod::TreeBorrows => this.tb_retag_return_place(), + BorrowTrackerMethod::StackedBorrows => this.sb_protect_place(place), + BorrowTrackerMethod::TreeBorrows => this.tb_protect_place(place), } } @@ -405,7 +413,7 @@ impl AllocState { alloc_id: AllocId, prov_extra: ProvenanceExtra, range: AllocRange, - machine: &mut MiriMachine<'_, 'tcx>, + machine: &MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx> { match self { AllocState::StackedBorrows(sb) => @@ -426,7 +434,7 @@ impl AllocState { alloc_id: AllocId, prov_extra: ProvenanceExtra, range: AllocRange, - machine: &mut MiriMachine<'_, 'tcx>, + machine: &MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx> { match self { AllocState::StackedBorrows(sb) => diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs index c9674e0a2fe2c..9b0f13dd62c55 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs @@ -6,11 +6,19 @@ use rustc_span::{Span, SpanData}; use rustc_target::abi::Size; use crate::borrow_tracker::{ - stacked_borrows::{err_sb_ub, Permission}, - AccessKind, GlobalStateInner, ProtectorKind, + stacked_borrows::Permission, AccessKind, GlobalStateInner, ProtectorKind, }; use crate::*; +/// Error reporting +fn err_sb_ub<'tcx>( + msg: String, + help: Vec, + history: Option, +) -> InterpError<'tcx> { + err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history }) +} + #[derive(Clone, Debug)] pub struct AllocHistory { id: AllocId, @@ -61,12 +69,15 @@ struct Invalidation { #[derive(Clone, Debug)] enum InvalidationCause { Access(AccessKind), - Retag(Permission, RetagCause), + Retag(Permission, RetagInfo), } impl Invalidation { fn generate_diagnostic(&self) -> (String, SpanData) { - let message = if let InvalidationCause::Retag(_, RetagCause::FnEntry) = self.cause { + let message = if matches!( + self.cause, + InvalidationCause::Retag(_, RetagInfo { cause: RetagCause::FnEntry, .. }) + ) { // For a FnEntry retag, our Span points at the caller. // See `DiagnosticCx::log_invalidation`. format!( @@ -87,8 +98,8 @@ impl fmt::Display for InvalidationCause { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { InvalidationCause::Access(kind) => write!(f, "{kind}"), - InvalidationCause::Retag(perm, kind) => - write!(f, "{perm:?} {retag}", retag = kind.summary()), + InvalidationCause::Retag(perm, info) => + write!(f, "{perm:?} {retag}", retag = info.summary()), } } } @@ -129,13 +140,13 @@ impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> { pub fn retag( machine: &'ecx MiriMachine<'mir, 'tcx>, - cause: RetagCause, + info: RetagInfo, new_tag: BorTag, orig_tag: ProvenanceExtra, range: AllocRange, ) -> Self { let operation = - Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None }); + Operation::Retag(RetagOp { info, new_tag, orig_tag, range, permission: None }); DiagnosticCxBuilder { machine, operation } } @@ -179,17 +190,23 @@ enum Operation { #[derive(Debug, Clone)] struct RetagOp { - cause: RetagCause, + info: RetagInfo, new_tag: BorTag, orig_tag: ProvenanceExtra, range: AllocRange, permission: Option, } +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RetagInfo { + pub cause: RetagCause, + pub in_field: bool, +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum RetagCause { Normal, - FnReturnPlace, + InPlaceFnPassing, FnEntry, TwoPhase, } @@ -221,7 +238,10 @@ impl AllocHistory { impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { pub fn start_grant(&mut self, perm: Permission) { let Operation::Retag(op) = &mut self.operation else { - unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation) + unreachable!( + "start_grant must only be called during a retag, this is: {:?}", + self.operation + ) }; op.permission = Some(perm); @@ -255,11 +275,11 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { pub fn log_invalidation(&mut self, tag: BorTag) { let mut span = self.machine.current_span(); let (range, cause) = match &self.operation { - Operation::Retag(RetagOp { cause, range, permission, .. }) => { - if *cause == RetagCause::FnEntry { + Operation::Retag(RetagOp { info, range, permission, .. }) => { + if info.cause == RetagCause::FnEntry { span = self.machine.caller_span(); } - (*range, InvalidationCause::Retag(permission.unwrap(), *cause)) + (*range, InvalidationCause::Retag(permission.unwrap(), *info)) } Operation::Access(AccessOp { kind, range, .. }) => (*range, InvalidationCause::Access(*kind)), @@ -286,7 +306,8 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { tag: BorTag, protector_tag: Option, ) -> Option { - let Some(created) = self.history + let Some(created) = self + .history .creations .iter() .rev() @@ -315,22 +336,27 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { None } }) - }).or_else(|| { + }) + .or_else(|| { // If we didn't find a retag that created this tag, it might be the base tag of // this allocation. if self.history.base.0.tag() == tag { Some(( - format!("{tag:?} was created here, as the base tag for {:?}", self.history.id), - self.history.base.1.data() + format!( + "{tag:?} was created here, as the base tag for {:?}", + self.history.id + ), + self.history.base.1.data(), )) } else { None } - }) else { - // But if we don't have a creation event, this is related to a wildcard, and there - // is really nothing we can do to help. - return None; - }; + }) + else { + // But if we don't have a creation event, this is related to a wildcard, and there + // is really nothing we can do to help. + return None; + }; let invalidated = self.history.invalidations.iter().rev().find_map(|event| { if event.tag == tag { Some(event.generate_diagnostic()) } else { None } @@ -363,9 +389,13 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { self.history.id, self.offset.bytes(), ); + let mut helps = vec![operation_summary(&op.info.summary(), self.history.id, op.range)]; + if op.info.in_field { + helps.push(format!("errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling")); + } err_sb_ub( format!("{action}{}", error_cause(stack, op.orig_tag)), - Some(operation_summary(&op.cause.summary(), self.history.id, op.range)), + helps, op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)), ) } @@ -388,7 +418,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { ); err_sb_ub( format!("{action}{}", error_cause(stack, op.tag)), - Some(operation_summary("an access", self.history.id, op.range)), + vec![operation_summary("an access", self.history.id, op.range)], op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)), ) } @@ -414,7 +444,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { Operation::Dealloc(_) => err_sb_ub( format!("deallocating while item {item:?} is {protected} by call {call_id:?}",), - None, + vec![], None, ), Operation::Retag(RetagOp { orig_tag: tag, .. }) @@ -423,7 +453,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { format!( "not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}", ), - None, + vec![], tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))), ), } @@ -441,7 +471,7 @@ impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> { alloc_id = self.history.id, cause = error_cause(stack, op.tag), ), - None, + vec![], op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)), ) } @@ -487,14 +517,18 @@ fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str { } } -impl RetagCause { +impl RetagInfo { fn summary(&self) -> String { - match self { + let mut s = match self.cause { RetagCause::Normal => "retag", RetagCause::FnEntry => "function-entry retag", - RetagCause::FnReturnPlace => "return-place retag", + RetagCause::InPlaceFnPassing => "in-place function argument/return passing protection", RetagCause::TwoPhase => "two-phase retag", } - .to_string() + .to_string(); + if self.in_field { + s.push_str(" (of a reference/box inside this compound value)"); + } + s } } diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index 29881bbcfca66..0351f586872eb 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -5,26 +5,24 @@ pub mod diagnostics; mod item; mod stack; -use log::trace; use std::cmp; use std::fmt::Write; +use std::mem; + +use log::trace; use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir::{Mutability, RetagKind}; -use rustc_middle::ty::{ - self, - layout::{HasParamEnv, LayoutOf}, - Ty, -}; -use rustc_target::abi::{Abi, Size}; +use rustc_middle::ty::{self, layout::HasParamEnv, Ty}; +use rustc_target::abi::{Abi, Align, Size}; use crate::borrow_tracker::{ - stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder, TagHistory}, + stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder}, AccessKind, GlobalStateInner, ProtectorKind, RetagFields, }; use crate::*; -use diagnostics::RetagCause; +use diagnostics::{RetagCause, RetagInfo}; pub use item::{Item, Permission}; pub use stack::Stack; @@ -168,15 +166,6 @@ impl NewPermission { } } -/// Error reporting -pub fn err_sb_ub<'tcx>( - msg: String, - help: Option, - history: Option, -) -> InterpError<'tcx> { - err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history }) -} - // # Stacked Borrows Core Begin /// We need to make at least the following things true: @@ -244,7 +233,7 @@ impl<'tcx> Stack { fn item_invalidated( item: &Item, global: &GlobalStateInner, - dcx: &mut DiagnosticCx<'_, '_, '_, 'tcx>, + dcx: &DiagnosticCx<'_, '_, '_, 'tcx>, cause: ItemInvalidationCause, ) -> InterpResult<'tcx> { if !global.tracked_pointer_tags.is_empty() { @@ -430,12 +419,15 @@ impl<'tcx> Stack { .find_granting(AccessKind::Write, derived_from, exposed_tags) .map_err(|()| dcx.grant_error(self))?; - let (Some(granting_idx), ProvenanceExtra::Concrete(_)) = (granting_idx, derived_from) else { + let (Some(granting_idx), ProvenanceExtra::Concrete(_)) = (granting_idx, derived_from) + else { // The parent is a wildcard pointer or matched the unknown bottom. // This is approximate. Nobody knows what happened, so forget everything. // The new thing is SRW anyway, so we cannot push it "on top of the unknown part" // (for all we know, it might join an SRW group inside the unknown). - trace!("reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown"); + trace!( + "reborrow: forgetting stack entirely due to SharedReadWrite reborrow from wildcard or unknown" + ); self.set_unknown_bottom(global.next_ptr_tag); return Ok(()); }; @@ -572,7 +564,7 @@ impl Stacks { alloc_id: AllocId, tag: ProvenanceExtra, range: AllocRange, - machine: &mut MiriMachine<'_, 'tcx>, + machine: &MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx> { trace!( "write access with tag {:?}: {:?}, size {}", @@ -593,7 +585,7 @@ impl Stacks { alloc_id: AllocId, tag: ProvenanceExtra, range: AllocRange, - machine: &mut MiriMachine<'_, 'tcx>, + machine: &MiriMachine<'_, 'tcx>, ) -> InterpResult<'tcx> { trace!("deallocation with tag {:?}: {:?}, size {}", tag, alloc_id, range.size.bytes()); let dcx = DiagnosticCxBuilder::dealloc(machine, tag); @@ -612,17 +604,18 @@ impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx> { } trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> { - /// Returns the `AllocId` the reborrow was done in, if some actual borrow stack manipulation - /// happened. + /// Returns the provenance that should be used henceforth. fn sb_reborrow( &mut self, place: &MPlaceTy<'tcx, Provenance>, size: Size, new_perm: NewPermission, new_tag: BorTag, - retag_cause: RetagCause, // What caused this retag, for diagnostics only - ) -> InterpResult<'tcx, Option> { + retag_info: RetagInfo, // diagnostics info about this retag + ) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); + // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). + this.check_ptr_access_align(place.ptr, size, Align::ONE, CheckInAllocMsg::InboundsTest)?; // It is crucial that this gets called on all code paths, to ensure we track tag creation. let log_creation = |this: &MiriInterpCx<'mir, 'tcx>, @@ -667,7 +660,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' // FIXME: can this be done cleaner? let dcx = DiagnosticCxBuilder::retag( &this.machine, - retag_cause, + retag_info, new_tag, orig_tag, alloc_range(base_offset, size), @@ -701,28 +694,19 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' // pointer tagging for example all calls to get_unchecked on them are invalid. if let Ok((alloc_id, base_offset, orig_tag)) = this.ptr_try_get_alloc_id(place.ptr) { log_creation(this, Some((alloc_id, base_offset, orig_tag)))?; - return Ok(Some(alloc_id)); + // Still give it the new provenance, it got retagged after all. + return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })); + } else { + // This pointer doesn't come with an AllocId. :shrug: + log_creation(this, None)?; + // Provenance unchanged. + return Ok(place.ptr.provenance); } - // This pointer doesn't come with an AllocId. :shrug: - log_creation(this, None)?; - return Ok(None); } let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?; log_creation(this, Some((alloc_id, base_offset, orig_tag)))?; - // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). - let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?; - if base_offset + size > alloc_size { - throw_ub!(PointerOutOfBounds { - alloc_id, - alloc_size, - ptr_offset: this.target_usize_to_isize(base_offset.bytes()), - ptr_size: size, - msg: CheckInAllocMsg::InboundsTest - }); - } - trace!( "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}", new_tag, @@ -758,7 +742,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let global = machine.borrow_tracker.as_ref().unwrap().borrow(); let dcx = DiagnosticCxBuilder::retag( machine, - retag_cause, + retag_info, new_tag, orig_tag, alloc_range(base_offset, size), @@ -801,7 +785,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let global = this.machine.borrow_tracker.as_ref().unwrap().borrow(); let dcx = DiagnosticCxBuilder::retag( &this.machine, - retag_cause, + retag_info, new_tag, orig_tag, alloc_range(base_offset, size), @@ -822,7 +806,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' } } - Ok(Some(alloc_id)) + Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })) } /// Retags an individual pointer, returning the retagged version. @@ -831,7 +815,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' &mut self, val: &ImmTy<'tcx, Provenance>, new_perm: NewPermission, - cause: RetagCause, // What caused this retag, for diagnostics only + info: RetagInfo, // diagnostics info about this retag ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { let this = self.eval_context_mut(); // We want a place for where the ptr *points to*, so we get one. @@ -849,25 +833,10 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr(); // Reborrow. - let alloc_id = this.sb_reborrow(&place, size, new_perm, new_tag, cause)?; + let new_prov = this.sb_reborrow(&place, size, new_perm, new_tag, info)?; // Adjust pointer. - let new_place = place.map_provenance(|p| { - p.map(|prov| { - match alloc_id { - Some(alloc_id) => { - // If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one. - // Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation. - Provenance::Concrete { alloc_id, tag: new_tag } - } - None => { - // Looks like this has to stay a wildcard pointer. - assert!(matches!(prov, Provenance::Wildcard)); - Provenance::Wildcard - } - } - }) - }); + let new_place = place.map_provenance(|_| new_prov); // Return new pointer. Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout)) @@ -883,12 +852,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { let this = self.eval_context_mut(); let new_perm = NewPermission::from_ref_ty(val.layout.ty, kind, this); - let retag_cause = match kind { + let cause = match kind { RetagKind::TwoPhase { .. } => RetagCause::TwoPhase, RetagKind::FnEntry => unreachable!(), RetagKind::Raw | RetagKind::Default => RetagCause::Normal, }; - this.sb_retag_reference(val, new_perm, retag_cause) + this.sb_retag_reference(val, new_perm, RetagInfo { cause, in_field: false }) } fn sb_retag_place_contents( @@ -903,7 +872,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { RetagKind::FnEntry => RetagCause::FnEntry, RetagKind::Default => RetagCause::Normal, }; - let mut visitor = RetagVisitor { ecx: this, kind, retag_cause, retag_fields }; + let mut visitor = + RetagVisitor { ecx: this, kind, retag_cause, retag_fields, in_field: false }; return visitor.visit_value(place); // The actual visitor. @@ -912,6 +882,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { kind: RetagKind, retag_cause: RetagCause, retag_fields: RetagFields, + in_field: bool, } impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> { #[inline(always)] // yes this helps in our benchmarks @@ -919,28 +890,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &mut self, place: &PlaceTy<'tcx, Provenance>, new_perm: NewPermission, - retag_cause: RetagCause, ) -> InterpResult<'tcx> { let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?; - let val = self.ecx.sb_retag_reference(&val, new_perm, retag_cause)?; + let val = self.ecx.sb_retag_reference( + &val, + new_perm, + RetagInfo { cause: self.retag_cause, in_field: self.in_field }, + )?; self.ecx.write_immediate(*val, place)?; Ok(()) } } - impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>> + impl<'ecx, 'mir, 'tcx> ValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>> for RetagVisitor<'ecx, 'mir, 'tcx> { type V = PlaceTy<'tcx, Provenance>; #[inline(always)] - fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> { + fn ecx(&self) -> &MiriInterpCx<'mir, 'tcx> { self.ecx } fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { // Boxes get a weak protectors, since they may be deallocated. let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx); - self.retag_ptr_inplace(place, new_perm, self.retag_cause) + self.retag_ptr_inplace(place, new_perm) } fn visit_value(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { @@ -957,7 +931,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ty::Ref(..) => { let new_perm = NewPermission::from_ref_ty(place.layout.ty, self.kind, self.ecx); - self.retag_ptr_inplace(place, new_perm, self.retag_cause)?; + self.retag_ptr_inplace(place, new_perm)?; } ty::RawPtr(..) => { // We do *not* want to recurse into raw pointers -- wide raw pointers have @@ -981,7 +955,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } }; if recurse { + let in_field = mem::replace(&mut self.in_field, true); // remember and restore old value self.walk_value(place)?; + self.in_field = in_field; } } } @@ -991,35 +967,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } - /// After a stack frame got pushed, retag the return place so that we are sure - /// it does not alias with anything. + /// Protect a place so that it cannot be used any more for the duration of the current function + /// call. /// - /// This is a HACK because there is nothing in MIR that would make the retag - /// explicit. Also see . - fn sb_retag_return_place(&mut self) -> InterpResult<'tcx> { + /// This is used to ensure soundness of in-place function argument/return passing. + fn sb_protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let return_place = &this.frame().return_place; - if return_place.layout.is_zst() { - // There may not be any memory here, nothing to do. - return Ok(()); - } - // We need this to be in-memory to use tagged pointers. - let return_place = this.force_allocation(&return_place.clone())?; - // We have to turn the place into a pointer to use the existing code. + // We have to turn the place into a pointer to use the usual retagging logic. // (The pointer type does not matter, so we use a raw pointer.) - let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?; - let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout); - // Reborrow it. With protection! That is part of the point. + let ptr = this.mplace_to_ref(place)?; + // Reborrow it. With protection! That is the entire point. let new_perm = NewPermission::Uniform { perm: Permission::Unique, access: Some(AccessKind::Write), protector: Some(ProtectorKind::StrongProtector), }; - let val = this.sb_retag_reference(&val, new_perm, RetagCause::FnReturnPlace)?; - // And use reborrowed pointer for return place. - let return_place = this.ref_to_mplace(&val)?; - this.frame_mut().return_place = return_place.into(); + let _new_ptr = this.sb_retag_reference( + &ptr, + new_perm, + RetagInfo { cause: RetagCause::InPlaceFnPassing, in_field: false }, + )?; + // We just throw away `new_ptr`, so nobody can access this memory while it is protected. Ok(()) } diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs index a1e949183ad40..291807c25eeb7 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs @@ -196,19 +196,19 @@ impl<'tcx> Stack { let ProvenanceExtra::Concrete(tag) = tag else { // Handle the wildcard case. // Go search the stack for an exposed tag. - if let Some(idx) = - self.borrows - .iter() - .enumerate() // we also need to know *where* in the stack - .rev() // search top-to-bottom - .find_map(|(idx, item)| { - // If the item fits and *might* be this wildcard, use it. - if item.perm().grants(access) && exposed_tags.contains(&item.tag()) { - Some(idx) - } else { - None - } - }) + if let Some(idx) = self + .borrows + .iter() + .enumerate() // we also need to know *where* in the stack + .rev() // search top-to-bottom + .find_map(|(idx, item)| { + // If the item fits and *might* be this wildcard, use it. + if item.perm().grants(access) && exposed_tags.contains(&item.tag()) { + Some(idx) + } else { + None + } + }) { return Ok(Some(idx)); } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs index 10873c46a6c5d..fd45671ba29d8 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/diagnostics.rs @@ -12,16 +12,46 @@ use crate::borrow_tracker::tree_borrows::{ use crate::borrow_tracker::{AccessKind, ProtectorKind}; use crate::*; +/// Cause of an access: either a real access or one +/// inserted by Tree Borrows due to a reborrow or a deallocation. +#[derive(Clone, Copy, Debug)] +pub enum AccessCause { + Explicit(AccessKind), + Reborrow, + Dealloc, +} + +impl fmt::Display for AccessCause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Explicit(kind) => write!(f, "{kind}"), + Self::Reborrow => write!(f, "reborrow"), + Self::Dealloc => write!(f, "deallocation"), + } + } +} + +impl AccessCause { + fn print_as_access(self, is_foreign: bool) -> String { + let rel = if is_foreign { "foreign" } else { "child" }; + match self { + Self::Explicit(kind) => format!("{rel} {kind}"), + Self::Reborrow => format!("reborrow (acting as a {rel} read access)"), + Self::Dealloc => format!("deallocation (acting as a {rel} write access)"), + } + } +} + /// Complete data for an event: #[derive(Clone, Debug)] pub struct Event { - /// Transformation of permissions that occured because of this event + /// Transformation of permissions that occured because of this event. pub transition: PermTransition, - /// Kind of the access that triggered this event - pub access_kind: AccessKind, - /// Relative position of the tag to the one used for the access + /// Kind of the access that triggered this event. + pub access_cause: AccessCause, + /// Relative position of the tag to the one used for the access. pub is_foreign: bool, - /// User-visible range of the access + /// User-visible range of the access. pub access_range: AllocRange, /// The transition recorded by this event only occured on a subrange of /// `access_range`: a single access on `access_range` triggers several events, @@ -36,7 +66,7 @@ pub struct Event { /// the `TbError`, which should satisfy /// `event.transition_range.contains(error.error_offset)`. pub transition_range: Range, - /// Line of code that triggered this event + /// Line of code that triggered this event. pub span: Span, } @@ -83,8 +113,8 @@ impl HistoryData { self.events.push((Some(created.0.data()), msg_creation)); for &Event { transition, - access_kind, is_foreign, + access_cause, access_range, span, transition_range: _, @@ -92,8 +122,10 @@ impl HistoryData { { // NOTE: `transition_range` is explicitly absent from the error message, it has no significance // to the user. The meaningful one is `access_range`. - self.events.push((Some(span.data()), format!("{this} then transitioned {transition} due to a {rel} {access_kind} at offsets {access_range:?}", rel = if is_foreign { "foreign" } else { "child" }))); - self.events.push((None, format!("this corresponds to {}", transition.summary()))); + let access = access_cause.print_as_access(is_foreign); + self.events.push((Some(span.data()), format!("{this} later transitioned to {endpoint} due to a {access} at offsets {access_range:?}", endpoint = transition.endpoint()))); + self.events + .push((None, format!("this transition corresponds to {}", transition.summary()))); } } } @@ -186,7 +218,7 @@ impl<'tcx> Tree { } } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy)] pub(super) enum TransitionError { /// This access is not allowed because some parent tag has insufficient permissions. /// For example, if a tag is `Frozen` and encounters a child write this will @@ -195,10 +227,10 @@ pub(super) enum TransitionError { ChildAccessForbidden(Permission), /// A protector was triggered due to an invalid transition that loses /// too much permissions. - /// For example, if a protected tag goes from `Active` to `Frozen` due - /// to a foreign write this will produce a `ProtectedTransition(PermTransition(Active, Frozen))`. + /// For example, if a protected tag goes from `Active` to `Disabled` due + /// to a foreign write this will produce a `ProtectedDisabled(Active)`. /// This kind of error can only occur on foreign accesses. - ProtectedTransition(PermTransition), + ProtectedDisabled(Permission), /// Cannot deallocate because some tag in the allocation is strongly protected. /// This kind of error can only occur on deallocations. ProtectedDealloc, @@ -212,12 +244,13 @@ impl History { /// Reconstruct the history relevant to `error_offset` by filtering /// only events whose range contains the offset we are interested in. - fn extract_relevant(&self, error_offset: u64) -> Self { + fn extract_relevant(&self, error_offset: u64, error_kind: TransitionError) -> Self { History { events: self .events .iter() .filter(|e| e.transition_range.contains(&error_offset)) + .filter(|e| e.transition.is_relevant(error_kind)) .cloned() .collect::>(), created: self.created, @@ -237,9 +270,8 @@ pub(super) struct TbError<'node> { /// On accesses rejected due to insufficient permissions, this is the /// tag that lacked those permissions. pub conflicting_info: &'node NodeDebugInfo, - /// Whether this was a Read or Write access. This field is ignored - /// when the error was triggered by a deallocation. - pub access_kind: AccessKind, + // What kind of access caused this error (read, write, reborrow, deallocation) + pub access_cause: AccessCause, /// Which tag the access that caused this error was made through, i.e. /// which tag was used to read/write/deallocate. pub accessed_info: &'node NodeDebugInfo, @@ -249,46 +281,43 @@ impl TbError<'_> { /// Produce a UB error. pub fn build<'tcx>(self) -> InterpError<'tcx> { use TransitionError::*; - let kind = self.access_kind; + let cause = self.access_cause; let accessed = self.accessed_info; let conflicting = self.conflicting_info; let accessed_is_conflicting = accessed.tag == conflicting.tag; + let title = format!("{cause} through {accessed} is forbidden"); let (title, details, conflicting_tag_name) = match self.error_kind { ChildAccessForbidden(perm) => { let conflicting_tag_name = if accessed_is_conflicting { "accessed" } else { "conflicting" }; - let title = format!("{kind} through {accessed} is forbidden"); let mut details = Vec::new(); if !accessed_is_conflicting { details.push(format!( "the accessed tag {accessed} is a child of the conflicting tag {conflicting}" )); } + let access = cause.print_as_access(/* is_foreign */ false); details.push(format!( - "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids child {kind}es" + "the {conflicting_tag_name} tag {conflicting} has state {perm} which forbids this {access}" )); (title, details, conflicting_tag_name) } - ProtectedTransition(transition) => { + ProtectedDisabled(before_disabled) => { let conflicting_tag_name = "protected"; - let title = format!("{kind} through {accessed} is forbidden"); + let access = cause.print_as_access(/* is_foreign */ true); let details = vec![ format!( "the accessed tag {accessed} is foreign to the {conflicting_tag_name} tag {conflicting} (i.e., it is not a child)" ), format!( - "the access would cause the {conflicting_tag_name} tag {conflicting} to transition {transition}" - ), - format!( - "this is {loss}, which is not allowed for protected tags", - loss = transition.summary(), + "this {access} would cause the {conflicting_tag_name} tag {conflicting} (currently {before_disabled}) to become Disabled" ), + format!("protected tags must never be Disabled"), ]; (title, details, conflicting_tag_name) } ProtectedDealloc => { let conflicting_tag_name = "strongly protected"; - let title = format!("deallocation through {accessed} is forbidden"); let details = vec![ format!( "the allocation of the accessed tag {accessed} also contains the {conflicting_tag_name} tag {conflicting}" @@ -303,7 +332,7 @@ impl TbError<'_> { history.extend(self.accessed_info.history.forget(), "accessed", false); } history.extend( - self.conflicting_info.history.extract_relevant(self.error_offset), + self.conflicting_info.history.extract_relevant(self.error_offset, self.error_kind), conflicting_tag_name, true, ); @@ -538,9 +567,13 @@ impl DisplayRepr { extraction_aux(tree, tree.root, show_unnamed, &mut v); let Some(root) = v.pop() else { if show_unnamed { - unreachable!("This allocation contains no tags, not even a root. This should not happen."); + unreachable!( + "This allocation contains no tags, not even a root. This should not happen." + ); } - eprintln!("This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags."); + eprintln!( + "This allocation does not contain named tags. Use `miri_print_borrow_state(_, true)` to also print unnamed tags." + ); return None; }; assert!(v.is_empty()); diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 3361d212f2c08..08b138c68aec8 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -1,16 +1,13 @@ use log::trace; -use rustc_target::abi::{Abi, Size}; +use rustc_target::abi::{Abi, Align, Size}; use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields}; use rustc_middle::{ mir::{Mutability, RetagKind}, - ty::{ - self, - layout::{HasParamEnv, LayoutOf}, - Ty, - }, + ty::{self, layout::HasParamEnv, Ty}, }; +use rustc_span::def_id::DefId; use crate::*; @@ -62,7 +59,14 @@ impl<'tcx> Tree { }; let global = machine.borrow_tracker.as_ref().unwrap(); let span = machine.current_span(); - self.perform_access(access_kind, tag, range, global, span) + self.perform_access( + access_kind, + tag, + range, + global, + span, + diagnostics::AccessCause::Explicit(access_kind), + ) } /// Check that this pointer has permission to deallocate this range. @@ -92,9 +96,9 @@ impl<'tcx> Tree { /// Policy for a new borrow. #[derive(Debug, Clone, Copy)] struct NewPermission { - /// Whether this borrow requires a read access on its parent. - /// `perform_read_access` is `true` for all pointers marked `dereferenceable`. - perform_read_access: bool, + /// Optionally ignore the actual size to do a zero-size reborrow. + /// If this is set then `dereferenceable` is not enforced. + zero_size: bool, /// Which permission should the pointer start with. initial_state: Permission, /// Whether this pointer is part of the arguments of a function call. @@ -113,7 +117,7 @@ impl<'tcx> NewPermission { let ty_is_freeze = pointee.is_freeze(*cx.tcx, cx.param_env()); let ty_is_unpin = pointee.is_unpin(*cx.tcx, cx.param_env()); let initial_state = match mutability { - Mutability::Mut if ty_is_unpin => Permission::new_unique_2phase(ty_is_freeze), + Mutability::Mut if ty_is_unpin => Permission::new_reserved(ty_is_freeze), Mutability::Not if ty_is_freeze => Permission::new_frozen(), // Raw pointers never enter this function so they are not handled. // However raw pointers are not the only pointers that take the parent @@ -121,20 +125,19 @@ impl<'tcx> NewPermission { // `&`s, which are excluded above. _ => return None, }; - // This field happens to be redundant since right now we always do a read, - // but it could be useful in the future. - let perform_read_access = true; let protector = (kind == RetagKind::FnEntry).then_some(ProtectorKind::StrongProtector); - Some(Self { perform_read_access, initial_state, protector }) + Some(Self { zero_size: false, initial_state, protector }) } - // Boxes are not handled by `from_ref_ty`, they need special behavior - // implemented here. - fn from_box_ty( + /// Compute permission for `Box`-like type (`Box` always, and also `Unique` if enabled). + /// These pointers allow deallocation so need a different kind of protector not handled + /// by `from_ref_ty`. + fn from_unique_ty( ty: Ty<'tcx>, kind: RetagKind, cx: &crate::MiriInterpCx<'_, 'tcx>, + zero_size: bool, ) -> Option { let pointee = ty.builtin_deref(true).unwrap().ty; pointee.is_unpin(*cx.tcx, cx.param_env()).then_some(()).map(|()| { @@ -142,8 +145,8 @@ impl<'tcx> NewPermission { // because it is valid to deallocate it within the function. let ty_is_freeze = ty.is_freeze(*cx.tcx, cx.param_env()); Self { - perform_read_access: true, - initial_state: Permission::new_unique_2phase(ty_is_freeze), + zero_size, + initial_state: Permission::new_reserved(ty_is_freeze), protector: (kind == RetagKind::FnEntry).then_some(ProtectorKind::WeakProtector), } }) @@ -158,23 +161,22 @@ impl<'mir: 'ecx, 'tcx: 'mir, 'ecx> EvalContextPrivExt<'mir, 'tcx, 'ecx> { } trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'mir, 'tcx> { - /// Returns the `AllocId` the reborrow was done in, if there is some actual - /// memory associated with this pointer. Returns `None` if there is no actual - /// memory allocated. Also checks that the reborrow of size `ptr_size` is - /// within bounds of the allocation. - /// - /// Also returns the tag that the pointer should get, which is essentially - /// `if new_perm.is_some() { new_tag } else { parent_tag }` along with - /// some logging (always) and fake reads (if `new_perm` is - /// `Some(NewPermission { perform_read_access: true }`). + /// Returns the provenance that should be used henceforth. fn tb_reborrow( &mut self, place: &MPlaceTy<'tcx, Provenance>, // parent tag extracted from here ptr_size: Size, - new_perm: Option, + new_perm: NewPermission, new_tag: BorTag, - ) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> { + ) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); + // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). + this.check_ptr_access_align( + place.ptr, + ptr_size, + Align::ONE, + CheckInAllocMsg::InboundsTest, + )?; // It is crucial that this gets called on all code paths, to ensure we track tag creation. let log_creation = |this: &MiriInterpCx<'mir, 'tcx>, @@ -194,51 +196,35 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' Ok(()) }; - let (alloc_id, base_offset, parent_prov) = if ptr_size > Size::ZERO { - this.ptr_get_alloc_id(place.ptr)? - } else { - match this.ptr_try_get_alloc_id(place.ptr) { - Ok(data) => data, - Err(_) => { - // This pointer doesn't come with an AllocId, so there's no - // memory to do retagging in. - trace!( - "reborrow of size 0: reference {:?} derived from {:?} (pointee {})", - new_tag, - place.ptr, - place.layout.ty, - ); - log_creation(this, None)?; - return Ok(None); - } + trace!("Reborrow of size {:?}", ptr_size); + let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr) { + Ok(data) => { + // Unlike SB, we *do* a proper retag for size 0 if can identify the allocation. + // After all, the pointer may be lazily initialized outside this initial range. + data + } + Err(_) => { + assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here + // This pointer doesn't come with an AllocId, so there's no + // memory to do retagging in. + trace!( + "reborrow of size 0: reference {:?} derived from {:?} (pointee {})", + new_tag, + place.ptr, + place.layout.ty, + ); + log_creation(this, None)?; + // Keep original provenance. + return Ok(place.ptr.provenance); } }; + log_creation(this, Some((alloc_id, base_offset, parent_prov)))?; + let orig_tag = match parent_prov { - ProvenanceExtra::Wildcard => return Ok(None), // TODO: handle wildcard pointers + ProvenanceExtra::Wildcard => return Ok(place.ptr.provenance), // TODO: handle wildcard pointers ProvenanceExtra::Concrete(tag) => tag, }; - // Protection against trying to get a reference to a vtable: - // vtables do not have an alloc_extra so the call to - // `get_alloc_extra` that follows fails. - let (alloc_size, _align, alloc_kind) = this.get_alloc_info(alloc_id); - if ptr_size == Size::ZERO && !matches!(alloc_kind, AllocKind::LiveData) { - return Ok(Some((alloc_id, orig_tag))); - } - - log_creation(this, Some((alloc_id, base_offset, parent_prov)))?; - - // Ensure we bail out if the pointer goes out-of-bounds (see miri#1050). - if base_offset + ptr_size > alloc_size { - throw_ub!(PointerOutOfBounds { - alloc_id, - alloc_size, - ptr_offset: this.target_usize_to_isize(base_offset.bytes()), - ptr_size, - msg: CheckInAllocMsg::InboundsTest - }); - } - trace!( "reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}", new_tag, @@ -248,8 +234,6 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' ptr_size.bytes() ); - let Some(new_perm) = new_perm else { return Ok(Some((alloc_id, orig_tag))); }; - if let Some(protect) = new_perm.protector { // We register the protection in two different places. // This makes creating a protector slower, but checking whether a tag @@ -264,42 +248,79 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' .insert(new_tag, protect); } + let alloc_kind = this.get_alloc_info(alloc_id).2; + if !matches!(alloc_kind, AllocKind::LiveData) { + assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here + // There's not actually any bytes here where accesses could even be tracked. + // Just produce the new provenance, nothing else to do. + return Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })); + } + let span = this.machine.current_span(); let alloc_extra = this.get_alloc_extra(alloc_id)?; let range = alloc_range(base_offset, ptr_size); let mut tree_borrows = alloc_extra.borrow_tracker_tb().borrow_mut(); - if new_perm.perform_read_access { - // Count this reborrow as a read access - let global = &this.machine.borrow_tracker.as_ref().unwrap(); - let span = this.machine.current_span(); - tree_borrows.perform_access(AccessKind::Read, orig_tag, range, global, span)?; + // All reborrows incur a (possibly zero-sized) read access to the parent + tree_borrows.perform_access( + AccessKind::Read, + orig_tag, + range, + this.machine.borrow_tracker.as_ref().unwrap(), + this.machine.current_span(), + diagnostics::AccessCause::Reborrow, + )?; + // Record the parent-child pair in the tree. + tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?; + drop(tree_borrows); + + // Also inform the data race model (but only if any bytes are actually affected). + if range.size.bytes() > 0 { if let Some(data_race) = alloc_extra.data_race.as_ref() { - data_race.read(alloc_id, range, &this.machine)?; + // We sometimes need to make it a write, since not all retags commute with reads! + // FIXME: Is that truly the semantics we want? Some optimizations are likely to be + // very unhappy without this. We'd tsill ge some UB just by picking a suitable + // interleaving, but wether UB happens can depend on whether a write occurs in the + // future... + let is_write = new_perm.initial_state.is_active() + || (new_perm.initial_state.is_reserved() && new_perm.protector.is_some()); + if is_write { + // Need to get mutable access to alloc_extra. + // (Cannot always do this as we can do read-only reborrowing on read-only allocations.) + let (alloc_extra, machine) = this.get_alloc_extra_mut(alloc_id)?; + alloc_extra.data_race.as_mut().unwrap().write(alloc_id, range, machine)?; + } else { + data_race.read(alloc_id, range, &this.machine)?; + } } } - // Record the parent-child pair in the tree. - tree_borrows.new_child(orig_tag, new_tag, new_perm.initial_state, range, span)?; - Ok(Some((alloc_id, new_tag))) + Ok(Some(Provenance::Concrete { alloc_id, tag: new_tag })) } /// Retags an individual pointer, returning the retagged version. fn tb_retag_reference( &mut self, val: &ImmTy<'tcx, Provenance>, - new_perm: Option, + new_perm: NewPermission, ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { let this = self.eval_context_mut(); // We want a place for where the ptr *points to*, so we get one. let place = this.ref_to_mplace(val)?; - // Get a lower bound of the size of this place. - // (When `extern type` are involved, use the size of the known prefix.) - let size = this - .size_and_align_of_mplace(&place)? - .map(|(size, _)| size) - .unwrap_or(place.layout.size); + // Determine the size of the reborrow. + // For most types this is the entire size of the place, however + // - when `extern type` is involved we use the size of the known prefix, + // - if the pointer is not reborrowed (raw pointer) or if `zero_size` is set + // then we override the size to do a zero-length reborrow. + let reborrow_size = match new_perm { + NewPermission { zero_size: false, .. } => + this.size_and_align_of_mplace(&place)? + .map(|(size, _)| size) + .unwrap_or(place.layout.size), + _ => Size::from_bytes(0), + }; + trace!("Creating new permission: {:?} with size {:?}", new_perm, reborrow_size); // This new tag is not guaranteed to actually be used. // @@ -310,25 +331,10 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<' let new_tag = this.machine.borrow_tracker.as_mut().unwrap().get_mut().new_ptr(); // Compute the actual reborrow. - let reborrowed = this.tb_reborrow(&place, size, new_perm, new_tag)?; + let new_prov = this.tb_reborrow(&place, reborrow_size, new_perm, new_tag)?; // Adjust pointer. - let new_place = place.map_provenance(|p| { - p.map(|prov| { - match reborrowed { - Some((alloc_id, actual_tag)) => { - // If `reborrow` could figure out the AllocId of this ptr, hard-code it into the new one. - // Even if we started out with a wildcard, this newly retagged pointer is tied to that allocation. - Provenance::Concrete { alloc_id, tag: actual_tag } - } - None => { - // Looks like this has to stay a wildcard pointer. - assert!(matches!(prov, Provenance::Wildcard)); - Provenance::Wildcard - } - } - }) - }); + let new_place = place.map_provenance(|_| new_prov); // Return new pointer. Ok(ImmTy::from_immediate(new_place.to_ref(this), val.layout)) @@ -345,12 +351,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { val: &ImmTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> { let this = self.eval_context_mut(); - let new_perm = if let &ty::Ref(_, pointee, mutability) = val.layout.ty.kind() { - NewPermission::from_ref_ty(pointee, mutability, kind, this) - } else { - None + let new_perm = match val.layout.ty.kind() { + &ty::Ref(_, pointee, mutability) => + NewPermission::from_ref_ty(pointee, mutability, kind, this), + _ => None, }; - this.tb_retag_reference(val, new_perm) + if let Some(new_perm) = new_perm { + this.tb_retag_reference(val, new_perm) + } else { + Ok(val.clone()) + } } /// Retag all pointers that are stored in this place. @@ -360,8 +370,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { place: &PlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let retag_fields = this.machine.borrow_tracker.as_mut().unwrap().get_mut().retag_fields; - let mut visitor = RetagVisitor { ecx: this, kind, retag_fields }; + let options = this.machine.borrow_tracker.as_mut().unwrap().get_mut(); + let retag_fields = options.retag_fields; + let unique_did = + options.unique_is_unique.then(|| this.tcx.lang_items().ptr_unique()).flatten(); + let mut visitor = RetagVisitor { ecx: this, kind, retag_fields, unique_did }; return visitor.visit_value(place); // The actual visitor. @@ -369,6 +382,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ecx: &'ecx mut MiriInterpCx<'mir, 'tcx>, kind: RetagKind, retag_fields: RetagFields, + unique_did: Option, } impl<'ecx, 'mir, 'tcx> RetagVisitor<'ecx, 'mir, 'tcx> { #[inline(always)] // yes this helps in our benchmarks @@ -377,24 +391,34 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { place: &PlaceTy<'tcx, Provenance>, new_perm: Option, ) -> InterpResult<'tcx> { - let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?; - let val = self.ecx.tb_retag_reference(&val, new_perm)?; - self.ecx.write_immediate(*val, place)?; + if let Some(new_perm) = new_perm { + let val = self.ecx.read_immediate(&self.ecx.place_to_op(place)?)?; + let val = self.ecx.tb_retag_reference(&val, new_perm)?; + self.ecx.write_immediate(*val, place)?; + } Ok(()) } } - impl<'ecx, 'mir, 'tcx> MutValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>> + impl<'ecx, 'mir, 'tcx> ValueVisitor<'mir, 'tcx, MiriMachine<'mir, 'tcx>> for RetagVisitor<'ecx, 'mir, 'tcx> { type V = PlaceTy<'tcx, Provenance>; #[inline(always)] - fn ecx(&mut self) -> &mut MiriInterpCx<'mir, 'tcx> { + fn ecx(&self) -> &MiriInterpCx<'mir, 'tcx> { self.ecx } + /// Regardless of how `Unique` is handled, Boxes are always reborrowed. + /// When `Unique` is also reborrowed, then it behaves exactly like `Box` + /// except for the fact that `Box` has a non-zero-sized reborrow. fn visit_box(&mut self, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { - let new_perm = NewPermission::from_box_ty(place.layout.ty, self.kind, self.ecx); + let new_perm = NewPermission::from_unique_ty( + place.layout.ty, + self.kind, + self.ecx, + /* zero_size */ false, + ); self.retag_ptr_inplace(place, new_perm) } @@ -426,6 +450,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // even if field retagging is not enabled. *shrug*) self.walk_value(place)?; } + ty::Adt(adt, _) if self.unique_did == Some(adt.did()) => { + let place = inner_ptr_of_unique(self.ecx, place)?; + let new_perm = NewPermission::from_unique_ty( + place.layout.ty, + self.kind, + self.ecx, + /* zero_size */ true, + ); + self.retag_ptr_inplace(&place, new_perm)?; + } _ => { // Not a reference/pointer/box. Only recurse if configured appropriately. let recurse = match self.retag_fields { @@ -442,43 +476,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } } - Ok(()) } } } - /// After a stack frame got pushed, retag the return place so that we are sure - /// it does not alias with anything. + /// Protect a place so that it cannot be used any more for the duration of the current function + /// call. /// - /// This is a HACK because there is nothing in MIR that would make the retag - /// explicit. Also see . - fn tb_retag_return_place(&mut self) -> InterpResult<'tcx> { + /// This is used to ensure soundness of in-place function argument/return passing. + fn tb_protect_place(&mut self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - //this.debug_hint_location(); - let return_place = &this.frame().return_place; - if return_place.layout.is_zst() { - // There may not be any memory here, nothing to do. - return Ok(()); - } - // We need this to be in-memory to use tagged pointers. - let return_place = this.force_allocation(&return_place.clone())?; - // We have to turn the place into a pointer to use the existing code. + // We have to turn the place into a pointer to use the usual retagging logic. // (The pointer type does not matter, so we use a raw pointer.) - let ptr_layout = this.layout_of(this.tcx.mk_mut_ptr(return_place.layout.ty))?; - let val = ImmTy::from_immediate(return_place.to_ref(this), ptr_layout); - // Reborrow it. With protection! That is part of the point. - // FIXME: do we truly want a 2phase borrow here? - let new_perm = Some(NewPermission { - initial_state: Permission::new_unique_2phase(/*freeze*/ false), - perform_read_access: true, + let ptr = this.mplace_to_ref(place)?; + // Reborrow it. With protection! That is the entire point. + let new_perm = NewPermission { + initial_state: Permission::new_active(), + zero_size: false, protector: Some(ProtectorKind::StrongProtector), - }); - let val = this.tb_retag_reference(&val, new_perm)?; - // And use reborrowed pointer for return place. - let return_place = this.ref_to_mplace(&val)?; - this.frame_mut().return_place = return_place.into(); + }; + let _new_ptr = this.tb_retag_reference(&ptr, new_perm)?; + // We just throw away `new_ptr`, so nobody can access this memory while it is protected. Ok(()) } @@ -538,3 +558,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { tree_borrows.give_pointer_debug_name(tag, nth_parent, name) } } + +/// Takes a place for a `Unique` and turns it into a place with the inner raw pointer. +/// I.e. input is what you get from the visitor upon encountering an `adt` that is `Unique`, +/// and output can be used by `retag_ptr_inplace`. +fn inner_ptr_of_unique<'tcx>( + ecx: &MiriInterpCx<'_, 'tcx>, + place: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> { + // Follows the same layout as `interpret/visitor.rs:walk_value` for `Box` in + // `rustc_const_eval`, just with one fewer layer. + // Here we have a `Unique(NonNull(*mut), PhantomData)` + assert_eq!(place.layout.fields.count(), 2, "Unique must have exactly 2 fields"); + let (nonnull, phantom) = (ecx.project_field(place, 0)?, ecx.project_field(place, 1)?); + assert!( + phantom.layout.ty.ty_adt_def().is_some_and(|adt| adt.is_phantom_data()), + "2nd field of `Unique` should be `PhantomData` but is `{:?}`", + phantom.layout.ty, + ); + // Now down to `NonNull(*mut)` + assert_eq!(nonnull.layout.fields.count(), 1, "NonNull must have exactly 1 field"); + let ptr = ecx.project_field(&nonnull, 0)?; + // Finally a plain `*mut` + Ok(ptr) +} diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs index 7e3e587db7248..0ce29ac5437f8 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/perms.rs @@ -1,6 +1,7 @@ use std::cmp::{Ordering, PartialOrd}; use std::fmt; +use crate::borrow_tracker::tree_borrows::diagnostics::TransitionError; use crate::borrow_tracker::tree_borrows::tree::AccessRelatedness; use crate::borrow_tracker::AccessKind; @@ -71,7 +72,12 @@ mod transition { // accesses, since the data is not being mutated. Hence the `{ .. }` res @ Reserved { .. } if !protected => res, Reserved { .. } => Frozen, // protected reserved - Active => Frozen, + Active => + if protected { + Disabled + } else { + Frozen + }, non_writeable @ (Frozen | Disabled) => non_writeable, }) } @@ -115,26 +121,43 @@ mod transition { /// Public interface to the state machine that controls read-write permissions. /// This is the "private `enum`" pattern. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Permission(PermissionPriv); +pub struct Permission { + inner: PermissionPriv, +} /// Transition from one permission to the next. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PermTransition(PermissionPriv, PermissionPriv); +pub struct PermTransition { + from: PermissionPriv, + to: PermissionPriv, +} impl Permission { /// Default initial permission of the root of a new tree. - pub fn new_root() -> Self { - Self(Active) + pub fn new_active() -> Self { + Self { inner: Active } } /// Default initial permission of a reborrowed mutable reference. - pub fn new_unique_2phase(ty_is_freeze: bool) -> Self { - Self(Reserved { ty_is_freeze }) + pub fn new_reserved(ty_is_freeze: bool) -> Self { + Self { inner: Reserved { ty_is_freeze } } } /// Default initial permission of a reborrowed shared reference pub fn new_frozen() -> Self { - Self(Frozen) + Self { inner: Frozen } + } + + pub fn is_active(self) -> bool { + matches!(self.inner, Active) + } + + pub fn is_reserved(self) -> bool { + matches!(self.inner, Reserved { .. }) + } + + pub fn is_frozen(self) -> bool { + matches!(self.inner, Frozen) } /// Apply the transition to the inner PermissionPriv. @@ -144,9 +167,9 @@ impl Permission { old_perm: Self, protected: bool, ) -> Option { - let old_state = old_perm.0; + let old_state = old_perm.inner; transition::perform_access(kind, rel_pos, old_state, protected) - .map(|new_state| PermTransition(old_state, new_state)) + .map(|new_state| PermTransition { from: old_state, to: new_state }) } } @@ -155,64 +178,32 @@ impl PermTransition { /// should be possible, but the same is not guaranteed by construction of /// transitions inferred by diagnostics. This checks that a transition /// reconstructed by diagnostics is indeed one that could happen. - fn is_possible(old: PermissionPriv, new: PermissionPriv) -> bool { - old <= new + fn is_possible(self) -> bool { + self.from <= self.to } - pub fn from(old: Permission, new: Permission) -> Option { - Self::is_possible(old.0, new.0).then_some(Self(old.0, new.0)) + pub fn from(from: Permission, to: Permission) -> Option { + let t = Self { from: from.inner, to: to.inner }; + t.is_possible().then_some(t) } pub fn is_noop(self) -> bool { - self.0 == self.1 + self.from == self.to } /// Extract result of a transition (checks that the starting point matches). pub fn applied(self, starting_point: Permission) -> Option { - (starting_point.0 == self.0).then_some(Permission(self.1)) + (starting_point.inner == self.from).then_some(Permission { inner: self.to }) } /// Extract starting point of a transition pub fn started(self) -> Permission { - Permission(self.0) - } - - /// Determines whether a transition that occured is compatible with the presence - /// of a Protector. This is not included in the `transition` functions because - /// it would distract from the few places where the transition is modified - /// because of a protector, but not forbidden. - /// - /// Note: this is not in charge of checking that there *is* a protector, - /// it should be used as - /// ``` - /// let no_protector_error = if is_protected(tag) { - /// transition.is_allowed_by_protector() - /// }; - /// ``` - pub fn is_allowed_by_protector(&self) -> bool { - let &Self(old, new) = self; - assert!(Self::is_possible(old, new)); - match (old, new) { - _ if old == new => true, - // It is always a protector violation to not be readable anymore - (_, Disabled) => false, - // In the case of a `Reserved` under a protector, both transitions - // `Reserved => Active` and `Reserved => Frozen` can legitimately occur. - // The first is standard (Child Write), the second is for Foreign Writes - // on protected Reserved where we must ensure that the pointer is not - // written to in the future. - (Reserved { .. }, Active) | (Reserved { .. }, Frozen) => true, - // This pointer should have stayed writeable for the whole function - (Active, Frozen) => false, - _ => unreachable!("Transition from {old:?} to {new:?} should never be possible"), - } + Permission { inner: self.from } } - /// Composition function: get the transition that can be added after `app` to - /// produce `self`. - pub fn apply_start(self, app: Self) -> Option { - let new_start = app.applied(Permission(self.0))?; - Self::from(new_start, Permission(self.1)) + /// Determines if this transition would disable the permission. + pub fn produces_disabled(self) -> bool { + self.to == Disabled } } @@ -224,10 +215,10 @@ pub mod diagnostics { f, "{}", match self { - PermissionPriv::Reserved { .. } => "Reserved", - PermissionPriv::Active => "Active", - PermissionPriv::Frozen => "Frozen", - PermissionPriv::Disabled => "Disabled", + Reserved { .. } => "Reserved", + Active => "Active", + Frozen => "Frozen", + Disabled => "Disabled", } ) } @@ -235,13 +226,13 @@ pub mod diagnostics { impl fmt::Display for PermTransition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "from {} to {}", self.0, self.1) + write!(f, "from {} to {}", self.from, self.to) } } impl fmt::Display for Permission { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + write!(f, "{}", self.inner) } } @@ -251,7 +242,7 @@ pub mod diagnostics { // Make sure there are all of the same length as each other // and also as `diagnostics::DisplayFmtPermission.uninit` otherwise // alignment will be incorrect. - match self.0 { + match self.inner { Reserved { ty_is_freeze: true } => "Res", Reserved { ty_is_freeze: false } => "Re*", Active => "Act", @@ -269,9 +260,9 @@ pub mod diagnostics { /// to have write permissions, because that's what the diagnostics care about /// (otherwise `Reserved -> Frozen` would be considered a noop). pub fn summary(&self) -> &'static str { - assert!(Self::is_possible(self.0, self.1)); - match (self.0, self.1) { - (_, Active) => "an activation", + assert!(self.is_possible()); + match (self.from, self.to) { + (_, Active) => "the first write to a 2-phase borrowed mutable reference", (_, Frozen) => "a loss of write permissions", (Frozen, Disabled) => "a loss of read permissions", (_, Disabled) => "a loss of read and write permissions", @@ -279,6 +270,113 @@ pub mod diagnostics { unreachable!("Transition from {old:?} to {new:?} should never be possible"), } } + + /// Determines whether `self` is a relevant transition for the error `err`. + /// `self` will be a transition that happened to a tag some time before + /// that tag caused the error. + /// + /// Irrelevant events: + /// - modifications of write permissions when the error is related to read permissions + /// (on failed reads and protected `Frozen -> Disabled`, ignore `Reserved -> Active`, + /// `Reserved -> Frozen`, and `Active -> Frozen`) + /// - all transitions for attempts to deallocate strongly protected tags + /// + /// # Panics + /// + /// This function assumes that its arguments apply to the same location + /// and that they were obtained during a normal execution. It will panic otherwise. + /// - all transitions involved in `self` and `err` should be increasing + /// (Reserved < Active < Frozen < Disabled); + /// - between `self` and `err` the permission should also be increasing, + /// so all permissions inside `err` should be greater than `self.1`; + /// - `Active` and `Reserved` cannot cause an error due to insufficient permissions, + /// so `err` cannot be a `ChildAccessForbidden(_)` of either of them; + /// - `err` should not be `ProtectedDisabled(Disabled)`, because the protected + /// tag should not have been `Disabled` in the first place (if this occurs it means + /// we have unprotected tags that become protected) + pub(in super::super) fn is_relevant(&self, err: TransitionError) -> bool { + // NOTE: `super::super` is the visibility of `TransitionError` + assert!(self.is_possible()); + if self.is_noop() { + return false; + } + match err { + TransitionError::ChildAccessForbidden(insufficient) => { + // Show where the permission was gained then lost, + // but ignore unrelated permissions. + // This eliminates transitions like `Active -> Frozen` + // when the error is a failed `Read`. + match (self.to, insufficient.inner) { + (Frozen, Frozen) => true, + (Active, Frozen) => true, + (Disabled, Disabled) => true, + // A pointer being `Disabled` is a strictly stronger source of + // errors than it being `Frozen`. If we try to access a `Disabled`, + // then where it became `Frozen` (or `Active`) is the least of our concerns for now. + (Active | Frozen, Disabled) => false, + + // `Active` and `Reserved` have all permissions, so a + // `ChildAccessForbidden(Reserved | Active)` can never exist. + (_, Active) | (_, Reserved { .. }) => + unreachable!("this permission cannot cause an error"), + // No transition has `Reserved` as its `.to` unless it's a noop. + (Reserved { .. }, _) => unreachable!("self is a noop transition"), + // All transitions produced in normal executions (using `apply_access`) + // change permissions in the order `Reserved -> Active -> Frozen -> Disabled`. + // We assume that the error was triggered on the same location that + // the transition `self` applies to, so permissions found must be increasing + // in the order `self.from < self.to <= insufficient.inner` + (Disabled, Frozen) => + unreachable!("permissions between self and err must be increasing"), + } + } + TransitionError::ProtectedDisabled(before_disabled) => { + // Show how we got to the starting point of the forbidden transition, + // but ignore what came before. + // This eliminates transitions like `Reserved -> Active` + // when the error is a `Frozen -> Disabled`. + match (self.to, before_disabled.inner) { + // We absolutely want to know where it was activated. + (Active, Active) => true, + // And knowing where it became Frozen is also important. + (Frozen, Frozen) => true, + // If the error is a transition `Frozen -> Disabled`, then we don't really + // care whether before that was `Reserved -> Active -> Frozen` or + // `Reserved -> Frozen` or even `Frozen` directly. + // The error will only show either + // - created as Frozen, then Frozen -> Disabled is forbidden + // - created as Reserved, later became Frozen, then Frozen -> Disabled is forbidden + // In both cases the `Reserved -> Active` part is inexistant or irrelevant. + (Active, Frozen) => false, + + (_, Disabled) => + unreachable!( + "permission that results in Disabled should not itself be Disabled in the first place" + ), + // No transition has `Reserved` as its `.to` unless it's a noop. + (Reserved { .. }, _) => unreachable!("self is a noop transition"), + + // Permissions only evolve in the order `Reserved -> Active -> Frozen -> Disabled`, + // so permissions found must be increasing in the order + // `self.from < self.to <= forbidden.from < forbidden.to`. + (Disabled, Reserved { .. } | Active | Frozen) + | (Frozen, Reserved { .. } | Active) + | (Active, Reserved { .. }) => + unreachable!("permissions between self and err must be increasing"), + } + } + // We don't care because protectors evolve independently from + // permissions. + TransitionError::ProtectedDealloc => false, + } + } + + /// Endpoint of a transition. + /// Meant only for diagnostics, use `applied` in non-diagnostics + /// code, which also checks that the starting point matches the current state. + pub fn endpoint(&self) -> Permission { + Permission { inner: self.to } + } } } @@ -290,7 +388,7 @@ mod propagation_optimization_checks { pub use super::*; impl PermissionPriv { /// Enumerate all states - pub fn all() -> impl Iterator { + pub fn all() -> impl Iterator { vec![ Active, Reserved { ty_is_freeze: true }, @@ -302,9 +400,15 @@ mod propagation_optimization_checks { } } + impl Permission { + pub fn all() -> impl Iterator { + PermissionPriv::all().map(|inner| Self { inner }) + } + } + impl AccessKind { /// Enumerate all AccessKind. - pub fn all() -> impl Iterator { + pub fn all() -> impl Iterator { use AccessKind::*; [Read, Write].into_iter() } @@ -312,7 +416,7 @@ mod propagation_optimization_checks { impl AccessRelatedness { /// Enumerate all relative positions - pub fn all() -> impl Iterator { + pub fn all() -> impl Iterator { use AccessRelatedness::*; [This, StrictChildAccess, AncestorAccess, DistantAccess].into_iter() } @@ -341,7 +445,7 @@ mod propagation_optimization_checks { } #[test] - fn foreign_read_is_noop_after_write() { + fn foreign_read_is_noop_after_foreign_write() { use transition::*; let old_access = AccessKind::Write; let new_access = AccessKind::Read; diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs index 52947aa0f9fe2..5abf13229bbcc 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/tree.rs @@ -19,6 +19,7 @@ use rustc_target::abi::Size; use crate::borrow_tracker::tree_borrows::{ diagnostics::{self, NodeDebugInfo, TbError, TransitionError}, + perms::PermTransition, unimap::{UniEntry, UniIndex, UniKeyMap, UniValMap}, Permission, }; @@ -28,17 +29,19 @@ use crate::*; /// Data for a single *location*. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) struct LocationState { - /// This pointer's current permission - permission: Permission, - /// A location is initialized when it is child accessed for the first time, - /// and it then stays initialized forever. - /// Before initialization we still apply some preemptive transitions on - /// `permission` to know what to do in case it ever gets initialized, - /// but these can never cause any immediate UB. There can however be UB - /// the moment we attempt to initialize (i.e. child-access) because some - /// foreign access done between the creation and the initialization is - /// incompatible with child accesses. + /// A location is initialized when it is child-accessed for the first time (and the initial + /// retag initializes the location for the range covered by the type), and it then stays + /// initialized forever. + /// For initialized locations, "permission" is the current permission. However, for + /// uninitialized locations, we still need to track the "future initial permission": this will + /// start out to be `default_initial_perm`, but foreign accesses need to be taken into account. + /// Crucially however, while transitions to `Disabled` would usually be UB if this location is + /// protected, that is *not* the case for uninitialized locations. Instead we just have a latent + /// "future initial permission" of `Disabled`, causing UB only if an access is ever actually + /// performed. initialized: bool, + /// This pointer's current permission / future initial permission. + permission: Permission, /// Strongest foreign access whose effects have already been applied to /// this node and all its children since the last child access. /// This is `None` if the most recent access is a child access, @@ -69,6 +72,104 @@ impl LocationState { pub fn permission(&self) -> Permission { self.permission } + + /// Apply the effect of an access to one location, including + /// - applying `Permission::perform_access` to the inner `Permission`, + /// - emitting protector UB if the location is initialized, + /// - updating the initialized status (child accesses produce initialized locations). + fn perform_access( + &mut self, + access_kind: AccessKind, + rel_pos: AccessRelatedness, + protected: bool, + ) -> Result { + let old_perm = self.permission; + let transition = Permission::perform_access(access_kind, rel_pos, old_perm, protected) + .ok_or(TransitionError::ChildAccessForbidden(old_perm))?; + // Why do only initialized locations cause protector errors? + // Consider two mutable references `x`, `y` into disjoint parts of + // the same allocation. A priori, these may actually both be used to + // access the entire allocation, as long as only reads occur. However, + // a write to `y` needs to somehow record that `x` can no longer be used + // on that location at all. For these uninitialized locations (i.e., locations + // that haven't been accessed with `x` yet), we track the "future initial state": + // it defaults to whatever the initial state of the tag is, + // but the access to `y` moves that "future initial state" of `x` to `Disabled`. + // However, usually a `Reserved -> Disabled` transition would be UB due to the protector! + // So clearly protectors shouldn't fire for such "future initial state" transitions. + // + // See the test `two_mut_protected_same_alloc` in `tests/pass/tree_borrows/tree-borrows.rs` + // for an example of safe code that would be UB if we forgot to check `self.initialized`. + if protected && self.initialized && transition.produces_disabled() { + return Err(TransitionError::ProtectedDisabled(old_perm)); + } + self.permission = transition.applied(old_perm).unwrap(); + self.initialized |= !rel_pos.is_foreign(); + Ok(transition) + } + + // Helper to optimize the tree traversal. + // The optimization here consists of observing thanks to the tests + // `foreign_read_is_noop_after_foreign_write` and `all_transitions_idempotent`, + // that there are actually just three possible sequences of events that can occur + // in between two child accesses that produce different results. + // + // Indeed, + // - applying any number of foreign read accesses is the same as applying + // exactly one foreign read, + // - applying any number of foreign read or write accesses is the same + // as applying exactly one foreign write. + // therefore the three sequences of events that can produce different + // outcomes are + // - an empty sequence (`self.latest_foreign_access = None`) + // - a nonempty read-only sequence (`self.latest_foreign_access = Some(Read)`) + // - a nonempty sequence with at least one write (`self.latest_foreign_access = Some(Write)`) + // + // This function not only determines if skipping the propagation right now + // is possible, it also updates the internal state to keep track of whether + // the propagation can be skipped next time. + // It is a performance loss not to call this function when a foreign access occurs. + // It is unsound not to call this function when a child access occurs. + fn skip_if_known_noop( + &mut self, + access_kind: AccessKind, + rel_pos: AccessRelatedness, + ) -> ContinueTraversal { + if rel_pos.is_foreign() { + let new_access_noop = match (self.latest_foreign_access, access_kind) { + // Previously applied transition makes the new one a guaranteed + // noop in the two following cases: + // (1) justified by `foreign_read_is_noop_after_foreign_write` + (Some(AccessKind::Write), AccessKind::Read) => true, + // (2) justified by `all_transitions_idempotent` + (Some(old), new) if old == new => true, + // In all other cases there has been a recent enough + // child access that the effects of the new foreign access + // need to be applied to this subtree. + _ => false, + }; + if new_access_noop { + // Abort traversal if the new transition is indeed guaranteed + // to be noop. + // No need to update `self.latest_foreign_access`, + // the type of the current streak among nonempty read-only + // or nonempty with at least one write has not changed. + ContinueTraversal::SkipChildren + } else { + // Otherwise propagate this time, and also record the + // access that just occurred so that we can skip the propagation + // next time. + self.latest_foreign_access = Some(access_kind); + ContinueTraversal::Recurse + } + } else { + // A child access occurred, this breaks the streak of foreign + // accesses in a row and the sequence since the previous child access + // is now empty. + self.latest_foreign_access = None; + ContinueTraversal::Recurse + } + } } /// Tree structure with both parents and children since we want to be @@ -275,7 +376,7 @@ where { impl Tree { /// Create a new tree, with only a root pointer. pub fn new(root_tag: BorTag, size: Size, span: Span) -> Self { - let root_perm = Permission::new_root(); + let root_perm = Permission::new_active(); let mut tag_mapping = UniKeyMap::default(); let root_idx = tag_mapping.insert(root_tag); let nodes = { @@ -349,7 +450,14 @@ impl<'tcx> Tree { global: &GlobalState, span: Span, // diagnostics ) -> InterpResult<'tcx> { - self.perform_access(AccessKind::Write, tag, access_range, global, span)?; + self.perform_access( + AccessKind::Write, + tag, + access_range, + global, + span, + diagnostics::AccessCause::Dealloc, + )?; for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) { TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms } .traverse_parents_this_children_others( @@ -368,7 +476,7 @@ impl<'tcx> Tree { let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args; TbError { conflicting_info, - access_kind: AccessKind::Write, + access_cause: diagnostics::AccessCause::Dealloc, error_offset: perms_range.start, error_kind, accessed_info, @@ -380,18 +488,23 @@ impl<'tcx> Tree { Ok(()) } - /// Maps the following propagation procedure to each range: - /// - initialize if needed; - /// - compute new state after transition; - /// - check that there is no protector that would forbid this; - /// - record this specific location as accessed. + /// Map the per-node and per-location `LocationState::perform_access` + /// to each location of `access_range`, on every tag of the allocation. + /// + /// `LocationState::perform_access` will take care of raising transition + /// errors and updating the `initialized` status of each location, + /// this traversal adds to that: + /// - inserting into the map locations that do not exist yet, + /// - trimming the traversal, + /// - recording the history. pub fn perform_access( &mut self, access_kind: AccessKind, tag: BorTag, access_range: AllocRange, global: &GlobalState, - span: Span, // diagnostics + span: Span, // diagnostics + access_cause: diagnostics::AccessCause, // diagnostics ) -> InterpResult<'tcx> { for (perms_range, perms) in self.rperms.iter_mut(access_range.start, access_range.size) { TreeVisitor { nodes: &mut self.nodes, tag_mapping: &self.tag_mapping, perms } @@ -403,76 +516,34 @@ impl<'tcx> Tree { let old_state = perm.or_insert_with(|| LocationState::new(node.default_initial_perm)); - // Optimize the tree traversal. - // The optimization here consists of observing thanks to the tests - // `foreign_read_is_noop_after_write` and `all_transitions_idempotent` - // that if we apply twice in a row the effects of a foreign access - // we can skip some branches. - // "two foreign accesses in a row" occurs when `perm.latest_foreign_access` is `Some(_)` - // AND the `rel_pos` of the current access corresponds to a foreign access. - if rel_pos.is_foreign() { - let new_access_noop = - match (old_state.latest_foreign_access, access_kind) { - // Previously applied transition makes the new one a guaranteed - // noop in the two following cases: - // (1) justified by `foreign_read_is_noop_after_write` - (Some(AccessKind::Write), AccessKind::Read) => true, - // (2) justified by `all_transitions_idempotent` - (Some(old), new) if old == new => true, - // In all other cases there has been a recent enough - // child access that the effects of the new foreign access - // need to be applied to this subtree. - _ => false, - }; - if new_access_noop { - // Abort traversal if the new transition is indeed guaranteed - // to be noop. - return Ok(ContinueTraversal::SkipChildren); - } else { - // Otherwise propagate this time, and also record the - // access that just occurred so that we can skip the propagation - // next time. - old_state.latest_foreign_access = Some(access_kind); - } - } else { - // A child access occurred, this breaks the streak of "two foreign - // accesses in a row" and we reset this field. - old_state.latest_foreign_access = None; + match old_state.skip_if_known_noop(access_kind, rel_pos) { + ContinueTraversal::SkipChildren => + return Ok(ContinueTraversal::SkipChildren), + _ => {} } - let old_perm = old_state.permission; let protected = global.borrow().protected_tags.contains_key(&node.tag); let transition = - Permission::perform_access(access_kind, rel_pos, old_perm, protected) - .ok_or(TransitionError::ChildAccessForbidden(old_perm))?; - if protected - // Can't trigger Protector on uninitialized locations - && old_state.initialized - && !transition.is_allowed_by_protector() - { - return Err(TransitionError::ProtectedTransition(transition)); - } + old_state.perform_access(access_kind, rel_pos, protected)?; + // Record the event as part of the history if !transition.is_noop() { node.debug_info.history.push(diagnostics::Event { transition, - access_kind, is_foreign: rel_pos.is_foreign(), + access_cause, access_range, transition_range: perms_range.clone(), span, }); - old_state.permission = - transition.applied(old_state.permission).unwrap(); } - old_state.initialized |= !rel_pos.is_foreign(); Ok(ContinueTraversal::Recurse) }, |args: ErrHandlerArgs<'_, TransitionError>| -> InterpError<'tcx> { let ErrHandlerArgs { error_kind, conflicting_info, accessed_info } = args; TbError { conflicting_info, - access_kind, + access_cause, error_offset: perms_range.start, error_kind, accessed_info, @@ -594,3 +665,87 @@ impl AccessRelatedness { } } } + +#[cfg(test)] +mod commutation_tests { + use super::*; + impl LocationState { + pub fn all() -> impl Iterator { + // We keep `latest_foreign_access` at `None` as that's just a cache. + Permission::all().flat_map(|permission| { + [false, true].into_iter().map(move |initialized| { + Self { permission, initialized, latest_foreign_access: None } + }) + }) + } + } + + #[test] + #[rustfmt::skip] + // Exhaustive check that for any starting configuration loc, + // for any two read accesses r1 and r2, if `loc + r1 + r2` is not UB + // and results in `loc'`, then `loc + r2 + r1` is also not UB and results + // in the same final state `loc'`. + // This lets us justify arbitrary read-read reorderings. + fn all_read_accesses_commute() { + let kind = AccessKind::Read; + // Two of the four combinations of `AccessRelatedness` are trivial, + // but we might as well check them all. + for rel1 in AccessRelatedness::all() { + for rel2 in AccessRelatedness::all() { + // Any protector state works, but we can't move reads across function boundaries + // so the two read accesses occur under the same protector. + for &protected in &[true, false] { + for loc in LocationState::all() { + // Apply 1 then 2. Failure here means that there is UB in the source + // and we skip the check in the target. + let mut loc12 = loc; + let Ok(_) = loc12.perform_access(kind, rel1, protected) else { continue }; + let Ok(_) = loc12.perform_access(kind, rel2, protected) else { continue }; + + // If 1 followed by 2 succeeded, then 2 followed by 1 must also succeed... + let mut loc21 = loc; + loc21.perform_access(kind, rel2, protected).unwrap(); + loc21.perform_access(kind, rel1, protected).unwrap(); + + // ... and produce the same final result. + assert_eq!( + loc12, loc21, + "Read accesses {:?} followed by {:?} do not commute !", + rel1, rel2 + ); + } + } + } + } + } + + #[test] + #[rustfmt::skip] + // Ensure that of 2 accesses happen, one foreign and one a child, and we are protected, that we + // get UB unless they are both reads. + fn protected_enforces_noalias() { + for rel1 in AccessRelatedness::all() { + for rel2 in AccessRelatedness::all() { + if rel1.is_foreign() == rel2.is_foreign() { + // We want to check pairs of accesses where one is foreign and one is not. + continue; + } + for kind1 in AccessKind::all() { + for kind2 in AccessKind::all() { + for mut state in LocationState::all() { + let protected = true; + let Ok(_) = state.perform_access(kind1, rel1, protected) else { continue }; + let Ok(_) = state.perform_access(kind2, rel2, protected) else { continue }; + // If these were both allowed, it must have been two reads. + assert!( + kind1 == AccessKind::Read && kind2 == AccessKind::Read, + "failed to enforce noalias between two accesses that are not both reads" + ); + } + } + } + } + } + } +} diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs index f6252c43f9fe2..37125337d6f0e 100644 --- a/src/tools/miri/src/concurrency/data_race.rs +++ b/src/tools/miri/src/concurrency/data_race.rs @@ -472,7 +472,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { // This is fine with StackedBorrow and race checks because they don't concern metadata on // the *value* (including the associated provenance if this is an AtomicPtr) at this location. // Only metadata on the location itself is used. - let scalar = this.allow_data_races_ref(move |this| this.read_scalar(&place.into()))?; + let scalar = this.allow_data_races_ref(move |this| this.read_scalar(place))?; this.validate_overlapping_atomic(place)?; this.buffered_atomic_read(place, atomic, scalar, || { this.validate_atomic_load(place, atomic) @@ -490,7 +490,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { this.atomic_access_check(dest)?; this.validate_overlapping_atomic(dest)?; - this.allow_data_races_mut(move |this| this.write_scalar(val, &dest.into()))?; + this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?; this.validate_atomic_store(dest, atomic)?; // FIXME: it's not possible to get the value before write_scalar. A read_scalar will cause // side effects from a read the program did not perform. So we have to initialise @@ -513,12 +513,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { this.atomic_access_check(place)?; this.validate_overlapping_atomic(place)?; - let old = this.allow_data_races_mut(|this| this.read_immediate(&place.into()))?; + let old = this.allow_data_races_mut(|this| this.read_immediate(place))?; // Atomics wrap around on overflow. let val = this.binary_op(op, &old, rhs)?; let val = if neg { this.unary_op(mir::UnOp::Not, &val)? } else { val }; - this.allow_data_races_mut(|this| this.write_immediate(*val, &place.into()))?; + this.allow_data_races_mut(|this| this.write_immediate(*val, place))?; this.validate_atomic_rmw(place, atomic)?; @@ -538,8 +538,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { this.atomic_access_check(place)?; this.validate_overlapping_atomic(place)?; - let old = this.allow_data_races_mut(|this| this.read_scalar(&place.into()))?; - this.allow_data_races_mut(|this| this.write_scalar(new, &place.into()))?; + let old = this.allow_data_races_mut(|this| this.read_scalar(place))?; + this.allow_data_races_mut(|this| this.write_scalar(new, place))?; this.validate_atomic_rmw(place, atomic)?; @@ -560,7 +560,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { this.atomic_access_check(place)?; this.validate_overlapping_atomic(place)?; - let old = this.allow_data_races_mut(|this| this.read_immediate(&place.into()))?; + let old = this.allow_data_races_mut(|this| this.read_immediate(place))?; let lt = this.binary_op(mir::BinOp::Lt, &old, &rhs)?.to_scalar().to_bool()?; let new_val = if min { @@ -569,7 +569,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { if lt { &rhs } else { &old } }; - this.allow_data_races_mut(|this| this.write_immediate(**new_val, &place.into()))?; + this.allow_data_races_mut(|this| this.write_immediate(**new_val, place))?; this.validate_atomic_rmw(place, atomic)?; @@ -603,7 +603,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { // to read with the failure ordering and if successful then try again with the success // read ordering and write in the success case. // Read as immediate for the sake of `binary_op()` - let old = this.allow_data_races_mut(|this| this.read_immediate(&(place.into())))?; + let old = this.allow_data_races_mut(|this| this.read_immediate(place))?; // `binary_op` will bail if either of them is not a scalar. let eq = this.binary_op(mir::BinOp::Eq, &old, expect_old)?; // If the operation would succeed, but is "weak", fail some portion @@ -621,7 +621,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { // if successful, perform a full rw-atomic validation // otherwise treat this as an atomic load with the fail ordering. if cmpxchg_success { - this.allow_data_races_mut(|this| this.write_scalar(new, &place.into()))?; + this.allow_data_races_mut(|this| this.write_scalar(new, place))?; this.validate_atomic_rmw(place, success)?; this.buffered_atomic_rmw(new, place, success, old.to_scalar())?; } else { @@ -714,7 +714,8 @@ impl VClockAlloc { MiriMemoryKind::Rust | MiriMemoryKind::Miri | MiriMemoryKind::C - | MiriMemoryKind::WinHeap, + | MiriMemoryKind::WinHeap + | MiriMemoryKind::Mmap, ) | MemoryKind::Stack => { let (alloc_index, clocks) = global.current_thread_state(thread_mgr); diff --git a/src/tools/miri/src/concurrency/init_once.rs b/src/tools/miri/src/concurrency/init_once.rs index 1f57e8b2b0abd..71582c75eae57 100644 --- a/src/tools/miri/src/concurrency/init_once.rs +++ b/src/tools/miri/src/concurrency/init_once.rs @@ -2,6 +2,7 @@ use std::collections::VecDeque; use std::num::NonZeroU32; use rustc_index::Idx; +use rustc_middle::ty::layout::TyAndLayout; use super::sync::EvalContextExtPriv as _; use super::thread::MachineCallback; @@ -94,10 +95,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn init_once_get_or_create_id( &mut self, lock_op: &OpTy<'tcx, Provenance>, + lock_layout: TyAndLayout<'tcx>, offset: u64, ) -> InterpResult<'tcx, InitOnceId> { let this = self.eval_context_mut(); - this.init_once_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset)) + this.init_once_get_or_create(|ecx, next_id| { + ecx.get_or_create_id(next_id, lock_op, lock_layout, offset) + }) } /// Provides the closure with the next InitOnceId. Creates that InitOnce if the closure returns None, diff --git a/src/tools/miri/src/concurrency/range_object_map.rs b/src/tools/miri/src/concurrency/range_object_map.rs index 89c009933bb03..859eb4bbb60d2 100644 --- a/src/tools/miri/src/concurrency/range_object_map.rs +++ b/src/tools/miri/src/concurrency/range_object_map.rs @@ -42,30 +42,19 @@ impl RangeObjectMap { /// in an existing allocation, then returns Err containing the position /// where such allocation should be inserted fn find_offset(&self, offset: Size) -> Result { - // We do a binary search. - let mut left = 0usize; // inclusive - let mut right = self.v.len(); // exclusive - loop { - if left == right { - // No element contains the given offset. But the - // position is where such element should be placed at. - return Err(left); - } - let candidate = left.checked_add(right).unwrap() / 2; - let elem = &self.v[candidate]; + self.v.binary_search_by(|elem| -> std::cmp::Ordering { if offset < elem.range.start { // We are too far right (offset is further left). - debug_assert!(candidate < right); // we are making progress - right = candidate; + // (`Greater` means that `elem` is greater than the desired target.) + std::cmp::Ordering::Greater } else if offset >= elem.range.end() { // We are too far left (offset is further right). - debug_assert!(candidate >= left); // we are making progress - left = candidate + 1; + std::cmp::Ordering::Less } else { // This is it! - return Ok(candidate); + std::cmp::Ordering::Equal } - } + }) } /// Determines whether a given access on `range` overlaps with diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs index f37a2fd2cd5b6..62f6d57ef36e1 100644 --- a/src/tools/miri/src/concurrency/sync.rs +++ b/src/tools/miri/src/concurrency/sync.rs @@ -6,6 +6,7 @@ use log::trace; use rustc_data_structures::fx::FxHashMap; use rustc_index::{Idx, IndexVec}; +use rustc_middle::ty::layout::TyAndLayout; use super::init_once::InitOnce; use super::vector_clock::VClock; @@ -200,11 +201,12 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>: &mut self, next_id: Id, lock_op: &OpTy<'tcx, Provenance>, + lock_layout: TyAndLayout<'tcx>, offset: u64, ) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); let value_place = - this.deref_operand_and_offset(lock_op, offset, this.machine.layouts.u32)?; + this.deref_pointer_and_offset(lock_op, offset, lock_layout, this.machine.layouts.u32)?; // Since we are lazy, this update has to be atomic. let (old, success) = this @@ -278,28 +280,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn mutex_get_or_create_id( &mut self, lock_op: &OpTy<'tcx, Provenance>, + lock_layout: TyAndLayout<'tcx>, offset: u64, ) -> InterpResult<'tcx, MutexId> { let this = self.eval_context_mut(); - this.mutex_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset)) + this.mutex_get_or_create(|ecx, next_id| { + ecx.get_or_create_id(next_id, lock_op, lock_layout, offset) + }) } fn rwlock_get_or_create_id( &mut self, lock_op: &OpTy<'tcx, Provenance>, + lock_layout: TyAndLayout<'tcx>, offset: u64, ) -> InterpResult<'tcx, RwLockId> { let this = self.eval_context_mut(); - this.rwlock_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset)) + this.rwlock_get_or_create(|ecx, next_id| { + ecx.get_or_create_id(next_id, lock_op, lock_layout, offset) + }) } fn condvar_get_or_create_id( &mut self, lock_op: &OpTy<'tcx, Provenance>, + lock_layout: TyAndLayout<'tcx>, offset: u64, ) -> InterpResult<'tcx, CondvarId> { let this = self.eval_context_mut(); - this.condvar_get_or_create(|ecx, next_id| ecx.get_or_create_id(next_id, lock_op, offset)) + this.condvar_get_or_create(|ecx, next_id| { + ecx.get_or_create_id(next_id, lock_op, lock_layout, offset) + }) } #[inline] diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs index d85cac7bcbb58..9c11ad85aefd3 100644 --- a/src/tools/miri/src/concurrency/thread.rs +++ b/src/tools/miri/src/concurrency/thread.rs @@ -133,10 +133,15 @@ pub struct Thread<'mir, 'tcx> { /// The join status. join_status: ThreadJoinStatus, - /// The temporary used for storing the argument of - /// the call to `miri_start_panic` (the panic payload) when unwinding. + /// Stack of active panic payloads for the current thread. Used for storing + /// the argument of the call to `miri_start_panic` (the panic payload) when unwinding. /// This is pointer-sized, and matches the `Payload` type in `src/libpanic_unwind/miri.rs`. - pub(crate) panic_payload: Option>, + /// + /// In real unwinding, the payload gets passed as an argument to the landing pad, + /// which then forwards it to 'Resume'. However this argument is implicit in MIR, + /// so we have to store it out-of-band. When there are multiple active unwinds, + /// the innermost one is always caught first, so we can store them as a stack. + pub(crate) panic_payloads: Vec>, /// Last OS error location in memory. It is a 32-bit integer. pub(crate) last_error: Option>, @@ -206,7 +211,7 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> { stack: Vec::new(), top_user_relevant_frame: None, join_status: ThreadJoinStatus::Joinable, - panic_payload: None, + panic_payloads: Vec::new(), last_error: None, on_stack_empty, } @@ -216,7 +221,7 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> { impl VisitTags for Thread<'_, '_> { fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) { let Thread { - panic_payload, + panic_payloads: panic_payload, last_error, stack, top_user_relevant_frame: _, @@ -226,7 +231,9 @@ impl VisitTags for Thread<'_, '_> { on_stack_empty: _, // we assume the closure captures no GC-relevant state } = self; - panic_payload.visit_tags(visit); + for payload in panic_payload { + payload.visit_tags(visit); + } last_error.visit_tags(visit); for frame in stack { frame.visit_tags(visit) @@ -827,7 +834,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(thread_info_place) = thread { this.write_scalar( Scalar::from_uint(new_thread_id.to_u32(), thread_info_place.layout.size), - &thread_info_place.into(), + &thread_info_place, )?; } diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs index a6e67ef8699df..fa93c9e00b14a 100644 --- a/src/tools/miri/src/concurrency/vector_clock.rs +++ b/src/tools/miri/src/concurrency/vector_clock.rs @@ -9,7 +9,7 @@ use std::{ /// A vector clock index, this is associated with a thread id /// but in some cases one vector index may be shared with -/// multiple thread ids if it safe to do so. +/// multiple thread ids if it's safe to do so. #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct VectorIdx(u32); diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index e3f81a78eeaf6..f3285ccf917fc 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -3,6 +3,8 @@ use std::num::NonZeroU64; use log::trace; +use rustc_const_eval::ReportErrorExt; +use rustc_errors::DiagnosticMessage; use rustc_span::{source_map::DUMMY_SP, SpanData, Symbol}; use rustc_target::abi::{Align, Size}; @@ -20,7 +22,7 @@ pub enum TerminationInfo { UnsupportedInIsolation(String), StackedBorrowsUb { msg: String, - help: Option, + help: Vec, history: Option, }, TreeBorrowsUb { @@ -83,7 +85,25 @@ impl fmt::Display for TerminationInfo { } } -impl MachineStopType for TerminationInfo {} +impl fmt::Debug for TerminationInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{self}") + } +} + +impl MachineStopType for TerminationInfo { + fn diagnostic_message(&self) -> DiagnosticMessage { + self.to_string().into() + } + fn add_args( + self: Box, + _: &mut dyn FnMut( + std::borrow::Cow<'static, str>, + rustc_errors::DiagnosticArgValue<'static>, + ), + ) { + } +} /// Miri specific diagnostics pub enum NonHaltingDiagnostic { @@ -204,11 +224,10 @@ pub fn report_error<'tcx, 'mir>( (None, format!("or pass `-Zmiri-isolation-error=warn` to configure Miri to return an error code from isolated operations (if supported for that operation) and continue with a warning")), ], StackedBorrowsUb { help, history, .. } => { - let url = "https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md"; msg.extend(help.clone()); let mut helps = vec![ (None, format!("this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental")), - (None, format!("see {url} for further information")), + (None, format!("see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information")), ]; if let Some(TagHistory {created, invalidated, protected}) = history.clone() { helps.push((Some(created.1), created.0)); @@ -254,6 +273,8 @@ pub fn report_error<'tcx, 'mir>( } else { #[rustfmt::skip] let title = match e.kind() { + UndefinedBehavior(UndefinedBehaviorInfo::ValidationError(e)) if matches!(e.kind, ValidationErrorKind::PointerAsInt { .. } | ValidationErrorKind::PartialPointer) => + bug!("This validation error should be impossible in Miri: {:?}", e.kind), UndefinedBehavior(_) => "Undefined Behavior", ResourceExhaustion(_) => @@ -283,11 +304,21 @@ pub fn report_error<'tcx, 'mir>( (None, format!("this usually indicates that your program performed an invalid operation and caused Undefined Behavior")), (None, format!("but due to `-Zmiri-symbolic-alignment-check`, alignment errors can also be false positives")), ], - UndefinedBehavior(_) => - vec![ + UndefinedBehavior(info) => { + let mut helps = vec![ (None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")), (None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")), - ], + ]; + if let UndefinedBehaviorInfo::PointerUseAfterFree(alloc_id, _) | UndefinedBehaviorInfo::PointerOutOfBounds { alloc_id, .. } = info { + if let Some(span) = ecx.machine.allocated_span(*alloc_id) { + helps.push((Some(span), format!("{:?} was allocated here:", alloc_id))); + } + if let Some(span) = ecx.machine.deallocated_span(*alloc_id) { + helps.push((Some(span), format!("{:?} was deallocated here:", alloc_id))); + } + } + helps + } InvalidProgram( InvalidProgramInfo::AlreadyReported(_) ) => { @@ -302,11 +333,34 @@ pub fn report_error<'tcx, 'mir>( let stacktrace = ecx.generate_stacktrace(); let (stacktrace, was_pruned) = prune_stacktrace(stacktrace, &ecx.machine); - e.print_backtrace(); - msg.insert(0, e.to_string()); + let (e, backtrace) = e.into_parts(); + backtrace.print_backtrace(); + + // We want to dump the allocation if this is `InvalidUninitBytes`. Since `add_args` consumes + // the `InterpError`, we extract the variables it before that. + let extra = match e { + UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some((alloc_id, access)))) => + Some((alloc_id, access)), + _ => None, + }; + + // FIXME(fee1-dead), HACK: we want to use the error as title therefore we can just extract the + // label and arguments from the InterpError. + let e = { + let handler = &ecx.tcx.sess.parse_sess.span_diagnostic; + let mut diag = ecx.tcx.sess.struct_allow(""); + let msg = e.diagnostic_message(); + e.add_args(handler, &mut diag); + let s = handler.eagerly_translate_to_string(msg, diag.args()); + diag.cancel(); + s + }; + + msg.insert(0, e); + report_msg( DiagLevel::Error, - &if let Some(title) = title { format!("{title}: {}", msg[0]) } else { msg[0].clone() }, + if let Some(title) = title { format!("{title}: {}", msg[0]) } else { msg[0].clone() }, msg, vec![], helps, @@ -332,15 +386,12 @@ pub fn report_error<'tcx, 'mir>( } // Extra output to help debug specific issues. - match e.kind() { - UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some((alloc_id, access)))) => { - eprintln!( - "Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:", - range = access.uninit, - ); - eprintln!("{:?}", ecx.dump_alloc(*alloc_id)); - } - _ => {} + if let Some((alloc_id, access)) = extra { + eprintln!( + "Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:", + range = access.bad, + ); + eprintln!("{:?}", ecx.dump_alloc(alloc_id)); } None @@ -359,7 +410,7 @@ pub fn report_leaks<'mir, 'tcx>( any_pruned |= pruned; report_msg( DiagLevel::Error, - &format!( + format!( "memory leaked: {id:?} ({}, size: {:?}, align: {:?}), allocated here:", kind, alloc.size().bytes(), @@ -386,7 +437,7 @@ pub fn report_leaks<'mir, 'tcx>( /// additional `span_label` or `note` call. pub fn report_msg<'tcx>( diag_level: DiagLevel, - title: &str, + title: String, span_msg: Vec, notes: Vec<(Option, String)>, helps: Vec<(Option, String)>, @@ -438,12 +489,15 @@ pub fn report_msg<'tcx>( // Add visual separator before backtrace. err.note(if extra_span { "BACKTRACE (of the first span):" } else { "BACKTRACE:" }); } + + let (mut err, handler) = err.into_diagnostic().unwrap(); + // Add backtrace for (idx, frame_info) in stacktrace.iter().enumerate() { let is_local = machine.is_local(frame_info); // No span for non-local frames and the first frame (which is the error site). if is_local && idx > 0 { - err.span_note(frame_info.span, frame_info.to_string()); + err.eager_subdiagnostic(handler, frame_info.as_note(machine.tcx)); } else { let sm = sess.source_map(); let span = sm.span_to_embeddable_string(frame_info.span); @@ -451,7 +505,7 @@ pub fn report_msg<'tcx>( } } - err.emit(); + handler.emit_diagnostic(&mut err); } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { @@ -463,15 +517,16 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { let (stacktrace, _was_pruned) = prune_stacktrace(stacktrace, self); let (title, diag_level) = match &e { - RejectedIsolatedOp(_) => ("operation rejected by isolation", DiagLevel::Warning), - Int2Ptr { .. } => ("integer-to-pointer cast", DiagLevel::Warning), + RejectedIsolatedOp(_) => + ("operation rejected by isolation".to_string(), DiagLevel::Warning), + Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning), CreatedPointerTag(..) | PoppedPointerTag(..) | CreatedCallId(..) | CreatedAlloc(..) | FreedAlloc(..) | ProgressReport { .. } - | WeakMemoryOutdatedLoad => ("tracking was triggered", DiagLevel::Note), + | WeakMemoryOutdatedLoad => ("tracking was triggered".to_string(), DiagLevel::Note), }; let msg = match &e { @@ -571,7 +626,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let stacktrace = this.generate_stacktrace(); report_msg( DiagLevel::Note, - "the place in the program where the ICE was triggered", + "the place in the program where the ICE was triggered".to_string(), vec![], vec![], vec![], diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs index 43d8f221ce3d2..88e7d5386db79 100644 --- a/src/tools/miri/src/eval.rs +++ b/src/tools/miri/src/eval.rs @@ -8,6 +8,7 @@ use std::task::Poll; use std::thread; use log::info; +use rustc_middle::ty::Ty; use crate::borrow_tracker::RetagFields; use crate::diagnostics::report_leaks; @@ -90,6 +91,10 @@ pub struct MiriConfig { pub validate: bool, /// Determines if Stacked Borrows or Tree Borrows is enabled. pub borrow_tracker: Option, + /// Whether `core::ptr::Unique` receives special treatment. + /// If `true` then `Unique` is reborrowed with its own new tag and permission, + /// otherwise `Unique` is just another raw pointer. + pub unique_is_unique: bool, /// Controls alignment checking. pub check_alignment: AlignmentCheck, /// Controls function [ABI](Abi) checking. @@ -156,6 +161,7 @@ impl Default for MiriConfig { env: vec![], validate: true, borrow_tracker: Some(BorrowTrackerMethod::StackedBorrows), + unique_is_unique: false, check_alignment: AlignmentCheck::Int, check_abi: true, isolated_op: IsolatedOp::Reject(RejectOpWith::Abort), @@ -177,7 +183,7 @@ impl Default for MiriConfig { mute_stdout_stderr: false, preemption_rate: 0.01, // 1% report_progress: None, - retag_fields: RetagFields::OnlyScalar, + retag_fields: RetagFields::Yes, external_so_file: None, gc_interval: 10_000, num_cpus: 1, @@ -240,7 +246,7 @@ impl MainThreadState { this.machine.main_fn_ret_place.unwrap().ptr, this.machine.layouts.isize, ); - let exit_code = this.read_target_isize(&ret_place.into())?; + let exit_code = this.read_target_isize(&ret_place)?; // Need to call this ourselves since we are not going to return to the scheduler // loop, and we want the main thread TLS to not show up as memory leaks. this.terminate_active_thread()?; @@ -295,11 +301,11 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( // Third argument (`argv`): created from `config.args`. let argv = { // Put each argument in memory, collect pointers. - let mut argvs = Vec::>::new(); + let mut argvs = Vec::>::with_capacity(config.args.len()); for arg in config.args.iter() { // Make space for `0` terminator. let size = u64::try_from(arg.len()).unwrap().checked_add(1).unwrap(); - let arg_type = tcx.mk_array(tcx.types.u8, size); + let arg_type = Ty::new_array(tcx, tcx.types.u8, size); let arg_place = ecx.allocate(ecx.layout_of(arg_type)?, MiriMemoryKind::Machine.into())?; ecx.write_os_str_to_c_str(OsStr::new(arg), arg_place.ptr, size)?; @@ -307,13 +313,15 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( argvs.push(arg_place.to_ref(&ecx)); } // Make an array with all these pointers, in the Miri memory. - let argvs_layout = ecx.layout_of( - tcx.mk_array(tcx.mk_imm_ptr(tcx.types.u8), u64::try_from(argvs.len()).unwrap()), - )?; + let argvs_layout = ecx.layout_of(Ty::new_array( + tcx, + Ty::new_imm_ptr(tcx, tcx.types.u8), + u64::try_from(argvs.len()).unwrap(), + ))?; let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?; for (idx, arg) in argvs.into_iter().enumerate() { - let place = ecx.mplace_field(&argvs_place, idx)?; - ecx.write_immediate(arg, &place.into())?; + let place = ecx.project_field(&argvs_place, idx)?; + ecx.write_immediate(arg, &place)?; } ecx.mark_immutable(&argvs_place); // A pointer to that place is the 3rd argument for main. @@ -322,15 +330,15 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( { let argc_place = ecx.allocate(ecx.machine.layouts.isize, MiriMemoryKind::Machine.into())?; - ecx.write_scalar(argc, &argc_place.into())?; + ecx.write_scalar(argc, &argc_place)?; ecx.mark_immutable(&argc_place); ecx.machine.argc = Some(*argc_place); let argv_place = ecx.allocate( - ecx.layout_of(tcx.mk_imm_ptr(tcx.types.unit))?, + ecx.layout_of(Ty::new_imm_ptr(tcx, tcx.types.unit))?, MiriMemoryKind::Machine.into(), )?; - ecx.write_immediate(argv, &argv_place.into())?; + ecx.write_immediate(argv, &argv_place)?; ecx.mark_immutable(&argv_place); ecx.machine.argv = Some(*argv_place); } @@ -339,14 +347,15 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( // Construct a command string with all the arguments. let cmd_utf16: Vec = args_to_utf16_command_string(config.args.iter()); - let cmd_type = tcx.mk_array(tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap()); + let cmd_type = + Ty::new_array(tcx, tcx.types.u16, u64::try_from(cmd_utf16.len()).unwrap()); let cmd_place = ecx.allocate(ecx.layout_of(cmd_type)?, MiriMemoryKind::Machine.into())?; ecx.machine.cmd_line = Some(*cmd_place); // Store the UTF-16 string. We just allocated so we know the bounds are fine. for (idx, &c) in cmd_utf16.iter().enumerate() { - let place = ecx.mplace_field(&cmd_place, idx)?; - ecx.write_scalar(Scalar::from_u16(c), &place.into())?; + let place = ecx.project_field(&cmd_place, idx)?; + ecx.write_scalar(Scalar::from_u16(c), &place)?; } ecx.mark_immutable(&cmd_place); } @@ -360,14 +369,18 @@ pub fn create_ecx<'mir, 'tcx: 'mir>( match entry_type { EntryFnType::Main { .. } => { - let start_id = tcx.lang_items().start_fn().unwrap(); + let start_id = tcx.lang_items().start_fn().unwrap_or_else(|| { + tcx.sess.fatal( + "could not find start function. Make sure the entry point is marked with `#[start]`." + ); + }); let main_ret_ty = tcx.fn_sig(entry_id).no_bound_vars().unwrap().output(); let main_ret_ty = main_ret_ty.no_bound_vars().unwrap(); let start_instance = ty::Instance::resolve( tcx, ty::ParamEnv::reveal_all(), start_id, - tcx.mk_substs(&[ty::subst::GenericArg::from(main_ret_ty)]), + tcx.mk_args(&[ty::GenericArg::from(main_ret_ty)]), ) .unwrap() .unwrap(); @@ -422,8 +435,9 @@ pub fn eval_entry<'tcx>( let mut ecx = match create_ecx(tcx, entry_id, entry_type, &config) { Ok(v) => v, Err(err) => { - err.print_backtrace(); - panic!("Miri initialization error: {}", err.kind()) + let (kind, backtrace) = err.into_parts(); + backtrace.print_backtrace(); + panic!("Miri initialization error: {kind:?}") } }; diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index a2b49e6f219d3..b5cc5c7e4867b 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -1,6 +1,5 @@ pub mod convert; -use std::any::Any; use std::cmp; use std::iter; use std::num::NonZeroUsize; @@ -10,38 +9,21 @@ use log::trace; use rustc_hir::def::{DefKind, Namespace}; use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rustc_index::IndexVec; use rustc_middle::mir; use rustc_middle::ty::{ self, - layout::{LayoutOf, TyAndLayout}, - List, TyCtxt, + layout::{IntegerExt as _, LayoutOf, TyAndLayout}, + List, Ty, TyCtxt, }; use rustc_span::{def_id::CrateNum, sym, Span, Symbol}; -use rustc_target::abi::{Align, FieldsShape, Size, Variants}; +use rustc_target::abi::{Align, FieldIdx, FieldsShape, Integer, Size, Variants}; use rustc_target::spec::abi::Abi; use rand::RngCore; use crate::*; -/// A trait to work around not having trait object upcasting: -/// Add `AsAny` as supertrait and your trait objects can be turned into `&dyn Any` on which you can -/// then call `downcast`. -pub trait AsAny: Any { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; -} -impl AsAny for T { - #[inline(always)] - fn as_any(&self) -> &dyn Any { - self - } - #[inline(always)] - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - // This mapping should match `decode_error_kind` in // . const UNIX_IO_ERROR_TABLE: &[(&str, std::io::ErrorKind)] = { @@ -162,11 +144,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let instance = this.resolve_path(path, Namespace::ValueNS); let cid = GlobalId { instance, promoted: None }; // We don't give a span -- this isn't actually used directly by the program anyway. - let const_val = this - .eval_global(cid, None) - .unwrap_or_else(|err| panic!("failed to evaluate required Rust item: {path:?}\n{err}")); - this.read_scalar(&const_val.into()) - .unwrap_or_else(|err| panic!("failed to read required Rust item: {path:?}\n{err}")) + let const_val = this.eval_global(cid, None).unwrap_or_else(|err| { + panic!("failed to evaluate required Rust item: {path:?}\n{err:?}") + }); + this.read_scalar(&const_val) + .unwrap_or_else(|err| panic!("failed to read required Rust item: {path:?}\n{err:?}")) } /// Helper function to get a `libc` constant as a `Scalar`. @@ -229,20 +211,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.layout_of(ty).unwrap() } - /// Project to the given *named* field of the mplace (which must be a struct or union type). - fn mplace_field_named( + /// Project to the given *named* field (which must be a struct or union type). + fn project_field_named>( &self, - mplace: &MPlaceTy<'tcx, Provenance>, + base: &P, name: &str, - ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { + ) -> InterpResult<'tcx, P> { let this = self.eval_context_ref(); - let adt = mplace.layout.ty.ty_adt_def().unwrap(); + let adt = base.layout().ty.ty_adt_def().unwrap(); for (idx, field) in adt.non_enum_variant().fields.iter().enumerate() { if field.name.as_str() == name { - return this.mplace_field(mplace, idx); + return this.project_field(base, idx); } } - bug!("No field named {} in type {}", name, mplace.layout.ty); + bug!("No field named {} in type {}", name, base.layout().ty); } /// Write an int of the appropriate size to `dest`. The target type may be signed or unsigned, @@ -251,13 +233,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn write_int( &mut self, i: impl Into, - dest: &PlaceTy<'tcx, Provenance>, + dest: &impl Writeable<'tcx, Provenance>, ) -> InterpResult<'tcx> { - assert!(dest.layout.abi.is_scalar(), "write_int on non-scalar type {}", dest.layout.ty); - let val = if dest.layout.abi.is_signed() { - Scalar::from_int(i, dest.layout.size) + assert!(dest.layout().abi.is_scalar(), "write_int on non-scalar type {}", dest.layout().ty); + let val = if dest.layout().abi.is_signed() { + Scalar::from_int(i, dest.layout().size) } else { - Scalar::from_uint(u64::try_from(i.into()).unwrap(), dest.layout.size) + Scalar::from_uint(u64::try_from(i.into()).unwrap(), dest.layout().size) }; self.eval_context_mut().write_scalar(val, dest) } @@ -266,12 +248,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn write_int_fields( &mut self, values: &[i128], - dest: &MPlaceTy<'tcx, Provenance>, + dest: &impl Writeable<'tcx, Provenance>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); for (idx, &val) in values.iter().enumerate() { - let field = this.mplace_field(dest, idx)?; - this.write_int(val, &field.into())?; + let field = this.project_field(dest, idx)?; + this.write_int(val, &field)?; } Ok(()) } @@ -280,18 +262,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn write_int_fields_named( &mut self, values: &[(&str, i128)], - dest: &MPlaceTy<'tcx, Provenance>, + dest: &impl Writeable<'tcx, Provenance>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); for &(name, val) in values.iter() { - let field = this.mplace_field_named(dest, name)?; - this.write_int(val, &field.into())?; + let field = this.project_field_named(dest, name)?; + this.write_int(val, &field)?; } Ok(()) } /// Write a 0 of the appropriate size to `dest`. - fn write_null(&mut self, dest: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { + fn write_null(&mut self, dest: &impl Writeable<'tcx, Provenance>) -> InterpResult<'tcx> { self.write_int(0, dest) } @@ -301,8 +283,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } /// Get the `Place` for a local - fn local_place(&mut self, local: mir::Local) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> { - let this = self.eval_context_mut(); + fn local_place(&self, local: mir::Local) -> InterpResult<'tcx, PlaceTy<'tcx, Provenance>> { + let this = self.eval_context_ref(); let place = mir::Place { local, projection: List::empty() }; this.eval_place(place) } @@ -336,7 +318,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Call a function: Push the stack frame and pass the arguments. /// For now, arguments must be scalars (so that the caller does not have to know the layout). /// - /// If you do not provie a return place, a dangling zero-sized place will be created + /// If you do not provide a return place, a dangling zero-sized place will be created /// for your convenience. fn call_function( &mut self, @@ -479,6 +461,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { self.ecx } + fn aggregate_field_order(memory_index: &IndexVec, idx: usize) -> usize { + // We need to do an *inverse* lookup: find the field that has position `idx` in memory order. + for (src_field, &mem_pos) in memory_index.iter_enumerated() { + if mem_pos as usize == idx { + return src_field.as_usize(); + } + } + panic!("invalid `memory_index`, could not find {}-th field in memory order", idx); + } + // Hook to detect `UnsafeCell`. fn visit_value(&mut self, v: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> { trace!("UnsafeCellVisitor: {:?} {:?}", *v, v.layout.ty); @@ -524,33 +516,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } - // Make sure we visit aggregates in increasing offset order. - fn visit_aggregate( - &mut self, - place: &MPlaceTy<'tcx, Provenance>, - fields: impl Iterator>>, - ) -> InterpResult<'tcx> { - match place.layout.fields { - FieldsShape::Array { .. } => { - // For the array layout, we know the iterator will yield sorted elements so - // we can avoid the allocation. - self.walk_aggregate(place, fields) - } - FieldsShape::Arbitrary { .. } => { - // Gather the subplaces and sort them before visiting. - let mut places = fields - .collect::>>>()?; - // we just compare offsets, the abs. value never matters - places.sort_by_key(|place| place.ptr.addr()); - self.walk_aggregate(place, places.into_iter().map(Ok)) - } - FieldsShape::Union { .. } | FieldsShape::Primitive => { - // Uh, what? - bug!("unions/primitives are not aggregates we should ever visit") - } - } - } - fn visit_union( &mut self, _v: &MPlaceTy<'tcx, Provenance>, @@ -616,14 +581,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// necessary. fn last_error_place(&mut self) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { let this = self.eval_context_mut(); - if let Some(errno_place) = this.active_thread_ref().last_error { - Ok(errno_place) + if let Some(errno_place) = this.active_thread_ref().last_error.as_ref() { + Ok(errno_place.clone()) } else { // Allocate new place, set initial value to 0. let errno_layout = this.machine.layouts.u32; let errno_place = this.allocate(errno_layout, MiriMemoryKind::Machine.into())?; - this.write_scalar(Scalar::from_u32(0), &errno_place.into())?; - this.active_thread_mut().last_error = Some(errno_place); + this.write_scalar(Scalar::from_u32(0), &errno_place)?; + this.active_thread_mut().last_error = Some(errno_place.clone()); Ok(errno_place) } } @@ -632,14 +597,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn set_last_error(&mut self, scalar: Scalar) -> InterpResult<'tcx> { let this = self.eval_context_mut(); let errno_place = this.last_error_place()?; - this.write_scalar(scalar, &errno_place.into()) + this.write_scalar(scalar, &errno_place) } /// Gets the last error variable. fn get_last_error(&mut self) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); let errno_place = this.last_error_place()?; - this.read_scalar(&errno_place.into()) + this.read_scalar(&errno_place) } /// This function tries to produce the most similar OS error from the `std::io::ErrorKind` @@ -730,44 +695,78 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } + /// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type + fn deref_pointer_as( + &self, + op: &impl Readable<'tcx, Provenance>, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { + let this = self.eval_context_ref(); + let ptr = this.read_pointer(op)?; + + let mplace = MPlaceTy::from_aligned_ptr(ptr, layout); + + this.check_mplace(&mplace)?; + + Ok(mplace) + } + + /// Deref' a pointer *without* checking that the place is dereferenceable. + fn deref_pointer_unchecked( + &self, + val: &ImmTy<'tcx, Provenance>, + layout: TyAndLayout<'tcx>, + ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { + let this = self.eval_context_ref(); + let mut mplace = this.ref_to_mplace(val)?; + + mplace.layout = layout; + mplace.align = layout.align.abi; + + Ok(mplace) + } + /// Calculates the MPlaceTy given the offset and layout of an access on an operand - fn deref_operand_and_offset( + fn deref_pointer_and_offset( &self, - op: &OpTy<'tcx, Provenance>, + op: &impl Readable<'tcx, Provenance>, offset: u64, - layout: TyAndLayout<'tcx>, + base_layout: TyAndLayout<'tcx>, + value_layout: TyAndLayout<'tcx>, ) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> { let this = self.eval_context_ref(); - let op_place = this.deref_operand(op)?; // FIXME: we still deref with the original type! + let op_place = this.deref_pointer_as(op, base_layout)?; let offset = Size::from_bytes(offset); // Ensure that the access is within bounds. - assert!(op_place.layout.size >= offset + layout.size); - let value_place = op_place.offset(offset, layout, this)?; + assert!(base_layout.size >= offset + value_layout.size); + let value_place = op_place.offset(offset, value_layout, this)?; Ok(value_place) } - fn read_scalar_at_offset( + fn deref_pointer_and_read( &self, - op: &OpTy<'tcx, Provenance>, + op: &impl Readable<'tcx, Provenance>, offset: u64, - layout: TyAndLayout<'tcx>, + base_layout: TyAndLayout<'tcx>, + value_layout: TyAndLayout<'tcx>, ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_ref(); - let value_place = this.deref_operand_and_offset(op, offset, layout)?; - this.read_scalar(&value_place.into()) + let value_place = this.deref_pointer_and_offset(op, offset, base_layout, value_layout)?; + this.read_scalar(&value_place) } - fn write_scalar_at_offset( + fn deref_pointer_and_write( &mut self, - op: &OpTy<'tcx, Provenance>, + op: &impl Readable<'tcx, Provenance>, offset: u64, value: impl Into>, - layout: TyAndLayout<'tcx>, + base_layout: TyAndLayout<'tcx>, + value_layout: TyAndLayout<'tcx>, ) -> InterpResult<'tcx, ()> { let this = self.eval_context_mut(); - let value_place = this.deref_operand_and_offset(op, offset, layout)?; - this.write_scalar(value, &value_place.into()) + let value_place = this.deref_pointer_and_offset(op, offset, base_layout, value_layout)?; + this.write_scalar(value, &value_place) } /// Parse a `timespec` struct and return it as a `std::time::Duration`. It returns `None` @@ -778,11 +777,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { tp: &MPlaceTy<'tcx, Provenance>, ) -> InterpResult<'tcx, Option> { let this = self.eval_context_mut(); - let seconds_place = this.mplace_field(tp, 0)?; - let seconds_scalar = this.read_scalar(&seconds_place.into())?; + let seconds_place = this.project_field(tp, 0)?; + let seconds_scalar = this.read_scalar(&seconds_place)?; let seconds = seconds_scalar.to_target_isize(this)?; - let nanoseconds_place = this.mplace_field(tp, 1)?; - let nanoseconds_scalar = this.read_scalar(&nanoseconds_place.into())?; + let nanoseconds_place = this.project_field(tp, 1)?; + let nanoseconds_scalar = this.read_scalar(&nanoseconds_place)?; let nanoseconds = nanoseconds_scalar.to_target_isize(this)?; Ok(try { @@ -1012,6 +1011,65 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { None => tcx.item_name(def_id), } } + + /// Converts `f` to integer type `dest_ty` after rounding with mode `round`. + /// Returns `None` if `f` is NaN or out of range. + fn float_to_int_checked( + &self, + f: F, + dest_ty: Ty<'tcx>, + round: rustc_apfloat::Round, + ) -> Option> + where + F: rustc_apfloat::Float + Into>, + { + let this = self.eval_context_ref(); + + match dest_ty.kind() { + // Unsigned + ty::Uint(t) => { + let size = Integer::from_uint_ty(this, *t).size(); + let res = f.to_u128_r(size.bits_usize(), round, &mut false); + if res.status.intersects( + rustc_apfloat::Status::INVALID_OP + | rustc_apfloat::Status::OVERFLOW + | rustc_apfloat::Status::UNDERFLOW, + ) { + // Floating point value is NaN (flagged with INVALID_OP) or outside the range + // of values of the integer type (flagged with OVERFLOW or UNDERFLOW). + None + } else { + // Floating point value can be represented by the integer type after rounding. + // The INEXACT flag is ignored on purpose to allow rounding. + Some(Scalar::from_uint(res.value, size)) + } + } + // Signed + ty::Int(t) => { + let size = Integer::from_int_ty(this, *t).size(); + let res = f.to_i128_r(size.bits_usize(), round, &mut false); + if res.status.intersects( + rustc_apfloat::Status::INVALID_OP + | rustc_apfloat::Status::OVERFLOW + | rustc_apfloat::Status::UNDERFLOW, + ) { + // Floating point value is NaN (flagged with INVALID_OP) or outside the range + // of values of the integer type (flagged with OVERFLOW or UNDERFLOW). + None + } else { + // Floating point value can be represented by the integer type after rounding. + // The INEXACT flag is ignored on purpose to allow rounding. + Some(Scalar::from_int(res.value, size)) + } + } + // Nothing else + _ => + span_bug!( + this.cur_span(), + "attempted float-to-int conversion with non-int output type {dest_ty:?}" + ), + } + } } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 893a4dbd4c890..d57da57431540 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +#![feature(float_gamma)] #![feature(map_try_insert)] #![feature(never_type)] #![feature(try_blocks)] @@ -6,8 +7,10 @@ #![feature(variant_count)] #![feature(yeet_expr)] #![feature(nonzero_ops)] -#![feature(local_key_cell_methods)] #![feature(round_ties_even)] +#![feature(os_str_bytes)] +#![feature(lint_reasons)] +#![feature(trait_upcasting)] // Configure clippy and other lints #![allow( clippy::collapsible_else_if, @@ -17,6 +20,7 @@ clippy::enum_variant_names, clippy::field_reassign_with_default, clippy::manual_map, + clippy::neg_cmp_op_on_partial_ord, clippy::new_without_default, clippy::single_match, clippy::useless_format, @@ -41,14 +45,17 @@ // Needed for rustdoc from bootstrap (with `-Znormalize-docs`). #![recursion_limit = "256"] +extern crate either; // the one from rustc + extern crate rustc_apfloat; extern crate rustc_ast; -#[macro_use] -extern crate rustc_middle; extern crate rustc_const_eval; extern crate rustc_data_structures; +extern crate rustc_errors; extern crate rustc_hir; extern crate rustc_index; +#[macro_use] +extern crate rustc_middle; extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 29f518fe58b58..90c3c70ae5b49 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -7,6 +7,7 @@ use std::fmt; use std::path::Path; use std::process; +use either::Either; use rand::rngs::StdRng; use rand::SeedableRng; @@ -24,7 +25,7 @@ use rustc_middle::{ }, }; use rustc_span::def_id::{CrateNum, DefId}; -use rustc_span::Symbol; +use rustc_span::{Span, SpanData, Symbol}; use rustc_target::abi::{Align, Size}; use rustc_target::spec::abi::Abi; @@ -112,6 +113,8 @@ pub enum MiriMemoryKind { /// Memory for thread-local statics. /// This memory may leak. Tls, + /// Memory mapped directly by the program + Mmap, } impl From for MemoryKind { @@ -127,7 +130,20 @@ impl MayLeak for MiriMemoryKind { use self::MiriMemoryKind::*; match self { Rust | Miri | C | WinHeap | Runtime => false, - Machine | Global | ExternStatic | Tls => true, + Machine | Global | ExternStatic | Tls | Mmap => true, + } + } +} + +impl MiriMemoryKind { + /// Whether we have a useful allocation span for an allocation of this kind. + fn should_save_allocation_span(self) -> bool { + use self::MiriMemoryKind::*; + match self { + // Heap allocations are fine since the `Allocation` is created immediately. + Rust | Miri | C | WinHeap | Mmap => true, + // Everything else is unclear, let's not show potentially confusing spans. + Machine | Global | ExternStatic | Tls | Runtime => false, } } } @@ -145,6 +161,7 @@ impl fmt::Display for MiriMemoryKind { Global => write!(f, "global (static or const)"), ExternStatic => write!(f, "extern static"), Tls => write!(f, "thread-local static"), + Mmap => write!(f, "mmap"), } } } @@ -308,12 +325,14 @@ pub struct PrimitiveLayouts<'tcx> { } impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> { - fn new(layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Result> { + fn new(layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Result> { let tcx = layout_cx.tcx; - let mut_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Mut }); - let const_raw_ptr = tcx.mk_ptr(TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Not }); + let mut_raw_ptr = + Ty::new_ptr(tcx, TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Mut }); + let const_raw_ptr = + Ty::new_ptr(tcx, TypeAndMut { ty: tcx.types.unit, mutbl: Mutability::Not }); Ok(Self { - unit: layout_cx.layout_of(tcx.mk_unit())?, + unit: layout_cx.layout_of(Ty::new_unit(tcx))?, i8: layout_cx.layout_of(tcx.types.i8)?, i16: layout_cx.layout_of(tcx.types.i16)?, i32: layout_cx.layout_of(tcx.types.i32)?, @@ -421,7 +440,7 @@ pub struct MiriMachine<'mir, 'tcx> { /// the emulated program. profiler: Option, /// Used with `profiler` to cache the `StringId`s for event names - /// uesd with `measureme`. + /// used with `measureme`. string_cache: FxHashMap, /// Cache of `Instance` exported under the given `Symbol` name. @@ -491,6 +510,10 @@ pub struct MiriMachine<'mir, 'tcx> { /// Whether to collect a backtrace when each allocation is created, just in case it leaks. pub(crate) collect_leak_backtraces: bool, + + /// The spans we will use to report where an allocation was created and deallocated in + /// diagnostics. + pub(crate) allocation_spans: RefCell)>>, } impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { @@ -510,7 +533,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { let pid = process::id(); // We adopt the same naming scheme for the profiler output that rustc uses. In rustc, // the PID is padded so that the nondeterministic value of the PID does not spread - // nondeterminisim to the allocator. In Miri we are not aiming for such performance + // nondeterminism to the allocator. In Miri we are not aiming for such performance // control, we just pad for consistency with rustc. let filename = format!("{crate_name}-{pid:07}"); let path = Path::new(out).join(filename); @@ -528,7 +551,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { let target = &tcx.sess.target; match target.arch.as_ref() { "wasm32" | "wasm64" => 64 * 1024, // https://webassembly.github.io/spec/core/exec/runtime.html#memory-instances - "aarch64" => + "aarch64" => { if target.options.vendor.as_ref() == "apple" { // No "definitive" source, but see: // https://www.wwdcnotes.com/notes/wwdc20/10214/ @@ -536,7 +559,8 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { 16 * 1024 } else { 4 * 1024 - }, + } + } _ => 4 * 1024, } }; @@ -614,6 +638,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { stack_addr, stack_size, collect_leak_backtraces: config.collect_leak_backtraces, + allocation_spans: RefCell::new(FxHashMap::default()), } } @@ -644,20 +669,24 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { val: ImmTy<'tcx, Provenance>, ) -> InterpResult<'tcx> { let place = this.allocate(val.layout, MiriMemoryKind::ExternStatic.into())?; - this.write_immediate(*val, &place.into())?; + this.write_immediate(*val, &place)?; Self::add_extern_static(this, name, place.ptr); Ok(()) } /// Sets up the "extern statics" for this machine. fn init_extern_statics(this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> { + // "__rust_no_alloc_shim_is_unstable" + let val = ImmTy::from_int(0, this.machine.layouts.u8); + Self::alloc_extern_static(this, "__rust_no_alloc_shim_is_unstable", val)?; + match this.tcx.sess.target.os.as_ref() { "linux" => { // "environ" Self::add_extern_static( this, "environ", - this.machine.env_vars.environ.unwrap().ptr, + this.machine.env_vars.environ.as_ref().unwrap().ptr, ); // A couple zero-initialized pointer-sized extern statics. // Most of them are for weak symbols, which we all set to null (indicating that the @@ -674,7 +703,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { Self::add_extern_static( this, "environ", - this.machine.env_vars.environ.unwrap().ptr, + this.machine.env_vars.environ.as_ref().unwrap().ptr, ); } "android" => { @@ -722,6 +751,30 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { // will panic when given the file. drop(self.profiler.take()); } + + pub(crate) fn round_up_to_multiple_of_page_size(&self, length: u64) -> Option { + #[allow(clippy::arithmetic_side_effects)] // page size is nonzero + (length.checked_add(self.page_size - 1)? / self.page_size).checked_mul(self.page_size) + } + + pub(crate) fn page_align(&self) -> Align { + Align::from_bytes(self.page_size).unwrap() + } + + pub(crate) fn allocated_span(&self, alloc_id: AllocId) -> Option { + self.allocation_spans + .borrow() + .get(&alloc_id) + .map(|(allocated, _deallocated)| allocated.data()) + } + + pub(crate) fn deallocated_span(&self, alloc_id: AllocId) -> Option { + self.allocation_spans + .borrow() + .get(&alloc_id) + .and_then(|(_allocated, deallocated)| *deallocated) + .map(Span::data) + } } impl VisitTags for MiriMachine<'_, '_> { @@ -771,6 +824,7 @@ impl VisitTags for MiriMachine<'_, '_> { stack_addr: _, stack_size: _, collect_leak_backtraces: _, + allocation_spans: _, } = self; threads.visit_tags(visit); @@ -874,7 +928,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ecx: &mut MiriInterpCx<'mir, 'tcx>, instance: ty::Instance<'tcx>, abi: Abi, - args: &[OpTy<'tcx, Provenance>], + args: &[FnArg<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option, unwind: mir::UnwindAction, @@ -887,12 +941,13 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ecx: &mut MiriInterpCx<'mir, 'tcx>, fn_val: Dlsym, abi: Abi, - args: &[OpTy<'tcx, Provenance>], + args: &[FnArg<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option, _unwind: mir::UnwindAction, ) -> InterpResult<'tcx> { - ecx.call_dlsym(fn_val, abi, args, dest, ret) + let args = ecx.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit? + ecx.call_dlsym(fn_val, abi, &args, dest, ret) } #[inline(always)] @@ -951,7 +1006,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { panic!("extern_statics cannot contain wildcards") }; let (shim_size, shim_align, _kind) = ecx.get_alloc_info(alloc_id); - let def_ty = ecx.tcx.type_of(def_id).subst_identity(); + let def_ty = ecx.tcx.type_of(def_id).instantiate_identity(); let extern_decl_layout = ecx.tcx.layout_of(ty::ParamEnv::empty().and(def_ty)).unwrap(); if extern_decl_layout.size != shim_size || extern_decl_layout.align.abi != shim_align { throw_unsup_format!( @@ -1030,6 +1085,14 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { }, |ptr| ecx.global_base_pointer(ptr), )?; + + if matches!(kind, MemoryKind::Machine(kind) if kind.should_save_allocation_span()) { + ecx.machine + .allocation_spans + .borrow_mut() + .insert(id, (ecx.machine.current_span(), None)); + } + Ok(Cow::Owned(alloc)) } @@ -1160,6 +1223,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { if let Some(borrow_tracker) = &mut alloc_extra.borrow_tracker { borrow_tracker.before_memory_deallocation(alloc_id, prove_extra, range, machine)?; } + if let Some((_, deallocated_at)) = machine.allocation_spans.borrow_mut().get_mut(&alloc_id) + { + *deallocated_at = Some(machine.current_span()); + } Ok(()) } @@ -1188,6 +1255,26 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { Ok(()) } + fn protect_in_place_function_argument( + ecx: &mut InterpCx<'mir, 'tcx, Self>, + place: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx> { + // We do need to write `uninit` so that even after the call ends, the former contents of + // this place cannot be observed any more. + ecx.write_uninit(place)?; + // If we have a borrow tracker, we also have it set up protection so that all reads *and + // writes* during this call are insta-UB. + if ecx.machine.borrow_tracker.is_some() { + // Have to do `to_op` first because a `Place::Local` doesn't imply the local doesn't have an address. + if let Either::Left(place) = ecx.place_to_op(place)?.as_mplace_or_imm() { + ecx.protect_place(&place)?; + } else { + // Locals that don't have their address taken are as protected as they can ever be. + } + } + Ok(()) + } + #[inline(always)] fn init_frame_extra( ecx: &mut InterpCx<'mir, 'tcx, Self>, @@ -1270,8 +1357,17 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { let stack_len = ecx.active_thread_stack().len(); ecx.active_thread_mut().set_top_user_relevant_frame(stack_len - 1); } - if ecx.machine.borrow_tracker.is_some() { - ecx.retag_return_place()?; + Ok(()) + } + + fn before_stack_pop( + ecx: &InterpCx<'mir, 'tcx, Self>, + frame: &Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>, + ) -> InterpResult<'tcx> { + // We want this *before* the return value copy, because the return place itself is protected + // until we do `end_call` here. + if let Some(borrow_tracker) = &ecx.machine.borrow_tracker { + borrow_tracker.borrow_mut().end_call(&frame.extra); } Ok(()) } @@ -1290,9 +1386,6 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { ecx.active_thread_mut().recompute_top_user_relevant_frame(); } let timing = frame.extra.timing.take(); - if let Some(borrow_tracker) = &ecx.machine.borrow_tracker { - borrow_tracker.borrow_mut().end_call(&frame.extra); - } let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding); if let Some(profiler) = ecx.machine.profiler.as_ref() { profiler.finish_recording_interval_event(timing.unwrap()); diff --git a/src/tools/miri/src/mono_hash_map.rs b/src/tools/miri/src/mono_hash_map.rs index 45057632df9b5..81b3153517f14 100644 --- a/src/tools/miri/src/mono_hash_map.rs +++ b/src/tools/miri/src/mono_hash_map.rs @@ -2,7 +2,7 @@ //! otherwise mutated. We also box items in the map. This means we can safely provide //! shared references into existing items in the `FxHashMap`, because they will not be dropped //! (from being removed) or moved (because they are boxed). -//! The API is is completely tailored to what `memory.rs` needs. It is still in +//! The API is completely tailored to what `memory.rs` needs. It is still in //! a separate file to minimize the amount of code that has to care about the unsafety. use std::borrow::Borrow; diff --git a/src/tools/miri/src/range_map.rs b/src/tools/miri/src/range_map.rs index 146715ddda238..4a3670b76ac25 100644 --- a/src/tools/miri/src/range_map.rs +++ b/src/tools/miri/src/range_map.rs @@ -27,35 +27,27 @@ impl RangeMap { #[inline(always)] pub fn new(size: Size, init: T) -> RangeMap { let size = size.bytes(); - let mut map = RangeMap { v: Vec::new() }; - if size > 0 { - map.v.push(Elem { range: 0..size, data: init }); - } - map + let v = if size > 0 { vec![Elem { range: 0..size, data: init }] } else { Vec::new() }; + RangeMap { v } } /// Finds the index containing the given offset. fn find_offset(&self, offset: u64) -> usize { - // We do a binary search. - let mut left = 0usize; // inclusive - let mut right = self.v.len(); // exclusive - loop { - debug_assert!(left < right, "find_offset: offset {offset} is out-of-bounds"); - let candidate = left.checked_add(right).unwrap() / 2; - let elem = &self.v[candidate]; - if offset < elem.range.start { - // We are too far right (offset is further left). - debug_assert!(candidate < right); // we are making progress - right = candidate; - } else if offset >= elem.range.end { - // We are too far left (offset is further right). - debug_assert!(candidate >= left); // we are making progress - left = candidate + 1; - } else { - // This is it! - return candidate; - } - } + self.v + .binary_search_by(|elem| -> std::cmp::Ordering { + if offset < elem.range.start { + // We are too far right (offset is further left). + // (`Greater` means that `elem` is greater than the desired target.) + std::cmp::Ordering::Greater + } else if offset >= elem.range.end { + // We are too far left (offset is further right). + std::cmp::Ordering::Less + } else { + // This is it! + std::cmp::Ordering::Equal + } + }) + .unwrap() } /// Provides read-only iteration over everything in the given range. This does diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs index 323991249d3a7..a3297bf819f0a 100644 --- a/src/tools/miri/src/shims/backtrace.rs +++ b/src/tools/miri/src/shims/backtrace.rs @@ -1,7 +1,7 @@ use crate::*; use rustc_ast::ast::Mutability; use rustc_middle::ty::layout::LayoutOf as _; -use rustc_middle::ty::{self, Instance}; +use rustc_middle::ty::{self, Instance, Ty}; use rustc_span::{BytePos, Loc, Symbol}; use rustc_target::{abi::Size, spec::abi::Abi}; @@ -71,7 +71,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let len: u64 = ptrs.len().try_into().unwrap(); let ptr_ty = this.machine.layouts.mut_raw_ptr.ty; - let array_layout = this.layout_of(tcx.mk_array(ptr_ty, len)).unwrap(); + let array_layout = this.layout_of(Ty::new_array(tcx.tcx, ptr_ty, len)).unwrap(); match flags { // storage for pointers is allocated by miri @@ -83,9 +83,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Write pointers into array for (i, ptr) in ptrs.into_iter().enumerate() { - let place = this.mplace_index(&alloc, i as u64)?; + let place = this.project_index(&alloc, i as u64)?; - this.write_pointer(ptr, &place.into())?; + this.write_pointer(ptr, &place)?; } this.write_immediate( @@ -97,7 +97,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { 1 => { let [_flags, buf] = this.check_shim(abi, Abi::Rust, link_name, args)?; - let buf_place = this.deref_operand(buf)?; + let buf_place = this.deref_pointer(buf)?; let ptr_layout = this.layout_of(ptr_ty)?; @@ -106,7 +106,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let op_place = buf_place.offset(offset, ptr_layout, this)?; - this.write_pointer(ptr, &op_place.into())?; + this.write_pointer(ptr, &op_place)?; } } _ => throw_unsup_format!("unknown `miri_get_backtrace` flags {}", flags), @@ -194,35 +194,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let filename_alloc = this.allocate_str(&filename, MiriMemoryKind::Rust.into(), Mutability::Mut)?; - this.write_immediate( - name_alloc.to_ref(this), - &this.mplace_field(&dest, 0)?.into(), - )?; - this.write_immediate( - filename_alloc.to_ref(this), - &this.mplace_field(&dest, 1)?.into(), - )?; + this.write_immediate(name_alloc.to_ref(this), &this.project_field(&dest, 0)?)?; + this.write_immediate(filename_alloc.to_ref(this), &this.project_field(&dest, 1)?)?; } 1 => { this.write_scalar( Scalar::from_target_usize(name.len().try_into().unwrap(), this), - &this.mplace_field(&dest, 0)?.into(), + &this.project_field(&dest, 0)?, )?; this.write_scalar( Scalar::from_target_usize(filename.len().try_into().unwrap(), this), - &this.mplace_field(&dest, 1)?.into(), + &this.project_field(&dest, 1)?, )?; } _ => throw_unsup_format!("unknown `miri_resolve_frame` flags {}", flags), } - this.write_scalar(Scalar::from_u32(lineno), &this.mplace_field(&dest, 2)?.into())?; - this.write_scalar(Scalar::from_u32(colno), &this.mplace_field(&dest, 3)?.into())?; + this.write_scalar(Scalar::from_u32(lineno), &this.project_field(&dest, 2)?)?; + this.write_scalar(Scalar::from_u32(colno), &this.project_field(&dest, 3)?)?; // Support a 4-field struct for now - this is deprecated // and slated for removal. if num_fields == 5 { - this.write_pointer(fn_ptr, &this.mplace_field(&dest, 4)?.into())?; + this.write_pointer(fn_ptr, &this.project_field(&dest, 4)?)?; } Ok(()) diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs index f50c135435fd7..3694ea51da6c6 100644 --- a/src/tools/miri/src/shims/env.rs +++ b/src/tools/miri/src/shims/env.rs @@ -6,6 +6,7 @@ use std::mem; use rustc_const_eval::interpret::Pointer; use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::Ty; use rustc_target::abi::Size; use crate::helpers::target_os_is_unix; @@ -86,8 +87,8 @@ impl<'tcx> EnvVars<'tcx> { ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?; } // Deallocate environ var list. - let environ = ecx.machine.env_vars.environ.unwrap(); - let old_vars_ptr = ecx.read_pointer(&environ.into())?; + let environ = ecx.machine.env_vars.environ.as_ref().unwrap(); + let old_vars_ptr = ecx.read_pointer(environ)?; ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; Ok(()) } @@ -430,8 +431,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn update_environ(&mut self) -> InterpResult<'tcx> { let this = self.eval_context_mut(); // Deallocate the old environ list, if any. - if let Some(environ) = this.machine.env_vars.environ { - let old_vars_ptr = this.read_pointer(&environ.into())?; + if let Some(environ) = this.machine.env_vars.environ.as_ref() { + let old_vars_ptr = this.read_pointer(environ)?; this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?; } else { // No `environ` allocated yet, let's do that. @@ -448,15 +449,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { vars.push(Pointer::null()); // Make an array with all these pointers inside Miri. let tcx = this.tcx; - let vars_layout = this.layout_of( - tcx.mk_array(this.machine.layouts.mut_raw_ptr.ty, u64::try_from(vars.len()).unwrap()), - )?; + let vars_layout = this.layout_of(Ty::new_array( + tcx.tcx, + this.machine.layouts.mut_raw_ptr.ty, + u64::try_from(vars.len()).unwrap(), + ))?; let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?; for (idx, var) in vars.into_iter().enumerate() { - let place = this.mplace_field(&vars_place, idx)?; - this.write_pointer(var, &place.into())?; + let place = this.project_field(&vars_place, idx)?; + this.write_pointer(var, &place)?; } - this.write_pointer(vars_place.ptr, &this.machine.env_vars.environ.unwrap().into())?; + this.write_pointer(vars_place.ptr, &this.machine.env_vars.environ.clone().unwrap())?; Ok(()) } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index aa0794c00bee5..7996e72615f0e 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -45,8 +45,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // List taken from `library/std/src/sys/common/alloc.rs`. // This list should be kept in sync with the one from libstd. let min_align = match this.tcx.sess.target.arch.as_ref() { - "x86" | "arm" | "mips" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8, - "x86_64" | "aarch64" | "mips64" | "s390x" | "sparc64" | "loongarch64" => 16, + "x86" | "arm" | "mips" | "mips32r6" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8, + "x86_64" | "aarch64" | "mips64" | "mips64r6" | "s390x" | "sparc64" | "loongarch64" => + 16, arch => bug!("unsupported target architecture for malloc: `{}`", arch), }; // Windows always aligns, even small allocations. @@ -347,7 +348,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Emulates calling the internal __rust_* allocator functions fn emulate_allocator( &mut self, - symbol: Symbol, default: impl FnOnce(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx>, ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { let this = self.eval_context_mut(); @@ -359,11 +359,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { match allocator_kind { AllocatorKind::Global => { - let (body, instance) = this - .lookup_exported_symbol(symbol)? - .expect("symbol should be present if there is a global allocator"); - - Ok(EmulateByNameResult::MirBody(body, instance)) + // When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion + // of this attribute. As such we have to call an exported Rust function, + // and not execute any Miri shim. Somewhat unintuitively doing so is done + // by returning `NotSupported`, which triggers the `lookup_exported_symbol` + // fallback case in `emulate_foreign_item`. + return Ok(EmulateByNameResult::NotSupported); } AllocatorKind::Default => { default(this)?; @@ -409,14 +410,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // &mut self, // arg1: &OpTy<'tcx, Provenance>, // arg2: &OpTy<'tcx, Provenance>, - // arg3: &OpTy<'tcx, Provenance>) + // arg3: &OpTy<'tcx, Provenance>, + // arg4: &OpTy<'tcx, Provenance>) // -> InterpResult<'tcx, Scalar> { // let this = self.eval_context_mut(); // // // First thing: load all the arguments. Details depend on the shim. // let arg1 = this.read_scalar(arg1)?.to_u32()?; // let arg2 = this.read_pointer(arg2)?; // when you need to work with the pointer directly - // let arg3 = this.deref_operand(arg3)?; // when you want to load/store through the pointer at its declared type + // let arg3 = this.deref_pointer_as(arg3, this.libc_ty_layout("some_libc_struct"))?; // when you want to load/store + // // through the pointer and supply the type information yourself + // let arg4 = this.deref_pointer(arg4)?; // when you want to load/store through the pointer and trust + // // the user-given type (which you shouldn't usually do) // // // ... // @@ -558,11 +563,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Rust allocation "__rust_alloc" | "miri_alloc" => { - let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; - let size = this.read_target_usize(size)?; - let align = this.read_target_usize(align)?; - let default = |this: &mut MiriInterpCx<'mir, 'tcx>| { + // Only call `check_shim` when `#[global_allocator]` isn't used. When that + // macro is used, we act like no shim exists, so that the exported function can run. + let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let size = this.read_target_usize(size)?; + let align = this.read_target_usize(align)?; + Self::check_alloc_request(size, align)?; let memory_kind = match link_name.as_str() { @@ -581,8 +588,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; match link_name.as_str() { - "__rust_alloc" => - return this.emulate_allocator(Symbol::intern("__rg_alloc"), default), + "__rust_alloc" => return this.emulate_allocator(default), "miri_alloc" => { default(this)?; return Ok(EmulateByNameResult::NeedsJumping); @@ -591,11 +597,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } "__rust_alloc_zeroed" => { - let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; - let size = this.read_target_usize(size)?; - let align = this.read_target_usize(align)?; + return this.emulate_allocator(|this| { + // See the comment for `__rust_alloc` why `check_shim` is only called in the + // default case. + let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let size = this.read_target_usize(size)?; + let align = this.read_target_usize(align)?; - return this.emulate_allocator(Symbol::intern("__rg_alloc_zeroed"), |this| { Self::check_alloc_request(size, align)?; let ptr = this.allocate_ptr( @@ -614,12 +622,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }); } "__rust_dealloc" | "miri_dealloc" => { - let [ptr, old_size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?; - let ptr = this.read_pointer(ptr)?; - let old_size = this.read_target_usize(old_size)?; - let align = this.read_target_usize(align)?; - let default = |this: &mut MiriInterpCx<'mir, 'tcx>| { + // See the comment for `__rust_alloc` why `check_shim` is only called in the + // default case. + let [ptr, old_size, align] = + this.check_shim(abi, Abi::Rust, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let old_size = this.read_target_usize(old_size)?; + let align = this.read_target_usize(align)?; + let memory_kind = match link_name.as_str() { "__rust_dealloc" => MiriMemoryKind::Rust, "miri_dealloc" => MiriMemoryKind::Miri, @@ -635,8 +646,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; match link_name.as_str() { - "__rust_dealloc" => - return this.emulate_allocator(Symbol::intern("__rg_dealloc"), default), + "__rust_dealloc" => { + return this.emulate_allocator(default); + } "miri_dealloc" => { default(this)?; return Ok(EmulateByNameResult::NeedsJumping); @@ -645,15 +657,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } "__rust_realloc" => { - let [ptr, old_size, align, new_size] = - this.check_shim(abi, Abi::Rust, link_name, args)?; - let ptr = this.read_pointer(ptr)?; - let old_size = this.read_target_usize(old_size)?; - let align = this.read_target_usize(align)?; - let new_size = this.read_target_usize(new_size)?; - // No need to check old_size; we anyway check that they match the allocation. + return this.emulate_allocator(|this| { + // See the comment for `__rust_alloc` why `check_shim` is only called in the + // default case. + let [ptr, old_size, align, new_size] = + this.check_shim(abi, Abi::Rust, link_name, args)?; + let ptr = this.read_pointer(ptr)?; + let old_size = this.read_target_usize(old_size)?; + let align = this.read_target_usize(align)?; + let new_size = this.read_target_usize(new_size)?; + // No need to check old_size; we anyway check that they match the allocation. - return this.emulate_allocator(Symbol::intern("__rg_realloc"), |this| { Self::check_alloc_request(new_size, align)?; let align = Align::from_bytes(align).unwrap(); @@ -676,6 +690,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let right = this.read_pointer(right)?; let n = Size::from_bytes(this.read_target_usize(n)?); + // C requires that this must always be a valid pointer (C18 §7.1.4). + this.ptr_get_alloc_id(left)?; + this.ptr_get_alloc_id(right)?; + let result = { let left_bytes = this.read_bytes_ptr_strip_provenance(left, n)?; let right_bytes = this.read_bytes_ptr_strip_provenance(right, n)?; @@ -700,6 +718,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let val = val as u8; + // C requires that this must always be a valid pointer (C18 §7.1.4). + this.ptr_get_alloc_id(ptr)?; + if let Some(idx) = this .read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))? .iter() @@ -724,6 +745,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let val = val as u8; + // C requires that this must always be a valid pointer (C18 §7.1.4). + this.ptr_get_alloc_id(ptr)?; + let idx = this .read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(num))? .iter() @@ -738,6 +762,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "strlen" => { let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; let ptr = this.read_pointer(ptr)?; + // This reads at least 1 byte, so we are already enforcing that this is a valid pointer. let n = this.read_c_str(ptr)?.len(); this.write_scalar( Scalar::from_target_usize(u64::try_from(n).unwrap(), this), @@ -750,6 +775,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let ptr_dest = this.read_pointer(ptr_dest)?; let ptr_src = this.read_pointer(ptr_src)?; let n = this.read_target_usize(n)?; + + // C requires that this must always be a valid pointer, even if `n` is zero, so we better check that. + // (This is more than Rust requires, so `mem_copy` is not sufficient.) + this.ptr_get_alloc_id(ptr_dest)?; + this.ptr_get_alloc_id(ptr_src)?; + this.mem_copy( ptr_src, Align::ONE, @@ -771,6 +802,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // pointer provenance is preserved by this implementation of `strcpy`. // That is probably overly cautious, but there also is no fundamental // reason to have `strcpy` destroy pointer provenance. + // This reads at least 1 byte, so we are already enforcing that this is a valid pointer. let n = this.read_c_str(ptr_src)?.len().checked_add(1).unwrap(); this.mem_copy( ptr_src, @@ -795,6 +827,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "atanf" | "log1pf" | "expm1f" + | "tgammaf" => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; // FIXME: Using host floats. @@ -810,6 +843,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "atanf" => f.atan(), "log1pf" => f.ln_1p(), "expm1f" => f.exp_m1(), + "tgammaf" => f.gamma(), _ => bug!(), }; this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; @@ -846,6 +880,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { | "atan" | "log1p" | "expm1" + | "tgamma" => { let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; // FIXME: Using host floats. @@ -861,6 +896,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "atan" => f.atan(), "log1p" => f.ln_1p(), "expm1" => f.exp_m1(), + "tgamma" => f.gamma(), _ => bug!(), }; this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; @@ -894,21 +930,57 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let x = this.read_scalar(x)?.to_f64()?; let exp = this.read_scalar(exp)?.to_i32()?; - // Saturating cast to i16. Even those are outside the valid exponent range so - // `scalbn` below will do its over/underflow handling. - let exp = if exp > i32::from(i16::MAX) { - i16::MAX - } else if exp < i32::from(i16::MIN) { - i16::MIN - } else { - exp.try_into().unwrap() - }; - let res = x.scalbn(exp); this.write_scalar(Scalar::from_f64(res), dest)?; } + "lgammaf_r" => { + let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // FIXME: Using host floats. + let x = f32::from_bits(this.read_scalar(x)?.to_u32()?); + let signp = this.deref_pointer(signp)?; + + let (res, sign) = x.ln_gamma(); + this.write_int(sign, &signp)?; + this.write_scalar(Scalar::from_u32(res.to_bits()), dest)?; + } + "lgamma_r" => { + let [x, signp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + // FIXME: Using host floats. + let x = f64::from_bits(this.read_scalar(x)?.to_u64()?); + let signp = this.deref_pointer(signp)?; + + let (res, sign) = x.ln_gamma(); + this.write_int(sign, &signp)?; + this.write_scalar(Scalar::from_u64(res.to_bits()), dest)?; + } + + // LLVM intrinsics + "llvm.prefetch" => { + let [p, rw, loc, ty] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - // Architecture-specific shims + let _ = this.read_pointer(p)?; + let rw = this.read_scalar(rw)?.to_i32()?; + let loc = this.read_scalar(loc)?.to_i32()?; + let ty = this.read_scalar(ty)?.to_i32()?; + + if ty == 1 { + // Data cache prefetch. + // Notably, we do not have to check the pointer, this operation is never UB! + + if !matches!(rw, 0 | 1) { + throw_unsup_format!("invalid `rw` value passed to `llvm.prefetch`: {}", rw); + } + if !matches!(loc, 0..=3) { + throw_unsup_format!( + "invalid `loc` value passed to `llvm.prefetch`: {}", + loc + ); + } + } else { + throw_unsup_format!("unsupported `llvm.prefetch` type argument: {}", ty); + } + } "llvm.x86.addcarry.64" if this.tcx.sess.target.arch == "x86_64" => { // Computes u8+u64+u64, returning tuple (u8,u64) comprising the output carry and truncated sum. let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?; @@ -922,9 +994,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { #[allow(clippy::arithmetic_side_effects)] // it's a u128, we can shift by 64 let (c_out, sum) = ((wide_sum >> 64).truncate::(), wide_sum.truncate::()); - let c_out_field = this.place_field(dest, 0)?; + let c_out_field = this.project_field(dest, 0)?; this.write_scalar(Scalar::from_u8(c_out), &c_out_field)?; - let sum_field = this.place_field(dest, 1)?; + let sum_field = this.project_field(dest, 1)?; this.write_scalar(Scalar::from_u64(sum), &sum_field)?; } "llvm.x86.sse2.pause" @@ -960,6 +1032,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } + name if name.starts_with("llvm.x86.sse.") => { + return shims::x86::sse::EvalContextExt::emulate_x86_sse_intrinsic( + this, link_name, abi, args, dest, + ); + } + // Platform-specific shims _ => return match this.tcx.sess.target.os.as_ref() { diff --git a/src/tools/miri/src/shims/intrinsics/atomic.rs b/src/tools/miri/src/shims/intrinsics/atomic.rs index 50f69bdca3631..e38b677f485f3 100644 --- a/src/tools/miri/src/shims/intrinsics/atomic.rs +++ b/src/tools/miri/src/shims/intrinsics/atomic.rs @@ -130,7 +130,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let [place] = check_arg_count(args)?; - let place = this.deref_operand(place)?; + let place = this.deref_pointer(place)?; // Perform atomic load. let val = this.read_scalar_atomic(&place, atomic)?; @@ -147,7 +147,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let [place, val] = check_arg_count(args)?; - let place = this.deref_operand(place)?; + let place = this.deref_pointer(place)?; // Perform regular load. let val = this.read_scalar(val)?; @@ -188,7 +188,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let [place, rhs] = check_arg_count(args)?; - let place = this.deref_operand(place)?; + let place = this.deref_pointer(place)?; let rhs = this.read_immediate(rhs)?; if !place.layout.ty.is_integral() && !place.layout.ty.is_unsafe_ptr() { @@ -229,7 +229,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let [place, new] = check_arg_count(args)?; - let place = this.deref_operand(place)?; + let place = this.deref_pointer(place)?; let new = this.read_scalar(new)?; let old = this.atomic_exchange_scalar(&place, new, atomic)?; @@ -248,7 +248,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let [place, expect_old, new] = check_arg_count(args)?; - let place = this.deref_operand(place)?; + let place = this.deref_pointer(place)?; let expect_old = this.read_immediate(expect_old)?; // read as immediate for the sake of `binary_op()` let new = this.read_scalar(new)?; diff --git a/src/tools/miri/src/shims/intrinsics/mod.rs b/src/tools/miri/src/shims/intrinsics/mod.rs index ca2c1652dc199..b2c297fe542c5 100644 --- a/src/tools/miri/src/shims/intrinsics/mod.rs +++ b/src/tools/miri/src/shims/intrinsics/mod.rs @@ -6,12 +6,12 @@ use std::iter; use log::trace; use rustc_apfloat::{Float, Round}; -use rustc_middle::ty::layout::{IntegerExt, LayoutOf}; +use rustc_middle::ty::layout::LayoutOf; use rustc_middle::{ mir, - ty::{self, FloatTy, Ty}, + ty::{self, FloatTy}, }; -use rustc_target::abi::{Integer, Size}; +use rustc_target::abi::Size; use crate::*; use atomic::EvalContextExt as _; @@ -96,13 +96,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Raw memory accesses "volatile_load" => { let [place] = check_arg_count(args)?; - let place = this.deref_operand(place)?; - this.copy_op(&place.into(), dest, /*allow_transmute*/ false)?; + let place = this.deref_pointer(place)?; + this.copy_op(&place, dest, /*allow_transmute*/ false)?; } "volatile_store" => { let [place, dest] = check_arg_count(args)?; - let place = this.deref_operand(place)?; - this.copy_op(dest, &place.into(), /*allow_transmute*/ false)?; + let place = this.deref_pointer(place)?; + this.copy_op(dest, &place, /*allow_transmute*/ false)?; } "write_bytes" | "volatile_set_memory" => { @@ -356,10 +356,28 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let val = this.read_immediate(val)?; let res = match val.layout.ty.kind() { - ty::Float(FloatTy::F32) => - this.float_to_int_unchecked(val.to_scalar().to_f32()?, dest.layout.ty)?, - ty::Float(FloatTy::F64) => - this.float_to_int_unchecked(val.to_scalar().to_f64()?, dest.layout.ty)?, + ty::Float(FloatTy::F32) => { + let f = val.to_scalar().to_f32()?; + this + .float_to_int_checked(f, dest.layout.ty, Round::TowardZero) + .ok_or_else(|| { + err_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{:?}`", + dest.layout.ty + ) + })? + } + ty::Float(FloatTy::F64) => { + let f = val.to_scalar().to_f64()?; + this + .float_to_int_checked(f, dest.layout.ty, Round::TowardZero) + .ok_or_else(|| { + err_ub_format!( + "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{:?}`", + dest.layout.ty + ) + })? + } _ => span_bug!( this.cur_span(), @@ -383,57 +401,4 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(()) } - - fn float_to_int_unchecked( - &self, - f: F, - dest_ty: Ty<'tcx>, - ) -> InterpResult<'tcx, Scalar> - where - F: Float + Into>, - { - let this = self.eval_context_ref(); - - // Step 1: cut off the fractional part of `f`. The result of this is - // guaranteed to be precisely representable in IEEE floats. - let f = f.round_to_integral(Round::TowardZero).value; - - // Step 2: Cast the truncated float to the target integer type and see if we lose any information in this step. - Ok(match dest_ty.kind() { - // Unsigned - ty::Uint(t) => { - let size = Integer::from_uint_ty(this, *t).size(); - let res = f.to_u128(size.bits_usize()); - if res.status.is_empty() { - // No status flags means there was no further rounding or other loss of precision. - Scalar::from_uint(res.value, size) - } else { - // `f` was not representable in this integer type. - throw_ub_format!( - "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", - ); - } - } - // Signed - ty::Int(t) => { - let size = Integer::from_int_ty(this, *t).size(); - let res = f.to_i128(size.bits_usize()); - if res.status.is_empty() { - // No status flags means there was no further rounding or other loss of precision. - Scalar::from_int(res.value, size) - } else { - // `f` was not representable in this integer type. - throw_ub_format!( - "`float_to_int_unchecked` intrinsic called on {f} which cannot be represented in target type `{dest_ty:?}`", - ); - } - } - // Nothing else - _ => - span_bug!( - this.cur_span(), - "`float_to_int_unchecked` called with non-int output type {dest_ty:?}" - ), - }) - } } diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs index 1995db715e8da..dd8c4a4f6ecab 100644 --- a/src/tools/miri/src/shims/intrinsics/simd.rs +++ b/src/tools/miri/src/shims/intrinsics/simd.rs @@ -1,4 +1,4 @@ -use rustc_apfloat::Float; +use rustc_apfloat::{Float, Round}; use rustc_middle::ty::layout::{HasParamEnv, LayoutOf}; use rustc_middle::{mir, ty, ty::FloatTy}; use rustc_target::abi::{Endian, HasDataLayout, Size}; @@ -57,8 +57,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; for i in 0..dest_len { - let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; - let dest = this.mplace_index(&dest, i)?; + let op = this.read_immediate(&this.project_index(&op, i)?)?; + let dest = this.project_index(&dest, i)?; let val = match which { Op::MirOp(mir_op) => this.unary_op(mir_op, &op)?.to_scalar(), Op::Abs => { @@ -104,7 +104,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } }; - this.write_scalar(val, &dest.into())?; + this.write_scalar(val, &dest)?; } } #[rustfmt::skip] @@ -172,9 +172,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; for i in 0..dest_len { - let left = this.read_immediate(&this.mplace_index(&left, i)?.into())?; - let right = this.read_immediate(&this.mplace_index(&right, i)?.into())?; - let dest = this.mplace_index(&dest, i)?; + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let right = this.read_immediate(&this.project_index(&right, i)?)?; + let dest = this.project_index(&dest, i)?; let val = match which { Op::MirOp(mir_op) => { let (val, overflowed, ty) = this.overflowing_binary_op(mir_op, &left, &right)?; @@ -217,7 +217,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fmin_op(&left, &right)? } }; - this.write_scalar(val, &dest.into())?; + this.write_scalar(val, &dest)?; } } "fma" => { @@ -232,10 +232,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(dest_len, c_len); for i in 0..dest_len { - let a = this.read_scalar(&this.mplace_index(&a, i)?.into())?; - let b = this.read_scalar(&this.mplace_index(&b, i)?.into())?; - let c = this.read_scalar(&this.mplace_index(&c, i)?.into())?; - let dest = this.mplace_index(&dest, i)?; + let a = this.read_scalar(&this.project_index(&a, i)?)?; + let b = this.read_scalar(&this.project_index(&b, i)?)?; + let c = this.read_scalar(&this.project_index(&c, i)?)?; + let dest = this.project_index(&dest, i)?; // Works for f32 and f64. // FIXME: using host floats to work around https://github.com/rust-lang/miri/issues/2468. @@ -258,7 +258,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Scalar::from_u64(res.to_bits()) } }; - this.write_scalar(val, &dest.into())?; + this.write_scalar(val, &dest)?; } } #[rustfmt::skip] @@ -295,13 +295,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { }; // Initialize with first lane, then proceed with the rest. - let mut res = this.read_immediate(&this.mplace_index(&op, 0)?.into())?; + let mut res = this.read_immediate(&this.project_index(&op, 0)?)?; if matches!(which, Op::MirOpBool(_)) { // Convert to `bool` scalar. res = imm_from_bool(simd_element_to_bool(res)?); } for i in 1..op_len { - let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; + let op = this.read_immediate(&this.project_index(&op, i)?)?; res = match which { Op::MirOp(mir_op) => { this.binary_op(mir_op, &res, &op)? @@ -355,7 +355,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let mut res = init; for i in 0..op_len { - let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; + let op = this.read_immediate(&this.project_index(&op, i)?)?; res = this.binary_op(mir_op, &res, &op)?; } this.write_immediate(*res, dest)?; @@ -372,13 +372,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(dest_len, no_len); for i in 0..dest_len { - let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?; - let yes = this.read_immediate(&this.mplace_index(&yes, i)?.into())?; - let no = this.read_immediate(&this.mplace_index(&no, i)?.into())?; - let dest = this.mplace_index(&dest, i)?; + let mask = this.read_immediate(&this.project_index(&mask, i)?)?; + let yes = this.read_immediate(&this.project_index(&yes, i)?)?; + let no = this.read_immediate(&this.project_index(&no, i)?)?; + let dest = this.project_index(&dest, i)?; let val = if simd_element_to_bool(mask)? { yes } else { no }; - this.write_immediate(*val, &dest.into())?; + this.write_immediate(*val, &dest)?; } } "select_bitmask" => { @@ -403,12 +403,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { & 1u64 .checked_shl(simd_bitmask_index(i, dest_len, this.data_layout().endian)) .unwrap(); - let yes = this.read_immediate(&this.mplace_index(&yes, i.into())?.into())?; - let no = this.read_immediate(&this.mplace_index(&no, i.into())?.into())?; - let dest = this.mplace_index(&dest, i.into())?; + let yes = this.read_immediate(&this.project_index(&yes, i.into())?)?; + let no = this.read_immediate(&this.project_index(&no, i.into())?)?; + let dest = this.project_index(&dest, i.into())?; let val = if mask != 0 { yes } else { no }; - this.write_immediate(*val, &dest.into())?; + this.write_immediate(*val, &dest)?; } for i in dest_len..bitmask_len { // If the mask is "padded", ensure that padding is all-zero. @@ -420,7 +420,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } } } - #[rustfmt::skip] "cast" | "as" | "cast_ptr" | "expose_addr" | "from_exposed_addr" => { let [op] = check_arg_count(args)?; let (op, op_len) = this.operand_to_simd(op)?; @@ -435,12 +434,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let from_exposed_cast = intrinsic_name == "from_exposed_addr"; for i in 0..dest_len { - let op = this.read_immediate(&this.mplace_index(&op, i)?.into())?; - let dest = this.mplace_index(&dest, i)?; + let op = this.read_immediate(&this.project_index(&op, i)?)?; + let dest = this.project_index(&dest, i)?; let val = match (op.layout.ty.kind(), dest.layout.ty.kind()) { // Int-to-(int|float): always safe - (ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) if safe_cast || unsafe_cast => + (ty::Int(_) | ty::Uint(_), ty::Int(_) | ty::Uint(_) | ty::Float(_)) + if safe_cast || unsafe_cast => this.int_to_int_or_float(&op, dest.layout.ty)?, // Float-to-float: always safe (ty::Float(_), ty::Float(_)) if safe_cast || unsafe_cast => @@ -449,21 +449,36 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { (ty::Float(_), ty::Int(_) | ty::Uint(_)) if safe_cast => this.float_to_float_or_int(&op, dest.layout.ty)?, // Float-to-int in unchecked mode - (ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if unsafe_cast => - this.float_to_int_unchecked(op.to_scalar().to_f32()?, dest.layout.ty)?.into(), - (ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if unsafe_cast => - this.float_to_int_unchecked(op.to_scalar().to_f64()?, dest.layout.ty)?.into(), - // Ptr-to-ptr cast - (ty::RawPtr(..), ty::RawPtr(..)) if ptr_cast => { - this.ptr_to_ptr(&op, dest.layout.ty)? - } - // Ptr/Int casts - (ty::RawPtr(..), ty::Int(_) | ty::Uint(_)) if expose_cast => { - this.pointer_expose_address_cast(&op, dest.layout.ty)? + (ty::Float(FloatTy::F32), ty::Int(_) | ty::Uint(_)) if unsafe_cast => { + let f = op.to_scalar().to_f32()?; + this.float_to_int_checked(f, dest.layout.ty, Round::TowardZero) + .ok_or_else(|| { + err_ub_format!( + "`simd_cast` intrinsic called on {f} which cannot be represented in target type `{:?}`", + dest.layout.ty + ) + })? + .into() } - (ty::Int(_) | ty::Uint(_), ty::RawPtr(..)) if from_exposed_cast => { - this.pointer_from_exposed_address_cast(&op, dest.layout.ty)? + (ty::Float(FloatTy::F64), ty::Int(_) | ty::Uint(_)) if unsafe_cast => { + let f = op.to_scalar().to_f64()?; + this.float_to_int_checked(f, dest.layout.ty, Round::TowardZero) + .ok_or_else(|| { + err_ub_format!( + "`simd_cast` intrinsic called on {f} which cannot be represented in target type `{:?}`", + dest.layout.ty + ) + })? + .into() } + // Ptr-to-ptr cast + (ty::RawPtr(..), ty::RawPtr(..)) if ptr_cast => + this.ptr_to_ptr(&op, dest.layout.ty)?, + // Ptr/Int casts + (ty::RawPtr(..), ty::Int(_) | ty::Uint(_)) if expose_cast => + this.pointer_expose_address_cast(&op, dest.layout.ty)?, + (ty::Int(_) | ty::Uint(_), ty::RawPtr(..)) if from_exposed_cast => + this.pointer_from_exposed_address_cast(&op, dest.layout.ty)?, // Error otherwise _ => throw_unsup_format!( @@ -472,7 +487,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { to_ty = dest.layout.ty, ), }; - this.write_immediate(val, &dest.into())?; + this.write_immediate(val, &dest)?; } } "shuffle" => { @@ -483,7 +498,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // `index` is an array, not a SIMD type let ty::Array(_, index_len) = index.layout.ty.kind() else { - span_bug!(this.cur_span(), "simd_shuffle index argument has non-array type {}", index.layout.ty) + span_bug!( + this.cur_span(), + "simd_shuffle index argument has non-array type {}", + index.layout.ty + ) }; let index_len = index_len.eval_target_usize(*this.tcx, this.param_env()); @@ -492,24 +511,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { for i in 0..dest_len { let src_index: u64 = this - .read_immediate(&this.operand_index(index, i)?)? + .read_immediate(&this.project_index(index, i)?)? .to_scalar() .to_u32()? .into(); - let dest = this.mplace_index(&dest, i)?; + let dest = this.project_index(&dest, i)?; let val = if src_index < left_len { - this.read_immediate(&this.mplace_index(&left, src_index)?.into())? + this.read_immediate(&this.project_index(&left, src_index)?)? } else if src_index < left_len.checked_add(right_len).unwrap() { let right_idx = src_index.checked_sub(left_len).unwrap(); - this.read_immediate(&this.mplace_index(&right, right_idx)?.into())? + this.read_immediate(&this.project_index(&right, right_idx)?)? } else { span_bug!( this.cur_span(), "simd_shuffle index {src_index} is out of bounds for 2 vectors of size {left_len}", ); }; - this.write_immediate(*val, &dest.into())?; + this.write_immediate(*val, &dest)?; } } "gather" => { @@ -524,18 +543,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(dest_len, mask_len); for i in 0..dest_len { - let passthru = this.read_immediate(&this.mplace_index(&passthru, i)?.into())?; - let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?; - let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?; - let dest = this.mplace_index(&dest, i)?; + let passthru = this.read_immediate(&this.project_index(&passthru, i)?)?; + let ptr = this.read_immediate(&this.project_index(&ptrs, i)?)?; + let mask = this.read_immediate(&this.project_index(&mask, i)?)?; + let dest = this.project_index(&dest, i)?; let val = if simd_element_to_bool(mask)? { - let place = this.deref_operand(&ptr.into())?; - this.read_immediate(&place.into())? + let place = this.deref_pointer(&ptr)?; + this.read_immediate(&place)? } else { passthru }; - this.write_immediate(*val, &dest.into())?; + this.write_immediate(*val, &dest)?; } } "scatter" => { @@ -548,13 +567,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { assert_eq!(ptrs_len, mask_len); for i in 0..ptrs_len { - let value = this.read_immediate(&this.mplace_index(&value, i)?.into())?; - let ptr = this.read_immediate(&this.mplace_index(&ptrs, i)?.into())?; - let mask = this.read_immediate(&this.mplace_index(&mask, i)?.into())?; + let value = this.read_immediate(&this.project_index(&value, i)?)?; + let ptr = this.read_immediate(&this.project_index(&ptrs, i)?)?; + let mask = this.read_immediate(&this.project_index(&mask, i)?)?; if simd_element_to_bool(mask)? { - let place = this.deref_operand(&ptr.into())?; - this.write_immediate(*value, &place.into())?; + let place = this.deref_pointer(&ptr)?; + this.write_immediate(*value, &place)?; } } } @@ -574,7 +593,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let mut res = 0u64; for i in 0..op_len { - let op = this.read_immediate(&this.mplace_index(&op, i.into())?.into())?; + let op = this.read_immediate(&this.project_index(&op, i.into())?)?; if simd_element_to_bool(op)? { res |= 1u64 .checked_shl(simd_bitmask_index(i, op_len, this.data_layout().endian)) @@ -584,7 +603,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // We have to force the place type to be an int so that we can write `res` into it. let mut dest = this.force_allocation(dest)?; dest.layout = this.machine.layouts.uint(dest.layout.size).unwrap(); - this.write_int(res, &dest.into())?; + this.write_int(res, &dest)?; } name => throw_unsup_format!("unimplemented intrinsic: `simd_{name}`"), @@ -622,9 +641,7 @@ fn fmax_op<'tcx>( right: &ImmTy<'tcx, Provenance>, ) -> InterpResult<'tcx, Scalar> { assert_eq!(left.layout.ty, right.layout.ty); - let ty::Float(float_ty) = left.layout.ty.kind() else { - bug!("fmax operand is not a float") - }; + let ty::Float(float_ty) = left.layout.ty.kind() else { bug!("fmax operand is not a float") }; let left = left.to_scalar(); let right = right.to_scalar(); Ok(match float_ty { @@ -638,9 +655,7 @@ fn fmin_op<'tcx>( right: &ImmTy<'tcx, Provenance>, ) -> InterpResult<'tcx, Scalar> { assert_eq!(left.layout.ty, right.layout.ty); - let ty::Float(float_ty) = left.layout.ty.kind() else { - bug!("fmin operand is not a float") - }; + let ty::Float(float_ty) = left.layout.ty.kind() else { bug!("fmin operand is not a float") }; let left = left.to_scalar(); let right = right.to_scalar(); Ok(match float_ty { diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index a423a0786b751..5a9574766f3d9 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -7,6 +7,7 @@ pub mod foreign_items; pub mod intrinsics; pub mod unix; pub mod windows; +mod x86; pub mod dlsym; pub mod env; @@ -31,7 +32,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &mut self, instance: ty::Instance<'tcx>, abi: Abi, - args: &[OpTy<'tcx, Provenance>], + args: &[FnArg<'tcx, Provenance>], dest: &PlaceTy<'tcx, Provenance>, ret: Option, unwind: mir::UnwindAction, @@ -41,7 +42,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // There are some more lang items we want to hook that CTFE does not hook (yet). if this.tcx.lang_items().align_offset_fn() == Some(instance.def.def_id()) { - let [ptr, align] = check_arg_count(args)?; + let args = this.copy_fn_args(args)?; + let [ptr, align] = check_arg_count(&args)?; if this.align_offset(ptr, align, dest, ret, unwind)? { return Ok(None); } @@ -55,7 +57,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // to run extra MIR), and Ok(Some(body)) if we found MIR to run for the // foreign function // Any needed call to `goto_block` will be performed by `emulate_foreign_item`. - return this.emulate_foreign_item(instance.def_id(), abi, args, dest, ret, unwind); + let args = this.copy_fn_args(args)?; // FIXME: Should `InPlace` arguments be reset to uninit? + return this.emulate_foreign_item(instance.def_id(), abi, &args, dest, ret, unwind); } // Otherwise, load the MIR. diff --git a/src/tools/miri/src/shims/os_str.rs b/src/tools/miri/src/shims/os_str.rs index 6bc5b8f39d5f0..f08f0aad5e764 100644 --- a/src/tools/miri/src/shims/os_str.rs +++ b/src/tools/miri/src/shims/os_str.rs @@ -8,6 +8,7 @@ use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::Ty; use crate::*; @@ -17,28 +18,13 @@ pub enum PathConversion { TargetToHost, } -#[cfg(unix)] -pub fn os_str_to_bytes<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, &[u8]> { - Ok(os_str.as_bytes()) -} - -#[cfg(not(unix))] -pub fn os_str_to_bytes<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, &[u8]> { - // On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the - // intermediate transformation into strings. Which invalidates non-utf8 paths that are actually - // valid. - os_str - .to_str() - .map(|s| s.as_bytes()) - .ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into()) -} - #[cfg(unix)] pub fn bytes_to_os_str<'tcx>(bytes: &[u8]) -> InterpResult<'tcx, &OsStr> { Ok(OsStr::from_bytes(bytes)) } #[cfg(not(unix))] pub fn bytes_to_os_str<'tcx>(bytes: &[u8]) -> InterpResult<'tcx, &OsStr> { + // We cannot use `from_os_str_bytes_unchecked` here since we can't trust `bytes`. let s = std::str::from_utf8(bytes) .map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?; Ok(OsStr::new(s)) @@ -97,7 +83,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ptr: Pointer>, size: u64, ) -> InterpResult<'tcx, (bool, u64)> { - let bytes = os_str_to_bytes(os_str)?; + let bytes = os_str.as_os_str_bytes(); self.eval_context_mut().write_c_str(bytes, ptr, size) } @@ -155,7 +141,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0` terminator. let this = self.eval_context_mut(); - let arg_type = this.tcx.mk_array(this.tcx.types.u8, size); + let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u8, size); let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?; let (written, _) = self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap(); assert!(written); @@ -171,7 +157,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let size = u64::try_from(os_str.len()).unwrap().checked_add(1).unwrap(); // Make space for `0x0000` terminator. let this = self.eval_context_mut(); - let arg_type = this.tcx.mk_array(this.tcx.types.u16, size); + let arg_type = Ty::new_array(this.tcx.tcx, this.tcx.types.u16, size); let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?; let (written, _) = self.write_os_str_to_wide_str(os_str, arg_place.ptr, size, /*truncate*/ false).unwrap(); diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index 18ae01a19f914..7aefdfcb976af 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -63,8 +63,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [payload] = this.check_shim(abi, Abi::Rust, link_name, args)?; let payload = this.read_scalar(payload)?; let thread = this.active_thread_mut(); - assert!(thread.panic_payload.is_none(), "the panic runtime should avoid double-panics"); - thread.panic_payload = Some(payload); + thread.panic_payloads.push(payload); // Jump to the unwind block to begin unwinding. this.unwind_to_block(unwind)?; @@ -146,7 +145,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // The Thread's `panic_payload` holds what was passed to `miri_start_panic`. // This is exactly the second argument we need to pass to `catch_fn`. - let payload = this.active_thread_mut().panic_payload.take().unwrap(); + let payload = this.active_thread_mut().panic_payloads.pop().unwrap(); // Push the `catch_fn` stackframe. let f_instance = this.get_ptr_fn(catch_unwind.catch_fn)?.as_instance()?; diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs index 756dec1beca45..d6d0483f5e3ac 100644 --- a/src/tools/miri/src/shims/time.rs +++ b/src/tools/miri/src/shims/time.rs @@ -25,6 +25,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.assert_target_os_is_unix("clock_gettime"); let clk_id = this.read_scalar(clk_id_op)?.to_i32()?; + let tp = this.deref_pointer_as(tp_op, this.libc_ty_layout("timespec"))?; let absolute_clocks; let mut relative_clocks; @@ -76,7 +77,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let tv_sec = duration.as_secs(); let tv_nsec = duration.subsec_nanos(); - this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &this.deref_operand(tp_op)?)?; + this.write_int_fields(&[tv_sec.into(), tv_nsec.into()], &tp)?; Ok(Scalar::from_i32(0)) } @@ -91,6 +92,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.assert_target_os_is_unix("gettimeofday"); this.check_no_isolation("`gettimeofday`")?; + let tv = this.deref_pointer_as(tv_op, this.libc_ty_layout("timeval"))?; + // Using tz is obsolete and should always be null let tz = this.read_pointer(tz_op)?; if !this.ptr_is_null(tz)? { @@ -103,7 +106,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let tv_sec = duration.as_secs(); let tv_usec = duration.subsec_micros(); - this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &this.deref_operand(tv_op)?)?; + this.write_int_fields(&[tv_sec.into(), tv_usec.into()], &tv)?; Ok(0) } @@ -118,6 +121,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.assert_target_os("windows", "GetSystemTimeAsFileTime"); this.check_no_isolation("`GetSystemTimeAsFileTime`")?; + let filetime = this.deref_pointer_as(LPFILETIME_op, this.windows_ty_layout("FILETIME"))?; + let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC"); let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC"); let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH"); @@ -131,10 +136,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap(); let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap(); - this.write_int_fields( - &[dwLowDateTime.into(), dwHighDateTime.into()], - &this.deref_operand(LPFILETIME_op)?, - )?; + this.write_int_fields(&[dwLowDateTime.into(), dwHighDateTime.into()], &filetime)?; Ok(()) } @@ -154,10 +156,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let qpc = i64::try_from(duration.as_nanos()).map_err(|_| { err_unsup_format!("programs running longer than 2^63 nanoseconds are not supported") })?; - this.write_scalar( - Scalar::from_i64(qpc), - &this.deref_operand(lpPerformanceCount_op)?.into(), - )?; + this.write_scalar(Scalar::from_i64(qpc), &this.deref_pointer(lpPerformanceCount_op)?)?; Ok(Scalar::from_i32(-1)) // return non-zero on success } @@ -177,7 +176,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // and thus 10^9 counts per second. this.write_scalar( Scalar::from_i64(1_000_000_000), - &this.deref_operand(lpFrequency_op)?.into(), + &this.deref_pointer_as(lpFrequency_op, this.machine.layouts.u64)?, )?; Ok(Scalar::from_i32(-1)) // Return non-zero on success } @@ -204,7 +203,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.assert_target_os("macos", "mach_timebase_info"); - let info = this.deref_operand(info_op)?; + let info = this.deref_pointer_as(info_op, this.libc_ty_layout("mach_timebase_info"))?; // Since our emulated ticks in `mach_absolute_time` *are* nanoseconds, // no scaling needs to happen. @@ -223,7 +222,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.assert_target_os_is_unix("nanosleep"); - let duration = match this.read_timespec(&this.deref_operand(req_op)?)? { + let req = this.deref_pointer_as(req_op, this.libc_ty_layout("timespec"))?; + + let duration = match this.read_timespec(&req)? { Some(duration) => duration, None => { let einval = this.eval_libc("EINVAL"); diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index c371e85c312e8..3a48019053563 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -10,6 +10,7 @@ use rustc_target::spec::abi::Abi; use crate::*; use shims::foreign_items::EmulateByNameResult; use shims::unix::fs::EvalContextExt as _; +use shims::unix::mem::EvalContextExt as _; use shims::unix::sync::EvalContextExt as _; use shims::unix::thread::EvalContextExt as _; @@ -190,7 +191,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Allocation "posix_memalign" => { let [ret, align, size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let ret = this.deref_operand(ret)?; + let ret = this.deref_pointer(ret)?; let align = this.read_target_usize(align)?; let size = this.read_target_usize(size)?; // Align must be power of 2, and also at least ptr-sized (POSIX rules). @@ -200,19 +201,30 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_int(einval, dest)?; } else { if size == 0 { - this.write_null(&ret.into())?; + this.write_null(&ret)?; } else { let ptr = this.allocate_ptr( Size::from_bytes(size), Align::from_bytes(align).unwrap(), MiriMemoryKind::C.into(), )?; - this.write_pointer(ptr, &ret.into())?; + this.write_pointer(ptr, &ret)?; } this.write_null(dest)?; } } + "mmap" => { + let [addr, length, prot, flags, fd, offset] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let ptr = this.mmap(addr, length, prot, flags, fd, offset)?; + this.write_scalar(ptr, dest)?; + } + "munmap" => { + let [addr, length] = this.check_shim(abi, Abi::C {unwind: false}, link_name, args)?; + let result = this.munmap(addr, length)?; + this.write_scalar(result, dest)?; + } + // Dynamic symbol loading "dlsym" => { let [handle, symbol] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -259,7 +271,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Thread-local storage "pthread_key_create" => { let [key, dtor] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let key_place = this.deref_operand(key)?; + let key_place = this.deref_pointer_as(key, this.libc_ty_layout("pthread_key_t"))?; let dtor = this.read_pointer(dtor)?; // Extract the function type out of the signature (that seems easier than constructing it ourselves). @@ -281,7 +293,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Create key and write it into the memory where `key_ptr` wants it. let key = this.machine.tls.create_tls_key(dtor, key_layout.size)?; - this.write_scalar(Scalar::from_uint(key, key_layout.size), &key_place.into())?; + this.write_scalar(Scalar::from_uint(key, key_layout.size), &key_place)?; // Return success (`0`). this.write_null(dest)?; @@ -494,9 +506,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "pthread_attr_getguardsize" if this.frame_in_std() => { let [_attr, guard_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let guard_size = this.deref_operand(guard_size)?; + let guard_size = this.deref_pointer(guard_size)?; let guard_size_layout = this.libc_ty_layout("size_t"); - this.write_scalar(Scalar::from_uint(this.machine.page_size, guard_size_layout.size), &guard_size.into())?; + this.write_scalar(Scalar::from_uint(this.machine.page_size, guard_size_layout.size), &guard_size)?; // Return success (`0`). this.write_null(dest)?; @@ -520,17 +532,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Hence we can mostly ignore the input `attr_place`. let [attr_place, addr_place, size_place] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let _attr_place = this.deref_operand(attr_place)?; - let addr_place = this.deref_operand(addr_place)?; - let size_place = this.deref_operand(size_place)?; + let _attr_place = this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?; + let addr_place = this.deref_pointer(addr_place)?; + let size_place = this.deref_pointer(size_place)?; this.write_scalar( Scalar::from_uint(this.machine.stack_addr, this.pointer_size()), - &addr_place.into(), + &addr_place, )?; this.write_scalar( Scalar::from_uint(this.machine.stack_size, this.pointer_size()), - &size_place.into(), + &size_place, )?; // Return success (`0`). @@ -563,10 +575,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.check_no_isolation("`getpwuid_r`")?; let uid = this.read_scalar(uid)?.to_u32()?; - let pwd = this.deref_operand(pwd)?; + let pwd = this.deref_pointer_as(pwd, this.libc_ty_layout("passwd"))?; let buf = this.read_pointer(buf)?; let buflen = this.read_target_usize(buflen)?; - let result = this.deref_operand(result)?; + let result = this.deref_pointer(result)?; // Must be for "us". if uid != crate::shims::unix::UID { @@ -575,20 +587,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Reset all fields to `uninit` to make sure nobody reads them. // (This is a std-only shim so we are okay with such hacks.) - this.write_uninit(&pwd.into())?; + this.write_uninit(&pwd)?; // We only set the home_dir field. #[allow(deprecated)] let home_dir = std::env::home_dir().unwrap(); let (written, _) = this.write_path_to_c_str(&home_dir, buf, buflen)?; - let pw_dir = this.mplace_field_named(&pwd, "pw_dir")?; - this.write_pointer(buf, &pw_dir.into())?; + let pw_dir = this.project_field_named(&pwd, "pw_dir")?; + this.write_pointer(buf, &pw_dir)?; if written { - this.write_pointer(pwd.ptr, &result.into())?; + this.write_pointer(pwd.ptr, &result)?; this.write_null(dest)?; } else { - this.write_null(&result.into())?; + this.write_null(&result)?; this.write_scalar(this.eval_libc("ERANGE"), dest)?; } } diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index d1b09cd7b558b..beaef22075b40 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::borrow::Cow; use std::collections::BTreeMap; use std::convert::TryInto; @@ -16,7 +17,6 @@ use rustc_target::abi::{Align, Size}; use crate::shims::os_str::bytes_to_os_str; use crate::*; -use shims::os_str::os_str_to_bytes; use shims::time::system_time_to_duration; #[derive(Debug)] @@ -25,7 +25,7 @@ pub struct FileHandle { writable: bool, } -pub trait FileDescriptor: std::fmt::Debug + helpers::AsAny { +pub trait FileDescriptor: std::fmt::Debug + Any { fn name(&self) -> &'static str; fn read<'tcx>( @@ -73,6 +73,18 @@ pub trait FileDescriptor: std::fmt::Debug + helpers::AsAny { } } +impl dyn FileDescriptor { + #[inline(always)] + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref() + } + + #[inline(always)] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + (self as &mut dyn Any).downcast_mut() + } +} + impl FileDescriptor for FileHandle { fn name(&self) -> &'static str { "FILE" @@ -345,7 +357,8 @@ trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx let (created_sec, created_nsec) = metadata.created.unwrap_or((0, 0)); let (modified_sec, modified_nsec) = metadata.modified.unwrap_or((0, 0)); - let buf = this.deref_operand(buf_op)?; + let buf = this.deref_pointer_as(buf_op, this.libc_ty_layout("stat"))?; + this.write_int_fields_named( &[ ("st_dev", 0), @@ -689,7 +702,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { // FIXME: Support fullfsync for all FDs let FileHandle { file, writable } = - file_descriptor.as_any().downcast_ref::().ok_or_else(|| { + file_descriptor.downcast_ref::().ok_or_else(|| { err_unsup_format!( "`F_FULLFSYNC` is only supported on file-backed file descriptors" ) @@ -1014,15 +1027,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { return Ok(-1); } - // Under normal circumstances, we would use `deref_operand(statxbuf_op)` to produce a - // proper `MemPlace` and then write the results of this function to it. However, the - // `syscall` function is untyped. This means that all the `statx` parameters are provided - // as `isize`s instead of having the proper types. Thus, we have to recover the layout of - // `statxbuf_op` by using the `libc::statx` struct type. - let statxbuf = { - let statx_layout = this.libc_ty_layout("statx"); - MPlaceTy::from_aligned_ptr(statxbuf_ptr, statx_layout) - }; + let statxbuf = this.deref_pointer_as(statxbuf_op, this.libc_ty_layout("statx"))?; let path = this.read_path_from_c_str(pathname_ptr)?.into_owned(); // See for a discussion of argument sizes. @@ -1149,7 +1154,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ("tv_sec", access_sec.into()), ("tv_nsec", access_nsec.into()), ], - &this.mplace_field_named(&statxbuf, "stx_atime")?, + &this.project_field_named(&statxbuf, "stx_atime")?, )?; #[rustfmt::skip] this.write_int_fields_named( @@ -1157,7 +1162,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ("tv_sec", created_sec.into()), ("tv_nsec", created_nsec.into()), ], - &this.mplace_field_named(&statxbuf, "stx_btime")?, + &this.project_field_named(&statxbuf, "stx_btime")?, )?; #[rustfmt::skip] this.write_int_fields_named( @@ -1165,7 +1170,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ("tv_sec", 0.into()), ("tv_nsec", 0.into()), ], - &this.mplace_field_named(&statxbuf, "stx_ctime")?, + &this.project_field_named(&statxbuf, "stx_ctime")?, )?; #[rustfmt::skip] this.write_int_fields_named( @@ -1173,7 +1178,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ("tv_sec", modified_sec.into()), ("tv_nsec", modified_nsec.into()), ], - &this.mplace_field_named(&statxbuf, "stx_mtime")?, + &this.project_field_named(&statxbuf, "stx_mtime")?, )?; Ok(0) @@ -1339,7 +1344,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let mut name = dir_entry.file_name(); // not a Path as there are no separators! name.push("\0"); // Add a NUL terminator - let name_bytes = os_str_to_bytes(&name)?; + let name_bytes = name.as_os_str_bytes(); let name_len = u64::try_from(name_bytes.len()).unwrap(); let dirent64_layout = this.libc_ty_layout("dirent64"); @@ -1428,8 +1433,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // pub d_name: [c_char; 1024], // } - let entry_place = this.deref_operand(entry_op)?; - let name_place = this.mplace_field(&entry_place, 5)?; + let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?; + let name_place = this.project_field(&entry_place, 5)?; let file_name = dir_entry.file_name(); // not a Path as there are no separators! let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str( @@ -1444,8 +1449,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ); } - let entry_place = this.deref_operand(entry_op)?; - // If the host is a Unix system, fill in the inode number with its real value. // If not, use 0 as a fallback value. #[cfg(unix)] @@ -1466,14 +1469,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { &entry_place, )?; - let result_place = this.deref_operand(result_op)?; - this.write_scalar(this.read_scalar(entry_op)?, &result_place.into())?; + let result_place = this.deref_pointer(result_op)?; + this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; 0 } None => { // end of stream: return 0, assign *result=NULL - this.write_null(&this.deref_operand(result_op)?.into())?; + this.write_null(&this.deref_pointer(result_op)?)?; 0 } Some(Err(e)) => @@ -1532,7 +1535,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(file_descriptor) = this.machine.file_handler.handles.get_mut(&fd) { // FIXME: Support ftruncate64 for all FDs let FileHandle { file, writable } = - file_descriptor.as_any().downcast_ref::().ok_or_else(|| { + file_descriptor.downcast_ref::().ok_or_else(|| { err_unsup_format!( "`ftruncate64` is only supported on file-backed file descriptors" ) @@ -1578,7 +1581,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { // FIXME: Support fsync for all FDs let FileHandle { file, writable } = - file_descriptor.as_any().downcast_ref::().ok_or_else(|| { + file_descriptor.downcast_ref::().ok_or_else(|| { err_unsup_format!("`fsync` is only supported on file-backed file descriptors") })?; let io_result = maybe_sync_file(file, *writable, File::sync_all); @@ -1603,7 +1606,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { // FIXME: Support fdatasync for all FDs let FileHandle { file, writable } = - file_descriptor.as_any().downcast_ref::().ok_or_else(|| { + file_descriptor.downcast_ref::().ok_or_else(|| { err_unsup_format!( "`fdatasync` is only supported on file-backed file descriptors" ) @@ -1653,7 +1656,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(file_descriptor) = this.machine.file_handler.handles.get(&fd) { // FIXME: Support sync_data_range for all FDs let FileHandle { file, writable } = - file_descriptor.as_any().downcast_ref::().ok_or_else(|| { + file_descriptor.downcast_ref::().ok_or_else(|| { err_unsup_format!( "`sync_data_range` is only supported on file-backed file descriptors" ) @@ -1695,7 +1698,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Cow::Borrowed(resolved.as_ref()), crate::shims::os_str::PathConversion::HostToTarget, ); - let mut path_bytes = crate::shims::os_str::os_str_to_bytes(resolved.as_ref())?; + let mut path_bytes = resolved.as_os_str_bytes(); let bufsize: usize = bufsize.try_into().unwrap(); if path_bytes.len() > bufsize { path_bytes = &path_bytes[..bufsize] @@ -1963,7 +1966,6 @@ impl FileMetadata { let file = match option { Some(file_descriptor) => &file_descriptor - .as_any() .downcast_ref::() .ok_or_else(|| { err_unsup_format!( diff --git a/src/tools/miri/src/shims/unix/linux/fd.rs b/src/tools/miri/src/shims/unix/linux/fd.rs index 3c263e4df9288..22fbb6da95a49 100644 --- a/src/tools/miri/src/shims/unix/linux/fd.rs +++ b/src/tools/miri/src/shims/unix/linux/fd.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + use rustc_middle::ty::ScalarInt; use crate::*; @@ -7,8 +9,6 @@ use socketpair::SocketPair; use shims::unix::fs::EvalContextExt as _; -use std::cell::Cell; - pub mod epoll; pub mod event; pub mod socketpair; @@ -71,17 +71,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL"); if op == epoll_ctl_add || op == epoll_ctl_mod { - let event = this.deref_operand(event)?; + let event = this.deref_pointer_as(event, this.libc_ty_layout("epoll_event"))?; - let events = this.mplace_field(&event, 0)?; - let events = this.read_scalar(&events.into())?.to_u32()?; - let data = this.mplace_field(&event, 1)?; - let data = this.read_scalar(&data.into())?; + let events = this.project_field(&event, 0)?; + let events = this.read_scalar(&events)?.to_u32()?; + let data = this.project_field(&event, 1)?; + let data = this.read_scalar(&data)?; let event = EpollEvent { events, data }; if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { let epfd = epfd - .as_any_mut() .downcast_mut::() .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; @@ -93,7 +92,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } else if op == epoll_ctl_del { if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { let epfd = epfd - .as_any_mut() .downcast_mut::() .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_ctl`"))?; @@ -154,7 +152,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { let _epfd = epfd - .as_any_mut() .downcast_mut::() .ok_or_else(|| err_unsup_format!("non-epoll FD passed to `epoll_wait`"))?; @@ -181,6 +178,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// `EFD_SEMAPHORE` - miri does not support semaphore-like semantics. /// /// + #[expect(clippy::needless_if)] fn eventfd( &mut self, val: &OpTy<'tcx, Provenance>, @@ -239,7 +237,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let _domain = this.read_scalar(domain)?.to_i32()?; let _type_ = this.read_scalar(type_)?.to_i32()?; let _protocol = this.read_scalar(protocol)?.to_i32()?; - let sv = this.deref_operand(sv)?; + let sv = this.deref_pointer(sv)?; let fh = &mut this.machine.file_handler; let sv0 = fh.insert_fd(Box::new(SocketPair)); @@ -247,8 +245,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let sv1 = fh.insert_fd(Box::new(SocketPair)); let sv1 = ScalarInt::try_from_int(sv1, sv.layout.size).unwrap(); - this.write_scalar(sv0, &sv.into())?; - this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?.into())?; + this.write_scalar(sv0, &sv)?; + this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?; Ok(Scalar::from_i32(0)) } diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs index 4cb7ee8efca7c..1bd751c59811f 100644 --- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs @@ -7,6 +7,7 @@ use crate::*; use shims::foreign_items::EmulateByNameResult; use shims::unix::fs::EvalContextExt as _; use shims::unix::linux::fd::EvalContextExt as _; +use shims::unix::linux::mem::EvalContextExt as _; use shims::unix::linux::sync::futex; use shims::unix::sync::EvalContextExt as _; use shims::unix::thread::EvalContextExt as _; @@ -68,6 +69,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.eventfd(val, flag)?; this.write_scalar(result, dest)?; } + "mremap" => { + let [old_address, old_size, new_size, flags] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let ptr = this.mremap(old_address, old_size, new_size, flags)?; + this.write_scalar(ptr, dest)?; + } "socketpair" => { let [domain, type_, protocol, sv] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; @@ -191,7 +198,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; this.read_scalar(pid)?.to_i32()?; this.read_target_usize(cpusetsize)?; - this.deref_operand(mask)?; + this.deref_pointer_as(mask, this.libc_ty_layout("cpu_set_t"))?; // FIXME: we just return an error; `num_cpus` then falls back to `sysconf`. let einval = this.eval_libc("EINVAL"); this.set_last_error(einval)?; diff --git a/src/tools/miri/src/shims/unix/linux/mem.rs b/src/tools/miri/src/shims/unix/linux/mem.rs new file mode 100644 index 0000000000000..026fd6ae5e7ca --- /dev/null +++ b/src/tools/miri/src/shims/unix/linux/mem.rs @@ -0,0 +1,67 @@ +//! This follows the pattern in src/shims/unix/mem.rs: We only support uses of mremap that would +//! correspond to valid uses of realloc. + +use crate::*; +use rustc_target::abi::Size; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn mremap( + &mut self, + old_address: &OpTy<'tcx, Provenance>, + old_size: &OpTy<'tcx, Provenance>, + new_size: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let old_address = this.read_target_usize(old_address)?; + let old_size = this.read_target_usize(old_size)?; + let new_size = this.read_target_usize(new_size)?; + let flags = this.read_scalar(flags)?.to_i32()?; + + // old_address must be a multiple of the page size + #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero + if old_address % this.machine.page_size != 0 || new_size == 0 { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(this.eval_libc("MAP_FAILED")); + } + + if flags & this.eval_libc_i32("MREMAP_FIXED") != 0 { + throw_unsup_format!("Miri does not support mremap wth MREMAP_FIXED"); + } + + if flags & this.eval_libc_i32("MREMAP_DONTUNMAP") != 0 { + throw_unsup_format!("Miri does not support mremap wth MREMAP_DONTUNMAP"); + } + + if flags & this.eval_libc_i32("MREMAP_MAYMOVE") == 0 { + // We only support MREMAP_MAYMOVE, so not passing the flag is just a failure + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(Scalar::from_maybe_pointer(Pointer::null(), this)); + } + + let old_address = Machine::ptr_from_addr_cast(this, old_address)?; + let align = this.machine.page_align(); + let ptr = this.reallocate_ptr( + old_address, + Some((Size::from_bytes(old_size), align)), + Size::from_bytes(new_size), + align, + MiriMemoryKind::Mmap.into(), + )?; + if let Some(increase) = new_size.checked_sub(old_size) { + // We just allocated this, the access is definitely in-bounds and fits into our address space. + // mmap guarantees new mappings are zero-init. + this.write_bytes_ptr( + ptr.offset(Size::from_bytes(old_size), this).unwrap().into(), + std::iter::repeat(0u8).take(usize::try_from(increase).unwrap()), + ) + .unwrap(); + } + // Memory mappings are always exposed + Machine::expose_ptr(this, ptr)?; + + Ok(Scalar::from_pointer(ptr, this)) + } +} diff --git a/src/tools/miri/src/shims/unix/linux/mod.rs b/src/tools/miri/src/shims/unix/linux/mod.rs index 437764c3824eb..856ec226de8fc 100644 --- a/src/tools/miri/src/shims/unix/linux/mod.rs +++ b/src/tools/miri/src/shims/unix/linux/mod.rs @@ -1,4 +1,5 @@ pub mod dlsym; pub mod fd; pub mod foreign_items; +pub mod mem; pub mod sync; diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs index 6889c43004224..0474c9fd90a5c 100644 --- a/src/tools/miri/src/shims/unix/linux/sync.rs +++ b/src/tools/miri/src/shims/unix/linux/sync.rs @@ -85,8 +85,11 @@ pub fn futex<'tcx>( return Ok(()); } - // `deref_operand` but not actually dereferencing the ptr yet (it might be NULL!). - let timeout = this.ref_to_mplace(&this.read_immediate(&args[3])?)?; + // `read_timespec` will check the place when it is not null. + let timeout = this.deref_pointer_unchecked( + &this.read_immediate(&args[3])?, + this.libc_ty_layout("timespec"), + )?; let timeout_time = if this.ptr_is_null(timeout.ptr)? { None } else { diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 1271788a97ef0..f073daab8ed8e 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -86,7 +86,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { "_NSGetEnviron" => { let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; this.write_pointer( - this.machine.env_vars.environ.expect("machine must be initialized").ptr, + this.machine + .env_vars + .environ + .as_ref() + .expect("machine must be initialized") + .ptr, dest, )?; } @@ -125,7 +130,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.check_no_isolation("`_NSGetExecutablePath`")?; let buf_ptr = this.read_pointer(buf)?; - let bufsize = this.deref_operand(bufsize)?; + let bufsize = this.deref_pointer(bufsize)?; // Using the host current_exe is a bit off, but consistent with Linux // (where stdlib reads /proc/self/exe). @@ -133,16 +138,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let (written, size_needed) = this.write_path_to_c_str( &path, buf_ptr, - this.read_scalar(&bufsize.into())?.to_u32()?.into(), + this.read_scalar(&bufsize)?.to_u32()?.into(), )?; if written { this.write_null(dest)?; } else { - this.write_scalar( - Scalar::from_u32(size_needed.try_into().unwrap()), - &bufsize.into(), - )?; + this.write_scalar(Scalar::from_u32(size_needed.try_into().unwrap()), &bufsize)?; this.write_int(-1, dest)?; } } @@ -197,16 +199,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(res, dest)?; } - // Incomplete shims that we "stub out" just to get pre-main initialization code to work. - // These shims are enabled only when the caller is in the standard library. - "mmap" if this.frame_in_std() => { - // This is a horrible hack, but since the guard page mechanism calls mmap and expects a particular return value, we just give it that value. - let [addr, _, _, _, _, _] = - this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; - let addr = this.read_scalar(addr)?; - this.write_scalar(addr, dest)?; - } - _ => return Ok(EmulateByNameResult::NotSupported), }; diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs new file mode 100644 index 0000000000000..a33d784d16651 --- /dev/null +++ b/src/tools/miri/src/shims/unix/mem.rs @@ -0,0 +1,158 @@ +//! This is an incomplete implementation of mmap/munmap which is restricted in order to be +//! implementable on top of the existing memory system. The point of these function as-written is +//! to allow memory allocators written entirely in Rust to be executed by Miri. This implementation +//! does not support other uses of mmap such as file mappings. +//! +//! mmap/munmap behave a lot like alloc/dealloc, and for simple use they are exactly +//! equivalent. That is the only part we support: no MAP_FIXED or MAP_SHARED or anything +//! else that goes beyond a basic allocation API. + +use crate::*; +use rustc_target::abi::Size; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn mmap( + &mut self, + addr: &OpTy<'tcx, Provenance>, + length: &OpTy<'tcx, Provenance>, + prot: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + fd: &OpTy<'tcx, Provenance>, + offset: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + // We do not support MAP_FIXED, so the addr argument is always ignored (except for the MacOS hack) + let addr = this.read_target_usize(addr)?; + let length = this.read_target_usize(length)?; + let prot = this.read_scalar(prot)?.to_i32()?; + let flags = this.read_scalar(flags)?.to_i32()?; + let fd = this.read_scalar(fd)?.to_i32()?; + let offset = this.read_target_usize(offset)?; + + let map_private = this.eval_libc_i32("MAP_PRIVATE"); + let map_anonymous = this.eval_libc_i32("MAP_ANONYMOUS"); + let map_shared = this.eval_libc_i32("MAP_SHARED"); + let map_fixed = this.eval_libc_i32("MAP_FIXED"); + + // This is a horrible hack, but on MacOS the guard page mechanism uses mmap + // in a way we do not support. We just give it the return value it expects. + if this.frame_in_std() && this.tcx.sess.target.os == "macos" && (flags & map_fixed) != 0 { + return Ok(Scalar::from_maybe_pointer(Pointer::from_addr_invalid(addr), this)); + } + + let prot_read = this.eval_libc_i32("PROT_READ"); + let prot_write = this.eval_libc_i32("PROT_WRITE"); + + // First, we do some basic argument validation as required by mmap + if (flags & (map_private | map_shared)).count_ones() != 1 { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(Scalar::from_maybe_pointer(Pointer::null(), this)); + } + if length == 0 { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(Scalar::from_maybe_pointer(Pointer::null(), this)); + } + + // If a user tries to map a file, we want to loudly inform them that this is not going + // to work. It is possible that POSIX gives us enough leeway to return an error, but the + // outcome for the user (I need to add cfg(miri)) is the same, just more frustrating. + if fd != -1 { + throw_unsup_format!("Miri does not support file-backed memory mappings"); + } + + // POSIX says: + // [ENOTSUP] + // * MAP_FIXED or MAP_PRIVATE was specified in the flags argument and the implementation + // does not support this functionality. + // * The implementation does not support the combination of accesses requested in the + // prot argument. + // + // Miri doesn't support MAP_FIXED or any any protections other than PROT_READ|PROT_WRITE. + if flags & map_fixed != 0 || prot != prot_read | prot_write { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("ENOTSUP")))?; + return Ok(Scalar::from_maybe_pointer(Pointer::null(), this)); + } + + // Miri does not support shared mappings, or any of the other extensions that for example + // Linux has added to the flags arguments. + if flags != map_private | map_anonymous { + throw_unsup_format!( + "Miri only supports calls to mmap which set the flags argument to MAP_PRIVATE|MAP_ANONYMOUS" + ); + } + + // This is only used for file mappings, which we don't support anyway. + if offset != 0 { + throw_unsup_format!("Miri does not support non-zero offsets to mmap"); + } + + let align = this.machine.page_align(); + let map_length = this.machine.round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX); + + let ptr = + this.allocate_ptr(Size::from_bytes(map_length), align, MiriMemoryKind::Mmap.into())?; + // We just allocated this, the access is definitely in-bounds and fits into our address space. + // mmap guarantees new mappings are zero-init. + this.write_bytes_ptr( + ptr.into(), + std::iter::repeat(0u8).take(usize::try_from(map_length).unwrap()), + ) + .unwrap(); + // Memory mappings don't use provenance, and are always exposed. + Machine::expose_ptr(this, ptr)?; + + Ok(Scalar::from_pointer(ptr, this)) + } + + fn munmap( + &mut self, + addr: &OpTy<'tcx, Provenance>, + length: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let addr = this.read_target_usize(addr)?; + let length = this.read_target_usize(length)?; + + // addr must be a multiple of the page size + #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero + if addr % this.machine.page_size != 0 { + this.set_last_error(Scalar::from_i32(this.eval_libc_i32("EINVAL")))?; + return Ok(Scalar::from_i32(-1)); + } + + let length = this.machine.round_up_to_multiple_of_page_size(length).unwrap_or(u64::MAX); + + let ptr = Machine::ptr_from_addr_cast(this, addr)?; + + let Ok(ptr) = ptr.into_pointer_or_addr() else { + throw_unsup_format!("Miri only supports munmap on memory allocated directly by mmap"); + }; + let Some((alloc_id, offset, _prov)) = Machine::ptr_get_alloc(this, ptr) else { + throw_unsup_format!("Miri only supports munmap on memory allocated directly by mmap"); + }; + + // Elsewhere in this function we are careful to check what we can and throw an unsupported + // error instead of Undefined Behavior when use of this function falls outside of the + // narrow scope we support. We deliberately do not check the MemoryKind of this allocation, + // because we want to report UB on attempting to unmap memory that Rust "understands", such + // the stack, heap, or statics. + let (_kind, alloc) = this.memory.alloc_map().get(alloc_id).unwrap(); + if offset != Size::ZERO || alloc.len() as u64 != length { + throw_unsup_format!( + "Miri only supports munmap calls that exactly unmap a region previously returned by mmap" + ); + } + + let len = Size::from_bytes(alloc.len() as u64); + this.deallocate_ptr( + ptr.into(), + Some((len, this.machine.page_align())), + MemoryKind::Machine(MiriMemoryKind::Mmap), + )?; + + Ok(Scalar::from_i32(0)) + } +} diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs index 6fefb054f3c04..a8ebd369abaaa 100644 --- a/src/tools/miri/src/shims/unix/mod.rs +++ b/src/tools/miri/src/shims/unix/mod.rs @@ -2,6 +2,7 @@ pub mod dlsym; pub mod foreign_items; mod fs; +mod mem; mod sync; mod thread; diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs index 05feeac45b5c1..6666ffbd1d578 100644 --- a/src/tools/miri/src/shims/unix/sync.rs +++ b/src/tools/miri/src/shims/unix/sync.rs @@ -18,14 +18,14 @@ use crate::*; const PTHREAD_MUTEX_NORMAL_FLAG: i32 = 0x8000000; fn is_mutex_kind_default<'mir, 'tcx: 'mir>( - ecx: &mut MiriInterpCx<'mir, 'tcx>, + ecx: &MiriInterpCx<'mir, 'tcx>, kind: i32, ) -> InterpResult<'tcx, bool> { Ok(kind == ecx.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")) } fn is_mutex_kind_normal<'mir, 'tcx: 'mir>( - ecx: &mut MiriInterpCx<'mir, 'tcx>, + ecx: &MiriInterpCx<'mir, 'tcx>, kind: i32, ) -> InterpResult<'tcx, bool> { let mutex_normal_kind = ecx.eval_libc_i32("PTHREAD_MUTEX_NORMAL"); @@ -36,7 +36,13 @@ fn mutexattr_get_kind<'mir, 'tcx: 'mir>( ecx: &MiriInterpCx<'mir, 'tcx>, attr_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, i32> { - ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)?.to_i32() + ecx.deref_pointer_and_read( + attr_op, + 0, + ecx.libc_ty_layout("pthread_mutexattr_t"), + ecx.machine.layouts.i32, + )? + .to_i32() } fn mutexattr_set_kind<'mir, 'tcx: 'mir>( @@ -44,7 +50,13 @@ fn mutexattr_set_kind<'mir, 'tcx: 'mir>( attr_op: &OpTy<'tcx, Provenance>, kind: i32, ) -> InterpResult<'tcx, ()> { - ecx.write_scalar_at_offset(attr_op, 0, Scalar::from_i32(kind), ecx.machine.layouts.i32) + ecx.deref_pointer_and_write( + attr_op, + 0, + Scalar::from_i32(kind), + ecx.libc_ty_layout("pthread_mutexattr_t"), + ecx.machine.layouts.i32, + ) } // pthread_mutex_t is between 24 and 48 bytes, depending on the platform. @@ -60,14 +72,20 @@ fn mutex_get_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, mutex_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, MutexId> { - ecx.mutex_get_or_create_id(mutex_op, 4) + ecx.mutex_get_or_create_id(mutex_op, ecx.libc_ty_layout("pthread_mutex_t"), 4) } fn mutex_reset_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, mutex_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ()> { - ecx.write_scalar_at_offset(mutex_op, 4, Scalar::from_i32(0), ecx.machine.layouts.u32) + ecx.deref_pointer_and_write( + mutex_op, + 4, + Scalar::from_i32(0), + ecx.libc_ty_layout("pthread_mutex_t"), + ecx.machine.layouts.u32, + ) } fn mutex_get_kind<'mir, 'tcx: 'mir>( @@ -75,7 +93,13 @@ fn mutex_get_kind<'mir, 'tcx: 'mir>( mutex_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, i32> { let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; - ecx.read_scalar_at_offset(mutex_op, offset, ecx.machine.layouts.i32)?.to_i32() + ecx.deref_pointer_and_read( + mutex_op, + offset, + ecx.libc_ty_layout("pthread_mutex_t"), + ecx.machine.layouts.i32, + )? + .to_i32() } fn mutex_set_kind<'mir, 'tcx: 'mir>( @@ -84,7 +108,13 @@ fn mutex_set_kind<'mir, 'tcx: 'mir>( kind: i32, ) -> InterpResult<'tcx, ()> { let offset = if ecx.pointer_size().bytes() == 8 { 16 } else { 12 }; - ecx.write_scalar_at_offset(mutex_op, offset, Scalar::from_i32(kind), ecx.machine.layouts.i32) + ecx.deref_pointer_and_write( + mutex_op, + offset, + Scalar::from_i32(kind), + ecx.libc_ty_layout("pthread_mutex_t"), + ecx.machine.layouts.i32, + ) } // pthread_rwlock_t is between 32 and 56 bytes, depending on the platform. @@ -98,7 +128,7 @@ fn rwlock_get_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, rwlock_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, RwLockId> { - ecx.rwlock_get_or_create_id(rwlock_op, 4) + ecx.rwlock_get_or_create_id(rwlock_op, ecx.libc_ty_layout("pthread_rwlock_t"), 4) } // pthread_condattr_t @@ -111,7 +141,13 @@ fn condattr_get_clock_id<'mir, 'tcx: 'mir>( ecx: &MiriInterpCx<'mir, 'tcx>, attr_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, i32> { - ecx.read_scalar_at_offset(attr_op, 0, ecx.machine.layouts.i32)?.to_i32() + ecx.deref_pointer_and_read( + attr_op, + 0, + ecx.libc_ty_layout("pthread_condattr_t"), + ecx.machine.layouts.i32, + )? + .to_i32() } fn condattr_set_clock_id<'mir, 'tcx: 'mir>( @@ -119,7 +155,13 @@ fn condattr_set_clock_id<'mir, 'tcx: 'mir>( attr_op: &OpTy<'tcx, Provenance>, clock_id: i32, ) -> InterpResult<'tcx, ()> { - ecx.write_scalar_at_offset(attr_op, 0, Scalar::from_i32(clock_id), ecx.machine.layouts.i32) + ecx.deref_pointer_and_write( + attr_op, + 0, + Scalar::from_i32(clock_id), + ecx.libc_ty_layout("pthread_condattr_t"), + ecx.machine.layouts.i32, + ) } // pthread_cond_t @@ -135,21 +177,33 @@ fn cond_get_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, cond_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, CondvarId> { - ecx.condvar_get_or_create_id(cond_op, 4) + ecx.condvar_get_or_create_id(cond_op, ecx.libc_ty_layout("pthread_cond_t"), 4) } fn cond_reset_id<'mir, 'tcx: 'mir>( ecx: &mut MiriInterpCx<'mir, 'tcx>, cond_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, ()> { - ecx.write_scalar_at_offset(cond_op, 4, Scalar::from_i32(0), ecx.machine.layouts.u32) + ecx.deref_pointer_and_write( + cond_op, + 4, + Scalar::from_i32(0), + ecx.libc_ty_layout("pthread_cond_t"), + ecx.machine.layouts.u32, + ) } fn cond_get_clock_id<'mir, 'tcx: 'mir>( ecx: &MiriInterpCx<'mir, 'tcx>, cond_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, i32> { - ecx.read_scalar_at_offset(cond_op, 8, ecx.machine.layouts.i32)?.to_i32() + ecx.deref_pointer_and_read( + cond_op, + 8, + ecx.libc_ty_layout("pthread_cond_t"), + ecx.machine.layouts.i32, + )? + .to_i32() } fn cond_set_clock_id<'mir, 'tcx: 'mir>( @@ -157,7 +211,13 @@ fn cond_set_clock_id<'mir, 'tcx: 'mir>( cond_op: &OpTy<'tcx, Provenance>, clock_id: i32, ) -> InterpResult<'tcx, ()> { - ecx.write_scalar_at_offset(cond_op, 8, Scalar::from_i32(clock_id), ecx.machine.layouts.i32) + ecx.deref_pointer_and_write( + cond_op, + 8, + Scalar::from_i32(clock_id), + ecx.libc_ty_layout("pthread_cond_t"), + ecx.machine.layouts.i32, + ) } /// Try to reacquire the mutex associated with the condition variable after we @@ -285,7 +345,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // However, the way libstd uses the pthread APIs works in our favor here, so we can get away with this. // This can always be revisited to have some external state to catch double-destroys // but not complain about the above code. See https://github.com/rust-lang/miri/pull/1933 - this.write_uninit(&this.deref_operand(attr_op)?.into())?; + this.write_uninit( + &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_mutexattr_t"))?, + )?; Ok(0) } @@ -437,7 +499,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { mutex_get_id(this, mutex_op)?; // This might lead to false positives, see comment in pthread_mutexattr_destroy - this.write_uninit(&this.deref_operand(mutex_op)?.into())?; + this.write_uninit( + &this.deref_pointer_as(mutex_op, this.libc_ty_layout("pthread_mutex_t"))?, + )?; // FIXME: delete interpreter state associated with this mutex. Ok(0) @@ -560,7 +624,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { rwlock_get_id(this, rwlock_op)?; // This might lead to false positives, see comment in pthread_mutexattr_destroy - this.write_uninit(&this.deref_operand(rwlock_op)?.into())?; + this.write_uninit( + &this.deref_pointer_as(rwlock_op, this.libc_ty_layout("pthread_rwlock_t"))?, + )?; // FIXME: delete interpreter state associated with this rwlock. Ok(0) @@ -609,7 +675,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let clock_id = condattr_get_clock_id(this, attr_op)?; - this.write_scalar(Scalar::from_i32(clock_id), &this.deref_operand(clk_id_op)?.into())?; + this.write_scalar(Scalar::from_i32(clock_id), &this.deref_pointer(clk_id_op)?)?; Ok(Scalar::from_i32(0)) } @@ -624,7 +690,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { condattr_get_clock_id(this, attr_op)?; // This might lead to false positives, see comment in pthread_mutexattr_destroy - this.write_uninit(&this.deref_operand(attr_op)?.into())?; + this.write_uninit( + &this.deref_pointer_as(attr_op, this.libc_ty_layout("pthread_condattr_t"))?, + )?; Ok(0) } @@ -715,7 +783,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Extract the timeout. let clock_id = cond_get_clock_id(this, cond_op)?; - let duration = match this.read_timespec(&this.deref_operand(abstime_op)?)? { + let duration = match this + .read_timespec(&this.deref_pointer_as(abstime_op, this.libc_ty_layout("timespec"))?)? + { Some(duration) => duration, None => { let einval = this.eval_libc("EINVAL"); @@ -797,7 +867,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { cond_get_clock_id(this, cond_op)?; // This might lead to false positives, see comment in pthread_mutexattr_destroy - this.write_uninit(&this.deref_operand(cond_op)?.into())?; + this.write_uninit(&this.deref_pointer_as(cond_op, this.libc_ty_layout("pthread_cond_t"))?)?; // FIXME: delete interpreter state associated with this condvar. Ok(0) diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs index 6165cfd282307..259689348ade3 100644 --- a/src/tools/miri/src/shims/unix/thread.rs +++ b/src/tools/miri/src/shims/unix/thread.rs @@ -13,7 +13,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); - let thread_info_place = this.deref_operand(thread)?; + let thread_info_place = this.deref_pointer_as(thread, this.libc_ty_layout("pthread_t"))?; let start_routine = this.read_pointer(start_routine)?; diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs index b8bb7f6245210..cb90ed57ffe99 100644 --- a/src/tools/miri/src/shims/windows/foreign_items.rs +++ b/src/tools/miri/src/shims/windows/foreign_items.rs @@ -10,8 +10,6 @@ use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle}; use shims::windows::sync::EvalContextExt as _; use shims::windows::thread::EvalContextExt as _; -use smallvec::SmallVec; - impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn emulate_foreign_item_by_name( @@ -92,7 +90,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let buf = this.read_pointer(buf)?; let n = this.read_scalar(n)?.to_u32()?; let byte_offset = this.read_target_usize(byte_offset)?; // is actually a pointer - let io_status_block = this.deref_operand(io_status_block)?; + let io_status_block = this + .deref_pointer_as(io_status_block, this.windows_ty_layout("IO_STATUS_BLOCK"))?; if byte_offset != 0 { throw_unsup_format!( @@ -123,10 +122,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // We have to put the result into io_status_block. if let Some(n) = written { let io_status_information = - this.mplace_field_named(&io_status_block, "Information")?; + this.project_field_named(&io_status_block, "Information")?; this.write_scalar( Scalar::from_target_usize(n.into(), this), - &io_status_information.into(), + &io_status_information, )?; } // Return whether this was a success. >= 0 is success. @@ -187,54 +186,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // Also called from `page_size` crate. let [system_info] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; - let system_info = this.deref_operand(system_info)?; + let system_info = + this.deref_pointer_as(system_info, this.windows_ty_layout("SYSTEM_INFO"))?; // Initialize with `0`. this.write_bytes_ptr( system_info.ptr, iter::repeat(0u8).take(system_info.layout.size.bytes_usize()), )?; // Set selected fields. - let word_layout = this.machine.layouts.u16; - let dword_layout = this.machine.layouts.u32; - let usize_layout = this.machine.layouts.usize; - - // Using `mplace_field` is error-prone, see: https://github.com/rust-lang/miri/issues/2136. - // Pointer fields have different sizes on different targets. - // To avoid all these issue we calculate the offsets ourselves. - let field_sizes = [ - word_layout.size, // 0, wProcessorArchitecture : WORD - word_layout.size, // 1, wReserved : WORD - dword_layout.size, // 2, dwPageSize : DWORD - usize_layout.size, // 3, lpMinimumApplicationAddress : LPVOID - usize_layout.size, // 4, lpMaximumApplicationAddress : LPVOID - usize_layout.size, // 5, dwActiveProcessorMask : DWORD_PTR - dword_layout.size, // 6, dwNumberOfProcessors : DWORD - dword_layout.size, // 7, dwProcessorType : DWORD - dword_layout.size, // 8, dwAllocationGranularity : DWORD - word_layout.size, // 9, wProcessorLevel : WORD - word_layout.size, // 10, wProcessorRevision : WORD - ]; - let field_offsets: SmallVec<[Size; 11]> = field_sizes - .iter() - .copied() - .scan(Size::ZERO, |a, x| { - let res = Some(*a); - *a = a.checked_add(x, this).unwrap(); - res - }) - .collect(); - - // Set page size. - let page_size = system_info.offset(field_offsets[2], dword_layout, &this.tcx)?; - this.write_scalar( - Scalar::from_int(this.machine.page_size, dword_layout.size), - &page_size.into(), - )?; - // Set number of processors. - let num_cpus = system_info.offset(field_offsets[6], dword_layout, &this.tcx)?; - this.write_scalar( - Scalar::from_int(this.machine.num_cpus, dword_layout.size), - &num_cpus.into(), + this.write_int_fields_named( + &[ + ("dwPageSize", this.machine.page_size.into()), + ("dwNumberOfProcessors", this.machine.num_cpus.into()), + ], + &system_info, )?; } @@ -426,7 +391,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [console, buffer_info] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; this.read_target_isize(console)?; - this.deref_operand(buffer_info)?; + // FIXME: this should use deref_pointer_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std + this.deref_pointer(buffer_info)?; // Indicate an error. // FIXME: we should set last_error, but to what? this.write_null(dest)?; @@ -542,7 +508,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let [console, mode] = this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; this.read_target_isize(console)?; - this.deref_operand(mode)?; + this.deref_pointer(mode)?; // Indicate an error. this.write_null(dest)?; } diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs index b9cc3e15be9fe..c8c8173aa51dd 100644 --- a/src/tools/miri/src/shims/windows/sync.rs +++ b/src/tools/miri/src/shims/windows/sync.rs @@ -7,10 +7,6 @@ use crate::concurrency::sync::{CondvarLock, RwLockMode}; use crate::concurrency::thread::MachineCallback; use crate::*; -const SRWLOCK_ID_OFFSET: u64 = 0; -const INIT_ONCE_ID_OFFSET: u64 = 0; -const CONDVAR_ID_OFFSET: u64 = 0; - impl<'mir, 'tcx> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { /// Try to reacquire the lock associated with the condition variable after we @@ -41,6 +37,33 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { Ok(()) } + + // Windows sync primitives are pointer sized. + // We only use the first 4 bytes for the id. + + fn srwlock_get_id( + &mut self, + rwlock_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, RwLockId> { + let this = self.eval_context_mut(); + this.rwlock_get_or_create_id(rwlock_op, this.windows_ty_layout("SRWLOCK"), 0) + } + + fn init_once_get_id( + &mut self, + init_once_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, InitOnceId> { + let this = self.eval_context_mut(); + this.init_once_get_or_create_id(init_once_op, this.windows_ty_layout("INIT_ONCE"), 0) + } + + fn condvar_get_id( + &mut self, + condvar_op: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, CondvarId> { + let this = self.eval_context_mut(); + this.condvar_get_or_create_id(condvar_op, this.windows_ty_layout("CONDITION_VARIABLE"), 0) + } } impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} @@ -48,7 +71,7 @@ impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn AcquireSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let id = this.srwlock_get_id(lock_op)?; let active_thread = this.get_active_thread(); if this.rwlock_is_locked(id) { @@ -72,7 +95,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { lock_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let id = this.srwlock_get_id(lock_op)?; let active_thread = this.get_active_thread(); if this.rwlock_is_locked(id) { @@ -86,7 +109,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn ReleaseSRWLockExclusive(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let id = this.srwlock_get_id(lock_op)?; let active_thread = this.get_active_thread(); if !this.rwlock_writer_unlock(id, active_thread) { @@ -101,7 +124,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn AcquireSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let id = this.srwlock_get_id(lock_op)?; let active_thread = this.get_active_thread(); if this.rwlock_is_write_locked(id) { @@ -118,7 +141,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { lock_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let id = this.srwlock_get_id(lock_op)?; let active_thread = this.get_active_thread(); if this.rwlock_is_write_locked(id) { @@ -131,7 +154,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn ReleaseSRWLockShared(&mut self, lock_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let id = this.srwlock_get_id(lock_op)?; let active_thread = this.get_active_thread(); if !this.rwlock_reader_unlock(id, active_thread) { @@ -154,9 +177,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let this = self.eval_context_mut(); let active_thread = this.get_active_thread(); - let id = this.init_once_get_or_create_id(init_once_op, INIT_ONCE_ID_OFFSET)?; + let id = this.init_once_get_id(init_once_op)?; let flags = this.read_scalar(flags_op)?.to_u32()?; - let pending_place = this.deref_operand(pending_op)?.into(); + let pending_place = this.deref_pointer(pending_op)?.into(); let context = this.read_pointer(context_op)?; if flags != 0 { @@ -229,7 +252,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let id = this.init_once_get_or_create_id(init_once_op, INIT_ONCE_ID_OFFSET)?; + let id = this.init_once_get_id(init_once_op)?; let flags = this.read_scalar(flags_op)?.to_u32()?; let context = this.read_pointer(context_op)?; @@ -300,7 +323,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let layout = this.machine.layouts.uint(size).unwrap(); let futex_val = this .read_scalar_atomic(&MPlaceTy::from_aligned_ptr(ptr, layout), AtomicReadOrd::Relaxed)?; - let compare_val = this.read_scalar(&MPlaceTy::from_aligned_ptr(compare, layout).into())?; + let compare_val = this.read_scalar(&MPlaceTy::from_aligned_ptr(compare, layout))?; if futex_val == compare_val { // If the values are the same, we have to block. @@ -372,8 +395,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?; - let lock_id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?; + let condvar_id = this.condvar_get_id(condvar_op)?; + let lock_id = this.srwlock_get_id(lock_op)?; let timeout_ms = this.read_scalar(timeout_op)?.to_u32()?; let flags = this.read_scalar(flags_op)?.to_u32()?; @@ -456,7 +479,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { fn WakeConditionVariable(&mut self, condvar_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?; + let condvar_id = this.condvar_get_id(condvar_op)?; if let Some((thread, lock)) = this.condvar_signal(condvar_id) { if let CondvarLock::RwLock { id, mode } = lock { @@ -475,7 +498,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { condvar_op: &OpTy<'tcx, Provenance>, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); - let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?; + let condvar_id = this.condvar_get_id(condvar_op)?; while let Some((thread, lock)) = this.condvar_signal(condvar_id) { if let CondvarLock::RwLock { id, mode } = lock { diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs index 9cbae15885985..3953a881a720e 100644 --- a/src/tools/miri/src/shims/windows/thread.rs +++ b/src/tools/miri/src/shims/windows/thread.rs @@ -29,7 +29,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let thread = if this.ptr_is_null(this.read_pointer(thread_op)?)? { None } else { - let thread_info_place = this.deref_operand(thread_op)?; + let thread_info_place = this.deref_pointer(thread_op)?; Some(thread_info_place) }; diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs new file mode 100644 index 0000000000000..36e673129ded4 --- /dev/null +++ b/src/tools/miri/src/shims/x86/mod.rs @@ -0,0 +1 @@ +pub(super) mod sse; diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs new file mode 100644 index 0000000000000..b18441bb408b5 --- /dev/null +++ b/src/tools/miri/src/shims/x86/sse.rs @@ -0,0 +1,585 @@ +use rustc_apfloat::{ieee::Single, Float as _}; +use rustc_middle::mir; +use rustc_span::Symbol; +use rustc_target::spec::abi::Abi; + +use rand::Rng as _; + +use crate::*; +use shims::foreign_items::EmulateByNameResult; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn emulate_x86_sse_intrinsic( + &mut self, + link_name: Symbol, + abi: Abi, + args: &[OpTy<'tcx, Provenance>], + dest: &PlaceTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> { + let this = self.eval_context_mut(); + // Prefix should have already been checked. + let unprefixed_name = link_name.as_str().strip_prefix("llvm.x86.sse.").unwrap(); + // All these intrinsics operate on 128-bit (f32x4) SIMD vectors unless stated otherwise. + // Many intrinsic names are sufixed with "ps" (packed single) or "ss" (scalar single), + // where single means single precision floating point (f32). "ps" means thet the operation + // is performed on each element of the vector, while "ss" means that the operation is + // performed only on the first element, copying the remaining elements from the input + // vector (for binary operations, from the left-hand side). + match unprefixed_name { + // Used to implement _mm_{add,sub,mul,div,min,max}_ss functions. + // Performs the operations on the first component of `left` and + // `right` and copies the remaining components from `left`. + "add.ss" | "sub.ss" | "mul.ss" | "div.ss" | "min.ss" | "max.ss" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match unprefixed_name { + "add.ss" => FloatBinOp::Arith(mir::BinOp::Add), + "sub.ss" => FloatBinOp::Arith(mir::BinOp::Sub), + "mul.ss" => FloatBinOp::Arith(mir::BinOp::Mul), + "div.ss" => FloatBinOp::Arith(mir::BinOp::Div), + "min.ss" => FloatBinOp::Min, + "max.ss" => FloatBinOp::Max, + _ => unreachable!(), + }; + + bin_op_ss(this, which, left, right, dest)?; + } + // Used to implement _mm_min_ps and _mm_max_ps functions. + // Note that the semantics are a bit different from Rust simd_min + // and simd_max intrinsics regarding handling of NaN and -0.0: Rust + // matches the IEEE min/max operations, while x86 has different + // semantics. + "min.ps" | "max.ps" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match unprefixed_name { + "min.ps" => FloatBinOp::Min, + "max.ps" => FloatBinOp::Max, + _ => unreachable!(), + }; + + bin_op_ps(this, which, left, right, dest)?; + } + // Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions. + // Performs the operations on the first component of `op` and + // copies the remaining components from `op`. + "sqrt.ss" | "rcp.ss" | "rsqrt.ss" => { + let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match unprefixed_name { + "sqrt.ss" => FloatUnaryOp::Sqrt, + "rcp.ss" => FloatUnaryOp::Rcp, + "rsqrt.ss" => FloatUnaryOp::Rsqrt, + _ => unreachable!(), + }; + + unary_op_ss(this, which, op, dest)?; + } + // Used to implement _mm_{sqrt,rcp,rsqrt}_ss functions. + // Performs the operations on all components of `op`. + "sqrt.ps" | "rcp.ps" | "rsqrt.ps" => { + let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match unprefixed_name { + "sqrt.ps" => FloatUnaryOp::Sqrt, + "rcp.ps" => FloatUnaryOp::Rcp, + "rsqrt.ps" => FloatUnaryOp::Rsqrt, + _ => unreachable!(), + }; + + unary_op_ps(this, which, op, dest)?; + } + // Used to implement the _mm_cmp_ss function. + // Performs a comparison operation on the first component of `left` + // and `right`, returning 0 if false or `u32::MAX` if true. The remaining + // components are copied from `left`. + "cmp.ss" => { + let [left, right, imm] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match this.read_scalar(imm)?.to_i8()? { + 0 => FloatBinOp::Cmp(FloatCmpOp::Eq), + 1 => FloatBinOp::Cmp(FloatCmpOp::Lt), + 2 => FloatBinOp::Cmp(FloatCmpOp::Le), + 3 => FloatBinOp::Cmp(FloatCmpOp::Unord), + 4 => FloatBinOp::Cmp(FloatCmpOp::Neq), + 5 => FloatBinOp::Cmp(FloatCmpOp::Nlt), + 6 => FloatBinOp::Cmp(FloatCmpOp::Nle), + 7 => FloatBinOp::Cmp(FloatCmpOp::Ord), + imm => { + throw_unsup_format!( + "invalid 3rd parameter of llvm.x86.sse.cmp.ps: {}", + imm + ); + } + }; + + bin_op_ss(this, which, left, right, dest)?; + } + // Used to implement the _mm_cmp_ps function. + // Performs a comparison operation on each component of `left` + // and `right`. For each component, returns 0 if false or u32::MAX + // if true. + "cmp.ps" => { + let [left, right, imm] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let which = match this.read_scalar(imm)?.to_i8()? { + 0 => FloatBinOp::Cmp(FloatCmpOp::Eq), + 1 => FloatBinOp::Cmp(FloatCmpOp::Lt), + 2 => FloatBinOp::Cmp(FloatCmpOp::Le), + 3 => FloatBinOp::Cmp(FloatCmpOp::Unord), + 4 => FloatBinOp::Cmp(FloatCmpOp::Neq), + 5 => FloatBinOp::Cmp(FloatCmpOp::Nlt), + 6 => FloatBinOp::Cmp(FloatCmpOp::Nle), + 7 => FloatBinOp::Cmp(FloatCmpOp::Ord), + imm => { + throw_unsup_format!( + "invalid 3rd parameter of llvm.x86.sse.cmp.ps: {}", + imm + ); + } + }; + + bin_op_ps(this, which, left, right, dest)?; + } + // Used to implement _mm_{,u}comi{eq,lt,le,gt,ge,neq}_ps functions. + // Compares the first component of `left` and `right` and returns + // a scalar value (0 or 1). + "comieq.ss" | "comilt.ss" | "comile.ss" | "comigt.ss" | "comige.ss" | "comineq.ss" + | "ucomieq.ss" | "ucomilt.ss" | "ucomile.ss" | "ucomigt.ss" | "ucomige.ss" + | "ucomineq.ss" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + + assert_eq!(left_len, right_len); + + let left = this.read_scalar(&this.project_index(&left, 0)?)?.to_f32()?; + let right = this.read_scalar(&this.project_index(&right, 0)?)?.to_f32()?; + // The difference between the com* and *ucom variants is signaling + // of exceptions when either argument is a quiet NaN. We do not + // support accessing the SSE status register from miri (or from Rust, + // for that matter), so we treat equally both variants. + let res = match unprefixed_name { + "comieq.ss" | "ucomieq.ss" => left == right, + "comilt.ss" | "ucomilt.ss" => left < right, + "comile.ss" | "ucomile.ss" => left <= right, + "comigt.ss" | "ucomigt.ss" => left > right, + "comige.ss" | "ucomige.ss" => left >= right, + "comineq.ss" | "ucomineq.ss" => left != right, + _ => unreachable!(), + }; + this.write_scalar(Scalar::from_i32(i32::from(res)), dest)?; + } + // Use to implement _mm_cvtss_si32 and _mm_cvttss_si32. + // Converts the first component of `op` from f32 to i32. + "cvtss2si" | "cvttss2si" => { + let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let (op, _) = this.operand_to_simd(op)?; + + let op = this.read_scalar(&this.project_index(&op, 0)?)?.to_f32()?; + + let rnd = match unprefixed_name { + // "current SSE rounding mode", assume nearest + // https://www.felixcloutier.com/x86/cvtss2si + "cvtss2si" => rustc_apfloat::Round::NearestTiesToEven, + // always truncate + // https://www.felixcloutier.com/x86/cvttss2si + "cvttss2si" => rustc_apfloat::Round::TowardZero, + _ => unreachable!(), + }; + + let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { + // Fallback to minimum acording to SSE semantics. + Scalar::from_i32(i32::MIN) + }); + + this.write_scalar(res, dest)?; + } + // Use to implement _mm_cvtss_si64 and _mm_cvttss_si64. + // Converts the first component of `op` from f32 to i64. + "cvtss2si64" | "cvttss2si64" => { + let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let (op, _) = this.operand_to_simd(op)?; + + let op = this.read_scalar(&this.project_index(&op, 0)?)?.to_f32()?; + + let rnd = match unprefixed_name { + // "current SSE rounding mode", assume nearest + // https://www.felixcloutier.com/x86/cvtss2si + "cvtss2si64" => rustc_apfloat::Round::NearestTiesToEven, + // always truncate + // https://www.felixcloutier.com/x86/cvttss2si + "cvttss2si64" => rustc_apfloat::Round::TowardZero, + _ => unreachable!(), + }; + + let res = this.float_to_int_checked(op, dest.layout.ty, rnd).unwrap_or_else(|| { + // Fallback to minimum acording to SSE semantics. + Scalar::from_i64(i64::MIN) + }); + + this.write_scalar(res, dest)?; + } + // Used to implement the _mm_cvtsi32_ss function. + // Converts `right` from i32 to f32. Returns a SIMD vector with + // the result in the first component and the remaining components + // are copied from `left`. + // https://www.felixcloutier.com/x86/cvtsi2ss + "cvtsi2ss" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + + let right = this.read_scalar(right)?.to_i32()?; + + let res0 = Scalar::from_f32(Single::from_i128(right.into()).value); + this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + + for i in 1..dest_len { + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let dest = this.project_index(&dest, i)?; + + this.write_immediate(*left, &dest)?; + } + } + // Used to implement the _mm_cvtsi64_ss function. + // Converts `right` from i64 to f32. Returns a SIMD vector with + // the result in the first component and the remaining components + // are copied from `left`. + // https://www.felixcloutier.com/x86/cvtsi2ss + "cvtsi642ss" => { + let [left, right] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let (left, left_len) = this.operand_to_simd(left)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + + let right = this.read_scalar(right)?.to_i64()?; + + let res0 = Scalar::from_f32(Single::from_i128(right.into()).value); + this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + + for i in 1..dest_len { + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let dest = this.project_index(&dest, i)?; + + this.write_immediate(*left, &dest)?; + } + } + // Used to implement the _mm_movemask_ps function. + // Returns a scalar integer where the i-th bit is the highest + // bit of the i-th component of `op`. + // https://www.felixcloutier.com/x86/movmskps + "movmsk.ps" => { + let [op] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let (op, op_len) = this.operand_to_simd(op)?; + + let mut res = 0; + for i in 0..op_len { + let op = this.read_scalar(&this.project_index(&op, i)?)?; + let op = op.to_u32()?; + + res |= (op >> 31) << i; + } + + this.write_scalar(Scalar::from_u32(res), dest)?; + } + _ => return Ok(EmulateByNameResult::NotSupported), + } + Ok(EmulateByNameResult::NeedsJumping) + } +} + +/// Floating point comparison operation +/// +/// +/// +#[derive(Copy, Clone)] +enum FloatCmpOp { + Eq, + Lt, + Le, + Unord, + Neq, + /// Not less-than + Nlt, + /// Not less-or-equal + Nle, + /// Ordered, i.e. neither of them is NaN + Ord, +} + +#[derive(Copy, Clone)] +enum FloatBinOp { + /// Arithmetic operation + Arith(mir::BinOp), + /// Comparison + Cmp(FloatCmpOp), + /// Minimum value (with SSE semantics) + /// + /// + /// + Min, + /// Maximum value (with SSE semantics) + /// + /// + /// + Max, +} + +/// Performs `which` scalar operation on `left` and `right` and returns +/// the result. +fn bin_op_f32<'tcx>( + this: &crate::MiriInterpCx<'_, 'tcx>, + which: FloatBinOp, + left: &ImmTy<'tcx, Provenance>, + right: &ImmTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + match which { + FloatBinOp::Arith(which) => { + let (res, _, _) = this.overflowing_binary_op(which, left, right)?; + Ok(res) + } + FloatBinOp::Cmp(which) => { + let left = left.to_scalar().to_f32()?; + let right = right.to_scalar().to_f32()?; + // FIXME: Make sure that these operations match the semantics of cmpps + let res = match which { + FloatCmpOp::Eq => left == right, + FloatCmpOp::Lt => left < right, + FloatCmpOp::Le => left <= right, + FloatCmpOp::Unord => left.is_nan() || right.is_nan(), + FloatCmpOp::Neq => left != right, + FloatCmpOp::Nlt => !(left < right), + FloatCmpOp::Nle => !(left <= right), + FloatCmpOp::Ord => !left.is_nan() && !right.is_nan(), + }; + Ok(Scalar::from_u32(if res { u32::MAX } else { 0 })) + } + FloatBinOp::Min => { + let left = left.to_scalar().to_f32()?; + let right = right.to_scalar().to_f32()?; + // SSE semantics to handle zero and NaN. Note that `x == Single::ZERO` + // is true when `x` is either +0 or -0. + if (left == Single::ZERO && right == Single::ZERO) + || left.is_nan() + || right.is_nan() + || left >= right + { + Ok(Scalar::from_f32(right)) + } else { + Ok(Scalar::from_f32(left)) + } + } + FloatBinOp::Max => { + let left = left.to_scalar().to_f32()?; + let right = right.to_scalar().to_f32()?; + // SSE semantics to handle zero and NaN. Note that `x == Single::ZERO` + // is true when `x` is either +0 or -0. + if (left == Single::ZERO && right == Single::ZERO) + || left.is_nan() + || right.is_nan() + || left <= right + { + Ok(Scalar::from_f32(right)) + } else { + Ok(Scalar::from_f32(left)) + } + } + } +} + +/// Performs `which` operation on the first component of `left` and `right` +/// and copies the other components from `left`. The result is stored in `dest`. +fn bin_op_ss<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatBinOp, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + let res0 = bin_op_f32( + this, + which, + &this.read_immediate(&this.project_index(&left, 0)?)?, + &this.read_immediate(&this.project_index(&right, 0)?)?, + )?; + this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + + for i in 1..dest_len { + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let dest = this.project_index(&dest, i)?; + + this.write_immediate(*left, &dest)?; + } + + Ok(()) +} + +/// Performs `which` operation on each component of `left`, and +/// `right` storing the result is stored in `dest`. +fn bin_op_ps<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatBinOp, + left: &OpTy<'tcx, Provenance>, + right: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (left, left_len) = this.operand_to_simd(left)?; + let (right, right_len) = this.operand_to_simd(right)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, left_len); + assert_eq!(dest_len, right_len); + + for i in 0..dest_len { + let left = this.read_immediate(&this.project_index(&left, i)?)?; + let right = this.read_immediate(&this.project_index(&right, i)?)?; + let dest = this.project_index(&dest, i)?; + + let res = bin_op_f32(this, which, &left, &right)?; + this.write_scalar(res, &dest)?; + } + + Ok(()) +} + +#[derive(Copy, Clone)] +enum FloatUnaryOp { + /// sqrt(x) + /// + /// + /// + Sqrt, + /// Approximation of 1/x + /// + /// + /// + Rcp, + /// Approximation of 1/sqrt(x) + /// + /// + /// + Rsqrt, +} + +/// Performs `which` scalar operation on `op` and returns the result. +#[allow(clippy::arithmetic_side_effects)] // floating point operations without side effects +fn unary_op_f32<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatUnaryOp, + op: &ImmTy<'tcx, Provenance>, +) -> InterpResult<'tcx, Scalar> { + match which { + FloatUnaryOp::Sqrt => { + let op = op.to_scalar(); + // FIXME using host floats + Ok(Scalar::from_u32(f32::from_bits(op.to_u32()?).sqrt().to_bits())) + } + FloatUnaryOp::Rcp => { + let op = op.to_scalar().to_f32()?; + let div = (Single::from_u128(1).value / op).value; + // Apply a relative error with a magnitude on the order of 2^-12 to simulate the + // inaccuracy of RCP. + let res = apply_random_float_error(this, div, -12); + Ok(Scalar::from_f32(res)) + } + FloatUnaryOp::Rsqrt => { + let op = op.to_scalar().to_u32()?; + // FIXME using host floats + let sqrt = Single::from_bits(f32::from_bits(op).sqrt().to_bits().into()); + let rsqrt = (Single::from_u128(1).value / sqrt).value; + // Apply a relative error with a magnitude on the order of 2^-12 to simulate the + // inaccuracy of RSQRT. + let res = apply_random_float_error(this, rsqrt, -12); + Ok(Scalar::from_f32(res)) + } + } +} + +/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). +#[allow(clippy::arithmetic_side_effects)] // floating point arithmetic cannot panic +fn apply_random_float_error( + this: &mut crate::MiriInterpCx<'_, '_>, + val: F, + err_scale: i32, +) -> F { + let rng = this.machine.rng.get_mut(); + // generates rand(0, 2^64) * 2^(scale - 64) = rand(0, 1) * 2^scale + let err = + F::from_u128(rng.gen::().into()).value.scalbn(err_scale.checked_sub(64).unwrap()); + // give it a random sign + let err = if rng.gen::() { -err } else { err }; + // multiple the value with (1+err) + (val * (F::from_u128(1).value + err).value).value +} + +/// Performs `which` operation on the first component of `op` and copies +/// the other components. The result is stored in `dest`. +fn unary_op_ss<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatUnaryOp, + op: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (op, op_len) = this.operand_to_simd(op)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, op_len); + + let res0 = unary_op_f32(this, which, &this.read_immediate(&this.project_index(&op, 0)?)?)?; + this.write_scalar(res0, &this.project_index(&dest, 0)?)?; + + for i in 1..dest_len { + let op = this.read_immediate(&this.project_index(&op, i)?)?; + let dest = this.project_index(&dest, i)?; + + this.write_immediate(*op, &dest)?; + } + + Ok(()) +} + +/// Performs `which` operation on each component of `op`, storing the +/// result is stored in `dest`. +fn unary_op_ps<'tcx>( + this: &mut crate::MiriInterpCx<'_, 'tcx>, + which: FloatUnaryOp, + op: &OpTy<'tcx, Provenance>, + dest: &PlaceTy<'tcx, Provenance>, +) -> InterpResult<'tcx, ()> { + let (op, op_len) = this.operand_to_simd(op)?; + let (dest, dest_len) = this.place_to_simd(dest)?; + + assert_eq!(dest_len, op_len); + + for i in 0..dest_len { + let op = this.read_immediate(&this.project_index(&op, i)?)?; + let dest = this.project_index(&dest, i)?; + + let res = unary_op_f32(this, which, &op)?; + this.write_scalar(res, &dest)?; + } + + Ok(()) +} diff --git a/src/tools/miri/test-cargo-miri/Cargo.lock b/src/tools/miri/test-cargo-miri/Cargo.lock index af38ceb1d4c7d..cf5ec2aa883d7 100644 --- a/src/tools/miri/test-cargo-miri/Cargo.lock +++ b/src/tools/miri/test-cargo-miri/Cargo.lock @@ -83,9 +83,9 @@ version = "0.1.0" [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] diff --git a/src/tools/miri/test-cargo-miri/run-test.py b/src/tools/miri/test-cargo-miri/run-test.py index 9df90c725e40f..ca2f69fc8cfa5 100755 --- a/src/tools/miri/test-cargo-miri/run-test.py +++ b/src/tools/miri/test-cargo-miri/run-test.py @@ -5,7 +5,11 @@ and the working directory to contain the cargo-miri-test project. ''' -import sys, subprocess, os, re, difflib +import difflib +import os +import re +import subprocess +import sys CGREEN = '\33[32m' CBOLD = '\33[1m' @@ -33,7 +37,8 @@ def normalize_stderr(str): return str def check_output(actual, path, name): - if 'MIRI_BLESS' in os.environ: + if os.environ.get("RUSTC_BLESS", "0") != "0": + # Write the output only if bless is set open(path, mode='w').write(actual) return True expected = open(path).read() @@ -46,7 +51,9 @@ def check_output(actual, path, name): print(f"--- END diff {name} ---") return False -def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}): +def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env=None): + if env is None: + env = {} print("Testing {}...".format(name)) ## Call `cargo miri`, capture all output p_env = os.environ.copy() @@ -64,13 +71,15 @@ def test(name, cmd, stdout_ref, stderr_ref, stdin=b'', env={}): stdout_matches = check_output(stdout, stdout_ref, "stdout") stderr_matches = check_output(stderr, stderr_ref, "stderr") - + if p.returncode == 0 and stdout_matches and stderr_matches: # All good! return fail("exit code was {}".format(p.returncode)) -def test_no_rebuild(name, cmd, env={}): +def test_no_rebuild(name, cmd, env=None): + if env is None: + env = {} print("Testing {}...".format(name)) p_env = os.environ.copy() p_env.update(env) @@ -84,13 +93,13 @@ def test_no_rebuild(name, cmd, env={}): stdout = stdout.decode("UTF-8") stderr = stderr.decode("UTF-8") if p.returncode != 0: - fail("rebuild failed"); + fail("rebuild failed") # Also check for 'Running' as a sanity check. if stderr.count(" Compiling ") > 0 or stderr.count(" Running ") == 0: print("--- BEGIN stderr ---") print(stderr, end="") print("--- END stderr ---") - fail("Something was being rebuilt when it should not be (or we got no output)"); + fail("Something was being rebuilt when it should not be (or we got no output)") def test_cargo_miri_run(): test("`cargo miri run` (no isolation)", diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock index 8be1ee54672d8..3ed564b4cbbb7 100644 --- a/src/tools/miri/test_dependencies/Cargo.lock +++ b/src/tools/miri/test_dependencies/Cargo.lock @@ -193,9 +193,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] diff --git a/src/tools/miri/tests/compiletest.rs b/src/tools/miri/tests/compiletest.rs index fa06c4b6a1288..8b97c8bb83c82 100644 --- a/src/tools/miri/tests/compiletest.rs +++ b/src/tools/miri/tests/compiletest.rs @@ -3,9 +3,8 @@ use regex::bytes::Regex; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::{env, process::Command}; -use ui_test::status_emitter::StatusEmitter; -use ui_test::CommandBuilder; use ui_test::{color_eyre::Result, Config, Match, Mode, OutputConflictHandling}; +use ui_test::{status_emitter, CommandBuilder}; fn miri_path() -> PathBuf { PathBuf::from(option_env!("MIRI").unwrap_or(env!("CARGO_BIN_EXE_miri"))) @@ -51,18 +50,10 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> let mut program = CommandBuilder::rustc(); program.program = miri_path(); - let in_rustc_test_suite = option_env!("RUSTC_STAGE").is_some(); - // Add some flags we always want. - if in_rustc_test_suite { - // Less aggressive warnings to make the rustc toolstate management less painful. - // (We often get warnings when e.g. a feature gets stabilized or some lint gets added/improved.) - program.args.push("-Astable-features".into()); - program.args.push("-Aunused".into()); - } else { - program.args.push("-Dwarnings".into()); - program.args.push("-Dunused".into()); - } + program.args.push("-Dwarnings".into()); + program.args.push("-Dunused".into()); + program.args.push("-Ainternal_features".into()); if let Ok(extra_flags) = env::var("MIRIFLAGS") { for flag in extra_flags.split_whitespace() { program.args.push(flag.into()); @@ -82,26 +73,26 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> program.args.push(flag); } + let bless = env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); let skip_ui_checks = env::var_os("MIRI_SKIP_UI_CHECKS").is_some(); - let output_conflict_handling = match (env::var_os("MIRI_BLESS").is_some(), skip_ui_checks) { - (false, false) => OutputConflictHandling::Error, + let output_conflict_handling = match (bless, skip_ui_checks) { + (false, false) => OutputConflictHandling::Error("./miri test --bless".into()), (true, false) => OutputConflictHandling::Bless, (false, true) => OutputConflictHandling::Ignore, - (true, true) => panic!("cannot use MIRI_BLESS and MIRI_SKIP_UI_CHECKS at the same time"), + (true, true) => panic!("cannot use RUSTC_BLESS and MIRI_SKIP_UI_CHECKS at the same time"), }; let mut config = Config { target: Some(target.to_owned()), stderr_filters: STDERR.clone(), stdout_filters: STDOUT.clone(), - root_dir: PathBuf::from(path), mode, program, output_conflict_handling, - quiet: false, + out_dir: PathBuf::from(std::env::var_os("CARGO_TARGET_DIR").unwrap()).join("ui"), edition: Some("2021".into()), - ..Config::default() + ..Config::rustc(path.into()) }; let use_std = env::var_os("MIRI_NO_STD").is_none(); @@ -122,39 +113,56 @@ fn test_config(target: &str, path: &str, mode: Mode, with_dependencies: bool) -> } fn run_tests(mode: Mode, path: &str, target: &str, with_dependencies: bool) -> Result<()> { - let mut config = test_config(target, path, mode, with_dependencies); + let config = test_config(target, path, mode, with_dependencies); // Handle command-line arguments. let mut after_dashdash = false; - config.path_filter.extend(std::env::args().skip(1).filter(|arg| { - if after_dashdash { - // Just propagate everything. - return true; - } - match &**arg { - "--quiet" => { - config.quiet = true; - false + let mut quiet = false; + let filters = std::env::args() + .skip(1) + .filter(|arg| { + if after_dashdash { + // Just propagate everything. + return true; } - "--" => { - after_dashdash = true; - false + match &**arg { + "--quiet" => { + quiet = true; + false + } + "--" => { + after_dashdash = true; + false + } + s if s.starts_with('-') => { + panic!("unknown compiletest flag `{s}`"); + } + _ => true, } - s if s.starts_with('-') => { - panic!("unknown compiletest flag `{s}`"); - } - _ => true, - } - })); - + }) + .collect::>(); eprintln!(" Compiler: {}", config.program.display()); ui_test::run_tests_generic( config, // The files we're actually interested in (all `.rs` files). - |path| path.extension().is_some_and(|ext| ext == "rs"), + |path| { + path.extension().is_some_and(|ext| ext == "rs") + && (filters.is_empty() + || filters.iter().any(|f| path.display().to_string().contains(f))) + }, // This could be used to overwrite the `Config` on a per-test basis. |_, _| None, - TextAndGha, + ( + if quiet { + Box::::default() + as Box + } else { + Box::new(status_emitter::Text) + }, + status_emitter::Gha:: { + name: format!("{mode:?} {path} ({target})"), + }, + ), ) } @@ -184,6 +192,8 @@ regexes! { // erase borrow tags "<[0-9]+>" => "", "<[0-9]+=" => " "$1", // erase whitespace that differs between platforms r" +at (.*\.rs)" => " at $1", // erase generics in backtraces @@ -269,56 +279,13 @@ fn main() -> Result<()> { fn run_dep_mode(target: String, mut args: impl Iterator) -> Result<()> { let path = args.next().expect("./miri run-dep must be followed by a file name"); let mut config = test_config(&target, "", Mode::Yolo, /* with dependencies */ true); - config.program.args.remove(0); // remove the `--error-format=json` argument - config.program.args.push("--color".into()); - config.program.args.push("always".into()); - let mut cmd = ui_test::test_command(config, Path::new(&path))?; - // Separate the arguments to the `cargo miri` invocation from - // the arguments to the interpreted prog - cmd.arg("--"); - cmd.args(args); - println!("{cmd:?}"); - if cmd.spawn()?.wait()?.success() { Ok(()) } else { std::process::exit(1) } -} + config.program.args.clear(); // We want to give the user full control over flags + config.build_dependencies_and_link_them()?; -/// This is a custom renderer for `ui_test` output that does not emit github actions -/// `group`s, while still producing regular github actions messages on test failures. -struct TextAndGha; -impl StatusEmitter for TextAndGha { - fn failed_test<'a>( - &'a self, - revision: &'a str, - path: &'a Path, - cmd: &'a Command, - stderr: &'a [u8], - ) -> Box { - Box::new(( - ui_test::status_emitter::Gha::.failed_test(revision, path, cmd, stderr), - ui_test::status_emitter::Text.failed_test(revision, path, cmd, stderr), - )) - } - - fn run_tests(&self, _config: &Config) -> Box { - Box::new(TextAndGha) - } + let mut cmd = config.program.build(&config.out_dir); - fn finalize( - &self, - failures: usize, - succeeded: usize, - ignored: usize, - filtered: usize, - ) -> Box { - Box::new(( - ui_test::status_emitter::Gha::.finalize(failures, succeeded, ignored, filtered), - ui_test::status_emitter::Text.finalize(failures, succeeded, ignored, filtered), - )) - } -} + cmd.arg(path); -impl ui_test::status_emitter::DuringTestRun for TextAndGha { - fn test_result(&mut self, path: &Path, revision: &str, result: &ui_test::TestResult) { - ui_test::status_emitter::Text.test_result(path, revision, result); - ui_test::status_emitter::Gha::.test_result(path, revision, result); - } + cmd.args(args); + if cmd.spawn()?.wait()?.success() { Ok(()) } else { std::process::exit(1) } } diff --git a/src/tools/miri/tests/fail/alloc/deallocate-twice.rs b/src/tools/miri/tests/fail/alloc/deallocate-twice.rs index f07bbda4a9be9..7a3ff84b2cb13 100644 --- a/src/tools/miri/tests/fail/alloc/deallocate-twice.rs +++ b/src/tools/miri/tests/fail/alloc/deallocate-twice.rs @@ -1,6 +1,6 @@ use std::alloc::{alloc, dealloc, Layout}; -//@error-in-other-file: dereferenced after this allocation got freed +//@error-in-other-file: has been freed fn main() { unsafe { diff --git a/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr b/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr index fa7a74ee13cfe..48d63e5905178 100644 --- a/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr +++ b/src/tools/miri/tests/fail/alloc/deallocate-twice.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling --> RUSTLIB/alloc/src/alloc.rs:LL:CC | LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/deallocate-twice.rs:LL:CC + | +LL | let x = alloc(Layout::from_size_align_unchecked(1, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/deallocate-twice.rs:LL:CC + | +LL | dealloc(x, Layout::from_size_align_unchecked(1, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC note: inside `main` --> $DIR/deallocate-twice.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs index 3ad56da2c2fff..ecdd3ae5fee75 100644 --- a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs +++ b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs @@ -4,6 +4,6 @@ fn main() { unsafe { let x = alloc(Layout::from_size_align_unchecked(1, 1)); let _y = realloc(x, Layout::from_size_align_unchecked(1, 1), 1); - let _z = *x; //~ ERROR: dereferenced after this allocation got freed + let _z = *x; //~ ERROR: has been freed } } diff --git a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr index 5631dcb4cc084..ff4cb399157d9 100644 --- a/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr +++ b/src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/reallocate-change-alloc.rs:LL:CC | LL | let _z = *x; - | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/reallocate-change-alloc.rs:LL:CC + | +LL | let x = alloc(Layout::from_size_align_unchecked(1, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/reallocate-change-alloc.rs:LL:CC + | +LL | let _y = realloc(x, Layout::from_size_align_unchecked(1, 1), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/reallocate-change-alloc.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs b/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs index 130e2a8301e1c..622348ad190d1 100644 --- a/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs +++ b/src/tools/miri/tests/fail/alloc/reallocate-dangling.rs @@ -1,6 +1,6 @@ use std::alloc::{alloc, dealloc, realloc, Layout}; -//@error-in-other-file: dereferenced after this allocation got freed +//@error-in-other-file: has been freed fn main() { unsafe { diff --git a/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr b/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr index b1460bfb763ee..52cc579c1e612 100644 --- a/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr +++ b/src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling --> RUSTLIB/alloc/src/alloc.rs:LL:CC | LL | unsafe { __rust_realloc(ptr, layout.size(), layout.align(), new_size) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/reallocate-dangling.rs:LL:CC + | +LL | let x = alloc(Layout::from_size_align_unchecked(1, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/reallocate-dangling.rs:LL:CC + | +LL | dealloc(x, Layout::from_size_align_unchecked(1, 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `std::alloc::realloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC note: inside `main` --> $DIR/reallocate-dangling.rs:LL:CC diff --git a/src/tools/miri/tests/fail/alloc/stack_free.stderr b/src/tools/miri/tests/fail/alloc/stack_free.stderr index b1636050a78ca..7c14d372f0c7c 100644 --- a/src/tools/miri/tests/fail/alloc/stack_free.stderr +++ b/src/tools/miri/tests/fail/alloc/stack_free.stderr @@ -9,7 +9,7 @@ LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } = note: BACKTRACE: = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC - = note: inside `alloc::alloc::box_free::` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC note: inside `main` diff --git a/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.rs b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.rs new file mode 100644 index 0000000000000..f40bf327cac17 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.rs @@ -0,0 +1,20 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// This makes a ref that was passed to us via &mut alias with things it should not alias with +fn retarget(x: &mut &u32, target: &mut u32) { + unsafe { + *x = &mut *(target as *mut _); + } +} + +fn main() { + let target = &mut 42; + let mut target_alias = &42; // initial dummy value + retarget(&mut target_alias, target); + // now `target_alias` points to the same thing as `target` + *target = 13; + let _val = *target_alias; + //~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /read access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.stderr rename to src/tools/miri/tests/fail/both_borrows/alias_through_mutation.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr new file mode 100644 index 0000000000000..655f1b5777769 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/alias_through_mutation.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: read access through is forbidden + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | let _val = *target_alias; + | ^^^^^^^^^^^^^ read access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child read access +help: the accessed tag was created here + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | *x = &mut *(target as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | retarget(&mut target_alias, target); + | ^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/alias_through_mutation.rs:LL:CC + | +LL | *target = 13; + | ^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/alias_through_mutation.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs new file mode 100644 index 0000000000000..a9efe17fddfab --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.rs @@ -0,0 +1,20 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +use std::mem; + +pub fn safe(x: &mut i32, y: &mut i32) { + //~[stack]^ ERROR: protect + *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ + *y = 2; +} + +fn main() { + let mut x = 0; + let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; + // We need to apply some tricky to be able to call `safe` with two mutable references + // with the same tag: We transmute both the fn ptr (to take raw ptrs) and the argument + // (to be raw, but still have the unique tag). + let safe_raw: fn(x: *mut i32, y: *mut i32) = + unsafe { mem::transmute::(safe) }; + safe_raw(xraw, xraw); +} diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr new file mode 100644 index 0000000000000..678211a700dbc --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.stack.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &mut i32) { + | ^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; + | ^^^^^^ +help: is this argument + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &mut i32) { + | ^ + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | safe_raw(xraw, xraw); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr new file mode 100644 index 0000000000000..2a9b4d688a912 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut1.tree.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | *x = 1; + | ^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &mut i32) { + | ^ +help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &mut i32) { + | ^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut1.rs:LL:CC + | +LL | safe_raw(xraw, xraw); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs new file mode 100644 index 0000000000000..74ea2b28627c1 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.rs @@ -0,0 +1,20 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +use std::mem; + +pub fn safe(x: &i32, y: &mut i32) { + //~[stack]^ ERROR: protect + let _v = *x; + *y = 2; //~[tree] ERROR: /write access through .* is forbidden/ +} + +fn main() { + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *const i32, y: *mut i32) = + unsafe { mem::transmute::(safe) }; + safe_raw(xshr, xraw); +} diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr new file mode 100644 index 0000000000000..90134ce367a19 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.stack.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | pub fn safe(x: &i32, y: &mut i32) { + | ^ not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | let xref = &mut x; + | ^^^^^^ +help: is this argument + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | pub fn safe(x: &i32, y: &mut i32) { + | ^ + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | safe_raw(xshr, xraw); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr new file mode 100644 index 0000000000000..d4858975ef103 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut2.tree.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | *y = 2; + | ^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | pub fn safe(x: &i32, y: &mut i32) { + | ^ +help: the accessed tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4] + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | let _v = *x; + | ^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut2.rs:LL:CC + | +LL | safe_raw(xshr, xraw); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs new file mode 100644 index 0000000000000..8cb60faf2d078 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.rs @@ -0,0 +1,20 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +use std::mem; + +pub fn safe(x: &mut i32, y: &i32) { + //~[stack]^ ERROR: borrow stack + *x = 1; //~[tree] ERROR: /write access through .* is forbidden/ + let _v = *y; +} + +fn main() { + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *mut i32, y: *const i32) = + unsafe { mem::transmute::(safe) }; + safe_raw(xraw, xshr); +} diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr new file mode 100644 index 0000000000000..a457bd9a6accf --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.stack.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &i32) { + | ^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of function-entry retag at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique function-entry retag inside this call + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr new file mode 100644 index 0000000000000..d1afca84a8b2e --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut3.tree.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | *x = 1; + | ^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &i32) { + | ^ +help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | pub fn safe(x: &mut i32, y: &i32) { + | ^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut3.rs:LL:CC + | +LL | safe_raw(xraw, xshr); + | ^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs new file mode 100644 index 0000000000000..e188a1f0c3438 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.rs @@ -0,0 +1,23 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +//@[tree]error-in-other-file: /write access through .* is forbidden/ +use std::cell::Cell; +use std::mem; + +// Make sure &mut UnsafeCell also is exclusive +pub fn safe(x: &i32, y: &mut Cell) { + //~[stack]^ ERROR: protect + y.set(1); + let _ = *x; +} + +fn main() { + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *const i32, y: *mut Cell) = + unsafe { mem::transmute::), _>(safe) }; + safe_raw(xshr, xraw as *mut _); +} diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr new file mode 100644 index 0000000000000..b53ae9bd5504b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.stack.stderr @@ -0,0 +1,30 @@ +error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | pub fn safe(x: &i32, y: &mut Cell) { + | ^ not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | let xref = &mut x; + | ^^^^^^ +help: is this argument + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | pub fn safe(x: &i32, y: &mut Cell) { + | ^ + = note: BACKTRACE (of the first span): + = note: inside `safe` at $DIR/aliasing_mut4.rs:LL:CC +note: inside `main` + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | safe_raw(xshr, xraw as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr new file mode 100644 index 0000000000000..106e5c19bf277 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/aliasing_mut4.tree.stderr @@ -0,0 +1,39 @@ +error: Undefined Behavior: write access through is forbidden + --> RUSTLIB/core/src/mem/mod.rs:LL:CC + | +LL | ptr::write(dest, src); + | ^^^^^^^^^^^^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Frozen) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | y.set(1); + | ^^^^^^^^ +help: the protected tag was created here, in the initial state Frozen + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | pub fn safe(x: &i32, y: &mut Cell) { + | ^ + = note: BACKTRACE (of the first span): + = note: inside `std::mem::replace::` at RUSTLIB/core/src/mem/mod.rs:LL:CC + = note: inside `std::cell::Cell::::replace` at RUSTLIB/core/src/cell.rs:LL:CC + = note: inside `std::cell::Cell::::set` at RUSTLIB/core/src/cell.rs:LL:CC +note: inside `safe` + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | y.set(1); + | ^^^^^^^^ +note: inside `main` + --> $DIR/aliasing_mut4.rs:LL:CC + | +LL | safe_raw(xshr, xraw as *mut _); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.rs b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.rs new file mode 100644 index 0000000000000..4936c1a901821 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.rs @@ -0,0 +1,38 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +fn demo_box_advanced_unique(mut our: Box) -> i32 { + unknown_code_1(&*our); + + // This "re-asserts" uniqueness of the reference: After writing, we know + // our tag is at the top of the stack. + *our = 5; + + unknown_code_2(); + + // We know this will return 5 + *our +} + +// Now comes the evil context +use std::ptr; + +static mut LEAK: *mut i32 = ptr::null_mut(); + +fn unknown_code_1(x: &i32) { + unsafe { + LEAK = x as *const _ as *mut _; + } +} + +fn unknown_code_2() { + unsafe { + *LEAK = 7; + //~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /write access through .* is forbidden/ + } +} + +fn main() { + demo_box_advanced_unique(Box::new(0)); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.stderr rename to src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr new file mode 100644 index 0000000000000..97f82db6fe732 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box_exclusive_violation1.tree.stderr @@ -0,0 +1,42 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | *LEAK = 7; + | ^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child write access +help: the accessed tag was created here + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | fn unknown_code_1(x: &i32) { + | ^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | unknown_code_1(&*our); + | ^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | *our = 5; + | ^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `unknown_code_2` at $DIR/box_exclusive_violation1.rs:LL:CC +note: inside `demo_box_advanced_unique` + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | unknown_code_2(); + | ^^^^^^^^^^^^^^^^ +note: inside `main` + --> $DIR/box_exclusive_violation1.rs:LL:CC + | +LL | demo_box_advanced_unique(Box::new(0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.rs b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.rs new file mode 100644 index 0000000000000..1d20b22b3ad88 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.rs @@ -0,0 +1,19 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +unsafe fn test(mut x: Box, y: *const i32) -> i32 { + // We will call this in a way that x and y alias. + *x = 5; + std::mem::forget(x); + *y + //~[stack]^ ERROR: weakly protected + //~[tree]| ERROR: /read access through .* is forbidden/ +} + +fn main() { + unsafe { + let mut v = 42; + let ptr = &mut v as *mut i32; + test(Box::from_raw(ptr), ptr); + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.stderr b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.stderr rename to src/tools/miri/tests/fail/both_borrows/box_noalias_violation.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr new file mode 100644 index 0000000000000..1ecd6620806ec --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/box_noalias_violation.tree.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: read access through is forbidden + --> $DIR/box_noalias_violation.rs:LL:CC + | +LL | *y + | ^^ read access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this foreign read access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/box_noalias_violation.rs:LL:CC + | +LL | let ptr = &mut v as *mut i32; + | ^^^^^^ +help: the protected tag was created here, in the initial state Reserved + --> $DIR/box_noalias_violation.rs:LL:CC + | +LL | unsafe fn test(mut x: Box, y: *const i32) -> i32 { + | ^^^^^ +help: the protected tag later transitioned to Active due to a child write access at offsets [0x0..0x4] + --> $DIR/box_noalias_violation.rs:LL:CC + | +LL | *x = 5; + | ^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference + = note: BACKTRACE (of the first span): + = note: inside `test` at $DIR/box_noalias_violation.rs:LL:CC +note: inside `main` + --> $DIR/box_noalias_violation.rs:LL:CC + | +LL | test(Box::from_raw(ptr), ptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.rs b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.rs new file mode 100644 index 0000000000000..6d535a14c0481 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.rs @@ -0,0 +1,19 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +mod safe { + use std::slice::from_raw_parts_mut; + + pub fn as_mut_slice(self_: &Vec) -> &mut [T] { + unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) } + } +} + +fn main() { + let v = vec![0, 1, 2]; + let v1 = safe::as_mut_slice(&v); + let v2 = safe::as_mut_slice(&v); + v1[1] = 5; + //~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/ + v2[1] = 7; + //~[tree]^ ERROR: /write access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.stderr rename to src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr new file mode 100644 index 0000000000000..9519c83f71c78 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/buggy_as_mut_slice.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | v2[1] = 7; + | ^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child write access +help: the accessed tag was created here + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | let v2 = safe::as_mut_slice(&v); + | ^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x4..0x8] + --> $DIR/buggy_as_mut_slice.rs:LL:CC + | +LL | v1[1] = 5; + | ^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/buggy_as_mut_slice.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.rs b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.rs new file mode 100644 index 0000000000000..80ededd210095 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.rs @@ -0,0 +1,28 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +mod safe { + use std::slice::from_raw_parts_mut; + + pub fn split_at_mut(self_: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { + let len = self_.len(); + let ptr = self_.as_mut_ptr(); + + unsafe { + assert!(mid <= len); + + ( + from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" + from_raw_parts_mut(ptr.offset(mid as isize), len - mid), + ) + //~[stack]^^^^ ERROR: /retag .* tag does not exist in the borrow stack/ + } + } +} + +fn main() { + let mut array = [1, 2, 3, 4]; + let (a, b) = safe::split_at_mut(&mut array, 0); + a[1] = 5; + b[1] = 6; + //~[tree]^ ERROR: /write access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr new file mode 100644 index 0000000000000..daa4339225dee --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.stack.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | / ( +LL | | from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" +LL | | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), +LL | | ) + | | ^ + | | | + | | trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | |_____________this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x0..0x10] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x10] + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x10] by a Unique retag + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `safe::split_at_mut::` at $DIR/buggy_split_at_mut.rs:LL:CC +note: inside `main` + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | let (a, b) = safe::split_at_mut(&mut array, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr new file mode 100644 index 0000000000000..4fd92df75d652 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/buggy_split_at_mut.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | b[1] = 6; + | ^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child write access +help: the accessed tag was created here + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | let (a, b) = safe::split_at_mut(&mut array, 0); + | ^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x4..0x8] + --> $DIR/buggy_split_at_mut.rs:LL:CC + | +LL | a[1] = 5; + | ^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/buggy_split_at_mut.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write1.rs b/src/tools/miri/tests/fail/both_borrows/illegal_write1.rs new file mode 100644 index 0000000000000..92f273f67bb22 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write1.rs @@ -0,0 +1,16 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +#![allow(invalid_reference_casting)] + +fn main() { + let target = Box::new(42); // has an implicit raw + let xref = &*target; + { + let x: *mut u32 = xref as *const _ as *mut _; + unsafe { *x = 42 }; + //~[stack]^ ERROR: /write access .* tag only grants SharedReadOnly permission/ + //~[tree]| ERROR: /write access through .* is forbidden/ + } + let _x = *xref; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write1.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/illegal_write1.stderr rename to src/tools/miri/tests/fail/both_borrows/illegal_write1.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr new file mode 100644 index 0000000000000..bb72159be65d4 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write1.tree.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/illegal_write1.rs:LL:CC + | +LL | unsafe { *x = 42 }; + | ^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Frozen + --> $DIR/illegal_write1.rs:LL:CC + | +LL | let xref = &*target; + | ^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/illegal_write1.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write5.rs b/src/tools/miri/tests/fail/both_borrows/illegal_write5.rs new file mode 100644 index 0000000000000..5de63c0f0683f --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write5.rs @@ -0,0 +1,20 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// A callee may not write to the destination of our `&mut` without us noticing. + +fn main() { + let mut x = 15; + let xraw = &mut x as *mut _; + // Derived from raw value, so using raw value is still ok ... + let xref = unsafe { &mut *xraw }; + callee(xraw); + // ... though any use of raw value will invalidate our ref. + let _val = *xref; + //~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /read access through .* is forbidden/ +} + +fn callee(xraw: *mut i32) { + unsafe { *xraw = 15 }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write5.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/illegal_write5.stderr rename to src/tools/miri/tests/fail/both_borrows/illegal_write5.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr new file mode 100644 index 0000000000000..05cc69553a007 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write5.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: read access through is forbidden + --> $DIR/illegal_write5.rs:LL:CC + | +LL | let _val = *xref; + | ^^^^^ read access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child read access +help: the accessed tag was created here + --> $DIR/illegal_write5.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; + | ^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/illegal_write5.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; + | ^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/illegal_write5.rs:LL:CC + | +LL | unsafe { *xraw = 15 }; + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/illegal_write5.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write6.rs b/src/tools/miri/tests/fail/both_borrows/illegal_write6.rs new file mode 100644 index 0000000000000..5c2dfc48e5221 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write6.rs @@ -0,0 +1,17 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +fn main() { + let x = &mut 0u32; + let p = x as *mut u32; + foo(x, p); +} + +fn foo(a: &mut u32, y: *mut u32) -> u32 { + *a = 1; + let _b = &*a; + unsafe { *y = 2 }; + //~[stack]^ ERROR: /not granting access .* because that would remove .* which is strongly protected/ + //~[tree]| ERROR: /write access through .* is forbidden/ + return *a; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write6.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/illegal_write6.stderr rename to src/tools/miri/tests/fail/both_borrows/illegal_write6.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr b/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr new file mode 100644 index 0000000000000..64e08f545e339 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/illegal_write6.tree.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/illegal_write6.rs:LL:CC + | +LL | unsafe { *y = 2 }; + | ^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/illegal_write6.rs:LL:CC + | +LL | let x = &mut 0u32; + | ^^^^^^^^^ +help: the protected tag was created here, in the initial state Reserved + --> $DIR/illegal_write6.rs:LL:CC + | +LL | fn foo(a: &mut u32, y: *mut u32) -> u32 { + | ^ +help: the protected tag later transitioned to Active due to a child write access at offsets [0x0..0x4] + --> $DIR/illegal_write6.rs:LL:CC + | +LL | *a = 1; + | ^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/illegal_write6.rs:LL:CC +note: inside `main` + --> $DIR/illegal_write6.rs:LL:CC + | +LL | foo(x, p); + | ^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.rs b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.rs new file mode 100644 index 0000000000000..0cec2d7c397b4 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.rs @@ -0,0 +1,18 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +fn inner(x: *mut i32, _y: &i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to write to `x` at all because `y` was assumed to be + // immutable for the duration of this call. + unsafe { *x = 0 }; + //~[stack]^ ERROR: protect + //~[tree]| ERROR: /write access through .* is forbidden/ +} + +fn main() { + let mut x = 0; + let xraw = &mut x as *mut _; + let xref = unsafe { &*xraw }; + inner(xraw, xref); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.stderr rename to src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr new file mode 100644 index 0000000000000..66f7f1788e4d5 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector2.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | unsafe { *x = 0 }; + | ^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Frozen) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | let xraw = &mut x as *mut _; + | ^^^^^^ +help: the protected tag was created here, in the initial state Frozen + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | fn inner(x: *mut i32, _y: &i32) { + | ^^ + = note: BACKTRACE (of the first span): + = note: inside `inner` at $DIR/invalidate_against_protector2.rs:LL:CC +note: inside `main` + --> $DIR/invalidate_against_protector2.rs:LL:CC + | +LL | inner(xraw, xref); + | ^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.rs b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.rs new file mode 100644 index 0000000000000..170bcf9590a76 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.rs @@ -0,0 +1,20 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +use std::alloc::{alloc, Layout}; + +fn inner(x: *mut i32, _y: &i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to write to `x` at all because `y` was assumed to be + // immutable for the duration of this call. + unsafe { *x = 0 }; + //~[stack]^ ERROR: protect + //~[tree]| ERROR: /write access through .* is forbidden/ +} + +fn main() { + unsafe { + let ptr = alloc(Layout::for_value(&0i32)) as *mut i32; + inner(ptr, &*ptr); + }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.stderr rename to src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr new file mode 100644 index 0000000000000..ef807d7362e44 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/invalidate_against_protector3.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through (root of the allocation) is forbidden + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | unsafe { *x = 0 }; + | ^^^^^^ write access through (root of the allocation) is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Frozen) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | let ptr = alloc(Layout::for_value(&0i32)) as *mut i32; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the protected tag was created here, in the initial state Frozen + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | fn inner(x: *mut i32, _y: &i32) { + | ^^ + = note: BACKTRACE (of the first span): + = note: inside `inner` at $DIR/invalidate_against_protector3.rs:LL:CC +note: inside `main` + --> $DIR/invalidate_against_protector3.rs:LL:CC + | +LL | inner(ptr, &*ptr); + | ^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.rs similarity index 75% rename from src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs rename to src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.rs index 075efe494123d..424aace239a21 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.rs +++ b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.rs @@ -1,3 +1,5 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows //@error-in-other-file: pointer to 4 bytes starting at offset 0 is out-of-bounds fn main() { diff --git a/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.stack.stderr b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.stack.stderr new file mode 100644 index 0000000000000..a5580160c391b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.stack.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/issue-miri-1050-1.rs:LL:CC + | +LL | let ptr = Box::into_raw(Box::new(0u16)); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` + --> $DIR/issue-miri-1050-1.rs:LL:CC + | +LL | drop(Box::from_raw(ptr as *mut u32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.tree.stderr new file mode 100644 index 0000000000000..a5580160c391b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-1.tree.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/issue-miri-1050-1.rs:LL:CC + | +LL | let ptr = Box::into_raw(Box::new(0u16)); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` + --> $DIR/issue-miri-1050-1.rs:LL:CC + | +LL | drop(Box::from_raw(ptr as *mut u32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.rs b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.rs new file mode 100644 index 0000000000000..5c947e6414258 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.rs @@ -0,0 +1,11 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +//@error-in-other-file: is a dangling pointer +use std::ptr::NonNull; + +fn main() { + unsafe { + let ptr = NonNull::::dangling(); + drop(Box::from_raw(ptr.as_ptr())); + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.stderr rename to src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.tree.stderr new file mode 100644 index 0000000000000..23d7fdcd03bc5 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/issue-miri-1050-2.tree.stderr @@ -0,0 +1,21 @@ +error: Undefined Behavior: out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance) + --> RUSTLIB/alloc/src/boxed.rs:LL:CC + | +LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: 0x4[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC +note: inside `main` + --> $DIR/issue-miri-1050-2.rs:LL:CC + | +LL | drop(Box::from_raw(ptr.as_ptr())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.rs b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.rs new file mode 100644 index 0000000000000..2e0f14e32dbc9 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.rs @@ -0,0 +1,16 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +// Make sure we catch this even without validation +//@compile-flags: -Zmiri-disable-validation + +// Make sure that we cannot load from memory a `&` that got already invalidated. +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &*xraw }; + let xref_in_mem = Box::new(xref); + unsafe { *xraw = 42 }; // unfreeze + let _val = *xref_in_mem; + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.stderr rename to src/tools/miri/tests/fail/both_borrows/load_invalid_shr.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr new file mode 100644 index 0000000000000..9a3618ed85e70 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/load_invalid_shr.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | let _val = *xref_in_mem; + | ^^^^^^^^^^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | let xref_in_mem = Box::new(xref); + | ^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | let xref = unsafe { &*xraw }; + | ^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/load_invalid_shr.rs:LL:CC + | +LL | unsafe { *xraw = 42 }; // unfreeze + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/load_invalid_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.rs b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.rs new file mode 100644 index 0000000000000..1bca637ae3bdf --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.rs @@ -0,0 +1,38 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +fn demo_mut_advanced_unique(our: &mut i32) -> i32 { + unknown_code_1(&*our); + + // This "re-asserts" uniqueness of the reference: After writing, we know + // our tag is at the top of the stack. + *our = 5; + + unknown_code_2(); + + // We know this will return 5 + *our +} + +// Now comes the evil context +use std::ptr; + +static mut LEAK: *mut i32 = ptr::null_mut(); + +fn unknown_code_1(x: &i32) { + unsafe { + LEAK = x as *const _ as *mut _; + } +} + +fn unknown_code_2() { + unsafe { + *LEAK = 7; + //~[stack]^ ERROR: /write access .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /write access through .* is forbidden/ + } +} + +fn main() { + demo_mut_advanced_unique(&mut 0); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.stderr rename to src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr new file mode 100644 index 0000000000000..9cb500679fa85 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation1.tree.stderr @@ -0,0 +1,42 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | *LEAK = 7; + | ^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child write access +help: the accessed tag was created here + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | fn unknown_code_1(x: &i32) { + | ^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | unknown_code_1(&*our); + | ^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | *our = 5; + | ^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `unknown_code_2` at $DIR/mut_exclusive_violation1.rs:LL:CC +note: inside `demo_mut_advanced_unique` + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | unknown_code_2(); + | ^^^^^^^^^^^^^^^^ +note: inside `main` + --> $DIR/mut_exclusive_violation1.rs:LL:CC + | +LL | demo_mut_advanced_unique(&mut 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.rs b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.rs new file mode 100644 index 0000000000000..43f8b9d950ca5 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.rs @@ -0,0 +1,16 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +use std::ptr::NonNull; + +fn main() { + unsafe { + let x = &mut 0; + let mut ptr1 = NonNull::from(x); + let mut ptr2 = ptr1.clone(); + let raw1 = ptr1.as_mut(); + let raw2 = ptr2.as_mut(); + let _val = *raw1; //~[stack] ERROR: /read access .* tag does not exist in the borrow stack/ + *raw2 = 2; + *raw1 = 3; //~[tree] ERROR: /write access through .* is forbidden/ + } +} diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.stack.stderr b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.stack.stderr new file mode 100644 index 0000000000000..258189f88783c --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.stack.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let _val = *raw1; + | ^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a Unique retag at offsets [0x0..0x4] + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let raw1 = ptr1.as_mut(); + | ^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a Unique retag + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let raw2 = ptr2.as_mut(); + | ^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr new file mode 100644 index 0000000000000..5d126bdaebfce --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/mut_exclusive_violation2.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | *raw1 = 3; + | ^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child write access +help: the accessed tag was created here + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let raw1 = ptr1.as_mut(); + | ^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | let raw1 = ptr1.as_mut(); + | ^^^^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/mut_exclusive_violation2.rs:LL:CC + | +LL | *raw2 = 2; + | ^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs new file mode 100644 index 0000000000000..98dbef6a996b5 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.rs @@ -0,0 +1,22 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +//@[stack]error-in-other-file: which is strongly protected +//@[tree]error-in-other-file: /deallocation through .* is forbidden/ +struct Newtype<'a>(&'a mut i32, i32); + +fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { + dealloc(); +} + +// Make sure that we protect references inside structs that are passed as ScalarPair. +fn main() { + let ptr = Box::into_raw(Box::new(0i32)); + #[rustfmt::skip] // I like my newlines + unsafe { + dealloc_while_running( + Newtype(&mut *ptr, 0), + || drop(Box::from_raw(ptr)), + ) + }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_pair_retagging.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/newtype_pair_retagging.stderr rename to src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr new file mode 100644 index 0000000000000..f26fc6cbaae26 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/newtype_pair_retagging.tree.stderr @@ -0,0 +1,55 @@ +error: Undefined Behavior: deallocation through is forbidden + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocation through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this deallocation (acting as a foreign write access) would cause the protected tag (currently Frozen) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/newtype_pair_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: the protected tag was created here, in the initial state Reserved + --> $DIR/newtype_pair_retagging.rs:LL:CC + | +LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { + | ^^ +help: the protected tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] + --> $DIR/newtype_pair_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside closure + --> $DIR/newtype_pair_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^^^^^^^ +note: inside `dealloc_while_running::<[closure@$DIR/newtype_pair_retagging.rs:LL:CC]>` + --> $DIR/newtype_pair_retagging.rs:LL:CC + | +LL | dealloc(); + | ^^^^^^^^^ +note: inside `main` + --> $DIR/newtype_pair_retagging.rs:LL:CC + | +LL | / dealloc_while_running( +LL | | Newtype(&mut *ptr, 0), +LL | | || drop(Box::from_raw(ptr)), +LL | | ) + | |_________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs new file mode 100644 index 0000000000000..e280050cdad77 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.rs @@ -0,0 +1,23 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +//@[stack]error-in-other-file: which is strongly protected +//@[tree]error-in-other-file: /deallocation through .* is forbidden/ + +struct Newtype<'a>(&'a mut i32); + +fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { + dealloc(); +} + +// Make sure that we protect references inside structs. +fn main() { + let ptr = Box::into_raw(Box::new(0i32)); + #[rustfmt::skip] // I like my newlines + unsafe { + dealloc_while_running( + Newtype(&mut *ptr), + || drop(Box::from_raw(ptr)), + ) + }; +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.stderr rename to src/tools/miri/tests/fail/both_borrows/newtype_retagging.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr new file mode 100644 index 0000000000000..687c72d574d0d --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/newtype_retagging.tree.stderr @@ -0,0 +1,55 @@ +error: Undefined Behavior: deallocation through is forbidden + --> RUSTLIB/alloc/src/alloc.rs:LL:CC + | +LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ deallocation through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) + = help: this deallocation (acting as a foreign write access) would cause the protected tag (currently Frozen) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: the protected tag was created here, in the initial state Reserved + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { + | ^^ +help: the protected tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC + = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC + = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC +note: inside closure + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | || drop(Box::from_raw(ptr)), + | ^^^^^^^^^^^^^^^^^^^^^^^^ +note: inside `dealloc_while_running::<[closure@$DIR/newtype_retagging.rs:LL:CC]>` + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | dealloc(); + | ^^^^^^^^^ +note: inside `main` + --> $DIR/newtype_retagging.rs:LL:CC + | +LL | / dealloc_while_running( +LL | | Newtype(&mut *ptr), +LL | | || drop(Box::from_raw(ptr)), +LL | | ) + | |_________^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/outdated_local.rs b/src/tools/miri/tests/fail/both_borrows/outdated_local.rs new file mode 100644 index 0000000000000..1330496bbe931 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/outdated_local.rs @@ -0,0 +1,14 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +fn main() { + let mut x = 0; + let y: *const i32 = &x; + x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local + + assert_eq!(unsafe { *y }, 1); + //~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /read access through .* is forbidden/ + + assert_eq!(x, 1); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr b/src/tools/miri/tests/fail/both_borrows/outdated_local.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/outdated_local.stderr rename to src/tools/miri/tests/fail/both_borrows/outdated_local.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr b/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr new file mode 100644 index 0000000000000..21800d6f4e445 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/outdated_local.tree.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: read access through is forbidden + --> $DIR/outdated_local.rs:LL:CC + | +LL | assert_eq!(unsafe { *y }, 1); + | ^^ read access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Frozen + --> $DIR/outdated_local.rs:LL:CC + | +LL | let y: *const i32 = &x; + | ^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/outdated_local.rs:LL:CC + | +LL | x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local + | ^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/outdated_local.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.rs b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.rs new file mode 100644 index 0000000000000..588478054452a --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.rs @@ -0,0 +1,15 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Make sure that we cannot pass by argument a `&` that got already invalidated. +fn foo(_: &i32) {} + +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &*xraw }; + unsafe { *xraw = 42 }; // unfreeze + foo(xref); + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.stderr rename to src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr new file mode 100644 index 0000000000000..677e6eeb48389 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr.tree.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/pass_invalid_shr.rs:LL:CC + | +LL | foo(xref); + | ^^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Frozen + --> $DIR/pass_invalid_shr.rs:LL:CC + | +LL | let xref = unsafe { &*xraw }; + | ^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/pass_invalid_shr.rs:LL:CC + | +LL | unsafe { *xraw = 42 }; // unfreeze + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/pass_invalid_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.rs b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.rs new file mode 100644 index 0000000000000..d23bb7555fb8f --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.rs @@ -0,0 +1,15 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Make sure that we cannot pass by argument a `&` that got already invalidated. +fn foo(_: Option<&i32>) {} + +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let some_xref = unsafe { Some(&*xraw) }; + unsafe { *xraw = 42 }; // unfreeze + foo(some_xref); + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.stack.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.stack.stderr new file mode 100644 index 0000000000000..26d9f38f239c4 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.stack.stderr @@ -0,0 +1,29 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | foo(some_xref); + | ^^^^^^^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x0..0x4] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | let some_xref = unsafe { Some(&*xraw) }; + | ^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | unsafe { *xraw = 42 }; // unfreeze + | ^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/pass_invalid_shr_option.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr new file mode 100644 index 0000000000000..242ae0de730f3 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_option.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | foo(some_xref); + | ^^^^^^^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | let some_xref = unsafe { Some(&*xraw) }; + | ^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | let some_xref = unsafe { Some(&*xraw) }; + | ^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/pass_invalid_shr_option.rs:LL:CC + | +LL | unsafe { *xraw = 42 }; // unfreeze + | ^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/pass_invalid_shr_option.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.rs b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.rs new file mode 100644 index 0000000000000..2244ab41d7e20 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.rs @@ -0,0 +1,16 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Make sure that we cannot pass by argument a `&` that got already invalidated. +fn foo(_: (&i32, &i32)) {} + +fn main() { + let x = &mut (42i32, 31i32); + let xraw0 = &mut x.0 as *mut _; + let xraw1 = &mut x.1 as *mut _; + let pair_xref = unsafe { (&*xraw0, &*xraw1) }; + unsafe { *xraw0 = 42 }; // unfreeze + foo(pair_xref); + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.stack.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.stack.stderr new file mode 100644 index 0000000000000..5f0fbf127597a --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.stack.stderr @@ -0,0 +1,29 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | foo(pair_xref); + | ^^^^^^^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x0..0x4] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | let pair_xref = unsafe { (&*xraw0, &*xraw1) }; + | ^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | unsafe { *xraw0 = 42 }; // unfreeze + | ^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/pass_invalid_shr_tuple.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr new file mode 100644 index 0000000000000..87b9b1361b7ba --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/pass_invalid_shr_tuple.tree.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | foo(pair_xref); + | ^^^^^^^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | let pair_xref = unsafe { (&*xraw0, &*xraw1) }; + | ^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | let pair_xref = unsafe { (&*xraw0, &*xraw1) }; + | ^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/pass_invalid_shr_tuple.rs:LL:CC + | +LL | unsafe { *xraw0 = 42 }; // unfreeze + | ^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/pass_invalid_shr_tuple.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs new file mode 100644 index 0000000000000..f192e76de1350 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.rs @@ -0,0 +1,29 @@ +//@revisions: stack tree +//@compile-flags: -Zmiri-preemption-rate=0 +//@[tree]compile-flags: -Zmiri-tree-borrows +use std::thread; + +#[derive(Copy, Clone)] +struct SendPtr(*mut i32); +unsafe impl Send for SendPtr {} + +fn main() { + let mut mem = 0; + let ptr = SendPtr(&mut mem as *mut _); + + let t = thread::spawn(move || { + let ptr = ptr; + // We do a protected 2phase retag (but no write!) in this thread. + fn retag(_x: &mut i32) {} //~[tree]ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `` + retag(unsafe { &mut *ptr.0 }); //~[stack]ERROR: Data race detected between (1) Read on thread `main` and (2) Write on thread `` + }); + + // We do a read in the main thread. + unsafe { ptr.0.read() }; + + // These two operations do not commute -- if the read happens after the retag, the retagged pointer + // gets frozen! So we want this to be considered UB so that we can still freely move the read around + // in this thread without worrying about reordering with retags in other threads. + + t.join().unwrap(); +} diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.stack.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.stack.stderr new file mode 100644 index 0000000000000..10fb1dece2afc --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.stack.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: Data race detected between (1) Read on thread `main` and (2) Write on thread `` at ALLOC. (2) just happened here + --> $DIR/retag_data_race_protected_read.rs:LL:CC + | +LL | retag(unsafe { &mut *ptr.0 }); + | ^^^^^^^^^^^ Data race detected between (1) Read on thread `main` and (2) Write on thread `` at ALLOC. (2) just happened here + | +help: and (1) occurred earlier here + --> $DIR/retag_data_race_protected_read.rs:LL:CC + | +LL | unsafe { ptr.0.read() }; + | ^^^^^^^^^^^^ + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE (of the first span): + = note: inside closure at $DIR/retag_data_race_protected_read.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr new file mode 100644 index 0000000000000..173acf4b96c03 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_protected_read.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: Data race detected between (1) Read on thread `main` and (2) Write on thread `` at ALLOC. (2) just happened here + --> $DIR/retag_data_race_protected_read.rs:LL:CC + | +LL | fn retag(_x: &mut i32) {} + | ^^ Data race detected between (1) Read on thread `main` and (2) Write on thread `` at ALLOC. (2) just happened here + | +help: and (1) occurred earlier here + --> $DIR/retag_data_race_protected_read.rs:LL:CC + | +LL | unsafe { ptr.0.read() }; + | ^^^^^^^^^^^^ + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE (of the first span): + = note: inside `main::{closure#0}::retag` at $DIR/retag_data_race_protected_read.rs:LL:CC +note: inside closure + --> $DIR/retag_data_race_protected_read.rs:LL:CC + | +LL | ... retag(unsafe { &mut *ptr.0 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs new file mode 100644 index 0000000000000..868b3beb53b7d --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs @@ -0,0 +1,33 @@ +//! Make sure that a retag acts like a write for the data race model. +//@revisions: stack tree +//@compile-flags: -Zmiri-preemption-rate=0 +//@[tree]compile-flags: -Zmiri-tree-borrows +#[derive(Copy, Clone)] +struct SendPtr(*mut u8); + +unsafe impl Send for SendPtr {} + +fn thread_1(p: SendPtr) { + let p = p.0; + unsafe { + let _r = &mut *p; + } +} + +fn thread_2(p: SendPtr) { + let p = p.0; + unsafe { + *p = 5; //~ ERROR: /Data race detected between \(1\) (Read|Write) on thread `` and \(2\) Write on thread ``/ + } +} + +fn main() { + let mut x = 0; + let p = std::ptr::addr_of_mut!(x); + let p = SendPtr(p); + + let t1 = std::thread::spawn(move || thread_1(p)); + let t2 = std::thread::spawn(move || thread_2(p)); + let _ = t1.join(); + let _ = t2.join(); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_write.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/retag_data_race_write.stderr rename to src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr new file mode 100644 index 0000000000000..37d216b9877be --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: Data race detected between (1) Read on thread `` and (2) Write on thread `` at ALLOC. (2) just happened here + --> $DIR/retag_data_race_write.rs:LL:CC + | +LL | *p = 5; + | ^^^^^^ Data race detected between (1) Read on thread `` and (2) Write on thread `` at ALLOC. (2) just happened here + | +help: and (1) occurred earlier here + --> $DIR/retag_data_race_write.rs:LL:CC + | +LL | let _r = &mut *p; + | ^^^^^^^ + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE (of the first span): + = note: inside `thread_2` at $DIR/retag_data_race_write.rs:LL:CC +note: inside closure + --> $DIR/retag_data_race_write.rs:LL:CC + | +LL | let t2 = std::thread::spawn(move || thread_2(p)); + | ^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.rs b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.rs new file mode 100644 index 0000000000000..43163ab1b8859 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.rs @@ -0,0 +1,16 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Make sure that we cannot return a `&` that got already invalidated. +fn foo(x: &mut (i32, i32)) -> &i32 { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &(*xraw).1 }; + unsafe { *xraw = (42, 23) }; // unfreeze + ret + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} + +fn main() { + foo(&mut (1, 2)); +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.stderr rename to src/tools/miri/tests/fail/both_borrows/return_invalid_shr.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr new file mode 100644 index 0000000000000..4c016ea621c7e --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr.tree.stderr @@ -0,0 +1,31 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | ret + | ^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Frozen + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | let ret = unsafe { &(*xraw).1 }; + | ^^^^^^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x8] + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/return_invalid_shr.rs:LL:CC +note: inside `main` + --> $DIR/return_invalid_shr.rs:LL:CC + | +LL | foo(&mut (1, 2)); + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.rs b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.rs new file mode 100644 index 0000000000000..a5b53c57a9cef --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.rs @@ -0,0 +1,19 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Make sure that we cannot return a `&` that got already invalidated, not even in an `Option`. +fn foo(x: &mut (i32, i32)) -> Option<&i32> { + let xraw = x as *mut (i32, i32); + let ret = Some(unsafe { &(*xraw).1 }); + unsafe { *xraw = (42, 23) }; // unfreeze + ret + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} + +fn main() { + match foo(&mut (1, 2)) { + Some(_x) => {} + None => {} + } +} diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.stack.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.stack.stderr new file mode 100644 index 0000000000000..7a9f061228a52 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.stack.stderr @@ -0,0 +1,34 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | ret + | ^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x4..0x8] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x4..0x8] + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | let ret = Some(unsafe { &(*xraw).1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a write access + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/return_invalid_shr_option.rs:LL:CC +note: inside `main` + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | match foo(&mut (1, 2)) { + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr new file mode 100644 index 0000000000000..b9ef6dea6ef4e --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_option.tree.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | ret + | ^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | let ret = Some(unsafe { &(*xraw).1 }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | let ret = Some(unsafe { &(*xraw).1 }); + | ^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x8] + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/return_invalid_shr_option.rs:LL:CC +note: inside `main` + --> $DIR/return_invalid_shr_option.rs:LL:CC + | +LL | match foo(&mut (1, 2)) { + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.rs b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.rs new file mode 100644 index 0000000000000..925525aaa5e1f --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.rs @@ -0,0 +1,16 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Make sure that we cannot return a `&` that got already invalidated, not even in a tuple. +fn foo(x: &mut (i32, i32)) -> (&i32,) { + let xraw = x as *mut (i32, i32); + let ret = (unsafe { &(*xraw).1 },); + unsafe { *xraw = (42, 23) }; // unfreeze + ret + //~[stack]^ ERROR: /retag .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /reborrow through .* is forbidden/ +} + +fn main() { + foo(&mut (1, 2)).0; +} diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.stack.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.stack.stderr new file mode 100644 index 0000000000000..6a98c9121ef76 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.stack.stderr @@ -0,0 +1,34 @@ +error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | ret + | ^^^ + | | + | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location + | this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x4..0x8] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x4..0x8] + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | let ret = (unsafe { &(*xraw).1 },); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: was later invalidated at offsets [0x0..0x8] by a write access + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/return_invalid_shr_tuple.rs:LL:CC +note: inside `main` + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | foo(&mut (1, 2)).0; + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr new file mode 100644 index 0000000000000..c3fd124e6cb37 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/return_invalid_shr_tuple.tree.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | ret + | ^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | let ret = (unsafe { &(*xraw).1 },); + | ^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | let ret = (unsafe { &(*xraw).1 },); + | ^^^^^^^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x8] + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | unsafe { *xraw = (42, 23) }; // unfreeze + | ^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/return_invalid_shr_tuple.rs:LL:CC +note: inside `main` + --> $DIR/return_invalid_shr_tuple.rs:LL:CC + | +LL | foo(&mut (1, 2)).0; + | ^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.rs b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.rs new file mode 100644 index 0000000000000..0c7f4b89711ae --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.rs @@ -0,0 +1,22 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +#![allow(invalid_reference_casting)] + +fn foo(x: &mut i32) -> i32 { + *x = 5; + unknown_code(&*x); + *x // must return 5 +} + +fn main() { + println!("{}", foo(&mut 0)); +} + +fn unknown_code(x: &i32) { + unsafe { + *(x as *const i32 as *mut i32) = 7; + //~[stack]^ ERROR: /write access .* only grants SharedReadOnly permission/ + //~[tree]| ERROR: /write access through .* is forbidden/ + } +} diff --git a/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.stack.stderr similarity index 100% rename from src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.stderr rename to src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.stack.stderr diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr new file mode 100644 index 0000000000000..9aeaa7ff0758e --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation1.tree.stderr @@ -0,0 +1,36 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | *(x as *const i32 as *mut i32) = 7; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Frozen which forbids this child write access +help: the accessed tag was created here + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | fn unknown_code(x: &i32) { + | ^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | unknown_code(&*x); + | ^^^ + = note: BACKTRACE (of the first span): + = note: inside `unknown_code` at $DIR/shr_frozen_violation1.rs:LL:CC +note: inside `foo` + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | unknown_code(&*x); + | ^^^^^^^^^^^^^^^^^ +note: inside `main` + --> $DIR/shr_frozen_violation1.rs:LL:CC + | +LL | println!("{}", foo(&mut 0)); + | ^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.rs b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.rs new file mode 100644 index 0000000000000..7b2b63ef215e6 --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.rs @@ -0,0 +1,15 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +fn main() { + unsafe { + let mut x = 0; + let ptr = std::ptr::addr_of_mut!(x); + let frozen = &*ptr; + let _val = *frozen; + x = 1; + let _val = *frozen; + //~[stack]^ ERROR: /read access .* tag does not exist in the borrow stack/ + //~[tree]| ERROR: /read access through .* is forbidden/ + let _val = x; // silence warning + } +} diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.stack.stderr b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.stack.stderr new file mode 100644 index 0000000000000..0f09359007dba --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.stack.stderr @@ -0,0 +1,28 @@ +error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + --> $DIR/shr_frozen_violation2.rs:LL:CC + | +LL | let _val = *frozen; + | ^^^^^^^ + | | + | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location + | this error occurs as part of an access at ALLOC[0x0..0x4] + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadOnly retag at offsets [0x0..0x4] + --> $DIR/shr_frozen_violation2.rs:LL:CC + | +LL | let frozen = &*ptr; + | ^^^^^ +help: was later invalidated at offsets [0x0..0x4] by a write access + --> $DIR/shr_frozen_violation2.rs:LL:CC + | +LL | x = 1; + | ^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/shr_frozen_violation2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr new file mode 100644 index 0000000000000..0269f3ab6409b --- /dev/null +++ b/src/tools/miri/tests/fail/both_borrows/shr_frozen_violation2.tree.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: read access through is forbidden + --> $DIR/shr_frozen_violation2.rs:LL:CC + | +LL | let _val = *frozen; + | ^^^^^^^ read access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Disabled which forbids this child read access +help: the accessed tag was created here, in the initial state Frozen + --> $DIR/shr_frozen_violation2.rs:LL:CC + | +LL | let frozen = &*ptr; + | ^^^^^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x4] + --> $DIR/shr_frozen_violation2.rs:LL:CC + | +LL | x = 1; + | ^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/shr_frozen_violation2.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/box-cell-alias.stderr b/src/tools/miri/tests/fail/box-cell-alias.stderr index fc946d6d39b3e..8c68261aaf7ab 100644 --- a/src/tools/miri/tests/fail/box-cell-alias.stderr +++ b/src/tools/miri/tests/fail/box-cell-alias.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: trying to retag from for SharedReadWrite permis --> $DIR/box-cell-alias.rs:LL:CC | LL | unsafe { (*ptr).set(20) }; - | ^^^^^^^^^^^^^^ + | ^^^^^^ | | | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location | this error occurs as part of retag at ALLOC[0x0..0x1] diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs index d89c670b632e6..d5e6d37226ab3 100644 --- a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs +++ b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs @@ -11,6 +11,6 @@ unsafe impl Send for SendRaw {} fn main() { unsafe { let dangling_ptr = std::thread::spawn(|| SendRaw(&TLS as *const u8)).join().unwrap(); - let _val = *dangling_ptr.0; //~ ERROR: dereferenced after this allocation got freed + let _val = *dangling_ptr.0; //~ ERROR: has been freed } } diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr index cc3e56398781b..0cb8aa29001f3 100644 --- a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr +++ b/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/thread_local_static_dealloc.rs:LL:CC | LL | let _val = *dangling_ptr.0; - | ^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr index 98db33e3206bd..ceb5955922a3f 100644 --- a/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr +++ b/src/tools/miri/tests/fail/concurrency/unwind_top_of_stack.stderr @@ -1,4 +1,5 @@ -thread '' panicked at 'explicit panic', $DIR/unwind_top_of_stack.rs:LL:CC +thread '' panicked at $DIR/unwind_top_of_stack.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: Undefined Behavior: unwinding past the topmost frame of the stack --> $DIR/unwind_top_of_stack.rs:LL:CC diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs index 4249c1cbf0177..49f3ae306a076 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs @@ -7,6 +7,6 @@ fn main() { let b = Box::new(42); &*b as *const i32 }; - let x = unsafe { ptr::addr_of!(*p) }; //~ ERROR: dereferenced after this allocation got freed + let x = unsafe { ptr::addr_of!(*p) }; //~ ERROR: has been freed panic!("this should never print: {:?}", x); } diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr index 5f081afe68af8..6a3efbdd3dd89 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/dangling_pointer_addr_of.rs:LL:CC | LL | let x = unsafe { ptr::addr_of!(*p) }; - | ^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/dangling_pointer_addr_of.rs:LL:CC + | +LL | let b = Box::new(42); + | ^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dangling_pointer_addr_of.rs:LL:CC + | +LL | }; + | ^ + = note: BACKTRACE (of the first span): = note: inside `main` at RUSTLIB/core/src/ptr/mod.rs:LL:CC = note: this error originates in the macro `ptr::addr_of` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs index ad2a599b60b48..675e3e15da8c6 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.rs @@ -6,6 +6,6 @@ fn main() { let b = Box::new(42); &*b as *const i32 }; - let x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed + let x = unsafe { *p }; //~ ERROR: has been freed panic!("this should never print: {}", x); } diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr index cb323818845df..fad4b4be28c2a 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/dangling_pointer_deref.rs:LL:CC | LL | let x = unsafe { *p }; - | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/dangling_pointer_deref.rs:LL:CC + | +LL | let b = Box::new(42); + | ^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dangling_pointer_deref.rs:LL:CC + | +LL | }; + | ^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/dangling_pointer_deref.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref_underscore.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref_underscore.rs deleted file mode 100644 index 7c5f440b774f2..0000000000000 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref_underscore.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Make sure we find these even with many checks disabled. -//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation - -fn main() { - let p = { - let b = Box::new(42); - &*b as *const i32 - }; - unsafe { - let _ = *p; //~ ERROR: dereferenced after this allocation got freed - } - panic!("this should never print"); -} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref_underscore.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref_underscore.stderr deleted file mode 100644 index 7b76389c75359..0000000000000 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref_underscore.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed - --> $DIR/dangling_pointer_deref_underscore.rs:LL:CC - | -LL | let _ = *p; - | ^^ pointer to ALLOC was dereferenced after this allocation got freed - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: - = note: inside `main` at $DIR/dangling_pointer_deref_underscore.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_offset.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_offset.rs new file mode 100644 index 0000000000000..65eca07a0708c --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_offset.rs @@ -0,0 +1,11 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let p = { + let b = Box::new(42); + &*b as *const i32 + }; + let x = unsafe { p.offset(42) }; //~ ERROR: /out-of-bounds pointer arithmetic: .* has been freed/ + panic!("this should never print: {:?}", x); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_offset.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_offset.stderr new file mode 100644 index 0000000000000..7ef5fd329a442 --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_offset.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has been freed, so this pointer is dangling + --> $DIR/dangling_pointer_offset.rs:LL:CC + | +LL | let x = unsafe { p.offset(42) }; + | ^^^^^^^^^^^^ out-of-bounds pointer arithmetic: ALLOC has been freed, so this pointer is dangling + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/dangling_pointer_offset.rs:LL:CC + | +LL | let b = Box::new(42); + | ^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dangling_pointer_offset.rs:LL:CC + | +LL | }; + | ^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/dangling_pointer_offset.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_project_underscore.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_project_underscore.rs new file mode 100644 index 0000000000000..4c6412439507d --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_project_underscore.rs @@ -0,0 +1,13 @@ +// Make sure we find these even with many checks disabled. +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation + +fn main() { + let p = { + let b = Box::new(42); + &*b as *const i32 + }; + unsafe { + let _ = *p; //~ ERROR: has been freed + } + panic!("this should never print"); +} diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_project_underscore.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_project_underscore.stderr new file mode 100644 index 0000000000000..1de6465802b2b --- /dev/null +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_project_underscore.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling + --> $DIR/dangling_pointer_project_underscore.rs:LL:CC + | +LL | let _ = *p; + | ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/dangling_pointer_project_underscore.rs:LL:CC + | +LL | let b = Box::new(42); + | ^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dangling_pointer_project_underscore.rs:LL:CC + | +LL | }; + | ^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/dangling_pointer_project_underscore.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs index 534d7d5f42f32..a1fefe04ab69c 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.rs @@ -1,11 +1,10 @@ // Make sure we find these even with many checks disabled. -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation +//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation fn main() { let p = { let b = Box::new(42); &*b as *const i32 as *const () }; - let _x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed + let _x = unsafe { *p }; //~ ERROR: has been freed } diff --git a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr index 02db6302a0a1e..bf6ee775e9465 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/dangling_zst_deref.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/dangling_zst_deref.rs:LL:CC | LL | let _x = unsafe { *p }; - | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/dangling_zst_deref.rs:LL:CC + | +LL | let b = Box::new(42); + | ^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dangling_zst_deref.rs:LL:CC + | +LL | }; + | ^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/dangling_zst_deref.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs index 54f353ebebeb1..87ca8a6077cad 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/dyn_size.rs @@ -1,5 +1,5 @@ -// should find the bug even without these, but gets masked by optimizations -//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -Zmir-opt-level=0 +// should find the bug even without these +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows struct SliceWithHead(u8, [u8]); diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs index a48a3189db2e3..73d0b12068013 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_deref_zst.rs @@ -1,6 +1,3 @@ -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 - fn main() { // This pointer *could* be NULL so we cannot load from it, not even at ZST let ptr = (&0u8 as *const u8).wrapping_sub(0x800) as *const (); diff --git a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs index 449c65d218a02..5537207ae424f 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/maybe_null_pointer_write_zst.rs @@ -1,6 +1,3 @@ -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 - fn main() { // This pointer *could* be NULL so we cannot load from it, not even at ZST. // Not using the () type here, as writes of that type do not even have MIR generated. diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs index d6a607c61cbeb..4cb805db09526 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_deref_zst.rs @@ -1,6 +1,3 @@ -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 - #[allow(deref_nullptr)] fn main() { let x: () = unsafe { *std::ptr::null() }; //~ ERROR: dereferencing pointer failed: null pointer is a dangling pointer diff --git a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs index 21344208130ea..ec34c631a4667 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/null_pointer_write_zst.rs @@ -1,6 +1,3 @@ -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 - #[allow(deref_nullptr)] fn main() { // Not using the () type here, as writes of that type do not even have MIR generated. diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr index b2461ce4230ad..7d2aed371bdc4 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read1.stderr @@ -6,8 +6,14 @@ LL | let x = unsafe { *v.as_ptr().wrapping_offset(5) }; | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/out_of_bounds_read1.rs:LL:CC + | +LL | let v: Vec = vec![1, 2]; + | ^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/out_of_bounds_read1.rs:LL:CC + = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr index b17058b406298..69a8498f0977e 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/out_of_bounds_read2.stderr @@ -6,8 +6,14 @@ LL | let x = unsafe { *v.as_ptr().wrapping_offset(5) }; | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/out_of_bounds_read2.rs:LL:CC + | +LL | let v: Vec = vec![1, 2]; + | ^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/out_of_bounds_read2.rs:LL:CC + = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs index 1373773f68d59..5f8a1988b3c10 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.rs @@ -1,5 +1,5 @@ -// This should fail even without validation, but some MIR opts mask the error -//@compile-flags: -Zmiri-disable-validation -Zmir-opt-level=0 +// This should fail even without validation +//@compile-flags: -Zmiri-disable-validation unsafe fn make_ref<'a>(x: *mut i32) -> &'a mut i32 { &mut *x @@ -8,7 +8,7 @@ unsafe fn make_ref<'a>(x: *mut i32) -> &'a mut i32 { fn main() { unsafe { let x = make_ref(&mut 0); // The temporary storing "0" is deallocated at the ";"! - let val = *x; //~ ERROR: dereferenced after this allocation got freed + let val = *x; //~ ERROR: has been freed println!("{}", val); } } diff --git a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr index 679e4809ca663..500f28a3cbc75 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/stack_temporary.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/stack_temporary.rs:LL:CC | LL | let val = *x; - | ^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs index 03113585d1464..f9983f48c6174 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs +++ b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.rs @@ -1,5 +1,5 @@ -// This should fail even without validation, but some MIR opts mask the error -//@compile-flags: -Zmiri-disable-validation -Zmir-opt-level=0 -Zmiri-permissive-provenance +// This should fail even without validation +//@compile-flags: -Zmiri-disable-validation -Zmiri-permissive-provenance static mut LEAK: usize = 0; @@ -10,7 +10,7 @@ fn fill(v: &mut i32) { } fn evil() { - unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer + let _ = unsafe { &mut *(LEAK as *mut i32) }; //~ ERROR: is a dangling pointer } fn main() { diff --git a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr index 2ba8116cadc24..6c41add60ef4a 100644 --- a/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr +++ b/src/tools/miri/tests/fail/dangling_pointers/storage_dead_dangling.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) --> $DIR/storage_dead_dangling.rs:LL:CC | -LL | unsafe { &mut *(LEAK as *mut i32) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) +LL | let _ = unsafe { &mut *(LEAK as *mut i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance) | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs index 5d7a0cc1dc93e..c921ce6b71651 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.rs @@ -32,7 +32,7 @@ pub fn main() { let ptr = ptr; // avoid field capturing // Also an error of the form: Data race detected between (1) Deallocate on thread `` and (2) Read on thread `` // but the invalid allocation is detected first. - *ptr.0 //~ ERROR: dereferenced after this allocation got freed + *ptr.0 //~ ERROR: has been freed }); j1.join().unwrap(); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr index f303d57c8bd9c..810e48d59c6b7 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr +++ b/src/tools/miri/tests/fail/data_race/dealloc_read_race2.stderr @@ -1,12 +1,26 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/dealloc_read_race2.rs:LL:CC | LL | *ptr.0 - | ^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/dealloc_read_race2.rs:LL:CC + | +LL | let pointer: *mut usize = Box::into_raw(Box::new(0usize)); + | ^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dealloc_read_race2.rs:LL:CC + | +LL | / __rust_dealloc( +LL | | ptr.0 as *mut _, +LL | | std::mem::size_of::(), +LL | | std::mem::align_of::(), +LL | | ) + | |_____________^ + = note: BACKTRACE (of the first span): = note: inside closure at $DIR/dealloc_read_race2.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs index a7f43f03c0258..e01132202d49e 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.rs @@ -31,7 +31,7 @@ pub fn main() { let ptr = ptr; // avoid field capturing // Also an error of the form: Data race detected between (1) Deallocate on thread `` and (2) Write on thread `` // but the invalid allocation is detected first. - *ptr.0 = 2; //~ ERROR: dereferenced after this allocation got freed + *ptr.0 = 2; //~ ERROR: has been freed }); j1.join().unwrap(); diff --git a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr index 23b8e9ade0e0e..7d672cd4d6228 100644 --- a/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr +++ b/src/tools/miri/tests/fail/data_race/dealloc_write_race2.stderr @@ -1,12 +1,26 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/dealloc_write_race2.rs:LL:CC | LL | *ptr.0 = 2; - | ^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/dealloc_write_race2.rs:LL:CC + | +LL | let pointer: *mut usize = Box::into_raw(Box::new(0usize)); + | ^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/dealloc_write_race2.rs:LL:CC + | +LL | / __rust_dealloc( +LL | | ptr.0 as *mut _, +LL | | std::mem::size_of::(), +LL | | std::mem::align_of::(), +LL | | ); + | |_____________^ + = note: BACKTRACE (of the first span): = note: inside closure at $DIR/dealloc_write_race2.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs index 40224ced12d08..c3e5c401d879a 100644 --- a/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs +++ b/src/tools/miri/tests/fail/data_race/read_write_race_stack.rs @@ -1,7 +1,4 @@ -//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows - -// Note: mir-opt-level set to 0 to prevent the read of stack_var in thread 1 -// from being optimized away and preventing the detection of the data-race. +//@compile-flags: -Zmiri-disable-weak-memory-emulation -Zmiri-preemption-rate=0 -Zmiri-disable-stacked-borrows use std::ptr::null_mut; use std::sync::atomic::{AtomicPtr, Ordering}; diff --git a/src/tools/miri/tests/fail/environ-gets-deallocated.rs b/src/tools/miri/tests/fail/environ-gets-deallocated.rs index e4597140b8440..6dcf1fdb1c7d3 100644 --- a/src/tools/miri/tests/fail/environ-gets-deallocated.rs +++ b/src/tools/miri/tests/fail/environ-gets-deallocated.rs @@ -20,5 +20,5 @@ fn main() { let pointer = get_environ(); let _x = unsafe { *pointer }; std::env::set_var("FOO", "BAR"); - let _y = unsafe { *pointer }; //~ ERROR: dereferenced after this allocation got freed + let _y = unsafe { *pointer }; //~ ERROR: has been freed } diff --git a/src/tools/miri/tests/fail/environ-gets-deallocated.stderr b/src/tools/miri/tests/fail/environ-gets-deallocated.stderr index a2d343bf8651c..6332846d5d8d1 100644 --- a/src/tools/miri/tests/fail/environ-gets-deallocated.stderr +++ b/src/tools/miri/tests/fail/environ-gets-deallocated.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/environ-gets-deallocated.rs:LL:CC | LL | let _y = unsafe { *pointer }; - | ^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/erroneous_const.rs b/src/tools/miri/tests/fail/erroneous_const.rs index d37837c71931e..65f7aafc3cc85 100644 --- a/src/tools/miri/tests/fail/erroneous_const.rs +++ b/src/tools/miri/tests/fail/erroneous_const.rs @@ -1,7 +1,5 @@ //! Make sure we detect erroneous constants post-monomorphization even when they are unused. //! (https://github.com/rust-lang/miri/issues/1382) -// Inlining changes the error location -//@compile-flags: -Zmir-opt-level=0 #![feature(never_type)] struct PrintName(T); diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs new file mode 100644 index 0000000000000..d47af50d407ec --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.rs @@ -0,0 +1,34 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows +#![feature(custom_mir, core_intrinsics)] +use std::intrinsics::mir::*; + +pub struct S(i32); + +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn main() { + mir! { + let _unit: (); + { + let non_copy = S(42); + let ptr = std::ptr::addr_of_mut!(non_copy); + // Inside `callee`, the first argument and `*ptr` are basically + // aliasing places! + Call(_unit = callee(Move(*ptr), ptr), after_call) + } + after_call = { + Return() + } + + } +} + +pub fn callee(x: S, ptr: *mut S) { + // With the setup above, if `x` is indeed moved in + // (i.e. we actually just get a pointer to the underlying storage), + // then writing to `ptr` will change the value stored in `x`! + unsafe { ptr.write(S(0)) }; + //~[stack]^ ERROR: not granting access + //~[tree]| ERROR: /write access .* forbidden/ + assert_eq!(x.0, 42); +} diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr new file mode 100644 index 0000000000000..381442e69b130 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.stack.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | unsafe { ptr.write(S(0)) }; + | ^^^^^^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | / mir! { +LL | | let _unit: (); +LL | | { +LL | | let non_copy = S(42); +... | +LL | | +LL | | } + | |_____^ +help: is this argument + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | unsafe { ptr.write(S(0)) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `callee` at $DIR/arg_inplace_mutate.rs:LL:CC +note: inside `main` + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | Call(_unit = callee(Move(*ptr), ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr new file mode 100644 index 0000000000000..544cd575ada33 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_mutate.tree.stderr @@ -0,0 +1,39 @@ +error: Undefined Behavior: write access through (root of the allocation) is forbidden + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | unsafe { ptr.write(S(0)) }; + | ^^^^^^^^^^^^^^^ write access through (root of the allocation) is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | / mir! { +LL | | let _unit: (); +LL | | { +LL | | let non_copy = S(42); +... | +LL | | +LL | | } + | |_____^ +help: the protected tag was created here, in the initial state Active + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | unsafe { ptr.write(S(0)) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `callee` at $DIR/arg_inplace_mutate.rs:LL:CC +note: inside `main` + --> $DIR/arg_inplace_mutate.rs:LL:CC + | +LL | Call(_unit = callee(Move(*ptr), ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs new file mode 100644 index 0000000000000..ea773048dd4e5 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.rs @@ -0,0 +1,27 @@ +#![feature(custom_mir, core_intrinsics)] +use std::intrinsics::mir::*; + +pub struct S(i32); + +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn main() { + mir! { + let _unit: (); + let _observe: i32; + { + let non_copy = S(42); + // This could change `non_copy` in-place + Call(_unit = change_arg(Move(non_copy)), after_call) + } + after_call = { + // So now we must not be allowed to observe non-copy again. + _observe = non_copy.0; //~ERROR: uninitialized + Return() + } + + } +} + +pub fn change_arg(mut x: S) { + x.0 = 0; +} diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.stderr new file mode 100644 index 0000000000000..5d9a3af0c8aa0 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_after.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/arg_inplace_observe_after.rs:LL:CC + | +LL | _observe = non_copy.0; + | ^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/arg_inplace_observe_after.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr new file mode 100644 index 0000000000000..cba23c21d12b1 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.none.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `change_arg` at $DIR/arg_inplace_observe_during.rs:LL:CC +note: inside `main` + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | Call(_unit = change_arg(Move(*ptr), ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs new file mode 100644 index 0000000000000..8c6a7df7a6dd5 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.rs @@ -0,0 +1,34 @@ +//@revisions: stack tree none +//@[tree]compile-flags: -Zmiri-tree-borrows +//@[none]compile-flags: -Zmiri-disable-stacked-borrows +#![feature(custom_mir, core_intrinsics)] +use std::intrinsics::mir::*; + +pub struct S(i32); + +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn main() { + mir! { + let _unit: (); + { + let non_copy = S(42); + let ptr = std::ptr::addr_of_mut!(non_copy); + // This could change `non_copy` in-place + Call(_unit = change_arg(Move(*ptr), ptr), after_call) + } + after_call = { + Return() + } + + } +} + +pub fn change_arg(mut x: S, ptr: *mut S) { + x.0 = 0; + // If `x` got passed in-place, we'd see the write through `ptr`! + // Make sure we are not allowed to do that read. + unsafe { ptr.read() }; + //~[stack]^ ERROR: not granting access + //~[tree]| ERROR: /read access .* forbidden/ + //~[none]| ERROR: uninitialized +} diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr new file mode 100644 index 0000000000000..f8532186be204 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.stack.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | / mir! { +LL | | let _unit: (); +LL | | { +LL | | let non_copy = S(42); +... | +LL | | +LL | | } + | |_____^ +help: is this argument + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | x.0 = 0; + | ^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `change_arg` at $DIR/arg_inplace_observe_during.rs:LL:CC +note: inside `main` + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | Call(_unit = change_arg(Move(*ptr), ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr new file mode 100644 index 0000000000000..c33645bdd280a --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/arg_inplace_observe_during.tree.stderr @@ -0,0 +1,39 @@ +error: Undefined Behavior: read access through (root of the allocation) is forbidden + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^ read access through (root of the allocation) is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign read access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | / mir! { +LL | | let _unit: (); +LL | | { +LL | | let non_copy = S(42); +... | +LL | | +LL | | } + | |_____^ +help: the protected tag was created here, in the initial state Active + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | x.0 = 0; + | ^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `change_arg` at $DIR/arg_inplace_observe_during.rs:LL:CC +note: inside `main` + --> $DIR/arg_inplace_observe_during.rs:LL:CC + | +LL | Call(_unit = change_arg(Move(*ptr), ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr index 7f87ec6f3bb69..f181f90abd3b8 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind1.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind1.rs:LL:CC +thread 'main' panicked at $DIR/exported_symbol_bad_unwind1.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding --> $DIR/exported_symbol_bad_unwind1.rs:LL:CC diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr index e1631471ae2be..bf5199307f6ce 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.both.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC +thread 'main' panicked at $DIR/exported_symbol_bad_unwind2.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: panic in a function that cannot unwind --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr index e1631471ae2be..bf5199307f6ce 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.definition.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC +thread 'main' panicked at $DIR/exported_symbol_bad_unwind2.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: panic in a function that cannot unwind --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr index b23c05a530357..c774bd4dd9112 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_bad_unwind2.extern_block.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_bad_unwind2.rs:LL:CC +thread 'main' panicked at $DIR/exported_symbol_bad_unwind2.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding --> $DIR/exported_symbol_bad_unwind2.rs:LL:CC diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr new file mode 100644 index 0000000000000..0a31adabf73d8 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.none.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC +note: inside `main` + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | Call(*ptr = myfun(ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs new file mode 100644 index 0000000000000..3d560af3d5eb3 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.rs @@ -0,0 +1,34 @@ +//@revisions: stack tree none +//@[tree]compile-flags: -Zmiri-tree-borrows +//@[none]compile-flags: -Zmiri-disable-stacked-borrows +#![feature(raw_ref_op)] +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; + +#[custom_mir(dialect = "runtime", phase = "optimized")] +pub fn main() { + mir! { + { + let x = 0; + let ptr = &raw mut x; + // We arrange for `myfun` to have a pointer that aliases + // its return place. Even just reading from that pointer is UB. + Call(*ptr = myfun(ptr), after_call) + } + + after_call = { + Return() + } + } +} + +fn myfun(ptr: *mut i32) -> i32 { + unsafe { ptr.read() }; + //~[stack]^ ERROR: not granting access + //~[tree]| ERROR: /read access .* forbidden/ + //~[none]| ERROR: uninitialized + // Without an aliasing model, reads are "fine" but at least they return uninit data. + 13 +} diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr new file mode 100644 index 0000000000000..875cc5edad986 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.stack.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information +help: was created by a SharedReadWrite retag at offsets [0x0..0x4] + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | / mir! { +LL | | { +LL | | let x = 0; +LL | | let ptr = &raw mut x; +... | +LL | | } +LL | | } + | |_____^ +help: is this argument + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC +note: inside `main` + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | Call(*ptr = myfun(ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr new file mode 100644 index 0000000000000..66c2fb8db19f0 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing.tree.stderr @@ -0,0 +1,39 @@ +error: Undefined Behavior: read access through (root of the allocation) is forbidden + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^ read access through (root of the allocation) is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign read access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | / mir! { +LL | | { +LL | | let x = 0; +LL | | let ptr = &raw mut x; +... | +LL | | } +LL | | } + | |_____^ +help: the protected tag was created here, in the initial state Active + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | unsafe { ptr.read() }; + | ^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `myfun` at $DIR/return_pointer_aliasing.rs:LL:CC +note: inside `main` + --> $DIR/return_pointer_aliasing.rs:LL:CC + | +LL | Call(*ptr = myfun(ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs new file mode 100644 index 0000000000000..9d53faccd1ef1 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.rs @@ -0,0 +1,30 @@ +//@compile-flags: -Zmiri-tree-borrows +#![feature(raw_ref_op)] +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; + +#[custom_mir(dialect = "runtime", phase = "optimized")] +pub fn main() { + mir! { + { + let _x = 0; + let ptr = &raw mut _x; + // We arrange for `myfun` to have a pointer that aliases + // its return place. Even just reading from that pointer is UB. + Call(_x = myfun(ptr), after_call) + } + + after_call = { + Return() + } + } +} + +fn myfun(ptr: *mut i32) -> i32 { + // This overwrites the return place, which shouldn't be possible through another pointer. + unsafe { ptr.write(0) }; + //~^ ERROR: /write access .* forbidden/ + 13 +} diff --git a/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr new file mode 100644 index 0000000000000..443ee8643fc28 --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/return_pointer_aliasing2.stderr @@ -0,0 +1,39 @@ +error: Undefined Behavior: write access through (root of the allocation) is forbidden + --> $DIR/return_pointer_aliasing2.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^ write access through (root of the allocation) is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this foreign write access would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> $DIR/return_pointer_aliasing2.rs:LL:CC + | +LL | / mir! { +LL | | { +LL | | let _x = 0; +LL | | let ptr = &raw mut _x; +... | +LL | | } +LL | | } + | |_____^ +help: the protected tag was created here, in the initial state Active + --> $DIR/return_pointer_aliasing2.rs:LL:CC + | +LL | unsafe { ptr.write(0) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `myfun` at $DIR/return_pointer_aliasing2.rs:LL:CC +note: inside `main` + --> $DIR/return_pointer_aliasing2.rs:LL:CC + | +LL | Call(_x = myfun(ptr), after_call) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `::core::intrinsics::mir::__internal_remove_let` which comes from the expansion of the macro `mir` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/function_calls/target_feature.rs b/src/tools/miri/tests/fail/function_calls/target_feature.rs new file mode 100644 index 0000000000000..84e01eb4803fe --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/target_feature.rs @@ -0,0 +1,12 @@ +//@only-target-x86_64: uses x86 target features +//@ignore-target-x86_64-apple-darwin: that target actually has ssse3 + +fn main() { + assert!(!is_x86_feature_detected!("ssse3")); + unsafe { + ssse3_fn(); //~ ERROR: calling a function that requires unavailable target features: ssse3 + } +} + +#[target_feature(enable = "ssse3")] +unsafe fn ssse3_fn() {} diff --git a/src/tools/miri/tests/fail/function_calls/target_feature.stderr b/src/tools/miri/tests/fail/function_calls/target_feature.stderr new file mode 100644 index 0000000000000..bddc6e7e89bdb --- /dev/null +++ b/src/tools/miri/tests/fail/function_calls/target_feature.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: calling a function that requires unavailable target features: ssse3 + --> $DIR/target_feature.rs:LL:CC + | +LL | ssse3_fn(); + | ^^^^^^^^^^ calling a function that requires unavailable target features: ssse3 + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/target_feature.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/generator-pinned-moved.rs b/src/tools/miri/tests/fail/generator-pinned-moved.rs index 240ae18cc45a6..33348ace9c41d 100644 --- a/src/tools/miri/tests/fail/generator-pinned-moved.rs +++ b/src/tools/miri/tests/fail/generator-pinned-moved.rs @@ -1,4 +1,4 @@ -//@compile-flags: -Zmiri-disable-validation +//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows #![feature(generators, generator_trait)] use std::{ @@ -10,9 +10,10 @@ fn firstn() -> impl Generator { static move || { let mut num = 0; let num = &mut num; + *num += 0; yield *num; - *num += 1; //~ ERROR: dereferenced after this allocation got freed + *num += 1; //~ERROR: has been freed } } diff --git a/src/tools/miri/tests/fail/generator-pinned-moved.stderr b/src/tools/miri/tests/fail/generator-pinned-moved.stderr index 80c5794736a9f..e29e352e64b0d 100644 --- a/src/tools/miri/tests/fail/generator-pinned-moved.stderr +++ b/src/tools/miri/tests/fail/generator-pinned-moved.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/generator-pinned-moved.rs:LL:CC | LL | *num += 1; - | ^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/generator-pinned-moved.rs:LL:CC + | +LL | let mut generator_iterator = Box::new(GeneratorIteratorAdapter(firstn())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/generator-pinned-moved.rs:LL:CC + | +LL | }; // *deallocate* generator_iterator + | ^ + = note: BACKTRACE (of the first span): = note: inside closure at $DIR/generator-pinned-moved.rs:LL:CC note: inside ` as std::iter::Iterator>::next` --> $DIR/generator-pinned-moved.rs:LL:CC diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs index 3df881bd43ca4..9c73bdc17bebc 100644 --- a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs +++ b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.rs @@ -10,6 +10,6 @@ fn main() { unsafe { let a = data.as_mut_ptr(); let b = a.wrapping_offset(1) as *mut _; - copy_nonoverlapping(a, b, 2); //~ ERROR: copy_nonoverlapping called on overlapping ranges + copy_nonoverlapping(a, b, 2); //~ ERROR: `copy_nonoverlapping` called on overlapping ranges } } diff --git a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr index cdb3da74ca954..13a76aae73045 100644 --- a/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr +++ b/src/tools/miri/tests/fail/intrinsics/copy_overlapping.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: copy_nonoverlapping called on overlapping ranges +error: Undefined Behavior: `copy_nonoverlapping` called on overlapping ranges --> $DIR/copy_overlapping.rs:LL:CC | LL | copy_nonoverlapping(a, b, 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ copy_nonoverlapping called on overlapping ranges + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `copy_nonoverlapping` called on overlapping ranges | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr index 7e24f45f653d1..ddf3249d059a1 100644 --- a/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr +++ b/src/tools/miri/tests/fail/intrinsics/float_to_int_64_neg.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128` +error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on -1.0000000000000999 which cannot be represented in target type `u128` --> $DIR/float_to_int_64_neg.rs:LL:CC | LL | float_to_int_unchecked::(-1.0000000000001f64); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1 which cannot be represented in target type `u128` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on -1.0000000000000999 which cannot be represented in target type `u128` | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr b/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr deleted file mode 100644 index 9c5d0d13108ce..0000000000000 --- a/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error: Undefined Behavior: overflowing shift by 64 in `unchecked_shr` - --> $DIR/overflowing-unchecked-rsh.rs:LL:CC - | -LL | let _n = 1i64.unchecked_shr(64); - | ^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 64 in `unchecked_shr` - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: - = note: inside `main` at $DIR/overflowing-unchecked-rsh.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr index a31b929d7a7ae..0b9cda62b33c2 100644 --- a/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_from_oob.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: out-of-bounds offset_from: ALLOC has size 4, so pointer at offset 10 is out-of-bounds +error: Undefined Behavior: out-of-bounds `offset_from`: ALLOC has size 4, so pointer at offset 10 is out-of-bounds --> $DIR/ptr_offset_from_oob.rs:LL:CC | LL | unsafe { end_ptr.offset_from(end_ptr) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds offset_from: ALLOC has size 4, so pointer at offset 10 is out-of-bounds + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds `offset_from`: ALLOC has size 4, so pointer at offset 10 is out-of-bounds | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr index b18147ce379d7..7cc4bc184a1b3 100644 --- a/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr +++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_ptr_plus_0.stderr @@ -6,7 +6,12 @@ LL | let _x = unsafe { x.offset(0) }; // UB despite offset 0, the pointer is | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/ptr_offset_ptr_plus_0.rs:LL:CC + | +LL | let x = Box::into_raw(Box::new(0u32)); + | ^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/ptr_offset_ptr_plus_0.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr index ea5ad62aea908..7b2387944af0d 100644 --- a/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr +++ b/src/tools/miri/tests/fail/intrinsics/simd-float-to-int.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32` +error: Undefined Behavior: `simd_cast` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32` --> $DIR/simd-float-to-int.rs:LL:CC | LL | let _x: i32x2 = f32x2::from_array([f32::MAX, f32::MIN]).to_int_unchecked(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `float_to_int_unchecked` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `simd_cast` intrinsic called on 3.40282347E+38 which cannot be represented in target type `i32` | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr b/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr index a745b61029d15..5beee034db296 100644 --- a/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr +++ b/src/tools/miri/tests/fail/intrinsics/simd-scatter.stderr @@ -11,8 +11,14 @@ LL | | ); | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/simd-scatter.rs:LL:CC + | +LL | let mut vec: Vec = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/simd-scatter.rs:LL:CC + = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs new file mode 100644 index 0000000000000..4554d0cb82ba0 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_shl.rs @@ -0,0 +1,8 @@ +#![feature(unchecked_math)] + +fn main() { + unsafe { + let _n = 1i8.unchecked_shl(8); + //~^ ERROR: overflowing shift by 8 in `unchecked_shl` + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_shl.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_shl.stderr new file mode 100644 index 0000000000000..264e9baf053d6 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_shl.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing shift by 8 in `unchecked_shl` + --> $DIR/unchecked_shl.rs:LL:CC + | +LL | let _n = 1i8.unchecked_shl(8); + | ^^^^^^^^^^^^^^^^^^^^ overflowing shift by 8 in `unchecked_shl` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_shl.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs b/src/tools/miri/tests/fail/intrinsics/unchecked_shr.rs similarity index 100% rename from src/tools/miri/tests/fail/intrinsics/overflowing-unchecked-rsh.rs rename to src/tools/miri/tests/fail/intrinsics/unchecked_shr.rs diff --git a/src/tools/miri/tests/fail/intrinsics/unchecked_shr.stderr b/src/tools/miri/tests/fail/intrinsics/unchecked_shr.stderr new file mode 100644 index 0000000000000..378ff9734c16e --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/unchecked_shr.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: overflowing shift by 64 in `unchecked_shr` + --> $DIR/unchecked_shr.rs:LL:CC + | +LL | let _n = 1i64.unchecked_shr(64); + | ^^^^^^^^^^^^^^^^^^^^^^ overflowing shift by 64 in `unchecked_shr` + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/unchecked_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs index e606d8b283c8a..59781f023661c 100644 --- a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs +++ b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.rs @@ -2,6 +2,6 @@ #[allow(deprecated, invalid_value)] fn main() { - unsafe { std::mem::uninitialized::() }; + let _ = unsafe { std::mem::uninitialized::() }; //~^ ERROR: attempted to instantiate uninhabited type `!` } diff --git a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr index 150128ba2a41c..f2cc343032627 100644 --- a/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr +++ b/src/tools/miri/tests/fail/intrinsics/uninit_uninhabited_type.stderr @@ -1,8 +1,8 @@ error: abnormal termination: aborted execution: attempted to instantiate uninhabited type `!` --> $DIR/uninit_uninhabited_type.rs:LL:CC | -LL | unsafe { std::mem::uninitialized::() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` +LL | let _ = unsafe { std::mem::uninitialized::() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!` | = note: inside `main` at $DIR/uninit_uninhabited_type.rs:LL:CC diff --git a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs index 6d9ae14c5c479..e9c6e464e88cf 100644 --- a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs +++ b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.rs @@ -1,5 +1,5 @@ #[allow(deprecated, invalid_value)] fn main() { - unsafe { std::mem::zeroed::() }; + let _ = unsafe { std::mem::zeroed::() }; //~^ ERROR: attempted to zero-initialize type `fn()`, which is invalid } diff --git a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr index 9d44ba9f746ad..77d5822804315 100644 --- a/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr +++ b/src/tools/miri/tests/fail/intrinsics/zero_fn_ptr.stderr @@ -1,8 +1,8 @@ error: abnormal termination: aborted execution: attempted to zero-initialize type `fn()`, which is invalid --> $DIR/zero_fn_ptr.rs:LL:CC | -LL | unsafe { std::mem::zeroed::() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to zero-initialize type `fn()`, which is invalid +LL | let _ = unsafe { std::mem::zeroed::() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to zero-initialize type `fn()`, which is invalid | = note: inside `main` at $DIR/zero_fn_ptr.rs:LL:CC diff --git a/src/tools/miri/tests/fail/layout_cycle.stderr b/src/tools/miri/tests/fail/layout_cycle.stderr index 62b7d5fb77d12..38907a1c50cc5 100644 --- a/src/tools/miri/tests/fail/layout_cycle.stderr +++ b/src/tools/miri/tests/fail/layout_cycle.stderr @@ -2,6 +2,7 @@ error[E0391]: cycle detected when computing layout of `S>` | = note: ...which requires computing layout of ` as Tr>::I`... = note: ...which again requires computing layout of `S>`, completing the cycle + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information error: post-monomorphization error: a cycle occurred during layout computation --> RUSTLIB/core/src/mem/mod.rs:LL:CC diff --git a/src/tools/miri/tests/fail/memleak.stderr b/src/tools/miri/tests/fail/memleak.stderr index 6d9b664c8f483..12bb944b07677 100644 --- a/src/tools/miri/tests/fail/memleak.stderr +++ b/src/tools/miri/tests/fail/memleak.stderr @@ -1,8 +1,8 @@ error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here: --> RUSTLIB/alloc/src/alloc.rs:LL:CC | -LL | unsafe { __rust_alloc(layout.size(), layout.align()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | __rust_alloc(layout.size(), layout.align()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr index 0e1146cf4ad93..87c5f466bc4be 100644 --- a/src/tools/miri/tests/fail/memleak_rc.32bit.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.32bit.stderr @@ -1,8 +1,8 @@ error: memory leaked: ALLOC (Rust heap, size: 16, align: 4), allocated here: --> RUSTLIB/alloc/src/alloc.rs:LL:CC | -LL | unsafe { __rust_alloc(layout.size(), layout.align()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | __rust_alloc(layout.size(), layout.align()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr index 4979588f370ff..ec5f5f5bed368 100644 --- a/src/tools/miri/tests/fail/memleak_rc.64bit.stderr +++ b/src/tools/miri/tests/fail/memleak_rc.64bit.stderr @@ -1,8 +1,8 @@ error: memory leaked: ALLOC (Rust heap, size: 32, align: 8), allocated here: --> RUSTLIB/alloc/src/alloc.rs:LL:CC | -LL | unsafe { __rust_alloc(layout.size(), layout.align()) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | __rust_alloc(layout.size(), layout.align()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC diff --git a/src/tools/miri/tests/fail/modifying_constants.rs b/src/tools/miri/tests/fail/modifying_constants.rs index 2783ebd155ff5..0d1bd7929b5b8 100644 --- a/src/tools/miri/tests/fail/modifying_constants.rs +++ b/src/tools/miri/tests/fail/modifying_constants.rs @@ -1,6 +1,8 @@ // This should fail even without validation/SB //@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows +#![allow(invalid_reference_casting)] + fn main() { let x = &1; // the `&1` is promoted to a constant, but it used to be that only the pointer is marked static, not the pointee let y = unsafe { &mut *(x as *const i32 as *mut i32) }; diff --git a/src/tools/miri/tests/fail/overlapping_assignment.rs b/src/tools/miri/tests/fail/overlapping_assignment.rs new file mode 100644 index 0000000000000..84994c179f9ea --- /dev/null +++ b/src/tools/miri/tests/fail/overlapping_assignment.rs @@ -0,0 +1,23 @@ +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; + +// It's not that easy to fool the MIR validity check +// which wants to prevent overlapping assignments... +// So we use two separate pointer arguments, and then arrange for them to alias. +#[custom_mir(dialect = "runtime", phase = "optimized")] +pub fn self_copy(ptr1: *mut [i32; 4], ptr2: *mut [i32; 4]) { + mir! { + { + *ptr1 = *ptr2; //~ERROR: overlapping ranges + Return() + } + } +} + +pub fn main() { + let mut x = [0; 4]; + let ptr = std::ptr::addr_of_mut!(x); + self_copy(ptr, ptr); +} diff --git a/src/tools/miri/tests/fail/overlapping_assignment.stderr b/src/tools/miri/tests/fail/overlapping_assignment.stderr new file mode 100644 index 0000000000000..42a000dfcc6c7 --- /dev/null +++ b/src/tools/miri/tests/fail/overlapping_assignment.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: `copy_nonoverlapping` called on overlapping ranges + --> $DIR/overlapping_assignment.rs:LL:CC + | +LL | *ptr1 = *ptr2; + | ^^^^^^^^^^^^^ `copy_nonoverlapping` called on overlapping ranges + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `self_copy` at $DIR/overlapping_assignment.rs:LL:CC +note: inside `main` + --> $DIR/overlapping_assignment.rs:LL:CC + | +LL | self_copy(ptr, ptr); + | ^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/bad_unwind.stderr b/src/tools/miri/tests/fail/panic/bad_unwind.stderr index 5d7f01f478656..18438c13b2118 100644 --- a/src/tools/miri/tests/fail/panic/bad_unwind.stderr +++ b/src/tools/miri/tests/fail/panic/bad_unwind.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'explicit panic', $DIR/bad_unwind.rs:LL:CC +thread 'main' panicked at $DIR/bad_unwind.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: Undefined Behavior: unwinding past a stack frame that does not allow unwinding --> $DIR/bad_unwind.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/double_panic.rs b/src/tools/miri/tests/fail/panic/double_panic.rs index 9378adb8609df..adb30714269e8 100644 --- a/src/tools/miri/tests/fail/panic/double_panic.rs +++ b/src/tools/miri/tests/fail/panic/double_panic.rs @@ -1,6 +1,4 @@ -//@error-in-other-file: the program aborted //@normalize-stderr-test: "\| +\^+" -> "| ^" -//@normalize-stderr-test: "unsafe \{ libc::abort\(\) \}|crate::intrinsics::abort\(\);" -> "ABORT();" //@normalize-stderr-test: "\n +[0-9]+:[^\n]+" -> "$1" //@normalize-stderr-test: "\n at [^\n]+" -> "$1" @@ -11,6 +9,7 @@ impl Drop for Foo { } } fn main() { + //~^ERROR: panic in a function that cannot unwind let _foo = Foo; panic!("first"); } diff --git a/src/tools/miri/tests/fail/panic/double_panic.stderr b/src/tools/miri/tests/fail/panic/double_panic.stderr index 77d5fc5d7ceb9..8e008da75ee87 100644 --- a/src/tools/miri/tests/fail/panic/double_panic.stderr +++ b/src/tools/miri/tests/fail/panic/double_panic.stderr @@ -1,31 +1,20 @@ -thread 'main' panicked at 'first', $DIR/double_panic.rs:LL:CC +thread 'main' panicked at $DIR/double_panic.rs:LL:CC: +first note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -thread 'main' panicked at 'second', $DIR/double_panic.rs:LL:CC +thread 'main' panicked at $DIR/double_panic.rs:LL:CC: +second stack backtrace: -thread panicked while panicking. aborting. -error: abnormal termination: the program aborted execution - --> RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC - | -LL | ABORT(); - | ^ the program aborted execution - | - = note: inside `std::sys::PLATFORM::abort_internal` at RUSTLIB/std/src/sys/PLATFORM/mod.rs:LL:CC - = note: inside `std::panicking::rust_panic_with_hook` at RUSTLIB/std/src/panicking.rs:LL:CC - = note: inside closure at RUSTLIB/std/src/panicking.rs:LL:CC - = note: inside `std::sys_common::backtrace::__rust_end_short_backtrace::<[closure@std::panicking::begin_panic_handler::{closure#0}], !>` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC - = note: inside `std::panicking::begin_panic_handler` at RUSTLIB/std/src/panicking.rs:LL:CC -note: inside `::drop` +error: abnormal termination: panic in a function that cannot unwind --> $DIR/double_panic.rs:LL:CC | -LL | panic!("second"); - | ^ - = note: inside `std::ptr::drop_in_place:: - shim(Some(Foo))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC -note: inside `main` - --> $DIR/double_panic.rs:LL:CC +LL | / fn main() { +LL | | +LL | | let _foo = Foo; +LL | | panic!("first"); +LL | | } + | |_^ panic in a function that cannot unwind | -LL | } - | ^ - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: inside `main` at $DIR/double_panic.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/panic/no_std.stderr b/src/tools/miri/tests/fail/panic/no_std.stderr index f8307c0c23bf8..47ec710052a25 100644 --- a/src/tools/miri/tests/fail/panic/no_std.stderr +++ b/src/tools/miri/tests/fail/panic/no_std.stderr @@ -1,4 +1,5 @@ -panicked at 'blarg I am dead', $DIR/no_std.rs:LL:CC +panicked at $DIR/no_std.rs:LL:CC: +blarg I am dead error: abnormal termination: the program aborted execution --> $DIR/no_std.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/panic/panic_abort1.stderr b/src/tools/miri/tests/fail/panic/panic_abort1.stderr index d9303fd0d0684..404777344d7a8 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort1.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort1.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'panicking from libstd', $DIR/panic_abort1.rs:LL:CC +thread 'main' panicked at $DIR/panic_abort1.rs:LL:CC: +panicking from libstd note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort2.stderr b/src/tools/miri/tests/fail/panic/panic_abort2.stderr index 54cbc9b5f6d96..5512d145c6b7d 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort2.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort2.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at '42-panicking from libstd', $DIR/panic_abort2.rs:LL:CC +thread 'main' panicked at $DIR/panic_abort2.rs:LL:CC: +42-panicking from libstd note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort3.stderr b/src/tools/miri/tests/fail/panic/panic_abort3.stderr index 64eea47b14b4b..9fdccd4e59e4b 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort3.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort3.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'panicking from libcore', $DIR/panic_abort3.rs:LL:CC +thread 'main' panicked at $DIR/panic_abort3.rs:LL:CC: +panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/panic_abort4.stderr b/src/tools/miri/tests/fail/panic/panic_abort4.stderr index 21beb1006459b..3c9557ca4ab61 100644 --- a/src/tools/miri/tests/fail/panic/panic_abort4.stderr +++ b/src/tools/miri/tests/fail/panic/panic_abort4.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at '42-panicking from libcore', $DIR/panic_abort4.rs:LL:CC +thread 'main' panicked at $DIR/panic_abort4.rs:LL:CC: +42-panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: the program aborted execution --> RUSTLIB/panic_abort/src/lib.rs:LL:CC diff --git a/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.rs b/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.rs new file mode 100644 index 0000000000000..93ad42ea1cced --- /dev/null +++ b/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.rs @@ -0,0 +1,22 @@ +//@error-in-other-file: aborted execution +// Backtraces vary wildly between platforms, we have to normalize away almost the entire thing. +// Full backtraces avoid annoying empty line differences. +//@compile-flags: -Zmiri-backtrace=full +//@normalize-stderr-test: "'main'|''" -> "$$NAME" +//@normalize-stderr-test: ".*(note|-->|\|).*\n" -> "" + +pub struct NoisyDrop {} + +impl Drop for NoisyDrop { + fn drop(&mut self) { + panic!("ow"); + } +} + +thread_local! { + pub static NOISY: NoisyDrop = const { NoisyDrop {} }; +} + +fn main() { + NOISY.with(|_| ()); +} diff --git a/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.stderr b/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.stderr new file mode 100644 index 0000000000000..550d009607d13 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.stderr @@ -0,0 +1,7 @@ +thread $NAME panicked at $DIR/thread_local_const_drop_panic.rs:LL:CC: +ow +fatal runtime error: thread local panicked on drop +error: abnormal termination: the program aborted execution + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/panic/thread_local_drop_panic.rs b/src/tools/miri/tests/fail/panic/thread_local_drop_panic.rs new file mode 100644 index 0000000000000..107d70a3b3c7f --- /dev/null +++ b/src/tools/miri/tests/fail/panic/thread_local_drop_panic.rs @@ -0,0 +1,20 @@ +//@error-in-other-file: aborted execution +// Backtraces vary wildly between platforms, we have to normalize away almost the entire thing +//@normalize-stderr-test: "'main'|''" -> "$$NAME" +//@normalize-stderr-test: ".*(note|-->|\|).*\n" -> "" + +pub struct NoisyDrop {} + +impl Drop for NoisyDrop { + fn drop(&mut self) { + panic!("ow"); + } +} + +thread_local! { + pub static NOISY: NoisyDrop = NoisyDrop {}; +} + +fn main() { + NOISY.with(|_| ()); +} diff --git a/src/tools/miri/tests/fail/panic/thread_local_drop_panic.stderr b/src/tools/miri/tests/fail/panic/thread_local_drop_panic.stderr new file mode 100644 index 0000000000000..3d6c41faabc40 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/thread_local_drop_panic.stderr @@ -0,0 +1,7 @@ +thread $NAME panicked at $DIR/thread_local_drop_panic.rs:LL:CC: +ow +fatal runtime error: thread local panicked on drop +error: abnormal termination: the program aborted execution + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/rc_as_ptr.rs b/src/tools/miri/tests/fail/rc_as_ptr.rs index 6aea1870748cc..ebcf49b8f99e7 100644 --- a/src/tools/miri/tests/fail/rc_as_ptr.rs +++ b/src/tools/miri/tests/fail/rc_as_ptr.rs @@ -16,5 +16,5 @@ fn main() { drop(strong); // But not any more. We can do Weak::as_raw(&weak), but accessing the pointer would lead to // undefined behaviour. - assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); //~ ERROR: dereferenced after this allocation got freed + assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); //~ ERROR: has been freed } diff --git a/src/tools/miri/tests/fail/rc_as_ptr.stderr b/src/tools/miri/tests/fail/rc_as_ptr.stderr index 70bdd157bdc34..460ed97713794 100644 --- a/src/tools/miri/tests/fail/rc_as_ptr.stderr +++ b/src/tools/miri/tests/fail/rc_as_ptr.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/rc_as_ptr.rs:LL:CC | LL | assert_eq!(42, **unsafe { &*Weak::as_ptr(&weak) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/rc_as_ptr.rs:LL:CC + | +LL | let strong = Rc::new(Box::new(42)); + | ^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/rc_as_ptr.rs:LL:CC + | +LL | drop(strong); + | ^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at RUSTLIB/core/src/macros/mod.rs:LL:CC = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/src/tools/miri/tests/fail/shims/memchr_null.rs b/src/tools/miri/tests/fail/shims/memchr_null.rs new file mode 100644 index 0000000000000..6bc7af7e6bf77 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memchr_null.rs @@ -0,0 +1,10 @@ +//@ignore-target-windows: No libc on Windows + +use std::ptr; + +// null is explicitly called out as UB in the C docs. +fn main() { + unsafe { + libc::memchr(ptr::null(), 0, 0); //~ERROR: dangling + } +} diff --git a/src/tools/miri/tests/fail/shims/memchr_null.stderr b/src/tools/miri/tests/fail/shims/memchr_null.stderr new file mode 100644 index 0000000000000..54b58f22c6c74 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memchr_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + --> $DIR/memchr_null.rs:LL:CC + | +LL | libc::memchr(ptr::null(), 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/memchr_null.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/memcmp_null.rs b/src/tools/miri/tests/fail/shims/memcmp_null.rs new file mode 100644 index 0000000000000..a4e0034c40bdc --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memcmp_null.rs @@ -0,0 +1,10 @@ +//@ignore-target-windows: No libc on Windows + +use std::ptr; + +// null is explicitly called out as UB in the C docs. +fn main() { + unsafe { + libc::memcmp(ptr::null(), ptr::null(), 0); //~ERROR: dangling + } +} diff --git a/src/tools/miri/tests/fail/shims/memcmp_null.stderr b/src/tools/miri/tests/fail/shims/memcmp_null.stderr new file mode 100644 index 0000000000000..8b2882fc243a5 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memcmp_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + --> $DIR/memcmp_null.rs:LL:CC + | +LL | libc::memcmp(ptr::null(), ptr::null(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/memcmp_null.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/memcmp_zero.rs b/src/tools/miri/tests/fail/shims/memcmp_zero.rs new file mode 100644 index 0000000000000..f2ddc20056327 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memcmp_zero.rs @@ -0,0 +1,13 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-permissive-provenance + +// C says that passing "invalid" pointers is UB for all string functions. +// It is unclear whether `(int*)42` is "invalid", but there is no actually +// a `char` living at that address, so arguably it cannot be a valid pointer. +// Hence this is UB. +fn main() { + let ptr = 42 as *const u8; + unsafe { + libc::memcmp(ptr.cast(), ptr.cast(), 0); //~ERROR: dangling + } +} diff --git a/src/tools/miri/tests/fail/shims/memcmp_zero.stderr b/src/tools/miri/tests/fail/shims/memcmp_zero.stderr new file mode 100644 index 0000000000000..e21b9b0600830 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memcmp_zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: 0x2a[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/memcmp_zero.rs:LL:CC + | +LL | libc::memcmp(ptr.cast(), ptr.cast(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: 0x2a[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/memcmp_zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/memcpy_zero.rs b/src/tools/miri/tests/fail/shims/memcpy_zero.rs new file mode 100644 index 0000000000000..5283fea4cb989 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memcpy_zero.rs @@ -0,0 +1,12 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-permissive-provenance +// C's memcpy is 0 bytes is UB for some pointers that are allowed in Rust's `copy_nonoverlapping`. + +fn main() { + let from = 42 as *const u8; + let to = 23 as *mut u8; + unsafe { + to.copy_from(from, 0); // this is fine + libc::memcpy(to.cast(), from.cast(), 0); //~ERROR: dangling + } +} diff --git a/src/tools/miri/tests/fail/shims/memcpy_zero.stderr b/src/tools/miri/tests/fail/shims/memcpy_zero.stderr new file mode 100644 index 0000000000000..7c1c3fe20c4e5 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memcpy_zero.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: 0x17[noalloc] is a dangling pointer (it has no provenance) + --> $DIR/memcpy_zero.rs:LL:CC + | +LL | libc::memcpy(to.cast(), from.cast(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: 0x17[noalloc] is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/memcpy_zero.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/memrchr_null.rs b/src/tools/miri/tests/fail/shims/memrchr_null.rs new file mode 100644 index 0000000000000..b6707d558d8f6 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memrchr_null.rs @@ -0,0 +1,11 @@ +//@ignore-target-windows: No libc on Windows +//@ignore-target-apple: No `memrchr` on some apple targets + +use std::ptr; + +// null is explicitly called out as UB in the C docs. +fn main() { + unsafe { + libc::memrchr(ptr::null(), 0, 0); //~ERROR: dangling + } +} diff --git a/src/tools/miri/tests/fail/shims/memrchr_null.stderr b/src/tools/miri/tests/fail/shims/memrchr_null.stderr new file mode 100644 index 0000000000000..cc11ba89f8fef --- /dev/null +++ b/src/tools/miri/tests/fail/shims/memrchr_null.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + --> $DIR/memrchr_null.rs:LL:CC + | +LL | libc::memrchr(ptr::null(), 0, 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: null pointer is a dangling pointer (it has no provenance) + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/memrchr_null.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.rs b/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.rs new file mode 100644 index 0000000000000..70f7a6a7cef12 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.rs @@ -0,0 +1,18 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + libc::free(ptr); //~ ERROR: which is mmap memory, using C heap deallocation operation + } +} diff --git a/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.stderr b/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.stderr new file mode 100644 index 0000000000000..54e0cd5275d58 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/mmap_invalid_dealloc.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: deallocating ALLOC, which is mmap memory, using C heap deallocation operation + --> $DIR/mmap_invalid_dealloc.rs:LL:CC + | +LL | libc::free(ptr); + | ^^^^^^^^^^^^^^^ deallocating ALLOC, which is mmap memory, using C heap deallocation operation + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/mmap_invalid_dealloc.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs new file mode 100644 index 0000000000000..c97b013ba5a34 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.rs @@ -0,0 +1,19 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + 4096, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + libc::munmap(ptr, 4096); + let _x = *(ptr as *mut u8); //~ ERROR: has been freed + } +} diff --git a/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr new file mode 100644 index 0000000000000..44e122330bcee --- /dev/null +++ b/src/tools/miri/tests/fail/shims/mmap_use_after_munmap.stderr @@ -0,0 +1,47 @@ +warning: integer-to-pointer cast + --> $DIR/mmap_use_after_munmap.rs:LL:CC + | +LL | libc::munmap(ptr, 4096); + | ^^^^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast + | + = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`, + = help: which means that Miri might miss pointer bugs in this program. + = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation. + = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead. + = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics. + = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning. + = note: BACKTRACE: + = note: inside `main` at $DIR/mmap_use_after_munmap.rs:LL:CC + +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling + --> $DIR/mmap_use_after_munmap.rs:LL:CC + | +LL | let _x = *(ptr as *mut u8); + | ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information +help: ALLOC was allocated here: + --> $DIR/mmap_use_after_munmap.rs:LL:CC + | +LL | let ptr = libc::mmap( + | ___________________^ +LL | | std::ptr::null_mut(), +LL | | 4096, +LL | | libc::PROT_READ | libc::PROT_WRITE, +... | +LL | | 0, +LL | | ); + | |_________^ +help: ALLOC was deallocated here: + --> $DIR/mmap_use_after_munmap.rs:LL:CC + | +LL | libc::munmap(ptr, 4096); + | ^^^^^^^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/mmap_use_after_munmap.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/tools/miri/tests/fail/shims/munmap.rs b/src/tools/miri/tests/fail/shims/munmap.rs new file mode 100644 index 0000000000000..453437a06cfcc --- /dev/null +++ b/src/tools/miri/tests/fail/shims/munmap.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-disable-isolation +//@ignore-target-windows: No libc on Windows + +#![feature(rustc_private)] +#![feature(strict_provenance)] + +use std::ptr; + +fn main() { + // Linux specifies that it is not an error if the specified range does not contain any pages. + // But we simply do not support such calls. This test checks that we report this as + // unsupported, not Undefined Behavior. + let res = unsafe { + libc::munmap( + //~^ ERROR: unsupported operation + // Some high address we surely have not allocated anything at + ptr::invalid_mut(1 << 30), + 4096, + ) + }; + assert_eq!(res, 0); +} diff --git a/src/tools/miri/tests/fail/shims/munmap.stderr b/src/tools/miri/tests/fail/shims/munmap.stderr new file mode 100644 index 0000000000000..cb47769c063f6 --- /dev/null +++ b/src/tools/miri/tests/fail/shims/munmap.stderr @@ -0,0 +1,39 @@ +warning: integer-to-pointer cast + --> $DIR/munmap.rs:LL:CC + | +LL | / libc::munmap( +LL | | +LL | | // Some high address we surely have not allocated anything at +LL | | ptr::invalid_mut(1 << 30), +LL | | 4096, +LL | | ) + | |_________^ integer-to-pointer cast + | + = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`, + = help: which means that Miri might miss pointer bugs in this program. + = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation. + = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead. + = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics. + = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning. + = note: BACKTRACE: + = note: inside `main` at $DIR/munmap.rs:LL:CC + +error: unsupported operation: Miri only supports munmap on memory allocated directly by mmap + --> $DIR/munmap.rs:LL:CC + | +LL | / libc::munmap( +LL | | +LL | | // Some high address we surely have not allocated anything at +LL | | ptr::invalid_mut(1 << 30), +LL | | 4096, +LL | | ) + | |_________^ Miri only supports munmap on memory allocated directly by mmap + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/munmap.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/tools/miri/tests/fail/shims/munmap_partial.rs b/src/tools/miri/tests/fail/shims/munmap_partial.rs new file mode 100644 index 0000000000000..938850ee2862b --- /dev/null +++ b/src/tools/miri/tests/fail/shims/munmap_partial.rs @@ -0,0 +1,18 @@ +//! Our mmap/munmap support is a thin wrapper over Interpcx::allocate_ptr. Since the underlying +//! layer has much more UB than munmap does, we need to be sure we throw an unsupported error here. +//@ignore-target-windows: No libc on Windows + +fn main() { + unsafe { + let ptr = libc::mmap( + std::ptr::null_mut(), + page_size::get() * 2, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + libc::munmap(ptr, 1); + //~^ ERROR: unsupported operation + } +} diff --git a/src/tools/miri/tests/fail/shims/munmap_partial.stderr b/src/tools/miri/tests/fail/shims/munmap_partial.stderr new file mode 100644 index 0000000000000..9a084c504374b --- /dev/null +++ b/src/tools/miri/tests/fail/shims/munmap_partial.stderr @@ -0,0 +1,29 @@ +warning: integer-to-pointer cast + --> $DIR/munmap_partial.rs:LL:CC + | +LL | libc::munmap(ptr, 1); + | ^^^^^^^^^^^^^^^^^^^^ integer-to-pointer cast + | + = help: This program is using integer-to-pointer casts or (equivalently) `ptr::from_exposed_addr`, + = help: which means that Miri might miss pointer bugs in this program. + = help: See https://doc.rust-lang.org/nightly/std/ptr/fn.from_exposed_addr.html for more details on that operation. + = help: To ensure that Miri does not miss bugs in your program, use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead. + = help: You can then pass the `-Zmiri-strict-provenance` flag to Miri, to ensure you are not relying on `from_exposed_addr` semantics. + = help: Alternatively, the `-Zmiri-permissive-provenance` flag disables this warning. + = note: BACKTRACE: + = note: inside `main` at $DIR/munmap_partial.rs:LL:CC + +error: unsupported operation: Miri only supports munmap calls that exactly unmap a region previously returned by mmap + --> $DIR/munmap_partial.rs:LL:CC + | +LL | libc::munmap(ptr, 1); + | ^^^^^^^^^^^^^^^^^^^^ Miri only supports munmap calls that exactly unmap a region previously returned by mmap + | + = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support + = note: BACKTRACE: + = note: inside `main` at $DIR/munmap_partial.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error; 1 warning emitted + diff --git a/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs b/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs deleted file mode 100644 index 73095bb2fc94b..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/alias_through_mutation.rs +++ /dev/null @@ -1,15 +0,0 @@ -// This makes a ref that was passed to us via &mut alias with things it should not alias with -fn retarget(x: &mut &u32, target: &mut u32) { - unsafe { - *x = &mut *(target as *mut _); - } -} - -fn main() { - let target = &mut 42; - let mut target_alias = &42; // initial dummy value - retarget(&mut target_alias, target); - // now `target_alias` points to the same thing as `target` - *target = 13; - let _val = *target_alias; //~ ERROR: /read access .* tag does not exist in the borrow stack/ -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs deleted file mode 100644 index 14a27d8e9dd65..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::mem; - -pub fn safe(_x: &mut i32, _y: &mut i32) {} //~ ERROR: protect - -fn main() { - let mut x = 0; - let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; - // We need to apply some tricky to be able to call `safe` with two mutable references - // with the same tag: We transmute both the fn ptr (to take raw ptrs) and the argument - // (to be raw, but still have the unique tag). - let safe_raw: fn(x: *mut i32, y: *mut i32) = - unsafe { mem::transmute::(safe) }; - safe_raw(xraw, xraw); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr deleted file mode 100644 index 3ce39968cbb13..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut1.stderr +++ /dev/null @@ -1,30 +0,0 @@ -error: Undefined Behavior: not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID - --> $DIR/aliasing_mut1.rs:LL:CC - | -LL | pub fn safe(_x: &mut i32, _y: &mut i32) {} - | ^^ not granting access to tag because that would remove [Unique for ] which is strongly protected because it is an argument of call ID - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x4] - --> $DIR/aliasing_mut1.rs:LL:CC - | -LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; - | ^^^^^^ -help: is this argument - --> $DIR/aliasing_mut1.rs:LL:CC - | -LL | pub fn safe(_x: &mut i32, _y: &mut i32) {} - | ^^ - = note: BACKTRACE (of the first span): - = note: inside `safe` at $DIR/aliasing_mut1.rs:LL:CC -note: inside `main` - --> $DIR/aliasing_mut1.rs:LL:CC - | -LL | safe_raw(xraw, xraw); - | ^^^^^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs deleted file mode 100644 index 84d901f83bbcc..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::mem; - -pub fn safe(_x: &i32, _y: &mut i32) {} //~ ERROR: protect - -fn main() { - let mut x = 0; - let xref = &mut x; - let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; - let xshr = &*xref; - // transmute fn ptr around so that we can avoid retagging - let safe_raw: fn(x: *const i32, y: *mut i32) = - unsafe { mem::transmute::(safe) }; - safe_raw(xshr, xraw); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr deleted file mode 100644 index df4b6cf02561c..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut2.stderr +++ /dev/null @@ -1,30 +0,0 @@ -error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID - --> $DIR/aliasing_mut2.rs:LL:CC - | -LL | pub fn safe(_x: &i32, _y: &mut i32) {} - | ^^ not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x4] - --> $DIR/aliasing_mut2.rs:LL:CC - | -LL | let xref = &mut x; - | ^^^^^^ -help: is this argument - --> $DIR/aliasing_mut2.rs:LL:CC - | -LL | pub fn safe(_x: &i32, _y: &mut i32) {} - | ^^ - = note: BACKTRACE (of the first span): - = note: inside `safe` at $DIR/aliasing_mut2.rs:LL:CC -note: inside `main` - --> $DIR/aliasing_mut2.rs:LL:CC - | -LL | safe_raw(xshr, xraw); - | ^^^^^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs deleted file mode 100644 index f1ba06b6e4f7a..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::mem; - -pub fn safe(_x: &mut i32, _y: &i32) {} //~ ERROR: borrow stack - -fn main() { - let mut x = 0; - let xref = &mut x; - let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; - let xshr = &*xref; - // transmute fn ptr around so that we can avoid retagging - let safe_raw: fn(x: *mut i32, y: *const i32) = - unsafe { mem::transmute::(safe) }; - safe_raw(xraw, xshr); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr deleted file mode 100644 index ae54d0248dc3d..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut3.stderr +++ /dev/null @@ -1,33 +0,0 @@ -error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> $DIR/aliasing_mut3.rs:LL:CC - | -LL | pub fn safe(_x: &mut i32, _y: &i32) {} - | ^^ - | | - | trying to retag from for SharedReadOnly permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - | this error occurs as part of function-entry retag at ALLOC[0x0..0x4] - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a SharedReadOnly retag at offsets [0x0..0x4] - --> $DIR/aliasing_mut3.rs:LL:CC - | -LL | safe_raw(xraw, xshr); - | ^^^^ -help: was later invalidated at offsets [0x0..0x4] by a Unique function-entry retag inside this call - --> $DIR/aliasing_mut3.rs:LL:CC - | -LL | safe_raw(xraw, xshr); - | ^^^^^^^^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `safe` at $DIR/aliasing_mut3.rs:LL:CC -note: inside `main` - --> $DIR/aliasing_mut3.rs:LL:CC - | -LL | safe_raw(xraw, xshr); - | ^^^^^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs deleted file mode 100644 index 52081b56223da..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::cell::Cell; -use std::mem; - -// Make sure &mut UnsafeCell also is exclusive -pub fn safe(_x: &i32, _y: &mut Cell) {} //~ ERROR: protect - -fn main() { - let mut x = 0; - let xref = &mut x; - let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; - let xshr = &*xref; - // transmute fn ptr around so that we can avoid retagging - let safe_raw: fn(x: *const i32, y: *mut Cell) = - unsafe { mem::transmute::), _>(safe) }; - safe_raw(xshr, xraw as *mut _); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr b/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr deleted file mode 100644 index ddf197bc63955..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/aliasing_mut4.stderr +++ /dev/null @@ -1,30 +0,0 @@ -error: Undefined Behavior: not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID - --> $DIR/aliasing_mut4.rs:LL:CC - | -LL | pub fn safe(_x: &i32, _y: &mut Cell) {} - | ^^ not granting access to tag because that would remove [SharedReadOnly for ] which is strongly protected because it is an argument of call ID - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x4] - --> $DIR/aliasing_mut4.rs:LL:CC - | -LL | let xref = &mut x; - | ^^^^^^ -help: is this argument - --> $DIR/aliasing_mut4.rs:LL:CC - | -LL | pub fn safe(_x: &i32, _y: &mut Cell) {} - | ^^ - = note: BACKTRACE (of the first span): - = note: inside `safe` at $DIR/aliasing_mut4.rs:LL:CC -note: inside `main` - --> $DIR/aliasing_mut4.rs:LL:CC - | -LL | safe_raw(xshr, xraw as *mut _); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs deleted file mode 100644 index 87a6b7bbd67ec..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/box_exclusive_violation1.rs +++ /dev/null @@ -1,33 +0,0 @@ -fn demo_box_advanced_unique(mut our: Box) -> i32 { - unknown_code_1(&*our); - - // This "re-asserts" uniqueness of the reference: After writing, we know - // our tag is at the top of the stack. - *our = 5; - - unknown_code_2(); - - // We know this will return 5 - *our -} - -// Now comes the evil context -use std::ptr; - -static mut LEAK: *mut i32 = ptr::null_mut(); - -fn unknown_code_1(x: &i32) { - unsafe { - LEAK = x as *const _ as *mut _; - } -} - -fn unknown_code_2() { - unsafe { - *LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/ - } -} - -fn main() { - demo_box_advanced_unique(Box::new(0)); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.rs b/src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.rs deleted file mode 100644 index 2067149b6c87c..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/box_noalias_violation.rs +++ /dev/null @@ -1,14 +0,0 @@ -unsafe fn test(mut x: Box, y: *const i32) -> i32 { - // We will call this in a way that x and y alias. - *x = 5; - std::mem::forget(x); - *y //~ERROR: weakly protected -} - -fn main() { - unsafe { - let mut v = 42; - let ptr = &mut v as *mut i32; - test(Box::from_raw(ptr), ptr); - } -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs b/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs deleted file mode 100644 index 8615a9a58ad96..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/buggy_as_mut_slice.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod safe { - use std::slice::from_raw_parts_mut; - - pub fn as_mut_slice(self_: &Vec) -> &mut [T] { - unsafe { from_raw_parts_mut(self_.as_ptr() as *mut T, self_.len()) } - } -} - -fn main() { - let v = vec![0, 1, 2]; - let v1 = safe::as_mut_slice(&v); - let _v2 = safe::as_mut_slice(&v); - v1[1] = 5; - //~^ ERROR: /write access .* tag does not exist in the borrow stack/ -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs deleted file mode 100644 index 8a1ea86d63385..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod safe { - use std::slice::from_raw_parts_mut; - - pub fn split_at_mut(self_: &mut [T], mid: usize) -> (&mut [T], &mut [T]) { - let len = self_.len(); - let ptr = self_.as_mut_ptr(); - - unsafe { - assert!(mid <= len); - - ( - from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" - from_raw_parts_mut(ptr.offset(mid as isize), len - mid), - ) - } - } -} - -fn main() { - let mut array = [1, 2, 3, 4]; - let (a, b) = safe::split_at_mut(&mut array, 0); - //~^ ERROR: /retag .* tag does not exist in the borrow stack/ - a[1] = 5; - b[1] = 6; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr b/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr deleted file mode 100644 index c75d8cab3fc85..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/buggy_split_at_mut.stderr +++ /dev/null @@ -1,28 +0,0 @@ -error: Undefined Behavior: trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> $DIR/buggy_split_at_mut.rs:LL:CC - | -LL | let (a, b) = safe::split_at_mut(&mut array, 0); - | ^ - | | - | trying to retag from for Unique permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - | this error occurs as part of retag at ALLOC[0x0..0x10] - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x10] - --> $DIR/buggy_split_at_mut.rs:LL:CC - | -LL | from_raw_parts_mut(ptr, len - mid), // BUG: should be "mid" instead of "len - mid" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: was later invalidated at offsets [0x0..0x10] by a Unique retag - --> $DIR/buggy_split_at_mut.rs:LL:CC - | -LL | from_raw_parts_mut(ptr.offset(mid as isize), len - mid), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/buggy_split_at_mut.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr index 516964b9a4e6f..8ebb35450e5fa 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/deallocate_against_protector1.stderr @@ -9,7 +9,7 @@ LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) } = note: BACKTRACE: = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC - = note: inside `alloc::alloc::box_free::` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC note: inside closure diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr index b76446d60f6d2..1a9d551431caa 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_read7.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: trying to retag from for SharedReadWrite permis --> $DIR/illegal_read7.rs:LL:CC | LL | let _val = *x.get_mut(); - | ^^^^^^^^^^^ + | ^ | | | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location | this error occurs as part of two-phase retag at ALLOC[0x0..0x4] diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs deleted file mode 100644 index f28401938a925..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write1.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - let target = Box::new(42); // has an implicit raw - let xref = &*target; - { - let x: *mut u32 = xref as *const _ as *mut _; - unsafe { *x = 42 }; //~ ERROR: /write access .* tag only grants SharedReadOnly permission/ - } - let _x = *xref; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs index 6f55b63cb5c64..f79f7b561b834 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs +++ b/src/tools/miri/tests/fail/stacked_borrows/illegal_write3.rs @@ -1,3 +1,5 @@ +#![allow(invalid_reference_casting)] + fn main() { let target = 42; // Make sure raw ptr with raw tag cannot mutate frozen location without breaking the shared ref. diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs deleted file mode 100644 index 1c655dc0a0fb4..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write5.rs +++ /dev/null @@ -1,16 +0,0 @@ -// A callee may not write to the destination of our `&mut` without us noticing. - -fn main() { - let mut x = 15; - let xraw = &mut x as *mut _; - // Derived from raw value, so using raw value is still ok ... - let xref = unsafe { &mut *xraw }; - callee(xraw); - // ... though any use of raw value will invalidate our ref. - let _val = *xref; - //~^ ERROR: /read access .* tag does not exist in the borrow stack/ -} - -fn callee(xraw: *mut i32) { - unsafe { *xraw = 15 }; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs b/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs deleted file mode 100644 index 7983b30ea1822..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/illegal_write6.rs +++ /dev/null @@ -1,12 +0,0 @@ -fn main() { - let x = &mut 0u32; - let p = x as *mut u32; - foo(x, p); -} - -fn foo(a: &mut u32, y: *mut u32) -> u32 { - *a = 1; - let _b = &*a; - unsafe { *y = 2 }; //~ ERROR: /not granting access .* because that would remove .* which is strongly protected/ - return *a; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr index da55e724fd81e..ba8977c567456 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut1.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: trying to retag from for SharedReadWrite permis --> $DIR/interior_mut1.rs:LL:CC | LL | let _val = *inner_shr.get(); - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^ | | | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location | this error occurs as part of retag at ALLOC[0x0..0x4] diff --git a/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr index 8c8a96cbbbd63..97ebe72bf29de 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/interior_mut2.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: trying to retag from for SharedReadWrite permis --> $DIR/interior_mut2.rs:LL:CC | LL | let _val = *inner_shr.get(); - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^ | | | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location | this error occurs as part of retag at ALLOC[0x0..0x4] diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs deleted file mode 100644 index f4e767302fd00..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector2.rs +++ /dev/null @@ -1,13 +0,0 @@ -fn inner(x: *mut i32, _y: &i32) { - // If `x` and `y` alias, retagging is fine with this... but we really - // shouldn't be allowed to write to `x` at all because `y` was assumed to be - // immutable for the duration of this call. - unsafe { *x = 0 }; //~ ERROR: protect -} - -fn main() { - let mut x = 0; - let xraw = &mut x as *mut _; - let xref = unsafe { &*xraw }; - inner(xraw, xref); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs b/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs deleted file mode 100644 index 634eb97217c6c..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/invalidate_against_protector3.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::alloc::{alloc, Layout}; - -fn inner(x: *mut i32, _y: &i32) { - // If `x` and `y` alias, retagging is fine with this... but we really - // shouldn't be allowed to write to `x` at all because `y` was assumed to be - // immutable for the duration of this call. - unsafe { *x = 0 }; //~ ERROR: protect -} - -fn main() { - unsafe { - let ptr = alloc(Layout::for_value(&0i32)) as *mut i32; - inner(ptr, &*ptr); - }; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr deleted file mode 100644 index c69a3af293c13..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-1.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error: Undefined Behavior: out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds - --> RUSTLIB/alloc/src/boxed.rs:LL:CC - | -LL | Box(unsafe { Unique::new_unchecked(raw) }, alloc) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ out-of-bounds pointer use: ALLOC has size 2, so pointer to 4 bytes starting at offset 0 is out-of-bounds - | - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: - = note: inside `std::boxed::Box::::from_raw_in` at RUSTLIB/alloc/src/boxed.rs:LL:CC - = note: inside `std::boxed::Box::::from_raw` at RUSTLIB/alloc/src/boxed.rs:LL:CC -note: inside `main` - --> $DIR/issue-miri-1050-1.rs:LL:CC - | -LL | drop(Box::from_raw(ptr as *mut u32)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs b/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs deleted file mode 100644 index 1b43daa925389..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/issue-miri-1050-2.rs +++ /dev/null @@ -1,9 +0,0 @@ -//@error-in-other-file: is a dangling pointer -use std::ptr::NonNull; - -fn main() { - unsafe { - let ptr = NonNull::::dangling(); - drop(Box::from_raw(ptr.as_ptr())); - } -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs deleted file mode 100644 index 8f94cc07a24f7..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/load_invalid_shr.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Make sure we catch this even without validation -//@compile-flags: -Zmiri-disable-validation - -// Make sure that we cannot load from memory a `&` that got already invalidated. -fn main() { - let x = &mut 42; - let xraw = x as *mut _; - let xref = unsafe { &*xraw }; - let xref_in_mem = Box::new(xref); - unsafe { *xraw = 42 }; // unfreeze - let _val = *xref_in_mem; //~ ERROR: /retag .* tag does not exist in the borrow stack/ -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs deleted file mode 100644 index f6fcdf1acdd2a..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation1.rs +++ /dev/null @@ -1,33 +0,0 @@ -fn demo_mut_advanced_unique(our: &mut i32) -> i32 { - unknown_code_1(&*our); - - // This "re-asserts" uniqueness of the reference: After writing, we know - // our tag is at the top of the stack. - *our = 5; - - unknown_code_2(); - - // We know this will return 5 - *our -} - -// Now comes the evil context -use std::ptr; - -static mut LEAK: *mut i32 = ptr::null_mut(); - -fn unknown_code_1(x: &i32) { - unsafe { - LEAK = x as *const _ as *mut _; - } -} - -fn unknown_code_2() { - unsafe { - *LEAK = 7; //~ ERROR: /write access .* tag does not exist in the borrow stack/ - } -} - -fn main() { - demo_mut_advanced_unique(&mut 0); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs deleted file mode 100644 index 2305ce746512a..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::ptr::NonNull; - -fn main() { - unsafe { - let x = &mut 0; - let mut ptr1 = NonNull::from(x); - let mut ptr2 = ptr1.clone(); - let raw1 = ptr1.as_mut(); - let _raw2 = ptr2.as_mut(); - let _val = *raw1; //~ ERROR: /read access .* tag does not exist in the borrow stack/ - } -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr b/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr deleted file mode 100644 index 30ce698761f3d..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/mut_exclusive_violation2.stderr +++ /dev/null @@ -1,28 +0,0 @@ -error: Undefined Behavior: attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - --> $DIR/mut_exclusive_violation2.rs:LL:CC - | -LL | let _val = *raw1; - | ^^^^^ - | | - | attempting a read access using at ALLOC[0x0], but that tag does not exist in the borrow stack for this location - | this error occurs as part of an access at ALLOC[0x0..0x4] - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a Unique retag at offsets [0x0..0x4] - --> $DIR/mut_exclusive_violation2.rs:LL:CC - | -LL | let raw1 = ptr1.as_mut(); - | ^^^^^^^^^^^^^ -help: was later invalidated at offsets [0x0..0x4] by a Unique retag - --> $DIR/mut_exclusive_violation2.rs:LL:CC - | -LL | let _raw2 = ptr2.as_mut(); - | ^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/mut_exclusive_violation2.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_pair_retagging.rs b/src/tools/miri/tests/fail/stacked_borrows/newtype_pair_retagging.rs deleted file mode 100644 index 1ae6740924c50..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/newtype_pair_retagging.rs +++ /dev/null @@ -1,18 +0,0 @@ -//@error-in-other-file: which is strongly protected -struct Newtype<'a>(&'a mut i32, i32); - -fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { - dealloc(); -} - -// Make sure that we protect references inside structs that are passed as ScalarPair. -fn main() { - let ptr = Box::into_raw(Box::new(0i32)); - #[rustfmt::skip] // I like my newlines - unsafe { - dealloc_while_running( - Newtype(&mut *ptr, 0), - || drop(Box::from_raw(ptr)), - ) - }; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs b/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs deleted file mode 100644 index f106274b8112e..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/newtype_retagging.rs +++ /dev/null @@ -1,18 +0,0 @@ -//@error-in-other-file: which is strongly protected -struct Newtype<'a>(&'a mut i32); - -fn dealloc_while_running(_n: Newtype<'_>, dealloc: impl FnOnce()) { - dealloc(); -} - -// Make sure that we protect references inside structs. -fn main() { - let ptr = Box::into_raw(Box::new(0i32)); - #[rustfmt::skip] // I like my newlines - unsafe { - dealloc_while_running( - Newtype(&mut *ptr), - || drop(Box::from_raw(ptr)), - ) - }; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs b/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs deleted file mode 100644 index 4262157d1e3df..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/outdated_local.rs +++ /dev/null @@ -1,9 +0,0 @@ -fn main() { - let mut x = 0; - let y: *const i32 = &x; - x = 1; // this invalidates y by reactivating the lowermost uniq borrow for this local - - assert_eq!(unsafe { *y }, 1); //~ ERROR: /read access .* tag does not exist in the borrow stack/ - - assert_eq!(x, 1); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs deleted file mode 100644 index 903c2733107bb..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/pass_invalid_shr.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Make sure that we cannot pass by argument a `&` that got already invalidated. -fn foo(_: &i32) {} - -fn main() { - let x = &mut 42; - let xraw = x as *mut _; - let xref = unsafe { &*xraw }; - unsafe { *xraw = 42 }; // unfreeze - foo(xref); //~ ERROR: /retag .* tag does not exist in the borrow stack/ -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stack.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stack.stderr new file mode 100644 index 0000000000000..c53a495b5e18b --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stack.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: Data race detected between (1) Read on thread `` and (2) Write on thread `` at ALLOC. (2) just happened here + --> $DIR/retag_data_race_read.rs:LL:CC + | +LL | *p = 5; + | ^^^^^^ Data race detected between (1) Read on thread `` and (2) Write on thread `` at ALLOC. (2) just happened here + | +help: and (1) occurred earlier here + --> $DIR/retag_data_race_read.rs:LL:CC + | +LL | let _r = &*p; + | ^^^ + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE (of the first span): + = note: inside `thread_2` at $DIR/retag_data_race_read.rs:LL:CC +note: inside closure + --> $DIR/retag_data_race_read.rs:LL:CC + | +LL | let t2 = std::thread::spawn(move || thread_2(p)); + | ^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr new file mode 100644 index 0000000000000..1e154eb0564a2 --- /dev/null +++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.tree.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: reborrow through (root of the allocation) is forbidden + --> RUSTLIB/std/src/rt.rs:LL:CC + | +LL | panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reborrow through (root of the allocation) is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag (root of the allocation) is foreign to the protected tag (i.e., it is not a child) + = help: this reborrow (acting as a foreign read access) would cause the protected tag (currently Active) to become Disabled + = help: protected tags must never be Disabled +help: the accessed tag was created here + --> RUSTLIB/std/src/rt.rs:LL:CC + | +LL | panic::catch_unwind(move || unsafe { init(argc, argv, sigpipe) }).map_err(rt_abort)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the protected tag was created here, in the initial state Active + --> RUSTLIB/std/src/panic.rs:LL:CC + | +LL | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { + | ^ + = note: BACKTRACE (of the first span): + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_write.rs b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_write.rs deleted file mode 100644 index c1dded40d3c35..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_write.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Make sure that a retag acts like a write for the data race model. -//@compile-flags: -Zmiri-preemption-rate=0 -#[derive(Copy, Clone)] -struct SendPtr(*mut u8); - -unsafe impl Send for SendPtr {} - -fn thread_1(p: SendPtr) { - let p = p.0; - unsafe { - let _r = &mut *p; - } -} - -fn thread_2(p: SendPtr) { - let p = p.0; - unsafe { - *p = 5; //~ ERROR: Data race detected between (1) Write on thread `` and (2) Write on thread `` - } -} - -fn main() { - let mut x = 0; - let p = std::ptr::addr_of_mut!(x); - let p = SendPtr(p); - - let t1 = std::thread::spawn(move || thread_1(p)); - let t2 = std::thread::spawn(move || thread_2(p)); - let _ = t1.join(); - let _ = t2.join(); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr index ff00c54570cd3..d357ab9639b40 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_option.stderr @@ -5,7 +5,8 @@ LL | ret | ^^^ | | | trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location - | this error occurs as part of retag at ALLOC[0x4..0x8] + | this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x4..0x8] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr index 61d041a8816d2..d346e6fa89597 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_mut_tuple.stderr @@ -5,7 +5,8 @@ LL | ret | ^^^ | | | trying to retag from for Unique permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location - | this error occurs as part of retag at ALLOC[0x4..0x8] + | this error occurs as part of retag (of a reference/box inside this compound value) at ALLOC[0x4..0x8] + | errors for retagging in fields are fairly new; please reach out to us (e.g. at ) if you find this error troubling | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs deleted file mode 100644 index 45526498dadf5..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Make sure that we cannot return a `&` that got already invalidated. -fn foo(x: &mut (i32, i32)) -> &i32 { - let xraw = x as *mut (i32, i32); - let ret = unsafe { &(*xraw).1 }; - unsafe { *xraw = (42, 23) }; // unfreeze - ret //~ ERROR: /retag .* tag does not exist in the borrow stack/ -} - -fn main() { - foo(&mut (1, 2)); -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs deleted file mode 100644 index 094ce33b9c1f7..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Make sure that we cannot return a `&` that got already invalidated, not even in an `Option`. -fn foo(x: &mut (i32, i32)) -> Option<&i32> { - let xraw = x as *mut (i32, i32); - let ret = Some(unsafe { &(*xraw).1 }); - unsafe { *xraw = (42, 23) }; // unfreeze - ret //~ ERROR: /retag .* tag does not exist in the borrow stack/ -} - -fn main() { - match foo(&mut (1, 2)) { - Some(_x) => {} - None => {} - } -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr deleted file mode 100644 index f14e8b8532f55..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_option.stderr +++ /dev/null @@ -1,33 +0,0 @@ -error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location - --> $DIR/return_invalid_shr_option.rs:LL:CC - | -LL | ret - | ^^^ - | | - | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location - | this error occurs as part of retag at ALLOC[0x4..0x8] - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a SharedReadOnly retag at offsets [0x4..0x8] - --> $DIR/return_invalid_shr_option.rs:LL:CC - | -LL | let ret = Some(unsafe { &(*xraw).1 }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: was later invalidated at offsets [0x0..0x8] by a write access - --> $DIR/return_invalid_shr_option.rs:LL:CC - | -LL | unsafe { *xraw = (42, 23) }; // unfreeze - | ^^^^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `foo` at $DIR/return_invalid_shr_option.rs:LL:CC -note: inside `main` - --> $DIR/return_invalid_shr_option.rs:LL:CC - | -LL | match foo(&mut (1, 2)) { - | ^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs deleted file mode 100644 index d0fd53e06aa26..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Make sure that we cannot return a `&` that got already invalidated, not even in a tuple. -fn foo(x: &mut (i32, i32)) -> (&i32,) { - let xraw = x as *mut (i32, i32); - let ret = (unsafe { &(*xraw).1 },); - unsafe { *xraw = (42, 23) }; // unfreeze - ret //~ ERROR: /retag .* tag does not exist in the borrow stack/ -} - -fn main() { - foo(&mut (1, 2)).0; -} diff --git a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr b/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr deleted file mode 100644 index 9ddaad4d1be30..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/return_invalid_shr_tuple.stderr +++ /dev/null @@ -1,33 +0,0 @@ -error: Undefined Behavior: trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location - --> $DIR/return_invalid_shr_tuple.rs:LL:CC - | -LL | ret - | ^^^ - | | - | trying to retag from for SharedReadOnly permission at ALLOC[0x4], but that tag does not exist in the borrow stack for this location - | this error occurs as part of retag at ALLOC[0x4..0x8] - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental - = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information -help: was created by a SharedReadOnly retag at offsets [0x4..0x8] - --> $DIR/return_invalid_shr_tuple.rs:LL:CC - | -LL | let ret = (unsafe { &(*xraw).1 },); - | ^^^^^^^^^^^^^^^^^^^^^^^^ -help: was later invalidated at offsets [0x0..0x8] by a write access - --> $DIR/return_invalid_shr_tuple.rs:LL:CC - | -LL | unsafe { *xraw = (42, 23) }; // unfreeze - | ^^^^^^^^^^^^^^^^ - = note: BACKTRACE (of the first span): - = note: inside `foo` at $DIR/return_invalid_shr_tuple.rs:LL:CC -note: inside `main` - --> $DIR/return_invalid_shr_tuple.rs:LL:CC - | -LL | foo(&mut (1, 2)).0; - | ^^^^^^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr index 589e1291ba731..10f97180b7540 100644 --- a/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr +++ b/src/tools/miri/tests/fail/stacked_borrows/shared_rw_borrows_are_weak1.stderr @@ -2,7 +2,7 @@ error: Undefined Behavior: trying to retag from for SharedReadWrite permis --> $DIR/shared_rw_borrows_are_weak1.rs:LL:CC | LL | y.get_mut(); - | ^^^^^^^^^^^ + | ^ | | | trying to retag from for SharedReadWrite permission at ALLOC[0x0], but that tag does not exist in the borrow stack for this location | this error occurs as part of two-phase retag at ALLOC[0x0..0x4] diff --git a/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs b/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs deleted file mode 100644 index d1322dc6e57bb..0000000000000 --- a/src/tools/miri/tests/fail/stacked_borrows/shr_frozen_violation1.rs +++ /dev/null @@ -1,15 +0,0 @@ -fn foo(x: &mut i32) -> i32 { - *x = 5; - unknown_code(&*x); - *x // must return 5 -} - -fn main() { - println!("{}", foo(&mut 0)); -} - -fn unknown_code(x: &i32) { - unsafe { - *(x as *const i32 as *mut i32) = 7; //~ ERROR: /write access .* only grants SharedReadOnly permission/ - } -} diff --git a/src/tools/miri/tests/fail/terminate-terminator.rs b/src/tools/miri/tests/fail/terminate-terminator.rs index 22ffa1b271107..bd6cd69215ae8 100644 --- a/src/tools/miri/tests/fail/terminate-terminator.rs +++ b/src/tools/miri/tests/fail/terminate-terminator.rs @@ -1,4 +1,4 @@ -//@compile-flags: -Zmir-opt-level=3 +//@compile-flags: -Zmir-opt-level=3 -Zinline-mir-hint-threshold=1000 // Enable MIR inlining to ensure that `TerminatorKind::Terminate` is generated // instead of just `UnwindAction::Terminate`. @@ -12,13 +12,13 @@ impl Drop for Foo { #[inline(always)] fn has_cleanup() { + //~^ ERROR: panic in a function that cannot unwind let _f = Foo; panic!(); } extern "C" fn panic_abort() { has_cleanup(); - //~^ ERROR: panic in a function that cannot unwind } fn main() { diff --git a/src/tools/miri/tests/fail/terminate-terminator.stderr b/src/tools/miri/tests/fail/terminate-terminator.stderr index 8ce4bb7cbb5e9..4f73b599a3f9b 100644 --- a/src/tools/miri/tests/fail/terminate-terminator.stderr +++ b/src/tools/miri/tests/fail/terminate-terminator.stderr @@ -1,20 +1,24 @@ warning: You have explicitly enabled MIR optimizations, overriding Miri's default which is to completely disable them. Any optimizations may hide UB that Miri would otherwise detect, and it is not necessarily possible to predict what kind of UB will be missed. If you are enabling optimizations to make Miri run faster, we advise using cfg(miri) to shrink your workload instead. The performance benefit of enabling MIR optimizations is usually marginal at best. -thread 'main' panicked at 'explicit panic', $DIR/terminate-terminator.rs:LL:CC +thread 'main' panicked at $DIR/terminate-terminator.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: panic in a function that cannot unwind --> $DIR/terminate-terminator.rs:LL:CC | LL | / fn has_cleanup() { +LL | | LL | | let _f = Foo; LL | | panic!(); LL | | } | |_^ panic in a function that cannot unwind -... -LL | has_cleanup(); - | ------------- in this inlined function call | - = note: inside `panic_abort` at $DIR/terminate-terminator.rs:LL:CC + = note: inside `has_cleanup` at $DIR/terminate-terminator.rs:LL:CC +note: inside `panic_abort` + --> $DIR/terminate-terminator.rs:LL:CC + | +LL | has_cleanup(); + | ^^^^^^^^^^^^^ note: inside `main` --> $DIR/terminate-terminator.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/tree-borrows/error-range.stderr b/src/tools/miri/tests/fail/tree-borrows/error-range.stderr deleted file mode 100644 index bc829fd86d350..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/error-range.stderr +++ /dev/null @@ -1,44 +0,0 @@ -error: Undefined Behavior: read access through is forbidden - --> $DIR/error-range.rs:LL:CC - | -LL | rmut[5] += 1; - | ^^^^^^^^^^^^ read access through is forbidden - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: the accessed tag is a child of the conflicting tag - = help: the conflicting tag has state Disabled which forbids child read accesses -help: the accessed tag was created here - --> $DIR/error-range.rs:LL:CC - | -LL | let rmut = &mut *addr_of_mut!(data[0..6]); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: the conflicting tag was created here, in the initial state Reserved - --> $DIR/error-range.rs:LL:CC - | -LL | let rmut = &mut *addr_of_mut!(data[0..6]); - | ^^^^ -help: the conflicting tag then transitioned from Reserved to Active due to a child write access at offsets [0x5..0x6] - --> $DIR/error-range.rs:LL:CC - | -LL | rmut[5] += 1; - | ^^^^^^^^^^^^ - = help: this corresponds to an activation -help: the conflicting tag then transitioned from Active to Frozen due to a foreign read access at offsets [0x5..0x6] - --> $DIR/error-range.rs:LL:CC - | -LL | let _v = data[5]; - | ^^^^^^^ - = help: this corresponds to a loss of write permissions -help: the conflicting tag then transitioned from Frozen to Disabled due to a foreign write access at offsets [0x5..0x6] - --> $DIR/error-range.rs:LL:CC - | -LL | data[5] = 1; - | ^^^^^^^^^^^ - = help: this corresponds to a loss of read permissions - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/error-range.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/tree-borrows/fragile-data-race.rs b/src/tools/miri/tests/fail/tree-borrows/fragile-data-race.rs deleted file mode 100644 index 215100de0a13c..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/fragile-data-race.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Race-condition-like interaction between a read and a reborrow. -//! Even though no write or fake write occurs, reads have an effect on protected -//! Reserved. This is a protected-retag/read data race, but is not *detected* as -//! a data race violation because reborrows are not writes. -//! -//! This test is sensitive to the exact schedule so we disable preemption. -//@compile-flags: -Zmiri-tree-borrows -Zmiri-preemption-rate=0 -use std::ptr::addr_of_mut; -use std::thread; - -#[derive(Copy, Clone)] -struct SendPtr(*mut u8); - -unsafe impl Send for SendPtr {} - -// First thread is just a reborrow, but for an instant `x` is -// protected and thus vulnerable to foreign reads. -fn thread_1(x: &mut u8) -> SendPtr { - thread::yield_now(); // make the other thread go first - SendPtr(x as *mut u8) -} - -// Second thread simply performs a read. -fn thread_2(x: &u8) { - let _val = *x; -} - -fn main() { - let mut x = 0u8; - let x_1 = unsafe { &mut *addr_of_mut!(x) }; - let xg = unsafe { &*addr_of_mut!(x) }; - - // The two threads are executed in parallel on aliasing pointers. - // UB occurs if the read of thread_2 occurs while the protector of thread_1 - // is in place. - let hf = thread::spawn(move || thread_1(x_1)); - let hg = thread::spawn(move || thread_2(xg)); - let SendPtr(p) = hf.join().unwrap(); - let () = hg.join().unwrap(); - - unsafe { *p = 1 }; //~ ERROR: /write access through .* is forbidden/ -} diff --git a/src/tools/miri/tests/fail/tree-borrows/fragile-data-race.stderr b/src/tools/miri/tests/fail/tree-borrows/fragile-data-race.stderr deleted file mode 100644 index 79744964a8bcc..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/fragile-data-race.stderr +++ /dev/null @@ -1,32 +0,0 @@ -error: Undefined Behavior: write access through is forbidden - --> $DIR/fragile-data-race.rs:LL:CC - | -LL | unsafe { *p = 1 }; - | ^^^^^^ write access through is forbidden - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: the accessed tag is a child of the conflicting tag - = help: the conflicting tag has state Frozen which forbids child write accesses -help: the accessed tag was created here - --> $DIR/fragile-data-race.rs:LL:CC - | -LL | fn thread_1(x: &mut u8) -> SendPtr { - | ^ -help: the conflicting tag was created here, in the initial state Reserved - --> RUSTLIB/std/src/panic.rs:LL:CC - | -LL | pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { - | ^ -help: the conflicting tag then transitioned from Reserved to Frozen due to a foreign read access at offsets [0x0..0x1] - --> RUSTLIB/core/src/ptr/mod.rs:LL:CC - | -LL | crate::intrinsics::read_via_copy(src) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: this corresponds to a loss of write permissions - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/fragile-data-race.rs:LL:CC - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/tree-borrows/read-to-local.rs b/src/tools/miri/tests/fail/tree-borrows/read-to-local.rs deleted file mode 100644 index 025b7ad22dc34..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/read-to-local.rs +++ /dev/null @@ -1,14 +0,0 @@ -//@compile-flags: -Zmiri-tree-borrows - -// Read to local variable kills reborrows *and* raw pointers derived from them. -// This test would succeed under Stacked Borrows. -fn main() { - unsafe { - let mut root = 6u8; - let mref = &mut root; - let ptr = mref as *mut u8; - *ptr = 0; // Write - assert_eq!(root, 0); // Parent Read - *ptr = 0; //~ ERROR: /write access through .* is forbidden/ - } -} diff --git a/src/tools/miri/tests/fail/tree-borrows/read-to-local.stderr b/src/tools/miri/tests/fail/tree-borrows/read-to-local.stderr deleted file mode 100644 index 8c9c2f8f9656b..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/read-to-local.stderr +++ /dev/null @@ -1,33 +0,0 @@ -error: Undefined Behavior: write access through is forbidden - --> $DIR/read-to-local.rs:LL:CC - | -LL | *ptr = 0; - | ^^^^^^^^ write access through is forbidden - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: the accessed tag has state Frozen which forbids child write accesses -help: the accessed tag was created here, in the initial state Reserved - --> $DIR/read-to-local.rs:LL:CC - | -LL | let mref = &mut root; - | ^^^^^^^^^ -help: the accessed tag then transitioned from Reserved to Active due to a child write access at offsets [0x0..0x1] - --> $DIR/read-to-local.rs:LL:CC - | -LL | *ptr = 0; // Write - | ^^^^^^^^ - = help: this corresponds to an activation -help: the accessed tag then transitioned from Active to Frozen due to a foreign read access at offsets [0x0..0x1] - --> $DIR/read-to-local.rs:LL:CC - | -LL | assert_eq!(root, 0); // Parent Read - | ^^^^^^^^^^^^^^^^^^^ - = help: this corresponds to a loss of write permissions - = note: BACKTRACE (of the first span): - = note: inside `main` at $DIR/read-to-local.rs:LL:CC - = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/tree-borrows/retag-data-race.rs b/src/tools/miri/tests/fail/tree-borrows/retag-data-race.rs deleted file mode 100644 index 8ef3d23e804b7..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/retag-data-race.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Make sure that a retag acts like a read for the data race model. -//! This is a retag/write race condition. -//! -//! This test is sensitive to the exact schedule so we disable preemption. -//@compile-flags: -Zmiri-tree-borrows -Zmiri-preemption-rate=0 -#[derive(Copy, Clone)] -struct SendPtr(*mut u8); - -unsafe impl Send for SendPtr {} - -unsafe fn thread_1(SendPtr(p): SendPtr) { - let _r = &*p; -} - -unsafe fn thread_2(SendPtr(p): SendPtr) { - *p = 5; //~ ERROR: Data race detected between (1) Read on thread `` and (2) Write on thread `` -} - -fn main() { - let mut x = 0; - let p = std::ptr::addr_of_mut!(x); - let p = SendPtr(p); - - let t1 = std::thread::spawn(move || unsafe { thread_1(p) }); - let t2 = std::thread::spawn(move || unsafe { thread_2(p) }); - let _ = t1.join(); - let _ = t2.join(); -} diff --git a/src/tools/miri/tests/fail/tree-borrows/retag-data-race.stderr b/src/tools/miri/tests/fail/tree-borrows/retag-data-race.stderr deleted file mode 100644 index f2cdfe7c314d5..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/retag-data-race.stderr +++ /dev/null @@ -1,25 +0,0 @@ -error: Undefined Behavior: Data race detected between (1) Read on thread `` and (2) Write on thread `` at ALLOC. (2) just happened here - --> $DIR/retag-data-race.rs:LL:CC - | -LL | *p = 5; - | ^^^^^^ Data race detected between (1) Read on thread `` and (2) Write on thread `` at ALLOC. (2) just happened here - | -help: and (1) occurred earlier here - --> $DIR/retag-data-race.rs:LL:CC - | -LL | let _r = &*p; - | ^^^ - = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior - = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE (of the first span): - = note: inside `thread_2` at $DIR/retag-data-race.rs:LL:CC -note: inside closure - --> $DIR/retag-data-race.rs:LL:CC - | -LL | let t2 = std::thread::spawn(move || unsafe { thread_2(p) }); - | ^^^^^^^^^^^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/tree-borrows/write-during-2phase.stderr b/src/tools/miri/tests/fail/tree-borrows/write-during-2phase.stderr deleted file mode 100644 index f6285bdcf16d4..0000000000000 --- a/src/tools/miri/tests/fail/tree-borrows/write-during-2phase.stderr +++ /dev/null @@ -1,45 +0,0 @@ -error: Undefined Behavior: read access through is forbidden - --> $DIR/write-during-2phase.rs:LL:CC - | -LL | fn add(&mut self, n: u64) -> u64 { - | ^^^^^^^^^ read access through is forbidden - | - = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental - = help: the accessed tag has state Disabled which forbids child read accesses -help: the accessed tag was created here, in the initial state Reserved - --> $DIR/write-during-2phase.rs:LL:CC - | -LL | let _res = f.add(unsafe { - | ________________^ -LL | | let n = f.0; -LL | | // This is the access at fault, but it's not immediately apparent because -LL | | // the reference that got invalidated is not under a Protector. -LL | | *inner = 42; -LL | | n -LL | | }); - | |______^ -help: the accessed tag then transitioned from Reserved to Disabled due to a foreign write access at offsets [0x0..0x8] - --> $DIR/write-during-2phase.rs:LL:CC - | -LL | *inner = 42; - | ^^^^^^^^^^^ - = help: this corresponds to a loss of read and write permissions - = note: BACKTRACE (of the first span): - = note: inside `Foo::add` at $DIR/write-during-2phase.rs:LL:CC -note: inside `main` - --> $DIR/write-during-2phase.rs:LL:CC - | -LL | let _res = f.add(unsafe { - | ________________^ -LL | | let n = f.0; -LL | | // This is the access at fault, but it's not immediately apparent because -LL | | // the reference that got invalidated is not under a Protector. -LL | | *inner = 42; -LL | | n -LL | | }); - | |______^ - -note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace - -error: aborting due to previous error - diff --git a/src/tools/miri/tests/fail/tree-borrows/alternate-read-write.rs b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs similarity index 100% rename from src/tools/miri/tests/fail/tree-borrows/alternate-read-write.rs rename to src/tools/miri/tests/fail/tree_borrows/alternate-read-write.rs diff --git a/src/tools/miri/tests/fail/tree-borrows/alternate-read-write.stderr b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr similarity index 76% rename from src/tools/miri/tests/fail/tree-borrows/alternate-read-write.stderr rename to src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr index bb601fc88352d..fcbc2e1278637 100644 --- a/src/tools/miri/tests/fail/tree-borrows/alternate-read-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/alternate-read-write.stderr @@ -6,7 +6,7 @@ LL | *y += 1; // Failure | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: the accessed tag is a child of the conflicting tag - = help: the conflicting tag has state Frozen which forbids child write accesses + = help: the conflicting tag has state Frozen which forbids this child write access help: the accessed tag was created here --> $DIR/alternate-read-write.rs:LL:CC | @@ -17,18 +17,18 @@ help: the conflicting tag was created here, in the initial state Reserved | LL | let y = unsafe { &mut *(x as *mut u8) }; | ^^^^^^^^^^^^^^^^^^^^ -help: the conflicting tag then transitioned from Reserved to Active due to a child write access at offsets [0x0..0x1] +help: the conflicting tag later transitioned to Active due to a child write access at offsets [0x0..0x1] --> $DIR/alternate-read-write.rs:LL:CC | LL | *y += 1; // Success | ^^^^^^^ - = help: this corresponds to an activation -help: the conflicting tag then transitioned from Active to Frozen due to a foreign read access at offsets [0x0..0x1] + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the conflicting tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1] --> $DIR/alternate-read-write.rs:LL:CC | LL | let _val = *x; | ^^ - = help: this corresponds to a loss of write permissions + = help: this transition corresponds to a loss of write permissions = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/alternate-read-write.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr new file mode 100644 index 0000000000000..1b05be9c54db2 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.default.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: entering unreachable code + --> $DIR/children-can-alias.rs:LL:CC + | +LL | std::hint::unreachable_unchecked(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entering unreachable code + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/children-can-alias.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs new file mode 100644 index 0000000000000..b5a01cd4324e2 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.rs @@ -0,0 +1,59 @@ +//@revisions: default uniq +//@compile-flags: -Zmiri-tree-borrows +//@[uniq]compile-flags: -Zmiri-unique-is-unique + +//! This is NOT intended behavior. +//! We should eventually find a solution so that the version with `Unique` passes too, +//! otherwise `Unique` is more strict than `&mut`! + +#![feature(ptr_internals)] + +use core::ptr::addr_of_mut; +use core::ptr::Unique; + +fn main() { + let mut data = 0u8; + let raw = addr_of_mut!(data); + unsafe { + raw_children_of_refmut_can_alias(&mut *raw); + raw_children_of_unique_can_alias(Unique::new_unchecked(raw)); + + // Ultimately the intended behavior is that both above tests would + // succeed. + std::hint::unreachable_unchecked(); + //~[default]^ ERROR: entering unreachable code + } +} + +unsafe fn raw_children_of_refmut_can_alias(x: &mut u8) { + let child1 = addr_of_mut!(*x); + let child2 = addr_of_mut!(*x); + // We create two raw aliases of `x`: they have the exact same + // tag and can be used interchangeably. + child1.write(1); + child2.write(2); + child1.write(1); + child2.write(2); +} + +unsafe fn raw_children_of_unique_can_alias(x: Unique) { + let child1 = x.as_ptr(); + let child2 = x.as_ptr(); + // Under `-Zmiri-unique-is-unique`, `Unique` accidentally offers more guarantees + // than `&mut`. Not because it responds differently to accesses but because + // there is no easy way to obtain a copy with the same tag. + // + // The closest (non-hack) attempt is two calls to `as_ptr`. + // - Without `-Zmiri-unique-is-unique`, independent `as_ptr` calls return pointers + // with the same tag that can thus be used interchangeably. + // - With the current implementation of `-Zmiri-unique-is-unique`, they return cousin + // tags with permissions that do not tolerate aliasing. + // Eventually we should make such aliasing allowed in some situations + // (e.g. when there is no protector), which will probably involve + // introducing a new kind of permission. + child1.write(1); + child2.write(2); + //~[uniq]^ ERROR: /write access through .* is forbidden/ + child1.write(1); + child2.write(2); +} diff --git a/src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr new file mode 100644 index 0000000000000..2f77b27729e7a --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/children-can-alias.uniq.stderr @@ -0,0 +1,37 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/children-can-alias.rs:LL:CC + | +LL | child2.write(2); + | ^^^^^^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child write access +help: the accessed tag was created here + --> $DIR/children-can-alias.rs:LL:CC + | +LL | let child2 = x.as_ptr(); + | ^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/children-can-alias.rs:LL:CC + | +LL | let child2 = x.as_ptr(); + | ^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x1] + --> $DIR/children-can-alias.rs:LL:CC + | +LL | child1.write(1); + | ^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `raw_children_of_unique_can_alias` at $DIR/children-can-alias.rs:LL:CC +note: inside `main` + --> $DIR/children-can-alias.rs:LL:CC + | +LL | raw_children_of_unique_can_alias(Unique::new_unchecked(raw)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree-borrows/error-range.rs b/src/tools/miri/tests/fail/tree_borrows/error-range.rs similarity index 100% rename from src/tools/miri/tests/fail/tree-borrows/error-range.rs rename to src/tools/miri/tests/fail/tree_borrows/error-range.rs diff --git a/src/tools/miri/tests/fail/tree_borrows/error-range.stderr b/src/tools/miri/tests/fail/tree_borrows/error-range.stderr new file mode 100644 index 0000000000000..b58dcd7572b1f --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/error-range.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: read access through is forbidden + --> $DIR/error-range.rs:LL:CC + | +LL | rmut[5] += 1; + | ^^^^^^^^^^^^ read access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Disabled which forbids this child read access +help: the accessed tag was created here + --> $DIR/error-range.rs:LL:CC + | +LL | let rmut = &mut *addr_of_mut!(data[0..6]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/error-range.rs:LL:CC + | +LL | let rmut = &mut *addr_of_mut!(data[0..6]); + | ^^^^ +help: the conflicting tag later transitioned to Disabled due to a foreign write access at offsets [0x5..0x6] + --> $DIR/error-range.rs:LL:CC + | +LL | data[5] = 1; + | ^^^^^^^^^^^ + = help: this transition corresponds to a loss of read permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/error-range.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.rs b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.rs new file mode 100644 index 0000000000000..a21cbab6a9fc1 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.rs @@ -0,0 +1,29 @@ +//@compile-flags: -Zmiri-tree-borrows +// This test is the TB counterpart to fail/stacked_borrows/fnentry_invalidation, +// but the SB version passes TB without error. +// An additional write access is inserted so that this test properly fails. + +// Test that spans displayed in diagnostics identify the function call, not the function +// definition, as the location of invalidation due to FnEntry retag. Technically the FnEntry retag +// occurs inside the function, but what the user wants to know is which call produced the +// invalidation. + +fn main() { + let mut x = 0i32; + let z = &mut x as *mut i32; + unsafe { + *z = 1; + } + x.do_bad(); + unsafe { + *z = 2; //~ ERROR: /write access through .* is forbidden/ + } +} + +trait Bad { + fn do_bad(&mut self) { + // who knows + } +} + +impl Bad for i32 {} diff --git a/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr new file mode 100644 index 0000000000000..0f1e7735a0e31 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/fnentry_invalidation.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | *z = 2; + | ^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | let z = &mut x as *mut i32; + | ^^^^^^ +help: the accessed tag later transitioned to Active due to a child write access at offsets [0x0..0x4] + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | *z = 1; + | ^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x4] + --> $DIR/fnentry_invalidation.rs:LL:CC + | +LL | x.do_bad(); + | ^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/fnentry_invalidation.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree-borrows/outside-range.rs b/src/tools/miri/tests/fail/tree_borrows/outside-range.rs similarity index 100% rename from src/tools/miri/tests/fail/tree-borrows/outside-range.rs rename to src/tools/miri/tests/fail/tree_borrows/outside-range.rs diff --git a/src/tools/miri/tests/fail/tree-borrows/outside-range.stderr b/src/tools/miri/tests/fail/tree_borrows/outside-range.stderr similarity index 85% rename from src/tools/miri/tests/fail/tree-borrows/outside-range.stderr rename to src/tools/miri/tests/fail/tree_borrows/outside-range.stderr index 14696c704fc1b..8b3bf8414db03 100644 --- a/src/tools/miri/tests/fail/tree-borrows/outside-range.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/outside-range.stderr @@ -6,8 +6,8 @@ LL | *y.add(3) = 42; | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: the accessed tag is foreign to the protected tag (i.e., it is not a child) - = help: the access would cause the protected tag to transition from Reserved to Disabled - = help: this is a loss of read and write permissions, which is not allowed for protected tags + = help: this foreign write access would cause the protected tag (currently Reserved) to become Disabled + = help: protected tags must never be Disabled help: the accessed tag was created here --> $DIR/outside-range.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.rs b/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.rs new file mode 100644 index 0000000000000..303c9ee0c0e72 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.rs @@ -0,0 +1,14 @@ +//@compile-flags: -Zmiri-tree-borrows + +// Read from local variable kills reborrows *and* raw pointers derived from them. +// This test would be accepted under Stacked Borrows. +fn main() { + unsafe { + let mut root = 6u8; + let mref = &mut root; + let ptr = mref as *mut u8; + *ptr = 0; // Write + assert_eq!(root, 0); // Parent Read + *ptr = 0; //~ ERROR: /write access through .* is forbidden/ + } +} diff --git a/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr new file mode 100644 index 0000000000000..addba9d809ef7 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/parent_read_freezes_raw_mut.stderr @@ -0,0 +1,33 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/parent_read_freezes_raw_mut.rs:LL:CC + | +LL | *ptr = 0; + | ^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/parent_read_freezes_raw_mut.rs:LL:CC + | +LL | let mref = &mut root; + | ^^^^^^^^^ +help: the accessed tag later transitioned to Active due to a child write access at offsets [0x0..0x1] + --> $DIR/parent_read_freezes_raw_mut.rs:LL:CC + | +LL | *ptr = 0; // Write + | ^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the accessed tag later transitioned to Frozen due to a reborrow (acting as a foreign read access) at offsets [0x0..0x1] + --> $DIR/parent_read_freezes_raw_mut.rs:LL:CC + | +LL | assert_eq!(root, 0); // Parent Read + | ^^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/parent_read_freezes_raw_mut.rs:LL:CC + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.rs b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.rs new file mode 100644 index 0000000000000..a22e3118341a1 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.rs @@ -0,0 +1,20 @@ +//@compile-flags: -Zmiri-tree-borrows +// This test is the TB counterpart to fail/stacked_borrows/pass_invalid_mut, +// but the SB version passes TB without error. +// An additional write access is inserted so that this test properly fails. + +// Make sure that we cannot use a `&mut` whose parent got invalidated. +// fail/both_borrows/pass_invalid_shr is already checking a forbidden read, +// so the new thing that this tests is a forbidden write. +fn foo(nope: &mut i32) { + *nope = 31; //~ ERROR: /write access through .* is forbidden/ +} + +fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &mut *xraw }; + *xref = 18; // activate xref + let _val = unsafe { *xraw }; // invalidate xref for writing + foo(xref); +} diff --git a/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr new file mode 100644 index 0000000000000..056ac6db5503d --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/pass_invalid_mut.stderr @@ -0,0 +1,43 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | *nope = 31; + | ^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Frozen which forbids this child write access +help: the accessed tag was created here + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | fn foo(nope: &mut i32) { + | ^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | let xref = unsafe { &mut *xraw }; + | ^^^^^^^^^^ +help: the conflicting tag later transitioned to Active due to a child write access at offsets [0x0..0x4] + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | *xref = 18; // activate xref + | ^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the conflicting tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x4] + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref for writing + | ^^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `foo` at $DIR/pass_invalid_mut.rs:LL:CC +note: inside `main` + --> $DIR/pass_invalid_mut.rs:LL:CC + | +LL | foo(xref); + | ^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree-borrows/reserved/cell-protected-write.rs b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs similarity index 97% rename from src/tools/miri/tests/fail/tree-borrows/reserved/cell-protected-write.rs rename to src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs index 872efe3ad5933..465679b72c343 100644 --- a/src/tools/miri/tests/fail/tree-borrows/reserved/cell-protected-write.rs +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.rs @@ -3,8 +3,8 @@ // Check how a Reserved with interior mutability // responds to a Foreign Write under a Protector #[path = "../../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; use std::cell::UnsafeCell; diff --git a/src/tools/miri/tests/fail/tree-borrows/reserved/cell-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr similarity index 84% rename from src/tools/miri/tests/fail/tree-borrows/reserved/cell-protected-write.stderr rename to src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr index b85793ff06337..769769d957dbe 100644 --- a/src/tools/miri/tests/fail/tree-borrows/reserved/cell-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/cell-protected-write.stderr @@ -1,4 +1,4 @@ -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -7,7 +7,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Re*| │ └─┬── | Re*| │ └──── Strongly protected | Re*| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── error: Undefined Behavior: write access through (y, callee:y, caller:y) is forbidden --> $DIR/cell-protected-write.rs:LL:CC | @@ -16,8 +16,8 @@ LL | *y = 1; | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: the accessed tag (y, callee:y, caller:y) is foreign to the protected tag (callee:x) (i.e., it is not a child) - = help: the access would cause the protected tag (callee:x) to transition from Reserved to Disabled - = help: this is a loss of read and write permissions, which is not allowed for protected tags + = help: this foreign write access would cause the protected tag (callee:x) (currently Reserved) to become Disabled + = help: protected tags must never be Disabled help: the accessed tag was created here --> $DIR/cell-protected-write.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/tree-borrows/reserved/int-protected-write.rs b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs similarity index 97% rename from src/tools/miri/tests/fail/tree-borrows/reserved/int-protected-write.rs rename to src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs index 3a1205a84f726..1e6e2eebd26ce 100644 --- a/src/tools/miri/tests/fail/tree-borrows/reserved/int-protected-write.rs +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.rs @@ -1,8 +1,8 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 #[path = "../../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; // Check how a Reserved without interior mutability responds to a Foreign // Write when under a protector diff --git a/src/tools/miri/tests/fail/tree-borrows/reserved/int-protected-write.stderr b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr similarity index 83% rename from src/tools/miri/tests/fail/tree-borrows/reserved/int-protected-write.stderr rename to src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr index 5de7dc0c7c51f..f7e9fb9e3c3a4 100644 --- a/src/tools/miri/tests/fail/tree-borrows/reserved/int-protected-write.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/reserved/int-protected-write.stderr @@ -1,4 +1,4 @@ -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -7,7 +7,7 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Res| │ └─┬── | Res| │ └──── Strongly protected | Res| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── error: Undefined Behavior: write access through (y, callee:y, caller:y) is forbidden --> $DIR/int-protected-write.rs:LL:CC | @@ -16,8 +16,8 @@ LL | *y = 0; | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental = help: the accessed tag (y, callee:y, caller:y) is foreign to the protected tag (callee:x) (i.e., it is not a child) - = help: the access would cause the protected tag (callee:x) to transition from Reserved to Disabled - = help: this is a loss of read and write permissions, which is not allowed for protected tags + = help: this foreign write access would cause the protected tag (callee:x) (currently Reserved) to become Disabled + = help: protected tags must never be Disabled help: the accessed tag was created here --> $DIR/int-protected-write.rs:LL:CC | diff --git a/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.rs b/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.rs new file mode 100644 index 0000000000000..869e48bea56c9 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.rs @@ -0,0 +1,22 @@ +//@compile-flags: -Zmiri-tree-borrows +// This test is the TB counterpart to fail/stacked_borrows/return_invalid_mut, +// but the SB version passes TB without error. +// An additional write access is inserted so that this test properly fails. + +// Make sure that we cannot use a returned `&mut` that got already invalidated. +// fail/both_borrows/return_invalid_shr is already testing that we cannot return +// a reference invalidated for reading, so the new thing that we test here +// is the case where the return value cannot be used for writing. +fn foo(x: &mut (i32, i32)) -> &mut i32 { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &mut (*xraw).1 }; + *ret = *ret; // activate + let _val = unsafe { *xraw }; // invalidate xref for writing + ret +} + +fn main() { + let arg = &mut (1, 2); + let ret = foo(arg); + *ret = 3; //~ ERROR: /write access through .* is forbidden/ +} diff --git a/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr b/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr new file mode 100644 index 0000000000000..978e5a340faf3 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/return_invalid_mut.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | *ret = 3; + | ^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Frozen which forbids this child write access +help: the accessed tag was created here + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | let ret = foo(arg); + | ^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | let ret = unsafe { &mut (*xraw).1 }; + | ^^^^^^^^^^^^^^ +help: the conflicting tag later transitioned to Active due to a child write access at offsets [0x4..0x8] + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | *ret = *ret; // activate + | ^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the conflicting tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x8] + --> $DIR/return_invalid_mut.rs:LL:CC + | +LL | let _val = unsafe { *xraw }; // invalidate xref for writing + | ^^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/return_invalid_mut.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree-borrows/strongly-protected.rs b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.rs similarity index 100% rename from src/tools/miri/tests/fail/tree-borrows/strongly-protected.rs rename to src/tools/miri/tests/fail/tree_borrows/strongly-protected.rs diff --git a/src/tools/miri/tests/fail/tree-borrows/strongly-protected.stderr b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr similarity index 84% rename from src/tools/miri/tests/fail/tree-borrows/strongly-protected.stderr rename to src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr index 071b216ff982b..55665e63e8a7e 100644 --- a/src/tools/miri/tests/fail/tree-borrows/strongly-protected.stderr +++ b/src/tools/miri/tests/fail/tree_borrows/strongly-protected.stderr @@ -17,16 +17,10 @@ help: the strongly protected tag was created here, in the initial state Re | LL | fn inner(x: &mut i32, f: fn(&mut i32)) { | ^ -help: the strongly protected tag then transitioned from Reserved to Active due to a child write access at offsets [0x0..0x4] - --> $DIR/strongly-protected.rs:LL:CC - | -LL | drop(unsafe { Box::from_raw(raw) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: this corresponds to an activation = note: BACKTRACE (of the first span): = note: inside `std::alloc::dealloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC = note: inside `::deallocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC - = note: inside `alloc::alloc::box_free::` at RUSTLIB/alloc/src/alloc.rs:LL:CC + = note: inside ` as std::ops::Drop>::drop` at RUSTLIB/alloc/src/boxed.rs:LL:CC = note: inside `std::ptr::drop_in_place::> - shim(Some(std::boxed::Box))` at RUSTLIB/core/src/ptr/mod.rs:LL:CC = note: inside `std::mem::drop::>` at RUSTLIB/core/src/mem/mod.rs:LL:CC note: inside closure diff --git a/src/tools/miri/tests/fail/tree_borrows/unique.default.stderr b/src/tools/miri/tests/fail/tree_borrows/unique.default.stderr new file mode 100644 index 0000000000000..eb4d5c10c2511 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/unique.default.stderr @@ -0,0 +1,32 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/unique.rs:LL:CC + | +LL | *uniq.as_ptr() = 3; + | ^^^^^^^^^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Frozen which forbids this child write access +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/unique.rs:LL:CC + | +LL | let refmut = &mut data; + | ^^^^^^^^^ +help: the accessed tag later transitioned to Active due to a child write access at offsets [0x0..0x1] + --> $DIR/unique.rs:LL:CC + | +LL | *uniq.as_ptr() = 1; // activation + | ^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the accessed tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1] + --> $DIR/unique.rs:LL:CC + | +LL | let _definitely_parent = data; // definitely Frozen by now + | ^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/unique.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/unique.rs b/src/tools/miri/tests/fail/tree_borrows/unique.rs new file mode 100644 index 0000000000000..0844dd21a5923 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/unique.rs @@ -0,0 +1,27 @@ +//@revisions: default uniq +//@compile-flags: -Zmiri-tree-borrows +//@[uniq]compile-flags: -Zmiri-unique-is-unique + +// A pattern that detects if `Unique` is treated as exclusive or not: +// activate the pointer behind a `Unique` then do a read that is parent +// iff `Unique` was specially reborrowed. + +#![feature(ptr_internals)] +use core::ptr::Unique; + +fn main() { + let mut data = 0u8; + let refmut = &mut data; + let rawptr = refmut as *mut u8; + + unsafe { + let uniq = Unique::new_unchecked(rawptr); + *uniq.as_ptr() = 1; // activation + let _maybe_parent = *rawptr; // maybe becomes Frozen + *uniq.as_ptr() = 2; + //~[uniq]^ ERROR: /write access through .* is forbidden/ + let _definitely_parent = data; // definitely Frozen by now + *uniq.as_ptr() = 3; + //~[default]^ ERROR: /write access through .* is forbidden/ + } +} diff --git a/src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr b/src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr new file mode 100644 index 0000000000000..2d8936f273fef --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/unique.uniq.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/unique.rs:LL:CC + | +LL | *uniq.as_ptr() = 2; + | ^^^^^^^^^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Frozen which forbids this child write access +help: the accessed tag was created here + --> $DIR/unique.rs:LL:CC + | +LL | *uniq.as_ptr() = 2; + | ^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Reserved + --> $DIR/unique.rs:LL:CC + | +LL | let uniq = Unique::new_unchecked(rawptr); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag later transitioned to Active due to a child write access at offsets [0x0..0x1] + --> $DIR/unique.rs:LL:CC + | +LL | *uniq.as_ptr() = 1; // activation + | ^^^^^^^^^^^^^^^^^^ + = help: this transition corresponds to the first write to a 2-phase borrowed mutable reference +help: the conflicting tag later transitioned to Frozen due to a foreign read access at offsets [0x0..0x1] + --> $DIR/unique.rs:LL:CC + | +LL | let _maybe_parent = *rawptr; // maybe becomes Frozen + | ^^^^^^^ + = help: this transition corresponds to a loss of write permissions + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/unique.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree-borrows/write-during-2phase.rs b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs similarity index 90% rename from src/tools/miri/tests/fail/tree-borrows/write-during-2phase.rs rename to src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs index 6695d36306bc3..3f8c9b6219a2e 100644 --- a/src/tools/miri/tests/fail/tree-borrows/write-during-2phase.rs +++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.rs @@ -9,7 +9,7 @@ struct Foo(u64); impl Foo { #[rustfmt::skip] // rustfmt is wrong about which line contains an error - fn add(&mut self, n: u64) -> u64 { //~ ERROR: /read access through .* is forbidden/ + fn add(&mut self, n: u64) -> u64 { //~ ERROR: /reborrow through .* is forbidden/ self.0 + n } } diff --git a/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr new file mode 100644 index 0000000000000..3a82d028704b7 --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/write-during-2phase.stderr @@ -0,0 +1,38 @@ +error: Undefined Behavior: reborrow through is forbidden + --> $DIR/write-during-2phase.rs:LL:CC + | +LL | fn add(&mut self, n: u64) -> u64 { + | ^^^^^^^^^ reborrow through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag has state Disabled which forbids this reborrow (acting as a child read access) +help: the accessed tag was created here, in the initial state Reserved + --> $DIR/write-during-2phase.rs:LL:CC + | +LL | let _res = f.add(unsafe { + | ^ +help: the accessed tag later transitioned to Disabled due to a foreign write access at offsets [0x0..0x8] + --> $DIR/write-during-2phase.rs:LL:CC + | +LL | *inner = 42; + | ^^^^^^^^^^^ + = help: this transition corresponds to a loss of read and write permissions + = note: BACKTRACE (of the first span): + = note: inside `Foo::add` at $DIR/write-during-2phase.rs:LL:CC +note: inside `main` + --> $DIR/write-during-2phase.rs:LL:CC + | +LL | let _res = f.add(unsafe { + | ________________^ +LL | | let n = f.0; +LL | | // This is the access at fault, but it's not immediately apparent because +LL | | // the reference that got invalidated is not under a Protector. +LL | | *inner = 42; +LL | | n +LL | | }); + | |______^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr b/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr new file mode 100644 index 0000000000000..0e2002c34f12f --- /dev/null +++ b/src/tools/miri/tests/fail/tree_borrows/write_to_shr.stderr @@ -0,0 +1,26 @@ +error: Undefined Behavior: write access through is forbidden + --> $DIR/write_to_shr.rs:LL:CC + | +LL | *xmut = 31; + | ^^^^^^^^^^ write access through is forbidden + | + = help: this indicates a potential bug in the program: it performed an invalid operation, but the Tree Borrows rules it violated are still experimental + = help: the accessed tag is a child of the conflicting tag + = help: the conflicting tag has state Frozen which forbids child write accesses +help: the accessed tag was created here + --> $DIR/write_to_shr.rs:LL:CC + | +LL | let xmut = unsafe { &mut *(xref as *const u64 as *mut u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: the conflicting tag was created here, in the initial state Frozen + --> $DIR/write_to_shr.rs:LL:CC + | +LL | let xref = unsafe { &*(x as *mut u64) }; + | ^^^^^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): + = note: inside `main` at $DIR/write_to_shr.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs index 6d31ded75c690..b5a9b2bf18ee3 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs +++ b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.rs @@ -1,5 +1,6 @@ -// should find the bug even without validation and stacked borrows, but gets masked by optimizations -//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows -Zmir-opt-level=0 -Cdebug-assertions=no +// should find the bug even without, but gets masked by optimizations +//@compile-flags: -Zmiri-disable-stacked-borrows -Cdebug-assertions=no +//@normalize-stderr-test: "but found [0-9]+" -> "but found $$ALIGN" #[repr(align(256))] #[derive(Debug)] @@ -19,6 +20,6 @@ fn main() { (&mut ptr as *mut _ as *mut *const u8).write(&buf as *const _ as *const u8); } // Re-borrow that. This should be UB. - let _ptr = &*ptr; //~ERROR: alignment 256 is required + let _ptr = &*ptr; //~ERROR: required 256 byte alignment } } diff --git a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr index a900b46612b8a..503721b955109 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr +++ b/src/tools/miri/tests/fail/unaligned_pointers/dyn_alignment.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required +error: Undefined Behavior: constructing invalid value: encountered an unaligned reference (required 256 byte alignment but found $ALIGN) --> $DIR/dyn_alignment.rs:LL:CC | LL | let _ptr = &*ptr; - | ^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | ^^^^^ constructing invalid value: encountered an unaligned reference (required 256 byte alignment but found $ALIGN) | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/unaligned_pointers/field_requires_parent_struct_alignment.rs b/src/tools/miri/tests/fail/unaligned_pointers/field_requires_parent_struct_alignment.rs new file mode 100644 index 0000000000000..fa1812adc2961 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/field_requires_parent_struct_alignment.rs @@ -0,0 +1,24 @@ +/// This tests that when a field sits at offset 0 in a 4-aligned struct, accessing the field +/// requires alignment 4 even if the field type has lower alignment requirements. + +#[repr(C)] +pub struct S { + x: u8, + y: u32, +} + +unsafe fn foo(x: *const S) -> u8 { + unsafe { (*x).x } //~ERROR: accessing memory with alignment 1, but alignment 4 is required +} + +fn main() { + unsafe { + let mem = [0u64; 16]; + let odd_ptr = std::ptr::addr_of!(mem).cast::().add(1); + // `odd_ptr` is now not aligned enough for `S`. + // If accessing field `x` can exploit that it is at offset 0 + // in a 4-aligned struct, that field access requires alignment 4, + // thus making this UB. + foo(odd_ptr.cast()); + } +} diff --git a/src/tools/miri/tests/fail/unaligned_pointers/field_requires_parent_struct_alignment.stderr b/src/tools/miri/tests/fail/unaligned_pointers/field_requires_parent_struct_alignment.stderr new file mode 100644 index 0000000000000..0f030a6e27ce0 --- /dev/null +++ b/src/tools/miri/tests/fail/unaligned_pointers/field_requires_parent_struct_alignment.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: accessing memory with alignment ALIGN, but alignment ALIGN is required + --> $DIR/field_requires_parent_struct_alignment.rs:LL:CC + | +LL | unsafe { (*x).x } + | ^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `foo` at $DIR/field_requires_parent_struct_alignment.rs:LL:CC +note: inside `main` + --> $DIR/field_requires_parent_struct_alignment.rs:LL:CC + | +LL | foo(odd_ptr.cast()); + | ^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs index 04dbe3fd8d497..289536287a90e 100644 --- a/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs +++ b/src/tools/miri/tests/fail/unaligned_pointers/unaligned_ptr_zst.rs @@ -1,6 +1,5 @@ // This should fail even without validation -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 -Zmiri-disable-validation -Cdebug-assertions=no +//@compile-flags: -Zmiri-disable-validation -Cdebug-assertions=no fn main() { // Try many times as this might work by chance. diff --git a/src/tools/miri/tests/fail/uninit_buffer.stderr b/src/tools/miri/tests/fail/uninit_buffer.stderr index 8da532cfff05b..90210bf7f11a4 100644 --- a/src/tools/miri/tests/fail/uninit_buffer.stderr +++ b/src/tools/miri/tests/fail/uninit_buffer.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory --> RUSTLIB/core/src/slice/cmp.rs:LL:CC | -LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory +LL | let mut order = unsafe { compare_bytes(left.as_ptr(), right.as_ptr(), len) as isize }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x10], but memory is uninitialized at [0x4..0x10], and this operation requires initialized memory | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/uninit_buffer_with_provenance.stderr b/src/tools/miri/tests/fail/uninit_buffer_with_provenance.stderr index 210fc8e109aa4..701ec6013696f 100644 --- a/src/tools/miri/tests/fail/uninit_buffer_with_provenance.stderr +++ b/src/tools/miri/tests/fail/uninit_buffer_with_provenance.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory --> RUSTLIB/core/src/slice/cmp.rs:LL:CC | -LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory +LL | let mut order = unsafe { compare_bytes(left.as_ptr(), right.as_ptr(), len) as isize }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/unwind-action-terminate.stderr b/src/tools/miri/tests/fail/unwind-action-terminate.stderr index 52a1879cb5fef..daa4a808df9ce 100644 --- a/src/tools/miri/tests/fail/unwind-action-terminate.stderr +++ b/src/tools/miri/tests/fail/unwind-action-terminate.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'explicit panic', $DIR/unwind-action-terminate.rs:LL:CC +thread 'main' panicked at $DIR/unwind-action-terminate.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: abnormal termination: panic in a function that cannot unwind --> $DIR/unwind-action-terminate.rs:LL:CC diff --git a/src/tools/miri/tests/fail/validity/nonzero.rs b/src/tools/miri/tests/fail/validity/nonzero.rs index 384c94a556998..7cba90bc15d13 100644 --- a/src/tools/miri/tests/fail/validity/nonzero.rs +++ b/src/tools/miri/tests/fail/validity/nonzero.rs @@ -1,5 +1,3 @@ -// gets masked by optimizations -//@compile-flags: -Zmir-opt-level=0 #![feature(rustc_attrs)] #![allow(unused_attributes)] diff --git a/src/tools/miri/tests/fail/validity/uninit_float.stderr b/src/tools/miri/tests/fail/validity/uninit_float.stderr index 677a0fc5570d7..e95e3b18128d2 100644 --- a/src/tools/miri/tests/fail/validity/uninit_float.stderr +++ b/src/tools/miri/tests/fail/validity/uninit_float.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized bytes +error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized memory, but expected a floating point number --> $DIR/uninit_float.rs:LL:CC | LL | let _val: [f32; 1] = unsafe { std::mem::uninitialized() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized bytes + | ^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized memory, but expected a floating point number | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/validity/uninit_integer.stderr b/src/tools/miri/tests/fail/validity/uninit_integer.stderr index a9ac2a6dc67e7..2156886cd71c1 100644 --- a/src/tools/miri/tests/fail/validity/uninit_integer.stderr +++ b/src/tools/miri/tests/fail/validity/uninit_integer.stderr @@ -1,8 +1,8 @@ -error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized bytes +error: Undefined Behavior: constructing invalid value at .value[0]: encountered uninitialized memory, but expected an integer --> $DIR/uninit_integer.rs:LL:CC | LL | let _val = unsafe { std::mem::MaybeUninit::<[usize; 1]>::uninit().assume_init() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized bytes + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .value[0]: encountered uninitialized memory, but expected an integer | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information diff --git a/src/tools/miri/tests/fail/zst2.rs b/src/tools/miri/tests/fail/zst2.rs index 82470866f179f..04218c264a3ef 100644 --- a/src/tools/miri/tests/fail/zst2.rs +++ b/src/tools/miri/tests/fail/zst2.rs @@ -1,6 +1,3 @@ -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 - fn main() { // Not using the () type here, as writes of that type do not even have MIR generated. // Also not assigning directly as that's array initialization, not assignment. @@ -11,5 +8,5 @@ fn main() { let mut x_box = Box::new(1u8); let x = &mut *x_box as *mut _ as *mut [u8; 0]; drop(x_box); - unsafe { *x = zst_val }; //~ ERROR: dereferenced after this allocation got freed + unsafe { *x = zst_val }; //~ ERROR: has been freed } diff --git a/src/tools/miri/tests/fail/zst2.stderr b/src/tools/miri/tests/fail/zst2.stderr index 6c49656e4c67d..49954b1fd143c 100644 --- a/src/tools/miri/tests/fail/zst2.stderr +++ b/src/tools/miri/tests/fail/zst2.stderr @@ -1,12 +1,22 @@ -error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed +error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling --> $DIR/zst2.rs:LL:CC | LL | unsafe { *x = zst_val }; - | ^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed + | ^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/zst2.rs:LL:CC + | +LL | let mut x_box = Box::new(1u8); + | ^^^^^^^^^^^^^ +help: ALLOC was deallocated here: + --> $DIR/zst2.rs:LL:CC + | +LL | drop(x_box); + | ^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/zst2.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/fail/zst3.rs b/src/tools/miri/tests/fail/zst3.rs index a511f38998feb..454bef25f2234 100644 --- a/src/tools/miri/tests/fail/zst3.rs +++ b/src/tools/miri/tests/fail/zst3.rs @@ -1,6 +1,3 @@ -// Some optimizations remove ZST accesses, thus masking this UB. -//@compile-flags: -Zmir-opt-level=0 - fn main() { // Not using the () type here, as writes of that type do not even have MIR generated. // Also not assigning directly as that's array initialization, not assignment. diff --git a/src/tools/miri/tests/fail/zst3.stderr b/src/tools/miri/tests/fail/zst3.stderr index c9accf2c8fbeb..b62aef675d20d 100644 --- a/src/tools/miri/tests/fail/zst3.stderr +++ b/src/tools/miri/tests/fail/zst3.stderr @@ -6,7 +6,12 @@ LL | unsafe { *(x as *mut [u8; 0]) = zst_val }; | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information - = note: BACKTRACE: +help: ALLOC was allocated here: + --> $DIR/zst3.rs:LL:CC + | +LL | let mut x_box = Box::new(1u8); + | ^^^^^^^^^^^^^ + = note: BACKTRACE (of the first span): = note: inside `main` at $DIR/zst3.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/panic/div-by-zero-2.stderr b/src/tools/miri/tests/panic/div-by-zero-2.stderr index 538d87113654d..0f088ddd1a544 100644 --- a/src/tools/miri/tests/panic/div-by-zero-2.stderr +++ b/src/tools/miri/tests/panic/div-by-zero-2.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'attempt to divide by zero', $DIR/div-by-zero-2.rs:LL:CC +thread 'main' panicked at $DIR/div-by-zero-2.rs:LL:CC: +attempt to divide by zero note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr index bff897775e8f6..acc0e58885f23 100644 --- a/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr +++ b/src/tools/miri/tests/panic/function_calls/exported_symbol_good_unwind.stderr @@ -1,4 +1,7 @@ -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC +thread 'main' panicked at $DIR/exported_symbol_good_unwind.rs:LL:CC: +explicit panic note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC -thread 'main' panicked at 'explicit panic', $DIR/exported_symbol_good_unwind.rs:LL:CC +thread 'main' panicked at $DIR/exported_symbol_good_unwind.rs:LL:CC: +explicit panic +thread 'main' panicked at $DIR/exported_symbol_good_unwind.rs:LL:CC: +explicit panic diff --git a/src/tools/miri/tests/panic/oob_subslice.stderr b/src/tools/miri/tests/panic/oob_subslice.stderr index 4c6deaeccf615..2bccb60352e0a 100644 --- a/src/tools/miri/tests/panic/oob_subslice.stderr +++ b/src/tools/miri/tests/panic/oob_subslice.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'range end index 5 out of range for slice of length 4', $DIR/oob_subslice.rs:LL:CC +thread 'main' panicked at $DIR/oob_subslice.rs:LL:CC: +range end index 5 out of range for slice of length 4 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr b/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr index 21e434d873f7b..2cff81c58af10 100644 --- a/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr +++ b/src/tools/miri/tests/panic/overflowing-lsh-neg.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'attempt to shift left with overflow', $DIR/overflowing-lsh-neg.rs:LL:CC +thread 'main' panicked at $DIR/overflowing-lsh-neg.rs:LL:CC: +attempt to shift left with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/overflowing-rsh-1.stderr b/src/tools/miri/tests/panic/overflowing-rsh-1.stderr index fd04bf1bd4ec3..13117df5fc39a 100644 --- a/src/tools/miri/tests/panic/overflowing-rsh-1.stderr +++ b/src/tools/miri/tests/panic/overflowing-rsh-1.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'attempt to shift right with overflow', $DIR/overflowing-rsh-1.rs:LL:CC +thread 'main' panicked at $DIR/overflowing-rsh-1.rs:LL:CC: +attempt to shift right with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/overflowing-rsh-2.stderr b/src/tools/miri/tests/panic/overflowing-rsh-2.stderr index eb568e4d742bc..986a66f899188 100644 --- a/src/tools/miri/tests/panic/overflowing-rsh-2.stderr +++ b/src/tools/miri/tests/panic/overflowing-rsh-2.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'attempt to shift right with overflow', $DIR/overflowing-rsh-2.rs:LL:CC +thread 'main' panicked at $DIR/overflowing-rsh-2.rs:LL:CC: +attempt to shift right with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/panic1.stderr b/src/tools/miri/tests/panic/panic1.stderr index 0f4535e6792e6..4eb4244d7472c 100644 --- a/src/tools/miri/tests/panic/panic1.stderr +++ b/src/tools/miri/tests/panic/panic1.stderr @@ -1,4 +1,5 @@ -thread 'main' panicked at 'panicking from libstd', $DIR/panic1.rs:LL:CC +thread 'main' panicked at $DIR/panic1.rs:LL:CC: +panicking from libstd stack backtrace: 0: std::panicking::begin_panic_handler at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/panic/panic2.stderr b/src/tools/miri/tests/panic/panic2.stderr index c192ca3f64c36..bee1af2bef662 100644 --- a/src/tools/miri/tests/panic/panic2.stderr +++ b/src/tools/miri/tests/panic/panic2.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at '42-panicking from libstd', $DIR/panic2.rs:LL:CC +thread 'main' panicked at $DIR/panic2.rs:LL:CC: +42-panicking from libstd note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/panic3.stderr b/src/tools/miri/tests/panic/panic3.stderr index 0ce4a37fd5197..8dac000d291bc 100644 --- a/src/tools/miri/tests/panic/panic3.stderr +++ b/src/tools/miri/tests/panic/panic3.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'panicking from libcore', $DIR/panic3.rs:LL:CC +thread 'main' panicked at $DIR/panic3.rs:LL:CC: +panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/panic4.stderr b/src/tools/miri/tests/panic/panic4.stderr index 82df953b61c03..13437fe7f0751 100644 --- a/src/tools/miri/tests/panic/panic4.stderr +++ b/src/tools/miri/tests/panic/panic4.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at '42-panicking from libcore', $DIR/panic4.rs:LL:CC +thread 'main' panicked at $DIR/panic4.rs:LL:CC: +42-panicking from libcore note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/transmute_fat2.stderr b/src/tools/miri/tests/panic/transmute_fat2.stderr index f497ab672550f..1f09a2c1a082c 100644 --- a/src/tools/miri/tests/panic/transmute_fat2.stderr +++ b/src/tools/miri/tests/panic/transmute_fat2.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 0', $DIR/transmute_fat2.rs:LL:CC +thread 'main' panicked at $DIR/transmute_fat2.rs:LL:CC: +index out of bounds: the len is 0 but the index is 0 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/unsupported_foreign_function.stderr b/src/tools/miri/tests/panic/unsupported_foreign_function.stderr index a49dbdae58a6f..4db1e2909618a 100644 --- a/src/tools/miri/tests/panic/unsupported_foreign_function.stderr +++ b/src/tools/miri/tests/panic/unsupported_foreign_function.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'unsupported Miri functionality: can't call foreign function `foo` on $OS', $DIR/unsupported_foreign_function.rs:LL:CC +thread 'main' panicked at $DIR/unsupported_foreign_function.rs:LL:CC: +unsupported Miri functionality: can't call foreign function `foo` on $OS note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/panic/unsupported_syscall.stderr b/src/tools/miri/tests/panic/unsupported_syscall.stderr index 90aa5a9073638..40ba6671d5b6d 100644 --- a/src/tools/miri/tests/panic/unsupported_syscall.stderr +++ b/src/tools/miri/tests/panic/unsupported_syscall.stderr @@ -1,2 +1,3 @@ -thread 'main' panicked at 'unsupported Miri functionality: can't execute syscall with ID 0', $DIR/unsupported_syscall.rs:LL:CC +thread 'main' panicked at $DIR/unsupported_syscall.rs:LL:CC: +unsupported Miri functionality: can't execute syscall with ID 0 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/tools/miri/tests/pass-dep/shims/libc-fs.rs b/src/tools/miri/tests/pass-dep/shims/libc-fs.rs index fbdf27688a9c8..767a4fdbede38 100644 --- a/src/tools/miri/tests/pass-dep/shims/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/shims/libc-fs.rs @@ -5,12 +5,15 @@ #![feature(io_error_uncategorized)] use std::convert::TryInto; -use std::ffi::{c_char, CStr, CString}; +use std::ffi::CString; use std::fs::{canonicalize, remove_dir_all, remove_file, File}; use std::io::{Error, ErrorKind, Write}; use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; +#[path = "../../utils/mod.rs"] +mod utils; + fn main() { test_dup_stdout_stderr(); test_canonicalize_too_long(); @@ -22,31 +25,9 @@ fn main() { test_o_tmpfile_flag(); } -fn tmp() -> PathBuf { - let path = std::env::var("MIRI_TEMP") - .unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap()); - // These are host paths. We need to convert them to the target. - let path = CString::new(path).unwrap(); - let mut out = Vec::with_capacity(1024); - - unsafe { - extern "Rust" { - fn miri_host_to_target_path( - path: *const c_char, - out: *mut c_char, - out_size: usize, - ) -> usize; - } - let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity()); - assert_eq!(ret, 0); - let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap(); - PathBuf::from(out) - } -} - /// Prepare: compute filename and make sure the file does not exist. fn prepare(filename: &str) -> PathBuf { - let path = tmp().join(filename); + let path = utils::tmp().join(filename); // Clean the paths for robustness. remove_file(&path).ok(); path @@ -55,7 +36,7 @@ fn prepare(filename: &str) -> PathBuf { /// Prepare directory: compute directory name and make sure it does not exist. #[allow(unused)] fn prepare_dir(dirname: &str) -> PathBuf { - let path = tmp().join(&dirname); + let path = utils::tmp().join(&dirname); // Clean the directory for robustness. remove_dir_all(&path).ok(); path diff --git a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs index 68504cb1c794f..ebfeb863abfd9 100644 --- a/src/tools/miri/tests/pass-dep/shims/libc-misc.rs +++ b/src/tools/miri/tests/pass-dep/shims/libc-misc.rs @@ -6,29 +6,8 @@ use std::fs::{remove_file, File}; use std::os::unix::io::AsRawFd; use std::path::PathBuf; -fn tmp() -> PathBuf { - use std::ffi::{c_char, CStr, CString}; - - let path = std::env::var("MIRI_TEMP") - .unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap()); - // These are host paths. We need to convert them to the target. - let path = CString::new(path).unwrap(); - let mut out = Vec::with_capacity(1024); - - unsafe { - extern "Rust" { - fn miri_host_to_target_path( - path: *const c_char, - out: *mut c_char, - out_size: usize, - ) -> usize; - } - let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity()); - assert_eq!(ret, 0); - let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap(); - PathBuf::from(out) - } -} +#[path = "../../utils/mod.rs"] +mod utils; /// Test allocating variant of `realpath`. fn test_posix_realpath_alloc() { @@ -38,7 +17,7 @@ fn test_posix_realpath_alloc() { use std::os::unix::ffi::OsStringExt; let buf; - let path = tmp().join("miri_test_libc_posix_realpath_alloc"); + let path = utils::tmp().join("miri_test_libc_posix_realpath_alloc"); let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); // Cleanup before test. @@ -63,7 +42,7 @@ fn test_posix_realpath_noalloc() { use std::ffi::{CStr, CString}; use std::os::unix::ffi::OsStrExt; - let path = tmp().join("miri_test_libc_posix_realpath_noalloc"); + let path = utils::tmp().join("miri_test_libc_posix_realpath_noalloc"); let c_path = CString::new(path.as_os_str().as_bytes()).expect("CString::new failed"); let mut v = vec![0; libc::PATH_MAX as usize]; @@ -103,7 +82,7 @@ fn test_posix_realpath_errors() { fn test_posix_fadvise() { use std::io::Write; - let path = tmp().join("miri_test_libc_posix_fadvise.txt"); + let path = utils::tmp().join("miri_test_libc_posix_fadvise.txt"); // Cleanup before test remove_file(&path).ok(); @@ -130,7 +109,7 @@ fn test_posix_fadvise() { fn test_sync_file_range() { use std::io::Write; - let path = tmp().join("miri_test_libc_sync_file_range.txt"); + let path = utils::tmp().join("miri_test_libc_sync_file_range.txt"); // Cleanup before test. remove_file(&path).ok(); @@ -243,7 +222,7 @@ fn test_isatty() { libc::isatty(libc::STDERR_FILENO); // But when we open a file, it is definitely not a TTY. - let path = tmp().join("notatty.txt"); + let path = utils::tmp().join("notatty.txt"); // Cleanup before test. remove_file(&path).ok(); let file = File::create(&path).unwrap(); diff --git a/src/tools/miri/tests/pass-dep/shims/mmap.rs b/src/tools/miri/tests/pass-dep/shims/mmap.rs new file mode 100644 index 0000000000000..03ffc3b38986f --- /dev/null +++ b/src/tools/miri/tests/pass-dep/shims/mmap.rs @@ -0,0 +1,70 @@ +//@ignore-target-windows: No libc on Windows +//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance +#![feature(strict_provenance)] + +use std::{ptr, slice}; + +fn test_mmap() { + let page_size = page_size::get(); + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) + }; + assert!(!ptr.is_null()); + + // Ensure that freshly mapped allocations are zeroed + let slice = unsafe { slice::from_raw_parts_mut(ptr as *mut u8, page_size) }; + assert!(slice.iter().all(|b| *b == 0)); + + // Do some writes, make sure they worked + for b in slice.iter_mut() { + *b = 1; + } + assert!(slice.iter().all(|b| *b == 1)); + + // Ensure that we can munmap with just an integer + let just_an_address = ptr::invalid_mut(ptr.addr()); + let res = unsafe { libc::munmap(just_an_address, page_size) }; + assert_eq!(res, 0i32); +} + +#[cfg(target_os = "linux")] +fn test_mremap() { + let page_size = page_size::get(); + let ptr = unsafe { + libc::mmap( + ptr::null_mut(), + page_size, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ) + }; + let slice = unsafe { slice::from_raw_parts_mut(ptr as *mut u8, page_size) }; + for b in slice.iter_mut() { + *b = 1; + } + + let ptr = unsafe { libc::mremap(ptr, page_size, page_size * 2, libc::MREMAP_MAYMOVE) }; + assert!(!ptr.is_null()); + + let slice = unsafe { slice::from_raw_parts_mut(ptr as *mut u8, page_size * 2) }; + assert!(&slice[..page_size].iter().all(|b| *b == 1)); + assert!(&slice[page_size..].iter().all(|b| *b == 0)); + + let res = unsafe { libc::munmap(ptr, page_size * 2) }; + assert_eq!(res, 0i32); +} + +fn main() { + test_mmap(); + #[cfg(target_os = "linux")] + test_mremap(); +} diff --git a/src/tools/miri/tests/pass/align_repeat_into_packed_field.rs b/src/tools/miri/tests/pass/align_repeat_into_packed_field.rs new file mode 100644 index 0000000000000..fb028627d9d45 --- /dev/null +++ b/src/tools/miri/tests/pass/align_repeat_into_packed_field.rs @@ -0,0 +1,26 @@ +#![feature(custom_mir, core_intrinsics)] +use std::intrinsics::mir::*; + +#[repr(packed)] +struct S { + field: [u32; 2], +} + +#[custom_mir(dialect = "runtime", phase = "optimized")] +fn test() { + mir! { + let s: S; + { + // Store a repeat expression directly into a field of a packed struct. + s.field = [0; 2]; + Return() + } + } +} + +fn main() { + // Run this a bunch of time to make sure it doesn't pass by chance. + for _ in 0..20 { + test(); + } +} diff --git a/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs b/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs new file mode 100644 index 0000000000000..735251039f772 --- /dev/null +++ b/src/tools/miri/tests/pass/align_repeat_into_well_aligned_array.rs @@ -0,0 +1,35 @@ +//@compile-flags: -Zmiri-symbolic-alignment-check + +use std::mem::size_of; + +fn main() { + let mut a = Params::new(); + // The array itself here happens to be quite well-aligned, but not all its elements have that + // large alignment and we better make sure that is still accepted by Miri. + a.key_block = [0; BLOCKBYTES]; +} + +#[repr(C)] +#[derive(Clone)] +#[allow(unused)] +pub struct Params { + hash_length: u8, + key_length: u8, + key_block: [u8; BLOCKBYTES], + max_leaf_length: u32, +} + +pub const OUTBYTES: usize = 8 * size_of::(); +pub const KEYBYTES: usize = 8 * size_of::(); +pub const BLOCKBYTES: usize = 16 * size_of::(); + +impl Params { + pub fn new() -> Self { + Self { + hash_length: OUTBYTES as u8, + key_length: 0, + key_block: [0; BLOCKBYTES], + max_leaf_length: 0, + } + } +} diff --git a/src/tools/miri/tests/pass/align_strange_enum_discriminant_offset.rs b/src/tools/miri/tests/pass/align_strange_enum_discriminant_offset.rs new file mode 100644 index 0000000000000..e0d05e0b65abf --- /dev/null +++ b/src/tools/miri/tests/pass/align_strange_enum_discriminant_offset.rs @@ -0,0 +1,23 @@ +#![allow(unused)] + +#[repr(u16)] +enum DeviceKind { + Nil = 0, +} + +#[repr(C, packed)] +struct DeviceInfo { + endianness: u8, + device_kind: DeviceKind, +} + +fn main() { + // The layout of `Option<(DeviceInfo, u64)>` is funny: it uses the + // `DeviceKind` enum as niche, so that is offset 1, but the niche type is u16! + // So despite the type having alignment 8 and the field type alignment 2, + // the actual alignment is 1. + let x = None::<(DeviceInfo, u8)>; + let y = None::<(DeviceInfo, u16)>; + let z = None::<(DeviceInfo, u64)>; + format!("{} {} {}", x.is_some(), y.is_some(), y.is_some()); +} diff --git a/src/tools/miri/tests/pass/btreemap.rs b/src/tools/miri/tests/pass/btreemap.rs index b7c0406becc4d..1213f81a6f128 100644 --- a/src/tools/miri/tests/pass/btreemap.rs +++ b/src/tools/miri/tests/pass/btreemap.rs @@ -1,7 +1,7 @@ //@revisions: stack tree //@[tree]compile-flags: -Zmiri-tree-borrows //@compile-flags: -Zmiri-strict-provenance -#![feature(btree_drain_filter)] +#![feature(btree_extract_if)] use std::collections::{BTreeMap, BTreeSet}; use std::mem; @@ -49,8 +49,8 @@ pub fn main() { } test_all_refs(&mut 13, b.values_mut()); - // Test forgetting the drain. - let mut d = b.drain_filter(|_, i| *i < 30); + // Test forgetting the extractor. + let mut d = b.extract_if(|_, i| *i < 30); d.next().unwrap(); mem::forget(d); } diff --git a/src/tools/miri/tests/pass/concurrency/simple.stderr b/src/tools/miri/tests/pass/concurrency/simple.stderr index 028cc0fb736ff..33d6b6841ade6 100644 --- a/src/tools/miri/tests/pass/concurrency/simple.stderr +++ b/src/tools/miri/tests/pass/concurrency/simple.stderr @@ -1,3 +1,5 @@ -thread '' panicked at 'Hello!', $DIR/simple.rs:LL:CC +thread '' panicked at $DIR/simple.rs:LL:CC: +Hello! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -thread 'childthread' panicked at 'Hello, world!', $DIR/simple.rs:LL:CC +thread 'childthread' panicked at $DIR/simple.rs:LL:CC: +Hello, world! diff --git a/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs new file mode 100644 index 0000000000000..d410a875b1bea --- /dev/null +++ b/src/tools/miri/tests/pass/function_calls/return_place_on_heap.rs @@ -0,0 +1,25 @@ +#![feature(raw_ref_op)] +#![feature(core_intrinsics)] +#![feature(custom_mir)] + +use std::intrinsics::mir::*; + +// Make sure calls with the return place "on the heap" work. +#[custom_mir(dialect = "runtime", phase = "optimized")] +pub fn main() { + mir! { + { + let x = 0; + let ptr = &raw mut x; + Call(*ptr = myfun(), after_call) + } + + after_call = { + Return() + } + } +} + +fn myfun() -> i32 { + 13 +} diff --git a/src/tools/miri/tests/pass/function_calls/target_feature.rs b/src/tools/miri/tests/pass/function_calls/target_feature.rs new file mode 100644 index 0000000000000..0be86ba373165 --- /dev/null +++ b/src/tools/miri/tests/pass/function_calls/target_feature.rs @@ -0,0 +1,12 @@ +//@only-target-x86_64: uses x86 target features +//@compile-flags: -C target-feature=+ssse3 + +fn main() { + assert!(is_x86_feature_detected!("ssse3")); + unsafe { + ssse3_fn(); + } +} + +#[target_feature(enable = "ssse3")] +unsafe fn ssse3_fn() {} diff --git a/src/tools/miri/tests/pass/generator.rs b/src/tools/miri/tests/pass/generator.rs index e059c7114e395..20099603455d4 100644 --- a/src/tools/miri/tests/pass/generator.rs +++ b/src/tools/miri/tests/pass/generator.rs @@ -13,7 +13,7 @@ use std::ptr; use std::sync::atomic::{AtomicUsize, Ordering}; fn basic() { - fn finish(mut amt: usize, mut t: T) -> T::Return + fn finish(mut amt: usize, self_referential: bool, mut t: T) -> T::Return where T: Generator, { @@ -22,7 +22,10 @@ fn basic() { loop { let state = t.as_mut().resume(()); // Test if the generator is valid (according to type invariants). - let _ = unsafe { ManuallyDrop::new(ptr::read(t.as_mut().get_unchecked_mut())) }; + // For self-referential generators however this is UB! + if !self_referential { + let _ = unsafe { ManuallyDrop::new(ptr::read(t.as_mut().get_unchecked_mut())) }; + } match state { GeneratorState::Yielded(y) => { amt -= y; @@ -40,9 +43,9 @@ fn basic() { panic!() } - finish(1, || yield 1); + finish(1, false, || yield 1); - finish(3, || { + finish(3, false, || { let mut x = 0; yield 1; x += 1; @@ -52,27 +55,27 @@ fn basic() { assert_eq!(x, 2); }); - finish(7 * 8 / 2, || { + finish(7 * 8 / 2, false, || { for i in 0..8 { yield i; } }); - finish(1, || { + finish(1, false, || { if true { yield 1; } else { } }); - finish(1, || { + finish(1, false, || { if false { } else { yield 1; } }); - finish(2, || { + finish(2, false, || { if { yield 1; false @@ -83,9 +86,20 @@ fn basic() { yield 1; }); - // also test a self-referential generator + // also test self-referential generators + assert_eq!( + finish(5, true, static || { + let mut x = 5; + let y = &mut x; + *y = 5; + yield *y; + *y = 10; + x + }), + 10 + ); assert_eq!( - finish(5, || { + finish(5, true, || { let mut x = Box::new(5); let y = &mut *x; *y = 5; @@ -97,7 +111,7 @@ fn basic() { ); let b = true; - finish(1, || { + finish(1, false, || { yield 1; if b { return; @@ -109,7 +123,7 @@ fn basic() { drop(x); }); - finish(3, || { + finish(3, false, || { yield 1; #[allow(unreachable_code)] let _x: (String, !) = (String::new(), { diff --git a/src/tools/miri/tests/pass/intrinsics-math.rs b/src/tools/miri/tests/pass/intrinsics-math.rs index 9f2dc333f33ec..e0e4f5654d6a4 100644 --- a/src/tools/miri/tests/pass/intrinsics-math.rs +++ b/src/tools/miri/tests/pass/intrinsics-math.rs @@ -1,5 +1,4 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// SPDX-FileCopyrightText: The Rust Project Developers (see https://thanks.rust-lang.org) +#![feature(float_gamma)] macro_rules! assert_approx_eq { ($a:expr, $b:expr) => {{ @@ -130,4 +129,20 @@ pub fn main() { ); assert_approx_eq!(0.5f32.atanh(), 0.54930614433405484569762261846126285f32); assert_approx_eq!(0.5f64.atanh(), 0.54930614433405484569762261846126285f64); + + assert_approx_eq!(5.0f32.gamma(), 24.0); + assert_approx_eq!(5.0f64.gamma(), 24.0); + // These fail even on the host, precision seems to be terrible. + //assert_approx_eq!(-0.5f32.gamma(), -2.0 * f32::consts::PI.sqrt()); + //assert_approx_eq!(-0.5f64.gamma(), -2.0 * f64::consts::PI.sqrt()); + + assert_eq!(2.0f32.ln_gamma(), (0.0, 1)); + assert_eq!(2.0f64.ln_gamma(), (0.0, 1)); + // Gamma(-0.5) = -2*sqrt(π) + let (val, sign) = (-0.5f32).ln_gamma(); + assert_approx_eq!(val, (2.0 * f32::consts::PI.sqrt()).ln()); + assert_eq!(sign, -1); + let (val, sign) = (-0.5f64).ln_gamma(); + assert_approx_eq!(val, (2.0 * f64::consts::PI.sqrt()).ln()); + assert_eq!(sign, -1); } diff --git a/src/tools/miri/tests/pass/intrinsics-x86-sse.rs b/src/tools/miri/tests/pass/intrinsics-x86-sse.rs new file mode 100644 index 0000000000000..677d7cc030e8c --- /dev/null +++ b/src/tools/miri/tests/pass/intrinsics-x86-sse.rs @@ -0,0 +1,1075 @@ +//@only-target-x86_64 + +use std::arch::x86_64::*; +use std::f32::NAN; +use std::mem::transmute; + +fn main() { + assert!(is_x86_feature_detected!("sse")); + + unsafe { + test_sse(); + } +} + +macro_rules! assert_approx_eq { + ($a:expr, $b:expr, $eps:expr) => {{ + let (a, b) = (&$a, &$b); + assert!( + (*a - *b).abs() < $eps, + "assertion failed: `(left !== right)` \ + (left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`)", + *a, + *b, + $eps, + (*a - *b).abs() + ); + }}; +} + +#[target_feature(enable = "sse")] +unsafe fn test_sse() { + // Mostly copied from library/stdarch/crates/core_arch/src/x86{,_64}/sse.rs + + #[target_feature(enable = "sse")] + unsafe fn assert_eq_m128(a: __m128, b: __m128) { + let r = _mm_cmpeq_ps(a, b); + if _mm_movemask_ps(r) != 0b1111 { + panic!("{:?} != {:?}", a, b); + } + } + + #[target_feature(enable = "sse")] + unsafe fn test_mm_add_ss() { + let a = _mm_set_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_set_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_add_ss(a, b); + assert_eq_m128(r, _mm_set_ps(-1.0, 5.0, 0.0, -15.0)); + } + test_mm_add_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_sub_ss() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_sub_ss(a, b); + assert_eq_m128(r, _mm_setr_ps(99.0, 5.0, 0.0, -10.0)); + } + test_mm_sub_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_mul_ss() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_mul_ss(a, b); + assert_eq_m128(r, _mm_setr_ps(100.0, 5.0, 0.0, -10.0)); + } + test_mm_mul_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_div_ss() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_div_ss(a, b); + assert_eq_m128(r, _mm_setr_ps(0.01, 5.0, 0.0, -10.0)); + } + test_mm_div_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_sqrt_ss() { + let a = _mm_setr_ps(4.0, 13.0, 16.0, 100.0); + let r = _mm_sqrt_ss(a); + let e = _mm_setr_ps(2.0, 13.0, 16.0, 100.0); + assert_eq_m128(r, e); + } + test_mm_sqrt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_sqrt_ps() { + let a = _mm_setr_ps(4.0, 13.0, 16.0, 100.0); + let r = _mm_sqrt_ps(a); + let e = _mm_setr_ps(2.0, 3.6055512, 4.0, 10.0); + assert_eq_m128(r, e); + } + test_mm_sqrt_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_rcp_ss() { + let a = _mm_setr_ps(4.0, 13.0, 16.0, 100.0); + let r = _mm_rcp_ss(a); + let e = _mm_setr_ps(0.24993896, 13.0, 16.0, 100.0); + let rel_err = 0.00048828125; + + let r: [f32; 4] = transmute(r); + let e: [f32; 4] = transmute(e); + assert_approx_eq!(r[0], e[0], 2. * rel_err); + for i in 1..4 { + assert_eq!(r[i], e[i]); + } + } + test_mm_rcp_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_rcp_ps() { + let a = _mm_setr_ps(4.0, 13.0, 16.0, 100.0); + let r = _mm_rcp_ps(a); + let e = _mm_setr_ps(0.24993896, 0.0769043, 0.06248474, 0.0099983215); + let rel_err = 0.00048828125; + + let r: [f32; 4] = transmute(r); + let e: [f32; 4] = transmute(e); + for i in 0..4 { + assert_approx_eq!(r[i], e[i], 2. * rel_err); + } + } + test_mm_rcp_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_rsqrt_ss() { + let a = _mm_setr_ps(4.0, 13.0, 16.0, 100.0); + let r = _mm_rsqrt_ss(a); + let e = _mm_setr_ps(0.49987793, 13.0, 16.0, 100.0); + let rel_err = 0.00048828125; + + let r: [f32; 4] = transmute(r); + let e: [f32; 4] = transmute(e); + assert_approx_eq!(r[0], e[0], 2. * rel_err); + for i in 1..4 { + assert_eq!(r[i], e[i]); + } + } + test_mm_rsqrt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_rsqrt_ps() { + let a = _mm_setr_ps(4.0, 13.0, 16.0, 100.0); + let r = _mm_rsqrt_ps(a); + let e = _mm_setr_ps(0.49987793, 0.2772827, 0.24993896, 0.099990845); + let rel_err = 0.00048828125; + + let r: [f32; 4] = transmute(r); + let e: [f32; 4] = transmute(e); + for i in 0..4 { + assert_approx_eq!(r[i], e[i], 2. * rel_err); + } + } + test_mm_rsqrt_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_min_ss() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_min_ss(a, b); + assert_eq_m128(r, _mm_setr_ps(-100.0, 5.0, 0.0, -10.0)); + } + test_mm_min_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_min_ps() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_min_ps(a, b); + assert_eq_m128(r, _mm_setr_ps(-100.0, 5.0, 0.0, -10.0)); + + // `_mm_min_ps` can **not** be implemented using the `simd_min` rust intrinsic because + // the semantics of `simd_min` are different to those of `_mm_min_ps` regarding handling + // of `-0.0`. + let a = _mm_setr_ps(-0.0, 0.0, 0.0, 0.0); + let b = _mm_setr_ps(0.0, 0.0, 0.0, 0.0); + let r1: [u8; 16] = transmute(_mm_min_ps(a, b)); + let r2: [u8; 16] = transmute(_mm_min_ps(b, a)); + let a: [u8; 16] = transmute(a); + let b: [u8; 16] = transmute(b); + assert_eq!(r1, b); + assert_eq!(r2, a); + assert_ne!(a, b); // sanity check that -0.0 is actually present + } + test_mm_min_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_max_ss() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_max_ss(a, b); + assert_eq_m128(r, _mm_setr_ps(-1.0, 5.0, 0.0, -10.0)); + } + test_mm_max_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_max_ps() { + let a = _mm_setr_ps(-1.0, 5.0, 0.0, -10.0); + let b = _mm_setr_ps(-100.0, 20.0, 0.0, -5.0); + let r = _mm_max_ps(a, b); + assert_eq_m128(r, _mm_setr_ps(-1.0, 20.0, 0.0, -5.0)); + + // `_mm_max_ps` can **not** be implemented using the `simd_max` rust intrinsic because + // the semantics of `simd_max` are different to those of `_mm_max_ps` regarding handling + // of `-0.0`. + let a = _mm_setr_ps(-0.0, 0.0, 0.0, 0.0); + let b = _mm_setr_ps(0.0, 0.0, 0.0, 0.0); + let r1: [u8; 16] = transmute(_mm_max_ps(a, b)); + let r2: [u8; 16] = transmute(_mm_max_ps(b, a)); + let a: [u8; 16] = transmute(a); + let b: [u8; 16] = transmute(b); + assert_eq!(r1, b); + assert_eq!(r2, a); + assert_ne!(a, b); // sanity check that -0.0 is actually present + } + test_mm_max_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpeq_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(-1.0, 5.0, 6.0, 7.0); + let r: [u32; 4] = transmute(_mm_cmpeq_ss(a, b)); + let e: [u32; 4] = transmute(_mm_setr_ps(transmute(0u32), 2.0, 3.0, 4.0)); + assert_eq!(r, e); + + let b2 = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let r2: [u32; 4] = transmute(_mm_cmpeq_ss(a, b2)); + let e2: [u32; 4] = transmute(_mm_setr_ps(transmute(0xffffffffu32), 2.0, 3.0, 4.0)); + assert_eq!(r2, e2); + } + test_mm_cmpeq_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmplt_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = 0u32; // a.extract(0) < b.extract(0) + let c1 = 0u32; // a.extract(0) < c.extract(0) + let d1 = !0u32; // a.extract(0) < d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmplt_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmplt_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmplt_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmplt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmple_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = 0u32; // a.extract(0) <= b.extract(0) + let c1 = !0u32; // a.extract(0) <= c.extract(0) + let d1 = !0u32; // a.extract(0) <= d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmple_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmple_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmple_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmple_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpgt_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = !0u32; // a.extract(0) > b.extract(0) + let c1 = 0u32; // a.extract(0) > c.extract(0) + let d1 = 0u32; // a.extract(0) > d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpgt_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpgt_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpgt_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpgt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpge_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = !0u32; // a.extract(0) >= b.extract(0) + let c1 = !0u32; // a.extract(0) >= c.extract(0) + let d1 = 0u32; // a.extract(0) >= d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpge_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpge_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpge_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpge_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpneq_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = !0u32; // a.extract(0) != b.extract(0) + let c1 = 0u32; // a.extract(0) != c.extract(0) + let d1 = !0u32; // a.extract(0) != d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpneq_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpneq_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpneq_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpneq_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpnlt_ss() { + // TODO: this test is exactly the same as for `_mm_cmpge_ss`, but there + // must be a difference. It may have to do with behavior in the + // presence of NaNs (signaling or quiet). If so, we should add tests + // for those. + + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = !0u32; // a.extract(0) >= b.extract(0) + let c1 = !0u32; // a.extract(0) >= c.extract(0) + let d1 = 0u32; // a.extract(0) >= d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpnlt_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpnlt_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpnlt_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpnlt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpnle_ss() { + // TODO: this test is exactly the same as for `_mm_cmpgt_ss`, but there + // must be a difference. It may have to do with behavior in the + // presence + // of NaNs (signaling or quiet). If so, we should add tests for those. + + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = !0u32; // a.extract(0) > b.extract(0) + let c1 = 0u32; // a.extract(0) > c.extract(0) + let d1 = 0u32; // a.extract(0) > d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpnle_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpnle_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpnle_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpnle_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpngt_ss() { + // TODO: this test is exactly the same as for `_mm_cmple_ss`, but there + // must be a difference. It may have to do with behavior in the + // presence of NaNs (signaling or quiet). If so, we should add tests + // for those. + + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = 0u32; // a.extract(0) <= b.extract(0) + let c1 = !0u32; // a.extract(0) <= c.extract(0) + let d1 = !0u32; // a.extract(0) <= d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpngt_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpngt_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpngt_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpngt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpnge_ss() { + // TODO: this test is exactly the same as for `_mm_cmplt_ss`, but there + // must be a difference. It may have to do with behavior in the + // presence of NaNs (signaling or quiet). If so, we should add tests + // for those. + + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(1.0, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = 0u32; // a.extract(0) < b.extract(0) + let c1 = 0u32; // a.extract(0) < c.extract(0) + let d1 = !0u32; // a.extract(0) < d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpnge_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpnge_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpnge_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpnge_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpord_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(NAN, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = !0u32; // a.extract(0) ord b.extract(0) + let c1 = 0u32; // a.extract(0) ord c.extract(0) + let d1 = !0u32; // a.extract(0) ord d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpord_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpord_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpord_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpord_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpunord_ss() { + let a = _mm_setr_ps(1.0, 2.0, 3.0, 4.0); + let b = _mm_setr_ps(0.0, 5.0, 6.0, 7.0); + let c = _mm_setr_ps(NAN, 5.0, 6.0, 7.0); + let d = _mm_setr_ps(2.0, 5.0, 6.0, 7.0); + + let b1 = 0u32; // a.extract(0) unord b.extract(0) + let c1 = !0u32; // a.extract(0) unord c.extract(0) + let d1 = 0u32; // a.extract(0) unord d.extract(0) + + let rb: [u32; 4] = transmute(_mm_cmpunord_ss(a, b)); + let eb: [u32; 4] = transmute(_mm_setr_ps(transmute(b1), 2.0, 3.0, 4.0)); + assert_eq!(rb, eb); + + let rc: [u32; 4] = transmute(_mm_cmpunord_ss(a, c)); + let ec: [u32; 4] = transmute(_mm_setr_ps(transmute(c1), 2.0, 3.0, 4.0)); + assert_eq!(rc, ec); + + let rd: [u32; 4] = transmute(_mm_cmpunord_ss(a, d)); + let ed: [u32; 4] = transmute(_mm_setr_ps(transmute(d1), 2.0, 3.0, 4.0)); + assert_eq!(rd, ed); + } + test_mm_cmpunord_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpeq_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, NAN); + let tru = !0u32; + let fls = 0u32; + + let e = [fls, fls, tru, fls]; + let r: [u32; 4] = transmute(_mm_cmpeq_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpeq_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmplt_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, NAN); + let tru = !0u32; + let fls = 0u32; + + let e = [tru, fls, fls, fls]; + let r: [u32; 4] = transmute(_mm_cmplt_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmplt_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmple_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, 4.0); + let b = _mm_setr_ps(15.0, 20.0, 1.0, NAN); + let tru = !0u32; + let fls = 0u32; + + let e = [tru, fls, tru, fls]; + let r: [u32; 4] = transmute(_mm_cmple_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmple_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpgt_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, 42.0); + let tru = !0u32; + let fls = 0u32; + + let e = [fls, tru, fls, fls]; + let r: [u32; 4] = transmute(_mm_cmpgt_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpgt_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpge_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, 42.0); + let tru = !0u32; + let fls = 0u32; + + let e = [fls, tru, tru, fls]; + let r: [u32; 4] = transmute(_mm_cmpge_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpge_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpneq_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, NAN); + let tru = !0u32; + let fls = 0u32; + + let e = [tru, tru, fls, tru]; + let r: [u32; 4] = transmute(_mm_cmpneq_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpneq_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpnlt_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, 5.0); + let tru = !0u32; + let fls = 0u32; + + let e = [fls, tru, tru, tru]; + let r: [u32; 4] = transmute(_mm_cmpnlt_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpnlt_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpnle_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, 5.0); + let tru = !0u32; + let fls = 0u32; + + let e = [fls, tru, fls, tru]; + let r: [u32; 4] = transmute(_mm_cmpnle_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpnle_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpngt_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, 5.0); + let tru = !0u32; + let fls = 0u32; + + let e = [tru, fls, tru, tru]; + let r: [u32; 4] = transmute(_mm_cmpngt_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpngt_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpnge_ps() { + let a = _mm_setr_ps(10.0, 50.0, 1.0, NAN); + let b = _mm_setr_ps(15.0, 20.0, 1.0, 5.0); + let tru = !0u32; + let fls = 0u32; + + let e = [tru, fls, fls, tru]; + let r: [u32; 4] = transmute(_mm_cmpnge_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpnge_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpord_ps() { + let a = _mm_setr_ps(10.0, 50.0, NAN, NAN); + let b = _mm_setr_ps(15.0, NAN, 1.0, NAN); + let tru = !0u32; + let fls = 0u32; + + let e = [tru, fls, fls, fls]; + let r: [u32; 4] = transmute(_mm_cmpord_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpord_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cmpunord_ps() { + let a = _mm_setr_ps(10.0, 50.0, NAN, NAN); + let b = _mm_setr_ps(15.0, NAN, 1.0, NAN); + let tru = !0u32; + let fls = 0u32; + + let e = [fls, tru, tru, tru]; + let r: [u32; 4] = transmute(_mm_cmpunord_ps(a, b)); + assert_eq!(r, e); + } + test_mm_cmpunord_ps(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_comieq_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[1i32, 0, 0, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_comieq_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_comieq_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_comieq_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_comilt_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[0i32, 1, 0, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_comilt_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_comilt_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_comilt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_comile_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[1i32, 1, 0, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_comile_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_comile_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_comile_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_comigt_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[1i32, 0, 1, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_comige_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_comige_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_comigt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_comineq_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[0i32, 1, 1, 1]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_comineq_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_comineq_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_comineq_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_ucomieq_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[1i32, 0, 0, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_ucomieq_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_ucomieq_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_ucomieq_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_ucomilt_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[0i32, 1, 0, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_ucomilt_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_ucomilt_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_ucomilt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_ucomile_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[1i32, 1, 0, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_ucomile_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_ucomile_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_ucomile_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_ucomigt_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[0i32, 0, 1, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_ucomigt_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_ucomigt_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_ucomigt_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_ucomige_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[1i32, 0, 1, 0]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_ucomige_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_ucomige_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_ucomige_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_ucomineq_ss() { + let aa = &[3.0f32, 12.0, 23.0, NAN]; + let bb = &[3.0f32, 47.5, 1.5, NAN]; + + let ee = &[0i32, 1, 1, 1]; + + for i in 0..4 { + let a = _mm_setr_ps(aa[i], 1.0, 2.0, 3.0); + let b = _mm_setr_ps(bb[i], 0.0, 2.0, 4.0); + + let r = _mm_ucomineq_ss(a, b); + + assert_eq!( + ee[i], r, + "_mm_ucomineq_ss({:?}, {:?}) = {}, expected: {} (i={})", + a, b, r, ee[i], i + ); + } + } + test_mm_ucomineq_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvtss_si32() { + let inputs = &[42.0f32, -3.1, 4.0e10, 4.0e-20, NAN, 2147483500.1]; + let result = &[42i32, -3, i32::MIN, 0, i32::MIN, 2147483520]; + for i in 0..inputs.len() { + let x = _mm_setr_ps(inputs[i], 1.0, 3.0, 4.0); + let e = result[i]; + let r = _mm_cvtss_si32(x); + assert_eq!(e, r, "TestCase #{} _mm_cvtss_si32({:?}) = {}, expected: {}", i, x, r, e); + } + } + test_mm_cvtss_si32(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvttss_si32() { + let inputs = &[ + (42.0f32, 42i32), + (-31.4, -31), + (-33.5, -33), + (-34.5, -34), + (10.999, 10), + (-5.99, -5), + (4.0e10, i32::MIN), + (4.0e-10, 0), + (NAN, i32::MIN), + (2147483500.1, 2147483520), + ]; + for i in 0..inputs.len() { + let (xi, e) = inputs[i]; + let x = _mm_setr_ps(xi, 1.0, 3.0, 4.0); + let r = _mm_cvttss_si32(x); + assert_eq!(e, r, "TestCase #{} _mm_cvttss_si32({:?}) = {}, expected: {}", i, x, r, e); + } + } + test_mm_cvttss_si32(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvtss_f32() { + let a = _mm_setr_ps(312.0134, 5.0, 6.0, 7.0); + assert_eq!(_mm_cvtss_f32(a), 312.0134); + } + test_mm_cvtss_f32(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvtsi32_ss() { + let inputs = &[ + (4555i32, 4555.0f32), + (322223333, 322223330.0), + (-432, -432.0), + (-322223333, -322223330.0), + ]; + + for i in 0..inputs.len() { + let (x, f) = inputs[i]; + let a = _mm_setr_ps(5.0, 6.0, 7.0, 8.0); + let r = _mm_cvtsi32_ss(a, x); + let e = _mm_setr_ps(f, 6.0, 7.0, 8.0); + assert_eq_m128(e, r); + } + } + test_mm_cvtsi32_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvtss_si64() { + let inputs = &[ + (42.0f32, 42i64), + (-31.4, -31), + (-33.5, -34), + (-34.5, -34), + (4.0e10, 40_000_000_000), + (4.0e-10, 0), + (f32::NAN, i64::MIN), + (2147483500.1, 2147483520), + (9.223371e18, 9223370937343148032), + ]; + for i in 0..inputs.len() { + let (xi, e) = inputs[i]; + let x = _mm_setr_ps(xi, 1.0, 3.0, 4.0); + let r = _mm_cvtss_si64(x); + assert_eq!(e, r, "TestCase #{} _mm_cvtss_si64({:?}) = {}, expected: {}", i, x, r, e); + } + } + test_mm_cvtss_si64(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvttss_si64() { + let inputs = &[ + (42.0f32, 42i64), + (-31.4, -31), + (-33.5, -33), + (-34.5, -34), + (10.999, 10), + (-5.99, -5), + (4.0e10, 40_000_000_000), + (4.0e-10, 0), + (f32::NAN, i64::MIN), + (2147483500.1, 2147483520), + (9.223371e18, 9223370937343148032), + (9.223372e18, i64::MIN), + ]; + for i in 0..inputs.len() { + let (xi, e) = inputs[i]; + let x = _mm_setr_ps(xi, 1.0, 3.0, 4.0); + let r = _mm_cvttss_si64(x); + assert_eq!(e, r, "TestCase #{} _mm_cvttss_si64({:?}) = {}, expected: {}", i, x, r, e); + } + } + test_mm_cvttss_si64(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_cvtsi64_ss() { + let inputs = &[ + (4555i64, 4555.0f32), + (322223333, 322223330.0), + (-432, -432.0), + (-322223333, -322223330.0), + (9223372036854775807, 9.223372e18), + (-9223372036854775808, -9.223372e18), + ]; + + for i in 0..inputs.len() { + let (x, f) = inputs[i]; + let a = _mm_setr_ps(5.0, 6.0, 7.0, 8.0); + let r = _mm_cvtsi64_ss(a, x); + let e = _mm_setr_ps(f, 6.0, 7.0, 8.0); + assert_eq_m128(e, r); + } + } + test_mm_cvtsi64_ss(); + + #[target_feature(enable = "sse")] + unsafe fn test_mm_movemask_ps() { + let r = _mm_movemask_ps(_mm_setr_ps(-1.0, 5.0, -5.0, 0.0)); + assert_eq!(r, 0b0101); + + let r = _mm_movemask_ps(_mm_setr_ps(-1.0, -5.0, -5.0, 0.0)); + assert_eq!(r, 0b0111); + } + test_mm_movemask_ps(); + + let x = 0i8; + _mm_prefetch(&x, _MM_HINT_T0); + _mm_prefetch(&x, _MM_HINT_T1); + _mm_prefetch(&x, _MM_HINT_T2); + _mm_prefetch(&x, _MM_HINT_NTA); + _mm_prefetch(&x, _MM_HINT_ET0); + _mm_prefetch(&x, _MM_HINT_ET1); +} diff --git a/src/tools/miri/tests/pass/intrinsics.rs b/src/tools/miri/tests/pass/intrinsics.rs index 6267e6e6bc111..8c6eeab22195c 100644 --- a/src/tools/miri/tests/pass/intrinsics.rs +++ b/src/tools/miri/tests/pass/intrinsics.rs @@ -3,7 +3,7 @@ //! Tests for various intrinsics that do not fit anywhere else. use std::intrinsics; -use std::mem::{size_of, size_of_val, size_of_val_raw}; +use std::mem::{discriminant, size_of, size_of_val, size_of_val_raw}; struct Bomb; @@ -39,4 +39,7 @@ fn main() { let _v = intrinsics::discriminant_value(&0); let _v = intrinsics::discriminant_value(&true); let _v = intrinsics::discriminant_value(&vec![1, 2, 3]); + // Make sure that even if the discriminant is stored together with data, the intrinsic returns + // only the discriminant, nothing about the data. + assert_eq!(discriminant(&Some(false)), discriminant(&Some(true))); } diff --git a/src/tools/miri/tests/pass/issues/issue-53728.rs b/src/tools/miri/tests/pass/issues/issue-53728.rs deleted file mode 100644 index 0c858d3444fb3..0000000000000 --- a/src/tools/miri/tests/pass/issues/issue-53728.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[repr(u16)] -#[allow(dead_code)] -enum DeviceKind { - Nil = 0, -} - -#[repr(packed)] -#[allow(dead_code)] -struct DeviceInfo { - endianness: u8, - device_kind: DeviceKind, -} - -fn main() { - let _x = None::<(DeviceInfo, u8)>; - let _y = None::<(DeviceInfo, u16)>; - let _z = None::<(DeviceInfo, u64)>; -} diff --git a/src/tools/miri/tests/pass/issues/issue-miri-1925.rs b/src/tools/miri/tests/pass/issues/issue-miri-1925.rs deleted file mode 100644 index 8655681349194..0000000000000 --- a/src/tools/miri/tests/pass/issues/issue-miri-1925.rs +++ /dev/null @@ -1,33 +0,0 @@ -//@compile-flags: -Zmiri-symbolic-alignment-check - -use std::mem::size_of; - -fn main() { - let mut a = Params::new(); - a.key_block = [0; BLOCKBYTES]; -} - -#[repr(C)] -#[derive(Clone)] -#[allow(unused)] -pub struct Params { - hash_length: u8, - key_length: u8, - key_block: [u8; BLOCKBYTES], - max_leaf_length: u32, -} - -pub const OUTBYTES: usize = 8 * size_of::(); -pub const KEYBYTES: usize = 8 * size_of::(); -pub const BLOCKBYTES: usize = 16 * size_of::(); - -impl Params { - pub fn new() -> Self { - Self { - hash_length: OUTBYTES as u8, - key_length: 0, - key_block: [0; BLOCKBYTES], - max_leaf_length: 0, - } - } -} diff --git a/src/tools/miri/tests/pass/panic/catch_panic.stderr b/src/tools/miri/tests/pass/panic/catch_panic.stderr index f43434582a299..aa005590db3ee 100644 --- a/src/tools/miri/tests/pass/panic/catch_panic.stderr +++ b/src/tools/miri/tests/pass/panic/catch_panic.stderr @@ -1,24 +1,35 @@ -thread 'main' panicked at 'Hello from panic: std', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +Hello from panic: std note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Caught panic message (&str): Hello from panic: std -thread 'main' panicked at 'Hello from panic: 1', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +Hello from panic: 1 Caught panic message (String): Hello from panic: 1 -thread 'main' panicked at 'Hello from panic: 2', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +Hello from panic: 2 Caught panic message (String): Hello from panic: 2 -thread 'main' panicked at 'Box', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +Box Failed to get caught panic message. -thread 'main' panicked at 'Hello from panic: core', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +Hello from panic: core Caught panic message (&str): Hello from panic: core -thread 'main' panicked at 'Hello from panic: 5', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +Hello from panic: 5 Caught panic message (String): Hello from panic: 5 -thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 4', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +index out of bounds: the len is 3 but the index is 4 Caught panic message (String): index out of bounds: the len is 3 but the index is 4 -thread 'main' panicked at 'attempt to divide by zero', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +attempt to divide by zero Caught panic message (&str): attempt to divide by zero -thread 'main' panicked at 'align_offset: align is not a power-of-two', RUSTLIB/core/src/ptr/const_ptr.rs:LL:CC +thread 'main' panicked at RUSTLIB/core/src/ptr/const_ptr.rs:LL:CC: +align_offset: align is not a power-of-two Caught panic message (&str): align_offset: align is not a power-of-two -thread 'main' panicked at 'assertion failed: false', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +assertion failed: false Caught panic message (&str): assertion failed: false -thread 'main' panicked at 'assertion failed: false', $DIR/catch_panic.rs:LL:CC +thread 'main' panicked at $DIR/catch_panic.rs:LL:CC: +assertion failed: false Caught panic message (&str): assertion failed: false Success! diff --git a/src/tools/miri/tests/pass/panic/concurrent-panic.stderr b/src/tools/miri/tests/pass/panic/concurrent-panic.stderr index fd8fabc89cccf..0bb3dcd8d248c 100644 --- a/src/tools/miri/tests/pass/panic/concurrent-panic.stderr +++ b/src/tools/miri/tests/pass/panic/concurrent-panic.stderr @@ -1,10 +1,12 @@ Thread 1 starting, will block on mutex Thread 1 reported it has started -thread '' panicked at 'panic in thread 2', $DIR/concurrent-panic.rs:LL:CC +thread '' panicked at $DIR/concurrent-panic.rs:LL:CC: +panic in thread 2 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Thread 2 blocking on thread 1 Thread 2 reported it has started Unlocking mutex -thread '' panicked at 'panic in thread 1', $DIR/concurrent-panic.rs:LL:CC +thread '' panicked at $DIR/concurrent-panic.rs:LL:CC: +panic in thread 1 Thread 1 has exited Thread 2 has exited diff --git a/src/tools/miri/tests/pass/panic/nested_panic_caught.rs b/src/tools/miri/tests/pass/panic/nested_panic_caught.rs new file mode 100644 index 0000000000000..884813150ad2a --- /dev/null +++ b/src/tools/miri/tests/pass/panic/nested_panic_caught.rs @@ -0,0 +1,25 @@ +//@normalize-stderr-test: "\| +\^+" -> "| ^" +//@normalize-stderr-test: "\n +[0-9]+:[^\n]+" -> "$1" +//@normalize-stderr-test: "\n at [^\n]+" -> "$1" + +// Checks that nested panics work correctly. + +use std::panic::catch_unwind; + +fn double() { + struct Double; + + impl Drop for Double { + fn drop(&mut self) { + let _ = catch_unwind(|| panic!("twice")); + } + } + + let _d = Double; + + panic!("once"); +} + +fn main() { + assert!(catch_unwind(|| double()).is_err()); +} diff --git a/src/tools/miri/tests/pass/panic/nested_panic_caught.stderr b/src/tools/miri/tests/pass/panic/nested_panic_caught.stderr new file mode 100644 index 0000000000000..4684beb33383a --- /dev/null +++ b/src/tools/miri/tests/pass/panic/nested_panic_caught.stderr @@ -0,0 +1,6 @@ +thread 'main' panicked at $DIR/nested_panic_caught.rs:LL:CC: +once +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +thread 'main' panicked at $DIR/nested_panic_caught.rs:LL:CC: +twice +stack backtrace: diff --git a/src/tools/miri/tests/pass/portable-simd-ptrs.rs b/src/tools/miri/tests/pass/portable-simd-ptrs.rs index 303c99834f5d8..843bb0284cfe7 100644 --- a/src/tools/miri/tests/pass/portable-simd-ptrs.rs +++ b/src/tools/miri/tests/pass/portable-simd-ptrs.rs @@ -6,7 +6,7 @@ use std::simd::*; fn main() { // Pointer casts - let _val: Simd<*const u8, 4> = Simd::<*const i32, 4>::splat(ptr::null()).cast_ptr(); + let _val: Simd<*const u8, 4> = Simd::<*const i32, 4>::splat(ptr::null()).cast(); let addrs = Simd::<*const i32, 4>::splat(ptr::null()).expose_addr(); let _ptrs = Simd::<*const i32, 4>::from_exposed_addr(addrs); } diff --git a/src/tools/miri/tests/pass/ptr_raw.rs b/src/tools/miri/tests/pass/ptr_raw.rs index 3ba0fba9a9412..2f1843589072f 100644 --- a/src/tools/miri/tests/pass/ptr_raw.rs +++ b/src/tools/miri/tests/pass/ptr_raw.rs @@ -20,6 +20,15 @@ fn basic_raw() { assert_eq!(*x, 23); } +fn assign_overlapping() { + // Test an assignment where LHS and RHS alias. + // In Mir, that's UB (see `fail/overlapping_assignment.rs`), but in surface Rust this is allowed. + let mut mem = [0u32; 4]; + let ptr = &mut mem as *mut [u32; 4]; + unsafe { *ptr = *ptr }; +} + fn main() { basic_raw(); + assign_overlapping(); } diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index af245aa89aa36..6ba39c1f563f9 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -5,7 +5,7 @@ #![feature(io_error_uncategorized)] use std::collections::HashMap; -use std::ffi::{c_char, OsString}; +use std::ffi::OsString; use std::fs::{ canonicalize, create_dir, read_dir, read_link, remove_dir, remove_dir_all, remove_file, rename, File, OpenOptions, @@ -13,6 +13,9 @@ use std::fs::{ use std::io::{Error, ErrorKind, IsTerminal, Read, Result, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; +#[path = "../../utils/mod.rs"] +mod utils; + fn main() { test_path_conversion(); test_file(); @@ -30,37 +33,9 @@ fn main() { test_from_raw_os_error(); } -fn host_to_target_path(path: String) -> PathBuf { - use std::ffi::{CStr, CString}; - - let path = CString::new(path).unwrap(); - let mut out = Vec::with_capacity(1024); - - unsafe { - extern "Rust" { - fn miri_host_to_target_path( - path: *const c_char, - out: *mut c_char, - out_size: usize, - ) -> usize; - } - let ret = miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity()); - assert_eq!(ret, 0); - let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap(); - PathBuf::from(out) - } -} - -fn tmp() -> PathBuf { - let path = std::env::var("MIRI_TEMP") - .unwrap_or_else(|_| std::env::temp_dir().into_os_string().into_string().unwrap()); - // These are host paths. We need to convert them to the target. - host_to_target_path(path) -} - /// Prepare: compute filename and make sure the file does not exist. fn prepare(filename: &str) -> PathBuf { - let path = tmp().join(filename); + let path = utils::tmp().join(filename); // Clean the paths for robustness. remove_file(&path).ok(); path @@ -68,7 +43,7 @@ fn prepare(filename: &str) -> PathBuf { /// Prepare directory: compute directory name and make sure it does not exist. fn prepare_dir(dirname: &str) -> PathBuf { - let path = tmp().join(&dirname); + let path = utils::tmp().join(&dirname); // Clean the directory for robustness. remove_dir_all(&path).ok(); path @@ -83,7 +58,7 @@ fn prepare_with_content(filename: &str, content: &[u8]) -> PathBuf { } fn test_path_conversion() { - let tmp = tmp(); + let tmp = utils::tmp(); assert!(tmp.is_absolute(), "{:?} is not absolute", tmp); assert!(tmp.is_dir(), "{:?} is not a directory", tmp); } diff --git a/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout b/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout index 641e469f50c26..c68b40b744bf2 100644 --- a/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout +++ b/src/tools/miri/tests/pass/shims/time-with-isolation2.stdout @@ -1 +1 @@ -The loop took around 13s +The loop took around 7s diff --git a/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs b/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs index c6373a7eaf135..830e9c33847cf 100644 --- a/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs +++ b/src/tools/miri/tests/pass/stacked-borrows/interior_mutability.rs @@ -1,4 +1,3 @@ -//@compile-flags: -Zmiri-retag-fields use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; use std::mem::{self, MaybeUninit}; diff --git a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs index d7d7d1f97d6e5..d2ba184184485 100644 --- a/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs +++ b/src/tools/miri/tests/pass/stacked-borrows/stacked-borrows.rs @@ -1,4 +1,3 @@ -//@compile-flags: -Zmiri-retag-fields #![feature(allocator_api)] use std::ptr; @@ -11,7 +10,7 @@ fn main() { mut_raw_mut(); partially_invalidate_mut(); drop_after_sharing(); - direct_mut_to_const_raw(); + // direct_mut_to_const_raw(); two_raw(); shr_and_raw(); disjoint_mutable_subborrows(); @@ -20,6 +19,7 @@ fn main() { mut_below_shr(); wide_raw_ptr_in_tuple(); not_unpin_not_protected(); + write_does_not_invalidate_all_aliases(); } // Make sure that reading from an `&mut` does, like reborrowing to `&`, @@ -111,14 +111,13 @@ fn drop_after_sharing() { } // Make sure that coercing &mut T to *const T produces a writeable pointer. -fn direct_mut_to_const_raw() { - // TODO: This is currently disabled, waiting on a decision on - /*let x = &mut 0; +// TODO: This is currently disabled, waiting on a decision on +/*fn direct_mut_to_const_raw() { + let x = &mut 0; let y: *const i32 = x; unsafe { *(y as *mut i32) = 1; } assert_eq!(*x, 1); - */ -} +}*/ // Make sure that we can create two raw pointers from a mutable reference and use them both. fn two_raw() { @@ -239,3 +238,28 @@ fn not_unpin_not_protected() { drop(unsafe { Box::from_raw(raw) }); }); } + +fn write_does_not_invalidate_all_aliases() { + mod other { + /// Some private memory to store stuff in. + static mut S: *mut i32 = 0 as *mut i32; + + pub fn lib1(x: &&mut i32) { + unsafe { + S = (x as *const &mut i32).cast::<*mut i32>().read(); + } + } + + pub fn lib2() { + unsafe { + *S = 1337; + } + } + } + + let x = &mut 0; + other::lib1(&x); + *x = 42; // a write to x -- invalidates other pointers? + other::lib2(); + assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated +} diff --git a/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs b/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs index 6e13a9ea8369b..4faf6004ad6fc 100644 --- a/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs +++ b/src/tools/miri/tests/pass/stacked-borrows/zst-field-retagging-terminates.rs @@ -1,4 +1,3 @@ -//@compile-flags: -Zmiri-retag-fields // Checks that the test does not run forever (which relies on a fast path). #![allow(dropping_copy_types)] diff --git a/src/tools/miri/tests/pass/strange_references.rs b/src/tools/miri/tests/pass/strange_references.rs new file mode 100644 index 0000000000000..fe5ff93a9ca8d --- /dev/null +++ b/src/tools/miri/tests/pass/strange_references.rs @@ -0,0 +1,25 @@ +//@revisions: stack tree +//@[tree]compile-flags: -Zmiri-tree-borrows + +// Create zero-sized references to vtables and function data. +// Just make sure nothing explodes. + +use std::{mem, ptr}; + +fn check_ref(x: &()) { + let _ptr = ptr::addr_of!(*x); +} + +fn main() { + check_ref({ + // Create reference to a function. + let fnptr: fn(&()) = check_ref; + unsafe { mem::transmute(fnptr) } + }); + check_ref({ + // Create reference to a vtable. + let wideptr: &dyn Send = &0; + let fields: (&i32, &()) = unsafe { mem::transmute(wideptr) }; + fields.1 + }) +} diff --git a/src/tools/miri/tests/pass/tree-borrows/cell-alternate-writes.stderr b/src/tools/miri/tests/pass/tree-borrows/cell-alternate-writes.stderr deleted file mode 100644 index 1eab4685a35f5..0000000000000 --- a/src/tools/miri/tests/pass/tree-borrows/cell-alternate-writes.stderr +++ /dev/null @@ -1,12 +0,0 @@ -────────────────────────────────────────────────────────────────────── -Warning: this tree is indicative only. Some tags may have been hidden. -0.. 1 -| Act| └─┬── -| Re*| └──── -────────────────────────────────────────────────────────────────────── -────────────────────────────────────────────────────────────────────── -Warning: this tree is indicative only. Some tags may have been hidden. -0.. 1 -| Act| └─┬── -| Act| └──── -────────────────────────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree-borrows/reborrow-is-read.stderr b/src/tools/miri/tests/pass/tree-borrows/reborrow-is-read.stderr deleted file mode 100644 index 8c4323b2f7fa7..0000000000000 --- a/src/tools/miri/tests/pass/tree-borrows/reborrow-is-read.stderr +++ /dev/null @@ -1,15 +0,0 @@ -────────────────────────────────────────────────────────────────────── -Warning: this tree is indicative only. Some tags may have been hidden. -0.. 1 -| Act| └─┬── -| Act| └─┬── -| Act| └──── -────────────────────────────────────────────────────────────────────── -────────────────────────────────────────────────────────────────────── -Warning: this tree is indicative only. Some tags may have been hidden. -0.. 1 -| Act| └─┬── -| Act| └─┬── -| Frz| ├──── -| Res| └──── -────────────────────────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree-borrows/transmute-unsafecell.rs b/src/tools/miri/tests/pass/tree-borrows/transmute-unsafecell.rs deleted file mode 100644 index e1a9334ab5410..0000000000000 --- a/src/tools/miri/tests/pass/tree-borrows/transmute-unsafecell.rs +++ /dev/null @@ -1,13 +0,0 @@ -//@compile-flags: -Zmiri-tree-borrows - -use core::cell::UnsafeCell; -use core::mem; - -fn main() { - unsafe { - let x = &0i32; - // As long as we only read, transmuting this to UnsafeCell should be fine. - let cell_x: &UnsafeCell = mem::transmute(&x); - let _val = *cell_x.get(); - } -} diff --git a/src/tools/miri/tests/pass/tree-borrows/2phase-interiormut.rs b/src/tools/miri/tests/pass/tree_borrows/2phase-interiormut.rs similarity index 100% rename from src/tools/miri/tests/pass/tree-borrows/2phase-interiormut.rs rename to src/tools/miri/tests/pass/tree_borrows/2phase-interiormut.rs diff --git a/src/tools/miri/tests/pass/tree-borrows/cell-alternate-writes.rs b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs similarity index 96% rename from src/tools/miri/tests/pass/tree-borrows/cell-alternate-writes.rs rename to src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs index 1bd94c6df67c8..398b542ed4c24 100644 --- a/src/tools/miri/tests/pass/tree-borrows/cell-alternate-writes.rs +++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.rs @@ -1,7 +1,7 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 #[path = "../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; use std::cell::UnsafeCell; diff --git a/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr new file mode 100644 index 0000000000000..f464e0b4f4948 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/cell-alternate-writes.stderr @@ -0,0 +1,12 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Re*| └──── +────────────────────────────────────────────────── +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree-borrows/copy-nonoverlapping.rs b/src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs similarity index 100% rename from src/tools/miri/tests/pass/tree-borrows/copy-nonoverlapping.rs rename to src/tools/miri/tests/pass/tree_borrows/copy-nonoverlapping.rs diff --git a/src/tools/miri/tests/pass/tree-borrows/end-of-protector.rs b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs similarity index 97% rename from src/tools/miri/tests/pass/tree-borrows/end-of-protector.rs rename to src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs index 76bbc73e662d3..fecc3360434d2 100644 --- a/src/tools/miri/tests/pass/tree-borrows/end-of-protector.rs +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.rs @@ -3,8 +3,8 @@ // Check that a protector goes back to normal behavior when the function // returns. #[path = "../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; fn main() { unsafe { diff --git a/src/tools/miri/tests/pass/tree-borrows/end-of-protector.stderr b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr similarity index 77% rename from src/tools/miri/tests/pass/tree-borrows/end-of-protector.stderr rename to src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr index c20da1a593fc0..265f6dfc9c834 100644 --- a/src/tools/miri/tests/pass/tree-borrows/end-of-protector.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/end-of-protector.stderr @@ -1,11 +1,11 @@ -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── | Res| └─┬── | Res| └──── -────────────────────────────────────────────────────────────────────── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -13,8 +13,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Res| └─┬── | Res| └─┬── | Res| └──── Strongly protected -────────────────────────────────────────────────────────────────────── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -23,8 +23,8 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Res| │ └─┬── | Res| │ └──── | Res| └──── -────────────────────────────────────────────────────────────────────── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -33,4 +33,4 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Dis| │ └─┬── | Dis| │ └──── | Act| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree-borrows/formatting.rs b/src/tools/miri/tests/pass/tree_borrows/formatting.rs similarity index 97% rename from src/tools/miri/tests/pass/tree-borrows/formatting.rs rename to src/tools/miri/tests/pass/tree_borrows/formatting.rs index 9021c4176383b..f22c408ad2538 100644 --- a/src/tools/miri/tests/pass/tree-borrows/formatting.rs +++ b/src/tools/miri/tests/pass/tree_borrows/formatting.rs @@ -1,8 +1,8 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 #[path = "../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; // Check the formatting of the trees. fn main() { @@ -17,6 +17,7 @@ fn main() { unsafe fn alignment_check() { let data: &mut [u8] = &mut [0; 1024]; name!(data.as_ptr()=>2, "data"); + name!(data.as_ptr()=>2, "data"); let alloc_id = alloc_id!(data.as_ptr()); let x = &mut data[1]; name!(x as *mut _, "data[1]"); diff --git a/src/tools/miri/tests/pass/tree-borrows/formatting.stderr b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr similarity index 81% rename from src/tools/miri/tests/pass/tree-borrows/formatting.stderr rename to src/tools/miri/tests/pass/tree_borrows/formatting.stderr index 8c3779fe1f7f7..673dae6210d88 100644 --- a/src/tools/miri/tests/pass/tree-borrows/formatting.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/formatting.stderr @@ -1,14 +1,14 @@ -─────────────────────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1.. 2.. 10.. 11..100..101..1000..1001..1024 | Act| Act| Act| Act| Act| Act| Act| Act| Act| └─┬── -| Res| Act| Res| Act| Res| Act| Res| Act| Res| └─┬── +| Res| Act| Res| Act| Res| Act| Res| Act| Res| └─┬── |----| Act|----|?Dis|----|?Dis| ----| ?Dis| ----| ├──── |----|----|----| Act|----|?Dis| ----| ?Dis| ----| ├──── |----|----|----|----|----| Frz| ----| ?Dis| ----| ├──── |----|----|----|----|----|----| ----| Act| ----| └──── -─────────────────────────────────────────────────────────────────────────────────────── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -28,4 +28,4 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Frz| └─┬── | Frz| ├──── | Frz| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree-borrows/reborrow-is-read.rs b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs similarity index 96% rename from src/tools/miri/tests/pass/tree-borrows/reborrow-is-read.rs rename to src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs index e3f3f2d4032fd..a38cd6d289416 100644 --- a/src/tools/miri/tests/pass/tree-borrows/reborrow-is-read.rs +++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.rs @@ -1,8 +1,8 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 #[path = "../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; // To check that a reborrow is counted as a Read access, we use a reborrow // with no additional Read to Freeze an Active pointer. diff --git a/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr new file mode 100644 index 0000000000000..b23d78a71566e --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/reborrow-is-read.stderr @@ -0,0 +1,15 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └─┬── +| Act| └──── +────────────────────────────────────────────────── +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └─┬── +| Frz| ├──── +| Res| └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree-borrows/reserved.rs b/src/tools/miri/tests/pass/tree_borrows/reserved.rs similarity index 95% rename from src/tools/miri/tests/pass/tree-borrows/reserved.rs rename to src/tools/miri/tests/pass/tree_borrows/reserved.rs index d8a8c27568d4d..8d0beab66f406 100644 --- a/src/tools/miri/tests/pass/tree-borrows/reserved.rs +++ b/src/tools/miri/tests/pass/tree_borrows/reserved.rs @@ -1,9 +1,8 @@ //@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 #[path = "../../utils/mod.rs"] +#[macro_use] mod utils; -use utils::macros::*; -use utils::miri_extern::miri_write_to_stderr; use std::cell::UnsafeCell; @@ -28,8 +27,8 @@ fn main() { } unsafe fn print(msg: &str) { - miri_write_to_stderr(msg.as_bytes()); - miri_write_to_stderr("\n".as_bytes()); + utils::miri_write_to_stderr(msg.as_bytes()); + utils::miri_write_to_stderr("\n".as_bytes()); } unsafe fn read_second(x: &mut T, y: *mut u8) { diff --git a/src/tools/miri/tests/pass/tree-borrows/reserved.stderr b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr similarity index 77% rename from src/tools/miri/tests/pass/tree-borrows/reserved.stderr rename to src/tools/miri/tests/pass/tree_borrows/reserved.stderr index afb917002221e..691fe8b77444d 100644 --- a/src/tools/miri/tests/pass/tree-borrows/reserved.stderr +++ b/src/tools/miri/tests/pass/tree_borrows/reserved.stderr @@ -1,5 +1,5 @@ [interior mut + protected] Foreign Read: Re* -> Frz -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -8,27 +8,27 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Re*| │ └─┬── | Frz| │ └──── | Re*| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── [interior mut] Foreign Read: Re* -> Re* -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 | Act| └─┬── | Re*| └─┬── | Re*| ├──── | Re*| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── [interior mut] Foreign Write: Re* -> Re* -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 8 | Act| └─┬── | Act| └─┬── | Re*| ├──── | Act| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── [protected] Foreign Read: Res -> Frz -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── @@ -37,22 +37,22 @@ Warning: this tree is indicative only. Some tags may have been hidden. | Res| │ └─┬── | Frz| │ └──── | Res| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── [] Foreign Read: Res -> Res -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── | Res| └─┬── | Res| ├──── | Res| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── [] Foreign Write: Res -> Dis -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── Warning: this tree is indicative only. Some tags may have been hidden. 0.. 1 | Act| └─┬── | Act| └─┬── | Dis| ├──── | Act| └──── -────────────────────────────────────────────────────────────────────── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs b/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs new file mode 100644 index 0000000000000..5973ef01ead08 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/sb_fails.rs @@ -0,0 +1,81 @@ +//@compile-flags: -Zmiri-tree-borrows + +// These tests fail Stacked Borrows, but pass Tree Borrows. +// A modified version of each is also available that fails Tree Borrows. +// They all have in common that in SB a mutable reborrow is enough to produce +// write access errors, but in TB an actual write is needed. + +mod fnentry_invalidation { + // Copied directly from fail/stacked_borrows/fnentry_invalidation.rs + // Version that fails TB: fail/tree_borrows/fnentry_invalidation.rs + pub fn main() { + let mut x = 0i32; + let z = &mut x as *mut i32; + x.do_bad(); + unsafe { + let _oof = *z; + // In SB this is an error, but in TB the mutable reborrow did + // not invalidate z for reading. + } + } + + trait Bad { + fn do_bad(&mut self) { + // who knows + } + } + + impl Bad for i32 {} +} + +mod pass_invalid_mut { + // Copied directly from fail/stacked_borrows/pass_invalid_mut.rs + // Version that fails TB: fail/tree_borrows/pass_invalid_mut.rs + fn foo(_: &mut i32) {} + + pub fn main() { + let x = &mut 42; + let xraw = x as *mut _; + let xref = unsafe { &mut *xraw }; + let _val = unsafe { *xraw }; // In SB this invalidates xref... + foo(xref); // ...which then cannot be reborrowed here. + // But in TB xref is Reserved and thus still writeable. + } +} + +mod return_invalid_mut { + // Copied directly from fail/stacked_borrows/return_invalid_mut.rs + // Version that fails TB: fail/tree_borrows/return_invalid_mut.rs + fn foo(x: &mut (i32, i32)) -> &mut i32 { + let xraw = x as *mut (i32, i32); + let ret = unsafe { &mut (*xraw).1 }; + let _val = unsafe { *xraw }; // In SB this invalidates ret... + ret // ...which then cannot be reborrowed here. + // But in TB ret is Reserved and thus still writeable. + } + + pub fn main() { + foo(&mut (1, 2)); + } +} + +mod static_memory_modification { + // Copied directly from fail/stacked_borrows/static_memory_modification.rs + // Version that fails TB: fail/tree_borrows/static_memory_modification.rs + static X: usize = 5; + + #[allow(mutable_transmutes)] + pub fn main() { + let _x = unsafe { + std::mem::transmute::<&usize, &mut usize>(&X) // In SB this mutable reborrow fails. + // But in TB we are allowed to transmute as long as we don't write. + }; + } +} + +fn main() { + fnentry_invalidation::main(); + pass_invalid_mut::main(); + return_invalid_mut::main(); + static_memory_modification::main(); +} diff --git a/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs b/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs new file mode 100644 index 0000000000000..1df0636e1e5d2 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/transmute-unsafecell.rs @@ -0,0 +1,32 @@ +//@compile-flags: -Zmiri-tree-borrows + +//! Testing `mem::transmute` between types with and without interior mutability. +//! All transmutations should work, as long as we don't do any actual accesses +//! that violate immutability. + +use core::cell::UnsafeCell; +use core::mem; + +fn main() { + unsafe { + ref_to_cell(); + cell_to_ref(); + } +} + +// Pretend that the reference has interior mutability. +// Don't actually mutate it though, it will fail because it has a Frozen parent. +unsafe fn ref_to_cell() { + let x = &42i32; + let cell_x: &UnsafeCell = mem::transmute(x); + let val = *cell_x.get(); + assert_eq!(val, 42); +} + +// Forget about the interior mutability of a cell. +unsafe fn cell_to_ref() { + let x = &UnsafeCell::new(42); + let ref_x: &i32 = mem::transmute(x); + let val = *ref_x; + assert_eq!(val, 42); +} diff --git a/src/tools/miri/tests/pass/tree-borrows/tree-borrows.rs b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs similarity index 79% rename from src/tools/miri/tests/pass/tree-borrows/tree-borrows.rs rename to src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs index aa6f7078890e9..531543441c253 100644 --- a/src/tools/miri/tests/pass/tree-borrows/tree-borrows.rs +++ b/src/tools/miri/tests/pass/tree_borrows/tree-borrows.rs @@ -1,4 +1,6 @@ +//@revisions: default uniq //@compile-flags: -Zmiri-tree-borrows +//@[uniq]compile-flags: -Zmiri-unique-is-unique #![feature(allocator_api)] use std::mem; @@ -7,6 +9,9 @@ use std::ptr; fn main() { aliasing_read_only_mutable_refs(); string_as_mut_ptr(); + two_mut_protected_same_alloc(); + direct_mut_to_const_raw(); + local_addr_of_mut(); // Stacked Borrows tests read_does_not_invalidate1(); @@ -16,7 +21,6 @@ fn main() { mut_raw_mut(); partially_invalidate_mut(); drop_after_sharing(); - direct_mut_to_const_raw(); two_raw(); shr_and_raw(); disjoint_mutable_subborrows(); @@ -25,6 +29,18 @@ fn main() { mut_below_shr(); wide_raw_ptr_in_tuple(); not_unpin_not_protected(); + write_does_not_invalidate_all_aliases(); +} + +#[allow(unused_assignments)] +fn local_addr_of_mut() { + let mut local = 0; + let ptr = ptr::addr_of_mut!(local); + // In SB, `local` and `*ptr` would have different tags, but in TB they have the same tag. + local = 1; + unsafe { *ptr = 2 }; + local = 3; + unsafe { *ptr = 4 }; } // Tree Borrows has no issue with several mutable references existing @@ -60,6 +76,23 @@ pub fn string_as_mut_ptr() { } } +// This function checks that there is no issue with having two mutable references +// from the same allocation both under a protector. +// This is safe code, it must absolutely not be UB. +// This test failing is a symptom of forgetting to check that only initialized +// locations can cause protector UB. +fn two_mut_protected_same_alloc() { + fn write_second(_x: &mut u8, y: &mut u8) { + // write through `y` will make some locations of `x` (protected) + // become Disabled. Those locations are outside of the range on which + // `x` is initialized, and the protector must not trigger. + *y = 1; + } + + let mut data = (0u8, 1u8); + write_second(&mut data.0, &mut data.1); +} + // ----- The tests below were taken from Stacked Borrows ---- // Make sure that reading from an `&mut` does, like reborrowing to `&`, @@ -152,12 +185,12 @@ fn drop_after_sharing() { // Make sure that coercing &mut T to *const T produces a writeable pointer. fn direct_mut_to_const_raw() { - // TODO: This is currently disabled, waiting on a decision on - /*let x = &mut 0; + let x = &mut 0; let y: *const i32 = x; - unsafe { *(y as *mut i32) = 1; } + unsafe { + *(y as *mut i32) = 1; + } assert_eq!(*x, 1); - */ } // Make sure that we can create two raw pointers from a mutable reference and use them both. @@ -278,3 +311,31 @@ fn not_unpin_not_protected() { drop(unsafe { Box::from_raw(raw) }); }); } + +fn write_does_not_invalidate_all_aliases() { + // In TB there are other ways to do that (`addr_of!(*x)` has the same tag as `x`), + // but let's still make sure this SB test keeps working. + + mod other { + /// Some private memory to store stuff in. + static mut S: *mut i32 = 0 as *mut i32; + + pub fn lib1(x: &&mut i32) { + unsafe { + S = (x as *const &mut i32).cast::<*mut i32>().read(); + } + } + + pub fn lib2() { + unsafe { + *S = 1337; + } + } + } + + let x = &mut 0; + other::lib1(&x); + *x = 42; // a write to x -- invalidates other pointers? + other::lib2(); + assert_eq!(*x, 1337); // oops, the value changed! I guess not all pointers were invalidated +} diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr new file mode 100644 index 0000000000000..f870d3bdec00c --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/unique.default.stderr @@ -0,0 +1,21 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Res| └─┬── +| Res| └──── +────────────────────────────────────────────────── +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └─┬── +| Act| └──── +────────────────────────────────────────────────── +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └─┬── +| Act| └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.rs b/src/tools/miri/tests/pass/tree_borrows/unique.rs new file mode 100644 index 0000000000000..44e2e8136252e --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/unique.rs @@ -0,0 +1,66 @@ +//@revisions: default uniq +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 +//@[uniq]compile-flags: -Zmiri-unique-is-unique + +#![feature(ptr_internals)] + +#[path = "../../utils/mod.rs"] +#[macro_use] +mod utils; + +use core::ptr::Unique; + +// Check general handling of Unique + +fn main() { + unsafe { + let base = &mut 5u8; + let alloc_id = alloc_id!(base); + name!(base); + + let raw = &mut *base as *mut u8; + name!(raw); + + // We create a `Unique` and expect it to have a fresh tag + // and uninitialized permissions. + let uniq = Unique::new_unchecked(raw); + + // With `-Zmiri-unique-is-unique`, `Unique::as_ptr` (which is called by + // `Vec::as_ptr`) generates pointers with a fresh tag, so to name the actual + // `base` pointer we care about we have to walk up the tree a bit. + // + // We care about naming this specific parent tag because it is the one + // that stays `Active` during the entire execution, unlike the leaves + // that will be invalidated the next time `as_ptr` is called. + // + // (We name it twice so that we have an indicator in the output of + // whether we got the distance correct: + // If the output shows + // + // |- + // '- + // + // then `nth_parent` is not big enough. + // The correct value for `nth_parent` should be the minimum + // integer for which the output shows + // + // '- + // ) + // + // Ultimately we want pointers obtained through independent + // calls of `as_ptr` to be able to alias, which will probably involve + // a new permission that allows aliasing when there is no protector. + let nth_parent = if cfg!(uniq) { 2 } else { 0 }; + name!(uniq.as_ptr()=>nth_parent, "uniq"); + name!(uniq.as_ptr()=>nth_parent, "uniq"); + print_state!(alloc_id); + + // We can activate the Unique and use it mutably. + *uniq.as_ptr() = 42; + print_state!(alloc_id); + + // Write through the raw parent disables the Unique + *raw = 42; + print_state!(alloc_id); + } +} diff --git a/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr new file mode 100644 index 0000000000000..9ab6b879aa762 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/unique.uniq.stderr @@ -0,0 +1,24 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Res| └─┬── +| Res| └─┬── +|----| └──── +────────────────────────────────────────────────── +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └─┬── +| Act| └─┬── +| Act| └──── +────────────────────────────────────────────────── +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 1 +| Act| └─┬── +| Act| └─┬── +| Act| └─┬── +| Dis| └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr new file mode 100644 index 0000000000000..a7712ae91fba8 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.default.stderr @@ -0,0 +1,6 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 2 +| Act| └─┬── +| Res| └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs b/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs new file mode 100644 index 0000000000000..e5d0a683a7237 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.rs @@ -0,0 +1,68 @@ +//@revisions: default uniq +//@compile-flags: -Zmiri-tree-borrows -Zmiri-tag-gc=0 +//@[uniq]compile-flags: -Zmiri-unique-is-unique + +#![feature(vec_into_raw_parts)] + +#[path = "../../utils/mod.rs"] +#[macro_use] +mod utils; + +// Check general handling of `Unique`: +// there is no *explicit* `Unique` being used here, but there is one +// hidden a few layers inside `Vec` that should be reflected in the tree structure. + +fn main() { + unsafe { + let base = vec![0u8, 1]; + let alloc_id = alloc_id!(base.as_ptr()); + + // With `-Zmiri-unique-is-unique`, `Unique::as_ptr` (which is called by + // `Vec::as_ptr`) generates pointers with a fresh tag, so to name the actual + // `base` pointer we care about we have to walk up the tree a bit. + // + // We care about naming this specific parent tag because it is the one + // that stays `Active` during the entire execution, unlike the leaves + // that will be invalidated the next time `as_ptr` is called. + // + // (We name it twice so that we have an indicator in the output of + // whether we got the distance correct: + // If the output shows + // + // |- + // '- + // + // then `nth_parent` is not big enough. + // The correct value for `nth_parent` should be the minimum + // integer for which the output shows + // + // '- + // ) + // + // Ultimately we want pointers obtained through independent + // calls of `as_ptr` to be able to alias, which will probably involve + // a new permission that allows aliasing when there is no protector. + let nth_parent = if cfg!(uniq) { 2 } else { 0 }; + name!(base.as_ptr()=>nth_parent); + name!(base.as_ptr()=>nth_parent); + + // Destruct the `Vec` + let (ptr, len, cap) = base.into_raw_parts(); + + // Expect this to be again the same pointer as the one obtained from `as_ptr`. + // Under `-Zmiri-unique-is-unique`, this will be a strict child. + name!(ptr, "raw_parts.0"); + + // This is where the presence of `Unique` has implications, + // because there will be a reborrow here iff the exclusivity of `Unique` + // is enforced. + let reconstructed = Vec::from_raw_parts(ptr, len, cap); + + // The `as_ptr` here (twice for the same reason as above) return either + // the same pointer once more (default) or a strict child (uniq). + name!(reconstructed.as_ptr()=>nth_parent); + name!(reconstructed.as_ptr()=>nth_parent); + + print_state!(alloc_id, false); + } +} diff --git a/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr b/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr new file mode 100644 index 0000000000000..e9f1cb3b1ed93 --- /dev/null +++ b/src/tools/miri/tests/pass/tree_borrows/vec_unique.uniq.stderr @@ -0,0 +1,8 @@ +────────────────────────────────────────────────── +Warning: this tree is indicative only. Some tags may have been hidden. +0.. 2 +| Act| └─┬── +|----| └─┬── +|----| └─┬── +|----| └──── +────────────────────────────────────────────────── diff --git a/src/tools/miri/tests/pass/vec.rs b/src/tools/miri/tests/pass/vec.rs index 06ec2f991722a..4ab2bcb7f2281 100644 --- a/src/tools/miri/tests/pass/vec.rs +++ b/src/tools/miri/tests/pass/vec.rs @@ -1,6 +1,7 @@ -//@revisions: stack tree -//@[tree]compile-flags: -Zmiri-tree-borrows +//@revisions: stack tree tree_uniq //@compile-flags: -Zmiri-strict-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows +//@[tree_uniq]compile-flags: -Zmiri-tree-borrows -Zmiri-unique-is-unique #![feature(iter_advance_by, iter_next_chunk)] // Gather all references from a mutable iterator and make sure Miri notices if @@ -99,7 +100,7 @@ fn vec_push_ptr_stable() { v.push(0); let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay. v.push(1); - let _val = *v0; + *v0 = *v0; } fn vec_extend_ptr_stable() { @@ -108,23 +109,23 @@ fn vec_extend_ptr_stable() { let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay. // `slice::Iter` (with `T: Copy`) specialization v.extend(&[1]); - let _val = *v0; + *v0 = *v0; // `vec::IntoIter` specialization v.extend(vec![2]); - let _val = *v0; + *v0 = *v0; // `TrustedLen` specialization v.extend(std::iter::once(3)); - let _val = *v0; + *v0 = *v0; // base case v.extend(std::iter::once(3).filter(|_| true)); - let _val = *v0; + *v0 = *v0; } fn vec_truncate_ptr_stable() { let mut v = vec![0; 10]; let v0 = unsafe { &mut *(&mut v[0] as *mut _) }; // laundering the lifetime -- we take care that `v` does not reallocate, so that's okay. v.truncate(5); - let _val = *v0; + *v0 = *v0; } fn push_str_ptr_stable() { diff --git a/src/tools/miri/tests/pass/vecdeque.rs b/src/tools/miri/tests/pass/vecdeque.rs index dfbf9bb83c1d3..ccecf3d30a414 100644 --- a/src/tools/miri/tests/pass/vecdeque.rs +++ b/src/tools/miri/tests/pass/vecdeque.rs @@ -1,6 +1,8 @@ -//@revisions: stack tree -//@[tree]compile-flags: -Zmiri-tree-borrows +//@revisions: stack tree tree_uniq //@compile-flags: -Zmiri-strict-provenance +//@[tree]compile-flags: -Zmiri-tree-borrows +//@[tree_uniq]compile-flags: -Zmiri-tree-borrows -Zmiri-unique-is-unique + use std::collections::VecDeque; fn test_all_refs<'a, T: 'a>(dummy: &mut T, iter: impl Iterator) { diff --git a/src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout b/src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout new file mode 100644 index 0000000000000..63de960ee2bdf --- /dev/null +++ b/src/tools/miri/tests/pass/vecdeque.tree_uniq.stdout @@ -0,0 +1,2 @@ +[2, 2] Iter([2, 2], []) +Iter([], []) diff --git a/src/tools/miri/tests/utils/fs.rs b/src/tools/miri/tests/utils/fs.rs new file mode 100644 index 0000000000000..47904926b48b9 --- /dev/null +++ b/src/tools/miri/tests/utils/fs.rs @@ -0,0 +1,29 @@ +use std::ffi::OsString; +use std::path::PathBuf; + +use super::miri_extern; + +pub fn host_to_target_path(path: OsString) -> PathBuf { + use std::ffi::{CStr, CString}; + + // Once into_os_str_bytes is stable we can use it here. + // (Unstable features would need feature flags in each test...) + let path = CString::new(path.into_string().unwrap()).unwrap(); + let mut out = Vec::with_capacity(1024); + + unsafe { + let ret = + miri_extern::miri_host_to_target_path(path.as_ptr(), out.as_mut_ptr(), out.capacity()); + assert_eq!(ret, 0); + // Here we panic if it's not UTF-8... but that is hard to avoid with OsStr APIs. + let out = CStr::from_ptr(out.as_ptr()).to_str().unwrap(); + PathBuf::from(out) + } +} + +pub fn tmp() -> PathBuf { + let path = + std::env::var_os("MIRI_TEMP").unwrap_or_else(|| std::env::temp_dir().into_os_string()); + // These are host paths. We need to convert them to the target. + host_to_target_path(path) +} diff --git a/src/tools/miri/tests/utils/macros.rs b/src/tools/miri/tests/utils/macros.rs index de223410fba6b..3f5b9f78ee01e 100644 --- a/src/tools/miri/tests/utils/macros.rs +++ b/src/tools/miri/tests/utils/macros.rs @@ -9,7 +9,7 @@ /// The id obtained can be passed directly to `print_state!`. macro_rules! alloc_id { ($ptr:expr) => { - crate::utils::miri_extern::miri_get_alloc_id($ptr as *const u8 as *const ()) + $crate::utils::miri_get_alloc_id($ptr as *const u8 as *const ()) }; } @@ -22,10 +22,10 @@ macro_rules! alloc_id { /// tags that have not been given a name. Defaults to `false`. macro_rules! print_state { ($alloc_id:expr) => { - crate::utils::macros::print_state!($alloc_id, false); + print_state!($alloc_id, false); }; ($alloc_id:expr, $show:expr) => { - crate::utils::miri_extern::miri_print_borrow_state($alloc_id, $show); + $crate::utils::miri_print_borrow_state($alloc_id, $show); }; } @@ -42,20 +42,16 @@ macro_rules! print_state { /// `stringify!($ptr)` the name of `ptr` in the source code. macro_rules! name { ($ptr:expr, $name:expr) => { - crate::utils::macros::name!($ptr => 0, $name); + name!($ptr => 0, $name); }; ($ptr:expr) => { - crate::utils::macros::name!($ptr => 0, stringify!($ptr)); + name!($ptr => 0, stringify!($ptr)); }; - ($ptr:expr => $nb:expr) => { - crate::utils::macros::name!($ptr => $nb, stringify!($ptr)); + ($ptr:expr => $nth_parent:expr) => { + name!($ptr => $nth_parent, stringify!($ptr)); }; - ($ptr:expr => $nb:expr, $name:expr) => { + ($ptr:expr => $nth_parent:expr, $name:expr) => { let name = $name.as_bytes(); - crate::utils::miri_extern::miri_pointer_name($ptr as *const u8 as *const (), $nb, name); + $crate::utils::miri_pointer_name($ptr as *const u8 as *const (), $nth_parent, name); }; } - -pub(crate) use alloc_id; -pub(crate) use name; -pub(crate) use print_state; diff --git a/src/tools/miri/tests/utils/miri_extern.rs b/src/tools/miri/tests/utils/miri_extern.rs index 6c4298c613b45..c0ef2c506413d 100644 --- a/src/tools/miri/tests/utils/miri_extern.rs +++ b/src/tools/miri/tests/utils/miri_extern.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - #[repr(C)] /// Layout of the return value of `miri_resolve_frame`, /// with fields in the exact same order. @@ -65,7 +63,7 @@ extern "Rust" { /// /// This is only useful as an input to `miri_print_borrow_stacks`, and it is a separate call because /// getting a pointer to an allocation at runtime can change the borrow stacks in the allocation. - /// This function should be considered unstable. It exists only to support `miri_print_borrow_stacks` and so + /// This function should be considered unstable. It exists only to support `miri_print_borrow_state` and so /// inherits all of its instability. pub fn miri_get_alloc_id(ptr: *const ()) -> u64; diff --git a/src/tools/miri/tests/utils/mod.rs b/src/tools/miri/tests/utils/mod.rs index e1ea77e4df8c7..593f82910c6fe 100644 --- a/src/tools/miri/tests/utils/mod.rs +++ b/src/tools/miri/tests/utils/mod.rs @@ -1,2 +1,10 @@ -pub mod macros; -pub mod miri_extern; +#![allow(dead_code)] + +#[macro_use] +mod macros; + +mod fs; +mod miri_extern; + +pub use fs::*; +pub use miri_extern::*; diff --git a/src/tools/miropt-test-tools/src/lib.rs b/src/tools/miropt-test-tools/src/lib.rs index f86c3ce0afeaa..e33ecfe8eab24 100644 --- a/src/tools/miropt-test-tools/src/lib.rs +++ b/src/tools/miropt-test-tools/src/lib.rs @@ -1,4 +1,5 @@ use std::fs; +use std::path::Path; pub struct MiroptTestFiles { pub expected_file: std::path::PathBuf, @@ -8,18 +9,52 @@ pub struct MiroptTestFiles { pub passes: Vec, } -pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec { +pub enum PanicStrategy { + Unwind, + Abort, +} + +pub fn output_file_suffix( + testfile: &Path, + bit_width: u32, + panic_strategy: PanicStrategy, +) -> String { + let mut each_bit_width = false; + let mut each_panic_strategy = false; + for line in fs::read_to_string(testfile).unwrap().lines() { + if line == "// EMIT_MIR_FOR_EACH_BIT_WIDTH" { + each_bit_width = true; + } + if line == "// EMIT_MIR_FOR_EACH_PANIC_STRATEGY" { + each_panic_strategy = true; + } + } + + let mut suffix = String::new(); + if each_bit_width { + suffix.push_str(&format!(".{}bit", bit_width)); + } + if each_panic_strategy { + match panic_strategy { + PanicStrategy::Unwind => suffix.push_str(".panic-unwind"), + PanicStrategy::Abort => suffix.push_str(".panic-abort"), + } + } + suffix +} + +pub fn files_for_miropt_test( + testfile: &std::path::Path, + bit_width: u32, + panic_strategy: PanicStrategy, +) -> Vec { let mut out = Vec::new(); let test_file_contents = fs::read_to_string(&testfile).unwrap(); let test_dir = testfile.parent().unwrap(); let test_crate = testfile.file_stem().unwrap().to_str().unwrap().replace('-', "_"); - let bit_width = if test_file_contents.lines().any(|l| l == "// EMIT_MIR_FOR_EACH_BIT_WIDTH") { - format!(".{}bit", bit_width) - } else { - String::new() - }; + let suffix = output_file_suffix(testfile, bit_width, panic_strategy); for l in test_file_contents.lines() { if l.starts_with("// EMIT_MIR ") { @@ -37,7 +72,7 @@ pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec< passes.push(trimmed.split('.').last().unwrap().to_owned()); let test_against = format!("{}.after.mir", trimmed); from_file = format!("{}.before.mir", trimmed); - expected_file = format!("{}{}.diff", trimmed, bit_width); + expected_file = format!("{}{}.diff", trimmed, suffix); assert!(test_names.next().is_none(), "two mir pass names specified for MIR diff"); to_file = Some(test_against); } else if let Some(first_pass) = test_names.next() { @@ -51,7 +86,7 @@ pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec< assert!(test_names.next().is_none(), "three mir pass names specified for MIR diff"); expected_file = - format!("{}{}.{}-{}.diff", test_name, bit_width, first_pass, second_pass); + format!("{}{}.{}-{}.diff", test_name, suffix, first_pass, second_pass); let second_file = format!("{}.{}.mir", test_name, second_pass); from_file = format!("{}.{}.mir", test_name, first_pass); to_file = Some(second_file); @@ -64,7 +99,7 @@ pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec< let extension = cap.get(1).unwrap().as_str(); expected_file = - format!("{}{}{}", test_name.trim_end_matches(extension), bit_width, extension,); + format!("{}{}{}", test_name.trim_end_matches(extension), suffix, extension,); from_file = test_name.to_string(); assert!(test_names.next().is_none(), "two mir pass names specified for MIR dump"); to_file = None; diff --git a/src/tools/opt-dist/Cargo.toml b/src/tools/opt-dist/Cargo.toml new file mode 100644 index 0000000000000..3f7dba81c3a9f --- /dev/null +++ b/src/tools/opt-dist/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "opt-dist" +version = "0.1.0" +edition = "2021" + +[dependencies] +build_helper = { path = "../build_helper" } +env_logger = "0.10" +log = "0.4" +anyhow = { version = "1", features = ["backtrace"] } +humantime = "2" +humansize = "2" +sysinfo = { version = "0.29", default-features = false } +fs_extra = "1" +camino = "1" +reqwest = { version = "0.11", features = ["blocking"] } +zip = { version = "0.6", default-features = false, features = ["deflate"] } +tar = "0.4" +xz = "0.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +glob = "0.3" +tempfile = "3.5" diff --git a/src/tools/opt-dist/README.md b/src/tools/opt-dist/README.md new file mode 100644 index 0000000000000..05a75870da140 --- /dev/null +++ b/src/tools/opt-dist/README.md @@ -0,0 +1,7 @@ +# Optimized build pipeline +This binary implements a heavily optimized build pipeline for `rustc` and `LLVM` artifacts that are used for both for +benchmarking using the perf. bot and for final distribution to users. + +It uses LTO, PGO and BOLT to optimize the compiler and LLVM as much as possible. +This logic is not part of bootstrap, because it needs to invoke bootstrap multiple times, force-rebuild various +artifacts repeatedly and sometimes go around bootstrap's cache mechanism. diff --git a/src/tools/opt-dist/src/bolt.rs b/src/tools/opt-dist/src/bolt.rs new file mode 100644 index 0000000000000..cf9f4fabcec12 --- /dev/null +++ b/src/tools/opt-dist/src/bolt.rs @@ -0,0 +1,103 @@ +use anyhow::Context; + +use crate::exec::cmd; +use crate::training::LlvmBoltProfile; +use camino::{Utf8Path, Utf8PathBuf}; + +use crate::utils::io::copy_file; + +/// Instruments an artifact at the given `path` (in-place) with BOLT and then calls `func`. +/// After this function finishes, the original file will be restored. +pub fn with_bolt_instrumented anyhow::Result, R>( + path: &Utf8Path, + func: F, +) -> anyhow::Result { + // Back up the original file. + // It will be restored to its original state when this function exits. + // By copying it, we break any existing hard links, so that they are not affected by the + // instrumentation. + let _backup_file = BackedUpFile::new(path)?; + + let instrumented_path = tempfile::NamedTempFile::new()?.into_temp_path(); + + // Instrument the original file with BOLT, saving the result into `instrumented_path` + cmd(&["llvm-bolt"]) + .arg("-instrument") + .arg(path) + // Make sure that each process will write its profiles into a separate file + .arg("--instrumentation-file-append-pid") + .arg("-o") + .arg(instrumented_path.display()) + .run() + .with_context(|| anyhow::anyhow!("Could not instrument {path} using BOLT"))?; + + // Copy the instrumented artifact over the original one + copy_file(&instrumented_path, path)?; + + // Run the function that will make use of the instrumented artifact. + // The original file will be restored when `_backup_file` is dropped. + func() +} + +/// Optimizes the file at `path` with BOLT in-place using the given `profile`. +pub fn bolt_optimize(path: &Utf8Path, profile: &LlvmBoltProfile) -> anyhow::Result<()> { + // Copy the artifact to a new location, so that we do not use the same input and output file. + // BOLT cannot handle optimizing when the input and output is the same file, because it performs + // in-place patching. + let temp_path = tempfile::NamedTempFile::new()?.into_temp_path(); + copy_file(path, &temp_path)?; + + cmd(&["llvm-bolt"]) + .arg(temp_path.display()) + .arg("-data") + .arg(&profile.0) + .arg("-o") + .arg(path) + // Reorder basic blocks within functions + .arg("-reorder-blocks=ext-tsp") + // Reorder functions within the binary + .arg("-reorder-functions=hfsort+") + // Split function code into hot and code regions + .arg("-split-functions") + // Split as many basic blocks as possible + .arg("-split-all-cold") + // Move jump tables to a separate section + .arg("-jump-tables=move") + // Fold functions with identical code + .arg("-icf=1") + // The following flag saves about 50 MiB of libLLVM.so size. + // However, it succeeds very non-deterministically. To avoid frequent artifact size swings, + // it is kept disabled for now. + // FIXME(kobzol): try to re-enable this once BOLT in-place rewriting is merged or after + // we bump LLVM. + // Try to reuse old text segments to reduce binary size + // .arg("--use-old-text") + // Update DWARF debug info in the final binary + .arg("-update-debug-sections") + // Print optimization statistics + .arg("-dyno-stats") + .run() + .with_context(|| anyhow::anyhow!("Could not optimize {path} with BOLT"))?; + + Ok(()) +} + +/// Copies a file to a temporary location and restores it (copies it back) when it is dropped. +pub struct BackedUpFile { + original: Utf8PathBuf, + backup: tempfile::TempPath, +} + +impl BackedUpFile { + pub fn new(file: &Utf8Path) -> anyhow::Result { + let temp_path = tempfile::NamedTempFile::new()?.into_temp_path(); + copy_file(file, &temp_path)?; + Ok(Self { backup: temp_path, original: file.to_path_buf() }) + } +} + +impl Drop for BackedUpFile { + fn drop(&mut self) { + copy_file(&self.backup, &self.original).expect("Cannot restore backed up file"); + } +} diff --git a/src/tools/opt-dist/src/environment/linux.rs b/src/tools/opt-dist/src/environment/linux.rs new file mode 100644 index 0000000000000..58b7e6d23067c --- /dev/null +++ b/src/tools/opt-dist/src/environment/linux.rs @@ -0,0 +1,58 @@ +use crate::environment::Environment; +use crate::exec::cmd; +use crate::utils::io::copy_directory; +use camino::{Utf8Path, Utf8PathBuf}; + +pub(super) struct LinuxEnvironment; + +impl Environment for LinuxEnvironment { + fn python_binary(&self) -> &'static str { + "python3" + } + + fn checkout_path(&self) -> Utf8PathBuf { + Utf8PathBuf::from("/checkout") + } + + fn host_llvm_dir(&self) -> Utf8PathBuf { + Utf8PathBuf::from("/rustroot") + } + + fn opt_artifacts(&self) -> Utf8PathBuf { + Utf8PathBuf::from("/tmp/tmp-multistage/opt-artifacts") + } + + fn build_root(&self) -> Utf8PathBuf { + self.checkout_path().join("obj") + } + + fn prepare_rustc_perf(&self) -> anyhow::Result<()> { + // /tmp/rustc-perf comes from the x64 dist Dockerfile + copy_directory(Utf8Path::new("/tmp/rustc-perf"), &self.rustc_perf_dir())?; + cmd(&[self.cargo_stage_0().as_str(), "build", "-p", "collector"]) + .workdir(&self.rustc_perf_dir()) + .env("RUSTC", &self.rustc_stage_0().into_string()) + .env("RUSTC_BOOTSTRAP", "1") + .run()?; + Ok(()) + } + + fn supports_bolt(&self) -> bool { + true + } + + fn supports_shared_llvm(&self) -> bool { + true + } + + fn executable_extension(&self) -> &'static str { + "" + } + + fn skipped_tests(&self) -> &'static [&'static str] { + &[ + // Fails because of linker errors, as of June 2023. + "tests/ui/process/nofile-limit.rs", + ] + } +} diff --git a/src/tools/opt-dist/src/environment/mod.rs b/src/tools/opt-dist/src/environment/mod.rs new file mode 100644 index 0000000000000..a8650fad011d9 --- /dev/null +++ b/src/tools/opt-dist/src/environment/mod.rs @@ -0,0 +1,77 @@ +use camino::Utf8PathBuf; + +#[cfg(target_family = "unix")] +mod linux; +#[cfg(target_family = "windows")] +mod windows; + +pub trait Environment { + fn host_triple(&self) -> String { + std::env::var("PGO_HOST").expect("PGO_HOST environment variable missing") + } + + fn python_binary(&self) -> &'static str; + + /// The rustc checkout, where the compiler source is located. + fn checkout_path(&self) -> Utf8PathBuf; + + /// Path to the host LLVM used to compile LLVM in `src/llvm-project`. + fn host_llvm_dir(&self) -> Utf8PathBuf; + + /// Directory where the optimization artifacts (PGO/BOLT profiles, etc.) + /// will be stored. + fn opt_artifacts(&self) -> Utf8PathBuf; + + /// The main directory where the build occurs. + fn build_root(&self) -> Utf8PathBuf; + + fn build_artifacts(&self) -> Utf8PathBuf { + self.build_root().join("build").join(self.host_triple()) + } + + fn cargo_stage_0(&self) -> Utf8PathBuf { + self.build_artifacts() + .join("stage0") + .join("bin") + .join(format!("cargo{}", self.executable_extension())) + } + + fn rustc_stage_0(&self) -> Utf8PathBuf { + self.build_artifacts() + .join("stage0") + .join("bin") + .join(format!("rustc{}", self.executable_extension())) + } + + fn rustc_stage_2(&self) -> Utf8PathBuf { + self.build_artifacts() + .join("stage2") + .join("bin") + .join(format!("rustc{}", self.executable_extension())) + } + + /// Path to the built rustc-perf benchmark suite. + fn rustc_perf_dir(&self) -> Utf8PathBuf { + self.opt_artifacts().join("rustc-perf") + } + + /// Download and/or compile rustc-perf. + fn prepare_rustc_perf(&self) -> anyhow::Result<()>; + + fn supports_bolt(&self) -> bool; + + fn supports_shared_llvm(&self) -> bool; + + /// What is the extension of binary executables in this environment? + fn executable_extension(&self) -> &'static str; + + /// List of test paths that should be skipped when testing the optimized artifacts. + fn skipped_tests(&self) -> &'static [&'static str]; +} + +pub fn create_environment() -> Box { + #[cfg(target_family = "unix")] + return Box::new(linux::LinuxEnvironment); + #[cfg(target_family = "windows")] + return Box::new(windows::WindowsEnvironment::new()); +} diff --git a/src/tools/opt-dist/src/environment/windows.rs b/src/tools/opt-dist/src/environment/windows.rs new file mode 100644 index 0000000000000..8a9733d64965f --- /dev/null +++ b/src/tools/opt-dist/src/environment/windows.rs @@ -0,0 +1,82 @@ +use crate::environment::Environment; +use crate::exec::cmd; +use crate::utils::io::move_directory; +use camino::Utf8PathBuf; +use std::io::Cursor; +use zip::ZipArchive; + +pub(super) struct WindowsEnvironment { + checkout_dir: Utf8PathBuf, +} + +impl WindowsEnvironment { + pub fn new() -> Self { + Self { checkout_dir: std::env::current_dir().unwrap().try_into().unwrap() } + } +} + +impl Environment for WindowsEnvironment { + fn python_binary(&self) -> &'static str { + "python" + } + + fn checkout_path(&self) -> Utf8PathBuf { + self.checkout_dir.clone() + } + + fn host_llvm_dir(&self) -> Utf8PathBuf { + self.checkout_path().join("citools").join("clang-rust") + } + + fn opt_artifacts(&self) -> Utf8PathBuf { + self.checkout_path().join("opt-artifacts") + } + + fn build_root(&self) -> Utf8PathBuf { + self.checkout_path() + } + + fn prepare_rustc_perf(&self) -> anyhow::Result<()> { + // FIXME: add some mechanism for synchronization of this commit SHA with + // Linux (which builds rustc-perf in a Dockerfile) + // rustc-perf version from 2023-05-30 + const PERF_COMMIT: &str = "8b2ac3042e1ff2c0074455a0a3618adef97156b1"; + + let url = format!("https://github.com/rust-lang/rustc-perf/archive/{PERF_COMMIT}.zip"); + let response = reqwest::blocking::get(url)?.error_for_status()?.bytes()?.to_vec(); + + let mut archive = ZipArchive::new(Cursor::new(response))?; + archive.extract(self.rustc_perf_dir())?; + move_directory( + &self.rustc_perf_dir().join(format!("rustc-perf-{PERF_COMMIT}")), + &self.rustc_perf_dir(), + )?; + + cmd(&[self.cargo_stage_0().as_str(), "build", "-p", "collector"]) + .workdir(&self.rustc_perf_dir()) + .env("RUSTC", &self.rustc_stage_0().into_string()) + .env("RUSTC_BOOTSTRAP", "1") + .run()?; + + Ok(()) + } + + fn supports_bolt(&self) -> bool { + false + } + + fn supports_shared_llvm(&self) -> bool { + false + } + + fn executable_extension(&self) -> &'static str { + ".exe" + } + + fn skipped_tests(&self) -> &'static [&'static str] { + &[ + // Fails as of June 2023. + "tests\\codegen\\vec-shrink-panik.rs", + ] + } +} diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs new file mode 100644 index 0000000000000..4765dceb5dddd --- /dev/null +++ b/src/tools/opt-dist/src/exec.rs @@ -0,0 +1,179 @@ +use crate::environment::Environment; +use crate::metrics::{load_metrics, record_metrics}; +use crate::timer::TimerSection; +use crate::training::{LlvmBoltProfile, LlvmPGOProfile, RustcPGOProfile}; +use camino::{Utf8Path, Utf8PathBuf}; +use std::collections::BTreeMap; +use std::fs::File; +use std::process::{Command, Stdio}; + +#[derive(Default)] +pub struct CmdBuilder { + args: Vec, + env: BTreeMap, + workdir: Option, + output: Option, +} + +impl CmdBuilder { + pub fn arg(mut self, arg: S) -> Self { + self.args.push(arg.to_string()); + self + } + + pub fn env(mut self, name: &str, value: &str) -> Self { + self.env.insert(name.to_string(), value.to_string()); + self + } + + pub fn workdir(mut self, path: &Utf8Path) -> Self { + self.workdir = Some(path.to_path_buf()); + self + } + + pub fn redirect_output(mut self, path: Utf8PathBuf) -> Self { + self.output = Some(path); + self + } + + pub fn run(self) -> anyhow::Result<()> { + let mut cmd_str = String::new(); + cmd_str.push_str( + &self + .env + .iter() + .map(|(key, value)| format!("{key}={value}")) + .collect::>() + .join(" "), + ); + if !self.env.is_empty() { + cmd_str.push(' '); + } + cmd_str.push_str(&self.args.join(" ")); + if let Some(ref path) = self.output { + cmd_str.push_str(&format!(" > {path:?}")); + } + cmd_str.push_str(&format!( + " [at {}]", + self.workdir + .clone() + .unwrap_or_else(|| std::env::current_dir().unwrap().try_into().unwrap()) + )); + log::info!("Executing `{cmd_str}`"); + + let mut cmd = Command::new(&self.args[0]); + cmd.stdin(Stdio::null()); + cmd.args(self.args.iter().skip(1)); + for (key, value) in &self.env { + cmd.env(key, value); + } + if let Some(ref output) = self.output { + cmd.stdout(File::create(output.clone().into_std_path_buf())?); + } + if let Some(ref workdir) = self.workdir { + cmd.current_dir(workdir.clone().into_std_path_buf()); + } + let exit_status = cmd.spawn()?.wait()?; + if !exit_status.success() { + Err(anyhow::anyhow!( + "Command {cmd_str} has failed with exit code {:?}", + exit_status.code(), + )) + } else { + Ok(()) + } + } +} + +pub fn cmd(args: &[&str]) -> CmdBuilder { + assert!(!args.is_empty()); + CmdBuilder { args: args.iter().map(|s| s.to_string()).collect(), ..Default::default() } +} + +pub struct Bootstrap { + cmd: CmdBuilder, + metrics_path: Utf8PathBuf, +} + +impl Bootstrap { + pub fn build(env: &dyn Environment) -> Self { + let metrics_path = env.build_root().join("build").join("metrics.json"); + let cmd = cmd(&[ + env.python_binary(), + env.checkout_path().join("x.py").as_str(), + "build", + "--target", + &env.host_triple(), + "--host", + &env.host_triple(), + "--stage", + "2", + "library/std", + ]) + .env("RUST_BACKTRACE", "full"); + Self { cmd, metrics_path } + } + + pub fn dist(env: &dyn Environment, dist_args: &[String]) -> Self { + let metrics_path = env.build_root().join("build").join("metrics.json"); + let cmd = cmd(&dist_args.iter().map(|arg| arg.as_str()).collect::>()) + .env("RUST_BACKTRACE", "full"); + Self { cmd, metrics_path } + } + + pub fn llvm_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { + self.cmd = self + .cmd + .arg("--llvm-profile-generate") + .env("LLVM_PROFILE_DIR", profile_dir.join("prof-%p").as_str()); + self + } + + pub fn llvm_pgo_optimize(mut self, profile: &LlvmPGOProfile) -> Self { + self.cmd = self.cmd.arg("--llvm-profile-use").arg(profile.0.as_str()); + self + } + + pub fn rustc_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { + self.cmd = self.cmd.arg("--rust-profile-generate").arg(profile_dir.as_str()); + self + } + + pub fn without_llvm_lto(mut self) -> Self { + self.cmd = self + .cmd + .arg("--set") + .arg("llvm.thin-lto=false") + .arg("--set") + .arg("llvm.link-shared=true"); + self + } + + pub fn rustc_pgo_optimize(mut self, profile: &RustcPGOProfile) -> Self { + self.cmd = self.cmd.arg("--rust-profile-use").arg(profile.0.as_str()); + self + } + + pub fn with_llvm_bolt_ldflags(mut self) -> Self { + self.cmd = self.cmd.arg("--set").arg("llvm.ldflags=-Wl,-q"); + self + } + + pub fn with_bolt_profile(mut self, profile: LlvmBoltProfile) -> Self { + self.cmd = self.cmd.arg("--reproducible-artifact").arg(profile.0.as_str()); + self + } + + /// Do not rebuild rustc, and use a previously built rustc sysroot instead. + pub fn avoid_rustc_rebuild(mut self) -> Self { + self.cmd = self.cmd.arg("--keep-stage").arg("0").arg("--keep-stage").arg("1"); + self + } + + pub fn run(self, timer: &mut TimerSection) -> anyhow::Result<()> { + self.cmd.run()?; + let metrics = load_metrics(&self.metrics_path)?; + record_metrics(&metrics, timer); + Ok(()) + } +} diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs new file mode 100644 index 0000000000000..8ab19674d05b7 --- /dev/null +++ b/src/tools/opt-dist/src/main.rs @@ -0,0 +1,215 @@ +use crate::bolt::{bolt_optimize, with_bolt_instrumented}; +use anyhow::Context; +use log::LevelFilter; +use utils::io; + +use crate::environment::{create_environment, Environment}; +use crate::exec::Bootstrap; +use crate::tests::run_tests; +use crate::timer::Timer; +use crate::training::{gather_llvm_bolt_profiles, gather_llvm_profiles, gather_rustc_profiles}; +use crate::utils::io::reset_directory; +use crate::utils::{ + clear_llvm_files, format_env_variables, print_binary_sizes, print_free_disk_space, + with_log_group, +}; + +mod bolt; +mod environment; +mod exec; +mod metrics; +mod tests; +mod timer; +mod training; +mod utils; + +fn is_try_build() -> bool { + std::env::var("DIST_TRY_BUILD").unwrap_or_else(|_| "0".to_string()) != "0" +} + +fn execute_pipeline( + env: &dyn Environment, + timer: &mut Timer, + dist_args: Vec, +) -> anyhow::Result<()> { + reset_directory(&env.opt_artifacts())?; + + with_log_group("Building rustc-perf", || env.prepare_rustc_perf())?; + + // Stage 1: Build PGO instrumented rustc + // We use a normal build of LLVM, because gathering PGO profiles for LLVM and `rustc` at the + // same time can cause issues, because the host and in-tree LLVM versions can diverge. + let rustc_pgo_profile = timer.section("Stage 1 (Rustc PGO)", |stage| { + let rustc_profile_dir_root = env.opt_artifacts().join("rustc-pgo"); + + stage.section("Build PGO instrumented rustc and LLVM", |section| { + let mut builder = Bootstrap::build(env).rustc_pgo_instrument(&rustc_profile_dir_root); + + if env.supports_shared_llvm() { + // This first LLVM that we build will be thrown away after this stage, and it + // doesn't really need LTO. Without LTO, it builds in ~1 minute thanks to sccache, + // with LTO it takes almost 10 minutes. It makes the followup Rustc PGO + // instrumented/optimized build a bit slower, but it seems to be worth it. + builder = builder.without_llvm_lto(); + } + + builder.run(section) + })?; + + let profile = stage + .section("Gather profiles", |_| gather_rustc_profiles(env, &rustc_profile_dir_root))?; + print_free_disk_space()?; + + stage.section("Build PGO optimized rustc", |section| { + Bootstrap::build(env).rustc_pgo_optimize(&profile).run(section) + })?; + + Ok(profile) + })?; + + // Stage 2: Gather LLVM PGO profiles + // Here we build a PGO instrumented LLVM, reusing the previously PGO optimized rustc. + // Then we use the instrumented LLVM to gather LLVM PGO profiles. + let llvm_pgo_profile = timer.section("Stage 2 (LLVM PGO)", |stage| { + // Remove the previous, uninstrumented build of LLVM. + clear_llvm_files(env)?; + + let llvm_profile_dir_root = env.opt_artifacts().join("llvm-pgo"); + + stage.section("Build PGO instrumented LLVM", |section| { + Bootstrap::build(env) + .llvm_pgo_instrument(&llvm_profile_dir_root) + .avoid_rustc_rebuild() + .run(section) + })?; + + let profile = stage + .section("Gather profiles", |_| gather_llvm_profiles(env, &llvm_profile_dir_root))?; + + print_free_disk_space()?; + + // Proactively delete the instrumented artifacts, to avoid using them by accident in + // follow-up stages. + clear_llvm_files(env)?; + + Ok(profile) + })?; + + let llvm_bolt_profile = if env.supports_bolt() { + // Stage 3: Build BOLT instrumented LLVM + // We build a PGO optimized LLVM in this step, then instrument it with BOLT and gather BOLT profiles. + // Note that we don't remove LLVM artifacts after this step, so that they are reused in the final dist build. + // BOLT instrumentation is performed "on-the-fly" when the LLVM library is copied to the sysroot of rustc, + // therefore the LLVM artifacts on disk are not "tainted" with BOLT instrumentation and they can be reused. + timer.section("Stage 3 (LLVM BOLT)", |stage| { + stage.section("Build PGO optimized LLVM", |stage| { + Bootstrap::build(env) + .with_llvm_bolt_ldflags() + .llvm_pgo_optimize(&llvm_pgo_profile) + .avoid_rustc_rebuild() + .run(stage) + })?; + + // Find the path to the `libLLVM.so` file + let llvm_lib = io::find_file_in_dir( + &env.build_artifacts().join("stage2").join("lib"), + "libLLVM", + ".so", + )?; + + // Instrument it and gather profiles + let profile = with_bolt_instrumented(&llvm_lib, || { + stage.section("Gather profiles", |_| gather_llvm_bolt_profiles(env)) + })?; + print_free_disk_space()?; + + // Now optimize the library with BOLT. The `libLLVM-XXX.so` library is actually hard-linked + // from several places, and this specific path (`llvm_lib`) will *not* be packaged into + // the final dist build. However, when BOLT optimizes an artifact, it does so *in-place*, + // therefore it will actually optimize all the hard links, which means that the final + // packaged `libLLVM.so` file *will* be BOLT optimized. + bolt_optimize(&llvm_lib, &profile).context("Could not optimize LLVM with BOLT")?; + + // LLVM is not being cleared here, we want to use the BOLT-optimized LLVM + Ok(Some(profile)) + })? + } else { + None + }; + + let mut dist = Bootstrap::dist(env, &dist_args) + .llvm_pgo_optimize(&llvm_pgo_profile) + .rustc_pgo_optimize(&rustc_pgo_profile) + .avoid_rustc_rebuild(); + + if let Some(llvm_bolt_profile) = llvm_bolt_profile { + dist = dist.with_bolt_profile(llvm_bolt_profile); + } + + // Final stage: Assemble the dist artifacts + // The previous PGO optimized rustc build and PGO optimized LLVM builds should be reused. + timer.section("Stage 4 (final build)", |stage| dist.run(stage))?; + + // After dist has finished, run a subset of the test suite on the optimized artifacts to discover + // possible regressions. + // The tests are not executed for try builds, which can be in various broken states, so we don't + // want to gatekeep them with tests. + if !is_try_build() { + timer.section("Run tests", |_| run_tests(env))?; + } + + Ok(()) +} + +fn main() -> anyhow::Result<()> { + // Make sure that we get backtraces for easier debugging in CI + std::env::set_var("RUST_BACKTRACE", "1"); + + env_logger::builder() + .filter_level(LevelFilter::Info) + .format_timestamp_millis() + .parse_default_env() + .init(); + + let mut build_args: Vec = std::env::args().skip(1).collect(); + println!("Running optimized build pipeline with args `{}`", build_args.join(" ")); + + with_log_group("Environment values", || { + println!("Environment values\n{}", format_env_variables()); + }); + + with_log_group("Printing config.toml", || { + if let Ok(config) = std::fs::read_to_string("config.toml") { + println!("Contents of `config.toml`:\n{config}"); + } + }); + + // Skip components that are not needed for try builds to speed them up + if is_try_build() { + log::info!("Skipping building of unimportant components for a try build"); + for target in [ + "rust-docs", + "rustc-docs", + "rust-docs-json", + "rust-analyzer", + "rustc-src", + "clippy", + "miri", + "rustfmt", + ] { + build_args.extend(["--skip".to_string(), target.to_string()]); + } + } + + let mut timer = Timer::new(); + let env = create_environment(); + + let result = execute_pipeline(env.as_ref(), &mut timer, build_args); + log::info!("Timer results\n{}", timer.format_stats()); + + print_free_disk_space()?; + result.context("Optimized build pipeline has failed")?; + print_binary_sizes(env.as_ref())?; + + Ok(()) +} diff --git a/src/tools/opt-dist/src/metrics.rs b/src/tools/opt-dist/src/metrics.rs new file mode 100644 index 0000000000000..cabe07eda329f --- /dev/null +++ b/src/tools/opt-dist/src/metrics.rs @@ -0,0 +1,106 @@ +use crate::timer::TimerSection; +use build_helper::metrics::{JsonNode, JsonRoot}; +use camino::Utf8Path; +use std::time::Duration; + +#[derive(Clone, Debug)] +pub struct BuildStep { + r#type: String, + children: Vec, + duration: Duration, +} + +impl BuildStep { + pub fn find_all_by_type(&self, r#type: &str) -> Vec<&BuildStep> { + let mut result = Vec::new(); + self.find_by_type(r#type, &mut result); + result + } + fn find_by_type<'a>(&'a self, r#type: &str, result: &mut Vec<&'a BuildStep>) { + if self.r#type == r#type { + result.push(self); + } + for child in &self.children { + child.find_by_type(r#type, result); + } + } +} + +/// Loads the metrics of the most recent bootstrap execution from a metrics.json file. +pub fn load_metrics(path: &Utf8Path) -> anyhow::Result { + let content = std::fs::read(path.as_std_path())?; + let mut metrics = serde_json::from_slice::(&content)?; + let invocation = metrics + .invocations + .pop() + .ok_or_else(|| anyhow::anyhow!("No bootstrap invocation found in metrics file"))?; + + fn parse(node: JsonNode) -> Option { + match node { + JsonNode::RustbuildStep { + type_: kind, + children, + duration_excluding_children_sec, + .. + } => { + let children: Vec<_> = children.into_iter().filter_map(parse).collect(); + let children_duration = children.iter().map(|c| c.duration).sum::(); + Some(BuildStep { + r#type: kind.to_string(), + children, + duration: children_duration + + Duration::from_secs_f64(duration_excluding_children_sec), + }) + } + JsonNode::TestSuite(_) => None, + } + } + + let duration = Duration::from_secs_f64(invocation.duration_including_children_sec); + let children: Vec<_> = invocation.children.into_iter().filter_map(parse).collect(); + Ok(BuildStep { r#type: "root".to_string(), children, duration }) +} + +/// Logs the individual metrics in a table and add Rustc and LLVM durations to the passed +/// timer. +pub fn record_metrics(metrics: &BuildStep, timer: &mut TimerSection) { + let llvm_steps = metrics.find_all_by_type("bootstrap::llvm::Llvm"); + let llvm_duration: Duration = llvm_steps.into_iter().map(|s| s.duration).sum(); + + let rustc_steps = metrics.find_all_by_type("bootstrap::compile::Rustc"); + let rustc_duration: Duration = rustc_steps.into_iter().map(|s| s.duration).sum(); + + // The LLVM step is part of the Rustc step + let rustc_duration = rustc_duration.saturating_sub(llvm_duration); + + if !llvm_duration.is_zero() { + timer.add_duration("LLVM", llvm_duration); + } + if !rustc_duration.is_zero() { + timer.add_duration("Rustc", rustc_duration); + } + + log_metrics(metrics); +} + +fn log_metrics(metrics: &BuildStep) { + use std::fmt::Write; + + let mut substeps: Vec<(u32, &BuildStep)> = Vec::new(); + + fn visit<'a>(step: &'a BuildStep, level: u32, substeps: &mut Vec<(u32, &'a BuildStep)>) { + substeps.push((level, step)); + for child in &step.children { + visit(child, level + 1, substeps); + } + } + + visit(metrics, 0, &mut substeps); + + let mut output = String::new(); + for (level, step) in substeps { + let label = format!("{}{}", ".".repeat(level as usize), step.r#type); + writeln!(output, "{label:<65}{:>8.2}s", step.duration.as_secs_f64()).unwrap(); + } + log::info!("Build step durations\n{output}"); +} diff --git a/src/tools/opt-dist/src/tests.rs b/src/tools/opt-dist/src/tests.rs new file mode 100644 index 0000000000000..3dd1a3223f5a5 --- /dev/null +++ b/src/tools/opt-dist/src/tests.rs @@ -0,0 +1,114 @@ +use crate::environment::Environment; +use crate::exec::cmd; +use crate::utils::io::{copy_directory, find_file_in_dir, unpack_archive}; +use anyhow::Context; +use camino::{Utf8Path, Utf8PathBuf}; + +/// Run tests on optimized dist artifacts. +pub fn run_tests(env: &dyn Environment) -> anyhow::Result<()> { + // After `dist` is executed, we extract its archived components into a sysroot directory, + // and then use that extracted rustc as a stage0 compiler. + // Then we run a subset of tests using that compiler, to have a basic smoke test which checks + // whether the optimization pipeline hasn't broken something. + let build_dir = env.build_root().join("build"); + let dist_dir = build_dir.join("dist"); + let unpacked_dist_dir = build_dir.join("unpacked-dist"); + std::fs::create_dir_all(&unpacked_dist_dir)?; + + let extract_dist_dir = |name: &str| -> anyhow::Result { + unpack_archive(&dist_dir.join(format!("{name}.tar.xz")), &unpacked_dist_dir)?; + let extracted_path = unpacked_dist_dir.join(name); + assert!(extracted_path.is_dir()); + Ok(extracted_path) + }; + let host_triple = env.host_triple(); + let version = find_dist_version(&dist_dir)?; + + // Extract rustc, libstd, cargo and src archives to create the optimized sysroot + let rustc_dir = extract_dist_dir(&format!("rustc-{version}-{host_triple}"))?.join("rustc"); + let libstd_dir = extract_dist_dir(&format!("rust-std-{version}-{host_triple}"))? + .join(format!("rust-std-{host_triple}")); + let cargo_dir = extract_dist_dir(&format!("cargo-{version}-{host_triple}"))?.join("cargo"); + let extracted_src_dir = extract_dist_dir(&format!("rust-src-{version}"))?.join("rust-src"); + + // We need to manually copy libstd to the extracted rustc sysroot + copy_directory( + &libstd_dir.join("lib").join("rustlib").join(&host_triple).join("lib"), + &rustc_dir.join("lib").join("rustlib").join(&host_triple).join("lib"), + )?; + + // Extract sources - they aren't in the `rustc-nightly-{host}` tarball, so we need to manually copy libstd + // sources to the extracted sysroot. We need sources available so that `-Zsimulate-remapped-rust-src-base` + // works correctly. + copy_directory( + &extracted_src_dir.join("lib").join("rustlib").join("src"), + &rustc_dir.join("lib").join("rustlib").join("src"), + )?; + + let rustc_path = rustc_dir.join("bin").join(format!("rustc{}", env.executable_extension())); + assert!(rustc_path.is_file()); + let cargo_path = cargo_dir.join("bin").join(format!("cargo{}", env.executable_extension())); + assert!(cargo_path.is_file()); + + // Specify path to a LLVM config so that LLVM is not rebuilt. + // It doesn't really matter which LLVM config we choose, because no sysroot will be compiled. + let llvm_config = env + .build_artifacts() + .join("llvm") + .join("bin") + .join(format!("llvm-config{}", env.executable_extension())); + assert!(llvm_config.is_file()); + + let config_content = format!( + r#"profile = "user" +changelog-seen = 2 + +[build] +rustc = "{rustc}" +cargo = "{cargo}" + +[target.{host_triple}] +llvm-config = "{llvm_config}" +"#, + rustc = rustc_path.to_string().replace('\\', "/"), + cargo = cargo_path.to_string().replace('\\', "/"), + llvm_config = llvm_config.to_string().replace('\\', "/") + ); + log::info!("Using following `config.toml` for running tests:\n{config_content}"); + + // Simulate a stage 0 compiler with the extracted optimized dist artifacts. + std::fs::write("config.toml", config_content)?; + + let x_py = env.checkout_path().join("x.py"); + let mut args = vec![ + env.python_binary(), + x_py.as_str(), + "test", + "--stage", + "0", + "tests/assembly", + "tests/codegen", + "tests/codegen-units", + "tests/incremental", + "tests/mir-opt", + "tests/pretty", + "tests/run-pass-valgrind", + "tests/ui", + ]; + for test_path in env.skipped_tests() { + args.extend(["--skip", test_path]); + } + cmd(&args).env("COMPILETEST_FORCE_STAGE0", "1").run().context("Cannot execute tests") +} + +/// Tries to find the version of the dist artifacts (either nightly, beta, or 1.XY.Z). +fn find_dist_version(directory: &Utf8Path) -> anyhow::Result { + // Lookup a known file with a unique prefix and extract the version from its filename + let archive = find_file_in_dir(directory, "reproducible-artifacts-", ".tar.xz")? + .file_name() + .unwrap() + .to_string(); + let (version, _) = + archive.strip_prefix("reproducible-artifacts-").unwrap().split_once("-").unwrap(); + Ok(version.to_string()) +} diff --git a/src/tools/opt-dist/src/timer.rs b/src/tools/opt-dist/src/timer.rs new file mode 100644 index 0000000000000..2b29ba8d59f0f --- /dev/null +++ b/src/tools/opt-dist/src/timer.rs @@ -0,0 +1,167 @@ +use std::ops::{Deref, DerefMut}; +use std::time::{Duration, SystemTime}; + +pub struct Timer { + root: TimerSection, +} + +impl Timer { + pub fn new() -> Self { + Timer { root: TimerSection::new(None) } + } + + pub fn format_stats(&self) -> String { + use std::fmt::Write; + + let mut items = Vec::new(); + for (name, child) in &self.root.children { + match child { + SectionEntry::SubSection(section) => { + section.collect_levels(0, name, &mut items); + } + SectionEntry::Duration(duration) => items.push((0, name, *duration)), + } + } + + let rows: Vec<(String, Duration)> = items + .into_iter() + .map(|(level, name, duration)| (format!("{}{name}:", " ".repeat(level)), duration)) + .collect(); + + let total_duration = self.total_duration(); + let total_duration_label = "Total duration:".to_string(); + + const SPACE_AFTER_LABEL: usize = 2; + let max_label_length = 16.max(rows.iter().map(|(label, _)| label.len()).max().unwrap_or(0)) + + SPACE_AFTER_LABEL; + + let table_width = max_label_length + 23; + let divider = "-".repeat(table_width); + + let mut output = String::new(); + writeln!(output, "{divider}").unwrap(); + for (label, duration) in rows { + let pct = (duration.as_millis() as f64 / total_duration.as_millis() as f64) * 100.0; + let duration_fmt = format!("{:>12.2}s ({pct:>5.2}%)", duration.as_secs_f64()); + writeln!(output, "{label:<0$} {duration_fmt}", max_label_length).unwrap(); + } + output.push('\n'); + + let total_duration = Duration::new(total_duration.as_secs(), 0); + let total_duration = format!( + "{:>1$}", + humantime::format_duration(total_duration).to_string(), + table_width - total_duration_label.len() + ); + writeln!(output, "{total_duration_label}{total_duration}").unwrap(); + + writeln!(output, "{divider}").unwrap(); + output + } +} + +impl Deref for Timer { + type Target = TimerSection; + + fn deref(&self) -> &Self::Target { + &self.root + } +} + +impl DerefMut for Timer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.root + } +} + +pub struct TimerSection { + name: Option, + children: Vec<(String, SectionEntry)>, + duration_excluding_children: Duration, +} + +impl TimerSection { + pub fn new(name: Option) -> Self { + TimerSection { + name, + children: Default::default(), + duration_excluding_children: Duration::ZERO, + } + } + + pub fn section anyhow::Result, R>( + &mut self, + name: &str, + func: F, + ) -> anyhow::Result { + let full_name = match &self.name { + Some(current_name) => { + format!("{current_name} > {name}") + } + None => name.to_string(), + }; + log::info!("Section `{full_name}` starts"); + let mut child = TimerSection { + name: Some(full_name.clone()), + children: Default::default(), + duration_excluding_children: Duration::ZERO, + }; + + let start = SystemTime::now(); + let result = func(&mut child); + let duration = start.elapsed().unwrap(); + + let msg = match result { + Ok(_) => "OK", + Err(_) => "FAIL", + }; + + child.duration_excluding_children = duration.saturating_sub(child.total_duration()); + + log::info!("Section `{full_name}` ended: {msg} ({:.2}s)`", duration.as_secs_f64()); + self.children.push((name.to_string(), SectionEntry::SubSection(child))); + result + } + + pub fn add_duration(&mut self, name: &str, duration: Duration) { + self.children.push((name.to_string(), SectionEntry::Duration(duration))); + } + + fn total_duration(&self) -> Duration { + self.duration_excluding_children + + self.children.iter().map(|(_, child)| child.total_duration()).sum::() + } + + fn collect_levels<'a>( + &'a self, + level: usize, + name: &'a str, + items: &mut Vec<(usize, &'a str, Duration)>, + ) { + items.push((level, name, self.total_duration())); + for (name, child) in &self.children { + match &child { + SectionEntry::Duration(duration) => { + items.push((level + 1, name, *duration)); + } + SectionEntry::SubSection(section) => { + section.collect_levels(level + 1, name, items); + } + } + } + } +} + +enum SectionEntry { + Duration(Duration), + SubSection(TimerSection), +} + +impl SectionEntry { + fn total_duration(&self) -> Duration { + match self { + SectionEntry::Duration(duration) => *duration, + SectionEntry::SubSection(timer) => timer.total_duration(), + } + } +} diff --git a/src/tools/opt-dist/src/training.rs b/src/tools/opt-dist/src/training.rs new file mode 100644 index 0000000000000..59c73fbd6953e --- /dev/null +++ b/src/tools/opt-dist/src/training.rs @@ -0,0 +1,223 @@ +use crate::environment::Environment; +use crate::exec::{cmd, CmdBuilder}; +use crate::utils::io::{count_files, delete_directory}; +use crate::utils::with_log_group; +use anyhow::Context; +use camino::{Utf8Path, Utf8PathBuf}; +use humansize::BINARY; + +const LLVM_PGO_CRATES: &[&str] = &[ + "syn-1.0.89", + "cargo-0.60.0", + "serde-1.0.136", + "ripgrep-13.0.0", + "regex-1.5.5", + "clap-3.1.6", + "hyper-0.14.18", +]; + +const RUSTC_PGO_CRATES: &[&str] = &[ + "externs", + "ctfe-stress-5", + "cargo-0.60.0", + "token-stream-stress", + "match-stress", + "tuple-stress", + "diesel-1.4.8", + "bitmaps-3.1.0", +]; + +const LLVM_BOLT_CRATES: &[&str] = LLVM_PGO_CRATES; + +fn init_compiler_benchmarks( + env: &dyn Environment, + profiles: &[&str], + scenarios: &[&str], + crates: &[&str], +) -> CmdBuilder { + // Run rustc-perf benchmarks + // Benchmark using profile_local with eprintln, which essentially just means + // don't actually benchmark -- just make sure we run rustc a bunch of times. + cmd(&[ + env.cargo_stage_0().as_str(), + "run", + "-p", + "collector", + "--bin", + "collector", + "--", + "profile_local", + "eprintln", + env.rustc_stage_2().as_str(), + "--id", + "Test", + "--cargo", + env.cargo_stage_0().as_str(), + "--profiles", + profiles.join(",").as_str(), + "--scenarios", + scenarios.join(",").as_str(), + "--include", + crates.join(",").as_str(), + ]) + .env("RUST_LOG", "collector=debug") + .env("RUSTC", env.rustc_stage_0().as_str()) + .env("RUSTC_BOOTSTRAP", "1") + .workdir(&env.rustc_perf_dir()) +} + +/// Describes which `llvm-profdata` binary should be used for merging PGO profiles. +enum LlvmProfdata { + /// Use llvm-profdata from the host toolchain (i.e. from LLVM provided externally). + Host, + /// Use llvm-profdata from the target toolchain (i.e. from LLVM built from `src/llvm-project`). + Target, +} + +fn merge_llvm_profiles( + env: &dyn Environment, + merged_path: &Utf8Path, + profile_dir: &Utf8Path, + profdata: LlvmProfdata, +) -> anyhow::Result<()> { + let llvm_profdata = match profdata { + LlvmProfdata::Host => env.host_llvm_dir().join("bin/llvm-profdata"), + LlvmProfdata::Target => env + .build_artifacts() + .join("llvm") + .join("build") + .join(format!("bin/llvm-profdata{}", env.executable_extension())), + }; + + cmd(&[llvm_profdata.as_str(), "merge", "-o", merged_path.as_str(), profile_dir.as_str()]) + .run() + .context("Cannot merge LLVM profiles")?; + Ok(()) +} + +fn log_profile_stats( + name: &str, + merged_profile: &Utf8Path, + profile_root: &Utf8Path, +) -> anyhow::Result<()> { + log::info!("{name} PGO statistics"); + log::info!( + "{merged_profile}: {}", + humansize::format_size(std::fs::metadata(merged_profile.as_std_path())?.len(), BINARY) + ); + log::info!( + "{profile_root}: {}", + humansize::format_size(fs_extra::dir::get_size(profile_root.as_std_path())?, BINARY) + ); + log::info!("Profile file count: {}", count_files(profile_root)?); + Ok(()) +} + +pub struct LlvmPGOProfile(pub Utf8PathBuf); + +pub fn gather_llvm_profiles( + env: &dyn Environment, + profile_root: &Utf8Path, +) -> anyhow::Result { + log::info!("Running benchmarks with PGO instrumented LLVM"); + + with_log_group("Running benchmarks", || { + init_compiler_benchmarks(env, &["Debug", "Opt"], &["Full"], LLVM_PGO_CRATES) + .run() + .context("Cannot gather LLVM PGO profiles") + })?; + + let merged_profile = env.opt_artifacts().join("llvm-pgo.profdata"); + log::info!("Merging LLVM PGO profiles to {merged_profile}"); + + merge_llvm_profiles(env, &merged_profile, profile_root, LlvmProfdata::Host)?; + log_profile_stats("LLVM", &merged_profile, profile_root)?; + + // We don't need the individual .profraw files now that they have been merged + // into a final .profdata + delete_directory(profile_root)?; + + Ok(LlvmPGOProfile(merged_profile)) +} + +pub struct RustcPGOProfile(pub Utf8PathBuf); + +pub fn gather_rustc_profiles( + env: &dyn Environment, + profile_root: &Utf8Path, +) -> anyhow::Result { + log::info!("Running benchmarks with PGO instrumented rustc"); + + // The profile data is written into a single filepath that is being repeatedly merged when each + // rustc invocation ends. Empirically, this can result in some profiling data being lost. That's + // why we override the profile path to include the PID. This will produce many more profiling + // files, but the resulting profile will produce a slightly faster rustc binary. + let profile_template = profile_root.join("default_%m_%p.profraw"); + + // Here we're profiling the `rustc` frontend, so we also include `Check`. + // The benchmark set includes various stress tests that put the frontend under pressure. + with_log_group("Running benchmarks", || { + init_compiler_benchmarks(env, &["Check", "Debug", "Opt"], &["All"], RUSTC_PGO_CRATES) + .env("LLVM_PROFILE_FILE", profile_template.as_str()) + .run() + .context("Cannot gather rustc PGO profiles") + })?; + + let merged_profile = env.opt_artifacts().join("rustc-pgo.profdata"); + log::info!("Merging Rustc PGO profiles to {merged_profile}"); + + merge_llvm_profiles(env, &merged_profile, profile_root, LlvmProfdata::Target)?; + log_profile_stats("Rustc", &merged_profile, profile_root)?; + + // We don't need the individual .profraw files now that they have been merged + // into a final .profdata + delete_directory(profile_root)?; + + Ok(RustcPGOProfile(merged_profile)) +} + +pub struct LlvmBoltProfile(pub Utf8PathBuf); + +pub fn gather_llvm_bolt_profiles(env: &dyn Environment) -> anyhow::Result { + log::info!("Running benchmarks with BOLT instrumented LLVM"); + + with_log_group("Running benchmarks", || { + init_compiler_benchmarks(env, &["Check", "Debug", "Opt"], &["Full"], LLVM_BOLT_CRATES) + .run() + .context("Cannot gather LLVM BOLT profiles") + })?; + + let merged_profile = env.opt_artifacts().join("llvm-bolt.profdata"); + let profile_root = Utf8PathBuf::from("/tmp/prof.fdata"); + log::info!("Merging LLVM BOLT profiles to {merged_profile}"); + + let profiles: Vec<_> = + glob::glob(&format!("{profile_root}*"))?.into_iter().collect::, _>>()?; + + let mut merge_args = vec!["merge-fdata"]; + merge_args.extend(profiles.iter().map(|p| p.to_str().unwrap())); + + with_log_group("Merging BOLT profiles", || { + cmd(&merge_args) + .redirect_output(merged_profile.clone()) + .run() + .context("Cannot merge BOLT profiles") + })?; + + log::info!("LLVM BOLT statistics"); + log::info!( + "{merged_profile}: {}", + humansize::format_size(std::fs::metadata(merged_profile.as_std_path())?.len(), BINARY) + ); + + let size = profiles + .iter() + .map(|p| std::fs::metadata(p).map(|metadata| metadata.len())) + .collect::, _>>()? + .into_iter() + .sum::(); + log::info!("{profile_root}: {}", humansize::format_size(size, BINARY)); + log::info!("Profile file count: {}", profiles.len()); + + Ok(LlvmBoltProfile(merged_profile)) +} diff --git a/src/tools/opt-dist/src/utils/io.rs b/src/tools/opt-dist/src/utils/io.rs new file mode 100644 index 0000000000000..8bd516fa349b0 --- /dev/null +++ b/src/tools/opt-dist/src/utils/io.rs @@ -0,0 +1,88 @@ +use anyhow::Context; +use camino::{Utf8Path, Utf8PathBuf}; +use fs_extra::dir::CopyOptions; +use std::fs::File; +use std::path::Path; + +/// Delete and re-create the directory. +pub fn reset_directory(path: &Utf8Path) -> anyhow::Result<()> { + log::info!("Resetting directory {path}"); + let _ = std::fs::remove_dir(path); + std::fs::create_dir_all(path)?; + Ok(()) +} + +pub fn copy_directory(src: &Utf8Path, dst: &Utf8Path) -> anyhow::Result<()> { + log::info!("Copying directory {src} to {dst}"); + fs_extra::dir::copy(src, dst, &CopyOptions::default().copy_inside(true))?; + Ok(()) +} + +pub fn copy_file, D: AsRef>(src: S, dst: D) -> anyhow::Result<()> { + log::info!("Copying file {} to {}", src.as_ref().display(), dst.as_ref().display()); + std::fs::copy(src.as_ref(), dst.as_ref())?; + Ok(()) +} + +#[allow(unused)] +pub fn move_directory(src: &Utf8Path, dst: &Utf8Path) -> anyhow::Result<()> { + log::info!("Moving directory {src} to {dst}"); + fs_extra::dir::move_dir(src, dst, &CopyOptions::default().content_only(true))?; + Ok(()) +} + +/// Counts all children of a directory (non-recursively). +pub fn count_files(dir: &Utf8Path) -> anyhow::Result { + Ok(std::fs::read_dir(dir)?.count() as u64) +} + +pub fn delete_directory(path: &Utf8Path) -> anyhow::Result<()> { + log::info!("Deleting directory `{path}`"); + std::fs::remove_dir_all(path.as_std_path()) + .context(format!("Cannot remove directory {path}"))?; + Ok(()) +} + +pub fn unpack_archive(path: &Utf8Path, dest_dir: &Utf8Path) -> anyhow::Result<()> { + log::info!("Unpacking directory `{path}` into `{dest_dir}`"); + + assert!(path.as_str().ends_with(".tar.xz")); + let file = File::open(path.as_std_path())?; + let file = xz::read::XzDecoder::new(file); + let mut archive = tar::Archive::new(file); + archive.unpack(dest_dir.as_std_path())?; + Ok(()) +} + +/// Returns paths in the given `dir` (non-recursively), optionally with the given `suffix`. +/// The `suffix` should contain the leading dot. +pub fn get_files_from_dir( + dir: &Utf8Path, + suffix: Option<&str>, +) -> anyhow::Result> { + let path = format!("{dir}/*{}", suffix.unwrap_or("")); + + Ok(glob::glob(&path)? + .into_iter() + .map(|p| p.map(|p| Utf8PathBuf::from_path_buf(p).unwrap())) + .collect::, _>>()?) +} + +/// Finds a single file in the specified `directory` with the given `prefix` and `suffix`. +pub fn find_file_in_dir( + directory: &Utf8Path, + prefix: &str, + suffix: &str, +) -> anyhow::Result { + let files = glob::glob(&format!("{directory}/{prefix}*{suffix}"))? + .into_iter() + .collect::, _>>()?; + match files.len() { + 0 => Err(anyhow::anyhow!("No file with prefix {prefix} found in {directory}")), + 1 => Ok(Utf8PathBuf::from_path_buf(files[0].clone()).unwrap()), + _ => Err(anyhow::anyhow!( + "More than one file with prefix {prefix} found in {directory}: {:?}", + files + )), + } +} diff --git a/src/tools/opt-dist/src/utils/mod.rs b/src/tools/opt-dist/src/utils/mod.rs new file mode 100644 index 0000000000000..9a3df15e302bb --- /dev/null +++ b/src/tools/opt-dist/src/utils/mod.rs @@ -0,0 +1,75 @@ +pub mod io; + +use crate::environment::Environment; +use crate::utils::io::{delete_directory, get_files_from_dir}; +use humansize::{format_size, BINARY}; +use sysinfo::{DiskExt, RefreshKind, System, SystemExt}; + +pub fn format_env_variables() -> String { + let vars = std::env::vars().map(|(key, value)| format!("{key}={value}")).collect::>(); + vars.join("\n") +} + +pub fn print_free_disk_space() -> anyhow::Result<()> { + let sys = System::new_with_specifics(RefreshKind::default().with_disks_list().with_disks()); + let available_space: u64 = sys.disks().iter().map(|d| d.available_space()).sum(); + let total_space: u64 = sys.disks().iter().map(|d| d.total_space()).sum(); + let used_space = total_space - available_space; + + log::info!( + "Free disk space: {} out of total {} ({:.2}% used)", + humansize::format_size(available_space, BINARY), + humansize::format_size(total_space, BINARY), + (used_space as f64 / total_space as f64) * 100.0 + ); + Ok(()) +} + +pub fn print_binary_sizes(env: &dyn Environment) -> anyhow::Result<()> { + use std::fmt::Write; + + let root = env.build_artifacts().join("stage2"); + + let mut files = get_files_from_dir(&root.join("bin"), None)?; + files.extend(get_files_from_dir(&root.join("lib"), Some(".so"))?); + files.sort_unstable(); + + let mut output = String::new(); + for file in files { + let size = std::fs::metadata(file.as_std_path())?.len(); + let size_formatted = format_size(size, BINARY); + let name = format!("{}:", file.file_name().unwrap()); + writeln!(output, "{name:<50}{size_formatted:>10}")?; + } + + log::info!("Rustc artifact size\n{output}"); + + Ok(()) +} + +pub fn clear_llvm_files(env: &dyn Environment) -> anyhow::Result<()> { + // Bootstrap currently doesn't support rebuilding LLVM when PGO options + // change (or any other llvm-related options); so just clear out the relevant + // directories ourselves. + log::info!("Clearing LLVM build files"); + delete_directory(&env.build_artifacts().join("llvm"))?; + delete_directory(&env.build_artifacts().join("lld"))?; + Ok(()) +} + +/// Wraps all output produced within the `func` closure in a CI output group, if we're running in +/// CI. +pub fn with_log_group R, R>(group: &str, func: F) -> R { + if is_in_ci() { + println!("::group::{group}"); + let result = func(); + println!("::endgroup::"); + result + } else { + func() + } +} + +fn is_in_ci() -> bool { + std::env::var("GITHUB_ACTIONS").is_ok() +} diff --git a/src/tools/publish_toolstate.py b/src/tools/publish_toolstate.py index 2018c239ba068..f9421117eaa2e 100755 --- a/src/tools/publish_toolstate.py +++ b/src/tools/publish_toolstate.py @@ -22,7 +22,7 @@ import urllib.request as urllib2 from urllib.error import HTTPError try: - import typing + import typing # noqa: F401 FIXME: py2 except ImportError: pass @@ -152,8 +152,8 @@ def update_latest( latest = json.load(f, object_pairs_hook=collections.OrderedDict) current_status = { - os: read_current_status(current_commit, 'history/' + os + '.tsv') - for os in ['windows', 'linux'] + os_: read_current_status(current_commit, 'history/' + os_ + '.tsv') + for os_ in ['windows', 'linux'] } slug = 'rust-lang/rust' @@ -170,10 +170,10 @@ def update_latest( changed = False create_issue_for_status = None # set to the status that caused the issue - for os, s in current_status.items(): - old = status[os] + for os_, s in current_status.items(): + old = status[os_] new = s.get(tool, old) - status[os] = new + status[os_] = new maintainers = ' '.join('@'+name for name in MAINTAINERS.get(tool, ())) # comparing the strings, but they are ordered appropriately: # "test-pass" > "test-fail" > "build-fail" @@ -181,12 +181,12 @@ def update_latest( # things got fixed or at least the status quo improved changed = True message += '🎉 {} on {}: {} → {} (cc {}).\n' \ - .format(tool, os, old, new, maintainers) + .format(tool, os_, old, new, maintainers) elif new < old: # tests or builds are failing and were not failing before changed = True title = '💔 {} on {}: {} → {}' \ - .format(tool, os, old, new) + .format(tool, os_, old, new) message += '{} (cc {}).\n' \ .format(title, maintainers) # See if we need to create an issue. diff --git a/src/tools/rls/Cargo.toml b/src/tools/rls/Cargo.toml index b84647eb33272..b7aa659c25a94 100644 --- a/src/tools/rls/Cargo.toml +++ b/src/tools/rls/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" license = "Apache-2.0/MIT" [dependencies] -serde = { version = "1.0.143", features = ["derive"] } serde_json = "1.0.83" diff --git a/src/tools/rls/src/main.rs b/src/tools/rls/src/main.rs index f96f1325d9636..d7be108e89c00 100644 --- a/src/tools/rls/src/main.rs +++ b/src/tools/rls/src/main.rs @@ -3,7 +3,7 @@ //! This is a small stub that replaces RLS to alert the user that RLS is no //! longer available. -use serde::Deserialize; +use serde_json::Value; use std::error::Error; use std::io::BufRead; use std::io::Write; @@ -21,7 +21,6 @@ fn main() { } } -#[derive(Deserialize)] struct Message { method: Option, } @@ -88,8 +87,10 @@ fn read_message_raw(reader: &mut R) -> Result fn read_message(reader: &mut R) -> Result> { let m = read_message_raw(reader)?; - match serde_json::from_str(&m) { - Ok(m) => Ok(m), + match serde_json::from_str::(&m) { + Ok(message) => Ok(Message { + method: message.get("method").and_then(|value| value.as_str().map(String::from)), + }), Err(e) => Err(format!("failed to parse message {m}\n{e}").into()), } } diff --git a/src/tools/rust-analyzer/.editorconfig b/src/tools/rust-analyzer/.editorconfig index 314f79d3f901c..f00ade5fd8266 100644 --- a/src/tools/rust-analyzer/.editorconfig +++ b/src/tools/rust-analyzer/.editorconfig @@ -7,13 +7,10 @@ trim_trailing_whitespace = true end_of_line = lf insert_final_newline = true indent_style = space - -[*.{rs,toml}] indent_size = 4 -[*.ts] -indent_size = 4 -[*.js] -indent_size = 4 -[*.json] -indent_size = 4 +[*.md] +indent_size = 2 + +[*.{yml, yaml}] +indent_size = 2 diff --git a/src/tools/rust-analyzer/.github/actions/github-release/README.md b/src/tools/rust-analyzer/.github/actions/github-release/README.md index 7b50d002001ee..c8ff3ec6e52c8 100644 --- a/src/tools/rust-analyzer/.github/actions/github-release/README.md +++ b/src/tools/rust-analyzer/.github/actions/github-release/README.md @@ -10,7 +10,7 @@ perform github releases but they all tend to have their set of drawbacks. Additionally nothing handles deleting releases which we need for our rolling `dev` release. -To handle all this this action rolls-its-own implementation using the +To handle all this, this action rolls its own implementation using the actions/toolkit repository and packages published there. These run in a Docker container and take various inputs to orchestrate the release from the build. diff --git a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml index 279f86b458dff..310a8a5be7ad5 100644 --- a/src/tools/rust-analyzer/.github/workflows/autopublish.yaml +++ b/src/tools/rust-analyzer/.github/workflows/autopublish.yaml @@ -28,11 +28,11 @@ jobs: - name: Publish Crates env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - PATCH: ${{ github.run_number }} + RUN_NUMBER: ${{ github.run_number }} shell: bash run: | git config --global user.email "runner@gha.local" - git config --global user.name "Github Action" + git config --global user.name "GitHub Action" rm Cargo.lock # Fix names for crates that were published before switch to kebab-case. cargo workspaces rename --from base-db base_db @@ -49,8 +49,8 @@ jobs: cargo workspaces rename --from project-model project_model cargo workspaces rename --from test-utils test_utils cargo workspaces rename --from text-edit text_edit - cargo workspaces rename ra_ap_%n # Remove library crates from the workspaces so we don't auto-publish them as well sed -i 's/ "lib\/\*",//' ./Cargo.toml + cargo workspaces rename ra_ap_%n find crates/rust-analyzer -type f -name '*.rs' -exec sed -i 's/rust_analyzer/ra_ap_rust_analyzer/g' {} + - cargo workspaces publish --yes --force '*' --exact --no-git-commit --allow-dirty --skip-published custom 0.0.$PATCH + cargo workspaces publish --yes --force '*' --exact --no-git-commit --allow-dirty --skip-published custom 0.0.$(($RUN_NUMBER + 133)) diff --git a/src/tools/rust-analyzer/.github/workflows/ci.yaml b/src/tools/rust-analyzer/.github/workflows/ci.yaml index bb77324378a40..9f246098e767b 100644 --- a/src/tools/rust-analyzer/.github/workflows/ci.yaml +++ b/src/tools/rust-analyzer/.github/workflows/ci.yaml @@ -18,12 +18,36 @@ env: RUSTUP_MAX_RETRIES: 10 jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + typescript: ${{ steps.filter.outputs.typescript }} + proc_macros: ${{ steps.filter.outputs.proc_macros }} + steps: + - uses: actions/checkout@v3 + - uses: dorny/paths-filter@4067d885736b84de7c414f582ac45897079b0a78 + id: filter + with: + filters: | + typescript: + - 'editors/code/**' + proc_macros: + - 'crates/proc-macro-api/**' + - 'crates/proc-macro-srv/**' + - 'crates/proc-macro-srv-cli/**' + - 'crates/proc-macro-test/**' + rust: + needs: changes if: github.repository == 'rust-lang/rust-analyzer' name: Rust runs-on: ${{ matrix.os }} env: CC: deny_c + RUST_CHANNEL: "${{ needs.changes.outputs.proc_macros == 'true' && 'nightly' || 'stable' }}" + USE_SYSROOT_ABI: "${{ needs.changes.outputs.proc_macros == 'true' && '--features sysroot-abi' || '' }}" strategy: fail-fast: false @@ -35,30 +59,32 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 20 - name: Install Rust toolchain run: | - rustup update --no-self-update stable - rustup component add rustfmt rust-src + rustup update --no-self-update ${{ env.RUST_CHANNEL }} + rustup component add --toolchain ${{ env.RUST_CHANNEL }} rustfmt rust-src + rustup default ${{ env.RUST_CHANNEL }} - name: Cache Dependencies - uses: Swatinem/rust-cache@76686c56f2b581d1bb5bda44b51f7e24bd9b8b8e + uses: Swatinem/rust-cache@988c164c3d0e93c4dbab36aaf5bbeb77425b2894 + with: + key: ${{ env.RUST_CHANNEL }} - name: Bump opt-level if: matrix.os == 'ubuntu-latest' run: sed -i '/\[profile.dev]/a opt-level=1' Cargo.toml - name: Compile (tests) - run: cargo test --no-run --locked + run: cargo test --no-run --locked ${{ env.USE_SYSROOT_ABI }} # It's faster to `test` before `build` ¯\_(ツ)_/¯ - name: Compile (rust-analyzer) if: matrix.os == 'ubuntu-latest' - run: cargo build --quiet + run: cargo build --quiet ${{ env.USE_SYSROOT_ABI }} - name: Test - run: cargo test -- --nocapture --quiet + run: cargo test ${{ env.USE_SYSROOT_ABI }} -- --nocapture --quiet - name: Run analysis-stats on rust-analyzer if: matrix.os == 'ubuntu-latest' @@ -90,7 +116,7 @@ jobs: rustup target add ${{ env.targets }} ${{ env.targets_ide }} - name: Cache Dependencies - uses: Swatinem/rust-cache@76686c56f2b581d1bb5bda44b51f7e24bd9b8b8e + uses: Swatinem/rust-cache@988c164c3d0e93c4dbab36aaf5bbeb77425b2894 - name: Check run: | @@ -102,6 +128,7 @@ jobs: done typescript: + needs: changes if: github.repository == 'rust-lang/rust-analyzer' name: TypeScript strategy: @@ -114,45 +141,58 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 + if: needs.changes.outputs.typescript == 'true' - name: Install Nodejs uses: actions/setup-node@v3 with: node-version: 16 + if: needs.changes.outputs.typescript == 'true' - name: Install xvfb - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true' run: sudo apt-get install -y xvfb - run: npm ci working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' # - run: npm audit || { sleep 10 && npm audit; } || { sleep 30 && npm audit; } # if: runner.os == 'Linux' # working-directory: ./editors/code + # If this steps fails, your code's type integrity might be wrong at some places at TypeScript level. + - run: npm run typecheck + working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' + + # You may fix the code automatically by running `npm run lint:fix` if this steps fails. - run: npm run lint working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' + + # To fix this steps, please run `npm run format`. + - run: npm run format:check + working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' - name: Run VS Code tests (Linux) - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-latest' && needs.changes.outputs.typescript == 'true' env: VSCODE_CLI: 1 run: xvfb-run npm test working-directory: ./editors/code - name: Run VS Code tests (Windows) - if: matrix.os == 'windows-latest' + if: matrix.os == 'windows-latest' && needs.changes.outputs.typescript == 'true' env: VSCODE_CLI: 1 run: npm test working-directory: ./editors/code - - run: npm run pretest - working-directory: ./editors/code - - run: npm run package --scripts-prepend-node-path working-directory: ./editors/code + if: needs.changes.outputs.typescript == 'true' end-success: name: bors build finished @@ -165,7 +205,7 @@ jobs: end-failure: name: bors build finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + if: github.event.pusher.name == 'bors' && !success() runs-on: ubuntu-latest needs: [rust, rust-cross, typescript] steps: diff --git a/src/tools/rust-analyzer/.github/workflows/metrics.yaml b/src/tools/rust-analyzer/.github/workflows/metrics.yaml index 3fe2fc917a39a..260e45ff517c1 100644 --- a/src/tools/rust-analyzer/.github/workflows/metrics.yaml +++ b/src/tools/rust-analyzer/.github/workflows/metrics.yaml @@ -1,8 +1,8 @@ name: metrics on: push: - branches: - - master + branches: + - master env: CARGO_INCREMENTAL: 0 @@ -11,20 +11,135 @@ env: RUSTUP_MAX_RETRIES: 10 jobs: - metrics: + setup_cargo: if: github.repository == 'rust-lang/rust-analyzer' runs-on: ubuntu-latest + steps: + - name: Install Rust toolchain + run: | + rustup update --no-self-update stable + rustup component add rustfmt rust-src + rustup default stable + - name: Cache cargo + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo-${{ github.sha }} + + build_metrics: + runs-on: ubuntu-latest + needs: setup_cargo + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Restore cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo-${{ github.sha }} + + - name: Collect build metrics + run: cargo xtask metrics build + + - name: Cache target + uses: actions/cache@v3 + with: + path: target/ + key: ${{ runner.os }}-target-${{ github.sha }} + + - name: Upload build metrics + uses: actions/upload-artifact@v3 + with: + name: build-${{ github.sha }} + path: target/build.json + if-no-files-found: error + + other_metrics: + strategy: + matrix: + names: [self, ripgrep, webrender, diesel] + runs-on: ubuntu-latest + needs: [setup_cargo, build_metrics] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Restore cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo-${{ github.sha }} + + - name: Restore target cache + uses: actions/cache@v3 + with: + path: target/ + key: ${{ runner.os }}-target-${{ github.sha }} + - name: Collect metrics + run: cargo xtask metrics ${{ matrix.names }} + + - name: Upload metrics + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.names }}-${{ github.sha }} + path: target/${{ matrix.names }}.json + if-no-files-found: error + + generate_final_metrics: + runs-on: ubuntu-latest + needs: [build_metrics, other_metrics] steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Install Rust toolchain - run: | - rustup update --no-self-update stable - rustup component add rustfmt rust-src - - - name: Collect metrics - run: cargo xtask metrics - env: - METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }} + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Download build metrics + uses: actions/download-artifact@v3 + with: + name: build-${{ github.sha }} + + - name: Download self metrics + uses: actions/download-artifact@v3 + with: + name: self-${{ github.sha }} + + - name: Download ripgrep metrics + uses: actions/download-artifact@v3 + with: + name: ripgrep-${{ github.sha }} + + - name: Download webrender metrics + uses: actions/download-artifact@v3 + with: + name: webrender-${{ github.sha }} + + - name: Download diesel metrics + uses: actions/download-artifact@v3 + with: + name: diesel-${{ github.sha }} + + - name: Combine json + run: | + git clone --depth 1 https://$METRICS_TOKEN@github.com/rust-analyzer/metrics.git + jq -s ".[0] * .[1] * .[2] * .[3] * .[4]" build.json self.json ripgrep.json webrender.json diesel.json -c >> metrics/metrics.json + cd metrics + git add . + git -c user.name=Bot -c user.email=dummy@example.com commit --message 📈 + git push origin master + env: + METRICS_TOKEN: ${{ secrets.METRICS_TOKEN }} diff --git a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml index 1b843fff1a4a1..6d026c9ad910b 100644 --- a/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml +++ b/src/tools/rust-analyzer/.github/workflows/publish-libs.yaml @@ -3,9 +3,9 @@ on: workflow_dispatch: push: branches: - - main + - master paths: - - 'lib/**' + - "lib/**" jobs: publish-libs: @@ -29,7 +29,7 @@ jobs: shell: bash run: | git config --global user.email "runner@gha.local" - git config --global user.name "Github Action" + git config --global user.name "GitHub Action" # Remove r-a crates from the workspaces so we don't auto-publish them as well sed -i 's/ "crates\/\*"//' ./Cargo.toml cargo workspaces publish --yes --exact --from-git --no-git-commit --allow-dirty diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index 48f4c6b55ed90..43681c785fdc0 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -270,7 +270,7 @@ jobs: - name: Publish Extension (Code Marketplace, nightly) if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') working-directory: ./editors/code - run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix + run: npx vsce publish --pat ${{ secrets.MARKETPLACE_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix --pre-release - name: Publish Extension (OpenVSX, nightly) if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer') diff --git a/src/tools/rust-analyzer/.vscode/launch.json b/src/tools/rust-analyzer/.vscode/launch.json index 1e21214ffc4bc..c353737a35a83 100644 --- a/src/tools/rust-analyzer/.vscode/launch.json +++ b/src/tools/rust-analyzer/.vscode/launch.json @@ -72,7 +72,7 @@ }, { // Used for testing the extension with a local build of the LSP server (in `target/release`) - // with all other extendions loaded. + // with all other extensions loaded. "name": "Run With Extensions", "type": "extensionHost", "request": "launch", diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 25242c6028a47..a2b263cf2d84d 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -19,18 +19,18 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "always-assert" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf688625d06217d5b1bb0ea9d9c44a1635fd0ee3534466388d18203174f4d11" +checksum = "4436e0292ab1bb631b42973c61205e704475fe8126af845c8d923c0996328127" dependencies = [ "log", ] [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "anymap" @@ -40,15 +40,15 @@ checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" [[package]] name = "arbitrary" -version = "1.2.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0224938f92e7aef515fac2ff2d18bd1115c1394ddf4a092e0c87e8be9499ee5" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "atty" @@ -77,8 +77,8 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", - "object", + "miniz_oxide 0.6.2", + "object 0.30.4", "rustc-demangle", ] @@ -87,12 +87,14 @@ name = "base-db" version = "0.0.0" dependencies = [ "cfg", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "profile", "rustc-hash", "salsa", "stdx", "syntax", "test-utils", + "triomphe", "tt", "vfs", ] @@ -103,6 +105,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" + [[package]] name = "byteorder" version = "1.4.3" @@ -111,9 +119,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "camino" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" dependencies = [ "serde", ] @@ -129,9 +137,9 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", @@ -143,9 +151,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg" @@ -169,32 +177,32 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chalk-derive" -version = "0.89.0" +version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea176c50987dc4765961aa165001e8eb5a722a26308c5797a47303ea91686aab" +checksum = "ff5053a8a42dbff5279a82423946fc56dc1253b76cf211b2b3c14b3aad4e1281" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "synstructure", ] [[package]] name = "chalk-ir" -version = "0.89.0" +version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473b480241695428c14e8f84f1c9a47ef232450a50faf3a4041e5c9dc11e0a3b" +checksum = "8a56de2146a8ed0fcd54f4bd50db852f1de4eac9e1efe568494f106c21b77d2a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "chalk-derive", "lazy_static", ] [[package]] name = "chalk-recursive" -version = "0.89.0" +version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764b4fe67cac3a3758185084efbfbd39bf0352795824ba849ddd2b64cd4bb28" +checksum = "5cc09e6e9531f3544989ef89b189e80fbc7ad9e2f73f1c5e03ddc9ffb0527463" dependencies = [ "chalk-derive", "chalk-ir", @@ -205,14 +213,14 @@ dependencies = [ [[package]] name = "chalk-solve" -version = "0.89.0" +version = "0.92.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a7e6160966eceb6e7dcc2f479a2af4c477aaf5bccbc640d82515995ab1a6cc" +checksum = "b392e02b4c81ec76d3748da839fc70a5539b83d27c9030668463d34d5110b860" dependencies = [ "chalk-derive", "chalk-ir", "ena", - "indexmap", + "indexmap 1.9.3", "itertools", "petgraph", "rustc-hash", @@ -221,9 +229,9 @@ dependencies = [ [[package]] name = "command-group" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "026c3922235f9f7d78f21251a026f3acdeb7cce3deba107fe09a4bfa63d850a2" +checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" dependencies = [ "nix", "winapi", @@ -257,9 +265,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -267,9 +275,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -278,22 +286,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.7.1", + "memoffset 0.9.0", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -305,7 +313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.12.3", "lock_api", "once_cell", "parking_lot_core 0.9.6", @@ -313,13 +321,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.2.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf460bbff5f571bfc762da5102729f59f338be7db17a21fade44c5c4f5005350" +checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -342,24 +350,30 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "ena" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "expect-test" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4661aca38d826eb7c72fe128e4238220616de4c0cc00db7bfc38e2e1364dd3" +checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" dependencies = [ "dissimilar", "once_cell", @@ -374,7 +388,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -385,12 +399,12 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] @@ -400,7 +414,6 @@ dependencies = [ "cargo_metadata", "command-group", "crossbeam-channel", - "jod-thread", "paths", "rustc-hash", "serde", @@ -412,19 +425,13 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -442,9 +449,9 @@ checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" [[package]] name = "gimli" -version = "0.27.0" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" [[package]] name = "hashbrown" @@ -452,6 +459,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.3.3" @@ -497,6 +510,7 @@ dependencies = [ "smallvec", "stdx", "syntax", + "triomphe", "tt", ] @@ -507,7 +521,7 @@ dependencies = [ "anymap", "arrayvec", "base-db", - "bitflags", + "bitflags 2.3.2", "cfg", "cov-mark", "dashmap", @@ -515,14 +529,14 @@ dependencies = [ "either", "expect-test", "fst", - "hashbrown", + "hashbrown 0.12.3", "hir-expand", "hkalbasi-rustc-ap-rustc_abi", "hkalbasi-rustc-ap-rustc_index", - "indexmap", + "indexmap 2.0.0", "intern", "itertools", - "la-arena", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "limit", "mbe", "once_cell", @@ -533,6 +547,7 @@ dependencies = [ "syntax", "test-utils", "tracing", + "triomphe", "tt", ] @@ -545,10 +560,10 @@ dependencies = [ "cov-mark", "either", "expect-test", - "hashbrown", + "hashbrown 0.12.3", "intern", "itertools", - "la-arena", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "limit", "mbe", "profile", @@ -557,6 +572,7 @@ dependencies = [ "stdx", "syntax", "tracing", + "triomphe", "tt", ] @@ -566,7 +582,7 @@ version = "0.0.0" dependencies = [ "arrayvec", "base-db", - "bitflags", + "bitflags 2.3.2", "chalk-derive", "chalk-ir", "chalk-recursive", @@ -580,9 +596,11 @@ dependencies = [ "hkalbasi-rustc-ap-rustc_index", "intern", "itertools", - "la-arena", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "limit", + "nohash-hasher", "once_cell", + "oorandom", "profile", "project-model", "rustc-hash", @@ -594,6 +612,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-tree", + "triomphe", "typed-arena", ] @@ -603,7 +622,7 @@ version = "0.0.20221221" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adabaadad9aa7576f97af02241cdf5554d62fb3d51a84cb05d77ba28edd3013f" dependencies = [ - "bitflags", + "bitflags 1.3.2", "hkalbasi-rustc-ap-rustc_index", "tracing", ] @@ -620,11 +639,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -644,6 +663,7 @@ dependencies = [ "ide-diagnostics", "ide-ssr", "itertools", + "nohash-hasher", "oorandom", "profile", "pulldown-cmark", @@ -655,6 +675,7 @@ dependencies = [ "text-edit", "toolchain", "tracing", + "triomphe", "url", ] @@ -707,10 +728,12 @@ dependencies = [ "expect-test", "fst", "hir", - "indexmap", + "indexmap 2.0.0", "itertools", "limit", + "line-index 0.1.0-pre.1", "memchr", + "nohash-hasher", "once_cell", "oorandom", "parser", @@ -723,6 +746,7 @@ dependencies = [ "test-utils", "text-edit", "tracing", + "triomphe", "xshell", ] @@ -737,6 +761,7 @@ dependencies = [ "hir", "ide-db", "itertools", + "once_cell", "profile", "serde_json", "sourcegen", @@ -755,18 +780,20 @@ dependencies = [ "hir", "ide-db", "itertools", + "nohash-hasher", "parser", "stdx", "syntax", "test-utils", "text-edit", + "triomphe", ] [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -774,12 +801,22 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", ] [[package]] @@ -788,7 +825,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "inotify-sys", "libc", ] @@ -816,9 +853,10 @@ name = "intern" version = "0.0.0" dependencies = [ "dashmap", - "hashbrown", + "hashbrown 0.12.3", "once_cell", "rustc-hash", + "triomphe", ] [[package]] @@ -832,9 +870,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jod-thread" @@ -858,13 +896,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] [[package]] name = "la-arena" -version = "0.3.0" +version = "0.3.1" + +[[package]] +name = "la-arena" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3752f229dcc5a481d60f385fa479ff46818033d881d2d801aa27dffcfb5e8306" [[package]] name = "lazy_static" @@ -874,25 +918,25 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "libmimalloc-sys" -version = "0.1.30" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174" +checksum = "f4ac0e912c8ef1b735e92369695618dc5b1819f5a7bf3f167301a3ba1cea515e" dependencies = [ "cc", "libc", @@ -902,11 +946,46 @@ dependencies = [ name = "limit" version = "0.0.0" +[[package]] +name = "line-index" +version = "0.1.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cad96769710c1745e11d4f940a8ff36000ade4bbada4285b001cb8aa2f745ce" +dependencies = [ + "nohash-hasher", + "text-size", +] + +[[package]] +name = "line-index" +version = "0.1.0" +dependencies = [ + "nohash-hasher", + "text-size", +] + +[[package]] +name = "load-cargo" +version = "0.0.0" +dependencies = [ + "anyhow", + "crossbeam-channel", + "ide", + "ide-db", + "itertools", + "proc-macro-api", + "project-model", + "tracing", + "tt", + "vfs", + "vfs-notify", +] + [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -914,16 +993,25 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "lsp-server" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "3711e4d6f491dc9edc0f1df80e204f38206775ac92c1241e89b79229a850bc00" dependencies = [ - "cfg-if", + "crossbeam-channel", + "log", + "serde", + "serde_json", ] [[package]] name = "lsp-server" -version = "0.7.0" +version = "0.7.2" dependencies = [ "crossbeam-channel", "log", @@ -938,22 +1026,13 @@ version = "0.94.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" dependencies = [ - "bitflags", + "bitflags 1.3.2", "serde", "serde_json", "serde_repr", "url", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - [[package]] name = "mbe" version = "0.0.0" @@ -977,36 +1056,36 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "mimalloc" -version = "0.1.34" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1" +checksum = "4e2894987a3459f3ffb755608bd82188f8ed00d0ae077f1edea29c068d639d98" dependencies = [ "libmimalloc-sys", ] @@ -1020,6 +1099,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.5" @@ -1029,7 +1117,7 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1038,7 +1126,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123" dependencies = [ - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1047,19 +1135,25 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "notify" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -1068,7 +1162,7 @@ dependencies = [ "libc", "mio", "walkdir", - "winapi", + "windows-sys 0.42.0", ] [[package]] @@ -1093,18 +1187,27 @@ dependencies = [ [[package]] name = "object" -version = "0.30.2" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c786513eb403643f2a88c244c2aaa270ef2153f55094587d0c48a3cf22a83" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -1163,7 +1266,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1173,16 +1276,16 @@ dependencies = [ "drop_bomb", "expect-test", "limit", - "rustc-ap-rustc_lexer", + "ra-ap-rustc_lexer", "sourcegen", "stdx", ] [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "paths" @@ -1190,9 +1293,9 @@ version = "0.0.0" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "perf-event" @@ -1220,7 +1323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -1234,7 +1337,7 @@ name = "proc-macro-api" version = "0.0.0" dependencies = [ "memmap2", - "object", + "object 0.32.0", "paths", "profile", "serde", @@ -1242,6 +1345,7 @@ dependencies = [ "snap", "stdx", "tracing", + "triomphe", "tt", ] @@ -1253,10 +1357,11 @@ dependencies = [ "libloading", "mbe", "memmap2", - "object", + "object 0.32.0", "paths", "proc-macro-api", "proc-macro-test", + "stdx", "tt", ] @@ -1264,6 +1369,7 @@ dependencies = [ name = "proc-macro-srv-cli" version = "0.0.0" dependencies = [ + "proc-macro-api", "proc-macro-srv", ] @@ -1282,9 +1388,9 @@ version = "0.0.0" [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1295,7 +1401,7 @@ version = "0.0.0" dependencies = [ "cfg-if", "countme", - "la-arena", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc", "once_cell", "perf-event", @@ -1312,7 +1418,8 @@ dependencies = [ "cargo_metadata", "cfg", "expect-test", - "la-arena", + "itertools", + "la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "paths", "profile", "rustc-hash", @@ -1322,6 +1429,7 @@ dependencies = [ "stdx", "toolchain", "tracing", + "triomphe", ] [[package]] @@ -1346,11 +1454,11 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" dependencies = [ - "bitflags", + "bitflags 1.3.2", "memchr", "unicase", ] @@ -1366,18 +1474,28 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] +[[package]] +name = "ra-ap-rustc_lexer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c145702ed3f237918e512685185dc8a4d0edc3a5326c63d20361d8ba9b45b3" +dependencies = [ + "unic-emoji-char", + "unicode-xid", +] + [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -1385,9 +1503,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1401,42 +1519,18 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] -[[package]] -name = "regex" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - [[package]] name = "rowan" -version = "0.15.10" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5811547e7ba31e903fe48c8ceab10d40d70a101f3d15523c847cce91aa71f332" +checksum = "64449cfef9483a475ed56ae30e2da5ee96448789fb2aa240a04beb6a055078bf" dependencies = [ "countme", - "hashbrown", - "memoffset 0.6.5", + "hashbrown 0.12.3", + "memoffset 0.8.0", "rustc-hash", "text-size", ] @@ -1451,6 +1545,7 @@ dependencies = [ "crossbeam-channel", "dissimilar", "expect-test", + "filetime", "flycheck", "hir", "hir-def", @@ -1459,16 +1554,18 @@ dependencies = [ "ide-db", "ide-ssr", "itertools", - "jod-thread", - "lsp-server", + "load-cargo", + "lsp-server 0.7.1", "lsp-types", "mbe", "mimalloc", + "mio", + "nohash-hasher", "num_cpus", "oorandom", "parking_lot 0.12.1", + "parking_lot_core 0.9.6", "proc-macro-api", - "proc-macro-srv", "profile", "project-model", "rayon", @@ -1480,14 +1577,13 @@ dependencies = [ "stdx", "syntax", "test-utils", - "threadpool", "tikv-jemallocator", "toolchain", "tracing", "tracing-log", "tracing-subscriber", "tracing-tree", - "tt", + "triomphe", "vfs", "vfs-notify", "winapi", @@ -1495,20 +1591,11 @@ dependencies = [ "xshell", ] -[[package]] -name = "rustc-ap-rustc_lexer" -version = "727.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f40f26e7abdcd3b982f36c09a634cc6187988fbf6ec466c91f8d30a12ac0237" -dependencies = [ - "unicode-xid", -] - [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -1518,9 +1605,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "salsa" @@ -1529,7 +1616,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b223dccb46c32753144d0b51290da7230bb4aedcd8379d6b4c9a474c18bf17a" dependencies = [ "crossbeam-utils", - "indexmap", + "indexmap 1.9.3", "lock_api", "log", "oorandom", @@ -1548,7 +1635,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1583,40 +1670,40 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.152" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.156" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "bdf3bf93142acad5821c99197022e170842cdbc1c30482b98750c688c640842a" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa", "ryu", "serde", @@ -1624,13 +1711,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -1650,9 +1737,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smol_str" -version = "0.1.23" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" dependencies = [ "serde", ] @@ -1682,6 +1769,8 @@ version = "0.0.0" dependencies = [ "always-assert", "backtrace", + "crossbeam-channel", + "jod-thread", "libc", "miow", "winapi", @@ -1689,9 +1778,20 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -1700,13 +1800,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.6" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "unicode-xid", ] @@ -1717,22 +1817,23 @@ dependencies = [ "cov-mark", "either", "expect-test", - "indexmap", + "indexmap 2.0.0", "itertools", "once_cell", "parser", "proc-macro2", "profile", "quote", + "ra-ap-rustc_lexer", "rayon", "rowan", - "rustc-ap-rustc_lexer", "rustc-hash", "smol_str", "sourcegen", "stdx", "test-utils", "text-edit", + "triomphe", "ungrammar", ] @@ -1763,42 +1864,34 @@ checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "tikv-jemalloc-ctl" version = "0.5.0" @@ -1812,12 +1905,11 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.5.2+5.3.0-patched" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" dependencies = [ "cc", - "fs_extra", "libc", ] @@ -1833,9 +1925,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "serde", "time-core", @@ -1843,9 +1935,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "tinyvec" @@ -1858,9 +1950,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toolchain" @@ -1883,20 +1975,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -1915,25 +2007,21 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ - "matchers", - "once_cell", - "regex", "sharded-slab", "thread_local", - "tracing", "tracing-core", "tracing-log", ] [[package]] name = "tracing-tree" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758e983ab7c54fee18403994507e7f212b9005e957ce7984996fac8d11facedb" +checksum = "4f9742d8df709837409dbb22aa25dd7769c260406f20ff48a2320b80a4a6aed0" dependencies = [ "atty", "nu-ansi-term", @@ -1942,6 +2030,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "triomphe" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db" + [[package]] name = "tt" version = "0.0.0" @@ -1962,6 +2056,47 @@ version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f" +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-emoji-char" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.6.0" @@ -1973,15 +2108,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1994,9 +2129,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-xid" @@ -2006,9 +2141,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -2033,7 +2168,8 @@ name = "vfs" version = "0.0.0" dependencies = [ "fst", - "indexmap", + "indexmap 2.0.0", + "nohash-hasher", "paths", "rustc-hash", "stdx", @@ -2044,9 +2180,9 @@ name = "vfs-notify" version = "0.0.0" dependencies = [ "crossbeam-channel", - "jod-thread", "notify", "paths", + "stdx", "tracing", "vfs", "walkdir", @@ -2054,12 +2190,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -2106,56 +2241,122 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "write-json" @@ -2208,9 +2409,9 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ "byteorder", "crc32fast", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 333f03ce2ffe5..f6a50bfa6b2c7 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -1,9 +1,10 @@ [workspace] members = ["xtask/", "lib/*", "crates/*"] exclude = ["crates/proc-macro-test/imp"] +resolver = "2" [workspace.package] -rust-version = "1.66" +rust-version = "1.70" edition = "2021" license = "MIT OR Apache-2.0" authors = ["rust-analyzer team"] @@ -34,6 +35,10 @@ debug = 0 # chalk-ir = { path = "../chalk/chalk-ir" } # chalk-recursive = { path = "../chalk/chalk-recursive" } # chalk-derive = { path = "../chalk/chalk-derive" } +# line-index = { path = "lib/line-index" } +# la-arena = { path = "lib/la-arena" } +# lsp-server = { path = "lib/lsp-server" } + # ungrammar = { path = "../ungrammar" } @@ -56,13 +61,13 @@ ide-diagnostics = { path = "./crates/ide-diagnostics", version = "0.0.0" } ide-ssr = { path = "./crates/ide-ssr", version = "0.0.0" } intern = { path = "./crates/intern", version = "0.0.0" } limit = { path = "./crates/limit", version = "0.0.0" } +load-cargo = { path = "./crates/load-cargo", version = "0.0.0" } mbe = { path = "./crates/mbe", version = "0.0.0" } parser = { path = "./crates/parser", version = "0.0.0" } paths = { path = "./crates/paths", version = "0.0.0" } proc-macro-api = { path = "./crates/proc-macro-api", version = "0.0.0" } proc-macro-srv = { path = "./crates/proc-macro-srv", version = "0.0.0" } proc-macro-srv-cli = { path = "./crates/proc-macro-srv-cli", version = "0.0.0" } -proc-macro-test = { path = "./crates/proc-macro-test", version = "0.0.0" } profile = { path = "./crates/profile", version = "0.0.0" } project-model = { path = "./crates/project-model", version = "0.0.0" } sourcegen = { path = "./crates/sourcegen", version = "0.0.0" } @@ -74,5 +79,28 @@ toolchain = { path = "./crates/toolchain", version = "0.0.0" } tt = { path = "./crates/tt", version = "0.0.0" } vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } + +# local crates that aren't published to crates.io. These should not have versions. +proc-macro-test = { path = "./crates/proc-macro-test" } + +# In-tree crates that are published separately and follow semver. See lib/README.md +line-index = { version = "0.1.0-pre.1" } +la-arena = { version = "0.3.1" } +lsp-server = { version = "0.7.1" } + # non-local crates -smallvec = { version = "1.10.0", features = ["const_new", "union", "const_generics"] } +smallvec = { version = "1.10.0", features = [ + "const_new", + "union", + "const_generics", +] } +smol_str = "0.2.0" +nohash-hasher = "0.2.0" +text-size = "1.1.0" +serde = { version = "1.0.156", features = ["derive"] } +serde_json = "1.0.96" +triomphe = { version = "0.1.8", default-features = false, features = ["std"] } +# can't upgrade due to dashmap depending on 0.12.3 currently +hashbrown = { version = "0.12.3", features = ["inline-more"], default-features = false } + +rustc_lexer = { version = "0.1.0", package = "ra-ap-rustc_lexer" } diff --git a/src/tools/rust-analyzer/bench_data/glorious_old_parser b/src/tools/rust-analyzer/bench_data/glorious_old_parser index 764893daa12ad..f593f2b2955ac 100644 --- a/src/tools/rust-analyzer/bench_data/glorious_old_parser +++ b/src/tools/rust-analyzer/bench_data/glorious_old_parser @@ -3808,7 +3808,7 @@ impl<'a> Parser<'a> { if self.eat_keyword(keywords::Else) || !cond.returns() { let sp = self.sess.source_map().next_point(lo); let mut err = self.diagnostic() - .struct_span_err(sp, "missing condition for `if` statemement"); + .struct_span_err(sp, "missing condition for `if` statement"); err.span_label(sp, "expected if condition here"); return Err(err) } diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml index f6a1075c190a8..171c113a950d0 100644 --- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml @@ -15,6 +15,10 @@ doctest = false salsa = "0.17.0-pre.2" rustc-hash = "1.1.0" +triomphe.workspace = true + +la-arena.workspace = true + # local deps cfg.workspace = true profile.workspace = true diff --git a/src/tools/rust-analyzer/crates/base-db/src/change.rs b/src/tools/rust-analyzer/crates/base-db/src/change.rs index b57f2345767d0..6a3b36b231280 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/change.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/change.rs @@ -1,19 +1,21 @@ //! Defines a unit of change that can applied to the database to get the next //! state. Changes are transactional. -use std::{fmt, sync::Arc}; +use std::fmt; use salsa::Durability; +use triomphe::Arc; use vfs::FileId; -use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId}; +use crate::{CrateGraph, ProcMacros, SourceDatabaseExt, SourceRoot, SourceRootId}; /// Encapsulate a bunch of raw `.set` calls on the database. #[derive(Default)] pub struct Change { pub roots: Option>, - pub files_changed: Vec<(FileId, Option>)>, + pub files_changed: Vec<(FileId, Option>)>, pub crate_graph: Option, + pub proc_macros: Option, } impl fmt::Debug for Change { @@ -33,7 +35,7 @@ impl fmt::Debug for Change { } impl Change { - pub fn new() -> Change { + pub fn new() -> Self { Change::default() } @@ -41,7 +43,7 @@ impl Change { self.roots = Some(roots); } - pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { + pub fn change_file(&mut self, file_id: FileId, new_text: Option>) { self.files_changed.push((file_id, new_text)) } @@ -49,6 +51,10 @@ impl Change { self.crate_graph = Some(graph); } + pub fn set_proc_macros(&mut self, proc_macros: ProcMacros) { + self.proc_macros = Some(proc_macros); + } + pub fn apply(self, db: &mut dyn SourceDatabaseExt) { let _p = profile::span("RootDatabase::apply_change"); if let Some(roots) = self.roots { @@ -67,11 +73,14 @@ impl Change { let source_root = db.source_root(source_root_id); let durability = durability(&source_root); // XXX: can't actually remove the file, just reset the text - let text = text.unwrap_or_default(); + let text = text.unwrap_or_else(|| Arc::from("")); db.set_file_text_with_durability(file_id, text, durability) } if let Some(crate_graph) = self.crate_graph { - db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) + db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH); + } + if let Some(proc_macros) = self.proc_macros { + db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH); } } } diff --git a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs index 8a7e9dfadfed2..323ee4260e4f3 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs @@ -1,32 +1,36 @@ //! A set of high-level utility fixture methods to use in tests. -use std::{mem, str::FromStr, sync::Arc}; +use std::{mem, str::FromStr, sync}; use cfg::CfgOptions; use rustc_hash::FxHashMap; use test_utils::{ - extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER, + extract_range_or_offset, Fixture, FixtureWithProjectMeta, RangeOrOffset, CURSOR_MARKER, + ESCAPED_CURSOR_MARKER, }; +use triomphe::Arc; use tt::token_id::{Leaf, Subtree, TokenTree}; use vfs::{file_set::FileSet, VfsPath}; use crate::{ input::{CrateName, CrateOrigin, LangCrateOrigin}, Change, CrateDisplayName, CrateGraph, CrateId, Dependency, Edition, Env, FileId, FilePosition, - FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, SourceDatabaseExt, - SourceRoot, SourceRootId, + FileRange, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacros, ReleaseChannel, + SourceDatabaseExt, SourceRoot, SourceRootId, }; pub const WORKSPACE: SourceRootId = SourceRootId(0); pub trait WithFixture: Default + SourceDatabaseExt + 'static { + #[track_caller] fn with_single_file(ra_fixture: &str) -> (Self, FileId) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); fixture.change.apply(&mut db); - assert_eq!(fixture.files.len(), 1); + assert_eq!(fixture.files.len(), 1, "Multiple file found in the fixture"); (db, fixture.files[0]) } + #[track_caller] fn with_many_files(ra_fixture: &str) -> (Self, Vec) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -35,6 +39,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static { (db, fixture.files) } + #[track_caller] fn with_files(ra_fixture: &str) -> Self { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -43,6 +48,7 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static { db } + #[track_caller] fn with_files_extra_proc_macros( ra_fixture: &str, proc_macros: Vec<(String, ProcMacro)>, @@ -54,18 +60,21 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static { db } + #[track_caller] fn with_position(ra_fixture: &str) -> (Self, FilePosition) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let offset = range_or_offset.expect_offset(); (db, FilePosition { file_id, offset }) } + #[track_caller] fn with_range(ra_fixture: &str) -> (Self, FileRange) { let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); let range = range_or_offset.expect_range(); (db, FileRange { file_id, range }) } + #[track_caller] fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) { let fixture = ChangeFixture::parse(ra_fixture); let mut db = Self::default(); @@ -93,6 +102,8 @@ pub struct ChangeFixture { pub change: Change, } +const SOURCE_ROOT_PREFIX: &str = "/"; + impl ChangeFixture { pub fn parse(ra_fixture: &str) -> ChangeFixture { Self::parse_with_proc_macros(ra_fixture, Vec::new()) @@ -100,9 +111,16 @@ impl ChangeFixture { pub fn parse_with_proc_macros( ra_fixture: &str, - mut proc_macros: Vec<(String, ProcMacro)>, + mut proc_macro_defs: Vec<(String, ProcMacro)>, ) -> ChangeFixture { - let (mini_core, proc_macro_names, fixture) = Fixture::parse(ra_fixture); + let FixtureWithProjectMeta { fixture, mini_core, proc_macro_names, toolchain } = + FixtureWithProjectMeta::parse(ra_fixture); + let toolchain = toolchain + .map(|it| { + ReleaseChannel::from_str(&it) + .unwrap_or_else(|| panic!("unknown release channel found: {it}")) + }) + .unwrap_or(ReleaseChannel::Stable); let mut change = Change::new(); let mut files = Vec::new(); @@ -115,7 +133,6 @@ impl ChangeFixture { let mut file_set = FileSet::default(); let mut current_source_root_kind = SourceRootKind::Local; - let source_root_prefix = "/".to_string(); let mut file_id = FileId(0); let mut roots = Vec::new(); @@ -135,19 +152,23 @@ impl ChangeFixture { entry.text.clone() }; - let meta = FileMeta::from(entry); - assert!(meta.path.starts_with(&source_root_prefix)); + let meta = FileMeta::from_fixture(entry, current_source_root_kind); + assert!(meta.path.starts_with(SOURCE_ROOT_PREFIX)); if !meta.deps.is_empty() { assert!(meta.krate.is_some(), "can't specify deps without naming the crate") } - if let Some(kind) = &meta.introduce_new_source_root { - let root = match current_source_root_kind { + if let Some(kind) = meta.introduce_new_source_root { + assert!( + meta.krate.is_some(), + "new_source_root meta doesn't make sense without crate meta" + ); + let prev_kind = mem::replace(&mut current_source_root_kind, kind); + let prev_root = match prev_kind { SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)), SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)), }; - roots.push(root); - current_source_root_kind = *kind; + roots.push(prev_root); } if let Some((krate, origin, version)) = meta.krate { @@ -157,19 +178,19 @@ impl ChangeFixture { meta.edition, Some(crate_name.clone().into()), version, - meta.cfg.clone(), meta.cfg, + Default::default(), meta.env, - Ok(Vec::new()), false, origin, meta.target_data_layout .as_deref() .map(Arc::from) .ok_or_else(|| "target_data_layout unset".into()), + Some(toolchain), ); let prev = crates.insert(crate_name.clone(), crate_id); - assert!(prev.is_none()); + assert!(prev.is_none(), "multiple crates with same name: {}", crate_name); for dep in meta.deps { let prelude = meta.extern_prelude.contains(&dep); let dep = CrateName::normalize_dashes(&dep); @@ -182,7 +203,7 @@ impl ChangeFixture { default_target_data_layout = meta.target_data_layout; } - change.change_file(file_id, Some(Arc::new(text))); + change.change_file(file_id, Some(Arc::from(text))); let path = VfsPath::new_virtual_path(meta.path); file_set.insert(file_id, path); files.push(file_id); @@ -197,15 +218,15 @@ impl ChangeFixture { Edition::CURRENT, Some(CrateName::new("test").unwrap().into()), None, - default_cfg.clone(), default_cfg, - Env::default(), - Ok(Vec::new()), + Default::default(), + Env::new_for_test_fixture(), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, default_target_data_layout - .map(|x| x.into()) + .map(|it| it.into()) .ok_or_else(|| "target_data_layout unset".into()), + Some(toolchain), ); } else { for (from, to, prelude) in crate_deps { @@ -232,7 +253,7 @@ impl ChangeFixture { fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string())); roots.push(SourceRoot::new_library(fs)); - change.change_file(core_file, Some(Arc::new(mini_core.source_code()))); + change.change_file(core_file, Some(Arc::from(mini_core.source_code()))); let all_crates = crate_graph.crates_in_topological_order(); @@ -241,13 +262,13 @@ impl ChangeFixture { Edition::Edition2021, Some(CrateDisplayName::from_canonical_name("core".to_string())), None, - CfgOptions::default(), - CfgOptions::default(), - Env::default(), - Ok(Vec::new()), + Default::default(), + Default::default(), + Env::new_for_test_fixture(), false, CrateOrigin::Lang(LangCrateOrigin::Core), target_layout.clone(), + Some(toolchain), ); for krate in all_crates { @@ -257,12 +278,13 @@ impl ChangeFixture { } } + let mut proc_macros = ProcMacros::default(); if !proc_macro_names.is_empty() { let proc_lib_file = file_id; file_id.0 += 1; - proc_macros.extend(default_test_proc_macros()); - let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macros); + proc_macro_defs.extend(default_test_proc_macros()); + let (proc_macro, source) = filter_test_proc_macros(&proc_macro_names, proc_macro_defs); let mut fs = FileSet::default(); fs.insert( proc_lib_file, @@ -270,7 +292,7 @@ impl ChangeFixture { ); roots.push(SourceRoot::new_library(fs)); - change.change_file(proc_lib_file, Some(Arc::new(source))); + change.change_file(proc_lib_file, Some(Arc::from(source))); let all_crates = crate_graph.crates_in_topological_order(); @@ -279,14 +301,15 @@ impl ChangeFixture { Edition::Edition2021, Some(CrateDisplayName::from_canonical_name("proc_macros".to_string())), None, - CfgOptions::default(), - CfgOptions::default(), - Env::default(), - Ok(proc_macro), + Default::default(), + Default::default(), + Env::new_for_test_fixture(), true, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, target_layout, + Some(toolchain), ); + proc_macros.insert(proc_macros_crate, Ok(proc_macro)); for krate in all_crates { crate_graph @@ -305,6 +328,7 @@ impl ChangeFixture { roots.push(root); change.set_roots(roots); change.set_crate_graph(crate_graph); + change.set_proc_macros(proc_macros); ChangeFixture { file_position, files, change } } @@ -323,7 +347,7 @@ pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream { ProcMacro { name: "identity".into(), kind: crate::ProcMacroKind::Attr, - expander: Arc::new(IdentityProcMacroExpander), + expander: sync::Arc::new(IdentityProcMacroExpander), }, ), ( @@ -337,7 +361,7 @@ pub fn derive_identity(item: TokenStream) -> TokenStream { ProcMacro { name: "DeriveIdentity".into(), kind: crate::ProcMacroKind::CustomDerive, - expander: Arc::new(IdentityProcMacroExpander), + expander: sync::Arc::new(IdentityProcMacroExpander), }, ), ( @@ -351,7 +375,7 @@ pub fn input_replace(attr: TokenStream, _item: TokenStream) -> TokenStream { ProcMacro { name: "input_replace".into(), kind: crate::ProcMacroKind::Attr, - expander: Arc::new(AttributeInputReplaceProcMacroExpander), + expander: sync::Arc::new(AttributeInputReplaceProcMacroExpander), }, ), ( @@ -365,7 +389,7 @@ pub fn mirror(input: TokenStream) -> TokenStream { ProcMacro { name: "mirror".into(), kind: crate::ProcMacroKind::FuncLike, - expander: Arc::new(MirrorProcMacroExpander), + expander: sync::Arc::new(MirrorProcMacroExpander), }, ), ( @@ -379,7 +403,7 @@ pub fn shorten(input: TokenStream) -> TokenStream { ProcMacro { name: "shorten".into(), kind: crate::ProcMacroKind::FuncLike, - expander: Arc::new(ShortenProcMacroExpander), + expander: sync::Arc::new(ShortenProcMacroExpander), }, ), ] @@ -423,52 +447,74 @@ struct FileMeta { target_data_layout: Option, } -fn parse_crate(crate_str: String) -> (String, CrateOrigin, Option) { - if let Some((a, b)) = crate_str.split_once('@') { - let (version, origin) = match b.split_once(':') { - Some(("CratesIo", data)) => match data.split_once(',') { - Some((version, url)) => { - (version, CrateOrigin::CratesIo { repo: Some(url.to_owned()), name: None }) - } - _ => panic!("Bad crates.io parameter: {data}"), - }, - _ => panic!("Bad string for crate origin: {b}"), - }; - (a.to_owned(), origin, Some(version.to_string())) - } else { - let crate_origin = match &*crate_str { - "std" => CrateOrigin::Lang(LangCrateOrigin::Std), - "core" => CrateOrigin::Lang(LangCrateOrigin::Core), - _ => CrateOrigin::CratesIo { repo: None, name: None }, - }; - (crate_str, crate_origin, None) - } -} - -impl From for FileMeta { - fn from(f: Fixture) -> FileMeta { +impl FileMeta { + fn from_fixture(f: Fixture, current_source_root_kind: SourceRootKind) -> Self { let mut cfg = CfgOptions::default(); - f.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into())); - f.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into())); + for (k, v) in f.cfgs { + if let Some(v) = v { + cfg.insert_key_value(k.into(), v.into()); + } else { + cfg.insert_atom(k.into()); + } + } + + let introduce_new_source_root = f.introduce_new_source_root.map(|kind| match &*kind { + "local" => SourceRootKind::Local, + "library" => SourceRootKind::Library, + invalid => panic!("invalid source root kind '{invalid}'"), + }); + let current_source_root_kind = + introduce_new_source_root.unwrap_or(current_source_root_kind); + let deps = f.deps; - FileMeta { + Self { path: f.path, - krate: f.krate.map(parse_crate), + krate: f.krate.map(|it| parse_crate(it, current_source_root_kind, f.library)), extern_prelude: f.extern_prelude.unwrap_or_else(|| deps.clone()), deps, cfg, - edition: f.edition.as_ref().map_or(Edition::CURRENT, |v| Edition::from_str(v).unwrap()), + edition: f.edition.map_or(Edition::CURRENT, |v| Edition::from_str(&v).unwrap()), env: f.env.into_iter().collect(), - introduce_new_source_root: f.introduce_new_source_root.map(|kind| match &*kind { - "local" => SourceRootKind::Local, - "library" => SourceRootKind::Library, - invalid => panic!("invalid source root kind '{invalid}'"), - }), + introduce_new_source_root, target_data_layout: f.target_data_layout, } } } +fn parse_crate( + crate_str: String, + current_source_root_kind: SourceRootKind, + explicit_non_workspace_member: bool, +) -> (String, CrateOrigin, Option) { + // syntax: + // "my_awesome_crate" + // "my_awesome_crate@0.0.1,http://example.com" + let (name, repo, version) = if let Some((name, remain)) = crate_str.split_once('@') { + let (version, repo) = + remain.split_once(',').expect("crate meta: found '@' without version and url"); + (name.to_owned(), Some(repo.to_owned()), Some(version.to_owned())) + } else { + (crate_str, None, None) + }; + + let non_workspace_member = explicit_non_workspace_member + || matches!(current_source_root_kind, SourceRootKind::Library); + + let origin = match LangCrateOrigin::from(&*name) { + LangCrateOrigin::Other => { + let name = name.clone(); + if non_workspace_member { + CrateOrigin::Library { repo, name } + } else { + CrateOrigin::Local { repo, name: Some(name) } + } + } + origin => CrateOrigin::Lang(origin), + }; + + (name, origin, version) +} + // Identity mapping #[derive(Debug)] struct IdentityProcMacroExpander; diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 43388e915b5d3..c47799f132093 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -6,14 +6,20 @@ //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how //! actual IO is done and lowered to input. -use std::{fmt, ops, panic::RefUnwindSafe, str::FromStr, sync::Arc}; +use std::{fmt, mem, ops, panic::RefUnwindSafe, str::FromStr, sync}; use cfg::CfgOptions; -use rustc_hash::FxHashMap; -use stdx::hash::{NoHashHashMap, NoHashHashSet}; +use la_arena::{Arena, Idx}; +use rustc_hash::{FxHashMap, FxHashSet}; use syntax::SmolStr; +use triomphe::Arc; use tt::token_id::Subtree; -use vfs::{file_set::FileSet, AnchoredPath, FileId, VfsPath}; +use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; + +// Map from crate id to the name of the crate and path of the proc-macro. If the value is `None`, +// then the crate for the proc-macro hasn't been build yet as the build data is missing. +pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; +pub type ProcMacros = FxHashMap; /// Files are grouped into source roots. A source root is a directory on the /// file systems which is watched for changes. Typically it corresponds to a @@ -79,17 +85,22 @@ impl SourceRoot { /// /// `CrateGraph` is `!Serialize` by design, see /// -#[derive(Debug, Clone, Default /* Serialize, Deserialize */)] +#[derive(Clone, Default)] pub struct CrateGraph { - arena: NoHashHashMap, + arena: Arena, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct CrateId(pub u32); +impl fmt::Debug for CrateGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.arena.iter().map(|(id, data)| (u32::from(id.into_raw()), data))) + .finish() + } +} -impl stdx::hash::NoHashHashable for CrateId {} +pub type CrateId = Idx; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CrateName(SmolStr); impl CrateName { @@ -127,15 +138,25 @@ impl ops::Deref for CrateName { } } -/// Origin of the crates. It is used in emitting monikers. +/// Origin of the crates. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum CrateOrigin { - /// Crates that are from crates.io official registry, - CratesIo { repo: Option, name: Option }, + /// Crates that are from the rustc workspace. + Rustc { name: String }, + /// Crates that are workspace members. + Local { repo: Option, name: Option }, + /// Crates that are non member libraries. + Library { repo: Option, name: String }, /// Crates that are provided by the language, like std, core, proc-macro, ... Lang(LangCrateOrigin), } +impl CrateOrigin { + pub fn is_local(&self) -> bool { + matches!(self, CrateOrigin::Local { .. }) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum LangCrateOrigin { Alloc, @@ -173,7 +194,7 @@ impl fmt::Display for LangCrateOrigin { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct CrateDisplayName { // The name we use to display various paths (with `_`). crate_name: CrateName, @@ -249,10 +270,36 @@ pub type TargetLayoutLoadResult = Result, Arc>; pub struct ProcMacro { pub name: SmolStr, pub kind: ProcMacroKind, - pub expander: Arc, + pub expander: sync::Arc, } -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum ReleaseChannel { + Stable, + Beta, + Nightly, +} + +impl ReleaseChannel { + pub fn as_str(self) -> &'static str { + match self { + ReleaseChannel::Stable => "stable", + ReleaseChannel::Beta => "beta", + ReleaseChannel::Nightly => "nightly", + } + } + + pub fn from_str(str: &str) -> Option { + Some(match str { + "" => ReleaseChannel::Stable, + "nightly" => ReleaseChannel::Nightly, + _ if str.starts_with("beta") => ReleaseChannel::Beta, + _ => return None, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CrateData { pub root_file_id: FileId, pub edition: Edition, @@ -265,13 +312,15 @@ pub struct CrateData { /// `Dependency` matters), this name should only be used for UI. pub display_name: Option, pub cfg_options: CfgOptions, - pub potential_cfg_options: CfgOptions, - pub target_layout: TargetLayoutLoadResult, + /// The cfg options that could be used by the crate + pub potential_cfg_options: Option, pub env: Env, pub dependencies: Vec, - pub proc_macro: ProcMacroLoadResult, pub origin: CrateOrigin, pub is_proc_macro: bool, + // FIXME: These things should not be per crate! These are more per workspace crate graph level things + pub target_layout: TargetLayoutLoadResult, + pub channel: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -290,7 +339,18 @@ pub struct Env { entries: FxHashMap, } -#[derive(Debug, Clone, PartialEq, Eq)] +impl Env { + pub fn new_for_test_fixture() -> Self { + Env { + entries: FxHashMap::from_iter([( + String::from("__ra_is_test_fixture"), + String::from("__ra_is_test_fixture"), + )]), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Dependency { pub crate_id: CrateId, pub name: CrateName, @@ -320,12 +380,12 @@ impl CrateGraph { display_name: Option, version: Option, cfg_options: CfgOptions, - potential_cfg_options: CfgOptions, + potential_cfg_options: Option, env: Env, - proc_macro: ProcMacroLoadResult, is_proc_macro: bool, origin: CrateOrigin, target_layout: Result, Arc>, + channel: Option, ) -> CrateId { let data = CrateData { root_file_id, @@ -335,16 +395,44 @@ impl CrateGraph { cfg_options, potential_cfg_options, env, - proc_macro, dependencies: Vec::new(), origin, target_layout, is_proc_macro, + channel, }; - let crate_id = CrateId(self.arena.len() as u32); - let prev = self.arena.insert(crate_id, data); - assert!(prev.is_none()); - crate_id + self.arena.alloc(data) + } + + /// Remove the crate from crate graph. If any crates depend on this crate, the dependency would be replaced + /// with the second input. + pub fn remove_and_replace( + &mut self, + id: CrateId, + replace_with: CrateId, + ) -> Result<(), CyclicDependenciesError> { + for (x, data) in self.arena.iter() { + if x == id { + continue; + } + for edge in &data.dependencies { + if edge.crate_id == id { + self.check_cycle_after_dependency(edge.crate_id, replace_with)?; + } + } + } + // if everything was ok, start to replace + for (x, data) in self.arena.iter_mut() { + if x == id { + continue; + } + for edge in &mut data.dependencies { + if edge.crate_id == id { + edge.crate_id = replace_with; + } + } + } + Ok(()) } pub fn add_dep( @@ -354,17 +442,26 @@ impl CrateGraph { ) -> Result<(), CyclicDependenciesError> { let _p = profile::span("add_dep"); - // Check if adding a dep from `from` to `to` creates a cycle. To figure - // that out, look for a path in the *opposite* direction, from `to` to - // `from`. - if let Some(path) = self.find_path(&mut NoHashHashSet::default(), dep.crate_id, from) { + self.check_cycle_after_dependency(from, dep.crate_id)?; + + self.arena[from].add_dep(dep); + Ok(()) + } + + /// Check if adding a dep from `from` to `to` creates a cycle. To figure + /// that out, look for a path in the *opposite* direction, from `to` to + /// `from`. + fn check_cycle_after_dependency( + &self, + from: CrateId, + to: CrateId, + ) -> Result<(), CyclicDependenciesError> { + if let Some(path) = self.find_path(&mut FxHashSet::default(), to, from) { let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect(); let err = CyclicDependenciesError { path }; - assert!(err.from().0 == from && err.to().0 == dep.crate_id); + assert!(err.from().0 == from && err.to().0 == to); return Err(err); } - - self.arena.get_mut(&from).unwrap().add_dep(dep); Ok(()) } @@ -373,14 +470,20 @@ impl CrateGraph { } pub fn iter(&self) -> impl Iterator + '_ { - self.arena.keys().copied() + self.arena.iter().map(|(idx, _)| idx) + } + + // FIXME: used for `handle_hack_cargo_workspace`, should be removed later + #[doc(hidden)] + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + self.arena.iter_mut() } /// Returns an iterator over all transitive dependencies of the given crate, /// including the crate itself. pub fn transitive_deps(&self, of: CrateId) -> impl Iterator { let mut worklist = vec![of]; - let mut deps = NoHashHashSet::default(); + let mut deps = FxHashSet::default(); while let Some(krate) = worklist.pop() { if !deps.insert(krate) { @@ -397,11 +500,11 @@ impl CrateGraph { /// including the crate itself. pub fn transitive_rev_deps(&self, of: CrateId) -> impl Iterator { let mut worklist = vec![of]; - let mut rev_deps = NoHashHashSet::default(); + let mut rev_deps = FxHashSet::default(); rev_deps.insert(of); - let mut inverted_graph = NoHashHashMap::<_, Vec<_>>::default(); - self.arena.iter().for_each(|(&krate, data)| { + let mut inverted_graph = FxHashMap::<_, Vec<_>>::default(); + self.arena.iter().for_each(|(krate, data)| { data.dependencies .iter() .for_each(|dep| inverted_graph.entry(dep.crate_id).or_default().push(krate)) @@ -424,9 +527,9 @@ impl CrateGraph { /// come before the crate itself). pub fn crates_in_topological_order(&self) -> Vec { let mut res = Vec::new(); - let mut visited = NoHashHashSet::default(); + let mut visited = FxHashSet::default(); - for krate in self.arena.keys().copied() { + for krate in self.iter() { go(self, &mut visited, &mut res, krate); } @@ -434,7 +537,7 @@ impl CrateGraph { fn go( graph: &CrateGraph, - visited: &mut NoHashHashSet, + visited: &mut FxHashSet, res: &mut Vec, source: CrateId, ) { @@ -450,31 +553,56 @@ impl CrateGraph { // FIXME: this only finds one crate with the given root; we could have multiple pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option { - let (&crate_id, _) = + let (crate_id, _) = self.arena.iter().find(|(_crate_id, data)| data.root_file_id == file_id)?; Some(crate_id) } + pub fn sort_deps(&mut self) { + self.arena + .iter_mut() + .for_each(|(_, data)| data.dependencies.sort_by_key(|dep| dep.crate_id)); + } + /// Extends this crate graph by adding a complete disjoint second crate - /// graph. + /// graph and adjust the ids in the [`ProcMacroPaths`] accordingly. /// - /// The ids of the crates in the `other` graph are shifted by the return - /// amount. - pub fn extend(&mut self, other: CrateGraph) -> u32 { - let start = self.arena.len() as u32; - self.arena.extend(other.arena.into_iter().map(|(id, mut data)| { - let new_id = id.shift(start); - for dep in &mut data.dependencies { - dep.crate_id = dep.crate_id.shift(start); + /// This will deduplicate the crates of the graph where possible. + /// Note that for deduplication to fully work, `self`'s crate dependencies must be sorted by crate id. + /// If the crate dependencies were sorted, the resulting graph from this `extend` call will also have the crate dependencies sorted. + pub fn extend(&mut self, mut other: CrateGraph, proc_macros: &mut ProcMacroPaths) { + let topo = other.crates_in_topological_order(); + let mut id_map: FxHashMap = FxHashMap::default(); + + for topo in topo { + let crate_data = &mut other.arena[topo]; + crate_data.dependencies.iter_mut().for_each(|dep| dep.crate_id = id_map[&dep.crate_id]); + crate_data.dependencies.sort_by_key(|dep| dep.crate_id); + + let res = self.arena.iter().find_map( + |(id, data)| { + if data == crate_data { + Some(id) + } else { + None + } + }, + ); + if let Some(res) = res { + id_map.insert(topo, res); + } else { + let id = self.arena.alloc(crate_data.clone()); + id_map.insert(topo, id); } - (new_id, data) - })); - start + } + + *proc_macros = + mem::take(proc_macros).into_iter().map(|(id, macros)| (id_map[&id], macros)).collect(); } fn find_path( &self, - visited: &mut NoHashHashSet, + visited: &mut FxHashSet, from: CrateId, to: CrateId, ) -> Option> { @@ -500,14 +628,14 @@ impl CrateGraph { // Work around for https://github.com/rust-lang/rust-analyzer/issues/6038. // As hacky as it gets. pub fn patch_cfg_if(&mut self) -> bool { - let cfg_if = self.hacky_find_crate("cfg_if"); - let std = self.hacky_find_crate("std"); + // we stupidly max by version in an attempt to have all duplicated std's depend on the same cfg_if so that deduplication still works + let cfg_if = + self.hacky_find_crate("cfg_if").max_by_key(|&it| self.arena[it].version.clone()); + let std = self.hacky_find_crate("std").next(); match (cfg_if, std) { (Some(cfg_if), Some(std)) => { - self.arena.get_mut(&cfg_if).unwrap().dependencies.clear(); - self.arena - .get_mut(&std) - .unwrap() + self.arena[cfg_if].dependencies.clear(); + self.arena[std] .dependencies .push(Dependency::new(CrateName::new("cfg_if").unwrap(), cfg_if)); true @@ -516,21 +644,15 @@ impl CrateGraph { } } - fn hacky_find_crate(&self, display_name: &str) -> Option { - self.iter().find(|it| self[*it].display_name.as_deref() == Some(display_name)) + fn hacky_find_crate<'a>(&'a self, display_name: &'a str) -> impl Iterator + 'a { + self.iter().filter(move |it| self[*it].display_name.as_deref() == Some(display_name)) } } impl ops::Index for CrateGraph { type Output = CrateData; fn index(&self, crate_id: CrateId) -> &CrateData { - &self.arena[&crate_id] - } -} - -impl CrateId { - fn shift(self, amount: u32) -> CrateId { - CrateId(self.0 + amount) + &self.arena[crate_id] } } @@ -632,7 +754,7 @@ impl fmt::Display for CyclicDependenciesError { mod tests { use crate::CrateOrigin; - use super::{CfgOptions, CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId}; + use super::{CrateGraph, CrateName, Dependency, Edition::Edition2018, Env, FileId}; #[test] fn detect_cyclic_dependency_indirect() { @@ -642,39 +764,39 @@ mod tests { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate3 = graph.add_crate_root( FileId(3u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -695,26 +817,26 @@ mod tests { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -732,39 +854,39 @@ mod tests { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate3 = graph.add_crate_root( FileId(3u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -782,26 +904,26 @@ mod tests { Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); let crate2 = graph.add_crate_root( FileId(2u32), Edition2018, None, None, - CfgOptions::default(), - CfgOptions::default(), + Default::default(), + Default::default(), Env::default(), - Ok(Vec::new()), false, - CrateOrigin::CratesIo { repo: None, name: None }, + CrateOrigin::Local { repo: None, name: None }, Err("".into()), + None, ); assert!(graph .add_dep( diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 9720db9d8ace3..af204e44e6ee1 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -6,18 +6,19 @@ mod input; mod change; pub mod fixture; -use std::{panic, sync::Arc}; +use std::panic; -use stdx::hash::NoHashHashSet; +use rustc_hash::FxHashSet; use syntax::{ast, Parse, SourceFile, TextRange, TextSize}; +use triomphe::Arc; pub use crate::{ change::Change, input::{ CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env, LangCrateOrigin, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, - ProcMacroId, ProcMacroKind, ProcMacroLoadResult, SourceRoot, SourceRootId, - TargetLayoutLoadResult, + ProcMacroId, ProcMacroKind, ProcMacroLoadResult, ProcMacroPaths, ProcMacros, + ReleaseChannel, SourceRoot, SourceRootId, TargetLayoutLoadResult, }, }; pub use salsa::{self, Cancelled}; @@ -53,13 +54,13 @@ pub struct FileRange { pub range: TextRange, } -pub const DEFAULT_LRU_CAP: usize = 128; +pub const DEFAULT_PARSE_LRU_CAP: usize = 128; pub trait FileLoader { /// Text of the file. - fn file_text(&self, file_id: FileId) -> Arc; + fn file_text(&self, file_id: FileId) -> Arc; fn resolve_path(&self, path: AnchoredPath<'_>) -> Option; - fn relevant_crates(&self, file_id: FileId) -> Arc>; + fn relevant_crates(&self, file_id: FileId) -> Arc>; } /// Database which stores all significant input facts: source code and project @@ -73,6 +74,10 @@ pub trait SourceDatabase: FileLoader + std::fmt::Debug { /// The crate graph. #[salsa::input] fn crate_graph(&self) -> Arc; + + /// The crate graph. + #[salsa::input] + fn proc_macros(&self) -> Arc; } fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse { @@ -86,7 +91,7 @@ fn parse_query(db: &dyn SourceDatabase, file_id: FileId) -> Parse Arc; + fn file_text(&self, file_id: FileId) -> Arc; /// Path to a file, relative to the root of its source root. /// Source root of the file. #[salsa::input] @@ -95,10 +100,10 @@ pub trait SourceDatabaseExt: SourceDatabase { #[salsa::input] fn source_root(&self, id: SourceRootId) -> Arc; - fn source_root_crates(&self, id: SourceRootId) -> Arc>; + fn source_root_crates(&self, id: SourceRootId) -> Arc>; } -fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc> { +fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc> { let graph = db.crate_graph(); let res = graph .iter() @@ -114,7 +119,7 @@ fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc(pub T); impl FileLoader for FileLoaderDelegate<&'_ T> { - fn file_text(&self, file_id: FileId) -> Arc { + fn file_text(&self, file_id: FileId) -> Arc { SourceDatabaseExt::file_text(self.0, file_id) } fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { @@ -124,7 +129,7 @@ impl FileLoader for FileLoaderDelegate<&'_ T> { source_root.resolve_path(path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { let _p = profile::span("relevant_crates"); let source_root = self.0.file_source_root(file_id); self.0.source_root_crates(source_root) diff --git a/src/tools/rust-analyzer/crates/cfg/Cargo.toml b/src/tools/rust-analyzer/crates/cfg/Cargo.toml index 0880bc239d83f..ed380897278b3 100644 --- a/src/tools/rust-analyzer/crates/cfg/Cargo.toml +++ b/src/tools/rust-analyzer/crates/cfg/Cargo.toml @@ -18,13 +18,13 @@ rustc-hash = "1.1.0" tt.workspace = true [dev-dependencies] -expect-test = "1.4.0" +expect-test = "1.4.1" oorandom = "11.1.3" # We depend on both individually instead of using `features = ["derive"]` to microoptimize the # build graph: if the feature was enabled, syn would be built early on in the graph if `smolstr` # supports `arbitrary`. This way, we avoid feature unification. -arbitrary = "1.2.2" -derive_arbitrary = "1.2.2" +arbitrary = "1.3.0" +derive_arbitrary = "1.3.1" # local deps mbe.workspace = true diff --git a/src/tools/rust-analyzer/crates/cfg/src/lib.rs b/src/tools/rust-analyzer/crates/cfg/src/lib.rs index 30709c968dacf..183b9b7d278c1 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/lib.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/lib.rs @@ -69,7 +69,7 @@ impl CfgOptions { } pub fn get_cfg_keys(&self) -> impl Iterator { - self.enabled.iter().map(|x| match x { + self.enabled.iter().map(|it| match it { CfgAtom::Flag(key) => key, CfgAtom::KeyValue { key, .. } => key, }) @@ -79,14 +79,14 @@ impl CfgOptions { &'a self, cfg_key: &'a str, ) -> impl Iterator + 'a { - self.enabled.iter().filter_map(move |x| match x { + self.enabled.iter().filter_map(move |it| match it { CfgAtom::KeyValue { key, value } if cfg_key == key => Some(value), _ => None, }) } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CfgDiff { // Invariants: No duplicates, no atom that's both in `enable` and `disable`. enable: Vec, diff --git a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml index 609d18c4eea33..e7f7adc784717 100644 --- a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml +++ b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml @@ -12,13 +12,12 @@ rust-version.workspace = true doctest = false [dependencies] -crossbeam-channel = "0.5.5" +crossbeam-channel = "0.5.8" tracing = "0.1.37" -cargo_metadata = "0.15.0" +cargo_metadata = "0.15.4" rustc-hash = "1.1.0" -serde = { version = "1.0.137", features = ["derive"] } -serde_json = "1.0.86" -jod-thread = "0.1.2" +serde_json.workspace = true +serde.workspace = true command-group = "2.0.1" # local deps diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs index accb14a51deb9..fbb943ccb99dd 100644 --- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs +++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs @@ -77,7 +77,7 @@ impl fmt::Display for FlycheckConfig { pub struct FlycheckHandle { // XXX: drop order is significant sender: Sender, - _thread: jod_thread::JoinHandle, + _thread: stdx::thread::JoinHandle, id: usize, } @@ -90,7 +90,7 @@ impl FlycheckHandle { ) -> FlycheckHandle { let actor = FlycheckActor::new(id, sender, config, workspace_root); let (sender, receiver) = unbounded::(); - let thread = jod_thread::Builder::new() + let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) .name("Flycheck".to_owned()) .spawn(move || actor.run(receiver)) .expect("failed to spawn thread"); @@ -395,7 +395,7 @@ struct CargoHandle { /// The handle to the actual cargo process. As we cannot cancel directly from with /// a read syscall dropping and therefore terminating the process is our best option. child: JodGroupChild, - thread: jod_thread::JoinHandle>, + thread: stdx::thread::JoinHandle>, receiver: Receiver, } @@ -409,7 +409,7 @@ impl CargoHandle { let (sender, receiver) = unbounded(); let actor = CargoActor::new(sender, stdout, stderr); - let thread = jod_thread::Builder::new() + let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker) .name("CargoHandle".to_owned()) .spawn(move || actor.run()) .expect("failed to spawn thread"); @@ -485,7 +485,7 @@ impl CargoActor { error.push_str(line); error.push('\n'); - return false; + false }; let output = streaming_output( self.stdout, diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml index 31d4018d2b6ab..30307deb79b88 100644 --- a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml @@ -14,21 +14,22 @@ doctest = false [dependencies] anymap = "1.0.0-beta.2" arrayvec = "0.7.2" -bitflags = "1.3.2" +bitflags = "2.1.0" cov-mark = "2.0.0-pre.1" # We need to freeze the version of the crate, as the raw-api feature is considered unstable dashmap = { version = "=5.4.0", features = ["raw-api"] } drop_bomb = "0.1.5" either = "1.7.0" fst = { version = "0.4.7", default-features = false } -hashbrown = { version = "0.12.1", default-features = false } -indexmap = "1.9.1" +indexmap = "2.0.0" itertools = "0.10.5" -la-arena = { version = "0.3.0", path = "../../lib/la-arena" } +la-arena.workspace = true once_cell = "1.17.0" rustc-hash = "1.1.0" -smallvec.workspace = true tracing = "0.1.35" +smallvec.workspace = true +hashbrown.workspace = true +triomphe.workspace = true rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs deleted file mode 100644 index b336f59ffee31..0000000000000 --- a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs +++ /dev/null @@ -1,554 +0,0 @@ -//! Defines hir-level representation of structs, enums and unions - -use std::sync::Arc; - -use base_db::CrateId; -use cfg::CfgOptions; -use either::Either; - -use hir_expand::{ - name::{AsName, Name}, - HirFileId, InFile, -}; -use intern::Interned; -use la_arena::{Arena, ArenaMap}; -use rustc_abi::{Integer, IntegerType}; -use syntax::ast::{self, HasName, HasVisibility}; - -use crate::{ - body::{CfgExpander, LowerCtx}, - builtin_type::{BuiltinInt, BuiltinUint}, - db::DefDatabase, - item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId}, - layout::{Align, ReprFlags, ReprOptions}, - nameres::diagnostics::DefDiagnostic, - src::HasChildSource, - src::HasSource, - trace::Trace, - tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}, - type_ref::TypeRef, - visibility::RawVisibility, - EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId, - VariantId, -}; - -/// Note that we use `StructData` for unions as well! -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StructData { - pub name: Name, - pub variant_data: Arc, - pub repr: Option, - pub visibility: RawVisibility, - pub rustc_has_incoherent_inherent_impls: bool, - pub fundamental: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EnumData { - pub name: Name, - pub variants: Arena, - pub repr: Option, - pub visibility: RawVisibility, - pub rustc_has_incoherent_inherent_impls: bool, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EnumVariantData { - pub name: Name, - pub variant_data: Arc, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum VariantData { - Record(Arena), - Tuple(Arena), - Unit, -} - -/// A single field of an enum variant or struct -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct FieldData { - pub name: Name, - pub type_ref: Interned, - pub visibility: RawVisibility, -} - -fn repr_from_value( - db: &dyn DefDatabase, - krate: CrateId, - item_tree: &ItemTree, - of: AttrOwner, -) -> Option { - item_tree.attrs(db, krate, of).by_key("repr").tt_values().find_map(parse_repr_tt) -} - -fn parse_repr_tt(tt: &Subtree) -> Option { - match tt.delimiter { - Delimiter { kind: DelimiterKind::Parenthesis, .. } => {} - _ => return None, - } - - let mut flags = ReprFlags::empty(); - let mut int = None; - let mut max_align: Option = None; - let mut min_pack: Option = None; - - let mut tts = tt.token_trees.iter().peekable(); - while let Some(tt) = tts.next() { - if let TokenTree::Leaf(Leaf::Ident(ident)) = tt { - flags.insert(match &*ident.text { - "packed" => { - let pack = if let Some(TokenTree::Subtree(tt)) = tts.peek() { - tts.next(); - if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() { - lit.text.parse().unwrap_or_default() - } else { - 0 - } - } else { - 0 - }; - let pack = Align::from_bytes(pack).unwrap(); - min_pack = - Some(if let Some(min_pack) = min_pack { min_pack.min(pack) } else { pack }); - ReprFlags::empty() - } - "align" => { - if let Some(TokenTree::Subtree(tt)) = tts.peek() { - tts.next(); - if let Some(TokenTree::Leaf(Leaf::Literal(lit))) = tt.token_trees.first() { - if let Ok(align) = lit.text.parse() { - let align = Align::from_bytes(align).ok(); - max_align = max_align.max(align); - } - } - } - ReprFlags::empty() - } - "C" => ReprFlags::IS_C, - "transparent" => ReprFlags::IS_TRANSPARENT, - repr => { - if let Some(builtin) = BuiltinInt::from_suffix(repr) - .map(Either::Left) - .or_else(|| BuiltinUint::from_suffix(repr).map(Either::Right)) - { - int = Some(match builtin { - Either::Left(bi) => match bi { - BuiltinInt::Isize => IntegerType::Pointer(true), - BuiltinInt::I8 => IntegerType::Fixed(Integer::I8, true), - BuiltinInt::I16 => IntegerType::Fixed(Integer::I16, true), - BuiltinInt::I32 => IntegerType::Fixed(Integer::I32, true), - BuiltinInt::I64 => IntegerType::Fixed(Integer::I64, true), - BuiltinInt::I128 => IntegerType::Fixed(Integer::I128, true), - }, - Either::Right(bu) => match bu { - BuiltinUint::Usize => IntegerType::Pointer(false), - BuiltinUint::U8 => IntegerType::Fixed(Integer::I8, false), - BuiltinUint::U16 => IntegerType::Fixed(Integer::I16, false), - BuiltinUint::U32 => IntegerType::Fixed(Integer::I32, false), - BuiltinUint::U64 => IntegerType::Fixed(Integer::I64, false), - BuiltinUint::U128 => IntegerType::Fixed(Integer::I128, false), - }, - }); - } - ReprFlags::empty() - } - }) - } - } - - Some(ReprOptions { int, align: max_align, pack: min_pack, flags, field_shuffle_seed: 0 }) -} - -impl StructData { - pub(crate) fn struct_data_query(db: &dyn DefDatabase, id: StructId) -> Arc { - db.struct_data_with_diagnostics(id).0 - } - - pub(crate) fn struct_data_with_diagnostics_query( - db: &dyn DefDatabase, - id: StructId, - ) -> (Arc, Arc<[DefDiagnostic]>) { - let loc = id.lookup(db); - let krate = loc.container.krate; - let item_tree = loc.id.item_tree(db); - let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); - let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); - let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); - let rustc_has_incoherent_inherent_impls = - attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); - let fundamental = attrs.by_key("fundamental").exists(); - - let strukt = &item_tree[loc.id.value]; - let (variant_data, diagnostics) = lower_fields( - db, - krate, - loc.id.file_id(), - loc.container.local_id, - &item_tree, - &cfg_options, - &strukt.fields, - None, - ); - ( - Arc::new(StructData { - name: strukt.name.clone(), - variant_data: Arc::new(variant_data), - repr, - visibility: item_tree[strukt.visibility].clone(), - rustc_has_incoherent_inherent_impls, - fundamental, - }), - diagnostics.into(), - ) - } - - pub(crate) fn union_data_query(db: &dyn DefDatabase, id: UnionId) -> Arc { - db.union_data_with_diagnostics(id).0 - } - - pub(crate) fn union_data_with_diagnostics_query( - db: &dyn DefDatabase, - id: UnionId, - ) -> (Arc, Arc<[DefDiagnostic]>) { - let loc = id.lookup(db); - let krate = loc.container.krate; - let item_tree = loc.id.item_tree(db); - let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); - let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); - - let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); - let rustc_has_incoherent_inherent_impls = - attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); - let fundamental = attrs.by_key("fundamental").exists(); - - let union = &item_tree[loc.id.value]; - let (variant_data, diagnostics) = lower_fields( - db, - krate, - loc.id.file_id(), - loc.container.local_id, - &item_tree, - &cfg_options, - &union.fields, - None, - ); - ( - Arc::new(StructData { - name: union.name.clone(), - variant_data: Arc::new(variant_data), - repr, - visibility: item_tree[union.visibility].clone(), - rustc_has_incoherent_inherent_impls, - fundamental, - }), - diagnostics.into(), - ) - } -} - -impl EnumData { - pub(crate) fn enum_data_query(db: &dyn DefDatabase, e: EnumId) -> Arc { - db.enum_data_with_diagnostics(e).0 - } - - pub(crate) fn enum_data_with_diagnostics_query( - db: &dyn DefDatabase, - e: EnumId, - ) -> (Arc, Arc<[DefDiagnostic]>) { - let loc = e.lookup(db); - let krate = loc.container.krate; - let item_tree = loc.id.item_tree(db); - let cfg_options = db.crate_graph()[krate].cfg_options.clone(); - let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); - let rustc_has_incoherent_inherent_impls = item_tree - .attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()) - .by_key("rustc_has_incoherent_inherent_impls") - .exists(); - - let enum_ = &item_tree[loc.id.value]; - let mut variants = Arena::new(); - let mut diagnostics = Vec::new(); - for tree_id in enum_.variants.clone() { - let attrs = item_tree.attrs(db, krate, tree_id.into()); - let var = &item_tree[tree_id]; - if attrs.is_cfg_enabled(&cfg_options) { - let (var_data, field_diagnostics) = lower_fields( - db, - krate, - loc.id.file_id(), - loc.container.local_id, - &item_tree, - &cfg_options, - &var.fields, - Some(enum_.visibility), - ); - diagnostics.extend(field_diagnostics); - - variants.alloc(EnumVariantData { - name: var.name.clone(), - variant_data: Arc::new(var_data), - }); - } else { - diagnostics.push(DefDiagnostic::unconfigured_code( - loc.container.local_id, - InFile::new(loc.id.file_id(), var.ast_id.upcast()), - attrs.cfg().unwrap(), - cfg_options.clone(), - )) - } - } - - ( - Arc::new(EnumData { - name: enum_.name.clone(), - variants, - repr, - visibility: item_tree[enum_.visibility].clone(), - rustc_has_incoherent_inherent_impls, - }), - diagnostics.into(), - ) - } - - pub fn variant(&self, name: &Name) -> Option { - let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?; - Some(id) - } - - pub fn variant_body_type(&self) -> IntegerType { - match self.repr { - Some(ReprOptions { int: Some(builtin), .. }) => builtin, - _ => IntegerType::Pointer(true), - } - } -} - -impl HasChildSource for EnumId { - type Value = ast::Variant; - fn child_source( - &self, - db: &dyn DefDatabase, - ) -> InFile> { - let src = self.lookup(db).source(db); - let mut trace = Trace::new_for_map(); - lower_enum(db, &mut trace, &src, self.lookup(db).container); - src.with_value(trace.into_map()) - } -} - -fn lower_enum( - db: &dyn DefDatabase, - trace: &mut Trace, - ast: &InFile, - module_id: ModuleId, -) { - let expander = CfgExpander::new(db, ast.file_id, module_id.krate); - let variants = ast - .value - .variant_list() - .into_iter() - .flat_map(|it| it.variants()) - .filter(|var| expander.is_cfg_enabled(db, var)); - for var in variants { - trace.alloc( - || var.clone(), - || EnumVariantData { - name: var.name().map_or_else(Name::missing, |it| it.as_name()), - variant_data: Arc::new(VariantData::new(db, ast.with_value(var.kind()), module_id)), - }, - ); - } -} - -impl VariantData { - fn new(db: &dyn DefDatabase, flavor: InFile, module_id: ModuleId) -> Self { - let mut expander = CfgExpander::new(db, flavor.file_id, module_id.krate); - let mut trace = Trace::new_for_arena(); - match lower_struct(db, &mut expander, &mut trace, &flavor) { - StructKind::Tuple => VariantData::Tuple(trace.into_arena()), - StructKind::Record => VariantData::Record(trace.into_arena()), - StructKind::Unit => VariantData::Unit, - } - } - - pub fn fields(&self) -> &Arena { - const EMPTY: &Arena = &Arena::new(); - match &self { - VariantData::Record(fields) | VariantData::Tuple(fields) => fields, - _ => EMPTY, - } - } - - pub fn field(&self, name: &Name) -> Option { - self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None }) - } - - pub fn kind(&self) -> StructKind { - match self { - VariantData::Record(_) => StructKind::Record, - VariantData::Tuple(_) => StructKind::Tuple, - VariantData::Unit => StructKind::Unit, - } - } -} - -impl HasChildSource for VariantId { - type Value = Either; - - fn child_source(&self, db: &dyn DefDatabase) -> InFile> { - let (src, module_id) = match self { - VariantId::EnumVariantId(it) => { - // I don't really like the fact that we call into parent source - // here, this might add to more queries then necessary. - let src = it.parent.child_source(db); - (src.map(|map| map[it.local_id].kind()), it.parent.lookup(db).container) - } - VariantId::StructId(it) => { - (it.lookup(db).source(db).map(|it| it.kind()), it.lookup(db).container) - } - VariantId::UnionId(it) => ( - it.lookup(db).source(db).map(|it| { - it.record_field_list() - .map(ast::StructKind::Record) - .unwrap_or(ast::StructKind::Unit) - }), - it.lookup(db).container, - ), - }; - let mut expander = CfgExpander::new(db, src.file_id, module_id.krate); - let mut trace = Trace::new_for_map(); - lower_struct(db, &mut expander, &mut trace, &src); - src.with_value(trace.into_map()) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum StructKind { - Tuple, - Record, - Unit, -} - -fn lower_struct( - db: &dyn DefDatabase, - expander: &mut CfgExpander, - trace: &mut Trace>, - ast: &InFile, -) -> StructKind { - let ctx = LowerCtx::new(db, ast.file_id); - - match &ast.value { - ast::StructKind::Tuple(fl) => { - for (i, fd) in fl.fields().enumerate() { - if !expander.is_cfg_enabled(db, &fd) { - continue; - } - - trace.alloc( - || Either::Left(fd.clone()), - || FieldData { - name: Name::new_tuple_field(i), - type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())), - visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())), - }, - ); - } - StructKind::Tuple - } - ast::StructKind::Record(fl) => { - for fd in fl.fields() { - if !expander.is_cfg_enabled(db, &fd) { - continue; - } - - trace.alloc( - || Either::Right(fd.clone()), - || FieldData { - name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), - type_ref: Interned::new(TypeRef::from_ast_opt(&ctx, fd.ty())), - visibility: RawVisibility::from_ast(db, ast.with_value(fd.visibility())), - }, - ); - } - StructKind::Record - } - ast::StructKind::Unit => StructKind::Unit, - } -} - -fn lower_fields( - db: &dyn DefDatabase, - krate: CrateId, - current_file_id: HirFileId, - container: LocalModuleId, - item_tree: &ItemTree, - cfg_options: &CfgOptions, - fields: &Fields, - override_visibility: Option, -) -> (VariantData, Vec) { - let mut diagnostics = Vec::new(); - match fields { - Fields::Record(flds) => { - let mut arena = Arena::new(); - for field_id in flds.clone() { - let attrs = item_tree.attrs(db, krate, field_id.into()); - let field = &item_tree[field_id]; - if attrs.is_cfg_enabled(cfg_options) { - arena.alloc(lower_field(item_tree, field, override_visibility)); - } else { - diagnostics.push(DefDiagnostic::unconfigured_code( - container, - InFile::new( - current_file_id, - match field.ast_id { - FieldAstId::Record(it) => it.upcast(), - FieldAstId::Tuple(it) => it.upcast(), - }, - ), - attrs.cfg().unwrap(), - cfg_options.clone(), - )) - } - } - (VariantData::Record(arena), diagnostics) - } - Fields::Tuple(flds) => { - let mut arena = Arena::new(); - for field_id in flds.clone() { - let attrs = item_tree.attrs(db, krate, field_id.into()); - let field = &item_tree[field_id]; - if attrs.is_cfg_enabled(cfg_options) { - arena.alloc(lower_field(item_tree, field, override_visibility)); - } else { - diagnostics.push(DefDiagnostic::unconfigured_code( - container, - InFile::new( - current_file_id, - match field.ast_id { - FieldAstId::Record(it) => it.upcast(), - FieldAstId::Tuple(it) => it.upcast(), - }, - ), - attrs.cfg().unwrap(), - cfg_options.clone(), - )) - } - } - (VariantData::Tuple(arena), diagnostics) - } - Fields::Unit => (VariantData::Unit, diagnostics), - } -} - -fn lower_field( - item_tree: &ItemTree, - field: &Field, - override_visibility: Option, -) -> FieldData { - FieldData { - name: field.name.clone(), - type_ref: field.type_ref.clone(), - visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(), - } -} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs index 200072c172ebe..fae07111806c0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs @@ -1,6 +1,11 @@ //! A higher level attributes based on TokenTree, with also some shortcuts. -use std::{hash::Hash, ops, sync::Arc}; +pub mod builtin; + +#[cfg(test)] +mod tests; + +use std::{hash::Hash, ops}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; @@ -16,14 +21,16 @@ use syntax::{ ast::{self, HasAttrs, IsString}, AstPtr, AstToken, SmolStr, TextRange, TextSize, }; +use triomphe::Arc; use crate::{ db::DefDatabase, item_tree::{AttrOwner, Fields, ItemTreeId, ItemTreeNode}, + lang_item::LangItem, nameres::{ModuleOrigin, ModuleSource}, src::{HasChildSource, HasSource}, - AdtId, AttrDefId, EnumId, GenericParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroId, - VariantId, + AdtId, AssocItemLoc, AttrDefId, EnumId, GenericParamId, ItemLoc, LocalEnumVariantId, + LocalFieldId, Lookup, MacroId, VariantId, }; /// Holds documentation @@ -88,6 +95,7 @@ impl Attrs { db: &dyn DefDatabase, e: EnumId, ) -> Arc> { + let _p = profile::span("variants_attrs_query"); // FIXME: There should be some proper form of mapping between item tree enum variant ids and hir enum variant ids let mut res = ArenaMap::default(); @@ -114,6 +122,7 @@ impl Attrs { db: &dyn DefDatabase, v: VariantId, ) -> Arc> { + let _p = profile::span("fields_attrs_query"); // FIXME: There should be some proper form of mapping between item tree field ids and hir field ids let mut res = ArenaMap::default(); @@ -128,13 +137,16 @@ impl Attrs { let cfg_options = &crate_graph[krate].cfg_options; - let Some(variant) = enum_.variants.clone().filter(|variant| { - let attrs = item_tree.attrs(db, krate, (*variant).into()); - attrs.is_cfg_enabled(cfg_options) - }) - .zip(0u32..) - .find(|(_variant, idx)| it.local_id == Idx::from_raw(RawIdx::from(*idx))) - .map(|(variant, _idx)| variant) + let Some(variant) = enum_ + .variants + .clone() + .filter(|variant| { + let attrs = item_tree.attrs(db, krate, (*variant).into()); + attrs.is_cfg_enabled(cfg_options) + }) + .zip(0u32..) + .find(|(_variant, idx)| it.local_id == Idx::from_raw(RawIdx::from(*idx))) + .map(|(variant, _idx)| variant) else { return Arc::new(res); }; @@ -175,13 +187,13 @@ impl Attrs { Arc::new(res) } +} +impl Attrs { pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { AttrQuery { attrs: self, key } } -} -impl Attrs { pub fn cfg(&self) -> Option { let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse); let first = cfgs.next()?; @@ -193,6 +205,7 @@ impl Attrs { None => Some(first), } } + pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { match self.cfg() { None => true, @@ -204,6 +217,10 @@ impl Attrs { self.by_key("lang").string_value() } + pub fn lang_item(&self) -> Option { + self.by_key("lang").string_value().and_then(|it| LangItem::from_str(it)) + } + pub fn docs(&self) -> Option { let docs = self.by_key("doc").attrs().filter_map(|attr| attr.string_value()); let indent = doc_indent(self); @@ -238,6 +255,14 @@ impl Attrs { }) } + pub fn doc_exprs(&self) -> impl Iterator + '_ { + self.by_key("doc").tt_values().map(DocExpr::parse) + } + + pub fn doc_aliases(&self) -> impl Iterator + '_ { + self.doc_exprs().flat_map(|doc_expr| doc_expr.aliases().to_vec()) + } + pub fn is_proc_macro(&self) -> bool { self.by_key("proc_macro").exists() } @@ -249,10 +274,139 @@ impl Attrs { pub fn is_proc_macro_derive(&self) -> bool { self.by_key("proc_macro_derive").exists() } + + pub fn is_test(&self) -> bool { + self.iter().any(|it| { + it.path() + .segments() + .iter() + .rev() + .zip(["core", "prelude", "v1", "test"].iter().rev()) + .all(|it| it.0.as_str() == Some(it.1)) + }) + } + + pub fn is_ignore(&self) -> bool { + self.by_key("ignore").exists() + } + + pub fn is_bench(&self) -> bool { + self.by_key("bench").exists() + } + + pub fn is_unstable(&self) -> bool { + self.by_key("unstable").exists() + } +} + +use std::slice::Iter as SliceIter; +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum DocAtom { + /// eg. `#[doc(hidden)]` + Flag(SmolStr), + /// eg. `#[doc(alias = "it")]` + /// + /// Note that a key can have multiple values that are all considered "active" at the same time. + /// For example, `#[doc(alias = "x")]` and `#[doc(alias = "y")]`. + KeyValue { key: SmolStr, value: SmolStr }, +} + +// Adapted from `CfgExpr` parsing code +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +// #[cfg_attr(test, derive(derive_arbitrary::Arbitrary))] +pub enum DocExpr { + Invalid, + /// eg. `#[doc(hidden)]`, `#[doc(alias = "x")]` + Atom(DocAtom), + /// eg. `#[doc(alias("x", "y"))]` + Alias(Vec), +} + +impl From for DocExpr { + fn from(atom: DocAtom) -> Self { + DocExpr::Atom(atom) + } +} + +impl DocExpr { + fn parse(tt: &tt::Subtree) -> DocExpr { + next_doc_expr(&mut tt.token_trees.iter()).unwrap_or(DocExpr::Invalid) + } + + pub fn aliases(&self) -> &[SmolStr] { + match self { + DocExpr::Atom(DocAtom::KeyValue { key, value }) if key == "alias" => { + std::slice::from_ref(value) + } + DocExpr::Alias(aliases) => aliases, + _ => &[], + } + } +} + +fn next_doc_expr(it: &mut SliceIter<'_, tt::TokenTree>) -> Option { + let name = match it.next() { + None => return None, + Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(), + Some(_) => return Some(DocExpr::Invalid), + }; + + // Peek + let ret = match it.as_slice().first() { + Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => { + match it.as_slice().get(1) { + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => { + it.next(); + it.next(); + // FIXME: escape? raw string? + let value = + SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); + DocAtom::KeyValue { key: name, value }.into() + } + _ => return Some(DocExpr::Invalid), + } + } + Some(tt::TokenTree::Subtree(subtree)) => { + it.next(); + let subs = parse_comma_sep(subtree); + match name.as_str() { + "alias" => DocExpr::Alias(subs), + _ => DocExpr::Invalid, + } + } + _ => DocAtom::Flag(name).into(), + }; + + // Eat comma separator + if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() { + if punct.char == ',' { + it.next(); + } + } + Some(ret) +} + +fn parse_comma_sep(subtree: &tt::Subtree) -> Vec { + subtree + .token_trees + .iter() + .filter_map(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { + // FIXME: escape? raw string? + Some(SmolStr::new(lit.text.trim_start_matches('"').trim_end_matches('"'))) + } + _ => None, + }) + .collect() } impl AttrsWithOwner { - pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Self { + pub(crate) fn attrs_with_owner(db: &dyn DefDatabase, owner: AttrDefId) -> Self { + Self { attrs: db.attrs(owner), owner } + } + + pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs { + let _p = profile::span("attrs_query"); // FIXME: this should use `Trace` to avoid duplication in `source_map` below let raw_attrs = match def { AttrDefId::ModuleId(module) => { @@ -286,31 +440,29 @@ impl AttrsWithOwner { } } AttrDefId::FieldId(it) => { - return Self { attrs: db.fields_attrs(it.parent)[it.local_id].clone(), owner: def }; + return db.fields_attrs(it.parent)[it.local_id].clone(); } AttrDefId::EnumVariantId(it) => { - return Self { - attrs: db.variants_attrs(it.parent)[it.local_id].clone(), - owner: def, - }; + return db.variants_attrs(it.parent)[it.local_id].clone(); } + // FIXME: DRY this up AttrDefId::AdtId(it) => match it { - AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AdtId::StructId(it) => attrs_from_item_tree_loc(db, it), + AdtId::EnumId(it) => attrs_from_item_tree_loc(db, it), + AdtId::UnionId(it) => attrs_from_item_tree_loc(db, it), }, - AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::TraitAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::TraitId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::TraitAliasId(it) => attrs_from_item_tree_loc(db, it), AttrDefId::MacroId(it) => match it { - MacroId::Macro2Id(it) => attrs_from_item_tree(it.lookup(db).id, db), - MacroId::MacroRulesId(it) => attrs_from_item_tree(it.lookup(db).id, db), - MacroId::ProcMacroId(it) => attrs_from_item_tree(it.lookup(db).id, db), + MacroId::Macro2Id(it) => attrs_from_item_tree(db, it.lookup(db).id), + MacroId::MacroRulesId(it) => attrs_from_item_tree(db, it.lookup(db).id), + MacroId::ProcMacroId(it) => attrs_from_item_tree(db, it.lookup(db).id), }, - AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db), - AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::ImplId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::ConstId(it) => attrs_from_item_tree_assoc(db, it), + AttrDefId::StaticId(it) => attrs_from_item_tree_assoc(db, it), + AttrDefId::FunctionId(it) => attrs_from_item_tree_assoc(db, it), + AttrDefId::TypeAliasId(it) => attrs_from_item_tree_assoc(db, it), AttrDefId::GenericParamId(it) => match it { GenericParamId::ConstParamId(it) => { let src = it.parent().child_source(db); @@ -331,11 +483,13 @@ impl AttrsWithOwner { RawAttrs::from_attrs_owner(db.upcast(), src.with_value(&src.value[it.local_id])) } }, - AttrDefId::ExternBlockId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::ExternBlockId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::ExternCrateId(it) => attrs_from_item_tree_loc(db, it), + AttrDefId::UseId(it) => attrs_from_item_tree_loc(db, it), }; let attrs = raw_attrs.filter(db.upcast(), def.krate(db)); - Self { attrs: Attrs(attrs), owner: def } + Attrs(attrs) } pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { @@ -371,7 +525,7 @@ impl AttrsWithOwner { AttrDefId::FieldId(id) => { let map = db.fields_attrs_source_map(id.parent); let file_id = id.parent.file_id(db); - let root = db.parse_or_expand(file_id).unwrap(); + let root = db.parse_or_expand(file_id); let owner = match &map[id.local_id] { Either::Left(it) => ast::AnyHasAttrs::new(it.to_node(&root)), Either::Right(it) => ast::AnyHasAttrs::new(it.to_node(&root)), @@ -379,28 +533,28 @@ impl AttrsWithOwner { InFile::new(file_id, owner) } AttrDefId::AdtId(adt) => match adt { - AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AdtId::StructId(id) => any_has_attrs(db, id), + AdtId::UnionId(id) => any_has_attrs(db, id), + AdtId::EnumId(id) => any_has_attrs(db, id), }, - AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::FunctionId(id) => any_has_attrs(db, id), AttrDefId::EnumVariantId(id) => { let map = db.variants_attrs_source_map(id.parent); let file_id = id.parent.lookup(db).id.file_id(); - let root = db.parse_or_expand(file_id).unwrap(); + let root = db.parse_or_expand(file_id); InFile::new(file_id, ast::AnyHasAttrs::new(map[id.local_id].to_node(&root))) } - AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::TraitAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::StaticId(id) => any_has_attrs(db, id), + AttrDefId::ConstId(id) => any_has_attrs(db, id), + AttrDefId::TraitId(id) => any_has_attrs(db, id), + AttrDefId::TraitAliasId(id) => any_has_attrs(db, id), + AttrDefId::TypeAliasId(id) => any_has_attrs(db, id), AttrDefId::MacroId(id) => match id { - MacroId::Macro2Id(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - MacroId::MacroRulesId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), - MacroId::ProcMacroId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + MacroId::Macro2Id(id) => any_has_attrs(db, id), + MacroId::MacroRulesId(id) => any_has_attrs(db, id), + MacroId::ProcMacroId(id) => any_has_attrs(db, id), }, - AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::ImplId(id) => any_has_attrs(db, id), AttrDefId::GenericParamId(id) => match id { GenericParamId::ConstParamId(id) => id .parent() @@ -415,7 +569,9 @@ impl AttrsWithOwner { .child_source(db) .map(|source| ast::AnyHasAttrs::new(source[id.local_id].clone())), }, - AttrDefId::ExternBlockId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::ExternBlockId(id) => any_has_attrs(db, id), + AttrDefId::ExternCrateId(id) => any_has_attrs(db, id), + AttrDefId::UseId(id) => any_has_attrs(db, id), }; AttrSourceMap::new(owner.as_ref().map(|node| node as &dyn HasAttrs)) @@ -635,19 +791,42 @@ impl<'attr> AttrQuery<'attr> { .nth(2); match name { - Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ref text, ..}))) => Some(text), + Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal{ ref text, ..}))) => Some(text), _ => None } }) } } -fn attrs_from_item_tree(id: ItemTreeId, db: &dyn DefDatabase) -> RawAttrs { +fn any_has_attrs( + db: &dyn DefDatabase, + id: impl Lookup>, +) -> InFile { + id.lookup(db).source(db).map(ast::AnyHasAttrs::new) +} + +fn attrs_from_item_tree(db: &dyn DefDatabase, id: ItemTreeId) -> RawAttrs { let tree = id.item_tree(db); let mod_item = N::id_to_mod_item(id.value); tree.raw_attrs(mod_item.into()).clone() } +fn attrs_from_item_tree_loc( + db: &dyn DefDatabase, + lookup: impl Lookup>, +) -> RawAttrs { + let id = lookup.lookup(db).id; + attrs_from_item_tree(db, id) +} + +fn attrs_from_item_tree_assoc( + db: &dyn DefDatabase, + lookup: impl Lookup>, +) -> RawAttrs { + let id = lookup.lookup(db).id; + attrs_from_item_tree(db, id) +} + pub(crate) fn variants_attrs_source_map( db: &dyn DefDatabase, def: EnumId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs new file mode 100644 index 0000000000000..cead64a33749f --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr/builtin.rs @@ -0,0 +1,693 @@ +//! Builtin attributes resolved by nameres. +//! +//! The actual definitions were copied from rustc's `compiler/rustc_feature/src/builtin_attrs.rs`. +//! +//! It was last synchronized with upstream commit e29821ff85a2a3000d226f99f62f89464028d5d6. +//! +//! The macros were adjusted to only expand to the attribute name, since that is all we need to do +//! name resolution, and `BUILTIN_ATTRIBUTES` is almost entirely unchanged from the original, to +//! ease updating. + +use once_cell::sync::OnceCell; +use rustc_hash::FxHashMap; + +/// Ignored attribute namespaces used by tools. +pub const TOOL_MODULES: &[&str] = &["rustfmt", "clippy"]; + +pub struct BuiltinAttribute { + pub name: &'static str, + pub template: AttributeTemplate, +} + +/// A template that the attribute input must match. +/// Only top-level shape (`#[attr]` vs `#[attr(...)]` vs `#[attr = ...]`) is considered now. +#[derive(Clone, Copy)] +pub struct AttributeTemplate { + pub word: bool, + pub list: Option<&'static str>, + pub name_value_str: Option<&'static str>, +} + +pub fn find_builtin_attr_idx(name: &str) -> Option { + static BUILTIN_LOOKUP_TABLE: OnceCell> = OnceCell::new(); + BUILTIN_LOOKUP_TABLE + .get_or_init(|| { + INERT_ATTRIBUTES.iter().map(|attr| attr.name).enumerate().map(|(a, b)| (b, a)).collect() + }) + .get(name) + .copied() +} + +// impl AttributeTemplate { +// const DEFAULT: AttributeTemplate = +// AttributeTemplate { word: false, list: None, name_value_str: None }; +// } + +/// A convenience macro for constructing attribute templates. +/// E.g., `template!(Word, List: "description")` means that the attribute +/// supports forms `#[attr]` and `#[attr(description)]`. +macro_rules! template { + (Word) => { template!(@ true, None, None) }; + (List: $descr: expr) => { template!(@ false, Some($descr), None) }; + (NameValueStr: $descr: expr) => { template!(@ false, None, Some($descr)) }; + (Word, List: $descr: expr) => { template!(@ true, Some($descr), None) }; + (Word, NameValueStr: $descr: expr) => { template!(@ true, None, Some($descr)) }; + (List: $descr1: expr, NameValueStr: $descr2: expr) => { + template!(@ false, Some($descr1), Some($descr2)) + }; + (Word, List: $descr1: expr, NameValueStr: $descr2: expr) => { + template!(@ true, Some($descr1), Some($descr2)) + }; + (@ $word: expr, $list: expr, $name_value_str: expr) => { + AttributeTemplate { + word: $word, list: $list, name_value_str: $name_value_str + } + }; +} + +macro_rules! ungated { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)? $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; +} + +macro_rules! gated { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $gate:ident, $msg:expr $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; +} + +macro_rules! rustc_attr { + (TEST, $attr:ident, $typ:expr, $tpl:expr, $duplicate:expr $(, @only_local: $only_local:expr)? $(,)?) => { + rustc_attr!( + $attr, + $typ, + $tpl, + $duplicate, + $(@only_local: $only_local,)? + concat!( + "the `#[", + stringify!($attr), + "]` attribute is just used for rustc unit tests \ + and will never be stable", + ), + ) + }; + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr $(, @only_local: $only_local:expr)?, $msg:expr $(,)?) => { + BuiltinAttribute { name: stringify!($attr), template: $tpl } + }; +} + +#[allow(unused_macros)] +macro_rules! experimental { + ($attr:ident) => { + concat!("the `#[", stringify!($attr), "]` attribute is an experimental feature") + }; +} + +/// Attributes that have a special meaning to rustc or rustdoc. +#[rustfmt::skip] +pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ + // ========================================================================== + // Stable attributes: + // ========================================================================== + + // Conditional compilation: + ungated!(cfg, Normal, template!(List: "predicate"), DuplicatesOk), + ungated!(cfg_attr, Normal, template!(List: "predicate, attr1, attr2, ..."), DuplicatesOk), + + // Testing: + ungated!(ignore, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing), + ungated!( + should_panic, Normal, + template!(Word, List: r#"expected = "reason""#, NameValueStr: "reason"), FutureWarnFollowing, + ), + // FIXME(Centril): This can be used on stable but shouldn't. + ungated!(reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"), ErrorFollowing), + + // Macros: + ungated!(automatically_derived, Normal, template!(Word), WarnFollowing), + ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ..."), WarnFollowingWordOnly), + ungated!(macro_escape, Normal, template!(Word), WarnFollowing), // Deprecated synonym for `macro_use`. + ungated!(macro_export, Normal, template!(Word, List: "local_inner_macros"), WarnFollowing), + ungated!(proc_macro, Normal, template!(Word), ErrorFollowing), + ungated!( + proc_macro_derive, Normal, + template!(List: "TraitName, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing, + ), + ungated!(proc_macro_attribute, Normal, template!(Word), ErrorFollowing), + + // Lints: + ungated!( + warn, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, + ), + ungated!( + allow, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, + ), + gated!( + expect, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), DuplicatesOk, + lint_reasons, experimental!(expect) + ), + ungated!( + forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, + ), + ungated!( + deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#), + DuplicatesOk, @only_local: true, + ), + ungated!(must_use, Normal, template!(Word, NameValueStr: "reason"), FutureWarnFollowing), + gated!( + must_not_suspend, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing, + experimental!(must_not_suspend) + ), + ungated!( + deprecated, Normal, + template!( + Word, + List: r#"/*opt*/ since = "version", /*opt*/ note = "reason""#, + NameValueStr: "reason" + ), + ErrorFollowing + ), + + // Crate properties: + ungated!(crate_name, CrateLevel, template!(NameValueStr: "name"), FutureWarnFollowing), + ungated!(crate_type, CrateLevel, template!(NameValueStr: "bin|lib|..."), DuplicatesOk), + // crate_id is deprecated + ungated!(crate_id, CrateLevel, template!(NameValueStr: "ignored"), FutureWarnFollowing), + + // ABI, linking, symbols, and FFI + ungated!( + link, Normal, + template!(List: r#"name = "...", /*opt*/ kind = "dylib|static|...", /*opt*/ wasm_import_module = "...", /*opt*/ import_name_type = "decorated|noprefix|undecorated""#), + DuplicatesOk, + ), + ungated!(link_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), + ungated!(no_link, Normal, template!(Word), WarnFollowing), + ungated!(repr, Normal, template!(List: "C"), DuplicatesOk, @only_local: true), + ungated!(export_name, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), + ungated!(link_section, Normal, template!(NameValueStr: "name"), FutureWarnPreceding), + ungated!(no_mangle, Normal, template!(Word), WarnFollowing, @only_local: true), + ungated!(used, Normal, template!(Word, List: "compiler|linker"), WarnFollowing, @only_local: true), + ungated!(link_ordinal, Normal, template!(List: "ordinal"), ErrorPreceding), + + // Limits: + ungated!(recursion_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), + ungated!(type_length_limit, CrateLevel, template!(NameValueStr: "N"), FutureWarnFollowing), + gated!( + move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, + large_assignments, experimental!(move_size_limit) + ), + + // Entry point: + gated!(unix_sigpipe, Normal, template!(Word, NameValueStr: "inherit|sig_ign|sig_dfl"), ErrorFollowing, experimental!(unix_sigpipe)), + ungated!(start, Normal, template!(Word), WarnFollowing), + ungated!(no_start, CrateLevel, template!(Word), WarnFollowing), + ungated!(no_main, CrateLevel, template!(Word), WarnFollowing), + + // Modules, prelude, and resolution: + ungated!(path, Normal, template!(NameValueStr: "file"), FutureWarnFollowing), + ungated!(no_std, CrateLevel, template!(Word), WarnFollowing), + ungated!(no_implicit_prelude, Normal, template!(Word), WarnFollowing), + ungated!(non_exhaustive, Normal, template!(Word), WarnFollowing), + + // Runtime + ungated!( + windows_subsystem, CrateLevel, + template!(NameValueStr: "windows|console"), FutureWarnFollowing + ), + ungated!(panic_handler, Normal, template!(Word), WarnFollowing), // RFC 2070 + + // Code generation: + ungated!(inline, Normal, template!(Word, List: "always|never"), FutureWarnFollowing, @only_local: true), + ungated!(cold, Normal, template!(Word), WarnFollowing, @only_local: true), + ungated!(no_builtins, CrateLevel, template!(Word), WarnFollowing), + ungated!( + target_feature, Normal, template!(List: r#"enable = "name""#), + DuplicatesOk, @only_local: true, + ), + ungated!(track_caller, Normal, template!(Word), WarnFollowing), + ungated!(instruction_set, Normal, template!(List: "set"), ErrorPreceding), + gated!( + no_sanitize, Normal, + template!(List: "address, kcfi, memory, thread"), DuplicatesOk, + experimental!(no_sanitize) + ), + gated!(no_coverage, Normal, template!(Word), WarnFollowing, experimental!(no_coverage)), + + ungated!( + doc, Normal, template!(List: "hidden|inline|...", NameValueStr: "string"), DuplicatesOk + ), + + // Debugging + ungated!( + debugger_visualizer, Normal, + template!(List: r#"natvis_file = "...", gdb_script_file = "...""#), DuplicatesOk + ), + + // ========================================================================== + // Unstable attributes: + // ========================================================================== + + // Linking: + gated!( + naked, Normal, template!(Word), WarnFollowing, @only_local: true, + naked_functions, experimental!(naked) + ), + + // Plugins: + // BuiltinAttribute { + // name: sym::plugin, + // only_local: false, + // type_: CrateLevel, + // template: template!(List: "name"), + // duplicates: DuplicatesOk, + // gate: Gated( + // Stability::Deprecated( + // "https://github.com/rust-lang/rust/pull/64675", + // Some("may be removed in a future compiler version"), + // ), + // sym::plugin, + // "compiler plugins are deprecated", + // cfg_fn!(plugin) + // ), + // }, + + // Testing: + gated!( + test_runner, CrateLevel, template!(List: "path"), ErrorFollowing, custom_test_frameworks, + "custom test frameworks are an unstable feature", + ), + // RFC #1268 + gated!( + marker, Normal, template!(Word), WarnFollowing, @only_local: true, + marker_trait_attr, experimental!(marker) + ), + gated!( + thread_local, Normal, template!(Word), WarnFollowing, + "`#[thread_local]` is an experimental feature, and does not currently handle destructors", + ), + gated!(no_core, CrateLevel, template!(Word), WarnFollowing, experimental!(no_core)), + // RFC 2412 + gated!( + optimize, Normal, template!(List: "size|speed"), ErrorPreceding, optimize_attribute, + experimental!(optimize), + ), + + gated!( + ffi_returns_twice, Normal, template!(Word), WarnFollowing, experimental!(ffi_returns_twice) + ), + gated!(ffi_pure, Normal, template!(Word), WarnFollowing, experimental!(ffi_pure)), + gated!(ffi_const, Normal, template!(Word), WarnFollowing, experimental!(ffi_const)), + gated!( + register_tool, CrateLevel, template!(List: "tool1, tool2, ..."), DuplicatesOk, + experimental!(register_tool), + ), + + gated!( + cmse_nonsecure_entry, Normal, template!(Word), WarnFollowing, + experimental!(cmse_nonsecure_entry) + ), + // RFC 2632 + gated!( + const_trait, Normal, template!(Word), WarnFollowing, const_trait_impl, + "`const_trait` is a temporary placeholder for marking a trait that is suitable for `const` \ + `impls` and all default bodies as `const`, which may be removed or renamed in the \ + future." + ), + // lang-team MCP 147 + gated!( + deprecated_safe, Normal, template!(List: r#"since = "version", note = "...""#), ErrorFollowing, + experimental!(deprecated_safe), + ), + + // `#[collapse_debuginfo]` + gated!( + collapse_debuginfo, Normal, template!(Word), WarnFollowing, + experimental!(collapse_debuginfo) + ), + + // RFC 2397 + gated!(do_not_recommend, Normal, template!(Word), WarnFollowing, experimental!(do_not_recommend)), + + // `#[cfi_encoding = ""]` + gated!( + cfi_encoding, Normal, template!(NameValueStr: "encoding"), ErrorPreceding, + experimental!(cfi_encoding) + ), + + // ========================================================================== + // Internal attributes: Stability, deprecation, and unsafe: + // ========================================================================== + + ungated!( + feature, CrateLevel, + template!(List: "name1, name2, ..."), DuplicatesOk, @only_local: true, + ), + // DuplicatesOk since it has its own validation + ungated!( + stable, Normal, + template!(List: r#"feature = "name", since = "version""#), DuplicatesOk, @only_local: true, + ), + ungated!( + unstable, Normal, + template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk, + ), + ungated!(rustc_const_unstable, Normal, template!(List: r#"feature = "name""#), DuplicatesOk), + ungated!( + rustc_const_stable, Normal, + template!(List: r#"feature = "name""#), DuplicatesOk, @only_local: true, + ), + ungated!( + rustc_default_body_unstable, Normal, + template!(List: r#"feature = "name", reason = "...", issue = "N""#), DuplicatesOk + ), + gated!( + allow_internal_unstable, Normal, template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, + "allow_internal_unstable side-steps feature gating and stability checks", + ), + gated!( + rustc_allow_const_fn_unstable, Normal, + template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, + "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" + ), + gated!( + allow_internal_unsafe, Normal, template!(Word), WarnFollowing, + "allow_internal_unsafe side-steps the unsafe_code lint", + ), + ungated!(rustc_safe_intrinsic, Normal, template!(Word), DuplicatesOk), + rustc_attr!(rustc_allowed_through_unstable_modules, Normal, template!(Word), WarnFollowing, + "rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \ + through unstable paths"), + + // ========================================================================== + // Internal attributes: Type system related: + // ========================================================================== + + gated!(fundamental, Normal, template!(Word), WarnFollowing, experimental!(fundamental)), + gated!( + may_dangle, Normal, template!(Word), WarnFollowing, dropck_eyepatch, + "`may_dangle` has unstable semantics and may be removed in the future", + ), + + // ========================================================================== + // Internal attributes: Runtime related: + // ========================================================================== + + rustc_attr!(rustc_allocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_nounwind, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_reallocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_deallocator, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!(rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + gated!( + default_lib_allocator, Normal, template!(Word), WarnFollowing, allocator_internals, + experimental!(default_lib_allocator), + ), + gated!( + needs_allocator, Normal, template!(Word), WarnFollowing, allocator_internals, + experimental!(needs_allocator), + ), + gated!(panic_runtime, Normal, template!(Word), WarnFollowing, experimental!(panic_runtime)), + gated!( + needs_panic_runtime, Normal, template!(Word), WarnFollowing, + experimental!(needs_panic_runtime) + ), + gated!( + compiler_builtins, Normal, template!(Word), WarnFollowing, + "the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \ + which contains compiler-rt intrinsics and will never be stable", + ), + gated!( + profiler_runtime, Normal, template!(Word), WarnFollowing, + "the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate \ + which contains the profiler runtime and will never be stable", + ), + + // ========================================================================== + // Internal attributes, Linkage: + // ========================================================================== + + gated!( + linkage, Normal, template!(NameValueStr: "external|internal|..."), ErrorPreceding, @only_local: true, + "the `linkage` attribute is experimental and not portable across platforms", + ), + rustc_attr!( + rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, @only_local: true, INTERNAL_UNSTABLE + ), + + // ========================================================================== + // Internal attributes, Macro related: + // ========================================================================== + + rustc_attr!( + rustc_builtin_macro, Normal, + template!(Word, List: "name, /*opt*/ attributes(name1, name2, ...)"), ErrorFollowing, + IMPL_DETAIL, + ), + rustc_attr!(rustc_proc_macro_decls, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + rustc_attr!( + rustc_macro_transparency, Normal, + template!(NameValueStr: "transparent|semitransparent|opaque"), ErrorFollowing, + "used internally for testing macro hygiene", + ), + + // ========================================================================== + // Internal attributes, Diagnostics related: + // ========================================================================== + + rustc_attr!( + rustc_on_unimplemented, Normal, + template!( + List: r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#, + NameValueStr: "message" + ), + ErrorFollowing, + INTERNAL_UNSTABLE + ), + // Enumerates "identity-like" conversion methods to suggest on type mismatch. + rustc_attr!( + rustc_conversion_suggestion, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE + ), + // Prevents field reads in the marked trait or method to be considered + // during dead code analysis. + rustc_attr!( + rustc_trivial_field_reads, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE + ), + // Used by the `rustc::potential_query_instability` lint to warn methods which + // might not be stable during incremental compilation. + rustc_attr!(rustc_lint_query_instability, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + // Used by the `rustc::untranslatable_diagnostic` and `rustc::diagnostic_outside_of_impl` lints + // to assist in changes to diagnostic APIs. + rustc_attr!(rustc_lint_diagnostics, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions` + // types (as well as any others in future). + rustc_attr!(rustc_lint_opt_ty, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE), + // Used by the `rustc::bad_opt_access` lint on fields + // types (as well as any others in future). + rustc_attr!(rustc_lint_opt_deny_field_access, Normal, template!(List: "message"), WarnFollowing, INTERNAL_UNSTABLE), + + // ========================================================================== + // Internal attributes, Const related: + // ========================================================================== + + rustc_attr!(rustc_promotable, Normal, template!(Word), WarnFollowing, IMPL_DETAIL), + rustc_attr!( + rustc_legacy_const_generics, Normal, template!(List: "N"), ErrorFollowing, + INTERNAL_UNSTABLE + ), + // Do not const-check this function's body. It will always get replaced during CTFE. + rustc_attr!( + rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, INTERNAL_UNSTABLE + ), + + // ========================================================================== + // Internal attributes, Layout related: + // ========================================================================== + + rustc_attr!( + rustc_layout_scalar_valid_range_start, Normal, template!(List: "value"), ErrorFollowing, + "the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \ + niche optimizations in libcore and libstd and will never be stable", + ), + rustc_attr!( + rustc_layout_scalar_valid_range_end, Normal, template!(List: "value"), ErrorFollowing, + "the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \ + niche optimizations in libcore and libstd and will never be stable", + ), + rustc_attr!( + rustc_nonnull_optimization_guaranteed, Normal, template!(Word), WarnFollowing, + "the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to enable \ + niche optimizations in libcore and libstd and will never be stable", + ), + + // ========================================================================== + // Internal attributes, Misc: + // ========================================================================== + gated!( + lang, Normal, template!(NameValueStr: "name"), DuplicatesOk, @only_local: true, lang_items, + "language items are subject to change", + ), + rustc_attr!( + rustc_pass_by_value, Normal, template!(Word), ErrorFollowing, + "#[rustc_pass_by_value] is used to mark types that must be passed by value instead of reference." + ), + rustc_attr!( + rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, @only_local: true, + "#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`." + ), + rustc_attr!( + rustc_coinductive, AttributeType::Normal, template!(Word), WarnFollowing, @only_local: true, + "#![rustc_coinductive] changes a trait to be coinductive, allowing cycles in the trait solver." + ), + rustc_attr!( + rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: true, + "#[rustc_allow_incoherent_impl] has to be added to all impl items of an incoherent inherent impl." + ), + rustc_attr!( + rustc_deny_explicit_impl, AttributeType::Normal, template!(Word), ErrorFollowing, @only_local: false, + "#[rustc_deny_explicit_impl] enforces that a trait can have no user-provided impls" + ), + rustc_attr!( + rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), ErrorFollowing, + "#[rustc_has_incoherent_inherent_impls] allows the addition of incoherent inherent impls for \ + the given type by annotating all impl items with #[rustc_allow_incoherent_impl]." + ), + rustc_attr!( + rustc_box, AttributeType::Normal, template!(Word), ErrorFollowing, + "#[rustc_box] allows creating boxes \ + and it is only intended to be used in `alloc`." + ), + + BuiltinAttribute { + // name: sym::rustc_diagnostic_item, + name: "rustc_diagnostic_item", + // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`. + // only_local: false, + // type_: Normal, + template: template!(NameValueStr: "name"), + // duplicates: ErrorFollowing, + // gate: Gated( + // Stability::Unstable, + // sym::rustc_attrs, + // "diagnostic items compiler internal support for linting", + // cfg_fn!(rustc_attrs), + // ), + }, + gated!( + // Used in resolve: + prelude_import, Normal, template!(Word), WarnFollowing, + "`#[prelude_import]` is for use by rustc only", + ), + gated!( + rustc_paren_sugar, Normal, template!(Word), WarnFollowing, unboxed_closures, + "unboxed_closures are still evolving", + ), + rustc_attr!( + rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing, @only_local: true, + "the `#[rustc_inherit_overflow_checks]` attribute is just used to control \ + overflow checking behavior of several libcore functions that are inlined \ + across crates and will never be stable", + ), + rustc_attr!( + rustc_reservation_impl, Normal, + template!(NameValueStr: "reservation message"), ErrorFollowing, + "the `#[rustc_reservation_impl]` attribute is internally used \ + for reserving for `for From for T` impl" + ), + rustc_attr!( + rustc_test_marker, Normal, template!(NameValueStr: "name"), WarnFollowing, + "the `#[rustc_test_marker]` attribute is used internally to track tests", + ), + rustc_attr!( + rustc_unsafe_specialization_marker, Normal, template!(Word), WarnFollowing, + "the `#[rustc_unsafe_specialization_marker]` attribute is used to check specializations" + ), + rustc_attr!( + rustc_specialization_trait, Normal, template!(Word), WarnFollowing, + "the `#[rustc_specialization_trait]` attribute is used to check specializations" + ), + rustc_attr!( + rustc_main, Normal, template!(Word), WarnFollowing, + "the `#[rustc_main]` attribute is used internally to specify test entry point function", + ), + rustc_attr!( + rustc_skip_array_during_method_dispatch, Normal, template!(Word), WarnFollowing, + "the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \ + from method dispatch when the receiver is an array, for compatibility in editions < 2021." + ), + rustc_attr!( + rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing, + "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ + definition of a trait, it's currently in experimental form and should be changed before \ + being exposed outside of the std" + ), + rustc_attr!( + rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing, + r#"`rustc_doc_primitive` is a rustc internal attribute"#, + ), + + // ========================================================================== + // Internal attributes, Testing: + // ========================================================================== + + rustc_attr!(TEST, rustc_effective_visibility, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_outlives, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_capture_analysis, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_insignificant_dtor, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_strict_coherence, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_variance, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_layout, Normal, template!(List: "field1, field2, ..."), WarnFollowing), + rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing), + rustc_attr!( + TEST, rustc_error, Normal, + template!(Word, List: "delay_span_bug_from_inside_query"), WarnFollowingWordOnly + ), + rustc_attr!(TEST, rustc_dump_user_substs, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing), + rustc_attr!( + TEST, rustc_if_this_changed, Normal, template!(Word, List: "DepNode"), DuplicatesOk + ), + rustc_attr!( + TEST, rustc_then_this_would_need, Normal, template!(List: "DepNode"), DuplicatesOk + ), + rustc_attr!( + TEST, rustc_clean, Normal, + template!(List: r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#), + DuplicatesOk, + ), + rustc_attr!( + TEST, rustc_partition_reused, Normal, + template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk, + ), + rustc_attr!( + TEST, rustc_partition_codegened, Normal, + template!(List: r#"cfg = "...", module = "...""#), DuplicatesOk, + ), + rustc_attr!( + TEST, rustc_expected_cgu_reuse, Normal, + template!(List: r#"cfg = "...", module = "...", kind = "...""#), DuplicatesOk, + ), + rustc_attr!(TEST, rustc_symbol_name, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_polymorphize_error, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_def_path, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_mir, Normal, template!(List: "arg1, arg2, ..."), DuplicatesOk), + gated!( + custom_mir, Normal, template!(List: r#"dialect = "...", phase = "...""#), + ErrorFollowing, "the `#[custom_mir]` attribute is just used for the Rust test suite", + ), + rustc_attr!(TEST, rustc_dump_program_clauses, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dump_env_program_clauses, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_object_lifetime_default, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dump_vtable, Normal, template!(Word), WarnFollowing), + rustc_attr!(TEST, rustc_dummy, Normal, template!(Word /* doesn't matter*/), DuplicatesOk), + gated!( + omit_gdb_pretty_printer_section, Normal, template!(Word), WarnFollowing, + "the `#[omit_gdb_pretty_printer_section]` attribute is just used for the Rust test suite", + ), +]; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs new file mode 100644 index 0000000000000..e4c8d446af7bd --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr/tests.rs @@ -0,0 +1,40 @@ +//! This module contains tests for doc-expression parsing. +//! Currently, it tests `#[doc(hidden)]` and `#[doc(alias)]`. + +use mbe::syntax_node_to_token_tree; +use syntax::{ast, AstNode}; + +use crate::attr::{DocAtom, DocExpr}; + +fn assert_parse_result(input: &str, expected: DocExpr) { + let (tt, _) = { + let source_file = ast::SourceFile::parse(input).ok().unwrap(); + let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + syntax_node_to_token_tree(tt.syntax()) + }; + let cfg = DocExpr::parse(&tt); + assert_eq!(cfg, expected); +} + +#[test] +fn test_doc_expr_parser() { + assert_parse_result("#![doc(hidden)]", DocAtom::Flag("hidden".into()).into()); + + assert_parse_result( + r#"#![doc(alias = "foo")]"#, + DocAtom::KeyValue { key: "alias".into(), value: "foo".into() }.into(), + ); + + assert_parse_result(r#"#![doc(alias("foo"))]"#, DocExpr::Alias(["foo".into()].into())); + assert_parse_result( + r#"#![doc(alias("foo", "bar", "baz"))]"#, + DocExpr::Alias(["foo".into(), "bar".into(), "baz".into()].into()), + ); + + assert_parse_result( + r#" + #[doc(alias("Bar", "Qux"))] + struct Foo;"#, + DocExpr::Alias(["Bar".into(), "Qux".into()].into()), + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs index b70e658efd79c..f8d492d0e5252 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -6,267 +6,30 @@ mod tests; pub mod scope; mod pretty; -use std::{ops::Index, sync::Arc}; +use std::ops::Index; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; -use drop_bomb::DropBomb; use either::Either; -use hir_expand::{ - attrs::RawAttrs, hygiene::Hygiene, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId, -}; +use hir_expand::{name::Name, HirFileId, InFile}; use la_arena::{Arena, ArenaMap}; -use limit::Limit; use profile::Count; use rustc_hash::FxHashMap; -use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr}; +use syntax::{ast, AstPtr, SyntaxNodePtr}; +use triomphe::Arc; use crate::{ - attr::Attrs, db::DefDatabase, - expr::{ + expander::Expander, + hir::{ dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId, RecordFieldPat, }, - item_scope::BuiltinShadowMode, - macro_id_to_def_id, nameres::DefMap, path::{ModPath, Path}, src::{HasChildSource, HasSource}, - AsMacroCall, BlockId, DefWithBodyId, HasModule, LocalModuleId, Lookup, MacroId, ModuleId, - UnresolvedMacro, + BlockId, DefWithBodyId, HasModule, Lookup, }; -pub use lower::LowerCtx; - -/// A subset of Expander that only deals with cfg attributes. We only need it to -/// avoid cyclic queries in crate def map during enum processing. -#[derive(Debug)] -pub(crate) struct CfgExpander { - cfg_options: CfgOptions, - hygiene: Hygiene, - krate: CrateId, -} - -#[derive(Debug)] -pub struct Expander { - cfg_expander: CfgExpander, - def_map: Arc, - current_file_id: HirFileId, - module: LocalModuleId, - /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. - recursion_depth: usize, -} - -impl CfgExpander { - pub(crate) fn new( - db: &dyn DefDatabase, - current_file_id: HirFileId, - krate: CrateId, - ) -> CfgExpander { - let hygiene = Hygiene::new(db.upcast(), current_file_id); - let cfg_options = db.crate_graph()[krate].cfg_options.clone(); - CfgExpander { cfg_options, hygiene, krate } - } - - pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { - Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene)) - } - - pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool { - let attrs = self.parse_attrs(db, owner); - attrs.is_cfg_enabled(&self.cfg_options) - } -} - -impl Expander { - pub fn new(db: &dyn DefDatabase, current_file_id: HirFileId, module: ModuleId) -> Expander { - let cfg_expander = CfgExpander::new(db, current_file_id, module.krate); - let def_map = module.def_map(db); - Expander { - cfg_expander, - def_map, - current_file_id, - module: module.local_id, - recursion_depth: 0, - } - } - - pub fn enter_expand( - &mut self, - db: &dyn DefDatabase, - macro_call: ast::MacroCall, - ) -> Result>, UnresolvedMacro> { - let mut unresolved_macro_err = None; - - let result = self.within_limit(db, |this| { - let macro_call = InFile::new(this.current_file_id, ¯o_call); - - let resolver = - |path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); - - let mut err = None; - let call_id = match macro_call.as_call_id_with_errors( - db, - this.def_map.krate(), - resolver, - &mut |e| { - err.get_or_insert(e); - }, - ) { - Ok(call_id) => call_id, - Err(resolve_err) => { - unresolved_macro_err = Some(resolve_err); - return ExpandResult { value: None, err: None }; - } - }; - ExpandResult { value: call_id.ok(), err } - }); - - if let Some(err) = unresolved_macro_err { - Err(err) - } else { - Ok(result) - } - } - - pub fn enter_expand_id( - &mut self, - db: &dyn DefDatabase, - call_id: MacroCallId, - ) -> ExpandResult> { - self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) - } - - fn enter_expand_inner( - db: &dyn DefDatabase, - call_id: MacroCallId, - mut err: Option, - ) -> ExpandResult> { - if err.is_none() { - err = db.macro_expand_error(call_id); - } - - let file_id = call_id.as_file(); - - let raw_node = match db.parse_or_expand(file_id) { - Some(it) => it, - None => { - // Only `None` if the macro expansion produced no usable AST. - if err.is_none() { - tracing::warn!("no error despite `parse_or_expand` failing"); - } - - return ExpandResult::only_err(err.unwrap_or_else(|| { - ExpandError::Other("failed to parse macro invocation".into()) - })); - } - }; - - ExpandResult { value: Some((file_id, raw_node)), err } - } - - pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { - self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); - self.current_file_id = mark.file_id; - if self.recursion_depth == usize::MAX { - // Recursion limit has been reached somewhere in the macro expansion tree. Reset the - // depth only when we get out of the tree. - if !self.current_file_id.is_macro() { - self.recursion_depth = 0; - } - } else { - self.recursion_depth -= 1; - } - mark.bomb.defuse(); - } - - pub(crate) fn to_source(&self, value: T) -> InFile { - InFile { file_id: self.current_file_id, value } - } - - pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { - self.cfg_expander.parse_attrs(db, owner) - } - - pub(crate) fn cfg_options(&self) -> &CfgOptions { - &self.cfg_expander.cfg_options - } - - pub fn current_file_id(&self) -> HirFileId { - self.current_file_id - } - - fn parse_path(&mut self, db: &dyn DefDatabase, path: ast::Path) -> Option { - let ctx = LowerCtx::with_hygiene(db, &self.cfg_expander.hygiene); - Path::from_src(path, &ctx) - } - - fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option { - self.def_map.resolve_path(db, self.module, path, BuiltinShadowMode::Other).0.take_macros() - } - - fn recursion_limit(&self, db: &dyn DefDatabase) -> Limit { - let limit = db.crate_limits(self.cfg_expander.krate).recursion_limit as _; - - #[cfg(not(test))] - return Limit::new(limit); - - // Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug - #[cfg(test)] - return Limit::new(std::cmp::min(32, limit)); - } - - fn within_limit( - &mut self, - db: &dyn DefDatabase, - op: F, - ) -> ExpandResult> - where - F: FnOnce(&mut Self) -> ExpandResult>, - { - if self.recursion_depth == usize::MAX { - // Recursion limit has been reached somewhere in the macro expansion tree. We should - // stop expanding other macro calls in this tree, or else this may result in - // exponential number of macro expansions, leading to a hang. - // - // The overflow error should have been reported when it occurred (see the next branch), - // so don't return overflow error here to avoid diagnostics duplication. - cov_mark::hit!(overflow_but_not_me); - return ExpandResult::only_err(ExpandError::RecursionOverflowPosioned); - } else if self.recursion_limit(db).check(self.recursion_depth + 1).is_err() { - self.recursion_depth = usize::MAX; - cov_mark::hit!(your_stack_belongs_to_me); - return ExpandResult::only_err(ExpandError::Other( - "reached recursion limit during macro expansion".into(), - )); - } - - let ExpandResult { value, err } = op(self); - let Some(call_id) = value else { - return ExpandResult { value: None, err }; - }; - - Self::enter_expand_inner(db, call_id, err).map(|value| { - value.and_then(|(new_file_id, node)| { - let node = T::cast(node)?; - - self.recursion_depth += 1; - self.cfg_expander.hygiene = Hygiene::new(db.upcast(), new_file_id); - let old_file_id = std::mem::replace(&mut self.current_file_id, new_file_id); - let mark = - Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") }; - Some((mark, node)) - }) - }) - } -} - -#[derive(Debug)] -pub struct Mark { - file_id: HirFileId, - bomb: DropBomb, -} - /// The body of an item (function, const etc.). #[derive(Debug, Eq, PartialEq)] pub struct Body { @@ -274,6 +37,9 @@ pub struct Body { pub pats: Arena, pub bindings: Arena, pub labels: Arena

  • { match self { AssertLint::ArithmeticOverflow(_, p) | AssertLint::UnconditionalPanic(_, p) => p, } diff --git a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs index 58cc161ddcc49..d202860840c25 100644 --- a/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs +++ b/compiler/rustc_mir_transform/src/ffi_unwind_calls.rs @@ -30,6 +30,8 @@ fn abi_can_unwind(abi: Abi) -> bool { | EfiApi | AvrInterrupt | AvrNonBlockingInterrupt + | RiscvInterruptM + | RiscvInterruptS | CCmseNonSecureCall | Wasm | RustIntrinsic diff --git a/compiler/rustc_mir_transform/src/function_item_references.rs b/compiler/rustc_mir_transform/src/function_item_references.rs index 5989dbebf2db9..a42eacbf22b42 100644 --- a/compiler/rustc_mir_transform/src/function_item_references.rs +++ b/compiler/rustc_mir_transform/src/function_item_references.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use rustc_hir::def_id::DefId; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; -use rustc_middle::ty::{self, EarlyBinder, PredicateKind, SubstsRef, Ty, TyCtxt}; +use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TyCtxt}; use rustc_session::lint::builtin::FUNCTION_ITEM_REFERENCES; use rustc_span::{symbol::sym, Span}; use rustc_target::spec::abi::Abi; @@ -34,26 +34,25 @@ impl<'tcx> Visitor<'tcx> for FunctionItemRefChecker<'_, 'tcx> { destination: _, target: _, unwind: _, - from_hir_call: _, + call_source: _, fn_span: _, } = &terminator.kind { let source_info = *self.body.source_info(location); let func_ty = func.ty(self.body, self.tcx); - if let ty::FnDef(def_id, substs_ref) = *func_ty.kind() { + if let ty::FnDef(def_id, args_ref) = *func_ty.kind() { // Handle calls to `transmute` if self.tcx.is_diagnostic_item(sym::transmute, def_id) { let arg_ty = args[0].ty(self.body, self.tcx); for inner_ty in arg_ty.walk().filter_map(|arg| arg.as_type()) { - if let Some((fn_id, fn_substs)) = - FunctionItemRefChecker::is_fn_ref(inner_ty) + if let Some((fn_id, fn_args)) = FunctionItemRefChecker::is_fn_ref(inner_ty) { let span = self.nth_arg_span(&args, 0); - self.emit_lint(fn_id, fn_substs, source_info, span); + self.emit_lint(fn_id, fn_args, source_info, span); } } } else { - self.check_bound_args(def_id, substs_ref, &args, source_info); + self.check_bound_args(def_id, args_ref, &args, source_info); } } } @@ -63,28 +62,30 @@ impl<'tcx> Visitor<'tcx> for FunctionItemRefChecker<'_, 'tcx> { impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { /// Emits a lint for function reference arguments bound by `fmt::Pointer` in calls to the - /// function defined by `def_id` with the substitutions `substs_ref`. + /// function defined by `def_id` with the substitutions `args_ref`. fn check_bound_args( &self, def_id: DefId, - substs_ref: SubstsRef<'tcx>, + args_ref: GenericArgsRef<'tcx>, args: &[Operand<'tcx>], source_info: SourceInfo, ) { let param_env = self.tcx.param_env(def_id); let bounds = param_env.caller_bounds(); for bound in bounds { - if let Some(bound_ty) = self.is_pointer_trait(&bound.kind().skip_binder()) { + if let Some(bound_ty) = self.is_pointer_trait(bound) { // Get the argument types as they appear in the function signature. - let arg_defs = self.tcx.fn_sig(def_id).subst_identity().skip_binder().inputs(); + let arg_defs = + self.tcx.fn_sig(def_id).instantiate_identity().skip_binder().inputs(); for (arg_num, arg_def) in arg_defs.iter().enumerate() { // For all types reachable from the argument type in the fn sig for inner_ty in arg_def.walk().filter_map(|arg| arg.as_type()) { // If the inner type matches the type bound by `Pointer` if inner_ty == bound_ty { // Do a substitution using the parameters from the callsite - let subst_ty = EarlyBinder(inner_ty).subst(self.tcx, substs_ref); - if let Some((fn_id, fn_substs)) = + let subst_ty = + EarlyBinder::bind(inner_ty).instantiate(self.tcx, args_ref); + if let Some((fn_id, fn_args)) = FunctionItemRefChecker::is_fn_ref(subst_ty) { let mut span = self.nth_arg_span(args, arg_num); @@ -94,7 +95,7 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { let callsite_ctxt = span.source_callsite().ctxt(); span = span.with_ctxt(callsite_ctxt); } - self.emit_lint(fn_id, fn_substs, source_info, span); + self.emit_lint(fn_id, fn_args, source_info, span); } } } @@ -104,8 +105,8 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { } /// If the given predicate is the trait `fmt::Pointer`, returns the bound parameter type. - fn is_pointer_trait(&self, bound: &PredicateKind<'tcx>) -> Option> { - if let ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) = bound { + fn is_pointer_trait(&self, bound: ty::Clause<'tcx>) -> Option> { + if let ty::ClauseKind::Trait(predicate) = bound.kind().skip_binder() { self.tcx .is_diagnostic_item(sym::Pointer, predicate.def_id()) .then(|| predicate.trait_ref.self_ty()) @@ -115,8 +116,8 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { } /// If a type is a reference or raw pointer to the anonymous type of a function definition, - /// returns that function's `DefId` and `SubstsRef`. - fn is_fn_ref(ty: Ty<'tcx>) -> Option<(DefId, SubstsRef<'tcx>)> { + /// returns that function's `DefId` and `GenericArgsRef`. + fn is_fn_ref(ty: Ty<'tcx>) -> Option<(DefId, GenericArgsRef<'tcx>)> { let referent_ty = match ty.kind() { ty::Ref(_, referent_ty, _) => Some(referent_ty), ty::RawPtr(ty_and_mut) => Some(&ty_and_mut.ty), @@ -124,8 +125,8 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { }; referent_ty .map(|ref_ty| { - if let ty::FnDef(def_id, substs_ref) = *ref_ty.kind() { - Some((def_id, substs_ref)) + if let ty::FnDef(def_id, args_ref) = *ref_ty.kind() { + Some((def_id, args_ref)) } else { None } @@ -145,7 +146,7 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { fn emit_lint( &self, fn_id: DefId, - fn_substs: SubstsRef<'tcx>, + fn_args: GenericArgsRef<'tcx>, source_info: SourceInfo, span: Span, ) { @@ -155,7 +156,7 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { .assert_crate_local() .lint_root; // FIXME: use existing printing routines to print the function signature - let fn_sig = self.tcx.fn_sig(fn_id).subst(self.tcx, fn_substs); + let fn_sig = self.tcx.fn_sig(fn_id).instantiate(self.tcx, fn_args); let unsafety = fn_sig.unsafety().prefix_str(); let abi = match fn_sig.abi() { Abi::Rust => String::from(""), @@ -167,15 +168,15 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> { } }; let ident = self.tcx.item_name(fn_id).to_ident_string(); - let ty_params = fn_substs.types().map(|ty| format!("{}", ty)); - let const_params = fn_substs.consts().map(|c| format!("{}", c)); + let ty_params = fn_args.types().map(|ty| format!("{ty}")); + let const_params = fn_args.consts().map(|c| format!("{c}")); let params = ty_params.chain(const_params).join(", "); let num_args = fn_sig.inputs().map_bound(|inputs| inputs.len()).skip_binder(); let variadic = if fn_sig.c_variadic() { ", ..." } else { "" }; let ret = if fn_sig.output().skip_binder().is_unit() { "" } else { " -> _" }; let sugg = format!( "{} as {}{}fn({}{}){}", - if params.is_empty() { ident.clone() } else { format!("{}::<{}>", ident, params) }, + if params.is_empty() { ident.clone() } else { format!("{ident}::<{params}>") }, unsafety, abi, vec!["_"; num_args].join(", "), diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index 891e446942e01..ff4822f333f08 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -50,8 +50,10 @@ //! For generators with state 1 (returned) and state 2 (poisoned) it does nothing. //! Otherwise it drops all the values in scope at the last suspension point. +use crate::abort_unwinding_calls; use crate::deref_separator::deref_finder; use crate::errors; +use crate::pass_manager as pm; use crate::simplify; use crate::MirPass; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -64,8 +66,9 @@ use rustc_index::{Idx, IndexVec}; use rustc_middle::mir::dump_mir; use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; use rustc_middle::mir::*; +use rustc_middle::ty::InstanceDef; use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; -use rustc_middle::ty::{GeneratorSubsts, SubstsRef}; +use rustc_middle::ty::{GeneratorArgs, GenericArgsRef}; use rustc_mir_dataflow::impls::{ MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive, }; @@ -194,11 +197,11 @@ fn replace_base<'tcx>(place: &mut Place<'tcx>, new_base: Place<'tcx>, tcx: TyCtx const SELF_ARG: Local = Local::from_u32(1); /// Generator has not been resumed yet. -const UNRESUMED: usize = GeneratorSubsts::UNRESUMED; +const UNRESUMED: usize = GeneratorArgs::UNRESUMED; /// Generator has returned / is completed. -const RETURNED: usize = GeneratorSubsts::RETURNED; +const RETURNED: usize = GeneratorArgs::RETURNED; /// Generator has panicked and is poisoned. -const POISONED: usize = GeneratorSubsts::POISONED; +const POISONED: usize = GeneratorArgs::POISONED; /// Number of variants to reserve in generator state. Corresponds to /// `UNRESUMED` (beginning of a generator) and `RETURNED`/`POISONED` @@ -223,14 +226,14 @@ struct TransformVisitor<'tcx> { tcx: TyCtxt<'tcx>, is_async_kind: bool, state_adt_ref: AdtDef<'tcx>, - state_substs: SubstsRef<'tcx>, + state_args: GenericArgsRef<'tcx>, // The type of the discriminant in the generator struct discr_ty: Ty<'tcx>, // Mapping from Local to (type of local, generator struct index) // FIXME(eddyb) This should use `IndexVec>`. - remap: FxHashMap, VariantIdx, usize)>, + remap: FxHashMap, VariantIdx, FieldIdx)>, // A map from a suspension point in a block to the locals which have live storage at that point storage_liveness: IndexVec>>, @@ -265,7 +268,7 @@ impl<'tcx> TransformVisitor<'tcx> { (false, true) => 1, // Poll::Pending }); - let kind = AggregateKind::Adt(self.state_adt_ref.did(), idx, self.state_substs, None, None); + let kind = AggregateKind::Adt(self.state_adt_ref.did(), idx, self.state_args, None, None); // `Poll::Pending` if self.is_async_kind && idx == VariantIdx::new(1) { @@ -295,11 +298,11 @@ impl<'tcx> TransformVisitor<'tcx> { } // Create a Place referencing a generator struct field - fn make_field(&self, variant_index: VariantIdx, idx: usize, ty: Ty<'tcx>) -> Place<'tcx> { + fn make_field(&self, variant_index: VariantIdx, idx: FieldIdx, ty: Ty<'tcx>) -> Place<'tcx> { let self_place = Place::from(SELF_ARG); let base = self.tcx.mk_place_downcast_unnamed(self_place, variant_index); let mut projection = base.projection.to_vec(); - projection.push(ProjectionElem::Field(FieldIdx::new(idx), ty)); + projection.push(ProjectionElem::Field(idx, ty)); Place { local: base.local, projection: self.tcx.mk_place_elems(&projection) } } @@ -413,8 +416,11 @@ impl<'tcx> MutVisitor<'tcx> for TransformVisitor<'tcx> { fn make_generator_state_argument_indirect<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let gen_ty = body.local_decls.raw[1].ty; - let ref_gen_ty = - tcx.mk_ref(tcx.lifetimes.re_erased, ty::TypeAndMut { ty: gen_ty, mutbl: Mutability::Mut }); + let ref_gen_ty = Ty::new_ref( + tcx, + tcx.lifetimes.re_erased, + ty::TypeAndMut { ty: gen_ty, mutbl: Mutability::Mut }, + ); // Replace the by value generator argument body.local_decls.raw[1].ty = ref_gen_ty; @@ -428,8 +434,8 @@ fn make_generator_state_argument_pinned<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body let pin_did = tcx.require_lang_item(LangItem::Pin, Some(body.span)); let pin_adt_ref = tcx.adt_def(pin_did); - let substs = tcx.mk_substs(&[ref_gen_ty.into()]); - let pin_ref_gen_ty = tcx.mk_adt(pin_adt_ref, substs); + let args = tcx.mk_args(&[ref_gen_ty.into()]); + let pin_ref_gen_ty = Ty::new_adt(tcx, pin_adt_ref, args); // Replace the by ref generator argument body.local_decls.raw[1].ty = pin_ref_gen_ty; @@ -481,7 +487,7 @@ fn replace_local<'tcx>( /// still using the `ResumeTy` indirection for the time being, and that indirection /// is removed here. After this transform, the generator body only knows about `&mut Context<'_>`. fn transform_async_context<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { - let context_mut_ref = tcx.mk_task_context(); + let context_mut_ref = Ty::new_task_context(tcx); // replace the type of the `resume` argument replace_resume_ty_local(tcx, body, Local::new(2), context_mut_ref); @@ -597,16 +603,15 @@ fn locals_live_across_suspend_points<'tcx>( let borrowed_locals_results = MaybeBorrowedLocals.into_engine(tcx, body_ref).pass_name("generator").iterate_to_fixpoint(); - let mut borrowed_locals_cursor = - rustc_mir_dataflow::ResultsCursor::new(body_ref, &borrowed_locals_results); + let mut borrowed_locals_cursor = borrowed_locals_results.cloned_results_cursor(body_ref); // Calculate the MIR locals that we actually need to keep storage around // for. - let requires_storage_results = MaybeRequiresStorage::new(body, &borrowed_locals_results) - .into_engine(tcx, body_ref) - .iterate_to_fixpoint(); - let mut requires_storage_cursor = - rustc_mir_dataflow::ResultsCursor::new(body_ref, &requires_storage_results); + let mut requires_storage_results = + MaybeRequiresStorage::new(borrowed_locals_results.cloned_results_cursor(body)) + .into_engine(tcx, body_ref) + .iterate_to_fixpoint(); + let mut requires_storage_cursor = requires_storage_results.as_results_cursor(body_ref); // Calculate the liveness of MIR locals ignoring borrows. let mut liveness = MaybeLiveLocals @@ -747,7 +752,7 @@ fn compute_storage_conflicts<'mir, 'tcx>( body: &'mir Body<'tcx>, saved_locals: &GeneratorSavedLocals, always_live_locals: BitSet, - requires_storage: rustc_mir_dataflow::Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>, + mut requires_storage: rustc_mir_dataflow::Results<'tcx, MaybeRequiresStorage<'_, 'mir, 'tcx>>, ) -> BitMatrix { assert_eq!(body.local_decls.len(), saved_locals.domain_size()); @@ -802,13 +807,14 @@ struct StorageConflictVisitor<'mir, 'tcx, 's> { local_conflicts: BitMatrix, } -impl<'mir, 'tcx> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx> +impl<'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R> for StorageConflictVisitor<'mir, 'tcx, '_> { type FlowState = BitSet; fn visit_statement_before_primary_effect( &mut self, + _results: &R, state: &Self::FlowState, _statement: &'mir Statement<'tcx>, loc: Location, @@ -818,6 +824,7 @@ impl<'mir, 'tcx> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx> fn visit_terminator_before_primary_effect( &mut self, + _results: &R, state: &Self::FlowState, _terminator: &'mir Terminator<'tcx>, loc: Location, @@ -852,7 +859,7 @@ fn sanitize_witness<'tcx>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, witness: Ty<'tcx>, - upvars: Vec>, + upvars: &'tcx ty::List>, layout: &GeneratorLayout<'tcx>, ) { let did = body.source.def_id(); @@ -903,7 +910,7 @@ fn compute_layout<'tcx>( liveness: LivenessInfo, body: &Body<'tcx>, ) -> ( - FxHashMap, VariantIdx, usize)>, + FxHashMap, VariantIdx, FieldIdx)>, GeneratorLayout<'tcx>, IndexVec>>, ) { @@ -981,6 +988,7 @@ fn compute_layout<'tcx>( // just use the first one here. That's fine; fields do not move // around inside generators, so it doesn't matter which variant // index we access them by. + let idx = FieldIdx::from_usize(idx); remap.entry(locals[saved_local]).or_insert((tys[saved_local].ty, variant_index, idx)); } variant_fields.push(fields); @@ -989,8 +997,23 @@ fn compute_layout<'tcx>( debug!("generator variant_fields = {:?}", variant_fields); debug!("generator storage_conflicts = {:#?}", storage_conflicts); - let layout = - GeneratorLayout { field_tys: tys, variant_fields, variant_source_info, storage_conflicts }; + let mut field_names = IndexVec::from_elem(None, &tys); + for var in &body.var_debug_info { + let VarDebugInfoContents::Place(place) = &var.value else { continue }; + let Some(local) = place.as_local() else { continue }; + let Some(&(_, variant, field)) = remap.get(&local) else { continue }; + + let saved_local = variant_fields[variant][field]; + field_names.get_or_insert_with(saved_local, || var.name); + } + + let layout = GeneratorLayout { + field_tys: tys, + field_names, + variant_fields, + variant_source_info, + storage_conflicts, + }; debug!(?layout); (remap, layout, storage_liveness) @@ -1045,7 +1068,10 @@ fn elaborate_generator_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { for (block, block_data) in body.basic_blocks.iter_enumerated() { let (target, unwind, source_info) = match block_data.terminator() { - Terminator { source_info, kind: TerminatorKind::Drop { place, target, unwind } } => { + Terminator { + source_info, + kind: TerminatorKind::Drop { place, target, unwind, replace: _ }, + } => { if let Some(local) = place.as_local() { if local == SELF_ARG { (target, unwind, source_info) @@ -1110,13 +1136,13 @@ fn create_generator_drop_shim<'tcx>( } // Replace the return variable - body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(tcx.mk_unit(), source_info); + body.local_decls[RETURN_PLACE] = LocalDecl::with_source_info(Ty::new_unit(tcx), source_info); make_generator_state_argument_indirect(tcx, &mut body); // Change the generator argument from &mut to *mut body.local_decls[SELF_ARG] = LocalDecl::with_source_info( - tcx.mk_ptr(ty::TypeAndMut { ty: gen_ty, mutbl: hir::Mutability::Mut }), + Ty::new_ptr(tcx, ty::TypeAndMut { ty: gen_ty, mutbl: hir::Mutability::Mut }), source_info, ); @@ -1124,7 +1150,25 @@ fn create_generator_drop_shim<'tcx>( // unrelated code from the resume part of the function simplify::remove_dead_blocks(tcx, &mut body); + // Update the body's def to become the drop glue. + // This needs to be updated before the AbortUnwindingCalls pass. + let gen_instance = body.source.instance; + let drop_in_place = tcx.require_lang_item(LangItem::DropInPlace, None); + let drop_instance = InstanceDef::DropGlue(drop_in_place, Some(gen_ty)); + body.source.instance = drop_instance; + + pm::run_passes_no_validate( + tcx, + &mut body, + &[&abort_unwinding_calls::AbortUnwindingCalls], + None, + ); + + // Temporary change MirSource to generator's instance so that dump_mir produces more sensible + // filename. + body.source.instance = gen_instance; dump_mir(tcx, false, "generator_drop", &0, &body, |_, _| Ok(())); + body.source.instance = drop_instance; body } @@ -1294,6 +1338,8 @@ fn create_generator_resume_function<'tcx>( // unrelated code from the drop part of the function simplify::remove_dead_blocks(tcx, body); + pm::run_passes_no_validate(tcx, body, &[&abort_unwinding_calls::AbortUnwindingCalls], None); + dump_mir(tcx, false, "generator_resume", &0, body, |_, _| Ok(())); } @@ -1304,6 +1350,7 @@ fn insert_clean_drop(body: &mut Body<'_>) -> BasicBlock { place: Place::from(SELF_ARG), target: return_block, unwind: UnwindAction::Continue, + replace: false, }; let source_info = SourceInfo::outermost(body.span); @@ -1407,7 +1454,7 @@ pub(crate) fn mir_generator_witnesses<'tcx>( // The first argument is the generator type passed by value let gen_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty; - // Get the interior types and substs which typeck computed + // Get the interior types and args which typeck computed let movable = match *gen_ty.kind() { ty::Generator(_, _, movability) => movability == hir::Movability::Movable, ty::Error(_) => return None, @@ -1441,38 +1488,38 @@ impl<'tcx> MirPass<'tcx> for StateTransform { // The first argument is the generator type passed by value let gen_ty = body.local_decls.raw[1].ty; - // Get the discriminant type and substs which typeck computed + // Get the discriminant type and args which typeck computed let (discr_ty, upvars, interior, movable) = match *gen_ty.kind() { - ty::Generator(_, substs, movability) => { - let substs = substs.as_generator(); + ty::Generator(_, args, movability) => { + let args = args.as_generator(); ( - substs.discr_ty(tcx), - substs.upvar_tys().collect::>(), - substs.witness(), + args.discr_ty(tcx), + args.upvar_tys(), + args.witness(), movability == hir::Movability::Movable, ) } _ => { - tcx.sess.delay_span_bug(body.span, format!("unexpected generator type {}", gen_ty)); + tcx.sess.delay_span_bug(body.span, format!("unexpected generator type {gen_ty}")); return; } }; let is_async_kind = matches!(body.generator_kind(), Some(GeneratorKind::Async(_))); - let (state_adt_ref, state_substs) = if is_async_kind { + let (state_adt_ref, state_args) = if is_async_kind { // Compute Poll let poll_did = tcx.require_lang_item(LangItem::Poll, None); let poll_adt_ref = tcx.adt_def(poll_did); - let poll_substs = tcx.mk_substs(&[body.return_ty().into()]); - (poll_adt_ref, poll_substs) + let poll_args = tcx.mk_args(&[body.return_ty().into()]); + (poll_adt_ref, poll_args) } else { // Compute GeneratorState let state_did = tcx.require_lang_item(LangItem::GeneratorState, None); let state_adt_ref = tcx.adt_def(state_did); - let state_substs = tcx.mk_substs(&[yield_ty.into(), body.return_ty().into()]); - (state_adt_ref, state_substs) + let state_args = tcx.mk_args(&[yield_ty.into(), body.return_ty().into()]); + (state_adt_ref, state_args) }; - let ret_ty = tcx.mk_adt(state_adt_ref, state_substs); + let ret_ty = Ty::new_adt(tcx, state_adt_ref, state_args); // We rename RETURN_PLACE which has type mir.return_ty to new_ret_local // RETURN_PLACE then is a fresh unused local with type ret_ty. @@ -1488,8 +1535,11 @@ impl<'tcx> MirPass<'tcx> for StateTransform { // case there is no `Assign` to it that the transform can turn into a store to the generator // state. After the yield the slot in the generator state would then be uninitialized. let resume_local = Local::new(2); - let resume_ty = - if is_async_kind { tcx.mk_task_context() } else { body.local_decls[resume_local].ty }; + let resume_ty = if is_async_kind { + Ty::new_task_context(tcx) + } else { + body.local_decls[resume_local].ty + }; let new_resume_local = replace_local(resume_local, resume_ty, body, tcx); // When first entering the generator, move the resume argument into its new local. @@ -1543,7 +1593,7 @@ impl<'tcx> MirPass<'tcx> for StateTransform { tcx, is_async_kind, state_adt_ref, - state_substs, + state_args, remap, storage_liveness, always_live_locals, @@ -1687,7 +1737,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> { destination, target: Some(_), unwind: _, - from_hir_call: _, + call_source: _, fn_span: _, } => { self.check_assigned_place(*destination, |this| { @@ -1736,7 +1786,9 @@ fn check_suspend_tys<'tcx>(tcx: TyCtxt<'tcx>, layout: &GeneratorLayout<'tcx>, bo debug!(?decl); if !decl.ignore_for_traits && linted_tys.insert(decl.ty) { - let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { continue }; + let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { + continue; + }; check_must_not_suspend_ty( tcx, @@ -1803,7 +1855,7 @@ fn check_must_not_suspend_ty<'tcx>( let mut has_emitted = false; for &(predicate, _) in tcx.explicit_item_bounds(def).skip_binder() { // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::PredicateKind::Clause(ty::Clause::Trait(ref poly_trait_predicate)) = + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = predicate.kind().skip_binder() { let def_id = poly_trait_predicate.trait_ref.def_id; diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 6c2e22a70b943..fc9e18378d5a2 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -1,6 +1,7 @@ //! Inlining pass for MIR functions use crate::deref_separator::deref_finder; use rustc_attr::InlineAttr; +use rustc_const_eval::transform::validate::validate_types; use rustc_hir::def_id::DefId; use rustc_index::bit_set::BitSet; use rustc_index::Idx; @@ -10,8 +11,7 @@ use rustc_middle::mir::*; use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::{self, Instance, InstanceDef, ParamEnv, Ty, TyCtxt}; use rustc_session::config::OptLevel; -use rustc_span::{hygiene::ExpnKind, ExpnData, LocalExpnId, Span}; -use rustc_target::abi::{FieldIdx, FIRST_VARIANT}; +use rustc_target::abi::FieldIdx; use rustc_target::spec::abi::Abi; use crate::simplify::{remove_dead_blocks, CfgSimplifier}; @@ -106,7 +106,7 @@ struct Inliner<'tcx> { /// Caller codegen attributes. codegen_fn_attrs: &'tcx CodegenFnAttrs, /// Stack of inlined instances. - /// We only check the `DefId` and not the substs because we want to + /// We only check the `DefId` and not the args because we want to /// avoid inlining cases of polymorphic recursion. /// The number of `DefId`s is finite, so checking history is enough /// to ensure that we do not loop endlessly while inlining. @@ -146,13 +146,16 @@ impl<'tcx> Inliner<'tcx> { Ok(new_blocks) => { debug!("inlined {}", callsite.callee); self.changed = true; + + self.history.push(callsite.callee.def_id()); + self.process_blocks(caller_body, new_blocks); + self.history.pop(); + inlined_count += 1; if inlined_count == inline_limit { + debug!("inline count reached"); return; } - self.history.push(callsite.callee.def_id()); - self.process_blocks(caller_body, new_blocks); - self.history.pop(); } } } @@ -193,11 +196,24 @@ impl<'tcx> Inliner<'tcx> { let Ok(callee_body) = callsite.callee.try_subst_mir_and_normalize_erasing_regions( self.tcx, self.param_env, - ty::EarlyBinder(callee_body.clone()), + ty::EarlyBinder::bind(callee_body.clone()), ) else { return Err("failed to normalize callee body"); }; + // Normally, this shouldn't be required, but trait normalization failure can create a + // validation ICE. + if !validate_types( + self.tcx, + MirPhase::Runtime(RuntimePhase::Optimized), + self.param_env, + &callee_body, + ) + .is_empty() + { + return Err("failed to validate callee body"); + } + // Check call signature compatibility. // Normally, this shouldn't be required, but trait normalization failure can create a // validation ICE. @@ -207,19 +223,29 @@ impl<'tcx> Inliner<'tcx> { return Err("failed to normalize return type"); } if callsite.fn_sig.abi() == Abi::RustCall { - let (arg_tuple, skipped_args) = match &args[..] { - [arg_tuple] => (arg_tuple, 0), - [_, arg_tuple] => (arg_tuple, 1), + // FIXME: Don't inline user-written `extern "rust-call"` functions, + // since this is generally perf-negative on rustc, and we hope that + // LLVM will inline these functions instead. + if callee_body.spread_arg.is_some() { + return Err("do not inline user-written rust-call functions"); + } + + let (self_arg, arg_tuple) = match &args[..] { + [arg_tuple] => (None, arg_tuple), + [self_arg, arg_tuple] => (Some(self_arg), arg_tuple), _ => bug!("Expected `rust-call` to have 1 or 2 args"), }; + let self_arg_ty = + self_arg.map(|self_arg| self_arg.ty(&caller_body.local_decls, self.tcx)); + let arg_tuple_ty = arg_tuple.ty(&caller_body.local_decls, self.tcx); - let ty::Tuple(arg_tuple_tys) = arg_tuple_ty.kind() else { + let ty::Tuple(arg_tuple_tys) = *arg_tuple_ty.kind() else { bug!("Closure arguments are not passed as a tuple"); }; for (arg_ty, input) in - arg_tuple_tys.iter().zip(callee_body.args_iter().skip(skipped_args)) + self_arg_ty.into_iter().chain(arg_tuple_tys).zip(callee_body.args_iter()) { let input_type = callee_body.local_decls[input].ty; if !util::is_subtype(self.tcx, self.param_env, input_type, arg_ty) { @@ -327,11 +353,11 @@ impl<'tcx> Inliner<'tcx> { let terminator = bb_data.terminator(); if let TerminatorKind::Call { ref func, target, fn_span, .. } = terminator.kind { let func_ty = func.ty(caller_body, self.tcx); - if let ty::FnDef(def_id, substs) = *func_ty.kind() { - // To resolve an instance its substs have to be fully normalized. - let substs = self.tcx.try_normalize_erasing_regions(self.param_env, substs).ok()?; + if let ty::FnDef(def_id, args) = *func_ty.kind() { + // To resolve an instance its args have to be fully normalized. + let args = self.tcx.try_normalize_erasing_regions(self.param_env, args).ok()?; let callee = - Instance::resolve(self.tcx, self.param_env, def_id, substs).ok().flatten()?; + Instance::resolve(self.tcx, self.param_env, def_id, args).ok().flatten()?; if let InstanceDef::Virtual(..) | InstanceDef::Intrinsic(_) = callee.def { return None; @@ -341,7 +367,7 @@ impl<'tcx> Inliner<'tcx> { return None; } - let fn_sig = self.tcx.fn_sig(def_id).subst(self.tcx, substs); + let fn_sig = self.tcx.fn_sig(def_id).instantiate(self.tcx, args); let source_info = SourceInfo { span: fn_span, ..terminator.source_info }; return Some(CallSite { callee, fn_sig, block: bb, target, source_info }); @@ -366,7 +392,7 @@ impl<'tcx> Inliner<'tcx> { // inlining. This is to ensure that the final crate doesn't have MIR that // reference unexported symbols if callsite.callee.def_id().is_local() { - let is_generic = callsite.callee.substs.non_erasable_generics().next().is_some(); + let is_generic = callsite.callee.args.non_erasable_generics().next().is_some(); if !is_generic && !callee_attrs.requests_inline() { return Err("not exported"); } @@ -435,7 +461,6 @@ impl<'tcx> Inliner<'tcx> { instance: callsite.callee, callee_body, cost: 0, - validation: Ok(()), }; // Traverse the MIR manually so we can account for the effects of inlining on the CFG. @@ -450,16 +475,16 @@ impl<'tcx> Inliner<'tcx> { checker.visit_basic_block_data(bb, blk); let term = blk.terminator(); - if let TerminatorKind::Drop { ref place, target, unwind } = term.kind { + if let TerminatorKind::Drop { ref place, target, unwind, replace: _ } = term.kind { work_list.push(target); // If the place doesn't actually need dropping, treat it like a regular goto. let ty = callsite .callee - .subst_mir(self.tcx, ty::EarlyBinder(&place.ty(callee_body, tcx).ty)); + .subst_mir(self.tcx, ty::EarlyBinder::bind(&place.ty(callee_body, tcx).ty)); if ty.needs_drop(tcx, self.param_env) && let UnwindAction::Cleanup(unwind) = unwind { - work_list.push(unwind); - } + work_list.push(unwind); + } } else if callee_attrs.instruction_set != self.codegen_fn_attrs.instruction_set && matches!(term.kind, TerminatorKind::InlineAsm { .. }) { @@ -474,14 +499,12 @@ impl<'tcx> Inliner<'tcx> { } } - // Abort if type validation found anything fishy. - checker.validation?; - + // N.B. We still apply our cost threshold to #[inline(always)] functions. + // That attribute is often applied to very large functions that exceed LLVM's (very + // generous) inlining threshold. Such functions are very poor MIR inlining candidates. + // Always inlining #[inline(always)] functions in MIR, on net, slows down the compiler. let cost = checker.cost; - if let InlineAttr::Always = callee_attrs.inline { - debug!("INLINING {:?} because inline(always) [cost={}]", callsite, cost); - Ok(()) - } else if cost <= threshold { + if cost <= threshold { debug!("INLINING {:?} [cost={} <= threshold={}]", callsite, cost, threshold); Ok(()) } else { @@ -519,7 +542,7 @@ impl<'tcx> Inliner<'tcx> { trace!("creating temp for return destination"); let dest = Rvalue::Ref( self.tcx.lifetimes.re_erased, - BorrowKind::Mut { allow_two_phase_borrow: false }, + BorrowKind::Mut { kind: MutBorrowKind::Default }, destination, ); let dest_ty = dest.ty(caller_body, self.tcx); @@ -551,16 +574,6 @@ impl<'tcx> Inliner<'tcx> { // Copy the arguments if needed. let args: Vec<_> = self.make_call_args(args, &callsite, caller_body, &callee_body); - let mut expn_data = ExpnData::default( - ExpnKind::Inlined, - callsite.source_info.span, - self.tcx.sess.edition(), - None, - None, - ); - expn_data.def_site = callee_body.span; - let expn_data = - self.tcx.with_stable_hashing_context(|hcx| LocalExpnId::fresh(expn_data, hcx)); let mut integrator = Integrator { args: &args, new_locals: Local::new(caller_body.local_decls.len()).., @@ -572,7 +585,6 @@ impl<'tcx> Inliner<'tcx> { cleanup_block: unwind, in_cleanup_block: false, tcx: self.tcx, - expn_data, always_live_locals: BitSet::new_filled(callee_body.local_decls.len()), }; @@ -778,11 +790,10 @@ struct CostChecker<'b, 'tcx> { cost: usize, callee_body: &'b Body<'tcx>, instance: ty::Instance<'tcx>, - validation: Result<(), &'static str>, } impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { - fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) { // Don't count StorageLive/StorageDead in the inlining cost. match statement.kind { StatementKind::StorageLive(_) @@ -791,18 +802,16 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { | StatementKind::Nop => {} _ => self.cost += INSTR_COST, } - - self.super_statement(statement, location); } - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, _: Location) { let tcx = self.tcx; match terminator.kind { TerminatorKind::Drop { ref place, unwind, .. } => { // If the place doesn't actually need dropping, treat it like a regular goto. let ty = self .instance - .subst_mir(tcx, ty::EarlyBinder(&place.ty(self.callee_body, tcx).ty)); + .subst_mir(tcx, ty::EarlyBinder::bind(&place.ty(self.callee_body, tcx).ty)); if ty.needs_drop(tcx, self.param_env) { self.cost += CALL_PENALTY; if let UnwindAction::Cleanup(_) = unwind { @@ -813,7 +822,7 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { } } TerminatorKind::Call { func: Operand::Constant(ref f), unwind, .. } => { - let fn_ty = self.instance.subst_mir(tcx, ty::EarlyBinder(&f.literal.ty())); + let fn_ty = self.instance.subst_mir(tcx, ty::EarlyBinder::bind(&f.literal.ty())); self.cost += if let ty::FnDef(def_id, _) = *fn_ty.kind() && tcx.is_intrinsic(def_id) { // Don't give intrinsics the extra penalty for calls INSTR_COST @@ -839,102 +848,6 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { } _ => self.cost += INSTR_COST, } - - self.super_terminator(terminator, location); - } - - /// This method duplicates code from MIR validation in an attempt to detect type mismatches due - /// to normalization failure. - fn visit_projection_elem( - &mut self, - local: Local, - proj_base: &[PlaceElem<'tcx>], - elem: PlaceElem<'tcx>, - context: PlaceContext, - location: Location, - ) { - if let ProjectionElem::Field(f, ty) = elem { - let parent = Place { local, projection: self.tcx.mk_place_elems(proj_base) }; - let parent_ty = parent.ty(&self.callee_body.local_decls, self.tcx); - let check_equal = |this: &mut Self, f_ty| { - if !util::is_equal_up_to_subtyping(this.tcx, this.param_env, ty, f_ty) { - trace!(?ty, ?f_ty); - this.validation = Err("failed to normalize projection type"); - return; - } - }; - - let kind = match parent_ty.ty.kind() { - &ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - self.tcx.type_of(def_id).subst(self.tcx, substs).kind() - } - kind => kind, - }; - - match kind { - ty::Tuple(fields) => { - let Some(f_ty) = fields.get(f.as_usize()) else { - self.validation = Err("malformed MIR"); - return; - }; - check_equal(self, *f_ty); - } - ty::Adt(adt_def, substs) => { - let var = parent_ty.variant_index.unwrap_or(FIRST_VARIANT); - let Some(field) = adt_def.variant(var).fields.get(f) else { - self.validation = Err("malformed MIR"); - return; - }; - check_equal(self, field.ty(self.tcx, substs)); - } - ty::Closure(_, substs) => { - let substs = substs.as_closure(); - let Some(f_ty) = substs.upvar_tys().nth(f.as_usize()) else { - self.validation = Err("malformed MIR"); - return; - }; - check_equal(self, f_ty); - } - &ty::Generator(def_id, substs, _) => { - let f_ty = if let Some(var) = parent_ty.variant_index { - let gen_body = if def_id == self.callee_body.source.def_id() { - self.callee_body - } else { - self.tcx.optimized_mir(def_id) - }; - - let Some(layout) = gen_body.generator_layout() else { - self.validation = Err("malformed MIR"); - return; - }; - - let Some(&local) = layout.variant_fields[var].get(f) else { - self.validation = Err("malformed MIR"); - return; - }; - - let Some(f_ty) = layout.field_tys.get(local) else { - self.validation = Err("malformed MIR"); - return; - }; - - f_ty.ty - } else { - let Some(f_ty) = substs.as_generator().prefix_tys().nth(f.index()) else { - self.validation = Err("malformed MIR"); - return; - }; - - f_ty - }; - - check_equal(self, f_ty); - } - _ => self.validation = Err("malformed MIR"), - } - } - - self.super_projection_elem(local, proj_base, elem, context, location); } } @@ -956,7 +869,6 @@ struct Integrator<'a, 'tcx> { cleanup_block: UnwindAction, in_cleanup_block: bool, tcx: TyCtxt<'tcx>, - expn_data: LocalExpnId, always_live_locals: BitSet, } @@ -1042,11 +954,6 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> { *scope = self.map_scope(*scope); } - fn visit_span(&mut self, span: &mut Span) { - // Make sure that all spans track the fact that they were inlined. - *span = span.fresh_expansion(self.expn_data); - } - fn visit_basic_block_data(&mut self, block: BasicBlock, data: &mut BasicBlockData<'tcx>) { self.in_cleanup_block = data.is_cleanup; self.super_basic_block_data(block, data); @@ -1146,10 +1053,10 @@ fn try_instance_mir<'tcx>( ) -> Result<&'tcx Body<'tcx>, &'static str> { match instance { ty::InstanceDef::DropGlue(_, Some(ty)) => match ty.kind() { - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let fields = def.all_fields(); for field in fields { - let field_ty = field.ty(tcx, substs); + let field_ty = field.ty(tcx, args); if field_ty.has_param() && field_ty.has_projections() { return Err("cannot build drop shim for polymorphic type"); } diff --git a/compiler/rustc_mir_transform/src/inline/cycle.rs b/compiler/rustc_mir_transform/src/inline/cycle.rs index 1ccf06f6153fc..822634129fc2b 100644 --- a/compiler/rustc_mir_transform/src/inline/cycle.rs +++ b/compiler/rustc_mir_transform/src/inline/cycle.rs @@ -3,7 +3,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::mir::TerminatorKind; use rustc_middle::ty::TypeVisitableExt; -use rustc_middle::ty::{self, subst::SubstsRef, InstanceDef, TyCtxt}; +use rustc_middle::ty::{self, GenericArgsRef, InstanceDef, TyCtxt}; use rustc_session::Limit; // FIXME: check whether it is cheaper to precompute the entire call graph instead of invoking @@ -43,16 +43,16 @@ pub(crate) fn mir_callgraph_reachable<'tcx>( recursion_limit: Limit, ) -> bool { trace!(%caller); - for &(callee, substs) in tcx.mir_inliner_callees(caller.def) { - let Ok(substs) = caller.try_subst_mir_and_normalize_erasing_regions( + for &(callee, args) in tcx.mir_inliner_callees(caller.def) { + let Ok(args) = caller.try_subst_mir_and_normalize_erasing_regions( tcx, param_env, - ty::EarlyBinder(substs), + ty::EarlyBinder::bind(args), ) else { - trace!(?caller, ?param_env, ?substs, "cannot normalize, skipping"); + trace!(?caller, ?param_env, ?args, "cannot normalize, skipping"); continue; }; - let Ok(Some(callee)) = ty::Instance::resolve(tcx, param_env, callee, substs) else { + let Ok(Some(callee)) = ty::Instance::resolve(tcx, param_env, callee, args) else { trace!(?callee, "cannot resolve, skipping"); continue; }; @@ -147,7 +147,7 @@ pub(crate) fn mir_callgraph_reachable<'tcx>( pub(crate) fn mir_inliner_callees<'tcx>( tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>, -) -> &'tcx [(DefId, SubstsRef<'tcx>)] { +) -> &'tcx [(DefId, GenericArgsRef<'tcx>)] { let steal; let guard; let body = match (instance, instance.def_id().as_local()) { @@ -165,7 +165,7 @@ pub(crate) fn mir_inliner_callees<'tcx>( if let TerminatorKind::Call { func, .. } = &terminator.kind { let ty = func.ty(&body.local_decls, tcx); let call = match ty.kind() { - ty::FnDef(def_id, substs) => (*def_id, *substs), + ty::FnDef(def_id, args) => (*def_id, *args), _ => continue, }; calls.insert(call); diff --git a/compiler/rustc_mir_transform/src/instsimplify.rs b/compiler/rustc_mir_transform/src/instsimplify.rs index e4dc617620e12..8b0a0903d1897 100644 --- a/compiler/rustc_mir_transform/src/instsimplify.rs +++ b/compiler/rustc_mir_transform/src/instsimplify.rs @@ -5,7 +5,7 @@ use crate::MirPass; use rustc_hir::Mutability; use rustc_middle::mir::*; use rustc_middle::ty::layout::ValidityRequirement; -use rustc_middle::ty::{self, ParamEnv, SubstsRef, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericArgsRef, ParamEnv, Ty, TyCtxt}; use rustc_span::symbol::Symbol; use rustc_target::abi::FieldIdx; @@ -57,7 +57,7 @@ struct InstSimplifyContext<'tcx, 'a> { impl<'tcx> InstSimplifyContext<'tcx, '_> { fn should_simplify(&self, source_info: &SourceInfo, rvalue: &Rvalue<'tcx>) -> bool { self.tcx.consider_optimizing(|| { - format!("InstSimplify - Rvalue: {:?} SourceInfo: {:?}", rvalue, source_info) + format!("InstSimplify - Rvalue: {rvalue:?} SourceInfo: {source_info:?}") }) } @@ -163,14 +163,14 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> { } // Transmuting a transparent struct/union to a field's type is a projection - if let ty::Adt(adt_def, substs) = operand_ty.kind() + if let ty::Adt(adt_def, args) = operand_ty.kind() && adt_def.repr().transparent() && (adt_def.is_struct() || adt_def.is_union()) && let Some(place) = operand.place() { let variant = adt_def.non_enum_variant(); for (i, field) in variant.fields.iter().enumerate() { - let field_ty = field.ty(self.tcx, substs); + let field_ty = field.ty(self.tcx, args); if field_ty == *cast_ty { let place = place.project_deeper(&[ProjectionElem::Field(FieldIdx::from_usize(i), *cast_ty)], self.tcx); let operand = if operand.is_move() { Operand::Move(place) } else { Operand::Copy(place) }; @@ -189,22 +189,22 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> { statements: &mut Vec>, ) { let TerminatorKind::Call { func, args, destination, target, .. } = &mut terminator.kind - else { return }; + else { + return; + }; // It's definitely not a clone if there are multiple arguments if args.len() != 1 { return; } - let Some(destination_block) = *target - else { return }; + let Some(destination_block) = *target else { return }; // Only bother looking more if it's easy to know what we're calling - let Some((fn_def_id, fn_substs)) = func.const_fn_def() - else { return }; + let Some((fn_def_id, fn_args)) = func.const_fn_def() else { return }; // Clone needs one subst, so we can cheaply rule out other stuff - if fn_substs.len() != 1 { + if fn_args.len() != 1 { return; } @@ -212,8 +212,7 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> { // doing DefId lookups to figure out what we're actually calling. let arg_ty = args[0].ty(self.local_decls, self.tcx); - let ty::Ref(_region, inner_ty, Mutability::Not) = *arg_ty.kind() - else { return }; + let ty::Ref(_region, inner_ty, Mutability::Not) = *arg_ty.kind() else { return }; if !inner_ty.is_trivially_pure_clone_copy() { return; @@ -227,15 +226,14 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> { if !self.tcx.consider_optimizing(|| { format!( "InstSimplify - Call: {:?} SourceInfo: {:?}", - (fn_def_id, fn_substs), + (fn_def_id, fn_args), terminator.source_info ) }) { return; } - let Some(arg_place) = args.pop().unwrap().place() - else { return }; + let Some(arg_place) = args.pop().unwrap().place() else { return }; statements.push(Statement { source_info: terminator.source_info, @@ -254,17 +252,21 @@ impl<'tcx> InstSimplifyContext<'tcx, '_> { terminator: &mut Terminator<'tcx>, _statements: &mut Vec>, ) { - let TerminatorKind::Call { func, target, .. } = &mut terminator.kind else { return; }; - let Some(target_block) = target else { return; }; + let TerminatorKind::Call { func, target, .. } = &mut terminator.kind else { + return; + }; + let Some(target_block) = target else { + return; + }; let func_ty = func.ty(self.local_decls, self.tcx); - let Some((intrinsic_name, substs)) = resolve_rust_intrinsic(self.tcx, func_ty) else { + let Some((intrinsic_name, args)) = resolve_rust_intrinsic(self.tcx, func_ty) else { return; }; // The intrinsics we are interested in have one generic parameter - if substs.is_empty() { + if args.is_empty() { return; } - let ty = substs.type_at(0); + let ty = args.type_at(0); let known_is_valid = intrinsic_assert_panics(self.tcx, self.param_env, ty, intrinsic_name); match known_is_valid { @@ -295,10 +297,10 @@ fn intrinsic_assert_panics<'tcx>( fn resolve_rust_intrinsic<'tcx>( tcx: TyCtxt<'tcx>, func_ty: Ty<'tcx>, -) -> Option<(Symbol, SubstsRef<'tcx>)> { - if let ty::FnDef(def_id, substs) = *func_ty.kind() { +) -> Option<(Symbol, GenericArgsRef<'tcx>)> { + if let ty::FnDef(def_id, args) = *func_ty.kind() { if tcx.is_intrinsic(def_id) { - return Some((tcx.item_name(def_id), substs)); + return Some((tcx.item_name(def_id), args)); } } None diff --git a/compiler/rustc_mir_transform/src/large_enums.rs b/compiler/rustc_mir_transform/src/large_enums.rs index 430a6f6cef5be..19108dabdf417 100644 --- a/compiler/rustc_mir_transform/src/large_enums.rs +++ b/compiler/rustc_mir_transform/src/large_enums.rs @@ -48,7 +48,7 @@ impl EnumSizeOpt { alloc_cache: &mut FxHashMap, AllocId>, ) -> Option<(AdtDef<'tcx>, usize, AllocId)> { let adt_def = match ty.kind() { - ty::Adt(adt_def, _substs) if adt_def.is_enum() => adt_def, + ty::Adt(adt_def, _args) if adt_def.is_enum() => adt_def, _ => return None, }; let layout = tcx.layout_of(param_env.and(ty)).ok()?; @@ -141,7 +141,7 @@ impl EnumSizeOpt { self.candidate(tcx, param_env, ty, &mut alloc_cache)?; let alloc = tcx.global_alloc(alloc_id).unwrap_memory(); - let tmp_ty = tcx.mk_array(tcx.types.usize, num_variants as u64); + let tmp_ty = Ty::new_array(tcx, tcx.types.usize, num_variants as u64); let size_array_local = local_decls.push(LocalDecl::new(tmp_ty, span)); let store_live = Statement { @@ -208,8 +208,9 @@ impl EnumSizeOpt { ))), }; - let dst = - Place::from(local_decls.push(LocalDecl::new(tcx.mk_mut_ptr(ty), span))); + let dst = Place::from( + local_decls.push(LocalDecl::new(Ty::new_mut_ptr(tcx, ty), span)), + ); let dst_ptr = Statement { source_info, @@ -219,7 +220,7 @@ impl EnumSizeOpt { ))), }; - let dst_cast_ty = tcx.mk_mut_ptr(tcx.types.u8); + let dst_cast_ty = Ty::new_mut_ptr(tcx, tcx.types.u8); let dst_cast_place = Place::from(local_decls.push(LocalDecl::new(dst_cast_ty, span))); @@ -231,8 +232,9 @@ impl EnumSizeOpt { ))), }; - let src = - Place::from(local_decls.push(LocalDecl::new(tcx.mk_imm_ptr(ty), span))); + let src = Place::from( + local_decls.push(LocalDecl::new(Ty::new_imm_ptr(tcx, ty), span)), + ); let src_ptr = Statement { source_info, @@ -242,7 +244,7 @@ impl EnumSizeOpt { ))), }; - let src_cast_ty = tcx.mk_imm_ptr(tcx.types.u8); + let src_cast_ty = Ty::new_imm_ptr(tcx, tcx.types.u8); let src_cast_place = Place::from(local_decls.push(LocalDecl::new(src_cast_ty, span))); diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 65864dc016f4f..bf798adee199e 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -2,7 +2,7 @@ #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] #![feature(box_patterns)] -#![feature(drain_filter)] +#![feature(is_sorted)] #![feature(let_chains)] #![feature(map_try_insert)] #![feature(min_specialization)] @@ -30,8 +30,8 @@ use rustc_hir::intravisit::{self, Visitor}; use rustc_index::IndexVec; use rustc_middle::mir::visit::Visitor as _; use rustc_middle::mir::{ - traversal, AnalysisPhase, Body, ClearCrossCrate, ConstQualifs, Constant, LocalDecl, MirPass, - MirPhase, Operand, Place, ProjectionElem, Promoted, RuntimePhase, Rvalue, SourceInfo, + traversal, AnalysisPhase, Body, CallSource, ClearCrossCrate, ConstQualifs, Constant, LocalDecl, + MirPass, MirPhase, Operand, Place, ProjectionElem, Promoted, RuntimePhase, Rvalue, SourceInfo, Statement, StatementKind, TerminatorKind, START_BLOCK, }; use rustc_middle::query::Providers; @@ -75,7 +75,7 @@ mod errors; mod ffi_unwind_calls; mod function_item_references; mod generator; -mod inline; +pub mod inline; mod instsimplify; mod large_enums; mod lower_intrinsics; @@ -84,6 +84,7 @@ mod match_branches; mod multiple_return_terminators; mod normalize_array_len; mod nrvo; +mod prettify; mod ref_prop; mod remove_noop_landing_pads; mod remove_storage_markers; @@ -188,7 +189,7 @@ fn remap_mir_for_const_eval_select<'tcx>( }; method(place) }).collect(); - terminator.kind = TerminatorKind::Call { func, args: arguments, destination, target, unwind, from_hir_call: false, fn_span }; + terminator.kind = TerminatorKind::Call { func, args: arguments, destination, target, unwind, call_source: CallSource::Misc, fn_span }; } _ => {} } @@ -337,38 +338,16 @@ fn inner_mir_for_ctfe(tcx: TyCtxt<'_>, def: LocalDefId) -> Body<'_> { return shim::build_adt_ctor(tcx, def.to_def_id()); } - let context = tcx - .hir() - .body_const_context(def) - .expect("mir_for_ctfe should not be used for runtime functions"); - - let body = tcx.mir_drops_elaborated_and_const_checked(def).borrow().clone(); + let body = tcx.mir_drops_elaborated_and_const_checked(def); + let body = match tcx.hir().body_const_context(def) { + // consts and statics do not have `optimized_mir`, so we can steal the body instead of + // cloning it. + Some(hir::ConstContext::Const | hir::ConstContext::Static(_)) => body.steal(), + Some(hir::ConstContext::ConstFn) => body.borrow().clone(), + None => bug!("`mir_for_ctfe` called on non-const {def:?}"), + }; let mut body = remap_mir_for_const_eval_select(tcx, body, hir::Constness::Const); - - match context { - // Do not const prop functions, either they get executed at runtime or exported to metadata, - // so we run const prop on them, or they don't, in which case we const evaluate some control - // flow paths of the function and any errors in those paths will get emitted as const eval - // errors. - hir::ConstContext::ConstFn => {} - // Static items always get evaluated, so we can just let const eval see if any erroneous - // control flow paths get executed. - hir::ConstContext::Static(_) => {} - // Associated constants get const prop run so we detect common failure situations in the - // crate that defined the constant. - // Technically we want to not run on regular const items, but oli-obk doesn't know how to - // conveniently detect that at this point without looking at the HIR. - hir::ConstContext::Const => { - pm::run_passes( - tcx, - &mut body, - &[&const_prop::ConstProp], - Some(MirPhase::Runtime(RuntimePhase::Optimized)), - ); - } - } - pm::run_passes(tcx, &mut body, &[&ctfe_limit::CtfeLimit], None); body @@ -445,10 +424,16 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> & run_analysis_to_runtime_passes(tcx, &mut body); + // Now that drop elaboration has been performed, we can check for + // unconditional drop recursion. + rustc_mir_build::lints::check_drop_recursion(tcx, &body); + tcx.alloc_steal_mir(body) } -fn run_analysis_to_runtime_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { +// Made public such that `mir_drops_elaborated_and_const_checked` can be overridden +// by custom rustc drivers, running all the steps by themselves. +pub fn run_analysis_to_runtime_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { assert!(body.phase == MirPhase::Analysis(AnalysisPhase::Initial)); let did = body.source.def_id(); @@ -552,15 +537,18 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { &normalize_array_len::NormalizeArrayLen, // has to run after `slice::len` lowering &const_goto::ConstGoto, &remove_unneeded_drops::RemoveUnneededDrops, + &ref_prop::ReferencePropagation, &sroa::ScalarReplacementOfAggregates, &match_branches::MatchBranchSimplification, // inst combine is after MatchBranchSimplification to clean up Ne(_1, false) &multiple_return_terminators::MultipleReturnTerminators, &instsimplify::InstSimplify, - &separate_const_switch::SeparateConstSwitch, &simplify::SimplifyLocals::BeforeConstProp, ©_prop::CopyProp, - &ref_prop::ReferencePropagation, + // Perform `SeparateConstSwitch` after SSA-based analyses, as cloning blocks may + // destroy the SSA property. It should still happen before const-propagation, so the + // latter pass will leverage the created opportunities. + &separate_const_switch::SeparateConstSwitch, &const_prop::ConstProp, &dataflow_const_prop::DataflowConstProp, // @@ -581,6 +569,9 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { &large_enums::EnumSizeOpt { discrepancy: 128 }, // Some cleanup necessary at least for LLVM and potentially other codegen backends. &add_call_guards::CriticalCallEdges, + // Cleanup for human readability, off by default. + &prettify::ReorderBasicBlocks, + &prettify::ReorderLocals, // Dump the end result for testing and debugging purposes. &dump_mir::Marker("PreCodegen"), ], @@ -608,7 +599,7 @@ fn inner_optimized_mir(tcx: TyCtxt<'_>, did: LocalDefId) -> Body<'_> { // computes and caches its result. Some(hir::ConstContext::ConstFn) => tcx.ensure_with_value().mir_for_ctfe(did), None => {} - Some(other) => panic!("do not use `optimized_mir` for constants: {:?}", other), + Some(other) => panic!("do not use `optimized_mir` for constants: {other:?}"), } debug!("about to call mir_drops_elaborated..."); let body = tcx.mir_drops_elaborated_and_const_checked(did).steal(); diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index dae01e41e5f3d..fc36c6e4124a6 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -2,7 +2,7 @@ use crate::{errors, MirPass}; use rustc_middle::mir::*; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; @@ -19,7 +19,8 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { &mut terminator.kind { let func_ty = func.ty(local_decls, tcx); - let Some((intrinsic_name, substs)) = resolve_rust_intrinsic(tcx, func_ty) else { + let Some((intrinsic_name, generic_args)) = resolve_rust_intrinsic(tcx, func_ty) + else { continue; }; match intrinsic_name { @@ -82,30 +83,45 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { drop(args); terminator.kind = TerminatorKind::Goto { target }; } - sym::wrapping_add | sym::wrapping_sub | sym::wrapping_mul => { - if let Some(target) = *target { - let lhs; - let rhs; - { - let mut args = args.drain(..); - lhs = args.next().unwrap(); - rhs = args.next().unwrap(); - } - let bin_op = match intrinsic_name { - sym::wrapping_add => BinOp::Add, - sym::wrapping_sub => BinOp::Sub, - sym::wrapping_mul => BinOp::Mul, - _ => bug!("unexpected intrinsic"), - }; - block.statements.push(Statement { - source_info: terminator.source_info, - kind: StatementKind::Assign(Box::new(( - *destination, - Rvalue::BinaryOp(bin_op, Box::new((lhs, rhs))), - ))), - }); - terminator.kind = TerminatorKind::Goto { target }; + sym::wrapping_add + | sym::wrapping_sub + | sym::wrapping_mul + | sym::unchecked_add + | sym::unchecked_sub + | sym::unchecked_mul + | sym::unchecked_div + | sym::unchecked_rem + | sym::unchecked_shl + | sym::unchecked_shr => { + let target = target.unwrap(); + let lhs; + let rhs; + { + let mut args = args.drain(..); + lhs = args.next().unwrap(); + rhs = args.next().unwrap(); } + let bin_op = match intrinsic_name { + sym::wrapping_add => BinOp::Add, + sym::wrapping_sub => BinOp::Sub, + sym::wrapping_mul => BinOp::Mul, + sym::unchecked_add => BinOp::AddUnchecked, + sym::unchecked_sub => BinOp::SubUnchecked, + sym::unchecked_mul => BinOp::MulUnchecked, + sym::unchecked_div => BinOp::Div, + sym::unchecked_rem => BinOp::Rem, + sym::unchecked_shl => BinOp::ShlUnchecked, + sym::unchecked_shr => BinOp::ShrUnchecked, + _ => bug!("unexpected intrinsic"), + }; + block.statements.push(Statement { + source_info: terminator.source_info, + kind: StatementKind::Assign(Box::new(( + *destination, + Rvalue::BinaryOp(bin_op, Box::new((lhs, rhs))), + ))), + }); + terminator.kind = TerminatorKind::Goto { target }; } sym::add_with_overflow | sym::sub_with_overflow | sym::mul_with_overflow => { if let Some(target) = *target { @@ -134,7 +150,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { } sym::size_of | sym::min_align_of => { if let Some(target) = *target { - let tp_ty = substs.type_at(0); + let tp_ty = generic_args.type_at(0); let null_op = match intrinsic_name { sym::size_of => NullOp::SizeOf, sym::min_align_of => NullOp::AlignOf, @@ -236,7 +252,9 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { if let (Some(target), Some(arg)) = (*target, args[0].place()) { let ty::RawPtr(ty::TypeAndMut { ty: dest_ty, .. }) = destination.ty(local_decls, tcx).ty.kind() - else { bug!(); }; + else { + bug!(); + }; block.statements.push(Statement { source_info: terminator.source_info, @@ -287,7 +305,7 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { terminator.kind = TerminatorKind::Unreachable; } } - _ if intrinsic_name.as_str().starts_with("simd_shuffle") => { + sym::simd_shuffle => { validate_simd_shuffle(tcx, args, terminator.source_info.span); } _ => {} @@ -300,10 +318,10 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { fn resolve_rust_intrinsic<'tcx>( tcx: TyCtxt<'tcx>, func_ty: Ty<'tcx>, -) -> Option<(Symbol, SubstsRef<'tcx>)> { - if let ty::FnDef(def_id, substs) = *func_ty.kind() { +) -> Option<(Symbol, GenericArgsRef<'tcx>)> { + if let ty::FnDef(def_id, args) = *func_ty.kind() { if tcx.is_intrinsic(def_id) { - return Some((tcx.item_name(def_id), substs)); + return Some((tcx.item_name(def_id), args)); } } None diff --git a/compiler/rustc_mir_transform/src/lower_slice_len.rs b/compiler/rustc_mir_transform/src/lower_slice_len.rs index 6e40dfa0d1382..b7cc0db95597d 100644 --- a/compiler/rustc_mir_transform/src/lower_slice_len.rs +++ b/compiler/rustc_mir_transform/src/lower_slice_len.rs @@ -54,7 +54,7 @@ fn lower_slice_len_call<'tcx>( args, destination, target: Some(bb), - from_hir_call: true, + call_source: CallSource::Normal, .. } => { // some heuristics for fast rejection diff --git a/compiler/rustc_mir_transform/src/match_branches.rs b/compiler/rustc_mir_transform/src/match_branches.rs index 59942dc76f9aa..bc29fb8ded16a 100644 --- a/compiler/rustc_mir_transform/src/match_branches.rs +++ b/compiler/rustc_mir_transform/src/match_branches.rs @@ -41,7 +41,7 @@ pub struct MatchBranchSimplification; impl<'tcx> MirPass<'tcx> for MatchBranchSimplification { fn is_enabled(&self, sess: &rustc_session::Session) -> bool { - sess.mir_opt_level() >= 3 + sess.mir_opt_level() >= 1 } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { @@ -51,7 +51,7 @@ impl<'tcx> MirPass<'tcx> for MatchBranchSimplification { let bbs = body.basic_blocks.as_mut(); let mut should_cleanup = false; 'outer: for bb_idx in bbs.indices() { - if !tcx.consider_optimizing(|| format!("MatchBranchSimplification {:?} ", def_id)) { + if !tcx.consider_optimizing(|| format!("MatchBranchSimplification {def_id:?} ")) { continue; } @@ -62,7 +62,12 @@ impl<'tcx> MirPass<'tcx> for MatchBranchSimplification { .. } if targets.iter().len() == 1 => { let (value, target) = targets.iter().next().unwrap(); - if target == targets.otherwise() { + // We require that this block and the two possible target blocks all be + // distinct. + if target == targets.otherwise() + || bb_idx == target + || bb_idx == targets.otherwise() + { continue; } (discr, value, target, targets.otherwise()) diff --git a/compiler/rustc_mir_transform/src/multiple_return_terminators.rs b/compiler/rustc_mir_transform/src/multiple_return_terminators.rs index 3957cd92c4e39..c97d034544a81 100644 --- a/compiler/rustc_mir_transform/src/multiple_return_terminators.rs +++ b/compiler/rustc_mir_transform/src/multiple_return_terminators.rs @@ -27,7 +27,7 @@ impl<'tcx> MirPass<'tcx> for MultipleReturnTerminators { } for bb in bbs { - if !tcx.consider_optimizing(|| format!("MultipleReturnTerminators {:?} ", def_id)) { + if !tcx.consider_optimizing(|| format!("MultipleReturnTerminators {def_id:?} ")) { break; } diff --git a/compiler/rustc_mir_transform/src/normalize_array_len.rs b/compiler/rustc_mir_transform/src/normalize_array_len.rs index 3d61d33ce3536..6c3b7c58fab5e 100644 --- a/compiler/rustc_mir_transform/src/normalize_array_len.rs +++ b/compiler/rustc_mir_transform/src/normalize_array_len.rs @@ -41,7 +41,7 @@ fn compute_slice_length<'tcx>( for (local, rvalue, _) in ssa.assignments(body) { match rvalue { Rvalue::Cast( - CastKind::Pointer(ty::adjustment::PointerCast::Unsize), + CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize), operand, cast_ty, ) => { diff --git a/compiler/rustc_mir_transform/src/nrvo.rs b/compiler/rustc_mir_transform/src/nrvo.rs index 5ce96012b9086..e1298b0654f2c 100644 --- a/compiler/rustc_mir_transform/src/nrvo.rs +++ b/compiler/rustc_mir_transform/src/nrvo.rs @@ -45,7 +45,7 @@ impl<'tcx> MirPass<'tcx> for RenameReturnPlace { return; }; - if !tcx.consider_optimizing(|| format!("RenameReturnPlace {:?}", def_id)) { + if !tcx.consider_optimizing(|| format!("RenameReturnPlace {def_id:?}")) { return; } diff --git a/compiler/rustc_mir_transform/src/pass_manager.rs b/compiler/rustc_mir_transform/src/pass_manager.rs index 710eed3ed3808..057f5fe82931b 100644 --- a/compiler/rustc_mir_transform/src/pass_manager.rs +++ b/compiler/rustc_mir_transform/src/pass_manager.rs @@ -118,7 +118,7 @@ fn run_passes_inner<'tcx>( dump_mir_for_pass(tcx, body, &name, false); } if validate { - validate_body(tcx, body, format!("before pass {}", name)); + validate_body(tcx, body, format!("before pass {name}")); } tcx.sess.time(name, || pass.run_pass(tcx, body)); @@ -127,7 +127,7 @@ fn run_passes_inner<'tcx>( dump_mir_for_pass(tcx, body, &name, true); } if validate { - validate_body(tcx, body, format!("after pass {}", name)); + validate_body(tcx, body, format!("after pass {name}")); } body.pass_count += 1; diff --git a/compiler/rustc_mir_transform/src/prettify.rs b/compiler/rustc_mir_transform/src/prettify.rs new file mode 100644 index 0000000000000..745fa30841c35 --- /dev/null +++ b/compiler/rustc_mir_transform/src/prettify.rs @@ -0,0 +1,150 @@ +//! These two passes provide no value to the compiler, so are off at every level. +//! +//! However, they can be enabled on the command line +//! (`-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals`) +//! to make the MIR easier to read for humans. + +use crate::MirPass; +use rustc_index::{bit_set::BitSet, IndexSlice, IndexVec}; +use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor}; +use rustc_middle::mir::*; +use rustc_middle::ty::TyCtxt; +use rustc_session::Session; + +/// Rearranges the basic blocks into a *reverse post-order*. +/// +/// Thus after this pass, all the successors of a block are later than it in the +/// `IndexVec`, unless that successor is a back-edge (such as from a loop). +pub struct ReorderBasicBlocks; + +impl<'tcx> MirPass<'tcx> for ReorderBasicBlocks { + fn is_enabled(&self, _session: &Session) -> bool { + false + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + let rpo: IndexVec = + body.basic_blocks.reverse_postorder().iter().copied().collect(); + if rpo.iter().is_sorted() { + return; + } + + let mut updater = BasicBlockUpdater { map: rpo.invert_bijective_mapping(), tcx }; + debug_assert_eq!(updater.map[START_BLOCK], START_BLOCK); + updater.visit_body(body); + + permute(body.basic_blocks.as_mut(), &updater.map); + } +} + +/// Rearranges the locals into *use* order. +/// +/// Thus after this pass, a local with a smaller [`Location`] where it was first +/// assigned or referenced will have a smaller number. +/// +/// (Does not reorder arguments nor the [`RETURN_PLACE`].) +pub struct ReorderLocals; + +impl<'tcx> MirPass<'tcx> for ReorderLocals { + fn is_enabled(&self, _session: &Session) -> bool { + false + } + + fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + let mut finder = + LocalFinder { map: IndexVec::new(), seen: BitSet::new_empty(body.local_decls.len()) }; + + // We can't reorder the return place or the arguments + for local in (0..=body.arg_count).map(Local::from_usize) { + finder.track(local); + } + + for (bb, bbd) in body.basic_blocks.iter_enumerated() { + finder.visit_basic_block_data(bb, bbd); + } + + // track everything in case there are some locals that we never saw, + // such as in non-block things like debug info or in non-uses. + for local in body.local_decls.indices() { + finder.track(local); + } + + if finder.map.iter().is_sorted() { + return; + } + + let mut updater = LocalUpdater { map: finder.map.invert_bijective_mapping(), tcx }; + + for local in (0..=body.arg_count).map(Local::from_usize) { + debug_assert_eq!(updater.map[local], local); + } + + updater.visit_body_preserves_cfg(body); + + permute(&mut body.local_decls, &updater.map); + } +} + +fn permute(data: &mut IndexVec, map: &IndexSlice) { + // FIXME: It would be nice to have a less-awkward way to apply permutations, + // but I don't know one that exists. `sort_by_cached_key` has logic for it + // internally, but not in a way that we're allowed to use here. + let mut enumerated: Vec<_> = std::mem::take(data).into_iter_enumerated().collect(); + enumerated.sort_by_key(|p| map[p.0]); + *data = enumerated.into_iter().map(|p| p.1).collect(); +} + +struct BasicBlockUpdater<'tcx> { + map: IndexVec, + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> MutVisitor<'tcx> for BasicBlockUpdater<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, _location: Location) { + for succ in terminator.successors_mut() { + *succ = self.map[*succ]; + } + } +} + +struct LocalFinder { + map: IndexVec, + seen: BitSet, +} + +impl LocalFinder { + fn track(&mut self, l: Local) { + if self.seen.insert(l) { + self.map.push(l); + } + } +} + +impl<'tcx> Visitor<'tcx> for LocalFinder { + fn visit_local(&mut self, l: Local, context: PlaceContext, _location: Location) { + // Exclude non-uses to keep `StorageLive` from controlling where we put + // a `Local`, since it might not actually be assigned until much later. + if context.is_use() { + self.track(l); + } + } +} + +struct LocalUpdater<'tcx> { + pub map: IndexVec, + pub tcx: TyCtxt<'tcx>, +} + +impl<'tcx> MutVisitor<'tcx> for LocalUpdater<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn visit_local(&mut self, l: &mut Local, _: PlaceContext, _: Location) { + *l = self.map[*l]; + } +} diff --git a/compiler/rustc_mir_transform/src/ref_prop.rs b/compiler/rustc_mir_transform/src/ref_prop.rs index bbd9f76ba5cc3..49a940b57799c 100644 --- a/compiler/rustc_mir_transform/src/ref_prop.rs +++ b/compiler/rustc_mir_transform/src/ref_prop.rs @@ -71,7 +71,7 @@ pub struct ReferencePropagation; impl<'tcx> MirPass<'tcx> for ReferencePropagation { fn is_enabled(&self, sess: &rustc_session::Session) -> bool { - sess.mir_opt_level() >= 4 + sess.mir_opt_level() >= 2 } #[instrument(level = "trace", skip(self, tcx, body))] @@ -265,7 +265,6 @@ fn compute_replacement<'tcx>( targets, storage_to_remove, allowed_replacements, - fully_replacable_locals, any_replacement: false, }; @@ -346,7 +345,6 @@ struct Replacer<'tcx> { storage_to_remove: BitSet, allowed_replacements: FxHashSet<(Local, Location)>, any_replacement: bool, - fully_replacable_locals: BitSet, } impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> { @@ -355,7 +353,10 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> { } fn visit_var_debug_info(&mut self, debuginfo: &mut VarDebugInfo<'tcx>) { - if let VarDebugInfoContents::Place(ref mut place) = debuginfo.value + // If the debuginfo is a pointer to another place: + // - if it's a reborrow, see through it; + // - if it's a direct borrow, increase `debuginfo.references`. + while let VarDebugInfoContents::Place(ref mut place) = debuginfo.value && place.projection.is_empty() && let Value::Pointer(target, _) = self.targets[place.local] && target.projection.iter().all(|p| p.can_use_in_debuginfo()) @@ -363,34 +364,37 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'tcx> { if let Some((&PlaceElem::Deref, rest)) = target.projection.split_last() { *place = Place::from(target.local).project_deeper(rest, self.tcx); self.any_replacement = true; - } else if self.fully_replacable_locals.contains(place.local) - && let Some(references) = debuginfo.references.checked_add(1) - { - debuginfo.references = references; - *place = target; - self.any_replacement = true; + } else { + break } } + + // Simplify eventual projections left inside `debuginfo`. + self.super_var_debug_info(debuginfo); } fn visit_place(&mut self, place: &mut Place<'tcx>, ctxt: PlaceContext, loc: Location) { - if place.projection.first() != Some(&PlaceElem::Deref) { - return; - } - loop { - if let Value::Pointer(target, _) = self.targets[place.local] { - let perform_opt = matches!(ctxt, PlaceContext::NonUse(_)) - || self.allowed_replacements.contains(&(target.local, loc)); - - if perform_opt { - *place = target.project_deeper(&place.projection[1..], self.tcx); - self.any_replacement = true; - continue; + if place.projection.first() != Some(&PlaceElem::Deref) { + return; + } + + let Value::Pointer(target, _) = self.targets[place.local] else { return }; + + let perform_opt = match ctxt { + PlaceContext::NonUse(NonUseContext::VarDebugInfo) => { + target.projection.iter().all(|p| p.can_use_in_debuginfo()) } + PlaceContext::NonUse(_) => true, + _ => self.allowed_replacements.contains(&(target.local, loc)), + }; + + if !perform_opt { + return; } - break; + *place = target.project_deeper(&place.projection[1..], self.tcx); + self.any_replacement = true; } } diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs index 4941c9edce305..4e85c76fbc066 100644 --- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs @@ -6,8 +6,8 @@ use rustc_middle::ty::TyCtxt; use rustc_target::spec::PanicStrategy; /// A pass that removes noop landing pads and replaces jumps to them with -/// `None`. This is important because otherwise LLVM generates terrible -/// code for these. +/// `UnwindAction::Continue`. This is important because otherwise LLVM generates +/// terrible code for these. pub struct RemoveNoopLandingPads; impl<'tcx> MirPass<'tcx> for RemoveNoopLandingPads { @@ -84,7 +84,17 @@ impl RemoveNoopLandingPads { fn remove_nop_landing_pads(&self, body: &mut Body<'_>) { debug!("body: {:#?}", body); - // make sure there's a resume block + // Skip the pass if there are no blocks with a resume terminator. + let has_resume = body + .basic_blocks + .iter_enumerated() + .any(|(_bb, block)| matches!(block.terminator().kind, TerminatorKind::Resume)); + if !has_resume { + debug!("remove_noop_landing_pads: no resume block in MIR"); + return; + } + + // make sure there's a resume block without any statements let resume_block = { let mut patch = MirPatch::new(body); let resume_block = patch.resume_block(); diff --git a/compiler/rustc_mir_transform/src/remove_uninit_drops.rs b/compiler/rustc_mir_transform/src/remove_uninit_drops.rs index 1f9e521d315d8..263849747981c 100644 --- a/compiler/rustc_mir_transform/src/remove_uninit_drops.rs +++ b/compiler/rustc_mir_transform/src/remove_uninit_drops.rs @@ -1,10 +1,12 @@ use rustc_index::bit_set::ChunkedBitSet; use rustc_middle::mir::{Body, TerminatorKind}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, VariantDef}; use rustc_mir_dataflow::impls::MaybeInitializedPlaces; use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex}; -use rustc_mir_dataflow::{self, move_path_children_matching, Analysis, MoveDataParamEnv}; +use rustc_mir_dataflow::{ + self, move_path_children_matching, Analysis, MaybeReachable, MoveDataParamEnv, +}; use rustc_target::abi::FieldIdx; use crate::MirPass; @@ -22,7 +24,7 @@ pub struct RemoveUninitDrops; impl<'tcx> MirPass<'tcx> for RemoveUninitDrops { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { let param_env = tcx.param_env(body.source.def_id()); - let Ok((_,move_data)) = MoveData::gather_moves(body, tcx, param_env) else { + let Ok(move_data) = MoveData::gather_moves(body, tcx, param_env) else { // We could continue if there are move errors, but there's not much point since our // init data isn't complete. return; @@ -38,10 +40,10 @@ impl<'tcx> MirPass<'tcx> for RemoveUninitDrops { let mut to_remove = vec![]; for (bb, block) in body.basic_blocks.iter_enumerated() { let terminator = block.terminator(); - let TerminatorKind::Drop { place, .. } = &terminator.kind - else { continue }; + let TerminatorKind::Drop { place, .. } = &terminator.kind else { continue }; maybe_inits.seek_before_primary_effect(body.terminator_loc(bb)); + let MaybeReachable::Reachable(maybe_inits) = maybe_inits.get() else { continue }; // If there's no move path for the dropped place, it's probably a `Deref`. Let it alone. let LookupResult::Exact(mpi) = mdpe.move_data.rev_lookup.find(place.as_ref()) else { @@ -51,7 +53,7 @@ impl<'tcx> MirPass<'tcx> for RemoveUninitDrops { let should_keep = is_needs_drop_and_init( tcx, param_env, - maybe_inits.get(), + maybe_inits, &mdpe.move_data, place.ty(body, tcx).ty, mpi, @@ -64,9 +66,9 @@ impl<'tcx> MirPass<'tcx> for RemoveUninitDrops { for bb in to_remove { let block = &mut body.basic_blocks_mut()[bb]; - let TerminatorKind::Drop { target, .. } - = &block.terminator().kind - else { unreachable!() }; + let TerminatorKind::Drop { target, .. } = &block.terminator().kind else { + unreachable!() + }; // Replace block terminator with `Goto`. block.terminator_mut().kind = TerminatorKind::Goto { target: *target }; @@ -99,7 +101,7 @@ fn is_needs_drop_and_init<'tcx>( // This pass is only needed for const-checking, so it doesn't handle as many cases as // `DropCtxt::open_drop`, since they aren't relevant in a const-context. match ty.kind() { - ty::Adt(adt, substs) => { + ty::Adt(adt, args) => { let dont_elaborate = adt.is_union() || adt.is_manually_drop() || adt.has_dtor(tcx); if dont_elaborate { return true; @@ -119,7 +121,7 @@ fn is_needs_drop_and_init<'tcx>( let downcast = move_path_children_matching(move_data, mpi, |x| x.is_downcast_to(vid)); let Some(dc_mpi) = downcast else { - return variant_needs_drop(tcx, param_env, substs, variant); + return variant_needs_drop(tcx, param_env, args, variant); }; dc_mpi @@ -131,7 +133,7 @@ fn is_needs_drop_and_init<'tcx>( .fields .iter() .enumerate() - .map(|(f, field)| (FieldIdx::from_usize(f), field.ty(tcx, substs), mpi)) + .map(|(f, field)| (FieldIdx::from_usize(f), field.ty(tcx, args), mpi)) .any(field_needs_drop_and_init) }) } @@ -149,11 +151,11 @@ fn is_needs_drop_and_init<'tcx>( fn variant_needs_drop<'tcx>( tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, variant: &VariantDef, ) -> bool { variant.fields.iter().any(|field| { - let f_ty = field.ty(tcx, substs); + let f_ty = field.ty(tcx, args); f_ty.needs_drop(tcx, param_env) }) } diff --git a/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs b/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs index 84ccf6e1f61d4..08b2a6537e93e 100644 --- a/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs +++ b/compiler/rustc_mir_transform/src/remove_unneeded_drops.rs @@ -27,7 +27,7 @@ impl<'tcx> MirPass<'tcx> for RemoveUnneededDrops { if ty.ty.needs_drop(tcx, param_env) { continue; } - if !tcx.consider_optimizing(|| format!("RemoveUnneededDrops {:?} ", did)) { + if !tcx.consider_optimizing(|| format!("RemoveUnneededDrops {did:?} ")) { continue; } debug!("SUCCESS: replacing `drop` with goto({:?})", target); diff --git a/compiler/rustc_mir_transform/src/remove_zsts.rs b/compiler/rustc_mir_transform/src/remove_zsts.rs index 1f37f03cff1ca..9c6c55b0811f7 100644 --- a/compiler/rustc_mir_transform/src/remove_zsts.rs +++ b/compiler/rustc_mir_transform/src/remove_zsts.rs @@ -15,7 +15,7 @@ impl<'tcx> MirPass<'tcx> for RemoveZsts { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // Avoid query cycles (generators require optimized MIR for layout). - if tcx.type_of(body.source.def_id()).subst_identity().is_generator() { + if tcx.type_of(body.source.def_id()).instantiate_identity().is_generator() { return; } let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id()); @@ -102,7 +102,7 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> { let op_ty = operand.ty(self.local_decls, self.tcx); if self.known_to_be_zst(op_ty) && self.tcx.consider_optimizing(|| { - format!("RemoveZsts - Operand: {:?} Location: {:?}", operand, loc) + format!("RemoveZsts - Operand: {operand:?} Location: {loc:?}") }) { *operand = Operand::Constant(Box::new(self.make_zst(op_ty))) diff --git a/compiler/rustc_mir_transform/src/required_consts.rs b/compiler/rustc_mir_transform/src/required_consts.rs index 0ea8f2ba93fd8..243cb463560e4 100644 --- a/compiler/rustc_mir_transform/src/required_consts.rs +++ b/compiler/rustc_mir_transform/src/required_consts.rs @@ -17,8 +17,8 @@ impl<'tcx> Visitor<'tcx> for RequiredConstsVisitor<'_, 'tcx> { let literal = constant.literal; match literal { ConstantKind::Ty(c) => match c.kind() { - ConstKind::Param(_) | ConstKind::Error(_) => {} - _ => bug!("only ConstKind::Param should be encountered here, got {:#?}", c), + ConstKind::Param(_) | ConstKind::Error(_) | ConstKind::Value(_) => {} + _ => bug!("only ConstKind::Param/Value should be encountered here, got {:#?}", c), }, ConstantKind::Unevaluated(..) => self.required_consts.push(*constant), ConstantKind::Val(..) => {} diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs index 2479856b727da..1d8e54cdca09d 100644 --- a/compiler/rustc_mir_transform/src/separate_const_switch.rs +++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs @@ -46,7 +46,9 @@ pub struct SeparateConstSwitch; impl<'tcx> MirPass<'tcx> for SeparateConstSwitch { fn is_enabled(&self, sess: &rustc_session::Session) -> bool { - sess.mir_opt_level() >= 4 + // This pass participates in some as-of-yet untested unsoundness found + // in https://github.com/rust-lang/rust/issues/112460 + sess.mir_opt_level() >= 2 && sess.opts.unstable_opts.unsound_mir_opts } fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index 7c47d8814db83..223dc59c68d17 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -3,8 +3,8 @@ use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_middle::mir::*; use rustc_middle::query::Providers; -use rustc_middle::ty::InternalSubsts; -use rustc_middle::ty::{self, EarlyBinder, GeneratorSubsts, Ty, TyCtxt}; +use rustc_middle::ty::GenericArgs; +use rustc_middle::ty::{self, EarlyBinder, GeneratorArgs, Ty, TyCtxt}; use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT}; use rustc_index::{Idx, IndexVec}; @@ -32,13 +32,15 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<' let mut result = match instance { ty::InstanceDef::Item(..) => bug!("item {:?} passed to make_shim", instance), ty::InstanceDef::VTableShim(def_id) => { - build_call_shim(tcx, instance, Some(Adjustment::Deref), CallKind::Direct(def_id)) + let adjustment = Adjustment::Deref { source: DerefSource::MutPtr }; + build_call_shim(tcx, instance, Some(adjustment), CallKind::Direct(def_id)) } ty::InstanceDef::FnPtrShim(def_id, ty) => { let trait_ = tcx.trait_of_item(def_id).unwrap(); let adjustment = match tcx.fn_trait_kind_from_def_id(trait_) { Some(ty::ClosureKind::FnOnce) => Adjustment::Identity, - Some(ty::ClosureKind::FnMut | ty::ClosureKind::Fn) => Adjustment::Deref, + Some(ty::ClosureKind::Fn) => Adjustment::Deref { source: DerefSource::ImmRef }, + Some(ty::ClosureKind::FnMut) => Adjustment::Deref { source: DerefSource::MutRef }, None => bug!("fn pointer {:?} is not an fn", ty), }; @@ -67,10 +69,19 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<' ty::InstanceDef::DropGlue(def_id, ty) => { // FIXME(#91576): Drop shims for generators aren't subject to the MIR passes at the end // of this function. Is this intentional? - if let Some(ty::Generator(gen_def_id, substs, _)) = ty.map(Ty::kind) { + if let Some(ty::Generator(gen_def_id, args, _)) = ty.map(Ty::kind) { let body = tcx.optimized_mir(*gen_def_id).generator_drop().unwrap(); - let body = EarlyBinder(body.clone()).subst(tcx, substs); + let mut body = EarlyBinder::bind(body.clone()).instantiate(tcx, args); debug!("make_shim({:?}) = {:?}", instance, body); + + // Run empty passes to mark phase change and perform validation. + pm::run_passes( + tcx, + &mut body, + &[], + Some(MirPhase::Runtime(RuntimePhase::Optimized)), + ); + return body; } @@ -107,16 +118,26 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<' result } +#[derive(Copy, Clone, Debug, PartialEq)] +enum DerefSource { + /// `fn shim(&self) { inner(*self )}`. + ImmRef, + /// `fn shim(&mut self) { inner(*self )}`. + MutRef, + /// `fn shim(*mut self) { inner(*self )}`. + MutPtr, +} + #[derive(Copy, Clone, Debug, PartialEq)] enum Adjustment { /// Pass the receiver as-is. Identity, - /// We get passed `&[mut] self` and call the target with `*self`. + /// We get passed a reference or a raw pointer to `self` and call the target with `*self`. /// /// This either copies `self` (if `Self: Copy`, eg. for function items), or moves out of it /// (for `VTableShim`, which effectively is passed `&own Self`). - Deref, + Deref { source: DerefSource }, /// We get passed `self: Self` and call the target with `&mut self`. /// @@ -148,12 +169,12 @@ fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option>) assert!(!matches!(ty, Some(ty) if ty.is_generator())); - let substs = if let Some(ty) = ty { - tcx.mk_substs(&[ty.into()]) + let args = if let Some(ty) = ty { + tcx.mk_args(&[ty.into()]) } else { - InternalSubsts::identity_for_item(tcx, def_id) + GenericArgs::identity_for_item(tcx, def_id) }; - let sig = tcx.fn_sig(def_id).subst(tcx, substs); + let sig = tcx.fn_sig(def_id).instantiate(tcx, args); let sig = tcx.erase_late_bound_regions(sig); let span = tcx.def_span(def_id); @@ -188,7 +209,7 @@ fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option>) // has been put into the body. let reborrow = Rvalue::Ref( tcx.lifetimes.re_erased, - BorrowKind::Mut { allow_two_phase_borrow: false }, + BorrowKind::Mut { kind: MutBorrowKind::Default }, tcx.mk_place_deref(dropee_ptr), ); let ref_ty = reborrow.ty(body.local_decls(), tcx); @@ -365,12 +386,10 @@ fn build_clone_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) - match self_ty.kind() { _ if is_copy => builder.copy_shim(), - ty::Closure(_, substs) => { - builder.tuple_like_shim(dest, src, substs.as_closure().upvar_tys()) - } + ty::Closure(_, args) => builder.tuple_like_shim(dest, src, args.as_closure().upvar_tys()), ty::Tuple(..) => builder.tuple_like_shim(dest, src, self_ty.tuple_fields()), - ty::Generator(gen_def_id, substs, hir::Movability::Movable) => { - builder.generator_shim(dest, src, *gen_def_id, substs.as_generator()) + ty::Generator(gen_def_id, args, hir::Movability::Movable) => { + builder.generator_shim(dest, src, *gen_def_id, args.as_generator()) } _ => bug!("clone shim for `{:?}` which is not `Copy` and is not an aggregate", self_ty), }; @@ -392,7 +411,7 @@ impl<'tcx> CloneShimBuilder<'tcx> { // we must subst the self_ty because it's // otherwise going to be TySelf and we can't index // or access fields of a Place of type TySelf. - let sig = tcx.fn_sig(def_id).subst(tcx, &[self_ty.into()]); + let sig = tcx.fn_sig(def_id).instantiate(tcx, &[self_ty.into()]); let sig = tcx.erase_late_bound_regions(sig); let span = tcx.def_span(def_id); @@ -473,7 +492,7 @@ impl<'tcx> CloneShimBuilder<'tcx> { let tcx = self.tcx; // `func == Clone::clone(&ty) -> ty` - let func_ty = tcx.mk_fn_def(self.def_id, [ty]); + let func_ty = Ty::new_fn_def(tcx, self.def_id, [ty]); let func = Operand::Constant(Box::new(Constant { span: self.span, user_ty: None, @@ -482,7 +501,11 @@ impl<'tcx> CloneShimBuilder<'tcx> { let ref_loc = self.make_place( Mutability::Not, - tcx.mk_ref(tcx.lifetimes.re_erased, ty::TypeAndMut { ty, mutbl: hir::Mutability::Not }), + Ty::new_ref( + tcx, + tcx.lifetimes.re_erased, + ty::TypeAndMut { ty, mutbl: hir::Mutability::Not }, + ), ); // `let ref_loc: &ty = &src;` @@ -500,7 +523,7 @@ impl<'tcx> CloneShimBuilder<'tcx> { destination: dest, target: Some(next), unwind: UnwindAction::Cleanup(cleanup), - from_hir_call: true, + call_source: CallSource::Normal, fn_span: self.span, }, false, @@ -544,6 +567,7 @@ impl<'tcx> CloneShimBuilder<'tcx> { place: dest_field, target: unwind, unwind: UnwindAction::Terminate, + replace: false, }, true, ); @@ -570,17 +594,17 @@ impl<'tcx> CloneShimBuilder<'tcx> { dest: Place<'tcx>, src: Place<'tcx>, gen_def_id: DefId, - substs: GeneratorSubsts<'tcx>, + args: GeneratorArgs<'tcx>, ) { self.block(vec![], TerminatorKind::Goto { target: self.block_index_offset(3) }, false); let unwind = self.block(vec![], TerminatorKind::Resume, true); // This will get overwritten with a switch once we know the target blocks let switch = self.block(vec![], TerminatorKind::Unreachable, false); - let unwind = self.clone_fields(dest, src, switch, unwind, substs.upvar_tys()); + let unwind = self.clone_fields(dest, src, switch, unwind, args.upvar_tys()); let target = self.block(vec![], TerminatorKind::Return, false); let unreachable = self.block(vec![], TerminatorKind::Unreachable, false); - let mut cases = Vec::with_capacity(substs.state_tys(gen_def_id, self.tcx).count()); - for (index, state_tys) in substs.state_tys(gen_def_id, self.tcx).enumerate() { + let mut cases = Vec::with_capacity(args.state_tys(gen_def_id, self.tcx).count()); + for (index, state_tys) in args.state_tys(gen_def_id, self.tcx).enumerate() { let variant_index = VariantIdx::new(index); let dest = self.tcx.mk_place_downcast_unnamed(dest, variant_index); let src = self.tcx.mk_place_downcast_unnamed(src, variant_index); @@ -596,7 +620,7 @@ impl<'tcx> CloneShimBuilder<'tcx> { cases.push((index as u128, start_block)); let _final_cleanup_block = self.clone_fields(dest, src, target, unwind, state_tys); } - let discr_ty = substs.discr_ty(self.tcx); + let discr_ty = args.discr_ty(self.tcx); let temp = self.make_place(Mutability::Mut, discr_ty); let rvalue = Rvalue::Discriminant(src); let statement = self.make_statement(StatementKind::Assign(Box::new((temp, rvalue)))); @@ -625,13 +649,13 @@ fn build_call_shim<'tcx>( // `FnPtrShim` contains the fn pointer type that a call shim is being built for - this is used // to substitute into the signature of the shim. It is not necessary for users of this // MIR body to perform further substitutions (see `InstanceDef::has_polymorphic_mir_body`). - let (sig_substs, untuple_args) = if let ty::InstanceDef::FnPtrShim(_, ty) = instance { + let (sig_args, untuple_args) = if let ty::InstanceDef::FnPtrShim(_, ty) = instance { let sig = tcx.erase_late_bound_regions(ty.fn_sig(tcx)); let untuple_args = sig.inputs(); // Create substitutions for the `Self` and `Args` generic parameters of the shim body. - let arg_tup = tcx.mk_tup(untuple_args); + let arg_tup = Ty::new_tup(tcx, untuple_args); (Some([ty.into(), arg_tup.into()]), Some(untuple_args)) } else { @@ -642,9 +666,12 @@ fn build_call_shim<'tcx>( let sig = tcx.fn_sig(def_id); let sig = sig.map_bound(|sig| tcx.erase_late_bound_regions(sig)); - assert_eq!(sig_substs.is_some(), !instance.has_polymorphic_mir_body()); - let mut sig = - if let Some(sig_substs) = sig_substs { sig.subst(tcx, &sig_substs) } else { sig.0 }; + assert_eq!(sig_args.is_some(), !instance.has_polymorphic_mir_body()); + let mut sig = if let Some(sig_args) = sig_args { + sig.instantiate(tcx, &sig_args) + } else { + sig.instantiate_identity() + }; if let CallKind::Indirect(fnty) = call_kind { // `sig` determines our local decls, and thus the callee type in the `Call` terminator. This @@ -663,8 +690,12 @@ fn build_call_shim<'tcx>( let self_arg = &mut inputs_and_output[0]; *self_arg = match rcvr_adjustment.unwrap() { Adjustment::Identity => fnty, - Adjustment::Deref => tcx.mk_imm_ptr(fnty), - Adjustment::RefMut => tcx.mk_mut_ptr(fnty), + Adjustment::Deref { source } => match source { + DerefSource::ImmRef => Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fnty), + DerefSource::MutRef => Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, fnty), + DerefSource::MutPtr => Ty::new_mut_ptr(tcx, fnty), + }, + Adjustment::RefMut => bug!("`RefMut` is never used with indirect calls: {instance:?}"), }; sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output); } @@ -676,7 +707,7 @@ fn build_call_shim<'tcx>( let mut inputs_and_output = sig.inputs_and_output.to_vec(); let self_arg = &mut inputs_and_output[0]; debug_assert!(tcx.generics_of(def_id).has_self && *self_arg == tcx.types.self_param); - *self_arg = tcx.mk_mut_ptr(*self_arg); + *self_arg = Ty::new_mut_ptr(tcx, *self_arg); sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output); } @@ -695,12 +726,13 @@ fn build_call_shim<'tcx>( let rcvr = rcvr_adjustment.map(|rcvr_adjustment| match rcvr_adjustment { Adjustment::Identity => Operand::Move(rcvr_place()), - Adjustment::Deref => Operand::Move(tcx.mk_place_deref(rcvr_place())), + Adjustment::Deref { source: _ } => Operand::Move(tcx.mk_place_deref(rcvr_place())), Adjustment::RefMut => { // let rcvr = &mut rcvr; let ref_rcvr = local_decls.push( LocalDecl::new( - tcx.mk_ref( + Ty::new_ref( + tcx, tcx.lifetimes.re_erased, ty::TypeAndMut { ty: sig.inputs()[0], mutbl: hir::Mutability::Mut }, ), @@ -708,7 +740,7 @@ fn build_call_shim<'tcx>( ) .immutable(), ); - let borrow_kind = BorrowKind::Mut { allow_two_phase_borrow: false }; + let borrow_kind = BorrowKind::Mut { kind: MutBorrowKind::Default }; statements.push(Statement { source_info, kind: StatementKind::Assign(Box::new(( @@ -726,7 +758,7 @@ fn build_call_shim<'tcx>( // `FnDef` call with optional receiver. CallKind::Direct(def_id) => { - let ty = tcx.type_of(def_id).subst_identity(); + let ty = tcx.type_of(def_id).instantiate_identity(); ( Operand::Constant(Box::new(Constant { span, @@ -785,7 +817,7 @@ fn build_call_shim<'tcx>( } else { UnwindAction::Continue }, - from_hir_call: true, + call_source: CallSource::Misc, fn_span: span, }, false, @@ -800,6 +832,7 @@ fn build_call_shim<'tcx>( place: rcvr_place(), target: BasicBlock::new(2), unwind: UnwindAction::Continue, + replace: false, }, false, ); @@ -815,6 +848,7 @@ fn build_call_shim<'tcx>( place: rcvr_place(), target: BasicBlock::new(4), unwind: UnwindAction::Terminate, + replace: false, }, true, ); @@ -841,12 +875,12 @@ pub fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> Body<'_> { // Normalize the sig. let sig = tcx .fn_sig(ctor_id) - .subst_identity() + .instantiate_identity() .no_bound_vars() .expect("LBR in ADT constructor signature"); let sig = tcx.normalize_erasing_regions(param_env, sig); - let ty::Adt(adt_def, substs) = sig.output().kind() else { + let ty::Adt(adt_def, args) = sig.output().kind() else { bug!("unexpected type for ADT ctor {:?}", sig.output()); }; @@ -869,7 +903,7 @@ pub fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> Body<'_> { // return; debug!("build_ctor: variant_index={:?}", variant_index); - let kind = AggregateKind::Adt(adt_def.did(), variant_index, substs, None, None); + let kind = AggregateKind::Adt(adt_def.did(), variant_index, args, None, None); let variant = adt_def.variant(variant_index); let statement = Statement { kind: StatementKind::Assign(Box::new(( @@ -914,7 +948,7 @@ pub fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> Body<'_> { fn build_fn_ptr_addr_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> Body<'tcx> { assert!(matches!(self_ty.kind(), ty::FnPtr(..)), "expected fn ptr, found {self_ty}"); let span = tcx.def_span(def_id); - let Some(sig) = tcx.fn_sig(def_id).subst(tcx, &[self_ty.into()]).no_bound_vars() else { + let Some(sig) = tcx.fn_sig(def_id).instantiate(tcx, &[self_ty.into()]).no_bound_vars() else { span_bug!(span, "FnPtr::addr with bound vars for `{self_ty}`"); }; let locals = local_decls_for_sig(&sig, span); @@ -924,7 +958,7 @@ fn build_fn_ptr_addr_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'t let rvalue = Rvalue::Cast( CastKind::FnPtrToPtr, Operand::Move(Place::from(Local::new(1))), - tcx.mk_imm_ptr(tcx.types.unit), + Ty::new_imm_ptr(tcx, tcx.types.unit), ); let stmt = Statement { source_info, diff --git a/compiler/rustc_mir_transform/src/simplify.rs b/compiler/rustc_mir_transform/src/simplify.rs index e59219321b7ff..b7a51cfd61966 100644 --- a/compiler/rustc_mir_transform/src/simplify.rs +++ b/compiler/rustc_mir_transform/src/simplify.rs @@ -199,7 +199,8 @@ impl<'a, 'tcx> CfgSimplifier<'a, 'tcx> { let last = current; *start = last; while let Some((current, mut terminator)) = terminators.pop() { - let Terminator { kind: TerminatorKind::Goto { ref mut target }, .. } = terminator else { + let Terminator { kind: TerminatorKind::Goto { ref mut target }, .. } = terminator + else { unreachable!(); }; *changed |= *target != last; diff --git a/compiler/rustc_mir_transform/src/sroa.rs b/compiler/rustc_mir_transform/src/sroa.rs index 2d77291293d53..e66ae8ff8845f 100644 --- a/compiler/rustc_mir_transform/src/sroa.rs +++ b/compiler/rustc_mir_transform/src/sroa.rs @@ -6,23 +6,29 @@ use rustc_middle::mir::visit::*; use rustc_middle::mir::*; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_mir_dataflow::value_analysis::{excluded_locals, iter_fields}; -use rustc_target::abi::FieldIdx; +use rustc_target::abi::{FieldIdx, ReprFlags, FIRST_VARIANT}; pub struct ScalarReplacementOfAggregates; impl<'tcx> MirPass<'tcx> for ScalarReplacementOfAggregates { fn is_enabled(&self, sess: &rustc_session::Session) -> bool { - sess.mir_opt_level() >= 3 + sess.mir_opt_level() >= 2 } #[instrument(level = "debug", skip(self, tcx, body))] fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { debug!(def_id = ?body.source.def_id()); + + // Avoid query cycles (generators require optimized MIR for layout). + if tcx.type_of(body.source.def_id()).instantiate_identity().is_generator() { + return; + } + let mut excluded = excluded_locals(body); let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id()); loop { debug!(?excluded); - let escaping = escaping_locals(&excluded, body); + let escaping = escaping_locals(tcx, param_env, &excluded, body); debug!(?escaping); let replacements = compute_flattening(tcx, param_env, body, escaping); debug!(?replacements); @@ -48,11 +54,45 @@ impl<'tcx> MirPass<'tcx> for ScalarReplacementOfAggregates { /// - the locals is a union or an enum; /// - the local's address is taken, and thus the relative addresses of the fields are observable to /// client code. -fn escaping_locals(excluded: &BitSet, body: &Body<'_>) -> BitSet { +fn escaping_locals<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + excluded: &BitSet, + body: &Body<'tcx>, +) -> BitSet { + let is_excluded_ty = |ty: Ty<'tcx>| { + if ty.is_union() || ty.is_enum() { + return true; + } + if let ty::Adt(def, _args) = ty.kind() { + if def.repr().flags.contains(ReprFlags::IS_SIMD) { + // Exclude #[repr(simd)] types so that they are not de-optimized into an array + return true; + } + // We already excluded unions and enums, so this ADT must have one variant + let variant = def.variant(FIRST_VARIANT); + if variant.fields.len() > 1 { + // If this has more than one field, it cannot be a wrapper that only provides a + // niche, so we do not want to automatically exclude it. + return false; + } + let Ok(layout) = tcx.layout_of(param_env.and(ty)) else { + // We can't get the layout + return true; + }; + if layout.layout.largest_niche().is_some() { + // This type has a niche + return true; + } + } + // Default for non-ADTs + false + }; + let mut set = BitSet::new_empty(body.local_decls.len()); set.insert_range(RETURN_PLACE..=Local::from_usize(body.arg_count)); for (local, decl) in body.local_decls().iter_enumerated() { - if decl.ty.is_union() || decl.ty.is_enum() || excluded.contains(local) { + if excluded.contains(local) || is_excluded_ty(decl.ty) { set.insert(local); } } @@ -121,7 +161,9 @@ struct ReplacementMap<'tcx> { impl<'tcx> ReplacementMap<'tcx> { fn replace_place(&self, tcx: TyCtxt<'tcx>, place: PlaceRef<'tcx>) -> Option> { - let &[PlaceElem::Field(f, _), ref rest @ ..] = place.projection else { return None; }; + let &[PlaceElem::Field(f, _), ref rest @ ..] = place.projection else { + return None; + }; let fields = self.fragments[place.local].as_ref()?; let (_, new_local) = fields[f]?; Some(Place { local: new_local, projection: tcx.mk_place_elems(&rest) }) @@ -396,13 +438,12 @@ impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> { VarDebugInfoContents::Composite { ty: _, ref mut fragments } => { let mut new_fragments = Vec::new(); debug!(?fragments); - fragments - .drain_filter(|fragment| { - if let Some(repl) = + fragments.retain_mut(|fragment| { + if let Some(repl) = self.replacements.replace_place(self.tcx, fragment.contents.as_ref()) { fragment.contents = repl; - false + true } else if let Some(local) = fragment.contents.as_local() && let Some(frg) = self.gather_debug_info_fragments(local) { @@ -410,12 +451,11 @@ impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> { f.projection.splice(0..0, fragment.projection.iter().copied()); f })); - true - } else { false + } else { + true } - }) - .for_each(drop); + }); debug!(?fragments); debug!(?new_fragments); fragments.extend(new_fragments); diff --git a/compiler/rustc_mir_transform/src/ssa.rs b/compiler/rustc_mir_transform/src/ssa.rs index e8e4246b7970f..04bc461c815cd 100644 --- a/compiler/rustc_mir_transform/src/ssa.rs +++ b/compiler/rustc_mir_transform/src/ssa.rs @@ -216,7 +216,6 @@ impl<'tcx> Visitor<'tcx> for SsaVisitor<'_> { PlaceContext::NonMutatingUse( NonMutatingUseContext::SharedBorrow | NonMutatingUseContext::ShallowBorrow - | NonMutatingUseContext::UniqueBorrow | NonMutatingUseContext::AddressOf, ) | PlaceContext::MutatingUse(_) => { @@ -267,11 +266,21 @@ fn compute_copy_classes(ssa: &mut SsaLocals, body: &Body<'_>) { let mut copies = IndexVec::from_fn_n(|l| l, body.local_decls.len()); for (local, rvalue, _) in ssa.assignments(body) { - let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) | Rvalue::CopyForDeref(place)) - = rvalue - else { continue }; + let (Rvalue::Use(Operand::Copy(place) | Operand::Move(place)) + | Rvalue::CopyForDeref(place)) = rvalue + else { + continue; + }; let Some(rhs) = place.as_local() else { continue }; + let local_ty = body.local_decls()[local].ty; + let rhs_ty = body.local_decls()[rhs].ty; + if local_ty != rhs_ty { + // FIXME(#112651): This can be removed afterwards. + trace!("skipped `{local:?} = {rhs:?}` due to subtyping: {local_ty} != {rhs_ty}"); + continue; + } + if !ssa.is_ssa(rhs) { continue; } diff --git a/compiler/rustc_mir_transform/src/uninhabited_enum_branching.rs b/compiler/rustc_mir_transform/src/uninhabited_enum_branching.rs index 5389b9f52eb2d..092bcb5c97930 100644 --- a/compiler/rustc_mir_transform/src/uninhabited_enum_branching.rs +++ b/compiler/rustc_mir_transform/src/uninhabited_enum_branching.rs @@ -105,7 +105,8 @@ impl<'tcx> MirPass<'tcx> for UninhabitedEnumBranching { for bb in body.basic_blocks.indices() { trace!("processing block {:?}", bb); - let Some(discriminant_ty) = get_switched_on_type(&body.basic_blocks[bb], tcx, body) else { + let Some(discriminant_ty) = get_switched_on_type(&body.basic_blocks[bb], tcx, body) + else { continue; }; diff --git a/compiler/rustc_monomorphize/messages.ftl b/compiler/rustc_monomorphize/messages.ftl index 6cea6a603f336..fdd47e6f79bd8 100644 --- a/compiler/rustc_monomorphize/messages.ftl +++ b/compiler/rustc_monomorphize/messages.ftl @@ -1,32 +1,32 @@ -monomorphize_recursion_limit = - reached the recursion limit while instantiating `{$shrunk}` - .note = `{$def_path_str}` defined here - -monomorphize_written_to_path = the full type name has been written to '{$path}' - -monomorphize_type_length_limit = reached the type-length limit while instantiating `{$shrunk}` - monomorphize_consider_type_length_limit = consider adding a `#![type_length_limit="{$type_length}"]` attribute to your crate -monomorphize_fatal_error = {$error_message} - -monomorphize_unknown_partition_strategy = unknown partitioning strategy +monomorphize_couldnt_dump_mono_stats = + unexpected error occurred while dumping monomorphization stats: {$error} -monomorphize_symbol_already_defined = symbol `{$symbol}` is already defined +monomorphize_encountered_error_while_instantiating = + the above error was encountered while instantiating `{$formatted_item}` -monomorphize_unused_generic_params = item has unused generic parameters +monomorphize_fatal_error = {$error_message} monomorphize_large_assignments = moving {$size} bytes .label = value moved from here .note = The current maximum size is {$limit}, but it can be customized with the move_size_limit attribute: `#![move_size_limit = "..."]` -monomorphize_couldnt_dump_mono_stats = - unexpected error occurred while dumping monomorphization stats: {$error} +monomorphize_recursion_limit = + reached the recursion limit while instantiating `{$shrunk}` + .note = `{$def_path_str}` defined here -monomorphize_encountered_error_while_instantiating = - the above error was encountered while instantiating `{$formatted_item}` +monomorphize_symbol_already_defined = symbol `{$symbol}` is already defined + +monomorphize_type_length_limit = reached the type-length limit while instantiating `{$shrunk}` monomorphize_unknown_cgu_collection_mode = unknown codegen-item collection mode '{$mode}', falling back to 'lazy' mode + +monomorphize_unknown_partition_strategy = unknown partitioning strategy + +monomorphize_unused_generic_params = item has unused generic parameters + +monomorphize_written_to_path = the full type name has been written to '{$path}' diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 35b154b7b34d1..55b14ce1c3ee0 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -35,15 +35,15 @@ //! //! - A "mono item" is something that results in a function or global in //! the LLVM IR of a codegen unit. Mono items do not stand on their -//! own, they can reference other mono items. For example, if function +//! own, they can use other mono items. For example, if function //! `foo()` calls function `bar()` then the mono item for `foo()` -//! references the mono item for function `bar()`. In general, the -//! definition for mono item A referencing a mono item B is that -//! the LLVM artifact produced for A references the LLVM artifact produced +//! uses the mono item for function `bar()`. In general, the +//! definition for mono item A using a mono item B is that +//! the LLVM artifact produced for A uses the LLVM artifact produced //! for B. //! -//! - Mono items and the references between them form a directed graph, -//! where the mono items are the nodes and references form the edges. +//! - Mono items and the uses between them form a directed graph, +//! where the mono items are the nodes and uses form the edges. //! Let's call this graph the "mono item graph". //! //! - The mono item graph for a program contains all mono items @@ -53,12 +53,11 @@ //! mono item graph for the current crate. It runs in two phases: //! //! 1. Discover the roots of the graph by traversing the HIR of the crate. -//! 2. Starting from the roots, find neighboring nodes by inspecting the MIR +//! 2. Starting from the roots, find uses by inspecting the MIR //! representation of the item corresponding to a given node, until no more //! new nodes are found. //! //! ### Discovering roots -//! //! The roots of the mono item graph correspond to the public non-generic //! syntactic items in the source code. We find them by walking the HIR of the //! crate, and whenever we hit upon a public function, method, or static item, @@ -69,25 +68,23 @@ //! specified. Functions marked `#[no_mangle]` and functions called by inlinable //! functions also always act as roots.) //! -//! ### Finding neighbor nodes -//! Given a mono item node, we can discover neighbors by inspecting its -//! MIR. We walk the MIR and any time we hit upon something that signifies a -//! reference to another mono item, we have found a neighbor. Since the -//! mono item we are currently at is always monomorphic, we also know the -//! concrete type arguments of its neighbors, and so all neighbors again will be -//! monomorphic. The specific forms a reference to a neighboring node can take -//! in MIR are quite diverse. Here is an overview: +//! ### Finding uses +//! Given a mono item node, we can discover uses by inspecting its MIR. We walk +//! the MIR to find other mono items used by each mono item. Since the mono +//! item we are currently at is always monomorphic, we also know the concrete +//! type arguments of its used mono items. The specific forms a use can take in +//! MIR are quite diverse. Here is an overview: //! //! #### Calling Functions/Methods -//! The most obvious form of one mono item referencing another is a +//! The most obvious way for one mono item to use another is a //! function or method call (represented by a CALL terminator in MIR). But -//! calls are not the only thing that might introduce a reference between two +//! calls are not the only thing that might introduce a use between two //! function mono items, and as we will see below, they are just a //! specialization of the form described next, and consequently will not get any //! special treatment in the algorithm. //! //! #### Taking a reference to a function or method -//! A function does not need to actually be called in order to be a neighbor of +//! A function does not need to actually be called in order to be used by //! another function. It suffices to just take a reference in order to introduce //! an edge. Consider the following example: //! @@ -109,29 +106,23 @@ //! The MIR of none of these functions will contain an explicit call to //! `print_val::`. Nonetheless, in order to mono this program, we need //! an instance of this function. Thus, whenever we encounter a function or -//! method in operand position, we treat it as a neighbor of the current +//! method in operand position, we treat it as a use of the current //! mono item. Calls are just a special case of that. //! //! #### Drop glue //! Drop glue mono items are introduced by MIR drop-statements. The -//! generated mono item will again have drop-glue item neighbors if the +//! generated mono item will have additional drop-glue item uses if the //! type to be dropped contains nested values that also need to be dropped. It -//! might also have a function item neighbor for the explicit `Drop::drop` +//! might also have a function item use for the explicit `Drop::drop` //! implementation of its type. //! //! #### Unsizing Casts -//! A subtle way of introducing neighbor edges is by casting to a trait object. +//! A subtle way of introducing use edges is by casting to a trait object. //! Since the resulting fat-pointer contains a reference to a vtable, we need to //! instantiate all object-safe methods of the trait, as we need to store //! pointers to these functions even if they never get called anywhere. This can //! be seen as a special case of taking a function reference. //! -//! #### Boxes -//! Since `Box` expression have special compiler support, no explicit calls to -//! `exchange_malloc()` and `box_free()` may show up in MIR, even if the -//! compiler will generate them. We have to observe `Rvalue::Box` expressions -//! and Box-typed drop-statements for that purpose. -//! //! //! Interaction with Cross-Crate Inlining //! ------------------------------------- @@ -151,7 +142,7 @@ //! Mono item collection can be performed in one of two modes: //! //! - Lazy mode means that items will only be instantiated when actually -//! referenced. The goal is to produce the least amount of machine code +//! used. The goal is to produce the least amount of machine code //! possible. //! //! - Eager mode is meant to be used in conjunction with incremental compilation @@ -179,27 +170,25 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId}; use rustc_hir::lang_items::LangItem; -use rustc_index::bit_set::GrowableBitSet; use rustc_middle::mir::interpret::{AllocId, ConstValue}; use rustc_middle::mir::interpret::{ErrorHandled, GlobalAlloc, Scalar}; use rustc_middle::mir::mono::{InstantiationMode, MonoItem}; use rustc_middle::mir::visit::Visitor as MirVisitor; use rustc_middle::mir::{self, Local, Location}; use rustc_middle::query::TyCtxtAt; -use rustc_middle::ty::adjustment::{CustomCoerceUnsized, PointerCast}; +use rustc_middle::ty::adjustment::{CustomCoerceUnsized, PointerCoercion}; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts}; use rustc_middle::ty::{ self, GenericParamDefKind, Instance, InstanceDef, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, VtblEntry, }; +use rustc_middle::ty::{GenericArgKind, GenericArgs}; use rustc_middle::{middle::codegen_fn_attrs::CodegenFnAttrFlags, mir::visit::TyContext}; use rustc_session::config::EntryFnType; use rustc_session::lint::builtin::LARGE_ASSIGNMENTS; use rustc_session::Limit; use rustc_span::source_map::{dummy_spanned, respan, Span, Spanned, DUMMY_SP}; use rustc_target::abi::Size; -use std::ops::Range; use std::path::PathBuf; use crate::errors::{ @@ -212,114 +201,51 @@ pub enum MonoItemCollectionMode { Lazy, } -/// Maps every mono item to all mono items it references in its -/// body. -pub struct InliningMap<'tcx> { - // Maps a source mono item to the range of mono items - // accessed by it. - // The range selects elements within the `targets` vecs. - index: FxHashMap, Range>, - targets: Vec>, - - // Contains one bit per mono item in the `targets` field. That bit - // is true if that mono item needs to be inlined into every CGU. - inlines: GrowableBitSet, -} - -/// Struct to store mono items in each collecting and if they should -/// be inlined. We call `instantiation_mode` to get their inlining -/// status when inserting new elements, which avoids calling it in -/// `inlining_map.lock_mut()`. See the `collect_items_rec` implementation -/// below. -struct MonoItems<'tcx> { - // If this is false, we do not need to compute whether items - // will need to be inlined. - compute_inlining: bool, - - // The TyCtxt used to determine whether the a item should - // be inlined. - tcx: TyCtxt<'tcx>, +pub struct UsageMap<'tcx> { + // Maps every mono item to the mono items used by it. + used_map: FxHashMap, Vec>>, - // The collected mono items. The bool field in each element - // indicates whether this element should be inlined. - items: Vec<(Spanned>, bool /*inlined*/)>, + // Maps every mono item to the mono items that use it. + user_map: FxHashMap, Vec>>, } -impl<'tcx> MonoItems<'tcx> { - #[inline] - fn push(&mut self, item: Spanned>) { - self.extend([item]); - } - - #[inline] - fn extend>>>(&mut self, iter: T) { - self.items.extend(iter.into_iter().map(|mono_item| { - let inlined = if !self.compute_inlining { - false - } else { - mono_item.node.instantiation_mode(self.tcx) == InstantiationMode::LocalCopy - }; - (mono_item, inlined) - })) - } -} +type MonoItems<'tcx> = Vec>>; -impl<'tcx> InliningMap<'tcx> { - fn new() -> InliningMap<'tcx> { - InliningMap { - index: FxHashMap::default(), - targets: Vec::new(), - inlines: GrowableBitSet::with_capacity(1024), - } +impl<'tcx> UsageMap<'tcx> { + fn new() -> UsageMap<'tcx> { + UsageMap { used_map: FxHashMap::default(), user_map: FxHashMap::default() } } - fn record_accesses<'a>( + fn record_used<'a>( &mut self, - source: MonoItem<'tcx>, - new_targets: &'a [(Spanned>, bool)], + user_item: MonoItem<'tcx>, + used_items: &'a [Spanned>], ) where 'tcx: 'a, { - let start_index = self.targets.len(); - let new_items_count = new_targets.len(); - let new_items_count_total = new_items_count + self.targets.len(); - - self.targets.reserve(new_items_count); - self.inlines.ensure(new_items_count_total); - - for (i, (Spanned { node: mono_item, .. }, inlined)) in new_targets.into_iter().enumerate() { - self.targets.push(*mono_item); - if *inlined { - self.inlines.insert(i + start_index); - } + let used_items: Vec<_> = used_items.iter().map(|item| item.node).collect(); + for &used_item in used_items.iter() { + self.user_map.entry(used_item).or_default().push(user_item); } - let end_index = self.targets.len(); - assert!(self.index.insert(source, start_index..end_index).is_none()); + assert!(self.used_map.insert(user_item, used_items).is_none()); } - /// Internally iterate over all items referenced by `source` which will be - /// made available for inlining. - pub fn with_inlining_candidates(&self, source: MonoItem<'tcx>, mut f: F) - where - F: FnMut(MonoItem<'tcx>), - { - if let Some(range) = self.index.get(&source) { - for (i, candidate) in self.targets[range.clone()].iter().enumerate() { - if self.inlines.contains(range.start + i) { - f(*candidate); - } - } - } + pub fn get_user_items(&self, item: MonoItem<'tcx>) -> &[MonoItem<'tcx>] { + self.user_map.get(&item).map(|items| items.as_slice()).unwrap_or(&[]) } - /// Internally iterate over all items and the things each accesses. - pub fn iter_accesses(&self, mut f: F) + /// Internally iterate over all inlined items used by `item`. + pub fn for_each_inlined_used_item(&self, tcx: TyCtxt<'tcx>, item: MonoItem<'tcx>, mut f: F) where - F: FnMut(MonoItem<'tcx>, &[MonoItem<'tcx>]), + F: FnMut(MonoItem<'tcx>), { - for (&accessor, range) in &self.index { - f(accessor, &self.targets[range.clone()]) + let used_items = self.used_map.get(&item).unwrap(); + for used_item in used_items.iter() { + let is_inlined = used_item.instantiation_mode(tcx) == InstantiationMode::LocalCopy; + if is_inlined { + f(*used_item); + } } } } @@ -328,7 +254,7 @@ impl<'tcx> InliningMap<'tcx> { pub fn collect_crate_mono_items( tcx: TyCtxt<'_>, mode: MonoItemCollectionMode, -) -> (FxHashSet>, InliningMap<'_>) { +) -> (FxHashSet>, UsageMap<'_>) { let _prof_timer = tcx.prof.generic_activity("monomorphization_collector"); let roots = @@ -337,12 +263,12 @@ pub fn collect_crate_mono_items( debug!("building mono item graph, beginning at roots"); let mut visited = MTLock::new(FxHashSet::default()); - let mut inlining_map = MTLock::new(InliningMap::new()); + let mut usage_map = MTLock::new(UsageMap::new()); let recursion_limit = tcx.recursion_limit(); { let visited: MTLockRef<'_, _> = &mut visited; - let inlining_map: MTLockRef<'_, _> = &mut inlining_map; + let usage_map: MTLockRef<'_, _> = &mut usage_map; tcx.sess.time("monomorphization_collector_graph_walk", || { par_for_each_in(roots, |root| { @@ -353,13 +279,13 @@ pub fn collect_crate_mono_items( visited, &mut recursion_depths, recursion_limit, - inlining_map, + usage_map, ); }); }); } - (visited.into_inner(), inlining_map.into_inner()) + (visited.into_inner(), usage_map.into_inner()) } // Find all non-generic items by walking the HIR. These items serve as roots to @@ -367,7 +293,7 @@ pub fn collect_crate_mono_items( #[instrument(skip(tcx, mode), level = "debug")] fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionMode) -> Vec> { debug!("collecting roots"); - let mut roots = MonoItems { compute_inlining: false, tcx, items: Vec::new() }; + let mut roots = Vec::new(); { let entry_fn = tcx.entry_fn(()); @@ -393,9 +319,8 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionMode) -> Vec, mode: MonoItemCollectionMode) -> Vec( tcx: TyCtxt<'tcx>, - starting_point: Spanned>, + starting_item: Spanned>, visited: MTLockRef<'_, FxHashSet>>, recursion_depths: &mut DefIdMap, recursion_limit: Limit, - inlining_map: MTLockRef<'_, InliningMap<'tcx>>, + usage_map: MTLockRef<'_, UsageMap<'tcx>>, ) { - if !visited.lock_mut().insert(starting_point.node) { + if !visited.lock_mut().insert(starting_item.node) { // We've been here already, no need to search again. return; } - let mut neighbors = MonoItems { compute_inlining: true, tcx, items: Vec::new() }; + let mut used_items = Vec::new(); let recursion_depth_reset; - // // Post-monomorphization errors MVP // // We can encounter errors while monomorphizing an item, but we don't have a good way of @@ -446,7 +370,7 @@ fn collect_items_rec<'tcx>( // FIXME: don't rely on global state, instead bubble up errors. Note: this is very hard to do. let error_count = tcx.sess.diagnostic().err_count(); - match starting_point.node { + match starting_item.node { MonoItem::Static(def_id) => { let instance = Instance::mono(tcx, def_id); @@ -454,22 +378,22 @@ fn collect_items_rec<'tcx>( debug_assert!(should_codegen_locally(tcx, &instance)); let ty = instance.ty(tcx, ty::ParamEnv::reveal_all()); - visit_drop_use(tcx, ty, true, starting_point.span, &mut neighbors); + visit_drop_use(tcx, ty, true, starting_item.span, &mut used_items); recursion_depth_reset = None; if let Ok(alloc) = tcx.eval_static_initializer(def_id) { for &id in alloc.inner().provenance().ptrs().values() { - collect_miri(tcx, id, &mut neighbors); + collect_alloc(tcx, id, &mut used_items); } } if tcx.needs_thread_local_shim(def_id) { - neighbors.push(respan( - starting_point.span, + used_items.push(respan( + starting_item.span, MonoItem::Fn(Instance { def: InstanceDef::ThreadLocalShim(def_id), - substs: InternalSubsts::empty(), + args: GenericArgs::empty(), }), )); } @@ -482,14 +406,14 @@ fn collect_items_rec<'tcx>( recursion_depth_reset = Some(check_recursion_limit( tcx, instance, - starting_point.span, + starting_item.span, recursion_depths, recursion_limit, )); check_type_length_limit(tcx, instance); rustc_data_structures::stack::ensure_sufficient_stack(|| { - collect_neighbours(tcx, instance, &mut neighbors); + collect_used_items(tcx, instance, &mut used_items); }); } MonoItem::GlobalAsm(item_id) => { @@ -507,13 +431,13 @@ fn collect_items_rec<'tcx>( hir::InlineAsmOperand::SymFn { anon_const } => { let fn_ty = tcx.typeck_body(anon_const.body).node_type(anon_const.hir_id); - visit_fn_use(tcx, fn_ty, false, *op_sp, &mut neighbors); + visit_fn_use(tcx, fn_ty, false, *op_sp, &mut used_items); } hir::InlineAsmOperand::SymStatic { path: _, def_id } => { let instance = Instance::mono(tcx, *def_id); if should_codegen_locally(tcx, &instance) { trace!("collecting static {:?}", def_id); - neighbors.push(dummy_spanned(MonoItem::Static(*def_id))); + used_items.push(dummy_spanned(MonoItem::Static(*def_id))); } } hir::InlineAsmOperand::In { .. } @@ -533,19 +457,19 @@ fn collect_items_rec<'tcx>( // Check for PMEs and emit a diagnostic if one happened. To try to show relevant edges of the // mono item graph. if tcx.sess.diagnostic().err_count() > error_count - && starting_point.node.is_generic_fn() - && starting_point.node.is_user_defined() + && starting_item.node.is_generic_fn() + && starting_item.node.is_user_defined() { - let formatted_item = with_no_trimmed_paths!(starting_point.node.to_string()); + let formatted_item = with_no_trimmed_paths!(starting_item.node.to_string()); tcx.sess.emit_note(EncounteredErrorWhileInstantiating { - span: starting_point.span, + span: starting_item.span, formatted_item, }); } - inlining_map.lock_mut().record_accesses(starting_point.node, &neighbors.items); + usage_map.lock_mut().record_used(starting_item.node, &used_items); - for (neighbour, _) in neighbors.items { - collect_items_rec(tcx, neighbour, visited, recursion_depths, recursion_limit, inlining_map); + for used_item in used_items { + collect_items_rec(tcx, used_item, visited, recursion_depths, recursion_limit, usage_map); } if let Some((def_id, depth)) = recursion_depth_reset { @@ -631,7 +555,7 @@ fn check_recursion_limit<'tcx>( fn check_type_length_limit<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) { let type_length = instance - .substs + .args .iter() .flat_map(|arg| arg.walk()) .filter(|arg| match arg.unpack() { @@ -661,14 +585,14 @@ fn check_type_length_limit<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) { } } -struct MirNeighborCollector<'a, 'tcx> { +struct MirUsedCollector<'a, 'tcx> { tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, output: &'a mut MonoItems<'tcx>, instance: Instance<'tcx>, } -impl<'a, 'tcx> MirNeighborCollector<'a, 'tcx> { +impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> { pub fn monomorphize(&self, value: T) -> T where T: TypeFoldable>, @@ -677,12 +601,12 @@ impl<'a, 'tcx> MirNeighborCollector<'a, 'tcx> { self.instance.subst_mir_and_normalize_erasing_regions( self.tcx, ty::ParamEnv::reveal_all(), - ty::EarlyBinder(value), + ty::EarlyBinder::bind(value), ) } } -impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { +impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) { debug!("visiting rvalue {:?}", *rvalue); @@ -693,7 +617,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { // have to instantiate all methods of the trait being cast to, so we // can build the appropriate vtable. mir::Rvalue::Cast( - mir::CastKind::Pointer(PointerCast::Unsize), + mir::CastKind::PointerCoercion(PointerCoercion::Unsize), ref operand, target_ty, ) @@ -719,7 +643,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { } } mir::Rvalue::Cast( - mir::CastKind::Pointer(PointerCast::ReifyFnPointer), + mir::CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer), ref operand, _, ) => { @@ -728,18 +652,18 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { visit_fn_use(self.tcx, fn_ty, false, span, &mut self.output); } mir::Rvalue::Cast( - mir::CastKind::Pointer(PointerCast::ClosureFnPointer(_)), + mir::CastKind::PointerCoercion(PointerCoercion::ClosureFnPointer(_)), ref operand, _, ) => { let source_ty = operand.ty(self.body, self.tcx); let source_ty = self.monomorphize(source_ty); match *source_ty.kind() { - ty::Closure(def_id, substs) => { + ty::Closure(def_id, args) => { let instance = Instance::resolve_closure( self.tcx, def_id, - substs, + args, ty::ClosureKind::FnOnce, ) .expect("failed to normalize and resolve closure during codegen"); @@ -951,12 +875,11 @@ fn visit_fn_use<'tcx>( source: Span, output: &mut MonoItems<'tcx>, ) { - if let ty::FnDef(def_id, substs) = *ty.kind() { + if let ty::FnDef(def_id, args) = *ty.kind() { let instance = if is_direct_call { - ty::Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, substs) + ty::Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args) } else { - match ty::Instance::resolve_for_fn_ptr(tcx, ty::ParamEnv::reveal_all(), def_id, substs) - { + match ty::Instance::resolve_for_fn_ptr(tcx, ty::ParamEnv::reveal_all(), def_id, args) { Some(instance) => instance, _ => bug!("failed to resolve instance for {ty}"), } @@ -1119,7 +1042,7 @@ fn find_vtable_types_for_unsizing<'tcx>( // T as dyn* Trait (_, &ty::Dynamic(_, _, ty::DynStar)) => ptr_vtable(source_ty, target_ty), - (&ty::Adt(source_adt_def, source_substs), &ty::Adt(target_adt_def, target_substs)) => { + (&ty::Adt(source_adt_def, source_args), &ty::Adt(target_adt_def, target_args)) => { assert_eq!(source_adt_def, target_adt_def); let CustomCoerceUnsized::Struct(coerce_index) = @@ -1135,8 +1058,8 @@ fn find_vtable_types_for_unsizing<'tcx>( find_vtable_types_for_unsizing( tcx, - source_fields[coerce_index].ty(*tcx, source_substs), - target_fields[coerce_index].ty(*tcx, target_substs), + source_fields[coerce_index].ty(*tcx, source_args), + target_fields[coerce_index].ty(*tcx, target_args), ) } _ => bug!( @@ -1321,7 +1244,7 @@ impl<'v> RootCollector<'_, 'v> { self.tcx, ty::ParamEnv::reveal_all(), start_def_id, - self.tcx.mk_substs(&[main_ret_ty.into()]), + self.tcx.mk_args(&[main_ret_ty.into()]), ) .unwrap() .unwrap(); @@ -1368,8 +1291,8 @@ fn create_mono_items_for_default_impls<'tcx>( ) } }; - let impl_substs = InternalSubsts::for_item(tcx, item.owner_id.to_def_id(), only_region_params); - let trait_ref = trait_ref.subst(tcx, impl_substs); + let impl_args = GenericArgs::for_item(tcx, item.owner_id.to_def_id(), only_region_params); + let trait_ref = trait_ref.instantiate(tcx, impl_args); // Unlike 'lazy' monomorphization that begins by collecting items transitively // called by `main` or other global items, when eagerly monomorphizing impl @@ -1380,7 +1303,7 @@ fn create_mono_items_for_default_impls<'tcx>( // consider higher-ranked predicates such as `for<'a> &'a mut [u8]: Copy` to // be trivially false. We must now check that the impl has no impossible-to-satisfy // predicates. - if tcx.subst_and_check_impossible_predicates((item.owner_id.to_def_id(), impl_substs)) { + if tcx.subst_and_check_impossible_predicates((item.owner_id.to_def_id(), impl_args)) { return; } @@ -1398,8 +1321,8 @@ fn create_mono_items_for_default_impls<'tcx>( // As mentioned above, the method is legal to eagerly instantiate if it // only has lifetime substitutions. This is validated by - let substs = trait_ref.substs.extend_to(tcx, method.def_id, only_region_params); - let instance = ty::Instance::expect_resolve(tcx, param_env, method.def_id, substs); + let args = trait_ref.args.extend_to(tcx, method.def_id, only_region_params); + let instance = ty::Instance::expect_resolve(tcx, param_env, method.def_id, args); let mono_item = create_fn_mono_item(tcx, instance, DUMMY_SP); if mono_item.node.is_instantiable(tcx) && should_codegen_locally(tcx, &instance) { @@ -1408,8 +1331,8 @@ fn create_mono_items_for_default_impls<'tcx>( } } -/// Scans the miri alloc in order to find function calls, closures, and drop-glue. -fn collect_miri<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoItems<'tcx>) { +/// Scans the CTFE alloc in order to find function calls, closures, and drop-glue. +fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoItems<'tcx>) { match tcx.global_alloc(alloc_id) { GlobalAlloc::Static(def_id) => { assert!(!tcx.is_thread_local_static(def_id)); @@ -1423,7 +1346,7 @@ fn collect_miri<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIte trace!("collecting {:?} with {:#?}", alloc_id, alloc); for &inner in alloc.inner().provenance().ptrs().values() { rustc_data_structures::stack::ensure_sufficient_stack(|| { - collect_miri(tcx, inner, output); + collect_alloc(tcx, inner, output); }); } } @@ -1435,20 +1358,20 @@ fn collect_miri<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIte } GlobalAlloc::VTable(ty, trait_ref) => { let alloc_id = tcx.vtable_allocation((ty, trait_ref)); - collect_miri(tcx, alloc_id, output) + collect_alloc(tcx, alloc_id, output) } } } /// Scans the MIR in order to find function calls, closures, and drop-glue. #[instrument(skip(tcx, output), level = "debug")] -fn collect_neighbours<'tcx>( +fn collect_used_items<'tcx>( tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, output: &mut MonoItems<'tcx>, ) { let body = tcx.instance_mir(instance.def); - MirNeighborCollector { tcx, body: &body, output, instance }.visit_body(&body); + MirUsedCollector { tcx, body: &body, output, instance }.visit_body(&body); } #[instrument(skip(tcx, output), level = "debug")] @@ -1458,10 +1381,10 @@ fn collect_const_value<'tcx>( output: &mut MonoItems<'tcx>, ) { match value { - ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => collect_miri(tcx, ptr.provenance, output), + ConstValue::Scalar(Scalar::Ptr(ptr, _size)) => collect_alloc(tcx, ptr.provenance, output), ConstValue::Slice { data: alloc, start: _, end: _ } | ConstValue::ByRef { alloc, .. } => { for &id in alloc.inner().provenance().ptrs().values() { - collect_miri(tcx, id, output); + collect_alloc(tcx, id, output); } } _ => {} diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index ecc50c3f664fd..5f05020acae91 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -1,4 +1,5 @@ #![feature(array_windows)] +#![feature(is_sorted)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] @@ -30,12 +31,12 @@ fn custom_coerce_unsize_info<'tcx>( source_ty: Ty<'tcx>, target_ty: Ty<'tcx>, ) -> CustomCoerceUnsized { - let trait_ref = ty::Binder::dummy(ty::TraitRef::from_lang_item( + let trait_ref = ty::TraitRef::from_lang_item( tcx.tcx, LangItem::CoerceUnsized, tcx.span, [source_ty, target_ty], - )); + ); match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), trait_ref)) { Ok(traits::ImplSource::UserDefined(traits::ImplSourceUserDefinedData { diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs new file mode 100644 index 0000000000000..de6db8ae6ae44 --- /dev/null +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -0,0 +1,1326 @@ +//! Partitioning Codegen Units for Incremental Compilation +//! ====================================================== +//! +//! The task of this module is to take the complete set of monomorphizations of +//! a crate and produce a set of codegen units from it, where a codegen unit +//! is a named set of (mono-item, linkage) pairs. That is, this module +//! decides which monomorphization appears in which codegen units with which +//! linkage. The following paragraphs describe some of the background on the +//! partitioning scheme. +//! +//! The most important opportunity for saving on compilation time with +//! incremental compilation is to avoid re-codegenning and re-optimizing code. +//! Since the unit of codegen and optimization for LLVM is "modules" or, how +//! we call them "codegen units", the particulars of how much time can be saved +//! by incremental compilation are tightly linked to how the output program is +//! partitioned into these codegen units prior to passing it to LLVM -- +//! especially because we have to treat codegen units as opaque entities once +//! they are created: There is no way for us to incrementally update an existing +//! LLVM module and so we have to build any such module from scratch if it was +//! affected by some change in the source code. +//! +//! From that point of view it would make sense to maximize the number of +//! codegen units by, for example, putting each function into its own module. +//! That way only those modules would have to be re-compiled that were actually +//! affected by some change, minimizing the number of functions that could have +//! been re-used but just happened to be located in a module that is +//! re-compiled. +//! +//! However, since LLVM optimization does not work across module boundaries, +//! using such a highly granular partitioning would lead to very slow runtime +//! code since it would effectively prohibit inlining and other inter-procedure +//! optimizations. We want to avoid that as much as possible. +//! +//! Thus we end up with a trade-off: The bigger the codegen units, the better +//! LLVM's optimizer can do its work, but also the smaller the compilation time +//! reduction we get from incremental compilation. +//! +//! Ideally, we would create a partitioning such that there are few big codegen +//! units with few interdependencies between them. For now though, we use the +//! following heuristic to determine the partitioning: +//! +//! - There are two codegen units for every source-level module: +//! - One for "stable", that is non-generic, code +//! - One for more "volatile" code, i.e., monomorphized instances of functions +//! defined in that module +//! +//! In order to see why this heuristic makes sense, let's take a look at when a +//! codegen unit can get invalidated: +//! +//! 1. The most straightforward case is when the BODY of a function or global +//! changes. Then any codegen unit containing the code for that item has to be +//! re-compiled. Note that this includes all codegen units where the function +//! has been inlined. +//! +//! 2. The next case is when the SIGNATURE of a function or global changes. In +//! this case, all codegen units containing a REFERENCE to that item have to be +//! re-compiled. This is a superset of case 1. +//! +//! 3. The final and most subtle case is when a REFERENCE to a generic function +//! is added or removed somewhere. Even though the definition of the function +//! might be unchanged, a new REFERENCE might introduce a new monomorphized +//! instance of this function which has to be placed and compiled somewhere. +//! Conversely, when removing a REFERENCE, it might have been the last one with +//! that particular set of generic arguments and thus we have to remove it. +//! +//! From the above we see that just using one codegen unit per source-level +//! module is not such a good idea, since just adding a REFERENCE to some +//! generic item somewhere else would invalidate everything within the module +//! containing the generic item. The heuristic above reduces this detrimental +//! side-effect of references a little by at least not touching the non-generic +//! code of the module. +//! +//! A Note on Inlining +//! ------------------ +//! As briefly mentioned above, in order for LLVM to be able to inline a +//! function call, the body of the function has to be available in the LLVM +//! module where the call is made. This has a few consequences for partitioning: +//! +//! - The partitioning algorithm has to take care of placing functions into all +//! codegen units where they should be available for inlining. It also has to +//! decide on the correct linkage for these functions. +//! +//! - The partitioning algorithm has to know which functions are likely to get +//! inlined, so it can distribute function instantiations accordingly. Since +//! there is no way of knowing for sure which functions LLVM will decide to +//! inline in the end, we apply a heuristic here: Only functions marked with +//! `#[inline]` are considered for inlining by the partitioner. The current +//! implementation will not try to determine if a function is likely to be +//! inlined by looking at the functions definition. +//! +//! Note though that as a side-effect of creating a codegen units per +//! source-level module, functions from the same module will be available for +//! inlining, even when they are not marked `#[inline]`. + +use std::cmp; +use std::collections::hash_map::Entry; +use std::fs::{self, File}; +use std::io::{BufWriter, Write}; +use std::path::{Path, PathBuf}; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::sync; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, DefIdSet, LOCAL_CRATE}; +use rustc_hir::definitions::DefPathDataName; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel}; +use rustc_middle::mir; +use rustc_middle::mir::mono::{ + CodegenUnit, CodegenUnitNameBuilder, InstantiationMode, Linkage, MonoItem, MonoItemData, + Visibility, +}; +use rustc_middle::query::Providers; +use rustc_middle::ty::print::{characteristic_def_id_of_type, with_no_trimmed_paths}; +use rustc_middle::ty::{self, visit::TypeVisitableExt, InstanceDef, TyCtxt}; +use rustc_session::config::{DumpMonoStatsFormat, SwitchWithOptPath}; +use rustc_session::CodegenUnits; +use rustc_span::symbol::Symbol; + +use crate::collector::UsageMap; +use crate::collector::{self, MonoItemCollectionMode}; +use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined, UnknownCguCollectionMode}; + +struct PartitioningCx<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + usage_map: &'a UsageMap<'tcx>, +} + +struct PlacedMonoItems<'tcx> { + /// The codegen units, sorted by name to make things deterministic. + codegen_units: Vec>, + + internalization_candidates: FxHashSet>, +} + +// The output CGUs are sorted by name. +fn partition<'tcx, I>( + tcx: TyCtxt<'tcx>, + mono_items: I, + usage_map: &UsageMap<'tcx>, +) -> Vec> +where + I: Iterator>, +{ + let _prof_timer = tcx.prof.generic_activity("cgu_partitioning"); + + let cx = &PartitioningCx { tcx, usage_map }; + + // Place all mono items into a codegen unit. `place_mono_items` is + // responsible for initializing the CGU size estimates. + let PlacedMonoItems { mut codegen_units, internalization_candidates } = { + let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_items"); + let placed = place_mono_items(cx, mono_items); + + debug_dump(tcx, "PLACE", &placed.codegen_units); + + placed + }; + + // Merge until we have at most `max_cgu_count` codegen units. + // `merge_codegen_units` is responsible for updating the CGU size + // estimates. + { + let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_merge_cgus"); + merge_codegen_units(cx, &mut codegen_units); + debug_dump(tcx, "MERGE", &codegen_units); + } + + // Make as many symbols "internal" as possible, so LLVM has more freedom to + // optimize. + if !tcx.sess.link_dead_code() { + let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols"); + internalize_symbols(cx, &mut codegen_units, internalization_candidates); + + debug_dump(tcx, "INTERNALIZE", &codegen_units); + } + + // Mark one CGU for dead code, if necessary. + let instrument_dead_code = + tcx.sess.instrument_coverage() && !tcx.sess.instrument_coverage_except_unused_functions(); + if instrument_dead_code { + mark_code_coverage_dead_code_cgu(&mut codegen_units); + } + + // Ensure CGUs are sorted by name, so that we get deterministic results. + if !codegen_units.is_sorted_by(|a, b| Some(a.name().as_str().cmp(b.name().as_str()))) { + let mut names = String::new(); + for cgu in codegen_units.iter() { + names += &format!("- {}\n", cgu.name()); + } + bug!("unsorted CGUs:\n{names}"); + } + + codegen_units +} + +fn place_mono_items<'tcx, I>(cx: &PartitioningCx<'_, 'tcx>, mono_items: I) -> PlacedMonoItems<'tcx> +where + I: Iterator>, +{ + let mut codegen_units = FxHashMap::default(); + let is_incremental_build = cx.tcx.sess.opts.incremental.is_some(); + let mut internalization_candidates = FxHashSet::default(); + + // Determine if monomorphizations instantiated in this crate will be made + // available to downstream crates. This depends on whether we are in + // share-generics mode and whether the current crate can even have + // downstream crates. + let export_generics = + cx.tcx.sess.opts.share_generics() && cx.tcx.local_crate_exports_generics(); + + let cgu_name_builder = &mut CodegenUnitNameBuilder::new(cx.tcx); + let cgu_name_cache = &mut FxHashMap::default(); + + for mono_item in mono_items { + // Handle only root items directly here. Inlined items are handled at + // the bottom of the loop based on reachability. + match mono_item.instantiation_mode(cx.tcx) { + InstantiationMode::GloballyShared { .. } => {} + InstantiationMode::LocalCopy => continue, + } + + let characteristic_def_id = characteristic_def_id_of_mono_item(cx.tcx, mono_item); + let is_volatile = is_incremental_build && mono_item.is_generic_fn(); + + let cgu_name = match characteristic_def_id { + Some(def_id) => compute_codegen_unit_name( + cx.tcx, + cgu_name_builder, + def_id, + is_volatile, + cgu_name_cache, + ), + None => fallback_cgu_name(cgu_name_builder), + }; + + let cgu = codegen_units.entry(cgu_name).or_insert_with(|| CodegenUnit::new(cgu_name)); + + let mut can_be_internalized = true; + let (linkage, visibility) = mono_item_linkage_and_visibility( + cx.tcx, + &mono_item, + &mut can_be_internalized, + export_generics, + ); + if visibility == Visibility::Hidden && can_be_internalized { + internalization_candidates.insert(mono_item); + } + let size_estimate = mono_item.size_estimate(cx.tcx); + + cgu.items_mut() + .insert(mono_item, MonoItemData { inlined: false, linkage, visibility, size_estimate }); + + // Get all inlined items that are reachable from `mono_item` without + // going via another root item. This includes drop-glue, functions from + // external crates, and local functions the definition of which is + // marked with `#[inline]`. + let mut reachable_inlined_items = FxHashSet::default(); + get_reachable_inlined_items(cx.tcx, mono_item, cx.usage_map, &mut reachable_inlined_items); + + // Add those inlined items. It's possible an inlined item is reachable + // from multiple root items within a CGU, which is fine, it just means + // the `insert` will be a no-op. + for inlined_item in reachable_inlined_items { + // This is a CGU-private copy. + cgu.items_mut().entry(inlined_item).or_insert_with(|| MonoItemData { + inlined: true, + linkage: Linkage::Internal, + visibility: Visibility::Default, + size_estimate: inlined_item.size_estimate(cx.tcx), + }); + } + } + + // Always ensure we have at least one CGU; otherwise, if we have a + // crate with just types (for example), we could wind up with no CGU. + if codegen_units.is_empty() { + let cgu_name = fallback_cgu_name(cgu_name_builder); + codegen_units.insert(cgu_name, CodegenUnit::new(cgu_name)); + } + + let mut codegen_units: Vec<_> = codegen_units.into_values().collect(); + codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str())); + + for cgu in codegen_units.iter_mut() { + cgu.compute_size_estimate(); + } + + return PlacedMonoItems { codegen_units, internalization_candidates }; + + fn get_reachable_inlined_items<'tcx>( + tcx: TyCtxt<'tcx>, + item: MonoItem<'tcx>, + usage_map: &UsageMap<'tcx>, + visited: &mut FxHashSet>, + ) { + usage_map.for_each_inlined_used_item(tcx, item, |inlined_item| { + let is_new = visited.insert(inlined_item); + if is_new { + get_reachable_inlined_items(tcx, inlined_item, usage_map, visited); + } + }); + } +} + +// This function requires the CGUs to be sorted by name on input, and ensures +// they are sorted by name on return, for deterministic behaviour. +fn merge_codegen_units<'tcx>( + cx: &PartitioningCx<'_, 'tcx>, + codegen_units: &mut Vec>, +) { + assert!(cx.tcx.sess.codegen_units().as_usize() >= 1); + + // A sorted order here ensures merging is deterministic. + assert!(codegen_units.is_sorted_by(|a, b| Some(a.name().as_str().cmp(b.name().as_str())))); + + // This map keeps track of what got merged into what. + let mut cgu_contents: FxHashMap> = + codegen_units.iter().map(|cgu| (cgu.name(), vec![cgu.name()])).collect(); + + // If N is the maximum number of CGUs, and the CGUs are sorted from largest + // to smallest, we repeatedly find which CGU in codegen_units[N..] has the + // greatest overlap of inlined items with codegen_units[N-1], merge that + // CGU into codegen_units[N-1], then re-sort by size and repeat. + // + // We use inlined item overlap to guide this merging because it minimizes + // duplication of inlined items, which makes LLVM be faster and generate + // better and smaller machine code. + // + // Why merge into codegen_units[N-1]? We want CGUs to have similar sizes, + // which means we don't want codegen_units[0..N] (the already big ones) + // getting any bigger, if we can avoid it. When we have more than N CGUs + // then at least one of the biggest N will have to grow. codegen_units[N-1] + // is the smallest of those, and so has the most room to grow. + let max_codegen_units = cx.tcx.sess.codegen_units().as_usize(); + while codegen_units.len() > max_codegen_units { + // Sort small CGUs to the back. + codegen_units.sort_by_key(|cgu| cmp::Reverse(cgu.size_estimate())); + + let cgu_dst = &codegen_units[max_codegen_units - 1]; + + // Find the CGU that overlaps the most with `cgu_dst`. In the case of a + // tie, favour the earlier (bigger) CGU. + let mut max_overlap = 0; + let mut max_overlap_i = max_codegen_units; + for (i, cgu_src) in codegen_units.iter().enumerate().skip(max_codegen_units) { + if cgu_src.size_estimate() <= max_overlap { + // None of the remaining overlaps can exceed `max_overlap`, so + // stop looking. + break; + } + + let overlap = compute_inlined_overlap(cgu_dst, cgu_src); + if overlap > max_overlap { + max_overlap = overlap; + max_overlap_i = i; + } + } + + let mut cgu_src = codegen_units.swap_remove(max_overlap_i); + let cgu_dst = &mut codegen_units[max_codegen_units - 1]; + + // Move the items from `cgu_src` to `cgu_dst`. Some of them may be + // duplicate inlined items, in which case the destination CGU is + // unaffected. Recalculate size estimates afterwards. + cgu_dst.items_mut().extend(cgu_src.items_mut().drain()); + cgu_dst.compute_size_estimate(); + + // Record that `cgu_dst` now contains all the stuff that was in + // `cgu_src` before. + let mut consumed_cgu_names = cgu_contents.remove(&cgu_src.name()).unwrap(); + cgu_contents.get_mut(&cgu_dst.name()).unwrap().append(&mut consumed_cgu_names); + } + + // Having multiple CGUs can drastically speed up compilation. But for + // non-incremental builds, tiny CGUs slow down compilation *and* result in + // worse generated code. So we don't allow CGUs smaller than this (unless + // there is just one CGU, of course). Note that CGU sizes of 100,000+ are + // common in larger programs, so this isn't all that large. + const NON_INCR_MIN_CGU_SIZE: usize = 1800; + + // Repeatedly merge the two smallest codegen units as long as: it's a + // non-incremental build, and the user didn't specify a CGU count, and + // there are multiple CGUs, and some are below the minimum size. + // + // The "didn't specify a CGU count" condition is because when an explicit + // count is requested we observe it as closely as possible. For example, + // the `compiler_builtins` crate sets `codegen-units = 10000` and it's + // critical they aren't merged. Also, some tests use explicit small values + // and likewise won't work if small CGUs are merged. + while cx.tcx.sess.opts.incremental.is_none() + && matches!(cx.tcx.sess.codegen_units(), CodegenUnits::Default(_)) + && codegen_units.len() > 1 + && codegen_units.iter().any(|cgu| cgu.size_estimate() < NON_INCR_MIN_CGU_SIZE) + { + // Sort small cgus to the back. + codegen_units.sort_by_key(|cgu| cmp::Reverse(cgu.size_estimate())); + + let mut smallest = codegen_units.pop().unwrap(); + let second_smallest = codegen_units.last_mut().unwrap(); + + // Move the items from `smallest` to `second_smallest`. Some of them + // may be duplicate inlined items, in which case the destination CGU is + // unaffected. Recalculate size estimates afterwards. + second_smallest.items_mut().extend(smallest.items_mut().drain()); + second_smallest.compute_size_estimate(); + + // Don't update `cgu_contents`, that's only for incremental builds. + } + + let cgu_name_builder = &mut CodegenUnitNameBuilder::new(cx.tcx); + + // Rename the newly merged CGUs. + if cx.tcx.sess.opts.incremental.is_some() { + // If we are doing incremental compilation, we want CGU names to + // reflect the path of the source level module they correspond to. + // For CGUs that contain the code of multiple modules because of the + // merging done above, we use a concatenation of the names of all + // contained CGUs. + let new_cgu_names: FxHashMap = cgu_contents + .into_iter() + // This `filter` makes sure we only update the name of CGUs that + // were actually modified by merging. + .filter(|(_, cgu_contents)| cgu_contents.len() > 1) + .map(|(current_cgu_name, cgu_contents)| { + let mut cgu_contents: Vec<&str> = cgu_contents.iter().map(|s| s.as_str()).collect(); + + // Sort the names, so things are deterministic and easy to + // predict. We are sorting primitive `&str`s here so we can + // use unstable sort. + cgu_contents.sort_unstable(); + + (current_cgu_name, cgu_contents.join("--")) + }) + .collect(); + + for cgu in codegen_units.iter_mut() { + if let Some(new_cgu_name) = new_cgu_names.get(&cgu.name()) { + if cx.tcx.sess.opts.unstable_opts.human_readable_cgu_names { + cgu.set_name(Symbol::intern(&new_cgu_name)); + } else { + // If we don't require CGU names to be human-readable, + // we use a fixed length hash of the composite CGU name + // instead. + let new_cgu_name = CodegenUnit::mangle_name(&new_cgu_name); + cgu.set_name(Symbol::intern(&new_cgu_name)); + } + } + } + + // A sorted order here ensures what follows can be deterministic. + codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str())); + } else { + // When compiling non-incrementally, we rename the CGUS so they have + // identical names except for the numeric suffix, something like + // `regex.f10ba03eb5ec7975-cgu.N`, where `N` varies. + // + // It is useful for debugging and profiling purposes if the resulting + // CGUs are sorted by name *and* reverse sorted by size. (CGU 0 is the + // biggest, CGU 1 is the second biggest, etc.) + // + // So first we reverse sort by size. Then we generate the names with + // zero-padded suffixes, which means they are automatically sorted by + // names. The numeric suffix width depends on the number of CGUs, which + // is always greater than zero: + // - [1,9] CGUs: `0`, `1`, `2`, ... + // - [10,99] CGUs: `00`, `01`, `02`, ... + // - [100,999] CGUs: `000`, `001`, `002`, ... + // - etc. + // + // If we didn't zero-pad the sorted-by-name order would be `XYZ-cgu.0`, + // `XYZ-cgu.1`, `XYZ-cgu.10`, `XYZ-cgu.11`, ..., `XYZ-cgu.2`, etc. + codegen_units.sort_by_key(|cgu| cmp::Reverse(cgu.size_estimate())); + let num_digits = codegen_units.len().ilog10() as usize + 1; + for (index, cgu) in codegen_units.iter_mut().enumerate() { + // Note: `WorkItem::short_description` depends on this name ending + // with `-cgu.` followed by a numeric suffix. Please keep it in + // sync with this code. + let suffix = format!("{index:0num_digits$}"); + let numbered_codegen_unit_name = + cgu_name_builder.build_cgu_name_no_mangle(LOCAL_CRATE, &["cgu"], Some(suffix)); + cgu.set_name(numbered_codegen_unit_name); + } + } +} + +/// Compute the combined size of all inlined items that appear in both `cgu1` +/// and `cgu2`. +fn compute_inlined_overlap<'tcx>(cgu1: &CodegenUnit<'tcx>, cgu2: &CodegenUnit<'tcx>) -> usize { + // Either order works. We pick the one that involves iterating over fewer + // items. + let (src_cgu, dst_cgu) = + if cgu1.items().len() <= cgu2.items().len() { (cgu1, cgu2) } else { (cgu2, cgu1) }; + + let mut overlap = 0; + for (item, data) in src_cgu.items().iter() { + if data.inlined { + if dst_cgu.items().contains_key(item) { + overlap += data.size_estimate; + } + } + } + overlap +} + +fn internalize_symbols<'tcx>( + cx: &PartitioningCx<'_, 'tcx>, + codegen_units: &mut [CodegenUnit<'tcx>], + internalization_candidates: FxHashSet>, +) { + /// For symbol internalization, we need to know whether a symbol/mono-item + /// is used from outside the codegen unit it is defined in. This type is + /// used to keep track of that. + #[derive(Clone, PartialEq, Eq, Debug)] + enum MonoItemPlacement { + SingleCgu(Symbol), + MultipleCgus, + } + + let mut mono_item_placements = FxHashMap::default(); + let single_codegen_unit = codegen_units.len() == 1; + + if !single_codegen_unit { + for cgu in codegen_units.iter() { + for item in cgu.items().keys() { + // If there is more than one codegen unit, we need to keep track + // in which codegen units each monomorphization is placed. + match mono_item_placements.entry(*item) { + Entry::Occupied(e) => { + let placement = e.into_mut(); + debug_assert!(match *placement { + MonoItemPlacement::SingleCgu(cgu_name) => cgu_name != cgu.name(), + MonoItemPlacement::MultipleCgus => true, + }); + *placement = MonoItemPlacement::MultipleCgus; + } + Entry::Vacant(e) => { + e.insert(MonoItemPlacement::SingleCgu(cgu.name())); + } + } + } + } + } + + // For each internalization candidates in each codegen unit, check if it is + // used from outside its defining codegen unit. + for cgu in codegen_units { + let home_cgu = MonoItemPlacement::SingleCgu(cgu.name()); + + for (item, data) in cgu.items_mut() { + if !internalization_candidates.contains(item) { + // This item is no candidate for internalizing, so skip it. + continue; + } + + if !single_codegen_unit { + debug_assert_eq!(mono_item_placements[item], home_cgu); + + if cx + .usage_map + .get_user_items(*item) + .iter() + .filter_map(|user_item| { + // Some user mono items might not have been + // instantiated. We can safely ignore those. + mono_item_placements.get(user_item) + }) + .any(|placement| *placement != home_cgu) + { + // Found a user from another CGU, so skip to the next item + // without marking this one as internal. + continue; + } + } + + // If we got here, we did not find any uses from other CGUs, so + // it's fine to make this monomorphization internal. + data.linkage = Linkage::Internal; + data.visibility = Visibility::Default; + } + } +} + +fn mark_code_coverage_dead_code_cgu<'tcx>(codegen_units: &mut [CodegenUnit<'tcx>]) { + assert!(!codegen_units.is_empty()); + + // Find the smallest CGU that has exported symbols and put the dead + // function stubs in that CGU. We look for exported symbols to increase + // the likelihood the linker won't throw away the dead functions. + // FIXME(#92165): In order to truly resolve this, we need to make sure + // the object file (CGU) containing the dead function stubs is included + // in the final binary. This will probably require forcing these + // function symbols to be included via `-u` or `/include` linker args. + let dead_code_cgu = codegen_units + .iter_mut() + .filter(|cgu| cgu.items().iter().any(|(_, data)| data.linkage == Linkage::External)) + .min_by_key(|cgu| cgu.size_estimate()); + + // If there are no CGUs that have externally linked items, then we just + // pick the first CGU as a fallback. + let dead_code_cgu = if let Some(cgu) = dead_code_cgu { cgu } else { &mut codegen_units[0] }; + + dead_code_cgu.make_code_coverage_dead_code_cgu(); +} + +fn characteristic_def_id_of_mono_item<'tcx>( + tcx: TyCtxt<'tcx>, + mono_item: MonoItem<'tcx>, +) -> Option { + match mono_item { + MonoItem::Fn(instance) => { + let def_id = match instance.def { + ty::InstanceDef::Item(def) => def, + ty::InstanceDef::VTableShim(..) + | ty::InstanceDef::ReifyShim(..) + | ty::InstanceDef::FnPtrShim(..) + | ty::InstanceDef::ClosureOnceShim { .. } + | ty::InstanceDef::Intrinsic(..) + | ty::InstanceDef::DropGlue(..) + | ty::InstanceDef::Virtual(..) + | ty::InstanceDef::CloneShim(..) + | ty::InstanceDef::ThreadLocalShim(..) + | ty::InstanceDef::FnPtrAddrShim(..) => return None, + }; + + // If this is a method, we want to put it into the same module as + // its self-type. If the self-type does not provide a characteristic + // DefId, we use the location of the impl after all. + + if tcx.trait_of_item(def_id).is_some() { + let self_ty = instance.args.type_at(0); + // This is a default implementation of a trait method. + return characteristic_def_id_of_type(self_ty).or(Some(def_id)); + } + + if let Some(impl_def_id) = tcx.impl_of_method(def_id) { + if tcx.sess.opts.incremental.is_some() + && tcx.trait_id_of_impl(impl_def_id) == tcx.lang_items().drop_trait() + { + // Put `Drop::drop` into the same cgu as `drop_in_place` + // since `drop_in_place` is the only thing that can + // call it. + return None; + } + + // When polymorphization is enabled, methods which do not depend on their generic + // parameters, but the self-type of their impl block do will fail to normalize. + if !tcx.sess.opts.unstable_opts.polymorphize || !instance.has_param() { + // This is a method within an impl, find out what the self-type is: + let impl_self_ty = tcx.subst_and_normalize_erasing_regions( + instance.args, + ty::ParamEnv::reveal_all(), + tcx.type_of(impl_def_id), + ); + if let Some(def_id) = characteristic_def_id_of_type(impl_self_ty) { + return Some(def_id); + } + } + } + + Some(def_id) + } + MonoItem::Static(def_id) => Some(def_id), + MonoItem::GlobalAsm(item_id) => Some(item_id.owner_id.to_def_id()), + } +} + +fn compute_codegen_unit_name( + tcx: TyCtxt<'_>, + name_builder: &mut CodegenUnitNameBuilder<'_>, + def_id: DefId, + volatile: bool, + cache: &mut CguNameCache, +) -> Symbol { + // Find the innermost module that is not nested within a function. + let mut current_def_id = def_id; + let mut cgu_def_id = None; + // Walk backwards from the item we want to find the module for. + loop { + if current_def_id.is_crate_root() { + if cgu_def_id.is_none() { + // If we have not found a module yet, take the crate root. + cgu_def_id = Some(def_id.krate.as_def_id()); + } + break; + } else if tcx.def_kind(current_def_id) == DefKind::Mod { + if cgu_def_id.is_none() { + cgu_def_id = Some(current_def_id); + } + } else { + // If we encounter something that is not a module, throw away + // any module that we've found so far because we now know that + // it is nested within something else. + cgu_def_id = None; + } + + current_def_id = tcx.parent(current_def_id); + } + + let cgu_def_id = cgu_def_id.unwrap(); + + *cache.entry((cgu_def_id, volatile)).or_insert_with(|| { + let def_path = tcx.def_path(cgu_def_id); + + let components = def_path.data.iter().map(|part| match part.data.name() { + DefPathDataName::Named(name) => name, + DefPathDataName::Anon { .. } => unreachable!(), + }); + + let volatile_suffix = volatile.then_some("volatile"); + + name_builder.build_cgu_name(def_path.krate, components, volatile_suffix) + }) +} + +// Anything we can't find a proper codegen unit for goes into this. +fn fallback_cgu_name(name_builder: &mut CodegenUnitNameBuilder<'_>) -> Symbol { + name_builder.build_cgu_name(LOCAL_CRATE, &["fallback"], Some("cgu")) +} + +fn mono_item_linkage_and_visibility<'tcx>( + tcx: TyCtxt<'tcx>, + mono_item: &MonoItem<'tcx>, + can_be_internalized: &mut bool, + export_generics: bool, +) -> (Linkage, Visibility) { + if let Some(explicit_linkage) = mono_item.explicit_linkage(tcx) { + return (explicit_linkage, Visibility::Default); + } + let vis = mono_item_visibility(tcx, mono_item, can_be_internalized, export_generics); + (Linkage::External, vis) +} + +type CguNameCache = FxHashMap<(DefId, bool), Symbol>; + +fn static_visibility<'tcx>( + tcx: TyCtxt<'tcx>, + can_be_internalized: &mut bool, + def_id: DefId, +) -> Visibility { + if tcx.is_reachable_non_generic(def_id) { + *can_be_internalized = false; + default_visibility(tcx, def_id, false) + } else { + Visibility::Hidden + } +} + +fn mono_item_visibility<'tcx>( + tcx: TyCtxt<'tcx>, + mono_item: &MonoItem<'tcx>, + can_be_internalized: &mut bool, + export_generics: bool, +) -> Visibility { + let instance = match mono_item { + // This is pretty complicated; see below. + MonoItem::Fn(instance) => instance, + + // Misc handling for generics and such, but otherwise: + MonoItem::Static(def_id) => return static_visibility(tcx, can_be_internalized, *def_id), + MonoItem::GlobalAsm(item_id) => { + return static_visibility(tcx, can_be_internalized, item_id.owner_id.to_def_id()); + } + }; + + let def_id = match instance.def { + InstanceDef::Item(def_id) | InstanceDef::DropGlue(def_id, Some(_)) => def_id, + + // We match the visibility of statics here + InstanceDef::ThreadLocalShim(def_id) => { + return static_visibility(tcx, can_be_internalized, def_id); + } + + // These are all compiler glue and such, never exported, always hidden. + InstanceDef::VTableShim(..) + | InstanceDef::ReifyShim(..) + | InstanceDef::FnPtrShim(..) + | InstanceDef::Virtual(..) + | InstanceDef::Intrinsic(..) + | InstanceDef::ClosureOnceShim { .. } + | InstanceDef::DropGlue(..) + | InstanceDef::CloneShim(..) + | InstanceDef::FnPtrAddrShim(..) => return Visibility::Hidden, + }; + + // The `start_fn` lang item is actually a monomorphized instance of a + // function in the standard library, used for the `main` function. We don't + // want to export it so we tag it with `Hidden` visibility but this symbol + // is only referenced from the actual `main` symbol which we unfortunately + // don't know anything about during partitioning/collection. As a result we + // forcibly keep this symbol out of the `internalization_candidates` set. + // + // FIXME: eventually we don't want to always force this symbol to have + // hidden visibility, it should indeed be a candidate for + // internalization, but we have to understand that it's referenced + // from the `main` symbol we'll generate later. + // + // This may be fixable with a new `InstanceDef` perhaps? Unsure! + if tcx.lang_items().start_fn() == Some(def_id) { + *can_be_internalized = false; + return Visibility::Hidden; + } + + let is_generic = instance.args.non_erasable_generics().next().is_some(); + + // Upstream `DefId` instances get different handling than local ones. + let Some(def_id) = def_id.as_local() else { + return if export_generics && is_generic { + // If it is an upstream monomorphization and we export generics, we must make + // it available to downstream crates. + *can_be_internalized = false; + default_visibility(tcx, def_id, true) + } else { + Visibility::Hidden + }; + }; + + if is_generic { + if export_generics { + if tcx.is_unreachable_local_definition(def_id) { + // This instance cannot be used from another crate. + Visibility::Hidden + } else { + // This instance might be useful in a downstream crate. + *can_be_internalized = false; + default_visibility(tcx, def_id.to_def_id(), true) + } + } else { + // We are not exporting generics or the definition is not reachable + // for downstream crates, we can internalize its instantiations. + Visibility::Hidden + } + } else { + // If this isn't a generic function then we mark this a `Default` if + // this is a reachable item, meaning that it's a symbol other crates may + // use when they link to us. + if tcx.is_reachable_non_generic(def_id.to_def_id()) { + *can_be_internalized = false; + debug_assert!(!is_generic); + return default_visibility(tcx, def_id.to_def_id(), false); + } + + // If this isn't reachable then we're gonna tag this with `Hidden` + // visibility. In some situations though we'll want to prevent this + // symbol from being internalized. + // + // There's two categories of items here: + // + // * First is weak lang items. These are basically mechanisms for + // libcore to forward-reference symbols defined later in crates like + // the standard library or `#[panic_handler]` definitions. The + // definition of these weak lang items needs to be referencable by + // libcore, so we're no longer a candidate for internalization. + // Removal of these functions can't be done by LLVM but rather must be + // done by the linker as it's a non-local decision. + // + // * Second is "std internal symbols". Currently this is primarily used + // for allocator symbols. Allocators are a little weird in their + // implementation, but the idea is that the compiler, at the last + // minute, defines an allocator with an injected object file. The + // `alloc` crate references these symbols (`__rust_alloc`) and the + // definition doesn't get hooked up until a linked crate artifact is + // generated. + // + // The symbols synthesized by the compiler (`__rust_alloc`) are thin + // veneers around the actual implementation, some other symbol which + // implements the same ABI. These symbols (things like `__rg_alloc`, + // `__rdl_alloc`, `__rde_alloc`, etc), are all tagged with "std + // internal symbols". + // + // The std-internal symbols here **should not show up in a dll as an + // exported interface**, so they return `false` from + // `is_reachable_non_generic` above and we'll give them `Hidden` + // visibility below. Like the weak lang items, though, we can't let + // LLVM internalize them as this decision is left up to the linker to + // omit them, so prevent them from being internalized. + let attrs = tcx.codegen_fn_attrs(def_id); + if attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) { + *can_be_internalized = false; + } + + Visibility::Hidden + } +} + +fn default_visibility(tcx: TyCtxt<'_>, id: DefId, is_generic: bool) -> Visibility { + if !tcx.sess.target.default_hidden_visibility { + return Visibility::Default; + } + + // Generic functions never have export-level C. + if is_generic { + return Visibility::Hidden; + } + + // Things with export level C don't get instantiated in + // downstream crates. + if !id.is_local() { + return Visibility::Hidden; + } + + // C-export level items remain at `Default`, all other internal + // items become `Hidden`. + match tcx.reachable_non_generics(id.krate).get(&id) { + Some(SymbolExportInfo { level: SymbolExportLevel::C, .. }) => Visibility::Default, + _ => Visibility::Hidden, + } +} + +fn debug_dump<'a, 'tcx: 'a>(tcx: TyCtxt<'tcx>, label: &str, cgus: &[CodegenUnit<'tcx>]) { + let dump = move || { + use std::fmt::Write; + + let mut num_cgus = 0; + let mut all_cgu_sizes = Vec::new(); + + // Note: every unique root item is placed exactly once, so the number + // of unique root items always equals the number of placed root items. + // + // Also, unreached inlined items won't be counted here. This is fine. + + let mut inlined_items = FxHashSet::default(); + + let mut root_items = 0; + let mut unique_inlined_items = 0; + let mut placed_inlined_items = 0; + + let mut root_size = 0; + let mut unique_inlined_size = 0; + let mut placed_inlined_size = 0; + + for cgu in cgus.iter() { + num_cgus += 1; + all_cgu_sizes.push(cgu.size_estimate()); + + for (item, data) in cgu.items() { + if !data.inlined { + root_items += 1; + root_size += data.size_estimate; + } else { + if inlined_items.insert(item) { + unique_inlined_items += 1; + unique_inlined_size += data.size_estimate; + } + placed_inlined_items += 1; + placed_inlined_size += data.size_estimate; + } + } + } + + all_cgu_sizes.sort_unstable_by_key(|&n| cmp::Reverse(n)); + + let unique_items = root_items + unique_inlined_items; + let placed_items = root_items + placed_inlined_items; + let items_ratio = placed_items as f64 / unique_items as f64; + + let unique_size = root_size + unique_inlined_size; + let placed_size = root_size + placed_inlined_size; + let size_ratio = placed_size as f64 / unique_size as f64; + + let mean_cgu_size = placed_size as f64 / num_cgus as f64; + + assert_eq!(placed_size, all_cgu_sizes.iter().sum::()); + + let s = &mut String::new(); + let _ = writeln!(s, "{label}"); + let _ = writeln!( + s, + "- unique items: {unique_items} ({root_items} root + {unique_inlined_items} inlined), \ + unique size: {unique_size} ({root_size} root + {unique_inlined_size} inlined)\n\ + - placed items: {placed_items} ({root_items} root + {placed_inlined_items} inlined), \ + placed size: {placed_size} ({root_size} root + {placed_inlined_size} inlined)\n\ + - placed/unique items ratio: {items_ratio:.2}, \ + placed/unique size ratio: {size_ratio:.2}\n\ + - CGUs: {num_cgus}, mean size: {mean_cgu_size:.1}, sizes: {}", + list(&all_cgu_sizes), + ); + let _ = writeln!(s); + + for (i, cgu) in cgus.iter().enumerate() { + let name = cgu.name(); + let size = cgu.size_estimate(); + let num_items = cgu.items().len(); + let mean_size = size as f64 / num_items as f64; + + let mut placed_item_sizes: Vec<_> = + cgu.items().values().map(|data| data.size_estimate).collect(); + placed_item_sizes.sort_unstable_by_key(|&n| cmp::Reverse(n)); + let sizes = list(&placed_item_sizes); + + let _ = writeln!(s, "- CGU[{i}]"); + let _ = writeln!(s, " - {name}, size: {size}"); + let _ = + writeln!(s, " - items: {num_items}, mean size: {mean_size:.1}, sizes: {sizes}",); + + for (item, data) in cgu.items_in_deterministic_order(tcx) { + let linkage = data.linkage; + let symbol_name = item.symbol_name(tcx).name; + let symbol_hash_start = symbol_name.rfind('h'); + let symbol_hash = symbol_hash_start.map_or("", |i| &symbol_name[i..]); + let kind = if !data.inlined { "root" } else { "inlined" }; + let size = data.size_estimate; + let _ = with_no_trimmed_paths!(writeln!( + s, + " - {item} [{linkage:?}] [{symbol_hash}] ({kind}, size: {size})" + )); + } + + let _ = writeln!(s); + } + + return std::mem::take(s); + + // Converts a slice to a string, capturing repetitions to save space. + // E.g. `[4, 4, 4, 3, 2, 1, 1, 1, 1, 1]` -> "[4 (x3), 3, 2, 1 (x5)]". + fn list(ns: &[usize]) -> String { + let mut v = Vec::new(); + if ns.is_empty() { + return "[]".to_string(); + } + + let mut elem = |curr, curr_count| { + if curr_count == 1 { + v.push(format!("{curr}")); + } else { + v.push(format!("{curr} (x{curr_count})")); + } + }; + + let mut curr = ns[0]; + let mut curr_count = 1; + + for &n in &ns[1..] { + if n != curr { + elem(curr, curr_count); + curr = n; + curr_count = 1; + } else { + curr_count += 1; + } + } + elem(curr, curr_count); + + format!("[{}]", v.join(", ")) + } + }; + + debug!("{}", dump()); +} + +#[inline(never)] // give this a place in the profiler +fn assert_symbols_are_distinct<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, mono_items: I) +where + I: Iterator>, + 'tcx: 'a, +{ + let _prof_timer = tcx.prof.generic_activity("assert_symbols_are_distinct"); + + let mut symbols: Vec<_> = + mono_items.map(|mono_item| (mono_item, mono_item.symbol_name(tcx))).collect(); + + symbols.sort_by_key(|sym| sym.1); + + for &[(mono_item1, ref sym1), (mono_item2, ref sym2)] in symbols.array_windows() { + if sym1 == sym2 { + let span1 = mono_item1.local_span(tcx); + let span2 = mono_item2.local_span(tcx); + + // Deterministically select one of the spans for error reporting + let span = match (span1, span2) { + (Some(span1), Some(span2)) => { + Some(if span1.lo().0 > span2.lo().0 { span1 } else { span2 }) + } + (span1, span2) => span1.or(span2), + }; + + tcx.sess.emit_fatal(SymbolAlreadyDefined { span, symbol: sym1.to_string() }); + } + } +} + +fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> (&DefIdSet, &[CodegenUnit<'_>]) { + let collection_mode = match tcx.sess.opts.unstable_opts.print_mono_items { + Some(ref s) => { + let mode = s.to_lowercase(); + let mode = mode.trim(); + if mode == "eager" { + MonoItemCollectionMode::Eager + } else { + if mode != "lazy" { + tcx.sess.emit_warning(UnknownCguCollectionMode { mode }); + } + + MonoItemCollectionMode::Lazy + } + } + None => { + if tcx.sess.link_dead_code() { + MonoItemCollectionMode::Eager + } else { + MonoItemCollectionMode::Lazy + } + } + }; + + let (items, usage_map) = collector::collect_crate_mono_items(tcx, collection_mode); + + tcx.sess.abort_if_errors(); + + let (codegen_units, _) = tcx.sess.time("partition_and_assert_distinct_symbols", || { + sync::join( + || { + let mut codegen_units = partition(tcx, items.iter().copied(), &usage_map); + codegen_units[0].make_primary(); + &*tcx.arena.alloc_from_iter(codegen_units) + }, + || assert_symbols_are_distinct(tcx, items.iter()), + ) + }); + + if tcx.prof.enabled() { + // Record CGU size estimates for self-profiling. + for cgu in codegen_units { + tcx.prof.artifact_size( + "codegen_unit_size_estimate", + cgu.name().as_str(), + cgu.size_estimate() as u64, + ); + } + } + + let mono_items: DefIdSet = items + .iter() + .filter_map(|mono_item| match *mono_item { + MonoItem::Fn(ref instance) => Some(instance.def_id()), + MonoItem::Static(def_id) => Some(def_id), + _ => None, + }) + .collect(); + + // Output monomorphization stats per def_id + if let SwitchWithOptPath::Enabled(ref path) = tcx.sess.opts.unstable_opts.dump_mono_stats { + if let Err(err) = + dump_mono_items_stats(tcx, &codegen_units, path, tcx.crate_name(LOCAL_CRATE)) + { + tcx.sess.emit_fatal(CouldntDumpMonoStats { error: err.to_string() }); + } + } + + if tcx.sess.opts.unstable_opts.print_mono_items.is_some() { + let mut item_to_cgus: FxHashMap<_, Vec<_>> = Default::default(); + + for cgu in codegen_units { + for (&mono_item, &data) in cgu.items() { + item_to_cgus.entry(mono_item).or_default().push((cgu.name(), data.linkage)); + } + } + + let mut item_keys: Vec<_> = items + .iter() + .map(|i| { + let mut output = with_no_trimmed_paths!(i.to_string()); + output.push_str(" @@"); + let mut empty = Vec::new(); + let cgus = item_to_cgus.get_mut(i).unwrap_or(&mut empty); + cgus.sort_by_key(|(name, _)| *name); + cgus.dedup(); + for &(ref cgu_name, linkage) in cgus.iter() { + output.push(' '); + output.push_str(cgu_name.as_str()); + + let linkage_abbrev = match linkage { + Linkage::External => "External", + Linkage::AvailableExternally => "Available", + Linkage::LinkOnceAny => "OnceAny", + Linkage::LinkOnceODR => "OnceODR", + Linkage::WeakAny => "WeakAny", + Linkage::WeakODR => "WeakODR", + Linkage::Appending => "Appending", + Linkage::Internal => "Internal", + Linkage::Private => "Private", + Linkage::ExternalWeak => "ExternalWeak", + Linkage::Common => "Common", + }; + + output.push('['); + output.push_str(linkage_abbrev); + output.push(']'); + } + output + }) + .collect(); + + item_keys.sort(); + + for item in item_keys { + println!("MONO_ITEM {item}"); + } + } + + (tcx.arena.alloc(mono_items), codegen_units) +} + +/// Outputs stats about instantiation counts and estimated size, per `MonoItem`'s +/// def, to a file in the given output directory. +fn dump_mono_items_stats<'tcx>( + tcx: TyCtxt<'tcx>, + codegen_units: &[CodegenUnit<'tcx>], + output_directory: &Option, + crate_name: Symbol, +) -> Result<(), Box> { + let output_directory = if let Some(ref directory) = output_directory { + fs::create_dir_all(directory)?; + directory + } else { + Path::new(".") + }; + + let format = tcx.sess.opts.unstable_opts.dump_mono_stats_format; + let ext = format.extension(); + let filename = format!("{crate_name}.mono_items.{ext}"); + let output_path = output_directory.join(&filename); + let file = File::create(&output_path)?; + let mut file = BufWriter::new(file); + + // Gather instantiated mono items grouped by def_id + let mut items_per_def_id: FxHashMap<_, Vec<_>> = Default::default(); + for cgu in codegen_units { + cgu.items() + .keys() + // Avoid variable-sized compiler-generated shims + .filter(|mono_item| mono_item.is_user_defined()) + .for_each(|mono_item| { + items_per_def_id.entry(mono_item.def_id()).or_default().push(mono_item); + }); + } + + #[derive(serde::Serialize)] + struct MonoItem { + name: String, + instantiation_count: usize, + size_estimate: usize, + total_estimate: usize, + } + + // Output stats sorted by total instantiated size, from heaviest to lightest + let mut stats: Vec<_> = items_per_def_id + .into_iter() + .map(|(def_id, items)| { + let name = with_no_trimmed_paths!(tcx.def_path_str(def_id)); + let instantiation_count = items.len(); + let size_estimate = items[0].size_estimate(tcx); + let total_estimate = instantiation_count * size_estimate; + MonoItem { name, instantiation_count, size_estimate, total_estimate } + }) + .collect(); + stats.sort_unstable_by_key(|item| cmp::Reverse(item.total_estimate)); + + if !stats.is_empty() { + match format { + DumpMonoStatsFormat::Json => serde_json::to_writer(file, &stats)?, + DumpMonoStatsFormat::Markdown => { + writeln!( + file, + "| Item | Instantiation count | Estimated Cost Per Instantiation | Total Estimated Cost |" + )?; + writeln!(file, "| --- | ---: | ---: | ---: |")?; + + for MonoItem { name, instantiation_count, size_estimate, total_estimate } in stats { + writeln!( + file, + "| `{name}` | {instantiation_count} | {size_estimate} | {total_estimate} |" + )?; + } + } + } + } + + Ok(()) +} + +fn codegened_and_inlined_items(tcx: TyCtxt<'_>, (): ()) -> &DefIdSet { + let (items, cgus) = tcx.collect_and_partition_mono_items(()); + let mut visited = DefIdSet::default(); + let mut result = items.clone(); + + for cgu in cgus { + for item in cgu.items().keys() { + if let MonoItem::Fn(ref instance) = item { + let did = instance.def_id(); + if !visited.insert(did) { + continue; + } + let body = tcx.instance_mir(instance.def); + for block in body.basic_blocks.iter() { + for statement in &block.statements { + let mir::StatementKind::Coverage(_) = statement.kind else { continue }; + let scope = statement.source_info.scope; + if let Some(inlined) = scope.inlined_instance(&body.source_scopes) { + result.insert(inlined.def_id()); + } + } + } + } + } + } + + tcx.arena.alloc(result) +} + +pub fn provide(providers: &mut Providers) { + providers.collect_and_partition_mono_items = collect_and_partition_mono_items; + providers.codegened_and_inlined_items = codegened_and_inlined_items; + + providers.is_codegened_item = |tcx, def_id| { + let (all_mono_items, _) = tcx.collect_and_partition_mono_items(()); + all_mono_items.contains(&def_id) + }; + + providers.codegen_unit = |tcx, name| { + let (_, all) = tcx.collect_and_partition_mono_items(()); + all.iter() + .find(|cgu| cgu.name() == name) + .unwrap_or_else(|| panic!("failed to find cgu with name {name:?}")) + }; +} diff --git a/compiler/rustc_monomorphize/src/partitioning/default.rs b/compiler/rustc_monomorphize/src/partitioning/default.rs deleted file mode 100644 index 37b7f6bf8a8fc..0000000000000 --- a/compiler/rustc_monomorphize/src/partitioning/default.rs +++ /dev/null @@ -1,568 +0,0 @@ -use std::collections::hash_map::Entry; - -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def::DefKind; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_hir::definitions::DefPathDataName; -use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel}; -use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder, Linkage, Visibility}; -use rustc_middle::mir::mono::{InstantiationMode, MonoItem}; -use rustc_middle::ty::print::characteristic_def_id_of_type; -use rustc_middle::ty::{self, visit::TypeVisitableExt, InstanceDef, TyCtxt}; -use rustc_span::symbol::Symbol; - -use super::PartitioningCx; -use crate::collector::InliningMap; -use crate::partitioning::merging; -use crate::partitioning::{ - MonoItemPlacement, Partition, PostInliningPartitioning, PreInliningPartitioning, -}; - -pub struct DefaultPartitioning; - -impl<'tcx> Partition<'tcx> for DefaultPartitioning { - fn place_root_mono_items( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - mono_items: &mut I, - ) -> PreInliningPartitioning<'tcx> - where - I: Iterator>, - { - let mut roots = FxHashSet::default(); - let mut codegen_units = FxHashMap::default(); - let is_incremental_build = cx.tcx.sess.opts.incremental.is_some(); - let mut internalization_candidates = FxHashSet::default(); - - // Determine if monomorphizations instantiated in this crate will be made - // available to downstream crates. This depends on whether we are in - // share-generics mode and whether the current crate can even have - // downstream crates. - let export_generics = - cx.tcx.sess.opts.share_generics() && cx.tcx.local_crate_exports_generics(); - - let cgu_name_builder = &mut CodegenUnitNameBuilder::new(cx.tcx); - let cgu_name_cache = &mut FxHashMap::default(); - - for mono_item in mono_items { - match mono_item.instantiation_mode(cx.tcx) { - InstantiationMode::GloballyShared { .. } => {} - InstantiationMode::LocalCopy => continue, - } - - let characteristic_def_id = characteristic_def_id_of_mono_item(cx.tcx, mono_item); - let is_volatile = is_incremental_build && mono_item.is_generic_fn(); - - let codegen_unit_name = match characteristic_def_id { - Some(def_id) => compute_codegen_unit_name( - cx.tcx, - cgu_name_builder, - def_id, - is_volatile, - cgu_name_cache, - ), - None => fallback_cgu_name(cgu_name_builder), - }; - - let codegen_unit = codegen_units - .entry(codegen_unit_name) - .or_insert_with(|| CodegenUnit::new(codegen_unit_name)); - - let mut can_be_internalized = true; - let (linkage, visibility) = mono_item_linkage_and_visibility( - cx.tcx, - &mono_item, - &mut can_be_internalized, - export_generics, - ); - if visibility == Visibility::Hidden && can_be_internalized { - internalization_candidates.insert(mono_item); - } - - codegen_unit.items_mut().insert(mono_item, (linkage, visibility)); - roots.insert(mono_item); - } - - // Always ensure we have at least one CGU; otherwise, if we have a - // crate with just types (for example), we could wind up with no CGU. - if codegen_units.is_empty() { - let codegen_unit_name = fallback_cgu_name(cgu_name_builder); - codegen_units.insert(codegen_unit_name, CodegenUnit::new(codegen_unit_name)); - } - - PreInliningPartitioning { - codegen_units: codegen_units.into_values().collect(), - roots, - internalization_candidates, - } - } - - fn merge_codegen_units( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: &mut PreInliningPartitioning<'tcx>, - ) { - merging::merge_codegen_units(cx, initial_partitioning); - } - - fn place_inlined_mono_items( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: PreInliningPartitioning<'tcx>, - ) -> PostInliningPartitioning<'tcx> { - let mut new_partitioning = Vec::new(); - let mut mono_item_placements = FxHashMap::default(); - - let PreInliningPartitioning { - codegen_units: initial_cgus, - roots, - internalization_candidates, - } = initial_partitioning; - - let single_codegen_unit = initial_cgus.len() == 1; - - for old_codegen_unit in initial_cgus { - // Collect all items that need to be available in this codegen unit. - let mut reachable = FxHashSet::default(); - for root in old_codegen_unit.items().keys() { - follow_inlining(*root, cx.inlining_map, &mut reachable); - } - - let mut new_codegen_unit = CodegenUnit::new(old_codegen_unit.name()); - - // Add all monomorphizations that are not already there. - for mono_item in reachable { - if let Some(linkage) = old_codegen_unit.items().get(&mono_item) { - // This is a root, just copy it over. - new_codegen_unit.items_mut().insert(mono_item, *linkage); - } else { - if roots.contains(&mono_item) { - bug!( - "GloballyShared mono-item inlined into other CGU: \ - {:?}", - mono_item - ); - } - - // This is a CGU-private copy. - new_codegen_unit - .items_mut() - .insert(mono_item, (Linkage::Internal, Visibility::Default)); - } - - if !single_codegen_unit { - // If there is more than one codegen unit, we need to keep track - // in which codegen units each monomorphization is placed. - match mono_item_placements.entry(mono_item) { - Entry::Occupied(e) => { - let placement = e.into_mut(); - debug_assert!(match *placement { - MonoItemPlacement::SingleCgu { cgu_name } => { - cgu_name != new_codegen_unit.name() - } - MonoItemPlacement::MultipleCgus => true, - }); - *placement = MonoItemPlacement::MultipleCgus; - } - Entry::Vacant(e) => { - e.insert(MonoItemPlacement::SingleCgu { - cgu_name: new_codegen_unit.name(), - }); - } - } - } - } - - new_partitioning.push(new_codegen_unit); - } - - return PostInliningPartitioning { - codegen_units: new_partitioning, - mono_item_placements, - internalization_candidates, - }; - - fn follow_inlining<'tcx>( - mono_item: MonoItem<'tcx>, - inlining_map: &InliningMap<'tcx>, - visited: &mut FxHashSet>, - ) { - if !visited.insert(mono_item) { - return; - } - - inlining_map.with_inlining_candidates(mono_item, |target| { - follow_inlining(target, inlining_map, visited); - }); - } - } - - fn internalize_symbols( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - partitioning: &mut PostInliningPartitioning<'tcx>, - ) { - if partitioning.codegen_units.len() == 1 { - // Fast path for when there is only one codegen unit. In this case we - // can internalize all candidates, since there is nowhere else they - // could be accessed from. - for cgu in &mut partitioning.codegen_units { - for candidate in &partitioning.internalization_candidates { - cgu.items_mut().insert(*candidate, (Linkage::Internal, Visibility::Default)); - } - } - - return; - } - - // Build a map from every monomorphization to all the monomorphizations that - // reference it. - let mut accessor_map: FxHashMap, Vec>> = Default::default(); - cx.inlining_map.iter_accesses(|accessor, accessees| { - for accessee in accessees { - accessor_map.entry(*accessee).or_default().push(accessor); - } - }); - - let mono_item_placements = &partitioning.mono_item_placements; - - // For each internalization candidates in each codegen unit, check if it is - // accessed from outside its defining codegen unit. - for cgu in &mut partitioning.codegen_units { - let home_cgu = MonoItemPlacement::SingleCgu { cgu_name: cgu.name() }; - - for (accessee, linkage_and_visibility) in cgu.items_mut() { - if !partitioning.internalization_candidates.contains(accessee) { - // This item is no candidate for internalizing, so skip it. - continue; - } - debug_assert_eq!(mono_item_placements[accessee], home_cgu); - - if let Some(accessors) = accessor_map.get(accessee) { - if accessors - .iter() - .filter_map(|accessor| { - // Some accessors might not have been - // instantiated. We can safely ignore those. - mono_item_placements.get(accessor) - }) - .any(|placement| *placement != home_cgu) - { - // Found an accessor from another CGU, so skip to the next - // item without marking this one as internal. - continue; - } - } - - // If we got here, we did not find any accesses from other CGUs, - // so it's fine to make this monomorphization internal. - *linkage_and_visibility = (Linkage::Internal, Visibility::Default); - } - } - } -} - -fn characteristic_def_id_of_mono_item<'tcx>( - tcx: TyCtxt<'tcx>, - mono_item: MonoItem<'tcx>, -) -> Option { - match mono_item { - MonoItem::Fn(instance) => { - let def_id = match instance.def { - ty::InstanceDef::Item(def) => def, - ty::InstanceDef::VTableShim(..) - | ty::InstanceDef::ReifyShim(..) - | ty::InstanceDef::FnPtrShim(..) - | ty::InstanceDef::ClosureOnceShim { .. } - | ty::InstanceDef::Intrinsic(..) - | ty::InstanceDef::DropGlue(..) - | ty::InstanceDef::Virtual(..) - | ty::InstanceDef::CloneShim(..) - | ty::InstanceDef::ThreadLocalShim(..) - | ty::InstanceDef::FnPtrAddrShim(..) => return None, - }; - - // If this is a method, we want to put it into the same module as - // its self-type. If the self-type does not provide a characteristic - // DefId, we use the location of the impl after all. - - if tcx.trait_of_item(def_id).is_some() { - let self_ty = instance.substs.type_at(0); - // This is a default implementation of a trait method. - return characteristic_def_id_of_type(self_ty).or(Some(def_id)); - } - - if let Some(impl_def_id) = tcx.impl_of_method(def_id) { - if tcx.sess.opts.incremental.is_some() - && tcx.trait_id_of_impl(impl_def_id) == tcx.lang_items().drop_trait() - { - // Put `Drop::drop` into the same cgu as `drop_in_place` - // since `drop_in_place` is the only thing that can - // call it. - return None; - } - - // When polymorphization is enabled, methods which do not depend on their generic - // parameters, but the self-type of their impl block do will fail to normalize. - if !tcx.sess.opts.unstable_opts.polymorphize || !instance.has_param() { - // This is a method within an impl, find out what the self-type is: - let impl_self_ty = tcx.subst_and_normalize_erasing_regions( - instance.substs, - ty::ParamEnv::reveal_all(), - tcx.type_of(impl_def_id), - ); - if let Some(def_id) = characteristic_def_id_of_type(impl_self_ty) { - return Some(def_id); - } - } - } - - Some(def_id) - } - MonoItem::Static(def_id) => Some(def_id), - MonoItem::GlobalAsm(item_id) => Some(item_id.owner_id.to_def_id()), - } -} - -fn compute_codegen_unit_name( - tcx: TyCtxt<'_>, - name_builder: &mut CodegenUnitNameBuilder<'_>, - def_id: DefId, - volatile: bool, - cache: &mut CguNameCache, -) -> Symbol { - // Find the innermost module that is not nested within a function. - let mut current_def_id = def_id; - let mut cgu_def_id = None; - // Walk backwards from the item we want to find the module for. - loop { - if current_def_id.is_crate_root() { - if cgu_def_id.is_none() { - // If we have not found a module yet, take the crate root. - cgu_def_id = Some(def_id.krate.as_def_id()); - } - break; - } else if tcx.def_kind(current_def_id) == DefKind::Mod { - if cgu_def_id.is_none() { - cgu_def_id = Some(current_def_id); - } - } else { - // If we encounter something that is not a module, throw away - // any module that we've found so far because we now know that - // it is nested within something else. - cgu_def_id = None; - } - - current_def_id = tcx.parent(current_def_id); - } - - let cgu_def_id = cgu_def_id.unwrap(); - - *cache.entry((cgu_def_id, volatile)).or_insert_with(|| { - let def_path = tcx.def_path(cgu_def_id); - - let components = def_path.data.iter().map(|part| match part.data.name() { - DefPathDataName::Named(name) => name, - DefPathDataName::Anon { .. } => unreachable!(), - }); - - let volatile_suffix = volatile.then_some("volatile"); - - name_builder.build_cgu_name(def_path.krate, components, volatile_suffix) - }) -} - -// Anything we can't find a proper codegen unit for goes into this. -fn fallback_cgu_name(name_builder: &mut CodegenUnitNameBuilder<'_>) -> Symbol { - name_builder.build_cgu_name(LOCAL_CRATE, &["fallback"], Some("cgu")) -} - -fn mono_item_linkage_and_visibility<'tcx>( - tcx: TyCtxt<'tcx>, - mono_item: &MonoItem<'tcx>, - can_be_internalized: &mut bool, - export_generics: bool, -) -> (Linkage, Visibility) { - if let Some(explicit_linkage) = mono_item.explicit_linkage(tcx) { - return (explicit_linkage, Visibility::Default); - } - let vis = mono_item_visibility(tcx, mono_item, can_be_internalized, export_generics); - (Linkage::External, vis) -} - -type CguNameCache = FxHashMap<(DefId, bool), Symbol>; - -fn static_visibility<'tcx>( - tcx: TyCtxt<'tcx>, - can_be_internalized: &mut bool, - def_id: DefId, -) -> Visibility { - if tcx.is_reachable_non_generic(def_id) { - *can_be_internalized = false; - default_visibility(tcx, def_id, false) - } else { - Visibility::Hidden - } -} - -fn mono_item_visibility<'tcx>( - tcx: TyCtxt<'tcx>, - mono_item: &MonoItem<'tcx>, - can_be_internalized: &mut bool, - export_generics: bool, -) -> Visibility { - let instance = match mono_item { - // This is pretty complicated; see below. - MonoItem::Fn(instance) => instance, - - // Misc handling for generics and such, but otherwise: - MonoItem::Static(def_id) => return static_visibility(tcx, can_be_internalized, *def_id), - MonoItem::GlobalAsm(item_id) => { - return static_visibility(tcx, can_be_internalized, item_id.owner_id.to_def_id()); - } - }; - - let def_id = match instance.def { - InstanceDef::Item(def_id) | InstanceDef::DropGlue(def_id, Some(_)) => def_id, - - // We match the visibility of statics here - InstanceDef::ThreadLocalShim(def_id) => { - return static_visibility(tcx, can_be_internalized, def_id); - } - - // These are all compiler glue and such, never exported, always hidden. - InstanceDef::VTableShim(..) - | InstanceDef::ReifyShim(..) - | InstanceDef::FnPtrShim(..) - | InstanceDef::Virtual(..) - | InstanceDef::Intrinsic(..) - | InstanceDef::ClosureOnceShim { .. } - | InstanceDef::DropGlue(..) - | InstanceDef::CloneShim(..) - | InstanceDef::FnPtrAddrShim(..) => return Visibility::Hidden, - }; - - // The `start_fn` lang item is actually a monomorphized instance of a - // function in the standard library, used for the `main` function. We don't - // want to export it so we tag it with `Hidden` visibility but this symbol - // is only referenced from the actual `main` symbol which we unfortunately - // don't know anything about during partitioning/collection. As a result we - // forcibly keep this symbol out of the `internalization_candidates` set. - // - // FIXME: eventually we don't want to always force this symbol to have - // hidden visibility, it should indeed be a candidate for - // internalization, but we have to understand that it's referenced - // from the `main` symbol we'll generate later. - // - // This may be fixable with a new `InstanceDef` perhaps? Unsure! - if tcx.lang_items().start_fn() == Some(def_id) { - *can_be_internalized = false; - return Visibility::Hidden; - } - - let is_generic = instance.substs.non_erasable_generics().next().is_some(); - - // Upstream `DefId` instances get different handling than local ones. - let Some(def_id) = def_id.as_local() else { - return if export_generics && is_generic { - // If it is an upstream monomorphization and we export generics, we must make - // it available to downstream crates. - *can_be_internalized = false; - default_visibility(tcx, def_id, true) - } else { - Visibility::Hidden - }; - }; - - if is_generic { - if export_generics { - if tcx.is_unreachable_local_definition(def_id) { - // This instance cannot be used from another crate. - Visibility::Hidden - } else { - // This instance might be useful in a downstream crate. - *can_be_internalized = false; - default_visibility(tcx, def_id.to_def_id(), true) - } - } else { - // We are not exporting generics or the definition is not reachable - // for downstream crates, we can internalize its instantiations. - Visibility::Hidden - } - } else { - // If this isn't a generic function then we mark this a `Default` if - // this is a reachable item, meaning that it's a symbol other crates may - // access when they link to us. - if tcx.is_reachable_non_generic(def_id.to_def_id()) { - *can_be_internalized = false; - debug_assert!(!is_generic); - return default_visibility(tcx, def_id.to_def_id(), false); - } - - // If this isn't reachable then we're gonna tag this with `Hidden` - // visibility. In some situations though we'll want to prevent this - // symbol from being internalized. - // - // There's two categories of items here: - // - // * First is weak lang items. These are basically mechanisms for - // libcore to forward-reference symbols defined later in crates like - // the standard library or `#[panic_handler]` definitions. The - // definition of these weak lang items needs to be referencable by - // libcore, so we're no longer a candidate for internalization. - // Removal of these functions can't be done by LLVM but rather must be - // done by the linker as it's a non-local decision. - // - // * Second is "std internal symbols". Currently this is primarily used - // for allocator symbols. Allocators are a little weird in their - // implementation, but the idea is that the compiler, at the last - // minute, defines an allocator with an injected object file. The - // `alloc` crate references these symbols (`__rust_alloc`) and the - // definition doesn't get hooked up until a linked crate artifact is - // generated. - // - // The symbols synthesized by the compiler (`__rust_alloc`) are thin - // veneers around the actual implementation, some other symbol which - // implements the same ABI. These symbols (things like `__rg_alloc`, - // `__rdl_alloc`, `__rde_alloc`, etc), are all tagged with "std - // internal symbols". - // - // The std-internal symbols here **should not show up in a dll as an - // exported interface**, so they return `false` from - // `is_reachable_non_generic` above and we'll give them `Hidden` - // visibility below. Like the weak lang items, though, we can't let - // LLVM internalize them as this decision is left up to the linker to - // omit them, so prevent them from being internalized. - let attrs = tcx.codegen_fn_attrs(def_id); - if attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) { - *can_be_internalized = false; - } - - Visibility::Hidden - } -} - -fn default_visibility(tcx: TyCtxt<'_>, id: DefId, is_generic: bool) -> Visibility { - if !tcx.sess.target.default_hidden_visibility { - return Visibility::Default; - } - - // Generic functions never have export-level C. - if is_generic { - return Visibility::Hidden; - } - - // Things with export level C don't get instantiated in - // downstream crates. - if !id.is_local() { - return Visibility::Hidden; - } - - // C-export level items remain at `Default`, all other internal - // items become `Hidden`. - match tcx.reachable_non_generics(id.krate).get(&id) { - Some(SymbolExportInfo { level: SymbolExportLevel::C, .. }) => Visibility::Default, - _ => Visibility::Hidden, - } -} diff --git a/compiler/rustc_monomorphize/src/partitioning/merging.rs b/compiler/rustc_monomorphize/src/partitioning/merging.rs deleted file mode 100644 index 5c524a18454ec..0000000000000 --- a/compiler/rustc_monomorphize/src/partitioning/merging.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::cmp; - -use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def_id::LOCAL_CRATE; -use rustc_middle::mir::mono::{CodegenUnit, CodegenUnitNameBuilder}; -use rustc_span::symbol::Symbol; - -use super::PartitioningCx; -use crate::partitioning::PreInliningPartitioning; - -pub fn merge_codegen_units<'tcx>( - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: &mut PreInliningPartitioning<'tcx>, -) { - assert!(cx.target_cgu_count >= 1); - let codegen_units = &mut initial_partitioning.codegen_units; - - // Note that at this point in time the `codegen_units` here may not be in a - // deterministic order (but we know they're deterministically the same set). - // We want this merging to produce a deterministic ordering of codegen units - // from the input. - // - // Due to basically how we've implemented the merging below (merge the two - // smallest into each other) we're sure to start off with a deterministic - // order (sorted by name). This'll mean that if two cgus have the same size - // the stable sort below will keep everything nice and deterministic. - codegen_units.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str())); - - // This map keeps track of what got merged into what. - let mut cgu_contents: FxHashMap> = - codegen_units.iter().map(|cgu| (cgu.name(), vec![cgu.name()])).collect(); - - // Merge the two smallest codegen units until the target size is reached. - while codegen_units.len() > cx.target_cgu_count { - // Sort small cgus to the back - codegen_units.sort_by_cached_key(|cgu| cmp::Reverse(cgu.size_estimate())); - let mut smallest = codegen_units.pop().unwrap(); - let second_smallest = codegen_units.last_mut().unwrap(); - - // Move the mono-items from `smallest` to `second_smallest` - second_smallest.modify_size_estimate(smallest.size_estimate()); - for (k, v) in smallest.items_mut().drain() { - second_smallest.items_mut().insert(k, v); - } - - // Record that `second_smallest` now contains all the stuff that was in - // `smallest` before. - let mut consumed_cgu_names = cgu_contents.remove(&smallest.name()).unwrap(); - cgu_contents.get_mut(&second_smallest.name()).unwrap().append(&mut consumed_cgu_names); - - debug!( - "CodegenUnit {} merged into CodegenUnit {}", - smallest.name(), - second_smallest.name() - ); - } - - let cgu_name_builder = &mut CodegenUnitNameBuilder::new(cx.tcx); - - if cx.tcx.sess.opts.incremental.is_some() { - // If we are doing incremental compilation, we want CGU names to - // reflect the path of the source level module they correspond to. - // For CGUs that contain the code of multiple modules because of the - // merging done above, we use a concatenation of the names of - // all contained CGUs. - let new_cgu_names: FxHashMap = cgu_contents - .into_iter() - // This `filter` makes sure we only update the name of CGUs that - // were actually modified by merging. - .filter(|(_, cgu_contents)| cgu_contents.len() > 1) - .map(|(current_cgu_name, cgu_contents)| { - let mut cgu_contents: Vec<&str> = cgu_contents.iter().map(|s| s.as_str()).collect(); - - // Sort the names, so things are deterministic and easy to - // predict. - - // We are sorting primitive &strs here so we can use unstable sort - cgu_contents.sort_unstable(); - - (current_cgu_name, cgu_contents.join("--")) - }) - .collect(); - - for cgu in codegen_units.iter_mut() { - if let Some(new_cgu_name) = new_cgu_names.get(&cgu.name()) { - if cx.tcx.sess.opts.unstable_opts.human_readable_cgu_names { - cgu.set_name(Symbol::intern(&new_cgu_name)); - } else { - // If we don't require CGU names to be human-readable, we - // use a fixed length hash of the composite CGU name - // instead. - let new_cgu_name = CodegenUnit::mangle_name(&new_cgu_name); - cgu.set_name(Symbol::intern(&new_cgu_name)); - } - } - } - } else { - // If we are compiling non-incrementally we just generate simple CGU - // names containing an index. - for (index, cgu) in codegen_units.iter_mut().enumerate() { - cgu.set_name(numbered_codegen_unit_name(cgu_name_builder, index)); - } - } -} - -fn numbered_codegen_unit_name( - name_builder: &mut CodegenUnitNameBuilder<'_>, - index: usize, -) -> Symbol { - name_builder.build_cgu_name_no_mangle(LOCAL_CRATE, &["cgu"], Some(index)) -} diff --git a/compiler/rustc_monomorphize/src/partitioning/mod.rs b/compiler/rustc_monomorphize/src/partitioning/mod.rs deleted file mode 100644 index eafe57a0c0207..0000000000000 --- a/compiler/rustc_monomorphize/src/partitioning/mod.rs +++ /dev/null @@ -1,672 +0,0 @@ -//! Partitioning Codegen Units for Incremental Compilation -//! ====================================================== -//! -//! The task of this module is to take the complete set of monomorphizations of -//! a crate and produce a set of codegen units from it, where a codegen unit -//! is a named set of (mono-item, linkage) pairs. That is, this module -//! decides which monomorphization appears in which codegen units with which -//! linkage. The following paragraphs describe some of the background on the -//! partitioning scheme. -//! -//! The most important opportunity for saving on compilation time with -//! incremental compilation is to avoid re-codegenning and re-optimizing code. -//! Since the unit of codegen and optimization for LLVM is "modules" or, how -//! we call them "codegen units", the particulars of how much time can be saved -//! by incremental compilation are tightly linked to how the output program is -//! partitioned into these codegen units prior to passing it to LLVM -- -//! especially because we have to treat codegen units as opaque entities once -//! they are created: There is no way for us to incrementally update an existing -//! LLVM module and so we have to build any such module from scratch if it was -//! affected by some change in the source code. -//! -//! From that point of view it would make sense to maximize the number of -//! codegen units by, for example, putting each function into its own module. -//! That way only those modules would have to be re-compiled that were actually -//! affected by some change, minimizing the number of functions that could have -//! been re-used but just happened to be located in a module that is -//! re-compiled. -//! -//! However, since LLVM optimization does not work across module boundaries, -//! using such a highly granular partitioning would lead to very slow runtime -//! code since it would effectively prohibit inlining and other inter-procedure -//! optimizations. We want to avoid that as much as possible. -//! -//! Thus we end up with a trade-off: The bigger the codegen units, the better -//! LLVM's optimizer can do its work, but also the smaller the compilation time -//! reduction we get from incremental compilation. -//! -//! Ideally, we would create a partitioning such that there are few big codegen -//! units with few interdependencies between them. For now though, we use the -//! following heuristic to determine the partitioning: -//! -//! - There are two codegen units for every source-level module: -//! - One for "stable", that is non-generic, code -//! - One for more "volatile" code, i.e., monomorphized instances of functions -//! defined in that module -//! -//! In order to see why this heuristic makes sense, let's take a look at when a -//! codegen unit can get invalidated: -//! -//! 1. The most straightforward case is when the BODY of a function or global -//! changes. Then any codegen unit containing the code for that item has to be -//! re-compiled. Note that this includes all codegen units where the function -//! has been inlined. -//! -//! 2. The next case is when the SIGNATURE of a function or global changes. In -//! this case, all codegen units containing a REFERENCE to that item have to be -//! re-compiled. This is a superset of case 1. -//! -//! 3. The final and most subtle case is when a REFERENCE to a generic function -//! is added or removed somewhere. Even though the definition of the function -//! might be unchanged, a new REFERENCE might introduce a new monomorphized -//! instance of this function which has to be placed and compiled somewhere. -//! Conversely, when removing a REFERENCE, it might have been the last one with -//! that particular set of generic arguments and thus we have to remove it. -//! -//! From the above we see that just using one codegen unit per source-level -//! module is not such a good idea, since just adding a REFERENCE to some -//! generic item somewhere else would invalidate everything within the module -//! containing the generic item. The heuristic above reduces this detrimental -//! side-effect of references a little by at least not touching the non-generic -//! code of the module. -//! -//! A Note on Inlining -//! ------------------ -//! As briefly mentioned above, in order for LLVM to be able to inline a -//! function call, the body of the function has to be available in the LLVM -//! module where the call is made. This has a few consequences for partitioning: -//! -//! - The partitioning algorithm has to take care of placing functions into all -//! codegen units where they should be available for inlining. It also has to -//! decide on the correct linkage for these functions. -//! -//! - The partitioning algorithm has to know which functions are likely to get -//! inlined, so it can distribute function instantiations accordingly. Since -//! there is no way of knowing for sure which functions LLVM will decide to -//! inline in the end, we apply a heuristic here: Only functions marked with -//! `#[inline]` are considered for inlining by the partitioner. The current -//! implementation will not try to determine if a function is likely to be -//! inlined by looking at the functions definition. -//! -//! Note though that as a side-effect of creating a codegen units per -//! source-level module, functions from the same module will be available for -//! inlining, even when they are not marked `#[inline]`. - -mod default; -mod merging; - -use std::cmp; -use std::fs::{self, File}; -use std::io::{BufWriter, Write}; -use std::path::{Path, PathBuf}; - -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::sync; -use rustc_hir::def_id::{DefIdSet, LOCAL_CRATE}; -use rustc_middle::mir; -use rustc_middle::mir::mono::MonoItem; -use rustc_middle::mir::mono::{CodegenUnit, Linkage}; -use rustc_middle::query::Providers; -use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::TyCtxt; -use rustc_session::config::{DumpMonoStatsFormat, SwitchWithOptPath}; -use rustc_span::symbol::Symbol; - -use crate::collector::InliningMap; -use crate::collector::{self, MonoItemCollectionMode}; -use crate::errors::{ - CouldntDumpMonoStats, SymbolAlreadyDefined, UnknownCguCollectionMode, UnknownPartitionStrategy, -}; - -enum Partitioner { - Default(default::DefaultPartitioning), - // Other partitioning strategies can go here. - Unknown, -} - -impl<'tcx> Partition<'tcx> for Partitioner { - fn place_root_mono_items( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - mono_items: &mut I, - ) -> PreInliningPartitioning<'tcx> - where - I: Iterator>, - { - match self { - Partitioner::Default(partitioner) => partitioner.place_root_mono_items(cx, mono_items), - Partitioner::Unknown => cx.tcx.sess.emit_fatal(UnknownPartitionStrategy), - } - } - - fn merge_codegen_units( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: &mut PreInliningPartitioning<'tcx>, - ) { - match self { - Partitioner::Default(partitioner) => { - partitioner.merge_codegen_units(cx, initial_partitioning) - } - Partitioner::Unknown => cx.tcx.sess.emit_fatal(UnknownPartitionStrategy), - } - } - - fn place_inlined_mono_items( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: PreInliningPartitioning<'tcx>, - ) -> PostInliningPartitioning<'tcx> { - match self { - Partitioner::Default(partitioner) => { - partitioner.place_inlined_mono_items(cx, initial_partitioning) - } - Partitioner::Unknown => cx.tcx.sess.emit_fatal(UnknownPartitionStrategy), - } - } - - fn internalize_symbols( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - post_inlining_partitioning: &mut PostInliningPartitioning<'tcx>, - ) { - match self { - Partitioner::Default(partitioner) => { - partitioner.internalize_symbols(cx, post_inlining_partitioning) - } - Partitioner::Unknown => cx.tcx.sess.emit_fatal(UnknownPartitionStrategy), - } - } -} - -pub struct PartitioningCx<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - target_cgu_count: usize, - inlining_map: &'a InliningMap<'tcx>, -} - -trait Partition<'tcx> { - fn place_root_mono_items( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - mono_items: &mut I, - ) -> PreInliningPartitioning<'tcx> - where - I: Iterator>; - - fn merge_codegen_units( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: &mut PreInliningPartitioning<'tcx>, - ); - - fn place_inlined_mono_items( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - initial_partitioning: PreInliningPartitioning<'tcx>, - ) -> PostInliningPartitioning<'tcx>; - - fn internalize_symbols( - &mut self, - cx: &PartitioningCx<'_, 'tcx>, - partitioning: &mut PostInliningPartitioning<'tcx>, - ); -} - -fn get_partitioner(tcx: TyCtxt<'_>) -> Partitioner { - let strategy = match &tcx.sess.opts.unstable_opts.cgu_partitioning_strategy { - None => "default", - Some(s) => &s[..], - }; - - match strategy { - "default" => Partitioner::Default(default::DefaultPartitioning), - _ => Partitioner::Unknown, - } -} - -pub fn partition<'tcx, I>( - tcx: TyCtxt<'tcx>, - mono_items: &mut I, - max_cgu_count: usize, - inlining_map: &InliningMap<'tcx>, -) -> Vec> -where - I: Iterator>, -{ - let _prof_timer = tcx.prof.generic_activity("cgu_partitioning"); - - let mut partitioner = get_partitioner(tcx); - let cx = &PartitioningCx { tcx, target_cgu_count: max_cgu_count, inlining_map }; - // In the first step, we place all regular monomorphizations into their - // respective 'home' codegen unit. Regular monomorphizations are all - // functions and statics defined in the local crate. - let mut initial_partitioning = { - let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_roots"); - partitioner.place_root_mono_items(cx, mono_items) - }; - - for cgu in &mut initial_partitioning.codegen_units { - cgu.create_size_estimate(tcx); - } - - debug_dump(tcx, "INITIAL PARTITIONING", &initial_partitioning.codegen_units); - - // Merge until we have at most `max_cgu_count` codegen units. - { - let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_merge_cgus"); - partitioner.merge_codegen_units(cx, &mut initial_partitioning); - debug_dump(tcx, "POST MERGING", &initial_partitioning.codegen_units); - } - - // In the next step, we use the inlining map to determine which additional - // monomorphizations have to go into each codegen unit. These additional - // monomorphizations can be drop-glue, functions from external crates, and - // local functions the definition of which is marked with `#[inline]`. - let mut post_inlining = { - let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_place_inline_items"); - partitioner.place_inlined_mono_items(cx, initial_partitioning) - }; - - for cgu in &mut post_inlining.codegen_units { - cgu.create_size_estimate(tcx); - } - - debug_dump(tcx, "POST INLINING", &post_inlining.codegen_units); - - // Next we try to make as many symbols "internal" as possible, so LLVM has - // more freedom to optimize. - if !tcx.sess.link_dead_code() { - let _prof_timer = tcx.prof.generic_activity("cgu_partitioning_internalize_symbols"); - partitioner.internalize_symbols(cx, &mut post_inlining); - } - - let instrument_dead_code = - tcx.sess.instrument_coverage() && !tcx.sess.instrument_coverage_except_unused_functions(); - - if instrument_dead_code { - assert!( - post_inlining.codegen_units.len() > 0, - "There must be at least one CGU that code coverage data can be generated in." - ); - - // Find the smallest CGU that has exported symbols and put the dead - // function stubs in that CGU. We look for exported symbols to increase - // the likelihood the linker won't throw away the dead functions. - // FIXME(#92165): In order to truly resolve this, we need to make sure - // the object file (CGU) containing the dead function stubs is included - // in the final binary. This will probably require forcing these - // function symbols to be included via `-u` or `/include` linker args. - let mut cgus: Vec<_> = post_inlining.codegen_units.iter_mut().collect(); - cgus.sort_by_key(|cgu| cgu.size_estimate()); - - let dead_code_cgu = - if let Some(cgu) = cgus.into_iter().rev().find(|cgu| { - cgu.items().iter().any(|(_, (linkage, _))| *linkage == Linkage::External) - }) { - cgu - } else { - // If there are no CGUs that have externally linked items, - // then we just pick the first CGU as a fallback. - &mut post_inlining.codegen_units[0] - }; - dead_code_cgu.make_code_coverage_dead_code_cgu(); - } - - // Finally, sort by codegen unit name, so that we get deterministic results. - let PostInliningPartitioning { - codegen_units: mut result, - mono_item_placements: _, - internalization_candidates: _, - } = post_inlining; - - result.sort_by(|a, b| a.name().as_str().cmp(b.name().as_str())); - - debug_dump(tcx, "FINAL", &result); - - result -} - -pub struct PreInliningPartitioning<'tcx> { - codegen_units: Vec>, - roots: FxHashSet>, - internalization_candidates: FxHashSet>, -} - -/// For symbol internalization, we need to know whether a symbol/mono-item is -/// accessed from outside the codegen unit it is defined in. This type is used -/// to keep track of that. -#[derive(Clone, PartialEq, Eq, Debug)] -enum MonoItemPlacement { - SingleCgu { cgu_name: Symbol }, - MultipleCgus, -} - -struct PostInliningPartitioning<'tcx> { - codegen_units: Vec>, - mono_item_placements: FxHashMap, MonoItemPlacement>, - internalization_candidates: FxHashSet>, -} - -fn debug_dump<'a, 'tcx: 'a>(tcx: TyCtxt<'tcx>, label: &str, cgus: &[CodegenUnit<'tcx>]) { - let dump = move || { - use std::fmt::Write; - - let num_cgus = cgus.len(); - let max = cgus.iter().map(|cgu| cgu.size_estimate()).max().unwrap(); - let min = cgus.iter().map(|cgu| cgu.size_estimate()).min().unwrap(); - let ratio = max as f64 / min as f64; - - let s = &mut String::new(); - let _ = writeln!( - s, - "{label} ({num_cgus} CodegenUnits, max={max}, min={min}, max/min={ratio:.1}):" - ); - for cgu in cgus { - let _ = - writeln!(s, "CodegenUnit {} estimated size {}:", cgu.name(), cgu.size_estimate()); - - for (mono_item, linkage) in cgu.items() { - let symbol_name = mono_item.symbol_name(tcx).name; - let symbol_hash_start = symbol_name.rfind('h'); - let symbol_hash = symbol_hash_start.map_or("", |i| &symbol_name[i..]); - - let _ = with_no_trimmed_paths!(writeln!( - s, - " - {} [{:?}] [{}] estimated size {}", - mono_item, - linkage, - symbol_hash, - mono_item.size_estimate(tcx) - )); - } - - let _ = writeln!(s); - } - - std::mem::take(s) - }; - - debug!("{}", dump()); -} - -#[inline(never)] // give this a place in the profiler -fn assert_symbols_are_distinct<'a, 'tcx, I>(tcx: TyCtxt<'tcx>, mono_items: I) -where - I: Iterator>, - 'tcx: 'a, -{ - let _prof_timer = tcx.prof.generic_activity("assert_symbols_are_distinct"); - - let mut symbols: Vec<_> = - mono_items.map(|mono_item| (mono_item, mono_item.symbol_name(tcx))).collect(); - - symbols.sort_by_key(|sym| sym.1); - - for &[(mono_item1, ref sym1), (mono_item2, ref sym2)] in symbols.array_windows() { - if sym1 == sym2 { - let span1 = mono_item1.local_span(tcx); - let span2 = mono_item2.local_span(tcx); - - // Deterministically select one of the spans for error reporting - let span = match (span1, span2) { - (Some(span1), Some(span2)) => { - Some(if span1.lo().0 > span2.lo().0 { span1 } else { span2 }) - } - (span1, span2) => span1.or(span2), - }; - - tcx.sess.emit_fatal(SymbolAlreadyDefined { span, symbol: sym1.to_string() }); - } - } -} - -fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> (&DefIdSet, &[CodegenUnit<'_>]) { - let collection_mode = match tcx.sess.opts.unstable_opts.print_mono_items { - Some(ref s) => { - let mode = s.to_lowercase(); - let mode = mode.trim(); - if mode == "eager" { - MonoItemCollectionMode::Eager - } else { - if mode != "lazy" { - tcx.sess.emit_warning(UnknownCguCollectionMode { mode }); - } - - MonoItemCollectionMode::Lazy - } - } - None => { - if tcx.sess.link_dead_code() { - MonoItemCollectionMode::Eager - } else { - MonoItemCollectionMode::Lazy - } - } - }; - - let (items, inlining_map) = collector::collect_crate_mono_items(tcx, collection_mode); - - tcx.sess.abort_if_errors(); - - let (codegen_units, _) = tcx.sess.time("partition_and_assert_distinct_symbols", || { - sync::join( - || { - let mut codegen_units = partition( - tcx, - &mut items.iter().copied(), - tcx.sess.codegen_units(), - &inlining_map, - ); - codegen_units[0].make_primary(); - &*tcx.arena.alloc_from_iter(codegen_units) - }, - || assert_symbols_are_distinct(tcx, items.iter()), - ) - }); - - if tcx.prof.enabled() { - // Record CGU size estimates for self-profiling. - for cgu in codegen_units { - tcx.prof.artifact_size( - "codegen_unit_size_estimate", - cgu.name().as_str(), - cgu.size_estimate() as u64, - ); - } - } - - let mono_items: DefIdSet = items - .iter() - .filter_map(|mono_item| match *mono_item { - MonoItem::Fn(ref instance) => Some(instance.def_id()), - MonoItem::Static(def_id) => Some(def_id), - _ => None, - }) - .collect(); - - // Output monomorphization stats per def_id - if let SwitchWithOptPath::Enabled(ref path) = tcx.sess.opts.unstable_opts.dump_mono_stats { - if let Err(err) = - dump_mono_items_stats(tcx, &codegen_units, path, tcx.crate_name(LOCAL_CRATE)) - { - tcx.sess.emit_fatal(CouldntDumpMonoStats { error: err.to_string() }); - } - } - - if tcx.sess.opts.unstable_opts.print_mono_items.is_some() { - let mut item_to_cgus: FxHashMap<_, Vec<_>> = Default::default(); - - for cgu in codegen_units { - for (&mono_item, &linkage) in cgu.items() { - item_to_cgus.entry(mono_item).or_default().push((cgu.name(), linkage)); - } - } - - let mut item_keys: Vec<_> = items - .iter() - .map(|i| { - let mut output = with_no_trimmed_paths!(i.to_string()); - output.push_str(" @@"); - let mut empty = Vec::new(); - let cgus = item_to_cgus.get_mut(i).unwrap_or(&mut empty); - cgus.sort_by_key(|(name, _)| *name); - cgus.dedup(); - for &(ref cgu_name, (linkage, _)) in cgus.iter() { - output.push(' '); - output.push_str(cgu_name.as_str()); - - let linkage_abbrev = match linkage { - Linkage::External => "External", - Linkage::AvailableExternally => "Available", - Linkage::LinkOnceAny => "OnceAny", - Linkage::LinkOnceODR => "OnceODR", - Linkage::WeakAny => "WeakAny", - Linkage::WeakODR => "WeakODR", - Linkage::Appending => "Appending", - Linkage::Internal => "Internal", - Linkage::Private => "Private", - Linkage::ExternalWeak => "ExternalWeak", - Linkage::Common => "Common", - }; - - output.push('['); - output.push_str(linkage_abbrev); - output.push(']'); - } - output - }) - .collect(); - - item_keys.sort(); - - for item in item_keys { - println!("MONO_ITEM {item}"); - } - } - - (tcx.arena.alloc(mono_items), codegen_units) -} - -/// Outputs stats about instantiation counts and estimated size, per `MonoItem`'s -/// def, to a file in the given output directory. -fn dump_mono_items_stats<'tcx>( - tcx: TyCtxt<'tcx>, - codegen_units: &[CodegenUnit<'tcx>], - output_directory: &Option, - crate_name: Symbol, -) -> Result<(), Box> { - let output_directory = if let Some(ref directory) = output_directory { - fs::create_dir_all(directory)?; - directory - } else { - Path::new(".") - }; - - let format = tcx.sess.opts.unstable_opts.dump_mono_stats_format; - let ext = format.extension(); - let filename = format!("{crate_name}.mono_items.{ext}"); - let output_path = output_directory.join(&filename); - let file = File::create(&output_path)?; - let mut file = BufWriter::new(file); - - // Gather instantiated mono items grouped by def_id - let mut items_per_def_id: FxHashMap<_, Vec<_>> = Default::default(); - for cgu in codegen_units { - for (&mono_item, _) in cgu.items() { - // Avoid variable-sized compiler-generated shims - if mono_item.is_user_defined() { - items_per_def_id.entry(mono_item.def_id()).or_default().push(mono_item); - } - } - } - - #[derive(serde::Serialize)] - struct MonoItem { - name: String, - instantiation_count: usize, - size_estimate: usize, - total_estimate: usize, - } - - // Output stats sorted by total instantiated size, from heaviest to lightest - let mut stats: Vec<_> = items_per_def_id - .into_iter() - .map(|(def_id, items)| { - let name = with_no_trimmed_paths!(tcx.def_path_str(def_id)); - let instantiation_count = items.len(); - let size_estimate = items[0].size_estimate(tcx); - let total_estimate = instantiation_count * size_estimate; - MonoItem { name, instantiation_count, size_estimate, total_estimate } - }) - .collect(); - stats.sort_unstable_by_key(|item| cmp::Reverse(item.total_estimate)); - - if !stats.is_empty() { - match format { - DumpMonoStatsFormat::Json => serde_json::to_writer(file, &stats)?, - DumpMonoStatsFormat::Markdown => { - writeln!( - file, - "| Item | Instantiation count | Estimated Cost Per Instantiation | Total Estimated Cost |" - )?; - writeln!(file, "| --- | ---: | ---: | ---: |")?; - - for MonoItem { name, instantiation_count, size_estimate, total_estimate } in stats { - writeln!( - file, - "| `{name}` | {instantiation_count} | {size_estimate} | {total_estimate} |" - )?; - } - } - } - } - - Ok(()) -} - -fn codegened_and_inlined_items(tcx: TyCtxt<'_>, (): ()) -> &DefIdSet { - let (items, cgus) = tcx.collect_and_partition_mono_items(()); - let mut visited = DefIdSet::default(); - let mut result = items.clone(); - - for cgu in cgus { - for (item, _) in cgu.items() { - if let MonoItem::Fn(ref instance) = item { - let did = instance.def_id(); - if !visited.insert(did) { - continue; - } - let body = tcx.instance_mir(instance.def); - for block in body.basic_blocks.iter() { - for statement in &block.statements { - let mir::StatementKind::Coverage(_) = statement.kind else { continue }; - let scope = statement.source_info.scope; - if let Some(inlined) = scope.inlined_instance(&body.source_scopes) { - result.insert(inlined.def_id()); - } - } - } - } - } - } - - tcx.arena.alloc(result) -} - -pub fn provide(providers: &mut Providers) { - providers.collect_and_partition_mono_items = collect_and_partition_mono_items; - providers.codegened_and_inlined_items = codegened_and_inlined_items; - - providers.is_codegened_item = |tcx, def_id| { - let (all_mono_items, _) = tcx.collect_and_partition_mono_items(()); - all_mono_items.contains(&def_id) - }; - - providers.codegen_unit = |tcx, name| { - let (_, all) = tcx.collect_and_partition_mono_items(()); - all.iter() - .find(|cgu| cgu.name() == name) - .unwrap_or_else(|| panic!("failed to find cgu with name {name:?}")) - }; -} diff --git a/compiler/rustc_monomorphize/src/polymorphize.rs b/compiler/rustc_monomorphize/src/polymorphize.rs index 88a3e028527a3..a8b7a0dbb681f 100644 --- a/compiler/rustc_monomorphize/src/polymorphize.rs +++ b/compiler/rustc_monomorphize/src/polymorphize.rs @@ -14,9 +14,8 @@ use rustc_middle::mir::{ use rustc_middle::query::Providers; use rustc_middle::ty::{ self, - subst::SubstsRef, visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor}, - Const, Ty, TyCtxt, UnusedGenericParams, + Const, GenericArgsRef, Ty, TyCtxt, UnusedGenericParams, }; use rustc_span::symbol::sym; use std::ops::ControlFlow; @@ -144,7 +143,7 @@ fn mark_used_by_default_parameters<'tcx>( | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::AssocTy @@ -163,7 +162,6 @@ fn mark_used_by_default_parameters<'tcx>( | DefKind::AnonConst | DefKind::InlineConst | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Field | DefKind::LifetimeParam | DefKind::GlobalAsm @@ -230,12 +228,12 @@ struct MarkUsedGenericParams<'a, 'tcx> { impl<'a, 'tcx> MarkUsedGenericParams<'a, 'tcx> { /// Invoke `unused_generic_params` on a body contained within the current item (e.g. /// a closure, generator or constant). - #[instrument(level = "debug", skip(self, def_id, substs))] - fn visit_child_body(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) { + #[instrument(level = "debug", skip(self, def_id, args))] + fn visit_child_body(&mut self, def_id: DefId, args: GenericArgsRef<'tcx>) { let instance = ty::InstanceDef::Item(def_id); let unused = self.tcx.unused_generic_params(instance); debug!(?self.unused_parameters, ?unused); - for (i, arg) in substs.iter().enumerate() { + for (i, arg) in args.iter().enumerate() { let i = i.try_into().unwrap(); if unused.is_used(i) { arg.visit_with(self); @@ -253,9 +251,9 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { if matches!(def_kind, DefKind::Closure | DefKind::Generator) { // Skip visiting the closure/generator that is currently being processed. This only // happens because the first argument to the closure is a reference to itself and - // that will call `visit_substs`, resulting in each generic parameter captured being + // that will call `visit_args`, resulting in each generic parameter captured being // considered used by default. - debug!("skipping closure substs"); + debug!("skipping closure args"); return; } } @@ -268,12 +266,12 @@ impl<'a, 'tcx> Visitor<'tcx> for MarkUsedGenericParams<'a, 'tcx> { ConstantKind::Ty(c) => { c.visit_with(self); } - ConstantKind::Unevaluated(mir::UnevaluatedConst { def, substs: _, promoted }, ty) => { + ConstantKind::Unevaluated(mir::UnevaluatedConst { def, args: _, promoted }, ty) => { // Avoid considering `T` unused when constants are of the form: // `>::foo::promoted[p]` if let Some(p) = promoted { if self.def_id == def && !self.tcx.generics_of(def).has_self { - // If there is a promoted, don't look at the substs - since it will always contain + // If there is a promoted, don't look at the args - since it will always contain // the generic parameters, instead, traverse the promoted MIR. let promoted = self.tcx.promoted_mir(def); self.visit_body(&promoted[p]); @@ -304,10 +302,10 @@ impl<'a, 'tcx> TypeVisitor> for MarkUsedGenericParams<'a, 'tcx> { self.unused_parameters.mark_used(param.index); ControlFlow::Continue(()) } - ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, substs }) + ty::ConstKind::Unevaluated(ty::UnevaluatedConst { def, args }) if matches!(self.tcx.def_kind(def), DefKind::AnonConst) => { - self.visit_child_body(def, substs); + self.visit_child_body(def, args); ControlFlow::Continue(()) } _ => c.super_visit_with(self), @@ -321,7 +319,7 @@ impl<'a, 'tcx> TypeVisitor> for MarkUsedGenericParams<'a, 'tcx> { } match *ty.kind() { - ty::Closure(def_id, substs) | ty::Generator(def_id, substs, ..) => { + ty::Closure(def_id, args) | ty::Generator(def_id, args, ..) => { debug!(?def_id); // Avoid cycle errors with generators. if def_id == self.def_id { @@ -330,7 +328,7 @@ impl<'a, 'tcx> TypeVisitor> for MarkUsedGenericParams<'a, 'tcx> { // Consider any generic parameters used by any closures/generators as used in the // parent. - self.visit_child_body(def_id, substs); + self.visit_child_body(def_id, args); ControlFlow::Continue(()) } ty::Param(param) => { diff --git a/compiler/rustc_monomorphize/src/util.rs b/compiler/rustc_monomorphize/src/util.rs index d12bfc6f6bb1d..a3433d3d13dba 100644 --- a/compiler/rustc_monomorphize/src/util.rs +++ b/compiler/rustc_monomorphize/src/util.rs @@ -27,14 +27,14 @@ pub(crate) fn dump_closure_profile<'tcx>(tcx: TyCtxt<'tcx>, closure_instance: In typeck_results.closure_size_eval[&closure_def_id]; let before_feature_tys = tcx.subst_and_normalize_erasing_regions( - closure_instance.substs, + closure_instance.args, param_env, - ty::EarlyBinder(before_feature_tys), + ty::EarlyBinder::bind(before_feature_tys), ); let after_feature_tys = tcx.subst_and_normalize_erasing_regions( - closure_instance.substs, + closure_instance.args, param_env, - ty::EarlyBinder(after_feature_tys), + ty::EarlyBinder::bind(after_feature_tys), ); let new_size = tcx diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 2d0f466e236ce..34cc0998c9b8c 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -1,230 +1,302 @@ -parse_struct_literal_body_without_path = - struct literal body without path - .suggestion = you might have forgotten to add the struct literal inside the block +parse_add_paren = try adding parentheses -parse_struct_literal_needing_parens = - invalid struct literal - .suggestion = you might need to surround the struct literal in parentheses +parse_ambiguous_missing_keyword_for_item_definition = missing `fn` or `struct` for function or struct definition + .suggestion = if you meant to call a macro, try + .help = if you meant to call a macro, remove the `pub` and add a trailing `!` after the identifier -parse_maybe_report_ambiguous_plus = - ambiguous `+` in a type - .suggestion = use parentheses to disambiguate +parse_ambiguous_range_pattern = the range pattern here has ambiguous interpretation + .suggestion = add parentheses to clarify the precedence -parse_maybe_recover_from_bad_type_plus = - expected a path on the left-hand side of `+`, not `{$ty}` +parse_array_brackets_instead_of_braces = this is a block expression, not an array + .suggestion = to make an array, use square brackets instead of curly braces -parse_add_paren = try adding parentheses +parse_assignment_else_not_allowed = ... else {"{"} ... {"}"} is not allowed -parse_forgot_paren = perhaps you forgot parentheses? +parse_assoc_lifetime = associated lifetimes are not supported + .label = the lifetime is given here + .help = if you meant to specify a trait object, write `dyn Trait + 'lifetime` -parse_expect_path = expected a path +parse_associated_static_item_not_allowed = associated `static` items are not allowed -parse_maybe_recover_from_bad_qpath_stage_2 = - missing angle brackets in associated item path - .suggestion = try: `{$ty}` +parse_async_block_in_2015 = `async` blocks are only allowed in Rust 2018 or later -parse_incorrect_semicolon = - expected item, found `;` - .suggestion = remove this semicolon - .help = {$name} declarations are not followed by a semicolon +parse_async_fn_in_2015 = `async fn` is not permitted in Rust 2015 + .label = to use `async fn`, switch to Rust 2018 or later -parse_incorrect_use_of_await = - incorrect use of `await` - .parentheses_suggestion = `await` is not a method call, remove the parentheses - .postfix_suggestion = `await` is a postfix operation +parse_async_move_block_in_2015 = `async move` blocks are only allowed in Rust 2018 or later -parse_in_in_typo = - expected iterable, found keyword `in` - .suggestion = remove the duplicated `in` +parse_async_move_order_incorrect = the order of `move` and `async` is incorrect + .suggestion = try switching the order -parse_invalid_variable_declaration = - invalid variable declaration +parse_attr_after_generic = trailing attribute after generic parameter + .label = attributes must go before parameters -parse_switch_mut_let_order = - switch the order of `mut` and `let` -parse_missing_let_before_mut = missing keyword -parse_use_let_not_auto = write `let` instead of `auto` to introduce a new variable -parse_use_let_not_var = write `let` instead of `var` to introduce a new variable +parse_attr_without_generics = attribute without generic parameters + .label = attributes are only permitted when preceding parameters -parse_invalid_comparison_operator = invalid comparison operator `{$invalid}` - .use_instead = `{$invalid}` is not a valid comparison operator, use `{$correct}` - .spaceship_operator_invalid = `<=>` is not a valid comparison operator, use `std::cmp::Ordering` +parse_attribute_on_param_type = attributes cannot be applied to a function parameter's type + .label = attributes are not allowed here -parse_invalid_logical_operator = `{$incorrect}` is not a logical operator - .note = unlike in e.g., Python and PHP, `&&` and `||` are used for logical operators - .use_amp_amp_for_conjunction = use `&&` to perform logical conjunction - .use_pipe_pipe_for_disjunction = use `||` to perform logical disjunction +parse_bad_assoc_type_bounds = bounds on associated types do not belong here + .label = belongs in `where` clause -parse_tilde_is_not_unary_operator = `~` cannot be used as a unary operator - .suggestion = use `!` to perform bitwise not +parse_bad_item_kind = {$descr} is not supported in {$ctx} + .help = consider moving the {$descr} out to a nearby module scope -parse_unexpected_if_with_if = unexpected `if` in the condition expression - .suggestion = remove the `if` +parse_bad_return_type_notation_dotdot = + return type notation uses `()` instead of `(..)` for elided arguments + .suggestion = remove the `..` -parse_unexpected_token_after_not = unexpected {$negated_desc} after identifier -parse_unexpected_token_after_not_bitwise = use `!` to perform bitwise not -parse_unexpected_token_after_not_logical = use `!` to perform logical negation -parse_unexpected_token_after_not_default = use `!` to perform logical negation or bitwise not +parse_bad_return_type_notation_output = + return type not allowed with return type notation + .suggestion = remove the return type -parse_malformed_loop_label = malformed loop label - .suggestion = use the correct loop label format +parse_bare_cr = {$double_quotes -> + [true] bare CR not allowed in string, use `\r` instead + *[false] character constant must be escaped: `\r` + } + .escape = escape the character -parse_lifetime_in_borrow_expression = borrow expressions cannot be annotated with lifetimes - .suggestion = remove the lifetime annotation - .label = annotated with lifetime here +parse_bare_cr_in_raw_string = bare CR not allowed in raw string -parse_field_expression_with_generic = field expressions cannot have generic arguments +parse_binary_float_literal_not_supported = binary float literal is not supported +parse_bounds_not_allowed_on_trait_aliases = bounds are not allowed on trait aliases -parse_macro_invocation_with_qualified_path = macros cannot use qualified paths +parse_box_not_pat = expected pattern, found {$descr} + .note = `box` is a reserved keyword + .suggestion = escape `box` to use it as an identifier -parse_unexpected_token_after_label = expected `while`, `for`, `loop` or `{"{"}` after a label - .suggestion_remove_label = consider removing the label - .suggestion_enclose_in_block = consider enclosing expression in a block +parse_box_syntax_removed = `box_syntax` has been removed + .suggestion = use `Box::new()` instead -parse_require_colon_after_labeled_expression = labeled expression must be followed by `:` - .note = labels are used before loops and blocks, allowing e.g., `break 'label` to them - .label = the label - .suggestion = add `:` after the label +parse_cannot_be_raw_ident = `{$ident}` cannot be a raw identifier -parse_do_catch_syntax_removed = found removed `do catch` syntax - .note = following RFC #2388, the new non-placeholder syntax is `try` - .suggestion = replace with the new syntax +parse_catch_after_try = keyword `catch` cannot follow a `try` block + .help = try using `match` on the result of the `try` block instead -parse_float_literal_requires_integer_part = float literals must have an integer part - .suggestion = must have an integer part +parse_cfg_attr_bad_delim = wrong `cfg_attr` delimiters +parse_colon_as_semi = statements are terminated with a semicolon + .suggestion = use a semicolon instead -parse_invalid_int_literal_width = invalid width `{$width}` for integer literal - .help = valid widths are 8, 16, 32, 64 and 128 +parse_comma_after_base_struct = cannot use a comma after the base struct + .note = the base struct must always be the last field + .suggestion = remove this comma -parse_invalid_num_literal_base_prefix = invalid base prefix for number literal - .note = base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase - .suggestion = try making the prefix lowercase +parse_comparison_interpreted_as_generic = + `<` is interpreted as a start of generic arguments for `{$type}`, not a comparison + .label_args = interpreted as generic arguments + .label_comparison = not interpreted as comparison + .suggestion = try comparing the cast value -parse_invalid_num_literal_suffix = invalid suffix `{$suffix}` for number literal - .label = invalid suffix `{$suffix}` - .help = the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) +parse_comparison_operators_cannot_be_chained = comparison operators cannot be chained + .sugg_parentheses_for_function_args = or use `(...)` if you meant to specify fn arguments + .sugg_split_comparison = split the comparison into two + .sugg_parenthesize = parenthesize the comparison +parse_compound_assignment_expression_in_let = can't reassign to an uninitialized variable + .suggestion = initialize the variable + .help = if you meant to overwrite, remove the `let` binding -parse_invalid_float_literal_width = invalid width `{$width}` for float literal - .help = valid widths are 32 and 64 +parse_const_bounds_missing_tilde = const bounds must start with `~` + .suggestion = add `~` -parse_invalid_float_literal_suffix = invalid suffix `{$suffix}` for float literal - .label = invalid suffix `{$suffix}` - .help = valid suffixes are `f32` and `f64` +parse_const_generic_without_braces = expressions must be enclosed in braces to be used as const generic arguments + .suggestion = enclose the `const` expression in braces -parse_int_literal_too_large = integer literal is too large +parse_const_global_cannot_be_mutable = const globals cannot be mutable + .label = cannot be mutable + .suggestion = you might want to declare a static instead -parse_missing_semicolon_before_array = expected `;`, found `[` - .suggestion = consider adding `;` here +parse_const_let_mutually_exclusive = `const` and `let` are mutually exclusive + .suggestion = remove `let` -parse_invalid_block_macro_segment = cannot use a `block` macro fragment here - .label = the `block` fragment is within this context +parse_cr_doc_comment = bare CR not allowed in {$block -> + [true] block doc-comment + *[false] doc-comment +} -parse_expect_dotdot_not_dotdotdot = expected `..`, found `...` - .suggestion = use `..` to fill in the rest of the fields +parse_default_not_followed_by_item = `default` is not followed by an item + .label = the `default` qualifier + .note = only `fn`, `const`, `type`, or `impl` items may be prefixed by `default` -parse_if_expression_missing_then_block = this `if` expression is missing a block after the condition - .add_then_block = add a block here - .condition_possibly_unfinished = this binary operation is possibly unfinished +parse_do_catch_syntax_removed = found removed `do catch` syntax + .note = following RFC #2388, the new non-placeholder syntax is `try` + .suggestion = replace with the new syntax -parse_if_expression_missing_condition = missing condition for `if` expression - .condition_label = expected condition here - .block_label = if this block is the condition of the `if` expression, then it must be followed by another block +parse_doc_comment_does_not_document_anything = found a documentation comment that doesn't document anything + .help = doc comments must come before what they document, if a comment was intended use `//` + .suggestion = missing comma here -parse_expected_expression_found_let = expected expression, found `let` statement +parse_doc_comment_on_param_type = documentation comments cannot be applied to a function parameter's type + .label = doc comments are not allowed here -parse_expect_eq_instead_of_eqeq = expected `=`, found `==` - .suggestion = consider using `=` here +parse_dot_dot_dot_for_remaining_fields = expected field pattern, found `{$token_str}` + .suggestion = to omit remaining fields, use `..` -parse_expected_else_block = expected `{"{"}`, found {$first_tok} - .label = expected an `if` or a block after this `else` - .suggestion = add an `if` if this is the condition of a chained `else if` statement +parse_dot_dot_dot_range_to_pattern_not_allowed = range-to patterns with `...` are not allowed + .suggestion = use `..=` instead -parse_outer_attribute_not_allowed_on_if_else = outer attributes are not allowed on `if` and `else` branches - .branch_label = the attributes are attached to this branch - .ctx_label = the branch belongs to this `{$ctx}` - .suggestion = remove the attributes +parse_dotdotdot = unexpected token: `...` + .suggest_exclusive_range = use `..` for an exclusive range + .suggest_inclusive_range = or `..=` for an inclusive range -parse_missing_in_in_for_loop = missing `in` in `for` loop - .use_in_not_of = try using `in` here instead - .add_in = try adding `in` here +parse_dotdotdot_rest_pattern = unexpected `...` + .label = not a valid pattern + .suggestion = for a rest pattern, use `..` instead of `...` -parse_missing_expression_in_for_loop = missing expression to iterate on in `for` loop - .suggestion = try adding an expression to the `for` loop +parse_double_colon_in_bound = expected `:` followed by trait or lifetime + .suggestion = use single colon -parse_loop_else = `{$loop_kind}...else` loops are not supported - .note = consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run - .loop_keyword = `else` is attached to this loop +parse_dyn_after_mut = `mut` must precede `dyn` + .suggestion = place `mut` before `dyn` -parse_missing_comma_after_match_arm = expected `,` following `match` arm - .suggestion = missing a comma here to end this `match` arm +parse_empty_exponent_float = expected at least one digit in exponent -parse_catch_after_try = keyword `catch` cannot follow a `try` block - .help = try using `match` on the result of the `try` block instead +parse_empty_unicode_escape = empty unicode escape + .label = this escape must have at least 1 hex digit -parse_comma_after_base_struct = cannot use a comma after the base struct - .note = the base struct must always be the last field - .suggestion = remove this comma +parse_enum_pattern_instead_of_identifier = expected identifier, found enum pattern + +parse_enum_struct_mutually_exclusive = `enum` and `struct` are mutually exclusive + .suggestion = replace `enum struct` with parse_eq_field_init = expected `:`, found `=` .suggestion = replace equals symbol with a colon -parse_dotdotdot = unexpected token: `...` - .suggest_exclusive_range = use `..` for an exclusive range - .suggest_inclusive_range = or `..=` for an inclusive range +parse_equals_struct_default = default values on `struct` fields aren't supported + .suggestion = remove this unsupported default value -parse_left_arrow_operator = unexpected token: `<-` - .suggestion = if you meant to write a comparison against a negative value, add a space in between `<` and `-` +parse_escape_only_char = {$byte -> + [true] byte + *[false] character + } constant must be escaped: `{$escaped_msg}` + .escape = escape the character -parse_remove_let = expected pattern, found `let` - .suggestion = remove the unnecessary `let` keyword +parse_expect_dotdot_not_dotdotdot = expected `..`, found `...` + .suggestion = use `..` to fill in the rest of the fields -parse_use_eq_instead = unexpected `==` - .suggestion = try using `=` instead +parse_expect_eq_instead_of_eqeq = expected `=`, found `==` + .suggestion = consider using `=` here -parse_use_empty_block_not_semi = expected { "`{}`" }, found `;` - .suggestion = try using { "`{}`" } instead +parse_expect_label_found_ident = expected a label, found an identifier + .suggestion = labels start with a tick -parse_comparison_interpreted_as_generic = - `<` is interpreted as a start of generic arguments for `{$type}`, not a comparison - .label_args = interpreted as generic arguments - .label_comparison = not interpreted as comparison - .suggestion = try comparing the cast value +parse_expect_path = expected a path -parse_shift_interpreted_as_generic = - `<<` is interpreted as a start of generic arguments for `{$type}`, not a shift - .label_args = interpreted as generic arguments - .label_comparison = not interpreted as shift - .suggestion = try shifting the cast value +parse_expected_binding_left_of_at = left-hand side of `@` must be a binding + .label_lhs = interpreted as a pattern, not a binding + .label_rhs = also a pattern + .note = bindings are `x`, `mut x`, `ref x`, and `ref mut x` + +parse_expected_builtin_ident = expected identifier after `builtin #` + +parse_expected_comma_after_pattern_field = expected `,` + +parse_expected_else_block = expected `{"{"}`, found {$first_tok} + .label = expected an `if` or a block after this `else` + .suggestion = add an `if` if this is the condition of a chained `else if` statement + +parse_expected_expression_found_let = expected expression, found `let` statement + +parse_expected_fn_path_found_fn_keyword = expected identifier, found keyword `fn` + .suggestion = use `Fn` to refer to the trait + +parse_expected_identifier = expected identifier + +parse_expected_identifier_found_doc_comment = expected identifier, found doc comment +parse_expected_identifier_found_doc_comment_str = expected identifier, found doc comment `{$token}` +parse_expected_identifier_found_keyword = expected identifier, found keyword +parse_expected_identifier_found_keyword_str = expected identifier, found keyword `{$token}` +parse_expected_identifier_found_reserved_identifier = expected identifier, found reserved identifier +parse_expected_identifier_found_reserved_identifier_str = expected identifier, found reserved identifier `{$token}` +parse_expected_identifier_found_reserved_keyword = expected identifier, found reserved keyword +parse_expected_identifier_found_reserved_keyword_str = expected identifier, found reserved keyword `{$token}` +parse_expected_identifier_found_str = expected identifier, found `{$token}` + +parse_expected_mut_or_const_in_raw_pointer_type = expected `mut` or `const` keyword in raw pointer type + .suggestion = add `mut` or `const` here + +parse_expected_semi_found_doc_comment_str = expected `;`, found doc comment `{$token}` +parse_expected_semi_found_keyword_str = expected `;`, found keyword `{$token}` +parse_expected_semi_found_reserved_identifier_str = expected `;`, found reserved identifier `{$token}` +parse_expected_semi_found_reserved_keyword_str = expected `;`, found reserved keyword `{$token}` +parse_expected_semi_found_str = expected `;`, found `{$token}` + +parse_expected_statement_after_outer_attr = expected statement after outer attribute + +parse_expected_trait_in_trait_impl_found_type = expected a trait, found type + +parse_extern_crate_name_with_dashes = crate name using dashes are not valid in `extern crate` statements + .label = dash-separated idents are not valid + .suggestion = if the original crate name uses dashes you need to use underscores in the code + +parse_extern_item_cannot_be_const = extern items cannot be `const` + .suggestion = try using a static value + .note = for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +parse_extra_if_in_let_else = remove the `if` if you meant to write a `let...else` statement + +parse_extra_impl_keyword_in_trait_impl = unexpected `impl` keyword + .suggestion = remove the extra `impl` + .note = this is parsed as an `impl Trait` type, but a trait is expected at this position + + +parse_field_expression_with_generic = field expressions cannot have generic arguments + +parse_float_literal_requires_integer_part = float literals must have an integer part + .suggestion = must have an integer part + +parse_float_literal_unsupported_base = {$base} float literal is not supported + +parse_fn_pointer_cannot_be_async = an `fn` pointer type cannot be `async` + .label = `async` because of this + .suggestion = remove the `async` qualifier + +parse_fn_pointer_cannot_be_const = an `fn` pointer type cannot be `const` + .label = `const` because of this + .suggestion = remove the `const` qualifier + +parse_fn_ptr_with_generics = function pointer types may not have generic parameters + .suggestion = consider moving the lifetime {$arity -> + [one] parameter + *[other] parameters + } to {$for_param_list_exists -> + [true] the + *[false] a + } `for` parameter list + +parse_forgot_paren = perhaps you forgot parentheses? parse_found_expr_would_be_stmt = expected expression, found `{$token}` .label = expected expression -parse_leading_plus_not_supported = leading `+` is not supported - .label = unexpected `+` - .suggestion_remove_plus = try removing the `+` +parse_function_body_equals_expr = function body cannot be `= expression;` + .suggestion = surround the expression with `{"{"}` and `{"}"}` instead of `=` and `;` -parse_parentheses_with_struct_fields = invalid `struct` delimiters or `fn` call arguments - .suggestion_braces_for_struct = if `{$type}` is a struct, use braces as delimiters - .suggestion_no_fields_for_fn = if `{$type}` is a function, use the arguments directly +parse_generic_args_in_pat_require_turbofish_syntax = generic args in patterns require the turbofish syntax -parse_labeled_loop_in_break = parentheses are required around this expression to avoid confusion with a labeled break expression +parse_generic_parameters_without_angle_brackets = generic parameters without surrounding angle brackets + .suggestion = surround the type parameters with angle brackets -parse_sugg_wrap_expression_in_parentheses = wrap the expression in parentheses +parse_generics_in_path = unexpected generic arguments in path -parse_array_brackets_instead_of_braces = this is a block expression, not an array - .suggestion = to make an array, use square brackets instead of curly braces +parse_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml` +parse_help_set_edition_standalone = pass `--edition {$edition}` to `rustc` +parse_hexadecimal_float_literal_not_supported = hexadecimal float literal is not supported +parse_if_expression_missing_condition = missing condition for `if` expression + .condition_label = expected condition here + .block_label = if this block is the condition of the `if` expression, then it must be followed by another block -parse_match_arm_body_without_braces = `match` arm body without braces - .label_statements = {$num_statements -> - [one] this statement is not surrounded by a body - *[other] these statements are not surrounded by a body - } - .label_arrow = while parsing the `match` arm starting here - .suggestion_add_braces = surround the {$num_statements -> - [one] statement - *[other] statements - } with a body - .suggestion_use_comma_not_semicolon = replace `;` with `,` to end a `match` arm expression +parse_if_expression_missing_then_block = this `if` expression is missing a block after the condition + .add_then_block = add a block here + .condition_possibly_unfinished = this binary operation is possibly unfinished + +parse_in_in_typo = + expected iterable, found keyword `in` + .suggestion = remove the duplicated `in` + +parse_inappropriate_default = {$article} {$descr} cannot be `default` + .label = `default` because of this + .note = only associated `fn`, `const`, and `type` items can be `default` parse_inclusive_range_extra_equals = unexpected `=` after inclusive range .suggestion_remove_eq = use `..=` instead @@ -238,36 +310,18 @@ parse_inclusive_range_no_end = inclusive range with no end .suggestion_open_range = use `..` instead .note = inclusive ranges must be bounded at the end (`..=b` or `a..=b`) -parse_struct_literal_not_allowed_here = struct literals are not allowed here - .suggestion = surround the struct literal with parentheses - -parse_invalid_interpolated_expression = invalid interpolated expression - -parse_hexadecimal_float_literal_not_supported = hexadecimal float literal is not supported -parse_octal_float_literal_not_supported = octal float literal is not supported -parse_binary_float_literal_not_supported = binary float literal is not supported -parse_not_supported = not supported - -parse_invalid_literal_suffix = suffixes on {$kind} literals are invalid - .label = invalid suffix `{$suffix}` - -parse_invalid_literal_suffix_on_tuple_index = suffixes on a tuple index are invalid - .label = invalid suffix `{$suffix}` - .tuple_exception_line_1 = `{$suffix}` is *temporarily* accepted on tuple index fields as it was incorrectly accepted on stable for a few releases - .tuple_exception_line_2 = on proc macros, you'll want to use `syn::Index::from` or `proc_macro::Literal::*_unsuffixed` for code that will desugar to tuple field access - .tuple_exception_line_3 = see issue #60210 for more information - -parse_expected_builtin_ident = expected identifier after `builtin #` - -parse_unknown_builtin_construct = unknown `builtin #` construct `{$name}` +parse_incorrect_parens_trait_bounds = incorrect parentheses around trait bounds +parse_incorrect_parens_trait_bounds_sugg = fix the parentheses -parse_non_string_abi_literal = non-string ABI literal - .suggestion = specify the ABI with a string literal +parse_incorrect_semicolon = + expected item, found `;` + .suggestion = remove this semicolon + .help = {$name} declarations are not followed by a semicolon -parse_mismatched_closing_delimiter = mismatched closing delimiter: `{$delimiter}` - .label_unmatched = mismatched closing delimiter - .label_opening_candidate = closing delimiter possibly meant for this - .label_unclosed = unclosed delimiter +parse_incorrect_use_of_await = + incorrect use of `await` + .parentheses_suggestion = `await` is not a method call, remove the parentheses + .postfix_suggestion = `await` is a postfix operation parse_incorrect_visibility_restriction = incorrect visibility restriction .help = some possible visibility restrictions are: @@ -276,36 +330,8 @@ parse_incorrect_visibility_restriction = incorrect visibility restriction `pub(in path::to::module)`: visible only on the specified path .suggestion = make this visible only to module `{$inner_str}` with `in` -parse_assignment_else_not_allowed = ... else {"{"} ... {"}"} is not allowed - -parse_expected_statement_after_outer_attr = expected statement after outer attribute - -parse_doc_comment_does_not_document_anything = found a documentation comment that doesn't document anything - .help = doc comments must come before what they document, if a comment was intended use `//` - .suggestion = missing comma here - -parse_const_let_mutually_exclusive = `const` and `let` are mutually exclusive - .suggestion = remove `let` - -parse_invalid_expression_in_let_else = a `{$operator}` expression cannot be directly assigned in `let...else` -parse_invalid_curly_in_let_else = right curly brace `{"}"}` before `else` in a `let...else` statement not allowed -parse_extra_if_in_let_else = remove the `if` if you meant to write a `let...else` statement - -parse_compound_assignment_expression_in_let = can't reassign to an uninitialized variable - .suggestion = initialize the variable - .help = if you meant to overwrite, remove the `let` binding - -parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes - .help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.) - -parse_invalid_meta_item = expected unsuffixed literal or identifier, found `{$token}` - -parse_label_inner_attr_does_not_annotate_this = the inner attribute doesn't annotate this {$item} -parse_sugg_change_inner_attr_to_outer = to annotate the {$item}, change the attribute from inner to outer style - -parse_inner_attr_not_permitted_after_outer_doc_comment = an inner attribute is not permitted following an outer doc comment - .label_attr = not permitted following an outer doc comment - .label_prev_doc_comment = previous doc comment +parse_inner_attr_explanation = inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files +parse_inner_attr_not_permitted = an inner attribute is not permitted in this context .label_does_not_annotate_this = {parse_label_inner_attr_does_not_annotate_this} .sugg_change_inner_to_outer = {parse_sugg_change_inner_attr_to_outer} @@ -315,522 +341,493 @@ parse_inner_attr_not_permitted_after_outer_attr = an inner attribute is not perm .label_does_not_annotate_this = {parse_label_inner_attr_does_not_annotate_this} .sugg_change_inner_to_outer = {parse_sugg_change_inner_attr_to_outer} -parse_inner_attr_not_permitted = an inner attribute is not permitted in this context +parse_inner_attr_not_permitted_after_outer_doc_comment = an inner attribute is not permitted following an outer doc comment + .label_attr = not permitted following an outer doc comment + .label_prev_doc_comment = previous doc comment .label_does_not_annotate_this = {parse_label_inner_attr_does_not_annotate_this} .sugg_change_inner_to_outer = {parse_sugg_change_inner_attr_to_outer} -parse_inner_attr_explanation = inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files -parse_outer_attr_explanation = outer attributes, like `#[test]`, annotate the item following them - parse_inner_doc_comment_not_permitted = expected outer doc comment .note = inner doc comments like this (starting with `//!` or `/*!`) can only appear before items .suggestion = you might have meant to write a regular comment .label_does_not_annotate_this = the inner doc comment doesn't annotate this {$item} .sugg_change_inner_to_outer = to annotate the {$item}, change the doc comment from inner to outer style -parse_expected_identifier_found_reserved_identifier_str = expected identifier, found reserved identifier `{$token}` -parse_expected_identifier_found_keyword_str = expected identifier, found keyword `{$token}` -parse_expected_identifier_found_reserved_keyword_str = expected identifier, found reserved keyword `{$token}` -parse_expected_identifier_found_doc_comment_str = expected identifier, found doc comment `{$token}` -parse_expected_identifier_found_str = expected identifier, found `{$token}` - -parse_expected_identifier_found_reserved_identifier = expected identifier, found reserved identifier -parse_expected_identifier_found_keyword = expected identifier, found keyword -parse_expected_identifier_found_reserved_keyword = expected identifier, found reserved keyword -parse_expected_identifier_found_doc_comment = expected identifier, found doc comment -parse_expected_identifier = expected identifier +parse_int_literal_too_large = integer literal is too large -parse_sugg_escape_identifier = escape `{$ident_name}` to use it as an identifier +parse_invalid_block_macro_segment = cannot use a `block` macro fragment here + .label = the `block` fragment is within this context + .suggestion = wrap this in another block -parse_sugg_remove_comma = remove this comma -parse_sugg_add_let_for_stmt = you might have meant to introduce a new binding +parse_invalid_char_in_escape = {parse_invalid_char_in_escape_msg}: `{$ch}` + .label = {parse_invalid_char_in_escape_msg} -parse_expected_semi_found_reserved_identifier_str = expected `;`, found reserved identifier `{$token}` -parse_expected_semi_found_keyword_str = expected `;`, found keyword `{$token}` -parse_expected_semi_found_reserved_keyword_str = expected `;`, found reserved keyword `{$token}` -parse_expected_semi_found_doc_comment_str = expected `;`, found doc comment `{$token}` -parse_expected_semi_found_str = expected `;`, found `{$token}` +parse_invalid_char_in_escape_msg = invalid character in {$is_hex -> + [true] numeric character + *[false] unicode + } escape -parse_sugg_change_this_to_semi = change this to `;` -parse_sugg_add_semi = add `;` here -parse_label_unexpected_token = unexpected token +parse_invalid_comparison_operator = invalid comparison operator `{$invalid}` + .use_instead = `{$invalid}` is not a valid comparison operator, use `{$correct}` + .spaceship_operator_invalid = `<=>` is not a valid comparison operator, use `std::cmp::Ordering` -parse_unmatched_angle_brackets = {$num_extra_brackets -> - [one] unmatched angle bracket - *[other] unmatched angle brackets - } - .suggestion = {$num_extra_brackets -> - [one] remove extra angle bracket - *[other] remove extra angle brackets - } +parse_invalid_curly_in_let_else = right curly brace `{"}"}` before `else` in a `let...else` statement not allowed +parse_invalid_digit_literal = invalid digit for a base {$base} literal -parse_generic_parameters_without_angle_brackets = generic parameters without surrounding angle brackets - .suggestion = surround the type parameters with angle brackets +parse_invalid_dyn_keyword = invalid `dyn` keyword + .help = `dyn` is only needed at the start of a trait `+`-separated list + .suggestion = remove this keyword -parse_comparison_operators_cannot_be_chained = comparison operators cannot be chained - .sugg_parentheses_for_function_args = or use `(...)` if you meant to specify fn arguments - .sugg_split_comparison = split the comparison into two - .sugg_parenthesize = parenthesize the comparison -parse_sugg_turbofish_syntax = use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments +parse_invalid_expression_in_let_else = a `{$operator}` expression cannot be directly assigned in `let...else` +parse_invalid_float_literal_suffix = invalid suffix `{$suffix}` for float literal + .label = invalid suffix `{$suffix}` + .help = valid suffixes are `f32` and `f64` -parse_question_mark_in_type = invalid `?` in type - .label = `?` is only allowed on expressions, not types - .suggestion = if you meant to express that the type might not contain a value, use the `Option` wrapper type +parse_invalid_float_literal_width = invalid width `{$width}` for float literal + .help = valid widths are 32 and 64 -parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head - .suggestion = remove parentheses in `for` loop +parse_invalid_identifier_with_leading_number = identifiers cannot start with a number -parse_doc_comment_on_param_type = documentation comments cannot be applied to a function parameter's type - .label = doc comments are not allowed here +parse_invalid_int_literal_width = invalid width `{$width}` for integer literal + .help = valid widths are 8, 16, 32, 64 and 128 -parse_attribute_on_param_type = attributes cannot be applied to a function parameter's type - .label = attributes are not allowed here +parse_invalid_interpolated_expression = invalid interpolated expression -parse_pattern_method_param_without_body = patterns aren't allowed in methods without bodies - .suggestion = give this argument a name or use an underscore to ignore it +parse_invalid_literal_suffix = suffixes on {$kind} literals are invalid + .label = invalid suffix `{$suffix}` -parse_self_param_not_first = unexpected `self` parameter in function - .label = must be the first parameter of an associated function +parse_invalid_literal_suffix_on_tuple_index = suffixes on a tuple index are invalid + .label = invalid suffix `{$suffix}` + .tuple_exception_line_1 = `{$suffix}` is *temporarily* accepted on tuple index fields as it was incorrectly accepted on stable for a few releases + .tuple_exception_line_2 = on proc macros, you'll want to use `syn::Index::from` or `proc_macro::Literal::*_unsuffixed` for code that will desugar to tuple field access + .tuple_exception_line_3 = see issue #60210 for more information -parse_const_generic_without_braces = expressions must be enclosed in braces to be used as const generic arguments - .suggestion = enclose the `const` expression in braces +parse_invalid_logical_operator = `{$incorrect}` is not a logical operator + .note = unlike in e.g., Python and PHP, `&&` and `||` are used for logical operators + .use_amp_amp_for_conjunction = use `&&` to perform logical conjunction + .use_pipe_pipe_for_disjunction = use `||` to perform logical disjunction -parse_unexpected_const_param_declaration = unexpected `const` parameter declaration - .label = expected a `const` expression, not a parameter declaration - .suggestion = `const` parameters must be declared for the `impl` +parse_invalid_meta_item = expected unsuffixed literal or identifier, found `{$token}` -parse_unexpected_const_in_generic_param = expected lifetime, type, or constant, found keyword `const` - .suggestion = the `const` keyword is only needed in the definition of the type +parse_invalid_num_literal_base_prefix = invalid base prefix for number literal + .note = base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase + .suggestion = try making the prefix lowercase -parse_async_move_order_incorrect = the order of `move` and `async` is incorrect - .suggestion = try switching the order +parse_invalid_num_literal_suffix = invalid suffix `{$suffix}` for number literal + .label = invalid suffix `{$suffix}` + .help = the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) -parse_double_colon_in_bound = expected `:` followed by trait or lifetime - .suggestion = use single colon +parse_invalid_unicode_escape = invalid unicode character escape + .label = invalid escape + .help = unicode escape must {$surrogate -> + [true] not be a surrogate + *[false] be at most 10FFFF + } -parse_fn_ptr_with_generics = function pointer types may not have generic parameters - .suggestion = consider moving the lifetime {$arity -> - [one] parameter - *[other] parameters - } to {$for_param_list_exists -> - [true] the - *[false] a - } `for` parameter list +parse_invalid_variable_declaration = + invalid variable declaration -parse_invalid_identifier_with_leading_number = identifiers cannot start with a number +parse_kw_bad_case = keyword `{$kw}` is written in the wrong case + .suggestion = write it in the correct case -parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of `fn` - .suggestion = replace `fn` with `impl` here +parse_label_inner_attr_does_not_annotate_this = the inner attribute doesn't annotate this {$item} +parse_label_unexpected_token = unexpected token -parse_expected_fn_path_found_fn_keyword = expected identifier, found keyword `fn` - .suggestion = use `Fn` to refer to the trait +parse_label_while_parsing_or_pattern_here = while parsing this or-pattern starting here -parse_path_single_colon = path separator must be a double colon - .suggestion = use a double colon instead +parse_labeled_loop_in_break = parentheses are required around this expression to avoid confusion with a labeled break expression -parse_colon_as_semi = statements are terminated with a semicolon - .suggestion = use a semicolon instead +parse_leading_plus_not_supported = leading `+` is not supported + .label = unexpected `+` + .suggestion_remove_plus = try removing the `+` -parse_type_ascription_removed = - if you meant to annotate an expression with a type, the type ascription syntax has been removed, see issue #101728 +parse_leading_underscore_unicode_escape = {parse_leading_underscore_unicode_escape_label}: `_` +parse_leading_underscore_unicode_escape_label = invalid start of unicode escape -parse_where_clause_before_tuple_struct_body = where clauses are not allowed before tuple struct bodies - .label = unexpected where clause - .name_label = while parsing this tuple struct - .body_label = the struct body - .suggestion = move the body before the where clause +parse_left_arrow_operator = unexpected token: `<-` + .suggestion = if you meant to write a comparison against a negative value, add a space in between `<` and `-` -parse_async_fn_in_2015 = `async fn` is not permitted in Rust 2015 - .label = to use `async fn`, switch to Rust 2018 or later +parse_lifetime_after_mut = lifetime must precede `mut` + .suggestion = place the lifetime before `mut` -parse_async_block_in_2015 = `async` blocks are only allowed in Rust 2018 or later +parse_lifetime_in_borrow_expression = borrow expressions cannot be annotated with lifetimes + .suggestion = remove the lifetime annotation + .label = annotated with lifetime here -parse_self_argument_pointer = cannot pass `self` by raw pointer - .label = cannot pass `self` by raw pointer +parse_lone_slash = invalid trailing slash in literal + .label = {parse_lone_slash} -parse_visibility_not_followed_by_item = visibility `{$vis}` is not followed by an item - .label = the visibility - .help = you likely meant to define an item, e.g., `{$vis} fn foo() {"{}"}` +parse_loop_else = `{$loop_kind}...else` loops are not supported + .note = consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run + .loop_keyword = `else` is attached to this loop -parse_default_not_followed_by_item = `default` is not followed by an item - .label = the `default` qualifier - .note = only `fn`, `const`, `type`, or `impl` items may be prefixed by `default` +parse_macro_expands_to_adt_field = macros cannot expand to {$adt_ty} fields -parse_missing_struct_for_struct_definition = missing `struct` for struct definition - .suggestion = add `struct` here to parse `{$ident}` as a public struct +parse_macro_expands_to_enum_variant = macros cannot expand to enum variants -parse_missing_fn_for_function_definition = missing `fn` for function definition - .suggestion = add `fn` here to parse `{$ident}` as a public function +parse_macro_expands_to_match_arm = macros cannot expand to match arms -parse_missing_fn_for_method_definition = missing `fn` for method definition - .suggestion = add `fn` here to parse `{$ident}` as a public method +parse_macro_invocation_visibility = can't qualify macro invocation with `pub` + .suggestion = remove the visibility + .help = try adjusting the macro to put `{$vis}` inside the invocation -parse_ambiguous_missing_keyword_for_item_definition = missing `fn` or `struct` for function or struct definition - .suggestion = if you meant to call a macro, try - .help = if you meant to call a macro, remove the `pub` and add a trailing `!` after the identifier +parse_macro_invocation_with_qualified_path = macros cannot use qualified paths -parse_missing_trait_in_trait_impl = missing trait in a trait impl - .suggestion_add_trait = add a trait here - .suggestion_remove_for = for an inherent impl, drop this `for` +parse_macro_name_remove_bang = macro names aren't followed by a `!` + .suggestion = remove the `!` -parse_missing_for_in_trait_impl = missing `for` in a trait impl - .suggestion = add `for` here +parse_macro_rules_missing_bang = expected `!` after `macro_rules` + .suggestion = add a `!` -parse_expected_trait_in_trait_impl_found_type = expected a trait, found type +parse_macro_rules_visibility = can't qualify macro_rules invocation with `{$vis}` + .suggestion = try exporting the macro -parse_extra_impl_keyword_in_trait_impl = unexpected `impl` keyword - .suggestion = remove the extra `impl` - .note = this is parsed as an `impl Trait` type, but a trait is expected at this position +parse_malformed_cfg_attr = malformed `cfg_attr` attribute input + .suggestion = missing condition and attribute + .note = for more information, visit +parse_malformed_loop_label = malformed loop label + .suggestion = use the correct loop label format -parse_non_item_in_item_list = non-item in item list - .suggestion_use_const_not_let = consider using `const` instead of `let` for associated const - .label_list_start = item list starts here - .label_non_item = non-item starts here - .label_list_end = item list ends here - .suggestion_remove_semicolon = consider removing this semicolon +parse_match_arm_body_without_braces = `match` arm body without braces + .label_statements = {$num_statements -> + [one] this statement is not surrounded by a body + *[other] these statements are not surrounded by a body + } + .label_arrow = while parsing the `match` arm starting here + .suggestion_add_braces = surround the {$num_statements -> + [one] statement + *[other] statements + } with a body + .suggestion_use_comma_not_semicolon = replace `;` with `,` to end a `match` arm expression -parse_bounds_not_allowed_on_trait_aliases = bounds are not allowed on trait aliases +parse_maybe_fn_typo_with_impl = you might have meant to write `impl` instead of `fn` + .suggestion = replace `fn` with `impl` here -parse_trait_alias_cannot_be_auto = trait aliases cannot be `auto` -parse_trait_alias_cannot_be_unsafe = trait aliases cannot be `unsafe` +parse_maybe_recover_from_bad_qpath_stage_2 = + missing angle brackets in associated item path + .suggestion = try: `{$ty}` -parse_associated_static_item_not_allowed = associated `static` items are not allowed +parse_maybe_recover_from_bad_type_plus = + expected a path on the left-hand side of `+`, not `{$ty}` -parse_extern_crate_name_with_dashes = crate name using dashes are not valid in `extern crate` statements - .label = dash-separated idents are not valid - .suggestion = if the original crate name uses dashes you need to use underscores in the code +parse_maybe_report_ambiguous_plus = + ambiguous `+` in a type + .suggestion = use parentheses to disambiguate -parse_extern_item_cannot_be_const = extern items cannot be `const` - .suggestion = try using a static value - .note = for more information, visit https://doc.rust-lang.org/std/keyword.extern.html +parse_meta_bad_delim = wrong meta list delimiters +parse_meta_bad_delim_suggestion = the delimiters should be `(` and `)` -parse_const_global_cannot_be_mutable = const globals cannot be mutable - .label = cannot be mutable - .suggestion = you might want to declare a static instead +parse_mismatched_closing_delimiter = mismatched closing delimiter: `{$delimiter}` + .label_unmatched = mismatched closing delimiter + .label_opening_candidate = closing delimiter possibly meant for this + .label_unclosed = unclosed delimiter + +parse_missing_comma_after_match_arm = expected `,` following `match` arm + .suggestion = missing a comma here to end this `match` arm parse_missing_const_type = missing type for `{$kind}` item .suggestion = provide a type for the item -parse_enum_struct_mutually_exclusive = `enum` and `struct` are mutually exclusive - .suggestion = replace `enum struct` with - -parse_unexpected_token_after_struct_name = expected `where`, `{"{"}`, `(`, or `;` after struct name -parse_unexpected_token_after_struct_name_found_reserved_identifier = expected `where`, `{"{"}`, `(`, or `;` after struct name, found reserved identifier `{$token}` -parse_unexpected_token_after_struct_name_found_keyword = expected `where`, `{"{"}`, `(`, or `;` after struct name, found keyword `{$token}` -parse_unexpected_token_after_struct_name_found_reserved_keyword = expected `where`, `{"{"}`, `(`, or `;` after struct name, found reserved keyword `{$token}` -parse_unexpected_token_after_struct_name_found_doc_comment = expected `where`, `{"{"}`, `(`, or `;` after struct name, found doc comment `{$token}` -parse_unexpected_token_after_struct_name_found_other = expected `where`, `{"{"}`, `(`, or `;` after struct name, found `{$token}` - -parse_unexpected_self_in_generic_parameters = unexpected keyword `Self` in generic parameters - .note = you cannot use `Self` as a generic parameter because it is reserved for associated items - -parse_unexpected_default_value_for_lifetime_in_generic_parameters = unexpected default lifetime parameter - .label = lifetime parameters cannot have default values - -parse_multiple_where_clauses = cannot define duplicate `where` clauses on an item - .label = previous `where` clause starts here - .suggestion = consider joining the two `where` clauses into one - -parse_nonterminal_expected_item_keyword = expected an item keyword -parse_nonterminal_expected_statement = expected a statement -parse_nonterminal_expected_ident = expected ident, found `{$token}` -parse_nonterminal_expected_lifetime = expected a lifetime, found `{$token}` +parse_missing_expression_in_for_loop = missing expression to iterate on in `for` loop + .suggestion = try adding an expression to the `for` loop -parse_or_pattern_not_allowed_in_let_binding = top-level or-patterns are not allowed in `let` bindings -parse_or_pattern_not_allowed_in_fn_parameters = top-level or-patterns are not allowed in function parameters -parse_sugg_remove_leading_vert_in_pattern = remove the `|` -parse_sugg_wrap_pattern_in_parens = wrap the pattern in parentheses +parse_missing_fn_for_function_definition = missing `fn` for function definition + .suggestion = add `fn` here to parse `{$ident}` as a public function -parse_note_pattern_alternatives_use_single_vert = alternatives in or-patterns are separated with `|`, not `||` +parse_missing_fn_for_method_definition = missing `fn` for method definition + .suggestion = add `fn` here to parse `{$ident}` as a public method -parse_unexpected_vert_vert_before_function_parameter = unexpected `||` before function parameter - .suggestion = remove the `||` +parse_missing_for_in_trait_impl = missing `for` in a trait impl + .suggestion = add `for` here -parse_label_while_parsing_or_pattern_here = while parsing this or-pattern starting here +parse_missing_in_in_for_loop = missing `in` in `for` loop + .use_in_not_of = try using `in` here instead + .add_in = try adding `in` here -parse_unexpected_vert_vert_in_pattern = unexpected token `||` in pattern - .suggestion = use a single `|` to separate multiple alternative patterns +parse_missing_let_before_mut = missing keyword +parse_missing_plus_in_bounds = expected `+` between lifetime and {$sym} + .suggestion = add `+` -parse_trailing_vert_not_allowed = a trailing `|` is not allowed in an or-pattern - .suggestion = remove the `{$token}` +parse_missing_semicolon_before_array = expected `;`, found `[` + .suggestion = consider adding `;` here -parse_dotdotdot_rest_pattern = unexpected `...` - .label = not a valid pattern - .suggestion = for a rest pattern, use `..` instead of `...` +parse_missing_struct_for_struct_definition = missing `struct` for struct definition + .suggestion = add `struct` here to parse `{$ident}` as a public struct -parse_pattern_on_wrong_side_of_at = pattern on wrong side of `@` - .label_pattern = pattern on the left, should be on the right - .label_binding = binding on the right, should be on the left - .suggestion = switch the order +parse_missing_trait_in_trait_impl = missing trait in a trait impl + .suggestion_add_trait = add a trait here + .suggestion_remove_for = for an inherent impl, drop this `for` -parse_expected_binding_left_of_at = left-hand side of `@` must be a binding - .label_lhs = interpreted as a pattern, not a binding - .label_rhs = also a pattern - .note = bindings are `x`, `mut x`, `ref x`, and `ref mut x` +parse_modifier_lifetime = `{$sigil}` may only modify trait bounds, not lifetime bounds + .suggestion = remove the `{$sigil}` -parse_ambiguous_range_pattern = the range pattern here has ambiguous interpretation - .suggestion = add parentheses to clarify the precedence +parse_more_than_one_char = character literal may only contain one codepoint + .followed_by = this `{$chr}` is followed by the combining {$len -> + [one] mark + *[other] marks + } `{$escaped_marks}` + .non_printing = there are non-printing characters, the full sequence is `{$escaped}` + .consider_normalized = consider using the normalized form `{$ch}` of this character + .remove_non = consider removing the non-printing characters + .use_double_quotes = if you meant to write a {$is_byte -> + [true] byte string + *[false] `str` + } literal, use double quotes -parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in pattern - .suggestion = remove the lifetime +parse_multiple_skipped_lines = multiple lines skipped by escaped newline + .label = skipping everything up to and including this point -parse_ref_mut_order_incorrect = the order of `mut` and `ref` is incorrect - .suggestion = try switching the order +parse_multiple_where_clauses = cannot define duplicate `where` clauses on an item + .label = previous `where` clause starts here + .suggestion = consider joining the two `where` clauses into one parse_mut_on_nested_ident_pattern = `mut` must be attached to each individual binding .suggestion = add `mut` to each binding parse_mut_on_non_ident_pattern = `mut` must be followed by a named binding .suggestion = remove the `mut` prefix -parse_note_mut_pattern_usage = `mut` may be followed by `variable` and `variable @ pattern` - -parse_repeated_mut_in_pattern = `mut` on a binding may not be repeated - .suggestion = remove the additional `mut`s +parse_need_plus_after_trait_object_lifetime = lifetime in trait object type must be followed by `+` -parse_dot_dot_dot_range_to_pattern_not_allowed = range-to patterns with `...` are not allowed - .suggestion = use `..=` instead +parse_nested_adt = `{$kw_str}` definition cannot be nested inside `{$keyword}` + .suggestion = consider creating a new `{$kw_str}` definition instead of nesting -parse_enum_pattern_instead_of_identifier = expected identifier, found enum pattern +parse_nested_c_variadic_type = C-variadic type `...` may not be nested inside another type -parse_dot_dot_dot_for_remaining_fields = expected field pattern, found `{$token_str}` - .suggestion = to omit remaining fields, use `..` +parse_no_brace_unicode_escape = incorrect unicode escape sequence + .label = {parse_no_brace_unicode_escape} + .use_braces = format of unicode escape sequences uses braces + .format_of_unicode = format of unicode escape sequences is `\u{"{...}"}` -parse_expected_comma_after_pattern_field = expected `,` +parse_no_digits_literal = no valid digits found for number -parse_return_types_use_thin_arrow = return types are denoted using `->` - .suggestion = use `->` instead +parse_non_item_in_item_list = non-item in item list + .suggestion_use_const_not_let = consider using `const` instead of `let` for associated const + .label_list_start = item list starts here + .label_non_item = non-item starts here + .label_list_end = item list ends here + .suggestion_remove_semicolon = consider removing this semicolon -parse_need_plus_after_trait_object_lifetime = lifetime in trait object type must be followed by `+` +parse_non_string_abi_literal = non-string ABI literal + .suggestion = specify the ABI with a string literal -parse_expected_mut_or_const_in_raw_pointer_type = expected `mut` or `const` keyword in raw pointer type - .suggestion = add `mut` or `const` here +parse_nonterminal_expected_ident = expected ident, found `{$token}` +parse_nonterminal_expected_item_keyword = expected an item keyword +parse_nonterminal_expected_lifetime = expected a lifetime, found `{$token}` -parse_lifetime_after_mut = lifetime must precede `mut` - .suggestion = place the lifetime before `mut` +parse_nonterminal_expected_statement = expected a statement +parse_not_supported = not supported -parse_dyn_after_mut = `mut` must precede `dyn` - .suggestion = place `mut` before `dyn` +parse_note_edition_guide = for more on editions, read https://doc.rust-lang.org/edition-guide -parse_fn_pointer_cannot_be_const = an `fn` pointer type cannot be `const` - .label = `const` because of this - .suggestion = remove the `const` qualifier +parse_note_mut_pattern_usage = `mut` may be followed by `variable` and `variable @ pattern` -parse_fn_pointer_cannot_be_async = an `fn` pointer type cannot be `async` - .label = `async` because of this - .suggestion = remove the `async` qualifier +parse_note_pattern_alternatives_use_single_vert = alternatives in or-patterns are separated with `|`, not `||` -parse_nested_c_variadic_type = C-variadic type `...` may not be nested inside another type +parse_octal_float_literal_not_supported = octal float literal is not supported +parse_or_pattern_not_allowed_in_fn_parameters = top-level or-patterns are not allowed in function parameters +parse_or_pattern_not_allowed_in_let_binding = top-level or-patterns are not allowed in `let` bindings +parse_out_of_range_hex_escape = out of range hex escape + .label = must be a character in the range [\x00-\x7f] -parse_invalid_dyn_keyword = invalid `dyn` keyword - .help = `dyn` is only needed at the start of a trait `+`-separated list - .suggestion = remove this keyword +parse_outer_attr_explanation = outer attributes, like `#[test]`, annotate the item following them -parse_help_set_edition_cargo = set `edition = "{$edition}"` in `Cargo.toml` -parse_help_set_edition_standalone = pass `--edition {$edition}` to `rustc` -parse_note_edition_guide = for more on editions, read https://doc.rust-lang.org/edition-guide +parse_outer_attribute_not_allowed_on_if_else = outer attributes are not allowed on `if` and `else` branches + .branch_label = the attributes are attached to this branch + .ctx_label = the branch belongs to this `{$ctx}` + .suggestion = remove the attributes -parse_unexpected_token_after_dot = unexpected token: `{$actual}` +parse_overlong_unicode_escape = overlong unicode escape + .label = must have at most 6 hex digits -parse_cannot_be_raw_ident = `{$ident}` cannot be a raw identifier +parse_parentheses_with_struct_fields = invalid `struct` delimiters or `fn` call arguments + .suggestion_braces_for_struct = if `{$type}` is a struct, use braces as delimiters + .suggestion_no_fields_for_fn = if `{$type}` is a function, use the arguments directly -parse_cr_doc_comment = bare CR not allowed in {$block -> - [true] block doc-comment - *[false] doc-comment -} +parse_parenthesized_lifetime = parenthesized lifetime bounds are not supported + .suggestion = remove the parentheses -parse_no_digits_literal = no valid digits found for number +parse_path_single_colon = path separator must be a double colon + .suggestion = use a double colon instead -parse_invalid_digit_literal = invalid digit for a base {$base} literal +parse_pattern_method_param_without_body = patterns aren't allowed in methods without bodies + .suggestion = give this argument a name or use an underscore to ignore it -parse_empty_exponent_float = expected at least one digit in exponent +parse_pattern_on_wrong_side_of_at = pattern on wrong side of `@` + .label_pattern = pattern on the left, should be on the right + .label_binding = binding on the right, should be on the left + .suggestion = switch the order -parse_float_literal_unsupported_base = {$base} float literal is not supported +parse_question_mark_in_type = invalid `?` in type + .label = `?` is only allowed on expressions, not types + .suggestion = if you meant to express that the type might not contain a value, use the `Option` wrapper type -parse_more_than_one_char = character literal may only contain one codepoint - .followed_by = this `{$chr}` is followed by the combining {$len -> - [one] mark - *[other] marks - } `{$escaped_marks}` - .non_printing = there are non-printing characters, the full sequence is `{$escaped}` - .consider_normalized = consider using the normalized form `{$ch}` of this character - .remove_non = consider removing the non-printing characters - .use_double_quotes = if you meant to write a {$is_byte -> - [true] byte string - *[false] `str` - } literal, use double quotes +parse_recover_import_as_use = expected item, found {$token_name} + .suggestion = items are imported using the `use` keyword -parse_no_brace_unicode_escape = incorrect unicode escape sequence - .label = {parse_no_brace_unicode_escape} - .use_braces = format of unicode escape sequences uses braces - .format_of_unicode = format of unicode escape sequences is `\u{"{...}"}` +parse_ref_mut_order_incorrect = the order of `mut` and `ref` is incorrect + .suggestion = try switching the order -parse_invalid_unicode_escape = invalid unicode character escape - .label = invalid escape - .help = unicode escape must {$surrogate -> - [true] not be a surrogate - *[false] be at most 10FFFF - } +parse_remove_let = expected pattern, found `let` + .suggestion = remove the unnecessary `let` keyword -parse_escape_only_char = {$byte -> - [true] byte - *[false] character - } constant must be escaped: `{$escaped_msg}` - .escape = escape the character +parse_repeated_mut_in_pattern = `mut` on a binding may not be repeated + .suggestion = remove the additional `mut`s -parse_bare_cr = {$double_quotes -> - [true] bare CR not allowed in string, use `\r` instead - *[false] character constant must be escaped: `\r` - } - .escape = escape the character +parse_require_colon_after_labeled_expression = labeled expression must be followed by `:` + .note = labels are used before loops and blocks, allowing e.g., `break 'label` to them + .label = the label + .suggestion = add `:` after the label -parse_bare_cr_in_raw_string = bare CR not allowed in raw string +parse_return_types_use_thin_arrow = return types are denoted using `->` + .suggestion = use `->` instead -parse_too_short_hex_escape = numeric character escape is too short +parse_self_argument_pointer = cannot pass `self` by raw pointer + .label = cannot pass `self` by raw pointer -parse_invalid_char_in_escape = {parse_invalid_char_in_escape_msg}: `{$ch}` - .label = {parse_invalid_char_in_escape_msg} +parse_self_param_not_first = unexpected `self` parameter in function + .label = must be the first parameter of an associated function -parse_invalid_char_in_escape_msg = invalid character in {$is_hex -> - [true] numeric character - *[false] unicode - } escape +parse_shift_interpreted_as_generic = + `<<` is interpreted as a start of generic arguments for `{$type}`, not a shift + .label_args = interpreted as generic arguments + .label_comparison = not interpreted as shift + .suggestion = try shifting the cast value -parse_out_of_range_hex_escape = out of range hex escape - .label = must be a character in the range [\x00-\x7f] +parse_single_colon_import_path = expected `::`, found `:` + .suggestion = use double colon + .note = import paths are delimited using `::` -parse_leading_underscore_unicode_escape = {parse_leading_underscore_unicode_escape_label}: `_` -parse_leading_underscore_unicode_escape_label = invalid start of unicode escape +parse_single_colon_struct_type = found single colon in a struct field type path + .suggestion = write a path separator here -parse_overlong_unicode_escape = overlong unicode escape - .label = must have at most 6 hex digits +parse_static_with_generics = static items may not have generic parameters -parse_unclosed_unicode_escape = unterminated unicode escape - .label = missing a closing `{"}"}` - .terminate = terminate the unicode escape +parse_struct_literal_body_without_path = + struct literal body without path + .suggestion = you might have forgotten to add the struct literal inside the block -parse_unicode_escape_in_byte = unicode escape in byte string - .label = {parse_unicode_escape_in_byte} - .help = unicode escape sequences cannot be used as a byte or in a byte string +parse_struct_literal_needing_parens = + invalid struct literal + .suggestion = you might need to surround the struct literal with parentheses -parse_empty_unicode_escape = empty unicode escape - .label = this escape must have at least 1 hex digit +parse_struct_literal_not_allowed_here = struct literals are not allowed here + .suggestion = surround the struct literal with parentheses -parse_zero_chars = empty character literal - .label = {parse_zero_chars} +parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes + .help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.) -parse_lone_slash = invalid trailing slash in literal - .label = {parse_lone_slash} +parse_sugg_add_let_for_stmt = you might have meant to introduce a new binding -parse_unskipped_whitespace = whitespace symbol '{$ch}' is not skipped - .label = {parse_unskipped_whitespace} +parse_sugg_add_semi = add `;` here +parse_sugg_change_inner_attr_to_outer = to annotate the {$item}, change the attribute from inner to outer style -parse_multiple_skipped_lines = multiple lines skipped by escaped newline - .label = skipping everything up to and including this point +parse_sugg_change_this_to_semi = change this to `;` +parse_sugg_escape_identifier = escape `{$ident_name}` to use it as an identifier -parse_unknown_prefix = prefix `{$prefix}` is unknown - .label = unknown prefix - .note = prefixed identifiers and literals are reserved since Rust 2021 - .suggestion_br = use `br` for a raw byte string - .suggestion_whitespace = consider inserting whitespace here +parse_sugg_remove_comma = remove this comma +parse_sugg_remove_leading_vert_in_pattern = remove the `|` +parse_sugg_turbofish_syntax = use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments -parse_too_many_hashes = too many `#` symbols: raw strings may be delimited by up to 255 `#` symbols, but found {$num} +parse_sugg_wrap_expression_in_parentheses = wrap the expression in parentheses -parse_unknown_start_of_token = unknown start of token: {$escaped} - .sugg_quotes = Unicode characters '“' (Left Double Quotation Mark) and '”' (Right Double Quotation Mark) look like '{$ascii_str}' ({$ascii_name}), but are not - .sugg_other = Unicode character '{$ch}' ({$u_name}) looks like '{$ascii_str}' ({$ascii_name}), but it is not - .help_null = source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used - .note_repeats = character appears {$repeats -> - [one] once more - *[other] {$repeats} more times - } +parse_sugg_wrap_pattern_in_parens = wrap the pattern in parentheses -parse_box_syntax_removed = `box_syntax` has been removed - .suggestion = use `Box::new()` instead +parse_switch_mut_let_order = + switch the order of `mut` and `let` -parse_bad_return_type_notation_output = - return type not allowed with return type notation - .suggestion = remove the return type +parse_ternary_operator = Rust has no ternary operator + .help = use an `if-else` expression instead -parse_bad_return_type_notation_dotdot = - return type notation uses `()` instead of `(..)` for elided arguments - .suggestion = remove the `..` +parse_tilde_const_lifetime = `~const` may only modify trait bounds, not lifetime bounds -parse_bad_assoc_type_bounds = bounds on associated types do not belong here - .label = belongs in `where` clause +parse_tilde_is_not_unary_operator = `~` cannot be used as a unary operator + .suggestion = use `!` to perform bitwise not -parse_attr_after_generic = trailing attribute after generic parameter - .label = attributes must go before parameters +parse_too_many_hashes = too many `#` symbols: raw strings may be delimited by up to 255 `#` symbols, but found {$num} -parse_attr_without_generics = attribute without generic parameters - .label = attributes are only permitted when preceding parameters +parse_too_short_hex_escape = numeric character escape is too short -parse_where_generics = generic parameters on `where` clauses are reserved for future use - .label = currently unsupported +parse_trailing_vert_not_allowed = a trailing `|` is not allowed in an or-pattern + .suggestion = remove the `{$token}` -parse_generics_in_path = unexpected generic arguments in path +parse_trait_alias_cannot_be_auto = trait aliases cannot be `auto` +parse_trait_alias_cannot_be_unsafe = trait aliases cannot be `unsafe` -parse_assoc_lifetime = associated lifetimes are not supported - .label = the lifetime is given here - .help = if you meant to specify a trait object, write `dyn Trait + 'lifetime` +parse_type_ascription_removed = + if you meant to annotate an expression with a type, the type ascription syntax has been removed, see issue #101728 -parse_tilde_const_lifetime = `~const` may only modify trait bounds, not lifetime bounds +parse_unclosed_unicode_escape = unterminated unicode escape + .label = missing a closing `{"}"}` + .terminate = terminate the unicode escape -parse_modifier_lifetime = `{$sigil}` may only modify trait bounds, not lifetime bounds - .suggestion = remove the `{$sigil}` +parse_underscore_literal_suffix = underscore literal suffix is not allowed -parse_parenthesized_lifetime = parenthesized lifetime bounds are not supported - .suggestion = remove the parentheses +parse_unexpected_const_in_generic_param = expected lifetime, type, or constant, found keyword `const` + .suggestion = the `const` keyword is only needed in the definition of the type -parse_const_bounds_missing_tilde = const bounds must start with `~` - .suggestion = add `~` +parse_unexpected_const_param_declaration = unexpected `const` parameter declaration + .label = expected a `const` expression, not a parameter declaration + .suggestion = `const` parameters must be declared for the `impl` -parse_underscore_literal_suffix = underscore literal suffix is not allowed +parse_unexpected_default_value_for_lifetime_in_generic_parameters = unexpected default lifetime parameter + .label = lifetime parameters cannot have default values -parse_expect_label_found_ident = expected a label, found an identifier - .suggestion = labels start with a tick +parse_unexpected_if_with_if = unexpected `if` in the condition expression + .suggestion = remove the `if` -parse_inappropriate_default = {$article} {$descr} cannot be `default` - .label = `default` because of this - .note = only associated `fn`, `const`, and `type` items can be `default` +parse_unexpected_lifetime_in_pattern = unexpected lifetime `{$symbol}` in pattern + .suggestion = remove the lifetime -parse_recover_import_as_use = expected item, found {$token_name} - .suggestion = items are imported using the `use` keyword +parse_unexpected_parentheses_in_for_head = unexpected parentheses surrounding `for` loop head + .suggestion = remove parentheses in `for` loop -parse_single_colon_import_path = expected `::`, found `:` - .suggestion = use double colon - .note = import paths are delimited using `::` +parse_unexpected_self_in_generic_parameters = unexpected keyword `Self` in generic parameters + .note = you cannot use `Self` as a generic parameter because it is reserved for associated items -parse_bad_item_kind = {$descr} is not supported in {$ctx} - .help = consider moving the {$descr} out to a nearby module scope +parse_unexpected_token_after_dot = unexpected token: `{$actual}` -parse_single_colon_struct_type = found single colon in a struct field type path - .suggestion = write a path separator here +parse_unexpected_token_after_label = expected `while`, `for`, `loop` or `{"{"}` after a label + .suggestion_remove_label = consider removing the label + .suggestion_enclose_in_block = consider enclosing expression in a block -parse_equals_struct_default = default values on `struct` fields aren't supported - .suggestion = remove this unsupported default value +parse_unexpected_token_after_not = unexpected {$negated_desc} after identifier +parse_unexpected_token_after_not_bitwise = use `!` to perform bitwise not +parse_unexpected_token_after_not_default = use `!` to perform logical negation or bitwise not -parse_macro_rules_missing_bang = expected `!` after `macro_rules` - .suggestion = add a `!` +parse_unexpected_token_after_not_logical = use `!` to perform logical negation +parse_unexpected_token_after_struct_name = expected `where`, `{"{"}`, `(`, or `;` after struct name +parse_unexpected_token_after_struct_name_found_doc_comment = expected `where`, `{"{"}`, `(`, or `;` after struct name, found doc comment `{$token}` +parse_unexpected_token_after_struct_name_found_keyword = expected `where`, `{"{"}`, `(`, or `;` after struct name, found keyword `{$token}` +parse_unexpected_token_after_struct_name_found_other = expected `where`, `{"{"}`, `(`, or `;` after struct name, found `{$token}` -parse_macro_name_remove_bang = macro names aren't followed by a `!` - .suggestion = remove the `!` +parse_unexpected_token_after_struct_name_found_reserved_identifier = expected `where`, `{"{"}`, `(`, or `;` after struct name, found reserved identifier `{$token}` +parse_unexpected_token_after_struct_name_found_reserved_keyword = expected `where`, `{"{"}`, `(`, or `;` after struct name, found reserved keyword `{$token}` +parse_unexpected_vert_vert_before_function_parameter = unexpected `||` before function parameter + .suggestion = remove the `||` -parse_macro_rules_visibility = can't qualify macro_rules invocation with `{$vis}` - .suggestion = try exporting the macro +parse_unexpected_vert_vert_in_pattern = unexpected token `||` in pattern + .suggestion = use a single `|` to separate multiple alternative patterns -parse_macro_invocation_visibility = can't qualify macro invocation with `pub` - .suggestion = remove the visibility - .help = try adjusting the macro to put `{$vis}` inside the invocation +parse_unicode_escape_in_byte = unicode escape in byte string + .label = {parse_unicode_escape_in_byte} + .help = unicode escape sequences cannot be used as a byte or in a byte string -parse_nested_adt = `{$kw_str}` definition cannot be nested inside `{$keyword}` - .suggestion = consider creating a new `{$kw_str}` definition instead of nesting +parse_unknown_builtin_construct = unknown `builtin #` construct `{$name}` -parse_function_body_equals_expr = function body cannot be `= expression;` - .suggestion = surround the expression with `{"{"}` and `{"}"}` instead of `=` and `;` +parse_unknown_prefix = prefix `{$prefix}` is unknown + .label = unknown prefix + .note = prefixed identifiers and literals are reserved since Rust 2021 + .suggestion_br = use `br` for a raw byte string + .suggestion_whitespace = consider inserting whitespace here -parse_box_not_pat = expected pattern, found {$descr} - .note = `box` is a reserved keyword - .suggestion = escape `box` to use it as an identifier +parse_unknown_start_of_token = unknown start of token: {$escaped} + .sugg_quotes = Unicode characters '“' (Left Double Quotation Mark) and '”' (Right Double Quotation Mark) look like '{$ascii_str}' ({$ascii_name}), but are not + .sugg_other = Unicode character '{$ch}' ({$u_name}) looks like '{$ascii_str}' ({$ascii_name}), but it is not + .help_null = source files must contain UTF-8 encoded text, unexpected null bytes might occur when a different encoding is used + .note_repeats = character appears {$repeats -> + [one] once more + *[other] {$repeats} more times + } parse_unmatched_angle = unmatched angle {$plural -> [true] brackets @@ -841,19 +838,45 @@ parse_unmatched_angle = unmatched angle {$plural -> *[false] bracket } -parse_missing_plus_in_bounds = expected `+` between lifetime and {$sym} - .suggestion = add `+` +parse_unmatched_angle_brackets = {$num_extra_brackets -> + [one] unmatched angle bracket + *[other] unmatched angle brackets + } + .suggestion = {$num_extra_brackets -> + [one] remove extra angle bracket + *[other] remove extra angle brackets + } -parse_incorrect_braces_trait_bounds = incorrect braces around trait bounds - .suggestion = remove the parentheses +parse_unskipped_whitespace = whitespace symbol '{$ch}' is not skipped + .label = {parse_unskipped_whitespace} -parse_kw_bad_case = keyword `{$kw}` is written in the wrong case - .suggestion = write it in the correct case +parse_use_empty_block_not_semi = expected { "`{}`" }, found `;` + .suggestion = try using { "`{}`" } instead -parse_meta_bad_delim = wrong meta list delimiters -parse_cfg_attr_bad_delim = wrong `cfg_attr` delimiters -parse_meta_bad_delim_suggestion = the delimiters should be `(` and `)` +parse_use_eq_instead = unexpected `==` + .suggestion = try using `=` instead -parse_malformed_cfg_attr = malformed `cfg_attr` attribute input - .suggestion = missing condition and attribute - .note = for more information, visit +parse_use_let_not_auto = write `let` instead of `auto` to introduce a new variable +parse_use_let_not_var = write `let` instead of `var` to introduce a new variable + +parse_visibility_not_followed_by_item = visibility `{$vis}` is not followed by an item + .label = the visibility + .help = you likely meant to define an item, e.g., `{$vis} fn foo() {"{}"}` + +parse_where_clause_before_const_body = where clauses are not allowed before const item bodies + .label = unexpected where clause + .name_label = while parsing this const item + .body_label = the item body + .suggestion = move the body before the where clause + +parse_where_clause_before_tuple_struct_body = where clauses are not allowed before tuple struct bodies + .label = unexpected where clause + .name_label = while parsing this tuple struct + .body_label = the struct body + .suggestion = move the body before the where clause + +parse_where_generics = generic parameters on `where` clauses are reserved for future use + .label = currently unsupported + +parse_zero_chars = empty character literal + .label = {parse_zero_chars} diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 84494eab855c4..e0b1e3678e41e 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -333,6 +333,17 @@ pub(crate) struct InvalidBlockMacroSegment { pub span: Span, #[label] pub context: Span, + #[subdiagnostic] + pub wrap: WrapInExplicitBlock, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")] +pub(crate) struct WrapInExplicitBlock { + #[suggestion_part(code = "{{ ")] + pub lo: Span, + #[suggestion_part(code = " }}")] + pub hi: Span, } #[derive(Diagnostic)] @@ -354,6 +365,14 @@ pub(crate) enum IfExpressionMissingThenBlockSub { AddThenBlock(#[primary_span] Span), } +#[derive(Diagnostic)] +#[diag(parse_ternary_operator)] +#[help] +pub struct TernaryOperator { + #[primary_span] + pub span: Span, +} + #[derive(Subdiagnostic)] #[suggestion(parse_extra_if_in_let_else, applicability = "maybe-incorrect", code = "")] pub(crate) struct IfExpressionLetSomeSub { @@ -1415,6 +1434,13 @@ pub(crate) struct AsyncBlockIn2015 { pub span: Span, } +#[derive(Diagnostic)] +#[diag(parse_async_move_block_in_2015)] +pub(crate) struct AsyncMoveBlockIn2015 { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(parse_self_argument_pointer)] pub(crate) struct SelfArgumentPointer { @@ -1789,6 +1815,12 @@ pub struct UnknownPrefix<'a> { pub sugg: Option, } +#[derive(Subdiagnostic)] +#[note(parse_macro_expands_to_adt_field)] +pub struct MacroExpandsToAdtField<'a> { + pub adt_ty: &'a str, +} + #[derive(Subdiagnostic)] pub enum UnknownPrefixSugg { #[suggestion( @@ -2604,21 +2636,24 @@ pub(crate) struct MissingPlusBounds { } #[derive(Diagnostic)] -#[diag(parse_incorrect_braces_trait_bounds)] -pub(crate) struct IncorrectBracesTraitBounds { +#[diag(parse_incorrect_parens_trait_bounds)] +pub(crate) struct IncorrectParensTraitBounds { #[primary_span] pub span: Vec, #[subdiagnostic] - pub sugg: IncorrectBracesTraitBoundsSugg, + pub sugg: IncorrectParensTraitBoundsSugg, } #[derive(Subdiagnostic)] -#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")] -pub(crate) struct IncorrectBracesTraitBoundsSugg { +#[multipart_suggestion( + parse_incorrect_parens_trait_bounds_sugg, + applicability = "machine-applicable" +)] +pub(crate) struct IncorrectParensTraitBoundsSugg { #[suggestion_part(code = " ")] - pub l: Span, - #[suggestion_part(code = "")] - pub r: Span, + pub wrong_span: Span, + #[suggestion_part(code = "(")] + pub new_span: Span, } #[derive(Diagnostic)] @@ -2681,3 +2716,48 @@ pub(crate) struct ExpectedBuiltinIdent { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(parse_static_with_generics)] +pub(crate) struct StaticWithGenerics { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(parse_where_clause_before_const_body)] +pub(crate) struct WhereClauseBeforeConstBody { + #[primary_span] + #[label] + pub span: Span, + #[label(parse_name_label)] + pub name: Span, + #[label(parse_body_label)] + pub body: Span, + #[subdiagnostic] + pub sugg: Option, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")] +pub(crate) struct WhereClauseBeforeConstBodySugg { + #[suggestion_part(code = "= {snippet} ")] + pub left: Span, + pub snippet: String, + #[suggestion_part(code = "")] + pub right: Span, +} + +#[derive(Diagnostic)] +#[diag(parse_generic_args_in_pat_require_turbofish_syntax)] +pub(crate) struct GenericArgsInPatRequireTurbofishSyntax { + #[primary_span] + pub span: Span, + #[suggestion( + parse_sugg_turbofish_syntax, + style = "verbose", + code = "::", + applicability = "maybe-incorrect" + )] + pub suggest_turbofish: Span, +} diff --git a/compiler/rustc_parse/src/lexer/diagnostics.rs b/compiler/rustc_parse/src/lexer/diagnostics.rs index 9e6d27bf036fc..b50bb47f2972d 100644 --- a/compiler/rustc_parse/src/lexer/diagnostics.rs +++ b/compiler/rustc_parse/src/lexer/diagnostics.rs @@ -46,7 +46,7 @@ pub fn report_missing_open_delim( }; err.span_label( unmatch_brace.found_span.shrink_to_lo(), - format!("missing open `{}` for this delimiter", missed_open), + format!("missing open `{missed_open}` for this delimiter"), ); reported_missing_open = true; } diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index c6e6b46e4551c..a375a1d69cdea 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -9,8 +9,8 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::util::unicode::contains_text_flow_control_chars; use rustc_errors::{error_code, Applicability, Diagnostic, DiagnosticBuilder, StashKey}; use rustc_lexer::unescape::{self, EscapeError, Mode}; -use rustc_lexer::Cursor; use rustc_lexer::{Base, DocStyle, RawStrError}; +use rustc_lexer::{Cursor, LiteralKind}; use rustc_session::lint::builtin::{ RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX, TEXT_DIRECTION_CODEPOINT_IN_COMMENT, }; @@ -74,7 +74,6 @@ pub(crate) fn parse_token_trees<'a>( // because the delimiter mismatch is more likely to be the root cause of error let mut buffer = Vec::with_capacity(1); - // Not using `emit_unclosed_delims` to use `db.buffer` for unmatched in unmatched_delims { if let Some(err) = make_unclosed_delims_error(unmatched, &sess) { err.buffer(&mut buffer); @@ -118,6 +117,7 @@ impl<'a> StringReader<'a> { let mut swallow_next_invalid = 0; // Skip trivial (whitespace & comments) tokens loop { + let str_before = self.cursor.as_str(); let token = self.cursor.advance_token(); let start = self.pos; self.pos = self.pos + BytePos(token.len); @@ -165,10 +165,7 @@ impl<'a> StringReader<'a> { continue; } rustc_lexer::TokenKind::Ident => { - let sym = nfc_normalize(self.str_from(start)); - let span = self.mk_sp(start, self.pos); - self.sess.symbol_gallery.insert(sym, span); - token::Ident(sym, false) + self.ident(start) } rustc_lexer::TokenKind::RawIdent => { let sym = nfc_normalize(self.str_from(start + BytePos(2))); @@ -182,10 +179,7 @@ impl<'a> StringReader<'a> { } rustc_lexer::TokenKind::UnknownPrefix => { self.report_unknown_prefix(start); - let sym = nfc_normalize(self.str_from(start)); - let span = self.mk_sp(start, self.pos); - self.sess.symbol_gallery.insert(sym, span); - token::Ident(sym, false) + self.ident(start) } rustc_lexer::TokenKind::InvalidIdent // Do not recover an identifier with emoji if the codepoint is a confusable @@ -203,6 +197,27 @@ impl<'a> StringReader<'a> { .push(span); token::Ident(sym, false) } + // split up (raw) c string literals to an ident and a string literal when edition < 2021. + rustc_lexer::TokenKind::Literal { + kind: kind @ (LiteralKind::CStr { .. } | LiteralKind::RawCStr { .. }), + suffix_start: _, + } if !self.mk_sp(start, self.pos).edition().at_least_rust_2021() => { + let prefix_len = match kind { + LiteralKind::CStr { .. } => 1, + LiteralKind::RawCStr { .. } => 2, + _ => unreachable!(), + }; + + // reset the state so that only the prefix ("c" or "cr") + // was consumed. + let lit_start = start + BytePos(prefix_len); + self.pos = lit_start; + self.cursor = Cursor::new(&str_before[prefix_len as usize..]); + + self.report_unknown_prefix(start); + let prefix_span = self.mk_sp(start, lit_start); + return (Token::new(self.ident(start), prefix_span), preceded_by_whitespace); + } rustc_lexer::TokenKind::Literal { kind, suffix_start } => { let suffix_start = start + BytePos(suffix_start); let (kind, symbol) = self.cook_lexer_literal(start, suffix_start, kind); @@ -317,6 +332,13 @@ impl<'a> StringReader<'a> { } } + fn ident(&self, start: BytePos) -> TokenKind { + let sym = nfc_normalize(self.str_from(start)); + let span = self.mk_sp(start, self.pos); + self.sess.symbol_gallery.insert(sym, span); + token::Ident(sym, false) + } + fn struct_fatal_span_char( &self, from_pos: BytePos, diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs index 318a299850902..07910113dee7f 100644 --- a/compiler/rustc_parse/src/lexer/tokentrees.rs +++ b/compiler/rustc_parse/src/lexer/tokentrees.rs @@ -198,7 +198,7 @@ impl<'a> TokenTreesReader<'a> { // An unexpected closing delimiter (i.e., there is no // matching opening delimiter). let token_str = token_to_string(&self.token); - let msg = format!("unexpected closing delimiter: `{}`", token_str); + let msg = format!("unexpected closing delimiter: `{token_str}`"); let mut err = self.string_reader.sess.span_diagnostic.struct_span_err(self.token.span, msg); report_suspicious_mismatch_block( diff --git a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs index eb9625f923ab9..b659c40b2336f 100644 --- a/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs +++ b/compiler/rustc_parse/src/lexer/unescape_error_reporting.rs @@ -27,7 +27,7 @@ pub(crate) fn emit_unescape_error( lit, span_with_quotes, mode, range, error ); let last_char = || { - let c = lit[range.clone()].chars().rev().next().unwrap(); + let c = lit[range.clone()].chars().next_back().unwrap(); let span = span.with_lo(span.hi() - BytePos(c.len_utf8() as u32)); (c, span) }; @@ -80,20 +80,14 @@ pub(crate) fn emit_unescape_error( let sugg = sugg.unwrap_or_else(|| { let prefix = mode.prefix_noraw(); let mut escaped = String::with_capacity(lit.len()); - let mut chrs = lit.chars().peekable(); - while let Some(first) = chrs.next() { - match (first, chrs.peek()) { - ('\\', Some('"')) => { - escaped.push('\\'); - escaped.push('"'); - chrs.next(); - } - ('"', _) => { - escaped.push('\\'); - escaped.push('"') - } - (c, _) => escaped.push(c), - }; + let mut in_escape = false; + for c in lit.chars() { + match c { + '\\' => in_escape = !in_escape, + '"' if !in_escape => escaped.push('\\'), + _ => in_escape = false, + } + escaped.push(c); } let sugg = format!("{prefix}\"{escaped}\""); MoreThanOneCharSugg::Quotes { @@ -135,7 +129,7 @@ pub(crate) fn emit_unescape_error( "unknown character escape" }; let ec = escaped_char(c); - let mut diag = handler.struct_span_err(span, format!("{}: `{}`", label, ec)); + let mut diag = handler.struct_span_err(span, format!("{label}: `{ec}`")); diag.span_label(span, label); if c == '{' || c == '}' && matches!(mode, Mode::Str | Mode::RawStr) { diag.help( @@ -151,14 +145,14 @@ pub(crate) fn emit_unescape_error( diag.span_suggestion( span_with_quotes, "if you meant to write a literal backslash (perhaps escaping in a regular expression), consider a raw string literal", - format!("r\"{}\"", lit), + format!("r\"{lit}\""), Applicability::MaybeIncorrect, ); } diag.help( "for more information, visit \ - ", + ", ); } diag.emit(); @@ -180,21 +174,20 @@ pub(crate) fn emit_unescape_error( Mode::RawByteStr => "raw byte string literal", _ => panic!("non-is_byte literal paired with NonAsciiCharInByte"), }; - let mut err = handler.struct_span_err(span, format!("non-ASCII character in {}", desc)); + let mut err = handler.struct_span_err(span, format!("non-ASCII character in {desc}")); let postfix = if unicode_width::UnicodeWidthChar::width(c).unwrap_or(1) == 0 { - format!(" but is {:?}", c) + format!(" but is {c:?}") } else { String::new() }; - err.span_label(span, format!("must be ASCII{}", postfix)); + err.span_label(span, format!("must be ASCII{postfix}")); // Note: the \\xHH suggestions are not given for raw byte string // literals, because they are araw and so cannot use any escapes. if (c as u32) <= 0xFF && mode != Mode::RawByteStr { err.span_suggestion( span, format!( - "if you meant to use the unicode code point for {:?}, use a \\xHH escape", - c + "if you meant to use the unicode code point for {c:?}, use a \\xHH escape" ), format!("\\x{:X}", c as u32), Applicability::MaybeIncorrect, @@ -206,7 +199,7 @@ pub(crate) fn emit_unescape_error( utf8.push(c); err.span_suggestion( span, - format!("if you meant to use the UTF-8 encoding of {:?}, use \\xHH escapes", c), + format!("if you meant to use the UTF-8 encoding of {c:?}, use \\xHH escapes"), utf8.as_bytes() .iter() .map(|b: &u8| format!("\\x{:X}", *b)) diff --git a/compiler/rustc_parse/src/lexer/unicode_chars.rs b/compiler/rustc_parse/src/lexer/unicode_chars.rs index 829d9693e55a6..bbfb160ebf7cb 100644 --- a/compiler/rustc_parse/src/lexer/unicode_chars.rs +++ b/compiler/rustc_parse/src/lexer/unicode_chars.rs @@ -349,7 +349,7 @@ pub(super) fn check_for_substitution( let span = Span::with_root_ctxt(pos, pos + Pos::from_usize(ch.len_utf8() * count)); let Some((_, ascii_name, token)) = ASCII_ARRAY.iter().find(|&&(s, _, _)| s == ascii_str) else { - let msg = format!("substitution character not found for '{}'", ch); + let msg = format!("substitution character not found for '{ch}'"); reader.sess.span_diagnostic.span_bug_no_panic(span, msg); return (None, None); }; diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index 25de780853291..892be36aae770 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -8,6 +8,7 @@ #![feature(never_type)] #![feature(rustc_attrs)] #![recursion_limit = "256"] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate tracing; @@ -205,7 +206,7 @@ pub fn stream_to_parser<'a>( stream: TokenStream, subparser_name: Option<&'static str>, ) -> Parser<'a> { - Parser::new(sess, stream, false, subparser_name) + Parser::new(sess, stream, subparser_name) } /// Runs the given subparser `f` on the tokens of the given `attr`'s item. @@ -215,7 +216,7 @@ pub fn parse_in<'a, T>( name: &'static str, mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>, ) -> PResult<'a, T> { - let mut parser = Parser::new(sess, tts, false, Some(name)); + let mut parser = Parser::new(sess, tts, Some(name)); let result = f(&mut parser)?; if parser.token != token::Eof { parser.unexpected()?; @@ -247,7 +248,7 @@ pub fn parse_cfg_attr( match parse_in(parse_sess, tokens.clone(), "`cfg_attr` input", |p| p.parse_cfg_attr()) { Ok(r) => return Some(r), Err(mut e) => { - e.help(format!("the valid syntax is `{}`", CFG_ATTR_GRAMMAR_HELP)) + e.help(format!("the valid syntax is `{CFG_ATTR_GRAMMAR_HELP}`")) .note(CFG_ATTR_NOTE_REF) .emit(); } diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index e1db19557cfe2..104de47b97dd1 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -36,7 +36,7 @@ impl<'a> Parser<'a> { pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> { let mut outer_attrs = ast::AttrVec::new(); let mut just_parsed_doc_comment = false; - let start_pos = self.token_cursor.num_next_calls; + let start_pos = self.num_bump_calls; loop { let attr = if self.check(&token::Pound) { let prev_outer_attr_sp = outer_attrs.last().map(|attr| attr.span); @@ -277,7 +277,7 @@ impl<'a> Parser<'a> { pub(crate) fn parse_inner_attributes(&mut self) -> PResult<'a, ast::AttrVec> { let mut attrs = ast::AttrVec::new(); loop { - let start_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap(); + let start_pos: u32 = self.num_bump_calls.try_into().unwrap(); // Only try to parse if it is an inner attribute (has `!`). let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) { Some(self.parse_attribute(InnerAttrPolicy::Permitted)?) @@ -298,7 +298,7 @@ impl<'a> Parser<'a> { None }; if let Some(attr) = attr { - let end_pos: u32 = self.token_cursor.num_next_calls.try_into().unwrap(); + let end_pos: u32 = self.num_bump_calls.try_into().unwrap(); // If we are currently capturing tokens, mark the location of this inner attribute. // If capturing ends up creating a `LazyAttrTokenStream`, we will include // this replace range with it, removing the inner attribute from the final @@ -422,15 +422,12 @@ impl<'a> Parser<'a> { } } -pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool { - // One of the attributes may either itself be a macro, - // or expand to macro attributes (`cfg_attr`). - attrs.iter().any(|attr| { - if attr.is_doc_comment() { - return false; - } - attr.ident().map_or(true, |ident| { - ident.name == sym::cfg_attr || !rustc_feature::is_builtin_attr_name(ident.name) - }) +/// The attributes are complete if all attributes are either a doc comment or a builtin attribute other than `cfg_attr` +pub fn is_complete(attrs: &[ast::Attribute]) -> bool { + attrs.iter().all(|attr| { + attr.is_doc_comment() + || attr.ident().is_some_and(|ident| { + ident.name != sym::cfg_attr && rustc_feature::is_builtin_attr_name(ident.name) + }) }) } diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs index 1e6ac54964f99..5d6c574baa612 100644 --- a/compiler/rustc_parse/src/parser/attr_wrapper.rs +++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs @@ -61,8 +61,8 @@ impl AttrWrapper { self.attrs.is_empty() } - pub fn maybe_needs_tokens(&self) -> bool { - crate::parser::attr::maybe_needs_tokens(&self.attrs) + pub fn is_complete(&self) -> bool { + crate::parser::attr::is_complete(&self.attrs) } } @@ -107,7 +107,7 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl { let tokens = std::iter::once((FlatToken::Token(self.start_token.0.clone()), self.start_token.1)) .chain((0..self.num_calls).map(|_| { - let token = cursor_snapshot.next(cursor_snapshot.desugar_doc_comments); + let token = cursor_snapshot.next(); (FlatToken::Token(token.0), token.1) })) .take(self.num_calls); @@ -145,13 +145,11 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl { // another replace range will capture the *replaced* tokens for the inner // range, not the original tokens. for (range, new_tokens) in replace_ranges.into_iter().rev() { - assert!(!range.is_empty(), "Cannot replace an empty range: {:?}", range); + assert!(!range.is_empty(), "Cannot replace an empty range: {range:?}"); // Replace ranges are only allowed to decrease the number of tokens. assert!( range.len() >= new_tokens.len(), - "Range {:?} has greater len than {:?}", - range, - new_tokens + "Range {range:?} has greater len than {new_tokens:?}" ); // Replace any removed tokens with `FlatToken::Empty`. @@ -201,7 +199,7 @@ impl<'a> Parser<'a> { // by definition if matches!(force_collect, ForceCollect::No) // None of our outer attributes can require tokens (e.g. a proc-macro) - && !attrs.maybe_needs_tokens() + && attrs.is_complete() // If our target supports custom inner attributes, then we cannot bail // out early, since we may need to capture tokens for a custom inner attribute // invocation. @@ -215,6 +213,7 @@ impl<'a> Parser<'a> { let start_token = (self.token.clone(), self.token_spacing); let cursor_snapshot = self.token_cursor.clone(); + let start_pos = self.num_bump_calls; let has_outer_attrs = !attrs.attrs.is_empty(); let prev_capturing = std::mem::replace(&mut self.capture_state.capturing, Capturing::Yes); @@ -244,9 +243,9 @@ impl<'a> Parser<'a> { // Now that we've parsed an AST node, we have more information available. if matches!(force_collect, ForceCollect::No) // We now have inner attributes available, so this check is more precise - // than `attrs.maybe_needs_tokens()` at the start of the function. + // than `attrs.is_complete()` at the start of the function. // As a result, we don't need to check `R::SUPPORTS_CUSTOM_INNER_ATTRS` - && !crate::parser::attr::maybe_needs_tokens(ret.attrs()) + && crate::parser::attr::is_complete(ret.attrs()) // Subtle: We call `has_cfg_or_cfg_attr` with the attrs from `ret`. // This ensures that we consider inner attributes (e.g. `#![cfg]`), // which require us to have tokens available @@ -275,8 +274,7 @@ impl<'a> Parser<'a> { let replace_ranges_end = self.capture_state.replace_ranges.len(); - let cursor_snapshot_next_calls = cursor_snapshot.num_next_calls; - let mut end_pos = self.token_cursor.num_next_calls; + let mut end_pos = self.num_bump_calls; let mut captured_trailing = false; @@ -303,12 +301,12 @@ impl<'a> Parser<'a> { // then extend the range of captured tokens to include it, since the parser // was not actually bumped past it. When the `LazyAttrTokenStream` gets converted // into an `AttrTokenStream`, we will create the proper token. - if self.token_cursor.break_last_token { + if self.break_last_token { assert!(!captured_trailing, "Cannot set break_last_token and have trailing token"); end_pos += 1; } - let num_calls = end_pos - cursor_snapshot_next_calls; + let num_calls = end_pos - start_pos; // If we have no attributes, then we will never need to // use any replace ranges. @@ -318,7 +316,7 @@ impl<'a> Parser<'a> { // Grab any replace ranges that occur *inside* the current AST node. // We will perform the actual replacement when we convert the `LazyAttrTokenStream` // to an `AttrTokenStream`. - let start_calls: u32 = cursor_snapshot_next_calls.try_into().unwrap(); + let start_calls: u32 = start_pos.try_into().unwrap(); self.capture_state.replace_ranges[replace_ranges_start..replace_ranges_end] .iter() .cloned() @@ -333,7 +331,7 @@ impl<'a> Parser<'a> { start_token, num_calls, cursor_snapshot, - break_last_token: self.token_cursor.break_last_token, + break_last_token: self.break_last_token, replace_ranges, }); @@ -361,14 +359,10 @@ impl<'a> Parser<'a> { // with a `FlatToken::AttrTarget`. If this AST node is inside an item // that has `#[derive]`, then this will allow us to cfg-expand this // AST node. - let start_pos = - if has_outer_attrs { attrs.start_pos } else { cursor_snapshot_next_calls }; + let start_pos = if has_outer_attrs { attrs.start_pos } else { start_pos }; let new_tokens = vec![(FlatToken::AttrTarget(attr_data), Spacing::Alone)]; - assert!( - !self.token_cursor.break_last_token, - "Should not have unglued last token with cfg attr" - ); + assert!(!self.break_last_token, "Should not have unglued last token with cfg attr"); let range: Range = (start_pos.try_into().unwrap())..(end_pos.try_into().unwrap()); self.capture_state.replace_ranges.push((range, new_tokens)); self.capture_state.replace_ranges.extend(inner_attr_replace_ranges); @@ -409,22 +403,19 @@ fn make_token_stream( FlatToken::Token(Token { kind: TokenKind::CloseDelim(delim), span }) => { let frame_data = stack .pop() - .unwrap_or_else(|| panic!("Token stack was empty for token: {:?}", token)); + .unwrap_or_else(|| panic!("Token stack was empty for token: {token:?}")); let (open_delim, open_sp) = frame_data.open_delim_sp.unwrap(); assert_eq!( open_delim, delim, - "Mismatched open/close delims: open={:?} close={:?}", - open_delim, span + "Mismatched open/close delims: open={open_delim:?} close={span:?}" ); let dspan = DelimSpan::from_pair(open_sp, span); let stream = AttrTokenStream::new(frame_data.inner); let delimited = AttrTokenTree::Delimited(dspan, delim, stream); stack .last_mut() - .unwrap_or_else(|| { - panic!("Bottom token frame is missing for token: {:?}", token) - }) + .unwrap_or_else(|| panic!("Bottom token frame is missing for token: {token:?}")) .inner .push(delimited); } @@ -456,7 +447,7 @@ fn make_token_stream( .inner .push(AttrTokenTree::Token(Token::new(unglued_first, first_span), spacing)); } else { - panic!("Unexpected last token {:?}", last_token) + panic!("Unexpected last token {last_token:?}") } } AttrTokenStream::new(final_buf.inner) @@ -469,6 +460,6 @@ mod size_asserts { use rustc_data_structures::static_assert_size; // tidy-alphabetical-start static_assert_size!(AttrWrapper, 16); - static_assert_size!(LazyAttrTokenStreamImpl, 120); + static_assert_size!(LazyAttrTokenStreamImpl, 104); // tidy-alphabetical-end } diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index c145403968574..6c8ef34063f87 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -4,17 +4,18 @@ use super::{ TokenExpectType, TokenType, }; use crate::errors::{ - AmbiguousPlus, AttributeOnParamType, BadQPathStage2, BadTypePlus, BadTypePlusSub, ColonAsSemi, - ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg, - ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentDoesNotDocumentAnything, - DocCommentOnParamType, DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, + AmbiguousPlus, AsyncMoveBlockIn2015, AttributeOnParamType, BadQPathStage2, BadTypePlus, + BadTypePlusSub, ColonAsSemi, ComparisonOperatorsCannotBeChained, + ComparisonOperatorsCannotBeChainedSugg, ConstGenericWithoutBraces, + ConstGenericWithoutBracesSugg, DocCommentDoesNotDocumentAnything, DocCommentOnParamType, + DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg, HelpIdentifierStartsWithNumber, InInTypo, IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, ParenthesesInForHead, ParenthesesInForHeadSugg, PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, StructLiteralNeedingParens, StructLiteralNeedingParensSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma, - UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, + TernaryOperator, UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, }; @@ -247,7 +248,7 @@ impl<'a> Parser<'a> { self.sess.span_diagnostic.struct_span_err(sp, m) } - pub fn span_bug>(&self, sp: S, m: impl Into) -> ! { + pub fn span_bug>(&self, sp: S, m: impl Into) -> ! { self.sess.span_diagnostic.span_bug(sp, m) } @@ -500,6 +501,10 @@ impl<'a> Parser<'a> { // Special-case "expected `;`" errors if expected.contains(&TokenType::Token(token::Semi)) { + if self.prev_token == token::Question && self.maybe_recover_from_ternary_operator() { + return Ok(true); + } + if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP { // Likely inside a macro, can't provide meaningful suggestions. } else if !sm.is_multiline(self.prev_token.span.until(self.token.span)) { @@ -569,6 +574,12 @@ impl<'a> Parser<'a> { return Err(self.sess.create_err(UseEqInstead { span: self.token.span })); } + if self.token.is_keyword(kw::Move) && self.prev_token.is_keyword(kw::Async) { + // The 2015 edition is in use because parsing of `async move` has failed. + let span = self.prev_token.span.to(self.token.span); + return Err(self.sess.create_err(AsyncMoveBlockIn2015 { span })); + } + let expect = tokens_to_string(&expected); let actual = super::token_descr(&self.token); let (msg_exp, (label_sp, label_exp)) = if expected.len() > 1 { @@ -605,6 +616,22 @@ impl<'a> Parser<'a> { } } + if let TokenKind::Ident(prev, _) = &self.prev_token.kind + && let TokenKind::Ident(cur, _) = &self.token.kind + { + let concat = Symbol::intern(&format!("{prev}{cur}")); + let ident = Ident::new(concat, DUMMY_SP); + if ident.is_used_keyword() || ident.is_reserved() || ident.is_raw_guess() { + let span = self.prev_token.span.to(self.token.span); + err.span_suggestion_verbose( + span, + format!("consider removing the space to spell keyword `{concat}`"), + concat, + Applicability::MachineApplicable, + ); + } + } + // `pub` may be used for an item or `pub(crate)` if self.prev_token.is_ident_named(sym::public) && (self.token.can_begin_item() @@ -751,13 +778,24 @@ impl<'a> Parser<'a> { tail.could_be_bare_literal = true; if maybe_struct_name.is_ident() && can_be_struct_literal { // Account for `if Example { a: one(), }.is_pos() {}`. - Err(self.sess.create_err(StructLiteralNeedingParens { - span: maybe_struct_name.span.to(expr.span), - sugg: StructLiteralNeedingParensSugg { - before: maybe_struct_name.span.shrink_to_lo(), - after: expr.span.shrink_to_hi(), - }, - })) + // expand `before` so that we take care of module path such as: + // `foo::Bar { ... } ` + // we expect to suggest `(foo::Bar { ... })` instead of `foo::(Bar { ... })` + let sm = self.sess.source_map(); + let before = maybe_struct_name.span.shrink_to_lo(); + if let Ok(extend_before) = sm.span_extend_prev_while(before, |t| { + t.is_alphanumeric() || t == ':' || t == '_' + }) { + Err(self.sess.create_err(StructLiteralNeedingParens { + span: maybe_struct_name.span.to(expr.span), + sugg: StructLiteralNeedingParensSugg { + before: extend_before.shrink_to_lo(), + after: expr.span.shrink_to_hi(), + }, + })) + } else { + return None; + } } else { self.sess.emit_err(StructLiteralBodyWithoutPath { span: expr.span, @@ -1303,6 +1341,45 @@ impl<'a> Parser<'a> { } } + /// Rust has no ternary operator (`cond ? then : else`). Parse it and try + /// to recover from it if `then` and `else` are valid expressions. Returns + /// whether it was a ternary operator. + pub(super) fn maybe_recover_from_ternary_operator(&mut self) -> bool { + if self.prev_token != token::Question { + return false; + } + + let lo = self.prev_token.span.lo(); + let snapshot = self.create_snapshot_for_diagnostic(); + + if match self.parse_expr() { + Ok(_) => true, + Err(err) => { + err.cancel(); + // The colon can sometimes be mistaken for type + // ascription. Catch when this happens and continue. + self.token == token::Colon + } + } { + if self.eat_noexpect(&token::Colon) { + match self.parse_expr() { + Ok(_) => { + self.sess.emit_err(TernaryOperator { span: self.token.span.with_lo(lo) }); + return true; + } + Err(err) => { + err.cancel(); + self.restore_snapshot(snapshot); + } + }; + } + } else { + self.restore_snapshot(snapshot); + }; + + false + } + pub(super) fn maybe_recover_from_bad_type_plus(&mut self, ty: &Ty) -> PResult<'a, ()> { // Do not add `+` to expected tokens. if !self.token.is_like_plus() { @@ -1407,8 +1484,9 @@ impl<'a> Parser<'a> { self.inc_dec_standalone_suggest(kind, spans).emit_verbose(&mut err) } IsStandalone::Subexpr => { - let Ok(base_src) = self.span_to_snippet(base.span) - else { return help_base_case(err, base) }; + let Ok(base_src) = self.span_to_snippet(base.span) else { + return help_base_case(err, base); + }; match kind.fixity { UnaryFixity::Pre => { self.prefix_inc_dec_suggest(base_src, kind, spans).emit(&mut err) @@ -1639,13 +1717,7 @@ impl<'a> Parser<'a> { self.recover_await_prefix(await_sp)? }; let sp = self.error_on_incorrect_await(lo, hi, &expr, is_question); - let kind = match expr.kind { - // Avoid knock-down errors as we don't know whether to interpret this as `foo().await?` - // or `foo()?.await` (the very reason we went with postfix syntax 😅). - ExprKind::Try(_) => ExprKind::Err, - _ => ExprKind::Await(expr, await_sp), - }; - let expr = self.mk_expr(lo.to(sp), kind); + let expr = self.mk_expr(lo.to(sp), ExprKind::Err); self.maybe_recover_from_bad_qpath(expr) } @@ -2029,7 +2101,7 @@ impl<'a> Parser<'a> { } pub(super) fn recover_arg_parse(&mut self) -> PResult<'a, (P, P)> { - let pat = self.parse_pat_no_top_alt(Some(Expected::ArgumentName))?; + let pat = self.parse_pat_no_top_alt(Some(Expected::ArgumentName), None)?; self.expect(&token::Colon)?; let ty = self.parse_ty()?; @@ -2083,7 +2155,7 @@ impl<'a> Parser<'a> { } _ => ( self.token.span, - format!("expected expression, found {}", super::token_descr(&self.token),), + format!("expected expression, found {}", super::token_descr(&self.token)), ), }; let mut err = self.struct_span_err(span, msg); @@ -2437,7 +2509,7 @@ impl<'a> Parser<'a> { // Skip the `:`. snapshot_pat.bump(); snapshot_type.bump(); - match snapshot_pat.parse_pat_no_top_alt(expected) { + match snapshot_pat.parse_pat_no_top_alt(expected, None) { Err(inner_err) => { inner_err.cancel(); } @@ -2563,6 +2635,7 @@ impl<'a> Parser<'a> { pub(crate) fn maybe_recover_unexpected_comma( &mut self, lo: Span, + is_mac_invoc: bool, rt: CommaRecoveryMode, ) -> PResult<'a, ()> { if self.token != token::Comma { @@ -2583,24 +2656,28 @@ impl<'a> Parser<'a> { let seq_span = lo.to(self.prev_token.span); let mut err = self.struct_span_err(comma_span, "unexpected `,` in pattern"); if let Ok(seq_snippet) = self.span_to_snippet(seq_span) { - err.multipart_suggestion( - format!( - "try adding parentheses to match on a tuple{}", - if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." }, - ), - vec![ - (seq_span.shrink_to_lo(), "(".to_string()), - (seq_span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MachineApplicable, - ); - if let CommaRecoveryMode::EitherTupleOrPipe = rt { - err.span_suggestion( - seq_span, - "...or a vertical bar to match on multiple alternatives", - seq_snippet.replace(',', " |"), + if is_mac_invoc { + err.note(fluent::parse_macro_expands_to_match_arm); + } else { + err.multipart_suggestion( + format!( + "try adding parentheses to match on a tuple{}", + if let CommaRecoveryMode::LikelyTuple = rt { "" } else { "..." }, + ), + vec![ + (seq_span.shrink_to_lo(), "(".to_string()), + (seq_span.shrink_to_hi(), ")".to_string()), + ], Applicability::MachineApplicable, ); + if let CommaRecoveryMode::EitherTupleOrPipe = rt { + err.span_suggestion( + seq_span, + "...or a vertical bar to match on multiple alternatives", + seq_snippet.replace(',', " |"), + Applicability::MachineApplicable, + ); + } } } Err(err) @@ -2701,7 +2778,7 @@ impl<'a> Parser<'a> { /// sequence of patterns until `)` is reached. fn skip_pat_list(&mut self) -> PResult<'a, ()> { while !self.check(&token::CloseDelim(Delimiter::Parenthesis)) { - self.parse_pat_no_top_alt(None)?; + self.parse_pat_no_top_alt(None, None)?; if !self.eat(&token::Comma) { return Ok(()); } diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 1b28f3c97e8f6..9ae3ef6172c73 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -22,6 +22,7 @@ use rustc_ast::{AnonConst, BinOp, BinOpKind, FnDecl, FnRetTy, MacCall, Param, Ty use rustc_ast::{Arm, Async, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits}; use rustc_ast::{ClosureBinder, MetaItemLit, StmtKind}; use rustc_ast_pretty::pprust; +use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{ AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult, StashKey, @@ -91,6 +92,18 @@ impl From> for LhsExpr { } } +#[derive(Debug)] +enum DestructuredFloat { + /// 1e2 + Single(Symbol, Span), + /// 1. + TrailingDot(Symbol, Span, Span), + /// 1.2 | 1.2e3 + MiddleDot(Symbol, Span, Span, Symbol, Span), + /// Invalid + Error, +} + impl<'a> Parser<'a> { /// Parses an expression. #[inline] @@ -181,13 +194,7 @@ impl<'a> Parser<'a> { self.expected_tokens.push(TokenType::Operator); while let Some(op) = self.check_assoc_op() { - // Adjust the span for interpolated LHS to point to the `$lhs` token - // and not to what it refers to. - let lhs_span = match self.prev_token.kind { - TokenKind::Interpolated(..) => self.prev_token.span, - _ => lhs.span, - }; - + let lhs_span = self.interpolated_or_expr_span(&lhs); let cur_op_span = self.token.span; let restrictions = if op.node.is_assign_like() { self.restrictions & Restrictions::NO_STRUCT_LITERAL @@ -226,7 +233,7 @@ impl<'a> Parser<'a> { _ => unreachable!(), } .into(); - let invalid = format!("{}=", &sugg); + let invalid = format!("{sugg}="); self.sess.emit_err(errors::InvalidComparisonOperator { span: sp, invalid: invalid.clone(), @@ -614,8 +621,8 @@ impl<'a> Parser<'a> { fn parse_expr_prefix_common(&mut self, lo: Span) -> PResult<'a, (Span, P)> { self.bump(); - let expr = self.parse_expr_prefix(None); - let (span, expr) = self.interpolated_or_expr_span(expr)?; + let expr = self.parse_expr_prefix(None)?; + let span = self.interpolated_or_expr_span(&expr); Ok((lo.to(span), expr)) } @@ -690,20 +697,12 @@ impl<'a> Parser<'a> { self.parse_expr_unary(lo, UnOp::Not) } - /// Returns the span of expr, if it was not interpolated or the span of the interpolated token. - fn interpolated_or_expr_span( - &self, - expr: PResult<'a, P>, - ) -> PResult<'a, (Span, P)> { - expr.map(|e| { - ( - match self.prev_token.kind { - TokenKind::Interpolated(..) => self.prev_token.span, - _ => e.span, - }, - e, - ) - }) + /// Returns the span of expr if it was not interpolated, or the span of the interpolated token. + fn interpolated_or_expr_span(&self, expr: &Expr) -> Span { + match self.prev_token.kind { + TokenKind::Interpolated(..) => self.prev_token.span, + _ => expr.span, + } } fn parse_assoc_op_cast( @@ -845,7 +844,7 @@ impl<'a> Parser<'a> { let msg = format!( "cast cannot be followed by {}", match with_postfix.kind { - ExprKind::Index(_, _) => "indexing", + ExprKind::Index(..) => "indexing", ExprKind::Try(_) => "`?`", ExprKind::Field(_, _) => "a field access", ExprKind::MethodCall(_) => "a method call", @@ -886,8 +885,8 @@ impl<'a> Parser<'a> { self.parse_expr_prefix_range(None) } else { self.parse_expr_prefix(None) - }; - let (hi, expr) = self.interpolated_or_expr_span(expr)?; + }?; + let hi = self.interpolated_or_expr_span(&expr); let span = lo.to(hi); if let Some(lt) = lifetime { self.error_remove_borrow_lifetime(span, lt.ident.span); @@ -918,8 +917,8 @@ impl<'a> Parser<'a> { fn parse_expr_dot_or_call(&mut self, attrs: Option) -> PResult<'a, P> { let attrs = self.parse_or_use_outer_attributes(attrs)?; self.collect_tokens_for_expr(attrs, |this, attrs| { - let base = this.parse_expr_bottom(); - let (span, base) = this.interpolated_or_expr_span(base)?; + let base = this.parse_expr_bottom()?; + let span = this.interpolated_or_expr_span(&base); this.parse_expr_dot_or_call_with(base, span, attrs) }) } @@ -1001,9 +1000,15 @@ impl<'a> Parser<'a> { } fn error_unexpected_after_dot(&self) { - // FIXME Could factor this out into non_fatal_unexpected or something. let actual = pprust::token_to_string(&self.token); - self.sess.emit_err(errors::UnexpectedTokenAfterDot { span: self.token.span, actual }); + let span = self.token.span; + let sm = self.sess.source_map(); + let (span, actual) = match (&self.token.kind, self.subparser_name) { + (token::Eof, Some(_)) if let Ok(actual) = sm.span_to_snippet(sm.next_point(span)) => + (span.shrink_to_hi(), actual.into()), + _ => (span, actual), + }; + self.sess.emit_err(errors::UnexpectedTokenAfterDot { span, actual }); } // We need an identifier or integer, but the next token is a float. @@ -1013,13 +1018,8 @@ impl<'a> Parser<'a> { // support pushing "future tokens" (would be also helpful to `break_and_eat`), or // we should break everything including floats into more basic proc-macro style // tokens in the lexer (probably preferable). - fn parse_expr_tuple_field_access_float( - &mut self, - lo: Span, - base: P, - float: Symbol, - suffix: Option, - ) -> P { + // See also `TokenKind::break_two_token_op` which does similar splitting of `>>` into `>`. + fn break_up_float(&mut self, float: Symbol) -> DestructuredFloat { #[derive(Debug)] enum FloatComponent { IdentLike(String), @@ -1039,7 +1039,7 @@ impl<'a> Parser<'a> { } components.push(Punct(c)); } else { - panic!("unexpected character in a float token: {:?}", c) + panic!("unexpected character in a float token: {c:?}") } } if !ident_like.is_empty() { @@ -1056,7 +1056,7 @@ impl<'a> Parser<'a> { match &*components { // 1e2 [IdentLike(i)] => { - self.parse_expr_tuple_field_access(lo, base, Symbol::intern(&i), suffix, None) + DestructuredFloat::Single(Symbol::intern(&i), span) } // 1. [IdentLike(i), Punct('.')] => { @@ -1068,11 +1068,8 @@ impl<'a> Parser<'a> { } else { (span, span) }; - assert!(suffix.is_none()); let symbol = Symbol::intern(&i); - self.token = Token::new(token::Ident(symbol, false), ident_span); - let next_token = (Token::new(token::Dot, dot_span), self.token_spacing); - self.parse_expr_tuple_field_access(lo, base, symbol, None, Some(next_token)) + DestructuredFloat::TrailingDot(symbol, ident_span, dot_span) } // 1.2 | 1.2e3 [IdentLike(i1), Punct('.'), IdentLike(i2)] => { @@ -1088,16 +1085,8 @@ impl<'a> Parser<'a> { (span, span, span) }; let symbol1 = Symbol::intern(&i1); - self.token = Token::new(token::Ident(symbol1, false), ident1_span); - // This needs to be `Spacing::Alone` to prevent regressions. - // See issue #76399 and PR #76285 for more details - let next_token1 = (Token::new(token::Dot, dot_span), Spacing::Alone); - let base1 = - self.parse_expr_tuple_field_access(lo, base, symbol1, None, Some(next_token1)); let symbol2 = Symbol::intern(&i2); - let next_token2 = Token::new(token::Ident(symbol2, false), ident2_span); - self.bump_with((next_token2, self.token_spacing)); // `.` - self.parse_expr_tuple_field_access(lo, base1, symbol2, suffix, None) + DestructuredFloat::MiddleDot(symbol1, ident1_span, dot_span, symbol2, ident2_span) } // 1e+ | 1e- (recovered) [IdentLike(_), Punct('+' | '-')] | @@ -1109,12 +1098,83 @@ impl<'a> Parser<'a> { [IdentLike(_), Punct('.'), IdentLike(_), Punct('+' | '-'), IdentLike(_)] => { // See the FIXME about `TokenCursor` above. self.error_unexpected_after_dot(); - base + DestructuredFloat::Error } - _ => panic!("unexpected components in a float token: {:?}", components), + _ => panic!("unexpected components in a float token: {components:?}"), } } + fn parse_expr_tuple_field_access_float( + &mut self, + lo: Span, + base: P, + float: Symbol, + suffix: Option, + ) -> P { + match self.break_up_float(float) { + // 1e2 + DestructuredFloat::Single(sym, _sp) => { + self.parse_expr_tuple_field_access(lo, base, sym, suffix, None) + } + // 1. + DestructuredFloat::TrailingDot(sym, ident_span, dot_span) => { + assert!(suffix.is_none()); + self.token = Token::new(token::Ident(sym, false), ident_span); + let next_token = (Token::new(token::Dot, dot_span), self.token_spacing); + self.parse_expr_tuple_field_access(lo, base, sym, None, Some(next_token)) + } + // 1.2 | 1.2e3 + DestructuredFloat::MiddleDot(symbol1, ident1_span, dot_span, symbol2, ident2_span) => { + self.token = Token::new(token::Ident(symbol1, false), ident1_span); + // This needs to be `Spacing::Alone` to prevent regressions. + // See issue #76399 and PR #76285 for more details + let next_token1 = (Token::new(token::Dot, dot_span), Spacing::Alone); + let base1 = + self.parse_expr_tuple_field_access(lo, base, symbol1, None, Some(next_token1)); + let next_token2 = Token::new(token::Ident(symbol2, false), ident2_span); + self.bump_with((next_token2, self.token_spacing)); // `.` + self.parse_expr_tuple_field_access(lo, base1, symbol2, suffix, None) + } + DestructuredFloat::Error => base, + } + } + + fn parse_field_name_maybe_tuple(&mut self) -> PResult<'a, ThinVec> { + let token::Literal(token::Lit { kind: token::Float, symbol, suffix }) = self.token.kind + else { + return Ok(thin_vec![self.parse_field_name()?]); + }; + Ok(match self.break_up_float(symbol) { + // 1e2 + DestructuredFloat::Single(sym, sp) => { + self.bump(); + thin_vec![Ident::new(sym, sp)] + } + // 1. + DestructuredFloat::TrailingDot(sym, sym_span, dot_span) => { + assert!(suffix.is_none()); + // Analogous to `Self::break_and_eat` + self.break_last_token = true; + // This might work, in cases like `1. 2`, and might not, + // in cases like `offset_of!(Ty, 1.)`. It depends on what comes + // after the float-like token, and therefore we have to make + // the other parts of the parser think that there is a dot literal. + self.token = Token::new(token::Ident(sym, false), sym_span); + self.bump_with((Token::new(token::Dot, dot_span), self.token_spacing)); + thin_vec![Ident::new(sym, sym_span)] + } + // 1.2 | 1.2e3 + DestructuredFloat::MiddleDot(symbol1, ident1_span, _dot_span, symbol2, ident2_span) => { + self.bump(); + thin_vec![Ident::new(symbol1, ident1_span), Ident::new(symbol2, ident2_span)] + } + DestructuredFloat::Error => { + self.bump(); + thin_vec![Ident::new(symbol, self.prev_token.span)] + } + }) + } + fn parse_expr_tuple_field_access( &mut self, lo: Span, @@ -1231,12 +1291,15 @@ impl<'a> Parser<'a> { let index = self.parse_expr()?; self.suggest_missing_semicolon_before_array(prev_span, open_delim_span)?; self.expect(&token::CloseDelim(Delimiter::Bracket))?; - Ok(self.mk_expr(lo.to(self.prev_token.span), self.mk_index(base, index))) + Ok(self.mk_expr( + lo.to(self.prev_token.span), + self.mk_index(base, index, open_delim_span.to(self.prev_token.span)), + )) } /// Assuming we have just parsed `.`, continue parsing into an expression. fn parse_dot_suffix(&mut self, self_arg: P, lo: Span) -> PResult<'a, P> { - if self.token.uninterpolated_span().rust_2018() && self.eat_keyword(kw::Await) { + if self.token.uninterpolated_span().at_least_rust_2018() && self.eat_keyword(kw::Await) { return Ok(self.mk_await_expr(self_arg, lo)); } @@ -1363,12 +1426,14 @@ impl<'a> Parser<'a> { self.parse_expr_yield() } else if self.is_do_yeet() { self.parse_expr_yeet() + } else if self.eat_keyword(kw::Become) { + self.parse_expr_become() } else if self.check_keyword(kw::Let) { self.parse_expr_let() } else if self.eat_keyword(kw::Underscore) { Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore)) - } else if self.token.uninterpolated_span().rust_2018() { - // `Span::rust_2018()` is somewhat expensive; don't get it repeatedly. + } else if self.token.uninterpolated_span().at_least_rust_2018() { + // `Span:.at_least_rust_2018()` is somewhat expensive; don't get it repeatedly. if self.check_keyword(kw::Async) { if self.is_async_block() { // Check for `async {` and `async move {`. @@ -1679,6 +1744,16 @@ impl<'a> Parser<'a> { self.maybe_recover_from_bad_qpath(expr) } + /// Parse `"become" expr`, with `"become"` token already eaten. + fn parse_expr_become(&mut self) -> PResult<'a, P> { + let lo = self.prev_token.span; + let kind = ExprKind::Become(self.parse_expr()?); + let span = lo.to(self.prev_token.span); + self.sess.gated_spans.gate(sym::explicit_tail_calls, span); + let expr = self.mk_expr(span, kind); + self.maybe_recover_from_bad_qpath(expr) + } + /// Parse `"break" (('label (:? expr)?) | expr?)` with `"break"` token already eaten. /// If the label is followed immediately by a `:` token, the label and `:` are /// parsed as part of the expression (i.e. a labeled loop). The language team has @@ -1821,10 +1896,11 @@ impl<'a> Parser<'a> { let (fields, _trailing, _recovered) = self.parse_seq_to_before_end( &TokenKind::CloseDelim(Delimiter::Parenthesis), seq_sep, - Parser::parse_field_name, + Parser::parse_field_name_maybe_tuple, )?; + let fields = fields.into_iter().flatten().collect::>(); let span = lo.to(self.token.span); - Ok(self.mk_expr(span, ExprKind::OffsetOf(container, fields.to_vec().into()))) + Ok(self.mk_expr(span, ExprKind::OffsetOf(container, fields.into()))) } /// Returns a string literal if the next token is a string literal. @@ -1955,17 +2031,14 @@ impl<'a> Parser<'a> { let recovered = self.recover_after_dot(); let token = recovered.as_ref().unwrap_or(&self.token); match token::Lit::from_token(token) { - Some(token_lit) => { - match MetaItemLit::from_token_lit(token_lit, token.span) { + Some(lit) => { + match MetaItemLit::from_token_lit(lit, token.span) { Ok(lit) => { self.bump(); Some(lit) } Err(err) => { - let span = token.span; - let token::Literal(lit) = token.kind else { - unreachable!(); - }; + let span = token.uninterpolated_span(); self.bump(); report_lit_error(&self.sess, err, lit, span); // Pack possible quotes and prefixes from the original literal into @@ -2109,6 +2182,10 @@ impl<'a> Parser<'a> { self.sess.emit_err(errors::InvalidBlockMacroSegment { span: self.token.span, context: lo.to(self.token.span), + wrap: errors::WrapInExplicitBlock { + lo: self.token.span.shrink_to_lo(), + hi: self.token.span.shrink_to_hi(), + }, }); } @@ -2143,7 +2220,7 @@ impl<'a> Parser<'a> { let movability = if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable }; - let asyncness = if self.token.uninterpolated_span().rust_2018() { + let asyncness = if self.token.uninterpolated_span().at_least_rust_2018() { self.parse_asyncness(Case::Sensitive) } else { Async::No @@ -2251,11 +2328,11 @@ impl<'a> Parser<'a> { let lo = self.token.span; let attrs = self.parse_outer_attributes()?; self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { - let pat = this.parse_pat_no_top_alt(Some(Expected::ParameterName))?; + let pat = this.parse_pat_no_top_alt(Some(Expected::ParameterName), None)?; let ty = if this.eat(&token::Colon) { this.parse_ty()? } else { - this.mk_ty(this.prev_token.span, TyKind::Infer) + this.mk_ty(pat.span, TyKind::Infer) }; Ok(( @@ -2413,7 +2490,7 @@ impl<'a> Parser<'a> { let else_span = self.prev_token.span; // `else` let attrs = self.parse_outer_attributes()?; // For recovery. let expr = if self.eat_keyword(kw::If) { - self.parse_expr_if()? + ensure_sufficient_stack(|| self.parse_expr_if())? } else if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) { self.parse_simple_block()? } else { @@ -2512,7 +2589,7 @@ impl<'a> Parser<'a> { // Recover from missing expression in `for` loop if matches!(expr.kind, ExprKind::Block(..)) - && !matches!(self.token.kind, token::OpenDelim(token::Delimiter::Brace)) + && !matches!(self.token.kind, token::OpenDelim(Delimiter::Brace)) && self.may_recover() { self.sess @@ -2694,7 +2771,7 @@ impl<'a> Parser<'a> { return None; } let pre_pat_snapshot = self.create_snapshot_for_diagnostic(); - match self.parse_pat_no_top_alt(None) { + match self.parse_pat_no_top_alt(None, None) { Ok(_pat) => { if self.token.kind == token::FatArrow { // Reached arm end. @@ -2916,7 +2993,8 @@ impl<'a> Parser<'a> { fn is_do_catch_block(&self) -> bool { self.token.is_keyword(kw::Do) && self.is_keyword_ahead(1, &[kw::Catch]) - && self.look_ahead(2, |t| *t == token::OpenDelim(Delimiter::Brace)) + && self + .look_ahead(2, |t| *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block()) && !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL) } @@ -2926,8 +3004,9 @@ impl<'a> Parser<'a> { fn is_try_block(&self) -> bool { self.token.is_keyword(kw::Try) - && self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace)) - && self.token.uninterpolated_span().rust_2018() + && self + .look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block()) + && self.token.uninterpolated_span().at_least_rust_2018() } /// Parses an `async move? {...}` expression. @@ -2945,10 +3024,14 @@ impl<'a> Parser<'a> { && (( // `async move {` self.is_keyword_ahead(1, &[kw::Move]) - && self.look_ahead(2, |t| *t == token::OpenDelim(Delimiter::Brace)) + && self.look_ahead(2, |t| { + *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block() + }) ) || ( // `async {` - self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace)) + self.look_ahead(1, |t| { + *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block() + }) )) } @@ -3273,8 +3356,8 @@ impl<'a> Parser<'a> { ExprKind::Binary(binop, lhs, rhs) } - fn mk_index(&self, expr: P, idx: P) -> ExprKind { - ExprKind::Index(expr, idx) + fn mk_index(&self, expr: P, idx: P, brackets_span: Span) -> ExprKind { + ExprKind::Index(expr, idx, brackets_span) } fn mk_call(&self, f: P, args: ThinVec>) -> ExprKind { diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs index cd779b0b43ebd..242c9d332bb02 100644 --- a/compiler/rustc_parse/src/parser/generics.rs +++ b/compiler/rustc_parse/src/parser/generics.rs @@ -43,6 +43,15 @@ impl<'a> Parser<'a> { fn parse_ty_param(&mut self, preceding_attrs: AttrVec) -> PResult<'a, GenericParam> { let ident = self.parse_ident()?; + // We might have a typo'd `Const` that was parsed as a type parameter. + if self.may_recover() + && ident.name.as_str().to_ascii_lowercase() == kw::Const.as_str() + && self.check_ident() + // `Const` followed by IDENT + { + return self.recover_const_param_with_mistyped_const(preceding_attrs, ident); + } + // Parse optional colon and param bounds. let mut colon_span = None; let bounds = if self.eat(&token::Colon) { @@ -120,6 +129,41 @@ impl<'a> Parser<'a> { }) } + pub(crate) fn recover_const_param_with_mistyped_const( + &mut self, + preceding_attrs: AttrVec, + mistyped_const_ident: Ident, + ) -> PResult<'a, GenericParam> { + let ident = self.parse_ident()?; + self.expect(&token::Colon)?; + let ty = self.parse_ty()?; + + // Parse optional const generics default value. + let default = if self.eat(&token::Eq) { Some(self.parse_const_arg()?) } else { None }; + + let mut err = self.struct_span_err( + mistyped_const_ident.span, + format!("`const` keyword was mistyped as `{}`", mistyped_const_ident.as_str()), + ); + err.span_suggestion_verbose( + mistyped_const_ident.span, + "use the `const` keyword", + kw::Const.as_str(), + Applicability::MachineApplicable, + ); + err.emit(); + + Ok(GenericParam { + ident, + id: ast::DUMMY_NODE_ID, + attrs: preceding_attrs, + bounds: Vec::new(), + kind: GenericParamKind::Const { ty, kw_span: mistyped_const_ident.span, default }, + is_placeholder: false, + colon_span: None, + }) + } + /// Parses a (possibly empty) list of lifetime and type parameters, possibly including /// a trailing comma and erroneous trailing attributes. pub(super) fn parse_generic_params(&mut self) -> PResult<'a, ThinVec> { diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 3783ec41b7e23..24c65d061f95a 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1,20 +1,20 @@ -use crate::errors; - use super::diagnostics::{dummy_arg, ConsumeClosingDelim}; use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle, TrailingToken}; +use crate::errors::{self, MacroExpandsToAdtField}; +use crate::fluent_generated as fluent; use ast::StaticItem; use rustc_ast::ast::*; use rustc_ast::ptr::P; use rustc_ast::token::{self, Delimiter, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree}; use rustc_ast::util::case::Case; +use rustc_ast::MacCall; use rustc_ast::{self as ast, AttrVec, Attribute, DUMMY_NODE_ID}; use rustc_ast::{Async, Const, Defaultness, IsAuto, Mutability, Unsafe, UseTree, UseTreeKind}; use rustc_ast::{BindingAnnotation, Block, FnDecl, FnSig, Param, SelfKind}; use rustc_ast::{EnumDef, FieldDef, Generics, TraitRef, Ty, TyKind, Variant, VariantData}; use rustc_ast::{FnHeader, ForeignItem, Path, PathSegment, Visibility, VisibilityKind}; -use rustc_ast::{MacCall, MacDelimiter}; use rustc_ast_pretty::pprust; use rustc_errors::{ struct_span_err, Applicability, DiagnosticBuilder, ErrorGuaranteed, IntoDiagnostic, PResult, @@ -226,9 +226,9 @@ impl<'a> Parser<'a> { } else if self.is_static_global() { // STATIC ITEM self.bump(); // `static` - let m = self.parse_mutability(); - let (ident, ty, expr) = self.parse_item_global(Some(m))?; - (ident, ItemKind::Static(Box::new(StaticItem { ty, mutability: m, expr }))) + let mutability = self.parse_mutability(); + let (ident, item) = self.parse_static_item(mutability)?; + (ident, ItemKind::Static(Box::new(item))) } else if let Const::Yes(const_span) = self.parse_constness(Case::Sensitive) { // CONST ITEM if self.token.is_keyword(kw::Impl) { @@ -236,8 +236,16 @@ impl<'a> Parser<'a> { self.recover_const_impl(const_span, attrs, def_())? } else { self.recover_const_mut(const_span); - let (ident, ty, expr) = self.parse_item_global(None)?; - (ident, ItemKind::Const(Box::new(ConstItem { defaultness: def_(), ty, expr }))) + let (ident, generics, ty, expr) = self.parse_const_item()?; + ( + ident, + ItemKind::Const(Box::new(ConstItem { + defaultness: def_(), + generics, + ty, + expr, + })), + ) } } else if self.check_keyword(kw::Trait) || self.check_auto_or_unsafe_trait_item() { // TRAIT ITEM @@ -878,6 +886,7 @@ impl<'a> Parser<'a> { self.sess.emit_err(errors::AssociatedStaticItemNotAllowed { span }); AssocItemKind::Const(Box::new(ConstItem { defaultness: Defaultness::Final, + generics: Generics::default(), ty, expr, })) @@ -892,7 +901,7 @@ impl<'a> Parser<'a> { /// Parses a `type` alias with the following grammar: /// ```ebnf - /// TypeAlias = "type" Ident Generics {":" GenericBounds}? {"=" Ty}? ";" ; + /// TypeAlias = "type" Ident Generics (":" GenericBounds)? WhereClause ("=" Ty)? WhereClause ";" ; /// ``` /// The `"type"` has already been eaten. fn parse_type_alias(&mut self, defaultness: Defaultness) -> PResult<'a, ItemInfo> { @@ -1220,33 +1229,132 @@ impl<'a> Parser<'a> { Ok(impl_info) } - /// Parse `["const" | ("static" "mut"?)] $ident ":" $ty (= $expr)?` with - /// `["const" | ("static" "mut"?)]` already parsed and stored in `m`. + /// Parse a static item with the prefix `"static" "mut"?` already parsed and stored in `mutability`. /// - /// When `m` is `"const"`, `$ident` may also be `"_"`. - fn parse_item_global( - &mut self, - m: Option, - ) -> PResult<'a, (Ident, P, Option>)> { - let id = if m.is_none() { self.parse_ident_or_underscore() } else { self.parse_ident() }?; + /// ```ebnf + /// Static = "static" "mut"? $ident ":" $ty (= $expr)? ";" ; + /// ``` + fn parse_static_item(&mut self, mutability: Mutability) -> PResult<'a, (Ident, StaticItem)> { + let ident = self.parse_ident()?; - // Parse the type of a `const` or `static mut?` item. - // That is, the `":" $ty` fragment. + if self.token.kind == TokenKind::Lt && self.may_recover() { + let generics = self.parse_generics()?; + self.sess.emit_err(errors::StaticWithGenerics { span: generics.span }); + } + + // Parse the type of a static item. That is, the `":" $ty` fragment. + // FIXME: This could maybe benefit from `.may_recover()`? let ty = match (self.eat(&token::Colon), self.check(&token::Eq) | self.check(&token::Semi)) { - // If there wasn't a `:` or the colon was followed by a `=` or `;` recover a missing type. (true, false) => self.parse_ty()?, - (colon, _) => self.recover_missing_const_type(colon, m), + // If there wasn't a `:` or the colon was followed by a `=` or `;`, recover a missing type. + (colon, _) => self.recover_missing_global_item_type(colon, Some(mutability)), }; let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None }; + + self.expect_semi()?; + + Ok((ident, StaticItem { ty, mutability, expr })) + } + + /// Parse a constant item with the prefix `"const"` already parsed. + /// + /// ```ebnf + /// Const = "const" ($ident | "_") Generics ":" $ty (= $expr)? WhereClause ";" ; + /// ``` + fn parse_const_item(&mut self) -> PResult<'a, (Ident, Generics, P, Option>)> { + let ident = self.parse_ident_or_underscore()?; + + let mut generics = self.parse_generics()?; + + // Check the span for emptiness instead of the list of parameters in order to correctly + // recognize and subsequently flag empty parameter lists (`<>`) as unstable. + if !generics.span.is_empty() { + self.sess.gated_spans.gate(sym::generic_const_items, generics.span); + } + + // Parse the type of a constant item. That is, the `":" $ty` fragment. + // FIXME: This could maybe benefit from `.may_recover()`? + let ty = match ( + self.eat(&token::Colon), + self.check(&token::Eq) | self.check(&token::Semi) | self.check_keyword(kw::Where), + ) { + (true, false) => self.parse_ty()?, + // If there wasn't a `:` or the colon was followed by a `=`, `;` or `where`, recover a missing type. + (colon, _) => self.recover_missing_global_item_type(colon, None), + }; + + // Proactively parse a where-clause to be able to provide a good error message in case we + // encounter the item body following it. + let before_where_clause = + if self.may_recover() { self.parse_where_clause()? } else { WhereClause::default() }; + + let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None }; + + let after_where_clause = self.parse_where_clause()?; + + // Provide a nice error message if the user placed a where-clause before the item body. + // Users may be tempted to write such code if they are still used to the deprecated + // where-clause location on type aliases and associated types. See also #89122. + if before_where_clause.has_where_token && let Some(expr) = &expr { + self.sess.emit_err(errors::WhereClauseBeforeConstBody { + span: before_where_clause.span, + name: ident.span, + body: expr.span, + sugg: if !after_where_clause.has_where_token { + self.sess.source_map().span_to_snippet(expr.span).ok().map(|body| { + errors::WhereClauseBeforeConstBodySugg { + left: before_where_clause.span.shrink_to_lo(), + snippet: body, + right: before_where_clause.span.shrink_to_hi().to(expr.span), + } + }) + } else { + // FIXME(generic_const_items): Provide a structured suggestion to merge the first + // where-clause into the second one. + None + }, + }); + } + + // Merge the predicates of both where-clauses since either one can be relevant. + // If we didn't parse a body (which is valid for associated consts in traits) and we were + // allowed to recover, `before_where_clause` contains the predicates, otherwise they are + // in `after_where_clause`. Further, both of them might contain predicates iff two + // where-clauses were provided which is syntactically ill-formed but we want to recover from + // it and treat them as one large where-clause. + let mut predicates = before_where_clause.predicates; + predicates.extend(after_where_clause.predicates); + let where_clause = WhereClause { + has_where_token: before_where_clause.has_where_token + || after_where_clause.has_where_token, + predicates, + span: if after_where_clause.has_where_token { + after_where_clause.span + } else { + before_where_clause.span + }, + }; + + if where_clause.has_where_token { + self.sess.gated_spans.gate(sym::generic_const_items, where_clause.span); + } + + generics.where_clause = where_clause; + self.expect_semi()?; - Ok((id, ty, expr)) + + Ok((ident, generics, ty, expr)) } /// We were supposed to parse `":" $ty` but the `:` or the type was missing. /// This means that the type is missing. - fn recover_missing_const_type(&mut self, colon_present: bool, m: Option) -> P { + fn recover_missing_global_item_type( + &mut self, + colon_present: bool, + m: Option, + ) -> P { // Construct the error and stash it away with the hope // that typeck will later enrich the error with a type. let kind = match m { @@ -1342,6 +1450,17 @@ impl<'a> Parser<'a> { } let ident = this.parse_field_ident("enum", vlo)?; + if this.token == token::Not { + if let Err(mut err) = this.unexpected::<()>() { + err.note(fluent::parse_macro_expands_to_enum_variant).emit(); + } + + this.bump(); + this.parse_delim_args()?; + + return Ok((None, TrailingToken::MaybeComma)); + } + let struct_def = if this.check(&token::OpenDelim(Delimiter::Brace)) { // Parse a struct variant. let (fields, recovered) = @@ -1369,7 +1488,7 @@ impl<'a> Parser<'a> { Ok((Some(vr), TrailingToken::MaybeComma)) }, - ).map_err(|mut err|{ + ).map_err(|mut err| { err.help("enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }`"); err }) @@ -1579,7 +1698,8 @@ impl<'a> Parser<'a> { self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| { let lo = this.token.span; let vis = this.parse_visibility(FollowedByType::No)?; - Ok((this.parse_single_struct_field(adt_ty, lo, vis, attrs)?, TrailingToken::None)) + this.parse_single_struct_field(adt_ty, lo, vis, attrs) + .map(|field| (field, TrailingToken::None)) }) } @@ -1713,8 +1833,8 @@ impl<'a> Parser<'a> { "field names and their types are separated with `:`", ":", Applicability::MachineApplicable, - ); - err.emit(); + ) + .emit(); } else { return Err(err); } @@ -1731,6 +1851,23 @@ impl<'a> Parser<'a> { attrs: AttrVec, ) -> PResult<'a, FieldDef> { let name = self.parse_field_ident(adt_ty, lo)?; + // Parse the macro invocation and recover + if self.token.kind == token::Not { + if let Err(mut err) = self.unexpected::() { + err.subdiagnostic(MacroExpandsToAdtField { adt_ty }).emit(); + self.bump(); + self.parse_delim_args()?; + return Ok(FieldDef { + span: DUMMY_SP, + ident: None, + vis, + id: DUMMY_NODE_ID, + ty: self.mk_ty(DUMMY_SP, TyKind::Err), + attrs, + is_placeholder: false, + }); + } + } self.expect_field_ty_separator()?; let ty = self.parse_ty()?; if self.token.kind == token::Colon && self.look_ahead(1, |tok| tok.kind != token::Colon) { @@ -1860,7 +1997,7 @@ impl<'a> Parser<'a> { let arrow = TokenTree::token_alone(token::FatArrow, pspan.between(bspan)); // `=>` let tokens = TokenStream::new(vec![params, arrow, body]); let dspan = DelimSpan::from_pair(pspan.shrink_to_lo(), bspan.shrink_to_hi()); - P(DelimArgs { dspan, delim: MacDelimiter::Brace, tokens }) + P(DelimArgs { dspan, delim: Delimiter::Brace, tokens }) } else { return self.unexpected(); }; @@ -2182,7 +2319,11 @@ impl<'a> Parser<'a> { // `extern ABI fn` || self.check_keyword_case(kw::Extern, case) && self.look_ahead(1, |t| t.can_begin_literal_maybe_minus()) - && self.look_ahead(2, |t| t.is_keyword_case(kw::Fn, case)) + && (self.look_ahead(2, |t| t.is_keyword_case(kw::Fn, case)) || + // this branch is only for better diagnostic in later, `pub` is not allowed here + (self.may_recover() + && self.look_ahead(2, |t| t.is_keyword(kw::Pub)) + && self.look_ahead(3, |t| t.is_keyword_case(kw::Fn, case)))) } /// Parses all the "front matter" (or "qualifiers") for a `fn` declaration, diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 0c265d7af0e6e..77c59bb38814f 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -24,12 +24,11 @@ use rustc_ast::tokenstream::{TokenStream, TokenTree, TokenTreeCursor}; use rustc_ast::util::case::Case; use rustc_ast::AttrId; use rustc_ast::DUMMY_NODE_ID; -use rustc_ast::{self as ast, AnonConst, AttrStyle, Const, DelimArgs, Extern}; -use rustc_ast::{Async, AttrArgs, AttrArgsEq, Expr, ExprKind, MacDelimiter, Mutability, StrLit}; +use rustc_ast::{self as ast, AnonConst, Const, DelimArgs, Extern}; +use rustc_ast::{Async, AttrArgs, AttrArgsEq, Expr, ExprKind, Mutability, StrLit}; use rustc_ast::{HasAttrs, HasTokens, Unsafe, Visibility, VisibilityKind}; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashMap; -use rustc_data_structures::sync::Ordering; use rustc_errors::PResult; use rustc_errors::{ Applicability, DiagnosticBuilder, ErrorGuaranteed, FatalError, IntoDiagnostic, MultiSpan, @@ -38,7 +37,7 @@ use rustc_session::parse::ParseSess; use rustc_span::source_map::{Span, DUMMY_SP}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use std::ops::Range; -use std::{cmp, mem, slice}; +use std::{mem, slice}; use thin_vec::ThinVec; use tracing::debug; @@ -135,10 +134,24 @@ pub struct Parser<'a> { pub capture_cfg: bool, restrictions: Restrictions, expected_tokens: Vec, - // Important: This must only be advanced from `bump` to ensure that - // `token_cursor.num_next_calls` is updated properly. token_cursor: TokenCursor, - desugar_doc_comments: bool, + // The number of calls to `bump`, i.e. the position in the token stream. + num_bump_calls: usize, + // During parsing we may sometimes need to 'unglue' a glued token into two + // component tokens (e.g. '>>' into '>' and '>), so the parser can consume + // them one at a time. This process bypasses the normal capturing mechanism + // (e.g. `num_bump_calls` will not be incremented), since the 'unglued' + // tokens due not exist in the original `TokenStream`. + // + // If we end up consuming both unglued tokens, this is not an issue. We'll + // end up capturing the single 'glued' token. + // + // However, sometimes we may want to capture just the first 'unglued' + // token. For example, capturing the `Vec` in `Option>` + // requires us to unglue the trailing `>>` token. The `break_last_token` + // field is used to track this token. It gets appended to the captured + // stream when we evaluate a `LazyAttrTokenStream`. + break_last_token: bool, /// This field is used to keep track of how many left angle brackets we have seen. This is /// required in order to detect extra leading left angle brackets (`<` characters) and error /// appropriately. @@ -162,7 +175,7 @@ pub struct Parser<'a> { // This type is used a lot, e.g. it's cloned when matching many declarative macro rules with nonterminals. Make sure // it doesn't unintentionally get bigger. #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(Parser<'_>, 272); +rustc_data_structures::static_assert_size!(Parser<'_>, 264); /// Stores span information about a closure. #[derive(Clone)] @@ -224,64 +237,29 @@ struct TokenCursor { // tokens are in `stack[n-1]`. `stack[0]` (when present) has no delimiters // because it's the outermost token stream which never has delimiters. stack: Vec<(TokenTreeCursor, Delimiter, DelimSpan)>, - - desugar_doc_comments: bool, - - // Counts the number of calls to `{,inlined_}next`. - num_next_calls: usize, - - // During parsing, we may sometimes need to 'unglue' a - // glued token into two component tokens - // (e.g. '>>' into '>' and '>), so that the parser - // can consume them one at a time. This process - // bypasses the normal capturing mechanism - // (e.g. `num_next_calls` will not be incremented), - // since the 'unglued' tokens due not exist in - // the original `TokenStream`. - // - // If we end up consuming both unglued tokens, - // then this is not an issue - we'll end up - // capturing the single 'glued' token. - // - // However, in certain circumstances, we may - // want to capture just the first 'unglued' token. - // For example, capturing the `Vec` - // in `Option>` requires us to unglue - // the trailing `>>` token. The `break_last_token` - // field is used to track this token - it gets - // appended to the captured stream when - // we evaluate a `LazyAttrTokenStream`. - break_last_token: bool, } impl TokenCursor { - fn next(&mut self, desugar_doc_comments: bool) -> (Token, Spacing) { - self.inlined_next(desugar_doc_comments) + fn next(&mut self) -> (Token, Spacing) { + self.inlined_next() } /// This always-inlined version should only be used on hot code paths. #[inline(always)] - fn inlined_next(&mut self, desugar_doc_comments: bool) -> (Token, Spacing) { + fn inlined_next(&mut self) -> (Token, Spacing) { loop { - // FIXME: we currently don't return `Delimiter` open/close delims. To fix #67062 we will - // need to, whereupon the `delim != Delimiter::Invisible` conditions below can be - // removed. + // FIXME: we currently don't return `Delimiter::Invisible` open/close delims. To fix + // #67062 we will need to, whereupon the `delim != Delimiter::Invisible` conditions + // below can be removed. if let Some(tree) = self.tree_cursor.next_ref() { match tree { - &TokenTree::Token(ref token, spacing) => match (desugar_doc_comments, token) { - (true, &Token { kind: token::DocComment(_, attr_style, data), span }) => { - let desugared = self.desugar(attr_style, data, span); - self.tree_cursor.replace_prev_and_rewind(desugared); - // Continue to get the first token of the desugared doc comment. - } - _ => { - debug_assert!(!matches!( - token.kind, - token::OpenDelim(_) | token::CloseDelim(_) - )); - return (token.clone(), spacing); - } - }, + &TokenTree::Token(ref token, spacing) => { + debug_assert!(!matches!( + token.kind, + token::OpenDelim(_) | token::CloseDelim(_) + )); + return (token.clone(), spacing); + } &TokenTree::Delimited(sp, delim, ref tts) => { let trees = tts.clone().into_trees(); self.stack.push((mem::replace(&mut self.tree_cursor, trees), delim, sp)); @@ -304,52 +282,6 @@ impl TokenCursor { } } } - - // Desugar a doc comment into something like `#[doc = r"foo"]`. - fn desugar(&mut self, attr_style: AttrStyle, data: Symbol, span: Span) -> Vec { - // Searches for the occurrences of `"#*` and returns the minimum number of `#`s - // required to wrap the text. E.g. - // - `abc d` is wrapped as `r"abc d"` (num_of_hashes = 0) - // - `abc "d"` is wrapped as `r#"abc "d""#` (num_of_hashes = 1) - // - `abc "##d##"` is wrapped as `r###"abc ##"d"##"###` (num_of_hashes = 3) - let mut num_of_hashes = 0; - let mut count = 0; - for ch in data.as_str().chars() { - count = match ch { - '"' => 1, - '#' if count > 0 => count + 1, - _ => 0, - }; - num_of_hashes = cmp::max(num_of_hashes, count); - } - - // `/// foo` becomes `doc = r"foo"`. - let delim_span = DelimSpan::from_single(span); - let body = TokenTree::Delimited( - delim_span, - Delimiter::Bracket, - [ - TokenTree::token_alone(token::Ident(sym::doc, false), span), - TokenTree::token_alone(token::Eq, span), - TokenTree::token_alone( - TokenKind::lit(token::StrRaw(num_of_hashes), data, None), - span, - ), - ] - .into_iter() - .collect::(), - ); - - if attr_style == AttrStyle::Inner { - vec![ - TokenTree::token_alone(token::Pound, span), - TokenTree::token_alone(token::Not, span), - body, - ] - } else { - vec![TokenTree::token_alone(token::Pound, span), body] - } - } } #[derive(Debug, Clone, PartialEq)] @@ -368,7 +300,7 @@ impl TokenType { fn to_string(&self) -> String { match self { TokenType::Token(t) => format!("`{}`", pprust::token_kind_to_string(t)), - TokenType::Keyword(kw) => format!("`{}`", kw), + TokenType::Keyword(kw) => format!("`{kw}`"), TokenType::Operator => "an operator".to_string(), TokenType::Lifetime => "lifetime".to_string(), TokenType::Ident => "identifier".to_string(), @@ -438,14 +370,13 @@ pub(super) fn token_descr(token: &Token) -> String { TokenDescription::DocComment => "doc comment", }); - if let Some(kind) = kind { format!("{} `{}`", kind, name) } else { format!("`{}`", name) } + if let Some(kind) = kind { format!("{kind} `{name}`") } else { format!("`{name}`") } } impl<'a> Parser<'a> { pub fn new( sess: &'a ParseSess, - tokens: TokenStream, - desugar_doc_comments: bool, + stream: TokenStream, subparser_name: Option<&'static str>, ) -> Self { let mut parser = Parser { @@ -456,14 +387,9 @@ impl<'a> Parser<'a> { capture_cfg: false, restrictions: Restrictions::empty(), expected_tokens: Vec::new(), - token_cursor: TokenCursor { - tree_cursor: tokens.into_trees(), - stack: Vec::new(), - num_next_calls: 0, - desugar_doc_comments, - break_last_token: false, - }, - desugar_doc_comments, + token_cursor: TokenCursor { tree_cursor: stream.into_trees(), stack: Vec::new() }, + num_bump_calls: 0, + break_last_token: false, unmatched_angle_bracket_count: 0, max_angle_bracket_count: 0, last_unexpected_token_span: None, @@ -536,7 +462,9 @@ impl<'a> Parser<'a> { } else if inedible.contains(&self.token.kind) { // leave it in the input Ok(false) - } else if self.last_unexpected_token_span == Some(self.token.span) { + } else if self.token.kind != token::Eof + && self.last_unexpected_token_span == Some(self.token.span) + { FatalError.raise(); } else { self.expected_one_of_not_found(edible, inedible) @@ -764,7 +692,7 @@ impl<'a> Parser<'a> { // If we consume any additional tokens, then this token // is not needed (we'll capture the entire 'glued' token), // and `bump` will set this field to `None` - self.token_cursor.break_last_token = true; + self.break_last_token = true; // Use the spacing of the glued token as the spacing // of the unglued second token. self.bump_with((Token::new(second, second_span), self.token_spacing)); @@ -921,7 +849,7 @@ impl<'a> Parser<'a> { expect_err .span_suggestion_short( sp, - format!("missing `{}`", token_str), + format!("missing `{token_str}`"), token_str, Applicability::MaybeIncorrect, ) @@ -1105,12 +1033,12 @@ impl<'a> Parser<'a> { pub fn bump(&mut self) { // Note: destructuring here would give nicer code, but it was found in #96210 to be slower // than `.0`/`.1` access. - let mut next = self.token_cursor.inlined_next(self.desugar_doc_comments); - self.token_cursor.num_next_calls += 1; + let mut next = self.token_cursor.inlined_next(); + self.num_bump_calls += 1; // We've retrieved an token from the underlying // cursor, so we no longer need to worry about // an unglued token. See `break_and_eat` for more details - self.token_cursor.break_last_token = false; + self.break_last_token = false; if next.0.span.is_dummy() { // Tweak the location for better diagnostics, but keep syntactic context intact. let fallback_span = self.token.span; @@ -1124,38 +1052,53 @@ impl<'a> Parser<'a> { } /// Look-ahead `dist` tokens of `self.token` and get access to that token there. - /// When `dist == 0` then the current token is looked at. + /// When `dist == 0` then the current token is looked at. `Eof` will be + /// returned if the look-ahead is any distance past the end of the tokens. pub fn look_ahead(&self, dist: usize, looker: impl FnOnce(&Token) -> R) -> R { if dist == 0 { return looker(&self.token); } - let tree_cursor = &self.token_cursor.tree_cursor; if let Some(&(_, delim, span)) = self.token_cursor.stack.last() && delim != Delimiter::Invisible { + // We are not in the outermost token stream, and the token stream + // we are in has non-skipped delimiters. Look for skipped + // delimiters in the lookahead range. + let tree_cursor = &self.token_cursor.tree_cursor; let all_normal = (0..dist).all(|i| { let token = tree_cursor.look_ahead(i); !matches!(token, Some(TokenTree::Delimited(_, Delimiter::Invisible, _))) }); if all_normal { + // There were no skipped delimiters. Do lookahead by plain indexing. return match tree_cursor.look_ahead(dist - 1) { - Some(tree) => match tree { - TokenTree::Token(token, _) => looker(token), - TokenTree::Delimited(dspan, delim, _) => { - looker(&Token::new(token::OpenDelim(*delim), dspan.open)) + Some(tree) => { + // Indexing stayed within the current token stream. + match tree { + TokenTree::Token(token, _) => looker(token), + TokenTree::Delimited(dspan, delim, _) => { + looker(&Token::new(token::OpenDelim(*delim), dspan.open)) + } } - }, - None => looker(&Token::new(token::CloseDelim(delim), span.close)), + } + None => { + // Indexing went past the end of the current token + // stream. Use the close delimiter, no matter how far + // ahead `dist` went. + looker(&Token::new(token::CloseDelim(delim), span.close)) + } }; } } + // We are in a more complex case. Just clone the token cursor and use + // `next`, skipping delimiters as necessary. Slow but simple. let mut cursor = self.token_cursor.clone(); let mut i = 0; let mut token = Token::dummy(); while i < dist { - token = cursor.next(/* desugar_doc_comments */ false).0; + token = cursor.next().0; if matches!( token.kind, token::OpenDelim(Delimiter::Invisible) | token::CloseDelim(Delimiter::Invisible) @@ -1164,7 +1107,7 @@ impl<'a> Parser<'a> { } i += 1; } - return looker(&token); + looker(&token) } /// Returns whether any of the given keywords are `dist` tokens ahead of the current one. @@ -1208,7 +1151,8 @@ impl<'a> Parser<'a> { fn parse_constness_(&mut self, case: Case, is_closure: bool) -> Const { // Avoid const blocks and const closures to be parsed as const items if (self.check_const_closure() == is_closure) - && self.look_ahead(1, |t| t != &token::OpenDelim(Delimiter::Brace)) + && !self + .look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block()) && self.eat_keyword_case(kw::Const, case) { Const::Yes(self.prev_token.uninterpolated_span()) @@ -1286,10 +1230,10 @@ impl<'a> Parser<'a> { || self.check(&token::OpenDelim(Delimiter::Brace)); delimited.then(|| { - // We've confirmed above that there is a delimiter so unwrapping is OK. - let TokenTree::Delimited(dspan, delim, tokens) = self.parse_token_tree() else { unreachable!() }; - - DelimArgs { dspan, delim: MacDelimiter::from_token(delim).unwrap(), tokens } + let TokenTree::Delimited(dspan, delim, tokens) = self.parse_token_tree() else { + unreachable!() + }; + DelimArgs { dspan, delim, tokens } }) } @@ -1305,12 +1249,11 @@ impl<'a> Parser<'a> { } /// Parses a single token tree from the input. - pub(crate) fn parse_token_tree(&mut self) -> TokenTree { + pub fn parse_token_tree(&mut self) -> TokenTree { match self.token.kind { token::OpenDelim(..) => { // Grab the tokens within the delimiters. - let tree_cursor = &self.token_cursor.tree_cursor; - let stream = tree_cursor.stream.clone(); + let stream = self.token_cursor.tree_cursor.stream.clone(); let (_, delim, span) = *self.token_cursor.stack.last().unwrap(); // Advance the token cursor through the entire delimited @@ -1341,15 +1284,6 @@ impl<'a> Parser<'a> { } } - /// Parses a stream of tokens into a list of `TokenTree`s, up to EOF. - pub fn parse_all_token_trees(&mut self) -> PResult<'a, Vec> { - let mut tts = Vec::new(); - while self.token != token::Eof { - tts.push(self.parse_token_tree()); - } - Ok(tts) - } - pub fn parse_tokens(&mut self) -> TokenStream { let mut result = Vec::new(); loop { @@ -1509,7 +1443,7 @@ impl<'a> Parser<'a> { } pub fn approx_token_stream_pos(&self) -> usize { - self.token_cursor.num_next_calls + self.num_bump_calls } } @@ -1535,18 +1469,6 @@ pub(crate) fn make_unclosed_delims_error( Some(err) } -pub fn emit_unclosed_delims(unclosed_delims: &mut Vec, sess: &ParseSess) { - let _ = sess.reached_eof.fetch_or( - unclosed_delims.iter().any(|unmatched_delim| unmatched_delim.found_delim.is_none()), - Ordering::Relaxed, - ); - for unmatched in unclosed_delims.drain(..) { - if let Some(mut e) = make_unclosed_delims_error(unmatched, sess) { - e.emit(); - } - } -} - /// A helper struct used when building an `AttrTokenStream` from /// a `LazyAttrTokenStream`. Both delimiter and non-delimited tokens /// are stored as `FlatToken::Token`. A vector of `FlatToken`s @@ -1569,7 +1491,7 @@ pub enum FlatToken { } #[derive(Debug)] -pub enum NtOrTt { +pub enum ParseNtResult { Nt(Nonterminal), Tt(TokenTree), } diff --git a/compiler/rustc_parse/src/parser/nonterminal.rs b/compiler/rustc_parse/src/parser/nonterminal.rs index adb0d372a40df..ff059a7e865a4 100644 --- a/compiler/rustc_parse/src/parser/nonterminal.rs +++ b/compiler/rustc_parse/src/parser/nonterminal.rs @@ -1,5 +1,5 @@ use rustc_ast::ptr::P; -use rustc_ast::token::{self, Delimiter, NonterminalKind, Token}; +use rustc_ast::token::{self, Delimiter, Nonterminal::*, NonterminalKind, Token}; use rustc_ast::HasTokens; use rustc_ast_pretty::pprust; use rustc_errors::IntoDiagnostic; @@ -8,7 +8,7 @@ use rustc_span::symbol::{kw, Ident}; use crate::errors::UnexpectedNonterminal; use crate::parser::pat::{CommaRecoveryMode, RecoverColon, RecoverComma}; -use crate::parser::{FollowedByType, ForceCollect, NtOrTt, Parser, PathStyle}; +use crate::parser::{FollowedByType, ForceCollect, ParseNtResult, Parser, PathStyle}; impl<'a> Parser<'a> { /// Checks whether a non-terminal may begin with a particular token. @@ -20,10 +20,21 @@ impl<'a> Parser<'a> { pub fn nonterminal_may_begin_with(kind: NonterminalKind, token: &Token) -> bool { /// Checks whether the non-terminal may contain a single (non-keyword) identifier. fn may_be_ident(nt: &token::Nonterminal) -> bool { - !matches!( - *nt, - token::NtItem(_) | token::NtBlock(_) | token::NtVis(_) | token::NtLifetime(_) - ) + match nt { + NtStmt(_) + | NtPat(_) + | NtExpr(_) + | NtTy(_) + | NtIdent(..) + | NtLiteral(_) // `true`, `false` + | NtMeta(_) + | NtPath(_) => true, + + NtItem(_) + | NtBlock(_) + | NtVis(_) + | NtLifetime(_) => false, + } } match kind { @@ -44,27 +55,19 @@ impl<'a> Parser<'a> { }, NonterminalKind::Block => match &token.kind { token::OpenDelim(Delimiter::Brace) => true, - token::Interpolated(nt) => !matches!( - **nt, - token::NtItem(_) - | token::NtPat(_) - | token::NtTy(_) - | token::NtIdent(..) - | token::NtMeta(_) - | token::NtPath(_) - | token::NtVis(_) - ), + token::Interpolated(nt) => match **nt { + NtBlock(_) | NtLifetime(_) | NtStmt(_) | NtExpr(_) | NtLiteral(_) => true, + NtItem(_) | NtPat(_) | NtTy(_) | NtIdent(..) | NtMeta(_) | NtPath(_) + | NtVis(_) => false, + }, _ => false, }, NonterminalKind::Path | NonterminalKind::Meta => match &token.kind { token::ModSep | token::Ident(..) => true, - token::Interpolated(nt) => match **nt { - token::NtPath(_) | token::NtMeta(_) => true, - _ => may_be_ident(&nt), - }, + token::Interpolated(nt) => may_be_ident(nt), _ => false, }, - NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr { .. } => { + NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr => { match &token.kind { token::Ident(..) | // box, ref, mut, and other identifiers (can stricten) token::OpenDelim(Delimiter::Parenthesis) | // tuple pattern @@ -79,7 +82,7 @@ impl<'a> Parser<'a> { token::Lt | // path (UFCS constant) token::BinOp(token::Shl) => true, // path (double UFCS) // leading vert `|` or-pattern - token::BinOp(token::Or) => matches!(kind, NonterminalKind::PatWithOr {..}), + token::BinOp(token::Or) => matches!(kind, NonterminalKind::PatWithOr), token::Interpolated(nt) => may_be_ident(nt), _ => false, } @@ -87,7 +90,7 @@ impl<'a> Parser<'a> { NonterminalKind::Lifetime => match &token.kind { token::Lifetime(_) => true, token::Interpolated(nt) => { - matches!(**nt, token::NtLifetime(_)) + matches!(**nt, NtLifetime(_)) } _ => false, }, @@ -100,18 +103,16 @@ impl<'a> Parser<'a> { /// Parse a non-terminal (e.g. MBE `:pat` or `:ident`). Inlined because there is only one call /// site. #[inline] - pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, NtOrTt> { - // Any `Nonterminal` which stores its tokens (currently `NtItem` and `NtExpr`) - // needs to have them force-captured here. + pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, ParseNtResult> { // A `macro_rules!` invocation may pass a captured item/expr to a proc-macro, // which requires having captured tokens available. Since we cannot determine // in advance whether or not a proc-macro will be (transitively) invoked, // we always capture tokens for any `Nonterminal` which needs them. let mut nt = match kind { // Note that TT is treated differently to all the others. - NonterminalKind::TT => return Ok(NtOrTt::Tt(self.parse_token_tree())), + NonterminalKind::TT => return Ok(ParseNtResult::Tt(self.parse_token_tree())), NonterminalKind::Item => match self.parse_item(ForceCollect::Yes)? { - Some(item) => token::NtItem(item), + Some(item) => NtItem(item), None => { return Err(UnexpectedNonterminal::Item(self.token.span) .into_diagnostic(&self.sess.span_diagnostic)); @@ -120,19 +121,19 @@ impl<'a> Parser<'a> { NonterminalKind::Block => { // While a block *expression* may have attributes (e.g. `#[my_attr] { ... }`), // the ':block' matcher does not support them - token::NtBlock(self.collect_tokens_no_attrs(|this| this.parse_block())?) + NtBlock(self.collect_tokens_no_attrs(|this| this.parse_block())?) } NonterminalKind::Stmt => match self.parse_stmt(ForceCollect::Yes)? { - Some(s) => token::NtStmt(P(s)), + Some(s) => NtStmt(P(s)), None => { return Err(UnexpectedNonterminal::Statement(self.token.span) .into_diagnostic(&self.sess.span_diagnostic)); } }, - NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr { .. } => { - token::NtPat(self.collect_tokens_no_attrs(|this| match kind { - NonterminalKind::PatParam { .. } => this.parse_pat_no_top_alt(None), - NonterminalKind::PatWithOr { .. } => this.parse_pat_allow_top_alt( + NonterminalKind::PatParam { .. } | NonterminalKind::PatWithOr => { + NtPat(self.collect_tokens_no_attrs(|this| match kind { + NonterminalKind::PatParam { .. } => this.parse_pat_no_top_alt(None, None), + NonterminalKind::PatWithOr => this.parse_pat_allow_top_alt( None, RecoverComma::No, RecoverColon::No, @@ -142,16 +143,16 @@ impl<'a> Parser<'a> { })?) } - NonterminalKind::Expr => token::NtExpr(self.parse_expr_force_collect()?), + NonterminalKind::Expr => NtExpr(self.parse_expr_force_collect()?), NonterminalKind::Literal => { // The `:literal` matcher does not support attributes - token::NtLiteral( + NtLiteral( self.collect_tokens_no_attrs(|this| this.parse_literal_maybe_minus())?, ) } - NonterminalKind::Ty => token::NtTy( - self.collect_tokens_no_attrs(|this| this.parse_no_question_mark_recover())?, + NonterminalKind::Ty => NtTy( + self.collect_tokens_no_attrs(|this| this.parse_ty_no_question_mark_recover())?, ), // this could be handled like a token, since it is one @@ -159,7 +160,7 @@ impl<'a> Parser<'a> { if let Some((ident, is_raw)) = get_macro_ident(&self.token) => { self.bump(); - token::NtIdent(ident, is_raw) + NtIdent(ident, is_raw) } NonterminalKind::Ident => { return Err(UnexpectedNonterminal::Ident { @@ -167,16 +168,16 @@ impl<'a> Parser<'a> { token: self.token.clone(), }.into_diagnostic(&self.sess.span_diagnostic)); } - NonterminalKind::Path => token::NtPath( + NonterminalKind::Path => NtPath( P(self.collect_tokens_no_attrs(|this| this.parse_path(PathStyle::Type))?), ), - NonterminalKind::Meta => token::NtMeta(P(self.parse_attr_item(true)?)), - NonterminalKind::Vis => token::NtVis( + NonterminalKind::Meta => NtMeta(P(self.parse_attr_item(true)?)), + NonterminalKind::Vis => NtVis( P(self.collect_tokens_no_attrs(|this| this.parse_visibility(FollowedByType::Yes))?), ), NonterminalKind::Lifetime => { if self.check_lifetime() { - token::NtLifetime(self.expect_lifetime().ident) + NtLifetime(self.expect_lifetime().ident) } else { return Err(UnexpectedNonterminal::Lifetime { span: self.token.span, @@ -196,7 +197,7 @@ impl<'a> Parser<'a> { ); } - Ok(NtOrTt::Nt(nt)) + Ok(ParseNtResult::Nt(nt)) } } diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index c317d96368ee6..3e4e927891072 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -2,13 +2,13 @@ use super::{ForceCollect, Parser, PathStyle, TrailingToken}; use crate::errors::{ self, AmbiguousRangePattern, DotDotDotForRemainingFields, DotDotDotRangeToPatternNotAllowed, DotDotDotRestPattern, EnumPatternInsteadOfIdentifier, ExpectedBindingLeftOfAt, - ExpectedCommaAfterPatternField, InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, - InclusiveRangeNoEnd, InvalidMutInPattern, PatternOnWrongSideOfAt, RefMutOrderIncorrect, - RemoveLet, RepeatedMutInPattern, TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg, - TrailingVertNotAllowed, UnexpectedLifetimeInPattern, UnexpectedVertVertBeforeFunctionParam, + ExpectedCommaAfterPatternField, GenericArgsInPatRequireTurbofishSyntax, + InclusiveRangeExtraEquals, InclusiveRangeMatchArrow, InclusiveRangeNoEnd, InvalidMutInPattern, + PatternOnWrongSideOfAt, RefMutOrderIncorrect, RemoveLet, RepeatedMutInPattern, + TopLevelOrPatternNotAllowed, TopLevelOrPatternNotAllowedSugg, TrailingVertNotAllowed, + UnexpectedLifetimeInPattern, UnexpectedVertVertBeforeFunctionParam, UnexpectedVertVertInPattern, }; -use crate::fluent_generated as fluent; use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole}; use rustc_ast::mut_visit::{noop_visit_pat, MutVisitor}; use rustc_ast::ptr::P; @@ -81,7 +81,8 @@ enum EatOrResult { } /// The syntax location of a given pattern. Used for diagnostics. -pub(super) enum PatternLocation { +#[derive(Clone, Copy)] +pub enum PatternLocation { LetBinding, FunctionParameter, } @@ -92,8 +93,12 @@ impl<'a> Parser<'a> { /// Corresponds to `pat` in RFC 2535 and does not admit or-patterns /// at the top level. Used when parsing the parameters of lambda expressions, /// functions, function pointers, and `pat` macro fragments. - pub fn parse_pat_no_top_alt(&mut self, expected: Option) -> PResult<'a, P> { - self.parse_pat_with_range_pat(true, expected) + pub fn parse_pat_no_top_alt( + &mut self, + expected: Option, + syntax_loc: Option, + ) -> PResult<'a, P> { + self.parse_pat_with_range_pat(true, expected, syntax_loc) } /// Parses a pattern. @@ -111,7 +116,7 @@ impl<'a> Parser<'a> { ra: RecoverColon, rt: CommaRecoveryMode, ) -> PResult<'a, P> { - self.parse_pat_allow_top_alt_inner(expected, rc, ra, rt).map(|(pat, _)| pat) + self.parse_pat_allow_top_alt_inner(expected, rc, ra, rt, None).map(|(pat, _)| pat) } /// Returns the pattern and a bool indicating whether we recovered from a trailing vert (true = @@ -122,6 +127,7 @@ impl<'a> Parser<'a> { rc: RecoverComma, ra: RecoverColon, rt: CommaRecoveryMode, + syntax_loc: Option, ) -> PResult<'a, (P, bool)> { // Keep track of whether we recovered from a trailing vert so that we can avoid duplicated // suggestions (which bothers rustfix). @@ -134,9 +140,13 @@ impl<'a> Parser<'a> { }; // Parse the first pattern (`p_0`). - let mut first_pat = self.parse_pat_no_top_alt(expected)?; + let mut first_pat = self.parse_pat_no_top_alt(expected, syntax_loc)?; if rc == RecoverComma::Yes { - self.maybe_recover_unexpected_comma(first_pat.span, rt)?; + self.maybe_recover_unexpected_comma( + first_pat.span, + matches!(first_pat.kind, PatKind::MacCall(_)), + rt, + )?; } // If the next token is not a `|`, @@ -173,12 +183,12 @@ impl<'a> Parser<'a> { break; } } - let pat = self.parse_pat_no_top_alt(expected).map_err(|mut err| { + let pat = self.parse_pat_no_top_alt(expected, syntax_loc).map_err(|mut err| { err.span_label(lo, WHILE_PARSING_OR_MSG); err })?; if rc == RecoverComma::Yes { - self.maybe_recover_unexpected_comma(pat.span, rt)?; + self.maybe_recover_unexpected_comma(pat.span, false, rt)?; } pats.push(pat); } @@ -209,46 +219,31 @@ impl<'a> Parser<'a> { rc, RecoverColon::No, CommaRecoveryMode::LikelyTuple, + Some(syntax_loc), )?; let colon = self.eat(&token::Colon); if let PatKind::Or(pats) = &pat.kind { let span = pat.span; - - if trailing_vert { - // We already emitted an error and suggestion to remove the trailing vert. Don't - // emit again. - - // FIXME(#100717): pass `TopLevelOrPatternNotAllowed::* { sub: None }` to - // `delay_span_bug()` instead of fluent message - self.sess.span_diagnostic.delay_span_bug( - span, - match syntax_loc { - PatternLocation::LetBinding => { - fluent::parse_or_pattern_not_allowed_in_let_binding - } - PatternLocation::FunctionParameter => { - fluent::parse_or_pattern_not_allowed_in_fn_parameters - } - }, - ); + let pat = pprust::pat_to_string(&pat); + let sub = if pats.len() == 1 { + Some(TopLevelOrPatternNotAllowedSugg::RemoveLeadingVert { span, pat }) } else { - let pat = pprust::pat_to_string(&pat); - let sub = if pats.len() == 1 { - Some(TopLevelOrPatternNotAllowedSugg::RemoveLeadingVert { span, pat }) - } else { - Some(TopLevelOrPatternNotAllowedSugg::WrapInParens { span, pat }) - }; + Some(TopLevelOrPatternNotAllowedSugg::WrapInParens { span, pat }) + }; - self.sess.emit_err(match syntax_loc { - PatternLocation::LetBinding => { - TopLevelOrPatternNotAllowed::LetBinding { span, sub } - } - PatternLocation::FunctionParameter => { - TopLevelOrPatternNotAllowed::FunctionParameter { span, sub } - } - }); + let mut err = self.sess.create_err(match syntax_loc { + PatternLocation::LetBinding => { + TopLevelOrPatternNotAllowed::LetBinding { span, sub } + } + PatternLocation::FunctionParameter => { + TopLevelOrPatternNotAllowed::FunctionParameter { span, sub } + } + }); + if trailing_vert { + err.delay_as_bug(); } + err.emit(); } Ok((pat, colon)) @@ -336,6 +331,7 @@ impl<'a> Parser<'a> { &mut self, allow_range_pat: bool, expected: Option, + syntax_loc: Option, ) -> PResult<'a, P> { maybe_recover_from_interpolated_ty_qpath!(self, true); maybe_whole!(self, NtPat, |x| x); @@ -375,11 +371,11 @@ impl<'a> Parser<'a> { // Parse _ PatKind::Wild } else if self.eat_keyword(kw::Mut) { - self.parse_pat_ident_mut()? + self.parse_pat_ident_mut(syntax_loc)? } else if self.eat_keyword(kw::Ref) { // Parse ref ident @ pat / ref mut ident @ pat let mutbl = self.parse_mutability(); - self.parse_pat_ident(BindingAnnotation(ByRef::Yes, mutbl))? + self.parse_pat_ident(BindingAnnotation(ByRef::Yes, mutbl), syntax_loc)? } else if self.eat_keyword(kw::Box) { self.parse_pat_box()? } else if self.check_inline_const(0) { @@ -401,7 +397,7 @@ impl<'a> Parser<'a> { // Parse `ident @ pat` // This can give false positives and parse nullary enums, // they are dealt with later in resolve. - self.parse_pat_ident(BindingAnnotation::NONE)? + self.parse_pat_ident(BindingAnnotation::NONE, syntax_loc)? } else if self.is_start_of_pat_with_path() { // Parse pattern starting with a path let (qself, path) = if self.eat_lt() { @@ -445,7 +441,7 @@ impl<'a> Parser<'a> { ); let mut err = self_.struct_span_err(self_.token.span, msg); - err.span_label(self_.token.span, format!("expected {}", expected)); + err.span_label(self_.token.span, format!("expected {expected}")); err }); PatKind::Lit(self.mk_expr(lo, ExprKind::Lit(lit))) @@ -502,7 +498,7 @@ impl<'a> Parser<'a> { // At this point we attempt to parse `@ $pat_rhs` and emit an error. self.bump(); // `@` - let mut rhs = self.parse_pat_no_top_alt(None)?; + let mut rhs = self.parse_pat_no_top_alt(None, None)?; let whole_span = lhs.span.to(rhs.span); if let PatKind::Ident(_, _, sub @ None) = &mut rhs.kind { @@ -558,7 +554,7 @@ impl<'a> Parser<'a> { } let mutbl = self.parse_mutability(); - let subpat = self.parse_pat_with_range_pat(false, expected)?; + let subpat = self.parse_pat_with_range_pat(false, expected, None)?; Ok(PatKind::Ref(subpat, mutbl)) } @@ -583,12 +579,12 @@ impl<'a> Parser<'a> { } /// Parse a mutable binding with the `mut` token already eaten. - fn parse_pat_ident_mut(&mut self) -> PResult<'a, PatKind> { + fn parse_pat_ident_mut(&mut self, syntax_loc: Option) -> PResult<'a, PatKind> { let mut_span = self.prev_token.span; if self.eat_keyword(kw::Ref) { self.sess.emit_err(RefMutOrderIncorrect { span: mut_span.to(self.prev_token.span) }); - return self.parse_pat_ident(BindingAnnotation::REF_MUT); + return self.parse_pat_ident(BindingAnnotation::REF_MUT, syntax_loc); } self.recover_additional_muts(); @@ -601,7 +597,7 @@ impl<'a> Parser<'a> { } // Parse the pattern we hope to be an identifier. - let mut pat = self.parse_pat_no_top_alt(Some(Expected::Identifier))?; + let mut pat = self.parse_pat_no_top_alt(Some(Expected::Identifier), None)?; // If we don't have `mut $ident (@ pat)?`, error. if let PatKind::Ident(BindingAnnotation(ByRef::No, m @ Mutability::Not), ..) = &mut pat.kind @@ -681,7 +677,7 @@ impl<'a> Parser<'a> { let msg = format!("expected {}, found {}", expected, super::token_descr(&self.token)); let mut err = self.struct_span_err(self.token.span, msg); - err.span_label(self.token.span, format!("expected {}", expected)); + err.span_label(self.token.span, format!("expected {expected}")); let sp = self.sess.source_map().start_point(self.token.span); if let Some(sp) = self.sess.ambiguous_block_expr_parse.borrow().get(&sp) { @@ -827,10 +823,26 @@ impl<'a> Parser<'a> { /// Parses `ident` or `ident @ pat`. /// Used by the copy foo and ref foo patterns to give a good /// error message when parsing mistakes like `ref foo(a, b)`. - fn parse_pat_ident(&mut self, binding_annotation: BindingAnnotation) -> PResult<'a, PatKind> { + fn parse_pat_ident( + &mut self, + binding_annotation: BindingAnnotation, + syntax_loc: Option, + ) -> PResult<'a, PatKind> { let ident = self.parse_ident()?; + + if self.may_recover() + && !matches!(syntax_loc, Some(PatternLocation::FunctionParameter)) + && self.check_noexpect(&token::Lt) + && self.look_ahead(1, |t| t.can_begin_type()) + { + return Err(self.sess.create_err(GenericArgsInPatRequireTurbofishSyntax { + span: self.token.span, + suggest_turbofish: self.token.span.shrink_to_lo(), + })); + } + let sub = if self.eat(&token::At) { - Some(self.parse_pat_no_top_alt(Some(Expected::BindingPattern))?) + Some(self.parse_pat_no_top_alt(Some(Expected::BindingPattern), None)?) } else { None }; @@ -919,14 +931,14 @@ impl<'a> Parser<'a> { // We cannot use `parse_pat_ident()` since it will complain `box` // is not an identifier. let sub = if self.eat(&token::At) { - Some(self.parse_pat_no_top_alt(Some(Expected::BindingPattern))?) + Some(self.parse_pat_no_top_alt(Some(Expected::BindingPattern), None)?) } else { None }; Ok(PatKind::Ident(BindingAnnotation::NONE, Ident::new(kw::Box, box_span), sub)) } else { - let pat = self.parse_pat_with_range_pat(false, None)?; + let pat = self.parse_pat_with_range_pat(false, None, None)?; self.sess.gated_spans.gate(sym::box_patterns, box_span.to(self.prev_token.span)); Ok(PatKind::Box(pat)) } @@ -938,7 +950,8 @@ impl<'a> Parser<'a> { let mut etc = false; let mut ate_comma = true; let mut delayed_err: Option> = None; - let mut etc_span = None; + let mut first_etc_and_maybe_comma_span = None; + let mut last_non_comma_dotdot_span = None; while self.token != token::CloseDelim(Delimiter::Brace) { let attrs = match self.parse_outer_attributes() { @@ -969,16 +982,31 @@ impl<'a> Parser<'a> { { etc = true; let mut etc_sp = self.token.span; + if first_etc_and_maybe_comma_span.is_none() { + if let Some(comma_tok) = self + .look_ahead(1, |t| if *t == token::Comma { Some(t.clone()) } else { None }) + { + let nw_span = self + .sess + .source_map() + .span_extend_to_line(comma_tok.span) + .trim_start(comma_tok.span.shrink_to_lo()) + .map(|s| self.sess.source_map().span_until_non_whitespace(s)); + first_etc_and_maybe_comma_span = nw_span.map(|s| etc_sp.to(s)); + } else { + first_etc_and_maybe_comma_span = + Some(self.sess.source_map().span_until_non_whitespace(etc_sp)); + } + } self.recover_bad_dot_dot(); self.bump(); // `..` || `...` || `_` if self.token == token::CloseDelim(Delimiter::Brace) { - etc_span = Some(etc_sp); break; } let token_str = super::token_descr(&self.token); - let msg = format!("expected `}}`, found {}", token_str); + let msg = format!("expected `}}`, found {token_str}"); let mut err = self.struct_span_err(self.token.span, msg); err.span_label(self.token.span, "expected `}`"); @@ -996,7 +1024,6 @@ impl<'a> Parser<'a> { ate_comma = true; } - etc_span = Some(etc_sp.until(self.token.span)); if self.token == token::CloseDelim(Delimiter::Brace) { // If the struct looks otherwise well formed, recover and continue. if let Some(sp) = comma_sp { @@ -1040,6 +1067,9 @@ impl<'a> Parser<'a> { } }?; ate_comma = this.eat(&token::Comma); + + last_non_comma_dotdot_span = Some(this.prev_token.span); + // We just ate a comma, so there's no need to use // `TrailingToken::Comma` Ok((field, TrailingToken::None)) @@ -1049,15 +1079,30 @@ impl<'a> Parser<'a> { } if let Some(mut err) = delayed_err { - if let Some(etc_span) = etc_span { - err.multipart_suggestion( - "move the `..` to the end of the field list", - vec![ - (etc_span, String::new()), - (self.token.span, format!("{}.. }}", if ate_comma { "" } else { ", " })), - ], - Applicability::MachineApplicable, - ); + if let Some(first_etc_span) = first_etc_and_maybe_comma_span { + if self.prev_token == token::DotDot { + // We have `.., x, ..`. + err.multipart_suggestion( + "remove the starting `..`", + vec![(first_etc_span, String::new())], + Applicability::MachineApplicable, + ); + } else { + if let Some(last_non_comma_dotdot_span) = last_non_comma_dotdot_span { + // We have `.., x`. + err.multipart_suggestion( + "move the `..` to the end of the field list", + vec![ + (first_etc_span, String::new()), + ( + self.token.span.to(last_non_comma_dotdot_span.shrink_to_hi()), + format!("{} .. }}", if ate_comma { "" } else { "," }), + ), + ], + Applicability::MachineApplicable, + ); + } + } } err.emit(); } diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index feb7e829caf68..445516c03a15e 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -679,7 +679,7 @@ impl<'a> Parser<'a> { ); err.span_suggestion( eq.to(before_next), - format!("remove the `=` if `{}` is a type", ident), + format!("remove the `=` if `{ident}` is a type"), "", Applicability::MaybeIncorrect, ) diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 54f9fc5d2b999..12c267351b9aa 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -23,6 +23,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder, ErrorGuaranteed, PResult}; use rustc_span::source_map::{BytePos, Span}; use rustc_span::symbol::{kw, sym, Ident}; +use std::borrow::Cow; use std::mem; use thin_vec::{thin_vec, ThinVec}; @@ -192,10 +193,9 @@ impl<'a> Parser<'a> { /// At this point, the `!` token after the path has already been eaten. fn parse_stmt_mac(&mut self, lo: Span, attrs: AttrVec, path: ast::Path) -> PResult<'a, Stmt> { let args = self.parse_delim_args()?; - let delim = args.delim.to_token(); let hi = self.prev_token.span; - let style = match delim { + let style = match args.delim { Delimiter::Brace => MacStmtStyle::Braces, _ => MacStmtStyle::NoBraces, }; @@ -299,7 +299,7 @@ impl<'a> Parser<'a> { Ok(ty) => (None, Some(ty)), Err(mut err) => { if let Ok(snip) = self.span_to_snippet(pat.span) { - err.span_label(pat.span, format!("while parsing the type for `{}`", snip)); + err.span_label(pat.span, format!("while parsing the type for `{snip}`")); } // we use noexpect here because we don't actually expect Eq to be here // but we are still checking for it in order to be able to handle it if @@ -364,7 +364,7 @@ impl<'a> Parser<'a> { // `let...else if`. Emit the same error that `parse_block()` would, // but explicitly point out that this pattern is not allowed. let msg = "conditional `else if` is not supported for `let...else`"; - return Err(self.error_block_no_opening_brace_msg(msg)); + return Err(self.error_block_no_opening_brace_msg(Cow::from(msg))); } let els = self.parse_block()?; self.check_let_else_init_bool_expr(&init); @@ -438,7 +438,7 @@ impl<'a> Parser<'a> { fn error_block_no_opening_brace_msg( &mut self, - msg: &str, + msg: Cow<'static, str>, ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { let sp = self.token.span; let mut e = self.struct_span_err(sp, msg); @@ -501,8 +501,8 @@ impl<'a> Parser<'a> { fn error_block_no_opening_brace(&mut self) -> PResult<'a, T> { let tok = super::token_descr(&self.token); - let msg = format!("expected `{{`, found {}", tok); - Err(self.error_block_no_opening_brace_msg(&msg)) + let msg = format!("expected `{{`, found {tok}"); + Err(self.error_block_no_opening_brace_msg(Cow::from(msg))) } /// Parses a block. Inner attributes are allowed. @@ -637,10 +637,9 @@ impl<'a> Parser<'a> { e.span_suggestion( sp.with_hi(sp.lo() + BytePos(marker.len() as u32)), format!( - "add a space before `{}` to use a regular comment", - doc_comment_marker, + "add a space before `{doc_comment_marker}` to use a regular comment", ), - format!("{} {}", comment_marker, doc_comment_marker), + format!("{comment_marker} {doc_comment_marker}"), Applicability::MaybeIncorrect, ); } diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index a29b696aea83c..2d888efb1f384 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -180,7 +180,7 @@ impl<'a> Parser<'a> { ) } - pub(super) fn parse_no_question_mark_recover(&mut self) -> PResult<'a, P> { + pub(super) fn parse_ty_no_question_mark_recover(&mut self) -> PResult<'a, P> { self.parse_ty_common( AllowPlus::Yes, AllowCVariadic::No, @@ -608,7 +608,7 @@ impl<'a> Parser<'a> { /// Is a `dyn B0 + ... + Bn` type allowed here? fn is_explicit_dyn_type(&mut self) -> bool { self.check_keyword(kw::Dyn) - && (self.token.uninterpolated_span().rust_2018() + && (self.token.uninterpolated_span().at_least_rust_2018() || self.look_ahead(1, |t| { (t.can_begin_bound() || t.kind == TokenKind::BinOp(token::Star)) && !can_continue_type_after_non_fn_ident(t) @@ -714,6 +714,7 @@ impl<'a> Parser<'a> { /// ``` fn parse_generic_bound(&mut self) -> PResult<'a, GenericBound> { let lo = self.token.span; + let leading_token = self.prev_token.clone(); let has_parens = self.eat(&token::OpenDelim(Delimiter::Parenthesis)); let inner_lo = self.token.span; @@ -722,7 +723,7 @@ impl<'a> Parser<'a> { self.error_lt_bound_with_modifiers(modifiers); self.parse_generic_lt_bound(lo, inner_lo, has_parens)? } else { - self.parse_generic_ty_bound(lo, has_parens, modifiers)? + self.parse_generic_ty_bound(lo, has_parens, modifiers, &leading_token)? }; Ok(bound) @@ -827,6 +828,7 @@ impl<'a> Parser<'a> { lo: Span, has_parens: bool, modifiers: BoundModifiers, + leading_token: &Token, ) -> PResult<'a, GenericBound> { let mut lifetime_defs = self.parse_late_bound_lifetime_defs()?; let mut path = if self.token.is_keyword(kw::Fn) @@ -873,18 +875,18 @@ impl<'a> Parser<'a> { } if has_parens { - if self.token.is_like_plus() { - // Someone has written something like `&dyn (Trait + Other)`. The correct code - // would be `&(dyn Trait + Other)`, but we don't have access to the appropriate - // span to suggest that. When written as `&dyn Trait + Other`, an appropriate - // suggestion is given. + // Someone has written something like `&dyn (Trait + Other)`. The correct code + // would be `&(dyn Trait + Other)` + if self.token.is_like_plus() && leading_token.is_keyword(kw::Dyn) { let bounds = vec![]; self.parse_remaining_bounds(bounds, true)?; self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; - let sp = vec![lo, self.prev_token.span]; - self.sess.emit_err(errors::IncorrectBracesTraitBounds { - span: sp, - sugg: errors::IncorrectBracesTraitBoundsSugg { l: lo, r: self.prev_token.span }, + self.sess.emit_err(errors::IncorrectParensTraitBounds { + span: vec![lo, self.prev_token.span], + sugg: errors::IncorrectParensTraitBoundsSugg { + wrong_span: leading_token.span.shrink_to_hi().to(lo), + new_span: leading_token.span.shrink_to_lo(), + }, }); } else { self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs index 928fdce313d91..f739659822047 100644 --- a/compiler/rustc_parse/src/validate_attr.rs +++ b/compiler/rustc_parse/src/validate_attr.rs @@ -2,9 +2,10 @@ use crate::{errors, parse_in}; +use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::MetaItemKind; -use rustc_ast::{self as ast, AttrArgs, AttrArgsEq, Attribute, DelimArgs, MacDelimiter, MetaItem}; +use rustc_ast::{self as ast, AttrArgs, AttrArgsEq, Attribute, DelimArgs, MetaItem}; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, FatalError, PResult}; use rustc_feature::{AttributeTemplate, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; @@ -84,8 +85,8 @@ pub fn parse_meta<'a>(sess: &'a ParseSess, attr: &Attribute) -> PResult<'a, Meta }) } -pub fn check_meta_bad_delim(sess: &ParseSess, span: DelimSpan, delim: MacDelimiter) { - if let ast::MacDelimiter::Parenthesis = delim { +pub fn check_meta_bad_delim(sess: &ParseSess, span: DelimSpan, delim: Delimiter) { + if let Delimiter::Parenthesis = delim { return; } sess.emit_err(errors::MetaBadDelim { @@ -94,8 +95,8 @@ pub fn check_meta_bad_delim(sess: &ParseSess, span: DelimSpan, delim: MacDelimit }); } -pub fn check_cfg_attr_bad_delim(sess: &ParseSess, span: DelimSpan, delim: MacDelimiter) { - if let ast::MacDelimiter::Parenthesis = delim { +pub fn check_cfg_attr_bad_delim(sess: &ParseSess, span: DelimSpan, delim: Delimiter) { + if let Delimiter::Parenthesis = delim { return; } sess.emit_err(errors::CfgAttrBadDelim { @@ -157,15 +158,15 @@ fn emit_malformed_attribute( matches!(name, sym::doc | sym::ignore | sym::inline | sym::link | sym::test | sym::bench) }; - let error_msg = format!("malformed `{}` attribute input", name); + let error_msg = format!("malformed `{name}` attribute input"); let mut msg = "attribute must be of the form ".to_owned(); let mut suggestions = vec![]; let mut first = true; let inner = if style == ast::AttrStyle::Inner { "!" } else { "" }; if template.word { first = false; - let code = format!("#{}[{}]", inner, name); - msg.push_str(&format!("`{}`", &code)); + let code = format!("#{inner}[{name}]"); + msg.push_str(&format!("`{code}`")); suggestions.push(code); } if let Some(descr) = template.list { @@ -173,16 +174,16 @@ fn emit_malformed_attribute( msg.push_str(" or "); } first = false; - let code = format!("#{}[{}({})]", inner, name, descr); - msg.push_str(&format!("`{}`", &code)); + let code = format!("#{inner}[{name}({descr})]"); + msg.push_str(&format!("`{code}`")); suggestions.push(code); } if let Some(descr) = template.name_value_str { if !first { msg.push_str(" or "); } - let code = format!("#{}[{} = \"{}\"]", inner, name, descr); - msg.push_str(&format!("`{}`", &code)); + let code = format!("#{inner}[{name} = \"{descr}\"]"); + msg.push_str(&format!("`{code}`")); suggestions.push(code); } if should_warn(name) { diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs index 7de84db211ed8..88452ccdf052e 100644 --- a/compiler/rustc_parse_format/src/lib.rs +++ b/compiler/rustc_parse_format/src/lib.rs @@ -109,6 +109,8 @@ pub struct Argument<'a> { pub struct FormatSpec<'a> { /// Optionally specified character to fill alignment with. pub fill: Option, + /// Span of the optionally specified fill character. + pub fill_span: Option, /// Optionally specified alignment. pub align: Alignment, /// The `+` or `-` flag. @@ -264,7 +266,7 @@ impl<'a> Iterator for Parser<'a> { Some(String(self.string(pos + 1))) } else { let arg = self.argument(lbrace_end); - if let Some(rbrace_pos) = self.must_consume('}') { + if let Some(rbrace_pos) = self.consume_closing_brace(&arg) { if self.is_source_literal { let lbrace_byte_pos = self.to_span_index(pos); let rbrace_byte_pos = self.to_span_index(rbrace_pos); @@ -450,69 +452,51 @@ impl<'a> Parser<'a> { /// Forces consumption of the specified character. If the character is not /// found, an error is emitted. - fn must_consume(&mut self, c: char) -> Option { + fn consume_closing_brace(&mut self, arg: &Argument<'_>) -> Option { self.ws(); - if let Some(&(pos, maybe)) = self.cur.peek() { - if c == maybe { + let pos; + let description; + + if let Some(&(peek_pos, maybe)) = self.cur.peek() { + if maybe == '}' { self.cur.next(); - Some(pos) - } else { - let pos = self.to_span_index(pos); - let description = format!("expected `'}}'`, found `{maybe:?}`"); - let label = "expected `}`".to_owned(); - let (note, secondary_label) = if c == '}' { - ( - Some( - "if you intended to print `{`, you can escape it using `{{`".to_owned(), - ), - self.last_opening_brace - .map(|sp| ("because of this opening brace".to_owned(), sp)), - ) - } else { - (None, None) - }; - self.errors.push(ParseError { - description, - note, - label, - span: pos.to(pos), - secondary_label, - should_be_replaced_with_positional_argument: false, - }); - None + return Some(peek_pos); } + + pos = peek_pos; + description = format!("expected `'}}'`, found `{maybe:?}`"); } else { - let description = format!("expected `{c:?}` but string was terminated"); + description = "expected `'}'` but string was terminated".to_owned(); // point at closing `"` - let pos = self.input.len() - if self.append_newline { 1 } else { 0 }; - let pos = self.to_span_index(pos); - if c == '}' { - let label = format!("expected `{c:?}`"); - let (note, secondary_label) = if c == '}' { - ( - Some( - "if you intended to print `{`, you can escape it using `{{`".to_owned(), - ), - self.last_opening_brace - .map(|sp| ("because of this opening brace".to_owned(), sp)), - ) - } else { - (None, None) - }; - self.errors.push(ParseError { - description, - note, - label, - span: pos.to(pos), - secondary_label, - should_be_replaced_with_positional_argument: false, - }); - } else { - self.err(description, format!("expected `{c:?}`"), pos.to(pos)); - } - None + pos = self.input.len() - if self.append_newline { 1 } else { 0 }; } + + let pos = self.to_span_index(pos); + + let label = "expected `'}'`".to_owned(); + let (note, secondary_label) = if arg.format.fill == Some('}') { + ( + Some("the character `'}'` is interpreted as a fill character because of the `:` that precedes it".to_owned()), + arg.format.fill_span.map(|sp| ("this is not interpreted as a formatting closing brace".to_owned(), sp)), + ) + } else { + ( + Some("if you intended to print `{`, you can escape it using `{{`".to_owned()), + self.last_opening_brace.map(|sp| ("because of this opening brace".to_owned(), sp)), + ) + }; + + self.errors.push(ParseError { + description, + note, + label, + span: pos.to(pos), + secondary_label, + should_be_replaced_with_positional_argument: false, + }); + + None } /// Consumes all whitespace characters until the first non-whitespace character @@ -608,6 +592,7 @@ impl<'a> Parser<'a> { fn format(&mut self) -> FormatSpec<'a> { let mut spec = FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -625,9 +610,10 @@ impl<'a> Parser<'a> { } // fill character - if let Some(&(_, c)) = self.cur.peek() { + if let Some(&(idx, c)) = self.cur.peek() { if let Some((_, '>' | '<' | '^')) = self.cur.clone().nth(1) { spec.fill = Some(c); + spec.fill_span = Some(self.span(idx, idx + 1)); self.cur.next(); } } @@ -722,6 +708,7 @@ impl<'a> Parser<'a> { fn inline_asm(&mut self) -> FormatSpec<'a> { let mut spec = FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, diff --git a/compiler/rustc_parse_format/src/tests.rs b/compiler/rustc_parse_format/src/tests.rs index 45314e2fb5500..0c594f9104cc5 100644 --- a/compiler/rustc_parse_format/src/tests.rs +++ b/compiler/rustc_parse_format/src/tests.rs @@ -9,6 +9,7 @@ fn same(fmt: &'static str, p: &[Piece<'static>]) { fn fmtdflt() -> FormatSpec<'static> { return FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -128,6 +129,7 @@ fn format_type() { position_span: InnerSpan { start: 2, end: 3 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -152,6 +154,7 @@ fn format_align_fill() { position_span: InnerSpan { start: 2, end: 3 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignRight, sign: None, alternate: false, @@ -173,6 +176,7 @@ fn format_align_fill() { position_span: InnerSpan { start: 2, end: 3 }, format: FormatSpec { fill: Some('0'), + fill_span: Some(InnerSpan::new(4, 5)), align: AlignLeft, sign: None, alternate: false, @@ -194,6 +198,7 @@ fn format_align_fill() { position_span: InnerSpan { start: 2, end: 3 }, format: FormatSpec { fill: Some('*'), + fill_span: Some(InnerSpan::new(4, 5)), align: AlignLeft, sign: None, alternate: false, @@ -218,6 +223,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -239,6 +245,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -260,6 +267,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 3 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -281,6 +289,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -302,6 +311,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -323,6 +333,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -344,6 +355,7 @@ fn format_counts() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, @@ -368,6 +380,7 @@ fn format_flags() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: Some(Sign::Minus), alternate: false, @@ -389,6 +402,7 @@ fn format_flags() { position_span: InnerSpan { start: 2, end: 2 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: Some(Sign::Plus), alternate: true, @@ -415,6 +429,7 @@ fn format_mixture() { position_span: InnerSpan { start: 7, end: 8 }, format: FormatSpec { fill: None, + fill_span: None, align: AlignUnknown, sign: None, alternate: false, diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 406801506013b..6eacbebe75f41 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -4,118 +4,185 @@ -passes_see_issue = see issue #{$issue} for more information -passes_incorrect_do_not_recommend_location = - `#[do_not_recommend]` can only be placed on trait implementations +passes_abi = + abi: {$abi} -passes_outer_crate_level_attr = - crate-level attribute should be an inner attribute: add an exclamation mark: `#![foo]` +passes_align = + align: {$align} -passes_inner_crate_level_attr = - crate-level attribute should be in the root module +passes_allow_incoherent_impl = + `rustc_allow_incoherent_impl` attribute should be applied to impl items. + .label = the only currently supported targets are inherent methods -passes_ignored_attr_with_macro = - `#[{$sym}]` is ignored on struct fields, match arms and macro defs - .warn = {-passes_previously_accepted} - .note = {-passes_see_issue(issue: "80564")} +passes_allow_internal_unstable = + attribute should be applied to a macro + .label = not a macro -passes_ignored_attr = - `#[{$sym}]` is ignored on struct fields and match arms - .warn = {-passes_previously_accepted} - .note = {-passes_see_issue(issue: "80564")} +passes_attr_application_enum = + attribute should be applied to an enum + .label = not an enum -passes_inline_ignored_function_prototype = - `#[inline]` is ignored on function prototypes +passes_attr_application_struct = + attribute should be applied to a struct + .label = not a struct -passes_inline_ignored_constants = - `#[inline]` is ignored on constants - .warn = {-passes_previously_accepted} - .note = {-passes_see_issue(issue: "65833")} +passes_attr_application_struct_enum_function_method_union = + attribute should be applied to a struct, enum, function, associated function, or union + .label = not a struct, enum, function, associated function, or union -passes_inline_not_fn_or_closure = - attribute should be applied to function or closure - .label = not a function or closure +passes_attr_application_struct_enum_union = + attribute should be applied to a struct, enum, or union + .label = not a struct, enum, or union -passes_no_coverage_ignored_function_prototype = - `#[no_coverage]` is ignored on function prototypes +passes_attr_application_struct_union = + attribute should be applied to a struct or union + .label = not a struct or union -passes_no_coverage_propagate = - `#[no_coverage]` does not propagate into items and must be applied to the contained functions directly +passes_attr_crate_level = + this attribute can only be applied at the crate level + .suggestion = to apply to the crate, use an inner attribute + .note = read for more information -passes_no_coverage_fn_defn = - `#[no_coverage]` may only be applied to function definitions +passes_attr_only_in_functions = + `{$attr}` attribute can only be used on functions -passes_no_coverage_not_coverable = - `#[no_coverage]` must be applied to coverable code - .label = not coverable code +passes_attr_only_on_main = + `{$attr}` attribute can only be used on `fn main()` -passes_should_be_applied_to_fn = - attribute should be applied to a function definition - .label = {$on_crate -> - [true] cannot be applied to crates - *[false] not a function definition - } +passes_attr_only_on_root_main = + `{$attr}` attribute can only be used on root `fn main()` -passes_naked_tracked_caller = - cannot use `#[track_caller]` with `#[naked]` +passes_both_ffi_const_and_pure = + `#[ffi_const]` function cannot be `#[ffi_pure]` -passes_should_be_applied_to_struct_enum = - attribute should be applied to a struct or enum - .label = not a struct or enum +passes_break_inside_async_block = + `{$name}` inside of an `async` block + .label = cannot `{$name}` inside of an `async` block + .async_block_label = enclosing `async` block -passes_should_be_applied_to_trait = - attribute should be applied to a trait - .label = not a trait +passes_break_inside_closure = + `{$name}` inside of a closure + .label = cannot `{$name}` inside of a closure + .closure_label = enclosing closure -passes_target_feature_on_statement = +passes_break_non_loop = + `break` with value from a `{$kind}` loop + .label = can only break with a value inside `loop` or breakable block + .label2 = you can't `break` with a value in a `{$kind}` loop + .suggestion = use `break` on its own without a value inside this `{$kind}` loop + .break_expr_suggestion = alternatively, you might have meant to use the available loop label + +passes_cannot_inline_naked_function = + naked functions cannot be inlined + +passes_cannot_stabilize_deprecated = + an API can't be stabilized after it is deprecated + .label = invalid version + .item = the stability attribute annotates this item + +passes_change_fields_to_be_of_unit_type = + consider changing the { $num -> + [one] field + *[other] fields + } to be of unit type to suppress this warning while preserving the field numbering, or remove the { $num -> + [one] field + *[other] fields + } + +passes_cold = {passes_should_be_applied_to_fn} .warn = {-passes_previously_accepted} .label = {passes_should_be_applied_to_fn.label} -passes_should_be_applied_to_static = - attribute should be applied to a static - .label = not a static +passes_collapse_debuginfo = + `collapse_debuginfo` attribute should be applied to macro definitions + .label = not a macro definition -passes_doc_expect_str = - doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")] +passes_confusables = attribute should be applied to an inherent method + .label = not an inherent method -passes_doc_alias_empty = - {$attr_str} attribute cannot have empty value +passes_const_impl_const_trait = + const `impl`s must be for traits marked with `#[const_trait]` + .note = this trait must be annotated with `#[const_trait]` + +passes_continue_labeled_block = + `continue` pointing to a labeled block + .label = labeled blocks cannot be `continue`'d + .block_label = labeled block the `continue` points to + +passes_dead_codes = + { $multiple -> + *[true] multiple {$descr}s are + [false] { $num -> + [one] {$descr} {$name_list} is + *[other] {$descr}s {$name_list} are + } + } never {$participle} + +passes_debug_visualizer_invalid = + invalid argument + .note_1 = expected: `natvis_file = "..."` + .note_2 = OR + .note_3 = expected: `gdb_script_file = "..."` + +passes_debug_visualizer_placement = + attribute should be applied to a module + +passes_debug_visualizer_unreadable = + couldn't read {$file}: {$error} + +passes_deprecated = + attribute is ignored here + +passes_deprecated_annotation_has_no_effect = + this `#[deprecated]` annotation has no effect + .suggestion = remove the unnecessary deprecation attribute + +passes_deprecated_attribute = + deprecated attribute must be paired with either stable or unstable attribute + +passes_diagnostic_item_first_defined = + the diagnostic item is first defined here passes_doc_alias_bad_char = {$char_} character isn't allowed in {$attr_str} -passes_doc_alias_start_end = - {$attr_str} cannot start or end with ' ' - passes_doc_alias_bad_location = {$attr_str} isn't allowed on {$location} -passes_doc_alias_not_an_alias = - {$attr_str} is the same as the item's name - passes_doc_alias_duplicated = doc alias is duplicated .label = first defined here -passes_doc_alias_not_string_literal = - `#[doc(alias("a"))]` expects string literals +passes_doc_alias_empty = + {$attr_str} attribute cannot have empty value passes_doc_alias_malformed = doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]` -passes_doc_keyword_empty_mod = - `#[doc(keyword = "...")]` should be used on empty modules +passes_doc_alias_not_an_alias = + {$attr_str} is the same as the item's name -passes_doc_keyword_not_mod = - `#[doc(keyword = "...")]` should be used on modules +passes_doc_alias_not_string_literal = + `#[doc(alias("a"))]` expects string literals -passes_doc_keyword_invalid_ident = - `{$doc_keyword}` is not a valid identifier +passes_doc_alias_start_end = + {$attr_str} cannot start or end with ' ' + +passes_doc_attr_not_crate_level = + `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute + +passes_doc_cfg_hide_takes_list = + `#[doc(cfg_hide(...)]` takes a list of attributes + +passes_doc_expect_str = + doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")] passes_doc_fake_variadic_not_valid = `#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity -passes_doc_keyword_only_impl = - `#[doc(keyword = "...")]` should be used on impl blocks +passes_doc_inline_conflict = + conflicting doc inlining attributes + .help = remove one of the conflicting attributes passes_doc_inline_conflict_first = this attribute... @@ -123,383 +190,297 @@ passes_doc_inline_conflict_first = passes_doc_inline_conflict_second = {"."}..conflicts with this attribute -passes_doc_inline_conflict = - conflicting doc inlining attributes - .help = remove one of the conflicting attributes - passes_doc_inline_only_use = this attribute can only be applied to a `use` item .label = only applicable on `use` items .not_a_use_item_label = not a `use` item .note = read for more information -passes_doc_attr_not_crate_level = - `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute +passes_doc_invalid = + invalid `doc` attribute -passes_attr_crate_level = - this attribute can only be applied at the crate level - .suggestion = to apply to the crate, use an inner attribute - .note = read for more information +passes_doc_keyword_empty_mod = + `#[doc(keyword = "...")]` should be used on empty modules -passes_doc_test_unknown = - unknown `doc(test)` attribute `{$path}` +passes_doc_keyword_invalid_ident = + `{$doc_keyword}` is not a valid identifier + +passes_doc_keyword_not_mod = + `#[doc(keyword = "...")]` should be used on modules + +passes_doc_keyword_only_impl = + `#[doc(keyword = "...")]` should be used on impl blocks + +passes_doc_masked_not_extern_crate_self = + this attribute cannot be applied to an `extern crate self` item + .label = not applicable on `extern crate self` items + .extern_crate_self_label = `extern crate self` defined here + +passes_doc_masked_only_extern_crate = + this attribute can only be applied to an `extern crate` item + .label = only applicable on `extern crate` items + .not_an_extern_crate_label = not an `extern crate` item + .note = read for more information + +passes_doc_test_literal = `#![doc(test(...)]` does not take a literal passes_doc_test_takes_list = `#[doc(test(...)]` takes a list of attributes -passes_doc_cfg_hide_takes_list = - `#[doc(cfg_hide(...)]` takes a list of attributes +passes_doc_test_unknown = + unknown `doc(test)` attribute `{$path}` passes_doc_test_unknown_any = unknown `doc` attribute `{$path}` +passes_doc_test_unknown_include = + unknown `doc` attribute `{$path}` + .suggestion = use `doc = include_str!` instead + passes_doc_test_unknown_spotlight = unknown `doc` attribute `{$path}` .note = `doc(spotlight)` was renamed to `doc(notable_trait)` .suggestion = use `notable_trait` instead .no_op_note = `doc(spotlight)` is now a no-op -passes_doc_test_unknown_include = - unknown `doc` attribute `{$path}` - .suggestion = use `doc = include_str!` instead - -passes_doc_invalid = - invalid `doc` attribute +passes_duplicate_diagnostic_item_in_crate = + duplicate diagnostic item in crate `{$crate_name}`: `{$name}`. + .note = the diagnostic item is first defined in crate `{$orig_crate_name}`. -passes_pass_by_value = - `pass_by_value` attribute should be applied to a struct, enum or type alias - .label = is not a struct, enum or type alias +passes_duplicate_feature_err = + the feature `{$feature}` has already been declared -passes_allow_incoherent_impl = - `rustc_allow_incoherent_impl` attribute should be applied to impl items. - .label = the only currently supported targets are inherent methods - -passes_has_incoherent_inherent_impl = - `rustc_has_incoherent_inherent_impls` attribute should be applied to types or traits. - .label = only adts, extern types and traits are supported - -passes_both_ffi_const_and_pure = - `#[ffi_const]` function cannot be `#[ffi_pure]` - -passes_ffi_pure_invalid_target = - `#[ffi_pure]` may only be used on foreign functions - -passes_ffi_const_invalid_target = - `#[ffi_const]` may only be used on foreign functions - -passes_ffi_returns_twice_invalid_target = - `#[ffi_returns_twice]` may only be used on foreign functions - -passes_must_use_async = - `must_use` attribute on `async` functions applies to the anonymous `Future` returned by the function, not the value within - .label = this attribute does nothing, the `Future`s returned by async functions are already `must_use` - -passes_must_use_no_effect = - `#[must_use]` has no effect when applied to {$article} {$target} - -passes_must_not_suspend = - `must_not_suspend` attribute should be applied to a struct, enum, or trait - .label = is not a struct, enum, or trait - -passes_cold = - {passes_should_be_applied_to_fn} - .warn = {-passes_previously_accepted} - .label = {passes_should_be_applied_to_fn.label} +passes_duplicate_lang_item = + found duplicate lang item `{$lang_item_name}` + .first_defined_span = the lang item is first defined here + .first_defined_crate_depends = the lang item is first defined in crate `{$orig_crate_name}` (which `{$orig_dependency_of}` depends on) + .first_defined_crate = the lang item is first defined in crate `{$orig_crate_name}`. + .first_definition_local = first definition in the local crate (`{$orig_crate_name}`) + .second_definition_local = second definition in the local crate (`{$crate_name}`) + .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} + .second_definition_path = second definition in `{$crate_name}` loaded from {$path} -passes_link = - attribute should be applied to an `extern` block with non-Rust ABI - .warn = {-passes_previously_accepted} - .label = not an `extern` block +passes_duplicate_lang_item_crate = + duplicate lang item in crate `{$crate_name}`: `{$lang_item_name}`. + .first_defined_span = the lang item is first defined here + .first_defined_crate_depends = the lang item is first defined in crate `{$orig_crate_name}` (which `{$orig_dependency_of}` depends on) + .first_defined_crate = the lang item is first defined in crate `{$orig_crate_name}`. + .first_definition_local = first definition in the local crate (`{$orig_crate_name}`) + .second_definition_local = second definition in the local crate (`{$crate_name}`) + .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} + .second_definition_path = second definition in `{$crate_name}` loaded from {$path} -passes_link_name = - attribute should be applied to a foreign function or static - .warn = {-passes_previously_accepted} - .label = not a foreign function or static - .help = try `#[link(name = "{$value}")]` instead +passes_duplicate_lang_item_crate_depends = + duplicate lang item in crate `{$crate_name}` (which `{$dependency_of}` depends on): `{$lang_item_name}`. + .first_defined_span = the lang item is first defined here + .first_defined_crate_depends = the lang item is first defined in crate `{$orig_crate_name}` (which `{$orig_dependency_of}` depends on) + .first_defined_crate = the lang item is first defined in crate `{$orig_crate_name}`. + .first_definition_local = first definition in the local crate (`{$orig_crate_name}`) + .second_definition_local = second definition in the local crate (`{$crate_name}`) + .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} + .second_definition_path = second definition in `{$crate_name}` loaded from {$path} -passes_no_link = - attribute should be applied to an `extern crate` item - .label = not an `extern crate` item +passes_empty_confusables = + expected at least one confusable name passes_export_name = attribute should be applied to a free function, impl method or static .label = not a free function, impl method or static -passes_rustc_layout_scalar_valid_range_not_struct = - attribute should be applied to a struct - .label = not a struct - -passes_rustc_layout_scalar_valid_range_arg = - expected exactly one integer literal argument - -passes_rustc_legacy_const_generics_only = - #[rustc_legacy_const_generics] functions must only have const generics - .label = non-const generic parameter - -passes_rustc_legacy_const_generics_index = - #[rustc_legacy_const_generics] must have one index for each generic parameter - .label = generic parameters - -passes_rustc_legacy_const_generics_index_exceed = - index exceeds number of arguments - .label = there {$arg_count -> - [one] is - *[other] are - } only {$arg_count} {$arg_count -> - [one] argument - *[other] arguments - } - -passes_rustc_legacy_const_generics_index_negative = - arguments should be non-negative integers - -passes_rustc_dirty_clean = - attribute requires -Z query-dep-graph to be enabled - -passes_link_section = - attribute should be applied to a function or static - .warn = {-passes_previously_accepted} - .label = not a function or static - -passes_no_mangle_foreign = - `#[no_mangle]` has no effect on a foreign {$foreign_item_kind} - .warn = {-passes_previously_accepted} - .label = foreign {$foreign_item_kind} - .note = symbol names in extern blocks are not mangled - .suggestion = remove this attribute - -passes_no_mangle = - attribute should be applied to a free function, impl method or static - .warn = {-passes_previously_accepted} - .label = not a free function, impl method or static - -passes_repr_ident = - meta item in `repr` must be an identifier +passes_expr_not_allowed_in_context = + {$expr} is not allowed in a `{$context}` -passes_repr_conflicting = - conflicting representation hints +passes_extern_main = + the `main` function cannot be declared in an `extern` block -passes_used_static = - attribute must be applied to a `static` variable +passes_feature_only_on_nightly = + `#![feature]` may not be used on the {$release_channel} release channel -passes_used_compiler_linker = - `used(compiler)` and `used(linker)` can't be used together +passes_feature_previously_declared = + feature `{$feature}` is declared {$declared}, but was previously declared {$prev_declared} -passes_allow_internal_unstable = - attribute should be applied to a macro - .label = not a macro +passes_feature_stable_twice = + feature `{$feature}` is declared stable since {$since}, but was previously declared stable since {$prev_since} -passes_debug_visualizer_placement = - attribute should be applied to a module +passes_ffi_const_invalid_target = + `#[ffi_const]` may only be used on foreign functions -passes_debug_visualizer_invalid = - invalid argument - .note_1 = expected: `natvis_file = "..."` - .note_2 = OR - .note_3 = expected: `gdb_script_file = "..."` +passes_ffi_pure_invalid_target = + `#[ffi_pure]` may only be used on foreign functions -passes_debug_visualizer_unreadable = - couldn't read {$file}: {$error} +passes_ffi_returns_twice_invalid_target = + `#[ffi_returns_twice]` may only be used on foreign functions -passes_rustc_allow_const_fn_unstable = - attribute should be applied to `const fn` - .label = not a `const fn` +passes_has_incoherent_inherent_impl = + `rustc_has_incoherent_inherent_impls` attribute should be applied to types or traits. + .label = only adts, extern types and traits are supported -passes_rustc_std_internal_symbol = - attribute should be applied to functions or statics - .label = not a function or static +passes_homogeneous_aggregate = + homogeneous_aggregate: {$homogeneous_aggregate} -passes_const_trait = - attribute should be applied to a trait +passes_ignored_attr = + `#[{$sym}]` is ignored on struct fields and match arms + .warn = {-passes_previously_accepted} + .note = {-passes_see_issue(issue: "80564")} -passes_stability_promotable = - attribute cannot be applied to an expression +passes_ignored_attr_with_macro = + `#[{$sym}]` is ignored on struct fields, match arms and macro defs + .warn = {-passes_previously_accepted} + .note = {-passes_see_issue(issue: "80564")} -passes_deprecated = - attribute is ignored here +passes_ignored_derived_impls = + `{$name}` has {$trait_list_len -> + [one] a derived impl + *[other] derived impls + } for the {$trait_list_len -> + [one] trait {$trait_list}, but this is + *[other] traits {$trait_list}, but these are + } intentionally ignored during dead code analysis -passes_macro_use = - `#[{$name}]` only has an effect on `extern crate` and modules +passes_implied_feature_not_exist = + feature `{$implied_by}` implying `{$feature}` does not exist -passes_macro_export = - `#[macro_export]` only has an effect on macro definitions +passes_incorrect_do_not_recommend_location = + `#[do_not_recommend]` can only be placed on trait implementations -passes_plugin_registrar = - `#[plugin_registrar]` only has an effect on functions +passes_incorrect_meta_item = expected a quoted string literal +passes_incorrect_meta_item_suggestion = consider surrounding this with quotes -passes_unused_empty_lints_note = - attribute `{$name}` with an empty list has no effect +passes_incorrect_target = + `{$name}` language item must be applied to a {$kind} with {$at_least -> + [true] at least {$num} + *[false] {$num} + } generic {$num -> + [one] argument + *[other] arguments + } + .label = this {$kind} has {$actual_num} generic {$actual_num -> + [one] argument + *[other] arguments + } -passes_unused_no_lints_note = - attribute `{$name}` without any lints has no effect +passes_ineffective_unstable_impl = an `#[unstable]` annotation here has no effect + .note = see issue #55436 for more information -passes_unused_default_method_body_const_note = - `default_method_body_is_const` has been replaced with `#[const_trait]` on traits +passes_inline_ignored_constants = + `#[inline]` is ignored on constants + .warn = {-passes_previously_accepted} + .note = {-passes_see_issue(issue: "65833")} -passes_unused = - unused attribute - .suggestion = remove this attribute +passes_inline_ignored_function_prototype = + `#[inline]` is ignored on function prototypes -passes_non_exported_macro_invalid_attrs = +passes_inline_not_fn_or_closure = attribute should be applied to function or closure .label = not a function or closure -passes_unused_duplicate = - unused attribute - .suggestion = remove this attribute - .note = attribute also specified here - .warn = {-passes_previously_accepted} - -passes_unused_multiple = - multiple `{$name}` attributes - .suggestion = remove this attribute - .note = attribute also specified here - -passes_rustc_lint_opt_ty = - `#[rustc_lint_opt_ty]` should be applied to a struct - .label = not a struct - -passes_rustc_lint_opt_deny_field_access = - `#[rustc_lint_opt_deny_field_access]` should be applied to a field - .label = not a field - -passes_link_ordinal = - attribute should be applied to a foreign function or static - .label = not a foreign function or static - -passes_collapse_debuginfo = - `collapse_debuginfo` attribute should be applied to macro definitions - .label = not a macro definition - -passes_deprecated_annotation_has_no_effect = - this `#[deprecated]` annotation has no effect - .suggestion = remove the unnecessary deprecation attribute - -passes_unknown_external_lang_item = - unknown external lang item: `{$lang_item}` - -passes_missing_panic_handler = - `#[panic_handler]` function required, but not found - -passes_missing_lang_item = - language item required, but not found: `{$name}` - .note = this can occur when a binary crate with `#![no_std]` is compiled for a target where `{$name}` is defined in the standard library - .help = you may be able to compile for a target that doesn't need `{$name}`, specify a target with `--target` or in `.cargo/config` - -passes_lang_item_on_incorrect_target = - `{$name}` language item must be applied to a {$expected_target} - .label = attribute should be applied to a {$expected_target}, not a {$actual_target} - -passes_unknown_lang_item = - definition of an unknown language item: `{$name}` - .label = definition of unknown language item `{$name}` +passes_inner_crate_level_attr = + crate-level attribute should be in the root module passes_invalid_attr_at_crate_level = `{$name}` attribute cannot be used at crate level .suggestion = perhaps you meant to use an outer attribute -passes_duplicate_diagnostic_item_in_crate = - duplicate diagnostic item in crate `{$crate_name}`: `{$name}`. - .note = the diagnostic item is first defined in crate `{$orig_crate_name}`. +passes_invalid_deprecation_version = + invalid deprecation version found + .label = invalid deprecation version + .item = the stability attribute annotates this item -passes_diagnostic_item_first_defined = - the diagnostic item is first defined here +passes_invalid_macro_export_arguments = `{$name}` isn't a valid `#[macro_export]` argument -passes_abi = - abi: {$abi} +passes_invalid_macro_export_arguments_too_many_items = `#[macro_export]` can only take 1 or 0 arguments -passes_align = - align: {$align} +passes_invalid_stability = + invalid stability version found + .label = invalid stability version + .item = the stability attribute annotates this item -passes_size = - size: {$size} +passes_lang_item_on_incorrect_target = + `{$name}` language item must be applied to a {$expected_target} + .label = attribute should be applied to a {$expected_target}, not a {$actual_target} -passes_homogeneous_aggregate = - homogeneous_aggregate: {$homogeneous_aggregate} +passes_layout = + layout error: {$layout_error} passes_layout_of = layout_of({$normalized_ty}) = {$ty_layout} -passes_unrecognized_field = - unrecognized field name `{$name}` +passes_link = + attribute should be applied to an `extern` block with non-Rust ABI + .warn = {-passes_previously_accepted} + .label = not an `extern` block -passes_layout = - layout error: {$layout_error} +passes_link_name = + attribute should be applied to a foreign function or static + .warn = {-passes_previously_accepted} + .label = not a foreign function or static + .help = try `#[link(name = "{$value}")]` instead -passes_feature_stable_twice = - feature `{$feature}` is declared stable since {$since}, but was previously declared stable since {$prev_since} +passes_link_ordinal = + attribute should be applied to a foreign function or static + .label = not a foreign function or static -passes_feature_previously_declared = - feature `{$feature}` is declared {$declared}, but was previously declared {$prev_declared} +passes_link_section = + attribute should be applied to a function or static + .warn = {-passes_previously_accepted} + .label = not a function or static -passes_expr_not_allowed_in_context = - {$expr} is not allowed in a `{$context}` +passes_macro_export = + `#[macro_export]` only has an effect on macro definitions -passes_const_impl_const_trait = - const `impl`s must be for traits marked with `#[const_trait]` - .note = this trait must be annotated with `#[const_trait]` +passes_macro_export_on_decl_macro = + `#[macro_export]` has no effect on declarative macro definitions + .note = declarative macros follow the same exporting rules as regular items -passes_break_non_loop = - `break` with value from a `{$kind}` loop - .label = can only break with a value inside `loop` or breakable block - .label2 = you can't `break` with a value in a `{$kind}` loop - .suggestion = use `break` on its own without a value inside this `{$kind}` loop - .break_expr_suggestion = alternatively, you might have meant to use the available loop label +passes_macro_use = + `#[{$name}]` only has an effect on `extern crate` and modules -passes_continue_labeled_block = - `continue` pointing to a labeled block - .label = labeled blocks cannot be `continue`'d - .block_label = labeled block the `continue` points to +passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal +passes_missing_const_err = + attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const` + .help = make the function or method const + .label = attribute specified here -passes_break_inside_closure = - `{$name}` inside of a closure - .label = cannot `{$name}` inside of a closure - .closure_label = enclosing closure +passes_missing_const_stab_attr = + {$descr} has missing const stability attribute -passes_break_inside_async_block = - `{$name}` inside of an `async` block - .label = cannot `{$name}` inside of an `async` block - .async_block_label = enclosing `async` block +passes_missing_lang_item = + language item required, but not found: `{$name}` + .note = this can occur when a binary crate with `#![no_std]` is compiled for a target where `{$name}` is defined in the standard library + .help = you may be able to compile for a target that doesn't need `{$name}`, specify a target with `--target` or in `.cargo/config` -passes_outside_loop = - `{$name}` outside of a loop{$is_break -> - [true] {" or labeled block"} - *[false] {""} - } - .label = cannot `{$name}` outside of a loop{$is_break -> - [true] {" or labeled block"} - *[false] {""} - } +passes_missing_panic_handler = + `#[panic_handler]` function required, but not found -passes_unlabeled_in_labeled_block = - unlabeled `{$cf_type}` inside of a labeled block - .label = `{$cf_type}` statements that would diverge to or through a labeled block need to bear a label +passes_missing_stability_attr = + {$descr} has missing stability attribute -passes_unlabeled_cf_in_while_condition = - `break` or `continue` with no label in the condition of a `while` loop - .label = unlabeled `{$cf_type}` in the condition of a `while` loop +passes_multiple_rustc_main = + multiple functions with a `#[rustc_main]` attribute + .first = first `#[rustc_main]` function + .additional = additional `#[rustc_main]` function -passes_cannot_inline_naked_function = - naked functions cannot be inlined +passes_multiple_start_functions = + multiple `start` functions + .label = multiple `start` functions + .previous = previous `#[start]` function here -passes_undefined_naked_function_abi = - Rust ABI is unsupported in naked functions +passes_must_not_suspend = + `must_not_suspend` attribute should be applied to a struct, enum, or trait + .label = is not a struct, enum, or trait -passes_no_patterns = - patterns not allowed in naked function parameters +passes_must_use_async = + `must_use` attribute on `async` functions applies to the anonymous `Future` returned by the function, not the value within + .label = this attribute does nothing, the `Future`s returned by async functions are already `must_use` -passes_params_not_allowed = - referencing function parameters is not allowed in naked functions - .help = follow the calling convention in asm block to use parameters +passes_must_use_no_effect = + `#[must_use]` has no effect when applied to {$article} {$target} passes_naked_functions_asm_block = naked functions must contain a single asm block .label_multiple_asm = multiple asm blocks are unsupported in naked functions .label_non_asm = non-asm is unsupported in naked functions -passes_naked_functions_operands = - only `const` and `sym` operands are supported in naked functions - passes_naked_functions_asm_options = asm options unsupported in naked functions: {$unsupported_options} @@ -507,30 +488,28 @@ passes_naked_functions_must_use_noreturn = asm in naked functions must use `noreturn` option .suggestion = consider specifying that the asm block is responsible for returning from the function -passes_attr_only_on_main = - `{$attr}` attribute can only be used on `fn main()` +passes_naked_functions_operands = + only `const` and `sym` operands are supported in naked functions -passes_attr_only_on_root_main = - `{$attr}` attribute can only be used on root `fn main()` +passes_naked_tracked_caller = + cannot use `#[track_caller]` with `#[naked]` -passes_attr_only_in_functions = - `{$attr}` attribute can only be used on functions +passes_no_coverage_fn_defn = + `#[no_coverage]` may only be applied to function definitions -passes_multiple_rustc_main = - multiple functions with a `#[rustc_main]` attribute - .first = first `#[rustc_main]` function - .additional = additional `#[rustc_main]` function +passes_no_coverage_ignored_function_prototype = + `#[no_coverage]` is ignored on function prototypes -passes_multiple_start_functions = - multiple `start` functions - .label = multiple `start` functions - .previous = previous `#[start]` function here +passes_no_coverage_not_coverable = + `#[no_coverage]` must be applied to coverable code + .label = not coverable code -passes_extern_main = - the `main` function cannot be declared in an `extern` block +passes_no_coverage_propagate = + `#[no_coverage]` does not propagate into items and must be applied to the contained functions directly -passes_unix_sigpipe_values = - valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl` +passes_no_link = + attribute should be applied to an `extern crate` item + .label = not an `extern crate` item passes_no_main_function = `main` function not found in crate `{$crate_name}` @@ -546,222 +525,266 @@ passes_no_main_function = .teach_note = If you don't know the basics of Rust, you can go look to the Rust Book to get started: https://doc.rust-lang.org/book/ .non_function_main = non-function item at `crate::main` is found -passes_duplicate_lang_item = - found duplicate lang item `{$lang_item_name}` - .first_defined_span = the lang item is first defined here - .first_defined_crate_depends = the lang item is first defined in crate `{$orig_crate_name}` (which `{$orig_dependency_of}` depends on) - .first_defined_crate = the lang item is first defined in crate `{$orig_crate_name}`. - .first_definition_local = first definition in the local crate (`{$orig_crate_name}`) - .second_definition_local = second definition in the local crate (`{$crate_name}`) - .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} - .second_definition_path = second definition in `{$crate_name}` loaded from {$path} +passes_no_mangle = + attribute should be applied to a free function, impl method or static + .warn = {-passes_previously_accepted} + .label = not a free function, impl method or static -passes_duplicate_lang_item_crate = - duplicate lang item in crate `{$crate_name}`: `{$lang_item_name}`. - .first_defined_span = the lang item is first defined here - .first_defined_crate_depends = the lang item is first defined in crate `{$orig_crate_name}` (which `{$orig_dependency_of}` depends on) - .first_defined_crate = the lang item is first defined in crate `{$orig_crate_name}`. - .first_definition_local = first definition in the local crate (`{$orig_crate_name}`) - .second_definition_local = second definition in the local crate (`{$crate_name}`) - .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} - .second_definition_path = second definition in `{$crate_name}` loaded from {$path} +passes_no_mangle_foreign = + `#[no_mangle]` has no effect on a foreign {$foreign_item_kind} + .warn = {-passes_previously_accepted} + .label = foreign {$foreign_item_kind} + .note = symbol names in extern blocks are not mangled + .suggestion = remove this attribute -passes_duplicate_lang_item_crate_depends = - duplicate lang item in crate `{$crate_name}` (which `{$dependency_of}` depends on): `{$lang_item_name}`. - .first_defined_span = the lang item is first defined here - .first_defined_crate_depends = the lang item is first defined in crate `{$orig_crate_name}` (which `{$orig_dependency_of}` depends on) - .first_defined_crate = the lang item is first defined in crate `{$orig_crate_name}`. - .first_definition_local = first definition in the local crate (`{$orig_crate_name}`) - .second_definition_local = second definition in the local crate (`{$crate_name}`) - .first_definition_path = first definition in `{$orig_crate_name}` loaded from {$orig_path} - .second_definition_path = second definition in `{$crate_name}` loaded from {$path} +passes_no_patterns = + patterns not allowed in naked function parameters -passes_incorrect_target = - `{$name}` language item must be applied to a {$kind} with {$at_least -> - [true] at least {$num} - *[false] {$num} - } generic {$num -> - [one] argument - *[other] arguments +passes_non_exported_macro_invalid_attrs = + attribute should be applied to function or closure + .label = not a function or closure + +passes_object_lifetime_err = + {$repr} + +passes_only_has_effect_on = + `#[{$attr_name}]` only has an effect on {$target_name -> + [function] functions + [module] modules + [implementation_block] implementation blocks + *[unspecified] (unspecified--this is a compiler bug) } - .label = this {$kind} has {$actual_num} generic {$actual_num -> - [one] argument - *[other] arguments + +passes_outer_crate_level_attr = + crate-level attribute should be an inner attribute: add an exclamation mark: `#![foo]` + +passes_outside_loop = + `{$name}` outside of a loop{$is_break -> + [true] {" or labeled block"} + *[false] {""} + } + .label = cannot `{$name}` outside of a loop{$is_break -> + [true] {" or labeled block"} + *[false] {""} } -passes_useless_assignment = - useless assignment of {$is_field_assign -> - [true] field - *[false] variable - } of type `{$ty}` to itself +passes_params_not_allowed = + referencing function parameters is not allowed in naked functions + .help = follow the calling convention in asm block to use parameters + +passes_parent_info = + {$num -> + [one] {$descr} + *[other] {$descr}s + } in this {$parent_descr} + +passes_pass_by_value = + `pass_by_value` attribute should be applied to a struct, enum or type alias + .label = is not a struct, enum or type alias + +passes_plugin_registrar = + `#[plugin_registrar]` only has an effect on functions + +passes_proc_macro_bad_sig = {$kind} has incorrect signature + +passes_repr_conflicting = + conflicting representation hints + +passes_repr_ident = + meta item in `repr` must be an identifier + +passes_rustc_allow_const_fn_unstable = + attribute should be applied to `const fn` + .label = not a `const fn` + +passes_rustc_dirty_clean = + attribute requires -Z query-dep-graph to be enabled + +passes_rustc_layout_scalar_valid_range_arg = + expected exactly one integer literal argument + +passes_rustc_layout_scalar_valid_range_not_struct = + attribute should be applied to a struct + .label = not a struct -passes_only_has_effect_on = - `#[{$attr_name}]` only has an effect on {$target_name -> - [function] functions - [module] modules - [implementation_block] implementation blocks - *[unspecified] (unspecified--this is a compiler bug) +passes_rustc_legacy_const_generics_index = + #[rustc_legacy_const_generics] must have one index for each generic parameter + .label = generic parameters + +passes_rustc_legacy_const_generics_index_exceed = + index exceeds number of arguments + .label = there {$arg_count -> + [one] is + *[other] are + } only {$arg_count} {$arg_count -> + [one] argument + *[other] arguments } -passes_object_lifetime_err = - {$repr} +passes_rustc_legacy_const_generics_index_negative = + arguments should be non-negative integers -passes_unrecognized_repr_hint = - unrecognized representation hint - .help = valid reprs are `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize` +passes_rustc_legacy_const_generics_only = + #[rustc_legacy_const_generics] functions must only have const generics + .label = non-const generic parameter -passes_attr_application_enum = - attribute should be applied to an enum - .label = not an enum +passes_rustc_lint_opt_deny_field_access = + `#[rustc_lint_opt_deny_field_access]` should be applied to a field + .label = not a field -passes_attr_application_struct = - attribute should be applied to a struct +passes_rustc_lint_opt_ty = + `#[rustc_lint_opt_ty]` should be applied to a struct .label = not a struct -passes_attr_application_struct_union = - attribute should be applied to a struct or union - .label = not a struct or union - -passes_attr_application_struct_enum_union = - attribute should be applied to a struct, enum, or union - .label = not a struct, enum, or union +passes_rustc_std_internal_symbol = + attribute should be applied to functions or statics + .label = not a function or static -passes_attr_application_struct_enum_function_method_union = - attribute should be applied to a struct, enum, function, associated function, or union - .label = not a struct, enum, function, associated function, or union +passes_should_be_applied_to_fn = + attribute should be applied to a function definition + .label = {$on_crate -> + [true] cannot be applied to crates + *[false] not a function definition + } -passes_transparent_incompatible = - transparent {$target} cannot have other repr hints +passes_should_be_applied_to_static = + attribute should be applied to a static + .label = not a static -passes_deprecated_attribute = - deprecated attribute must be paired with either stable or unstable attribute +passes_should_be_applied_to_struct_enum = + attribute should be applied to a struct or enum + .label = not a struct or enum -passes_useless_stability = - this stability annotation is useless - .label = useless stability annotation - .item = the stability attribute annotates this item +passes_should_be_applied_to_trait = + attribute should be applied to a trait + .label = not a trait -passes_invalid_stability = - invalid stability version found - .label = invalid stability version - .item = the stability attribute annotates this item +passes_size = + size: {$size} -passes_cannot_stabilize_deprecated = - an API can't be stabilized after it is deprecated - .label = invalid version - .item = the stability attribute annotates this item +passes_skipping_const_checks = skipping const checks -passes_invalid_deprecation_version = - invalid deprecation version found - .label = invalid deprecation version - .item = the stability attribute annotates this item +passes_stability_promotable = + attribute cannot be applied to an expression -passes_missing_stability_attr = - {$descr} has missing stability attribute +passes_string_interpolation_only_works = string interpolation only works in `format!` invocations -passes_missing_const_stab_attr = - {$descr} has missing const stability attribute +passes_target_feature_on_statement = + {passes_should_be_applied_to_fn} + .warn = {-passes_previously_accepted} + .label = {passes_should_be_applied_to_fn.label} passes_trait_impl_const_stable = trait implementations cannot be const stable yet .note = see issue #67792 for more information -passes_feature_only_on_nightly = - `#![feature]` may not be used on the {$release_channel} release channel - -passes_unknown_feature = - unknown feature `{$feature}` - -passes_implied_feature_not_exist = - feature `{$implied_by}` implying `{$feature}` does not exist - -passes_duplicate_feature_err = - the feature `{$feature}` has already been declared +passes_transparent_incompatible = + transparent {$target} cannot have other repr hints -passes_missing_const_err = - attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const` - .help = make the function or method const - .label = attribute specified here +passes_undefined_naked_function_abi = + Rust ABI is unsupported in naked functions -passes_dead_codes = - { $multiple -> - *[true] multiple {$descr}s are - [false] { $num -> - [one] {$descr} {$name_list} is - *[other] {$descr}s {$name_list} are - } - } never {$participle} +passes_unix_sigpipe_values = + valid values for `#[unix_sigpipe = "..."]` are `inherit`, `sig_ign`, or `sig_dfl` -passes_change_fields_to_be_of_unit_type = - consider changing the { $num -> - [one] field - *[other] fields - } to be of unit type to suppress this warning while preserving the field numbering, or remove the { $num -> - [one] field - *[other] fields - } +passes_unknown_external_lang_item = + unknown external lang item: `{$lang_item}` -passes_parent_info = - {$num -> - [one] {$descr} - *[other] {$descr}s - } in this {$parent_descr} +passes_unknown_feature = + unknown feature `{$feature}` -passes_ignored_derived_impls = - `{$name}` has {$trait_list_len -> - [one] a derived impl - *[other] derived impls - } for the {$trait_list_len -> - [one] trait {$trait_list}, but this is - *[other] traits {$trait_list}, but these are - } intentionally ignored during dead code analysis +passes_unknown_lang_item = + definition of an unknown language item: `{$name}` + .label = definition of unknown language item `{$name}` -passes_proc_macro_bad_sig = {$kind} has incorrect signature +passes_unlabeled_cf_in_while_condition = + `break` or `continue` with no label in the condition of a `while` loop + .label = unlabeled `{$cf_type}` in the condition of a `while` loop -passes_skipping_const_checks = skipping const checks +passes_unlabeled_in_labeled_block = + unlabeled `{$cf_type}` inside of a labeled block + .label = `{$cf_type}` statements that would diverge to or through a labeled block need to bear a label -passes_invalid_macro_export_arguments = `{$name}` isn't a valid `#[macro_export]` argument +passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been partially stabilized since {$since} and is succeeded by the feature `{$implies}` + .suggestion = if you are using features which are still unstable, change to using `{$implies}` + .suggestion_remove = if you are using features which are now stable, remove this line -passes_invalid_macro_export_arguments_too_many_items = `#[macro_export]` can only take 1 or 0 arguments +passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable passes_unreachable_due_to_uninhabited = unreachable {$descr} .label = unreachable {$descr} .label_orig = any code following this expression is unreachable .note = this expression has type `{$ty}`, which is uninhabited -passes_unused_var_maybe_capture_ref = unused variable: `{$name}` - .help = did you mean to capture by reference instead? +passes_unrecognized_field = + unrecognized field name `{$name}` + +passes_unrecognized_repr_hint = + unrecognized representation hint + .help = valid reprs are `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize` + +passes_unused = + unused attribute + .suggestion = remove this attribute + +passes_unused_assign = value assigned to `{$name}` is never read + .help = maybe it is overwritten before being read? + +passes_unused_assign_passed = value passed to `{$name}` is never read + .help = maybe it is overwritten before being read? passes_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read .help = did you mean to capture by reference instead? -passes_unused_var_remove_field = unused variable: `{$name}` -passes_unused_var_remove_field_suggestion = try removing the field +passes_unused_default_method_body_const_note = + `default_method_body_is_const` has been replaced with `#[const_trait]` on traits -passes_unused_var_assigned_only = variable `{$name}` is assigned to, but never used - .note = consider using `_{$name}` instead +passes_unused_duplicate = + unused attribute + .suggestion = remove this attribute + .note = attribute also specified here + .warn = {-passes_previously_accepted} -passes_unnecessary_stable_feature = the feature `{$feature}` has been stable since {$since} and no longer requires an attribute to enable +passes_unused_empty_lints_note = + attribute `{$name}` with an empty list has no effect -passes_unnecessary_partial_stable_feature = the feature `{$feature}` has been partially stabilized since {$since} and is succeeded by the feature `{$implies}` - .suggestion = if you are using features which are still unstable, change to using `{$implies}` - .suggestion_remove = if you are using features which are now stable, remove this line +passes_unused_multiple = + multiple `{$name}` attributes + .suggestion = remove this attribute + .note = attribute also specified here -passes_ineffective_unstable_impl = an `#[unstable]` annotation here has no effect - .note = see issue #55436 for more information +passes_unused_no_lints_note = + attribute `{$name}` without any lints has no effect -passes_unused_assign = value assigned to `{$name}` is never read - .help = maybe it is overwritten before being read? +passes_unused_var_assigned_only = variable `{$name}` is assigned to, but never used + .note = consider using `_{$name}` instead -passes_unused_assign_passed = value passed to `{$name}` is never read - .help = maybe it is overwritten before being read? +passes_unused_var_maybe_capture_ref = unused variable: `{$name}` + .help = did you mean to capture by reference instead? -passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal -passes_string_interpolation_only_works = string interpolation only works in `format!` invocations +passes_unused_var_remove_field = unused variable: `{$name}` +passes_unused_var_remove_field_suggestion = try removing the field + +passes_unused_variable_try_ignore = unused variable: `{$name}` + .suggestion = try ignoring the field passes_unused_variable_try_prefix = unused variable: `{$name}` .label = unused variable .suggestion = if this is intentional, prefix it with an underscore -passes_unused_variable_try_ignore = unused variable: `{$name}` - .suggestion = try ignoring the field +passes_used_compiler_linker = + `used(compiler)` and `used(linker)` can't be used together + +passes_used_static = + attribute must be applied to a `static` variable + +passes_useless_assignment = + useless assignment of {$is_field_assign -> + [true] field + *[false] variable + } of type `{$ty}` to itself + +passes_useless_stability = + this stability annotation is useless + .label = useless stability annotation + .item = the stability attribute annotates this item diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index c3189d1fefe40..197b335bdec9b 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -10,7 +10,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, IntoDiagnosticArg, MultiSpan}; use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::LocalModDefId; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{ self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID, CRATE_OWNER_ID, @@ -110,9 +110,6 @@ impl CheckAttrVisitor<'_> { sym::no_coverage => self.check_no_coverage(hir_id, attr, span, target), sym::non_exhaustive => self.check_non_exhaustive(hir_id, attr, span, target), sym::marker => self.check_marker(hir_id, attr, span, target), - sym::rustc_must_implement_one_of => { - self.check_rustc_must_implement_one_of(attr, span, target) - } sym::target_feature => self.check_target_feature(hir_id, attr, span, target), sym::thread_local => self.check_thread_local(attr, span, target), sym::track_caller => { @@ -159,12 +156,14 @@ impl CheckAttrVisitor<'_> { | sym::rustc_dirty | sym::rustc_if_this_changed | sym::rustc_then_this_would_need => self.check_rustc_dirty_clean(&attr), - sym::rustc_coinductive => self.check_rustc_coinductive(&attr, span, target), + sym::rustc_coinductive + | sym::rustc_must_implement_one_of + | sym::rustc_deny_explicit_impl + | sym::const_trait => self.check_must_be_applied_to_trait(&attr, span, target), sym::cmse_nonsecure_entry => { self.check_cmse_nonsecure_entry(hir_id, attr, span, target) } sym::collapse_debuginfo => self.check_collapse_debuginfo(attr, span, target), - sym::const_trait => self.check_const_trait(attr, span, target), sym::must_not_suspend => self.check_must_not_suspend(&attr, span, target), sym::must_use => self.check_must_use(hir_id, &attr, target), sym::rustc_pass_by_value => self.check_pass_by_value(&attr, span, target), @@ -184,6 +183,7 @@ impl CheckAttrVisitor<'_> { | sym::rustc_allowed_through_unstable_modules | sym::rustc_promotable => self.check_stability_promotable(&attr, span, target), sym::link_ordinal => self.check_link_ordinal(&attr, span, target), + sym::rustc_confusables => self.check_confusables(&attr, target), _ => true, }; @@ -567,25 +567,6 @@ impl CheckAttrVisitor<'_> { } } - /// Checks if the `#[rustc_must_implement_one_of]` attribute on a `target` is valid. Returns `true` if valid. - fn check_rustc_must_implement_one_of( - &self, - attr: &Attribute, - span: Span, - target: Target, - ) -> bool { - match target { - Target::Trait => true, - _ => { - self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait { - attr_span: attr.span, - defn_span: span, - }); - false - } - } - } - /// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid. fn check_target_feature( &self, @@ -714,7 +695,6 @@ impl CheckAttrVisitor<'_> { | Target::GlobalAsm | Target::TyAlias | Target::OpaqueTy - | Target::ImplTraitPlaceholder | Target::Enum | Target::Variant | Target::Struct @@ -898,6 +878,44 @@ impl CheckAttrVisitor<'_> { } } + fn check_doc_masked( + &self, + attr: &Attribute, + meta: &NestedMetaItem, + hir_id: HirId, + target: Target, + ) -> bool { + if target != Target::ExternCrate { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span(), + errors::DocMaskedOnlyExternCrate { + attr_span: meta.span(), + item_span: (attr.style == AttrStyle::Outer) + .then(|| self.tcx.hir().span(hir_id)), + }, + ); + return false; + } + + if self.tcx.extern_mod_stmt_cnum(hir_id.owner).is_none() { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span(), + errors::DocMaskedNotExternCrateSelf { + attr_span: meta.span(), + item_span: (attr.style == AttrStyle::Outer) + .then(|| self.tcx.hir().span(hir_id)), + }, + ); + return false; + } + + true + } + /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid. fn check_attr_not_crate_level( &self, @@ -944,21 +962,28 @@ impl CheckAttrVisitor<'_> { let mut is_valid = true; if let Some(metas) = meta.meta_item_list() { for i_meta in metas { - match i_meta.name_or_empty() { - sym::attr | sym::no_crate_inject => {} - _ => { + match (i_meta.name_or_empty(), i_meta.meta_item()) { + (sym::attr | sym::no_crate_inject, _) => {} + (_, Some(m)) => { self.tcx.emit_spanned_lint( INVALID_DOC_ATTRIBUTES, hir_id, i_meta.span(), errors::DocTestUnknown { - path: rustc_ast_pretty::pprust::path_to_string( - &i_meta.meta_item().unwrap().path, - ), + path: rustc_ast_pretty::pprust::path_to_string(&m.path), }, ); is_valid = false; } + (_, None) => { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span(), + errors::DocTestLiteral, + ); + is_valid = false; + } } } } else { @@ -1061,6 +1086,17 @@ impl CheckAttrVisitor<'_> { is_valid = false; } + sym::masked + if !self.check_doc_masked( + attr, + meta, + hir_id, + target, + ) => + { + is_valid = false; + } + // no_default_passes: deprecated // passes: deprecated // plugins: removed, but rustdoc warns about it itself @@ -1446,9 +1482,9 @@ impl CheckAttrVisitor<'_> { }; let Some(ItemLike::Item(Item { - kind: ItemKind::Fn(FnSig { decl, .. }, generics, _), - .. - })) = item else { + kind: ItemKind::Fn(FnSig { decl, .. }, generics, _), .. + })) = item + else { bug!("should be a function item"); }; @@ -1584,8 +1620,8 @@ impl CheckAttrVisitor<'_> { } } - /// Checks if the `#[rustc_coinductive]` attribute is applied to a trait. - fn check_rustc_coinductive(&self, attr: &Attribute, span: Span, target: Target) -> bool { + /// Checks if the attribute is applied to a trait. + fn check_must_be_applied_to_trait(&self, attr: &Attribute, span: Span, target: Target) -> bool { match target { Target::Trait => true, _ => { @@ -1979,17 +2015,6 @@ impl CheckAttrVisitor<'_> { } } - /// `#[const_trait]` only applies to traits. - fn check_const_trait(&self, attr: &Attribute, _span: Span, target: Target) -> bool { - match target { - Target::Trait => true, - _ => { - self.tcx.sess.emit_err(errors::ConstTrait { attr_span: attr.span }); - false - } - } - } - fn check_stability_promotable(&self, attr: &Attribute, _span: Span, target: Target) -> bool { match target { Target::Expression => { @@ -2010,6 +2035,46 @@ impl CheckAttrVisitor<'_> { } } + fn check_confusables(&self, attr: &Attribute, target: Target) -> bool { + match target { + Target::Method(MethodKind::Inherent) => { + let Some(meta) = attr.meta() else { + return false; + }; + let ast::MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { + return false; + }; + + let mut candidates = Vec::new(); + + for meta in metas { + let NestedMetaItem::Lit(meta_lit) = meta else { + self.tcx.sess.emit_err(errors::IncorrectMetaItem { + span: meta.span(), + suggestion: errors::IncorrectMetaItemSuggestion { + lo: meta.span().shrink_to_lo(), + hi: meta.span().shrink_to_hi(), + }, + }); + return false; + }; + candidates.push(meta_lit.symbol); + } + + if candidates.is_empty() { + self.tcx.sess.emit_err(errors::EmptyConfusables { span: attr.span }); + return false; + } + + true + } + _ => { + self.tcx.sess.emit_err(errors::Confusables { attr_span: attr.span }); + false + } + } + } + fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) { match target { Target::Closure | Target::Expression | Target::Statement | Target::Arm => { @@ -2068,6 +2133,20 @@ impl CheckAttrVisitor<'_> { ); } } + } else { + // special case when `#[macro_export]` is applied to a macro 2.0 + let (macro_definition, _) = + self.tcx.hir().find(hir_id).unwrap().expect_item().expect_macro(); + let is_decl_macro = !macro_definition.macro_rules; + + if is_decl_macro { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::MacroExport::OnDeclMacro, + ); + } } } @@ -2131,8 +2210,12 @@ impl CheckAttrVisitor<'_> { } let tcx = self.tcx; - let Some(token_stream_def_id) = tcx.get_diagnostic_item(sym::TokenStream) else { return; }; - let Some(token_stream) = tcx.type_of(token_stream_def_id).no_bound_vars() else { return; }; + let Some(token_stream_def_id) = tcx.get_diagnostic_item(sym::TokenStream) else { + return; + }; + let Some(token_stream) = tcx.type_of(token_stream_def_id).no_bound_vars() else { + return; + }; let def_id = hir_id.expect_owner().def_id; let param_env = ty::ParamEnv::empty(); @@ -2141,10 +2224,10 @@ impl CheckAttrVisitor<'_> { let ocx = ObligationCtxt::new(&infcx); let span = tcx.def_span(def_id); - let fresh_substs = infcx.fresh_substs_for_item(span, def_id.to_def_id()); + let fresh_args = infcx.fresh_args_for_item(span, def_id.to_def_id()); let sig = tcx.liberate_late_bound_regions( def_id.to_def_id(), - tcx.fn_sig(def_id).subst(tcx, fresh_substs), + tcx.fn_sig(def_id).instantiate(tcx, fresh_args), ); let mut cause = ObligationCause::misc(span, def_id); @@ -2382,10 +2465,10 @@ fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) } } -fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { let check_attr_visitor = &mut CheckAttrVisitor { tcx, abort: Cell::new(false) }; tcx.hir().visit_item_likes_in_module(module_def_id, check_attr_visitor); - if module_def_id.is_top_level_module() { + if module_def_id.to_local_def_id().is_top_level_module() { check_attr_visitor.check_attributes(CRATE_HIR_ID, DUMMY_SP, Target::Mod, None); check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs()); } diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs index 2357b0aadefbb..8437e9a40e2a2 100644 --- a/compiler/rustc_passes/src/check_const.rs +++ b/compiler/rustc_passes/src/check_const.rs @@ -9,7 +9,7 @@ use rustc_attr as attr; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::intravisit::{self, Visitor}; use rustc_middle::hir::nested_filter; use rustc_middle::query::Providers; @@ -45,7 +45,7 @@ impl NonConstExpr { Self::Loop(ForLoop) | Self::Match(ForLoopDesugar) => &[sym::const_for], - Self::Match(TryDesugar) => &[sym::const_try], + Self::Match(TryDesugar(_)) => &[sym::const_try], // All other expressions are allowed. Self::Loop(Loop | While) | Self::Match(Normal | FormatArgs) => &[], @@ -55,7 +55,7 @@ impl NonConstExpr { } } -fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { let mut vis = CheckConstVisitor::new(tcx); tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis); } @@ -157,10 +157,8 @@ impl<'tcx> CheckConstVisitor<'tcx> { // is a pretty narrow case, however. if tcx.sess.is_nightly_build() { for gate in missing_secondary { - let note = format!( - "add `#![feature({})]` to the crate attributes to enable", - gate, - ); + let note = + format!("add `#![feature({gate})]` to the crate attributes to enable",); err.help(note); } } @@ -199,6 +197,11 @@ impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> { self.recurse_into(kind, None, |this| intravisit::walk_anon_const(this, anon)); } + fn visit_inline_const(&mut self, block: &'tcx hir::ConstBlock) { + let kind = Some(hir::ConstContext::Const); + self.recurse_into(kind, None, |this| intravisit::walk_inline_const(this, block)); + } + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { let owner = self.tcx.hir().body_owner_def_id(body.id()); let kind = self.tcx.hir().body_const_context(owner); diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index 7812dcde44c83..d1c3bcf3839d5 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -4,10 +4,11 @@ use hir::def_id::{LocalDefIdMap, LocalDefIdSet}; use itertools::Itertools; +use rustc_data_structures::unord::UnordSet; use rustc_errors::MultiSpan; use rustc_hir as hir; use rustc_hir::def::{CtorOf, DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Node, PatKind, TyKind}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; @@ -42,8 +43,16 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { ) } +/// Determine if a work from the worklist is coming from the a `#[allow]` +/// or a `#[expect]` of `dead_code` +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum ComesFromAllowExpect { + Yes, + No, +} + struct MarkSymbolVisitor<'tcx> { - worklist: Vec, + worklist: Vec<(LocalDefId, ComesFromAllowExpect)>, tcx: TyCtxt<'tcx>, maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, live_symbols: LocalDefIdSet, @@ -72,7 +81,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { fn check_def_id(&mut self, def_id: DefId) { if let Some(def_id) = def_id.as_local() { if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) { - self.worklist.push(def_id); + self.worklist.push((def_id, ComesFromAllowExpect::No)); } self.live_symbols.insert(def_id); } @@ -87,7 +96,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { fn handle_res(&mut self, res: Res) { match res { - Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias, def_id) => { + Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias { .. }, def_id) => { self.check_def_id(def_id); } _ if self.in_pat => {} @@ -269,14 +278,16 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { } fn mark_live_symbols(&mut self) { - let mut scanned = LocalDefIdSet::default(); - while let Some(id) = self.worklist.pop() { - if !scanned.insert(id) { + let mut scanned = UnordSet::default(); + while let Some(work) = self.worklist.pop() { + if !scanned.insert(work) { continue; } + let (id, comes_from_allow_expect) = work; + // Avoid accessing the HIR for the synthesized associated type generated for RPITITs. - if self.tcx.opt_rpitit_info(id.to_def_id()).is_some() { + if self.tcx.is_impl_trait_in_trait(id.to_def_id()) { self.live_symbols.insert(id); continue; } @@ -286,7 +297,30 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { let id = self.struct_constructors.get(&id).copied().unwrap_or(id); if let Some(node) = self.tcx.hir().find_by_def_id(id) { - self.live_symbols.insert(id); + // When using `#[allow]` or `#[expect]` of `dead_code`, we do a QOL improvement + // by declaring fn calls, statics, ... within said items as live, as well as + // the item itself, although technically this is not the case. + // + // This means that the lint for said items will never be fired. + // + // This doesn't make any difference for the item declared with `#[allow]`, as + // the lint firing will be a nop, as it will be silenced by the `#[allow]` of + // the item. + // + // However, for `#[expect]`, the presence or absence of the lint is relevant, + // so we don't add it to the list of live symbols when it comes from a + // `#[expect]`. This means that we will correctly report an item as live or not + // for the `#[expect]` case. + // + // Note that an item can and will be duplicated on the worklist with different + // `ComesFromAllowExpect`, particulary if it was added from the + // `effective_visibilities` query or from the `#[allow]`/`#[expect]` checks, + // this "duplication" is essential as otherwise a function with `#[expect]` + // called from a `pub fn` may be falsely reported as not live, falsely + // triggering the `unfulfilled_lint_expectations` lint. + if comes_from_allow_expect != ComesFromAllowExpect::Yes { + self.live_symbols.insert(id); + } self.visit_node(node); } } @@ -304,7 +338,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of) && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads) { - let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap().subst_identity(); + let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap().instantiate_identity(); if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind() && let Some(adt_def_id) = adt_def.did().as_local() { @@ -353,7 +387,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { //// This is done to handle the case where, for example, the static //// method of a private type is used, but the type itself is never //// called directly. - let self_ty = self.tcx.type_of(item).subst_identity(); + let self_ty = self.tcx.type_of(item).instantiate_identity(); match *self_ty.kind() { ty::Adt(def, _) => self.check_def_id(def.did()), ty::Foreign(did) => self.check_def_id(did), @@ -500,18 +534,33 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { self.in_pat = in_pat; } + + fn visit_inline_const(&mut self, c: &'tcx hir::ConstBlock) { + // When inline const blocks are used in pattern position, paths + // referenced by it should be considered as used. + let in_pat = mem::replace(&mut self.in_pat, false); + + self.live_symbols.insert(c.def_id); + intravisit::walk_inline_const(self, c); + + self.in_pat = in_pat; + } } -fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { +fn has_allow_dead_code_or_lang_attr( + tcx: TyCtxt<'_>, + def_id: LocalDefId, +) -> Option { fn has_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { tcx.has_attr(def_id, sym::lang) // Stable attribute for #[lang = "panic_impl"] || tcx.has_attr(def_id, sym::panic_handler) } - fn has_allow_dead_code(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + fn has_allow_expect_dead_code(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); - tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0 == lint::Allow + let lint_level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0; + matches!(lint_level, lint::Allow | lint::Expect(_)) } fn has_used_like_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { @@ -526,9 +575,13 @@ fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool } } - has_allow_dead_code(tcx, def_id) - || has_used_like_attr(tcx, def_id) - || has_lang_attr(tcx, def_id) + if has_allow_expect_dead_code(tcx, def_id) { + Some(ComesFromAllowExpect::Yes) + } else if has_used_like_attr(tcx, def_id) || has_lang_attr(tcx, def_id) { + Some(ComesFromAllowExpect::No) + } else { + None + } } // These check_* functions seeds items that @@ -546,21 +599,23 @@ fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool // * Implementations of traits and trait methods fn check_item<'tcx>( tcx: TyCtxt<'tcx>, - worklist: &mut Vec, + worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>, struct_constructors: &mut LocalDefIdMap, id: hir::ItemId, ) { let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id); - if allow_dead_code { - worklist.push(id.owner_id.def_id); + if let Some(comes_from_allow) = allow_dead_code { + worklist.push((id.owner_id.def_id, comes_from_allow)); } match tcx.def_kind(id.owner_id) { DefKind::Enum => { let item = tcx.hir().item(id); if let hir::ItemKind::Enum(ref enum_def, _) = item.kind { - if allow_dead_code { - worklist.extend(enum_def.variants.iter().map(|variant| variant.def_id)); + if let Some(comes_from_allow) = allow_dead_code { + worklist.extend( + enum_def.variants.iter().map(|variant| (variant.def_id, comes_from_allow)), + ); } for variant in enum_def.variants { @@ -572,7 +627,7 @@ fn check_item<'tcx>( } DefKind::Impl { of_trait } => { if of_trait { - worklist.push(id.owner_id.def_id); + worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No)); } // get DefIds from another query @@ -583,8 +638,10 @@ fn check_item<'tcx>( // And we access the Map here to get HirId from LocalDefId for id in local_def_ids { - if of_trait || has_allow_dead_code_or_lang_attr(tcx, id) { - worklist.push(id); + if of_trait { + worklist.push((id, ComesFromAllowExpect::No)); + } else if let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id) { + worklist.push((id, comes_from_allow)); } } } @@ -598,43 +655,59 @@ fn check_item<'tcx>( } DefKind::GlobalAsm => { // global_asm! is always live. - worklist.push(id.owner_id.def_id); + worklist.push((id.owner_id.def_id, ComesFromAllowExpect::No)); } _ => {} } } -fn check_trait_item(tcx: TyCtxt<'_>, worklist: &mut Vec, id: hir::TraitItemId) { +fn check_trait_item( + tcx: TyCtxt<'_>, + worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>, + id: hir::TraitItemId, +) { use hir::TraitItemKind::{Const, Fn}; if matches!(tcx.def_kind(id.owner_id), DefKind::AssocConst | DefKind::AssocFn) { let trait_item = tcx.hir().trait_item(id); if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_))) - && has_allow_dead_code_or_lang_attr(tcx, trait_item.owner_id.def_id) + && let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, trait_item.owner_id.def_id) { - worklist.push(trait_item.owner_id.def_id); + worklist.push((trait_item.owner_id.def_id, comes_from_allow)); } } } -fn check_foreign_item(tcx: TyCtxt<'_>, worklist: &mut Vec, id: hir::ForeignItemId) { +fn check_foreign_item( + tcx: TyCtxt<'_>, + worklist: &mut Vec<(LocalDefId, ComesFromAllowExpect)>, + id: hir::ForeignItemId, +) { if matches!(tcx.def_kind(id.owner_id), DefKind::Static(_) | DefKind::Fn) - && has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id) + && let Some(comes_from_allow) = has_allow_dead_code_or_lang_attr(tcx, id.owner_id.def_id) { - worklist.push(id.owner_id.def_id); + worklist.push((id.owner_id.def_id, comes_from_allow)); } } -fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> (Vec, LocalDefIdMap) { +fn create_and_seed_worklist( + tcx: TyCtxt<'_>, +) -> (Vec<(LocalDefId, ComesFromAllowExpect)>, LocalDefIdMap) { let effective_visibilities = &tcx.effective_visibilities(()); // see `MarkSymbolVisitor::struct_constructors` let mut struct_constructors = Default::default(); let mut worklist = effective_visibilities .iter() .filter_map(|(&id, effective_vis)| { - effective_vis.is_public_at_level(Level::Reachable).then_some(id) + effective_vis + .is_public_at_level(Level::Reachable) + .then_some(id) + .map(|id| (id, ComesFromAllowExpect::No)) }) // Seed entry point - .chain(tcx.entry_fn(()).and_then(|(def_id, _)| def_id.as_local())) + .chain( + tcx.entry_fn(()) + .and_then(|(def_id, _)| def_id.as_local().map(|id| (id, ComesFromAllowExpect::No))), + ) .collect::>(); let crate_items = tcx.hir_crate_items(()); @@ -696,7 +769,7 @@ impl<'tcx> DeadVisitor<'tcx> { if self.live_symbols.contains(&field.did.expect_local()) { return ShouldWarnAboutField::No; } - let field_type = self.tcx.type_of(field.did).subst_identity(); + let field_type = self.tcx.type_of(field.did).instantiate_identity(); if field_type.is_phantom_data() { return ShouldWarnAboutField::No; } @@ -850,7 +923,7 @@ impl<'tcx> DeadVisitor<'tcx> { | DefKind::Fn | DefKind::Static(_) | DefKind::Const - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::Enum | DefKind::Union | DefKind::ForeignTy => self.warn_dead_code(def_id, "used"), @@ -867,13 +940,11 @@ impl<'tcx> DeadVisitor<'tcx> { return true; }; - self.live_symbols.contains(&def_id) - || has_allow_dead_code_or_lang_attr(self.tcx, def_id) - || name.as_str().starts_with('_') + self.live_symbols.contains(&def_id) || name.as_str().starts_with('_') } } -fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalDefId) { +fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) { let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(()); let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits }; @@ -898,7 +969,7 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalDefId) { if !live_symbols.contains(&item.owner_id.def_id) { let parent = tcx.local_parent(item.owner_id.def_id); - if parent != module && !live_symbols.contains(&parent) { + if parent != module.to_local_def_id() && !live_symbols.contains(&parent) { // We already have diagnosed something. continue; } diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs index ffd8f77b78b8f..4f71704b8853d 100644 --- a/compiler/rustc_passes/src/entry.rs +++ b/compiler/rustc_passes/src/entry.rs @@ -31,7 +31,7 @@ struct EntryContext<'tcx> { } fn entry_fn(tcx: TyCtxt<'_>, (): ()) -> Option<(DefId, EntryFnType)> { - let any_exe = tcx.sess.crate_types().iter().any(|ty| *ty == CrateType::Executable); + let any_exe = tcx.crate_types().iter().any(|ty| *ty == CrateType::Executable); if !any_exe { // No need to find a main function. return None; @@ -187,12 +187,6 @@ fn sigpipe(tcx: TyCtxt<'_>, def_id: DefId) -> u8 { fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) { let sp = tcx.def_span(CRATE_DEF_ID); - if tcx.sess.parse_sess.reached_eof.load(rustc_data_structures::sync::Ordering::Relaxed) { - // There's an unclosed brace that made the parser reach `Eof`, we shouldn't complain about - // the missing `fn main()` then as it might have been hidden inside an unclosed block. - tcx.sess.delay_span_bug(sp, "`main` not found, but expected unclosed brace error"); - return; - } // There is no main function. let mut has_filename = true; diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 99fc69d1bec7b..683717344cecc 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -267,6 +267,25 @@ pub struct DocInlineOnlyUse { pub item_span: Option, } +#[derive(LintDiagnostic)] +#[diag(passes_doc_masked_only_extern_crate)] +#[note] +pub struct DocMaskedOnlyExternCrate { + #[label] + pub attr_span: Span, + #[label(passes_not_an_extern_crate_label)] + pub item_span: Option, +} + +#[derive(LintDiagnostic)] +#[diag(passes_doc_masked_not_extern_crate_self)] +pub struct DocMaskedNotExternCrateSelf { + #[label] + pub attr_span: Span, + #[label(passes_extern_crate_self_label)] + pub item_span: Option, +} + #[derive(Diagnostic)] #[diag(passes_doc_attr_not_crate_level)] pub struct DocAttrNotCrateLevel<'a> { @@ -281,6 +300,10 @@ pub struct DocTestUnknown { pub path: String, } +#[derive(LintDiagnostic)] +#[diag(passes_doc_test_literal)] +pub struct DocTestLiteral; + #[derive(LintDiagnostic)] #[diag(passes_doc_test_takes_list)] pub struct DocTestTakesList; @@ -607,19 +630,44 @@ pub struct RustcStdInternalSymbol { } #[derive(Diagnostic)] -#[diag(passes_const_trait)] -pub struct ConstTrait { +#[diag(passes_link_ordinal)] +pub struct LinkOrdinal { #[primary_span] pub attr_span: Span, } #[derive(Diagnostic)] -#[diag(passes_link_ordinal)] -pub struct LinkOrdinal { +#[diag(passes_confusables)] +pub struct Confusables { #[primary_span] pub attr_span: Span, } +#[derive(Diagnostic)] +#[diag(passes_empty_confusables)] +pub(crate) struct EmptyConfusables { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_incorrect_meta_item, code = "E0539")] +pub(crate) struct IncorrectMetaItem { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub suggestion: IncorrectMetaItemSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(passes_incorrect_meta_item_suggestion, applicability = "maybe-incorrect")] +pub(crate) struct IncorrectMetaItemSuggestion { + #[suggestion_part(code = "\"")] + pub lo: Span, + #[suggestion_part(code = "\"")] + pub hi: Span, +} + #[derive(Diagnostic)] #[diag(passes_stability_promotable)] pub struct StabilityPromotable { @@ -642,6 +690,10 @@ pub enum MacroExport { #[diag(passes_macro_export)] Normal, + #[diag(passes_macro_export_on_decl_macro)] + #[note] + OnDeclMacro, + #[diag(passes_invalid_macro_export_arguments)] UnknownItem { name: Symbol }, @@ -1153,14 +1205,6 @@ pub struct UnixSigpipeValues { pub span: Span, } -#[derive(Diagnostic)] -#[diag(passes_no_main_function, code = "E0601")] -pub struct NoMainFunction { - #[primary_span] - pub span: Span, - pub crate_name: String, -} - pub struct NoMainErr { pub sp: Span, pub crate_name: Symbol, diff --git a/compiler/rustc_passes/src/hir_id_validator.rs b/compiler/rustc_passes/src/hir_id_validator.rs index 363e1743677ad..f825363ae39a1 100644 --- a/compiler/rustc_passes/src/hir_id_validator.rs +++ b/compiler/rustc_passes/src/hir_id_validator.rs @@ -89,9 +89,8 @@ impl<'a, 'hir> HirIdValidator<'a, 'hir> { self.error(|| { format!( - "ItemLocalIds not assigned densely in {}. \ - Max ItemLocalId = {}, missing IDs = {:#?}; seen IDs = {:#?}", - pretty_owner, max, missing_items, seen_items + "ItemLocalIds not assigned densely in {pretty_owner}. \ + Max ItemLocalId = {max}, missing IDs = {missing_items:#?}; seen IDs = {seen_items:#?}" ) }); } diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs index dc5e454074ded..5aa8aef6a859a 100644 --- a/compiler/rustc_passes/src/hir_stats.rs +++ b/compiler/rustc_passes/src/hir_stats.rs @@ -126,12 +126,12 @@ impl<'k> StatCollector<'k> { let total_size = nodes.iter().map(|(_, node)| node.stats.count * node.stats.size).sum(); - eprintln!("{} {}", prefix, title); + eprintln!("{prefix} {title}"); eprintln!( "{} {:<18}{:>18}{:>14}{:>14}", prefix, "Name", "Accumulated Size", "Count", "Item Size" ); - eprintln!("{} ----------------------------------------------------------------", prefix); + eprintln!("{prefix} ----------------------------------------------------------------"); let percent = |m, n| (m * 100) as f64 / n as f64; @@ -163,9 +163,9 @@ impl<'k> StatCollector<'k> { } } } - eprintln!("{} ----------------------------------------------------------------", prefix); + eprintln!("{prefix} ----------------------------------------------------------------"); eprintln!("{} {:<18}{:>10}", prefix, "Total", to_readable_str(total_size)); - eprintln!("{}", prefix); + eprintln!("{prefix}"); } } @@ -302,8 +302,8 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { [ ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index, - Path, AddrOf, Break, Continue, Ret, InlineAsm, OffsetOf, Struct, Repeat, Yield, - Err + Path, AddrOf, Break, Continue, Ret, Become, InlineAsm, OffsetOf, Struct, Repeat, + Yield, Err ] ); hir_visit::walk_expr(self, e) @@ -569,7 +569,8 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, - InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err + InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, + Become, IncludedBytes, Err ] ); ast_visit::walk_expr(self, e) diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs index 5a1ae808e661a..a7a8af864ac12 100644 --- a/compiler/rustc_passes/src/layout_test.rs +++ b/compiler/rustc_passes/src/layout_test.rs @@ -16,7 +16,7 @@ pub fn test_layout(tcx: TyCtxt<'_>) { for id in tcx.hir().items() { if matches!( tcx.def_kind(id.owner_id), - DefKind::TyAlias | DefKind::Enum | DefKind::Struct | DefKind::Union + DefKind::TyAlias { .. } | DefKind::Enum | DefKind::Struct | DefKind::Union ) { for attr in tcx.get_attrs(id.owner_id, sym::rustc_layout) { dump_layout_of(tcx, id.owner_id.def_id, attr); @@ -27,9 +27,8 @@ pub fn test_layout(tcx: TyCtxt<'_>) { } fn dump_layout_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) { - let tcx = tcx; let param_env = tcx.param_env(item_def_id); - let ty = tcx.type_of(item_def_id).subst_identity(); + let ty = tcx.type_of(item_def_id).instantiate_identity(); match tcx.layout_of(param_env.and(ty)) { Ok(ty_layout) => { // Check out the `#[rustc_layout(..)]` attribute to tell what to dump. @@ -93,7 +92,8 @@ fn dump_layout_of(tcx: TyCtxt<'_>, item_def_id: LocalDefId, attr: &Attribute) { Err(layout_error) => { tcx.sess.emit_fatal(Spanned { - node: layout_error, + node: layout_error.into_diagnostic(), + span: tcx.def_span(item_def_id.to_def_id()), }); } @@ -109,12 +109,7 @@ impl<'tcx> LayoutOfHelpers<'tcx> for UnwrapLayoutCx<'tcx> { type LayoutOfResult = TyAndLayout<'tcx>; fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! { - span_bug!( - span, - "`#[rustc_layout(..)]` test resulted in `layout_of({}) = Err({})`", - ty, - err - ); + span_bug!(span, "`#[rustc_layout(..)]` test resulted in `layout_of({ty}) = Err({err})`",); } } diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs index 63b1578d43fd7..20e996eaec4c9 100644 --- a/compiler/rustc_passes/src/liveness.rs +++ b/compiler/rustc_passes/src/liveness.rs @@ -463,6 +463,7 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { | hir::ExprKind::Lit(_) | hir::ExprKind::ConstBlock(..) | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Block(..) | hir::ExprKind::Assign(..) | hir::ExprKind::AssignOp(..) @@ -604,7 +605,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { for var_idx in 0..self.ir.var_kinds.len() { let var = Variable::from(var_idx); if test(var) { - write!(wr, " {:?}", var)?; + write!(wr, " {var:?}")?; } } Ok(()) @@ -746,7 +747,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { let ty = self.typeck_results.node_type(hir_id); match ty.kind() { - ty::Closure(_def_id, substs) => match substs.as_closure().kind() { + ty::Closure(_def_id, args) => match args.as_closure().kind() { ty::ClosureKind::Fn => {} ty::ClosureKind::FnMut => {} ty::ClosureKind::FnOnce => return succ, @@ -967,6 +968,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln) } + hir::ExprKind::Become(ref e) => { + // Ignore succ and subst exit_ln. + self.propagate_through_expr(e, self.exit_ln) + } + hir::ExprKind::Break(label, ref opt_expr) => { // Find which label this break jumps to let target = match label.target_id { @@ -1055,7 +1061,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { self.propagate_through_expr(&l, ln) } - hir::ExprKind::Index(ref l, ref r) | hir::ExprKind::Binary(_, ref l, ref r) => { + hir::ExprKind::Index(ref l, ref r, _) | hir::ExprKind::Binary(_, ref l, ref r) => { let r_succ = self.propagate_through_expr(&r, succ); self.propagate_through_expr(&l, r_succ) } @@ -1099,7 +1105,6 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { } // Then do a second pass for inputs - let mut succ = succ; for (op, _op_sp) in asm.operands.iter().rev() { match op { hir::InlineAsmOperand::In { expr, .. } => { @@ -1408,6 +1413,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { | hir::ExprKind::DropTemps(..) | hir::ExprKind::Unary(..) | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Break(..) | hir::ExprKind::Continue(..) | hir::ExprKind::Lit(_) @@ -1676,12 +1682,16 @@ impl<'tcx> Liveness<'_, 'tcx> { opt_body: Option<&hir::Body<'_>>, ) -> Vec { let mut suggs = Vec::new(); - let Some(opt_body) = opt_body else { return suggs; }; + let Some(opt_body) = opt_body else { + return suggs; + }; let mut visitor = CollectLitsVisitor { lit_exprs: vec![] }; intravisit::walk_body(&mut visitor, opt_body); for lit_expr in visitor.lit_exprs { let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue }; - let rustc_ast::LitKind::Str(syb, _) = litx.node else{ continue; }; + let rustc_ast::LitKind::Str(syb, _) = litx.node else { + continue; + }; let name_str: &str = syb.as_str(); let name_pa = format!("{{{name}}}"); if name_str.contains(&name_pa) { diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs index 73cfe68e7f214..0aaf85086e455 100644 --- a/compiler/rustc_passes/src/loops.rs +++ b/compiler/rustc_passes/src/loops.rs @@ -1,7 +1,7 @@ use Context::*; use rustc_hir as hir; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::LocalModDefId; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{Destination, Movability, Node}; use rustc_middle::hir::map::Map; @@ -24,7 +24,7 @@ enum Context { Closure(Span), AsyncClosure(Span), LabeledBlock, - AnonConst, + Constant, } #[derive(Copy, Clone)] @@ -34,7 +34,7 @@ struct CheckLoopVisitor<'a, 'hir> { cx: Context, } -fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { tcx.hir().visit_item_likes_in_module( module_def_id, &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: tcx.hir(), cx: Normal }, @@ -53,7 +53,11 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { } fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) { - self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c)); + self.with_context(Constant, |v| intravisit::walk_anon_const(v, c)); + } + + fn visit_inline_const(&mut self, c: &'hir hir::ConstBlock) { + self.with_context(Constant, |v| intravisit::walk_inline_const(v, c)); } fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) { @@ -192,7 +196,7 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> { AsyncClosure(closure_span) => { self.sess.emit_err(BreakInsideAsyncBlock { span, closure_span, name }); } - Normal | AnonConst => { + Normal | Constant => { self.sess.emit_err(OutsideLoop { span, name, is_break: name == "break" }); } } diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs index a849d61edfeaa..7f36c59ad98d9 100644 --- a/compiler/rustc_passes/src/naked_functions.rs +++ b/compiler/rustc_passes/src/naked_functions.rs @@ -3,7 +3,7 @@ use rustc_ast::InlineAsmOptions; use rustc_hir as hir; use rustc_hir::def::DefKind; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::intravisit::Visitor; use rustc_hir::{ExprKind, InlineAsmOperand, StmtKind}; use rustc_middle::query::Providers; @@ -23,7 +23,7 @@ pub(crate) fn provide(providers: &mut Providers) { *providers = Providers { check_mod_naked_functions, ..*providers }; } -fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { let items = tcx.hir_module_items(module_def_id); for def_id in items.definitions() { if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { @@ -204,6 +204,7 @@ impl<'tcx> CheckInlineAssembly<'tcx> { | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::OffsetOf(..) + | ExprKind::Become(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) => { diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index 160528e4074d4..e62833b358b53 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -98,15 +98,11 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> { self.worklist.push(def_id); } else { match res { - // If this path leads to a constant, then we need to - // recurse into the constant to continue finding - // items that are reachable. - Res::Def(DefKind::Const | DefKind::AssocConst, _) => { + // Reachable constants and reachable statics can have their contents inlined + // into other crates. Mark them as reachable and recurse into their body. + Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::Static(_), _) => { self.worklist.push(def_id); } - - // If this wasn't a static, then the destination is - // surely reachable. _ => { self.reachable_symbols.insert(def_id); } @@ -236,7 +232,7 @@ impl<'tcx> ReachableContext<'tcx> { // Reachable constants will be inlined into other crates // unconditionally, so we need to make sure that their // contents are also reachable. - hir::ItemKind::Const(_, init) | hir::ItemKind::Static(_, _, init) => { + hir::ItemKind::Const(_, _, init) | hir::ItemKind::Static(_, _, init) => { self.visit_nested_body(init); } @@ -364,10 +360,10 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { fn reachable_set(tcx: TyCtxt<'_>, (): ()) -> LocalDefIdSet { let effective_visibilities = &tcx.effective_visibilities(()); - let any_library = - tcx.sess.crate_types().iter().any(|ty| { - *ty == CrateType::Rlib || *ty == CrateType::Dylib || *ty == CrateType::ProcMacro - }); + let any_library = tcx + .crate_types() + .iter() + .any(|ty| *ty == CrateType::Rlib || *ty == CrateType::Dylib || *ty == CrateType::ProcMacro); let mut reachable_context = ReachableContext { tcx, maybe_typeck_results: None, diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index b81b7ad6013b5..9c265e8ec11e5 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -9,7 +9,7 @@ use rustc_attr::{ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; +use rustc_hir::def_id::{LocalDefId, LocalModDefId, CRATE_DEF_ID}; use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant}; @@ -115,7 +115,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { let attrs = self.tcx.hir().attrs(self.tcx.hir().local_def_id_to_hir_id(def_id)); debug!("annotate(id = {:?}, attrs = {:?})", def_id, attrs); - let depr = attr::find_deprecation(&self.tcx.sess, attrs); + let depr = attr::find_deprecation(self.tcx.sess, self.tcx.features(), attrs); let mut is_deprecated = false; if let Some((depr, span)) = &depr { is_deprecated = true; @@ -682,7 +682,7 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index { /// Cross-references the feature names of unstable APIs with enabled /// features and possibly prints errors. -fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { tcx.hir().visit_item_likes_in_module(module_def_id, &mut Checker { tcx }); } @@ -732,13 +732,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> { // For implementations of traits, check the stability of each item // individually as it's possible to have a stable trait with unstable // items. - hir::ItemKind::Impl(hir::Impl { - of_trait: Some(ref t), - self_ty, - items, - constness, - .. - }) => { + hir::ItemKind::Impl(hir::Impl { of_trait: Some(ref t), self_ty, items, .. }) => { let features = self.tcx.features(); if features.staged_api { let attrs = self.tcx.hir().attrs(item.hir_id()); @@ -769,7 +763,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> { // `#![feature(const_trait_impl)]` is unstable, so any impl declared stable // needs to have an error emitted. if features.const_trait_impl - && *constness == hir::Constness::Const + && self.tcx.is_const_trait_impl_raw(item.owner_id.to_def_id()) && const_stab.is_some_and(|(stab, _)| stab.is_const_stable()) { self.tcx.sess.emit_err(errors::TraitImplConstStable { span: item.span }); @@ -856,7 +850,9 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> { /// See issue #94972 for details on why this is a special case fn is_unstable_reexport(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { // Get the LocalDefId so we can lookup the item to check the kind. - let Some(owner) = id.as_owner() else { return false; }; + let Some(owner) = id.as_owner() else { + return false; + }; let def_id = owner.def_id; let Some(stab) = tcx.stability().local_stability(def_id) else { diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs index fc6372cf99ee8..75e071f1fcfed 100644 --- a/compiler/rustc_passes/src/weak_lang_items.rs +++ b/compiler/rustc_passes/src/weak_lang_items.rs @@ -43,7 +43,7 @@ pub fn check_crate(tcx: TyCtxt<'_>, items: &mut lang_items::LanguageItems) { fn verify(tcx: TyCtxt<'_>, items: &lang_items::LanguageItems) { // We only need to check for the presence of weak lang items if we're // emitting something that's not an rlib. - let needs_check = tcx.sess.crate_types().iter().any(|kind| match *kind { + let needs_check = tcx.crate_types().iter().any(|kind| match *kind { CrateType::Dylib | CrateType::ProcMacro | CrateType::Cdylib diff --git a/compiler/rustc_privacy/messages.ftl b/compiler/rustc_privacy/messages.ftl index a26d1b2b381e8..b91e0d18a804f 100644 --- a/compiler/rustc_privacy/messages.ftl +++ b/compiler/rustc_privacy/messages.ftl @@ -2,22 +2,29 @@ privacy_field_is_private = field `{$field_name}` of {$variant_descr} `{$def_path privacy_field_is_private_is_update_syntax_label = field `{$field_name}` is private privacy_field_is_private_label = private field -privacy_item_is_private = {$kind} `{$descr}` is private - .label = private {$kind} -privacy_unnamed_item_is_private = {$kind} is private - .label = private {$kind} +privacy_from_private_dep_in_public_interface = + {$kind} `{$descr}` from private dependency '{$krate}' in public interface privacy_in_public_interface = {$vis_descr} {$kind} `{$descr}` in public interface .label = can't leak {$vis_descr} {$kind} .visibility_label = `{$descr}` declared as {$vis_descr} -privacy_report_effective_visibility = {$descr} - -privacy_from_private_dep_in_public_interface = - {$kind} `{$descr}` from private dependency '{$krate}' in public interface - +privacy_item_is_private = {$kind} `{$descr}` is private + .label = private {$kind} privacy_private_in_public_lint = {$vis_descr} {$kind} `{$descr}` in public interface (error {$kind -> [trait] E0445 *[other] E0446 }) + +privacy_private_interface_or_bounds_lint = {$ty_kind} `{$ty_descr}` is more private than the item `{$item_descr}` + .item_label = {$item_kind} `{$item_descr}` is reachable at visibility `{$item_vis_descr}` + .ty_note = but {$ty_kind} `{$ty_descr}` is only usable at visibility `{$ty_vis_descr}` + +privacy_report_effective_visibility = {$descr} + +privacy_unnameable_types_lint = {$kind} `{$descr}` is reachable but cannot be named + .label = reachable at visibility `{$reachable_vis}`, but can only be named at visibility `{$reexported_vis}` + +privacy_unnamed_item_is_private = {$kind} is private + .label = private {$kind} diff --git a/compiler/rustc_privacy/src/errors.rs b/compiler/rustc_privacy/src/errors.rs index 72b53eefa0817..da18f0c82682e 100644 --- a/compiler/rustc_privacy/src/errors.rs +++ b/compiler/rustc_privacy/src/errors.rs @@ -98,3 +98,32 @@ pub struct PrivateInPublicLint<'a> { pub kind: &'a str, pub descr: DiagnosticArgFromDisplay<'a>, } + +#[derive(LintDiagnostic)] +#[diag(privacy_unnameable_types_lint)] +pub struct UnnameableTypesLint<'a> { + #[label] + pub span: Span, + pub kind: &'a str, + pub descr: DiagnosticArgFromDisplay<'a>, + pub reachable_vis: &'a str, + pub reexported_vis: &'a str, +} + +// Used for `private_interfaces` and `private_bounds` lints. +// They will replace private-in-public errors and compatibility lints in future. +// See https://rust-lang.github.io/rfcs/2145-type-privacy.html for more details. +#[derive(LintDiagnostic)] +#[diag(privacy_private_interface_or_bounds_lint)] +pub struct PrivateInterfacesOrBoundsLint<'a> { + #[label(privacy_item_label)] + pub item_span: Span, + pub item_kind: &'a str, + pub item_descr: DiagnosticArgFromDisplay<'a>, + pub item_vis_descr: &'a str, + #[note(privacy_ty_note)] + pub ty_span: Span, + pub ty_kind: &'a str, + pub ty_descr: DiagnosticArgFromDisplay<'a>, + pub ty_vis_descr: &'a str, +} diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 65dfdf31e5457..0eb344ba69065 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -20,15 +20,15 @@ use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; +use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, CRATE_DEF_ID}; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{AssocItemKind, HirIdSet, ItemId, Node, PatKind}; +use rustc_hir::{AssocItemKind, ForeignItemKind, HirIdSet, ItemId, Node, PatKind}; use rustc_middle::bug; use rustc_middle::hir::nested_filter; use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility, Level}; use rustc_middle::query::Providers; use rustc_middle::span_bug; -use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{self, Const, GenericParamDefKind}; use rustc_middle::ty::{TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_session::lint; @@ -42,8 +42,8 @@ use std::{fmt, mem}; use errors::{ FieldIsPrivate, FieldIsPrivateLabel, FromPrivateDependencyInPublicInterface, InPublicInterface, - InPublicInterfaceTraits, ItemIsPrivate, PrivateInPublicLint, ReportEffectiveVisibility, - UnnamedItemIsPrivate, + InPublicInterfaceTraits, ItemIsPrivate, PrivateInPublicLint, PrivateInterfacesOrBoundsLint, + ReportEffectiveVisibility, UnnameableTypesLint, UnnamedItemIsPrivate, }; fluent_messages! { "../messages.ftl" } @@ -52,6 +52,17 @@ fluent_messages! { "../messages.ftl" } /// Generic infrastructure used to implement specific visitors below. //////////////////////////////////////////////////////////////////////////////// +struct LazyDefPathStr<'tcx> { + def_id: DefId, + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> fmt::Display for LazyDefPathStr<'tcx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.tcx.def_path_str(self.def_id)) + } +} + /// Implemented to visit all `DefId`s in a type. /// Visiting `DefId`s is useful because visibilities and reachabilities are attached to them. /// The idea is to visit "all components of a type", as documented in @@ -62,14 +73,10 @@ fluent_messages! { "../messages.ftl" } /// in `impl Trait`, see individual comments in `DefIdVisitorSkeleton::visit_ty`. trait DefIdVisitor<'tcx> { type BreakTy = (); + const SHALLOW: bool = false; + const SKIP_ASSOC_TYS: bool = false; fn tcx(&self) -> TyCtxt<'tcx>; - fn shallow(&self) -> bool { - false - } - fn skip_assoc_tys(&self) -> bool { - false - } fn visit_def_id( &mut self, def_id: DefId, @@ -101,7 +108,13 @@ trait DefIdVisitor<'tcx> { &mut self, predicates: ty::GenericPredicates<'tcx>, ) -> ControlFlow { - self.skeleton().visit_predicates(predicates) + self.skeleton().visit_clauses(predicates.predicates) + } + fn visit_clauses( + &mut self, + clauses: &[(ty::Clause<'tcx>, Span)], + ) -> ControlFlow { + self.skeleton().visit_clauses(clauses) } } @@ -116,81 +129,44 @@ where V: DefIdVisitor<'tcx> + ?Sized, { fn visit_trait(&mut self, trait_ref: TraitRef<'tcx>) -> ControlFlow { - let TraitRef { def_id, substs, .. } = trait_ref; + let TraitRef { def_id, args, .. } = trait_ref; self.def_id_visitor.visit_def_id(def_id, "trait", &trait_ref.print_only_trait_path())?; - if self.def_id_visitor.shallow() { - ControlFlow::Continue(()) - } else { - substs.visit_with(self) - } + if V::SHALLOW { ControlFlow::Continue(()) } else { args.visit_with(self) } } fn visit_projection_ty(&mut self, projection: ty::AliasTy<'tcx>) -> ControlFlow { let tcx = self.def_id_visitor.tcx(); - let (trait_ref, assoc_substs) = if tcx.def_kind(projection.def_id) - != DefKind::ImplTraitPlaceholder - { - projection.trait_ref_and_own_substs(tcx) - } else { - // HACK(RPITIT): Remove this when RPITITs are lowered to regular assoc tys - let def_id = tcx.impl_trait_in_trait_parent_fn(projection.def_id); - let trait_generics = tcx.generics_of(def_id); - ( - ty::TraitRef::new(tcx, def_id, projection.substs.truncate_to(tcx, trait_generics)), - &projection.substs[trait_generics.count()..], - ) - }; + let (trait_ref, assoc_args) = projection.trait_ref_and_own_args(tcx); self.visit_trait(trait_ref)?; - if self.def_id_visitor.shallow() { + if V::SHALLOW { ControlFlow::Continue(()) } else { - assoc_substs.iter().try_for_each(|subst| subst.visit_with(self)) + assoc_args.iter().try_for_each(|subst| subst.visit_with(self)) } } - fn visit_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ControlFlow { - match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { - trait_ref, - constness: _, - polarity: _, - })) => self.visit_trait(trait_ref), - ty::PredicateKind::Clause(ty::Clause::Projection(ty::ProjectionPredicate { - projection_ty, - term, - })) => { + fn visit_clause(&mut self, clause: ty::Clause<'tcx>) -> ControlFlow { + match clause.kind().skip_binder() { + ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, polarity: _ }) => { + self.visit_trait(trait_ref) + } + ty::ClauseKind::Projection(ty::ProjectionPredicate { projection_ty, term }) => { term.visit_with(self)?; self.visit_projection_ty(projection_ty) } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - ty, - _region, - ))) => ty.visit_with(self), - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) => ControlFlow::Continue(()), - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, _region)) => ty.visit_with(self), + ty::ClauseKind::RegionOutlives(..) => ControlFlow::Continue(()), + ty::ClauseKind::ConstArgHasType(ct, ty) => { ct.visit_with(self)?; ty.visit_with(self) } - ty::PredicateKind::ConstEvaluatable(ct) => ct.visit_with(self), - ty::PredicateKind::WellFormed(arg) => arg.visit_with(self), - - ty::PredicateKind::ObjectSafe(_) - | ty::PredicateKind::ClosureKind(_, _, _) - | ty::PredicateKind::Subtype(_) - | ty::PredicateKind::Coerce(_) - | ty::PredicateKind::ConstEquate(_, _) - | ty::PredicateKind::TypeWellFormedFromEnv(_) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::AliasRelate(..) => bug!("unexpected predicate: {:?}", predicate), + ty::ClauseKind::ConstEvaluatable(ct) => ct.visit_with(self), + ty::ClauseKind::WellFormed(arg) => arg.visit_with(self), } } - fn visit_predicates( - &mut self, - predicates: ty::GenericPredicates<'tcx>, - ) -> ControlFlow { - let ty::GenericPredicates { parent: _, predicates } = predicates; - predicates.iter().try_for_each(|&(predicate, _span)| self.visit_predicate(predicate)) + fn visit_clauses(&mut self, clauses: &[(ty::Clause<'tcx>, Span)]) -> ControlFlow { + clauses.into_iter().try_for_each(|&(clause, _span)| self.visit_clause(clause)) } } @@ -202,7 +178,7 @@ where fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { let tcx = self.def_id_visitor.tcx(); - // InternalSubsts are not visited here because they are visited below + // GenericArgs are not visited here because they are visited below // in `super_visit_with`. match *ty.kind() { ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did: def_id, .. }, _)), ..) @@ -211,28 +187,31 @@ where | ty::Closure(def_id, ..) | ty::Generator(def_id, ..) => { self.def_id_visitor.visit_def_id(def_id, "type", &ty)?; - if self.def_id_visitor.shallow() { + if V::SHALLOW { return ControlFlow::Continue(()); } // Default type visitor doesn't visit signatures of fn types. // Something like `fn() -> Priv {my_func}` is considered a private type even if // `my_func` is public, so we need to visit signatures. if let ty::FnDef(..) = ty.kind() { - // FIXME: this should probably use `substs` from `FnDef` - tcx.fn_sig(def_id).subst_identity().visit_with(self)?; + // FIXME: this should probably use `args` from `FnDef` + tcx.fn_sig(def_id).instantiate_identity().visit_with(self)?; } - // Inherent static methods don't have self type in substs. + // Inherent static methods don't have self type in args. // Something like `fn() {my_method}` type of the method // `impl Pub { pub fn my_method() {} }` is considered a private type, // so we need to visit the self type additionally. if let Some(assoc_item) = tcx.opt_associated_item(def_id) { if let Some(impl_def_id) = assoc_item.impl_container(tcx) { - tcx.type_of(impl_def_id).subst_identity().visit_with(self)?; + tcx.type_of(impl_def_id).instantiate_identity().visit_with(self)?; } } } + ty::Alias(ty::Weak, alias) => { + self.def_id_visitor.visit_def_id(alias.def_id, "type alias", &ty); + } ty::Alias(ty::Projection, proj) => { - if self.def_id_visitor.skip_assoc_tys() { + if V::SKIP_ASSOC_TYS { // Visitors searching for minimal visibility/reachability want to // conservatively approximate associated types like `::Alias` // as visible/reachable even if both `Type` and `Trait` are private. @@ -240,11 +219,11 @@ where // free type aliases, but this isn't done yet. return ControlFlow::Continue(()); } - // This will also visit substs if necessary, so we don't need to recurse. + // This will also visit args if necessary, so we don't need to recurse. return self.visit_projection_ty(proj); } ty::Alias(ty::Inherent, data) => { - if self.def_id_visitor.skip_assoc_tys() { + if V::SKIP_ASSOC_TYS { // Visitors searching for minimal visibility/reachability want to // conservatively approximate associated types like `Type::Alias` // as visible/reachable even if `Type` is private. @@ -259,21 +238,11 @@ where &LazyDefPathStr { def_id: data.def_id, tcx }, )?; - struct LazyDefPathStr<'tcx> { - def_id: DefId, - tcx: TyCtxt<'tcx>, - } - impl<'tcx> fmt::Display for LazyDefPathStr<'tcx> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.tcx.def_path_str(self.def_id)) - } - } - - // This will also visit substs if necessary, so we don't need to recurse. - return if self.def_id_visitor.shallow() { + // This will also visit args if necessary, so we don't need to recurse. + return if V::SHALLOW { ControlFlow::Continue(()) } else { - data.substs.iter().try_for_each(|subst| subst.visit_with(self)) + data.args.iter().try_for_each(|subst| subst.visit_with(self)) }; } ty::Dynamic(predicates, ..) => { @@ -284,10 +253,10 @@ where ty::ExistentialPredicate::Trait(trait_ref) => trait_ref, ty::ExistentialPredicate::Projection(proj) => proj.trait_ref(tcx), ty::ExistentialPredicate::AutoTrait(def_id) => { - ty::ExistentialTraitRef { def_id, substs: InternalSubsts::empty() } + ty::ExistentialTraitRef { def_id, args: GenericArgs::empty() } } }; - let ty::ExistentialTraitRef { def_id, substs: _ } = trait_ref; + let ty::ExistentialTraitRef { def_id, args: _ } = trait_ref; self.def_id_visitor.visit_def_id(def_id, "trait", &trait_ref)?; } } @@ -301,10 +270,7 @@ where // through the trait list (default type visitor doesn't visit those traits). // All traits in the list are considered the "primary" part of the type // and are visited by shallow visitors. - self.visit_predicates(ty::GenericPredicates { - parent: None, - predicates: tcx.explicit_item_bounds(def_id).skip_binder(), - })?; + self.visit_clauses(tcx.explicit_item_bounds(def_id).skip_binder())?; } } // These types don't have their own def-ids (but may have subcomponents @@ -332,11 +298,7 @@ where } } - if self.def_id_visitor.shallow() { - ControlFlow::Continue(()) - } else { - ty.super_visit_with(self) - } + if V::SHALLOW { ControlFlow::Continue(()) } else { ty.super_visit_with(self) } } fn visit_const(&mut self, c: Const<'tcx>) -> ControlFlow { @@ -353,22 +315,20 @@ fn min(vis1: ty::Visibility, vis2: ty::Visibility, tcx: TyCtxt<'_>) -> ty::Visib /// Visitor used to determine impl visibility and reachability. //////////////////////////////////////////////////////////////////////////////// -struct FindMin<'a, 'tcx, VL: VisibilityLike> { +struct FindMin<'a, 'tcx, VL: VisibilityLike, const SHALLOW: bool> { tcx: TyCtxt<'tcx>, effective_visibilities: &'a EffectiveVisibilities, min: VL, } -impl<'a, 'tcx, VL: VisibilityLike> DefIdVisitor<'tcx> for FindMin<'a, 'tcx, VL> { +impl<'a, 'tcx, VL: VisibilityLike, const SHALLOW: bool> DefIdVisitor<'tcx> + for FindMin<'a, 'tcx, VL, SHALLOW> +{ + const SHALLOW: bool = SHALLOW; + const SKIP_ASSOC_TYS: bool = true; fn tcx(&self) -> TyCtxt<'tcx> { self.tcx } - fn shallow(&self) -> bool { - VL::SHALLOW - } - fn skip_assoc_tys(&self) -> bool { - true - } fn visit_def_id( &mut self, def_id: DefId, @@ -384,51 +344,51 @@ impl<'a, 'tcx, VL: VisibilityLike> DefIdVisitor<'tcx> for FindMin<'a, 'tcx, VL> trait VisibilityLike: Sized { const MAX: Self; - const SHALLOW: bool = false; - fn new_min(find: &FindMin<'_, '_, Self>, def_id: LocalDefId) -> Self; + fn new_min( + find: &FindMin<'_, '_, Self, SHALLOW>, + def_id: LocalDefId, + ) -> Self; - // Returns an over-approximation (`skip_assoc_tys` = true) of visibility due to + // Returns an over-approximation (`SKIP_ASSOC_TYS` = true) of visibility due to // associated types for which we can't determine visibility precisely. - fn of_impl( + fn of_impl( def_id: LocalDefId, tcx: TyCtxt<'_>, effective_visibilities: &EffectiveVisibilities, ) -> Self { - let mut find = FindMin { tcx, effective_visibilities, min: Self::MAX }; - find.visit(tcx.type_of(def_id).subst_identity()); + let mut find = FindMin::<_, SHALLOW> { tcx, effective_visibilities, min: Self::MAX }; + find.visit(tcx.type_of(def_id).instantiate_identity()); if let Some(trait_ref) = tcx.impl_trait_ref(def_id) { - find.visit_trait(trait_ref.subst_identity()); + find.visit_trait(trait_ref.instantiate_identity()); } find.min } } impl VisibilityLike for ty::Visibility { const MAX: Self = ty::Visibility::Public; - fn new_min(find: &FindMin<'_, '_, Self>, def_id: LocalDefId) -> Self { + fn new_min( + find: &FindMin<'_, '_, Self, SHALLOW>, + def_id: LocalDefId, + ) -> Self { min(find.tcx.local_visibility(def_id), find.min, find.tcx) } } -impl VisibilityLike for Option { - const MAX: Self = Some(EffectiveVisibility::from_vis(ty::Visibility::Public)); - // Type inference is very smart sometimes. - // It can make an impl reachable even some components of its type or trait are unreachable. - // E.g. methods of `impl ReachableTrait for ReachableTy { ... }` - // can be usable from other crates (#57264). So we skip substs when calculating reachability - // and consider an impl reachable if its "shallow" type and trait are reachable. - // - // The assumption we make here is that type-inference won't let you use an impl without knowing - // both "shallow" version of its self type and "shallow" version of its trait if it exists - // (which require reaching the `DefId`s in them). - const SHALLOW: bool = true; - fn new_min(find: &FindMin<'_, '_, Self>, def_id: LocalDefId) -> Self { - if let Some(min) = find.min { - return find - .effective_visibilities - .effective_vis(def_id) - .map(|eff_vis| min.min(*eff_vis, find.tcx)); - } - None +impl VisibilityLike for EffectiveVisibility { + const MAX: Self = EffectiveVisibility::from_vis(ty::Visibility::Public); + fn new_min( + find: &FindMin<'_, '_, Self, SHALLOW>, + def_id: LocalDefId, + ) -> Self { + let effective_vis = + find.effective_visibilities.effective_vis(def_id).copied().unwrap_or_else(|| { + let private_vis = ty::Visibility::Restricted( + find.tcx.parent_module_from_def_id(def_id).to_local_def_id(), + ); + EffectiveVisibility::from_vis(private_vis) + }); + + effective_vis.min(find.min, find.tcx) } } @@ -453,7 +413,7 @@ struct EmbargoVisitor<'tcx> { /// pub macro m() { /// n::p::f() /// } - macro_reachable: FxHashSet<(LocalDefId, LocalDefId)>, + macro_reachable: FxHashSet<(LocalModDefId, LocalModDefId)>, /// Preliminary pass for marking all underlying types of `impl Trait`s as reachable. impl_trait_pass: bool, /// Has something changed in the level map? @@ -487,14 +447,16 @@ impl<'tcx> EmbargoVisitor<'tcx> { &mut self, def_id: LocalDefId, inherited_effective_vis: EffectiveVisibility, - nominal_vis: Option, + max_vis: Option, level: Level, ) { - let private_vis = ty::Visibility::Restricted(self.tcx.parent_module_from_def_id(def_id)); - if Some(private_vis) != nominal_vis { + // FIXME(typed_def_id): Make `Visibility::Restricted` use a `LocalModDefId` by default. + let private_vis = + ty::Visibility::Restricted(self.tcx.parent_module_from_def_id(def_id).into()); + if max_vis != Some(private_vis) { self.changed |= self.effective_visibilities.update( def_id, - nominal_vis, + max_vis, || private_vis, inherited_effective_vis, level, @@ -549,6 +511,8 @@ impl<'tcx> EmbargoVisitor<'tcx> { // The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252). return; } + // FIXME(typed_def_id): Introduce checked constructors that check def_kind. + let macro_module_def_id = LocalModDefId::new_unchecked(macro_module_def_id); if self.effective_visibilities.public_at_level(local_def_id).is_none() { return; @@ -560,10 +524,10 @@ impl<'tcx> EmbargoVisitor<'tcx> { loop { let changed_reachability = self.update_macro_reachable(module_def_id, macro_module_def_id, macro_ev); - if changed_reachability || module_def_id == CRATE_DEF_ID { + if changed_reachability || module_def_id == LocalModDefId::CRATE_DEF_ID { break; } - module_def_id = self.tcx.local_parent(module_def_id); + module_def_id = LocalModDefId::new_unchecked(self.tcx.local_parent(module_def_id)); } } @@ -571,8 +535,8 @@ impl<'tcx> EmbargoVisitor<'tcx> { /// module. Returns `true` if the level has changed. fn update_macro_reachable( &mut self, - module_def_id: LocalDefId, - defining_mod: LocalDefId, + module_def_id: LocalModDefId, + defining_mod: LocalModDefId, macro_ev: EffectiveVisibility, ) -> bool { if self.macro_reachable.insert((module_def_id, defining_mod)) { @@ -585,8 +549,8 @@ impl<'tcx> EmbargoVisitor<'tcx> { fn update_macro_reachable_mod( &mut self, - module_def_id: LocalDefId, - defining_mod: LocalDefId, + module_def_id: LocalModDefId, + defining_mod: LocalModDefId, macro_ev: EffectiveVisibility, ) { let module = self.tcx.hir().get_module(module_def_id).0; @@ -601,7 +565,7 @@ impl<'tcx> EmbargoVisitor<'tcx> { macro_ev, ); } - for child in self.tcx.module_children_local(module_def_id) { + for child in self.tcx.module_children_local(module_def_id.to_local_def_id()) { // FIXME: Use module children for the logic above too. if !child.reexport_chain.is_empty() && child.vis.is_accessible_from(defining_mod, self.tcx) @@ -618,13 +582,13 @@ impl<'tcx> EmbargoVisitor<'tcx> { def_id: LocalDefId, def_kind: DefKind, vis: ty::Visibility, - module: LocalDefId, + module: LocalModDefId, macro_ev: EffectiveVisibility, ) { self.update(def_id, macro_ev, Level::Reachable); match def_kind { // No type privacy, so can be directly marked as reachable. - DefKind::Const | DefKind::Static(_) | DefKind::TraitAlias | DefKind::TyAlias => { + DefKind::Const | DefKind::Static(_) | DefKind::TraitAlias | DefKind::TyAlias { .. } => { if vis.is_accessible_from(module, self.tcx) { self.update(def_id, macro_ev, Level::Reachable); } @@ -649,7 +613,11 @@ impl<'tcx> EmbargoVisitor<'tcx> { // the module, however may be reachable. DefKind::Mod => { if vis.is_accessible_from(module, self.tcx) { - self.update_macro_reachable(def_id, module, macro_ev); + self.update_macro_reachable( + LocalModDefId::new_unchecked(def_id), + module, + macro_ev, + ); } } @@ -680,7 +648,6 @@ impl<'tcx> EmbargoVisitor<'tcx> { | DefKind::ForeignTy | DefKind::Fn | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::AssocFn | DefKind::Trait | DefKind::TyParam @@ -751,7 +718,7 @@ impl<'tcx> Visitor<'tcx> for EmbargoVisitor<'tcx> { reach.generics().predicates(); if trait_item_ref.kind == AssocItemKind::Type - && !tcx.impl_defaultness(trait_item_ref.id.owner_id).has_value() + && !tcx.defaultness(trait_item_ref.id.owner_id).has_value() { // No type to visit. } else { @@ -766,28 +733,34 @@ impl<'tcx> Visitor<'tcx> for EmbargoVisitor<'tcx> { } } hir::ItemKind::Impl(ref impl_) => { - if let Some(item_ev) = Option::::of_impl( + // Type inference is very smart sometimes. It can make an impl reachable even some + // components of its type or trait are unreachable. E.g. methods of + // `impl ReachableTrait for ReachableTy { ... }` + // can be usable from other crates (#57264). So we skip args when calculating + // reachability and consider an impl reachable if its "shallow" type and trait are + // reachable. + // + // The assumption we make here is that type-inference won't let you use an impl + // without knowing both "shallow" version of its self type and "shallow" version of + // its trait if it exists (which require reaching the `DefId`s in them). + let item_ev = EffectiveVisibility::of_impl::( item.owner_id.def_id, self.tcx, &self.effective_visibilities, - ) { - self.update_eff_vis(item.owner_id.def_id, item_ev, None, Level::Direct); + ); - self.reach(item.owner_id.def_id, item_ev) - .generics() - .predicates() - .ty() - .trait_ref(); + self.update_eff_vis(item.owner_id.def_id, item_ev, None, Level::Direct); - for impl_item_ref in impl_.items { - let def_id = impl_item_ref.id.owner_id.def_id; - let nominal_vis = - impl_.of_trait.is_none().then(|| self.tcx.local_visibility(def_id)); - self.update_eff_vis(def_id, item_ev, nominal_vis, Level::Direct); + self.reach(item.owner_id.def_id, item_ev).generics().predicates().ty().trait_ref(); - if let Some(impl_item_ev) = self.get(def_id) { - self.reach(def_id, impl_item_ev).generics().predicates().ty(); - } + for impl_item_ref in impl_.items { + let def_id = impl_item_ref.id.owner_id.def_id; + let max_vis = + impl_.of_trait.is_none().then(|| self.tcx.local_visibility(def_id)); + self.update_eff_vis(def_id, item_ev, max_vis, Level::Direct); + + if let Some(impl_item_ev) = self.get(def_id) { + self.reach(def_id, impl_item_ev).generics().predicates().ty(); } } } @@ -859,13 +832,15 @@ impl ReachEverythingInTheInterfaceVisitor<'_, '_> { GenericParamDefKind::Lifetime => {} GenericParamDefKind::Type { has_default, .. } => { if has_default { - self.visit(self.ev.tcx.type_of(param.def_id).subst_identity()); + self.visit(self.ev.tcx.type_of(param.def_id).instantiate_identity()); } } GenericParamDefKind::Const { has_default } => { - self.visit(self.ev.tcx.type_of(param.def_id).subst_identity()); + self.visit(self.ev.tcx.type_of(param.def_id).instantiate_identity()); if has_default { - self.visit(self.ev.tcx.const_param_default(param.def_id).subst_identity()); + self.visit( + self.ev.tcx.const_param_default(param.def_id).instantiate_identity(), + ); } } } @@ -879,13 +854,13 @@ impl ReachEverythingInTheInterfaceVisitor<'_, '_> { } fn ty(&mut self) -> &mut Self { - self.visit(self.ev.tcx.type_of(self.item_def_id).subst_identity()); + self.visit(self.ev.tcx.type_of(self.item_def_id).instantiate_identity()); self } fn trait_ref(&mut self) -> &mut Self { if let Some(trait_ref) = self.ev.tcx.impl_trait_ref(self.item_def_id) { - self.visit_trait(trait_ref.subst_identity()); + self.visit_trait(trait_ref.instantiate_identity()); } self } @@ -902,7 +877,12 @@ impl<'tcx> DefIdVisitor<'tcx> for ReachEverythingInTheInterfaceVisitor<'_, 'tcx> _descr: &dyn fmt::Display, ) -> ControlFlow { if let Some(def_id) = def_id.as_local() { - self.ev.update_eff_vis(def_id, self.effective_vis, None, self.level); + // All effective visibilities except `reachable_through_impl_trait` are limited to + // nominal visibility. If any type or trait is leaked farther than that, it will + // produce type privacy errors on any use, so we don't consider it leaked. + let max_vis = (self.level != Level::ReachableThroughImplTrait) + .then(|| self.ev.tcx.local_visibility(def_id)); + self.ev.update_eff_vis(def_id, self.effective_vis, max_vis, self.level); } ControlFlow::Continue(()) } @@ -916,6 +896,21 @@ pub struct TestReachabilityVisitor<'tcx, 'a> { effective_visibilities: &'a EffectiveVisibilities, } +fn vis_to_string<'tcx>(def_id: LocalDefId, vis: ty::Visibility, tcx: TyCtxt<'tcx>) -> String { + match vis { + ty::Visibility::Restricted(restricted_id) => { + if restricted_id.is_top_level_module() { + "pub(crate)".to_string() + } else if restricted_id == tcx.parent_module_from_def_id(def_id).to_local_def_id() { + "pub(self)".to_string() + } else { + format!("pub({})", tcx.item_name(restricted_id.to_def_id())) + } + } + ty::Visibility::Public => "pub".to_string(), + } +} + impl<'tcx, 'a> TestReachabilityVisitor<'tcx, 'a> { fn effective_visibility_diagnostic(&mut self, def_id: LocalDefId) { if self.tcx.has_attr(def_id, sym::rustc_effective_visibility) { @@ -923,18 +918,7 @@ impl<'tcx, 'a> TestReachabilityVisitor<'tcx, 'a> { let span = self.tcx.def_span(def_id.to_def_id()); if let Some(effective_vis) = self.effective_visibilities.effective_vis(def_id) { for level in Level::all_levels() { - let vis_str = match effective_vis.at_level(level) { - ty::Visibility::Restricted(restricted_id) => { - if restricted_id.is_top_level_module() { - "pub(crate)".to_string() - } else if *restricted_id == self.tcx.parent_module_from_def_id(def_id) { - "pub(self)".to_string() - } else { - format!("pub({})", self.tcx.item_name(restricted_id.to_def_id())) - } - } - ty::Visibility::Public => "pub".to_string(), - }; + let vis_str = vis_to_string(def_id, *effective_vis.at_level(level), self.tcx); if level != Level::Direct { error_msg.push_str(", "); } @@ -1151,7 +1135,7 @@ impl<'tcx> TypePrivacyVisitor<'tcx> { let typeck_results = self.typeck_results(); let result: ControlFlow<()> = try { self.visit(typeck_results.node_type(id))?; - self.visit(typeck_results.node_substs(id))?; + self.visit(typeck_results.node_args(id))?; if let Some(adjustments) = typeck_results.adjustments().get(id) { adjustments.iter().try_for_each(|adjustment| self.visit(adjustment.target))?; } @@ -1246,14 +1230,14 @@ impl<'tcx> Visitor<'tcx> for TypePrivacyVisitor<'tcx> { self.tcx.types.never, ); - for (pred, _) in bounds.predicates() { - match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => { + for (clause, _) in bounds.clauses() { + match clause.kind().skip_binder() { + ty::ClauseKind::Trait(trait_predicate) => { if self.visit_trait(trait_predicate.trait_ref).is_break() { return; } } - ty::PredicateKind::Clause(ty::Clause::Projection(proj_predicate)) => { + ty::ClauseKind::Projection(proj_predicate) => { let term = self.visit(proj_predicate.term); if term.is_break() || self.visit_projection_ty(proj_predicate.projection_ty).is_break() @@ -1286,7 +1270,7 @@ impl<'tcx> Visitor<'tcx> for TypePrivacyVisitor<'tcx> { // Method calls have to be checked specially. self.span = segment.ident.span; if let Some(def_id) = self.typeck_results().type_dependent_def_id(expr.hir_id) { - if self.visit(self.tcx.type_of(def_id).subst_identity()).is_break() { + if self.visit(self.tcx.type_of(def_id).instantiate_identity()).is_break() { return; } } else { @@ -1749,23 +1733,26 @@ struct SearchInterfaceForPrivateItemsVisitor<'tcx> { item_def_id: LocalDefId, /// The visitor checks that each component type is at least this visible. required_visibility: ty::Visibility, + required_effective_vis: Option, has_old_errors: bool, in_assoc_ty: bool, + in_primary_interface: bool, } impl SearchInterfaceForPrivateItemsVisitor<'_> { fn generics(&mut self) -> &mut Self { + self.in_primary_interface = true; for param in &self.tcx.generics_of(self.item_def_id).params { match param.kind { GenericParamDefKind::Lifetime => {} GenericParamDefKind::Type { has_default, .. } => { if has_default { - self.visit(self.tcx.type_of(param.def_id).subst_identity()); + self.visit(self.tcx.type_of(param.def_id).instantiate_identity()); } } // FIXME(generic_const_exprs): May want to look inside const here GenericParamDefKind::Const { .. } => { - self.visit(self.tcx.type_of(param.def_id).subst_identity()); + self.visit(self.tcx.type_of(param.def_id).instantiate_identity()); } } } @@ -1773,6 +1760,7 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> { } fn predicates(&mut self) -> &mut Self { + self.in_primary_interface = false; // N.B., we use `explicit_predicates_of` and not `predicates_of` // because we don't want to report privacy errors due to where // clauses that the compiler inferred. We only want to @@ -1784,15 +1772,14 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> { } fn bounds(&mut self) -> &mut Self { - self.visit_predicates(ty::GenericPredicates { - parent: None, - predicates: self.tcx.explicit_item_bounds(self.item_def_id).skip_binder(), - }); + self.in_primary_interface = false; + self.visit_clauses(self.tcx.explicit_item_bounds(self.item_def_id).skip_binder()); self } fn ty(&mut self) -> &mut Self { - self.visit(self.tcx.type_of(self.item_def_id).subst_identity()); + self.in_primary_interface = true; + self.visit(self.tcx.type_of(self.item_def_id).instantiate_identity()); self } @@ -1815,12 +1802,14 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> { }; let vis = self.tcx.local_visibility(local_def_id); + let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id); + let span = self.tcx.def_span(self.item_def_id.to_def_id()); + let vis_span = self.tcx.def_span(def_id); if !vis.is_at_least(self.required_visibility, self.tcx) { - let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id); let vis_descr = match vis { ty::Visibility::Public => "public", ty::Visibility::Restricted(vis_def_id) => { - if vis_def_id == self.tcx.parent_module(hir_id) { + if vis_def_id == self.tcx.parent_module(hir_id).to_local_def_id() { "private" } else if vis_def_id.is_top_level_module() { "crate-private" @@ -1829,12 +1818,11 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> { } } }; - let span = self.tcx.def_span(self.item_def_id.to_def_id()); + if self.has_old_errors || self.in_assoc_ty || self.tcx.resolutions(()).has_pub_restricted { - let vis_span = self.tcx.def_span(def_id); if kind == "trait" { self.tcx.sess.emit_err(InPublicInterfaceTraits { span, @@ -1862,6 +1850,39 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> { } } + let Some(effective_vis) = self.required_effective_vis else { + return false; + }; + + let reachable_at_vis = *effective_vis.at_level(Level::Reachable); + + if !vis.is_at_least(reachable_at_vis, self.tcx) { + let lint = if self.in_primary_interface { + lint::builtin::PRIVATE_INTERFACES + } else { + lint::builtin::PRIVATE_BOUNDS + }; + self.tcx.emit_spanned_lint( + lint, + hir_id, + span, + PrivateInterfacesOrBoundsLint { + item_span: span, + item_kind: self.tcx.def_descr(self.item_def_id.to_def_id()), + item_descr: (&LazyDefPathStr { + def_id: self.item_def_id.to_def_id(), + tcx: self.tcx, + }) + .into(), + item_vis_descr: &vis_to_string(self.item_def_id, reachable_at_vis, self.tcx), + ty_span: vis_span, + ty_kind: kind, + ty_descr: descr.into(), + ty_vis_descr: &vis_to_string(local_def_id, vis, self.tcx), + }, + ); + } + false } @@ -1895,25 +1916,55 @@ impl<'tcx> DefIdVisitor<'tcx> for SearchInterfaceForPrivateItemsVisitor<'tcx> { } } -struct PrivateItemsInPublicInterfacesChecker<'tcx> { +struct PrivateItemsInPublicInterfacesChecker<'tcx, 'a> { tcx: TyCtxt<'tcx>, old_error_set_ancestry: HirIdSet, + effective_visibilities: &'a EffectiveVisibilities, } -impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { +impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx, '_> { fn check( &self, def_id: LocalDefId, required_visibility: ty::Visibility, + required_effective_vis: Option, ) -> SearchInterfaceForPrivateItemsVisitor<'tcx> { SearchInterfaceForPrivateItemsVisitor { tcx: self.tcx, item_def_id: def_id, required_visibility, + required_effective_vis, has_old_errors: self .old_error_set_ancestry .contains(&self.tcx.hir().local_def_id_to_hir_id(def_id)), in_assoc_ty: false, + in_primary_interface: true, + } + } + + fn check_unnameable(&self, def_id: LocalDefId, effective_vis: Option) { + let Some(effective_vis) = effective_vis else { + return; + }; + + let reexported_at_vis = effective_vis.at_level(Level::Reexported); + let reachable_at_vis = effective_vis.at_level(Level::Reachable); + + if reachable_at_vis.is_public() && reexported_at_vis != reachable_at_vis { + let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); + let span = self.tcx.def_span(def_id.to_def_id()); + self.tcx.emit_spanned_lint( + lint::builtin::UNNAMEABLE_TYPES, + hir_id, + span, + UnnameableTypesLint { + span, + kind: self.tcx.def_descr(def_id.to_def_id()), + descr: (&LazyDefPathStr { def_id: def_id.to_def_id(), tcx: self.tcx }).into(), + reachable_vis: &vis_to_string(def_id, *reachable_at_vis, self.tcx), + reexported_vis: &vis_to_string(def_id, *reexported_at_vis, self.tcx), + }, + ); } } @@ -1922,13 +1973,15 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { def_id: LocalDefId, assoc_item_kind: AssocItemKind, vis: ty::Visibility, + effective_vis: Option, ) { - let mut check = self.check(def_id, vis); + let mut check = self.check(def_id, vis, effective_vis); let (check_ty, is_assoc_ty) = match assoc_item_kind { AssocItemKind::Const | AssocItemKind::Fn { .. } => (true, false), - AssocItemKind::Type => (self.tcx.impl_defaultness(def_id).has_value(), true), + AssocItemKind::Type => (self.tcx.defaultness(def_id).has_value(), true), }; + check.in_assoc_ty = is_assoc_ty; check.generics().predicates(); if check_ty { @@ -1936,50 +1989,72 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { } } + fn get(&self, def_id: LocalDefId) -> Option { + self.effective_visibilities.effective_vis(def_id).copied() + } + pub fn check_item(&mut self, id: ItemId) { let tcx = self.tcx; let def_id = id.owner_id.def_id; let item_visibility = tcx.local_visibility(def_id); + let effective_vis = self.get(def_id); let def_kind = tcx.def_kind(def_id); match def_kind { - DefKind::Const | DefKind::Static(_) | DefKind::Fn | DefKind::TyAlias => { - self.check(def_id, item_visibility).generics().predicates().ty(); + DefKind::Const | DefKind::Static(_) | DefKind::Fn | DefKind::TyAlias { .. } => { + if let DefKind::TyAlias { .. } = def_kind { + self.check_unnameable(def_id, effective_vis); + } + self.check(def_id, item_visibility, effective_vis).generics().predicates().ty(); } DefKind::OpaqueTy => { // `ty()` for opaque types is the underlying type, // it's not a part of interface, so we skip it. - self.check(def_id, item_visibility).generics().bounds(); + self.check(def_id, item_visibility, effective_vis).generics().bounds(); } DefKind::Trait => { let item = tcx.hir().item(id); if let hir::ItemKind::Trait(.., trait_item_refs) = item.kind { - self.check(item.owner_id.def_id, item_visibility).generics().predicates(); + self.check_unnameable(item.owner_id.def_id, effective_vis); + + self.check(item.owner_id.def_id, item_visibility, effective_vis) + .generics() + .predicates(); for trait_item_ref in trait_item_refs { self.check_assoc_item( trait_item_ref.id.owner_id.def_id, trait_item_ref.kind, item_visibility, + effective_vis, ); if let AssocItemKind::Type = trait_item_ref.kind { - self.check(trait_item_ref.id.owner_id.def_id, item_visibility).bounds(); + self.check( + trait_item_ref.id.owner_id.def_id, + item_visibility, + effective_vis, + ) + .bounds(); } } } } DefKind::TraitAlias => { - self.check(def_id, item_visibility).generics().predicates(); + self.check(def_id, item_visibility, effective_vis).generics().predicates(); } DefKind::Enum => { let item = tcx.hir().item(id); if let hir::ItemKind::Enum(ref def, _) = item.kind { - self.check(item.owner_id.def_id, item_visibility).generics().predicates(); + self.check_unnameable(item.owner_id.def_id, effective_vis); + + self.check(item.owner_id.def_id, item_visibility, effective_vis) + .generics() + .predicates(); for variant in def.variants { for field in variant.data.fields() { - self.check(field.def_id, item_visibility).ty(); + self.check(field.def_id, item_visibility, effective_vis).ty(); } } } @@ -1989,8 +2064,16 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { let item = tcx.hir().item(id); if let hir::ItemKind::ForeignMod { items, .. } = item.kind { for foreign_item in items { - let vis = tcx.local_visibility(foreign_item.id.owner_id.def_id); - self.check(foreign_item.id.owner_id.def_id, vis) + let foreign_item = tcx.hir().foreign_item(foreign_item.id); + + let ev = self.get(foreign_item.owner_id.def_id); + let vis = tcx.local_visibility(foreign_item.owner_id.def_id); + + if let ForeignItemKind::Type = foreign_item.kind { + self.check_unnameable(foreign_item.owner_id.def_id, ev); + } + + self.check(foreign_item.owner_id.def_id, vis, ev) .generics() .predicates() .ty(); @@ -2003,11 +2086,21 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { if let hir::ItemKind::Struct(ref struct_def, _) | hir::ItemKind::Union(ref struct_def, _) = item.kind { - self.check(item.owner_id.def_id, item_visibility).generics().predicates(); + self.check_unnameable(item.owner_id.def_id, effective_vis); + self.check(item.owner_id.def_id, item_visibility, effective_vis) + .generics() + .predicates(); for field in struct_def.fields() { let field_visibility = tcx.local_visibility(field.def_id); - self.check(field.def_id, min(item_visibility, field_visibility, tcx)).ty(); + let field_ev = self.get(field.def_id); + + self.check( + field.def_id, + min(item_visibility, field_visibility, tcx), + field_ev, + ) + .ty(); } } } @@ -2018,12 +2111,35 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { DefKind::Impl { .. } => { let item = tcx.hir().item(id); if let hir::ItemKind::Impl(ref impl_) = item.kind { - let impl_vis = - ty::Visibility::of_impl(item.owner_id.def_id, tcx, &Default::default()); + let impl_vis = ty::Visibility::of_impl::( + item.owner_id.def_id, + tcx, + &Default::default(), + ); + + // We are using the non-shallow version here, unlike when building the + // effective visisibilities table to avoid large number of false positives. + // For example in + // + // impl From for Pub { + // fn from(_: Priv) -> Pub {...} + // } + // + // lints shouldn't be emmited even if `from` effective visibility + // is larger than `Priv` nominal visibility and if `Priv` can leak + // in some scenarios due to type inference. + let impl_ev = EffectiveVisibility::of_impl::( + item.owner_id.def_id, + tcx, + self.effective_visibilities, + ); + // check that private components do not appear in the generics or predicates of inherent impls // this check is intentionally NOT performed for impls of traits, per #90586 if impl_.of_trait.is_none() { - self.check(item.owner_id.def_id, impl_vis).generics().predicates(); + self.check(item.owner_id.def_id, impl_vis, Some(impl_ev)) + .generics() + .predicates(); } for impl_item_ref in impl_.items { let impl_item_vis = if impl_.of_trait.is_none() { @@ -2035,10 +2151,19 @@ impl<'tcx> PrivateItemsInPublicInterfacesChecker<'tcx> { } else { impl_vis }; + + let impl_item_ev = if impl_.of_trait.is_none() { + self.get(impl_item_ref.id.owner_id.def_id) + .map(|ev| ev.min(impl_ev, self.tcx)) + } else { + Some(impl_ev) + }; + self.check_assoc_item( impl_item_ref.id.owner_id.def_id, impl_item_ref.kind, impl_item_vis, + impl_item_ev, ); } } @@ -2080,7 +2205,7 @@ fn local_visibility(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Visibility { kind: hir::ItemKind::Use(_, hir::UseKind::ListStem) | hir::ItemKind::OpaqueTy(..), .. - }) => ty::Visibility::Restricted(tcx.parent_module(hir_id)), + }) => ty::Visibility::Restricted(tcx.parent_module(hir_id).to_local_def_id()), // Visibilities of trait impl items are inherited from their traits // and are not filled in resolve. Node::ImplItem(impl_item) => { @@ -2108,18 +2233,25 @@ fn local_visibility(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Visibility { } } -fn check_mod_privacy(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { +fn check_mod_privacy(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { // Check privacy of names not checked in previous compilation stages. - let mut visitor = - NamePrivacyVisitor { tcx, maybe_typeck_results: None, current_item: module_def_id }; + let mut visitor = NamePrivacyVisitor { + tcx, + maybe_typeck_results: None, + current_item: module_def_id.to_local_def_id(), + }; let (module, span, hir_id) = tcx.hir().get_module(module_def_id); intravisit::walk_mod(&mut visitor, module, hir_id); // Check privacy of explicitly written types and traits as well as // inferred types of expressions and patterns. - let mut visitor = - TypePrivacyVisitor { tcx, maybe_typeck_results: None, current_item: module_def_id, span }; + let mut visitor = TypePrivacyVisitor { + tcx, + maybe_typeck_results: None, + current_item: module_def_id.to_local_def_id(), + span, + }; intravisit::walk_mod(&mut visitor, module, hir_id); } @@ -2137,7 +2269,7 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities { changed: false, }; - visitor.effective_visibilities.check_invariants(tcx, true); + visitor.effective_visibilities.check_invariants(tcx); if visitor.impl_trait_pass { // Underlying types of `impl Trait`s are marked as reachable unconditionally, // so this pass doesn't need to be a part of the fixed point iteration below. @@ -2154,7 +2286,7 @@ fn effective_visibilities(tcx: TyCtxt<'_>, (): ()) -> &EffectiveVisibilities { break; } } - visitor.effective_visibilities.check_invariants(tcx, false); + visitor.effective_visibilities.check_invariants(tcx); let mut check_visitor = TestReachabilityVisitor { tcx, effective_visibilities: &visitor.effective_visibilities }; @@ -2190,7 +2322,11 @@ fn check_private_in_public(tcx: TyCtxt<'_>, (): ()) { } // Check for private types and traits in public interfaces. - let mut checker = PrivateItemsInPublicInterfacesChecker { tcx, old_error_set_ancestry }; + let mut checker = PrivateItemsInPublicInterfacesChecker { + tcx, + old_error_set_ancestry, + effective_visibilities, + }; for id in tcx.hir().items() { checker.check_item(id); diff --git a/compiler/rustc_query_impl/Cargo.toml b/compiler/rustc_query_impl/Cargo.toml index e596993465c1b..ac697a3ae3e99 100644 --- a/compiler/rustc_query_impl/Cargo.toml +++ b/compiler/rustc_query_impl/Cargo.toml @@ -7,7 +7,6 @@ edition = "2021" [dependencies] -memoffset = { version = "0.6.0", features = ["unstable_const"] } field-offset = "0.3.5" measureme = "10.0.0" rustc_ast = { path = "../rustc_ast" } @@ -25,5 +24,8 @@ rustc_span = { path = "../rustc_span" } thin-vec = "0.2.12" tracing = "0.1" +# Not used directly, but included to enable the unstable_offset_of feature +memoffset = { version = "0.9.0", features = ["unstable_offset_of"] } + [features] rustc_use_parallel_compiler = ["rustc-rayon-core", "rustc_query_system/rustc_use_parallel_compiler"] diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index 4cf0f1305a709..53005ede84378 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -11,6 +11,7 @@ #![allow(rustc::potential_query_instability, unused_parens)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_middle; diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 244f0e84b43d9..def6ac280b804 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -16,7 +16,7 @@ use rustc_middle::query::on_disk_cache::AbsoluteBytePos; use rustc_middle::query::on_disk_cache::{CacheDecoder, CacheEncoder, EncodedDepNodeIndex}; use rustc_middle::query::Key; use rustc_middle::ty::tls::{self, ImplicitCtxt}; -use rustc_middle::ty::{self, TyCtxt}; +use rustc_middle::ty::{self, print::with_no_queries, TyCtxt}; use rustc_query_system::dep_graph::{DepNodeParams, HasDepContext}; use rustc_query_system::ich::StableHashingContext; use rustc_query_system::query::{ @@ -183,7 +183,7 @@ pub(super) fn encode_all_query_results<'tcx>( encoder: &mut CacheEncoder<'_, 'tcx>, query_result_index: &mut EncodedDepNodeIndex, ) { - for encode in super::ENCODE_QUERY_RESULTS.iter().copied().filter_map(|e| e) { + for encode in super::ENCODE_QUERY_RESULTS.iter().copied().flatten() { encode(tcx, encoder, query_result_index); } } @@ -312,7 +312,7 @@ pub(crate) fn create_query_frame< ); let description = if tcx.sess.verbose() { format!("{description} [{name:?}]") } else { description }; - let span = if kind == dep_graph::DepKind::def_span { + let span = if kind == dep_graph::DepKind::def_span || with_no_queries() { // The `def_span` query is used to calculate `default_span`, // so exit to avoid infinite recursion. None @@ -320,7 +320,7 @@ pub(crate) fn create_query_frame< Some(key.default_span(tcx)) }; let def_id = key.key_as_def_id(); - let def_kind = if kind == dep_graph::DepKind::opt_def_kind { + let def_kind = if kind == dep_graph::DepKind::opt_def_kind || with_no_queries() { // Try to avoid infinite recursion. None } else { @@ -531,6 +531,8 @@ macro_rules! define_queries { key: queries::$name::Key<'tcx>, mode: QueryMode, ) -> Option>> { + #[cfg(debug_assertions)] + let _guard = tracing::span!(tracing::Level::TRACE, stringify!($name), ?key).entered(); get_query_incr( QueryType::config(tcx), QueryCtxt::new(tcx), @@ -571,10 +573,16 @@ macro_rules! define_queries { cache_on_disk: |tcx, key| ::rustc_middle::query::cached::$name(tcx, key), execute_query: |tcx, key| erase(tcx.$name(key)), compute: |tcx, key| { + #[cfg(debug_assertions)] + let _guard = tracing::span!(tracing::Level::TRACE, stringify!($name), ?key).entered(); __rust_begin_short_backtrace(|| queries::$name::provided_to_erased( tcx, - call_provider!([$($modifiers)*][tcx, $name, key]) + { + let ret = call_provider!([$($modifiers)*][tcx, $name, key]); + tracing::trace!(?ret); + ret + } ) ) }, diff --git a/compiler/rustc_query_system/Cargo.toml b/compiler/rustc_query_system/Cargo.toml index e02cf38b671d9..584355df80249 100644 --- a/compiler/rustc_query_system/Cargo.toml +++ b/compiler/rustc_query_system/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [lib] [dependencies] -parking_lot = "0.11" +parking_lot = "0.12" rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } diff --git a/compiler/rustc_query_system/messages.ftl b/compiler/rustc_query_system/messages.ftl index 0d01123ad88ba..d5fed8fe179fd 100644 --- a/compiler/rustc_query_system/messages.ftl +++ b/compiler/rustc_query_system/messages.ftl @@ -1,30 +1,31 @@ -query_system_reentrant = internal compiler error: reentrant incremental verify failure, suppressing message - -query_system_increment_compilation = internal compiler error: encountered incremental compilation error with {$dep_node} - .help = This is a known issue with the compiler. Run {$run_cmd} to allow your project to compile - -query_system_increment_compilation_note1 = Please follow the instructions below to create a bug report with the provided information -query_system_increment_compilation_note2 = See for more information - query_system_cycle = cycle detected when {$stack_bottom} + .note = see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information -query_system_cycle_usage = cycle used when {$usage} +query_system_cycle_recursive_trait_alias = trait aliases cannot be recursive -query_system_cycle_stack_single = ...which immediately requires {$stack_bottom} again +query_system_cycle_recursive_ty_alias = type aliases cannot be recursive +query_system_cycle_recursive_ty_alias_help1 = consider using a struct, enum, or union instead to break the cycle +query_system_cycle_recursive_ty_alias_help2 = see for more information query_system_cycle_stack_middle = ...which requires {$desc}... query_system_cycle_stack_multiple = ...which again requires {$stack_bottom}, completing the cycle -query_system_cycle_recursive_ty_alias = type aliases cannot be recursive -query_system_cycle_recursive_ty_alias_help1 = consider using a struct, enum, or union instead to break the cycle -query_system_cycle_recursive_ty_alias_help2 = see for more information +query_system_cycle_stack_single = ...which immediately requires {$stack_bottom} again -query_system_cycle_recursive_trait_alias = trait aliases cannot be recursive +query_system_cycle_usage = cycle used when {$usage} query_system_cycle_which_requires = ...which requires {$desc}... +query_system_increment_compilation = internal compiler error: encountered incremental compilation error with {$dep_node} + .help = This is a known issue with the compiler. Run {$run_cmd} to allow your project to compile + +query_system_increment_compilation_note1 = Please follow the instructions below to create a bug report with the provided information +query_system_increment_compilation_note2 = See for more information + +query_system_layout_of_depth = query depth increased by {$depth} when {$desc} + query_system_query_overflow = queries overflow the depth limit! .help = consider increasing the recursion limit by adding a `#![recursion_limit = "{$suggested_limit}"]` attribute to your crate (`{$crate_name}`) -query_system_layout_of_depth = query depth increased by {$depth} when {$desc} +query_system_reentrant = internal compiler error: reentrant incremental verify failure, suppressing message diff --git a/compiler/rustc_query_system/src/dep_graph/dep_node.rs b/compiler/rustc_query_system/src/dep_graph/dep_node.rs index 9e1ca6ab515d8..39a4cb1b179b4 100644 --- a/compiler/rustc_query_system/src/dep_graph/dep_node.rs +++ b/compiler/rustc_query_system/src/dep_graph/dep_node.rs @@ -46,7 +46,7 @@ use super::{DepContext, DepKind, FingerprintStyle}; use crate::ich::StableHashingContext; use rustc_data_structures::fingerprint::{Fingerprint, PackedFingerprint}; -use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; +use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableOrd, ToStableHashKey}; use rustc_hir::definitions::DefPathHash; use std::fmt; use std::hash::Hash; @@ -247,3 +247,14 @@ impl HashStable for WorkProductId { self.hash.hash_stable(hcx, hasher) } } +impl ToStableHashKey for WorkProductId { + type KeyType = Fingerprint; + #[inline] + fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType { + self.hash + } +} +unsafe impl StableOrd for WorkProductId { + // Fingerprint can use unstable (just a tuple of `u64`s), so WorkProductId can as well + const CAN_USE_UNSTABLE_SORT: bool = true; +} diff --git a/compiler/rustc_query_system/src/dep_graph/graph.rs b/compiler/rustc_query_system/src/dep_graph/graph.rs index c0d7386dd6adb..30422ea110264 100644 --- a/compiler/rustc_query_system/src/dep_graph/graph.rs +++ b/compiler/rustc_query_system/src/dep_graph/graph.rs @@ -1,4 +1,3 @@ -use parking_lot::Mutex; use rustc_data_structures::fingerprint::Fingerprint; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::profiling::{EventId, QueryInvocationId, SelfProfilerRef}; @@ -6,6 +5,7 @@ use rustc_data_structures::sharded::{self, Sharded}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_data_structures::steal::Steal; use rustc_data_structures::sync::{AtomicU32, AtomicU64, Lock, Lrc, Ordering}; +use rustc_data_structures::unord::UnordMap; use rustc_index::IndexVec; use rustc_serialize::opaque::{FileEncodeResult, FileEncoder}; use smallvec::{smallvec, SmallVec}; @@ -87,13 +87,13 @@ pub struct DepGraphData { colors: DepNodeColorMap, - processed_side_effects: Mutex>, + processed_side_effects: Lock>, /// When we load, there may be `.o` files, cached MIR, or other such /// things available to us. If we find that they are not dirty, we /// load the path to the file storing those work-products here into /// this map. We can later look for and extract that data. - previous_work_products: FxHashMap, + previous_work_products: WorkProductMap, dep_node_debug: Lock, String>>, @@ -116,7 +116,7 @@ impl DepGraph { pub fn new( profiler: &SelfProfilerRef, prev_graph: SerializedDepGraph, - prev_work_products: FxHashMap, + prev_work_products: WorkProductMap, encoder: FileEncoder, record_graph: bool, record_stats: bool, @@ -556,7 +556,7 @@ impl DepGraph { result, prev_index, hash_result, - |value| format!("{:?}", value), + |value| format!("{value:?}"), ); #[cfg(debug_assertions)] @@ -688,7 +688,7 @@ impl DepGraph { /// Access the map of work-products created during the cached run. Only /// used during saving of the dep-graph. - pub fn previous_work_products(&self) -> &FxHashMap { + pub fn previous_work_products(&self) -> &WorkProductMap { &self.data.as_ref().unwrap().previous_work_products } @@ -1048,9 +1048,11 @@ pub struct WorkProduct { /// /// By convention, file extensions are currently used as identifiers, i.e. the key "o" maps to /// the object file's path, and "dwo" to the dwarf object file's path. - pub saved_files: FxHashMap, + pub saved_files: UnordMap, } +pub type WorkProductMap = UnordMap; + // Index type for `DepNodeData`'s edges. rustc_index::newtype_index! { struct EdgeIndex {} @@ -1432,7 +1434,7 @@ pub(crate) fn print_markframe_trace( let mut current = frame; while let Some(frame) = current { let node = data.previous.index_to_node(frame.index); - eprintln!("#{i} {:?}", node); + eprintln!("#{i} {node:?}"); current = frame.parent; i += 1; } diff --git a/compiler/rustc_query_system/src/dep_graph/mod.rs b/compiler/rustc_query_system/src/dep_graph/mod.rs index 40e7131987fab..0fd9e35d6dc8d 100644 --- a/compiler/rustc_query_system/src/dep_graph/mod.rs +++ b/compiler/rustc_query_system/src/dep_graph/mod.rs @@ -7,7 +7,7 @@ mod serialized; pub use dep_node::{DepKindStruct, DepNode, DepNodeParams, WorkProductId}; pub use graph::{ hash_result, DepGraph, DepGraphData, DepNodeColor, DepNodeIndex, TaskDeps, TaskDepsRef, - WorkProduct, + WorkProduct, WorkProductMap, }; pub use query::DepGraphQuery; pub use serialized::{SerializedDepGraph, SerializedDepNodeIndex}; diff --git a/compiler/rustc_query_system/src/error.rs b/compiler/rustc_query_system/src/error.rs index cf2f04c7486b8..e49e78cc7c410 100644 --- a/compiler/rustc_query_system/src/error.rs +++ b/compiler/rustc_query_system/src/error.rs @@ -57,6 +57,8 @@ pub struct Cycle { pub alias: Option, #[subdiagnostic] pub cycle_usage: Option, + #[note] + pub note_span: (), } #[derive(Diagnostic)] diff --git a/compiler/rustc_query_system/src/query/caches.rs b/compiler/rustc_query_system/src/query/caches.rs index 9a09f516ec920..4ba9d53a92f75 100644 --- a/compiler/rustc_query_system/src/query/caches.rs +++ b/compiler/rustc_query_system/src/query/caches.rs @@ -1,9 +1,7 @@ use crate::dep_graph::DepNodeIndex; use rustc_data_structures::fx::FxHashMap; -use rustc_data_structures::sharded; -#[cfg(parallel_compiler)] -use rustc_data_structures::sharded::Sharded; +use rustc_data_structures::sharded::{self, Sharded}; use rustc_data_structures::sync::Lock; use rustc_index::{Idx, IndexVec}; use std::fmt::Debug; @@ -37,10 +35,7 @@ impl<'tcx, K: Eq + Hash, V: 'tcx> CacheSelector<'tcx, V> for DefaultCacheSelecto } pub struct DefaultCache { - #[cfg(parallel_compiler)] cache: Sharded>, - #[cfg(not(parallel_compiler))] - cache: Lock>, } impl Default for DefaultCache { @@ -60,10 +55,7 @@ where #[inline(always)] fn lookup(&self, key: &K) -> Option<(V, DepNodeIndex)> { let key_hash = sharded::make_hash(key); - #[cfg(parallel_compiler)] let lock = self.cache.get_shard_by_hash(key_hash).lock(); - #[cfg(not(parallel_compiler))] - let lock = self.cache.lock(); let result = lock.raw_entry().from_key_hashed_nocheck(key_hash, key); if let Some((_, value)) = result { Some(*value) } else { None } @@ -71,29 +63,16 @@ where #[inline] fn complete(&self, key: K, value: V, index: DepNodeIndex) { - #[cfg(parallel_compiler)] let mut lock = self.cache.get_shard_by_value(&key).lock(); - #[cfg(not(parallel_compiler))] - let mut lock = self.cache.lock(); // We may be overwriting another value. This is all right, since the dep-graph // will check that the fingerprint matches. lock.insert(key, (value, index)); } fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)) { - #[cfg(parallel_compiler)] - { - let shards = self.cache.lock_shards(); - for shard in shards.iter() { - for (k, v) in shard.iter() { - f(k, &v.0, v.1); - } - } - } - #[cfg(not(parallel_compiler))] - { - let map = self.cache.lock(); - for (k, v) in map.iter() { + let shards = self.cache.lock_shards(); + for shard in shards.iter() { + for (k, v) in shard.iter() { f(k, &v.0, v.1); } } @@ -151,10 +130,7 @@ impl<'tcx, K: Idx, V: 'tcx> CacheSelector<'tcx, V> for VecCacheSelector { } pub struct VecCache { - #[cfg(parallel_compiler)] cache: Sharded>>, - #[cfg(not(parallel_compiler))] - cache: Lock>>, } impl Default for VecCache { @@ -173,38 +149,20 @@ where #[inline(always)] fn lookup(&self, key: &K) -> Option<(V, DepNodeIndex)> { - #[cfg(parallel_compiler)] let lock = self.cache.get_shard_by_hash(key.index() as u64).lock(); - #[cfg(not(parallel_compiler))] - let lock = self.cache.lock(); if let Some(Some(value)) = lock.get(*key) { Some(*value) } else { None } } #[inline] fn complete(&self, key: K, value: V, index: DepNodeIndex) { - #[cfg(parallel_compiler)] let mut lock = self.cache.get_shard_by_hash(key.index() as u64).lock(); - #[cfg(not(parallel_compiler))] - let mut lock = self.cache.lock(); lock.insert(key, (value, index)); } fn iter(&self, f: &mut dyn FnMut(&Self::Key, &Self::Value, DepNodeIndex)) { - #[cfg(parallel_compiler)] - { - let shards = self.cache.lock_shards(); - for shard in shards.iter() { - for (k, v) in shard.iter_enumerated() { - if let Some(v) = v { - f(&k, &v.0, v.1); - } - } - } - } - #[cfg(not(parallel_compiler))] - { - let map = self.cache.lock(); - for (k, v) in map.iter_enumerated() { + let shards = self.cache.lock_shards(); + for shard in shards.iter() { + for (k, v) in shard.iter_enumerated() { if let Some(v) = v { f(&k, &v.0, v.1); } diff --git a/compiler/rustc_query_system/src/query/job.rs b/compiler/rustc_query_system/src/query/job.rs index f45f7ca5da6dd..1b12489244120 100644 --- a/compiler/rustc_query_system/src/query/job.rs +++ b/compiler/rustc_query_system/src/query/job.rs @@ -13,6 +13,7 @@ use rustc_session::Session; use rustc_span::Span; use std::hash::Hash; +use std::io::Write; use std::num::NonZeroU64; #[cfg(parallel_compiler)] @@ -20,12 +21,11 @@ use { parking_lot::{Condvar, Mutex}, rayon_core, rustc_data_structures::fx::FxHashSet, - rustc_data_structures::sync::Lock, - rustc_data_structures::sync::Lrc, rustc_data_structures::{defer, jobserver}, rustc_span::DUMMY_SP, std::iter, std::process, + std::sync::Arc, }; /// Represents a span and a query key. @@ -190,7 +190,7 @@ struct QueryWaiter { query: Option, condvar: Condvar, span: Span, - cycle: Lock>>, + cycle: Mutex>>, } #[cfg(parallel_compiler)] @@ -204,20 +204,20 @@ impl QueryWaiter { #[cfg(parallel_compiler)] struct QueryLatchInfo { complete: bool, - waiters: Vec>>, + waiters: Vec>>, } #[cfg(parallel_compiler)] #[derive(Clone)] pub(super) struct QueryLatch { - info: Lrc>>, + info: Arc>>, } #[cfg(parallel_compiler)] impl QueryLatch { fn new() -> Self { QueryLatch { - info: Lrc::new(Mutex::new(QueryLatchInfo { complete: false, waiters: Vec::new() })), + info: Arc::new(Mutex::new(QueryLatchInfo { complete: false, waiters: Vec::new() })), } } @@ -228,11 +228,11 @@ impl QueryLatch { span: Span, ) -> Result<(), CycleError> { let waiter = - Lrc::new(QueryWaiter { query, span, cycle: Lock::new(None), condvar: Condvar::new() }); + Arc::new(QueryWaiter { query, span, cycle: Mutex::new(None), condvar: Condvar::new() }); self.wait_on_inner(&waiter); // FIXME: Get rid of this lock. We have ownership of the QueryWaiter - // although another thread may still have a Lrc reference so we cannot - // use Lrc::get_mut + // although another thread may still have a Arc reference so we cannot + // use Arc::get_mut let mut cycle = waiter.cycle.lock(); match cycle.take() { None => Ok(()), @@ -241,7 +241,7 @@ impl QueryLatch { } /// Awaits the caller on this latch by blocking the current thread. - fn wait_on_inner(&self, waiter: &Lrc>) { + fn wait_on_inner(&self, waiter: &Arc>) { let mut info = self.info.lock(); if !info.complete { // We push the waiter on to the `waiters` list. It can be accessed inside @@ -275,7 +275,7 @@ impl QueryLatch { /// Removes a single waiter from the list of waiters. /// This is used to break query cycles. - fn extract_waiter(&self, waiter: usize) -> Lrc> { + fn extract_waiter(&self, waiter: usize) -> Arc> { let mut info = self.info.lock(); debug_assert!(!info.complete); // Remove the waiter from the list of waiters @@ -427,7 +427,7 @@ where fn remove_cycle( query_map: &QueryMap, jobs: &mut Vec, - wakelist: &mut Vec>>, + wakelist: &mut Vec>>, ) -> bool { let mut visited = FxHashSet::default(); let mut stack = Vec::new(); @@ -592,7 +592,10 @@ pub(crate) fn report_cycle<'a, D: DepKind>( }); } - let alias = if stack.iter().all(|entry| entry.query.def_kind == Some(DefKind::TyAlias)) { + let alias = if stack + .iter() + .all(|entry| matches!(entry.query.def_kind, Some(DefKind::TyAlias { .. }))) + { Some(crate::error::Alias::Ty) } else if stack.iter().all(|entry| entry.query.def_kind == Some(DefKind::TraitAlias)) { Some(crate::error::Alias::Trait) @@ -607,6 +610,7 @@ pub(crate) fn report_cycle<'a, D: DepKind>( alias, cycle_usage: cycle_usage, stack_count, + note_span: (), }; cycle_diag.into_diagnostic(&sess.parse_sess.span_diagnostic) @@ -617,30 +621,50 @@ pub fn print_query_stack( mut current_query: Option, handler: &Handler, num_frames: Option, + mut file: Option, ) -> usize { // Be careful relying on global state here: this code is called from // a panic hook, which means that the global `Handler` may be in a weird // state if it was responsible for triggering the panic. - let mut i = 0; + let mut count_printed = 0; + let mut count_total = 0; let query_map = qcx.try_collect_active_jobs(); + if let Some(ref mut file) = file { + let _ = writeln!(file, "\n\nquery stack during panic:"); + } while let Some(query) = current_query { - if Some(i) == num_frames { - break; - } let Some(query_info) = query_map.as_ref().and_then(|map| map.get(&query)) else { break; }; - let mut diag = Diagnostic::new( - Level::FailureNote, - format!("#{} [{:?}] {}", i, query_info.query.dep_kind, query_info.query.description), - ); - diag.span = query_info.job.span.into(); - handler.force_print_diagnostic(diag); + if Some(count_printed) < num_frames || num_frames.is_none() { + // Only print to stderr as many stack frames as `num_frames` when present. + let mut diag = Diagnostic::new( + Level::FailureNote, + format!( + "#{} [{:?}] {}", + count_printed, query_info.query.dep_kind, query_info.query.description + ), + ); + diag.span = query_info.job.span.into(); + handler.force_print_diagnostic(diag); + count_printed += 1; + } + + if let Some(ref mut file) = file { + let _ = writeln!( + file, + "#{} [{:?}] {}", + count_total, query_info.query.dep_kind, query_info.query.description + ); + } current_query = query_info.job.parent; - i += 1; + count_total += 1; } - i + if let Some(ref mut file) = file { + let _ = writeln!(file, "end of query stack"); + } + count_printed } diff --git a/compiler/rustc_query_system/src/query/plumbing.rs b/compiler/rustc_query_system/src/query/plumbing.rs index 730e4c8d30db3..4adb4eb7475be 100644 --- a/compiler/rustc_query_system/src/query/plumbing.rs +++ b/compiler/rustc_query_system/src/query/plumbing.rs @@ -69,6 +69,8 @@ where make_query: fn(Qcx, K) -> QueryStackFrame, jobs: &mut QueryMap, ) -> Option<()> { + let mut active = Vec::new(); + #[cfg(parallel_compiler)] { // We use try_lock_shards here since we are called from the @@ -77,8 +79,7 @@ where for shard in shards.iter() { for (k, v) in shard.iter() { if let QueryResult::Started(ref job) = *v { - let query = make_query(qcx, *k); - jobs.insert(job.id, QueryJobInfo { query, job: job.clone() }); + active.push((*k, job.clone())); } } } @@ -91,12 +92,18 @@ where // really hurt much.) for (k, v) in self.active.try_lock()?.iter() { if let QueryResult::Started(ref job) = *v { - let query = make_query(qcx, *k); - jobs.insert(job.id, QueryJobInfo { query, job: job.clone() }); + active.push((*k, job.clone())); } } } + // Call `make_query` while we're not holding a `self.active` lock as `make_query` may call + // queries leading to a deadlock. + for (key, job) in active { + let query = make_query(qcx, key); + jobs.insert(job.id, QueryJobInfo { query, job }); + } + Some(()) } } @@ -119,18 +126,13 @@ where #[cold] #[inline(never)] -fn mk_cycle( - query: Q, - qcx: Qcx, - cycle_error: CycleError, - handler: HandleCycleError, -) -> Q::Value +fn mk_cycle(query: Q, qcx: Qcx, cycle_error: CycleError) -> Q::Value where Q: QueryConfig, Qcx: QueryContext, { let error = report_cycle(qcx.dep_context().sess(), &cycle_error); - handle_cycle_error(query, qcx, &cycle_error, error, handler) + handle_cycle_error(query, qcx, &cycle_error, error) } fn handle_cycle_error( @@ -138,14 +140,13 @@ fn handle_cycle_error( qcx: Qcx, cycle_error: &CycleError, mut error: DiagnosticBuilder<'_, ErrorGuaranteed>, - handler: HandleCycleError, ) -> Q::Value where Q: QueryConfig, Qcx: QueryContext, { use HandleCycleError::*; - match handler { + match query.handle_cycle_error() { Error => { error.emit(); query.value_from_cycle_error(*qcx.dep_context(), &cycle_error.cycle) @@ -270,7 +271,7 @@ where &qcx.current_query_job(), span, ); - (mk_cycle(query, qcx, error, query.handle_cycle_error()), None) + (mk_cycle(query, qcx, error), None) } #[inline(always)] @@ -307,7 +308,7 @@ where (v, Some(index)) } - Err(cycle) => (mk_cycle(query, qcx, cycle, query.handle_cycle_error()), None), + Err(cycle) => (mk_cycle(query, qcx, cycle), None), } } diff --git a/compiler/rustc_query_system/src/values.rs b/compiler/rustc_query_system/src/values.rs index b6e2cfa3dca5e..ce551078cc028 100644 --- a/compiler/rustc_query_system/src/values.rs +++ b/compiler/rustc_query_system/src/values.rs @@ -6,10 +6,13 @@ pub trait Value: Sized { } impl Value for T { - default fn from_cycle_error(tcx: Tcx, _: &[QueryInfo]) -> T { + default fn from_cycle_error(tcx: Tcx, cycle: &[QueryInfo]) -> T { tcx.sess().abort_if_errors(); // Ideally we would use `bug!` here. But bug! is only defined in rustc_middle, and it's // non-trivial to define it earlier. - panic!("Value::from_cycle_error called without errors"); + panic!( + "<{} as Value>::from_cycle_error called without errors: {cycle:#?}", + std::any::type_name::() + ); } } diff --git a/compiler/rustc_resolve/Cargo.toml b/compiler/rustc_resolve/Cargo.toml index 1c16d85f1b9c2..46da0aa2853ef 100644 --- a/compiler/rustc_resolve/Cargo.toml +++ b/compiler/rustc_resolve/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] bitflags = "1.2.1" -pulldown-cmark = { version = "0.9.2", default-features = false } +pulldown-cmark = { version = "0.9.3", default-features = false } rustc_arena = { path = "../rustc_arena" } rustc_ast = { path = "../rustc_ast" } rustc_ast_pretty = { path = "../rustc_ast_pretty" } diff --git a/compiler/rustc_resolve/messages.ftl b/compiler/rustc_resolve/messages.ftl index 345255c4c6935..f98918cba8800 100644 --- a/compiler/rustc_resolve/messages.ftl +++ b/compiler/rustc_resolve/messages.ftl @@ -1,79 +1,130 @@ -resolve_parent_module_reset_for_binding = - parent module is reset for binding +resolve_accessible_unsure = not sure whether the path is accessible or not + .note = the type may have associated items, but we are currently not checking them + +resolve_add_as_non_derive = + add as non-Derive macro + `#[{$macro_path}]` + +resolve_added_macro_use = + have you added the `#[macro_use]` on the module/import? resolve_ampersand_used_without_explicit_lifetime_name = `&` without an explicit lifetime name cannot be used here .note = explicit lifetime name needed here -resolve_underscore_lifetime_name_cannot_be_used_here = - `'_` cannot be used here - .note = `'_` is a reserved lifetime name +resolve_ancestor_only = + visibilities can only be restricted to ancestor modules -resolve_crate_may_not_be_imported = - `$crate` may not be imported +resolve_associated_const_with_similar_name_exists = + there is an associated constant with a similar name -resolve_crate_root_imports_must_be_named_explicitly = - crate root imports need to be explicitly named: `use crate as name;` +resolve_associated_fn_with_similar_name_exists = + there is an associated function with a similar name -resolve_generic_params_from_outer_function = - can't use generic parameters from outer function - .label = use of generic parameter from outer function - .suggestion = try using a local generic parameter instead +resolve_associated_type_with_similar_name_exists = + there is an associated type with a similar name -resolve_self_type_implicitly_declared_by_impl = - `Self` type implicitly declared here, by this `impl` +resolve_attempt_to_use_non_constant_value_in_constant = + attempt to use a non-constant value in a constant + +resolve_attempt_to_use_non_constant_value_in_constant_label_with_suggestion = + non-constant value + +resolve_attempt_to_use_non_constant_value_in_constant_with_suggestion = + consider using `{$suggestion}` instead of `{$current}` + +resolve_attempt_to_use_non_constant_value_in_constant_without_suggestion = + this would need to be a `{$suggestion}` + +resolve_binding_shadows_something_unacceptable = + {$shadowing_binding}s cannot shadow {$shadowed_binding}s + .label = cannot be named the same as {$article} {$shadowed_binding} + .label_shadowed_binding = the {$shadowed_binding} `{$name}` is {$participle} here + +resolve_binding_shadows_something_unacceptable_suggestion = + try specify the pattern arguments + +resolve_cannot_be_reexported_crate_public = + `{$ident}` is only public within the crate, and cannot be re-exported outside + +resolve_cannot_be_reexported_private = + `{$ident}` is private, and cannot be re-exported + +resolve_cannot_capture_dynamic_environment_in_fn_item = + can't capture dynamic environment in a fn item + .help = use the `|| {"{"} ... {"}"}` closure form instead + +resolve_cannot_determine_import_resolution = + cannot determine resolution for the import + .note = import resolution is stuck, try simplifying other imports + +resolve_cannot_find_ident_in_this_scope = + cannot find {$expected} `{$ident}` in this scope + +resolve_cannot_glob_import_possible_crates = + cannot glob-import all possible crates resolve_cannot_use_self_type_here = can't use `Self` here -resolve_use_a_type_here_instead = - use a type here instead +resolve_change_import_binding = + you can use `as` to change the binding name of the import -resolve_type_param_from_outer_fn = - type parameter from outer function +resolve_consider_adding_a_derive = + consider adding a derive + +resolve_consider_adding_macro_export = + consider adding a `#[macro_export]` to the macro in the imported module + +resolve_consider_declaring_with_pub = + consider declaring type or module `{$ident}` with `pub` + +resolve_consider_marking_as_pub = + consider marking `{$ident}` as `pub` in the imported module + +resolve_const_not_member_of_trait = + const `{$const_}` is not a member of trait `{$trait_}` + .label = not a member of trait `{$trait_}` resolve_const_param_from_outer_fn = const parameter from outer function -resolve_try_using_local_generic_parameter = - try using a local generic parameter instead +resolve_const_param_in_enum_discriminant = + const parameters may not be used in enum discriminant values -resolve_try_adding_local_generic_param_on_method = - try adding a local generic parameter in this method instead +resolve_const_param_in_non_trivial_anon_const = + const parameters may only be used as standalone arguments, i.e. `{$name}` -resolve_help_try_using_local_generic_param = - try using a local generic parameter instead +resolve_const_param_in_ty_of_const_param = + const parameters may not be used in the type of const parameters -resolve_name_is_already_used_as_generic_parameter = - the name `{$name}` is already used for a generic parameter in this item's generic parameters - .label = already used - .first_use_of_name = first use of `{$name}` +resolve_crate_may_not_be_imported = + `$crate` may not be imported -resolve_method_not_member_of_trait = - method `{$method}` is not a member of trait `{$trait_}` - .label = not a member of trait `{$trait_}` +resolve_crate_root_imports_must_be_named_explicitly = + crate root imports need to be explicitly named: `use crate as name;` -resolve_associated_fn_with_similar_name_exists = - there is an associated function with a similar name +resolve_expected_found = + expected module, found {$res} `{$path_str}` + .label = not a module -resolve_type_not_member_of_trait = - type `{$type_}` is not a member of trait `{$trait_}` - .label = not a member of trait `{$trait_}` +resolve_explicit_unsafe_traits = + unsafe traits like `{$ident}` should be implemented explicitly -resolve_associated_type_with_similar_name_exists = - there is an associated type with a similar name +resolve_forward_declared_generic_param = + generic parameters with a default cannot use forward declared identifiers + .label = defaulted generic parameters cannot be forward declared -resolve_const_not_member_of_trait = - const `{$const_}` is not a member of trait `{$trait_}` - .label = not a member of trait `{$trait_}` +resolve_generic_params_from_outer_function = + can't use generic parameters from outer function + .label = use of generic parameter from outer function + .suggestion = try using a local generic parameter instead -resolve_associated_const_with_similar_name_exists = - there is an associated constant with a similar name +resolve_glob_import_doesnt_reexport = + glob import doesn't reexport anything because no candidate is public enough -resolve_variable_bound_with_different_mode = - variable `{$variable_name}` is bound inconsistently across alternatives separated by `|` - .label = bound in different ways - .first_binding_span = first binding +resolve_help_try_using_local_generic_param = + try using a local generic parameter instead resolve_ident_bound_more_than_once_in_parameter_list = identifier `{$identifier}` is bound more than once in this parameter list @@ -83,125 +134,120 @@ resolve_ident_bound_more_than_once_in_same_pattern = identifier `{$identifier}` is bound more than once in the same pattern .label = used in a pattern more than once -resolve_undeclared_label = - use of undeclared label `{$name}` - .label = undeclared label `{$name}` +resolve_imported_crate = `$crate` may not be imported -resolve_label_with_similar_name_reachable = - a label with a similar name is reachable +resolve_imports_cannot_refer_to = + imports cannot refer to {$what} -resolve_try_using_similarly_named_label = - try using similarly named label +resolve_indeterminate = + cannot determine resolution for the visibility -resolve_unreachable_label_with_similar_name_exists = - a label with a similar name exists but is unreachable +resolve_invalid_asm_sym = + invalid `sym` operand + .label = is a local variable + .help = `sym` operands must refer to either a function or a static -resolve_self_import_can_only_appear_once_in_the_list = - `self` import can only appear once in an import list - .label = can only appear once in an import list +resolve_is_not_directly_importable = + `{$target}` is not directly importable + .label = cannot be imported directly -resolve_self_import_only_in_import_list_with_non_empty_prefix = - `self` import can only appear in an import list with a non-empty prefix - .label = can only appear in an import list with a non-empty prefix +resolve_items_in_traits_are_not_importable = + items in traits are not importable -resolve_cannot_capture_dynamic_environment_in_fn_item = - can't capture dynamic environment in a fn item - .help = use the `|| {"{"} ... {"}"}` closure form instead +resolve_label_with_similar_name_reachable = + a label with a similar name is reachable -resolve_attempt_to_use_non_constant_value_in_constant = - attempt to use a non-constant value in a constant +resolve_lifetime_param_in_enum_discriminant = + lifetime parameters may not be used in enum discriminant values -resolve_attempt_to_use_non_constant_value_in_constant_with_suggestion = - consider using `{$suggestion}` instead of `{$current}` +resolve_lifetime_param_in_non_trivial_anon_const = + lifetime parameters may not be used in const expressions -resolve_attempt_to_use_non_constant_value_in_constant_label_with_suggestion = - non-constant value +resolve_lifetime_param_in_ty_of_const_param = + lifetime parameters may not be used in the type of const parameters -resolve_attempt_to_use_non_constant_value_in_constant_without_suggestion = - this would need to be a `{$suggestion}` +resolve_lowercase_self = + attempt to use a non-constant value in a constant + .suggestion = try using `Self` -resolve_self_imports_only_allowed_within = - `self` imports are only allowed within a {"{"} {"}"} list +resolve_macro_expected_found = + expected {$expected}, found {$found} `{$macro_path}` -resolve_self_imports_only_allowed_within_suggestion = - consider importing the module directly +resolve_macro_use_extern_crate_self = `#[macro_use]` is not supported on `extern crate self` -resolve_self_imports_only_allowed_within_multipart_suggestion = - alternatively, use the multi-path `use` syntax to import `self` +resolve_method_not_member_of_trait = + method `{$method}` is not a member of trait `{$trait_}` + .label = not a member of trait `{$trait_}` -resolve_binding_shadows_something_unacceptable = - {$shadowing_binding}s cannot shadow {$shadowed_binding}s - .label = cannot be named the same as {$article} {$shadowed_binding} - .label_shadowed_binding = the {$shadowed_binding} `{$name}` is {$participle} here +resolve_module_only = + visibility must resolve to a module -resolve_binding_shadows_something_unacceptable_suggestion = - try specify the pattern arguments +resolve_name_is_already_used_as_generic_parameter = + the name `{$name}` is already used for a generic parameter in this item's generic parameters + .label = already used + .first_use_of_name = first use of `{$name}` -resolve_forward_declared_generic_param = - generic parameters with a default cannot use forward declared identifiers - .label = defaulted generic parameters cannot be forward declared +resolve_param_in_enum_discriminant = + generic parameters may not be used in enum discriminant values + .label = cannot perform const operation using `{$name}` + +resolve_param_in_non_trivial_anon_const = + generic parameters may not be used in const operations + .label = cannot perform const operation using `{$name}` + +resolve_param_in_non_trivial_anon_const_help = + use `#![feature(generic_const_exprs)]` to allow generic const expressions resolve_param_in_ty_of_const_param = the type of const parameters must not depend on other generic parameters .label = the type must not depend on the parameter `{$name}` -resolve_type_param_in_ty_of_const_param = - type parameters may not be used in the type of const parameters - -resolve_const_param_in_ty_of_const_param = - const parameters may not be used in the type of const parameters - -resolve_lifetime_param_in_ty_of_const_param = - lifetime parameters may not be used in the type of const parameters +resolve_parent_module_reset_for_binding = + parent module is reset for binding -resolve_self_in_generic_param_default = - generic parameters cannot use `Self` in their defaults - .label = `Self` in generic parameter default +resolve_proc_macro_same_crate = can't use a procedural macro from the same crate that defines it + .help = you can define integration tests in a directory named `tests` -resolve_param_in_non_trivial_anon_const = - generic parameters may not be used in const operations - .label = cannot perform const operation using `{$name}` +resolve_reexport_of_crate_public = + re-export of crate public `{$ident}` -resolve_param_in_non_trivial_anon_const_help = - use `#![feature(generic_const_exprs)]` to allow generic const expressions +resolve_reexport_of_private = + re-export of private `{$ident}` -resolve_type_param_in_non_trivial_anon_const = - type parameters may not be used in const expressions +resolve_relative_2018 = + relative paths are not supported in visibilities in 2018 edition or later + .suggestion = try -resolve_const_param_in_non_trivial_anon_const = - const parameters may only be used as standalone arguments, i.e. `{$name}` +resolve_remove_surrounding_derive = + remove from the surrounding `derive()` -resolve_lifetime_param_in_non_trivial_anon_const = - lifetime parameters may not be used in const expressions +resolve_self_import_can_only_appear_once_in_the_list = + `self` import can only appear once in an import list + .label = can only appear once in an import list -resolve_unreachable_label = - use of unreachable label `{$name}` - .label = unreachable label `{$name}` - .label_definition_span = unreachable label defined here - .note = labels are unreachable through functions, closures, async blocks and modules +resolve_self_import_only_in_import_list_with_non_empty_prefix = + `self` import can only appear in an import list with a non-empty prefix + .label = can only appear in an import list with a non-empty prefix -resolve_unreachable_label_suggestion_use_similarly_named = - try using similarly named label +resolve_self_imports_only_allowed_within = + `self` imports are only allowed within a {"{"} {"}"} list -resolve_unreachable_label_similar_name_reachable = - a label with a similar name is reachable +resolve_self_imports_only_allowed_within_multipart_suggestion = + alternatively, use the multi-path `use` syntax to import `self` -resolve_unreachable_label_similar_name_unreachable = - a label with a similar name exists but is also unreachable +resolve_self_imports_only_allowed_within_suggestion = + consider importing the module directly -resolve_trait_impl_mismatch = - item `{$name}` is an associated {$kind}, which doesn't match its trait `{$trait_path}` - .label = does not match trait - .label_trait_item = item in trait +resolve_self_in_generic_param_default = + generic parameters cannot use `Self` in their defaults + .label = `Self` in generic parameter default -resolve_invalid_asm_sym = - invalid `sym` operand - .label = is a local variable - .help = `sym` operands must refer to either a function or a static +resolve_self_type_implicitly_declared_by_impl = + `Self` type implicitly declared here, by this `impl` -resolve_lowercase_self = - attempt to use a non-constant value in a constant - .suggestion = try using `Self` +resolve_tool_module_imported = + cannot use a tool module through an import + .note = the tool module imported here resolve_trait_impl_duplicate = duplicate definitions with name `{$name}`: @@ -209,56 +255,66 @@ resolve_trait_impl_duplicate = .old_span_label = previous definition here .trait_item_span = item in trait -resolve_relative_2018 = - relative paths are not supported in visibilities in 2018 edition or later - .suggestion = try +resolve_trait_impl_mismatch = + item `{$name}` is an associated {$kind}, which doesn't match its trait `{$trait_path}` + .label = does not match trait + .label_trait_item = item in trait -resolve_ancestor_only = - visibilities can only be restricted to ancestor modules +resolve_try_adding_local_generic_param_on_method = + try adding a local generic parameter in this method instead -resolve_expected_found = - expected module, found {$res} `{$path_str}` - .label = not a module +resolve_try_using_local_generic_parameter = + try using a local generic parameter instead -resolve_indeterminate = - cannot determine resolution for the visibility +resolve_try_using_similarly_named_label = + try using similarly named label -resolve_tool_module_imported = - cannot use a tool module through an import - .note = the tool module imported here +resolve_type_not_member_of_trait = + type `{$type_}` is not a member of trait `{$trait_}` + .label = not a member of trait `{$trait_}` -resolve_module_only = - visibility must resolve to a module +resolve_type_param_from_outer_fn = + type parameter from outer function -resolve_macro_expected_found = - expected {$expected}, found {$found} `{$macro_path}` +resolve_type_param_in_enum_discriminant = + type parameters may not be used in enum discriminant values -resolve_remove_surrounding_derive = - remove from the surrounding `derive()` +resolve_type_param_in_non_trivial_anon_const = + type parameters may not be used in const expressions -resolve_add_as_non_derive = - add as non-Derive macro - `#[{$macro_path}]` +resolve_type_param_in_ty_of_const_param = + type parameters may not be used in the type of const parameters -resolve_proc_macro_same_crate = can't use a procedural macro from the same crate that defines it - .help = you can define integration tests in a directory named `tests` +resolve_undeclared_label = + use of undeclared label `{$name}` + .label = undeclared label `{$name}` -resolve_imported_crate = `$crate` may not be imported +resolve_underscore_lifetime_name_cannot_be_used_here = + `'_` cannot be used here + .note = `'_` is a reserved lifetime name -resolve_macro_use_extern_crate_self = `#[macro_use]` is not supported on `extern crate self` +resolve_unreachable_label = + use of unreachable label `{$name}` + .label = unreachable label `{$name}` + .label_definition_span = unreachable label defined here + .note = labels are unreachable through functions, closures, async blocks and modules -resolve_accessible_unsure = not sure whether the path is accessible or not - .note = the type may have associated items, but we are currently not checking them +resolve_unreachable_label_similar_name_reachable = + a label with a similar name is reachable -resolve_param_in_enum_discriminant = - generic parameters may not be used in enum discriminant values - .label = cannot perform const operation using `{$name}` +resolve_unreachable_label_similar_name_unreachable = + a label with a similar name exists but is also unreachable -resolve_type_param_in_enum_discriminant = - type parameters may not be used in enum discriminant values +resolve_unreachable_label_suggestion_use_similarly_named = + try using similarly named label -resolve_const_param_in_enum_discriminant = - const parameters may not be used in enum discriminant values +resolve_unreachable_label_with_similar_name_exists = + a label with a similar name exists but is unreachable -resolve_lifetime_param_in_enum_discriminant = - lifetime parameters may not be used in enum discriminant values +resolve_use_a_type_here_instead = + use a type here instead + +resolve_variable_bound_with_different_mode = + variable `{$variable_name}` is bound inconsistently across alternatives separated by `|` + .label = bound in different ways + .first_binding_span = first binding diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 7277773334513..127bec22c176d 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -6,10 +6,10 @@ //! Imports are also considered items and placed into modules here, but not resolved yet. use crate::def_collector::collect_definitions; -use crate::imports::{Import, ImportKind}; +use crate::imports::{ImportData, ImportKind}; use crate::macros::{MacroRulesBinding, MacroRulesScope, MacroRulesScopeRef}; use crate::Namespace::{self, MacroNS, TypeNS, ValueNS}; -use crate::{errors, BindingKey, MacroData}; +use crate::{errors, BindingKey, MacroData, NameBindingData}; use crate::{Determinacy, ExternPreludeEntry, Finalize, Module, ModuleKind, ModuleOrUniformRoot}; use crate::{NameBinding, NameBindingKind, ParentScope, PathResult, PerNS, ResolutionError}; use crate::{Resolver, ResolverArenas, Segment, ToNameBinding, VisResolutionError}; @@ -31,17 +31,17 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::Span; use std::cell::Cell; -use std::ptr; type Res = def::Res; impl<'a, Id: Into> ToNameBinding<'a> for (Module<'a>, ty::Visibility, Span, LocalExpnId) { - fn to_name_binding(self, arenas: &'a ResolverArenas<'a>) -> &'a NameBinding<'a> { - arenas.alloc_name_binding(NameBinding { + fn to_name_binding(self, arenas: &'a ResolverArenas<'a>) -> NameBinding<'a> { + arenas.alloc_name_binding(NameBindingData { kind: NameBindingKind::Module(self.0), ambiguity: None, + warn_ambiguity: false, vis: self.1.to_def_id(), span: self.2, expansion: self.3, @@ -50,10 +50,11 @@ impl<'a, Id: Into> ToNameBinding<'a> } impl<'a, Id: Into> ToNameBinding<'a> for (Res, ty::Visibility, Span, LocalExpnId) { - fn to_name_binding(self, arenas: &'a ResolverArenas<'a>) -> &'a NameBinding<'a> { - arenas.alloc_name_binding(NameBinding { + fn to_name_binding(self, arenas: &'a ResolverArenas<'a>) -> NameBinding<'a> { + arenas.alloc_name_binding(NameBindingData { kind: NameBindingKind::Res(self.0), ambiguity: None, + warn_ambiguity: false, vis: self.1.to_def_id(), span: self.2, expansion: self.3, @@ -70,8 +71,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { { let binding = def.to_name_binding(self.arenas); let key = self.new_disambiguated_key(ident, ns); - if let Err(old_binding) = self.try_define(parent, key, binding) { - self.report_conflict(parent, ident, ns, old_binding, &binding); + if let Err(old_binding) = self.try_define(parent, key, binding, false) { + self.report_conflict(parent, ident, ns, old_binding, binding); } } @@ -142,8 +143,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { Some(def_id) => self.macro_def_scope(def_id), None => expn_id .as_local() - .and_then(|expn_id| self.ast_transform_scopes.get(&expn_id)) - .unwrap_or(&self.graph_root), + .and_then(|expn_id| self.ast_transform_scopes.get(&expn_id).copied()) + .unwrap_or(self.graph_root), } } @@ -170,7 +171,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { return macro_data.clone(); } - let load_macro_untracked = self.cstore().load_macro_untracked(def_id, &self.tcx.sess); + let load_macro_untracked = self.cstore().load_macro_untracked(def_id, self.tcx); let (ext, macro_rules) = match load_macro_untracked { LoadedMacro::MacroDef(item, edition) => ( Lrc::new(self.compile_macro(&item, edition).0), @@ -277,7 +278,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { }; match self.r.resolve_path( &segments, - Some(TypeNS), + None, parent_scope, finalize.then(|| Finalize::new(id, path.span)), None, @@ -354,7 +355,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { vis: ty::Visibility, ) { let current_module = self.parent_scope.module; - let import = self.r.arenas.alloc_import(Import { + let import = self.r.arenas.alloc_import(ImportData { kind, parent_scope: self.parent_scope, module_path, @@ -378,7 +379,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { if !type_ns_only || ns == TypeNS { let key = BindingKey::new(target, ns); let mut resolution = this.resolution(current_module, key).borrow_mut(); - resolution.add_single_import(import); + resolution.single_imports.insert(import); } }); } @@ -699,7 +700,10 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { // These items live in the type namespace. ItemKind::TyAlias(..) => { - let res = Res::Def(DefKind::TyAlias, def_id); + let res = Res::Def( + DefKind::TyAlias { lazy: self.r.tcx.features().lazy_type_alias }, + def_id, + ); self.r.define(parent, ident, TypeNS, (res, vis, sp, expansion)); } @@ -848,7 +852,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { (used, Some(ModuleOrUniformRoot::Module(module)), binding) }) .unwrap_or((true, None, self.r.dummy_binding)); - let import = self.r.arenas.alloc_import(Import { + let import = self.r.arenas.alloc_import(ImportData { kind: ImportKind::ExternCrate { source: orig_name, target: ident, id: item.id }, root_id: item.id, parent_scope: self.parent_scope, @@ -864,7 +868,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { }); self.r.potentially_unused_imports.push(import); let imported_binding = self.r.import(binding, import); - if ptr::eq(parent, self.r.graph_root) { + if parent == self.r.graph_root { if let Some(entry) = self.r.extern_prelude.get(&ident.normalize_to_macros_2_0()) { if expansion != LocalExpnId::ROOT && orig_name.is_some() @@ -947,10 +951,9 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { DefKind::Struct | DefKind::Union | DefKind::Variant - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::TraitAlias | DefKind::AssocTy, _, @@ -996,12 +999,12 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { fn add_macro_use_binding( &mut self, name: Symbol, - binding: &'a NameBinding<'a>, + binding: NameBinding<'a>, span: Span, allow_shadowing: bool, ) { if self.r.macro_use_prelude.insert(name, binding).is_some() && !allow_shadowing { - let msg = format!("`{}` is already in scope", name); + let msg = format!("`{name}` is already in scope"); let note = "macro-expanded `#[macro_use]`s may not shadow existing macros (see RFC 1560)"; self.r.tcx.sess.struct_span_err(span, msg).note(note).emit(); @@ -1058,7 +1061,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { } let macro_use_import = |this: &Self, span| { - this.r.arenas.alloc_import(Import { + this.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroUse, root_id: item.id, parent_scope: this.parent_scope, @@ -1228,7 +1231,7 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { self.r.set_binding_parent_module(binding, parent_scope.module); self.r.all_macro_rules.insert(ident.name, res); if is_macro_export { - let import = self.r.arenas.alloc_import(Import { + let import = self.r.arenas.alloc_import(ImportData { kind: ImportKind::MacroExport, root_id: item.id, parent_scope: self.parent_scope, diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index dc35c8b176f5c..7dbbd4c34ea7d 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -362,7 +362,7 @@ impl Resolver<'_, '_> { let mut span_snippets = spans .iter() .filter_map(|s| match tcx.sess.source_map().span_to_snippet(*s) { - Ok(s) => Some(format!("`{}`", s)), + Ok(s) => Some(format!("`{s}`")), _ => None, }) .collect::>(); @@ -440,7 +440,7 @@ impl Resolver<'_, '_> { // If we are not in Rust 2018 edition, then we don't make any further // suggestions. - if !tcx.sess.rust_2018() { + if !tcx.sess.at_least_rust_2018() { continue; } diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 8c6ac822a77a7..cd1a9b934cf53 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1,14 +1,12 @@ -use std::ptr; - +use rustc_ast::expand::StrippedCfgItem; use rustc_ast::ptr::P; use rustc_ast::visit::{self, Visitor}; use rustc_ast::{self as ast, Crate, ItemKind, ModKind, NodeId, Path, CRATE_NODE_ID}; +use rustc_ast::{MetaItemKind, NestedMetaItem}; use rustc_ast_pretty::pprust; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{ - pluralize, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, -}; -use rustc_errors::{struct_span_err, SuggestionStyle}; +use rustc_errors::{pluralize, report_ambiguity_error, struct_span_err, SuggestionStyle}; +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan}; use rustc_feature::BUILTIN_ATTRIBUTES; use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, CtorOf, DefKind, NonMacroAttrKind, PerNS}; @@ -17,8 +15,9 @@ use rustc_hir::PrimTy; use rustc_middle::bug; use rustc_middle::ty::TyCtxt; use rustc_session::lint::builtin::ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE; +use rustc_session::lint::builtin::AMBIGUOUS_GLOB_IMPORTS; use rustc_session::lint::builtin::MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS; -use rustc_session::lint::BuiltinLintDiagnostics; +use rustc_session::lint::{AmbiguityErrorDiag, BuiltinLintDiagnostics}; use rustc_session::Session; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; @@ -28,6 +27,10 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{BytePos, Span, SyntaxContext}; use thin_vec::ThinVec; +use crate::errors::{ + AddedMacroUse, ChangeImportBinding, ChangeImportBindingSuggestion, ConsiderAddingADerive, + ExplicitUnsafeTraits, +}; use crate::imports::{Import, ImportKind}; use crate::late::{PatternSource, Rib}; use crate::path_names_to_string; @@ -97,6 +100,7 @@ pub(crate) struct ImportSuggestion { pub descr: &'static str, pub path: Path, pub accessible: bool, + pub via_import: bool, /// An extra note that should be issued if this item is suggested pub note: Option, } @@ -130,13 +134,29 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } for ambiguity_error in &self.ambiguity_errors { - self.report_ambiguity_error(ambiguity_error); + let diag = self.ambiguity_diagnostics(ambiguity_error); + if ambiguity_error.warning { + let NameBindingKind::Import { import, .. } = ambiguity_error.b1.0.kind else { + unreachable!() + }; + self.lint_buffer.buffer_lint_with_diagnostic( + AMBIGUOUS_GLOB_IMPORTS, + import.root_id, + ambiguity_error.ident.span, + diag.msg.to_string(), + BuiltinLintDiagnostics::AmbiguousGlobImports { diag }, + ); + } else { + let mut err = struct_span_err!(self.tcx.sess, diag.span, E0659, "{}", &diag.msg); + report_ambiguity_error(&mut err, diag); + err.emit(); + } } let mut reported_spans = FxHashSet::default(); - for error in &self.privacy_errors { + for error in std::mem::take(&mut self.privacy_errors) { if reported_spans.insert(error.dedup_span) { - self.report_privacy_error(error); + self.report_privacy_error(&error); } } } @@ -175,13 +195,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - pub(crate) fn report_conflict<'b>( + pub(crate) fn report_conflict( &mut self, parent: Module<'_>, ident: Ident, ns: Namespace, - new_binding: &NameBinding<'b>, - old_binding: &NameBinding<'b>, + new_binding: NameBinding<'a>, + old_binding: NameBinding<'a>, ) { // Error on the second of two conflicting names if old_binding.span.lo() > new_binding.span.lo() { @@ -223,7 +243,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { (TypeNS, _) => "type", }; - let msg = format!("the name `{}` is defined multiple times", name); + let msg = format!("the name `{name}` is defined multiple times"); let mut err = match (old_binding.is_extern_crate(), new_binding.is_extern_crate()) { (true, true) => struct_span_err!(self.tcx.sess, span, E0259, "{}", msg), @@ -245,17 +265,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { container )); - err.span_label(span, format!("`{}` re{} here", name, new_participle)); + err.span_label(span, format!("`{name}` re{new_participle} here")); if !old_binding.span.is_dummy() && old_binding.span != span { err.span_label( self.tcx.sess.source_map().guess_head_span(old_binding.span), - format!("previous {} of the {} `{}` here", old_noun, old_kind, name), + format!("previous {old_noun} of the {old_kind} `{name}` here"), ); } // See https://github.com/rust-lang/rust/issues/32354 use NameBindingKind::Import; - let can_suggest = |binding: &NameBinding<'_>, import: &self::Import<'_>| { + let can_suggest = |binding: NameBinding<'_>, import: self::Import<'_>| { !binding.span.is_dummy() && !matches!(import.kind, ImportKind::MacroUse | ImportKind::MacroExport) }; @@ -265,22 +285,22 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { (Import { import: new, .. }, Import { import: old, .. }) if { (new.has_attributes || old.has_attributes) - && can_suggest(old_binding, old) - && can_suggest(new_binding, new) + && can_suggest(old_binding, *old) + && can_suggest(new_binding, *new) } => { if old.has_attributes { - Some((new, new_binding.span, true)) + Some((*new, new_binding.span, true)) } else { - Some((old, old_binding.span, true)) + Some((*old, old_binding.span, true)) } } // Otherwise prioritize the new binding. - (Import { import, .. }, other) if can_suggest(new_binding, import) => { - Some((import, new_binding.span, other.is_import())) + (Import { import, .. }, other) if can_suggest(new_binding, *import) => { + Some((*import, new_binding.span, other.is_import())) } - (other, Import { import, .. }) if can_suggest(old_binding, import) => { - Some((import, old_binding.span, other.is_import())) + (other, Import { import, .. }) if can_suggest(old_binding, *import) => { + Some((*import, old_binding.span, other.is_import())) } _ => None, }; @@ -334,19 +354,19 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { &self, err: &mut Diagnostic, name: Symbol, - import: &Import<'_>, + import: Import<'_>, binding_span: Span, ) { let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() { - format!("Other{}", name) + format!("Other{name}") } else { - format!("other_{}", name) + format!("other_{name}") }; let mut suggestion = None; match import.kind { ImportKind::Single { type_ns_only: true, .. } => { - suggestion = Some(format!("self as {}", suggested_name)) + suggestion = Some(format!("self as {suggested_name}")) } ImportKind::Single { source, .. } => { if let Some(pos) = @@ -374,16 +394,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { _ => unreachable!(), } - let rename_msg = "you can use `as` to change the binding name of the import"; if let Some(suggestion) = suggestion { - err.span_suggestion( - binding_span, - rename_msg, - suggestion, - Applicability::MaybeIncorrect, - ); + err.subdiagnostic(ChangeImportBindingSuggestion { span: binding_span, suggestion }); } else { - err.span_label(binding_span, rename_msg); + err.subdiagnostic(ChangeImportBinding { span: binding_span }); } } @@ -412,7 +426,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { fn add_suggestion_for_duplicate_nested_use( &self, err: &mut Diagnostic, - import: &Import<'_>, + import: Import<'_>, binding_span: Span, ) { assert!(import.is_nested()); @@ -454,7 +468,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { &mut self, finalize: Option, path: &[Segment], - second_binding: Option<&NameBinding<'_>>, + second_binding: Option>, ) { let Some(Finalize { node_id, root_span, .. }) = finalize else { return; @@ -588,11 +602,11 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let sugg_msg = "try using a local generic parameter instead"; let name = self.tcx.item_name(def_id); let (span, snippet) = if span.is_empty() { - let snippet = format!("<{}>", name); + let snippet = format!("<{name}>"); (span, snippet) } else { let span = sm.span_through_char(span, '<').shrink_to_hi(); - let snippet = format!("{}, ", name); + let snippet = format!("{name}, "); (span, snippet) }; // Suggest the modification to the user @@ -653,7 +667,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { name, ); for sp in target_sp { - err.span_label(sp, format!("pattern doesn't bind `{}`", name)); + err.span_label(sp, format!("pattern doesn't bind `{name}`")); } for sp in origin_sp { err.span_label(sp, "variable not in all patterns"); @@ -680,8 +694,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if import_suggestions.is_empty() { let help_msg = format!( "if you meant to match on a variant or a `const` item, consider \ - making the path in the pattern qualified: `path::to::ModOrType::{}`", - name, + making the path in the pattern qualified: `path::to::ModOrType::{name}`", ); err.span_help(span, help_msg); } @@ -776,7 +789,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { .tcx .sess .create_err(errs::SelfImportOnlyInImportListWithNonEmptyPrefix { span }), - ResolutionError::FailedToResolve { label, suggestion } => { + ResolutionError::FailedToResolve { last_segment, label, suggestion, module } => { let mut err = struct_span_err!(self.tcx.sess, span, E0433, "failed to resolve: {}", &label); err.span_label(span, label); @@ -789,6 +802,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { err.multipart_suggestion(msg, suggestions, applicability); } + if let Some(ModuleOrUniformRoot::Module(module)) = module + && let Some(module) = module.opt_def_id() + && let Some(last_segment) = last_segment + { + self.find_cfg_stripped(&mut err, &last_segment, module); + } + err } ResolutionError::CannotCaptureDynamicEnvironmentInFnItem => { @@ -932,8 +952,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let mut err = self.tcx.sess.struct_span_err_with_code( span, format!( - "item `{}` is an associated {}, which doesn't match its trait `{}`", - name, kind, trait_path, + "item `{name}` is an associated {kind}, which doesn't match its trait `{trait_path}`", ), code, ); @@ -971,9 +990,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { VisResolutionError::AncestorOnly(span) => { self.tcx.sess.create_err(errs::AncestorOnly(span)) } - VisResolutionError::FailedToResolve(span, label, suggestion) => { - self.into_struct_error(span, ResolutionError::FailedToResolve { label, suggestion }) - } + VisResolutionError::FailedToResolve(span, label, suggestion) => self.into_struct_error( + span, + ResolutionError::FailedToResolve { + last_segment: None, + label, + suggestion, + module: None, + }, + ), VisResolutionError::ExpectedFound(span, path_str, res) => { self.tcx.sess.create_err(errs::ExpectedFound { span, res, path_str }) } @@ -1184,14 +1209,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { // avoid suggesting anything with a hygienic name if ident.name == lookup_ident.name && ns == namespace - && !ptr::eq(in_module, parent_scope.module) + && in_module != parent_scope.module && !ident.span.normalize_to_macros_2_0().from_expansion() { let res = name_binding.res(); if filter_fn(res) { // create the path let mut segms = path_segments.clone(); - if lookup_ident.span.rust_2018() { + if lookup_ident.span.at_least_rust_2018() { // crate-local absolute paths start with `crate::` in edition 2018 // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) segms.insert(0, ast::PathSegment::from_ident(crate_name)); @@ -1243,6 +1268,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { path, accessible: child_accessible, note, + via_import, }); } } @@ -1255,7 +1281,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { path_segments.push(ast::PathSegment::from_ident(ident)); let is_extern_crate_that_also_appears_in_prelude = - name_binding.is_extern_crate() && lookup_ident.span.rust_2018(); + name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); if !is_extern_crate_that_also_appears_in_prelude { // add the module to the lookup @@ -1302,7 +1328,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { &filter_fn, ); - if lookup_ident.span.rust_2018() { + if lookup_ident.span.at_least_rust_2018() { let extern_prelude_names = self.extern_prelude.clone(); for (ident, _) in extern_prelude_names.into_iter() { if ident.span.from_expansion() { @@ -1337,6 +1363,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { macro_kind: MacroKind, parent_scope: &ParentScope<'a>, ident: Ident, + krate: &Crate, ) { let is_expected = &|res: Res| res.macro_kind() == Some(macro_kind); let suggestion = self.early_lookup_typo_candidate( @@ -1349,25 +1376,28 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let import_suggestions = self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected); + let (span, found_use) = match parent_scope.module.nearest_parent_mod().as_local() { + Some(def_id) => UsePlacementFinder::check(krate, self.def_id_to_node_id[def_id]), + None => (None, FoundUse::No), + }; show_candidates( self.tcx, err, - None, + span, &import_suggestions, Instead::No, - FoundUse::Yes, + found_use, DiagnosticMode::Normal, vec![], "", ); if macro_kind == MacroKind::Derive && (ident.name == sym::Send || ident.name == sym::Sync) { - let msg = format!("unsafe traits like `{}` should be implemented explicitly", ident); - err.span_note(ident.span, msg); + err.subdiagnostic(ExplicitUnsafeTraits { span: ident.span, ident }); return; } if self.macro_names.contains(&ident.normalize_to_macros_2_0()) { - err.help("have you added the `#[macro_use]` on the module/import?"); + err.subdiagnostic(AddedMacroUse); return; } if ident.name == kw::Default @@ -1376,19 +1406,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let span = self.def_span(def_id); let source_map = self.tcx.sess.source_map(); let head_span = source_map.guess_head_span(span); - if let Ok(head) = source_map.span_to_snippet(head_span) { - err.span_suggestion(head_span, "consider adding a derive", format!("#[derive(Default)]\n{head}"), Applicability::MaybeIncorrect); - } else { - err.span_help( - head_span, - "consider adding `#[derive(Default)]` to this enum", - ); - } + err.subdiagnostic(ConsiderAddingADerive { + span: head_span.shrink_to_lo(), + suggestion: "#[derive(Default)]\n".to_string(), + }); } for ns in [Namespace::MacroNS, Namespace::TypeNS, Namespace::ValueNS] { if let Ok(binding) = self.early_resolve_ident_in_lexical_scope( ident, - ScopeSet::All(ns, false), + ScopeSet::All(ns), &parent_scope, None, false, @@ -1399,10 +1425,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { "a function-like macro".to_string() } Res::Def(DefKind::Macro(MacroKind::Attr), _) | Res::NonMacroAttr(..) => { - format!("an attribute: `#[{}]`", ident) + format!("an attribute: `#[{ident}]`") } Res::Def(DefKind::Macro(MacroKind::Derive), _) => { - format!("a derive macro: `#[derive({})]`", ident) + format!("a derive macro: `#[derive({ident})]`") } Res::ToolMod => { // Don't confuse the user with tool modules. @@ -1423,7 +1449,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if !import.span.is_dummy() { err.span_note( import.span, - format!("`{}` is imported here, but it is {}", ident, desc), + format!("`{ident}` is imported here, but it is {desc}"), ); // Silence the 'unused import' warning we might get, // since this diagnostic already covers that import. @@ -1431,7 +1457,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { return; } } - err.note(format!("`{}` is in scope, but it is {}", ident, desc)); + err.note(format!("`{ident}` is in scope, but it is {desc}")); return; } } @@ -1500,7 +1526,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { true } - fn binding_description(&self, b: &NameBinding<'_>, ident: Ident, from_prelude: bool) -> String { + fn binding_description(&self, b: NameBinding<'_>, ident: Ident, from_prelude: bool) -> String { let res = b.res(); if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) { // These already contain the "built-in" prefix or look bad with it. @@ -1527,20 +1553,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - fn report_ambiguity_error(&self, ambiguity_error: &AmbiguityError<'_>) { - let AmbiguityError { kind, ident, b1, b2, misc1, misc2 } = *ambiguity_error; + fn ambiguity_diagnostics(&self, ambiguity_error: &AmbiguityError<'_>) -> AmbiguityErrorDiag { + let AmbiguityError { kind, ident, b1, b2, misc1, misc2, .. } = *ambiguity_error; let (b1, b2, misc1, misc2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() { // We have to print the span-less alternative first, otherwise formatting looks bad. (b2, b1, misc2, misc1, true) } else { (b1, b2, misc1, misc2, false) }; - - let mut err = struct_span_err!(self.tcx.sess, ident.span, E0659, "`{ident}` is ambiguous"); - err.span_label(ident.span, "ambiguous name"); - err.note(format!("ambiguous because of {}", kind.descr())); - - let mut could_refer_to = |b: &NameBinding<'_>, misc: AmbiguityErrorMisc, also: &str| { + let could_refer_to = |b: NameBinding<'_>, misc: AmbiguityErrorMisc, also: &str| { let what = self.binding_description(b, ident, misc == AmbiguityErrorMisc::FromPrelude); let note_msg = format!("`{ident}` could{also} refer to {what}"); @@ -1555,7 +1576,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { "consider adding an explicit import of `{ident}` to disambiguate" )) } - if b.is_extern_crate() && ident.span.rust_2018() { + if b.is_extern_crate() && ident.span.at_least_rust_2018() { help_msgs.push(format!("use `::{ident}` to refer to this {thing} unambiguously")) } match misc { @@ -1566,21 +1587,40 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { AmbiguityErrorMisc::FromPrelude | AmbiguityErrorMisc::None => {} } - err.span_note(b.span, note_msg); - for (i, help_msg) in help_msgs.iter().enumerate() { - let or = if i == 0 { "" } else { "or " }; - err.help(format!("{}{}", or, help_msg)); - } + ( + b.span, + note_msg, + help_msgs + .iter() + .enumerate() + .map(|(i, help_msg)| { + let or = if i == 0 { "" } else { "or " }; + format!("{or}{help_msg}") + }) + .collect::>(), + ) }; - - could_refer_to(b1, misc1, ""); - could_refer_to(b2, misc2, " also"); - err.emit(); + let (b1_span, b1_note_msg, b1_help_msgs) = could_refer_to(b1, misc1, ""); + let (b2_span, b2_note_msg, b2_help_msgs) = could_refer_to(b2, misc2, " also"); + + AmbiguityErrorDiag { + msg: format!("`{ident}` is ambiguous"), + span: ident.span, + label_span: ident.span, + label_msg: "ambiguous name".to_string(), + note_msg: format!("ambiguous because of {}", kind.descr()), + b1_span, + b1_note_msg, + b1_help_msgs, + b2_span, + b2_note_msg, + b2_help_msgs, + } } /// If the binding refers to a tuple struct constructor with fields, /// returns the span of its fields. - fn ctor_fields_span(&self, binding: &NameBinding<'_>) -> Option { + fn ctor_fields_span(&self, binding: NameBinding<'_>) -> Option { if let NameBindingKind::Res(Res::Def( DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), ctor_def_id, @@ -1596,8 +1636,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { None } - fn report_privacy_error(&self, privacy_error: &PrivacyError<'_>) { - let PrivacyError { ident, binding, .. } = *privacy_error; + fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'a>) { + let PrivacyError { ident, binding, outermost_res, parent_scope, dedup_span } = + *privacy_error; let res = binding.res(); let ctor_fields_span = self.ctor_fields_span(binding); @@ -1606,13 +1647,40 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if ctor_fields_span.is_some() { plain_descr + " constructor" } else { plain_descr }; let import_descr = nonimport_descr.clone() + " import"; let get_descr = - |b: &NameBinding<'_>| if b.is_import() { &import_descr } else { &nonimport_descr }; + |b: NameBinding<'_>| if b.is_import() { &import_descr } else { &nonimport_descr }; // Print the primary message. let descr = get_descr(binding); let mut err = struct_span_err!(self.tcx.sess, ident.span, E0603, "{} `{}` is private", descr, ident); - err.span_label(ident.span, format!("private {}", descr)); + err.span_label(ident.span, format!("private {descr}")); + + if let Some((this_res, outer_ident)) = outermost_res { + let import_suggestions = self.lookup_import_candidates( + outer_ident, + this_res.ns().unwrap_or(Namespace::TypeNS), + &parent_scope, + &|res: Res| res == this_res, + ); + let point_to_def = !show_candidates( + self.tcx, + &mut err, + Some(dedup_span.until(outer_ident.span.shrink_to_hi())), + &import_suggestions, + Instead::Yes, + FoundUse::Yes, + DiagnosticMode::Import, + vec![], + "", + ); + // If we suggest importing a public re-export, don't point at the definition. + if point_to_def && ident.span != outer_ident.span { + err.span_label( + outer_ident.span, + format!("{} `{outer_ident}` is not publicly re-exported", this_res.descr()), + ); + } + } let mut non_exhaustive = None; // If an ADT is foreign and marked as `non_exhaustive`, then that's @@ -1659,7 +1727,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { _ => None, }; - let first = ptr::eq(binding, first_binding); + let first = binding == first_binding; let msg = format!( "{and_refers_to}the {item} `{name}`{which} is defined here{dots}", and_refers_to = if first { "" } else { "...and refers to " }, @@ -1677,7 +1745,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if next_binding.is_none() && let Some(span) = non_exhaustive { note_span.push_span_label( span, - format!("cannot be constructed because it is `#[non_exhaustive]`"), + "cannot be constructed because it is `#[non_exhaustive]`", ); } err.span_note(note_span, msg); @@ -1689,7 +1757,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { pub(crate) fn find_similarly_named_module_or_crate( &mut self, ident: Symbol, - current_module: &Module<'a>, + current_module: Module<'a>, ) -> Option { let mut candidates = self .extern_prelude @@ -1699,7 +1767,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { self.module_map .iter() .filter(|(_, module)| { - current_module.is_ancestor_of(module) && !ptr::eq(current_module, *module) + current_module.is_ancestor_of(**module) && current_module != **module }) .flat_map(|(_, module)| module.kind.name()), ) @@ -1719,12 +1787,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { opt_ns: Option, // `None` indicates a module path in import parent_scope: &ParentScope<'a>, ribs: Option<&PerNS>>>, - ignore_binding: Option<&'a NameBinding<'a>>, + ignore_binding: Option>, module: Option>, - i: usize, + failed_segment_idx: usize, ident: Ident, ) -> (String, Option) { - let is_last = i == path.len() - 1; + let is_last = failed_segment_idx == path.len() - 1; let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS }; let module_res = match module { Some(ModuleOrUniformRoot::Module(module)) => module.res(), @@ -1758,8 +1826,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } else { (format!("could not find `{ident}` in the crate root"), None) } - } else if i > 0 { - let parent = path[i - 1].ident.name; + } else if failed_segment_idx > 0 { + let parent = path[failed_segment_idx - 1].ident.name; let parent = match parent { // ::foo is mounted at the crate root for 2015, and is the extern // prelude for 2018+ @@ -1770,7 +1838,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { _ => format!("`{parent}`"), }; - let mut msg = format!("could not find `{}` in {}", ident, parent); + let mut msg = format!("could not find `{ident}` in {parent}"); if ns == TypeNS || ns == ValueNS { let ns_to_try = if ns == TypeNS { ValueNS } else { TypeNS }; let binding = if let Some(module) = module { @@ -1798,10 +1866,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { _ => None, } } else { - let scopes = ScopeSet::All(ns_to_try, opt_ns.is_none()); self.early_resolve_ident_in_lexical_scope( ident, - scopes, + ScopeSet::All(ns_to_try), parent_scope, None, false, @@ -1886,12 +1953,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let suggestion = match_span.map(|span| { ( vec![(span, String::from(""))], - format!("`{}` is defined here, but is not a type", ident), + format!("`{ident}` is defined here, but is not a type"), Applicability::MaybeIncorrect, ) }); - (format!("use of undeclared type `{}`", ident), suggestion) + (format!("use of undeclared type `{ident}`"), suggestion) } else { let mut suggestion = None; if ident.name == sym::alloc { @@ -1903,7 +1970,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } suggestion = suggestion.or_else(|| { - self.find_similarly_named_module_or_crate(ident.name, &parent_scope.module).map( + self.find_similarly_named_module_or_crate(ident.name, parent_scope.module).map( |sugg| { ( vec![(ident.span, sugg.to_string())], @@ -1913,7 +1980,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { }, ) }); - (format!("use of undeclared crate or module `{}`", ident), suggestion) + (format!("use of undeclared crate or module `{ident}`"), suggestion) } } @@ -1933,7 +2000,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if fst.ident.name == kw::PathRoot && !snd.ident.is_path_segment_keyword() => {} // `ident::...` on 2018. (Some(fst), _) - if fst.ident.span.rust_2018() && !fst.ident.is_path_segment_keyword() => + if fst.ident.span.at_least_rust_2018() && !fst.ident.is_path_segment_keyword() => { // Insert a placeholder that's later replaced by `self`/`super`/etc. path.insert(0, Segment::from_ident(Ident::empty())); @@ -2072,7 +2139,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { /// ``` pub(crate) fn check_for_module_export_macro( &mut self, - import: &'a Import<'a>, + import: Import<'a>, module: ModuleOrUniformRoot<'a>, ident: Ident, ) -> Option<(Option, Option)> { @@ -2084,9 +2151,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { crate_module = parent; } - if ModuleOrUniformRoot::same_def(ModuleOrUniformRoot::Module(crate_module), module) { - // Don't make a suggestion if the import was already from the root of the - // crate. + if module == ModuleOrUniformRoot::Module(crate_module) { + // Don't make a suggestion if the import was already from the root of the crate. return None; } @@ -2098,16 +2164,16 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let module_name = crate_module.kind.name().unwrap(); let import_snippet = match import.kind { ImportKind::Single { source, target, .. } if source != target => { - format!("{} as {}", source, target) + format!("{source} as {target}") } - _ => format!("{}", ident), + _ => format!("{ident}"), }; let mut corrections: Vec<(Span, String)> = Vec::new(); if !import.is_nested() { // Assume this is the easy case of `use issue_59764::foo::makro;` and just remove // intermediate segments. - corrections.push((import.span, format!("{}::{}", module_name, import_snippet))); + corrections.push((import.span, format!("{module_name}::{import_snippet}"))); } else { // Find the binding span (and any trailing commas and spaces). // ie. `use a::b::{c, d, e};` @@ -2174,11 +2240,11 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { start_point, if has_nested { // In this case, `start_snippet` must equal '{'. - format!("{}{}, ", start_snippet, import_snippet) + format!("{start_snippet}{import_snippet}, ") } else { // In this case, add a `{`, then the moved import, then whatever // was there before. - format!("{{{}, {}", import_snippet, start_snippet) + format!("{{{import_snippet}, {start_snippet}") }, )); @@ -2207,6 +2273,44 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { None } } + + /// Finds a cfg-ed out item inside `module` with the matching name. + pub(crate) fn find_cfg_stripped( + &mut self, + err: &mut Diagnostic, + last_segment: &Symbol, + module: DefId, + ) { + let local_items; + let symbols = if module.is_local() { + local_items = self + .stripped_cfg_items + .iter() + .filter_map(|item| { + let parent_module = self.opt_local_def_id(item.parent_module)?.to_def_id(); + Some(StrippedCfgItem { parent_module, name: item.name, cfg: item.cfg.clone() }) + }) + .collect::>(); + local_items.as_slice() + } else { + self.tcx.stripped_cfg_items(module.krate) + }; + + for &StrippedCfgItem { parent_module, name, ref cfg } in symbols { + if parent_module != module || name.name != *last_segment { + continue; + } + + err.span_note(name.span, "found an item that was configured out"); + + if let MetaItemKind::List(nested) = &cfg.kind + && let NestedMetaItem::MetaItem(meta_item) = &nested[0] + && let MetaItemKind::NameValue(feature_name) = &meta_item.kind + { + err.note(format!("the item is gated behind the `{}` feature", feature_name.symbol)); + } + } + } } /// Given a `binding_span` of a binding within a use statement: @@ -2404,7 +2508,8 @@ pub(crate) fn import_candidates( /// When an entity with a given name is not available in scope, we search for /// entities with that name in all crates. This method allows outputting the -/// results of this search in a programmer-friendly way +/// results of this search in a programmer-friendly way. If any entities are +/// found and suggested, returns `true`, otherwise returns `false`. fn show_candidates( tcx: TyCtxt<'_>, err: &mut Diagnostic, @@ -2416,19 +2521,19 @@ fn show_candidates( mode: DiagnosticMode, path: Vec, append: &str, -) { +) -> bool { if candidates.is_empty() { - return; + return false; } - let mut accessible_path_strings: Vec<(String, &str, Option, &Option)> = + let mut accessible_path_strings: Vec<(String, &str, Option, &Option, bool)> = Vec::new(); - let mut inaccessible_path_strings: Vec<(String, &str, Option, &Option)> = + let mut inaccessible_path_strings: Vec<(String, &str, Option, &Option, bool)> = Vec::new(); candidates.iter().for_each(|c| { (if c.accessible { &mut accessible_path_strings } else { &mut inaccessible_path_strings }) - .push((path_names_to_string(&c.path), c.descr, c.did, &c.note)) + .push((path_names_to_string(&c.path), c.descr, c.did, &c.note, c.via_import)) }); // we want consistent results across executions, but candidates are produced @@ -2436,26 +2541,31 @@ fn show_candidates( for path_strings in [&mut accessible_path_strings, &mut inaccessible_path_strings] { path_strings.sort_by(|a, b| a.0.cmp(&b.0)); let core_path_strings = - path_strings.drain_filter(|p| p.0.starts_with("core::")).collect::>(); + path_strings.extract_if(|p| p.0.starts_with("core::")).collect::>(); path_strings.extend(core_path_strings); path_strings.dedup_by(|a, b| a.0 == b.0); } if !accessible_path_strings.is_empty() { - let (determiner, kind, name) = if accessible_path_strings.len() == 1 { - ("this", accessible_path_strings[0].1, format!(" `{}`", accessible_path_strings[0].0)) - } else { - ("one of these", "items", String::new()) - }; + let (determiner, kind, name, through) = + if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] { + ( + "this", + *descr, + format!(" `{name}`"), + if *via_import { " through its public re-export" } else { "" }, + ) + } else { + ("one of these", "items", String::new(), "") + }; let instead = if let Instead::Yes = instead { " instead" } else { "" }; let mut msg = if let DiagnosticMode::Pattern = mode { format!( - "if you meant to match on {}{}{}, use the full path in the pattern", - kind, instead, name + "if you meant to match on {kind}{instead}{name}, use the full path in the pattern", ) } else { - format!("consider importing {} {}{}", determiner, kind, instead) + format!("consider importing {determiner} {kind}{through}{instead}") }; for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { @@ -2471,7 +2581,7 @@ fn show_candidates( accessible_path_strings.into_iter().map(|a| a.0), Applicability::MaybeIncorrect, ); - return; + return true; } DiagnosticMode::Import => ("", ""), DiagnosticMode::Normal => ("use ", ";\n"), @@ -2512,6 +2622,7 @@ fn show_candidates( err.help(msg); } + true } else if !matches!(mode, DiagnosticMode::Import) { assert!(!inaccessible_path_strings.is_empty()); @@ -2520,13 +2631,9 @@ fn show_candidates( } else { "" }; - if inaccessible_path_strings.len() == 1 { - let (name, descr, def_id, note) = &inaccessible_path_strings[0]; + if let [(name, descr, def_id, note, _)] = &inaccessible_path_strings[..] { let msg = format!( - "{}{} `{}`{} exists but is inaccessible", - prefix, - descr, - name, + "{prefix}{descr} `{name}`{} exists but is inaccessible", if let DiagnosticMode::Pattern = mode { ", which" } else { "" } ); @@ -2540,27 +2647,27 @@ fn show_candidates( err.note(msg); } if let Some(note) = (*note).as_deref() { - err.note(note); + err.note(note.to_string()); } } else { - let (_, descr_first, _, _) = &inaccessible_path_strings[0]; + let (_, descr_first, _, _, _) = &inaccessible_path_strings[0]; let descr = if inaccessible_path_strings .iter() .skip(1) - .all(|(_, descr, _, _)| descr == descr_first) + .all(|(_, descr, _, _, _)| descr == descr_first) { descr_first } else { "item" }; let plural_descr = - if descr.ends_with('s') { format!("{}es", descr) } else { format!("{}s", descr) }; + if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") }; - let mut msg = format!("{}these {} exist but are inaccessible", prefix, plural_descr); + let mut msg = format!("{prefix}these {plural_descr} exist but are inaccessible"); let mut has_colon = false; let mut spans = Vec::new(); - for (name, _, def_id, _) in &inaccessible_path_strings { + for (name, _, def_id, _, _) in &inaccessible_path_strings { if let Some(local_def_id) = def_id.and_then(|did| did.as_local()) { let span = tcx.source_span(local_def_id); let span = tcx.sess.source_map().guess_head_span(span); @@ -2577,7 +2684,7 @@ fn show_candidates( let mut multi_span = MultiSpan::from_spans(spans.iter().map(|(_, sp)| *sp).collect()); for (name, span) in spans { - multi_span.push_span_label(span, format!("`{}`: not accessible", name)); + multi_span.push_span_label(span, format!("`{name}`: not accessible")); } for note in inaccessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { @@ -2586,6 +2693,9 @@ fn show_candidates( err.span_note(multi_span, msg); } + true + } else { + false } } diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index 7393bdb388a56..46f5df5ca6f9a 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -5,7 +5,6 @@ use rustc_ast::visit::Visitor; use rustc_ast::Crate; use rustc_ast::EnumDef; use rustc_data_structures::fx::FxHashSet; -use rustc_data_structures::intern::Interned; use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_middle::middle::privacy::Level; @@ -13,12 +12,10 @@ use rustc_middle::middle::privacy::{EffectiveVisibilities, EffectiveVisibility}; use rustc_middle::ty::Visibility; use std::mem; -type ImportId<'a> = Interned<'a, NameBinding<'a>>; - #[derive(Clone, Copy)] enum ParentId<'a> { Def(LocalDefId), - Import(ImportId<'a>), + Import(NameBinding<'a>), } impl ParentId<'_> { @@ -36,7 +33,7 @@ pub(crate) struct EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> { /// While walking import chains we need to track effective visibilities per-binding, and def id /// keys in `Resolver::effective_visibilities` are not enough for that, because multiple /// bindings can correspond to a single def id in imports. So we keep a separate table. - import_effective_visibilities: EffectiveVisibilities>, + import_effective_visibilities: EffectiveVisibilities>, // It's possible to recalculate this at any point, but it's relatively expensive. current_private_vis: Visibility, changed: bool, @@ -47,7 +44,7 @@ impl Resolver<'_, '_> { self.get_nearest_non_block_module(def_id.to_def_id()).nearest_parent_mod().expect_local() } - fn private_vis_import(&mut self, binding: ImportId<'_>) -> Visibility { + fn private_vis_import(&mut self, binding: NameBinding<'_>) -> Visibility { let NameBindingKind::Import { import, .. } = binding.kind else { unreachable!() }; Visibility::Restricted( import @@ -75,13 +72,13 @@ impl<'r, 'a, 'tcx> EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> { pub(crate) fn compute_effective_visibilities<'c>( r: &'r mut Resolver<'a, 'tcx>, krate: &'c Crate, - ) -> FxHashSet>> { + ) -> FxHashSet> { let mut visitor = EffectiveVisibilitiesVisitor { r, def_effective_visibilities: Default::default(), import_effective_visibilities: Default::default(), current_private_vis: Visibility::Restricted(CRATE_DEF_ID), - changed: false, + changed: true, }; visitor.def_effective_visibilities.update_root(); @@ -131,23 +128,25 @@ impl<'r, 'a, 'tcx> EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> { // If the binding is ambiguous, put the root ambiguity binding and all reexports // leading to it into the table. They are used by the `ambiguous_glob_reexports` // lint. For all bindings added to the table this way `is_ambiguity` returns true. + let is_ambiguity = + |binding: NameBinding<'a>, warn: bool| binding.ambiguity.is_some() && !warn; let mut parent_id = ParentId::Def(module_id); + let mut warn_ambiguity = binding.warn_ambiguity; while let NameBindingKind::Import { binding: nested_binding, .. } = binding.kind { - let binding_id = ImportId::new_unchecked(binding); - self.update_import(binding_id, parent_id); + self.update_import(binding, parent_id); - if binding.ambiguity.is_some() { + if is_ambiguity(binding, warn_ambiguity) { // Stop at the root ambiguity, further bindings in the chain should not // be reexported because the root ambiguity blocks any access to them. // (Those further bindings are most likely not ambiguities themselves.) break; } - parent_id = ParentId::Import(binding_id); + parent_id = ParentId::Import(binding); binding = nested_binding; + warn_ambiguity |= nested_binding.warn_ambiguity; } - - if binding.ambiguity.is_none() + if !is_ambiguity(binding, warn_ambiguity) && let Some(def_id) = binding.res().opt_def_id().and_then(|id| id.as_local()) { self.update_def(def_id, binding.vis.expect_local(), parent_id); } @@ -192,7 +191,7 @@ impl<'r, 'a, 'tcx> EffectiveVisibilitiesVisitor<'r, 'a, 'tcx> { } } - fn update_import(&mut self, binding: ImportId<'a>, parent_id: ParentId<'a>) { + fn update_import(&mut self, binding: NameBinding<'a>, parent_id: ParentId<'a>) { let nominal_vis = binding.vis.expect_local(); let Some(cheap_private_vis) = self.may_update(nominal_vis, parent_id) else { return }; let inherited_eff_vis = self.effective_vis_or_private(parent_id); diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 2ab55f12637c8..e4b89c65853d0 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -330,6 +330,7 @@ pub(crate) struct ParamInTyOfConstParam { pub(crate) param_kind: Option, } +#[derive(Debug)] #[derive(Subdiagnostic)] pub(crate) enum ParamKindInTyOfConstParam { #[note(resolve_type_param_in_ty_of_const_param)] @@ -365,6 +366,7 @@ pub(crate) struct ParamInNonTrivialAnonConst { #[help(resolve_param_in_non_trivial_anon_const_help)] pub(crate) struct ParamInNonTrivialAnonConstHelp; +#[derive(Debug)] #[derive(Subdiagnostic)] pub(crate) enum ParamKindInNonTrivialAnonConst { #[note(resolve_type_param_in_non_trivial_anon_const)] @@ -562,6 +564,7 @@ pub(crate) struct CfgAccessibleUnsure { pub(crate) span: Span, } +#[derive(Debug)] #[derive(Diagnostic)] #[diag(resolve_param_in_enum_discriminant)] pub(crate) struct ParamInEnumDiscriminant { @@ -573,6 +576,7 @@ pub(crate) struct ParamInEnumDiscriminant { pub(crate) param_kind: ParamKindInEnumDiscriminant, } +#[derive(Debug)] #[derive(Subdiagnostic)] pub(crate) enum ParamKindInEnumDiscriminant { #[note(resolve_type_param_in_enum_discriminant)] @@ -582,3 +586,144 @@ pub(crate) enum ParamKindInEnumDiscriminant { #[note(resolve_lifetime_param_in_enum_discriminant)] Lifetime, } + +#[derive(Subdiagnostic)] +#[label(resolve_change_import_binding)] +pub(crate) struct ChangeImportBinding { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Subdiagnostic)] +#[suggestion( + resolve_change_import_binding, + code = "{suggestion}", + applicability = "maybe-incorrect" +)] +pub(crate) struct ChangeImportBindingSuggestion { + #[primary_span] + pub(crate) span: Span, + pub(crate) suggestion: String, +} + +#[derive(Diagnostic)] +#[diag(resolve_imports_cannot_refer_to)] +pub(crate) struct ImportsCannotReferTo<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) what: &'a str, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_find_ident_in_this_scope)] +pub(crate) struct CannotFindIdentInThisScope<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) expected: &'a str, + pub(crate) ident: Ident, +} + +#[derive(Subdiagnostic)] +#[note(resolve_explicit_unsafe_traits)] +pub(crate) struct ExplicitUnsafeTraits { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Subdiagnostic)] +#[help(resolve_added_macro_use)] +pub(crate) struct AddedMacroUse; + +#[derive(Subdiagnostic)] +#[suggestion( + resolve_consider_adding_a_derive, + code = "{suggestion}", + applicability = "maybe-incorrect" +)] +pub(crate) struct ConsiderAddingADerive { + #[primary_span] + pub(crate) span: Span, + pub(crate) suggestion: String, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_determine_import_resolution)] +pub(crate) struct CannotDetermineImportResolution { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_be_reexported_private, code = "E0364")] +pub(crate) struct CannotBeReexportedPrivate { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_be_reexported_crate_public, code = "E0364")] +pub(crate) struct CannotBeReexportedCratePublic { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_be_reexported_private, code = "E0365")] +#[note(resolve_consider_declaring_with_pub)] +pub(crate) struct CannotBeReexportedPrivateNS { + #[primary_span] + #[label(resolve_reexport_of_private)] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_be_reexported_crate_public, code = "E0365")] +#[note(resolve_consider_declaring_with_pub)] +pub(crate) struct CannotBeReexportedCratePublicNS { + #[primary_span] + #[label(resolve_reexport_of_crate_public)] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Subdiagnostic)] +#[help(resolve_consider_adding_macro_export)] +pub(crate) struct ConsiderAddingMacroExport { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Subdiagnostic)] +#[note(resolve_consider_marking_as_pub)] +pub(crate) struct ConsiderMarkingAsPub { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Diagnostic)] +#[diag(resolve_cannot_glob_import_possible_crates)] +pub(crate) struct CannotGlobImportAllCrates { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(resolve_items_in_traits_are_not_importable)] +pub(crate) struct ItemsInTraitsAreNotImportable { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag(resolve_is_not_directly_importable, code = "E0253")] +pub(crate) struct IsNotDirectlyImportable { + #[primary_span] + #[label] + pub(crate) span: Span, + pub(crate) target: Ident, +} diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 945c7ce3a9b32..3bd9cea27ce6a 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -11,8 +11,6 @@ use rustc_span::hygiene::{ExpnId, ExpnKind, LocalExpnId, MacroKind, SyntaxContex use rustc_span::symbol::{kw, Ident}; use rustc_span::{Span, DUMMY_SP}; -use std::ptr; - use crate::errors::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; use crate::late::{ ConstantHasGenerics, HasGenericParams, NoConstantGenericsReason, PathSource, Rib, RibKind, @@ -20,7 +18,7 @@ use crate::late::{ use crate::macros::{sub_namespace_match, MacroRulesScope}; use crate::BindingKey; use crate::{errors, AmbiguityError, AmbiguityErrorMisc, AmbiguityKind, Determinacy, Finalize}; -use crate::{Import, ImportKind, LexicalScopeBinding, Module, ModuleKind, ModuleOrUniformRoot}; +use crate::{ImportKind, LexicalScopeBinding, Module, ModuleKind, ModuleOrUniformRoot}; use crate::{NameBinding, NameBindingKind, ParentScope, PathResult, PrivacyError, Res}; use crate::{ResolutionError, Resolver, Scope, ScopeSet, Segment, ToNameBinding, Weak}; @@ -88,7 +86,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let rust_2015 = ctxt.edition().is_rust_2015(); let (ns, macro_kind, is_absolute_path) = match scope_set { - ScopeSet::All(ns, _) => (ns, None, false), + ScopeSet::All(ns) => (ns, None, false), ScopeSet::AbsolutePath(ns) => (ns, None, true), ScopeSet::Macro(macro_kind) => (MacroNS, Some(macro_kind), false), ScopeSet::Late(ns, ..) => (ns, None, false), @@ -284,7 +282,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { parent_scope: &ParentScope<'a>, finalize: Option, ribs: &[Rib<'a>], - ignore_binding: Option<&'a NameBinding<'a>>, + ignore_binding: Option>, ) -> Option> { assert!(ns == TypeNS || ns == ValueNS); let orig_ident = ident; @@ -370,7 +368,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { /// expansion and import resolution (perhaps they can be merged in the future). /// The function is used for resolving initial segments of macro paths (e.g., `foo` in /// `foo::bar!();` or `foo!();`) and also for import paths on 2018 edition. - #[instrument(level = "debug", skip(self, scope_set))] + #[instrument(level = "debug", skip(self))] pub(crate) fn early_resolve_ident_in_lexical_scope( &mut self, orig_ident: Ident, @@ -378,8 +376,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { parent_scope: &ParentScope<'a>, finalize: Option, force: bool, - ignore_binding: Option<&'a NameBinding<'a>>, - ) -> Result<&'a NameBinding<'a>, Determinacy> { + ignore_binding: Option>, + ) -> Result, Determinacy> { bitflags::bitflags! { struct Flags: u8 { const MACRO_RULES = 1 << 0; @@ -397,11 +395,11 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { return Err(Determinacy::Determined); } - let (ns, macro_kind, is_import) = match scope_set { - ScopeSet::All(ns, is_import) => (ns, None, is_import), - ScopeSet::AbsolutePath(ns) => (ns, None, false), - ScopeSet::Macro(macro_kind) => (MacroNS, Some(macro_kind), false), - ScopeSet::Late(ns, ..) => (ns, None, false), + let (ns, macro_kind) = match scope_set { + ScopeSet::All(ns) => (ns, None), + ScopeSet::AbsolutePath(ns) => (ns, None), + ScopeSet::Macro(macro_kind) => (MacroNS, Some(macro_kind)), + ScopeSet::Late(ns, ..) => (ns, None), }; // This is *the* result, resolution from the scope closest to the resolved identifier. @@ -415,7 +413,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { // } // So we have to save the innermost solution and continue searching in outer scopes // to detect potential ambiguities. - let mut innermost_result: Option<(&NameBinding<'_>, Flags)> = None; + let mut innermost_result: Option<(NameBinding<'_>, Flags)> = None; let mut determinacy = Determinacy::Determined; // Go through all the scopes and try to resolve the name. @@ -538,7 +536,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ), ); } - let misc_flags = if ptr::eq(module, this.graph_root) { + let misc_flags = if module == this.graph_root { Flags::MISC_SUGGEST_CRATE } else if module.is_normal() { Flags::MISC_SUGGEST_SELF @@ -631,9 +629,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let derive_helper_compat = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat); - let ambiguity_error_kind = if is_import { - Some(AmbiguityKind::Import) - } else if is_builtin(innermost_res) || is_builtin(res) { + let ambiguity_error_kind = if is_builtin(innermost_res) + || is_builtin(res) + { Some(AmbiguityKind::BuiltinAttr) } else if innermost_res == derive_helper_compat || res == derive_helper_compat && innermost_res != derive_helper @@ -679,6 +677,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ident: orig_ident, b1: innermost_binding, b2: binding, + warning: false, misc1: misc(innermost_flags), misc2: misc(flags), }); @@ -717,7 +716,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ident: Ident, ns: Namespace, parent_scope: &ParentScope<'a>, - ) -> Result<&'a NameBinding<'a>, Determinacy> { + ) -> Result, Determinacy> { self.resolve_ident_in_module_ext(module, ident, ns, parent_scope, None, None) .map_err(|(determinacy, _)| determinacy) } @@ -730,8 +729,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ns: Namespace, parent_scope: &ParentScope<'a>, finalize: Option, - ignore_binding: Option<&'a NameBinding<'a>>, - ) -> Result<&'a NameBinding<'a>, Determinacy> { + ignore_binding: Option>, + ) -> Result, Determinacy> { self.resolve_ident_in_module_ext(module, ident, ns, parent_scope, finalize, ignore_binding) .map_err(|(determinacy, _)| determinacy) } @@ -744,8 +743,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ns: Namespace, parent_scope: &ParentScope<'a>, finalize: Option, - ignore_binding: Option<&'a NameBinding<'a>>, - ) -> Result<&'a NameBinding<'a>, (Determinacy, Weak)> { + ignore_binding: Option>, + ) -> Result, (Determinacy, Weak)> { let tmp_parent_scope; let mut adjusted_parent_scope = parent_scope; match module { @@ -782,8 +781,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ns: Namespace, parent_scope: &ParentScope<'a>, finalize: Option, - ignore_binding: Option<&'a NameBinding<'a>>, - ) -> Result<&'a NameBinding<'a>, Determinacy> { + ignore_binding: Option>, + ) -> Result, Determinacy> { self.resolve_ident_in_module_unadjusted_ext( module, ident, @@ -809,8 +808,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { finalize: Option, // This binding should be ignored during in-module resolution, so that we don't get // "self-confirming" import resolutions during import validation and checking. - ignore_binding: Option<&'a NameBinding<'a>>, - ) -> Result<&'a NameBinding<'a>, (Determinacy, Weak)> { + ignore_binding: Option>, + ) -> Result, (Determinacy, Weak)> { let module = match module { ModuleOrUniformRoot::Module(module) => module, ModuleOrUniformRoot::CrateRootAndExternPrelude => { @@ -853,10 +852,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - let scopes = ScopeSet::All(ns, true); let binding = self.early_resolve_ident_in_lexical_scope( ident, - scopes, + ScopeSet::All(ns), parent_scope, finalize, finalize.is_some(), @@ -874,13 +872,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { // binding if it exists. What we really want here is having two separate scopes in // a module - one for non-globs and one for globs, but until that's done use this // hack to avoid inconsistent resolution ICEs during import validation. - let binding = - [resolution.binding, resolution.shadowed_glob].into_iter().find_map(|binding| { - match (binding, ignore_binding) { - (Some(binding), Some(ignored)) if ptr::eq(binding, ignored) => None, - _ => binding, - } - }); + let binding = [resolution.binding, resolution.shadowed_glob] + .into_iter() + .find_map(|binding| if binding == ignore_binding { None } else { binding }); if let Some(Finalize { path_span, report_private, .. }) = finalize { let Some(binding) = binding else { @@ -893,6 +887,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ident, binding, dedup_span: path_span, + outermost_res: None, + parent_scope: *parent_scope, }); } else { return Err((Determined, Weak::No)); @@ -910,17 +906,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ident, b1: binding, b2: shadowed_glob, + warning: false, misc1: AmbiguityErrorMisc::None, misc2: AmbiguityErrorMisc::None, }); } if !restricted_shadowing && binding.expansion != LocalExpnId::ROOT { - if let NameBindingKind::Import { - import: Import { kind: ImportKind::MacroExport, .. }, - .. - } = binding.kind - { + if let NameBindingKind::Import { import, .. } = binding.kind + && matches!(import.kind, ImportKind::MacroExport) { self.macro_expanded_macro_export_errors.insert((path_span, binding.span)); } } @@ -929,7 +923,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { return Ok(binding); } - let check_usable = |this: &mut Self, binding: &'a NameBinding<'a>| { + let check_usable = |this: &mut Self, binding: NameBinding<'a>| { let usable = this.is_accessible_from(binding.vis, parent_scope.module); if usable { Ok(binding) } else { Err((Determined, Weak::No)) } }; @@ -954,7 +948,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } if let Some(ignored) = ignore_binding && let NameBindingKind::Import { import, .. } = ignored.kind && - ptr::eq(import, &**single_import) { + import == *single_import { // Ignore not just the binding itself, but if it has a shadowed_glob, // ignore that, too, because this loop is supposed to only process // named imports. @@ -1351,7 +1345,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { opt_ns: Option, // `None` indicates a module path in import parent_scope: &ParentScope<'a>, finalize: Option, - ignore_binding: Option<&'a NameBinding<'a>>, + ignore_binding: Option>, ) -> PathResult<'a> { self.resolve_path_with_ribs(path, opt_ns, parent_scope, finalize, None, ignore_binding) } @@ -1363,22 +1357,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { parent_scope: &ParentScope<'a>, finalize: Option, ribs: Option<&PerNS>>>, - ignore_binding: Option<&'a NameBinding<'a>>, + ignore_binding: Option>, ) -> PathResult<'a> { - debug!( - "resolve_path(path={:?}, opt_ns={:?}, finalize={:?}) path_len: {}", - path, - opt_ns, - finalize, - path.len() - ); - let mut module = None; let mut allow_super = true; let mut second_binding = None; - for (i, &Segment { ident, id, .. }) in path.iter().enumerate() { - debug!("resolve_path ident {} {:?} {:?}", i, ident, id); + // We'll provide more context to the privacy errors later, up to `len`. + let privacy_errors_len = self.privacy_errors.len(); + + for (segment_idx, &Segment { ident, id, .. }) in path.iter().enumerate() { + debug!("resolve_path ident {} {:?} {:?}", segment_idx, ident, id); let record_segment_res = |this: &mut Self, res| { if finalize.is_some() { if let Some(id) = id { @@ -1390,7 +1379,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } }; - let is_last = i + 1 == path.len(); + let is_last = segment_idx + 1 == path.len(); let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS }; let name = ident.name; @@ -1399,7 +1388,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if ns == TypeNS { if allow_super && name == kw::Super { let mut ctxt = ident.span.ctxt().normalize_to_macros_2_0(); - let self_module = match i { + let self_module = match segment_idx { 0 => Some(self.resolve_self(&mut ctxt, parent_scope.module)), _ => match module { Some(ModuleOrUniformRoot::Module(module)) => Some(module), @@ -1414,11 +1403,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { continue; } } - return PathResult::failed(ident.span, false, finalize.is_some(), || { - ("there are too many leading `super` keywords".to_string(), None) - }); + return PathResult::failed( + ident.span, + false, + finalize.is_some(), + module, + || ("there are too many leading `super` keywords".to_string(), None), + ); } - if i == 0 { + if segment_idx == 0 { if name == kw::SelfLower { let mut ctxt = ident.span.ctxt().normalize_to_macros_2_0(); module = Some(ModuleOrUniformRoot::Module( @@ -1426,13 +1419,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { )); continue; } - if name == kw::PathRoot && ident.span.rust_2018() { + if name == kw::PathRoot && ident.span.at_least_rust_2018() { module = Some(ModuleOrUniformRoot::ExternPrelude); continue; } if name == kw::PathRoot && ident.span.is_rust_2015() - && self.tcx.sess.rust_2018() + && self.tcx.sess.at_least_rust_2018() { // `::a::b` from 2015 macro on 2018 global edition module = Some(ModuleOrUniformRoot::CrateRootAndExternPrelude); @@ -1447,82 +1440,77 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } // Report special messages for path segment keywords in wrong positions. - if ident.is_path_segment_keyword() && i != 0 { - return PathResult::failed(ident.span, false, finalize.is_some(), || { + if ident.is_path_segment_keyword() && segment_idx != 0 { + return PathResult::failed(ident.span, false, finalize.is_some(), module, || { let name_str = if name == kw::PathRoot { "crate root".to_string() } else { - format!("`{}`", name) + format!("`{name}`") }; - let label = if i == 1 && path[0].ident.name == kw::PathRoot { - format!("global paths cannot start with {}", name_str) + let label = if segment_idx == 1 && path[0].ident.name == kw::PathRoot { + format!("global paths cannot start with {name_str}") } else { - format!("{} in paths can only be used in start position", name_str) + format!("{name_str} in paths can only be used in start position") }; (label, None) }); } - enum FindBindingResult<'a> { - Binding(Result<&'a NameBinding<'a>, Determinacy>), - Res(Res), - } - let find_binding_in_ns = |this: &mut Self, ns| { - let binding = if let Some(module) = module { - this.resolve_ident_in_module( - module, - ident, - ns, - parent_scope, - finalize, - ignore_binding, - ) - } else if let Some(ribs) = ribs - && let Some(TypeNS | ValueNS) = opt_ns - { - match this.resolve_ident_in_lexical_scope( - ident, - ns, - parent_scope, - finalize, - &ribs[ns], - ignore_binding, - ) { - // we found a locally-imported or available item/module - Some(LexicalScopeBinding::Item(binding)) => Ok(binding), - // we found a local variable or type param - Some(LexicalScopeBinding::Res(res)) => return FindBindingResult::Res(res), - _ => Err(Determinacy::determined(finalize.is_some())), + let binding = if let Some(module) = module { + self.resolve_ident_in_module( + module, + ident, + ns, + parent_scope, + finalize, + ignore_binding, + ) + } else if let Some(ribs) = ribs && let Some(TypeNS | ValueNS) = opt_ns { + match self.resolve_ident_in_lexical_scope( + ident, + ns, + parent_scope, + finalize, + &ribs[ns], + ignore_binding, + ) { + // we found a locally-imported or available item/module + Some(LexicalScopeBinding::Item(binding)) => Ok(binding), + // we found a local variable or type param + Some(LexicalScopeBinding::Res(res)) => { + record_segment_res(self, res); + return PathResult::NonModule(PartialRes::with_unresolved_segments( + res, + path.len() - 1, + )); } - } else { - let scopes = ScopeSet::All(ns, opt_ns.is_none()); - this.early_resolve_ident_in_lexical_scope( - ident, - scopes, - parent_scope, - finalize, - finalize.is_some(), - ignore_binding, - ) - }; - FindBindingResult::Binding(binding) - }; - let binding = match find_binding_in_ns(self, ns) { - FindBindingResult::Res(res) => { - record_segment_res(self, res); - return PathResult::NonModule(PartialRes::with_unresolved_segments( - res, - path.len() - 1, - )); + _ => Err(Determinacy::determined(finalize.is_some())), } - FindBindingResult::Binding(binding) => binding, + } else { + self.early_resolve_ident_in_lexical_scope( + ident, + ScopeSet::All(ns), + parent_scope, + finalize, + finalize.is_some(), + ignore_binding, + ) }; + match binding { Ok(binding) => { - if i == 1 { + if segment_idx == 1 { second_binding = Some(binding); } let res = binding.res(); + + // Mark every privacy error in this path with the res to the last element. This allows us + // to detect the item the user cares about and either find an alternative import, or tell + // the user it is not accessible. + for error in &mut self.privacy_errors[privacy_errors_len..] { + error.outermost_res = Some((res, ident)); + } + let maybe_assoc = opt_ns != Some(MacroNS) && PathSource::Type.is_expected(res); if let Some(next_module) = binding.module() { module = Some(ModuleOrUniformRoot::Module(next_module)); @@ -1543,17 +1531,23 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { record_segment_res(self, res); return PathResult::NonModule(PartialRes::with_unresolved_segments( res, - path.len() - i - 1, + path.len() - segment_idx - 1, )); } else { - return PathResult::failed(ident.span, is_last, finalize.is_some(), || { - let label = format!( - "`{ident}` is {} {}, not a module", - res.article(), - res.descr() - ); - (label, None) - }); + return PathResult::failed( + ident.span, + is_last, + finalize.is_some(), + module, + || { + let label = format!( + "`{ident}` is {} {}, not a module", + res.article(), + res.descr() + ); + (label, None) + }, + ); } } Err(Undetermined) => return PathResult::Indeterminate, @@ -1562,23 +1556,29 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if opt_ns.is_some() && !module.is_normal() { return PathResult::NonModule(PartialRes::with_unresolved_segments( module.res().unwrap(), - path.len() - i, + path.len() - segment_idx, )); } } - return PathResult::failed(ident.span, is_last, finalize.is_some(), || { - self.report_path_resolution_error( - path, - opt_ns, - parent_scope, - ribs, - ignore_binding, - module, - i, - ident, - ) - }); + return PathResult::failed( + ident.span, + is_last, + finalize.is_some(), + module, + || { + self.report_path_resolution_error( + path, + opt_ns, + parent_scope, + ribs, + ignore_binding, + module, + segment_idx, + ident, + ) + }, + ); } } } diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 7c4c05d4b9452..a175d9f6c7f04 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -1,15 +1,18 @@ //! A bunch of methods and structures more or less related to resolving imports. use crate::diagnostics::{import_candidates, DiagnosticMode, Suggestion}; +use crate::errors::{ + CannotBeReexportedCratePublic, CannotBeReexportedCratePublicNS, CannotBeReexportedPrivate, + CannotBeReexportedPrivateNS, CannotDetermineImportResolution, CannotGlobImportAllCrates, + ConsiderAddingMacroExport, ConsiderMarkingAsPub, IsNotDirectlyImportable, + ItemsInTraitsAreNotImportable, +}; use crate::Determinacy::{self, *}; -use crate::Namespace::*; +use crate::{fluent_generated as fluent, Namespace::*}; use crate::{module_to_string, names_to_string, ImportSuggestion}; -use crate::{ - AmbiguityError, AmbiguityErrorMisc, AmbiguityKind, BindingKey, ModuleKind, ResolutionError, - Resolver, Segment, -}; +use crate::{AmbiguityKind, BindingKey, ModuleKind, ResolutionError, Resolver, Segment}; use crate::{Finalize, Module, ModuleOrUniformRoot, ParentScope, PerNS, ScopeSet}; -use crate::{NameBinding, NameBindingKind, PathResult}; +use crate::{NameBinding, NameBindingData, NameBindingKind, PathResult}; use rustc_ast::NodeId; use rustc_data_structures::fx::FxHashSet; @@ -21,7 +24,8 @@ use rustc_middle::metadata::Reexport; use rustc_middle::span_bug; use rustc_middle::ty; use rustc_session::lint::builtin::{ - AMBIGUOUS_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, UNUSED_IMPORTS, + AMBIGUOUS_GLOB_REEXPORTS, HIDDEN_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, + UNUSED_IMPORTS, }; use rustc_session::lint::BuiltinLintDiagnostics; use rustc_span::edit_distance::find_best_match_for_name; @@ -31,7 +35,7 @@ use rustc_span::Span; use smallvec::SmallVec; use std::cell::Cell; -use std::{mem, ptr}; +use std::mem; type Res = def::Res; @@ -44,9 +48,9 @@ pub(crate) enum ImportKind<'a> { /// `target` in `use prefix::source as target`. target: Ident, /// Bindings to which `source` refers to. - source_bindings: PerNS, Determinacy>>>, + source_bindings: PerNS, Determinacy>>>, /// Bindings introduced by `target`. - target_bindings: PerNS>>>, + target_bindings: PerNS>>>, /// `true` for `...::{self [as target]}` imports, `false` otherwise. type_ns_only: bool, /// Did this import result from a nested import? ie. `use foo::{bar, baz};` @@ -131,7 +135,7 @@ impl<'a> std::fmt::Debug for ImportKind<'a> { /// One import. #[derive(Debug, Clone)] -pub(crate) struct Import<'a> { +pub(crate) struct ImportData<'a> { pub kind: ImportKind<'a>, /// Node ID of the "root" use item -- this is always the same as `ImportKind`'s `id` @@ -168,7 +172,11 @@ pub(crate) struct Import<'a> { pub used: Cell, } -impl<'a> Import<'a> { +/// All imports are unique and allocated on a same arena, +/// so we can use referential equality to compare them. +pub(crate) type Import<'a> = Interned<'a, ImportData<'a>>; + +impl<'a> ImportData<'a> { pub(crate) fn is_glob(&self) -> bool { matches!(self.kind, ImportKind::Glob { .. }) } @@ -210,15 +218,15 @@ impl<'a> Import<'a> { pub(crate) struct NameResolution<'a> { /// Single imports that may define the name in the namespace. /// Imports are arena-allocated, so it's ok to use pointers as keys. - pub single_imports: FxHashSet>>, + pub single_imports: FxHashSet>, /// The least shadowable known binding for this name, or None if there are no known bindings. - pub binding: Option<&'a NameBinding<'a>>, - pub shadowed_glob: Option<&'a NameBinding<'a>>, + pub binding: Option>, + pub shadowed_glob: Option>, } impl<'a> NameResolution<'a> { /// Returns the binding for the name if it is known or None if it not known. - pub(crate) fn binding(&self) -> Option<&'a NameBinding<'a>> { + pub(crate) fn binding(&self) -> Option> { self.binding.and_then(|binding| { if !binding.is_glob_import() || self.single_imports.is_empty() { Some(binding) @@ -227,10 +235,6 @@ impl<'a> NameResolution<'a> { } }) } - - pub(crate) fn add_single_import(&mut self, import: &'a Import<'a>) { - self.single_imports.insert(Interned::new_unchecked(import)); - } } /// An error that may be transformed into a diagnostic later. Used to combine multiple unresolved @@ -246,15 +250,12 @@ struct UnresolvedImportError { // Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` // are permitted for backward-compatibility under a deprecation lint. -fn pub_use_of_private_extern_crate_hack(import: &Import<'_>, binding: &NameBinding<'_>) -> bool { +fn pub_use_of_private_extern_crate_hack(import: Import<'_>, binding: NameBinding<'_>) -> bool { match (&import.kind, &binding.kind) { - ( - ImportKind::Single { .. }, - NameBindingKind::Import { - import: Import { kind: ImportKind::ExternCrate { .. }, .. }, - .. - }, - ) => import.expect_vis().is_public(), + (ImportKind::Single { .. }, NameBindingKind::Import { import: binding_import, .. }) => { + matches!(binding_import.kind, ImportKind::ExternCrate { .. }) + && import.expect_vis().is_public() + } _ => false, } } @@ -262,11 +263,7 @@ fn pub_use_of_private_extern_crate_hack(import: &Import<'_>, binding: &NameBindi impl<'a, 'tcx> Resolver<'a, 'tcx> { /// Given a binding and an import that resolves to it, /// return the corresponding binding defined by the import. - pub(crate) fn import( - &self, - binding: &'a NameBinding<'a>, - import: &'a Import<'a>, - ) -> &'a NameBinding<'a> { + pub(crate) fn import(&self, binding: NameBinding<'a>, import: Import<'a>) -> NameBinding<'a> { let import_vis = import.expect_vis().to_def_id(); let vis = if binding.vis.is_at_least(import_vis, self.tcx) || pub_use_of_private_extern_crate_hack(import, binding) @@ -284,9 +281,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - self.arenas.alloc_name_binding(NameBinding { + self.arenas.alloc_name_binding(NameBindingData { kind: NameBindingKind::Import { binding, import, used: Cell::new(false) }, ambiguity: None, + warn_ambiguity: false, span: import.span, vis, expansion: import.parent_scope.expansion, @@ -294,16 +292,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } /// Define the name or return the existing binding if there is a collision. + /// `update` indicates if the definition is a redefinition of an existing binding. pub(crate) fn try_define( &mut self, module: Module<'a>, key: BindingKey, - binding: &'a NameBinding<'a>, - ) -> Result<(), &'a NameBinding<'a>> { + binding: NameBinding<'a>, + warn_ambiguity: bool, + ) -> Result<(), NameBinding<'a>> { let res = binding.res(); self.check_reserved_macro_name(key.ident, res); self.set_binding_parent_module(binding, module); - self.update_resolution(module, key, |this, resolution| { + self.update_resolution(module, key, warn_ambiguity, |this, resolution| { if let Some(old_binding) = resolution.binding { if res == Res::Err && old_binding.res() != Res::Err { // Do not override real bindings with `Res::Err`s from error recovery. @@ -311,15 +311,42 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } match (old_binding.is_glob_import(), binding.is_glob_import()) { (true, true) => { - if res != old_binding.res() { - resolution.binding = Some(this.ambiguity( - AmbiguityKind::GlobVsGlob, - old_binding, - binding, - )); + // FIXME: remove `!binding.is_ambiguity()` after delete the warning ambiguity. + if !binding.is_ambiguity() + && let NameBindingKind::Import { import: old_import, .. } = old_binding.kind + && let NameBindingKind::Import { import, .. } = binding.kind + && old_import == import { + // We should replace the `old_binding` with `binding` regardless + // of whether they has same resolution or not when they are + // imported from the same glob-import statement. + // However we currently using `Some(old_binding)` for back compact + // purposes. + // This case can be removed after once `Undetermined` is prepared + // for glob-imports. + } else if res != old_binding.res() { + let binding = if warn_ambiguity { + this.warn_ambiguity( + AmbiguityKind::GlobVsGlob, + old_binding, + binding, + ) + } else { + this.ambiguity( + AmbiguityKind::GlobVsGlob, + old_binding, + binding, + ) + }; + resolution.binding = Some(binding); } else if !old_binding.vis.is_at_least(binding.vis, this.tcx) { // We are glob-importing the same item but with greater visibility. resolution.binding = Some(binding); + } else if binding.is_ambiguity() { + resolution.binding = + Some(self.arenas.alloc_name_binding(NameBindingData { + warn_ambiguity: true, + ..(*binding).clone() + })); } } (old_glob @ true, false) | (old_glob @ false, true) => { @@ -337,7 +364,21 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } else { resolution.binding = Some(nonglob_binding); } - resolution.shadowed_glob = Some(glob_binding); + + if let Some(old_binding) = resolution.shadowed_glob { + assert!(old_binding.is_glob_import()); + if glob_binding.res() != old_binding.res() { + resolution.shadowed_glob = Some(this.ambiguity( + AmbiguityKind::GlobVsGlob, + old_binding, + glob_binding, + )); + } else if !old_binding.vis.is_at_least(binding.vis, this.tcx) { + resolution.shadowed_glob = Some(glob_binding); + } + } else { + resolution.shadowed_glob = Some(glob_binding); + } } (false, false) => { return Err(old_binding); @@ -354,41 +395,61 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { fn ambiguity( &self, kind: AmbiguityKind, - primary_binding: &'a NameBinding<'a>, - secondary_binding: &'a NameBinding<'a>, - ) -> &'a NameBinding<'a> { - self.arenas.alloc_name_binding(NameBinding { + primary_binding: NameBinding<'a>, + secondary_binding: NameBinding<'a>, + ) -> NameBinding<'a> { + self.arenas.alloc_name_binding(NameBindingData { ambiguity: Some((secondary_binding, kind)), - ..primary_binding.clone() + ..(*primary_binding).clone() + }) + } + + fn warn_ambiguity( + &self, + kind: AmbiguityKind, + primary_binding: NameBinding<'a>, + secondary_binding: NameBinding<'a>, + ) -> NameBinding<'a> { + self.arenas.alloc_name_binding(NameBindingData { + ambiguity: Some((secondary_binding, kind)), + warn_ambiguity: true, + ..(*primary_binding).clone() }) } // Use `f` to mutate the resolution of the name in the module. // If the resolution becomes a success, define it in the module's glob importers. - fn update_resolution(&mut self, module: Module<'a>, key: BindingKey, f: F) -> T + fn update_resolution( + &mut self, + module: Module<'a>, + key: BindingKey, + warn_ambiguity: bool, + f: F, + ) -> T where F: FnOnce(&mut Resolver<'a, 'tcx>, &mut NameResolution<'a>) -> T, { // Ensure that `resolution` isn't borrowed when defining in the module's glob importers, // during which the resolution might end up getting re-defined via a glob cycle. - let (binding, t) = { + let (binding, t, warn_ambiguity) = { let resolution = &mut *self.resolution(module, key).borrow_mut(); let old_binding = resolution.binding(); let t = f(self, resolution); - match resolution.binding() { - _ if old_binding.is_some() => return t, - None => return t, - Some(binding) => match old_binding { - Some(old_binding) if ptr::eq(old_binding, binding) => return t, - _ => (binding, t), - }, + if let Some(binding) = resolution.binding() && old_binding != Some(binding) { + (binding, t, warn_ambiguity || old_binding.is_some()) + } else { + return t; } }; - // Define `binding` in `module`s glob importers. - for import in module.glob_importers.borrow_mut().iter() { + let Ok(glob_importers) = module.glob_importers.try_borrow_mut() else { + return t; + }; + + // Define or update `binding` in `module`s glob importers. + for import in glob_importers.iter() { let mut ident = key.ident; let scope = match ident.span.reverse_glob_adjust(module.expansion, import.span) { Some(Some(def)) => self.expn_def_scope(def), @@ -396,9 +457,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { None => continue, }; if self.is_accessible_from(binding.vis, scope) { - let imported_binding = self.import(binding, import); + let imported_binding = self.import(binding, *import); let key = BindingKey { ident, ..key }; - let _ = self.try_define(import.parent_scope.module, key, imported_binding); + let _ = self.try_define( + import.parent_scope.module, + key, + imported_binding, + warn_ambiguity, + ); } } @@ -407,7 +473,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { // Define a dummy resolution containing a `Res::Err` as a placeholder for a failed // or indeterminate resolution, also mark such failed imports as used to avoid duplicate diagnostics. - fn import_dummy_binding(&mut self, import: &'a Import<'a>, is_indeterminate: bool) { + fn import_dummy_binding(&mut self, import: Import<'a>, is_indeterminate: bool) { if let ImportKind::Single { target, ref target_bindings, .. } = import.kind { if !(is_indeterminate || target_bindings.iter().all(|binding| binding.get().is_none())) { @@ -417,7 +483,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let dummy_binding = self.import(dummy_binding, import); self.per_ns(|this, ns| { let key = BindingKey::new(target, ns); - let _ = this.try_define(import.parent_scope.module, key, dummy_binding); + let _ = this.try_define(import.parent_scope.module, key, dummy_binding, false); }); self.record_use(target, dummy_binding, false); } else if import.imported_module.get().is_none() { @@ -445,7 +511,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { prev_indeterminate_count = indeterminate_count; indeterminate_count = 0; for import in mem::take(&mut self.indeterminate_imports) { - let import_indeterminate_count = self.resolve_import(&import); + let import_indeterminate_count = self.resolve_import(import); indeterminate_count += import_indeterminate_count; match import_indeterminate_count { 0 => self.determined_imports.push(import), @@ -457,7 +523,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { pub(crate) fn finalize_imports(&mut self) { for module in self.arenas.local_modules().iter() { - self.finalize_resolutions_in(module); + self.finalize_resolutions_in(*module); } let mut seen_spans = FxHashSet::default(); @@ -467,15 +533,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let indeterminate_imports = mem::take(&mut self.indeterminate_imports); for (is_indeterminate, import) in determined_imports - .into_iter() + .iter() .map(|i| (false, i)) - .chain(indeterminate_imports.into_iter().map(|i| (true, i))) + .chain(indeterminate_imports.iter().map(|i| (true, i))) { - let unresolved_import_error = self.finalize_import(import); + let unresolved_import_error = self.finalize_import(*import); // If this import is unresolved then create a dummy import // resolution for it so that later resolve stages won't complain. - self.import_dummy_binding(import, is_indeterminate); + self.import_dummy_binding(*import, is_indeterminate); if let Some(err) = unresolved_import_error { if let ImportKind::Single { source, ref source_bindings, .. } = import.kind { @@ -497,27 +563,34 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { errors = vec![]; } if seen_spans.insert(err.span) { - errors.push((import, err)); + errors.push((*import, err)); prev_root_id = import.root_id; } - } else if is_indeterminate { - let path = import_path_to_string( - &import.module_path.iter().map(|seg| seg.ident).collect::>(), - &import.kind, - import.span, - ); - let err = UnresolvedImportError { - span: import.span, - label: None, - note: None, - suggestion: None, - candidates: None, - }; - // FIXME: there should be a better way of doing this than - // formatting this as a string then checking for `::` - if path.contains("::") { - errors.push((import, err)) - } + } + } + + if !errors.is_empty() { + self.throw_unresolved_import_error(errors); + return; + } + + for import in &indeterminate_imports { + let path = import_path_to_string( + &import.module_path.iter().map(|seg| seg.ident).collect::>(), + &import.kind, + import.span, + ); + let err = UnresolvedImportError { + span: import.span, + label: None, + note: None, + suggestion: None, + candidates: None, + }; + // FIXME: there should be a better way of doing this than + // formatting this as a string then checking for `::` + if path.contains("::") { + errors.push((*import, err)) } } @@ -526,35 +599,75 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - pub(crate) fn check_reexport_ambiguities( + pub(crate) fn check_hidden_glob_reexports( &mut self, - exported_ambiguities: FxHashSet>>, + exported_ambiguities: FxHashSet>, ) { for module in self.arenas.local_modules().iter() { - module.for_each_child(self, |this, ident, ns, binding| { - if let NameBindingKind::Import { import, .. } = binding.kind - && let Some((amb_binding, _)) = binding.ambiguity - && binding.res() != Res::Err - && exported_ambiguities.contains(&Interned::new_unchecked(binding)) - { - this.lint_buffer.buffer_lint_with_diagnostic( - AMBIGUOUS_GLOB_REEXPORTS, - import.root_id, - import.root_span, - "ambiguous glob re-exports", - BuiltinLintDiagnostics::AmbiguousGlobReexports { - name: ident.to_string(), - namespace: ns.descr().to_string(), - first_reexport_span: import.root_span, - duplicate_reexport_span: amb_binding.span, - }, - ); + for (key, resolution) in self.resolutions(*module).borrow().iter() { + let resolution = resolution.borrow(); + + if let Some(binding) = resolution.binding { + if let NameBindingKind::Import { import, .. } = binding.kind + && let Some((amb_binding, _)) = binding.ambiguity + && binding.res() != Res::Err + && exported_ambiguities.contains(&binding) + { + self.lint_buffer.buffer_lint_with_diagnostic( + AMBIGUOUS_GLOB_REEXPORTS, + import.root_id, + import.root_span, + "ambiguous glob re-exports", + BuiltinLintDiagnostics::AmbiguousGlobReexports { + name: key.ident.to_string(), + namespace: key.ns.descr().to_string(), + first_reexport_span: import.root_span, + duplicate_reexport_span: amb_binding.span, + }, + ); + } + + if let Some(glob_binding) = resolution.shadowed_glob { + let binding_id = match binding.kind { + NameBindingKind::Res(res) => { + Some(self.def_id_to_node_id[res.def_id().expect_local()]) + } + NameBindingKind::Module(module) => { + Some(self.def_id_to_node_id[module.def_id().expect_local()]) + } + NameBindingKind::Import { import, .. } => import.id(), + }; + + if binding.res() != Res::Err + && glob_binding.res() != Res::Err + && let NameBindingKind::Import { import: glob_import, .. } = glob_binding.kind + && let Some(binding_id) = binding_id + && let Some(glob_import_id) = glob_import.id() + && let glob_import_def_id = self.local_def_id(glob_import_id) + && self.effective_visibilities.is_exported(glob_import_def_id) + && glob_binding.vis.is_public() + && !binding.vis.is_public() + { + self.lint_buffer.buffer_lint_with_diagnostic( + HIDDEN_GLOB_REEXPORTS, + binding_id, + binding.span, + "private item shadows public glob re-export", + BuiltinLintDiagnostics::HiddenGlobReexports { + name: key.ident.name.to_string(), + namespace: key.ns.descr().to_owned(), + glob_reexport_span: glob_binding.span, + private_item_span: binding.span, + }, + ); + } + } } - }); + } } } - fn throw_unresolved_import_error(&self, errors: Vec<(&Import<'_>, UnresolvedImportError)>) { + fn throw_unresolved_import_error(&mut self, errors: Vec<(Import<'_>, UnresolvedImportError)>) { if errors.is_empty() { return; } @@ -624,6 +737,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { _ => {} } } + + match &import.kind { + ImportKind::Single { source, .. } => { + if let Some(ModuleOrUniformRoot::Module(module)) = import.imported_module.get() + && let Some(module) = module.opt_def_id() + { + self.find_cfg_stripped(&mut diag, &source.name, module) + } + }, + _ => {} + } } diag.emit(); @@ -635,13 +759,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { /// /// Meanwhile, if resolve successful, the resolved bindings are written /// into the module. - fn resolve_import(&mut self, import: &'a Import<'a>) -> usize { + fn resolve_import(&mut self, import: Import<'a>) -> usize { debug!( "(resolving import for module) resolving import `{}::...` in `{}`", Segment::names_to_string(&import.module_path), module_to_string(import.parent_scope.module).unwrap_or_else(|| "???".to_string()), ); - let module = if let Some(module) = import.imported_module.get() { module } else { @@ -708,14 +831,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } source_binding @ (Ok(..) | Err(Determined)) => { if source_binding.is_ok() { - let msg = format!("`{}` is not directly importable", target); - struct_span_err!(this.tcx.sess, import.span, E0253, "{}", &msg) - .span_label(import.span, "cannot be imported directly") + this.tcx + .sess + .create_err(IsNotDirectlyImportable { span: import.span, target }) .emit(); } let key = BindingKey::new(target, ns); - this.update_resolution(parent, key, |_, resolution| { - resolution.single_imports.remove(&Interned::new_unchecked(import)); + this.update_resolution(parent, key, false, |_, resolution| { + resolution.single_imports.remove(&import); }); } } @@ -729,7 +852,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { /// /// Optionally returns an unresolved import error. This error is buffered and used to /// consolidate multiple unresolved import errors into a single diagnostic. - fn finalize_import(&mut self, import: &'a Import<'a>) -> Option { + fn finalize_import(&mut self, import: Import<'a>) -> Option { let orig_vis = import.vis.take(); let ignore_binding = match &import.kind { ImportKind::Single { target_bindings, .. } => target_bindings[TypeNS].get(), @@ -737,6 +860,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { }; let prev_ambiguity_errors_len = self.ambiguity_errors.len(); let finalize = Finalize::with_root_span(import.root_id, import.span, import.root_span); + + // We'll provide more context to the privacy errors later, up to `len`. + let privacy_errors_len = self.privacy_errors.len(); + let path_res = self.resolve_path( &import.module_path, None, @@ -751,25 +878,46 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { PathResult::Module(module) => { // Consistency checks, analogous to `finalize_macro_resolutions`. if let Some(initial_module) = import.imported_module.get() { - if !ModuleOrUniformRoot::same_def(module, initial_module) && no_ambiguity { + if module != initial_module && no_ambiguity { span_bug!(import.span, "inconsistent resolution for an import"); } } else if self.privacy_errors.is_empty() { - let msg = "cannot determine resolution for the import"; - let msg_note = "import resolution is stuck, try simplifying other imports"; - self.tcx.sess.struct_span_err(import.span, msg).note(msg_note).emit(); + self.tcx + .sess + .create_err(CannotDetermineImportResolution { span: import.span }) + .emit(); } module } - PathResult::Failed { is_error_from_last_segment: false, span, label, suggestion } => { + PathResult::Failed { + is_error_from_last_segment: false, + span, + label, + suggestion, + module, + } => { if no_ambiguity { assert!(import.imported_module.get().is_none()); - self.report_error(span, ResolutionError::FailedToResolve { label, suggestion }); + self.report_error( + span, + ResolutionError::FailedToResolve { + last_segment: None, + label, + suggestion, + module, + }, + ); } return None; } - PathResult::Failed { is_error_from_last_segment: true, span, label, suggestion } => { + PathResult::Failed { + is_error_from_last_segment: true, + span, + label, + suggestion, + .. + } => { if no_ambiguity { assert!(import.imported_module.get().is_none()); let err = match self.make_path_suggestion( @@ -800,8 +948,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } return None; } - PathResult::NonModule(_) => { - if no_ambiguity { + PathResult::NonModule(partial_res) => { + if no_ambiguity && partial_res.full_res() != Some(Res::Err) { + // Check if there are no ambiguities and the result is not dummy. assert!(import.imported_module.get().is_none()); } // The error was already reported earlier. @@ -831,7 +980,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } if let ModuleOrUniformRoot::Module(module) = module { - if ptr::eq(module, import.parent_scope.module) { + if module == import.parent_scope.module { // Importing a module into itself is not allowed. return Some(UnresolvedImportError { span: import.span, @@ -848,14 +997,28 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { && let Some(max_vis) = max_vis.get() && !max_vis.is_at_least(import.expect_vis(), self.tcx) { - let msg = "glob import doesn't reexport anything because no candidate is public enough"; - self.lint_buffer.buffer_lint(UNUSED_IMPORTS, id, import.span, msg); + self.lint_buffer.buffer_lint(UNUSED_IMPORTS, id, import.span, fluent::resolve_glob_import_doesnt_reexport); } return None; } _ => unreachable!(), }; + if self.privacy_errors.len() != privacy_errors_len { + // Get the Res for the last element, so that we can point to alternative ways of + // importing it if available. + let mut path = import.module_path.clone(); + path.push(Segment::from_ident(ident)); + if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = + self.resolve_path(&path, None, &import.parent_scope, Some(finalize), ignore_binding) + { + let res = module.res().map(|r| (r, ident)); + for error in &mut self.privacy_errors[privacy_errors_len..] { + error.outermost_res = res; + } + } + } + let mut all_ns_err = true; self.per_ns(|this, ns| { if !type_ns_only || ns == TypeNS { @@ -873,7 +1036,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { match binding { Ok(binding) => { // Consistency checks, analogous to `finalize_macro_resolutions`. - let initial_binding = source_bindings[ns].get().map(|initial_binding| { + let initial_res = source_bindings[ns].get().map(|initial_binding| { all_ns_err = false; if let Some(target_binding) = target_bindings[ns].get() { if target.name == kw::Underscore @@ -887,29 +1050,30 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ); } } - initial_binding + initial_binding.res() }); let res = binding.res(); - if let Ok(initial_binding) = initial_binding { - let initial_res = initial_binding.res(); - if res != initial_res && this.ambiguity_errors.is_empty() { - this.ambiguity_errors.push(AmbiguityError { - kind: AmbiguityKind::Import, - ident, - b1: initial_binding, - b2: binding, - misc1: AmbiguityErrorMisc::None, - misc2: AmbiguityErrorMisc::None, - }); + let has_ambiguity_error = this + .ambiguity_errors + .iter() + .filter(|error| !error.warning) + .next() + .is_some(); + if res == Res::Err || has_ambiguity_error { + this.tcx + .sess + .delay_span_bug(import.span, "some error happened for an import"); + return; + } + if let Ok(initial_res) = initial_res { + if res != initial_res { + span_bug!(import.span, "inconsistent resolution for an import"); } - } else if res != Res::Err - && this.ambiguity_errors.is_empty() - && this.privacy_errors.is_empty() - { - let msg = "cannot determine resolution for the import"; - let msg_note = - "import resolution is stuck, try simplifying other imports"; - this.tcx.sess.struct_span_err(import.span, msg).note(msg_note).emit(); + } else if this.privacy_errors.is_empty() { + this.tcx + .sess + .create_err(CannotDetermineImportResolution { span: import.span }) + .emit(); } } Err(..) => { @@ -996,18 +1160,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { ModuleOrUniformRoot::Module(module) => { let module_str = module_to_string(module); if let Some(module_str) = module_str { - format!("no `{}` in `{}`", ident, module_str) + format!("no `{ident}` in `{module_str}`") } else { - format!("no `{}` in the root", ident) + format!("no `{ident}` in the root") } } _ => { if !ident.is_path_segment_keyword() { - format!("no external crate `{}`", ident) + format!("no external crate `{ident}`") } else { // HACK(eddyb) this shows up for `self` & `super`, which // should work instead - for now keep the same error message. - format!("no `{}` in the root", ident) + format!("no `{ident}` in the root") } } }; @@ -1055,10 +1219,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let (ns, binding) = reexport_error.unwrap(); if pub_use_of_private_extern_crate_hack(import, binding) { let msg = format!( - "extern crate `{}` is private, and cannot be \ + "extern crate `{ident}` is private, and cannot be \ re-exported (error E0365), consider declaring with \ - `pub`", - ident + `pub`" ); self.lint_buffer.buffer_lint( PUB_USE_OF_PRIVATE_EXTERN_CRATE, @@ -1067,46 +1230,43 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { msg, ); } else { - let error_msg = if crate_private_reexport { - format!( - "`{}` is only public within the crate, and cannot be re-exported outside", - ident - ) - } else { - format!("`{}` is private, and cannot be re-exported", ident) - }; - if ns == TypeNS { - let label_msg = if crate_private_reexport { - format!("re-export of crate public `{}`", ident) + let mut err = if crate_private_reexport { + self.tcx.sess.create_err(CannotBeReexportedCratePublicNS { + span: import.span, + ident, + }) } else { - format!("re-export of private `{}`", ident) + self.tcx + .sess + .create_err(CannotBeReexportedPrivateNS { span: import.span, ident }) }; - - struct_span_err!(self.tcx.sess, import.span, E0365, "{}", error_msg) - .span_label(import.span, label_msg) - .note(format!("consider declaring type or module `{}` with `pub`", ident)) - .emit(); + err.emit(); } else { - let mut err = - struct_span_err!(self.tcx.sess, import.span, E0364, "{error_msg}"); + let mut err = if crate_private_reexport { + self.tcx + .sess + .create_err(CannotBeReexportedCratePublic { span: import.span, ident }) + } else { + self.tcx + .sess + .create_err(CannotBeReexportedPrivate { span: import.span, ident }) + }; + match binding.kind { NameBindingKind::Res(Res::Def(DefKind::Macro(_), def_id)) // exclude decl_macro if self.get_macro_by_def_id(def_id).macro_rules => { - err.span_help( - binding.span, - "consider adding a `#[macro_export]` to the macro in the imported module", - ); + err.subdiagnostic(ConsiderAddingMacroExport { + span: binding.span, + }); } _ => { - err.span_note( - import.span, - format!( - "consider marking `{ident}` as `pub` in the imported module" - ), - ); + err.subdiagnostic(ConsiderMarkingAsPub { + span: import.span, + ident, + }); } } err.emit(); @@ -1144,9 +1304,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { fn check_for_redundant_imports( &mut self, ident: Ident, - import: &'a Import<'a>, - source_bindings: &PerNS, Determinacy>>>, - target_bindings: &PerNS>>>, + import: Import<'a>, + source_bindings: &PerNS, Determinacy>>>, + target_bindings: &PerNS>>>, target: Ident, ) { // This function is only called for single imports. @@ -1175,7 +1335,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { match this.early_resolve_ident_in_lexical_scope( target, - ScopeSet::All(ns, false), + ScopeSet::All(ns), &import.parent_scope, None, false, @@ -1201,25 +1361,25 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { UNUSED_IMPORTS, id, import.span, - format!("the item `{}` is imported redundantly", ident), + format!("the item `{ident}` is imported redundantly"), BuiltinLintDiagnostics::RedundantImport(redundant_spans, ident), ); } } - fn resolve_glob_import(&mut self, import: &'a Import<'a>) { + fn resolve_glob_import(&mut self, import: Import<'a>) { // This function is only called for glob imports. let ImportKind::Glob { id, is_prelude, .. } = import.kind else { unreachable!() }; let ModuleOrUniformRoot::Module(module) = import.imported_module.get().unwrap() else { - self.tcx.sess.span_err(import.span, "cannot glob-import all possible crates"); + self.tcx.sess.create_err(CannotGlobImportAllCrates { span: import.span }).emit(); return; }; if module.is_trait() { - self.tcx.sess.span_err(import.span, "items in traits are not importable"); + self.tcx.sess.create_err(ItemsInTraitsAreNotImportable { span: import.span }).emit(); return; - } else if ptr::eq(module, import.parent_scope.module) { + } else if module == import.parent_scope.module { return; } else if is_prelude { self.prelude = Some(module); @@ -1247,7 +1407,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { }; if self.is_accessible_from(binding.vis, scope) { let imported_binding = self.import(binding, import); - let _ = self.try_define(import.parent_scope.module, key, imported_binding); + let warn_ambiguity = self + .resolution(import.parent_scope.module, key) + .borrow() + .binding() + .is_some_and(|binding| binding.is_warn_ambiguity()); + let _ = self.try_define( + import.parent_scope.module, + key, + imported_binding, + warn_ambiguity, + ); } } @@ -1266,7 +1436,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { module.for_each_child(self, |this, ident, _, binding| { let res = binding.res().expect_non_local(); - if res != def::Res::Err && !binding.is_ambiguity() { + let error_ambiguity = binding.is_ambiguity() && !binding.warn_ambiguity; + if res != def::Res::Err && !error_ambiguity { let mut reexport_chain = SmallVec::new(); let mut next_binding = binding; while let NameBindingKind::Import { binding, import, .. } = next_binding.kind { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index e0611907613c4..c87db96a5dd92 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -6,6 +6,7 @@ //! If you wonder why there's no `early.rs`, that's because it's split into three files - //! `build_reduced_graph.rs`, `macros.rs` and `imports.rs`. +use crate::errors::ImportsCannotReferTo; use crate::BindingKey; use crate::{path_names_to_string, rustdoc, BindingError, Finalize, LexicalScopeBinding}; use crate::{Module, ModuleOrUniformRoot, NameBinding, ParentScope, PathResult}; @@ -336,6 +337,7 @@ enum LifetimeBinderKind { PolyTrait, WhereBound, Item, + ConstItem, Function, Closure, ImplBlock, @@ -348,7 +350,7 @@ impl LifetimeBinderKind { BareFnType => "type", PolyTrait => "bound", WhereBound => "bound", - Item => "item", + Item | ConstItem => "item", ImplBlock => "impl block", Function => "function", Closure => "closure", @@ -468,7 +470,7 @@ impl<'a> PathSource<'a> { | DefKind::Enum | DefKind::Trait | DefKind::TraitAlias - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::AssocTy | DefKind::TyParam | DefKind::OpaqueTy @@ -507,7 +509,7 @@ impl<'a> PathSource<'a> { DefKind::Struct | DefKind::Union | DefKind::Variant - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::AssocTy, _, ) | Res::SelfTyParam { .. } @@ -548,6 +550,7 @@ enum MaybeExported<'a> { Ok(NodeId), Impl(Option), ImplItem(Result), + NestedUse(&'a Visibility), } impl MaybeExported<'_> { @@ -558,7 +561,9 @@ impl MaybeExported<'_> { trait_def_id.as_local() } MaybeExported::Impl(None) => return true, - MaybeExported::ImplItem(Err(vis)) => return vis.kind.is_pub(), + MaybeExported::ImplItem(Err(vis)) | MaybeExported::NestedUse(vis) => { + return vis.kind.is_pub(); + } }; def_id.map_or(true, |def_id| r.effective_visibilities.is_exported(def_id)) } @@ -899,9 +904,12 @@ impl<'a: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast, sig.decl.inputs.iter().map(|Param { ty, .. }| (None, &**ty)), &sig.decl.output, ); + + if let Some((async_node_id, span)) = sig.header.asyncness.opt_return_id() { + this.record_lifetime_params_for_impl_trait(async_node_id, span); + } }, ); - self.record_lifetime_params_for_async(fn_id, sig.header.asyncness.opt_return_id()); return; } FnKind::Fn(..) => { @@ -937,12 +945,14 @@ impl<'a: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast, .iter() .map(|Param { pat, ty, .. }| (Some(&**pat), &**ty)), &declaration.output, - ) + ); + + if let Some((async_node_id, span)) = async_node_id { + this.record_lifetime_params_for_impl_trait(async_node_id, span); + } }, ); - this.record_lifetime_params_for_async(fn_id, async_node_id); - if let Some(body) = body { // Ignore errors in function bodies if this is rustdoc // Be sure not to set this until the function signature has been resolved. @@ -1283,7 +1293,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { ident: Ident, ns: Namespace, finalize: Option, - ignore_binding: Option<&'a NameBinding<'a>>, + ignore_binding: Option>, ) -> Option> { self.r.resolve_ident_in_lexical_scope( ident, @@ -1360,7 +1370,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { fn visit_generic_params(&mut self, params: &'ast [GenericParam], add_self_upper: bool) { // For type parameter defaults, we have to ban access - // to following type parameters, as the InternalSubsts can only + // to following type parameters, as the GenericArgs can only // provide previous type parameters as they're built. We // put all the parameters on the ban list and then remove // them one by one as they are processed and become available. @@ -1631,9 +1641,13 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { .. } = &rib.kind { - diag.span_help( - *span, - "consider introducing a higher-ranked lifetime here with `for<'a>`", + diag.multipart_suggestion( + "consider introducing a higher-ranked lifetime here", + vec![ + (span.shrink_to_lo(), "for<'a> ".into()), + (lifetime.ident.span.shrink_to_hi(), "'a ".into()), + ], + Applicability::MachineApplicable, ); break; } @@ -1685,6 +1699,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { // Leave the responsibility to create the `LocalDefId` to lowering. let param = self.r.next_node_id(); let res = LifetimeRes::Fresh { param, binder }; + self.record_lifetime_param(param, res); // Record the created lifetime parameter so lowering can pick it up and add it to HIR. self.r @@ -1725,7 +1740,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { Res::Def(DefKind::Struct, def_id) | Res::Def(DefKind::Union, def_id) | Res::Def(DefKind::Enum, def_id) - | Res::Def(DefKind::TyAlias, def_id) + | Res::Def(DefKind::TyAlias { .. }, def_id) | Res::Def(DefKind::Trait, def_id) if i + 1 == proj_start => { @@ -1908,10 +1923,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { candidate: LifetimeElisionCandidate, ) { if let Some(prev_res) = self.r.lifetimes_res_map.insert(id, res) { - panic!( - "lifetime {:?} resolved multiple times ({:?} before, {:?} now)", - id, prev_res, res - ) + panic!("lifetime {id:?} resolved multiple times ({prev_res:?} before, {res:?} now)") } match res { LifetimeRes::Param { .. } | LifetimeRes::Fresh { .. } | LifetimeRes::Static => { @@ -1927,8 +1939,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { fn record_lifetime_param(&mut self, id: NodeId, res: LifetimeRes) { if let Some(prev_res) = self.r.lifetimes_res_map.insert(id, res) { panic!( - "lifetime parameter {:?} resolved multiple times ({:?} before, {:?} now)", - id, prev_res, res + "lifetime parameter {id:?} resolved multiple times ({prev_res:?} before, {res:?} now)" ) } } @@ -2244,12 +2255,13 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { _ => &[TypeNS], }; let report_error = |this: &Self, ns| { - let what = if ns == TypeNS { "type parameters" } else { "local variables" }; if this.should_report_errs() { + let what = if ns == TypeNS { "type parameters" } else { "local variables" }; this.r .tcx .sess - .span_err(ident.span, format!("imports cannot refer to {}", what)); + .create_err(ImportsCannotReferTo { span: ident.span, what }) + .emit(); } }; @@ -2278,7 +2290,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { fn resolve_item(&mut self, item: &'ast Item) { let mod_inner_docs = matches!(item.kind, ItemKind::Mod(..)) && rustdoc::inner_docs(&item.attrs); - if !mod_inner_docs && !matches!(item.kind, ItemKind::Impl(..)) { + if !mod_inner_docs && !matches!(item.kind, ItemKind::Impl(..) | ItemKind::Use(..)) { self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); } @@ -2395,33 +2407,53 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { }); } - ItemKind::Static(box ast::StaticItem { ref ty, ref expr, .. }) - | ItemKind::Const(box ast::ConstItem { ref ty, ref expr, .. }) => { + ItemKind::Static(box ast::StaticItem { ref ty, ref expr, .. }) => { self.with_static_rib(|this| { this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Static), |this| { this.visit_ty(ty); }); - this.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + if let Some(expr) = expr { + // We already forbid generic params because of the above item rib, + // so it doesn't matter whether this is a trivial constant. + this.resolve_const_body(expr, Some((item.ident, ConstantItemKind::Static))); + } + }); + } + + ItemKind::Const(box ast::ConstItem { ref generics, ref ty, ref expr, .. }) => { + self.with_generic_param_rib( + &generics.params, + RibKind::Item(HasGenericParams::Yes(generics.span)), + LifetimeRibKind::Generics { + binder: item.id, + kind: LifetimeBinderKind::ConstItem, + span: generics.span, + }, + |this| { + this.visit_generics(generics); + + this.with_lifetime_rib( + LifetimeRibKind::Elided(LifetimeRes::Static), + |this| this.visit_ty(ty), + ); + if let Some(expr) = expr { - let constant_item_kind = match item.kind { - ItemKind::Const(..) => ConstantItemKind::Const, - ItemKind::Static(..) => ConstantItemKind::Static, - _ => unreachable!(), - }; - // We already forbid generic params because of the above item rib, - // so it doesn't matter whether this is a trivial constant. - this.with_constant_rib( - IsRepeatExpr::No, - ConstantHasGenerics::Yes, - Some((item.ident, constant_item_kind)), - |this| this.visit_expr(expr), + this.resolve_const_body( + expr, + Some((item.ident, ConstantItemKind::Const)), ); } - }); - }); + }, + ); } ItemKind::Use(ref use_tree) => { + let maybe_exported = match use_tree.kind { + UseTreeKind::Simple(_) | UseTreeKind::Glob => MaybeExported::Ok(item.id), + UseTreeKind::Nested(_) => MaybeExported::NestedUse(&item.vis), + }; + self.resolve_doc_links(&item.attrs, maybe_exported); + self.future_proof_import(use_tree); } @@ -2454,8 +2486,11 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { F: FnOnce(&mut Self), { debug!("with_generic_param_rib"); - let LifetimeRibKind::Generics { binder, span: generics_span, kind: generics_kind, .. } - = lifetime_kind else { panic!() }; + let LifetimeRibKind::Generics { binder, span: generics_span, kind: generics_kind, .. } = + lifetime_kind + else { + panic!() + }; let mut function_type_rib = Rib::new(kind); let mut function_value_rib = Rib::new(kind); @@ -2560,7 +2595,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { let res = match kind { RibKind::Item(..) | RibKind::AssocItem => Res::Def(def_kind, def_id.to_def_id()), RibKind::Normal => { - if self.r.tcx.sess.features_untracked().non_lifetime_binders { + if self.r.tcx.features().non_lifetime_binders { Res::Def(def_kind, def_id.to_def_id()) } else { Res::Err @@ -2682,28 +2717,31 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { for item in trait_items { self.resolve_doc_links(&item.attrs, MaybeExported::Ok(item.id)); match &item.kind { - AssocItemKind::Const(box ast::ConstItem { ty, expr, .. }) => { - self.visit_ty(ty); - // Only impose the restrictions of `ConstRibKind` for an - // actual constant expression in a provided default. - if let Some(expr) = expr { - // We allow arbitrary const expressions inside of associated consts, - // even if they are potentially not const evaluatable. - // - // Type parameters can already be used and as associated consts are - // not used as part of the type system, this is far less surprising. - self.with_lifetime_rib( - LifetimeRibKind::Elided(LifetimeRes::Infer), - |this| { - this.with_constant_rib( - IsRepeatExpr::No, - ConstantHasGenerics::Yes, - None, - |this| this.visit_expr(expr), - ) - }, - ); - } + AssocItemKind::Const(box ast::ConstItem { generics, ty, expr, .. }) => { + self.with_generic_param_rib( + &generics.params, + RibKind::AssocItem, + LifetimeRibKind::Generics { + binder: item.id, + span: generics.span, + kind: LifetimeBinderKind::ConstItem, + }, + |this| { + this.visit_generics(generics); + this.visit_ty(ty); + + // Only impose the restrictions of `ConstRibKind` for an + // actual constant expression in a provided default. + if let Some(expr) = expr { + // We allow arbitrary const expressions inside of associated consts, + // even if they are potentially not const evaluatable. + // + // Type parameters can already be used and as associated consts are + // not used as part of the type system, this is far less surprising. + this.resolve_const_body(expr, None); + } + }, + ); } AssocItemKind::Fn(box Fn { generics, .. }) => { walk_assoc_item(self, generics, LifetimeBinderKind::Function, item); @@ -2858,36 +2896,42 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { use crate::ResolutionError::*; self.resolve_doc_links(&item.attrs, MaybeExported::ImplItem(trait_id.ok_or(&item.vis))); match &item.kind { - AssocItemKind::Const(box ast::ConstItem { ty, expr, .. }) => { + AssocItemKind::Const(box ast::ConstItem { generics, ty, expr, .. }) => { debug!("resolve_implementation AssocItemKind::Const"); - // If this is a trait impl, ensure the const - // exists in trait - self.check_trait_item( - item.id, - item.ident, - &item.kind, - ValueNS, - item.span, - seen_trait_items, - |i, s, c| ConstNotMemberOfTrait(i, s, c), - ); - self.visit_ty(ty); - if let Some(expr) = expr { - // We allow arbitrary const expressions inside of associated consts, - // even if they are potentially not const evaluatable. - // - // Type parameters can already be used and as associated consts are - // not used as part of the type system, this is far less surprising. - self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { - this.with_constant_rib( - IsRepeatExpr::No, - ConstantHasGenerics::Yes, - None, - |this| this.visit_expr(expr), - ) - }); - } + self.with_generic_param_rib( + &generics.params, + RibKind::AssocItem, + LifetimeRibKind::Generics { + binder: item.id, + span: generics.span, + kind: LifetimeBinderKind::ConstItem, + }, + |this| { + // If this is a trait impl, ensure the const + // exists in trait + this.check_trait_item( + item.id, + item.ident, + &item.kind, + ValueNS, + item.span, + seen_trait_items, + |i, s, c| ConstNotMemberOfTrait(i, s, c), + ); + + this.visit_generics(generics); + this.visit_ty(ty); + if let Some(expr) = expr { + // We allow arbitrary const expressions inside of associated consts, + // even if they are potentially not const evaluatable. + // + // Type parameters can already be used and as associated consts are + // not used as part of the type system, this is far less surprising. + this.resolve_const_body(expr, None); + } + }, + ); } AssocItemKind::Fn(box Fn { generics, .. }) => { debug!("resolve_implementation AssocItemKind::Fn"); @@ -2966,7 +3010,9 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { F: FnOnce(Ident, String, Option) -> ResolutionError<'a>, { // If there is a TraitRef in scope for an impl, then the method must be in the trait. - let Some((module, _)) = &self.current_trait_ref else { return; }; + let Some((module, _)) = self.current_trait_ref else { + return; + }; ident.span.normalize_to_macros_2_0_and_adjust(module.expansion); let key = BindingKey::new(ident, ns); let mut binding = self.r.resolution(module, key).try_borrow().ok().and_then(|r| r.binding); @@ -3043,6 +3089,14 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { ); } + fn resolve_const_body(&mut self, expr: &'ast Expr, item: Option<(Ident, ConstantItemKind)>) { + self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { + this.with_constant_rib(IsRepeatExpr::No, ConstantHasGenerics::Yes, item, |this| { + this.visit_expr(expr) + }); + }) + } + fn resolve_params(&mut self, params: &'ast [Param]) { let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())]; self.with_lifetime_rib(LifetimeRibKind::Elided(LifetimeRes::Infer), |this| { @@ -3497,7 +3551,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { let report_errors = |this: &mut Self, res: Option| { if this.should_report_errs() { let (err, candidates) = - this.smart_resolve_report_errors(path, path_span, source, res); + this.smart_resolve_report_errors(path, None, path_span, source, res); let def_id = this.parent_scope.module.nearest_parent_mod(); let instead = res.is_some(); @@ -3524,7 +3578,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { None }; - this.r.use_injections.push(UseError { + let ue = UseError { err, candidates, def_id, @@ -3532,7 +3586,9 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { suggestion, path: path.into(), is_call: source.is_call(), - }); + }; + + this.r.use_injections.push(ue); } PartialRes::new(Res::Err) @@ -3547,13 +3603,18 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { // Before we start looking for candidates, we have to get our hands // on the type user is trying to perform invocation on; basically: // we're transforming `HashMap::new` into just `HashMap`. - let prefix_path = match path.split_last() { - Some((_, path)) if !path.is_empty() => path, + let (following_seg, prefix_path) = match path.split_last() { + Some((last, path)) if !path.is_empty() => (Some(last), path), _ => return Some(parent_err), }; - let (mut err, candidates) = - this.smart_resolve_report_errors(prefix_path, path_span, PathSource::Type, None); + let (mut err, candidates) = this.smart_resolve_report_errors( + prefix_path, + following_seg, + path_span, + PathSource::Type, + None, + ); // There are two different error messages user might receive at // this point: @@ -3866,8 +3927,22 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { PathResult::Module(ModuleOrUniformRoot::Module(module)) => { PartialRes::new(module.res().unwrap()) } - PathResult::Failed { is_error_from_last_segment: false, span, label, suggestion } => { - return Err(respan(span, ResolutionError::FailedToResolve { label, suggestion })); + PathResult::Failed { + is_error_from_last_segment: false, + span, + label, + suggestion, + module, + } => { + return Err(respan( + span, + ResolutionError::FailedToResolve { + last_segment: None, + label, + suggestion, + module, + }, + )); } PathResult::Module(..) | PathResult::Failed { .. } => return Ok(None), PathResult::Indeterminate => bug!("indeterminate path result in resolve_qpath"), @@ -3875,12 +3950,14 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { if path.len() > 1 && let Some(res) = result.full_res() + && let Some((&last_segment, prev_segs)) = path.split_last() + && prev_segs.iter().all(|seg| !seg.has_generic_args) && res != Res::Err && path[0].ident.name != kw::PathRoot && path[0].ident.name != kw::DollarCrate { let unqualified_result = { - match self.resolve_path(&[*path.last().unwrap()], Some(ns), None) { + match self.resolve_path(&[last_segment], Some(ns), None) { PathResult::NonModule(path_res) => path_res.expect_full_res(), PathResult::Module(ModuleOrUniformRoot::Module(module)) => { module.res().unwrap() @@ -3890,11 +3967,14 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { }; if res == unqualified_result { let lint = lint::builtin::UNUSED_QUALIFICATIONS; - self.r.lint_buffer.buffer_lint( + self.r.lint_buffer.buffer_lint_with_diagnostic( lint, finalize.node_id, finalize.path_span, "unnecessary qualification", + lint::BuiltinLintDiagnostics::UnusedQualifications { + removal_span: finalize.path_span.until(last_segment.ident.span), + } ) } } @@ -4196,7 +4276,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { ExprKind::ConstBlock(ref ct) => { self.resolve_anon_const(ct, AnonConstKind::InlineConst); } - ExprKind::Index(ref elem, ref idx) => { + ExprKind::Index(ref elem, ref idx, _) => { self.resolve_expr(elem, Some(expr)); self.visit_expr(idx); } @@ -4252,39 +4332,32 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { ) } - /// Construct the list of in-scope lifetime parameters for async lowering. + /// Construct the list of in-scope lifetime parameters for impl trait lowering. /// We include all lifetime parameters, either named or "Fresh". /// The order of those parameters does not matter, as long as it is /// deterministic. - fn record_lifetime_params_for_async( - &mut self, - fn_id: NodeId, - async_node_id: Option<(NodeId, Span)>, - ) { - if let Some((async_node_id, span)) = async_node_id { - let mut extra_lifetime_params = - self.r.extra_lifetime_params_map.get(&fn_id).cloned().unwrap_or_default(); - for rib in self.lifetime_ribs.iter().rev() { - extra_lifetime_params.extend( - rib.bindings.iter().map(|(&ident, &(node_id, res))| (ident, node_id, res)), - ); - match rib.kind { - LifetimeRibKind::Item => break, - LifetimeRibKind::AnonymousCreateParameter { binder, .. } => { - if let Some(earlier_fresh) = self.r.extra_lifetime_params_map.get(&binder) { - extra_lifetime_params.extend(earlier_fresh); - } - } - LifetimeRibKind::Generics { .. } => {} - _ => { - // We are in a function definition. We should only find `Generics` - // and `AnonymousCreateParameter` inside the innermost `Item`. - span_bug!(span, "unexpected rib kind: {:?}", rib.kind) + fn record_lifetime_params_for_impl_trait(&mut self, impl_trait_node_id: NodeId, span: Span) { + let mut extra_lifetime_params = vec![]; + + for rib in self.lifetime_ribs.iter().rev() { + extra_lifetime_params + .extend(rib.bindings.iter().map(|(&ident, &(node_id, res))| (ident, node_id, res))); + match rib.kind { + LifetimeRibKind::Item => break, + LifetimeRibKind::AnonymousCreateParameter { binder, .. } => { + if let Some(earlier_fresh) = self.r.extra_lifetime_params_map.get(&binder) { + extra_lifetime_params.extend(earlier_fresh); } } + LifetimeRibKind::Generics { .. } => {} + _ => { + // We are in a function definition. We should only find `Generics` + // and `AnonymousCreateParameter` inside the innermost `Item`. + span_bug!(span, "unexpected rib kind: {:?}", rib.kind) + } } - self.r.extra_lifetime_params_map.insert(async_node_id, extra_lifetime_params); } + self.r.extra_lifetime_params_map.insert(impl_trait_node_id, extra_lifetime_params); } fn resolve_and_cache_rustdoc_path(&mut self, path_str: &str, ns: Namespace) -> Option { @@ -4301,7 +4374,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { if let Some(res) = res && let Some(def_id) = res.opt_def_id() && !def_id.is_local() - && self.r.tcx.sess.crate_types().contains(&CrateType::ProcMacro) + && self.r.tcx.crate_types().contains(&CrateType::ProcMacro) && matches!(self.r.tcx.sess.opts.resolve_doc_links, ResolveDocLinks::ExportedMetadata) { // Encoding foreign def ids in proc macro crate metadata will ICE. return None; @@ -4316,7 +4389,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { match self.r.tcx.sess.opts.resolve_doc_links { ResolveDocLinks::None => return, ResolveDocLinks::ExportedMetadata - if !self.r.tcx.sess.crate_types().iter().copied().any(CrateType::has_metadata) + if !self.r.tcx.crate_types().iter().copied().any(CrateType::has_metadata) || !maybe_exported.eval(self.r) => { return; @@ -4375,7 +4448,7 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { .into_iter() .filter_map(|tr| { if !tr.def_id.is_local() - && self.r.tcx.sess.crate_types().contains(&CrateType::ProcMacro) + && self.r.tcx.crate_types().contains(&CrateType::ProcMacro) && matches!( self.r.tcx.sess.opts.resolve_doc_links, ResolveDocLinks::ExportedMetadata @@ -4403,6 +4476,7 @@ impl<'ast> Visitor<'ast> for LifetimeCountVisitor<'_, '_, '_> { fn visit_item(&mut self, item: &'ast Item) { match &item.kind { ItemKind::TyAlias(box TyAlias { ref generics, .. }) + | ItemKind::Const(box ConstItem { ref generics, .. }) | ItemKind::Fn(box Fn { ref generics, .. }) | ItemKind::Enum(_, ref generics) | ItemKind::Struct(_, ref generics) @@ -4422,7 +4496,6 @@ impl<'ast> Visitor<'ast> for LifetimeCountVisitor<'_, '_, '_> { ItemKind::Mod(..) | ItemKind::ForeignMod(..) | ItemKind::Static(..) - | ItemKind::Const(..) | ItemKind::Use(..) | ItemKind::ExternCrate(..) | ItemKind::MacroDef(..) diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index df65825802e12..c34b7df9b4603 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -4,20 +4,20 @@ use crate::late::{LifetimeBinderKind, LifetimeRes, LifetimeRibKind, LifetimeUseS use crate::{errors, path_names_to_string}; use crate::{Module, ModuleKind, ModuleOrUniformRoot}; use crate::{PathResult, PathSource, Segment}; +use rustc_hir::def::Namespace::{self, *}; use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt}; use rustc_ast::{ self as ast, AssocItemKind, Expr, ExprKind, GenericParam, GenericParamKind, Item, ItemKind, MethodCall, NodeId, Path, Ty, TyKind, DUMMY_NODE_ID, }; -use rustc_ast_pretty::pprust::path_segment_to_string; +use rustc_ast_pretty::pprust::where_bound_predicate_to_string; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{ pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, }; use rustc_hir as hir; -use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, CtorOf, DefKind}; use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; use rustc_hir::PrimTy; @@ -29,6 +29,7 @@ use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::Span; +use std::borrow::Cow; use std::iter; use std::ops::Deref; @@ -148,6 +149,7 @@ struct BaseError { span_label: Option<(Span, &'static str)>, could_be_expr: bool, suggestion: Option<(Span, &'static str, String)>, + module: Option, } #[derive(Debug)] @@ -209,19 +211,24 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { _ => false, }, suggestion: None, + module: None, } } else { let item_span = path.last().unwrap().ident.span; - let (mod_prefix, mod_str, suggestion) = if path.len() == 1 { + let (mod_prefix, mod_str, module, suggestion) = if path.len() == 1 { debug!(?self.diagnostic_metadata.current_impl_items); debug!(?self.diagnostic_metadata.current_function); let suggestion = if self.current_trait_ref.is_none() && let Some((fn_kind, _)) = self.diagnostic_metadata.current_function && let Some(FnCtxt::Assoc(_)) = fn_kind.ctxt() + && let FnKind::Fn(_, _, sig, ..) = fn_kind && let Some(items) = self.diagnostic_metadata.current_impl_items && let Some(item) = items.iter().find(|i| { if let AssocItemKind::Fn(..) | AssocItemKind::Const(..) = &i.kind && i.ident.name == item_str.name + // don't suggest if the item is in Fn signature arguments + // issue #112590 + && !sig.span.contains(item_span) { debug!(?item_str.name); return true @@ -246,26 +253,37 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } else { None }; - (String::new(), "this scope".to_string(), suggestion) + (String::new(), "this scope".to_string(), None, suggestion) } else if path.len() == 2 && path[0].ident.name == kw::PathRoot { if self.r.tcx.sess.edition() > Edition::Edition2015 { // In edition 2018 onwards, the `::foo` syntax may only pull from the extern prelude // which overrides all other expectations of item type expected = "crate"; - (String::new(), "the list of imported crates".to_string(), None) + (String::new(), "the list of imported crates".to_string(), None, None) } else { - (String::new(), "the crate root".to_string(), None) + ( + String::new(), + "the crate root".to_string(), + Some(CRATE_DEF_ID.to_def_id()), + None, + ) } } else if path.len() == 2 && path[0].ident.name == kw::Crate { - (String::new(), "the crate root".to_string(), None) + (String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), None) } else { let mod_path = &path[..path.len() - 1]; - let mod_prefix = match self.resolve_path(mod_path, Some(TypeNS), None) { + let mod_res = self.resolve_path(mod_path, Some(TypeNS), None); + let mod_prefix = match mod_res { PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(), _ => None, - } - .map_or_else(String::new, |res| format!("{} ", res.descr())); - (mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), None) + }; + + let module_did = mod_prefix.as_ref().and_then(Res::mod_def_id); + + let mod_prefix = + mod_prefix.map_or_else(String::new, |res| (format!("{} ", res.descr()))); + + (mod_prefix, format!("`{}`", Segment::names_to_string(mod_path)), module_did, None) }; let (fallback_label, suggestion) = if path_str == "async" @@ -299,21 +317,65 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { span_label: None, could_be_expr: false, suggestion, + module, } } } + /// Try to suggest for a module path that cannot be resolved. + /// Such as `fmt::Debug` where `fmt` is not resolved without importing, + /// here we search with `lookup_import_candidates` for a module named `fmt` + /// with `TypeNS` as namespace. + /// + /// We need a separate function here because we won't suggest for a path with single segment + /// and we won't change `SourcePath` api `is_expected` to match `Type` with `DefKind::Mod` + pub(crate) fn smart_resolve_partial_mod_path_errors( + &mut self, + prefix_path: &[Segment], + following_seg: Option<&Segment>, + ) -> Vec { + if let Some(segment) = prefix_path.last() && + let Some(following_seg) = following_seg + { + let candidates = self.r.lookup_import_candidates( + segment.ident, + Namespace::TypeNS, + &self.parent_scope, + &|res: Res| matches!(res, Res::Def(DefKind::Mod, _)), + ); + // double check next seg is valid + candidates + .into_iter() + .filter(|candidate| { + if let Some(def_id) = candidate.did && + let Some(module) = self.r.get_module(def_id) { + Some(def_id) != self.parent_scope.module.opt_def_id() && + self.r.resolutions(module).borrow().iter().any(|(key, _r)| { + key.ident.name == following_seg.ident.name + }) + } else { + false + } + }) + .collect::>() + } else { + Vec::new() + } + } + /// Handles error reporting for `smart_resolve_path_fragment` function. /// Creates base error and amends it with one short label and possibly some longer helps/notes. pub(crate) fn smart_resolve_report_errors( &mut self, path: &[Segment], + following_seg: Option<&Segment>, span: Span, source: PathSource<'_>, res: Option, ) -> (DiagnosticBuilder<'tcx, ErrorGuaranteed>, Vec) { debug!(?res, ?source); let base_error = self.make_base_error(path, span, source, res); + let code = source.error_code(res.is_some()); let mut err = self.r.tcx.sess.struct_span_err_with_code( base_error.span, @@ -347,8 +409,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { return (err, Vec::new()); } - let (found, candidates) = - self.try_lookup_name_relaxed(&mut err, source, path, span, res, &base_error); + let (found, candidates) = self.try_lookup_name_relaxed( + &mut err, + source, + path, + following_seg, + span, + res, + &base_error, + ); if found { return (err, candidates); } @@ -357,7 +426,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { // if we have suggested using pattern matching, then don't add needless suggestions // for typos. - fallback |= self.suggest_typo(&mut err, source, path, span, &base_error); + fallback |= self.suggest_typo(&mut err, source, path, following_seg, span, &base_error); if fallback { // Fallback label. @@ -365,6 +434,10 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } self.err_code_special_cases(&mut err, source, path, span); + if let Some(module) = base_error.module { + self.r.find_cfg_stripped(&mut err, &path.last().unwrap().ident.name, module); + } + (err, candidates) } @@ -373,20 +446,29 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { err: &mut Diagnostic, base_error: &BaseError, ) { - let Some(ty) = self.diagnostic_metadata.current_type_path else { return; }; - let TyKind::Path(_, path) = &ty.kind else { return; }; + let Some(ty) = self.diagnostic_metadata.current_type_path else { + return; + }; + let TyKind::Path(_, path) = &ty.kind else { + return; + }; for segment in &path.segments { - let Some(params) = &segment.args else { continue; }; - let ast::GenericArgs::AngleBracketed(ref params) = params.deref() else { continue; }; + let Some(params) = &segment.args else { + continue; + }; + let ast::GenericArgs::AngleBracketed(ref params) = params.deref() else { + continue; + }; for param in ¶ms.args { - let ast::AngleBracketedArg::Constraint(constraint) = param else { continue; }; + let ast::AngleBracketedArg::Constraint(constraint) = param else { + continue; + }; let ast::AssocConstraintKind::Bound { bounds } = &constraint.kind else { continue; }; for bound in bounds { - let ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifier::None) - = bound else - { + let ast::GenericBound::Trait(trait_ref, ast::TraitBoundModifier::None) = bound + else { continue; }; if base_error.span == trait_ref.span { @@ -450,6 +532,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { err: &mut Diagnostic, source: PathSource<'_>, path: &[Segment], + following_seg: Option<&Segment>, span: Span, res: Option, base_error: &BaseError, @@ -472,19 +555,19 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } }) .collect::>(); - let crate_def_id = CRATE_DEF_ID.to_def_id(); // Try to filter out intrinsics candidates, as long as we have // some other candidates to suggest. let intrinsic_candidates: Vec<_> = candidates - .drain_filter(|sugg| { + .extract_if(|sugg| { let path = path_names_to_string(&sugg.path); path.starts_with("core::intrinsics::") || path.starts_with("std::intrinsics::") }) .collect(); if candidates.is_empty() { // Put them back if we have no more candidates to suggest... - candidates.extend(intrinsic_candidates); + candidates = intrinsic_candidates; } + let crate_def_id = CRATE_DEF_ID.to_def_id(); if candidates.is_empty() && is_expected(Res::Def(DefKind::Enum, crate_def_id)) { let mut enum_candidates: Vec<_> = self .r @@ -502,13 +585,13 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { let others = match enum_candidates.len() { 1 => String::new(), 2 => " and 1 other".to_owned(), - n => format!(" and {} others", n), + n => format!(" and {n} others"), }; format!("there is an enum variant `{}`{}; ", enum_candidates[0].0, others) } else { String::new() }; - let msg = format!("{}try using the variant's enum", preamble); + let msg = format!("{preamble}try using the variant's enum"); err.span_suggestions( span, @@ -520,8 +603,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } // Try finding a suitable replacement. - let typo_sugg = - self.lookup_typo_candidate(path, source.namespace(), is_expected).to_opt_suggestion(); + let typo_sugg = self + .lookup_typo_candidate(path, following_seg, source.namespace(), is_expected) + .to_opt_suggestion(); if path.len() == 1 && self.self_type_is_available() { if let Some(candidate) = self.lookup_assoc_candidate(ident, ns, is_expected, source.is_call()) @@ -612,13 +696,17 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { ident.name == path[0].ident.name { err.span_help( ident.span, - format!("the binding `{}` is available in a different scope in the same function", path_str), + format!("the binding `{path_str}` is available in a different scope in the same function"), ); return (true, candidates); } } } + if candidates.is_empty() { + candidates = self.smart_resolve_partial_mod_path_errors(path, following_seg); + } + return (false, candidates); } @@ -702,12 +790,14 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { err: &mut Diagnostic, source: PathSource<'_>, path: &[Segment], + following_seg: Option<&Segment>, span: Span, base_error: &BaseError, ) -> bool { let is_expected = &|res| source.is_expected(res); let ident_span = path.last().map_or(span, |ident| ident.ident.span); - let typo_sugg = self.lookup_typo_candidate(path, source.namespace(), is_expected); + let typo_sugg = + self.lookup_typo_candidate(path, following_seg, source.namespace(), is_expected); let is_in_same_file = &|sp1, sp2| { let source_map = self.r.tcx.sess.source_map(); let file1 = source_map.span_to_filename(sp1); @@ -768,7 +858,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { for label_rib in &self.label_ribs { for (label_ident, node_id) in &label_rib.bindings { let ident = path.last().unwrap().ident; - if format!("'{}", ident) == label_ident.to_string() { + if format!("'{ident}") == label_ident.to_string() { err.span_label(label_ident.span, "a label with a similar name exists"); if let PathSource::Expr(Some(Expr { kind: ExprKind::Break(None, Some(_)), @@ -893,7 +983,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { if let Some(ident) = fn_kind.ident() { err.span_label( ident.span, - format!("this function {} have a `self` parameter", doesnt), + format!("this function {doesnt} have a `self` parameter"), ); } } @@ -1030,7 +1120,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { }; // Confirm that the target is an associated type. - let (ty, position, path) = if let ast::TyKind::Path(Some(qself), path) = &bounded_ty.kind { + let (ty, _, path) = if let ast::TyKind::Path(Some(qself), path) = &bounded_ty.kind { // use this to verify that ident is a type param. let Some(partial_res) = self.r.partial_res_map.get(&bounded_ty.id) else { return false; @@ -1059,7 +1149,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { return false; } if let ( - [ast::PathSegment { ident: constrain_ident, args: None, .. }], + [ast::PathSegment { args: None, .. }], [ast::GenericBound::Trait(poly_trait_ref, ast::TraitBoundModifier::None)], ) = (&type_param_path.segments[..], &bounds[..]) { @@ -1067,29 +1157,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { &poly_trait_ref.trait_ref.path.segments[..] { if ident.span == span { + let Some(new_where_bound_predicate) = + mk_where_bound_predicate(path, poly_trait_ref, ty) + else { + return false; + }; err.span_suggestion_verbose( *where_span, - format!("constrain the associated type to `{}`", ident), - format!( - "{}: {}<{} = {}>", - self.r - .tcx - .sess - .source_map() - .span_to_snippet(ty.span) // Account for `<&'a T as Foo>::Bar`. - .unwrap_or_else(|_| constrain_ident.to_string()), - path.segments[..position] - .iter() - .map(|segment| path_segment_to_string(segment)) - .collect::>() - .join("::"), - path.segments[position..] - .iter() - .map(|segment| path_segment_to_string(segment)) - .collect::>() - .join("::"), - ident, - ), + format!("constrain the associated type to `{ident}`"), + where_bound_predicate_to_string(&new_where_bound_predicate), Applicability::MaybeIncorrect, ); } @@ -1104,37 +1180,34 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { /// return the span of whole call and the span for all arguments expect the first one (`self`). fn call_has_self_arg(&self, source: PathSource<'_>) -> Option<(Span, Option)> { let mut has_self_arg = None; - if let PathSource::Expr(Some(parent)) = source { - match &parent.kind { - ExprKind::Call(_, args) if !args.is_empty() => { - let mut expr_kind = &args[0].kind; - loop { - match expr_kind { - ExprKind::Path(_, arg_name) if arg_name.segments.len() == 1 => { - if arg_name.segments[0].ident.name == kw::SelfLower { - let call_span = parent.span; - let tail_args_span = if args.len() > 1 { - Some(Span::new( - args[1].span.lo(), - args.last().unwrap().span.hi(), - call_span.ctxt(), - None, - )) - } else { - None - }; - has_self_arg = Some((call_span, tail_args_span)); - } - break; + if let PathSource::Expr(Some(parent)) = source + && let ExprKind::Call(_, args) = &parent.kind + && !args.is_empty() { + let mut expr_kind = &args[0].kind; + loop { + match expr_kind { + ExprKind::Path(_, arg_name) if arg_name.segments.len() == 1 => { + if arg_name.segments[0].ident.name == kw::SelfLower { + let call_span = parent.span; + let tail_args_span = if args.len() > 1 { + Some(Span::new( + args[1].span.lo(), + args.last().unwrap().span.hi(), + call_span.ctxt(), + None, + )) + } else { + None + }; + has_self_arg = Some((call_span, tail_args_span)); } - ExprKind::AddrOf(_, _, expr) => expr_kind = &expr.kind, - _ => break, + break; } + ExprKind::AddrOf(_, _, expr) => expr_kind = &expr.kind, + _ => break, } } - _ => (), - } - }; + } has_self_arg } @@ -1144,15 +1217,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { // where a brace being opened means a block is being started. Look // ahead for the next text to see if `span` is followed by a `{`. let sm = self.r.tcx.sess.source_map(); - let sp = sm.span_look_ahead(span, None, Some(50)); - let followed_by_brace = matches!(sm.span_to_snippet(sp), Ok(ref snippet) if snippet == "{"); - // In case this could be a struct literal that needs to be surrounded - // by parentheses, find the appropriate span. - let closing_span = sm.span_look_ahead(span, Some("}"), Some(50)); - let closing_brace: Option = sm - .span_to_snippet(closing_span) - .map_or(None, |s| if s == "}" { Some(span.to(closing_span)) } else { None }); - (followed_by_brace, closing_brace) + if let Some(followed_brace_span) = sm.span_look_ahead(span, "{", Some(50)) { + // In case this could be a struct literal that needs to be surrounded + // by parentheses, find the appropriate span. + let close_brace_span = sm.span_look_ahead(followed_brace_span, "}", Some(50)); + let closing_brace = close_brace_span.map(|sp| span.to(sp)); + (true, closing_brace) + } else { + (false, None) + } } /// Provides context-dependent help for errors reported by the `smart_resolve_path_fragment` @@ -1248,7 +1321,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { }), ) if followed_by_brace => { if let Some(sp) = closing_brace { - err.span_label(span, fallback_label); + err.span_label(span, fallback_label.to_string()); err.multipart_suggestion( "surround the struct literal with parentheses", vec![ @@ -1262,8 +1335,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { span, // Note the parentheses surrounding the suggestion below format!( "you might want to surround a struct literal with parentheses: \ - `({} {{ /* fields */ }})`?", - path_str + `({path_str} {{ /* fields */ }})`?" ), ); } @@ -1297,7 +1369,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { .map(|(idx, new)| (new, old_fields.get(idx))) .map(|(new, old)| { let new = new.to_ident_string(); - if let Some(Some(old)) = old && new != *old { format!("{}: {}", new, old) } else { new } + if let Some(Some(old)) = old && new != *old { format!("{new}: {old}") } else { new } }) .collect::>() } else { @@ -1314,13 +1386,13 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { }; err.span_suggestion( span, - format!("use struct {} syntax instead", descr), + format!("use struct {descr} syntax instead"), format!("{path_str} {{{pad}{fields}{pad}}}"), applicability, ); } _ => { - err.span_label(span, fallback_label); + err.span_label(span, fallback_label.to_string()); } } }; @@ -1333,7 +1405,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { })) | PathSource::Struct, ) => { - err.span_label(span, fallback_label); + err.span_label(span, fallback_label.to_string()); err.span_suggestion_verbose( span.shrink_to_hi(), "use `!` to invoke the macro", @@ -1345,9 +1417,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } } (Res::Def(DefKind::Macro(MacroKind::Bang), _), _) => { - err.span_label(span, fallback_label); + err.span_label(span, fallback_label.to_string()); } - (Res::Def(DefKind::TyAlias, def_id), PathSource::Trait(_)) => { + (Res::Def(DefKind::TyAlias { .. }, def_id), PathSource::Trait(_)) => { err.span_label(span, "type aliases cannot be used as traits"); if self.r.tcx.sess.is_nightly_build() { let msg = "you might have meant to use `#![feature(trait_alias)]` instead of a \ @@ -1508,15 +1580,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { err.span_suggestion( span, "use the tuple variant pattern syntax instead", - format!("{}({})", path_str, fields), + format!("{path_str}({fields})"), Applicability::HasPlaceholders, ); } (Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }, _) if ns == ValueNS => { - err.span_label(span, fallback_label); + err.span_label(span, fallback_label.to_string()); err.note("can't use `Self` as a constructor, you must use the implemented struct"); } - (Res::Def(DefKind::TyAlias | DefKind::AssocTy, _), _) if ns == ValueNS => { + (Res::Def(DefKind::TyAlias { .. } | DefKind::AssocTy, _), _) if ns == ValueNS => { err.note("can't use a type alias as a constructor"); } _ => return false, @@ -1537,7 +1609,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { return None; } - let resolutions = self.r.resolutions(module); + let resolutions = self.r.resolutions(*module); let targets = resolutions .borrow() .iter() @@ -1659,6 +1731,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { fn lookup_typo_candidate( &mut self, path: &[Segment], + following_seg: Option<&Segment>, ns: Namespace, filter_fn: &impl Fn(Res) -> bool, ) -> TypoCandidate { @@ -1737,6 +1810,26 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } } + // if next_seg is present, let's filter everything that does not continue the path + if let Some(following_seg) = following_seg { + names.retain(|suggestion| match suggestion.res { + Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, _) => { + // FIXME: this is not totally accurate, but mostly works + suggestion.candidate != following_seg.ident.name + } + Res::Def(DefKind::Mod, def_id) => self.r.get_module(def_id).map_or_else( + || false, + |module| { + self.r + .resolutions(module) + .borrow() + .iter() + .any(|(key, _)| key.ident.name == following_seg.ident.name) + }, + ), + _ => true, + }); + } let name = path[path.len() - 1].ident.name; // Make sure error reporting is deterministic. names.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); @@ -1747,7 +1840,8 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { None, ) { Some(found) => { - let Some(sugg) = names.into_iter().find(|suggestion| suggestion.candidate == found) else { + let Some(sugg) = names.into_iter().find(|suggestion| suggestion.candidate == found) + else { return TypoCandidate::None; }; if found == name { @@ -1829,6 +1923,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { path, accessible: true, note: None, + via_import: false, }, )); } else { @@ -1895,9 +1990,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { if !suggestable_variants.is_empty() { let msg = if non_suggestable_variant_count == 0 && suggestable_variants.len() == 1 { - format!("try {} the enum's variant", source_msg) + format!("try {source_msg} the enum's variant") } else { - format!("try {} one of the enum's variants", source_msg) + format!("try {source_msg} one of the enum's variants") }; err.span_suggestions( @@ -1910,19 +2005,15 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { // If the enum has no tuple variants.. if non_suggestable_variant_count == variants.len() { - err.help(format!("the enum has no tuple variants {}", source_msg)); + err.help(format!("the enum has no tuple variants {source_msg}")); } // If there are also non-tuple variants.. if non_suggestable_variant_count == 1 { - err.help(format!( - "you might have meant {} the enum's non-tuple variant", - source_msg - )); + err.help(format!("you might have meant {source_msg} the enum's non-tuple variant")); } else if non_suggestable_variant_count >= 1 { err.help(format!( - "you might have meant {} one of the enum's non-tuple variants", - source_msg + "you might have meant {source_msg} one of the enum's non-tuple variants" )); } } else { @@ -1942,7 +2033,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { .map(|(variant, _, kind)| (path_names_to_string(variant), kind)) .map(|(variant, kind)| match kind { CtorKind::Const => variant, - CtorKind::Fn => format!("({}())", variant), + CtorKind::Fn => format!("({variant}())"), }) .collect::>(); let no_suggestable_variant = suggestable_variants.is_empty(); @@ -1967,7 +2058,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { .filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind)) .map(|(variant, _, kind)| (path_names_to_string(variant), kind)) .filter_map(|(variant, kind)| match kind { - CtorKind::Fn => Some(format!("({}(/* fields */))", variant)), + CtorKind::Fn => Some(format!("({variant}(/* fields */))")), _ => None, }) .collect::>(); @@ -2180,10 +2271,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { None => { debug!(?param.ident, ?param.ident.span); let deletion_span = deletion_span(); - // the give lifetime originates from expanded code so we won't be able to remove it #104432 - let lifetime_only_in_expanded_code = - deletion_span.map(|sp| sp.in_derive_expansion()).unwrap_or(true); - if !lifetime_only_in_expanded_code { + + // if the lifetime originates from expanded code, we won't be able to remove it #104432 + if deletion_span.is_some_and(|sp| !sp.in_derive_expansion()) { self.r.lint_buffer.buffer_lint_with_diagnostic( lint::builtin::UNUSED_LIFETIMES, param.id, @@ -2243,20 +2333,27 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { &self, err: &mut Diagnostic, name: Option<&str>, - suggest: impl Fn(&mut Diagnostic, bool, Span, &str, String) -> bool, + suggest: impl Fn(&mut Diagnostic, bool, Span, Cow<'static, str>, String) -> bool, ) { let mut suggest_note = true; for rib in self.lifetime_ribs.iter().rev() { let mut should_continue = true; match rib.kind { LifetimeRibKind::Generics { binder: _, span, kind } => { + // Avoid suggesting placing lifetime parameters on constant items unless the relevant + // feature is enabled. Suggest the parent item as a possible location if applicable. + if let LifetimeBinderKind::ConstItem = kind + && !self.r.tcx().features().generic_const_items + { + continue; + } + if !span.can_be_used_for_suggestions() && suggest_note && let Some(name) = name { suggest_note = false; // Avoid displaying the same help multiple times. err.span_label( span, format!( - "lifetime `{}` is missing in item created through this procedural macro", - name, + "lifetime `{name}` is missing in item created through this procedural macro", ), ); continue; @@ -2288,25 +2385,26 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { (span, sugg) }; if higher_ranked { - let message = format!( + let message = Cow::from(format!( "consider making the {} lifetime-generic with a new `{}` lifetime", kind.descr(), name.unwrap_or("'a"), - ); - should_continue = suggest(err, true, span, &message, sugg); + )); + should_continue = suggest(err, true, span, message, sugg); err.note_once( "for more information on higher-ranked polymorphism, visit \ https://doc.rust-lang.org/nomicon/hrtb.html", ); } else if let Some(name) = name { - let message = format!("consider introducing lifetime `{}` here", name); - should_continue = suggest(err, false, span, &message, sugg); + let message = + Cow::from(format!("consider introducing lifetime `{name}` here")); + should_continue = suggest(err, false, span, message, sugg); } else { - let message = "consider introducing a named lifetime parameter"; - should_continue = suggest(err, false, span, &message, sugg); + let message = Cow::from("consider introducing a named lifetime parameter"); + should_continue = suggest(err, false, span, message, sugg); } } - LifetimeRibKind::Item => break, + LifetimeRibKind::Item | LifetimeRibKind::ConstParamTy => break, _ => {} } if !should_continue { @@ -2412,7 +2510,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { .lifetime_ribs .iter() .rev() - .take_while(|rib| !matches!(rib.kind, LifetimeRibKind::Item)) + .take_while(|rib| { + !matches!(rib.kind, LifetimeRibKind::Item | LifetimeRibKind::ConstParamTy) + }) .flat_map(|rib| rib.bindings.iter()) .map(|(&ident, &res)| (ident, res)) .filter(|(ident, _)| ident.name != kw::UnderscoreLifetime) @@ -2443,7 +2543,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } let help_name = if let Some(ident) = ident { - format!("`{}`", ident) + format!("`{ident}`") } else { format!("argument {}", index + 1) }; @@ -2451,7 +2551,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { if lifetime_count == 1 { m.push_str(&help_name[..]) } else { - m.push_str(&format!("one of {}'s {} lifetimes", help_name, lifetime_count)[..]) + m.push_str(&format!("one of {help_name}'s {lifetime_count} lifetimes")[..]) } } @@ -2481,14 +2581,12 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } else if num_params == 1 { err.help(format!( "this function's return type contains a borrowed value, \ - but the signature does not say which {} it is borrowed from", - m + but the signature does not say which {m} it is borrowed from" )); } else { err.help(format!( "this function's return type contains a borrowed value, \ - but the signature does not say whether it is borrowed from {}", - m + but the signature does not say whether it is borrowed from {m}" )); } } @@ -2507,7 +2605,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } MissingLifetimeKind::Ampersand => { debug_assert_eq!(lt.count, 1); - (lt.span.shrink_to_hi(), format!("{} ", existing_name)) + (lt.span.shrink_to_hi(), format!("{existing_name} ")) } MissingLifetimeKind::Comma => { let sugg: String = std::iter::repeat([existing_name.as_str(), ", "]) @@ -2554,7 +2652,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } 1 => { err.multipart_suggestion_verbose( - format!("consider using the `{}` lifetime", existing_name), + format!("consider using the `{existing_name}` lifetime"), spans_suggs, Applicability::MaybeIncorrect, ); @@ -2584,6 +2682,72 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { } } +fn mk_where_bound_predicate( + path: &Path, + poly_trait_ref: &ast::PolyTraitRef, + ty: &ast::Ty, +) -> Option { + use rustc_span::DUMMY_SP; + let modified_segments = { + let mut segments = path.segments.clone(); + let [preceding @ .., second_last, last] = segments.as_mut_slice() else { + return None; + }; + let mut segments = ThinVec::from(preceding); + + let added_constraint = ast::AngleBracketedArg::Constraint(ast::AssocConstraint { + id: DUMMY_NODE_ID, + ident: last.ident, + gen_args: None, + kind: ast::AssocConstraintKind::Equality { + term: ast::Term::Ty(ast::ptr::P(ast::Ty { + kind: ast::TyKind::Path(None, poly_trait_ref.trait_ref.path.clone()), + id: DUMMY_NODE_ID, + span: DUMMY_SP, + tokens: None, + })), + }, + span: DUMMY_SP, + }); + + match second_last.args.as_deref_mut() { + Some(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { args, .. })) => { + args.push(added_constraint); + } + Some(_) => return None, + None => { + second_last.args = + Some(ast::ptr::P(ast::GenericArgs::AngleBracketed(ast::AngleBracketedArgs { + args: ThinVec::from([added_constraint]), + span: DUMMY_SP, + }))); + } + } + + segments.push(second_last.clone()); + segments + }; + + let new_where_bound_predicate = ast::WhereBoundPredicate { + span: DUMMY_SP, + bound_generic_params: ThinVec::new(), + bounded_ty: ast::ptr::P(ty.clone()), + bounds: vec![ast::GenericBound::Trait( + ast::PolyTraitRef { + bound_generic_params: ThinVec::new(), + trait_ref: ast::TraitRef { + path: ast::Path { segments: modified_segments, span: DUMMY_SP, tokens: None }, + ref_id: DUMMY_NODE_ID, + }, + span: DUMMY_SP, + }, + ast::TraitBoundModifier::None, + )], + }; + + Some(new_where_bound_predicate) +} + /// Report lifetime/lifetime shadowing as an error. pub(super) fn signal_lifetime_shadowing(sess: &Session, orig: Ident, shadower: Ident) { let mut err = struct_span_err!( @@ -2605,9 +2769,9 @@ pub(super) fn signal_label_shadowing(sess: &Session, orig: Span, shadower: Ident let shadower = shadower.span; let mut err = sess.struct_span_warn( shadower, - format!("label name `{}` shadows a label name that is already in scope", name), + format!("label name `{name}` shadows a label name that is already in scope"), ); err.span_label(orig, "first declared here"); - err.span_label(shadower, format!("label `{}` already in scope", name)); + err.span_label(shadower, format!("label `{name}` already in scope")); err.emit(); } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 3d2bd8429068e..76e54e60d142b 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -9,14 +9,16 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(assert_matches)] #![feature(box_patterns)] -#![feature(drain_filter)] +#![feature(extract_if)] #![feature(if_let_guard)] #![feature(iter_intersperse)] #![feature(let_chains)] #![feature(never_type)] +#![feature(rustc_attrs)] #![recursion_limit = "256"] #![allow(rustdoc::private_intra_doc_links)] #![allow(rustc::potential_query_instability)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate tracing; @@ -25,6 +27,7 @@ use errors::{ ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst, ParamKindInTyOfConstParam, }; use rustc_arena::{DroplessArena, TypedArena}; +use rustc_ast::expand::StrippedCfgItem; use rustc_ast::node_id::NodeMap; use rustc_ast::{self as ast, attr, NodeId, CRATE_NODE_ID}; use rustc_ast::{AngleBracketedArg, Crate, Expr, ExprKind, GenericArg, GenericArgs, LitKind, Path}; @@ -60,10 +63,10 @@ use rustc_span::{Span, DUMMY_SP}; use smallvec::{smallvec, SmallVec}; use std::cell::{Cell, RefCell}; use std::collections::BTreeSet; -use std::{fmt, ptr}; +use std::fmt; use diagnostics::{ImportSuggestion, LabelSuggestion, Suggestion}; -use imports::{Import, ImportKind, NameResolution}; +use imports::{Import, ImportData, ImportKind, NameResolution}; use late::{HasGenericParams, PathSource, PatternSource}; use macros::{MacroRulesBinding, MacroRulesScope, MacroRulesScopeRef}; @@ -127,10 +130,10 @@ enum Scope<'a> { /// with different restrictions when looking up the resolution. /// This enum is currently used only for early resolution (imports and macros), /// but not for late resolution yet. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum ScopeSet<'a> { /// All scopes with the given namespace. - All(Namespace, /*is_import*/ bool), + All(Namespace), /// Crate root, then extern prelude (used for mixed 2015-2018 mode in macros). AbsolutePath(Namespace), /// All scopes with macro namespace and the given macro kind restriction. @@ -171,6 +174,7 @@ enum ImplTraitContext { Universal(LocalDefId), } +#[derive(Debug)] struct BindingError { name: Symbol, origin: BTreeSet, @@ -178,6 +182,7 @@ struct BindingError { could_be_path: bool, } +#[derive(Debug)] enum ResolutionError<'a> { /// Error E0401: can't use type or const parameters from outer function. GenericParamsFromOuterFunction(Res, HasGenericParams), @@ -207,7 +212,12 @@ enum ResolutionError<'a> { /// Error E0431: `self` import can only appear in an import list with a non-empty prefix. SelfImportOnlyInImportListWithNonEmptyPrefix, /// Error E0433: failed to resolve. - FailedToResolve { label: String, suggestion: Option }, + FailedToResolve { + last_segment: Option, + label: String, + suggestion: Option, + module: Option>, + }, /// Error E0434: can't capture dynamic environment in a fn item. CannotCaptureDynamicEnvironmentInFnItem, /// Error E0435: attempt to use a non-constant value in a constant. @@ -344,7 +354,7 @@ impl<'a> From<&'a ast::PathSegment> for Segment { /// forward. #[derive(Debug)] enum LexicalScopeBinding<'a> { - Item(&'a NameBinding<'a>), + Item(NameBinding<'a>), Res(Res), } @@ -357,7 +367,7 @@ impl<'a> LexicalScopeBinding<'a> { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] enum ModuleOrUniformRoot<'a> { /// Regular module. Module(Module<'a>), @@ -375,23 +385,6 @@ enum ModuleOrUniformRoot<'a> { CurrentScope, } -impl ModuleOrUniformRoot<'_> { - fn same_def(lhs: Self, rhs: Self) -> bool { - match (lhs, rhs) { - (ModuleOrUniformRoot::Module(lhs), ModuleOrUniformRoot::Module(rhs)) => { - ptr::eq(lhs, rhs) - } - ( - ModuleOrUniformRoot::CrateRootAndExternPrelude, - ModuleOrUniformRoot::CrateRootAndExternPrelude, - ) - | (ModuleOrUniformRoot::ExternPrelude, ModuleOrUniformRoot::ExternPrelude) - | (ModuleOrUniformRoot::CurrentScope, ModuleOrUniformRoot::CurrentScope) => true, - _ => false, - } - } -} - #[derive(Debug)] enum PathResult<'a> { Module(ModuleOrUniformRoot<'a>), @@ -402,6 +395,7 @@ enum PathResult<'a> { label: String, suggestion: Option, is_error_from_last_segment: bool, + module: Option>, }, } @@ -410,11 +404,12 @@ impl<'a> PathResult<'a> { span: Span, is_error_from_last_segment: bool, finalize: bool, + module: Option>, label_and_suggestion: impl FnOnce() -> (String, Option), ) -> PathResult<'a> { let (label, suggestion) = if finalize { label_and_suggestion() } else { (String::new(), None) }; - PathResult::Failed { span, label, suggestion, is_error_from_last_segment } + PathResult::Failed { span, label, suggestion, is_error_from_last_segment, module } } } @@ -508,11 +503,11 @@ struct ModuleData<'a> { /// Whether `#[no_implicit_prelude]` is active. no_implicit_prelude: bool, - glob_importers: RefCell>>, - globs: RefCell>>, + glob_importers: RefCell>>, + globs: RefCell>>, /// Used to memoize the traits in this module for faster searches through all traits in scope. - traits: RefCell)]>>>, + traits: RefCell)]>>>, /// Span of the module itself. Used for error reporting. span: Span, @@ -520,7 +515,11 @@ struct ModuleData<'a> { expansion: ExpnId, } -type Module<'a> = &'a ModuleData<'a>; +/// All modules are unique and allocated on a same arena, +/// so we can use referential equality to compare them. +#[derive(Clone, Copy, PartialEq)] +#[rustc_pass_by_value] +struct Module<'a>(Interned<'a, ModuleData<'a>>); impl<'a> ModuleData<'a> { fn new( @@ -548,11 +547,13 @@ impl<'a> ModuleData<'a> { expansion, } } +} - fn for_each_child<'tcx, R, F>(&'a self, resolver: &mut R, mut f: F) +impl<'a> Module<'a> { + fn for_each_child<'tcx, R, F>(self, resolver: &mut R, mut f: F) where R: AsMut>, - F: FnMut(&mut R, Ident, Namespace, &'a NameBinding<'a>), + F: FnMut(&mut R, Ident, Namespace, NameBinding<'a>), { for (key, name_resolution) in resolver.as_mut().resolutions(self).borrow().iter() { if let Some(binding) = name_resolution.borrow().binding { @@ -562,7 +563,7 @@ impl<'a> ModuleData<'a> { } /// This modifies `self` in place. The traits will be stored in `self.traits`. - fn ensure_traits<'tcx, R>(&'a self, resolver: &mut R) + fn ensure_traits<'tcx, R>(self, resolver: &mut R) where R: AsMut>, { @@ -581,7 +582,7 @@ impl<'a> ModuleData<'a> { } } - fn res(&self) -> Option { + fn res(self) -> Option { match self.kind { ModuleKind::Def(kind, def_id, _) => Some(Res::Def(kind, def_id)), _ => None, @@ -589,11 +590,11 @@ impl<'a> ModuleData<'a> { } // Public for rustdoc. - fn def_id(&self) -> DefId { + fn def_id(self) -> DefId { self.opt_def_id().expect("`ModuleData::def_id` is called on a block module") } - fn opt_def_id(&self) -> Option { + fn opt_def_id(self) -> Option { match self.kind { ModuleKind::Def(_, def_id, _) => Some(def_id), _ => None, @@ -601,15 +602,15 @@ impl<'a> ModuleData<'a> { } // `self` resolves to the first module ancestor that `is_normal`. - fn is_normal(&self) -> bool { + fn is_normal(self) -> bool { matches!(self.kind, ModuleKind::Def(DefKind::Mod, _, _)) } - fn is_trait(&self) -> bool { + fn is_trait(self) -> bool { matches!(self.kind, ModuleKind::Def(DefKind::Trait, _, _)) } - fn nearest_item_scope(&'a self) -> Module<'a> { + fn nearest_item_scope(self) -> Module<'a> { match self.kind { ModuleKind::Def(DefKind::Enum | DefKind::Trait, ..) => { self.parent.expect("enum or trait module without a parent") @@ -620,15 +621,15 @@ impl<'a> ModuleData<'a> { /// The [`DefId`] of the nearest `mod` item ancestor (which may be this module). /// This may be the crate root. - fn nearest_parent_mod(&self) -> DefId { + fn nearest_parent_mod(self) -> DefId { match self.kind { ModuleKind::Def(DefKind::Mod, def_id, _) => def_id, _ => self.parent.expect("non-root module without parent").nearest_parent_mod(), } } - fn is_ancestor_of(&self, mut other: &Self) -> bool { - while !ptr::eq(self, other) { + fn is_ancestor_of(self, mut other: Self) -> bool { + while self != other { if let Some(parent) = other.parent { other = parent; } else { @@ -639,7 +640,15 @@ impl<'a> ModuleData<'a> { } } -impl<'a> fmt::Debug for ModuleData<'a> { +impl<'a> std::ops::Deref for Module<'a> { + type Target = ModuleData<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> fmt::Debug for Module<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.res()) } @@ -647,20 +656,25 @@ impl<'a> fmt::Debug for ModuleData<'a> { /// Records a possibly-private value, type, or module definition. #[derive(Clone, Debug)] -struct NameBinding<'a> { +struct NameBindingData<'a> { kind: NameBindingKind<'a>, - ambiguity: Option<(&'a NameBinding<'a>, AmbiguityKind)>, + ambiguity: Option<(NameBinding<'a>, AmbiguityKind)>, + warn_ambiguity: bool, expansion: LocalExpnId, span: Span, vis: ty::Visibility, } +/// All name bindings are unique and allocated on a same arena, +/// so we can use referential equality to compare them. +type NameBinding<'a> = Interned<'a, NameBindingData<'a>>; + trait ToNameBinding<'a> { - fn to_name_binding(self, arenas: &'a ResolverArenas<'a>) -> &'a NameBinding<'a>; + fn to_name_binding(self, arenas: &'a ResolverArenas<'a>) -> NameBinding<'a>; } -impl<'a> ToNameBinding<'a> for &'a NameBinding<'a> { - fn to_name_binding(self, _: &'a ResolverArenas<'a>) -> &'a NameBinding<'a> { +impl<'a> ToNameBinding<'a> for NameBinding<'a> { + fn to_name_binding(self, _: &'a ResolverArenas<'a>) -> NameBinding<'a> { self } } @@ -669,7 +683,7 @@ impl<'a> ToNameBinding<'a> for &'a NameBinding<'a> { enum NameBindingKind<'a> { Res(Res), Module(Module<'a>), - Import { binding: &'a NameBinding<'a>, import: &'a Import<'a>, used: Cell }, + Import { binding: NameBinding<'a>, import: Import<'a>, used: Cell }, } impl<'a> NameBindingKind<'a> { @@ -679,12 +693,16 @@ impl<'a> NameBindingKind<'a> { } } +#[derive(Debug)] struct PrivacyError<'a> { ident: Ident, - binding: &'a NameBinding<'a>, + binding: NameBinding<'a>, dedup_span: Span, + outermost_res: Option<(Res, Ident)>, + parent_scope: ParentScope<'a>, } +#[derive(Debug)] struct UseError<'a> { err: DiagnosticBuilder<'a, ErrorGuaranteed>, /// Candidates which user could `use` to access the missing type. @@ -704,7 +722,6 @@ struct UseError<'a> { #[derive(Clone, Copy, PartialEq, Debug)] enum AmbiguityKind { - Import, BuiltinAttr, DeriveHelper, MacroRulesVsModularized, @@ -717,7 +734,6 @@ enum AmbiguityKind { impl AmbiguityKind { fn descr(self) -> &'static str { match self { - AmbiguityKind::Import => "multiple potential import sources", AmbiguityKind::BuiltinAttr => "a name conflict with a builtin attribute", AmbiguityKind::DeriveHelper => "a name conflict with a derive helper attribute", AmbiguityKind::MacroRulesVsModularized => { @@ -749,13 +765,14 @@ enum AmbiguityErrorMisc { struct AmbiguityError<'a> { kind: AmbiguityKind, ident: Ident, - b1: &'a NameBinding<'a>, - b2: &'a NameBinding<'a>, + b1: NameBinding<'a>, + b2: NameBinding<'a>, misc1: AmbiguityErrorMisc, misc2: AmbiguityErrorMisc, + warning: bool, } -impl<'a> NameBinding<'a> { +impl<'a> NameBindingData<'a> { fn module(&self) -> Option> { match self.kind { NameBindingKind::Module(module) => Some(module), @@ -780,6 +797,14 @@ impl<'a> NameBinding<'a> { } } + fn is_warn_ambiguity(&self) -> bool { + self.warn_ambiguity + || match self.kind { + NameBindingKind::Import { binding, .. } => binding.is_warn_ambiguity(), + _ => false, + } + } + fn is_possibly_imported_variant(&self) -> bool { match self.kind { NameBindingKind::Import { binding, .. } => binding.is_possibly_imported_variant(), @@ -793,14 +818,12 @@ impl<'a> NameBinding<'a> { fn is_extern_crate(&self) -> bool { match self.kind { - NameBindingKind::Import { - import: &Import { kind: ImportKind::ExternCrate { .. }, .. }, - .. - } => true, - NameBindingKind::Module(&ModuleData { - kind: ModuleKind::Def(DefKind::Mod, def_id, _), - .. - }) => def_id.is_crate_root(), + NameBindingKind::Import { import, .. } => { + matches!(import.kind, ImportKind::ExternCrate { .. }) + } + NameBindingKind::Module(module) + if let ModuleKind::Def(DefKind::Mod, def_id, _) = module.kind + => def_id.is_crate_root(), _ => false, } } @@ -843,7 +866,7 @@ impl<'a> NameBinding<'a> { fn may_appear_after( &self, invoc_parent_expansion: LocalExpnId, - binding: &NameBinding<'_>, + binding: NameBinding<'_>, ) -> bool { // self > max(invoc, binding) => !(self <= invoc || self <= binding) // Expansions are partially ordered, so "may appear after" is an inversion of @@ -860,7 +883,7 @@ impl<'a> NameBinding<'a> { #[derive(Default, Clone)] struct ExternPreludeEntry<'a> { - extern_crate_item: Option<&'a NameBinding<'a>>, + extern_crate_item: Option>, introduced_by_item: bool, } @@ -905,10 +928,10 @@ pub struct Resolver<'a, 'tcx> { field_visibility_spans: FxHashMap>, /// All imports known to succeed or fail. - determined_imports: Vec<&'a Import<'a>>, + determined_imports: Vec>, /// All non-determined imports. - indeterminate_imports: Vec<&'a Import<'a>>, + indeterminate_imports: Vec>, // Spans for local variables found during pattern resolution. // Used for suggestions during error reporting. @@ -950,7 +973,7 @@ pub struct Resolver<'a, 'tcx> { /// language items. empty_module: Module<'a>, module_map: FxHashMap>, - binding_parent_modules: FxHashMap>, Module<'a>>, + binding_parent_modules: FxHashMap, Module<'a>>, underscore_disambiguator: u32, @@ -972,7 +995,7 @@ pub struct Resolver<'a, 'tcx> { macro_expanded_macro_export_errors: BTreeSet<(Span, Span)>, arenas: &'a ResolverArenas<'a>, - dummy_binding: &'a NameBinding<'a>, + dummy_binding: NameBinding<'a>, used_extern_options: FxHashSet, macro_names: FxHashSet, @@ -981,7 +1004,7 @@ pub struct Resolver<'a, 'tcx> { /// the surface (`macro` items in libcore), but are actually attributes or derives. builtin_macro_kinds: FxHashMap, registered_tools: &'tcx RegisteredTools, - macro_use_prelude: FxHashMap>, + macro_use_prelude: FxHashMap>, macro_map: FxHashMap, dummy_ext_bang: Lrc, dummy_ext_derive: Lrc, @@ -993,7 +1016,7 @@ pub struct Resolver<'a, 'tcx> { proc_macro_stubs: FxHashSet, /// Traces collected during macro resolution and validated when it's complete. single_segment_macro_resolutions: - Vec<(Ident, MacroKind, ParentScope<'a>, Option<&'a NameBinding<'a>>)>, + Vec<(Ident, MacroKind, ParentScope<'a>, Option>)>, multi_segment_macro_resolutions: Vec<(Vec, Span, MacroKind, ParentScope<'a>, Option)>, builtin_attrs: Vec<(Ident, ParentScope<'a>)>, @@ -1018,7 +1041,7 @@ pub struct Resolver<'a, 'tcx> { /// Avoid duplicated errors for "name already defined". name_already_seen: FxHashMap, - potentially_unused_imports: Vec<&'a Import<'a>>, + potentially_unused_imports: Vec>, /// Table for mapping struct IDs into struct constructor IDs, /// it's not used during normal resolution, only for better error reporting. @@ -1059,6 +1082,9 @@ pub struct Resolver<'a, 'tcx> { /// Whether lifetime elision was successful. lifetime_elision_allowed: FxHashSet, + /// Names of items that were stripped out via cfg with their corresponding cfg meta item. + stripped_cfg_items: Vec>, + effective_visibilities: EffectiveVisibilities, doc_link_resolutions: FxHashMap, doc_link_traits_in_scope: FxHashMap>, @@ -1070,7 +1096,7 @@ pub struct Resolver<'a, 'tcx> { pub struct ResolverArenas<'a> { modules: TypedArena>, local_modules: RefCell>>, - imports: TypedArena>, + imports: TypedArena>, name_resolutions: TypedArena>>, ast_paths: TypedArena, dropless: DroplessArena, @@ -1086,8 +1112,13 @@ impl<'a> ResolverArenas<'a> { no_implicit_prelude: bool, module_map: &mut FxHashMap>, ) -> Module<'a> { - let module = - self.modules.alloc(ModuleData::new(parent, kind, expn_id, span, no_implicit_prelude)); + let module = Module(Interned::new_unchecked(self.modules.alloc(ModuleData::new( + parent, + kind, + expn_id, + span, + no_implicit_prelude, + )))); let def_id = module.opt_def_id(); if def_id.map_or(true, |def_id| def_id.is_local()) { self.local_modules.borrow_mut().push(module); @@ -1100,11 +1131,11 @@ impl<'a> ResolverArenas<'a> { fn local_modules(&'a self) -> std::cell::Ref<'a, Vec>> { self.local_modules.borrow() } - fn alloc_name_binding(&'a self, name_binding: NameBinding<'a>) -> &'a NameBinding<'a> { - self.dropless.alloc(name_binding) + fn alloc_name_binding(&'a self, name_binding: NameBindingData<'a>) -> NameBinding<'a> { + Interned::new_unchecked(self.dropless.alloc(name_binding)) } - fn alloc_import(&'a self, import: Import<'a>) -> &'a Import<'_> { - self.imports.alloc(import) + fn alloc_import(&'a self, import: ImportData<'a>) -> Import<'a> { + Interned::new_unchecked(self.imports.alloc(import)) } fn alloc_name_resolution(&'a self) -> &'a RefCell> { self.name_resolutions.alloc(Default::default()) @@ -1138,7 +1169,7 @@ impl<'tcx> Resolver<'_, 'tcx> { } fn local_def_id(&self, node: NodeId) -> LocalDefId { - self.opt_local_def_id(node).unwrap_or_else(|| panic!("no entry for node id: `{:?}`", node)) + self.opt_local_def_id(node).unwrap_or_else(|| panic!("no entry for node id: `{node:?}`")) } /// Adds a definition with a parent definition. @@ -1251,7 +1282,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let registered_tools = tcx.registered_tools(()); - let features = tcx.sess.features_untracked(); + let features = tcx.features(); let mut resolver = Resolver { tcx, @@ -1299,9 +1330,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { macro_expanded_macro_export_errors: BTreeSet::new(), arenas, - dummy_binding: arenas.alloc_name_binding(NameBinding { + dummy_binding: arenas.alloc_name_binding(NameBindingData { kind: NameBindingKind::Res(Res::Err), ambiguity: None, + warn_ambiguity: false, expansion: LocalExpnId::ROOT, span: DUMMY_SP, vis: ty::Visibility::Public, @@ -1353,6 +1385,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { proc_macros: Default::default(), confused_type_with_std_module: Default::default(), lifetime_elision_allowed: Default::default(), + stripped_cfg_items: Default::default(), effective_visibilities: Default::default(), doc_link_resolutions: Default::default(), doc_link_traits_in_scope: Default::default(), @@ -1410,6 +1443,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let main_def = self.main_def; let confused_type_with_std_module = self.confused_type_with_std_module; let effective_visibilities = self.effective_visibilities; + + self.tcx.feed_local_crate().stripped_cfg_items(self.tcx.arena.alloc_from_iter( + self.stripped_cfg_items.into_iter().filter_map(|item| { + let parent_module = self.node_id_to_def_id.get(&item.parent_module)?.to_def_id(); + Some(StrippedCfgItem { parent_module, name: item.name, cfg: item.cfg }) + }), + )); + let global_ctxt = ResolverGlobalCtxt { expn_that_defined, visibilities, @@ -1496,10 +1537,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let exported_ambiguities = self.tcx.sess.time("compute_effective_visibilities", || { EffectiveVisibilitiesVisitor::compute_effective_visibilities(self, krate) }); - self.tcx.sess.time("check_reexport_ambiguities", || { - self.check_reexport_ambiguities(exported_ambiguities) + self.tcx.sess.time("check_hidden_glob_reexports", || { + self.check_hidden_glob_reexports(exported_ambiguities) }); - self.tcx.sess.time("finalize_macro_resolutions", || self.finalize_macro_resolutions()); + self.tcx + .sess + .time("finalize_macro_resolutions", || self.finalize_macro_resolutions(krate)); self.tcx.sess.time("late_resolve_crate", || self.late_resolve_crate(krate)); self.tcx.sess.time("resolve_main", || self.resolve_main()); self.tcx.sess.time("resolve_check_unused", || self.check_unused(krate)); @@ -1529,7 +1572,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - self.visit_scopes(ScopeSet::All(TypeNS, false), parent_scope, ctxt, |this, scope, _, _| { + self.visit_scopes(ScopeSet::All(TypeNS), parent_scope, ctxt, |this, scope, _, _| { match scope { Scope::Module(module, _) => { this.traits_in_module(module, assoc_item, &mut found_traits); @@ -1598,7 +1641,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { self.maybe_unused_trait_imports.insert(def_id); import_ids.push(def_id); } - self.add_to_glob_map(&import, trait_name); + self.add_to_glob_map(*import, trait_name); kind = &binding.kind; } import_ids @@ -1620,7 +1663,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { module.populate_on_access.set(false); self.build_reduced_graph_external(module); } - &module.lazy_resolutions + &module.0.0.lazy_resolutions } fn resolution( @@ -1653,11 +1696,16 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { false } - fn record_use( + fn record_use(&mut self, ident: Ident, used_binding: NameBinding<'a>, is_lexical_scope: bool) { + self.record_use_inner(ident, used_binding, is_lexical_scope, used_binding.warn_ambiguity); + } + + fn record_use_inner( &mut self, ident: Ident, - used_binding: &'a NameBinding<'a>, + used_binding: NameBinding<'a>, is_lexical_scope: bool, + warn_ambiguity: bool, ) { if let Some((b2, kind)) = used_binding.ambiguity { let ambiguity_error = AmbiguityError { @@ -1667,9 +1715,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { b2, misc1: AmbiguityErrorMisc::None, misc2: AmbiguityErrorMisc::None, + warning: warn_ambiguity, }; if !self.matches_previous_ambiguity_error(&ambiguity_error) { - // avoid duplicated span information to be emitt out + // avoid duplicated span information to be emit out self.ambiguity_errors.push(ambiguity_error); } } @@ -1678,10 +1727,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { // but not introduce it, as used if they are accessed from lexical scope. if is_lexical_scope { if let Some(entry) = self.extern_prelude.get(&ident.normalize_to_macros_2_0()) { - if let Some(crate_item) = entry.extern_crate_item { - if ptr::eq(used_binding, crate_item) && !entry.introduced_by_item { - return; - } + if !entry.introduced_by_item && entry.extern_crate_item == Some(used_binding) { + return; } } } @@ -1690,13 +1737,13 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if let Some(id) = import.id() { self.used_imports.insert(id); } - self.add_to_glob_map(&import, ident); - self.record_use(ident, binding, false); + self.add_to_glob_map(import, ident); + self.record_use_inner(ident, binding, false, warn_ambiguity || binding.warn_ambiguity); } } #[inline] - fn add_to_glob_map(&mut self, import: &Import<'_>, ident: Ident) { + fn add_to_glob_map(&mut self, import: Import<'_>, ident: Ident) { if let ImportKind::Glob { id, .. } = import.kind { let def_id = self.local_def_id(id); self.glob_map.entry(def_id).or_default().insert(ident.name); @@ -1788,7 +1835,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { fn record_partial_res(&mut self, node_id: NodeId, resolution: PartialRes) { debug!("(recording res) recording {:?} for {}", resolution, node_id); if let Some(prev_res) = self.partial_res_map.insert(node_id, resolution) { - panic!("path resolved multiple times ({:?} before, {:?} now)", prev_res, resolution); + panic!("path resolved multiple times ({prev_res:?} before, {resolution:?} now)"); } } @@ -1805,11 +1852,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { vis.is_accessible_from(module.nearest_parent_mod(), self.tcx) } - fn set_binding_parent_module(&mut self, binding: &'a NameBinding<'a>, module: Module<'a>) { - if let Some(old_module) = - self.binding_parent_modules.insert(Interned::new_unchecked(binding), module) - { - if !ptr::eq(module, old_module) { + fn set_binding_parent_module(&mut self, binding: NameBinding<'a>, module: Module<'a>) { + if let Some(old_module) = self.binding_parent_modules.insert(binding, module) { + if module != old_module { span_bug!(binding.span, "parent module is reset for binding"); } } @@ -1817,25 +1862,25 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { fn disambiguate_macro_rules_vs_modularized( &self, - macro_rules: &'a NameBinding<'a>, - modularized: &'a NameBinding<'a>, + macro_rules: NameBinding<'a>, + modularized: NameBinding<'a>, ) -> bool { // Some non-controversial subset of ambiguities "modularized macro name" vs "macro_rules" // is disambiguated to mitigate regressions from macro modularization. // Scoping for `macro_rules` behaves like scoping for `let` at module level, in general. match ( - self.binding_parent_modules.get(&Interned::new_unchecked(macro_rules)), - self.binding_parent_modules.get(&Interned::new_unchecked(modularized)), + self.binding_parent_modules.get(¯o_rules), + self.binding_parent_modules.get(&modularized), ) { (Some(macro_rules), Some(modularized)) => { macro_rules.nearest_parent_mod() == modularized.nearest_parent_mod() - && modularized.is_ancestor_of(macro_rules) + && modularized.is_ancestor_of(*macro_rules) } _ => false, } } - fn extern_prelude_get(&mut self, ident: Ident, finalize: bool) -> Option<&'a NameBinding<'a>> { + fn extern_prelude_get(&mut self, ident: Ident, finalize: bool) -> Option> { if ident.is_path_segment_keyword() { // Make sure `self`, `super` etc produce an error when passed to here. return None; @@ -1849,7 +1894,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } else { let crate_id = if finalize { let Some(crate_id) = - self.crate_loader(|c| c.process_path_extern(ident.name, ident.span)) else { return Some(self.dummy_binding); }; + self.crate_loader(|c| c.process_path_extern(ident.name, ident.span)) + else { + return Some(self.dummy_binding); + }; crate_id } else { self.crate_loader(|c| c.maybe_process_path_extern(ident.name))? diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index df5c16a9375fb..6a5b675b4bb07 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1,12 +1,15 @@ //! A bunch of methods and structures more or less related to resolving macros and //! interface provided by `Resolver` to macro expander. -use crate::errors::{self, AddAsNonDerive, MacroExpectedFound, RemoveSurroundingDerive}; +use crate::errors::{ + self, AddAsNonDerive, CannotFindIdentInThisScope, MacroExpectedFound, RemoveSurroundingDerive, +}; use crate::Namespace::*; use crate::{BuiltinMacroState, Determinacy}; use crate::{DeriveData, Finalize, ParentScope, ResolutionError, Resolver, ScopeSet}; use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment}; -use rustc_ast::{self as ast, attr, Inline, ItemKind, ModKind, NodeId}; +use rustc_ast::expand::StrippedCfgItem; +use rustc_ast::{self as ast, attr, Crate, Inline, ItemKind, ModKind, NodeId}; use rustc_ast_pretty::pprust; use rustc_attr::StabilityLevel; use rustc_data_structures::intern::Interned; @@ -21,7 +24,9 @@ use rustc_hir::def_id::{CrateNum, LocalDefId}; use rustc_middle::middle::stability; use rustc_middle::ty::RegisteredTools; use rustc_middle::ty::TyCtxt; -use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE}; +use rustc_session::lint::builtin::{ + LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, +}; use rustc_session::lint::builtin::{UNUSED_MACROS, UNUSED_MACRO_RULES}; use rustc_session::lint::BuiltinLintDiagnostics; use rustc_session::parse::feature_err; @@ -39,7 +44,7 @@ type Res = def::Res; /// Not modularized, can shadow previous `macro_rules` bindings, etc. #[derive(Debug)] pub(crate) struct MacroRulesBinding<'a> { - pub(crate) binding: &'a NameBinding<'a>, + pub(crate) binding: NameBinding<'a>, /// `macro_rules` scope into which the `macro_rules` item was planted. pub(crate) parent_macro_rules_scope: MacroRulesScopeRef<'a>, pub(crate) ident: Ident, @@ -137,9 +142,9 @@ pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { } } } - // We implicitly add `rustfmt` and `clippy` to known tools, + // We implicitly add `rustfmt`, `clippy`, `diagnostic` to known tools, // but it's not an error to register them explicitly. - let predefined_tools = [sym::clippy, sym::rustfmt]; + let predefined_tools = [sym::clippy, sym::rustfmt, sym::diagnostic]; registered_tools.extend(predefined_tools.iter().cloned().map(Ident::with_dummy_span)); registered_tools } @@ -202,7 +207,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { self.tcx .sess .diagnostic() - .bug(format!("built-in macro `{}` was already registered", name)); + .bug(format!("built-in macro `{name}` was already registered")); } } @@ -465,6 +470,10 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { self.proc_macros.push(id) } + fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem) { + self.stripped_cfg_items.push(StrippedCfgItem { parent_module: parent_node, name, cfg }); + } + fn registered_tools(&self) -> &RegisteredTools { &self.registered_tools } @@ -561,7 +570,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } let mut err = self.tcx.sess.create_err(err); - err.span_label(path.span, format!("not {} {}", article, expected)); + err.span_label(path.span, format!("not {article} {expected}")); err.emit(); @@ -569,10 +578,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } // We are trying to avoid reporting this error if other related errors were reported. - if res != Res::Err - && inner_attr - && !self.tcx.sess.features_untracked().custom_inner_attributes - { + if res != Res::Err && inner_attr && !self.tcx.features().custom_inner_attributes { let msg = match res { Res::Def(..) => "inner macro attributes are unstable", Res::NonMacroAttr(..) => "custom inner attributes are unstable", @@ -591,6 +597,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } + if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) + && path.segments.len() >= 2 + && path.segments[0].ident.name == sym::diagnostic + { + self.tcx.sess.parse_sess.buffer_lint( + UNKNOWN_DIAGNOSTIC_ATTRIBUTES, + path.segments[1].span(), + node_id, + "unknown diagnostic attribute", + ); + } + Ok((ext, res)) } @@ -638,7 +656,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { self.prohibit_imported_non_macro_attrs(None, res.ok(), path_span); res } else { - let scope_set = kind.map_or(ScopeSet::All(MacroNS, false), ScopeSet::Macro); + let scope_set = kind.map_or(ScopeSet::All(MacroNS), ScopeSet::Macro); let binding = self.early_resolve_ident_in_lexical_scope( path[0].ident, scope_set, @@ -669,7 +687,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { res.map(|res| (self.get_macro(res).map(|macro_data| macro_data.ext), res)) } - pub(crate) fn finalize_macro_resolutions(&mut self) { + pub(crate) fn finalize_macro_resolutions(&mut self, krate: &Crate) { let check_consistency = |this: &mut Self, path: &[Segment], span, @@ -721,7 +739,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } path_res @ (PathResult::NonModule(..) | PathResult::Failed { .. }) => { let mut suggestion = None; - let (span, label) = if let PathResult::Failed { span, label, .. } = path_res { + let (span, label, module) = if let PathResult::Failed { span, label, module, .. } = path_res { // try to suggest if it's not a macro, maybe a function if let PathResult::NonModule(partial_res) = self.maybe_resolve_path(&path, Some(ValueNS), &parent_scope) && partial_res.unresolved_segments() == 0 { @@ -733,7 +751,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { Applicability::MaybeIncorrect )); } - (span, label) + (span, label, module) } else { ( path_span, @@ -742,11 +760,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { kind.article(), kind.descr() ), + None, ) }; self.report_error( span, - ResolutionError::FailedToResolve { label, suggestion }, + ResolutionError::FailedToResolve { last_segment: path.last().map(|segment| segment.ident.name), label, suggestion, module }, ); } PathResult::Module(..) | PathResult::Indeterminate => unreachable!(), @@ -787,9 +806,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } Err(..) => { let expected = kind.descr_expected(); - let msg = format!("cannot find {} `{}` in this scope", expected, ident); - let mut err = self.tcx.sess.struct_span_err(ident.span, msg); - self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident); + + let mut err = self.tcx.sess.create_err(CannotFindIdentInThisScope { + span: ident.span, + expected, + ident, + }); + + self.unresolved_macro_suggestions(&mut err, kind, &parent_scope, ident, krate); err.emit(); } } @@ -827,7 +851,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if !is_allowed(feature) && !allowed_by_implication { let lint_buffer = &mut self.lint_buffer; let soft_handler = - |lint, span, msg: &_| lint_buffer.buffer_lint(lint, node_id, span, msg); + |lint, span, msg: String| lint_buffer.buffer_lint(lint, node_id, span, msg); stability::report_unstable( self.tcx.sess, feature, @@ -846,7 +870,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { let (message, lint) = stability::deprecation_message_and_lint(depr, "macro", &path); stability::early_report_deprecation( &mut self.lint_buffer, - &message, + message, depr.suggestion, lint, span, @@ -857,7 +881,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { fn prohibit_imported_non_macro_attrs( &self, - binding: Option<&'a NameBinding<'a>>, + binding: Option>, res: Option, span: Span, ) { @@ -882,7 +906,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { if macro_kind.is_some() && sub_namespace_match(macro_kind, Some(MacroKind::Attr)) { self.tcx.sess.span_err( ident.span, - format!("name `{}` is reserved in attribute namespace", ident), + format!("name `{ident}` is reserved in attribute namespace"), ); } } @@ -896,7 +920,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { item: &ast::Item, edition: Edition, ) -> (SyntaxExtension, Vec<(usize, Span)>) { - let (mut result, mut rule_spans) = compile_declarative_macro(self.tcx.sess, item, edition); + let (mut result, mut rule_spans) = + compile_declarative_macro(self.tcx.sess, self.tcx.features(), item, edition); if let Some(builtin_name) = result.builtin_name { // The macro was marked with `#[rustc_builtin_macro]`. diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs index d433391f272e0..ba7417b6ddaf6 100644 --- a/compiler/rustc_resolve/src/rustdoc.rs +++ b/compiler/rustc_resolve/src/rustdoc.rs @@ -1,4 +1,4 @@ -use pulldown_cmark::{BrokenLink, Event, LinkType, Options, Parser, Tag}; +use pulldown_cmark::{BrokenLink, CowStr, Event, LinkType, Options, Parser, Tag}; use rustc_ast as ast; use rustc_ast::util::comments::beautify_doc_string; use rustc_data_structures::fx::FxHashMap; @@ -392,16 +392,73 @@ pub(crate) fn attrs_to_preprocessed_links(attrs: &[ast::Attribute]) -> Vec(doc: &'md str) -> Vec> { + let mut broken_link_callback = |link: BrokenLink<'md>| Some((link.reference, "".into())); + let mut event_iter = Parser::new_with_broken_link_callback( &doc, main_body_opts(), - Some(&mut |link: BrokenLink<'_>| Some((link.reference, "".into()))), + Some(&mut broken_link_callback), ) - .filter_map(|event| match event { - Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { - Some(preprocess_link(&dest)) + .into_iter(); + let mut links = Vec::new(); + + while let Some(event) = event_iter.next() { + match event { + Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { + if matches!( + link_type, + LinkType::Inline + | LinkType::ReferenceUnknown + | LinkType::Reference + | LinkType::Shortcut + | LinkType::ShortcutUnknown + ) { + if let Some(display_text) = collect_link_data(&mut event_iter) { + links.push(display_text); + } + } + + links.push(preprocess_link(&dest)); + } + _ => {} + } + } + + links +} + +/// Collects additional data of link. +fn collect_link_data<'input, 'callback>( + event_iter: &mut Parser<'input, 'callback>, +) -> Option> { + let mut display_text: Option = None; + let mut append_text = |text: CowStr<'_>| { + if let Some(display_text) = &mut display_text { + display_text.push_str(&text); + } else { + display_text = Some(text.to_string()); + } + }; + + while let Some(event) = event_iter.next() { + match event { + Event::Text(text) => { + append_text(text); + } + Event::Code(code) => { + append_text(code); + } + Event::End(_) => { + break; + } + _ => {} } - _ => None, - }) - .collect() + } + + display_text.map(String::into_boxed_str) } diff --git a/compiler/rustc_serialize/Cargo.toml b/compiler/rustc_serialize/Cargo.toml index 6046780685ad8..46b923e8c7b76 100644 --- a/compiler/rustc_serialize/Cargo.toml +++ b/compiler/rustc_serialize/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] -indexmap = "1.9.3" +indexmap = "2.0.0" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } thin-vec = "0.2.12" diff --git a/compiler/rustc_session/Cargo.toml b/compiler/rustc_session/Cargo.toml index 3af83aaaaa8a2..e26d25d9a4123 100644 --- a/compiler/rustc_session/Cargo.toml +++ b/compiler/rustc_session/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.0" edition = "2021" [dependencies] +bitflags = "1.2.1" getopts = "0.2" rustc_macros = { path = "../rustc_macros" } tracing = "0.1" diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index a8fe560d1a760..b07c6db599e2a 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -1,13 +1,20 @@ -session_incorrect_cgu_reuse_type = - CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least -> - [one] {"at least "} - *[other] {""} - }`{$expected_reuse}` +session_binary_float_literal_not_supported = binary float literal is not supported +session_branch_protection_requires_aarch64 = `-Zbranch-protection` is only supported on aarch64 + +session_cannot_enable_crt_static_linux = sanitizer is incompatible with statically linked libc, disable it using `-C target-feature=-crt-static` + +session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible with `-Zsanitizer={$second}` session_cgu_not_recorded = CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded -session_feature_gate_error = {$explain} +session_crate_name_does_not_match = `--crate-name` and `#[crate_name]` are required to match, but `{$s}` != `{$name}` + +session_crate_name_empty = crate name must not be empty + +session_crate_name_invalid = crate names cannot start with a `-`, but `{$s}` has a leading hyphen + +session_expr_parentheses_needed = parentheses are required to parse this as an expression session_feature_diagnostic_for_issue = see issue #{$n} for more information @@ -15,91 +22,93 @@ session_feature_diagnostic_for_issue = session_feature_diagnostic_help = add `#![feature({$feature})]` to the crate attributes to enable -session_not_circumvent_feature = `-Zunleash-the-miri-inside-of-you` may not be used to circumvent feature gates, except when testing error paths in the CTFE engine - -session_profile_use_file_does_not_exist = file `{$path}` passed to `-C profile-use` does not exist. +session_feature_gate_error = {$explain} -session_linker_plugin_lto_windows_not_supported = linker plugin based LTO is not supported together with `-C prefer-dynamic` when targeting Windows-like targets +session_file_is_not_writeable = output file {$file} is not writeable -- check its permissions -session_profile_sample_use_file_does_not_exist = file `{$path}` passed to `-C profile-sample-use` does not exist. +session_file_write_fail = failed to write `{$path}` due to error `{$err}` -session_target_requires_unwind_tables = target requires unwind tables, they cannot be disabled with `-C force-unwind-tables=no` +session_hexadecimal_float_literal_not_supported = hexadecimal float literal is not supported -session_instrumentation_not_supported = {$us} instrumentation is not supported for this target +session_incompatible_linker_flavor = linker flavor `{$flavor}` is incompatible with the current target + .note = compatible flavors are: {$compatible_list} -session_sanitizer_not_supported = {$us} sanitizer is not supported for this target +session_incorrect_cgu_reuse_type = + CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least -> + [one] {"at least "} + *[other] {""} + }`{$expected_reuse}` -session_sanitizers_not_supported = {$us} sanitizers are not supported for this target +session_instrumentation_not_supported = {$us} instrumentation is not supported for this target -session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible with `-Zsanitizer={$second}` +session_int_literal_too_large = integer literal is too large + .note = value exceeds limit of `{$limit}` -session_cannot_enable_crt_static_linux = sanitizer is incompatible with statically linked libc, disable it using `-C target-feature=-crt-static` +session_invalid_character_in_create_name = invalid character `{$character}` in crate name: `{$crate_name}` +session_invalid_character_in_create_name_help = you can either pass `--crate-name` on the command line or add `#![crate_name="…"]` to set the crate name -session_sanitizer_cfi_requires_lto = `-Zsanitizer=cfi` requires `-Clto`, `-Clto=thin`, or `-Clinker-plugin-lto` +session_invalid_float_literal_suffix = invalid suffix `{$suffix}` for float literal + .label = invalid suffix `{$suffix}` + .help = valid suffixes are `f32` and `f64` -session_sanitizer_cfi_canonical_jump_tables_requires_cfi = `-Zsanitizer-cfi-canonical-jump-tables` requires `-Zsanitizer=cfi` +session_invalid_float_literal_width = invalid width `{$width}` for float literal + .help = valid widths are 32 and 64 -session_sanitizer_cfi_generalize_pointers_requires_cfi = `-Zsanitizer-cfi-generalize-pointers` requires `-Zsanitizer=cfi` or `-Zsanitizer=kcfi` +session_invalid_int_literal_width = invalid width `{$width}` for integer literal + .help = valid widths are 8, 16, 32, 64 and 128 -session_sanitizer_cfi_normalize_integers_requires_cfi = `-Zsanitizer-cfi-normalize-integers` requires `-Zsanitizer=cfi` or `-Zsanitizer=kcfi` +session_invalid_literal_suffix = suffixes on {$kind} literals are invalid + .label = invalid suffix `{$suffix}` -session_split_lto_unit_requires_lto = `-Zsplit-lto-unit` requires `-Clto`, `-Clto=thin`, or `-Clinker-plugin-lto` +session_invalid_num_literal_base_prefix = invalid base prefix for number literal + .note = base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase + .suggestion = try making the prefix lowercase -session_unstable_virtual_function_elimination = `-Zvirtual-function-elimination` requires `-Clto` +session_invalid_num_literal_suffix = invalid suffix `{$suffix}` for number literal + .label = invalid suffix `{$suffix}` + .help = the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) -session_unsupported_dwarf_version = requested DWARF version {$dwarf_version} is greater than 5 +session_linker_plugin_lto_windows_not_supported = linker plugin based LTO is not supported together with `-C prefer-dynamic` when targeting Windows-like targets -session_target_stack_protector_not_supported = `-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored +session_not_circumvent_feature = `-Zunleash-the-miri-inside-of-you` may not be used to circumvent feature gates, except when testing error paths in the CTFE engine -session_branch_protection_requires_aarch64 = `-Zbranch-protection` is only supported on aarch64 +session_not_supported = not supported -session_split_debuginfo_unstable_platform = `-Csplit-debuginfo={$debuginfo}` is unstable on this platform +session_nul_in_c_str = null characters in C string literals are not supported -session_file_is_not_writeable = output file {$file} is not writeable -- check its permissions +session_octal_float_literal_not_supported = octal float literal is not supported +session_optimization_fuel_exhausted = optimization-fuel-exhausted: {$msg} -session_crate_name_does_not_match = `--crate-name` and `#[crate_name]` are required to match, but `{$s}` != `{$name}` +session_profile_sample_use_file_does_not_exist = file `{$path}` passed to `-C profile-sample-use` does not exist. -session_crate_name_invalid = crate names cannot start with a `-`, but `{$s}` has a leading hyphen +session_profile_use_file_does_not_exist = file `{$path}` passed to `-C profile-use` does not exist. -session_crate_name_empty = crate name must not be empty +session_sanitizer_cfi_canonical_jump_tables_requires_cfi = `-Zsanitizer-cfi-canonical-jump-tables` requires `-Zsanitizer=cfi` -session_invalid_character_in_create_name = invalid character `{$character}` in crate name: `{$crate_name}` +session_sanitizer_cfi_generalize_pointers_requires_cfi = `-Zsanitizer-cfi-generalize-pointers` requires `-Zsanitizer=cfi` or `-Zsanitizer=kcfi` -session_expr_parentheses_needed = parentheses are required to parse this as an expression +session_sanitizer_cfi_normalize_integers_requires_cfi = `-Zsanitizer-cfi-normalize-integers` requires `-Zsanitizer=cfi` or `-Zsanitizer=kcfi` -session_skipping_const_checks = skipping const checks -session_unleashed_feature_help_named = skipping check for `{$gate}` feature -session_unleashed_feature_help_unnamed = skipping check that does not even have a feature gate +session_sanitizer_cfi_requires_lto = `-Zsanitizer=cfi` requires `-Clto` or `-Clinker-plugin-lto` -session_hexadecimal_float_literal_not_supported = hexadecimal float literal is not supported -session_octal_float_literal_not_supported = octal float literal is not supported -session_binary_float_literal_not_supported = binary float literal is not supported -session_not_supported = not supported +session_sanitizer_cfi_requires_single_codegen_unit = `-Zsanitizer=cfi` with `-Clto` requires `-Ccodegen-units=1` -session_invalid_literal_suffix = suffixes on {$kind} literals are invalid - .label = invalid suffix `{$suffix}` +session_sanitizer_not_supported = {$us} sanitizer is not supported for this target -session_invalid_num_literal_base_prefix = invalid base prefix for number literal - .note = base prefixes (`0xff`, `0b1010`, `0o755`) are lowercase - .suggestion = try making the prefix lowercase +session_sanitizers_not_supported = {$us} sanitizers are not supported for this target -session_invalid_num_literal_suffix = invalid suffix `{$suffix}` for number literal - .label = invalid suffix `{$suffix}` - .help = the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) +session_skipping_const_checks = skipping const checks +session_split_debuginfo_unstable_platform = `-Csplit-debuginfo={$debuginfo}` is unstable on this platform -session_invalid_float_literal_width = invalid width `{$width}` for float literal - .help = valid widths are 32 and 64 +session_split_lto_unit_requires_lto = `-Zsplit-lto-unit` requires `-Clto`, `-Clto=thin`, or `-Clinker-plugin-lto` -session_invalid_float_literal_suffix = invalid suffix `{$suffix}` for float literal - .label = invalid suffix `{$suffix}` - .help = valid suffixes are `f32` and `f64` +session_target_requires_unwind_tables = target requires unwind tables, they cannot be disabled with `-C force-unwind-tables=no` -session_int_literal_too_large = integer literal is too large - .note = value exceeds limit of `{$limit}` +session_target_stack_protector_not_supported = `-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored -session_invalid_int_literal_width = invalid width `{$width}` for integer literal - .help = valid widths are 8, 16, 32, 64 and 128 +session_unleashed_feature_help_named = skipping check for `{$gate}` feature +session_unleashed_feature_help_unnamed = skipping check that does not even have a feature gate -session_optimization_fuel_exhausted = optimization-fuel-exhausted: {$msg} +session_unstable_virtual_function_elimination = `-Zvirtual-function-elimination` requires `-Clto` -session_nul_in_c_str = null characters in C string literals are not supported +session_unsupported_dwarf_version = requested DWARF version {$dwarf_version} is greater than 5 diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs index 0dfee92f40434..df81e1f8305ea 100644 --- a/compiler/rustc_session/src/code_stats.rs +++ b/compiler/rustc_session/src/code_stats.rs @@ -1,5 +1,6 @@ -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::sync::Lock; +use rustc_span::def_id::DefId; use rustc_span::Symbol; use rustc_target::abi::{Align, Size}; use std::cmp; @@ -65,9 +66,29 @@ pub struct TypeSizeInfo { pub variants: Vec, } +pub struct VTableSizeInfo { + pub trait_name: String, + + /// Number of entries in a vtable with the current algorithm + /// (i.e. with upcasting). + pub entries: usize, + + /// Number of entries in a vtable, as-if we did not have trait upcasting. + pub entries_ignoring_upcasting: usize, + + /// Number of entries in a vtable needed solely for upcasting + /// (i.e. `entries - entries_ignoring_upcasting`). + pub entries_for_upcasting: usize, + + /// Cost of having upcasting in % relative to the number of entries without + /// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`). + pub upcasting_cost_percent: f64, +} + #[derive(Default)] pub struct CodeStats { type_sizes: Lock>, + vtable_sizes: Lock>, } impl CodeStats { @@ -101,6 +122,14 @@ impl CodeStats { self.type_sizes.borrow_mut().insert(info); } + pub fn record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) { + let prev = self.vtable_sizes.lock().insert(trait_did, info); + assert!( + prev.is_none(), + "size of vtable for `{trait_name}` ({trait_did:?}) is already recorded" + ); + } + pub fn print_type_sizes(&self) { let type_sizes = self.type_sizes.borrow(); let mut sorted: Vec<_> = type_sizes.iter().collect(); @@ -196,4 +225,31 @@ impl CodeStats { } } } + + pub fn print_vtable_sizes(&self, crate_name: &str) { + let mut infos = + std::mem::take(&mut *self.vtable_sizes.lock()).into_values().collect::>(); + + // Primary sort: cost % in reverse order (from largest to smallest) + // Secondary sort: trait_name + infos.sort_by(|a, b| { + a.upcasting_cost_percent + .total_cmp(&b.upcasting_cost_percent) + .reverse() + .then_with(|| a.trait_name.cmp(&b.trait_name)) + }); + + for VTableSizeInfo { + trait_name, + entries, + entries_ignoring_upcasting, + entries_for_upcasting, + upcasting_cost_percent, + } in infos + { + println!( + r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"# + ); + } + } } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 6c8c8e484f939..f00472f181d8e 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3,16 +3,16 @@ pub use crate::options::*; +use crate::errors::FileWriteFail; use crate::search_paths::SearchPath; use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind}; -use crate::{early_error, early_warn, Session}; use crate::{lint, HashStableContext}; +use crate::{EarlyErrorHandler, Session}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; - use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey}; use rustc_target::abi::Align; -use rustc_target::spec::{PanicStrategy, SanitizerSet, SplitDebuginfo}; +use rustc_target::spec::{PanicStrategy, RelocModel, SanitizerSet, SplitDebuginfo}; use rustc_target::spec::{Target, TargetTriple, TargetWarnings, TARGETS}; use crate::parse::{CrateCheckConfig, CrateConfig}; @@ -30,7 +30,9 @@ use std::collections::btree_map::{ Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter, }; use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsStr; use std::fmt; +use std::fs; use std::hash::Hash; use std::iter; use std::path::{Path, PathBuf}; @@ -200,6 +202,128 @@ pub enum LinkerPluginLto { Disabled, } +impl LinkerPluginLto { + pub fn enabled(&self) -> bool { + match *self { + LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true, + LinkerPluginLto::Disabled => false, + } + } +} + +/// The different values `-C link-self-contained` can take: a list of individually enabled or +/// disabled components used during linking, coming from the rustc distribution, instead of being +/// found somewhere on the host system. +/// +/// They can be set in bulk via `-C link-self-contained=yes|y|on` or `-C +/// link-self-contained=no|n|off`, and those boolean values are the historical defaults. +/// +/// But each component is fine-grained, and can be unstably targeted, to use: +/// - some CRT objects +/// - the libc static library +/// - libgcc/libunwind libraries +/// - a linker we distribute +/// - some sanitizer runtime libraries +/// - all other MinGW libraries and Windows import libs +/// +#[derive(Default, Clone, PartialEq, Debug)] +pub struct LinkSelfContained { + /// Whether the user explicitly set `-C link-self-contained` on or off, the historical values. + /// Used for compatibility with the existing opt-in and target inference. + pub explicitly_set: Option, + + /// The components that are enabled. + components: LinkSelfContainedComponents, +} + +bitflags::bitflags! { + #[derive(Default)] + /// The `-C link-self-contained` components that can individually be enabled or disabled. + pub struct LinkSelfContainedComponents: u8 { + /// CRT objects (e.g. on `windows-gnu`, `musl`, `wasi` targets) + const CRT_OBJECTS = 1 << 0; + /// libc static library (e.g. on `musl`, `wasi` targets) + const LIBC = 1 << 1; + /// libgcc/libunwind (e.g. on `windows-gnu`, `fuchsia`, `fortanix`, `gnullvm` targets) + const UNWIND = 1 << 2; + /// Linker, dlltool, and their necessary libraries (e.g. on `windows-gnu` and for `rust-lld`) + const LINKER = 1 << 3; + /// Sanitizer runtime libraries + const SANITIZERS = 1 << 4; + /// Other MinGW libs and Windows import libs + const MINGW = 1 << 5; + } +} + +impl FromStr for LinkSelfContainedComponents { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "crto" => LinkSelfContainedComponents::CRT_OBJECTS, + "libc" => LinkSelfContainedComponents::LIBC, + "unwind" => LinkSelfContainedComponents::UNWIND, + "linker" => LinkSelfContainedComponents::LINKER, + "sanitizers" => LinkSelfContainedComponents::SANITIZERS, + "mingw" => LinkSelfContainedComponents::MINGW, + _ => return Err(()), + }) + } +} + +impl LinkSelfContained { + /// Incorporates an enabled or disabled component as specified on the CLI, if possible. + /// For example: `+linker`, and `-crto`. + pub(crate) fn handle_cli_component(&mut self, component: &str) -> Result<(), ()> { + // Note that for example `-Cself-contained=y -Cself-contained=-linker` is not an explicit + // set of all values like `y` or `n` used to be. Therefore, if this flag had previously been + // set in bulk with its historical values, then manually setting a component clears that + // `explicitly_set` state. + if let Some(component_to_enable) = component.strip_prefix('+') { + self.explicitly_set = None; + self.components.insert(component_to_enable.parse()?); + Ok(()) + } else if let Some(component_to_disable) = component.strip_prefix('-') { + self.explicitly_set = None; + self.components.remove(component_to_disable.parse()?); + Ok(()) + } else { + Err(()) + } + } + + /// Turns all components on or off and records that this was done explicitly for compatibility + /// purposes. + pub(crate) fn set_all_explicitly(&mut self, enabled: bool) { + self.explicitly_set = Some(enabled); + self.components = if enabled { + LinkSelfContainedComponents::all() + } else { + LinkSelfContainedComponents::empty() + }; + } + + /// Helper creating a fully enabled `LinkSelfContained` instance. Used in tests. + pub fn on() -> Self { + let mut on = LinkSelfContained::default(); + on.set_all_explicitly(true); + on + } + + /// To help checking CLI usage while some of the values are unstable: returns whether one of the + /// components was set individually. This would also require the `-Zunstable-options` flag, to + /// be allowed. + fn are_unstable_variants_set(&self) -> bool { + let any_component_set = !self.components.is_empty(); + self.explicitly_set.is_none() && any_component_set + } + + /// Returns whether the self-contained linker component is enabled. + pub fn linker(&self) -> bool { + self.components.contains(LinkSelfContainedComponents::LINKER) + } +} + /// Used with `-Z assert-incr-state`. #[derive(Clone, Copy, PartialEq, Hash, Debug)] pub enum IncrementalStateAssertion { @@ -212,15 +336,6 @@ pub enum IncrementalStateAssertion { NotLoaded, } -impl LinkerPluginLto { - pub fn enabled(&self) -> bool { - match *self { - LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true, - LinkerPluginLto::Disabled => false, - } - } -} - /// The different settings that can be enabled via the `-Z location-detail` flag. #[derive(Copy, Clone, PartialEq, Hash, Debug)] pub struct LocationDetail { @@ -311,7 +426,9 @@ pub enum OutputType { } // Safety: Trivial C-Style enums have a stable sort order across compilation sessions. -unsafe impl StableOrd for OutputType {} +unsafe impl StableOrd for OutputType { + const CAN_USE_UNSTABLE_SORT: bool = true; +} impl ToStableHashKey for OutputType { type KeyType = Self; @@ -333,7 +450,7 @@ impl OutputType { } } - fn shorthand(&self) -> &'static str { + pub fn shorthand(&self) -> &'static str { match *self { OutputType::Bitcode => "llvm-bc", OutputType::Assembly => "asm", @@ -386,6 +503,18 @@ impl OutputType { OutputType::Exe => "", } } + + pub fn is_text_output(&self) -> bool { + match *self { + OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::DepInfo => true, + OutputType::Bitcode | OutputType::Object | OutputType::Metadata | OutputType::Exe => { + false + } + } + } } /// The type of diagnostics output to generate. @@ -438,14 +567,14 @@ pub enum ResolveDocLinks { /// dependency tracking for command-line arguments. Also only hash keys, since tracking /// should only depend on the output types, not the paths they're written to. #[derive(Clone, Debug, Hash, HashStable_Generic)] -pub struct OutputTypes(BTreeMap>); +pub struct OutputTypes(BTreeMap>); impl OutputTypes { - pub fn new(entries: &[(OutputType, Option)]) -> OutputTypes { + pub fn new(entries: &[(OutputType, Option)]) -> OutputTypes { OutputTypes(BTreeMap::from_iter(entries.iter().map(|&(k, ref v)| (k, v.clone())))) } - pub fn get(&self, key: &OutputType) -> Option<&Option> { + pub fn get(&self, key: &OutputType) -> Option<&Option> { self.0.get(key) } @@ -453,11 +582,15 @@ impl OutputTypes { self.0.contains_key(key) } - pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option> { + pub fn iter(&self) -> BTreeMapIter<'_, OutputType, Option> { + self.0.iter() + } + + pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option> { self.0.keys() } - pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option> { + pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option> { self.0.values() } @@ -579,8 +712,14 @@ impl ExternEntry { } } +#[derive(Clone, PartialEq, Debug)] +pub struct PrintRequest { + pub kind: PrintKind, + pub out: OutFileName, +} + #[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum PrintRequest { +pub enum PrintKind { FileNames, Sysroot, TargetLibdir, @@ -606,10 +745,18 @@ pub enum PrintRequest { pub enum TraitSolver { /// Classic trait solver in `rustc_trait_selection::traits::select` Classic, - /// Chalk trait solver - Chalk, /// Experimental trait solver in `rustc_trait_selection::solve` Next, + /// Use the new trait solver during coherence + NextCoherence, +} + +#[derive(Default, Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum DumpSolverProofTree { + Always, + OnError, + #[default] + Never, } pub enum Input { @@ -658,11 +805,83 @@ impl Input { } } +#[derive(Clone, Hash, Debug, HashStable_Generic, PartialEq)] +pub enum OutFileName { + Real(PathBuf), + Stdout, +} + +impl OutFileName { + pub fn parent(&self) -> Option<&Path> { + match *self { + OutFileName::Real(ref path) => path.parent(), + OutFileName::Stdout => None, + } + } + + pub fn filestem(&self) -> Option<&OsStr> { + match *self { + OutFileName::Real(ref path) => path.file_stem(), + OutFileName::Stdout => Some(OsStr::new("stdout")), + } + } + + pub fn is_stdout(&self) -> bool { + match *self { + OutFileName::Real(_) => false, + OutFileName::Stdout => true, + } + } + + pub fn is_tty(&self) -> bool { + use std::io::IsTerminal; + match *self { + OutFileName::Real(_) => false, + OutFileName::Stdout => std::io::stdout().is_terminal(), + } + } + + pub fn as_path(&self) -> &Path { + match *self { + OutFileName::Real(ref path) => path.as_ref(), + OutFileName::Stdout => &Path::new("stdout"), + } + } + + /// For a given output filename, return the actual name of the file that + /// can be used to write codegen data of type `flavor`. For real-path + /// output filenames, this would be trivial as we can just use the path. + /// Otherwise for stdout, return a temporary path so that the codegen data + /// may be later copied to stdout. + pub fn file_for_writing( + &self, + outputs: &OutputFilenames, + flavor: OutputType, + codegen_unit_name: Option<&str>, + ) -> PathBuf { + match *self { + OutFileName::Real(ref path) => path.clone(), + OutFileName::Stdout => outputs.temp_path(flavor, codegen_unit_name), + } + } + + pub fn overwrite(&self, content: &str, sess: &Session) { + match self { + OutFileName::Stdout => print!("{content}"), + OutFileName::Real(path) => { + if let Err(e) = fs::write(path, content) { + sess.emit_fatal(FileWriteFail { path, err: e.to_string() }); + } + } + } + } +} + #[derive(Clone, Hash, Debug, HashStable_Generic)] pub struct OutputFilenames { pub out_directory: PathBuf, filestem: String, - pub single_output_file: Option, + pub single_output_file: Option, pub temps_directory: Option, pub outputs: OutputTypes, } @@ -675,7 +894,7 @@ impl OutputFilenames { pub fn new( out_directory: PathBuf, out_filestem: String, - single_output_file: Option, + single_output_file: Option, temps_directory: Option, extra: String, outputs: OutputTypes, @@ -689,12 +908,12 @@ impl OutputFilenames { } } - pub fn path(&self, flavor: OutputType) -> PathBuf { + pub fn path(&self, flavor: OutputType) -> OutFileName { self.outputs .get(&flavor) .and_then(|p| p.to_owned()) .or_else(|| self.single_output_file.clone()) - .unwrap_or_else(|| self.output_path(flavor)) + .unwrap_or_else(|| OutFileName::Real(self.output_path(flavor))) } /// Gets the output path where a compilation artifact of the given type @@ -828,6 +1047,7 @@ impl Default for Options { json_future_incompat: false, pretty: None, working_dir: RealFileName::LocalPath(std::env::current_dir().unwrap()), + color: ColorConfig::Auto, } } } @@ -973,6 +1193,7 @@ fn default_configuration(sess: &Session) -> CrateConfig { let os = &sess.target.os; let env = &sess.target.env; let abi = &sess.target.abi; + let relocation_model = sess.target.relocation_model.desc_symbol(); let vendor = &sess.target.vendor; let min_atomic_width = sess.target.min_atomic_width(); let max_atomic_width = sess.target.max_atomic_width(); @@ -998,6 +1219,9 @@ fn default_configuration(sess: &Session) -> CrateConfig { ret.insert((sym::target_pointer_width, Some(Symbol::intern(&wordsz)))); ret.insert((sym::target_env, Some(Symbol::intern(env)))); ret.insert((sym::target_abi, Some(Symbol::intern(abi)))); + if sess.is_nightly_build() { + ret.insert((sym::relocation_model, Some(relocation_model))); + } ret.insert((sym::target_vendor, Some(Symbol::intern(vendor)))); if sess.target.has_thread_local { ret.insert((sym::target_thread_local, None)); @@ -1195,6 +1419,8 @@ impl CrateCheckConfig { .into_iter() .map(|sanitizer| Symbol::intern(sanitizer.as_str().unwrap())); + let relocation_model_values = RelocModel::all(); + // Unknown possible values: // - `feature` // - `target_feature` @@ -1233,6 +1459,10 @@ impl CrateCheckConfig { .entry(sym::target_has_atomic_equal_alignment) .or_insert_with(no_values) .extend(atomic_values); + self.expecteds + .entry(sym::relocation_model) + .or_insert_with(empty_values) + .extend(relocation_model_values); // Target specific values { @@ -1308,6 +1538,7 @@ pub fn build_configuration(sess: &Session, mut user_cfg: CrateConfig) -> CrateCo } pub(super) fn build_target_config( + handler: &EarlyErrorHandler, opts: &Options, target_override: Option, sysroot: &Path, @@ -1317,27 +1548,20 @@ pub(super) fn build_target_config( |t| Ok((t, TargetWarnings::empty())), ); let (target, target_warnings) = target_result.unwrap_or_else(|e| { - early_error( - opts.error_format, - format!( - "Error loading target specification: {}. \ - Run `rustc --print target-list` for a list of built-in targets", - e - ), - ) + handler.early_error(format!( + "Error loading target specification: {e}. \ + Run `rustc --print target-list` for a list of built-in targets" + )) }); for warning in target_warnings.warning_messages() { - early_warn(opts.error_format, warning) + handler.early_warn(warning) } if !matches!(target.pointer_width, 16 | 32 | 64) { - early_error( - opts.error_format, - format!( - "target specification was invalid: unrecognized target-pointer-width {}", - target.pointer_width - ), - ) + handler.early_error(format!( + "target specification was invalid: unrecognized target-pointer-width {}", + target.pointer_width + )) } target @@ -1573,8 +1797,8 @@ pub fn rustc_optgroups() -> Vec { } pub fn get_cmd_lint_options( + handler: &EarlyErrorHandler, matches: &getopts::Matches, - error_format: ErrorOutputType, ) -> (Vec<(String, lint::Level)>, bool, Option) { let mut lint_opts_with_position = vec![]; let mut describe_lints = false; @@ -1598,14 +1822,14 @@ pub fn get_cmd_lint_options( let lint_cap = matches.opt_str("cap-lints").map(|cap| { lint::Level::from_str(&cap) - .unwrap_or_else(|| early_error(error_format, format!("unknown lint level: `{cap}`"))) + .unwrap_or_else(|| handler.early_error(format!("unknown lint level: `{cap}`"))) }); (lint_opts, describe_lints, lint_cap) } /// Parses the `--color` flag. -pub fn parse_color(matches: &getopts::Matches) -> ColorConfig { +pub fn parse_color(handler: &EarlyErrorHandler, matches: &getopts::Matches) -> ColorConfig { match matches.opt_str("color").as_deref() { Some("auto") => ColorConfig::Auto, Some("always") => ColorConfig::Always, @@ -1613,13 +1837,10 @@ pub fn parse_color(matches: &getopts::Matches) -> ColorConfig { None => ColorConfig::Auto, - Some(arg) => early_error( - ErrorOutputType::default(), - format!( - "argument for `--color` must be auto, \ + Some(arg) => handler.early_error(format!( + "argument for `--color` must be auto, \ always or never (instead was `{arg}`)" - ), - ), + )), } } @@ -1662,7 +1883,7 @@ impl JsonUnusedExterns { /// /// The first value returned is how to render JSON diagnostics, and the second /// is whether or not artifact notifications are enabled. -pub fn parse_json(matches: &getopts::Matches) -> JsonConfig { +pub fn parse_json(handler: &EarlyErrorHandler, matches: &getopts::Matches) -> JsonConfig { let mut json_rendered: fn(ColorConfig) -> HumanReadableErrorType = HumanReadableErrorType::Default; let mut json_color = ColorConfig::Never; @@ -1674,10 +1895,7 @@ pub fn parse_json(matches: &getopts::Matches) -> JsonConfig { // won't actually be emitting any colors and anything colorized is // embedded in a diagnostic message anyway. if matches.opt_str("color").is_some() { - early_error( - ErrorOutputType::default(), - "cannot specify the `--color` option with `--json`", - ); + handler.early_error("cannot specify the `--color` option with `--json`"); } for sub_option in option.split(',') { @@ -1688,10 +1906,7 @@ pub fn parse_json(matches: &getopts::Matches) -> JsonConfig { "unused-externs" => json_unused_externs = JsonUnusedExterns::Loud, "unused-externs-silent" => json_unused_externs = JsonUnusedExterns::Silent, "future-incompat" => json_future_incompat = true, - s => early_error( - ErrorOutputType::default(), - format!("unknown `--json` option `{s}`"), - ), + s => handler.early_error(format!("unknown `--json` option `{s}`")), } } } @@ -1706,6 +1921,7 @@ pub fn parse_json(matches: &getopts::Matches) -> JsonConfig { /// Parses the `--error-format` flag. pub fn parse_error_format( + handler: &mut EarlyErrorHandler, matches: &getopts::Matches, color: ColorConfig, json_rendered: HumanReadableErrorType, @@ -1726,13 +1942,15 @@ pub fn parse_error_format( Some("pretty-json") => ErrorOutputType::Json { pretty: true, json_rendered }, Some("short") => ErrorOutputType::HumanReadable(HumanReadableErrorType::Short(color)), - Some(arg) => early_error( - ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color)), - format!( + Some(arg) => { + handler.abort_if_error_and_set_error_format(ErrorOutputType::HumanReadable( + HumanReadableErrorType::Default(color), + )); + handler.early_error(format!( "argument for `--error-format` must be `human`, `json` or \ `short` (instead was `{arg}`)" - ), - ), + )) + } } } else { ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color)) @@ -1745,10 +1963,7 @@ pub fn parse_error_format( // `--error-format=json`. This means that `--json` is specified we // should actually be emitting JSON blobs. _ if !matches.opt_strs("json").is_empty() => { - early_error( - ErrorOutputType::default(), - "using `--json` requires also using `--error-format=json`", - ); + handler.early_error("using `--json` requires also using `--error-format=json`"); } _ => {} @@ -1757,16 +1972,13 @@ pub fn parse_error_format( error_format } -pub fn parse_crate_edition(matches: &getopts::Matches) -> Edition { +pub fn parse_crate_edition(handler: &EarlyErrorHandler, matches: &getopts::Matches) -> Edition { let edition = match matches.opt_str("edition") { Some(arg) => Edition::from_str(&arg).unwrap_or_else(|_| { - early_error( - ErrorOutputType::default(), - format!( - "argument for `--edition` must be one of: \ + handler.early_error(format!( + "argument for `--edition` must be one of: \ {EDITION_NAME_LIST}. (instead was `{arg}`)" - ), - ) + )) }), None => DEFAULT_EDITION, }; @@ -1775,62 +1987,58 @@ pub fn parse_crate_edition(matches: &getopts::Matches) -> Edition { let is_nightly = nightly_options::match_is_nightly_build(matches); let msg = if !is_nightly { format!( - "the crate requires edition {}, but the latest edition supported by this Rust version is {}", - edition, LATEST_STABLE_EDITION + "the crate requires edition {edition}, but the latest edition supported by this Rust version is {LATEST_STABLE_EDITION}" ) } else { format!("edition {edition} is unstable and only available with -Z unstable-options") }; - early_error(ErrorOutputType::default(), msg) + handler.early_error(msg) } edition } fn check_error_format_stability( + handler: &mut EarlyErrorHandler, unstable_opts: &UnstableOptions, error_format: ErrorOutputType, json_rendered: HumanReadableErrorType, ) { if !unstable_opts.unstable_options { if let ErrorOutputType::Json { pretty: true, json_rendered } = error_format { - early_error( - ErrorOutputType::Json { pretty: false, json_rendered }, - "`--error-format=pretty-json` is unstable", - ); + handler.abort_if_error_and_set_error_format(ErrorOutputType::Json { + pretty: false, + json_rendered, + }); + handler.early_error("`--error-format=pretty-json` is unstable"); } if let ErrorOutputType::HumanReadable(HumanReadableErrorType::AnnotateSnippet(_)) = error_format { - early_error( - ErrorOutputType::Json { pretty: false, json_rendered }, - "`--error-format=human-annotate-rs` is unstable", - ); + handler.abort_if_error_and_set_error_format(ErrorOutputType::Json { + pretty: false, + json_rendered, + }); + handler.early_error("`--error-format=human-annotate-rs` is unstable"); } } } fn parse_output_types( + handler: &EarlyErrorHandler, unstable_opts: &UnstableOptions, matches: &getopts::Matches, - error_format: ErrorOutputType, ) -> OutputTypes { let mut output_types = BTreeMap::new(); if !unstable_opts.parse_only { for list in matches.opt_strs("emit") { for output_type in list.split(',') { - let (shorthand, path) = match output_type.split_once('=') { - None => (output_type, None), - Some((shorthand, path)) => (shorthand, Some(PathBuf::from(path))), - }; + let (shorthand, path) = split_out_file_name(output_type); let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| { - early_error( - error_format, - format!( - "unknown emission type: `{shorthand}` - expected one of: {display}", - display = OutputType::shorthands_display(), - ), - ) + handler.early_error(format!( + "unknown emission type: `{shorthand}` - expected one of: {display}", + display = OutputType::shorthands_display(), + )) }); output_types.insert(output_type, path); } @@ -1842,10 +2050,18 @@ fn parse_output_types( OutputTypes(output_types) } +fn split_out_file_name(arg: &str) -> (&str, Option) { + match arg.split_once('=') { + None => (arg, None), + Some((kind, "-")) => (kind, Some(OutFileName::Stdout)), + Some((kind, path)) => (kind, Some(OutFileName::Real(PathBuf::from(path)))), + } +} + fn should_override_cgus_and_disable_thinlto( + handler: &EarlyErrorHandler, output_types: &OutputTypes, matches: &getopts::Matches, - error_format: ErrorOutputType, mut codegen_units: Option, ) -> (bool, Option) { let mut disable_local_thinlto = false; @@ -1863,15 +2079,12 @@ fn should_override_cgus_and_disable_thinlto( Some(n) if n > 1 => { if matches.opt_present("o") { for ot in &incompatible { - early_warn( - error_format, - format!( - "`--emit={ot}` with `-o` incompatible with \ + handler.early_warn(format!( + "`--emit={ot}` with `-o` incompatible with \ `-C codegen-units=N` for N > 1", - ), - ); + )); } - early_warn(error_format, "resetting to default -C codegen-units=1"); + handler.early_warn("resetting to default -C codegen-units=1"); codegen_units = Some(1); disable_local_thinlto = true; } @@ -1884,109 +2097,132 @@ fn should_override_cgus_and_disable_thinlto( } if codegen_units == Some(0) { - early_error(error_format, "value for codegen units must be a positive non-zero integer"); + handler.early_error("value for codegen units must be a positive non-zero integer"); } (disable_local_thinlto, codegen_units) } -fn check_thread_count(unstable_opts: &UnstableOptions, error_format: ErrorOutputType) { +fn check_thread_count(handler: &EarlyErrorHandler, unstable_opts: &UnstableOptions) { if unstable_opts.threads == 0 { - early_error(error_format, "value for threads must be a positive non-zero integer"); + handler.early_error("value for threads must be a positive non-zero integer"); } if unstable_opts.threads > 1 && unstable_opts.fuel.is_some() { - early_error(error_format, "optimization fuel is incompatible with multiple threads"); + handler.early_error("optimization fuel is incompatible with multiple threads"); } } fn collect_print_requests( + handler: &EarlyErrorHandler, cg: &mut CodegenOptions, unstable_opts: &mut UnstableOptions, matches: &getopts::Matches, - error_format: ErrorOutputType, ) -> Vec { let mut prints = Vec::::new(); if cg.target_cpu.as_ref().is_some_and(|s| s == "help") { - prints.push(PrintRequest::TargetCPUs); + prints.push(PrintRequest { kind: PrintKind::TargetCPUs, out: OutFileName::Stdout }); cg.target_cpu = None; }; if cg.target_feature == "help" { - prints.push(PrintRequest::TargetFeatures); + prints.push(PrintRequest { kind: PrintKind::TargetFeatures, out: OutFileName::Stdout }); cg.target_feature = String::new(); } - const PRINT_REQUESTS: &[(&str, PrintRequest)] = &[ - ("crate-name", PrintRequest::CrateName), - ("file-names", PrintRequest::FileNames), - ("sysroot", PrintRequest::Sysroot), - ("target-libdir", PrintRequest::TargetLibdir), - ("cfg", PrintRequest::Cfg), - ("calling-conventions", PrintRequest::CallingConventions), - ("target-list", PrintRequest::TargetList), - ("target-cpus", PrintRequest::TargetCPUs), - ("target-features", PrintRequest::TargetFeatures), - ("relocation-models", PrintRequest::RelocationModels), - ("code-models", PrintRequest::CodeModels), - ("tls-models", PrintRequest::TlsModels), - ("native-static-libs", PrintRequest::NativeStaticLibs), - ("stack-protector-strategies", PrintRequest::StackProtectorStrategies), - ("target-spec-json", PrintRequest::TargetSpec), - ("all-target-specs-json", PrintRequest::AllTargetSpecs), - ("link-args", PrintRequest::LinkArgs), - ("split-debuginfo", PrintRequest::SplitDebuginfo), - ("deployment-target", PrintRequest::DeploymentTarget), + const PRINT_KINDS: &[(&str, PrintKind)] = &[ + ("crate-name", PrintKind::CrateName), + ("file-names", PrintKind::FileNames), + ("sysroot", PrintKind::Sysroot), + ("target-libdir", PrintKind::TargetLibdir), + ("cfg", PrintKind::Cfg), + ("calling-conventions", PrintKind::CallingConventions), + ("target-list", PrintKind::TargetList), + ("target-cpus", PrintKind::TargetCPUs), + ("target-features", PrintKind::TargetFeatures), + ("relocation-models", PrintKind::RelocationModels), + ("code-models", PrintKind::CodeModels), + ("tls-models", PrintKind::TlsModels), + ("native-static-libs", PrintKind::NativeStaticLibs), + ("stack-protector-strategies", PrintKind::StackProtectorStrategies), + ("target-spec-json", PrintKind::TargetSpec), + ("all-target-specs-json", PrintKind::AllTargetSpecs), + ("link-args", PrintKind::LinkArgs), + ("split-debuginfo", PrintKind::SplitDebuginfo), + ("deployment-target", PrintKind::DeploymentTarget), ]; + // We disallow reusing the same path in multiple prints, such as `--print + // cfg=output.txt --print link-args=output.txt`, because outputs are printed + // by disparate pieces of the compiler, and keeping track of which files + // need to be overwritten vs appended to is annoying. + let mut printed_paths = FxHashSet::default(); + prints.extend(matches.opt_strs("print").into_iter().map(|req| { - match PRINT_REQUESTS.iter().find(|&&(name, _)| name == req) { - Some((_, PrintRequest::TargetSpec)) => { + let (req, out) = split_out_file_name(&req); + + if out.is_some() && !unstable_opts.unstable_options { + handler.early_error( + "the `-Z unstable-options` flag must also be passed to \ + enable the path print option", + ); + } + let kind = match PRINT_KINDS.iter().find(|&&(name, _)| name == req) { + Some((_, PrintKind::TargetSpec)) => { if unstable_opts.unstable_options { - PrintRequest::TargetSpec + PrintKind::TargetSpec } else { - early_error( - error_format, + handler.early_error( "the `-Z unstable-options` flag must also be passed to \ enable the target-spec-json print option", ); } } - Some((_, PrintRequest::AllTargetSpecs)) => { + Some((_, PrintKind::AllTargetSpecs)) => { if unstable_opts.unstable_options { - PrintRequest::AllTargetSpecs + PrintKind::AllTargetSpecs } else { - early_error( - error_format, + handler.early_error( "the `-Z unstable-options` flag must also be passed to \ enable the all-target-specs-json print option", ); } } - Some(&(_, print_request)) => print_request, + Some(&(_, print_kind)) => print_kind, None => { let prints = - PRINT_REQUESTS.iter().map(|(name, _)| format!("`{name}`")).collect::>(); + PRINT_KINDS.iter().map(|(name, _)| format!("`{name}`")).collect::>(); let prints = prints.join(", "); - early_error( - error_format, - format!("unknown print request `{req}`. Valid print requests are: {prints}"), - ); + handler.early_error(format!( + "unknown print request `{req}`. Valid print requests are: {prints}" + )); + } + }; + + let out = out.unwrap_or(OutFileName::Stdout); + if let OutFileName::Real(path) = &out { + if !printed_paths.insert(path.clone()) { + handler.early_error(format!( + "cannot print multiple outputs to the same path: {}", + path.display(), + )); } } + + PrintRequest { kind, out } })); prints } pub fn parse_target_triple( + handler: &EarlyErrorHandler, matches: &getopts::Matches, - error_format: ErrorOutputType, ) -> TargetTriple { match matches.opt_str("target") { Some(target) if target.ends_with(".json") => { let path = Path::new(&target); TargetTriple::from_path(path).unwrap_or_else(|_| { - early_error(error_format, format!("target file {path:?} does not exist")) + handler.early_error(format!("target file {path:?} does not exist")) }) } Some(target) => TargetTriple::TargetTriple(target), @@ -1995,9 +2231,9 @@ pub fn parse_target_triple( } fn parse_opt_level( + handler: &EarlyErrorHandler, matches: &getopts::Matches, cg: &CodegenOptions, - error_format: ErrorOutputType, ) -> OptLevel { // The `-O` and `-C opt-level` flags specify the same setting, so we want to be able // to use them interchangeably. However, because they're technically different flags, @@ -2025,13 +2261,10 @@ fn parse_opt_level( "s" => OptLevel::Size, "z" => OptLevel::SizeMin, arg => { - early_error( - error_format, - format!( - "optimization level needs to be \ + handler.early_error(format!( + "optimization level needs to be \ between 0-3, s or z (instead was `{arg}`)" - ), - ); + )); } } } @@ -2051,23 +2284,23 @@ fn select_debuginfo(matches: &getopts::Matches, cg: &CodegenOptions) -> DebugInf } pub(crate) fn parse_assert_incr_state( + handler: &EarlyErrorHandler, opt_assertion: &Option, - error_format: ErrorOutputType, ) -> Option { match opt_assertion { Some(s) if s.as_str() == "loaded" => Some(IncrementalStateAssertion::Loaded), Some(s) if s.as_str() == "not-loaded" => Some(IncrementalStateAssertion::NotLoaded), Some(s) => { - early_error(error_format, format!("unexpected incremental state assertion value: {s}")) + handler.early_error(format!("unexpected incremental state assertion value: {s}")) } None => None, } } fn parse_native_lib_kind( + handler: &EarlyErrorHandler, matches: &getopts::Matches, kind: &str, - error_format: ErrorOutputType, ) -> (NativeLibKind, Option) { let (kind, modifiers) = match kind.split_once(':') { None => (kind, None), @@ -2085,35 +2318,31 @@ fn parse_native_lib_kind( } else { ", the `-Z unstable-options` flag must also be passed to use it" }; - early_error(error_format, format!("library kind `link-arg` is unstable{why}")) + handler.early_error(format!("library kind `link-arg` is unstable{why}")) } NativeLibKind::LinkArg } - _ => early_error( - error_format, - format!( - "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg" - ), - ), + _ => handler.early_error(format!( + "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg" + )), }; match modifiers { None => (kind, None), - Some(modifiers) => parse_native_lib_modifiers(kind, modifiers, error_format, matches), + Some(modifiers) => parse_native_lib_modifiers(handler, kind, modifiers, matches), } } fn parse_native_lib_modifiers( + handler: &EarlyErrorHandler, mut kind: NativeLibKind, modifiers: &str, - error_format: ErrorOutputType, matches: &getopts::Matches, ) -> (NativeLibKind, Option) { let mut verbatim = None; for modifier in modifiers.split(',') { let (modifier, value) = match modifier.strip_prefix(['+', '-']) { Some(m) => (m, modifier.starts_with('+')), - None => early_error( - error_format, + None => handler.early_error( "invalid linking modifier syntax, expected '+' or '-' prefix \ before one of: bundle, verbatim, whole-archive, as-needed", ), @@ -2126,21 +2355,20 @@ fn parse_native_lib_modifiers( } else { ", the `-Z unstable-options` flag must also be passed to use it" }; - early_error(error_format, format!("linking modifier `{modifier}` is unstable{why}")) + handler.early_error(format!("linking modifier `{modifier}` is unstable{why}")) } }; let assign_modifier = |dst: &mut Option| { if dst.is_some() { let msg = format!("multiple `{modifier}` modifiers in a single `-l` option"); - early_error(error_format, msg) + handler.early_error(msg) } else { *dst = Some(value); } }; match (modifier, &mut kind) { ("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle), - ("bundle", _) => early_error( - error_format, + ("bundle", _) => handler.early_error( "linking modifier `bundle` is only compatible with `static` linking kind", ), @@ -2149,8 +2377,7 @@ fn parse_native_lib_modifiers( ("whole-archive", NativeLibKind::Static { whole_archive, .. }) => { assign_modifier(whole_archive) } - ("whole-archive", _) => early_error( - error_format, + ("whole-archive", _) => handler.early_error( "linking modifier `whole-archive` is only compatible with `static` linking kind", ), @@ -2159,28 +2386,24 @@ fn parse_native_lib_modifiers( report_unstable_modifier(); assign_modifier(as_needed) } - ("as-needed", _) => early_error( - error_format, + ("as-needed", _) => handler.early_error( "linking modifier `as-needed` is only compatible with \ `dylib` and `framework` linking kinds", ), // Note: this error also excludes the case with empty modifier // string, like `modifiers = ""`. - _ => early_error( - error_format, - format!( - "unknown linking modifier `{modifier}`, expected one \ + _ => handler.early_error(format!( + "unknown linking modifier `{modifier}`, expected one \ of: bundle, verbatim, whole-archive, as-needed" - ), - ), + )), } } (kind, verbatim) } -fn parse_libs(matches: &getopts::Matches, error_format: ErrorOutputType) -> Vec { +fn parse_libs(handler: &EarlyErrorHandler, matches: &getopts::Matches) -> Vec { matches .opt_strs("l") .into_iter() @@ -2194,7 +2417,7 @@ fn parse_libs(matches: &getopts::Matches, error_format: ErrorOutputType) -> Vec< let (name, kind, verbatim) = match s.split_once('=') { None => (s, NativeLibKind::Unspecified, None), Some((kind, name)) => { - let (kind, verbatim) = parse_native_lib_kind(matches, kind, error_format); + let (kind, verbatim) = parse_native_lib_kind(handler, matches, kind); (name.to_string(), kind, verbatim) } }; @@ -2204,7 +2427,7 @@ fn parse_libs(matches: &getopts::Matches, error_format: ErrorOutputType) -> Vec< Some((name, new_name)) => (name.to_string(), Some(new_name.to_owned())), }; if name.is_empty() { - early_error(error_format, "library name must not be empty"); + handler.early_error("library name must not be empty"); } NativeLib { name, new_name, kind, verbatim } }) @@ -2212,9 +2435,9 @@ fn parse_libs(matches: &getopts::Matches, error_format: ErrorOutputType) -> Vec< } pub fn parse_externs( + handler: &EarlyErrorHandler, matches: &getopts::Matches, unstable_opts: &UnstableOptions, - error_format: ErrorOutputType, ) -> Externs { let is_unstable_enabled = unstable_opts.unstable_options; let mut externs: BTreeMap = BTreeMap::new(); @@ -2278,8 +2501,7 @@ pub fn parse_externs( let mut force = false; if let Some(opts) = options { if !is_unstable_enabled { - early_error( - error_format, + handler.early_error( "the `-Z unstable-options` flag must also be passed to \ enable `--extern` options", ); @@ -2291,15 +2513,14 @@ pub fn parse_externs( if let ExternLocation::ExactPaths(_) = &entry.location { add_prelude = false; } else { - early_error( - error_format, + handler.early_error( "the `noprelude` --extern option requires a file path", ); } } "nounused" => nounused_dep = true, "force" => force = true, - _ => early_error(error_format, format!("unknown --extern option `{opt}`")), + _ => handler.early_error(format!("unknown --extern option `{opt}`")), } } } @@ -2318,18 +2539,15 @@ pub fn parse_externs( } fn parse_remap_path_prefix( + handler: &EarlyErrorHandler, matches: &getopts::Matches, unstable_opts: &UnstableOptions, - error_format: ErrorOutputType, ) -> Vec<(PathBuf, PathBuf)> { let mut mapping: Vec<(PathBuf, PathBuf)> = matches .opt_strs("remap-path-prefix") .into_iter() .map(|remap| match remap.rsplit_once('=') { - None => early_error( - error_format, - "--remap-path-prefix must contain '=' between FROM and TO", - ), + None => handler.early_error("--remap-path-prefix must contain '=' between FROM and TO"), Some((from, to)) => (PathBuf::from(from), PathBuf::from(to)), }) .collect(); @@ -2345,86 +2563,77 @@ fn parse_remap_path_prefix( // JUSTIFICATION: before wrapper fn is available #[allow(rustc::bad_opt_access)] -pub fn build_session_options(matches: &getopts::Matches) -> Options { - let color = parse_color(matches); +pub fn build_session_options( + handler: &mut EarlyErrorHandler, + matches: &getopts::Matches, +) -> Options { + let color = parse_color(handler, matches); - let edition = parse_crate_edition(matches); + let edition = parse_crate_edition(handler, matches); let JsonConfig { json_rendered, json_artifact_notifications, json_unused_externs, json_future_incompat, - } = parse_json(matches); + } = parse_json(handler, matches); - let error_format = parse_error_format(matches, color, json_rendered); + let error_format = parse_error_format(handler, matches, color, json_rendered); + + handler.abort_if_error_and_set_error_format(error_format); let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_else(|_| { - early_error(error_format, "`--diagnostic-width` must be an positive integer"); + handler.early_error("`--diagnostic-width` must be an positive integer"); }); let unparsed_crate_types = matches.opt_strs("crate-type"); let crate_types = parse_crate_types_from_list(unparsed_crate_types) - .unwrap_or_else(|e| early_error(error_format, e)); + .unwrap_or_else(|e| handler.early_error(e)); - let mut unstable_opts = UnstableOptions::build(matches, error_format); - let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format); + let mut unstable_opts = UnstableOptions::build(handler, matches); + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(handler, matches); - check_error_format_stability(&unstable_opts, error_format, json_rendered); + check_error_format_stability(handler, &unstable_opts, error_format, json_rendered); if !unstable_opts.unstable_options && json_unused_externs.is_enabled() { - early_error( - error_format, + handler.early_error( "the `-Z unstable-options` flag must also be passed to enable \ the flag `--json=unused-externs`", ); } - let output_types = parse_output_types(&unstable_opts, matches, error_format); + let output_types = parse_output_types(handler, &unstable_opts, matches); - let mut cg = CodegenOptions::build(matches, error_format); - let (disable_local_thinlto, mut codegen_units) = should_override_cgus_and_disable_thinlto( - &output_types, - matches, - error_format, - cg.codegen_units, - ); + let mut cg = CodegenOptions::build(handler, matches); + let (disable_local_thinlto, mut codegen_units) = + should_override_cgus_and_disable_thinlto(handler, &output_types, matches, cg.codegen_units); - check_thread_count(&unstable_opts, error_format); + check_thread_count(handler, &unstable_opts); let incremental = cg.incremental.as_ref().map(PathBuf::from); - let assert_incr_state = parse_assert_incr_state(&unstable_opts.assert_incr_state, error_format); + let assert_incr_state = parse_assert_incr_state(handler, &unstable_opts.assert_incr_state); if unstable_opts.profile && incremental.is_some() { - early_error( - error_format, - "can't instrument with gcov profiling when compiling incrementally", - ); + handler.early_error("can't instrument with gcov profiling when compiling incrementally"); } if unstable_opts.profile { match codegen_units { Some(1) => {} None => codegen_units = Some(1), - Some(_) => early_error( - error_format, - "can't instrument with gcov profiling with multiple codegen units", - ), + Some(_) => handler + .early_error("can't instrument with gcov profiling with multiple codegen units"), } } if cg.profile_generate.enabled() && cg.profile_use.is_some() { - early_error( - error_format, - "options `-C profile-generate` and `-C profile-use` are exclusive", - ); + handler.early_error("options `-C profile-generate` and `-C profile-use` are exclusive"); } if unstable_opts.profile_sample_use.is_some() && (cg.profile_generate.enabled() || cg.profile_use.is_some()) { - early_error( - error_format, + handler.early_error( "option `-Z profile-sample-use` cannot be used with `-C profile-generate` or `-C profile-use`", ); } @@ -2433,23 +2642,19 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { // precedence. match (cg.symbol_mangling_version, unstable_opts.symbol_mangling_version) { (Some(smv_c), Some(smv_z)) if smv_c != smv_z => { - early_error( - error_format, + handler.early_error( "incompatible values passed for `-C symbol-mangling-version` \ and `-Z symbol-mangling-version`", ); } (Some(SymbolManglingVersion::V0), _) => {} (Some(_), _) if !unstable_opts.unstable_options => { - early_error( - error_format, - "`-C symbol-mangling-version=legacy` requires `-Z unstable-options`", - ); + handler + .early_error("`-C symbol-mangling-version=legacy` requires `-Z unstable-options`"); } (None, None) => {} (None, smv) => { - early_warn( - error_format, + handler.early_warn( "`-Z symbol-mangling-version` is deprecated; use `-C symbol-mangling-version`", ); cg.symbol_mangling_version = smv; @@ -2461,25 +2666,19 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { // precedence. match (cg.instrument_coverage, unstable_opts.instrument_coverage) { (Some(ic_c), Some(ic_z)) if ic_c != ic_z => { - early_error( - error_format, + handler.early_error( "incompatible values passed for `-C instrument-coverage` \ and `-Z instrument-coverage`", ); } (Some(InstrumentCoverage::Off | InstrumentCoverage::All), _) => {} (Some(_), _) if !unstable_opts.unstable_options => { - early_error( - error_format, - "`-C instrument-coverage=except-*` requires `-Z unstable-options`", - ); + handler.early_error("`-C instrument-coverage=except-*` requires `-Z unstable-options`"); } (None, None) => {} (None, ic) => { - early_warn( - error_format, - "`-Z instrument-coverage` is deprecated; use `-C instrument-coverage`", - ); + handler + .early_warn("`-Z instrument-coverage` is deprecated; use `-C instrument-coverage`"); cg.instrument_coverage = ic; } _ => {} @@ -2487,8 +2686,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { if cg.instrument_coverage.is_some() && cg.instrument_coverage != Some(InstrumentCoverage::Off) { if cg.profile_generate.enabled() || cg.profile_use.is_some() { - early_error( - error_format, + handler.early_error( "option `-C instrument-coverage` is not compatible with either `-C profile-use` \ or `-C profile-generate`", ); @@ -2501,8 +2699,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { match cg.symbol_mangling_version { None => cg.symbol_mangling_version = Some(SymbolManglingVersion::V0), Some(SymbolManglingVersion::Legacy) => { - early_warn( - error_format, + handler.early_warn( "-C instrument-coverage requires symbol mangling version `v0`, \ but `-C symbol-mangling-version=legacy` was specified", ); @@ -2518,20 +2715,44 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { if !cg.embed_bitcode { match cg.lto { LtoCli::No | LtoCli::Unspecified => {} - LtoCli::Yes | LtoCli::NoParam | LtoCli::Thin | LtoCli::Fat => early_error( - error_format, - "options `-C embed-bitcode=no` and `-C lto` are incompatible", - ), + LtoCli::Yes | LtoCli::NoParam | LtoCli::Thin | LtoCli::Fat => { + handler.early_error("options `-C embed-bitcode=no` and `-C lto` are incompatible") + } + } + } + + // For testing purposes, until we have more feedback about these options: ensure `-Z + // unstable-options` is required when using the unstable `-C link-self-contained` options, like + // `-C link-self-contained=+linker`, and when using the unstable `-C linker-flavor` options, like + // `-C linker-flavor=gnu-lld-cc`. + if !nightly_options::is_unstable_enabled(matches) { + let uses_unstable_self_contained_option = + cg.link_self_contained.are_unstable_variants_set(); + if uses_unstable_self_contained_option { + handler.early_error( + "only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, \ + the `-Z unstable-options` flag must also be passed to use the unstable values", + ); + } + + if let Some(flavor) = cg.linker_flavor { + if flavor.is_unstable() { + handler.early_error(format!( + "the linker flavor `{}` is unstable, the `-Z unstable-options` \ + flag must also be passed to use the unstable values", + flavor.desc() + )); + } } } - let prints = collect_print_requests(&mut cg, &mut unstable_opts, matches, error_format); + let prints = collect_print_requests(handler, &mut cg, &mut unstable_opts, matches); let cg = cg; let sysroot_opt = matches.opt_str("sysroot").map(|m| PathBuf::from(&m)); - let target_triple = parse_target_triple(matches, error_format); - let opt_level = parse_opt_level(matches, &cg, error_format); + let target_triple = parse_target_triple(handler, matches); + let opt_level = parse_opt_level(handler, matches, &cg); // The `-g` and `-C debuginfo` flags specify the same setting, so we want to be able // to use them interchangeably. See the note above (regarding `-O` and `-C opt-level`) // for more details. @@ -2540,28 +2761,32 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { let mut search_paths = vec![]; for s in &matches.opt_strs("L") { - search_paths.push(SearchPath::from_cli_opt(s, error_format)); + search_paths.push(SearchPath::from_cli_opt(handler, s)); } - let libs = parse_libs(matches, error_format); + let libs = parse_libs(handler, matches); let test = matches.opt_present("test"); if !cg.remark.is_empty() && debuginfo == DebugInfo::None { - early_warn(error_format, "-C remark requires \"-C debuginfo=n\" to show source locations"); + handler.early_warn("-C remark requires \"-C debuginfo=n\" to show source locations"); + } + + if cg.remark.is_empty() && unstable_opts.remark_dir.is_some() { + handler.early_warn("using -Z remark-dir without enabling remarks using e.g. -C remark=all"); } - let externs = parse_externs(matches, &unstable_opts, error_format); + let externs = parse_externs(handler, matches, &unstable_opts); let crate_name = matches.opt_str("crate-name"); - let remap_path_prefix = parse_remap_path_prefix(matches, &unstable_opts, error_format); + let remap_path_prefix = parse_remap_path_prefix(handler, matches, &unstable_opts); - let pretty = parse_pretty(&unstable_opts, error_format); + let pretty = parse_pretty(handler, &unstable_opts); // query-dep-graph is required if dump-dep-graph is given #106736 if unstable_opts.dump_dep_graph && !unstable_opts.query_dep_graph { - early_error(error_format, "can't dump dependency graph without `-Z query-dep-graph`"); + handler.early_error("can't dump dependency graph without `-Z query-dep-graph`"); } // Try to find a directory containing the Rust `src`, for more details see @@ -2593,7 +2818,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { }; let working_dir = std::env::current_dir().unwrap_or_else(|e| { - early_error(error_format, format!("Current directory is invalid: {e}")); + handler.early_error(format!("Current directory is invalid: {e}")); }); let remap = FilePathMapping::new(remap_path_prefix.clone()); @@ -2641,10 +2866,11 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { json_future_incompat, pretty, working_dir, + color, } } -fn parse_pretty(unstable_opts: &UnstableOptions, efmt: ErrorOutputType) -> Option { +fn parse_pretty(handler: &EarlyErrorHandler, unstable_opts: &UnstableOptions) -> Option { use PpMode::*; let first = match unstable_opts.unpretty.as_deref()? { @@ -2663,16 +2889,13 @@ fn parse_pretty(unstable_opts: &UnstableOptions, efmt: ErrorOutputType) -> Optio "thir-flat" => ThirFlat, "mir" => Mir, "mir-cfg" => MirCFG, - name => early_error( - efmt, - format!( - "argument to `unpretty` must be one of `normal`, `identified`, \ + name => handler.early_error(format!( + "argument to `unpretty` must be one of `normal`, `identified`, \ `expanded`, `expanded,identified`, `expanded,hygiene`, \ `ast-tree`, `ast-tree,expanded`, `hir`, `hir,identified`, \ `hir,typed`, `hir-tree`, `thir-tree`, `thir-flat`, `mir` or \ `mir-cfg`; got {name}" - ), - ), + )), }; debug!("got unpretty option: {first:?}"); Some(first) @@ -2712,8 +2935,8 @@ pub fn parse_crate_types_from_list(list_list: Vec) -> Result bool { @@ -2729,7 +2952,11 @@ pub mod nightly_options { UnstableFeatures::from_environment(krate).is_nightly_build() } - pub fn check_nightly_options(matches: &getopts::Matches, flags: &[RustcOptGroup]) { + pub fn check_nightly_options( + handler: &EarlyErrorHandler, + matches: &getopts::Matches, + flags: &[RustcOptGroup], + ) { let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); let really_allows_unstable_options = match_is_nightly_build(matches); @@ -2741,14 +2968,11 @@ pub mod nightly_options { continue; } if opt.name != "Z" && !has_z_unstable_option { - early_error( - ErrorOutputType::default(), - format!( - "the `-Z unstable-options` flag must also be passed to enable \ + handler.early_error(format!( + "the `-Z unstable-options` flag must also be passed to enable \ the flag `{}`", - opt.name - ), - ); + opt.name + )); } if really_allows_unstable_options { continue; @@ -2759,7 +2983,12 @@ pub mod nightly_options { "the option `{}` is only accepted on the nightly compiler", opt.name ); - early_error(ErrorOutputType::default(), msg); + let _ = handler.early_error_no_abort(msg); + handler.early_note("selecting a toolchain with `+toolchain` arguments require a rustup proxy; see "); + handler.early_help( + "consider switching to a nightly toolchain: `rustup default nightly`", + ); + handler.early_note("for more information about Rust's stability policy, see "); } OptionStability::Stable => {} } @@ -2892,7 +3121,7 @@ pub(crate) mod dep_tracking { use super::{ BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType, InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, - OomStrategy, OptLevel, OutputType, OutputTypes, Passes, ResolveDocLinks, + OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes, Passes, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths, }; @@ -2990,6 +3219,7 @@ pub(crate) mod dep_tracking { SourceFileHashAlgorithm, TrimmedDefPaths, Option, + OutFileName, OutputType, RealFileName, LocationDetail, diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs index dc475e8c6d57c..c53a355b533ea 100644 --- a/compiler/rustc_session/src/cstore.rs +++ b/compiler/rustc_session/src/cstore.rs @@ -13,6 +13,7 @@ use rustc_hir::definitions::{DefKey, DefPath, DefPathHash, Definitions}; use rustc_span::hygiene::{ExpnHash, ExpnId}; use rustc_span::symbol::Symbol; use rustc_span::Span; +use rustc_target::spec::abi::Abi; use rustc_target::spec::Target; use std::any::Any; @@ -147,6 +148,7 @@ pub enum DllCallingConvention { pub struct ForeignModule { pub foreign_items: Vec, pub def_id: DefId, + pub abi: Abi, } #[derive(Copy, Clone, Debug, HashStable_Generic)] diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 546c0fa8e03e3..78940462b2c85 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -114,6 +114,10 @@ pub struct CannotEnableCrtStaticLinux; #[diag(session_sanitizer_cfi_requires_lto)] pub struct SanitizerCfiRequiresLto; +#[derive(Diagnostic)] +#[diag(session_sanitizer_cfi_requires_single_codegen_unit)] +pub struct SanitizerCfiRequiresSingleCodegenUnit; + #[derive(Diagnostic)] #[diag(session_sanitizer_cfi_canonical_jump_tables_requires_cfi)] pub struct SanitizerCfiCanonicalJumpTablesRequiresCfi; @@ -163,6 +167,13 @@ pub struct FileIsNotWriteable<'a> { pub file: &'a std::path::Path, } +#[derive(Diagnostic)] +#[diag(session_file_write_fail)] +pub(crate) struct FileWriteFail<'a> { + pub path: &'a std::path::Path, + pub err: String, +} + #[derive(Diagnostic)] #[diag(session_crate_name_does_not_match)] pub struct CrateNameDoesNotMatch { @@ -192,6 +203,14 @@ pub struct InvalidCharacterInCrateName { pub span: Option, pub character: char, pub crate_name: Symbol, + #[subdiagnostic] + pub crate_name_help: Option, +} + +#[derive(Subdiagnostic)] +pub enum InvalidCrateNameHelp { + #[help(session_invalid_character_in_create_name_help)] + AddCrateName, } #[derive(Subdiagnostic)] @@ -422,3 +441,11 @@ pub fn report_lit_error(sess: &ParseSess, err: LitError, lit: token::Lit, span: pub struct OptimisationFuelExhausted { pub msg: String, } + +#[derive(Diagnostic)] +#[diag(session_incompatible_linker_flavor)] +#[note] +pub struct IncompatibleLinkerFlavor { + pub flavor: &'static str, + pub compatible_list: String, +} diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index 590a68c660061..a270817f3109b 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -10,6 +10,7 @@ #![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_macros; @@ -27,7 +28,7 @@ pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pas pub use rustc_lint_defs as lint; pub mod parse; -mod code_stats; +pub mod code_stats; #[macro_use] pub mod config; pub mod cstore; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 2c4c4a7a6ce29..055ab2d9c1583 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1,10 +1,10 @@ use crate::config::*; -use crate::early_error; -use crate::lint; use crate::search_paths::SearchPath; use crate::utils::NativeLib; +use crate::{lint, EarlyErrorHandler}; use rustc_data_structures::profiling::TimePassesFormat; +use rustc_errors::ColorConfig; use rustc_errors::{LanguageIdentifier, TerminalUrl}; use rustc_target::spec::{CodeModel, LinkerFlavorCli, MergeFunctions, PanicStrategy, SanitizerSet}; use rustc_target::spec::{ @@ -213,6 +213,7 @@ top_level_options!( /// The (potentially remapped) working directory working_dir: RealFileName [TRACKED], + color: ColorConfig [UNTRACKED], } ); @@ -245,10 +246,10 @@ macro_rules! options { impl $struct_name { pub fn build( + handler: &EarlyErrorHandler, matches: &getopts::Matches, - error_format: ErrorOutputType, ) -> $struct_name { - build_options(matches, $stat, $prefix, $outputname, error_format) + build_options(handler, matches, $stat, $prefix, $outputname) } fn dep_tracking_hash(&self, for_crate_hash: bool, error_format: ErrorOutputType) -> u64 { @@ -309,11 +310,11 @@ type OptionSetter = fn(&mut O, v: Option<&str>) -> bool; type OptionDescrs = &'static [(&'static str, OptionSetter, &'static str, &'static str)]; fn build_options( + handler: &EarlyErrorHandler, matches: &getopts::Matches, descrs: OptionDescrs, prefix: &str, outputname: &str, - error_format: ErrorOutputType, ) -> O { let mut op = O::default(); for option in matches.opt_strs(prefix) { @@ -327,15 +328,12 @@ fn build_options( Some((_, setter, type_desc, _)) => { if !setter(&mut op, value) { match value { - None => early_error( - error_format, + None => handler.early_error( format!( - "{0} option `{1}` requires {2} ({3} {1}=)", - outputname, key, type_desc, prefix + "{outputname} option `{key}` requires {type_desc} ({prefix} {key}=)" ), ), - Some(value) => early_error( - error_format, + Some(value) => handler.early_error( format!( "incorrect value `{value}` for {outputname} option `{key}` - {type_desc} was expected" ), @@ -343,7 +341,7 @@ fn build_options( } } } - None => early_error(error_format, format!("unknown {outputname} option: `{key}`")), + None => handler.early_error(format!("unknown {outputname} option: `{key}`")), } } return op; @@ -372,7 +370,7 @@ mod desc { pub const parse_opt_panic_strategy: &str = parse_panic_strategy; pub const parse_oom_strategy: &str = "either `panic` or `abort`"; pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`"; - pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `shadow-call-stack`, or `thread`"; + pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, or `thread`"; pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2"; pub const parse_cfguard: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`"; @@ -389,7 +387,7 @@ mod desc { pub const parse_unpretty: &str = "`string` or `string=string`"; pub const parse_treat_err_as_bug: &str = "either no value or a number bigger than 0"; pub const parse_trait_solver: &str = - "one of the supported solver modes (`classic`, `chalk`, or `next`)"; + "one of the supported solver modes (`classic`, `next`, or `next-coherence`)"; pub const parse_lto: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, `fat`, or omitted"; pub const parse_linker_plugin_lto: &str = @@ -413,12 +411,15 @@ mod desc { pub const parse_split_dwarf_kind: &str = "one of supported split dwarf modes (`split` or `single`)"; pub const parse_gcc_ld: &str = "one of: no value, `lld`"; + pub const parse_link_self_contained: &str = "one of: `y`, `yes`, `on`, `n`, `no`, `off`, or a list of enabled (`+` prefix) and disabled (`-` prefix) \ + components: `crto`, `libc`, `unwind`, `linker`, `sanitizers`, `mingw`"; pub const parse_stack_protector: &str = "one of (`none` (default), `basic`, `strong`, or `all`)"; pub const parse_branch_protection: &str = "a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`"; pub const parse_proc_macro_execution_strategy: &str = "one of supported execution strategies (`same-thread`, or `cross-thread`)"; + pub const parse_dump_solver_proof_tree: &str = "one of: `always`, `on-request`, `on-error`"; } mod parse { @@ -694,6 +695,7 @@ mod parse { "shadow-call-stack" => SanitizerSet::SHADOWCALLSTACK, "thread" => SanitizerSet::THREAD, "hwaddress" => SanitizerSet::HWADDRESS, + "safestack" => SanitizerSet::SAFESTACK, _ => return false, } } @@ -983,8 +985,8 @@ mod parse { pub(crate) fn parse_trait_solver(slot: &mut TraitSolver, v: Option<&str>) -> bool { match v { Some("classic") => *slot = TraitSolver::Classic, - Some("chalk") => *slot = TraitSolver::Chalk, Some("next") => *slot = TraitSolver::Next, + Some("next-coherence") => *slot = TraitSolver::NextCoherence, // default trait solver is subject to change.. Some("default") => *slot = TraitSolver::Classic, _ => return false, @@ -1123,6 +1125,34 @@ mod parse { } } + pub(crate) fn parse_link_self_contained(slot: &mut LinkSelfContained, v: Option<&str>) -> bool { + // Whenever `-C link-self-contained` is passed without a value, it's an opt-in + // just like `parse_opt_bool`, the historical value of this flag. + // + // 1. Parse historical single bool values + let s = v.unwrap_or("y"); + match s { + "y" | "yes" | "on" => { + slot.set_all_explicitly(true); + return true; + } + "n" | "no" | "off" => { + slot.set_all_explicitly(false); + return true; + } + _ => {} + } + + // 2. Parse a list of enabled and disabled components. + for comp in s.split(',') { + if slot.handle_cli_component(comp).is_err() { + return false; + } + } + + true + } + pub(crate) fn parse_wasi_exec_model(slot: &mut Option, v: Option<&str>) -> bool { match v { Some("command") => *slot = Some(WasiExecModel::Command), @@ -1209,6 +1239,19 @@ mod parse { }; true } + + pub(crate) fn parse_dump_solver_proof_tree( + slot: &mut DumpSolverProofTree, + v: Option<&str>, + ) -> bool { + match v { + None | Some("always") => *slot = DumpSolverProofTree::Always, + Some("never") => *slot = DumpSolverProofTree::Never, + Some("on-error") => *slot = DumpSolverProofTree::OnError, + _ => return false, + }; + true + } } options! { @@ -1266,9 +1309,9 @@ options! { #[rustc_lint_opt_deny_field_access("use `Session::link_dead_code` instead of this field")] link_dead_code: Option = (None, parse_opt_bool, [TRACKED], "keep dead code at link time (useful for code coverage) (default: no)"), - link_self_contained: Option = (None, parse_opt_bool, [UNTRACKED], + link_self_contained: LinkSelfContained = (LinkSelfContained::default(), parse_link_self_contained, [UNTRACKED], "control whether to link Rust provided C objects/libraries or rely - on C toolchain installed in the system"), + on a C toolchain or linker installed in the system"), linker: Option = (None, parse_opt_pathbuf, [UNTRACKED], "system linker to link outputs with"), linker_flavor: Option = (None, parse_linker_flavor, [UNTRACKED], @@ -1315,7 +1358,7 @@ options! { "control generation of position-independent code (PIC) \ (`rustc --print relocation-models` for details)"), remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED], - "print remarks for these optimization passes (space separated, or \"all\")"), + "output remarks for these optimization passes (space separated, or \"all\")"), rpath: bool = (false, parse_bool, [UNTRACKED], "set rpath values in libs/exes (default: no)"), save_temps: bool = (false, parse_bool, [UNTRACKED], @@ -1371,8 +1414,6 @@ options! { "set options for branch target identification and pointer authentication on AArch64"), cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED], "instrument control-flow architecture protection"), - cgu_partitioning_strategy: Option = (None, parse_opt_string, [TRACKED], - "the codegen unit partitioning strategy to use"), codegen_backend: Option = (None, parse_opt_string, [TRACKED], "the backend to use"), combine_cgu: bool = (false, parse_bool, [TRACKED], @@ -1391,8 +1432,6 @@ options! { dep_tasks: bool = (false, parse_bool, [UNTRACKED], "print tasks that execute and the color their dep node gets (requires debug build) \ (default: no)"), - diagnostic_width: Option = (None, parse_opt_number, [UNTRACKED], - "set the current output width for diagnostic truncation"), dont_buffer_diagnostics: bool = (false, parse_bool, [UNTRACKED], "emit diagnostics rather than buffering (breaks NLL error downgrading, sorting) \ (default: no)"), @@ -1436,6 +1475,11 @@ options! { "output statistics about monomorphization collection"), dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED], "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"), + dump_solver_proof_tree: DumpSolverProofTree = (DumpSolverProofTree::Never, parse_dump_solver_proof_tree, [UNTRACKED], + "dump a proof tree for every goal evaluated by the new trait solver. If the flag is specified without any options after it + then it defaults to `always`. If the flag is not specified at all it defaults to `on-request`."), + dump_solver_proof_tree_use_cache: Option = (None, parse_opt_bool, [UNTRACKED], + "determines whether dumped proof trees use the global cache"), dwarf_version: Option = (None, parse_opt_number, [TRACKED], "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), dylib_lto: bool = (false, parse_bool, [UNTRACKED], @@ -1536,9 +1580,6 @@ options! { "what location details should be tracked when using caller_location, either \ `none`, or a comma separated list of location details, for which \ valid options are `file`, `line`, and `column` (default: `file,line,column`)"), - lower_impl_trait_in_trait_to_assoc_ty: bool = (false, parse_bool, [TRACKED], - "modify the lowering strategy for `impl Trait` in traits so that they are lowered to \ - generic associated types"), ls: bool = (false, parse_bool, [UNTRACKED], "list the symbols defined by a library crate (default: no)"), macro_backtrace: bool = (false, parse_bool, [UNTRACKED], @@ -1558,14 +1599,14 @@ options! { "use like `-Zmir-enable-passes=+DestinationPropagation,-InstSimplify`. Forces the specified passes to be \ enabled, overriding all other checks. Passes that are not specified are enabled or \ disabled by other flags as usual."), + mir_include_spans: bool = (false, parse_bool, [UNTRACKED], + "use line numbers relative to the function in mir pretty printing"), mir_keep_place_mention: bool = (false, parse_bool, [TRACKED], "keep place mention MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ (default: no)"), #[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")] mir_opt_level: Option = (None, parse_opt_number, [TRACKED], "MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"), - mir_pretty_relative_line_numbers: bool = (false, parse_bool, [UNTRACKED], - "use line numbers relative to the function in mir pretty printing"), move_size_limit: Option = (None, parse_opt_number, [TRACKED], "the size at which the `large_assignments` lint starts to be emitted"), mutable_noalias: bool = (true, parse_bool, [TRACKED], @@ -1611,7 +1652,7 @@ options! { plt: Option = (None, parse_opt_bool, [TRACKED], "whether to use the PLT when calling into shared libraries; only has effect for PIC code on systems with ELF binaries - (default: PLT is disabled if full relro is enabled)"), + (default: PLT is disabled if full relro is enabled on x86_64)"), polonius: bool = (false, parse_bool, [TRACKED], "enable polonius-based borrow-checker (default: no)"), polymorphize: bool = (false, parse_bool, [TRACKED], @@ -1624,6 +1665,9 @@ options! { "use a more precise version of drop elaboration for matches on enums (default: yes). \ This results in better codegen, but has caused miscompilations on some tier 2 platforms. \ See #77382 and #74551."), + #[rustc_lint_opt_deny_field_access("use `Session::print_codegen_stats` instead of this field")] + print_codegen_stats: bool = (false, parse_bool, [UNTRACKED], + "print codegen statistics (default: no)"), print_fuel: Option = (None, parse_opt_string, [TRACKED], "make rustc print the total optimization fuel used by a crate"), print_llvm_passes: bool = (false, parse_bool, [UNTRACKED], @@ -1632,6 +1676,8 @@ options! { "print the result of the monomorphization collection pass"), print_type_sizes: bool = (false, parse_bool, [UNTRACKED], "print layout information for each type encountered (default: no)"), + print_vtable_sizes: bool = (false, parse_bool, [UNTRACKED], + "print size comparison between old and new vtable layouts (default: no)"), proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED], "show backtraces for panics during proc-macro execution (default: no)"), proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, @@ -1658,6 +1704,9 @@ options! { "choose which RELRO level to use"), remap_cwd_prefix: Option = (None, parse_opt_pathbuf, [TRACKED], "remap paths under the current working directory to this path prefix"), + remark_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], + "directory into which to write optimization remarks (if not specified, they will be \ +written to standard error output)"), report_delayed_bugs: bool = (false, parse_bool, [TRACKED], "immediately print bugs registered with `delay_span_bug` (default: no)"), sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], @@ -1826,10 +1875,13 @@ options! { Requires `-Clto[=[fat,yes]]`"), wasi_exec_model: Option = (None, parse_wasi_exec_model, [TRACKED], "whether to build a wasi command or reactor"), + write_long_types_to_disk: bool = (true, parse_bool, [UNTRACKED], + "whether long type names should be written to files instead of being printed in errors"), // tidy-alphabetical-end // If you add a new option, please update: // - compiler/rustc_interface/src/tests.rs + // - src/doc/unstable-book/src/compiler-flags } #[derive(Clone, Hash, PartialEq, Eq, Debug)] diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs index fdb9fae44e153..c0884fb21cd18 100644 --- a/compiler/rustc_session/src/output.rs +++ b/compiler/rustc_session/src/output.rs @@ -1,21 +1,21 @@ //! Related to out filenames of compilation (e.g. save analysis, binaries). -use crate::config::{CrateType, Input, OutputFilenames, OutputType}; +use crate::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; use crate::errors::{ CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable, - InvalidCharacterInCrateName, + InvalidCharacterInCrateName, InvalidCrateNameHelp, }; use crate::Session; use rustc_ast::{self as ast, attr}; use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; -use std::path::{Path, PathBuf}; +use std::path::Path; pub fn out_filename( sess: &Session, crate_type: CrateType, outputs: &OutputFilenames, crate_name: Symbol, -) -> PathBuf { +) -> OutFileName { let default_filename = filename_for_input(sess, crate_type, crate_name, outputs); let out_filename = outputs .outputs @@ -24,7 +24,9 @@ pub fn out_filename( .or_else(|| outputs.single_output_file.clone()) .unwrap_or(default_filename); - check_file_is_writeable(&out_filename, sess); + if let OutFileName::Real(ref path) = out_filename { + check_file_is_writeable(path, sess); + } out_filename } @@ -99,7 +101,16 @@ pub fn validate_crate_name(sess: &Session, s: Symbol, sp: Option) { continue; } err_count += 1; - sess.emit_err(InvalidCharacterInCrateName { span: sp, character: c, crate_name: s }); + sess.emit_err(InvalidCharacterInCrateName { + span: sp, + character: c, + crate_name: s, + crate_name_help: if sp.is_none() { + Some(InvalidCrateNameHelp::AddCrateName) + } else { + None + }, + }); } } @@ -112,7 +123,7 @@ pub fn filename_for_metadata( sess: &Session, crate_name: Symbol, outputs: &OutputFilenames, -) -> PathBuf { +) -> OutFileName { // If the command-line specified the path, use that directly. if let Some(Some(out_filename)) = sess.opts.output_types.get(&OutputType::Metadata) { return out_filename.clone(); @@ -120,12 +131,13 @@ pub fn filename_for_metadata( let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); - let out_filename = outputs - .single_output_file - .clone() - .unwrap_or_else(|| outputs.out_directory.join(&format!("lib{libname}.rmeta"))); + let out_filename = outputs.single_output_file.clone().unwrap_or_else(|| { + OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rmeta"))) + }); - check_file_is_writeable(&out_filename, sess); + if let OutFileName::Real(ref path) = out_filename { + check_file_is_writeable(path, sess); + } out_filename } @@ -135,23 +147,33 @@ pub fn filename_for_input( crate_type: CrateType, crate_name: Symbol, outputs: &OutputFilenames, -) -> PathBuf { +) -> OutFileName { let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); match crate_type { - CrateType::Rlib => outputs.out_directory.join(&format!("lib{libname}.rlib")), + CrateType::Rlib => { + OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rlib"))) + } CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => { let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix); - outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")) + OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))) } CrateType::Staticlib => { let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix); - outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")) + OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}"))) } CrateType::Executable => { let suffix = &sess.target.exe_suffix; let out_filename = outputs.path(OutputType::Exe); - if suffix.is_empty() { out_filename } else { out_filename.with_extension(&suffix[1..]) } + if let OutFileName::Real(ref path) = out_filename { + if suffix.is_empty() { + out_filename + } else { + OutFileName::Real(path.with_extension(&suffix[1..])) + } + } else { + out_filename + } } } } diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 7b396dde91ba1..1cf63e9b7ba74 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -8,8 +8,8 @@ use crate::lint::{ }; use rustc_ast::node_id::NodeId; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; -use rustc_data_structures::sync::{AppendOnlyVec, AtomicBool, Lock, Lrc}; -use rustc_errors::{emitter::SilentEmitter, ColorConfig, Handler}; +use rustc_data_structures::sync::{AppendOnlyVec, Lock, Lrc}; +use rustc_errors::{emitter::SilentEmitter, Handler}; use rustc_errors::{ fallback_fluent_bundle, Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, EmissionGuarantee, ErrorGuaranteed, IntoDiagnostic, MultiSpan, Noted, StashKey, @@ -51,13 +51,6 @@ impl GatedSpans { debug_assert_eq!(span, removed_span); } - /// Is the provided `feature` gate ungated currently? - /// - /// Using this is discouraged unless you have a really good reason to. - pub fn is_ungated(&self, feature: Symbol) -> bool { - self.spans.borrow().get(&feature).map_or(true, |spans| spans.is_empty()) - } - /// Prepend the given set of `spans` onto the set in `self`. pub fn merge(&self, mut spans: FxHashMap>) { let mut inner = self.spans.borrow_mut(); @@ -84,6 +77,7 @@ impl SymbolGallery { /// Construct a diagnostic for a language feature error due to the given `span`. /// The `feature`'s `Symbol` is the one you used in `active.rs` and `rustc_span::symbols`. +#[track_caller] pub fn feature_err( sess: &ParseSess, feature: Symbol, @@ -123,7 +117,8 @@ pub fn feature_err_issue( /// Construct a future incompatibility diagnostic for a feature gate. /// /// This diagnostic is only a warning and *does not cause compilation to fail*. -pub fn feature_warn(sess: &ParseSess, feature: Symbol, span: Span, explain: &str) { +#[track_caller] +pub fn feature_warn(sess: &ParseSess, feature: Symbol, span: Span, explain: &'static str) { feature_warn_issue(sess, feature, span, GateIssue::Language, explain); } @@ -135,12 +130,13 @@ pub fn feature_warn(sess: &ParseSess, feature: Symbol, span: Span, explain: &str /// Almost always, you want to use this for a language feature. If so, prefer `feature_warn`. #[allow(rustc::diagnostic_outside_of_impl)] #[allow(rustc::untranslatable_diagnostic)] +#[track_caller] pub fn feature_warn_issue( sess: &ParseSess, feature: Symbol, span: Span, issue: GateIssue, - explain: &str, + explain: &'static str, ) { let mut err = sess.span_diagnostic.struct_span_warn(span, explain); add_feature_diagnostics_for_issue(&mut err, sess, feature, issue); @@ -208,8 +204,6 @@ pub struct ParseSess { pub ambiguous_block_expr_parse: Lock>, pub gated_spans: GatedSpans, pub symbol_gallery: SymbolGallery, - /// The parser has reached `Eof` due to an unclosed brace. Used to silence unnecessary errors. - pub reached_eof: AtomicBool, /// Environment variables accessed during the build and their values when they exist. pub env_depinfo: Lock)>>, /// File paths accessed during the build. @@ -228,14 +222,7 @@ impl ParseSess { pub fn new(locale_resources: Vec<&'static str>, file_path_mapping: FilePathMapping) -> Self { let fallback_bundle = fallback_fluent_bundle(locale_resources, false); let sm = Lrc::new(SourceMap::new(file_path_mapping)); - let handler = Handler::with_tty_emitter( - ColorConfig::Auto, - true, - None, - Some(sm.clone()), - None, - fallback_bundle, - ); + let handler = Handler::with_tty_emitter(Some(sm.clone()), fallback_bundle); ParseSess::with_span_handler(handler, sm) } @@ -253,7 +240,6 @@ impl ParseSess { ambiguous_block_expr_parse: Lock::new(FxHashMap::default()), gated_spans: GatedSpans::default(), symbol_gallery: SymbolGallery::default(), - reached_eof: AtomicBool::new(false), env_depinfo: Default::default(), file_depinfo: Default::default(), assume_incomplete_release: false, @@ -265,13 +251,9 @@ impl ParseSess { pub fn with_silent_emitter(fatal_note: Option) -> Self { let fallback_bundle = fallback_fluent_bundle(Vec::new(), false); let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let fatal_handler = - Handler::with_tty_emitter(ColorConfig::Auto, false, None, None, None, fallback_bundle); - let handler = Handler::with_emitter( - false, - None, - Box::new(SilentEmitter { fatal_handler, fatal_note }), - ); + let fatal_handler = Handler::with_tty_emitter(None, fallback_bundle).disable_warnings(); + let handler = Handler::with_emitter(Box::new(SilentEmitter { fatal_handler, fatal_note })) + .disable_warnings(); ParseSess::with_span_handler(handler, sm) } @@ -357,6 +339,7 @@ impl ParseSess { self.create_warning(warning).emit() } + #[track_caller] pub fn create_note<'a>( &'a self, note: impl IntoDiagnostic<'a, Noted>, @@ -364,10 +347,12 @@ impl ParseSess { note.into_diagnostic(&self.span_diagnostic) } + #[track_caller] pub fn emit_note<'a>(&'a self, note: impl IntoDiagnostic<'a, Noted>) -> Noted { self.create_note(note).emit() } + #[track_caller] pub fn create_fatal<'a>( &'a self, fatal: impl IntoDiagnostic<'a, !>, @@ -375,6 +360,7 @@ impl ParseSess { fatal.into_diagnostic(&self.span_diagnostic) } + #[track_caller] pub fn emit_fatal<'a>(&'a self, fatal: impl IntoDiagnostic<'a, !>) -> ! { self.create_fatal(fatal).emit() } @@ -389,16 +375,19 @@ impl ParseSess { } #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_warn(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { self.span_diagnostic.struct_warn(msg) } #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_fatal(&self, msg: impl Into) -> DiagnosticBuilder<'_, !> { self.span_diagnostic.struct_fatal(msg) } #[rustc_lint_diagnostics] + #[track_caller] pub fn struct_diagnostic( &self, msg: impl Into, diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs index 56a6b6f3b03ef..07e78d1760e80 100644 --- a/compiler/rustc_session/src/search_paths.rs +++ b/compiler/rustc_session/src/search_paths.rs @@ -1,5 +1,5 @@ use crate::filesearch::make_target_lib_path; -use crate::{config, early_error}; +use crate::EarlyErrorHandler; use std::path::{Path, PathBuf}; #[derive(Clone, Debug)] @@ -46,7 +46,7 @@ impl PathKind { } impl SearchPath { - pub fn from_cli_opt(path: &str, output: config::ErrorOutputType) -> Self { + pub fn from_cli_opt(handler: &EarlyErrorHandler, path: &str) -> Self { let (kind, path) = if let Some(stripped) = path.strip_prefix("native=") { (PathKind::Native, stripped) } else if let Some(stripped) = path.strip_prefix("crate=") { @@ -61,7 +61,7 @@ impl SearchPath { (PathKind::All, path) }; if path.is_empty() { - early_error(output, "empty search path given via `-L`"); + handler.early_error("empty search path given via `-L`"); } let dir = PathBuf::from(path); diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index bbe52dbced071..086ce4e69646a 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,8 +1,10 @@ use crate::cgu_reuse_tracker::CguReuseTracker; use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; -use crate::config::Input; -use crate::config::{self, CrateType, InstrumentCoverage, OptLevel, OutputType, SwitchWithOptPath}; +use crate::config::{ + self, CrateType, InstrumentCoverage, OptLevel, OutFileName, OutputType, SwitchWithOptPath, +}; +use crate::config::{ErrorOutputType, Input}; use crate::errors; use crate::parse::{add_feature_diagnostics, ParseSess}; use crate::search_paths::{PathKind, SearchPath}; @@ -15,7 +17,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; use rustc_data_structures::jobserver::{self, Client}; use rustc_data_structures::profiling::{duration_to_secs_str, SelfProfiler, SelfProfilerRef}; use rustc_data_structures::sync::{ - self, AtomicU64, AtomicUsize, Lock, Lrc, OnceCell, OneThread, Ordering, Ordering::SeqCst, + self, AtomicU64, AtomicUsize, Lock, Lrc, OneThread, Ordering, Ordering::SeqCst, }; use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitterWriter; use rustc_errors::emitter::{Emitter, EmitterWriter, HumanReadableErrorType}; @@ -23,7 +25,7 @@ use rustc_errors::json::JsonEmitter; use rustc_errors::registry::Registry; use rustc_errors::{ error_code, fallback_fluent_bundle, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, - ErrorGuaranteed, FluentBundle, IntoDiagnostic, LazyFallbackBundle, MultiSpan, Noted, + ErrorGuaranteed, FluentBundle, Handler, IntoDiagnostic, LazyFallbackBundle, MultiSpan, Noted, TerminalUrl, }; use rustc_macros::HashStable_Generic; @@ -128,14 +130,12 @@ pub struct Limits { pub move_size_limit: Limit, /// The maximum length of types during monomorphization. pub type_length_limit: Limit, - /// The maximum blocks a const expression can evaluate. - pub const_eval_limit: Limit, } pub struct CompilerIO { pub input: Input, pub output_dir: Option, - pub output_file: Option, + pub output_file: Option, pub temps_dir: Option, } @@ -152,16 +152,6 @@ pub struct Session { /// Input, input file path and output file path to this compilation process. pub io: CompilerIO, - crate_types: OnceCell>, - /// The `stable_crate_id` is constructed out of the crate name and all the - /// `-C metadata` arguments passed to the compiler. Its value forms a unique - /// global identifier for the crate. It is used to allow multiple crates - /// with the same name to coexist. See the - /// `rustc_symbol_mangling` crate for more information. - pub stable_crate_id: OnceCell, - - features: OnceCell, - incr_comp_session: OneThread>, /// Used for incremental compilation tests. Will only be populated if /// `-Zquery-dep-graph` is specified. @@ -234,6 +224,27 @@ pub enum MetadataKind { Compressed, } +#[derive(Clone, Copy)] +pub enum CodegenUnits { + /// Specified by the user. In this case we try fairly hard to produce the + /// number of CGUs requested. + User(usize), + + /// A default value, i.e. not specified by the user. In this case we take + /// more liberties about CGU formation, e.g. avoid producing very small + /// CGUs. + Default(usize), +} + +impl CodegenUnits { + pub fn as_usize(self) -> usize { + match self { + CodegenUnits::User(n) => n, + CodegenUnits::Default(n) => n, + } + } +} + impl Session { pub fn miri_unleashed_feature(&self, span: Span, feature_gate: Option) { self.miri_unleashed_features.lock().push((span, feature_gate)); @@ -289,55 +300,11 @@ impl Session { self.parse_sess.span_diagnostic.emit_future_breakage_report(diags); } - pub fn local_stable_crate_id(&self) -> StableCrateId { - self.stable_crate_id.get().copied().unwrap() - } - - pub fn crate_types(&self) -> &[CrateType] { - self.crate_types.get().unwrap().as_slice() - } - /// Returns true if the crate is a testing one. pub fn is_test_crate(&self) -> bool { self.opts.test } - pub fn needs_crate_hash(&self) -> bool { - // Why is the crate hash needed for these configurations? - // - debug_assertions: for the "fingerprint the result" check in - // `rustc_query_system::query::plumbing::execute_job`. - // - incremental: for query lookups. - // - needs_metadata: for putting into crate metadata. - // - instrument_coverage: for putting into coverage data (see - // `hash_mir_source`). - cfg!(debug_assertions) - || self.opts.incremental.is_some() - || self.needs_metadata() - || self.instrument_coverage() - } - - pub fn metadata_kind(&self) -> MetadataKind { - self.crate_types() - .iter() - .map(|ty| match *ty { - CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib => { - MetadataKind::None - } - CrateType::Rlib => MetadataKind::Uncompressed, - CrateType::Dylib | CrateType::ProcMacro => MetadataKind::Compressed, - }) - .max() - .unwrap_or(MetadataKind::None) - } - - pub fn needs_metadata(&self) -> bool { - self.metadata_kind() != MetadataKind::None - } - - pub fn init_crate_types(&self, crate_types: Vec) { - self.crate_types.set(crate_types).expect("`crate_types` was initialized twice") - } - #[rustc_lint_diagnostics] #[track_caller] pub fn struct_span_warn>( @@ -656,7 +623,7 @@ impl Session { pub fn delay_span_bug>( &self, sp: S, - msg: impl Into, + msg: impl Into, ) -> ErrorGuaranteed { self.diagnostic().delay_span_bug(sp, msg) } @@ -736,21 +703,6 @@ impl Session { self.opts.cg.instrument_coverage() == InstrumentCoverage::ExceptUnusedFunctions } - /// Gets the features enabled for the current compilation session. - /// DO NOT USE THIS METHOD if there is a TyCtxt available, as it circumvents - /// dependency tracking. Use tcx.features() instead. - #[inline] - pub fn features_untracked(&self) -> &rustc_feature::Features { - self.features.get().unwrap() - } - - pub fn init_features(&self, features: rustc_feature::Features) { - match self.features.set(features) { - Ok(()) => {} - Err(_) => panic!("`features` was initialized twice"), - } - } - pub fn is_sanitizer_cfi_enabled(&self) -> bool { self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI) } @@ -974,25 +926,25 @@ impl Session { } /// Are we allowed to use features from the Rust 2018 edition? - pub fn rust_2018(&self) -> bool { - self.edition().rust_2018() + pub fn at_least_rust_2018(&self) -> bool { + self.edition().at_least_rust_2018() } /// Are we allowed to use features from the Rust 2021 edition? - pub fn rust_2021(&self) -> bool { - self.edition().rust_2021() + pub fn at_least_rust_2021(&self) -> bool { + self.edition().at_least_rust_2021() } /// Are we allowed to use features from the Rust 2024 edition? - pub fn rust_2024(&self) -> bool { - self.edition().rust_2024() + pub fn at_least_rust_2024(&self) -> bool { + self.edition().at_least_rust_2024() } - /// Returns `true` if we cannot skip the PLT for shared library calls. + /// Returns `true` if we should use the PLT for shared library calls. pub fn needs_plt(&self) -> bool { - // Check if the current target usually needs PLT to be enabled. + // Check if the current target usually wants PLT to be enabled. // The user can use the command line flag to override it. - let needs_plt = self.target.needs_plt; + let want_plt = self.target.plt_by_default; let dbg_opts = &self.opts.unstable_opts; @@ -1004,8 +956,8 @@ impl Session { let full_relro = RelroLevel::Full == relro_level; // If user didn't explicitly forced us to use / skip the PLT, - // then try to skip it where possible. - dbg_opts.plt.unwrap_or(needs_plt || !full_relro) + // then use it unless the target doesn't want it by default or the full relro forces it on. + dbg_opts.plt.unwrap_or(want_plt || !full_relro) } /// Checks if LLVM lifetime markers should be emitted. @@ -1036,6 +988,10 @@ impl Session { self.opts.unstable_opts.verbose } + pub fn print_llvm_stats(&self) -> bool { + self.opts.unstable_opts.print_codegen_stats + } + pub fn verify_llvm_ir(&self) -> bool { self.opts.unstable_opts.verify_llvm_ir || option_env!("RUSTC_VERIFY_LLVM_IR").is_some() } @@ -1104,7 +1060,7 @@ impl Session { // If there's only one codegen unit and LTO isn't enabled then there's // no need for ThinLTO so just return false. - if self.codegen_units() == 1 { + if self.codegen_units().as_usize() == 1 { return config::Lto::No; } @@ -1206,19 +1162,19 @@ impl Session { /// Returns the number of codegen units that should be used for this /// compilation - pub fn codegen_units(&self) -> usize { + pub fn codegen_units(&self) -> CodegenUnits { if let Some(n) = self.opts.cli_forced_codegen_units { - return n; + return CodegenUnits::User(n); } if let Some(n) = self.target.default_codegen_units { - return n as usize; + return CodegenUnits::Default(n as usize); } // If incremental compilation is turned on, we default to a high number // codegen units in order to reduce the "collateral damage" small // changes cause. if self.opts.incremental.is_some() { - return 256; + return CodegenUnits::Default(256); } // Why is 16 codegen units the default all the time? @@ -1271,7 +1227,7 @@ impl Session { // As a result 16 was chosen here! Mostly because it was a power of 2 // and most benchmarks agreed it was roughly a local optimum. Not very // scientific. - 16 + CodegenUnits::Default(16) } pub fn teach(&self, code: &DiagnosticId) -> bool { @@ -1325,18 +1281,15 @@ fn default_emitter( ); Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) } else { - let emitter = EmitterWriter::stderr( - color_config, - Some(source_map), - bundle, - fallback_bundle, - short, - sopts.unstable_opts.teach, - sopts.diagnostic_width, - macro_backtrace, - track_diagnostics, - terminal_url, - ); + let emitter = EmitterWriter::stderr(color_config, fallback_bundle) + .fluent_bundle(bundle) + .sm(Some(source_map)) + .short_message(short) + .teach(sopts.unstable_opts.teach) + .diagnostic_width(sopts.diagnostic_width) + .macro_backtrace(macro_backtrace) + .track_diagnostics(track_diagnostics) + .terminal_url(terminal_url); Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) } } @@ -1361,6 +1314,7 @@ fn default_emitter( // JUSTIFICATION: literally session construction #[allow(rustc::bad_opt_access)] pub fn build_session( + handler: &EarlyErrorHandler, sopts: config::Options, io: CompilerIO, bundle: Option>, @@ -1370,6 +1324,7 @@ pub fn build_session( file_loader: Option>, target_override: Option, cfg_version: &'static str, + ice_file: Option, ) -> Session { // FIXME: This is not general enough to make the warning lint completely override // normal diagnostic warnings, since the warning lint can also be denied and changed @@ -1387,19 +1342,18 @@ pub fn build_session( None => filesearch::get_or_default_sysroot().expect("Failed finding sysroot"), }; - let target_cfg = config::build_target_config(&sopts, target_override, &sysroot); + let target_cfg = config::build_target_config(handler, &sopts, target_override, &sysroot); let host_triple = TargetTriple::from_triple(config::host_triple()); - let (host, target_warnings) = Target::search(&host_triple, &sysroot).unwrap_or_else(|e| { - early_error(sopts.error_format, format!("Error loading host specification: {e}")) - }); + let (host, target_warnings) = Target::search(&host_triple, &sysroot) + .unwrap_or_else(|e| handler.early_error(format!("Error loading host specification: {e}"))); for warning in target_warnings.warning_messages() { - early_warn(sopts.error_format, warning) + handler.early_warn(warning) } let loader = file_loader.unwrap_or_else(|| Box::new(RealFileLoader)); let hash_kind = sopts.unstable_opts.src_hash_algorithm.unwrap_or_else(|| { if target_cfg.is_like_msvc { - SourceFileHashAlgorithm::Sha1 + SourceFileHashAlgorithm::Sha256 } else { SourceFileHashAlgorithm::Md5 } @@ -1416,10 +1370,11 @@ pub fn build_session( ); let emitter = default_emitter(&sopts, registry, source_map.clone(), bundle, fallback_bundle); - let span_diagnostic = rustc_errors::Handler::with_emitter_and_flags( - emitter, - sopts.unstable_opts.diagnostic_handler_flags(can_emit_warnings), - ); + let mut span_diagnostic = rustc_errors::Handler::with_emitter(emitter) + .with_flags(sopts.unstable_opts.diagnostic_handler_flags(can_emit_warnings)); + if let Some(ice_file) = ice_file { + span_diagnostic = span_diagnostic.with_ice_file(ice_file); + } let self_profiler = if let SwitchWithOptPath::Enabled(ref d) = sopts.unstable_opts.self_profile { @@ -1435,7 +1390,7 @@ pub fn build_session( match profiler { Ok(profiler) => Some(Arc::new(profiler)), Err(e) => { - early_warn(sopts.error_format, format!("failed to create profiler: {e}")); + handler.early_warn(format!("failed to create profiler: {e}")); None } } @@ -1492,9 +1447,6 @@ pub fn build_session( parse_sess, sysroot, io, - crate_types: OnceCell::new(), - stable_crate_id: OnceCell::new(), - features: OnceCell::new(), incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)), cgu_reuse_tracker, prof, @@ -1595,13 +1547,19 @@ fn validate_commandline_args_with_session_available(sess: &Session) { // LLVM CFI requires LTO. if sess.is_sanitizer_cfi_enabled() - && !(sess.lto() == config::Lto::Fat - || sess.lto() == config::Lto::Thin - || sess.opts.cg.linker_plugin_lto.enabled()) + && !(sess.lto() == config::Lto::Fat || sess.opts.cg.linker_plugin_lto.enabled()) { sess.emit_err(errors::SanitizerCfiRequiresLto); } + // LLVM CFI using rustc LTO requires a single codegen unit. + if sess.is_sanitizer_cfi_enabled() + && sess.lto() == config::Lto::Fat + && !(sess.codegen_units().as_usize() == 1) + { + sess.emit_err(errors::SanitizerCfiRequiresSingleCodegenUnit); + } + // LLVM CFI is incompatible with LLVM KCFI. if sess.is_sanitizer_cfi_enabled() && sess.is_sanitizer_kcfi_enabled() { sess.emit_err(errors::CannotMixAndMatchSanitizers { @@ -1675,6 +1633,13 @@ fn validate_commandline_args_with_session_available(sess: &Session) { if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray { sess.emit_err(errors::InstrumentationNotSupported { us: "XRay".to_string() }); } + + if let Some(flavor) = sess.opts.cg.linker_flavor { + if let Some(compatible_list) = sess.target.linker_flavor.check_compatibility(flavor) { + let flavor = flavor.desc(); + sess.emit_err(errors::IncompatibleLinkerFlavor { flavor, compatible_list }); + } + } } /// Holds data on the current incremental compilation session, if there is one. @@ -1695,7 +1660,64 @@ pub enum IncrCompSession { InvalidBecauseOfErrors { session_directory: PathBuf }, } -fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler { +/// A wrapper around an [`Handler`] that is used for early error emissions. +pub struct EarlyErrorHandler { + handler: Handler, +} + +impl EarlyErrorHandler { + pub fn new(output: ErrorOutputType) -> Self { + let emitter = mk_emitter(output); + Self { handler: rustc_errors::Handler::with_emitter(emitter) } + } + + pub fn abort_if_errors(&self) { + self.handler.abort_if_errors() + } + + /// Swap out the underlying handler once we acquire the user's preference on error emission + /// format. Any errors prior to that will cause an abort and all stashed diagnostics of the + /// previous handler will be emitted. + pub fn abort_if_error_and_set_error_format(&mut self, output: ErrorOutputType) { + self.handler.abort_if_errors(); + + let emitter = mk_emitter(output); + self.handler = Handler::with_emitter(emitter); + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_note(&self, msg: impl Into) { + self.handler.struct_note_without_error(msg).emit() + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_help(&self, msg: impl Into) { + self.handler.struct_help(msg).emit() + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + #[must_use = "ErrorGuaranteed must be returned from `run_compiler` in order to exit with a non-zero status code"] + pub fn early_error_no_abort(&self, msg: impl Into) -> ErrorGuaranteed { + self.handler.struct_err(msg).emit() + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_error(&self, msg: impl Into) -> ! { + self.handler.struct_fatal(msg).emit() + } + + #[allow(rustc::untranslatable_diagnostic)] + #[allow(rustc::diagnostic_outside_of_impl)] + pub fn early_warn(&self, msg: impl Into) { + self.handler.struct_warn(msg).emit() + } +} + +fn mk_emitter(output: ErrorOutputType) -> Box { // FIXME(#100717): early errors aren't translated at the moment, so this is fine, but it will // need to reference every crate that might emit an early error for translation to work. let fallback_bundle = @@ -1703,18 +1725,7 @@ fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler let emitter: Box = match output { config::ErrorOutputType::HumanReadable(kind) => { let (short, color_config) = kind.unzip(); - Box::new(EmitterWriter::stderr( - color_config, - None, - None, - fallback_bundle, - short, - false, - None, - false, - false, - TerminalUrl::No, - )) + Box::new(EmitterWriter::stderr(color_config, fallback_bundle).short_message(short)) } config::ErrorOutputType::Json { pretty, json_rendered } => Box::new(JsonEmitter::basic( pretty, @@ -1727,27 +1738,5 @@ fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler TerminalUrl::No, )), }; - rustc_errors::Handler::with_emitter(true, None, emitter) -} - -#[allow(rustc::untranslatable_diagnostic)] -#[allow(rustc::diagnostic_outside_of_impl)] -#[must_use = "ErrorGuaranteed must be returned from `run_compiler` in order to exit with a non-zero status code"] -pub fn early_error_no_abort( - output: config::ErrorOutputType, - msg: impl Into, -) -> ErrorGuaranteed { - early_error_handler(output).struct_err(msg).emit() -} - -#[allow(rustc::untranslatable_diagnostic)] -#[allow(rustc::diagnostic_outside_of_impl)] -pub fn early_error(output: config::ErrorOutputType, msg: impl Into) -> ! { - early_error_handler(output).struct_fatal(msg).emit() -} - -#[allow(rustc::untranslatable_diagnostic)] -#[allow(rustc::diagnostic_outside_of_impl)] -pub fn early_warn(output: config::ErrorOutputType, msg: impl Into) { - early_error_handler(output).struct_warn(msg).emit() + emitter } diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs index 1d15e2c28d837..71f2591fe6654 100644 --- a/compiler/rustc_session/src/utils.rs +++ b/compiler/rustc_session/src/utils.rs @@ -7,6 +7,7 @@ impl Session { pub fn timer(&self, what: &'static str) -> VerboseTimingGuard<'_> { self.prof.verbose_generic_activity(what) } + /// Used by `-Z self-profile`. pub fn time(&self, what: &'static str, f: impl FnOnce() -> R) -> R { self.prof.verbose_generic_activity(what).run(f) } diff --git a/compiler/rustc_smir/Cargo.toml b/compiler/rustc_smir/Cargo.toml index 80360a3c73f8d..80d4e7ed02fe2 100644 --- a/compiler/rustc_smir/Cargo.toml +++ b/compiler/rustc_smir/Cargo.toml @@ -4,13 +4,18 @@ version = "0.0.0" edition = "2021" [dependencies] -rustc_hir = { path = "../rustc_hir" } +# Use optional dependencies for rustc_* in order to support building this crate separately. +rustc_hir = { path = "../rustc_hir", optional = true } rustc_middle = { path = "../rustc_middle", optional = true } rustc_span = { path = "../rustc_span", optional = true } +rustc_target = { path = "../rustc_target", optional = true } tracing = "0.1" +scoped-tls = "1.0" [features] default = [ + "rustc_hir", "rustc_middle", "rustc_span", + "rustc_target", ] diff --git a/compiler/rustc_smir/rust-toolchain.toml b/compiler/rustc_smir/rust-toolchain.toml index 157dfd620ee1b..d75e8e33b1c56 100644 --- a/compiler/rustc_smir/rust-toolchain.toml +++ b/compiler/rustc_smir/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-02-28" +channel = "nightly-2023-06-14" components = [ "rustfmt", "rustc-dev" ] diff --git a/compiler/rustc_smir/src/lib.rs b/compiler/rustc_smir/src/lib.rs index b00f0a1c15312..8cb533c8d6726 100644 --- a/compiler/rustc_smir/src/lib.rs +++ b/compiler/rustc_smir/src/lib.rs @@ -1,6 +1,6 @@ //! The WIP stable interface to rustc internals. //! -//! For more information see https://github.com/rust-lang/project-stable-mir +//! For more information see //! //! # Note //! @@ -11,11 +11,25 @@ test(attr(allow(unused_variables), deny(warnings))) )] #![cfg_attr(not(feature = "default"), feature(rustc_private))] -#![feature(local_key_cell_methods)] #![feature(ptr_metadata)] +#![feature(type_alias_impl_trait)] // Used to define opaque types. +#![feature(intra_doc_pointers)] + +// Declare extern rustc_* crates to enable building this crate separately from the compiler. +#[cfg(not(feature = "default"))] +extern crate rustc_hir; +#[cfg(not(feature = "default"))] +extern crate rustc_middle; +#[cfg(not(feature = "default"))] +extern crate rustc_span; +#[cfg(not(feature = "default"))] +extern crate rustc_target; pub mod rustc_internal; pub mod stable_mir; // Make this module private for now since external users should not call these directly. mod rustc_smir; + +#[macro_use] +extern crate scoped_tls; diff --git a/compiler/rustc_smir/src/rustc_internal/mod.rs b/compiler/rustc_smir/src/rustc_internal/mod.rs index 609a04d263c96..078ff67446f6e 100644 --- a/compiler/rustc_smir/src/rustc_internal/mod.rs +++ b/compiler/rustc_smir/src/rustc_internal/mod.rs @@ -3,6 +3,9 @@ //! For that, we define APIs that will temporarily be public to 3P that exposes rustc internal APIs //! until stable MIR is complete. +use std::fmt::Debug; +use std::string::ToString; + use crate::{ rustc_smir::Tables, stable_mir::{self, with}, @@ -24,21 +27,117 @@ pub fn crate_item(did: DefId) -> stable_mir::CrateItem { with_tables(|t| t.crate_item(did)) } +pub fn adt_def(did: DefId) -> stable_mir::ty::AdtDef { + with_tables(|t| t.adt_def(did)) +} + +pub fn foreign_def(did: DefId) -> stable_mir::ty::ForeignDef { + with_tables(|t| t.foreign_def(did)) +} + +pub fn fn_def(did: DefId) -> stable_mir::ty::FnDef { + with_tables(|t| t.fn_def(did)) +} + +pub fn closure_def(did: DefId) -> stable_mir::ty::ClosureDef { + with_tables(|t| t.closure_def(did)) +} + +pub fn generator_def(did: DefId) -> stable_mir::ty::GeneratorDef { + with_tables(|t| t.generator_def(did)) +} + +pub fn alias_def(did: DefId) -> stable_mir::ty::AliasDef { + with_tables(|t| t.alias_def(did)) +} + +pub fn param_def(did: DefId) -> stable_mir::ty::ParamDef { + with_tables(|t| t.param_def(did)) +} + +pub fn br_named_def(did: DefId) -> stable_mir::ty::BrNamedDef { + with_tables(|t| t.br_named_def(did)) +} + +pub fn trait_def(did: DefId) -> stable_mir::ty::TraitDef { + with_tables(|t| t.trait_def(did)) +} + +pub fn impl_def(did: DefId) -> stable_mir::ty::ImplDef { + with_tables(|t| t.impl_def(did)) +} + impl<'tcx> Tables<'tcx> { pub fn item_def_id(&self, item: &stable_mir::CrateItem) -> DefId { self.def_ids[item.0] } + pub fn trait_def_id(&self, trait_def: &stable_mir::ty::TraitDef) -> DefId { + self.def_ids[trait_def.0] + } + + pub fn impl_trait_def_id(&self, impl_def: &stable_mir::ty::ImplDef) -> DefId { + self.def_ids[impl_def.0] + } + pub fn crate_item(&mut self, did: DefId) -> stable_mir::CrateItem { + stable_mir::CrateItem(self.create_def_id(did)) + } + + pub fn adt_def(&mut self, did: DefId) -> stable_mir::ty::AdtDef { + stable_mir::ty::AdtDef(self.create_def_id(did)) + } + + pub fn foreign_def(&mut self, did: DefId) -> stable_mir::ty::ForeignDef { + stable_mir::ty::ForeignDef(self.create_def_id(did)) + } + + pub fn fn_def(&mut self, did: DefId) -> stable_mir::ty::FnDef { + stable_mir::ty::FnDef(self.create_def_id(did)) + } + + pub fn closure_def(&mut self, did: DefId) -> stable_mir::ty::ClosureDef { + stable_mir::ty::ClosureDef(self.create_def_id(did)) + } + + pub fn generator_def(&mut self, did: DefId) -> stable_mir::ty::GeneratorDef { + stable_mir::ty::GeneratorDef(self.create_def_id(did)) + } + + pub fn alias_def(&mut self, did: DefId) -> stable_mir::ty::AliasDef { + stable_mir::ty::AliasDef(self.create_def_id(did)) + } + + pub fn param_def(&mut self, did: DefId) -> stable_mir::ty::ParamDef { + stable_mir::ty::ParamDef(self.create_def_id(did)) + } + + pub fn br_named_def(&mut self, did: DefId) -> stable_mir::ty::BrNamedDef { + stable_mir::ty::BrNamedDef(self.create_def_id(did)) + } + + pub fn trait_def(&mut self, did: DefId) -> stable_mir::ty::TraitDef { + stable_mir::ty::TraitDef(self.create_def_id(did)) + } + + pub fn const_def(&mut self, did: DefId) -> stable_mir::ty::ConstDef { + stable_mir::ty::ConstDef(self.create_def_id(did)) + } + + pub fn impl_def(&mut self, did: DefId) -> stable_mir::ty::ImplDef { + stable_mir::ty::ImplDef(self.create_def_id(did)) + } + + fn create_def_id(&mut self, did: DefId) -> stable_mir::DefId { // FIXME: this becomes inefficient when we have too many ids for (i, &d) in self.def_ids.iter().enumerate() { if d == did { - return stable_mir::CrateItem(i); + return i; } } let id = self.def_ids.len(); self.def_ids.push(did); - stable_mir::CrateItem(id) + id } } @@ -49,3 +148,10 @@ pub fn crate_num(item: &stable_mir::Crate) -> CrateNum { pub fn run(tcx: TyCtxt<'_>, f: impl FnOnce()) { crate::stable_mir::run(Tables { tcx, def_ids: vec![], types: vec![] }, f); } + +/// A type that provides internal information but that can still be used for debug purpose. +pub type Opaque = impl Debug + ToString + Clone; + +pub(crate) fn opaque(value: &T) -> Opaque { + format!("{value:?}") +} diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index 6af43f5d3f358..06b37008ebed3 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -7,9 +7,19 @@ //! //! For now, we are developing everything inside `rustc`, thus, we keep this module private. -use crate::stable_mir::{self, ty::TyKind, Context}; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use crate::rustc_internal::{self, opaque}; +use crate::stable_mir::mir::{CopyNonOverlapping, UserTypeProjection, VariantIdx}; +use crate::stable_mir::ty::{ + allocation_filter, new_allocation, Const, FloatTy, IntTy, Movability, RigidTy, TyKind, UintTy, +}; +use crate::stable_mir::{self, Context}; +use rustc_hir as hir; +use rustc_middle::mir::coverage::CodeRegion; +use rustc_middle::mir::interpret::alloc_range; +use rustc_middle::mir::{self, ConstantKind}; +use rustc_middle::ty::{self, Ty, TyCtxt, Variance}; use rustc_span::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_target::abi::FieldIdx; use tracing::debug; impl<'tcx> Context for Tables<'tcx> { @@ -34,6 +44,35 @@ impl<'tcx> Context for Tables<'tcx> { fn entry_fn(&mut self) -> Option { Some(self.crate_item(self.tcx.entry_fn(())?.0)) } + + fn all_trait_decls(&mut self) -> stable_mir::TraitDecls { + self.tcx + .traits(LOCAL_CRATE) + .iter() + .map(|trait_def_id| self.trait_def(*trait_def_id)) + .collect() + } + + fn trait_decl(&mut self, trait_def: &stable_mir::ty::TraitDef) -> stable_mir::ty::TraitDecl { + let def_id = self.trait_def_id(trait_def); + let trait_def = self.tcx.trait_def(def_id); + trait_def.stable(self) + } + + fn all_trait_impls(&mut self) -> stable_mir::ImplTraitDecls { + self.tcx + .trait_impls_in_crate(LOCAL_CRATE) + .iter() + .map(|impl_def_id| self.impl_def(*impl_def_id)) + .collect() + } + + fn trait_impl(&mut self, impl_def: &stable_mir::ty::ImplDef) -> stable_mir::ty::ImplTrait { + let def_id = self.impl_trait_def_id(impl_def); + let impl_trait = self.tcx.impl_trait_ref(def_id).unwrap(); + impl_trait.stable(self) + } + fn mir_body(&mut self, item: &stable_mir::CrateItem) -> stable_mir::mir::Body { let def_id = self.item_def_id(item); let mir = self.tcx.optimized_mir(def_id); @@ -42,8 +81,12 @@ impl<'tcx> Context for Tables<'tcx> { .basic_blocks .iter() .map(|block| stable_mir::mir::BasicBlock { - terminator: rustc_terminator_to_terminator(block.terminator()), - statements: block.statements.iter().map(rustc_statement_to_statement).collect(), + terminator: block.terminator().stable(self), + statements: block + .statements + .iter() + .map(|statement| statement.stable(self)) + .collect(), }) .collect(), locals: mir.local_decls.iter().map(|decl| self.intern_ty(decl.ty)).collect(), @@ -55,7 +98,8 @@ impl<'tcx> Context for Tables<'tcx> { } fn ty_kind(&mut self, ty: crate::stable_mir::ty::Ty) -> TyKind { - self.rustc_ty_to_ty(self.types[ty.0]) + let ty = self.types[ty.0]; + ty.stable(self) } } @@ -66,40 +110,6 @@ pub struct Tables<'tcx> { } impl<'tcx> Tables<'tcx> { - fn rustc_ty_to_ty(&mut self, ty: Ty<'tcx>) -> TyKind { - match ty.kind() { - ty::Bool => TyKind::Bool, - ty::Char => todo!(), - ty::Int(_) => todo!(), - ty::Uint(_) => todo!(), - ty::Float(_) => todo!(), - ty::Adt(_, _) => todo!(), - ty::Foreign(_) => todo!(), - ty::Str => todo!(), - ty::Array(_, _) => todo!(), - ty::Slice(_) => todo!(), - ty::RawPtr(_) => todo!(), - ty::Ref(_, _, _) => todo!(), - ty::FnDef(_, _) => todo!(), - ty::FnPtr(_) => todo!(), - ty::Placeholder(..) => todo!(), - ty::Dynamic(_, _, _) => todo!(), - ty::Closure(_, _) => todo!(), - ty::Generator(_, _, _) => todo!(), - ty::GeneratorWitness(_) => todo!(), - ty::GeneratorWitnessMIR(_, _) => todo!(), - ty::Never => todo!(), - ty::Tuple(fields) => { - TyKind::Tuple(fields.iter().map(|ty| self.intern_ty(ty)).collect()) - } - ty::Alias(_, _) => todo!(), - ty::Param(_) => todo!(), - ty::Bound(_, _) => todo!(), - ty::Infer(_) => todo!(), - ty::Error(_) => todo!(), - } - } - fn intern_ty(&mut self, ty: Ty<'tcx>) -> stable_mir::ty::Ty { if let Some(id) = self.types.iter().position(|&t| t == ty) { return stable_mir::ty::Ty(id); @@ -118,222 +128,1080 @@ fn smir_crate(tcx: TyCtxt<'_>, crate_num: CrateNum) -> stable_mir::Crate { stable_mir::Crate { id: crate_num.into(), name: crate_name, is_local } } -fn rustc_statement_to_statement( - s: &rustc_middle::mir::Statement<'_>, -) -> stable_mir::mir::Statement { - use rustc_middle::mir::StatementKind::*; - match &s.kind { - Assign(assign) => stable_mir::mir::Statement::Assign( - rustc_place_to_place(&assign.0), - rustc_rvalue_to_rvalue(&assign.1), - ), - FakeRead(_) => todo!(), - SetDiscriminant { .. } => todo!(), - Deinit(_) => todo!(), - StorageLive(_) => todo!(), - StorageDead(_) => todo!(), - Retag(_, _) => todo!(), - PlaceMention(_) => todo!(), - AscribeUserType(_, _) => todo!(), - Coverage(_) => todo!(), - Intrinsic(_) => todo!(), - ConstEvalCounter => todo!(), - Nop => stable_mir::mir::Statement::Nop, - } -} - -fn rustc_rvalue_to_rvalue(rvalue: &rustc_middle::mir::Rvalue<'_>) -> stable_mir::mir::Rvalue { - use rustc_middle::mir::Rvalue::*; - match rvalue { - Use(op) => stable_mir::mir::Rvalue::Use(rustc_op_to_op(op)), - Repeat(_, _) => todo!(), - Ref(_, _, _) => todo!(), - ThreadLocalRef(_) => todo!(), - AddressOf(_, _) => todo!(), - Len(_) => todo!(), - Cast(_, _, _) => todo!(), - BinaryOp(_, _) => todo!(), - CheckedBinaryOp(bin_op, ops) => stable_mir::mir::Rvalue::CheckedBinaryOp( - rustc_bin_op_to_bin_op(bin_op), - rustc_op_to_op(&ops.0), - rustc_op_to_op(&ops.1), - ), - NullaryOp(_, _) => todo!(), - UnaryOp(un_op, op) => { - stable_mir::mir::Rvalue::UnaryOp(rustc_un_op_to_un_op(un_op), rustc_op_to_op(op)) - } - Discriminant(_) => todo!(), - Aggregate(_, _) => todo!(), - ShallowInitBox(_, _) => todo!(), - CopyForDeref(_) => todo!(), - } -} - -fn rustc_op_to_op(op: &rustc_middle::mir::Operand<'_>) -> stable_mir::mir::Operand { - use rustc_middle::mir::Operand::*; - match op { - Copy(place) => stable_mir::mir::Operand::Copy(rustc_place_to_place(place)), - Move(place) => stable_mir::mir::Operand::Move(rustc_place_to_place(place)), - Constant(c) => stable_mir::mir::Operand::Constant(c.to_string()), - } -} - -fn rustc_place_to_place(place: &rustc_middle::mir::Place<'_>) -> stable_mir::mir::Place { - stable_mir::mir::Place { - local: place.local.as_usize(), - projection: format!("{:?}", place.projection), - } -} - -fn rustc_unwind_to_unwind( - unwind: &rustc_middle::mir::UnwindAction, -) -> stable_mir::mir::UnwindAction { - use rustc_middle::mir::UnwindAction; - match unwind { - UnwindAction::Continue => stable_mir::mir::UnwindAction::Continue, - UnwindAction::Unreachable => stable_mir::mir::UnwindAction::Unreachable, - UnwindAction::Terminate => stable_mir::mir::UnwindAction::Terminate, - UnwindAction::Cleanup(bb) => stable_mir::mir::UnwindAction::Cleanup(bb.as_usize()), - } -} - -fn rustc_assert_msg_to_msg<'tcx>( - assert_message: &rustc_middle::mir::AssertMessage<'tcx>, -) -> stable_mir::mir::AssertMessage { - use rustc_middle::mir::AssertKind; - match assert_message { - AssertKind::BoundsCheck { len, index } => stable_mir::mir::AssertMessage::BoundsCheck { - len: rustc_op_to_op(len), - index: rustc_op_to_op(index), - }, - AssertKind::Overflow(bin_op, op1, op2) => stable_mir::mir::AssertMessage::Overflow( - rustc_bin_op_to_bin_op(bin_op), - rustc_op_to_op(op1), - rustc_op_to_op(op2), - ), - AssertKind::OverflowNeg(op) => { - stable_mir::mir::AssertMessage::OverflowNeg(rustc_op_to_op(op)) - } - AssertKind::DivisionByZero(op) => { - stable_mir::mir::AssertMessage::DivisionByZero(rustc_op_to_op(op)) - } - AssertKind::RemainderByZero(op) => { - stable_mir::mir::AssertMessage::RemainderByZero(rustc_op_to_op(op)) - } - AssertKind::ResumedAfterReturn(generator) => { - stable_mir::mir::AssertMessage::ResumedAfterReturn(rustc_generator_to_generator( - generator, - )) - } - AssertKind::ResumedAfterPanic(generator) => { - stable_mir::mir::AssertMessage::ResumedAfterPanic(rustc_generator_to_generator( - generator, - )) - } - AssertKind::MisalignedPointerDereference { required, found } => { - stable_mir::mir::AssertMessage::MisalignedPointerDereference { - required: rustc_op_to_op(required), - found: rustc_op_to_op(found), - } - } - } -} - -fn rustc_bin_op_to_bin_op(bin_op: &rustc_middle::mir::BinOp) -> stable_mir::mir::BinOp { - use rustc_middle::mir::BinOp; - match bin_op { - BinOp::Add => stable_mir::mir::BinOp::Add, - BinOp::Sub => stable_mir::mir::BinOp::Sub, - BinOp::Mul => stable_mir::mir::BinOp::Mul, - BinOp::Div => stable_mir::mir::BinOp::Div, - BinOp::Rem => stable_mir::mir::BinOp::Rem, - BinOp::BitXor => stable_mir::mir::BinOp::BitXor, - BinOp::BitAnd => stable_mir::mir::BinOp::BitAnd, - BinOp::BitOr => stable_mir::mir::BinOp::BitOr, - BinOp::Shl => stable_mir::mir::BinOp::Shl, - BinOp::Shr => stable_mir::mir::BinOp::Shr, - BinOp::Eq => stable_mir::mir::BinOp::Eq, - BinOp::Lt => stable_mir::mir::BinOp::Lt, - BinOp::Le => stable_mir::mir::BinOp::Le, - BinOp::Ne => stable_mir::mir::BinOp::Ne, - BinOp::Ge => stable_mir::mir::BinOp::Ge, - BinOp::Gt => stable_mir::mir::BinOp::Gt, - BinOp::Offset => stable_mir::mir::BinOp::Offset, - } -} - -fn rustc_un_op_to_un_op(unary_op: &rustc_middle::mir::UnOp) -> stable_mir::mir::UnOp { - use rustc_middle::mir::UnOp; - match unary_op { - UnOp::Not => stable_mir::mir::UnOp::Not, - UnOp::Neg => stable_mir::mir::UnOp::Neg, - } -} - -fn rustc_generator_to_generator( - generator: &rustc_hir::GeneratorKind, -) -> stable_mir::mir::GeneratorKind { - use rustc_hir::{AsyncGeneratorKind, GeneratorKind}; - match generator { - GeneratorKind::Async(async_gen) => { - let async_gen = match async_gen { - AsyncGeneratorKind::Block => stable_mir::mir::AsyncGeneratorKind::Block, - AsyncGeneratorKind::Closure => stable_mir::mir::AsyncGeneratorKind::Closure, - AsyncGeneratorKind::Fn => stable_mir::mir::AsyncGeneratorKind::Fn, - }; - stable_mir::mir::GeneratorKind::Async(async_gen) - } - GeneratorKind::Gen => stable_mir::mir::GeneratorKind::Gen, - } -} - -fn rustc_terminator_to_terminator( - terminator: &rustc_middle::mir::Terminator<'_>, -) -> stable_mir::mir::Terminator { - use rustc_middle::mir::TerminatorKind::*; - use stable_mir::mir::Terminator; - match &terminator.kind { - Goto { target } => Terminator::Goto { target: target.as_usize() }, - SwitchInt { discr, targets } => Terminator::SwitchInt { - discr: rustc_op_to_op(discr), - targets: targets - .iter() - .map(|(value, target)| stable_mir::mir::SwitchTarget { - value, - target: target.as_usize(), +/// Trait used to convert between an internal MIR type to a Stable MIR type. +pub(crate) trait Stable<'tcx> { + /// The stable representation of the type implementing Stable. + type T; + /// Converts an object to the equivalent Stable MIR representation. + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T; +} + +impl<'tcx> Stable<'tcx> for mir::Statement<'tcx> { + type T = stable_mir::mir::Statement; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::StatementKind::*; + match &self.kind { + Assign(assign) => { + stable_mir::mir::Statement::Assign(assign.0.stable(tables), assign.1.stable(tables)) + } + FakeRead(fake_read_place) => stable_mir::mir::Statement::FakeRead( + fake_read_place.0.stable(tables), + fake_read_place.1.stable(tables), + ), + SetDiscriminant { place: plc, variant_index: idx } => { + stable_mir::mir::Statement::SetDiscriminant { + place: plc.as_ref().stable(tables), + variant_index: idx.stable(tables), + } + } + Deinit(place) => stable_mir::mir::Statement::Deinit(place.stable(tables)), + StorageLive(place) => stable_mir::mir::Statement::StorageLive(place.stable(tables)), + StorageDead(place) => stable_mir::mir::Statement::StorageDead(place.stable(tables)), + Retag(retag, place) => { + stable_mir::mir::Statement::Retag(retag.stable(tables), place.stable(tables)) + } + PlaceMention(place) => stable_mir::mir::Statement::PlaceMention(place.stable(tables)), + AscribeUserType(place_projection, variance) => { + stable_mir::mir::Statement::AscribeUserType { + place: place_projection.as_ref().0.stable(tables), + projections: place_projection.as_ref().1.stable(tables), + variance: variance.stable(tables), + } + } + Coverage(coverage) => stable_mir::mir::Statement::Coverage(stable_mir::mir::Coverage { + kind: coverage.kind.stable(tables), + code_region: coverage.code_region.as_ref().map(|reg| reg.stable(tables)), + }), + Intrinsic(intrinstic) => { + stable_mir::mir::Statement::Intrinsic(intrinstic.stable(tables)) + } + ConstEvalCounter => stable_mir::mir::Statement::ConstEvalCounter, + Nop => stable_mir::mir::Statement::Nop, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::Rvalue<'tcx> { + type T = stable_mir::mir::Rvalue; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::Rvalue::*; + match self { + Use(op) => stable_mir::mir::Rvalue::Use(op.stable(tables)), + Repeat(op, len) => { + let cnst = ConstantKind::from_const(*len, tables.tcx); + let len = Const { literal: cnst.stable(tables) }; + stable_mir::mir::Rvalue::Repeat(op.stable(tables), len) + } + Ref(region, kind, place) => stable_mir::mir::Rvalue::Ref( + opaque(region), + kind.stable(tables), + place.stable(tables), + ), + ThreadLocalRef(def_id) => { + stable_mir::mir::Rvalue::ThreadLocalRef(rustc_internal::crate_item(*def_id)) + } + AddressOf(mutability, place) => { + stable_mir::mir::Rvalue::AddressOf(mutability.stable(tables), place.stable(tables)) + } + Len(place) => stable_mir::mir::Rvalue::Len(place.stable(tables)), + Cast(cast_kind, op, ty) => stable_mir::mir::Rvalue::Cast( + cast_kind.stable(tables), + op.stable(tables), + tables.intern_ty(*ty), + ), + BinaryOp(bin_op, ops) => stable_mir::mir::Rvalue::BinaryOp( + bin_op.stable(tables), + ops.0.stable(tables), + ops.1.stable(tables), + ), + CheckedBinaryOp(bin_op, ops) => stable_mir::mir::Rvalue::CheckedBinaryOp( + bin_op.stable(tables), + ops.0.stable(tables), + ops.1.stable(tables), + ), + NullaryOp(null_op, ty) => { + stable_mir::mir::Rvalue::NullaryOp(null_op.stable(tables), tables.intern_ty(*ty)) + } + UnaryOp(un_op, op) => { + stable_mir::mir::Rvalue::UnaryOp(un_op.stable(tables), op.stable(tables)) + } + Discriminant(place) => stable_mir::mir::Rvalue::Discriminant(place.stable(tables)), + Aggregate(agg_kind, operands) => { + let operands = operands.iter().map(|op| op.stable(tables)).collect(); + stable_mir::mir::Rvalue::Aggregate(agg_kind.stable(tables), operands) + } + ShallowInitBox(op, ty) => { + stable_mir::mir::Rvalue::ShallowInitBox(op.stable(tables), tables.intern_ty(*ty)) + } + CopyForDeref(place) => stable_mir::mir::Rvalue::CopyForDeref(place.stable(tables)), + } + } +} + +impl<'tcx> Stable<'tcx> for mir::Mutability { + type T = stable_mir::mir::Mutability; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use mir::Mutability::*; + match *self { + Not => stable_mir::mir::Mutability::Not, + Mut => stable_mir::mir::Mutability::Mut, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::BorrowKind { + type T = stable_mir::mir::BorrowKind; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::BorrowKind::*; + match *self { + Shared => stable_mir::mir::BorrowKind::Shared, + Shallow => stable_mir::mir::BorrowKind::Shallow, + Mut { kind } => stable_mir::mir::BorrowKind::Mut { kind: kind.stable(tables) }, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::MutBorrowKind { + type T = stable_mir::mir::MutBorrowKind; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use mir::MutBorrowKind::*; + match *self { + Default => stable_mir::mir::MutBorrowKind::Default, + TwoPhaseBorrow => stable_mir::mir::MutBorrowKind::TwoPhaseBorrow, + ClosureCapture => stable_mir::mir::MutBorrowKind::ClosureCapture, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> { + type T = stable_mir::mir::NullOp; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::NullOp::*; + match self { + SizeOf => stable_mir::mir::NullOp::SizeOf, + AlignOf => stable_mir::mir::NullOp::AlignOf, + OffsetOf(indices) => stable_mir::mir::NullOp::OffsetOf( + indices.iter().map(|idx| idx.stable(tables)).collect(), + ), + } + } +} + +impl<'tcx> Stable<'tcx> for mir::CastKind { + type T = stable_mir::mir::CastKind; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::CastKind::*; + match self { + PointerExposeAddress => stable_mir::mir::CastKind::PointerExposeAddress, + PointerFromExposedAddress => stable_mir::mir::CastKind::PointerFromExposedAddress, + PointerCoercion(c) => stable_mir::mir::CastKind::PointerCoercion(c.stable(tables)), + DynStar => stable_mir::mir::CastKind::DynStar, + IntToInt => stable_mir::mir::CastKind::IntToInt, + FloatToInt => stable_mir::mir::CastKind::FloatToInt, + FloatToFloat => stable_mir::mir::CastKind::FloatToFloat, + IntToFloat => stable_mir::mir::CastKind::IntToFloat, + PtrToPtr => stable_mir::mir::CastKind::PtrToPtr, + FnPtrToPtr => stable_mir::mir::CastKind::FnPtrToPtr, + Transmute => stable_mir::mir::CastKind::Transmute, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::AliasKind { + type T = stable_mir::ty::AliasKind; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use ty::AliasKind::*; + match self { + Projection => stable_mir::ty::AliasKind::Projection, + Inherent => stable_mir::ty::AliasKind::Inherent, + Opaque => stable_mir::ty::AliasKind::Opaque, + Weak => stable_mir::ty::AliasKind::Weak, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::AliasTy<'tcx> { + type T = stable_mir::ty::AliasTy; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + let ty::AliasTy { args, def_id, .. } = self; + stable_mir::ty::AliasTy { def_id: tables.alias_def(*def_id), args: args.stable(tables) } + } +} + +impl<'tcx> Stable<'tcx> for ty::DynKind { + type T = stable_mir::ty::DynKind; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use ty::DynKind; + match self { + DynKind::Dyn => stable_mir::ty::DynKind::Dyn, + DynKind::DynStar => stable_mir::ty::DynKind::DynStar, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::ExistentialPredicate<'tcx> { + type T = stable_mir::ty::ExistentialPredicate; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::ExistentialPredicate::*; + match self { + ty::ExistentialPredicate::Trait(existential_trait_ref) => { + Trait(existential_trait_ref.stable(tables)) + } + ty::ExistentialPredicate::Projection(existential_projection) => { + Projection(existential_projection.stable(tables)) + } + ty::ExistentialPredicate::AutoTrait(def_id) => AutoTrait(tables.trait_def(*def_id)), + } + } +} + +impl<'tcx> Stable<'tcx> for ty::ExistentialTraitRef<'tcx> { + type T = stable_mir::ty::ExistentialTraitRef; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + let ty::ExistentialTraitRef { def_id, args } = self; + stable_mir::ty::ExistentialTraitRef { + def_id: tables.trait_def(*def_id), + generic_args: args.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for ty::TermKind<'tcx> { + type T = stable_mir::ty::TermKind; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::TermKind; + match self { + ty::TermKind::Ty(ty) => TermKind::Type(tables.intern_ty(*ty)), + ty::TermKind::Const(cnst) => { + let cnst = ConstantKind::from_const(*cnst, tables.tcx); + let cnst = Const { literal: cnst.stable(tables) }; + TermKind::Const(cnst) + } + } + } +} + +impl<'tcx> Stable<'tcx> for ty::ExistentialProjection<'tcx> { + type T = stable_mir::ty::ExistentialProjection; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + let ty::ExistentialProjection { def_id, args, term } = self; + stable_mir::ty::ExistentialProjection { + def_id: tables.trait_def(*def_id), + generic_args: args.stable(tables), + term: term.unpack().stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for ty::adjustment::PointerCoercion { + type T = stable_mir::mir::PointerCoercion; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use ty::adjustment::PointerCoercion; + match self { + PointerCoercion::ReifyFnPointer => stable_mir::mir::PointerCoercion::ReifyFnPointer, + PointerCoercion::UnsafeFnPointer => stable_mir::mir::PointerCoercion::UnsafeFnPointer, + PointerCoercion::ClosureFnPointer(unsafety) => { + stable_mir::mir::PointerCoercion::ClosureFnPointer(unsafety.stable(tables)) + } + PointerCoercion::MutToConstPointer => { + stable_mir::mir::PointerCoercion::MutToConstPointer + } + PointerCoercion::ArrayToPointer => stable_mir::mir::PointerCoercion::ArrayToPointer, + PointerCoercion::Unsize => stable_mir::mir::PointerCoercion::Unsize, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_hir::Unsafety { + type T = stable_mir::mir::Safety; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_hir::Unsafety::Unsafe => stable_mir::mir::Safety::Unsafe, + rustc_hir::Unsafety::Normal => stable_mir::mir::Safety::Normal, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::FakeReadCause { + type T = stable_mir::mir::FakeReadCause; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use mir::FakeReadCause::*; + match self { + ForMatchGuard => stable_mir::mir::FakeReadCause::ForMatchGuard, + ForMatchedPlace(local_def_id) => { + stable_mir::mir::FakeReadCause::ForMatchedPlace(opaque(local_def_id)) + } + ForGuardBinding => stable_mir::mir::FakeReadCause::ForGuardBinding, + ForLet(local_def_id) => stable_mir::mir::FakeReadCause::ForLet(opaque(local_def_id)), + ForIndex => stable_mir::mir::FakeReadCause::ForIndex, + } + } +} + +impl<'tcx> Stable<'tcx> for FieldIdx { + type T = usize; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + self.as_usize() + } +} + +impl<'tcx> Stable<'tcx> for mir::Operand<'tcx> { + type T = stable_mir::mir::Operand; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use mir::Operand::*; + match self { + Copy(place) => stable_mir::mir::Operand::Copy(place.stable(tables)), + Move(place) => stable_mir::mir::Operand::Move(place.stable(tables)), + Constant(c) => stable_mir::mir::Operand::Constant(c.to_string()), + } + } +} + +impl<'tcx> Stable<'tcx> for mir::Place<'tcx> { + type T = stable_mir::mir::Place; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + stable_mir::mir::Place { + local: self.local.as_usize(), + projection: format!("{:?}", self.projection), + } + } +} + +impl<'tcx> Stable<'tcx> for mir::coverage::CoverageKind { + type T = stable_mir::mir::CoverageKind; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::coverage::CoverageKind; + match self { + CoverageKind::Counter { function_source_hash, id } => { + stable_mir::mir::CoverageKind::Counter { + function_source_hash: *function_source_hash as usize, + id: opaque(id), + } + } + CoverageKind::Expression { id, lhs, op, rhs } => { + stable_mir::mir::CoverageKind::Expression { + id: opaque(id), + lhs: opaque(lhs), + op: op.stable(tables), + rhs: opaque(rhs), + } + } + CoverageKind::Unreachable => stable_mir::mir::CoverageKind::Unreachable, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::UserTypeProjection { + type T = stable_mir::mir::UserTypeProjection; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + UserTypeProjection { base: self.base.as_usize(), projection: format!("{:?}", self.projs) } + } +} + +impl<'tcx> Stable<'tcx> for mir::coverage::Op { + type T = stable_mir::mir::Op; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::coverage::Op::*; + match self { + Subtract => stable_mir::mir::Op::Subtract, + Add => stable_mir::mir::Op::Add, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::Local { + type T = stable_mir::mir::Local; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + self.as_usize() + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::VariantIdx { + type T = VariantIdx; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + self.as_usize() + } +} + +impl<'tcx> Stable<'tcx> for Variance { + type T = stable_mir::mir::Variance; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + match self { + Variance::Bivariant => stable_mir::mir::Variance::Bivariant, + Variance::Contravariant => stable_mir::mir::Variance::Contravariant, + Variance::Covariant => stable_mir::mir::Variance::Covariant, + Variance::Invariant => stable_mir::mir::Variance::Invariant, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::RetagKind { + type T = stable_mir::mir::RetagKind; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::RetagKind; + match self { + RetagKind::FnEntry => stable_mir::mir::RetagKind::FnEntry, + RetagKind::TwoPhase => stable_mir::mir::RetagKind::TwoPhase, + RetagKind::Raw => stable_mir::mir::RetagKind::Raw, + RetagKind::Default => stable_mir::mir::RetagKind::Default, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::UserTypeAnnotationIndex { + type T = usize; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + self.as_usize() + } +} + +impl<'tcx> Stable<'tcx> for CodeRegion { + type T = stable_mir::mir::CodeRegion; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + stable_mir::mir::CodeRegion { + file_name: self.file_name.as_str().to_string(), + start_line: self.start_line as usize, + start_col: self.start_col as usize, + end_line: self.end_line as usize, + end_col: self.end_col as usize, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::UnwindAction { + type T = stable_mir::mir::UnwindAction; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::UnwindAction; + match self { + UnwindAction::Continue => stable_mir::mir::UnwindAction::Continue, + UnwindAction::Unreachable => stable_mir::mir::UnwindAction::Unreachable, + UnwindAction::Terminate => stable_mir::mir::UnwindAction::Terminate, + UnwindAction::Cleanup(bb) => stable_mir::mir::UnwindAction::Cleanup(bb.as_usize()), + } + } +} + +impl<'tcx> Stable<'tcx> for mir::NonDivergingIntrinsic<'tcx> { + type T = stable_mir::mir::NonDivergingIntrinsic; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::NonDivergingIntrinsic; + match self { + NonDivergingIntrinsic::Assume(op) => { + stable_mir::mir::NonDivergingIntrinsic::Assume(op.stable(tables)) + } + NonDivergingIntrinsic::CopyNonOverlapping(copy_non_overlapping) => { + stable_mir::mir::NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping { + src: copy_non_overlapping.src.stable(tables), + dst: copy_non_overlapping.dst.stable(tables), + count: copy_non_overlapping.count.stable(tables), }) + } + } + } +} + +impl<'tcx> Stable<'tcx> for mir::AssertMessage<'tcx> { + type T = stable_mir::mir::AssertMessage; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::AssertKind; + match self { + AssertKind::BoundsCheck { len, index } => stable_mir::mir::AssertMessage::BoundsCheck { + len: len.stable(tables), + index: index.stable(tables), + }, + AssertKind::Overflow(bin_op, op1, op2) => stable_mir::mir::AssertMessage::Overflow( + bin_op.stable(tables), + op1.stable(tables), + op2.stable(tables), + ), + AssertKind::OverflowNeg(op) => { + stable_mir::mir::AssertMessage::OverflowNeg(op.stable(tables)) + } + AssertKind::DivisionByZero(op) => { + stable_mir::mir::AssertMessage::DivisionByZero(op.stable(tables)) + } + AssertKind::RemainderByZero(op) => { + stable_mir::mir::AssertMessage::RemainderByZero(op.stable(tables)) + } + AssertKind::ResumedAfterReturn(generator) => { + stable_mir::mir::AssertMessage::ResumedAfterReturn(generator.stable(tables)) + } + AssertKind::ResumedAfterPanic(generator) => { + stable_mir::mir::AssertMessage::ResumedAfterPanic(generator.stable(tables)) + } + AssertKind::MisalignedPointerDereference { required, found } => { + stable_mir::mir::AssertMessage::MisalignedPointerDereference { + required: required.stable(tables), + found: found.stable(tables), + } + } + } + } +} + +impl<'tcx> Stable<'tcx> for mir::BinOp { + type T = stable_mir::mir::BinOp; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use mir::BinOp; + match self { + BinOp::Add => stable_mir::mir::BinOp::Add, + BinOp::AddUnchecked => stable_mir::mir::BinOp::AddUnchecked, + BinOp::Sub => stable_mir::mir::BinOp::Sub, + BinOp::SubUnchecked => stable_mir::mir::BinOp::SubUnchecked, + BinOp::Mul => stable_mir::mir::BinOp::Mul, + BinOp::MulUnchecked => stable_mir::mir::BinOp::MulUnchecked, + BinOp::Div => stable_mir::mir::BinOp::Div, + BinOp::Rem => stable_mir::mir::BinOp::Rem, + BinOp::BitXor => stable_mir::mir::BinOp::BitXor, + BinOp::BitAnd => stable_mir::mir::BinOp::BitAnd, + BinOp::BitOr => stable_mir::mir::BinOp::BitOr, + BinOp::Shl => stable_mir::mir::BinOp::Shl, + BinOp::ShlUnchecked => stable_mir::mir::BinOp::ShlUnchecked, + BinOp::Shr => stable_mir::mir::BinOp::Shr, + BinOp::ShrUnchecked => stable_mir::mir::BinOp::ShrUnchecked, + BinOp::Eq => stable_mir::mir::BinOp::Eq, + BinOp::Lt => stable_mir::mir::BinOp::Lt, + BinOp::Le => stable_mir::mir::BinOp::Le, + BinOp::Ne => stable_mir::mir::BinOp::Ne, + BinOp::Ge => stable_mir::mir::BinOp::Ge, + BinOp::Gt => stable_mir::mir::BinOp::Gt, + BinOp::Offset => stable_mir::mir::BinOp::Offset, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::UnOp { + type T = stable_mir::mir::UnOp; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use mir::UnOp; + match self { + UnOp::Not => stable_mir::mir::UnOp::Not, + UnOp::Neg => stable_mir::mir::UnOp::Neg, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::AggregateKind<'tcx> { + type T = stable_mir::mir::AggregateKind; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + mir::AggregateKind::Array(ty) => { + stable_mir::mir::AggregateKind::Array(tables.intern_ty(*ty)) + } + mir::AggregateKind::Tuple => stable_mir::mir::AggregateKind::Tuple, + mir::AggregateKind::Adt(def_id, var_idx, generic_arg, user_ty_index, field_idx) => { + stable_mir::mir::AggregateKind::Adt( + rustc_internal::adt_def(*def_id), + var_idx.index(), + generic_arg.stable(tables), + user_ty_index.map(|idx| idx.index()), + field_idx.map(|idx| idx.index()), + ) + } + mir::AggregateKind::Closure(def_id, generic_arg) => { + stable_mir::mir::AggregateKind::Closure( + rustc_internal::closure_def(*def_id), + generic_arg.stable(tables), + ) + } + mir::AggregateKind::Generator(def_id, generic_arg, movability) => { + stable_mir::mir::AggregateKind::Generator( + rustc_internal::generator_def(*def_id), + generic_arg.stable(tables), + movability.stable(tables), + ) + } + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_hir::GeneratorKind { + type T = stable_mir::mir::GeneratorKind; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use rustc_hir::{AsyncGeneratorKind, GeneratorKind}; + match self { + GeneratorKind::Async(async_gen) => { + let async_gen = match async_gen { + AsyncGeneratorKind::Block => stable_mir::mir::AsyncGeneratorKind::Block, + AsyncGeneratorKind::Closure => stable_mir::mir::AsyncGeneratorKind::Closure, + AsyncGeneratorKind::Fn => stable_mir::mir::AsyncGeneratorKind::Fn, + }; + stable_mir::mir::GeneratorKind::Async(async_gen) + } + GeneratorKind::Gen => stable_mir::mir::GeneratorKind::Gen, + } + } +} + +impl<'tcx> Stable<'tcx> for mir::InlineAsmOperand<'tcx> { + type T = stable_mir::mir::InlineAsmOperand; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::InlineAsmOperand; + + let (in_value, out_place) = match self { + InlineAsmOperand::In { value, .. } => (Some(value.stable(tables)), None), + InlineAsmOperand::Out { place, .. } => (None, place.map(|place| place.stable(tables))), + InlineAsmOperand::InOut { in_value, out_place, .. } => { + (Some(in_value.stable(tables)), out_place.map(|place| place.stable(tables))) + } + InlineAsmOperand::Const { .. } + | InlineAsmOperand::SymFn { .. } + | InlineAsmOperand::SymStatic { .. } => (None, None), + }; + + stable_mir::mir::InlineAsmOperand { in_value, out_place, raw_rpr: format!("{self:?}") } + } +} + +impl<'tcx> Stable<'tcx> for mir::Terminator<'tcx> { + type T = stable_mir::mir::Terminator; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_middle::mir::TerminatorKind::*; + use stable_mir::mir::Terminator; + match &self.kind { + Goto { target } => Terminator::Goto { target: target.as_usize() }, + SwitchInt { discr, targets } => Terminator::SwitchInt { + discr: discr.stable(tables), + targets: targets + .iter() + .map(|(value, target)| stable_mir::mir::SwitchTarget { + value, + target: target.as_usize(), + }) + .collect(), + otherwise: targets.otherwise().as_usize(), + }, + Resume => Terminator::Resume, + Terminate => Terminator::Abort, + Return => Terminator::Return, + Unreachable => Terminator::Unreachable, + Drop { place, target, unwind, replace: _ } => Terminator::Drop { + place: place.stable(tables), + target: target.as_usize(), + unwind: unwind.stable(tables), + }, + Call { func, args, destination, target, unwind, call_source: _, fn_span: _ } => { + Terminator::Call { + func: func.stable(tables), + args: args.iter().map(|arg| arg.stable(tables)).collect(), + destination: destination.stable(tables), + target: target.map(|t| t.as_usize()), + unwind: unwind.stable(tables), + } + } + Assert { cond, expected, msg, target, unwind } => Terminator::Assert { + cond: cond.stable(tables), + expected: *expected, + msg: msg.stable(tables), + target: target.as_usize(), + unwind: unwind.stable(tables), + }, + InlineAsm { template, operands, options, line_spans, destination, unwind } => { + Terminator::InlineAsm { + template: format!("{template:?}"), + operands: operands.iter().map(|operand| operand.stable(tables)).collect(), + options: format!("{options:?}"), + line_spans: format!("{line_spans:?}"), + destination: destination.map(|d| d.as_usize()), + unwind: unwind.stable(tables), + } + } + Yield { .. } | GeneratorDrop | FalseEdge { .. } | FalseUnwind { .. } => unreachable!(), + } + } +} + +impl<'tcx> Stable<'tcx> for ty::GenericArgs<'tcx> { + type T = stable_mir::ty::GenericArgs; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::GenericArgs; + + GenericArgs(self.iter().map(|arg| arg.unpack().stable(tables)).collect()) + } +} + +impl<'tcx> Stable<'tcx> for ty::GenericArgKind<'tcx> { + type T = stable_mir::ty::GenericArgKind; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::GenericArgKind; + match self { + ty::GenericArgKind::Lifetime(region) => GenericArgKind::Lifetime(opaque(region)), + ty::GenericArgKind::Type(ty) => GenericArgKind::Type(tables.intern_ty(*ty)), + ty::GenericArgKind::Const(cnst) => { + let cnst = ConstantKind::from_const(*cnst, tables.tcx); + GenericArgKind::Const(stable_mir::ty::Const { literal: cnst.stable(tables) }) + } + } + } +} + +impl<'tcx, S, V> Stable<'tcx> for ty::Binder<'tcx, S> +where + S: Stable<'tcx, T = V>, +{ + type T = stable_mir::ty::Binder; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::Binder; + + Binder { + value: self.as_ref().skip_binder().stable(tables), + bound_vars: self + .bound_vars() + .iter() + .map(|bound_var| bound_var.stable(tables)) .collect(), - otherwise: targets.otherwise().as_usize(), - }, - Resume => Terminator::Resume, - Terminate => Terminator::Abort, - Return => Terminator::Return, - Unreachable => Terminator::Unreachable, - Drop { place, target, unwind } => Terminator::Drop { - place: rustc_place_to_place(place), - target: target.as_usize(), - unwind: rustc_unwind_to_unwind(unwind), - }, - Call { func, args, destination, target, unwind, from_hir_call: _, fn_span: _ } => { - Terminator::Call { - func: rustc_op_to_op(func), - args: args.iter().map(|arg| rustc_op_to_op(arg)).collect(), - destination: rustc_place_to_place(destination), - target: target.map(|t| t.as_usize()), - unwind: rustc_unwind_to_unwind(unwind), - } - } - Assert { cond, expected, msg, target, unwind } => Terminator::Assert { - cond: rustc_op_to_op(cond), - expected: *expected, - msg: rustc_assert_msg_to_msg(msg), - target: target.as_usize(), - unwind: rustc_unwind_to_unwind(unwind), - }, - Yield { .. } => todo!(), - GeneratorDrop => Terminator::GeneratorDrop, - FalseEdge { .. } => todo!(), - FalseUnwind { .. } => todo!(), - InlineAsm { .. } => todo!(), + } + } +} + +impl<'tcx, S, V> Stable<'tcx> for ty::EarlyBinder +where + S: Stable<'tcx, T = V>, +{ + type T = stable_mir::ty::EarlyBinder; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::EarlyBinder; + + EarlyBinder { value: self.as_ref().skip_binder().stable(tables) } + } +} + +impl<'tcx> Stable<'tcx> for ty::FnSig<'tcx> { + type T = stable_mir::ty::FnSig; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use rustc_target::spec::abi; + use stable_mir::ty::{Abi, FnSig}; + + FnSig { + inputs_and_output: self + .inputs_and_output + .iter() + .map(|ty| tables.intern_ty(ty)) + .collect(), + c_variadic: self.c_variadic, + unsafety: self.unsafety.stable(tables), + abi: match self.abi { + abi::Abi::Rust => Abi::Rust, + abi::Abi::C { unwind } => Abi::C { unwind }, + abi::Abi::Cdecl { unwind } => Abi::Cdecl { unwind }, + abi::Abi::Stdcall { unwind } => Abi::Stdcall { unwind }, + abi::Abi::Fastcall { unwind } => Abi::Fastcall { unwind }, + abi::Abi::Vectorcall { unwind } => Abi::Vectorcall { unwind }, + abi::Abi::Thiscall { unwind } => Abi::Thiscall { unwind }, + abi::Abi::Aapcs { unwind } => Abi::Aapcs { unwind }, + abi::Abi::Win64 { unwind } => Abi::Win64 { unwind }, + abi::Abi::SysV64 { unwind } => Abi::SysV64 { unwind }, + abi::Abi::PtxKernel => Abi::PtxKernel, + abi::Abi::Msp430Interrupt => Abi::Msp430Interrupt, + abi::Abi::X86Interrupt => Abi::X86Interrupt, + abi::Abi::AmdGpuKernel => Abi::AmdGpuKernel, + abi::Abi::EfiApi => Abi::EfiApi, + abi::Abi::AvrInterrupt => Abi::AvrInterrupt, + abi::Abi::AvrNonBlockingInterrupt => Abi::AvrNonBlockingInterrupt, + abi::Abi::CCmseNonSecureCall => Abi::CCmseNonSecureCall, + abi::Abi::Wasm => Abi::Wasm, + abi::Abi::System { unwind } => Abi::System { unwind }, + abi::Abi::RustIntrinsic => Abi::RustIntrinsic, + abi::Abi::RustCall => Abi::RustCall, + abi::Abi::PlatformIntrinsic => Abi::PlatformIntrinsic, + abi::Abi::Unadjusted => Abi::Unadjusted, + abi::Abi::RustCold => Abi::RustCold, + abi::Abi::RiscvInterruptM => Abi::RiscvInterruptM, + abi::Abi::RiscvInterruptS => Abi::RiscvInterruptS, + }, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::BoundTyKind { + type T = stable_mir::ty::BoundTyKind; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::BoundTyKind; + + match self { + ty::BoundTyKind::Anon => BoundTyKind::Anon, + ty::BoundTyKind::Param(def_id, symbol) => { + BoundTyKind::Param(rustc_internal::param_def(*def_id), symbol.to_string()) + } + } + } +} + +impl<'tcx> Stable<'tcx> for ty::BoundRegionKind { + type T = stable_mir::ty::BoundRegionKind; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::BoundRegionKind; + + match self { + ty::BoundRegionKind::BrAnon(option_span) => { + BoundRegionKind::BrAnon(option_span.map(|span| opaque(&span))) + } + ty::BoundRegionKind::BrNamed(def_id, symbol) => { + BoundRegionKind::BrNamed(rustc_internal::br_named_def(*def_id), symbol.to_string()) + } + ty::BoundRegionKind::BrEnv => BoundRegionKind::BrEnv, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::BoundVariableKind { + type T = stable_mir::ty::BoundVariableKind; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::BoundVariableKind; + + match self { + ty::BoundVariableKind::Ty(bound_ty_kind) => { + BoundVariableKind::Ty(bound_ty_kind.stable(tables)) + } + ty::BoundVariableKind::Region(bound_region_kind) => { + BoundVariableKind::Region(bound_region_kind.stable(tables)) + } + ty::BoundVariableKind::Const => BoundVariableKind::Const, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::IntTy { + type T = IntTy; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + match self { + ty::IntTy::Isize => IntTy::Isize, + ty::IntTy::I8 => IntTy::I8, + ty::IntTy::I16 => IntTy::I16, + ty::IntTy::I32 => IntTy::I32, + ty::IntTy::I64 => IntTy::I64, + ty::IntTy::I128 => IntTy::I128, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::UintTy { + type T = UintTy; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + match self { + ty::UintTy::Usize => UintTy::Usize, + ty::UintTy::U8 => UintTy::U8, + ty::UintTy::U16 => UintTy::U16, + ty::UintTy::U32 => UintTy::U32, + ty::UintTy::U64 => UintTy::U64, + ty::UintTy::U128 => UintTy::U128, + } + } +} + +impl<'tcx> Stable<'tcx> for ty::FloatTy { + type T = FloatTy; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + match self { + ty::FloatTy::F32 => FloatTy::F32, + ty::FloatTy::F64 => FloatTy::F64, + } + } +} + +impl<'tcx> Stable<'tcx> for hir::Movability { + type T = Movability; + + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + match self { + hir::Movability::Static => Movability::Static, + hir::Movability::Movable => Movability::Movable, + } + } +} + +impl<'tcx> Stable<'tcx> for Ty<'tcx> { + type T = stable_mir::ty::TyKind; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self.kind() { + ty::Bool => TyKind::RigidTy(RigidTy::Bool), + ty::Char => TyKind::RigidTy(RigidTy::Char), + ty::Int(int_ty) => TyKind::RigidTy(RigidTy::Int(int_ty.stable(tables))), + ty::Uint(uint_ty) => TyKind::RigidTy(RigidTy::Uint(uint_ty.stable(tables))), + ty::Float(float_ty) => TyKind::RigidTy(RigidTy::Float(float_ty.stable(tables))), + ty::Adt(adt_def, generic_args) => TyKind::RigidTy(RigidTy::Adt( + rustc_internal::adt_def(adt_def.did()), + generic_args.stable(tables), + )), + ty::Foreign(def_id) => { + TyKind::RigidTy(RigidTy::Foreign(rustc_internal::foreign_def(*def_id))) + } + ty::Str => TyKind::RigidTy(RigidTy::Str), + ty::Array(ty, constant) => { + let cnst = ConstantKind::from_const(*constant, tables.tcx); + let cnst = stable_mir::ty::Const { literal: cnst.stable(tables) }; + TyKind::RigidTy(RigidTy::Array(tables.intern_ty(*ty), cnst)) + } + ty::Slice(ty) => TyKind::RigidTy(RigidTy::Slice(tables.intern_ty(*ty))), + ty::RawPtr(ty::TypeAndMut { ty, mutbl }) => { + TyKind::RigidTy(RigidTy::RawPtr(tables.intern_ty(*ty), mutbl.stable(tables))) + } + ty::Ref(region, ty, mutbl) => TyKind::RigidTy(RigidTy::Ref( + opaque(region), + tables.intern_ty(*ty), + mutbl.stable(tables), + )), + ty::FnDef(def_id, generic_args) => TyKind::RigidTy(RigidTy::FnDef( + rustc_internal::fn_def(*def_id), + generic_args.stable(tables), + )), + ty::FnPtr(poly_fn_sig) => TyKind::RigidTy(RigidTy::FnPtr(poly_fn_sig.stable(tables))), + ty::Dynamic(existential_predicates, region, dyn_kind) => { + TyKind::RigidTy(RigidTy::Dynamic( + existential_predicates + .iter() + .map(|existential_predicate| existential_predicate.stable(tables)) + .collect(), + opaque(region), + dyn_kind.stable(tables), + )) + } + ty::Closure(def_id, generic_args) => TyKind::RigidTy(RigidTy::Closure( + rustc_internal::closure_def(*def_id), + generic_args.stable(tables), + )), + ty::Generator(def_id, generic_args, movability) => TyKind::RigidTy(RigidTy::Generator( + rustc_internal::generator_def(*def_id), + generic_args.stable(tables), + movability.stable(tables), + )), + ty::Never => TyKind::RigidTy(RigidTy::Never), + ty::Tuple(fields) => TyKind::RigidTy(RigidTy::Tuple( + fields.iter().map(|ty| tables.intern_ty(ty)).collect(), + )), + ty::Alias(alias_kind, alias_ty) => { + TyKind::Alias(alias_kind.stable(tables), alias_ty.stable(tables)) + } + ty::Param(param_ty) => TyKind::Param(param_ty.stable(tables)), + ty::Bound(debruijn_idx, bound_ty) => { + TyKind::Bound(debruijn_idx.as_usize(), bound_ty.stable(tables)) + } + ty::Placeholder(..) + | ty::GeneratorWitness(_) + | ty::GeneratorWitnessMIR(_, _) + | ty::Infer(_) + | ty::Error(_) => { + unreachable!(); + } + } + } +} + +impl<'tcx> Stable<'tcx> for ty::ParamTy { + type T = stable_mir::ty::ParamTy; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::ParamTy; + ParamTy { index: self.index, name: self.name.to_string() } + } +} + +impl<'tcx> Stable<'tcx> for ty::BoundTy { + type T = stable_mir::ty::BoundTy; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::BoundTy; + BoundTy { var: self.var.as_usize(), kind: self.kind.stable(tables) } + } +} + +impl<'tcx> Stable<'tcx> for mir::interpret::Allocation { + type T = stable_mir::ty::Allocation; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + allocation_filter(self, alloc_range(rustc_target::abi::Size::ZERO, self.size()), tables) + } +} + +impl<'tcx> Stable<'tcx> for ty::trait_def::TraitSpecializationKind { + type T = stable_mir::ty::TraitSpecializationKind; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::TraitSpecializationKind; + + match self { + ty::trait_def::TraitSpecializationKind::None => TraitSpecializationKind::None, + ty::trait_def::TraitSpecializationKind::Marker => TraitSpecializationKind::Marker, + ty::trait_def::TraitSpecializationKind::AlwaysApplicable => { + TraitSpecializationKind::AlwaysApplicable + } + } + } +} + +impl<'tcx> Stable<'tcx> for ty::TraitDef { + type T = stable_mir::ty::TraitDecl; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::TraitDecl; + + TraitDecl { + def_id: rustc_internal::trait_def(self.def_id), + unsafety: self.unsafety.stable(tables), + paren_sugar: self.paren_sugar, + has_auto_impl: self.has_auto_impl, + is_marker: self.is_marker, + is_coinductive: self.is_coinductive, + skip_array_during_method_dispatch: self.skip_array_during_method_dispatch, + specialization_kind: self.specialization_kind.stable(tables), + must_implement_one_of: self + .must_implement_one_of + .as_ref() + .map(|idents| idents.iter().map(|ident| opaque(ident)).collect()), + implement_via_object: self.implement_via_object, + deny_explicit_impl: self.deny_explicit_impl, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_middle::mir::ConstantKind<'tcx> { + type T = stable_mir::ty::ConstantKind; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + ConstantKind::Ty(c) => match c.kind() { + ty::Value(val) => { + let const_val = tables.tcx.valtree_to_const_val((c.ty(), val)); + stable_mir::ty::ConstantKind::Allocated(new_allocation(self, const_val, tables)) + } + ty::ParamCt(param) => stable_mir::ty::ConstantKind::ParamCt(opaque(¶m)), + ty::ErrorCt(_) => unreachable!(), + _ => unimplemented!(), + }, + ConstantKind::Unevaluated(unev_const, ty) => { + stable_mir::ty::ConstantKind::Unevaluated(stable_mir::ty::UnevaluatedConst { + ty: tables.intern_ty(*ty), + def: tables.const_def(unev_const.def), + args: unev_const.args.stable(tables), + promoted: unev_const.promoted.map(|u| u.as_u32()), + }) + } + ConstantKind::Val(val, _) => { + stable_mir::ty::ConstantKind::Allocated(new_allocation(self, *val, tables)) + } + } + } +} + +impl<'tcx> Stable<'tcx> for ty::TraitRef<'tcx> { + type T = stable_mir::ty::TraitRef; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + use stable_mir::ty::TraitRef; + + TraitRef { def_id: rustc_internal::trait_def(self.def_id), args: self.args.stable(tables) } } } diff --git a/compiler/rustc_smir/src/stable_mir/mir/body.rs b/compiler/rustc_smir/src/stable_mir/mir/body.rs index 6328c35aa5982..c16bd6cbd70e2 100644 --- a/compiler/rustc_smir/src/stable_mir/mir/body.rs +++ b/compiler/rustc_smir/src/stable_mir/mir/body.rs @@ -1,4 +1,8 @@ -use crate::stable_mir::ty::Ty; +use crate::rustc_internal::Opaque; +use crate::stable_mir::ty::{ + AdtDef, ClosureDef, Const, GeneratorDef, GenericArgs, Movability, Region, +}; +use crate::stable_mir::{self, ty::Ty}; #[derive(Clone, Debug)] pub struct Body { @@ -46,6 +50,23 @@ pub enum Terminator { unwind: UnwindAction, }, GeneratorDrop, + InlineAsm { + template: String, + operands: Vec, + options: String, + line_spans: String, + destination: Option, + unwind: UnwindAction, + }, +} + +#[derive(Clone, Debug)] +pub struct InlineAsmOperand { + pub in_value: Option, + pub out_place: Option, + // This field has a raw debug representation of MIR's InlineAsmOperand. + // For now we care about place/operand + the rest in a debug format. + pub raw_rpr: String, } #[derive(Clone, Debug)] @@ -71,15 +92,20 @@ pub enum AssertMessage { #[derive(Clone, Debug)] pub enum BinOp { Add, + AddUnchecked, Sub, + SubUnchecked, Mul, + MulUnchecked, Div, Rem, BitXor, BitAnd, BitOr, Shl, + ShlUnchecked, Shr, + ShrUnchecked, Eq, Lt, Le, @@ -108,18 +134,225 @@ pub enum AsyncGeneratorKind { Fn, } +pub(crate) type LocalDefId = Opaque; +pub(crate) type CounterValueReference = Opaque; +pub(crate) type InjectedExpressionId = Opaque; +pub(crate) type ExpressionOperandId = Opaque; + +/// The FakeReadCause describes the type of pattern why a FakeRead statement exists. +#[derive(Clone, Debug)] +pub enum FakeReadCause { + ForMatchGuard, + ForMatchedPlace(LocalDefId), + ForGuardBinding, + ForLet(LocalDefId), + ForIndex, +} + +/// Describes what kind of retag is to be performed +#[derive(Clone, Debug)] +pub enum RetagKind { + FnEntry, + TwoPhase, + Raw, + Default, +} + +#[derive(Clone, Debug)] +pub enum Variance { + Covariant, + Invariant, + Contravariant, + Bivariant, +} + +#[derive(Clone, Debug)] +pub enum Op { + Subtract, + Add, +} + +#[derive(Clone, Debug)] +pub enum CoverageKind { + Counter { + function_source_hash: usize, + id: CounterValueReference, + }, + Expression { + id: InjectedExpressionId, + lhs: ExpressionOperandId, + op: Op, + rhs: ExpressionOperandId, + }, + Unreachable, +} + +#[derive(Clone, Debug)] +pub struct CodeRegion { + pub file_name: String, + pub start_line: usize, + pub start_col: usize, + pub end_line: usize, + pub end_col: usize, +} + +#[derive(Clone, Debug)] +pub struct Coverage { + pub kind: CoverageKind, + pub code_region: Option, +} + +#[derive(Clone, Debug)] +pub struct CopyNonOverlapping { + pub src: Operand, + pub dst: Operand, + pub count: Operand, +} + +#[derive(Clone, Debug)] +pub enum NonDivergingIntrinsic { + Assume(Operand), + CopyNonOverlapping(CopyNonOverlapping), +} + #[derive(Clone, Debug)] pub enum Statement { Assign(Place, Rvalue), + FakeRead(FakeReadCause, Place), + SetDiscriminant { place: Place, variant_index: VariantIdx }, + Deinit(Place), + StorageLive(Local), + StorageDead(Local), + Retag(RetagKind, Place), + PlaceMention(Place), + AscribeUserType { place: Place, projections: UserTypeProjection, variance: Variance }, + Coverage(Coverage), + Intrinsic(NonDivergingIntrinsic), + ConstEvalCounter, Nop, } -// FIXME this is incomplete #[derive(Clone, Debug)] pub enum Rvalue { - Use(Operand), + /// Creates a pointer with the indicated mutability to the place. + /// + /// This is generated by pointer casts like `&v as *const _` or raw address of expressions like + /// `&raw v` or `addr_of!(v)`. + AddressOf(Mutability, Place), + + /// Creates an aggregate value, like a tuple or struct. + /// + /// This is needed because dataflow analysis needs to distinguish + /// `dest = Foo { x: ..., y: ... }` from `dest.x = ...; dest.y = ...;` in the case that `Foo` + /// has a destructor. + /// + /// Disallowed after deaggregation for all aggregate kinds except `Array` and `Generator`. After + /// generator lowering, `Generator` aggregate kinds are disallowed too. + Aggregate(AggregateKind, Vec), + + /// * `Offset` has the same semantics as [`offset`](pointer::offset), except that the second + /// parameter may be a `usize` as well. + /// * The comparison operations accept `bool`s, `char`s, signed or unsigned integers, floats, + /// raw pointers, or function pointers and return a `bool`. The types of the operands must be + /// matching, up to the usual caveat of the lifetimes in function pointers. + /// * Left and right shift operations accept signed or unsigned integers not necessarily of the + /// same type and return a value of the same type as their LHS. Like in Rust, the RHS is + /// truncated as needed. + /// * The `Bit*` operations accept signed integers, unsigned integers, or bools with matching + /// types and return a value of that type. + /// * The remaining operations accept signed integers, unsigned integers, or floats with + /// matching types and return a value of that type. + BinaryOp(BinOp, Operand, Operand), + + /// Performs essentially all of the casts that can be performed via `as`. + /// + /// This allows for casts from/to a variety of types. + Cast(CastKind, Operand, Ty), + + /// Same as `BinaryOp`, but yields `(T, bool)` with a `bool` indicating an error condition. + /// + /// For addition, subtraction, and multiplication on integers the error condition is set when + /// the infinite precision result would not be equal to the actual result. CheckedBinaryOp(BinOp, Operand, Operand), + + /// A CopyForDeref is equivalent to a read from a place. + /// When such a read happens, it is guaranteed that the only use of the returned value is a + /// deref operation, immediately followed by one or more projections. + CopyForDeref(Place), + + /// Computes the discriminant of the place, returning it as an integer of type + /// [`discriminant_ty`]. Returns zero for types without discriminant. + /// + /// The validity requirements for the underlying value are undecided for this rvalue, see + /// [#91095]. Note too that the value of the discriminant is not the same thing as the + /// variant index; use [`discriminant_for_variant`] to convert. + /// + /// [`discriminant_ty`]: rustc_middle::ty::Ty::discriminant_ty + /// [#91095]: https://github.com/rust-lang/rust/issues/91095 + /// [`discriminant_for_variant`]: rustc_middle::ty::Ty::discriminant_for_variant + Discriminant(Place), + + /// Yields the length of the place, as a `usize`. + /// + /// If the type of the place is an array, this is the array length. For slices (`[T]`, not + /// `&[T]`) this accesses the place's metadata to determine the length. This rvalue is + /// ill-formed for places of other types. + Len(Place), + + /// Creates a reference to the place. + Ref(Region, BorrowKind, Place), + + /// Creates an array where each element is the value of the operand. + /// + /// This is the cause of a bug in the case where the repetition count is zero because the value + /// is not dropped, see [#74836]. + /// + /// Corresponds to source code like `[x; 32]`. + /// + /// [#74836]: https://github.com/rust-lang/rust/issues/74836 + Repeat(Operand, Const), + + /// Transmutes a `*mut u8` into shallow-initialized `Box`. + /// + /// This is different from a normal transmute because dataflow analysis will treat the box as + /// initialized but its content as uninitialized. Like other pointer casts, this in general + /// affects alias analysis. + ShallowInitBox(Operand, Ty), + + /// Creates a pointer/reference to the given thread local. + /// + /// The yielded type is a `*mut T` if the static is mutable, otherwise if the static is extern a + /// `*const T`, and if neither of those apply a `&T`. + /// + /// **Note:** This is a runtime operation that actually executes code and is in this sense more + /// like a function call. Also, eliminating dead stores of this rvalue causes `fn main() {}` to + /// SIGILL for some reason that I (JakobDegen) never got a chance to look into. + /// + /// **Needs clarification**: Are there weird additional semantics here related to the runtime + /// nature of this operation? + ThreadLocalRef(stable_mir::CrateItem), + + /// Computes a value as described by the operation. + NullaryOp(NullOp, Ty), + + /// Exactly like `BinaryOp`, but less operands. + /// + /// Also does two's-complement arithmetic. Negation requires a signed integer or a float; + /// bitwise not requires a signed integer, unsigned integer, or bool. Both operation kinds + /// return a value with the same type as their operand. UnaryOp(UnOp, Operand), + + /// Yields the operand unchanged + Use(Operand), +} + +#[derive(Clone, Debug)] +pub enum AggregateKind { + Array(Ty), + Tuple, + Adt(AdtDef, VariantIdx, GenericArgs, Option, Option), + Closure(ClosureDef, GenericArgs), + Generator(GeneratorDef, GenericArgs, Movability), } #[derive(Clone, Debug)] @@ -131,12 +364,115 @@ pub enum Operand { #[derive(Clone, Debug)] pub struct Place { - pub local: usize, + pub local: Local, + pub projection: String, +} + +#[derive(Clone, Debug)] +pub struct UserTypeProjection { + pub base: UserTypeAnnotationIndex, pub projection: String, } +pub type Local = usize; + +type FieldIdx = usize; + +/// The source-order index of a variant in a type. +pub type VariantIdx = usize; + +type UserTypeAnnotationIndex = usize; + #[derive(Clone, Debug)] pub struct SwitchTarget { pub value: u128, pub target: usize, } + +#[derive(Clone, Debug)] +pub enum BorrowKind { + /// Data must be immutable and is aliasable. + Shared, + + /// The immediately borrowed place must be immutable, but projections from + /// it don't need to be. For example, a shallow borrow of `a.b` doesn't + /// conflict with a mutable borrow of `a.b.c`. + Shallow, + + /// Data is mutable and not aliasable. + Mut { + /// `true` if this borrow arose from method-call auto-ref + kind: MutBorrowKind, + }, +} + +#[derive(Clone, Debug)] +pub enum MutBorrowKind { + Default, + TwoPhaseBorrow, + ClosureCapture, +} + +#[derive(Clone, Debug)] +pub enum Mutability { + Not, + Mut, +} + +#[derive(Clone, Debug)] +pub enum Safety { + Unsafe, + Normal, +} + +#[derive(Clone, Debug)] +pub enum PointerCoercion { + /// Go from a fn-item type to a fn-pointer type. + ReifyFnPointer, + + /// Go from a safe fn pointer to an unsafe fn pointer. + UnsafeFnPointer, + + /// Go from a non-capturing closure to an fn pointer or an unsafe fn pointer. + /// It cannot convert a closure that requires unsafe. + ClosureFnPointer(Safety), + + /// Go from a mut raw pointer to a const raw pointer. + MutToConstPointer, + + /// Go from `*const [T; N]` to `*const T` + ArrayToPointer, + + /// Unsize a pointer/reference value, e.g., `&[T; n]` to + /// `&[T]`. Note that the source could be a thin or fat pointer. + /// This will do things like convert thin pointers to fat + /// pointers, or convert structs containing thin pointers to + /// structs containing fat pointers, or convert between fat + /// pointers. + Unsize, +} + +#[derive(Clone, Debug)] +pub enum CastKind { + PointerExposeAddress, + PointerFromExposedAddress, + PointerCoercion(PointerCoercion), + DynStar, + IntToInt, + FloatToInt, + FloatToFloat, + IntToFloat, + PtrToPtr, + FnPtrToPtr, + Transmute, +} + +#[derive(Clone, Debug)] +pub enum NullOp { + /// Returns the size of a value of that type. + SizeOf, + /// Returns the minimum alignment of a type. + AlignOf, + /// Returns the offset of a field. + OffsetOf(Vec), +} diff --git a/compiler/rustc_smir/src/stable_mir/mod.rs b/compiler/rustc_smir/src/stable_mir/mod.rs index 612777b9c7539..19061742b64be 100644 --- a/compiler/rustc_smir/src/stable_mir/mod.rs +++ b/compiler/rustc_smir/src/stable_mir/mod.rs @@ -15,7 +15,7 @@ use std::cell::Cell; use crate::rustc_smir::Tables; -use self::ty::{Ty, TyKind}; +use self::ty::{ImplDef, ImplTrait, TraitDecl, TraitDef, Ty, TyKind}; pub mod mir; pub mod ty; @@ -32,6 +32,12 @@ pub type DefId = usize; /// A list of crate items. pub type CrateItems = Vec; +/// A list of trait decls. +pub type TraitDecls = Vec; + +/// A list of impl trait decls. +pub type ImplTraitDecls = Vec; + /// Holds information about a crate. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Crate { @@ -79,11 +85,31 @@ pub fn all_local_items() -> CrateItems { with(|cx| cx.all_local_items()) } +pub fn all_trait_decls() -> TraitDecls { + with(|cx| cx.all_trait_decls()) +} + +pub fn trait_decl(trait_def: &TraitDef) -> TraitDecl { + with(|cx| cx.trait_decl(trait_def)) +} + +pub fn all_trait_impls() -> ImplTraitDecls { + with(|cx| cx.all_trait_impls()) +} + +pub fn trait_impl(trait_impl: &ImplDef) -> ImplTrait { + with(|cx| cx.trait_impl(trait_impl)) +} + pub trait Context { fn entry_fn(&mut self) -> Option; /// Retrieve all items of the local crate that have a MIR associated with them. fn all_local_items(&mut self) -> CrateItems; fn mir_body(&mut self, item: &CrateItem) -> mir::Body; + fn all_trait_decls(&mut self) -> TraitDecls; + fn trait_decl(&mut self, trait_def: &TraitDef) -> TraitDecl; + fn all_trait_impls(&mut self) -> ImplTraitDecls; + fn trait_impl(&mut self, trait_impl: &ImplDef) -> ImplTrait; /// Get information about the local crate. fn local_crate(&self) -> Crate; /// Retrieve a list of all external crates. @@ -100,18 +126,17 @@ pub trait Context { fn rustc_tables(&mut self, f: &mut dyn FnMut(&mut Tables<'_>)); } -thread_local! { - /// A thread local variable that stores a pointer to the tables mapping between TyCtxt - /// datastructures and stable MIR datastructures. - static TLV: Cell<*mut ()> = const { Cell::new(std::ptr::null_mut()) }; -} +// A thread local variable that stores a pointer to the tables mapping between TyCtxt +// datastructures and stable MIR datastructures +scoped_thread_local! (static TLV: Cell<*mut ()>); pub fn run(mut context: impl Context, f: impl FnOnce()) { - assert!(TLV.get().is_null()); + assert!(!TLV.is_set()); fn g<'a>(mut context: &mut (dyn Context + 'a), f: impl FnOnce()) { - TLV.set(&mut context as *mut &mut _ as _); - f(); - TLV.replace(std::ptr::null_mut()); + let ptr: *mut () = &mut context as *mut &mut _ as _; + TLV.set(&Cell::new(ptr), || { + f(); + }); } g(&mut context, f); } @@ -119,9 +144,10 @@ pub fn run(mut context: impl Context, f: impl FnOnce()) { /// Loads the current context and calls a function with it. /// Do not nest these, as that will ICE. pub(crate) fn with(f: impl FnOnce(&mut dyn Context) -> R) -> R { - let ptr = TLV.replace(std::ptr::null_mut()) as *mut &mut dyn Context; - assert!(!ptr.is_null()); - let ret = f(unsafe { *ptr }); - TLV.set(ptr as _); - ret + assert!(TLV.is_set()); + TLV.with(|tlv| { + let ptr = tlv.get(); + assert!(!ptr.is_null()); + f(unsafe { *(ptr as *mut &mut dyn Context) }) + }) } diff --git a/compiler/rustc_smir/src/stable_mir/ty.rs b/compiler/rustc_smir/src/stable_mir/ty.rs index f27801b0f6cae..7a6601f09da43 100644 --- a/compiler/rustc_smir/src/stable_mir/ty.rs +++ b/compiler/rustc_smir/src/stable_mir/ty.rs @@ -1,4 +1,10 @@ -use super::with; +use rustc_middle::mir::interpret::{alloc_range, AllocRange, ConstValue, Pointer}; + +use super::{mir::Mutability, mir::Safety, with, DefId}; +use crate::{ + rustc_internal::{opaque, Opaque}, + rustc_smir::{Stable, Tables}, +}; #[derive(Copy, Clone, Debug)] pub struct Ty(pub usize); @@ -9,7 +15,449 @@ impl Ty { } } +#[derive(Debug, Clone)] +pub struct Const { + pub literal: ConstantKind, +} + +type Ident = Opaque; +pub(crate) type Region = Opaque; +type Span = Opaque; + +#[derive(Clone, Debug)] pub enum TyKind { + RigidTy(RigidTy), + Alias(AliasKind, AliasTy), + Param(ParamTy), + Bound(usize, BoundTy), +} + +#[derive(Clone, Debug)] +pub enum RigidTy { Bool, + Char, + Int(IntTy), + Uint(UintTy), + Float(FloatTy), + Adt(AdtDef, GenericArgs), + Foreign(ForeignDef), + Str, + Array(Ty, Const), + Slice(Ty), + RawPtr(Ty, Mutability), + Ref(Region, Ty, Mutability), + FnDef(FnDef, GenericArgs), + FnPtr(PolyFnSig), + Closure(ClosureDef, GenericArgs), + Generator(GeneratorDef, GenericArgs, Movability), + Dynamic(Vec>, Region, DynKind), + Never, Tuple(Vec), } + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum IntTy { + Isize, + I8, + I16, + I32, + I64, + I128, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UintTy { + Usize, + U8, + U16, + U32, + U64, + U128, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FloatTy { + F32, + F64, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Movability { + Static, + Movable, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ForeignDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FnDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ClosureDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct GeneratorDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ParamDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct BrNamedDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AdtDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AliasDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct TraitDef(pub(crate) DefId); + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ConstDef(pub(crate) DefId); + +impl TraitDef { + pub fn trait_decl(&self) -> TraitDecl { + with(|cx| cx.trait_decl(self)) + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ImplDef(pub(crate) DefId); + +impl ImplDef { + pub fn trait_impl(&self) -> ImplTrait { + with(|cx| cx.trait_impl(self)) + } +} + +#[derive(Clone, Debug)] +pub struct GenericArgs(pub Vec); + +#[derive(Clone, Debug)] +pub enum GenericArgKind { + Lifetime(Region), + Type(Ty), + Const(Const), +} + +#[derive(Clone, Debug)] +pub enum TermKind { + Type(Ty), + Const(Const), +} + +#[derive(Clone, Debug)] +pub enum AliasKind { + Projection, + Inherent, + Opaque, + Weak, +} + +#[derive(Clone, Debug)] +pub struct AliasTy { + pub def_id: AliasDef, + pub args: GenericArgs, +} + +pub type PolyFnSig = Binder; + +#[derive(Clone, Debug)] +pub struct FnSig { + pub inputs_and_output: Vec, + pub c_variadic: bool, + pub unsafety: Safety, + pub abi: Abi, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Abi { + Rust, + C { unwind: bool }, + Cdecl { unwind: bool }, + Stdcall { unwind: bool }, + Fastcall { unwind: bool }, + Vectorcall { unwind: bool }, + Thiscall { unwind: bool }, + Aapcs { unwind: bool }, + Win64 { unwind: bool }, + SysV64 { unwind: bool }, + PtxKernel, + Msp430Interrupt, + X86Interrupt, + AmdGpuKernel, + EfiApi, + AvrInterrupt, + AvrNonBlockingInterrupt, + CCmseNonSecureCall, + Wasm, + System { unwind: bool }, + RustIntrinsic, + RustCall, + PlatformIntrinsic, + Unadjusted, + RustCold, + RiscvInterruptM, + RiscvInterruptS, +} + +#[derive(Clone, Debug)] +pub struct Binder { + pub value: T, + pub bound_vars: Vec, +} + +#[derive(Clone, Debug)] +pub struct EarlyBinder { + pub value: T, +} + +#[derive(Clone, Debug)] +pub enum BoundVariableKind { + Ty(BoundTyKind), + Region(BoundRegionKind), + Const, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum BoundTyKind { + Anon, + Param(ParamDef, String), +} + +#[derive(Clone, Debug)] +pub enum BoundRegionKind { + BrAnon(Option), + BrNamed(BrNamedDef, String), + BrEnv, +} + +#[derive(Clone, Debug)] +pub enum DynKind { + Dyn, + DynStar, +} + +#[derive(Clone, Debug)] +pub enum ExistentialPredicate { + Trait(ExistentialTraitRef), + Projection(ExistentialProjection), + AutoTrait(TraitDef), +} + +#[derive(Clone, Debug)] +pub struct ExistentialTraitRef { + pub def_id: TraitDef, + pub generic_args: GenericArgs, +} + +#[derive(Clone, Debug)] +pub struct ExistentialProjection { + pub def_id: TraitDef, + pub generic_args: GenericArgs, + pub term: TermKind, +} + +#[derive(Clone, Debug)] +pub struct ParamTy { + pub index: u32, + pub name: String, +} + +#[derive(Clone, Debug)] +pub struct BoundTy { + pub var: usize, + pub kind: BoundTyKind, +} + +pub type Bytes = Vec>; +pub type Size = usize; +pub type Prov = Opaque; +pub type Align = u64; +pub type Promoted = u32; +pub type InitMaskMaterialized = Vec; + +/// Stores the provenance information of pointers stored in memory. +#[derive(Clone, Debug)] +pub struct ProvenanceMap { + /// Provenance in this map applies from the given offset for an entire pointer-size worth of + /// bytes. Two entries in this map are always at least a pointer size apart. + pub ptrs: Vec<(Size, Prov)>, +} + +#[derive(Clone, Debug)] +pub struct Allocation { + pub bytes: Bytes, + pub provenance: ProvenanceMap, + pub align: Align, + pub mutability: Mutability, +} + +impl Allocation { + /// Creates new empty `Allocation` from given `Align`. + fn new_empty_allocation(align: rustc_target::abi::Align) -> Allocation { + Allocation { + bytes: Vec::new(), + provenance: ProvenanceMap { ptrs: Vec::new() }, + align: align.bytes(), + mutability: Mutability::Not, + } + } +} + +// We need this method instead of a Stable implementation +// because we need to get `Ty` of the const we are trying to create, to do that +// we need to have access to `ConstantKind` but we can't access that inside Stable impl. +pub fn new_allocation<'tcx>( + const_kind: &rustc_middle::mir::ConstantKind<'tcx>, + const_value: ConstValue<'tcx>, + tables: &mut Tables<'tcx>, +) -> Allocation { + match const_value { + ConstValue::Scalar(scalar) => { + let size = scalar.size(); + let align = tables + .tcx + .layout_of(rustc_middle::ty::ParamEnv::reveal_all().and(const_kind.ty())) + .unwrap() + .align; + let mut allocation = rustc_middle::mir::interpret::Allocation::uninit(size, align.abi); + allocation + .write_scalar(&tables.tcx, alloc_range(rustc_target::abi::Size::ZERO, size), scalar) + .unwrap(); + allocation.stable(tables) + } + ConstValue::ZeroSized => { + let align = tables + .tcx + .layout_of(rustc_middle::ty::ParamEnv::empty().and(const_kind.ty())) + .unwrap() + .align; + Allocation::new_empty_allocation(align.abi) + } + ConstValue::Slice { data, start, end } => { + let alloc_id = tables.tcx.create_memory_alloc(data); + let ptr = Pointer::new(alloc_id, rustc_target::abi::Size::from_bytes(start)); + let scalar_ptr = rustc_middle::mir::interpret::Scalar::from_pointer(ptr, &tables.tcx); + let scalar_len = rustc_middle::mir::interpret::Scalar::from_target_usize( + (end - start) as u64, + &tables.tcx, + ); + let layout = tables + .tcx + .layout_of(rustc_middle::ty::ParamEnv::reveal_all().and(const_kind.ty())) + .unwrap(); + let mut allocation = + rustc_middle::mir::interpret::Allocation::uninit(layout.size, layout.align.abi); + allocation + .write_scalar( + &tables.tcx, + alloc_range(rustc_target::abi::Size::ZERO, tables.tcx.data_layout.pointer_size), + scalar_ptr, + ) + .unwrap(); + allocation + .write_scalar( + &tables.tcx, + alloc_range(tables.tcx.data_layout.pointer_size, scalar_len.size()), + scalar_len, + ) + .unwrap(); + allocation.stable(tables) + } + ConstValue::ByRef { alloc, offset } => { + let ty_size = tables + .tcx + .layout_of(rustc_middle::ty::ParamEnv::reveal_all().and(const_kind.ty())) + .unwrap() + .size; + allocation_filter(&alloc.0, alloc_range(offset, ty_size), tables) + } + } +} + +/// Creates an `Allocation` only from information within the `AllocRange`. +pub fn allocation_filter<'tcx>( + alloc: &rustc_middle::mir::interpret::Allocation, + alloc_range: AllocRange, + tables: &mut Tables<'tcx>, +) -> Allocation { + let mut bytes: Vec> = alloc + .inspect_with_uninit_and_ptr_outside_interpreter( + alloc_range.start.bytes_usize()..alloc_range.end().bytes_usize(), + ) + .iter() + .copied() + .map(Some) + .collect(); + for (i, b) in bytes.iter_mut().enumerate() { + if !alloc + .init_mask() + .get(rustc_target::abi::Size::from_bytes(i + alloc_range.start.bytes_usize())) + { + *b = None; + } + } + let mut ptrs = Vec::new(); + for (offset, prov) in alloc + .provenance() + .ptrs() + .iter() + .filter(|a| a.0 >= alloc_range.start && a.0 <= alloc_range.end()) + { + ptrs.push((offset.bytes_usize() - alloc_range.start.bytes_usize(), opaque(prov))); + } + Allocation { + bytes: bytes, + provenance: ProvenanceMap { ptrs }, + align: alloc.align.bytes(), + mutability: alloc.mutability.stable(tables), + } +} + +#[derive(Clone, Debug)] +pub enum ConstantKind { + Allocated(Allocation), + Unevaluated(UnevaluatedConst), + ParamCt(Opaque), +} + +#[derive(Clone, Debug)] +pub struct UnevaluatedConst { + pub ty: Ty, + pub def: ConstDef, + pub args: GenericArgs, + pub promoted: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum TraitSpecializationKind { + None, + Marker, + AlwaysApplicable, +} + +#[derive(Clone, Debug)] +pub struct TraitDecl { + pub def_id: TraitDef, + pub unsafety: Safety, + pub paren_sugar: bool, + pub has_auto_impl: bool, + pub is_marker: bool, + pub is_coinductive: bool, + pub skip_array_during_method_dispatch: bool, + pub specialization_kind: TraitSpecializationKind, + pub must_implement_one_of: Option>, + pub implement_via_object: bool, + pub deny_explicit_impl: bool, +} + +pub type ImplTrait = EarlyBinder; + +#[derive(Clone, Debug)] +pub struct TraitRef { + pub def_id: TraitDef, + pub args: GenericArgs, +} diff --git a/compiler/rustc_span/Cargo.toml b/compiler/rustc_span/Cargo.toml index a7c7575f392e6..ee93f74e750df 100644 --- a/compiler/rustc_span/Cargo.toml +++ b/compiler/rustc_span/Cargo.toml @@ -18,4 +18,4 @@ tracing = "0.1" sha1 = "0.10.0" sha2 = "0.10.1" md5 = { package = "md-5", version = "0.10.0" } -indexmap = { version = "1.9.3" } +indexmap = { version = "2.0.0" } diff --git a/compiler/rustc_span/src/def_id.rs b/compiler/rustc_span/src/def_id.rs index f65a6aa4fb21a..595babc26ae68 100644 --- a/compiler/rustc_span/src/def_id.rs +++ b/compiler/rustc_span/src/def_id.rs @@ -28,10 +28,16 @@ impl CrateNum { CrateNum::from_usize(x) } + // FIXME(typed_def_id): Replace this with `as_mod_def_id`. #[inline] pub fn as_def_id(self) -> DefId { DefId { krate: self, index: CRATE_DEF_INDEX } } + + #[inline] + pub fn as_mod_def_id(self) -> ModDefId { + ModDefId::new_unchecked(DefId { krate: self, index: CRATE_DEF_INDEX }) + } } impl fmt::Display for CrateNum { @@ -485,3 +491,92 @@ impl ToStableHashKey for CrateNum { self.as_def_id().to_stable_hash_key(hcx) } } + +macro_rules! typed_def_id { + ($Name:ident, $LocalName:ident) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encodable, Decodable, HashStable_Generic)] + pub struct $Name(DefId); + + impl $Name { + pub const fn new_unchecked(def_id: DefId) -> Self { + Self(def_id) + } + + pub fn to_def_id(self) -> DefId { + self.into() + } + + pub fn is_local(self) -> bool { + self.0.is_local() + } + + pub fn as_local(self) -> Option<$LocalName> { + self.0.as_local().map($LocalName::new_unchecked) + } + } + + impl From<$LocalName> for $Name { + fn from(local: $LocalName) -> Self { + Self(local.0.to_def_id()) + } + } + + impl From<$Name> for DefId { + fn from(typed: $Name) -> Self { + typed.0 + } + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encodable, Decodable, HashStable_Generic)] + pub struct $LocalName(LocalDefId); + + impl !Ord for $LocalName {} + impl !PartialOrd for $LocalName {} + + impl $LocalName { + pub const fn new_unchecked(def_id: LocalDefId) -> Self { + Self(def_id) + } + + pub fn to_def_id(self) -> DefId { + self.0.into() + } + + pub fn to_local_def_id(self) -> LocalDefId { + self.0 + } + } + + impl From<$LocalName> for LocalDefId { + fn from(typed: $LocalName) -> Self { + typed.0 + } + } + + impl From<$LocalName> for DefId { + fn from(typed: $LocalName) -> Self { + typed.0.into() + } + } + }; +} + +// N.B.: when adding new typed `DefId`s update the corresponding trait impls in +// `rustc_middle::dep_graph::def_node` for `DepNodeParams`. +typed_def_id! { ModDefId, LocalModDefId } + +impl LocalModDefId { + pub const CRATE_DEF_ID: Self = Self::new_unchecked(CRATE_DEF_ID); +} + +impl ModDefId { + pub fn is_top_level_module(self) -> bool { + self.0.is_top_level_module() + } +} + +impl LocalModDefId { + pub fn is_top_level_module(self) -> bool { + self.0.is_top_level_module() + } +} diff --git a/compiler/rustc_span/src/edit_distance.rs b/compiler/rustc_span/src/edit_distance.rs index 259f423865480..96a118e590f39 100644 --- a/compiler/rustc_span/src/edit_distance.rs +++ b/compiler/rustc_span/src/edit_distance.rs @@ -238,8 +238,9 @@ fn find_best_match_for_name_impl( } fn find_match_by_sorted_words(iter_names: &[Symbol], lookup: &str) -> Option { + let lookup_sorted_by_words = sort_by_words(lookup); iter_names.iter().fold(None, |result, candidate| { - if sort_by_words(candidate.as_str()) == sort_by_words(lookup) { + if sort_by_words(candidate.as_str()) == lookup_sorted_by_words { Some(*candidate) } else { result @@ -247,9 +248,9 @@ fn find_match_by_sorted_words(iter_names: &[Symbol], lookup: &str) -> Option String { +fn sort_by_words(name: &str) -> Vec<&str> { let mut split_words: Vec<&str> = name.split('_').collect(); // We are sorting primitive &strs and can use unstable sort here. split_words.sort_unstable(); - split_words.join("_") + split_words } diff --git a/compiler/rustc_span/src/edition.rs b/compiler/rustc_span/src/edition.rs index f16db69aae232..608b8c24bde8b 100644 --- a/compiler/rustc_span/src/edition.rs +++ b/compiler/rustc_span/src/edition.rs @@ -82,17 +82,17 @@ impl Edition { } /// Are we allowed to use features from the Rust 2018 edition? - pub fn rust_2018(self) -> bool { + pub fn at_least_rust_2018(self) -> bool { self >= Edition::Edition2018 } /// Are we allowed to use features from the Rust 2021 edition? - pub fn rust_2021(self) -> bool { + pub fn at_least_rust_2021(self) -> bool { self >= Edition::Edition2021 } /// Are we allowed to use features from the Rust 2024 edition? - pub fn rust_2024(self) -> bool { + pub fn at_least_rust_2024(self) -> bool { self >= Edition::Edition2024 } } diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index c669b64dd2c81..9f2ff4378425e 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -320,7 +320,6 @@ impl ExpnId { // Stop going up the backtrace once include! is encountered if expn_data.is_root() || expn_data.kind == ExpnKind::Macro(MacroKind::Bang, sym::include) - || expn_data.kind == ExpnKind::Inlined { break; } @@ -508,7 +507,7 @@ impl HygieneData { self.normalize_to_macro_rules(call_site_ctxt) }; - if call_site_ctxt == SyntaxContext::root() { + if call_site_ctxt.is_root() { return self.apply_mark_internal(ctxt, expn_id, transparency); } @@ -672,12 +671,17 @@ impl SyntaxContext { } #[inline] - pub(crate) fn as_u32(self) -> u32 { + pub const fn is_root(self) -> bool { + self.0 == SyntaxContext::root().as_u32() + } + + #[inline] + pub(crate) const fn as_u32(self) -> u32 { self.0 } #[inline] - pub(crate) fn from_u32(raw: u32) -> SyntaxContext { + pub(crate) const fn from_u32(raw: u32) -> SyntaxContext { SyntaxContext(raw) } @@ -1058,8 +1062,6 @@ pub enum ExpnKind { AstPass(AstPass), /// Desugaring done by the compiler during HIR lowering. Desugaring(DesugaringKind), - /// MIR inlining - Inlined, } impl ExpnKind { @@ -1073,7 +1075,6 @@ impl ExpnKind { }, ExpnKind::AstPass(kind) => kind.descr().to_string(), ExpnKind::Desugaring(kind) => format!("desugaring of {}", kind.descr()), - ExpnKind::Inlined => "inlined source".to_string(), } } } @@ -1151,7 +1152,6 @@ pub enum DesugaringKind { Await, ForLoop, WhileLoop, - Replace, } impl DesugaringKind { @@ -1167,7 +1167,6 @@ impl DesugaringKind { DesugaringKind::OpaqueTy => "`impl Trait`", DesugaringKind::ForLoop => "`for` loop", DesugaringKind::WhileLoop => "`while` loop", - DesugaringKind::Replace => "drop and replace", } } } @@ -1294,7 +1293,7 @@ pub fn decode_expn_id( decode_data: impl FnOnce(ExpnId) -> (ExpnData, ExpnHash), ) -> ExpnId { if index == 0 { - debug!("decode_expn_id: deserialized root"); + trace!("decode_expn_id: deserialized root"); return ExpnId::root(); } @@ -1327,7 +1326,7 @@ pub fn decode_syntax_context SyntaxContext ) -> SyntaxContext { let raw_id: u32 = Decodable::decode(d); if raw_id == 0 { - debug!("decode_syntax_context: deserialized root"); + trace!("decode_syntax_context: deserialized root"); // The root is special return SyntaxContext::root(); } @@ -1506,7 +1505,7 @@ impl HashStable for SyntaxContext { const TAG_EXPANSION: u8 = 0; const TAG_NO_EXPANSION: u8 = 1; - if *self == SyntaxContext::root() { + if self.is_root() { TAG_NO_EXPANSION.hash_stable(ctx, hasher); } else { TAG_EXPANSION.hash_stable(ctx, hasher); diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 3d3833dfdab14..c24b8d9ec1709 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -23,6 +23,7 @@ #![feature(round_char_boundary)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] #[macro_use] extern crate rustc_macros; @@ -65,11 +66,11 @@ use rustc_data_structures::sync::{Lock, Lrc}; use std::borrow::Cow; use std::cmp::{self, Ordering}; -use std::fmt; use std::hash::Hash; use std::ops::{Add, Range, Sub}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::{fmt, iter}; use md5::Digest; use md5::Md5; @@ -594,12 +595,6 @@ impl Span { matches!(outer_expn.kind, ExpnKind::Macro(..)) && outer_expn.collapse_debuginfo } - /// Returns `true` if this span comes from MIR inlining. - pub fn is_inlined(self) -> bool { - let outer_expn = self.ctxt().outer_expn_data(); - matches!(outer_expn.kind, ExpnKind::Inlined) - } - /// Returns `true` if `span` originates in a derive-macro's expansion. pub fn in_derive_expansion(self) -> bool { matches!(self.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Derive, _)) @@ -611,7 +606,7 @@ impl Span { // FIXME: If this span comes from a `derive` macro but it points at code the user wrote, // the callsite span and the span will be pointing at different places. It also means that // we can safely provide suggestions on this span. - || (matches!(self.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Derive, _)) + || (self.in_derive_expansion() && self.parent_callsite().map(|p| (p.lo(), p.hi())) != Some((self.lo(), self.hi()))) } @@ -691,6 +686,12 @@ impl Span { } /// Walk down the expansion ancestors to find a span that's contained within `outer`. + /// + /// The span returned by this method may have a different [`SyntaxContext`] as `outer`. + /// If you need to extend the span, use [`find_ancestor_inside_same_ctxt`] instead, + /// because joining spans with different syntax contexts can create unexpected results. + /// + /// [`find_ancestor_inside_same_ctxt`]: Self::find_ancestor_inside_same_ctxt pub fn find_ancestor_inside(mut self, outer: Span) -> Option { while !outer.contains(self) { self = self.parent_callsite()?; @@ -698,11 +699,34 @@ impl Span { Some(self) } - /// Like `find_ancestor_inside`, but specifically for when spans might not - /// overlaps. Take care when using this, and prefer `find_ancestor_inside` - /// when you know that the spans are nested (modulo macro expansion). + /// Walk down the expansion ancestors to find a span with the same [`SyntaxContext`] as + /// `other`. + /// + /// Like [`find_ancestor_inside_same_ctxt`], but specifically for when spans might not + /// overlap. Take care when using this, and prefer [`find_ancestor_inside`] or + /// [`find_ancestor_inside_same_ctxt`] when you know that the spans are nested (modulo + /// macro expansion). + /// + /// [`find_ancestor_inside`]: Self::find_ancestor_inside + /// [`find_ancestor_inside_same_ctxt`]: Self::find_ancestor_inside_same_ctxt pub fn find_ancestor_in_same_ctxt(mut self, other: Span) -> Option { - while !Span::eq_ctxt(self, other) { + while !self.eq_ctxt(other) { + self = self.parent_callsite()?; + } + Some(self) + } + + /// Walk down the expansion ancestors to find a span that's contained within `outer` and + /// has the same [`SyntaxContext`] as `outer`. + /// + /// This method is the combination of [`find_ancestor_inside`] and + /// [`find_ancestor_in_same_ctxt`] and should be preferred when extending the returned span. + /// If you do not need to modify the span, use [`find_ancestor_inside`] instead. + /// + /// [`find_ancestor_inside`]: Self::find_ancestor_inside + /// [`find_ancestor_in_same_ctxt`]: Self::find_ancestor_in_same_ctxt + pub fn find_ancestor_inside_same_ctxt(mut self, outer: Span) -> Option { + while !outer.contains(self) || !self.eq_ctxt(outer) { self = self.parent_callsite()?; } Some(self) @@ -713,24 +737,28 @@ impl Span { self.ctxt().edition() } + /// Is this edition 2015? #[inline] pub fn is_rust_2015(self) -> bool { self.edition().is_rust_2015() } + /// Are we allowed to use features from the Rust 2018 edition? #[inline] - pub fn rust_2018(self) -> bool { - self.edition().rust_2018() + pub fn at_least_rust_2018(self) -> bool { + self.edition().at_least_rust_2018() } + /// Are we allowed to use features from the Rust 2021 edition? #[inline] - pub fn rust_2021(self) -> bool { - self.edition().rust_2021() + pub fn at_least_rust_2021(self) -> bool { + self.edition().at_least_rust_2021() } + /// Are we allowed to use features from the Rust 2024 edition? #[inline] - pub fn rust_2024(self) -> bool { - self.edition().rust_2024() + pub fn at_least_rust_2024(self) -> bool { + self.edition().at_least_rust_2024() } /// Returns the source callee. @@ -739,12 +767,15 @@ impl Span { /// else returns the `ExpnData` for the macro definition /// corresponding to the source callsite. pub fn source_callee(self) -> Option { - fn source_callee(expn_data: ExpnData) -> ExpnData { - let next_expn_data = expn_data.call_site.ctxt().outer_expn_data(); - if !next_expn_data.is_root() { source_callee(next_expn_data) } else { expn_data } - } let expn_data = self.ctxt().outer_expn_data(); - if !expn_data.is_root() { Some(source_callee(expn_data)) } else { None } + + // Create an iterator of call site expansions + iter::successors(Some(expn_data), |expn_data| { + Some(expn_data.call_site.ctxt().outer_expn_data()) + }) + // Find the last expansion which is not root + .take_while(|expn_data| !expn_data.is_root()) + .last() } /// Checks if a span is "internal" to a macro in which `#[unstable]` @@ -783,7 +814,7 @@ impl Span { pub fn macro_backtrace(mut self) -> impl Iterator { let mut prev_span = DUMMY_SP; - std::iter::from_fn(move || { + iter::from_fn(move || { loop { let expn_data = self.ctxt().outer_expn_data(); if expn_data.is_root() { @@ -832,9 +863,9 @@ impl Span { // Return the macro span on its own to avoid weird diagnostic output. It is preferable to // have an incomplete span than a completely nonsensical one. if span_data.ctxt != end_data.ctxt { - if span_data.ctxt == SyntaxContext::root() { + if span_data.ctxt.is_root() { return end; - } else if end_data.ctxt == SyntaxContext::root() { + } else if end_data.ctxt.is_root() { return self; } // Both spans fall within a macro. @@ -843,7 +874,7 @@ impl Span { Span::new( cmp::min(span_data.lo, end_data.lo), cmp::max(span_data.hi, end_data.hi), - if span_data.ctxt == SyntaxContext::root() { end_data.ctxt } else { span_data.ctxt }, + if span_data.ctxt.is_root() { end_data.ctxt } else { span_data.ctxt }, if span_data.parent == end_data.parent { span_data.parent } else { None }, ) } @@ -861,7 +892,7 @@ impl Span { Span::new( span.hi, end.lo, - if end.ctxt == SyntaxContext::root() { end.ctxt } else { span.ctxt }, + if end.ctxt.is_root() { end.ctxt } else { span.ctxt }, if span.parent == end.parent { span.parent } else { None }, ) } @@ -885,9 +916,9 @@ impl Span { // Return the macro span on its own to avoid weird diagnostic output. It is preferable to // have an incomplete span than a completely nonsensical one. if span_data.ctxt != end_data.ctxt { - if span_data.ctxt == SyntaxContext::root() { + if span_data.ctxt.is_root() { return end; - } else if end_data.ctxt == SyntaxContext::root() { + } else if end_data.ctxt.is_root() { return self; } // Both spans fall within a macro. @@ -896,7 +927,7 @@ impl Span { Span::new( span_data.lo, end_data.lo, - if end_data.ctxt == SyntaxContext::root() { end_data.ctxt } else { span_data.ctxt }, + if end_data.ctxt.is_root() { end_data.ctxt } else { span_data.ctxt }, if span_data.parent == end_data.parent { span_data.parent } else { None }, ) } @@ -2162,7 +2193,8 @@ where // If this is not an empty or invalid span, we want to hash the last // position that belongs to it, as opposed to hashing the first // position past it. - let Some((file, line_lo, col_lo, line_hi, col_hi)) = ctx.span_data_to_lines_and_cols(&span) else { + let Some((file, line_lo, col_lo, line_hi, col_hi)) = ctx.span_data_to_lines_and_cols(&span) + else { Hash::hash(&TAG_INVALID_SPAN, hasher); return; }; diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 1824510a9742e..983b2ab04a4ae 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -744,6 +744,21 @@ impl SourceMap { }) } + /// Extends the given `Span` to previous character while the previous character matches the predicate + pub fn span_extend_prev_while( + &self, + span: Span, + f: impl Fn(char) -> bool, + ) -> Result { + self.span_to_source(span, |s, start, _end| { + let n = s[..start] + .char_indices() + .rfind(|&(_, c)| !f(c)) + .map_or(start, |(i, _)| start - i - 1); + Ok(span.with_lo(span.lo() - BytePos(n as u32))) + }) + } + /// Extends the given `Span` to just before the next occurrence of `c`. pub fn span_extend_to_next_char(&self, sp: Span, c: char, accept_newlines: bool) -> Span { if let Ok(next_source) = self.span_to_next_source(sp) { @@ -958,24 +973,21 @@ impl SourceMap { Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt(), None) } - /// Returns a new span to check next none-whitespace character or some specified expected character - /// If `expect` is none, the first span of non-whitespace character is returned. - /// If `expect` presented, the first span of the character `expect` is returned - /// Otherwise, the span reached to limit is returned. - pub fn span_look_ahead(&self, span: Span, expect: Option<&str>, limit: Option) -> Span { + /// Check whether span is followed by some specified expected string in limit scope + pub fn span_look_ahead(&self, span: Span, expect: &str, limit: Option) -> Option { let mut sp = span; for _ in 0..limit.unwrap_or(100_usize) { sp = self.next_point(sp); if let Ok(ref snippet) = self.span_to_snippet(sp) { - if expect.is_some_and(|es| snippet == es) { - break; + if snippet == expect { + return Some(sp); } - if expect.is_none() && snippet.chars().any(|c| !c.is_whitespace()) { + if snippet.chars().any(|c| !c.is_whitespace()) { break; } } } - sp + None } /// Finds the width of the character, either before or after the end of provided span, @@ -1057,11 +1069,7 @@ impl SourceMap { /// This index is guaranteed to be valid for the lifetime of this `SourceMap`, /// since `source_files` is a `MonotonicVec` pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize { - self.files - .borrow() - .source_files - .binary_search_by_key(&pos, |key| key.start_pos) - .unwrap_or_else(|p| p - 1) + self.files.borrow().source_files.partition_point(|x| x.start_pos <= pos) - 1 } pub fn count_lines(&self) -> usize { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 874d578fe1db3..28a2dfebcfe96 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -326,6 +326,7 @@ symbols! { abi_efiapi, abi_msp430_interrupt, abi_ptx, + abi_riscv_interrupt, abi_sysv64, abi_thiscall, abi_unadjusted, @@ -372,6 +373,7 @@ symbols! { arm_target_feature, array, arrays, + as_mut_ptr, as_ptr, as_ref, as_str, @@ -399,6 +401,7 @@ symbols! { async_await, async_closure, async_fn_in_trait, + async_fn_track_caller, atomic, atomic_mod, atomics, @@ -432,7 +435,6 @@ symbols! { bool, borrowck_graphviz_format, borrowck_graphviz_postflow, - box_free, box_new, box_patterns, box_syntax, @@ -443,6 +445,7 @@ symbols! { bridge, bswap, builtin_syntax, + c, c_str, c_str_literals, c_unwind, @@ -466,6 +469,7 @@ symbols! { cfg_hide, cfg_overflow_checks, cfg_panic, + cfg_relocation_model, cfg_sanitize, cfg_target_abi, cfg_target_compact, @@ -497,6 +501,7 @@ symbols! { cold, collapse_debuginfo, column, + compare_bytes, compare_exchange, compare_exchange_weak, compile_error, @@ -540,6 +545,7 @@ symbols! { const_panic_fmt, const_param_ty, const_precise_live_drops, + const_ptr_cast, const_raw_ptr_deref, const_raw_ptr_to_usize_cast, const_refs_to_cell, @@ -571,6 +577,7 @@ symbols! { crate_type, crate_visibility_modifier, crt_dash_static: "crt-static", + csky_target_feature, cstring_type, ctlz, ctlz_nonzero, @@ -620,6 +627,7 @@ symbols! { destruct, destructuring_assignment, diagnostic, + diagnostic_namespace, direct, discriminant_kind, discriminant_type, @@ -655,8 +663,10 @@ symbols! { dyn_metadata, dyn_star, dyn_trait, + dynamic_no_pic: "dynamic-no-pic", e, edition_panic, + effects, eh_catch_typeinfo, eh_personality, emit, @@ -687,6 +697,7 @@ symbols! { expf32, expf64, explicit_generic_args_with_impl_trait, + explicit_tail_calls, export_name, expr, extended_key_value_attributes, @@ -701,7 +712,9 @@ symbols! { f, f16c_target_feature, f32, + f32_nan, f64, + f64_nan, fabsf32, fabsf64, fadd_fast, @@ -756,7 +769,6 @@ symbols! { from_desugaring, from_fn, from_iter, - from_method, from_output, from_residual, from_size_align_unchecked, @@ -779,6 +791,7 @@ symbols! { generic_associated_types, generic_associated_types_extended, generic_const_exprs, + generic_const_items, generic_param_attrs, get_context, global_allocator, @@ -791,6 +804,7 @@ symbols! { hexagon_target_feature, hidden, homogeneous_aggregate, + host, html_favicon_url, html_logo_url, html_no_source, @@ -815,6 +829,7 @@ symbols! { impl_trait_in_bindings, impl_trait_in_fn_trait_return, impl_trait_projections, + implement_via_object, implied_by, import, import_name_type, @@ -854,6 +869,7 @@ symbols! { item, item_like_imports, iter, + iter_mut, iter_repeat, iterator_collect_fn, kcfi, @@ -868,6 +884,7 @@ symbols! { large_assignments, lateout, lazy_normalization_consts, + lazy_type_alias, le, len, let_chains, @@ -1101,6 +1118,8 @@ symbols! { path, pattern_parentheses, phantom_data, + pic, + pie, pin, platform_intrinsics, plugin, @@ -1146,12 +1165,20 @@ symbols! { profiler_builtins, profiler_runtime, ptr, + ptr_cast, + ptr_cast_const, + ptr_cast_mut, + ptr_const_is_null, + ptr_from_mut, + ptr_from_ref, ptr_guaranteed_cmp, + ptr_is_null, ptr_mask, ptr_null, ptr_null_mut, ptr_offset_from, ptr_offset_from_unsigned, + ptr_unique, pub_macro_rules, pub_restricted, public, @@ -1201,6 +1228,7 @@ symbols! { register_tool, relaxed_adts, relaxed_struct_unsize, + relocation_model, rem, rem_assign, repr, @@ -1221,6 +1249,8 @@ symbols! { rintf64, riscv_target_feature, rlib, + ropi, + ropi_rwpi: "ropi-rwpi", rotate_left, rotate_right, roundevenf32, @@ -1255,6 +1285,7 @@ symbols! { rustc_clean, rustc_coherence_is_core, rustc_coinductive, + rustc_confusables, rustc_const_stable, rustc_const_unstable, rustc_conversion_suggestion, @@ -1270,13 +1301,14 @@ symbols! { rustc_dummy, rustc_dump_env_program_clauses, rustc_dump_program_clauses, - rustc_dump_user_substs, + rustc_dump_user_args, rustc_dump_vtable, rustc_effective_visibility, rustc_error, rustc_evaluate_where_clauses, rustc_expected_cgu_reuse, rustc_has_incoherent_inherent_impls, + rustc_host, rustc_if_this_changed, rustc_inherit_overflow_checks, rustc_insignificant_dtor, @@ -1330,6 +1362,7 @@ symbols! { rustdoc_missing_doc_code_examples, rustfmt, rvalue_static_promotion, + rwpi, s, safety, sanitize, @@ -1355,9 +1388,13 @@ symbols! { simd_arith_offset, simd_as, simd_bitmask, + simd_bitreverse, + simd_bswap, simd_cast, simd_cast_ptr, simd_ceil, + simd_ctlz, + simd_cttz, simd_div, simd_eq, simd_expose_addr, @@ -1454,6 +1491,10 @@ symbols! { stop_after_dataflow, store, str, + str_from_utf8, + str_from_utf8_mut, + str_from_utf8_unchecked, + str_from_utf8_unchecked_mut, str_split_whitespace, str_trim, str_trim_end, @@ -1547,6 +1588,7 @@ symbols! { type_length_limit, type_macros, type_name, + type_privacy_lints, u128, u16, u32, diff --git a/compiler/rustc_symbol_mangling/src/legacy.rs b/compiler/rustc_symbol_mangling/src/legacy.rs index 254ede4e6a00a..3a33568084c41 100644 --- a/compiler/rustc_symbol_mangling/src/legacy.rs +++ b/compiler/rustc_symbol_mangling/src/legacy.rs @@ -2,8 +2,8 @@ use rustc_data_structures::stable_hasher::{Hash64, HashStable, StableHasher}; use rustc_hir::def_id::CrateNum; use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; use rustc_middle::ty::print::{PrettyPrinter, Print, Printer}; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_middle::util::common::record_time; use std::fmt::{self, Write}; @@ -26,7 +26,7 @@ pub(super) fn mangle<'tcx>( let key = tcx.def_key(ty_def_id); match key.disambiguated_data.data { DefPathData::TypeNs(_) | DefPathData::ValueNs(_) => { - instance_ty = tcx.type_of(ty_def_id).subst_identity(); + instance_ty = tcx.type_of(ty_def_id).instantiate_identity(); debug!(?instance_ty); break; } @@ -58,7 +58,7 @@ pub(super) fn mangle<'tcx>( def_id, if let ty::InstanceDef::DropGlue(_, _) = instance.def { // Add the name of the dropped type to the symbol name - &*instance.substs + &*instance.args } else { &[] }, @@ -95,8 +95,8 @@ fn get_symbol_hash<'tcx>( instantiating_crate: Option, ) -> Hash64 { let def_id = instance.def_id(); - let substs = instance.substs; - debug!("get_symbol_hash(def_id={:?}, parameters={:?})", def_id, substs); + let args = instance.args; + debug!("get_symbol_hash(def_id={:?}, parameters={:?})", def_id, args); tcx.with_stable_hashing_context(|mut hcx| { let mut hasher = StableHasher::new(); @@ -122,7 +122,7 @@ fn get_symbol_hash<'tcx>( } // also include any type parameters (for generic items) - substs.hash_stable(hcx, &mut hasher); + args.hash_stable(hcx, &mut hasher); if let Some(instantiating_crate) = instantiating_crate { tcx.def_path_hash(instantiating_crate.as_def_id()) @@ -219,10 +219,10 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> { fn print_type(mut self, ty: Ty<'tcx>) -> Result { match *ty.kind() { // Print all nominal types as paths (unlike `pretty_print_type`). - ty::FnDef(def_id, substs) - | ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, substs, .. }) - | ty::Closure(def_id, substs) - | ty::Generator(def_id, substs, _) => self.print_def_path(def_id, substs), + ty::FnDef(def_id, args) + | ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) + | ty::Closure(def_id, args) + | ty::Generator(def_id, args, _) => self.print_def_path(def_id, args), // The `pretty_print_type` formatting of array size depends on // -Zverbose flag, so we cannot reuse it here. @@ -230,7 +230,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> { self.write_str("[")?; self = self.print_type(ty)?; self.write_str("; ")?; - if let Some(size) = size.kind().try_to_bits(self.tcx().data_layout.pointer_size) { + if let Some(size) = size.try_to_bits(self.tcx().data_layout.pointer_size) { write!(self, "{size}")? } else if let ty::ConstKind::Param(param) = size.kind() { self = param.print(self)? diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index 692542da78ea2..74538e9f5a3fc 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -108,7 +108,7 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::mir::mono::{InstantiationMode, MonoItem}; use rustc_middle::query::Providers; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Instance, TyCtxt}; use rustc_session::config::SymbolManglingVersion; @@ -144,7 +144,7 @@ fn symbol_name_provider<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> ty // This closure determines the instantiating crate for instances that // need an instantiating-crate-suffix for their symbol name, in order // to differentiate between local copies. - if is_generic(instance.substs) { + if is_generic(instance.args) { // For generics we might find re-usable upstream instances. If there // is one, we rely on the symbol being instantiated locally. instance.upstream_monomorphization(tcx).unwrap_or(LOCAL_CRATE) @@ -174,13 +174,13 @@ fn compute_symbol_name<'tcx>( compute_instantiating_crate: impl FnOnce() -> CrateNum, ) -> String { let def_id = instance.def_id(); - let substs = instance.substs; + let args = instance.args; - debug!("symbol_name(def_id={:?}, substs={:?})", def_id, substs); + debug!("symbol_name(def_id={:?}, args={:?})", def_id, args); if let Some(def_id) = def_id.as_local() { if tcx.proc_macro_decls_static(()) == Some(def_id) { - let stable_crate_id = tcx.sess.local_stable_crate_id(); + let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); return tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id); } } @@ -246,7 +246,7 @@ fn compute_symbol_name<'tcx>( // the ID of the instantiating crate. This avoids symbol conflicts // in case the same instances is emitted in two crates of the same // project. - let avoid_cross_crate_conflicts = is_generic(substs) || is_globally_shared_function; + let avoid_cross_crate_conflicts = is_generic(args) || is_globally_shared_function; let instantiating_crate = avoid_cross_crate_conflicts.then(compute_instantiating_crate); @@ -278,6 +278,6 @@ fn compute_symbol_name<'tcx>( symbol } -fn is_generic(substs: SubstsRef<'_>) -> bool { - substs.non_erasable_generics().next().is_some() +fn is_generic(args: GenericArgsRef<'_>) -> bool { + args.non_erasable_generics().next().is_some() } diff --git a/compiler/rustc_symbol_mangling/src/test.rs b/compiler/rustc_symbol_mangling/src/test.rs index 985b2210745f4..eddfd0df318e2 100644 --- a/compiler/rustc_symbol_mangling/src/test.rs +++ b/compiler/rustc_symbol_mangling/src/test.rs @@ -7,7 +7,7 @@ use crate::errors::{Kind, TestOutput}; use rustc_hir::def_id::LocalDefId; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{subst::InternalSubsts, Instance, TyCtxt}; +use rustc_middle::ty::{GenericArgs, Instance, TyCtxt}; use rustc_span::symbol::{sym, Symbol}; const SYMBOL_NAME: Symbol = sym::rustc_symbol_name; @@ -57,7 +57,7 @@ impl SymbolNamesTest<'_> { let def_id = def_id.to_def_id(); let instance = Instance::new( def_id, - tcx.erase_regions(InternalSubsts::identity_for_item(tcx, def_id)), + tcx.erase_regions(GenericArgs::identity_for_item(tcx, def_id)), ); let mangled = tcx.symbol_name(instance); tcx.sess.emit_err(TestOutput { diff --git a/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs b/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs index 9fa49123a868e..d345368d552b8 100644 --- a/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs +++ b/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs @@ -7,19 +7,19 @@ /// /// For more information about LLVM CFI and cross-language LLVM CFI support for the Rust compiler, /// see design document in the tracking issue #89653. -use core::fmt::Display; use rustc_data_structures::base_n; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::DiagnosticMessage; use rustc_hir as hir; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; +use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ self, Const, ExistentialPredicate, FloatTy, FnSig, Instance, IntTy, List, Region, RegionKind, TermKind, Ty, TyCtxt, UintTy, }; +use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgsRef}; use rustc_span::def_id::DefId; use rustc_span::sym; use rustc_target::abi::call::{Conv, FnAbi}; +use rustc_target::abi::Integer; use rustc_target::spec::abi::Abi; use std::fmt::Write as _; @@ -94,44 +94,54 @@ fn encode_const<'tcx>( dict: &mut FxHashMap, usize>, options: EncodeTyOptions, ) -> String { - // L[n]E as literal argument + // L[n][]E as literal argument let mut s = String::from('L'); - // Element type - s.push_str(&encode_ty(tcx, c.ty(), dict, options)); + match c.kind() { + // Const parameters + ty::ConstKind::Param(..) => { + // LE as literal argument - // The only allowed types of const parameters are bool, u8, u16, u32, u64, u128, usize i8, i16, - // i32, i64, i128, isize, and char. The bool value false is encoded as 0 and true as 1. - fn push_signed_value(s: &mut String, value: T, zero: T) { - if value < zero { - s.push('n') - }; - let _ = write!(s, "{value}"); - } - - fn push_unsigned_value(s: &mut String, value: T) { - let _ = write!(s, "{value}"); - } + // Element type + s.push_str(&encode_ty(tcx, c.ty(), dict, options)); + } - if let Some(scalar_int) = c.kind().try_to_scalar_int() { - let signed = c.ty().is_signed(); - match scalar_int.size().bits() { - 8 if signed => push_signed_value(&mut s, scalar_int.try_to_i8().unwrap(), 0), - 16 if signed => push_signed_value(&mut s, scalar_int.try_to_i16().unwrap(), 0), - 32 if signed => push_signed_value(&mut s, scalar_int.try_to_i32().unwrap(), 0), - 64 if signed => push_signed_value(&mut s, scalar_int.try_to_i64().unwrap(), 0), - 128 if signed => push_signed_value(&mut s, scalar_int.try_to_i128().unwrap(), 0), - 8 => push_unsigned_value(&mut s, scalar_int.try_to_u8().unwrap()), - 16 => push_unsigned_value(&mut s, scalar_int.try_to_u16().unwrap()), - 32 => push_unsigned_value(&mut s, scalar_int.try_to_u32().unwrap()), - 64 => push_unsigned_value(&mut s, scalar_int.try_to_u64().unwrap()), - 128 => push_unsigned_value(&mut s, scalar_int.try_to_u128().unwrap()), - _ => { - bug!("encode_const: unexpected size `{:?}`", scalar_int.size().bits()); + // Literal arguments + ty::ConstKind::Value(..) => { + // L[n]E as literal argument + + // Element type + s.push_str(&encode_ty(tcx, c.ty(), dict, options)); + + // The only allowed types of const values are bool, u8, u16, u32, + // u64, u128, usize i8, i16, i32, i64, i128, isize, and char. The + // bool value false is encoded as 0 and true as 1. + match c.ty().kind() { + ty::Int(ity) => { + let bits = c.eval_bits(tcx, ty::ParamEnv::reveal_all(), c.ty()); + let val = Integer::from_int_ty(&tcx, *ity).size().sign_extend(bits) as i128; + if val < 0 { + s.push('n'); + } + let _ = write!(s, "{val}"); + } + ty::Uint(_) => { + let val = c.eval_bits(tcx, ty::ParamEnv::reveal_all(), c.ty()); + let _ = write!(s, "{val}"); + } + ty::Bool => { + let val = c.try_eval_bool(tcx, ty::ParamEnv::reveal_all()).unwrap(); + let _ = write!(s, "{val}"); + } + _ => { + bug!("encode_const: unexpected type `{:?}`", c.ty()); + } } - }; - } else { - bug!("encode_const: unexpected type `{:?}`", c.ty()); + } + + _ => { + bug!("encode_const: unexpected kind `{:?}`", c.kind()); + } } // Close the "L..E" pair @@ -213,12 +223,12 @@ fn encode_predicate<'tcx>( ty::ExistentialPredicate::Trait(trait_ref) => { let name = encode_ty_name(tcx, trait_ref.def_id); let _ = write!(s, "u{}{}", name.len(), &name); - s.push_str(&encode_substs(tcx, trait_ref.substs, dict, options)); + s.push_str(&encode_args(tcx, trait_ref.args, dict, options)); } ty::ExistentialPredicate::Projection(projection) => { let name = encode_ty_name(tcx, projection.def_id); let _ = write!(s, "u{}{}", name.len(), &name); - s.push_str(&encode_substs(tcx, projection.substs, dict, options)); + s.push_str(&encode_args(tcx, projection.args, dict, options)); match projection.term.unpack() { TermKind::Ty(ty) => s.push_str(&encode_ty(tcx, ty, dict, options)), TermKind::Const(c) => s.push_str(&encode_const(tcx, c, dict, options)), @@ -287,21 +297,21 @@ fn encode_region<'tcx>( s } -/// Encodes substs using the Itanium C++ ABI with vendor extended type qualifiers and types for Rust +/// Encodes args using the Itanium C++ ABI with vendor extended type qualifiers and types for Rust /// types that are not used at the FFI boundary. -fn encode_substs<'tcx>( +fn encode_args<'tcx>( tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, dict: &mut FxHashMap, usize>, options: EncodeTyOptions, ) -> String { // [IE] as part of vendor extended type let mut s = String::new(); - let substs: Vec> = substs.iter().collect(); - if !substs.is_empty() { + let args: Vec> = args.iter().collect(); + if !args.is_empty() { s.push('I'); - for subst in substs { - match subst.unpack() { + for arg in args { + match arg.unpack() { GenericArgKind::Lifetime(region) => { s.push_str(&encode_region(tcx, region, dict, options)); } @@ -401,7 +411,7 @@ fn encode_ty_name(tcx: TyCtxt<'_>, def_id: DefId) -> String { let _ = write!(s, "{}", name.len()); // Prepend a '_' if name starts with a digit or '_' - if let Some(first) = name.as_bytes().get(0) { + if let Some(first) = name.as_bytes().first() { if first.is_ascii_digit() || *first == b'_' { s.push('_'); } @@ -502,7 +512,14 @@ fn encode_ty<'tcx>( ty::Array(ty0, len) => { // A let mut s = String::from("A"); - let _ = write!(s, "{}", &len.kind().try_to_scalar().unwrap().to_u64().unwrap()); + let _ = write!( + s, + "{}", + &len.try_to_scalar() + .unwrap() + .to_u64() + .unwrap_or_else(|_| panic!("failed to convert length to u64")) + ); s.push_str(&encode_ty(tcx, *ty0, dict, options)); compress(dict, DictKey::Ty(ty, TyQ::None), &mut s); typeid.push_str(&s); @@ -518,7 +535,7 @@ fn encode_ty<'tcx>( } // User-defined types - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { let mut s = String::new(); let def_id = adt_def.did(); if let Some(cfi_encoding) = tcx.get_attr(def_id, sym::cfi_encoding) { @@ -534,10 +551,7 @@ fn encode_ty<'tcx>( tcx.sess .struct_span_err( cfi_encoding.span, - DiagnosticMessage::Str(format!( - "invalid `cfi_encoding` for `{:?}`", - ty.kind() - )), + format!("invalid `cfi_encoding` for `{:?}`", ty.kind()), ) .emit(); } @@ -567,7 +581,7 @@ fn encode_ty<'tcx>( // , as vendor extended type. let name = encode_ty_name(tcx, def_id); let _ = write!(s, "u{}{}", name.len(), &name); - s.push_str(&encode_substs(tcx, substs, dict, options)); + s.push_str(&encode_args(tcx, args, dict, options)); compress(dict, DictKey::Ty(ty, TyQ::None), &mut s); } typeid.push_str(&s); @@ -589,10 +603,7 @@ fn encode_ty<'tcx>( tcx.sess .struct_span_err( cfi_encoding.span, - DiagnosticMessage::Str(format!( - "invalid `cfi_encoding` for `{:?}`", - ty.kind() - )), + format!("invalid `cfi_encoding` for `{:?}`", ty.kind()), ) .emit(); } @@ -608,15 +619,30 @@ fn encode_ty<'tcx>( } // Function types - ty::FnDef(def_id, substs) - | ty::Closure(def_id, substs) - | ty::Generator(def_id, substs, ..) => { + ty::FnDef(def_id, args) | ty::Closure(def_id, args) => { // u[IE], where is , // as vendor extended type. let mut s = String::new(); let name = encode_ty_name(tcx, *def_id); let _ = write!(s, "u{}{}", name.len(), &name); - s.push_str(&encode_substs(tcx, substs, dict, options)); + s.push_str(&encode_args(tcx, args, dict, options)); + compress(dict, DictKey::Ty(ty, TyQ::None), &mut s); + typeid.push_str(&s); + } + + ty::Generator(def_id, args, ..) => { + // u[IE], where is , + // as vendor extended type. + let mut s = String::new(); + let name = encode_ty_name(tcx, *def_id); + let _ = write!(s, "u{}{}", name.len(), &name); + // Encode parent args only + s.push_str(&encode_args( + tcx, + tcx.mk_args(args.as_generator().parent_args()), + dict, + options, + )); compress(dict, DictKey::Ty(ty, TyQ::None), &mut s); typeid.push_str(&s); } @@ -628,7 +654,7 @@ fn encode_ty<'tcx>( s.push_str("u3refI"); s.push_str(&encode_ty(tcx, *ty0, dict, options)); s.push('E'); - compress(dict, DictKey::Ty(tcx.mk_imm_ref(*region, *ty0), TyQ::None), &mut s); + compress(dict, DictKey::Ty(Ty::new_imm_ref(tcx, *region, *ty0), TyQ::None), &mut s); if ty.is_mutable_ptr() { s = format!("{}{}", "U3mut", &s); compress(dict, DictKey::Ty(ty, TyQ::Mut), &mut s); @@ -681,12 +707,12 @@ fn encode_ty<'tcx>( } // Unexpected types - ty::Bound(..) + ty::Alias(..) + | ty::Bound(..) | ty::Error(..) | ty::GeneratorWitness(..) | ty::GeneratorWitnessMIR(..) | ty::Infer(..) - | ty::Alias(..) | ty::Placeholder(..) => { bug!("encode_ty: unexpected `{:?}`", ty.kind()); } @@ -717,18 +743,18 @@ fn transform_predicates<'tcx>( tcx.mk_poly_existential_predicates(&predicates) } -/// Transforms substs for being encoded and used in the substitution dictionary. -fn transform_substs<'tcx>( +/// Transforms args for being encoded and used in the substitution dictionary. +fn transform_args<'tcx>( tcx: TyCtxt<'tcx>, - substs: SubstsRef<'tcx>, + args: GenericArgsRef<'tcx>, options: TransformTyOptions, -) -> SubstsRef<'tcx> { - let substs = substs.iter().map(|subst| match subst.unpack() { - GenericArgKind::Type(ty) if ty.is_c_void(tcx) => tcx.mk_unit().into(), +) -> GenericArgsRef<'tcx> { + let args = args.iter().map(|arg| match arg.unpack() { + GenericArgKind::Type(ty) if ty.is_c_void(tcx) => Ty::new_unit(tcx).into(), GenericArgKind::Type(ty) => transform_ty(tcx, ty, options).into(), - _ => subst, + _ => arg, }); - tcx.mk_substs_from_iter(substs) + tcx.mk_args_from_iter(args) } // Transforms a ty:Ty for being encoded and used in the substitution dictionary. It transforms all @@ -739,7 +765,12 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio let mut ty = ty; match ty.kind() { - ty::Float(..) | ty::Char | ty::Str | ty::Never | ty::Foreign(..) => {} + ty::Float(..) + | ty::Char + | ty::Str + | ty::Never + | ty::Foreign(..) + | ty::GeneratorWitness(..) => {} ty::Bool => { if options.contains(EncodeTyOptions::NORMALIZE_INTEGERS) { @@ -789,24 +820,25 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio _ if ty.is_unit() => {} ty::Tuple(tys) => { - ty = tcx.mk_tup_from_iter(tys.iter().map(|ty| transform_ty(tcx, ty, options))); + ty = Ty::new_tup_from_iter(tcx, tys.iter().map(|ty| transform_ty(tcx, ty, options))); } ty::Array(ty0, len) => { - let len = len.kind().try_to_scalar().unwrap().to_u64().unwrap(); - ty = tcx.mk_array(transform_ty(tcx, *ty0, options), len); + let len = len.eval_target_usize(tcx, ty::ParamEnv::reveal_all()); + + ty = Ty::new_array(tcx, transform_ty(tcx, *ty0, options), len); } ty::Slice(ty0) => { - ty = tcx.mk_slice(transform_ty(tcx, *ty0, options)); + ty = Ty::new_slice(tcx, transform_ty(tcx, *ty0, options)); } - ty::Adt(adt_def, substs) => { + ty::Adt(adt_def, args) => { if ty.is_c_void(tcx) { - ty = tcx.mk_unit(); + ty = Ty::new_unit(tcx); } else if options.contains(TransformTyOptions::GENERALIZE_REPR_C) && adt_def.repr().c() { - ty = tcx.mk_adt(*adt_def, ty::List::empty()); + ty = Ty::new_adt(tcx, *adt_def, ty::List::empty()); } else if adt_def.repr().transparent() && adt_def.is_struct() { // Don't transform repr(transparent) types with an user-defined CFI encoding to // preserve the user-defined CFI encoding. @@ -816,13 +848,13 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio let variant = adt_def.non_enum_variant(); let param_env = tcx.param_env(variant.def_id); let field = variant.fields.iter().find(|field| { - let ty = tcx.type_of(field.did).subst_identity(); + let ty = tcx.type_of(field.did).instantiate_identity(); let is_zst = tcx.layout_of(param_env.and(ty)).is_ok_and(|layout| layout.is_zst()); !is_zst }); if let Some(field) = field { - let ty0 = tcx.type_of(field.did).subst(tcx, substs); + let ty0 = tcx.type_of(field.did).instantiate(tcx, args); // Generalize any repr(transparent) user-defined type that is either a pointer // or reference, and either references itself or any other type that contains or // references itself, to avoid a reference cycle. @@ -837,37 +869,37 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio } } else { // Transform repr(transparent) types without non-ZST field into () - ty = tcx.mk_unit(); + ty = Ty::new_unit(tcx); } } else { - ty = tcx.mk_adt(*adt_def, transform_substs(tcx, substs, options)); + ty = Ty::new_adt(tcx, *adt_def, transform_args(tcx, args, options)); } } - ty::FnDef(def_id, substs) => { - ty = tcx.mk_fn_def(*def_id, transform_substs(tcx, substs, options)); + ty::FnDef(def_id, args) => { + ty = Ty::new_fn_def(tcx, *def_id, transform_args(tcx, args, options)); } - ty::Closure(def_id, substs) => { - ty = tcx.mk_closure(*def_id, transform_substs(tcx, substs, options)); + ty::Closure(def_id, args) => { + ty = Ty::new_closure(tcx, *def_id, transform_args(tcx, args, options)); } - ty::Generator(def_id, substs, movability) => { - ty = tcx.mk_generator(*def_id, transform_substs(tcx, substs, options), *movability); + ty::Generator(def_id, args, movability) => { + ty = Ty::new_generator(tcx, *def_id, transform_args(tcx, args, options), *movability); } ty::Ref(region, ty0, ..) => { if options.contains(TransformTyOptions::GENERALIZE_POINTERS) { if ty.is_mutable_ptr() { - ty = tcx.mk_mut_ref(tcx.lifetimes.re_static, tcx.mk_unit()); + ty = Ty::new_mut_ref(tcx, tcx.lifetimes.re_static, Ty::new_unit(tcx)); } else { - ty = tcx.mk_imm_ref(tcx.lifetimes.re_static, tcx.mk_unit()); + ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_static, Ty::new_unit(tcx)); } } else { if ty.is_mutable_ptr() { - ty = tcx.mk_mut_ref(*region, transform_ty(tcx, *ty0, options)); + ty = Ty::new_mut_ref(tcx, *region, transform_ty(tcx, *ty0, options)); } else { - ty = tcx.mk_imm_ref(*region, transform_ty(tcx, *ty0, options)); + ty = Ty::new_imm_ref(tcx, *region, transform_ty(tcx, *ty0, options)); } } } @@ -875,22 +907,22 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio ty::RawPtr(tm) => { if options.contains(TransformTyOptions::GENERALIZE_POINTERS) { if ty.is_mutable_ptr() { - ty = tcx.mk_mut_ptr(tcx.mk_unit()); + ty = Ty::new_mut_ptr(tcx, Ty::new_unit(tcx)); } else { - ty = tcx.mk_imm_ptr(tcx.mk_unit()); + ty = Ty::new_imm_ptr(tcx, Ty::new_unit(tcx)); } } else { if ty.is_mutable_ptr() { - ty = tcx.mk_mut_ptr(transform_ty(tcx, tm.ty, options)); + ty = Ty::new_mut_ptr(tcx, transform_ty(tcx, tm.ty, options)); } else { - ty = tcx.mk_imm_ptr(transform_ty(tcx, tm.ty, options)); + ty = Ty::new_imm_ptr(tcx, transform_ty(tcx, tm.ty, options)); } } } ty::FnPtr(fn_sig) => { if options.contains(TransformTyOptions::GENERALIZE_POINTERS) { - ty = tcx.mk_imm_ptr(tcx.mk_unit()); + ty = Ty::new_imm_ptr(tcx, Ty::new_unit(tcx)); } else { let parameters: Vec> = fn_sig .skip_binder() @@ -899,33 +931,43 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio .map(|ty| transform_ty(tcx, *ty, options)) .collect(); let output = transform_ty(tcx, fn_sig.skip_binder().output(), options); - ty = tcx.mk_fn_ptr(ty::Binder::bind_with_vars( - tcx.mk_fn_sig( - parameters, - output, - fn_sig.c_variadic(), - fn_sig.unsafety(), - fn_sig.abi(), + ty = Ty::new_fn_ptr( + tcx, + ty::Binder::bind_with_vars( + tcx.mk_fn_sig( + parameters, + output, + fn_sig.c_variadic(), + fn_sig.unsafety(), + fn_sig.abi(), + ), + fn_sig.bound_vars(), ), - fn_sig.bound_vars(), - )); + ); } } ty::Dynamic(predicates, _region, kind) => { - ty = tcx.mk_dynamic( + ty = Ty::new_dynamic( + tcx, transform_predicates(tcx, predicates, options), tcx.lifetimes.re_erased, *kind, ); } + ty::Alias(..) => { + ty = transform_ty( + tcx, + tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), ty), + options, + ); + } + ty::Bound(..) | ty::Error(..) - | ty::GeneratorWitness(..) | ty::GeneratorWitnessMIR(..) | ty::Infer(..) - | ty::Alias(..) | ty::Param(..) | ty::Placeholder(..) => { bug!("transform_ty: unexpected `{:?}`", ty.kind()); @@ -1076,14 +1118,16 @@ pub fn typeid_for_instance<'tcx>( )]); // Is the concrete self mutable? let self_ty = if fn_abi.args[0].layout.ty.is_mutable_ptr() { - tcx.mk_mut_ref( + Ty::new_mut_ref( + tcx, tcx.lifetimes.re_erased, - tcx.mk_dynamic(existential_predicates, tcx.lifetimes.re_erased, ty::Dyn), + Ty::new_dynamic(tcx, existential_predicates, tcx.lifetimes.re_erased, ty::Dyn), ) } else { - tcx.mk_imm_ref( + Ty::new_imm_ref( + tcx, tcx.lifetimes.re_erased, - tcx.mk_dynamic(existential_predicates, tcx.lifetimes.re_erased, ty::Dyn), + Ty::new_dynamic(tcx, existential_predicates, tcx.lifetimes.re_erased, ty::Dyn), ) }; diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 4cccc6398927c..da19a3ba4fd61 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -27,7 +27,7 @@ pub(super) fn mangle<'tcx>( ) -> String { let def_id = instance.def_id(); // FIXME(eddyb) this should ideally not be needed. - let substs = tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), instance.substs); + let args = tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), instance.args); let prefix = "_R"; let mut cx = &mut SymbolMangler { @@ -50,9 +50,9 @@ pub(super) fn mangle<'tcx>( }; cx = if let Some(shim_kind) = shim_kind { - cx.path_append_ns(|cx| cx.print_def_path(def_id, substs), 'S', 0, shim_kind).unwrap() + cx.path_append_ns(|cx| cx.print_def_path(def_id, args), 'S', 0, shim_kind).unwrap() } else { - cx.print_def_path(def_id, substs).unwrap() + cx.print_def_path(def_id, args).unwrap() }; if let Some(instantiating_crate) = instantiating_crate { cx = cx.print_def_path(instantiating_crate.as_def_id(), &[]).unwrap(); @@ -245,19 +245,19 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { fn print_def_path( mut self, def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], ) -> Result { - if let Some(&i) = self.paths.get(&(def_id, substs)) { + if let Some(&i) = self.paths.get(&(def_id, args)) { return self.print_backref(i); } let start = self.out.len(); - self = self.default_print_def_path(def_id, substs)?; + self = self.default_print_def_path(def_id, args)?; // Only cache paths that do not refer to an enclosing // binder (which would change depending on context). - if !substs.iter().any(|k| k.has_escaping_bound_vars()) { - self.paths.insert((def_id, substs), start); + if !args.iter().any(|k| k.has_escaping_bound_vars()) { + self.paths.insert((def_id, args), start); } Ok(self) } @@ -265,7 +265,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { fn print_impl_path( mut self, impl_def_id: DefId, - substs: &'tcx [GenericArg<'tcx>], + args: &'tcx [GenericArg<'tcx>], mut self_ty: Ty<'tcx>, mut impl_trait_ref: Option>, ) -> Result { @@ -273,8 +273,8 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { let parent_def_id = DefId { index: key.parent.unwrap(), ..impl_def_id }; let mut param_env = self.tcx.param_env_reveal_all_normalized(impl_def_id); - if !substs.is_empty() { - param_env = EarlyBinder(param_env).subst(self.tcx, substs); + if !args.is_empty() { + param_env = EarlyBinder::bind(param_env).instantiate(self.tcx, args); } match &mut impl_trait_ref { @@ -295,7 +295,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { // Encode impl generic params if the substitutions contain parameters (implying // polymorphization is enabled) and this isn't an inherent impl. - if impl_trait_ref.is_some() && substs.iter().any(|a| a.has_non_region_param()) { + if impl_trait_ref.is_some() && args.iter().any(|a| a.has_non_region_param()) { self = self.path_generic_args( |this| { this.path_append_ns( @@ -305,7 +305,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { "", ) }, - substs, + args, )?; } else { self.push_disambiguator(key.disambiguated_data.disambiguator as u64); @@ -315,7 +315,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { self = self_ty.print(self)?; if let Some(trait_ref) = impl_trait_ref { - self = self.print_def_path(trait_ref.def_id, trait_ref.substs)?; + self = self.print_def_path(trait_ref.def_id, trait_ref.args)?; } Ok(self) @@ -431,12 +431,12 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { } // Mangle all nominal types as paths. - ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did: def_id, .. }, _)), substs) - | ty::FnDef(def_id, substs) - | ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, substs, .. }) - | ty::Closure(def_id, substs) - | ty::Generator(def_id, substs, _) => { - self = self.print_def_path(def_id, substs)?; + ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did: def_id, .. }, _)), args) + | ty::FnDef(def_id, args) + | ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) + | ty::Closure(def_id, args) + | ty::Generator(def_id, args, _) => { + self = self.print_def_path(def_id, args)?; } ty::Foreign(def_id) => { self = self.print_def_path(def_id, &[])?; @@ -483,6 +483,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { } ty::Alias(ty::Inherent, _) => bug!("symbol_names: unexpected inherent projection"), + ty::Alias(ty::Weak, _) => bug!("symbol_names: unexpected weak projection"), ty::GeneratorWitness(_) => bug!("symbol_names: unexpected `GeneratorWitness`"), ty::GeneratorWitnessMIR(..) => bug!("symbol_names: unexpected `GeneratorWitnessMIR`"), } @@ -534,9 +535,9 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { match predicate.as_ref().skip_binder() { ty::ExistentialPredicate::Trait(trait_ref) => { // Use a type that can't appear in defaults of type parameters. - let dummy_self = cx.tcx.mk_fresh_ty(0); + let dummy_self = Ty::new_fresh(cx.tcx, 0); let trait_ref = trait_ref.with_self_ty(cx.tcx, dummy_self); - cx = cx.print_def_path(trait_ref.def_id, trait_ref.substs)?; + cx = cx.print_def_path(trait_ref.def_id, trait_ref.args)?; } ty::ExistentialPredicate::Projection(projection) => { let name = cx.tcx.associated_item(projection.def_id).name; @@ -627,7 +628,8 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { valtree, ty ) }); - let s = std::str::from_utf8(slice).expect("non utf8 str from miri"); + let s = std::str::from_utf8(slice) + .expect("non utf8 str from MIR interpreter"); self.push("e"); @@ -650,7 +652,8 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { .builtin_deref(true) .expect("tried to dereference on non-ptr type") .ty; - let dereferenced_const = self.tcx.mk_const(ct.kind(), pointee_ty); + // FIXME(const_generics): add an assert that we only do this for valtrees. + let dereferenced_const = self.tcx.mk_ct_from_kind(ct.kind(), pointee_ty); self = dereferenced_const.print(self)?; } } @@ -677,13 +680,13 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { self.push("T"); self = print_field_list(self)?; } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let variant_idx = contents.variant.expect("destructed const of adt without variant idx"); let variant_def = &def.variant(variant_idx); self.push("V"); - self = self.print_def_path(variant_def.def_id, substs)?; + self = self.print_def_path(variant_def.def_id, args)?; match variant_def.ctor_kind() { Some(CtorKind::Const) => { @@ -748,7 +751,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> { self.push("Y"); self = self_ty.print(self)?; - self.print_def_path(trait_ref.def_id, trait_ref.substs) + self.print_def_path(trait_ref.def_id, trait_ref.args) } fn path_append_impl( diff --git a/compiler/rustc_target/Cargo.toml b/compiler/rustc_target/Cargo.toml index dff22fad4ec4c..393e59e8b007e 100644 --- a/compiler/rustc_target/Cargo.toml +++ b/compiler/rustc_target/Cargo.toml @@ -14,3 +14,8 @@ rustc_feature = { path = "../rustc_feature" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_span = { path = "../rustc_span" } + +[dependencies.object] +version = "0.32.0" +default-features = false +features = ["elf"] diff --git a/compiler/rustc_target/src/abi/call/aarch64.rs b/compiler/rustc_target/src/abi/call/aarch64.rs index a84988fa75c6d..b4c7b0f120f91 100644 --- a/compiler/rustc_target/src/abi/call/aarch64.rs +++ b/compiler/rustc_target/src/abi/call/aarch64.rs @@ -1,25 +1,15 @@ use crate::abi::call::{ArgAbi, FnAbi, Reg, RegKind, Uniform}; use crate::abi::{HasDataLayout, TyAbiInterface}; -/// Given integer-types M and register width N (e.g. M=u16 and N=32 bits), the -/// `ParamExtension` policy specifies how a uM value should be treated when -/// passed via register or stack-slot of width N. See also rust-lang/rust#97463. +/// Indicates the variant of the AArch64 ABI we are compiling for. +/// Used to accommodate Apple and Microsoft's deviations from the usual AAPCS ABI. +/// +/// Corresponds to Clang's `AArch64ABIInfo::ABIKind`. #[derive(Copy, Clone, PartialEq)] -pub enum ParamExtension { - /// Indicates that when passing an i8/i16, either as a function argument or - /// as a return value, it must be sign-extended to 32 bits, and likewise a - /// u8/u16 must be zero-extended to 32-bits. (This variant is here to - /// accommodate Apple's deviation from the usual AArch64 ABI as defined by - /// ARM.) - /// - /// See also: - ExtendTo32Bits, - - /// Indicates that no sign- nor zero-extension is performed: if a value of - /// type with bitwidth M is passed as function argument or return value, - /// then M bits are copied into the least significant M bits, and the - /// remaining bits of the register (or word of memory) are untouched. - NoExtension, +pub enum AbiKind { + AAPCS, + DarwinPCS, + Win64, } fn is_homogeneous_aggregate<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) -> Option @@ -45,15 +35,17 @@ where }) } -fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>, param_policy: ParamExtension) +fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>, kind: AbiKind) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !ret.layout.is_aggregate() { - match param_policy { - ParamExtension::ExtendTo32Bits => ret.extend_integer_width_to(32), - ParamExtension::NoExtension => {} + if kind == AbiKind::DarwinPCS { + // On Darwin, when returning an i8/i16, it must be sign-extended to 32 bits, + // and likewise a u8/u16 must be zero-extended to 32-bits. + // See also: + ret.extend_integer_width_to(32) } return; } @@ -70,15 +62,17 @@ where ret.make_indirect(); } -fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, param_policy: ParamExtension) +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, kind: AbiKind) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !arg.layout.is_aggregate() { - match param_policy { - ParamExtension::ExtendTo32Bits => arg.extend_integer_width_to(32), - ParamExtension::NoExtension => {} + if kind == AbiKind::DarwinPCS { + // On Darwin, when passing an i8/i16, it must be sign-extended to 32 bits, + // and likewise a u8/u16 must be zero-extended to 32-bits. + // See also: + arg.extend_integer_width_to(32); } return; } @@ -87,27 +81,39 @@ where return; } let size = arg.layout.size; - let bits = size.bits(); - if bits <= 128 { - arg.cast_to(Uniform { unit: Reg::i64(), total: size }); + let align = if kind == AbiKind::AAPCS { + // When passing small aggregates by value, the AAPCS ABI mandates using the unadjusted + // alignment of the type (not including `repr(align)`). + // This matches behavior of `AArch64ABIInfo::classifyArgumentType` in Clang. + // See: + arg.layout.unadjusted_abi_align + } else { + arg.layout.align.abi + }; + if size.bits() <= 128 { + if align.bits() == 128 { + arg.cast_to(Uniform { unit: Reg::i128(), total: size }); + } else { + arg.cast_to(Uniform { unit: Reg::i64(), total: size }); + } return; } arg.make_indirect(); } -pub fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, param_policy: ParamExtension) +pub fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, kind: AbiKind) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !fn_abi.ret.is_ignore() { - classify_ret(cx, &mut fn_abi.ret, param_policy); + classify_ret(cx, &mut fn_abi.ret, kind); } for arg in fn_abi.args.iter_mut() { if arg.is_ignore() { continue; } - classify_arg(cx, arg, param_policy); + classify_arg(cx, arg, kind); } } diff --git a/compiler/rustc_target/src/abi/call/csky.rs b/compiler/rustc_target/src/abi/call/csky.rs new file mode 100644 index 0000000000000..bbe95fa20ac55 --- /dev/null +++ b/compiler/rustc_target/src/abi/call/csky.rs @@ -0,0 +1,31 @@ +// See https://github.com/llvm/llvm-project/blob/d85b94bf0080dcd780656c0f5e6342800720eba9/llvm/lib/Target/CSKY/CSKYCallingConv.td +use crate::abi::call::{ArgAbi, FnAbi}; + +fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { + if ret.layout.is_aggregate() || ret.layout.size.bits() > 64 { + ret.make_indirect(); + } else { + ret.extend_integer_width_to(32); + } +} + +fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { + if arg.layout.is_aggregate() || arg.layout.size.bits() > 64 { + arg.make_indirect(); + } else { + arg.extend_integer_width_to(32); + } +} + +pub fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { + if !fn_abi.ret.is_ignore() { + classify_ret(&mut fn_abi.ret); + } + + for arg in fn_abi.args.iter_mut() { + if arg.is_ignore() { + continue; + } + classify_arg(arg); + } +} diff --git a/compiler/rustc_target/src/abi/call/m68k.rs b/compiler/rustc_target/src/abi/call/m68k.rs index c1e0f54af5f1e..1d4649ed8678e 100644 --- a/compiler/rustc_target/src/abi/call/m68k.rs +++ b/compiler/rustc_target/src/abi/call/m68k.rs @@ -10,7 +10,7 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { if arg.layout.is_aggregate() { - arg.make_indirect_byval(); + arg.make_indirect_byval(None); } else { arg.extend_integer_width_to(32); } diff --git a/compiler/rustc_target/src/abi/call/mod.rs b/compiler/rustc_target/src/abi/call/mod.rs index 1ae11f5671cd9..8fab13d5d5daf 100644 --- a/compiler/rustc_target/src/abi/call/mod.rs +++ b/compiler/rustc_target/src/abi/call/mod.rs @@ -2,7 +2,6 @@ use crate::abi::{self, Abi, Align, FieldsShape, Size}; use crate::abi::{HasDataLayout, TyAbiInterface, TyAndLayout}; use crate::spec::{self, HasTargetSpec}; use rustc_span::Symbol; -use std::fmt; use std::str::FromStr; mod aarch64; @@ -10,6 +9,7 @@ mod amdgpu; mod arm; mod avr; mod bpf; +mod csky; mod hexagon; mod loongarch; mod m68k; @@ -495,9 +495,7 @@ impl<'a, Ty> ArgAbi<'a, Ty> { .set(ArgAttribute::NonNull) .set(ArgAttribute::NoUndef); attrs.pointee_size = layout.size; - // FIXME(eddyb) We should be doing this, but at least on - // i686-pc-windows-msvc, it results in wrong stack offsets. - // attrs.pointee_align = Some(layout.align.abi); + attrs.pointee_align = Some(layout.align.abi); let extra_attrs = layout.is_unsized().then_some(ArgAttributes::new()); @@ -514,11 +512,19 @@ impl<'a, Ty> ArgAbi<'a, Ty> { self.mode = Self::indirect_pass_mode(&self.layout); } - pub fn make_indirect_byval(&mut self) { + pub fn make_indirect_byval(&mut self, byval_align: Option) { self.make_indirect(); match self.mode { - PassMode::Indirect { attrs: _, extra_attrs: _, ref mut on_stack } => { + PassMode::Indirect { ref mut attrs, extra_attrs: _, ref mut on_stack } => { *on_stack = true; + + // Some platforms, like 32-bit x86, change the alignment of the type when passing + // `byval`. Account for that. + if let Some(byval_align) = byval_align { + // On all targets with byval align this is currently true, so let's assert it. + debug_assert!(byval_align >= Align::from_bytes(4).unwrap()); + attrs.pointee_align = Some(byval_align); + } } _ => unreachable!(), } @@ -598,6 +604,25 @@ pub enum Conv { AmdGpuKernel, AvrInterrupt, AvrNonBlockingInterrupt, + + RiscvInterrupt { + kind: RiscvInterruptKind, + }, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, HashStable_Generic)] +pub enum RiscvInterruptKind { + Machine, + Supervisor, +} + +impl RiscvInterruptKind { + pub fn as_str(&self) -> &'static str { + match self { + Self::Machine => "machine", + Self::Supervisor => "supervisor", + } + } } /// Metadata describing how the arguments to a native function @@ -633,16 +658,6 @@ pub enum AdjustForForeignAbiError { Unsupported { arch: Symbol, abi: spec::abi::Abi }, } -impl fmt::Display for AdjustForForeignAbiError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Unsupported { arch, abi } => { - write!(f, "target architecture {arch:?} does not support `extern {abi}` ABI") - } - } - } -} - impl<'a, Ty> FnAbi<'a, Ty> { pub fn adjust_for_foreign_abi( &mut self, @@ -655,7 +670,8 @@ impl<'a, Ty> FnAbi<'a, Ty> { { if abi == spec::abi::Abi::X86Interrupt { if let Some(arg) = self.args.first_mut() { - arg.make_indirect_byval(); + // FIXME(pcwalton): This probably should use the x86 `byval` ABI... + arg.make_indirect_byval(None); } return Ok(()); } @@ -683,20 +699,23 @@ impl<'a, Ty> FnAbi<'a, Ty> { } }, "aarch64" => { - let param_policy = if cx.target_spec().is_like_osx { - aarch64::ParamExtension::ExtendTo32Bits + let kind = if cx.target_spec().is_like_osx { + aarch64::AbiKind::DarwinPCS + } else if cx.target_spec().is_like_windows { + aarch64::AbiKind::Win64 } else { - aarch64::ParamExtension::NoExtension + aarch64::AbiKind::AAPCS }; - aarch64::compute_abi_info(cx, self, param_policy) + aarch64::compute_abi_info(cx, self, kind) } "amdgpu" => amdgpu::compute_abi_info(cx, self), "arm" => arm::compute_abi_info(cx, self), "avr" => avr::compute_abi_info(self), "loongarch64" => loongarch::compute_abi_info(cx, self), "m68k" => m68k::compute_abi_info(self), - "mips" => mips::compute_abi_info(cx, self), - "mips64" => mips64::compute_abi_info(cx, self), + "csky" => csky::compute_abi_info(self), + "mips" | "mips32r6" => mips::compute_abi_info(cx, self), + "mips64" | "mips64r6" => mips64::compute_abi_info(cx, self), "powerpc" => powerpc::compute_abi_info(self), "powerpc64" => powerpc64::compute_abi_info(cx, self), "s390x" => s390x::compute_abi_info(cx, self), @@ -755,6 +774,12 @@ impl FromStr for Conv { "AmdGpuKernel" => Ok(Conv::AmdGpuKernel), "AvrInterrupt" => Ok(Conv::AvrInterrupt), "AvrNonBlockingInterrupt" => Ok(Conv::AvrNonBlockingInterrupt), + "RiscvInterrupt(machine)" => { + Ok(Conv::RiscvInterrupt { kind: RiscvInterruptKind::Machine }) + } + "RiscvInterrupt(supervisor)" => { + Ok(Conv::RiscvInterrupt { kind: RiscvInterruptKind::Supervisor }) + } _ => Err(format!("'{s}' is not a valid value for entry function call convention.")), } } diff --git a/compiler/rustc_target/src/abi/call/wasm.rs b/compiler/rustc_target/src/abi/call/wasm.rs index 44427ee5317c1..0eb2309ecb28b 100644 --- a/compiler/rustc_target/src/abi/call/wasm.rs +++ b/compiler/rustc_target/src/abi/call/wasm.rs @@ -36,7 +36,7 @@ where { arg.extend_integer_width_to(32); if arg.layout.is_aggregate() && !unwrap_trivial_aggregate(cx, arg) { - arg.make_indirect_byval(); + arg.make_indirect_byval(None); } } diff --git a/compiler/rustc_target/src/abi/call/x86.rs b/compiler/rustc_target/src/abi/call/x86.rs index 7c26335dcf4cd..b738c3133d95e 100644 --- a/compiler/rustc_target/src/abi/call/x86.rs +++ b/compiler/rustc_target/src/abi/call/x86.rs @@ -1,5 +1,5 @@ use crate::abi::call::{ArgAttribute, FnAbi, PassMode, Reg, RegKind}; -use crate::abi::{HasDataLayout, TyAbiInterface}; +use crate::abi::{Abi, Align, HasDataLayout, TyAbiInterface, TyAndLayout}; use crate::spec::HasTargetSpec; #[derive(PartialEq)] @@ -53,8 +53,75 @@ where if arg.is_ignore() { continue; } - if arg.layout.is_aggregate() { - arg.make_indirect_byval(); + + // FIXME: MSVC 2015+ will pass the first 3 vector arguments in [XYZ]MM0-2 + // See https://reviews.llvm.org/D72114 for Clang behavior + + let t = cx.target_spec(); + let align_4 = Align::from_bytes(4).unwrap(); + let align_16 = Align::from_bytes(16).unwrap(); + + if t.is_like_msvc + && arg.layout.is_adt() + && let Some(max_repr_align) = arg.layout.max_repr_align + && max_repr_align > align_4 + { + // MSVC has special rules for overaligned arguments: https://reviews.llvm.org/D72114. + // Summarized here: + // - Arguments with _requested_ alignment > 4 are passed indirectly. + // - For backwards compatibility, arguments with natural alignment > 4 are still passed + // on stack (via `byval`). For example, this includes `double`, `int64_t`, + // and structs containing them, provided they lack an explicit alignment attribute. + assert!(arg.layout.align.abi >= max_repr_align, + "abi alignment {:?} less than requested alignment {max_repr_align:?}", + arg.layout.align.abi, + ); + arg.make_indirect(); + } else if arg.layout.is_aggregate() { + // We need to compute the alignment of the `byval` argument. The rules can be found in + // `X86_32ABIInfo::getTypeStackAlignInBytes` in Clang's `TargetInfo.cpp`. Summarized + // here, they are: + // + // 1. If the natural alignment of the type is <= 4, the alignment is 4. + // + // 2. Otherwise, on Linux, the alignment of any vector type is the natural alignment. + // This doesn't matter here because we only pass aggregates via `byval`, not vectors. + // + // 3. Otherwise, on Apple platforms, the alignment of anything that contains a vector + // type is 16. + // + // 4. If none of these conditions are true, the alignment is 4. + + fn contains_vector<'a, Ty, C>(cx: &C, layout: TyAndLayout<'a, Ty>) -> bool + where + Ty: TyAbiInterface<'a, C> + Copy, + { + match layout.abi { + Abi::Uninhabited | Abi::Scalar(_) | Abi::ScalarPair(..) => false, + Abi::Vector { .. } => true, + Abi::Aggregate { .. } => { + for i in 0..layout.fields.count() { + if contains_vector(cx, layout.field(cx, i)) { + return true; + } + } + false + } + } + } + + let byval_align = if arg.layout.align.abi < align_4 { + // (1.) + align_4 + } else if t.is_like_osx && contains_vector(cx, arg.layout) { + // (3.) + align_16 + } else { + // (4.) + align_4 + }; + + arg.make_indirect_byval(Some(byval_align)); } else { arg.extend_integer_width_to(32); } diff --git a/compiler/rustc_target/src/abi/call/x86_64.rs b/compiler/rustc_target/src/abi/call/x86_64.rs index 9427f27d1b7bb..d1efe97769925 100644 --- a/compiler/rustc_target/src/abi/call/x86_64.rs +++ b/compiler/rustc_target/src/abi/call/x86_64.rs @@ -1,5 +1,5 @@ // The classification code for the x86_64 ABI is taken from the clay language -// https://github.com/jckarter/clay/blob/master/compiler/src/externals.cpp +// https://github.com/jckarter/clay/blob/db0bd2702ab0b6e48965cd85f8859bbd5f60e48e/compiler/externals.cpp use crate::abi::call::{ArgAbi, CastTarget, FnAbi, Reg, RegKind}; use crate::abi::{self, Abi, HasDataLayout, Size, TyAbiInterface, TyAndLayout}; @@ -153,9 +153,9 @@ fn reg_component(cls: &[Option], i: &mut usize, size: Size) -> Option], size: Size) -> CastTarget { +fn cast_target(cls: &[Option], size: Size) -> Option { let mut i = 0; - let lo = reg_component(cls, &mut i, size).unwrap(); + let lo = reg_component(cls, &mut i, size)?; let offset = Size::from_bytes(8) * (i as u64); let mut target = CastTarget::from(lo); if size > offset { @@ -164,7 +164,7 @@ fn cast_target(cls: &[Option], size: Size) -> CastTarget { } } assert_eq!(reg_component(cls, &mut i, Size::ZERO), None); - target + Some(target) } const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9 @@ -213,7 +213,7 @@ where match cls_or_mem { Err(Memory) => { if is_arg { - arg.make_indirect_byval(); + arg.make_indirect_byval(None); } else { // `sret` parameter thus one less integer register available arg.make_indirect(); @@ -227,7 +227,9 @@ where // split into sized chunks passed individually if arg.layout.is_aggregate() { let size = arg.layout.size; - arg.cast_to(cast_target(cls, size)) + if let Some(cast_target) = cast_target(cls, size) { + arg.cast_to(cast_target); + } } else { arg.extend_integer_width_to(32); } diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs index 589cd3cf96b3e..dd435dbb0a306 100644 --- a/compiler/rustc_target/src/abi/mod.rs +++ b/compiler/rustc_target/src/abi/mod.rs @@ -39,7 +39,7 @@ impl<'a, Ty> Deref for TyAndLayout<'a, Ty> { /// Trait that needs to be implemented by the higher-level type representation /// (e.g. `rustc_middle::ty::Ty`), to provide `rustc_target::abi` functionality. -pub trait TyAbiInterface<'a, C>: Sized { +pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug { fn ty_and_layout_for_variant( this: TyAndLayout<'a, Self>, cx: &C, @@ -135,29 +135,13 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { for index in indices { offset += layout.fields.offset(index); layout = layout.field(cx, index); + assert!( + layout.is_sized(), + "offset of unsized field (type {:?}) cannot be computed statically", + layout.ty + ); } offset } } - -impl<'a, Ty> TyAndLayout<'a, Ty> { - /// Returns `true` if the layout corresponds to an unsized type. - pub fn is_unsized(&self) -> bool { - self.abi.is_unsized() - } - - #[inline] - pub fn is_sized(&self) -> bool { - self.abi.is_sized() - } - - /// Returns `true` if the type is a ZST and not unsized. - pub fn is_zst(&self) -> bool { - match self.abi { - Abi::Scalar(_) | Abi::ScalarPair(..) | Abi::Vector { .. } => false, - Abi::Uninhabited => self.size.bytes() == 0, - Abi::Aggregate { sized } => sized && self.size.bytes() == 0, - } - } -} diff --git a/compiler/rustc_target/src/asm/csky.rs b/compiler/rustc_target/src/asm/csky.rs new file mode 100644 index 0000000000000..6f0e7f7994959 --- /dev/null +++ b/compiler/rustc_target/src/asm/csky.rs @@ -0,0 +1,128 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use rustc_span::Symbol; +use std::fmt; + +def_reg_class! { + CSKY CSKYInlineAsmRegClass { + reg, + freg, + } +} + +impl CSKYInlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + &[] + } + + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + _ty: InlineAsmType, + ) -> Option<(char, &'static str)> { + None + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + None + } + + pub fn supported_types( + self, + _arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option)] { + match self { + Self::reg => types! { _: I8, I16, I32; }, + Self::freg => types! { _: F32; }, + } + } +} + +// The reserved registers are taken from +def_regs! { + CSKY CSKYInlineAsmReg CSKYInlineAsmRegClass { + r0: reg = ["r0","a0"], + r1: reg = ["r1","a1"], + r2: reg = ["r2","a2"], + r3: reg = ["r3","a3"], + r4: reg = ["r4","l0"], + r5: reg = ["r5","l1"], + r6: reg = ["r6","l2"], + r9: reg = ["r9","l5"],// feature e2 + r10: reg = ["r10","l6"],// feature e2 + r11: reg = ["r11","l7"],// feature e2 + r12: reg = ["r12","t0"],// feature e2 + r13: reg = ["r13","t1"],// feature e2 + r16: reg = ["r16","l8"],// feature high-register + r17: reg = ["r17","l9"],// feature high-register + r18: reg = ["r18","t2"],// feature high-register + r19: reg = ["r19","t3"],// feature high-register + r20: reg = ["r20","t4"],// feature high-register + r21: reg = ["r21","t5"],// feature high-register + r22: reg = ["r22","t6"],// feature high-register + r23: reg = ["r23","t7", "fp"],// feature high-register + r24: reg = ["r24","t8", "sop"],// feature high-register + r25: reg = ["r25","t9","tp", "bsp"],// feature high-register + f0: freg = ["fr0","vr0"], + f1: freg = ["fr1","vr1"], + f2: freg = ["fr2","vr2"], + f3: freg = ["fr3","vr3"], + f4: freg = ["fr4","vr4"], + f5: freg = ["fr5","vr5"], + f6: freg = ["fr6","vr6"], + f7: freg = ["fr7","vr7"], + f8: freg = ["fr8","vr8"], + f9: freg = ["fr9","vr9"], + f10: freg = ["fr10","vr10"], + f11: freg = ["fr11","vr11"], + f12: freg = ["fr12","vr12"], + f13: freg = ["fr13","vr13"], + f14: freg = ["fr14","vr14"], + f15: freg = ["fr15","vr15"], + f16: freg = ["fr16","vr16"], + f17: freg = ["fr17","vr17"], + f18: freg = ["fr18","vr18"], + f19: freg = ["fr19","vr19"], + f20: freg = ["fr20","vr20"], + f21: freg = ["fr21","vr21"], + f22: freg = ["fr22","vr22"], + f23: freg = ["fr23","vr23"], + f24: freg = ["fr24","vr24"], + f25: freg = ["fr25","vr25"], + f26: freg = ["fr26","vr26"], + f27: freg = ["fr27","vr27"], + f28: freg = ["fr28","vr28"], + f29: freg = ["fr29","vr29"], + f30: freg = ["fr30","vr30"], + f31: freg = ["fr31","vr31"], + #error = ["r7", "l3"] => + "the base pointer cannot be used as an operand for inline asm", + #error = ["r8","l4"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["r14","sp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["r15","lr"] => + "the link register cannot be used as an operand for inline asm", + #error = ["r31","tls"] => + "reserver for tls", + #error = ["r28", "gb", "rgb", "rdb"] => + "the global pointer cannot be used as an operand for inline asm", + #error = ["r26","r27","r29","tb", "rtb", "r30","svbr"] => + "reserved by the ABI", + } +} + +impl CSKYInlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + _modifier: Option, + ) -> fmt::Result { + out.write_str(self.name()) + } +} diff --git a/compiler/rustc_target/src/asm/mod.rs b/compiler/rustc_target/src/asm/mod.rs index e60b8e78e5d57..a11884bea268f 100644 --- a/compiler/rustc_target/src/asm/mod.rs +++ b/compiler/rustc_target/src/asm/mod.rs @@ -167,6 +167,7 @@ mod aarch64; mod arm; mod avr; mod bpf; +mod csky; mod hexagon; mod loongarch; mod m68k; @@ -184,6 +185,7 @@ pub use aarch64::{AArch64InlineAsmReg, AArch64InlineAsmRegClass}; pub use arm::{ArmInlineAsmReg, ArmInlineAsmRegClass}; pub use avr::{AvrInlineAsmReg, AvrInlineAsmRegClass}; pub use bpf::{BpfInlineAsmReg, BpfInlineAsmRegClass}; +pub use csky::{CSKYInlineAsmReg, CSKYInlineAsmRegClass}; pub use hexagon::{HexagonInlineAsmReg, HexagonInlineAsmRegClass}; pub use loongarch::{LoongArchInlineAsmReg, LoongArchInlineAsmRegClass}; pub use m68k::{M68kInlineAsmReg, M68kInlineAsmRegClass}; @@ -220,6 +222,7 @@ pub enum InlineAsmArch { Avr, Msp430, M68k, + CSKY, } impl FromStr for InlineAsmArch { @@ -238,8 +241,8 @@ impl FromStr for InlineAsmArch { "powerpc64" => Ok(Self::PowerPC64), "hexagon" => Ok(Self::Hexagon), "loongarch64" => Ok(Self::LoongArch64), - "mips" => Ok(Self::Mips), - "mips64" => Ok(Self::Mips64), + "mips" | "mips32r6" => Ok(Self::Mips), + "mips64" | "mips64r6" => Ok(Self::Mips64), "s390x" => Ok(Self::S390x), "spirv" => Ok(Self::SpirV), "wasm32" => Ok(Self::Wasm32), @@ -248,6 +251,7 @@ impl FromStr for InlineAsmArch { "avr" => Ok(Self::Avr), "msp430" => Ok(Self::Msp430), "m68k" => Ok(Self::M68k), + "csky" => Ok(Self::CSKY), _ => Err(()), } } @@ -272,6 +276,7 @@ pub enum InlineAsmReg { Avr(AvrInlineAsmReg), Msp430(Msp430InlineAsmReg), M68k(M68kInlineAsmReg), + CSKY(CSKYInlineAsmReg), // Placeholder for invalid register constraints for the current target Err, } @@ -292,6 +297,7 @@ impl InlineAsmReg { Self::Avr(r) => r.name(), Self::Msp430(r) => r.name(), Self::M68k(r) => r.name(), + Self::CSKY(r) => r.name(), Self::Err => "", } } @@ -311,6 +317,7 @@ impl InlineAsmReg { Self::Avr(r) => InlineAsmRegClass::Avr(r.reg_class()), Self::Msp430(r) => InlineAsmRegClass::Msp430(r.reg_class()), Self::M68k(r) => InlineAsmRegClass::M68k(r.reg_class()), + Self::CSKY(r) => InlineAsmRegClass::CSKY(r.reg_class()), Self::Err => InlineAsmRegClass::Err, } } @@ -344,6 +351,7 @@ impl InlineAsmReg { InlineAsmArch::Avr => Self::Avr(AvrInlineAsmReg::parse(name)?), InlineAsmArch::Msp430 => Self::Msp430(Msp430InlineAsmReg::parse(name)?), InlineAsmArch::M68k => Self::M68k(M68kInlineAsmReg::parse(name)?), + InlineAsmArch::CSKY => Self::CSKY(CSKYInlineAsmReg::parse(name)?), }) } @@ -371,6 +379,7 @@ impl InlineAsmReg { Self::Avr(r) => r.validate(arch, reloc_model, target_features, target, is_clobber), Self::Msp430(r) => r.validate(arch, reloc_model, target_features, target, is_clobber), Self::M68k(r) => r.validate(arch, reloc_model, target_features, target, is_clobber), + Self::CSKY(r) => r.validate(arch, reloc_model, target_features, target, is_clobber), Self::Err => unreachable!(), } } @@ -397,6 +406,7 @@ impl InlineAsmReg { Self::Avr(r) => r.emit(out, arch, modifier), Self::Msp430(r) => r.emit(out, arch, modifier), Self::M68k(r) => r.emit(out, arch, modifier), + Self::CSKY(r) => r.emit(out, arch, modifier), Self::Err => unreachable!("Use of InlineAsmReg::Err"), } } @@ -416,6 +426,7 @@ impl InlineAsmReg { Self::Avr(r) => r.overlapping_regs(|r| cb(Self::Avr(r))), Self::Msp430(_) => cb(self), Self::M68k(_) => cb(self), + Self::CSKY(_) => cb(self), Self::Err => unreachable!("Use of InlineAsmReg::Err"), } } @@ -440,6 +451,7 @@ pub enum InlineAsmRegClass { Avr(AvrInlineAsmRegClass), Msp430(Msp430InlineAsmRegClass), M68k(M68kInlineAsmRegClass), + CSKY(CSKYInlineAsmRegClass), // Placeholder for invalid register constraints for the current target Err, } @@ -463,6 +475,7 @@ impl InlineAsmRegClass { Self::Avr(r) => r.name(), Self::Msp430(r) => r.name(), Self::M68k(r) => r.name(), + Self::CSKY(r) => r.name(), Self::Err => rustc_span::symbol::sym::reg, } } @@ -488,6 +501,7 @@ impl InlineAsmRegClass { Self::Avr(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Avr), Self::Msp430(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Msp430), Self::M68k(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::M68k), + Self::CSKY(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::CSKY), Self::Err => unreachable!("Use of InlineAsmRegClass::Err"), } } @@ -520,6 +534,7 @@ impl InlineAsmRegClass { Self::Avr(r) => r.suggest_modifier(arch, ty), Self::Msp430(r) => r.suggest_modifier(arch, ty), Self::M68k(r) => r.suggest_modifier(arch, ty), + Self::CSKY(r) => r.suggest_modifier(arch, ty), Self::Err => unreachable!("Use of InlineAsmRegClass::Err"), } } @@ -548,6 +563,7 @@ impl InlineAsmRegClass { Self::Avr(r) => r.default_modifier(arch), Self::Msp430(r) => r.default_modifier(arch), Self::M68k(r) => r.default_modifier(arch), + Self::CSKY(r) => r.default_modifier(arch), Self::Err => unreachable!("Use of InlineAsmRegClass::Err"), } } @@ -575,6 +591,7 @@ impl InlineAsmRegClass { Self::Avr(r) => r.supported_types(arch), Self::Msp430(r) => r.supported_types(arch), Self::M68k(r) => r.supported_types(arch), + Self::CSKY(r) => r.supported_types(arch), Self::Err => unreachable!("Use of InlineAsmRegClass::Err"), } } @@ -607,6 +624,7 @@ impl InlineAsmRegClass { InlineAsmArch::Avr => Self::Avr(AvrInlineAsmRegClass::parse(name)?), InlineAsmArch::Msp430 => Self::Msp430(Msp430InlineAsmRegClass::parse(name)?), InlineAsmArch::M68k => Self::M68k(M68kInlineAsmRegClass::parse(name)?), + InlineAsmArch::CSKY => Self::CSKY(CSKYInlineAsmRegClass::parse(name)?), }) } @@ -630,6 +648,7 @@ impl InlineAsmRegClass { Self::Avr(r) => r.valid_modifiers(arch), Self::Msp430(r) => r.valid_modifiers(arch), Self::M68k(r) => r.valid_modifiers(arch), + Self::CSKY(r) => r.valid_modifiers(arch), Self::Err => unreachable!("Use of InlineAsmRegClass::Err"), } } @@ -826,6 +845,11 @@ pub fn allocatable_registers( m68k::fill_reg_map(arch, reloc_model, target_features, target, &mut map); map } + InlineAsmArch::CSKY => { + let mut map = csky::regclass_map(); + csky::fill_reg_map(arch, reloc_model, target_features, target, &mut map); + map + } } } diff --git a/compiler/rustc_target/src/json.rs b/compiler/rustc_target/src/json.rs index 75bb76a9de087..af455b6432f52 100644 --- a/compiler/rustc_target/src/json.rs +++ b/compiler/rustc_target/src/json.rs @@ -92,6 +92,7 @@ impl ToJson for Option { impl ToJson for crate::abi::call::Conv { fn to_json(&self) -> Json { + let buf: String; let s = match self { Self::C => "C", Self::Rust => "Rust", @@ -110,6 +111,10 @@ impl ToJson for crate::abi::call::Conv { Self::AmdGpuKernel => "AmdGpuKernel", Self::AvrInterrupt => "AvrInterrupt", Self::AvrNonBlockingInterrupt => "AvrNonBlockingInterrupt", + Self::RiscvInterrupt { kind } => { + buf = format!("RiscvInterrupt({})", kind.as_str()); + &buf + } }; Json::String(s.to_owned()) } diff --git a/compiler/rustc_target/src/lib.rs b/compiler/rustc_target/src/lib.rs index dc2cc23ffb1e4..b52002b123968 100644 --- a/compiler/rustc_target/src/lib.rs +++ b/compiler/rustc_target/src/lib.rs @@ -11,12 +11,15 @@ #![feature(assert_matches)] #![feature(associated_type_bounds)] #![feature(exhaustive_patterns)] +#![feature(iter_intersperse)] +#![feature(let_chains)] #![feature(min_specialization)] #![feature(never_type)] #![feature(rustc_attrs)] #![feature(step_trait)] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] +#![cfg_attr(not(bootstrap), allow(internal_features))] use std::path::{Path, PathBuf}; diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs index 2b135b6703481..e2df7e0bdcc79 100644 --- a/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs +++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs @@ -2,7 +2,7 @@ use super::apple_base::{opts, Arch}; use crate::spec::{Cc, FramePointer, LinkerFlavor, Lld, Target, TargetOptions}; pub fn target() -> Target { - let llvm_target = "arm64-apple-ios-macabi"; + let llvm_target = "arm64-apple-ios14.0-macabi"; let arch = Arch::Arm64_macabi; let mut base = opts("ios", arch); diff --git a/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs b/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs index bb7c39ff26bdf..f7cdfa71c4b65 100644 --- a/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs +++ b/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs @@ -1,10 +1,10 @@ -use super::apple_base::{opts, Arch}; +use super::apple_base::{opts, tvos_llvm_target, Arch}; use crate::spec::{FramePointer, Target, TargetOptions}; pub fn target() -> Target { let arch = Arch::Arm64; Target { - llvm_target: "arm64-apple-tvos".into(), + llvm_target: tvos_llvm_target(arch).into(), pointer_width: 64, data_layout: "e-m:o-i64:64-i128:128-n32:64-S128".into(), arch: arch.target_arch(), diff --git a/compiler/rustc_target/src/spec/aarch64_be_unknown_netbsd.rs b/compiler/rustc_target/src/spec/aarch64_be_unknown_netbsd.rs new file mode 100644 index 0000000000000..98ae05974aa84 --- /dev/null +++ b/compiler/rustc_target/src/spec/aarch64_be_unknown_netbsd.rs @@ -0,0 +1,17 @@ +use crate::abi::Endian; +use crate::spec::{Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "aarch64_be-unknown-netbsd".into(), + pointer_width: 64, + data_layout: "E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128".into(), + arch: "aarch64".into(), + options: TargetOptions { + mcount: "__mcount".into(), + max_atomic_width: Some(128), + endian: Endian::Big, + ..super::netbsd_base::opts() + }, + } +} diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_hermit.rs b/compiler/rustc_target/src/spec/aarch64_unknown_hermit.rs index 87e8d62702691..2414867be5526 100644 --- a/compiler/rustc_target/src/spec/aarch64_unknown_hermit.rs +++ b/compiler/rustc_target/src/spec/aarch64_unknown_hermit.rs @@ -1,15 +1,15 @@ -use crate::spec::Target; +use crate::spec::{Target, TargetOptions}; pub fn target() -> Target { - let mut base = super::hermit_base::opts(); - base.max_atomic_width = Some(128); - base.features = "+v8a,+strict-align,+neon,+fp-armv8".into(); - Target { llvm_target: "aarch64-unknown-hermit".into(), pointer_width: 64, - data_layout: "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128".into(), arch: "aarch64".into(), - options: base, + data_layout: "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128".into(), + options: TargetOptions { + features: "+v8a,+strict-align,+neon,+fp-armv8".into(), + max_atomic_width: Some(128), + ..super::hermit_base::opts() + }, } } diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_linux_ohos.rs b/compiler/rustc_target/src/spec/aarch64_unknown_linux_ohos.rs index bf1b089f657b4..c8f3db00e01bf 100644 --- a/compiler/rustc_target/src/spec/aarch64_unknown_linux_ohos.rs +++ b/compiler/rustc_target/src/spec/aarch64_unknown_linux_ohos.rs @@ -3,9 +3,7 @@ use crate::spec::{Target, TargetOptions}; use super::SanitizerSet; pub fn target() -> Target { - let mut base = super::linux_musl_base::opts(); - base.env = "ohos".into(); - base.crt_static_default = false; + let mut base = super::linux_ohos_base::opts(); base.max_atomic_width = Some(128); Target { @@ -17,8 +15,6 @@ pub fn target() -> Target { options: TargetOptions { features: "+reserve-x18".into(), mcount: "\u{1}_mcount".into(), - force_emulated_tls: true, - has_thread_local: false, supported_sanitizers: SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::LEAK diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_teeos.rs b/compiler/rustc_target/src/spec/aarch64_unknown_teeos.rs new file mode 100644 index 0000000000000..64a7dc681c896 --- /dev/null +++ b/compiler/rustc_target/src/spec/aarch64_unknown_teeos.rs @@ -0,0 +1,16 @@ +use crate::spec::Target; + +pub fn target() -> Target { + let mut base = super::teeos_base::opts(); + base.features = "+strict-align,+neon,+fp-armv8".into(); + base.max_atomic_width = Some(128); + base.linker = Some("aarch64-linux-gnu-ld".into()); + + Target { + llvm_target: "aarch64-unknown-none".into(), + pointer_width: 64, + data_layout: "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128".into(), + arch: "aarch64".into(), + options: base, + } +} diff --git a/compiler/rustc_target/src/spec/abi.rs b/compiler/rustc_target/src/spec/abi.rs index eb3f66ac308dd..550cdf6bda6f8 100644 --- a/compiler/rustc_target/src/spec/abi.rs +++ b/compiler/rustc_target/src/spec/abi.rs @@ -38,6 +38,8 @@ pub enum Abi { PlatformIntrinsic, Unadjusted, RustCold, + RiscvInterruptM, + RiscvInterruptS, } impl Abi { @@ -107,11 +109,29 @@ const AbiDatas: &[AbiData] = &[ AbiData { abi: Abi::PlatformIntrinsic, name: "platform-intrinsic" }, AbiData { abi: Abi::Unadjusted, name: "unadjusted" }, AbiData { abi: Abi::RustCold, name: "rust-cold" }, + AbiData { abi: Abi::RiscvInterruptM, name: "riscv-interrupt-m" }, + AbiData { abi: Abi::RiscvInterruptS, name: "riscv-interrupt-s" }, ]; +#[derive(Copy, Clone, Debug)] +pub enum AbiUnsupported { + Unrecognized, + Reason { explain: &'static str }, +} + /// Returns the ABI with the given name (if any). -pub fn lookup(name: &str) -> Option { - AbiDatas.iter().find(|abi_data| name == abi_data.name).map(|&x| x.abi) +pub fn lookup(name: &str) -> Result { + AbiDatas.iter().find(|abi_data| name == abi_data.name).map(|&x| x.abi).ok_or_else(|| match name { + "riscv-interrupt" => AbiUnsupported::Reason { + explain: "please use one of riscv-interrupt-m or riscv-interrupt-s for machine- or supervisor-level interrupts, respectively", + }, + "riscv-interrupt-u" => AbiUnsupported::Reason { + explain: "user-mode interrupt handlers have been removed from LLVM pending standardization, see: https://reviews.llvm.org/D149314", + }, + + _ => AbiUnsupported::Unrecognized, + + }) } pub fn all_names() -> Vec<&'static str> { @@ -150,7 +170,8 @@ pub fn is_stable(name: &str) -> Result<(), AbiDisabled> { // Stable "Rust" | "C" | "C-unwind" | "cdecl" | "cdecl-unwind" | "stdcall" | "stdcall-unwind" | "fastcall" | "fastcall-unwind" | "aapcs" | "aapcs-unwind" | "win64" | "win64-unwind" - | "sysv64" | "sysv64-unwind" | "system" | "system-unwind" | "efiapi" => Ok(()), + | "sysv64" | "sysv64-unwind" | "system" | "system-unwind" | "efiapi" | "thiscall" + | "thiscall-unwind" => Ok(()), "rust-intrinsic" => Err(AbiDisabled::Unstable { feature: sym::intrinsics, explain: "intrinsics are subject to change", @@ -167,14 +188,6 @@ pub fn is_stable(name: &str) -> Result<(), AbiDisabled> { feature: sym::abi_vectorcall, explain: "vectorcall-unwind ABI is experimental and subject to change", }), - "thiscall" => Err(AbiDisabled::Unstable { - feature: sym::abi_thiscall, - explain: "thiscall is experimental and subject to change", - }), - "thiscall-unwind" => Err(AbiDisabled::Unstable { - feature: sym::abi_thiscall, - explain: "thiscall-unwind ABI is experimental and subject to change", - }), "rust-call" => Err(AbiDisabled::Unstable { feature: sym::unboxed_closures, explain: "rust-call ABI is subject to change", @@ -207,6 +220,10 @@ pub fn is_stable(name: &str) -> Result<(), AbiDisabled> { feature: sym::abi_avr_interrupt, explain: "avr-interrupt and avr-non-blocking-interrupt ABIs are experimental and subject to change", }), + "riscv-interrupt-m" | "riscv-interrupt-s" => Err(AbiDisabled::Unstable { + feature: sym::abi_riscv_interrupt, + explain: "riscv-interrupt ABIs are experimental and subject to change", + }), "C-cmse-nonsecure-call" => Err(AbiDisabled::Unstable { feature: sym::abi_c_cmse_nonsecure_call, explain: "C-cmse-nonsecure-call ABI is experimental and subject to change", @@ -267,6 +284,8 @@ impl Abi { PlatformIntrinsic => 32, Unadjusted => 33, RustCold => 34, + RiscvInterruptM => 35, + RiscvInterruptS => 36, }; debug_assert!( AbiDatas diff --git a/compiler/rustc_target/src/spec/abi/tests.rs b/compiler/rustc_target/src/spec/abi/tests.rs index 8bea5e5efe3b0..251a12fe7aa6b 100644 --- a/compiler/rustc_target/src/spec/abi/tests.rs +++ b/compiler/rustc_target/src/spec/abi/tests.rs @@ -4,19 +4,19 @@ use super::*; #[test] fn lookup_Rust() { let abi = lookup("Rust"); - assert!(abi.is_some() && abi.unwrap().data().name == "Rust"); + assert!(abi.is_ok() && abi.unwrap().data().name == "Rust"); } #[test] fn lookup_cdecl() { let abi = lookup("cdecl"); - assert!(abi.is_some() && abi.unwrap().data().name == "cdecl"); + assert!(abi.is_ok() && abi.unwrap().data().name == "cdecl"); } #[test] fn lookup_baz() { let abi = lookup("baz"); - assert!(abi.is_none()); + assert!(matches!(abi, Err(AbiUnsupported::Unrecognized))) } #[test] diff --git a/compiler/rustc_target/src/spec/apple/tests.rs b/compiler/rustc_target/src/spec/apple/tests.rs index 3c90a5e7e93ea..3b23ddadcc47c 100644 --- a/compiler/rustc_target/src/spec/apple/tests.rs +++ b/compiler/rustc_target/src/spec/apple/tests.rs @@ -30,6 +30,9 @@ fn macos_link_environment_unmodified() { for target in all_macos_targets { // macOS targets should only remove information for cross-compiling, but never // for the host. - assert_eq!(target.link_env_remove, crate::spec::cvs!["IPHONEOS_DEPLOYMENT_TARGET"]); + assert_eq!( + target.link_env_remove, + crate::spec::cvs!["IPHONEOS_DEPLOYMENT_TARGET", "TVOS_DEPLOYMENT_TARGET"], + ); } } diff --git a/compiler/rustc_target/src/spec/apple_base.rs b/compiler/rustc_target/src/spec/apple_base.rs index ff2246318288e..8a8d1ab95e81e 100644 --- a/compiler/rustc_target/src/spec/apple_base.rs +++ b/compiler/rustc_target/src/spec/apple_base.rs @@ -240,7 +240,12 @@ fn link_env_remove(arch: Arch, os: &'static str) -> StaticCow<[StaticCow]> // Remove the `SDKROOT` environment variable if it's clearly set for the wrong platform, which // may occur when we're linking a custom build script while targeting iOS for example. if let Ok(sdkroot) = env::var("SDKROOT") { - if sdkroot.contains("iPhoneOS.platform") || sdkroot.contains("iPhoneSimulator.platform") + if sdkroot.contains("iPhoneOS.platform") + || sdkroot.contains("iPhoneSimulator.platform") + || sdkroot.contains("AppleTVOS.platform") + || sdkroot.contains("AppleTVSimulator.platform") + || sdkroot.contains("WatchOS.platform") + || sdkroot.contains("WatchSimulator.platform") { env_remove.push("SDKROOT".into()) } @@ -249,6 +254,7 @@ fn link_env_remove(arch: Arch, os: &'static str) -> StaticCow<[StaticCow]> // "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld", // although this is apparently ignored when using the linker at "/usr/bin/ld". env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".into()); + env_remove.push("TVOS_DEPLOYMENT_TARGET".into()); env_remove.into() } else { // Otherwise if cross-compiling for a different OS/SDK, remove any part @@ -299,6 +305,16 @@ fn tvos_lld_platform_version() -> String { format!("{major}.{minor}") } +pub fn tvos_llvm_target(arch: Arch) -> String { + let (major, minor) = tvos_deployment_target(); + format!("{}-apple-tvos{}.{}.0", arch.target_name(), major, minor) +} + +pub fn tvos_sim_llvm_target(arch: Arch) -> String { + let (major, minor) = tvos_deployment_target(); + format!("{}-apple-tvos{}.{}.0-simulator", arch.target_name(), major, minor) +} + fn watchos_deployment_target() -> (u32, u32) { // If you are looking for the default deployment target, prefer `rustc --print deployment-target`. from_set_deployment_target("WATCHOS_DEPLOYMENT_TARGET").unwrap_or((5, 0)) diff --git a/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabi.rs b/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabi.rs index c0f1827ad3f88..400030ca0c60e 100644 --- a/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabi.rs +++ b/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabi.rs @@ -11,6 +11,7 @@ pub fn target() -> Target { features: "+strict-align,+v6".into(), max_atomic_width: Some(64), mcount: "\u{1}__gnu_mcount_nc".into(), + llvm_mcount_intrinsic: Some("llvm.arm.gnu.eabi.mcount".into()), ..super::linux_gnu_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabihf.rs b/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabihf.rs index 79b8958c22ad2..6228fb15a83fc 100644 --- a/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabihf.rs +++ b/compiler/rustc_target/src/spec/arm_unknown_linux_gnueabihf.rs @@ -11,6 +11,7 @@ pub fn target() -> Target { features: "+strict-align,+v6,+vfp2,-d32".into(), max_atomic_width: Some(64), mcount: "\u{1}__gnu_mcount_nc".into(), + llvm_mcount_intrinsic: Some("llvm.arm.gnu.eabi.mcount".into()), ..super::linux_gnu_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/armeb_unknown_linux_gnueabi.rs b/compiler/rustc_target/src/spec/armeb_unknown_linux_gnueabi.rs index 4836f3cf7202b..1d66515a72e07 100644 --- a/compiler/rustc_target/src/spec/armeb_unknown_linux_gnueabi.rs +++ b/compiler/rustc_target/src/spec/armeb_unknown_linux_gnueabi.rs @@ -13,6 +13,7 @@ pub fn target() -> Target { endian: Endian::Big, max_atomic_width: Some(64), mcount: "\u{1}__gnu_mcount_nc".into(), + llvm_mcount_intrinsic: Some("llvm.arm.gnu.eabi.mcount".into()), ..super::linux_gnu_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs b/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs index 5632bcfcefe28..446efa90d0985 100644 --- a/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs +++ b/compiler/rustc_target/src/spec/armebv7r_none_eabi.rs @@ -5,7 +5,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, Targ pub fn target() -> Target { Target { - llvm_target: "armebv7r-unknown-none-eabi".into(), + llvm_target: "armebv7r-none-eabi".into(), pointer_width: 32, data_layout: "E-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".into(), arch: "arm".into(), @@ -18,7 +18,7 @@ pub fn target() -> Target { panic_strategy: PanicStrategy::Abort, max_atomic_width: Some(64), emit_debug_gdb_scripts: false, - // GCC and Clang default to 8 for arm-none here + // GCC defaults to 8 for arm-none here. c_enum_min_bits: Some(8), ..Default::default() }, diff --git a/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs b/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs index 2815de3589db6..0c9e99ff84bfd 100644 --- a/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs +++ b/compiler/rustc_target/src/spec/armebv7r_none_eabihf.rs @@ -5,7 +5,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, Targ pub fn target() -> Target { Target { - llvm_target: "armebv7r-unknown-none-eabihf".into(), + llvm_target: "armebv7r-none-eabihf".into(), pointer_width: 32, data_layout: "E-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".into(), arch: "arm".into(), @@ -19,7 +19,7 @@ pub fn target() -> Target { features: "+vfp3,-d32,-fp16".into(), max_atomic_width: Some(64), emit_debug_gdb_scripts: false, - // GCC and Clang default to 8 for arm-none here + // GCC defaults to 8 for arm-none here. c_enum_min_bits: Some(8), ..Default::default() }, diff --git a/compiler/rustc_target/src/spec/armv4t_none_eabi.rs b/compiler/rustc_target/src/spec/armv4t_none_eabi.rs index d0f988b278ff8..44fdd3178af4f 100644 --- a/compiler/rustc_target/src/spec/armv4t_none_eabi.rs +++ b/compiler/rustc_target/src/spec/armv4t_none_eabi.rs @@ -39,9 +39,9 @@ pub fn target() -> Target { has_thumb_interworking: true, relocation_model: RelocModel::Static, panic_strategy: PanicStrategy::Abort, - // from thumb_base, rust-lang/rust#44993. + // From thumb_base, rust-lang/rust#44993. emit_debug_gdb_scripts: false, - // from thumb_base, apparently gcc/clang give enums a minimum of 8 bits on no-os targets + // From thumb_base, GCC gives enums a minimum of 8 bits on no-os targets. c_enum_min_bits: Some(8), ..Default::default() }, diff --git a/compiler/rustc_target/src/spec/armv4t_unknown_linux_gnueabi.rs b/compiler/rustc_target/src/spec/armv4t_unknown_linux_gnueabi.rs index 1de63a920c80f..cffebcc9581ca 100644 --- a/compiler/rustc_target/src/spec/armv4t_unknown_linux_gnueabi.rs +++ b/compiler/rustc_target/src/spec/armv4t_unknown_linux_gnueabi.rs @@ -12,6 +12,7 @@ pub fn target() -> Target { // Atomic operations provided by compiler-builtins max_atomic_width: Some(32), mcount: "\u{1}__gnu_mcount_nc".into(), + llvm_mcount_intrinsic: Some("llvm.arm.gnu.eabi.mcount".into()), has_thumb_interworking: true, ..super::linux_gnu_base::opts() }, diff --git a/compiler/rustc_target/src/spec/armv6_unknown_freebsd.rs b/compiler/rustc_target/src/spec/armv6_unknown_freebsd.rs index b7cfccc8b3da4..4a8aa31576fa5 100644 --- a/compiler/rustc_target/src/spec/armv6_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/armv6_unknown_freebsd.rs @@ -13,6 +13,7 @@ pub fn target() -> Target { features: "+v6,+vfp2,-d32".into(), max_atomic_width: Some(64), mcount: "\u{1}__gnu_mcount_nc".into(), + llvm_mcount_intrinsic: Some("llvm.arm.gnu.eabi.mcount".into()), ..super::freebsd_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/armv7_sony_vita_newlibeabihf.rs b/compiler/rustc_target/src/spec/armv7_sony_vita_newlibeabihf.rs index e2c0808f1fdbf..62c93603621ab 100644 --- a/compiler/rustc_target/src/spec/armv7_sony_vita_newlibeabihf.rs +++ b/compiler/rustc_target/src/spec/armv7_sony_vita_newlibeabihf.rs @@ -1,15 +1,18 @@ use crate::abi::Endian; -use crate::spec::{cvs, Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, TargetOptions}; +use crate::spec::{cvs, Cc, LinkerFlavor, Lld, RelocModel, Target, TargetOptions}; /// A base target for PlayStation Vita devices using the VITASDK toolchain (using newlib). /// /// Requires the VITASDK toolchain on the host system. pub fn target() -> Target { - let pre_link_args = TargetOptions::link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-Wl,-q"]); + let pre_link_args = TargetOptions::link_args( + LinkerFlavor::Gnu(Cc::Yes, Lld::No), + &["-Wl,-q", "-Wl,--pic-veneer"], + ); Target { - llvm_target: "armv7a-vita-eabihf".into(), + llvm_target: "thumbv7a-vita-eabihf".into(), pointer_width: 32, data_layout: "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".into(), arch: "arm".into(), @@ -18,21 +21,19 @@ pub fn target() -> Target { os: "vita".into(), endian: Endian::Little, c_int_width: "32".into(), - dynamic_linking: false, env: "newlib".into(), vendor: "sony".into(), abi: "eabihf".into(), linker_flavor: LinkerFlavor::Gnu(Cc::Yes, Lld::No), no_default_libraries: false, cpu: "cortex-a9".into(), - executables: true, families: cvs!["unix"], linker: Some("arm-vita-eabi-gcc".into()), relocation_model: RelocModel::Static, - features: "+v7,+neon".into(), + features: "+v7,+neon,+vfp3,+thumb2,+thumb-mode".into(), pre_link_args, exe_suffix: ".elf".into(), - panic_strategy: PanicStrategy::Abort, + has_thumb_interworking: true, max_atomic_width: Some(64), ..Default::default() }, diff --git a/compiler/rustc_target/src/spec/armv7_unknown_linux_gnueabi.rs b/compiler/rustc_target/src/spec/armv7_unknown_linux_gnueabi.rs index 903042d7e7a02..73ae212a7a6eb 100644 --- a/compiler/rustc_target/src/spec/armv7_unknown_linux_gnueabi.rs +++ b/compiler/rustc_target/src/spec/armv7_unknown_linux_gnueabi.rs @@ -14,6 +14,7 @@ pub fn target() -> Target { features: "+v7,+thumb2,+soft-float,-neon".into(), max_atomic_width: Some(64), mcount: "\u{1}__gnu_mcount_nc".into(), + llvm_mcount_intrinsic: Some("llvm.arm.gnu.eabi.mcount".into()), ..super::linux_gnu_base::opts() }, } diff --git a/compiler/rustc_target/src/spec/armv7_unknown_linux_ohos.rs b/compiler/rustc_target/src/spec/armv7_unknown_linux_ohos.rs index 16da245336773..e9b0bda68ef14 100644 --- a/compiler/rustc_target/src/spec/armv7_unknown_linux_ohos.rs +++ b/compiler/rustc_target/src/spec/armv7_unknown_linux_ohos.rs @@ -17,12 +17,8 @@ pub fn target() -> Target { abi: "eabi".into(), features: "+v7,+thumb2,+soft-float,-neon".into(), max_atomic_width: Some(64), - env: "ohos".into(), - crt_static_default: false, mcount: "\u{1}mcount".into(), - force_emulated_tls: true, - has_thread_local: false, - ..super::linux_musl_base::opts() + ..super::linux_ohos_base::opts() }, } } diff --git a/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs b/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs index 8cdf3c36ba2ea..c134f3e090781 100644 --- a/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs +++ b/compiler/rustc_target/src/spec/armv7a_none_eabihf.rs @@ -18,7 +18,7 @@ pub fn target() -> Target { max_atomic_width: Some(64), panic_strategy: PanicStrategy::Abort, emit_debug_gdb_scripts: false, - // GCC and Clang default to 8 for arm-none here + // GCC defaults to 8 for arm-none here. c_enum_min_bits: Some(8), ..Default::default() }; diff --git a/compiler/rustc_target/src/spec/armv7r_none_eabi.rs b/compiler/rustc_target/src/spec/armv7r_none_eabi.rs index 74905ed5a4e07..68b2527985a6f 100644 --- a/compiler/rustc_target/src/spec/armv7r_none_eabi.rs +++ b/compiler/rustc_target/src/spec/armv7r_none_eabi.rs @@ -4,7 +4,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, Targ pub fn target() -> Target { Target { - llvm_target: "armv7r-unknown-none-eabi".into(), + llvm_target: "armv7r-none-eabi".into(), pointer_width: 32, data_layout: "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".into(), arch: "arm".into(), @@ -17,7 +17,7 @@ pub fn target() -> Target { panic_strategy: PanicStrategy::Abort, max_atomic_width: Some(64), emit_debug_gdb_scripts: false, - // GCC and Clang default to 8 for arm-none here + // GCC defaults to 8 for arm-none here. c_enum_min_bits: Some(8), ..Default::default() }, diff --git a/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs b/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs index 516b3f5c17eed..909765a310fed 100644 --- a/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs +++ b/compiler/rustc_target/src/spec/armv7r_none_eabihf.rs @@ -4,7 +4,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, Targ pub fn target() -> Target { Target { - llvm_target: "armv7r-unknown-none-eabihf".into(), + llvm_target: "armv7r-none-eabihf".into(), pointer_width: 32, data_layout: "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64".into(), arch: "arm".into(), @@ -18,7 +18,7 @@ pub fn target() -> Target { features: "+vfp3,-d32,-fp16".into(), max_atomic_width: Some(64), emit_debug_gdb_scripts: false, - // GCC and Clang default to 8 for arm-none here + // GCC defaults to 8 for arm-none here. c_enum_min_bits: Some(8), ..Default::default() }, diff --git a/compiler/rustc_target/src/spec/avr_gnu_base.rs b/compiler/rustc_target/src/spec/avr_gnu_base.rs index 9c3406b53130e..cd324c94bbe8b 100644 --- a/compiler/rustc_target/src/spec/avr_gnu_base.rs +++ b/compiler/rustc_target/src/spec/avr_gnu_base.rs @@ -1,4 +1,5 @@ use crate::spec::{Cc, LinkerFlavor, Lld, RelocModel, Target, TargetOptions}; +use object::elf; /// A base target for AVR devices using the GNU toolchain. /// @@ -22,10 +23,346 @@ pub fn target(target_cpu: &'static str, mmcu: &'static str) -> Target { LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-lgcc"], ), - max_atomic_width: Some(0), + max_atomic_width: Some(16), atomic_cas: false, relocation_model: RelocModel::Static, ..TargetOptions::default() }, } } + +/// Resolve the value of the EF_AVR_ARCH field for AVR ELF files, given the +/// name of the target CPU / MCU. +/// +/// In ELF files using the AVR architecture, the lower 7 bits of the e_flags +/// field is a code that identifies the "ISA revision" of the object code. +/// +/// This flag is generally set by AVR compilers in their output ELF files, +/// and linkers like avr-ld check this flag in all of their input files to +/// make sure they are compiled with the same ISA revision. +pub fn ef_avr_arch(target_cpu: &str) -> u32 { + // Adapted from llvm-project/llvm/lib/target/AVR/AVRDevices.td + match target_cpu { + // Generic MCUs + "avr1" => elf::EF_AVR_ARCH_AVR1, + "avr2" => elf::EF_AVR_ARCH_AVR2, + "avr25" => elf::EF_AVR_ARCH_AVR25, + "avr3" => elf::EF_AVR_ARCH_AVR3, + "avr31" => elf::EF_AVR_ARCH_AVR31, + "avr35" => elf::EF_AVR_ARCH_AVR35, + "avr4" => elf::EF_AVR_ARCH_AVR4, + "avr5" => elf::EF_AVR_ARCH_AVR5, + "avr51" => elf::EF_AVR_ARCH_AVR51, + "avr6" => elf::EF_AVR_ARCH_AVR6, + "avrxmega1" => elf::EF_AVR_ARCH_XMEGA1, + "avrxmega2" => elf::EF_AVR_ARCH_XMEGA2, + "avrxmega3" => elf::EF_AVR_ARCH_XMEGA3, + "avrxmega4" => elf::EF_AVR_ARCH_XMEGA4, + "avrxmega5" => elf::EF_AVR_ARCH_XMEGA5, + "avrxmega6" => elf::EF_AVR_ARCH_XMEGA6, + "avrxmega7" => elf::EF_AVR_ARCH_XMEGA7, + "avrtiny" => elf::EF_AVR_ARCH_AVRTINY, + + // Specific MCUs + "at90s1200" => elf::EF_AVR_ARCH_AVR1, + "attiny11" => elf::EF_AVR_ARCH_AVR1, + "attiny12" => elf::EF_AVR_ARCH_AVR1, + "attiny15" => elf::EF_AVR_ARCH_AVR1, + "attiny28" => elf::EF_AVR_ARCH_AVR1, + "at90s2313" => elf::EF_AVR_ARCH_AVR2, + "at90s2323" => elf::EF_AVR_ARCH_AVR2, + "at90s2333" => elf::EF_AVR_ARCH_AVR2, + "at90s2343" => elf::EF_AVR_ARCH_AVR2, + "attiny22" => elf::EF_AVR_ARCH_AVR2, + "attiny26" => elf::EF_AVR_ARCH_AVR2, + "at86rf401" => elf::EF_AVR_ARCH_AVR25, + "at90s4414" => elf::EF_AVR_ARCH_AVR2, + "at90s4433" => elf::EF_AVR_ARCH_AVR2, + "at90s4434" => elf::EF_AVR_ARCH_AVR2, + "at90s8515" => elf::EF_AVR_ARCH_AVR2, + "at90c8534" => elf::EF_AVR_ARCH_AVR2, + "at90s8535" => elf::EF_AVR_ARCH_AVR2, + "ata5272" => elf::EF_AVR_ARCH_AVR25, + "ata6616c" => elf::EF_AVR_ARCH_AVR25, + "attiny13" => elf::EF_AVR_ARCH_AVR25, + "attiny13a" => elf::EF_AVR_ARCH_AVR25, + "attiny2313" => elf::EF_AVR_ARCH_AVR25, + "attiny2313a" => elf::EF_AVR_ARCH_AVR25, + "attiny24" => elf::EF_AVR_ARCH_AVR25, + "attiny24a" => elf::EF_AVR_ARCH_AVR25, + "attiny4313" => elf::EF_AVR_ARCH_AVR25, + "attiny44" => elf::EF_AVR_ARCH_AVR25, + "attiny44a" => elf::EF_AVR_ARCH_AVR25, + "attiny84" => elf::EF_AVR_ARCH_AVR25, + "attiny84a" => elf::EF_AVR_ARCH_AVR25, + "attiny25" => elf::EF_AVR_ARCH_AVR25, + "attiny45" => elf::EF_AVR_ARCH_AVR25, + "attiny85" => elf::EF_AVR_ARCH_AVR25, + "attiny261" => elf::EF_AVR_ARCH_AVR25, + "attiny261a" => elf::EF_AVR_ARCH_AVR25, + "attiny441" => elf::EF_AVR_ARCH_AVR25, + "attiny461" => elf::EF_AVR_ARCH_AVR25, + "attiny461a" => elf::EF_AVR_ARCH_AVR25, + "attiny841" => elf::EF_AVR_ARCH_AVR25, + "attiny861" => elf::EF_AVR_ARCH_AVR25, + "attiny861a" => elf::EF_AVR_ARCH_AVR25, + "attiny87" => elf::EF_AVR_ARCH_AVR25, + "attiny43u" => elf::EF_AVR_ARCH_AVR25, + "attiny48" => elf::EF_AVR_ARCH_AVR25, + "attiny88" => elf::EF_AVR_ARCH_AVR25, + "attiny828" => elf::EF_AVR_ARCH_AVR25, + "at43usb355" => elf::EF_AVR_ARCH_AVR3, + "at76c711" => elf::EF_AVR_ARCH_AVR3, + "atmega103" => elf::EF_AVR_ARCH_AVR31, + "at43usb320" => elf::EF_AVR_ARCH_AVR31, + "attiny167" => elf::EF_AVR_ARCH_AVR35, + "at90usb82" => elf::EF_AVR_ARCH_AVR35, + "at90usb162" => elf::EF_AVR_ARCH_AVR35, + "ata5505" => elf::EF_AVR_ARCH_AVR35, + "ata6617c" => elf::EF_AVR_ARCH_AVR35, + "ata664251" => elf::EF_AVR_ARCH_AVR35, + "atmega8u2" => elf::EF_AVR_ARCH_AVR35, + "atmega16u2" => elf::EF_AVR_ARCH_AVR35, + "atmega32u2" => elf::EF_AVR_ARCH_AVR35, + "attiny1634" => elf::EF_AVR_ARCH_AVR35, + "atmega8" => elf::EF_AVR_ARCH_AVR4, + "ata6289" => elf::EF_AVR_ARCH_AVR4, + "atmega8a" => elf::EF_AVR_ARCH_AVR4, + "ata6285" => elf::EF_AVR_ARCH_AVR4, + "ata6286" => elf::EF_AVR_ARCH_AVR4, + "ata6612c" => elf::EF_AVR_ARCH_AVR4, + "atmega48" => elf::EF_AVR_ARCH_AVR4, + "atmega48a" => elf::EF_AVR_ARCH_AVR4, + "atmega48pa" => elf::EF_AVR_ARCH_AVR4, + "atmega48pb" => elf::EF_AVR_ARCH_AVR4, + "atmega48p" => elf::EF_AVR_ARCH_AVR4, + "atmega88" => elf::EF_AVR_ARCH_AVR4, + "atmega88a" => elf::EF_AVR_ARCH_AVR4, + "atmega88p" => elf::EF_AVR_ARCH_AVR4, + "atmega88pa" => elf::EF_AVR_ARCH_AVR4, + "atmega88pb" => elf::EF_AVR_ARCH_AVR4, + "atmega8515" => elf::EF_AVR_ARCH_AVR4, + "atmega8535" => elf::EF_AVR_ARCH_AVR4, + "atmega8hva" => elf::EF_AVR_ARCH_AVR4, + "at90pwm1" => elf::EF_AVR_ARCH_AVR4, + "at90pwm2" => elf::EF_AVR_ARCH_AVR4, + "at90pwm2b" => elf::EF_AVR_ARCH_AVR4, + "at90pwm3" => elf::EF_AVR_ARCH_AVR4, + "at90pwm3b" => elf::EF_AVR_ARCH_AVR4, + "at90pwm81" => elf::EF_AVR_ARCH_AVR4, + "ata5702m322" => elf::EF_AVR_ARCH_AVR5, + "ata5782" => elf::EF_AVR_ARCH_AVR5, + "ata5790" => elf::EF_AVR_ARCH_AVR5, + "ata5790n" => elf::EF_AVR_ARCH_AVR5, + "ata5791" => elf::EF_AVR_ARCH_AVR5, + "ata5795" => elf::EF_AVR_ARCH_AVR5, + "ata5831" => elf::EF_AVR_ARCH_AVR5, + "ata6613c" => elf::EF_AVR_ARCH_AVR5, + "ata6614q" => elf::EF_AVR_ARCH_AVR5, + "ata8210" => elf::EF_AVR_ARCH_AVR5, + "ata8510" => elf::EF_AVR_ARCH_AVR5, + "atmega16" => elf::EF_AVR_ARCH_AVR5, + "atmega16a" => elf::EF_AVR_ARCH_AVR5, + "atmega161" => elf::EF_AVR_ARCH_AVR5, + "atmega162" => elf::EF_AVR_ARCH_AVR5, + "atmega163" => elf::EF_AVR_ARCH_AVR5, + "atmega164a" => elf::EF_AVR_ARCH_AVR5, + "atmega164p" => elf::EF_AVR_ARCH_AVR5, + "atmega164pa" => elf::EF_AVR_ARCH_AVR5, + "atmega165" => elf::EF_AVR_ARCH_AVR5, + "atmega165a" => elf::EF_AVR_ARCH_AVR5, + "atmega165p" => elf::EF_AVR_ARCH_AVR5, + "atmega165pa" => elf::EF_AVR_ARCH_AVR5, + "atmega168" => elf::EF_AVR_ARCH_AVR5, + "atmega168a" => elf::EF_AVR_ARCH_AVR5, + "atmega168p" => elf::EF_AVR_ARCH_AVR5, + "atmega168pa" => elf::EF_AVR_ARCH_AVR5, + "atmega168pb" => elf::EF_AVR_ARCH_AVR5, + "atmega169" => elf::EF_AVR_ARCH_AVR5, + "atmega169a" => elf::EF_AVR_ARCH_AVR5, + "atmega169p" => elf::EF_AVR_ARCH_AVR5, + "atmega169pa" => elf::EF_AVR_ARCH_AVR5, + "atmega32" => elf::EF_AVR_ARCH_AVR5, + "atmega32a" => elf::EF_AVR_ARCH_AVR5, + "atmega323" => elf::EF_AVR_ARCH_AVR5, + "atmega324a" => elf::EF_AVR_ARCH_AVR5, + "atmega324p" => elf::EF_AVR_ARCH_AVR5, + "atmega324pa" => elf::EF_AVR_ARCH_AVR5, + "atmega324pb" => elf::EF_AVR_ARCH_AVR5, + "atmega325" => elf::EF_AVR_ARCH_AVR5, + "atmega325a" => elf::EF_AVR_ARCH_AVR5, + "atmega325p" => elf::EF_AVR_ARCH_AVR5, + "atmega325pa" => elf::EF_AVR_ARCH_AVR5, + "atmega3250" => elf::EF_AVR_ARCH_AVR5, + "atmega3250a" => elf::EF_AVR_ARCH_AVR5, + "atmega3250p" => elf::EF_AVR_ARCH_AVR5, + "atmega3250pa" => elf::EF_AVR_ARCH_AVR5, + "atmega328" => elf::EF_AVR_ARCH_AVR5, + "atmega328p" => elf::EF_AVR_ARCH_AVR5, + "atmega328pb" => elf::EF_AVR_ARCH_AVR5, + "atmega329" => elf::EF_AVR_ARCH_AVR5, + "atmega329a" => elf::EF_AVR_ARCH_AVR5, + "atmega329p" => elf::EF_AVR_ARCH_AVR5, + "atmega329pa" => elf::EF_AVR_ARCH_AVR5, + "atmega3290" => elf::EF_AVR_ARCH_AVR5, + "atmega3290a" => elf::EF_AVR_ARCH_AVR5, + "atmega3290p" => elf::EF_AVR_ARCH_AVR5, + "atmega3290pa" => elf::EF_AVR_ARCH_AVR5, + "atmega406" => elf::EF_AVR_ARCH_AVR5, + "atmega64" => elf::EF_AVR_ARCH_AVR5, + "atmega64a" => elf::EF_AVR_ARCH_AVR5, + "atmega640" => elf::EF_AVR_ARCH_AVR5, + "atmega644" => elf::EF_AVR_ARCH_AVR5, + "atmega644a" => elf::EF_AVR_ARCH_AVR5, + "atmega644p" => elf::EF_AVR_ARCH_AVR5, + "atmega644pa" => elf::EF_AVR_ARCH_AVR5, + "atmega645" => elf::EF_AVR_ARCH_AVR5, + "atmega645a" => elf::EF_AVR_ARCH_AVR5, + "atmega645p" => elf::EF_AVR_ARCH_AVR5, + "atmega649" => elf::EF_AVR_ARCH_AVR5, + "atmega649a" => elf::EF_AVR_ARCH_AVR5, + "atmega649p" => elf::EF_AVR_ARCH_AVR5, + "atmega6450" => elf::EF_AVR_ARCH_AVR5, + "atmega6450a" => elf::EF_AVR_ARCH_AVR5, + "atmega6450p" => elf::EF_AVR_ARCH_AVR5, + "atmega6490" => elf::EF_AVR_ARCH_AVR5, + "atmega6490a" => elf::EF_AVR_ARCH_AVR5, + "atmega6490p" => elf::EF_AVR_ARCH_AVR5, + "atmega64rfr2" => elf::EF_AVR_ARCH_AVR5, + "atmega644rfr2" => elf::EF_AVR_ARCH_AVR5, + "atmega16hva" => elf::EF_AVR_ARCH_AVR5, + "atmega16hva2" => elf::EF_AVR_ARCH_AVR5, + "atmega16hvb" => elf::EF_AVR_ARCH_AVR5, + "atmega16hvbrevb" => elf::EF_AVR_ARCH_AVR5, + "atmega32hvb" => elf::EF_AVR_ARCH_AVR5, + "atmega32hvbrevb" => elf::EF_AVR_ARCH_AVR5, + "atmega64hve" => elf::EF_AVR_ARCH_AVR5, + "atmega64hve2" => elf::EF_AVR_ARCH_AVR5, + "at90can32" => elf::EF_AVR_ARCH_AVR5, + "at90can64" => elf::EF_AVR_ARCH_AVR5, + "at90pwm161" => elf::EF_AVR_ARCH_AVR5, + "at90pwm216" => elf::EF_AVR_ARCH_AVR5, + "at90pwm316" => elf::EF_AVR_ARCH_AVR5, + "atmega32c1" => elf::EF_AVR_ARCH_AVR5, + "atmega64c1" => elf::EF_AVR_ARCH_AVR5, + "atmega16m1" => elf::EF_AVR_ARCH_AVR5, + "atmega32m1" => elf::EF_AVR_ARCH_AVR5, + "atmega64m1" => elf::EF_AVR_ARCH_AVR5, + "atmega16u4" => elf::EF_AVR_ARCH_AVR5, + "atmega32u4" => elf::EF_AVR_ARCH_AVR5, + "atmega32u6" => elf::EF_AVR_ARCH_AVR5, + "at90usb646" => elf::EF_AVR_ARCH_AVR5, + "at90usb647" => elf::EF_AVR_ARCH_AVR5, + "at90scr100" => elf::EF_AVR_ARCH_AVR5, + "at94k" => elf::EF_AVR_ARCH_AVR5, + "m3000" => elf::EF_AVR_ARCH_AVR5, + "atmega128" => elf::EF_AVR_ARCH_AVR51, + "atmega128a" => elf::EF_AVR_ARCH_AVR51, + "atmega1280" => elf::EF_AVR_ARCH_AVR51, + "atmega1281" => elf::EF_AVR_ARCH_AVR51, + "atmega1284" => elf::EF_AVR_ARCH_AVR51, + "atmega1284p" => elf::EF_AVR_ARCH_AVR51, + "atmega128rfa1" => elf::EF_AVR_ARCH_AVR51, + "atmega128rfr2" => elf::EF_AVR_ARCH_AVR51, + "atmega1284rfr2" => elf::EF_AVR_ARCH_AVR51, + "at90can128" => elf::EF_AVR_ARCH_AVR51, + "at90usb1286" => elf::EF_AVR_ARCH_AVR51, + "at90usb1287" => elf::EF_AVR_ARCH_AVR51, + "atmega2560" => elf::EF_AVR_ARCH_AVR6, + "atmega2561" => elf::EF_AVR_ARCH_AVR6, + "atmega256rfr2" => elf::EF_AVR_ARCH_AVR6, + "atmega2564rfr2" => elf::EF_AVR_ARCH_AVR6, + "atxmega16a4" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega16a4u" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega16c4" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega16d4" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32a4" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32a4u" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32c3" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32c4" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32d3" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32d4" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega32e5" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega16e5" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega8e5" => elf::EF_AVR_ARCH_XMEGA2, + "atxmega64a3" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64a3u" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64a4u" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64b1" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64b3" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64c3" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64d3" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64d4" => elf::EF_AVR_ARCH_XMEGA4, + "atxmega64a1" => elf::EF_AVR_ARCH_XMEGA5, + "atxmega64a1u" => elf::EF_AVR_ARCH_XMEGA5, + "atxmega128a3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128a3u" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128b1" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128b3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128c3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128d3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128d4" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega192a3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega192a3u" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega192c3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega192d3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega256a3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega256a3u" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega256a3b" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega256a3bu" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega256c3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega256d3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega384c3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega384d3" => elf::EF_AVR_ARCH_XMEGA6, + "atxmega128a1" => elf::EF_AVR_ARCH_XMEGA7, + "atxmega128a1u" => elf::EF_AVR_ARCH_XMEGA7, + "atxmega128a4u" => elf::EF_AVR_ARCH_XMEGA7, + "attiny4" => elf::EF_AVR_ARCH_AVRTINY, + "attiny5" => elf::EF_AVR_ARCH_AVRTINY, + "attiny9" => elf::EF_AVR_ARCH_AVRTINY, + "attiny10" => elf::EF_AVR_ARCH_AVRTINY, + "attiny20" => elf::EF_AVR_ARCH_AVRTINY, + "attiny40" => elf::EF_AVR_ARCH_AVRTINY, + "attiny102" => elf::EF_AVR_ARCH_AVRTINY, + "attiny104" => elf::EF_AVR_ARCH_AVRTINY, + "attiny202" => elf::EF_AVR_ARCH_XMEGA3, + "attiny402" => elf::EF_AVR_ARCH_XMEGA3, + "attiny204" => elf::EF_AVR_ARCH_XMEGA3, + "attiny404" => elf::EF_AVR_ARCH_XMEGA3, + "attiny804" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1604" => elf::EF_AVR_ARCH_XMEGA3, + "attiny406" => elf::EF_AVR_ARCH_XMEGA3, + "attiny806" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1606" => elf::EF_AVR_ARCH_XMEGA3, + "attiny807" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1607" => elf::EF_AVR_ARCH_XMEGA3, + "attiny212" => elf::EF_AVR_ARCH_XMEGA3, + "attiny412" => elf::EF_AVR_ARCH_XMEGA3, + "attiny214" => elf::EF_AVR_ARCH_XMEGA3, + "attiny414" => elf::EF_AVR_ARCH_XMEGA3, + "attiny814" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1614" => elf::EF_AVR_ARCH_XMEGA3, + "attiny416" => elf::EF_AVR_ARCH_XMEGA3, + "attiny816" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1616" => elf::EF_AVR_ARCH_XMEGA3, + "attiny3216" => elf::EF_AVR_ARCH_XMEGA3, + "attiny417" => elf::EF_AVR_ARCH_XMEGA3, + "attiny817" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1617" => elf::EF_AVR_ARCH_XMEGA3, + "attiny3217" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1624" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1626" => elf::EF_AVR_ARCH_XMEGA3, + "attiny1627" => elf::EF_AVR_ARCH_XMEGA3, + "atmega808" => elf::EF_AVR_ARCH_XMEGA3, + "atmega809" => elf::EF_AVR_ARCH_XMEGA3, + "atmega1608" => elf::EF_AVR_ARCH_XMEGA3, + "atmega1609" => elf::EF_AVR_ARCH_XMEGA3, + "atmega3208" => elf::EF_AVR_ARCH_XMEGA3, + "atmega3209" => elf::EF_AVR_ARCH_XMEGA3, + "atmega4808" => elf::EF_AVR_ARCH_XMEGA3, + "atmega4809" => elf::EF_AVR_ARCH_XMEGA3, + + // Unknown target CPU => Unspecified/generic code + _ => 0, + } +} diff --git a/compiler/rustc_target/src/spec/csky_unknown_linux_gnuabiv2.rs b/compiler/rustc_target/src/spec/csky_unknown_linux_gnuabiv2.rs new file mode 100644 index 0000000000000..7d03dd26f5ded --- /dev/null +++ b/compiler/rustc_target/src/spec/csky_unknown_linux_gnuabiv2.rs @@ -0,0 +1,20 @@ +use crate::spec::{Cc, LinkerFlavor, Lld, Target, TargetOptions}; + +// This target is for glibc Linux on Csky + +pub fn target() -> Target { + Target { + //https://github.com/llvm/llvm-project/blob/8b76aea8d8b1b71f6220bc2845abc749f18a19b7/clang/lib/Basic/Targets/CSKY.h + llvm_target: "csky-unknown-linux-gnuabiv2".into(), + pointer_width: 32, + data_layout: "e-m:e-S32-p:32:32-i32:32:32-i64:32:32-f32:32:32-f64:32:32-v64:32:32-v128:32:32-a:0:32-Fi32-n32".into(), + arch: "csky".into(), + options: TargetOptions { + abi: "abiv2".into(), + features: "+2e3,+3e7,+7e10,+cache,+dsp1e2,+dspe60,+e1,+e2,+edsp,+elrw,+hard-tp,+high-registers,+hwdiv,+mp,+mp1e2,+nvic,+trust".into(), + late_link_args_static: TargetOptions::link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-l:libatomic.a"]), + max_atomic_width: Some(32), + ..super::linux_gnu_base::opts() + }, + } +} diff --git a/compiler/rustc_target/src/spec/hermit_base.rs b/compiler/rustc_target/src/spec/hermit_base.rs index dd9991381e7b9..c6e98fc1a115f 100644 --- a/compiler/rustc_target/src/spec/hermit_base.rs +++ b/compiler/rustc_target/src/spec/hermit_base.rs @@ -1,21 +1,15 @@ use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, TargetOptions, TlsModel}; pub fn opts() -> TargetOptions { - let pre_link_args = TargetOptions::link_args( - LinkerFlavor::Gnu(Cc::No, Lld::No), - &["--build-id", "--hash-style=gnu", "--Bstatic"], - ); - TargetOptions { os: "hermit".into(), - linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), linker: Some("rust-lld".into()), - has_thread_local: true, - pre_link_args, - panic_strategy: PanicStrategy::Abort, + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + tls_model: TlsModel::InitialExec, position_independent_executables: true, static_position_independent_executables: true, - tls_model: TlsModel::InitialExec, + has_thread_local: true, + panic_strategy: PanicStrategy::Abort, ..Default::default() } } diff --git a/compiler/rustc_target/src/spec/linux_ohos_base.rs b/compiler/rustc_target/src/spec/linux_ohos_base.rs new file mode 100644 index 0000000000000..4ad4c837336f8 --- /dev/null +++ b/compiler/rustc_target/src/spec/linux_ohos_base.rs @@ -0,0 +1,12 @@ +use crate::spec::TargetOptions; + +pub fn opts() -> TargetOptions { + let mut base = super::linux_base::opts(); + + base.env = "ohos".into(); + base.crt_static_default = false; + base.force_emulated_tls = true; + base.has_thread_local = false; + + base +} diff --git a/compiler/rustc_target/src/spec/loongarch64_unknown_none.rs b/compiler/rustc_target/src/spec/loongarch64_unknown_none.rs new file mode 100644 index 0000000000000..dbc96d68eae17 --- /dev/null +++ b/compiler/rustc_target/src/spec/loongarch64_unknown_none.rs @@ -0,0 +1,23 @@ +use super::{Cc, CodeModel, LinkerFlavor, Lld, PanicStrategy, RelocModel}; +use super::{Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "loongarch64-unknown-none".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n64-S128".into(), + arch: "loongarch64".into(), + options: TargetOptions { + cpu: "generic".into(), + features: "+f,+d".into(), + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + linker: Some("rust-lld".into()), + llvm_abiname: "lp64d".into(), + max_atomic_width: Some(64), + relocation_model: RelocModel::Static, + panic_strategy: PanicStrategy::Abort, + code_model: Some(CodeModel::Small), + ..Default::default() + }, + } +} diff --git a/compiler/rustc_target/src/spec/loongarch64_unknown_none_softfloat.rs b/compiler/rustc_target/src/spec/loongarch64_unknown_none_softfloat.rs new file mode 100644 index 0000000000000..c4d5c7bc44ccd --- /dev/null +++ b/compiler/rustc_target/src/spec/loongarch64_unknown_none_softfloat.rs @@ -0,0 +1,24 @@ +use super::{Cc, CodeModel, LinkerFlavor, Lld, PanicStrategy, RelocModel}; +use super::{Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "loongarch64-unknown-none".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n64-S128".into(), + arch: "loongarch64".into(), + options: TargetOptions { + cpu: "generic".into(), + features: "-f,-d".into(), + abi: "softfloat".into(), + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + linker: Some("rust-lld".into()), + llvm_abiname: "lp64s".into(), + max_atomic_width: Some(64), + relocation_model: RelocModel::Static, + panic_strategy: PanicStrategy::Abort, + code_model: Some(CodeModel::Small), + ..Default::default() + }, + } +} diff --git a/compiler/rustc_target/src/spec/mips64_unknown_linux_gnuabi64.rs b/compiler/rustc_target/src/spec/mips64_unknown_linux_gnuabi64.rs index fc5dbd114e425..b9df0046b12ba 100644 --- a/compiler/rustc_target/src/spec/mips64_unknown_linux_gnuabi64.rs +++ b/compiler/rustc_target/src/spec/mips64_unknown_linux_gnuabi64.rs @@ -12,7 +12,7 @@ pub fn target() -> Target { endian: Endian::Big, // NOTE(mips64r2) matches C toolchain cpu: "mips64r2".into(), - features: "+mips64r2".into(), + features: "+mips64r2,+xgot".into(), max_atomic_width: Some(64), mcount: "_mcount".into(), diff --git a/compiler/rustc_target/src/spec/mips64el_unknown_linux_gnuabi64.rs b/compiler/rustc_target/src/spec/mips64el_unknown_linux_gnuabi64.rs index e0d5f6f57f156..57ad8c47399b3 100644 --- a/compiler/rustc_target/src/spec/mips64el_unknown_linux_gnuabi64.rs +++ b/compiler/rustc_target/src/spec/mips64el_unknown_linux_gnuabi64.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { abi: "abi64".into(), // NOTE(mips64r2) matches C toolchain cpu: "mips64r2".into(), - features: "+mips64r2".into(), + features: "+mips64r2,+xgot".into(), max_atomic_width: Some(64), mcount: "_mcount".into(), diff --git a/compiler/rustc_target/src/spec/mipsisa32r6_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/mipsisa32r6_unknown_linux_gnu.rs index 1e066b271e28a..983a449b006a9 100644 --- a/compiler/rustc_target/src/spec/mipsisa32r6_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/mipsisa32r6_unknown_linux_gnu.rs @@ -6,7 +6,7 @@ pub fn target() -> Target { llvm_target: "mipsisa32r6-unknown-linux-gnu".into(), pointer_width: 32, data_layout: "E-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64".into(), - arch: "mips".into(), + arch: "mips32r6".into(), options: TargetOptions { endian: Endian::Big, cpu: "mips32r6".into(), diff --git a/compiler/rustc_target/src/spec/mipsisa32r6el_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/mipsisa32r6el_unknown_linux_gnu.rs index 4785929c1000b..ec0facdfb7bfe 100644 --- a/compiler/rustc_target/src/spec/mipsisa32r6el_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/mipsisa32r6el_unknown_linux_gnu.rs @@ -5,7 +5,7 @@ pub fn target() -> Target { llvm_target: "mipsisa32r6el-unknown-linux-gnu".into(), pointer_width: 32, data_layout: "e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64".into(), - arch: "mips".into(), + arch: "mips32r6".into(), options: TargetOptions { cpu: "mips32r6".into(), diff --git a/compiler/rustc_target/src/spec/mipsisa64r6_unknown_linux_gnuabi64.rs b/compiler/rustc_target/src/spec/mipsisa64r6_unknown_linux_gnuabi64.rs index 766ac76806448..16dd1c416f4f2 100644 --- a/compiler/rustc_target/src/spec/mipsisa64r6_unknown_linux_gnuabi64.rs +++ b/compiler/rustc_target/src/spec/mipsisa64r6_unknown_linux_gnuabi64.rs @@ -6,7 +6,7 @@ pub fn target() -> Target { llvm_target: "mipsisa64r6-unknown-linux-gnuabi64".into(), pointer_width: 64, data_layout: "E-m:e-i8:8:32-i16:16:32-i64:64-n32:64-S128".into(), - arch: "mips64".into(), + arch: "mips64r6".into(), options: TargetOptions { abi: "abi64".into(), endian: Endian::Big, diff --git a/compiler/rustc_target/src/spec/mipsisa64r6el_unknown_linux_gnuabi64.rs b/compiler/rustc_target/src/spec/mipsisa64r6el_unknown_linux_gnuabi64.rs index d2b07c654dcf8..8d0a6aa8f5145 100644 --- a/compiler/rustc_target/src/spec/mipsisa64r6el_unknown_linux_gnuabi64.rs +++ b/compiler/rustc_target/src/spec/mipsisa64r6el_unknown_linux_gnuabi64.rs @@ -5,7 +5,7 @@ pub fn target() -> Target { llvm_target: "mipsisa64r6el-unknown-linux-gnuabi64".into(), pointer_width: 64, data_layout: "e-m:e-i8:8:32-i16:16:32-i64:64-n32:64-S128".into(), - arch: "mips64".into(), + arch: "mips64r6".into(), options: TargetOptions { abi: "abi64".into(), // NOTE(mips64r6) matches C toolchain diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index ba4b89c9ea10b..5bef63b1a2e64 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -42,7 +42,7 @@ use crate::spec::crt_objects::{CrtObjects, LinkSelfContainedDefault}; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_fs_util::try_canonicalize; use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::{kw, sym, Symbol}; use serde_json::Value; use std::borrow::Cow; use std::collections::BTreeMap; @@ -62,6 +62,7 @@ mod android_base; mod apple_base; pub use apple_base::deployment_target as current_apple_deployment_target; mod avr_gnu_base; +pub use avr_gnu_base::ef_avr_arch; mod bpf_base; mod dragonfly_base; mod freebsd_base; @@ -73,6 +74,7 @@ mod l4re_base; mod linux_base; mod linux_gnu_base; mod linux_musl_base; +mod linux_ohos_base; mod linux_uclibc_base; mod msvc_base; mod netbsd_base; @@ -81,8 +83,10 @@ mod openbsd_base; mod redox_base; mod solaris_base; mod solid_base; +mod teeos_base; mod thumb_base; mod uefi_msvc_base; +mod unikraft_linux_musl_base; mod vxworks_base; mod wasm_base; mod windows_gnu_base; @@ -160,15 +164,49 @@ pub enum LinkerFlavor { /// linker flavors (`LinkerFlavor`). #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum LinkerFlavorCli { + // New (unstable) flavors, with direct counterparts in `LinkerFlavor`. + Gnu(Cc, Lld), + Darwin(Cc, Lld), + WasmLld(Cc), + Unix(Cc), + // Note: `Msvc(Lld::No)` is also a stable value. + Msvc(Lld), + EmCc, + Bpf, + Ptx, + + // Below: the legacy stable values. Gcc, Ld, Lld(LldFlavor), - Msvc, Em, BpfLinker, PtxLinker, } +impl LinkerFlavorCli { + /// Returns whether this `-C linker-flavor` option is one of the unstable values. + pub fn is_unstable(&self) -> bool { + match self { + LinkerFlavorCli::Gnu(..) + | LinkerFlavorCli::Darwin(..) + | LinkerFlavorCli::WasmLld(..) + | LinkerFlavorCli::Unix(..) + | LinkerFlavorCli::Msvc(Lld::Yes) + | LinkerFlavorCli::EmCc + | LinkerFlavorCli::Bpf + | LinkerFlavorCli::Ptx + | LinkerFlavorCli::BpfLinker + | LinkerFlavorCli::PtxLinker => true, + LinkerFlavorCli::Gcc + | LinkerFlavorCli::Ld + | LinkerFlavorCli::Lld(..) + | LinkerFlavorCli::Msvc(Lld::No) + | LinkerFlavorCli::Em => false, + } + } +} + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub enum LldFlavor { Wasm, @@ -205,16 +243,22 @@ impl ToJson for LldFlavor { } impl LinkerFlavor { - pub fn from_cli(cli: LinkerFlavorCli, target: &TargetOptions) -> LinkerFlavor { - Self::from_cli_impl(cli, target.linker_flavor.lld_flavor(), target.linker_flavor.is_gnu()) - } - - /// The passed CLI flavor is preferred over other args coming from the default target spec, - /// so this function can produce a flavor that is incompatible with the current target. - /// FIXME: Produce errors when `-Clinker-flavor` is set to something incompatible - /// with the current target. - fn from_cli_impl(cli: LinkerFlavorCli, lld_flavor: LldFlavor, is_gnu: bool) -> LinkerFlavor { + /// At this point the target's reference linker flavor doesn't yet exist and we need to infer + /// it. The inference always succeds and gives some result, and we don't report any flavor + /// incompatibility errors for json target specs. The CLI flavor is used as the main source + /// of truth, other flags are used in case of ambiguities. + fn from_cli_json(cli: LinkerFlavorCli, lld_flavor: LldFlavor, is_gnu: bool) -> LinkerFlavor { match cli { + LinkerFlavorCli::Gnu(cc, lld) => LinkerFlavor::Gnu(cc, lld), + LinkerFlavorCli::Darwin(cc, lld) => LinkerFlavor::Darwin(cc, lld), + LinkerFlavorCli::WasmLld(cc) => LinkerFlavor::WasmLld(cc), + LinkerFlavorCli::Unix(cc) => LinkerFlavor::Unix(cc), + LinkerFlavorCli::Msvc(lld) => LinkerFlavor::Msvc(lld), + LinkerFlavorCli::EmCc => LinkerFlavor::EmCc, + LinkerFlavorCli::Bpf => LinkerFlavor::Bpf, + LinkerFlavorCli::Ptx => LinkerFlavor::Ptx, + + // Below: legacy stable values LinkerFlavorCli::Gcc => match lld_flavor { LldFlavor::Ld if is_gnu => LinkerFlavor::Gnu(Cc::Yes, Lld::No), LldFlavor::Ld64 => LinkerFlavor::Darwin(Cc::Yes, Lld::No), @@ -230,7 +274,6 @@ impl LinkerFlavor { LinkerFlavorCli::Lld(LldFlavor::Ld64) => LinkerFlavor::Darwin(Cc::No, Lld::Yes), LinkerFlavorCli::Lld(LldFlavor::Wasm) => LinkerFlavor::WasmLld(Cc::No), LinkerFlavorCli::Lld(LldFlavor::Link) => LinkerFlavor::Msvc(Lld::Yes), - LinkerFlavorCli::Msvc => LinkerFlavor::Msvc(Lld::No), LinkerFlavorCli::Em => LinkerFlavor::EmCc, LinkerFlavorCli::BpfLinker => LinkerFlavor::Bpf, LinkerFlavorCli::PtxLinker => LinkerFlavor::Ptx, @@ -250,13 +293,119 @@ impl LinkerFlavor { LinkerFlavorCli::Ld } LinkerFlavor::Msvc(Lld::Yes) => LinkerFlavorCli::Lld(LldFlavor::Link), - LinkerFlavor::Msvc(..) => LinkerFlavorCli::Msvc, + LinkerFlavor::Msvc(..) => LinkerFlavorCli::Msvc(Lld::No), LinkerFlavor::EmCc => LinkerFlavorCli::Em, LinkerFlavor::Bpf => LinkerFlavorCli::BpfLinker, LinkerFlavor::Ptx => LinkerFlavorCli::PtxLinker, } } + fn infer_cli_hints(cli: LinkerFlavorCli) -> (Option, Option) { + match cli { + LinkerFlavorCli::Gnu(cc, lld) | LinkerFlavorCli::Darwin(cc, lld) => { + (Some(cc), Some(lld)) + } + LinkerFlavorCli::WasmLld(cc) => (Some(cc), Some(Lld::Yes)), + LinkerFlavorCli::Unix(cc) => (Some(cc), None), + LinkerFlavorCli::Msvc(lld) => (Some(Cc::No), Some(lld)), + LinkerFlavorCli::EmCc => (Some(Cc::Yes), Some(Lld::Yes)), + LinkerFlavorCli::Bpf | LinkerFlavorCli::Ptx => (None, None), + + // Below: legacy stable values + LinkerFlavorCli::Gcc => (Some(Cc::Yes), None), + LinkerFlavorCli::Ld => (Some(Cc::No), Some(Lld::No)), + LinkerFlavorCli::Lld(_) => (Some(Cc::No), Some(Lld::Yes)), + LinkerFlavorCli::Em => (Some(Cc::Yes), Some(Lld::Yes)), + LinkerFlavorCli::BpfLinker | LinkerFlavorCli::PtxLinker => (None, None), + } + } + + fn infer_linker_hints(linker_stem: &str) -> (Option, Option) { + // Remove any version postfix. + let stem = linker_stem + .rsplit_once('-') + .and_then(|(lhs, rhs)| rhs.chars().all(char::is_numeric).then_some(lhs)) + .unwrap_or(linker_stem); + + // GCC/Clang can have an optional target prefix. + if stem == "emcc" + || stem == "gcc" + || stem.ends_with("-gcc") + || stem == "g++" + || stem.ends_with("-g++") + || stem == "clang" + || stem.ends_with("-clang") + || stem == "clang++" + || stem.ends_with("-clang++") + { + (Some(Cc::Yes), Some(Lld::No)) + } else if stem == "wasm-ld" + || stem.ends_with("-wasm-ld") + || stem == "ld.lld" + || stem == "lld" + || stem == "rust-lld" + || stem == "lld-link" + { + (Some(Cc::No), Some(Lld::Yes)) + } else if stem == "ld" || stem.ends_with("-ld") || stem == "link" { + (Some(Cc::No), Some(Lld::No)) + } else { + (None, None) + } + } + + fn with_hints(self, (cc_hint, lld_hint): (Option, Option)) -> LinkerFlavor { + match self { + LinkerFlavor::Gnu(cc, lld) => { + LinkerFlavor::Gnu(cc_hint.unwrap_or(cc), lld_hint.unwrap_or(lld)) + } + LinkerFlavor::Darwin(cc, lld) => { + LinkerFlavor::Darwin(cc_hint.unwrap_or(cc), lld_hint.unwrap_or(lld)) + } + LinkerFlavor::WasmLld(cc) => LinkerFlavor::WasmLld(cc_hint.unwrap_or(cc)), + LinkerFlavor::Unix(cc) => LinkerFlavor::Unix(cc_hint.unwrap_or(cc)), + LinkerFlavor::Msvc(lld) => LinkerFlavor::Msvc(lld_hint.unwrap_or(lld)), + LinkerFlavor::EmCc | LinkerFlavor::Bpf | LinkerFlavor::Ptx => self, + } + } + + pub fn with_cli_hints(self, cli: LinkerFlavorCli) -> LinkerFlavor { + self.with_hints(LinkerFlavor::infer_cli_hints(cli)) + } + + pub fn with_linker_hints(self, linker_stem: &str) -> LinkerFlavor { + self.with_hints(LinkerFlavor::infer_linker_hints(linker_stem)) + } + + pub fn check_compatibility(self, cli: LinkerFlavorCli) -> Option { + let compatible = |cli| { + // The CLI flavor should be compatible with the target if: + // 1. they are counterparts: they have the same principal flavor. + match (self, cli) { + (LinkerFlavor::Gnu(..), LinkerFlavorCli::Gnu(..)) + | (LinkerFlavor::Darwin(..), LinkerFlavorCli::Darwin(..)) + | (LinkerFlavor::WasmLld(..), LinkerFlavorCli::WasmLld(..)) + | (LinkerFlavor::Unix(..), LinkerFlavorCli::Unix(..)) + | (LinkerFlavor::Msvc(..), LinkerFlavorCli::Msvc(..)) + | (LinkerFlavor::EmCc, LinkerFlavorCli::EmCc) + | (LinkerFlavor::Bpf, LinkerFlavorCli::Bpf) + | (LinkerFlavor::Ptx, LinkerFlavorCli::Ptx) => return true, + _ => {} + } + + // 2. or, the flavor is legacy and survives this roundtrip. + cli == self.with_cli_hints(cli).to_cli() + }; + (!compatible(cli)).then(|| { + LinkerFlavorCli::all() + .iter() + .filter(|cli| compatible(**cli)) + .map(|cli| cli.desc()) + .intersperse(", ") + .collect() + }) + } + pub fn lld_flavor(self) -> LldFlavor { match self { LinkerFlavor::Gnu(..) @@ -273,11 +422,52 @@ impl LinkerFlavor { pub fn is_gnu(self) -> bool { matches!(self, LinkerFlavor::Gnu(..)) } + + /// Returns whether the flavor uses the `lld` linker. + pub fn uses_lld(self) -> bool { + // Exhaustive match in case new flavors are added in the future. + match self { + LinkerFlavor::Gnu(_, Lld::Yes) + | LinkerFlavor::Darwin(_, Lld::Yes) + | LinkerFlavor::WasmLld(..) + | LinkerFlavor::EmCc + | LinkerFlavor::Msvc(Lld::Yes) => true, + LinkerFlavor::Gnu(..) + | LinkerFlavor::Darwin(..) + | LinkerFlavor::Msvc(_) + | LinkerFlavor::Unix(_) + | LinkerFlavor::Bpf + | LinkerFlavor::Ptx => false, + } + } + + /// Returns whether the flavor calls the linker via a C/C++ compiler. + pub fn uses_cc(self) -> bool { + // Exhaustive match in case new flavors are added in the future. + match self { + LinkerFlavor::Gnu(Cc::Yes, _) + | LinkerFlavor::Darwin(Cc::Yes, _) + | LinkerFlavor::WasmLld(Cc::Yes) + | LinkerFlavor::Unix(Cc::Yes) + | LinkerFlavor::EmCc => true, + LinkerFlavor::Gnu(..) + | LinkerFlavor::Darwin(..) + | LinkerFlavor::WasmLld(_) + | LinkerFlavor::Msvc(_) + | LinkerFlavor::Unix(_) + | LinkerFlavor::Bpf + | LinkerFlavor::Ptx => false, + } + } } macro_rules! linker_flavor_cli_impls { ($(($($flavor:tt)*) $string:literal)*) => ( impl LinkerFlavorCli { + const fn all() -> &'static [LinkerFlavorCli] { + &[$($($flavor)*,)*] + } + pub const fn one_of() -> &'static str { concat!("one of: ", $($string, " ",)*) } @@ -289,8 +479,8 @@ macro_rules! linker_flavor_cli_impls { }) } - pub fn desc(&self) -> &str { - match *self { + pub fn desc(self) -> &'static str { + match self { $($($flavor)* => $string,)* } } @@ -299,13 +489,31 @@ macro_rules! linker_flavor_cli_impls { } linker_flavor_cli_impls! { + (LinkerFlavorCli::Gnu(Cc::No, Lld::No)) "gnu" + (LinkerFlavorCli::Gnu(Cc::No, Lld::Yes)) "gnu-lld" + (LinkerFlavorCli::Gnu(Cc::Yes, Lld::No)) "gnu-cc" + (LinkerFlavorCli::Gnu(Cc::Yes, Lld::Yes)) "gnu-lld-cc" + (LinkerFlavorCli::Darwin(Cc::No, Lld::No)) "darwin" + (LinkerFlavorCli::Darwin(Cc::No, Lld::Yes)) "darwin-lld" + (LinkerFlavorCli::Darwin(Cc::Yes, Lld::No)) "darwin-cc" + (LinkerFlavorCli::Darwin(Cc::Yes, Lld::Yes)) "darwin-lld-cc" + (LinkerFlavorCli::WasmLld(Cc::No)) "wasm-lld" + (LinkerFlavorCli::WasmLld(Cc::Yes)) "wasm-lld-cc" + (LinkerFlavorCli::Unix(Cc::No)) "unix" + (LinkerFlavorCli::Unix(Cc::Yes)) "unix-cc" + (LinkerFlavorCli::Msvc(Lld::Yes)) "msvc-lld" + (LinkerFlavorCli::Msvc(Lld::No)) "msvc" + (LinkerFlavorCli::EmCc) "em-cc" + (LinkerFlavorCli::Bpf) "bpf" + (LinkerFlavorCli::Ptx) "ptx" + + // Below: legacy stable values (LinkerFlavorCli::Gcc) "gcc" (LinkerFlavorCli::Ld) "ld" (LinkerFlavorCli::Lld(LldFlavor::Ld)) "ld.lld" (LinkerFlavorCli::Lld(LldFlavor::Ld64)) "ld64.lld" (LinkerFlavorCli::Lld(LldFlavor::Link)) "lld-link" (LinkerFlavorCli::Lld(LldFlavor::Wasm)) "wasm-ld" - (LinkerFlavorCli::Msvc) "msvc" (LinkerFlavorCli::Em) "em" (LinkerFlavorCli::BpfLinker) "bpf-linker" (LinkerFlavorCli::PtxLinker) "ptx-linker" @@ -447,6 +655,43 @@ pub enum RelocModel { RopiRwpi, } +impl RelocModel { + pub fn desc(&self) -> &str { + match *self { + RelocModel::Static => "static", + RelocModel::Pic => "pic", + RelocModel::Pie => "pie", + RelocModel::DynamicNoPic => "dynamic-no-pic", + RelocModel::Ropi => "ropi", + RelocModel::Rwpi => "rwpi", + RelocModel::RopiRwpi => "ropi-rwpi", + } + } + pub const fn desc_symbol(&self) -> Symbol { + match *self { + RelocModel::Static => kw::Static, + RelocModel::Pic => sym::pic, + RelocModel::Pie => sym::pie, + RelocModel::DynamicNoPic => sym::dynamic_no_pic, + RelocModel::Ropi => sym::ropi, + RelocModel::Rwpi => sym::rwpi, + RelocModel::RopiRwpi => sym::ropi_rwpi, + } + } + + pub const fn all() -> [Symbol; 7] { + [ + RelocModel::Static.desc_symbol(), + RelocModel::Pic.desc_symbol(), + RelocModel::Pie.desc_symbol(), + RelocModel::DynamicNoPic.desc_symbol(), + RelocModel::Ropi.desc_symbol(), + RelocModel::Rwpi.desc_symbol(), + RelocModel::RopiRwpi.desc_symbol(), + ] + } +} + impl FromStr for RelocModel { type Err = (); @@ -466,16 +711,7 @@ impl FromStr for RelocModel { impl ToJson for RelocModel { fn to_json(&self) -> Json { - match *self { - RelocModel::Static => "static", - RelocModel::Pic => "pic", - RelocModel::Pie => "pie", - RelocModel::DynamicNoPic => "dynamic-no-pic", - RelocModel::Ropi => "ropi", - RelocModel::Rwpi => "rwpi", - RelocModel::RopiRwpi => "ropi-rwpi", - } - .to_json() + self.desc().to_json() } } @@ -596,6 +832,17 @@ impl LinkOutputKind { _ => return None, }) } + + pub fn can_link_dylib(self) -> bool { + match self { + LinkOutputKind::StaticNoPicExe | LinkOutputKind::StaticPicExe => false, + LinkOutputKind::DynamicNoPicExe + | LinkOutputKind::DynamicPicExe + | LinkOutputKind::DynamicDylib + | LinkOutputKind::StaticDylib + | LinkOutputKind::WasiReactorExe => true, + } + } } impl fmt::Display for LinkOutputKind { @@ -757,9 +1004,9 @@ impl StackProbeType { .and_then(|o| o.as_array()) .ok_or_else(|| "expected `min-llvm-version-for-inline` to be an array")?; let mut iter = min_version.into_iter().map(|v| { - let int = v.as_u64().ok_or_else( - || "expected `min-llvm-version-for-inline` values to be integers", - )?; + let int = v.as_u64().ok_or_else(|| { + "expected `min-llvm-version-for-inline` values to be integers" + })?; u32::try_from(int) .map_err(|_| "`min-llvm-version-for-inline` values don't convert to u32") }); @@ -815,6 +1062,7 @@ bitflags::bitflags! { const SHADOWCALLSTACK = 1 << 7; const KCFI = 1 << 8; const KERNELADDRESS = 1 << 9; + const SAFESTACK = 1 << 10; } } @@ -831,6 +1079,7 @@ impl SanitizerSet { SanitizerSet::LEAK => "leak", SanitizerSet::MEMORY => "memory", SanitizerSet::MEMTAG => "memtag", + SanitizerSet::SAFESTACK => "safestack", SanitizerSet::SHADOWCALLSTACK => "shadow-call-stack", SanitizerSet::THREAD => "thread", SanitizerSet::HWADDRESS => "hwaddress", @@ -871,6 +1120,7 @@ impl IntoIterator for SanitizerSet { SanitizerSet::THREAD, SanitizerSet::HWADDRESS, SanitizerSet::KERNELADDRESS, + SanitizerSet::SAFESTACK, ] .iter() .copied() @@ -1024,6 +1274,7 @@ supported_targets! { ("i586-unknown-linux-gnu", i586_unknown_linux_gnu), ("loongarch64-unknown-linux-gnu", loongarch64_unknown_linux_gnu), ("m68k-unknown-linux-gnu", m68k_unknown_linux_gnu), + ("csky-unknown-linux-gnuabiv2", csky_unknown_linux_gnuabiv2), ("mips-unknown-linux-gnu", mips_unknown_linux_gnu), ("mips64-unknown-linux-gnuabi64", mips64_unknown_linux_gnuabi64), ("mips64el-unknown-linux-gnuabi64", mips64el_unknown_linux_gnuabi64), @@ -1079,6 +1330,7 @@ supported_targets! { ("armv7-linux-androideabi", armv7_linux_androideabi), ("thumbv7neon-linux-androideabi", thumbv7neon_linux_androideabi), ("aarch64-linux-android", aarch64_linux_android), + ("riscv64-linux-android", riscv64_linux_android), ("aarch64-unknown-freebsd", aarch64_unknown_freebsd), ("armv6-unknown-freebsd", armv6_unknown_freebsd), @@ -1101,10 +1353,12 @@ supported_targets! { ("x86_64-unknown-openbsd", x86_64_unknown_openbsd), ("aarch64-unknown-netbsd", aarch64_unknown_netbsd), + ("aarch64_be-unknown-netbsd", aarch64_be_unknown_netbsd), ("armv6-unknown-netbsd-eabihf", armv6_unknown_netbsd_eabihf), ("armv7-unknown-netbsd-eabihf", armv7_unknown_netbsd_eabihf), ("i686-unknown-netbsd", i686_unknown_netbsd), ("powerpc-unknown-netbsd", powerpc_unknown_netbsd), + ("riscv64gc-unknown-netbsd", riscv64gc_unknown_netbsd), ("sparc64-unknown-netbsd", sparc64_unknown_netbsd), ("x86_64-unknown-netbsd", x86_64_unknown_netbsd), @@ -1179,8 +1433,12 @@ supported_targets! { ("asmjs-unknown-emscripten", asmjs_unknown_emscripten), ("wasm32-unknown-emscripten", wasm32_unknown_emscripten), ("wasm32-unknown-unknown", wasm32_unknown_unknown), - ("wasm32-wasi", wasm32_wasi), + ("wasm32-unknown-wasi", wasm32_unknown_wasi), + ("wasm32-wasi-preview1-threads", wasm32_wasi_preview1_threads), + ("wasm32-wasmer-wasi", wasm32_wasmer_wasi), + ("wasm64-unknown-emscripten", wasm64_unknown_emscripten), ("wasm64-unknown-unknown", wasm64_unknown_unknown), + ("wasm64-wasmer-wasi", wasm64_wasmer_wasi), ("thumbv6m-none-eabi", thumbv6m_none_eabi), ("thumbv7m-none-eabi", thumbv7m_none_eabi), @@ -1196,12 +1454,16 @@ supported_targets! { ("msp430-none-elf", msp430_none_elf), ("aarch64-unknown-hermit", aarch64_unknown_hermit), + ("riscv64gc-unknown-hermit", riscv64gc_unknown_hermit), ("x86_64-unknown-hermit", x86_64_unknown_hermit), + ("x86_64-unikraft-linux-musl", x86_64_unikraft_linux_musl), + ("riscv32i-unknown-none-elf", riscv32i_unknown_none_elf), ("riscv32im-unknown-none-elf", riscv32im_unknown_none_elf), ("riscv32imc-unknown-none-elf", riscv32imc_unknown_none_elf), ("riscv32imc-esp-espidf", riscv32imc_esp_espidf), + ("riscv32imac-esp-espidf", riscv32imac_esp_espidf), ("riscv32imac-unknown-none-elf", riscv32imac_unknown_none_elf), ("riscv32imac-unknown-xous-elf", riscv32imac_unknown_xous_elf), ("riscv32gc-unknown-linux-gnu", riscv32gc_unknown_linux_gnu), @@ -1211,6 +1473,11 @@ supported_targets! { ("riscv64gc-unknown-linux-gnu", riscv64gc_unknown_linux_gnu), ("riscv64gc-unknown-linux-musl", riscv64gc_unknown_linux_musl), + ("sparc-unknown-none-elf", sparc_unknown_none_elf), + + ("loongarch64-unknown-none", loongarch64_unknown_none), + ("loongarch64-unknown-none-softfloat", loongarch64_unknown_none_softfloat), + ("aarch64-unknown-none", aarch64_unknown_none), ("aarch64-unknown-none-softfloat", aarch64_unknown_none_softfloat), @@ -1260,6 +1527,8 @@ supported_targets! { ("x86_64-unknown-none", x86_64_unknown_none), + ("aarch64-unknown-teeos", aarch64_unknown_teeos), + ("mips64-openwrt-linux-musl", mips64_openwrt_linux_musl), ("aarch64-unknown-nto-qnx710", aarch64_unknown_nto_qnx_710), @@ -1268,6 +1537,7 @@ supported_targets! { ("aarch64-unknown-linux-ohos", aarch64_unknown_linux_ohos), ("armv7-unknown-linux-ohos", armv7_unknown_linux_ohos), + ("x86_64-unknown-linux-ohos", x86_64_unknown_linux_ohos), } /// Cow-Vec-Str: Cow<'static, [Cow<'static, str>]> @@ -1573,7 +1843,7 @@ pub struct TargetOptions { pub static_position_independent_executables: bool, /// Determines if the target always requires using the PLT for indirect /// library calls or not. This controls the default value of the `-Z plt` flag. - pub needs_plt: bool, + pub plt_by_default: bool, /// Either partial, full, or off. Full RELRO makes the dynamic linker /// resolve all symbols at startup and marks the GOT read-only before /// starting the program, preventing overwriting the GOT. @@ -1683,6 +1953,9 @@ pub struct TargetOptions { /// Use platform dependent mcount function pub mcount: StaticCow, + /// Use LLVM intrinsic for mcount function name + pub llvm_mcount_intrinsic: Option>, + /// LLVM ABI name, corresponds to the '-mabi' parameter available in multilib C compilers pub llvm_abiname: StaticCow, @@ -1798,7 +2071,7 @@ impl TargetOptions { } fn update_from_cli(&mut self) { - self.linker_flavor = LinkerFlavor::from_cli_impl( + self.linker_flavor = LinkerFlavor::from_cli_json( self.linker_flavor_json, self.lld_flavor_json, self.linker_is_gnu_json, @@ -1812,12 +2085,7 @@ impl TargetOptions { ] { args.clear(); for (flavor, args_json) in args_json { - // Cannot use `from_cli` due to borrow checker. - let linker_flavor = LinkerFlavor::from_cli_impl( - *flavor, - self.lld_flavor_json, - self.linker_is_gnu_json, - ); + let linker_flavor = self.linker_flavor.with_cli_hints(*flavor); // Normalize to no lld to avoid asserts. let linker_flavor = match linker_flavor { LinkerFlavor::Gnu(cc, _) => LinkerFlavor::Gnu(cc, Lld::No), @@ -1900,7 +2168,7 @@ impl Default for TargetOptions { no_default_libraries: true, position_independent_executables: false, static_position_independent_executables: false, - needs_plt: false, + plt_by_default: true, relro_level: RelroLevel::None, pre_link_objects: Default::default(), post_link_objects: Default::default(), @@ -1949,6 +2217,7 @@ impl Default for TargetOptions { override_export_symbols: None, merge_functions: MergeFunctions::Aliases, mcount: "mcount".into(), + llvm_mcount_intrinsic: None, llvm_abiname: "".into(), relax_elf_relocations: false, llvm_args: cvs![], @@ -2037,6 +2306,7 @@ impl Target { PtxKernel => self.arch == "nvptx64", Msp430Interrupt => self.arch == "msp430", AmdGpuKernel => self.arch == "amdgcn", + RiscvInterruptM | RiscvInterruptS => ["riscv32", "riscv64"].contains(&&self.arch[..]), AvrInterrupt | AvrNonBlockingInterrupt => self.arch == "avr", Wasm => ["wasm32", "wasm64"].contains(&&self.arch[..]), Thiscall { .. } => self.arch == "x86", @@ -2364,6 +2634,7 @@ impl Target { Some("leak") => SanitizerSet::LEAK, Some("memory") => SanitizerSet::MEMORY, Some("memtag") => SanitizerSet::MEMTAG, + Some("safestack") => SanitizerSet::SAFESTACK, Some("shadow-call-stack") => SanitizerSet::SHADOWCALLSTACK, Some("thread") => SanitizerSet::THREAD, Some("hwaddress") => SanitizerSet::HWADDRESS, @@ -2467,7 +2738,7 @@ impl Target { let name = (stringify!($key_name)).replace("_", "-"); obj.remove(&name).and_then(|o| o.as_str().and_then(|s| { match lookup_abi(s) { - Some(abi) => base.$key_name = Some(abi), + Ok(abi) => base.$key_name = Some(abi), _ => return Some(Err(format!("'{}' is not a valid value for abi", s))), } Some(Ok(())) @@ -2572,7 +2843,7 @@ impl Target { key!(no_default_libraries, bool); key!(position_independent_executables, bool); key!(static_position_independent_executables, bool); - key!(needs_plt, bool); + key!(plt_by_default, bool); key!(relro_level, RelroLevel)?; key!(archive_format); key!(allow_asm, bool); @@ -2604,6 +2875,7 @@ impl Target { key!(override_export_symbols, opt_list); key!(merge_functions, MergeFunctions)?; key!(mcount = "target-mcount"); + key!(llvm_mcount_intrinsic, optional); key!(llvm_abiname); key!(relax_elf_relocations, bool); key!(llvm_args, list); @@ -2828,7 +3100,7 @@ impl ToJson for Target { target_option_val!(no_default_libraries); target_option_val!(position_independent_executables); target_option_val!(static_position_independent_executables); - target_option_val!(needs_plt); + target_option_val!(plt_by_default); target_option_val!(relro_level); target_option_val!(archive_format); target_option_val!(allow_asm); @@ -2860,6 +3132,7 @@ impl ToJson for Target { target_option_val!(override_export_symbols); target_option_val!(merge_functions); target_option_val!(mcount, "target-mcount"); + target_option_val!(llvm_mcount_intrinsic); target_option_val!(llvm_abiname); target_option_val!(relax_elf_relocations); target_option_val!(llvm_args); diff --git a/compiler/rustc_target/src/spec/powerpc64_ibm_aix.rs b/compiler/rustc_target/src/spec/powerpc64_ibm_aix.rs index 34934379c7e84..4e105a03e2871 100644 --- a/compiler/rustc_target/src/spec/powerpc64_ibm_aix.rs +++ b/compiler/rustc_target/src/spec/powerpc64_ibm_aix.rs @@ -11,7 +11,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64-ibm-aix".into(), pointer_width: 64, - data_layout: "E-m:a-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), + data_layout: "E-m:a-Fi64-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), arch: "powerpc64".into(), options: base, } diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs index 08b273207301c..e8fe55a00db07 100644 --- a/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/powerpc64_unknown_freebsd.rs @@ -11,7 +11,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64-unknown-freebsd".into(), pointer_width: 64, - data_layout: "E-m:e-i64:64-n32:64".into(), + data_layout: "E-m:e-Fn32-i64:64-n32:64".into(), arch: "powerpc64".into(), options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs index ce64de861cd26..7a0cc539f1a81 100644 --- a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_gnu.rs @@ -11,7 +11,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64-unknown-linux-gnu".into(), pointer_width: 64, - data_layout: "E-m:e-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), + data_layout: "E-m:e-Fi64-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), arch: "powerpc64".into(), options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs index 81286a668fe71..f80b22828c1d9 100644 --- a/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/powerpc64_unknown_linux_musl.rs @@ -11,7 +11,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64-unknown-linux-musl".into(), pointer_width: 64, - data_layout: "E-m:e-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), + data_layout: "E-m:e-Fi64-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), arch: "powerpc64".into(), options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs index 7232dce3e96cc..3643f7b0c3746 100644 --- a/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs +++ b/compiler/rustc_target/src/spec/powerpc64_unknown_openbsd.rs @@ -11,7 +11,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64-unknown-openbsd".into(), pointer_width: 64, - data_layout: "E-m:e-i64:64-n32:64".into(), + data_layout: "E-m:e-Fn32-i64:64-n32:64".into(), arch: "powerpc64".into(), options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs b/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs index 10da7872c7361..b0472e64e134d 100644 --- a/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs +++ b/compiler/rustc_target/src/spec/powerpc64_wrs_vxworks.rs @@ -11,7 +11,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64-unknown-linux-gnu".into(), pointer_width: 64, - data_layout: "E-m:e-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), + data_layout: "E-m:e-Fi64-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), arch: "powerpc64".into(), options: TargetOptions { endian: Endian::Big, ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs b/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs index 8c941e1065153..342b1cf4f4ca8 100644 --- a/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/powerpc64le_unknown_freebsd.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64le-unknown-freebsd".into(), pointer_width: 64, - data_layout: "e-m:e-i64:64-n32:64".into(), + data_layout: "e-m:e-Fn32-i64:64-n32:64".into(), arch: "powerpc64".into(), options: TargetOptions { mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs index fd896e086b541..815e3d2781ca2 100644 --- a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_gnu.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64le-unknown-linux-gnu".into(), pointer_width: 64, - data_layout: "e-m:e-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), + data_layout: "e-m:e-Fn32-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), arch: "powerpc64".into(), options: TargetOptions { mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs index 3cffcf49772ed..0b9b78bcec898 100644 --- a/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/powerpc64le_unknown_linux_musl.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc64le-unknown-linux-musl".into(), pointer_width: 64, - data_layout: "e-m:e-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), + data_layout: "e-m:e-Fn32-i64:64-n32:64-S128-v256:256:256-v512:512:512".into(), arch: "powerpc64".into(), options: TargetOptions { mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs b/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs index 342f321bd5bf7..e036f5bdbada7 100644 --- a/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/powerpc_unknown_freebsd.rs @@ -14,7 +14,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-freebsd13.0".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { endian: Endian::Big, diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs index c8c61dc46eefe..c8d6f8b9c676c 100644 --- a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnu.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-linux-gnu".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs index 5c51ec91f715d..fdaa9d366d92a 100644 --- a/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs +++ b/compiler/rustc_target/src/spec/powerpc_unknown_linux_gnuspe.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-linux-gnuspe".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { abi: "spe".into(), diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs index fc7d802cbf44b..7fe708cf5304a 100644 --- a/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/powerpc_unknown_linux_musl.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-linux-musl".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { endian: Endian::Big, mcount: "_mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs b/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs index 912149c79e44c..6f8875ba7b402 100644 --- a/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs +++ b/compiler/rustc_target/src/spec/powerpc_unknown_netbsd.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-netbsd".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { endian: Endian::Big, mcount: "__mcount".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc_unknown_openbsd.rs b/compiler/rustc_target/src/spec/powerpc_unknown_openbsd.rs index dec85f9961b82..280d36698b488 100644 --- a/compiler/rustc_target/src/spec/powerpc_unknown_openbsd.rs +++ b/compiler/rustc_target/src/spec/powerpc_unknown_openbsd.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-openbsd".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: base, } diff --git a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs index a8c1c2a6132ea..6f245e6ab62a1 100644 --- a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs +++ b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-linux-gnu".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { endian: Endian::Big, features: "+secure-plt".into(), ..base }, } diff --git a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs index abb8d13daef40..1d5a5e5c6ac6c 100644 --- a/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs +++ b/compiler/rustc_target/src/spec/powerpc_wrs_vxworks_spe.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { Target { llvm_target: "powerpc-unknown-linux-gnuspe".into(), pointer_width: 32, - data_layout: "E-m:e-p:32:32-i64:64-n32".into(), + data_layout: "E-m:e-p:32:32-Fn32-i64:64-n32".into(), arch: "powerpc".into(), options: TargetOptions { abi: "spe".into(), diff --git a/compiler/rustc_target/src/spec/riscv32imac_esp_espidf.rs b/compiler/rustc_target/src/spec/riscv32imac_esp_espidf.rs new file mode 100644 index 0000000000000..0795065409ad9 --- /dev/null +++ b/compiler/rustc_target/src/spec/riscv32imac_esp_espidf.rs @@ -0,0 +1,31 @@ +use crate::spec::{cvs, PanicStrategy, RelocModel, Target, TargetOptions}; + +pub fn target() -> Target { + Target { + data_layout: "e-m:e-p:32:32-i64:64-n32-S128".into(), + llvm_target: "riscv32".into(), + pointer_width: 32, + arch: "riscv32".into(), + + options: TargetOptions { + families: cvs!["unix"], + os: "espidf".into(), + env: "newlib".into(), + vendor: "espressif".into(), + linker: Some("riscv32-esp-elf-gcc".into()), + cpu: "generic-rv32".into(), + + // As RiscV32IMAC architecture does natively support atomics, + // automatically enable the support for the Rust STD library. + max_atomic_width: Some(64), + atomic_cas: true, + + features: "+m,+a,+c".into(), + panic_strategy: PanicStrategy::Abort, + relocation_model: RelocModel::Static, + emit_debug_gdb_scripts: false, + eh_frame_header: false, + ..Default::default() + }, + } +} diff --git a/compiler/rustc_target/src/spec/riscv64_linux_android.rs b/compiler/rustc_target/src/spec/riscv64_linux_android.rs new file mode 100644 index 0000000000000..af0d685549414 --- /dev/null +++ b/compiler/rustc_target/src/spec/riscv64_linux_android.rs @@ -0,0 +1,19 @@ +use crate::spec::{CodeModel, SanitizerSet, Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "riscv64-linux-android".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128".into(), + arch: "riscv64".into(), + options: TargetOptions { + code_model: Some(CodeModel::Medium), + cpu: "generic-rv64".into(), + features: "+m,+a,+f,+d,+c".into(), + llvm_abiname: "lp64d".into(), + supported_sanitizers: SanitizerSet::ADDRESS, + max_atomic_width: Some(64), + ..super::android_base::opts() + }, + } +} diff --git a/compiler/rustc_target/src/spec/riscv64gc_unknown_hermit.rs b/compiler/rustc_target/src/spec/riscv64gc_unknown_hermit.rs new file mode 100644 index 0000000000000..1f6a34c0cacc8 --- /dev/null +++ b/compiler/rustc_target/src/spec/riscv64gc_unknown_hermit.rs @@ -0,0 +1,20 @@ +use crate::spec::{CodeModel, RelocModel, Target, TargetOptions, TlsModel}; + +pub fn target() -> Target { + Target { + llvm_target: "riscv64-unknown-hermit".into(), + pointer_width: 64, + arch: "riscv64".into(), + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128".into(), + options: TargetOptions { + cpu: "generic-rv64".into(), + features: "+m,+a,+f,+d,+c".into(), + relocation_model: RelocModel::Pic, + code_model: Some(CodeModel::Medium), + tls_model: TlsModel::LocalExec, + max_atomic_width: Some(64), + llvm_abiname: "lp64d".into(), + ..super::hermit_base::opts() + }, + } +} diff --git a/compiler/rustc_target/src/spec/riscv64gc_unknown_netbsd.rs b/compiler/rustc_target/src/spec/riscv64gc_unknown_netbsd.rs new file mode 100644 index 0000000000000..a89bd363a47eb --- /dev/null +++ b/compiler/rustc_target/src/spec/riscv64gc_unknown_netbsd.rs @@ -0,0 +1,19 @@ +use crate::spec::{CodeModel, Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "riscv64-unknown-netbsd".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128".into(), + arch: "riscv64".into(), + options: TargetOptions { + code_model: Some(CodeModel::Medium), + cpu: "generic-rv64".into(), + features: "+m,+a,+f,+d,+c".into(), + llvm_abiname: "lp64d".into(), + max_atomic_width: Some(64), + mcount: "__mcount".into(), + ..super::netbsd_base::opts() + }, + } +} diff --git a/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs index 12968abda082e..b10e6264b73c3 100644 --- a/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/sparc_unknown_linux_gnu.rs @@ -5,7 +5,7 @@ pub fn target() -> Target { let mut base = super::linux_gnu_base::opts(); base.endian = Endian::Big; base.cpu = "v9".into(); - base.max_atomic_width = Some(64); + base.max_atomic_width = Some(32); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-mv8plus"]); Target { diff --git a/compiler/rustc_target/src/spec/sparc_unknown_none_elf.rs b/compiler/rustc_target/src/spec/sparc_unknown_none_elf.rs new file mode 100644 index 0000000000000..7e908a0f36552 --- /dev/null +++ b/compiler/rustc_target/src/spec/sparc_unknown_none_elf.rs @@ -0,0 +1,27 @@ +use crate::abi::Endian; +use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel, Target, TargetOptions}; + +pub fn target() -> Target { + let options = TargetOptions { + linker_flavor: LinkerFlavor::Gnu(Cc::Yes, Lld::No), + linker: Some("sparc-elf-gcc".into()), + endian: Endian::Big, + cpu: "v7".into(), + abi: "elf".into(), + max_atomic_width: Some(32), + atomic_cas: true, + panic_strategy: PanicStrategy::Abort, + relocation_model: RelocModel::Static, + no_default_libraries: false, + emit_debug_gdb_scripts: false, + eh_frame_header: false, + ..Default::default() + }; + Target { + data_layout: "E-m:e-p:32:32-i64:64-f128:64-n32-S64".into(), + llvm_target: "sparc-unknown-none-elf".into(), + pointer_width: 32, + arch: "sparc".into(), + options, + } +} diff --git a/compiler/rustc_target/src/spec/teeos_base.rs b/compiler/rustc_target/src/spec/teeos_base.rs new file mode 100644 index 0000000000000..1bc71bab01605 --- /dev/null +++ b/compiler/rustc_target/src/spec/teeos_base.rs @@ -0,0 +1,29 @@ +use super::{Cc, LinkerFlavor, Lld, PanicStrategy}; +use crate::spec::{RelroLevel, TargetOptions}; + +pub fn opts() -> TargetOptions { + let lld_args = &["-zmax-page-size=4096", "-znow", "-ztext", "--execute-only"]; + let cc_args = &["-Wl,-zmax-page-size=4096", "-Wl,-znow", "-Wl,-ztext", "-mexecute-only"]; + + let mut pre_link_args = TargetOptions::link_args(LinkerFlavor::Gnu(Cc::No, Lld::No), lld_args); + super::add_link_args(&mut pre_link_args, LinkerFlavor::Gnu(Cc::Yes, Lld::No), cc_args); + + TargetOptions { + os: "teeos".into(), + vendor: "unknown".into(), + dynamic_linking: true, + linker_flavor: LinkerFlavor::Gnu(Cc::Yes, Lld::No), + // rpath hardcodes -Wl, so it can't be used together with ld.lld. + // C TAs also don't support rpath, so this is fine. + has_rpath: false, + // Note: Setting has_thread_local to true causes an error when + // loading / dyn-linking the TA + has_thread_local: false, + position_independent_executables: true, + relro_level: RelroLevel::Full, + crt_static_respected: true, + pre_link_args, + panic_strategy: PanicStrategy::Abort, + ..Default::default() + } +} diff --git a/compiler/rustc_target/src/spec/thumb_base.rs b/compiler/rustc_target/src/spec/thumb_base.rs index 2220b9326c977..0decfecb4cd1d 100644 --- a/compiler/rustc_target/src/spec/thumb_base.rs +++ b/compiler/rustc_target/src/spec/thumb_base.rs @@ -52,7 +52,7 @@ pub fn opts() -> TargetOptions { // breaks debugging. Preserve LR by default to prevent that from happening. frame_pointer: FramePointer::Always, // ARM supports multiple ABIs for enums, the linux one matches the default of 32 here - // but any arm-none or thumb-none target will be defaulted to 8 on GCC and clang + // but any arm-none or thumb-none target will be defaulted to 8 on GCC. c_enum_min_bits: Some(8), ..Default::default() } diff --git a/compiler/rustc_target/src/spec/thumbv4t_none_eabi.rs b/compiler/rustc_target/src/spec/thumbv4t_none_eabi.rs index 9c59bb9114ecb..88a76f49acd6c 100644 --- a/compiler/rustc_target/src/spec/thumbv4t_none_eabi.rs +++ b/compiler/rustc_target/src/spec/thumbv4t_none_eabi.rs @@ -45,8 +45,6 @@ pub fn target() -> Target { relocation_model: RelocModel::Static, // suggested from thumb_base, rust-lang/rust#44993. emit_debug_gdb_scripts: false, - // suggested from thumb_base, with no-os gcc/clang use 8-bit enums - c_enum_min_bits: Some(8), frame_pointer: FramePointer::MayOmit, main_needs_argc_argv: false, diff --git a/compiler/rustc_target/src/spec/unikraft_linux_musl_base.rs b/compiler/rustc_target/src/spec/unikraft_linux_musl_base.rs new file mode 100644 index 0000000000000..9ccd0a1e7caec --- /dev/null +++ b/compiler/rustc_target/src/spec/unikraft_linux_musl_base.rs @@ -0,0 +1,15 @@ +use crate::spec::{cvs, PanicStrategy, RelocModel, TargetOptions}; + +pub fn opts() -> TargetOptions { + TargetOptions { + os: "linux".into(), + env: "musl".into(), + vendor: "unikraft".into(), + linker: Some("kraftld".into()), + relocation_model: RelocModel::Static, + families: cvs!["unix"], + has_thread_local: true, + panic_strategy: PanicStrategy::Abort, + ..Default::default() + } +} diff --git a/compiler/rustc_target/src/spec/wasm32_unknown_wasi.rs b/compiler/rustc_target/src/spec/wasm32_unknown_wasi.rs new file mode 100644 index 0000000000000..06accffa45804 --- /dev/null +++ b/compiler/rustc_target/src/spec/wasm32_unknown_wasi.rs @@ -0,0 +1,118 @@ +//! The `wasm32-wasi` target is a new and still (as of April 2019) an +//! experimental target. The definition in this file is likely to be tweaked +//! over time and shouldn't be relied on too much. +//! +//! The `wasi` target is a proposal to define a standardized set of syscalls +//! that WebAssembly files can interoperate with. This set of syscalls is +//! intended to empower WebAssembly binaries with native capabilities such as +//! filesystem access, network access, etc. +//! +//! You can see more about the proposal at . +//! +//! The Rust target definition here is interesting in a few ways. We want to +//! serve two use cases here with this target: +//! +//! * First, we want Rust usage of the target to be as hassle-free as possible, +//! ideally avoiding the need to configure and install a local wasm32-wasi +//! toolchain. +//! +//! * Second, one of the primary use cases of LLVM's new wasm backend and the +//! wasm support in LLD is that any compiled language can interoperate with +//! any other. To that the `wasm32-wasi` target is the first with a viable C +//! standard library and sysroot common definition, so we want Rust and C/C++ +//! code to interoperate when compiled to `wasm32-unknown-unknown`. +//! +//! You'll note, however, that the two goals above are somewhat at odds with one +//! another. To attempt to solve both use cases in one go we define a target +//! that (ab)uses the `crt-static` target feature to indicate which one you're +//! in. +//! +//! ## No interop with C required +//! +//! By default the `crt-static` target feature is enabled, and when enabled +//! this means that the bundled version of `libc.a` found in `liblibc.rlib` +//! is used. This isn't intended really for interoperation with a C because it +//! may be the case that Rust's bundled C library is incompatible with a +//! foreign-compiled C library. In this use case, though, we use `rust-lld` and +//! some copied crt startup object files to ensure that you can download the +//! wasi target for Rust and you're off to the races, no further configuration +//! necessary. +//! +//! All in all, by default, no external dependencies are required. You can +//! compile `wasm32-wasi` binaries straight out of the box. You can't, however, +//! reliably interoperate with C code in this mode (yet). +//! +//! ## Interop with C required +//! +//! For the second goal we repurpose the `target-feature` flag, meaning that +//! you'll need to do a few things to have C/Rust code interoperate. +//! +//! 1. All Rust code needs to be compiled with `-C target-feature=-crt-static`, +//! indicating that the bundled C standard library in the Rust sysroot will +//! not be used. +//! +//! 2. If you're using rustc to build a linked artifact then you'll need to +//! specify `-C linker` to a `clang` binary that supports +//! `wasm32-wasi` and is configured with the `wasm32-wasi` sysroot. This +//! will cause Rust code to be linked against the libc.a that the specified +//! `clang` provides. +//! +//! 3. If you're building a staticlib and integrating Rust code elsewhere, then +//! compiling with `-C target-feature=-crt-static` is all you need to do. +//! +//! You can configure the linker via Cargo using the +//! `CARGO_TARGET_WASM32_WASI_LINKER` env var. Be sure to also set +//! `CC_wasm32-wasi` if any crates in the dependency graph are using the `cc` +//! crate. +//! +//! ## Remember, this is all in flux +//! +//! The wasi target is **very** new in its specification. It's likely going to +//! be a long effort to get it standardized and stable. We'll be following it as +//! best we can with this target. Don't start relying on too much here unless +//! you know what you're getting in to! + +use super::crt_objects::{self, LinkSelfContainedDefault}; +use super::{wasm_base, Cc, LinkerFlavor, Target}; + +pub fn target() -> Target { + let mut options = wasm_base::options(); + + options.os = "wasi".into(); + options.add_pre_link_args(LinkerFlavor::WasmLld(Cc::Yes), &["--target=wasm32-wasi"]); + + options.pre_link_objects_self_contained = crt_objects::pre_wasi_self_contained(); + options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); + + // FIXME: Figure out cases in which WASM needs to link with a native toolchain. + options.link_self_contained = LinkSelfContainedDefault::True; + + // Right now this is a bit of a workaround but we're currently saying that + // the target by default has a static crt which we're taking as a signal + // for "use the bundled crt". If that's turned off then the system's crt + // will be used, but this means that default usage of this target doesn't + // need an external compiler but it's still interoperable with an external + // compiler if configured correctly. + options.crt_static_default = true; + options.crt_static_respected = true; + + // Allow `+crt-static` to create a "cdylib" output which is just a wasm file + // without a main function. + options.crt_static_allows_dylibs = true; + + // WASI's `sys::args::init` function ignores its arguments; instead, + // `args::args()` makes the WASI API calls itself. + options.main_needs_argc_argv = false; + + // And, WASI mangles the name of "main" to distinguish between different + // signatures. + options.entry_name = "__main_void".into(); + + Target { + llvm_target: "wasm32-wasi".into(), + pointer_width: 32, + data_layout: "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20".into(), + arch: "wasm32".into(), + options, + } +} \ No newline at end of file diff --git a/compiler/rustc_target/src/spec/wasm32_wasi.rs b/compiler/rustc_target/src/spec/wasm32_wasi.rs deleted file mode 100644 index a0476d542e642..0000000000000 --- a/compiler/rustc_target/src/spec/wasm32_wasi.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! The `wasm32-wasi` target is a new and still (as of April 2019) an -//! experimental target. The definition in this file is likely to be tweaked -//! over time and shouldn't be relied on too much. -//! -//! The `wasi` target is a proposal to define a standardized set of syscalls -//! that WebAssembly files can interoperate with. This set of syscalls is -//! intended to empower WebAssembly binaries with native capabilities such as -//! filesystem access, network access, etc. -//! -//! You can see more about the proposal at . -//! -//! The Rust target definition here is interesting in a few ways. We want to -//! serve two use cases here with this target: -//! -//! * First, we want Rust usage of the target to be as hassle-free as possible, -//! ideally avoiding the need to configure and install a local wasm32-wasi -//! toolchain. -//! -//! * Second, one of the primary use cases of LLVM's new wasm backend and the -//! wasm support in LLD is that any compiled language can interoperate with -//! any other. To that the `wasm32-wasi` target is the first with a viable C -//! standard library and sysroot common definition, so we want Rust and C/C++ -//! code to interoperate when compiled to `wasm32-unknown-unknown`. -//! -//! You'll note, however, that the two goals above are somewhat at odds with one -//! another. To attempt to solve both use cases in one go we define a target -//! that (ab)uses the `crt-static` target feature to indicate which one you're -//! in. -//! -//! ## No interop with C required -//! -//! By default the `crt-static` target feature is enabled, and when enabled -//! this means that the bundled version of `libc.a` found in `liblibc.rlib` -//! is used. This isn't intended really for interoperation with a C because it -//! may be the case that Rust's bundled C library is incompatible with a -//! foreign-compiled C library. In this use case, though, we use `rust-lld` and -//! some copied crt startup object files to ensure that you can download the -//! wasi target for Rust and you're off to the races, no further configuration -//! necessary. -//! -//! All in all, by default, no external dependencies are required. You can -//! compile `wasm32-wasi` binaries straight out of the box. You can't, however, -//! reliably interoperate with C code in this mode (yet). -//! -//! ## Interop with C required -//! -//! For the second goal we repurpose the `target-feature` flag, meaning that -//! you'll need to do a few things to have C/Rust code interoperate. -//! -//! 1. All Rust code needs to be compiled with `-C target-feature=-crt-static`, -//! indicating that the bundled C standard library in the Rust sysroot will -//! not be used. -//! -//! 2. If you're using rustc to build a linked artifact then you'll need to -//! specify `-C linker` to a `clang` binary that supports -//! `wasm32-wasi` and is configured with the `wasm32-wasi` sysroot. This -//! will cause Rust code to be linked against the libc.a that the specified -//! `clang` provides. -//! -//! 3. If you're building a staticlib and integrating Rust code elsewhere, then -//! compiling with `-C target-feature=-crt-static` is all you need to do. -//! -//! You can configure the linker via Cargo using the -//! `CARGO_TARGET_WASM32_WASI_LINKER` env var. Be sure to also set -//! `CC_wasm32-wasi` if any crates in the dependency graph are using the `cc` -//! crate. -//! -//! ## Remember, this is all in flux -//! -//! The wasi target is **very** new in its specification. It's likely going to -//! be a long effort to get it standardized and stable. We'll be following it as -//! best we can with this target. Don't start relying on too much here unless -//! you know what you're getting in to! - -use super::crt_objects::{self, LinkSelfContainedDefault}; -use super::{wasm_base, Cc, LinkerFlavor, Target}; - -pub fn target() -> Target { - let mut options = wasm_base::options(); - - options.os = "wasi".into(); - options.add_pre_link_args(LinkerFlavor::WasmLld(Cc::Yes), &["--target=wasm32-wasi"]); - - options.pre_link_objects_self_contained = crt_objects::pre_wasi_self_contained(); - options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); - - // FIXME: Figure out cases in which WASM needs to link with a native toolchain. - options.link_self_contained = LinkSelfContainedDefault::True; - - // Right now this is a bit of a workaround but we're currently saying that - // the target by default has a static crt which we're taking as a signal - // for "use the bundled crt". If that's turned off then the system's crt - // will be used, but this means that default usage of this target doesn't - // need an external compiler but it's still interoperable with an external - // compiler if configured correctly. - options.crt_static_default = true; - options.crt_static_respected = true; - - // Allow `+crt-static` to create a "cdylib" output which is just a wasm file - // without a main function. - options.crt_static_allows_dylibs = true; - - // WASI's `sys::args::init` function ignores its arguments; instead, - // `args::args()` makes the WASI API calls itself. - options.main_needs_argc_argv = false; - - // And, WASI mangles the name of "main" to distinguish between different - // signatures. - options.entry_name = "__main_void".into(); - - Target { - llvm_target: "wasm32-wasi".into(), - pointer_width: 32, - data_layout: "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20".into(), - arch: "wasm32".into(), - options, - } -} diff --git a/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs b/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs new file mode 100644 index 0000000000000..c567155fee653 --- /dev/null +++ b/compiler/rustc_target/src/spec/wasm32_wasi_preview1_threads.rs @@ -0,0 +1,134 @@ +//! The `wasm32-wasi-preview1-threads` target is a new and still (as of July 2023) an +//! experimental target. The definition in this file is likely to be tweaked +//! over time and shouldn't be relied on too much. +//! +//! The `wasi-threads` target is a proposal to define a standardized set of syscalls +//! that WebAssembly files can interoperate with. This set of syscalls is +//! intended to empower WebAssembly binaries with native capabilities such as +//! threads, filesystem access, network access, etc. +//! +//! You can see more about the proposal at . +//! +//! The Rust target definition here is interesting in a few ways. We want to +//! serve two use cases here with this target: +//! +//! * First, we want Rust usage of the target to be as hassle-free as possible, +//! ideally avoiding the need to configure and install a local wasm32-wasi-preview1-threads +//! toolchain. +//! +//! * Second, one of the primary use cases of LLVM's new wasm backend and the +//! wasm support in LLD is that any compiled language can interoperate with +//! any other. To that the `wasm32-wasi-preview1-threads` target is the first with a viable C +//! standard library and sysroot common definition, so we want Rust and C/C++ +//! code to interoperate when compiled to `wasm32-unknown-unknown`. +//! +//! You'll note, however, that the two goals above are somewhat at odds with one +//! another. To attempt to solve both use cases in one go we define a target +//! that (ab)uses the `crt-static` target feature to indicate which one you're +//! in. +//! +//! ## No interop with C required +//! +//! By default the `crt-static` target feature is enabled, and when enabled +//! this means that the bundled version of `libc.a` found in `liblibc.rlib` +//! is used. This isn't intended really for interoperation with a C because it +//! may be the case that Rust's bundled C library is incompatible with a +//! foreign-compiled C library. In this use case, though, we use `rust-lld` and +//! some copied crt startup object files to ensure that you can download the +//! wasi target for Rust and you're off to the races, no further configuration +//! necessary. +//! +//! All in all, by default, no external dependencies are required. You can +//! compile `wasm32-wasi-preview1-threads` binaries straight out of the box. You can't, however, +//! reliably interoperate with C code in this mode (yet). +//! +//! ## Interop with C required +//! +//! For the second goal we repurpose the `target-feature` flag, meaning that +//! you'll need to do a few things to have C/Rust code interoperate. +//! +//! 1. All Rust code needs to be compiled with `-C target-feature=-crt-static`, +//! indicating that the bundled C standard library in the Rust sysroot will +//! not be used. +//! +//! 2. If you're using rustc to build a linked artifact then you'll need to +//! specify `-C linker` to a `clang` binary that supports +//! `wasm32-wasi-preview1-threads` and is configured with the `wasm32-wasi-preview1-threads` sysroot. This +//! will cause Rust code to be linked against the libc.a that the specified +//! `clang` provides. +//! +//! 3. If you're building a staticlib and integrating Rust code elsewhere, then +//! compiling with `-C target-feature=-crt-static` is all you need to do. +//! +//! You can configure the linker via Cargo using the +//! `CARGO_TARGET_WASM32_WASI_LINKER` env var. Be sure to also set +//! `CC_wasm32-wasi-preview1-threads` if any crates in the dependency graph are using the `cc` +//! crate. +//! +//! ## Remember, this is all in flux +//! +//! The wasi target is **very** new in its specification. It's likely going to +//! be a long effort to get it standardized and stable. We'll be following it as +//! best we can with this target. Don't start relying on too much here unless +//! you know what you're getting in to! + +use super::crt_objects::{self, LinkSelfContainedDefault}; +use super::{wasm_base, Cc, LinkerFlavor, Target}; + +pub fn target() -> Target { + let mut options = wasm_base::options(); + + options.os = "wasi".into(); + + options.add_pre_link_args( + LinkerFlavor::WasmLld(Cc::No), + &["--import-memory", "--export-memory", "--shared-memory"], + ); + options.add_pre_link_args( + LinkerFlavor::WasmLld(Cc::Yes), + &[ + "--target=wasm32-wasi-threads", + "-Wl,--import-memory", + "-Wl,--export-memory,", + "-Wl,--shared-memory", + ], + ); + + options.pre_link_objects_self_contained = crt_objects::pre_wasi_self_contained(); + options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); + + // FIXME: Figure out cases in which WASM needs to link with a native toolchain. + options.link_self_contained = LinkSelfContainedDefault::True; + + // Right now this is a bit of a workaround but we're currently saying that + // the target by default has a static crt which we're taking as a signal + // for "use the bundled crt". If that's turned off then the system's crt + // will be used, but this means that default usage of this target doesn't + // need an external compiler but it's still interoperable with an external + // compiler if configured correctly. + options.crt_static_default = true; + options.crt_static_respected = true; + + // Allow `+crt-static` to create a "cdylib" output which is just a wasm file + // without a main function. + options.crt_static_allows_dylibs = true; + + // WASI's `sys::args::init` function ignores its arguments; instead, + // `args::args()` makes the WASI API calls itself. + options.main_needs_argc_argv = false; + + // And, WASI mangles the name of "main" to distinguish between different + // signatures. + options.entry_name = "__main_void".into(); + + options.singlethread = false; + options.features = "+atomics,+bulk-memory,+mutable-globals".into(); + + Target { + llvm_target: "wasm32-wasi".into(), + pointer_width: 32, + data_layout: "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20".into(), + arch: "wasm32".into(), + options, + } +} diff --git a/compiler/rustc_target/src/spec/wasm32_wasmer_wasi.rs b/compiler/rustc_target/src/spec/wasm32_wasmer_wasi.rs new file mode 100644 index 0000000000000..bcf0083de7e82 --- /dev/null +++ b/compiler/rustc_target/src/spec/wasm32_wasmer_wasi.rs @@ -0,0 +1,129 @@ +//! The `wasm32-wasmer-wasi` target is a new that extends upon the +//! `wasmer32-unknown-wasi` and implements some missing key capabilities. +//! +//! The `wasi` target is a proposal to define a standardized set of syscalls +//! that WebAssembly files can interoperate with. This set of syscalls is +//! intended to empower WebAssembly binaries with native capabilities such as +//! filesystem access, network access, etc. +//! +//! You can see more about the proposal at . +//! +//! The Rust target definition here is interesting in a few ways. We want to +//! serve two use cases here with this target: +//! +//! * First, we want Rust usage of the target to be as hassle-free as possible, +//! ideally avoiding the need to configure and install a local wasm32-wasi +//! toolchain. +//! +//! * Second, one of the primary use cases of LLVM's new wasm backend and the +//! wasm support in LLD is that any compiled language can interoperate with +//! any other. To that the `wasm32-wasi` target is the first with a viable C +//! standard library and sysroot common definition, so we want Rust and C/C++ +//! code to interoperate when compiled to `wasm32-unknown-unknown`. +//! +//! You'll note, however, that the two goals above are somewhat at odds with one +//! another. To attempt to solve both use cases in one go we define a target +//! that (ab)uses the `crt-static` target feature to indicate which one you're +//! in. +//! +//! ## No interop with C required +//! +//! By default the `crt-static` target feature is enabled, and when enabled +//! this means that the bundled version of `libc.a` found in `liblibc.rlib` +//! is used. This isn't intended really for interoperation with a C because it +//! may be the case that Rust's bundled C library is incompatible with a +//! foreign-compiled C library. In this use case, though, we use `rust-lld` and +//! some copied crt startup object files to ensure that you can download the +//! wasi target for Rust and you're off to the races, no further configuration +//! necessary. +//! +//! All in all, by default, no external dependencies are required. You can +//! compile `wasm32-wasi` binaries straight out of the box. You can't, however, +//! reliably interoperate with C code in this mode (yet). +//! +//! ## Interop with C required +//! +//! For the second goal we repurpose the `target-feature` flag, meaning that +//! you'll need to do a few things to have C/Rust code interoperate. +//! +//! 1. All Rust code needs to be compiled with `-C target-feature=-crt-static`, +//! indicating that the bundled C standard library in the Rust sysroot will +//! not be used. +//! +//! 2. If you're using rustc to build a linked artifact then you'll need to +//! specify `-C linker` to a `clang` binary that supports +//! `wasm32-wasi` and is configured with the `wasm32-wasi` sysroot. This +//! will cause Rust code to be linked against the libc.a that the specified +//! `clang` provides. +//! +//! 3. If you're building a staticlib and integrating Rust code elsewhere, then +//! compiling with `-C target-feature=-crt-static` is all you need to do. +//! +//! You can configure the linker via Cargo using the +//! `CARGO_TARGET_WASM32_WASI_LINKER` env var. Be sure to also set +//! `CC_wasm32-wasi` if any crates in the dependency graph are using the `cc` +//! crate. +//! +//! ## Remember, this is all in flux +//! +//! The wasi target is **very** new in its specification. It's likely going to +//! be a long effort to get it standardized and stable. We'll be following it as +//! best we can with this target. Don't start relying on too much here unless +//! you know what you're getting in to! + +use super::wasm_base; +use super::{crt_objects::{self, LinkSelfContainedDefault}, Cc, LinkerFlavor, Target}; + +pub fn target() -> Target { + let mut options = wasm_base::options(); + + options.os = "wasi".into(); + options.vendor = "wasmer".into(); + options.add_pre_link_args(LinkerFlavor::WasmLld(Cc::Yes), &[ + "--target=wasm32-wasi", + // We need shared memory for multithreading + "--shared-memory", + "--no-check-features" + ]); + + options.pre_link_objects_self_contained = crt_objects::pre_wasi_self_contained(); + options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); + + // FIXME: Figure out cases in which WASM needs to link with a native toolchain. + options.link_self_contained = LinkSelfContainedDefault::True; + + // WASI(X) now supports multi-threading + options.singlethread = false; + + // Right now this is a bit of a workaround but we're currently saying that + // the target by default has a static crt which we're taking as a signal + // for "use the bundled crt". If that's turned off then the system's crt + // will be used, but this means that default usage of this target doesn't + // need an external compiler but it's still interoperable with an external + // compiler if configured correctly. + options.crt_static_default = true; + options.crt_static_respected = true; + + // Allow `+crt-static` to create a "cdylib" output which is just a wasm file + // without a main function. + options.crt_static_allows_dylibs = true; + + // WASI's `sys::args::init` function ignores its arguments; instead, + // `args::args()` makes the WASI API calls itself. + options.main_needs_argc_argv = false; + + // And, WASI mangles the name of "main" to distinguish between different + // signatures. + options.entry_name = "__main_void".into(); + + // WASIX enables more WASM features + options.features = "+bulk-memory,+atomics,+mutable-globals,+sign-ext,+nontrapping-fptoint".into(); + + Target { + llvm_target: "wasm32-wasi".into(), + pointer_width: 32, + data_layout: "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20".into(), + arch: "wasm32".into(), + options, + } +} diff --git a/compiler/rustc_target/src/spec/wasm64_unknown_emscripten.rs b/compiler/rustc_target/src/spec/wasm64_unknown_emscripten.rs new file mode 100644 index 0000000000000..3069f44c76328 --- /dev/null +++ b/compiler/rustc_target/src/spec/wasm64_unknown_emscripten.rs @@ -0,0 +1,13 @@ +use super::wasm_base; +use super::Target; + + +pub fn target() -> Target { + Target { + llvm_target: "wasm64-unknown-emscripten".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-p10:8:8-p20:8:8-i64:64-f128:64-n32:64-S128-ni:1:10:20".into(), + arch: "wasm64".into(), + options: wasm_base::options(), + } +} diff --git a/compiler/rustc_target/src/spec/wasm64_wasmer_wasi.rs b/compiler/rustc_target/src/spec/wasm64_wasmer_wasi.rs new file mode 100644 index 0000000000000..35f1813d01cab --- /dev/null +++ b/compiler/rustc_target/src/spec/wasm64_wasmer_wasi.rs @@ -0,0 +1,64 @@ +//! 64-bit WebAssembly target with a full operating system based on WASI +//! + +use super::wasm_base; +use super::{crt_objects::{self, LinkSelfContainedDefault}, Cc, LinkerFlavor, Target}; + +pub fn target() -> Target { + let mut options = wasm_base::options(); + options.os = "wasi".into(); + options.vendor = "wasmer".into(); + options.add_pre_link_args(LinkerFlavor::WasmLld(Cc::No), &["-mwasm64"]); + options.add_pre_link_args(LinkerFlavor::WasmLld(Cc::Yes), &[ + "--target=wasm64-wasi", + // We need shared memory for multithreading + "--shared-memory", + "--no-check-features" + ]); + + options.pre_link_objects_self_contained = crt_objects::pre_wasi_self_contained(); + options.post_link_objects_self_contained = crt_objects::post_wasi_self_contained(); + + // FIXME: Figure out cases in which WASM needs to link with a native toolchain. + options.link_self_contained = LinkSelfContainedDefault::True; + + // WASI(X) now supports multi-threading + options.singlethread = false; + + // Right now this is a bit of a workaround but we're currently saying that + // the target by default has a static crt which we're taking as a signal + // for "use the bundled crt". If that's turned off then the system's crt + // will be used, but this means that default usage of this target doesn't + // need an external compiler but it's still interoperable with an external + // compiler if configured correctly. + options.crt_static_default = true; + options.crt_static_respected = true; + + // Allow `+crt-static` to create a "cdylib" output which is just a wasm file + // without a main function. + options.crt_static_allows_dylibs = true; + + // WASIX's `sys::args::init` function ignores its arguments; instead, + // `args::args()` makes the WASIX API calls itself. + options.main_needs_argc_argv = false; + + // And, WASI mangles the name of "main" to distinguish between different + // signatures. + options.entry_name = "__main_void".into(); + + // Any engine that implements wasm64 will surely implement the rest of these + // features since they were all merged into the official spec by the time + // wasm64 was designed. + // TODO: Adding "+atomics" here seems to enable more of the multithreading however it does not yet + // work properly so more work is needed to finish this, otherwise this is very close to + // full networking support. + options.features = "+bulk-memory,+atomics,+mutable-globals,+sign-ext,+nontrapping-fptoint".into(); + + Target { + llvm_target: "wasm64-wasi".into(), + pointer_width: 64, + data_layout: "e-m:e-p:64:64-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20".into(), + arch: "wasm64".into(), + options, + } +} diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios.rs index 1dcb47056a463..061b6a96fc888 100644 --- a/compiler/rustc_target/src/spec/x86_64_apple_ios.rs +++ b/compiler/rustc_target/src/spec/x86_64_apple_ios.rs @@ -13,7 +13,7 @@ pub fn target() -> Target { .into(), arch: arch.target_arch(), options: TargetOptions { - max_atomic_width: Some(64), + max_atomic_width: Some(128), stack_probes: StackProbeType::X86, ..base }, diff --git a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs index 5a3e2a79bb9cc..50f359c357bdf 100644 --- a/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs +++ b/compiler/rustc_target/src/spec/x86_64_apple_ios_macabi.rs @@ -2,7 +2,7 @@ use super::apple_base::{opts, Arch}; use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target, TargetOptions}; pub fn target() -> Target { - let llvm_target = "x86_64-apple-ios-macabi"; + let llvm_target = "x86_64-apple-ios14.0-macabi"; let arch = Arch::X86_64_macabi; let mut base = opts("ios", arch); @@ -15,7 +15,7 @@ pub fn target() -> Target { .into(), arch: arch.target_arch(), options: TargetOptions { - max_atomic_width: Some(64), + max_atomic_width: Some(128), stack_probes: StackProbeType::X86, ..base }, diff --git a/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs b/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs index 550ce0b9ce577..2ec4d9569e3ea 100644 --- a/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs +++ b/compiler/rustc_target/src/spec/x86_64_apple_tvos.rs @@ -1,15 +1,16 @@ -use super::apple_base::{opts, Arch}; +use super::apple_base::{opts, tvos_sim_llvm_target, Arch}; use crate::spec::{StackProbeType, Target, TargetOptions}; pub fn target() -> Target { let arch = Arch::X86_64_sim; Target { - llvm_target: "x86_64-apple-tvos".into(), + llvm_target: tvos_sim_llvm_target(arch).into(), pointer_width: 64, - data_layout: "e-m:o-i64:64-f80:128-n8:16:32:64-S128".into(), + data_layout: "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + .into(), arch: arch.target_arch(), options: TargetOptions { - max_atomic_width: Some(64), + max_atomic_width: Some(128), stack_probes: StackProbeType::X86, ..opts("tvos", arch) }, diff --git a/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs b/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs index 75ce02cba1de0..5fcc00a86ff95 100644 --- a/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs +++ b/compiler/rustc_target/src/spec/x86_64_apple_watchos_sim.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { .into(), arch: arch.target_arch(), options: TargetOptions { - max_atomic_width: Some(64), + max_atomic_width: Some(128), stack_probes: StackProbeType::X86, forces_embed_bitcode: true, // Taken from a clang build on Xcode 11.4.1. diff --git a/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs b/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs index cba6fda19dc3e..a7ed74f47212f 100644 --- a/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs +++ b/compiler/rustc_target/src/spec/x86_64_fortanix_unknown_sgx.rs @@ -63,6 +63,7 @@ pub fn target() -> Target { linker: Some("rust-lld".into()), max_atomic_width: Some(64), cpu: "x86-64".into(), + plt_by_default: false, features: "+rdrnd,+rdseed,+lvi-cfi,+lvi-load-hardening".into(), llvm_args: cvs!["--x86-experimental-lvi-inline-asm-hardening"], position_independent_executables: true, diff --git a/compiler/rustc_target/src/spec/x86_64_linux_android.rs b/compiler/rustc_target/src/spec/x86_64_linux_android.rs index a3bdb5f5465b0..c110674fd870f 100644 --- a/compiler/rustc_target/src/spec/x86_64_linux_android.rs +++ b/compiler/rustc_target/src/spec/x86_64_linux_android.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, SanitizerSet, StackProbeType, Target, T pub fn target() -> Target { let mut base = super::android_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; // https://developer.android.com/ndk/guides/abis.html#86-64 base.features = "+mmx,+sse,+sse2,+sse3,+ssse3,+sse4.1,+sse4.2,+popcnt".into(); base.max_atomic_width = Some(64); diff --git a/compiler/rustc_target/src/spec/x86_64_pc_nto_qnx710.rs b/compiler/rustc_target/src/spec/x86_64_pc_nto_qnx710.rs index 6fb2dfd807a0f..8424757df076c 100644 --- a/compiler/rustc_target/src/spec/x86_64_pc_nto_qnx710.rs +++ b/compiler/rustc_target/src/spec/x86_64_pc_nto_qnx710.rs @@ -10,6 +10,7 @@ pub fn target() -> Target { arch: "x86_64".into(), options: TargetOptions { cpu: "x86-64".into(), + plt_by_default: false, max_atomic_width: Some(64), pre_link_args: TargetOptions::link_args( LinkerFlavor::Gnu(Cc::Yes, Lld::No), diff --git a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs index cb62a8173222e..e2c59d2938e69 100644 --- a/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs +++ b/compiler/rustc_target/src/spec/x86_64_pc_solaris.rs @@ -4,10 +4,11 @@ pub fn target() -> Target { let mut base = super::solaris_base::opts(); base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), &["-m64"]); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.vendor = "pc".into(); base.max_atomic_width = Some(64); base.stack_probes = StackProbeType::X86; - base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::THREAD; Target { llvm_target: "x86_64-pc-solaris".into(), diff --git a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs index 37feaa9dbbf84..1b8885c34da78 100644 --- a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs +++ b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnu.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, Target}; pub fn target() -> Target { let mut base = super::windows_gnu_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; // Use high-entropy 64 bit address space for ASLR base.add_pre_link_args( LinkerFlavor::Gnu(Cc::No, Lld::No), diff --git a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs index 039bc2bd2bb84..8f5e398a0be91 100644 --- a/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs +++ b/compiler/rustc_target/src/spec/x86_64_pc_windows_gnullvm.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, Target}; pub fn target() -> Target { let mut base = super::windows_gnullvm_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.max_atomic_width = Some(64); base.linker = Some("x86_64-w64-mingw32-clang".into()); diff --git a/compiler/rustc_target/src/spec/x86_64_pc_windows_msvc.rs b/compiler/rustc_target/src/spec/x86_64_pc_windows_msvc.rs index 081806aa69819..6b897ca7070e3 100644 --- a/compiler/rustc_target/src/spec/x86_64_pc_windows_msvc.rs +++ b/compiler/rustc_target/src/spec/x86_64_pc_windows_msvc.rs @@ -3,6 +3,7 @@ use crate::spec::Target; pub fn target() -> Target { let mut base = super::windows_msvc_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); Target { diff --git a/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs b/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs index 0f31ea86b3f16..650065f6330a0 100644 --- a/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs +++ b/compiler/rustc_target/src/spec/x86_64_sun_solaris.rs @@ -4,6 +4,7 @@ pub fn target() -> Target { let mut base = super::solaris_base::opts(); base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), &["-m64"]); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.vendor = "sun".into(); base.max_atomic_width = Some(64); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unikraft_linux_musl.rs b/compiler/rustc_target/src/spec/x86_64_unikraft_linux_musl.rs new file mode 100644 index 0000000000000..2aa093b131f5f --- /dev/null +++ b/compiler/rustc_target/src/spec/x86_64_unikraft_linux_musl.rs @@ -0,0 +1,19 @@ +use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target, TargetOptions}; + +pub fn target() -> Target { + Target { + llvm_target: "x86_64-unknown-linux-musl".into(), + pointer_width: 64, + arch: "x86_64".into(), + data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + .into(), + options: TargetOptions { + cpu: "x86-64".into(), + plt_by_default: false, + pre_link_args: TargetOptions::link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]), + max_atomic_width: Some(64), + stack_probes: StackProbeType::X86, + ..super::unikraft_linux_musl_base::opts() + }, + } +} diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs b/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs index 67ce3768db0c8..3b8e75977b5ac 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_dragonfly.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::dragonfly_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs index b41e5842aad13..b2d91d09996ff 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, SanitizerSet, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::freebsd_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_fuchsia.rs b/compiler/rustc_target/src/spec/x86_64_unknown_fuchsia.rs index a3231d19f4c72..bee9354196028 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_fuchsia.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_fuchsia.rs @@ -3,6 +3,7 @@ use crate::spec::{SanitizerSet, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::fuchsia_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.stack_probes = StackProbeType::X86; base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs b/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs index 9a7a3b501cfdc..16ed3150e6e2e 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_haiku.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::haiku_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs b/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs index fb1af33f80a86..1eb0693011176 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_hermit.rs @@ -1,18 +1,19 @@ -use crate::spec::{StackProbeType, Target}; +use crate::spec::{StackProbeType, Target, TargetOptions}; pub fn target() -> Target { - let mut base = super::hermit_base::opts(); - base.cpu = "x86-64".into(); - base.max_atomic_width = Some(64); - base.features = "+rdrnd,+rdseed".into(); - base.stack_probes = StackProbeType::X86; - Target { llvm_target: "x86_64-unknown-hermit".into(), pointer_width: 64, + arch: "x86_64".into(), data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" .into(), - arch: "x86_64".into(), - options: base, + options: TargetOptions { + cpu: "x86-64".into(), + features: "+rdrnd,+rdseed".into(), + plt_by_default: false, + max_atomic_width: Some(64), + stack_probes: StackProbeType::X86, + ..super::hermit_base::opts() + }, } } diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs b/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs index 04a12a7bfa642..9259cfe5f0ed6 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_illumos.rs @@ -4,8 +4,9 @@ pub fn target() -> Target { let mut base = super::illumos_base::opts(); base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), &["-m64", "-std=c99"]); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); - base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI; + base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::THREAD; Target { // LLVM does not currently have a separate illumos target, diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/x86_64_unknown_l4re_uclibc.rs index 26da7e800114a..912d289c47f9f 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_l4re_uclibc.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_l4re_uclibc.rs @@ -3,6 +3,7 @@ use crate::spec::{PanicStrategy, Target}; pub fn target() -> Target { let mut base = super::l4re_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.panic_strategy = PanicStrategy::Abort; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs index 9af1049b87026..2f970f87cc642 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, SanitizerSet, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::linux_gnu_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; @@ -11,6 +12,7 @@ pub fn target() -> Target { | SanitizerSet::CFI | SanitizerSet::LEAK | SanitizerSet::MEMORY + | SanitizerSet::SAFESTACK | SanitizerSet::THREAD; base.supports_xray = true; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs index 626d5b480c632..5469d02c59239 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnux32.rs @@ -10,7 +10,7 @@ pub fn target() -> Target { base.has_thread_local = false; // BUG(GabrielMajeri): disabling the PLT on x86_64 Linux with x32 ABI // breaks code gen. See LLVM bug 36743 - base.needs_plt = true; + base.plt_by_default = true; Target { llvm_target: "x86_64-unknown-linux-gnux32".into(), diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs index bf4cf7d7becad..7154f5fa3068f 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, SanitizerSet, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::linux_musl_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_ohos.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_ohos.rs new file mode 100644 index 0000000000000..a96be8cd55466 --- /dev/null +++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_ohos.rs @@ -0,0 +1,26 @@ +use crate::spec::{Cc, LinkerFlavor, Lld, SanitizerSet, StackProbeType, Target}; + +pub fn target() -> Target { + let mut base = super::linux_ohos_base::opts(); + base.cpu = "x86-64".into(); + base.max_atomic_width = Some(64); + base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); + base.stack_probes = StackProbeType::X86; + base.static_position_independent_executables = true; + base.supported_sanitizers = SanitizerSet::ADDRESS + | SanitizerSet::CFI + | SanitizerSet::LEAK + | SanitizerSet::MEMORY + | SanitizerSet::THREAD; + base.supports_xray = true; + + Target { + // LLVM 15 doesn't support OpenHarmony yet, use a linux target instead. + llvm_target: "x86_64-unknown-linux-musl".into(), + pointer_width: 64, + data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + .into(), + arch: "x86_64".into(), + options: base, + } +} diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs index 74c434935ba88..2e7bf34f7d2f6 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, SanitizerSet, StackProbeType, Target, T pub fn target() -> Target { let mut base = super::netbsd_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_none.rs b/compiler/rustc_target/src/spec/x86_64_unknown_none.rs index 43c5ce78ce34e..fe3b24f2d4afa 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_none.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_none.rs @@ -10,6 +10,7 @@ use super::{RelroLevel, SanitizerSet, StackProbeType, Target, TargetOptions}; pub fn target() -> Target { let opts = TargetOptions { cpu: "x86-64".into(), + plt_by_default: false, max_atomic_width: Some(64), stack_probes: StackProbeType::X86, position_independent_executables: true, diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs index 8e4d42a0acaf3..86fa9bf7ed2a3 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::openbsd_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs b/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs index b47f15cf57897..decc973678227 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_redox.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::redox_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs b/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs index a7ae17839da8c..67664a74710a3 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs @@ -10,6 +10,7 @@ use crate::spec::Target; pub fn target() -> Target { let mut base = super::uefi_msvc_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); // We disable MMX and SSE for now, even though UEFI allows using them. Problem is, you have to diff --git a/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs b/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs index c3eaa6939bb13..1a9d2a57182d5 100644 --- a/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs +++ b/compiler/rustc_target/src/spec/x86_64_uwp_windows_gnu.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, Target}; pub fn target() -> Target { let mut base = super::windows_uwp_gnu_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; // Use high-entropy 64 bit address space for ASLR base.add_pre_link_args( LinkerFlavor::Gnu(Cc::No, Lld::No), diff --git a/compiler/rustc_target/src/spec/x86_64_uwp_windows_msvc.rs b/compiler/rustc_target/src/spec/x86_64_uwp_windows_msvc.rs index b2769350bf66d..1ae403fa83f31 100644 --- a/compiler/rustc_target/src/spec/x86_64_uwp_windows_msvc.rs +++ b/compiler/rustc_target/src/spec/x86_64_uwp_windows_msvc.rs @@ -3,6 +3,7 @@ use crate::spec::Target; pub fn target() -> Target { let mut base = super::windows_uwp_msvc_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); Target { diff --git a/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs b/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs index 365ade6bcf9c0..a7c4aaecf9109 100644 --- a/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs +++ b/compiler/rustc_target/src/spec/x86_64_wrs_vxworks.rs @@ -3,6 +3,7 @@ use crate::spec::{Cc, LinkerFlavor, Lld, StackProbeType, Target}; pub fn target() -> Target { let mut base = super::vxworks_base::opts(); base.cpu = "x86-64".into(); + base.plt_by_default = false; base.max_atomic_width = Some(64); base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]); base.stack_probes = StackProbeType::X86; diff --git a/compiler/rustc_trait_selection/messages.ftl b/compiler/rustc_trait_selection/messages.ftl index 8fea3fc140d72..f4c9dfa34882e 100644 --- a/compiler/rustc_trait_selection/messages.ftl +++ b/compiler/rustc_trait_selection/messages.ftl @@ -1,17 +1,32 @@ -trait_selection_dump_vtable_entries = vtable entries for `{$trait_ref}`: {$entries} +trait_selection_adjust_signature_borrow = consider adjusting the signature so it borrows its {$len -> + [one] argument + *[other] arguments + } -trait_selection_unable_to_construct_constant_value = unable to construct a constant value for the unevaluated constant {$unevaluated} +trait_selection_adjust_signature_remove_borrow = consider adjusting the signature so it does not borrow its {$len -> + [one] argument + *[other] arguments + } + +trait_selection_closure_fn_mut_label = closure is `FnMut` because it mutates the variable `{$place}` here + +trait_selection_closure_fn_once_label = closure is `FnOnce` because it moves the variable `{$place}` out of its environment + +trait_selection_closure_kind_mismatch = expected a closure that implements the `{$expected}` trait, but this closure only implements `{$found}` + .label = this closure implements `{$found}`, not `{$expected}` + +trait_selection_closure_kind_requirement = the requirement to implement `{$expected}` derives from here + +trait_selection_dump_vtable_entries = vtable entries for `{$trait_ref}`: {$entries} trait_selection_empty_on_clause_in_rustc_on_unimplemented = empty `on`-clause in `#[rustc_on_unimplemented]` .label = empty on-clause here +trait_selection_inherent_projection_normalization_overflow = overflow evaluating associated type `{$ty}` + trait_selection_invalid_on_clause_in_rustc_on_unimplemented = invalid `on`-clause in `#[rustc_on_unimplemented]` .label = invalid on-clause here -trait_selection_no_value_in_rustc_on_unimplemented = this attribute must have a valid value - .label = expected value here - .note = eg `#[rustc_on_unimplemented(message="foo")]` - trait_selection_negative_positive_conflict = found both positive and negative implementation of trait `{$trait_desc}`{$self_desc -> [none] {""} *[default] {" "}for type `{$self_desc}` @@ -21,4 +36,8 @@ trait_selection_negative_positive_conflict = found both positive and negative im .positive_implementation_here = positive implementation here .positive_implementation_in_crate = positive implementation in crate `{$positive_impl_cname}` -trait_selection_inherent_projection_normalization_overflow = overflow evaluating associated type `{$ty}` +trait_selection_no_value_in_rustc_on_unimplemented = this attribute must have a valid value + .label = expected value here + .note = eg `#[rustc_on_unimplemented(message="foo")]` + +trait_selection_unable_to_construct_constant_value = unable to construct a constant value for the unevaluated constant {$unevaluated} diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs index 54e22cc3d7fe2..c1fb287d63efa 100644 --- a/compiler/rustc_trait_selection/src/errors.rs +++ b/compiler/rustc_trait_selection/src/errors.rs @@ -1,7 +1,10 @@ use crate::fluent_generated as fluent; -use rustc_errors::{ErrorGuaranteed, Handler, IntoDiagnostic}; +use rustc_errors::{ + AddToDiagnostic, Applicability, Diagnostic, ErrorGuaranteed, Handler, IntoDiagnostic, + SubdiagnosticMessage, +}; use rustc_macros::Diagnostic; -use rustc_middle::ty::{self, PolyTraitRef, Ty}; +use rustc_middle::ty::{self, ClosureKind, PolyTraitRef, Ty}; use rustc_span::{Span, Symbol}; #[derive(Diagnostic)] @@ -97,3 +100,68 @@ pub struct InherentProjectionNormalizationOverflow { pub span: Span, pub ty: String, } + +pub enum AdjustSignatureBorrow { + Borrow { to_borrow: Vec<(Span, String)> }, + RemoveBorrow { remove_borrow: Vec<(Span, String)> }, +} + +impl AddToDiagnostic for AdjustSignatureBorrow { + fn add_to_diagnostic_with(self, diag: &mut Diagnostic, _: F) + where + F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage, + { + match self { + AdjustSignatureBorrow::Borrow { to_borrow } => { + diag.set_arg("len", to_borrow.len()); + diag.multipart_suggestion_verbose( + fluent::trait_selection_adjust_signature_borrow, + to_borrow, + Applicability::MaybeIncorrect, + ); + } + AdjustSignatureBorrow::RemoveBorrow { remove_borrow } => { + diag.set_arg("len", remove_borrow.len()); + diag.multipart_suggestion_verbose( + fluent::trait_selection_adjust_signature_remove_borrow, + remove_borrow, + Applicability::MaybeIncorrect, + ); + } + } + } +} + +#[derive(Diagnostic)] +#[diag(trait_selection_closure_kind_mismatch, code = "E0525")] +pub struct ClosureKindMismatch { + #[primary_span] + #[label] + pub closure_span: Span, + pub expected: ClosureKind, + pub found: ClosureKind, + #[label(trait_selection_closure_kind_requirement)] + pub cause_span: Span, + + #[subdiagnostic] + pub fn_once_label: Option, + + #[subdiagnostic] + pub fn_mut_label: Option, +} + +#[derive(Subdiagnostic)] +#[label(trait_selection_closure_fn_once_label)] +pub struct ClosureFnOnceLabel { + #[primary_span] + pub span: Span, + pub place: String, +} + +#[derive(Subdiagnostic)] +#[label(trait_selection_closure_fn_mut_label)] +pub struct ClosureFnMutLabel { + #[primary_span] + pub span: Span, + pub place: String, +} diff --git a/compiler/rustc_trait_selection/src/infer.rs b/compiler/rustc_trait_selection/src/infer.rs index 142c20014a015..38153cccfdded 100644 --- a/compiler/rustc_trait_selection/src/infer.rs +++ b/compiler/rustc_trait_selection/src/infer.rs @@ -1,11 +1,11 @@ use crate::traits::query::evaluate_obligation::InferCtxtExt as _; -use crate::traits::{self, ObligationCtxt}; +use crate::traits::{self, DefiningAnchor, ObligationCtxt}; use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_middle::arena::ArenaAllocatable; use rustc_middle::infer::canonical::{Canonical, CanonicalQueryResponse, QueryResponse}; -use rustc_middle::traits::query::Fallible; +use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_middle::ty::{GenericArg, ToPredicate}; use rustc_span::DUMMY_SP; @@ -72,7 +72,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { cause: traits::ObligationCause::dummy(), param_env, recursion_depth: 0, - predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(self.tcx), + predicate: ty::Binder::dummy(trait_ref).to_predicate(self.tcx), }; self.evaluate_obligation(&obligation).unwrap_or(traits::EvaluationResult::EvaluatedToErr) } @@ -80,10 +80,10 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { pub trait InferCtxtBuilderExt<'tcx> { fn enter_canonical_trait_query( - &mut self, + self, canonical_key: &Canonical<'tcx, K>, - operation: impl FnOnce(&ObligationCtxt<'_, 'tcx>, K) -> Fallible, - ) -> Fallible> + operation: impl FnOnce(&ObligationCtxt<'_, 'tcx>, K) -> Result, + ) -> Result, NoSolution> where K: TypeFoldable>, R: Debug + TypeFoldable>, @@ -108,17 +108,18 @@ impl<'tcx> InferCtxtBuilderExt<'tcx> for InferCtxtBuilder<'tcx> { /// have `'tcx` be free on this function so that we can talk about /// `K: TypeFoldable>`.) fn enter_canonical_trait_query( - &mut self, + self, canonical_key: &Canonical<'tcx, K>, - operation: impl FnOnce(&ObligationCtxt<'_, 'tcx>, K) -> Fallible, - ) -> Fallible> + operation: impl FnOnce(&ObligationCtxt<'_, 'tcx>, K) -> Result, + ) -> Result, NoSolution> where K: TypeFoldable>, R: Debug + TypeFoldable>, Canonical<'tcx, QueryResponse<'tcx, R>>: ArenaAllocatable<'tcx>, { - let (infcx, key, canonical_inference_vars) = - self.build_with_canonical(DUMMY_SP, canonical_key); + let (infcx, key, canonical_inference_vars) = self + .with_opaque_type_inference(DefiningAnchor::Bubble) + .build_with_canonical(DUMMY_SP, canonical_key); let ocx = ObligationCtxt::new(&infcx); let value = operation(&ocx, key)?; ocx.make_canonicalized_query_response(canonical_inference_vars, value) diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index ed3994be98796..56d37d58de752 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -14,8 +14,7 @@ #![feature(associated_type_bounds)] #![feature(box_patterns)] #![feature(control_flow_enum)] -#![feature(drain_filter)] -#![feature(hash_drain_filter)] +#![feature(extract_if)] #![feature(let_chains)] #![feature(if_let_guard)] #![feature(never_type)] diff --git a/compiler/rustc_trait_selection/src/solve/alias_relate.rs b/compiler/rustc_trait_selection/src/solve/alias_relate.rs new file mode 100644 index 0000000000000..6b839d64b87ca --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/alias_relate.rs @@ -0,0 +1,217 @@ +//! Implements the `AliasRelate` goal, which is used when unifying aliases. +//! Doing this via a separate goal is called "deferred alias relation" and part +//! of our more general approach to "lazy normalization". +//! +//! This goal, e.g. `A alias-relate B`, may be satisfied by one of three branches: +//! * normalizes-to: If `A` is a projection, we can prove the equivalent +//! projection predicate with B as the right-hand side of the projection. +//! This goal is computed in both directions, if both are aliases. +//! * subst-relate: Equate `A` and `B` by their substs, if they're both +//! aliases with the same def-id. +//! * bidirectional-normalizes-to: If `A` and `B` are both projections, and both +//! may apply, then we can compute the "intersection" of both normalizes-to by +//! performing them together. This is used specifically to resolve ambiguities. +use super::{EvalCtxt, SolverMode}; +use rustc_infer::traits::query::NoSolution; +use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; +use rustc_middle::ty; + +/// We may need to invert the alias relation direction if dealing an alias on the RHS. +#[derive(Debug)] +enum Invert { + No, + Yes, +} + +impl<'tcx> EvalCtxt<'_, 'tcx> { + #[instrument(level = "debug", skip(self), ret)] + pub(super) fn compute_alias_relate_goal( + &mut self, + goal: Goal<'tcx, (ty::Term<'tcx>, ty::Term<'tcx>, ty::AliasRelationDirection)>, + ) -> QueryResult<'tcx> { + let tcx = self.tcx(); + let Goal { param_env, predicate: (lhs, rhs, direction) } = goal; + if lhs.is_infer() || rhs.is_infer() { + bug!( + "`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated" + ); + } + + match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) { + (None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"), + + // RHS is not a projection, only way this is true is if LHS normalizes-to RHS + (Some(alias_lhs), None) => self.assemble_normalizes_to_candidate( + param_env, + alias_lhs, + rhs, + direction, + Invert::No, + ), + + // LHS is not a projection, only way this is true is if RHS normalizes-to LHS + (None, Some(alias_rhs)) => self.assemble_normalizes_to_candidate( + param_env, + alias_rhs, + lhs, + direction, + Invert::Yes, + ), + + (Some(alias_lhs), Some(alias_rhs)) => { + debug!("both sides are aliases"); + + let mut candidates = Vec::new(); + // LHS normalizes-to RHS + candidates.extend(self.assemble_normalizes_to_candidate( + param_env, + alias_lhs, + rhs, + direction, + Invert::No, + )); + // RHS normalizes-to RHS + candidates.extend(self.assemble_normalizes_to_candidate( + param_env, + alias_rhs, + lhs, + direction, + Invert::Yes, + )); + // Relate via args + candidates.extend( + self.assemble_subst_relate_candidate( + param_env, alias_lhs, alias_rhs, direction, + ), + ); + debug!(?candidates); + + if let Some(merged) = self.try_merge_responses(&candidates) { + Ok(merged) + } else { + // When relating two aliases and we have ambiguity, if both + // aliases can be normalized to something, we prefer + // "bidirectionally normalizing" both of them within the same + // candidate. + // + // See . + // + // As this is incomplete, we must not do so during coherence. + match self.solver_mode() { + SolverMode::Normal => { + if let Ok(bidirectional_normalizes_to_response) = self + .assemble_bidirectional_normalizes_to_candidate( + param_env, lhs, rhs, direction, + ) + { + Ok(bidirectional_normalizes_to_response) + } else { + self.flounder(&candidates) + } + } + SolverMode::Coherence => self.flounder(&candidates), + } + } + } + } + } + + #[instrument(level = "debug", skip(self), ret)] + fn assemble_normalizes_to_candidate( + &mut self, + param_env: ty::ParamEnv<'tcx>, + alias: ty::AliasTy<'tcx>, + other: ty::Term<'tcx>, + direction: ty::AliasRelationDirection, + invert: Invert, + ) -> QueryResult<'tcx> { + self.probe_candidate("normalizes-to").enter(|ecx| { + ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?; + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + } + + // Computes the normalizes-to branch, with side-effects. This must be performed + // in a probe in order to not taint the evaluation context. + fn normalizes_to_inner( + &mut self, + param_env: ty::ParamEnv<'tcx>, + alias: ty::AliasTy<'tcx>, + other: ty::Term<'tcx>, + direction: ty::AliasRelationDirection, + invert: Invert, + ) -> Result<(), NoSolution> { + let other = match direction { + // This is purely an optimization. No need to instantiate a new + // infer var and equate the RHS to it. + ty::AliasRelationDirection::Equate => other, + + // Instantiate an infer var and subtype our RHS to it, so that we + // properly represent a subtype relation between the LHS and RHS + // of the goal. + ty::AliasRelationDirection::Subtype => { + let fresh = self.next_term_infer_of_kind(other); + let (sub, sup) = match invert { + Invert::No => (fresh, other), + Invert::Yes => (other, fresh), + }; + self.sub(param_env, sub, sup)?; + fresh + } + }; + self.add_goal(Goal::new( + self.tcx(), + param_env, + ty::ProjectionPredicate { projection_ty: alias, term: other }, + )); + + Ok(()) + } + + fn assemble_subst_relate_candidate( + &mut self, + param_env: ty::ParamEnv<'tcx>, + alias_lhs: ty::AliasTy<'tcx>, + alias_rhs: ty::AliasTy<'tcx>, + direction: ty::AliasRelationDirection, + ) -> QueryResult<'tcx> { + self.probe_candidate("args relate").enter(|ecx| { + match direction { + ty::AliasRelationDirection::Equate => { + ecx.eq(param_env, alias_lhs, alias_rhs)?; + } + ty::AliasRelationDirection::Subtype => { + ecx.sub(param_env, alias_lhs, alias_rhs)?; + } + } + + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + } + + fn assemble_bidirectional_normalizes_to_candidate( + &mut self, + param_env: ty::ParamEnv<'tcx>, + lhs: ty::Term<'tcx>, + rhs: ty::Term<'tcx>, + direction: ty::AliasRelationDirection, + ) -> QueryResult<'tcx> { + self.probe_candidate("bidir normalizes-to").enter(|ecx| { + ecx.normalizes_to_inner( + param_env, + lhs.to_alias_ty(ecx.tcx()).unwrap(), + rhs, + direction, + Invert::No, + )?; + ecx.normalizes_to_inner( + param_env, + rhs.to_alias_ty(ecx.tcx()).unwrap(), + lhs, + direction, + Invert::Yes, + )?; + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + } +} diff --git a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs index f32ff0442a4c5..36194f973b572 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs @@ -1,17 +1,18 @@ //! Code shared by trait and projection goals for candidate assembly. -use super::search_graph::OverflowHandler; use super::{EvalCtxt, SolverMode}; use crate::traits::coherence; -use rustc_data_structures::fx::FxIndexSet; use rustc_hir::def_id::DefId; use rustc_infer::traits::query::NoSolution; -use rustc_infer::traits::util::elaborate; use rustc_infer::traits::Reveal; -use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult}; -use rustc_middle::ty::fast_reject::TreatProjections; -use rustc_middle::ty::TypeFoldable; +use rustc_middle::traits::solve::inspect::CandidateKind; +use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, QueryResult}; +use rustc_middle::traits::BuiltinImplSource; +use rustc_middle::ty::fast_reject::{SimplifiedType, TreatParams}; use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{fast_reject, TypeFoldable}; +use rustc_middle::ty::{ToPredicate, TypeVisitableExt}; +use rustc_span::ErrorGuaranteed; use std::fmt::Debug; pub(super) mod structural_traits; @@ -48,7 +49,7 @@ pub(super) enum CandidateSource { /// Notable examples are auto traits, `Sized`, and `DiscriminantKind`. /// For a list of all traits with builtin impls, check out the /// [`EvalCtxt::assemble_builtin_impl_candidates`] method. Not - BuiltinImpl, + BuiltinImpl(BuiltinImplSource), /// An assumption from the environment. /// /// More precisely we've used the `n-th` assumption in the `param_env`. @@ -98,24 +99,24 @@ pub(super) trait GoalKind<'tcx>: fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId; - // Try equating an assumption predicate against a goal's predicate. If it - // holds, then execute the `then` callback, which should do any additional - // work, then produce a response (typically by executing - // [`EvalCtxt::evaluate_added_goals_and_make_canonical_response`]). + /// Try equating an assumption predicate against a goal's predicate. If it + /// holds, then execute the `then` callback, which should do any additional + /// work, then produce a response (typically by executing + /// [`EvalCtxt::evaluate_added_goals_and_make_canonical_response`]). fn probe_and_match_goal_against_assumption( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - assumption: ty::Predicate<'tcx>, + assumption: ty::Clause<'tcx>, then: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>, ) -> QueryResult<'tcx>; - // Consider a clause, which consists of a "assumption" and some "requirements", - // to satisfy a goal. If the requirements hold, then attempt to satisfy our - // goal by equating it with the assumption. + /// Consider a clause, which consists of a "assumption" and some "requirements", + /// to satisfy a goal. If the requirements hold, then attempt to satisfy our + /// goal by equating it with the assumption. fn consider_implied_clause( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - assumption: ty::Predicate<'tcx>, + assumption: ty::Clause<'tcx>, requirements: impl IntoIterator>>, ) -> QueryResult<'tcx> { Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| { @@ -131,36 +132,32 @@ pub(super) trait GoalKind<'tcx>: fn consider_alias_bound_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - assumption: ty::Predicate<'tcx>, + assumption: ty::Clause<'tcx>, ) -> QueryResult<'tcx> { Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| { ecx.validate_alias_bound_self_from_param_env(goal) }) } - // Consider a clause specifically for a `dyn Trait` self type. This requires - // additionally checking all of the supertraits and object bounds to hold, - // since they're not implied by the well-formedness of the object type. + /// Consider a clause specifically for a `dyn Trait` self type. This requires + /// additionally checking all of the supertraits and object bounds to hold, + /// since they're not implied by the well-formedness of the object type. fn consider_object_bound_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - assumption: ty::Predicate<'tcx>, + assumption: ty::Clause<'tcx>, ) -> QueryResult<'tcx> { Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| { let tcx = ecx.tcx(); let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else { - bug!("expected object type in `consider_object_bound_candidate`"); - }; - ecx.add_goals( - structural_traits::predicates_for_object_candidate( - &ecx, - goal.param_env, - goal.predicate.trait_ref(tcx), - bounds, - ) - .into_iter() - .map(|pred| goal.with(tcx, pred)), - ); + bug!("expected object type in `consider_object_bound_candidate`"); + }; + ecx.add_goals(structural_traits::predicates_for_object_candidate( + &ecx, + goal.param_env, + goal.predicate.trait_ref(tcx), + bounds, + )); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -171,112 +168,137 @@ pub(super) trait GoalKind<'tcx>: impl_def_id: DefId, ) -> QueryResult<'tcx>; - // A type implements an `auto trait` if its components do as well. These components - // are given by built-in rules from [`instantiate_constituent_tys_for_auto_trait`]. + /// If the predicate contained an error, we want to avoid emitting unnecessary trait + /// errors but still want to emit errors for other trait goals. We have some special + /// handling for this case. + /// + /// Trait goals always hold while projection goals never do. This is a bit arbitrary + /// but prevents incorrect normalization while hiding any trait errors. + fn consider_error_guaranteed_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + guar: ErrorGuaranteed, + ) -> QueryResult<'tcx>; + + /// A type implements an `auto trait` if its components do as well. + /// + /// These components are given by built-in rules from + /// [`structural_traits::instantiate_constituent_tys_for_auto_trait`]. fn consider_auto_trait_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A trait alias holds if the RHS traits and `where` clauses hold. + /// A trait alias holds if the RHS traits and `where` clauses hold. fn consider_trait_alias_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A type is `Copy` or `Clone` if its components are `Sized`. These components - // are given by built-in rules from [`instantiate_constituent_tys_for_sized_trait`]. + /// A type is `Copy` or `Clone` if its components are `Sized`. + /// + /// These components are given by built-in rules from + /// [`structural_traits::instantiate_constituent_tys_for_sized_trait`]. fn consider_builtin_sized_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A type is `Copy` or `Clone` if its components are `Copy` or `Clone`. These - // components are given by built-in rules from [`instantiate_constituent_tys_for_copy_clone_trait`]. + /// A type is `Copy` or `Clone` if its components are `Copy` or `Clone`. + /// + /// These components are given by built-in rules from + /// [`structural_traits::instantiate_constituent_tys_for_copy_clone_trait`]. fn consider_builtin_copy_clone_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A type is `PointerLike` if we can compute its layout, and that layout - // matches the layout of `usize`. + /// A type is `PointerLike` if we can compute its layout, and that layout + /// matches the layout of `usize`. fn consider_builtin_pointer_like_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A type is a `FnPtr` if it is of `FnPtr` type. + /// A type is a `FnPtr` if it is of `FnPtr` type. fn consider_builtin_fn_ptr_trait_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A callable type (a closure, fn def, or fn ptr) is known to implement the `Fn` - // family of traits where `A` is given by the signature of the type. + /// A callable type (a closure, fn def, or fn ptr) is known to implement the `Fn` + /// family of traits where `A` is given by the signature of the type. fn consider_builtin_fn_trait_candidates( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, kind: ty::ClosureKind, ) -> QueryResult<'tcx>; - // `Tuple` is implemented if the `Self` type is a tuple. + /// `Tuple` is implemented if the `Self` type is a tuple. fn consider_builtin_tuple_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // `Pointee` is always implemented. - // - // See the projection implementation for the `Metadata` types for all of - // the built-in types. For structs, the metadata type is given by the struct - // tail. + /// `Pointee` is always implemented. + /// + /// See the projection implementation for the `Metadata` types for all of + /// the built-in types. For structs, the metadata type is given by the struct + /// tail. fn consider_builtin_pointee_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A generator (that comes from an `async` desugaring) is known to implement - // `Future`, where `O` is given by the generator's return type - // that was computed during type-checking. + /// A generator (that comes from an `async` desugaring) is known to implement + /// `Future`, where `O` is given by the generator's return type + /// that was computed during type-checking. fn consider_builtin_future_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // A generator (that doesn't come from an `async` desugaring) is known to - // implement `Generator`, given the resume, yield, - // and return types of the generator computed during type-checking. + /// A generator (that doesn't come from an `async` desugaring) is known to + /// implement `Generator`, given the resume, yield, + /// and return types of the generator computed during type-checking. fn consider_builtin_generator_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // The most common forms of unsizing are array to slice, and concrete (Sized) - // type into a `dyn Trait`. ADTs and Tuples can also have their final field - // unsized if it's generic. - fn consider_builtin_unsize_candidate( + fn consider_builtin_discriminant_kind_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - // `dyn Trait1` can be unsized to `dyn Trait2` if they are the same trait, or - // if `Trait2` is a (transitive) supertrait of `Trait2`. - fn consider_builtin_dyn_upcast_candidates( + fn consider_builtin_destruct_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - ) -> Vec>; + ) -> QueryResult<'tcx>; - fn consider_builtin_discriminant_kind_candidate( + fn consider_builtin_transmute_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; - fn consider_builtin_destruct_candidate( + /// Consider (possibly several) candidates to upcast or unsize a type to another + /// type, excluding the coercion of a sized type into a `dyn Trait`. + /// + /// We return the `BuiltinImplSource` for each candidate as it is needed + /// for unsize coercion in hir typeck and because it is difficult to + /// otherwise recompute this for codegen. This is a bit of a mess but the + /// easiest way to maintain the existing behavior for now. + fn consider_structural_builtin_unsize_candidates( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - ) -> QueryResult<'tcx>; + ) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)>; - fn consider_builtin_transmute_candidate( + /// Consider the `Unsize` candidate corresponding to coercing a sized type + /// into a `dyn Trait`. + /// + /// This is computed separately from the rest of the `Unsize` candidates + /// since it is only done once per self type, and not once per + /// *normalization step* (in `assemble_candidates_via_self_ty`). + fn consider_unsize_to_dyn_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx>; @@ -288,108 +310,263 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { goal: Goal<'tcx, G>, ) -> Vec> { debug_assert_eq!(goal, self.resolve_vars_if_possible(goal)); + if let Some(ambig) = self.assemble_self_ty_infer_ambiguity_response(goal) { + return ambig; + } + + let mut candidates = self.assemble_candidates_via_self_ty(goal, 0); + + self.assemble_unsize_to_dyn_candidate(goal, &mut candidates); - // HACK: `_: Trait` is ambiguous, because it may be satisfied via a builtin rule, - // object bound, alias bound, etc. We are unable to determine this until we can at - // least structurally resolve the type one layer. - if goal.predicate.self_ty().is_ty_var() { - return vec![Candidate { - source: CandidateSource::BuiltinImpl, + self.assemble_blanket_impl_candidates(goal, &mut candidates); + + self.assemble_param_env_candidates(goal, &mut candidates); + + self.assemble_coherence_unknowable_candidates(goal, &mut candidates); + + candidates + } + + /// `?0: Trait` is ambiguous, because it may be satisfied via a builtin rule, + /// object bound, alias bound, etc. We are unable to determine this until we can at + /// least structurally resolve the type one layer. + /// + /// It would also require us to consider all impls of the trait, which is both pretty + /// bad for perf and would also constrain the self type if there is just a single impl. + fn assemble_self_ty_infer_ambiguity_response>( + &mut self, + goal: Goal<'tcx, G>, + ) -> Option>> { + goal.predicate.self_ty().is_ty_var().then(|| { + vec![Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), result: self .evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) .unwrap(), - }]; + }] + }) + } + + /// Assemble candidates which apply to the self type. This only looks at candidate which + /// apply to the specific self type and ignores all others. + /// + /// Returns `None` if the self type is still ambiguous. + fn assemble_candidates_via_self_ty>( + &mut self, + goal: Goal<'tcx, G>, + num_steps: usize, + ) -> Vec> { + debug_assert_eq!(goal, self.resolve_vars_if_possible(goal)); + if let Some(ambig) = self.assemble_self_ty_infer_ambiguity_response(goal) { + return ambig; } let mut candidates = Vec::new(); - self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates); - - self.assemble_impl_candidates(goal, &mut candidates); + self.assemble_non_blanket_impl_candidates(goal, &mut candidates); self.assemble_builtin_impl_candidates(goal, &mut candidates); - self.assemble_param_env_candidates(goal, &mut candidates); - self.assemble_alias_bound_candidates(goal, &mut candidates); self.assemble_object_bound_candidates(goal, &mut candidates); - self.assemble_coherence_unknowable_candidates(goal, &mut candidates); - + self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates, num_steps); candidates } - /// If the self type of a goal is a projection, computing the relevant candidates is difficult. + /// If the self type of a goal is an alias we first try to normalize the self type + /// and compute the candidates for the normalized self type in case that succeeds. + /// + /// These candidates are used in addition to the ones with the alias as a self type. + /// We do this to simplify both builtin candidates and for better performance. + /// + /// We generate the builtin candidates on the fly by looking at the self type, e.g. + /// add `FnPtr` candidates if the self type is a function pointer. Handling builtin + /// candidates while the self type is still an alias seems difficult. This is similar + /// to `try_structurally_resolve_type` during hir typeck (FIXME once implemented). /// - /// To deal with this, we first try to normalize the self type and add the candidates for the normalized - /// self type to the list of candidates in case that succeeds. We also have to consider candidates with the - /// projection as a self type as well + /// Looking at all impls for some trait goal is prohibitively expensive. We therefore + /// only look at implementations with a matching self type. Because of this function, + /// we can avoid looking at all existing impls if the self type is an alias. #[instrument(level = "debug", skip_all)] fn assemble_candidates_after_normalizing_self_ty>( &mut self, goal: Goal<'tcx, G>, candidates: &mut Vec>, + num_steps: usize, ) { let tcx = self.tcx(); - let &ty::Alias(_, projection_ty) = goal.predicate.self_ty().kind() else { - return + let &ty::Alias(_, projection_ty) = goal.predicate.self_ty().kind() else { return }; + + candidates.extend(self.probe(|_| CandidateKind::NormalizedSelfTyAssembly).enter(|ecx| { + if num_steps < ecx.local_overflow_limit() { + let normalized_ty = ecx.next_ty_infer(); + let normalizes_to_goal = goal.with( + tcx, + ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() }, + ); + ecx.add_goal(normalizes_to_goal); + if let Err(NoSolution) = ecx.try_evaluate_added_goals() { + debug!("self type normalization failed"); + return vec![]; + } + let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty); + debug!(?normalized_ty, "self type normalized"); + // NOTE: Alternatively we could call `evaluate_goal` here and only + // have a `Normalized` candidate. This doesn't work as long as we + // use `CandidateSource` in winnowing. + let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty)); + ecx.assemble_candidates_via_self_ty(goal, num_steps + 1) + } else { + match ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW) { + Ok(result) => vec![Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result, + }], + Err(NoSolution) => vec![], + } + } + })); + } + + #[instrument(level = "debug", skip_all)] + fn assemble_non_blanket_impl_candidates>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + let tcx = self.tcx(); + let self_ty = goal.predicate.self_ty(); + let trait_impls = tcx.trait_impls_of(goal.predicate.trait_def_id(tcx)); + let mut consider_impls_for_simplified_type = |simp| { + if let Some(impls_for_type) = trait_impls.non_blanket_impls().get(&simp) { + for &impl_def_id in impls_for_type { + match G::consider_impl_candidate(self, goal, impl_def_id) { + Ok(result) => candidates + .push(Candidate { source: CandidateSource::Impl(impl_def_id), result }), + Err(NoSolution) => (), + } + } + } }; - let normalized_self_candidates: Result<_, NoSolution> = self.probe(|ecx| { - ecx.with_incremented_depth( - |ecx| { - let result = ecx.evaluate_added_goals_and_make_canonical_response( - Certainty::Maybe(MaybeCause::Overflow), - )?; - Ok(vec![Candidate { source: CandidateSource::BuiltinImpl, result }]) - }, - |ecx| { - let normalized_ty = ecx.next_ty_infer(); - let normalizes_to_goal = goal.with( - tcx, - ty::Binder::dummy(ty::ProjectionPredicate { - projection_ty, - term: normalized_ty.into(), - }), - ); - ecx.add_goal(normalizes_to_goal); - let _ = ecx.try_evaluate_added_goals().inspect_err(|_| { - debug!("self type normalization failed"); - })?; - let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty); - debug!(?normalized_ty, "self type normalized"); - // NOTE: Alternatively we could call `evaluate_goal` here and only - // have a `Normalized` candidate. This doesn't work as long as we - // use `CandidateSource` in winnowing. - let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty)); - Ok(ecx.assemble_and_evaluate_candidates(goal)) - }, - ) - }); + match self_ty.kind() { + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Adt(_, _) + | ty::Foreign(_) + | ty::Str + | ty::Array(_, _) + | ty::Slice(_) + | ty::RawPtr(_) + | ty::Ref(_, _, _) + | ty::FnDef(_, _) + | ty::FnPtr(_) + | ty::Dynamic(_, _, _) + | ty::Closure(_, _) + | ty::Generator(_, _, _) + | ty::Never + | ty::Tuple(_) => { + let simp = + fast_reject::simplify_type(tcx, self_ty, TreatParams::ForLookup).unwrap(); + consider_impls_for_simplified_type(simp); + } + + // HACK: For integer and float variables we have to manually look at all impls + // which have some integer or float as a self type. + ty::Infer(ty::IntVar(_)) => { + use ty::IntTy::*; + use ty::UintTy::*; + // This causes a compiler error if any new integer kinds are added. + let (I8 | I16 | I32 | I64 | I128 | Isize): ty::IntTy; + let (U8 | U16 | U32 | U64 | U128 | Usize): ty::UintTy; + let possible_integers = [ + // signed integers + SimplifiedType::Int(I8), + SimplifiedType::Int(I16), + SimplifiedType::Int(I32), + SimplifiedType::Int(I64), + SimplifiedType::Int(I128), + SimplifiedType::Int(Isize), + // unsigned integers + SimplifiedType::Uint(U8), + SimplifiedType::Uint(U16), + SimplifiedType::Uint(U32), + SimplifiedType::Uint(U64), + SimplifiedType::Uint(U128), + SimplifiedType::Uint(Usize), + ]; + for simp in possible_integers { + consider_impls_for_simplified_type(simp); + } + } + + ty::Infer(ty::FloatVar(_)) => { + // This causes a compiler error if any new float kinds are added. + let (ty::FloatTy::F32 | ty::FloatTy::F64); + let possible_floats = [ + SimplifiedType::Float(ty::FloatTy::F32), + SimplifiedType::Float(ty::FloatTy::F64), + ]; + + for simp in possible_floats { + consider_impls_for_simplified_type(simp); + } + } - if let Ok(normalized_self_candidates) = normalized_self_candidates { - candidates.extend(normalized_self_candidates); + // The only traits applying to aliases and placeholders are blanket impls. + // + // Impls which apply to an alias after normalization are handled by + // `assemble_candidates_after_normalizing_self_ty`. + ty::Alias(_, _) | ty::Placeholder(..) | ty::Error(_) => (), + + // FIXME: These should ideally not exist as a self type. It would be nice for + // the builtin auto trait impls of generators to instead directly recurse + // into the witness. + ty::GeneratorWitness(_) | ty::GeneratorWitnessMIR(_, _) => (), + + // These variants should not exist as a self type. + ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) + | ty::Param(_) + | ty::Bound(_, _) => bug!("unexpected self type: {self_ty}"), } } - #[instrument(level = "debug", skip_all)] - fn assemble_impl_candidates>( + fn assemble_unsize_to_dyn_candidate>( &mut self, goal: Goal<'tcx, G>, candidates: &mut Vec>, ) { let tcx = self.tcx(); - tcx.for_each_relevant_impl_treating_projections( - goal.predicate.trait_def_id(tcx), - goal.predicate.self_ty(), - TreatProjections::NextSolverLookup, - |impl_def_id| match G::consider_impl_candidate(self, goal, impl_def_id) { + if tcx.lang_items().unsize_trait() == Some(goal.predicate.trait_def_id(tcx)) { + match G::consider_unsize_to_dyn_candidate(self, goal) { + Ok(result) => candidates.push(Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result, + }), + Err(NoSolution) => (), + } + } + } + + fn assemble_blanket_impl_candidates>( + &mut self, + goal: Goal<'tcx, G>, + candidates: &mut Vec>, + ) { + let tcx = self.tcx(); + let trait_impls = tcx.trait_impls_of(goal.predicate.trait_def_id(tcx)); + for &impl_def_id in trait_impls.blanket_impls() { + match G::consider_impl_candidate(self, goal, impl_def_id) { Ok(result) => candidates .push(Candidate { source: CandidateSource::Impl(impl_def_id), result }), Err(NoSolution) => (), - }, - ); + } + } } #[instrument(level = "debug", skip_all)] @@ -398,8 +575,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { goal: Goal<'tcx, G>, candidates: &mut Vec>, ) { - let lang_items = self.tcx().lang_items(); - let trait_def_id = goal.predicate.trait_def_id(self.tcx()); + let tcx = self.tcx(); + let lang_items = tcx.lang_items(); + let trait_def_id = goal.predicate.trait_def_id(tcx); // N.B. When assembling built-in candidates for lang items that are also // `auto` traits, then the auto trait candidate that is assembled in @@ -408,9 +586,11 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { // Instead of adding the logic here, it's a better idea to add it in // `EvalCtxt::disqualify_auto_trait_candidate_due_to_possible_impl` in // `solve::trait_goals` instead. - let result = if self.tcx().trait_is_auto(trait_def_id) { + let result = if let Err(guar) = goal.predicate.error_reported() { + G::consider_error_guaranteed_candidate(self, guar) + } else if tcx.trait_is_auto(trait_def_id) { G::consider_auto_trait_candidate(self, goal) - } else if self.tcx().trait_is_alias(trait_def_id) { + } else if tcx.trait_is_alias(trait_def_id) { G::consider_trait_alias_candidate(self, goal) } else if lang_items.sized_trait() == Some(trait_def_id) { G::consider_builtin_sized_candidate(self, goal) @@ -432,8 +612,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { G::consider_builtin_future_candidate(self, goal) } else if lang_items.gen_trait() == Some(trait_def_id) { G::consider_builtin_generator_candidate(self, goal) - } else if lang_items.unsize_trait() == Some(trait_def_id) { - G::consider_builtin_unsize_candidate(self, goal) } else if lang_items.discriminant_kind_trait() == Some(trait_def_id) { G::consider_builtin_discriminant_kind_candidate(self, goal) } else if lang_items.destruct_trait() == Some(trait_def_id) { @@ -445,17 +623,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { }; match result { - Ok(result) => { - candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }) - } + Ok(result) => candidates.push(Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result, + }), Err(NoSolution) => (), } // There may be multiple unsize candidates for a trait with several supertraits: // `trait Foo: Bar + Bar` and `dyn Foo: Unsize>` if lang_items.unsize_trait() == Some(trait_def_id) { - for result in G::consider_builtin_dyn_upcast_candidates(self, goal) { - candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }); + for (result, source) in G::consider_structural_builtin_unsize_candidates(self, goal) { + candidates.push(Candidate { source: CandidateSource::BuiltinImpl(source), result }); } } } @@ -508,14 +687,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { | ty::Placeholder(..) | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) | ty::Alias(ty::Inherent, _) + | ty::Alias(ty::Weak, _) | ty::Error(_) => return, ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) | ty::Bound(..) => bug!("unexpected self type for `{goal:?}`"), - // Excluding IATs here as they don't have meaningful item bounds. + // Excluding IATs and type aliases here as they don't have meaningful item bounds. ty::Alias(ty::Projection | ty::Opaque, alias_ty) => alias_ty, }; - for assumption in self.tcx().item_bounds(alias_ty.def_id).subst(self.tcx(), alias_ty.substs) + for assumption in + self.tcx().item_bounds(alias_ty.def_id).instantiate(self.tcx(), alias_ty.args) { match G::consider_alias_bound_candidate(self, goal, assumption) { Ok(result) => { @@ -555,7 +736,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { self.tcx(), ty::TraitPredicate { trait_ref: self_trait_ref, - constness: ty::BoundConstness::NotConst, polarity: ty::ImplPolarity::Positive, }, ); @@ -618,9 +798,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { }; match result { - Ok(result) => { - candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }) - } + Ok(result) => candidates.push(Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result, + }), Err(NoSolution) => (), } } @@ -631,6 +812,11 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { goal: Goal<'tcx, G>, candidates: &mut Vec>, ) { + let tcx = self.tcx(); + if !tcx.trait_def(goal.predicate.trait_def_id(tcx)).implement_via_object { + return; + } + let self_ty = goal.predicate.self_ty(); let bounds = match *self_ty.kind() { ty::Bool @@ -663,32 +849,53 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ty::Dynamic(bounds, ..) => bounds, }; - let tcx = self.tcx(); - let own_bounds: FxIndexSet<_> = - bounds.iter().map(|bound| bound.with_self_ty(tcx, self_ty)).collect(); - for assumption in elaborate(tcx, own_bounds.iter().copied()) - // we only care about bounds that match the `Self` type - .filter_only_self() - { - // FIXME: Predicates are fully elaborated in the object type's existential bounds - // list. We want to only consider these pre-elaborated projections, and not other - // projection predicates that we reach by elaborating the principal trait ref, - // since that'll cause ambiguity. - // - // We can remove this when we have implemented intersections in responses. - if assumption.to_opt_poly_projection_pred().is_some() - && !own_bounds.contains(&assumption) - { - continue; - } + // Do not consider built-in object impls for non-object-safe types. + if bounds.principal_def_id().is_some_and(|def_id| !tcx.check_is_object_safe(def_id)) { + return; + } - match G::consider_object_bound_candidate(self, goal, assumption) { - Ok(result) => { - candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result }) + // Consider all of the auto-trait and projection bounds, which don't + // need to be recorded as a `BuiltinImplSource::Object` since they don't + // really have a vtable base... + for bound in bounds { + match bound.skip_binder() { + ty::ExistentialPredicate::Trait(_) => { + // Skip principal + } + ty::ExistentialPredicate::Projection(_) + | ty::ExistentialPredicate::AutoTrait(_) => { + match G::consider_object_bound_candidate( + self, + goal, + bound.with_self_ty(tcx, self_ty), + ) { + Ok(result) => candidates.push(Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result, + }), + Err(NoSolution) => (), + } } - Err(NoSolution) => (), } } + + // FIXME: We only need to do *any* of this if we're considering a trait goal, + // since we don't need to look at any supertrait or anything if we are doing + // a projection goal. + if let Some(principal) = bounds.principal() { + let principal_trait_ref = principal.with_self_ty(tcx, self_ty); + self.walk_vtable(principal_trait_ref, |ecx, assumption, vtable_base, _| { + match G::consider_object_bound_candidate(ecx, goal, assumption.to_predicate(tcx)) { + Ok(result) => candidates.push(Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Object { + vtable_base, + }), + result, + }), + Err(NoSolution) => (), + } + }); + } } #[instrument(level = "debug", skip_all)] @@ -697,24 +904,43 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { goal: Goal<'tcx, G>, candidates: &mut Vec>, ) { + let tcx = self.tcx(); match self.solver_mode() { SolverMode::Normal => return, - SolverMode::Coherence => { - let trait_ref = goal.predicate.trait_ref(self.tcx()); - match coherence::trait_ref_is_knowable(self.tcx(), trait_ref) { - Ok(()) => {} - Err(_) => match self - .evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) - { - Ok(result) => candidates - .push(Candidate { source: CandidateSource::BuiltinImpl, result }), - // FIXME: This will be reachable at some point if we're in - // `assemble_candidates_after_normalizing_self_ty` and we get a - // universe error. We'll deal with it at this point. - Err(NoSolution) => bug!("coherence candidate resulted in NoSolution"), - }, + SolverMode::Coherence => {} + }; + + let result = self.probe_candidate("coherence unknowable").enter(|ecx| { + let trait_ref = goal.predicate.trait_ref(tcx); + + #[derive(Debug)] + enum FailureKind { + Overflow, + NoSolution(NoSolution), + } + let lazily_normalize_ty = |ty| match ecx.try_normalize_ty(goal.param_env, ty) { + Ok(Some(ty)) => Ok(ty), + Ok(None) => Err(FailureKind::Overflow), + Err(e) => Err(FailureKind::NoSolution(e)), + }; + + match coherence::trait_ref_is_knowable(tcx, trait_ref, lazily_normalize_ty) { + Err(FailureKind::Overflow) => { + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW) + } + Err(FailureKind::NoSolution(NoSolution)) | Ok(Ok(())) => Err(NoSolution), + Ok(Err(_)) => { + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } } + }); + + match result { + Ok(result) => candidates.push(Candidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result, + }), + Err(NoSolution) => {} } } diff --git a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs index 0ede32c753c69..c47767101a466 100644 --- a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs @@ -1,6 +1,9 @@ +//! Code which is used by built-in goals that match "structurally", such a auto +//! traits, `Copy`/`Clone`. use rustc_data_structures::fx::FxHashMap; use rustc_hir::{def_id::DefId, Movability, Mutability}; use rustc_infer::traits::query::NoSolution; +use rustc_middle::traits::solve::Goal; use rustc_middle::ty::{ self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, }; @@ -28,12 +31,12 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_auto_trait<'tcx>( | ty::Char => Ok(vec![]), // Treat `str` like it's defined as `struct str([u8]);` - ty::Str => Ok(vec![tcx.mk_slice(tcx.types.u8)]), + ty::Str => Ok(vec![Ty::new_slice(tcx, tcx.types.u8)]), ty::Dynamic(..) | ty::Param(..) | ty::Foreign(..) - | ty::Alias(ty::Projection | ty::Inherent, ..) + | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) | ty::Placeholder(..) | ty::Bound(..) | ty::Infer(_) => { @@ -51,36 +54,36 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_auto_trait<'tcx>( Ok(tys.iter().collect()) } - ty::Closure(_, ref substs) => Ok(vec![substs.as_closure().tupled_upvars_ty()]), + ty::Closure(_, ref args) => Ok(vec![args.as_closure().tupled_upvars_ty()]), - ty::Generator(_, ref substs, _) => { - let generator_substs = substs.as_generator(); - Ok(vec![generator_substs.tupled_upvars_ty(), generator_substs.witness()]) + ty::Generator(_, ref args, _) => { + let generator_args = args.as_generator(); + Ok(vec![generator_args.tupled_upvars_ty(), generator_args.witness()]) } ty::GeneratorWitness(types) => Ok(ecx.instantiate_binder_with_placeholders(types).to_vec()), - ty::GeneratorWitnessMIR(def_id, substs) => Ok(ecx + ty::GeneratorWitnessMIR(def_id, args) => Ok(ecx .tcx() .generator_hidden_types(def_id) .map(|bty| { ecx.instantiate_binder_with_placeholders(replace_erased_lifetimes_with_bound_vars( tcx, - bty.subst(tcx, substs), + bty.instantiate(tcx, args), )) }) .collect()), // For `PhantomData`, we pass `T`. - ty::Adt(def, substs) if def.is_phantom_data() => Ok(vec![substs.type_at(0)]), + ty::Adt(def, args) if def.is_phantom_data() => Ok(vec![args.type_at(0)]), - ty::Adt(def, substs) => Ok(def.all_fields().map(|f| f.ty(tcx, substs)).collect()), + ty::Adt(def, args) => Ok(def.all_fields().map(|f| f.ty(tcx, args)).collect()), - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { // We can resolve the `impl Trait` to its concrete type, // which enforces a DAG between the functions requiring // the auto trait bounds in question. - Ok(vec![tcx.type_of(def_id).subst(tcx, substs)]) + Ok(vec![tcx.type_of(def_id).instantiate(tcx, args)]) } } } @@ -96,7 +99,7 @@ pub(in crate::solve) fn replace_erased_lifetimes_with_bound_vars<'tcx>( let br = ty::BoundRegion { var: ty::BoundVar::from_u32(counter), kind: ty::BrAnon(None) }; counter += 1; - tcx.mk_re_late_bound(current_depth, br) + ty::Region::new_late_bound(tcx, current_depth, br) } // All free regions should be erased here. r => bug!("unexpected region: {r:?}"), @@ -146,13 +149,9 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_sized_trait<'tcx>( ty::Tuple(tys) => Ok(tys.to_vec()), - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let sized_crit = def.sized_constraint(ecx.tcx()); - Ok(sized_crit - .0 - .iter() - .map(|ty| sized_crit.rebind(*ty).subst(ecx.tcx(), substs)) - .collect()) + Ok(sized_crit.iter_instantiated(ecx.tcx(), args).collect()) } } } @@ -162,14 +161,12 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>( ty: Ty<'tcx>, ) -> Result>, NoSolution> { match *ty.kind() { - ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) - | ty::FnDef(..) - | ty::FnPtr(_) - | ty::Error(_) => Ok(vec![]), + ty::FnDef(..) | ty::FnPtr(_) | ty::Error(_) => Ok(vec![]), // Implementations are provided in core ty::Uint(_) | ty::Int(_) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) | ty::Bool | ty::Float(_) | ty::Char @@ -196,11 +193,11 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>( ty::Tuple(tys) => Ok(tys.to_vec()), - ty::Closure(_, substs) => Ok(vec![substs.as_closure().tupled_upvars_ty()]), + ty::Closure(_, args) => Ok(vec![args.as_closure().tupled_upvars_ty()]), - ty::Generator(_, substs, Movability::Movable) => { + ty::Generator(_, args, Movability::Movable) => { if ecx.tcx().features().generator_clone { - let generator = substs.as_generator(); + let generator = args.as_generator(); Ok(vec![generator.tupled_upvars_ty(), generator.witness()]) } else { Err(NoSolution) @@ -209,13 +206,13 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_copy_clone_trait<'tcx>( ty::GeneratorWitness(types) => Ok(ecx.instantiate_binder_with_placeholders(types).to_vec()), - ty::GeneratorWitnessMIR(def_id, substs) => Ok(ecx + ty::GeneratorWitnessMIR(def_id, args) => Ok(ecx .tcx() .generator_hidden_types(def_id) .map(|bty| { ecx.instantiate_binder_with_placeholders(replace_erased_lifetimes_with_bound_vars( ecx.tcx(), - bty.subst(ecx.tcx(), substs), + bty.instantiate(ecx.tcx(), args), )) }) .collect()), @@ -230,14 +227,14 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>( ) -> Result, Ty<'tcx>)>>, NoSolution> { match *self_ty.kind() { // keep this in sync with assemble_fn_pointer_candidates until the old solver is removed. - ty::FnDef(def_id, substs) => { + ty::FnDef(def_id, args) => { let sig = tcx.fn_sig(def_id); if sig.skip_binder().is_fn_trait_compatible() && tcx.codegen_fn_attrs(def_id).target_features.is_empty() { Ok(Some( - sig.subst(tcx, substs) - .map_bound(|sig| (tcx.mk_tup(sig.inputs()), sig.output())), + sig.instantiate(tcx, args) + .map_bound(|sig| (Ty::new_tup(tcx, sig.inputs()), sig.output())), )) } else { Err(NoSolution) @@ -246,14 +243,14 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>( // keep this in sync with assemble_fn_pointer_candidates until the old solver is removed. ty::FnPtr(sig) => { if sig.is_fn_trait_compatible() { - Ok(Some(sig.map_bound(|sig| (tcx.mk_tup(sig.inputs()), sig.output())))) + Ok(Some(sig.map_bound(|sig| (Ty::new_tup(tcx, sig.inputs()), sig.output())))) } else { Err(NoSolution) } } - ty::Closure(_, substs) => { - let closure_substs = substs.as_closure(); - match closure_substs.kind_ty().to_opt_closure_kind() { + ty::Closure(_, args) => { + let closure_args = args.as_closure(); + match closure_args.kind_ty().to_opt_closure_kind() { // If the closure's kind doesn't extend the goal kind, // then the closure doesn't implement the trait. Some(closure_kind) => { @@ -269,7 +266,7 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>( } } } - Ok(Some(closure_substs.sig().map_bound(|sig| (sig.inputs()[0], sig.output())))) + Ok(Some(closure_args.sig().map_bound(|sig| (sig.inputs()[0], sig.output())))) } ty::Bool | ty::Char @@ -347,17 +344,18 @@ pub(in crate::solve) fn predicates_for_object_candidate<'tcx>( param_env: ty::ParamEnv<'tcx>, trait_ref: ty::TraitRef<'tcx>, object_bound: &'tcx ty::List>, -) -> Vec> { +) -> Vec>> { let tcx = ecx.tcx(); let mut requirements = vec![]; requirements.extend( - tcx.super_predicates_of(trait_ref.def_id).instantiate(tcx, trait_ref.substs).predicates, + tcx.super_predicates_of(trait_ref.def_id).instantiate(tcx, trait_ref.args).predicates, ); for item in tcx.associated_items(trait_ref.def_id).in_definition_order() { // FIXME(associated_const_equality): Also add associated consts to // the requirements here. if item.kind == ty::AssocKind::Type { - requirements.extend(tcx.item_bounds(item.def_id).subst(tcx, trait_ref.substs)); + requirements + .extend(tcx.item_bounds(item.def_id).iter_instantiated(tcx, trait_ref.args)); } } @@ -377,17 +375,22 @@ pub(in crate::solve) fn predicates_for_object_candidate<'tcx>( } } - requirements.fold_with(&mut ReplaceProjectionWith { - ecx, - param_env, - mapping: replace_projection_with, - }) + let mut folder = + ReplaceProjectionWith { ecx, param_env, mapping: replace_projection_with, nested: vec![] }; + let folded_requirements = requirements.fold_with(&mut folder); + + folder + .nested + .into_iter() + .chain(folded_requirements.into_iter().map(|clause| Goal::new(tcx, param_env, clause))) + .collect() } struct ReplaceProjectionWith<'a, 'tcx> { ecx: &'a EvalCtxt<'a, 'tcx>, param_env: ty::ParamEnv<'tcx>, mapping: FxHashMap>, + nested: Vec>>, } impl<'tcx> TypeFolder> for ReplaceProjectionWith<'_, 'tcx> { @@ -403,13 +406,12 @@ impl<'tcx> TypeFolder> for ReplaceProjectionWith<'_, 'tcx> { // but the where clauses we instantiated are not. We can solve this by instantiating // the binder at the usage site. let proj = self.ecx.instantiate_binder_with_infer(*replacement); - // FIXME: Technically this folder could be fallible? - let nested = self - .ecx - .eq_and_get_goals(self.param_env, alias_ty, proj.projection_ty) - .expect("expected to be able to unify goal projection with dyn's projection"); - // FIXME: Technically we could register these too.. - assert!(nested.is_empty(), "did not expect unification to have any nested goals"); + // FIXME: Technically this equate could be fallible... + self.nested.extend( + self.ecx + .eq_and_get_goals(self.param_env, alias_ty, proj.projection_ty) + .expect("expected to be able to unify goal projection with dyn's projection"), + ); proj.term.ty().unwrap() } else { ty.super_fold_with(self) diff --git a/compiler/rustc_trait_selection/src/solve/canonicalize.rs b/compiler/rustc_trait_selection/src/solve/canonicalize.rs index ff4bff10cc8a3..a9d182abfb7f7 100644 --- a/compiler/rustc_trait_selection/src/solve/canonicalize.rs +++ b/compiler/rustc_trait_selection/src/solve/canonicalize.rs @@ -125,9 +125,8 @@ impl<'a, 'tcx> Canonicalizer<'a, 'tcx> { // - var_infos: [E0, U1, E1, U1, E1, E6, U6], curr_compressed_uv: 1, next_orig_uv: 6 // - var_infos: [E0, U1, E1, U1, E1, E2, U2], curr_compressed_uv: 2, next_orig_uv: - // - // This algorithm runs in `O(nm)` where `n` is the number of different universe - // indices in the input and `m` is the number of canonical variables. - // This should be fine as both `n` and `m` are expected to be small. + // This algorithm runs in `O(n²)` where `n` is the number of different universe + // indices in the input. This should be fine as `n` is expected to be small. let mut curr_compressed_uv = ty::UniverseIndex::ROOT; let mut existential_in_new_uv = false; let mut next_orig_uv = Some(ty::UniverseIndex::ROOT); @@ -209,7 +208,19 @@ impl<'tcx> TypeFolder> for Canonicalizer<'_, 'tcx> { } fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { - let r = self.infcx.shallow_resolve(r); + if let ty::ReVar(vid) = *r { + let resolved_region = self + .infcx + .inner + .borrow_mut() + .unwrap_region_constraints() + .opportunistic_resolve_var(self.infcx.tcx, vid); + assert_eq!( + r, resolved_region, + "region var should have been resolved, {r} -> {resolved_region}" + ); + } + let kind = match *r { ty::ReLateBound(..) => return r, @@ -246,50 +257,38 @@ impl<'tcx> TypeFolder> for Canonicalizer<'_, 'tcx> { ty::ReError(_) => return r, }; - let var = ty::BoundVar::from( - self.variables.iter().position(|&v| v == r.into()).unwrap_or_else(|| { - let var = self.variables.len(); - self.variables.push(r.into()); - self.primitive_var_infos.push(CanonicalVarInfo { kind }); - var - }), - ); + let existing_bound_var = match self.canonicalize_mode { + CanonicalizeMode::Input => None, + CanonicalizeMode::Response { .. } => { + self.variables.iter().position(|&v| v == r.into()).map(ty::BoundVar::from) + } + }; + let var = existing_bound_var.unwrap_or_else(|| { + let var = ty::BoundVar::from(self.variables.len()); + self.variables.push(r.into()); + self.primitive_var_infos.push(CanonicalVarInfo { kind }); + var + }); let br = ty::BoundRegion { var, kind: BrAnon(None) }; - self.interner().mk_re_late_bound(self.binder_index, br) + ty::Region::new_late_bound(self.interner(), self.binder_index, br) } - fn fold_ty(&mut self, mut t: Ty<'tcx>) -> Ty<'tcx> { + fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { let kind = match *t.kind() { - ty::Infer(ty::TyVar(mut vid)) => { - // We need to canonicalize the *root* of our ty var. - // This is so that our canonical response correctly reflects - // any equated inference vars correctly! - let root_vid = self.infcx.root_var(vid); - if root_vid != vid { - t = self.infcx.tcx.mk_ty_var(root_vid); - vid = root_vid; - } - - match self.infcx.probe_ty_var(vid) { - Ok(t) => return self.fold_ty(t), - Err(ui) => CanonicalVarKind::Ty(CanonicalTyVarKind::General(ui)), - } + ty::Infer(ty::TyVar(vid)) => { + assert_eq!(self.infcx.root_var(vid), vid, "ty vid should have been resolved"); + let Err(ui) = self.infcx.probe_ty_var(vid) else { + bug!("ty var should have been resolved: {t}"); + }; + CanonicalVarKind::Ty(CanonicalTyVarKind::General(ui)) } ty::Infer(ty::IntVar(vid)) => { - let nt = self.infcx.opportunistic_resolve_int_var(vid); - if nt != t { - return self.fold_ty(nt); - } else { - CanonicalVarKind::Ty(CanonicalTyVarKind::Int) - } + assert_eq!(self.infcx.opportunistic_resolve_int_var(vid), t); + CanonicalVarKind::Ty(CanonicalTyVarKind::Int) } ty::Infer(ty::FloatVar(vid)) => { - let nt = self.infcx.opportunistic_resolve_float_var(vid); - if nt != t { - return self.fold_ty(nt); - } else { - CanonicalVarKind::Ty(CanonicalTyVarKind::Float) - } + assert_eq!(self.infcx.opportunistic_resolve_float_var(vid), t); + CanonicalVarKind::Ty(CanonicalTyVarKind::Float) } ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { bug!("fresh var during canonicalization: {t:?}") @@ -349,25 +348,22 @@ impl<'tcx> TypeFolder> for Canonicalizer<'_, 'tcx> { }), ); let bt = ty::BoundTy { var, kind: BoundTyKind::Anon }; - self.interner().mk_bound(self.binder_index, bt) + Ty::new_bound(self.infcx.tcx, self.binder_index, bt) } - fn fold_const(&mut self, mut c: ty::Const<'tcx>) -> ty::Const<'tcx> { + fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { let kind = match c.kind() { - ty::ConstKind::Infer(ty::InferConst::Var(mut vid)) => { - // We need to canonicalize the *root* of our const var. - // This is so that our canonical response correctly reflects - // any equated inference vars correctly! - let root_vid = self.infcx.root_const_var(vid); - if root_vid != vid { - c = self.infcx.tcx.mk_const(ty::InferConst::Var(root_vid), c.ty()); - vid = root_vid; - } - - match self.infcx.probe_const_var(vid) { - Ok(c) => return self.fold_const(c), - Err(universe) => CanonicalVarKind::Const(universe, c.ty()), - } + ty::ConstKind::Infer(ty::InferConst::Var(vid)) => { + assert_eq!( + self.infcx.root_const_var(vid), + vid, + "const var should have been resolved" + ); + let Err(ui) = self.infcx.probe_const_var(vid) else { + bug!("const var should have been resolved"); + }; + // FIXME: we should fold this ty eventually + CanonicalVarKind::Const(ui, c.ty()) } ty::ConstKind::Infer(ty::InferConst::Fresh(_)) => { bug!("fresh var during canonicalization: {c:?}") @@ -409,6 +405,6 @@ impl<'tcx> TypeFolder> for Canonicalizer<'_, 'tcx> { var }), ); - self.interner().mk_const(ty::ConstKind::Bound(self.binder_index, var), c.ty()) + ty::Const::new_bound(self.infcx.tcx, self.binder_index, var, c.ty()) } } diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index f91c672775301..5c2cbe399536a 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -1,33 +1,41 @@ +use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_infer::infer::at::ToTrace; use rustc_infer::infer::canonical::CanonicalVarValues; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_infer::infer::{ - DefineOpaqueTypes, InferCtxt, InferOk, LateBoundRegionConversionTime, RegionVariableOrigin, - TyCtxtInferExt, + DefineOpaqueTypes, InferCtxt, InferOk, LateBoundRegionConversionTime, TyCtxtInferExt, }; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::ObligationCause; +use rustc_middle::infer::canonical::CanonicalVarInfos; use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; +use rustc_middle::traits::solve::inspect; use rustc_middle::traits::solve::{ - CanonicalInput, CanonicalResponse, Certainty, MaybeCause, PredefinedOpaques, + CanonicalInput, CanonicalResponse, Certainty, IsNormalizesToHack, PredefinedOpaques, PredefinedOpaquesData, QueryResult, }; -use rustc_middle::traits::DefiningAnchor; +use rustc_middle::traits::{specialization_graph, DefiningAnchor}; use rustc_middle::ty::{ - self, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, - TypeVisitor, + self, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, }; +use rustc_session::config::DumpSolverProofTree; use rustc_span::DUMMY_SP; +use std::io::Write; use std::ops::ControlFlow; -use crate::traits::specialization_graph; +use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment}; -use super::search_graph::{self, OverflowHandler}; +use super::inspect::ProofTreeBuilder; +use super::search_graph; use super::SolverMode; use super::{search_graph::SearchGraph, Goal}; +pub use select::InferCtxtSelectExt; mod canonical; +mod probe; +mod select; pub struct EvalCtxt<'a, 'tcx> { /// The inference context that backs (mostly) inference and placeholder terms @@ -47,6 +55,9 @@ pub struct EvalCtxt<'a, 'tcx> { /// the job already. infcx: &'a InferCtxt<'tcx>, + /// The variable info for the `var_values`, only used to make an ambiguous response + /// with no constraints. + variables: CanonicalVarInfos<'tcx>, pub(super) var_values: CanonicalVarValues<'tcx>, predefined_opaques_in_body: PredefinedOpaques<'tcx>, @@ -73,12 +84,8 @@ pub struct EvalCtxt<'a, 'tcx> { // ambiguous goals. Instead, a probe needs to be introduced somewhere in the // evaluation code. tainted: Result<(), NoSolution>, -} -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub(super) enum IsNormalizesToHack { - Yes, - No, + inspect: ProofTreeBuilder<'tcx>, } #[derive(Debug, Clone)] @@ -110,6 +117,27 @@ impl NestedGoals<'_> { } } +#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)] +pub enum GenerateProofTree { + Yes(UseGlobalCache), + IfEnabled, + Never, +} + +#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)] +pub enum UseGlobalCache { + Yes, + No, +} +impl UseGlobalCache { + pub fn from_bool(use_cache: bool) -> Self { + match use_cache { + true => UseGlobalCache::Yes, + false => UseGlobalCache::No, + } + } +} + pub trait InferCtxtEvalExt<'tcx> { /// Evaluates a goal from **outside** of the trait solver. /// @@ -118,7 +146,11 @@ pub trait InferCtxtEvalExt<'tcx> { fn evaluate_root_goal( &self, goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> Result<(bool, Certainty, Vec>>), NoSolution>; + generate_proof_tree: GenerateProofTree, + ) -> ( + Result<(bool, Certainty, Vec>>), NoSolution>, + Option>, + ); } impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { @@ -126,25 +158,63 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { fn evaluate_root_goal( &self, goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> Result<(bool, Certainty, Vec>>), NoSolution> { - let mode = if self.intercrate { SolverMode::Coherence } else { SolverMode::Normal }; - let mut search_graph = search_graph::SearchGraph::new(self.tcx, mode); + generate_proof_tree: GenerateProofTree, + ) -> ( + Result<(bool, Certainty, Vec>>), NoSolution>, + Option>, + ) { + EvalCtxt::enter_root(self, generate_proof_tree, |ecx| { + ecx.evaluate_goal(IsNormalizesToHack::No, goal) + }) + } +} + +impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { + pub(super) fn solver_mode(&self) -> SolverMode { + self.search_graph.solver_mode() + } + + pub(super) fn local_overflow_limit(&self) -> usize { + self.search_graph.local_overflow_limit() + } + + /// Creates a root evaluation context and search graph. This should only be + /// used from outside of any evaluation, and other methods should be preferred + /// over using this manually (such as [`InferCtxtEvalExt::evaluate_root_goal`]). + fn enter_root( + infcx: &InferCtxt<'tcx>, + generate_proof_tree: GenerateProofTree, + f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> R, + ) -> (R, Option>) { + let mode = if infcx.intercrate { SolverMode::Coherence } else { SolverMode::Normal }; + let mut search_graph = search_graph::SearchGraph::new(infcx.tcx, mode); let mut ecx = EvalCtxt { search_graph: &mut search_graph, - infcx: self, + infcx, + nested_goals: NestedGoals::new(), + inspect: ProofTreeBuilder::new_maybe_root(infcx.tcx, generate_proof_tree), + // Only relevant when canonicalizing the response, // which we don't do within this evaluation context. - predefined_opaques_in_body: self + predefined_opaques_in_body: infcx .tcx .mk_predefined_opaques_in_body(PredefinedOpaquesData::default()), - // Only relevant when canonicalizing the response. max_input_universe: ty::UniverseIndex::ROOT, + variables: ty::List::empty(), var_values: CanonicalVarValues::dummy(), - nested_goals: NestedGoals::new(), tainted: Ok(()), }; - let result = ecx.evaluate_goal(IsNormalizesToHack::No, goal); + let result = f(&mut ecx); + + let tree = ecx.inspect.finalize(); + if let (Some(tree), DumpSolverProofTree::Always) = + (&tree, infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree) + { + let mut lock = std::io::stdout().lock(); + let _ = lock.write_fmt(format_args!("{tree:?}\n")); + let _ = lock.flush(); + } assert!( ecx.nested_goals.is_empty(), @@ -152,13 +222,70 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { ); assert!(search_graph.is_empty()); - result + (result, tree) } -} -impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { - pub(super) fn solver_mode(&self) -> SolverMode { - self.search_graph.solver_mode() + /// Creates a nested evaluation context that shares the same search graph as the + /// one passed in. This is suitable for evaluation, granted that the search graph + /// has had the nested goal recorded on its stack ([`SearchGraph::with_new_goal`]), + /// but it's preferable to use other methods that call this one rather than this + /// method directly. + /// + /// This function takes care of setting up the inference context, setting the anchor, + /// and registering opaques from the canonicalized input. + fn enter_canonical( + tcx: TyCtxt<'tcx>, + search_graph: &'a mut search_graph::SearchGraph<'tcx>, + canonical_input: CanonicalInput<'tcx>, + goal_evaluation: &mut ProofTreeBuilder<'tcx>, + f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>, Goal<'tcx, ty::Predicate<'tcx>>) -> R, + ) -> R { + let intercrate = match search_graph.solver_mode() { + SolverMode::Normal => false, + SolverMode::Coherence => true, + }; + let (ref infcx, input, var_values) = tcx + .infer_ctxt() + .intercrate(intercrate) + .with_next_trait_solver(true) + .with_opaque_type_inference(canonical_input.value.anchor) + .build_with_canonical(DUMMY_SP, &canonical_input); + + let mut ecx = EvalCtxt { + infcx, + variables: canonical_input.variables, + var_values, + predefined_opaques_in_body: input.predefined_opaques_in_body, + max_input_universe: canonical_input.max_universe, + search_graph, + nested_goals: NestedGoals::new(), + tainted: Ok(()), + inspect: goal_evaluation.new_goal_evaluation_step(input), + }; + + for &(key, ty) in &input.predefined_opaques_in_body.opaque_types { + ecx.insert_hidden_type(key, input.goal.param_env, ty) + .expect("failed to prepopulate opaque types"); + } + + if !ecx.nested_goals.is_empty() { + panic!("prepopulating opaque types shouldn't add goals: {:?}", ecx.nested_goals); + } + + let result = f(&mut ecx, input.goal); + + goal_evaluation.goal_evaluation_step(ecx.inspect); + + // When creating a query response we clone the opaque type constraints + // instead of taking them. This would cause an ICE here, since we have + // assertions against dropping an `InferCtxt` without taking opaques. + // FIXME: Once we remove support for the old impl we can remove this. + if input.anchor != DefiningAnchor::Error { + // This seems ok, but fragile. + let _ = infcx.take_opaque_types(); + } + + result } /// The entry point of the solver. @@ -170,57 +297,37 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { /// Instead of calling this function directly, use either [EvalCtxt::evaluate_goal] /// if you're inside of the solver or [InferCtxtEvalExt::evaluate_root_goal] if you're /// outside of it. - #[instrument(level = "debug", skip(tcx, search_graph), ret)] + #[instrument(level = "debug", skip(tcx, search_graph, goal_evaluation), ret)] fn evaluate_canonical_goal( tcx: TyCtxt<'tcx>, search_graph: &'a mut search_graph::SearchGraph<'tcx>, canonical_input: CanonicalInput<'tcx>, + mut goal_evaluation: &mut ProofTreeBuilder<'tcx>, ) -> QueryResult<'tcx> { + goal_evaluation.canonicalized_goal(canonical_input); + // Deal with overflow, caching, and coinduction. // // The actual solver logic happens in `ecx.compute_goal`. - search_graph.with_new_goal(tcx, canonical_input, |search_graph| { - let intercrate = match search_graph.solver_mode() { - SolverMode::Normal => false, - SolverMode::Coherence => true, - }; - let (ref infcx, input, var_values) = tcx - .infer_ctxt() - .intercrate(intercrate) - .with_opaque_type_inference(canonical_input.value.anchor) - .build_with_canonical(DUMMY_SP, &canonical_input); - - for &(a, b) in &input.predefined_opaques_in_body.opaque_types { - let InferOk { value: (), obligations } = infcx - .register_hidden_type_in_new_solver(a, input.goal.param_env, b) - .expect("expected opaque type instantiation to succeed"); - // We're only registering opaques already defined by the caller, - // so we're not responsible for proving that they satisfy their - // item bounds, unless we use them in a normalizes-to goal, - // which is handled in `EvalCtxt::unify_existing_opaque_tys`. - let _ = obligations; - } - let mut ecx = EvalCtxt { - infcx, - var_values, - predefined_opaques_in_body: input.predefined_opaques_in_body, - max_input_universe: canonical_input.max_universe, - search_graph, - nested_goals: NestedGoals::new(), - tainted: Ok(()), - }; - - let result = ecx.compute_goal(input.goal); - - // When creating a query response we clone the opaque type constraints - // instead of taking them. This would cause an ICE here, since we have - // assertions against dropping an `InferCtxt` without taking opaques. - // FIXME: Once we remove support for the old impl we can remove this. - if input.anchor != DefiningAnchor::Error { - let _ = infcx.take_opaque_types(); - } - - result + ensure_sufficient_stack(|| { + search_graph.with_new_goal( + tcx, + canonical_input, + goal_evaluation, + |search_graph, goal_evaluation| { + EvalCtxt::enter_canonical( + tcx, + search_graph, + canonical_input, + goal_evaluation, + |ecx, goal| { + let result = ecx.compute_goal(goal); + ecx.inspect.query_result(result); + result + }, + ) + }, + ) }) } @@ -232,16 +339,38 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { goal: Goal<'tcx, ty::Predicate<'tcx>>, ) -> Result<(bool, Certainty, Vec>>), NoSolution> { let (orig_values, canonical_goal) = self.canonicalize_goal(goal); - let canonical_response = - EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?; + let mut goal_evaluation = self.inspect.new_goal_evaluation(goal, is_normalizes_to_hack); + let encountered_overflow = self.search_graph.encountered_overflow(); + let canonical_response = EvalCtxt::evaluate_canonical_goal( + self.tcx(), + self.search_graph, + canonical_goal, + &mut goal_evaluation, + ); + goal_evaluation.query_result(canonical_response); + let canonical_response = match canonical_response { + Err(e) => { + self.inspect.goal_evaluation(goal_evaluation); + return Err(e); + } + Ok(response) => response, + }; - let has_changed = !canonical_response.value.var_values.is_identity() + let has_changed = !canonical_response.value.var_values.is_identity_modulo_regions() || !canonical_response.value.external_constraints.opaque_types.is_empty(); - let (certainty, nested_goals) = self.instantiate_and_apply_query_response( + let (certainty, nested_goals) = match self.instantiate_and_apply_query_response( goal.param_env, orig_values, canonical_response, - )?; + ) { + Err(e) => { + self.inspect.goal_evaluation(goal_evaluation); + return Err(e); + } + Ok(response) => response, + }; + goal_evaluation.returned_goals(&nested_goals); + self.inspect.goal_evaluation(goal_evaluation); if !has_changed && !nested_goals.is_empty() { bug!("an unchanged goal shouldn't have any side-effects on instantiation"); @@ -259,47 +388,78 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { && is_normalizes_to_hack == IsNormalizesToHack::No && !self.search_graph.in_cycle() { - debug!("rerunning goal to check result is stable"); - let (_orig_values, canonical_goal) = self.canonicalize_goal(goal); - let new_canonical_response = - EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?; - if !new_canonical_response.value.var_values.is_identity() { - bug!( - "unstable result: re-canonicalized goal={canonical_goal:#?} \ - first_response={canonical_response:#?} \ - second_response={new_canonical_response:#?}" - ); - } - if certainty != new_canonical_response.value.certainty { - bug!( - "unstable certainty: {certainty:#?} re-canonicalized goal={canonical_goal:#?} \ - first_response={canonical_response:#?} \ - second_response={new_canonical_response:#?}" - ); - } + // The nested evaluation has to happen with the original state + // of `encountered_overflow`. + let from_original_evaluation = + self.search_graph.reset_encountered_overflow(encountered_overflow); + self.check_evaluate_goal_stable_result(goal, canonical_goal, canonical_response); + // In case the evaluation was unstable, we manually make sure that this + // debug check does not influence the result of the parent goal. + self.search_graph.reset_encountered_overflow(from_original_evaluation); } Ok((has_changed, certainty, nested_goals)) } + fn check_evaluate_goal_stable_result( + &mut self, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + original_input: CanonicalInput<'tcx>, + original_result: CanonicalResponse<'tcx>, + ) { + let (_orig_values, canonical_goal) = self.canonicalize_goal(goal); + let result = EvalCtxt::evaluate_canonical_goal( + self.tcx(), + self.search_graph, + canonical_goal, + // FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal` + &mut ProofTreeBuilder::new_noop(), + ); + + macro_rules! fail { + ($msg:expr) => {{ + let msg = $msg; + warn!( + "unstable result: {msg}\n\ + original goal: {original_input:?},\n\ + original result: {original_result:?}\n\ + re-canonicalized goal: {canonical_goal:?}\n\ + second response: {result:?}" + ); + return; + }}; + } + + let Ok(new_canonical_response) = result else { fail!("second response was error") }; + // We only check for modulo regions as we convert all regions in + // the input to new existentials, even if they're expected to be + // `'static` or a placeholder region. + if !new_canonical_response.value.var_values.is_identity_modulo_regions() { + fail!("additional constraints from second response") + } + if original_result.value.certainty != new_canonical_response.value.certainty { + fail!("unstable certainty") + } + } + fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> { let Goal { param_env, predicate } = goal; let kind = predicate.kind(); if let Some(kind) = kind.no_bound_vars() { match kind { - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) => { self.compute_trait_goal(Goal { param_env, predicate }) } - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(predicate)) => { self.compute_projection_goal(Goal { param_env, predicate }) } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => { + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(predicate)) => { self.compute_type_outlives_goal(Goal { param_env, predicate }) } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(predicate)) => { self.compute_region_outlives_goal(Goal { param_env, predicate }) } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { self.compute_const_arg_has_type_goal(Goal { param_env, predicate: (ct, ty) }) } ty::PredicateKind::Subtype(predicate) => { @@ -308,32 +468,28 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { ty::PredicateKind::Coerce(predicate) => { self.compute_coerce_goal(Goal { param_env, predicate }) } - ty::PredicateKind::ClosureKind(def_id, substs, kind) => self - .compute_closure_kind_goal(Goal { - param_env, - predicate: (def_id, substs, kind), - }), + ty::PredicateKind::ClosureKind(def_id, args, kind) => self + .compute_closure_kind_goal(Goal { param_env, predicate: (def_id, args, kind) }), ty::PredicateKind::ObjectSafe(trait_def_id) => { self.compute_object_safe_goal(trait_def_id) } - ty::PredicateKind::WellFormed(arg) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { self.compute_well_formed_goal(Goal { param_env, predicate: arg }) } - ty::PredicateKind::Ambiguous => { - self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) - } - // FIXME: implement these predicates :) - ty::PredicateKind::ConstEvaluatable(_) | ty::PredicateKind::ConstEquate(_, _) => { - self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(ct)) => { + self.compute_const_evaluatable_goal(Goal { param_env, predicate: ct }) } - ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("TypeWellFormedFromEnv is only used for Chalk") + ty::PredicateKind::ConstEquate(_, _) => { + bug!("ConstEquate should not be emitted when `-Ztrait-solver=next` is active") } ty::PredicateKind::AliasRelate(lhs, rhs, direction) => self .compute_alias_relate_goal(Goal { param_env, predicate: (lhs, rhs, direction), }), + ty::PredicateKind::Ambiguous => { + self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) + } } } else { let kind = self.infcx.instantiate_binder_with_placeholders(kind); @@ -347,123 +503,116 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { // the certainty of all the goals. #[instrument(level = "debug", skip(self))] pub(super) fn try_evaluate_added_goals(&mut self) -> Result { - let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new()); - let mut new_goals = NestedGoals::new(); - - let response = self.repeat_while_none( - |_| Ok(Certainty::Maybe(MaybeCause::Overflow)), - |this| { - let mut has_changed = Err(Certainty::Yes); - - if let Some(goal) = goals.normalizes_to_hack_goal.take() { - // Replace the goal with an unconstrained infer var, so the - // RHS does not affect projection candidate assembly. - let unconstrained_rhs = this.next_term_infer_of_kind(goal.predicate.term); - let unconstrained_goal = goal.with( - this.tcx(), - ty::Binder::dummy(ty::ProjectionPredicate { - projection_ty: goal.predicate.projection_ty, - term: unconstrained_rhs, - }), - ); + let inspect = self.inspect.new_evaluate_added_goals(); + let inspect = core::mem::replace(&mut self.inspect, inspect); + + let mut response = Ok(Certainty::OVERFLOW); + for _ in 0..self.local_overflow_limit() { + // FIXME: This match is a bit ugly, it might be nice to change the inspect + // stuff to use a closure instead. which should hopefully simplify this a bit. + match self.evaluate_added_goals_step() { + Ok(Some(cert)) => { + response = Ok(cert); + break; + } + Ok(None) => {} + Err(NoSolution) => { + response = Err(NoSolution); + break; + } + } + } - let (_, certainty, instantiate_goals) = - match this.evaluate_goal(IsNormalizesToHack::Yes, unconstrained_goal) { - Ok(r) => r, - Err(NoSolution) => return Some(Err(NoSolution)), - }; - new_goals.goals.extend(instantiate_goals); - - // Finally, equate the goal's RHS with the unconstrained var. - // We put the nested goals from this into goals instead of - // next_goals to avoid needing to process the loop one extra - // time if this goal returns something -- I don't think this - // matters in practice, though. - match this.eq_and_get_goals( - goal.param_env, - goal.predicate.term, - unconstrained_rhs, - ) { - Ok(eq_goals) => { - goals.goals.extend(eq_goals); - } - Err(NoSolution) => return Some(Err(NoSolution)), - }; - - // We only look at the `projection_ty` part here rather than - // looking at the "has changed" return from evaluate_goal, - // because we expect the `unconstrained_rhs` part of the predicate - // to have changed -- that means we actually normalized successfully! - if goal.predicate.projection_ty - != this.resolve_vars_if_possible(goal.predicate.projection_ty) - { - has_changed = Ok(()) - } + self.inspect.eval_added_goals_result(response); - match certainty { - Certainty::Yes => {} - Certainty::Maybe(_) => { - // We need to resolve vars here so that we correctly - // deal with `has_changed` in the next iteration. - new_goals.normalizes_to_hack_goal = - Some(this.resolve_vars_if_possible(goal)); - has_changed = has_changed.map_err(|c| c.unify_with(certainty)); - } - } - } + if response.is_err() { + self.tainted = Err(NoSolution); + } - for goal in goals.goals.drain(..) { - let (changed, certainty, instantiate_goals) = - match this.evaluate_goal(IsNormalizesToHack::No, goal) { - Ok(result) => result, - Err(NoSolution) => return Some(Err(NoSolution)), - }; - new_goals.goals.extend(instantiate_goals); + let goal_evaluations = std::mem::replace(&mut self.inspect, inspect); + self.inspect.added_goals_evaluation(goal_evaluations); - if changed { - has_changed = Ok(()); - } + response + } - match certainty { - Certainty::Yes => {} - Certainty::Maybe(_) => { - new_goals.goals.push(goal); - has_changed = has_changed.map_err(|c| c.unify_with(certainty)); - } - } - } + /// Iterate over all added goals: returning `Ok(Some(_))` in case we can stop rerunning. + /// + /// Goals for the next step get directly added the the nested goals of the `EvalCtxt`. + fn evaluate_added_goals_step(&mut self) -> Result, NoSolution> { + let tcx = self.tcx(); + let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new()); + + self.inspect.evaluate_added_goals_loop_start(); + // If this loop did not result in any progress, what's our final certainty. + let mut unchanged_certainty = Some(Certainty::Yes); + if let Some(goal) = goals.normalizes_to_hack_goal.take() { + // Replace the goal with an unconstrained infer var, so the + // RHS does not affect projection candidate assembly. + let unconstrained_rhs = self.next_term_infer_of_kind(goal.predicate.term); + let unconstrained_goal = goal.with( + tcx, + ty::ProjectionPredicate { + projection_ty: goal.predicate.projection_ty, + term: unconstrained_rhs, + }, + ); + + let (_, certainty, instantiate_goals) = + self.evaluate_goal(IsNormalizesToHack::Yes, unconstrained_goal)?; + self.add_goals(instantiate_goals); + + // Finally, equate the goal's RHS with the unconstrained var. + // We put the nested goals from this into goals instead of + // next_goals to avoid needing to process the loop one extra + // time if this goal returns something -- I don't think this + // matters in practice, though. + let eq_goals = + self.eq_and_get_goals(goal.param_env, goal.predicate.term, unconstrained_rhs)?; + goals.goals.extend(eq_goals); + + // We only look at the `projection_ty` part here rather than + // looking at the "has changed" return from evaluate_goal, + // because we expect the `unconstrained_rhs` part of the predicate + // to have changed -- that means we actually normalized successfully! + if goal.predicate.projection_ty + != self.resolve_vars_if_possible(goal.predicate.projection_ty) + { + unchanged_certainty = None; + } - core::mem::swap(&mut new_goals, &mut goals); - match has_changed { - Ok(()) => None, - Err(certainty) => Some(Ok(certainty)), + match certainty { + Certainty::Yes => {} + Certainty::Maybe(_) => { + // We need to resolve vars here so that we correctly + // deal with `has_changed` in the next iteration. + self.set_normalizes_to_hack_goal(self.resolve_vars_if_possible(goal)); + unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty)); } - }, - ); + } + } - if response.is_err() { - self.tainted = Err(NoSolution); + for goal in goals.goals.drain(..) { + let (has_changed, certainty, instantiate_goals) = + self.evaluate_goal(IsNormalizesToHack::No, goal)?; + self.add_goals(instantiate_goals); + if has_changed { + unchanged_certainty = None; + } + + match certainty { + Certainty::Yes => {} + Certainty::Maybe(_) => { + self.add_goal(goal); + unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty)); + } + } } - self.nested_goals = goals; - response + Ok(unchanged_certainty) } } impl<'tcx> EvalCtxt<'_, 'tcx> { - pub(super) fn probe(&mut self, f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> T) -> T { - let mut ecx = EvalCtxt { - infcx: self.infcx, - var_values: self.var_values, - predefined_opaques_in_body: self.predefined_opaques_in_body, - max_input_universe: self.max_input_universe, - search_graph: self.search_graph, - nested_goals: self.nested_goals.clone(), - tainted: self.tainted, - }; - self.infcx.probe(|_| f(&mut ecx)) - } - pub(super) fn tcx(&self) -> TyCtxt<'tcx> { self.infcx.tcx } @@ -475,10 +624,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { }) } - pub(super) fn next_region_infer(&self) -> ty::Region<'tcx> { - self.infcx.next_region_var(RegionVariableOrigin::MiscVariable(DUMMY_SP)) - } - pub(super) fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> { self.infcx.next_const_var( ty, @@ -656,24 +801,18 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { self.infcx.resolve_vars_if_possible(value) } - pub(super) fn fresh_substs_for_item(&self, def_id: DefId) -> ty::SubstsRef<'tcx> { - self.infcx.fresh_substs_for_item(DUMMY_SP, def_id) + pub(super) fn fresh_args_for_item(&self, def_id: DefId) -> ty::GenericArgsRef<'tcx> { + self.infcx.fresh_args_for_item(DUMMY_SP, def_id) } - pub(super) fn translate_substs( + pub(super) fn translate_args( &self, param_env: ty::ParamEnv<'tcx>, source_impl: DefId, - source_substs: ty::SubstsRef<'tcx>, + source_args: ty::GenericArgsRef<'tcx>, target_node: specialization_graph::Node, - ) -> ty::SubstsRef<'tcx> { - crate::traits::translate_substs( - self.infcx, - param_env, - source_impl, - source_substs, - target_node, - ) + ) -> ty::GenericArgsRef<'tcx> { + crate::traits::translate_args(self.infcx, param_env, source_impl, source_args, target_node) } pub(super) fn register_ty_outlives(&self, ty: Ty<'tcx>, lt: ty::Region<'tcx>) { @@ -706,6 +845,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { scope: Ty<'tcx>, assume: rustc_transmute::Assume, ) -> Result { + use rustc_transmute::Answer; // FIXME(transmutability): This really should be returning nested goals for `Answer::If*` match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable( ObligationCause::dummy(), @@ -713,30 +853,53 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { scope, assume, ) { - rustc_transmute::Answer::Yes => Ok(Certainty::Yes), - rustc_transmute::Answer::No(_) - | rustc_transmute::Answer::IfTransmutable { .. } - | rustc_transmute::Answer::IfAll(_) - | rustc_transmute::Answer::IfAny(_) => Err(NoSolution), + Answer::Yes => Ok(Certainty::Yes), + Answer::No(_) | Answer::If(_) => Err(NoSolution), } } - pub(super) fn can_define_opaque_ty(&mut self, def_id: LocalDefId) -> bool { + pub(super) fn can_define_opaque_ty(&self, def_id: LocalDefId) -> bool { self.infcx.opaque_type_origin(def_id).is_some() } - pub(super) fn register_opaque_ty( + pub(super) fn insert_hidden_type( &mut self, - a: ty::OpaqueTypeKey<'tcx>, - b: Ty<'tcx>, + opaque_type_key: OpaqueTypeKey<'tcx>, param_env: ty::ParamEnv<'tcx>, + hidden_ty: Ty<'tcx>, ) -> Result<(), NoSolution> { - let InferOk { value: (), obligations } = - self.infcx.register_hidden_type_in_new_solver(a, param_env, b)?; - self.add_goals(obligations.into_iter().map(|obligation| obligation.into())); + let mut obligations = Vec::new(); + self.infcx.insert_hidden_type( + opaque_type_key, + &ObligationCause::dummy(), + param_env, + hidden_ty, + true, + &mut obligations, + )?; + self.add_goals(obligations.into_iter().map(|o| o.into())); Ok(()) } + pub(super) fn add_item_bounds_for_hidden_type( + &mut self, + opaque_def_id: DefId, + opaque_args: ty::GenericArgsRef<'tcx>, + param_env: ty::ParamEnv<'tcx>, + hidden_ty: Ty<'tcx>, + ) { + let mut obligations = Vec::new(); + self.infcx.add_item_bounds_for_hidden_type( + opaque_def_id, + opaque_args, + ObligationCause::dummy(), + param_env, + hidden_ty, + &mut obligations, + ); + self.add_goals(obligations.into_iter().map(|o| o.into())); + } + // Do something for each opaque/hidden pair defined with `def_id` in the // current inference context. pub(super) fn unify_existing_opaque_tys( @@ -753,23 +916,72 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { if candidate_key.def_id != key.def_id { continue; } - values.extend(self.probe(|ecx| { - for (a, b) in std::iter::zip(candidate_key.substs, key.substs) { + values.extend(self.probe_candidate("opaque type storage").enter(|ecx| { + for (a, b) in std::iter::zip(candidate_key.args, key.args) { ecx.eq(param_env, a, b)?; } ecx.eq(param_env, candidate_ty, ty)?; - let mut obl = vec![]; - ecx.infcx.add_item_bounds_for_hidden_type( - candidate_key, - ObligationCause::dummy(), + ecx.add_item_bounds_for_hidden_type( + candidate_key.def_id.to_def_id(), + candidate_key.args, param_env, candidate_ty, - &mut obl, ); - ecx.add_goals(obl.into_iter().map(Into::into)); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) })); } values } + + // Try to evaluate a const, or return `None` if the const is too generic. + // This doesn't mean the const isn't evaluatable, though, and should be treated + // as an ambiguity rather than no-solution. + pub(super) fn try_const_eval_resolve( + &self, + param_env: ty::ParamEnv<'tcx>, + unevaluated: ty::UnevaluatedConst<'tcx>, + ty: Ty<'tcx>, + ) -> Option> { + use rustc_middle::mir::interpret::ErrorHandled; + match self.infcx.try_const_eval_resolve(param_env, unevaluated, ty, None) { + Ok(ct) => Some(ct), + Err(ErrorHandled::Reported(e)) => Some(ty::Const::new_error(self.tcx(), e.into(), ty)), + Err(ErrorHandled::TooGeneric) => None, + } + } + + /// Walk through the vtable of a principal trait ref, executing a `supertrait_visitor` + /// for every trait ref encountered (including the principal). Passes both the vtable + /// base and the (optional) vptr slot. + pub(super) fn walk_vtable( + &mut self, + principal: ty::PolyTraitRef<'tcx>, + mut supertrait_visitor: impl FnMut(&mut Self, ty::PolyTraitRef<'tcx>, usize, Option), + ) { + let tcx = self.tcx(); + let mut offset = 0; + prepare_vtable_segments::<()>(tcx, principal, |segment| { + match segment { + VtblSegment::MetadataDSA => { + offset += TyCtxt::COMMON_VTABLE_ENTRIES.len(); + } + VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { + let own_vtable_entries = count_own_vtable_entries(tcx, trait_ref); + + supertrait_visitor( + self, + trait_ref, + offset, + emit_vptr.then(|| offset + own_vtable_entries), + ); + + offset += own_vtable_entries; + if emit_vptr { + offset += 1; + } + } + } + ControlFlow::Continue(()) + }); + } } diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs index fdb209fbff871..523841951b078 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs @@ -1,26 +1,30 @@ -/// Canonicalization is used to separate some goal from its context, -/// throwing away unnecessary information in the process. -/// -/// This is necessary to cache goals containing inference variables -/// and placeholders without restricting them to the current `InferCtxt`. -/// -/// Canonicalization is fairly involved, for more details see the relevant -/// section of the [rustc-dev-guide][c]. -/// -/// [c]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html +//! Canonicalization is used to separate some goal from its context, +//! throwing away unnecessary information in the process. +//! +//! This is necessary to cache goals containing inference variables +//! and placeholders without restricting them to the current `InferCtxt`. +//! +//! Canonicalization is fairly involved, for more details see the relevant +//! section of the [rustc-dev-guide][c]. +//! +//! [c]: https://rustc-dev-guide.rust-lang.org/solve/canonicalization.html use super::{CanonicalInput, Certainty, EvalCtxt, Goal}; use crate::solve::canonicalize::{CanonicalizeMode, Canonicalizer}; -use crate::solve::{CanonicalResponse, QueryResult, Response}; +use crate::solve::{response_no_constraints_raw, CanonicalResponse, QueryResult, Response}; +use rustc_data_structures::fx::FxHashSet; use rustc_index::IndexVec; use rustc_infer::infer::canonical::query_response::make_query_region_constraints; use rustc_infer::infer::canonical::CanonicalVarValues; use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints}; -use rustc_infer::infer::InferOk; +use rustc_infer::infer::InferCtxt; use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{ - ExternalConstraints, ExternalConstraintsData, MaybeCause, PredefinedOpaquesData, QueryInput, + ExternalConstraintsData, MaybeCause, PredefinedOpaquesData, QueryInput, +}; +use rustc_middle::ty::{ + self, BoundVar, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, + TypeVisitableExt, }; -use rustc_middle::ty::{self, BoundVar, GenericArgKind, Ty}; use rustc_span::DUMMY_SP; use std::iter; use std::ops::Deref; @@ -28,10 +32,14 @@ use std::ops::Deref; impl<'tcx> EvalCtxt<'_, 'tcx> { /// Canonicalizes the goal remembering the original values /// for each bound variable. - pub(super) fn canonicalize_goal( + pub(super) fn canonicalize_goal>>( &self, - goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> (Vec>, CanonicalInput<'tcx>) { + goal: Goal<'tcx, T>, + ) -> (Vec>, CanonicalInput<'tcx, T>) { + let opaque_types = self.infcx.clone_opaque_types_for_query_response(); + let (goal, opaque_types) = + (goal, opaque_types).fold_with(&mut EagerResolver { infcx: self.infcx }); + let mut orig_values = Default::default(); let canonical_goal = Canonicalizer::canonicalize( self.infcx, @@ -40,11 +48,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { QueryInput { goal, anchor: self.infcx.defining_use_anchor, - predefined_opaques_in_body: self.tcx().mk_predefined_opaques_in_body( - PredefinedOpaquesData { - opaque_types: self.infcx.clone_opaque_types_for_query_response(), - }, - ), + predefined_opaques_in_body: self + .tcx() + .mk_predefined_opaques_in_body(PredefinedOpaquesData { opaque_types }), }, ); (orig_values, canonical_goal) @@ -70,34 +76,43 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ); let certainty = certainty.unify_with(goals_certainty); + if let Certainty::OVERFLOW = certainty { + // If we have overflow, it's probable that we're substituting a type + // into itself infinitely and any partial substitutions in the query + // response are probably not useful anyways, so just return an empty + // query response. + // + // This may prevent us from potentially useful inference, e.g. + // 2 candidates, one ambiguous and one overflow, which both + // have the same inference constraints. + // + // Changing this to retain some constraints in the future + // won't be a breaking change, so this is good enough for now. + return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow)); + } - let response = match certainty { - Certainty::Yes | Certainty::Maybe(MaybeCause::Ambiguity) => { - let external_constraints = self.compute_external_query_constraints()?; - Response { var_values: self.var_values, external_constraints, certainty } - } - Certainty::Maybe(MaybeCause::Overflow) => { - // If we have overflow, it's probable that we're substituting a type - // into itself infinitely and any partial substitutions in the query - // response are probably not useful anyways, so just return an empty - // query response. - // - // This may prevent us from potentially useful inference, e.g. - // 2 candidates, one ambiguous and one overflow, which both - // have the same inference constraints. - // - // Changing this to retain some constraints in the future - // won't be a breaking change, so this is good enough for now. - return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow)); - } - }; + let var_values = self.var_values; + let external_constraints = self.compute_external_query_constraints()?; + + let (var_values, mut external_constraints) = + (var_values, external_constraints).fold_with(&mut EagerResolver { infcx: self.infcx }); + // Remove any trivial region constraints once we've resolved regions + external_constraints + .region_constraints + .outlives + .retain(|(outlives, _)| outlives.0.as_region().map_or(true, |re| re != outlives.1)); let canonical = Canonicalizer::canonicalize( self.infcx, CanonicalizeMode::Response { max_input_universe: self.max_input_universe }, &mut Default::default(), - response, + Response { + var_values, + certainty, + external_constraints: self.tcx().mk_external_constraints(external_constraints), + }, ); + Ok(canonical) } @@ -109,38 +124,36 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { &self, maybe_cause: MaybeCause, ) -> CanonicalResponse<'tcx> { - let unconstrained_response = Response { - var_values: CanonicalVarValues { - var_values: self.tcx().mk_substs_from_iter(self.var_values.var_values.iter().map( - |arg| -> ty::GenericArg<'tcx> { - match arg.unpack() { - GenericArgKind::Lifetime(_) => self.next_region_infer().into(), - GenericArgKind::Type(_) => self.next_ty_infer().into(), - GenericArgKind::Const(ct) => self.next_const_infer(ct.ty()).into(), - } - }, - )), - }, - external_constraints: self - .tcx() - .mk_external_constraints(ExternalConstraintsData::default()), - certainty: Certainty::Maybe(maybe_cause), - }; - - Canonicalizer::canonicalize( - self.infcx, - CanonicalizeMode::Response { max_input_universe: self.max_input_universe }, - &mut Default::default(), - unconstrained_response, + response_no_constraints_raw( + self.tcx(), + self.max_input_universe, + self.variables, + Certainty::Maybe(maybe_cause), ) } + /// Computes the region constraints and *new* opaque types registered when + /// proving a goal. + /// + /// If an opaque was already constrained before proving this goal, then the + /// external constraints do not need to record that opaque, since if it is + /// further constrained by inference, that will be passed back in the var + /// values. #[instrument(level = "debug", skip(self), ret)] - fn compute_external_query_constraints(&self) -> Result, NoSolution> { + fn compute_external_query_constraints( + &self, + ) -> Result, NoSolution> { + // We only check for leaks from universes which were entered inside + // of the query. + self.infcx.leak_check(self.max_input_universe, None).map_err(|e| { + debug!(?e, "failed the leak check"); + NoSolution + })?; + // Cannot use `take_registered_region_obligations` as we may compute the response // inside of a `probe` whenever we have multiple choices inside of the solver. let region_obligations = self.infcx.inner.borrow().region_obligations().to_owned(); - let region_constraints = self.infcx.with_region_constraints(|region_constraints| { + let mut region_constraints = self.infcx.with_region_constraints(|region_constraints| { make_query_region_constraints( self.tcx(), region_obligations @@ -150,15 +163,16 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ) }); + let mut seen = FxHashSet::default(); + region_constraints.outlives.retain(|outlives| seen.insert(*outlives)); + let mut opaque_types = self.infcx.clone_opaque_types_for_query_response(); // Only return opaque type keys for newly-defined opaques opaque_types.retain(|(a, _)| { self.predefined_opaques_in_body.opaque_types.iter().all(|(pa, _)| pa != a) }); - Ok(self - .tcx() - .mk_external_constraints(ExternalConstraintsData { region_constraints, opaque_types })) + Ok(ExternalConstraintsData { region_constraints, opaque_types }) } /// After calling a canonical query, we apply the constraints returned @@ -201,7 +215,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { // created inside of the query directly instead of returning them to the // caller. let prev_universe = self.infcx.universe(); - let universes_created_in_query = response.max_universe.index() + 1; + let universes_created_in_query = response.max_universe.index(); for _ in 0..universes_created_in_query { self.infcx.create_next_universe(); } @@ -240,7 +254,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } } - let var_values = self.tcx().mk_substs_from_iter(response.variables.iter().enumerate().map( + let var_values = self.tcx().mk_args_from_iter(response.variables.iter().enumerate().map( |(index, info)| { if info.universe() != ty::UniverseIndex::ROOT { // A variable from inside a binder of the query. While ideally these shouldn't @@ -310,13 +324,71 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { param_env: ty::ParamEnv<'tcx>, opaque_types: &[(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)], ) -> Result<(), NoSolution> { - for &(a, b) in opaque_types { - let InferOk { value: (), obligations } = - self.infcx.register_hidden_type_in_new_solver(a, param_env, b)?; - // It's sound to drop these obligations, since the normalizes-to goal - // is responsible for proving these obligations. - let _ = obligations; + for &(key, ty) in opaque_types { + self.insert_hidden_type(key, param_env, ty)?; } Ok(()) } } + +/// Resolves ty, region, and const vars to their inferred values or their root vars. +struct EagerResolver<'a, 'tcx> { + infcx: &'a InferCtxt<'tcx>, +} + +impl<'tcx> TypeFolder> for EagerResolver<'_, 'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + + fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { + match *t.kind() { + ty::Infer(ty::TyVar(vid)) => match self.infcx.probe_ty_var(vid) { + Ok(t) => t.fold_with(self), + Err(_) => Ty::new_var(self.infcx.tcx, self.infcx.root_var(vid)), + }, + ty::Infer(ty::IntVar(vid)) => self.infcx.opportunistic_resolve_int_var(vid), + ty::Infer(ty::FloatVar(vid)) => self.infcx.opportunistic_resolve_float_var(vid), + _ => { + if t.has_infer() { + t.super_fold_with(self) + } else { + t + } + } + } + } + + fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { + match *r { + ty::ReVar(vid) => self + .infcx + .inner + .borrow_mut() + .unwrap_region_constraints() + .opportunistic_resolve_var(self.infcx.tcx, vid), + _ => r, + } + } + + fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { + match c.kind() { + ty::ConstKind::Infer(ty::InferConst::Var(vid)) => { + // FIXME: we need to fold the ty too, I think. + match self.infcx.probe_const_var(vid) { + Ok(c) => c.fold_with(self), + Err(_) => { + ty::Const::new_var(self.infcx.tcx, self.infcx.root_const_var(vid), c.ty()) + } + } + } + _ => { + if c.has_infer() { + c.super_fold_with(self) + } else { + c + } + } + } + } +} diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs new file mode 100644 index 0000000000000..317c43baf8f51 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs @@ -0,0 +1,68 @@ +use super::EvalCtxt; +use rustc_middle::traits::solve::{inspect, QueryResult}; +use std::marker::PhantomData; + +pub(in crate::solve) struct ProbeCtxt<'me, 'a, 'tcx, F, T> { + ecx: &'me mut EvalCtxt<'a, 'tcx>, + probe_kind: F, + _result: PhantomData, +} + +impl<'tcx, F, T> ProbeCtxt<'_, '_, 'tcx, F, T> +where + F: FnOnce(&T) -> inspect::CandidateKind<'tcx>, +{ + pub(in crate::solve) fn enter(self, f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> T) -> T { + let ProbeCtxt { ecx: outer_ecx, probe_kind, _result } = self; + + let mut nested_ecx = EvalCtxt { + infcx: outer_ecx.infcx, + variables: outer_ecx.variables, + var_values: outer_ecx.var_values, + predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body, + max_input_universe: outer_ecx.max_input_universe, + search_graph: outer_ecx.search_graph, + nested_goals: outer_ecx.nested_goals.clone(), + tainted: outer_ecx.tainted, + inspect: outer_ecx.inspect.new_goal_candidate(), + }; + let r = nested_ecx.infcx.probe(|_| f(&mut nested_ecx)); + if !outer_ecx.inspect.is_noop() { + let cand_kind = probe_kind(&r); + nested_ecx.inspect.candidate_kind(cand_kind); + outer_ecx.inspect.goal_candidate(nested_ecx.inspect); + } + r + } +} + +impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { + /// `probe_kind` is only called when proof tree building is enabled so it can be + /// as expensive as necessary to output the desired information. + pub(in crate::solve) fn probe(&mut self, probe_kind: F) -> ProbeCtxt<'_, 'a, 'tcx, F, T> + where + F: FnOnce(&T) -> inspect::CandidateKind<'tcx>, + { + ProbeCtxt { ecx: self, probe_kind, _result: PhantomData } + } + + pub(in crate::solve) fn probe_candidate( + &mut self, + name: &'static str, + ) -> ProbeCtxt< + '_, + 'a, + 'tcx, + impl FnOnce(&QueryResult<'tcx>) -> inspect::CandidateKind<'tcx>, + QueryResult<'tcx>, + > { + ProbeCtxt { + ecx: self, + probe_kind: move |result: &QueryResult<'tcx>| inspect::CandidateKind::Candidate { + name: name.to_string(), + result: *result, + }, + _result: PhantomData, + } + } +} diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs new file mode 100644 index 0000000000000..42d7a587cac4a --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs @@ -0,0 +1,398 @@ +use rustc_hir as hir; +use rustc_hir::def_id::DefId; +use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt}; +use rustc_infer::traits::{ + Obligation, PolyTraitObligation, PredicateObligation, Selection, SelectionResult, TraitEngine, +}; +use rustc_middle::traits::solve::{CanonicalInput, Certainty, Goal}; +use rustc_middle::traits::{ + BuiltinImplSource, ImplSource, ImplSourceUserDefinedData, ObligationCause, SelectionError, +}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::DUMMY_SP; + +use crate::solve::assembly::{Candidate, CandidateSource}; +use crate::solve::eval_ctxt::{EvalCtxt, GenerateProofTree}; +use crate::solve::inspect::ProofTreeBuilder; +use crate::traits::StructurallyNormalizeExt; +use crate::traits::TraitEngineExt; + +pub trait InferCtxtSelectExt<'tcx> { + fn select_in_new_trait_solver( + &self, + obligation: &PolyTraitObligation<'tcx>, + ) -> SelectionResult<'tcx, Selection<'tcx>>; +} + +impl<'tcx> InferCtxtSelectExt<'tcx> for InferCtxt<'tcx> { + fn select_in_new_trait_solver( + &self, + obligation: &PolyTraitObligation<'tcx>, + ) -> SelectionResult<'tcx, Selection<'tcx>> { + assert!(self.next_trait_solver()); + + let trait_goal = Goal::new( + self.tcx, + obligation.param_env, + self.instantiate_binder_with_placeholders(obligation.predicate), + ); + + let (result, _) = EvalCtxt::enter_root(self, GenerateProofTree::Never, |ecx| { + let goal = Goal::new(ecx.tcx(), trait_goal.param_env, trait_goal.predicate); + let (orig_values, canonical_goal) = ecx.canonicalize_goal(goal); + let mut candidates = ecx.compute_canonical_trait_candidates(canonical_goal); + + // pseudo-winnow + if candidates.len() == 0 { + return Err(SelectionError::Unimplemented); + } else if candidates.len() > 1 { + let mut i = 0; + while i < candidates.len() { + let should_drop_i = (0..candidates.len()).filter(|&j| i != j).any(|j| { + candidate_should_be_dropped_in_favor_of( + ecx.tcx(), + &candidates[i], + &candidates[j], + ) + }); + if should_drop_i { + candidates.swap_remove(i); + } else { + i += 1; + if i > 1 { + return Ok(None); + } + } + } + } + + let candidate = candidates.pop().unwrap(); + let (certainty, nested_goals) = ecx + .instantiate_and_apply_query_response( + trait_goal.param_env, + orig_values, + candidate.result, + ) + .map_err(|_| SelectionError::Unimplemented)?; + + Ok(Some((candidate, certainty, nested_goals))) + }); + + let (candidate, certainty, nested_goals) = match result { + Ok(Some((candidate, certainty, nested_goals))) => (candidate, certainty, nested_goals), + Ok(None) => return Ok(None), + Err(e) => return Err(e), + }; + + let nested_obligations: Vec<_> = nested_goals + .into_iter() + .map(|goal| { + Obligation::new(self.tcx, ObligationCause::dummy(), goal.param_env, goal.predicate) + }) + .collect(); + + let goal = self.resolve_vars_if_possible(trait_goal); + match (certainty, candidate.source) { + // Rematching the implementation will instantiate the same nested goals that + // would have caused the ambiguity, so we can still make progress here regardless. + (_, CandidateSource::Impl(def_id)) => { + rematch_impl(self, goal, def_id, nested_obligations) + } + + // If an unsize goal is ambiguous, then we can manually rematch it to make + // selection progress for coercion during HIR typeck. If it is *not* ambiguous, + // but is `BuiltinImplSource::Misc`, it may have nested `Unsize` goals, + // and we need to rematch those to detect tuple unsizing and trait upcasting. + // FIXME: This will be wrong if we have param-env or where-clause bounds + // with the unsize goal -- we may need to mark those with different impl + // sources. + (Certainty::Maybe(_), CandidateSource::BuiltinImpl(src)) + | (Certainty::Yes, CandidateSource::BuiltinImpl(src @ BuiltinImplSource::Misc)) + if self.tcx.lang_items().unsize_trait() == Some(goal.predicate.def_id()) => + { + rematch_unsize(self, goal, nested_obligations, src, certainty) + } + + // Technically some builtin impls have nested obligations, but if + // `Certainty::Yes`, then they should've all been verified and don't + // need re-checking. + (Certainty::Yes, CandidateSource::BuiltinImpl(src)) => { + Ok(Some(ImplSource::Builtin(src, nested_obligations))) + } + + // It's fine not to do anything to rematch these, since there are no + // nested obligations. + (Certainty::Yes, CandidateSource::ParamEnv(_) | CandidateSource::AliasBound) => { + Ok(Some(ImplSource::Param(nested_obligations))) + } + + (Certainty::Maybe(_), _) => Ok(None), + } + } +} + +impl<'tcx> EvalCtxt<'_, 'tcx> { + fn compute_canonical_trait_candidates( + &mut self, + canonical_input: CanonicalInput<'tcx>, + ) -> Vec> { + // This doesn't record the canonical goal on the stack during the + // candidate assembly step, but that's fine. Selection is conceptually + // outside of the solver, and if there were any cycles, we'd encounter + // the cycle anyways one step later. + EvalCtxt::enter_canonical( + self.tcx(), + self.search_graph, + canonical_input, + // FIXME: This is wrong, idk if we even want to track stuff here. + &mut ProofTreeBuilder::new_noop(), + |ecx, goal| { + let trait_goal = Goal { + param_env: goal.param_env, + predicate: goal + .predicate + .to_opt_poly_trait_pred() + .expect("we canonicalized a trait goal") + .no_bound_vars() + .expect("we instantiated all bound vars"), + }; + ecx.assemble_and_evaluate_candidates(trait_goal) + }, + ) + } +} + +fn candidate_should_be_dropped_in_favor_of<'tcx>( + tcx: TyCtxt<'tcx>, + victim: &Candidate<'tcx>, + other: &Candidate<'tcx>, +) -> bool { + match (victim.source, other.source) { + (CandidateSource::ParamEnv(victim_idx), CandidateSource::ParamEnv(other_idx)) => { + victim_idx >= other_idx + } + (_, CandidateSource::ParamEnv(_)) => true, + + // FIXME: we could prefer earlier vtable bases perhaps... + ( + CandidateSource::BuiltinImpl(BuiltinImplSource::Object { .. }), + CandidateSource::BuiltinImpl(BuiltinImplSource::Object { .. }), + ) => false, + (_, CandidateSource::BuiltinImpl(BuiltinImplSource::Object { .. })) => true, + + (CandidateSource::Impl(victim_def_id), CandidateSource::Impl(other_def_id)) => { + tcx.specializes((other_def_id, victim_def_id)) + && other.result.value.certainty == Certainty::Yes + } + + _ => false, + } +} + +fn rematch_impl<'tcx>( + infcx: &InferCtxt<'tcx>, + goal: Goal<'tcx, ty::TraitPredicate<'tcx>>, + impl_def_id: DefId, + mut nested: Vec>, +) -> SelectionResult<'tcx, Selection<'tcx>> { + let args = infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); + let impl_trait_ref = + infcx.tcx.impl_trait_ref(impl_def_id).unwrap().instantiate(infcx.tcx, args); + + nested.extend( + infcx + .at(&ObligationCause::dummy(), goal.param_env) + .eq(DefineOpaqueTypes::No, goal.predicate.trait_ref, impl_trait_ref) + .map_err(|_| SelectionError::Unimplemented)? + .into_obligations(), + ); + + nested.extend( + infcx.tcx.predicates_of(impl_def_id).instantiate(infcx.tcx, args).into_iter().map( + |(pred, _)| Obligation::new(infcx.tcx, ObligationCause::dummy(), goal.param_env, pred), + ), + ); + + Ok(Some(ImplSource::UserDefined(ImplSourceUserDefinedData { impl_def_id, args, nested }))) +} + +/// The `Unsize` trait is particularly important to coercion, so we try rematch it. +/// NOTE: This must stay in sync with `consider_builtin_unsize_candidate` in trait +/// goal assembly in the solver, both for soundness and in order to avoid ICEs. +fn rematch_unsize<'tcx>( + infcx: &InferCtxt<'tcx>, + goal: Goal<'tcx, ty::TraitPredicate<'tcx>>, + mut nested: Vec>, + source: BuiltinImplSource, + certainty: Certainty, +) -> SelectionResult<'tcx, Selection<'tcx>> { + let tcx = infcx.tcx; + let a_ty = structurally_normalize(goal.predicate.self_ty(), infcx, goal.param_env, &mut nested); + let b_ty = structurally_normalize( + goal.predicate.trait_ref.args.type_at(1), + infcx, + goal.param_env, + &mut nested, + ); + + match (a_ty.kind(), b_ty.kind()) { + // Don't try to coerce `?0` to `dyn Trait` + (ty::Infer(ty::TyVar(_)), _) | (_, ty::Infer(ty::TyVar(_))) => Ok(None), + // Stall any ambiguous upcasting goals, since we can't rematch those + (ty::Dynamic(_, _, ty::Dyn), ty::Dynamic(_, _, ty::Dyn)) => match certainty { + Certainty::Yes => Ok(Some(ImplSource::Builtin(source, nested))), + _ => Ok(None), + }, + // `T` -> `dyn Trait` upcasting + (_, &ty::Dynamic(data, region, ty::Dyn)) => { + // Check that the type implements all of the predicates of the def-id. + // (i.e. the principal, all of the associated types match, and any auto traits) + nested.extend(data.iter().map(|pred| { + Obligation::new( + infcx.tcx, + ObligationCause::dummy(), + goal.param_env, + pred.with_self_ty(tcx, a_ty), + ) + })); + // The type must be Sized to be unsized. + let sized_def_id = tcx.require_lang_item(hir::LangItem::Sized, None); + nested.push(Obligation::new( + infcx.tcx, + ObligationCause::dummy(), + goal.param_env, + ty::TraitRef::new(tcx, sized_def_id, [a_ty]), + )); + // The type must outlive the lifetime of the `dyn` we're unsizing into. + nested.push(Obligation::new( + infcx.tcx, + ObligationCause::dummy(), + goal.param_env, + ty::OutlivesPredicate(a_ty, region), + )); + + Ok(Some(ImplSource::Builtin(source, nested))) + } + // `[T; n]` -> `[T]` unsizing + (&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => { + nested.extend( + infcx + .at(&ObligationCause::dummy(), goal.param_env) + .eq(DefineOpaqueTypes::No, a_elem_ty, b_elem_ty) + .expect("expected rematch to succeed") + .into_obligations(), + ); + + Ok(Some(ImplSource::Builtin(source, nested))) + } + // Struct unsizing `Struct` -> `Struct` where `T: Unsize` + (&ty::Adt(a_def, a_args), &ty::Adt(b_def, b_args)) + if a_def.is_struct() && a_def.did() == b_def.did() => + { + let unsizing_params = tcx.unsizing_params_for_adt(a_def.did()); + // We must be unsizing some type parameters. This also implies + // that the struct has a tail field. + if unsizing_params.is_empty() { + bug!("expected rematch to succeed") + } + + let tail_field = a_def + .non_enum_variant() + .fields + .raw + .last() + .expect("expected unsized ADT to have a tail field"); + let tail_field_ty = tcx.type_of(tail_field.did); + + let a_tail_ty = tail_field_ty.instantiate(tcx, a_args); + let b_tail_ty = tail_field_ty.instantiate(tcx, b_args); + + // Substitute just the unsizing params from B into A. The type after + // this substitution must be equal to B. This is so we don't unsize + // unrelated type parameters. + let new_a_args = tcx.mk_args_from_iter( + a_args + .iter() + .enumerate() + .map(|(i, a)| if unsizing_params.contains(i as u32) { b_args[i] } else { a }), + ); + let unsized_a_ty = Ty::new_adt(tcx, a_def, new_a_args); + + nested.extend( + infcx + .at(&ObligationCause::dummy(), goal.param_env) + .eq(DefineOpaqueTypes::No, unsized_a_ty, b_ty) + .expect("expected rematch to succeed") + .into_obligations(), + ); + + // Finally, we require that `TailA: Unsize` for the tail field + // types. + nested.push(Obligation::new( + tcx, + ObligationCause::dummy(), + goal.param_env, + ty::TraitRef::new(tcx, goal.predicate.def_id(), [a_tail_ty, b_tail_ty]), + )); + + Ok(Some(ImplSource::Builtin(source, nested))) + } + // Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize` + (&ty::Tuple(a_tys), &ty::Tuple(b_tys)) + if a_tys.len() == b_tys.len() && !a_tys.is_empty() => + { + let (a_last_ty, a_rest_tys) = a_tys.split_last().unwrap(); + let b_last_ty = b_tys.last().unwrap(); + + // Substitute just the tail field of B., and require that they're equal. + let unsized_a_ty = + Ty::new_tup_from_iter(tcx, a_rest_tys.iter().chain([b_last_ty]).copied()); + nested.extend( + infcx + .at(&ObligationCause::dummy(), goal.param_env) + .eq(DefineOpaqueTypes::No, unsized_a_ty, b_ty) + .expect("expected rematch to succeed") + .into_obligations(), + ); + + // Similar to ADTs, require that we can unsize the tail. + nested.push(Obligation::new( + tcx, + ObligationCause::dummy(), + goal.param_env, + ty::TraitRef::new(tcx, goal.predicate.def_id(), [*a_last_ty, *b_last_ty]), + )); + + // We need to be able to detect tuple unsizing to require its feature gate. + assert_eq!( + source, + BuiltinImplSource::TupleUnsizing, + "compiler-errors wants to know if this can ever be triggered..." + ); + Ok(Some(ImplSource::Builtin(source, nested))) + } + _ => { + assert_ne!(certainty, Certainty::Yes); + Ok(None) + } + } +} + +fn structurally_normalize<'tcx>( + ty: Ty<'tcx>, + infcx: &InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + nested: &mut Vec>, +) -> Ty<'tcx> { + if matches!(ty.kind(), ty::Alias(..)) { + let mut engine = >::new(infcx); + let normalized_ty = infcx + .at(&ObligationCause::dummy(), param_env) + .structurally_normalize(ty, &mut *engine) + .expect("normalization shouldn't fail if we got to here"); + nested.extend(engine.pending_obligations()); + normalized_ty + } else { + ty + } +} diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 4a403196c7e05..f1d3091225c0f 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -10,6 +10,7 @@ use rustc_infer::traits::{ use rustc_middle::ty; use rustc_middle::ty::error::{ExpectedFound, TypeError}; +use super::eval_ctxt::GenerateProofTree; use super::{Certainty, InferCtxtEvalExt}; /// A trait engine using the new trait solver. @@ -25,20 +26,28 @@ use super::{Certainty, InferCtxtEvalExt}; /// here as this will have to deal with far more root goals than `evaluate_all`. pub struct FulfillmentCtxt<'tcx> { obligations: Vec>, + + /// The snapshot in which this context was created. Using the context + /// outside of this snapshot leads to subtle bugs if the snapshot + /// gets rolled back. Because of this we explicitly check that we only + /// use the context in exactly this snapshot. + usable_in_snapshot: usize, } impl<'tcx> FulfillmentCtxt<'tcx> { - pub fn new() -> FulfillmentCtxt<'tcx> { - FulfillmentCtxt { obligations: Vec::new() } + pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx> { + FulfillmentCtxt { obligations: Vec::new(), usable_in_snapshot: infcx.num_open_snapshots() } } } impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { + #[instrument(level = "debug", skip(self, infcx))] fn register_predicate_obligation( &mut self, - _infcx: &InferCtxt<'tcx>, + infcx: &InferCtxt<'tcx>, obligation: PredicateObligation<'tcx>, ) { + assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); self.obligations.push(obligation); } @@ -46,8 +55,11 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { self.obligations .drain(..) .map(|obligation| { - let code = - infcx.probe(|_| match infcx.evaluate_root_goal(obligation.clone().into()) { + let code = infcx.probe(|_| { + match infcx + .evaluate_root_goal(obligation.clone().into(), GenerateProofTree::IfEnabled) + .0 + { Ok((_, Certainty::Maybe(MaybeCause::Ambiguity), _)) => { FulfillmentErrorCode::CodeAmbiguity { overflow: false } } @@ -60,7 +72,8 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { Err(_) => { bug!("did not expect selection error when collecting ambiguity errors") } - }); + } + }); FulfillmentError { obligation: obligation.clone(), @@ -72,6 +85,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { } fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec> { + assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); let mut errors = Vec::new(); for i in 0.. { if !infcx.tcx.recursion_limit().value_within_limit(i) { @@ -81,72 +95,61 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { let mut has_changed = false; for obligation in mem::take(&mut self.obligations) { let goal = obligation.clone().into(); - let (changed, certainty, nested_goals) = match infcx.evaluate_root_goal(goal) { - Ok(result) => result, - Err(NoSolution) => { - errors.push(FulfillmentError { - obligation: obligation.clone(), - code: match goal.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(_)) => { - FulfillmentErrorCode::CodeProjectionError( - // FIXME: This could be a `Sorts` if the term is a type - MismatchedProjectionTypes { err: TypeError::Mismatch }, - ) - } - ty::PredicateKind::AliasRelate(_, _, _) => { - FulfillmentErrorCode::CodeProjectionError( - MismatchedProjectionTypes { err: TypeError::Mismatch }, - ) - } - ty::PredicateKind::Subtype(pred) => { - let (a, b) = infcx.instantiate_binder_with_placeholders( - goal.predicate.kind().rebind((pred.a, pred.b)), - ); - let expected_found = ExpectedFound::new(true, a, b); - FulfillmentErrorCode::CodeSubtypeError( - expected_found, - TypeError::Sorts(expected_found), - ) - } - ty::PredicateKind::Coerce(pred) => { - let (a, b) = infcx.instantiate_binder_with_placeholders( - goal.predicate.kind().rebind((pred.a, pred.b)), - ); - let expected_found = ExpectedFound::new(false, a, b); - FulfillmentErrorCode::CodeSubtypeError( - expected_found, - TypeError::Sorts(expected_found), - ) - } - ty::PredicateKind::ConstEquate(a, b) => { - let (a, b) = infcx.instantiate_binder_with_placeholders( - goal.predicate.kind().rebind((a, b)), - ); - let expected_found = ExpectedFound::new(true, a, b); - FulfillmentErrorCode::CodeConstEquateError( - expected_found, - TypeError::ConstMismatch(expected_found), - ) - } - ty::PredicateKind::Clause(_) - | ty::PredicateKind::WellFormed(_) - | ty::PredicateKind::ObjectSafe(_) - | ty::PredicateKind::ClosureKind(_, _, _) - | ty::PredicateKind::ConstEvaluatable(_) - | ty::PredicateKind::Ambiguous => { - FulfillmentErrorCode::CodeSelectionError( - SelectionError::Unimplemented, - ) - } - ty::PredicateKind::TypeWellFormedFromEnv(_) => { - bug!("unexpected goal: {goal:?}") - } - }, - root_obligation: obligation, - }); - continue; - } - }; + let (changed, certainty, nested_goals) = + match infcx.evaluate_root_goal(goal, GenerateProofTree::IfEnabled).0 { + Ok(result) => result, + Err(NoSolution) => { + errors.push(FulfillmentError { + obligation: obligation.clone(), + code: match goal.predicate.kind().skip_binder() { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) => { + FulfillmentErrorCode::CodeProjectionError( + // FIXME: This could be a `Sorts` if the term is a type + MismatchedProjectionTypes { err: TypeError::Mismatch }, + ) + } + ty::PredicateKind::AliasRelate(_, _, _) => { + FulfillmentErrorCode::CodeProjectionError( + MismatchedProjectionTypes { err: TypeError::Mismatch }, + ) + } + ty::PredicateKind::Subtype(pred) => { + let (a, b) = infcx.instantiate_binder_with_placeholders( + goal.predicate.kind().rebind((pred.a, pred.b)), + ); + let expected_found = ExpectedFound::new(true, a, b); + FulfillmentErrorCode::CodeSubtypeError( + expected_found, + TypeError::Sorts(expected_found), + ) + } + ty::PredicateKind::Coerce(pred) => { + let (a, b) = infcx.instantiate_binder_with_placeholders( + goal.predicate.kind().rebind((pred.a, pred.b)), + ); + let expected_found = ExpectedFound::new(false, a, b); + FulfillmentErrorCode::CodeSubtypeError( + expected_found, + TypeError::Sorts(expected_found), + ) + } + ty::PredicateKind::Clause(_) + | ty::PredicateKind::ObjectSafe(_) + | ty::PredicateKind::ClosureKind(_, _, _) + | ty::PredicateKind::Ambiguous => { + FulfillmentErrorCode::CodeSelectionError( + SelectionError::Unimplemented, + ) + } + ty::PredicateKind::ConstEquate(..) => { + bug!("unexpected goal: {goal:?}") + } + }, + root_obligation: obligation, + }); + continue; + } + }; // Push any nested goals that we get from unifying our canonical response // with our obligation onto the fulfillment context. self.obligations.extend(nested_goals.into_iter().map(|goal| { diff --git a/compiler/rustc_trait_selection/src/solve/inherent_projection.rs b/compiler/rustc_trait_selection/src/solve/inherent_projection.rs new file mode 100644 index 0000000000000..28fe59b7f6a70 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/inherent_projection.rs @@ -0,0 +1,50 @@ +//! Computes a normalizes-to (projection) goal for inherent associated types, +//! `#![feature(inherent_associated_type)]`. Since astconv already determines +//! which impl the IAT is being projected from, we just: +//! 1. instantiate substs, +//! 2. equate the self type, and +//! 3. instantiate and register where clauses. +use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; +use rustc_middle::ty; + +use super::EvalCtxt; + +impl<'tcx> EvalCtxt<'_, 'tcx> { + pub(super) fn normalize_inherent_associated_type( + &mut self, + goal: Goal<'tcx, ty::ProjectionPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + let tcx = self.tcx(); + let inherent = goal.predicate.projection_ty; + let expected = goal.predicate.term.ty().expect("inherent consts are treated separately"); + + let impl_def_id = tcx.parent(inherent.def_id); + let impl_substs = self.fresh_args_for_item(impl_def_id); + + // Equate impl header and add impl where clauses + self.eq( + goal.param_env, + inherent.self_ty(), + tcx.type_of(impl_def_id).instantiate(tcx, impl_substs), + )?; + + // Equate IAT with the RHS of the project goal + let inherent_substs = inherent.rebase_inherent_args_onto_impl(impl_substs, tcx); + self.eq( + goal.param_env, + expected, + tcx.type_of(inherent.def_id).instantiate(tcx, inherent_substs), + ) + .expect("expected goal term to be fully unconstrained"); + + // Check both where clauses on the impl and IAT + self.add_goals( + tcx.predicates_of(inherent.def_id) + .instantiate(tcx, inherent_substs) + .into_iter() + .map(|(pred, _)| goal.with(tcx, pred)), + ); + + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } +} diff --git a/compiler/rustc_trait_selection/src/solve/inspect.rs b/compiler/rustc_trait_selection/src/solve/inspect.rs new file mode 100644 index 0000000000000..cda68396321ce --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/inspect.rs @@ -0,0 +1,428 @@ +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::solve::inspect::{self, CacheHit, CandidateKind}; +use rustc_middle::traits::solve::{ + CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult, +}; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::config::DumpSolverProofTree; + +use super::eval_ctxt::UseGlobalCache; +use super::GenerateProofTree; + +#[derive(Eq, PartialEq, Debug, Hash, HashStable)] +pub struct WipGoalEvaluation<'tcx> { + pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>, + pub canonicalized_goal: Option>, + + pub evaluation_steps: Vec>, + + pub cache_hit: Option, + pub is_normalizes_to_hack: IsNormalizesToHack, + pub returned_goals: Vec>>, + + pub result: Option>, +} + +impl<'tcx> WipGoalEvaluation<'tcx> { + pub fn finalize(self) -> inspect::GoalEvaluation<'tcx> { + inspect::GoalEvaluation { + uncanonicalized_goal: self.uncanonicalized_goal, + canonicalized_goal: self.canonicalized_goal.unwrap(), + kind: match self.cache_hit { + Some(hit) => inspect::GoalEvaluationKind::CacheHit(hit), + None => inspect::GoalEvaluationKind::Uncached { + revisions: self + .evaluation_steps + .into_iter() + .map(WipGoalEvaluationStep::finalize) + .collect(), + }, + }, + is_normalizes_to_hack: self.is_normalizes_to_hack, + returned_goals: self.returned_goals, + result: self.result.unwrap(), + } + } +} + +#[derive(Eq, PartialEq, Debug, Hash, HashStable)] +pub struct WipAddedGoalsEvaluation<'tcx> { + pub evaluations: Vec>>, + pub result: Option>, +} + +impl<'tcx> WipAddedGoalsEvaluation<'tcx> { + pub fn finalize(self) -> inspect::AddedGoalsEvaluation<'tcx> { + inspect::AddedGoalsEvaluation { + evaluations: self + .evaluations + .into_iter() + .map(|evaluations| { + evaluations.into_iter().map(WipGoalEvaluation::finalize).collect() + }) + .collect(), + result: self.result.unwrap(), + } + } +} + +#[derive(Eq, PartialEq, Debug, Hash, HashStable)] +pub struct WipGoalEvaluationStep<'tcx> { + pub instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>, + + pub nested_goal_evaluations: Vec>, + pub candidates: Vec>, + + pub result: Option>, +} + +impl<'tcx> WipGoalEvaluationStep<'tcx> { + pub fn finalize(self) -> inspect::GoalEvaluationStep<'tcx> { + inspect::GoalEvaluationStep { + instantiated_goal: self.instantiated_goal, + nested_goal_evaluations: self + .nested_goal_evaluations + .into_iter() + .map(WipAddedGoalsEvaluation::finalize) + .collect(), + candidates: self.candidates.into_iter().map(WipGoalCandidate::finalize).collect(), + result: self.result.unwrap(), + } + } +} + +#[derive(Eq, PartialEq, Debug, Hash, HashStable)] +pub struct WipGoalCandidate<'tcx> { + pub nested_goal_evaluations: Vec>, + pub candidates: Vec>, + pub kind: Option>, +} + +impl<'tcx> WipGoalCandidate<'tcx> { + pub fn finalize(self) -> inspect::GoalCandidate<'tcx> { + inspect::GoalCandidate { + nested_goal_evaluations: self + .nested_goal_evaluations + .into_iter() + .map(WipAddedGoalsEvaluation::finalize) + .collect(), + candidates: self.candidates.into_iter().map(WipGoalCandidate::finalize).collect(), + kind: self.kind.unwrap(), + } + } +} + +#[derive(Debug)] +pub enum DebugSolver<'tcx> { + Root, + GoalEvaluation(WipGoalEvaluation<'tcx>), + AddedGoalsEvaluation(WipAddedGoalsEvaluation<'tcx>), + GoalEvaluationStep(WipGoalEvaluationStep<'tcx>), + GoalCandidate(WipGoalCandidate<'tcx>), +} + +impl<'tcx> From> for DebugSolver<'tcx> { + fn from(g: WipGoalEvaluation<'tcx>) -> DebugSolver<'tcx> { + DebugSolver::GoalEvaluation(g) + } +} + +impl<'tcx> From> for DebugSolver<'tcx> { + fn from(g: WipAddedGoalsEvaluation<'tcx>) -> DebugSolver<'tcx> { + DebugSolver::AddedGoalsEvaluation(g) + } +} + +impl<'tcx> From> for DebugSolver<'tcx> { + fn from(g: WipGoalEvaluationStep<'tcx>) -> DebugSolver<'tcx> { + DebugSolver::GoalEvaluationStep(g) + } +} + +impl<'tcx> From> for DebugSolver<'tcx> { + fn from(g: WipGoalCandidate<'tcx>) -> DebugSolver<'tcx> { + DebugSolver::GoalCandidate(g) + } +} + +pub struct ProofTreeBuilder<'tcx> { + state: Option>>, +} + +struct BuilderData<'tcx> { + tree: DebugSolver<'tcx>, + use_global_cache: UseGlobalCache, +} + +impl<'tcx> ProofTreeBuilder<'tcx> { + fn new( + state: impl Into>, + use_global_cache: UseGlobalCache, + ) -> ProofTreeBuilder<'tcx> { + ProofTreeBuilder { + state: Some(Box::new(BuilderData { tree: state.into(), use_global_cache })), + } + } + + fn nested(&self, state: impl Into>) -> Self { + match &self.state { + Some(prev_state) => Self { + state: Some(Box::new(BuilderData { + tree: state.into(), + use_global_cache: prev_state.use_global_cache, + })), + }, + None => Self { state: None }, + } + } + + fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> { + self.state.as_mut().map(|boxed| &mut boxed.tree) + } + + pub fn finalize(self) -> Option> { + match self.state?.tree { + DebugSolver::GoalEvaluation(wip_goal_evaluation) => { + Some(wip_goal_evaluation.finalize()) + } + root => unreachable!("unexpected proof tree builder root node: {:?}", root), + } + } + + pub fn use_global_cache(&self) -> bool { + self.state + .as_ref() + .map(|state| matches!(state.use_global_cache, UseGlobalCache::Yes)) + .unwrap_or(true) + } + + pub fn new_maybe_root( + tcx: TyCtxt<'tcx>, + generate_proof_tree: GenerateProofTree, + ) -> ProofTreeBuilder<'tcx> { + match generate_proof_tree { + GenerateProofTree::Never => ProofTreeBuilder::new_noop(), + GenerateProofTree::IfEnabled => { + let opts = &tcx.sess.opts.unstable_opts; + match opts.dump_solver_proof_tree { + DumpSolverProofTree::Always => { + let use_cache = opts.dump_solver_proof_tree_use_cache.unwrap_or(true); + ProofTreeBuilder::new_root(UseGlobalCache::from_bool(use_cache)) + } + // `OnError` is handled by reevaluating goals in error + // reporting with `GenerateProofTree::Yes`. + DumpSolverProofTree::OnError | DumpSolverProofTree::Never => { + ProofTreeBuilder::new_noop() + } + } + } + GenerateProofTree::Yes(use_cache) => ProofTreeBuilder::new_root(use_cache), + } + } + + pub fn new_root(use_global_cache: UseGlobalCache) -> ProofTreeBuilder<'tcx> { + ProofTreeBuilder::new(DebugSolver::Root, use_global_cache) + } + + pub fn new_noop() -> ProofTreeBuilder<'tcx> { + ProofTreeBuilder { state: None } + } + + pub fn is_noop(&self) -> bool { + self.state.is_none() + } + + pub fn new_goal_evaluation( + &mut self, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + is_normalizes_to_hack: IsNormalizesToHack, + ) -> ProofTreeBuilder<'tcx> { + if self.state.is_none() { + return ProofTreeBuilder { state: None }; + } + + self.nested(WipGoalEvaluation { + uncanonicalized_goal: goal, + canonicalized_goal: None, + evaluation_steps: vec![], + is_normalizes_to_hack, + cache_hit: None, + returned_goals: vec![], + result: None, + }) + } + + pub fn canonicalized_goal(&mut self, canonical_goal: CanonicalInput<'tcx>) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::GoalEvaluation(goal_evaluation) => { + assert_eq!(goal_evaluation.canonicalized_goal.replace(canonical_goal), None); + } + _ => unreachable!(), + } + } + } + + pub fn cache_hit(&mut self, cache_hit: CacheHit) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::GoalEvaluation(goal_evaluation) => { + assert_eq!(goal_evaluation.cache_hit.replace(cache_hit), None); + } + _ => unreachable!(), + }; + } + } + + pub fn returned_goals(&mut self, goals: &[Goal<'tcx, ty::Predicate<'tcx>>]) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::GoalEvaluation(evaluation) => { + assert!(evaluation.returned_goals.is_empty()); + evaluation.returned_goals.extend(goals); + } + _ => unreachable!(), + } + } + } + pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) { + if let Some(this) = self.as_mut() { + match (this, goal_evaluation.state.unwrap().tree) { + ( + DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation { + evaluations, .. + }), + DebugSolver::GoalEvaluation(goal_evaluation), + ) => evaluations.last_mut().unwrap().push(goal_evaluation), + (this @ DebugSolver::Root, goal_evaluation) => *this = goal_evaluation, + _ => unreachable!(), + } + } + } + + pub fn new_goal_evaluation_step( + &mut self, + instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>, + ) -> ProofTreeBuilder<'tcx> { + if self.state.is_none() { + return ProofTreeBuilder { state: None }; + } + + self.nested(WipGoalEvaluationStep { + instantiated_goal, + nested_goal_evaluations: vec![], + candidates: vec![], + result: None, + }) + } + pub fn goal_evaluation_step(&mut self, goal_eval_step: ProofTreeBuilder<'tcx>) { + if let Some(this) = self.as_mut() { + match (this, goal_eval_step.state.unwrap().tree) { + (DebugSolver::GoalEvaluation(goal_eval), DebugSolver::GoalEvaluationStep(step)) => { + goal_eval.evaluation_steps.push(step); + } + _ => unreachable!(), + } + } + } + + pub fn new_goal_candidate(&mut self) -> ProofTreeBuilder<'tcx> { + if self.state.is_none() { + return ProofTreeBuilder { state: None }; + } + + self.nested(WipGoalCandidate { + nested_goal_evaluations: vec![], + candidates: vec![], + kind: None, + }) + } + + pub fn candidate_kind(&mut self, candidate_kind: CandidateKind<'tcx>) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::GoalCandidate(this) => { + assert_eq!(this.kind.replace(candidate_kind), None) + } + _ => unreachable!(), + } + } + } + + pub fn goal_candidate(&mut self, candidate: ProofTreeBuilder<'tcx>) { + if let Some(this) = self.as_mut() { + match (this, candidate.state.unwrap().tree) { + ( + DebugSolver::GoalCandidate(WipGoalCandidate { candidates, .. }) + | DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { candidates, .. }), + DebugSolver::GoalCandidate(candidate), + ) => candidates.push(candidate), + _ => unreachable!(), + } + } + } + + pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> { + if self.state.is_none() { + return ProofTreeBuilder { state: None }; + } + + self.nested(WipAddedGoalsEvaluation { evaluations: vec![], result: None }) + } + + pub fn evaluate_added_goals_loop_start(&mut self) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::AddedGoalsEvaluation(this) => { + this.evaluations.push(vec![]); + } + _ => unreachable!(), + } + } + } + + pub fn eval_added_goals_result(&mut self, result: Result) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::AddedGoalsEvaluation(this) => { + assert_eq!(this.result.replace(result), None); + } + _ => unreachable!(), + } + } + } + + pub fn added_goals_evaluation(&mut self, goals_evaluation: ProofTreeBuilder<'tcx>) { + if let Some(this) = self.as_mut() { + match (this, goals_evaluation.state.unwrap().tree) { + ( + DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { + nested_goal_evaluations, + .. + }) + | DebugSolver::GoalCandidate(WipGoalCandidate { + nested_goal_evaluations, .. + }), + DebugSolver::AddedGoalsEvaluation(added_goals_evaluation), + ) => nested_goal_evaluations.push(added_goals_evaluation), + _ => unreachable!(), + } + } + } + + pub fn query_result(&mut self, result: QueryResult<'tcx>) { + if let Some(this) = self.as_mut() { + match this { + DebugSolver::GoalEvaluation(goal_evaluation) => { + assert_eq!(goal_evaluation.result.replace(result), None); + } + DebugSolver::GoalEvaluationStep(evaluation_step) => { + assert_eq!(evaluation_step.result.replace(result), None); + } + DebugSolver::Root + | DebugSolver::AddedGoalsEvaluation(_) + | DebugSolver::GoalCandidate(_) => unreachable!(), + } + } + } +} diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs index 26ace28f5fd24..75a99f799a24c 100644 --- a/compiler/rustc_trait_selection/src/solve/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -1,36 +1,50 @@ -//! The new trait solver, currently still WIP. +//! The next-generation trait solver, currently still WIP. //! -//! As a user of the trait system, you can use `TyCtxt::evaluate_goal` to -//! interact with this solver. +//! As a user of rust, you can use `-Ztrait-solver=next` or `next-coherence` +//! to enable the new trait solver always, or just within coherence, respectively. +//! +//! As a developer of rustc, you shouldn't be using the new trait +//! solver without asking the trait-system-refactor-initiative, but it can +//! be enabled with `InferCtxtBuilder::with_next_trait_solver`. This will +//! ensure that trait solving using that inference context will be routed +//! to the new trait solver. //! //! For a high-level overview of how this solver works, check out the relevant //! section of the rustc-dev-guide. //! //! FIXME(@lcnr): Write that section. If you read this before then ask me //! about it on zulip. - use rustc_hir::def_id::DefId; use rustc_infer::infer::canonical::{Canonical, CanonicalVarValues}; use rustc_infer::traits::query::NoSolution; +use rustc_middle::infer::canonical::CanonicalVarInfos; use rustc_middle::traits::solve::{ CanonicalResponse, Certainty, ExternalConstraintsData, Goal, QueryResult, Response, }; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt, UniverseIndex}; use rustc_middle::ty::{ CoercePredicate, RegionOutlivesPredicate, SubtypePredicate, TypeOutlivesPredicate, }; +mod alias_relate; mod assembly; mod canonicalize; mod eval_ctxt; mod fulfill; +mod inherent_projection; +pub mod inspect; +mod normalize; mod opaques; mod project_goals; mod search_graph; mod trait_goals; +mod weak_types; -pub use eval_ctxt::{EvalCtxt, InferCtxtEvalExt}; +pub use eval_ctxt::{ + EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt, UseGlobalCache, +}; pub use fulfill::FulfillmentCtxt; +pub(crate) use normalize::{deeply_normalize, deeply_normalize_with_skipped_universes}; #[derive(Debug, Clone, Copy)] enum SolverMode { @@ -116,10 +130,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { #[instrument(level = "debug", skip(self))] fn compute_closure_kind_goal( &mut self, - goal: Goal<'tcx, (DefId, ty::SubstsRef<'tcx>, ty::ClosureKind)>, + goal: Goal<'tcx, (DefId, ty::GenericArgsRef<'tcx>, ty::ClosureKind)>, ) -> QueryResult<'tcx> { - let (_, substs, expected_kind) = goal.predicate; - let found_kind = substs.as_closure().kind_ty().to_opt_closure_kind(); + let (_, args, expected_kind) = goal.predicate; + let found_kind = args.as_closure().kind_ty().to_opt_closure_kind(); let Some(found_kind) = found_kind else { return self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS); @@ -154,123 +168,40 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { } } - #[instrument(level = "debug", skip(self), ret)] - fn compute_alias_relate_goal( + #[instrument(level = "debug", skip(self))] + fn compute_const_evaluatable_goal( &mut self, - goal: Goal<'tcx, (ty::Term<'tcx>, ty::Term<'tcx>, ty::AliasRelationDirection)>, + Goal { param_env, predicate: ct }: Goal<'tcx, ty::Const<'tcx>>, ) -> QueryResult<'tcx> { - let tcx = self.tcx(); - // We may need to invert the alias relation direction if dealing an alias on the RHS. - #[derive(Debug)] - enum Invert { - No, - Yes, - } - let evaluate_normalizes_to = - |ecx: &mut EvalCtxt<'_, 'tcx>, alias, other, direction, invert| { - let span = tracing::span!( - tracing::Level::DEBUG, - "compute_alias_relate_goal(evaluate_normalizes_to)", - ?alias, - ?other, - ?direction, - ?invert - ); - let _enter = span.enter(); - let result = ecx.probe(|ecx| { - let other = match direction { - // This is purely an optimization. - ty::AliasRelationDirection::Equate => other, - - ty::AliasRelationDirection::Subtype => { - let fresh = ecx.next_term_infer_of_kind(other); - let (sub, sup) = match invert { - Invert::No => (fresh, other), - Invert::Yes => (other, fresh), - }; - ecx.sub(goal.param_env, sub, sup)?; - fresh - } - }; - ecx.add_goal(goal.with( - tcx, - ty::Binder::dummy(ty::ProjectionPredicate { - projection_ty: alias, - term: other, - }), - )); - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }); - debug!(?result); - result - }; - - let (lhs, rhs, direction) = goal.predicate; - - if lhs.is_infer() || rhs.is_infer() { - bug!( - "`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated" - ); - } - - match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) { - (None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"), - - // RHS is not a projection, only way this is true is if LHS normalizes-to RHS - (Some(alias_lhs), None) => { - evaluate_normalizes_to(self, alias_lhs, rhs, direction, Invert::No) - } - - // LHS is not a projection, only way this is true is if RHS normalizes-to LHS - (None, Some(alias_rhs)) => { - evaluate_normalizes_to(self, alias_rhs, lhs, direction, Invert::Yes) - } - - (Some(alias_lhs), Some(alias_rhs)) => { - debug!("both sides are aliases"); - - let mut candidates = Vec::new(); - // LHS normalizes-to RHS - candidates.extend( - evaluate_normalizes_to(self, alias_lhs, rhs, direction, Invert::No).ok(), - ); - // RHS normalizes-to RHS - candidates.extend( - evaluate_normalizes_to(self, alias_rhs, lhs, direction, Invert::Yes).ok(), - ); - // Relate via substs - let subst_relate_response = self.probe(|ecx| { - let span = tracing::span!( - tracing::Level::DEBUG, - "compute_alias_relate_goal(relate_via_substs)", - ?alias_lhs, - ?alias_rhs, - ?direction - ); - let _enter = span.enter(); - - match direction { - ty::AliasRelationDirection::Equate => { - ecx.eq(goal.param_env, alias_lhs, alias_rhs)?; - } - ty::AliasRelationDirection::Subtype => { - ecx.sub(goal.param_env, alias_lhs, alias_rhs)?; - } - } - - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }); - candidates.extend(subst_relate_response); - debug!(?candidates); - - if let Some(merged) = self.try_merge_responses(&candidates) { - Ok(merged) - } else if let Ok(subst_relate_response) = subst_relate_response { - Ok(subst_relate_response) + match ct.kind() { + ty::ConstKind::Unevaluated(uv) => { + // We never return `NoSolution` here as `try_const_eval_resolve` emits an + // error itself when failing to evaluate, so emitting an additional fulfillment + // error in that case is unnecessary noise. This may change in the future once + // evaluation failures are allowed to impact selection, e.g. generic const + // expressions in impl headers or `where`-clauses. + + // FIXME(generic_const_exprs): Implement handling for generic + // const expressions here. + if let Some(_normalized) = self.try_const_eval_resolve(param_env, uv, ct.ty()) { + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } else { - self.flounder(&candidates) + self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } } + ty::ConstKind::Infer(_) => { + self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) + } + ty::ConstKind::Placeholder(_) | ty::ConstKind::Value(_) | ty::ConstKind::Error(_) => { + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + // We can freely ICE here as: + // - `Param` gets replaced with a placeholder during canonicalization + // - `Bound` cannot exist as we don't have a binder around the self Type + // - `Expr` is part of `feature(generic_const_exprs)` and is not implemented yet + ty::ConstKind::Param(_) | ty::ConstKind::Bound(_, _) | ty::ConstKind::Expr(_) => { + bug!("unexpect const kind: {:?}", ct) + } } } @@ -342,33 +273,64 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { return Err(NoSolution); } - let Certainty::Maybe(maybe_cause) = responses.iter().fold( - Certainty::AMBIGUOUS, - |certainty, response| { + let Certainty::Maybe(maybe_cause) = + responses.iter().fold(Certainty::AMBIGUOUS, |certainty, response| { certainty.unify_with(response.value.certainty) - }, - ) else { + }) + else { bug!("expected flounder response to be ambiguous") }; Ok(self.make_ambiguous_response_no_constraints(maybe_cause)) } + + /// Normalize a type when it is structually matched on. + /// + /// For self types this is generally already handled through + /// `assemble_candidates_after_normalizing_self_ty`, so anything happening + /// in [`EvalCtxt::assemble_candidates_via_self_ty`] does not have to normalize + /// the self type. It is required when structurally matching on any other + /// arguments of a trait goal, e.g. when assembling builtin unsize candidates. + fn try_normalize_ty( + &mut self, + param_env: ty::ParamEnv<'tcx>, + mut ty: Ty<'tcx>, + ) -> Result>, NoSolution> { + for _ in 0..self.local_overflow_limit() { + let ty::Alias(_, projection_ty) = *ty.kind() else { + return Ok(Some(ty)); + }; + + let normalized_ty = self.next_ty_infer(); + let normalizes_to_goal = Goal::new( + self.tcx(), + param_env, + ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() }, + ); + self.add_goal(normalizes_to_goal); + self.try_evaluate_added_goals()?; + ty = self.resolve_vars_if_possible(normalized_ty); + } + + Ok(None) + } } -pub(super) fn response_no_constraints<'tcx>( +fn response_no_constraints_raw<'tcx>( tcx: TyCtxt<'tcx>, - goal: Canonical<'tcx, impl Sized>, + max_universe: UniverseIndex, + variables: CanonicalVarInfos<'tcx>, certainty: Certainty, -) -> QueryResult<'tcx> { - Ok(Canonical { - max_universe: goal.max_universe, - variables: goal.variables, +) -> CanonicalResponse<'tcx> { + Canonical { + max_universe, + variables, value: Response { - var_values: CanonicalVarValues::make_identity(tcx, goal.variables), + var_values: CanonicalVarValues::make_identity(tcx, variables), // FIXME: maybe we should store the "no response" version in tcx, like // we do for tcx.types and stuff. external_constraints: tcx.mk_external_constraints(ExternalConstraintsData::default()), certainty, }, - }) + } } diff --git a/compiler/rustc_trait_selection/src/solve/normalize.rs b/compiler/rustc_trait_selection/src/solve/normalize.rs new file mode 100644 index 0000000000000..872f0c8791609 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/normalize.rs @@ -0,0 +1,232 @@ +use crate::traits::error_reporting::TypeErrCtxtExt; +use crate::traits::query::evaluate_obligation::InferCtxtExt; +use crate::traits::{needs_normalization, BoundVarReplacer, PlaceholderReplacer}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_infer::infer::at::At; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::traits::TraitEngineExt; +use rustc_infer::traits::{FulfillmentError, Obligation, TraitEngine}; +use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; +use rustc_middle::traits::Reveal; +use rustc_middle::ty::{self, AliasTy, Ty, TyCtxt, UniverseIndex}; +use rustc_middle::ty::{FallibleTypeFolder, TypeSuperFoldable}; +use rustc_middle::ty::{TypeFoldable, TypeVisitableExt}; + +use super::FulfillmentCtxt; + +/// Deeply normalize all aliases in `value`. This does not handle inference and expects +/// its input to be already fully resolved. +pub(crate) fn deeply_normalize<'tcx, T: TypeFoldable>>( + at: At<'_, 'tcx>, + value: T, +) -> Result>> { + assert!(!value.has_escaping_bound_vars()); + deeply_normalize_with_skipped_universes(at, value, vec![]) +} + +/// Deeply normalize all aliases in `value`. This does not handle inference and expects +/// its input to be already fully resolved. +/// +/// Additionally takes a list of universes which represents the binders which have been +/// entered before passing `value` to the function. This is currently needed for +/// `normalize_erasing_regions`, which skips binders as it walks through a type. +pub(crate) fn deeply_normalize_with_skipped_universes<'tcx, T: TypeFoldable>>( + at: At<'_, 'tcx>, + value: T, + universes: Vec>, +) -> Result>> { + let fulfill_cx = FulfillmentCtxt::new(at.infcx); + let mut folder = NormalizationFolder { at, fulfill_cx, depth: 0, universes }; + + value.try_fold_with(&mut folder) +} + +struct NormalizationFolder<'me, 'tcx> { + at: At<'me, 'tcx>, + fulfill_cx: FulfillmentCtxt<'tcx>, + depth: usize, + universes: Vec>, +} + +impl<'tcx> NormalizationFolder<'_, 'tcx> { + fn normalize_alias_ty( + &mut self, + alias: AliasTy<'tcx>, + ) -> Result, Vec>> { + let infcx = self.at.infcx; + let tcx = infcx.tcx; + let recursion_limit = tcx.recursion_limit(); + if !recursion_limit.value_within_limit(self.depth) { + self.at.infcx.err_ctxt().report_overflow_error( + &alias.to_ty(tcx), + self.at.cause.span, + true, + |_| {}, + ); + } + + self.depth += 1; + + let new_infer_ty = infcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::NormalizeProjectionType, + span: self.at.cause.span, + }); + let obligation = Obligation::new( + tcx, + self.at.cause.clone(), + self.at.param_env, + ty::ProjectionPredicate { projection_ty: alias, term: new_infer_ty.into() }, + ); + + // Do not emit an error if normalization is known to fail but instead + // keep the projection unnormalized. This is the case for projections + // with a `T: Trait` where-clause and opaque types outside of the defining + // scope. + let result = if infcx.predicate_may_hold(&obligation) { + self.fulfill_cx.register_predicate_obligation(infcx, obligation); + let errors = self.fulfill_cx.select_all_or_error(infcx); + if !errors.is_empty() { + return Err(errors); + } + let ty = infcx.resolve_vars_if_possible(new_infer_ty); + ty.try_fold_with(self)? + } else { + alias.to_ty(tcx).try_super_fold_with(self)? + }; + + self.depth -= 1; + Ok(result) + } + + fn normalize_unevaluated_const( + &mut self, + ty: Ty<'tcx>, + uv: ty::UnevaluatedConst<'tcx>, + ) -> Result, Vec>> { + let infcx = self.at.infcx; + let tcx = infcx.tcx; + let recursion_limit = tcx.recursion_limit(); + if !recursion_limit.value_within_limit(self.depth) { + self.at.infcx.err_ctxt().report_overflow_error( + &ty::Const::new_unevaluated(tcx, uv, ty), + self.at.cause.span, + true, + |_| {}, + ); + } + + self.depth += 1; + + let new_infer_ct = infcx.next_const_var( + ty, + ConstVariableOrigin { + kind: ConstVariableOriginKind::MiscVariable, + span: self.at.cause.span, + }, + ); + let obligation = Obligation::new( + tcx, + self.at.cause.clone(), + self.at.param_env, + ty::ProjectionPredicate { + projection_ty: tcx.mk_alias_ty(uv.def, uv.args), + term: new_infer_ct.into(), + }, + ); + + let result = if infcx.predicate_may_hold(&obligation) { + self.fulfill_cx.register_predicate_obligation(infcx, obligation); + let errors = self.fulfill_cx.select_all_or_error(infcx); + if !errors.is_empty() { + return Err(errors); + } + let ct = infcx.resolve_vars_if_possible(new_infer_ct); + ct.try_fold_with(self)? + } else { + ty::Const::new_unevaluated(tcx, uv, ty).try_super_fold_with(self)? + }; + + self.depth -= 1; + Ok(result) + } +} + +impl<'tcx> FallibleTypeFolder> for NormalizationFolder<'_, 'tcx> { + type Error = Vec>; + + fn interner(&self) -> TyCtxt<'tcx> { + self.at.infcx.tcx + } + + fn try_fold_binder>>( + &mut self, + t: ty::Binder<'tcx, T>, + ) -> Result, Self::Error> { + self.universes.push(None); + let t = t.try_super_fold_with(self)?; + self.universes.pop(); + Ok(t) + } + + fn try_fold_ty(&mut self, ty: Ty<'tcx>) -> Result, Self::Error> { + let reveal = self.at.param_env.reveal(); + let infcx = self.at.infcx; + debug_assert_eq!(ty, infcx.shallow_resolve(ty)); + if !needs_normalization(&ty, reveal) { + return Ok(ty); + } + + // We don't normalize opaque types unless we have + // `Reveal::All`, even if we're in the defining scope. + let data = match *ty.kind() { + ty::Alias(kind, alias_ty) if kind != ty::Opaque || reveal == Reveal::All => alias_ty, + _ => return ty.try_super_fold_with(self), + }; + + if data.has_escaping_bound_vars() { + let (data, mapped_regions, mapped_types, mapped_consts) = + BoundVarReplacer::replace_bound_vars(infcx, &mut self.universes, data); + let result = ensure_sufficient_stack(|| self.normalize_alias_ty(data))?; + Ok(PlaceholderReplacer::replace_placeholders( + infcx, + mapped_regions, + mapped_types, + mapped_consts, + &self.universes, + result, + )) + } else { + ensure_sufficient_stack(|| self.normalize_alias_ty(data)) + } + } + + fn try_fold_const(&mut self, ct: ty::Const<'tcx>) -> Result, Self::Error> { + let reveal = self.at.param_env.reveal(); + let infcx = self.at.infcx; + debug_assert_eq!(ct, infcx.shallow_resolve(ct)); + if !needs_normalization(&ct, reveal) { + return Ok(ct); + } + + let uv = match ct.kind() { + ty::ConstKind::Unevaluated(ct) => ct, + _ => return ct.try_super_fold_with(self), + }; + + if uv.has_escaping_bound_vars() { + let (uv, mapped_regions, mapped_types, mapped_consts) = + BoundVarReplacer::replace_bound_vars(infcx, &mut self.universes, uv); + let result = ensure_sufficient_stack(|| self.normalize_unevaluated_const(ct.ty(), uv))?; + Ok(PlaceholderReplacer::replace_placeholders( + infcx, + mapped_regions, + mapped_types, + mapped_consts, + &self.universes, + result, + )) + } else { + ensure_sufficient_stack(|| self.normalize_unevaluated_const(ct.ty(), uv)) + } + } +} diff --git a/compiler/rustc_trait_selection/src/solve/opaques.rs b/compiler/rustc_trait_selection/src/solve/opaques.rs index a5de4ddee82ba..f08adc0208b57 100644 --- a/compiler/rustc_trait_selection/src/solve/opaques.rs +++ b/compiler/rustc_trait_selection/src/solve/opaques.rs @@ -1,3 +1,6 @@ +//! Computes a normalizes-to (projection) goal for opaque types. This goal +//! behaves differently depending on the param-env's reveal mode and whether +//! the opaque is in a defining scope. use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; use rustc_middle::traits::Reveal; @@ -20,16 +23,14 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { let Some(opaque_ty_def_id) = opaque_ty.def_id.as_local() else { return Err(NoSolution); }; - let opaque_ty = - ty::OpaqueTypeKey { def_id: opaque_ty_def_id, substs: opaque_ty.substs }; // FIXME: at some point we should call queries without defining // new opaque types but having the existing opaque type definitions. // This will require moving this below "Prefer opaques registered already". if !self.can_define_opaque_ty(opaque_ty_def_id) { return Err(NoSolution); } - // FIXME: This may have issues when the substs contain aliases... - match self.tcx().uses_unique_placeholders_ignoring_regions(opaque_ty.substs) { + // FIXME: This may have issues when the args contain aliases... + match self.tcx().uses_unique_placeholders_ignoring_regions(opaque_ty.args) { Err(NotUniqueParam::NotParam(param)) if param.is_non_region_infer() => { return self.evaluate_added_goals_and_make_canonical_response( Certainty::AMBIGUOUS, @@ -41,7 +42,10 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { Ok(()) => {} } // Prefer opaques registered already. - let matches = self.unify_existing_opaque_tys(goal.param_env, opaque_ty, expected); + let opaque_type_key = + ty::OpaqueTypeKey { def_id: opaque_ty_def_id, args: opaque_ty.args }; + let matches = + self.unify_existing_opaque_tys(goal.param_env, opaque_type_key, expected); if !matches.is_empty() { if let Some(response) = self.try_merge_responses(&matches) { return Ok(response); @@ -50,15 +54,29 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } } // Otherwise, define a new opaque type - self.register_opaque_ty(opaque_ty, expected, goal.param_env)?; + self.insert_hidden_type(opaque_type_key, goal.param_env, expected)?; + self.add_item_bounds_for_hidden_type( + opaque_ty.def_id, + opaque_ty.args, + goal.param_env, + expected, + ); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } (Reveal::UserFacing, SolverMode::Coherence) => { + // An impossible opaque type bound is the only way this goal will fail + // e.g. assigning `impl Copy := NotCopy` + self.add_item_bounds_for_hidden_type( + opaque_ty.def_id, + opaque_ty.args, + goal.param_env, + expected, + ); self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } (Reveal::All, _) => { // FIXME: Add an assertion that opaque type storage is empty. - let actual = tcx.type_of(opaque_ty.def_id).subst(tcx, opaque_ty.substs); + let actual = tcx.type_of(opaque_ty.def_id).instantiate(tcx, opaque_ty.args); self.eq(goal.param_env, expected, actual)?; self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs index 7d7dfa2c83776..e47e228774e07 100644 --- a/compiler/rustc_trait_selection/src/solve/project_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -1,20 +1,21 @@ -use crate::traits::specialization_graph; +use crate::traits::{check_args_compatible, specialization_graph}; use super::assembly::{self, structural_traits}; use super::EvalCtxt; -use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_hir::LangItem; use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::specialization_graph::LeafDef; use rustc_infer::traits::Reveal; +use rustc_middle::traits::solve::inspect::CandidateKind; use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, QueryResult}; +use rustc_middle::traits::BuiltinImplSource; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::ProjectionPredicate; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::ty::{ToPredicate, TypeVisitableExt}; -use rustc_span::{sym, DUMMY_SP}; +use rustc_span::{sym, ErrorGuaranteed, DUMMY_SP}; impl<'tcx> EvalCtxt<'_, 'tcx> { #[instrument(level = "debug", skip(self), ret)] @@ -22,25 +23,66 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { &mut self, goal: Goal<'tcx, ProjectionPredicate<'tcx>>, ) -> QueryResult<'tcx> { - match goal.predicate.projection_ty.kind(self.tcx()) { - ty::AliasKind::Projection => { + let def_id = goal.predicate.def_id(); + match self.tcx().def_kind(def_id) { + DefKind::AssocTy | DefKind::AssocConst => { // To only compute normalization once for each projection we only - // normalize if the expected term is an unconstrained inference variable. + // assemble normalization candidates if the expected term is an + // unconstrained inference variable. + // + // Why: For better cache hits, since if we have an unconstrained RHS then + // there are only as many cache keys as there are (canonicalized) alias + // types in each normalizes-to goal. This also weakens inference in a + // forwards-compatible way so we don't use the value of the RHS term to + // affect candidate assembly for projections. // // E.g. for `::Assoc == u32` we recursively compute the goal // `exists ::Assoc == U` and then take the resulting type for // `U` and equate it with `u32`. This means that we don't need a separate - // projection cache in the solver. + // projection cache in the solver, since we're piggybacking off of regular + // goal caching. if self.term_is_fully_unconstrained(goal) { - let candidates = self.assemble_and_evaluate_candidates(goal); - self.merge_candidates(candidates) + match self.tcx().associated_item(def_id).container { + ty::AssocItemContainer::TraitContainer => { + let candidates = self.assemble_and_evaluate_candidates(goal); + self.merge_candidates(candidates) + } + ty::AssocItemContainer::ImplContainer => { + self.normalize_inherent_associated_type(goal) + } + } } else { self.set_normalizes_to_hack_goal(goal); self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } } - ty::AliasKind::Opaque => self.normalize_opaque_type(goal), - ty::AliasKind::Inherent => bug!("IATs not supported here yet"), + DefKind::AnonConst => self.normalize_anon_const(goal), + DefKind::OpaqueTy => self.normalize_opaque_type(goal), + DefKind::TyAlias { .. } => self.normalize_weak_type(goal), + kind => bug!("unknown DefKind {} in projection goal: {goal:#?}", kind.descr(def_id)), + } + } + + #[instrument(level = "debug", skip(self), ret)] + fn normalize_anon_const( + &mut self, + goal: Goal<'tcx, ty::ProjectionPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + if let Some(normalized_const) = self.try_const_eval_resolve( + goal.param_env, + ty::UnevaluatedConst::new( + goal.predicate.projection_ty.def_id, + goal.predicate.projection_ty.args, + ), + self.tcx() + .type_of(goal.predicate.projection_ty.def_id) + .no_bound_vars() + .expect("const ty should not rely on other generics"), + ) { + self.eq(goal.param_env, normalized_const, goal.predicate.term.ct().unwrap())?; + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } else { + self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } } } @@ -65,24 +107,35 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { fn probe_and_match_goal_against_assumption( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - assumption: ty::Predicate<'tcx>, + assumption: ty::Clause<'tcx>, then: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>, ) -> QueryResult<'tcx> { - if let Some(poly_projection_pred) = assumption.to_opt_poly_projection_pred() - && poly_projection_pred.projection_def_id() == goal.predicate.def_id() - { - ecx.probe(|ecx| { - let assumption_projection_pred = - ecx.instantiate_binder_with_infer(poly_projection_pred); - ecx.eq( - goal.param_env, - goal.predicate.projection_ty, - assumption_projection_pred.projection_ty, - )?; - ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term) - .expect("expected goal term to be fully unconstrained"); - then(ecx) - }) + if let Some(projection_pred) = assumption.as_projection_clause() { + if projection_pred.projection_def_id() == goal.predicate.def_id() { + let tcx = ecx.tcx(); + ecx.probe_candidate("assumption").enter(|ecx| { + let assumption_projection_pred = + ecx.instantiate_binder_with_infer(projection_pred); + ecx.eq( + goal.param_env, + goal.predicate.projection_ty, + assumption_projection_pred.projection_ty, + )?; + ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term) + .expect("expected goal term to be fully unconstrained"); + + // Add GAT where clauses from the trait's definition + ecx.add_goals( + tcx.predicates_of(goal.predicate.def_id()) + .instantiate_own(tcx, goal.predicate.projection_ty.args) + .map(|(pred, _)| goal.with(tcx, pred)), + ); + + then(ecx) + }) + } else { + Err(NoSolution) + } } else { Err(NoSolution) } @@ -98,24 +151,31 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { let goal_trait_ref = goal.predicate.projection_ty.trait_ref(tcx); let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::ForLookup }; - if !drcx.substs_refs_may_unify(goal_trait_ref.substs, impl_trait_ref.skip_binder().substs) { + if !drcx.args_refs_may_unify(goal_trait_ref.args, impl_trait_ref.skip_binder().args) { return Err(NoSolution); } - ecx.probe(|ecx| { - let impl_substs = ecx.fresh_substs_for_item(impl_def_id); - let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); + ecx.probe(|r| CandidateKind::Candidate { name: "impl".into(), result: *r }).enter(|ecx| { + let impl_args = ecx.fresh_args_for_item(impl_def_id); + let impl_trait_ref = impl_trait_ref.instantiate(tcx, impl_args); ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?; let where_clause_bounds = tcx .predicates_of(impl_def_id) - .instantiate(tcx, impl_substs) + .instantiate(tcx, impl_args) .predicates .into_iter() .map(|pred| goal.with(tcx, pred)); ecx.add_goals(where_clause_bounds); + // Add GAT where clauses from the trait's definition + ecx.add_goals( + tcx.predicates_of(goal.predicate.def_id()) + .instantiate_own(tcx, goal.predicate.projection_ty.args) + .map(|(pred, _)| goal.with(tcx, pred)), + ); + // In case the associated item is hidden due to specialization, we have to // return ambiguity this would otherwise be incomplete, resulting in // unsoundness during coherence (#105782). @@ -124,79 +184,94 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { goal.param_env, goal_trait_ref, goal.predicate.def_id(), - impl_def_id - )? else { + impl_def_id, + )? + else { return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS); }; - if !assoc_def.item.defaultness(tcx).has_value() { - let guar = tcx.sess.delay_span_bug( - tcx.def_span(assoc_def.item.def_id), - "missing value for assoc item in impl", - ); + let error_response = |ecx: &mut EvalCtxt<'_, 'tcx>, reason| { + let guar = tcx.sess.delay_span_bug(tcx.def_span(assoc_def.item.def_id), reason); let error_term = match assoc_def.item.kind { - ty::AssocKind::Const => tcx - .const_error( - tcx.type_of(goal.predicate.def_id()) - .subst(tcx, goal.predicate.projection_ty.substs), - guar, - ) - .into(), - ty::AssocKind::Type => tcx.ty_error(guar).into(), + ty::AssocKind::Const => ty::Const::new_error( + tcx, + guar, + tcx.type_of(goal.predicate.def_id()) + .instantiate(tcx, goal.predicate.projection_ty.args), + ) + .into(), + ty::AssocKind::Type => Ty::new_error(tcx, guar).into(), ty::AssocKind::Fn => unreachable!(), }; ecx.eq(goal.param_env, goal.predicate.term, error_term) .expect("expected goal term to be fully unconstrained"); - return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes); + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }; + + if !assoc_def.item.defaultness(tcx).has_value() { + return error_response(ecx, "missing value for assoc item in impl"); } - // Getting the right substitutions here is complex, e.g. given: + // Getting the right args here is complex, e.g. given: // - a goal ` as Trait>::Assoc` // - the applicable impl `impl Trait for Vec` // - and the impl which defines `Assoc` being `impl Trait for Vec` // - // We first rebase the goal substs onto the impl, going from `[Vec, i32, u64]` + // We first rebase the goal args onto the impl, going from `[Vec, i32, u64]` // to `[u32, u64]`. // - // And then map these substs to the substs of the defining impl of `Assoc`, going + // And then map these args to the args of the defining impl of `Assoc`, going // from `[u32, u64]` to `[u32, i32, u64]`. - let impl_substs_with_gat = goal.predicate.projection_ty.substs.rebase_onto( + let impl_args_with_gat = goal.predicate.projection_ty.args.rebase_onto( tcx, goal_trait_ref.def_id, - impl_substs, + impl_args, ); - let substs = ecx.translate_substs( + let args = ecx.translate_args( goal.param_env, impl_def_id, - impl_substs_with_gat, + impl_args_with_gat, assoc_def.defining_node, ); + if !check_args_compatible(tcx, assoc_def.item, args) { + return error_response( + ecx, + "associated item has mismatched generic item arguments", + ); + } + // Finally we construct the actual value of the associated type. - let is_const = matches!(tcx.def_kind(assoc_def.item.def_id), DefKind::AssocConst); - let ty = tcx.type_of(assoc_def.item.def_id); - let term: ty::EarlyBinder> = if is_const { - let identity_substs = - ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id); - let did = assoc_def.item.def_id; - let kind = - ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs)); - ty.map_bound(|ty| tcx.mk_const(kind, ty).into()) - } else { - ty.map_bound(|ty| ty.into()) + let term = match assoc_def.item.kind { + ty::AssocKind::Type => tcx.type_of(assoc_def.item.def_id).map_bound(|ty| ty.into()), + ty::AssocKind::Const => bug!("associated const projection is not supported yet"), + ty::AssocKind::Fn => unreachable!("we should never project to a fn"), }; - ecx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs)) + ecx.eq(goal.param_env, goal.predicate.term, term.instantiate(tcx, args)) .expect("expected goal term to be fully unconstrained"); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } - fn consider_auto_trait_candidate( + /// Fail to normalize if the predicate contains an error, alternatively, we could normalize to `ty::Error` + /// and succeed. Can experiment with this to figure out what results in better error messages. + fn consider_error_guaranteed_candidate( _ecx: &mut EvalCtxt<'_, 'tcx>, + _guar: ErrorGuaranteed, + ) -> QueryResult<'tcx> { + Err(NoSolution) + } + + fn consider_auto_trait_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx> { - bug!("auto traits do not have associated types: {:?}", goal); + ecx.tcx().sess.delay_span_bug( + ecx.tcx().def_span(goal.predicate.def_id()), + "associated types not allowed on auto traits", + ); + Err(NoSolution) } fn consider_trait_alias_candidate( @@ -280,7 +355,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx> { let tcx = ecx.tcx(); - ecx.probe(|ecx| { + ecx.probe_candidate("builtin pointee").enter(|ecx| { let metadata_ty = match goal.predicate.self_ty().kind() { ty::Bool | ty::Char @@ -300,14 +375,14 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { | ty::Never | ty::Foreign(..) => tcx.types.unit, - ty::Error(e) => tcx.ty_error(*e), + ty::Error(e) => Ty::new_error(tcx, *e), ty::Str | ty::Slice(_) => tcx.types.usize, ty::Dynamic(_, _, _) => { let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None); tcx.type_of(dyn_metadata) - .subst(tcx, &[ty::GenericArg::from(goal.predicate.self_ty())]) + .instantiate(tcx, &[ty::GenericArg::from(goal.predicate.self_ty())]) } ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => { @@ -322,29 +397,21 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { tcx.types.unit } - ty::Adt(def, substs) if def.is_struct() => { - match def.non_enum_variant().fields.raw.last() { - None => tcx.types.unit, - Some(field_def) => { - let self_ty = field_def.ty(tcx, substs); - ecx.add_goal(goal.with( - tcx, - ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)), - )); - return ecx - .evaluate_added_goals_and_make_canonical_response(Certainty::Yes); - } + ty::Adt(def, args) if def.is_struct() => match def.non_enum_variant().tail_opt() { + None => tcx.types.unit, + Some(field_def) => { + let self_ty = field_def.ty(tcx, args); + ecx.add_goal(goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty))); + return ecx + .evaluate_added_goals_and_make_canonical_response(Certainty::Yes); } - } + }, ty::Adt(_, _) => tcx.types.unit, ty::Tuple(elements) => match elements.last() { None => tcx.types.unit, Some(&self_ty) => { - ecx.add_goal(goal.with( - tcx, - ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)), - )); + ecx.add_goal(goal.with(tcx, goal.predicate.with_self_ty(tcx, self_ty))); return ecx .evaluate_added_goals_and_make_canonical_response(Certainty::Yes); } @@ -370,7 +437,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx> { let self_ty = goal.predicate.self_ty(); - let ty::Generator(def_id, substs, _) = *self_ty.kind() else { + let ty::Generator(def_id, args, _) = *self_ty.kind() else { return Err(NoSolution); }; @@ -380,15 +447,15 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { return Err(NoSolution); } - let term = substs.as_generator().return_ty().into(); + let term = args.as_generator().return_ty().into(); Self::consider_implied_clause( ecx, goal, - ty::Binder::dummy(ty::ProjectionPredicate { + ty::ProjectionPredicate { projection_ty: ecx.tcx().mk_alias_ty(goal.predicate.def_id(), [self_ty]), term, - }) + } .to_predicate(tcx), // Technically, we need to check that the future type is Sized, // but that's already proven by the generator being WF. @@ -401,7 +468,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx> { let self_ty = goal.predicate.self_ty(); - let ty::Generator(def_id, substs, _) = *self_ty.kind() else { + let ty::Generator(def_id, args, _) = *self_ty.kind() else { return Err(NoSolution); }; @@ -411,7 +478,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { return Err(NoSolution); } - let generator = substs.as_generator(); + let generator = args.as_generator(); let name = tcx.associated_item(goal.predicate.def_id()).name; let term = if name == sym::Return { @@ -425,12 +492,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { Self::consider_implied_clause( ecx, goal, - ty::Binder::dummy(ty::ProjectionPredicate { + ty::ProjectionPredicate { projection_ty: ecx .tcx() .mk_alias_ty(goal.predicate.def_id(), [self_ty, generator.resume_ty()]), term, - }) + } .to_predicate(tcx), // Technically, we need to check that the future type is Sized, // but that's already proven by the generator being WF. @@ -438,17 +505,17 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ) } - fn consider_builtin_unsize_candidate( + fn consider_unsize_to_dyn_candidate( _ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx> { - bug!("`Unsize` does not have an associated type: {:?}", goal); + bug!("`Unsize` does not have an associated type: {:?}", goal) } - fn consider_builtin_dyn_upcast_candidates( + fn consider_structural_builtin_unsize_candidates( _ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - ) -> Vec> { + ) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)> { bug!("`Unsize` does not have an associated type: {:?}", goal); } @@ -497,7 +564,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { ), }; - ecx.probe(|ecx| { + ecx.probe_candidate("builtin discriminant kind").enter(|ecx| { ecx.eq(goal.param_env, goal.predicate.term, discriminant_ty.into()) .expect("expected goal term to be fully unconstrained"); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs index 56f126e91572f..be48447e27c24 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/cache.rs @@ -19,21 +19,25 @@ rustc_index::newtype_index! { #[derive(Debug, Clone)] pub(super) struct ProvisionalEntry<'tcx> { - // In case we have a coinductive cycle, this is the - // the currently least restrictive result of this goal. - pub(super) response: QueryResult<'tcx>, - // In case of a cycle, the position of deepest stack entry involved - // in that cycle. This is monotonically decreasing in the stack as all - // elements between the current stack element in the deepest stack entry - // involved have to also be involved in that cycle. - // - // We can only move entries to the global cache once we're complete done - // with the cycle. If this entry has not been involved in a cycle, - // this is just its own depth. + /// In case we have a coinductive cycle, this is the + /// the current provisional result of this goal. + /// + /// This starts out as `None` for all goals and gets to some + /// when the goal gets popped from the stack or we rerun evaluation + /// for this goal to reach a fixpoint. + pub(super) response: Option>, + /// In case of a cycle, the position of deepest stack entry involved + /// in that cycle. This is monotonically decreasing in the stack as all + /// elements between the current stack element in the deepest stack entry + /// involved have to also be involved in that cycle. + /// + /// We can only move entries to the global cache once we're complete done + /// with the cycle. If this entry has not been involved in a cycle, + /// this is just its own depth. pub(super) depth: StackDepth, - // The goal for this entry. Should always be equal to the corresponding goal - // in the lookup table. + /// The goal for this entry. Should always be equal to the corresponding goal + /// in the lookup table. pub(super) input: CanonicalInput<'tcx>, } @@ -92,7 +96,7 @@ impl<'tcx> ProvisionalCache<'tcx> { self.entries[entry_index].depth } - pub(super) fn provisional_result(&self, entry_index: EntryIndex) -> QueryResult<'tcx> { + pub(super) fn provisional_result(&self, entry_index: EntryIndex) -> Option> { self.entries[entry_index].response } } diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs index 19e4b23009a79..49ebfa4e6cbce 100644 --- a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs @@ -1,35 +1,51 @@ mod cache; -mod overflow; - -pub(super) use overflow::OverflowHandler; use self::cache::ProvisionalEntry; +use super::inspect::ProofTreeBuilder; +use super::SolverMode; use cache::ProvisionalCache; -use overflow::OverflowData; +use rustc_data_structures::fx::FxHashSet; +use rustc_index::Idx; use rustc_index::IndexVec; use rustc_middle::dep_graph::DepKind; -use rustc_middle::traits::solve::{CanonicalInput, Certainty, MaybeCause, QueryResult}; +use rustc_middle::traits::solve::inspect::CacheHit; +use rustc_middle::traits::solve::CacheData; +use rustc_middle::traits::solve::{CanonicalInput, Certainty, EvaluationCache, QueryResult}; use rustc_middle::ty::TyCtxt; -use std::{collections::hash_map::Entry, mem}; - -use super::SolverMode; +use rustc_session::Limit; +use std::collections::hash_map::Entry; rustc_index::newtype_index! { pub struct StackDepth {} } -struct StackElem<'tcx> { +#[derive(Debug)] +struct StackEntry<'tcx> { input: CanonicalInput<'tcx>, + available_depth: Limit, + // The maximum depth reached by this stack entry, only up-to date + // for the top of the stack and lazily updated for the rest. + reached_depth: StackDepth, + encountered_overflow: bool, has_been_used: bool, + + /// We put only the root goal of a coinductive cycle into the global cache. + /// + /// If we were to use that result when later trying to prove another cycle + /// participant, we can end up with unstable query results. + /// + /// See tests/ui/new-solver/coinduction/incompleteness-unstable-result.rs for + /// an example of where this is needed. + cycle_participants: FxHashSet>, } pub(super) struct SearchGraph<'tcx> { mode: SolverMode, + local_overflow_limit: usize, /// The stack of goals currently being computed. /// /// An element is *deeper* in the stack if its index is *lower*. - stack: IndexVec>, - overflow_data: OverflowData, + stack: IndexVec>, provisional_cache: ProvisionalCache<'tcx>, } @@ -37,8 +53,8 @@ impl<'tcx> SearchGraph<'tcx> { pub(super) fn new(tcx: TyCtxt<'tcx>, mode: SolverMode) -> SearchGraph<'tcx> { Self { mode, + local_overflow_limit: tcx.recursion_limit().0.ilog2() as usize, stack: Default::default(), - overflow_data: OverflowData::new(tcx), provisional_cache: ProvisionalCache::empty(), } } @@ -47,19 +63,42 @@ impl<'tcx> SearchGraph<'tcx> { self.mode } - /// We do not use the global cache during coherence. + pub(super) fn local_overflow_limit(&self) -> usize { + self.local_overflow_limit + } + + /// Update the stack and reached depths on cache hits. + #[instrument(level = "debug", skip(self))] + fn on_cache_hit(&mut self, additional_depth: usize, encountered_overflow: bool) { + let reached_depth = self.stack.next_index().plus(additional_depth); + if let Some(last) = self.stack.raw.last_mut() { + last.reached_depth = last.reached_depth.max(reached_depth); + last.encountered_overflow |= encountered_overflow; + } + } + + /// Pops the highest goal from the stack, lazily updating the + /// the next goal in the stack. /// + /// Directly popping from the stack instead of using this method + /// would cause us to not track overflow and recursion depth correctly. + fn pop_stack(&mut self) -> StackEntry<'tcx> { + let elem = self.stack.pop().unwrap(); + if let Some(last) = self.stack.raw.last_mut() { + last.reached_depth = last.reached_depth.max(elem.reached_depth); + last.encountered_overflow |= elem.encountered_overflow; + } + elem + } + /// The trait solver behavior is different for coherence - /// so we would have to add the solver mode to the cache key. - /// This is probably not worth it as trait solving during - /// coherence tends to already be incredibly fast. - /// - /// We could add another global cache for coherence instead, - /// but that's effort so let's only do it if necessary. - pub(super) fn should_use_global_cache(&self) -> bool { + /// so we use a separate cache. Alternatively we could use + /// a single cache and share it between coherence and ordinary + /// trait solving. + pub(super) fn global_cache(&self, tcx: TyCtxt<'tcx>) -> &'tcx EvaluationCache<'tcx> { match self.mode { - SolverMode::Normal => true, - SolverMode::Coherence => false, + SolverMode::Normal => &tcx.new_solver_evaluation_cache, + SolverMode::Coherence => &tcx.new_solver_coherence_evaluation_cache, } } @@ -85,202 +124,257 @@ impl<'tcx> SearchGraph<'tcx> { } } - /// Tries putting the new goal on the stack, returning an error if it is already cached. + /// Fetches whether the current goal encountered overflow. + /// + /// This should only be used for the check in `evaluate_goal`. + pub(super) fn encountered_overflow(&self) -> bool { + if let Some(last) = self.stack.raw.last() { last.encountered_overflow } else { false } + } + + /// Resets `encountered_overflow` of the current goal. + /// + /// This should only be used for the check in `evaluate_goal`. + pub(super) fn reset_encountered_overflow(&mut self, encountered_overflow: bool) -> bool { + if let Some(last) = self.stack.raw.last_mut() { + let prev = last.encountered_overflow; + last.encountered_overflow = encountered_overflow; + prev + } else { + false + } + } + + /// Returns the remaining depth allowed for nested goals. + /// + /// This is generally simply one less than the current depth. + /// However, if we encountered overflow, we significantly reduce + /// the remaining depth of all nested goals to prevent hangs + /// in case there is exponential blowup. + fn allowed_depth_for_nested( + tcx: TyCtxt<'tcx>, + stack: &IndexVec>, + ) -> Option { + if let Some(last) = stack.raw.last() { + if last.available_depth.0 == 0 { + return None; + } + + Some(if last.encountered_overflow { + Limit(last.available_depth.0 / 4) + } else { + Limit(last.available_depth.0 - 1) + }) + } else { + Some(tcx.recursion_limit()) + } + } + + /// Probably the most involved method of the whole solver. /// - /// This correctly updates the provisional cache if there is a cycle. - #[instrument(level = "debug", skip(self, tcx), ret)] - fn try_push_stack( + /// Given some goal which is proven via the `prove_goal` closure, this + /// handles caching, overflow, and coinductive cycles. + pub(super) fn with_new_goal( &mut self, tcx: TyCtxt<'tcx>, input: CanonicalInput<'tcx>, - ) -> Result<(), QueryResult<'tcx>> { - // Look at the provisional cache to check for cycles. + inspect: &mut ProofTreeBuilder<'tcx>, + mut prove_goal: impl FnMut(&mut Self, &mut ProofTreeBuilder<'tcx>) -> QueryResult<'tcx>, + ) -> QueryResult<'tcx> { + // Check for overflow. + let Some(available_depth) = Self::allowed_depth_for_nested(tcx, &self.stack) else { + if let Some(last) = self.stack.raw.last_mut() { + last.encountered_overflow = true; + } + return Self::response_no_constraints(tcx, input, Certainty::OVERFLOW); + }; + + // Try to fetch the goal from the global cache. + if inspect.use_global_cache() { + if let Some(CacheData { result, reached_depth, encountered_overflow }) = + self.global_cache(tcx).get( + tcx, + input, + |cycle_participants| { + self.stack.iter().any(|entry| cycle_participants.contains(&entry.input)) + }, + available_depth, + ) + { + self.on_cache_hit(reached_depth, encountered_overflow); + return result; + } + } + + // Look at the provisional cache to detect cycles. let cache = &mut self.provisional_cache; match cache.lookup_table.entry(input) { - // No entry, simply push this goal on the stack after dealing with overflow. + // No entry, we push this goal on the stack and try to prove it. Entry::Vacant(v) => { - if self.overflow_data.has_overflow(self.stack.len()) { - return Err(self.deal_with_overflow(tcx, input)); - } - - let depth = self.stack.push(StackElem { input, has_been_used: false }); - let response = super::response_no_constraints(tcx, input, Certainty::Yes); - let entry_index = cache.entries.push(ProvisionalEntry { response, depth, input }); + let depth = self.stack.next_index(); + let entry = StackEntry { + input, + available_depth, + reached_depth: depth, + encountered_overflow: false, + has_been_used: false, + cycle_participants: Default::default(), + }; + assert_eq!(self.stack.push(entry), depth); + let entry_index = + cache.entries.push(ProvisionalEntry { response: None, depth, input }); v.insert(entry_index); - Ok(()) } // We have a nested goal which relies on a goal `root` deeper in the stack. // - // We first store that we may have to rerun `evaluate_goal` for `root` in case the - // provisional response is not equal to the final response. We also update the depth - // of all goals which recursively depend on our current goal to depend on `root` + // We first store that we may have to reprove `root` in case the provisional + // response is not equal to the final response. We also update the depth of all + // goals which recursively depend on our current goal to depend on `root` // instead. // // Finally we can return either the provisional response for that goal if we have a // coinductive cycle or an ambiguous result if the cycle is inductive. Entry::Occupied(entry_index) => { - let entry_index = *entry_index.get(); + inspect.cache_hit(CacheHit::Provisional); + let entry_index = *entry_index.get(); let stack_depth = cache.depth(entry_index); debug!("encountered cycle with depth {stack_depth:?}"); cache.add_dependency_of_leaf_on(entry_index); + let mut iter = self.stack.iter_mut(); + let root = iter.nth(stack_depth.as_usize()).unwrap(); + for e in iter { + root.cycle_participants.insert(e.input); + } + // If we're in a cycle, we have to retry proving the current goal + // until we reach a fixpoint. self.stack[stack_depth].has_been_used = true; - // NOTE: The goals on the stack aren't the only goals involved in this cycle. - // We can also depend on goals which aren't part of the stack but coinductively - // depend on the stack themselves. We already checked whether all the goals - // between these goals and their root on the stack. This means that as long as - // each goal in a cycle is checked for coinductivity by itself, simply checking - // the stack is enough. - if self.stack.raw[stack_depth.index()..] - .iter() - .all(|g| g.input.value.goal.predicate.is_coinductive(tcx)) - { - Err(cache.provisional_result(entry_index)) + return if let Some(result) = cache.provisional_result(entry_index) { + result } else { - Err(super::response_no_constraints( - tcx, - input, - Certainty::Maybe(MaybeCause::Overflow), - )) - } - } - } - } - - /// We cannot simply store the result of [super::EvalCtxt::compute_goal] as we have to deal with - /// coinductive cycles. - /// - /// When we encounter a coinductive cycle, we have to prove the final result of that cycle - /// while we are still computing that result. Because of this we continuously recompute the - /// cycle until the result of the previous iteration is equal to the final result, at which - /// point we are done. - /// - /// This function returns `true` if we were able to finalize the goal and `false` if it has - /// updated the provisional cache and we have to recompute the current goal. - /// - /// FIXME: Refer to the rustc-dev-guide entry once it exists. - #[instrument(level = "debug", skip(self, actual_input), ret)] - fn try_finalize_goal( - &mut self, - actual_input: CanonicalInput<'tcx>, - response: QueryResult<'tcx>, - ) -> bool { - let stack_elem = self.stack.pop().unwrap(); - let StackElem { input, has_been_used } = stack_elem; - assert_eq!(input, actual_input); + // If we don't have a provisional result yet, the goal has to + // still be on the stack. + let mut goal_on_stack = false; + let mut is_coinductive = true; + for entry in self.stack.raw[stack_depth.index()..] + .iter() + .skip_while(|entry| entry.input != input) + { + goal_on_stack = true; + is_coinductive &= entry.input.value.goal.predicate.is_coinductive(tcx); + } + debug_assert!(goal_on_stack); - let cache = &mut self.provisional_cache; - let provisional_entry_index = *cache.lookup_table.get(&input).unwrap(); - let provisional_entry = &mut cache.entries[provisional_entry_index]; - // We eagerly update the response in the cache here. If we have to reevaluate - // this goal we use the new response when hitting a cycle, and we definitely - // want to access the final response whenever we look at the cache. - let prev_response = mem::replace(&mut provisional_entry.response, response); - - // Was the current goal the root of a cycle and was the provisional response - // different from the final one. - if has_been_used && prev_response != response { - // If so, remove all entries whose result depends on this goal - // from the provisional cache... - // - // That's not completely correct, as a nested goal can also - // depend on a goal which is lower in the stack so it doesn't - // actually depend on the current goal. This should be fairly - // rare and is hopefully not relevant for performance. - #[allow(rustc::potential_query_instability)] - cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index); - cache.entries.truncate(provisional_entry_index.index() + 1); - - // ...and finally push our goal back on the stack and reevaluate it. - self.stack.push(StackElem { input, has_been_used: false }); - false - } else { - true - } - } - - pub(super) fn with_new_goal( - &mut self, - tcx: TyCtxt<'tcx>, - canonical_input: CanonicalInput<'tcx>, - mut loop_body: impl FnMut(&mut Self) -> QueryResult<'tcx>, - ) -> QueryResult<'tcx> { - if self.should_use_global_cache() { - if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_input, tcx) { - debug!(?canonical_input, ?result, "cache hit"); - return result; + if is_coinductive { + Self::response_no_constraints(tcx, input, Certainty::Yes) + } else { + Self::response_no_constraints(tcx, input, Certainty::OVERFLOW) + } + }; } } - match self.try_push_stack(tcx, canonical_input) { - Ok(()) => {} - // Our goal is already on the stack, eager return. - Err(response) => return response, - } - // This is for global caching, so we properly track query dependencies. - // Everything that affects the `Result` should be performed within this + // Everything that affects the `result` should be performed within this // `with_anon_task` closure. - let (result, dep_node) = tcx.dep_graph.with_anon_task(tcx, DepKind::TraitSelect, || { - self.repeat_while_none( - |this| { - let result = this.deal_with_overflow(tcx, canonical_input); - let _ = this.stack.pop().unwrap(); - result - }, - |this| { - let result = loop_body(this); - this.try_finalize_goal(canonical_input, result).then(|| result) - }, - ) - }); + let ((final_entry, result), dep_node) = + tcx.dep_graph.with_anon_task(tcx, DepKind::TraitSelect, || { + // When we encounter a coinductive cycle, we have to fetch the + // result of that cycle while we are still computing it. Because + // of this we continuously recompute the cycle until the result + // of the previous iteration is equal to the final result, at which + // point we are done. + for _ in 0..self.local_overflow_limit() { + let response = prove_goal(self, inspect); + + // Check whether the current goal is the root of a cycle and whether + // we have to rerun because its provisional result differed from the + // final result. + // + // Also update the response for this goal stored in the provisional + // cache. + let stack_entry = self.pop_stack(); + debug_assert_eq!(stack_entry.input, input); + let cache = &mut self.provisional_cache; + let provisional_entry_index = + *cache.lookup_table.get(&stack_entry.input).unwrap(); + let provisional_entry = &mut cache.entries[provisional_entry_index]; + if stack_entry.has_been_used + && provisional_entry.response.map_or(true, |r| r != response) + { + // If so, update the provisional result for this goal and remove + // all entries whose result depends on this goal from the provisional + // cache... + // + // That's not completely correct, as a nested goal can also only + // depend on a goal which is lower in the stack so it doesn't + // actually depend on the current goal. This should be fairly + // rare and is hopefully not relevant for performance. + provisional_entry.response = Some(response); + #[allow(rustc::potential_query_instability)] + cache.lookup_table.retain(|_key, index| *index <= provisional_entry_index); + cache.entries.truncate(provisional_entry_index.index() + 1); + // ...and finally push our goal back on the stack and reevaluate it. + self.stack.push(StackEntry { has_been_used: false, ..stack_entry }); + } else { + return (stack_entry, response); + } + } + + debug!("canonical cycle overflow"); + let current_entry = self.pop_stack(); + let result = Self::response_no_constraints(tcx, input, Certainty::OVERFLOW); + (current_entry, result) + }); + + // We're now done with this goal. In case this goal is involved in a larger cycle + // do not remove it from the provisional cache and update its provisional result. + // We only add the root of cycles to the global cache. + // + // It is not possible for any nested goal to depend on something deeper on the + // stack, as this would have also updated the depth of the current goal. let cache = &mut self.provisional_cache; - let provisional_entry_index = *cache.lookup_table.get(&canonical_input).unwrap(); + let provisional_entry_index = *cache.lookup_table.get(&input).unwrap(); let provisional_entry = &mut cache.entries[provisional_entry_index]; let depth = provisional_entry.depth; - - // If not, we're done with this goal. - // - // Check whether that this goal doesn't depend on a goal deeper on the stack - // and if so, move it to the global cache. - // - // Note that if any nested goal were to depend on something deeper on the stack, - // this would have also updated the depth of the current goal. if depth == self.stack.next_index() { - // If the current goal is the head of a cycle, we drop all other - // cycle participants without moving them to the global cache. - let other_cycle_participants = provisional_entry_index.index() + 1; - for (i, entry) in cache.entries.drain_enumerated(other_cycle_participants..) { + for (i, entry) in cache.entries.drain_enumerated(provisional_entry_index.index()..) { let actual_index = cache.lookup_table.remove(&entry.input); debug_assert_eq!(Some(i), actual_index); debug_assert!(entry.depth == depth); } - let current_goal = cache.entries.pop().unwrap(); - let actual_index = cache.lookup_table.remove(¤t_goal.input); - debug_assert_eq!(Some(provisional_entry_index), actual_index); - debug_assert!(current_goal.depth == depth); - - // We move the root goal to the global cache if we either did not hit an overflow or if it's - // the root goal as that will now always hit the same overflow limit. + // When encountering a cycle, both inductive and coinductive, we only + // move the root into the global cache. We also store all other cycle + // participants involved. // - // NOTE: We cannot move any non-root goals to the global cache. When replaying the root goal's - // dependencies, our non-root goal may no longer appear as child of the root goal. - // - // See https://github.com/rust-lang/rust/pull/108071 for some additional context. - let can_cache = !self.overflow_data.did_overflow() || self.stack.is_empty(); - if self.should_use_global_cache() && can_cache { - tcx.new_solver_evaluation_cache.insert( - current_goal.input, - dep_node, - current_goal.response, - ); - } + // We disable the global cache entry of the root goal if a cycle + // participant is on the stack. This is necessary to prevent unstable + // results. See the comment of `StackEntry::cycle_participants` for + // more details. + let reached_depth = final_entry.reached_depth.as_usize() - self.stack.len(); + self.global_cache(tcx).insert( + input, + reached_depth, + final_entry.encountered_overflow, + final_entry.cycle_participants, + dep_node, + result, + ) + } else { + provisional_entry.response = Some(result); } result } + + fn response_no_constraints( + tcx: TyCtxt<'tcx>, + goal: CanonicalInput<'tcx>, + certainty: Certainty, + ) -> QueryResult<'tcx> { + Ok(super::response_no_constraints_raw(tcx, goal.max_universe, goal.variables, certainty)) + } } diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/overflow.rs b/compiler/rustc_trait_selection/src/solve/search_graph/overflow.rs deleted file mode 100644 index e0a2e0c5cc29b..0000000000000 --- a/compiler/rustc_trait_selection/src/solve/search_graph/overflow.rs +++ /dev/null @@ -1,120 +0,0 @@ -use rustc_infer::infer::canonical::Canonical; -use rustc_infer::traits::query::NoSolution; -use rustc_middle::traits::solve::{Certainty, MaybeCause, QueryResult}; -use rustc_middle::ty::TyCtxt; -use rustc_session::Limit; - -use super::SearchGraph; -use crate::solve::{response_no_constraints, EvalCtxt}; - -/// When detecting a solver overflow, we return ambiguity. Overflow can be -/// *hidden* by either a fatal error in an **AND** or a trivial success in an **OR**. -/// -/// This is in issue in case of exponential blowup, e.g. if each goal on the stack -/// has multiple nested (overflowing) candidates. To deal with this, we reduce the limit -/// used by the solver when hitting the default limit for the first time. -/// -/// FIXME: Get tests where always using the `default_limit` results in a hang and refer -/// to them here. We can also improve the overflow strategy if necessary. -pub(super) struct OverflowData { - default_limit: Limit, - current_limit: Limit, - /// When proving an **AND** we have to repeatedly iterate over the yet unproven goals. - /// - /// Because of this each iteration also increases the depth in addition to the stack - /// depth. - additional_depth: usize, -} - -impl OverflowData { - pub(super) fn new(tcx: TyCtxt<'_>) -> OverflowData { - let default_limit = tcx.recursion_limit(); - OverflowData { default_limit, current_limit: default_limit, additional_depth: 0 } - } - - #[inline] - pub(super) fn did_overflow(&self) -> bool { - self.default_limit.0 != self.current_limit.0 - } - - #[inline] - pub(super) fn has_overflow(&self, depth: usize) -> bool { - !self.current_limit.value_within_limit(depth + self.additional_depth) - } - - /// Updating the current limit when hitting overflow. - fn deal_with_overflow(&mut self) { - // When first hitting overflow we reduce the overflow limit - // for all future goals to prevent hangs if there's an exponential - // blowup. - self.current_limit.0 = self.default_limit.0 / 8; - } -} - -pub(in crate::solve) trait OverflowHandler<'tcx> { - fn search_graph(&mut self) -> &mut SearchGraph<'tcx>; - - fn repeat_while_none( - &mut self, - on_overflow: impl FnOnce(&mut Self) -> Result, - mut loop_body: impl FnMut(&mut Self) -> Option>, - ) -> Result { - let start_depth = self.search_graph().overflow_data.additional_depth; - let depth = self.search_graph().stack.len(); - while !self.search_graph().overflow_data.has_overflow(depth) { - if let Some(result) = loop_body(self) { - self.search_graph().overflow_data.additional_depth = start_depth; - return result; - } - - self.search_graph().overflow_data.additional_depth += 1; - } - self.search_graph().overflow_data.additional_depth = start_depth; - self.search_graph().overflow_data.deal_with_overflow(); - on_overflow(self) - } - - // Increment the `additional_depth` by one and evaluate `body`, or `on_overflow` - // if the depth is overflown. - fn with_incremented_depth( - &mut self, - on_overflow: impl FnOnce(&mut Self) -> T, - body: impl FnOnce(&mut Self) -> T, - ) -> T { - let depth = self.search_graph().stack.len(); - self.search_graph().overflow_data.additional_depth += 1; - - let result = if self.search_graph().overflow_data.has_overflow(depth) { - self.search_graph().overflow_data.deal_with_overflow(); - on_overflow(self) - } else { - body(self) - }; - - self.search_graph().overflow_data.additional_depth -= 1; - result - } -} - -impl<'tcx> OverflowHandler<'tcx> for EvalCtxt<'_, 'tcx> { - fn search_graph(&mut self) -> &mut SearchGraph<'tcx> { - &mut self.search_graph - } -} - -impl<'tcx> OverflowHandler<'tcx> for SearchGraph<'tcx> { - fn search_graph(&mut self) -> &mut SearchGraph<'tcx> { - self - } -} - -impl<'tcx> SearchGraph<'tcx> { - pub fn deal_with_overflow( - &mut self, - tcx: TyCtxt<'tcx>, - goal: Canonical<'tcx, impl Sized>, - ) -> QueryResult<'tcx> { - self.overflow_data.deal_with_overflow(); - response_no_constraints(tcx, goal, Certainty::Maybe(MaybeCause::Overflow)) - } -} diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index f722f281314e6..8685f3100a822 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -5,12 +5,13 @@ use super::{EvalCtxt, SolverMode}; use rustc_hir::def_id::DefId; use rustc_hir::{LangItem, Movability}; use rustc_infer::traits::query::NoSolution; -use rustc_infer::traits::util::supertraits; +use rustc_middle::traits::solve::inspect::CandidateKind; use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, QueryResult}; +use rustc_middle::traits::{BuiltinImplSource, Reveal}; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams, TreatProjections}; use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt}; use rustc_middle::ty::{TraitPredicate, TypeVisitableExt}; -use rustc_span::DUMMY_SP; +use rustc_span::{ErrorGuaranteed, DUMMY_SP}; impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { fn self_ty(self) -> Ty<'tcx> { @@ -38,10 +39,9 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::ForLookup }; - if !drcx.substs_refs_may_unify( - goal.predicate.trait_ref.substs, - impl_trait_ref.skip_binder().substs, - ) { + if !drcx + .args_refs_may_unify(goal.predicate.trait_ref.args, impl_trait_ref.skip_binder().args) + { return Err(NoSolution); } @@ -61,14 +61,14 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { }, }; - ecx.probe(|ecx| { - let impl_substs = ecx.fresh_substs_for_item(impl_def_id); - let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); + ecx.probe_candidate("impl").enter(|ecx| { + let impl_args = ecx.fresh_args_for_item(impl_def_id); + let impl_trait_ref = impl_trait_ref.instantiate(tcx, impl_args); ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?; let where_clause_bounds = tcx .predicates_of(impl_def_id) - .instantiate(tcx, impl_substs) + .instantiate(tcx, impl_args) .predicates .into_iter() .map(|pred| goal.with(tcx, pred)); @@ -78,27 +78,36 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { }) } + fn consider_error_guaranteed_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + _guar: ErrorGuaranteed, + ) -> QueryResult<'tcx> { + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + fn probe_and_match_goal_against_assumption( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - assumption: ty::Predicate<'tcx>, + assumption: ty::Clause<'tcx>, then: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>, ) -> QueryResult<'tcx> { - if let Some(poly_trait_pred) = assumption.to_opt_poly_trait_pred() - && poly_trait_pred.def_id() == goal.predicate.def_id() - && poly_trait_pred.polarity() == goal.predicate.polarity - { - // FIXME: Constness - ecx.probe(|ecx| { - let assumption_trait_pred = - ecx.instantiate_binder_with_infer(poly_trait_pred); - ecx.eq( - goal.param_env, - goal.predicate.trait_ref, - assumption_trait_pred.trait_ref, - )?; - then(ecx) - }) + if let Some(trait_clause) = assumption.as_trait_clause() { + if trait_clause.def_id() == goal.predicate.def_id() + && trait_clause.polarity() == goal.predicate.polarity + { + // FIXME: Constness + ecx.probe_candidate("assumption").enter(|ecx| { + let assumption_trait_pred = ecx.instantiate_binder_with_infer(trait_clause); + ecx.eq( + goal.param_env, + goal.predicate.trait_ref, + assumption_trait_pred.trait_ref, + )?; + then(ecx) + }) + } else { + Err(NoSolution) + } } else { Err(NoSolution) } @@ -116,6 +125,32 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { return result; } + // Don't call `type_of` on a local TAIT that's in the defining scope, + // since that may require calling `typeck` on the same item we're + // currently type checking, which will result in a fatal cycle that + // ideally we want to avoid, since we can make progress on this goal + // via an alias bound or a locally-inferred hidden type instead. + // + // Also, don't call `type_of` on a TAIT in `Reveal::All` mode, since + // we already normalize the self type in + // `assemble_candidates_after_normalizing_self_ty`, and we'd + // just be registering an identical candidate here. + // + // Returning `Err(NoSolution)` here is ok in `SolverMode::Coherence` + // since we'll always be registering an ambiguous candidate in + // `assemble_candidates_after_normalizing_self_ty` due to normalizing + // the TAIT. + if let ty::Alias(ty::Opaque, opaque_ty) = goal.predicate.self_ty().kind() { + if matches!(goal.param_env.reveal(), Reveal::All) + || opaque_ty + .def_id + .as_local() + .is_some_and(|def_id| ecx.can_define_opaque_ty(def_id)) + { + return Err(NoSolution); + } + } + ecx.probe_and_evaluate_goal_for_constituent_tys( goal, structural_traits::instantiate_constituent_tys_for_auto_trait, @@ -132,10 +167,10 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { let tcx = ecx.tcx(); - ecx.probe(|ecx| { + ecx.probe_candidate("trait alias").enter(|ecx| { let nested_obligations = tcx .predicates_of(goal.predicate.def_id()) - .instantiate(tcx, goal.predicate.trait_ref.substs); + .instantiate(tcx, goal.predicate.trait_ref.args); ecx.add_goals(nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p))); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) @@ -308,7 +343,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { } let self_ty = goal.predicate.self_ty(); - let ty::Generator(def_id, substs, _) = *self_ty.kind() else { + let ty::Generator(def_id, args, _) = *self_ty.kind() else { return Err(NoSolution); }; @@ -318,7 +353,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { return Err(NoSolution); } - let generator = substs.as_generator(); + let generator = args.as_generator(); Self::consider_implied_clause( ecx, goal, @@ -330,7 +365,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { ) } - fn consider_builtin_unsize_candidate( + fn consider_builtin_discriminant_kind_candidate( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, ) -> QueryResult<'tcx> { @@ -338,136 +373,205 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { return Err(NoSolution); } - let tcx = ecx.tcx(); - let a_ty = goal.predicate.self_ty(); - let b_ty = goal.predicate.trait_ref.substs.type_at(1); - if b_ty.is_ty_var() { - return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS); + // `DiscriminantKind` is automatically implemented for every type. + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + + fn consider_builtin_destruct_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> QueryResult<'tcx> { + if goal.predicate.polarity != ty::ImplPolarity::Positive { + return Err(NoSolution); } - ecx.probe(|ecx| { - match (a_ty.kind(), b_ty.kind()) { - // Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b` - (&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => { - // Dyn upcasting is handled separately, since due to upcasting, - // when there are two supertraits that differ by substs, we - // may return more than one query response. - Err(NoSolution) - } - // `T` -> `dyn Trait` unsizing - (_, &ty::Dynamic(data, region, ty::Dyn)) => { - // Can only unsize to an object-safe type - if data - .principal_def_id() - .is_some_and(|def_id| !tcx.check_is_object_safe(def_id)) - { - return Err(NoSolution); - } - let Some(sized_def_id) = tcx.lang_items().sized_trait() else { - return Err(NoSolution); - }; - // Check that the type implements all of the predicates of the def-id. - // (i.e. the principal, all of the associated types match, and any auto traits) - ecx.add_goals( - data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))), - ); - // The type must be Sized to be unsized. - ecx.add_goal(goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty]))); - // The type must outlive the lifetime of the `dyn` we're unsizing into. - ecx.add_goal( - goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))), - ); - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - } - // `[T; n]` -> `[T]` unsizing - (&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => { - // We just require that the element type stays the same - ecx.eq(goal.param_env, a_elem_ty, b_elem_ty)?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - } - // Struct unsizing `Struct` -> `Struct` where `T: Unsize` - (&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs)) - if a_def.is_struct() && a_def.did() == b_def.did() => - { - let unsizing_params = tcx.unsizing_params_for_adt(a_def.did()); - // We must be unsizing some type parameters. This also implies - // that the struct has a tail field. - if unsizing_params.is_empty() { - return Err(NoSolution); - } + // FIXME(-Ztrait-solver=next): Implement this when we get const working in the new solver - let tail_field = a_def - .non_enum_variant() - .fields - .raw - .last() - .expect("expected unsized ADT to have a tail field"); - let tail_field_ty = tcx.type_of(tail_field.did); - - let a_tail_ty = tail_field_ty.subst(tcx, a_substs); - let b_tail_ty = tail_field_ty.subst(tcx, b_substs); - - // Substitute just the unsizing params from B into A. The type after - // this substitution must be equal to B. This is so we don't unsize - // unrelated type parameters. - let new_a_substs = - tcx.mk_substs_from_iter(a_substs.iter().enumerate().map(|(i, a)| { - if unsizing_params.contains(i as u32) { b_substs[i] } else { a } - })); - let unsized_a_ty = tcx.mk_adt(a_def, new_a_substs); - - // Finally, we require that `TailA: Unsize` for the tail field - // types. - ecx.eq(goal.param_env, unsized_a_ty, b_ty)?; - ecx.add_goal(goal.with( - tcx, - ty::TraitRef::new(tcx, goal.predicate.def_id(), [a_tail_ty, b_tail_ty]), - )); - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - } - // Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize` - (&ty::Tuple(a_tys), &ty::Tuple(b_tys)) - if a_tys.len() == b_tys.len() && !a_tys.is_empty() => - { - let (a_last_ty, a_rest_tys) = a_tys.split_last().unwrap(); - let b_last_ty = b_tys.last().unwrap(); - - // Substitute just the tail field of B., and require that they're equal. - let unsized_a_ty = - tcx.mk_tup_from_iter(a_rest_tys.iter().chain([b_last_ty]).copied()); - ecx.eq(goal.param_env, unsized_a_ty, b_ty)?; - - // Similar to ADTs, require that the rest of the fields are equal. - ecx.add_goal(goal.with( - tcx, - ty::TraitRef::new(tcx, goal.predicate.def_id(), [*a_last_ty, *b_last_ty]), - )); - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - } - _ => Err(NoSolution), + // `Destruct` is automatically implemented for every type in + // non-const environments. + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + + fn consider_builtin_transmute_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> QueryResult<'tcx> { + if goal.predicate.polarity != ty::ImplPolarity::Positive { + return Err(NoSolution); + } + + // `rustc_transmute` does not have support for type or const params + if goal.has_non_region_placeholders() { + return Err(NoSolution); + } + + // Erase regions because we compute layouts in `rustc_transmute`, + // which will ICE for region vars. + let args = ecx.tcx().erase_regions(goal.predicate.trait_ref.args); + + let Some(assume) = + rustc_transmute::Assume::from_const(ecx.tcx(), goal.param_env, args.const_at(3)) + else { + return Err(NoSolution); + }; + + let certainty = ecx.is_transmutable( + rustc_transmute::Types { dst: args.type_at(0), src: args.type_at(1) }, + args.type_at(2), + assume, + )?; + ecx.evaluate_added_goals_and_make_canonical_response(certainty) + } + + fn consider_unsize_to_dyn_candidate( + ecx: &mut EvalCtxt<'_, 'tcx>, + goal: Goal<'tcx, Self>, + ) -> QueryResult<'tcx> { + ecx.probe(|_| CandidateKind::UnsizeAssembly).enter(|ecx| { + let a_ty = goal.predicate.self_ty(); + // We need to normalize the b_ty since it's destructured as a `dyn Trait`. + let Some(b_ty) = + ecx.try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1))? + else { + return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW); + }; + + let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else { + return Err(NoSolution); + }; + + let tcx = ecx.tcx(); + + // Can only unsize to an object-safe trait. + if b_data.principal_def_id().is_some_and(|def_id| !tcx.check_is_object_safe(def_id)) { + return Err(NoSolution); + } + + // Check that the type implements all of the predicates of the trait object. + // (i.e. the principal, all of the associated types match, and any auto traits) + ecx.add_goals(b_data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty)))); + + // The type must be `Sized` to be unsized. + if let Some(sized_def_id) = tcx.lang_items().sized_trait() { + ecx.add_goal(goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty]))); + } else { + return Err(NoSolution); } + + // The type must outlive the lifetime of the `dyn` we're unsizing into. + ecx.add_goal(goal.with(tcx, ty::OutlivesPredicate(a_ty, b_region))); + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } - fn consider_builtin_dyn_upcast_candidates( + /// ```ignore (builtin impl example) + /// trait Trait { + /// fn foo(&self); + /// } + /// // results in the following builtin impl + /// impl<'a, T: Trait + 'a> Unsize for T {} + /// ``` + fn consider_structural_builtin_unsize_candidates( ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, Self>, - ) -> Vec> { + ) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)> { if goal.predicate.polarity != ty::ImplPolarity::Positive { return vec![]; } - let tcx = ecx.tcx(); - - let a_ty = goal.predicate.self_ty(); - let b_ty = goal.predicate.trait_ref.substs.type_at(1); - let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else { - return vec![]; + let misc_candidate = |ecx: &mut EvalCtxt<'_, 'tcx>, certainty| { + ( + ecx.evaluate_added_goals_and_make_canonical_response(certainty).unwrap(), + BuiltinImplSource::Misc, + ) }; - let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else { - return vec![]; + + let result_to_single = |result, source| match result { + Ok(resp) => vec![(resp, source)], + Err(NoSolution) => vec![], }; + ecx.probe(|_| CandidateKind::UnsizeAssembly).enter(|ecx| { + let a_ty = goal.predicate.self_ty(); + // We need to normalize the b_ty since it's matched structurally + // in the other functions below. + let b_ty = match ecx + .try_normalize_ty(goal.param_env, goal.predicate.trait_ref.args.type_at(1)) + { + Ok(Some(b_ty)) => b_ty, + Ok(None) => return vec![misc_candidate(ecx, Certainty::OVERFLOW)], + Err(_) => return vec![], + }; + + let goal = goal.with(ecx.tcx(), (a_ty, b_ty)); + match (a_ty.kind(), b_ty.kind()) { + (ty::Infer(ty::TyVar(..)), ..) => bug!("unexpected infer {a_ty:?} {b_ty:?}"), + (_, ty::Infer(ty::TyVar(..))) => vec![misc_candidate(ecx, Certainty::AMBIGUOUS)], + + // Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`. + ( + &ty::Dynamic(a_data, a_region, ty::Dyn), + &ty::Dynamic(b_data, b_region, ty::Dyn), + ) => ecx.consider_builtin_dyn_upcast_candidates( + goal, a_data, a_region, b_data, b_region, + ), + + // `T` -> `dyn Trait` unsizing is handled separately in `consider_unsize_to_dyn_candidate` + (_, &ty::Dynamic(..)) => vec![], + + // `[T; N]` -> `[T]` unsizing + (&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => result_to_single( + ecx.consider_builtin_array_unsize(goal, a_elem_ty, b_elem_ty), + BuiltinImplSource::Misc, + ), + + // `Struct` -> `Struct` where `T: Unsize` + (&ty::Adt(a_def, a_args), &ty::Adt(b_def, b_args)) + if a_def.is_struct() && a_def == b_def => + { + result_to_single( + ecx.consider_builtin_struct_unsize(goal, a_def, a_args, b_args), + BuiltinImplSource::Misc, + ) + } + + // `(A, B, T)` -> `(A, B, U)` where `T: Unsize` + (&ty::Tuple(a_tys), &ty::Tuple(b_tys)) + if a_tys.len() == b_tys.len() && !a_tys.is_empty() => + { + result_to_single( + ecx.consider_builtin_tuple_unsize(goal, a_tys, b_tys), + BuiltinImplSource::TupleUnsizing, + ) + } + + _ => vec![], + } + }) + } +} + +impl<'tcx> EvalCtxt<'_, 'tcx> { + /// Trait upcasting allows for coercions between trait objects: + /// ```ignore (builtin impl example) + /// trait Super {} + /// trait Trait: Super {} + /// // results in builtin impls upcasting to a super trait + /// impl<'a, 'b: 'a> Unsize for dyn Trait + 'b {} + /// // and impls removing auto trait bounds. + /// impl<'a, 'b: 'a> Unsize for dyn Trait + Send + 'b {} + /// ``` + fn consider_builtin_dyn_upcast_candidates( + &mut self, + goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>, + a_data: &'tcx ty::List>, + a_region: ty::Region<'tcx>, + b_data: &'tcx ty::List>, + b_region: ty::Region<'tcx>, + ) -> Vec<(CanonicalResponse<'tcx>, BuiltinImplSource)> { + let tcx = self.tcx(); + let Goal { predicate: (a_ty, _b_ty), .. } = goal; + // All of a's auto traits need to be in b's auto traits. let auto_traits_compatible = b_data.auto_traits().all(|b| a_data.auto_traits().any(|a| a == b)); @@ -475,125 +579,241 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { return vec![]; } - let mut unsize_dyn_to_principal = |principal: Option>| { - ecx.probe(|ecx| -> Result<_, NoSolution> { - // Require that all of the trait predicates from A match B, except for - // the auto traits. We do this by constructing a new A type with B's - // auto traits, and equating these types. - let new_a_data = principal - .into_iter() - .map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait)) - .chain(a_data.iter().filter(|a| { - matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_)) - })) - .chain( - b_data - .auto_traits() - .map(ty::ExistentialPredicate::AutoTrait) - .map(ty::Binder::dummy), - ); - let new_a_data = tcx.mk_poly_existential_predicates_from_iter(new_a_data); - let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn); - - // We also require that A's lifetime outlives B's lifetime. - ecx.eq(goal.param_env, new_a_ty, b_ty)?; - ecx.add_goal( - goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))), - ); - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }) - }; - let mut responses = vec![]; // If the principal def ids match (or are both none), then we're not doing // trait upcasting. We're just removing auto traits (or shortening the lifetime). if a_data.principal_def_id() == b_data.principal_def_id() { - if let Ok(response) = unsize_dyn_to_principal(a_data.principal()) { - responses.push(response); - } - } else if let Some(a_principal) = a_data.principal() - && let Some(b_principal) = b_data.principal() - { - for super_trait_ref in supertraits(tcx, a_principal.with_self_ty(tcx, a_ty)) { - if super_trait_ref.def_id() != b_principal.def_id() { - continue; - } - let erased_trait_ref = super_trait_ref - .map_bound(|trait_ref| ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref)); - if let Ok(response) = unsize_dyn_to_principal(Some(erased_trait_ref)) { - responses.push(response); - } + if let Ok(resp) = self.consider_builtin_upcast_to_principal( + goal, + a_data, + a_region, + b_data, + b_region, + a_data.principal(), + ) { + responses.push((resp, BuiltinImplSource::Misc)); } + } else if let Some(a_principal) = a_data.principal() { + self.walk_vtable( + a_principal.with_self_ty(tcx, a_ty), + |ecx, new_a_principal, _, vtable_vptr_slot| { + if let Ok(resp) = ecx.probe_candidate("dyn upcast").enter(|ecx| { + ecx.consider_builtin_upcast_to_principal( + goal, + a_data, + a_region, + b_data, + b_region, + Some(new_a_principal.map_bound(|trait_ref| { + ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) + })), + ) + }) { + responses + .push((resp, BuiltinImplSource::TraitUpcasting { vtable_vptr_slot })); + } + }, + ); } responses } - fn consider_builtin_discriminant_kind_candidate( - ecx: &mut EvalCtxt<'_, 'tcx>, - goal: Goal<'tcx, Self>, + fn consider_builtin_upcast_to_principal( + &mut self, + goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>, + a_data: &'tcx ty::List>, + a_region: ty::Region<'tcx>, + b_data: &'tcx ty::List>, + b_region: ty::Region<'tcx>, + upcast_principal: Option>, ) -> QueryResult<'tcx> { - if goal.predicate.polarity != ty::ImplPolarity::Positive { - return Err(NoSolution); + let param_env = goal.param_env; + + // More than one projection in a_ty's bounds may match the projection + // in b_ty's bound. Use this to first determine *which* apply without + // having any inference side-effects. We process obligations because + // unification may initially succeed due to deferred projection equality. + let projection_may_match = + |ecx: &mut Self, + source_projection: ty::PolyExistentialProjection<'tcx>, + target_projection: ty::PolyExistentialProjection<'tcx>| { + source_projection.item_def_id() == target_projection.item_def_id() + && ecx + .probe(|_| CandidateKind::UpcastProbe) + .enter(|ecx| -> Result<(), NoSolution> { + ecx.eq(param_env, source_projection, target_projection)?; + let _ = ecx.try_evaluate_added_goals()?; + Ok(()) + }) + .is_ok() + }; + + for bound in b_data { + match bound.skip_binder() { + // Check that a's supertrait (upcast_principal) is compatible + // with the target (b_ty). + ty::ExistentialPredicate::Trait(target_principal) => { + self.eq(param_env, upcast_principal.unwrap(), bound.rebind(target_principal))?; + } + // Check that b_ty's projection is satisfied by exactly one of + // a_ty's projections. First, we look through the list to see if + // any match. If not, error. Then, if *more* than one matches, we + // return ambiguity. Otherwise, if exactly one matches, equate + // it with b_ty's projection. + ty::ExistentialPredicate::Projection(target_projection) => { + let target_projection = bound.rebind(target_projection); + let mut matching_projections = + a_data.projection_bounds().filter(|source_projection| { + projection_may_match(self, *source_projection, target_projection) + }); + let Some(source_projection) = matching_projections.next() else { + return Err(NoSolution); + }; + if matching_projections.next().is_some() { + return self.evaluate_added_goals_and_make_canonical_response( + Certainty::AMBIGUOUS, + ); + } + self.eq(param_env, source_projection, target_projection)?; + } + // Check that b_ty's auto traits are present in a_ty's bounds. + ty::ExistentialPredicate::AutoTrait(def_id) => { + if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) { + return Err(NoSolution); + } + } + } } - // `DiscriminantKind` is automatically implemented for every type. - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + // Also require that a_ty's lifetime outlives b_ty's lifetime. + self.add_goal(Goal::new( + self.tcx(), + param_env, + ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)), + )); + + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } - fn consider_builtin_destruct_candidate( - ecx: &mut EvalCtxt<'_, 'tcx>, - goal: Goal<'tcx, Self>, + /// We have the following builtin impls for arrays: + /// ```ignore (builtin impl example) + /// impl Unsize<[T]> for [T; N] {} + /// ``` + /// While the impl itself could theoretically not be builtin, + /// the actual unsizing behavior is builtin. Its also easier to + /// make all impls of `Unsize` builtin as we're able to use + /// `#[rustc_deny_explicit_impl]` in this case. + fn consider_builtin_array_unsize( + &mut self, + goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>, + a_elem_ty: Ty<'tcx>, + b_elem_ty: Ty<'tcx>, ) -> QueryResult<'tcx> { - if goal.predicate.polarity != ty::ImplPolarity::Positive { - return Err(NoSolution); - } - - if !goal.param_env.is_const() { - // `Destruct` is automatically implemented for every type in - // non-const environments. - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - } else { - // FIXME(-Ztrait-solver=next): Implement this when we get const working in the new solver - Err(NoSolution) - } + self.eq(goal.param_env, a_elem_ty, b_elem_ty)?; + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } - fn consider_builtin_transmute_candidate( - ecx: &mut EvalCtxt<'_, 'tcx>, - goal: Goal<'tcx, Self>, + /// We generate a builtin `Unsize` impls for structs with generic parameters only + /// mentioned by the last field. + /// ```ignore (builtin impl example) + /// struct Foo { + /// sized_field: Vec, + /// unsizable: Box, + /// } + /// // results in the following builtin impl + /// impl Unsize> for Foo + /// where + /// Box: Unsize>, + /// {} + /// ``` + fn consider_builtin_struct_unsize( + &mut self, + goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>, + def: ty::AdtDef<'tcx>, + a_args: ty::GenericArgsRef<'tcx>, + b_args: ty::GenericArgsRef<'tcx>, ) -> QueryResult<'tcx> { - if goal.predicate.polarity != ty::ImplPolarity::Positive { - return Err(NoSolution); - } + let tcx = self.tcx(); + let Goal { predicate: (_a_ty, b_ty), .. } = goal; - // `rustc_transmute` does not have support for type or const params - if goal.has_non_region_placeholders() { + let unsizing_params = tcx.unsizing_params_for_adt(def.did()); + // We must be unsizing some type parameters. This also implies + // that the struct has a tail field. + if unsizing_params.is_empty() { return Err(NoSolution); } - // Erase regions because we compute layouts in `rustc_transmute`, - // which will ICE for region vars. - let substs = ecx.tcx().erase_regions(goal.predicate.trait_ref.substs); + let tail_field = def.non_enum_variant().tail(); + let tail_field_ty = tcx.type_of(tail_field.did); + + let a_tail_ty = tail_field_ty.instantiate(tcx, a_args); + let b_tail_ty = tail_field_ty.instantiate(tcx, b_args); + + // Substitute just the unsizing params from B into A. The type after + // this substitution must be equal to B. This is so we don't unsize + // unrelated type parameters. + let new_a_args = tcx.mk_args_from_iter( + a_args + .iter() + .enumerate() + .map(|(i, a)| if unsizing_params.contains(i as u32) { b_args[i] } else { a }), + ); + let unsized_a_ty = Ty::new_adt(tcx, def, new_a_args); + + // Finally, we require that `TailA: Unsize` for the tail field + // types. + self.eq(goal.param_env, unsized_a_ty, b_ty)?; + self.add_goal(goal.with( + tcx, + ty::TraitRef::new( + tcx, + tcx.lang_items().unsize_trait().unwrap(), + [a_tail_ty, b_tail_ty], + ), + )); + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } - let Some(assume) = rustc_transmute::Assume::from_const( - ecx.tcx(), - goal.param_env, - substs.const_at(3), - ) else { - return Err(NoSolution); - }; + /// We generate the following builtin impl for tuples of all sizes. + /// + /// This impl is still unstable and we emit a feature error when it + /// when it is used by a coercion. + /// ```ignore (builtin impl example) + /// impl Unsize<(T, V)> for (T, U) + /// where + /// U: Unsize, + /// {} + /// ``` + fn consider_builtin_tuple_unsize( + &mut self, + goal: Goal<'tcx, (Ty<'tcx>, Ty<'tcx>)>, + a_tys: &'tcx ty::List>, + b_tys: &'tcx ty::List>, + ) -> QueryResult<'tcx> { + let tcx = self.tcx(); + let Goal { predicate: (_a_ty, b_ty), .. } = goal; - let certainty = ecx.is_transmutable( - rustc_transmute::Types { dst: substs.type_at(0), src: substs.type_at(1) }, - substs.type_at(2), - assume, - )?; - ecx.evaluate_added_goals_and_make_canonical_response(certainty) + let (&a_last_ty, a_rest_tys) = a_tys.split_last().unwrap(); + let &b_last_ty = b_tys.last().unwrap(); + + // Substitute just the tail field of B., and require that they're equal. + let unsized_a_ty = + Ty::new_tup_from_iter(tcx, a_rest_tys.iter().copied().chain([b_last_ty])); + self.eq(goal.param_env, unsized_a_ty, b_ty)?; + + // Similar to ADTs, require that we can unsize the tail. + self.add_goal(goal.with( + tcx, + ty::TraitRef::new( + tcx, + tcx.lang_items().unsize_trait().unwrap(), + [a_last_ty, b_last_ty], + ), + )); + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } -} -impl<'tcx> EvalCtxt<'_, 'tcx> { // Return `Some` if there is an impl (built-in or user provided) that may // hold for the self type of the goal, which for coherence and soundness // purposes must disqualify the built-in auto impl assembled by considering @@ -618,7 +838,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { ty::Dynamic(..) | ty::Param(..) | ty::Foreign(..) - | ty::Alias(ty::Projection | ty::Inherent, ..) + | ty::Alias(ty::Projection | ty::Weak | ty::Inherent, ..) | ty::Placeholder(..) => Some(Err(NoSolution)), ty::Infer(_) | ty::Bound(_, _) => bug!("unexpected type `{self_ty}`"), @@ -665,7 +885,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { | ty::Tuple(_) | ty::Adt(_, _) // FIXME: Handling opaques here is kinda sus. Especially because we - // simplify them to PlaceholderSimplifiedType. + // simplify them to SimplifiedType::Placeholder. | ty::Alias(ty::Opaque, _) => { let mut disqualifying_impl = None; self.tcx().for_each_relevant_impl_treating_projections( @@ -698,16 +918,11 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { goal: Goal<'tcx, TraitPredicate<'tcx>>, constituent_tys: impl Fn(&EvalCtxt<'_, 'tcx>, Ty<'tcx>) -> Result>, NoSolution>, ) -> QueryResult<'tcx> { - self.probe(|ecx| { + self.probe_candidate("constituent tys").enter(|ecx| { ecx.add_goals( constituent_tys(ecx, goal.predicate.self_ty())? .into_iter() - .map(|ty| { - goal.with( - ecx.tcx(), - ty::Binder::dummy(goal.predicate.with_self_ty(ecx.tcx(), ty)), - ) - }) + .map(|ty| goal.with(ecx.tcx(), goal.predicate.with_self_ty(ecx.tcx(), ty))) .collect::>(), ); ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) diff --git a/compiler/rustc_trait_selection/src/solve/weak_types.rs b/compiler/rustc_trait_selection/src/solve/weak_types.rs new file mode 100644 index 0000000000000..54de32cf61891 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/weak_types.rs @@ -0,0 +1,34 @@ +//! Computes a normalizes-to (projection) goal for inherent associated types, +//! `#![feature(lazy_type_alias)]` and `#![feature(type_alias_impl_trait)]`. +//! +//! Since a weak alias is not ambiguous, this just computes the `type_of` of +//! the alias and registers the where-clauses of the type alias. +use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; +use rustc_middle::ty; + +use super::EvalCtxt; + +impl<'tcx> EvalCtxt<'_, 'tcx> { + pub(super) fn normalize_weak_type( + &mut self, + goal: Goal<'tcx, ty::ProjectionPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + let tcx = self.tcx(); + let weak_ty = goal.predicate.projection_ty; + let expected = goal.predicate.term.ty().expect("no such thing as a const alias"); + + let actual = tcx.type_of(weak_ty.def_id).instantiate(tcx, weak_ty.args); + self.eq(goal.param_env, expected, actual)?; + + // Check where clauses + self.add_goals( + tcx.predicates_of(weak_ty.def_id) + .instantiate(tcx, weak_ty.args) + .predicates + .into_iter() + .map(|pred| goal.with(tcx, pred)), + ); + + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } +} diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index 62d2aad5277f6..ba5000da6cd86 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -95,15 +95,14 @@ impl<'tcx> AutoTraitFinder<'tcx> { tcx, ObligationCause::dummy(), orig_env, - ty::Binder::dummy(ty::TraitPredicate { + ty::TraitPredicate { trait_ref, - constness: ty::BoundConstness::NotConst, polarity: if polarity { ImplPolarity::Positive } else { ImplPolarity::Negative }, - }), + }, )); if let Ok(Some(ImplSource::UserDefined(_))) = result { debug!( @@ -152,21 +151,16 @@ impl<'tcx> AutoTraitFinder<'tcx> { // traits::project will see that 'T: SomeTrait' is in our ParamEnv, allowing // SelectionContext to return it back to us. - let Some((new_env, user_env)) = self.evaluate_predicates( - &infcx, - trait_did, - ty, - orig_env, - orig_env, - &mut fresh_preds, - ) else { + let Some((new_env, user_env)) = + self.evaluate_predicates(&infcx, trait_did, ty, orig_env, orig_env, &mut fresh_preds) + else { return AutoTraitResult::NegativeImpl; }; let (full_env, full_user_env) = self .evaluate_predicates(&infcx, trait_did, ty, new_env, user_env, &mut fresh_preds) .unwrap_or_else(|| { - panic!("Failed to fully process: {:?} {:?} {:?}", ty, trait_did, orig_env) + panic!("Failed to fully process: {ty:?} {trait_did:?} {orig_env:?}") }); debug!( @@ -183,7 +177,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { ocx.register_bound(ObligationCause::dummy(), full_env, ty, trait_did); let errors = ocx.select_all_or_error(); if !errors.is_empty() { - panic!("Unable to fulfill trait {:?} for '{:?}': {:?}", trait_did, ty, errors); + panic!("Unable to fulfill trait {trait_did:?} for '{ty:?}': {errors:?}"); } let outlives_env = OutlivesEnvironment::new(full_env); @@ -255,7 +249,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { // that are already in the `ParamEnv` (modulo regions): we already // know that they must hold. for predicate in param_env.caller_bounds() { - fresh_preds.insert(self.clean_pred(infcx, predicate)); + fresh_preds.insert(self.clean_pred(infcx, predicate.as_predicate())); } let mut select = SelectionContext::new(&infcx); @@ -265,13 +259,13 @@ impl<'tcx> AutoTraitFinder<'tcx> { predicates.push_back(ty::Binder::dummy(ty::TraitPredicate { trait_ref: ty::TraitRef::new(infcx.tcx, trait_did, [ty]), - constness: ty::BoundConstness::NotConst, // Auto traits are positive polarity: ty::ImplPolarity::Positive, })); - let computed_preds = param_env.caller_bounds().iter(); - let mut user_computed_preds: FxIndexSet<_> = user_env.caller_bounds().iter().collect(); + let computed_preds = param_env.caller_bounds().iter().map(|c| c.as_predicate()); + let mut user_computed_preds: FxIndexSet<_> = + user_env.caller_bounds().iter().map(|c| c.as_predicate()).collect(); let mut new_env = param_env; let dummy_cause = ObligationCause::dummy(); @@ -291,7 +285,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { new_env, pred, )); - let result = select.select(&obligation); + let result = select.poly_select(&obligation); match result { Ok(Some(ref impl_source)) => { @@ -328,7 +322,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { } Ok(None) => {} Err(SelectionError::Unimplemented) => { - if self.is_param_no_infer(pred.skip_binder().trait_ref.substs) { + if self.is_param_no_infer(pred.skip_binder().trait_ref.args) { already_visited.remove(&pred); self.add_user_pred(&mut user_computed_preds, pred.to_predicate(self.tcx)); predicates.push_back(pred); @@ -338,27 +332,25 @@ impl<'tcx> AutoTraitFinder<'tcx> { {:?} {:?} {:?}", ty, pred, - pred.skip_binder().trait_ref.substs + pred.skip_binder().trait_ref.args ); return None; } } - _ => panic!("Unexpected error for '{:?}': {:?}", ty, result), + _ => panic!("Unexpected error for '{ty:?}': {result:?}"), }; let normalized_preds = elaborate(tcx, computed_preds.clone().chain(user_computed_preds.iter().cloned())); new_env = ty::ParamEnv::new( - tcx.mk_predicates_from_iter(normalized_preds), + tcx.mk_clauses_from_iter(normalized_preds.filter_map(|p| p.as_clause())), param_env.reveal(), - param_env.constness(), ); } let final_user_env = ty::ParamEnv::new( - tcx.mk_predicates_from_iter(user_computed_preds.into_iter()), + tcx.mk_clauses_from_iter(user_computed_preds.into_iter().filter_map(|p| p.as_clause())), user_env.reveal(), - user_env.constness(), ); debug!( "evaluate_nested_obligations(ty={:?}, trait_did={:?}): succeeded with '{:?}' \ @@ -400,22 +392,22 @@ impl<'tcx> AutoTraitFinder<'tcx> { let mut should_add_new = true; user_computed_preds.retain(|&old_pred| { if let ( - ty::PredicateKind::Clause(ty::Clause::Trait(new_trait)), - ty::PredicateKind::Clause(ty::Clause::Trait(old_trait)), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(new_trait)), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(old_trait)), ) = (new_pred.kind().skip_binder(), old_pred.kind().skip_binder()) { if new_trait.def_id() == old_trait.def_id() { - let new_substs = new_trait.trait_ref.substs; - let old_substs = old_trait.trait_ref.substs; + let new_args = new_trait.trait_ref.args; + let old_args = old_trait.trait_ref.args; - if !new_substs.types().eq(old_substs.types()) { + if !new_args.types().eq(old_args.types()) { // We can't compare lifetimes if the types are different, // so skip checking `old_pred`. return true; } for (new_region, old_region) in - iter::zip(new_substs.regions(), old_substs.regions()) + iter::zip(new_args.regions(), old_args.regions()) { match (*new_region, *old_region) { // If both predicates have an `ReLateBound` (a HRTB) in the @@ -568,8 +560,8 @@ impl<'tcx> AutoTraitFinder<'tcx> { finished_map } - fn is_param_no_infer(&self, substs: SubstsRef<'_>) -> bool { - self.is_of_param(substs.type_at(0)) && !substs.types().any(|t| t.has_infer_types()) + fn is_param_no_infer(&self, args: GenericArgsRef<'_>) -> bool { + self.is_of_param(args.type_at(0)) && !args.types().any(|t| t.has_infer_types()) } pub fn is_of_param(&self, ty: Ty<'_>) -> bool { @@ -621,14 +613,14 @@ impl<'tcx> AutoTraitFinder<'tcx> { let bound_predicate = predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(p)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(p)) => { // Add this to `predicates` so that we end up calling `select` // with it. If this predicate ends up being unimplemented, // then `evaluate_predicates` will handle adding it the `ParamEnv` // if possible. predicates.push_back(bound_predicate.rebind(p)); } - ty::PredicateKind::Clause(ty::Clause::Projection(p)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(p)) => { let p = bound_predicate.rebind(p); debug!( "evaluate_nested_obligations: examining projection predicate {:?}", @@ -640,7 +632,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { // an inference variable. // Additionally, we check if we've seen this predicate before, // to avoid rendering duplicate bounds to the user. - if self.is_param_no_infer(p.skip_binder().projection_ty.substs) + if self.is_param_no_infer(p.skip_binder().projection_ty.args) && !p.term().skip_binder().has_infer_types() && is_new_pred { @@ -753,16 +745,16 @@ impl<'tcx> AutoTraitFinder<'tcx> { // subobligations or getting an error) when we started off with // inference variables if p.term().skip_binder().has_infer_types() { - panic!("Unexpected result when selecting {:?} {:?}", ty, obligation) + panic!("Unexpected result when selecting {ty:?} {obligation:?}") } } } } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(binder)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(binder)) => { let binder = bound_predicate.rebind(binder); selcx.infcx.region_outlives_predicate(&dummy_cause, binder) } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(binder)) => { + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(binder)) => { let binder = bound_predicate.rebind(binder); match ( binder.no_bound_vars(), @@ -793,7 +785,7 @@ impl<'tcx> AutoTraitFinder<'tcx> { unevaluated, Some(obligation.cause.span), ) { - Ok(Some(valtree)) => Ok(selcx.tcx().mk_const(valtree, c.ty())), + Ok(Some(valtree)) => Ok(ty::Const::new_value(selcx.tcx(),valtree, c.ty())), Ok(None) => { let tcx = self.tcx; let reported = @@ -826,18 +818,15 @@ impl<'tcx> AutoTraitFinder<'tcx> { // we start out with a `ParamEnv` with no inference variables, // and these don't correspond to adding any new bounds to // the `ParamEnv`. - ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) | ty::PredicateKind::AliasRelate(..) | ty::PredicateKind::ObjectSafe(..) | ty::PredicateKind::ClosureKind(..) | ty::PredicateKind::Subtype(..) // FIXME(generic_const_exprs): you can absolutely add this as a where clauses - | ty::PredicateKind::ConstEvaluatable(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) | ty::PredicateKind::Coerce(..) => {} - ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("predicate should only exist in the environment: {bound_predicate:?}") - } ty::PredicateKind::Ambiguous => return false, }; } diff --git a/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs b/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs deleted file mode 100644 index 28967e1cc55b2..0000000000000 --- a/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! Defines a Chalk-based `TraitEngine` - -use crate::infer::canonical::OriginalQueryValues; -use crate::infer::InferCtxt; -use crate::traits::query::NoSolution; -use crate::traits::{ - ChalkEnvironmentAndGoal, FulfillmentError, FulfillmentErrorCode, PredicateObligation, - SelectionError, TraitEngine, -}; -use rustc_data_structures::fx::FxIndexSet; -use rustc_middle::ty::TypeVisitableExt; - -pub struct FulfillmentContext<'tcx> { - obligations: FxIndexSet>, - - usable_in_snapshot: bool, -} - -impl FulfillmentContext<'_> { - pub(super) fn new() -> Self { - FulfillmentContext { obligations: FxIndexSet::default(), usable_in_snapshot: false } - } - - pub(crate) fn new_in_snapshot() -> Self { - FulfillmentContext { usable_in_snapshot: true, ..Self::new() } - } -} - -impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> { - fn register_predicate_obligation( - &mut self, - infcx: &InferCtxt<'tcx>, - obligation: PredicateObligation<'tcx>, - ) { - if !self.usable_in_snapshot { - assert!(!infcx.is_in_snapshot()); - } - let obligation = infcx.resolve_vars_if_possible(obligation); - - self.obligations.insert(obligation); - } - - fn collect_remaining_errors( - &mut self, - _infcx: &InferCtxt<'tcx>, - ) -> Vec> { - // any remaining obligations are errors - self.obligations - .iter() - .map(|obligation| FulfillmentError { - obligation: obligation.clone(), - code: FulfillmentErrorCode::CodeAmbiguity { overflow: false }, - // FIXME - does Chalk have a notation of 'root obligation'? - // This is just for diagnostics, so it's okay if this is wrong - root_obligation: obligation.clone(), - }) - .collect() - } - - fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec> { - if !self.usable_in_snapshot { - assert!(!infcx.is_in_snapshot()); - } - - let mut errors = Vec::new(); - let mut next_round = FxIndexSet::default(); - let mut making_progress; - - loop { - making_progress = false; - - // We iterate over all obligations, and record if we are able - // to unambiguously prove at least one obligation. - for obligation in self.obligations.drain(..) { - let obligation = infcx.resolve_vars_if_possible(obligation); - let environment = obligation.param_env.caller_bounds(); - let goal = ChalkEnvironmentAndGoal { environment, goal: obligation.predicate }; - let mut orig_values = OriginalQueryValues::default(); - if goal.references_error() { - continue; - } - - let canonical_goal = - infcx.canonicalize_query_preserving_universes(goal, &mut orig_values); - - match infcx.tcx.evaluate_goal(canonical_goal) { - Ok(response) => { - if response.is_proven() { - making_progress = true; - - match infcx.instantiate_query_response_and_region_obligations( - &obligation.cause, - obligation.param_env, - &orig_values, - &response, - ) { - Ok(infer_ok) => next_round.extend( - infer_ok.obligations.into_iter().map(|obligation| { - assert!(!infcx.is_in_snapshot()); - infcx.resolve_vars_if_possible(obligation) - }), - ), - - Err(_err) => errors.push(FulfillmentError { - obligation: obligation.clone(), - code: FulfillmentErrorCode::CodeSelectionError( - SelectionError::Unimplemented, - ), - // FIXME - does Chalk have a notation of 'root obligation'? - // This is just for diagnostics, so it's okay if this is wrong - root_obligation: obligation, - }), - } - } else { - // Ambiguous: retry at next round. - next_round.insert(obligation); - } - } - - Err(NoSolution) => errors.push(FulfillmentError { - obligation: obligation.clone(), - code: FulfillmentErrorCode::CodeSelectionError( - SelectionError::Unimplemented, - ), - // FIXME - does Chalk have a notation of 'root obligation'? - // This is just for diagnostics, so it's okay if this is wrong - root_obligation: obligation, - }), - } - } - next_round = std::mem::replace(&mut self.obligations, next_round); - - if !making_progress { - break; - } - } - - errors - } - - fn drain_unstalled_obligations( - &mut self, - _: &InferCtxt<'tcx>, - ) -> Vec> { - unimplemented!() - } - - fn pending_obligations(&self) -> Vec> { - self.obligations.iter().cloned().collect() - } -} diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index e8c5a8fab2a37..5746781ae35d7 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -5,9 +5,9 @@ //! [trait-specialization]: https://rustc-dev-guide.rust-lang.org/traits/specialization.html use crate::infer::outlives::env::OutlivesEnvironment; -use crate::infer::{CombinedSnapshot, InferOk}; +use crate::infer::InferOk; use crate::traits::outlives_bounds::InferCtxtExt as _; -use crate::traits::select::IntercrateAmbiguityCause; +use crate::traits::select::{IntercrateAmbiguityCause, TreatInductiveCycleAs}; use crate::traits::util::impl_subject_and_oblig; use crate::traits::SkipLeakCheck; use crate::traits::{ @@ -23,13 +23,15 @@ use rustc_middle::traits::specialization_graph::OverlapMode; use rustc_middle::traits::DefiningAnchor; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; -use rustc_middle::ty::{self, ImplSubject, Ty, TyCtxt, TypeVisitor}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitor}; +use rustc_session::lint::builtin::COINDUCTIVE_OVERLAP_IN_COHERENCE; use rustc_span::symbol::sym; use rustc_span::DUMMY_SP; use std::fmt::Debug; use std::iter; use std::ops::ControlFlow; +use super::query::evaluate_obligation::InferCtxtExt; use super::NormalizeExt; /// Whether we do the orphan check relative to this crate or @@ -62,6 +64,21 @@ pub fn add_placeholder_note(err: &mut Diagnostic) { ); } +#[derive(Debug, Clone, Copy)] +enum TrackAmbiguityCauses { + Yes, + No, +} + +impl TrackAmbiguityCauses { + fn is_yes(self) -> bool { + match self { + TrackAmbiguityCauses::Yes => true, + TrackAmbiguityCauses::No => false, + } + } +} + /// If there are types that satisfy both impls, returns `Some` /// with a suitably-freshened `ImplHeader` with those types /// substituted. Otherwise, returns `None`. @@ -80,9 +97,7 @@ pub fn overlapping_impls( let impl1_ref = tcx.impl_trait_ref(impl1_def_id); let impl2_ref = tcx.impl_trait_ref(impl2_def_id); let may_overlap = match (impl1_ref, impl2_ref) { - (Some(a), Some(b)) => { - drcx.substs_refs_may_unify(a.skip_binder().substs, b.skip_binder().substs) - } + (Some(a), Some(b)) => drcx.args_refs_may_unify(a.skip_binder().args, b.skip_binder().args), (None, None) => { let self_ty1 = tcx.type_of(impl1_def_id).skip_binder(); let self_ty2 = tcx.type_of(impl2_def_id).skip_binder(); @@ -97,29 +112,28 @@ pub fn overlapping_impls( return None; } - let infcx = tcx - .infer_ctxt() - .with_opaque_type_inference(DefiningAnchor::Bubble) - .intercrate(true) - .build(); - let selcx = &mut SelectionContext::new(&infcx); - let overlaps = - overlap(selcx, skip_leak_check, impl1_def_id, impl2_def_id, overlap_mode).is_some(); - if !overlaps { - return None; - } + let _overlap_with_bad_diagnostics = overlap( + tcx, + TrackAmbiguityCauses::No, + skip_leak_check, + impl1_def_id, + impl2_def_id, + overlap_mode, + )?; // In the case where we detect an error, run the check again, but // this time tracking intercrate ambiguity causes for better // diagnostics. (These take time and can lead to false errors.) - let infcx = tcx - .infer_ctxt() - .with_opaque_type_inference(DefiningAnchor::Bubble) - .intercrate(true) - .build(); - let selcx = &mut SelectionContext::new(&infcx); - selcx.enable_tracking_intercrate_ambiguity_causes(); - Some(overlap(selcx, skip_leak_check, impl1_def_id, impl2_def_id, overlap_mode).unwrap()) + let overlap = overlap( + tcx, + TrackAmbiguityCauses::Yes, + skip_leak_check, + impl1_def_id, + impl2_def_id, + overlap_mode, + ) + .unwrap(); + Some(overlap) } fn with_fresh_ty_vars<'cx, 'tcx>( @@ -128,58 +142,60 @@ fn with_fresh_ty_vars<'cx, 'tcx>( impl_def_id: DefId, ) -> ty::ImplHeader<'tcx> { let tcx = selcx.tcx(); - let impl_substs = selcx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + let impl_args = selcx.infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); let header = ty::ImplHeader { impl_def_id, - self_ty: tcx.type_of(impl_def_id).subst(tcx, impl_substs), - trait_ref: tcx.impl_trait_ref(impl_def_id).map(|i| i.subst(tcx, impl_substs)), - predicates: tcx.predicates_of(impl_def_id).instantiate(tcx, impl_substs).predicates, + self_ty: tcx.type_of(impl_def_id).instantiate(tcx, impl_args), + trait_ref: tcx.impl_trait_ref(impl_def_id).map(|i| i.instantiate(tcx, impl_args)), + predicates: tcx + .predicates_of(impl_def_id) + .instantiate(tcx, impl_args) + .iter() + .map(|(c, s)| (c.as_predicate(), s)) + .collect(), }; - let InferOk { value: mut header, obligations } = - selcx.infcx.at(&ObligationCause::dummy(), param_env).normalize(header); + let InferOk { value: mut header, obligations } = selcx + .infcx + .at(&ObligationCause::dummy_with_span(tcx.def_span(impl_def_id)), param_env) + .normalize(header); - header.predicates.extend(obligations.into_iter().map(|o| o.predicate)); + header.predicates.extend(obligations.into_iter().map(|o| (o.predicate, o.cause.span))); header } /// Can both impl `a` and impl `b` be satisfied by a common type (including /// where-clauses)? If so, returns an `ImplHeader` that unifies the two impls. -fn overlap<'cx, 'tcx>( - selcx: &mut SelectionContext<'cx, 'tcx>, +#[instrument(level = "debug", skip(tcx))] +fn overlap<'tcx>( + tcx: TyCtxt<'tcx>, + track_ambiguity_causes: TrackAmbiguityCauses, skip_leak_check: SkipLeakCheck, impl1_def_id: DefId, impl2_def_id: DefId, overlap_mode: OverlapMode, ) -> Option> { - debug!( - "overlap(impl1_def_id={:?}, impl2_def_id={:?}, overlap_mode={:?})", - impl1_def_id, impl2_def_id, overlap_mode - ); - - selcx.infcx.probe_maybe_skip_leak_check(skip_leak_check.is_yes(), |snapshot| { - overlap_within_probe(selcx, impl1_def_id, impl2_def_id, overlap_mode, snapshot) - }) -} - -fn overlap_within_probe<'cx, 'tcx>( - selcx: &mut SelectionContext<'cx, 'tcx>, - impl1_def_id: DefId, - impl2_def_id: DefId, - overlap_mode: OverlapMode, - snapshot: &CombinedSnapshot<'tcx>, -) -> Option> { - let infcx = selcx.infcx; - if overlap_mode.use_negative_impl() { - if negative_impl(infcx.tcx, impl1_def_id, impl2_def_id) - || negative_impl(infcx.tcx, impl2_def_id, impl1_def_id) + if impl_intersection_has_negative_obligation(tcx, impl1_def_id, impl2_def_id) + || impl_intersection_has_negative_obligation(tcx, impl2_def_id, impl1_def_id) { return None; } } + let infcx = tcx + .infer_ctxt() + .with_opaque_type_inference(DefiningAnchor::Bubble) + .skip_leak_check(skip_leak_check.is_yes()) + .intercrate(true) + .with_next_trait_solver(tcx.next_trait_solver_in_coherence()) + .build(); + let selcx = &mut SelectionContext::new(&infcx); + if track_ambiguity_causes.is_yes() { + selcx.enable_tracking_intercrate_ambiguity_causes(); + } + // For the purposes of this check, we don't bring any placeholder // types into scope; instead, we replace the generic types with // fresh type variables, and hence we do our evaluations in an @@ -189,27 +205,100 @@ fn overlap_within_probe<'cx, 'tcx>( let impl1_header = with_fresh_ty_vars(selcx, param_env, impl1_def_id); let impl2_header = with_fresh_ty_vars(selcx, param_env, impl2_def_id); - let obligations = equate_impl_headers(selcx.infcx, &impl1_header, &impl2_header)?; + // Equate the headers to find their intersection (the general type, with infer vars, + // that may apply both impls). + let equate_obligations = equate_impl_headers(selcx.infcx, &impl1_header, &impl2_header)?; debug!("overlap: unification check succeeded"); if overlap_mode.use_implicit_negative() { - if implicit_negative(selcx, param_env, &impl1_header, impl2_header, obligations) { - return None; + for mode in [TreatInductiveCycleAs::Ambig, TreatInductiveCycleAs::Recur] { + if let Some(failing_obligation) = selcx.with_treat_inductive_cycle_as(mode, |selcx| { + impl_intersection_has_impossible_obligation( + selcx, + param_env, + &impl1_header, + &impl2_header, + &equate_obligations, + ) + }) { + if matches!(mode, TreatInductiveCycleAs::Recur) { + let first_local_impl = impl1_header + .impl_def_id + .as_local() + .or(impl2_header.impl_def_id.as_local()) + .expect("expected one of the impls to be local"); + infcx.tcx.struct_span_lint_hir( + COINDUCTIVE_OVERLAP_IN_COHERENCE, + infcx.tcx.local_def_id_to_hir_id(first_local_impl), + infcx.tcx.def_span(first_local_impl), + format!( + "implementations {} will conflict in the future", + match impl1_header.trait_ref { + Some(trait_ref) => { + let trait_ref = infcx.resolve_vars_if_possible(trait_ref); + format!( + "of `{}` for `{}`", + trait_ref.print_only_trait_path(), + trait_ref.self_ty() + ) + } + None => format!( + "for `{}`", + infcx.resolve_vars_if_possible(impl1_header.self_ty) + ), + }, + ), + |lint| { + lint.note( + "impls that are not considered to overlap may be considered to \ + overlap in the future", + ) + .span_label( + infcx.tcx.def_span(impl1_header.impl_def_id), + "the first impl is here", + ) + .span_label( + infcx.tcx.def_span(impl2_header.impl_def_id), + "the second impl is here", + ); + if !failing_obligation.cause.span.is_dummy() { + lint.span_label( + failing_obligation.cause.span, + format!( + "`{}` may be considered to hold in future releases, \ + causing the impls to overlap", + infcx + .resolve_vars_if_possible(failing_obligation.predicate) + ), + ); + } + lint + }, + ); + } + + return None; + } } } - // We disable the leak when creating the `snapshot` by using - // `infcx.probe_maybe_disable_leak_check`. - if infcx.leak_check(true, snapshot).is_err() { + // We toggle the `leak_check` by using `skip_leak_check` when constructing the + // inference context, so this may be a noop. + if infcx.leak_check(ty::UniverseIndex::ROOT, None).is_err() { debug!("overlap: leak check failed"); return None; } let intercrate_ambiguity_causes = selcx.take_intercrate_ambiguity_causes(); debug!("overlap: intercrate_ambiguity_causes={:#?}", intercrate_ambiguity_causes); - - let involves_placeholder = - matches!(selcx.infcx.region_constraints_added_in_snapshot(snapshot), Some(true)); + let involves_placeholder = infcx + .inner + .borrow_mut() + .unwrap_region_constraints() + .data() + .constraints + .iter() + .any(|c| c.0.involves_placeholders()); let impl_header = selcx.infcx.resolve_vars_if_possible(impl1_header); Some(OverlapResult { impl_header, intercrate_ambiguity_causes, involves_placeholder }) @@ -236,67 +325,75 @@ fn equate_impl_headers<'tcx>( result.map(|infer_ok| infer_ok.obligations).ok() } -/// Given impl1 and impl2 check if both impls can be satisfied by a common type (including -/// where-clauses) If so, return false, otherwise return true, they are disjoint. -fn implicit_negative<'cx, 'tcx>( +/// Check if both impls can be satisfied by a common type by considering whether +/// any of either impl's obligations is not known to hold. +/// +/// For example, given these two impls: +/// `impl From for Box` (in my crate) +/// `impl From for Box where E: Error` (in libstd) +/// +/// After replacing both impl headers with inference vars (which happens before +/// this function is called), we get: +/// `Box: From` +/// `Box: From` +/// +/// This gives us `?E = MyLocalType`. We then certainly know that `MyLocalType: Error` +/// never holds in intercrate mode since a local impl does not exist, and a +/// downstream impl cannot be added -- therefore can consider the intersection +/// of the two impls above to be empty. +/// +/// Importantly, this works even if there isn't a `impl !Error for MyLocalType`. +fn impl_intersection_has_impossible_obligation<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, param_env: ty::ParamEnv<'tcx>, impl1_header: &ty::ImplHeader<'tcx>, - impl2_header: ty::ImplHeader<'tcx>, - obligations: PredicateObligations<'tcx>, -) -> bool { - // There's no overlap if obligations are unsatisfiable or if the obligation negated is - // satisfied. - // - // For example, given these two impl headers: - // - // `impl<'a> From<&'a str> for Box` - // `impl From for Box where E: Error` - // - // So we have: - // - // `Box: From<&'?a str>` - // `Box: From` - // - // After equating the two headers: - // - // `Box = Box` - // So, `?E = &'?a str` and then given the where clause `&'?a str: Error`. - // - // If the obligation `&'?a str: Error` holds, it means that there's overlap. If that doesn't - // hold we need to check if `&'?a str: !Error` holds, if doesn't hold there's overlap because - // at some point an impl for `&'?a str: Error` could be added. - debug!( - "implicit_negative(impl1_header={:?}, impl2_header={:?}, obligations={:?})", - impl1_header, impl2_header, obligations - ); + impl2_header: &ty::ImplHeader<'tcx>, + obligations: &PredicateObligations<'tcx>, +) -> Option> { let infcx = selcx.infcx; - let opt_failing_obligation = impl1_header - .predicates - .iter() - .copied() - .chain(impl2_header.predicates) - .map(|p| infcx.resolve_vars_if_possible(p)) - .map(|p| Obligation { - cause: ObligationCause::dummy(), - param_env, - recursion_depth: 0, - predicate: p, - }) - .chain(obligations) - .find(|o| !selcx.predicate_may_hold_fatal(o)); - if let Some(failing_obligation) = opt_failing_obligation { - debug!("overlap: obligation unsatisfiable {:?}", failing_obligation); - true - } else { - false - } + [&impl1_header.predicates, &impl2_header.predicates] + .into_iter() + .flatten() + .map(|&(predicate, span)| { + Obligation::new(infcx.tcx, ObligationCause::dummy_with_span(span), param_env, predicate) + }) + .chain(obligations.into_iter().cloned()) + .find(|obligation: &PredicateObligation<'tcx>| { + if infcx.next_trait_solver() { + infcx.evaluate_obligation(obligation).map_or(false, |result| !result.may_apply()) + } else { + // We use `evaluate_root_obligation` to correctly track intercrate + // ambiguity clauses. We cannot use this in the new solver. + selcx.evaluate_root_obligation(obligation).map_or( + false, // Overflow has occurred, and treat the obligation as possibly holding. + |result| !result.may_apply(), + ) + } + }) } -/// Given impl1 and impl2 check if both impls are never satisfied by a common type (including -/// where-clauses) If so, return true, they are disjoint and false otherwise. -fn negative_impl(tcx: TyCtxt<'_>, impl1_def_id: DefId, impl2_def_id: DefId) -> bool { +/// Check if both impls can be satisfied by a common type by considering whether +/// any of first impl's obligations is known not to hold *via a negative predicate*. +/// +/// For example, given these two impls: +/// `struct MyCustomBox(Box);` +/// `impl From<&str> for MyCustomBox` (in my crate) +/// `impl From for MyCustomBox where E: Error` (in my crate) +/// +/// After replacing the second impl's header with inference vars, we get: +/// `MyCustomBox: From<&str>` +/// `MyCustomBox: From` +/// +/// This gives us `?E = &str`. We then try to prove the first impl's predicates +/// after negating, giving us `&str: !Error`. This is a negative impl provided by +/// libstd, and therefore we can guarantee for certain that libstd will never add +/// a positive impl for `&str: Error` (without it being a breaking change). +fn impl_intersection_has_negative_obligation( + tcx: TyCtxt<'_>, + impl1_def_id: DefId, + impl2_def_id: DefId, +) -> bool { debug!("negative_impl(impl1_def_id={:?}, impl2_def_id={:?})", impl1_def_id, impl2_def_id); // Create an infcx, taking the predicates of impl1 as assumptions: @@ -307,13 +404,13 @@ fn negative_impl(tcx: TyCtxt<'_>, impl1_def_id: DefId, impl2_def_id: DefId) -> b &infcx, ObligationCause::dummy(), impl_env, - tcx.impl_subject(impl1_def_id).subst_identity(), + tcx.impl_subject(impl1_def_id).instantiate_identity(), ) { Ok(s) => s, Err(err) => { tcx.sess.delay_span_bug( tcx.def_span(impl1_def_id), - format!("failed to fully normalize {:?}: {:?}", impl1_def_id, err), + format!("failed to fully normalize {impl1_def_id:?}: {err:?}"), ); return false; } @@ -321,58 +418,46 @@ fn negative_impl(tcx: TyCtxt<'_>, impl1_def_id: DefId, impl2_def_id: DefId) -> b // Attempt to prove that impl2 applies, given all of the above. let selcx = &mut SelectionContext::new(&infcx); - let impl2_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl2_def_id); - let (subject2, obligations) = - impl_subject_and_oblig(selcx, impl_env, impl2_def_id, impl2_substs, |_, _| { + let impl2_args = infcx.fresh_args_for_item(DUMMY_SP, impl2_def_id); + let (subject2, normalization_obligations) = + impl_subject_and_oblig(selcx, impl_env, impl2_def_id, impl2_args, |_, _| { ObligationCause::dummy() }); - !equate(&infcx, impl_env, subject1, subject2, obligations, impl1_def_id) -} - -fn equate<'tcx>( - infcx: &InferCtxt<'tcx>, - impl_env: ty::ParamEnv<'tcx>, - subject1: ImplSubject<'tcx>, - subject2: ImplSubject<'tcx>, - obligations: impl Iterator>, - body_def_id: DefId, -) -> bool { - // do the impls unify? If not, not disjoint. - let Ok(InferOk { obligations: more_obligations, .. }) = - infcx.at(&ObligationCause::dummy(), impl_env).eq(DefineOpaqueTypes::No,subject1, subject2) + // do the impls unify? If not, then it's not currently possible to prove any + // obligations about their intersection. + let Ok(InferOk { obligations: equate_obligations, .. }) = + infcx.at(&ObligationCause::dummy(), impl_env).eq(DefineOpaqueTypes::No, subject1, subject2) else { debug!("explicit_disjoint: {:?} does not unify with {:?}", subject1, subject2); - return true; + return false; }; - let opt_failing_obligation = obligations - .into_iter() - .chain(more_obligations) - .find(|o| negative_impl_exists(infcx, o, body_def_id)); - - if let Some(failing_obligation) = opt_failing_obligation { - debug!("overlap: obligation unsatisfiable {:?}", failing_obligation); - false - } else { - true + for obligation in normalization_obligations.into_iter().chain(equate_obligations) { + if negative_impl_exists(&infcx, &obligation, impl1_def_id) { + debug!("overlap: obligation unsatisfiable {:?}", obligation); + return true; + } } + + false } -/// Try to prove that a negative impl exist for the given obligation and its super predicates. +/// Try to prove that a negative impl exist for the obligation or its supertraits. +/// +/// If such a negative impl exists, then the obligation definitely must not hold +/// due to coherence, even if it's not necessarily "knowable" in this crate. Any +/// valid impl downstream would not be able to exist due to the overlapping +/// negative impl. #[instrument(level = "debug", skip(infcx))] fn negative_impl_exists<'tcx>( infcx: &InferCtxt<'tcx>, o: &PredicateObligation<'tcx>, body_def_id: DefId, ) -> bool { - if resolve_negative_obligation(infcx.fork(), o, body_def_id) { - return true; - } - // Try to prove a negative obligation exists for super predicates for pred in util::elaborate(infcx.tcx, iter::once(o.predicate)) { - if resolve_negative_obligation(infcx.fork(), &o.with(infcx.tcx, pred), body_def_id) { + if prove_negated_obligation(infcx.fork(), &o.with(infcx.tcx, pred), body_def_id) { return true; } } @@ -381,7 +466,7 @@ fn negative_impl_exists<'tcx>( } #[instrument(level = "debug", skip(infcx))] -fn resolve_negative_obligation<'tcx>( +fn prove_negated_obligation<'tcx>( infcx: InferCtxt<'tcx>, o: &PredicateObligation<'tcx>, body_def_id: DefId, @@ -403,7 +488,10 @@ fn resolve_negative_obligation<'tcx>( let body_def_id = body_def_id.as_local().unwrap_or(CRATE_DEF_ID); let ocx = ObligationCtxt::new(&infcx); - let wf_tys = ocx.assumed_wf_types(param_env, DUMMY_SP, body_def_id); + let Ok(wf_tys) = ocx.assumed_wf_types(param_env, body_def_id) else { + return false; + }; + let outlives_env = OutlivesEnvironment::with_bounds( param_env, infcx.implied_bounds_tys(param_env, body_def_id, wf_tys), @@ -417,22 +505,23 @@ fn resolve_negative_obligation<'tcx>( /// This both checks whether any downstream or sibling crates could /// implement it and whether an upstream crate can add this impl /// without breaking backwards compatibility. -#[instrument(level = "debug", skip(tcx), ret)] -pub fn trait_ref_is_knowable<'tcx>( +#[instrument(level = "debug", skip(tcx, lazily_normalize_ty), ret)] +pub fn trait_ref_is_knowable<'tcx, E: Debug>( tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, -) -> Result<(), Conflict> { + mut lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result, E>, +) -> Result, E> { if Some(trait_ref.def_id) == tcx.lang_items().fn_ptr_trait() { // The only types implementing `FnPtr` are function pointers, // so if there's no impl of `FnPtr` in the current crate, // then such an impl will never be added in the future. - return Ok(()); + return Ok(Ok(())); } - if orphan_check_trait_ref(trait_ref, InCrate::Remote).is_ok() { + if orphan_check_trait_ref(trait_ref, InCrate::Remote, &mut lazily_normalize_ty)?.is_ok() { // A downstream or cousin crate is allowed to implement some // substitution of this trait-ref. - return Err(Conflict::Downstream); + return Ok(Err(Conflict::Downstream)); } if trait_ref_is_local_or_fundamental(tcx, trait_ref) { @@ -441,7 +530,7 @@ pub fn trait_ref_is_knowable<'tcx>( // allowed to implement a substitution of this trait ref, which // means impls could only come from dependencies of this crate, // which we already know about. - return Ok(()); + return Ok(Ok(())); } // This is a remote non-fundamental trait, so if another crate @@ -452,10 +541,10 @@ pub fn trait_ref_is_knowable<'tcx>( // and if we are an intermediate owner, then we don't care // about future-compatibility, which means that we're OK if // we are an owner. - if orphan_check_trait_ref(trait_ref, InCrate::Local).is_ok() { - Ok(()) + if orphan_check_trait_ref(trait_ref, InCrate::Local, &mut lazily_normalize_ty)?.is_ok() { + Ok(Ok(())) } else { - Err(Conflict::Upstream) + Ok(Err(Conflict::Upstream)) } } @@ -482,7 +571,7 @@ pub enum OrphanCheckErr<'tcx> { pub fn orphan_check(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Result<(), OrphanCheckErr<'_>> { // We only except this routine to be invoked on implementations // of a trait, not inherent implementations. - let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().subst_identity(); + let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity(); debug!(?trait_ref); // If the *trait* is local to the crate, ok. @@ -491,7 +580,7 @@ pub fn orphan_check(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Result<(), OrphanChe return Ok(()); } - orphan_check_trait_ref(trait_ref, InCrate::Local) + orphan_check_trait_ref::(trait_ref, InCrate::Local, |ty| Ok(ty)).unwrap() } /// Checks whether a trait-ref is potentially implementable by a crate. @@ -580,11 +669,12 @@ pub fn orphan_check(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Result<(), OrphanChe /// /// Note that this function is never called for types that have both type /// parameters and inference variables. -#[instrument(level = "trace", ret)] -fn orphan_check_trait_ref<'tcx>( +#[instrument(level = "trace", skip(lazily_normalize_ty), ret)] +fn orphan_check_trait_ref<'tcx, E: Debug>( trait_ref: ty::TraitRef<'tcx>, in_crate: InCrate, -) -> Result<(), OrphanCheckErr<'tcx>> { + lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result, E>, +) -> Result>, E> { if trait_ref.has_infer() && trait_ref.has_param() { bug!( "can't orphan check a trait ref with both params and inference variables {:?}", @@ -592,9 +682,10 @@ fn orphan_check_trait_ref<'tcx>( ); } - let mut checker = OrphanChecker::new(in_crate); - match trait_ref.visit_with(&mut checker) { + let mut checker = OrphanChecker::new(in_crate, lazily_normalize_ty); + Ok(match trait_ref.visit_with(&mut checker) { ControlFlow::Continue(()) => Err(OrphanCheckErr::NonLocalInputType(checker.non_local_tys)), + ControlFlow::Break(OrphanCheckEarlyExit::NormalizationFailure(err)) => return Err(err), ControlFlow::Break(OrphanCheckEarlyExit::ParamTy(ty)) => { // Does there exist some local type after the `ParamTy`. checker.search_first_local_ty = true; @@ -607,34 +698,39 @@ fn orphan_check_trait_ref<'tcx>( } } ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(_)) => Ok(()), - } + }) } -struct OrphanChecker<'tcx> { +struct OrphanChecker<'tcx, F> { in_crate: InCrate, in_self_ty: bool, + lazily_normalize_ty: F, /// Ignore orphan check failures and exclusively search for the first /// local type. search_first_local_ty: bool, non_local_tys: Vec<(Ty<'tcx>, bool)>, } -impl<'tcx> OrphanChecker<'tcx> { - fn new(in_crate: InCrate) -> Self { +impl<'tcx, F, E> OrphanChecker<'tcx, F> +where + F: FnOnce(Ty<'tcx>) -> Result, E>, +{ + fn new(in_crate: InCrate, lazily_normalize_ty: F) -> Self { OrphanChecker { in_crate, in_self_ty: true, + lazily_normalize_ty, search_first_local_ty: false, non_local_tys: Vec::new(), } } - fn found_non_local_ty(&mut self, t: Ty<'tcx>) -> ControlFlow> { + fn found_non_local_ty(&mut self, t: Ty<'tcx>) -> ControlFlow> { self.non_local_tys.push((t, self.in_self_ty)); ControlFlow::Continue(()) } - fn found_param_ty(&mut self, t: Ty<'tcx>) -> ControlFlow> { + fn found_param_ty(&mut self, t: Ty<'tcx>) -> ControlFlow> { if self.search_first_local_ty { ControlFlow::Continue(()) } else { @@ -650,18 +746,28 @@ impl<'tcx> OrphanChecker<'tcx> { } } -enum OrphanCheckEarlyExit<'tcx> { +enum OrphanCheckEarlyExit<'tcx, E> { + NormalizationFailure(E), ParamTy(Ty<'tcx>), LocalTy(Ty<'tcx>), } -impl<'tcx> TypeVisitor> for OrphanChecker<'tcx> { - type BreakTy = OrphanCheckEarlyExit<'tcx>; +impl<'tcx, F, E> TypeVisitor> for OrphanChecker<'tcx, F> +where + F: FnMut(Ty<'tcx>) -> Result, E>, +{ + type BreakTy = OrphanCheckEarlyExit<'tcx, E>; fn visit_region(&mut self, _r: ty::Region<'tcx>) -> ControlFlow { ControlFlow::Continue(()) } fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { + // Need to lazily normalize here in with `-Ztrait-solver=next-coherence`. + let ty = match (self.lazily_normalize_ty)(ty) { + Ok(ty) => ty, + Err(err) => return ControlFlow::Break(OrphanCheckEarlyExit::NormalizationFailure(err)), + }; + let result = match *ty.kind() { ty::Bool | ty::Char @@ -676,7 +782,9 @@ impl<'tcx> TypeVisitor> for OrphanChecker<'tcx> { | ty::RawPtr(..) | ty::Never | ty::Tuple(..) - | ty::Alias(ty::Projection | ty::Inherent, ..) => self.found_non_local_ty(ty), + | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) => { + self.found_non_local_ty(ty) + } ty::Param(..) => self.found_param_ty(ty), @@ -689,11 +797,11 @@ impl<'tcx> TypeVisitor> for OrphanChecker<'tcx> { // For fundamental types, we just look inside of them. ty::Ref(_, ty, _) => ty.visit_with(self), - ty::Adt(def, substs) => { + ty::Adt(def, args) => { if self.def_id_is_local(def.did()) { ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) } else if def.is_fundamental() { - substs.visit_with(self) + args.visit_with(self) } else { self.found_non_local_ty(ty) } diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index bd1ea43a78e92..3d0d3812d0c7e 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -176,7 +176,7 @@ fn satisfied_from_param_env<'tcx>( fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow { debug!("is_const_evaluatable: candidate={:?}", c); if self.infcx.probe(|_| { - let ocx = ObligationCtxt::new_in_snapshot(self.infcx); + let ocx = ObligationCtxt::new(self.infcx); ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty()).is_ok() && ocx.eq(&ObligationCause::dummy(), self.param_env, c, self.ct).is_ok() && ocx.select_all_or_error().is_empty() @@ -191,7 +191,7 @@ fn satisfied_from_param_env<'tcx>( if let ty::ConstKind::Expr(e) = c.kind() { e.visit_with(self) } else { - // FIXME(generic_const_exprs): This doesn't recurse into `>::ASSOC`'s substs. + // FIXME(generic_const_exprs): This doesn't recurse into `>::ASSOC`'s args. // This is currently unobservable as `>::ASSOC` creates an anon const // with its own `ConstEvaluatable` bound in the param env which we will visit separately. // @@ -207,7 +207,7 @@ fn satisfied_from_param_env<'tcx>( for pred in param_env.caller_bounds() { match pred.kind().skip_binder() { - ty::PredicateKind::ConstEvaluatable(ce) => { + ty::ClauseKind::ConstEvaluatable(ce) => { let b_ct = tcx.expand_abstract_consts(ce); let mut v = Visitor { ct, infcx, param_env, single_match }; let _ = b_ct.visit_with(&mut v); @@ -219,7 +219,7 @@ fn satisfied_from_param_env<'tcx>( } if let Some(Ok(c)) = single_match { - let ocx = ObligationCtxt::new_in_snapshot(infcx); + let ocx = ObligationCtxt::new(infcx); assert!(ocx.eq(&ObligationCause::dummy(), param_env, c.ty(), ct.ty()).is_ok()); assert!(ocx.eq(&ObligationCause::dummy(), param_env, c, ct).is_ok()); assert!(ocx.select_all_or_error().is_empty()); diff --git a/compiler/rustc_trait_selection/src/traits/engine.rs b/compiler/rustc_trait_selection/src/traits/engine.rs index 000427bbe83ec..820973dc090ad 100644 --- a/compiler/rustc_trait_selection/src/traits/engine.rs +++ b/compiler/rustc_trait_selection/src/traits/engine.rs @@ -1,9 +1,10 @@ use std::cell::RefCell; use std::fmt::Debug; +use super::FulfillmentContext; use super::TraitEngine; -use super::{ChalkFulfillmentContext, FulfillmentContext}; use crate::solve::FulfillmentCtxt as NextFulfillmentCtxt; +use crate::traits::error_reporting::TypeErrCtxtExt; use crate::traits::NormalizeExt; use rustc_data_structures::fx::FxIndexSet; use rustc_errors::ErrorGuaranteed; @@ -14,37 +15,35 @@ use rustc_infer::infer::canonical::{ }; use rustc_infer::infer::outlives::env::OutlivesEnvironment; use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk}; -use rustc_infer::traits::query::Fallible; use rustc_infer::traits::{ FulfillmentError, Obligation, ObligationCause, PredicateObligation, TraitEngineExt as _, }; use rustc_middle::arena::ArenaAllocatable; +use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::error::TypeError; use rustc_middle::ty::ToPredicate; use rustc_middle::ty::TypeFoldable; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::config::TraitSolver; -use rustc_span::Span; pub trait TraitEngineExt<'tcx> { - fn new(tcx: TyCtxt<'tcx>) -> Box; - fn new_in_snapshot(tcx: TyCtxt<'tcx>) -> Box; + fn new(infcx: &InferCtxt<'tcx>) -> Box; } impl<'tcx> TraitEngineExt<'tcx> for dyn TraitEngine<'tcx> { - fn new(tcx: TyCtxt<'tcx>) -> Box { - match tcx.sess.opts.unstable_opts.trait_solver { - TraitSolver::Classic => Box::new(FulfillmentContext::new()), - TraitSolver::Chalk => Box::new(ChalkFulfillmentContext::new()), - TraitSolver::Next => Box::new(NextFulfillmentCtxt::new()), - } - } - - fn new_in_snapshot(tcx: TyCtxt<'tcx>) -> Box { - match tcx.sess.opts.unstable_opts.trait_solver { - TraitSolver::Classic => Box::new(FulfillmentContext::new_in_snapshot()), - TraitSolver::Chalk => Box::new(ChalkFulfillmentContext::new_in_snapshot()), - TraitSolver::Next => Box::new(NextFulfillmentCtxt::new()), + fn new(infcx: &InferCtxt<'tcx>) -> Box { + match (infcx.tcx.sess.opts.unstable_opts.trait_solver, infcx.next_trait_solver()) { + (TraitSolver::Classic, false) | (TraitSolver::NextCoherence, false) => { + Box::new(FulfillmentContext::new(infcx)) + } + (TraitSolver::Next | TraitSolver::NextCoherence, true) => { + Box::new(NextFulfillmentCtxt::new(infcx)) + } + _ => bug!( + "incompatible combination of -Ztrait-solver flag ({:?}) and InferCtxt::next_trait_solver ({:?})", + infcx.tcx.sess.opts.unstable_opts.trait_solver, + infcx.next_trait_solver() + ), } } } @@ -58,11 +57,7 @@ pub struct ObligationCtxt<'a, 'tcx> { impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> { pub fn new(infcx: &'a InferCtxt<'tcx>) -> Self { - Self { infcx, engine: RefCell::new(>::new(infcx.tcx)) } - } - - pub fn new_in_snapshot(infcx: &'a InferCtxt<'tcx>) -> Self { - Self { infcx, engine: RefCell::new(>::new_in_snapshot(infcx.tcx)) } + Self { infcx, engine: RefCell::new(>::new(infcx)) } } pub fn register_obligation(&self, obligation: PredicateObligation<'tcx>) { @@ -102,7 +97,7 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> { cause, recursion_depth: 0, param_env, - predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx), + predicate: ty::Binder::dummy(trait_ref).to_predicate(tcx), }); } @@ -202,17 +197,24 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> { } } + pub fn assumed_wf_types_and_report_errors( + &self, + param_env: ty::ParamEnv<'tcx>, + def_id: LocalDefId, + ) -> Result>, ErrorGuaranteed> { + self.assumed_wf_types(param_env, def_id) + .map_err(|errors| self.infcx.err_ctxt().report_fulfillment_errors(&errors)) + } + pub fn assumed_wf_types( &self, param_env: ty::ParamEnv<'tcx>, - span: Span, def_id: LocalDefId, - ) -> FxIndexSet> { + ) -> Result>, Vec>> { let tcx = self.infcx.tcx; - let assumed_wf_types = tcx.assumed_wf_types(def_id); let mut implied_bounds = FxIndexSet::default(); - let cause = ObligationCause::misc(span, def_id); - for ty in assumed_wf_types { + let mut errors = Vec::new(); + for &(ty, span) in tcx.assumed_wf_types(def_id) { // FIXME(@lcnr): rustc currently does not check wf for types // pre-normalization, meaning that implied bounds are sometimes // incorrect. See #100910 for more details. @@ -225,17 +227,26 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> { // sound and then uncomment this line again. // implied_bounds.insert(ty); - let normalized = self.normalize(&cause, param_env, ty); - implied_bounds.insert(normalized); + let cause = ObligationCause::misc(span, def_id); + match self + .infcx + .at(&cause, param_env) + .deeply_normalize(ty, &mut **self.engine.borrow_mut()) + { + // Insert well-formed types, ignoring duplicates. + Ok(normalized) => drop(implied_bounds.insert(normalized)), + Err(normalization_errors) => errors.extend(normalization_errors), + }; } - implied_bounds + + if errors.is_empty() { Ok(implied_bounds) } else { Err(errors) } } pub fn make_canonicalized_query_response( &self, inference_vars: CanonicalVarValues<'tcx>, answer: T, - ) -> Fallible> + ) -> Result, NoSolution> where T: Debug + TypeFoldable>, Canonical<'tcx, QueryResponse<'tcx, T>>: ArenaAllocatable<'tcx>, diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/ambiguity.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/ambiguity.rs index 7ab652761a410..fd813ca4ecb3f 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/ambiguity.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/ambiguity.rs @@ -1,7 +1,7 @@ use rustc_hir::def_id::DefId; use rustc_infer::infer::{InferCtxt, LateBoundRegionConversionTime}; use rustc_infer::traits::util::elaborate; -use rustc_infer::traits::{Obligation, ObligationCause, TraitObligation}; +use rustc_infer::traits::{Obligation, ObligationCause, PolyTraitObligation}; use rustc_middle::ty; use rustc_span::{Span, DUMMY_SP}; @@ -14,20 +14,20 @@ pub enum Ambiguity { pub fn recompute_applicable_impls<'tcx>( infcx: &InferCtxt<'tcx>, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, ) -> Vec { let tcx = infcx.tcx; let param_env = obligation.param_env; let impl_may_apply = |impl_def_id| { - let ocx = ObligationCtxt::new_in_snapshot(infcx); + let ocx = ObligationCtxt::new(infcx); let placeholder_obligation = infcx.instantiate_binder_with_placeholders(obligation.predicate); let obligation_trait_ref = ocx.normalize(&ObligationCause::dummy(), param_env, placeholder_obligation.trait_ref); - let impl_substs = infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); - let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().subst(tcx, impl_substs); + let impl_args = infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); + let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap().instantiate(tcx, impl_args); let impl_trait_ref = ocx.normalize(&ObligationCause::dummy(), param_env, impl_trait_ref); if let Err(_) = @@ -36,7 +36,7 @@ pub fn recompute_applicable_impls<'tcx>( return false; } - let impl_predicates = tcx.predicates_of(impl_def_id).instantiate(tcx, impl_substs); + let impl_predicates = tcx.predicates_of(impl_def_id).instantiate(tcx, impl_args); ocx.register_obligations(impl_predicates.predicates.iter().map(|&predicate| { Obligation::new(tcx, ObligationCause::dummy(), param_env, predicate) })); @@ -45,7 +45,7 @@ pub fn recompute_applicable_impls<'tcx>( }; let param_env_candidate_may_apply = |poly_trait_predicate: ty::PolyTraitPredicate<'tcx>| { - let ocx = ObligationCtxt::new_in_snapshot(infcx); + let ocx = ObligationCtxt::new(infcx); let placeholder_obligation = infcx.instantiate_binder_with_placeholders(obligation.predicate); let obligation_trait_ref = @@ -84,7 +84,7 @@ pub fn recompute_applicable_impls<'tcx>( tcx.predicates_of(obligation.cause.body_id.to_def_id()).instantiate_identity(tcx); for (pred, span) in elaborate(tcx, predicates.into_iter()) { let kind = pred.kind(); - if let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = kind.skip_binder() + if let ty::ClauseKind::Trait(trait_pred) = kind.skip_binder() && param_env_candidate_may_apply(kind.rebind(trait_pred)) { if kind.rebind(trait_pred.trait_ref) == ty::Binder::dummy(ty::TraitRef::identity(tcx, trait_pred.def_id())) { diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index a10ececbb1ea7..457d5420ca3c9 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -5,13 +5,14 @@ pub mod suggestions; use super::{ FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes, Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt, OutputTypeParameterMismatch, Overflow, - PredicateObligation, SelectionContext, SelectionError, TraitNotObjectSafe, + PredicateObligation, SelectionError, TraitNotObjectSafe, }; +use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch}; use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode}; use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use crate::infer::{self, InferCtxt}; +use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache}; use crate::traits::query::evaluate_obligation::InferCtxtExt as _; -use crate::traits::query::normalize::QueryNormalizeExt as _; use crate::traits::specialize::to_pretty_impl_header; use crate::traits::NormalizeExt; use on_unimplemented::{AppendConstMessage, OnUnimplementedNote, TypeErrCtxtExt as _}; @@ -28,21 +29,24 @@ use rustc_hir::{GenericParam, Item, Node}; use rustc_infer::infer::error_reporting::TypeErrCtxt; use rustc_infer::infer::{InferOk, TypeTrace}; use rustc_middle::traits::select::OverflowError; -use rustc_middle::traits::SelectionOutputTypeParameterMismatch; +use rustc_middle::traits::solve::Goal; +use rustc_middle::traits::{DefiningAnchor, SelectionOutputTypeParameterMismatch}; use rustc_middle::ty::abstract_const::NotConstEvaluatable; use rustc_middle::ty::error::{ExpectedFound, TypeError}; -use rustc_middle::ty::fold::{TypeFolder, TypeSuperFoldable}; +use rustc_middle::ty::fold::{BottomUpFolder, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::print::{with_forced_trimmed_paths, FmtPrinter, Print}; use rustc_middle::ty::{ self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable, TypeVisitable, TypeVisitableExt, }; -use rustc_session::config::TraitSolver; +use rustc_session::config::{DumpSolverProofTree, TraitSolver}; use rustc_session::Limit; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::symbol::sym; use rustc_span::{ExpnKind, Span, DUMMY_SP}; +use std::borrow::Cow; use std::fmt; +use std::io::Write; use std::iter; use std::ops::ControlFlow; use suggestions::TypeErrCtxtExt as _; @@ -59,12 +63,17 @@ pub enum CandidateSimilarity { Fuzzy { ignoring_lifetimes: bool }, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ImplCandidate<'tcx> { pub trait_ref: ty::TraitRef<'tcx>, pub similarity: CandidateSimilarity, } +enum GetSafeTransmuteErrorAndReason { + Silent, + Error { err_msg: String, safe_transmute_explanation: String }, +} + pub trait InferCtxtExt<'tcx> { /// Given some node representing a fn-like thing in the HIR map, /// returns a span and `ArgKind` information that describes the @@ -92,7 +101,6 @@ pub trait InferCtxtExt<'tcx> { &self, param_env: ty::ParamEnv<'tcx>, ty: ty::Binder<'tcx, Ty<'tcx>>, - constness: ty::BoundConstness, polarity: ty::ImplPolarity, ) -> Result<(ty::ClosureKind, ty::Binder<'tcx, Ty<'tcx>>), ()>; } @@ -148,6 +156,12 @@ pub trait TypeErrCtxtExt<'tcx> { root_obligation: &PredicateObligation<'tcx>, error: &SelectionError<'tcx>, ); + + fn report_const_param_not_wf( + &self, + ty: Ty<'tcx>, + obligation: &PredicateObligation<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>; } impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { @@ -212,7 +226,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { let span = variant_data.ctor_hir_id().map_or(DUMMY_SP, |id| hir.span(id)); (span, None, vec![ArgKind::empty(); variant_data.fields().len()]) } - _ => panic!("non-FnLike node found: {:?}", node), + _ => panic!("non-FnLike node found: {node:?}"), }) } @@ -259,10 +273,10 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { found_str, ); - err.span_label(span, format!("expected {} that takes {}", kind, expected_str)); + err.span_label(span, format!("expected {kind} that takes {expected_str}")); if let Some(found_span) = found_span { - err.span_label(found_span, format!("takes {}", found_str)); + err.span_label(found_span, format!("takes {found_str}")); // Suggest to take and ignore the arguments with expected_args_length `_`s if // found arguments is empty (assume the user just wants to ignore args in this case). @@ -275,7 +289,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { "consider changing the closure to take and ignore the expected argument{}", pluralize!(expected_args.len()) ), - format!("|{}|", underscores), + format!("|{underscores}|"), Applicability::MachineApplicable, ); } @@ -290,7 +304,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { err.span_suggestion_verbose( found_span, "change the closure to take multiple arguments instead of a single tuple", - format!("|{}|", sugg), + format!("|{sugg}|"), Applicability::MachineApplicable, ); } @@ -342,7 +356,6 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { &self, param_env: ty::ParamEnv<'tcx>, ty: ty::Binder<'tcx, Ty<'tcx>>, - constness: ty::BoundConstness, polarity: ty::ImplPolarity, ) -> Result<(ty::ClosureKind, ty::Binder<'tcx, Ty<'tcx>>), ()> { self.commit_if_ok(|_| { @@ -358,14 +371,15 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { span: DUMMY_SP, kind: TypeVariableOriginKind::MiscVariable, }); + // FIXME(effects) let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, [ty.skip_binder(), var]); let obligation = Obligation::new( self.tcx, ObligationCause::dummy(), param_env, - ty.rebind(ty::TraitPredicate { trait_ref, constness, polarity }), + ty.rebind(ty::TraitPredicate { trait_ref, polarity }), ); - let ocx = ObligationCtxt::new_in_snapshot(self); + let ocx = ObligationCtxt::new(self); ocx.register_obligation(obligation); if ocx.select_all_or_error().is_empty() { return Ok(( @@ -618,6 +632,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { error: &SelectionError<'tcx>, ) { let tcx = self.tcx; + + if tcx.sess.opts.unstable_opts.dump_solver_proof_tree == DumpSolverProofTree::OnError { + dump_proof_tree(root_obligation, self.infcx); + } + let mut span = obligation.cause.span; // FIXME: statically guarantee this by tainting after the diagnostic is emitted self.set_tainted_by_errors( @@ -640,6 +659,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { span = obligation.cause.span; } } + if let ObligationCauseCode::CompareImplItemObligation { impl_item_def_id, trait_item_def_id, @@ -656,15 +676,21 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { return; } + // Report a const-param specific error + if let ObligationCauseCode::ConstParam(ty) = *obligation.cause.code().peel_derives() + { + self.report_const_param_not_wf(ty, &obligation).emit(); + return; + } + let bound_predicate = obligation.predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_predicate)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_predicate)) => { let trait_predicate = bound_predicate.rebind(trait_predicate); - let mut trait_predicate = self.resolve_vars_if_possible(trait_predicate); + let trait_predicate = self.resolve_vars_if_possible(trait_predicate); - trait_predicate.remap_constness_diag(obligation.param_env); - let predicate_is_const = ty::BoundConstness::ConstIfConst - == trait_predicate.skip_binder().constness; + // FIXME(effects) + let predicate_is_const = false; if self.tcx.sess.has_errors().is_some() && trait_predicate.references_error() @@ -677,9 +703,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { .get_parent_trait_ref(obligation.cause.code()) .map(|(t, s)| { ( - format!(" in `{}`", t), - format!("within `{}`, ", t), - s.map(|s| (format!("within this `{}`", t), s)), + format!(" in `{t}`"), + format!("within `{t}`, "), + s.map(|s| (format!("within this `{t}`"), s)), ) }) .unwrap_or_default(); @@ -724,11 +750,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { == self.tcx.lang_items().transmute_trait() { // Recompute the safe transmute reason and use that for the error reporting - self.get_safe_transmute_error_and_reason( + match self.get_safe_transmute_error_and_reason( obligation.clone(), trait_ref, span, - ) + ) { + GetSafeTransmuteErrorAndReason::Silent => return, + GetSafeTransmuteErrorAndReason::Error { + err_msg, + safe_transmute_explanation, + } => (err_msg, Some(safe_transmute_explanation)), + } } else { (err_msg, None) }; @@ -940,7 +972,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { && self.fallback_has_occurred { let predicate = trait_predicate.map_bound(|trait_pred| { - trait_pred.with_self_ty(self.tcx, self.tcx.mk_unit()) + trait_pred.with_self_ty(self.tcx, Ty::new_unit(self.tcx)) }); let unit_obligation = obligation.with(tcx, predicate); if self.predicate_may_hold(&unit_obligation) { @@ -995,8 +1027,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { span_bug!(span, "coerce requirement gave wrong error: `{:?}`", predicate) } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(..)) => { span_bug!( span, "outlives clauses should not error outside borrowck. obligation: `{:?}`", @@ -1004,7 +1036,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ) } - ty::PredicateKind::Clause(ty::Clause::Projection(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) => { span_bug!( span, "projection clauses should be implied from elsewhere. obligation: `{:?}`", @@ -1017,12 +1049,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { report_object_safety_error(self.tcx, span, trait_def_id, violations) } - ty::PredicateKind::ClosureKind(closure_def_id, closure_substs, kind) => { - let found_kind = self.closure_kind(closure_substs).unwrap(); + ty::PredicateKind::ClosureKind(closure_def_id, closure_args, kind) => { + let found_kind = self.closure_kind(closure_args).unwrap(); self.report_closure_error(&obligation, closure_def_id, found_kind, kind) } - ty::PredicateKind::WellFormed(ty) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(ty)) => { + let ty = self.resolve_vars_if_possible(ty); match self.tcx.sess.opts.unstable_opts.trait_solver { TraitSolver::Classic => { // WF predicates cannot themselves make @@ -1032,18 +1065,18 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // (which may fail). span_bug!(span, "WF predicate not satisfied for {:?}", ty); } - TraitSolver::Chalk | TraitSolver::Next => { + TraitSolver::Next | TraitSolver::NextCoherence => { // FIXME: we'll need a better message which takes into account // which bounds actually failed to hold. self.tcx.sess.struct_span_err( span, - format!("the type `{}` is not well-formed", ty), + format!("the type `{ty}` is not well-formed"), ) } } } - ty::PredicateKind::ConstEvaluatable(..) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) => { // Errors for `ConstEvaluatable` predicates show up as // `SelectionError::ConstEvalFailure`, // not `Unimplemented`. @@ -1067,20 +1100,15 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ty::PredicateKind::Ambiguous => span_bug!(span, "ambiguous"), - ty::PredicateKind::TypeWellFormedFromEnv(..) => span_bug!( - span, - "TypeWellFormedFromEnv predicate should only exist in the environment" - ), - ty::PredicateKind::AliasRelate(..) => span_bug!( span, "AliasRelate predicate should never be the predicate cause of a SelectionError" ), - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { let mut diag = self.tcx.sess.struct_span_err( span, - format!("the constant `{}` is not of type `{}`", ct, ty), + format!("the constant `{ct}` is not of type `{ty}`"), ); self.note_type_err( &mut diag, @@ -1122,6 +1150,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } } + SelectionError::OpaqueTypeAutoTraitLeakageUnknown(def_id) => self.report_opaque_type_auto_trait_leakage( + &obligation, + def_id, + ), + TraitNotObjectSafe(did) => { let violations = self.tcx.object_safety_violations(did); report_object_safety_error(self.tcx, span, did, violations) @@ -1140,16 +1173,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } // Already reported in the query. - SelectionError::NotConstEvaluatable(NotConstEvaluatable::Error(_)) => { - // FIXME(eddyb) remove this once `ErrorGuaranteed` becomes a proof token. - self.tcx.sess.delay_span_bug(span, "`ErrorGuaranteed` without an error"); - return; - } + SelectionError::NotConstEvaluatable(NotConstEvaluatable::Error(_)) | // Already reported. - Overflow(OverflowError::Error(_)) => { - self.tcx.sess.delay_span_bug(span, "`OverflowError` has been reported"); - return; - } + Overflow(OverflowError::Error(_)) => return, + Overflow(_) => { bug!("overflow should be handled before the `report_selection_error` path"); } @@ -1162,6 +1189,102 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self.point_at_returns_when_relevant(&mut err, &obligation); err.emit(); } + + fn report_const_param_not_wf( + &self, + ty: Ty<'tcx>, + obligation: &PredicateObligation<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + let span = obligation.cause.span; + + let mut diag = match ty.kind() { + _ if ty.has_param() => { + span_bug!(span, "const param tys cannot mention other generic parameters"); + } + ty::Float(_) => { + struct_span_err!( + self.tcx.sess, + span, + E0741, + "`{ty}` is forbidden as the type of a const generic parameter", + ) + } + ty::FnPtr(_) => { + struct_span_err!( + self.tcx.sess, + span, + E0741, + "using function pointers as const generic parameters is forbidden", + ) + } + ty::RawPtr(_) => { + struct_span_err!( + self.tcx.sess, + span, + E0741, + "using raw pointers as const generic parameters is forbidden", + ) + } + ty::Adt(def, _) => { + // We should probably see if we're *allowed* to derive `ConstParamTy` on the type... + let mut diag = struct_span_err!( + self.tcx.sess, + span, + E0741, + "`{ty}` must implement `ConstParamTy` to be used as the type of a const generic parameter", + ); + // Only suggest derive if this isn't a derived obligation, + // and the struct is local. + if let Some(span) = self.tcx.hir().span_if_local(def.did()) + && obligation.cause.code().parent().is_none() + { + if ty.is_structural_eq_shallow(self.tcx) { + diag.span_suggestion( + span, + "add `#[derive(ConstParamTy)]` to the struct", + "#[derive(ConstParamTy)]\n", + Applicability::MachineApplicable, + ); + } else { + // FIXME(adt_const_params): We should check there's not already an + // overlapping `Eq`/`PartialEq` impl. + diag.span_suggestion( + span, + "add `#[derive(ConstParamTy, PartialEq, Eq)]` to the struct", + "#[derive(ConstParamTy, PartialEq, Eq)]\n", + Applicability::MachineApplicable, + ); + } + } + diag + } + _ => { + struct_span_err!( + self.tcx.sess, + span, + E0741, + "`{ty}` can't be used as a const parameter type", + ) + } + }; + + let mut code = obligation.cause.code(); + let mut pred = obligation.predicate.to_opt_poly_trait_pred(); + while let Some((next_code, next_pred)) = code.parent() { + if let Some(pred) = pred { + let pred = self.instantiate_binder_with_placeholders(pred); + diag.note(format!( + "`{}` must implement `{}`, but it does not", + pred.self_ty(), + pred.print_modifiers_and_trait_path() + )); + } + code = next_code; + pred = next_pred; + } + + diag + } } trait InferCtxtPrivExt<'tcx> { @@ -1292,7 +1415,7 @@ trait InferCtxtPrivExt<'tcx> { obligation: PredicateObligation<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, span: Span, - ) -> (String, Option); + ) -> GetSafeTransmuteErrorAndReason; fn add_tuple_trait_message( &self, @@ -1345,6 +1468,12 @@ trait InferCtxtPrivExt<'tcx> { terr: TypeError<'tcx>, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>; + fn report_opaque_type_auto_trait_leakage( + &self, + obligation: &PredicateObligation<'tcx>, + def_id: DefId, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>; + fn report_type_parameter_mismatch_error( &self, obligation: &PredicateObligation<'tcx>, @@ -1372,8 +1501,8 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let bound_error = error.kind(); let (cond, error) = match (cond.kind().skip_binder(), bound_error.skip_binder()) { ( - ty::PredicateKind::Clause(ty::Clause::Trait(..)), - ty::PredicateKind::Clause(ty::Clause::Trait(error)), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(..)), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(error)), ) => (cond, bound_error.rebind(error)), _ => { // FIXME: make this work in other cases too. @@ -1383,7 +1512,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { for pred in super::elaborate(self.tcx, std::iter::once(cond)) { let bound_predicate = pred.kind(); - if let ty::PredicateKind::Clause(ty::Clause::Trait(implication)) = + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(implication)) = bound_predicate.skip_binder() { let error = error.to_poly_trait_ref(); @@ -1404,6 +1533,10 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { #[instrument(skip(self), level = "debug")] fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) { + if self.tcx.sess.opts.unstable_opts.dump_solver_proof_tree == DumpSolverProofTree::OnError { + dump_proof_tree(&error.root_obligation, self.infcx); + } + match error.code { FulfillmentErrorCode::CodeSelectionError(ref selection_error) => { self.report_selection_error( @@ -1474,14 +1607,14 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } self.probe(|_| { - let ocx = ObligationCtxt::new_in_snapshot(self); + let ocx = ObligationCtxt::new(self); // try to find the mismatched types to report the error with. // // this can fail if the problem was higher-ranked, in which // cause I have no idea for a good error message. let bound_predicate = predicate.kind(); - let (values, err) = if let ty::PredicateKind::Clause(ty::Clause::Projection(data)) = + let (values, err) = if let ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) = bound_predicate.skip_binder() { let data = self.instantiate_binder_with_fresh_vars( @@ -1490,21 +1623,24 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { bound_predicate.rebind(data), ); let unnormalized_term = match data.term.unpack() { - ty::TermKind::Ty(_) => self - .tcx - .mk_projection(data.projection_ty.def_id, data.projection_ty.substs) - .into(), - ty::TermKind::Const(ct) => self - .tcx - .mk_const( - ty::UnevaluatedConst { - def: data.projection_ty.def_id, - substs: data.projection_ty.substs, - }, - ct.ty(), - ) - .into(), + ty::TermKind::Ty(_) => Ty::new_projection( + self.tcx, + data.projection_ty.def_id, + data.projection_ty.args, + ) + .into(), + ty::TermKind::Const(ct) => ty::Const::new_unevaluated( + self.tcx, + ty::UnevaluatedConst { + def: data.projection_ty.def_id, + args: data.projection_ty.args, + }, + ct.ty(), + ) + .into(), }; + // FIXME(-Ztrait-solver=next): For diagnostic purposes, it would be nice + // to deeply normalize this type. let normalized_term = ocx.normalize(&obligation.cause, obligation.param_env, unnormalized_term); @@ -1564,7 +1700,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut diag = struct_span_err!(self.tcx.sess, obligation.cause.span, E0271, "{msg}"); let secondary_span = (|| { - let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = + let ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj)) = predicate.kind().skip_binder() else { return None; @@ -1602,7 +1738,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { }), ) => Some(( ty.span, - with_forced_trimmed_paths!(format!( + with_forced_trimmed_paths!(Cow::from(format!( "type mismatch resolving `{}`", self.resolve_vars_if_possible(predicate) .print(FmtPrinter::new_with_limit( @@ -1612,7 +1748,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { )) .unwrap() .into_buffer() - )), + ))), )), _ => None, } @@ -1702,12 +1838,13 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ty::Alias(ty::Projection, ..) => Some(12), ty::Alias(ty::Inherent, ..) => Some(13), ty::Alias(ty::Opaque, ..) => Some(14), - ty::Never => Some(15), - ty::Adt(..) => Some(16), - ty::Generator(..) => Some(17), - ty::Foreign(..) => Some(18), - ty::GeneratorWitness(..) => Some(19), - ty::GeneratorWitnessMIR(..) => Some(20), + ty::Alias(ty::Weak, ..) => Some(15), + ty::Never => Some(16), + ty::Adt(..) => Some(17), + ty::Generator(..) => Some(18), + ty::Foreign(..) => Some(19), + ty::GeneratorWitness(..) => Some(20), + ty::GeneratorWitnessMIR(..) => Some(21), ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => None, } } @@ -1772,9 +1909,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { .all_impls(trait_pred.def_id()) .filter_map(|def_id| { if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative - || !trait_pred - .skip_binder() - .is_constness_satisfied_by(self.tcx.constness(def_id)) + || !self.tcx.is_user_visible_dep(def_id.krate) { return None; } @@ -1803,10 +1938,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { other: bool, ) -> bool { let other = if other { "other " } else { "" }; - let report = |mut candidates: Vec>, err: &mut Diagnostic| { - candidates.sort(); - candidates.dedup(); - let len = candidates.len(); + let report = |candidates: Vec>, err: &mut Diagnostic| { if candidates.is_empty() { return false; } @@ -1835,26 +1967,31 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { candidates.iter().map(|c| c.print_only_trait_path().to_string()).collect(); traits.sort(); traits.dedup(); + // FIXME: this could use a better heuristic, like just checking + // that args[1..] is the same. + let all_traits_equal = traits.len() == 1; - let mut candidates: Vec = candidates + let candidates: Vec = candidates .into_iter() .map(|c| { - if traits.len() == 1 { + if all_traits_equal { format!("\n {}", c.self_ty()) } else { - format!("\n {}", c) + format!("\n {c}") } }) .collect(); - candidates.sort(); - candidates.dedup(); let end = if candidates.len() <= 9 { candidates.len() } else { 8 }; err.help(format!( "the following {other}types implement trait `{}`:{}{}", trait_ref.print_only_trait_path(), candidates[..end].join(""), - if len > 9 { format!("\nand {} others", len - 8) } else { String::new() } + if candidates.len() > 9 { + format!("\nand {} others", candidates.len() - 8) + } else { + String::new() + } )); true }; @@ -1868,7 +2005,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Mentioning implementers of `Copy`, `Debug` and friends is not useful. return false; } - let normalized_impl_candidates: Vec<_> = self + let mut impl_candidates: Vec<_> = self .tcx .all_impls(def_id) // Ignore automatically derived impls and `!Trait` impls. @@ -1877,7 +2014,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { || self.tcx.is_automatically_derived(def_id) }) .filter_map(|def_id| self.tcx.impl_trait_ref(def_id)) - .map(ty::EarlyBinder::subst_identity) + .map(ty::EarlyBinder::instantiate_identity) .filter(|trait_ref| { let self_ty = trait_ref.self_ty(); // Avoid mentioning type parameters. @@ -1895,7 +2032,10 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } }) .collect(); - return report(normalized_impl_candidates, err); + + impl_candidates.sort(); + impl_candidates.dedup(); + return report(impl_candidates, err); } // Sort impl candidates so that ordering is consistent for UI tests. @@ -1904,27 +2044,25 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // // Prefer more similar candidates first, then sort lexicographically // by their normalized string representation. - let mut normalized_impl_candidates_and_similarities = impl_candidates + let mut impl_candidates: Vec<_> = impl_candidates .iter() - .copied() - .map(|ImplCandidate { trait_ref, similarity }| { - // FIXME(compiler-errors): This should be using `NormalizeExt::normalize` - let normalized = self - .at(&ObligationCause::dummy(), ty::ParamEnv::empty()) - .query_normalize(trait_ref) - .map_or(trait_ref, |normalized| normalized.value); - (similarity, normalized) + .cloned() + .map(|mut cand| { + // Fold the consts so that they shows up as, e.g., `10` + // instead of `core::::array::{impl#30}::{constant#0}`. + cand.trait_ref = cand.trait_ref.fold_with(&mut BottomUpFolder { + tcx: self.tcx, + ty_op: |ty| ty, + lt_op: |lt| lt, + ct_op: |ct| ct.eval(self.tcx, ty::ParamEnv::empty()), + }); + cand }) - .collect::>(); - normalized_impl_candidates_and_similarities.sort(); - normalized_impl_candidates_and_similarities.dedup(); - - let normalized_impl_candidates = normalized_impl_candidates_and_similarities - .into_iter() - .map(|(_, normalized)| normalized) - .collect::>(); + .collect(); + impl_candidates.sort_by_key(|cand| (cand.similarity, cand.trait_ref)); + impl_candidates.dedup(); - report(normalized_impl_candidates, err) + report(impl_candidates.into_iter().map(|cand| cand.trait_ref).collect(), err) } fn report_similar_impl_candidates_for_root_obligation( @@ -2037,10 +2175,8 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { format!("trait impl{} with same name found", pluralize!(trait_impls.len())), ); let trait_crate = self.tcx.crate_name(trait_with_same_path.krate); - let crate_msg = format!( - "perhaps two different versions of crate `{}` are being used?", - trait_crate - ); + let crate_msg = + format!("perhaps two different versions of crate `{trait_crate}` are being used?"); err.note(crate_msg); suggested = true; } @@ -2075,7 +2211,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let bound_predicate = predicate.kind(); let mut err = match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => { let trait_ref = bound_predicate.rebind(data.trait_ref); debug!(?trait_ref); @@ -2125,7 +2261,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Pick the first substitution that still contains inference variables as the one // we're going to emit an error for. If there are none (see above), fall back to // a more general error. - let subst = data.trait_ref.substs.iter().find(|s| s.has_non_region_infer()); + let subst = data.trait_ref.args.iter().find(|s| s.has_non_region_infer()); let mut err = if let Some(subst) = subst { self.emit_inference_failure_err( @@ -2145,55 +2281,40 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ) }; - let obligation = obligation.with(self.tcx, trait_ref); - let mut selcx = SelectionContext::new(&self); - match selcx.select_from_obligation(&obligation) { - Ok(None) => { - let ambiguities = - ambiguity::recompute_applicable_impls(self.infcx, &obligation); - let has_non_region_infer = trait_ref - .skip_binder() - .substs - .types() - .any(|t| !t.is_ty_or_numeric_infer()); - // It doesn't make sense to talk about applicable impls if there are more - // than a handful of them. - if ambiguities.len() > 1 && ambiguities.len() < 10 && has_non_region_infer { - if self.tainted_by_errors().is_some() && subst.is_none() { - // If `subst.is_none()`, then this is probably two param-env - // candidates or impl candidates that are equal modulo lifetimes. - // Therefore, if we've already emitted an error, just skip this - // one, since it's not particularly actionable. - err.cancel(); - return; - } - self.annotate_source_of_ambiguity(&mut err, &ambiguities, predicate); - } else { - if self.tainted_by_errors().is_some() { - err.cancel(); - return; - } - err.note(format!("cannot satisfy `{}`", predicate)); - let impl_candidates = self.find_similar_impl_candidates( - predicate.to_opt_poly_trait_pred().unwrap(), - ); - if impl_candidates.len() < 10 { - self.report_similar_impl_candidates( - impl_candidates.as_slice(), - trait_ref, - obligation.cause.body_id, - &mut err, - false, - ); - } - } + let ambiguities = ambiguity::recompute_applicable_impls( + self.infcx, + &obligation.with(self.tcx, trait_ref), + ); + let has_non_region_infer = + trait_ref.skip_binder().args.types().any(|t| !t.is_ty_or_numeric_infer()); + // It doesn't make sense to talk about applicable impls if there are more + // than a handful of them. + if ambiguities.len() > 1 && ambiguities.len() < 10 && has_non_region_infer { + if self.tainted_by_errors().is_some() && subst.is_none() { + // If `subst.is_none()`, then this is probably two param-env + // candidates or impl candidates that are equal modulo lifetimes. + // Therefore, if we've already emitted an error, just skip this + // one, since it's not particularly actionable. + err.cancel(); + return; } - _ => { - if self.tainted_by_errors().is_some() { - err.cancel(); - return; - } - err.note(format!("cannot satisfy `{}`", predicate)); + self.annotate_source_of_ambiguity(&mut err, &ambiguities, predicate); + } else { + if self.tainted_by_errors().is_some() { + err.cancel(); + return; + } + err.note(format!("cannot satisfy `{predicate}`")); + let impl_candidates = self + .find_similar_impl_candidates(predicate.to_opt_poly_trait_pred().unwrap()); + if impl_candidates.len() < 10 { + self.report_similar_impl_candidates( + impl_candidates.as_slice(), + trait_ref, + obligation.cause.body_id, + &mut err, + false, + ); } } @@ -2203,7 +2324,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self.suggest_fully_qualified_path(&mut err, def_id, span, trait_ref.def_id()); } - if let Some(ty::subst::GenericArgKind::Type(_)) = subst.map(|subst| subst.unpack()) + if let Some(ty::GenericArgKind::Type(_)) = subst.map(|subst| subst.unpack()) && let Some(body_id) = self.tcx.hir().maybe_body_owned_by(obligation.cause.body_id) { let mut expr_finder = FindExprBySpan::new(span); @@ -2247,7 +2368,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { if let Some(local_def_id) = data.trait_ref.def_id.as_local() && let Some(hir::Node::Item(hir::Item { ident: trait_name, kind: hir::ItemKind::Trait(_, _, _, _, trait_item_refs), .. })) = self.tcx.hir().find_by_def_id(local_def_id) && let Some(method_ref) = trait_item_refs.iter().find(|item_ref| item_ref.ident == *assoc_item_name) { - err.span_label(method_ref.span, format!("`{}::{}` defined here", trait_name, assoc_item_name)); + err.span_label(method_ref.span, format!("`{trait_name}::{assoc_item_name}` defined here")); } err.span_label(span, format!("cannot {verb} associated {noun} of trait")); @@ -2258,17 +2379,18 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { && let Some(impl_def_id) = trait_impls.non_blanket_impls().values().flatten().next() { let non_blanket_impl_count = trait_impls.non_blanket_impls().values().flatten().count(); - let message = if non_blanket_impl_count == 1 { - "use the fully-qualified path to the only available implementation".to_string() - } else { - format!( - "use a fully-qualified path to a specific available implementation ({} found)", - non_blanket_impl_count - ) - }; + // If there is only one implementation of the trait, suggest using it. + // Otherwise, use a placeholder comment for the implementation. + let (message, impl_suggestion) = if non_blanket_impl_count == 1 {( + "use the fully-qualified path to the only available implementation", + format!("<{} as ", self.tcx.type_of(impl_def_id).instantiate_identity()) + )} else { + ("use a fully-qualified path to a specific available implementation", + " InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err } - ty::PredicateKind::WellFormed(arg) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { // Same hacky approach as above to avoid deluging user // with error messages. if arg.references_error() @@ -2329,13 +2451,13 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { true, ) } - ty::PredicateKind::Clause(ty::Clause::Projection(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => { if predicate.references_error() || self.tainted_by_errors().is_some() { return; } let subst = data .projection_ty - .substs + .args .iter() .chain(Some(data.term.into_arg())) .find(|g| g.has_non_region_infer()); @@ -2347,7 +2469,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ErrorCode::E0284, true, ); - err.note(format!("cannot satisfy `{}`", predicate)); + err.note(format!("cannot satisfy `{predicate}`")); err } else { // If we can't find a substitution, just print a generic error @@ -2358,12 +2480,12 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { "type annotations needed: cannot satisfy `{}`", predicate, ); - err.span_label(span, format!("cannot satisfy `{}`", predicate)); + err.span_label(span, format!("cannot satisfy `{predicate}`")); err } } - ty::PredicateKind::ConstEvaluatable(data) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(data)) => { if predicate.references_error() || self.tainted_by_errors().is_some() { return; } @@ -2386,7 +2508,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { "type annotations needed: cannot satisfy `{}`", predicate, ); - err.span_label(span, format!("cannot satisfy `{}`", predicate)); + err.span_label(span, format!("cannot satisfy `{predicate}`")); err } } @@ -2401,7 +2523,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { "type annotations needed: cannot satisfy `{}`", predicate, ); - err.span_label(span, format!("cannot satisfy `{}`", predicate)); + err.span_label(span, format!("cannot satisfy `{predicate}`")); err } }; @@ -2438,7 +2560,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } } } - let mut crate_names: Vec<_> = crates.iter().map(|n| format!("`{}`", n)).collect(); + let mut crate_names: Vec<_> = crates.iter().map(|n| format!("`{n}`")).collect(); crate_names.sort(); crate_names.dedup(); post.sort(); @@ -2465,7 +2587,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { predicate ); let post = if post.len() > 1 || (post.len() == 1 && post[0].contains('\n')) { - format!(":\n{}", post.iter().map(|p| format!("- {}", p)).collect::>().join("\n"),) + format!(":\n{}", post.iter().map(|p| format!("- {p}")).collect::>().join("\n"),) } else if post.len() == 1 { format!(": `{}`", post[0]) } else { @@ -2474,7 +2596,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { match (spans.len(), crates.len(), crate_names.len()) { (0, 0, 0) => { - err.note(format!("cannot satisfy `{}`", predicate)); + err.note(format!("cannot satisfy `{predicate}`")); } (0, _, 1) => { err.note(format!("{} in the `{}` crate{}", msg, crates[0], post,)); @@ -2526,11 +2648,11 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { - if let ty::Param(ty::ParamTy { name, .. }) = *ty.kind() { + if let ty::Param(_) = *ty.kind() { let infcx = self.infcx; *self.var_map.entry(ty).or_insert_with(|| { infcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeParameterDefinition(name, None), + kind: TypeVariableOriginKind::MiscVariable, span: DUMMY_SP, }) }) @@ -2577,10 +2699,17 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err: &mut Diagnostic, obligation: &PredicateObligation<'tcx>, ) { - let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = obligation.predicate.kind().skip_binder() else { return; }; + let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) = + obligation.predicate.kind().skip_binder() + else { + return; + }; let (ObligationCauseCode::BindingObligation(item_def_id, span) - | ObligationCauseCode::ExprBindingObligation(item_def_id, span, ..)) - = *obligation.cause.code().peel_derives() else { return; }; + | ObligationCauseCode::ExprBindingObligation(item_def_id, span, ..)) = + *obligation.cause.code().peel_derives() + else { + return; + }; debug!(?pred, ?item_def_id, ?span); let (Some(node), true) = ( @@ -2638,7 +2767,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err.span_suggestion_verbose( span, "consider relaxing the implicit `Sized` restriction", - format!("{} ?Sized", separator), + format!("{separator} ?Sized"), Applicability::MachineApplicable, ); } @@ -2691,9 +2820,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { if obligated_types.iter().any(|ot| ot == &self_ty) { return true; } - if let ty::Adt(def, substs) = self_ty.kind() - && let [arg] = &substs[..] - && let ty::subst::GenericArgKind::Type(ty) = arg.unpack() + if let ty::Adt(def, args) = self_ty.kind() + && let [arg] = &args[..] + && let ty::GenericArgKind::Type(ty) = arg.unpack() && let ty::Adt(inner_def, _) = ty.kind() && inner_def == def { @@ -2729,7 +2858,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } }) .unwrap_or_else(|| { - format!("the trait bound `{}` is not satisfied{}", trait_predicate, post_message) + format!("the trait bound `{trait_predicate}` is not satisfied{post_message}") }) } @@ -2738,32 +2867,41 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { obligation: PredicateObligation<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, span: Span, - ) -> (String, Option) { + ) -> GetSafeTransmuteErrorAndReason { + use rustc_transmute::Answer; + // Erase regions because layout code doesn't particularly care about regions. let trait_ref = self.tcx.erase_regions(self.tcx.erase_late_bound_regions(trait_ref)); let src_and_dst = rustc_transmute::Types { - dst: trait_ref.substs.type_at(0), - src: trait_ref.substs.type_at(1), + dst: trait_ref.args.type_at(0), + src: trait_ref.args.type_at(1), }; - let scope = trait_ref.substs.type_at(2); - let Some(assume) = - rustc_transmute::Assume::from_const(self.infcx.tcx, obligation.param_env, trait_ref.substs.const_at(3)) else { - span_bug!(span, "Unable to construct rustc_transmute::Assume where it was previously possible"); - }; + let scope = trait_ref.args.type_at(2); + let Some(assume) = rustc_transmute::Assume::from_const( + self.infcx.tcx, + obligation.param_env, + trait_ref.args.const_at(3), + ) else { + span_bug!( + span, + "Unable to construct rustc_transmute::Assume where it was previously possible" + ); + }; + match rustc_transmute::TransmuteTypeEnv::new(self.infcx).is_transmutable( obligation.cause, src_and_dst, scope, assume, ) { - rustc_transmute::Answer::No(reason) => { - let dst = trait_ref.substs.type_at(0); - let src = trait_ref.substs.type_at(1); - let custom_err_msg = format!( + Answer::No(reason) => { + let dst = trait_ref.args.type_at(0); + let src = trait_ref.args.type_at(1); + let err_msg = format!( "`{src}` cannot be safely transmuted into `{dst}` in the defining scope of `{scope}`" ); - let reason_msg = match reason { + let safe_transmute_explanation = match reason { rustc_transmute::Reason::SrcIsUnspecified => { format!("`{src}` does not have a well-specified layout") } @@ -2779,19 +2917,39 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { rustc_transmute::Reason::DstIsPrivate => format!( "`{dst}` is or contains a type or field that is not visible in that scope" ), - // FIXME(bryangarza): Include the number of bytes of src and dst rustc_transmute::Reason::DstIsTooBig => { format!("The size of `{src}` is smaller than the size of `{dst}`") } + rustc_transmute::Reason::DstHasStricterAlignment { + src_min_align, + dst_min_align, + } => { + format!( + "The minimum alignment of `{src}` ({src_min_align}) should be greater than that of `{dst}` ({dst_min_align})" + ) + } + rustc_transmute::Reason::DstIsMoreUnique => { + format!("`{src}` is a shared reference, but `{dst}` is a unique reference") + } + // Already reported by rustc + rustc_transmute::Reason::TypeError => { + return GetSafeTransmuteErrorAndReason::Silent; + } + rustc_transmute::Reason::SrcLayoutUnknown => { + format!("`{src}` has an unknown layout") + } + rustc_transmute::Reason::DstLayoutUnknown => { + format!("`{dst}` has an unknown layout") + } }; - (custom_err_msg, Some(reason_msg)) + GetSafeTransmuteErrorAndReason::Error { err_msg, safe_transmute_explanation } } // Should never get a Yes at this point! We already ran it before, and did not get a Yes. - rustc_transmute::Answer::Yes => span_bug!( + Answer::Yes => span_bug!( span, "Inconsistent rustc_transmute::is_transmutable(...) result, got Yes", ), - _ => span_bug!(span, "Unsupported rustc_transmute::Reason variant"), + other => span_bug!(span, "Unsupported rustc_transmute::Answer variant: `{other:?}`"), } } @@ -2830,12 +2988,19 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { unsatisfied_const: bool, ) { let body_def_id = obligation.cause.body_id; + let span = if let ObligationCauseCode::BinOp { rhs_span: Some(rhs_span), .. } = + obligation.cause.code() + { + *rhs_span + } else { + span + }; + // Try to report a help message if is_fn_trait && let Ok((implemented_kind, params)) = self.type_implements_fn_trait( obligation.param_env, trait_ref.self_ty(), - trait_predicate.skip_binder().constness, trait_predicate.skip_binder().polarity, ) { @@ -2869,8 +3034,9 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self.report_similar_impl_candidates_for_root_obligation(&obligation, *trait_predicate, body_def_id, err); } - self.maybe_suggest_convert_to_slice( + self.suggest_convert_to_slice( err, + obligation, trait_ref, impl_candidates.as_slice(), span, @@ -2906,7 +3072,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Note any argument mismatches let given_ty = params.skip_binder(); - let expected_ty = trait_ref.skip_binder().substs.type_at(1); + let expected_ty = trait_ref.skip_binder().args.type_at(1); if let ty::Tuple(given) = given_ty.kind() && let ty::Tuple(expected) = expected_ty.kind() { @@ -2937,34 +3103,14 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { fn maybe_add_note_for_unsatisfied_const( &self, - obligation: &PredicateObligation<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, - trait_predicate: &ty::PolyTraitPredicate<'tcx>, - err: &mut Diagnostic, - span: Span, + _obligation: &PredicateObligation<'tcx>, + _trait_ref: ty::PolyTraitRef<'tcx>, + _trait_predicate: &ty::PolyTraitPredicate<'tcx>, + _err: &mut Diagnostic, + _span: Span, ) -> UnsatisfiedConst { - let mut unsatisfied_const = UnsatisfiedConst(false); - if trait_predicate.is_const_if_const() && obligation.param_env.is_const() { - let non_const_predicate = trait_ref.without_const(); - let non_const_obligation = Obligation { - cause: obligation.cause.clone(), - param_env: obligation.param_env.without_const(), - predicate: non_const_predicate.to_predicate(self.tcx), - recursion_depth: obligation.recursion_depth, - }; - if self.predicate_may_hold(&non_const_obligation) { - unsatisfied_const = UnsatisfiedConst(true); - err.span_note( - span, - format!( - "the trait `{}` is implemented for `{}`, \ - but that implementation is not `const`", - non_const_predicate.print_modifiers_and_trait_path(), - trait_ref.skip_binder().self_ty(), - ), - ); - } - } + let unsatisfied_const = UnsatisfiedConst(false); + // FIXME(effects) unsatisfied_const } @@ -2976,24 +3122,15 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { kind: ty::ClosureKind, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { let closure_span = self.tcx.def_span(closure_def_id); - let mut err = struct_span_err!( - self.tcx.sess, - closure_span, - E0525, - "expected a closure that implements the `{}` trait, \ - but this closure only implements `{}`", - kind, - found_kind - ); - err.span_label( + let mut err = ClosureKindMismatch { closure_span, - format!("this closure implements `{}`, not `{}`", found_kind, kind), - ); - err.span_label( - obligation.cause.span, - format!("the requirement to implement `{}` derives from here", kind), - ); + expected: kind, + found: found_kind, + cause_span: obligation.cause.span, + fn_once_label: None, + fn_mut_label: None, + }; // Additional context information explaining why the closure only implements // a particular trait. @@ -3001,30 +3138,22 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let hir_id = self.tcx.hir().local_def_id_to_hir_id(closure_def_id.expect_local()); match (found_kind, typeck_results.closure_kind_origins().get(hir_id)) { (ty::ClosureKind::FnOnce, Some((span, place))) => { - err.span_label( - *span, - format!( - "closure is `FnOnce` because it moves the \ - variable `{}` out of its environment", - ty::place_to_string_for_capture(self.tcx, place) - ), - ); + err.fn_once_label = Some(ClosureFnOnceLabel { + span: *span, + place: ty::place_to_string_for_capture(self.tcx, &place), + }) } (ty::ClosureKind::FnMut, Some((span, place))) => { - err.span_label( - *span, - format!( - "closure is `FnMut` because it mutates the \ - variable `{}` here", - ty::place_to_string_for_capture(self.tcx, place) - ), - ); + err.fn_mut_label = Some(ClosureFnMutLabel { + span: *span, + place: ty::place_to_string_for_capture(self.tcx, &place), + }) } _ => {} } } - err + self.tcx.sess.create_err(err) } fn report_type_parameter_mismatch_cyclic_type_error( @@ -3049,6 +3178,39 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ) } + fn report_opaque_type_auto_trait_leakage( + &self, + obligation: &PredicateObligation<'tcx>, + def_id: DefId, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + let name = match self.tcx.opaque_type_origin(def_id.expect_local()) { + hir::OpaqueTyOrigin::FnReturn(_) | hir::OpaqueTyOrigin::AsyncFn(_) => { + format!("opaque type") + } + hir::OpaqueTyOrigin::TyAlias { .. } => { + format!("`{}`", self.tcx.def_path_debug_str(def_id)) + } + }; + let mut err = self.tcx.sess.struct_span_err( + obligation.cause.span, + format!("cannot check whether the hidden type of {name} satisfies auto traits"), + ); + err.span_note(self.tcx.def_span(def_id), "opaque type is declared here"); + match self.defining_use_anchor { + DefiningAnchor::Bubble | DefiningAnchor::Error => {} + DefiningAnchor::Bind(bind) => { + err.span_note( + self.tcx.def_ident_span(bind).unwrap_or_else(|| self.tcx.def_span(bind)), + "this item depends on auto traits of the hidden type, \ + but may also be registering the hidden type. \ + This is not supported right now. \ + You can try moving the opaque type and the item that actually registers a hidden type into a new submodule".to_string(), + ); + } + }; + err + } + fn report_type_parameter_mismatch_error( &self, obligation: &PredicateObligation<'tcx>, @@ -3088,7 +3250,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut not_tupled = false; - let found = match found_trait_ref.skip_binder().substs.type_at(1).kind() { + let found = match found_trait_ref.skip_binder().args.type_at(1).kind() { ty::Tuple(ref tys) => vec![ArgKind::empty(); tys.len()], _ => { not_tupled = true; @@ -3096,7 +3258,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } }; - let expected_ty = expected_trait_ref.skip_binder().substs.type_at(1); + let expected_ty = expected_trait_ref.skip_binder().args.type_at(1); let expected = match expected_ty.kind() { ty::Tuple(ref tys) => { tys.iter().map(|t| ArgKind::from_expected_ty(t, Some(span))).collect() @@ -3178,7 +3340,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } match obligation.predicate.kind().skip_binder() { - ty::PredicateKind::ConstEvaluatable(ct) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(ct)) => { let ty::ConstKind::Unevaluated(uv) = ct.kind() else { bug!("const evaluatable failed for non-unevaluated const `{ct:?}`"); }; @@ -3186,8 +3348,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let const_span = self.tcx.def_span(uv.def); match self.tcx.sess.source_map().span_to_snippet(const_span) { Ok(snippet) => err.help(format!( - "try adding a `where` bound using this expression: `where [(); {}]:`", - snippet + "try adding a `where` bound using this expression: `where [(); {snippet}]:`" )), _ => err.help("consider adding a `where` bound using this expression"), }; @@ -3360,3 +3521,16 @@ pub enum DefIdOrName { DefId(DefId), Name(&'static str), } + +pub fn dump_proof_tree<'tcx>(o: &Obligation<'tcx, ty::Predicate<'tcx>>, infcx: &InferCtxt<'tcx>) { + infcx.probe(|_| { + let goal = Goal { predicate: o.predicate, param_env: o.param_env }; + let tree = infcx + .evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No)) + .1 + .expect("proof tree should have been generated"); + let mut lock = std::io::stdout().lock(); + let _ = lock.write_fmt(format_args!("{tree:?}\n")); + let _ = lock.flush(); + }); +} diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs index 10bd027b6848a..0e73bad19185b 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs @@ -6,7 +6,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_errors::{struct_span_err, ErrorGuaranteed}; use rustc_hir as hir; use rustc_hir::def_id::DefId; -use rustc_middle::ty::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt}; use rustc_parse_format::{ParseMode, Parser, Piece, Position}; use rustc_span::symbol::{kw, sym, Symbol}; @@ -25,7 +25,7 @@ pub trait TypeErrCtxtExt<'tcx> { &self, trait_ref: ty::PolyTraitRef<'tcx>, obligation: &PredicateObligation<'tcx>, - ) -> Option<(DefId, SubstsRef<'tcx>)>; + ) -> Option<(DefId, GenericArgsRef<'tcx>)>; /*private*/ fn describe_enclosure(&self, hir_id: hir::HirId) -> Option<&'static str>; @@ -41,7 +41,6 @@ pub trait TypeErrCtxtExt<'tcx> { static ALLOWED_FORMAT_SYMBOLS: &[Symbol] = &[ kw::SelfUpper, sym::ItemContext, - sym::from_method, sym::from_desugaring, sym::direct, sym::cause, @@ -57,7 +56,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { &self, trait_ref: ty::PolyTraitRef<'tcx>, obligation: &PredicateObligation<'tcx>, - ) -> Option<(DefId, SubstsRef<'tcx>)> { + ) -> Option<(DefId, GenericArgsRef<'tcx>)> { let tcx = self.tcx; let param_env = obligation.param_env; let trait_ref = self.instantiate_binder_with_placeholders(trait_ref); @@ -67,26 +66,23 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut fuzzy_match_impls = vec![]; self.tcx.for_each_relevant_impl(trait_ref.def_id, trait_self_ty, |def_id| { - let impl_substs = self.fresh_substs_for_item(obligation.cause.span, def_id); - let impl_trait_ref = tcx.impl_trait_ref(def_id).unwrap().subst(tcx, impl_substs); + let impl_args = self.fresh_args_for_item(obligation.cause.span, def_id); + let impl_trait_ref = tcx.impl_trait_ref(def_id).unwrap().instantiate(tcx, impl_args); let impl_self_ty = impl_trait_ref.self_ty(); if self.can_eq(param_env, trait_self_ty, impl_self_ty) { - self_match_impls.push((def_id, impl_substs)); + self_match_impls.push((def_id, impl_args)); - if iter::zip( - trait_ref.substs.types().skip(1), - impl_trait_ref.substs.types().skip(1), - ) - .all(|(u, v)| self.fuzzy_match_tys(u, v, false).is_some()) + if iter::zip(trait_ref.args.types().skip(1), impl_trait_ref.args.types().skip(1)) + .all(|(u, v)| self.fuzzy_match_tys(u, v, false).is_some()) { - fuzzy_match_impls.push((def_id, impl_substs)); + fuzzy_match_impls.push((def_id, impl_args)); } } }); - let impl_def_id_and_substs = if self_match_impls.len() == 1 { + let impl_def_id_and_args = if self_match_impls.len() == 1 { self_match_impls[0] } else if fuzzy_match_impls.len() == 1 { fuzzy_match_impls[0] @@ -94,8 +90,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { return None; }; - tcx.has_attr(impl_def_id_and_substs.0, sym::rustc_on_unimplemented) - .then_some(impl_def_id_and_substs) + tcx.has_attr(impl_def_id_and_args.0, sym::rustc_on_unimplemented) + .then_some(impl_def_id_and_args) } /// Used to set on_unimplemented's `ItemContext` @@ -144,9 +140,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { trait_ref: ty::PolyTraitRef<'tcx>, obligation: &PredicateObligation<'tcx>, ) -> OnUnimplementedNote { - let (def_id, substs) = self + let (def_id, args) = self .impl_similar_to(trait_ref, obligation) - .unwrap_or_else(|| (trait_ref.def_id(), trait_ref.skip_binder().substs)); + .unwrap_or_else(|| (trait_ref.def_id(), trait_ref.skip_binder().args)); let trait_ref = trait_ref.skip_binder(); let mut flags = vec![]; @@ -172,26 +168,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } } - if let ObligationCauseCode::ItemObligation(item) - | ObligationCauseCode::BindingObligation(item, _) - | ObligationCauseCode::ExprItemObligation(item, ..) - | ObligationCauseCode::ExprBindingObligation(item, ..) = *obligation.cause.code() - { - // FIXME: maybe also have some way of handling methods - // from other traits? That would require name resolution, - // which we might want to be some sort of hygienic. - // - // Currently I'm leaving it for what I need for `try`. - if self.tcx.trait_of_item(item) == Some(trait_ref.def_id) { - let method = self.tcx.item_name(item); - flags.push((sym::from_method, None)); - flags.push((sym::from_method, Some(method.to_string()))); - } - } - if let Some(k) = obligation.cause.span.desugaring_kind() { flags.push((sym::from_desugaring, None)); - flags.push((sym::from_desugaring, Some(format!("{:?}", k)))); + flags.push((sym::from_desugaring, Some(format!("{k:?}")))); } if let ObligationCauseCode::MainFunctionType = obligation.cause.code() { @@ -210,14 +189,14 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // signature with no type arguments resolved flags.push(( sym::_Self, - Some(self.tcx.type_of(def.did()).subst_identity().to_string()), + Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()), )); } for param in generics.params.iter() { let value = match param.kind { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { - substs[param.index as usize].to_string() + args[param.index as usize].to_string() } GenericParamDefKind::Lifetime => continue, }; @@ -225,13 +204,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { flags.push((name, Some(value))); if let GenericParamDefKind::Type { .. } = param.kind { - let param_ty = substs[param.index as usize].expect_ty(); + let param_ty = args[param.index as usize].expect_ty(); if let Some(def) = param_ty.ty_adt_def() { // We also want to be able to select the parameter's // original signature with no type arguments resolved flags.push(( name, - Some(self.tcx.type_of(def.did()).subst_identity().to_string()), + Some(self.tcx.type_of(def.did()).instantiate_identity().to_string()), )); } } @@ -267,7 +246,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // signature with no type arguments resolved flags.push(( sym::_Self, - Some(format!("[{}]", self.tcx.type_of(def.did()).subst_identity())), + Some(format!("[{}]", self.tcx.type_of(def.did()).instantiate_identity())), )); } if aty.is_integral() { @@ -278,15 +257,15 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]` if let ty::Array(aty, len) = self_ty.kind() { flags.push((sym::_Self, Some("[]".to_string()))); - let len = len.kind().try_to_value().and_then(|v| v.try_to_target_usize(self.tcx)); - flags.push((sym::_Self, Some(format!("[{}; _]", aty)))); + let len = len.try_to_value().and_then(|v| v.try_to_target_usize(self.tcx)); + flags.push((sym::_Self, Some(format!("[{aty}; _]")))); if let Some(n) = len { - flags.push((sym::_Self, Some(format!("[{}; {}]", aty, n)))); + flags.push((sym::_Self, Some(format!("[{aty}; {n}]")))); } if let Some(def) = aty.ty_adt_def() { // We also want to be able to select the array's type's original // signature with no type arguments resolved - let def_ty = self.tcx.type_of(def.did()).subst_identity(); + let def_ty = self.tcx.type_of(def.did()).instantiate_identity(); flags.push((sym::_Self, Some(format!("[{def_ty}; _]")))); if let Some(n) = len { flags.push((sym::_Self, Some(format!("[{def_ty}; {n}]")))); @@ -350,18 +329,13 @@ pub struct OnUnimplementedNote { } /// Append a message for `~const Trait` errors. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum AppendConstMessage { + #[default] Default, Custom(Symbol), } -impl Default for AppendConstMessage { - fn default() -> Self { - AppendConstMessage::Default - } -} - impl<'tcx> OnUnimplementedDirective { fn parse( tcx: TyCtxt<'tcx>, @@ -605,7 +579,7 @@ impl<'tcx> OnUnimplementedFormatString { "there is no parameter `{}` on {}", s, if trait_def_id == item_def_id { - format!("trait `{}`", trait_name) + format!("trait `{trait_name}`") } else { "impl".to_string() } @@ -647,7 +621,7 @@ impl<'tcx> OnUnimplementedFormatString { .filter_map(|param| { let value = match param.kind { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { - trait_ref.substs[param.index as usize].to_string() + trait_ref.args[param.index as usize].to_string() } GenericParamDefKind::Lifetime => return None, }; @@ -672,7 +646,7 @@ impl<'tcx> OnUnimplementedFormatString { None => { if let Some(val) = options.get(&s) { val - } else if s == sym::from_desugaring || s == sym::from_method { + } else if s == sym::from_desugaring { // don't break messages using these two arguments incorrectly &empty_string } else if s == sym::ItemContext { diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 82bad96ea42db..611ec6b00ef60 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -5,6 +5,7 @@ use super::{ PredicateObligation, }; +use crate::errors; use crate::infer::InferCtxt; use crate::traits::{NormalizeExt, ObligationCtxt}; @@ -13,7 +14,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::{ error_code, pluralize, struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, - ErrorGuaranteed, MultiSpan, Style, + ErrorGuaranteed, MultiSpan, Style, SuggestionStyle, }; use rustc_hir as hir; use rustc_hir::def::DefKind; @@ -30,7 +31,7 @@ use rustc_middle::hir::map; use rustc_middle::ty::error::TypeError::{self, Sorts}; use rustc_middle::ty::{ self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, - GeneratorDiagnosticData, GeneratorInteriorTypeCause, InferTy, InternalSubsts, IsSuggestable, + GeneratorDiagnosticData, GeneratorInteriorTypeCause, GenericArgs, InferTy, IsSuggestable, ToPredicate, Ty, TyCtxt, TypeAndMut, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, TypeckResults, }; @@ -38,8 +39,8 @@ use rustc_span::def_id::LocalDefId; use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span, DUMMY_SP}; use rustc_target::spec::abi; +use std::borrow::Cow; use std::iter; -use std::ops::Deref; use super::InferCtxtPrivExt; use crate::infer::InferCtxtExt as _; @@ -186,7 +187,12 @@ pub trait TypeErrCtxtExt<'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, ) -> bool; - fn get_closure_name(&self, def_id: DefId, err: &mut Diagnostic, msg: &str) -> Option; + fn get_closure_name( + &self, + def_id: DefId, + err: &mut Diagnostic, + msg: Cow<'static, str>, + ) -> Option; fn suggest_fn_call( &self, @@ -356,6 +362,15 @@ pub trait TypeErrCtxtExt<'tcx> { err: &mut Diagnostic, trait_pred: ty::PolyTraitPredicate<'tcx>, ); + + fn suggest_option_method_if_applicable( + &self, + failed_pred: ty::Predicate<'tcx>, + param_env: ty::ParamEnv<'tcx>, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + ); + fn note_function_argument_obligation( &self, body_id: LocalDefId, @@ -383,9 +398,10 @@ pub trait TypeErrCtxtExt<'tcx> { param_env: ty::ParamEnv<'tcx>, ) -> Vec))>>; - fn maybe_suggest_convert_to_slice( + fn suggest_convert_to_slice( &self, err: &mut Diagnostic, + obligation: &PredicateObligation<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, candidate_impls: &[ImplCandidate<'tcx>], span: Span, @@ -420,7 +436,7 @@ fn suggest_restriction<'tcx>( ) { if hir_generics.where_clause_span.from_expansion() || hir_generics.where_clause_span.desugaring_kind().is_some() - || projection.is_some_and(|projection| tcx.opt_rpitit_info(projection.def_id).is_some()) + || projection.is_some_and(|projection| tcx.is_impl_trait_in_trait(projection.def_id)) { return; } @@ -464,13 +480,13 @@ fn suggest_restriction<'tcx>( .visit_ty(input); } // The type param `T: Trait` we will suggest to introduce. - let type_param = format!("{}: {}", type_param_name, bound_str); + let type_param = format!("{type_param_name}: {bound_str}"); let mut sugg = vec![ if let Some(span) = hir_generics.span_for_param_suggestion() { - (span, format!(", {}", type_param)) + (span, format!(", {type_param}")) } else { - (hir_generics.span, format!("<{}>", type_param)) + (hir_generics.span, format!("<{type_param}>")) }, // `fn foo(t: impl Trait)` // ^ suggest `where ::A: Bound` @@ -515,7 +531,7 @@ fn suggest_restriction<'tcx>( err.span_suggestion_verbose( sp, - format!("consider further restricting {}", msg), + format!("consider further restricting {msg}"), suggestion, Applicability::MachineApplicable, ); @@ -639,6 +655,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { | hir::ItemKind::Impl(hir::Impl { generics, .. }) | hir::ItemKind::Fn(_, generics, _) | hir::ItemKind::TyAlias(_, generics) + | hir::ItemKind::Const(_, generics, _) | hir::ItemKind::TraitAlias(generics, _) | hir::ItemKind::OpaqueTy(hir::OpaqueTy { generics, .. }), .. @@ -655,7 +672,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // this that we do in `suggest_restriction` and pull the // `impl Trait` into a new generic if it shows up somewhere // else in the predicate. - if !trait_pred.skip_binder().trait_ref.substs[1..] + if !trait_pred.skip_binder().trait_ref.args[1..] .iter() .all(|g| g.is_suggestable(self.tcx, false)) { @@ -678,7 +695,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { term ); } else { - constraint.push_str(&format!("<{} = {}>", name, term)); + constraint.push_str(&format!("<{name} = {term}>")); } } @@ -704,6 +721,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { | hir::ItemKind::Impl(hir::Impl { generics, .. }) | hir::ItemKind::Fn(_, generics, _) | hir::ItemKind::TyAlias(_, generics) + | hir::ItemKind::Const(_, generics, _) | hir::ItemKind::TraitAlias(generics, _) | hir::ItemKind::OpaqueTy(hir::OpaqueTy { generics, .. }), .. @@ -737,14 +755,20 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, ) -> bool { // It only make sense when suggesting dereferences for arguments - let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, call_hir_id, .. } = obligation.cause.code() - else { return false; }; - let Some(typeck_results) = &self.typeck_results - else { return false; }; - let hir::Node::Expr(expr) = self.tcx.hir().get(*arg_hir_id) - else { return false; }; - let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr) - else { return false; }; + let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, call_hir_id, .. } = + obligation.cause.code() + else { + return false; + }; + let Some(typeck_results) = &self.typeck_results else { + return false; + }; + let hir::Node::Expr(expr) = self.tcx.hir().get(*arg_hir_id) else { + return false; + }; + let Some(arg_ty) = typeck_results.expr_ty_adjusted_opt(expr) else { + return false; + }; let span = obligation.cause.span; let mut real_trait_pred = trait_pred; @@ -755,23 +779,19 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { real_trait_pred = parent_trait_pred; } - let real_ty = real_trait_pred.self_ty(); // We `erase_late_bound_regions` here because `make_subregion` does not handle // `ReLateBound`, and we don't particularly care about the regions. - if !self.can_eq( - obligation.param_env, - self.tcx.erase_late_bound_regions(real_ty), - arg_ty, - ) { + let real_ty = self.tcx.erase_late_bound_regions(real_trait_pred.self_ty()); + if !self.can_eq(obligation.param_env, real_ty, arg_ty) { continue; } - if let ty::Ref(region, base_ty, mutbl) = *real_ty.skip_binder().kind() { + if let ty::Ref(region, base_ty, mutbl) = *real_ty.kind() { let autoderef = (self.autoderef_steps)(base_ty); if let Some(steps) = autoderef.into_iter().enumerate().find_map(|(steps, (ty, obligations))| { // Re-add the `&` - let ty = self.tcx.mk_ref(region, TypeAndMut { ty, mutbl }); + let ty = Ty::new_ref(self.tcx, region, TypeAndMut { ty, mutbl }); // Remapping bound vars here let real_trait_pred_and_ty = @@ -857,7 +877,12 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { /// Given a closure's `DefId`, return the given name of the closure. /// /// This doesn't account for reassignments, but it's only used for suggestions. - fn get_closure_name(&self, def_id: DefId, err: &mut Diagnostic, msg: &str) -> Option { + fn get_closure_name( + &self, + def_id: DefId, + err: &mut Diagnostic, + msg: Cow<'static, str>, + ) -> Option { let get_name = |err: &mut Diagnostic, kind: &hir::PatKind<'_>| -> Option { // Get the local name of this closure. This can be inaccurate because // of the possibility of reassignment, but this should be good enough. @@ -900,7 +925,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { return false; } - if let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = obligation.predicate.kind().skip_binder() + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) = obligation.predicate.kind().skip_binder() && Some(trait_pred.def_id()) == self.tcx.lang_items().sized_trait() { // Don't suggest calling to turn an unsized type into a sized type @@ -913,11 +938,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { trait_pred.self_ty(), ); - let Some((def_id_or_name, output, inputs)) = self.extract_callable_info( - obligation.cause.body_id, - obligation.param_env, - self_ty, - ) else { return false; }; + let Some((def_id_or_name, output, inputs)) = + self.extract_callable_info(obligation.cause.body_id, obligation.param_env, self_ty) + else { + return false; + }; // Remapping bound vars here let trait_pred_and_self = trait_pred.map_bound(|trait_pred| (trait_pred, output)); @@ -934,17 +959,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let msg = match def_id_or_name { DefIdOrName::DefId(def_id) => match self.tcx.def_kind(def_id) { DefKind::Ctor(CtorOf::Struct, _) => { - "use parentheses to construct this tuple struct".to_string() + Cow::from("use parentheses to construct this tuple struct") } DefKind::Ctor(CtorOf::Variant, _) => { - "use parentheses to construct this tuple variant".to_string() + Cow::from("use parentheses to construct this tuple variant") } - kind => format!( + kind => Cow::from(format!( "use parentheses to call this {}", self.tcx.def_kind_descr(kind, def_id) - ), + )), }, - DefIdOrName::Name(name) => format!("use parentheses to call this {name}"), + DefIdOrName::Name(name) => Cow::from(format!("use parentheses to call this {name}")), }; let args = inputs @@ -979,7 +1004,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { .. })) => { err.span_label(*fn_decl_span, "consider calling this closure"); - let Some(name) = self.get_closure_name(def_id, err, &msg) else { + let Some(name) = self.get_closure_name(def_id, err, msg.clone()) else { return false; }; name.to_string() @@ -992,7 +1017,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let name = self.tcx.def_path_str(def_id); err.span_label( self.tcx.def_span(def_id), - format!("consider calling the constructor for `{}`", name), + format!("consider calling the constructor for `{name}`"), ); name } @@ -1015,26 +1040,40 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { span.remove_mark(); } let mut expr_finder = FindExprBySpan::new(span); - let Some(body_id) = self.tcx.hir().maybe_body_owned_by(obligation.cause.body_id) else { return; }; + let Some(body_id) = self.tcx.hir().maybe_body_owned_by(obligation.cause.body_id) else { + return; + }; let body = self.tcx.hir().body(body_id); expr_finder.visit_expr(body.value); - let Some(expr) = expr_finder.result else { return; }; - let Some(typeck) = &self.typeck_results else { return; }; - let Some(ty) = typeck.expr_ty_adjusted_opt(expr) else { return; }; + let Some(expr) = expr_finder.result else { + return; + }; + let Some(typeck) = &self.typeck_results else { + return; + }; + let Some(ty) = typeck.expr_ty_adjusted_opt(expr) else { + return; + }; if !ty.is_unit() { return; }; - let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { return; }; - let hir::def::Res::Local(hir_id) = path.res else { return; }; + let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind else { + return; + }; + let hir::def::Res::Local(hir_id) = path.res else { + return; + }; let Some(hir::Node::Pat(pat)) = self.tcx.hir().find(hir_id) else { return; }; - let Some(hir::Node::Local(hir::Local { - ty: None, - init: Some(init), - .. - })) = self.tcx.hir().find_parent(pat.hir_id) else { return; }; - let hir::ExprKind::Block(block, None) = init.kind else { return; }; + let Some(hir::Node::Local(hir::Local { ty: None, init: Some(init), .. })) = + self.tcx.hir().find_parent(pat.hir_id) + else { + return; + }; + let hir::ExprKind::Block(block, None) = init.kind else { + return; + }; if block.expr.is_some() { return; } @@ -1042,7 +1081,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err.span_label(block.span, "this empty block is missing a tail expression"); return; }; - let hir::StmtKind::Semi(tail_expr) = stmt.kind else { return; }; + let hir::StmtKind::Semi(tail_expr) = stmt.kind else { + return; + }; let Some(ty) = typeck.expr_ty_opt(tail_expr) else { err.span_label(block.span, "this block is missing a tail expression"); return; @@ -1072,12 +1113,18 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ) -> bool { let self_ty = self.resolve_vars_if_possible(trait_pred.self_ty()); let ty = self.instantiate_binder_with_placeholders(self_ty); - let Some(generics) = self.tcx.hir().get_generics(obligation.cause.body_id) else { return false }; + let Some(generics) = self.tcx.hir().get_generics(obligation.cause.body_id) else { + return false; + }; let ty::Ref(_, inner_ty, hir::Mutability::Not) = ty.kind() else { return false }; let ty::Param(param) = inner_ty.kind() else { return false }; - let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, .. } = obligation.cause.code() else { return false }; + let ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, .. } = + obligation.cause.code() + else { + return false; + }; let arg_node = self.tcx.hir().get(*arg_hir_id); - let Node::Expr(Expr { kind: hir::ExprKind::Path(_), ..}) = arg_node else { return false }; + let Node::Expr(Expr { kind: hir::ExprKind::Path(_), .. }) = arg_node else { return false }; let clone_trait = self.tcx.require_lang_item(LangItem::Clone, None); let has_clone = |ty| { @@ -1123,24 +1170,33 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { found: Ty<'tcx>, ) -> Option<(DefIdOrName, Ty<'tcx>, Vec>)> { // Autoderef is useful here because sometimes we box callables, etc. - let Some((def_id_or_name, output, inputs)) = (self.autoderef_steps)(found).into_iter().find_map(|(found, _)| { - match *found.kind() { - ty::FnPtr(fn_sig) => - Some((DefIdOrName::Name("function pointer"), fn_sig.output(), fn_sig.inputs())), - ty::FnDef(def_id, _) => { - let fn_sig = found.fn_sig(self.tcx); - Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) - } - ty::Closure(def_id, substs) => { - let fn_sig = substs.as_closure().sig(); - Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs().map_bound(|inputs| &inputs[1..]))) - } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - self.tcx.item_bounds(def_id).subst(self.tcx, substs).iter().find_map(|pred| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() + let Some((def_id_or_name, output, inputs)) = + (self.autoderef_steps)(found).into_iter().find_map(|(found, _)| { + match *found.kind() { + ty::FnPtr(fn_sig) => Some(( + DefIdOrName::Name("function pointer"), + fn_sig.output(), + fn_sig.inputs(), + )), + ty::FnDef(def_id, _) => { + let fn_sig = found.fn_sig(self.tcx); + Some((DefIdOrName::DefId(def_id), fn_sig.output(), fn_sig.inputs())) + } + ty::Closure(def_id, args) => { + let fn_sig = args.as_closure().sig(); + Some(( + DefIdOrName::DefId(def_id), + fn_sig.output(), + fn_sig.inputs().map_bound(|inputs| &inputs[1..]), + )) + } + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { + self.tcx.item_bounds(def_id).instantiate(self.tcx, args).iter().find_map( + |pred| { + if let ty::ClauseKind::Projection(proj) = pred.kind().skip_binder() && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + // args tuple will always be args[1] + && let ty::Tuple(args) = proj.projection_ty.args.type_at(1).kind() { Some(( DefIdOrName::DefId(def_id), @@ -1150,14 +1206,15 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } else { None } - }) - } - ty::Dynamic(data, _, ty::Dyn) => { - data.iter().find_map(|pred| { - if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() + }, + ) + } + ty::Dynamic(data, _, ty::Dyn) => { + data.iter().find_map(|pred| { + if let ty::ExistentialPredicate::Projection(proj) = pred.skip_binder() && Some(proj.def_id) == self.tcx.lang_items().fn_once_output() - // for existential projection, substs are shifted over by 1 - && let ty::Tuple(args) = proj.substs.type_at(0).kind() + // for existential projection, args are shifted over by 1 + && let ty::Tuple(args) = proj.args.type_at(0).kind() { Some(( DefIdOrName::Name("trait object"), @@ -1167,11 +1224,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } else { None } - }) - } - ty::Param(param) => { - let generics = self.tcx.generics_of(body_id); - let name = if generics.count() > param.index as usize + }) + } + ty::Param(param) => { + let generics = self.tcx.generics_of(body_id); + let name = if generics.count() > param.index as usize && let def = generics.param_at(param.index as usize, self.tcx) && matches!(def.kind, ty::GenericParamDefKind::Type { .. }) && def.name == param.name @@ -1180,12 +1237,12 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } else { DefIdOrName::Name("type parameter") }; - param_env.caller_bounds().iter().find_map(|pred| { - if let ty::PredicateKind::Clause(ty::Clause::Projection(proj)) = pred.kind().skip_binder() + param_env.caller_bounds().iter().find_map(|pred| { + if let ty::ClauseKind::Projection(proj) = pred.kind().skip_binder() && Some(proj.projection_ty.def_id) == self.tcx.lang_items().fn_once_output() && proj.projection_ty.self_ty() == found - // args tuple will always be substs[1] - && let ty::Tuple(args) = proj.projection_ty.substs.type_at(1).kind() + // args tuple will always be args[1] + && let ty::Tuple(args) = proj.projection_ty.args.type_at(1).kind() { Some(( name, @@ -1195,11 +1252,14 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } else { None } - }) + }) + } + _ => None, } - _ => None, - } - }) else { return None; }; + }) + else { + return None; + }; let output = self.instantiate_binder_with_fresh_vars( DUMMY_SP, @@ -1278,13 +1338,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let trait_pred_and_imm_ref = old_pred.map_bound(|trait_pred| { ( trait_pred, - self.tcx.mk_imm_ref(self.tcx.lifetimes.re_static, trait_pred.self_ty()), + Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_static, trait_pred.self_ty()), ) }); let trait_pred_and_mut_ref = old_pred.map_bound(|trait_pred| { ( trait_pred, - self.tcx.mk_mut_ref(self.tcx.lifetimes.re_static, trait_pred.self_ty()), + Ty::new_mut_ref(self.tcx, self.tcx.lifetimes.re_static, trait_pred.self_ty()), ) }); @@ -1336,12 +1396,12 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Because of this, we modify the error to refer to the original obligation and // return early in the caller. - let msg = format!("the trait bound `{}` is not satisfied", old_pred); + let msg = format!("the trait bound `{old_pred}` is not satisfied"); if has_custom_message { err.note(msg); } else { err.message = - vec![(rustc_errors::DiagnosticMessage::Str(msg), Style::NoStyle)]; + vec![(rustc_errors::DiagnosticMessage::from(msg), Style::NoStyle)]; } err.span_label( span, @@ -1369,30 +1429,34 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Issue #109436, we need to add parentheses properly for method calls // for example, `foo.into()` should be `(&foo).into()` - if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet( - self.tcx.sess.source_map().span_look_ahead(span, Some("."), Some(50)), - ) { - if snippet == "." { - err.multipart_suggestion_verbose( - sugg_msg, - vec![ - (span.shrink_to_lo(), format!("({}", sugg_prefix)), - (span.shrink_to_hi(), ")".to_string()), - ], - Applicability::MaybeIncorrect, - ); - return true; - } + if let Some(_) = + self.tcx.sess.source_map().span_look_ahead(span, ".", Some(50)) + { + err.multipart_suggestion_verbose( + sugg_msg, + vec![ + (span.shrink_to_lo(), format!("({sugg_prefix}")), + (span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MaybeIncorrect, + ); + return true; } // Issue #104961, we need to add parentheses properly for compound expressions // for example, `x.starts_with("hi".to_string() + "you")` // should be `x.starts_with(&("hi".to_string() + "you"))` - let Some(body_id) = self.tcx.hir().maybe_body_owned_by(obligation.cause.body_id) else { return false; }; + let Some(body_id) = + self.tcx.hir().maybe_body_owned_by(obligation.cause.body_id) + else { + return false; + }; let body = self.tcx.hir().body(body_id); let mut expr_finder = FindExprBySpan::new(span); expr_finder.visit_expr(body.value); - let Some(expr) = expr_finder.result else { return false; }; + let Some(expr) = expr_finder.result else { + return false; + }; let needs_parens = match expr.kind { // parenthesize if needed (Issue #46756) hir::ExprKind::Cast(_, _) | hir::ExprKind::Binary(_, _, _) => true, @@ -1403,10 +1467,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let span = if needs_parens { span } else { span.shrink_to_lo() }; let suggestions = if !needs_parens { - vec![(span.shrink_to_lo(), format!("{}", sugg_prefix))] + vec![(span.shrink_to_lo(), sugg_prefix)] } else { vec![ - (span.shrink_to_lo(), format!("{}(", sugg_prefix)), + (span.shrink_to_lo(), format!("{sugg_prefix}(")), (span.shrink_to_hi(), ")".to_string()), ] }; @@ -1443,9 +1507,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self_ty: Ty<'tcx>, target_ty: Ty<'tcx>, ) { - let ty::Ref(_, object_ty, hir::Mutability::Not) = target_ty.kind() else { return; }; - let ty::Dynamic(predicates, _, ty::Dyn) = object_ty.kind() else { return; }; - let self_ref_ty = self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, self_ty); + let ty::Ref(_, object_ty, hir::Mutability::Not) = target_ty.kind() else { + return; + }; + let ty::Dynamic(predicates, _, ty::Dyn) = object_ty.kind() else { + return; + }; + let self_ref_ty = Ty::new_imm_ref(self.tcx, self.tcx.lifetimes.re_erased, self_ty); for predicate in predicates.iter() { if !self.predicate_must_hold_modulo_regions( @@ -1546,7 +1614,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } // Maybe suggest removal of borrows from expressions, like in `for i in &&&foo {}`. - let Some(mut expr) = expr_finder.result else { return false; }; + let Some(mut expr) = expr_finder.result else { + return false; + }; let mut count = 0; let mut suggestions = vec![]; // Skipping binder here, remapping below @@ -1619,20 +1689,19 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } // FIXME: account for associated `async fn`s. if let hir::Expr { span, kind: hir::ExprKind::Call(base, _), .. } = expr { - if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) = obligation.predicate.kind().skip_binder() { err.span_label(*span, format!("this call returns `{}`", pred.self_ty())); } if let Some(typeck_results) = &self.typeck_results && let ty = typeck_results.expr_ty_adjusted(base) - && let ty::FnDef(def_id, _substs) = ty.kind() + && let ty::FnDef(def_id, _args) = ty.kind() && let Some(hir::Node::Item(hir::Item { ident, span, vis_span, .. })) = hir.get_if_local(*def_id) { let msg = format!( - "alternatively, consider making `fn {}` asynchronous", - ident + "alternatively, consider making `fn {ident}` asynchronous" ); if vis_span.is_empty() { err.span_suggestion_verbose( @@ -1686,8 +1755,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { if let ty::Ref(region, t_type, mutability) = *trait_pred.skip_binder().self_ty().kind() { let suggested_ty = match mutability { - hir::Mutability::Mut => self.tcx.mk_imm_ref(region, t_type), - hir::Mutability::Not => self.tcx.mk_mut_ref(region, t_type), + hir::Mutability::Mut => Ty::new_imm_ref(self.tcx, region, t_type), + hir::Mutability::Not => Ty::new_mut_ref(self.tcx, region, t_type), }; // Remapping bound vars here @@ -1778,7 +1847,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { fn return_type_span(&self, obligation: &PredicateObligation<'tcx>) -> Option { let hir = self.tcx.hir(); - let Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, ..), .. })) = hir.find_by_def_id(obligation.cause.body_id) else { + let Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, ..), .. })) = + hir.find_by_def_id(obligation.cause.body_id) + else { return None; }; @@ -1881,10 +1952,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // don't print out the [type error] here err.delay_as_bug(); } else { - err.span_label( - expr.span, - format!("this returned value is of type `{}`", ty), - ); + err.span_label(expr.span, format!("this returned value is of type `{ty}`")); } } } @@ -1905,7 +1973,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { infcx: &InferCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, ) -> Ty<'tcx> { - let inputs = trait_ref.skip_binder().substs.type_at(1); + let inputs = trait_ref.skip_binder().args.type_at(1); let sig = match inputs.kind() { ty::Tuple(inputs) if infcx.tcx.is_fn_trait(trait_ref.def_id()) => { infcx.tcx.mk_fn_sig( @@ -1931,7 +1999,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ), }; - infcx.tcx.mk_fn_ptr(trait_ref.rebind(sig)) + Ty::new_fn_ptr(infcx.tcx, trait_ref.rebind(sig)) } let argument_kind = match expected.skip_binder().self_ty().kind() { @@ -1981,21 +2049,21 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = cause && let predicates = self.tcx.predicates_of(def_id).instantiate_identity(self.tcx) && let Some(pred) = predicates.predicates.get(*idx) - && let ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) = pred.kind().skip_binder() + && let ty::ClauseKind::Trait(trait_pred) = pred.kind().skip_binder() && self.tcx.is_fn_trait(trait_pred.def_id()) { let expected_self = self.tcx.anonymize_bound_vars(pred.kind().rebind(trait_pred.self_ty())); - let expected_substs = self + let expected_args = self .tcx - .anonymize_bound_vars(pred.kind().rebind(trait_pred.trait_ref.substs)); + .anonymize_bound_vars(pred.kind().rebind(trait_pred.trait_ref.args)); // Find another predicate whose self-type is equal to the expected self type, - // but whose substs don't match. + // but whose args don't match. let other_pred = predicates.into_iter() .enumerate() .find(|(other_idx, (pred, _))| match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(trait_pred)) + ty::ClauseKind::Trait(trait_pred) if self.tcx.is_fn_trait(trait_pred.def_id()) && other_idx != idx // Make sure that the self type matches @@ -2004,10 +2072,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { == self.tcx.anonymize_bound_vars( pred.kind().rebind(trait_pred.self_ty()), ) - // But the substs don't match (i.e. incompatible args) - && expected_substs + // But the args don't match (i.e. incompatible args) + && expected_args != self.tcx.anonymize_bound_vars( - pred.kind().rebind(trait_pred.trait_ref.substs), + pred.kind().rebind(trait_pred.trait_ref.args), ) => { true @@ -2121,7 +2189,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // bound was introduced. At least one generator should be present for this diagnostic to be // modified. let (mut trait_ref, mut target_ty) = match obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(p)) => (Some(p), Some(p.self_ty())), + ty::PredicateKind::Clause(ty::ClauseKind::Trait(p)) => (Some(p), Some(p.self_ty())), _ => (None, None), }; let mut generator = None; @@ -2202,7 +2270,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // Only continue if a generator was found. debug!(?generator, ?trait_ref, ?target_ty); - let (Some(generator_did), Some(trait_ref), Some(target_ty)) = (generator, trait_ref, target_ty) else { + let (Some(generator_did), Some(trait_ref), Some(target_ty)) = + (generator, trait_ref, target_ty) + else { return false; }; @@ -2383,8 +2453,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err.clear_code(); err.set_primary_message(format!( - "{} cannot be {} between threads safely", - future_or_generator, trait_verb + "{future_or_generator} cannot be {trait_verb} between threads safely" )); let original_span = err.span.primary_span().unwrap(); @@ -2393,7 +2462,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let message = outer_generator .and_then(|generator_did| { Some(match self.tcx.generator_kind(generator_did).unwrap() { - GeneratorKind::Gen => format!("generator is not {}", trait_name), + GeneratorKind::Gen => format!("generator is not {trait_name}"), GeneratorKind::Async(AsyncGeneratorKind::Fn) => self .tcx .parent(generator_did) @@ -2401,73 +2470,73 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { .map(|parent_did| hir.local_def_id_to_hir_id(parent_did)) .and_then(|parent_hir_id| hir.opt_name(parent_hir_id)) .map(|name| { - format!("future returned by `{}` is not {}", name, trait_name) + format!("future returned by `{name}` is not {trait_name}") })?, GeneratorKind::Async(AsyncGeneratorKind::Block) => { - format!("future created by async block is not {}", trait_name) + format!("future created by async block is not {trait_name}") } GeneratorKind::Async(AsyncGeneratorKind::Closure) => { - format!("future created by async closure is not {}", trait_name) + format!("future created by async closure is not {trait_name}") } }) }) - .unwrap_or_else(|| format!("{} is not {}", future_or_generator, trait_name)); + .unwrap_or_else(|| format!("{future_or_generator} is not {trait_name}")); span.push_span_label(original_span, message); err.set_span(span); - format!("is not {}", trait_name) + format!("is not {trait_name}") } else { format!("does not implement `{}`", trait_pred.print_modifiers_and_trait_path()) }; - let mut explain_yield = - |interior_span: Span, yield_span: Span, scope_span: Option| { - let mut span = MultiSpan::from_span(yield_span); - let snippet = match source_map.span_to_snippet(interior_span) { - // #70935: If snippet contains newlines, display "the value" instead - // so that we do not emit complex diagnostics. - Ok(snippet) if !snippet.contains('\n') => format!("`{}`", snippet), - _ => "the value".to_string(), - }; - // note: future is not `Send` as this value is used across an await - // --> $DIR/issue-70935-complex-spans.rs:13:9 - // | - // LL | baz(|| async { - // | ______________- - // | | - // | | - // LL | | foo(tx.clone()); - // LL | | }).await; - // | | - ^^^^^^ await occurs here, with value maybe used later - // | |__________| - // | has type `closure` which is not `Send` - // note: value is later dropped here - // LL | | }).await; - // | | ^ - // - span.push_span_label( - yield_span, - format!("{} occurs here, with {} maybe used later", await_or_yield, snippet), - ); - span.push_span_label( - interior_span, - format!("has type `{}` which {}", target_ty, trait_explanation), - ); - if let Some(scope_span) = scope_span { - let scope_span = source_map.end_point(scope_span); + let mut explain_yield = |interior_span: Span, + yield_span: Span, + scope_span: Option| { + let mut span = MultiSpan::from_span(yield_span); + let snippet = match source_map.span_to_snippet(interior_span) { + // #70935: If snippet contains newlines, display "the value" instead + // so that we do not emit complex diagnostics. + Ok(snippet) if !snippet.contains('\n') => format!("`{snippet}`"), + _ => "the value".to_string(), + }; + // note: future is not `Send` as this value is used across an await + // --> $DIR/issue-70935-complex-spans.rs:13:9 + // | + // LL | baz(|| async { + // | ______________- + // | | + // | | + // LL | | foo(tx.clone()); + // LL | | }).await; + // | | - ^^^^^^ await occurs here, with value maybe used later + // | |__________| + // | has type `closure` which is not `Send` + // note: value is later dropped here + // LL | | }).await; + // | | ^ + // + span.push_span_label( + yield_span, + format!("{await_or_yield} occurs here, with {snippet} maybe used later"), + ); + span.push_span_label( + interior_span, + format!("has type `{target_ty}` which {trait_explanation}"), + ); + if let Some(scope_span) = scope_span { + let scope_span = source_map.end_point(scope_span); - let msg = format!("{} is later dropped here", snippet); - span.push_span_label(scope_span, msg); - } - err.span_note( + let msg = format!("{snippet} is later dropped here"); + span.push_span_label(scope_span, msg); + } + err.span_note( span, format!( - "{} {} as this value is used across {}", - future_or_generator, trait_explanation, an_await_or_yield + "{future_or_generator} {trait_explanation} as this value is used across {an_await_or_yield}" ), ); - }; + }; match interior_or_upvar_span { GeneratorInteriorOrUpvar::Interior(interior_span, interior_extra_info) => { if let Some((scope_span, yield_span, expr, from_awaited_ty)) = interior_extra_info { @@ -2477,15 +2546,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { span.push_span_label( await_span, format!( - "await occurs here on type `{}`, which {}", - target_ty, trait_explanation + "await occurs here on type `{target_ty}`, which {trait_explanation}" ), ); err.span_note( span, format!( - "future {not_trait} as it awaits another future which {not_trait}", - not_trait = trait_explanation + "future {trait_explanation} as it awaits another future which {trait_explanation}" ), ); } else { @@ -2568,18 +2635,16 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let ref_kind = if is_mut { "&mut" } else { "&" }; ( format!( - "has type `{}` which {}, because `{}` is not `{}`", - target_ty, trait_explanation, ref_ty, ref_ty_trait + "has type `{target_ty}` which {trait_explanation}, because `{ref_ty}` is not `{ref_ty_trait}`" ), format!( - "captured value {} because `{}` references cannot be sent unless their referent is `{}`", - trait_explanation, ref_kind, ref_ty_trait + "captured value {trait_explanation} because `{ref_kind}` references cannot be sent unless their referent is `{ref_ty_trait}`" ), ) } None => ( - format!("has type `{}` which {}", target_ty, trait_explanation), - format!("captured value {}", trait_explanation), + format!("has type `{target_ty}` which {trait_explanation}"), + format!("captured value {trait_explanation}"), ), }; @@ -2635,7 +2700,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { | ObligationCauseCode::MatchImpl(..) | ObligationCauseCode::ReturnType | ObligationCauseCode::ReturnValue(_) - | ObligationCauseCode::BlockTailExpression(_) + | ObligationCauseCode::BlockTailExpression(..) | ObligationCauseCode::AwaitableExpr(_) | ObligationCauseCode::ForLoopIterator | ObligationCauseCode::QuestionMark @@ -2643,8 +2708,15 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { | ObligationCauseCode::LetElse | ObligationCauseCode::BinOp { .. } | ObligationCauseCode::AscribeUserTypeProvePredicate(..) - | ObligationCauseCode::RustCall - | ObligationCauseCode::DropImpl => {} + | ObligationCauseCode::DropImpl + | ObligationCauseCode::ConstParam(_) => {} + ObligationCauseCode::RustCall => { + if let Some(pred) = predicate.to_opt_poly_trait_pred() + && Some(pred.def_id()) == self.tcx.lang_items().sized_trait() + { + err.note("argument required to be sized due to `extern \"rust-call\"` ABI"); + } + } ObligationCauseCode::SliceOrArrayElem => { err.note("slice and array elements must have `Sized` type"); } @@ -2661,8 +2733,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } ObligationCauseCode::ObjectTypeBound(object_ty, region) => { err.note(format!( - "required so that the lifetime bound of `{}` for `{}` is satisfied", - region, object_ty, + "required so that the lifetime bound of `{region}` for `{object_ty}` is satisfied", )); } ObligationCauseCode::ItemObligation(_) @@ -2697,6 +2768,32 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let msg = format!("required by this bound in `{short_item_name}`"); multispan.push_span_label(span, msg); err.span_note(multispan, descr); + if let ty::PredicateKind::Clause(clause) = predicate.kind().skip_binder() + && let ty::ClauseKind::Trait(trait_pred) = clause + { + let def_id = trait_pred.def_id(); + let visible_item = if let Some(local) = def_id.as_local() { + // Check for local traits being reachable. + let vis = &self.tcx.resolutions(()).effective_visibilities; + // Account for non-`pub` traits in the root of the local crate. + let is_locally_reachable = self.tcx.parent(def_id).is_crate_root(); + vis.is_reachable(local) || is_locally_reachable + } else { + // Check for foreign traits being reachable. + self.tcx.visible_parent_map(()).get(&def_id).is_some() + }; + if let DefKind::Trait = tcx.def_kind(item_def_id) && !visible_item { + // FIXME(estebank): extend this to search for all the types that do + // implement this trait and list them. + err.note(format!( + "`{short_item_name}` is a \"sealed trait\", because to implement \ + it you also need to implement `{}`, which is not accessible; \ + this is usually done to force you to use one of the provided \ + types that already implement it", + with_no_trimmed_paths!(tcx.def_path_str(def_id)), + )); + } + } } else { err.span_note(tcx.def_span(item_def_id), descr); } @@ -2755,7 +2852,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err.note("all local variables must have a statically known size"); } Some(Node::Local(hir::Local { - init: Some(hir::Expr { kind: hir::ExprKind::Index(_, _), span, .. }), + init: Some(hir::Expr { kind: hir::ExprKind::Index(..), span, .. }), .. })) => { // When encountering an assignment of an unsized trait, like @@ -2786,10 +2883,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err.help("unsized locals are gated as an unstable feature"); } } - ObligationCauseCode::SizedArgumentType(sp) => { - if let Some(span) = sp { + ObligationCauseCode::SizedArgumentType(ty_span) => { + if let Some(span) = ty_span { if let ty::PredicateKind::Clause(clause) = predicate.kind().skip_binder() - && let ty::Clause::Trait(trait_pred) = clause + && let ty::ClauseKind::Trait(trait_pred) = clause && let ty::Dynamic(..) = trait_pred.self_ty().kind() { let span = if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) @@ -2956,11 +3053,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut msg = "required because it captures the following types: ".to_owned(); for ty in bound_tys.skip_binder() { - with_forced_trimmed_paths!(write!(msg, "`{}`, ", ty).unwrap()); + with_forced_trimmed_paths!(write!(msg, "`{ty}`, ").unwrap()); } - err.note(msg.trim_end_matches(", ")) + err.note(msg.trim_end_matches(", ").to_string()) } - ty::GeneratorWitnessMIR(def_id, substs) => { + ty::GeneratorWitnessMIR(def_id, args) => { use std::fmt::Write; // FIXME: this is kind of an unusual format for rustc, can we make it more clear? @@ -2969,10 +3066,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut msg = "required because it captures the following types: ".to_owned(); for bty in tcx.generator_hidden_types(*def_id) { - let ty = bty.subst(tcx, substs); - write!(msg, "`{}`, ", ty).unwrap(); + let ty = bty.instantiate(tcx, args); + write!(msg, "`{ty}`, ").unwrap(); } - err.note(msg.trim_end_matches(", ")) + err.note(msg.trim_end_matches(", ").to_string()) } ty::Generator(def_id, _, _) => { let sp = self.tcx.def_span(def_id); @@ -3029,7 +3126,6 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ObligationCauseCode::ImplDerivedObligation(ref data) => { let mut parent_trait_pred = self.resolve_vars_if_possible(data.derived.parent_trait_pred); - parent_trait_pred.remap_constness_diag(param_env); let parent_def_id = parent_trait_pred.def_id(); let (self_ty, file) = self.tcx.short_ty_string(parent_trait_pred.skip_binder().self_ty()); @@ -3171,6 +3267,29 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ) }); } + ObligationCauseCode::TypeAlias(ref nested, span, def_id) => { + // #74711: avoid a stack overflow + ensure_sufficient_stack(|| { + self.note_obligation_cause_code( + body_id, + err, + predicate, + param_env, + nested, + obligated_types, + seen_requirements, + ) + }); + let mut multispan = MultiSpan::from(span); + multispan.push_span_label(span, "required by this bound"); + err.span_note( + multispan, + format!( + "required by a bound on the type alias `{}`", + self.infcx.tcx.item_name(def_id) + ), + ); + } ObligationCauseCode::FunctionArgumentObligation { arg_hir_id, call_hir_id, @@ -3271,9 +3390,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; // `::Output` let projection_ty = trait_pred.map_bound(|trait_pred| { - self.tcx.mk_projection( + Ty::new_projection( + self.tcx, item_def_id, - // Future::Output has no substs + // Future::Output has no args [trait_pred.self_ty()], ) }); @@ -3314,7 +3434,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { _ => return, }; if let ty::Float(_) = trait_ref.skip_binder().self_ty().kind() - && let ty::Infer(InferTy::IntVar(_)) = trait_ref.skip_binder().substs.type_at(1).kind() + && let ty::Infer(InferTy::IntVar(_)) = trait_ref.skip_binder().args.type_at(1).kind() { err.span_suggestion_verbose( rhs_span.shrink_to_hi(), @@ -3334,15 +3454,15 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let Some(diagnostic_name) = self.tcx.get_diagnostic_name(trait_pred.def_id()) else { return; }; - let (adt, substs) = match trait_pred.skip_binder().self_ty().kind() { - ty::Adt(adt, substs) if adt.did().is_local() => (adt, substs), + let (adt, args) = match trait_pred.skip_binder().self_ty().kind() { + ty::Adt(adt, args) if adt.did().is_local() => (adt, args), _ => return, }; let can_derive = { let is_derivable_trait = match diagnostic_name { sym::Default => !adt.is_enum(), sym::PartialEq | sym::PartialOrd => { - let rhs_ty = trait_pred.skip_binder().trait_ref.substs.type_at(1); + let rhs_ty = trait_pred.skip_binder().trait_ref.args.type_at(1); trait_pred.skip_binder().self_ty() == rhs_ty } sym::Eq | sym::Ord | sym::Clone | sym::Copy | sym::Hash | sym::Debug => true, @@ -3351,8 +3471,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { is_derivable_trait && // Ensure all fields impl the trait. adt.all_fields().all(|field| { - let field_ty = field.ty(self.tcx, substs); - let trait_substs = match diagnostic_name { + let field_ty = field.ty(self.tcx, args); + let trait_args = match diagnostic_name { sym::PartialEq | sym::PartialOrd => { Some(field_ty) } @@ -3361,7 +3481,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let trait_pred = trait_pred.map_bound_ref(|tr| ty::TraitPredicate { trait_ref: ty::TraitRef::new(self.tcx, trait_pred.def_id(), - [field_ty].into_iter().chain(trait_substs), + [field_ty].into_iter().chain(trait_args), ), ..*tr }); @@ -3382,7 +3502,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { trait_pred.skip_binder().self_ty(), diagnostic_name, ), - format!("#[derive({})]\n", diagnostic_name), + format!("#[derive({diagnostic_name})]\n"), Applicability::MaybeIncorrect, ); } @@ -3396,7 +3516,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ) { if let ObligationCauseCode::ImplDerivedObligation(_) = obligation.cause.code() && self.tcx.is_diagnostic_item(sym::SliceIndex, trait_pred.skip_binder().trait_ref.def_id) - && let ty::Slice(_) = trait_pred.skip_binder().trait_ref.substs.type_at(1).kind() + && let ty::Slice(_) = trait_pred.skip_binder().trait_ref.args.type_at(1).kind() && let ty::Ref(_, inner_ty, _) = trait_pred.skip_binder().self_ty().kind() && let ty::Uint(ty::UintTy::Usize) = inner_ty.kind() { @@ -3425,7 +3545,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { { if let hir::Expr { kind: hir::ExprKind::Block(..), .. } = expr { let expr = expr.peel_blocks(); - let ty = typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error_misc()); + let ty = typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx,)); let span = expr.span; if Some(span) != err.span.primary_span() { err.span_label( @@ -3445,12 +3565,12 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // to an associated type (as seen from `trait_pred`) in the predicate. Like in // trait_pred `S: Sum<::Item>` and predicate `i32: Sum<&()>` let mut type_diffs = vec![]; - if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = parent_code.deref() - && let Some(node_substs) = typeck_results.node_substs_opt(call_hir_id) - && let where_clauses = self.tcx.predicates_of(def_id).instantiate(self.tcx, node_substs) + if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = parent_code + && let Some(node_args) = typeck_results.node_args_opt(call_hir_id) + && let where_clauses = self.tcx.predicates_of(def_id).instantiate(self.tcx, node_args) && let Some(where_pred) = where_clauses.predicates.get(*idx) { - if let Some(where_pred) = where_pred.to_opt_poly_trait_pred() + if let Some(where_pred) = where_pred.as_trait_clause() && let Some(failed_pred) = failed_pred.to_opt_poly_trait_pred() { let where_pred = self.instantiate_binder_with_placeholders(where_pred); @@ -3461,7 +3581,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { ); let zipped = - iter::zip(where_pred.trait_ref.substs, failed_pred.trait_ref.substs); + iter::zip(where_pred.trait_ref.args, failed_pred.trait_ref.args); for (expected, actual) in zipped { self.probe(|_| { match self @@ -3473,13 +3593,13 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } }) }; - } else if let Some(where_pred) = where_pred.to_opt_poly_projection_pred() + } else if let Some(where_pred) = where_pred.as_projection_clause() && let Some(failed_pred) = failed_pred.to_opt_poly_projection_pred() && let Some(found) = failed_pred.skip_binder().term.ty() { type_diffs = vec![ Sorts(ty::error::ExpectedFound { - expected: self.tcx.mk_alias(ty::Projection, where_pred.skip_binder().projection_ty), + expected: Ty::new_alias(self.tcx,ty::Projection, where_pred.skip_binder().projection_ty), found, }), ]; @@ -3509,15 +3629,95 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { err.replace_span_with(path.ident.span, true); } } - if let Some(Node::Expr(hir::Expr { - kind: - hir::ExprKind::Call(hir::Expr { span, .. }, _) - | hir::ExprKind::MethodCall(hir::PathSegment { ident: Ident { span, .. }, .. }, ..), - .. - })) = hir.find(call_hir_id) + + if let Some(Node::Expr(expr)) = hir.find(call_hir_id) { + if let hir::ExprKind::Call(hir::Expr { span, .. }, _) + | hir::ExprKind::MethodCall( + hir::PathSegment { ident: Ident { span, .. }, .. }, + .., + ) = expr.kind + { + if Some(*span) != err.span.primary_span() { + err.span_label(*span, "required by a bound introduced by this call"); + } + } + + if let hir::ExprKind::MethodCall(_, expr, ..) = expr.kind { + self.suggest_option_method_if_applicable(failed_pred, param_env, err, expr); + } + } + } + + fn suggest_option_method_if_applicable( + &self, + failed_pred: ty::Predicate<'tcx>, + param_env: ty::ParamEnv<'tcx>, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + ) { + let tcx = self.tcx; + let infcx = self.infcx; + let Some(typeck_results) = self.typeck_results.as_ref() else { return }; + + // Make sure we're dealing with the `Option` type. + let Some(option_ty_adt) = typeck_results.expr_ty_adjusted(expr).ty_adt_def() else { + return; + }; + if !tcx.is_diagnostic_item(sym::Option, option_ty_adt.did()) { + return; + } + + // Given the predicate `fn(&T): FnOnce<(U,)>`, extract `fn(&T)` and `(U,)`, + // then suggest `Option::as_deref(_mut)` if `U` can deref to `T` + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, .. })) + = failed_pred.kind().skip_binder() + && tcx.is_fn_trait(trait_ref.def_id) + && let [self_ty, found_ty] = trait_ref.args.as_slice() + && let Some(fn_ty) = self_ty.as_type().filter(|ty| ty.is_fn()) + && let fn_sig @ ty::FnSig { + abi: abi::Abi::Rust, + c_variadic: false, + unsafety: hir::Unsafety::Normal, + .. + } = fn_ty.fn_sig(tcx).skip_binder() + + // Extract first param of fn sig with peeled refs, e.g. `fn(&T)` -> `T` + && let Some(&ty::Ref(_, target_ty, needs_mut)) = fn_sig.inputs().first().map(|t| t.kind()) + && !target_ty.has_escaping_bound_vars() + + // Extract first tuple element out of fn trait, e.g. `FnOnce<(U,)>` -> `U` + && let Some(ty::Tuple(tys)) = found_ty.as_type().map(Ty::kind) + && let &[found_ty] = tys.as_slice() + && !found_ty.has_escaping_bound_vars() + + // Extract `::Target` assoc type and check that it is `T` + && let Some(deref_target_did) = tcx.lang_items().deref_target() + && let projection = Ty::new_projection(tcx,deref_target_did, tcx.mk_args(&[ty::GenericArg::from(found_ty)])) + && let InferOk { value: deref_target, obligations } = infcx.at(&ObligationCause::dummy(), param_env).normalize(projection) + && obligations.iter().all(|obligation| infcx.predicate_must_hold_modulo_regions(obligation)) + && infcx.can_eq(param_env, deref_target, target_ty) { - if Some(*span) != err.span.primary_span() { - err.span_label(*span, "required by a bound introduced by this call"); + let help = if let hir::Mutability::Mut = needs_mut + && let Some(deref_mut_did) = tcx.lang_items().deref_mut_trait() + && infcx + .type_implements_trait(deref_mut_did, iter::once(found_ty), param_env) + .must_apply_modulo_regions() + { + Some(("call `Option::as_deref_mut()` first", ".as_deref_mut()")) + } else if let hir::Mutability::Not = needs_mut { + Some(("call `Option::as_deref()` first", ".as_deref()")) + } else { + None + }; + + if let Some((msg, sugg)) = help { + err.span_suggestion_with_style( + expr.span.shrink_to_hi(), + msg, + sugg, + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways + ); } } } @@ -3539,7 +3739,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let mut assocs = vec![]; let mut expr = expr; let mut prev_ty = self.resolve_vars_if_possible( - typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error_misc()), + typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx)), ); while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = expr.kind { // Point at every method call in the chain with the resulting type. @@ -3550,7 +3750,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self.probe_assoc_types_at_expr(&type_diffs, span, prev_ty, expr.hir_id, param_env); assocs.push(assocs_in_this_method); prev_ty = self.resolve_vars_if_possible( - typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(tcx.ty_error_misc()), + typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx)), ); if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind @@ -3568,7 +3768,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { if let hir::Node::Param(param) = parent { // ...and it is a an fn argument. let prev_ty = self.resolve_vars_if_possible( - typeck_results.node_type_opt(param.hir_id).unwrap_or(tcx.ty_error_misc()), + typeck_results.node_type_opt(param.hir_id).unwrap_or(Ty::new_misc_error(tcx,)), ); let assocs_in_this_method = self.probe_assoc_types_at_expr(&type_diffs, param.ty_span, prev_ty, param.hir_id, param_env); if assocs_in_this_method.iter().any(|a| a.is_some()) { @@ -3594,11 +3794,17 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { while let Some(assocs_in_method) = assocs.next() { let Some(prev_assoc_in_method) = assocs.peek() else { for entry in assocs_in_method { - let Some((span, (assoc, ty))) = entry else { continue; }; - if primary_spans.is_empty() || type_diffs.iter().any(|diff| { - let Sorts(expected_found) = diff else { return false; }; - self.can_eq(param_env, expected_found.found, ty) - }) { + let Some((span, (assoc, ty))) = entry else { + continue; + }; + if primary_spans.is_empty() + || type_diffs.iter().any(|diff| { + let Sorts(expected_found) = diff else { + return false; + }; + self.can_eq(param_env, expected_found.found, ty) + }) + { // FIXME: this doesn't quite work for `Iterator::collect` // because we have `Vec` and `()`, but we'd want `i32` // to point at the `.into_iter()` call, but as long as we @@ -3626,7 +3832,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let assoc = with_forced_trimmed_paths!(self.tcx.def_path_str(assoc)); if !self.can_eq(param_env, ty, *prev_ty) { if type_diffs.iter().any(|diff| { - let Sorts(expected_found) = diff else { return false; }; + let Sorts(expected_found) = diff else { + return false; + }; self.can_eq(param_env, expected_found.found, ty) }) { primary_spans.push(span); @@ -3671,18 +3879,22 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { body_id: hir::HirId, param_env: ty::ParamEnv<'tcx>, ) -> Vec))>> { - let ocx = ObligationCtxt::new_in_snapshot(self.infcx); + let ocx = ObligationCtxt::new(self.infcx); let mut assocs_in_this_method = Vec::with_capacity(type_diffs.len()); for diff in type_diffs { - let Sorts(expected_found) = diff else { continue; }; - let ty::Alias(ty::Projection, proj) = expected_found.expected.kind() else { continue; }; + let Sorts(expected_found) = diff else { + continue; + }; + let ty::Alias(ty::Projection, proj) = expected_found.expected.kind() else { + continue; + }; let origin = TypeVariableOrigin { kind: TypeVariableOriginKind::TypeInference, span }; let trait_def_id = proj.trait_def_id(self.tcx); // Make `Self` be equivalent to the type of the call chain // expression we're looking at now, so that we can tell what // for example `Iterator::Item` is at this point in the chain. - let substs = InternalSubsts::for_item(self.tcx, trait_def_id, |param, _| { + let args = GenericArgs::for_item(self.tcx, trait_def_id, |param, _| { match param.kind { ty::GenericParamDefKind::Type { .. } => { if param.index == 0 { @@ -3698,12 +3910,12 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // in. For example, this would be what `Iterator::Item` is here. let ty_var = self.infcx.next_ty_var(origin); // This corresponds to `::Item = _`. - let projection = ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::Projection( - ty::ProjectionPredicate { - projection_ty: self.tcx.mk_alias_ty(proj.def_id, substs), + let projection = ty::Binder::dummy(ty::PredicateKind::Clause( + ty::ClauseKind::Projection(ty::ProjectionPredicate { + projection_ty: self.tcx.mk_alias_ty(proj.def_id, args), term: ty_var.into(), - }, - ))); + }), + )); let body_def_id = self.tcx.hir().enclosing_body_owner(body_id); // Add `::Item = _` obligation. ocx.register_obligation(Obligation::misc( @@ -3731,13 +3943,22 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { /// If the type that failed selection is an array or a reference to an array, /// but the trait is implemented for slices, suggest that the user converts /// the array into a slice. - fn maybe_suggest_convert_to_slice( + fn suggest_convert_to_slice( &self, err: &mut Diagnostic, + obligation: &PredicateObligation<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, candidate_impls: &[ImplCandidate<'tcx>], span: Span, ) { + // We can only suggest the slice coersion for function and binary operation arguments, + // since the suggestion would make no sense in turbofish or call + let (ObligationCauseCode::BinOp { .. } + | ObligationCauseCode::FunctionArgumentObligation { .. }) = obligation.cause.code() + else { + return; + }; + // Three cases where we can make a suggestion: // 1. `[T; _]` (array of T) // 2. `&[T; _]` (reference to array of T) @@ -3776,7 +3997,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { .map(|trait_ref| trait_ref.trait_ref.self_ty()) .find(|t| is_slice(*t)) { - let msg = format!("convert the array to a `{}` slice instead", slice_ty); + let msg = format!("convert the array to a `{slice_ty}` slice instead"); if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { let mut suggestions = vec![]; @@ -3805,6 +4026,10 @@ fn hint_missing_borrow<'tcx>( found_node: Node<'_>, err: &mut Diagnostic, ) { + if matches!(found_node, Node::TraitItem(..)) { + return; + } + let found_args = match found.kind() { ty::FnPtr(f) => infcx.instantiate_binder_with_placeholders(*f).inputs().iter(), kind => { @@ -3819,7 +4044,9 @@ fn hint_missing_borrow<'tcx>( }; // This could be a variant constructor, for example. - let Some(fn_decl) = found_node.fn_decl() else { return; }; + let Some(fn_decl) = found_node.fn_decl() else { + return; + }; let args = fn_decl.inputs.iter(); @@ -3874,19 +4101,11 @@ fn hint_missing_borrow<'tcx>( } if !to_borrow.is_empty() { - err.multipart_suggestion_verbose( - "consider borrowing the argument", - to_borrow, - Applicability::MaybeIncorrect, - ); + err.subdiagnostic(errors::AdjustSignatureBorrow::Borrow { to_borrow }); } if !remove_borrow.is_empty() { - err.multipart_suggestion_verbose( - "do not borrow the argument", - remove_borrow, - Applicability::MaybeIncorrect, - ); + err.subdiagnostic(errors::AdjustSignatureBorrow::RemoveBorrow { remove_borrow }); } } diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index 2f85c32b5750d..3ebf1246a4141 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -4,11 +4,11 @@ use rustc_data_structures::obligation_forest::{Error, ForestObligation, Outcome} use rustc_data_structures::obligation_forest::{ObligationForest, ObligationProcessor}; use rustc_infer::infer::DefineOpaqueTypes; use rustc_infer::traits::ProjectionCacheKey; -use rustc_infer::traits::{SelectionError, TraitEngine, TraitObligation}; +use rustc_infer::traits::{PolyTraitObligation, SelectionError, TraitEngine}; use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::ty::abstract_const::NotConstEvaluatable; use rustc_middle::ty::error::{ExpectedFound, TypeError}; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Binder, Const, TypeVisitableExt}; use std::marker::PhantomData; @@ -50,20 +50,15 @@ impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> { /// method `select_all_or_error` can be used to report any remaining /// ambiguous cases as errors. pub struct FulfillmentContext<'tcx> { - // A list of all obligations that have been registered with this - // fulfillment context. + /// A list of all obligations that have been registered with this + /// fulfillment context. predicates: ObligationForest>, - // Is it OK to register obligations into this infcx inside - // an infcx snapshot? - // - // The "primary fulfillment" in many cases in typeck lives - // outside of any snapshot, so any use of it inside a snapshot - // will lead to trouble and therefore is checked against, but - // other fulfillment contexts sometimes do live inside of - // a snapshot (they don't *straddle* a snapshot, so there - // is no trouble there). - usable_in_snapshot: bool, + /// The snapshot in which this context was created. Using the context + /// outside of this snapshot leads to subtle bugs if the snapshot + /// gets rolled back. Because of this we explicitly check that we only + /// use the context in exactly this snapshot. + usable_in_snapshot: usize, } #[derive(Clone, Debug)] @@ -80,18 +75,17 @@ pub struct PendingPredicateObligation<'tcx> { #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] static_assert_size!(PendingPredicateObligation<'_>, 72); -impl<'a, 'tcx> FulfillmentContext<'tcx> { +impl<'tcx> FulfillmentContext<'tcx> { /// Creates a new fulfillment context. - pub(super) fn new() -> FulfillmentContext<'tcx> { - FulfillmentContext { predicates: ObligationForest::new(), usable_in_snapshot: false } - } - - pub(super) fn new_in_snapshot() -> FulfillmentContext<'tcx> { - FulfillmentContext { predicates: ObligationForest::new(), usable_in_snapshot: true } + pub(super) fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentContext<'tcx> { + FulfillmentContext { + predicates: ObligationForest::new(), + usable_in_snapshot: infcx.num_open_snapshots(), + } } /// Attempts to select obligations using `selcx`. - fn select(&mut self, selcx: SelectionContext<'a, 'tcx>) -> Vec> { + fn select(&mut self, selcx: SelectionContext<'_, 'tcx>) -> Vec> { let span = debug_span!("select", obligation_forest_size = ?self.predicates.len()); let _enter = span.enter(); @@ -116,19 +110,19 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> { } impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> { + #[inline] fn register_predicate_obligation( &mut self, infcx: &InferCtxt<'tcx>, obligation: PredicateObligation<'tcx>, ) { + assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); // this helps to reduce duplicate errors, as well as making // debug output much nicer to read and so on. let obligation = infcx.resolve_vars_if_possible(obligation); debug!(?obligation, "register_predicate_obligation"); - assert!(!infcx.is_in_snapshot() || self.usable_in_snapshot); - self.predicates .register_obligation(PendingPredicateObligation { obligation, stalled_on: vec![] }); } @@ -332,7 +326,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { // Evaluation will discard candidates using the leak check. // This means we need to pass it the bound version of our // predicate. - ty::PredicateKind::Clause(ty::Clause::Trait(trait_ref)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_ref)) => { let trait_obligation = obligation.with(infcx.tcx, binder.rebind(trait_ref)); self.process_trait_obligation( @@ -341,7 +335,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { &mut pending_obligation.stalled_on, ) } - ty::PredicateKind::Clause(ty::Clause::Projection(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => { let project_obligation = obligation.with(infcx.tcx, binder.rebind(data)); self.process_projection_obligation( @@ -350,30 +344,27 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { &mut pending_obligation.stalled_on, ) } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(_)) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(_)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::WellFormed(_) + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) | ty::PredicateKind::ObjectSafe(_) | ty::PredicateKind::ClosureKind(..) | ty::PredicateKind::Subtype(_) | ty::PredicateKind::Coerce(_) - | ty::PredicateKind::ConstEvaluatable(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) | ty::PredicateKind::ConstEquate(..) => { let pred = ty::Binder::dummy(infcx.instantiate_binder_with_placeholders(binder)); ProcessResult::Changed(mk_pending(vec![obligation.with(infcx.tcx, pred)])) } ty::PredicateKind::Ambiguous => ProcessResult::Unchanged, - ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("TypeWellFormedFromEnv is only used for Chalk") - } ty::PredicateKind::AliasRelate(..) => { bug!("AliasRelate is only used for new solver") } }, Some(pred) => match pred { - ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => { let trait_obligation = obligation.with(infcx.tcx, Binder::dummy(data)); self.process_trait_obligation( @@ -383,7 +374,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { ) } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(data)) => { if infcx.considering_regions { infcx.region_outlives_predicate(&obligation.cause, Binder::dummy(data)); } @@ -391,7 +382,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { ProcessResult::Changed(vec![]) } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( t_a, r_b, ))) => { @@ -401,7 +392,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { ProcessResult::Changed(vec![]) } - ty::PredicateKind::Clause(ty::Clause::Projection(ref data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(ref data)) => { let project_obligation = obligation.with(infcx.tcx, Binder::dummy(*data)); self.process_projection_obligation( @@ -419,8 +410,8 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { } } - ty::PredicateKind::ClosureKind(_, closure_substs, kind) => { - match self.selcx.infcx.closure_kind(closure_substs) { + ty::PredicateKind::ClosureKind(_, closure_args, kind) => { + match self.selcx.infcx.closure_kind(closure_args) { Some(closure_kind) => { if closure_kind.extends(kind) { ProcessResult::Changed(vec![]) @@ -432,7 +423,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { } } - ty::PredicateKind::WellFormed(arg) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { match wf::obligations( self.selcx.infcx, obligation.param_env, @@ -497,7 +488,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { } } - ty::PredicateKind::ConstEvaluatable(uv) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(uv)) => { match const_evaluatable::is_const_evaluatable( self.selcx.infcx, uv, @@ -537,7 +528,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { debug!("equating consts:\nc1= {:?}\nc2= {:?}", c1, c2); use rustc_hir::def::DefKind; - use ty::ConstKind::Unevaluated; + use ty::Unevaluated; match (c1.kind(), c2.kind()) { (Unevaluated(a), Unevaluated(b)) if a.def == b.def && tcx.def_kind(a.def) == DefKind::AssocConst => @@ -545,7 +536,7 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { if let Ok(new_obligations) = infcx .at(&obligation.cause, obligation.param_env) .trace(c1, c2) - .eq(DefineOpaqueTypes::No, a.substs, b.substs) + .eq(DefineOpaqueTypes::No, a.args, b.args) { return ProcessResult::Changed(mk_pending( new_obligations.into_obligations(), @@ -568,31 +559,30 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { let stalled_on = &mut pending_obligation.stalled_on; - let mut evaluate = |c: Const<'tcx>| { - if let ty::ConstKind::Unevaluated(unevaluated) = c.kind() { - match self.selcx.infcx.try_const_eval_resolve( - obligation.param_env, - unevaluated, - c.ty(), - Some(obligation.cause.span), - ) { - Ok(val) => Ok(val), - Err(e) => match e { - ErrorHandled::TooGeneric => { - stalled_on.extend( - unevaluated.substs.iter().filter_map( + let mut evaluate = + |c: Const<'tcx>| { + if let ty::ConstKind::Unevaluated(unevaluated) = c.kind() { + match self.selcx.infcx.try_const_eval_resolve( + obligation.param_env, + unevaluated, + c.ty(), + Some(obligation.cause.span), + ) { + Ok(val) => Ok(val), + Err(e) => match e { + ErrorHandled::TooGeneric => { + stalled_on.extend(unevaluated.args.iter().filter_map( TyOrConstInferVar::maybe_from_generic_arg, - ), - ); - Err(ErrorHandled::TooGeneric) - } - _ => Err(e), - }, + )); + Err(ErrorHandled::TooGeneric) + } + _ => Err(e), + }, + } + } else { + Ok(c) } - } else { - Ok(c) - } - }; + }; match (evaluate(c1), evaluate(c2)) { (Ok(c1), Ok(c2)) => { @@ -633,13 +623,10 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { } } ty::PredicateKind::Ambiguous => ProcessResult::Unchanged, - ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("TypeWellFormedFromEnv is only used for Chalk") - } ty::PredicateKind::AliasRelate(..) => { bug!("AliasRelate is only used for new solver") } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { match self.selcx.infcx.at(&obligation.cause, obligation.param_env).eq( DefineOpaqueTypes::No, ct.ty(), @@ -679,11 +666,11 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> { fn process_trait_obligation( &mut self, obligation: &PredicateObligation<'tcx>, - trait_obligation: TraitObligation<'tcx>, + trait_obligation: PolyTraitObligation<'tcx>, stalled_on: &mut Vec>, ) -> ProcessResult, FulfillmentErrorCode<'tcx>> { let infcx = self.selcx.infcx; - if obligation.predicate.is_global() { + if obligation.predicate.is_global() && !self.selcx.is_intercrate() { // no type variables present, can use evaluation for better caching. // FIXME: consider caching errors too. if infcx.predicate_must_hold_considering_regions(obligation) { @@ -695,7 +682,7 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> { } } - match self.selcx.select(&trait_obligation) { + match self.selcx.poly_select(&trait_obligation) { Ok(Some(impl_source)) => { debug!("selecting trait at depth {} yielded Ok(Some)", obligation.recursion_depth); ProcessResult::Changed(mk_pending(impl_source.nested_obligations())) @@ -708,9 +695,9 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> { // trait selection is because we don't have enough // information about the types in the trait. stalled_on.clear(); - stalled_on.extend(substs_infer_vars( + stalled_on.extend(args_infer_vars( &self.selcx, - trait_obligation.predicate.map_bound(|pred| pred.trait_ref.substs), + trait_obligation.predicate.map_bound(|pred| pred.trait_ref.args), )); debug!( @@ -737,7 +724,7 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> { ) -> ProcessResult, FulfillmentErrorCode<'tcx>> { let tcx = self.selcx.tcx(); - if obligation.predicate.is_global() { + if obligation.predicate.is_global() && !self.selcx.is_intercrate() { // no type variables present, can use evaluation for better caching. // FIXME: consider caching errors too. if self.selcx.infcx.predicate_must_hold_considering_regions(obligation) { @@ -765,9 +752,9 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> { ProjectAndUnifyResult::Holds(os) => ProcessResult::Changed(mk_pending(os)), ProjectAndUnifyResult::FailedNormalization => { stalled_on.clear(); - stalled_on.extend(substs_infer_vars( + stalled_on.extend(args_infer_vars( &self.selcx, - project_obligation.predicate.map_bound(|pred| pred.projection_ty.substs), + project_obligation.predicate.map_bound(|pred| pred.projection_ty.args), )); ProcessResult::Unchanged } @@ -782,14 +769,14 @@ impl<'a, 'tcx> FulfillProcessor<'a, 'tcx> { } } -/// Returns the set of inference variables contained in `substs`. -fn substs_infer_vars<'a, 'tcx>( +/// Returns the set of inference variables contained in `args`. +fn args_infer_vars<'a, 'tcx>( selcx: &SelectionContext<'a, 'tcx>, - substs: ty::Binder<'tcx, SubstsRef<'tcx>>, + args: ty::Binder<'tcx, GenericArgsRef<'tcx>>, ) -> impl Iterator> { selcx .infcx - .resolve_vars_if_possible(substs) + .resolve_vars_if_possible(args) .skip_binder() // ok because this check doesn't care about regions .iter() .filter(|arg| arg.has_non_region_infer()) diff --git a/compiler/rustc_trait_selection/src/traits/misc.rs b/compiler/rustc_trait_selection/src/traits/misc.rs index 2210ef975e6c8..ab07b10c65f58 100644 --- a/compiler/rustc_trait_selection/src/traits/misc.rs +++ b/compiler/rustc_trait_selection/src/traits/misc.rs @@ -43,7 +43,7 @@ pub fn type_allowed_to_implement_copy<'tcx>( self_type: Ty<'tcx>, parent_cause: ObligationCause<'tcx>, ) -> Result<(), CopyImplementationError<'tcx>> { - let (adt, substs) = match self_type.kind() { + let (adt, args) = match self_type.kind() { // These types used to have a builtin impl. // Now libcore provides that impl. ty::Uint(_) @@ -56,7 +56,7 @@ pub fn type_allowed_to_implement_copy<'tcx>( | ty::Ref(_, _, hir::Mutability::Not) | ty::Array(..) => return Ok(()), - &ty::Adt(adt, substs) => (adt, substs), + &ty::Adt(adt, args) => (adt, args), _ => return Err(CopyImplementationError::NotAnAdt), }; @@ -66,7 +66,7 @@ pub fn type_allowed_to_implement_copy<'tcx>( param_env, self_type, adt, - substs, + args, parent_cause, hir::LangItem::Copy, ) @@ -91,7 +91,7 @@ pub fn type_allowed_to_implement_const_param_ty<'tcx>( self_type: Ty<'tcx>, parent_cause: ObligationCause<'tcx>, ) -> Result<(), ConstParamTyImplementationError<'tcx>> { - let (adt, substs) = match self_type.kind() { + let (adt, args) = match self_type.kind() { // `core` provides these impls. ty::Uint(_) | ty::Int(_) @@ -100,9 +100,10 @@ pub fn type_allowed_to_implement_const_param_ty<'tcx>( | ty::Str | ty::Array(..) | ty::Slice(_) - | ty::Ref(.., hir::Mutability::Not) => return Ok(()), + | ty::Ref(.., hir::Mutability::Not) + | ty::Tuple(_) => return Ok(()), - &ty::Adt(adt, substs) => (adt, substs), + &ty::Adt(adt, args) => (adt, args), _ => return Err(ConstParamTyImplementationError::NotAnAdtOrBuiltinAllowed), }; @@ -112,7 +113,7 @@ pub fn type_allowed_to_implement_const_param_ty<'tcx>( param_env, self_type, adt, - substs, + args, parent_cause, hir::LangItem::ConstParamTy, ) @@ -127,7 +128,7 @@ pub fn all_fields_implement_trait<'tcx>( param_env: ty::ParamEnv<'tcx>, self_type: Ty<'tcx>, adt: AdtDef<'tcx>, - substs: &'tcx List>, + args: &'tcx List>, parent_cause: ObligationCause<'tcx>, lang_item: LangItem, ) -> Result<(), Vec<(&'tcx ty::FieldDef, Ty<'tcx>, InfringingFieldsReason<'tcx>)>> { @@ -140,7 +141,7 @@ pub fn all_fields_implement_trait<'tcx>( let infcx = tcx.infer_ctxt().build(); let ocx = traits::ObligationCtxt::new(&infcx); - let unnormalized_ty = field.ty(tcx, substs); + let unnormalized_ty = field.ty(tcx, args); if unnormalized_ty.references_error() { continue; } @@ -153,11 +154,11 @@ pub fn all_fields_implement_trait<'tcx>( // FIXME(compiler-errors): This gives us better spans for bad // projection types like in issue-50480. - // If the ADT has substs, point to the cause we are given. + // If the ADT has args, point to the cause we are given. // If it does not, then this field probably doesn't normalize // to begin with, and point to the bad field's span instead. let normalization_cause = if field - .ty(tcx, traits::InternalSubsts::identity_for_item(tcx, adt.did())) + .ty(tcx, traits::GenericArgs::identity_for_item(tcx, adt.did())) .has_non_region_param() { parent_cause.clone() diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index f265230ff772d..d2210c6d5d91b 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -3,7 +3,6 @@ //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/traits/resolution.html pub mod auto_trait; -mod chalk_fulfill; pub(crate) mod coherence; pub mod const_evaluatable; mod engine; @@ -12,14 +11,16 @@ mod fulfill; pub mod misc; mod object_safety; pub mod outlives_bounds; -mod project; +pub mod project; pub mod query; +#[allow(hidden_glob_reexports)] mod select; mod specialize; mod structural_match; mod structural_normalize; +#[allow(hidden_glob_reexports)] mod util; -mod vtable; +pub mod vtable; pub mod wf; use crate::infer::outlives::env::OutlivesEnvironment; @@ -30,14 +31,16 @@ use rustc_errors::ErrorGuaranteed; use rustc_middle::query::Providers; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt}; -use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeSuperVisitable}; -use rustc_middle::ty::{InternalSubsts, SubstsRef}; +use rustc_middle::ty::{self, ToPredicate, Ty, TyCtxt, TypeFolder, TypeSuperVisitable}; +use rustc_middle::ty::{GenericArgs, GenericArgsRef}; use rustc_span::def_id::DefId; use rustc_span::Span; use std::fmt::Debug; use std::ops::ControlFlow; +pub(crate) use self::project::{needs_normalization, BoundVarReplacer, PlaceholderReplacer}; + pub use self::FulfillmentErrorCode::*; pub use self::ImplSource::*; pub use self::ObligationCauseCode::*; @@ -58,21 +61,17 @@ pub use self::select::{EvaluationResult, IntercrateAmbiguityCause, OverflowError pub use self::specialize::specialization_graph::FutureCompatOverlapError; pub use self::specialize::specialization_graph::FutureCompatOverlapErrorKind; pub use self::specialize::{ - specialization_graph, translate_substs, translate_substs_with_cause, OverlapError, -}; -pub use self::structural_match::{ - search_for_adt_const_param_violation, search_for_structural_match_violation, + specialization_graph, translate_args, translate_args_with_cause, OverlapError, }; +pub use self::structural_match::search_for_structural_match_violation; pub use self::structural_normalize::StructurallyNormalizeExt; pub use self::util::elaborate; -pub use self::util::{expand_trait_aliases, TraitAliasExpander}; -pub use self::util::{get_vtable_index_of_object_method, impl_item_is_final, upcast_choices}; pub use self::util::{ - supertrait_def_ids, supertraits, transitive_bounds, transitive_bounds_that_define_assoc_item, - SupertraitDefIds, + check_args_compatible, supertrait_def_ids, supertraits, transitive_bounds, + transitive_bounds_that_define_assoc_item, SupertraitDefIds, }; - -pub use self::chalk_fulfill::FulfillmentContext as ChalkFulfillmentContext; +pub use self::util::{expand_trait_aliases, TraitAliasExpander}; +pub use self::util::{get_vtable_index_of_object_method, impl_item_is_final, upcast_choices}; pub use rustc_infer::traits::*; @@ -114,11 +113,11 @@ pub fn predicates_for_generics<'tcx>( param_env: ty::ParamEnv<'tcx>, generic_bounds: ty::InstantiatedPredicates<'tcx>, ) -> impl Iterator> { - generic_bounds.into_iter().enumerate().map(move |(idx, (predicate, span))| Obligation { + generic_bounds.into_iter().enumerate().map(move |(idx, (clause, span))| Obligation { cause: cause(idx, span), recursion_depth: 0, param_env, - predicate, + predicate: clause.as_predicate(), }) } @@ -134,7 +133,7 @@ pub fn type_known_to_meet_bound_modulo_regions<'tcx>( def_id: DefId, ) -> bool { let trait_ref = ty::TraitRef::new(infcx.tcx, def_id, [ty]); - pred_known_to_hold_modulo_regions(infcx, param_env, trait_ref.without_const()) + pred_known_to_hold_modulo_regions(infcx, param_env, trait_ref) } /// FIXME(@lcnr): this function doesn't seem right and shouldn't exist? @@ -161,7 +160,7 @@ fn pred_known_to_hold_modulo_regions<'tcx>( // the we do no inference in the process of checking this obligation. let goal = infcx.resolve_vars_if_possible((obligation.predicate, obligation.param_env)); infcx.probe(|_| { - let ocx = ObligationCtxt::new_in_snapshot(infcx); + let ocx = ObligationCtxt::new(infcx); ocx.register_obligation(obligation); let errors = ocx.select_all_or_error(); @@ -185,8 +184,8 @@ fn do_normalize_predicates<'tcx>( tcx: TyCtxt<'tcx>, cause: ObligationCause<'tcx>, elaborated_env: ty::ParamEnv<'tcx>, - predicates: Vec>, -) -> Result>, ErrorGuaranteed> { + predicates: Vec>, +) -> Result>, ErrorGuaranteed> { let span = cause.span; // FIXME. We should really... do something with these region // obligations. But this call just continues the older @@ -270,16 +269,66 @@ pub fn normalize_param_env_or_error<'tcx>( // parameter environments once for every fn as it goes, // and errors will get reported then; so outside of type inference we // can be sure that no errors should occur. - let mut predicates: Vec<_> = - util::elaborate(tcx, unnormalized_env.caller_bounds().into_iter()).collect(); + let mut predicates: Vec<_> = util::elaborate( + tcx, + unnormalized_env.caller_bounds().into_iter().map(|predicate| { + if tcx.features().generic_const_exprs { + return predicate; + } + + struct ConstNormalizer<'tcx>(TyCtxt<'tcx>); + + impl<'tcx> TypeFolder> for ConstNormalizer<'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.0 + } + + fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> { + // While it is pretty sus to be evaluating things with an empty param env, it + // should actually be okay since without `feature(generic_const_exprs)` the only + // const arguments that have a non-empty param env are array repeat counts. These + // do not appear in the type system though. + c.eval(self.0, ty::ParamEnv::empty()) + } + } + + // This whole normalization step is a hack to work around the fact that + // `normalize_param_env_or_error` is fundamentally broken from using an + // unnormalized param env with a trait solver that expects the param env + // to be normalized. + // + // When normalizing the param env we can end up evaluating obligations + // that have been normalized but can only be proven via a where clause + // which is still in its unnormalized form. example: + // + // Attempting to prove `T: Trait<::Assoc>` in a param env + // with a `T: Trait<::Assoc>` where clause will fail because + // we first normalize obligations before proving them so we end up proving + // `T: Trait`. Since lazy normalization is not implemented equating `u8` + // with `::Assoc` fails outright so we incorrectly believe that + // we cannot prove `T: Trait`. + // + // The same thing is true for const generics- attempting to prove + // `T: Trait` with the same thing as a where clauses + // will fail. After normalization we may be attempting to prove `T: Trait<4>` with + // the unnormalized where clause `T: Trait`. In order + // for the obligation to hold `4` must be equal to `ConstKind::Unevaluated(...)` + // but as we do not have lazy norm implemented, equating the two consts fails outright. + // + // Ideally we would not normalize consts here at all but it is required for backwards + // compatibility. Eventually when lazy norm is implemented this can just be removed. + // We do not normalize types here as there is no backwards compatibility requirement + // for us to do so. + // + // FIXME(-Ztrait-solver=next): remove this hack since we have deferred projection equality + predicate.fold_with(&mut ConstNormalizer(tcx)) + }), + ) + .collect(); debug!("normalize_param_env_or_error: elaborated-predicates={:?}", predicates); - let elaborated_env = ty::ParamEnv::new( - tcx.mk_predicates(&predicates), - unnormalized_env.reveal(), - unnormalized_env.constness(), - ); + let elaborated_env = ty::ParamEnv::new(tcx.mk_clauses(&predicates), unnormalized_env.reveal()); // HACK: we are trying to normalize the param-env inside *itself*. The problem is that // normalization expects its param-env to be already normalized, which means we have @@ -300,11 +349,8 @@ pub fn normalize_param_env_or_error<'tcx>( // This works fairly well because trait matching does not actually care about param-env // TypeOutlives predicates - these are normally used by regionck. let outlives_predicates: Vec<_> = predicates - .drain_filter(|predicate| { - matches!( - predicate.kind().skip_binder(), - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) - ) + .extract_if(|predicate| { + matches!(predicate.kind().skip_binder(), ty::ClauseKind::TypeOutlives(..)) }) .collect(); @@ -312,12 +358,9 @@ pub fn normalize_param_env_or_error<'tcx>( "normalize_param_env_or_error: predicates=(non-outlives={:?}, outlives={:?})", predicates, outlives_predicates ); - let Ok(non_outlives_predicates) = do_normalize_predicates( - tcx, - cause.clone(), - elaborated_env, - predicates, - ) else { + let Ok(non_outlives_predicates) = + do_normalize_predicates(tcx, cause.clone(), elaborated_env, predicates) + else { // An unnormalized env is better than nothing. debug!("normalize_param_env_or_error: errored resolving non-outlives predicates"); return elaborated_env; @@ -329,17 +372,11 @@ pub fn normalize_param_env_or_error<'tcx>( // here. I believe they should not matter, because we are ignoring TypeOutlives param-env // predicates here anyway. Keeping them here anyway because it seems safer. let outlives_env = non_outlives_predicates.iter().chain(&outlives_predicates).cloned(); - let outlives_env = ty::ParamEnv::new( - tcx.mk_predicates_from_iter(outlives_env), - unnormalized_env.reveal(), - unnormalized_env.constness(), - ); - let Ok(outlives_predicates) = do_normalize_predicates( - tcx, - cause, - outlives_env, - outlives_predicates, - ) else { + let outlives_env = + ty::ParamEnv::new(tcx.mk_clauses_from_iter(outlives_env), unnormalized_env.reveal()); + let Ok(outlives_predicates) = + do_normalize_predicates(tcx, cause, outlives_env, outlives_predicates) + else { // An unnormalized env is better than nothing. debug!("normalize_param_env_or_error: errored resolving outlives predicates"); return elaborated_env; @@ -349,14 +386,15 @@ pub fn normalize_param_env_or_error<'tcx>( let mut predicates = non_outlives_predicates; predicates.extend(outlives_predicates); debug!("normalize_param_env_or_error: final predicates={:?}", predicates); - ty::ParamEnv::new( - tcx.mk_predicates(&predicates), - unnormalized_env.reveal(), - unnormalized_env.constness(), - ) + ty::ParamEnv::new(tcx.mk_clauses(&predicates), unnormalized_env.reveal()) } -/// Normalize a type and process all resulting obligations, returning any errors +/// Normalize a type and process all resulting obligations, returning any errors. +/// +/// FIXME(-Ztrait-solver=next): This should be replaced by `At::deeply_normalize` +/// which has the same behavior with the new solver. Because using a separate +/// fulfillment context worsens caching in the old solver, `At::deeply_normalize` +/// is still lazy with the old solver as it otherwise negatively impacts perf. #[instrument(skip_all)] pub fn fully_normalize<'tcx, T>( infcx: &InferCtxt<'tcx>, @@ -385,10 +423,7 @@ where /// Normalizes the predicates and checks whether they hold in an empty environment. If this /// returns true, then either normalize encountered an error or one of the predicates did not /// hold. Used when creating vtables to check for unsatisfiable methods. -pub fn impossible_predicates<'tcx>( - tcx: TyCtxt<'tcx>, - predicates: Vec>, -) -> bool { +pub fn impossible_predicates<'tcx>(tcx: TyCtxt<'tcx>, predicates: Vec>) -> bool { debug!("impossible_predicates(predicates={:?})", predicates); let infcx = tcx.infer_ctxt().build(); @@ -408,7 +443,7 @@ pub fn impossible_predicates<'tcx>( fn subst_and_check_impossible_predicates<'tcx>( tcx: TyCtxt<'tcx>, - key: (DefId, SubstsRef<'tcx>), + key: (DefId, GenericArgsRef<'tcx>), ) -> bool { debug!("subst_and_check_impossible_predicates(key={:?})", key); @@ -428,11 +463,14 @@ fn subst_and_check_impossible_predicates<'tcx>( result } -/// Checks whether a trait's method is impossible to call on a given impl. +/// Checks whether a trait's associated item is impossible to reference on a given impl. /// /// This only considers predicates that reference the impl's generics, and not /// those that reference the method's generics. -fn is_impossible_method(tcx: TyCtxt<'_>, (impl_def_id, trait_item_def_id): (DefId, DefId)) -> bool { +fn is_impossible_associated_item( + tcx: TyCtxt<'_>, + (impl_def_id, trait_item_def_id): (DefId, DefId), +) -> bool { struct ReferencesOnlyParentGenerics<'tcx> { tcx: TyCtxt<'tcx>, generics: &'tcx ty::Generics, @@ -475,7 +513,7 @@ fn is_impossible_method(tcx: TyCtxt<'_>, (impl_def_id, trait_item_def_id): (DefI let impl_trait_ref = tcx .impl_trait_ref(impl_def_id) .expect("expected impl to correspond to trait") - .subst_identity(); + .instantiate_identity(); let param_env = tcx.param_env(impl_def_id); let mut visitor = ReferencesOnlyParentGenerics { tcx, generics, trait_item_def_id }; @@ -485,7 +523,7 @@ fn is_impossible_method(tcx: TyCtxt<'_>, (impl_def_id, trait_item_def_id): (DefI tcx, ObligationCause::dummy_with_span(*span), param_env, - ty::EarlyBinder(*pred).subst(tcx, impl_trait_ref.substs), + ty::EarlyBinder::bind(*pred).instantiate(tcx, impl_trait_ref.args), ) }) }); @@ -510,7 +548,7 @@ pub fn provide(providers: &mut Providers) { specializes: specialize::specializes, subst_and_check_impossible_predicates, check_tys_might_be_eq: misc::check_tys_might_be_eq, - is_impossible_method, + is_impossible_associated_item, ..*providers }; } diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs index c81bf6ebc2eaa..5823b4508d94e 100644 --- a/compiler/rustc_trait_selection/src/traits/object_safety.rs +++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs @@ -17,10 +17,10 @@ use rustc_errors::{DelayDm, FatalError, MultiSpan}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_middle::query::Providers; -use rustc_middle::ty::subst::{GenericArg, InternalSubsts}; use rustc_middle::ty::{ self, EarlyBinder, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, }; +use rustc_middle::ty::{GenericArg, GenericArgs}; use rustc_middle::ty::{ToPredicate, TypeVisitableExt}; use rustc_session::lint::builtin::WHERE_CLAUSES_OBJECT_SAFETY; use rustc_span::symbol::Symbol; @@ -101,7 +101,7 @@ pub fn is_vtable_safe_method(tcx: TyCtxt<'_>, trait_def_id: DefId, method: ty::A debug_assert!(tcx.generics_of(trait_def_id).has_self); debug!("is_vtable_safe_method({:?}, {:?})", trait_def_id, method); // Any method that has a `Self: Sized` bound cannot be called. - if generics_require_sized_self(tcx, method.def_id) { + if tcx.generics_require_sized_self(method.def_id) { return false; } @@ -115,15 +115,11 @@ fn object_safety_violations_for_trait( tcx: TyCtxt<'_>, trait_def_id: DefId, ) -> Vec { - // Check methods for violations. + // Check assoc items for violations. let mut violations: Vec<_> = tcx .associated_items(trait_def_id) .in_definition_order() - .filter(|item| item.kind == ty::AssocKind::Fn) - .filter_map(|&item| { - object_safety_violation_for_method(tcx, trait_def_id, item) - .map(|(code, span)| ObjectSafetyViolation::Method(item.name, code, span)) - }) + .filter_map(|&item| object_safety_violation_for_assoc_item(tcx, trait_def_id, item)) .collect(); // Check the trait itself. @@ -145,30 +141,6 @@ fn object_safety_violations_for_trait( violations.push(ObjectSafetyViolation::SupertraitNonLifetimeBinder(spans)); } - violations.extend( - tcx.associated_items(trait_def_id) - .in_definition_order() - .filter(|item| item.kind == ty::AssocKind::Const) - .map(|item| { - let ident = item.ident(tcx); - ObjectSafetyViolation::AssocConst(ident.name, ident.span) - }), - ); - - if !tcx.features().generic_associated_types_extended { - violations.extend( - tcx.associated_items(trait_def_id) - .in_definition_order() - .filter(|item| item.kind == ty::AssocKind::Type) - .filter(|item| !tcx.generics_of(item.def_id).params.is_empty()) - .filter(|item| tcx.opt_rpitit_info(item.def_id).is_none()) - .map(|item| { - let ident = item.ident(tcx); - ObjectSafetyViolation::GAT(ident.name, ident.span) - }), - ); - } - debug!( "object_safety_violations_for_trait(trait_def_id={:?}) = {:?}", trait_def_id, violations @@ -298,23 +270,23 @@ fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span tcx.associated_items(trait_def_id) .in_definition_order() .filter(|item| item.kind == ty::AssocKind::Type) - .flat_map(|item| tcx.explicit_item_bounds(item.def_id).subst_identity_iter_copied()) - .filter_map(|pred_span| predicate_references_self(tcx, pred_span)) + .flat_map(|item| tcx.explicit_item_bounds(item.def_id).instantiate_identity_iter_copied()) + .filter_map(|c| predicate_references_self(tcx, c)) .collect() } fn predicate_references_self<'tcx>( tcx: TyCtxt<'tcx>, - (predicate, sp): (ty::Predicate<'tcx>, Span), + (predicate, sp): (ty::Clause<'tcx>, Span), ) -> Option { let self_ty = tcx.types.self_param; let has_self_ty = |arg: &GenericArg<'tcx>| arg.walk().any(|arg| arg == self_ty.into()); match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(ref data)) => { + ty::ClauseKind::Trait(ref data) => { // In the case of a trait predicate, we can skip the "self" type. - data.trait_ref.substs[1..].iter().any(has_self_ty).then_some(sp) + data.trait_ref.args[1..].iter().any(has_self_ty).then_some(sp) } - ty::PredicateKind::Clause(ty::Clause::Projection(ref data)) => { + ty::ClauseKind::Projection(ref data) => { // And similarly for projections. This should be redundant with // the previous check because any projection should have a // matching `Trait` predicate with the same inputs, but we do @@ -330,26 +302,16 @@ fn predicate_references_self<'tcx>( // // This is ALT2 in issue #56288, see that for discussion of the // possible alternatives. - data.projection_ty.substs[1..].iter().any(has_self_ty).then_some(sp) - } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(_ct, ty)) => { - has_self_ty(&ty.into()).then_some(sp) + data.projection_ty.args[1..].iter().any(has_self_ty).then_some(sp) } + ty::ClauseKind::ConstArgHasType(_ct, ty) => has_self_ty(&ty.into()).then_some(sp), - ty::PredicateKind::AliasRelate(..) => bug!("`AliasRelate` not allowed as assumption"), - - ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) - | ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) + ty::ClauseKind::WellFormed(..) + | ty::ClauseKind::TypeOutlives(..) + | ty::ClauseKind::RegionOutlives(..) // FIXME(generic_const_exprs): this can mention `Self` - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => None, + | ty::ClauseKind::ConstEvaluatable(..) + => None, } } @@ -369,7 +331,7 @@ fn super_predicates_have_non_lifetime_binders( } fn trait_has_sized_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> bool { - generics_require_sized_self(tcx, trait_def_id) + tcx.generics_require_sized_self(trait_def_id) } fn generics_require_sized_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool { @@ -381,54 +343,66 @@ fn generics_require_sized_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool { let predicates = tcx.predicates_of(def_id); let predicates = predicates.instantiate_identity(tcx).predicates; elaborate(tcx, predicates.into_iter()).any(|pred| match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(ref trait_pred)) => { + ty::ClauseKind::Trait(ref trait_pred) => { trait_pred.def_id() == sized_def_id && trait_pred.self_ty().is_param(0) } - ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => false, + ty::ClauseKind::RegionOutlives(_) + | ty::ClauseKind::TypeOutlives(_) + | ty::ClauseKind::Projection(_) + | ty::ClauseKind::ConstArgHasType(_, _) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => false, }) } -/// Returns `Some(_)` if this method makes the containing trait not object safe. -fn object_safety_violation_for_method( +/// Returns `Some(_)` if this item makes the containing trait not object safe. +#[instrument(level = "debug", skip(tcx), ret)] +fn object_safety_violation_for_assoc_item( tcx: TyCtxt<'_>, trait_def_id: DefId, - method: ty::AssocItem, -) -> Option<(MethodViolationCode, Span)> { - debug!("object_safety_violation_for_method({:?}, {:?})", trait_def_id, method); - // Any method that has a `Self : Sized` requisite is otherwise + item: ty::AssocItem, +) -> Option { + // Any item that has a `Self : Sized` requisite is otherwise // exempt from the regulations. - if generics_require_sized_self(tcx, method.def_id) { + if tcx.generics_require_sized_self(item.def_id) { return None; } - let violation = virtual_call_violation_for_method(tcx, trait_def_id, method); - // Get an accurate span depending on the violation. - violation.map(|v| { - let node = tcx.hir().get_if_local(method.def_id); - let span = match (&v, node) { - (MethodViolationCode::ReferencesSelfInput(Some(span)), _) => *span, - (MethodViolationCode::UndispatchableReceiver(Some(span)), _) => *span, - (MethodViolationCode::ReferencesImplTraitInTrait(span), _) => *span, - (MethodViolationCode::ReferencesSelfOutput, Some(node)) => { - node.fn_decl().map_or(method.ident(tcx).span, |decl| decl.output.span()) + match item.kind { + // Associated consts are never object safe, as they can't have `where` bounds yet at all, + // and associated const bounds in trait objects aren't a thing yet either. + ty::AssocKind::Const => { + Some(ObjectSafetyViolation::AssocConst(item.name, item.ident(tcx).span)) + } + ty::AssocKind::Fn => virtual_call_violation_for_method(tcx, trait_def_id, item).map(|v| { + let node = tcx.hir().get_if_local(item.def_id); + // Get an accurate span depending on the violation. + let span = match (&v, node) { + (MethodViolationCode::ReferencesSelfInput(Some(span)), _) => *span, + (MethodViolationCode::UndispatchableReceiver(Some(span)), _) => *span, + (MethodViolationCode::ReferencesImplTraitInTrait(span), _) => *span, + (MethodViolationCode::ReferencesSelfOutput, Some(node)) => { + node.fn_decl().map_or(item.ident(tcx).span, |decl| decl.output.span()) + } + _ => item.ident(tcx).span, + }; + + ObjectSafetyViolation::Method(item.name, v, span) + }), + // Associated types can only be object safe if they have `Self: Sized` bounds. + ty::AssocKind::Type => { + if !tcx.features().generic_associated_types_extended + && !tcx.generics_of(item.def_id).params.is_empty() + && !item.is_impl_trait_in_trait() + { + Some(ObjectSafetyViolation::GAT(item.name, item.ident(tcx).span)) + } else { + // We will permit associated types if they are explicitly mentioned in the trait object. + // We can't check this here, as here we only check if it is guaranteed to not be possible. + None } - _ => method.ident(tcx).span, - }; - (v, span) - }) + } + } } /// Returns `Some(_)` if this method cannot be called on a trait @@ -440,7 +414,7 @@ fn virtual_call_violation_for_method<'tcx>( trait_def_id: DefId, method: ty::AssocItem, ) -> Option { - let sig = tcx.fn_sig(method.def_id).subst_identity(); + let sig = tcx.fn_sig(method.def_id).instantiate_identity(); // The method's first parameter must be named `self` if !method.fn_has_self_parameter { @@ -526,7 +500,7 @@ fn virtual_call_violation_for_method<'tcx>( // #78372 tcx.sess.delay_span_bug( tcx.def_span(method.def_id), - format!("error: {}\n while computing layout for type {:?}", err, ty), + format!("error: {err}\n while computing layout for type {ty:?}"), ); None } @@ -535,7 +509,7 @@ fn virtual_call_violation_for_method<'tcx>( // e.g., `Rc<()>` let unit_receiver_ty = - receiver_for_self_ty(tcx, receiver_ty, tcx.mk_unit(), method.def_id); + receiver_for_self_ty(tcx, receiver_ty, Ty::new_unit(tcx), method.def_id); match abi_of_ty(unit_receiver_ty) { Some(Abi::Scalar(..)) => (), @@ -543,8 +517,7 @@ fn virtual_call_violation_for_method<'tcx>( tcx.sess.delay_span_bug( tcx.def_span(method.def_id), format!( - "receiver when `Self = ()` should have a Scalar ABI; found {:?}", - abi + "receiver when `Self = ()` should have a Scalar ABI; found {abi:?}" ), ); } @@ -562,8 +535,7 @@ fn virtual_call_violation_for_method<'tcx>( tcx.sess.delay_span_bug( tcx.def_span(method.def_id), format!( - "receiver when `Self = {}` should have a ScalarPair ABI; found {:?}", - trait_object_ty, abi + "receiver when `Self = {trait_object_ty}` should have a ScalarPair ABI; found {abi:?}" ), ); } @@ -583,7 +555,7 @@ fn virtual_call_violation_for_method<'tcx>( // because a trait object can't claim to live longer than the concrete // type. If the lifetime bound holds on dyn Trait then it's guaranteed // to hold as well on the concrete type. - if pred.to_opt_type_outlives().is_some() { + if pred.as_type_outlives_clause().is_some() { return false; } @@ -600,11 +572,10 @@ fn virtual_call_violation_for_method<'tcx>( // only if the autotrait is one of the trait object's trait bounds, like // in `dyn Trait + AutoTrait`. This guarantees that trait objects only // implement auto traits if the underlying type does as well. - if let ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate { + if let ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref: pred_trait_ref, - constness: ty::BoundConstness::NotConst, polarity: ty::ImplPolarity::Positive, - })) = pred.kind().skip_binder() + }) = pred.kind().skip_binder() && pred_trait_ref.self_ty() == tcx.types.self_param && tcx.trait_is_auto(pred_trait_ref.def_id) { @@ -612,7 +583,7 @@ fn virtual_call_violation_for_method<'tcx>( // allowed to have generic parameters so `auto trait Bound {}` // would already have reported an error at the definition of the // auto trait. - if pred_trait_ref.substs.len() != 1 { + if pred_trait_ref.args.len() != 1 { tcx.sess.diagnostic().delay_span_bug( span, "auto traits cannot have generic parameters", @@ -638,11 +609,11 @@ fn receiver_for_self_ty<'tcx>( method_def_id: DefId, ) -> Ty<'tcx> { debug!("receiver_for_self_ty({:?}, {:?}, {:?})", receiver_ty, self_ty, method_def_id); - let substs = InternalSubsts::for_item(tcx, method_def_id, |param, _| { + let args = GenericArgs::for_item(tcx, method_def_id, |param, _| { if param.index == 0 { self_ty.into() } else { tcx.mk_param_from_def(param) } }); - let result = EarlyBinder(receiver_ty).subst(tcx, substs); + let result = EarlyBinder::bind(receiver_ty).instantiate(tcx, args); debug!( "receiver_for_self_ty({:?}, {:?}, {:?}) = {:?}", receiver_ty, self_ty, method_def_id, result @@ -690,7 +661,7 @@ fn object_ty_for_trait<'tcx>( ); debug!(?existential_predicates); - tcx.mk_dynamic(existential_predicates, lifetime, ty::Dyn) + Ty::new_dynamic(tcx, existential_predicates, lifetime, ty::Dyn) } /// Checks the method's receiver (the `self` argument) can be dispatched on when `Self` is a @@ -758,7 +729,7 @@ fn receiver_is_dispatchable<'tcx>( // FIXME(mikeyhew) this is a total hack. Once object_safe_for_dispatch is stabilized, we can // replace this with `dyn Trait` let unsized_self_ty: Ty<'tcx> = - tcx.mk_ty_param(u32::MAX, Symbol::intern("RustaceansAreAwesome")); + Ty::new_param(tcx, u32::MAX, Symbol::intern("RustaceansAreAwesome")); // `Receiver[Self => U]` let unsized_receiver_ty = @@ -772,27 +743,22 @@ fn receiver_is_dispatchable<'tcx>( // Self: Unsize let unsize_predicate = ty::TraitRef::new(tcx, unsize_did, [tcx.types.self_param, unsized_self_ty]) - .without_const() .to_predicate(tcx); // U: Trait let trait_predicate = { let trait_def_id = method.trait_container(tcx).unwrap(); - let substs = InternalSubsts::for_item(tcx, trait_def_id, |param, _| { + let args = GenericArgs::for_item(tcx, trait_def_id, |param, _| { if param.index == 0 { unsized_self_ty.into() } else { tcx.mk_param_from_def(param) } }); - ty::TraitRef::new(tcx, trait_def_id, substs).to_predicate(tcx) + ty::TraitRef::new(tcx, trait_def_id, args).to_predicate(tcx) }; let caller_bounds = param_env.caller_bounds().iter().chain([unsize_predicate, trait_predicate]); - ty::ParamEnv::new( - tcx.mk_predicates_from_iter(caller_bounds), - param_env.reveal(), - param_env.constness(), - ) + ty::ParamEnv::new(tcx.mk_clauses_from_iter(caller_bounds), param_env.reveal()) }; // Receiver: DispatchFromDyn U]> @@ -949,5 +915,10 @@ pub fn contains_illegal_impl_trait_in_trait<'tcx>( } pub fn provide(providers: &mut Providers) { - *providers = Providers { object_safety_violations, check_is_object_safe, ..*providers }; + *providers = Providers { + object_safety_violations, + check_is_object_safe, + generics_require_sized_self, + ..*providers + }; } diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 0db8023289127..32bbd626d4e49 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -1,15 +1,15 @@ use crate::infer::InferCtxt; -use crate::traits::query::type_op::{self, TypeOp, TypeOpOutput}; -use crate::traits::query::NoSolution; use crate::traits::{ObligationCause, ObligationCtxt}; use rustc_data_structures::fx::FxIndexSet; use rustc_infer::infer::resolve::OpportunisticRegionResolver; +use rustc_infer::infer::InferOk; +use rustc_middle::infer::canonical::{OriginalQueryValues, QueryRegionConstraints}; use rustc_middle::ty::{self, ParamEnv, Ty, TypeFolder, TypeVisitableExt}; use rustc_span::def_id::LocalDefId; pub use rustc_middle::traits::query::OutlivesBound; -type Bounds<'a, 'tcx: 'a> = impl Iterator> + 'a; +pub type Bounds<'a, 'tcx: 'a> = impl Iterator> + 'a; pub trait InferCtxtExt<'a, 'tcx> { fn implied_outlives_bounds( &self, @@ -57,35 +57,37 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> { let ty = OpportunisticRegionResolver::new(self).fold_ty(ty); // We do not expect existential variables in implied bounds. - // We may however encounter unconstrained lifetime variables in invalid - // code. See #110161 for context. + // We may however encounter unconstrained lifetime variables + // in very rare cases. + // + // See `ui/implied-bounds/implied-bounds-unconstrained-2.rs` for + // an example. assert!(!ty.has_non_region_infer()); - if ty.has_infer() { - self.tcx.sess.delay_span_bug( - self.tcx.def_span(body_id), - "skipped implied_outlives_bounds due to unconstrained lifetimes", - ); + + let mut canonical_var_values = OriginalQueryValues::default(); + let canonical_ty = + self.canonicalize_query_keep_static(param_env.and(ty), &mut canonical_var_values); + let Ok(canonical_result) = self.tcx.implied_outlives_bounds(canonical_ty) else { return vec![]; - } + }; - let span = self.tcx.def_span(body_id); - let result = param_env - .and(type_op::implied_outlives_bounds::ImpliedOutlivesBounds { ty }) - .fully_perform(self); - let result = match result { - Ok(r) => r, - Err(NoSolution) => { - self.tcx.sess.delay_span_bug( - span, - "implied_outlives_bounds failed to solve all obligations", - ); - return vec![]; - } + let mut constraints = QueryRegionConstraints::default(); + let Ok(InferOk { value, obligations }) = self + .instantiate_nll_query_response_and_region_obligations( + &ObligationCause::dummy(), + param_env, + &canonical_var_values, + canonical_result, + &mut constraints, + ) + else { + return vec![]; }; + assert_eq!(&obligations, &[]); - let TypeOpOutput { output, constraints, .. } = result; + if !constraints.is_empty() { + let span = self.tcx.def_span(body_id); - if let Some(constraints) = constraints { debug!(?constraints); if !constraints.member_constraints.is_empty() { span_bug!(span, "{:#?}", constraints.member_constraints); @@ -112,7 +114,7 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> { } }; - output + value } fn implied_bounds_tys( diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 51069897120af..06a1027e5dfdb 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -1,7 +1,8 @@ //! Code for projecting associated types out of trait references. +use super::check_args_compatible; use super::specialization_graph; -use super::translate_substs; +use super::translate_args; use super::util; use super::MismatchedProjectionTypes; use super::Obligation; @@ -10,11 +11,10 @@ use super::PredicateObligation; use super::Selection; use super::SelectionContext; use super::SelectionError; -use super::{ - ImplSourceClosureData, ImplSourceFnPointerData, ImplSourceFutureData, ImplSourceGeneratorData, - ImplSourceUserDefinedData, -}; use super::{Normalized, NormalizedTy, ProjectionCacheEntry, ProjectionCacheKey}; +use rustc_middle::traits::BuiltinImplSource; +use rustc_middle::traits::ImplSource; +use rustc_middle::traits::ImplSourceUserDefinedData; use crate::errors::InherentProjectionNormalizationOverflow; use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; @@ -30,7 +30,9 @@ use rustc_hir::lang_items::LangItem; use rustc_infer::infer::at::At; use rustc_infer::infer::resolve::OpportunisticRegionResolver; use rustc_infer::infer::DefineOpaqueTypes; -use rustc_infer::traits::ImplSourceBuiltinData; +use rustc_infer::traits::FulfillmentError; +use rustc_infer::traits::ObligationCauseCode; +use rustc_infer::traits::TraitEngine; use rustc_middle::traits::select::OverflowError; use rustc_middle::ty::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::visit::{MaxUniverse, TypeVisitable, TypeVisitableExt}; @@ -55,14 +57,55 @@ pub trait NormalizeExt<'tcx> { /// This normalization should be used when the type contains inference variables or the /// projection may be fallible. fn normalize>>(&self, t: T) -> InferOk<'tcx, T>; + + /// Deeply normalizes `value`, replacing all aliases which can by normalized in + /// the current environment. In the new solver this errors in case normalization + /// fails or is ambiguous. This only normalizes opaque types with `Reveal::All`. + /// + /// In the old solver this simply uses `normalizes` and adds the nested obligations + /// to the `fulfill_cx`. This is necessary as we otherwise end up recomputing the + /// same goals in both a temporary and the shared context which negatively impacts + /// performance as these don't share caching. + /// + /// FIXME(-Ztrait-solver=next): This has the same behavior as `traits::fully_normalize` + /// in the new solver, but because of performance reasons, we currently reuse an + /// existing fulfillment context in the old solver. Once we also eagerly prove goals with + /// the old solver or have removed the old solver, remove `traits::fully_normalize` and + /// rename this function to `At::fully_normalize`. + fn deeply_normalize>>( + self, + value: T, + fulfill_cx: &mut dyn TraitEngine<'tcx>, + ) -> Result>>; } impl<'tcx> NormalizeExt<'tcx> for At<'_, 'tcx> { fn normalize>>(&self, value: T) -> InferOk<'tcx, T> { - let mut selcx = SelectionContext::new(self.infcx); - let Normalized { value, obligations } = - normalize_with_depth(&mut selcx, self.param_env, self.cause.clone(), 0, value); - InferOk { value, obligations } + if self.infcx.next_trait_solver() { + InferOk { value, obligations: Vec::new() } + } else { + let mut selcx = SelectionContext::new(self.infcx); + let Normalized { value, obligations } = + normalize_with_depth(&mut selcx, self.param_env, self.cause.clone(), 0, value); + InferOk { value, obligations } + } + } + + fn deeply_normalize>>( + self, + value: T, + fulfill_cx: &mut dyn TraitEngine<'tcx>, + ) -> Result>> { + if self.infcx.next_trait_solver() { + crate::solve::deeply_normalize(self, value) + } else { + let value = self + .normalize(value) + .into_value_registering_obligations(self.infcx, &mut *fulfill_cx); + let errors = fulfill_cx.select_where_possible(self.infcx); + let value = self.infcx.resolve_vars_if_possible(value); + if errors.is_empty() { Ok(value) } else { Err(errors) } + } } } @@ -90,8 +133,6 @@ enum ProjectionCandidate<'tcx> { /// From an "impl" (or a "pseudo-impl" returned by select) Select(Selection<'tcx>), - - ImplTraitInTrait(ImplSourceUserDefinedData<'tcx, PredicateObligation<'tcx>>), } enum ProjectionCandidateSet<'tcx> { @@ -406,6 +447,7 @@ impl<'a, 'b, 'tcx> AssocTypeNormalizer<'a, 'b, 'tcx> { depth: usize, obligations: &'a mut Vec>, ) -> AssocTypeNormalizer<'a, 'b, 'tcx> { + debug_assert!(!selcx.infcx.next_trait_solver()); AssocTypeNormalizer { selcx, param_env, @@ -441,8 +483,7 @@ impl<'a, 'b, 'tcx> AssocTypeNormalizer<'a, 'b, 'tcx> { assert!( !value.has_escaping_bound_vars(), - "Normalizing {:?} without wrapping in a `Binder`", - value + "Normalizing {value:?} without wrapping in a `Binder`" ); if !needs_normalization(&value, self.param_env.reveal()) { @@ -484,7 +525,7 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx // ``` // for<'a> fn(::One<'a, Box::Two<'a>>>>) // ``` - // We normalize the substs on the projection before the projecting, but + // We normalize the args on the projection before the projecting, but // if we're naive, we'll // replace bound vars on inner, project inner, replace placeholders on inner, // replace bound vars on outer, project outer, replace placeholders on outer @@ -499,13 +540,10 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx // // On the other hand, this does add a bit of complexity, since we only // replace bound vars if the current type is a `Projection` and we need - // to make sure we don't forget to fold the substs regardless. + // to make sure we don't forget to fold the args regardless. match kind { - // This is really important. While we *can* handle this, this has - // severe performance implications for large opaque types with - // late-bound regions. See `issue-88862` benchmark. - ty::Opaque if !data.substs.has_escaping_bound_vars() => { + ty::Opaque => { // Only normalize `impl Trait` outside of type inference, usually in codegen. match self.param_env.reveal() { Reveal::UserFacing => ty.super_fold_with(self), @@ -521,9 +559,9 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx ); } - let substs = data.substs.fold_with(self); + let args = data.args.fold_with(self); let generic_ty = self.interner().type_of(data.def_id); - let concrete_ty = generic_ty.subst(self.interner(), substs); + let concrete_ty = generic_ty.instantiate(self.interner(), args); self.depth += 1; let folded_ty = self.fold_ty(concrete_ty); self.depth -= 1; @@ -531,7 +569,6 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx } } } - ty::Opaque => ty.super_fold_with(self), ty::Projection if !data.has_escaping_bound_vars() => { // This branch is *mostly* just an optimization: when we don't @@ -621,6 +658,28 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx ); normalized_ty } + ty::Weak => { + let infcx = self.selcx.infcx; + self.obligations.extend( + infcx.tcx.predicates_of(data.def_id).instantiate_own(infcx.tcx, data.args).map( + |(mut predicate, span)| { + if data.has_escaping_bound_vars() { + (predicate, ..) = BoundVarReplacer::replace_bound_vars( + infcx, + &mut self.universes, + predicate, + ); + } + let mut cause = self.cause.clone(); + cause.map_code(|code| { + ObligationCauseCode::TypeAlias(code, span, data.def_id) + }); + Obligation::new(infcx.tcx, cause, self.param_env, predicate) + }, + ), + ); + infcx.tcx.type_of(data.def_id).instantiate(infcx.tcx, data.args).fold_with(self) + } ty::Inherent if !data.has_escaping_bound_vars() => { // This branch is *mostly* just an optimization: when we don't @@ -672,7 +731,9 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx #[instrument(skip(self), level = "debug")] fn fold_const(&mut self, constant: ty::Const<'tcx>) -> ty::Const<'tcx> { let tcx = self.selcx.tcx(); - if tcx.lazy_normalization() || !needs_normalization(&constant, self.param_env.reveal()) { + if tcx.features().generic_const_exprs + || !needs_normalization(&constant, self.param_env.reveal()) + { constant } else { let constant = constant.super_fold_with(self); @@ -824,7 +885,7 @@ impl<'tcx> TypeFolder> for BoundVarReplacer<'_, 'tcx> { let universe = self.universe_for(debruijn); let p = ty::PlaceholderRegion { universe, bound: br }; self.mapped_regions.insert(p, br); - self.infcx.tcx.mk_re_placeholder(p) + ty::Region::new_placeholder(self.infcx.tcx, p) } _ => r, } @@ -842,7 +903,7 @@ impl<'tcx> TypeFolder> for BoundVarReplacer<'_, 'tcx> { let universe = self.universe_for(debruijn); let p = ty::PlaceholderType { universe, bound: bound_ty }; self.mapped_types.insert(p, bound_ty); - self.infcx.tcx.mk_placeholder(p) + Ty::new_placeholder(self.infcx.tcx, p) } _ if t.has_vars_bound_at_or_above(self.current_index) => t.super_fold_with(self), _ => t, @@ -861,7 +922,7 @@ impl<'tcx> TypeFolder> for BoundVarReplacer<'_, 'tcx> { let universe = self.universe_for(debruijn); let p = ty::PlaceholderConst { universe, bound: bound_const }; self.mapped_consts.insert(p, bound_const); - self.infcx.tcx.mk_const(p, ct.ty()) + ty::Const::new_placeholder(self.infcx.tcx, p, ct.ty()) } _ => ct.super_fold_with(self), } @@ -945,7 +1006,7 @@ impl<'tcx> TypeFolder> for PlaceholderReplacer<'_, 'tcx> { let db = ty::DebruijnIndex::from_usize( self.universe_indices.len() - index + self.current_index.as_usize() - 1, ); - self.interner().mk_re_late_bound(db, *replace_var) + ty::Region::new_late_bound(self.interner(), db, *replace_var) } None => r1, } @@ -972,7 +1033,7 @@ impl<'tcx> TypeFolder> for PlaceholderReplacer<'_, 'tcx> { let db = ty::DebruijnIndex::from_usize( self.universe_indices.len() - index + self.current_index.as_usize() - 1, ); - self.interner().mk_bound(db, *replace_var) + Ty::new_bound(self.infcx.tcx, db, *replace_var) } None => ty, } @@ -996,7 +1057,7 @@ impl<'tcx> TypeFolder> for PlaceholderReplacer<'_, 'tcx> { let db = ty::DebruijnIndex::from_usize( self.universe_indices.len() - index + self.current_index.as_usize() - 1, ); - self.interner().mk_const(ty::ConstKind::Bound(db, *replace_var), ct.ty()) + ty::Const::new_bound(self.infcx.tcx, db, *replace_var, ct.ty()) } None => ct, } @@ -1059,6 +1120,7 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>( obligations: &mut Vec>, ) -> Result>, InProgress> { let infcx = selcx.infcx; + debug_assert!(!selcx.infcx.next_trait_solver()); // Don't use the projection cache in intercrate mode - // the `infcx` may be re-used between intercrate in non-intercrate // mode, which could lead to using incorrect cache results. @@ -1152,7 +1214,7 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>( let projected_term = selcx.infcx.resolve_vars_if_possible(projected_term); - let mut result = if projected_term.has_projections() { + let result = if projected_term.has_projections() { let mut normalizer = AssocTypeNormalizer::new( selcx, param_env, @@ -1162,19 +1224,14 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>( ); let normalized_ty = normalizer.fold(projected_term); + let mut deduped = SsoHashSet::with_capacity(projected_obligations.len()); + projected_obligations.retain(|obligation| deduped.insert(obligation.clone())); + Normalized { value: normalized_ty, obligations: projected_obligations } } else { Normalized { value: projected_term, obligations: projected_obligations } }; - let mut deduped: SsoHashSet<_> = Default::default(); - result.obligations.drain_filter(|projected_obligation| { - if !deduped.insert(projected_obligation.clone()) { - return true; - } - false - }); - if use_cache { infcx.inner.borrow_mut().projection_cache().insert_term(cache_key, result.clone()); } @@ -1244,7 +1301,7 @@ fn normalize_to_error<'a, 'tcx>( cause, recursion_depth: depth, param_env, - predicate: trait_ref.without_const().to_predicate(selcx.tcx()), + predicate: trait_ref.to_predicate(selcx.tcx()), }; let tcx = selcx.infcx.tcx; let new_value = selcx.infcx.next_ty_var(TypeVariableOrigin { @@ -1274,7 +1331,7 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>( }); } - let substs = compute_inherent_assoc_ty_substs( + let args = compute_inherent_assoc_ty_args( selcx, param_env, alias_ty, @@ -1284,7 +1341,7 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>( ); // Register the obligations arising from the impl and from the associated type itself. - let predicates = tcx.predicates_of(alias_ty.def_id).instantiate(tcx, substs); + let predicates = tcx.predicates_of(alias_ty.def_id).instantiate(tcx, args); for (predicate, span) in predicates { let predicate = normalize_with_depth_to( selcx, @@ -1318,7 +1375,7 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>( )); } - let ty = tcx.type_of(alias_ty.def_id).subst(tcx, substs); + let ty = tcx.type_of(alias_ty.def_id).instantiate(tcx, args); let mut ty = selcx.infcx.resolve_vars_if_possible(ty); if ty.has_projections() { @@ -1328,22 +1385,30 @@ pub fn normalize_inherent_projection<'a, 'b, 'tcx>( ty } -pub fn compute_inherent_assoc_ty_substs<'a, 'b, 'tcx>( +pub fn compute_inherent_assoc_ty_args<'a, 'b, 'tcx>( selcx: &'a mut SelectionContext<'b, 'tcx>, param_env: ty::ParamEnv<'tcx>, alias_ty: ty::AliasTy<'tcx>, cause: ObligationCause<'tcx>, depth: usize, obligations: &mut Vec>, -) -> ty::SubstsRef<'tcx> { +) -> ty::GenericArgsRef<'tcx> { let tcx = selcx.tcx(); let impl_def_id = tcx.parent(alias_ty.def_id); - let impl_substs = selcx.infcx.fresh_substs_for_item(cause.span, impl_def_id); + let impl_args = selcx.infcx.fresh_args_for_item(cause.span, impl_def_id); - let impl_ty = tcx.type_of(impl_def_id).subst(tcx, impl_substs); - let impl_ty = - normalize_with_depth_to(selcx, param_env, cause.clone(), depth + 1, impl_ty, obligations); + let mut impl_ty = tcx.type_of(impl_def_id).instantiate(tcx, impl_args); + if !selcx.infcx.next_trait_solver() { + impl_ty = normalize_with_depth_to( + selcx, + param_env, + cause.clone(), + depth + 1, + impl_ty, + obligations, + ); + } // Infer the generic parameters of the impl by unifying the // impl type with the self type of the projection. @@ -1360,7 +1425,7 @@ pub fn compute_inherent_assoc_ty_substs<'a, 'b, 'tcx>( } } - alias_ty.rebase_substs_onto_impl(impl_substs, tcx) + alias_ty.rebase_inherent_args_onto_impl(impl_args, tcx) } enum Projected<'tcx> { @@ -1375,7 +1440,7 @@ struct Progress<'tcx> { impl<'tcx> Progress<'tcx> { fn error(tcx: TyCtxt<'tcx>, guar: ErrorGuaranteed) -> Self { - Progress { term: tcx.ty_error(guar).into(), obligations: vec![] } + Progress { term: Ty::new_error(tcx, guar).into(), obligations: vec![] } } fn with_addl_obligations(mut self, mut obligations: Vec>) -> Self { @@ -1407,8 +1472,6 @@ fn project<'cx, 'tcx>( let mut candidates = ProjectionCandidateSet::None; - assemble_candidate_for_impl_trait_in_trait(selcx, obligation, &mut candidates); - // Make sure that the following procedures are kept in order. ParamEnv // needs to be first because it has highest priority, and Select checks // the return value of push_candidate which assumes it's ran at last. @@ -1434,19 +1497,20 @@ fn project<'cx, 'tcx>( ProjectionCandidateSet::None => { let tcx = selcx.tcx(); let term = match tcx.def_kind(obligation.predicate.def_id) { - DefKind::AssocTy | DefKind::ImplTraitPlaceholder => tcx - .mk_projection(obligation.predicate.def_id, obligation.predicate.substs) - .into(), - DefKind::AssocConst => tcx - .mk_const( - ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new( - obligation.predicate.def_id, - obligation.predicate.substs, - )), - tcx.type_of(obligation.predicate.def_id) - .subst(tcx, obligation.predicate.substs), - ) - .into(), + DefKind::AssocTy => { + Ty::new_projection(tcx, obligation.predicate.def_id, obligation.predicate.args) + .into() + } + DefKind::AssocConst => ty::Const::new_unevaluated( + tcx, + ty::UnevaluatedConst::new( + obligation.predicate.def_id, + obligation.predicate.args, + ), + tcx.type_of(obligation.predicate.def_id) + .instantiate(tcx, obligation.predicate.args), + ) + .into(), kind => { bug!("unknown projection def-id: {}", kind.descr(obligation.predicate.def_id)) } @@ -1462,48 +1526,6 @@ fn project<'cx, 'tcx>( } } -/// If the predicate's item is an `ImplTraitPlaceholder`, we do a select on the -/// corresponding trait ref. If this yields an `impl`, then we're able to project -/// to a concrete type, since we have an `impl`'s method to provide the RPITIT. -fn assemble_candidate_for_impl_trait_in_trait<'cx, 'tcx>( - selcx: &mut SelectionContext<'cx, 'tcx>, - obligation: &ProjectionTyObligation<'tcx>, - candidate_set: &mut ProjectionCandidateSet<'tcx>, -) { - let tcx = selcx.tcx(); - if tcx.def_kind(obligation.predicate.def_id) == DefKind::ImplTraitPlaceholder { - let trait_fn_def_id = tcx.impl_trait_in_trait_parent_fn(obligation.predicate.def_id); - - let trait_def_id = tcx.parent(trait_fn_def_id); - let trait_substs = - obligation.predicate.substs.truncate_to(tcx, tcx.generics_of(trait_def_id)); - // FIXME(named-returns): Binders - let trait_predicate = ty::TraitRef::new(tcx, trait_def_id, trait_substs); - - let _ = selcx.infcx.commit_if_ok(|_| { - match selcx.select(&obligation.with(tcx, trait_predicate)) { - Ok(Some(super::ImplSource::UserDefined(data))) => { - candidate_set.push_candidate(ProjectionCandidate::ImplTraitInTrait(data)); - Ok(()) - } - Ok(None) => { - candidate_set.mark_ambiguous(); - Err(()) - } - Ok(Some(_)) => { - // Don't know enough about the impl to provide a useful signature - Err(()) - } - Err(e) => { - debug!(error = ?e, "selection error"); - candidate_set.mark_error(e); - Err(()) - } - } - }); - } -} - /// The first thing we have to do is scan through the parameter /// environment to see whether there are any projection predicates /// there that can answer this question. @@ -1543,9 +1565,9 @@ fn assemble_candidates_from_trait_def<'cx, 'tcx>( // Check whether the self-type is itself a projection. // If so, extract what we know from the trait and try to come up with a good answer. let bounds = match *obligation.predicate.self_ty().kind() { - // Excluding IATs here as they don't have meaningful item bounds. + // Excluding IATs and type aliases here as they don't have meaningful item bounds. ty::Alias(ty::Projection | ty::Opaque, ref data) => { - tcx.item_bounds(data.def_id).subst(tcx, data.substs) + tcx.item_bounds(data.def_id).instantiate(tcx, data.args) } ty::Infer(ty::TyVar(_)) => { // If the self-type is an inference variable, then it MAY wind up @@ -1584,6 +1606,10 @@ fn assemble_candidates_from_object_ty<'cx, 'tcx>( let tcx = selcx.tcx(); + if !tcx.trait_def(obligation.predicate.trait_def_id(tcx)).implement_via_object { + return; + } + let self_ty = obligation.predicate.self_ty(); let object_ty = selcx.infcx.shallow_resolve(self_ty); let data = match object_ty.kind() { @@ -1620,15 +1646,13 @@ fn assemble_candidates_from_predicates<'cx, 'tcx>( obligation: &ProjectionTyObligation<'tcx>, candidate_set: &mut ProjectionCandidateSet<'tcx>, ctor: fn(ty::PolyProjectionPredicate<'tcx>) -> ProjectionCandidate<'tcx>, - env_predicates: impl Iterator>, + env_predicates: impl Iterator>, potentially_unnormalized_candidates: bool, ) { let infcx = selcx.infcx; for predicate in env_predicates { let bound_predicate = predicate.kind(); - if let ty::PredicateKind::Clause(ty::Clause::Projection(data)) = - predicate.kind().skip_binder() - { + if let ty::ClauseKind::Projection(data) = predicate.kind().skip_binder() { let data = bound_predicate.rebind(data); if data.projection_def_id() != obligation.predicate.def_id { continue; @@ -1670,15 +1694,10 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( obligation: &ProjectionTyObligation<'tcx>, candidate_set: &mut ProjectionCandidateSet<'tcx>, ) { - // Can't assemble candidate from impl for RPITIT - if selcx.tcx().def_kind(obligation.predicate.def_id) == DefKind::ImplTraitPlaceholder { - return; - } - // If we are resolving `>::Item == Type`, // start out by selecting the predicate `T as TraitRef<...>`: - let poly_trait_ref = ty::Binder::dummy(obligation.predicate.trait_ref(selcx.tcx())); - let trait_obligation = obligation.with(selcx.tcx(), poly_trait_ref); + let trait_ref = obligation.predicate.trait_ref(selcx.tcx()); + let trait_obligation = obligation.with(selcx.tcx(), trait_ref); let _ = selcx.infcx.commit_if_ok(|_| { let impl_source = match selcx.select(&trait_obligation) { Ok(Some(impl_source)) => impl_source, @@ -1694,12 +1713,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( }; let eligible = match &impl_source { - super::ImplSource::Closure(_) - | super::ImplSource::Generator(_) - | super::ImplSource::Future(_) - | super::ImplSource::FnPointer(_) - | super::ImplSource::TraitAlias(_) => true, - super::ImplSource::UserDefined(impl_data) => { + ImplSource::UserDefined(impl_data) => { // We have to be careful when projecting out of an // impl because of specialization. If we are not in // codegen (i.e., projection mode is not "any"), and the @@ -1737,7 +1751,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( if obligation.param_env.reveal() == Reveal::All { // NOTE(eddyb) inference variables can resolve to parameters, so // assume `poly_trait_ref` isn't monomorphic, if it contains any. - let poly_trait_ref = selcx.infcx.resolve_vars_if_possible(poly_trait_ref); + let poly_trait_ref = selcx.infcx.resolve_vars_if_possible(trait_ref); !poly_trait_ref.still_further_specializable() } else { debug!( @@ -1749,14 +1763,18 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( } } } - super::ImplSource::Builtin(..) => { + ImplSource::Builtin(BuiltinImplSource::Misc, _) => { // While a builtin impl may be known to exist, the associated type may not yet // be known. Any type with multiple potential associated types is therefore // not eligible. let self_ty = selcx.infcx.shallow_resolve(obligation.predicate.self_ty()); let lang_items = selcx.tcx().lang_items(); - if lang_items.discriminant_kind_trait() == Some(poly_trait_ref.def_id()) { + if [lang_items.gen_trait(), lang_items.future_trait()].contains(&Some(trait_ref.def_id)) + || selcx.tcx().fn_trait_kind_from_def_id(trait_ref.def_id).is_some() + { + true + } else if lang_items.discriminant_kind_trait() == Some(trait_ref.def_id) { match self_ty.kind() { ty::Bool | ty::Char @@ -1791,7 +1809,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( | ty::Infer(..) | ty::Error(_) => false, } - } else if lang_items.pointee_trait() == Some(poly_trait_ref.def_id()) { + } else if lang_items.pointee_trait() == Some(trait_ref.def_id) { let tail = selcx.tcx().struct_tail_with_normalize( self_ty, |ty| { @@ -1844,8 +1862,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( if selcx.infcx.predicate_must_hold_modulo_regions( &obligation.with( selcx.tcx(), - ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty]) - .without_const(), + ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty]), ), ) => { @@ -1866,10 +1883,10 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( } } } else { - bug!("unexpected builtin trait with associated type: {poly_trait_ref:?}") + bug!("unexpected builtin trait with associated type: {trait_ref:?}") } } - super::ImplSource::Param(..) => { + ImplSource::Param(..) => { // This case tell us nothing about the value of an // associated type. Consider: // @@ -1897,19 +1914,18 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( // in `assemble_candidates_from_param_env`. false } - super::ImplSource::Object(_) => { + ImplSource::Builtin(BuiltinImplSource::Object { .. }, _) => { // Handled by the `Object` projection candidate. See // `assemble_candidates_from_object_ty` for an explanation of // why we special case object types. false } - super::ImplSource::AutoImpl(..) - | super::ImplSource::TraitUpcasting(_) - | super::ImplSource::ConstDestruct(_) => { + ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) + | ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => { // These traits have no associated types. selcx.tcx().sess.delay_span_bug( obligation.cause.span, - format!("Cannot project an associated type from `{:?}`", impl_source), + format!("Cannot project an associated type from `{impl_source:?}`"), ); return Err(()); } @@ -1946,9 +1962,6 @@ fn confirm_candidate<'cx, 'tcx>( ProjectionCandidate::Select(impl_source) => { confirm_select_candidate(selcx, obligation, impl_source) } - ProjectionCandidate::ImplTraitInTrait(data) => { - confirm_impl_trait_in_trait_candidate(selcx, obligation, data) - } }; // When checking for cycle during evaluation, we compare predicates with @@ -1968,18 +1981,28 @@ fn confirm_select_candidate<'cx, 'tcx>( impl_source: Selection<'tcx>, ) -> Progress<'tcx> { match impl_source { - super::ImplSource::UserDefined(data) => confirm_impl_candidate(selcx, obligation, data), - super::ImplSource::Generator(data) => confirm_generator_candidate(selcx, obligation, data), - super::ImplSource::Future(data) => confirm_future_candidate(selcx, obligation, data), - super::ImplSource::Closure(data) => confirm_closure_candidate(selcx, obligation, data), - super::ImplSource::FnPointer(data) => confirm_fn_pointer_candidate(selcx, obligation, data), - super::ImplSource::Builtin(data) => confirm_builtin_candidate(selcx, obligation, data), - super::ImplSource::Object(_) - | super::ImplSource::AutoImpl(..) - | super::ImplSource::Param(..) - | super::ImplSource::TraitUpcasting(_) - | super::ImplSource::TraitAlias(..) - | super::ImplSource::ConstDestruct(_) => { + ImplSource::UserDefined(data) => confirm_impl_candidate(selcx, obligation, data), + ImplSource::Builtin(BuiltinImplSource::Misc, data) => { + let trait_def_id = obligation.predicate.trait_def_id(selcx.tcx()); + let lang_items = selcx.tcx().lang_items(); + if lang_items.gen_trait() == Some(trait_def_id) { + confirm_generator_candidate(selcx, obligation, data) + } else if lang_items.future_trait() == Some(trait_def_id) { + confirm_future_candidate(selcx, obligation, data) + } else if selcx.tcx().fn_trait_kind_from_def_id(trait_def_id).is_some() { + if obligation.predicate.self_ty().is_closure() { + confirm_closure_candidate(selcx, obligation, data) + } else { + confirm_fn_pointer_candidate(selcx, obligation, data) + } + } else { + confirm_builtin_candidate(selcx, obligation, data) + } + } + ImplSource::Builtin(BuiltinImplSource::Object { .. }, _) + | ImplSource::Param(..) + | ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) + | ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => { // we don't create Select candidates with this kind of resolution span_bug!( obligation.cause.span, @@ -1993,9 +2016,14 @@ fn confirm_select_candidate<'cx, 'tcx>( fn confirm_generator_candidate<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, obligation: &ProjectionTyObligation<'tcx>, - impl_source: ImplSourceGeneratorData<'tcx, PredicateObligation<'tcx>>, + nested: Vec>, ) -> Progress<'tcx> { - let gen_sig = impl_source.substs.as_generator().poly_sig(); + let ty::Generator(_, args, _) = + selcx.infcx.shallow_resolve(obligation.predicate.self_ty()).kind() + else { + unreachable!() + }; + let gen_sig = args.as_generator().poly_sig(); let Normalized { value: gen_sig, obligations } = normalize_with_depth( selcx, obligation.param_env, @@ -2027,22 +2055,27 @@ fn confirm_generator_candidate<'cx, 'tcx>( }; ty::ProjectionPredicate { - projection_ty: tcx.mk_alias_ty(obligation.predicate.def_id, trait_ref.substs), + projection_ty: tcx.mk_alias_ty(obligation.predicate.def_id, trait_ref.args), term: ty.into(), } }); confirm_param_env_candidate(selcx, obligation, predicate, false) - .with_addl_obligations(impl_source.nested) + .with_addl_obligations(nested) .with_addl_obligations(obligations) } fn confirm_future_candidate<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, obligation: &ProjectionTyObligation<'tcx>, - impl_source: ImplSourceFutureData<'tcx, PredicateObligation<'tcx>>, + nested: Vec>, ) -> Progress<'tcx> { - let gen_sig = impl_source.substs.as_generator().poly_sig(); + let ty::Generator(_, args, _) = + selcx.infcx.shallow_resolve(obligation.predicate.self_ty()).kind() + else { + unreachable!() + }; + let gen_sig = args.as_generator().poly_sig(); let Normalized { value: gen_sig, obligations } = normalize_with_depth( selcx, obligation.param_env, @@ -2066,24 +2099,24 @@ fn confirm_future_candidate<'cx, 'tcx>( debug_assert_eq!(tcx.associated_item(obligation.predicate.def_id).name, sym::Output); ty::ProjectionPredicate { - projection_ty: tcx.mk_alias_ty(obligation.predicate.def_id, trait_ref.substs), + projection_ty: tcx.mk_alias_ty(obligation.predicate.def_id, trait_ref.args), term: return_ty.into(), } }); confirm_param_env_candidate(selcx, obligation, predicate, false) - .with_addl_obligations(impl_source.nested) + .with_addl_obligations(nested) .with_addl_obligations(obligations) } fn confirm_builtin_candidate<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, obligation: &ProjectionTyObligation<'tcx>, - data: ImplSourceBuiltinData>, + data: Vec>, ) -> Progress<'tcx> { let tcx = selcx.tcx(); let self_ty = obligation.predicate.self_ty(); - let substs = tcx.mk_substs(&[self_ty.into()]); + let args = tcx.mk_args(&[self_ty.into()]); let lang_items = tcx.lang_items(); let item_def_id = obligation.predicate.def_id; let trait_def_id = tcx.trait_of_item(item_def_id).unwrap(); @@ -2113,8 +2146,7 @@ fn confirm_builtin_candidate<'cx, 'tcx>( LangItem::Sized, obligation.cause.span(), [self_ty], - ) - .without_const(); + ); obligations.push(obligation.with(tcx, sized_predicate)); } (metadata_ty.into(), obligations) @@ -2123,19 +2155,19 @@ fn confirm_builtin_candidate<'cx, 'tcx>( }; let predicate = - ty::ProjectionPredicate { projection_ty: tcx.mk_alias_ty(item_def_id, substs), term }; + ty::ProjectionPredicate { projection_ty: tcx.mk_alias_ty(item_def_id, args), term }; confirm_param_env_candidate(selcx, obligation, ty::Binder::dummy(predicate), false) .with_addl_obligations(obligations) - .with_addl_obligations(data.nested) + .with_addl_obligations(data) } fn confirm_fn_pointer_candidate<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, obligation: &ProjectionTyObligation<'tcx>, - fn_pointer_impl_source: ImplSourceFnPointerData<'tcx, PredicateObligation<'tcx>>, + nested: Vec>, ) -> Progress<'tcx> { - let fn_type = selcx.infcx.shallow_resolve(fn_pointer_impl_source.fn_ty); + let fn_type = selcx.infcx.shallow_resolve(obligation.predicate.self_ty()); let sig = fn_type.fn_sig(selcx.tcx()); let Normalized { value: sig, obligations } = normalize_with_depth( selcx, @@ -2146,16 +2178,20 @@ fn confirm_fn_pointer_candidate<'cx, 'tcx>( ); confirm_callable_candidate(selcx, obligation, sig, util::TupleArgumentsFlag::Yes) - .with_addl_obligations(fn_pointer_impl_source.nested) + .with_addl_obligations(nested) .with_addl_obligations(obligations) } fn confirm_closure_candidate<'cx, 'tcx>( selcx: &mut SelectionContext<'cx, 'tcx>, obligation: &ProjectionTyObligation<'tcx>, - impl_source: ImplSourceClosureData<'tcx, PredicateObligation<'tcx>>, + nested: Vec>, ) -> Progress<'tcx> { - let closure_sig = impl_source.substs.as_closure().sig(); + let ty::Closure(_, args) = selcx.infcx.shallow_resolve(obligation.predicate.self_ty()).kind() + else { + unreachable!() + }; + let closure_sig = args.as_closure().sig(); let Normalized { value: closure_sig, obligations } = normalize_with_depth( selcx, obligation.param_env, @@ -2167,7 +2203,7 @@ fn confirm_closure_candidate<'cx, 'tcx>( debug!(?obligation, ?closure_sig, ?obligations, "confirm_closure_candidate"); confirm_callable_candidate(selcx, obligation, closure_sig, util::TupleArgumentsFlag::No) - .with_addl_obligations(impl_source.nested) + .with_addl_obligations(nested) .with_addl_obligations(obligations) } @@ -2192,7 +2228,7 @@ fn confirm_callable_candidate<'cx, 'tcx>( flag, ) .map_bound(|(trait_ref, ret_type)| ty::ProjectionPredicate { - projection_ty: tcx.mk_alias_ty(fn_once_output_def_id, trait_ref.substs), + projection_ty: tcx.mk_alias_ty(fn_once_output_def_id, trait_ref.args), term: ret_type.into(), }); @@ -2259,11 +2295,10 @@ fn confirm_param_env_candidate<'cx, 'tcx>( } Err(e) => { let msg = format!( - "Failed to unify obligation `{:?}` with poly_projection `{:?}`: {:?}", - obligation, poly_cache_entry, e, + "Failed to unify obligation `{obligation:?}` with poly_projection `{poly_cache_entry:?}`: {e:?}", ); debug!("confirm_param_env_candidate: {}", msg); - let err = infcx.tcx.ty_error_with_message(obligation.cause.span, msg); + let err = Ty::new_error_with_message(infcx.tcx, obligation.cause.span, msg); Progress { term: err.into(), obligations: vec![] } } } @@ -2276,7 +2311,7 @@ fn confirm_impl_candidate<'cx, 'tcx>( ) -> Progress<'tcx> { let tcx = selcx.tcx(); - let ImplSourceUserDefinedData { impl_def_id, substs, mut nested } = impl_impl_source; + let ImplSourceUserDefinedData { impl_def_id, args, mut nested } = impl_impl_source; let assoc_item_id = obligation.predicate.def_id; let trait_def_id = tcx.trait_id_of_impl(impl_def_id).unwrap(); @@ -2295,173 +2330,37 @@ fn confirm_impl_candidate<'cx, 'tcx>( "confirm_impl_candidate: no associated type {:?} for {:?}", assoc_ty.item.name, obligation.predicate ); - return Progress { term: tcx.ty_error_misc().into(), obligations: nested }; + return Progress { term: Ty::new_misc_error(tcx).into(), obligations: nested }; } // If we're trying to normalize ` as X>::A` using //`impl X for Vec { type A = Box; }`, then: // - // * `obligation.predicate.substs` is `[Vec, S]` - // * `substs` is `[u32]` - // * `substs` ends up as `[u32, S]` - let substs = obligation.predicate.substs.rebase_onto(tcx, trait_def_id, substs); - let substs = - translate_substs(selcx.infcx, param_env, impl_def_id, substs, assoc_ty.defining_node); + // * `obligation.predicate.args` is `[Vec, S]` + // * `args` is `[u32]` + // * `args` ends up as `[u32, S]` + let args = obligation.predicate.args.rebase_onto(tcx, trait_def_id, args); + let args = translate_args(selcx.infcx, param_env, impl_def_id, args, assoc_ty.defining_node); let ty = tcx.type_of(assoc_ty.item.def_id); let is_const = matches!(tcx.def_kind(assoc_ty.item.def_id), DefKind::AssocConst); let term: ty::EarlyBinder> = if is_const { let did = assoc_ty.item.def_id; - let identity_substs = crate::traits::InternalSubsts::identity_for_item(tcx, did); - let kind = ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs)); - ty.map_bound(|ty| tcx.mk_const(kind, ty).into()) + let identity_args = crate::traits::GenericArgs::identity_for_item(tcx, did); + let uv = ty::UnevaluatedConst::new(did, identity_args); + ty.map_bound(|ty| ty::Const::new_unevaluated(tcx, uv, ty).into()) } else { ty.map_bound(|ty| ty.into()) }; - if !check_substs_compatible(tcx, assoc_ty.item, substs) { - let err = tcx.ty_error_with_message( + if !check_args_compatible(tcx, assoc_ty.item, args) { + let err = Ty::new_error_with_message( + tcx, obligation.cause.span, "impl item and trait item have different parameters", ); Progress { term: err.into(), obligations: nested } } else { assoc_ty_own_obligations(selcx, obligation, &mut nested); - Progress { term: term.subst(tcx, substs), obligations: nested } - } -} - -// Verify that the trait item and its implementation have compatible substs lists -fn check_substs_compatible<'tcx>( - tcx: TyCtxt<'tcx>, - assoc_item: ty::AssocItem, - substs: ty::SubstsRef<'tcx>, -) -> bool { - fn check_substs_compatible_inner<'tcx>( - tcx: TyCtxt<'tcx>, - generics: &'tcx ty::Generics, - args: &'tcx [ty::GenericArg<'tcx>], - ) -> bool { - if generics.count() != args.len() { - return false; - } - - let (parent_args, own_args) = args.split_at(generics.parent_count); - - if let Some(parent) = generics.parent - && let parent_generics = tcx.generics_of(parent) - && !check_substs_compatible_inner(tcx, parent_generics, parent_args) { - return false; - } - - for (param, arg) in std::iter::zip(&generics.params, own_args) { - match (¶m.kind, arg.unpack()) { - (ty::GenericParamDefKind::Type { .. }, ty::GenericArgKind::Type(_)) - | (ty::GenericParamDefKind::Lifetime, ty::GenericArgKind::Lifetime(_)) - | (ty::GenericParamDefKind::Const { .. }, ty::GenericArgKind::Const(_)) => {} - _ => return false, - } - } - - true + Progress { term: term.instantiate(tcx, args), obligations: nested } } - - let generics = tcx.generics_of(assoc_item.def_id); - // Chop off any additional substs (RPITIT) substs - let substs = &substs[0..generics.count().min(substs.len())]; - check_substs_compatible_inner(tcx, generics, substs) -} - -fn confirm_impl_trait_in_trait_candidate<'tcx>( - selcx: &mut SelectionContext<'_, 'tcx>, - obligation: &ProjectionTyObligation<'tcx>, - data: ImplSourceUserDefinedData<'tcx, PredicateObligation<'tcx>>, -) -> Progress<'tcx> { - let tcx = selcx.tcx(); - let mut obligations = data.nested; - - let trait_fn_def_id = tcx.impl_trait_in_trait_parent_fn(obligation.predicate.def_id); - let leaf_def = match specialization_graph::assoc_def(tcx, data.impl_def_id, trait_fn_def_id) { - Ok(assoc_ty) => assoc_ty, - Err(guar) => return Progress::error(tcx, guar), - }; - // We don't support specialization for RPITITs anyways... yet. - // Also don't try to project to an RPITIT that has no value - if !leaf_def.is_final() || !leaf_def.item.defaultness(tcx).has_value() { - return Progress { term: tcx.ty_error_misc().into(), obligations }; - } - - // Use the default `impl Trait` for the trait, e.g., for a default trait body - if leaf_def.item.container == ty::AssocItemContainer::TraitContainer { - return Progress { - term: tcx.mk_opaque(obligation.predicate.def_id, obligation.predicate.substs).into(), - obligations, - }; - } - - // Rebase from {trait}::{fn}::{opaque} to {impl}::{fn}::{opaque}, - // since `data.substs` are the impl substs. - let impl_fn_substs = - obligation.predicate.substs.rebase_onto(tcx, tcx.parent(trait_fn_def_id), data.substs); - let impl_fn_substs = translate_substs( - selcx.infcx, - obligation.param_env, - data.impl_def_id, - impl_fn_substs, - leaf_def.defining_node, - ); - - if !check_substs_compatible(tcx, leaf_def.item, impl_fn_substs) { - let err = tcx.ty_error_with_message( - obligation.cause.span, - "impl method and trait method have different parameters", - ); - return Progress { term: err.into(), obligations }; - } - - let impl_fn_def_id = leaf_def.item.def_id; - - let cause = ObligationCause::new( - obligation.cause.span, - obligation.cause.body_id, - super::ItemObligation(impl_fn_def_id), - ); - let predicates = normalize_with_depth_to( - selcx, - obligation.param_env, - cause.clone(), - obligation.recursion_depth + 1, - tcx.predicates_of(impl_fn_def_id).instantiate(tcx, impl_fn_substs), - &mut obligations, - ); - obligations.extend(predicates.into_iter().map(|(pred, span)| { - Obligation::with_depth( - tcx, - ObligationCause::new( - obligation.cause.span, - obligation.cause.body_id, - if span.is_dummy() { - super::ItemObligation(impl_fn_def_id) - } else { - super::BindingObligation(impl_fn_def_id, span) - }, - ), - obligation.recursion_depth + 1, - obligation.param_env, - pred, - ) - })); - - let ty = normalize_with_depth_to( - selcx, - obligation.param_env, - cause.clone(), - obligation.recursion_depth + 1, - tcx.collect_return_position_impl_trait_in_trait_tys(impl_fn_def_id).map_or_else( - |guar| tcx.ty_error(guar), - |tys| tys[&obligation.predicate.def_id].subst(tcx, impl_fn_substs), - ), - &mut obligations, - ); - - Progress { term: ty.into(), obligations } } // Get obligations corresponding to the predicates from the where-clause of the @@ -2474,7 +2373,7 @@ fn assoc_ty_own_obligations<'cx, 'tcx>( let tcx = selcx.tcx(); let predicates = tcx .predicates_of(obligation.predicate.def_id) - .instantiate_own(tcx, obligation.predicate.substs); + .instantiate_own(tcx, obligation.predicate.args); for (predicate, span) in predicates { let normalized = normalize_with_depth_to( selcx, diff --git a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs index 455b53bfb7d8f..9484a50e3a913 100644 --- a/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs +++ b/compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs @@ -1,6 +1,11 @@ -use rustc_middle::ty::{self, Ty, TyCtxt}; +use crate::traits::query::normalize::QueryNormalizeExt; +use crate::traits::query::NoSolution; +use crate::traits::{Normalized, ObligationCause, ObligationCtxt}; -pub use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult}; +use rustc_data_structures::fx::FxHashSet; +use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult}; +use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt}; +use rustc_span::source_map::{Span, DUMMY_SP}; /// This returns true if the type `ty` is "trivial" for /// dropck-outlives -- that is, if it doesn't require any types to @@ -44,8 +49,8 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { // (T1..Tn) and closures have same properties as T1..Tn -- // check if *all* of them are trivial. ty::Tuple(tys) => tys.iter().all(|t| trivial_dropck_outlives(tcx, t)), - ty::Closure(_, ref substs) => { - trivial_dropck_outlives(tcx, substs.as_closure().tupled_upvars_ty()) + ty::Closure(_, ref args) => { + trivial_dropck_outlives(tcx, args.as_closure().tupled_upvars_ty()) } ty::Adt(def, _) => { @@ -71,3 +76,260 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { | ty::Generator(..) => false, } } + +pub fn compute_dropck_outlives_inner<'tcx>( + ocx: &ObligationCtxt<'_, 'tcx>, + goal: ParamEnvAnd<'tcx, Ty<'tcx>>, +) -> Result, NoSolution> { + let tcx = ocx.infcx.tcx; + let ParamEnvAnd { param_env, value: for_ty } = goal; + + let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] }; + + // A stack of types left to process. Each round, we pop + // something from the stack and invoke + // `dtorck_constraint_for_ty_inner`. This may produce new types that + // have to be pushed on the stack. This continues until we have explored + // all the reachable types from the type `for_ty`. + // + // Example: Imagine that we have the following code: + // + // ```rust + // struct A { + // value: B, + // children: Vec, + // } + // + // struct B { + // value: u32 + // } + // + // fn f() { + // let a: A = ...; + // .. + // } // here, `a` is dropped + // ``` + // + // at the point where `a` is dropped, we need to figure out + // which types inside of `a` contain region data that may be + // accessed by any destructors in `a`. We begin by pushing `A` + // onto the stack, as that is the type of `a`. We will then + // invoke `dtorck_constraint_for_ty_inner` which will expand `A` + // into the types of its fields `(B, Vec)`. These will get + // pushed onto the stack. Eventually, expanding `Vec` will + // lead to us trying to push `A` a second time -- to prevent + // infinite recursion, we notice that `A` was already pushed + // once and stop. + let mut ty_stack = vec![(for_ty, 0)]; + + // Set used to detect infinite recursion. + let mut ty_set = FxHashSet::default(); + + let cause = ObligationCause::dummy(); + let mut constraints = DropckConstraint::empty(); + while let Some((ty, depth)) = ty_stack.pop() { + debug!( + "{} kinds, {} overflows, {} ty_stack", + result.kinds.len(), + result.overflows.len(), + ty_stack.len() + ); + dtorck_constraint_for_ty_inner(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?; + + // "outlives" represent types/regions that may be touched + // by a destructor. + result.kinds.append(&mut constraints.outlives); + result.overflows.append(&mut constraints.overflows); + + // If we have even one overflow, we should stop trying to evaluate further -- + // chances are, the subsequent overflows for this evaluation won't provide useful + // information and will just decrease the speed at which we can emit these errors + // (since we'll be printing for just that much longer for the often enormous types + // that result here). + if !result.overflows.is_empty() { + break; + } + + // dtorck types are "types that will get dropped but which + // do not themselves define a destructor", more or less. We have + // to push them onto the stack to be expanded. + for ty in constraints.dtorck_types.drain(..) { + let Normalized { value: ty, obligations } = + ocx.infcx.at(&cause, param_env).query_normalize(ty)?; + ocx.register_obligations(obligations); + + debug!("dropck_outlives: ty from dtorck_types = {:?}", ty); + + match ty.kind() { + // All parameters live for the duration of the + // function. + ty::Param(..) => {} + + // A projection that we couldn't resolve - it + // might have a destructor. + ty::Alias(..) => { + result.kinds.push(ty.into()); + } + + _ => { + if ty_set.insert(ty) { + ty_stack.push((ty, depth + 1)); + } + } + } + } + } + + debug!("dropck_outlives: result = {:#?}", result); + Ok(result) +} + +/// Returns a set of constraints that needs to be satisfied in +/// order for `ty` to be valid for destruction. +pub fn dtorck_constraint_for_ty_inner<'tcx>( + tcx: TyCtxt<'tcx>, + span: Span, + for_ty: Ty<'tcx>, + depth: usize, + ty: Ty<'tcx>, + constraints: &mut DropckConstraint<'tcx>, +) -> Result<(), NoSolution> { + debug!("dtorck_constraint_for_ty_inner({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty); + + if !tcx.recursion_limit().value_within_limit(depth) { + constraints.overflows.push(ty); + return Ok(()); + } + + if trivial_dropck_outlives(tcx, ty) { + return Ok(()); + } + + match ty.kind() { + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Never + | ty::Foreign(..) + | ty::RawPtr(..) + | ty::Ref(..) + | ty::FnDef(..) + | ty::FnPtr(_) + | ty::GeneratorWitness(..) + | ty::GeneratorWitnessMIR(..) => { + // these types never have a destructor + } + + ty::Array(ety, _) | ty::Slice(ety) => { + // single-element containers, behave like their element + rustc_data_structures::stack::ensure_sufficient_stack(|| { + dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, *ety, constraints) + })?; + } + + ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| { + for ty in tys.iter() { + dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?; + } + Ok::<_, NoSolution>(()) + })?, + + ty::Closure(_, args) => { + if !args.as_closure().is_valid() { + // By the time this code runs, all type variables ought to + // be fully resolved. + + tcx.sess.delay_span_bug( + span, + format!("upvar_tys for closure not found. Expected capture information for closure {ty}",), + ); + return Err(NoSolution); + } + + rustc_data_structures::stack::ensure_sufficient_stack(|| { + for ty in args.as_closure().upvar_tys() { + dtorck_constraint_for_ty_inner(tcx, span, for_ty, depth + 1, ty, constraints)?; + } + Ok::<_, NoSolution>(()) + })? + } + + ty::Generator(_, args, _movability) => { + // rust-lang/rust#49918: types can be constructed, stored + // in the interior, and sit idle when generator yields + // (and is subsequently dropped). + // + // It would be nice to descend into interior of a + // generator to determine what effects dropping it might + // have (by looking at any drop effects associated with + // its interior). + // + // However, the interior's representation uses things like + // GeneratorWitness that explicitly assume they are not + // traversed in such a manner. So instead, we will + // simplify things for now by treating all generators as + // if they were like trait objects, where its upvars must + // all be alive for the generator's (potential) + // destructor. + // + // In particular, skipping over `_interior` is safe + // because any side-effects from dropping `_interior` can + // only take place through references with lifetimes + // derived from lifetimes attached to the upvars and resume + // argument, and we *do* incorporate those here. + + if !args.as_generator().is_valid() { + // By the time this code runs, all type variables ought to + // be fully resolved. + tcx.sess.delay_span_bug( + span, + format!("upvar_tys for generator not found. Expected capture information for generator {ty}",), + ); + return Err(NoSolution); + } + + constraints + .outlives + .extend(args.as_generator().upvar_tys().iter().map(ty::GenericArg::from)); + constraints.outlives.push(args.as_generator().resume_ty().into()); + } + + ty::Adt(def, args) => { + let DropckConstraint { dtorck_types, outlives, overflows } = + tcx.at(span).adt_dtorck_constraint(def.did())?; + // FIXME: we can try to recursively `dtorck_constraint_on_ty` + // there, but that needs some way to handle cycles. + constraints + .dtorck_types + .extend(dtorck_types.iter().map(|t| EarlyBinder::bind(*t).instantiate(tcx, args))); + constraints + .outlives + .extend(outlives.iter().map(|t| EarlyBinder::bind(*t).instantiate(tcx, args))); + constraints + .overflows + .extend(overflows.iter().map(|t| EarlyBinder::bind(*t).instantiate(tcx, args))); + } + + // Objects must be alive in order for their destructor + // to be called. + ty::Dynamic(..) => { + constraints.outlives.push(ty.into()); + } + + // Types that can't be resolved. Pass them forward. + ty::Alias(..) | ty::Param(..) => { + constraints.dtorck_types.push(ty); + } + + ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => { + // By the time this code runs, all type variables ought to + // be fully resolved. + return Err(NoSolution); + } + } + + Ok(()) +} diff --git a/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs b/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs index edbe2de8105e6..65f32b1c48a4d 100644 --- a/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs +++ b/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs @@ -1,5 +1,4 @@ use rustc_infer::traits::{TraitEngine, TraitEngineExt}; -use rustc_middle::ty; use crate::infer::canonical::OriginalQueryValues; use crate::infer::InferCtxt; @@ -66,21 +65,11 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { ) -> Result { let mut _orig_values = OriginalQueryValues::default(); - let param_env = match obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { - // we ignore the value set to it. - let mut _constness = pred.constness; - obligation - .param_env - .with_constness(_constness.and(obligation.param_env.constness())) - } - // constness has no effect on the given predicate. - _ => obligation.param_env.without_const(), - }; + let param_env = obligation.param_env; - if self.tcx.trait_solver_next() { + if self.next_trait_solver() { self.probe(|snapshot| { - let mut fulfill_cx = crate::solve::FulfillmentCtxt::new(); + let mut fulfill_cx = crate::solve::FulfillmentCtxt::new(self); fulfill_cx.register_predicate_obligation(self, obligation.clone()); // True errors // FIXME(-Ztrait-solver=next): Overflows are reported as ambig here, is that OK? @@ -90,13 +79,14 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { Ok(EvaluationResult::EvaluatedToAmbig) } else if self.opaque_types_added_in_snapshot(snapshot) { Ok(EvaluationResult::EvaluatedToOkModuloOpaqueTypes) - } else if self.region_constraints_added_in_snapshot(snapshot).is_some() { + } else if self.region_constraints_added_in_snapshot(snapshot) { Ok(EvaluationResult::EvaluatedToOkModuloRegions) } else { Ok(EvaluationResult::EvaluatedToOk) } }) } else { + assert!(!self.intercrate); let c_pred = self.canonicalize_query_keep_static( param_env.and(obligation.predicate), &mut _orig_values, diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs index 8bf934cb78ae1..87beaddc6c200 100644 --- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs @@ -30,7 +30,7 @@ pub trait QueryNormalizeExt<'tcx> { /// /// After codegen, when lifetimes do not matter, it is preferable to instead /// use [`TyCtxt::normalize_erasing_regions`], which wraps this procedure. - fn query_normalize(&self, value: T) -> Result, NoSolution> + fn query_normalize(self, value: T) -> Result, NoSolution> where T: TypeFoldable>; } @@ -49,7 +49,7 @@ impl<'cx, 'tcx> QueryNormalizeExt<'tcx> for At<'cx, 'tcx> { /// normalizing, but for now should be used only when we actually /// know that normalization will succeed, since error reporting /// and other details are still "under development". - fn query_normalize(&self, value: T) -> Result, NoSolution> + fn query_normalize(self, value: T) -> Result, NoSolution> where T: TypeFoldable>, { @@ -60,19 +60,6 @@ impl<'cx, 'tcx> QueryNormalizeExt<'tcx> for At<'cx, 'tcx> { self.param_env, self.cause, ); - if !needs_normalization(&value, self.param_env.reveal()) { - return Ok(Normalized { value, obligations: vec![] }); - } - - let mut normalizer = QueryNormalizer { - infcx: self.infcx, - cause: self.cause, - param_env: self.param_env, - obligations: vec![], - cache: SsoHashMap::new(), - anon_depth: 0, - universes: vec![], - }; // This is actually a consequence by the way `normalize_erasing_regions` works currently. // Because it needs to call the `normalize_generic_arg_after_erasing_regions`, it folds @@ -84,14 +71,38 @@ impl<'cx, 'tcx> QueryNormalizeExt<'tcx> for At<'cx, 'tcx> { // We *could* replace escaping bound vars eagerly here, but it doesn't seem really necessary. // The rest of the code is already set up to be lazy about replacing bound vars, // and only when we actually have to normalize. - if value.has_escaping_bound_vars() { + let universes = if value.has_escaping_bound_vars() { let mut max_visitor = MaxEscapingBoundVarVisitor { outer_index: ty::INNERMOST, escaping: 0 }; value.visit_with(&mut max_visitor); - if max_visitor.escaping > 0 { - normalizer.universes.extend((0..max_visitor.escaping).map(|_| None)); + vec![None; max_visitor.escaping] + } else { + vec![] + }; + + if self.infcx.next_trait_solver() { + match crate::solve::deeply_normalize_with_skipped_universes(self, value, universes) { + Ok(value) => return Ok(Normalized { value, obligations: vec![] }), + Err(_errors) => { + return Err(NoSolution); + } } } + + if !needs_normalization(&value, self.param_env.reveal()) { + return Ok(Normalized { value, obligations: vec![] }); + } + + let mut normalizer = QueryNormalizer { + infcx: self.infcx, + cause: self.cause, + param_env: self.param_env, + obligations: vec![], + cache: SsoHashMap::new(), + anon_depth: 0, + universes, + }; + let result = value.try_fold_with(&mut normalizer); info!( "normalize::<{}>: result={:?} with {} obligations", @@ -207,20 +218,17 @@ impl<'cx, 'tcx> FallibleTypeFolder> for QueryNormalizer<'cx, 'tcx> }; // See note in `rustc_trait_selection::traits::project` about why we - // wait to fold the substs. + // wait to fold the args. // Wrap this in a closure so we don't accidentally return from the outer function let res = match kind { - // This is really important. While we *can* handle this, this has - // severe performance implications for large opaque types with - // late-bound regions. See `issue-88862` benchmark. - ty::Opaque if !data.substs.has_escaping_bound_vars() => { + ty::Opaque => { // Only normalize `impl Trait` outside of type inference, usually in codegen. match self.param_env.reveal() { Reveal::UserFacing => ty.try_super_fold_with(self)?, Reveal::All => { - let substs = data.substs.try_fold_with(self)?; + let args = data.args.try_fold_with(self)?; let recursion_limit = self.interner().recursion_limit(); if !recursion_limit.value_within_limit(self.anon_depth) { // A closure or generator may have itself as in its upvars. @@ -236,14 +244,14 @@ impl<'cx, 'tcx> FallibleTypeFolder> for QueryNormalizer<'cx, 'tcx> } let generic_ty = self.interner().type_of(data.def_id); - let concrete_ty = generic_ty.subst(self.interner(), substs); + let concrete_ty = generic_ty.instantiate(self.interner(), args); self.anon_depth += 1; if concrete_ty == ty { bug!( - "infinite recursion generic_ty: {:#?}, substs: {:#?}, \ + "infinite recursion generic_ty: {:#?}, args: {:#?}, \ concrete_ty: {:#?}, ty: {:#?}", generic_ty, - substs, + args, concrete_ty, ty ); @@ -255,9 +263,7 @@ impl<'cx, 'tcx> FallibleTypeFolder> for QueryNormalizer<'cx, 'tcx> } } - ty::Opaque => ty.try_super_fold_with(self)?, - - ty::Projection | ty::Inherent => { + ty::Projection | ty::Inherent | ty::Weak => { // See note in `rustc_trait_selection::traits::project` let infcx = self.infcx; @@ -282,6 +288,7 @@ impl<'cx, 'tcx> FallibleTypeFolder> for QueryNormalizer<'cx, 'tcx> debug!("QueryNormalizer: orig_values = {:#?}", orig_values); let result = match kind { ty::Projection => tcx.normalize_projection_ty(c_data), + ty::Weak => tcx.normalize_weak_ty(c_data), ty::Inherent => tcx.normalize_inherent_projection_ty(c_data), _ => unreachable!(), }?; @@ -292,7 +299,7 @@ impl<'cx, 'tcx> FallibleTypeFolder> for QueryNormalizer<'cx, 'tcx> if !tcx.sess.opts.actually_rustdoc { tcx.sess.delay_span_bug( DUMMY_SP, - format!("unexpected ambiguity: {:?} {:?}", c_data, result), + format!("unexpected ambiguity: {c_data:?} {result:?}"), ); } return Err(NoSolution); @@ -321,8 +328,12 @@ impl<'cx, 'tcx> FallibleTypeFolder> for QueryNormalizer<'cx, 'tcx> }; // `tcx.normalize_projection_ty` may normalize to a type that still has // unevaluated consts, so keep normalizing here if that's the case. - if res != ty && res.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) { - res.try_super_fold_with(self)? + // Similarly, `tcx.normalize_weak_ty` will only unwrap one layer of type + // and we need to continue folding it to reveal the TAIT behind it. + if res != ty + && (res.has_type_flags(ty::TypeFlags::HAS_CT_PROJECTION) || kind == ty::Weak) + { + res.try_fold_with(self)? } else { res } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs index e6db96c9e5580..302b6016e5ea6 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/ascribe_user_type.rs @@ -1,8 +1,13 @@ use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::Fallible; -use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; +use crate::traits::ObligationCtxt; +use rustc_hir::def_id::{DefId, CRATE_DEF_ID}; +use rustc_infer::traits::Obligation; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; +use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, UserArgs, UserSelfTy, UserType}; pub use rustc_middle::traits::query::type_op::AscribeUserType; +use rustc_span::{Span, DUMMY_SP}; impl<'tcx> super::QueryTypeOp<'tcx> for AscribeUserType<'tcx> { type QueryResponse = (); @@ -17,7 +22,122 @@ impl<'tcx> super::QueryTypeOp<'tcx> for AscribeUserType<'tcx> { fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_ascribe_user_type(canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + type_op_ascribe_user_type_with_span(ocx, key, None) + } +} + +/// The core of the `type_op_ascribe_user_type` query: for diagnostics purposes in NLL HRTB errors, +/// this query can be re-run to better track the span of the obligation cause, and improve the error +/// message. Do not call directly unless you're in that very specific context. +pub fn type_op_ascribe_user_type_with_span<'tcx>( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, AscribeUserType<'tcx>>, + span: Option, +) -> Result<(), NoSolution> { + let (param_env, AscribeUserType { mir_ty, user_ty }) = key.into_parts(); + debug!("type_op_ascribe_user_type: mir_ty={:?} user_ty={:?}", mir_ty, user_ty); + let span = span.unwrap_or(DUMMY_SP); + match user_ty { + UserType::Ty(user_ty) => relate_mir_and_user_ty(ocx, param_env, span, mir_ty, user_ty)?, + UserType::TypeOf(def_id, user_args) => { + relate_mir_and_user_args(ocx, param_env, span, mir_ty, def_id, user_args)? + } + }; + Ok(()) +} + +#[instrument(level = "debug", skip(ocx, param_env, span))] +fn relate_mir_and_user_ty<'tcx>( + ocx: &ObligationCtxt<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Span, + mir_ty: Ty<'tcx>, + user_ty: Ty<'tcx>, +) -> Result<(), NoSolution> { + let cause = ObligationCause::dummy_with_span(span); + let user_ty = ocx.normalize(&cause, param_env, user_ty); + ocx.eq(&cause, param_env, mir_ty, user_ty)?; + + // FIXME(#104764): We should check well-formedness before normalization. + let predicate = + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(user_ty.into()))); + ocx.register_obligation(Obligation::new(ocx.infcx.tcx, cause, param_env, predicate)); + Ok(()) +} + +#[instrument(level = "debug", skip(ocx, param_env, span))] +fn relate_mir_and_user_args<'tcx>( + ocx: &ObligationCtxt<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + span: Span, + mir_ty: Ty<'tcx>, + def_id: DefId, + user_args: UserArgs<'tcx>, +) -> Result<(), NoSolution> { + let UserArgs { user_self_ty, args } = user_args; + let tcx = ocx.infcx.tcx; + let cause = ObligationCause::dummy_with_span(span); + + let ty = tcx.type_of(def_id).instantiate(tcx, args); + let ty = ocx.normalize(&cause, param_env, ty); + debug!("relate_type_and_user_type: ty of def-id is {:?}", ty); + + ocx.eq(&cause, param_env, mir_ty, ty)?; + + // Prove the predicates coming along with `def_id`. + // + // Also, normalize the `instantiated_predicates` + // because otherwise we wind up with duplicate "type + // outlives" error messages. + let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, args); + + debug!(?instantiated_predicates); + for (instantiated_predicate, predicate_span) in instantiated_predicates { + let span = if span == DUMMY_SP { predicate_span } else { span }; + let cause = ObligationCause::new( + span, + CRATE_DEF_ID, + ObligationCauseCode::AscribeUserTypeProvePredicate(predicate_span), + ); + let instantiated_predicate = + ocx.normalize(&cause.clone(), param_env, instantiated_predicate); + + ocx.register_obligation(Obligation::new(tcx, cause, param_env, instantiated_predicate)); + } + + if let Some(UserSelfTy { impl_def_id, self_ty }) = user_self_ty { + let self_ty = ocx.normalize(&cause, param_env, self_ty); + let impl_self_ty = tcx.type_of(impl_def_id).instantiate(tcx, args); + let impl_self_ty = ocx.normalize(&cause, param_env, impl_self_ty); + + ocx.eq(&cause, param_env, self_ty, impl_self_ty)?; + let predicate = ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + impl_self_ty.into(), + ))); + ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, predicate)); + } + + // In addition to proving the predicates, we have to + // prove that `ty` is well-formed -- this is because + // the WF of `ty` is predicated on the args being + // well-formed, and we haven't proven *that*. We don't + // want to prove the WF of types from `args` directly because they + // haven't been normalized. + // + // FIXME(nmatsakis): Well, perhaps we should normalize + // them? This would only be relevant if some input + // type were ill-formed but did not appear in `ty`, + // which...could happen with normalization... + let predicate = + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(ty.into()))); + ocx.register_obligation(Obligation::new(tcx, cause, param_env, predicate)); + Ok(()) } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs index 3d6c1d9e2b02e..c99e018e9aee3 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/custom.rs @@ -1,11 +1,13 @@ use crate::infer::canonical::query_response; use crate::infer::InferCtxt; use crate::traits::query::type_op::TypeOpOutput; -use crate::traits::query::Fallible; use crate::traits::ObligationCtxt; +use rustc_errors::ErrorGuaranteed; use rustc_infer::infer::region_constraints::RegionConstraintData; use rustc_middle::traits::query::NoSolution; +use rustc_middle::ty::{TyCtxt, TypeFoldable}; use rustc_span::source_map::DUMMY_SP; +use rustc_span::Span; use std::fmt; @@ -17,15 +19,16 @@ pub struct CustomTypeOp { impl CustomTypeOp { pub fn new<'tcx, R>(closure: F, description: &'static str) -> Self where - F: FnOnce(&ObligationCtxt<'_, 'tcx>) -> Fallible, + F: FnOnce(&ObligationCtxt<'_, 'tcx>) -> Result, { CustomTypeOp { closure, description } } } -impl<'tcx, F, R: fmt::Debug> super::TypeOp<'tcx> for CustomTypeOp +impl<'tcx, F, R> super::TypeOp<'tcx> for CustomTypeOp where - F: FnOnce(&ObligationCtxt<'_, 'tcx>) -> Fallible, + F: FnOnce(&ObligationCtxt<'_, 'tcx>) -> Result, + R: fmt::Debug + TypeFoldable>, { type Output = R; /// We can't do any custom error reporting for `CustomTypeOp`, so @@ -35,12 +38,16 @@ where /// Processes the operation and all resulting obligations, /// returning the final result along with any region constraints /// (they will be given over to the NLL region solver). - fn fully_perform(self, infcx: &InferCtxt<'tcx>) -> Fallible> { + fn fully_perform( + self, + infcx: &InferCtxt<'tcx>, + span: Span, + ) -> Result, ErrorGuaranteed> { if cfg!(debug_assertions) { info!("fully_perform({:?})", self); } - Ok(scrape_region_constraints(infcx, self.closure)?.0) + Ok(scrape_region_constraints(infcx, self.closure, self.description, span)?.0) } } @@ -52,10 +59,16 @@ impl fmt::Debug for CustomTypeOp { /// Executes `op` and then scrapes out all the "old style" region /// constraints that result, creating query-region-constraints. -pub fn scrape_region_constraints<'tcx, Op: super::TypeOp<'tcx, Output = R>, R>( +pub fn scrape_region_constraints<'tcx, Op, R>( infcx: &InferCtxt<'tcx>, - op: impl FnOnce(&ObligationCtxt<'_, 'tcx>) -> Fallible, -) -> Fallible<(TypeOpOutput<'tcx, Op>, RegionConstraintData<'tcx>)> { + op: impl FnOnce(&ObligationCtxt<'_, 'tcx>) -> Result, + name: &'static str, + span: Span, +) -> Result<(TypeOpOutput<'tcx, Op>, RegionConstraintData<'tcx>), ErrorGuaranteed> +where + R: TypeFoldable>, + Op: super::TypeOp<'tcx, Output = R>, +{ // During NLL, we expect that nobody will register region // obligations **except** as part of a custom type op (and, at the // end of each custom type op, we scrape out the region @@ -64,25 +77,28 @@ pub fn scrape_region_constraints<'tcx, Op: super::TypeOp<'tcx, Output = R>, R>( let pre_obligations = infcx.take_registered_region_obligations(); assert!( pre_obligations.is_empty(), - "scrape_region_constraints: incoming region obligations = {:#?}", - pre_obligations, + "scrape_region_constraints: incoming region obligations = {pre_obligations:#?}", ); let value = infcx.commit_if_ok(|_| { - let ocx = ObligationCtxt::new_in_snapshot(infcx); - let value = op(&ocx)?; + let ocx = ObligationCtxt::new(infcx); + let value = op(&ocx).map_err(|_| { + infcx.tcx.sess.delay_span_bug(span, format!("error performing operation: {name}")) + })?; let errors = ocx.select_all_or_error(); if errors.is_empty() { Ok(value) } else { - infcx.tcx.sess.delay_span_bug( + Err(infcx.tcx.sess.delay_span_bug( DUMMY_SP, - format!("errors selecting obligation during MIR typeck: {:?}", errors), - ); - Err(NoSolution) + format!("errors selecting obligation during MIR typeck: {errors:?}"), + )) } })?; + // Next trait solver performs operations locally, and normalize goals should resolve vars. + let value = infcx.resolve_vars_if_possible(value); + let region_obligations = infcx.take_registered_region_obligations(); let region_constraint_data = infcx.take_and_reset_region_constraints(); let region_constraints = query_response::make_query_region_constraints( diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/eq.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/eq.rs index 8c9b9610cb6ef..f65893088066e 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/eq.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/eq.rs @@ -1,5 +1,7 @@ use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::Fallible; +use crate::traits::ObligationCtxt; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::ObligationCause; use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; pub use rustc_middle::traits::query::type_op::Eq; @@ -17,7 +19,15 @@ impl<'tcx> super::QueryTypeOp<'tcx> for Eq<'tcx> { fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_eq(canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + ocx.eq(&ObligationCause::dummy(), key.param_env, key.value.a, key.value.b)?; + Ok(()) + } } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs index 18d7c9b193601..979498fb6e678 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs @@ -1,7 +1,17 @@ -use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::Fallible; +use crate::solve; +use crate::traits::query::NoSolution; +use crate::traits::wf; +use crate::traits::ObligationCtxt; + +use rustc_infer::infer::canonical::Canonical; +use rustc_infer::infer::outlives::components::{push_outlives_components, Component}; use rustc_infer::traits::query::OutlivesBound; -use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt}; +use rustc_middle::infer::canonical::CanonicalQueryResponse; +use rustc_middle::traits::ObligationCause; +use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt}; +use rustc_span::def_id::CRATE_DEF_ID; +use rustc_span::source_map::DUMMY_SP; +use smallvec::{smallvec, SmallVec}; #[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)] pub struct ImpliedOutlivesBounds<'tcx> { @@ -28,7 +38,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> { fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { + ) -> Result, NoSolution> { // FIXME this `unchecked_map` is only necessary because the // query is defined as taking a `ParamEnvAnd`; it should // take an `ImpliedOutlivesBounds` instead @@ -39,4 +49,179 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> { tcx.implied_outlives_bounds(canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty) + } +} + +pub fn compute_implied_outlives_bounds_inner<'tcx>( + ocx: &ObligationCtxt<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, +) -> Result>, NoSolution> { + let tcx = ocx.infcx.tcx; + + // Sometimes when we ask what it takes for T: WF, we get back that + // U: WF is required; in that case, we push U onto this stack and + // process it next. Because the resulting predicates aren't always + // guaranteed to be a subset of the original type, so we need to store the + // WF args we've computed in a set. + let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default(); + let mut wf_args = vec![ty.into()]; + + let mut outlives_bounds: Vec, ty::Region<'tcx>>> = + vec![]; + + while let Some(arg) = wf_args.pop() { + if !checked_wf_args.insert(arg) { + continue; + } + + // Compute the obligations for `arg` to be well-formed. If `arg` is + // an unresolved inference variable, just substituted an empty set + // -- because the return type here is going to be things we *add* + // to the environment, it's always ok for this set to be smaller + // than the ultimate set. (Note: normally there won't be + // unresolved inference variables here anyway, but there might be + // during typeck under some circumstances.) + // + // FIXME(@lcnr): It's not really "always fine", having fewer implied + // bounds can be backward incompatible, e.g. #101951 was caused by + // us not dealing with inference vars in `TypeOutlives` predicates. + let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP) + .unwrap_or_default(); + + for obligation in obligations { + debug!(?obligation); + assert!(!obligation.has_escaping_bound_vars()); + + // While these predicates should all be implied by other parts of + // the program, they are still relevant as they may constrain + // inference variables, which is necessary to add the correct + // implied bounds in some cases, mostly when dealing with projections. + // + // Another important point here: we only register `Projection` + // predicates, since otherwise we might register outlives + // predicates containing inference variables, and we don't + // learn anything new from those. + if obligation.predicate.has_non_region_infer() { + match obligation.predicate.kind().skip_binder() { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) + | ty::PredicateKind::AliasRelate(..) => { + ocx.register_obligation(obligation.clone()); + } + _ => {} + } + } + + let pred = match obligation.predicate.kind().no_bound_vars() { + None => continue, + Some(pred) => pred, + }; + match pred { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(..)) + // FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound + // if we ever support that + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) + | ty::PredicateKind::Subtype(..) + | ty::PredicateKind::Coerce(..) + | ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) + | ty::PredicateKind::ClosureKind(..) + | ty::PredicateKind::ObjectSafe(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) + | ty::PredicateKind::ConstEquate(..) + | ty::PredicateKind::Ambiguous + | ty::PredicateKind::AliasRelate(..) + => {} + + // We need to search through *all* WellFormed predicates + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { + wf_args.push(arg); + } + + // We need to register region relationships + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate( + r_a, + r_b, + ))) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)), + + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( + ty_a, + r_b, + ))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)), + } + } + } + + // This call to `select_all_or_error` is necessary to constrain inference variables, which we + // use further down when computing the implied bounds. + match ocx.select_all_or_error().as_slice() { + [] => (), + _ => return Err(NoSolution), + } + + // We lazily compute the outlives components as + // `select_all_or_error` constrains inference variables. + let mut implied_bounds = Vec::new(); + for ty::OutlivesPredicate(a, r_b) in outlives_bounds { + match a.unpack() { + ty::GenericArgKind::Lifetime(r_a) => { + implied_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a)) + } + ty::GenericArgKind::Type(ty_a) => { + let mut ty_a = ocx.infcx.resolve_vars_if_possible(ty_a); + // Need to manually normalize in the new solver as `wf::obligations` does not. + if ocx.infcx.next_trait_solver() { + ty_a = solve::deeply_normalize( + ocx.infcx.at(&ObligationCause::dummy(), param_env), + ty_a, + ) + .map_err(|_errs| NoSolution)?; + } + let mut components = smallvec![]; + push_outlives_components(tcx, ty_a, &mut components); + implied_bounds.extend(implied_bounds_from_components(r_b, components)) + } + ty::GenericArgKind::Const(_) => unreachable!(), + } + } + + Ok(implied_bounds) +} + +/// When we have an implied bound that `T: 'a`, we can further break +/// this down to determine what relationships would have to hold for +/// `T: 'a` to hold. We get to assume that the caller has validated +/// those relationships. +fn implied_bounds_from_components<'tcx>( + sub_region: ty::Region<'tcx>, + sup_components: SmallVec<[Component<'tcx>; 4]>, +) -> Vec> { + sup_components + .into_iter() + .filter_map(|component| { + match component { + Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)), + Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)), + Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)), + Component::EscapingAlias(_) => + // If the projection has escaping regions, don't + // try to infer any implied bounds even for its + // free components. This is conservative, because + // the caller will still have to prove that those + // free components outlive `sub_region`. But the + // idea is that the WAY that the caller proves + // that may change in the future and we want to + // give ourselves room to get smarter here. + { + None + } + Component::UnresolvedInferenceVariable(..) => None, + } + }) + .collect() } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs index 9e8bc8bce9a8f..9d7933e23a8b2 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/mod.rs @@ -2,13 +2,14 @@ use crate::infer::canonical::{ Canonical, CanonicalQueryResponse, OriginalQueryValues, QueryRegionConstraints, }; use crate::infer::{InferCtxt, InferOk}; -use crate::traits::query::Fallible; -use crate::traits::ObligationCause; +use crate::traits::{ObligationCause, ObligationCtxt}; +use rustc_errors::ErrorGuaranteed; use rustc_infer::infer::canonical::Certainty; -use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::PredicateObligations; +use rustc_middle::traits::query::NoSolution; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; +use rustc_span::Span; use std::fmt; pub mod ascribe_user_type; @@ -22,6 +23,8 @@ pub mod subtype; pub use rustc_middle::traits::query::type_op::*; +use self::custom::scrape_region_constraints; + /// "Type ops" are used in NLL to perform some particular action and /// extract out the resulting region constraints (or an error if it /// cannot be completed). @@ -32,7 +35,11 @@ pub trait TypeOp<'tcx>: Sized + fmt::Debug { /// Processes the operation and all resulting obligations, /// returning the final result along with any region constraints /// (they will be given over to the NLL region solver). - fn fully_perform(self, infcx: &InferCtxt<'tcx>) -> Fallible>; + fn fully_perform( + self, + infcx: &InferCtxt<'tcx>, + span: Span, + ) -> Result, ErrorGuaranteed>; } /// The output from performing a type op @@ -74,18 +81,32 @@ pub trait QueryTypeOp<'tcx>: fmt::Debug + Copy + TypeFoldable> + 't fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible>; + ) -> Result, NoSolution>; + + /// In the new trait solver, we already do caching in the solver itself, + /// so there's no need to canonicalize and cache via the query system. + /// Additionally, even if we were to canonicalize, we'd still need to + /// make sure to feed it predefined opaque types and the defining anchor + /// and that would require duplicating all of the tcx queries. Instead, + /// just perform these ops locally. + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result; fn fully_perform_into( query_key: ParamEnvAnd<'tcx, Self>, infcx: &InferCtxt<'tcx>, output_query_region_constraints: &mut QueryRegionConstraints<'tcx>, - ) -> Fallible<( - Self::QueryResponse, - Option>>, - PredicateObligations<'tcx>, - Certainty, - )> { + ) -> Result< + ( + Self::QueryResponse, + Option>>, + PredicateObligations<'tcx>, + Certainty, + ), + NoSolution, + > { if let Some(result) = QueryTypeOp::try_fast_path(infcx.tcx, &query_key) { return Ok((result, None, vec![], Certainty::Proven)); } @@ -120,10 +141,26 @@ where type Output = Q::QueryResponse; type ErrorInfo = Canonical<'tcx, ParamEnvAnd<'tcx, Q>>; - fn fully_perform(self, infcx: &InferCtxt<'tcx>) -> Fallible> { + fn fully_perform( + self, + infcx: &InferCtxt<'tcx>, + span: Span, + ) -> Result, ErrorGuaranteed> { + if infcx.next_trait_solver() { + return Ok(scrape_region_constraints( + infcx, + |ocx| QueryTypeOp::perform_locally_in_new_solver(ocx, self), + "query type op", + span, + )? + .0); + } + let mut region_constraints = QueryRegionConstraints::default(); let (output, error_info, mut obligations, _) = - Q::fully_perform_into(self, infcx, &mut region_constraints)?; + Q::fully_perform_into(self, infcx, &mut region_constraints).map_err(|_| { + infcx.tcx.sess.delay_span_bug(span, format!("error performing {self:?}")) + })?; // Typically, instantiating NLL query results does not // create obligations. However, in some cases there @@ -151,7 +188,10 @@ where } } if !progress { - return Err(NoSolution); + return Err(infcx.tcx.sess.delay_span_bug( + span, + format!("ambiguity processing {obligations:?} from {self:?}"), + )); } } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs index 5b216c0769236..9559f5002f63b 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/normalize.rs @@ -1,5 +1,7 @@ use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::Fallible; +use crate::traits::ObligationCtxt; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::ObligationCause; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::{self, Lift, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt}; use std::fmt; @@ -19,33 +21,41 @@ where fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { + ) -> Result, NoSolution> { T::type_op_method(tcx, canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + // FIXME(-Ztrait-solver=next): shouldn't be using old normalizer + Ok(ocx.normalize(&ObligationCause::dummy(), key.param_env, key.value.value)) + } } pub trait Normalizable<'tcx>: fmt::Debug + TypeFoldable> + Lift<'tcx> + Copy { fn type_op_method( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>, - ) -> Fallible>; + ) -> Result, NoSolution>; } impl<'tcx> Normalizable<'tcx> for Ty<'tcx> { fn type_op_method( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_normalize_ty(canonicalized) } } -impl<'tcx> Normalizable<'tcx> for ty::Predicate<'tcx> { +impl<'tcx> Normalizable<'tcx> for ty::Clause<'tcx> { fn type_op_method( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>, - ) -> Fallible> { - tcx.type_op_normalize_predicate(canonicalized) + ) -> Result, NoSolution> { + tcx.type_op_normalize_clause(canonicalized) } } @@ -53,7 +63,7 @@ impl<'tcx> Normalizable<'tcx> for ty::PolyFnSig<'tcx> { fn type_op_method( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_normalize_poly_fn_sig(canonicalized) } } @@ -62,7 +72,7 @@ impl<'tcx> Normalizable<'tcx> for ty::FnSig<'tcx> { fn type_op_method( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_normalize_fn_sig(canonicalized) } } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs index 21ef4e24fdb05..59f4a22ac7594 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/outlives.rs @@ -1,6 +1,9 @@ use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::dropck_outlives::{trivial_dropck_outlives, DropckOutlivesResult}; -use crate::traits::query::Fallible; +use crate::traits::query::dropck_outlives::{ + compute_dropck_outlives_inner, trivial_dropck_outlives, +}; +use crate::traits::ObligationCtxt; +use rustc_middle::traits::query::{DropckOutlivesResult, NoSolution}; use rustc_middle::ty::{ParamEnvAnd, Ty, TyCtxt}; #[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable, Lift)] @@ -27,17 +30,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for DropckOutlives<'tcx> { fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { - // Subtle: note that we are not invoking - // `infcx.at(...).dropck_outlives(...)` here, but rather the - // underlying `dropck_outlives` query. This same underlying - // query is also used by the - // `infcx.at(...).dropck_outlives(...)` fn. Avoiding the - // wrapper means we don't need an infcx in this code, which is - // good because the interface doesn't give us one (so that we - // know we are not registering any subregion relations or - // other things). - + ) -> Result, NoSolution> { // FIXME convert to the type expected by the `dropck_outlives` // query. This should eventually be fixed by changing the // *underlying query*. @@ -48,4 +41,11 @@ impl<'tcx> super::QueryTypeOp<'tcx> for DropckOutlives<'tcx> { tcx.dropck_outlives(canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + compute_dropck_outlives_inner(ocx, key.param_env.and(key.value.dropped_ty)) + } } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs index baa2fbb6751c3..789ef647246e9 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/prove_predicate.rs @@ -1,5 +1,8 @@ use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::Fallible; +use crate::traits::ObligationCtxt; +use rustc_infer::traits::Obligation; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::ObligationCause; use rustc_middle::ty::{self, ParamEnvAnd, TyCtxt}; pub use rustc_middle::traits::query::type_op::ProvePredicate; @@ -15,7 +18,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ProvePredicate<'tcx> { // `&T`, accounts for about 60% percentage of the predicates // we have to prove. No need to canonicalize and all that for // such cases. - if let ty::PredicateKind::Clause(ty::Clause::Trait(trait_ref)) = + if let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_ref)) = key.value.predicate.kind().skip_binder() { if let Some(sized_def_id) = tcx.lang_items().sized_trait() { @@ -33,7 +36,20 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ProvePredicate<'tcx> { fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_prove_predicate(canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + ocx.register_obligation(Obligation::new( + ocx.infcx.tcx, + ObligationCause::dummy(), + key.param_env, + key.value.predicate, + )); + Ok(()) + } } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/subtype.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/subtype.rs index c51292eba14be..10976d5cd7162 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/subtype.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/subtype.rs @@ -1,5 +1,7 @@ use crate::infer::canonical::{Canonical, CanonicalQueryResponse}; -use crate::traits::query::Fallible; +use crate::traits::ObligationCtxt; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::traits::ObligationCause; use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; pub use rustc_middle::traits::query::type_op::Subtype; @@ -14,7 +16,15 @@ impl<'tcx> super::QueryTypeOp<'tcx> for Subtype<'tcx> { fn perform_query( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>, - ) -> Fallible> { + ) -> Result, NoSolution> { tcx.type_op_subtype(canonicalized) } + + fn perform_locally_in_new_solver( + ocx: &ObligationCtxt<'_, 'tcx>, + key: ParamEnvAnd<'tcx, Self>, + ) -> Result { + ocx.sub(&ObligationCause::dummy(), key.param_env, key.value.sub, key.value.sup)?; + Ok(()) + } } diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index 8bc82b9f54926..e3da87a221064 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -10,7 +10,7 @@ use hir::def_id::DefId; use hir::LangItem; use rustc_hir as hir; use rustc_infer::traits::ObligationCause; -use rustc_infer::traits::{Obligation, SelectionError, TraitObligation}; +use rustc_infer::traits::{Obligation, PolyTraitObligation, SelectionError}; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -124,11 +124,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { self.assemble_candidates_from_projected_tys(obligation, &mut candidates); self.assemble_candidates_from_caller_bounds(stack, &mut candidates)?; - // Auto implementations have lower priority, so we only - // consider triggering a default if there is no other impl that can apply. - if candidates.vec.is_empty() { - self.assemble_candidates_from_auto_impls(obligation, &mut candidates); - } + self.assemble_candidates_from_auto_impls(obligation, &mut candidates); } debug!("candidate list size: {}", candidates.vec.len()); Ok(candidates) @@ -137,13 +133,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(level = "debug", skip(self, candidates))] fn assemble_candidates_from_projected_tys( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // Before we go into the whole placeholder thing, just // quickly check if the self-type is a projection at all. match obligation.predicate.skip_binder().trait_ref.self_ty().kind() { - // Excluding IATs here as they don't have meaningful item bounds. + // Excluding IATs and type aliases here as they don't have meaningful item bounds. ty::Alias(ty::Projection | ty::Opaque, _) => {} ty::Infer(ty::TyVar(_)) => { span_bug!( @@ -158,9 +154,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .infcx .probe(|_| self.match_projection_obligation_against_definition_bounds(obligation)); - candidates - .vec - .extend(result.into_iter().map(|(idx, constness)| ProjectionCandidate(idx, constness))); + // FIXME(effects) proper constness needed? + candidates.vec.extend( + result.into_iter().map(|idx| ProjectionCandidate(idx, ty::BoundConstness::NotConst)), + ); } /// Given an obligation like ``, searches the obligations that the caller @@ -181,7 +178,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .caller_bounds() .iter() .filter(|p| !p.references_error()) - .filter_map(|p| p.to_opt_poly_trait_pred()); + .filter_map(|p| p.as_trait_clause()); // Micro-optimization: filter out predicates relating to different traits. let matching_bounds = @@ -206,10 +203,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_generator_candidates( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { - // Okay to skip binder because the substs on generator types never + // Okay to skip binder because the args on generator types never // touch bound regions, they just capture the in-scope // type/region parameters. let self_ty = obligation.self_ty().skip_binder(); @@ -231,7 +228,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_future_candidates( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { let self_ty = obligation.self_ty().skip_binder(); @@ -254,21 +251,21 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// unified during the confirmation step. fn assemble_closure_candidates( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { let Some(kind) = self.tcx().fn_trait_kind_from_def_id(obligation.predicate.def_id()) else { return; }; - // Okay to skip binder because the substs on closure types never + // Okay to skip binder because the args on closure types never // touch bound regions, they just capture the in-scope // type/region parameters match *obligation.self_ty().skip_binder().kind() { - ty::Closure(def_id, closure_substs) => { + ty::Closure(def_id, closure_args) => { let is_const = self.tcx().is_const_fn_raw(def_id); debug!(?kind, ?obligation, "assemble_unboxed_candidates"); - match self.infcx.closure_kind(closure_substs) { + match self.infcx.closure_kind(closure_args) { Some(closure_kind) => { debug!(?closure_kind, "assemble_unboxed_candidates"); if closure_kind.extends(kind) { @@ -292,7 +289,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// Implements one of the `Fn()` family for a fn pointer. fn assemble_fn_pointer_candidates( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // We provide impl of all fn traits for fn pointers. @@ -334,7 +331,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(level = "debug", skip(self, candidates))] fn assemble_candidates_from_impls( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // Essentially any user-written impl will match with an error type, @@ -351,7 +348,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::ForLookup }; - let obligation_substs = obligation.predicate.skip_binder().trait_ref.substs; + let obligation_args = obligation.predicate.skip_binder().trait_ref.args; self.tcx().for_each_relevant_impl( obligation.predicate.def_id(), obligation.predicate.skip_binder().trait_ref.self_ty(), @@ -360,7 +357,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // consider a "quick reject". This avoids creating more types // and so forth that we need to. let impl_trait_ref = self.tcx().impl_trait_ref(impl_def_id).unwrap(); - if !drcx.substs_refs_may_unify(obligation_substs, impl_trait_ref.0.substs) { + if !drcx.args_refs_may_unify(obligation_args, impl_trait_ref.skip_binder().args) { return; } if self.reject_fn_ptr_impls( @@ -372,7 +369,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } self.infcx.probe(|_| { - if let Ok(_substs) = self.match_impl(impl_def_id, impl_trait_ref, obligation) { + if let Ok(_args) = self.match_impl(impl_def_id, impl_trait_ref, obligation) { candidates.vec.push(ImplCandidate(impl_def_id)); } }); @@ -386,9 +383,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// `FnPtr`, when we wanted to report that it doesn't implement `Trait`. #[instrument(level = "trace", skip(self), ret)] fn reject_fn_ptr_impls( - &self, + &mut self, impl_def_id: DefId, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, impl_self_ty: Ty<'tcx>, ) -> bool { // Let `impl Trait for Vec` go through the normal rejection path. @@ -400,8 +397,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { }; for &(predicate, _) in self.tcx().predicates_of(impl_def_id).predicates { - let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) - = predicate.kind().skip_binder() else { continue }; + let ty::ClauseKind::Trait(pred) = predicate.kind().skip_binder() else { continue }; if fn_ptr_trait != pred.trait_ref.def_id { continue; } @@ -415,17 +411,15 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // Fast path to avoid evaluating an obligation that trivially holds. // There may be more bounds, but these are checked by the regular path. ty::FnPtr(..) => return false, + // These may potentially implement `FnPtr` ty::Placeholder(..) | ty::Dynamic(_, _, _) | ty::Alias(_, _) | ty::Infer(_) - | ty::Param(..) => {} + | ty::Param(..) + | ty::Bound(_, _) => {} - ty::Bound(_, _) => span_bug!( - obligation.cause.span(), - "cannot have escaping bound var in self type of {obligation:#?}" - ), // These can't possibly implement `FnPtr` as they are concrete types // and not `FnPtr` ty::Bool @@ -461,10 +455,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { self.tcx().mk_predicate(obligation.predicate.map_bound(|mut pred| { pred.trait_ref = ty::TraitRef::new(self.tcx(), fn_ptr_trait, [pred.trait_ref.self_ty()]); - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) + ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) })), ); - if let Ok(r) = self.infcx.evaluate_obligation(&obligation) { + if let Ok(r) = self.evaluate_root_obligation(&obligation) { if !r.may_apply() { return true; } @@ -475,7 +469,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_candidates_from_auto_impls( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // Okay to skip binder here because the tests we do below do not involve bound regions. @@ -516,7 +510,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // for an example of a test case that exercises // this path. } - ty::Infer(ty::TyVar(_)) => { + ty::Infer(ty::TyVar(_) | ty::IntVar(_) | ty::FloatVar(_)) => { // The auto impl might apply; we don't know. candidates.ambiguous = true; } @@ -536,7 +530,63 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - _ => candidates.vec.push(AutoImplCandidate), + ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + bug!( + "asked to assemble auto trait candidates of unexpected type: {:?}", + self_ty + ); + } + + ty::Alias(_, _) + if candidates.vec.iter().any(|c| matches!(c, ProjectionCandidate(..))) => + { + // We do not generate an auto impl candidate for `impl Trait`s which already + // reference our auto trait. + // + // For example during candidate assembly for `impl Send: Send`, we don't have + // to look at the constituent types for this opaque types to figure out that this + // trivially holds. + // + // Note that this is only sound as projection candidates of opaque types + // are always applicable for auto traits. + } + ty::Alias(_, _) => candidates.vec.push(AutoImplCandidate), + + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Array(_, _) + | ty::Slice(_) + | ty::Adt(..) + | ty::RawPtr(_) + | ty::Ref(..) + | ty::FnDef(..) + | ty::FnPtr(_) + | ty::Closure(_, _) + | ty::Generator(..) + | ty::Never + | ty::Tuple(_) + | ty::GeneratorWitness(_) + | ty::GeneratorWitnessMIR(..) => { + // Only consider auto impls if there are no manual impls for the root of `self_ty`. + // + // For example, we only consider auto candidates for `&i32: Auto` if no explicit impl + // for `&SomeType: Auto` exists. Due to E0321 the only crate where impls + // for `&SomeType: Auto` can be defined is the crate where `Auto` has been defined. + // + // Generally, we have to guarantee that for all `SimplifiedType`s the only crate + // which may define impls for that type is either the crate defining the type + // or the trait. This should be guaranteed by the orphan check. + let mut has_impl = false; + self.tcx().for_each_relevant_impl(def_id, self_ty, |_| has_impl = true); + if !has_impl { + candidates.vec.push(AutoImplCandidate) + } + } + ty::Error(_) => {} // do not add an auto trait impl for `ty::Error` for now. } } } @@ -544,7 +594,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// Searches for impls that might apply to `obligation`. fn assemble_candidates_from_object_ty( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { debug!( @@ -552,6 +602,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { "assemble_candidates_from_object_ty", ); + if !self.tcx().trait_def(obligation.predicate.def_id()).implement_via_object { + return; + } + self.infcx.probe(|_snapshot| { if obligation.has_non_region_late_bound() { return; @@ -647,7 +701,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let ty = traits::normalize_projection_type( self, param_env, - tcx.mk_alias_ty(tcx.lang_items().deref_target()?, trait_ref.substs), + tcx.mk_alias_ty(tcx.lang_items().deref_target()?, trait_ref.args), cause.clone(), 0, // We're *intentionally* throwing these away, @@ -664,7 +718,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// Searches for unsizing that might apply to `obligation`. fn assemble_candidates_for_unsizing( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // We currently never consider higher-ranked obligations e.g. @@ -685,13 +739,16 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // Don't add any candidates if there are bound regions. return; }; - let target = obligation.predicate.skip_binder().trait_ref.substs.type_at(1); + let target = obligation.predicate.skip_binder().trait_ref.args.type_at(1); debug!(?source, ?target, "assemble_candidates_for_unsizing"); match (source.kind(), target.kind()) { // Trait+Kx+'a -> Trait+Ky+'b (upcasts). - (&ty::Dynamic(ref data_a, _, ty::Dyn), &ty::Dynamic(ref data_b, _, ty::Dyn)) => { + ( + &ty::Dynamic(ref a_data, a_region, ty::Dyn), + &ty::Dynamic(ref b_data, b_region, ty::Dyn), + ) => { // Upcast coercions permit several things: // // 1. Dropping auto traits, e.g., `Foo + Send` to `Foo` @@ -703,19 +760,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // // We always perform upcasting coercions when we can because of reason // #2 (region bounds). - let auto_traits_compatible = data_b + let auto_traits_compatible = b_data .auto_traits() // All of a's auto traits need to be in b's auto traits. - .all(|b| data_a.auto_traits().any(|a| a == b)); + .all(|b| a_data.auto_traits().any(|a| a == b)); if auto_traits_compatible { - let principal_def_id_a = data_a.principal_def_id(); - let principal_def_id_b = data_b.principal_def_id(); + let principal_def_id_a = a_data.principal_def_id(); + let principal_def_id_b = b_data.principal_def_id(); if principal_def_id_a == principal_def_id_b { // no cyclic candidates.vec.push(BuiltinUnsizeCandidate); } else if principal_def_id_a.is_some() && principal_def_id_b.is_some() { // not casual unsizing, now check whether this is trait upcasting coercion. - let principal_a = data_a.principal().unwrap(); + let principal_a = a_data.principal().unwrap(); let target_trait_did = principal_def_id_b.unwrap(); let source_trait_ref = principal_a.with_self_ty(self.tcx(), source); if let Some(deref_trait_ref) = self.need_migrate_deref_output_trait_object( @@ -731,9 +788,23 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { for (idx, upcast_trait_ref) in util::supertraits(self.tcx(), source_trait_ref).enumerate() { - if upcast_trait_ref.def_id() == target_trait_did { - candidates.vec.push(TraitUpcastingUnsizeCandidate(idx)); - } + self.infcx.probe(|_| { + if upcast_trait_ref.def_id() == target_trait_did + && let Ok(nested) = self.match_upcast_principal( + obligation, + upcast_trait_ref, + a_data, + b_data, + a_region, + b_region, + ) + { + if nested.is_none() { + candidates.ambiguous = true; + } + candidates.vec.push(TraitUpcastingUnsizeCandidate(idx)); + } + }) } } } @@ -778,7 +849,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(level = "debug", skip(self, obligation, candidates))] fn assemble_candidates_for_transmutability( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { if obligation.predicate.has_non_region_param() { @@ -796,7 +867,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(level = "debug", skip(self, obligation, candidates))] fn assemble_candidates_for_trait_alias( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // Okay to skip binder here because the tests we do below do not involve bound regions. @@ -833,12 +904,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_const_destruct_candidates( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // If the predicate is `~const Destruct` in a non-const environment, we don't actually need // to check anything. We'll short-circuit checking any obligations in confirmation, too. - if !obligation.is_const() { + // FIXME(effects) + if true { candidates.vec.push(ConstDestructCandidate(None)); return; } @@ -920,7 +992,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_candidate_for_tuple( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { let self_ty = self.infcx.shallow_resolve(obligation.self_ty().skip_binder()); @@ -962,7 +1034,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_candidate_for_pointer_like( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { // The regions of a type don't affect the size of the type @@ -987,7 +1059,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn assemble_candidates_for_fn_ptr_trait( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { let self_ty = self.infcx.shallow_resolve(obligation.self_ty()); diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 0d9f55d4c2edf..88d0300330917 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -6,16 +6,16 @@ //! //! [rustc dev guide]: //! https://rustc-dev-guide.rust-lang.org/traits/resolution.html#confirmation +use rustc_ast::Mutability; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_hir::lang_items::LangItem; use rustc_infer::infer::LateBoundRegionConversionTime::HigherRankedType; use rustc_infer::infer::{DefineOpaqueTypes, InferOk}; -use rustc_middle::traits::SelectionOutputTypeParameterMismatch; +use rustc_middle::traits::{BuiltinImplSource, SelectionOutputTypeParameterMismatch}; use rustc_middle::ty::{ - self, Binder, GenericParamDefKind, InternalSubsts, SubstsRef, ToPolyTraitRef, ToPredicate, - TraitRef, Ty, TyCtxt, TypeVisitableExt, + self, GenericArgs, GenericArgsRef, GenericParamDefKind, ToPolyTraitRef, ToPredicate, + TraitPredicate, Ty, TyCtxt, TypeVisitableExt, }; -use rustc_session::config::TraitSolver; use rustc_span::def_id::DefId; use crate::traits::project::{normalize_with_depth, normalize_with_depth_to}; @@ -26,12 +26,9 @@ use crate::traits::vtable::{ }; use crate::traits::{ BuiltinDerivedObligation, ImplDerivedObligation, ImplDerivedObligationCause, ImplSource, - ImplSourceAutoImplData, ImplSourceBuiltinData, ImplSourceClosureData, - ImplSourceConstDestructData, ImplSourceFnPointerData, ImplSourceFutureData, - ImplSourceGeneratorData, ImplSourceObjectData, ImplSourceTraitAliasData, - ImplSourceTraitUpcastingData, ImplSourceUserDefinedData, Normalized, Obligation, - ObligationCause, OutputTypeParameterMismatch, PredicateObligation, Selection, SelectionError, - TraitNotObjectSafe, TraitObligation, Unimplemented, + ImplSourceUserDefinedData, Normalized, Obligation, ObligationCause, + OutputTypeParameterMismatch, PolyTraitObligation, PredicateObligation, Selection, + SelectionError, TraitNotObjectSafe, Unimplemented, }; use super::BuiltinImplConditions; @@ -45,24 +42,24 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(level = "debug", skip(self))] pub(super) fn confirm_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, candidate: SelectionCandidate<'tcx>, ) -> Result, SelectionError<'tcx>> { let mut impl_src = match candidate { BuiltinCandidate { has_nested } => { let data = self.confirm_builtin_candidate(obligation, has_nested); - ImplSource::Builtin(data) + ImplSource::Builtin(BuiltinImplSource::Misc, data) } TransmutabilityCandidate => { let data = self.confirm_transmutability_candidate(obligation)?; - ImplSource::Builtin(data) + ImplSource::Builtin(BuiltinImplSource::Misc, data) } ParamCandidate(param) => { let obligations = self.confirm_param_candidate(obligation, param.map_bound(|t| t.trait_ref)); - ImplSource::Param(obligations, param.skip_binder().constness) + ImplSource::Param(obligations) } ImplCandidate(impl_def_id) => { @@ -70,65 +67,58 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } AutoImplCandidate => { - let data = self.confirm_auto_impl_candidate(obligation); - ImplSource::AutoImpl(data) + let data = self.confirm_auto_impl_candidate(obligation)?; + ImplSource::Builtin(BuiltinImplSource::Misc, data) } - ProjectionCandidate(idx, constness) => { + ProjectionCandidate(idx, _) => { let obligations = self.confirm_projection_candidate(obligation, idx)?; - ImplSource::Param(obligations, constness) + ImplSource::Param(obligations) } - ObjectCandidate(idx) => { - let data = self.confirm_object_candidate(obligation, idx)?; - ImplSource::Object(data) - } + ObjectCandidate(idx) => self.confirm_object_candidate(obligation, idx)?, ClosureCandidate { .. } => { let vtable_closure = self.confirm_closure_candidate(obligation)?; - ImplSource::Closure(vtable_closure) + ImplSource::Builtin(BuiltinImplSource::Misc, vtable_closure) } GeneratorCandidate => { let vtable_generator = self.confirm_generator_candidate(obligation)?; - ImplSource::Generator(vtable_generator) + ImplSource::Builtin(BuiltinImplSource::Misc, vtable_generator) } FutureCandidate => { let vtable_future = self.confirm_future_candidate(obligation)?; - ImplSource::Future(vtable_future) + ImplSource::Builtin(BuiltinImplSource::Misc, vtable_future) } FnPointerCandidate { is_const } => { let data = self.confirm_fn_pointer_candidate(obligation, is_const)?; - ImplSource::FnPointer(data) + ImplSource::Builtin(BuiltinImplSource::Misc, data) } TraitAliasCandidate => { let data = self.confirm_trait_alias_candidate(obligation); - ImplSource::TraitAlias(data) + ImplSource::Builtin(BuiltinImplSource::Misc, data) } BuiltinObjectCandidate => { // This indicates something like `Trait + Send: Send`. In this case, we know that // this holds because that's what the object type is telling us, and there's really // no additional obligations to prove and no types in particular to unify, etc. - ImplSource::Param(Vec::new(), ty::BoundConstness::NotConst) + ImplSource::Builtin(BuiltinImplSource::Misc, Vec::new()) } - BuiltinUnsizeCandidate => { - let data = self.confirm_builtin_unsize_candidate(obligation)?; - ImplSource::Builtin(data) - } + BuiltinUnsizeCandidate => self.confirm_builtin_unsize_candidate(obligation)?, TraitUpcastingUnsizeCandidate(idx) => { - let data = self.confirm_trait_upcasting_unsize_candidate(obligation, idx)?; - ImplSource::TraitUpcasting(data) + self.confirm_trait_upcasting_unsize_candidate(obligation, idx)? } ConstDestructCandidate(def_id) => { let data = self.confirm_const_destruct_candidate(obligation, def_id)?; - ImplSource::ConstDestruct(data) + ImplSource::Builtin(BuiltinImplSource::Misc, data) } }; @@ -138,20 +128,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { subobligation.set_depth_from_parent(obligation.recursion_depth); } - if !obligation.predicate.is_const_if_const() { - // normalize nested predicates according to parent predicate's constness. - impl_src = impl_src.map(|mut o| { - o.predicate = o.predicate.without_const(self.tcx()); - o - }); - } - Ok(impl_src) } fn confirm_projection_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, idx: usize, ) -> Result>, SelectionError<'tcx>> { let tcx = self.tcx(); @@ -161,17 +143,18 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { self.infcx.instantiate_binder_with_placeholders(trait_predicate).trait_ref; let placeholder_self_ty = placeholder_trait_predicate.self_ty(); let placeholder_trait_predicate = ty::Binder::dummy(placeholder_trait_predicate); - let (def_id, substs) = match *placeholder_self_ty.kind() { - // Excluding IATs here as they don't have meaningful item bounds. - ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - (def_id, substs) + let (def_id, args) = match *placeholder_self_ty.kind() { + // Excluding IATs and type aliases here as they don't have meaningful item bounds. + ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) => { + (def_id, args) } _ => bug!("projection candidate for unexpected type: {:?}", placeholder_self_ty), }; - let candidate_predicate = tcx.item_bounds(def_id).map_bound(|i| i[idx]).subst(tcx, substs); + let candidate_predicate = + tcx.item_bounds(def_id).map_bound(|i| i[idx]).instantiate(tcx, args); let candidate = candidate_predicate - .to_opt_poly_trait_pred() + .as_trait_clause() .expect("projection candidate is not a trait predicate") .map_bound(|t| t.trait_ref); let mut obligations = Vec::new(); @@ -193,7 +176,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { })?); if let ty::Alias(ty::Projection, ..) = placeholder_self_ty.kind() { - let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); + let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, args); for (predicate, _) in predicates { let normalized = normalize_with_depth_to( self, @@ -218,7 +201,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn confirm_param_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, param: ty::PolyTraitRef<'tcx>, ) -> Vec> { debug!(?obligation, ?param, "confirm_param_candidate"); @@ -241,9 +224,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn confirm_builtin_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, has_nested: bool, - ) -> ImplSourceBuiltinData> { + ) -> Vec> { debug!(?obligation, ?has_nested, "confirm_builtin_candidate"); let lang_items = self.tcx().lang_items(); @@ -276,14 +259,63 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { debug!(?obligations); - ImplSourceBuiltinData { nested: obligations } + obligations } + #[instrument(level = "debug", skip(self))] fn confirm_transmutability_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> Result>, SelectionError<'tcx>> { - debug!(?obligation, "confirm_transmutability_candidate"); + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, SelectionError<'tcx>> { + use rustc_transmute::{Answer, Condition}; + #[instrument(level = "debug", skip(tcx, obligation, predicate))] + fn flatten_answer_tree<'tcx>( + tcx: TyCtxt<'tcx>, + obligation: &PolyTraitObligation<'tcx>, + predicate: TraitPredicate<'tcx>, + cond: Condition>, + ) -> Vec> { + match cond { + // FIXME(bryangarza): Add separate `IfAny` case, instead of treating as `IfAll` + // Not possible until the trait solver supports disjunctions of obligations + Condition::IfAll(conds) | Condition::IfAny(conds) => conds + .into_iter() + .flat_map(|cond| flatten_answer_tree(tcx, obligation, predicate, cond)) + .collect(), + Condition::IfTransmutable { src, dst } => { + let trait_def_id = obligation.predicate.def_id(); + let scope = predicate.trait_ref.args.type_at(2); + let assume_const = predicate.trait_ref.args.const_at(3); + let make_obl = |from_ty, to_ty| { + let trait_ref1 = ty::TraitRef::new( + tcx, + trait_def_id, + [ + ty::GenericArg::from(to_ty), + ty::GenericArg::from(from_ty), + ty::GenericArg::from(scope), + ty::GenericArg::from(assume_const), + ], + ); + Obligation::with_depth( + tcx, + obligation.cause.clone(), + obligation.recursion_depth + 1, + obligation.param_env, + trait_ref1, + ) + }; + + // If Dst is mutable, check bidirectionally. + // For example, transmuting bool -> u8 is OK as long as you can't update that u8 + // to be > 1, because you could later transmute the u8 back to a bool and get UB. + match dst.mutability { + Mutability::Not => vec![make_obl(src.ty, dst.ty)], + Mutability::Mut => vec![make_obl(src.ty, dst.ty), make_obl(dst.ty, src.ty)], + } + } + } + } // We erase regions here because transmutability calls layout queries, // which does not handle inference regions and doesn't particularly @@ -296,26 +328,30 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let Some(assume) = rustc_transmute::Assume::from_const( self.infcx.tcx, obligation.param_env, - predicate.trait_ref.substs.const_at(3) + predicate.trait_ref.args.const_at(3), ) else { return Err(Unimplemented); }; + let dst = predicate.trait_ref.args.type_at(0); + let src = predicate.trait_ref.args.type_at(1); + debug!(?src, ?dst); let mut transmute_env = rustc_transmute::TransmuteTypeEnv::new(self.infcx); let maybe_transmutable = transmute_env.is_transmutable( obligation.cause.clone(), - rustc_transmute::Types { - dst: predicate.trait_ref.substs.type_at(0), - src: predicate.trait_ref.substs.type_at(1), - }, - predicate.trait_ref.substs.type_at(2), + rustc_transmute::Types { dst, src }, + predicate.trait_ref.args.type_at(2), assume, ); - match maybe_transmutable { - rustc_transmute::Answer::Yes => Ok(ImplSourceBuiltinData { nested: vec![] }), - _ => Err(Unimplemented), - } + let fully_flattened = match maybe_transmutable { + Answer::No(_) => Err(Unimplemented)?, + Answer::If(cond) => flatten_answer_tree(self.tcx(), obligation, predicate, cond), + Answer::Yes => vec![], + }; + + debug!(?fully_flattened); + Ok(fully_flattened) } /// This handles the case where an `auto trait Foo` impl is being used. @@ -325,22 +361,22 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// 2. For each where-clause `C` declared on `Foo`, `[Self => X] C` holds. fn confirm_auto_impl_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> ImplSourceAutoImplData> { + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, SelectionError<'tcx>> { debug!(?obligation, "confirm_auto_impl_candidate"); let self_ty = self.infcx.shallow_resolve(obligation.predicate.self_ty()); - let types = self.constituent_types_for_ty(self_ty); - self.vtable_auto_impl(obligation, obligation.predicate.def_id(), types) + let types = self.constituent_types_for_ty(self_ty)?; + Ok(self.vtable_auto_impl(obligation, obligation.predicate.def_id(), types)) } /// See `confirm_auto_impl_candidate`. fn vtable_auto_impl( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, trait_def_id: DefId, nested: ty::Binder<'tcx, Vec>>, - ) -> ImplSourceAutoImplData> { + ) -> Vec> { debug!(?nested, "vtable_auto_impl"); ensure_sufficient_stack(|| { let cause = obligation.derived_cause(BuiltinDerivedObligation); @@ -352,7 +388,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation.recursion_depth + 1, obligation.param_env, trait_def_id, - &trait_ref.substs, + &trait_ref.args, obligation.predicate, ); @@ -370,25 +406,25 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { debug!(?obligations, "vtable_auto_impl"); - ImplSourceAutoImplData { trait_def_id, nested: obligations } + obligations }) } fn confirm_impl_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, impl_def_id: DefId, ) -> ImplSourceUserDefinedData<'tcx, PredicateObligation<'tcx>> { debug!(?obligation, ?impl_def_id, "confirm_impl_candidate"); // First, create the substitutions by matching the impl again, // this time not in a probe. - let substs = self.rematch_impl(impl_def_id, obligation); - debug!(?substs, "impl substs"); + let args = self.rematch_impl(impl_def_id, obligation); + debug!(?args, "impl args"); ensure_sufficient_stack(|| { self.vtable_impl( impl_def_id, - substs, + args, &obligation.cause, obligation.recursion_depth + 1, obligation.param_env, @@ -400,40 +436,40 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn vtable_impl( &mut self, impl_def_id: DefId, - substs: Normalized<'tcx, SubstsRef<'tcx>>, + args: Normalized<'tcx, GenericArgsRef<'tcx>>, cause: &ObligationCause<'tcx>, recursion_depth: usize, param_env: ty::ParamEnv<'tcx>, parent_trait_pred: ty::Binder<'tcx, ty::TraitPredicate<'tcx>>, ) -> ImplSourceUserDefinedData<'tcx, PredicateObligation<'tcx>> { - debug!(?impl_def_id, ?substs, ?recursion_depth, "vtable_impl"); + debug!(?impl_def_id, ?args, ?recursion_depth, "vtable_impl"); let mut impl_obligations = self.impl_or_trait_obligations( cause, recursion_depth, param_env, impl_def_id, - &substs.value, + &args.value, parent_trait_pred, ); debug!(?impl_obligations, "vtable_impl"); // Because of RFC447, the impl-trait-ref and obligations - // are sufficient to determine the impl substs, without + // are sufficient to determine the impl args, without // relying on projections in the impl-trait-ref. // // e.g., `impl> Foo<::T> for V` - impl_obligations.extend(substs.obligations); + impl_obligations.extend(args.obligations); - ImplSourceUserDefinedData { impl_def_id, substs: substs.value, nested: impl_obligations } + ImplSourceUserDefinedData { impl_def_id, args: args.value, nested: impl_obligations } } fn confirm_object_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, index: usize, - ) -> Result>, SelectionError<'tcx>> { + ) -> Result>, SelectionError<'tcx>> { let tcx = self.tcx(); debug!(?obligation, ?index, "confirm_object_candidate"); @@ -481,7 +517,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // will be checked in the code below. for super_trait in tcx .super_predicates_of(trait_predicate.def_id()) - .instantiate(tcx, trait_predicate.trait_ref.substs) + .instantiate(tcx, trait_predicate.trait_ref.args) .predicates .into_iter() { @@ -519,67 +555,65 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // higher-ranked things. // Prevent, e.g., `dyn Iterator`. for bound in self.tcx().item_bounds(assoc_type).transpose_iter() { - let subst_bound = - if defs.count() == 0 { - bound.subst(tcx, trait_predicate.trait_ref.substs) - } else { - let mut substs = smallvec::SmallVec::with_capacity(defs.count()); - substs.extend(trait_predicate.trait_ref.substs.iter()); - let mut bound_vars: smallvec::SmallVec<[ty::BoundVariableKind; 8]> = - smallvec::SmallVec::with_capacity( - bound.0.kind().bound_vars().len() + defs.count(), - ); - bound_vars.extend(bound.0.kind().bound_vars().into_iter()); - InternalSubsts::fill_single(&mut substs, defs, &mut |param, _| match param - .kind - { - GenericParamDefKind::Type { .. } => { - let kind = ty::BoundTyKind::Param(param.def_id, param.name); - let bound_var = ty::BoundVariableKind::Ty(kind); - bound_vars.push(bound_var); - tcx.mk_bound( - ty::INNERMOST, - ty::BoundTy { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind, - }, - ) - .into() - } - GenericParamDefKind::Lifetime => { - let kind = ty::BoundRegionKind::BrNamed(param.def_id, param.name); - let bound_var = ty::BoundVariableKind::Region(kind); - bound_vars.push(bound_var); - tcx.mk_re_late_bound( - ty::INNERMOST, - ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind, - }, - ) - .into() - } - GenericParamDefKind::Const { .. } => { - let bound_var = ty::BoundVariableKind::Const; - bound_vars.push(bound_var); - tcx.mk_const( - ty::ConstKind::Bound( - ty::INNERMOST, - ty::BoundVar::from_usize(bound_vars.len() - 1), - ), - tcx.type_of(param.def_id) - .no_bound_vars() - .expect("const parameter types cannot be generic"), - ) - .into() - } - }); - let bound_vars = tcx.mk_bound_variable_kinds(&bound_vars); - let assoc_ty_substs = tcx.mk_substs(&substs); - let bound = - bound.map_bound(|b| b.kind().skip_binder()).subst(tcx, assoc_ty_substs); - tcx.mk_predicate(ty::Binder::bind_with_vars(bound, bound_vars)) - }; + let subst_bound = if defs.count() == 0 { + bound.instantiate(tcx, trait_predicate.trait_ref.args) + } else { + let mut args = smallvec::SmallVec::with_capacity(defs.count()); + args.extend(trait_predicate.trait_ref.args.iter()); + let mut bound_vars: smallvec::SmallVec<[ty::BoundVariableKind; 8]> = + smallvec::SmallVec::with_capacity( + bound.skip_binder().kind().bound_vars().len() + defs.count(), + ); + bound_vars.extend(bound.skip_binder().kind().bound_vars().into_iter()); + GenericArgs::fill_single(&mut args, defs, &mut |param, _| match param.kind { + GenericParamDefKind::Type { .. } => { + let kind = ty::BoundTyKind::Param(param.def_id, param.name); + let bound_var = ty::BoundVariableKind::Ty(kind); + bound_vars.push(bound_var); + Ty::new_bound( + tcx, + ty::INNERMOST, + ty::BoundTy { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind, + }, + ) + .into() + } + GenericParamDefKind::Lifetime => { + let kind = ty::BoundRegionKind::BrNamed(param.def_id, param.name); + let bound_var = ty::BoundVariableKind::Region(kind); + bound_vars.push(bound_var); + ty::Region::new_late_bound( + tcx, + ty::INNERMOST, + ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind, + }, + ) + .into() + } + GenericParamDefKind::Const { .. } => { + let bound_var = ty::BoundVariableKind::Const; + bound_vars.push(bound_var); + ty::Const::new_bound( + tcx, + ty::INNERMOST, + ty::BoundVar::from_usize(bound_vars.len() - 1), + tcx.type_of(param.def_id) + .no_bound_vars() + .expect("const parameter types cannot be generic"), + ) + .into() + } + }); + let bound_vars = tcx.mk_bound_variable_kinds(&bound_vars); + let assoc_ty_args = tcx.mk_args(&args); + let bound = + bound.map_bound(|b| b.kind().skip_binder()).instantiate(tcx, assoc_ty_args); + ty::Binder::bind_with_vars(bound, bound_vars).to_predicate(tcx) + }; let normalized_bound = normalize_with_depth_to( self, obligation.param_env, @@ -599,23 +633,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { (unnormalized_upcast_trait_ref, ty::Binder::dummy(object_trait_ref)), ); - Ok(ImplSourceObjectData { upcast_trait_ref, vtable_base, nested }) + Ok(ImplSource::Builtin(BuiltinImplSource::Object { vtable_base: vtable_base }, nested)) } fn confirm_fn_pointer_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - is_const: bool, - ) -> Result>, SelectionError<'tcx>> - { + obligation: &PolyTraitObligation<'tcx>, + // FIXME(effects) + _is_const: bool, + ) -> Result>, SelectionError<'tcx>> { debug!(?obligation, "confirm_fn_pointer_candidate"); let tcx = self.tcx(); - let Some(self_ty) = self - .infcx - .shallow_resolve(obligation.self_ty().no_bound_vars()) else - { + let Some(self_ty) = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars()) else { // FIXME: Ideally we'd support `for<'a> fn(&'a ()): Fn(&'a ())`, // but we do not currently. Luckily, such a bound is not // particularly useful, so we don't expect users to write @@ -636,16 +667,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let mut nested = self.confirm_poly_trait_refs(obligation, trait_ref)?; let cause = obligation.derived_cause(BuiltinDerivedObligation); - if obligation.is_const() && !is_const { - // function is a trait method - if let ty::FnDef(def_id, substs) = self_ty.kind() && let Some(trait_id) = tcx.trait_of_item(*def_id) { - let trait_ref = TraitRef::from_method(tcx, trait_id, *substs); - let poly_trait_pred = Binder::dummy(trait_ref).with_constness(ty::BoundConstness::ConstIfConst); - let obligation = Obligation::new(tcx, cause.clone(), obligation.param_env, poly_trait_pred); - nested.push(obligation); - } - } - // Confirm the `type Output: Sized;` bound that is present on `FnOnce` let output_ty = self.infcx.instantiate_binder_with_placeholders(sig.output()); let output_ty = normalize_with_depth_to( @@ -659,51 +680,49 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let tr = ty::TraitRef::from_lang_item(self.tcx(), LangItem::Sized, cause.span, [output_ty]); nested.push(Obligation::new(self.infcx.tcx, cause, obligation.param_env, tr)); - Ok(ImplSourceFnPointerData { fn_ty: self_ty, nested }) + Ok(nested) } fn confirm_trait_alias_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> ImplSourceTraitAliasData<'tcx, PredicateObligation<'tcx>> { + obligation: &PolyTraitObligation<'tcx>, + ) -> Vec> { debug!(?obligation, "confirm_trait_alias_candidate"); - let alias_def_id = obligation.predicate.def_id(); let predicate = self.infcx.instantiate_binder_with_placeholders(obligation.predicate); let trait_ref = predicate.trait_ref; let trait_def_id = trait_ref.def_id; - let substs = trait_ref.substs; + let args = trait_ref.args; let trait_obligations = self.impl_or_trait_obligations( &obligation.cause, obligation.recursion_depth, obligation.param_env, trait_def_id, - &substs, + &args, obligation.predicate, ); debug!(?trait_def_id, ?trait_obligations, "trait alias obligations"); - ImplSourceTraitAliasData { alias_def_id, substs, nested: trait_obligations } + trait_obligations } fn confirm_generator_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> Result>, SelectionError<'tcx>> - { - // Okay to skip binder because the substs on generator types never + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, SelectionError<'tcx>> { + // Okay to skip binder because the args on generator types never // touch bound regions, they just capture the in-scope // type/region parameters. let self_ty = self.infcx.shallow_resolve(obligation.self_ty().skip_binder()); - let ty::Generator(generator_def_id, substs, _) = *self_ty.kind() else { + let ty::Generator(generator_def_id, args, _) = *self_ty.kind() else { bug!("closure candidate for non-closure {:?}", obligation); }; - debug!(?obligation, ?generator_def_id, ?substs, "confirm_generator_candidate"); + debug!(?obligation, ?generator_def_id, ?args, "confirm_generator_candidate"); - let gen_sig = substs.as_generator().poly_sig(); + let gen_sig = args.as_generator().poly_sig(); // NOTE: The self-type is a generator type and hence is // in fact unparameterized (or at least does not reference any @@ -725,24 +744,24 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let nested = self.confirm_poly_trait_refs(obligation, trait_ref)?; debug!(?trait_ref, ?nested, "generator candidate obligations"); - Ok(ImplSourceGeneratorData { generator_def_id, substs, nested }) + Ok(nested) } fn confirm_future_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> Result>, SelectionError<'tcx>> { - // Okay to skip binder because the substs on generator types never + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, SelectionError<'tcx>> { + // Okay to skip binder because the args on generator types never // touch bound regions, they just capture the in-scope // type/region parameters. let self_ty = self.infcx.shallow_resolve(obligation.self_ty().skip_binder()); - let ty::Generator(generator_def_id, substs, _) = *self_ty.kind() else { + let ty::Generator(generator_def_id, args, _) = *self_ty.kind() else { bug!("closure candidate for non-closure {:?}", obligation); }; - debug!(?obligation, ?generator_def_id, ?substs, "confirm_future_candidate"); + debug!(?obligation, ?generator_def_id, ?args, "confirm_future_candidate"); - let gen_sig = substs.as_generator().poly_sig(); + let gen_sig = args.as_generator().poly_sig(); let trait_ref = super::util::future_trait_ref_and_outputs( self.tcx(), @@ -755,41 +774,38 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let nested = self.confirm_poly_trait_refs(obligation, trait_ref)?; debug!(?trait_ref, ?nested, "future candidate obligations"); - Ok(ImplSourceFutureData { generator_def_id, substs, nested }) + Ok(nested) } #[instrument(skip(self), level = "debug")] fn confirm_closure_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> Result>, SelectionError<'tcx>> { + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, SelectionError<'tcx>> { let kind = self .tcx() .fn_trait_kind_from_def_id(obligation.predicate.def_id()) .unwrap_or_else(|| bug!("closure candidate for non-fn trait {:?}", obligation)); - // Okay to skip binder because the substs on closure types never + // Okay to skip binder because the args on closure types never // touch bound regions, they just capture the in-scope // type/region parameters. let self_ty = self.infcx.shallow_resolve(obligation.self_ty().skip_binder()); - let ty::Closure(closure_def_id, substs) = *self_ty.kind() else { + let ty::Closure(closure_def_id, args) = *self_ty.kind() else { bug!("closure candidate for non-closure {:?}", obligation); }; - let trait_ref = self.closure_trait_ref_unnormalized(obligation, substs); + let trait_ref = self.closure_trait_ref_unnormalized(obligation, args); let mut nested = self.confirm_poly_trait_refs(obligation, trait_ref)?; debug!(?closure_def_id, ?trait_ref, ?nested, "confirm closure candidate obligations"); - // FIXME: Chalk - if self.tcx().sess.opts.unstable_opts.trait_solver != TraitSolver::Chalk { - nested.push(obligation.with( - self.tcx(), - ty::Binder::dummy(ty::PredicateKind::ClosureKind(closure_def_id, substs, kind)), - )); - } + nested.push(obligation.with( + self.tcx(), + ty::Binder::dummy(ty::PredicateKind::ClosureKind(closure_def_id, args, kind)), + )); - Ok(ImplSourceClosureData { closure_def_id, substs, nested }) + Ok(nested) } /// In the case of closure types and fn pointers, @@ -820,7 +836,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(skip(self), level = "trace")] fn confirm_poly_trait_refs( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, self_ty_trait_ref: ty::PolyTraitRef<'tcx>, ) -> Result>, SelectionError<'tcx>> { let obligation_trait_ref = obligation.predicate.to_poly_trait_ref(); @@ -855,76 +871,34 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn confirm_trait_upcasting_unsize_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, idx: usize, - ) -> Result>, SelectionError<'tcx>> - { + ) -> Result>, SelectionError<'tcx>> { let tcx = self.tcx(); // `assemble_candidates_for_unsizing` should ensure there are no late-bound // regions here. See the comment there for more details. - let source = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars().unwrap()); - let target = obligation.predicate.skip_binder().trait_ref.substs.type_at(1); - let target = self.infcx.shallow_resolve(target); - - debug!(?source, ?target, "confirm_trait_upcasting_unsize_candidate"); - - let mut nested = vec![]; - let source_trait_ref; - let upcast_trait_ref; - match (source.kind(), target.kind()) { - // TraitA+Kx+'a -> TraitB+Ky+'b (trait upcasting coercion). - ( - &ty::Dynamic(ref data_a, r_a, repr_a @ ty::Dyn), - &ty::Dynamic(ref data_b, r_b, ty::Dyn), - ) => { - // See `assemble_candidates_for_unsizing` for more info. - // We already checked the compatibility of auto traits within `assemble_candidates_for_unsizing`. - let principal_a = data_a.principal().unwrap(); - source_trait_ref = principal_a.with_self_ty(tcx, source); - upcast_trait_ref = util::supertraits(tcx, source_trait_ref).nth(idx).unwrap(); - assert_eq!(data_b.principal_def_id(), Some(upcast_trait_ref.def_id())); - let existential_predicate = upcast_trait_ref.map_bound(|trait_ref| { - ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef::erase_self_ty( - tcx, trait_ref, - )) - }); - let iter = Some(existential_predicate) - .into_iter() - .chain( - data_a - .projection_bounds() - .map(|b| b.map_bound(ty::ExistentialPredicate::Projection)), - ) - .chain( - data_b - .auto_traits() - .map(ty::ExistentialPredicate::AutoTrait) - .map(ty::Binder::dummy), - ); - let existential_predicates = tcx.mk_poly_existential_predicates_from_iter(iter); - let source_trait = tcx.mk_dynamic(existential_predicates, r_b, repr_a); - - // Require that the traits involved in this upcast are **equal**; - // only the **lifetime bound** is changed. - let InferOk { obligations, .. } = self - .infcx - .at(&obligation.cause, obligation.param_env) - .sup(DefineOpaqueTypes::No, target, source_trait) - .map_err(|_| Unimplemented)?; - nested.extend(obligations); - - let outlives = ty::OutlivesPredicate(r_a, r_b); - nested.push(Obligation::with_depth( - tcx, - obligation.cause.clone(), - obligation.recursion_depth + 1, - obligation.param_env, - obligation.predicate.rebind(outlives), - )); - } - _ => bug!(), - }; + let predicate = obligation.predicate.no_bound_vars().unwrap(); + let a_ty = self.infcx.shallow_resolve(predicate.self_ty()); + let b_ty = self.infcx.shallow_resolve(predicate.trait_ref.args.type_at(1)); + + let ty::Dynamic(a_data, a_region, ty::Dyn) = *a_ty.kind() else { bug!() }; + let ty::Dynamic(b_data, b_region, ty::Dyn) = *b_ty.kind() else { bug!() }; + + let source_principal = a_data.principal().unwrap().with_self_ty(tcx, a_ty); + let unnormalized_upcast_principal = + util::supertraits(tcx, source_principal).nth(idx).unwrap(); + + let nested = self + .match_upcast_principal( + obligation, + unnormalized_upcast_principal, + a_data, + b_data, + a_region, + b_region, + )? + .expect("did not expect ambiguity during confirmation"); let vtable_segment_callback = { let mut vptr_offset = 0; @@ -935,7 +909,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } VtblSegment::TraitOwnEntries { trait_ref, emit_vptr } => { vptr_offset += count_own_vtable_entries(tcx, trait_ref); - if trait_ref == upcast_trait_ref { + if trait_ref == unnormalized_upcast_principal { if emit_vptr { return ControlFlow::Break(Some(vptr_offset)); } else { @@ -953,27 +927,25 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { }; let vtable_vptr_slot = - prepare_vtable_segments(tcx, source_trait_ref, vtable_segment_callback).unwrap(); + prepare_vtable_segments(tcx, source_principal, vtable_segment_callback).unwrap(); - Ok(ImplSourceTraitUpcastingData { upcast_trait_ref, vtable_vptr_slot, nested }) + Ok(ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { vtable_vptr_slot }, nested)) } fn confirm_builtin_unsize_candidate( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> Result>, SelectionError<'tcx>> { + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, SelectionError<'tcx>> { let tcx = self.tcx(); // `assemble_candidates_for_unsizing` should ensure there are no late-bound // regions here. See the comment there for more details. let source = self.infcx.shallow_resolve(obligation.self_ty().no_bound_vars().unwrap()); - let target = obligation.predicate.skip_binder().trait_ref.substs.type_at(1); + let target = obligation.predicate.skip_binder().trait_ref.args.type_at(1); let target = self.infcx.shallow_resolve(target); - debug!(?source, ?target, "confirm_builtin_unsize_candidate"); - let mut nested = vec![]; - match (source.kind(), target.kind()) { + Ok(match (source.kind(), target.kind()) { // Trait+Kx+'a -> Trait+Ky+'b (auto traits and lifetime subtyping). (&ty::Dynamic(ref data_a, r_a, dyn_a), &ty::Dynamic(ref data_b, r_b, dyn_b)) if dyn_a == dyn_b => @@ -996,26 +968,27 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .map(ty::Binder::dummy), ); let existential_predicates = tcx.mk_poly_existential_predicates_from_iter(iter); - let source_trait = tcx.mk_dynamic(existential_predicates, r_b, dyn_a); + let source_trait = Ty::new_dynamic(tcx, existential_predicates, r_b, dyn_a); // Require that the traits involved in this upcast are **equal**; // only the **lifetime bound** is changed. - let InferOk { obligations, .. } = self + let InferOk { mut obligations, .. } = self .infcx .at(&obligation.cause, obligation.param_env) .sup(DefineOpaqueTypes::No, target, source_trait) .map_err(|_| Unimplemented)?; - nested.extend(obligations); // Register one obligation for 'a: 'b. let outlives = ty::OutlivesPredicate(r_a, r_b); - nested.push(Obligation::with_depth( + obligations.push(Obligation::with_depth( tcx, obligation.cause.clone(), obligation.recursion_depth + 1, obligation.param_env, obligation.predicate.rebind(outlives), )); + + ImplSource::Builtin(BuiltinImplSource::Misc, obligations) } // `T` -> `Trait` @@ -1041,11 +1014,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // words, if the object type is `Foo + Send`, this would create an obligation for // the `Send` check.) // - Projection predicates - nested.extend( - data.iter().map(|predicate| { - predicate_to_obligation(predicate.with_self_ty(tcx, source)) - }), - ); + let mut nested: Vec<_> = data + .iter() + .map(|predicate| predicate_to_obligation(predicate.with_self_ty(tcx, source))) + .collect(); // We can only make objects from sized types. let tr = ty::TraitRef::from_lang_item( @@ -1054,12 +1026,16 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation.cause.span, [source], ); - nested.push(predicate_to_obligation(tr.without_const().to_predicate(tcx))); + nested.push(predicate_to_obligation(tr.to_predicate(tcx))); // If the type is `Foo + 'a`, ensure that the type // being cast to `Foo + 'a` outlives `'a`: let outlives = ty::OutlivesPredicate(source, r); - nested.push(predicate_to_obligation(ty::Binder::dummy(outlives).to_predicate(tcx))); + nested.push(predicate_to_obligation( + ty::Binder::dummy(ty::ClauseKind::TypeOutlives(outlives)).to_predicate(tcx), + )); + + ImplSource::Builtin(BuiltinImplSource::Misc, nested) } // `[T; n]` -> `[T]` @@ -1069,24 +1045,22 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .at(&obligation.cause, obligation.param_env) .eq(DefineOpaqueTypes::No, b, a) .map_err(|_| Unimplemented)?; - nested.extend(obligations); + + ImplSource::Builtin(BuiltinImplSource::Misc, obligations) } // `Struct` -> `Struct` - (&ty::Adt(def, substs_a), &ty::Adt(_, substs_b)) => { + (&ty::Adt(def, args_a), &ty::Adt(_, args_b)) => { let unsizing_params = tcx.unsizing_params_for_adt(def.did()); if unsizing_params.is_empty() { return Err(Unimplemented); } - let tail_field = def - .non_enum_variant() - .fields - .raw - .last() - .expect("expected unsized ADT to have a tail field"); + let tail_field = def.non_enum_variant().tail(); let tail_field_ty = tcx.type_of(tail_field.did); + let mut nested = vec![]; + // Extract `TailField` and `TailField` from `Struct` and `Struct`, // normalizing in the process, since `type_of` returns something directly from // astconv (which means it's un-normalized). @@ -1095,7 +1069,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation.param_env, obligation.cause.clone(), obligation.recursion_depth + 1, - tail_field_ty.subst(tcx, substs_a), + tail_field_ty.instantiate(tcx, args_a), &mut nested, ); let target_tail = normalize_with_depth_to( @@ -1103,16 +1077,17 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation.param_env, obligation.cause.clone(), obligation.recursion_depth + 1, - tail_field_ty.subst(tcx, substs_b), + tail_field_ty.instantiate(tcx, args_b), &mut nested, ); // Check that the source struct with the target's // unsizing parameters is equal to the target. - let substs = tcx.mk_substs_from_iter(substs_a.iter().enumerate().map(|(i, k)| { - if unsizing_params.contains(i as u32) { substs_b[i] } else { k } - })); - let new_struct = tcx.mk_adt(def, substs); + let args = + tcx.mk_args_from_iter(args_a.iter().enumerate().map(|(i, k)| { + if unsizing_params.contains(i as u32) { args_b[i] } else { k } + })); + let new_struct = Ty::new_adt(tcx, def, args); let InferOk { obligations, .. } = self .infcx .at(&obligation.cause, obligation.param_env) @@ -1130,6 +1105,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ), ); nested.push(tail_unsize_obligation); + + ImplSource::Builtin(BuiltinImplSource::Misc, nested) } // `(.., T)` -> `(.., U)` @@ -1143,36 +1120,36 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // Check that the source tuple with the target's // last element is equal to the target. let new_tuple = - tcx.mk_tup_from_iter(a_mid.iter().copied().chain(iter::once(b_last))); - let InferOk { obligations, .. } = self + Ty::new_tup_from_iter(tcx, a_mid.iter().copied().chain(iter::once(b_last))); + let InferOk { mut obligations, .. } = self .infcx .at(&obligation.cause, obligation.param_env) .eq(DefineOpaqueTypes::No, target, new_tuple) .map_err(|_| Unimplemented)?; - nested.extend(obligations); // Add a nested `T: Unsize` predicate. let last_unsize_obligation = obligation.with( tcx, ty::TraitRef::new(tcx, obligation.predicate.def_id(), [a_last, b_last]), ); - nested.push(last_unsize_obligation); + obligations.push(last_unsize_obligation); + + ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, obligations) } _ => bug!("source: {source}, target: {target}"), - }; - - Ok(ImplSourceBuiltinData { nested }) + }) } fn confirm_const_destruct_candidate( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, impl_def_id: Option, - ) -> Result>, SelectionError<'tcx>> { + ) -> Result>, SelectionError<'tcx>> { // `~const Destruct` in a non-const environment is always trivially true, since our type is `Drop` - if !obligation.is_const() { - return Ok(ImplSourceConstDestructData { nested: vec![] }); + // FIXME(effects) + if true { + return Ok(vec![]); } let drop_trait = self.tcx().require_lang_item(LangItem::Drop, None); @@ -1192,8 +1169,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { trait_pred.trait_ref.def_id = drop_trait; trait_pred }); - let substs = self.rematch_impl(impl_def_id, &new_obligation); - debug!(?substs, "impl substs"); + let args = self.rematch_impl(impl_def_id, &new_obligation); + debug!(?args, "impl args"); let cause = obligation.derived_cause(|derived| { ImplDerivedObligation(Box::new(ImplDerivedObligationCause { @@ -1206,7 +1183,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let obligations = ensure_sufficient_stack(|| { self.vtable_impl( impl_def_id, - substs, + args, &cause, new_obligation.recursion_depth + 1, new_obligation.param_env, @@ -1218,7 +1195,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // We want to confirm the ADT's fields if we have an ADT let mut stack = match *self_ty.skip_binder().kind() { - ty::Adt(def, substs) => def.all_fields().map(|f| f.ty(tcx, substs)).collect(), + ty::Adt(def, args) => def.all_fields().map(|f| f.ty(tcx, args)).collect(), _ => vec![self_ty.skip_binder()], }; @@ -1251,20 +1228,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ty::Tuple(tys) => { stack.extend(tys.iter()); } - ty::Closure(_, substs) => { - stack.push(substs.as_closure().tupled_upvars_ty()); + ty::Closure(_, args) => { + stack.push(args.as_closure().tupled_upvars_ty()); } - ty::Generator(_, substs, _) => { - let generator = substs.as_generator(); + ty::Generator(_, args, _) => { + let generator = args.as_generator(); stack.extend([generator.tupled_upvars_ty(), generator.witness()]); } ty::GeneratorWitness(tys) => { stack.extend(tcx.erase_late_bound_regions(tys).to_vec()); } - ty::GeneratorWitnessMIR(def_id, substs) => { + ty::GeneratorWitnessMIR(def_id, args) => { let tcx = self.tcx(); stack.extend(tcx.generator_hidden_types(def_id).map(|bty| { - let ty = bty.subst(tcx, substs); + let ty = bty.instantiate(tcx, args); debug_assert!(!ty.has_late_bound_regions()); ty })) @@ -1273,6 +1250,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // If we have a projection type, make sure to normalize it so we replace it // with a fresh infer variable ty::Alias(ty::Projection | ty::Inherent, ..) => { + // FIXME(effects) this needs constness let predicate = normalize_with_depth_to( self, obligation.param_env, @@ -1285,7 +1263,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { cause.span, [nested_ty], ), - constness: ty::BoundConstness::ConstIfConst, polarity: ty::ImplPolarity::Positive, }), &mut nested, @@ -1304,6 +1281,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // since it's either not `const Drop` (and we raise an error during selection), // or it's an ADT (and we need to check for a custom impl during selection) _ => { + // FIXME(effects) this needs constness let predicate = self_ty.rebind(ty::TraitPredicate { trait_ref: ty::TraitRef::from_lang_item( self.tcx(), @@ -1311,7 +1289,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { cause.span, [nested_ty], ), - constness: ty::BoundConstness::ConstIfConst, polarity: ty::ImplPolarity::Positive, }); @@ -1326,6 +1303,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - Ok(ImplSourceConstDestructData { nested }) + Ok(nested) } } diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 3baf1c97c9f46..19385e2d7f260 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -15,11 +15,12 @@ use super::util::closure_trait_ref_and_return_type; use super::wf; use super::{ ErrorReporting, ImplDerivedObligation, ImplDerivedObligationCause, Normalized, Obligation, - ObligationCause, ObligationCauseCode, Overflow, PredicateObligation, Selection, SelectionError, - SelectionResult, TraitObligation, TraitQueryMode, + ObligationCause, ObligationCauseCode, Overflow, PolyTraitObligation, PredicateObligation, + Selection, SelectionError, SelectionResult, TraitQueryMode, }; use crate::infer::{InferCtxt, InferOk, TypeFreshener}; +use crate::solve::InferCtxtSelectExt; use crate::traits::error_reporting::TypeErrCtxtExt; use crate::traits::project::try_normalize_with_depth_to; use crate::traits::project::ProjectAndUnifyResult; @@ -33,14 +34,13 @@ use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_infer::infer::DefineOpaqueTypes; use rustc_infer::infer::LateBoundRegionConversionTime; -use rustc_infer::traits::TraitEngine; -use rustc_infer::traits::TraitEngineExt; +use rustc_infer::traits::TraitObligation; use rustc_middle::dep_graph::{DepKind, DepNodeIndex}; use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::ty::abstract_const::NotConstEvaluatable; use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::relate::TypeRelation; -use rustc_middle::ty::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, EarlyBinder, PolyProjectionPredicate, ToPolyTraitRef, ToPredicate}; use rustc_middle::ty::{Ty, TyCtxt, TypeFoldable, TypeVisitableExt}; use rustc_span::symbol::sym; @@ -74,22 +74,21 @@ impl IntercrateAmbiguityCause { match self { IntercrateAmbiguityCause::DownstreamCrate { trait_desc, self_desc } => { let self_desc = if let Some(ty) = self_desc { - format!(" for type `{}`", ty) + format!(" for type `{ty}`") } else { String::new() }; - format!("downstream crates may implement trait `{}`{}", trait_desc, self_desc) + format!("downstream crates may implement trait `{trait_desc}`{self_desc}") } IntercrateAmbiguityCause::UpstreamCrateUpdate { trait_desc, self_desc } => { let self_desc = if let Some(ty) = self_desc { - format!(" for type `{}`", ty) + format!(" for type `{ty}`") } else { String::new() }; format!( - "upstream crates may add a new impl of trait `{}`{} \ - in future versions", - trait_desc, self_desc + "upstream crates may add a new impl of trait `{trait_desc}`{self_desc} \ + in future versions" ) } IntercrateAmbiguityCause::ReservationImpl { message } => message.clone(), @@ -119,11 +118,13 @@ pub struct SelectionContext<'cx, 'tcx> { /// policy. In essence, canonicalized queries need their errors propagated /// rather than immediately reported because we do not have accurate spans. query_mode: TraitQueryMode, + + treat_inductive_cycle: TreatInductiveCycleAs, } // A stack that walks back up the stack frame. struct TraitObligationStack<'prev, 'tcx> { - obligation: &'prev TraitObligation<'tcx>, + obligation: &'prev PolyTraitObligation<'tcx>, /// The trait predicate from `obligation` but "freshened" with the /// selection-context's freshener. Used to check for recursion. @@ -199,6 +200,27 @@ enum BuiltinImplConditions<'tcx> { Ambiguous, } +#[derive(Copy, Clone)] +pub enum TreatInductiveCycleAs { + /// This is the previous behavior, where `Recur` represents an inductive + /// cycle that is known not to hold. This is not forwards-compatible with + /// coinduction, and will be deprecated. This is the default behavior + /// of the old trait solver due to back-compat reasons. + Recur, + /// This is the behavior of the new trait solver, where inductive cycles + /// are treated as ambiguous and possibly holding. + Ambig, +} + +impl From for EvaluationResult { + fn from(treat: TreatInductiveCycleAs) -> EvaluationResult { + match treat { + TreatInductiveCycleAs::Ambig => EvaluatedToUnknown, + TreatInductiveCycleAs::Recur => EvaluatedToRecur, + } + } +} + impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { pub fn new(infcx: &'cx InferCtxt<'tcx>) -> SelectionContext<'cx, 'tcx> { SelectionContext { @@ -206,9 +228,26 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { freshener: infcx.freshener(), intercrate_ambiguity_causes: None, query_mode: TraitQueryMode::Standard, + treat_inductive_cycle: TreatInductiveCycleAs::Recur, } } + // Sets the `TreatInductiveCycleAs` mode temporarily in the selection context + pub fn with_treat_inductive_cycle_as( + &mut self, + treat_inductive_cycle: TreatInductiveCycleAs, + f: impl FnOnce(&mut Self) -> T, + ) -> T { + // Should be executed in a context where caching is disabled, + // otherwise the cache is poisoned with the temporary result. + assert!(self.is_intercrate()); + let treat_inductive_cycle = + std::mem::replace(&mut self.treat_inductive_cycle, treat_inductive_cycle); + let value = f(self); + self.treat_inductive_cycle = treat_inductive_cycle; + value + } + pub fn with_query_mode( infcx: &'cx InferCtxt<'tcx>, query_mode: TraitQueryMode, @@ -260,10 +299,14 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// Attempts to satisfy the obligation. If successful, this will affect the surrounding /// type environment by performing unification. #[instrument(level = "debug", skip(self), ret)] - pub fn select( + pub fn poly_select( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, ) -> SelectionResult<'tcx, Selection<'tcx>> { + if self.infcx.next_trait_solver() { + return self.infcx.select_in_new_trait_solver(obligation); + } + let candidate = match self.select_from_obligation(obligation) { Err(SelectionError::Overflow(OverflowError::Canonical)) => { // In standard mode, overflow must have been caught and reported @@ -290,9 +333,21 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - pub(crate) fn select_from_obligation( + pub fn select( &mut self, obligation: &TraitObligation<'tcx>, + ) -> SelectionResult<'tcx, Selection<'tcx>> { + self.poly_select(&Obligation { + cause: obligation.cause.clone(), + param_env: obligation.param_env, + predicate: ty::Binder::dummy(obligation.predicate), + recursion_depth: obligation.recursion_depth, + }) + } + + fn select_from_obligation( + &mut self, + obligation: &PolyTraitObligation<'tcx>, ) -> SelectionResult<'tcx, SelectionCandidate<'tcx>> { debug_assert!(!obligation.predicate.has_escaping_bound_vars()); @@ -307,6 +362,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { &mut self, stack: &TraitObligationStack<'o, 'tcx>, ) -> SelectionResult<'tcx, SelectionCandidate<'tcx>> { + debug_assert!(!self.infcx.next_trait_solver()); // Watch out for overflow. This intentionally bypasses (and does // not update) the cache. self.check_recursion_limit(&stack.obligation, &stack.obligation)?; @@ -365,7 +421,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } if !candidate_set.ambiguous && no_candidates_apply { - let trait_ref = stack.obligation.predicate.skip_binder().trait_ref; + let trait_ref = self.infcx.resolve_vars_if_possible( + stack.obligation.predicate.skip_binder().trait_ref, + ); if !trait_ref.references_error() { let self_ty = trait_ref.self_ty(); let (trait_desc, self_desc) = with_no_trimmed_paths!({ @@ -516,37 +574,23 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // The result is "true" if the obligation *may* hold and "false" if // we can be sure it does not. - /// Evaluates whether the obligation `obligation` can be satisfied (by any means). - pub fn predicate_may_hold_fatal(&mut self, obligation: &PredicateObligation<'tcx>) -> bool { - debug!(?obligation, "predicate_may_hold_fatal"); - - // This fatal query is a stopgap that should only be used in standard mode, - // where we do not expect overflow to be propagated. - assert!(self.query_mode == TraitQueryMode::Standard); - - self.evaluate_root_obligation(obligation) - .expect("Overflow should be caught earlier in standard query mode") - .may_apply() - } - /// Evaluates whether the obligation `obligation` can be satisfied /// and returns an `EvaluationResult`. This is meant for the /// *initial* call. + /// + /// Do not use this directly, use `infcx.evaluate_obligation` instead. pub fn evaluate_root_obligation( &mut self, obligation: &PredicateObligation<'tcx>, ) -> Result { + debug_assert!(!self.infcx.next_trait_solver()); self.evaluation_probe(|this| { let goal = this.infcx.resolve_vars_if_possible((obligation.predicate, obligation.param_env)); - let mut result = if this.tcx().trait_solver_next() { - this.evaluate_predicates_recursively_in_new_solver([obligation.clone()])? - } else { - this.evaluate_predicate_recursively( - TraitObligationStackList::empty(&ProvisionalEvaluationCache::default()), - obligation.clone(), - )? - }; + let mut result = this.evaluate_predicate_recursively( + TraitObligationStackList::empty(&ProvisionalEvaluationCache::default()), + obligation.clone(), + )?; // If the predicate has done any inference, then downgrade the // result to ambiguous. if this.infcx.shallow_resolve(goal) != goal { @@ -561,9 +605,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { op: impl FnOnce(&mut Self) -> Result, ) -> Result { self.infcx.probe(|snapshot| -> Result { + let outer_universe = self.infcx.universe(); let result = op(self)?; - match self.infcx.leak_check(true, snapshot) { + match self.infcx.leak_check(outer_universe, Some(snapshot)) { Ok(()) => {} Err(_) => return Ok(EvaluatedToErr), } @@ -572,9 +617,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { return Ok(result.max(EvaluatedToOkModuloOpaqueTypes)); } - match self.infcx.region_constraints_added_in_snapshot(snapshot) { - None => Ok(result), - Some(_) => Ok(result.max(EvaluatedToOkModuloRegions)), + if self.infcx.region_constraints_added_in_snapshot(snapshot) { + Ok(result.max(EvaluatedToOkModuloRegions)) + } else { + Ok(result) } }) } @@ -591,42 +637,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { where I: IntoIterator> + std::fmt::Debug, { - if self.tcx().trait_solver_next() { - self.evaluate_predicates_recursively_in_new_solver(predicates) - } else { - let mut result = EvaluatedToOk; - for mut obligation in predicates { - obligation.set_depth_from_parent(stack.depth()); - let eval = self.evaluate_predicate_recursively(stack, obligation.clone())?; - if let EvaluatedToErr = eval { - // fast-path - EvaluatedToErr is the top of the lattice, - // so we don't need to look on the other predicates. - return Ok(EvaluatedToErr); - } else { - result = cmp::max(result, eval); - } + let mut result = EvaluatedToOk; + for mut obligation in predicates { + obligation.set_depth_from_parent(stack.depth()); + let eval = self.evaluate_predicate_recursively(stack, obligation.clone())?; + if let EvaluatedToErr = eval { + // fast-path - EvaluatedToErr is the top of the lattice, + // so we don't need to look on the other predicates. + return Ok(EvaluatedToErr); + } else { + result = cmp::max(result, eval); } - Ok(result) } - } - - /// Evaluates the predicates using the new solver when `-Ztrait-solver=next` is enabled - fn evaluate_predicates_recursively_in_new_solver( - &mut self, - predicates: impl IntoIterator>, - ) -> Result { - let mut fulfill_cx = crate::solve::FulfillmentCtxt::new(); - fulfill_cx.register_predicate_obligations(self.infcx, predicates); - // True errors - // FIXME(-Ztrait-solver=next): Overflows are reported as ambig here, is that OK? - if !fulfill_cx.select_where_possible(self.infcx).is_empty() { - return Ok(EvaluatedToErr); - } - if !fulfill_cx.select_all_or_error(self.infcx).is_empty() { - return Ok(EvaluatedToAmbig); - } - // Regions and opaques are handled in the `evaluation_probe` by looking at the snapshot - Ok(EvaluatedToOk) + Ok(result) } #[instrument( @@ -640,7 +663,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { previous_stack: TraitObligationStackList<'o, 'tcx>, obligation: PredicateObligation<'tcx>, ) -> Result { - // `previous_stack` stores a `TraitObligation`, while `obligation` is + debug_assert!(!self.infcx.next_trait_solver()); + // `previous_stack` stores a `PolyTraitObligation`, while `obligation` is // a `PredicateObligation`. These are distinct types, so we can't // use any `Option` combinator method that would force them to be // the same. @@ -652,7 +676,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ensure_sufficient_stack(|| { let bound_predicate = obligation.predicate.kind(); match bound_predicate.skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(t)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(t)) => { let t = bound_predicate.rebind(t); debug_assert!(!t.has_escaping_bound_vars()); let obligation = obligation.with(self.tcx(), t); @@ -683,7 +707,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - ty::PredicateKind::WellFormed(arg) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { // So, there is a bit going on here. First, `WellFormed` predicates // are coinductive, like trait predicates with auto traits. // This means that we need to detect if we have recursively @@ -735,7 +759,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { stack.update_reached_depth(stack_arg.1); return Ok(EvaluatedToOk); } else { - return Ok(EvaluatedToRecur); + return Ok(self.treat_inductive_cycle.into()); } } return Ok(EvaluatedToOk); @@ -769,7 +793,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(pred)) => { + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(pred)) => { // A global type with no free lifetimes or generic parameters // outlives anything. if pred.0.has_free_regions() @@ -783,7 +807,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) => { // We do not consider region relationships when evaluating trait matches. Ok(EvaluatedToOkModuloRegions) } @@ -796,7 +820,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - ty::PredicateKind::Clause(ty::Clause::Projection(data)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => { let data = bound_predicate.rebind(data); let project_obligation = obligation.with(self.tcx(), data); match project::poly_project_and_unify_type(self, &project_obligation) { @@ -853,13 +877,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } ProjectAndUnifyResult::FailedNormalization => Ok(EvaluatedToAmbig), - ProjectAndUnifyResult::Recursive => Ok(EvaluatedToRecur), + ProjectAndUnifyResult::Recursive => Ok(self.treat_inductive_cycle.into()), ProjectAndUnifyResult::MismatchedProjectionTypes(_) => Ok(EvaluatedToErr), } } - ty::PredicateKind::ClosureKind(_, closure_substs, kind) => { - match self.infcx.closure_kind(closure_substs) { + ty::PredicateKind::ClosureKind(_, closure_args, kind) => { + match self.infcx.closure_kind(closure_args) { Some(closure_kind) => { if closure_kind.extends(kind) { Ok(EvaluatedToOk) @@ -871,7 +895,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } - ty::PredicateKind::ConstEvaluatable(uv) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(uv)) => { match const_evaluatable::is_const_evaluatable( self.infcx, uv, @@ -901,7 +925,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ); use rustc_hir::def::DefKind; - use ty::ConstKind::Unevaluated; + use ty::Unevaluated; match (c1.kind(), c2.kind()) { (Unevaluated(a), Unevaluated(b)) if a.def == b.def && tcx.def_kind(a.def) == DefKind::AssocConst => @@ -910,7 +934,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .infcx .at(&obligation.cause, obligation.param_env) .trace(c1, c2) - .eq(DefineOpaqueTypes::No, a.substs, b.substs) + .eq(DefineOpaqueTypes::No, a.args, b.args) { return self.evaluate_predicates_recursively( previous_stack, @@ -976,14 +1000,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } } - ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("TypeWellFormedFromEnv is only used for chalk") - } ty::PredicateKind::AliasRelate(..) => { bug!("AliasRelate is only used for new solver") } ty::PredicateKind::Ambiguous => Ok(EvaluatedToAmbig), - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { match self.infcx.at(&obligation.cause, obligation.param_env).eq( DefineOpaqueTypes::No, ct.ty(), @@ -1004,7 +1025,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn evaluate_trait_predicate_recursively<'o>( &mut self, previous_stack: TraitObligationStackList<'o, 'tcx>, - mut obligation: TraitObligation<'tcx>, + mut obligation: PolyTraitObligation<'tcx>, ) -> Result { if !self.is_intercrate() && obligation.is_global() @@ -1018,13 +1039,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } let stack = self.push_stack(previous_stack, &obligation); - let mut fresh_trait_pred = stack.fresh_trait_pred; - let mut param_env = obligation.param_env; - - fresh_trait_pred = fresh_trait_pred.map_bound(|mut pred| { - pred.remap_constness(&mut param_env); - pred - }); + let fresh_trait_pred = stack.fresh_trait_pred; + let param_env = obligation.param_env; debug!(?fresh_trait_pred); @@ -1175,7 +1191,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { Some(EvaluatedToOk) } else { debug!("evaluate_stack --> recursive, inductive"); - Some(EvaluatedToRecur) + Some(self.treat_inductive_cycle.into()) } } else { None @@ -1186,6 +1202,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { &mut self, stack: &TraitObligationStack<'o, 'tcx>, ) -> Result { + debug_assert!(!self.infcx.next_trait_solver()); // In intercrate mode, whenever any of the generics are unbound, // there can always be an impl. Even if there are no impls in // this crate, perhaps the type would be unified with @@ -1211,7 +1228,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // terms of `Fn` etc, but we could probably make this more // precise still. let unbound_input_types = - stack.fresh_trait_pred.skip_binder().trait_ref.substs.types().any(|ty| ty.is_fresh()); + stack.fresh_trait_pred.skip_binder().trait_ref.args.types().any(|ty| ty.is_fresh()); if unbound_input_types && stack.iter().skip(1).any(|prev| { @@ -1403,55 +1420,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { (result, dep_node) } - /// filter_impls filters constant trait obligations and candidates that have a positive impl - /// for a negative goal and a negative impl for a positive goal + /// filter_impls filters candidates that have a positive impl for a negative + /// goal and a negative impl for a positive goal #[instrument(level = "debug", skip(self, candidates))] fn filter_impls( &mut self, candidates: Vec>, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, ) -> Vec> { trace!("{candidates:#?}"); let tcx = self.tcx(); let mut result = Vec::with_capacity(candidates.len()); for candidate in candidates { - // Respect const trait obligations - if obligation.is_const() { - match candidate { - // const impl - ImplCandidate(def_id) if tcx.constness(def_id) == hir::Constness::Const => {} - // const param - ParamCandidate(trait_pred) if trait_pred.is_const_if_const() => {} - // const projection - ProjectionCandidate(_, ty::BoundConstness::ConstIfConst) - // auto trait impl - | AutoImplCandidate - // generator / future, this will raise error in other places - // or ignore error with const_async_blocks feature - | GeneratorCandidate - | FutureCandidate - // FnDef where the function is const - | FnPointerCandidate { is_const: true } - | ConstDestructCandidate(_) - | ClosureCandidate { is_const: true } => {} - - FnPointerCandidate { is_const: false } => { - if let ty::FnDef(def_id, _) = obligation.self_ty().skip_binder().kind() && tcx.trait_of_item(*def_id).is_some() { - // Trait methods are not seen as const unless the trait is implemented as const. - // We do not filter that out in here, but nested obligations will be needed to confirm this. - } else { - continue - } - } - - _ => { - // reject all other types of candidates - continue; - } - } - } - if let ImplCandidate(def_id) = candidate { if ty::ImplPolarity::Reservation == tcx.impl_polarity(def_id) || obligation.polarity() == tcx.impl_polarity(def_id) @@ -1472,7 +1453,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn filter_reservation_impls( &mut self, candidate: SelectionCandidate<'tcx>, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, ) -> SelectionResult<'tcx, SelectionCandidate<'tcx>> { let tcx = self.tcx(); // Treat reservation impls as ambiguity. @@ -1504,7 +1485,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn is_knowable<'o>(&mut self, stack: &TraitObligationStack<'o, 'tcx>) -> Result<(), Conflict> { debug!("is_knowable(intercrate={:?})", self.is_intercrate()); - if !self.is_intercrate() || stack.obligation.polarity() == ty::ImplPolarity::Negative { + if !self.is_intercrate() { return Ok(()); } @@ -1516,7 +1497,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // bound regions. let trait_ref = predicate.skip_binder().trait_ref; - coherence::trait_ref_is_knowable(self.tcx(), trait_ref) + coherence::trait_ref_is_knowable::(self.tcx(), trait_ref, |ty| Ok(ty)).unwrap() } /// Returns `true` if the global caches can be used. @@ -1545,7 +1526,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { fn check_candidate_cache( &mut self, - mut param_env: ty::ParamEnv<'tcx>, + param_env: ty::ParamEnv<'tcx>, cache_fresh_trait_pred: ty::PolyTraitPredicate<'tcx>, ) -> Option>> { // Neither the global nor local cache is aware of intercrate @@ -1556,8 +1537,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { return None; } let tcx = self.tcx(); - let mut pred = cache_fresh_trait_pred.skip_binder(); - pred.remap_constness(&mut param_env); + let pred = cache_fresh_trait_pred.skip_binder(); if self.can_use_global_caches(param_env) { if let Some(res) = tcx.selection_cache.get(&(param_env, pred), tcx) { @@ -1603,15 +1583,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(skip(self, param_env, cache_fresh_trait_pred, dep_node), level = "debug")] fn insert_candidate_cache( &mut self, - mut param_env: ty::ParamEnv<'tcx>, + param_env: ty::ParamEnv<'tcx>, cache_fresh_trait_pred: ty::PolyTraitPredicate<'tcx>, dep_node: DepNodeIndex, candidate: SelectionResult<'tcx, SelectionCandidate<'tcx>>, ) { let tcx = self.tcx(); - let mut pred = cache_fresh_trait_pred.skip_binder(); - - pred.remap_constness(&mut param_env); + let pred = cache_fresh_trait_pred.skip_binder(); if !self.can_cache_candidate(&candidate) { debug!(?pred, ?candidate, "insert_candidate_cache - candidate is not cacheable"); @@ -1644,17 +1622,17 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { #[instrument(level = "debug", skip(self), ret)] fn match_projection_obligation_against_definition_bounds( &mut self, - obligation: &TraitObligation<'tcx>, - ) -> smallvec::SmallVec<[(usize, ty::BoundConstness); 2]> { + obligation: &PolyTraitObligation<'tcx>, + ) -> smallvec::SmallVec<[usize; 2]> { let poly_trait_predicate = self.infcx.resolve_vars_if_possible(obligation.predicate); let placeholder_trait_predicate = self.infcx.instantiate_binder_with_placeholders(poly_trait_predicate); debug!(?placeholder_trait_predicate); let tcx = self.infcx.tcx; - let (def_id, substs) = match *placeholder_trait_predicate.trait_ref.self_ty().kind() { - ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - (def_id, substs) + let (def_id, args) = match *placeholder_trait_predicate.trait_ref.self_ty().kind() { + ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) => { + (def_id, args) } _ => { span_bug!( @@ -1665,7 +1643,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ); } }; - let bounds = tcx.item_bounds(def_id).subst(tcx, substs); + let bounds = tcx.item_bounds(def_id).instantiate(tcx, args); // The bounds returned by `item_bounds` may contain duplicates after // normalization, so try to deduplicate when possible to avoid @@ -1677,9 +1655,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { .enumerate() .filter_map(|(idx, bound)| { let bound_predicate = bound.kind(); - if let ty::PredicateKind::Clause(ty::Clause::Trait(pred)) = - bound_predicate.skip_binder() - { + if let ty::ClauseKind::Trait(pred) = bound_predicate.skip_binder() { let bound = bound_predicate.rebind(pred.trait_ref); if self.infcx.probe(|_| { match self.match_normalize_trait_ref( @@ -1696,7 +1672,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { _ => false, } }) { - return Some((idx, pred.constness)); + return Some(idx); } } None @@ -1709,7 +1685,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { /// variables or placeholders, the normalized bound is returned. fn match_normalize_trait_ref( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, trait_bound: ty::PolyTraitRef<'tcx>, placeholder_trait_ref: ty::TraitRef<'tcx>, ) -> Result>, ()> { @@ -1804,11 +1780,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { if is_match { let generics = self.tcx().generics_of(obligation.predicate.def_id); // FIXME(generic-associated-types): Addresses aggressive inference in #92917. - // If this type is a GAT, and of the GAT substs resolve to something new, + // If this type is a GAT, and of the GAT args resolve to something new, // that means that we must have newly inferred something about the GAT. // We should give up in that case. if !generics.params.is_empty() - && obligation.predicate.substs[generics.parent_count..] + && obligation.predicate.args[generics.parent_count..] .iter() .any(|&p| p.has_non_region_infer() && self.infcx.shallow_resolve(p) != p) { @@ -1846,6 +1822,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { /// candidates and prefer where-clause candidates. /// /// See the comment for "SelectionCandidate" for more details. + #[instrument(level = "debug", skip(self))] fn candidate_should_be_dropped_in_favor_of( &mut self, victim: &EvaluatedCandidate<'tcx>, @@ -1869,13 +1846,6 @@ impl<'tcx> SelectionContext<'_, 'tcx> { // This is a fix for #53123 and prevents winnowing from accidentally extending the // lifetime of a variable. match (&other.candidate, &victim.candidate) { - (_, AutoImplCandidate) | (AutoImplCandidate, _) => { - bug!( - "default implementations shouldn't be recorded \ - when there are other valid candidates" - ); - } - // FIXME(@jswrenn): this should probably be more sophisticated (TransmutabilityCandidate, _) | (_, TransmutabilityCandidate) => DropVictim::No, @@ -1890,7 +1860,6 @@ impl<'tcx> SelectionContext<'_, 'tcx> { (ParamCandidate(other), ParamCandidate(victim)) => { let same_except_bound_vars = other.skip_binder().trait_ref == victim.skip_binder().trait_ref - && other.skip_binder().constness == victim.skip_binder().constness && other.skip_binder().polarity == victim.skip_binder().polarity && !other.skip_binder().trait_ref.has_escaping_bound_vars(); if same_except_bound_vars { @@ -1900,12 +1869,6 @@ impl<'tcx> SelectionContext<'_, 'tcx> { // probably best characterized as a "hack", since we might prefer to just do our // best to *not* create essentially duplicate candidates in the first place. DropVictim::drop_if(other.bound_vars().len() <= victim.bound_vars().len()) - } else if other.skip_binder().trait_ref == victim.skip_binder().trait_ref - && victim.skip_binder().constness == ty::BoundConstness::NotConst - && other.skip_binder().polarity == victim.skip_binder().polarity - { - // Drop otherwise equivalent non-const candidates in favor of const candidates. - DropVictim::Yes } else { DropVictim::No } @@ -1917,6 +1880,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { ( ParamCandidate(ref other_cand), ImplCandidate(..) + | AutoImplCandidate | ClosureCandidate { .. } | GeneratorCandidate | FutureCandidate @@ -1944,6 +1908,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { } ( ImplCandidate(_) + | AutoImplCandidate | ClosureCandidate { .. } | GeneratorCandidate | FutureCandidate @@ -1977,6 +1942,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { ( ObjectCandidate(_) | ProjectionCandidate(..), ImplCandidate(..) + | AutoImplCandidate | ClosureCandidate { .. } | GeneratorCandidate | FutureCandidate @@ -1990,6 +1956,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { ( ImplCandidate(..) + | AutoImplCandidate | ClosureCandidate { .. } | GeneratorCandidate | FutureCandidate @@ -2080,6 +2047,19 @@ impl<'tcx> SelectionContext<'_, 'tcx> { } } + (AutoImplCandidate, ImplCandidate(_)) | (ImplCandidate(_), AutoImplCandidate) => { + DropVictim::No + } + + (AutoImplCandidate, _) | (_, AutoImplCandidate) => { + bug!( + "default implementations shouldn't be recorded \ + when there are other global candidates: {:?} {:?}", + other, + victim + ); + } + // Everything else is ambiguous ( ImplCandidate(_) @@ -2110,7 +2090,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { impl<'tcx> SelectionContext<'_, 'tcx> { fn sized_conditions( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, ) -> BuiltinImplConditions<'tcx> { use self::BuiltinImplConditions::{Ambiguous, None, Where}; @@ -2146,16 +2126,14 @@ impl<'tcx> SelectionContext<'_, 'tcx> { obligation.predicate.rebind(tys.last().map_or_else(Vec::new, |&last| vec![last])), ), - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let sized_crit = def.sized_constraint(self.tcx()); // (*) binder moved here - Where(obligation.predicate.rebind({ - sized_crit - .0 - .iter() - .map(|ty| sized_crit.rebind(*ty).subst(self.tcx(), substs)) - .collect() - })) + Where( + obligation + .predicate + .rebind(sized_crit.iter_instantiated(self.tcx(), args).collect()), + ) } ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => None, @@ -2172,7 +2150,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { fn copy_clone_conditions( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, ) -> BuiltinImplConditions<'tcx> { // NOTE: binder moved to (*) let self_ty = self.infcx.shallow_resolve(obligation.predicate.skip_binder().self_ty()); @@ -2180,14 +2158,11 @@ impl<'tcx> SelectionContext<'_, 'tcx> { use self::BuiltinImplConditions::{Ambiguous, None, Where}; match *self_ty.kind() { - ty::Infer(ty::IntVar(_)) - | ty::Infer(ty::FloatVar(_)) - | ty::FnDef(..) - | ty::FnPtr(_) - | ty::Error(_) => Where(ty::Binder::dummy(Vec::new())), + ty::FnDef(..) | ty::FnPtr(_) | ty::Error(_) => Where(ty::Binder::dummy(Vec::new())), ty::Uint(_) | ty::Int(_) + | ty::Infer(ty::IntVar(_) | ty::FloatVar(_)) | ty::Bool | ty::Float(_) | ty::Char @@ -2211,20 +2186,21 @@ impl<'tcx> SelectionContext<'_, 'tcx> { Where(obligation.predicate.rebind(tys.iter().collect())) } - ty::Generator(_, substs, hir::Movability::Movable) => { + ty::Generator(_, args, hir::Movability::Movable) => { if self.tcx().features().generator_clone { let resolved_upvars = - self.infcx.shallow_resolve(substs.as_generator().tupled_upvars_ty()); + self.infcx.shallow_resolve(args.as_generator().tupled_upvars_ty()); let resolved_witness = - self.infcx.shallow_resolve(substs.as_generator().witness()); + self.infcx.shallow_resolve(args.as_generator().witness()); if resolved_upvars.is_ty_var() || resolved_witness.is_ty_var() { // Not yet resolved. Ambiguous } else { - let all = substs + let all = args .as_generator() .upvar_tys() - .chain(iter::once(substs.as_generator().witness())) + .iter() + .chain([args.as_generator().witness()]) .collect::>(); Where(obligation.predicate.rebind(all)) } @@ -2248,24 +2224,24 @@ impl<'tcx> SelectionContext<'_, 'tcx> { Where(ty::Binder::bind_with_vars(witness_tys.to_vec(), all_vars)) } - ty::GeneratorWitnessMIR(def_id, ref substs) => { + ty::GeneratorWitnessMIR(def_id, ref args) => { let hidden_types = bind_generator_hidden_types_above( self.infcx, def_id, - substs, + args, obligation.predicate.bound_vars(), ); Where(hidden_types) } - ty::Closure(_, substs) => { + ty::Closure(_, args) => { // (*) binder moved here - let ty = self.infcx.shallow_resolve(substs.as_closure().tupled_upvars_ty()); + let ty = self.infcx.shallow_resolve(args.as_closure().tupled_upvars_ty()); if let ty::Infer(ty::TyVar(_)) = ty.kind() { // Not yet resolved. Ambiguous } else { - Where(obligation.predicate.rebind(substs.as_closure().upvar_tys().collect())) + Where(obligation.predicate.rebind(args.as_closure().upvar_tys().to_vec())) } } @@ -2305,8 +2281,8 @@ impl<'tcx> SelectionContext<'_, 'tcx> { fn constituent_types_for_ty( &self, t: ty::Binder<'tcx, Ty<'tcx>>, - ) -> ty::Binder<'tcx, Vec>> { - match *t.skip_binder().kind() { + ) -> Result>>, SelectionError<'tcx>> { + Ok(match *t.skip_binder().kind() { ty::Uint(_) | ty::Int(_) | ty::Bool @@ -2319,13 +2295,13 @@ impl<'tcx> SelectionContext<'_, 'tcx> { | ty::Char => ty::Binder::dummy(Vec::new()), // Treat this like `struct str([u8]);` - ty::Str => ty::Binder::dummy(vec![self.tcx().mk_slice(self.tcx().types.u8)]), + ty::Str => ty::Binder::dummy(vec![Ty::new_slice(self.tcx(), self.tcx().types.u8)]), ty::Placeholder(..) | ty::Dynamic(..) | ty::Param(..) | ty::Foreign(..) - | ty::Alias(ty::Projection | ty::Inherent, ..) + | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) | ty::Bound(..) | ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { bug!("asked to assemble constituent types of unexpected type: {:?}", t); @@ -2342,14 +2318,14 @@ impl<'tcx> SelectionContext<'_, 'tcx> { t.rebind(tys.iter().collect()) } - ty::Closure(_, ref substs) => { - let ty = self.infcx.shallow_resolve(substs.as_closure().tupled_upvars_ty()); + ty::Closure(_, ref args) => { + let ty = self.infcx.shallow_resolve(args.as_closure().tupled_upvars_ty()); t.rebind(vec![ty]) } - ty::Generator(_, ref substs, _) => { - let ty = self.infcx.shallow_resolve(substs.as_generator().tupled_upvars_ty()); - let witness = substs.as_generator().witness(); + ty::Generator(_, ref args, _) => { + let ty = self.infcx.shallow_resolve(args.as_generator().tupled_upvars_ty()); + let witness = args.as_generator().witness(); t.rebind([ty].into_iter().chain(iter::once(witness)).collect()) } @@ -2358,24 +2334,28 @@ impl<'tcx> SelectionContext<'_, 'tcx> { types.map_bound(|types| types.to_vec()) } - ty::GeneratorWitnessMIR(def_id, ref substs) => { - bind_generator_hidden_types_above(self.infcx, def_id, substs, t.bound_vars()) + ty::GeneratorWitnessMIR(def_id, ref args) => { + bind_generator_hidden_types_above(self.infcx, def_id, args, t.bound_vars()) } // For `PhantomData`, we pass `T`. - ty::Adt(def, substs) if def.is_phantom_data() => t.rebind(substs.types().collect()), + ty::Adt(def, args) if def.is_phantom_data() => t.rebind(args.types().collect()), - ty::Adt(def, substs) => { - t.rebind(def.all_fields().map(|f| f.ty(self.tcx(), substs)).collect()) + ty::Adt(def, args) => { + t.rebind(def.all_fields().map(|f| f.ty(self.tcx(), args)).collect()) } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { + let ty = self.tcx().type_of(def_id); + if ty.skip_binder().references_error() { + return Err(SelectionError::OpaqueTypeAutoTraitLeakageUnknown(def_id)); + } // We can resolve the `impl Trait` to its concrete type, // which enforces a DAG between the functions requiring // the auto trait bounds in question. - t.rebind(vec![self.tcx().type_of(def_id).subst(self.tcx(), substs)]) + t.rebind(vec![ty.instantiate(self.tcx(), args)]) } - } + }) } fn collect_predicates_for_types( @@ -2444,14 +2424,14 @@ impl<'tcx> SelectionContext<'_, 'tcx> { fn rematch_impl( &mut self, impl_def_id: DefId, - obligation: &TraitObligation<'tcx>, - ) -> Normalized<'tcx, SubstsRef<'tcx>> { + obligation: &PolyTraitObligation<'tcx>, + ) -> Normalized<'tcx, GenericArgsRef<'tcx>> { let impl_trait_ref = self.tcx().impl_trait_ref(impl_def_id).unwrap(); match self.match_impl(impl_def_id, impl_trait_ref, obligation) { - Ok(substs) => substs, + Ok(args) => args, Err(()) => { // FIXME: A rematch may fail when a candidate cache hit occurs - // on thefreshened form of the trait predicate, but the match + // on the freshened form of the trait predicate, but the match // fails for some reason that is not captured in the freshened // cache key. For example, equating an impl trait ref against // the placeholder trait ref may fail due the Generalizer relation @@ -2460,12 +2440,11 @@ impl<'tcx> SelectionContext<'_, 'tcx> { let guar = self.infcx.tcx.sess.delay_span_bug( obligation.cause.span, format!( - "Impl {:?} was matchable against {:?} but now is not", - impl_def_id, obligation + "Impl {impl_def_id:?} was matchable against {obligation:?} but now is not" ), ); - let value = self.infcx.fresh_substs_for_item(obligation.cause.span, impl_def_id); - let err = self.tcx().ty_error(guar); + let value = self.infcx.fresh_args_for_item(obligation.cause.span, impl_def_id); + let err = Ty::new_error(self.tcx(), guar); let value = value.fold_with(&mut BottomUpFolder { tcx: self.tcx(), ty_op: |_| err, @@ -2482,15 +2461,15 @@ impl<'tcx> SelectionContext<'_, 'tcx> { &mut self, impl_def_id: DefId, impl_trait_ref: EarlyBinder>, - obligation: &TraitObligation<'tcx>, - ) -> Result>, ()> { + obligation: &PolyTraitObligation<'tcx>, + ) -> Result>, ()> { let placeholder_obligation = self.infcx.instantiate_binder_with_placeholders(obligation.predicate); let placeholder_obligation_trait_ref = placeholder_obligation.trait_ref; - let impl_substs = self.infcx.fresh_substs_for_item(obligation.cause.span, impl_def_id); + let impl_args = self.infcx.fresh_args_for_item(obligation.cause.span, impl_def_id); - let impl_trait_ref = impl_trait_ref.subst(self.tcx(), impl_substs); + let impl_trait_ref = impl_trait_ref.instantiate(self.tcx(), impl_args); if impl_trait_ref.references_error() { return Err(()); } @@ -2532,7 +2511,99 @@ impl<'tcx> SelectionContext<'_, 'tcx> { return Err(()); } - Ok(Normalized { value: impl_substs, obligations: nested_obligations }) + Ok(Normalized { value: impl_args, obligations: nested_obligations }) + } + + fn match_upcast_principal( + &mut self, + obligation: &PolyTraitObligation<'tcx>, + unnormalized_upcast_principal: ty::PolyTraitRef<'tcx>, + a_data: &'tcx ty::List>, + b_data: &'tcx ty::List>, + a_region: ty::Region<'tcx>, + b_region: ty::Region<'tcx>, + ) -> SelectionResult<'tcx, Vec>> { + let tcx = self.tcx(); + let mut nested = vec![]; + + let upcast_principal = normalize_with_depth_to( + self, + obligation.param_env, + obligation.cause.clone(), + obligation.recursion_depth + 1, + unnormalized_upcast_principal, + &mut nested, + ); + + for bound in b_data { + match bound.skip_binder() { + // Check that a_ty's supertrait (upcast_principal) is compatible + // with the target (b_ty). + ty::ExistentialPredicate::Trait(target_principal) => { + nested.extend( + self.infcx + .at(&obligation.cause, obligation.param_env) + .sup( + DefineOpaqueTypes::No, + upcast_principal.map_bound(|trait_ref| { + ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) + }), + bound.rebind(target_principal), + ) + .map_err(|_| SelectionError::Unimplemented)? + .into_obligations(), + ); + } + // Check that b_ty's projection is satisfied by exactly one of + // a_ty's projections. First, we look through the list to see if + // any match. If not, error. Then, if *more* than one matches, we + // return ambiguity. Otherwise, if exactly one matches, equate + // it with b_ty's projection. + ty::ExistentialPredicate::Projection(target_projection) => { + let target_projection = bound.rebind(target_projection); + let mut matching_projections = + a_data.projection_bounds().filter(|source_projection| { + // Eager normalization means that we can just use can_eq + // here instead of equating and processing obligations. + source_projection.item_def_id() == target_projection.item_def_id() + && self.infcx.can_eq( + obligation.param_env, + *source_projection, + target_projection, + ) + }); + let Some(source_projection) = matching_projections.next() else { + return Err(SelectionError::Unimplemented); + }; + if matching_projections.next().is_some() { + return Ok(None); + } + nested.extend( + self.infcx + .at(&obligation.cause, obligation.param_env) + .sup(DefineOpaqueTypes::No, source_projection, target_projection) + .map_err(|_| SelectionError::Unimplemented)? + .into_obligations(), + ); + } + // Check that b_ty's auto traits are present in a_ty's bounds. + ty::ExistentialPredicate::AutoTrait(def_id) => { + if !a_data.auto_traits().any(|source_def_id| source_def_id == def_id) { + return Err(SelectionError::Unimplemented); + } + } + } + } + + nested.push(Obligation::with_depth( + tcx, + obligation.cause.clone(), + obligation.recursion_depth + 1, + obligation.param_env, + ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)), + )); + + Ok(Some(nested)) } /// Normalize `where_clause_trait_ref` and try to match it against @@ -2540,7 +2611,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { /// result from the normalization. fn match_where_clause_trait_ref( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, where_clause_trait_ref: ty::PolyTraitRef<'tcx>, ) -> Result>, ()> { self.match_poly_trait_ref(obligation, where_clause_trait_ref) @@ -2551,7 +2622,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { #[instrument(skip(self), level = "debug")] fn match_poly_trait_ref( &mut self, - obligation: &TraitObligation<'tcx>, + obligation: &PolyTraitObligation<'tcx>, poly_trait_ref: ty::PolyTraitRef<'tcx>, ) -> Result>, ()> { self.infcx @@ -2577,7 +2648,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { fn push_stack<'o>( &mut self, previous_stack: TraitObligationStackList<'o, 'tcx>, - obligation: &'o TraitObligation<'tcx>, + obligation: &'o PolyTraitObligation<'tcx>, ) -> TraitObligationStack<'o, 'tcx> { let fresh_trait_pred = obligation.predicate.fold_with(&mut self.freshener); @@ -2596,10 +2667,10 @@ impl<'tcx> SelectionContext<'_, 'tcx> { #[instrument(skip(self), level = "debug")] fn closure_trait_ref_unnormalized( &mut self, - obligation: &TraitObligation<'tcx>, - substs: SubstsRef<'tcx>, + obligation: &PolyTraitObligation<'tcx>, + args: GenericArgsRef<'tcx>, ) -> ty::PolyTraitRef<'tcx> { - let closure_sig = substs.as_closure().sig(); + let closure_sig = args.as_closure().sig(); debug!(?closure_sig); @@ -2632,8 +2703,8 @@ impl<'tcx> SelectionContext<'_, 'tcx> { cause: &ObligationCause<'tcx>, recursion_depth: usize, param_env: ty::ParamEnv<'tcx>, - def_id: DefId, // of impl or trait - substs: SubstsRef<'tcx>, // for impl or trait + def_id: DefId, // of impl or trait + args: GenericArgsRef<'tcx>, // for impl or trait parent_trait_pred: ty::Binder<'tcx, ty::TraitPredicate<'tcx>>, ) -> Vec> { let tcx = self.tcx(); @@ -2654,7 +2725,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { // that order. let predicates = tcx.predicates_of(def_id); assert_eq!(predicates.parent, None); - let predicates = predicates.instantiate_own(tcx, substs); + let predicates = predicates.instantiate_own(tcx, args); let mut obligations = Vec::with_capacity(predicates.len()); for (index, (predicate, span)) in predicates.into_iter().enumerate() { let cause = @@ -2670,7 +2741,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { })) }) }; - let predicate = normalize_with_depth_to( + let clause = normalize_with_depth_to( self, param_env, cause.clone(), @@ -2678,7 +2749,12 @@ impl<'tcx> SelectionContext<'_, 'tcx> { predicate, &mut obligations, ); - obligations.push(Obligation { cause, recursion_depth, param_env, predicate }); + obligations.push(Obligation { + cause, + recursion_depth, + param_env, + predicate: clause.as_predicate(), + }); } obligations @@ -3002,7 +3078,7 @@ pub enum ProjectionMatchesProjection { fn bind_generator_hidden_types_above<'tcx>( infcx: &InferCtxt<'tcx>, def_id: DefId, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, bound_vars: &ty::List, ) -> ty::Binder<'tcx, Vec>> { let tcx = infcx.tcx; @@ -3018,7 +3094,7 @@ fn bind_generator_hidden_types_above<'tcx>( // Deduplicate tys to avoid repeated work. .filter(|bty| seen_tys.insert(*bty)) .map(|bty| { - let mut ty = bty.subst(tcx, substs); + let mut ty = bty.instantiate(tcx, args); // Only remap erased regions if we use them. if considering_regions { @@ -3029,7 +3105,7 @@ fn bind_generator_hidden_types_above<'tcx>( kind: ty::BrAnon(None), }; counter += 1; - tcx.mk_re_late_bound(current_depth, br) + ty::Region::new_late_bound(tcx, current_depth, br) } r => bug!("unexpected region: {r:?}"), }) diff --git a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs index 9a4b72013b88d..729cf2f33136b 100644 --- a/compiler/rustc_trait_selection/src/traits/specialize/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/specialize/mod.rs @@ -23,7 +23,7 @@ use rustc_data_structures::fx::FxIndexSet; use rustc_errors::{error_code, DelayDm, Diagnostic}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::ty::{self, ImplSubject, Ty, TyCtxt, TypeVisitableExt}; -use rustc_middle::ty::{InternalSubsts, SubstsRef}; +use rustc_middle::ty::{GenericArgs, GenericArgsRef}; use rustc_session::lint::builtin::COHERENCE_LEAK_CHECK; use rustc_session::lint::builtin::ORDER_DEPENDENT_TRAIT_OBJECTS; use rustc_span::{Span, DUMMY_SP}; @@ -48,7 +48,7 @@ pub struct OverlapError<'tcx> { /// When we have selected one impl, but are actually using item definitions from /// a parent impl providing a default, we need a way to translate between the /// type parameters of the two impls. Here the `source_impl` is the one we've -/// selected, and `source_substs` is a substitution of its generics. +/// selected, and `source_args` is a substitution of its generics. /// And `target_node` is the impl/trait we're actually going to get the /// definition from. The resulting substitution will map from `target_node`'s /// generics to `source_impl`'s generics as instantiated by `source_subst`. @@ -76,51 +76,46 @@ pub struct OverlapError<'tcx> { /// through associated type projection. We deal with such cases by using /// *fulfillment* to relate the two impls, requiring that all projections are /// resolved. -pub fn translate_substs<'tcx>( +pub fn translate_args<'tcx>( infcx: &InferCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, source_impl: DefId, - source_substs: SubstsRef<'tcx>, + source_args: GenericArgsRef<'tcx>, target_node: specialization_graph::Node, -) -> SubstsRef<'tcx> { - translate_substs_with_cause( - infcx, - param_env, - source_impl, - source_substs, - target_node, - |_, _| ObligationCause::dummy(), - ) +) -> GenericArgsRef<'tcx> { + translate_args_with_cause(infcx, param_env, source_impl, source_args, target_node, |_, _| { + ObligationCause::dummy() + }) } -/// Like [translate_substs], but obligations from the parent implementation +/// Like [translate_args], but obligations from the parent implementation /// are registered with the provided `ObligationCause`. /// /// This is for reporting *region* errors from those bounds. Type errors should /// not happen because the specialization graph already checks for those, and /// will result in an ICE. -pub fn translate_substs_with_cause<'tcx>( +pub fn translate_args_with_cause<'tcx>( infcx: &InferCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, source_impl: DefId, - source_substs: SubstsRef<'tcx>, + source_args: GenericArgsRef<'tcx>, target_node: specialization_graph::Node, cause: impl Fn(usize, Span) -> ObligationCause<'tcx>, -) -> SubstsRef<'tcx> { +) -> GenericArgsRef<'tcx> { debug!( - "translate_substs({:?}, {:?}, {:?}, {:?})", - param_env, source_impl, source_substs, target_node + "translate_args({:?}, {:?}, {:?}, {:?})", + param_env, source_impl, source_args, target_node ); let source_trait_ref = - infcx.tcx.impl_trait_ref(source_impl).unwrap().subst(infcx.tcx, &source_substs); + infcx.tcx.impl_trait_ref(source_impl).unwrap().instantiate(infcx.tcx, &source_args); // translate the Self and Param parts of the substitution, since those // vary across impls - let target_substs = match target_node { + let target_args = match target_node { specialization_graph::Node::Impl(target_impl) => { // no need to translate if we're targeting the impl we started with if source_impl == target_impl { - return source_substs; + return source_args; } fulfill_implication(infcx, param_env, source_trait_ref, source_impl, target_impl, cause) @@ -131,11 +126,11 @@ pub fn translate_substs_with_cause<'tcx>( ) }) } - specialization_graph::Node::Trait(..) => source_trait_ref.substs, + specialization_graph::Node::Trait(..) => source_trait_ref.args, }; // directly inherent the method generics, since those do not vary across impls - source_substs.rebase_onto(infcx.tcx, source_impl, target_substs) + source_args.rebase_onto(infcx.tcx, source_impl, target_args) } /// Is `impl1` a specialization of `impl2`? @@ -172,7 +167,7 @@ pub(super) fn specializes(tcx: TyCtxt<'_>, (impl1_def_id, impl2_def_id): (DefId, // create a parameter environment corresponding to a (placeholder) instantiation of impl1 let penv = tcx.param_env(impl1_def_id); - let impl1_trait_ref = tcx.impl_trait_ref(impl1_def_id).unwrap().subst_identity(); + let impl1_trait_ref = tcx.impl_trait_ref(impl1_def_id).unwrap().instantiate_identity(); // Create an infcx, taking the predicates of impl1 as assumptions: let infcx = tcx.infer_ctxt().build(); @@ -196,7 +191,7 @@ fn fulfill_implication<'tcx>( source_impl: DefId, target_impl: DefId, error_cause: impl Fn(usize, Span) -> ObligationCause<'tcx>, -) -> Result, ()> { +) -> Result, ()> { debug!( "fulfill_implication({:?}, trait_ref={:?} |- {:?} applies)", param_env, source_trait_ref, target_impl @@ -221,24 +216,22 @@ fn fulfill_implication<'tcx>( let source_trait = ImplSubject::Trait(source_trait_ref); let selcx = &mut SelectionContext::new(&infcx); - let target_substs = infcx.fresh_substs_for_item(DUMMY_SP, target_impl); + let target_args = infcx.fresh_args_for_item(DUMMY_SP, target_impl); let (target_trait, obligations) = - util::impl_subject_and_oblig(selcx, param_env, target_impl, target_substs, error_cause); + util::impl_subject_and_oblig(selcx, param_env, target_impl, target_args, error_cause); // do the impls unify? If not, no specialization. - let Ok(InferOk { obligations: more_obligations, .. }) = - infcx.at(&ObligationCause::dummy(), param_env).eq(DefineOpaqueTypes::No, source_trait, target_trait) + let Ok(InferOk { obligations: more_obligations, .. }) = infcx + .at(&ObligationCause::dummy(), param_env) + .eq(DefineOpaqueTypes::No, source_trait, target_trait) else { - debug!( - "fulfill_implication: {:?} does not unify with {:?}", - source_trait, target_trait - ); + debug!("fulfill_implication: {:?} does not unify with {:?}", source_trait, target_trait); return Err(()); }; // Needs to be `in_snapshot` because this function is used to rebase // substitutions, which may happen inside of a select within a probe. - let ocx = ObligationCtxt::new_in_snapshot(infcx); + let ocx = ObligationCtxt::new(infcx); // attempt to prove all of the predicates for impl2 given those for impl1 // (which are packed up in penv) ocx.register_obligations(obligations.chain(more_obligations)); @@ -261,7 +254,7 @@ fn fulfill_implication<'tcx>( // Now resolve the *substitution* we built for the target earlier, replacing // the inference variables inside with whatever we got from fulfillment. - Ok(infcx.resolve_vars_if_possible(target_substs)) + Ok(infcx.resolve_vars_if_possible(target_args)) } /// Query provider for `specialization_graph_of`. @@ -395,16 +388,16 @@ fn report_conflicting_impls<'tcx>( impl_span, format!( "conflicting implementation{}", - overlap.self_ty.map_or_else(String::new, |ty| format!(" for `{}`", ty)) + overlap.self_ty.map_or_else(String::new, |ty| format!(" for `{ty}`")) ), ); } Err(cname) => { let msg = match to_pretty_impl_header(tcx, overlap.with_impl) { Some(s) => { - format!("conflicting implementation in crate `{}`:\n- {}", cname, s) + format!("conflicting implementation in crate `{cname}`:\n- {s}") } - None => format!("conflicting implementation in crate `{}`", cname), + None => format!("conflicting implementation in crate `{cname}`"), }; err.note(msg); } @@ -469,21 +462,21 @@ fn report_conflicting_impls<'tcx>( pub(crate) fn to_pretty_impl_header(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Option { use std::fmt::Write; - let trait_ref = tcx.impl_trait_ref(impl_def_id)?.subst_identity(); + let trait_ref = tcx.impl_trait_ref(impl_def_id)?.instantiate_identity(); let mut w = "impl".to_owned(); - let substs = InternalSubsts::identity_for_item(tcx, impl_def_id); + let args = GenericArgs::identity_for_item(tcx, impl_def_id); // FIXME: Currently only handles ?Sized. // Needs to support ?Move and ?DynSized when they are implemented. let mut types_without_default_bounds = FxIndexSet::default(); let sized_trait = tcx.lang_items().sized_trait(); - if !substs.is_empty() { - types_without_default_bounds.extend(substs.types()); + if !args.is_empty() { + types_without_default_bounds.extend(args.types()); w.push('<'); w.push_str( - &substs + &args .iter() .map(|k| k.to_string()) .filter(|k| k != "'_") @@ -497,7 +490,7 @@ pub(crate) fn to_pretty_impl_header(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Opti w, " {} for {}", trait_ref.print_only_trait_path(), - tcx.type_of(impl_def_id).subst_identity() + tcx.type_of(impl_def_id).instantiate_identity() ) .unwrap(); @@ -507,29 +500,17 @@ pub(crate) fn to_pretty_impl_header(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Opti let mut pretty_predicates = Vec::with_capacity(predicates.len() + types_without_default_bounds.len()); - for (mut p, _) in predicates { - if let Some(poly_trait_ref) = p.to_opt_poly_trait_pred() { + for (p, _) in predicates { + if let Some(poly_trait_ref) = p.as_trait_clause() { if Some(poly_trait_ref.def_id()) == sized_trait { types_without_default_bounds.remove(&poly_trait_ref.self_ty().skip_binder()); continue; } - - if ty::BoundConstness::ConstIfConst == poly_trait_ref.skip_binder().constness { - let new_trait_pred = poly_trait_ref.map_bound(|mut trait_pred| { - trait_pred.constness = ty::BoundConstness::NotConst; - trait_pred - }); - - p = tcx.mk_predicate( - new_trait_pred.map_bound(|p| ty::PredicateKind::Clause(ty::Clause::Trait(p))), - ) - } } pretty_predicates.push(p.to_string()); } - pretty_predicates - .extend(types_without_default_bounds.iter().map(|ty| format!("{}: ?Sized", ty))); + pretty_predicates.extend(types_without_default_bounds.iter().map(|ty| format!("{ty}: ?Sized"))); if !pretty_predicates.is_empty() { write!(w, "\n where {}", pretty_predicates.join(", ")).unwrap(); diff --git a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs index aa5c624f471ff..e9a592bdee799 100644 --- a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs +++ b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs @@ -180,7 +180,7 @@ impl<'tcx> ChildrenExt<'tcx> for Children { if le && !ge { debug!( "descending as child of TraitRef {:?}", - tcx.impl_trait_ref(possible_sibling).unwrap().subst_identity() + tcx.impl_trait_ref(possible_sibling).unwrap().instantiate_identity() ); // The impl specializes `possible_sibling`. @@ -188,7 +188,7 @@ impl<'tcx> ChildrenExt<'tcx> for Children { } else if ge && !le { debug!( "placing as parent of TraitRef {:?}", - tcx.impl_trait_ref(possible_sibling).unwrap().subst_identity() + tcx.impl_trait_ref(possible_sibling).unwrap().instantiate_identity() ); replace_children.push(possible_sibling); diff --git a/compiler/rustc_trait_selection/src/traits/structural_match.rs b/compiler/rustc_trait_selection/src/traits/structural_match.rs index e38ae9381c1d7..0864e4dc84199 100644 --- a/compiler/rustc_trait_selection/src/traits/structural_match.rs +++ b/compiler/rustc_trait_selection/src/traits/structural_match.rs @@ -34,24 +34,7 @@ pub fn search_for_structural_match_violation<'tcx>( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, ) -> Option> { - ty.visit_with(&mut Search { tcx, span, seen: FxHashSet::default(), adt_const_param: false }) - .break_value() -} - -/// This method traverses the structure of `ty`, trying to find any -/// types that are not allowed to be used in a const generic. -/// -/// This is either because the type does not implement `StructuralEq` -/// and `StructuralPartialEq`, or because the type is intentionally -/// not supported in const generics (such as floats and raw pointers, -/// which are allowed in match blocks). -pub fn search_for_adt_const_param_violation<'tcx>( - span: Span, - tcx: TyCtxt<'tcx>, - ty: Ty<'tcx>, -) -> Option> { - ty.visit_with(&mut Search { tcx, span, seen: FxHashSet::default(), adt_const_param: true }) - .break_value() + ty.visit_with(&mut Search { tcx, span, seen: FxHashSet::default() }).break_value() } /// This implements the traversal over the structure of a given type to try to @@ -65,11 +48,6 @@ struct Search<'tcx> { /// Tracks ADTs previously encountered during search, so that /// we will not recur on them again. seen: FxHashSet, - - // Additionally deny things that have been allowed in patterns, - // but are not allowed in adt const params, such as floats and - // fn ptrs. - adt_const_param: bool, } impl<'tcx> Search<'tcx> { @@ -84,8 +62,8 @@ impl<'tcx> TypeVisitor> for Search<'tcx> { fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { debug!("Search visiting ty: {:?}", ty); - let (adt_def, substs) = match *ty.kind() { - ty::Adt(adt_def, substs) => (adt_def, substs), + let (adt_def, args) = match *ty.kind() { + ty::Adt(adt_def, args) => (adt_def, args), ty::Param(_) => { return ControlFlow::Break(ty); } @@ -124,41 +102,29 @@ impl<'tcx> TypeVisitor> for Search<'tcx> { } ty::FnPtr(..) => { - if !self.adt_const_param { - return ControlFlow::Continue(()); - } else { - return ControlFlow::Break(ty); - } + return ControlFlow::Continue(()); } ty::RawPtr(..) => { - if !self.adt_const_param { - // structural-match ignores substructure of - // `*const _`/`*mut _`, so skip `super_visit_with`. - // - // For example, if you have: - // ``` - // struct NonStructural; - // #[derive(PartialEq, Eq)] - // struct T(*const NonStructural); - // const C: T = T(std::ptr::null()); - // ``` - // - // Even though `NonStructural` does not implement `PartialEq`, - // structural equality on `T` does not recur into the raw - // pointer. Therefore, one can still use `C` in a pattern. - return ControlFlow::Continue(()); - } else { - return ControlFlow::Break(ty); - } + // structural-match ignores substructure of + // `*const _`/`*mut _`, so skip `super_visit_with`. + // + // For example, if you have: + // ``` + // struct NonStructural; + // #[derive(PartialEq, Eq)] + // struct T(*const NonStructural); + // const C: T = T(std::ptr::null()); + // ``` + // + // Even though `NonStructural` does not implement `PartialEq`, + // structural equality on `T` does not recur into the raw + // pointer. Therefore, one can still use `C` in a pattern. + return ControlFlow::Continue(()); } ty::Float(_) => { - if !self.adt_const_param { - return ControlFlow::Continue(()); - } else { - return ControlFlow::Break(ty); - } + return ControlFlow::Continue(()); } ty::Array(..) | ty::Slice(_) | ty::Ref(..) | ty::Tuple(..) => { @@ -191,15 +157,15 @@ impl<'tcx> TypeVisitor> for Search<'tcx> { // instead looks directly at its fields outside // this match), so we skip super_visit_with. // - // (Must not recur on substs for `PhantomData` cf + // (Must not recur on args for `PhantomData` cf // rust-lang/rust#55028 and rust-lang/rust#55837; but also - // want to skip substs when only uses of generic are + // want to skip args when only uses of generic are // behind unsafe pointers `*const T`/`*mut T`.) // even though we skip super_visit_with, we must recur on // fields of ADT. let tcx = self.tcx; - adt_def.all_fields().map(|field| field.ty(tcx, substs)).try_for_each(|field_ty| { + adt_def.all_fields().map(|field| field.ty(tcx, args)).try_for_each(|field_ty| { let ty = self.tcx.normalize_erasing_regions(ty::ParamEnv::empty(), field_ty); debug!("structural-match ADT: field_ty={:?}, ty={:?}", field_ty, ty); ty.visit_with(self) diff --git a/compiler/rustc_trait_selection/src/traits/structural_normalize.rs b/compiler/rustc_trait_selection/src/traits/structural_normalize.rs index af8dd0da5792a..d3c4dc45923d9 100644 --- a/compiler/rustc_trait_selection/src/traits/structural_normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/structural_normalize.rs @@ -21,8 +21,10 @@ impl<'tcx> StructurallyNormalizeExt<'tcx> for At<'_, 'tcx> { ) -> Result, Vec>> { assert!(!ty.is_ty_var(), "should have resolved vars before calling"); - if self.infcx.tcx.trait_solver_next() { - while let ty::Alias(ty::Projection, projection_ty) = *ty.kind() { + if self.infcx.next_trait_solver() { + while let ty::Alias(ty::Projection | ty::Inherent | ty::Weak, projection_ty) = + *ty.kind() + { let new_infer_ty = self.infcx.next_ty_var(TypeVariableOrigin { kind: TypeVariableOriginKind::NormalizeProjectionType, span: self.cause.span, diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs index 82f3df401988d..a76272e9d092d 100644 --- a/compiler/rustc_trait_selection/src/traits/util.rs +++ b/compiler/rustc_trait_selection/src/traits/util.rs @@ -4,7 +4,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::Diagnostic; use rustc_hir::def_id::DefId; use rustc_infer::infer::InferOk; -use rustc_middle::ty::SubstsRef; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, ImplSubject, ToPredicate, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use smallvec::SmallVec; @@ -41,11 +41,16 @@ impl<'tcx> TraitAliasExpansionInfo<'tcx> { /// Adds diagnostic labels to `diag` for the expansion path of a trait through all intermediate /// trait aliases. - pub fn label_with_exp_info(&self, diag: &mut Diagnostic, top_label: &str, use_desc: &str) { + pub fn label_with_exp_info( + &self, + diag: &mut Diagnostic, + top_label: &'static str, + use_desc: &str, + ) { diag.span_label(self.top().1, top_label); if self.path.len() > 1 { for (_, sp) in self.path.iter().rev().skip(1).take(self.path.len() - 2) { - diag.span_label(*sp, format!("referenced here ({})", use_desc)); + diag.span_label(*sp, format!("referenced here ({use_desc})")); } } if self.top().1 != self.bottom().1 { @@ -53,7 +58,7 @@ impl<'tcx> TraitAliasExpansionInfo<'tcx> { // redundant labels. diag.span_label( self.bottom().1, - format!("trait alias used in trait object type ({})", use_desc), + format!("trait alias used in trait object type ({use_desc})"), ); } } @@ -96,7 +101,7 @@ impl<'tcx> TraitAliasExpander<'tcx> { fn expand(&mut self, item: &TraitAliasExpansionInfo<'tcx>) -> bool { let tcx = self.tcx; let trait_ref = item.trait_ref(); - let pred = trait_ref.without_const().to_predicate(tcx); + let pred = trait_ref.to_predicate(tcx); debug!("expand_trait_aliases: trait_ref={:?}", trait_ref); @@ -108,9 +113,13 @@ impl<'tcx> TraitAliasExpander<'tcx> { // Don't recurse if this trait alias is already on the stack for the DFS search. let anon_pred = anonymize_predicate(tcx, pred); - if item.path.iter().rev().skip(1).any(|&(tr, _)| { - anonymize_predicate(tcx, tr.without_const().to_predicate(tcx)) == anon_pred - }) { + if item + .path + .iter() + .rev() + .skip(1) + .any(|&(tr, _)| anonymize_predicate(tcx, tr.to_predicate(tcx)) == anon_pred) + { return false; } @@ -120,7 +129,7 @@ impl<'tcx> TraitAliasExpander<'tcx> { let items = predicates.predicates.iter().rev().filter_map(|(pred, span)| { pred.subst_supertrait(tcx, &trait_ref) - .to_opt_poly_trait_pred() + .as_trait_clause() .map(|trait_ref| item.clone_and_push(trait_ref.map_bound(|t| t.trait_ref), *span)) }); debug!("expand_trait_aliases: items={:?}", items.clone().collect::>()); @@ -177,7 +186,7 @@ impl Iterator for SupertraitDefIds<'_> { predicates .predicates .iter() - .filter_map(|(pred, _)| pred.to_opt_poly_trait_pred()) + .filter_map(|(pred, _)| pred.as_trait_clause()) .map(|trait_ref| trait_ref.def_id()) .filter(|&super_def_id| visited.insert(super_def_id)), ); @@ -189,24 +198,24 @@ impl Iterator for SupertraitDefIds<'_> { // Other /////////////////////////////////////////////////////////////////////////// -/// Instantiate all bound parameters of the impl subject with the given substs, +/// Instantiate all bound parameters of the impl subject with the given args, /// returning the resulting subject and all obligations that arise. /// The obligations are closed under normalization. pub fn impl_subject_and_oblig<'a, 'tcx>( selcx: &mut SelectionContext<'a, 'tcx>, param_env: ty::ParamEnv<'tcx>, impl_def_id: DefId, - impl_substs: SubstsRef<'tcx>, + impl_args: GenericArgsRef<'tcx>, cause: impl Fn(usize, Span) -> ObligationCause<'tcx>, ) -> (ImplSubject<'tcx>, impl Iterator>) { let subject = selcx.tcx().impl_subject(impl_def_id); - let subject = subject.subst(selcx.tcx(), impl_substs); + let subject = subject.instantiate(selcx.tcx(), impl_args); let InferOk { value: subject, obligations: normalization_obligations1 } = selcx.infcx.at(&ObligationCause::dummy(), param_env).normalize(subject); let predicates = selcx.tcx().predicates_of(impl_def_id); - let predicates = predicates.instantiate(selcx.tcx(), impl_substs); + let predicates = predicates.instantiate(selcx.tcx(), impl_args); let InferOk { value: predicates, obligations: normalization_obligations2 } = selcx.infcx.at(&ObligationCause::dummy(), param_env).normalize(predicates); let impl_obligations = super::predicates_for_generics(cause, param_env, predicates); @@ -236,18 +245,18 @@ pub fn upcast_choices<'tcx>( /// Given an upcast trait object described by `object`, returns the /// index of the method `method_def_id` (which should be part of /// `object.upcast_trait_ref`) within the vtable for `object`. -pub fn get_vtable_index_of_object_method<'tcx, N>( +pub fn get_vtable_index_of_object_method<'tcx>( tcx: TyCtxt<'tcx>, - object: &super::ImplSourceObjectData<'tcx, N>, + vtable_base: usize, method_def_id: DefId, ) -> Option { // Count number of methods preceding the one we are selecting and // add them to the total offset. - tcx.own_existential_vtable_entries(object.upcast_trait_ref.def_id()) + tcx.own_existential_vtable_entries(tcx.parent(method_def_id)) .iter() .copied() .position(|def_id| def_id == method_def_id) - .map(|index| object.vtable_base + index) + .map(|index| vtable_base + index) } pub fn closure_trait_ref_and_return_type<'tcx>( @@ -260,7 +269,7 @@ pub fn closure_trait_ref_and_return_type<'tcx>( assert!(!self_ty.has_escaping_bound_vars()); let arguments_tuple = match tuple_arguments { TupleArgumentsFlag::No => sig.skip_binder().inputs()[0], - TupleArgumentsFlag::Yes => tcx.mk_tup(sig.skip_binder().inputs()), + TupleArgumentsFlag::Yes => Ty::new_tup(tcx, sig.skip_binder().inputs()), }; let trait_ref = ty::TraitRef::new(tcx, fn_trait_def_id, [self_ty, arguments_tuple]); sig.map_bound(|sig| (trait_ref, sig.output())) @@ -290,10 +299,51 @@ pub fn future_trait_ref_and_outputs<'tcx>( pub fn impl_item_is_final(tcx: TyCtxt<'_>, assoc_item: &ty::AssocItem) -> bool { assoc_item.defaultness(tcx).is_final() - && tcx.impl_defaultness(assoc_item.container_id(tcx)).is_final() + && tcx.defaultness(assoc_item.container_id(tcx)).is_final() } pub enum TupleArgumentsFlag { Yes, No, } + +// Verify that the trait item and its implementation have compatible args lists +pub fn check_args_compatible<'tcx>( + tcx: TyCtxt<'tcx>, + assoc_item: ty::AssocItem, + args: ty::GenericArgsRef<'tcx>, +) -> bool { + fn check_args_compatible_inner<'tcx>( + tcx: TyCtxt<'tcx>, + generics: &'tcx ty::Generics, + args: &'tcx [ty::GenericArg<'tcx>], + ) -> bool { + if generics.count() != args.len() { + return false; + } + + let (parent_args, own_args) = args.split_at(generics.parent_count); + + if let Some(parent) = generics.parent + && let parent_generics = tcx.generics_of(parent) + && !check_args_compatible_inner(tcx, parent_generics, parent_args) { + return false; + } + + for (param, arg) in std::iter::zip(&generics.params, own_args) { + match (¶m.kind, arg.unpack()) { + (ty::GenericParamDefKind::Type { .. }, ty::GenericArgKind::Type(_)) + | (ty::GenericParamDefKind::Lifetime, ty::GenericArgKind::Lifetime(_)) + | (ty::GenericParamDefKind::Const { .. }, ty::GenericArgKind::Const(_)) => {} + _ => return false, + } + } + + true + } + + let generics = tcx.generics_of(assoc_item.def_id); + // Chop off any additional args (RPITIT) args + let args = &args[0..generics.count().min(args.len())]; + check_args_compatible_inner(tcx, generics, args) +} diff --git a/compiler/rustc_trait_selection/src/traits/vtable.rs b/compiler/rustc_trait_selection/src/traits/vtable.rs index cc674ceee3d5d..427ac36843215 100644 --- a/compiler/rustc_trait_selection/src/traits/vtable.rs +++ b/compiler/rustc_trait_selection/src/traits/vtable.rs @@ -5,8 +5,9 @@ use rustc_hir::lang_items::LangItem; use rustc_infer::traits::util::PredicateSet; use rustc_infer::traits::ImplSource; use rustc_middle::query::Providers; +use rustc_middle::traits::BuiltinImplSource; use rustc_middle::ty::visit::TypeVisitableExt; -use rustc_middle::ty::InternalSubsts; +use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{self, GenericParamDefKind, ToPredicate, Ty, TyCtxt, VtblEntry}; use rustc_span::{sym, Span}; use smallvec::SmallVec; @@ -15,17 +16,27 @@ use std::fmt::Debug; use std::ops::ControlFlow; #[derive(Clone, Debug)] -pub(super) enum VtblSegment<'tcx> { +pub enum VtblSegment<'tcx> { MetadataDSA, TraitOwnEntries { trait_ref: ty::PolyTraitRef<'tcx>, emit_vptr: bool }, } /// Prepare the segments for a vtable -pub(super) fn prepare_vtable_segments<'tcx, T>( +pub fn prepare_vtable_segments<'tcx, T>( tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>, - mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow, + segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow, ) -> Option { + prepare_vtable_segments_inner(tcx, trait_ref, segment_visitor).break_value() +} + +/// Helper for [`prepare_vtable_segments`] that returns `ControlFlow`, +/// such that we can use `?` in the body. +fn prepare_vtable_segments_inner<'tcx, T>( + tcx: TyCtxt<'tcx>, + trait_ref: ty::PolyTraitRef<'tcx>, + mut segment_visitor: impl FnMut(VtblSegment<'tcx>) -> ControlFlow, +) -> ControlFlow { // The following constraints holds for the final arrangement. // 1. The whole virtual table of the first direct super trait is included as the // the prefix. If this trait doesn't have any super traits, then this step @@ -71,20 +82,18 @@ pub(super) fn prepare_vtable_segments<'tcx, T>( // N, N-vptr, O // emit dsa segment first. - if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::MetadataDSA) { - return Some(v); - } + segment_visitor(VtblSegment::MetadataDSA)?; let mut emit_vptr_on_new_entry = false; let mut visited = PredicateSet::new(tcx); - let predicate = trait_ref.without_const().to_predicate(tcx); + let predicate = trait_ref.to_predicate(tcx); let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> = - smallvec![(trait_ref, emit_vptr_on_new_entry, None)]; + smallvec![(trait_ref, emit_vptr_on_new_entry, maybe_iter(None))]; visited.insert(predicate); // the main traversal loop: // basically we want to cut the inheritance directed graph into a few non-overlapping slices of nodes - // that each node is emitted after all its descendents have been emitted. + // such that each node is emitted after all its descendants have been emitted. // so we convert the directed graph into a tree by skipping all previously visited nodes using a visited set. // this is done on the fly. // Each loop run emits a slice - it starts by find a "childless" unvisited node, backtracking upwards, and it @@ -105,98 +114,107 @@ pub(super) fn prepare_vtable_segments<'tcx, T>( // Loop run #1: Emitting the slice [D C] (in reverse order). No one has a next-sibling node. // Loop run #1: Stack after exiting out is []. Now the function exits. - loop { + 'outer: loop { // dive deeper into the stack, recording the path 'diving_in: loop { - if let Some((inner_most_trait_ref, _, _)) = stack.last() { - let inner_most_trait_ref = *inner_most_trait_ref; - let mut direct_super_traits_iter = tcx - .super_predicates_of(inner_most_trait_ref.def_id()) - .predicates - .into_iter() - .filter_map(move |(pred, _)| { - pred.subst_supertrait(tcx, &inner_most_trait_ref).to_opt_poly_trait_pred() - }); + let &(inner_most_trait_ref, _, _) = stack.last().unwrap(); + + let mut direct_super_traits_iter = tcx + .super_predicates_of(inner_most_trait_ref.def_id()) + .predicates + .into_iter() + .filter_map(move |(pred, _)| { + pred.subst_supertrait(tcx, &inner_most_trait_ref).as_trait_clause() + }); - 'diving_in_skip_visited_traits: loop { - if let Some(next_super_trait) = direct_super_traits_iter.next() { - if visited.insert(next_super_trait.to_predicate(tcx)) { - // We're throwing away potential constness of super traits here. - // FIXME: handle ~const super traits - let next_super_trait = next_super_trait.map_bound(|t| t.trait_ref); - stack.push(( - next_super_trait, - emit_vptr_on_new_entry, - Some(direct_super_traits_iter), - )); - break 'diving_in_skip_visited_traits; - } else { - continue 'diving_in_skip_visited_traits; - } - } else { - break 'diving_in; - } + // Find an unvisited supertrait + match direct_super_traits_iter + .find(|&super_trait| visited.insert(super_trait.to_predicate(tcx))) + { + // Push it to the stack for the next iteration of 'diving_in to pick up + Some(unvisited_super_trait) => { + // We're throwing away potential constness of super traits here. + // FIXME: handle ~const super traits + let next_super_trait = unvisited_super_trait.map_bound(|t| t.trait_ref); + stack.push(( + next_super_trait, + emit_vptr_on_new_entry, + maybe_iter(Some(direct_super_traits_iter)), + )) } + + // There are no more unvisited direct super traits, dive-in finished + None => break 'diving_in, } } - // Other than the left-most path, vptr should be emitted for each trait. - emit_vptr_on_new_entry = true; - // emit innermost item, move to next sibling and stop there if possible, otherwise jump to outer level. - 'exiting_out: loop { - if let Some((inner_most_trait_ref, emit_vptr, siblings_opt)) = stack.last_mut() { - if let ControlFlow::Break(v) = (segment_visitor)(VtblSegment::TraitOwnEntries { - trait_ref: *inner_most_trait_ref, - emit_vptr: *emit_vptr, - }) { - return Some(v); - } + while let Some((inner_most_trait_ref, emit_vptr, mut siblings)) = stack.pop() { + segment_visitor(VtblSegment::TraitOwnEntries { + trait_ref: inner_most_trait_ref, + emit_vptr, + })?; + + // If we've emitted (fed to `segment_visitor`) a trait that has methods present in the vtable, + // we'll need to emit vptrs from now on. + if !emit_vptr_on_new_entry + && has_own_existential_vtable_entries(tcx, inner_most_trait_ref.def_id()) + { + emit_vptr_on_new_entry = true; + } - 'exiting_out_skip_visited_traits: loop { - if let Some(siblings) = siblings_opt { - if let Some(next_inner_most_trait_ref) = siblings.next() { - if visited.insert(next_inner_most_trait_ref.to_predicate(tcx)) { - // We're throwing away potential constness of super traits here. - // FIXME: handle ~const super traits - let next_inner_most_trait_ref = - next_inner_most_trait_ref.map_bound(|t| t.trait_ref); - *inner_most_trait_ref = next_inner_most_trait_ref; - *emit_vptr = emit_vptr_on_new_entry; - break 'exiting_out; - } else { - continue 'exiting_out_skip_visited_traits; - } - } - } - stack.pop(); - continue 'exiting_out; - } + if let Some(next_inner_most_trait_ref) = + siblings.find(|&sibling| visited.insert(sibling.to_predicate(tcx))) + { + // We're throwing away potential constness of super traits here. + // FIXME: handle ~const super traits + let next_inner_most_trait_ref = + next_inner_most_trait_ref.map_bound(|t| t.trait_ref); + + stack.push((next_inner_most_trait_ref, emit_vptr_on_new_entry, siblings)); + + // just pushed a new trait onto the stack, so we need to go through its super traits + continue 'outer; } - // all done - return None; } + + // the stack is empty, all done + return ControlFlow::Continue(()); } } +/// Turns option of iterator into an iterator (this is just flatten) +fn maybe_iter(i: Option) -> impl Iterator { + // Flatten is bad perf-vise, we could probably implement a special case here that is better + i.into_iter().flatten() +} + fn dump_vtable_entries<'tcx>( tcx: TyCtxt<'tcx>, sp: Span, trait_ref: ty::PolyTraitRef<'tcx>, entries: &[VtblEntry<'tcx>], ) { - tcx.sess.emit_err(DumpVTableEntries { - span: sp, - trait_ref, - entries: format!("{:#?}", entries), - }); + tcx.sess.emit_err(DumpVTableEntries { span: sp, trait_ref, entries: format!("{entries:#?}") }); +} + +fn has_own_existential_vtable_entries(tcx: TyCtxt<'_>, trait_def_id: DefId) -> bool { + own_existential_vtable_entries_iter(tcx, trait_def_id).next().is_some() } fn own_existential_vtable_entries(tcx: TyCtxt<'_>, trait_def_id: DefId) -> &[DefId] { + tcx.arena.alloc_from_iter(own_existential_vtable_entries_iter(tcx, trait_def_id)) +} + +fn own_existential_vtable_entries_iter( + tcx: TyCtxt<'_>, + trait_def_id: DefId, +) -> impl Iterator + '_ { let trait_methods = tcx .associated_items(trait_def_id) .in_definition_order() .filter(|item| item.kind == ty::AssocKind::Fn); + // Now list each method's DefId (for within its trait). let own_entries = trait_methods.filter_map(move |&trait_method| { debug!("own_existential_vtable_entry: trait_method={:?}", trait_method); @@ -211,7 +229,7 @@ fn own_existential_vtable_entries(tcx: TyCtxt<'_>, trait_def_id: DefId) -> &[Def Some(def_id) }); - tcx.arena.alloc_from_iter(own_entries.into_iter()) + own_entries } /// Given a trait `trait_ref`, iterates the vtable entries @@ -241,12 +259,12 @@ fn vtable_entries<'tcx>( debug!("vtable_entries: trait_method={:?}", def_id); // The method may have some early-bound lifetimes; add regions for those. - let substs = trait_ref.map_bound(|trait_ref| { - InternalSubsts::for_item(tcx, def_id, |param, _| match param.kind { + let args = trait_ref.map_bound(|trait_ref| { + GenericArgs::for_item(tcx, def_id, |param, _| match param.kind { GenericParamDefKind::Lifetime => tcx.lifetimes.re_erased.into(), GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { - trait_ref.substs[param.index as usize] + trait_ref.args[param.index as usize] } }) }); @@ -254,14 +272,14 @@ fn vtable_entries<'tcx>( // The trait type may have higher-ranked lifetimes in it; // erase them if they appear, so that we get the type // at some particular call site. - let substs = tcx - .normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), substs); + let args = + tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), args); // It's possible that the method relies on where-clauses that // do not hold for this particular set of type parameters. // Note that this method could then never be called, so we // do not want to try and codegen it, in that case (see #23435). - let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, substs); + let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, args); if impossible_predicates( tcx, predicates.map(|(predicate, _)| predicate).collect(), @@ -274,7 +292,7 @@ fn vtable_entries<'tcx>( tcx, ty::ParamEnv::reveal_all(), def_id, - substs, + args, ) .expect("resolution failed during building vtable representation"); VtblEntry::Method(instance) @@ -362,9 +380,9 @@ pub(crate) fn vtable_trait_upcasting_coercion_new_vptr_slot<'tcx>( let trait_ref = ty::TraitRef::new(tcx, unsize_trait_did, [source, target]); - match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), ty::Binder::dummy(trait_ref))) { - Ok(ImplSource::TraitUpcasting(implsrc_traitcasting)) => { - implsrc_traitcasting.vtable_vptr_slot + match tcx.codegen_select_candidate((ty::ParamEnv::reveal_all(), trait_ref)) { + Ok(ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { vtable_vptr_slot }, _)) => { + *vtable_vptr_slot } otherwise => bug!("expected TraitUpcasting candidate, got {otherwise:?}"), } diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 086ab32b52007..f26310665f9fa 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -2,8 +2,8 @@ use crate::infer::InferCtxt; use crate::traits; use rustc_hir as hir; use rustc_hir::lang_items::LangItem; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{GenericArg, GenericArgKind, GenericArgsRef}; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; use rustc_span::{Span, DUMMY_SP}; @@ -77,12 +77,19 @@ pub fn unnormalized_obligations<'tcx>( param_env: ty::ParamEnv<'tcx>, arg: GenericArg<'tcx>, ) -> Option>> { + debug_assert_eq!(arg, infcx.resolve_vars_if_possible(arg)); + + // However, if `arg` IS an unresolved inference variable, returns `None`, + // because we are not able to make any progress at all. This is to prevent + // "livelock" where we say "$0 is WF if $0 is WF". + if arg.is_non_region_infer() { + return None; + } + if let ty::GenericArgKind::Lifetime(..) = arg.unpack() { return Some(vec![]); } - debug_assert_eq!(arg, infcx.resolve_vars_if_possible(arg)); - let mut wf = WfPredicates { infcx, param_env, @@ -142,29 +149,32 @@ pub fn predicate_obligations<'tcx>( // It's ok to skip the binder here because wf code is prepared for it match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(t)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(t)) => { wf.compute_trait_pred(&t, Elaborate::None); } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) => {} - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate(ty, _reg))) => { + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) => {} + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( + ty, + _reg, + ))) => { wf.compute(ty.into()); } - ty::PredicateKind::Clause(ty::Clause::Projection(t)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(t)) => { wf.compute_projection(t.projection_ty); wf.compute(match t.term.unpack() { ty::TermKind::Ty(ty) => ty.into(), ty::TermKind::Const(c) => c.into(), }) } - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(ct, ty)) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { wf.compute(ct.into()); wf.compute(ty.into()); } - ty::PredicateKind::WellFormed(arg) => { + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { wf.compute(arg); } - ty::PredicateKind::ConstEvaluatable(ct) => { + ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(ct)) => { wf.compute(ct.into()); } @@ -174,8 +184,7 @@ pub fn predicate_obligations<'tcx>( | ty::PredicateKind::Coerce(..) | ty::PredicateKind::ConstEquate(..) | ty::PredicateKind::Ambiguous - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::TypeWellFormedFromEnv(..) => { + | ty::PredicateKind::AliasRelate(..) => { bug!("We should only wf check where clauses, unexpected predicate: {predicate:?}") } } @@ -247,7 +256,7 @@ fn extend_cause_with_original_assoc_item_obligation<'tcx>( // It is fine to skip the binder as we don't care about regions here. match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(proj)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Projection(proj)) => { // The obligation comes not from the current `impl` nor the `trait` being implemented, // but rather from a "second order" obligation, where an associated type has a // projection coming from another associated type. See @@ -264,7 +273,7 @@ fn extend_cause_with_original_assoc_item_obligation<'tcx>( cause.span = impl_item_span; } } - ty::PredicateKind::Clause(ty::Clause::Trait(pred)) => { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => { // An associated item obligation born out of the `trait` failed to be met. An example // can be seen in `ui/associated-types/point-at-type-on-obligation-failure-2.rs`. debug!("extended_cause_with_original_assoc_item_obligation trait proj {:?}", pred); @@ -293,6 +302,16 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { } fn normalize(self, infcx: &InferCtxt<'tcx>) -> Vec> { + // Do not normalize `wf` obligations with the new solver. + // + // The current deep normalization routine with the new solver does not + // handle ambiguity and the new solver correctly deals with unnnormalized goals. + // If the user relies on normalized types, e.g. for `fn implied_outlives_bounds`, + // it is their responsibility to normalize while avoiding ambiguity. + if infcx.next_trait_solver() { + return self.out; + } + let cause = self.cause(traits::WellFormed(None)); let param_env = self.param_env; let mut obligations = Vec::with_capacity(self.out.len()); @@ -322,18 +341,14 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { let trait_ref = &trait_pred.trait_ref; // Negative trait predicates don't require supertraits to hold, just - // that their substs are WF. + // that their args are WF. if trait_pred.polarity == ty::ImplPolarity::Negative { self.compute_negative_trait_pred(trait_ref); return; } // if the trait predicate is not const, the wf obligations should not be const as well. - let obligations = if trait_pred.constness == ty::BoundConstness::NotConst { - self.nominal_obligations_without_const(trait_ref.def_id, trait_ref.substs) - } else { - self.nominal_obligations(trait_ref.def_id, trait_ref.substs) - }; + let obligations = self.nominal_obligations(trait_ref.def_id, trait_ref.args); debug!("compute_trait_pred obligations {:?}", obligations); let param_env = self.param_env; @@ -364,7 +379,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { self.out.extend( trait_ref - .substs + .args .iter() .enumerate() .filter(|(_, arg)| { @@ -386,7 +401,9 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { cause, depth, param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(arg)), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + arg, + ))), ) }), ); @@ -395,7 +412,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // Compute the obligations that are required for `trait_ref` to be WF, // given that it is a *negative* trait predicate. fn compute_negative_trait_pred(&mut self, trait_ref: &ty::TraitRef<'tcx>) { - for arg in trait_ref.substs { + for arg in trait_ref.args { self.compute(arg); } } @@ -406,7 +423,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // A projection is well-formed if // // (a) its predicates hold (*) - // (b) its substs are wf + // (b) its args are wf // // (*) The predicates of an associated type include the predicates of // the trait that it's contained in. For example, given @@ -424,18 +441,17 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // `i32: Clone` // `i32: Copy` // ] - // Projection types do not require const predicates. - let obligations = self.nominal_obligations_without_const(data.def_id, data.substs); + let obligations = self.nominal_obligations(data.def_id, data.args); self.out.extend(obligations); - self.compute_projection_substs(data.substs); + self.compute_projection_args(data.args); } fn compute_inherent_projection(&mut self, data: ty::AliasTy<'tcx>) { // An inherent projection is well-formed if // // (a) its predicates hold (*) - // (b) its substs are wf + // (b) its args are wf // // (*) The predicates of an inherent associated type include the // predicates of the impl that it's contained in. @@ -443,7 +459,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { if !data.self_ty().has_escaping_bound_vars() { // FIXME(inherent_associated_types): Should this happen inside of a snapshot? // FIXME(inherent_associated_types): This is incompatible with the new solver and lazy norm! - let substs = traits::project::compute_inherent_assoc_ty_substs( + let args = traits::project::compute_inherent_assoc_ty_args( &mut traits::SelectionContext::new(self.infcx), self.param_env, data, @@ -451,23 +467,21 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { self.recursion_depth, &mut self.out, ); - // Inherent projection types do not require const predicates. - let obligations = self.nominal_obligations_without_const(data.def_id, substs); + let obligations = self.nominal_obligations(data.def_id, args); self.out.extend(obligations); } - self.compute_projection_substs(data.substs); + self.compute_projection_args(data.args); } - fn compute_projection_substs(&mut self, substs: SubstsRef<'tcx>) { + fn compute_projection_args(&mut self, args: GenericArgsRef<'tcx>) { let tcx = self.tcx(); let cause = self.cause(traits::WellFormed(None)); let param_env = self.param_env; let depth = self.recursion_depth; self.out.extend( - substs - .iter() + args.iter() .filter(|arg| { matches!(arg.unpack(), GenericArgKind::Type(..) | GenericArgKind::Const(..)) }) @@ -478,7 +492,9 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { cause.clone(), depth, param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(arg)), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + arg, + ))), ) }), ); @@ -494,7 +510,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { cause, self.recursion_depth, self.param_env, - ty::Binder::dummy(trait_ref).without_const(), + ty::Binder::dummy(trait_ref), )); } } @@ -518,11 +534,12 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { match ct.kind() { ty::ConstKind::Unevaluated(uv) => { if !ct.has_escaping_bound_vars() { - let obligations = self.nominal_obligations(uv.def, uv.substs); + let obligations = self.nominal_obligations(uv.def, uv.args); self.out.extend(obligations); - let predicate = - ty::Binder::dummy(ty::PredicateKind::ConstEvaluatable(ct)); + let predicate = ty::Binder::dummy(ty::PredicateKind::Clause( + ty::ClauseKind::ConstEvaluatable(ct), + )); let cause = self.cause(traits::WellFormed(None)); self.out.push(traits::Obligation::with_depth( self.tcx(), @@ -541,19 +558,22 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { cause, self.recursion_depth, self.param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(ct.into())), + ty::Binder::dummy(ty::PredicateKind::Clause( + ty::ClauseKind::WellFormed(ct.into()), + )), )); } ty::ConstKind::Expr(_) => { - // FIXME(generic_const_exprs): this doesnt verify that given `Expr(N + 1)` the + // FIXME(generic_const_exprs): this doesn't verify that given `Expr(N + 1)` the // trait bound `typeof(N): Add` holds. This is currently unnecessary // as `ConstKind::Expr` is only produced via normalization of `ConstKind::Unevaluated` // which means that the `DefId` would have been typeck'd elsewhere. However in // the future we may allow directly lowering to `ConstKind::Expr` in which case // we would not be proving bounds we should. - let predicate = - ty::Binder::dummy(ty::PredicateKind::ConstEvaluatable(ct)); + let predicate = ty::Binder::dummy(ty::PredicateKind::Clause( + ty::ClauseKind::ConstEvaluatable(ct), + )); let cause = self.cause(traits::WellFormed(None)); self.out.push(traits::Obligation::with_depth( self.tcx(), @@ -634,14 +654,14 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { self.compute_inherent_projection(data); } - ty::Adt(def, substs) => { + ty::Adt(def, args) => { // WfNominalType - let obligations = self.nominal_obligations(def.did(), substs); + let obligations = self.nominal_obligations(def.did(), args); self.out.extend(obligations); } - ty::FnDef(did, substs) => { - let obligations = self.nominal_obligations_without_const(did, substs); + ty::FnDef(did, args) => { + let obligations = self.nominal_obligations(did, args); self.out.extend(obligations); } @@ -654,14 +674,14 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { cause, depth, param_env, - ty::Binder::dummy(ty::PredicateKind::Clause(ty::Clause::TypeOutlives( - ty::OutlivesPredicate(rty, r), - ))), + ty::Binder::dummy(ty::PredicateKind::Clause( + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(rty, r)), + )), )); } } - ty::Generator(did, substs, ..) => { + ty::Generator(did, args, ..) => { // Walk ALL the types in the generator: this will // include the upvar types as well as the yield // type. Note that this is mildly distinct from @@ -669,11 +689,11 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // about the signature of the closure. We don't // have the problem of implied bounds here since // generators don't take arguments. - let obligations = self.nominal_obligations(did, substs); + let obligations = self.nominal_obligations(did, args); self.out.extend(obligations); } - ty::Closure(did, substs) => { + ty::Closure(did, args) => { // Only check the upvar types for WF, not the rest // of the types within. This is needed because we // capture the signature and it may not be WF @@ -696,7 +716,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // fn(&'a T) }`, as discussed in #25860. walker.skip_current_subtree(); // subtree handled below // FIXME(eddyb) add the type to `walker` instead of recursing. - self.compute(substs.as_closure().tupled_upvars_ty().into()); + self.compute(args.as_closure().tupled_upvars_ty().into()); // Note that we cannot skip the generic types // types. Normally, within the fn // body where they are created, the generics will @@ -712,7 +732,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // can cause compiler crashes when the user abuses unsafe // code to procure such a closure. // See tests/ui/type-alias-impl-trait/wf_check_closures.rs - let obligations = self.nominal_obligations(did, substs); + let obligations = self.nominal_obligations(did, args); self.out.extend(obligations); } @@ -721,16 +741,21 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // types appearing in the fn signature } - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { + ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => { // All of the requirements on type parameters // have already been checked for `impl Trait` in // return position. We do need to check type-alias-impl-trait though. if self.tcx().is_type_alias_impl_trait(def_id) { - let obligations = self.nominal_obligations(def_id, substs); + let obligations = self.nominal_obligations(def_id, args); self.out.extend(obligations); } } + ty::Alias(ty::Weak, ty::AliasTy { def_id, args, .. }) => { + let obligations = self.nominal_obligations(def_id, args); + self.out.extend(obligations); + } + ty::Dynamic(data, r, _) => { // WfObject // @@ -779,7 +804,9 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { cause, self.recursion_depth, param_env, - ty::Binder::dummy(ty::PredicateKind::WellFormed(ty.into())), + ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed( + ty.into(), + ))), )); } } @@ -789,11 +816,10 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { } #[instrument(level = "debug", skip(self))] - fn nominal_obligations_inner( + fn nominal_obligations( &mut self, def_id: DefId, - substs: SubstsRef<'tcx>, - remap_constness: bool, + args: GenericArgsRef<'tcx>, ) -> Vec> { let predicates = self.tcx().predicates_of(def_id); let mut origins = vec![def_id; predicates.predicates.len()]; @@ -803,21 +829,18 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { origins.extend(iter::repeat(parent).take(head.predicates.len())); } - let predicates = predicates.instantiate(self.tcx(), substs); + let predicates = predicates.instantiate(self.tcx(), args); trace!("{:#?}", predicates); debug_assert_eq!(predicates.predicates.len(), origins.len()); iter::zip(predicates, origins.into_iter().rev()) - .map(|((mut pred, span), origin_def_id)| { + .map(|((pred, span), origin_def_id)| { let code = if span.is_dummy() { traits::ItemObligation(origin_def_id) } else { traits::BindingObligation(origin_def_id, span) }; let cause = self.cause(code); - if remap_constness { - pred = pred.without_const(self.tcx()); - } traits::Obligation::with_depth( self.tcx(), cause, @@ -830,22 +853,6 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { .collect() } - fn nominal_obligations( - &mut self, - def_id: DefId, - substs: SubstsRef<'tcx>, - ) -> Vec> { - self.nominal_obligations_inner(def_id, substs, false) - } - - fn nominal_obligations_without_const( - &mut self, - def_id: DefId, - substs: SubstsRef<'tcx>, - ) -> Vec> { - self.nominal_obligations_inner(def_id, substs, true) - } - fn from_object_ty( &mut self, ty: Ty<'tcx>, @@ -918,7 +925,7 @@ pub fn object_region_bounds<'tcx>( // Since we don't actually *know* the self type for an object, // this "open(err)" serves as a kind of dummy standin -- basically // a placeholder type. - let open_ty = tcx.mk_fresh_ty(0); + let open_ty = Ty::new_fresh(tcx, 0); let predicates = existential_predicates.iter().filter_map(|predicate| { if let ty::ExistentialPredicate::Projection(_) = predicate.skip_binder() { @@ -951,7 +958,7 @@ pub fn object_region_bounds<'tcx>( pub(crate) fn required_region_bounds<'tcx>( tcx: TyCtxt<'tcx>, erased_self_ty: Ty<'tcx>, - predicates: impl Iterator>, + predicates: impl Iterator>, ) -> Vec> { assert!(!erased_self_ty.has_escaping_bound_vars()); @@ -959,24 +966,7 @@ pub(crate) fn required_region_bounds<'tcx>( .filter_map(|pred| { debug!(?pred); match pred.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::Trait(..)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::WellFormed(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::TypeWellFormedFromEnv(..) => None, - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - ref t, - ref r, - ))) => { + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ref t, ref r)) => { // Search for a bound of the form `erased_self_ty // : 'a`, but be wary of something like `for<'a> // erased_self_ty : 'a` (we interpret a @@ -992,6 +982,12 @@ pub(crate) fn required_region_bounds<'tcx>( None } } + ty::ClauseKind::Trait(_) + | ty::ClauseKind::RegionOutlives(_) + | ty::ClauseKind::Projection(_) + | ty::ClauseKind::ConstArgHasType(_, _) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => None, } }) .collect() diff --git a/compiler/rustc_traits/Cargo.toml b/compiler/rustc_traits/Cargo.toml index dfd6fbff7c903..37e00c0e4bcff 100644 --- a/compiler/rustc_traits/Cargo.toml +++ b/compiler/rustc_traits/Cargo.toml @@ -11,9 +11,6 @@ rustc_hir = { path = "../rustc_hir" } rustc_ast = { path = "../rustc_ast" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } -chalk-ir = "0.87.0" -chalk-engine = "0.87.0" -chalk-solve = "0.87.0" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } rustc_infer = { path = "../rustc_infer" } rustc_trait_selection = { path = "../rustc_trait_selection" } diff --git a/compiler/rustc_traits/src/chalk/db.rs b/compiler/rustc_traits/src/chalk/db.rs deleted file mode 100644 index c319b2e31c7e6..0000000000000 --- a/compiler/rustc_traits/src/chalk/db.rs +++ /dev/null @@ -1,792 +0,0 @@ -//! Provides the `RustIrDatabase` implementation for `chalk-solve` -//! -//! The purpose of the `chalk_solve::RustIrDatabase` is to get data about -//! specific types, such as bounds, where clauses, or fields. This file contains -//! the minimal logic to assemble the types for `chalk-solve` by calling out to -//! either the `TyCtxt` (for information about types) or -//! `crate::chalk::lowering` (to lower rustc types into Chalk types). - -use rustc_middle::traits::ChalkRustInterner as RustInterner; -use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable}; -use rustc_middle::ty::{InternalSubsts, SubstsRef}; -use rustc_target::abi::{Integer, IntegerType}; - -use rustc_ast::ast; - -use rustc_hir::def_id::DefId; - -use rustc_span::symbol::sym; - -use std::fmt; -use std::sync::Arc; - -use crate::chalk::lowering::LowerInto; - -pub struct RustIrDatabase<'tcx> { - pub(crate) interner: RustInterner<'tcx>, -} - -impl fmt::Debug for RustIrDatabase<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "RustIrDatabase") - } -} - -impl<'tcx> RustIrDatabase<'tcx> { - fn where_clauses_for( - &self, - def_id: DefId, - bound_vars: SubstsRef<'tcx>, - ) -> Vec>> { - self.interner - .tcx - .predicates_defined_on(def_id) - .instantiate_own(self.interner.tcx, bound_vars) - .filter_map(|(wc, _)| LowerInto::lower_into(wc, self.interner)) - .collect() - } - - fn bounds_for(&self, def_id: DefId, bound_vars: SubstsRef<'tcx>) -> Vec - where - ty::Predicate<'tcx>: LowerInto<'tcx, std::option::Option>, - { - self.interner - .tcx - .explicit_item_bounds(def_id) - .subst_iter_copied(self.interner.tcx, &bound_vars) - .filter_map(|(bound, _)| LowerInto::>::lower_into(bound, self.interner)) - .collect() - } -} - -impl<'tcx> chalk_solve::RustIrDatabase> for RustIrDatabase<'tcx> { - fn interner(&self) -> RustInterner<'tcx> { - self.interner - } - - fn associated_ty_data( - &self, - assoc_type_id: chalk_ir::AssocTypeId>, - ) -> Arc>> { - let def_id = assoc_type_id.0; - let assoc_item = self.interner.tcx.associated_item(def_id); - let Some(trait_def_id) = assoc_item.trait_container(self.interner.tcx) else { - unimplemented!("Not possible??"); - }; - match assoc_item.kind { - AssocKind::Type => {} - _ => unimplemented!("Not possible??"), - } - let bound_vars = bound_vars_for_item(self.interner.tcx, def_id); - let binders = binders_for(self.interner, bound_vars); - - let where_clauses = self.where_clauses_for(def_id, bound_vars); - let bounds = self.bounds_for(def_id, bound_vars); - - Arc::new(chalk_solve::rust_ir::AssociatedTyDatum { - trait_id: chalk_ir::TraitId(trait_def_id), - id: assoc_type_id, - name: (), - binders: chalk_ir::Binders::new( - binders, - chalk_solve::rust_ir::AssociatedTyDatumBound { bounds, where_clauses }, - ), - }) - } - - fn trait_datum( - &self, - trait_id: chalk_ir::TraitId>, - ) -> Arc>> { - use chalk_solve::rust_ir::WellKnownTrait::*; - - let def_id = trait_id.0; - let trait_def = self.interner.tcx.trait_def(def_id); - - let bound_vars = bound_vars_for_item(self.interner.tcx, def_id); - let binders = binders_for(self.interner, bound_vars); - - let where_clauses = self.where_clauses_for(def_id, bound_vars); - - let associated_ty_ids: Vec<_> = self - .interner - .tcx - .associated_items(def_id) - .in_definition_order() - .filter(|i| i.kind == AssocKind::Type) - .map(|i| chalk_ir::AssocTypeId(i.def_id)) - .collect(); - - let lang_items = self.interner.tcx.lang_items(); - let well_known = if lang_items.sized_trait() == Some(def_id) { - Some(Sized) - } else if lang_items.copy_trait() == Some(def_id) { - Some(Copy) - } else if lang_items.clone_trait() == Some(def_id) { - Some(Clone) - } else if lang_items.drop_trait() == Some(def_id) { - Some(Drop) - } else if lang_items.fn_trait() == Some(def_id) { - Some(Fn) - } else if lang_items.fn_once_trait() == Some(def_id) { - Some(FnOnce) - } else if lang_items.fn_mut_trait() == Some(def_id) { - Some(FnMut) - } else if lang_items.unsize_trait() == Some(def_id) { - Some(Unsize) - } else if lang_items.unpin_trait() == Some(def_id) { - Some(Unpin) - } else if lang_items.coerce_unsized_trait() == Some(def_id) { - Some(CoerceUnsized) - } else if lang_items.dispatch_from_dyn_trait() == Some(def_id) { - Some(DispatchFromDyn) - } else if lang_items.tuple_trait() == Some(def_id) { - Some(Tuple) - } else { - None - }; - Arc::new(chalk_solve::rust_ir::TraitDatum { - id: trait_id, - binders: chalk_ir::Binders::new( - binders, - chalk_solve::rust_ir::TraitDatumBound { where_clauses }, - ), - flags: chalk_solve::rust_ir::TraitFlags { - auto: trait_def.has_auto_impl, - marker: trait_def.is_marker, - upstream: !def_id.is_local(), - fundamental: self.interner.tcx.has_attr(def_id, sym::fundamental), - non_enumerable: true, - coinductive: false, - }, - associated_ty_ids, - well_known, - }) - } - - fn adt_datum( - &self, - adt_id: chalk_ir::AdtId>, - ) -> Arc>> { - let adt_def = adt_id.0; - - let bound_vars = bound_vars_for_item(self.interner.tcx, adt_def.did()); - let binders = binders_for(self.interner, bound_vars); - - let where_clauses = self.where_clauses_for(adt_def.did(), bound_vars); - - let variants: Vec<_> = adt_def - .variants() - .iter() - .map(|variant| chalk_solve::rust_ir::AdtVariantDatum { - fields: variant - .fields - .iter() - .map(|field| field.ty(self.interner.tcx, bound_vars).lower_into(self.interner)) - .collect(), - }) - .collect(); - Arc::new(chalk_solve::rust_ir::AdtDatum { - id: adt_id, - binders: chalk_ir::Binders::new( - binders, - chalk_solve::rust_ir::AdtDatumBound { variants, where_clauses }, - ), - flags: chalk_solve::rust_ir::AdtFlags { - upstream: !adt_def.did().is_local(), - fundamental: adt_def.is_fundamental(), - phantom_data: adt_def.is_phantom_data(), - }, - kind: match adt_def.adt_kind() { - ty::AdtKind::Struct => chalk_solve::rust_ir::AdtKind::Struct, - ty::AdtKind::Union => chalk_solve::rust_ir::AdtKind::Union, - ty::AdtKind::Enum => chalk_solve::rust_ir::AdtKind::Enum, - }, - }) - } - - fn adt_repr( - &self, - adt_id: chalk_ir::AdtId>, - ) -> Arc>> { - let adt_def = adt_id.0; - let int = |i| chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Int(i)).intern(self.interner); - let uint = |i| chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Uint(i)).intern(self.interner); - Arc::new(chalk_solve::rust_ir::AdtRepr { - c: adt_def.repr().c(), - packed: adt_def.repr().packed(), - int: adt_def.repr().int.map(|i| match i { - IntegerType::Pointer(true) => int(chalk_ir::IntTy::Isize), - IntegerType::Pointer(false) => uint(chalk_ir::UintTy::Usize), - IntegerType::Fixed(i, true) => match i { - Integer::I8 => int(chalk_ir::IntTy::I8), - Integer::I16 => int(chalk_ir::IntTy::I16), - Integer::I32 => int(chalk_ir::IntTy::I32), - Integer::I64 => int(chalk_ir::IntTy::I64), - Integer::I128 => int(chalk_ir::IntTy::I128), - }, - IntegerType::Fixed(i, false) => match i { - Integer::I8 => uint(chalk_ir::UintTy::U8), - Integer::I16 => uint(chalk_ir::UintTy::U16), - Integer::I32 => uint(chalk_ir::UintTy::U32), - Integer::I64 => uint(chalk_ir::UintTy::U64), - Integer::I128 => uint(chalk_ir::UintTy::U128), - }, - }), - }) - } - - fn adt_size_align( - &self, - adt_id: chalk_ir::AdtId>, - ) -> Arc { - let tcx = self.interner.tcx; - let did = adt_id.0.did(); - - // Grab the ADT and the param we might need to calculate its layout - let param_env = tcx.param_env(did); - let adt_ty = tcx.type_of(did).subst_identity(); - - // The ADT is a 1-zst if it's a ZST and its alignment is 1. - // Mark the ADT as _not_ a 1-zst if there was a layout error. - let one_zst = if let Ok(layout) = tcx.layout_of(param_env.and(adt_ty)) { - layout.is_zst() && layout.align.abi.bytes() == 1 - } else { - false - }; - - Arc::new(chalk_solve::rust_ir::AdtSizeAlign::from_one_zst(one_zst)) - } - - fn fn_def_datum( - &self, - fn_def_id: chalk_ir::FnDefId>, - ) -> Arc>> { - let def_id = fn_def_id.0; - let bound_vars = bound_vars_for_item(self.interner.tcx, def_id); - let binders = binders_for(self.interner, bound_vars); - - let where_clauses = self.where_clauses_for(def_id, bound_vars); - - let sig = self.interner.tcx.fn_sig(def_id); - let (inputs_and_output, iobinders, _) = crate::chalk::lowering::collect_bound_vars( - self.interner, - self.interner.tcx, - sig.map_bound(|s| s.inputs_and_output()).subst(self.interner.tcx, bound_vars), - ); - - let argument_types = inputs_and_output[..inputs_and_output.len() - 1] - .iter() - .map(|t| sig.rebind(*t).subst(self.interner.tcx, &bound_vars).lower_into(self.interner)) - .collect(); - - let return_type = sig - .rebind(inputs_and_output[inputs_and_output.len() - 1]) - .subst(self.interner.tcx, &bound_vars) - .lower_into(self.interner); - - let bound = chalk_solve::rust_ir::FnDefDatumBound { - inputs_and_output: chalk_ir::Binders::new( - iobinders, - chalk_solve::rust_ir::FnDefInputsAndOutputDatum { argument_types, return_type }, - ), - where_clauses, - }; - Arc::new(chalk_solve::rust_ir::FnDefDatum { - id: fn_def_id, - sig: sig.0.lower_into(self.interner), - binders: chalk_ir::Binders::new(binders, bound), - }) - } - - fn impl_datum( - &self, - impl_id: chalk_ir::ImplId>, - ) -> Arc>> { - let def_id = impl_id.0; - let bound_vars = bound_vars_for_item(self.interner.tcx, def_id); - let binders = binders_for(self.interner, bound_vars); - - let trait_ref = self.interner.tcx.impl_trait_ref(def_id).expect("not an impl"); - let trait_ref = trait_ref.subst(self.interner.tcx, bound_vars); - - let where_clauses = self.where_clauses_for(def_id, bound_vars); - - let value = chalk_solve::rust_ir::ImplDatumBound { - trait_ref: trait_ref.lower_into(self.interner), - where_clauses, - }; - - let associated_ty_value_ids: Vec<_> = self - .interner - .tcx - .associated_items(def_id) - .in_definition_order() - .filter(|i| i.kind == AssocKind::Type) - .map(|i| chalk_solve::rust_ir::AssociatedTyValueId(i.def_id)) - .collect(); - - Arc::new(chalk_solve::rust_ir::ImplDatum { - polarity: self.interner.tcx.impl_polarity(def_id).lower_into(self.interner), - binders: chalk_ir::Binders::new(binders, value), - impl_type: chalk_solve::rust_ir::ImplType::Local, - associated_ty_value_ids, - }) - } - - fn impls_for_trait( - &self, - trait_id: chalk_ir::TraitId>, - parameters: &[chalk_ir::GenericArg>], - _binders: &chalk_ir::CanonicalVarKinds>, - ) -> Vec>> { - let def_id = trait_id.0; - - // FIXME(chalk): use TraitDef::for_each_relevant_impl, but that will - // require us to be able to interconvert `Ty<'tcx>`, and we're - // not there yet. - - let all_impls = self.interner.tcx.all_impls(def_id); - let matched_impls = all_impls.filter(|impl_def_id| { - use chalk_ir::could_match::CouldMatch; - let trait_ref = self.interner.tcx.impl_trait_ref(*impl_def_id).unwrap(); - let bound_vars = bound_vars_for_item(self.interner.tcx, *impl_def_id); - - let self_ty = trait_ref.map_bound(|t| t.self_ty()); - let self_ty = self_ty.subst(self.interner.tcx, bound_vars); - let lowered_ty = self_ty.lower_into(self.interner); - - parameters[0].assert_ty_ref(self.interner).could_match( - self.interner, - self.unification_database(), - &lowered_ty, - ) - }); - - let impls = matched_impls.map(chalk_ir::ImplId).collect(); - impls - } - - fn impl_provided_for( - &self, - auto_trait_id: chalk_ir::TraitId>, - chalk_ty: &chalk_ir::TyKind>, - ) -> bool { - use chalk_ir::Scalar::*; - use chalk_ir::TyKind::*; - - let trait_def_id = auto_trait_id.0; - let all_impls = self.interner.tcx.all_impls(trait_def_id); - for impl_def_id in all_impls { - let trait_ref = self.interner.tcx.impl_trait_ref(impl_def_id).unwrap().subst_identity(); - let self_ty = trait_ref.self_ty(); - let provides = match (self_ty.kind(), chalk_ty) { - (&ty::Adt(impl_adt_def, ..), Adt(id, ..)) => impl_adt_def.did() == id.0.did(), - (_, AssociatedType(_ty_id, ..)) => { - // FIXME(chalk): See https://github.com/rust-lang/rust/pull/77152#discussion_r494484774 - false - } - (ty::Bool, Scalar(Bool)) => true, - (ty::Char, Scalar(Char)) => true, - (ty::Int(ty1), Scalar(Int(ty2))) => matches!( - (ty1, ty2), - (ty::IntTy::Isize, chalk_ir::IntTy::Isize) - | (ty::IntTy::I8, chalk_ir::IntTy::I8) - | (ty::IntTy::I16, chalk_ir::IntTy::I16) - | (ty::IntTy::I32, chalk_ir::IntTy::I32) - | (ty::IntTy::I64, chalk_ir::IntTy::I64) - | (ty::IntTy::I128, chalk_ir::IntTy::I128) - ), - (ty::Uint(ty1), Scalar(Uint(ty2))) => matches!( - (ty1, ty2), - (ty::UintTy::Usize, chalk_ir::UintTy::Usize) - | (ty::UintTy::U8, chalk_ir::UintTy::U8) - | (ty::UintTy::U16, chalk_ir::UintTy::U16) - | (ty::UintTy::U32, chalk_ir::UintTy::U32) - | (ty::UintTy::U64, chalk_ir::UintTy::U64) - | (ty::UintTy::U128, chalk_ir::UintTy::U128) - ), - (ty::Float(ty1), Scalar(Float(ty2))) => matches!( - (ty1, ty2), - (ty::FloatTy::F32, chalk_ir::FloatTy::F32) - | (ty::FloatTy::F64, chalk_ir::FloatTy::F64) - ), - (&ty::Tuple(substs), Tuple(len, _)) => substs.len() == *len, - (&ty::Array(..), Array(..)) => true, - (&ty::Slice(..), Slice(..)) => true, - (&ty::RawPtr(type_and_mut), Raw(mutability, _)) => { - match (type_and_mut.mutbl, mutability) { - (ast::Mutability::Mut, chalk_ir::Mutability::Mut) => true, - (ast::Mutability::Mut, chalk_ir::Mutability::Not) => false, - (ast::Mutability::Not, chalk_ir::Mutability::Mut) => false, - (ast::Mutability::Not, chalk_ir::Mutability::Not) => true, - } - } - (&ty::Ref(.., mutability1), Ref(mutability2, ..)) => { - match (mutability1, mutability2) { - (ast::Mutability::Mut, chalk_ir::Mutability::Mut) => true, - (ast::Mutability::Mut, chalk_ir::Mutability::Not) => false, - (ast::Mutability::Not, chalk_ir::Mutability::Mut) => false, - (ast::Mutability::Not, chalk_ir::Mutability::Not) => true, - } - } - ( - &ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }), - OpaqueType(opaque_ty_id, ..), - ) => def_id == opaque_ty_id.0, - (&ty::FnDef(def_id, ..), FnDef(fn_def_id, ..)) => def_id == fn_def_id.0, - (&ty::Str, Str) => true, - (&ty::Never, Never) => true, - (&ty::Closure(def_id, ..), Closure(closure_id, _)) => def_id == closure_id.0, - (&ty::Foreign(def_id), Foreign(foreign_def_id)) => def_id == foreign_def_id.0, - (&ty::Error(..), Error) => false, - _ => false, - }; - if provides { - return true; - } - } - false - } - - fn associated_ty_value( - &self, - associated_ty_id: chalk_solve::rust_ir::AssociatedTyValueId>, - ) -> Arc>> { - let def_id = associated_ty_id.0; - let assoc_item = self.interner.tcx.associated_item(def_id); - let impl_id = assoc_item.container_id(self.interner.tcx); - match assoc_item.kind { - AssocKind::Type => {} - _ => unimplemented!("Not possible??"), - } - - let trait_item_id = assoc_item.trait_item_def_id.expect("assoc_ty with no trait version"); - let bound_vars = bound_vars_for_item(self.interner.tcx, def_id); - let binders = binders_for(self.interner, bound_vars); - let ty = self - .interner - .tcx - .type_of(def_id) - .subst(self.interner.tcx, bound_vars) - .lower_into(self.interner); - - Arc::new(chalk_solve::rust_ir::AssociatedTyValue { - impl_id: chalk_ir::ImplId(impl_id), - associated_ty_id: chalk_ir::AssocTypeId(trait_item_id), - value: chalk_ir::Binders::new( - binders, - chalk_solve::rust_ir::AssociatedTyValueBound { ty }, - ), - }) - } - - fn custom_clauses(&self) -> Vec>> { - vec![] - } - - fn local_impls_to_coherence_check( - &self, - _trait_id: chalk_ir::TraitId>, - ) -> Vec>> { - unimplemented!() - } - - fn opaque_ty_data( - &self, - opaque_ty_id: chalk_ir::OpaqueTyId>, - ) -> Arc>> { - let bound_vars = ty::fold::shift_vars( - self.interner.tcx, - bound_vars_for_item(self.interner.tcx, opaque_ty_id.0), - 1, - ); - let where_clauses = self.where_clauses_for(opaque_ty_id.0, bound_vars); - - let identity_substs = InternalSubsts::identity_for_item(self.interner.tcx, opaque_ty_id.0); - - let explicit_item_bounds = self.interner.tcx.explicit_item_bounds(opaque_ty_id.0); - let bounds = - explicit_item_bounds - .subst_iter_copied(self.interner.tcx, &bound_vars) - .map(|(bound, _)| { - bound.fold_with(&mut ReplaceOpaqueTyFolder { - tcx: self.interner.tcx, - opaque_ty_id, - identity_substs, - binder_index: ty::INNERMOST, - }) - }) - .filter_map(|bound| { - LowerInto::< - Option>> - >::lower_into(bound, self.interner) - }) - .collect(); - - // Binder for the bound variable representing the concrete impl Trait type. - let existential_binder = chalk_ir::VariableKinds::from1( - self.interner, - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General), - ); - - let value = chalk_solve::rust_ir::OpaqueTyDatumBound { - bounds: chalk_ir::Binders::new(existential_binder.clone(), bounds), - where_clauses: chalk_ir::Binders::new(existential_binder, where_clauses), - }; - - let binders = binders_for(self.interner, bound_vars); - Arc::new(chalk_solve::rust_ir::OpaqueTyDatum { - opaque_ty_id, - bound: chalk_ir::Binders::new(binders, value), - }) - } - - fn program_clauses_for_env( - &self, - environment: &chalk_ir::Environment>, - ) -> chalk_ir::ProgramClauses> { - chalk_solve::program_clauses_for_env(self, environment) - } - - fn well_known_trait_id( - &self, - well_known_trait: chalk_solve::rust_ir::WellKnownTrait, - ) -> Option>> { - use chalk_solve::rust_ir::WellKnownTrait::*; - let lang_items = self.interner.tcx.lang_items(); - let def_id = match well_known_trait { - Sized => lang_items.sized_trait(), - Copy => lang_items.copy_trait(), - Clone => lang_items.clone_trait(), - Drop => lang_items.drop_trait(), - Fn => lang_items.fn_trait(), - FnMut => lang_items.fn_mut_trait(), - FnOnce => lang_items.fn_once_trait(), - Generator => lang_items.gen_trait(), - Unsize => lang_items.unsize_trait(), - Unpin => lang_items.unpin_trait(), - CoerceUnsized => lang_items.coerce_unsized_trait(), - DiscriminantKind => lang_items.discriminant_kind_trait(), - DispatchFromDyn => lang_items.dispatch_from_dyn_trait(), - Tuple => lang_items.tuple_trait(), - }; - def_id.map(chalk_ir::TraitId) - } - - fn is_object_safe(&self, trait_id: chalk_ir::TraitId>) -> bool { - self.interner.tcx.check_is_object_safe(trait_id.0) - } - - fn hidden_opaque_type( - &self, - _id: chalk_ir::OpaqueTyId>, - ) -> chalk_ir::Ty> { - // FIXME(chalk): actually get hidden ty - self.interner.tcx.types.unit.lower_into(self.interner) - } - - fn closure_kind( - &self, - _closure_id: chalk_ir::ClosureId>, - substs: &chalk_ir::Substitution>, - ) -> chalk_solve::rust_ir::ClosureKind { - let kind = &substs.as_slice(self.interner)[substs.len(self.interner) - 3]; - match kind.assert_ty_ref(self.interner).kind(self.interner) { - chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Int(int_ty)) => match int_ty { - chalk_ir::IntTy::I8 => chalk_solve::rust_ir::ClosureKind::Fn, - chalk_ir::IntTy::I16 => chalk_solve::rust_ir::ClosureKind::FnMut, - chalk_ir::IntTy::I32 => chalk_solve::rust_ir::ClosureKind::FnOnce, - _ => bug!("bad closure kind"), - }, - _ => bug!("bad closure kind"), - } - } - - fn closure_inputs_and_output( - &self, - _closure_id: chalk_ir::ClosureId>, - substs: &chalk_ir::Substitution>, - ) -> chalk_ir::Binders>> - { - let sig = &substs.as_slice(self.interner)[substs.len(self.interner) - 2]; - match sig.assert_ty_ref(self.interner).kind(self.interner) { - chalk_ir::TyKind::Function(f) => { - let substitution = f.substitution.0.as_slice(self.interner); - let return_type = substitution.last().unwrap().assert_ty_ref(self.interner).clone(); - // Closure arguments are tupled - let argument_tuple = substitution[0].assert_ty_ref(self.interner); - let argument_types = match argument_tuple.kind(self.interner) { - chalk_ir::TyKind::Tuple(_len, substitution) => substitution - .iter(self.interner) - .map(|arg| arg.assert_ty_ref(self.interner)) - .cloned() - .collect(), - _ => bug!("Expecting closure FnSig args to be tupled."), - }; - - chalk_ir::Binders::new( - chalk_ir::VariableKinds::from_iter( - self.interner, - (0..f.num_binders).map(|_| chalk_ir::VariableKind::Lifetime), - ), - chalk_solve::rust_ir::FnDefInputsAndOutputDatum { argument_types, return_type }, - ) - } - _ => panic!("Invalid sig."), - } - } - - fn closure_upvars( - &self, - _closure_id: chalk_ir::ClosureId>, - substs: &chalk_ir::Substitution>, - ) -> chalk_ir::Binders>> { - let inputs_and_output = self.closure_inputs_and_output(_closure_id, substs); - let tuple = substs.as_slice(self.interner).last().unwrap().assert_ty_ref(self.interner); - inputs_and_output.map_ref(|_| tuple.clone()) - } - - fn closure_fn_substitution( - &self, - _closure_id: chalk_ir::ClosureId>, - substs: &chalk_ir::Substitution>, - ) -> chalk_ir::Substitution> { - let substitution = &substs.as_slice(self.interner)[0..substs.len(self.interner) - 3]; - chalk_ir::Substitution::from_iter(self.interner, substitution) - } - - fn generator_datum( - &self, - _generator_id: chalk_ir::GeneratorId>, - ) -> Arc>> { - unimplemented!() - } - - fn generator_witness_datum( - &self, - _generator_id: chalk_ir::GeneratorId>, - ) -> Arc>> { - unimplemented!() - } - - fn unification_database(&self) -> &dyn chalk_ir::UnificationDatabase> { - self - } - - fn discriminant_type( - &self, - _: chalk_ir::Ty>, - ) -> chalk_ir::Ty> { - unimplemented!() - } -} - -impl<'tcx> chalk_ir::UnificationDatabase> for RustIrDatabase<'tcx> { - fn fn_def_variance( - &self, - def_id: chalk_ir::FnDefId>, - ) -> chalk_ir::Variances> { - let variances = self.interner.tcx.variances_of(def_id.0); - chalk_ir::Variances::from_iter( - self.interner, - variances.iter().map(|v| v.lower_into(self.interner)), - ) - } - - fn adt_variance( - &self, - adt_id: chalk_ir::AdtId>, - ) -> chalk_ir::Variances> { - let variances = self.interner.tcx.variances_of(adt_id.0.did()); - chalk_ir::Variances::from_iter( - self.interner, - variances.iter().map(|v| v.lower_into(self.interner)), - ) - } -} - -/// Creates an `InternalSubsts` that maps each generic parameter to a higher-ranked -/// var bound at index `0`. For types, we use a `BoundVar` index equal to -/// the type parameter index. For regions, we use the `BoundRegionKind::BrNamed` -/// variant (which has a `DefId`). -fn bound_vars_for_item(tcx: TyCtxt<'_>, def_id: DefId) -> SubstsRef<'_> { - InternalSubsts::for_item(tcx, def_id, |param, substs| match param.kind { - ty::GenericParamDefKind::Type { .. } => tcx - .mk_bound( - ty::INNERMOST, - ty::BoundTy { - var: ty::BoundVar::from(param.index), - kind: ty::BoundTyKind::Param(param.def_id, param.name), - }, - ) - .into(), - - ty::GenericParamDefKind::Lifetime => { - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(substs.len()), - kind: ty::BrAnon(None), - }; - tcx.mk_re_late_bound(ty::INNERMOST, br).into() - } - - ty::GenericParamDefKind::Const { .. } => tcx - .mk_const( - ty::ConstKind::Bound(ty::INNERMOST, ty::BoundVar::from(param.index)), - tcx.type_of(param.def_id).subst_identity(), - ) - .into(), - }) -} - -fn binders_for<'tcx>( - interner: RustInterner<'tcx>, - bound_vars: SubstsRef<'tcx>, -) -> chalk_ir::VariableKinds> { - chalk_ir::VariableKinds::from_iter( - interner, - bound_vars.iter().map(|arg| match arg.unpack() { - ty::subst::GenericArgKind::Lifetime(_re) => chalk_ir::VariableKind::Lifetime, - ty::subst::GenericArgKind::Type(_ty) => { - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General) - } - ty::subst::GenericArgKind::Const(c) => { - chalk_ir::VariableKind::Const(c.ty().lower_into(interner)) - } - }), - ) -} - -struct ReplaceOpaqueTyFolder<'tcx> { - tcx: TyCtxt<'tcx>, - opaque_ty_id: chalk_ir::OpaqueTyId>, - identity_substs: SubstsRef<'tcx>, - binder_index: ty::DebruijnIndex, -} - -impl<'tcx> ty::TypeFolder> for ReplaceOpaqueTyFolder<'tcx> { - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn fold_binder>>( - &mut self, - t: ty::Binder<'tcx, T>, - ) -> ty::Binder<'tcx, T> { - self.binder_index.shift_in(1); - let t = t.super_fold_with(self); - self.binder_index.shift_out(1); - t - } - - fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { - if let ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) = *ty.kind() { - if def_id == self.opaque_ty_id.0 && substs == self.identity_substs { - return self - .tcx - .mk_bound(self.binder_index, ty::BoundTy::from(ty::BoundVar::from_u32(0))); - } - } - ty - } -} diff --git a/compiler/rustc_traits/src/chalk/lowering.rs b/compiler/rustc_traits/src/chalk/lowering.rs deleted file mode 100644 index e447ab94f6408..0000000000000 --- a/compiler/rustc_traits/src/chalk/lowering.rs +++ /dev/null @@ -1,1241 +0,0 @@ -//! Contains the logic to lower rustc types into Chalk types -//! -//! In many cases there is a 1:1 relationship between a rustc type and a Chalk type. -//! For example, a `SubstsRef` maps almost directly to a `Substitution`. In some -//! other cases, such as `Param`s, there is no Chalk type, so we have to handle -//! accordingly. -//! -//! ## `Ty` lowering -//! Much of the `Ty` lowering is 1:1 with Chalk. (Or will be eventually). A -//! helpful table for what types lower to what can be found in the -//! [Chalk book](https://rust-lang.github.io/chalk/book/types/rust_types.html). -//! The most notable difference lies with `Param`s. To convert from rustc to -//! Chalk, we eagerly and deeply convert `Param`s to placeholders (in goals) or -//! bound variables (for clause generation through functions in `db`). -//! -//! ## `Region` lowering -//! Regions are handled in rustc and Chalk is quite differently. In rustc, there -//! is a difference between "early bound" and "late bound" regions, where only -//! the late bound regions have a `DebruijnIndex`. Moreover, in Chalk all -//! regions (Lifetimes) have an associated index. In rustc, only `BrAnon`s have -//! an index, whereas `BrNamed` don't. In order to lower regions to Chalk, we -//! convert all regions into `BrAnon` late-bound regions. -//! -//! ## `Const` lowering -//! Chalk doesn't handle consts currently, so consts are currently lowered to -//! an empty tuple. -//! -//! ## Bound variable collection -//! Another difference between rustc and Chalk lies in the handling of binders. -//! Chalk requires that we store the bound parameter kinds, whereas rustc does -//! not. To lower anything wrapped in a `Binder`, we first deeply find any bound -//! variables from the current `Binder`. - -use rustc_ast::ast; -use rustc_middle::traits::{ChalkEnvironmentAndGoal, ChalkRustInterner as RustInterner}; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; -use rustc_middle::ty::{ - self, Binder, Region, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, - TypeSuperVisitable, TypeVisitable, TypeVisitor, -}; -use rustc_span::def_id::DefId; - -use chalk_ir::{FnSig, ForeignDefId}; -use rustc_hir::Unsafety; -use std::collections::btree_map::{BTreeMap, Entry}; -use std::ops::ControlFlow; - -/// Essentially an `Into` with a `&RustInterner` parameter -pub(crate) trait LowerInto<'tcx, T> { - /// Lower a rustc construct (e.g., `ty::TraitPredicate`) to a chalk type, consuming `self`. - fn lower_into(self, interner: RustInterner<'tcx>) -> T; -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::Substitution>> for SubstsRef<'tcx> { - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> chalk_ir::Substitution> { - chalk_ir::Substitution::from_iter(interner, self.iter().map(|s| s.lower_into(interner))) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::Substitution>> - for &'tcx ty::List> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> chalk_ir::Substitution> { - chalk_ir::Substitution::from_iter( - interner, - self.iter().map(|ty| GenericArg::from(ty).lower_into(interner)), - ) - } -} - -impl<'tcx> LowerInto<'tcx, SubstsRef<'tcx>> for &chalk_ir::Substitution> { - fn lower_into(self, interner: RustInterner<'tcx>) -> SubstsRef<'tcx> { - interner - .tcx - .mk_substs_from_iter(self.iter(interner).map(|subst| subst.lower_into(interner))) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::InEnvironment>>> - for ChalkEnvironmentAndGoal<'tcx> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> chalk_ir::InEnvironment>> { - let clauses = self.environment.into_iter().map(|predicate| { - let (predicate, binders, _named_regions) = - collect_bound_vars(interner, interner.tcx, predicate.kind()); - let consequence = match predicate { - ty::PredicateKind::TypeWellFormedFromEnv(ty) => { - chalk_ir::DomainGoal::FromEnv(chalk_ir::FromEnv::Ty(ty.lower_into(interner))) - } - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => { - chalk_ir::DomainGoal::FromEnv(chalk_ir::FromEnv::Trait( - predicate.trait_ref.lower_into(interner), - )) - } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => { - chalk_ir::DomainGoal::Holds(chalk_ir::WhereClause::LifetimeOutlives( - chalk_ir::LifetimeOutlives { - a: predicate.0.lower_into(interner), - b: predicate.1.lower_into(interner), - }, - )) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => { - chalk_ir::DomainGoal::Holds(chalk_ir::WhereClause::TypeOutlives( - chalk_ir::TypeOutlives { - ty: predicate.0.lower_into(interner), - lifetime: predicate.1.lower_into(interner), - }, - )) - } - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => { - chalk_ir::DomainGoal::Holds(chalk_ir::WhereClause::AliasEq( - predicate.lower_into(interner), - )) - } - ty::PredicateKind::WellFormed(arg) => match arg.unpack() { - ty::GenericArgKind::Type(ty) => chalk_ir::DomainGoal::WellFormed( - chalk_ir::WellFormed::Ty(ty.lower_into(interner)), - ), - // FIXME(chalk): we need to change `WellFormed` in Chalk to take a `GenericArg` - _ => chalk_ir::DomainGoal::WellFormed(chalk_ir::WellFormed::Ty( - interner.tcx.types.unit.lower_into(interner), - )), - }, - ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::ConstEquate(..) => bug!("unexpected predicate {}", predicate), - }; - let value = chalk_ir::ProgramClauseImplication { - consequence, - conditions: chalk_ir::Goals::empty(interner), - priority: chalk_ir::ClausePriority::High, - constraints: chalk_ir::Constraints::empty(interner), - }; - chalk_ir::ProgramClauseData(chalk_ir::Binders::new(binders, value)).intern(interner) - }); - - let goal: chalk_ir::GoalData> = self.goal.lower_into(interner); - chalk_ir::InEnvironment { - environment: chalk_ir::Environment { - clauses: chalk_ir::ProgramClauses::from_iter(interner, clauses), - }, - goal: goal.intern(interner), - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::GoalData>> for ty::Predicate<'tcx> { - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::GoalData> { - let (predicate, binders, _named_regions) = - collect_bound_vars(interner, interner.tcx, self.kind()); - - let value = match predicate { - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => { - chalk_ir::GoalData::DomainGoal(chalk_ir::DomainGoal::Holds( - chalk_ir::WhereClause::Implemented(predicate.trait_ref.lower_into(interner)), - )) - } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => { - chalk_ir::GoalData::DomainGoal(chalk_ir::DomainGoal::Holds( - chalk_ir::WhereClause::LifetimeOutlives(chalk_ir::LifetimeOutlives { - a: predicate.0.lower_into(interner), - b: predicate.1.lower_into(interner), - }), - )) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => { - chalk_ir::GoalData::DomainGoal(chalk_ir::DomainGoal::Holds( - chalk_ir::WhereClause::TypeOutlives(chalk_ir::TypeOutlives { - ty: predicate.0.lower_into(interner), - lifetime: predicate.1.lower_into(interner), - }), - )) - } - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => { - chalk_ir::GoalData::DomainGoal(chalk_ir::DomainGoal::Holds( - chalk_ir::WhereClause::AliasEq(predicate.lower_into(interner)), - )) - } - ty::PredicateKind::WellFormed(arg) => match arg.unpack() { - GenericArgKind::Type(ty) => match ty.kind() { - // FIXME(chalk): In Chalk, a placeholder is WellFormed if it - // `FromEnv`. However, when we "lower" Params, we don't update - // the environment. - ty::Placeholder(..) => { - chalk_ir::GoalData::All(chalk_ir::Goals::empty(interner)) - } - - _ => chalk_ir::GoalData::DomainGoal(chalk_ir::DomainGoal::WellFormed( - chalk_ir::WellFormed::Ty(ty.lower_into(interner)), - )), - }, - // FIXME(chalk): handle well formed consts - GenericArgKind::Const(..) => { - chalk_ir::GoalData::All(chalk_ir::Goals::empty(interner)) - } - GenericArgKind::Lifetime(lt) => bug!("unexpected well formed predicate: {:?}", lt), - }, - - ty::PredicateKind::ObjectSafe(t) => chalk_ir::GoalData::DomainGoal( - chalk_ir::DomainGoal::ObjectSafe(chalk_ir::TraitId(t)), - ), - - ty::PredicateKind::Subtype(ty::SubtypePredicate { a, b, a_is_expected: _ }) => { - chalk_ir::GoalData::SubtypeGoal(chalk_ir::SubtypeGoal { - a: a.lower_into(interner), - b: b.lower_into(interner), - }) - } - - // FIXME(chalk): other predicates - // - // We can defer this, but ultimately we'll want to express - // some of these in terms of chalk operations. - ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::ConstEquate(..) => { - chalk_ir::GoalData::All(chalk_ir::Goals::empty(interner)) - } - ty::PredicateKind::TypeWellFormedFromEnv(ty) => chalk_ir::GoalData::DomainGoal( - chalk_ir::DomainGoal::FromEnv(chalk_ir::FromEnv::Ty(ty.lower_into(interner))), - ), - }; - - chalk_ir::GoalData::Quantified( - chalk_ir::QuantifierKind::ForAll, - chalk_ir::Binders::new(binders, value.intern(interner)), - ) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::TraitRef>> - for rustc_middle::ty::TraitRef<'tcx> -{ - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::TraitRef> { - chalk_ir::TraitRef { - trait_id: chalk_ir::TraitId(self.def_id), - substitution: self.substs.lower_into(interner), - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::AliasEq>> - for rustc_middle::ty::ProjectionPredicate<'tcx> -{ - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::AliasEq> { - // FIXME(associated_const_equality): teach chalk about terms for alias eq. - chalk_ir::AliasEq { - ty: self.term.ty().unwrap().lower_into(interner), - alias: chalk_ir::AliasTy::Projection(chalk_ir::ProjectionTy { - associated_ty_id: chalk_ir::AssocTypeId(self.projection_ty.def_id), - substitution: self.projection_ty.substs.lower_into(interner), - }), - } - } -} - -/* -// FIXME(...): Where do I add this to Chalk? I can't find it in the rustc repo anywhere. -impl<'tcx> LowerInto<'tcx, chalk_ir::Term>> for rustc_middle::ty::Term<'tcx> { - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::Term> { - match self { - ty::Term::Ty(ty) => ty.lower_into(interner).into(), - ty::Term::Const(c) => c.lower_into(interner).into(), - } - } -} -*/ - -impl<'tcx> LowerInto<'tcx, chalk_ir::Ty>> for Ty<'tcx> { - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::Ty> { - let int = |i| chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Int(i)); - let uint = |i| chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Uint(i)); - let float = |f| chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Float(f)); - - match *self.kind() { - ty::Bool => chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Bool), - ty::Char => chalk_ir::TyKind::Scalar(chalk_ir::Scalar::Char), - ty::Int(ty) => match ty { - ty::IntTy::Isize => int(chalk_ir::IntTy::Isize), - ty::IntTy::I8 => int(chalk_ir::IntTy::I8), - ty::IntTy::I16 => int(chalk_ir::IntTy::I16), - ty::IntTy::I32 => int(chalk_ir::IntTy::I32), - ty::IntTy::I64 => int(chalk_ir::IntTy::I64), - ty::IntTy::I128 => int(chalk_ir::IntTy::I128), - }, - ty::Uint(ty) => match ty { - ty::UintTy::Usize => uint(chalk_ir::UintTy::Usize), - ty::UintTy::U8 => uint(chalk_ir::UintTy::U8), - ty::UintTy::U16 => uint(chalk_ir::UintTy::U16), - ty::UintTy::U32 => uint(chalk_ir::UintTy::U32), - ty::UintTy::U64 => uint(chalk_ir::UintTy::U64), - ty::UintTy::U128 => uint(chalk_ir::UintTy::U128), - }, - ty::Float(ty) => match ty { - ty::FloatTy::F32 => float(chalk_ir::FloatTy::F32), - ty::FloatTy::F64 => float(chalk_ir::FloatTy::F64), - }, - ty::Adt(def, substs) => { - chalk_ir::TyKind::Adt(chalk_ir::AdtId(def), substs.lower_into(interner)) - } - ty::Foreign(def_id) => chalk_ir::TyKind::Foreign(ForeignDefId(def_id)), - ty::Str => chalk_ir::TyKind::Str, - ty::Array(ty, len) => { - chalk_ir::TyKind::Array(ty.lower_into(interner), len.lower_into(interner)) - } - ty::Slice(ty) => chalk_ir::TyKind::Slice(ty.lower_into(interner)), - - ty::RawPtr(ptr) => { - chalk_ir::TyKind::Raw(ptr.mutbl.lower_into(interner), ptr.ty.lower_into(interner)) - } - ty::Ref(region, ty, mutability) => chalk_ir::TyKind::Ref( - mutability.lower_into(interner), - region.lower_into(interner), - ty.lower_into(interner), - ), - ty::FnDef(def_id, substs) => { - chalk_ir::TyKind::FnDef(chalk_ir::FnDefId(def_id), substs.lower_into(interner)) - } - ty::FnPtr(sig) => { - let (inputs_and_outputs, binders, _named_regions) = - collect_bound_vars(interner, interner.tcx, sig.inputs_and_output()); - chalk_ir::TyKind::Function(chalk_ir::FnPointer { - num_binders: binders.len(interner), - sig: sig.lower_into(interner), - substitution: chalk_ir::FnSubst(chalk_ir::Substitution::from_iter( - interner, - inputs_and_outputs.iter().map(|ty| { - chalk_ir::GenericArgData::Ty(ty.lower_into(interner)).intern(interner) - }), - )), - }) - } - // FIXME(dyn-star): handle the dynamic kind (dyn or dyn*) - ty::Dynamic(predicates, region, _kind) => chalk_ir::TyKind::Dyn(chalk_ir::DynTy { - bounds: predicates.lower_into(interner), - lifetime: region.lower_into(interner), - }), - ty::Closure(def_id, substs) => { - chalk_ir::TyKind::Closure(chalk_ir::ClosureId(def_id), substs.lower_into(interner)) - } - ty::Generator(def_id, substs, _) => chalk_ir::TyKind::Generator( - chalk_ir::GeneratorId(def_id), - substs.lower_into(interner), - ), - ty::GeneratorWitness(_) => unimplemented!(), - ty::GeneratorWitnessMIR(..) => unimplemented!(), - ty::Never => chalk_ir::TyKind::Never, - ty::Tuple(types) => chalk_ir::TyKind::Tuple(types.len(), types.lower_into(interner)), - ty::Alias(ty::Projection, ty::AliasTy { def_id, substs, .. }) => { - chalk_ir::TyKind::Alias(chalk_ir::AliasTy::Projection(chalk_ir::ProjectionTy { - associated_ty_id: chalk_ir::AssocTypeId(def_id), - substitution: substs.lower_into(interner), - })) - } - ty::Alias(ty::Inherent, _) => unimplemented!(), - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - chalk_ir::TyKind::Alias(chalk_ir::AliasTy::Opaque(chalk_ir::OpaqueTy { - opaque_ty_id: chalk_ir::OpaqueTyId(def_id), - substitution: substs.lower_into(interner), - })) - } - // This should have been done eagerly prior to this, and all Params - // should have been substituted to placeholders - ty::Param(_) => panic!("Lowering Param when not expected."), - ty::Bound(db, bound) => chalk_ir::TyKind::BoundVar(chalk_ir::BoundVar::new( - chalk_ir::DebruijnIndex::new(db.as_u32()), - bound.var.index(), - )), - ty::Placeholder(_placeholder) => { - chalk_ir::TyKind::Placeholder(chalk_ir::PlaceholderIndex { - ui: chalk_ir::UniverseIndex { counter: _placeholder.universe.as_usize() }, - idx: _placeholder.bound.var.as_usize(), - }) - } - ty::Infer(_infer) => unimplemented!(), - ty::Error(_) => chalk_ir::TyKind::Error, - } - .intern(interner) - } -} - -impl<'tcx> LowerInto<'tcx, Ty<'tcx>> for &chalk_ir::Ty> { - fn lower_into(self, interner: RustInterner<'tcx>) -> Ty<'tcx> { - use chalk_ir::TyKind; - - let kind = match self.kind(interner) { - TyKind::Adt(struct_id, substitution) => { - ty::Adt(struct_id.0, substitution.lower_into(interner)) - } - TyKind::Scalar(scalar) => match scalar { - chalk_ir::Scalar::Bool => ty::Bool, - chalk_ir::Scalar::Char => ty::Char, - chalk_ir::Scalar::Int(int_ty) => match int_ty { - chalk_ir::IntTy::Isize => ty::Int(ty::IntTy::Isize), - chalk_ir::IntTy::I8 => ty::Int(ty::IntTy::I8), - chalk_ir::IntTy::I16 => ty::Int(ty::IntTy::I16), - chalk_ir::IntTy::I32 => ty::Int(ty::IntTy::I32), - chalk_ir::IntTy::I64 => ty::Int(ty::IntTy::I64), - chalk_ir::IntTy::I128 => ty::Int(ty::IntTy::I128), - }, - chalk_ir::Scalar::Uint(int_ty) => match int_ty { - chalk_ir::UintTy::Usize => ty::Uint(ty::UintTy::Usize), - chalk_ir::UintTy::U8 => ty::Uint(ty::UintTy::U8), - chalk_ir::UintTy::U16 => ty::Uint(ty::UintTy::U16), - chalk_ir::UintTy::U32 => ty::Uint(ty::UintTy::U32), - chalk_ir::UintTy::U64 => ty::Uint(ty::UintTy::U64), - chalk_ir::UintTy::U128 => ty::Uint(ty::UintTy::U128), - }, - chalk_ir::Scalar::Float(float_ty) => match float_ty { - chalk_ir::FloatTy::F32 => ty::Float(ty::FloatTy::F32), - chalk_ir::FloatTy::F64 => ty::Float(ty::FloatTy::F64), - }, - }, - TyKind::Array(ty, c) => { - let ty = ty.lower_into(interner); - let c = c.lower_into(interner); - ty::Array(ty, c) - } - TyKind::FnDef(id, substitution) => ty::FnDef(id.0, substitution.lower_into(interner)), - TyKind::Closure(closure, substitution) => { - ty::Closure(closure.0, substitution.lower_into(interner)) - } - TyKind::Generator(generator, substitution) => ty::Generator( - generator.0, - substitution.lower_into(interner), - ast::Movability::Static, - ), - TyKind::GeneratorWitness(..) => unimplemented!(), - TyKind::Never => ty::Never, - TyKind::Tuple(_len, substitution) => { - ty::Tuple(substitution.lower_into(interner).into_type_list(interner.tcx)) - } - TyKind::Slice(ty) => ty::Slice(ty.lower_into(interner)), - TyKind::Raw(mutbl, ty) => ty::RawPtr(ty::TypeAndMut { - ty: ty.lower_into(interner), - mutbl: mutbl.lower_into(interner), - }), - TyKind::Ref(mutbl, lifetime, ty) => ty::Ref( - lifetime.lower_into(interner), - ty.lower_into(interner), - mutbl.lower_into(interner), - ), - TyKind::Str => ty::Str, - TyKind::OpaqueType(opaque_ty, substitution) => ty::Alias( - ty::Opaque, - interner.tcx.mk_alias_ty(opaque_ty.0, substitution.lower_into(interner)), - ), - TyKind::AssociatedType(assoc_ty, substitution) => ty::Alias( - ty::Projection, - interner.tcx.mk_alias_ty(assoc_ty.0, substitution.lower_into(interner)), - ), - TyKind::Foreign(def_id) => ty::Foreign(def_id.0), - TyKind::Error => return interner.tcx.ty_error_misc(), - TyKind::Alias(alias_ty) => match alias_ty { - chalk_ir::AliasTy::Projection(projection) => ty::Alias( - ty::Projection, - interner.tcx.mk_alias_ty( - projection.associated_ty_id.0, - projection.substitution.lower_into(interner), - ), - ), - chalk_ir::AliasTy::Opaque(opaque) => ty::Alias( - ty::Opaque, - interner.tcx.mk_alias_ty( - opaque.opaque_ty_id.0, - opaque.substitution.lower_into(interner), - ), - ), - }, - TyKind::Function(_quantified_ty) => unimplemented!(), - TyKind::BoundVar(bound) => ty::Bound( - ty::DebruijnIndex::from_usize(bound.debruijn.depth() as usize), - ty::BoundTy { - var: ty::BoundVar::from_usize(bound.index), - kind: ty::BoundTyKind::Anon, - }, - ), - TyKind::Placeholder(placeholder) => ty::Placeholder(ty::Placeholder { - universe: ty::UniverseIndex::from_usize(placeholder.ui.counter), - bound: ty::BoundTy { - var: ty::BoundVar::from_usize(placeholder.idx), - kind: ty::BoundTyKind::Anon, - }, - }), - TyKind::InferenceVar(_, _) => unimplemented!(), - TyKind::Dyn(_) => unimplemented!(), - }; - interner.tcx.mk_ty_from_kind(kind) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::Lifetime>> for Region<'tcx> { - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::Lifetime> { - match *self { - ty::ReEarlyBound(_) => { - panic!("Should have already been substituted."); - } - ty::ReError(_) => { - panic!("Error lifetime should not have already been lowered."); - } - ty::ReLateBound(db, br) => chalk_ir::LifetimeData::BoundVar(chalk_ir::BoundVar::new( - chalk_ir::DebruijnIndex::new(db.as_u32()), - br.var.as_usize(), - )) - .intern(interner), - ty::ReFree(_) => unimplemented!(), - ty::ReStatic => chalk_ir::LifetimeData::Static.intern(interner), - ty::ReVar(_) => unimplemented!(), - ty::RePlaceholder(placeholder_region) => { - chalk_ir::LifetimeData::Placeholder(chalk_ir::PlaceholderIndex { - ui: chalk_ir::UniverseIndex { counter: placeholder_region.universe.index() }, - idx: 0, // FIXME: This `idx: 0` is sus. - }) - .intern(interner) - } - ty::ReErased => chalk_ir::LifetimeData::Erased.intern(interner), - } - } -} - -impl<'tcx> LowerInto<'tcx, Region<'tcx>> for &chalk_ir::Lifetime> { - fn lower_into(self, interner: RustInterner<'tcx>) -> Region<'tcx> { - let tcx = interner.tcx; - match self.data(interner) { - chalk_ir::LifetimeData::BoundVar(var) => tcx.mk_re_late_bound( - ty::DebruijnIndex::from_u32(var.debruijn.depth()), - ty::BoundRegion { - var: ty::BoundVar::from_usize(var.index), - kind: ty::BrAnon(None), - }, - ), - chalk_ir::LifetimeData::InferenceVar(_var) => unimplemented!(), - chalk_ir::LifetimeData::Placeholder(p) => tcx.mk_re_placeholder(ty::Placeholder { - universe: ty::UniverseIndex::from_usize(p.ui.counter), - bound: ty::BoundRegion { - var: ty::BoundVar::from_usize(p.idx), - kind: ty::BoundRegionKind::BrAnon(None), - }, - }), - chalk_ir::LifetimeData::Static => tcx.lifetimes.re_static, - chalk_ir::LifetimeData::Erased => tcx.lifetimes.re_erased, - chalk_ir::LifetimeData::Phantom(void, _) => match *void {}, - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::Const>> for ty::Const<'tcx> { - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::Const> { - let ty = self.ty().lower_into(interner); - let value = match self.kind() { - ty::ConstKind::Value(val) => { - chalk_ir::ConstValue::Concrete(chalk_ir::ConcreteConst { interned: val }) - } - ty::ConstKind::Bound(db, bound) => chalk_ir::ConstValue::BoundVar( - chalk_ir::BoundVar::new(chalk_ir::DebruijnIndex::new(db.as_u32()), bound.index()), - ), - _ => unimplemented!("Const not implemented. {:?}", self), - }; - chalk_ir::ConstData { ty, value }.intern(interner) - } -} - -impl<'tcx> LowerInto<'tcx, ty::Const<'tcx>> for &chalk_ir::Const> { - fn lower_into(self, interner: RustInterner<'tcx>) -> ty::Const<'tcx> { - let data = self.data(interner); - let ty = data.ty.lower_into(interner); - let kind = match data.value { - chalk_ir::ConstValue::BoundVar(var) => ty::ConstKind::Bound( - ty::DebruijnIndex::from_u32(var.debruijn.depth()), - ty::BoundVar::from_u32(var.index as u32), - ), - chalk_ir::ConstValue::InferenceVar(_var) => unimplemented!(), - chalk_ir::ConstValue::Placeholder(_p) => unimplemented!(), - chalk_ir::ConstValue::Concrete(c) => ty::ConstKind::Value(c.interned), - }; - interner.tcx.mk_const(kind, ty) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::GenericArg>> for GenericArg<'tcx> { - fn lower_into(self, interner: RustInterner<'tcx>) -> chalk_ir::GenericArg> { - match self.unpack() { - ty::subst::GenericArgKind::Type(ty) => { - chalk_ir::GenericArgData::Ty(ty.lower_into(interner)) - } - ty::subst::GenericArgKind::Lifetime(lifetime) => { - chalk_ir::GenericArgData::Lifetime(lifetime.lower_into(interner)) - } - ty::subst::GenericArgKind::Const(c) => { - chalk_ir::GenericArgData::Const(c.lower_into(interner)) - } - } - .intern(interner) - } -} - -impl<'tcx> LowerInto<'tcx, ty::subst::GenericArg<'tcx>> - for &chalk_ir::GenericArg> -{ - fn lower_into(self, interner: RustInterner<'tcx>) -> ty::subst::GenericArg<'tcx> { - match self.data(interner) { - chalk_ir::GenericArgData::Ty(ty) => { - let t: Ty<'tcx> = ty.lower_into(interner); - t.into() - } - chalk_ir::GenericArgData::Lifetime(lifetime) => { - let r: Region<'tcx> = lifetime.lower_into(interner); - r.into() - } - chalk_ir::GenericArgData::Const(c) => { - let c: ty::Const<'tcx> = c.lower_into(interner); - c.into() - } - } - } -} - -// We lower into an Option here since there are some predicates which Chalk -// doesn't have a representation for yet (as a `WhereClause`), but are so common -// that we just are accepting the unsoundness for now. The `Option` will -// eventually be removed. -impl<'tcx> LowerInto<'tcx, Option>>> - for ty::Predicate<'tcx> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> Option>> { - let (predicate, binders, _named_regions) = - collect_bound_vars(interner, interner.tcx, self.kind()); - let value = match predicate { - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => { - Some(chalk_ir::WhereClause::Implemented(predicate.trait_ref.lower_into(interner))) - } - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => { - Some(chalk_ir::WhereClause::LifetimeOutlives(chalk_ir::LifetimeOutlives { - a: predicate.0.lower_into(interner), - b: predicate.1.lower_into(interner), - })) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => { - Some(chalk_ir::WhereClause::TypeOutlives(chalk_ir::TypeOutlives { - ty: predicate.0.lower_into(interner), - lifetime: predicate.1.lower_into(interner), - })) - } - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => { - Some(chalk_ir::WhereClause::AliasEq(predicate.lower_into(interner))) - } - ty::PredicateKind::WellFormed(_ty) => None, - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => None, - - ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("unexpected predicate {self}") - } - }; - value.map(|value| chalk_ir::Binders::new(binders, value)) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::Binders>>> - for &'tcx ty::List> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> chalk_ir::Binders>> { - // `Self` has one binder: - // Binder<&'tcx ty::List>> - // The return type has two: - // Binders<&[Binders>]> - // This means that any variables that are escaping `self` need to be - // shifted in by one so that they are still escaping. - let predicates = ty::fold::shift_vars(interner.tcx, self, 1); - - let self_ty = interner.tcx.mk_bound( - // This is going to be wrapped in a binder - ty::DebruijnIndex::from_usize(1), - ty::BoundTy { var: ty::BoundVar::from_usize(0), kind: ty::BoundTyKind::Anon }, - ); - let where_clauses = predicates.into_iter().map(|predicate| { - let (predicate, binders, _named_regions) = - collect_bound_vars(interner, interner.tcx, predicate); - match predicate { - ty::ExistentialPredicate::Trait(ty::ExistentialTraitRef { def_id, substs }) => { - chalk_ir::Binders::new( - binders.clone(), - chalk_ir::WhereClause::Implemented(chalk_ir::TraitRef { - trait_id: chalk_ir::TraitId(def_id), - substitution: interner - .tcx - .mk_substs_trait(self_ty, substs) - .lower_into(interner), - }), - ) - } - ty::ExistentialPredicate::Projection(predicate) => chalk_ir::Binders::new( - binders.clone(), - chalk_ir::WhereClause::AliasEq(chalk_ir::AliasEq { - alias: chalk_ir::AliasTy::Projection(chalk_ir::ProjectionTy { - associated_ty_id: chalk_ir::AssocTypeId(predicate.def_id), - substitution: interner - .tcx - .mk_substs_trait(self_ty, predicate.substs) - .lower_into(interner), - }), - // FIXME(associated_const_equality): teach chalk about terms for alias eq. - ty: predicate.term.ty().unwrap().lower_into(interner), - }), - ), - ty::ExistentialPredicate::AutoTrait(def_id) => chalk_ir::Binders::new( - binders.clone(), - chalk_ir::WhereClause::Implemented(chalk_ir::TraitRef { - trait_id: chalk_ir::TraitId(def_id), - substitution: interner - .tcx - .mk_substs_trait(self_ty, []) - .lower_into(interner), - }), - ), - } - }); - - // Binder for the bound variable representing the concrete underlying type. - let existential_binder = chalk_ir::VariableKinds::from1( - interner, - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General), - ); - let value = chalk_ir::QuantifiedWhereClauses::from_iter(interner, where_clauses); - chalk_ir::Binders::new(existential_binder, value) - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::FnSig>> - for ty::Binder<'tcx, ty::FnSig<'tcx>> -{ - fn lower_into(self, _interner: RustInterner<'_>) -> FnSig> { - chalk_ir::FnSig { - abi: self.abi(), - safety: match self.unsafety() { - Unsafety::Normal => chalk_ir::Safety::Safe, - Unsafety::Unsafe => chalk_ir::Safety::Unsafe, - }, - variadic: self.c_variadic(), - } - } -} - -// We lower into an Option here since there are some predicates which Chalk -// doesn't have a representation for yet (as an `InlineBound`). The `Option` will -// eventually be removed. -impl<'tcx> LowerInto<'tcx, Option>>> - for ty::Predicate<'tcx> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> Option>> { - let (predicate, binders, _named_regions) = - collect_bound_vars(interner, interner.tcx, self.kind()); - match predicate { - ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => { - Some(chalk_ir::Binders::new( - binders, - chalk_solve::rust_ir::InlineBound::TraitBound( - predicate.trait_ref.lower_into(interner), - ), - )) - } - ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => { - Some(chalk_ir::Binders::new( - binders, - chalk_solve::rust_ir::InlineBound::AliasEqBound(predicate.lower_into(interner)), - )) - } - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(_predicate)) => None, - ty::PredicateKind::WellFormed(_ty) => None, - ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) => None, - - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => { - bug!("unexpected predicate {}", &self) - } - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_solve::rust_ir::TraitBound>> - for ty::TraitRef<'tcx> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> chalk_solve::rust_ir::TraitBound> { - chalk_solve::rust_ir::TraitBound { - trait_id: chalk_ir::TraitId(self.def_id), - args_no_self: self.substs[1..].iter().map(|arg| arg.lower_into(interner)).collect(), - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_ir::Mutability> for ast::Mutability { - fn lower_into(self, _interner: RustInterner<'tcx>) -> chalk_ir::Mutability { - match self { - rustc_ast::Mutability::Mut => chalk_ir::Mutability::Mut, - rustc_ast::Mutability::Not => chalk_ir::Mutability::Not, - } - } -} - -impl<'tcx> LowerInto<'tcx, ast::Mutability> for chalk_ir::Mutability { - fn lower_into(self, _interner: RustInterner<'tcx>) -> ast::Mutability { - match self { - chalk_ir::Mutability::Mut => ast::Mutability::Mut, - chalk_ir::Mutability::Not => ast::Mutability::Not, - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_solve::rust_ir::Polarity> for ty::ImplPolarity { - fn lower_into(self, _interner: RustInterner<'tcx>) -> chalk_solve::rust_ir::Polarity { - match self { - ty::ImplPolarity::Positive => chalk_solve::rust_ir::Polarity::Positive, - ty::ImplPolarity::Negative => chalk_solve::rust_ir::Polarity::Negative, - // FIXME(chalk) reservation impls - ty::ImplPolarity::Reservation => chalk_solve::rust_ir::Polarity::Negative, - } - } -} -impl<'tcx> LowerInto<'tcx, chalk_ir::Variance> for ty::Variance { - fn lower_into(self, _interner: RustInterner<'tcx>) -> chalk_ir::Variance { - match self { - ty::Variance::Covariant => chalk_ir::Variance::Covariant, - ty::Variance::Invariant => chalk_ir::Variance::Invariant, - ty::Variance::Contravariant => chalk_ir::Variance::Contravariant, - ty::Variance::Bivariant => unimplemented!(), - } - } -} - -impl<'tcx> LowerInto<'tcx, chalk_solve::rust_ir::AliasEqBound>> - for ty::ProjectionPredicate<'tcx> -{ - fn lower_into( - self, - interner: RustInterner<'tcx>, - ) -> chalk_solve::rust_ir::AliasEqBound> { - let (trait_ref, own_substs) = self.projection_ty.trait_ref_and_own_substs(interner.tcx); - chalk_solve::rust_ir::AliasEqBound { - trait_bound: trait_ref.lower_into(interner), - associated_ty_id: chalk_ir::AssocTypeId(self.projection_ty.def_id), - parameters: own_substs.iter().map(|arg| arg.lower_into(interner)).collect(), - value: self.term.ty().unwrap().lower_into(interner), - } - } -} - -/// To collect bound vars, we have to do two passes. In the first pass, we -/// collect all `BoundRegionKind`s and `ty::Bound`s. In the second pass, we then -/// replace `BrNamed` into `BrAnon`. The two separate passes are important, -/// since we can only replace `BrNamed` with `BrAnon`s with indices *after* all -/// "real" `BrAnon`s. -/// -/// It's important to note that because of prior substitution, we may have -/// late-bound regions, even outside of fn contexts, since this is the best way -/// to prep types for chalk lowering. -pub(crate) fn collect_bound_vars<'tcx, T: TypeFoldable>>( - interner: RustInterner<'tcx>, - tcx: TyCtxt<'tcx>, - ty: Binder<'tcx, T>, -) -> (T, chalk_ir::VariableKinds>, BTreeMap) { - let mut bound_vars_collector = BoundVarsCollector::new(); - ty.as_ref().skip_binder().visit_with(&mut bound_vars_collector); - let mut parameters = bound_vars_collector.parameters; - let named_parameters: BTreeMap = bound_vars_collector - .named_parameters - .into_iter() - .enumerate() - .map(|(i, def_id)| (def_id, (i + parameters.len()) as u32)) - .collect(); - - let mut bound_var_substitutor = NamedBoundVarSubstitutor::new(tcx, &named_parameters); - let new_ty = ty.skip_binder().fold_with(&mut bound_var_substitutor); - - for var in named_parameters.values() { - parameters.insert(*var, chalk_ir::VariableKind::Lifetime); - } - - (0..parameters.len()).for_each(|i| { - parameters - .get(&(i as u32)) - .or_else(|| bug!("Skipped bound var index: parameters={:?}", parameters)); - }); - - let binders = chalk_ir::VariableKinds::from_iter(interner, parameters.into_values()); - - (new_ty, binders, named_parameters) -} - -pub(crate) struct BoundVarsCollector<'tcx> { - binder_index: ty::DebruijnIndex, - pub(crate) parameters: BTreeMap>>, - pub(crate) named_parameters: Vec, -} - -impl<'tcx> BoundVarsCollector<'tcx> { - pub(crate) fn new() -> Self { - BoundVarsCollector { - binder_index: ty::INNERMOST, - parameters: BTreeMap::new(), - named_parameters: vec![], - } - } -} - -impl<'tcx> TypeVisitor> for BoundVarsCollector<'tcx> { - fn visit_binder>>( - &mut self, - t: &Binder<'tcx, T>, - ) -> ControlFlow { - self.binder_index.shift_in(1); - let result = t.super_visit_with(self); - self.binder_index.shift_out(1); - result - } - - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { - match *t.kind() { - ty::Bound(debruijn, bound_ty) if debruijn == self.binder_index => { - match self.parameters.entry(bound_ty.var.as_u32()) { - Entry::Vacant(entry) => { - entry.insert(chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)); - } - Entry::Occupied(entry) => match entry.get() { - chalk_ir::VariableKind::Ty(_) => {} - _ => panic!(), - }, - } - } - - _ => (), - }; - - t.super_visit_with(self) - } - - fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow { - match *r { - ty::ReLateBound(index, br) if index == self.binder_index => match br.kind { - ty::BoundRegionKind::BrNamed(def_id, _name) => { - if !self.named_parameters.iter().any(|d| *d == def_id) { - self.named_parameters.push(def_id); - } - } - - ty::BoundRegionKind::BrAnon(_) => match self.parameters.entry(br.var.as_u32()) { - Entry::Vacant(entry) => { - entry.insert(chalk_ir::VariableKind::Lifetime); - } - Entry::Occupied(entry) => match entry.get() { - chalk_ir::VariableKind::Lifetime => {} - _ => panic!(), - }, - }, - - ty::BoundRegionKind::BrEnv => unimplemented!(), - }, - - ty::ReEarlyBound(_re) => { - // FIXME(chalk): jackh726 - I think we should always have already - // substituted away `ReEarlyBound`s for `ReLateBound`s, but need to confirm. - unimplemented!(); - } - - _ => (), - }; - - ControlFlow::Continue(()) - } -} - -/// This is used to replace `BoundRegionKind::BrNamed` with `BoundRegionKind::BrAnon`. -/// Note: we assume that we will always have room for more bound vars. (i.e. we -/// won't ever hit the `u32` limit in `BrAnon`s). -struct NamedBoundVarSubstitutor<'a, 'tcx> { - tcx: TyCtxt<'tcx>, - binder_index: ty::DebruijnIndex, - named_parameters: &'a BTreeMap, -} - -impl<'a, 'tcx> NamedBoundVarSubstitutor<'a, 'tcx> { - fn new(tcx: TyCtxt<'tcx>, named_parameters: &'a BTreeMap) -> Self { - NamedBoundVarSubstitutor { tcx, binder_index: ty::INNERMOST, named_parameters } - } -} - -impl<'a, 'tcx> TypeFolder> for NamedBoundVarSubstitutor<'a, 'tcx> { - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn fold_binder>>( - &mut self, - t: Binder<'tcx, T>, - ) -> Binder<'tcx, T> { - self.binder_index.shift_in(1); - let result = t.super_fold_with(self); - self.binder_index.shift_out(1); - result - } - - fn fold_region(&mut self, r: Region<'tcx>) -> Region<'tcx> { - match *r { - ty::ReLateBound(index, br) if index == self.binder_index => match br.kind { - ty::BrNamed(def_id, _name) => match self.named_parameters.get(&def_id) { - Some(_) => { - let new_br = ty::BoundRegion { var: br.var, kind: ty::BrAnon(None) }; - return self.tcx.mk_re_late_bound(index, new_br); - } - None => panic!("Missing `BrNamed`."), - }, - ty::BrEnv => unimplemented!(), - ty::BrAnon(..) => {} - }, - _ => (), - }; - - r - } -} - -/// Used to substitute `Param`s with placeholders. We do this since Chalk -/// have a notion of `Param`s. -pub(crate) struct ParamsSubstitutor<'tcx> { - tcx: TyCtxt<'tcx>, - binder_index: ty::DebruijnIndex, - list: Vec, - next_ty_placeholder: usize, - pub(crate) params: rustc_data_structures::fx::FxHashMap, - pub(crate) named_regions: BTreeMap, -} - -impl<'tcx> ParamsSubstitutor<'tcx> { - pub(crate) fn new(tcx: TyCtxt<'tcx>, next_ty_placeholder: usize) -> Self { - ParamsSubstitutor { - tcx, - binder_index: ty::INNERMOST, - list: vec![], - next_ty_placeholder, - params: rustc_data_structures::fx::FxHashMap::default(), - named_regions: BTreeMap::default(), - } - } -} - -impl<'tcx> TypeFolder> for ParamsSubstitutor<'tcx> { - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn fold_binder>>( - &mut self, - t: Binder<'tcx, T>, - ) -> Binder<'tcx, T> { - self.binder_index.shift_in(1); - let result = t.super_fold_with(self); - self.binder_index.shift_out(1); - result - } - - fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - match *t.kind() { - ty::Param(param) => match self.list.iter().position(|r| r == ¶m) { - Some(idx) => self.tcx.mk_placeholder(ty::PlaceholderType { - universe: ty::UniverseIndex::from_usize(0), - bound: ty::BoundTy { - var: ty::BoundVar::from_usize(idx), - kind: ty::BoundTyKind::Anon, - }, - }), - None => { - self.list.push(param); - let idx = self.list.len() - 1 + self.next_ty_placeholder; - self.params.insert(idx as u32, param); - self.tcx.mk_placeholder(ty::PlaceholderType { - universe: ty::UniverseIndex::from_usize(0), - bound: ty::BoundTy { - var: ty::BoundVar::from_usize(idx), - kind: ty::BoundTyKind::Anon, - }, - }) - } - }, - _ => t.super_fold_with(self), - } - } - - fn fold_region(&mut self, r: Region<'tcx>) -> Region<'tcx> { - match *r { - // FIXME(chalk) - jackh726 - this currently isn't hit in any tests, - // since canonicalization will already change these to canonical - // variables (ty::ReLateBound). - ty::ReEarlyBound(_re) => match self.named_regions.get(&_re.def_id) { - Some(idx) => { - let br = ty::BoundRegion { - var: ty::BoundVar::from_u32(*idx), - kind: ty::BrAnon(None), - }; - self.tcx.mk_re_late_bound(self.binder_index, br) - } - None => { - let idx = self.named_regions.len() as u32; - let br = ty::BoundRegion { - var: ty::BoundVar::from_u32(idx), - kind: ty::BrAnon(None), - }; - self.named_regions.insert(_re.def_id, idx); - self.tcx.mk_re_late_bound(self.binder_index, br) - } - }, - - _ => r, - } - } -} - -pub(crate) struct ReverseParamsSubstitutor<'tcx> { - tcx: TyCtxt<'tcx>, - params: rustc_data_structures::fx::FxHashMap, -} - -impl<'tcx> ReverseParamsSubstitutor<'tcx> { - pub(crate) fn new( - tcx: TyCtxt<'tcx>, - params: rustc_data_structures::fx::FxHashMap, - ) -> Self { - Self { tcx, params } - } -} - -impl<'tcx> TypeFolder> for ReverseParamsSubstitutor<'tcx> { - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - match *t.kind() { - ty::Placeholder(ty::PlaceholderType { universe: ty::UniverseIndex::ROOT, bound }) => { - match self.params.get(&bound.var.as_u32()) { - Some(&ty::ParamTy { index, name }) => self.tcx.mk_ty_param(index, name), - None => t, - } - } - - _ => t.super_fold_with(self), - } - } -} - -/// Used to collect `Placeholder`s. -pub(crate) struct PlaceholdersCollector { - universe_index: ty::UniverseIndex, - pub(crate) next_ty_placeholder: usize, - pub(crate) next_anon_region_placeholder: u32, -} - -impl PlaceholdersCollector { - pub(crate) fn new() -> Self { - PlaceholdersCollector { - universe_index: ty::UniverseIndex::ROOT, - next_ty_placeholder: 0, - next_anon_region_placeholder: 0, - } - } -} - -impl<'tcx> TypeVisitor> for PlaceholdersCollector { - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { - match t.kind() { - ty::Placeholder(p) if p.universe == self.universe_index => { - self.next_ty_placeholder = self.next_ty_placeholder.max(p.bound.var.as_usize() + 1); - } - - _ => (), - }; - - t.super_visit_with(self) - } - - fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow { - match *r { - ty::RePlaceholder(p) if p.universe == self.universe_index => { - if let ty::BoundRegionKind::BrAnon(_) = p.bound.kind { - self.next_anon_region_placeholder = - self.next_anon_region_placeholder.max(p.bound.var.as_u32()); - } - // FIXME: This doesn't seem to handle BrNamed at all? - } - - _ => (), - }; - - ControlFlow::Continue(()) - } -} diff --git a/compiler/rustc_traits/src/chalk/mod.rs b/compiler/rustc_traits/src/chalk/mod.rs deleted file mode 100644 index 8834449c9a458..0000000000000 --- a/compiler/rustc_traits/src/chalk/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Calls `chalk-solve` to solve a `ty::Predicate` -//! -//! In order to call `chalk-solve`, this file must convert a `CanonicalChalkEnvironmentAndGoal` into -//! a Chalk uncanonical goal. It then calls Chalk, and converts the answer back into rustc solution. - -pub(crate) mod db; -pub(crate) mod lowering; - -use rustc_middle::infer::canonical::{CanonicalTyVarKind, CanonicalVarKind}; -use rustc_middle::query::Providers; -use rustc_middle::traits::ChalkRustInterner; -use rustc_middle::ty::{self, TyCtxt, TypeFoldable, TypeVisitable}; - -use rustc_infer::infer::canonical::{ - Canonical, CanonicalVarValues, Certainty, QueryRegionConstraints, QueryResponse, -}; -use rustc_infer::traits::{self, CanonicalChalkEnvironmentAndGoal}; - -use crate::chalk::db::RustIrDatabase as ChalkRustIrDatabase; -use crate::chalk::lowering::LowerInto; -use crate::chalk::lowering::{ParamsSubstitutor, PlaceholdersCollector, ReverseParamsSubstitutor}; - -use chalk_solve::Solution; - -pub(crate) fn provide(p: &mut Providers) { - *p = Providers { evaluate_goal, ..*p }; -} - -pub(crate) fn evaluate_goal<'tcx>( - tcx: TyCtxt<'tcx>, - obligation: CanonicalChalkEnvironmentAndGoal<'tcx>, -) -> Result<&'tcx Canonical<'tcx, QueryResponse<'tcx, ()>>, traits::query::NoSolution> { - let interner = ChalkRustInterner { tcx }; - - // Chalk doesn't have a notion of `Params`, so instead we use placeholders. - let mut placeholders_collector = PlaceholdersCollector::new(); - obligation.visit_with(&mut placeholders_collector); - - let mut params_substitutor = - ParamsSubstitutor::new(tcx, placeholders_collector.next_ty_placeholder); - let obligation = obligation.fold_with(&mut params_substitutor); - let params = params_substitutor.params; - - let max_universe = obligation.max_universe.index(); - - let lowered_goal: chalk_ir::UCanonical< - chalk_ir::InEnvironment>>, - > = chalk_ir::UCanonical { - canonical: chalk_ir::Canonical { - binders: chalk_ir::CanonicalVarKinds::from_iter( - interner, - obligation.variables.iter().map(|v| match v.kind { - CanonicalVarKind::PlaceholderTy(_ty) => unimplemented!(), - CanonicalVarKind::PlaceholderRegion(_ui) => unimplemented!(), - CanonicalVarKind::Ty(ty) => match ty { - CanonicalTyVarKind::General(ui) => chalk_ir::WithKind::new( - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General), - chalk_ir::UniverseIndex { counter: ui.index() }, - ), - CanonicalTyVarKind::Int => chalk_ir::WithKind::new( - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::Integer), - chalk_ir::UniverseIndex::root(), - ), - CanonicalTyVarKind::Float => chalk_ir::WithKind::new( - chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::Float), - chalk_ir::UniverseIndex::root(), - ), - }, - CanonicalVarKind::Region(ui) => chalk_ir::WithKind::new( - chalk_ir::VariableKind::Lifetime, - chalk_ir::UniverseIndex { counter: ui.index() }, - ), - CanonicalVarKind::Const(_ui, _ty) => unimplemented!(), - CanonicalVarKind::PlaceholderConst(_pc, _ty) => unimplemented!(), - }), - ), - value: obligation.value.lower_into(interner), - }, - universes: max_universe + 1, - }; - - use chalk_solve::Solver; - let mut solver = chalk_engine::solve::SLGSolver::new(32, None); - let db = ChalkRustIrDatabase { interner }; - debug!(?lowered_goal); - let solution = solver.solve(&db, &lowered_goal); - debug!(?obligation, ?solution, "evaluate goal"); - - // Ideally, the code to convert *back* to rustc types would live close to - // the code to convert *from* rustc types. Right now though, we don't - // really need this and so it's really minimal. - // Right now, we also treat a `Unique` solution the same as - // `Ambig(Definite)`. This really isn't right. - let make_solution = |subst: chalk_ir::Substitution<_>, - binders: chalk_ir::CanonicalVarKinds<_>| { - use rustc_middle::infer::canonical::CanonicalVarInfo; - - let mut reverse_param_substitutor = ReverseParamsSubstitutor::new(tcx, params); - let var_values = tcx.mk_substs_from_iter( - subst - .as_slice(interner) - .iter() - .map(|p| p.lower_into(interner).fold_with(&mut reverse_param_substitutor)), - ); - let variables = binders.iter(interner).map(|var| { - let kind = match var.kind { - chalk_ir::VariableKind::Ty(ty_kind) => CanonicalVarKind::Ty(match ty_kind { - chalk_ir::TyVariableKind::General => CanonicalTyVarKind::General( - ty::UniverseIndex::from_usize(var.skip_kind().counter), - ), - chalk_ir::TyVariableKind::Integer => CanonicalTyVarKind::Int, - chalk_ir::TyVariableKind::Float => CanonicalTyVarKind::Float, - }), - chalk_ir::VariableKind::Lifetime => { - CanonicalVarKind::Region(ty::UniverseIndex::from_usize(var.skip_kind().counter)) - } - // FIXME(compiler-errors): We don't currently have a way of turning - // a Chalk ty back into a rustc ty, right? - chalk_ir::VariableKind::Const(_) => todo!(), - }; - CanonicalVarInfo { kind } - }); - let max_universe = binders.iter(interner).map(|v| v.skip_kind().counter).max().unwrap_or(0); - let sol = Canonical { - max_universe: ty::UniverseIndex::from_usize(max_universe), - variables: tcx.mk_canonical_var_infos_from_iter(variables), - value: QueryResponse { - var_values: CanonicalVarValues { var_values }, - region_constraints: QueryRegionConstraints::default(), - certainty: Certainty::Proven, - opaque_types: vec![], - value: (), - }, - }; - tcx.arena.alloc(sol) - }; - solution - .map(|s| match s { - Solution::Unique(subst) => { - // FIXME(chalk): handle constraints - make_solution(subst.value.subst, subst.binders) - } - Solution::Ambig(guidance) => { - match guidance { - chalk_solve::Guidance::Definite(subst) => { - make_solution(subst.value, subst.binders) - } - chalk_solve::Guidance::Suggested(_) => unimplemented!(), - chalk_solve::Guidance::Unknown => { - // chalk_fulfill doesn't use the var_values here, so - // let's just ignore that - let sol = Canonical { - max_universe: ty::UniverseIndex::from_usize(0), - variables: obligation.variables, - value: QueryResponse { - var_values: CanonicalVarValues::dummy(), - region_constraints: QueryRegionConstraints::default(), - certainty: Certainty::Ambiguous, - opaque_types: vec![], - value: (), - }, - }; - &*tcx.arena.alloc(sol) - } - } - } - }) - .ok_or(traits::query::NoSolution) -} diff --git a/compiler/rustc_traits/src/codegen.rs b/compiler/rustc_traits/src/codegen.rs index ddba03b0b12b3..2cd1c3b502ae7 100644 --- a/compiler/rustc_traits/src/codegen.rs +++ b/compiler/rustc_traits/src/codegen.rs @@ -5,7 +5,7 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{FulfillmentErrorCode, TraitEngineExt as _}; -use rustc_middle::traits::{CodegenObligationError, DefiningAnchor}; +use rustc_middle::traits::CodegenObligationError; use rustc_middle::ty::{self, TyCtxt}; use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt; use rustc_trait_selection::traits::{ @@ -22,20 +22,14 @@ use rustc_trait_selection::traits::{ /// This also expects that `trait_ref` is fully normalized. pub fn codegen_select_candidate<'tcx>( tcx: TyCtxt<'tcx>, - (param_env, trait_ref): (ty::ParamEnv<'tcx>, ty::PolyTraitRef<'tcx>), + (param_env, trait_ref): (ty::ParamEnv<'tcx>, ty::TraitRef<'tcx>), ) -> Result<&'tcx ImplSource<'tcx, ()>, CodegenObligationError> { // We expect the input to be fully normalized. debug_assert_eq!(trait_ref, tcx.normalize_erasing_regions(param_env, trait_ref)); // Do the initial selection for the obligation. This yields the // shallow result we are looking for -- that is, what specific impl. - let infcx = tcx - .infer_ctxt() - .ignoring_regions() - .with_opaque_type_inference(DefiningAnchor::Bubble) - .build(); - //~^ HACK `Bubble` is required for - // this test to pass: type-alias-impl-trait/assoc-projection-ice.rs + let infcx = tcx.infer_ctxt().ignoring_regions().build(); let mut selcx = SelectionContext::new(&infcx); let obligation_cause = ObligationCause::dummy(); @@ -55,7 +49,7 @@ pub fn codegen_select_candidate<'tcx>( // Currently, we use a fulfillment context to completely resolve // all nested obligations. This is because they can inform the // inference of the impl's type parameters. - let mut fulfill_cx = >::new(tcx); + let mut fulfill_cx = >::new(&infcx); let impl_source = selection.map(|predicate| { fulfill_cx.register_predicate_obligation(&infcx, predicate); }); @@ -79,10 +73,5 @@ pub fn codegen_select_candidate<'tcx>( let impl_source = infcx.resolve_vars_if_possible(impl_source); let impl_source = infcx.tcx.erase_regions(impl_source); - // Opaque types may have gotten their hidden types constrained, but we can ignore them safely - // as they will get constrained elsewhere, too. - // (ouz-a) This is required for `type-alias-impl-trait/assoc-projection-ice.rs` to pass - let _ = infcx.take_opaque_types(); - Ok(&*tcx.arena.alloc(impl_source)) } diff --git a/compiler/rustc_traits/src/dropck_outlives.rs b/compiler/rustc_traits/src/dropck_outlives.rs index 83f6c7d07fe78..074764f0c4e2e 100644 --- a/compiler/rustc_traits/src/dropck_outlives.rs +++ b/compiler/rustc_traits/src/dropck_outlives.rs @@ -3,17 +3,14 @@ use rustc_hir::def_id::DefId; use rustc_infer::infer::canonical::{Canonical, QueryResponse}; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::query::Providers; -use rustc_middle::ty::InternalSubsts; -use rustc_middle::ty::{self, EarlyBinder, ParamEnvAnd, Ty, TyCtxt}; -use rustc_span::source_map::{Span, DUMMY_SP}; +use rustc_middle::traits::query::{DropckConstraint, DropckOutlivesResult}; +use rustc_middle::ty::GenericArgs; +use rustc_middle::ty::TyCtxt; use rustc_trait_selection::infer::InferCtxtBuilderExt; -use rustc_trait_selection::traits::query::dropck_outlives::trivial_dropck_outlives; use rustc_trait_selection::traits::query::dropck_outlives::{ - DropckConstraint, DropckOutlivesResult, + compute_dropck_outlives_inner, dtorck_constraint_for_ty_inner, }; -use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; use rustc_trait_selection::traits::query::{CanonicalTyGoal, NoSolution}; -use rustc_trait_selection::traits::{Normalized, ObligationCause}; pub(crate) fn provide(p: &mut Providers) { *p = Providers { dropck_outlives, adt_dtorck_constraint, ..*p }; @@ -26,263 +23,10 @@ fn dropck_outlives<'tcx>( debug!("dropck_outlives(goal={:#?})", canonical_goal); tcx.infer_ctxt().enter_canonical_trait_query(&canonical_goal, |ocx, goal| { - let tcx = ocx.infcx.tcx; - let ParamEnvAnd { param_env, value: for_ty } = goal; - - let mut result = DropckOutlivesResult { kinds: vec![], overflows: vec![] }; - - // A stack of types left to process. Each round, we pop - // something from the stack and invoke - // `dtorck_constraint_for_ty`. This may produce new types that - // have to be pushed on the stack. This continues until we have explored - // all the reachable types from the type `for_ty`. - // - // Example: Imagine that we have the following code: - // - // ```rust - // struct A { - // value: B, - // children: Vec, - // } - // - // struct B { - // value: u32 - // } - // - // fn f() { - // let a: A = ...; - // .. - // } // here, `a` is dropped - // ``` - // - // at the point where `a` is dropped, we need to figure out - // which types inside of `a` contain region data that may be - // accessed by any destructors in `a`. We begin by pushing `A` - // onto the stack, as that is the type of `a`. We will then - // invoke `dtorck_constraint_for_ty` which will expand `A` - // into the types of its fields `(B, Vec)`. These will get - // pushed onto the stack. Eventually, expanding `Vec` will - // lead to us trying to push `A` a second time -- to prevent - // infinite recursion, we notice that `A` was already pushed - // once and stop. - let mut ty_stack = vec![(for_ty, 0)]; - - // Set used to detect infinite recursion. - let mut ty_set = FxHashSet::default(); - - let cause = ObligationCause::dummy(); - let mut constraints = DropckConstraint::empty(); - while let Some((ty, depth)) = ty_stack.pop() { - debug!( - "{} kinds, {} overflows, {} ty_stack", - result.kinds.len(), - result.overflows.len(), - ty_stack.len() - ); - dtorck_constraint_for_ty(tcx, DUMMY_SP, for_ty, depth, ty, &mut constraints)?; - - // "outlives" represent types/regions that may be touched - // by a destructor. - result.kinds.append(&mut constraints.outlives); - result.overflows.append(&mut constraints.overflows); - - // If we have even one overflow, we should stop trying to evaluate further -- - // chances are, the subsequent overflows for this evaluation won't provide useful - // information and will just decrease the speed at which we can emit these errors - // (since we'll be printing for just that much longer for the often enormous types - // that result here). - if !result.overflows.is_empty() { - break; - } - - // dtorck types are "types that will get dropped but which - // do not themselves define a destructor", more or less. We have - // to push them onto the stack to be expanded. - for ty in constraints.dtorck_types.drain(..) { - let Normalized { value: ty, obligations } = - ocx.infcx.at(&cause, param_env).query_normalize(ty)?; - ocx.register_obligations(obligations); - - debug!("dropck_outlives: ty from dtorck_types = {:?}", ty); - - match ty.kind() { - // All parameters live for the duration of the - // function. - ty::Param(..) => {} - - // A projection that we couldn't resolve - it - // might have a destructor. - ty::Alias(..) => { - result.kinds.push(ty.into()); - } - - _ => { - if ty_set.insert(ty) { - ty_stack.push((ty, depth + 1)); - } - } - } - } - } - - debug!("dropck_outlives: result = {:#?}", result); - Ok(result) + compute_dropck_outlives_inner(ocx, goal) }) } -/// Returns a set of constraints that needs to be satisfied in -/// order for `ty` to be valid for destruction. -fn dtorck_constraint_for_ty<'tcx>( - tcx: TyCtxt<'tcx>, - span: Span, - for_ty: Ty<'tcx>, - depth: usize, - ty: Ty<'tcx>, - constraints: &mut DropckConstraint<'tcx>, -) -> Result<(), NoSolution> { - debug!("dtorck_constraint_for_ty({:?}, {:?}, {:?}, {:?})", span, for_ty, depth, ty); - - if !tcx.recursion_limit().value_within_limit(depth) { - constraints.overflows.push(ty); - return Ok(()); - } - - if trivial_dropck_outlives(tcx, ty) { - return Ok(()); - } - - match ty.kind() { - ty::Bool - | ty::Char - | ty::Int(_) - | ty::Uint(_) - | ty::Float(_) - | ty::Str - | ty::Never - | ty::Foreign(..) - | ty::RawPtr(..) - | ty::Ref(..) - | ty::FnDef(..) - | ty::FnPtr(_) - | ty::GeneratorWitness(..) - | ty::GeneratorWitnessMIR(..) => { - // these types never have a destructor - } - - ty::Array(ety, _) | ty::Slice(ety) => { - // single-element containers, behave like their element - rustc_data_structures::stack::ensure_sufficient_stack(|| { - dtorck_constraint_for_ty(tcx, span, for_ty, depth + 1, *ety, constraints) - })?; - } - - ty::Tuple(tys) => rustc_data_structures::stack::ensure_sufficient_stack(|| { - for ty in tys.iter() { - dtorck_constraint_for_ty(tcx, span, for_ty, depth + 1, ty, constraints)?; - } - Ok::<_, NoSolution>(()) - })?, - - ty::Closure(_, substs) => { - if !substs.as_closure().is_valid() { - // By the time this code runs, all type variables ought to - // be fully resolved. - - tcx.sess.delay_span_bug( - span, - format!("upvar_tys for closure not found. Expected capture information for closure {ty}",), - ); - return Err(NoSolution); - } - - rustc_data_structures::stack::ensure_sufficient_stack(|| { - for ty in substs.as_closure().upvar_tys() { - dtorck_constraint_for_ty(tcx, span, for_ty, depth + 1, ty, constraints)?; - } - Ok::<_, NoSolution>(()) - })? - } - - ty::Generator(_, substs, _movability) => { - // rust-lang/rust#49918: types can be constructed, stored - // in the interior, and sit idle when generator yields - // (and is subsequently dropped). - // - // It would be nice to descend into interior of a - // generator to determine what effects dropping it might - // have (by looking at any drop effects associated with - // its interior). - // - // However, the interior's representation uses things like - // GeneratorWitness that explicitly assume they are not - // traversed in such a manner. So instead, we will - // simplify things for now by treating all generators as - // if they were like trait objects, where its upvars must - // all be alive for the generator's (potential) - // destructor. - // - // In particular, skipping over `_interior` is safe - // because any side-effects from dropping `_interior` can - // only take place through references with lifetimes - // derived from lifetimes attached to the upvars and resume - // argument, and we *do* incorporate those here. - - if !substs.as_generator().is_valid() { - // By the time this code runs, all type variables ought to - // be fully resolved. - tcx.sess.delay_span_bug( - span, - format!("upvar_tys for generator not found. Expected capture information for generator {ty}",), - ); - return Err(NoSolution); - } - - constraints.outlives.extend( - substs - .as_generator() - .upvar_tys() - .map(|t| -> ty::subst::GenericArg<'tcx> { t.into() }), - ); - constraints.outlives.push(substs.as_generator().resume_ty().into()); - } - - ty::Adt(def, substs) => { - let DropckConstraint { dtorck_types, outlives, overflows } = - tcx.at(span).adt_dtorck_constraint(def.did())?; - // FIXME: we can try to recursively `dtorck_constraint_on_ty` - // there, but that needs some way to handle cycles. - constraints - .dtorck_types - .extend(dtorck_types.iter().map(|t| EarlyBinder(*t).subst(tcx, substs))); - constraints - .outlives - .extend(outlives.iter().map(|t| EarlyBinder(*t).subst(tcx, substs))); - constraints - .overflows - .extend(overflows.iter().map(|t| EarlyBinder(*t).subst(tcx, substs))); - } - - // Objects must be alive in order for their destructor - // to be called. - ty::Dynamic(..) => { - constraints.outlives.push(ty.into()); - } - - // Types that can't be resolved. Pass them forward. - ty::Alias(..) | ty::Param(..) => { - constraints.dtorck_types.push(ty); - } - - ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) | ty::Error(_) => { - // By the time this code runs, all type variables ought to - // be fully resolved. - return Err(NoSolution); - } - } - - Ok(()) -} - /// Calculates the dtorck constraint for a type. pub(crate) fn adt_dtorck_constraint( tcx: TyCtxt<'_>, @@ -297,11 +41,11 @@ pub(crate) fn adt_dtorck_constraint( } else if def.is_phantom_data() { // The first generic parameter here is guaranteed to be a type because it's // `PhantomData`. - let substs = InternalSubsts::identity_for_item(tcx, def_id); - assert_eq!(substs.len(), 1); + let args = GenericArgs::identity_for_item(tcx, def_id); + assert_eq!(args.len(), 1); let result = DropckConstraint { outlives: vec![], - dtorck_types: vec![substs.type_at(0)], + dtorck_types: vec![args.type_at(0)], overflows: vec![], }; debug!("dtorck_constraint: {:?} => {:?}", def, result); @@ -310,8 +54,8 @@ pub(crate) fn adt_dtorck_constraint( let mut result = DropckConstraint::empty(); for field in def.all_fields() { - let fty = tcx.type_of(field.did).subst_identity(); - dtorck_constraint_for_ty(tcx, span, fty, 0, fty, &mut result)?; + let fty = tcx.type_of(field.did).instantiate_identity(); + dtorck_constraint_for_ty_inner(tcx, span, fty, 0, fty, &mut result)?; } result.outlives.extend(tcx.destructor_constraints(def)); dedup_dtorck_constraint(&mut result); diff --git a/compiler/rustc_traits/src/evaluate_obligation.rs b/compiler/rustc_traits/src/evaluate_obligation.rs index f5b2753b7973d..e8917d72483c6 100644 --- a/compiler/rustc_traits/src/evaluate_obligation.rs +++ b/compiler/rustc_traits/src/evaluate_obligation.rs @@ -1,6 +1,5 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::query::Providers; -use rustc_middle::traits::DefiningAnchor; use rustc_middle::ty::{ParamEnvAnd, TyCtxt}; use rustc_span::source_map::DUMMY_SP; use rustc_trait_selection::traits::query::CanonicalPredicateGoal; @@ -16,14 +15,10 @@ fn evaluate_obligation<'tcx>( tcx: TyCtxt<'tcx>, canonical_goal: CanonicalPredicateGoal<'tcx>, ) -> Result { - assert!(!tcx.trait_solver_next()); + assert!(!tcx.next_trait_solver_globally()); debug!("evaluate_obligation(canonical_goal={:#?})", canonical_goal); - // HACK This bubble is required for this tests to pass: - // impl-trait/issue99642.rs - let (ref infcx, goal, _canonical_inference_vars) = tcx - .infer_ctxt() - .with_opaque_type_inference(DefiningAnchor::Bubble) - .build_with_canonical(DUMMY_SP, &canonical_goal); + let (ref infcx, goal, _canonical_inference_vars) = + tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal); debug!("evaluate_obligation: goal={:#?}", goal); let ParamEnvAnd { param_env, value: predicate } = goal; diff --git a/compiler/rustc_traits/src/implied_outlives_bounds.rs b/compiler/rustc_traits/src/implied_outlives_bounds.rs index 0c2bb863e1fe0..959838ab348fd 100644 --- a/compiler/rustc_traits/src/implied_outlives_bounds.rs +++ b/compiler/rustc_traits/src/implied_outlives_bounds.rs @@ -3,18 +3,13 @@ //! [`rustc_trait_selection::traits::query::type_op::implied_outlives_bounds`]. use rustc_infer::infer::canonical::{self, Canonical}; -use rustc_infer::infer::outlives::components::{push_outlives_components, Component}; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::query::OutlivesBound; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; -use rustc_span::def_id::CRATE_DEF_ID; -use rustc_span::source_map::DUMMY_SP; +use rustc_middle::ty::TyCtxt; use rustc_trait_selection::infer::InferCtxtBuilderExt; -use rustc_trait_selection::traits::query::{CanonicalTyGoal, Fallible, NoSolution}; -use rustc_trait_selection::traits::wf; -use rustc_trait_selection::traits::ObligationCtxt; -use smallvec::{smallvec, SmallVec}; +use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::compute_implied_outlives_bounds_inner; +use rustc_trait_selection::traits::query::{CanonicalTyGoal, NoSolution}; pub(crate) fn provide(p: &mut Providers) { *p = Providers { implied_outlives_bounds, ..*p }; @@ -29,164 +24,6 @@ fn implied_outlives_bounds<'tcx>( > { tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| { let (param_env, ty) = key.into_parts(); - compute_implied_outlives_bounds(ocx, param_env, ty) + compute_implied_outlives_bounds_inner(ocx, param_env, ty) }) } - -fn compute_implied_outlives_bounds<'tcx>( - ocx: &ObligationCtxt<'_, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - ty: Ty<'tcx>, -) -> Fallible>> { - let tcx = ocx.infcx.tcx; - - // Sometimes when we ask what it takes for T: WF, we get back that - // U: WF is required; in that case, we push U onto this stack and - // process it next. Because the resulting predicates aren't always - // guaranteed to be a subset of the original type, so we need to store the - // WF args we've computed in a set. - let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default(); - let mut wf_args = vec![ty.into()]; - - let mut outlives_bounds: Vec, ty::Region<'tcx>>> = - vec![]; - - while let Some(arg) = wf_args.pop() { - if !checked_wf_args.insert(arg) { - continue; - } - - // Compute the obligations for `arg` to be well-formed. If `arg` is - // an unresolved inference variable, just substituted an empty set - // -- because the return type here is going to be things we *add* - // to the environment, it's always ok for this set to be smaller - // than the ultimate set. (Note: normally there won't be - // unresolved inference variables here anyway, but there might be - // during typeck under some circumstances.) - // - // FIXME(@lcnr): It's not really "always fine", having fewer implied - // bounds can be backward incompatible, e.g. #101951 was caused by - // us not dealing with inference vars in `TypeOutlives` predicates. - let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP) - .unwrap_or_default(); - - for obligation in obligations { - debug!(?obligation); - assert!(!obligation.has_escaping_bound_vars()); - - // While these predicates should all be implied by other parts of - // the program, they are still relevant as they may constrain - // inference variables, which is necessary to add the correct - // implied bounds in some cases, mostly when dealing with projections. - // - // Another important point here: we only register `Projection` - // predicates, since otherwise we might register outlives - // predicates containing inference variables, and we don't - // learn anything new from those. - if obligation.predicate.has_non_region_infer() { - match obligation.predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::AliasRelate(..) => { - ocx.register_obligation(obligation.clone()); - } - _ => {} - } - } - - let pred = match obligation.predicate.kind().no_bound_vars() { - None => continue, - Some(pred) => pred, - }; - match pred { - ty::PredicateKind::Clause(ty::Clause::Trait(..)) - // FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound - // if we ever support that - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) - | ty::PredicateKind::Subtype(..) - | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::ClosureKind(..) - | ty::PredicateKind::ObjectSafe(..) - | ty::PredicateKind::ConstEvaluatable(..) - | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::TypeWellFormedFromEnv(..) => {} - - // We need to search through *all* WellFormed predicates - ty::PredicateKind::WellFormed(arg) => { - wf_args.push(arg); - } - - // We need to register region relationships - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(ty::OutlivesPredicate( - r_a, - r_b, - ))) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)), - - ty::PredicateKind::Clause(ty::Clause::TypeOutlives(ty::OutlivesPredicate( - ty_a, - r_b, - ))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)), - } - } - } - - // This call to `select_all_or_error` is necessary to constrain inference variables, which we - // use further down when computing the implied bounds. - match ocx.select_all_or_error().as_slice() { - [] => (), - _ => return Err(NoSolution), - } - - // We lazily compute the outlives components as - // `select_all_or_error` constrains inference variables. - let implied_bounds = outlives_bounds - .into_iter() - .flat_map(|ty::OutlivesPredicate(a, r_b)| match a.unpack() { - ty::GenericArgKind::Lifetime(r_a) => vec![OutlivesBound::RegionSubRegion(r_b, r_a)], - ty::GenericArgKind::Type(ty_a) => { - let ty_a = ocx.infcx.resolve_vars_if_possible(ty_a); - let mut components = smallvec![]; - push_outlives_components(tcx, ty_a, &mut components); - implied_bounds_from_components(r_b, components) - } - ty::GenericArgKind::Const(_) => unreachable!(), - }) - .collect(); - - Ok(implied_bounds) -} - -/// When we have an implied bound that `T: 'a`, we can further break -/// this down to determine what relationships would have to hold for -/// `T: 'a` to hold. We get to assume that the caller has validated -/// those relationships. -fn implied_bounds_from_components<'tcx>( - sub_region: ty::Region<'tcx>, - sup_components: SmallVec<[Component<'tcx>; 4]>, -) -> Vec> { - sup_components - .into_iter() - .filter_map(|component| { - match component { - Component::Region(r) => Some(OutlivesBound::RegionSubRegion(sub_region, r)), - Component::Param(p) => Some(OutlivesBound::RegionSubParam(sub_region, p)), - Component::Alias(p) => Some(OutlivesBound::RegionSubAlias(sub_region, p)), - Component::EscapingAlias(_) => - // If the projection has escaping regions, don't - // try to infer any implied bounds even for its - // free components. This is conservative, because - // the caller will still have to prove that those - // free components outlive `sub_region`. But the - // idea is that the WAY that the caller proves - // that may change in the future and we want to - // give ourselves room to get smarter here. - { - None - } - Component::UnresolvedInferenceVariable(..) => None, - } - }) - .collect() -} diff --git a/compiler/rustc_traits/src/lib.rs b/compiler/rustc_traits/src/lib.rs index b0f9c57154f1b..dfa5814219bc2 100644 --- a/compiler/rustc_traits/src/lib.rs +++ b/compiler/rustc_traits/src/lib.rs @@ -1,10 +1,8 @@ -//! New recursive solver modeled on Chalk's recursive solver. Most of -//! the guts are broken up into modules; see the comments in those modules. +//! Queries that are independent from the main solver code. #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] #![feature(let_chains)] -#![feature(drain_filter)] #![recursion_limit = "256"] #[macro_use] @@ -12,7 +10,6 @@ extern crate tracing; #[macro_use] extern crate rustc_middle; -mod chalk; mod codegen; mod dropck_outlives; mod evaluate_obligation; @@ -21,7 +18,8 @@ mod normalize_erasing_regions; mod normalize_projection_ty; mod type_op; -pub use type_op::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause}; +pub use rustc_trait_selection::traits::query::type_op::ascribe_user_type::type_op_ascribe_user_type_with_span; +pub use type_op::type_op_prove_predicate_with_cause; use rustc_middle::query::Providers; @@ -29,7 +27,6 @@ pub fn provide(p: &mut Providers) { dropck_outlives::provide(p); evaluate_obligation::provide(p); implied_outlives_bounds::provide(p); - chalk::provide(p); normalize_projection_ty::provide(p); normalize_erasing_regions::provide(p); type_op::provide(p); diff --git a/compiler/rustc_traits/src/normalize_erasing_regions.rs b/compiler/rustc_traits/src/normalize_erasing_regions.rs index 94c33efaeff7a..2563e3ed1a34f 100644 --- a/compiler/rustc_traits/src/normalize_erasing_regions.rs +++ b/compiler/rustc_traits/src/normalize_erasing_regions.rs @@ -56,20 +56,19 @@ fn try_normalize_after_erasing_regions<'tcx, T: TypeFoldable> + Par fn not_outlives_predicate(p: ty::Predicate<'_>) -> bool { match p.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::RegionOutlives(..)) - | ty::PredicateKind::Clause(ty::Clause::TypeOutlives(..)) => false, - ty::PredicateKind::Clause(ty::Clause::Trait(..)) - | ty::PredicateKind::Clause(ty::Clause::Projection(..)) - | ty::PredicateKind::Clause(ty::Clause::ConstArgHasType(..)) + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(..)) => false, + ty::PredicateKind::Clause(ty::ClauseKind::Trait(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) | ty::PredicateKind::AliasRelate(..) - | ty::PredicateKind::WellFormed(..) + | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..)) | ty::PredicateKind::ObjectSafe(..) | ty::PredicateKind::ClosureKind(..) | ty::PredicateKind::Subtype(..) | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::ConstEvaluatable(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) | ty::PredicateKind::ConstEquate(..) - | ty::PredicateKind::Ambiguous - | ty::PredicateKind::TypeWellFormedFromEnv(..) => true, + | ty::PredicateKind::Ambiguous => true, } } diff --git a/compiler/rustc_traits/src/normalize_projection_ty.rs b/compiler/rustc_traits/src/normalize_projection_ty.rs index b552ba41acd3d..0dbac56b47dd4 100644 --- a/compiler/rustc_traits/src/normalize_projection_ty.rs +++ b/compiler/rustc_traits/src/normalize_projection_ty.rs @@ -10,7 +10,12 @@ use rustc_trait_selection::traits::{self, ObligationCause, SelectionContext}; use std::sync::atomic::Ordering; pub(crate) fn provide(p: &mut Providers) { - *p = Providers { normalize_projection_ty, normalize_inherent_projection_ty, ..*p }; + *p = Providers { + normalize_projection_ty, + normalize_weak_ty, + normalize_inherent_projection_ty, + ..*p + }; } fn normalize_projection_ty<'tcx>( @@ -43,6 +48,33 @@ fn normalize_projection_ty<'tcx>( ) } +fn normalize_weak_ty<'tcx>( + tcx: TyCtxt<'tcx>, + goal: CanonicalProjectionGoal<'tcx>, +) -> Result<&'tcx Canonical<'tcx, QueryResponse<'tcx, NormalizationResult<'tcx>>>, NoSolution> { + debug!("normalize_provider(goal={:#?})", goal); + + tcx.sess.perf_stats.normalize_projection_ty.fetch_add(1, Ordering::Relaxed); + tcx.infer_ctxt().enter_canonical_trait_query( + &goal, + |ocx, ParamEnvAnd { param_env, value: goal }| { + let obligations = tcx.predicates_of(goal.def_id).instantiate_own(tcx, goal.args).map( + |(predicate, span)| { + traits::Obligation::new( + tcx, + ObligationCause::dummy_with_span(span), + param_env, + predicate, + ) + }, + ); + ocx.register_obligations(obligations); + let normalized_ty = tcx.type_of(goal.def_id).instantiate(tcx, goal.args); + Ok(NormalizationResult { normalized_ty }) + }, + ) +} + fn normalize_inherent_projection_ty<'tcx>( tcx: TyCtxt<'tcx>, goal: CanonicalProjectionGoal<'tcx>, diff --git a/compiler/rustc_traits/src/type_op.rs b/compiler/rustc_traits/src/type_op.rs index b1cbd88ce27ea..01b9a5640b86a 100644 --- a/compiler/rustc_traits/src/type_op.rs +++ b/compiler/rustc_traits/src/type_op.rs @@ -1,21 +1,18 @@ -use rustc_hir as hir; use rustc_infer::infer::canonical::{Canonical, QueryResponse}; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::query::Providers; -use rustc_middle::traits::{DefiningAnchor, ObligationCauseCode}; -use rustc_middle::ty::{self, FnSig, Lift, PolyFnSig, Ty, TyCtxt, TypeFoldable}; -use rustc_middle::ty::{ParamEnvAnd, Predicate}; -use rustc_middle::ty::{UserSelfTy, UserSubsts, UserType}; -use rustc_span::def_id::CRATE_DEF_ID; -use rustc_span::{Span, DUMMY_SP}; +use rustc_middle::traits::query::NoSolution; +use rustc_middle::ty::{Clause, ParamEnvAnd}; +use rustc_middle::ty::{FnSig, Lift, PolyFnSig, Ty, TyCtxt, TypeFoldable}; use rustc_trait_selection::infer::InferCtxtBuilderExt; use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt; -use rustc_trait_selection::traits::query::type_op::ascribe_user_type::AscribeUserType; +use rustc_trait_selection::traits::query::type_op::ascribe_user_type::{ + type_op_ascribe_user_type_with_span, AscribeUserType, +}; use rustc_trait_selection::traits::query::type_op::eq::Eq; use rustc_trait_selection::traits::query::type_op::normalize::Normalize; use rustc_trait_selection::traits::query::type_op::prove_predicate::ProvePredicate; use rustc_trait_selection::traits::query::type_op::subtype::Subtype; -use rustc_trait_selection::traits::query::{Fallible, NoSolution}; use rustc_trait_selection::traits::{Normalized, Obligation, ObligationCause, ObligationCtxt}; use std::fmt; @@ -26,7 +23,7 @@ pub(crate) fn provide(p: &mut Providers) { type_op_prove_predicate, type_op_subtype, type_op_normalize_ty, - type_op_normalize_predicate, + type_op_normalize_clause, type_op_normalize_fn_sig, type_op_normalize_poly_fn_sig, ..*p @@ -42,111 +39,6 @@ fn type_op_ascribe_user_type<'tcx>( }) } -/// The core of the `type_op_ascribe_user_type` query: for diagnostics purposes in NLL HRTB errors, -/// this query can be re-run to better track the span of the obligation cause, and improve the error -/// message. Do not call directly unless you're in that very specific context. -pub fn type_op_ascribe_user_type_with_span<'tcx>( - ocx: &ObligationCtxt<'_, 'tcx>, - key: ParamEnvAnd<'tcx, AscribeUserType<'tcx>>, - span: Option, -) -> Result<(), NoSolution> { - let (param_env, AscribeUserType { mir_ty, user_ty }) = key.into_parts(); - debug!("type_op_ascribe_user_type: mir_ty={:?} user_ty={:?}", mir_ty, user_ty); - let span = span.unwrap_or(DUMMY_SP); - match user_ty { - UserType::Ty(user_ty) => relate_mir_and_user_ty(ocx, param_env, span, mir_ty, user_ty)?, - UserType::TypeOf(def_id, user_substs) => { - relate_mir_and_user_substs(ocx, param_env, span, mir_ty, def_id, user_substs)? - } - }; - Ok(()) -} - -#[instrument(level = "debug", skip(ocx, param_env, span))] -fn relate_mir_and_user_ty<'tcx>( - ocx: &ObligationCtxt<'_, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - span: Span, - mir_ty: Ty<'tcx>, - user_ty: Ty<'tcx>, -) -> Result<(), NoSolution> { - let cause = ObligationCause::dummy_with_span(span); - let user_ty = ocx.normalize(&cause, param_env, user_ty); - ocx.eq(&cause, param_env, mir_ty, user_ty)?; - - // FIXME(#104764): We should check well-formedness before normalization. - let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(user_ty.into())); - ocx.register_obligation(Obligation::new(ocx.infcx.tcx, cause, param_env, predicate)); - Ok(()) -} - -#[instrument(level = "debug", skip(ocx, param_env, span))] -fn relate_mir_and_user_substs<'tcx>( - ocx: &ObligationCtxt<'_, 'tcx>, - param_env: ty::ParamEnv<'tcx>, - span: Span, - mir_ty: Ty<'tcx>, - def_id: hir::def_id::DefId, - user_substs: UserSubsts<'tcx>, -) -> Result<(), NoSolution> { - let param_env = param_env.without_const(); - let UserSubsts { user_self_ty, substs } = user_substs; - let tcx = ocx.infcx.tcx; - let cause = ObligationCause::dummy_with_span(span); - - let ty = tcx.type_of(def_id).subst(tcx, substs); - let ty = ocx.normalize(&cause, param_env, ty); - debug!("relate_type_and_user_type: ty of def-id is {:?}", ty); - - ocx.eq(&cause, param_env, mir_ty, ty)?; - - // Prove the predicates coming along with `def_id`. - // - // Also, normalize the `instantiated_predicates` - // because otherwise we wind up with duplicate "type - // outlives" error messages. - let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs); - - debug!(?instantiated_predicates); - for (instantiated_predicate, predicate_span) in instantiated_predicates { - let span = if span == DUMMY_SP { predicate_span } else { span }; - let cause = ObligationCause::new( - span, - CRATE_DEF_ID, - ObligationCauseCode::AscribeUserTypeProvePredicate(predicate_span), - ); - let instantiated_predicate = - ocx.normalize(&cause.clone(), param_env, instantiated_predicate); - - ocx.register_obligation(Obligation::new(tcx, cause, param_env, instantiated_predicate)); - } - - if let Some(UserSelfTy { impl_def_id, self_ty }) = user_self_ty { - let self_ty = ocx.normalize(&cause, param_env, self_ty); - let impl_self_ty = tcx.type_of(impl_def_id).subst(tcx, substs); - let impl_self_ty = ocx.normalize(&cause, param_env, impl_self_ty); - - ocx.eq(&cause, param_env, self_ty, impl_self_ty)?; - let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(impl_self_ty.into())); - ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, predicate)); - } - - // In addition to proving the predicates, we have to - // prove that `ty` is well-formed -- this is because - // the WF of `ty` is predicated on the substs being - // well-formed, and we haven't proven *that*. We don't - // want to prove the WF of types from `substs` directly because they - // haven't been normalized. - // - // FIXME(nmatsakis): Well, perhaps we should normalize - // them? This would only be relevant if some input - // type were ill-formed but did not appear in `ty`, - // which...could happen with normalization... - let predicate = ty::Binder::dummy(ty::PredicateKind::WellFormed(ty.into())); - ocx.register_obligation(Obligation::new(tcx, cause, param_env, predicate)); - Ok(()) -} - fn type_op_eq<'tcx>( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Eq<'tcx>>>, @@ -160,7 +52,7 @@ fn type_op_eq<'tcx>( fn type_op_normalize<'tcx, T>( ocx: &ObligationCtxt<'_, 'tcx>, key: ParamEnvAnd<'tcx, Normalize>, -) -> Fallible +) -> Result where T: fmt::Debug + TypeFoldable> + Lift<'tcx>, { @@ -178,10 +70,10 @@ fn type_op_normalize_ty<'tcx>( tcx.infer_ctxt().enter_canonical_trait_query(&canonicalized, type_op_normalize) } -fn type_op_normalize_predicate<'tcx>( +fn type_op_normalize_clause<'tcx>( tcx: TyCtxt<'tcx>, - canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>>, -) -> Result<&'tcx Canonical<'tcx, QueryResponse<'tcx, Predicate<'tcx>>>, NoSolution> { + canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Normalize>>>, +) -> Result<&'tcx Canonical<'tcx, QueryResponse<'tcx, Clause<'tcx>>>, NoSolution> { tcx.infer_ctxt().enter_canonical_trait_query(&canonicalized, type_op_normalize) } @@ -213,15 +105,10 @@ fn type_op_prove_predicate<'tcx>( tcx: TyCtxt<'tcx>, canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, ProvePredicate<'tcx>>>, ) -> Result<&'tcx Canonical<'tcx, QueryResponse<'tcx, ()>>, NoSolution> { - // HACK This bubble is required for this test to pass: - // impl-trait/issue-99642.rs - tcx.infer_ctxt().with_opaque_type_inference(DefiningAnchor::Bubble).enter_canonical_trait_query( - &canonicalized, - |ocx, key| { - type_op_prove_predicate_with_cause(ocx, key, ObligationCause::dummy()); - Ok(()) - }, - ) + tcx.infer_ctxt().enter_canonical_trait_query(&canonicalized, |ocx, key| { + type_op_prove_predicate_with_cause(ocx, key, ObligationCause::dummy()); + Ok(()) + }) } /// The core of the `type_op_prove_predicate` query: for diagnostics purposes in NLL HRTB errors, diff --git a/compiler/rustc_transmute/src/layout/mod.rs b/compiler/rustc_transmute/src/layout/mod.rs index f8d05bc89d26d..76d97e0e6e7a6 100644 --- a/compiler/rustc_transmute/src/layout/mod.rs +++ b/compiler/rustc_transmute/src/layout/mod.rs @@ -30,33 +30,49 @@ impl fmt::Debug for Byte { } pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone {} -pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone {} +pub trait Ref: Debug + Hash + Eq + PartialEq + Copy + Clone { + fn min_align(&self) -> usize; + + fn is_mutable(&self) -> bool; +} impl Def for ! {} -impl Ref for ! {} +impl Ref for ! { + fn min_align(&self) -> usize { + unreachable!() + } + fn is_mutable(&self) -> bool { + unreachable!() + } +} #[cfg(feature = "rustc")] -pub(crate) mod rustc { +pub mod rustc { use rustc_middle::mir::Mutability; - use rustc_middle::ty; - use rustc_middle::ty::Region; - use rustc_middle::ty::Ty; + use rustc_middle::ty::{self, Ty}; /// A reference in the layout. #[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone, Copy)] pub struct Ref<'tcx> { - lifetime: Region<'tcx>, - ty: Ty<'tcx>, - mutability: Mutability, + pub lifetime: ty::Region<'tcx>, + pub ty: Ty<'tcx>, + pub mutability: Mutability, + pub align: usize, } - impl<'tcx> super::Ref for Ref<'tcx> {} + impl<'tcx> super::Ref for Ref<'tcx> { + fn min_align(&self) -> usize { + self.align + } - impl<'tcx> Ref<'tcx> { - pub fn min_align(&self) -> usize { - todo!() + fn is_mutable(&self) -> bool { + match self.mutability { + Mutability::Mut => true, + Mutability::Not => false, + } } } + impl<'tcx> Ref<'tcx> {} /// A visibility node in the layout. #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] diff --git a/compiler/rustc_transmute/src/layout/tree.rs b/compiler/rustc_transmute/src/layout/tree.rs index a6d88b1342ae8..e8ddb0a43962f 100644 --- a/compiler/rustc_transmute/src/layout/tree.rs +++ b/compiler/rustc_transmute/src/layout/tree.rs @@ -175,8 +175,8 @@ pub(crate) mod rustc { use rustc_middle::ty::layout::LayoutError; use rustc_middle::ty::util::Discr; use rustc_middle::ty::AdtDef; + use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::ParamEnv; - use rustc_middle::ty::SubstsRef; use rustc_middle::ty::VariantDef; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::ErrorGuaranteed; @@ -188,14 +188,14 @@ pub(crate) mod rustc { /// The layout of the type is unspecified. Unspecified, /// This error will be surfaced elsewhere by rustc, so don't surface it. - Unknown, + UnknownLayout, TypeError(ErrorGuaranteed), } - impl<'tcx> From> for Err { - fn from(err: LayoutError<'tcx>) -> Self { + impl<'tcx> From<&LayoutError<'tcx>> for Err { + fn from(err: &LayoutError<'tcx>) -> Self { match err { - LayoutError::Unknown(..) => Self::Unknown, + LayoutError::Unknown(..) | LayoutError::ReferencesError(..) => Self::UnknownLayout, err => unimplemented!("{:?}", err), } } @@ -221,7 +221,7 @@ pub(crate) mod rustc { } impl LayoutSummary { - fn from_ty<'tcx>(ty: Ty<'tcx>, ctx: TyCtxt<'tcx>) -> Result> { + fn from_ty<'tcx>(ty: Ty<'tcx>, ctx: TyCtxt<'tcx>) -> Result> { use rustc_middle::ty::ParamEnvAnd; use rustc_target::abi::{TyAndLayout, Variants}; @@ -297,7 +297,7 @@ pub(crate) mod rustc { .fold(Tree::unit(), |tree, elt| tree.then(elt))) } - ty::Adt(adt_def, substs_ref) => { + ty::Adt(adt_def, args_ref) => { use rustc_middle::ty::AdtKind; // If the layout is ill-specified, halt. @@ -316,7 +316,7 @@ pub(crate) mod rustc { AdtKind::Struct => Self::from_repr_c_variant( ty, *adt_def, - substs_ref, + args_ref, &layout_summary, None, adt_def.non_enum_variant(), @@ -330,7 +330,7 @@ pub(crate) mod rustc { tree = tree.or(Self::from_repr_c_variant( ty, *adt_def, - substs_ref, + args_ref, &layout_summary, Some(discr), adt_def.variant(idx), @@ -351,7 +351,7 @@ pub(crate) mod rustc { let mut tree = Tree::uninhabited(); for field in adt_def.all_fields() { - let variant_ty = field.ty(tcx, substs_ref); + let variant_ty = field.ty(tcx, args_ref); let variant_layout = layout_of(tcx, variant_ty)?; let padding_needed = ty_layout.size() - variant_layout.size(); let variant = Self::def(Def::Field(field)) @@ -365,6 +365,17 @@ pub(crate) mod rustc { } })) } + + ty::Ref(lifetime, ty, mutability) => { + let align = layout_of(tcx, *ty)?.align(); + Ok(Tree::Ref(Ref { + lifetime: *lifetime, + ty: *ty, + mutability: *mutability, + align, + })) + } + _ => Err(Err::Unspecified), } } @@ -372,7 +383,7 @@ pub(crate) mod rustc { fn from_repr_c_variant( ty: Ty<'tcx>, adt_def: AdtDef<'tcx>, - substs_ref: SubstsRef<'tcx>, + args_ref: GenericArgsRef<'tcx>, layout_summary: &LayoutSummary, discr: Option>, variant_def: &'tcx VariantDef, @@ -416,7 +427,7 @@ pub(crate) mod rustc { // Next come fields. let fields_span = trace_span!("treeifying fields").entered(); for field_def in variant_def.fields.iter() { - let field_ty = field_def.ty(tcx, substs_ref); + let field_ty = field_def.ty(tcx, args_ref); let _span = trace_span!("treeifying field", field = ?field_ty).entered(); // begin with the field's visibility @@ -471,7 +482,7 @@ pub(crate) mod rustc { fn layout_of<'tcx>( ctx: TyCtxt<'tcx>, ty: Ty<'tcx>, - ) -> Result> { + ) -> Result> { use rustc_middle::ty::ParamEnvAnd; use rustc_target::abi::TyAndLayout; diff --git a/compiler/rustc_transmute/src/lib.rs b/compiler/rustc_transmute/src/lib.rs index 77c0526e3aabe..05ad4a4a12ab3 100644 --- a/compiler/rustc_transmute/src/lib.rs +++ b/compiler/rustc_transmute/src/lib.rs @@ -8,7 +8,7 @@ extern crate tracing; pub(crate) use rustc_data_structures::fx::{FxIndexMap as Map, FxIndexSet as Set}; -pub(crate) mod layout; +pub mod layout; pub(crate) mod maybe_transmutable; #[derive(Default)] @@ -19,29 +19,29 @@ pub struct Assume { pub validity: bool, } -/// The type encodes answers to the question: "Are these types transmutable?" -#[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)] -pub enum Answer -where - R: layout::Ref, -{ - /// `Src` is transmutable into `Dst`. +/// Either we have an error, transmutation is allowed, or we have an optional +/// Condition that must hold. +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum Answer { Yes, - - /// `Src` is NOT transmutable into `Dst`. No(Reason), + If(Condition), +} +/// A condition which must hold for safe transmutation to be possible. +#[derive(Debug, Hash, Eq, PartialEq, Clone)] +pub enum Condition { /// `Src` is transmutable into `Dst`, if `src` is transmutable into `dst`. IfTransmutable { src: R, dst: R }, /// `Src` is transmutable into `Dst`, if all of the enclosed requirements are met. - IfAll(Vec>), + IfAll(Vec>), /// `Src` is transmutable into `Dst` if any of the enclosed requirements are met. - IfAny(Vec>), + IfAny(Vec>), } -/// Answers: Why wasn't the source type transmutable into the destination type? +/// Answers "why wasn't the source type transmutable into the destination type?" #[derive(Debug, Hash, Eq, PartialEq, PartialOrd, Ord, Clone)] pub enum Reason { /// The layout of the source type is unspecified. @@ -54,6 +54,16 @@ pub enum Reason { DstIsPrivate, /// `Dst` is larger than `Src`, and the excess bytes were not exclusively uninitialized. DstIsTooBig, + /// Src should have a stricter alignment than Dst, but it does not. + DstHasStricterAlignment { src_min_align: usize, dst_min_align: usize }, + /// Can't go from shared pointer to unique pointer + DstIsMoreUnique, + /// Encountered a type error + TypeError, + /// The layout of src is unknown + SrcLayoutUnknown, + /// The layout of dst is unknown + DstLayoutUnknown, } #[cfg(feature = "rustc")] @@ -68,6 +78,7 @@ mod rustc { use rustc_middle::ty::ParamEnv; use rustc_middle::ty::Ty; use rustc_middle::ty::TyCtxt; + use rustc_middle::ty::ValTree; /// The source and destination types of a transmutation. #[derive(TypeVisitable, Debug, Clone, Copy)] @@ -138,7 +149,17 @@ mod rustc { ); let variant = adt_def.non_enum_variant(); - let fields = c.to_valtree().unwrap_branch(); + let fields = match c.try_to_valtree() { + Some(ValTree::Branch(branch)) => branch, + _ => { + return Some(Self { + alignment: true, + lifetimes: true, + safety: true, + validity: true, + }); + } + }; let get_field = |name| { let (field_idx, _) = variant diff --git a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs index 2e2fb90e71c1a..b223a90f7514b 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/mod.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/mod.rs @@ -1,13 +1,13 @@ -use crate::Map; -use crate::{Answer, Reason}; - +pub(crate) mod query_context; #[cfg(test)] mod tests; -mod query_context; -use query_context::QueryContext; +use crate::{ + layout::{self, dfa, Byte, Dfa, Nfa, Ref, Tree, Uninhabited}, + maybe_transmutable::query_context::QueryContext, + Answer, Condition, Map, Reason, +}; -use crate::layout::{self, dfa, Byte, Dfa, Nfa, Tree, Uninhabited}; pub(crate) struct MaybeTransmutableQuery where C: QueryContext, @@ -33,6 +33,7 @@ where Self { src, dst, scope, assume, context } } + // FIXME(bryangarza): Delete this when all usages are removed pub(crate) fn map_layouts( self, f: F, @@ -53,6 +54,7 @@ where } } +// FIXME: Nix this cfg, so we can write unit tests independently of rustc #[cfg(feature = "rustc")] mod rustc { use super::*; @@ -66,30 +68,26 @@ mod rustc { /// then computes an answer using those trees. #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] pub fn answer(self) -> Answer< as QueryContext>::Ref> { - let query_or_answer = self.map_layouts(|src, dst, scope, &context| { - // Convert `src` and `dst` from their rustc representations, to `Tree`-based - // representations. If these conversions fail, conclude that the transmutation is - // unacceptable; the layouts of both the source and destination types must be - // well-defined. - let src = Tree::from_ty(src, context); - let dst = Tree::from_ty(dst, context); - - match (src, dst) { - // Answer `Yes` here, because 'unknown layout' and type errors will already - // be reported by rustc. No need to spam the user with more errors. - (Err(Err::TypeError(_)), _) => Err(Answer::Yes), - (_, Err(Err::TypeError(_))) => Err(Answer::Yes), - (Err(Err::Unknown), _) => Err(Answer::Yes), - (_, Err(Err::Unknown)) => Err(Answer::Yes), - (Err(Err::Unspecified), _) => Err(Answer::No(Reason::SrcIsUnspecified)), - (_, Err(Err::Unspecified)) => Err(Answer::No(Reason::DstIsUnspecified)), - (Ok(src), Ok(dst)) => Ok((src, dst)), - } - }); + let Self { src, dst, scope, assume, context } = self; + + // Convert `src` and `dst` from their rustc representations, to `Tree`-based + // representations. If these conversions fail, conclude that the transmutation is + // unacceptable; the layouts of both the source and destination types must be + // well-defined. + let src = Tree::from_ty(src, context); + let dst = Tree::from_ty(dst, context); - match query_or_answer { - Ok(query) => query.answer(), - Err(answer) => answer, + match (src, dst) { + (Err(Err::TypeError(_)), _) | (_, Err(Err::TypeError(_))) => { + Answer::No(Reason::TypeError) + } + (Err(Err::UnknownLayout), _) => Answer::No(Reason::SrcLayoutUnknown), + (_, Err(Err::UnknownLayout)) => Answer::No(Reason::DstLayoutUnknown), + (Err(Err::Unspecified), _) => Answer::No(Reason::SrcIsUnspecified), + (_, Err(Err::Unspecified)) => Answer::No(Reason::DstIsUnspecified), + (Ok(src), Ok(dst)) => { + MaybeTransmutableQuery { src, dst, scope, assume, context }.answer() + } } } } @@ -107,6 +105,7 @@ where #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] pub(crate) fn answer(self) -> Answer<::Ref> { let assume_visibility = self.assume.safety; + // FIXME(bryangarza): Refactor this code to get rid of `map_layouts` let query_or_answer = self.map_layouts(|src, dst, scope, context| { // Remove all `Def` nodes from `src`, without checking their visibility. let src = src.prune(&|def| true); @@ -155,6 +154,7 @@ where #[inline(always)] #[instrument(level = "debug", skip(self), fields(src = ?self.src, dst = ?self.dst))] pub(crate) fn answer(self) -> Answer<::Ref> { + // FIXME(bryangarza): Refactor this code to get rid of `map_layouts` let query_or_answer = self .map_layouts(|src, dst, scope, context| Ok((Dfa::from_nfa(src), Dfa::from_nfa(dst)))); @@ -203,8 +203,29 @@ where if let Some(answer) = cache.get(&(src_state, dst_state)) { answer.clone() } else { + debug!(?src_state, ?dst_state); + debug!(src = ?self.src); + debug!(dst = ?self.dst); + debug!( + src_transitions_len = self.src.transitions.len(), + dst_transitions_len = self.dst.transitions.len() + ); let answer = if dst_state == self.dst.accepting { // truncation: `size_of(Src) >= size_of(Dst)` + // + // Why is truncation OK to do? Because even though the Src is bigger, all we care about + // is whether we have enough data for the Dst to be valid in accordance with what its + // type dictates. + // For example, in a u8 to `()` transmutation, we have enough data available from the u8 + // to transmute it to a `()` (though in this case does `()` really need any data to + // begin with? It doesn't). Same thing with u8 to fieldless struct. + // Now then, why is something like u8 to bool not allowed? That is not because the bool + // is smaller in size, but rather because those 2 bits that we are re-interpreting from + // the u8 could introduce invalid states for the bool type. + // + // So, if it's possible to transmute to a smaller Dst by truncating, and we can guarantee + // that none of the actually-used data can introduce an invalid state for Dst's type, we + // are able to safely transmute, even with truncation. Answer::Yes } else if src_state == self.src.accepting { // extension: `size_of(Src) >= size_of(Dst)` @@ -214,108 +235,201 @@ where Answer::No(Reason::DstIsTooBig) } } else { - let src_quantification = if self.assume.validity { + let src_quantifier = if self.assume.validity { // if the compiler may assume that the programmer is doing additional validity checks, // (e.g.: that `src != 3u8` when the destination type is `bool`) // then there must exist at least one transition out of `src_state` such that the transmute is viable... - there_exists + Quantifier::ThereExists } else { // if the compiler cannot assume that the programmer is doing additional validity checks, // then for all transitions out of `src_state`, such that the transmute is viable... - // then there must exist at least one transition out of `src_state` such that the transmute is viable... - for_all + // then there must exist at least one transition out of `dst_state` such that the transmute is viable... + Quantifier::ForAll + }; + + let bytes_answer = src_quantifier.apply( + // for each of the byte transitions out of the `src_state`... + self.src.bytes_from(src_state).unwrap_or(&Map::default()).into_iter().map( + |(&src_validity, &src_state_prime)| { + // ...try to find a matching transition out of `dst_state`. + if let Some(dst_state_prime) = + self.dst.byte_from(dst_state, src_validity) + { + self.answer_memo(cache, src_state_prime, dst_state_prime) + } else if let Some(dst_state_prime) = + // otherwise, see if `dst_state` has any outgoing `Uninit` transitions + // (any init byte is a valid uninit byte) + self.dst.byte_from(dst_state, Byte::Uninit) + { + self.answer_memo(cache, src_state_prime, dst_state_prime) + } else { + // otherwise, we've exhausted our options. + // the DFAs, from this point onwards, are bit-incompatible. + Answer::No(Reason::DstIsBitIncompatible) + } + }, + ), + ); + + // The below early returns reflect how this code would behave: + // if self.assume.validity { + // or(bytes_answer, refs_answer) + // } else { + // and(bytes_answer, refs_answer) + // } + // ...if `refs_answer` was computed lazily. The below early + // returns can be deleted without impacting the correctness of + // the algoritm; only its performance. + debug!(?bytes_answer); + match bytes_answer { + Answer::No(_) if !self.assume.validity => return bytes_answer, + Answer::Yes if self.assume.validity => return bytes_answer, + _ => {} }; - src_quantification( - self.src.bytes_from(src_state).unwrap_or(&Map::default()), - |(&src_validity, &src_state_prime)| { - if let Some(dst_state_prime) = self.dst.byte_from(dst_state, src_validity) { - self.answer_memo(cache, src_state_prime, dst_state_prime) - } else if let Some(dst_state_prime) = - self.dst.byte_from(dst_state, Byte::Uninit) - { - self.answer_memo(cache, src_state_prime, dst_state_prime) - } else { - Answer::No(Reason::DstIsBitIncompatible) - } - }, - ) + let refs_answer = src_quantifier.apply( + // for each reference transition out of `src_state`... + self.src.refs_from(src_state).unwrap_or(&Map::default()).into_iter().map( + |(&src_ref, &src_state_prime)| { + // ...there exists a reference transition out of `dst_state`... + Quantifier::ThereExists.apply( + self.dst + .refs_from(dst_state) + .unwrap_or(&Map::default()) + .into_iter() + .map(|(&dst_ref, &dst_state_prime)| { + if !src_ref.is_mutable() && dst_ref.is_mutable() { + Answer::No(Reason::DstIsMoreUnique) + } else if !self.assume.alignment + && src_ref.min_align() < dst_ref.min_align() + { + Answer::No(Reason::DstHasStricterAlignment { + src_min_align: src_ref.min_align(), + dst_min_align: dst_ref.min_align(), + }) + } else { + // ...such that `src` is transmutable into `dst`, if + // `src_ref` is transmutability into `dst_ref`. + and( + Answer::If(Condition::IfTransmutable { + src: src_ref, + dst: dst_ref, + }), + self.answer_memo( + cache, + src_state_prime, + dst_state_prime, + ), + ) + } + }), + ) + }, + ), + ); + + if self.assume.validity { + or(bytes_answer, refs_answer) + } else { + and(bytes_answer, refs_answer) + } }; - cache.insert((src_state, dst_state), answer.clone()); + if let Some(..) = cache.insert((src_state, dst_state), answer.clone()) { + panic!("failed to correctly cache transmutability") + } answer } } } -impl Answer +fn and(lhs: Answer, rhs: Answer) -> Answer where - R: layout::Ref, + R: PartialEq, { - pub(crate) fn and(self, rhs: Self) -> Self { - match (self, rhs) { - (Self::No(reason), _) | (_, Self::No(reason)) => Self::No(reason), - (Self::Yes, Self::Yes) => Self::Yes, - (Self::IfAll(mut lhs), Self::IfAll(ref mut rhs)) => { - lhs.append(rhs); - Self::IfAll(lhs) - } - (constraint, Self::IfAll(mut constraints)) - | (Self::IfAll(mut constraints), constraint) => { - constraints.push(constraint); - Self::IfAll(constraints) - } - (lhs, rhs) => Self::IfAll(vec![lhs, rhs]), + match (lhs, rhs) { + // If both are errors, then we should return the more specific one + (Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason)) + | (Answer::No(reason), Answer::No(_)) + // If either is an error, return it + | (Answer::No(reason), _) | (_, Answer::No(reason)) => Answer::No(reason), + // If only one side has a condition, pass it along + | (Answer::Yes, other) | (other, Answer::Yes) => other, + // If both sides have IfAll conditions, merge them + (Answer::If(Condition::IfAll(mut lhs)), Answer::If(Condition::IfAll(ref mut rhs))) => { + lhs.append(rhs); + Answer::If(Condition::IfAll(lhs)) } - } - - pub(crate) fn or(self, rhs: Self) -> Self { - match (self, rhs) { - (Self::Yes, _) | (_, Self::Yes) => Self::Yes, - (Self::No(lhr), Self::No(rhr)) => Self::No(lhr), - (Self::IfAny(mut lhs), Self::IfAny(ref mut rhs)) => { - lhs.append(rhs); - Self::IfAny(lhs) - } - (constraint, Self::IfAny(mut constraints)) - | (Self::IfAny(mut constraints), constraint) => { - constraints.push(constraint); - Self::IfAny(constraints) - } - (lhs, rhs) => Self::IfAny(vec![lhs, rhs]), + // If only one side is an IfAll, add the other Condition to it + (Answer::If(cond), Answer::If(Condition::IfAll(mut conds))) + | (Answer::If(Condition::IfAll(mut conds)), Answer::If(cond)) => { + conds.push(cond); + Answer::If(Condition::IfAll(conds)) } + // Otherwise, both lhs and rhs conditions can be combined in a parent IfAll + (Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAll(vec![lhs, rhs])), } } -pub fn for_all(iter: I, f: F) -> Answer +fn or(lhs: Answer, rhs: Answer) -> Answer where - R: layout::Ref, - I: IntoIterator, - F: FnMut(::Item) -> Answer, + R: PartialEq, { - use std::ops::ControlFlow::{Break, Continue}; - let (Continue(result) | Break(result)) = - iter.into_iter().map(f).try_fold(Answer::Yes, |constraints, constraint| { - match constraint.and(constraints) { - Answer::No(reason) => Break(Answer::No(reason)), - maybe => Continue(maybe), - } - }); - result + match (lhs, rhs) { + // If both are errors, then we should return the more specific one + (Answer::No(Reason::DstIsBitIncompatible), Answer::No(reason)) + | (Answer::No(reason), Answer::No(_)) => Answer::No(reason), + // Otherwise, errors can be ignored for the rest of the pattern matching + (Answer::No(_), other) | (other, Answer::No(_)) => or(other, Answer::Yes), + // If only one side has a condition, pass it along + (Answer::Yes, other) | (other, Answer::Yes) => other, + // If both sides have IfAny conditions, merge them + (Answer::If(Condition::IfAny(mut lhs)), Answer::If(Condition::IfAny(ref mut rhs))) => { + lhs.append(rhs); + Answer::If(Condition::IfAny(lhs)) + } + // If only one side is an IfAny, add the other Condition to it + (Answer::If(cond), Answer::If(Condition::IfAny(mut conds))) + | (Answer::If(Condition::IfAny(mut conds)), Answer::If(cond)) => { + conds.push(cond); + Answer::If(Condition::IfAny(conds)) + } + // Otherwise, both lhs and rhs conditions can be combined in a parent IfAny + (Answer::If(lhs), Answer::If(rhs)) => Answer::If(Condition::IfAny(vec![lhs, rhs])), + } } -pub fn there_exists(iter: I, f: F) -> Answer -where - R: layout::Ref, - I: IntoIterator, - F: FnMut(::Item) -> Answer, -{ - use std::ops::ControlFlow::{Break, Continue}; - let (Continue(result) | Break(result)) = iter.into_iter().map(f).try_fold( - Answer::No(Reason::DstIsBitIncompatible), - |constraints, constraint| match constraint.or(constraints) { - Answer::Yes => Break(Answer::Yes), - maybe => Continue(maybe), - }, - ); - result +pub enum Quantifier { + ThereExists, + ForAll, +} + +impl Quantifier { + pub fn apply(&self, iter: I) -> Answer + where + R: layout::Ref, + I: IntoIterator>, + { + use std::ops::ControlFlow::{Break, Continue}; + + let (init, try_fold_f): (_, fn(_, _) -> _) = match self { + Self::ThereExists => { + (Answer::No(Reason::DstIsBitIncompatible), |accum: Answer, next| { + match or(accum, next) { + Answer::Yes => Break(Answer::Yes), + maybe => Continue(maybe), + } + }) + } + Self::ForAll => (Answer::Yes, |accum: Answer, next| { + let answer = and(accum, next); + match answer { + Answer::No(_) => Break(answer), + maybe => Continue(maybe), + } + }), + }; + + let (Continue(result) | Break(result)) = iter.into_iter().try_fold(init, try_fold_f); + result + } } diff --git a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs index a8675f4ae37d8..e49bebf571dea 100644 --- a/compiler/rustc_transmute/src/maybe_transmutable/tests.rs +++ b/compiler/rustc_transmute/src/maybe_transmutable/tests.rs @@ -1,9 +1,11 @@ use super::query_context::test::{Def, UltraMinimal}; use crate::maybe_transmutable::MaybeTransmutableQuery; -use crate::{layout, Answer, Reason}; +use crate::{layout, Reason}; use itertools::Itertools; mod bool { + use crate::Answer; + use super::*; #[test] diff --git a/compiler/rustc_ty_utils/Cargo.toml b/compiler/rustc_ty_utils/Cargo.toml index 51885c9b47eb8..50dac3a37a4a0 100644 --- a/compiler/rustc_ty_utils/Cargo.toml +++ b/compiler/rustc_ty_utils/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] tracing = "0.1" +itertools = "0.10.1" rustc_middle = { path = "../rustc_middle" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } diff --git a/compiler/rustc_ty_utils/messages.ftl b/compiler/rustc_ty_utils/messages.ftl index 5bc3e3c00c9af..c416aa52a24a4 100644 --- a/compiler/rustc_ty_utils/messages.ftl +++ b/compiler/rustc_ty_utils/messages.ftl @@ -1,65 +1,65 @@ -ty_utils_needs_drop_overflow = overflow while checking whether `{$query_ty}` requires drop +ty_utils_address_and_deref_not_supported = dereferencing or taking the address is not supported in generic constants -ty_utils_generic_constant_too_complex = overly complex generic constant - .help = consider moving this anonymous constant into a `const` function - .maybe_supported = this operation may be supported in the future +ty_utils_adt_not_supported = struct/enum construction is not supported in generic constants -ty_utils_borrow_not_supported = borrowing is not supported in generic constants +ty_utils_array_not_supported = array construction is not supported in generic constants -ty_utils_address_and_deref_not_supported = dereferencing or taking the address is not supported in generic constants +ty_utils_assign_not_supported = assignment is not supported in generic constants -ty_utils_array_not_supported = array construction is not supported in generic constants +ty_utils_binary_not_supported = unsupported binary operation in generic constants ty_utils_block_not_supported = blocks are not supported in generic constants -ty_utils_never_to_any_not_supported = coercing the `never` type is not supported in generic constants - -ty_utils_tuple_not_supported = tuple construction is not supported in generic constants +ty_utils_borrow_not_supported = borrowing is not supported in generic constants -ty_utils_index_not_supported = indexing is not supported in generic constants +ty_utils_box_not_supported = allocations are not allowed in generic constants -ty_utils_field_not_supported = field access is not supported in generic constants +ty_utils_closure_and_return_not_supported = closures and function keywords are not supported in generic constants ty_utils_const_block_not_supported = const blocks are not supported in generic constants -ty_utils_adt_not_supported = struct/enum construction is not supported in generic constants +ty_utils_control_flow_not_supported = control flow is not supported in generic constants -ty_utils_pointer_not_supported = pointer casts are not allowed in generic constants +ty_utils_field_not_supported = field access is not supported in generic constants -ty_utils_yield_not_supported = generator control flow is not allowed in generic constants +ty_utils_generic_constant_too_complex = overly complex generic constant + .help = consider moving this anonymous constant into a `const` function + .maybe_supported = this operation may be supported in the future -ty_utils_loop_not_supported = loops and loop control flow are not supported in generic constants +ty_utils_impl_trait_duplicate_arg = non-defining opaque type use in defining scope + .label = generic argument `{$arg}` used twice + .note = for this opaque type -ty_utils_box_not_supported = allocations are not allowed in generic constants +ty_utils_impl_trait_not_param = non-defining opaque type use in defining scope + .label = argument `{$arg}` is not a generic parameter + .note = for this opaque type -ty_utils_binary_not_supported = unsupported binary operation in generic constants +ty_utils_index_not_supported = indexing is not supported in generic constants + +ty_utils_inline_asm_not_supported = assembly is not supported in generic constants ty_utils_logical_op_not_supported = unsupported operation in generic constants, short-circuiting operations would imply control flow -ty_utils_assign_not_supported = assignment is not supported in generic constants +ty_utils_loop_not_supported = loops and loop control flow are not supported in generic constants -ty_utils_closure_and_return_not_supported = closures and function keywords are not supported in generic constants +ty_utils_multiple_array_fields_simd_type = monomorphising SIMD type `{$ty}` with more than one array field -ty_utils_control_flow_not_supported = control flow is not supported in generic constants +ty_utils_needs_drop_overflow = overflow while checking whether `{$query_ty}` requires drop -ty_utils_inline_asm_not_supported = assembly is not supported in generic constants +ty_utils_never_to_any_not_supported = coercing the `never` type is not supported in generic constants -ty_utils_operation_not_supported = unsupported operation in generic constants +ty_utils_non_primitive_simd_type = monomorphising SIMD type `{$ty}` with a non-primitive-scalar (integer/float/pointer) element type `{$e_ty}` -ty_utils_unexpected_fnptr_associated_item = `FnPtr` trait with unexpected associated item +ty_utils_operation_not_supported = unsupported operation in generic constants -ty_utils_zero_length_simd_type = monomorphising SIMD type `{$ty}` of zero length +ty_utils_oversized_simd_type = monomorphising SIMD type `{$ty}` of length greater than {$max_lanes} -ty_utils_multiple_array_fields_simd_type = monomorphising SIMD type `{$ty}` with more than one array field +ty_utils_pointer_not_supported = pointer casts are not allowed in generic constants -ty_utils_oversized_simd_type = monomorphising SIMD type `{$ty}` of length greater than {$max_lanes} +ty_utils_tuple_not_supported = tuple construction is not supported in generic constants -ty_utils_non_primitive_simd_type = monomorphising SIMD type `{$ty}` with a non-primitive-scalar (integer/float/pointer) element type `{$e_ty}` +ty_utils_unexpected_fnptr_associated_item = `FnPtr` trait with unexpected associated item -ty_utils_impl_trait_duplicate_arg = non-defining opaque type use in defining scope - .label = generic argument `{$arg}` used twice - .note = for this opaque type +ty_utils_yield_not_supported = generator control flow is not allowed in generic constants -ty_utils_impl_trait_not_param = non-defining opaque type use in defining scope - .label = argument `{$arg}` is not a generic parameter - .note = for this opaque type +ty_utils_zero_length_simd_type = monomorphising SIMD type `{$ty}` of zero length diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 15c1910461666..4d0b847533b82 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -9,6 +9,7 @@ use rustc_session::config::OptLevel; use rustc_span::def_id::DefId; use rustc_target::abi::call::{ ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, Conv, FnAbi, PassMode, Reg, RegKind, + RiscvInterruptKind, }; use rustc_target::abi::*; use rustc_target::spec::abi::Abi as SpecAbi; @@ -51,12 +52,12 @@ fn fn_sig_for_fn_abi<'tcx>( // // We normalize the `fn_sig` again after substituting at a later point. let mut sig = match *ty.kind() { - ty::FnDef(def_id, substs) => tcx + ty::FnDef(def_id, args) => tcx .fn_sig(def_id) .map_bound(|fn_sig| { tcx.normalize_erasing_regions(tcx.param_env(def_id), fn_sig) }) - .subst(tcx, substs), + .instantiate(tcx, args), _ => unreachable!(), }; @@ -64,15 +65,15 @@ fn fn_sig_for_fn_abi<'tcx>( // Modify `fn(self, ...)` to `fn(self: *mut Self, ...)`. sig = sig.map_bound(|mut sig| { let mut inputs_and_output = sig.inputs_and_output.to_vec(); - inputs_and_output[0] = tcx.mk_mut_ptr(inputs_and_output[0]); + inputs_and_output[0] = Ty::new_mut_ptr(tcx, inputs_and_output[0]); sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output); sig }); } sig } - ty::Closure(def_id, substs) => { - let sig = substs.as_closure().sig(); + ty::Closure(def_id, args) => { + let sig = args.as_closure().sig(); let bound_vars = tcx.mk_bound_variable_kinds_from_iter( sig.bound_vars().iter().chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))), @@ -81,8 +82,8 @@ fn fn_sig_for_fn_abi<'tcx>( var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::BrEnv, }; - let env_region = tcx.mk_re_late_bound(ty::INNERMOST, br); - let env_ty = tcx.closure_env_ty(def_id, substs, env_region).unwrap(); + let env_region = ty::Region::new_late_bound(tcx, ty::INNERMOST, br); + let env_ty = tcx.closure_env_ty(def_id, args, env_region).unwrap(); let sig = sig.skip_binder(); ty::Binder::bind_with_vars( @@ -96,8 +97,8 @@ fn fn_sig_for_fn_abi<'tcx>( bound_vars, ) } - ty::Generator(did, substs, _) => { - let sig = substs.as_generator().poly_sig(); + ty::Generator(did, args, _) => { + let sig = args.as_generator().poly_sig(); let bound_vars = tcx.mk_bound_variable_kinds_from_iter( sig.bound_vars().iter().chain(iter::once(ty::BoundVariableKind::Region(ty::BrEnv))), @@ -106,12 +107,13 @@ fn fn_sig_for_fn_abi<'tcx>( var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::BrEnv, }; - let env_ty = tcx.mk_mut_ref(tcx.mk_re_late_bound(ty::INNERMOST, br), ty); + let env_ty = + Ty::new_mut_ref(tcx, ty::Region::new_late_bound(tcx, ty::INNERMOST, br), ty); let pin_did = tcx.require_lang_item(LangItem::Pin, None); let pin_adt_ref = tcx.adt_def(pin_did); - let pin_substs = tcx.mk_substs(&[env_ty.into()]); - let env_ty = tcx.mk_adt(pin_adt_ref, pin_substs); + let pin_args = tcx.mk_args(&[env_ty.into()]); + let env_ty = Ty::new_adt(tcx, pin_adt_ref, pin_args); let sig = sig.skip_binder(); // The `FnSig` and the `ret_ty` here is for a generators main @@ -122,8 +124,8 @@ fn fn_sig_for_fn_abi<'tcx>( // The signature should be `Future::poll(_, &mut Context<'_>) -> Poll` let poll_did = tcx.require_lang_item(LangItem::Poll, None); let poll_adt_ref = tcx.adt_def(poll_did); - let poll_substs = tcx.mk_substs(&[sig.return_ty.into()]); - let ret_ty = tcx.mk_adt(poll_adt_ref, poll_substs); + let poll_args = tcx.mk_args(&[sig.return_ty.into()]); + let ret_ty = Ty::new_adt(tcx, poll_adt_ref, poll_args); // We have to replace the `ResumeTy` that is used for type and borrow checking // with `&mut Context<'_>` which is used in codegen. @@ -137,15 +139,15 @@ fn fn_sig_for_fn_abi<'tcx>( panic!("expected `ResumeTy`, found `{:?}`", sig.resume_ty); }; } - let context_mut_ref = tcx.mk_task_context(); + let context_mut_ref = Ty::new_task_context(tcx); (context_mut_ref, ret_ty) } else { // The signature should be `Generator::resume(_, Resume) -> GeneratorState` let state_did = tcx.require_lang_item(LangItem::GeneratorState, None); let state_adt_ref = tcx.adt_def(state_did); - let state_substs = tcx.mk_substs(&[sig.yield_ty.into(), sig.return_ty.into()]); - let ret_ty = tcx.mk_adt(state_adt_ref, state_substs); + let state_args = tcx.mk_args(&[sig.yield_ty.into(), sig.return_ty.into()]); + let ret_ty = Ty::new_adt(tcx, state_adt_ref, state_args); (sig.resume_ty, ret_ty) }; @@ -192,6 +194,8 @@ fn conv_from_spec_abi(tcx: TyCtxt<'_>, abi: SpecAbi) -> Conv { AmdGpuKernel => Conv::AmdGpuKernel, AvrInterrupt => Conv::AvrInterrupt, AvrNonBlockingInterrupt => Conv::AvrNonBlockingInterrupt, + RiscvInterruptM => Conv::RiscvInterrupt { kind: RiscvInterruptKind::Machine }, + RiscvInterruptS => Conv::RiscvInterrupt { kind: RiscvInterruptKind::Supervisor }, Wasm => Conv::C, // These API constants ought to be more specific... @@ -202,7 +206,7 @@ fn conv_from_spec_abi(tcx: TyCtxt<'_>, abi: SpecAbi) -> Conv { fn fn_abi_of_fn_ptr<'tcx>( tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List>)>, -) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, FnAbiError<'tcx>> { +) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { let (param_env, (sig, extra_args)) = query.into_parts(); let cx = LayoutCx { tcx, param_env }; @@ -212,7 +216,7 @@ fn fn_abi_of_fn_ptr<'tcx>( fn fn_abi_of_instance<'tcx>( tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, (ty::Instance<'tcx>, &'tcx ty::List>)>, -) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, FnAbiError<'tcx>> { +) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { let (param_env, (instance, extra_args)) = query.into_parts(); let sig = fn_sig_for_fn_abi(tcx, instance, param_env); @@ -331,7 +335,7 @@ fn fn_abi_new_uncached<'tcx>( fn_def_id: Option, // FIXME(eddyb) replace this with something typed, like an `enum`. force_thin_self_ptr: bool, -) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, FnAbiError<'tcx>> { +) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> { let sig = cx.tcx.normalize_erasing_late_bound_regions(cx.param_env, sig); let conv = conv_from_spec_abi(cx.tcx(), sig.abi); @@ -376,7 +380,7 @@ fn fn_abi_new_uncached<'tcx>( let is_drop_in_place = fn_def_id.is_some() && fn_def_id == cx.tcx.lang_items().drop_in_place_fn(); - let arg_of = |ty: Ty<'tcx>, arg_idx: Option| -> Result<_, FnAbiError<'tcx>> { + let arg_of = |ty: Ty<'tcx>, arg_idx: Option| -> Result<_, &'tcx FnAbiError<'tcx>> { let span = tracing::debug_span!("arg_of"); let _entered = span.enter(); let is_return = arg_idx.is_none(); @@ -386,7 +390,8 @@ fn fn_abi_new_uncached<'tcx>( _ => bug!("argument to drop_in_place is not a raw ptr: {:?}", ty), }); - let layout = cx.layout_of(ty)?; + let layout = + cx.layout_of(ty).map_err(|err| &*cx.tcx.arena.alloc(FnAbiError::Layout(*err)))?; let layout = if force_thin_self_ptr && arg_idx == Some(0) { // Don't pass the vtable, it's not an argument of the virtual fn. // Instead, pass just the data pointer, but give it the type `*const/mut dyn Trait` @@ -454,7 +459,7 @@ fn fn_abi_adjust_for_abi<'tcx>( fn_abi: &mut FnAbi<'tcx, Ty<'tcx>>, abi: SpecAbi, fn_def_id: Option, -) -> Result<(), FnAbiError<'tcx>> { +) -> Result<(), &'tcx FnAbiError<'tcx>> { if abi == SpecAbi::Unadjusted { return Ok(()); } @@ -548,7 +553,9 @@ fn fn_abi_adjust_for_abi<'tcx>( fixup(arg, Some(arg_idx)); } } else { - fn_abi.adjust_for_foreign_abi(cx, abi)?; + fn_abi + .adjust_for_foreign_abi(cx, abi) + .map_err(|err| &*cx.tcx.arena.alloc(FnAbiError::AdjustForForeignAbi(err)))?; } Ok(()) @@ -563,7 +570,7 @@ fn make_thin_self_ptr<'tcx>( let fat_pointer_ty = if layout.is_unsized() { // unsized `self` is passed as a pointer to `self` // FIXME (mikeyhew) change this to use &own if it is ever added to the language - tcx.mk_mut_ptr(layout.ty) + Ty::new_mut_ptr(tcx, layout.ty) } else { match layout.abi { Abi::ScalarPair(..) | Abi::Scalar(..) => (), @@ -597,7 +604,7 @@ fn make_thin_self_ptr<'tcx>( // we now have a type like `*mut RcBox` // change its layout to that of `*mut ()`, a thin pointer, but keep the same type // this is understood as a special case elsewhere in the compiler - let unit_ptr_ty = tcx.mk_mut_ptr(tcx.mk_unit()); + let unit_ptr_ty = Ty::new_mut_ptr(tcx, Ty::new_unit(tcx)); TyAndLayout { ty: fat_pointer_ty, diff --git a/compiler/rustc_ty_utils/src/assoc.rs b/compiler/rustc_ty_utils/src/assoc.rs index ed574f22e61d0..780f7ea426f7b 100644 --- a/compiler/rustc_ty_utils/src/assoc.rs +++ b/compiler/rustc_ty_utils/src/assoc.rs @@ -5,7 +5,7 @@ use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId}; use rustc_hir::definitions::DefPathData; use rustc_hir::intravisit::{self, Visitor}; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, ImplTraitInTraitData, InternalSubsts, TyCtxt}; +use rustc_middle::ty::{self, GenericArgs, ImplTraitInTraitData, Ty, TyCtxt}; use rustc_span::symbol::kw; pub fn provide(providers: &mut Providers) { @@ -24,70 +24,54 @@ fn associated_item_def_ids(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &[DefId] { let item = tcx.hir().expect_item(def_id); match item.kind { hir::ItemKind::Trait(.., ref trait_item_refs) => { - if tcx.lower_impl_trait_in_trait_to_assoc_ty() { - // We collect RPITITs for each trait method's return type and create a - // corresponding associated item using associated_types_for_impl_traits_in_associated_fn - // query. - tcx.arena.alloc_from_iter( - trait_item_refs - .iter() - .map(|trait_item_ref| trait_item_ref.id.owner_id.to_def_id()) - .chain( - trait_item_refs - .iter() - .filter(|trait_item_ref| { - matches!(trait_item_ref.kind, hir::AssocItemKind::Fn { .. }) - }) - .flat_map(|trait_item_ref| { - let trait_fn_def_id = - trait_item_ref.id.owner_id.def_id.to_def_id(); - tcx.associated_types_for_impl_traits_in_associated_fn( - trait_fn_def_id, - ) - }) - .map(|def_id| *def_id), - ), - ) - } else { - tcx.arena.alloc_from_iter( - trait_item_refs - .iter() - .map(|trait_item_ref| trait_item_ref.id.owner_id.to_def_id()), - ) - } + // We collect RPITITs for each trait method's return type and create a + // corresponding associated item using associated_types_for_impl_traits_in_associated_fn + // query. + tcx.arena.alloc_from_iter( + trait_item_refs + .iter() + .map(|trait_item_ref| trait_item_ref.id.owner_id.to_def_id()) + .chain( + trait_item_refs + .iter() + .filter(|trait_item_ref| { + matches!(trait_item_ref.kind, hir::AssocItemKind::Fn { .. }) + }) + .flat_map(|trait_item_ref| { + let trait_fn_def_id = trait_item_ref.id.owner_id.def_id.to_def_id(); + tcx.associated_types_for_impl_traits_in_associated_fn( + trait_fn_def_id, + ) + }) + .map(|def_id| *def_id), + ), + ) } hir::ItemKind::Impl(ref impl_) => { - if tcx.lower_impl_trait_in_trait_to_assoc_ty() { - // We collect RPITITs for each trait method's return type, on the impl side too and - // create a corresponding associated item using - // associated_types_for_impl_traits_in_associated_fn query. - tcx.arena.alloc_from_iter( - impl_ - .items - .iter() - .map(|impl_item_ref| impl_item_ref.id.owner_id.to_def_id()) - .chain(impl_.of_trait.iter().flat_map(|_| { - impl_ - .items - .iter() - .filter(|impl_item_ref| { - matches!(impl_item_ref.kind, hir::AssocItemKind::Fn { .. }) - }) - .flat_map(|impl_item_ref| { - let impl_fn_def_id = - impl_item_ref.id.owner_id.def_id.to_def_id(); - tcx.associated_types_for_impl_traits_in_associated_fn( - impl_fn_def_id, - ) - }) - .map(|def_id| *def_id) - })), - ) - } else { - tcx.arena.alloc_from_iter( - impl_.items.iter().map(|impl_item_ref| impl_item_ref.id.owner_id.to_def_id()), - ) - } + // We collect RPITITs for each trait method's return type, on the impl side too and + // create a corresponding associated item using + // associated_types_for_impl_traits_in_associated_fn query. + tcx.arena.alloc_from_iter( + impl_ + .items + .iter() + .map(|impl_item_ref| impl_item_ref.id.owner_id.to_def_id()) + .chain(impl_.of_trait.iter().flat_map(|_| { + impl_ + .items + .iter() + .filter(|impl_item_ref| { + matches!(impl_item_ref.kind, hir::AssocItemKind::Fn { .. }) + }) + .flat_map(|impl_item_ref| { + let impl_fn_def_id = impl_item_ref.id.owner_id.def_id.to_def_id(); + tcx.associated_types_for_impl_traits_in_associated_fn( + impl_fn_def_id, + ) + }) + .map(|def_id| *def_id) + })), + ) } _ => span_bug!(item.span, "associated_item_def_ids: not impl or trait"), } @@ -231,7 +215,9 @@ fn associated_types_for_impl_traits_in_associated_fn( } DefKind::Impl { .. } => { - let Some(trait_fn_def_id) = tcx.associated_item(fn_def_id).trait_item_def_id else { return &[] }; + let Some(trait_fn_def_id) = tcx.associated_item(fn_def_id).trait_item_def_id else { + return &[]; + }; tcx.arena.alloc_from_iter( tcx.associated_types_for_impl_traits_in_associated_fn(trait_fn_def_id).iter().map( @@ -259,7 +245,7 @@ fn associated_type_for_impl_trait_in_trait( opaque_ty_def_id: LocalDefId, ) -> LocalDefId { let (hir::OpaqueTyOrigin::FnReturn(fn_def_id) | hir::OpaqueTyOrigin::AsyncFn(fn_def_id)) = - tcx.hir().expect_item(opaque_ty_def_id).expect_opaque_ty().origin + tcx.opaque_type_origin(opaque_ty_def_id) else { bug!("expected opaque for {opaque_ty_def_id:?}"); }; @@ -297,13 +283,14 @@ fn associated_type_for_impl_trait_in_trait( // Copy visility of the containing function. trait_assoc_ty.visibility(tcx.visibility(fn_def_id)); - // Copy impl_defaultness of the containing function. - trait_assoc_ty.impl_defaultness(tcx.impl_defaultness(fn_def_id)); + // Copy defaultness of the containing function. + trait_assoc_ty.defaultness(tcx.defaultness(fn_def_id)); // Copy type_of of the opaque. - trait_assoc_ty.type_of(ty::EarlyBinder(tcx.mk_opaque( + trait_assoc_ty.type_of(ty::EarlyBinder::bind(Ty::new_opaque( + tcx, opaque_ty_def_id.to_def_id(), - InternalSubsts::identity_for_item(tcx, opaque_ty_def_id), + GenericArgs::identity_for_item(tcx, opaque_ty_def_id), ))); trait_assoc_ty.is_type_alias_impl_trait(false); @@ -337,15 +324,10 @@ fn associated_type_for_impl_trait_in_trait( param_def_id_to_index, has_self: opaque_ty_generics.has_self, has_late_bound_regions: opaque_ty_generics.has_late_bound_regions, + host_effect_index: parent_generics.host_effect_index, } }); - // There are no predicates for the synthesized associated type. - trait_assoc_ty.explicit_predicates_of(ty::GenericPredicates { - parent: Some(trait_def_id.to_def_id()), - predicates: &[], - }); - // There are no inferred outlives for the synthesized associated type. trait_assoc_ty.inferred_outlives_of(&[]); @@ -364,8 +346,16 @@ fn associated_type_for_impl_trait_in_impl( ) -> LocalDefId { let impl_local_def_id = tcx.local_parent(impl_fn_def_id); - // FIXME fix the span, we probably want the def_id of the return type of the function - let span = tcx.def_span(impl_fn_def_id); + let decl = tcx + .hir() + .find_by_def_id(impl_fn_def_id) + .expect("expected item") + .fn_decl() + .expect("expected decl"); + let span = match decl.output { + hir::FnRetTy::DefaultReturn(_) => tcx.def_span(impl_fn_def_id), + hir::FnRetTy::Return(ty) => ty.span, + }; let impl_assoc_ty = tcx.at(span).create_def(impl_local_def_id, DefPathData::ImplTraitAssocTy); let local_def_id = impl_assoc_ty.def_id(); @@ -393,8 +383,8 @@ fn associated_type_for_impl_trait_in_impl( // Copy visility of the containing function. impl_assoc_ty.visibility(tcx.visibility(impl_fn_def_id)); - // Copy impl_defaultness of the containing function. - impl_assoc_ty.impl_defaultness(tcx.impl_defaultness(impl_fn_def_id)); + // Copy defaultness of the containing function. + impl_assoc_ty.defaultness(tcx.defaultness(impl_fn_def_id)); // Copy generics_of the trait's associated item but the impl as the parent. // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty) resolves to the trait instead of the impl @@ -421,15 +411,10 @@ fn associated_type_for_impl_trait_in_impl( param_def_id_to_index, has_self: false, has_late_bound_regions: trait_assoc_generics.has_late_bound_regions, + host_effect_index: parent_generics.host_effect_index, } }); - // There are no predicates for the synthesized associated type. - impl_assoc_ty.explicit_predicates_of(ty::GenericPredicates { - parent: Some(impl_local_def_id.to_def_id()), - predicates: &[], - }); - // There are no inferred outlives for the synthesized associated type. impl_assoc_ty.inferred_outlives_of(&[]); diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index 1219bb4009886..383cc996b9e69 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -33,14 +33,16 @@ pub(crate) fn destructure_const<'tcx>( let (fields, variant) = match const_.ty().kind() { ty::Array(inner_ty, _) | ty::Slice(inner_ty) => { // construct the consts for the elements of the array/slice - let field_consts = - branches.iter().map(|b| tcx.mk_const(*b, *inner_ty)).collect::>(); + let field_consts = branches + .iter() + .map(|b| ty::Const::new_value(tcx, *b, *inner_ty)) + .collect::>(); debug!(?field_consts); (field_consts, None) } ty::Adt(def, _) if def.variants().is_empty() => bug!("unreachable"), - ty::Adt(def, substs) => { + ty::Adt(def, args) => { let (variant_idx, branches) = if def.is_enum() { let (head, rest) = branches.split_first().unwrap(); (VariantIdx::from_u32(head.unwrap_leaf().try_to_u32().unwrap()), rest) @@ -51,8 +53,8 @@ pub(crate) fn destructure_const<'tcx>( let mut field_consts = Vec::with_capacity(fields.len()); for (field, field_valtree) in iter::zip(fields, branches) { - let field_ty = field.ty(tcx, substs); - let field_const = tcx.mk_const(*field_valtree, field_ty); + let field_ty = field.ty(tcx, args); + let field_const = ty::Const::new_value(tcx, *field_valtree, field_ty); field_consts.push(field_const); } debug!(?field_consts); @@ -61,7 +63,7 @@ pub(crate) fn destructure_const<'tcx>( } ty::Tuple(elem_tys) => { let fields = iter::zip(*elem_tys, branches) - .map(|(elem_ty, elem_valtree)| tcx.mk_const(*elem_valtree, elem_ty)) + .map(|(elem_ty, elem_valtree)| ty::Const::new_value(tcx, *elem_valtree, elem_ty)) .collect::>(); (fields, None) @@ -78,8 +80,9 @@ pub(crate) fn destructure_const<'tcx>( fn check_binop(op: mir::BinOp) -> bool { use mir::BinOp::*; match op { - Add | Sub | Mul | Div | Rem | BitXor | BitAnd | BitOr | Shl | Shr | Eq | Lt | Le | Ne - | Ge | Gt => true, + Add | AddUnchecked | Sub | SubUnchecked | Mul | MulUnchecked | Div | Rem | BitXor + | BitAnd | BitOr | Shl | ShlUnchecked | Shr | ShrUnchecked | Eq | Lt | Le | Ne | Ge + | Gt => true, Offset => false, } } @@ -116,7 +119,7 @@ fn recurse_build<'tcx>( let sp = node.span; match tcx.at(sp).lit_to_const(LitToConstInput { lit: &lit.node, ty: node.ty, neg }) { Ok(c) => c, - Err(LitToConstError::Reported(guar)) => tcx.const_error(node.ty, guar), + Err(LitToConstError::Reported(guar)) => ty::Const::new_error(tcx, guar, node.ty), Err(LitToConstError::TypeError) => { bug!("encountered type error in lit_to_const") } @@ -124,17 +127,17 @@ fn recurse_build<'tcx>( } &ExprKind::NonHirLiteral { lit, user_ty: _ } => { let val = ty::ValTree::from_scalar_int(lit); - tcx.mk_const(val, node.ty) + ty::Const::new_value(tcx, val, node.ty) } &ExprKind::ZstLiteral { user_ty: _ } => { let val = ty::ValTree::zst(); - tcx.mk_const(val, node.ty) + ty::Const::new_value(tcx, val, node.ty) } - &ExprKind::NamedConst { def_id, substs, user_ty: _ } => { - let uneval = ty::UnevaluatedConst::new(def_id, substs); - tcx.mk_const(uneval, node.ty) + &ExprKind::NamedConst { def_id, args, user_ty: _ } => { + let uneval = ty::UnevaluatedConst::new(def_id, args); + ty::Const::new_unevaluated(tcx, uneval, node.ty) } - ExprKind::ConstParam { param, .. } => tcx.mk_const(*param, node.ty), + ExprKind::ConstParam { param, .. } => ty::Const::new_param(tcx, *param, node.ty), ExprKind::Call { fun, args, .. } => { let fun = recurse_build(tcx, body, *fun, root_span)?; @@ -144,16 +147,16 @@ fn recurse_build<'tcx>( new_args.push(recurse_build(tcx, body, id, root_span)?); } let new_args = tcx.mk_const_list(&new_args); - tcx.mk_const(Expr::FunctionCall(fun, new_args), node.ty) + ty::Const::new_expr(tcx, Expr::FunctionCall(fun, new_args), node.ty) } &ExprKind::Binary { op, lhs, rhs } if check_binop(op) => { let lhs = recurse_build(tcx, body, lhs, root_span)?; let rhs = recurse_build(tcx, body, rhs, root_span)?; - tcx.mk_const(Expr::Binop(op, lhs, rhs), node.ty) + ty::Const::new_expr(tcx, Expr::Binop(op, lhs, rhs), node.ty) } &ExprKind::Unary { op, arg } if check_unop(op) => { let arg = recurse_build(tcx, body, arg, root_span)?; - tcx.mk_const(Expr::UnOp(op, arg), node.ty) + ty::Const::new_expr(tcx, Expr::UnOp(op, arg), node.ty) } // This is necessary so that the following compiles: // @@ -171,14 +174,14 @@ fn recurse_build<'tcx>( } // `ExprKind::Use` happens when a `hir::ExprKind::Cast` is a // "coercion cast" i.e. using a coercion or is a no-op. - // This is important so that `N as usize as usize` doesnt unify with `N as usize`. (untested) + // This is important so that `N as usize as usize` doesn't unify with `N as usize`. (untested) &ExprKind::Use { source } => { let arg = recurse_build(tcx, body, source, root_span)?; - tcx.mk_const(Expr::Cast(CastKind::Use, arg, node.ty), node.ty) + ty::Const::new_expr(tcx, Expr::Cast(CastKind::Use, arg, node.ty), node.ty) } &ExprKind::Cast { source } => { let arg = recurse_build(tcx, body, source, root_span)?; - tcx.mk_const(Expr::Cast(CastKind::As, arg, node.ty), node.ty) + ty::Const::new_expr(tcx, Expr::Cast(CastKind::As, arg, node.ty), node.ty) } ExprKind::Borrow { arg, .. } => { let arg_node = &body.exprs[*arg]; @@ -218,7 +221,7 @@ fn recurse_build<'tcx>( maybe_supported_error(GenericConstantTooComplexSub::AdtNotSupported(node.span))? } // dont know if this is correct - ExprKind::Pointer { .. } => { + ExprKind::PointerCoercion { .. } => { error(GenericConstantTooComplexSub::PointerNotSupported(node.span))? } ExprKind::Yield { .. } => { @@ -240,7 +243,8 @@ fn recurse_build<'tcx>( ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => { error(GenericConstantTooComplexSub::AssignNotSupported(node.span))? } - ExprKind::Closure { .. } | ExprKind::Return { .. } => { + // FIXME(explicit_tail_calls): maybe get `become` a new error + ExprKind::Closure { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } => { error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))? } // let expressions imply control flow @@ -302,8 +306,9 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { } match expr.kind { - thir::ExprKind::NamedConst { substs, .. } - | thir::ExprKind::ConstBlock { substs, .. } => substs.has_non_region_param(), + thir::ExprKind::NamedConst { args, .. } | thir::ExprKind::ConstBlock { args, .. } => { + args.has_non_region_param() + } thir::ExprKind::ConstParam { .. } => true, thir::ExprKind::Repeat { value, count } => { self.visit_expr(&self.thir()[value]); @@ -320,7 +325,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::Cast { .. } | thir::ExprKind::Use { .. } | thir::ExprKind::NeverToAny { .. } - | thir::ExprKind::Pointer { .. } + | thir::ExprKind::PointerCoercion { .. } | thir::ExprKind::Loop { .. } | thir::ExprKind::Let { .. } | thir::ExprKind::Match { .. } @@ -336,6 +341,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::Break { .. } | thir::ExprKind::Continue { .. } | thir::ExprKind::Return { .. } + | thir::ExprKind::Become { .. } | thir::ExprKind::Array { .. } | thir::ExprKind::Tuple { .. } | thir::ExprKind::Adt(_) @@ -419,7 +425,7 @@ pub fn thir_abstract_const( let root_span = body.exprs[body_id].span; - Ok(Some(ty::EarlyBinder(recurse_build(tcx, body, body_id, root_span)?))) + Ok(Some(ty::EarlyBinder::bind(recurse_build(tcx, body, body_id, root_span)?))) } pub fn provide(providers: &mut Providers) { diff --git a/compiler/rustc_ty_utils/src/errors.rs b/compiler/rustc_ty_utils/src/errors.rs index 553bf40ef3a48..947d4bbe86e1c 100644 --- a/compiler/rustc_ty_utils/src/errors.rs +++ b/compiler/rustc_ty_utils/src/errors.rs @@ -113,7 +113,7 @@ pub struct DuplicateArg<'tcx> { } #[derive(Diagnostic)] -#[diag(ty_utils_impl_trait_not_param)] +#[diag(ty_utils_impl_trait_not_param, code = "E0792")] pub struct NotParam<'tcx> { pub arg: GenericArg<'tcx>, #[primary_span] diff --git a/compiler/rustc_ty_utils/src/implied_bounds.rs b/compiler/rustc_ty_utils/src/implied_bounds.rs index 081be065864c8..436f10a4f7b91 100644 --- a/compiler/rustc_ty_utils/src/implied_bounds.rs +++ b/compiler/rustc_ty_utils/src/implied_bounds.rs @@ -1,46 +1,132 @@ -use rustc_hir::{def::DefKind, def_id::DefId}; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::LocalDefId; use rustc_middle::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::Span; +use std::iter; pub fn provide(providers: &mut Providers) { - *providers = Providers { assumed_wf_types, ..*providers }; + *providers = Providers { + assumed_wf_types, + assumed_wf_types_for_rpitit: |tcx, def_id| { + assert!(tcx.is_impl_trait_in_trait(def_id.to_def_id())); + tcx.assumed_wf_types(def_id) + }, + ..*providers + }; } -fn assumed_wf_types(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List> { +fn assumed_wf_types<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &'tcx [(Ty<'tcx>, Span)] { match tcx.def_kind(def_id) { DefKind::Fn => { - let sig = tcx.fn_sig(def_id).subst_identity(); - let liberated_sig = tcx.liberate_late_bound_regions(def_id, sig); - liberated_sig.inputs_and_output + let sig = tcx.fn_sig(def_id).instantiate_identity(); + let liberated_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), sig); + tcx.arena.alloc_from_iter(itertools::zip_eq( + liberated_sig.inputs_and_output, + fn_sig_spans(tcx, def_id), + )) } DefKind::AssocFn => { - let sig = tcx.fn_sig(def_id).subst_identity(); - let liberated_sig = tcx.liberate_late_bound_regions(def_id, sig); + let sig = tcx.fn_sig(def_id).instantiate_identity(); + let liberated_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), sig); let mut assumed_wf_types: Vec<_> = - tcx.assumed_wf_types(tcx.parent(def_id)).as_slice().into(); - assumed_wf_types.extend(liberated_sig.inputs_and_output); - tcx.mk_type_list(&assumed_wf_types) + tcx.assumed_wf_types(tcx.local_parent(def_id)).into(); + assumed_wf_types.extend(itertools::zip_eq( + liberated_sig.inputs_and_output, + fn_sig_spans(tcx, def_id), + )); + tcx.arena.alloc_slice(&assumed_wf_types) } DefKind::Impl { .. } => { - match tcx.impl_trait_ref(def_id) { - Some(trait_ref) => { - let types: Vec<_> = trait_ref.skip_binder().substs.types().collect(); - tcx.mk_type_list(&types) + // Trait arguments and the self type for trait impls or only the self type for + // inherent impls. + let tys = match tcx.impl_trait_ref(def_id) { + Some(trait_ref) => trait_ref.skip_binder().args.types().collect(), + None => vec![tcx.type_of(def_id).instantiate_identity()], + }; + + let mut impl_spans = impl_spans(tcx, def_id); + tcx.arena.alloc_from_iter(tys.into_iter().map(|ty| (ty, impl_spans.next().unwrap()))) + } + DefKind::AssocTy if let Some(data) = tcx.opt_rpitit_info(def_id.to_def_id()) => match data { + ty::ImplTraitInTraitData::Trait { fn_def_id, .. } => { + // We need to remap all of the late-bound lifetimes in theassumed wf types + // of the fn (which are represented as ReFree) to the early-bound lifetimes + // of the RPITIT (which are represented by ReEarlyBound owned by the opaque). + // Luckily, this is very easy to do because we already have that mapping + // stored in the HIR of this RPITIT. + // + // Side-note: We don't really need to do this remapping for early-bound + // lifetimes because they're already "linked" by the bidirectional outlives + // predicates we insert in the `explicit_predicates_of` query for RPITITs. + let mut mapping = FxHashMap::default(); + let generics = tcx.generics_of(def_id); + + // For each captured opaque lifetime, if it's late-bound (`ReFree` in this case, + // since it has been liberated), map it back to the early-bound lifetime of + // the GAT. Since RPITITs also have all of the fn's generics, we slice only + // the end of the list corresponding to the opaque's generics. + for param in &generics.params[tcx.generics_of(fn_def_id).params.len()..] { + let orig_lt = tcx.map_rpit_lifetime_to_fn_lifetime(param.def_id.expect_local()); + if matches!(*orig_lt, ty::ReFree(..)) { + mapping.insert( + orig_lt, + ty::Region::new_early_bound( + tcx, + ty::EarlyBoundRegion { + def_id: param.def_id, + index: param.index, + name: param.name, + }, + ), + ); + } } - // Only the impl self type - None => tcx.mk_type_list(&[tcx.type_of(def_id).subst_identity()]), + // FIXME: This could use a real folder, I guess. + let remapped_wf_tys = tcx.fold_regions( + tcx.assumed_wf_types(fn_def_id.expect_local()).to_vec(), + |region, _| { + // If `region` is a `ReFree` that is captured by the + // opaque, remap it to its corresponding the early- + // bound region. + if let Some(remapped_region) = mapping.get(®ion) { + *remapped_region + } else { + region + } + }, + ); + tcx.arena.alloc_from_iter(remapped_wf_tys) } - } - DefKind::AssocConst | DefKind::AssocTy => tcx.assumed_wf_types(tcx.parent(def_id)), - DefKind::OpaqueTy => match tcx.def_kind(tcx.parent(def_id)) { - DefKind::TyAlias => ty::List::empty(), - DefKind::AssocTy => tcx.assumed_wf_types(tcx.parent(def_id)), + // Assumed wf types for RPITITs in an impl just inherit (and instantiate) + // the assumed wf types of the trait's RPITIT GAT. + ty::ImplTraitInTraitData::Impl { .. } => { + let impl_def_id = tcx.local_parent(def_id); + let rpitit_def_id = tcx.associated_item(def_id).trait_item_def_id.unwrap(); + let args = ty::GenericArgs::identity_for_item(tcx, def_id).rebase_onto( + tcx, + impl_def_id.to_def_id(), + tcx.impl_trait_ref(impl_def_id).unwrap().instantiate_identity().args, + ); + tcx.arena.alloc_from_iter( + ty::EarlyBinder::bind(tcx.assumed_wf_types_for_rpitit(rpitit_def_id)) + .iter_instantiated_copied(tcx, args) + .chain(tcx.assumed_wf_types(impl_def_id).into_iter().copied()), + ) + } + }, + DefKind::AssocConst | DefKind::AssocTy => tcx.assumed_wf_types(tcx.local_parent(def_id)), + DefKind::OpaqueTy => match tcx.def_kind(tcx.local_parent(def_id)) { + DefKind::TyAlias { .. } => ty::List::empty(), + DefKind::AssocTy => tcx.assumed_wf_types(tcx.local_parent(def_id)), // Nested opaque types only occur in associated types: // ` type Opaque = impl Trait<&'static T, AssocTy = impl Nested>; ` // assumed_wf_types should include those of `Opaque`, `Opaque` itself // and `&'static T`. - DefKind::OpaqueTy => bug!("unimplemented implied bounds for neseted opaque types"), - def_kind @ _ => { + DefKind::OpaqueTy => bug!("unimplemented implied bounds for nested opaque types"), + def_kind => { bug!("unimplemented implied bounds for opaque types with parent {def_kind:?}") } }, @@ -50,7 +136,7 @@ fn assumed_wf_types(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List> { | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias + | DefKind::TyAlias { .. } | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::TyParam @@ -64,7 +150,6 @@ fn assumed_wf_types(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List> { | DefKind::ForeignMod | DefKind::AnonConst | DefKind::InlineConst - | DefKind::ImplTraitPlaceholder | DefKind::Field | DefKind::LifetimeParam | DefKind::GlobalAsm @@ -72,3 +157,28 @@ fn assumed_wf_types(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List> { | DefKind::Generator => ty::List::empty(), } } + +fn fn_sig_spans(tcx: TyCtxt<'_>, def_id: LocalDefId) -> impl Iterator + '_ { + let node = tcx.hir().get(tcx.local_def_id_to_hir_id(def_id)); + if let Some(decl) = node.fn_decl() { + decl.inputs.iter().map(|ty| ty.span).chain(iter::once(decl.output.span())) + } else { + bug!("unexpected item for fn {def_id:?}: {node:?}") + } +} + +fn impl_spans(tcx: TyCtxt<'_>, def_id: LocalDefId) -> impl Iterator + '_ { + let item = tcx.hir().expect_item(def_id); + if let hir::ItemKind::Impl(impl_) = item.kind { + let trait_args = impl_ + .of_trait + .into_iter() + .flat_map(|trait_ref| trait_ref.path.segments.last().unwrap().args().args) + .map(|arg| arg.span()); + let dummy_spans_for_default_args = + impl_.of_trait.into_iter().flat_map(|trait_ref| iter::repeat(trait_ref.path.span)); + iter::once(impl_.self_ty.span).chain(trait_args).chain(dummy_spans_for_default_args) + } else { + bug!("unexpected item for impl {def_id:?}: {item:?}") + } +} diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index 36a20c78fcc7b..e1a15b5cf9ff1 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -1,69 +1,64 @@ use rustc_errors::ErrorGuaranteed; +use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::query::Providers; -use rustc_middle::traits::CodegenObligationError; -use rustc_middle::ty::subst::SubstsRef; +use rustc_middle::traits::{BuiltinImplSource, CodegenObligationError}; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, Instance, TyCtxt, TypeVisitableExt}; use rustc_span::sym; use rustc_trait_selection::traits; -use traits::{translate_substs, Reveal}; +use traits::{translate_args, Reveal}; use crate::errors::UnexpectedFnPtrAssociatedItem; fn resolve_instance<'tcx>( tcx: TyCtxt<'tcx>, - key: ty::ParamEnvAnd<'tcx, (DefId, SubstsRef<'tcx>)>, + key: ty::ParamEnvAnd<'tcx, (DefId, GenericArgsRef<'tcx>)>, ) -> Result>, ErrorGuaranteed> { - let (param_env, (def, substs)) = key.into_parts(); + let (param_env, (def_id, args)) = key.into_parts(); - let result = if let Some(trait_def_id) = tcx.trait_of_item(def) { + let result = if let Some(trait_def_id) = tcx.trait_of_item(def_id) { debug!(" => associated item, attempting to find impl in param_env {:#?}", param_env); resolve_associated_item( tcx, - def, + def_id, param_env, trait_def_id, - tcx.normalize_erasing_regions(param_env, substs), + tcx.normalize_erasing_regions(param_env, args), ) } else { - let ty = tcx.type_of(def); - let item_type = tcx.subst_and_normalize_erasing_regions(substs, param_env, ty); + let def = if matches!(tcx.def_kind(def_id), DefKind::Fn) && tcx.is_intrinsic(def_id) { + debug!(" => intrinsic"); + ty::InstanceDef::Intrinsic(def_id) + } else if Some(def_id) == tcx.lang_items().drop_in_place_fn() { + let ty = args.type_at(0); - let def = match *item_type.kind() { - ty::FnDef(def_id, ..) if tcx.is_intrinsic(def_id) => { - debug!(" => intrinsic"); - ty::InstanceDef::Intrinsic(def) - } - ty::FnDef(def_id, substs) if Some(def_id) == tcx.lang_items().drop_in_place_fn() => { - let ty = substs.type_at(0); - - if ty.needs_drop(tcx, param_env) { - debug!(" => nontrivial drop glue"); - match *ty.kind() { - ty::Closure(..) - | ty::Generator(..) - | ty::Tuple(..) - | ty::Adt(..) - | ty::Dynamic(..) - | ty::Array(..) - | ty::Slice(..) => {} - // Drop shims can only be built from ADTs. - _ => return Ok(None), - } - - ty::InstanceDef::DropGlue(def_id, Some(ty)) - } else { - debug!(" => trivial drop glue"); - ty::InstanceDef::DropGlue(def_id, None) + if ty.needs_drop(tcx, param_env) { + debug!(" => nontrivial drop glue"); + match *ty.kind() { + ty::Closure(..) + | ty::Generator(..) + | ty::Tuple(..) + | ty::Adt(..) + | ty::Dynamic(..) + | ty::Array(..) + | ty::Slice(..) => {} + // Drop shims can only be built from ADTs. + _ => return Ok(None), } + + ty::InstanceDef::DropGlue(def_id, Some(ty)) + } else { + debug!(" => trivial drop glue"); + ty::InstanceDef::DropGlue(def_id, None) } - _ => { - debug!(" => free item"); - ty::InstanceDef::Item(def) - } + } else { + debug!(" => free item"); + ty::InstanceDef::Item(def_id) }; - Ok(Some(Instance { def, substs })) + + Ok(Some(Instance { def, args })) }; debug!("inner_resolve_instance: result={:?}", result); result @@ -74,13 +69,13 @@ fn resolve_associated_item<'tcx>( trait_item_id: DefId, param_env: ty::ParamEnv<'tcx>, trait_id: DefId, - rcvr_substs: SubstsRef<'tcx>, + rcvr_args: GenericArgsRef<'tcx>, ) -> Result>, ErrorGuaranteed> { - debug!(?trait_item_id, ?param_env, ?trait_id, ?rcvr_substs, "resolve_associated_item"); + debug!(?trait_item_id, ?param_env, ?trait_id, ?rcvr_args, "resolve_associated_item"); - let trait_ref = ty::TraitRef::from_method(tcx, trait_id, rcvr_substs); + let trait_ref = ty::TraitRef::from_method(tcx, trait_id, rcvr_args); - let vtbl = match tcx.codegen_select_candidate((param_env, ty::Binder::dummy(trait_ref))) { + let vtbl = match tcx.codegen_select_candidate((param_env, trait_ref)) { Ok(vtbl) => vtbl, Err(CodegenObligationError::Ambiguity) => { let reported = tcx.sess.delay_span_bug( @@ -102,9 +97,9 @@ fn resolve_associated_item<'tcx>( traits::ImplSource::UserDefined(impl_data) => { debug!( "resolving ImplSource::UserDefined: {:?}, {:?}, {:?}, {:?}", - param_env, trait_item_id, rcvr_substs, impl_data + param_env, trait_item_id, rcvr_args, impl_data ); - assert!(!rcvr_substs.has_infer()); + assert!(!rcvr_args.has_infer()); assert!(!trait_ref.has_infer()); let trait_def_id = tcx.trait_id_of_impl(impl_data.impl_def_id).unwrap(); @@ -117,15 +112,15 @@ fn resolve_associated_item<'tcx>( }); let infcx = tcx.infer_ctxt().build(); let param_env = param_env.with_reveal_all_normalized(tcx); - let substs = rcvr_substs.rebase_onto(tcx, trait_def_id, impl_data.substs); - let substs = translate_substs( + let args = rcvr_args.rebase_onto(tcx, trait_def_id, impl_data.args); + let args = translate_args( &infcx, param_env, impl_data.impl_def_id, - substs, + args, leaf_def.defining_node, ); - let substs = infcx.tcx.erase_regions(substs); + let args = infcx.tcx.erase_regions(args); // Since this is a trait item, we need to see if the item is either a trait default item // or a specialization because we can't resolve those unless we can `Reveal::All`. @@ -159,7 +154,7 @@ fn resolve_associated_item<'tcx>( return Err(guard); } - let substs = tcx.erase_regions(substs); + let args = tcx.erase_regions(args); // Check if we just resolved an associated `const` declaration from // a `trait` to an associated `const` definition in an `impl`, where @@ -175,96 +170,17 @@ fn resolve_associated_item<'tcx>( ))?; } - Some(ty::Instance::new(leaf_def.item.def_id, substs)) - } - traits::ImplSource::Generator(generator_data) => { - if cfg!(debug_assertions) && tcx.item_name(trait_item_id) != sym::resume { - // For compiler developers who'd like to add new items to `Generator`, - // you either need to generate a shim body, or perhaps return - // `InstanceDef::Item` pointing to a trait default method body if - // it is given a default implementation by the trait. - span_bug!( - tcx.def_span(generator_data.generator_def_id), - "no definition for `{trait_ref}::{}` for built-in generator type", - tcx.item_name(trait_item_id) - ) - } - Some(Instance { - def: ty::InstanceDef::Item(generator_data.generator_def_id), - substs: generator_data.substs, - }) - } - traits::ImplSource::Future(future_data) => { - if Some(trait_item_id) == tcx.lang_items().future_poll_fn() { - // `Future::poll` is generated by the compiler. - Some(Instance { - def: ty::InstanceDef::Item(future_data.generator_def_id), - substs: future_data.substs, - }) - } else { - // All other methods are default methods of the `Future` trait. - // (this assumes that `ImplSource::Future` is only used for methods on `Future`) - debug_assert!(tcx.impl_defaultness(trait_item_id).has_value()); - Some(Instance::new(trait_item_id, rcvr_substs)) - } + Some(ty::Instance::new(leaf_def.item.def_id, args)) } - traits::ImplSource::Closure(closure_data) => { - if cfg!(debug_assertions) - && ![sym::call, sym::call_mut, sym::call_once] - .contains(&tcx.item_name(trait_item_id)) - { - // For compiler developers who'd like to add new items to `Fn`/`FnMut`/`FnOnce`, - // you either need to generate a shim body, or perhaps return - // `InstanceDef::Item` pointing to a trait default method body if - // it is given a default implementation by the trait. - span_bug!( - tcx.def_span(closure_data.closure_def_id), - "no definition for `{trait_ref}::{}` for built-in closure type", - tcx.item_name(trait_item_id) - ) - } - let trait_closure_kind = tcx.fn_trait_kind_from_def_id(trait_id).unwrap(); - Instance::resolve_closure( - tcx, - closure_data.closure_def_id, - closure_data.substs, - trait_closure_kind, - ) - } - traits::ImplSource::FnPointer(ref data) => match data.fn_ty.kind() { - ty::FnDef(..) | ty::FnPtr(..) => { - if cfg!(debug_assertions) - && ![sym::call, sym::call_mut, sym::call_once] - .contains(&tcx.item_name(trait_item_id)) - { - // For compiler developers who'd like to add new items to `Fn`/`FnMut`/`FnOnce`, - // you either need to generate a shim body, or perhaps return - // `InstanceDef::Item` pointing to a trait default method body if - // it is given a default implementation by the trait. - bug!( - "no definition for `{trait_ref}::{}` for built-in fn type", - tcx.item_name(trait_item_id) - ) - } - Some(Instance { - def: ty::InstanceDef::FnPtrShim(trait_item_id, data.fn_ty), - substs: rcvr_substs, - }) - } - _ => bug!( - "no built-in definition for `{trait_ref}::{}` for non-fn type", - tcx.item_name(trait_item_id) - ), - }, - traits::ImplSource::Object(ref data) => { - traits::get_vtable_index_of_object_method(tcx, data, trait_item_id).map(|index| { - Instance { + traits::ImplSource::Builtin(BuiltinImplSource::Object { vtable_base }, _) => { + traits::get_vtable_index_of_object_method(tcx, *vtable_base, trait_item_id).map( + |index| Instance { def: ty::InstanceDef::Virtual(trait_item_id, index), - substs: rcvr_substs, - } - }) + args: rcvr_args, + }, + ) } - traits::ImplSource::Builtin(..) => { + traits::ImplSource::Builtin(BuiltinImplSource::Misc, _) => { let lang_items = tcx.lang_items(); if Some(trait_ref.def_id) == lang_items.clone_trait() { // FIXME(eddyb) use lang items for methods instead of names. @@ -284,14 +200,14 @@ fn resolve_associated_item<'tcx>( Some(Instance { def: ty::InstanceDef::CloneShim(trait_item_id, self_ty), - substs: rcvr_substs, + args: rcvr_args, }) } else { assert_eq!(name, sym::clone_from); // Use the default `fn clone_from` from `trait Clone`. - let substs = tcx.erase_regions(rcvr_substs); - Some(ty::Instance::new(trait_item_id, substs)) + let args = tcx.erase_regions(rcvr_args); + Some(ty::Instance::new(trait_item_id, args)) } } else if Some(trait_ref.def_id) == lang_items.fn_ptr_trait() { if lang_items.fn_ptr_addr() == Some(trait_item_id) { @@ -301,22 +217,80 @@ fn resolve_associated_item<'tcx>( } Some(Instance { def: ty::InstanceDef::FnPtrAddrShim(trait_item_id, self_ty), - substs: rcvr_substs, + args: rcvr_args, }) } else { tcx.sess.emit_fatal(UnexpectedFnPtrAssociatedItem { span: tcx.def_span(trait_item_id), }) } + } else if Some(trait_ref.def_id) == lang_items.future_trait() { + let ty::Generator(generator_def_id, args, _) = *rcvr_args.type_at(0).kind() else { + bug!() + }; + if Some(trait_item_id) == tcx.lang_items().future_poll_fn() { + // `Future::poll` is generated by the compiler. + Some(Instance { def: ty::InstanceDef::Item(generator_def_id), args: args }) + } else { + // All other methods are default methods of the `Future` trait. + // (this assumes that `ImplSource::Builtin` is only used for methods on `Future`) + debug_assert!(tcx.defaultness(trait_item_id).has_value()); + Some(Instance::new(trait_item_id, rcvr_args)) + } + } else if Some(trait_ref.def_id) == lang_items.gen_trait() { + let ty::Generator(generator_def_id, args, _) = *rcvr_args.type_at(0).kind() else { + bug!() + }; + if cfg!(debug_assertions) && tcx.item_name(trait_item_id) != sym::resume { + // For compiler developers who'd like to add new items to `Generator`, + // you either need to generate a shim body, or perhaps return + // `InstanceDef::Item` pointing to a trait default method body if + // it is given a default implementation by the trait. + span_bug!( + tcx.def_span(generator_def_id), + "no definition for `{trait_ref}::{}` for built-in generator type", + tcx.item_name(trait_item_id) + ) + } + Some(Instance { def: ty::InstanceDef::Item(generator_def_id), args }) + } else if tcx.fn_trait_kind_from_def_id(trait_ref.def_id).is_some() { + // FIXME: This doesn't check for malformed libcore that defines, e.g., + // `trait Fn { fn call_once(&self) { .. } }`. This is mostly for extension + // methods. + if cfg!(debug_assertions) + && ![sym::call, sym::call_mut, sym::call_once] + .contains(&tcx.item_name(trait_item_id)) + { + // For compiler developers who'd like to add new items to `Fn`/`FnMut`/`FnOnce`, + // you either need to generate a shim body, or perhaps return + // `InstanceDef::Item` pointing to a trait default method body if + // it is given a default implementation by the trait. + bug!( + "no definition for `{trait_ref}::{}` for built-in callable type", + tcx.item_name(trait_item_id) + ) + } + match *rcvr_args.type_at(0).kind() { + ty::Closure(closure_def_id, args) => { + let trait_closure_kind = tcx.fn_trait_kind_from_def_id(trait_id).unwrap(); + Instance::resolve_closure(tcx, closure_def_id, args, trait_closure_kind) + } + ty::FnDef(..) | ty::FnPtr(..) => Some(Instance { + def: ty::InstanceDef::FnPtrShim(trait_item_id, rcvr_args.type_at(0)), + args: rcvr_args, + }), + _ => bug!( + "no built-in definition for `{trait_ref}::{}` for non-fn type", + tcx.item_name(trait_item_id) + ), + } } else { None } } - traits::ImplSource::AutoImpl(..) - | traits::ImplSource::Param(..) - | traits::ImplSource::TraitAlias(..) - | traits::ImplSource::TraitUpcasting(_) - | traits::ImplSource::ConstDestruct(_) => None, + traits::ImplSource::Param(..) + | traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _) + | traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => None, }) } diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index 16cd8bc8e698f..6b4273c03e4fa 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -8,7 +8,7 @@ use rustc_middle::ty::layout::{ IntegerExt, LayoutCx, LayoutError, LayoutOf, TyAndLayout, MAX_SIMD_LANES, }; use rustc_middle::ty::{ - self, subst::SubstsRef, AdtDef, EarlyBinder, ReprOptions, Ty, TyCtxt, TypeVisitableExt, + self, AdtDef, EarlyBinder, GenericArgsRef, ReprOptions, Ty, TyCtxt, TypeVisitableExt, }; use rustc_session::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use rustc_span::symbol::Symbol; @@ -31,7 +31,7 @@ pub fn provide(providers: &mut Providers) { fn layout_of<'tcx>( tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>, -) -> Result, LayoutError<'tcx>> { +) -> Result, &'tcx LayoutError<'tcx>> { let (param_env, ty) = query.into_parts(); debug!(?ty); @@ -45,7 +45,9 @@ fn layout_of<'tcx>( let ty = match tcx.try_normalize_erasing_regions(param_env, ty) { Ok(t) => t, Err(normalization_error) => { - return Err(LayoutError::NormalizationFailure(ty, normalization_error)); + return Err(tcx + .arena + .alloc(LayoutError::NormalizationFailure(ty, normalization_error))); } }; @@ -66,27 +68,41 @@ fn layout_of<'tcx>( Ok(layout) } +fn error<'tcx>( + cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, + err: LayoutError<'tcx>, +) -> &'tcx LayoutError<'tcx> { + cx.tcx.arena.alloc(err) +} + fn univariant_uninterned<'tcx>( cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, ty: Ty<'tcx>, fields: &IndexSlice>, repr: &ReprOptions, kind: StructKind, -) -> Result> { +) -> Result> { let dl = cx.data_layout(); let pack = repr.pack; if pack.is_some() && repr.align.is_some() { cx.tcx.sess.delay_span_bug(DUMMY_SP, "struct cannot be packed and aligned"); - return Err(LayoutError::Unknown(ty)); + return Err(cx.tcx.arena.alloc(LayoutError::Unknown(ty))); } - cx.univariant(dl, fields, repr, kind).ok_or(LayoutError::SizeOverflow(ty)) + cx.univariant(dl, fields, repr, kind).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty))) } fn layout_of_uncached<'tcx>( cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, ty: Ty<'tcx>, -) -> Result, LayoutError<'tcx>> { +) -> Result, &'tcx LayoutError<'tcx>> { + // Types that reference `ty::Error` pessimistically don't have a meaningful layout. + // The only side-effect of this is possibly worse diagnostics in case the layout + // was actually computable (like if the `ty::Error` showed up only in a `PhantomData`). + if let Err(guar) = ty.error_reported() { + return Err(error(cx, LayoutError::ReferencesError(guar))); + } + let tcx = cx.tcx; let param_env = cx.param_env; let dl = cx.data_layout(); @@ -145,17 +161,35 @@ fn layout_of_uncached<'tcx>( return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr))); } - let unsized_part = tcx.struct_tail_erasing_lifetimes(pointee, param_env); - let metadata = if let Some(metadata_def_id) = tcx.lang_items().metadata_type() // Projection eagerly bails out when the pointee references errors, // fall back to structurally deducing metadata. && !pointee.references_error() { - let metadata_ty = tcx.normalize_erasing_regions( + let pointee_metadata = Ty::new_projection(tcx,metadata_def_id, [pointee]); + let metadata_ty = match tcx.try_normalize_erasing_regions( param_env, - tcx.mk_projection(metadata_def_id, [pointee]), - ); + pointee_metadata, + ) { + Ok(metadata_ty) => metadata_ty, + Err(mut err) => { + // Usually `::Metadata` can't be normalized because + // its struct tail cannot be normalized either, so try to get a + // more descriptive layout error here, which will lead to less confusing + // diagnostics. + match tcx.try_normalize_erasing_regions( + param_env, + tcx.struct_tail_without_normalization(pointee), + ) { + Ok(_) => {}, + Err(better_err) => { + err = better_err; + } + } + return Err(error(cx, LayoutError::NormalizationFailure(pointee, err))); + }, + }; + let metadata_layout = cx.layout_of(metadata_ty)?; // If the metadata is a 1-zst, then the pointer is thin. if metadata_layout.is_zst() && metadata_layout.align.abi.bytes() == 1 { @@ -163,10 +197,13 @@ fn layout_of_uncached<'tcx>( } let Abi::Scalar(metadata) = metadata_layout.abi else { - return Err(LayoutError::Unknown(unsized_part)); + return Err(error(cx, LayoutError::Unknown(pointee))); }; + metadata } else { + let unsized_part = tcx.struct_tail_erasing_lifetimes(pointee, param_env); + match unsized_part.kind() { ty::Foreign(..) => { return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr))); @@ -178,7 +215,7 @@ fn layout_of_uncached<'tcx>( vtable } _ => { - return Err(LayoutError::Unknown(unsized_part)); + return Err(error(cx, LayoutError::Unknown(pointee))); } } }; @@ -200,14 +237,18 @@ fn layout_of_uncached<'tcx>( if count.has_projections() { count = tcx.normalize_erasing_regions(param_env, count); if count.has_projections() { - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); } } - let count = - count.try_eval_target_usize(tcx, param_env).ok_or(LayoutError::Unknown(ty))?; + let count = count + .try_eval_target_usize(tcx, param_env) + .ok_or_else(|| error(cx, LayoutError::Unknown(ty)))?; let element = cx.layout_of(element)?; - let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow(ty))?; + let size = element + .size + .checked_mul(count, dl) + .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?; let abi = if count != 0 && ty.is_privately_uninhabited(tcx, param_env) { Abi::Uninhabited @@ -224,6 +265,8 @@ fn layout_of_uncached<'tcx>( largest_niche, align: element.align, size, + max_repr_align: None, + unadjusted_abi_align: element.align.abi, }) } ty::Slice(element) => { @@ -235,6 +278,8 @@ fn layout_of_uncached<'tcx>( largest_niche: None, align: element.align, size: Size::ZERO, + max_repr_align: None, + unadjusted_abi_align: element.align.abi, }) } ty::Str => tcx.mk_layout(LayoutS { @@ -244,6 +289,8 @@ fn layout_of_uncached<'tcx>( largest_niche: None, align: dl.i8_align, size: Size::ZERO, + max_repr_align: None, + unadjusted_abi_align: dl.i8_align.abi, }), // Odd unit types. @@ -265,12 +312,14 @@ fn layout_of_uncached<'tcx>( tcx.mk_layout(unit) } - ty::Generator(def_id, substs, _) => generator_layout(cx, ty, def_id, substs)?, + ty::Generator(def_id, args, _) => generator_layout(cx, ty, def_id, args)?, - ty::Closure(_, ref substs) => { - let tys = substs.as_closure().upvar_tys(); + ty::Closure(_, ref args) => { + let tys = args.as_closure().upvar_tys(); univariant( - &tys.map(|ty| Ok(cx.layout_of(ty)?.layout)).try_collect::>()?, + &tys.iter() + .map(|ty| Ok(cx.layout_of(ty)?.layout)) + .try_collect::>()?, &ReprOptions::default(), StructKind::AlwaysSized, )? @@ -288,14 +337,14 @@ fn layout_of_uncached<'tcx>( } // SIMD vector types. - ty::Adt(def, substs) if def.repr().simd() => { + ty::Adt(def, args) if def.repr().simd() => { if !def.is_struct() { // Should have yielded E0517 by now. tcx.sess.delay_span_bug( DUMMY_SP, "#[repr(simd)] was applied to an ADT that is not a struct", ); - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); } let fields = &def.non_enum_variant().fields; @@ -315,17 +364,17 @@ fn layout_of_uncached<'tcx>( } // Type of the first ADT field: - let f0_ty = fields[FieldIdx::from_u32(0)].ty(tcx, substs); + let f0_ty = fields[FieldIdx::from_u32(0)].ty(tcx, args); // Heterogeneous SIMD vectors are not supported: // (should be caught by typeck) for fi in fields { - if fi.ty(tcx, substs) != f0_ty { + if fi.ty(tcx, args) != f0_ty { tcx.sess.delay_span_bug( DUMMY_SP, "#[repr(simd)] was applied to an ADT with heterogeneous field type", ); - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); } } @@ -347,7 +396,7 @@ fn layout_of_uncached<'tcx>( // Extract the number of elements from the layout of the array field: let FieldsShape::Array { count, .. } = cx.layout_of(f0_ty)?.layout.fields() else { - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); }; (*e_ty, *count, true) @@ -376,7 +425,10 @@ fn layout_of_uncached<'tcx>( }; // Compute the size and alignment of the vector: - let size = e_ly.size.checked_mul(e_len, dl).ok_or(LayoutError::SizeOverflow(ty))?; + let size = e_ly + .size + .checked_mul(e_len, dl) + .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?; let align = dl.vector_align(size); let size = size.align_to(align.abi); @@ -394,11 +446,13 @@ fn layout_of_uncached<'tcx>( largest_niche: e_ly.largest_niche, size, align, + max_repr_align: None, + unadjusted_abi_align: align.abi, }) } // ADTs. - ty::Adt(def, substs) => { + ty::Adt(def, args) => { // Cache the field layouts. let variants = def .variants() @@ -406,7 +460,7 @@ fn layout_of_uncached<'tcx>( .map(|v| { v.fields .iter() - .map(|field| Ok(cx.layout_of(field.ty(tcx, substs))?.layout)) + .map(|field| Ok(cx.layout_of(field.ty(tcx, args))?.layout)) .try_collect::>() }) .try_collect::>()?; @@ -417,61 +471,118 @@ fn layout_of_uncached<'tcx>( tcx.def_span(def.did()), "union cannot be packed and aligned", ); - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); } return Ok(tcx.mk_layout( - cx.layout_of_union(&def.repr(), &variants).ok_or(LayoutError::Unknown(ty))?, + cx.layout_of_union(&def.repr(), &variants) + .ok_or_else(|| error(cx, LayoutError::Unknown(ty)))?, )); } - tcx.mk_layout( - cx.layout_of_struct_or_enum( + let get_discriminant_type = + |min, max| Integer::repr_discr(tcx, ty, &def.repr(), min, max); + + let discriminants_iter = || { + def.is_enum() + .then(|| def.discriminants(tcx).map(|(v, d)| (v, d.val as i128))) + .into_iter() + .flatten() + }; + + let dont_niche_optimize_enum = def.repr().inhibit_enum_layout_opt() + || def + .variants() + .iter_enumerated() + .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32())); + + let maybe_unsized = def.is_struct() + && def.non_enum_variant().tail_opt().is_some_and(|last_field| { + let param_env = tcx.param_env(def.did()); + !tcx.type_of(last_field.did).instantiate_identity().is_sized(tcx, param_env) + }); + + let Some(layout) = cx.layout_of_struct_or_enum( + &def.repr(), + &variants, + def.is_enum(), + def.is_unsafe_cell(), + tcx.layout_scalar_valid_range(def.did()), + get_discriminant_type, + discriminants_iter(), + dont_niche_optimize_enum, + !maybe_unsized, + ) else { + return Err(error(cx, LayoutError::SizeOverflow(ty))); + }; + + // If the struct tail is sized and can be unsized, check that unsizing doesn't move the fields around. + if cfg!(debug_assertions) + && maybe_unsized + && def.non_enum_variant().tail().ty(tcx, args).is_sized(tcx, cx.param_env) + { + let mut variants = variants; + let tail_replacement = cx.layout_of(Ty::new_slice(tcx, tcx.types.u8)).unwrap(); + *variants[FIRST_VARIANT].raw.last_mut().unwrap() = tail_replacement.layout; + + let Some(unsized_layout) = cx.layout_of_struct_or_enum( &def.repr(), &variants, def.is_enum(), def.is_unsafe_cell(), tcx.layout_scalar_valid_range(def.did()), - |min, max| Integer::repr_discr(tcx, ty, &def.repr(), min, max), - def.is_enum() - .then(|| def.discriminants(tcx).map(|(v, d)| (v, d.val as i128))) - .into_iter() - .flatten(), - def.repr().inhibit_enum_layout_opt() - || def - .variants() - .iter_enumerated() - .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32())), - { - let param_env = tcx.param_env(def.did()); - def.is_struct() - && match def.variants().iter().next().and_then(|x| x.fields.raw.last()) - { - Some(last_field) => tcx - .type_of(last_field.did) - .subst_identity() - .is_sized(tcx, param_env), - None => false, - } - }, - ) - .ok_or(LayoutError::SizeOverflow(ty))?, - ) + get_discriminant_type, + discriminants_iter(), + dont_niche_optimize_enum, + !maybe_unsized, + ) else { + bug!("failed to compute unsized layout of {ty:?}"); + }; + + let FieldsShape::Arbitrary { offsets: sized_offsets, .. } = &layout.fields else { + bug!("unexpected FieldsShape for sized layout of {ty:?}: {:?}", layout.fields); + }; + let FieldsShape::Arbitrary { offsets: unsized_offsets, .. } = + &unsized_layout.fields + else { + bug!( + "unexpected FieldsShape for unsized layout of {ty:?}: {:?}", + unsized_layout.fields + ); + }; + + let (sized_tail, sized_fields) = sized_offsets.raw.split_last().unwrap(); + let (unsized_tail, unsized_fields) = unsized_offsets.raw.split_last().unwrap(); + + if sized_fields != unsized_fields { + bug!("unsizing {ty:?} changed field order!\n{layout:?}\n{unsized_layout:?}"); + } + + if sized_tail < unsized_tail { + bug!("unsizing {ty:?} moved tail backwards!\n{layout:?}\n{unsized_layout:?}"); + } + } + + tcx.mk_layout(layout) } // Types with no meaningful known layout. ty::Alias(..) => { // NOTE(eddyb) `layout_of` query should've normalized these away, // if that was possible, so there's no reason to try again here. - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); } - ty::Bound(..) | ty::GeneratorWitness(..) | ty::GeneratorWitnessMIR(..) | ty::Infer(_) => { + ty::Bound(..) + | ty::GeneratorWitness(..) + | ty::GeneratorWitnessMIR(..) + | ty::Infer(_) + | ty::Error(_) => { bug!("Layout::compute: unexpected type `{}`", ty) } - ty::Placeholder(..) | ty::Param(_) | ty::Error(_) => { - return Err(LayoutError::Unknown(ty)); + ty::Placeholder(..) | ty::Param(_) => { + return Err(error(cx, LayoutError::Unknown(ty))); } }) } @@ -606,21 +717,21 @@ fn generator_layout<'tcx>( cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, ty: Ty<'tcx>, def_id: hir::def_id::DefId, - substs: SubstsRef<'tcx>, -) -> Result, LayoutError<'tcx>> { + args: GenericArgsRef<'tcx>, +) -> Result, &'tcx LayoutError<'tcx>> { use SavedLocalEligibility::*; let tcx = cx.tcx; - let subst_field = |ty: Ty<'tcx>| EarlyBinder(ty).subst(tcx, substs); + let subst_field = |ty: Ty<'tcx>| EarlyBinder::bind(ty).instantiate(tcx, args); let Some(info) = tcx.generator_layout(def_id) else { - return Err(LayoutError::Unknown(ty)); + return Err(error(cx, LayoutError::Unknown(ty))); }; let (ineligible_locals, assignments) = generator_saved_local_eligibility(&info); // Build a prefix layout, including "promoting" all ineligible // locals as part of the prefix. We compute the layout of all of // these fields at once to get optimal packing. - let tag_index = substs.as_generator().prefix_tys().count(); + let tag_index = args.as_generator().prefix_tys().len(); // `info.variant_fields` already accounts for the reserved variants, so no need to add them. let max_discr = (info.variant_fields.len() - 1) as u128; @@ -634,11 +745,12 @@ fn generator_layout<'tcx>( let promoted_layouts = ineligible_locals .iter() .map(|local| subst_field(info.field_tys[local].ty)) - .map(|ty| tcx.mk_maybe_uninit(ty)) + .map(|ty| Ty::new_maybe_uninit(tcx, ty)) .map(|ty| Ok(cx.layout_of(ty)?.layout)); - let prefix_layouts = substs + let prefix_layouts = args .as_generator() .prefix_tys() + .iter() .map(|ty| Ok(cx.layout_of(ty)?.layout)) .chain(iter::once(Ok(tag_layout))) .chain(promoted_layouts) @@ -794,6 +906,8 @@ fn generator_layout<'tcx>( largest_niche: prefix.largest_niche, size, align, + max_repr_align: None, + unadjusted_abi_align: align.abi, }); debug!("generator layout ({:?}): {:#?}", ty, layout); Ok(layout) @@ -844,11 +958,11 @@ fn record_layout_for_printing_outlined<'tcx>( record(adt_kind.into(), adt_packed, opt_discr_size, variant_infos); } - ty::Generator(def_id, substs, _) => { + ty::Generator(def_id, args, _) => { debug!("print-type-size t: `{:?}` record generator", layout.ty); // Generators always have a begin/poisoned/end state with additional suspend points let (variant_infos, opt_discr_size) = - variant_info_for_generator(cx, layout, def_id, substs); + variant_info_for_generator(cx, layout, def_id, args); record(DataTypeKind::Generator, false, opt_discr_size, variant_infos); } @@ -938,19 +1052,20 @@ fn variant_info_for_generator<'tcx>( cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, layout: TyAndLayout<'tcx>, def_id: DefId, - substs: ty::SubstsRef<'tcx>, + args: ty::GenericArgsRef<'tcx>, ) -> (Vec, Option) { let Variants::Multiple { tag, ref tag_encoding, tag_field, .. } = layout.variants else { return (vec![], None); }; - let (generator, state_specific_names) = cx.tcx.generator_layout_and_saved_local_names(def_id); + let generator = cx.tcx.optimized_mir(def_id).generator_layout().unwrap(); let upvar_names = cx.tcx.closure_saved_names_of_captured_variables(def_id); let mut upvars_size = Size::ZERO; - let upvar_fields: Vec<_> = substs + let upvar_fields: Vec<_> = args .as_generator() .upvar_tys() + .iter() .zip(upvar_names) .enumerate() .map(|(field_idx, (_, name))| { @@ -959,7 +1074,7 @@ fn variant_info_for_generator<'tcx>( upvars_size = upvars_size.max(offset + field_layout.size); FieldInfo { kind: FieldKind::Upvar, - name: Symbol::intern(&name), + name: *name, offset: offset.bytes(), size: field_layout.size.bytes(), align: field_layout.align.abi.bytes(), @@ -983,9 +1098,10 @@ fn variant_info_for_generator<'tcx>( variant_size = variant_size.max(offset + field_layout.size); FieldInfo { kind: FieldKind::GeneratorLocal, - name: state_specific_names.get(*local).copied().flatten().unwrap_or( - Symbol::intern(&format!(".generator_field{}", local.as_usize())), - ), + name: generator.field_names[*local].unwrap_or(Symbol::intern(&format!( + ".generator_field{}", + local.as_usize() + ))), offset: offset.bytes(), size: field_layout.size.bytes(), align: field_layout.align.abi.bytes(), @@ -1022,7 +1138,7 @@ fn variant_info_for_generator<'tcx>( } VariantInfo { - name: Some(Symbol::intern(&ty::GeneratorSubsts::variant_name(variant_idx))), + name: Some(Symbol::intern(&ty::GeneratorArgs::variant_name(variant_idx))), kind: SizeKind::Exact, size: variant_size.bytes(), align: variant_layout.align.abi.bytes(), diff --git a/compiler/rustc_ty_utils/src/layout_sanity_check.rs b/compiler/rustc_ty_utils/src/layout_sanity_check.rs index c4a4cda68016d..8633334381ada 100644 --- a/compiler/rustc_ty_utils/src/layout_sanity_check.rs +++ b/compiler/rustc_ty_utils/src/layout_sanity_check.rs @@ -52,7 +52,7 @@ pub(super) fn sanity_check_layout<'tcx>( let mut fields = non_zst_fields(cx, layout); let Some(first) = fields.next() else { // No fields here, so this could be a primitive or enum -- either way it's not a newtype around a thing - return *layout + return *layout; }; if fields.next().is_none() { let (offset, first) = first; @@ -77,7 +77,7 @@ pub(super) fn sanity_check_layout<'tcx>( Abi::Uninhabited | Abi::Aggregate { .. }, "ABI unexpectedly missing alignment and/or size in {layout:#?}" ); - return + return; }; assert_eq!( layout.layout.align().abi, diff --git a/compiler/rustc_ty_utils/src/lib.rs b/compiler/rustc_ty_utils/src/lib.rs index 55b8857ed391f..147b600f7ba52 100644 --- a/compiler/rustc_ty_utils/src/lib.rs +++ b/compiler/rustc_ty_utils/src/lib.rs @@ -8,6 +8,7 @@ #![feature(assert_matches)] #![feature(iterator_try_collect)] #![feature(let_chains)] +#![feature(if_let_guard)] #![feature(never_type)] #![feature(box_patterns)] #![recursion_limit = "256"] diff --git a/compiler/rustc_ty_utils/src/needs_drop.rs b/compiler/rustc_ty_utils/src/needs_drop.rs index 1f9701b93226d..1fc5d9359a48a 100644 --- a/compiler/rustc_ty_utils/src/needs_drop.rs +++ b/compiler/rustc_ty_utils/src/needs_drop.rs @@ -3,8 +3,8 @@ use rustc_data_structures::fx::FxHashSet; use rustc_hir::def_id::DefId; use rustc_middle::query::Providers; -use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::util::{needs_drop_components, AlwaysRequiresDrop}; +use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt}; use rustc_session::Limit; use rustc_span::{sym, DUMMY_SP}; @@ -19,13 +19,32 @@ fn needs_drop_raw<'tcx>(tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx> // needs drop. let adt_has_dtor = |adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant); - let res = - drop_tys_helper(tcx, query.value, query.param_env, adt_has_dtor, false).next().is_some(); + let res = drop_tys_helper(tcx, query.value, query.param_env, adt_has_dtor, false) + .filter(filter_array_elements(tcx, query.param_env)) + .next() + .is_some(); debug!("needs_drop_raw({:?}) = {:?}", query, res); res } +/// HACK: in order to not mistakenly assume that `[PhantomData; N]` requires drop glue +/// we check the element type for drop glue. The correct fix would be looking at the +/// entirety of the code around `needs_drop_components` and this file and come up with +/// logic that is easier to follow while not repeating any checks that may thus diverge. +fn filter_array_elements<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> impl Fn(&Result, AlwaysRequiresDrop>) -> bool { + move |ty| match ty { + Ok(ty) => match *ty.kind() { + ty::Array(elem, _) => tcx.needs_drop_raw(param_env.and(elem)), + _ => true, + }, + Err(AlwaysRequiresDrop) => true, + } +} + fn has_significant_drop_raw<'tcx>( tcx: TyCtxt<'tcx>, query: ty::ParamEnvAnd<'tcx, Ty<'tcx>>, @@ -37,6 +56,7 @@ fn has_significant_drop_raw<'tcx>( adt_consider_insignificant_dtor(tcx), true, ) + .filter(filter_array_elements(tcx, query.param_env)) .next() .is_some(); debug!("has_significant_drop_raw({:?}) = {:?}", query, res); @@ -80,7 +100,7 @@ impl<'tcx, F> NeedsDropTypes<'tcx, F> { impl<'tcx, F, I> Iterator for NeedsDropTypes<'tcx, F> where - F: Fn(ty::AdtDef<'tcx>, SubstsRef<'tcx>) -> NeedsDropResult, + F: Fn(ty::AdtDef<'tcx>, GenericArgsRef<'tcx>) -> NeedsDropResult, I: Iterator>, { type Item = NeedsDropResult>; @@ -96,7 +116,7 @@ where return Some(Err(AlwaysRequiresDrop)); } - let components = match needs_drop_components(ty, &tcx.data_layout) { + let components = match needs_drop_components(tcx, ty) { Err(e) => return Some(Err(e)), Ok(components) => components, }; @@ -119,21 +139,25 @@ where _ if component.is_copy_modulo_regions(tcx, self.param_env) => (), - ty::Closure(_, substs) => { - queue_type(self, substs.as_closure().tupled_upvars_ty()); + ty::Closure(_, args) => { + for upvar in args.as_closure().upvar_tys() { + queue_type(self, upvar); + } } - ty::Generator(def_id, substs, _) => { - let substs = substs.as_generator(); - queue_type(self, substs.tupled_upvars_ty()); + ty::Generator(def_id, args, _) => { + let args = args.as_generator(); + for upvar in args.upvar_tys() { + queue_type(self, upvar); + } - let witness = substs.witness(); + let witness = args.witness(); let interior_tys = match witness.kind() { &ty::GeneratorWitness(tys) => tcx.erase_late_bound_regions(tys), _ => { tcx.sess.delay_span_bug( tcx.hir().span_if_local(def_id).unwrap_or(DUMMY_SP), - format!("unexpected generator witness type {:?}", witness), + format!("unexpected generator witness type {witness:?}"), ); return Some(Err(AlwaysRequiresDrop)); } @@ -147,8 +171,8 @@ where // Check for a `Drop` impl and whether this is a union or // `ManuallyDrop`. If it's a struct or enum without a `Drop` // impl then check whether the field types need `Drop`. - ty::Adt(adt_def, substs) => { - let tys = match (self.adt_components)(adt_def, substs) { + ty::Adt(adt_def, args) => { + let tys = match (self.adt_components)(adt_def, args) { Err(e) => return Some(Err(e)), Ok(tys) => tys, }; @@ -160,7 +184,7 @@ where queue_type(self, required); } } - ty::Array(..) | ty::Alias(..) | ty::Param(_) => { + ty::Alias(..) | ty::Array(..) | ty::Placeholder(_) | ty::Param(_) => { if ty == component { // Return the type to the caller: they may be able // to normalize further than we can. @@ -172,7 +196,31 @@ where queue_type(self, component); } } - _ => return Some(Err(AlwaysRequiresDrop)), + + ty::Foreign(_) | ty::Dynamic(..) => { + return Some(Err(AlwaysRequiresDrop)); + } + + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Slice(_) + | ty::Ref(..) + | ty::RawPtr(..) + | ty::FnDef(..) + | ty::FnPtr(..) + | ty::Tuple(_) + | ty::Bound(..) + | ty::GeneratorWitness(..) + | ty::GeneratorWitnessMIR(..) + | ty::Never + | ty::Infer(_) + | ty::Error(_) => { + bug!("unexpected type returned by `needs_drop_components`: {component}") + } } } } @@ -210,7 +258,7 @@ fn drop_tys_helper<'tcx>( match subty.kind() { ty::Adt(adt_id, subst) => { for subty in tcx.adt_drop_tys(adt_id.did())? { - vec.push(EarlyBinder(subty).subst(tcx, subst)); + vec.push(EarlyBinder::bind(subty).instantiate(tcx, subst)); } } _ => vec.push(subty), @@ -219,7 +267,7 @@ fn drop_tys_helper<'tcx>( }) } - let adt_components = move |adt_def: ty::AdtDef<'tcx>, substs: SubstsRef<'tcx>| { + let adt_components = move |adt_def: ty::AdtDef<'tcx>, args: GenericArgsRef<'tcx>| { if adt_def.is_manually_drop() { debug!("drop_tys_helper: `{:?}` is manually drop", adt_def); Ok(Vec::new()) @@ -235,7 +283,7 @@ fn drop_tys_helper<'tcx>( // Since the destructor is insignificant, we just want to make sure all of // the passed in type parameters are also insignificant. // Eg: Vec dtor is insignificant when T=i32 but significant when T=Mutex. - Ok(substs.types().collect()) + Ok(args.types().collect()) } } } else if adt_def.is_union() { @@ -243,8 +291,8 @@ fn drop_tys_helper<'tcx>( Ok(Vec::new()) } else { let field_tys = adt_def.all_fields().map(|field| { - let r = tcx.type_of(field.did).subst(tcx, substs); - debug!("drop_tys_helper: Subst into {:?} with {:?} getting {:?}", field, substs, r); + let r = tcx.type_of(field.did).instantiate(tcx, args); + debug!("drop_tys_helper: Subst into {:?} with {:?} getting {:?}", field, args, r); r }); if only_significant { @@ -295,10 +343,10 @@ fn adt_drop_tys<'tcx>( // significant. let adt_has_dtor = |adt_def: ty::AdtDef<'tcx>| adt_def.destructor(tcx).map(|_| DtorType::Significant); - // `tcx.type_of(def_id)` identical to `tcx.make_adt(def, identity_substs)` + // `tcx.type_of(def_id)` identical to `tcx.make_adt(def, identity_args)` drop_tys_helper( tcx, - tcx.type_of(def_id).subst_identity(), + tcx.type_of(def_id).instantiate_identity(), tcx.param_env(def_id), adt_has_dtor, false, @@ -307,7 +355,7 @@ fn adt_drop_tys<'tcx>( .map(|components| tcx.mk_type_list(&components)) } // If `def_id` refers to a generic ADT, the queries above and below act as if they had been handed -// a `tcx.make_ty(def, identity_substs)` and as such it is legal to substitute the generic parameters +// a `tcx.make_ty(def, identity_args)` and as such it is legal to substitute the generic parameters // of the ADT into the outputted `ty`s. fn adt_significant_drop_tys( tcx: TyCtxt<'_>, @@ -315,7 +363,7 @@ fn adt_significant_drop_tys( ) -> Result<&ty::List>, AlwaysRequiresDrop> { drop_tys_helper( tcx, - tcx.type_of(def_id).subst_identity(), // identical to `tcx.make_adt(def, identity_substs)` + tcx.type_of(def_id).instantiate_identity(), // identical to `tcx.make_adt(def, identity_args)` tcx.param_env(def_id), adt_consider_insignificant_dtor(tcx), true, diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index 4e91dd380e865..38768f0a05b6d 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -1,12 +1,13 @@ use rustc_data_structures::fx::FxHashSet; -use rustc_errors::ErrorGuaranteed; +use rustc_hir::intravisit::Visitor; use rustc_hir::{def::DefKind, def_id::LocalDefId}; +use rustc_hir::{intravisit, CRATE_HIR_ID}; use rustc_middle::query::Providers; use rustc_middle::ty::util::{CheckRegions, NotUniqueParam}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::ty::{TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_span::Span; -use rustc_type_ir::AliasKind; +use rustc_trait_selection::traits::check_args_compatible; use std::ops::ControlFlow; use crate::errors::{DuplicateArg, NotParam}; @@ -19,26 +20,42 @@ struct OpaqueTypeCollector<'tcx> { /// Avoid infinite recursion due to recursive declarations. seen: FxHashSet, + + span: Option, } impl<'tcx> OpaqueTypeCollector<'tcx> { - fn collect( - tcx: TyCtxt<'tcx>, - item: LocalDefId, - val: ty::Binder<'tcx, impl TypeVisitable>>, - ) -> Vec { - let mut collector = Self { tcx, opaques: Vec::new(), item, seen: Default::default() }; - val.skip_binder().visit_with(&mut collector); - collector.opaques + fn new(tcx: TyCtxt<'tcx>, item: LocalDefId) -> Self { + Self { tcx, opaques: Vec::new(), item, seen: Default::default(), span: None } } fn span(&self) -> Span { - self.tcx.def_span(self.item) + self.span.unwrap_or_else(|| { + self.tcx.def_ident_span(self.item).unwrap_or_else(|| self.tcx.def_span(self.item)) + }) + } + + fn visit_spanned(&mut self, span: Span, value: impl TypeVisitable>) { + let old = self.span; + self.span = Some(span); + value.visit_with(self); + self.span = old; + } + + fn parent_trait_ref(&self) -> Option> { + let parent = self.parent()?; + if matches!(self.tcx.def_kind(parent), DefKind::Impl { .. }) { + Some(self.tcx.impl_trait_ref(parent)?.instantiate_identity()) + } else { + None + } } fn parent(&self) -> Option { match self.tcx.def_kind(self.item) { - DefKind::Fn => None, + DefKind::AnonConst | DefKind::InlineConst | DefKind::Fn | DefKind::TyAlias { .. } => { + None + } DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => { Some(self.tcx.local_parent(self.item)) } @@ -48,119 +65,245 @@ impl<'tcx> OpaqueTypeCollector<'tcx> { ), } } + + /// Returns `true` if `opaque_hir_id` is a sibling or a child of a sibling of `self.item`. + /// + /// Example: + /// ```ignore UNSOLVED (is this a bug?) + /// # #![feature(type_alias_impl_trait)] + /// pub mod foo { + /// pub mod bar { + /// pub trait Bar { /* ... */ } + /// pub type Baz = impl Bar; + /// + /// # impl Bar for () {} + /// fn f1() -> Baz { /* ... */ } + /// } + /// fn f2() -> bar::Baz { /* ... */ } + /// } + /// ``` + /// + /// and `opaque_def_id` is the `DefId` of the definition of the opaque type `Baz`. + /// For the above example, this function returns `true` for `f1` and `false` for `f2`. + #[instrument(level = "trace", skip(self), ret)] + fn check_tait_defining_scope(&self, opaque_def_id: LocalDefId) -> bool { + let mut hir_id = self.tcx.hir().local_def_id_to_hir_id(self.item); + let opaque_hir_id = self.tcx.hir().local_def_id_to_hir_id(opaque_def_id); + + // Named opaque types can be defined by any siblings or children of siblings. + let scope = self.tcx.hir().get_defining_scope(opaque_hir_id); + // We walk up the node tree until we hit the root or the scope of the opaque type. + while hir_id != scope && hir_id != CRATE_HIR_ID { + hir_id = self.tcx.hir().get_parent_item(hir_id).into(); + } + // Syntactically, we are allowed to define the concrete type if: + hir_id == scope + } + + fn collect_body_and_predicate_taits(&mut self) { + // Look at all where bounds. + self.tcx.predicates_of(self.item).instantiate_identity(self.tcx).visit_with(self); + // An item is allowed to constrain opaques declared within its own body (but not nested within + // nested functions). + self.collect_taits_declared_in_body(); + } + + #[instrument(level = "trace", skip(self))] + fn collect_taits_declared_in_body(&mut self) { + let body = self.tcx.hir().body(self.tcx.hir().body_owned_by(self.item)).value; + struct TaitInBodyFinder<'a, 'tcx> { + collector: &'a mut OpaqueTypeCollector<'tcx>, + } + impl<'v> intravisit::Visitor<'v> for TaitInBodyFinder<'_, '_> { + #[instrument(level = "trace", skip(self))] + fn visit_nested_item(&mut self, id: rustc_hir::ItemId) { + let id = id.owner_id.def_id; + if let DefKind::TyAlias { .. } = self.collector.tcx.def_kind(id) { + let items = self.collector.tcx.opaque_types_defined_by(id); + self.collector.opaques.extend(items); + } + } + #[instrument(level = "trace", skip(self))] + // Recurse into these, as they are type checked with their parent + fn visit_nested_body(&mut self, id: rustc_hir::BodyId) { + let body = self.collector.tcx.hir().body(id); + self.visit_body(body); + } + } + TaitInBodyFinder { collector: self }.visit_expr(body); + } } impl<'tcx> TypeVisitor> for OpaqueTypeCollector<'tcx> { - type BreakTy = ErrorGuaranteed; - #[instrument(skip(self), ret, level = "trace")] - fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { + fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { + t.super_visit_with(self)?; match t.kind() { - ty::Alias(AliasKind::Opaque, alias_ty) if alias_ty.def_id.is_local() => { + ty::Alias(ty::Opaque, alias_ty) if alias_ty.def_id.is_local() => { if !self.seen.insert(alias_ty.def_id.expect_local()) { return ControlFlow::Continue(()); } - match self.tcx.uses_unique_generic_params(alias_ty.substs, CheckRegions::Bound) { + + // TAITs outside their defining scopes are ignored. + let origin = self.tcx.opaque_type_origin(alias_ty.def_id.expect_local()); + trace!(?origin); + match origin { + rustc_hir::OpaqueTyOrigin::FnReturn(_) + | rustc_hir::OpaqueTyOrigin::AsyncFn(_) => {} + rustc_hir::OpaqueTyOrigin::TyAlias { in_assoc_ty } => { + if !in_assoc_ty { + if !self.check_tait_defining_scope(alias_ty.def_id.expect_local()) { + return ControlFlow::Continue(()); + } + } + } + } + + self.opaques.push(alias_ty.def_id.expect_local()); + + match self.tcx.uses_unique_generic_params(alias_ty.args, CheckRegions::Bound) { Ok(()) => { // FIXME: implement higher kinded lifetime bounds on nested opaque types. They are not // supported at all, so this is sound to do, but once we want to support them, you'll // start seeing the error below. - self.opaques.push(alias_ty.def_id.expect_local()); - // Collect opaque types nested within the associated type bounds of this opaque type. - for (pred, _span) in self + // We use identity args here, because we already know that the opaque type uses + // only generic parameters, and thus substituting would not give us more information. + for (pred, span) in self .tcx .explicit_item_bounds(alias_ty.def_id) - .subst_iter_copied(self.tcx, alias_ty.substs) + .instantiate_identity_iter_copied() { trace!(?pred); - pred.visit_with(self)?; + self.visit_spanned(span, pred); } - - ControlFlow::Continue(()) } Err(NotUniqueParam::NotParam(arg)) => { - let err = self.tcx.sess.emit_err(NotParam { + self.tcx.sess.emit_err(NotParam { arg, span: self.span(), opaque_span: self.tcx.def_span(alias_ty.def_id), }); - ControlFlow::Break(err) } Err(NotUniqueParam::DuplicateParam(arg)) => { - let err = self.tcx.sess.emit_err(DuplicateArg { + self.tcx.sess.emit_err(DuplicateArg { arg, span: self.span(), opaque_span: self.tcx.def_span(alias_ty.def_id), }); - ControlFlow::Break(err) } } } - ty::Alias(AliasKind::Projection, alias_ty) => { - if let Some(parent) = self.parent() { - trace!(?alias_ty); - let (trait_ref, own_substs) = alias_ty.trait_ref_and_own_substs(self.tcx); - - trace!(?trait_ref, ?own_substs); - // This avoids having to do normalization of `Self::AssocTy` by only - // supporting the case of a method defining opaque types from assoc types - // in the same impl block. - if trait_ref.self_ty() == self.tcx.type_of(parent).subst_identity() { - for assoc in self.tcx.associated_items(parent).in_definition_order() { + ty::Alias(ty::Weak, alias_ty) if alias_ty.def_id.is_local() => { + self.tcx + .type_of(alias_ty.def_id) + .instantiate(self.tcx, alias_ty.args) + .visit_with(self)?; + } + ty::Alias(ty::Projection, alias_ty) => { + // This avoids having to do normalization of `Self::AssocTy` by only + // supporting the case of a method defining opaque types from assoc types + // in the same impl block. + if let Some(parent_trait_ref) = self.parent_trait_ref() { + // If the trait ref of the associated item and the impl differs, + // then we can't use the impl's identity substitutions below, so + // just skip. + if alias_ty.trait_ref(self.tcx) == parent_trait_ref { + let parent = self.parent().expect("we should have a parent here"); + + for &assoc in self.tcx.associated_items(parent).in_definition_order() { trace!(?assoc); - if assoc.trait_item_def_id == Some(alias_ty.def_id) { - // We reconstruct the generic args of the associated type within the impl - // from the impl's generics and the generic args passed to the type via the - // projection. - let substs = ty::InternalSubsts::identity_for_item( - self.tcx, - parent.to_def_id(), - ); - trace!(?substs); - let substs: Vec<_> = - substs.iter().chain(own_substs.iter().copied()).collect(); - trace!(?substs); - // Find opaque types in this associated type. + if assoc.trait_item_def_id != Some(alias_ty.def_id) { + continue; + } + + // If the type is further specializable, then the type_of + // is not actually correct below. + if !assoc.defaultness(self.tcx).is_final() { + continue; + } + + let impl_args = alias_ty.args.rebase_onto( + self.tcx, + parent_trait_ref.def_id, + ty::GenericArgs::identity_for_item(self.tcx, parent), + ); + + if check_args_compatible(self.tcx, assoc, impl_args) { return self .tcx .type_of(assoc.def_id) - .subst(self.tcx, &substs) + .instantiate(self.tcx, impl_args) .visit_with(self); + } else { + self.tcx.sess.delay_span_bug( + self.tcx.def_span(assoc.def_id), + "item had incorrect args", + ); } } } } - t.super_visit_with(self) } - _ => t.super_visit_with(self), + ty::Adt(def, _) if def.did().is_local() => { + if !self.seen.insert(def.did().expect_local()) { + return ControlFlow::Continue(()); + } + for variant in def.variants().iter() { + for field in variant.fields.iter() { + // Don't use the `ty::Adt` args, we either + // * found the opaque in the args + // * will find the opaque in the unsubstituted fields + // The only other situation that can occur is that after substituting, + // some projection resolves to an opaque that we would have otherwise + // not found. While we could substitute and walk those, that would mean we + // would have to walk all substitutions of an Adt, which can quickly + // degenerate into looking at an exponential number of types. + let ty = self.tcx.type_of(field.did).instantiate_identity(); + self.visit_spanned(self.tcx.def_span(field.did), ty); + } + } + } + _ => trace!(kind=?t.kind()), } + ControlFlow::Continue(()) } } fn opaque_types_defined_by<'tcx>(tcx: TyCtxt<'tcx>, item: LocalDefId) -> &'tcx [LocalDefId] { let kind = tcx.def_kind(item); trace!(?kind); - // FIXME(type_alias_impl_trait): This is definitely still wrong except for RPIT and impl trait in assoc types. + let mut collector = OpaqueTypeCollector::new(tcx, item); match kind { - // We're also doing this for `AssocTy` for the wf checks in `check_opaque_meets_bounds` - DefKind::Fn | DefKind::AssocFn | DefKind::AssocTy | DefKind::AssocConst => { - let defined_opaques = match kind { - DefKind::Fn => { - OpaqueTypeCollector::collect(tcx, item, tcx.fn_sig(item).subst_identity()) - } - DefKind::AssocFn => { - OpaqueTypeCollector::collect(tcx, item, tcx.fn_sig(item).subst_identity()) - } - DefKind::AssocTy | DefKind::AssocConst => OpaqueTypeCollector::collect( - tcx, - item, - ty::Binder::dummy(tcx.type_of(item).subst_identity()), - ), - _ => unreachable!(), + // Walk over the signature of the function-like to find the opaques. + DefKind::AssocFn | DefKind::Fn => { + let ty_sig = tcx.fn_sig(item).instantiate_identity(); + let hir_sig = tcx.hir().get_by_def_id(item).fn_sig().unwrap(); + // Walk over the inputs and outputs manually in order to get good spans for them. + collector.visit_spanned(hir_sig.decl.output.span(), ty_sig.output()); + for (hir, ty) in hir_sig.decl.inputs.iter().zip(ty_sig.inputs().iter()) { + collector.visit_spanned(hir.span, ty.map_bound(|x| *x)); + } + collector.collect_body_and_predicate_taits(); + } + // Walk over the type of the item to find opaques. + DefKind::Static(_) | DefKind::Const | DefKind::AssocConst | DefKind::AnonConst => { + let span = match tcx.hir().get_by_def_id(item).ty() { + Some(ty) => ty.span, + _ => tcx.def_span(item), }; - tcx.arena.alloc_from_iter(defined_opaques) + collector.visit_spanned(span, tcx.type_of(item).instantiate_identity()); + collector.collect_body_and_predicate_taits(); + } + // We're also doing this for `AssocTy` for the wf checks in `check_opaque_meets_bounds` + DefKind::TyAlias { .. } | DefKind::AssocTy => { + tcx.type_of(item).instantiate_identity().visit_with(&mut collector); + } + DefKind::OpaqueTy => { + for (pred, span) in tcx.explicit_item_bounds(item).instantiate_identity_iter_copied() { + collector.visit_spanned(span, pred); + } } DefKind::Mod | DefKind::Struct @@ -168,29 +311,25 @@ fn opaque_types_defined_by<'tcx>(tcx: TyCtxt<'tcx>, item: LocalDefId) -> &'tcx [ | DefKind::Enum | DefKind::Variant | DefKind::Trait - | DefKind::TyAlias | DefKind::ForeignTy | DefKind::TraitAlias | DefKind::TyParam - | DefKind::Const | DefKind::ConstParam - | DefKind::Static(_) | DefKind::Ctor(_, _) | DefKind::Macro(_) | DefKind::ExternCrate | DefKind::Use | DefKind::ForeignMod - | DefKind::AnonConst - | DefKind::InlineConst - | DefKind::OpaqueTy - | DefKind::ImplTraitPlaceholder | DefKind::Field | DefKind::LifetimeParam | DefKind::GlobalAsm - | DefKind::Impl { .. } - | DefKind::Closure - | DefKind::Generator => &[], + | DefKind::Impl { .. } => {} + // Closures and generators are type checked with their parent, so there is no difference here. + DefKind::Closure | DefKind::Generator | DefKind::InlineConst => { + return tcx.opaque_types_defined_by(tcx.local_parent(item)); + } } + tcx.arena.alloc_from_iter(collector.opaques) } pub(super) fn provide(providers: &mut Providers) { diff --git a/compiler/rustc_ty_utils/src/representability.rs b/compiler/rustc_ty_utils/src/representability.rs index 0b5e27c2c743f..f34e0df2c75ef 100644 --- a/compiler/rustc_ty_utils/src/representability.rs +++ b/compiler/rustc_ty_utils/src/representability.rs @@ -31,7 +31,7 @@ fn representability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Representability { } Representability::Representable } - DefKind::Field => representability_ty(tcx, tcx.type_of(def_id).subst_identity()), + DefKind::Field => representability_ty(tcx, tcx.type_of(def_id).instantiate_identity()), def_kind => bug!("unexpected {def_kind:?}"), } } @@ -68,14 +68,14 @@ representability_adt_ty(Bar<..>) is in the cycle and representability(Bar) is *not* in the cycle. */ fn representability_adt_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Representability { - let ty::Adt(adt, substs) = ty.kind() else { bug!("expected adt") }; + let ty::Adt(adt, args) = ty.kind() else { bug!("expected adt") }; if let Some(def_id) = adt.did().as_local() { rtry!(tcx.representability(def_id)); } // At this point, we know that the item of the ADT type is representable; // but the type parameters may cause a cycle with an upstream type let params_in_repr = tcx.params_in_repr(adt.did()); - for (i, subst) in substs.iter().enumerate() { + for (i, subst) in args.iter().enumerate() { if let ty::GenericArgKind::Type(ty) = subst.unpack() { if params_in_repr.contains(i as u32) { rtry!(representability_ty(tcx, ty)); @@ -91,7 +91,11 @@ fn params_in_repr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> BitSet { let mut params_in_repr = BitSet::new_empty(generics.params.len()); for variant in adt_def.variants() { for field in variant.fields.iter() { - params_in_repr_ty(tcx, tcx.type_of(field.did).subst_identity(), &mut params_in_repr); + params_in_repr_ty( + tcx, + tcx.type_of(field.did).instantiate_identity(), + &mut params_in_repr, + ); } } params_in_repr @@ -99,9 +103,9 @@ fn params_in_repr(tcx: TyCtxt<'_>, def_id: LocalDefId) -> BitSet { fn params_in_repr_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, params_in_repr: &mut BitSet) { match *ty.kind() { - ty::Adt(adt, substs) => { + ty::Adt(adt, args) => { let inner_params_in_repr = tcx.params_in_repr(adt.did()); - for (i, subst) in substs.iter().enumerate() { + for (i, subst) in args.iter().enumerate() { if let ty::GenericArgKind::Type(ty) = subst.unpack() { if inner_params_in_repr.contains(i as u32) { params_in_repr_ty(tcx, ty, params_in_repr); diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index 65dc3c39c6ae6..ba0258b63cb62 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -1,13 +1,11 @@ -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_index::bit_set::BitSet; use rustc_middle::query::Providers; use rustc_middle::ty::{ - self, Binder, EarlyBinder, ImplTraitInTraitData, Predicate, PredicateKind, ToPredicate, Ty, - TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, + self, EarlyBinder, ToPredicate, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, }; -use rustc_session::config::TraitSolver; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; use rustc_span::DUMMY_SP; use rustc_trait_selection::traits; @@ -39,14 +37,12 @@ fn sized_constraint_for_ty<'tcx>( Some(&ty) => sized_constraint_for_ty(tcx, adtdef, ty), }, - Adt(adt, substs) => { + Adt(adt, args) => { // recursive case let adt_tys = adt.sized_constraint(tcx); debug!("sized_constraint_for_ty({:?}) intermediate = {:?}", ty, adt_tys); adt_tys - .0 - .iter() - .map(|ty| adt_tys.rebind(*ty).subst(tcx, substs)) + .iter_instantiated(tcx, args) .flat_map(|ty| sized_constraint_for_ty(tcx, adtdef, ty)) .collect() } @@ -62,11 +58,18 @@ fn sized_constraint_for_ty<'tcx>( // we know that `T` is Sized and do not need to check // it on the impl. - let Some(sized_trait) = tcx.lang_items().sized_trait() else { return vec![ty] }; - let sized_predicate = - ty::TraitRef::new(tcx, sized_trait, [ty]).without_const().to_predicate(tcx); + let Some(sized_trait_def_id) = tcx.lang_items().sized_trait() else { return vec![ty] }; let predicates = tcx.predicates_of(adtdef.did()).predicates; - if predicates.iter().any(|(p, _)| *p == sized_predicate) { vec![] } else { vec![ty] } + if predicates.iter().any(|(p, _)| { + p.as_trait_clause().is_some_and(|trait_pred| { + trait_pred.def_id() == sized_trait_def_id + && trait_pred.self_ty().skip_binder() == ty + }) + }) { + vec![] + } else { + vec![ty] + } } Placeholder(..) | Bound(..) | Infer(..) => { @@ -77,13 +80,13 @@ fn sized_constraint_for_ty<'tcx>( result } -fn impl_defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness { +fn defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness { match tcx.hir().get_by_def_id(def_id) { hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(impl_), .. }) => impl_.defaultness, hir::Node::ImplItem(hir::ImplItem { defaultness, .. }) | hir::Node::TraitItem(hir::TraitItem { defaultness, .. }) => *defaultness, node => { - bug!("`impl_defaultness` called on {:?}", node); + bug!("`defaultness` called on {:?}", node); } } } @@ -96,24 +99,25 @@ fn impl_defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness { /// - a tuple of type parameters or projections, if there are multiple /// such. /// - an Error, if a type is infinitely sized -fn adt_sized_constraint(tcx: TyCtxt<'_>, def_id: DefId) -> &[Ty<'_>] { +fn adt_sized_constraint<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: DefId, +) -> ty::EarlyBinder<&'tcx ty::List>> { if let Some(def_id) = def_id.as_local() { if matches!(tcx.representability(def_id), ty::Representability::Infinite) { - return tcx.mk_type_list(&[tcx.ty_error_misc()]); + return ty::EarlyBinder::bind(tcx.mk_type_list(&[Ty::new_misc_error(tcx)])); } } let def = tcx.adt_def(def_id); - let result = tcx.mk_type_list_from_iter( - def.variants() - .iter() - .filter_map(|v| v.fields.raw.last()) - .flat_map(|f| sized_constraint_for_ty(tcx, def, tcx.type_of(f.did).subst_identity())), - ); + let result = + tcx.mk_type_list_from_iter(def.variants().iter().filter_map(|v| v.tail_opt()).flat_map( + |f| sized_constraint_for_ty(tcx, def, tcx.type_of(f.did).instantiate_identity()), + )); debug!("adt_sized_constraint: {:?} => {:?}", def, result); - result + ty::EarlyBinder::bind(result) } /// See `ParamEnv` struct definition for details. @@ -122,20 +126,6 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { let ty::InstantiatedPredicates { mut predicates, .. } = tcx.predicates_of(def_id).instantiate_identity(tcx); - // When computing the param_env of an RPITIT, use predicates of the containing function, - // *except* for the additional assumption that the RPITIT normalizes to the trait method's - // default opaque type. This is needed to properly check the item bounds of the assoc - // type hold (`check_type_bounds`), since that method already installs a similar projection - // bound, so they will conflict. - // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): I don't like this, we should - // at least be making sure that the generics in RPITITs and their parent fn don't - // get out of alignment, or else we do actually need to substitute these predicates. - if let Some(ImplTraitInTraitData::Trait { fn_def_id, .. }) - | Some(ImplTraitInTraitData::Impl { fn_def_id, .. }) = tcx.opt_rpitit_info(def_id) - { - predicates = tcx.predicates_of(fn_def_id).instantiate_identity(tcx).predicates; - } - // Finally, we have to normalize the bounds in the environment, in // case they contain any associated type projections. This process // can yield errors if the put in illegal associated types, like @@ -148,15 +138,12 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { // are any errors at that point, so outside of type inference you can be // sure that this will succeed without errors anyway. - if tcx.sess.opts.unstable_opts.trait_solver == TraitSolver::Chalk { - let environment = well_formed_types_in_env(tcx, def_id); - predicates.extend(environment); - } - if tcx.def_kind(def_id) == DefKind::AssocFn - && tcx.associated_item(def_id).container == ty::AssocItemContainer::TraitContainer + && let assoc_item = tcx.associated_item(def_id) + && assoc_item.container == ty::AssocItemContainer::TraitContainer + && assoc_item.defaultness(tcx).has_value() { - let sig = tcx.fn_sig(def_id).subst_identity(); + let sig = tcx.fn_sig(def_id).instantiate_identity(); // We accounted for the binder of the fn sig, so skip the binder. sig.skip_binder().visit_with(&mut ImplTraitInTraitFinder { tcx, @@ -169,84 +156,9 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { } let local_did = def_id.as_local(); - // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): This isn't correct for - // RPITITs in const trait fn. - let hir_id = local_did.and_then(|def_id| tcx.opt_local_def_id_to_hir_id(def_id)); - - // FIXME(consts): This is not exactly in line with the constness query. - let constness = match hir_id { - Some(hir_id) => match tcx.hir().get(hir_id) { - hir::Node::TraitItem(hir::TraitItem { kind: hir::TraitItemKind::Fn(..), .. }) - if tcx.is_const_default_method(def_id) => - { - hir::Constness::Const - } - - hir::Node::Item(hir::Item { kind: hir::ItemKind::Const(..), .. }) - | hir::Node::Item(hir::Item { kind: hir::ItemKind::Static(..), .. }) - | hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Const(..), .. - }) - | hir::Node::AnonConst(_) - | hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Const(..), .. }) - | hir::Node::ImplItem(hir::ImplItem { - kind: - hir::ImplItemKind::Fn( - hir::FnSig { - header: hir::FnHeader { constness: hir::Constness::Const, .. }, - .. - }, - .., - ), - .. - }) => hir::Constness::Const, - - hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Type(..) | hir::ImplItemKind::Fn(..), - .. - }) => { - let parent_hir_id = tcx.hir().parent_id(hir_id); - match tcx.hir().get(parent_hir_id) { - hir::Node::Item(hir::Item { - kind: hir::ItemKind::Impl(hir::Impl { constness, .. }), - .. - }) => *constness, - _ => span_bug!( - tcx.def_span(parent_hir_id.owner), - "impl item's parent node is not an impl", - ), - } - } - - hir::Node::Item(hir::Item { - kind: - hir::ItemKind::Fn(hir::FnSig { header: hir::FnHeader { constness, .. }, .. }, ..), - .. - }) - | hir::Node::TraitItem(hir::TraitItem { - kind: - hir::TraitItemKind::Fn( - hir::FnSig { header: hir::FnHeader { constness, .. }, .. }, - .., - ), - .. - }) - | hir::Node::Item(hir::Item { - kind: hir::ItemKind::Impl(hir::Impl { constness, .. }), - .. - }) => *constness, - - _ => hir::Constness::NotConst, - }, - // FIXME(consts): It's suspicious that a param-env for a foreign item - // will always have NotConst param-env, though we don't typically use - // that param-env for anything meaningful right now, so it's likely - // not an issue. - None => hir::Constness::NotConst, - }; let unnormalized_env = - ty::ParamEnv::new(tcx.mk_predicates(&predicates), traits::Reveal::UserFacing, constness); + ty::ParamEnv::new(tcx.mk_clauses(&predicates), traits::Reveal::UserFacing); let body_id = local_did.unwrap_or(CRATE_DEF_ID); let cause = traits::ObligationCause::misc(tcx.def_span(def_id), body_id); @@ -259,7 +171,7 @@ fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { /// its corresponding opaque within the body of a default-body trait method. struct ImplTraitInTraitFinder<'a, 'tcx> { tcx: TyCtxt<'tcx>, - predicates: &'a mut Vec>, + predicates: &'a mut Vec>, fn_def_id: DefId, bound_vars: &'tcx ty::List, seen: FxHashSet, @@ -279,8 +191,10 @@ impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow { if let ty::Alias(ty::Projection, unshifted_alias_ty) = *ty.kind() - && self.tcx.is_impl_trait_in_trait(unshifted_alias_ty.def_id) - && self.tcx.impl_trait_in_trait_parent_fn(unshifted_alias_ty.def_id) == self.fn_def_id + && let Some(ty::ImplTraitInTraitData::Trait { fn_def_id, .. } + | ty::ImplTraitInTraitData::Impl { fn_def_id, .. }) + = self.tcx.opt_rpitit_info(unshifted_alias_ty.def_id) + && fn_def_id == self.fn_def_id && self.seen.insert(unshifted_alias_ty.def_id) { // We have entered some binders as we've walked into the @@ -289,12 +203,13 @@ impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { let shifted_alias_ty = self.tcx.fold_regions(unshifted_alias_ty, |re, depth| { if let ty::ReLateBound(index, bv) = re.kind() { if depth != ty::INNERMOST { - return self.tcx.mk_re_error_with_message( + return ty::Region::new_error_with_message( + self.tcx, DUMMY_SP, "we shouldn't walk non-predicate binders with `impl Trait`...", ); } - self.tcx.mk_re_late_bound(index.shifted_out_to_binder(self.depth), bv) + ty::Region::new_late_bound(self.tcx, index.shifted_out_to_binder(self.depth), bv) } else { re } @@ -303,11 +218,7 @@ impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { // If we're lowering to associated item, install the opaque type which is just // the `type_of` of the trait's associated item. If we're using the old lowering // strategy, then just reinterpret the associated type like an opaque :^) - let default_ty = if self.tcx.lower_impl_trait_in_trait_to_assoc_ty() { - self.tcx.type_of(shifted_alias_ty.def_id).subst(self.tcx, shifted_alias_ty.substs) - } else { - self.tcx.mk_alias(ty::Opaque, shifted_alias_ty) - }; + let default_ty = self.tcx.type_of(shifted_alias_ty.def_id).instantiate(self.tcx, shifted_alias_ty.args); self.predicates.push( ty::Binder::bind_with_vars( @@ -324,7 +235,7 @@ impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { for bound in self .tcx .item_bounds(unshifted_alias_ty.def_id) - .subst_iter(self.tcx, unshifted_alias_ty.substs) + .iter_instantiated(self.tcx, unshifted_alias_ty.args) { bound.visit_with(self); } @@ -334,138 +245,10 @@ impl<'tcx> TypeVisitor> for ImplTraitInTraitFinder<'_, 'tcx> { } } -/// Elaborate the environment. -/// -/// Collect a list of `Predicate`'s used for building the `ParamEnv`. Adds `TypeWellFormedFromEnv`'s -/// that are assumed to be well-formed (because they come from the environment). -/// -/// Used only in chalk mode. -fn well_formed_types_in_env(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List> { - use rustc_hir::{ForeignItemKind, ImplItemKind, ItemKind, Node, TraitItemKind}; - use rustc_middle::ty::subst::GenericArgKind; - - debug!("environment(def_id = {:?})", def_id); - - // The environment of an impl Trait type is its defining function's environment. - if let Some(parent) = ty::is_impl_trait_defn(tcx, def_id) { - return well_formed_types_in_env(tcx, parent.to_def_id()); - } - - // Compute the bounds on `Self` and the type parameters. - let ty::InstantiatedPredicates { predicates, .. } = - tcx.predicates_of(def_id).instantiate_identity(tcx); - - let clauses = predicates.into_iter(); - - if !def_id.is_local() { - return ty::List::empty(); - } - let node = tcx.hir().get_by_def_id(def_id.expect_local()); - - enum NodeKind { - TraitImpl, - InherentImpl, - Fn, - Other, - } - - let node_kind = match node { - Node::TraitItem(item) => match item.kind { - TraitItemKind::Fn(..) => NodeKind::Fn, - _ => NodeKind::Other, - }, - - Node::ImplItem(item) => match item.kind { - ImplItemKind::Fn(..) => NodeKind::Fn, - _ => NodeKind::Other, - }, - - Node::Item(item) => match item.kind { - ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => NodeKind::TraitImpl, - ItemKind::Impl(hir::Impl { of_trait: None, .. }) => NodeKind::InherentImpl, - ItemKind::Fn(..) => NodeKind::Fn, - _ => NodeKind::Other, - }, - - Node::ForeignItem(item) => match item.kind { - ForeignItemKind::Fn(..) => NodeKind::Fn, - _ => NodeKind::Other, - }, - - // FIXME: closures? - _ => NodeKind::Other, - }; - - // FIXME(eddyb) isn't the unordered nature of this a hazard? - let mut inputs = FxIndexSet::default(); - - match node_kind { - // In a trait impl, we assume that the header trait ref and all its - // constituents are well-formed. - NodeKind::TraitImpl => { - let trait_ref = tcx.impl_trait_ref(def_id).expect("not an impl").subst_identity(); - - // FIXME(chalk): this has problems because of late-bound regions - //inputs.extend(trait_ref.substs.iter().flat_map(|arg| arg.walk())); - inputs.extend(trait_ref.substs.iter()); - } - - // In an inherent impl, we assume that the receiver type and all its - // constituents are well-formed. - NodeKind::InherentImpl => { - let self_ty = tcx.type_of(def_id).subst_identity(); - inputs.extend(self_ty.walk()); - } - - // In an fn, we assume that the arguments and all their constituents are - // well-formed. - NodeKind::Fn => { - let fn_sig = tcx.fn_sig(def_id).subst_identity(); - let fn_sig = tcx.liberate_late_bound_regions(def_id, fn_sig); - - inputs.extend(fn_sig.inputs().iter().flat_map(|ty| ty.walk())); - } - - NodeKind::Other => (), - } - let input_clauses = inputs.into_iter().filter_map(|arg| { - match arg.unpack() { - GenericArgKind::Type(ty) => { - let binder = Binder::dummy(PredicateKind::TypeWellFormedFromEnv(ty)); - Some(tcx.mk_predicate(binder)) - } - - // FIXME(eddyb) no WF conditions from lifetimes? - GenericArgKind::Lifetime(_) => None, - - // FIXME(eddyb) support const generics in Chalk - GenericArgKind::Const(_) => None, - } - }); - - tcx.mk_predicates_from_iter(clauses.chain(input_clauses)) -} - fn param_env_reveal_all_normalized(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { tcx.param_env(def_id).with_reveal_all_normalized(tcx) } -fn instance_def_size_estimate<'tcx>( - tcx: TyCtxt<'tcx>, - instance_def: ty::InstanceDef<'tcx>, -) -> usize { - use ty::InstanceDef; - - match instance_def { - InstanceDef::Item(..) | InstanceDef::DropGlue(..) => { - let mir = tcx.instance_mir(instance_def); - mir.basic_blocks.iter().map(|bb| bb.statements.len() + 1).sum() - } - // Estimate the size of other compiler-generated shims to be 1. - _ => 1, - } -} - /// If `def_id` is an issue 33140 hack impl, returns its self type; otherwise, returns `None`. /// /// See [`ty::ImplOverlapKind::Issue33140`] for more details. @@ -489,8 +272,8 @@ fn issue33140_self_ty(tcx: TyCtxt<'_>, def_id: DefId) -> Option, def_id: DefId) -> Option(tcx: TyCtxt<'tcx>, def_id: DefId) -> BitSet(tcx: TyCtxt<'tcx>, def_id: DefId) -> BitSet + Hash + Ord; type DefId: Clone + Debug + Hash + Ord; type Binder; - type Ty: Clone + Debug + Hash + Ord; - type Const: Clone + Debug + Hash + Ord; - type Region: Clone + Debug + Hash + Ord; + type Ty: Clone + DebugWithInfcx + Hash + Ord; + type Const: Clone + DebugWithInfcx + Hash + Ord; + type Region: Clone + DebugWithInfcx + Hash + Ord; type Predicate; type TypeAndMut: Clone + Debug + Hash + Ord; type Mutability: Clone + Debug + Hash + Ord; type Movability: Clone + Debug + Hash + Ord; - type PolyFnSig: Clone + Debug + Hash + Ord; - type ListBinderExistentialPredicate: Clone + Debug + Hash + Ord; - type BinderListTy: Clone + Debug + Hash + Ord; - type ListTy: Clone + Debug + Hash + Ord; - type AliasTy: Clone + Debug + Hash + Ord; + type PolyFnSig: Clone + DebugWithInfcx + Hash + Ord; + type ListBinderExistentialPredicate: Clone + DebugWithInfcx + Hash + Ord; + type BinderListTy: Clone + DebugWithInfcx + Hash + Ord; + type ListTy: Clone + Debug + Hash + Ord + IntoIterator; + type AliasTy: Clone + DebugWithInfcx + Hash + Ord; type ParamTy: Clone + Debug + Hash + Ord; type BoundTy: Clone + Debug + Hash + Ord; type PlaceholderType: Clone + Debug + Hash + Ord; - type InferTy: Clone + Debug + Hash + Ord; + type InferTy: Clone + DebugWithInfcx + Hash + Ord; type ErrorGuaranteed: Clone + Debug + Hash + Ord; type PredicateKind: Clone + Debug + Hash + PartialEq + Eq; type AllocId: Clone + Debug + Hash + Ord; + type InferConst: Clone + DebugWithInfcx + Hash + Ord; + type AliasConst: Clone + DebugWithInfcx + Hash + Ord; + type PlaceholderConst: Clone + Debug + Hash + Ord; + type ParamConst: Clone + Debug + Hash + Ord; + type BoundConst: Clone + Debug + Hash + Ord; + type ValueConst: Clone + Debug + Hash + Ord; + type ExprConst: Clone + DebugWithInfcx + Hash + Ord; + type EarlyBoundRegion: Clone + Debug + Hash + Ord; type BoundRegion: Clone + Debug + Hash + Ord; type FreeRegion: Clone + Debug + Hash + Ord; - type RegionVid: Clone + Debug + Hash + Ord; + type RegionVid: Clone + DebugWithInfcx + Hash + Ord; type PlaceholderRegion: Clone + Debug + Hash + Ord; + + fn ty_and_mut_to_parts(ty_and_mut: Self::TypeAndMut) -> (Self::Ty, Self::Mutability); + fn mutability_is_mut(mutbl: Self::Mutability) -> bool; } /// Imagine you have a function `F: FnOnce(&[T]) -> R`, plus an iterator `iter` @@ -203,6 +216,11 @@ bitflags! { /// Does this have `ConstKind::Placeholder`? const HAS_CT_PLACEHOLDER = 1 << 8; + /// Does this have placeholders? + const HAS_PLACEHOLDER = TypeFlags::HAS_TY_PLACEHOLDER.bits + | TypeFlags::HAS_RE_PLACEHOLDER.bits + | TypeFlags::HAS_CT_PLACEHOLDER.bits; + /// `true` if there are "names" of regions and so forth /// that are local to a particular fn/inferctxt const HAS_FREE_LOCAL_REGIONS = 1 << 9; @@ -390,7 +408,19 @@ impl DebruijnIndex { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub fn debug_bound_var( + fmt: &mut T, + debruijn: DebruijnIndex, + var: impl std::fmt::Debug, +) -> Result<(), std::fmt::Error> { + if debruijn == INNERMOST { + write!(fmt, "^{var:?}") + } else { + write!(fmt, "^{}_{:?}", debruijn.index(), var) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Encodable, Decodable, HashStable_Generic)] pub enum IntTy { Isize, @@ -448,7 +478,7 @@ impl IntTy { } } -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Debug)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] #[derive(Encodable, Decodable, HashStable_Generic)] pub enum UintTy { Usize, @@ -506,7 +536,7 @@ impl UintTy { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Encodable, Decodable, HashStable_Generic)] pub enum FloatTy { F32, @@ -752,20 +782,6 @@ impl fmt::Debug for FloatVid { } } -impl fmt::Debug for InferTy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use InferTy::*; - match *self { - TyVar(ref v) => v.fmt(f), - IntVar(ref v) => v.fmt(f), - FloatVar(ref v) => v.fmt(f), - FreshTy(v) => write!(f, "FreshTy({v:?})"), - FreshIntTy(v) => write!(f, "FreshIntTy({v:?})"), - FreshFloatTy(v) => write!(f, "FreshFloatTy({v:?})"), - } - } -} - impl fmt::Debug for Variance { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match *self { diff --git a/compiler/rustc_type_ir/src/structural_impls.rs b/compiler/rustc_type_ir/src/structural_impls.rs index 45a2e9023c9ca..f36f4ec8697fa 100644 --- a/compiler/rustc_type_ir/src/structural_impls.rs +++ b/compiler/rustc_type_ir/src/structural_impls.rs @@ -4,11 +4,13 @@ use crate::fold::{FallibleTypeFolder, TypeFoldable}; use crate::visit::{TypeVisitable, TypeVisitor}; -use crate::Interner; +use crate::{ConstKind, FloatTy, InferTy, IntTy, Interner, UintTy, UniverseIndex}; use rustc_data_structures::functor::IdFunctor; use rustc_data_structures::sync::Lrc; use rustc_index::{Idx, IndexVec}; +use core::fmt; +use std::marker::PhantomData; use std::ops::ControlFlow; /////////////////////////////////////////////////////////////////////////// @@ -21,7 +23,6 @@ TrivialTypeTraversalImpls! { (), bool, usize, - u8, u16, u32, u64, @@ -163,3 +164,189 @@ impl, Ix: Idx> TypeVisitable for IndexVec { + fn universe_of_ty(&self, ty: I::InferTy) -> Option; + fn universe_of_lt(&self, lt: I::RegionVid) -> Option; + fn universe_of_ct(&self, ct: I::InferConst) -> Option; +} + +impl InferCtxtLike for core::convert::Infallible { + fn universe_of_ty(&self, _ty: ::InferTy) -> Option { + match *self {} + } + fn universe_of_ct(&self, _ct: ::InferConst) -> Option { + match *self {} + } + fn universe_of_lt(&self, _lt: ::RegionVid) -> Option { + match *self {} + } +} + +pub trait DebugWithInfcx: fmt::Debug { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result; +} + +impl + ?Sized> DebugWithInfcx for &'_ T { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + >::fmt(this.map(|&data| data), f) + } +} +impl> DebugWithInfcx for [T] { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + match f.alternate() { + true => { + write!(f, "[\n")?; + for element in this.data.iter() { + write!(f, "{:?},\n", &this.wrap(element))?; + } + write!(f, "]") + } + false => { + write!(f, "[")?; + if this.data.len() > 0 { + for element in &this.data[..(this.data.len() - 1)] { + write!(f, "{:?}, ", &this.wrap(element))?; + } + if let Some(element) = this.data.last() { + write!(f, "{:?}", &this.wrap(element))?; + } + } + write!(f, "]") + } + } + } +} + +pub struct OptWithInfcx<'a, I: Interner, InfCtx: InferCtxtLike, T> { + pub data: T, + pub infcx: Option<&'a InfCtx>, + _interner: PhantomData, +} + +impl, T: Copy> Copy for OptWithInfcx<'_, I, InfCtx, T> {} +impl, T: Clone> Clone for OptWithInfcx<'_, I, InfCtx, T> { + fn clone(&self) -> Self { + Self { data: self.data.clone(), infcx: self.infcx, _interner: self._interner } + } +} + +impl<'a, I: Interner, T> OptWithInfcx<'a, I, core::convert::Infallible, T> { + pub fn new_no_ctx(data: T) -> Self { + Self { data, infcx: None, _interner: PhantomData } + } +} + +impl<'a, I: Interner, InfCtx: InferCtxtLike, T> OptWithInfcx<'a, I, InfCtx, T> { + pub fn new(data: T, infcx: &'a InfCtx) -> Self { + Self { data, infcx: Some(infcx), _interner: PhantomData } + } + + pub fn wrap(self, u: U) -> OptWithInfcx<'a, I, InfCtx, U> { + OptWithInfcx { data: u, infcx: self.infcx, _interner: PhantomData } + } + + pub fn map(self, f: impl FnOnce(T) -> U) -> OptWithInfcx<'a, I, InfCtx, U> { + OptWithInfcx { data: f(self.data), infcx: self.infcx, _interner: PhantomData } + } + + pub fn as_ref(&self) -> OptWithInfcx<'a, I, InfCtx, &T> { + OptWithInfcx { data: &self.data, infcx: self.infcx, _interner: PhantomData } + } +} + +impl, T: DebugWithInfcx> fmt::Debug + for OptWithInfcx<'_, I, InfCtx, T> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + DebugWithInfcx::fmt(self.as_ref(), f) + } +} + +impl fmt::Debug for IntTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name_str()) + } +} + +impl fmt::Debug for UintTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name_str()) + } +} + +impl fmt::Debug for FloatTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name_str()) + } +} + +impl fmt::Debug for InferTy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use InferTy::*; + match *self { + TyVar(ref v) => v.fmt(f), + IntVar(ref v) => v.fmt(f), + FloatVar(ref v) => v.fmt(f), + FreshTy(v) => write!(f, "FreshTy({v:?})"), + FreshIntTy(v) => write!(f, "FreshIntTy({v:?})"), + FreshFloatTy(v) => write!(f, "FreshFloatTy({v:?})"), + } + } +} +impl> DebugWithInfcx for InferTy { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + use InferTy::*; + match this.infcx.and_then(|infcx| infcx.universe_of_ty(*this.data)) { + None => write!(f, "{:?}", this.data), + Some(universe) => match *this.data { + TyVar(ty_vid) => write!(f, "?{}_{}t", ty_vid.index(), universe.index()), + IntVar(_) | FloatVar(_) | FreshTy(_) | FreshIntTy(_) | FreshFloatTy(_) => { + unreachable!() + } + }, + } + } +} + +impl fmt::Debug for ConstKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} +impl DebugWithInfcx for ConstKind { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + use ConstKind::*; + + match this.data { + Param(param) => write!(f, "{param:?}"), + Infer(var) => write!(f, "{:?}", &this.wrap(var)), + Bound(debruijn, var) => crate::debug_bound_var(f, *debruijn, var.clone()), + Placeholder(placeholder) => write!(f, "{placeholder:?}"), + Unevaluated(uv) => { + write!(f, "{:?}", &this.wrap(uv)) + } + Value(valtree) => write!(f, "{valtree:?}"), + Error(_) => write!(f, "{{const error}}"), + Expr(expr) => write!(f, "{:?}", &this.wrap(expr)), + } + } +} diff --git a/compiler/rustc_type_ir/src/sty.rs b/compiler/rustc_type_ir/src/sty.rs index f7344bacc028c..72bd50ace6de8 100644 --- a/compiler/rustc_type_ir/src/sty.rs +++ b/compiler/rustc_type_ir/src/sty.rs @@ -3,7 +3,6 @@ use std::cmp::Ordering; use std::{fmt, hash}; -use crate::DebruijnIndex; use crate::FloatTy; use crate::HashStableContext; use crate::IntTy; @@ -11,6 +10,7 @@ use crate::Interner; use crate::TyDecoder; use crate::TyEncoder; use crate::UintTy; +use crate::{DebruijnIndex, DebugWithInfcx, InferCtxtLike, OptWithInfcx}; use self::RegionKind::*; use self::TyKind::*; @@ -36,9 +36,18 @@ pub enum DynKind { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Encodable, Decodable, HashStable_Generic)] pub enum AliasKind { + /// A projection `::AssocType`. + /// Can get normalized away if monomorphic enough. Projection, + /// An associated type in an inherent `impl` Inherent, + /// An opaque type (usually from `impl Trait` in type aliases or function return types) + /// Can only be normalized away in RevealAll mode Opaque, + /// A type alias that actually checks its trait bounds. + /// Currently only used if the type alias references opaque types. + /// Can always be normalized away. + Weak, } /// Defines the kinds of types used by the type system. @@ -66,11 +75,11 @@ pub enum TyKind { /// Algebraic data types (ADT). For example: structures, enumerations and unions. /// /// For example, the type `List` would be represented using the `AdtDef` - /// for `struct List` and the substs `[i32]`. + /// for `struct List` and the args `[i32]`. /// /// Note that generic parameters in fields only get lazily substituted - /// by using something like `adt_def.all_fields().map(|field| field.ty(tcx, substs))`. - Adt(I::AdtDef, I::SubstsRef), + /// by using something like `adt_def.all_fields().map(|field| field.ty(tcx, args))`. + Adt(I::AdtDef, I::GenericArgsRef), /// An unsized FFI type that is opaque to Rust. Written as `extern type T`. Foreign(I::DefId), @@ -102,7 +111,7 @@ pub enum TyKind { /// fn foo() -> i32 { 1 } /// let bar = foo; // bar: fn() -> i32 {foo} /// ``` - FnDef(I::DefId, I::SubstsRef), + FnDef(I::DefId, I::GenericArgsRef), /// A pointer to a function. Written as `fn() -> i32`. /// @@ -122,20 +131,20 @@ pub enum TyKind { /// The anonymous type of a closure. Used to represent the type of `|a| a`. /// - /// Closure substs contain both the - potentially substituted - generic parameters + /// Closure args contain both the - potentially substituted - generic parameters /// of its parent and some synthetic parameters. See the documentation for - /// `ClosureSubsts` for more details. - Closure(I::DefId, I::SubstsRef), + /// `ClosureArgs` for more details. + Closure(I::DefId, I::GenericArgsRef), /// The anonymous type of a generator. Used to represent the type of /// `|a| yield a`. /// - /// For more info about generator substs, visit the documentation for - /// `GeneratorSubsts`. - Generator(I::DefId, I::SubstsRef, I::Movability), + /// For more info about generator args, visit the documentation for + /// `GeneratorArgs`. + Generator(I::DefId, I::GenericArgsRef, I::Movability), /// A type representing the types stored inside a generator. - /// This should only appear as part of the `GeneratorSubsts`. + /// This should only appear as part of the `GeneratorArgs`. /// /// Note that the captured variables for generators are stored separately /// using a tuple in the same way as for closures. @@ -160,7 +169,7 @@ pub enum TyKind { GeneratorWitness(I::BinderListTy), /// A type representing the types stored inside a generator. - /// This should only appear as part of the `GeneratorSubsts`. + /// This should only appear as part of the `GeneratorArgs`. /// /// Unlike upvars, the witness can reference lifetimes from /// inside of the generator itself. To deal with them in @@ -168,7 +177,7 @@ pub enum TyKind { /// lifetimes bound by the witness itself. /// /// This variant is only using when `drop_tracking_mir` is set. - /// This contains the `DefId` and the `SubstsRef` of the generator. + /// This contains the `DefId` and the `GenericArgsRef` of the generator. /// The actual witness types are computed on MIR by the `mir_generator_witnesses` query. /// /// Looking at the following example, the witness for this generator @@ -183,7 +192,7 @@ pub enum TyKind { /// } /// # ; /// ``` - GeneratorWitnessMIR(I::DefId, I::SubstsRef), + GeneratorWitnessMIR(I::DefId, I::GenericArgsRef), /// The never type `!`. Never, @@ -191,7 +200,9 @@ pub enum TyKind { /// A tuple type. For example, `(i32, bool)`. Tuple(I::ListTy), - /// A projection or opaque type. Both of these types + /// A projection, opaque type, weak type alias, or inherent associated type. + /// All of these types are represented as pairs of def-id and args, and can + /// be normalized, so they are grouped conceptually. Alias(AliasKind, I::AliasTy), /// A type parameter; for example, `T` in `fn f(x: T) {}`. @@ -294,7 +305,7 @@ impl Clone for TyKind { Str => Str, Array(t, c) => Array(t.clone(), c.clone()), Slice(t) => Slice(t.clone()), - RawPtr(t) => RawPtr(t.clone()), + RawPtr(p) => RawPtr(p.clone()), Ref(r, t, m) => Ref(r.clone(), t.clone(), m.clone()), FnDef(d, s) => FnDef(d.clone(), s.clone()), FnPtr(s) => FnPtr(s.clone()), @@ -495,38 +506,82 @@ impl hash::Hash for TyKind { } } +impl DebugWithInfcx for TyKind { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> fmt::Result { + match this.data { + Bool => write!(f, "bool"), + Char => write!(f, "char"), + Int(i) => write!(f, "{i:?}"), + Uint(u) => write!(f, "{u:?}"), + Float(float) => write!(f, "{float:?}"), + Adt(d, s) => f.debug_tuple_field2_finish("Adt", d, &this.wrap(s)), + Foreign(d) => f.debug_tuple_field1_finish("Foreign", d), + Str => write!(f, "str"), + Array(t, c) => write!(f, "[{:?}; {:?}]", &this.wrap(t), &this.wrap(c)), + Slice(t) => write!(f, "[{:?}]", &this.wrap(t)), + RawPtr(p) => { + let (ty, mutbl) = I::ty_and_mut_to_parts(p.clone()); + match I::mutability_is_mut(mutbl) { + true => write!(f, "*mut "), + false => write!(f, "*const "), + }?; + write!(f, "{:?}", &this.wrap(ty)) + } + Ref(r, t, m) => match I::mutability_is_mut(m.clone()) { + true => write!(f, "&{:?} mut {:?}", &this.wrap(r), &this.wrap(t)), + false => write!(f, "&{:?} {:?}", &this.wrap(r), &this.wrap(t)), + }, + FnDef(d, s) => f.debug_tuple_field2_finish("FnDef", d, &this.wrap(s)), + FnPtr(s) => write!(f, "{:?}", &this.wrap(s)), + Dynamic(p, r, repr) => match repr { + DynKind::Dyn => write!(f, "dyn {:?} + {:?}", &this.wrap(p), &this.wrap(r)), + DynKind::DynStar => { + write!(f, "dyn* {:?} + {:?}", &this.wrap(p), &this.wrap(r)) + } + }, + Closure(d, s) => f.debug_tuple_field2_finish("Closure", d, &this.wrap(s)), + Generator(d, s, m) => f.debug_tuple_field3_finish("Generator", d, &this.wrap(s), m), + GeneratorWitness(g) => f.debug_tuple_field1_finish("GeneratorWitness", &this.wrap(g)), + GeneratorWitnessMIR(d, s) => { + f.debug_tuple_field2_finish("GeneratorWitnessMIR", d, &this.wrap(s)) + } + Never => write!(f, "!"), + Tuple(t) => { + let mut iter = t.clone().into_iter(); + + write!(f, "(")?; + + match iter.next() { + None => return write!(f, ")"), + Some(ty) => write!(f, "{:?}", &this.wrap(ty))?, + }; + + match iter.next() { + None => return write!(f, ",)"), + Some(ty) => write!(f, "{:?})", &this.wrap(ty))?, + } + + for ty in iter { + write!(f, ", {:?}", &this.wrap(ty))?; + } + write!(f, ")") + } + Alias(i, a) => f.debug_tuple_field2_finish("Alias", i, &this.wrap(a)), + Param(p) => write!(f, "{p:?}"), + Bound(d, b) => crate::debug_bound_var(f, *d, b), + Placeholder(p) => write!(f, "{p:?}"), + Infer(t) => write!(f, "{:?}", this.wrap(t)), + TyKind::Error(_) => write!(f, "{{type error}}"), + } + } +} // This is manually implemented because a derive would require `I: Debug` impl fmt::Debug for TyKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Bool => f.write_str("Bool"), - Char => f.write_str("Char"), - Int(i) => f.debug_tuple_field1_finish("Int", i), - Uint(u) => f.debug_tuple_field1_finish("Uint", u), - Float(float) => f.debug_tuple_field1_finish("Float", float), - Adt(d, s) => f.debug_tuple_field2_finish("Adt", d, s), - Foreign(d) => f.debug_tuple_field1_finish("Foreign", d), - Str => f.write_str("Str"), - Array(t, c) => f.debug_tuple_field2_finish("Array", t, c), - Slice(t) => f.debug_tuple_field1_finish("Slice", t), - RawPtr(t) => f.debug_tuple_field1_finish("RawPtr", t), - Ref(r, t, m) => f.debug_tuple_field3_finish("Ref", r, t, m), - FnDef(d, s) => f.debug_tuple_field2_finish("FnDef", d, s), - FnPtr(s) => f.debug_tuple_field1_finish("FnPtr", s), - Dynamic(p, r, repr) => f.debug_tuple_field3_finish("Dynamic", p, r, repr), - Closure(d, s) => f.debug_tuple_field2_finish("Closure", d, s), - Generator(d, s, m) => f.debug_tuple_field3_finish("Generator", d, s, m), - GeneratorWitness(g) => f.debug_tuple_field1_finish("GeneratorWitness", g), - GeneratorWitnessMIR(d, s) => f.debug_tuple_field2_finish("GeneratorWitnessMIR", d, s), - Never => f.write_str("Never"), - Tuple(t) => f.debug_tuple_field1_finish("Tuple", t), - Alias(i, a) => f.debug_tuple_field2_finish("Alias", i, a), - Param(p) => f.debug_tuple_field1_finish("Param", p), - Bound(d, b) => f.debug_tuple_field2_finish("Bound", d, b), - Placeholder(p) => f.debug_tuple_field1_finish("Placeholder", p), - Infer(t) => f.debug_tuple_field1_finish("Infer", t), - TyKind::Error(e) => f.debug_tuple_field1_finish("Error", e), - } + OptWithInfcx::new_no_ctx(self).fmt(f) } } @@ -535,7 +590,7 @@ impl Encodable for TyKind where I::ErrorGuaranteed: Encodable, I::AdtDef: Encodable, - I::SubstsRef: Encodable, + I::GenericArgsRef: Encodable, I::DefId: Encodable, I::Ty: Encodable, I::Const: Encodable, @@ -569,9 +624,9 @@ where Float(f) => e.emit_enum_variant(disc, |e| { f.encode(e); }), - Adt(adt, substs) => e.emit_enum_variant(disc, |e| { + Adt(adt, args) => e.emit_enum_variant(disc, |e| { adt.encode(e); - substs.encode(e); + args.encode(e); }), Foreign(def_id) => e.emit_enum_variant(disc, |e| { def_id.encode(e); @@ -592,9 +647,9 @@ where t.encode(e); m.encode(e); }), - FnDef(def_id, substs) => e.emit_enum_variant(disc, |e| { + FnDef(def_id, args) => e.emit_enum_variant(disc, |e| { def_id.encode(e); - substs.encode(e); + args.encode(e); }), FnPtr(polyfnsig) => e.emit_enum_variant(disc, |e| { polyfnsig.encode(e); @@ -604,25 +659,25 @@ where r.encode(e); repr.encode(e); }), - Closure(def_id, substs) => e.emit_enum_variant(disc, |e| { + Closure(def_id, args) => e.emit_enum_variant(disc, |e| { def_id.encode(e); - substs.encode(e); + args.encode(e); }), - Generator(def_id, substs, m) => e.emit_enum_variant(disc, |e| { + Generator(def_id, args, m) => e.emit_enum_variant(disc, |e| { def_id.encode(e); - substs.encode(e); + args.encode(e); m.encode(e); }), GeneratorWitness(b) => e.emit_enum_variant(disc, |e| { b.encode(e); }), - GeneratorWitnessMIR(def_id, substs) => e.emit_enum_variant(disc, |e| { + GeneratorWitnessMIR(def_id, args) => e.emit_enum_variant(disc, |e| { def_id.encode(e); - substs.encode(e); + args.encode(e); }), Never => e.emit_enum_variant(disc, |_| {}), - Tuple(substs) => e.emit_enum_variant(disc, |e| { - substs.encode(e); + Tuple(args) => e.emit_enum_variant(disc, |e| { + args.encode(e); }), Alias(k, p) => e.emit_enum_variant(disc, |e| { k.encode(e); @@ -653,7 +708,7 @@ impl> Decodable for TyKind where I::ErrorGuaranteed: Decodable, I::AdtDef: Decodable, - I::SubstsRef: Decodable, + I::GenericArgsRef: Decodable, I::DefId: Decodable, I::Ty: Decodable, I::Const: Decodable, @@ -720,7 +775,7 @@ impl HashStable for TyKind where I::AdtDef: HashStable, I::DefId: HashStable, - I::SubstsRef: HashStable, + I::GenericArgsRef: HashStable, I::Ty: HashStable, I::Const: HashStable, I::TypeAndMut: HashStable, @@ -757,9 +812,9 @@ where Float(f) => { f.hash_stable(__hcx, __hasher); } - Adt(adt, substs) => { + Adt(adt, args) => { adt.hash_stable(__hcx, __hasher); - substs.hash_stable(__hcx, __hasher); + args.hash_stable(__hcx, __hasher); } Foreign(def_id) => { def_id.hash_stable(__hcx, __hasher); @@ -780,9 +835,9 @@ where t.hash_stable(__hcx, __hasher); m.hash_stable(__hcx, __hasher); } - FnDef(def_id, substs) => { + FnDef(def_id, args) => { def_id.hash_stable(__hcx, __hasher); - substs.hash_stable(__hcx, __hasher); + args.hash_stable(__hcx, __hasher); } FnPtr(polyfnsig) => { polyfnsig.hash_stable(__hcx, __hasher); @@ -792,25 +847,25 @@ where r.hash_stable(__hcx, __hasher); repr.hash_stable(__hcx, __hasher); } - Closure(def_id, substs) => { + Closure(def_id, args) => { def_id.hash_stable(__hcx, __hasher); - substs.hash_stable(__hcx, __hasher); + args.hash_stable(__hcx, __hasher); } - Generator(def_id, substs, m) => { + Generator(def_id, args, m) => { def_id.hash_stable(__hcx, __hasher); - substs.hash_stable(__hcx, __hasher); + args.hash_stable(__hcx, __hasher); m.hash_stable(__hcx, __hasher); } GeneratorWitness(b) => { b.hash_stable(__hcx, __hasher); } - GeneratorWitnessMIR(def_id, substs) => { + GeneratorWitnessMIR(def_id, args) => { def_id.hash_stable(__hcx, __hasher); - substs.hash_stable(__hcx, __hasher); + args.hash_stable(__hcx, __hasher); } Never => {} - Tuple(substs) => { - substs.hash_stable(__hcx, __hasher); + Tuple(args) => { + args.hash_stable(__hcx, __hasher); } Alias(k, p) => { k.hash_stable(__hcx, __hasher); @@ -836,6 +891,224 @@ where } } +/// Represents a constant in Rust. +// #[derive(derive_more::From)] +pub enum ConstKind { + /// A const generic parameter. + Param(I::ParamConst), + + /// Infer the value of the const. + Infer(I::InferConst), + + /// Bound const variable, used only when preparing a trait query. + Bound(DebruijnIndex, I::BoundConst), + + /// A placeholder const - universally quantified higher-ranked const. + Placeholder(I::PlaceholderConst), + + /// An unnormalized const item such as an anon const or assoc const or free const item. + /// Right now anything other than anon consts does not actually work properly but this + /// should + Unevaluated(I::AliasConst), + + /// Used to hold computed value. + Value(I::ValueConst), + + /// A placeholder for a const which could not be computed; this is + /// propagated to avoid useless error messages. + Error(I::ErrorGuaranteed), + + /// Unevaluated non-const-item, used by `feature(generic_const_exprs)` to represent + /// const arguments such as `N + 1` or `foo(N)` + Expr(I::ExprConst), +} + +const fn const_kind_discriminant(value: &ConstKind) -> usize { + match value { + ConstKind::Param(_) => 0, + ConstKind::Infer(_) => 1, + ConstKind::Bound(_, _) => 2, + ConstKind::Placeholder(_) => 3, + ConstKind::Unevaluated(_) => 4, + ConstKind::Value(_) => 5, + ConstKind::Error(_) => 6, + ConstKind::Expr(_) => 7, + } +} + +impl hash::Hash for ConstKind { + fn hash(&self, state: &mut H) { + const_kind_discriminant(self).hash(state); + match self { + ConstKind::Param(p) => p.hash(state), + ConstKind::Infer(i) => i.hash(state), + ConstKind::Bound(d, b) => { + d.hash(state); + b.hash(state); + } + ConstKind::Placeholder(p) => p.hash(state), + ConstKind::Unevaluated(u) => u.hash(state), + ConstKind::Value(v) => v.hash(state), + ConstKind::Error(e) => e.hash(state), + ConstKind::Expr(e) => e.hash(state), + } + } +} + +impl HashStable for ConstKind +where + I::ParamConst: HashStable, + I::InferConst: HashStable, + I::BoundConst: HashStable, + I::PlaceholderConst: HashStable, + I::AliasConst: HashStable, + I::ValueConst: HashStable, + I::ErrorGuaranteed: HashStable, + I::ExprConst: HashStable, +{ + fn hash_stable( + &self, + hcx: &mut CTX, + hasher: &mut rustc_data_structures::stable_hasher::StableHasher, + ) { + const_kind_discriminant(self).hash_stable(hcx, hasher); + match self { + ConstKind::Param(p) => p.hash_stable(hcx, hasher), + ConstKind::Infer(i) => i.hash_stable(hcx, hasher), + ConstKind::Bound(d, b) => { + d.hash_stable(hcx, hasher); + b.hash_stable(hcx, hasher); + } + ConstKind::Placeholder(p) => p.hash_stable(hcx, hasher), + ConstKind::Unevaluated(u) => u.hash_stable(hcx, hasher), + ConstKind::Value(v) => v.hash_stable(hcx, hasher), + ConstKind::Error(e) => e.hash_stable(hcx, hasher), + ConstKind::Expr(e) => e.hash_stable(hcx, hasher), + } + } +} + +impl> Decodable for ConstKind +where + I::ParamConst: Decodable, + I::InferConst: Decodable, + I::BoundConst: Decodable, + I::PlaceholderConst: Decodable, + I::AliasConst: Decodable, + I::ValueConst: Decodable, + I::ErrorGuaranteed: Decodable, + I::ExprConst: Decodable, +{ + fn decode(d: &mut D) -> Self { + match Decoder::read_usize(d) { + 0 => ConstKind::Param(Decodable::decode(d)), + 1 => ConstKind::Infer(Decodable::decode(d)), + 2 => ConstKind::Bound(Decodable::decode(d), Decodable::decode(d)), + 3 => ConstKind::Placeholder(Decodable::decode(d)), + 4 => ConstKind::Unevaluated(Decodable::decode(d)), + 5 => ConstKind::Value(Decodable::decode(d)), + 6 => ConstKind::Error(Decodable::decode(d)), + 7 => ConstKind::Expr(Decodable::decode(d)), + _ => panic!( + "{}", + format!( + "invalid enum variant tag while decoding `{}`, expected 0..{}", + "ConstKind", 8, + ) + ), + } + } +} + +impl> Encodable for ConstKind +where + I::ParamConst: Encodable, + I::InferConst: Encodable, + I::BoundConst: Encodable, + I::PlaceholderConst: Encodable, + I::AliasConst: Encodable, + I::ValueConst: Encodable, + I::ErrorGuaranteed: Encodable, + I::ExprConst: Encodable, +{ + fn encode(&self, e: &mut E) { + let disc = const_kind_discriminant(self); + match self { + ConstKind::Param(p) => e.emit_enum_variant(disc, |e| p.encode(e)), + ConstKind::Infer(i) => e.emit_enum_variant(disc, |e| i.encode(e)), + ConstKind::Bound(d, b) => e.emit_enum_variant(disc, |e| { + d.encode(e); + b.encode(e); + }), + ConstKind::Placeholder(p) => e.emit_enum_variant(disc, |e| p.encode(e)), + ConstKind::Unevaluated(u) => e.emit_enum_variant(disc, |e| u.encode(e)), + ConstKind::Value(v) => e.emit_enum_variant(disc, |e| v.encode(e)), + ConstKind::Error(er) => e.emit_enum_variant(disc, |e| er.encode(e)), + ConstKind::Expr(ex) => e.emit_enum_variant(disc, |e| ex.encode(e)), + } + } +} + +impl PartialOrd for ConstKind { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ConstKind { + fn cmp(&self, other: &Self) -> Ordering { + const_kind_discriminant(self) + .cmp(&const_kind_discriminant(other)) + .then_with(|| match (self, other) { + (ConstKind::Param(p1), ConstKind::Param(p2)) => p1.cmp(p2), + (ConstKind::Infer(i1), ConstKind::Infer(i2)) => i1.cmp(i2), + (ConstKind::Bound(d1, b1), ConstKind::Bound(d2, b2)) => d1.cmp(d2).then_with(|| b1.cmp(b2)), + (ConstKind::Placeholder(p1), ConstKind::Placeholder(p2)) => p1.cmp(p2), + (ConstKind::Unevaluated(u1), ConstKind::Unevaluated(u2)) => u1.cmp(u2), + (ConstKind::Value(v1), ConstKind::Value(v2)) => v1.cmp(v2), + (ConstKind::Error(e1), ConstKind::Error(e2)) => e1.cmp(e2), + (ConstKind::Expr(e1), ConstKind::Expr(e2)) => e1.cmp(e2), + _ => { + debug_assert!(false, "This branch must be unreachable, maybe the match is missing an arm? self = {self:?}, other = {other:?}"); + Ordering::Equal + } + }) + } +} + +impl PartialEq for ConstKind { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Param(l0), Self::Param(r0)) => l0 == r0, + (Self::Infer(l0), Self::Infer(r0)) => l0 == r0, + (Self::Bound(l0, l1), Self::Bound(r0, r1)) => l0 == r0 && l1 == r1, + (Self::Placeholder(l0), Self::Placeholder(r0)) => l0 == r0, + (Self::Unevaluated(l0), Self::Unevaluated(r0)) => l0 == r0, + (Self::Value(l0), Self::Value(r0)) => l0 == r0, + (Self::Error(l0), Self::Error(r0)) => l0 == r0, + (Self::Expr(l0), Self::Expr(r0)) => l0 == r0, + _ => false, + } + } +} + +impl Eq for ConstKind {} + +impl Clone for ConstKind { + fn clone(&self) -> Self { + match self { + Self::Param(arg0) => Self::Param(arg0.clone()), + Self::Infer(arg0) => Self::Infer(arg0.clone()), + Self::Bound(arg0, arg1) => Self::Bound(arg0.clone(), arg1.clone()), + Self::Placeholder(arg0) => Self::Placeholder(arg0.clone()), + Self::Unevaluated(arg0) => Self::Unevaluated(arg0.clone()), + Self::Value(arg0) => Self::Value(arg0.clone()), + Self::Error(arg0) => Self::Error(arg0.clone()), + Self::Expr(arg0) => Self::Expr(arg0.clone()), + } + } +} + /// Representation of regions. Note that the NLL checker uses a distinct /// representation of regions. For this reason, it internally replaces all the /// regions with inference variables -- the index of the variable is then used @@ -897,7 +1170,7 @@ where /// These are regions that are stored behind a binder and must be substituted /// with some concrete region before being used. There are two kind of /// bound regions: early-bound, which are bound in an item's `Generics`, -/// and are substituted by an `InternalSubsts`, and late-bound, which are part of +/// and are substituted by an `GenericArgs`, and late-bound, which are part of /// higher-ranked types (e.g., `for<'a> fn(&'a ())`), and are substituted by /// the likes of `liberate_late_bound_regions`. The distinction exists /// because higher-ranked lifetimes aren't supported in all places. See [1][2]. @@ -1098,21 +1371,23 @@ impl hash::Hash for RegionKind { } } -// This is manually implemented because a derive would require `I: Debug` -impl fmt::Debug for RegionKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { +impl DebugWithInfcx for RegionKind { + fn fmt>( + this: OptWithInfcx<'_, I, InfCtx, &Self>, + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + match this.data { ReEarlyBound(data) => write!(f, "ReEarlyBound({data:?})"), ReLateBound(binder_id, bound_region) => { write!(f, "ReLateBound({binder_id:?}, {bound_region:?})") } - ReFree(fr) => fr.fmt(f), + ReFree(fr) => write!(f, "{fr:?}"), ReStatic => f.write_str("ReStatic"), - ReVar(vid) => vid.fmt(f), + ReVar(vid) => write!(f, "{:?}", &this.wrap(vid)), RePlaceholder(placeholder) => write!(f, "RePlaceholder({placeholder:?})"), @@ -1122,6 +1397,11 @@ impl fmt::Debug for RegionKind { } } } +impl fmt::Debug for RegionKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + OptWithInfcx::new_no_ctx(self).fmt(f) + } +} // This is manually implemented because a derive would require `I: Encodable` impl Encodable for RegionKind diff --git a/config.example.toml b/config.example.toml index d0eaa9fd7ffac..7998c3a44149a 100644 --- a/config.example.toml +++ b/config.example.toml @@ -94,7 +94,7 @@ changelog-seen = 2 # the same format as above, but since these targets are experimental, they are # not built by default and the experimental Rust compilation targets that depend # on them will not work unless the user opts in to building them. -#experimental-targets = "AVR;M68k" +#experimental-targets = "AVR;M68k;CSKY" # Cap the number of parallel linker invocations when compiling LLVM. # This can be useful when building LLVM with debug info, which significantly @@ -250,6 +250,13 @@ changelog-seen = 2 # target when running tests, otherwise this can be omitted. #nodejs = "node" +# The npm executable to use. Note that this is used for rustdoc-gui tests, +# otherwise this can be omitted. +# +# Under Windows this should be `npm.cmd` or path to it (verified on nodejs v18.06), or +# error will be emitted. +#npm = "npm" + # Python interpreter to use for various tasks throughout the build, notably # rustdoc tests, the lldb python interpreter, and some dist bits and pieces. # @@ -393,10 +400,20 @@ changelog-seen = 2 # ============================================================================= [rust] -# Whether or not to optimize the compiler and standard library. +# Whether or not to optimize when compiling the compiler and standard library, +# and what level of optimization to use. # WARNING: Building with optimize = false is NOT SUPPORTED. Due to bootstrapping, # building without optimizations takes much longer than optimizing. Further, some platforms # fail to build without this optimization (c.f. #65352). +# The valid options are: +# true - Enable optimizations. +# false - Disable optimizations. +# 0 - Disable optimizations. +# 1 - Basic optimizations. +# 2 - Some optimizations. +# 3 - All optimizations. +# "s" - Optimize for binary size. +# "z" - Optimize for binary size, but also turn off loop vectorization. #optimize = true # Indicates that the build should be configured for debugging Rust. A @@ -673,10 +690,6 @@ changelog-seen = 2 # Build compiler with the optimization enabled and -Zvalidate-mir, currently only for `std` #validate-mir-opts = 3 -# Copy the linker, DLLs, and various libraries from MinGW into the rustc toolchain. -# Only applies when the host or target is pc-windows-gnu. -#include-mingw-linker = true - # ============================================================================= # Options for specific targets # @@ -745,12 +758,14 @@ changelog-seen = 2 # This option will override the same option under [build] section. #sanitizers = build.sanitizers (bool) -# Build the profiler runtime for this target(required when compiling with options that depend -# on this runtime, such as `-C profile-generate` or `-C instrument-coverage`). +# When true, build the profiler runtime for this target (required when compiling +# with options that depend on this runtime, such as `-C profile-generate` or +# `-C instrument-coverage`). This may also be given a path to an existing build +# of the profiling runtime library from LLVM's compiler-rt. # This option will override the same option under [build] section. #profiler = build.profiler (bool) -# This option supports enable `rpath` in each target independently, +# This option supports enable `rpath` in each target independently, # and will override the same option under [rust] section. It only works on Unix platforms #rpath = rust.rpath (bool) @@ -769,9 +784,10 @@ changelog-seen = 2 # The full path to the musl libdir. #musl-libdir = musl-root/lib -# The root location of the `wasm32-wasi` sysroot. Only used for the -# `wasm32-wasi` target. If you are building wasm32-wasi target, make sure to -# create a `[target.wasm32-wasi]` section and move this field there. +# The root location of the `wasm32-unknown-wasi` and 'wasm64-unknown-wasi' sysroot. Only used for the +# `wasi` target os. If you are building wasm32-wasi/wasm64-wasi target, make sure to +# create a `[target.wasm32-unknown-wasi]`, `[target.wasm32-wasmer-wasi]` or '[target.wasm64-wasmer-wasi]' +# section and move this field there. #wasi-root = (path) # Used in testing for configuring where the QEMU images are located, you @@ -827,3 +843,7 @@ changelog-seen = 2 # # Available options: fast, balanced, best #compression-profile = "fast" + +# Copy the linker, DLLs, and various libraries from MinGW into the rustc toolchain. +# Only applies when the host or target is pc-windows-gnu. +#include-mingw-linker = true diff --git a/config.toml b/config.toml new file mode 100644 index 0000000000000..d23e854db6197 --- /dev/null +++ b/config.toml @@ -0,0 +1,20 @@ +changelog-seen = 2 + +[build] +target = ["wasm32-wasmer-wasi", "wasm64-wasmer-wasi"] +extended = true +tools = [ "clippy", "rustfmt" ] +configure-args = [] + +[rust] +lld = true +llvm-tools = true + +[llvm] +download-ci-llvm = false + +[target.wasm32-wasmer-wasi] +wasi-root = "../wasix-libc/sysroot32" + +[target.wasm64-wasmer-wasi] +wasi-root = "../wasix-libc/sysroot64" diff --git a/library/alloc/Cargo.toml b/library/alloc/Cargo.toml index 95c07abf73106..e5f828c4c0b34 100644 --- a/library/alloc/Cargo.toml +++ b/library/alloc/Cargo.toml @@ -35,3 +35,4 @@ compiler-builtins-mem = ['compiler_builtins/mem'] compiler-builtins-c = ["compiler_builtins/c"] compiler-builtins-no-asm = ["compiler_builtins/no-asm"] compiler-builtins-mangled-names = ["compiler_builtins/mangled-names"] +compiler-builtins-weak-intrinsics = ["compiler_builtins/weak-intrinsics"] diff --git a/library/alloc/benches/btree/map.rs b/library/alloc/benches/btree/map.rs index ec1b0a8eba039..7d236647750d0 100644 --- a/library/alloc/benches/btree/map.rs +++ b/library/alloc/benches/btree/map.rs @@ -385,7 +385,7 @@ pub fn clone_slim_100_and_clear(b: &mut Bencher) { #[bench] pub fn clone_slim_100_and_drain_all(b: &mut Bencher) { let src = slim_map(100); - b.iter(|| src.clone().drain_filter(|_, _| true).count()) + b.iter(|| src.clone().extract_if(|_, _| true).count()) } #[bench] @@ -393,7 +393,7 @@ pub fn clone_slim_100_and_drain_half(b: &mut Bencher) { let src = slim_map(100); b.iter(|| { let mut map = src.clone(); - assert_eq!(map.drain_filter(|i, _| i % 2 == 0).count(), 100 / 2); + assert_eq!(map.extract_if(|i, _| i % 2 == 0).count(), 100 / 2); assert_eq!(map.len(), 100 / 2); }) } @@ -456,7 +456,7 @@ pub fn clone_slim_10k_and_clear(b: &mut Bencher) { #[bench] pub fn clone_slim_10k_and_drain_all(b: &mut Bencher) { let src = slim_map(10_000); - b.iter(|| src.clone().drain_filter(|_, _| true).count()) + b.iter(|| src.clone().extract_if(|_, _| true).count()) } #[bench] @@ -464,7 +464,7 @@ pub fn clone_slim_10k_and_drain_half(b: &mut Bencher) { let src = slim_map(10_000); b.iter(|| { let mut map = src.clone(); - assert_eq!(map.drain_filter(|i, _| i % 2 == 0).count(), 10_000 / 2); + assert_eq!(map.extract_if(|i, _| i % 2 == 0).count(), 10_000 / 2); assert_eq!(map.len(), 10_000 / 2); }) } @@ -527,7 +527,7 @@ pub fn clone_fat_val_100_and_clear(b: &mut Bencher) { #[bench] pub fn clone_fat_val_100_and_drain_all(b: &mut Bencher) { let src = fat_val_map(100); - b.iter(|| src.clone().drain_filter(|_, _| true).count()) + b.iter(|| src.clone().extract_if(|_, _| true).count()) } #[bench] @@ -535,7 +535,7 @@ pub fn clone_fat_val_100_and_drain_half(b: &mut Bencher) { let src = fat_val_map(100); b.iter(|| { let mut map = src.clone(); - assert_eq!(map.drain_filter(|i, _| i % 2 == 0).count(), 100 / 2); + assert_eq!(map.extract_if(|i, _| i % 2 == 0).count(), 100 / 2); assert_eq!(map.len(), 100 / 2); }) } diff --git a/library/alloc/benches/btree/set.rs b/library/alloc/benches/btree/set.rs index 3f4b0e0f14af7..09d72c7206469 100644 --- a/library/alloc/benches/btree/set.rs +++ b/library/alloc/benches/btree/set.rs @@ -69,7 +69,7 @@ pub fn clone_100_and_clear(b: &mut Bencher) { #[bench] pub fn clone_100_and_drain_all(b: &mut Bencher) { let src = slim_set(100); - b.iter(|| src.clone().drain_filter(|_| true).count()) + b.iter(|| src.clone().extract_if(|_| true).count()) } #[bench] @@ -77,7 +77,7 @@ pub fn clone_100_and_drain_half(b: &mut Bencher) { let src = slim_set(100); b.iter(|| { let mut set = src.clone(); - assert_eq!(set.drain_filter(|i| i % 2 == 0).count(), 100 / 2); + assert_eq!(set.extract_if(|i| i % 2 == 0).count(), 100 / 2); assert_eq!(set.len(), 100 / 2); }) } @@ -140,7 +140,7 @@ pub fn clone_10k_and_clear(b: &mut Bencher) { #[bench] pub fn clone_10k_and_drain_all(b: &mut Bencher) { let src = slim_set(10_000); - b.iter(|| src.clone().drain_filter(|_| true).count()) + b.iter(|| src.clone().extract_if(|_| true).count()) } #[bench] @@ -148,7 +148,7 @@ pub fn clone_10k_and_drain_half(b: &mut Bencher) { let src = slim_set(10_000); b.iter(|| { let mut set = src.clone(); - assert_eq!(set.drain_filter(|i| i % 2 == 0).count(), 10_000 / 2); + assert_eq!(set.extract_if(|i| i % 2 == 0).count(), 10_000 / 2); assert_eq!(set.len(), 10_000 / 2); }) } diff --git a/library/alloc/benches/lib.rs b/library/alloc/benches/lib.rs index b25d63d835b54..638f343fb244b 100644 --- a/library/alloc/benches/lib.rs +++ b/library/alloc/benches/lib.rs @@ -1,7 +1,7 @@ // Disabling on android for the time being // See https://github.com/rust-lang/rust/issues/73535#event-3477699747 #![cfg(not(target_os = "android"))] -#![feature(btree_drain_filter)] +#![feature(btree_extract_if)] #![feature(iter_next_chunk)] #![feature(repr_simd)] #![feature(slice_partition_dedup)] diff --git a/library/alloc/src/alloc.rs b/library/alloc/src/alloc.rs index 6f2ba957bcda0..5205ed9fb50a9 100644 --- a/library/alloc/src/alloc.rs +++ b/library/alloc/src/alloc.rs @@ -4,9 +4,7 @@ #[cfg(not(test))] use core::intrinsics; -use core::intrinsics::{min_align_of_val, size_of_val}; -use core::ptr::Unique; #[cfg(not(test))] use core::ptr::{self, NonNull}; @@ -37,6 +35,8 @@ extern "Rust" { #[rustc_allocator_zeroed] #[rustc_nounwind] fn __rust_alloc_zeroed(size: usize, align: usize) -> *mut u8; + + static __rust_no_alloc_shim_is_unstable: u8; } /// The global memory allocator. @@ -90,7 +90,13 @@ pub use std::alloc::Global; #[must_use = "losing the pointer will leak memory"] #[inline] pub unsafe fn alloc(layout: Layout) -> *mut u8 { - unsafe { __rust_alloc(layout.size(), layout.align()) } + unsafe { + // Make sure we don't accidentally allow omitting the allocator shim in + // stable code until it is actually stabilized. + core::ptr::read_volatile(&__rust_no_alloc_shim_is_unstable); + + __rust_alloc(layout.size(), layout.align()) + } } /// Deallocate memory with the global allocator. @@ -327,22 +333,6 @@ unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 { } } -#[cfg_attr(not(test), lang = "box_free")] -#[inline] -// This signature has to be the same as `Box`, otherwise an ICE will happen. -// When an additional parameter to `Box` is added (like `A: Allocator`), this has to be added here as -// well. -// For example if `Box` is changed to `struct Box(Unique, A)`, -// this function has to be changed to `fn box_free(Unique, A)` as well. -pub(crate) unsafe fn box_free(ptr: Unique, alloc: A) { - unsafe { - let size = size_of_val(ptr.as_ref()); - let align = min_align_of_val(ptr.as_ref()); - let layout = Layout::from_size_align_unchecked(size, align); - alloc.deallocate(From::from(ptr.cast()), layout) - } -} - // # Allocation error handler #[cfg(not(no_global_oom_handling))] @@ -402,7 +392,6 @@ pub mod __alloc_error_handler { static __rust_alloc_error_handler_should_panic: u8; } - #[allow(unused_unsafe)] if unsafe { __rust_alloc_error_handler_should_panic != 0 } { panic!("memory allocation of {size} bytes failed") } else { diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 1768687e8cd02..96b93830f9600 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -157,12 +157,12 @@ use core::hash::{Hash, Hasher}; use core::iter::FusedIterator; use core::marker::Tuple; use core::marker::Unsize; -use core::mem; +use core::mem::{self, SizedTypeProperties}; use core::ops::{ CoerceUnsized, Deref, DerefMut, DispatchFromDyn, Generator, GeneratorState, Receiver, }; use core::pin::Pin; -use core::ptr::{self, Unique}; +use core::ptr::{self, NonNull, Unique}; use core::task::{Context, Poll}; #[cfg(not(no_global_oom_handling))] @@ -479,8 +479,12 @@ impl Box { where A: Allocator, { - let layout = Layout::new::>(); - let ptr = alloc.allocate(layout)?.cast(); + let ptr = if T::IS_ZST { + NonNull::dangling() + } else { + let layout = Layout::new::>(); + alloc.allocate(layout)?.cast() + }; unsafe { Ok(Box::from_raw_in(ptr.as_ptr(), alloc)) } } @@ -549,8 +553,12 @@ impl Box { where A: Allocator, { - let layout = Layout::new::>(); - let ptr = alloc.allocate_zeroed(layout)?.cast(); + let ptr = if T::IS_ZST { + NonNull::dangling() + } else { + let layout = Layout::new::>(); + alloc.allocate_zeroed(layout)?.cast() + }; unsafe { Ok(Box::from_raw_in(ptr.as_ptr(), alloc)) } } @@ -675,14 +683,16 @@ impl Box<[T]> { #[unstable(feature = "allocator_api", issue = "32838")] #[inline] pub fn try_new_uninit_slice(len: usize) -> Result]>, AllocError> { - unsafe { + let ptr = if T::IS_ZST || len == 0 { + NonNull::dangling() + } else { let layout = match Layout::array::>(len) { Ok(l) => l, Err(_) => return Err(AllocError), }; - let ptr = Global.allocate(layout)?; - Ok(RawVec::from_raw_parts_in(ptr.as_mut_ptr() as *mut _, len, Global).into_box(len)) - } + Global.allocate(layout)?.cast() + }; + unsafe { Ok(RawVec::from_raw_parts_in(ptr.as_ptr(), len, Global).into_box(len)) } } /// Constructs a new boxed slice with uninitialized contents, with the memory @@ -707,14 +717,16 @@ impl Box<[T]> { #[unstable(feature = "allocator_api", issue = "32838")] #[inline] pub fn try_new_zeroed_slice(len: usize) -> Result]>, AllocError> { - unsafe { + let ptr = if T::IS_ZST || len == 0 { + NonNull::dangling() + } else { let layout = match Layout::array::>(len) { Ok(l) => l, Err(_) => return Err(AllocError), }; - let ptr = Global.allocate_zeroed(layout)?; - Ok(RawVec::from_raw_parts_in(ptr.as_mut_ptr() as *mut _, len, Global).into_box(len)) - } + Global.allocate_zeroed(layout)?.cast() + }; + unsafe { Ok(RawVec::from_raw_parts_in(ptr.as_ptr(), len, Global).into_box(len)) } } } @@ -1211,8 +1223,18 @@ impl Box { #[stable(feature = "rust1", since = "1.0.0")] unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box { + #[inline] fn drop(&mut self) { - // FIXME: Do nothing, drop is currently performed by compiler. + // the T in the Box is dropped by the compiler before the destructor is run + + let ptr = self.0; + + unsafe { + let layout = Layout::for_value_raw(ptr.as_ptr()); + if layout.size() != 0 { + self.1.deallocate(From::from(ptr.cast()), layout); + } + } } } @@ -2161,7 +2183,7 @@ impl dyn Error + Send { let err: Box = self; ::downcast(err).map_err(|s| unsafe { // Reapply the `Send` marker. - mem::transmute::, Box>(s) + Box::from_raw(Box::into_raw(s) as *mut (dyn Error + Send)) }) } } @@ -2175,7 +2197,7 @@ impl dyn Error + Send + Sync { let err: Box = self; ::downcast(err).map_err(|s| unsafe { // Reapply the `Send + Sync` marker. - mem::transmute::, Box>(s) + Box::from_raw(Box::into_raw(s) as *mut (dyn Error + Send + Sync)) }) } } diff --git a/library/alloc/src/collections/binary_heap/mod.rs b/library/alloc/src/collections/binary_heap/mod.rs index 2c089bb314900..66573b90db963 100644 --- a/library/alloc/src/collections/binary_heap/mod.rs +++ b/library/alloc/src/collections/binary_heap/mod.rs @@ -143,6 +143,7 @@ #![allow(missing_docs)] #![stable(feature = "rust1", since = "1.0.0")] +use core::alloc::Allocator; use core::fmt; use core::iter::{FusedIterator, InPlaceIterable, SourceIter, TrustedLen}; use core::mem::{self, swap, ManuallyDrop}; @@ -150,6 +151,7 @@ use core::num::NonZeroUsize; use core::ops::{Deref, DerefMut}; use core::ptr; +use crate::alloc::Global; use crate::collections::TryReserveError; use crate::slice; use crate::vec::{self, AsVecIntoIter, Vec}; @@ -271,8 +273,11 @@ mod tests; /// [peek\_mut]: BinaryHeap::peek_mut #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "BinaryHeap")] -pub struct BinaryHeap { - data: Vec, +pub struct BinaryHeap< + T, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + data: Vec, } /// Structure wrapping a mutable reference to the greatest item on a @@ -283,22 +288,26 @@ pub struct BinaryHeap { /// /// [`peek_mut`]: BinaryHeap::peek_mut #[stable(feature = "binary_heap_peek_mut", since = "1.12.0")] -pub struct PeekMut<'a, T: 'a + Ord> { - heap: &'a mut BinaryHeap, +pub struct PeekMut< + 'a, + T: 'a + Ord, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + heap: &'a mut BinaryHeap, // If a set_len + sift_down are required, this is Some. If a &mut T has not // yet been exposed to peek_mut()'s caller, it's None. original_len: Option, } #[stable(feature = "collection_debug", since = "1.17.0")] -impl fmt::Debug for PeekMut<'_, T> { +impl fmt::Debug for PeekMut<'_, T, A> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("PeekMut").field(&self.heap.data[0]).finish() } } #[stable(feature = "binary_heap_peek_mut", since = "1.12.0")] -impl Drop for PeekMut<'_, T> { +impl Drop for PeekMut<'_, T, A> { fn drop(&mut self) { if let Some(original_len) = self.original_len { // SAFETY: That's how many elements were in the Vec at the time of @@ -315,7 +324,7 @@ impl Drop for PeekMut<'_, T> { } #[stable(feature = "binary_heap_peek_mut", since = "1.12.0")] -impl Deref for PeekMut<'_, T> { +impl Deref for PeekMut<'_, T, A> { type Target = T; fn deref(&self) -> &T { debug_assert!(!self.heap.is_empty()); @@ -325,7 +334,7 @@ impl Deref for PeekMut<'_, T> { } #[stable(feature = "binary_heap_peek_mut", since = "1.12.0")] -impl DerefMut for PeekMut<'_, T> { +impl DerefMut for PeekMut<'_, T, A> { fn deref_mut(&mut self) -> &mut T { debug_assert!(!self.heap.is_empty()); @@ -353,10 +362,10 @@ impl DerefMut for PeekMut<'_, T> { } } -impl<'a, T: Ord> PeekMut<'a, T> { +impl<'a, T: Ord, A: Allocator> PeekMut<'a, T, A> { /// Removes the peeked value from the heap and returns it. #[stable(feature = "binary_heap_peek_mut_pop", since = "1.18.0")] - pub fn pop(mut this: PeekMut<'a, T>) -> T { + pub fn pop(mut this: PeekMut<'a, T, A>) -> T { if let Some(original_len) = this.original_len.take() { // SAFETY: This is how many elements were in the Vec at the time of // the BinaryHeap::peek_mut call. @@ -371,7 +380,7 @@ impl<'a, T: Ord> PeekMut<'a, T> { } #[stable(feature = "rust1", since = "1.0.0")] -impl Clone for BinaryHeap { +impl Clone for BinaryHeap { fn clone(&self) -> Self { BinaryHeap { data: self.data.clone() } } @@ -391,18 +400,22 @@ impl Default for BinaryHeap { } #[stable(feature = "binaryheap_debug", since = "1.4.0")] -impl fmt::Debug for BinaryHeap { +impl fmt::Debug for BinaryHeap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_list().entries(self.iter()).finish() } } -struct RebuildOnDrop<'a, T: Ord> { - heap: &'a mut BinaryHeap, +struct RebuildOnDrop< + 'a, + T: Ord, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + heap: &'a mut BinaryHeap, rebuild_from: usize, } -impl<'a, T: Ord> Drop for RebuildOnDrop<'a, T> { +impl Drop for RebuildOnDrop<'_, T, A> { fn drop(&mut self) { self.heap.rebuild_tail(self.rebuild_from); } @@ -446,6 +459,52 @@ impl BinaryHeap { pub fn with_capacity(capacity: usize) -> BinaryHeap { BinaryHeap { data: Vec::with_capacity(capacity) } } +} + +impl BinaryHeap { + /// Creates an empty `BinaryHeap` as a max-heap, using `A` as allocator. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(allocator_api)] + /// + /// use std::alloc::System; + /// use std::collections::BinaryHeap; + /// let mut heap = BinaryHeap::new_in(System); + /// heap.push(4); + /// ``` + #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] + pub fn new_in(alloc: A) -> BinaryHeap { + BinaryHeap { data: Vec::new_in(alloc) } + } + + /// Creates an empty `BinaryHeap` with at least the specified capacity, using `A` as allocator. + /// + /// The binary heap will be able to hold at least `capacity` elements without + /// reallocating. This method is allowed to allocate for more elements than + /// `capacity`. If `capacity` is 0, the binary heap will not allocate. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(allocator_api)] + /// + /// use std::alloc::System; + /// use std::collections::BinaryHeap; + /// let mut heap = BinaryHeap::with_capacity_in(10, System); + /// heap.push(4); + /// ``` + #[unstable(feature = "allocator_api", issue = "32838")] + #[must_use] + pub fn with_capacity_in(capacity: usize, alloc: A) -> BinaryHeap { + BinaryHeap { data: Vec::with_capacity_in(capacity, alloc) } + } /// Returns a mutable reference to the greatest item in the binary heap, or /// `None` if it is empty. @@ -478,7 +537,7 @@ impl BinaryHeap { /// If the item is modified then the worst case time complexity is *O*(log(*n*)), /// otherwise it's *O*(1). #[stable(feature = "binary_heap_peek_mut", since = "1.12.0")] - pub fn peek_mut(&mut self) -> Option> { + pub fn peek_mut(&mut self) -> Option> { if self.is_empty() { None } else { Some(PeekMut { heap: self, original_len: None }) } } @@ -573,7 +632,7 @@ impl BinaryHeap { /// ``` #[must_use = "`self` will be dropped if the result is not used"] #[stable(feature = "binary_heap_extras_15", since = "1.5.0")] - pub fn into_sorted_vec(mut self) -> Vec { + pub fn into_sorted_vec(mut self) -> Vec { let mut end = self.len(); while end > 1 { end -= 1; @@ -831,7 +890,7 @@ impl BinaryHeap { /// ``` #[inline] #[unstable(feature = "binary_heap_drain_sorted", issue = "59278")] - pub fn drain_sorted(&mut self) -> DrainSorted<'_, T> { + pub fn drain_sorted(&mut self) -> DrainSorted<'_, T, A> { DrainSorted { inner: self } } @@ -874,7 +933,7 @@ impl BinaryHeap { } } -impl BinaryHeap { +impl BinaryHeap { /// Returns an iterator visiting all values in the underlying vector, in /// arbitrary order. /// @@ -911,7 +970,7 @@ impl BinaryHeap { /// assert_eq!(heap.into_iter_sorted().take(2).collect::>(), [5, 4]); /// ``` #[unstable(feature = "binary_heap_into_iter_sorted", issue = "59278")] - pub fn into_iter_sorted(self) -> IntoIterSorted { + pub fn into_iter_sorted(self) -> IntoIterSorted { IntoIterSorted { inner: self } } @@ -1178,10 +1237,17 @@ impl BinaryHeap { /// ``` #[must_use = "`self` will be dropped if the result is not used"] #[stable(feature = "binary_heap_extras_15", since = "1.5.0")] - pub fn into_vec(self) -> Vec { + pub fn into_vec(self) -> Vec { self.into() } + /// Returns a reference to the underlying allocator. + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn allocator(&self) -> &A { + self.data.allocator() + } + /// Returns the length of the binary heap. /// /// # Examples @@ -1249,7 +1315,7 @@ impl BinaryHeap { /// ``` #[inline] #[stable(feature = "drain", since = "1.6.0")] - pub fn drain(&mut self) -> Drain<'_, T> { + pub fn drain(&mut self) -> Drain<'_, T, A> { Drain { iter: self.data.drain(..) } } @@ -1419,19 +1485,30 @@ impl FusedIterator for Iter<'_, T> {} /// [`into_iter`]: BinaryHeap::into_iter #[stable(feature = "rust1", since = "1.0.0")] #[derive(Clone)] -pub struct IntoIter { - iter: vec::IntoIter, +pub struct IntoIter< + T, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + iter: vec::IntoIter, +} + +impl IntoIter { + /// Returns a reference to the underlying allocator. + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn allocator(&self) -> &A { + self.iter.allocator() + } } #[stable(feature = "collection_debug", since = "1.17.0")] -impl fmt::Debug for IntoIter { +impl fmt::Debug for IntoIter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("IntoIter").field(&self.iter.as_slice()).finish() } } #[stable(feature = "rust1", since = "1.0.0")] -impl Iterator for IntoIter { +impl Iterator for IntoIter { type Item = T; #[inline] @@ -1446,7 +1523,7 @@ impl Iterator for IntoIter { } #[stable(feature = "rust1", since = "1.0.0")] -impl DoubleEndedIterator for IntoIter { +impl DoubleEndedIterator for IntoIter { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back() @@ -1454,14 +1531,14 @@ impl DoubleEndedIterator for IntoIter { } #[stable(feature = "rust1", since = "1.0.0")] -impl ExactSizeIterator for IntoIter { +impl ExactSizeIterator for IntoIter { fn is_empty(&self) -> bool { self.iter.is_empty() } } #[stable(feature = "fused", since = "1.26.0")] -impl FusedIterator for IntoIter {} +impl FusedIterator for IntoIter {} #[stable(feature = "default_iters", since = "1.70.0")] impl Default for IntoIter { @@ -1481,8 +1558,8 @@ impl Default for IntoIter { // also refer to the vec::in_place_collect module documentation to get an overview #[unstable(issue = "none", feature = "inplace_iteration")] #[doc(hidden)] -unsafe impl SourceIter for IntoIter { - type Source = IntoIter; +unsafe impl SourceIter for IntoIter { + type Source = IntoIter; #[inline] unsafe fn as_inner(&mut self) -> &mut Self::Source { @@ -1492,7 +1569,7 @@ unsafe impl SourceIter for IntoIter { #[unstable(issue = "none", feature = "inplace_iteration")] #[doc(hidden)] -unsafe impl InPlaceIterable for IntoIter {} +unsafe impl InPlaceIterable for IntoIter {} unsafe impl AsVecIntoIter for IntoIter { type Item = I; @@ -1505,12 +1582,23 @@ unsafe impl AsVecIntoIter for IntoIter { #[must_use = "iterators are lazy and do nothing unless consumed"] #[unstable(feature = "binary_heap_into_iter_sorted", issue = "59278")] #[derive(Clone, Debug)] -pub struct IntoIterSorted { - inner: BinaryHeap, +pub struct IntoIterSorted< + T, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + inner: BinaryHeap, +} + +impl IntoIterSorted { + /// Returns a reference to the underlying allocator. + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn allocator(&self) -> &A { + self.inner.allocator() + } } #[unstable(feature = "binary_heap_into_iter_sorted", issue = "59278")] -impl Iterator for IntoIterSorted { +impl Iterator for IntoIterSorted { type Item = T; #[inline] @@ -1526,13 +1614,13 @@ impl Iterator for IntoIterSorted { } #[unstable(feature = "binary_heap_into_iter_sorted", issue = "59278")] -impl ExactSizeIterator for IntoIterSorted {} +impl ExactSizeIterator for IntoIterSorted {} #[unstable(feature = "binary_heap_into_iter_sorted", issue = "59278")] -impl FusedIterator for IntoIterSorted {} +impl FusedIterator for IntoIterSorted {} #[unstable(feature = "trusted_len", issue = "37572")] -unsafe impl TrustedLen for IntoIterSorted {} +unsafe impl TrustedLen for IntoIterSorted {} /// A draining iterator over the elements of a `BinaryHeap`. /// @@ -1542,12 +1630,24 @@ unsafe impl TrustedLen for IntoIterSorted {} /// [`drain`]: BinaryHeap::drain #[stable(feature = "drain", since = "1.6.0")] #[derive(Debug)] -pub struct Drain<'a, T: 'a> { - iter: vec::Drain<'a, T>, +pub struct Drain< + 'a, + T: 'a, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + iter: vec::Drain<'a, T, A>, +} + +impl Drain<'_, T, A> { + /// Returns a reference to the underlying allocator. + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn allocator(&self) -> &A { + self.iter.allocator() + } } #[stable(feature = "drain", since = "1.6.0")] -impl Iterator for Drain<'_, T> { +impl Iterator for Drain<'_, T, A> { type Item = T; #[inline] @@ -1562,7 +1662,7 @@ impl Iterator for Drain<'_, T> { } #[stable(feature = "drain", since = "1.6.0")] -impl DoubleEndedIterator for Drain<'_, T> { +impl DoubleEndedIterator for Drain<'_, T, A> { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back() @@ -1570,14 +1670,14 @@ impl DoubleEndedIterator for Drain<'_, T> { } #[stable(feature = "drain", since = "1.6.0")] -impl ExactSizeIterator for Drain<'_, T> { +impl ExactSizeIterator for Drain<'_, T, A> { fn is_empty(&self) -> bool { self.iter.is_empty() } } #[stable(feature = "fused", since = "1.26.0")] -impl FusedIterator for Drain<'_, T> {} +impl FusedIterator for Drain<'_, T, A> {} /// A draining iterator over the elements of a `BinaryHeap`. /// @@ -1587,17 +1687,29 @@ impl FusedIterator for Drain<'_, T> {} /// [`drain_sorted`]: BinaryHeap::drain_sorted #[unstable(feature = "binary_heap_drain_sorted", issue = "59278")] #[derive(Debug)] -pub struct DrainSorted<'a, T: Ord> { - inner: &'a mut BinaryHeap, +pub struct DrainSorted< + 'a, + T: Ord, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { + inner: &'a mut BinaryHeap, +} + +impl<'a, T: Ord, A: Allocator> DrainSorted<'a, T, A> { + /// Returns a reference to the underlying allocator. + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn allocator(&self) -> &A { + self.inner.allocator() + } } #[unstable(feature = "binary_heap_drain_sorted", issue = "59278")] -impl<'a, T: Ord> Drop for DrainSorted<'a, T> { +impl<'a, T: Ord, A: Allocator> Drop for DrainSorted<'a, T, A> { /// Removes heap elements in heap order. fn drop(&mut self) { - struct DropGuard<'r, 'a, T: Ord>(&'r mut DrainSorted<'a, T>); + struct DropGuard<'r, 'a, T: Ord, A: Allocator>(&'r mut DrainSorted<'a, T, A>); - impl<'r, 'a, T: Ord> Drop for DropGuard<'r, 'a, T> { + impl<'r, 'a, T: Ord, A: Allocator> Drop for DropGuard<'r, 'a, T, A> { fn drop(&mut self) { while self.0.inner.pop().is_some() {} } @@ -1612,7 +1724,7 @@ impl<'a, T: Ord> Drop for DrainSorted<'a, T> { } #[unstable(feature = "binary_heap_drain_sorted", issue = "59278")] -impl Iterator for DrainSorted<'_, T> { +impl Iterator for DrainSorted<'_, T, A> { type Item = T; #[inline] @@ -1628,20 +1740,20 @@ impl Iterator for DrainSorted<'_, T> { } #[unstable(feature = "binary_heap_drain_sorted", issue = "59278")] -impl ExactSizeIterator for DrainSorted<'_, T> {} +impl ExactSizeIterator for DrainSorted<'_, T, A> {} #[unstable(feature = "binary_heap_drain_sorted", issue = "59278")] -impl FusedIterator for DrainSorted<'_, T> {} +impl FusedIterator for DrainSorted<'_, T, A> {} #[unstable(feature = "trusted_len", issue = "37572")] -unsafe impl TrustedLen for DrainSorted<'_, T> {} +unsafe impl TrustedLen for DrainSorted<'_, T, A> {} #[stable(feature = "binary_heap_extras_15", since = "1.5.0")] -impl From> for BinaryHeap { +impl From> for BinaryHeap { /// Converts a `Vec` into a `BinaryHeap`. /// /// This conversion happens in-place, and has *O*(*n*) time complexity. - fn from(vec: Vec) -> BinaryHeap { + fn from(vec: Vec) -> BinaryHeap { let mut heap = BinaryHeap { data: vec }; heap.rebuild(); heap @@ -1665,12 +1777,12 @@ impl From<[T; N]> for BinaryHeap { } #[stable(feature = "binary_heap_extras_15", since = "1.5.0")] -impl From> for Vec { +impl From> for Vec { /// Converts a `BinaryHeap` into a `Vec`. /// /// This conversion requires no data movement or allocation, and has /// constant time complexity. - fn from(heap: BinaryHeap) -> Vec { + fn from(heap: BinaryHeap) -> Vec { heap.data } } @@ -1683,9 +1795,9 @@ impl FromIterator for BinaryHeap { } #[stable(feature = "rust1", since = "1.0.0")] -impl IntoIterator for BinaryHeap { +impl IntoIterator for BinaryHeap { type Item = T; - type IntoIter = IntoIter; + type IntoIter = IntoIter; /// Creates a consuming iterator, that is, one that moves each value out of /// the binary heap in arbitrary order. The binary heap cannot be used @@ -1705,13 +1817,13 @@ impl IntoIterator for BinaryHeap { /// println!("{x}"); /// } /// ``` - fn into_iter(self) -> IntoIter { + fn into_iter(self) -> IntoIter { IntoIter { iter: self.data.into_iter() } } } #[stable(feature = "rust1", since = "1.0.0")] -impl<'a, T> IntoIterator for &'a BinaryHeap { +impl<'a, T, A: Allocator> IntoIterator for &'a BinaryHeap { type Item = &'a T; type IntoIter = Iter<'a, T>; @@ -1721,7 +1833,7 @@ impl<'a, T> IntoIterator for &'a BinaryHeap { } #[stable(feature = "rust1", since = "1.0.0")] -impl Extend for BinaryHeap { +impl Extend for BinaryHeap { #[inline] fn extend>(&mut self, iter: I) { let guard = RebuildOnDrop { rebuild_from: self.len(), heap: self }; @@ -1740,7 +1852,7 @@ impl Extend for BinaryHeap { } #[stable(feature = "extend_ref", since = "1.2.0")] -impl<'a, T: 'a + Ord + Copy> Extend<&'a T> for BinaryHeap { +impl<'a, T: 'a + Ord + Copy, A: Allocator> Extend<&'a T> for BinaryHeap { fn extend>(&mut self, iter: I) { self.extend(iter.into_iter().cloned()); } diff --git a/library/alloc/src/collections/binary_heap/tests.rs b/library/alloc/src/collections/binary_heap/tests.rs index 500caa35678ab..565a7b7975f38 100644 --- a/library/alloc/src/collections/binary_heap/tests.rs +++ b/library/alloc/src/collections/binary_heap/tests.rs @@ -309,6 +309,7 @@ fn test_drain_sorted() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_drain_sorted_leak() { let d0 = CrashTestDummy::new(0); let d1 = CrashTestDummy::new(1); @@ -475,6 +476,7 @@ fn test_retain() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_retain_catch_unwind() { let mut heap = BinaryHeap::from(vec![3, 1, 2]); @@ -502,6 +504,7 @@ fn test_retain_catch_unwind() { // FIXME: re-enable emscripten once it can unwind again #[test] #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn panic_safe() { use rand::seq::SliceRandom; use std::cmp; diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs index 1f8a1ecba6e67..5481b327d691d 100644 --- a/library/alloc/src/collections/btree/map.rs +++ b/library/alloc/src/collections/btree/map.rs @@ -613,8 +613,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -636,8 +634,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -661,8 +657,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// # #![feature(allocator_api)] /// # #![feature(btreemap_alloc)] @@ -688,8 +682,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -744,8 +736,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -830,8 +820,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -917,8 +905,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -943,8 +929,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -982,8 +966,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -1017,8 +999,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// #![feature(map_try_insert)] /// @@ -1051,8 +1031,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -1078,8 +1056,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -1132,7 +1108,7 @@ impl BTreeMap { K: Ord, F: FnMut(&K, &mut V) -> bool, { - self.drain_filter(|k, v| !f(k, v)); + self.extract_if(|k, v| !f(k, v)).for_each(drop); } /// Moves all elements from `other` into `self`, leaving `other` empty. @@ -1208,8 +1184,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// use std::ops::Bound::Included; @@ -1251,8 +1225,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -1283,8 +1255,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -1336,8 +1306,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -1395,40 +1363,37 @@ impl BTreeMap { /// The iterator also lets you mutate the value of each element in the /// closure, regardless of whether you choose to keep or remove it. /// - /// If the iterator is only partially consumed or not consumed at all, each - /// of the remaining elements is still subjected to the closure, which may - /// change its value and, by returning `true`, have the element removed and - /// dropped. + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// Use [`retain`] with a negated predicate if you do not need the returned iterator. /// - /// It is unspecified how many more elements will be subjected to the - /// closure if a panic occurs in the closure, or a panic occurs while - /// dropping an element, or if the `DrainFilter` value is leaked. + /// [`retain`]: BTreeMap::retain /// /// # Examples /// /// Splitting a map into even and odd keys, reusing the original map: /// /// ``` - /// #![feature(btree_drain_filter)] + /// #![feature(btree_extract_if)] /// use std::collections::BTreeMap; /// /// let mut map: BTreeMap = (0..8).map(|x| (x, x)).collect(); - /// let evens: BTreeMap<_, _> = map.drain_filter(|k, _v| k % 2 == 0).collect(); + /// let evens: BTreeMap<_, _> = map.extract_if(|k, _v| k % 2 == 0).collect(); /// let odds = map; /// assert_eq!(evens.keys().copied().collect::>(), [0, 2, 4, 6]); /// assert_eq!(odds.keys().copied().collect::>(), [1, 3, 5, 7]); /// ``` - #[unstable(feature = "btree_drain_filter", issue = "70530")] - pub fn drain_filter(&mut self, pred: F) -> DrainFilter<'_, K, V, F, A> + #[unstable(feature = "btree_extract_if", issue = "70530")] + pub fn extract_if(&mut self, pred: F) -> ExtractIf<'_, K, V, F, A> where K: Ord, F: FnMut(&K, &mut V) -> bool, { - let (inner, alloc) = self.drain_filter_inner(); - DrainFilter { pred, inner, alloc } + let (inner, alloc) = self.extract_if_inner(); + ExtractIf { pred, inner, alloc } } - pub(super) fn drain_filter_inner(&mut self) -> (DrainFilterInner<'_, K, V>, A) + pub(super) fn extract_if_inner(&mut self) -> (ExtractIfInner<'_, K, V>, A) where K: Ord, { @@ -1436,7 +1401,7 @@ impl BTreeMap { let (root, dormant_root) = DormantMutRef::new(root); let front = root.borrow_mut().first_leaf_edge(); ( - DrainFilterInner { + ExtractIfInner { length: &mut self.length, dormant_root: Some(dormant_root), cur_leaf_edge: Some(front), @@ -1445,7 +1410,7 @@ impl BTreeMap { ) } else { ( - DrainFilterInner { + ExtractIfInner { length: &mut self.length, dormant_root: None, cur_leaf_edge: None, @@ -1899,9 +1864,10 @@ impl Default for Values<'_, K, V> { } } -/// An iterator produced by calling `drain_filter` on BTreeMap. -#[unstable(feature = "btree_drain_filter", issue = "70530")] -pub struct DrainFilter< +/// An iterator produced by calling `extract_if` on BTreeMap. +#[unstable(feature = "btree_extract_if", issue = "70530")] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ExtractIf< 'a, K, V, @@ -1911,13 +1877,13 @@ pub struct DrainFilter< F: 'a + FnMut(&K, &mut V) -> bool, { pred: F, - inner: DrainFilterInner<'a, K, V>, + inner: ExtractIfInner<'a, K, V>, /// The BTreeMap will outlive this IntoIter so we don't care about drop order for `alloc`. alloc: A, } -/// Most of the implementation of DrainFilter are generic over the type -/// of the predicate, thus also serving for BTreeSet::DrainFilter. -pub(super) struct DrainFilterInner<'a, K, V> { +/// Most of the implementation of ExtractIf are generic over the type +/// of the predicate, thus also serving for BTreeSet::ExtractIf. +pub(super) struct ExtractIfInner<'a, K, V> { /// Reference to the length field in the borrowed map, updated live. length: &'a mut usize, /// Buried reference to the root field in the borrowed map. @@ -1929,30 +1895,20 @@ pub(super) struct DrainFilterInner<'a, K, V> { cur_leaf_edge: Option, K, V, marker::Leaf>, marker::Edge>>, } -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl Drop for DrainFilter<'_, K, V, F, A> -where - F: FnMut(&K, &mut V) -> bool, -{ - fn drop(&mut self) { - self.for_each(drop); - } -} - -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl fmt::Debug for DrainFilter<'_, K, V, F> +#[unstable(feature = "btree_extract_if", issue = "70530")] +impl fmt::Debug for ExtractIf<'_, K, V, F> where K: fmt::Debug, V: fmt::Debug, F: FnMut(&K, &mut V) -> bool, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("DrainFilter").field(&self.inner.peek()).finish() + f.debug_tuple("ExtractIf").field(&self.inner.peek()).finish() } } -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl Iterator for DrainFilter<'_, K, V, F, A> +#[unstable(feature = "btree_extract_if", issue = "70530")] +impl Iterator for ExtractIf<'_, K, V, F, A> where F: FnMut(&K, &mut V) -> bool, { @@ -1967,14 +1923,14 @@ where } } -impl<'a, K, V> DrainFilterInner<'a, K, V> { +impl<'a, K, V> ExtractIfInner<'a, K, V> { /// Allow Debug implementations to predict the next element. pub(super) fn peek(&self) -> Option<(&K, &V)> { let edge = self.cur_leaf_edge.as_ref()?; edge.reborrow().next_kv().ok().map(Handle::into_kv) } - /// Implementation of a typical `DrainFilter::next` method, given the predicate. + /// Implementation of a typical `ExtractIf::next` method, given the predicate. pub(super) fn next(&mut self, pred: &mut F, alloc: A) -> Option<(K, V)> where F: FnMut(&K, &mut V) -> bool, @@ -2001,7 +1957,7 @@ impl<'a, K, V> DrainFilterInner<'a, K, V> { None } - /// Implementation of a typical `DrainFilter::size_hint` method. + /// Implementation of a typical `ExtractIf::size_hint` method. pub(super) fn size_hint(&self) -> (usize, Option) { // In most of the btree iterators, `self.length` is the number of elements // yet to be visited. Here, it includes elements that were visited and that @@ -2011,8 +1967,8 @@ impl<'a, K, V> DrainFilterInner<'a, K, V> { } } -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl FusedIterator for DrainFilter<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool {} +#[unstable(feature = "btree_extract_if", issue = "70530")] +impl FusedIterator for ExtractIf<'_, K, V, F> where F: FnMut(&K, &mut V) -> bool {} #[stable(feature = "btree_range", since = "1.17.0")] impl<'a, K, V> Iterator for Range<'a, K, V> { @@ -2400,8 +2356,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2432,8 +2386,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2465,8 +2417,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2486,8 +2436,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2507,8 +2455,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2533,8 +2479,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2558,8 +2502,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::collections::BTreeMap; /// @@ -2590,8 +2532,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// #![feature(btree_cursors)] /// @@ -2603,6 +2543,8 @@ impl BTreeMap { /// a.insert(2, "b"); /// a.insert(3, "c"); /// a.insert(4, "c"); + /// let cursor = a.lower_bound(Bound::Included(&2)); + /// assert_eq!(cursor.key(), Some(&2)); /// let cursor = a.lower_bound(Bound::Excluded(&2)); /// assert_eq!(cursor.key(), Some(&3)); /// ``` @@ -2631,8 +2573,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// #![feature(btree_cursors)] /// @@ -2644,6 +2584,8 @@ impl BTreeMap { /// a.insert(2, "b"); /// a.insert(3, "c"); /// a.insert(4, "c"); + /// let cursor = a.lower_bound_mut(Bound::Included(&2)); + /// assert_eq!(cursor.key(), Some(&2)); /// let cursor = a.lower_bound_mut(Bound::Excluded(&2)); /// assert_eq!(cursor.key(), Some(&3)); /// ``` @@ -2685,8 +2627,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// #![feature(btree_cursors)] /// @@ -2698,6 +2638,8 @@ impl BTreeMap { /// a.insert(2, "b"); /// a.insert(3, "c"); /// a.insert(4, "c"); + /// let cursor = a.upper_bound(Bound::Included(&3)); + /// assert_eq!(cursor.key(), Some(&3)); /// let cursor = a.upper_bound(Bound::Excluded(&3)); /// assert_eq!(cursor.key(), Some(&2)); /// ``` @@ -2726,8 +2668,6 @@ impl BTreeMap { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// #![feature(btree_cursors)] /// @@ -2739,6 +2679,8 @@ impl BTreeMap { /// a.insert(2, "b"); /// a.insert(3, "c"); /// a.insert(4, "c"); + /// let cursor = a.upper_bound_mut(Bound::Included(&3)); + /// assert_eq!(cursor.key(), Some(&3)); /// let cursor = a.upper_bound_mut(Bound::Excluded(&3)); /// assert_eq!(cursor.key(), Some(&2)); /// ``` diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs index 7ecffe3eef2d5..8681cfcd61757 100644 --- a/library/alloc/src/collections/btree/map/tests.rs +++ b/library/alloc/src/collections/btree/map/tests.rs @@ -941,13 +941,13 @@ fn test_retain() { assert_eq!(map[&6], 60); } -mod test_drain_filter { +mod test_extract_if { use super::*; #[test] fn empty() { let mut map: BTreeMap = BTreeMap::new(); - map.drain_filter(|_, _| unreachable!("there's nothing to decide on")); + map.extract_if(|_, _| unreachable!("there's nothing to decide on")).for_each(drop); assert_eq!(map.height(), None); map.check(); } @@ -957,7 +957,7 @@ mod test_drain_filter { fn consumed_keeping_all() { let pairs = (0..3).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - assert!(map.drain_filter(|_, _| false).eq(iter::empty())); + assert!(map.extract_if(|_, _| false).eq(iter::empty())); map.check(); } @@ -966,7 +966,7 @@ mod test_drain_filter { fn consumed_removing_all() { let pairs = (0..3).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs.clone()); - assert!(map.drain_filter(|_, _| true).eq(pairs)); + assert!(map.extract_if(|_, _| true).eq(pairs)); assert!(map.is_empty()); map.check(); } @@ -977,7 +977,7 @@ mod test_drain_filter { let pairs = (0..3).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); assert!( - map.drain_filter(|_, v| { + map.extract_if(|_, v| { *v += 6; false }) @@ -994,7 +994,7 @@ mod test_drain_filter { let pairs = (0..3).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); assert!( - map.drain_filter(|_, v| { + map.extract_if(|_, v| { *v += 6; true }) @@ -1008,7 +1008,7 @@ mod test_drain_filter { fn underfull_keeping_all() { let pairs = (0..3).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - map.drain_filter(|_, _| false); + map.extract_if(|_, _| false).for_each(drop); assert!(map.keys().copied().eq(0..3)); map.check(); } @@ -1018,7 +1018,7 @@ mod test_drain_filter { let pairs = (0..3).map(|i| (i, i)); for doomed in 0..3 { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i == doomed); + map.extract_if(|i, _| *i == doomed).for_each(drop); assert_eq!(map.len(), 2); map.check(); } @@ -1029,7 +1029,7 @@ mod test_drain_filter { let pairs = (0..3).map(|i| (i, i)); for sacred in 0..3 { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i != sacred); + map.extract_if(|i, _| *i != sacred).for_each(drop); assert!(map.keys().copied().eq(sacred..=sacred)); map.check(); } @@ -1039,7 +1039,7 @@ mod test_drain_filter { fn underfull_removing_all() { let pairs = (0..3).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - map.drain_filter(|_, _| true); + map.extract_if(|_, _| true).for_each(drop); assert!(map.is_empty()); map.check(); } @@ -1048,7 +1048,7 @@ mod test_drain_filter { fn height_0_keeping_all() { let pairs = (0..node::CAPACITY).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - map.drain_filter(|_, _| false); + map.extract_if(|_, _| false).for_each(drop); assert!(map.keys().copied().eq(0..node::CAPACITY)); map.check(); } @@ -1058,7 +1058,7 @@ mod test_drain_filter { let pairs = (0..node::CAPACITY).map(|i| (i, i)); for doomed in 0..node::CAPACITY { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i == doomed); + map.extract_if(|i, _| *i == doomed).for_each(drop); assert_eq!(map.len(), node::CAPACITY - 1); map.check(); } @@ -1069,7 +1069,7 @@ mod test_drain_filter { let pairs = (0..node::CAPACITY).map(|i| (i, i)); for sacred in 0..node::CAPACITY { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i != sacred); + map.extract_if(|i, _| *i != sacred).for_each(drop); assert!(map.keys().copied().eq(sacred..=sacred)); map.check(); } @@ -1079,7 +1079,7 @@ mod test_drain_filter { fn height_0_removing_all() { let pairs = (0..node::CAPACITY).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - map.drain_filter(|_, _| true); + map.extract_if(|_, _| true).for_each(drop); assert!(map.is_empty()); map.check(); } @@ -1087,7 +1087,7 @@ mod test_drain_filter { #[test] fn height_0_keeping_half() { let mut map = BTreeMap::from_iter((0..16).map(|i| (i, i))); - assert_eq!(map.drain_filter(|i, _| *i % 2 == 0).count(), 8); + assert_eq!(map.extract_if(|i, _| *i % 2 == 0).count(), 8); assert_eq!(map.len(), 8); map.check(); } @@ -1096,7 +1096,7 @@ mod test_drain_filter { fn height_1_removing_all() { let pairs = (0..MIN_INSERTS_HEIGHT_1).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - map.drain_filter(|_, _| true); + map.extract_if(|_, _| true).for_each(drop); assert!(map.is_empty()); map.check(); } @@ -1106,7 +1106,7 @@ mod test_drain_filter { let pairs = (0..MIN_INSERTS_HEIGHT_1).map(|i| (i, i)); for doomed in 0..MIN_INSERTS_HEIGHT_1 { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i == doomed); + map.extract_if(|i, _| *i == doomed).for_each(drop); assert_eq!(map.len(), MIN_INSERTS_HEIGHT_1 - 1); map.check(); } @@ -1117,7 +1117,7 @@ mod test_drain_filter { let pairs = (0..MIN_INSERTS_HEIGHT_1).map(|i| (i, i)); for sacred in 0..MIN_INSERTS_HEIGHT_1 { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i != sacred); + map.extract_if(|i, _| *i != sacred).for_each(drop); assert!(map.keys().copied().eq(sacred..=sacred)); map.check(); } @@ -1128,7 +1128,7 @@ mod test_drain_filter { let pairs = (0..MIN_INSERTS_HEIGHT_2).map(|i| (i, i)); for doomed in (0..MIN_INSERTS_HEIGHT_2).step_by(12) { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i == doomed); + map.extract_if(|i, _| *i == doomed).for_each(drop); assert_eq!(map.len(), MIN_INSERTS_HEIGHT_2 - 1); map.check(); } @@ -1139,7 +1139,7 @@ mod test_drain_filter { let pairs = (0..MIN_INSERTS_HEIGHT_2).map(|i| (i, i)); for sacred in (0..MIN_INSERTS_HEIGHT_2).step_by(12) { let mut map = BTreeMap::from_iter(pairs.clone()); - map.drain_filter(|i, _| *i != sacred); + map.extract_if(|i, _| *i != sacred).for_each(drop); assert!(map.keys().copied().eq(sacred..=sacred)); map.check(); } @@ -1149,12 +1149,13 @@ mod test_drain_filter { fn height_2_removing_all() { let pairs = (0..MIN_INSERTS_HEIGHT_2).map(|i| (i, i)); let mut map = BTreeMap::from_iter(pairs); - map.drain_filter(|_, _| true); + map.extract_if(|_, _| true).for_each(drop); assert!(map.is_empty()); map.check(); } #[test] + #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn drop_panic_leak() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); @@ -1164,7 +1165,8 @@ mod test_drain_filter { map.insert(b.spawn(Panic::InDrop), ()); map.insert(c.spawn(Panic::Never), ()); - catch_unwind(move || drop(map.drain_filter(|dummy, _| dummy.query(true)))).unwrap_err(); + catch_unwind(move || map.extract_if(|dummy, _| dummy.query(true)).for_each(drop)) + .unwrap_err(); assert_eq!(a.queried(), 1); assert_eq!(b.queried(), 1); @@ -1175,6 +1177,7 @@ mod test_drain_filter { } #[test] + #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn pred_panic_leak() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); @@ -1184,8 +1187,10 @@ mod test_drain_filter { map.insert(b.spawn(Panic::InQuery), ()); map.insert(c.spawn(Panic::InQuery), ()); - catch_unwind(AssertUnwindSafe(|| drop(map.drain_filter(|dummy, _| dummy.query(true))))) - .unwrap_err(); + catch_unwind(AssertUnwindSafe(|| { + map.extract_if(|dummy, _| dummy.query(true)).for_each(drop) + })) + .unwrap_err(); assert_eq!(a.queried(), 1); assert_eq!(b.queried(), 1); @@ -1201,6 +1206,7 @@ mod test_drain_filter { // Same as above, but attempt to use the iterator again after the panic in the predicate #[test] + #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn pred_panic_reuse() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); @@ -1211,7 +1217,7 @@ mod test_drain_filter { map.insert(c.spawn(Panic::InQuery), ()); { - let mut it = map.drain_filter(|dummy, _| dummy.query(true)); + let mut it = map.extract_if(|dummy, _| dummy.query(true)); catch_unwind(AssertUnwindSafe(|| while it.next().is_some() {})).unwrap_err(); // Iterator behaviour after a panic is explicitly unspecified, // so this is just the current implementation: @@ -1449,6 +1455,7 @@ fn test_clear() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_clear_drop_panic_leak() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); @@ -1540,11 +1547,13 @@ fn test_clone_panic_leak(size: usize) { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_clone_panic_leak_height_0() { test_clone_panic_leak(3) } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_clone_panic_leak_height_1() { test_clone_panic_leak(MIN_INSERTS_HEIGHT_1) } @@ -1651,8 +1660,8 @@ fn assert_sync() { v.into_values() } - fn drain_filter(v: &mut BTreeMap) -> impl Sync + '_ { - v.drain_filter(|_, _| false) + fn extract_if(v: &mut BTreeMap) -> impl Sync + '_ { + v.extract_if(|_, _| false) } fn iter(v: &BTreeMap) -> impl Sync + '_ { @@ -1720,8 +1729,8 @@ fn assert_send() { v.into_values() } - fn drain_filter(v: &mut BTreeMap) -> impl Send + '_ { - v.drain_filter(|_, _| false) + fn extract_if(v: &mut BTreeMap) -> impl Send + '_ { + v.extract_if(|_, _| false) } fn iter(v: &BTreeMap) -> impl Send + '_ { @@ -2099,6 +2108,7 @@ create_append_test!(test_append_239, 239); create_append_test!(test_append_1700, 1700); #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_append_drop_leak() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); @@ -2240,6 +2250,7 @@ fn test_split_off_large_random_sorted() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_into_iter_drop_leak_height_0() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); @@ -2263,6 +2274,7 @@ fn test_into_iter_drop_leak_height_0() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_into_iter_drop_leak_height_1() { let size = MIN_INSERTS_HEIGHT_1; for panic_point in vec![0, 1, size - 2, size - 1] { diff --git a/library/alloc/src/collections/btree/set.rs b/library/alloc/src/collections/btree/set.rs index 940fa30afb80f..9da230915b822 100644 --- a/library/alloc/src/collections/btree/set.rs +++ b/library/alloc/src/collections/btree/set.rs @@ -999,7 +999,7 @@ impl BTreeSet { T: Ord, F: FnMut(&T) -> bool, { - self.drain_filter(|v| !f(v)); + self.extract_if(|v| !f(v)).for_each(drop); } /// Moves all elements from `other` into `self`, leaving `other` empty. @@ -1084,36 +1084,33 @@ impl BTreeSet { /// yielded. If the closure returns `false`, or panics, the element remains /// in the set and will not be yielded. /// - /// If the iterator is only partially consumed or not consumed at all, each - /// of the remaining elements is still subjected to the closure and removed - /// and dropped if it returns `true`. - /// - /// It is unspecified how many more elements will be subjected to the - /// closure if a panic occurs in the closure, or if a panic occurs while - /// dropping an element, or if the `DrainFilter` itself is leaked. + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// Use [`retain`] with a negated predicate if you do not need the returned iterator. /// + /// [`retain`]: BTreeSet::retain /// # Examples /// /// Splitting a set into even and odd values, reusing the original set: /// /// ``` - /// #![feature(btree_drain_filter)] + /// #![feature(btree_extract_if)] /// use std::collections::BTreeSet; /// /// let mut set: BTreeSet = (0..8).collect(); - /// let evens: BTreeSet<_> = set.drain_filter(|v| v % 2 == 0).collect(); + /// let evens: BTreeSet<_> = set.extract_if(|v| v % 2 == 0).collect(); /// let odds = set; /// assert_eq!(evens.into_iter().collect::>(), vec![0, 2, 4, 6]); /// assert_eq!(odds.into_iter().collect::>(), vec![1, 3, 5, 7]); /// ``` - #[unstable(feature = "btree_drain_filter", issue = "70530")] - pub fn drain_filter<'a, F>(&'a mut self, pred: F) -> DrainFilter<'a, T, F, A> + #[unstable(feature = "btree_extract_if", issue = "70530")] + pub fn extract_if<'a, F>(&'a mut self, pred: F) -> ExtractIf<'a, T, F, A> where T: Ord, F: 'a + FnMut(&T) -> bool, { - let (inner, alloc) = self.map.drain_filter_inner(); - DrainFilter { pred, inner, alloc } + let (inner, alloc) = self.map.extract_if_inner(); + ExtractIf { pred, inner, alloc } } /// Gets an iterator that visits the elements in the `BTreeSet` in ascending @@ -1124,19 +1121,6 @@ impl BTreeSet { /// ``` /// use std::collections::BTreeSet; /// - /// let set = BTreeSet::from([1, 2, 3]); - /// let mut set_iter = set.iter(); - /// assert_eq!(set_iter.next(), Some(&1)); - /// assert_eq!(set_iter.next(), Some(&2)); - /// assert_eq!(set_iter.next(), Some(&3)); - /// assert_eq!(set_iter.next(), None); - /// ``` - /// - /// Values returned by the iterator are returned in ascending order: - /// - /// ``` - /// use std::collections::BTreeSet; - /// /// let set = BTreeSet::from([3, 1, 2]); /// let mut set_iter = set.iter(); /// assert_eq!(set_iter.next(), Some(&1)); @@ -1275,9 +1259,10 @@ impl<'a, T, A: Allocator + Clone> IntoIterator for &'a BTreeSet { } } -/// An iterator produced by calling `drain_filter` on BTreeSet. -#[unstable(feature = "btree_drain_filter", issue = "70530")] -pub struct DrainFilter< +/// An iterator produced by calling `extract_if` on BTreeSet. +#[unstable(feature = "btree_extract_if", issue = "70530")] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ExtractIf< 'a, T, F, @@ -1287,34 +1272,24 @@ pub struct DrainFilter< F: 'a + FnMut(&T) -> bool, { pred: F, - inner: super::map::DrainFilterInner<'a, T, SetValZST>, + inner: super::map::ExtractIfInner<'a, T, SetValZST>, /// The BTreeMap will outlive this IntoIter so we don't care about drop order for `alloc`. alloc: A, } -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl Drop for DrainFilter<'_, T, F, A> -where - F: FnMut(&T) -> bool, -{ - fn drop(&mut self) { - self.for_each(drop); - } -} - -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl fmt::Debug for DrainFilter<'_, T, F, A> +#[unstable(feature = "btree_extract_if", issue = "70530")] +impl fmt::Debug for ExtractIf<'_, T, F, A> where T: fmt::Debug, F: FnMut(&T) -> bool, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("DrainFilter").field(&self.inner.peek().map(|(k, _)| k)).finish() + f.debug_tuple("ExtractIf").field(&self.inner.peek().map(|(k, _)| k)).finish() } } -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl<'a, T, F, A: Allocator + Clone> Iterator for DrainFilter<'_, T, F, A> +#[unstable(feature = "btree_extract_if", issue = "70530")] +impl<'a, T, F, A: Allocator + Clone> Iterator for ExtractIf<'_, T, F, A> where F: 'a + FnMut(&T) -> bool, { @@ -1331,11 +1306,8 @@ where } } -#[unstable(feature = "btree_drain_filter", issue = "70530")] -impl FusedIterator for DrainFilter<'_, T, F, A> where - F: FnMut(&T) -> bool -{ -} +#[unstable(feature = "btree_extract_if", issue = "70530")] +impl FusedIterator for ExtractIf<'_, T, F, A> where F: FnMut(&T) -> bool {} #[stable(feature = "rust1", since = "1.0.0")] impl Extend for BTreeSet { diff --git a/library/alloc/src/collections/btree/set/tests.rs b/library/alloc/src/collections/btree/set/tests.rs index a7c839d77ed4c..e05bf0e20036d 100644 --- a/library/alloc/src/collections/btree/set/tests.rs +++ b/library/alloc/src/collections/btree/set/tests.rs @@ -366,18 +366,19 @@ fn test_retain() { } #[test] -fn test_drain_filter() { +fn test_extract_if() { let mut x = BTreeSet::from([1]); let mut y = BTreeSet::from([1]); - x.drain_filter(|_| true); - y.drain_filter(|_| false); + x.extract_if(|_| true).for_each(drop); + y.extract_if(|_| false).for_each(drop); assert_eq!(x.len(), 0); assert_eq!(y.len(), 1); } #[test] -fn test_drain_filter_drop_panic_leak() { +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn test_extract_if_drop_panic_leak() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); let c = CrashTestDummy::new(2); @@ -386,7 +387,7 @@ fn test_drain_filter_drop_panic_leak() { set.insert(b.spawn(Panic::InDrop)); set.insert(c.spawn(Panic::Never)); - catch_unwind(move || drop(set.drain_filter(|dummy| dummy.query(true)))).ok(); + catch_unwind(move || set.extract_if(|dummy| dummy.query(true)).for_each(drop)).ok(); assert_eq!(a.queried(), 1); assert_eq!(b.queried(), 1); @@ -397,7 +398,8 @@ fn test_drain_filter_drop_panic_leak() { } #[test] -fn test_drain_filter_pred_panic_leak() { +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn test_extract_if_pred_panic_leak() { let a = CrashTestDummy::new(0); let b = CrashTestDummy::new(1); let c = CrashTestDummy::new(2); @@ -406,7 +408,8 @@ fn test_drain_filter_pred_panic_leak() { set.insert(b.spawn(Panic::InQuery)); set.insert(c.spawn(Panic::InQuery)); - catch_unwind(AssertUnwindSafe(|| drop(set.drain_filter(|dummy| dummy.query(true))))).ok(); + catch_unwind(AssertUnwindSafe(|| set.extract_if(|dummy| dummy.query(true)).for_each(drop))) + .ok(); assert_eq!(a.queried(), 1); assert_eq!(b.queried(), 1); @@ -603,8 +606,8 @@ fn assert_sync() { v.range(..) } - fn drain_filter(v: &mut BTreeSet) -> impl Sync + '_ { - v.drain_filter(|_| false) + fn extract_if(v: &mut BTreeSet) -> impl Sync + '_ { + v.extract_if(|_| false) } fn difference(v: &BTreeSet) -> impl Sync + '_ { @@ -642,8 +645,8 @@ fn assert_send() { v.range(..) } - fn drain_filter(v: &mut BTreeSet) -> impl Send + '_ { - v.drain_filter(|_| false) + fn extract_if(v: &mut BTreeSet) -> impl Send + '_ { + v.extract_if(|_| false) } fn difference(v: &BTreeSet) -> impl Send + '_ { diff --git a/library/alloc/src/collections/linked_list.rs b/library/alloc/src/collections/linked_list.rs index 4cd34ac2fa74f..2c26f9e031244 100644 --- a/library/alloc/src/collections/linked_list.rs +++ b/library/alloc/src/collections/linked_list.rs @@ -18,7 +18,7 @@ use core::hash::{Hash, Hasher}; use core::iter::FusedIterator; use core::marker::PhantomData; use core::mem; -use core::ptr::{NonNull, Unique}; +use core::ptr::NonNull; use super::SpecExtend; use crate::alloc::{Allocator, Global}; @@ -168,15 +168,16 @@ impl LinkedList { /// Adds the given node to the front of the list. /// /// # Safety - /// `node` must point to a valid node that was boxed using the list's allocator. + /// `node` must point to a valid node that was boxed and leaked using the list's allocator. + /// This method takes ownership of the node, so the pointer should not be used again. #[inline] - unsafe fn push_front_node(&mut self, node: Unique>) { + unsafe fn push_front_node(&mut self, node: NonNull>) { // This method takes care not to create mutable references to whole nodes, // to maintain validity of aliasing pointers into `element`. unsafe { (*node.as_ptr()).next = self.head; (*node.as_ptr()).prev = None; - let node = Some(NonNull::from(node)); + let node = Some(node); match self.head { None => self.tail = node, @@ -212,15 +213,16 @@ impl LinkedList { /// Adds the given node to the back of the list. /// /// # Safety - /// `node` must point to a valid node that was boxed using the list's allocator. + /// `node` must point to a valid node that was boxed and leaked using the list's allocator. + /// This method takes ownership of the node, so the pointer should not be used again. #[inline] - unsafe fn push_back_node(&mut self, node: Unique>) { + unsafe fn push_back_node(&mut self, node: NonNull>) { // This method takes care not to create mutable references to whole nodes, // to maintain validity of aliasing pointers into `element`. unsafe { (*node.as_ptr()).next = None; (*node.as_ptr()).prev = self.tail; - let node = Some(NonNull::from(node)); + let node = Some(node); match self.tail { None => self.head = node, @@ -842,8 +844,8 @@ impl LinkedList { #[stable(feature = "rust1", since = "1.0.0")] pub fn push_front(&mut self, elt: T) { let node = Box::new_in(Node::new(elt), &self.alloc); - let node_ptr = Unique::from(Box::leak(node)); - // SAFETY: node_ptr is a unique pointer to a node we boxed with self.alloc + let node_ptr = NonNull::from(Box::leak(node)); + // SAFETY: node_ptr is a unique pointer to a node we boxed with self.alloc and leaked unsafe { self.push_front_node(node_ptr); } @@ -890,8 +892,8 @@ impl LinkedList { #[stable(feature = "rust1", since = "1.0.0")] pub fn push_back(&mut self, elt: T) { let node = Box::new_in(Node::new(elt), &self.alloc); - let node_ptr = Unique::from(Box::leak(node)); - // SAFETY: node_ptr is a unique pointer to a node we boxed with self.alloc + let node_ptr = NonNull::from(Box::leak(node)); + // SAFETY: node_ptr is a unique pointer to a node we boxed with self.alloc and leaked unsafe { self.push_back_node(node_ptr); } @@ -1030,7 +1032,11 @@ impl LinkedList { /// If the closure returns false, the element will remain in the list and will not be yielded /// by the iterator. /// - /// Note that `drain_filter` lets you mutate every element in the filter closure, regardless of + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// Use `extract_if().for_each(drop)` if you do not need the returned iterator. + /// + /// Note that `extract_if` lets you mutate every element in the filter closure, regardless of /// whether you choose to keep or remove it. /// /// # Examples @@ -1038,20 +1044,20 @@ impl LinkedList { /// Splitting a list into evens and odds, reusing the original list: /// /// ``` - /// #![feature(drain_filter)] + /// #![feature(extract_if)] /// use std::collections::LinkedList; /// /// let mut numbers: LinkedList = LinkedList::new(); /// numbers.extend(&[1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15]); /// - /// let evens = numbers.drain_filter(|x| *x % 2 == 0).collect::>(); + /// let evens = numbers.extract_if(|x| *x % 2 == 0).collect::>(); /// let odds = numbers; /// /// assert_eq!(evens.into_iter().collect::>(), vec![2, 4, 6, 8, 14]); /// assert_eq!(odds.into_iter().collect::>(), vec![1, 3, 5, 9, 11, 13, 15]); /// ``` - #[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] - pub fn drain_filter(&mut self, filter: F) -> DrainFilter<'_, T, F, A> + #[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] + pub fn extract_if(&mut self, filter: F) -> ExtractIf<'_, T, F, A> where F: FnMut(&mut T) -> bool, { @@ -1059,7 +1065,7 @@ impl LinkedList { let it = self.head; let old_len = self.len; - DrainFilter { list: self, it, pred: filter, idx: 0, old_len } + ExtractIf { list: self, it, pred: filter, idx: 0, old_len } } } @@ -1803,9 +1809,10 @@ impl<'a, T, A: Allocator> CursorMut<'a, T, A> { } } -/// An iterator produced by calling `drain_filter` on LinkedList. -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -pub struct DrainFilter< +/// An iterator produced by calling `extract_if` on LinkedList. +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ExtractIf< 'a, T: 'a, F: 'a, @@ -1820,8 +1827,8 @@ pub struct DrainFilter< old_len: usize, } -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -impl Iterator for DrainFilter<'_, T, F, A> +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +impl Iterator for ExtractIf<'_, T, F, A> where F: FnMut(&mut T) -> bool, { @@ -1849,40 +1856,13 @@ where } } -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -impl Drop for DrainFilter<'_, T, F, A> -where - F: FnMut(&mut T) -> bool, -{ - fn drop(&mut self) { - struct DropGuard<'r, 'a, T, F, A: Allocator>(&'r mut DrainFilter<'a, T, F, A>) - where - F: FnMut(&mut T) -> bool; - - impl<'r, 'a, T, F, A: Allocator> Drop for DropGuard<'r, 'a, T, F, A> - where - F: FnMut(&mut T) -> bool, - { - fn drop(&mut self) { - self.0.for_each(drop); - } - } - - while let Some(item) = self.next() { - let guard = DropGuard(self); - drop(item); - mem::forget(guard); - } - } -} - -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -impl fmt::Debug for DrainFilter<'_, T, F> +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +impl fmt::Debug for ExtractIf<'_, T, F> where F: FnMut(&mut T) -> bool, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("DrainFilter").field(&self.list).finish() + f.debug_tuple("ExtractIf").field(&self.list).finish() } } diff --git a/library/alloc/src/collections/linked_list/tests.rs b/library/alloc/src/collections/linked_list/tests.rs index 04594d55b6abf..8dcd59d12d927 100644 --- a/library/alloc/src/collections/linked_list/tests.rs +++ b/library/alloc/src/collections/linked_list/tests.rs @@ -540,10 +540,10 @@ fn test_show() { } #[test] -fn drain_filter_test() { +fn extract_if_test() { let mut m: LinkedList = LinkedList::new(); m.extend(&[1, 2, 3, 4, 5, 6]); - let deleted = m.drain_filter(|v| *v < 4).collect::>(); + let deleted = m.extract_if(|v| *v < 4).collect::>(); check_links(&m); @@ -555,7 +555,7 @@ fn drain_filter_test() { fn drain_to_empty_test() { let mut m: LinkedList = LinkedList::new(); m.extend(&[1, 2, 3, 4, 5, 6]); - let deleted = m.drain_filter(|_| true).collect::>(); + let deleted = m.extract_if(|_| true).collect::>(); check_links(&m); @@ -811,11 +811,11 @@ fn test_contains() { } #[test] -fn drain_filter_empty() { +fn extract_if_empty() { let mut list: LinkedList = LinkedList::new(); { - let mut iter = list.drain_filter(|_| true); + let mut iter = list.extract_if(|_| true); assert_eq!(iter.size_hint(), (0, Some(0))); assert_eq!(iter.next(), None); assert_eq!(iter.size_hint(), (0, Some(0))); @@ -828,13 +828,13 @@ fn drain_filter_empty() { } #[test] -fn drain_filter_zst() { +fn extract_if_zst() { let mut list: LinkedList<_> = [(), (), (), (), ()].into_iter().collect(); let initial_len = list.len(); let mut count = 0; { - let mut iter = list.drain_filter(|_| true); + let mut iter = list.extract_if(|_| true); assert_eq!(iter.size_hint(), (0, Some(initial_len))); while let Some(_) = iter.next() { count += 1; @@ -851,14 +851,14 @@ fn drain_filter_zst() { } #[test] -fn drain_filter_false() { +fn extract_if_false() { let mut list: LinkedList<_> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter().collect(); let initial_len = list.len(); let mut count = 0; { - let mut iter = list.drain_filter(|_| false); + let mut iter = list.extract_if(|_| false); assert_eq!(iter.size_hint(), (0, Some(initial_len))); for _ in iter.by_ref() { count += 1; @@ -874,14 +874,14 @@ fn drain_filter_false() { } #[test] -fn drain_filter_true() { +fn extract_if_true() { let mut list: LinkedList<_> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_iter().collect(); let initial_len = list.len(); let mut count = 0; { - let mut iter = list.drain_filter(|_| true); + let mut iter = list.extract_if(|_| true); assert_eq!(iter.size_hint(), (0, Some(initial_len))); while let Some(_) = iter.next() { count += 1; @@ -898,7 +898,7 @@ fn drain_filter_true() { } #[test] -fn drain_filter_complex() { +fn extract_if_complex() { { // [+xxx++++++xxxxx++++x+x++] let mut list = [ @@ -908,7 +908,7 @@ fn drain_filter_complex() { .into_iter() .collect::>(); - let removed = list.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = list.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); @@ -926,7 +926,7 @@ fn drain_filter_complex() { .into_iter() .collect::>(); - let removed = list.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = list.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); @@ -944,7 +944,7 @@ fn drain_filter_complex() { .into_iter() .collect::>(); - let removed = list.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = list.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); @@ -961,7 +961,7 @@ fn drain_filter_complex() { .into_iter() .collect::>(); - let removed = list.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = list.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]); @@ -975,7 +975,7 @@ fn drain_filter_complex() { .into_iter() .collect::>(); - let removed = list.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = list.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]); @@ -985,7 +985,8 @@ fn drain_filter_complex() { } #[test] -fn drain_filter_drop_panic_leak() { +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn extract_if_drop_panic_leak() { let d0 = CrashTestDummy::new(0); let d1 = CrashTestDummy::new(1); let d2 = CrashTestDummy::new(2); @@ -1004,21 +1005,28 @@ fn drain_filter_drop_panic_leak() { q.push_front(d1.spawn(Panic::InDrop)); q.push_front(d0.spawn(Panic::Never)); - catch_unwind(AssertUnwindSafe(|| drop(q.drain_filter(|_| true)))).unwrap_err(); + catch_unwind(AssertUnwindSafe(|| q.extract_if(|_| true).for_each(drop))).unwrap_err(); assert_eq!(d0.dropped(), 1); assert_eq!(d1.dropped(), 1); + assert_eq!(d2.dropped(), 0); + assert_eq!(d3.dropped(), 0); + assert_eq!(d4.dropped(), 0); + assert_eq!(d5.dropped(), 0); + assert_eq!(d6.dropped(), 0); + assert_eq!(d7.dropped(), 0); + drop(q); assert_eq!(d2.dropped(), 1); assert_eq!(d3.dropped(), 1); assert_eq!(d4.dropped(), 1); assert_eq!(d5.dropped(), 1); assert_eq!(d6.dropped(), 1); assert_eq!(d7.dropped(), 1); - assert!(q.is_empty()); } #[test] -fn drain_filter_pred_panic_leak() { +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn extract_if_pred_panic_leak() { static mut DROPS: i32 = 0; #[derive(Debug)] @@ -1043,7 +1051,7 @@ fn drain_filter_pred_panic_leak() { q.push_front(D(0)); catch_unwind(AssertUnwindSafe(|| { - drop(q.drain_filter(|item| if item.0 >= 2 { panic!() } else { true })) + q.extract_if(|item| if item.0 >= 2 { panic!() } else { true }).for_each(drop) })) .ok(); @@ -1124,6 +1132,7 @@ fn test_drop_clear() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_drop_panic() { static mut DROPS: i32 = 0; diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 896da37f94c02..5965ec2affac6 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -2283,21 +2283,21 @@ impl VecDeque { unsafe { slice::from_raw_parts_mut(ptr.add(self.head), self.len) } } - /// Rotates the double-ended queue `mid` places to the left. + /// Rotates the double-ended queue `n` places to the left. /// /// Equivalently, - /// - Rotates item `mid` into the first position. - /// - Pops the first `mid` items and pushes them to the end. - /// - Rotates `len() - mid` places to the right. + /// - Rotates item `n` into the first position. + /// - Pops the first `n` items and pushes them to the end. + /// - Rotates `len() - n` places to the right. /// /// # Panics /// - /// If `mid` is greater than `len()`. Note that `mid == len()` + /// If `n` is greater than `len()`. Note that `n == len()` /// does _not_ panic and is a no-op rotation. /// /// # Complexity /// - /// Takes `*O*(min(mid, len() - mid))` time and no extra space. + /// Takes `*O*(min(n, len() - n))` time and no extra space. /// /// # Examples /// @@ -2316,31 +2316,31 @@ impl VecDeque { /// assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); /// ``` #[stable(feature = "vecdeque_rotate", since = "1.36.0")] - pub fn rotate_left(&mut self, mid: usize) { - assert!(mid <= self.len()); - let k = self.len - mid; - if mid <= k { - unsafe { self.rotate_left_inner(mid) } + pub fn rotate_left(&mut self, n: usize) { + assert!(n <= self.len()); + let k = self.len - n; + if n <= k { + unsafe { self.rotate_left_inner(n) } } else { unsafe { self.rotate_right_inner(k) } } } - /// Rotates the double-ended queue `k` places to the right. + /// Rotates the double-ended queue `n` places to the right. /// /// Equivalently, - /// - Rotates the first item into position `k`. - /// - Pops the last `k` items and pushes them to the front. - /// - Rotates `len() - k` places to the left. + /// - Rotates the first item into position `n`. + /// - Pops the last `n` items and pushes them to the front. + /// - Rotates `len() - n` places to the left. /// /// # Panics /// - /// If `k` is greater than `len()`. Note that `k == len()` + /// If `n` is greater than `len()`. Note that `n == len()` /// does _not_ panic and is a no-op rotation. /// /// # Complexity /// - /// Takes `*O*(min(k, len() - k))` time and no extra space. + /// Takes `*O*(min(n, len() - n))` time and no extra space. /// /// # Examples /// @@ -2359,13 +2359,13 @@ impl VecDeque { /// assert_eq!(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); /// ``` #[stable(feature = "vecdeque_rotate", since = "1.36.0")] - pub fn rotate_right(&mut self, k: usize) { - assert!(k <= self.len()); - let mid = self.len - k; - if k <= mid { - unsafe { self.rotate_right_inner(k) } + pub fn rotate_right(&mut self, n: usize) { + assert!(n <= self.len()); + let k = self.len - n; + if n <= k { + unsafe { self.rotate_right_inner(n) } } else { - unsafe { self.rotate_left_inner(mid) } + unsafe { self.rotate_left_inner(k) } } } diff --git a/library/alloc/src/collections/vec_deque/tests.rs b/library/alloc/src/collections/vec_deque/tests.rs index 205a8ff3c19a8..b7fdebfa60b2a 100644 --- a/library/alloc/src/collections/vec_deque/tests.rs +++ b/library/alloc/src/collections/vec_deque/tests.rs @@ -351,14 +351,14 @@ fn test_rotate_left_right() { } #[test] -#[should_panic = "assertion failed: mid <= self.len()"] +#[should_panic = "assertion failed: n <= self.len()"] fn test_rotate_left_panic() { let mut tester: VecDeque<_> = (1..=10).collect(); tester.rotate_left(tester.len() + 1); } #[test] -#[should_panic = "assertion failed: k <= self.len()"] +#[should_panic = "assertion failed: n <= self.len()"] fn test_rotate_right_panic() { let mut tester: VecDeque<_> = (1..=10).collect(); tester.rotate_right(tester.len() + 1); diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index f99395c72aa03..62856fc9a49b5 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -888,7 +888,7 @@ impl From<&CStr> for Arc { #[stable(feature = "shared_from_slice2", since = "1.24.0")] impl From for Rc { /// Converts a [`CString`] into an [Rc]<[CStr]> by moving the [`CString`] - /// data into a new [`Arc`] buffer. + /// data into a new [`Rc`] buffer. #[inline] fn from(s: CString) -> Rc { let rc: Rc<[u8]> = Rc::from(s.into_inner()); diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 59fa91c1066dc..ffe6d6373875c 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -56,6 +56,11 @@ //! [`Rc`]: rc //! [`RefCell`]: core::cell +// To run alloc tests without x.py without ending up with two copies of alloc, Miri needs to be +// able to "empty" this crate. See . +// rustc itself never sets the feature, so this line has no effect there. +#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))] +// #![allow(unused_attributes)] #![stable(feature = "alloc", since = "1.36.0")] #![doc( @@ -75,11 +80,6 @@ ))] #![no_std] #![needs_allocator] -// To run alloc tests without x.py without ending up with two copies of alloc, Miri needs to be -// able to "empty" this crate. See . -// rustc itself never sets the feature, so this line has no affect there. -#![cfg(any(not(feature = "miri-test-libstd"), test, doctest))] -// // Lints: #![deny(unsafe_op_in_unsafe_fn)] #![deny(fuzzy_provenance_casts)] @@ -88,6 +88,8 @@ #![warn(missing_docs)] #![allow(explicit_outlives_requirements)] #![warn(multiple_supertrait_upcastable)] +#![cfg_attr(not(bootstrap), allow(internal_features))] +#![cfg_attr(not(bootstrap), allow(rustdoc::redundant_explicit_links))] // // Library features: // tidy-alphabetical-start @@ -137,7 +139,6 @@ #![feature(maybe_uninit_uninit_array_transpose)] #![feature(pattern)] #![feature(pointer_byte_offsets)] -#![feature(provide_any)] #![feature(ptr_internals)] #![feature(ptr_metadata)] #![feature(ptr_sub_ptr)] diff --git a/library/alloc/src/raw_vec.rs b/library/alloc/src/raw_vec.rs index dfd30d99cf041..01b03de6acb5e 100644 --- a/library/alloc/src/raw_vec.rs +++ b/library/alloc/src/raw_vec.rs @@ -432,16 +432,26 @@ impl RawVec { let (ptr, layout) = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) }; // See current_memory() why this assert is here let _: () = const { assert!(mem::size_of::() % mem::align_of::() == 0) }; - let ptr = unsafe { - // `Layout::array` cannot overflow here because it would have - // overflowed earlier when capacity was larger. - let new_size = mem::size_of::().unchecked_mul(cap); - let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); - self.alloc - .shrink(ptr, layout, new_layout) - .map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })? - }; - self.set_ptr_and_cap(ptr, cap); + + // If shrinking to 0, deallocate the buffer. We don't reach this point + // for the T::IS_ZST case since current_memory() will have returned + // None. + if cap == 0 { + unsafe { self.alloc.deallocate(ptr, layout) }; + self.ptr = Unique::dangling(); + self.cap = 0; + } else { + let ptr = unsafe { + // `Layout::array` cannot overflow here because it would have + // overflowed earlier when capacity was larger. + let new_size = mem::size_of::().unchecked_mul(cap); + let new_layout = Layout::from_size_align_unchecked(new_size, layout.align()); + self.alloc + .shrink(ptr, layout, new_layout) + .map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })? + }; + self.set_ptr_and_cap(ptr, cap); + } Ok(()) } } diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index 38a711ac7509f..c485680f92ec5 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -258,19 +258,19 @@ use core::iter; use core::marker::{PhantomData, Unsize}; #[cfg(not(no_global_oom_handling))] use core::mem::size_of_val; -use core::mem::{self, align_of_val_raw, forget}; -use core::ops::{CoerceUnsized, Deref, DispatchFromDyn, Receiver}; +use core::mem::{self, align_of_val_raw, forget, ManuallyDrop}; +use core::ops::{CoerceUnsized, Deref, DerefMut, DispatchFromDyn, Receiver}; use core::panic::{RefUnwindSafe, UnwindSafe}; #[cfg(not(no_global_oom_handling))] use core::pin::Pin; -use core::ptr::{self, NonNull}; +use core::ptr::{self, drop_in_place, NonNull}; #[cfg(not(no_global_oom_handling))] use core::slice::from_raw_parts_mut; #[cfg(not(no_global_oom_handling))] use crate::alloc::handle_alloc_error; #[cfg(not(no_global_oom_handling))] -use crate::alloc::{box_free, WriteCloneIntoRaw}; +use crate::alloc::WriteCloneIntoRaw; use crate::alloc::{AllocError, Allocator, Global, Layout}; use crate::borrow::{Cow, ToOwned}; #[cfg(not(no_global_oom_handling))] @@ -313,13 +313,17 @@ fn rcbox_layout_for_value_layout(layout: Layout) -> Layout { #[cfg_attr(not(test), rustc_diagnostic_item = "Rc")] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_insignificant_dtor] -pub struct Rc { +pub struct Rc< + T: ?Sized, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { ptr: NonNull>, phantom: PhantomData>, + alloc: A, } #[stable(feature = "rust1", since = "1.0.0")] -impl !Send for Rc {} +impl !Send for Rc {} // Note that this negative impl isn't strictly necessary for correctness, // as `Rc` transitively contains a `Cell`, which is itself `!Sync`. @@ -327,20 +331,32 @@ impl !Send for Rc {} // having an explicit negative impl is nice for documentation purposes // and results in nicer error messages. #[stable(feature = "rust1", since = "1.0.0")] -impl !Sync for Rc {} +impl !Sync for Rc {} #[stable(feature = "catch_unwind", since = "1.9.0")] -impl UnwindSafe for Rc {} +impl UnwindSafe for Rc {} #[stable(feature = "rc_ref_unwind_safe", since = "1.58.0")] -impl RefUnwindSafe for Rc {} +impl RefUnwindSafe for Rc {} #[unstable(feature = "coerce_unsized", issue = "18598")] -impl, U: ?Sized> CoerceUnsized> for Rc {} +impl, U: ?Sized, A: Allocator> CoerceUnsized> for Rc {} #[unstable(feature = "dispatch_from_dyn", issue = "none")] impl, U: ?Sized> DispatchFromDyn> for Rc {} impl Rc { + #[inline] + unsafe fn from_inner(ptr: NonNull>) -> Self { + unsafe { Self::from_inner_in(ptr, Global) } + } + + #[inline] + unsafe fn from_ptr(ptr: *mut RcBox) -> Self { + unsafe { Self::from_inner(NonNull::new_unchecked(ptr)) } + } +} + +impl Rc { #[inline(always)] fn inner(&self) -> &RcBox { // This unsafety is ok because while this Rc is alive we're guaranteed @@ -348,12 +364,14 @@ impl Rc { unsafe { self.ptr.as_ref() } } - unsafe fn from_inner(ptr: NonNull>) -> Self { - Self { ptr, phantom: PhantomData } + #[inline] + unsafe fn from_inner_in(ptr: NonNull>, alloc: A) -> Self { + Self { ptr, phantom: PhantomData, alloc } } - unsafe fn from_ptr(ptr: *mut RcBox) -> Self { - unsafe { Self::from_inner(NonNull::new_unchecked(ptr)) } + #[inline] + unsafe fn from_ptr_in(ptr: *mut RcBox, alloc: A) -> Self { + unsafe { Self::from_inner_in(NonNull::new_unchecked(ptr), alloc) } } } @@ -450,7 +468,7 @@ impl Rc { let init_ptr: NonNull> = uninit_ptr.cast(); - let weak = Weak { ptr: init_ptr }; + let weak = Weak { ptr: init_ptr, alloc: Global }; // It's important we don't give up ownership of the weak pointer, or // else the memory might be freed by the time `data_fn` returns. If @@ -504,7 +522,7 @@ impl Rc { Rc::from_ptr(Rc::allocate_for_layout( Layout::new::(), |layout| Global.allocate(layout), - |mem| mem as *mut RcBox>, + <*mut u8>::cast, )) } } @@ -537,7 +555,7 @@ impl Rc { Rc::from_ptr(Rc::allocate_for_layout( Layout::new::(), |layout| Global.allocate_zeroed(layout), - |mem| mem as *mut RcBox>, + <*mut u8>::cast, )) } } @@ -594,7 +612,7 @@ impl Rc { Ok(Rc::from_ptr(Rc::try_allocate_for_layout( Layout::new::(), |layout| Global.allocate(layout), - |mem| mem as *mut RcBox>, + <*mut u8>::cast, )?)) } } @@ -627,7 +645,7 @@ impl Rc { Ok(Rc::from_ptr(Rc::try_allocate_for_layout( Layout::new::(), |layout| Global.allocate_zeroed(layout), - |mem| mem as *mut RcBox>, + <*mut u8>::cast, )?)) } } @@ -639,6 +657,235 @@ impl Rc { pub fn pin(value: T) -> Pin> { unsafe { Pin::new_unchecked(Rc::new(value)) } } +} + +impl Rc { + /// Returns a reference to the underlying allocator. + /// + /// Note: this is an associated function, which means that you have + /// to call it as `Rc::allocator(&r)` instead of `r.allocator()`. This + /// is so that there is no conflict with a method on the inner type. + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn allocator(this: &Self) -> &A { + &this.alloc + } + /// Constructs a new `Rc` in the provided allocator. + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api)] + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let five = Rc::new_in(5, System); + /// ``` + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn new_in(value: T, alloc: A) -> Rc { + // NOTE: Prefer match over unwrap_or_else since closure sometimes not inlineable. + // That would make code size bigger. + match Self::try_new_in(value, alloc) { + Ok(m) => m, + Err(_) => handle_alloc_error(Layout::new::>()), + } + } + + /// Constructs a new `Rc` with uninitialized contents in the provided allocator. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(get_mut_unchecked)] + /// #![feature(allocator_api)] + /// + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let mut five = Rc::::new_uninit_in(System); + /// + /// let five = unsafe { + /// // Deferred initialization: + /// Rc::get_mut_unchecked(&mut five).as_mut_ptr().write(5); + /// + /// five.assume_init() + /// }; + /// + /// assert_eq!(*five, 5) + /// ``` + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_uninit_in(alloc: A) -> Rc, A> { + unsafe { + Rc::from_ptr_in( + Rc::allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate(layout), + <*mut u8>::cast, + ), + alloc, + ) + } + } + + /// Constructs a new `Rc` with uninitialized contents, with the memory + /// being filled with `0` bytes, in the provided allocator. + /// + /// See [`MaybeUninit::zeroed`][zeroed] for examples of correct and + /// incorrect usage of this method. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(allocator_api)] + /// + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let zero = Rc::::new_zeroed_in(System); + /// let zero = unsafe { zero.assume_init() }; + /// + /// assert_eq!(*zero, 0) + /// ``` + /// + /// [zeroed]: mem::MaybeUninit::zeroed + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_zeroed_in(alloc: A) -> Rc, A> { + unsafe { + Rc::from_ptr_in( + Rc::allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate_zeroed(layout), + <*mut u8>::cast, + ), + alloc, + ) + } + } + + /// Constructs a new `Rc` in the provided allocator, returning an error if the allocation + /// fails + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api)] + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let five = Rc::try_new_in(5, System); + /// # Ok::<(), std::alloc::AllocError>(()) + /// ``` + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn try_new_in(value: T, alloc: A) -> Result { + // There is an implicit weak pointer owned by all the strong + // pointers, which ensures that the weak destructor never frees + // the allocation while the strong destructor is running, even + // if the weak pointer is stored inside the strong one. + let (ptr, alloc) = Box::into_unique(Box::try_new_in( + RcBox { strong: Cell::new(1), weak: Cell::new(1), value }, + alloc, + )?); + Ok(unsafe { Self::from_inner_in(ptr.into(), alloc) }) + } + + /// Constructs a new `Rc` with uninitialized contents, in the provided allocator, returning an + /// error if the allocation fails + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api, new_uninit)] + /// #![feature(get_mut_unchecked)] + /// + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let mut five = Rc::::try_new_uninit_in(System)?; + /// + /// let five = unsafe { + /// // Deferred initialization: + /// Rc::get_mut_unchecked(&mut five).as_mut_ptr().write(5); + /// + /// five.assume_init() + /// }; + /// + /// assert_eq!(*five, 5); + /// # Ok::<(), std::alloc::AllocError>(()) + /// ``` + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn try_new_uninit_in(alloc: A) -> Result, A>, AllocError> { + unsafe { + Ok(Rc::from_ptr_in( + Rc::try_allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate(layout), + <*mut u8>::cast, + )?, + alloc, + )) + } + } + + /// Constructs a new `Rc` with uninitialized contents, with the memory + /// being filled with `0` bytes, in the provided allocator, returning an error if the allocation + /// fails + /// + /// See [`MaybeUninit::zeroed`][zeroed] for examples of correct and + /// incorrect usage of this method. + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api, new_uninit)] + /// + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let zero = Rc::::try_new_zeroed_in(System)?; + /// let zero = unsafe { zero.assume_init() }; + /// + /// assert_eq!(*zero, 0); + /// # Ok::<(), std::alloc::AllocError>(()) + /// ``` + /// + /// [zeroed]: mem::MaybeUninit::zeroed + #[unstable(feature = "allocator_api", issue = "32838")] + //#[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn try_new_zeroed_in(alloc: A) -> Result, A>, AllocError> { + unsafe { + Ok(Rc::from_ptr_in( + Rc::try_allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate_zeroed(layout), + <*mut u8>::cast, + )?, + alloc, + )) + } + } + + /// Constructs a new `Pin>` in the provided allocator. If `T` does not implement `Unpin`, then + /// `value` will be pinned in memory and unable to be moved. + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn pin_in(value: T, alloc: A) -> Pin { + unsafe { Pin::new_unchecked(Rc::new_in(value, alloc)) } + } /// Returns the inner value, if the `Rc` has exactly one strong reference. /// @@ -665,13 +912,14 @@ impl Rc { if Rc::strong_count(&this) == 1 { unsafe { let val = ptr::read(&*this); // copy the contained object + let alloc = ptr::read(&this.alloc); // copy the allocator // Indicate to Weaks that they can't be promoted by decrementing // the strong count, and then remove the implicit "strong weak" // pointer while also handling drop logic by just crafting a // fake Weak. this.inner().dec_strong(); - let _weak = Weak { ptr: this.ptr }; + let _weak = Weak { ptr: this.ptr, alloc }; forget(this); Ok(val) } @@ -758,7 +1006,7 @@ impl Rc<[T]> { Layout::array::(len).unwrap(), |layout| Global.allocate_zeroed(layout), |mem| { - ptr::slice_from_raw_parts_mut(mem as *mut T, len) + ptr::slice_from_raw_parts_mut(mem.cast::(), len) as *mut RcBox<[mem::MaybeUninit]> }, )) @@ -766,7 +1014,84 @@ impl Rc<[T]> { } } -impl Rc> { +impl Rc<[T], A> { + /// Constructs a new reference-counted slice with uninitialized contents. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(get_mut_unchecked)] + /// #![feature(allocator_api)] + /// + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let mut values = Rc::<[u32], _>::new_uninit_slice_in(3, System); + /// + /// let values = unsafe { + /// // Deferred initialization: + /// Rc::get_mut_unchecked(&mut values)[0].as_mut_ptr().write(1); + /// Rc::get_mut_unchecked(&mut values)[1].as_mut_ptr().write(2); + /// Rc::get_mut_unchecked(&mut values)[2].as_mut_ptr().write(3); + /// + /// values.assume_init() + /// }; + /// + /// assert_eq!(*values, [1, 2, 3]) + /// ``` + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_uninit_slice_in(len: usize, alloc: A) -> Rc<[mem::MaybeUninit], A> { + unsafe { Rc::from_ptr_in(Rc::allocate_for_slice_in(len, &alloc), alloc) } + } + + /// Constructs a new reference-counted slice with uninitialized contents, with the memory being + /// filled with `0` bytes. + /// + /// See [`MaybeUninit::zeroed`][zeroed] for examples of correct and + /// incorrect usage of this method. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(allocator_api)] + /// + /// use std::rc::Rc; + /// use std::alloc::System; + /// + /// let values = Rc::<[u32], _>::new_zeroed_slice_in(3, System); + /// let values = unsafe { values.assume_init() }; + /// + /// assert_eq!(*values, [0, 0, 0]) + /// ``` + /// + /// [zeroed]: mem::MaybeUninit::zeroed + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_zeroed_slice_in(len: usize, alloc: A) -> Rc<[mem::MaybeUninit], A> { + unsafe { + Rc::from_ptr_in( + Rc::allocate_for_layout( + Layout::array::(len).unwrap(), + |layout| alloc.allocate_zeroed(layout), + |mem| { + ptr::slice_from_raw_parts_mut(mem.cast::(), len) + as *mut RcBox<[mem::MaybeUninit]> + }, + ), + alloc, + ) + } + } +} + +impl Rc, A> { /// Converts to `Rc`. /// /// # Safety @@ -796,54 +1121,174 @@ impl Rc> { /// /// assert_eq!(*five, 5) /// ``` - #[unstable(feature = "new_uninit", issue = "63291")] + #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub unsafe fn assume_init(self) -> Rc + where + A: Clone, + { + let md_self = mem::ManuallyDrop::new(self); + unsafe { Rc::from_inner_in(md_self.ptr.cast(), md_self.alloc.clone()) } + } +} + +impl Rc<[mem::MaybeUninit], A> { + /// Converts to `Rc<[T]>`. + /// + /// # Safety + /// + /// As with [`MaybeUninit::assume_init`], + /// it is up to the caller to guarantee that the inner value + /// really is in an initialized state. + /// Calling this when the content is not yet fully initialized + /// causes immediate undefined behavior. + /// + /// [`MaybeUninit::assume_init`]: mem::MaybeUninit::assume_init + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(get_mut_unchecked)] + /// + /// use std::rc::Rc; + /// + /// let mut values = Rc::<[u32]>::new_uninit_slice(3); + /// + /// // Deferred initialization: + /// let data = Rc::get_mut(&mut values).unwrap(); + /// data[0].write(1); + /// data[1].write(2); + /// data[2].write(3); + /// + /// let values = unsafe { values.assume_init() }; + /// + /// assert_eq!(*values, [1, 2, 3]) + /// ``` + #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub unsafe fn assume_init(self) -> Rc<[T], A> + where + A: Clone, + { + let md_self = mem::ManuallyDrop::new(self); + unsafe { Rc::from_ptr_in(md_self.ptr.as_ptr() as _, md_self.alloc.clone()) } + } +} + +impl Rc { + /// Constructs an `Rc` from a raw pointer. + /// + /// The raw pointer must have been previously returned by a call to + /// [`Rc::into_raw`][into_raw] where `U` must have the same size + /// and alignment as `T`. This is trivially true if `U` is `T`. + /// Note that if `U` is not `T` but has the same size and alignment, this is + /// basically like transmuting references of different types. See + /// [`mem::transmute`][transmute] for more information on what + /// restrictions apply in this case. + /// + /// The raw pointer must point to a block of memory allocated by the global allocator + /// + /// The user of `from_raw` has to make sure a specific value of `T` is only + /// dropped once. + /// + /// This function is unsafe because improper use may lead to memory unsafety, + /// even if the returned `Rc` is never accessed. + /// + /// [into_raw]: Rc::into_raw + /// [transmute]: core::mem::transmute + /// + /// # Examples + /// + /// ``` + /// use std::rc::Rc; + /// + /// let x = Rc::new("hello".to_owned()); + /// let x_ptr = Rc::into_raw(x); + /// + /// unsafe { + /// // Convert back to an `Rc` to prevent leak. + /// let x = Rc::from_raw(x_ptr); + /// assert_eq!(&*x, "hello"); + /// + /// // Further calls to `Rc::from_raw(x_ptr)` would be memory-unsafe. + /// } + /// + /// // The memory was freed when `x` went out of scope above, so `x_ptr` is now dangling! + /// ``` + #[inline] + #[stable(feature = "rc_raw", since = "1.17.0")] + pub unsafe fn from_raw(ptr: *const T) -> Self { + unsafe { Self::from_raw_in(ptr, Global) } + } + + /// Increments the strong reference count on the `Rc` associated with the + /// provided pointer by one. + /// + /// # Safety + /// + /// The pointer must have been obtained through `Rc::into_raw`, the + /// associated `Rc` instance must be valid (i.e. the strong count must be at + /// least 1) for the duration of this method, and `ptr` must point to a block of memory + /// allocated by the global allocator. + /// + /// # Examples + /// + /// ``` + /// use std::rc::Rc; + /// + /// let five = Rc::new(5); + /// + /// unsafe { + /// let ptr = Rc::into_raw(five); + /// Rc::increment_strong_count(ptr); + /// + /// let five = Rc::from_raw(ptr); + /// assert_eq!(2, Rc::strong_count(&five)); + /// } + /// ``` #[inline] - pub unsafe fn assume_init(self) -> Rc { - unsafe { Rc::from_inner(mem::ManuallyDrop::new(self).ptr.cast()) } + #[stable(feature = "rc_mutate_strong_count", since = "1.53.0")] + pub unsafe fn increment_strong_count(ptr: *const T) { + unsafe { Self::increment_strong_count_in(ptr, Global) } } -} -impl Rc<[mem::MaybeUninit]> { - /// Converts to `Rc<[T]>`. + /// Decrements the strong reference count on the `Rc` associated with the + /// provided pointer by one. /// /// # Safety /// - /// As with [`MaybeUninit::assume_init`], - /// it is up to the caller to guarantee that the inner value - /// really is in an initialized state. - /// Calling this when the content is not yet fully initialized - /// causes immediate undefined behavior. - /// - /// [`MaybeUninit::assume_init`]: mem::MaybeUninit::assume_init + /// The pointer must have been obtained through `Rc::into_raw`, the + /// associated `Rc` instance must be valid (i.e. the strong count must be at + /// least 1) when invoking this method, and `ptr` must point to a block of memory + /// allocated by the global allocator. This method can be used to release the final `Rc` and + /// backing storage, but **should not** be called after the final `Rc` has been released. /// /// # Examples /// /// ``` - /// #![feature(new_uninit)] - /// #![feature(get_mut_unchecked)] - /// /// use std::rc::Rc; /// - /// let mut values = Rc::<[u32]>::new_uninit_slice(3); - /// - /// // Deferred initialization: - /// let data = Rc::get_mut(&mut values).unwrap(); - /// data[0].write(1); - /// data[1].write(2); - /// data[2].write(3); + /// let five = Rc::new(5); /// - /// let values = unsafe { values.assume_init() }; + /// unsafe { + /// let ptr = Rc::into_raw(five); + /// Rc::increment_strong_count(ptr); /// - /// assert_eq!(*values, [1, 2, 3]) + /// let five = Rc::from_raw(ptr); + /// assert_eq!(2, Rc::strong_count(&five)); + /// Rc::decrement_strong_count(ptr); + /// assert_eq!(1, Rc::strong_count(&five)); + /// } /// ``` - #[unstable(feature = "new_uninit", issue = "63291")] #[inline] - pub unsafe fn assume_init(self) -> Rc<[T]> { - unsafe { Rc::from_ptr(mem::ManuallyDrop::new(self).ptr.as_ptr() as _) } + #[stable(feature = "rc_mutate_strong_count", since = "1.53.0")] + pub unsafe fn decrement_strong_count(ptr: *const T) { + unsafe { Self::decrement_strong_count_in(ptr, Global) } } } -impl Rc { +impl Rc { /// Consumes the `Rc`, returning the wrapped pointer. /// /// To avoid a memory leak the pointer must be converted back to an `Rc` using @@ -891,16 +1336,18 @@ impl Rc { unsafe { ptr::addr_of_mut!((*ptr).value) } } - /// Constructs an `Rc` from a raw pointer. + /// Constructs an `Rc` from a raw pointer in the provided allocator. /// /// The raw pointer must have been previously returned by a call to - /// [`Rc::into_raw`][into_raw] where `U` must have the same size + /// [`Rc::into_raw`][into_raw] where `U` must have the same size /// and alignment as `T`. This is trivially true if `U` is `T`. /// Note that if `U` is not `T` but has the same size and alignment, this is /// basically like transmuting references of different types. See /// [`mem::transmute`] for more information on what /// restrictions apply in this case. /// + /// The raw pointer must point to a block of memory allocated by `alloc` + /// /// The user of `from_raw` has to make sure a specific value of `T` is only /// dropped once. /// @@ -912,14 +1359,17 @@ impl Rc { /// # Examples /// /// ``` + /// #![feature(allocator_api)] + /// /// use std::rc::Rc; + /// use std::alloc::System; /// - /// let x = Rc::new("hello".to_owned()); + /// let x = Rc::new_in("hello".to_owned(), System); /// let x_ptr = Rc::into_raw(x); /// /// unsafe { /// // Convert back to an `Rc` to prevent leak. - /// let x = Rc::from_raw(x_ptr); + /// let x = Rc::from_raw_in(x_ptr, System); /// assert_eq!(&*x, "hello"); /// /// // Further calls to `Rc::from_raw(x_ptr)` would be memory-unsafe. @@ -927,14 +1377,14 @@ impl Rc { /// /// // The memory was freed when `x` went out of scope above, so `x_ptr` is now dangling! /// ``` - #[stable(feature = "rc_raw", since = "1.17.0")] - pub unsafe fn from_raw(ptr: *const T) -> Self { + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn from_raw_in(ptr: *const T, alloc: A) -> Self { let offset = unsafe { data_offset(ptr) }; // Reverse the offset to find the original RcBox. let rc_ptr = unsafe { ptr.byte_sub(offset) as *mut RcBox }; - unsafe { Self::from_ptr(rc_ptr) } + unsafe { Self::from_ptr_in(rc_ptr, alloc) } } /// Creates a new [`Weak`] pointer to this allocation. @@ -951,11 +1401,14 @@ impl Rc { #[must_use = "this returns a new `Weak` pointer, \ without modifying the original `Rc`"] #[stable(feature = "rc_weak", since = "1.4.0")] - pub fn downgrade(this: &Self) -> Weak { + pub fn downgrade(this: &Self) -> Weak + where + A: Clone, + { this.inner().inc_weak(); // Make sure we do not create a dangling Weak debug_assert!(!is_dangling(this.ptr.as_ptr())); - Weak { ptr: this.ptr } + Weak { ptr: this.ptr, alloc: this.alloc.clone() } } /// Gets the number of [`Weak`] pointers to this allocation. @@ -999,30 +1452,37 @@ impl Rc { /// /// # Safety /// - /// The pointer must have been obtained through `Rc::into_raw`, and the + /// The pointer must have been obtained through `Rc::into_raw`, the /// associated `Rc` instance must be valid (i.e. the strong count must be at - /// least 1) for the duration of this method. + /// least 1) for the duration of this method, and `ptr` must point to a block of memory + /// allocated by `alloc` /// /// # Examples /// /// ``` + /// #![feature(allocator_api)] + /// /// use std::rc::Rc; + /// use std::alloc::System; /// - /// let five = Rc::new(5); + /// let five = Rc::new_in(5, System); /// /// unsafe { /// let ptr = Rc::into_raw(five); - /// Rc::increment_strong_count(ptr); + /// Rc::increment_strong_count_in(ptr, System); /// - /// let five = Rc::from_raw(ptr); + /// let five = Rc::from_raw_in(ptr, System); /// assert_eq!(2, Rc::strong_count(&five)); /// } /// ``` #[inline] - #[stable(feature = "rc_mutate_strong_count", since = "1.53.0")] - pub unsafe fn increment_strong_count(ptr: *const T) { + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn increment_strong_count_in(ptr: *const T, alloc: A) + where + A: Clone, + { // Retain Rc, but don't touch refcount by wrapping in ManuallyDrop - let rc = unsafe { mem::ManuallyDrop::new(Rc::::from_raw(ptr)) }; + let rc = unsafe { mem::ManuallyDrop::new(Rc::::from_raw_in(ptr, alloc)) }; // Now increase refcount, but don't drop new refcount either let _rc_clone: mem::ManuallyDrop<_> = rc.clone(); } @@ -1032,33 +1492,36 @@ impl Rc { /// /// # Safety /// - /// The pointer must have been obtained through `Rc::into_raw`, and the + /// The pointer must have been obtained through `Rc::into_raw`, the /// associated `Rc` instance must be valid (i.e. the strong count must be at - /// least 1) when invoking this method. This method can be used to release - /// the final `Rc` and backing storage, but **should not** be called after - /// the final `Rc` has been released. + /// least 1) when invoking this method, and `ptr` must point to a block of memory + /// allocated by `alloc`. This method can be used to release the final `Rc` and backing storage, + /// but **should not** be called after the final `Rc` has been released. /// /// # Examples /// /// ``` + /// #![feature(allocator_api)] + /// /// use std::rc::Rc; + /// use std::alloc::System; /// - /// let five = Rc::new(5); + /// let five = Rc::new_in(5, System); /// /// unsafe { /// let ptr = Rc::into_raw(five); - /// Rc::increment_strong_count(ptr); + /// Rc::increment_strong_count_in(ptr, System); /// - /// let five = Rc::from_raw(ptr); + /// let five = Rc::from_raw_in(ptr, System); /// assert_eq!(2, Rc::strong_count(&five)); - /// Rc::decrement_strong_count(ptr); + /// Rc::decrement_strong_count_in(ptr, System); /// assert_eq!(1, Rc::strong_count(&five)); /// } /// ``` #[inline] - #[stable(feature = "rc_mutate_strong_count", since = "1.53.0")] - pub unsafe fn decrement_strong_count(ptr: *const T) { - unsafe { drop(Rc::from_raw(ptr)) }; + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn decrement_strong_count_in(ptr: *const T, alloc: A) { + unsafe { drop(Rc::from_raw_in(ptr, alloc)) }; } /// Returns `true` if there are no other `Rc` or [`Weak`] pointers to @@ -1169,7 +1632,7 @@ impl Rc { #[inline] #[stable(feature = "ptr_eq", since = "1.17.0")] /// Returns `true` if the two `Rc`s point to the same allocation in a vein similar to - /// [`ptr::eq`]. See [that function][`ptr::eq`] for caveats when comparing `dyn Trait` pointers. + /// [`ptr::eq`]. This function ignores the metadata of `dyn Trait` pointers. /// /// # Examples /// @@ -1184,11 +1647,11 @@ impl Rc { /// assert!(!Rc::ptr_eq(&five, &other_five)); /// ``` pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.ptr.as_ptr() == other.ptr.as_ptr() + this.ptr.as_ptr() as *const () == other.ptr.as_ptr() as *const () } } -impl Rc { +impl Rc { /// Makes a mutable reference into the given `Rc`. /// /// If there are other `Rc` pointers to the same allocation, then `make_mut` will @@ -1246,7 +1709,7 @@ impl Rc { if Rc::strong_count(this) != 1 { // Gotta clone the data, there are other Rcs. // Pre-allocate memory to allow writing the cloned value directly. - let mut rc = Self::new_uninit(); + let mut rc = Self::new_uninit_in(this.alloc.clone()); unsafe { let data = Rc::get_mut_unchecked(&mut rc); (**this).write_clone_into_raw(data.as_mut_ptr()); @@ -1254,7 +1717,7 @@ impl Rc { } } else if Rc::weak_count(this) != 0 { // Can just steal the data, all that's left is Weaks - let mut rc = Self::new_uninit(); + let mut rc = Self::new_uninit_in(this.alloc.clone()); unsafe { let data = Rc::get_mut_unchecked(&mut rc); data.as_mut_ptr().copy_from_nonoverlapping(&**this, 1); @@ -1310,7 +1773,7 @@ impl Rc { } } -impl Rc { +impl Rc { /// Attempt to downcast the `Rc` to a concrete type. /// /// # Examples @@ -1331,12 +1794,13 @@ impl Rc { /// ``` #[inline] #[stable(feature = "rc_downcast", since = "1.29.0")] - pub fn downcast(self) -> Result, Rc> { + pub fn downcast(self) -> Result, Self> { if (*self).is::() { unsafe { let ptr = self.ptr.cast::>(); + let alloc = self.alloc.clone(); forget(self); - Ok(Rc::from_inner(ptr)) + Ok(Rc::from_inner_in(ptr, alloc)) } } else { Err(self) @@ -1371,11 +1835,12 @@ impl Rc { /// [`downcast`]: Self::downcast #[inline] #[unstable(feature = "downcast_unchecked", issue = "90850")] - pub unsafe fn downcast_unchecked(self) -> Rc { + pub unsafe fn downcast_unchecked(self) -> Rc { unsafe { let ptr = self.ptr.cast::>(); + let alloc = self.alloc.clone(); mem::forget(self); - Rc::from_inner(ptr) + Rc::from_inner_in(ptr, alloc) } } } @@ -1427,40 +1892,41 @@ impl Rc { Ok(inner) } +} +impl Rc { /// Allocates an `RcBox` with sufficient space for an unsized inner value #[cfg(not(no_global_oom_handling))] - unsafe fn allocate_for_ptr(ptr: *const T) -> *mut RcBox { + unsafe fn allocate_for_ptr_in(ptr: *const T, alloc: &A) -> *mut RcBox { // Allocate for the `RcBox` using the given value. unsafe { - Self::allocate_for_layout( + Rc::::allocate_for_layout( Layout::for_value(&*ptr), - |layout| Global.allocate(layout), + |layout| alloc.allocate(layout), |mem| mem.with_metadata_of(ptr as *const RcBox), ) } } #[cfg(not(no_global_oom_handling))] - fn from_box(v: Box) -> Rc { + fn from_box_in(src: Box) -> Rc { unsafe { - let (box_unique, alloc) = Box::into_unique(v); - let bptr = box_unique.as_ptr(); - - let value_size = size_of_val(&*bptr); - let ptr = Self::allocate_for_ptr(bptr); + let value_size = size_of_val(&*src); + let ptr = Self::allocate_for_ptr_in(&*src, Box::allocator(&src)); // Copy value as bytes ptr::copy_nonoverlapping( - bptr as *const T as *const u8, + &*src as *const T as *const u8, &mut (*ptr).value as *mut _ as *mut u8, value_size, ); // Free the allocation without dropping its contents - box_free(box_unique, alloc); + let (bptr, alloc) = Box::into_raw_with_allocator(src); + let src = Box::from_raw(bptr as *mut mem::ManuallyDrop); + drop(src); - Self::from_ptr(ptr) + Self::from_ptr_in(ptr, alloc) } } } @@ -1473,7 +1939,7 @@ impl Rc<[T]> { Self::allocate_for_layout( Layout::array::(len).unwrap(), |layout| Global.allocate(layout), - |mem| ptr::slice_from_raw_parts_mut(mem as *mut T, len) as *mut RcBox<[T]>, + |mem| ptr::slice_from_raw_parts_mut(mem.cast::(), len) as *mut RcBox<[T]>, ) } } @@ -1540,6 +2006,21 @@ impl Rc<[T]> { } } +impl Rc<[T], A> { + /// Allocates an `RcBox<[T]>` with the given length. + #[inline] + #[cfg(not(no_global_oom_handling))] + unsafe fn allocate_for_slice_in(len: usize, alloc: &A) -> *mut RcBox<[T]> { + unsafe { + Rc::<[T]>::allocate_for_layout( + Layout::array::(len).unwrap(), + |layout| alloc.allocate(layout), + |mem| ptr::slice_from_raw_parts_mut(mem.cast::(), len) as *mut RcBox<[T]>, + ) + } + } +} + /// Specialization trait used for `From<&[T]>`. trait RcFromSlice { fn from_slice(slice: &[T]) -> Self; @@ -1562,7 +2043,7 @@ impl RcFromSlice for Rc<[T]> { } #[stable(feature = "rust1", since = "1.0.0")] -impl Deref for Rc { +impl Deref for Rc { type Target = T; #[inline(always)] @@ -1575,7 +2056,7 @@ impl Deref for Rc { impl Receiver for Rc {} #[stable(feature = "rust1", since = "1.0.0")] -unsafe impl<#[may_dangle] T: ?Sized> Drop for Rc { +unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Rc { /// Drops the `Rc`. /// /// This will decrement the strong reference count. If the strong reference @@ -1613,7 +2094,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Rc { self.inner().dec_weak(); if self.inner().weak() == 0 { - Global.deallocate(self.ptr.cast(), Layout::for_value(self.ptr.as_ref())); + self.alloc.deallocate(self.ptr.cast(), Layout::for_value(self.ptr.as_ref())); } } } @@ -1621,7 +2102,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Rc { } #[stable(feature = "rust1", since = "1.0.0")] -impl Clone for Rc { +impl Clone for Rc { /// Makes a clone of the `Rc` pointer. /// /// This creates another pointer to the same allocation, increasing the @@ -1637,10 +2118,10 @@ impl Clone for Rc { /// let _ = Rc::clone(&five); /// ``` #[inline] - fn clone(&self) -> Rc { + fn clone(&self) -> Self { unsafe { self.inner().inc_strong(); - Self::from_inner(self.ptr) + Self::from_inner_in(self.ptr, self.alloc.clone()) } } } @@ -1665,20 +2146,20 @@ impl Default for Rc { } #[stable(feature = "rust1", since = "1.0.0")] -trait RcEqIdent { - fn eq(&self, other: &Rc) -> bool; - fn ne(&self, other: &Rc) -> bool; +trait RcEqIdent { + fn eq(&self, other: &Rc) -> bool; + fn ne(&self, other: &Rc) -> bool; } #[stable(feature = "rust1", since = "1.0.0")] -impl RcEqIdent for Rc { +impl RcEqIdent for Rc { #[inline] - default fn eq(&self, other: &Rc) -> bool { + default fn eq(&self, other: &Rc) -> bool { **self == **other } #[inline] - default fn ne(&self, other: &Rc) -> bool { + default fn ne(&self, other: &Rc) -> bool { **self != **other } } @@ -1697,20 +2178,20 @@ impl MarkerEq for T {} /// /// We can only do this when `T: Eq` as a `PartialEq` might be deliberately irreflexive. #[stable(feature = "rust1", since = "1.0.0")] -impl RcEqIdent for Rc { +impl RcEqIdent for Rc { #[inline] - fn eq(&self, other: &Rc) -> bool { + fn eq(&self, other: &Rc) -> bool { Rc::ptr_eq(self, other) || **self == **other } #[inline] - fn ne(&self, other: &Rc) -> bool { + fn ne(&self, other: &Rc) -> bool { !Rc::ptr_eq(self, other) && **self != **other } } #[stable(feature = "rust1", since = "1.0.0")] -impl PartialEq for Rc { +impl PartialEq for Rc { /// Equality for two `Rc`s. /// /// Two `Rc`s are equal if their inner values are equal, even if they are @@ -1730,7 +2211,7 @@ impl PartialEq for Rc { /// assert!(five == Rc::new(5)); /// ``` #[inline] - fn eq(&self, other: &Rc) -> bool { + fn eq(&self, other: &Rc) -> bool { RcEqIdent::eq(self, other) } @@ -1752,16 +2233,16 @@ impl PartialEq for Rc { /// assert!(five != Rc::new(6)); /// ``` #[inline] - fn ne(&self, other: &Rc) -> bool { + fn ne(&self, other: &Rc) -> bool { RcEqIdent::ne(self, other) } } #[stable(feature = "rust1", since = "1.0.0")] -impl Eq for Rc {} +impl Eq for Rc {} #[stable(feature = "rust1", since = "1.0.0")] -impl PartialOrd for Rc { +impl PartialOrd for Rc { /// Partial comparison for two `Rc`s. /// /// The two are compared by calling `partial_cmp()` on their inner values. @@ -1777,7 +2258,7 @@ impl PartialOrd for Rc { /// assert_eq!(Some(Ordering::Less), five.partial_cmp(&Rc::new(6))); /// ``` #[inline(always)] - fn partial_cmp(&self, other: &Rc) -> Option { + fn partial_cmp(&self, other: &Rc) -> Option { (**self).partial_cmp(&**other) } @@ -1795,7 +2276,7 @@ impl PartialOrd for Rc { /// assert!(five < Rc::new(6)); /// ``` #[inline(always)] - fn lt(&self, other: &Rc) -> bool { + fn lt(&self, other: &Rc) -> bool { **self < **other } @@ -1813,7 +2294,7 @@ impl PartialOrd for Rc { /// assert!(five <= Rc::new(5)); /// ``` #[inline(always)] - fn le(&self, other: &Rc) -> bool { + fn le(&self, other: &Rc) -> bool { **self <= **other } @@ -1831,7 +2312,7 @@ impl PartialOrd for Rc { /// assert!(five > Rc::new(4)); /// ``` #[inline(always)] - fn gt(&self, other: &Rc) -> bool { + fn gt(&self, other: &Rc) -> bool { **self > **other } @@ -1849,13 +2330,13 @@ impl PartialOrd for Rc { /// assert!(five >= Rc::new(5)); /// ``` #[inline(always)] - fn ge(&self, other: &Rc) -> bool { + fn ge(&self, other: &Rc) -> bool { **self >= **other } } #[stable(feature = "rust1", since = "1.0.0")] -impl Ord for Rc { +impl Ord for Rc { /// Comparison for two `Rc`s. /// /// The two are compared by calling `cmp()` on their inner values. @@ -1871,34 +2352,34 @@ impl Ord for Rc { /// assert_eq!(Ordering::Less, five.cmp(&Rc::new(6))); /// ``` #[inline] - fn cmp(&self, other: &Rc) -> Ordering { + fn cmp(&self, other: &Rc) -> Ordering { (**self).cmp(&**other) } } #[stable(feature = "rust1", since = "1.0.0")] -impl Hash for Rc { +impl Hash for Rc { fn hash(&self, state: &mut H) { (**self).hash(state); } } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Display for Rc { +impl fmt::Display for Rc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&**self, f) } } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Debug for Rc { +impl fmt::Debug for Rc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f) } } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Pointer for Rc { +impl fmt::Pointer for Rc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Pointer::fmt(&(&**self as *const T), f) } @@ -1984,7 +2465,7 @@ impl From for Rc { #[cfg(not(no_global_oom_handling))] #[stable(feature = "shared_from_slice", since = "1.21.0")] -impl From> for Rc { +impl From> for Rc { /// Move a boxed object to a new, reference counted, allocation. /// /// # Example @@ -1996,31 +2477,37 @@ impl From> for Rc { /// assert_eq!(1, *shared); /// ``` #[inline] - fn from(v: Box) -> Rc { - Rc::from_box(v) + fn from(v: Box) -> Rc { + Rc::from_box_in(v) } } #[cfg(not(no_global_oom_handling))] #[stable(feature = "shared_from_slice", since = "1.21.0")] -impl From> for Rc<[T]> { +impl From> for Rc<[T], A> { /// Allocate a reference-counted slice and move `v`'s items into it. /// /// # Example /// /// ``` /// # use std::rc::Rc; - /// let original: Box> = Box::new(vec![1, 2, 3]); - /// let shared: Rc> = Rc::from(original); - /// assert_eq!(vec![1, 2, 3], *shared); + /// let unique: Vec = vec![1, 2, 3]; + /// let shared: Rc<[i32]> = Rc::from(unique); + /// assert_eq!(&[1, 2, 3], &shared[..]); /// ``` #[inline] - fn from(mut v: Vec) -> Rc<[T]> { + fn from(v: Vec) -> Rc<[T], A> { unsafe { - let rc = Rc::copy_from_slice(&v); - // Allow the Vec to free its memory, but not destroy its contents - v.set_len(0); - rc + let (vec_ptr, len, cap, alloc) = v.into_raw_parts_with_alloc(); + + let rc_ptr = Self::allocate_for_slice_in(len, &alloc); + ptr::copy_nonoverlapping(vec_ptr, &mut (*rc_ptr).value as *mut [T] as *mut T, len); + + // Create a `Vec` with length 0, to deallocate the buffer + // without dropping its contents or the allocator + let _ = Vec::from_raw_parts_in(vec_ptr, 0, cap, &alloc); + + Self::from_ptr_in(rc_ptr, alloc) } } } @@ -2191,7 +2678,10 @@ impl> ToRcSlice for I { /// /// [`upgrade`]: Weak::upgrade #[stable(feature = "rc_weak", since = "1.4.0")] -pub struct Weak { +pub struct Weak< + T: ?Sized, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { // This is a `NonNull` to allow optimizing the size of this type in enums, // but it is not necessarily a valid pointer. // `Weak::new` sets this to `usize::MAX` so that it doesn’t need @@ -2199,15 +2689,16 @@ pub struct Weak { // will ever have because RcBox has alignment at least 2. // This is only possible when `T: Sized`; unsized `T` never dangle. ptr: NonNull>, + alloc: A, } #[stable(feature = "rc_weak", since = "1.4.0")] -impl !Send for Weak {} +impl !Send for Weak {} #[stable(feature = "rc_weak", since = "1.4.0")] -impl !Sync for Weak {} +impl !Sync for Weak {} #[unstable(feature = "coerce_unsized", issue = "18598")] -impl, U: ?Sized> CoerceUnsized> for Weak {} +impl, U: ?Sized, A: Allocator> CoerceUnsized> for Weak {} #[unstable(feature = "dispatch_from_dyn", issue = "none")] impl, U: ?Sized> DispatchFromDyn> for Weak {} @@ -2226,16 +2717,45 @@ impl Weak { /// let empty: Weak = Weak::new(); /// assert!(empty.upgrade().is_none()); /// ``` + #[inline] #[stable(feature = "downgraded_weak", since = "1.10.0")] - #[rustc_const_unstable(feature = "const_weak_new", issue = "95091", reason = "recently added")] + #[rustc_const_stable(feature = "const_weak_new", since = "1.73.0")] #[must_use] pub const fn new() -> Weak { - Weak { ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::>(usize::MAX)) } } + Weak { + ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::>(usize::MAX)) }, + alloc: Global, + } + } +} + +impl Weak { + /// Constructs a new `Weak`, without allocating any memory, technically in the provided + /// allocator. + /// Calling [`upgrade`] on the return value always gives [`None`]. + /// + /// [`upgrade`]: Weak::upgrade + /// + /// # Examples + /// + /// ``` + /// use std::rc::Weak; + /// + /// let empty: Weak = Weak::new(); + /// assert!(empty.upgrade().is_none()); + /// ``` + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn new_in(alloc: A) -> Weak { + Weak { + ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::>(usize::MAX)) }, + alloc, + } } } pub(crate) fn is_dangling(ptr: *mut T) -> bool { - (ptr as *mut ()).addr() == usize::MAX + (ptr.cast::<()>()).addr() == usize::MAX } /// Helper type to allow accessing the reference counts without @@ -2246,6 +2766,56 @@ struct WeakInner<'a> { } impl Weak { + /// Converts a raw pointer previously created by [`into_raw`] back into `Weak`. + /// + /// This can be used to safely get a strong reference (by calling [`upgrade`] + /// later) or to deallocate the weak count by dropping the `Weak`. + /// + /// It takes ownership of one weak reference (with the exception of pointers created by [`new`], + /// as these don't own anything; the method still works on them). + /// + /// # Safety + /// + /// The pointer must have originated from the [`into_raw`] and must still own its potential + /// weak reference, and `ptr` must point to a block of memory allocated by the global allocator. + /// + /// It is allowed for the strong count to be 0 at the time of calling this. Nevertheless, this + /// takes ownership of one weak reference currently represented as a raw pointer (the weak + /// count is not modified by this operation) and therefore it must be paired with a previous + /// call to [`into_raw`]. + /// + /// # Examples + /// + /// ``` + /// use std::rc::{Rc, Weak}; + /// + /// let strong = Rc::new("hello".to_owned()); + /// + /// let raw_1 = Rc::downgrade(&strong).into_raw(); + /// let raw_2 = Rc::downgrade(&strong).into_raw(); + /// + /// assert_eq!(2, Rc::weak_count(&strong)); + /// + /// assert_eq!("hello", &*unsafe { Weak::from_raw(raw_1) }.upgrade().unwrap()); + /// assert_eq!(1, Rc::weak_count(&strong)); + /// + /// drop(strong); + /// + /// // Decrement the last weak count. + /// assert!(unsafe { Weak::from_raw(raw_2) }.upgrade().is_none()); + /// ``` + /// + /// [`into_raw`]: Weak::into_raw + /// [`upgrade`]: Weak::upgrade + /// [`new`]: Weak::new + #[inline] + #[stable(feature = "weak_into_raw", since = "1.45.0")] + pub unsafe fn from_raw(ptr: *const T) -> Self { + unsafe { Self::from_raw_in(ptr, Global) } + } +} + +impl Weak { /// Returns a raw pointer to the object `T` pointed to by this `Weak`. /// /// The pointer is valid only if there are some strong references. The pointer may be dangling, @@ -2323,6 +2893,45 @@ impl Weak { result } + /// Consumes the `Weak` and turns it into a raw pointer. + /// + /// This converts the weak pointer into a raw pointer, while still preserving the ownership of + /// one weak reference (the weak count is not modified by this operation). It can be turned + /// back into the `Weak` with [`from_raw`]. + /// + /// The same restrictions of accessing the target of the pointer as with + /// [`as_ptr`] apply. + /// + /// # Examples + /// + /// ``` + /// use std::rc::{Rc, Weak}; + /// + /// let strong = Rc::new("hello".to_owned()); + /// let weak = Rc::downgrade(&strong); + /// let raw = weak.into_raw(); + /// + /// assert_eq!(1, Rc::weak_count(&strong)); + /// assert_eq!("hello", unsafe { &*raw }); + /// + /// drop(unsafe { Weak::from_raw(raw) }); + /// assert_eq!(0, Rc::weak_count(&strong)); + /// ``` + /// + /// [`from_raw`]: Weak::from_raw + /// [`as_ptr`]: Weak::as_ptr + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn into_raw_and_alloc(self) -> (*const T, A) + where + A: Clone, + { + let result = self.as_ptr(); + let alloc = self.alloc.clone(); + mem::forget(self); + (result, alloc) + } + /// Converts a raw pointer previously created by [`into_raw`] back into `Weak`. /// /// This can be used to safely get a strong reference (by calling [`upgrade`] @@ -2334,7 +2943,7 @@ impl Weak { /// # Safety /// /// The pointer must have originated from the [`into_raw`] and must still own its potential - /// weak reference. + /// weak reference, and `ptr` must point to a block of memory allocated by `alloc`. /// /// It is allowed for the strong count to be 0 at the time of calling this. Nevertheless, this /// takes ownership of one weak reference currently represented as a raw pointer (the weak @@ -2365,8 +2974,9 @@ impl Weak { /// [`into_raw`]: Weak::into_raw /// [`upgrade`]: Weak::upgrade /// [`new`]: Weak::new - #[stable(feature = "weak_into_raw", since = "1.45.0")] - pub unsafe fn from_raw(ptr: *const T) -> Self { + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn from_raw_in(ptr: *const T, alloc: A) -> Self { // See Weak::as_ptr for context on how the input pointer is derived. let ptr = if is_dangling(ptr as *mut T) { @@ -2382,7 +2992,7 @@ impl Weak { }; // SAFETY: we now have recovered the original Weak pointer, so can create the Weak. - Weak { ptr: unsafe { NonNull::new_unchecked(ptr) } } + Weak { ptr: unsafe { NonNull::new_unchecked(ptr) }, alloc } } /// Attempts to upgrade the `Weak` pointer to an [`Rc`], delaying @@ -2411,7 +3021,10 @@ impl Weak { #[must_use = "this returns a new `Rc`, \ without modifying the original weak pointer"] #[stable(feature = "rc_weak", since = "1.4.0")] - pub fn upgrade(&self) -> Option> { + pub fn upgrade(&self) -> Option> + where + A: Clone, + { let inner = self.inner()?; if inner.strong() == 0 { @@ -2419,7 +3032,7 @@ impl Weak { } else { unsafe { inner.inc_strong(); - Some(Rc::from_inner(self.ptr)) + Some(Rc::from_inner_in(self.ptr, self.alloc.clone())) } } } @@ -2439,15 +3052,15 @@ impl Weak { #[must_use] #[stable(feature = "weak_counts", since = "1.41.0")] pub fn weak_count(&self) -> usize { - self.inner() - .map(|inner| { - if inner.strong() > 0 { - inner.weak() - 1 // subtract the implicit weak ptr - } else { - 0 - } - }) - .unwrap_or(0) + if let Some(inner) = self.inner() { + if inner.strong() > 0 { + inner.weak() - 1 // subtract the implicit weak ptr + } else { + 0 + } + } else { + 0 + } } /// Returns `None` when the pointer is dangling and there is no allocated `RcBox`, @@ -2468,8 +3081,8 @@ impl Weak { } /// Returns `true` if the two `Weak`s point to the same allocation similar to [`ptr::eq`], or if - /// both don't point to any allocation (because they were created with `Weak::new()`). See [that - /// function][`ptr::eq`] for caveats when comparing `dyn Trait` pointers. + /// both don't point to any allocation (because they were created with `Weak::new()`). However, + /// this function ignores the metadata of `dyn Trait` pointers. /// /// # Notes /// @@ -2510,12 +3123,12 @@ impl Weak { #[must_use] #[stable(feature = "weak_ptr_eq", since = "1.39.0")] pub fn ptr_eq(&self, other: &Self) -> bool { - self.ptr.as_ptr() == other.ptr.as_ptr() + ptr::eq(self.ptr.as_ptr() as *const (), other.ptr.as_ptr() as *const ()) } } #[stable(feature = "rc_weak", since = "1.4.0")] -unsafe impl<#[may_dangle] T: ?Sized> Drop for Weak { +unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Weak { /// Drops the `Weak` pointer. /// /// # Examples @@ -2548,14 +3161,14 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Weak { // the strong pointers have disappeared. if inner.weak() == 0 { unsafe { - Global.deallocate(self.ptr.cast(), Layout::for_value_raw(self.ptr.as_ptr())); + self.alloc.deallocate(self.ptr.cast(), Layout::for_value_raw(self.ptr.as_ptr())); } } } } #[stable(feature = "rc_weak", since = "1.4.0")] -impl Clone for Weak { +impl Clone for Weak { /// Makes a clone of the `Weak` pointer that points to the same allocation. /// /// # Examples @@ -2568,16 +3181,16 @@ impl Clone for Weak { /// let _ = Weak::clone(&weak_five); /// ``` #[inline] - fn clone(&self) -> Weak { + fn clone(&self) -> Weak { if let Some(inner) = self.inner() { inner.inc_weak() } - Weak { ptr: self.ptr } + Weak { ptr: self.ptr, alloc: self.alloc.clone() } } } #[stable(feature = "rc_weak", since = "1.4.0")] -impl fmt::Debug for Weak { +impl fmt::Debug for Weak { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "(Weak)") } @@ -2709,21 +3322,21 @@ impl<'a> RcInnerPtr for WeakInner<'a> { } #[stable(feature = "rust1", since = "1.0.0")] -impl borrow::Borrow for Rc { +impl borrow::Borrow for Rc { fn borrow(&self) -> &T { &**self } } #[stable(since = "1.5.0", feature = "smart_ptr_as_ref")] -impl AsRef for Rc { +impl AsRef for Rc { fn as_ref(&self) -> &T { &**self } } #[stable(feature = "pin", since = "1.33.0")] -impl Unpin for Rc {} +impl Unpin for Rc {} /// Get the offset within an `RcBox` for the payload behind a pointer. /// @@ -2746,3 +3359,139 @@ fn data_offset_align(align: usize) -> usize { let layout = Layout::new::>(); layout.size() + layout.padding_needed_for(align) } + +/// A uniquely owned `Rc` +/// +/// This represents an `Rc` that is known to be uniquely owned -- that is, have exactly one strong +/// reference. Multiple weak pointers can be created, but attempts to upgrade those to strong +/// references will fail unless the `UniqueRc` they point to has been converted into a regular `Rc`. +/// +/// Because they are uniquely owned, the contents of a `UniqueRc` can be freely mutated. A common +/// use case is to have an object be mutable during its initialization phase but then have it become +/// immutable and converted to a normal `Rc`. +/// +/// This can be used as a flexible way to create cyclic data structures, as in the example below. +/// +/// ``` +/// #![feature(unique_rc_arc)] +/// use std::rc::{Rc, Weak, UniqueRc}; +/// +/// struct Gadget { +/// #[allow(dead_code)] +/// me: Weak, +/// } +/// +/// fn create_gadget() -> Option> { +/// let mut rc = UniqueRc::new(Gadget { +/// me: Weak::new(), +/// }); +/// rc.me = UniqueRc::downgrade(&rc); +/// Some(UniqueRc::into_rc(rc)) +/// } +/// +/// create_gadget().unwrap(); +/// ``` +/// +/// An advantage of using `UniqueRc` over [`Rc::new_cyclic`] to build cyclic data structures is that +/// [`Rc::new_cyclic`]'s `data_fn` parameter cannot be async or return a [`Result`]. As shown in the +/// previous example, `UniqueRc` allows for more flexibility in the construction of cyclic data, +/// including fallible or async constructors. +#[unstable(feature = "unique_rc_arc", issue = "112566")] +#[derive(Debug)] +pub struct UniqueRc { + ptr: NonNull>, + phantom: PhantomData>, +} + +impl UniqueRc { + /// Creates a new `UniqueRc` + /// + /// Weak references to this `UniqueRc` can be created with [`UniqueRc::downgrade`]. Upgrading + /// these weak references will fail before the `UniqueRc` has been converted into an [`Rc`]. + /// After converting the `UniqueRc` into an [`Rc`], any weak references created beforehand will + /// point to the new [`Rc`]. + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn new(value: T) -> Self { + Self { + ptr: Box::leak(Box::new(RcBox { + strong: Cell::new(0), + // keep one weak reference so if all the weak pointers that are created are dropped + // the UniqueRc still stays valid. + weak: Cell::new(1), + value, + })) + .into(), + phantom: PhantomData, + } + } + + /// Creates a new weak reference to the `UniqueRc` + /// + /// Attempting to upgrade this weak reference will fail before the `UniqueRc` has been converted + /// to a [`Rc`] using [`UniqueRc::into_rc`]. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn downgrade(this: &Self) -> Weak { + // SAFETY: This pointer was allocated at creation time and we guarantee that we only have + // one strong reference before converting to a regular Rc. + unsafe { + this.ptr.as_ref().inc_weak(); + } + Weak { ptr: this.ptr, alloc: Global } + } + + /// Converts the `UniqueRc` into a regular [`Rc`] + /// + /// This consumes the `UniqueRc` and returns a regular [`Rc`] that contains the `value` that + /// is passed to `into_rc`. + /// + /// Any weak references created before this method is called can now be upgraded to strong + /// references. + #[unstable(feature = "unique_rc_arc", issue = "112566")] + pub fn into_rc(this: Self) -> Rc { + let mut this = ManuallyDrop::new(this); + // SAFETY: This pointer was allocated at creation time so we know it is valid. + unsafe { + // Convert our weak reference into a strong reference + this.ptr.as_mut().strong.set(1); + Rc::from_inner(this.ptr) + } + } +} + +#[unstable(feature = "unique_rc_arc", issue = "112566")] +impl Deref for UniqueRc { + type Target = T; + + fn deref(&self) -> &T { + // SAFETY: This pointer was allocated at creation time so we know it is valid. + unsafe { &self.ptr.as_ref().value } + } +} + +#[unstable(feature = "unique_rc_arc", issue = "112566")] +impl DerefMut for UniqueRc { + fn deref_mut(&mut self) -> &mut T { + // SAFETY: This pointer was allocated at creation time so we know it is valid. We know we + // have unique ownership and therefore it's safe to make a mutable reference because + // `UniqueRc` owns the only strong reference to itself. + unsafe { &mut (*self.ptr.as_ptr()).value } + } +} + +#[unstable(feature = "unique_rc_arc", issue = "112566")] +unsafe impl<#[may_dangle] T> Drop for UniqueRc { + fn drop(&mut self) { + unsafe { + // destroy the contained object + drop_in_place(DerefMut::deref_mut(self)); + + // remove the implicit "strong weak" pointer now that we've destroyed the contents. + self.ptr.as_ref().dec_weak(); + + if self.ptr.as_ref().weak() == 0 { + Global.deallocate(self.ptr.cast(), Layout::for_value(self.ptr.as_ref())); + } + } + } +} diff --git a/library/alloc/src/rc/tests.rs b/library/alloc/src/rc/tests.rs index 2784108e0e635..1f221b86f120d 100644 --- a/library/alloc/src/rc/tests.rs +++ b/library/alloc/src/rc/tests.rs @@ -574,3 +574,48 @@ fn test_rc_cyclic_with_two_ref() { assert_eq!(Rc::strong_count(&two_refs), 3); assert_eq!(Rc::weak_count(&two_refs), 2); } + +#[test] +fn test_unique_rc_weak() { + let rc = UniqueRc::new(42); + let weak = UniqueRc::downgrade(&rc); + assert!(weak.upgrade().is_none()); + + let _rc = UniqueRc::into_rc(rc); + assert_eq!(*weak.upgrade().unwrap(), 42); +} + +#[test] +fn test_unique_rc_drop_weak() { + let rc = UniqueRc::new(42); + let weak = UniqueRc::downgrade(&rc); + mem::drop(weak); + + let rc = UniqueRc::into_rc(rc); + assert_eq!(*rc, 42); +} + +#[test] +fn test_unique_rc_drops_contents() { + let mut dropped = false; + struct DropMe<'a>(&'a mut bool); + impl Drop for DropMe<'_> { + fn drop(&mut self) { + *self.0 = true; + } + } + { + let rc = UniqueRc::new(DropMe(&mut dropped)); + drop(rc); + } + assert!(dropped); +} + +#[test] +fn test_unique_rc_weak_clone_holding_ref() { + let mut v = UniqueRc::new(0u8); + let w = UniqueRc::downgrade(&v); + let r = &mut *v; + let _ = w.clone(); // touch weak count + *r = 123; +} diff --git a/library/alloc/src/slice/tests.rs b/library/alloc/src/slice/tests.rs index f674530aaa54c..54bc4e77b16f0 100644 --- a/library/alloc/src/slice/tests.rs +++ b/library/alloc/src/slice/tests.rs @@ -187,6 +187,7 @@ std::thread_local!(static SILENCE_PANIC: Cell = Cell::new(false)); #[test] #[cfg_attr(target_os = "emscripten", ignore)] // no threads +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn panic_safe() { panic::update_hook(move |prev, info| { if !SILENCE_PANIC.with(|s| s.get()) { diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs index 8497740990443..38f9f39fbf89a 100644 --- a/library/alloc/src/str.rs +++ b/library/alloc/src/str.rs @@ -223,8 +223,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = "this is a string"; /// let boxed_str = s.to_owned().into_boxed_str(); @@ -487,8 +485,6 @@ impl str { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let string = String::from("birthday gift"); /// let boxed_str = string.clone().into_boxed_str(); @@ -602,8 +598,6 @@ impl str { /// /// # Examples /// -/// Basic usage: -/// /// ``` /// let smile_utf8 = Box::new([226, 152, 186]); /// let smile = unsafe { std::str::from_boxed_utf8_unchecked(smile_utf8) }; @@ -618,7 +612,7 @@ pub unsafe fn from_boxed_utf8_unchecked(v: Box<[u8]>) -> Box { } /// Converts the bytes while the bytes are still ascii. -/// For better average performance, this is happens in chunks of `2*size_of::()`. +/// For better average performance, this happens in chunks of `2*size_of::()`. /// Returns a vec with the converted bytes. #[inline] #[cfg(not(test))] diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 498fbd93288c8..ed43244ebda19 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -388,8 +388,6 @@ pub struct String { /// /// # Examples /// -/// Basic usage: -/// /// ``` /// // some invalid bytes, in a vector /// let bytes = vec![0, 159]; @@ -412,9 +410,8 @@ pub struct FromUtf8Error { /// This type is the error type for the [`from_utf16`] method on [`String`]. /// /// [`from_utf16`]: String::from_utf16 -/// # Examples /// -/// Basic usage: +/// # Examples /// /// ``` /// // 𝄞muic @@ -441,8 +438,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = String::new(); /// ``` @@ -472,8 +467,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::with_capacity(10); /// @@ -661,8 +654,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // 𝄞music /// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, @@ -704,8 +695,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // 𝄞music /// let v = &[0xD834, 0xDD1E, 0x006d, 0x0075, @@ -784,8 +773,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// use std::mem; /// @@ -827,8 +814,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // some bytes, in a vector /// let sparkle_heart = vec![240, 159, 146, 150]; @@ -852,8 +837,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = String::from("hello"); /// let bytes = s.into_bytes(); @@ -871,8 +854,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = String::from("foo"); /// @@ -889,8 +870,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("foobar"); /// let s_mut_str = s.as_mut_str(); @@ -910,8 +889,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("foo"); /// @@ -966,8 +943,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = String::with_capacity(10); /// @@ -1157,8 +1132,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("foo"); /// @@ -1206,8 +1179,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("abc"); /// @@ -1235,8 +1206,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = String::from("hello"); /// @@ -1263,8 +1232,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("hello"); /// @@ -1287,14 +1254,12 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` - /// let mut s = String::from("foo"); + /// let mut s = String::from("abč"); /// - /// assert_eq!(s.pop(), Some('o')); - /// assert_eq!(s.pop(), Some('o')); - /// assert_eq!(s.pop(), Some('f')); + /// assert_eq!(s.pop(), Some('č')); + /// assert_eq!(s.pop(), Some('b')); + /// assert_eq!(s.pop(), Some('a')); /// /// assert_eq!(s.pop(), None); /// ``` @@ -1321,14 +1286,12 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` - /// let mut s = String::from("foo"); + /// let mut s = String::from("abç"); /// - /// assert_eq!(s.remove(0), 'f'); - /// assert_eq!(s.remove(1), 'o'); - /// assert_eq!(s.remove(0), 'o'); + /// assert_eq!(s.remove(0), 'a'); + /// assert_eq!(s.remove(1), 'ç'); + /// assert_eq!(s.remove(0), 'b'); /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] @@ -1514,8 +1477,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::with_capacity(3); /// @@ -1563,8 +1524,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("bar"); /// @@ -1595,8 +1554,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("hello"); /// @@ -1620,8 +1577,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let a = String::from("foo"); /// assert_eq!(a.len(), 3); @@ -1641,8 +1596,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut v = String::new(); /// assert!(v.is_empty()); @@ -1697,8 +1650,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("foo"); /// @@ -1734,8 +1685,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("α is alpha, β is beta"); /// let beta_offset = s.find('β').unwrap_or(s.len()); @@ -1784,8 +1733,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let mut s = String::from("α is alpha, β is beta"); /// let beta_offset = s.find('β').unwrap_or(s.len()); @@ -1834,8 +1781,6 @@ impl String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s = String::from("hello"); /// @@ -1851,30 +1796,29 @@ impl String { } /// Consumes and leaks the `String`, returning a mutable reference to the contents, - /// `&'static mut str`. + /// `&'a mut str`. /// - /// This is mainly useful for data that lives for the remainder of - /// the program's life. Dropping the returned reference will cause a memory - /// leak. + /// The caller has free choice over the returned lifetime, including `'static`. Indeed, + /// this function is ideally used for data that lives for the remainder of the program's life, + /// as dropping the returned reference will cause a memory leak. /// /// It does not reallocate or shrink the `String`, /// so the leaked allocation may include unused capacity that is not part - /// of the returned slice. + /// of the returned slice. If you don't want that, call [`into_boxed_str`], + /// and then [`Box::leak`]. /// - /// # Examples + /// [`into_boxed_str`]: Self::into_boxed_str /// - /// Simple usage: + /// # Examples /// /// ``` - /// #![feature(string_leak)] - /// /// let x = String::from("bucket"); /// let static_ref: &'static mut str = x.leak(); /// assert_eq!(static_ref, "bucket"); /// ``` - #[unstable(feature = "string_leak", issue = "102929")] + #[stable(feature = "string_leak", since = "1.72.0")] #[inline] - pub fn leak(self) -> &'static mut str { + pub fn leak<'a>(self) -> &'a mut str { let slice = self.vec.leak(); unsafe { from_utf8_unchecked_mut(slice) } } @@ -1885,8 +1829,6 @@ impl FromUtf8Error { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // some invalid bytes, in a vector /// let bytes = vec![0, 159]; @@ -1909,8 +1851,6 @@ impl FromUtf8Error { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // some invalid bytes, in a vector /// let bytes = vec![0, 159]; @@ -1937,8 +1877,6 @@ impl FromUtf8Error { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // some invalid bytes, in a vector /// let bytes = vec![0, 159]; @@ -2489,8 +2427,6 @@ pub trait ToString { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let i = 5; /// let five = String::from("5"); @@ -2526,6 +2462,7 @@ impl ToString for T { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[unstable(feature = "ascii_char", issue = "110998")] impl ToString for core::ascii::Char { @@ -2535,6 +2472,7 @@ impl ToString for core::ascii::Char { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "char_to_string_specialization", since = "1.46.0")] impl ToString for char { @@ -2544,6 +2482,7 @@ impl ToString for char { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "bool_to_string_specialization", since = "1.68.0")] impl ToString for bool { @@ -2553,6 +2492,7 @@ impl ToString for bool { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "u8_to_string_specialization", since = "1.54.0")] impl ToString for u8 { @@ -2573,6 +2513,7 @@ impl ToString for u8 { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "i8_to_string_specialization", since = "1.54.0")] impl ToString for i8 { @@ -2596,6 +2537,7 @@ impl ToString for i8 { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "str_to_string_specialization", since = "1.9.0")] impl ToString for str { @@ -2605,6 +2547,7 @@ impl ToString for str { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "cow_str_to_string_specialization", since = "1.17.0")] impl ToString for Cow<'_, str> { @@ -2614,6 +2557,7 @@ impl ToString for Cow<'_, str> { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] #[stable(feature = "string_to_string_specialization", since = "1.17.0")] impl ToString for String { @@ -2623,8 +2567,9 @@ impl ToString for String { } } +#[doc(hidden)] #[cfg(not(no_global_oom_handling))] -#[stable(feature = "fmt_arguments_to_string_specialization", since = "CURRENT_RUSTC_VERSION")] +#[stable(feature = "fmt_arguments_to_string_specialization", since = "1.71.0")] impl ToString for fmt::Arguments<'_> { #[inline] fn to_string(&self) -> String { @@ -2701,8 +2646,6 @@ impl From> for String { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s1: String = String::from("hello world"); /// let s2: Box = s1.into_boxed_str(); @@ -2722,8 +2665,6 @@ impl From for Box { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s1: String = String::from("hello world"); /// let s2: Box = Box::from(s1); @@ -2856,8 +2797,6 @@ impl From for Vec { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let s1 = String::from("hello world"); /// let v1 = Vec::from(s1); diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index bfdb7a92beff6..d3b7558440c67 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -33,7 +33,7 @@ use core::sync::atomic::Ordering::{Acquire, Relaxed, Release}; #[cfg(not(no_global_oom_handling))] use crate::alloc::handle_alloc_error; #[cfg(not(no_global_oom_handling))] -use crate::alloc::{box_free, WriteCloneIntoRaw}; +use crate::alloc::WriteCloneIntoRaw; use crate::alloc::{AllocError, Allocator, Global, Layout}; use crate::borrow::{Cow, ToOwned}; use crate::boxed::Box; @@ -153,7 +153,7 @@ macro_rules! acquire { /// /// ## `Deref` behavior /// -/// `Arc` automatically dereferences to `T` (via the [`Deref`][deref] trait), +/// `Arc` automatically dereferences to `T` (via the [`Deref`] trait), /// so you can call `T`'s methods on a value of type `Arc`. To avoid name /// clashes with `T`'s methods, the methods of `Arc` itself are associated /// functions, called using [fully qualified syntax]: @@ -187,7 +187,6 @@ macro_rules! acquire { /// [mutex]: ../../std/sync/struct.Mutex.html /// [rwlock]: ../../std/sync/struct.RwLock.html /// [atomic]: core::sync::atomic -/// [deref]: core::ops::Deref /// [downgrade]: Arc::downgrade /// [upgrade]: Weak::upgrade /// [RefCell\]: core::cell::RefCell @@ -246,32 +245,48 @@ macro_rules! acquire { /// [rc_examples]: crate::rc#examples #[cfg_attr(not(test), rustc_diagnostic_item = "Arc")] #[stable(feature = "rust1", since = "1.0.0")] -pub struct Arc { +pub struct Arc< + T: ?Sized, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { ptr: NonNull>, phantom: PhantomData>, + alloc: A, } #[stable(feature = "rust1", since = "1.0.0")] -unsafe impl Send for Arc {} +unsafe impl Send for Arc {} #[stable(feature = "rust1", since = "1.0.0")] -unsafe impl Sync for Arc {} +unsafe impl Sync for Arc {} #[stable(feature = "catch_unwind", since = "1.9.0")] -impl UnwindSafe for Arc {} +impl UnwindSafe for Arc {} #[unstable(feature = "coerce_unsized", issue = "18598")] -impl, U: ?Sized> CoerceUnsized> for Arc {} +impl, U: ?Sized, A: Allocator> CoerceUnsized> for Arc {} #[unstable(feature = "dispatch_from_dyn", issue = "none")] impl, U: ?Sized> DispatchFromDyn> for Arc {} impl Arc { unsafe fn from_inner(ptr: NonNull>) -> Self { - Self { ptr, phantom: PhantomData } + unsafe { Self::from_inner_in(ptr, Global) } } unsafe fn from_ptr(ptr: *mut ArcInner) -> Self { - unsafe { Self::from_inner(NonNull::new_unchecked(ptr)) } + unsafe { Self::from_ptr_in(ptr, Global) } + } +} + +impl Arc { + #[inline] + unsafe fn from_inner_in(ptr: NonNull>, alloc: A) -> Self { + Self { ptr, phantom: PhantomData, alloc } + } + + #[inline] + unsafe fn from_ptr_in(ptr: *mut ArcInner, alloc: A) -> Self { + unsafe { Self::from_inner_in(NonNull::new_unchecked(ptr), alloc) } } } @@ -296,7 +311,10 @@ impl Arc { /// /// [`upgrade`]: Weak::upgrade #[stable(feature = "arc_weak", since = "1.4.0")] -pub struct Weak { +pub struct Weak< + T: ?Sized, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> { // This is a `NonNull` to allow optimizing the size of this type in enums, // but it is not necessarily a valid pointer. // `Weak::new` sets this to `usize::MAX` so that it doesn’t need @@ -304,15 +322,16 @@ pub struct Weak { // will ever have because RcBox has alignment at least 2. // This is only possible when `T: Sized`; unsized `T` never dangle. ptr: NonNull>, + alloc: A, } #[stable(feature = "arc_weak", since = "1.4.0")] -unsafe impl Send for Weak {} +unsafe impl Send for Weak {} #[stable(feature = "arc_weak", since = "1.4.0")] -unsafe impl Sync for Weak {} +unsafe impl Sync for Weak {} #[unstable(feature = "coerce_unsized", issue = "18598")] -impl, U: ?Sized> CoerceUnsized> for Weak {} +impl, U: ?Sized, A: Allocator> CoerceUnsized> for Weak {} #[unstable(feature = "dispatch_from_dyn", issue = "none")] impl, U: ?Sized> DispatchFromDyn> for Weak {} @@ -442,7 +461,7 @@ impl Arc { .into(); let init_ptr: NonNull> = uninit_ptr.cast(); - let weak = Weak { ptr: init_ptr }; + let weak = Weak { ptr: init_ptr, alloc: Global }; // It's important we don't give up ownership of the weak pointer, or // else the memory might be freed by the time `data_fn` returns. If @@ -510,7 +529,7 @@ impl Arc { Arc::from_ptr(Arc::allocate_for_layout( Layout::new::(), |layout| Global.allocate(layout), - |mem| mem as *mut ArcInner>, + <*mut u8>::cast, )) } } @@ -544,7 +563,7 @@ impl Arc { Arc::from_ptr(Arc::allocate_for_layout( Layout::new::(), |layout| Global.allocate_zeroed(layout), - |mem| mem as *mut ArcInner>, + <*mut u8>::cast, )) } } @@ -617,7 +636,7 @@ impl Arc { Ok(Arc::from_ptr(Arc::try_allocate_for_layout( Layout::new::(), |layout| Global.allocate(layout), - |mem| mem as *mut ArcInner>, + <*mut u8>::cast, )?)) } } @@ -650,10 +669,258 @@ impl Arc { Ok(Arc::from_ptr(Arc::try_allocate_for_layout( Layout::new::(), |layout| Global.allocate_zeroed(layout), - |mem| mem as *mut ArcInner>, + <*mut u8>::cast, )?)) } } +} + +impl Arc { + /// Returns a reference to the underlying allocator. + /// + /// Note: this is an associated function, which means that you have + /// to call it as `Arc::allocator(&a)` instead of `a.allocator()`. This + /// is so that there is no conflict with a method on the inner type. + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn allocator(this: &Self) -> &A { + &this.alloc + } + /// Constructs a new `Arc` in the provided allocator. + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let five = Arc::new_in(5, System); + /// ``` + #[inline] + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn new_in(data: T, alloc: A) -> Arc { + // Start the weak pointer count as 1 which is the weak pointer that's + // held by all the strong pointers (kinda), see std/rc.rs for more info + let x = Box::new_in( + ArcInner { + strong: atomic::AtomicUsize::new(1), + weak: atomic::AtomicUsize::new(1), + data, + }, + alloc, + ); + let (ptr, alloc) = Box::into_unique(x); + unsafe { Self::from_inner_in(ptr.into(), alloc) } + } + + /// Constructs a new `Arc` with uninitialized contents in the provided allocator. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(get_mut_unchecked)] + /// #![feature(allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let mut five = Arc::::new_uninit_in(System); + /// + /// let five = unsafe { + /// // Deferred initialization: + /// Arc::get_mut_unchecked(&mut five).as_mut_ptr().write(5); + /// + /// five.assume_init() + /// }; + /// + /// assert_eq!(*five, 5) + /// ``` + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_uninit_in(alloc: A) -> Arc, A> { + unsafe { + Arc::from_ptr_in( + Arc::allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate(layout), + <*mut u8>::cast, + ), + alloc, + ) + } + } + + /// Constructs a new `Arc` with uninitialized contents, with the memory + /// being filled with `0` bytes, in the provided allocator. + /// + /// See [`MaybeUninit::zeroed`][zeroed] for examples of correct and incorrect usage + /// of this method. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let zero = Arc::::new_zeroed_in(System); + /// let zero = unsafe { zero.assume_init() }; + /// + /// assert_eq!(*zero, 0) + /// ``` + /// + /// [zeroed]: mem::MaybeUninit::zeroed + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_zeroed_in(alloc: A) -> Arc, A> { + unsafe { + Arc::from_ptr_in( + Arc::allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate_zeroed(layout), + <*mut u8>::cast, + ), + alloc, + ) + } + } + + /// Constructs a new `Pin>` in the provided allocator. If `T` does not implement `Unpin`, + /// then `data` will be pinned in memory and unable to be moved. + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn pin_in(data: T, alloc: A) -> Pin> { + unsafe { Pin::new_unchecked(Arc::new_in(data, alloc)) } + } + + /// Constructs a new `Pin>` in the provided allocator, return an error if allocation + /// fails. + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn try_pin_in(data: T, alloc: A) -> Result>, AllocError> { + unsafe { Ok(Pin::new_unchecked(Arc::try_new_in(data, alloc)?)) } + } + + /// Constructs a new `Arc` in the provided allocator, returning an error if allocation fails. + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let five = Arc::try_new_in(5, System)?; + /// # Ok::<(), std::alloc::AllocError>(()) + /// ``` + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn try_new_in(data: T, alloc: A) -> Result, AllocError> { + // Start the weak pointer count as 1 which is the weak pointer that's + // held by all the strong pointers (kinda), see std/rc.rs for more info + let x = Box::try_new_in( + ArcInner { + strong: atomic::AtomicUsize::new(1), + weak: atomic::AtomicUsize::new(1), + data, + }, + alloc, + )?; + let (ptr, alloc) = Box::into_unique(x); + Ok(unsafe { Self::from_inner_in(ptr.into(), alloc) }) + } + + /// Constructs a new `Arc` with uninitialized contents, in the provided allocator, returning an + /// error if allocation fails. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit, allocator_api)] + /// #![feature(get_mut_unchecked)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let mut five = Arc::::try_new_uninit_in(System)?; + /// + /// let five = unsafe { + /// // Deferred initialization: + /// Arc::get_mut_unchecked(&mut five).as_mut_ptr().write(5); + /// + /// five.assume_init() + /// }; + /// + /// assert_eq!(*five, 5); + /// # Ok::<(), std::alloc::AllocError>(()) + /// ``` + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn try_new_uninit_in(alloc: A) -> Result, A>, AllocError> { + unsafe { + Ok(Arc::from_ptr_in( + Arc::try_allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate(layout), + <*mut u8>::cast, + )?, + alloc, + )) + } + } + + /// Constructs a new `Arc` with uninitialized contents, with the memory + /// being filled with `0` bytes, in the provided allocator, returning an error if allocation + /// fails. + /// + /// See [`MaybeUninit::zeroed`][zeroed] for examples of correct and incorrect usage + /// of this method. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit, allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let zero = Arc::::try_new_zeroed_in(System)?; + /// let zero = unsafe { zero.assume_init() }; + /// + /// assert_eq!(*zero, 0); + /// # Ok::<(), std::alloc::AllocError>(()) + /// ``` + /// + /// [zeroed]: mem::MaybeUninit::zeroed + #[unstable(feature = "allocator_api", issue = "32838")] + // #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn try_new_zeroed_in(alloc: A) -> Result, A>, AllocError> { + unsafe { + Ok(Arc::from_ptr_in( + Arc::try_allocate_for_layout( + Layout::new::(), + |layout| alloc.allocate_zeroed(layout), + <*mut u8>::cast, + )?, + alloc, + )) + } + } /// Returns the inner value, if the `Arc` has exactly one strong reference. /// /// Otherwise, an [`Err`] is returned with the same `Arc` that was @@ -695,9 +962,10 @@ impl Arc { unsafe { let elem = ptr::read(&this.ptr.as_ref().data); + let alloc = ptr::read(&this.alloc); // copy the allocator // Make a weak pointer to clean up the implicit strong-weak reference - let _weak = Weak { ptr: this.ptr }; + let _weak = Weak { ptr: this.ptr, alloc }; mem::forget(this); Ok(elem) @@ -814,9 +1082,11 @@ impl Arc { // in `drop_slow`. Instead of dropping the value behind the pointer, // it is read and eventually returned; `ptr::read` has the same // safety conditions as `ptr::drop_in_place`. + let inner = unsafe { ptr::read(Self::get_mut_unchecked(&mut this)) }; + let alloc = unsafe { ptr::read(&this.alloc) }; - drop(Weak { ptr: this.ptr }); + drop(Weak { ptr: this.ptr, alloc }); Some(inner) } @@ -891,7 +1161,83 @@ impl Arc<[T]> { } } -impl Arc> { +impl Arc<[T], A> { + /// Constructs a new atomically reference-counted slice with uninitialized contents in the + /// provided allocator. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(get_mut_unchecked)] + /// #![feature(allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let mut values = Arc::<[u32], _>::new_uninit_slice_in(3, System); + /// + /// let values = unsafe { + /// // Deferred initialization: + /// Arc::get_mut_unchecked(&mut values)[0].as_mut_ptr().write(1); + /// Arc::get_mut_unchecked(&mut values)[1].as_mut_ptr().write(2); + /// Arc::get_mut_unchecked(&mut values)[2].as_mut_ptr().write(3); + /// + /// values.assume_init() + /// }; + /// + /// assert_eq!(*values, [1, 2, 3]) + /// ``` + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_uninit_slice_in(len: usize, alloc: A) -> Arc<[mem::MaybeUninit], A> { + unsafe { Arc::from_ptr_in(Arc::allocate_for_slice_in(len, &alloc), alloc) } + } + + /// Constructs a new atomically reference-counted slice with uninitialized contents, with the memory being + /// filled with `0` bytes, in the provided allocator. + /// + /// See [`MaybeUninit::zeroed`][zeroed] for examples of correct and + /// incorrect usage of this method. + /// + /// # Examples + /// + /// ``` + /// #![feature(new_uninit)] + /// #![feature(allocator_api)] + /// + /// use std::sync::Arc; + /// use std::alloc::System; + /// + /// let values = Arc::<[u32], _>::new_zeroed_slice_in(3, System); + /// let values = unsafe { values.assume_init() }; + /// + /// assert_eq!(*values, [0, 0, 0]) + /// ``` + /// + /// [zeroed]: mem::MaybeUninit::zeroed + #[cfg(not(no_global_oom_handling))] + #[unstable(feature = "new_uninit", issue = "63291")] + #[inline] + pub fn new_zeroed_slice_in(len: usize, alloc: A) -> Arc<[mem::MaybeUninit], A> { + unsafe { + Arc::from_ptr_in( + Arc::allocate_for_layout( + Layout::array::(len).unwrap(), + |layout| alloc.allocate_zeroed(layout), + |mem| { + ptr::slice_from_raw_parts_mut(mem.cast::(), len) + as *mut ArcInner<[mem::MaybeUninit]> + }, + ), + alloc, + ) + } + } +} + +impl Arc, A> { /// Converts to `Arc`. /// /// # Safety @@ -924,12 +1270,16 @@ impl Arc> { #[unstable(feature = "new_uninit", issue = "63291")] #[must_use = "`self` will be dropped if the result is not used"] #[inline] - pub unsafe fn assume_init(self) -> Arc { - unsafe { Arc::from_inner(mem::ManuallyDrop::new(self).ptr.cast()) } + pub unsafe fn assume_init(self) -> Arc + where + A: Clone, + { + let md_self = mem::ManuallyDrop::new(self); + unsafe { Arc::from_inner_in(md_self.ptr.cast(), md_self.alloc.clone()) } } } -impl Arc<[mem::MaybeUninit]> { +impl Arc<[mem::MaybeUninit], A> { /// Converts to `Arc<[T]>`. /// /// # Safety @@ -965,12 +1315,129 @@ impl Arc<[mem::MaybeUninit]> { #[unstable(feature = "new_uninit", issue = "63291")] #[must_use = "`self` will be dropped if the result is not used"] #[inline] - pub unsafe fn assume_init(self) -> Arc<[T]> { - unsafe { Arc::from_ptr(mem::ManuallyDrop::new(self).ptr.as_ptr() as _) } + pub unsafe fn assume_init(self) -> Arc<[T], A> + where + A: Clone, + { + let md_self = mem::ManuallyDrop::new(self); + unsafe { Arc::from_ptr_in(md_self.ptr.as_ptr() as _, md_self.alloc.clone()) } } } impl Arc { + /// Constructs an `Arc` from a raw pointer. + /// + /// The raw pointer must have been previously returned by a call to + /// [`Arc::into_raw`][into_raw] where `U` must have the same size and + /// alignment as `T`. This is trivially true if `U` is `T`. + /// Note that if `U` is not `T` but has the same size and alignment, this is + /// basically like transmuting references of different types. See + /// [`mem::transmute`][transmute] for more information on what + /// restrictions apply in this case. + /// + /// The user of `from_raw` has to make sure a specific value of `T` is only + /// dropped once. + /// + /// This function is unsafe because improper use may lead to memory unsafety, + /// even if the returned `Arc` is never accessed. + /// + /// [into_raw]: Arc::into_raw + /// [transmute]: core::mem::transmute + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// + /// let x = Arc::new("hello".to_owned()); + /// let x_ptr = Arc::into_raw(x); + /// + /// unsafe { + /// // Convert back to an `Arc` to prevent leak. + /// let x = Arc::from_raw(x_ptr); + /// assert_eq!(&*x, "hello"); + /// + /// // Further calls to `Arc::from_raw(x_ptr)` would be memory-unsafe. + /// } + /// + /// // The memory was freed when `x` went out of scope above, so `x_ptr` is now dangling! + /// ``` + #[inline] + #[stable(feature = "rc_raw", since = "1.17.0")] + pub unsafe fn from_raw(ptr: *const T) -> Self { + unsafe { Arc::from_raw_in(ptr, Global) } + } + + /// Increments the strong reference count on the `Arc` associated with the + /// provided pointer by one. + /// + /// # Safety + /// + /// The pointer must have been obtained through `Arc::into_raw`, and the + /// associated `Arc` instance must be valid (i.e. the strong count must be at + /// least 1) for the duration of this method. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// + /// let five = Arc::new(5); + /// + /// unsafe { + /// let ptr = Arc::into_raw(five); + /// Arc::increment_strong_count(ptr); + /// + /// // This assertion is deterministic because we haven't shared + /// // the `Arc` between threads. + /// let five = Arc::from_raw(ptr); + /// assert_eq!(2, Arc::strong_count(&five)); + /// } + /// ``` + #[inline] + #[stable(feature = "arc_mutate_strong_count", since = "1.51.0")] + pub unsafe fn increment_strong_count(ptr: *const T) { + unsafe { Arc::increment_strong_count_in(ptr, Global) } + } + + /// Decrements the strong reference count on the `Arc` associated with the + /// provided pointer by one. + /// + /// # Safety + /// + /// The pointer must have been obtained through `Arc::into_raw`, and the + /// associated `Arc` instance must be valid (i.e. the strong count must be at + /// least 1) when invoking this method. This method can be used to release the final + /// `Arc` and backing storage, but **should not** be called after the final `Arc` has been + /// released. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// + /// let five = Arc::new(5); + /// + /// unsafe { + /// let ptr = Arc::into_raw(five); + /// Arc::increment_strong_count(ptr); + /// + /// // Those assertions are deterministic because we haven't shared + /// // the `Arc` between threads. + /// let five = Arc::from_raw(ptr); + /// assert_eq!(2, Arc::strong_count(&five)); + /// Arc::decrement_strong_count(ptr); + /// assert_eq!(1, Arc::strong_count(&five)); + /// } + /// ``` + #[inline] + #[stable(feature = "arc_mutate_strong_count", since = "1.51.0")] + pub unsafe fn decrement_strong_count(ptr: *const T) { + unsafe { Arc::decrement_strong_count_in(ptr, Global) } + } +} + +impl Arc { /// Consumes the `Arc`, returning the wrapped pointer. /// /// To avoid a memory leak the pointer must be converted back to an `Arc` using @@ -1020,16 +1487,18 @@ impl Arc { unsafe { ptr::addr_of_mut!((*ptr).data) } } - /// Constructs an `Arc` from a raw pointer. + /// Constructs an `Arc` from a raw pointer. /// /// The raw pointer must have been previously returned by a call to - /// [`Arc::into_raw`][into_raw] where `U` must have the same size and + /// [`Arc::into_raw`][into_raw] where `U` must have the same size and /// alignment as `T`. This is trivially true if `U` is `T`. /// Note that if `U` is not `T` but has the same size and alignment, this is /// basically like transmuting references of different types. See - /// [`mem::transmute`][transmute] for more information on what + /// [`mem::transmute`] for more information on what /// restrictions apply in this case. /// + /// The raw pointer must point to a block of memory allocated by `alloc` + /// /// The user of `from_raw` has to make sure a specific value of `T` is only /// dropped once. /// @@ -1037,19 +1506,21 @@ impl Arc { /// even if the returned `Arc` is never accessed. /// /// [into_raw]: Arc::into_raw - /// [transmute]: core::mem::transmute /// /// # Examples /// /// ``` + /// #![feature(allocator_api)] + /// /// use std::sync::Arc; + /// use std::alloc::System; /// - /// let x = Arc::new("hello".to_owned()); + /// let x = Arc::new_in("hello".to_owned(), System); /// let x_ptr = Arc::into_raw(x); /// /// unsafe { /// // Convert back to an `Arc` to prevent leak. - /// let x = Arc::from_raw(x_ptr); + /// let x = Arc::from_raw_in(x_ptr, System); /// assert_eq!(&*x, "hello"); /// /// // Further calls to `Arc::from_raw(x_ptr)` would be memory-unsafe. @@ -1057,15 +1528,16 @@ impl Arc { /// /// // The memory was freed when `x` went out of scope above, so `x_ptr` is now dangling! /// ``` - #[stable(feature = "rc_raw", since = "1.17.0")] - pub unsafe fn from_raw(ptr: *const T) -> Self { + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn from_raw_in(ptr: *const T, alloc: A) -> Self { unsafe { let offset = data_offset(ptr); // Reverse the offset to find the original ArcInner. let arc_ptr = ptr.byte_sub(offset) as *mut ArcInner; - Self::from_ptr(arc_ptr) + Self::from_ptr_in(arc_ptr, alloc) } } @@ -1083,7 +1555,10 @@ impl Arc { #[must_use = "this returns a new `Weak` pointer, \ without modifying the original `Arc`"] #[stable(feature = "arc_weak", since = "1.4.0")] - pub fn downgrade(this: &Self) -> Weak { + pub fn downgrade(this: &Self) -> Weak + where + A: Clone, + { // This Relaxed is OK because we're checking the value in the CAS // below. let mut cur = this.inner().weak.load(Relaxed); @@ -1110,7 +1585,7 @@ impl Arc { Ok(_) => { // Make sure we do not create a dangling Weak debug_assert!(!is_dangling(this.ptr.as_ptr())); - return Weak { ptr: this.ptr }; + return Weak { ptr: this.ptr, alloc: this.alloc.clone() }; } Err(old) => cur = old, } @@ -1181,30 +1656,37 @@ impl Arc { /// /// The pointer must have been obtained through `Arc::into_raw`, and the /// associated `Arc` instance must be valid (i.e. the strong count must be at - /// least 1) for the duration of this method. + /// least 1) for the duration of this method,, and `ptr` must point to a block of memory + /// allocated by `alloc`. /// /// # Examples /// /// ``` + /// #![feature(allocator_api)] + /// /// use std::sync::Arc; + /// use std::alloc::System; /// - /// let five = Arc::new(5); + /// let five = Arc::new_in(5, System); /// /// unsafe { /// let ptr = Arc::into_raw(five); - /// Arc::increment_strong_count(ptr); + /// Arc::increment_strong_count_in(ptr, System); /// /// // This assertion is deterministic because we haven't shared /// // the `Arc` between threads. - /// let five = Arc::from_raw(ptr); + /// let five = Arc::from_raw_in(ptr, System); /// assert_eq!(2, Arc::strong_count(&five)); /// } /// ``` #[inline] - #[stable(feature = "arc_mutate_strong_count", since = "1.51.0")] - pub unsafe fn increment_strong_count(ptr: *const T) { + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn increment_strong_count_in(ptr: *const T, alloc: A) + where + A: Clone, + { // Retain Arc, but don't touch refcount by wrapping in ManuallyDrop - let arc = unsafe { mem::ManuallyDrop::new(Arc::::from_raw(ptr)) }; + let arc = unsafe { mem::ManuallyDrop::new(Arc::from_raw_in(ptr, alloc)) }; // Now increase refcount, but don't drop new refcount either let _arc_clone: mem::ManuallyDrop<_> = arc.clone(); } @@ -1214,35 +1696,39 @@ impl Arc { /// /// # Safety /// - /// The pointer must have been obtained through `Arc::into_raw`, and the + /// The pointer must have been obtained through `Arc::into_raw`, the /// associated `Arc` instance must be valid (i.e. the strong count must be at - /// least 1) when invoking this method. This method can be used to release the final + /// least 1) when invoking this method, and `ptr` must point to a block of memory + /// allocated by `alloc`. This method can be used to release the final /// `Arc` and backing storage, but **should not** be called after the final `Arc` has been /// released. /// /// # Examples /// /// ``` + /// #![feature(allocator_api)] + /// /// use std::sync::Arc; + /// use std::alloc::System; /// - /// let five = Arc::new(5); + /// let five = Arc::new_in(5, System); /// /// unsafe { /// let ptr = Arc::into_raw(five); - /// Arc::increment_strong_count(ptr); + /// Arc::increment_strong_count_in(ptr, System); /// /// // Those assertions are deterministic because we haven't shared /// // the `Arc` between threads. - /// let five = Arc::from_raw(ptr); + /// let five = Arc::from_raw_in(ptr, System); /// assert_eq!(2, Arc::strong_count(&five)); - /// Arc::decrement_strong_count(ptr); + /// Arc::decrement_strong_count_in(ptr, System); /// assert_eq!(1, Arc::strong_count(&five)); /// } /// ``` #[inline] - #[stable(feature = "arc_mutate_strong_count", since = "1.51.0")] - pub unsafe fn decrement_strong_count(ptr: *const T) { - unsafe { drop(Arc::from_raw(ptr)) }; + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn decrement_strong_count_in(ptr: *const T, alloc: A) { + unsafe { drop(Arc::from_raw_in(ptr, alloc)) }; } #[inline] @@ -1263,11 +1749,14 @@ impl Arc { unsafe { ptr::drop_in_place(Self::get_mut_unchecked(self)) }; // Drop the weak ref collectively held by all strong references - drop(Weak { ptr: self.ptr }); + // Take a reference to `self.alloc` instead of cloning because 1. it'll + // last long enough, and 2. you should be able to drop `Arc`s with + // unclonable allocators + drop(Weak { ptr: self.ptr, alloc: &self.alloc }); } /// Returns `true` if the two `Arc`s point to the same allocation in a vein similar to - /// [`ptr::eq`]. See [that function][`ptr::eq`] for caveats when comparing `dyn Trait` pointers. + /// [`ptr::eq`]. This function ignores the metadata of `dyn Trait` pointers. /// /// # Examples /// @@ -1287,7 +1776,7 @@ impl Arc { #[must_use] #[stable(feature = "ptr_eq", since = "1.17.0")] pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.ptr.as_ptr() == other.ptr.as_ptr() + this.ptr.as_ptr() as *const () == other.ptr.as_ptr() as *const () } } @@ -1345,40 +1834,42 @@ impl Arc { inner } +} +impl Arc { /// Allocates an `ArcInner` with sufficient space for an unsized inner value. + #[inline] #[cfg(not(no_global_oom_handling))] - unsafe fn allocate_for_ptr(ptr: *const T) -> *mut ArcInner { + unsafe fn allocate_for_ptr_in(ptr: *const T, alloc: &A) -> *mut ArcInner { // Allocate for the `ArcInner` using the given value. unsafe { - Self::allocate_for_layout( + Arc::allocate_for_layout( Layout::for_value(&*ptr), - |layout| Global.allocate(layout), + |layout| alloc.allocate(layout), |mem| mem.with_metadata_of(ptr as *const ArcInner), ) } } #[cfg(not(no_global_oom_handling))] - fn from_box(v: Box) -> Arc { + fn from_box_in(src: Box) -> Arc { unsafe { - let (box_unique, alloc) = Box::into_unique(v); - let bptr = box_unique.as_ptr(); - - let value_size = size_of_val(&*bptr); - let ptr = Self::allocate_for_ptr(bptr); + let value_size = size_of_val(&*src); + let ptr = Self::allocate_for_ptr_in(&*src, Box::allocator(&src)); // Copy value as bytes ptr::copy_nonoverlapping( - bptr as *const T as *const u8, + &*src as *const T as *const u8, &mut (*ptr).data as *mut _ as *mut u8, value_size, ); // Free the allocation without dropping its contents - box_free(box_unique, alloc); + let (bptr, alloc) = Box::into_raw_with_allocator(src); + let src = Box::from_raw(bptr as *mut mem::ManuallyDrop); + drop(src); - Self::from_ptr(ptr) + Self::from_ptr_in(ptr, alloc) } } } @@ -1391,7 +1882,7 @@ impl Arc<[T]> { Self::allocate_for_layout( Layout::array::(len).unwrap(), |layout| Global.allocate(layout), - |mem| ptr::slice_from_raw_parts_mut(mem as *mut T, len) as *mut ArcInner<[T]>, + |mem| ptr::slice_from_raw_parts_mut(mem.cast::(), len) as *mut ArcInner<[T]>, ) } } @@ -1460,6 +1951,21 @@ impl Arc<[T]> { } } +impl Arc<[T], A> { + /// Allocates an `ArcInner<[T]>` with the given length. + #[inline] + #[cfg(not(no_global_oom_handling))] + unsafe fn allocate_for_slice_in(len: usize, alloc: &A) -> *mut ArcInner<[T]> { + unsafe { + Arc::allocate_for_layout( + Layout::array::(len).unwrap(), + |layout| alloc.allocate(layout), + |mem| ptr::slice_from_raw_parts_mut(mem.cast::(), len) as *mut ArcInner<[T]>, + ) + } + } +} + /// Specialization trait used for `From<&[T]>`. #[cfg(not(no_global_oom_handling))] trait ArcFromSlice { @@ -1483,7 +1989,7 @@ impl ArcFromSlice for Arc<[T]> { } #[stable(feature = "rust1", since = "1.0.0")] -impl Clone for Arc { +impl Clone for Arc { /// Makes a clone of the `Arc` pointer. /// /// This creates another pointer to the same allocation, increasing the @@ -1499,7 +2005,7 @@ impl Clone for Arc { /// let _ = Arc::clone(&five); /// ``` #[inline] - fn clone(&self) -> Arc { + fn clone(&self) -> Arc { // Using a relaxed ordering is alright here, as knowledge of the // original reference prevents other threads from erroneously deleting // the object. @@ -1532,12 +2038,12 @@ impl Clone for Arc { abort(); } - unsafe { Self::from_inner(self.ptr) } + unsafe { Self::from_inner_in(self.ptr, self.alloc.clone()) } } } #[stable(feature = "rust1", since = "1.0.0")] -impl Deref for Arc { +impl Deref for Arc { type Target = T; #[inline] @@ -1549,7 +2055,7 @@ impl Deref for Arc { #[unstable(feature = "receiver_trait", issue = "none")] impl Receiver for Arc {} -impl Arc { +impl Arc { /// Makes a mutable reference into the given `Arc`. /// /// If there are other `Arc` pointers to the same allocation, then `make_mut` will @@ -1615,7 +2121,7 @@ impl Arc { if this.inner().strong.compare_exchange(1, 0, Acquire, Relaxed).is_err() { // Another strong pointer exists, so we must clone. // Pre-allocate memory to allow writing the cloned value directly. - let mut arc = Self::new_uninit(); + let mut arc = Self::new_uninit_in(this.alloc.clone()); unsafe { let data = Arc::get_mut_unchecked(&mut arc); (**this).write_clone_into_raw(data.as_mut_ptr()); @@ -1636,10 +2142,10 @@ impl Arc { // Materialize our own implicit weak pointer, so that it can clean // up the ArcInner as needed. - let _weak = Weak { ptr: this.ptr }; + let _weak = Weak { ptr: this.ptr, alloc: this.alloc.clone() }; // Can just steal the data, all that's left is Weaks - let mut arc = Self::new_uninit(); + let mut arc = Self::new_uninit_in(this.alloc.clone()); unsafe { let data = Arc::get_mut_unchecked(&mut arc); data.as_mut_ptr().copy_from_nonoverlapping(&**this, 1); @@ -1692,7 +2198,7 @@ impl Arc { } } -impl Arc { +impl Arc { /// Returns a mutable reference into the given `Arc`, if there are /// no other `Arc` or [`Weak`] pointers to the same allocation. /// @@ -1830,7 +2336,7 @@ impl Arc { } #[stable(feature = "rust1", since = "1.0.0")] -unsafe impl<#[may_dangle] T: ?Sized> Drop for Arc { +unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Arc { /// Drops the `Arc`. /// /// This will decrement the strong reference count. If the strong reference @@ -1901,7 +2407,7 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Arc { } } -impl Arc { +impl Arc { /// Attempt to downcast the `Arc` to a concrete type. /// /// # Examples @@ -1922,15 +2428,16 @@ impl Arc { /// ``` #[inline] #[stable(feature = "rc_downcast", since = "1.29.0")] - pub fn downcast(self) -> Result, Self> + pub fn downcast(self) -> Result, Self> where T: Any + Send + Sync, { if (*self).is::() { unsafe { let ptr = self.ptr.cast::>(); + let alloc = self.alloc.clone(); mem::forget(self); - Ok(Arc::from_inner(ptr)) + Ok(Arc::from_inner_in(ptr, alloc)) } } else { Err(self) @@ -1965,14 +2472,15 @@ impl Arc { /// [`downcast`]: Self::downcast #[inline] #[unstable(feature = "downcast_unchecked", issue = "90850")] - pub unsafe fn downcast_unchecked(self) -> Arc + pub unsafe fn downcast_unchecked(self) -> Arc where T: Any + Send + Sync, { unsafe { let ptr = self.ptr.cast::>(); + let alloc = self.alloc.clone(); mem::forget(self); - Arc::from_inner(ptr) + Arc::from_inner_in(ptr, alloc) } } } @@ -1991,11 +2499,43 @@ impl Weak { /// let empty: Weak = Weak::new(); /// assert!(empty.upgrade().is_none()); /// ``` + #[inline] #[stable(feature = "downgraded_weak", since = "1.10.0")] - #[rustc_const_unstable(feature = "const_weak_new", issue = "95091", reason = "recently added")] + #[rustc_const_stable(feature = "const_weak_new", since = "1.73.0")] #[must_use] pub const fn new() -> Weak { - Weak { ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::>(usize::MAX)) } } + Weak { + ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::>(usize::MAX)) }, + alloc: Global, + } + } +} + +impl Weak { + /// Constructs a new `Weak`, without allocating any memory, technically in the provided + /// allocator. + /// Calling [`upgrade`] on the return value always gives [`None`]. + /// + /// [`upgrade`]: Weak::upgrade + /// + /// # Examples + /// + /// ``` + /// #![feature(allocator_api)] + /// + /// use std::sync::Weak; + /// use std::alloc::System; + /// + /// let empty: Weak = Weak::new_in(System); + /// assert!(empty.upgrade().is_none()); + /// ``` + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub fn new_in(alloc: A) -> Weak { + Weak { + ptr: unsafe { NonNull::new_unchecked(ptr::invalid_mut::>(usize::MAX)) }, + alloc, + } } } @@ -2007,6 +2547,55 @@ struct WeakInner<'a> { } impl Weak { + /// Converts a raw pointer previously created by [`into_raw`] back into `Weak`. + /// + /// This can be used to safely get a strong reference (by calling [`upgrade`] + /// later) or to deallocate the weak count by dropping the `Weak`. + /// + /// It takes ownership of one weak reference (with the exception of pointers created by [`new`], + /// as these don't own anything; the method still works on them). + /// + /// # Safety + /// + /// The pointer must have originated from the [`into_raw`] and must still own its potential + /// weak reference. + /// + /// It is allowed for the strong count to be 0 at the time of calling this. Nevertheless, this + /// takes ownership of one weak reference currently represented as a raw pointer (the weak + /// count is not modified by this operation) and therefore it must be paired with a previous + /// call to [`into_raw`]. + /// # Examples + /// + /// ``` + /// use std::sync::{Arc, Weak}; + /// + /// let strong = Arc::new("hello".to_owned()); + /// + /// let raw_1 = Arc::downgrade(&strong).into_raw(); + /// let raw_2 = Arc::downgrade(&strong).into_raw(); + /// + /// assert_eq!(2, Arc::weak_count(&strong)); + /// + /// assert_eq!("hello", &*unsafe { Weak::from_raw(raw_1) }.upgrade().unwrap()); + /// assert_eq!(1, Arc::weak_count(&strong)); + /// + /// drop(strong); + /// + /// // Decrement the last weak count. + /// assert!(unsafe { Weak::from_raw(raw_2) }.upgrade().is_none()); + /// ``` + /// + /// [`new`]: Weak::new + /// [`into_raw`]: Weak::into_raw + /// [`upgrade`]: Weak::upgrade + #[inline] + #[stable(feature = "weak_into_raw", since = "1.45.0")] + pub unsafe fn from_raw(ptr: *const T) -> Self { + unsafe { Weak::from_raw_in(ptr, Global) } + } +} + +impl Weak { /// Returns a raw pointer to the object `T` pointed to by this `Weak`. /// /// The pointer is valid only if there are some strong references. The pointer may be dangling, @@ -2084,7 +2673,8 @@ impl Weak { result } - /// Converts a raw pointer previously created by [`into_raw`] back into `Weak`. + /// Converts a raw pointer previously created by [`into_raw`] back into `Weak` in the provided + /// allocator. /// /// This can be used to safely get a strong reference (by calling [`upgrade`] /// later) or to deallocate the weak count by dropping the `Weak`. @@ -2095,7 +2685,7 @@ impl Weak { /// # Safety /// /// The pointer must have originated from the [`into_raw`] and must still own its potential - /// weak reference. + /// weak reference, and must point to a block of memory allocated by `alloc`. /// /// It is allowed for the strong count to be 0 at the time of calling this. Nevertheless, this /// takes ownership of one weak reference currently represented as a raw pointer (the weak @@ -2125,8 +2715,9 @@ impl Weak { /// [`new`]: Weak::new /// [`into_raw`]: Weak::into_raw /// [`upgrade`]: Weak::upgrade - #[stable(feature = "weak_into_raw", since = "1.45.0")] - pub unsafe fn from_raw(ptr: *const T) -> Self { + #[inline] + #[unstable(feature = "allocator_api", issue = "32838")] + pub unsafe fn from_raw_in(ptr: *const T, alloc: A) -> Self { // See Weak::as_ptr for context on how the input pointer is derived. let ptr = if is_dangling(ptr as *mut T) { @@ -2142,11 +2733,11 @@ impl Weak { }; // SAFETY: we now have recovered the original Weak pointer, so can create the Weak. - Weak { ptr: unsafe { NonNull::new_unchecked(ptr) } } + Weak { ptr: unsafe { NonNull::new_unchecked(ptr) }, alloc } } } -impl Weak { +impl Weak { /// Attempts to upgrade the `Weak` pointer to an [`Arc`], delaying /// dropping of the inner value if successful. /// @@ -2173,28 +2764,35 @@ impl Weak { #[must_use = "this returns a new `Arc`, \ without modifying the original weak pointer"] #[stable(feature = "arc_weak", since = "1.4.0")] - pub fn upgrade(&self) -> Option> { + pub fn upgrade(&self) -> Option> + where + A: Clone, + { + #[inline] + fn checked_increment(n: usize) -> Option { + // Any write of 0 we can observe leaves the field in permanently zero state. + if n == 0 { + return None; + } + // See comments in `Arc::clone` for why we do this (for `mem::forget`). + assert!(n <= MAX_REFCOUNT, "{}", INTERNAL_OVERFLOW_ERROR); + Some(n + 1) + } + // We use a CAS loop to increment the strong count instead of a // fetch_add as this function should never take the reference count // from zero to one. - self.inner()? - .strong - // Relaxed is fine for the failure case because we don't have any expectations about the new state. - // Acquire is necessary for the success case to synchronise with `Arc::new_cyclic`, when the inner - // value can be initialized after `Weak` references have already been created. In that case, we - // expect to observe the fully initialized value. - .fetch_update(Acquire, Relaxed, |n| { - // Any write of 0 we can observe leaves the field in permanently zero state. - if n == 0 { - return None; - } - // See comments in `Arc::clone` for why we do this (for `mem::forget`). - assert!(n <= MAX_REFCOUNT, "{}", INTERNAL_OVERFLOW_ERROR); - Some(n + 1) - }) - .ok() - // null checked above - .map(|_| unsafe { Arc::from_inner(self.ptr) }) + // + // Relaxed is fine for the failure case because we don't have any expectations about the new state. + // Acquire is necessary for the success case to synchronise with `Arc::new_cyclic`, when the inner + // value can be initialized after `Weak` references have already been created. In that case, we + // expect to observe the fully initialized value. + if self.inner()?.strong.fetch_update(Acquire, Relaxed, checked_increment).is_ok() { + // SAFETY: pointer is not null, verified in checked_increment + unsafe { Some(Arc::from_inner_in(self.ptr, self.alloc.clone())) } + } else { + None + } } /// Gets the number of strong (`Arc`) pointers pointing to this allocation. @@ -2220,22 +2818,22 @@ impl Weak { #[must_use] #[stable(feature = "weak_counts", since = "1.41.0")] pub fn weak_count(&self) -> usize { - self.inner() - .map(|inner| { - let weak = inner.weak.load(Acquire); - let strong = inner.strong.load(Acquire); - if strong == 0 { - 0 - } else { - // Since we observed that there was at least one strong pointer - // after reading the weak count, we know that the implicit weak - // reference (present whenever any strong references are alive) - // was still around when we observed the weak count, and can - // therefore safely subtract it. - weak - 1 - } - }) - .unwrap_or(0) + if let Some(inner) = self.inner() { + let weak = inner.weak.load(Acquire); + let strong = inner.strong.load(Acquire); + if strong == 0 { + 0 + } else { + // Since we observed that there was at least one strong pointer + // after reading the weak count, we know that the implicit weak + // reference (present whenever any strong references are alive) + // was still around when we observed the weak count, and can + // therefore safely subtract it. + weak - 1 + } + } else { + 0 + } } /// Returns `None` when the pointer is dangling and there is no allocated `ArcInner`, @@ -2256,8 +2854,8 @@ impl Weak { } /// Returns `true` if the two `Weak`s point to the same allocation similar to [`ptr::eq`], or if - /// both don't point to any allocation (because they were created with `Weak::new()`). See [that - /// function][`ptr::eq`] for caveats when comparing `dyn Trait` pointers. + /// both don't point to any allocation (because they were created with `Weak::new()`). However, + /// this function ignores the metadata of `dyn Trait` pointers. /// /// # Notes /// @@ -2300,12 +2898,12 @@ impl Weak { #[must_use] #[stable(feature = "weak_ptr_eq", since = "1.39.0")] pub fn ptr_eq(&self, other: &Self) -> bool { - self.ptr.as_ptr() == other.ptr.as_ptr() + ptr::eq(self.ptr.as_ptr() as *const (), other.ptr.as_ptr() as *const ()) } } #[stable(feature = "arc_weak", since = "1.4.0")] -impl Clone for Weak { +impl Clone for Weak { /// Makes a clone of the `Weak` pointer that points to the same allocation. /// /// # Examples @@ -2318,11 +2916,11 @@ impl Clone for Weak { /// let _ = Weak::clone(&weak_five); /// ``` #[inline] - fn clone(&self) -> Weak { + fn clone(&self) -> Weak { let inner = if let Some(inner) = self.inner() { inner } else { - return Weak { ptr: self.ptr }; + return Weak { ptr: self.ptr, alloc: self.alloc.clone() }; }; // See comments in Arc::clone() for why this is relaxed. This can use a // fetch_add (ignoring the lock) because the weak count is only locked @@ -2335,7 +2933,7 @@ impl Clone for Weak { abort(); } - Weak { ptr: self.ptr } + Weak { ptr: self.ptr, alloc: self.alloc.clone() } } } @@ -2361,7 +2959,7 @@ impl Default for Weak { } #[stable(feature = "arc_weak", since = "1.4.0")] -unsafe impl<#[may_dangle] T: ?Sized> Drop for Weak { +unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Weak { /// Drops the `Weak` pointer. /// /// # Examples @@ -2399,25 +2997,27 @@ unsafe impl<#[may_dangle] T: ?Sized> Drop for Weak { if inner.weak.fetch_sub(1, Release) == 1 { acquire!(inner.weak); - unsafe { Global.deallocate(self.ptr.cast(), Layout::for_value_raw(self.ptr.as_ptr())) } + unsafe { + self.alloc.deallocate(self.ptr.cast(), Layout::for_value_raw(self.ptr.as_ptr())) + } } } } #[stable(feature = "rust1", since = "1.0.0")] -trait ArcEqIdent { - fn eq(&self, other: &Arc) -> bool; - fn ne(&self, other: &Arc) -> bool; +trait ArcEqIdent { + fn eq(&self, other: &Arc) -> bool; + fn ne(&self, other: &Arc) -> bool; } #[stable(feature = "rust1", since = "1.0.0")] -impl ArcEqIdent for Arc { +impl ArcEqIdent for Arc { #[inline] - default fn eq(&self, other: &Arc) -> bool { + default fn eq(&self, other: &Arc) -> bool { **self == **other } #[inline] - default fn ne(&self, other: &Arc) -> bool { + default fn ne(&self, other: &Arc) -> bool { **self != **other } } @@ -2430,20 +3030,20 @@ impl ArcEqIdent for Arc { /// /// We can only do this when `T: Eq` as a `PartialEq` might be deliberately irreflexive. #[stable(feature = "rust1", since = "1.0.0")] -impl ArcEqIdent for Arc { +impl ArcEqIdent for Arc { #[inline] - fn eq(&self, other: &Arc) -> bool { + fn eq(&self, other: &Arc) -> bool { Arc::ptr_eq(self, other) || **self == **other } #[inline] - fn ne(&self, other: &Arc) -> bool { + fn ne(&self, other: &Arc) -> bool { !Arc::ptr_eq(self, other) && **self != **other } } #[stable(feature = "rust1", since = "1.0.0")] -impl PartialEq for Arc { +impl PartialEq for Arc { /// Equality for two `Arc`s. /// /// Two `Arc`s are equal if their inner values are equal, even if they are @@ -2462,7 +3062,7 @@ impl PartialEq for Arc { /// assert!(five == Arc::new(5)); /// ``` #[inline] - fn eq(&self, other: &Arc) -> bool { + fn eq(&self, other: &Arc) -> bool { ArcEqIdent::eq(self, other) } @@ -2483,13 +3083,13 @@ impl PartialEq for Arc { /// assert!(five != Arc::new(6)); /// ``` #[inline] - fn ne(&self, other: &Arc) -> bool { + fn ne(&self, other: &Arc) -> bool { ArcEqIdent::ne(self, other) } } #[stable(feature = "rust1", since = "1.0.0")] -impl PartialOrd for Arc { +impl PartialOrd for Arc { /// Partial comparison for two `Arc`s. /// /// The two are compared by calling `partial_cmp()` on their inner values. @@ -2504,7 +3104,7 @@ impl PartialOrd for Arc { /// /// assert_eq!(Some(Ordering::Less), five.partial_cmp(&Arc::new(6))); /// ``` - fn partial_cmp(&self, other: &Arc) -> Option { + fn partial_cmp(&self, other: &Arc) -> Option { (**self).partial_cmp(&**other) } @@ -2521,7 +3121,7 @@ impl PartialOrd for Arc { /// /// assert!(five < Arc::new(6)); /// ``` - fn lt(&self, other: &Arc) -> bool { + fn lt(&self, other: &Arc) -> bool { *(*self) < *(*other) } @@ -2538,7 +3138,7 @@ impl PartialOrd for Arc { /// /// assert!(five <= Arc::new(5)); /// ``` - fn le(&self, other: &Arc) -> bool { + fn le(&self, other: &Arc) -> bool { *(*self) <= *(*other) } @@ -2555,7 +3155,7 @@ impl PartialOrd for Arc { /// /// assert!(five > Arc::new(4)); /// ``` - fn gt(&self, other: &Arc) -> bool { + fn gt(&self, other: &Arc) -> bool { *(*self) > *(*other) } @@ -2572,12 +3172,12 @@ impl PartialOrd for Arc { /// /// assert!(five >= Arc::new(5)); /// ``` - fn ge(&self, other: &Arc) -> bool { + fn ge(&self, other: &Arc) -> bool { *(*self) >= *(*other) } } #[stable(feature = "rust1", since = "1.0.0")] -impl Ord for Arc { +impl Ord for Arc { /// Comparison for two `Arc`s. /// /// The two are compared by calling `cmp()` on their inner values. @@ -2592,29 +3192,29 @@ impl Ord for Arc { /// /// assert_eq!(Ordering::Less, five.cmp(&Arc::new(6))); /// ``` - fn cmp(&self, other: &Arc) -> Ordering { + fn cmp(&self, other: &Arc) -> Ordering { (**self).cmp(&**other) } } #[stable(feature = "rust1", since = "1.0.0")] -impl Eq for Arc {} +impl Eq for Arc {} #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Display for Arc { +impl fmt::Display for Arc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&**self, f) } } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Debug for Arc { +impl fmt::Debug for Arc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f) } } #[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Pointer for Arc { +impl fmt::Pointer for Arc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Pointer::fmt(&(&**self as *const T), f) } @@ -2639,7 +3239,7 @@ impl Default for Arc { } #[stable(feature = "rust1", since = "1.0.0")] -impl Hash for Arc { +impl Hash for Arc { fn hash(&self, state: &mut H) { (**self).hash(state) } @@ -2726,7 +3326,7 @@ impl From for Arc { #[cfg(not(no_global_oom_handling))] #[stable(feature = "shared_from_slice", since = "1.21.0")] -impl From> for Arc { +impl From> for Arc { /// Move a boxed object to a new, reference-counted allocation. /// /// # Example @@ -2738,14 +3338,14 @@ impl From> for Arc { /// assert_eq!("eggplant", &shared[..]); /// ``` #[inline] - fn from(v: Box) -> Arc { - Arc::from_box(v) + fn from(v: Box) -> Arc { + Arc::from_box_in(v) } } #[cfg(not(no_global_oom_handling))] #[stable(feature = "shared_from_slice", since = "1.21.0")] -impl From> for Arc<[T]> { +impl From> for Arc<[T], A> { /// Allocate a reference-counted slice and move `v`'s items into it. /// /// # Example @@ -2757,12 +3357,18 @@ impl From> for Arc<[T]> { /// assert_eq!(&[1, 2, 3], &shared[..]); /// ``` #[inline] - fn from(mut v: Vec) -> Arc<[T]> { + fn from(v: Vec) -> Arc<[T], A> { unsafe { - let rc = Arc::copy_from_slice(&v); - // Allow the Vec to free its memory, but not destroy its contents - v.set_len(0); - rc + let (vec_ptr, len, cap, alloc) = v.into_raw_parts_with_alloc(); + + let rc_ptr = Self::allocate_for_slice_in(len, &alloc); + ptr::copy_nonoverlapping(vec_ptr, &mut (*rc_ptr).data as *mut [T] as *mut T, len); + + // Create a `Vec` with length 0, to deallocate the buffer + // without dropping its contents or the allocator + let _ = Vec::from_raw_parts_in(vec_ptr, 0, cap, &alloc); + + Self::from_ptr_in(rc_ptr, alloc) } } } @@ -2814,12 +3420,13 @@ impl From> for Arc<[u8]> { } #[stable(feature = "boxed_slice_try_from", since = "1.43.0")] -impl TryFrom> for Arc<[T; N]> { - type Error = Arc<[T]>; +impl TryFrom> for Arc<[T; N], A> { + type Error = Arc<[T], A>; - fn try_from(boxed_slice: Arc<[T]>) -> Result { + fn try_from(boxed_slice: Arc<[T], A>) -> Result { if boxed_slice.len() == N { - Ok(unsafe { Arc::from_raw(Arc::into_raw(boxed_slice) as *mut [T; N]) }) + let alloc = boxed_slice.alloc.clone(); + Ok(unsafe { Arc::from_raw_in(Arc::into_raw(boxed_slice) as *mut [T; N], alloc) }) } else { Err(boxed_slice) } @@ -2912,21 +3519,21 @@ impl> ToArcSlice for I { } #[stable(feature = "rust1", since = "1.0.0")] -impl borrow::Borrow for Arc { +impl borrow::Borrow for Arc { fn borrow(&self) -> &T { &**self } } #[stable(since = "1.5.0", feature = "smart_ptr_as_ref")] -impl AsRef for Arc { +impl AsRef for Arc { fn as_ref(&self) -> &T { &**self } } #[stable(feature = "pin", since = "1.33.0")] -impl Unpin for Arc {} +impl Unpin for Arc {} /// Get the offset within an `ArcInner` for the payload behind a pointer. /// @@ -2966,7 +3573,7 @@ impl core::error::Error for Arc { core::error::Error::source(&**self) } - fn provide<'a>(&'a self, req: &mut core::any::Demand<'a>) { + fn provide<'a>(&'a self, req: &mut core::error::Request<'a>) { core::error::Error::provide(&**self, req); } } diff --git a/library/alloc/src/vec/drain_filter.rs b/library/alloc/src/vec/drain_filter.rs deleted file mode 100644 index 21b0902346206..0000000000000 --- a/library/alloc/src/vec/drain_filter.rs +++ /dev/null @@ -1,197 +0,0 @@ -use crate::alloc::{Allocator, Global}; -use core::mem::{ManuallyDrop, SizedTypeProperties}; -use core::ptr; -use core::slice; - -use super::Vec; - -/// An iterator which uses a closure to determine if an element should be removed. -/// -/// This struct is created by [`Vec::drain_filter`]. -/// See its documentation for more. -/// -/// # Example -/// -/// ``` -/// #![feature(drain_filter)] -/// -/// let mut v = vec![0, 1, 2]; -/// let iter: std::vec::DrainFilter<'_, _, _> = v.drain_filter(|x| *x % 2 == 0); -/// ``` -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -#[derive(Debug)] -pub struct DrainFilter< - 'a, - T, - F, - #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, -> where - F: FnMut(&mut T) -> bool, -{ - pub(super) vec: &'a mut Vec, - /// The index of the item that will be inspected by the next call to `next`. - pub(super) idx: usize, - /// The number of items that have been drained (removed) thus far. - pub(super) del: usize, - /// The original length of `vec` prior to draining. - pub(super) old_len: usize, - /// The filter test predicate. - pub(super) pred: F, - /// A flag that indicates a panic has occurred in the filter test predicate. - /// This is used as a hint in the drop implementation to prevent consumption - /// of the remainder of the `DrainFilter`. Any unprocessed items will be - /// backshifted in the `vec`, but no further items will be dropped or - /// tested by the filter predicate. - pub(super) panic_flag: bool, -} - -impl DrainFilter<'_, T, F, A> -where - F: FnMut(&mut T) -> bool, -{ - /// Returns a reference to the underlying allocator. - #[unstable(feature = "allocator_api", issue = "32838")] - #[inline] - pub fn allocator(&self) -> &A { - self.vec.allocator() - } - - /// Keep unyielded elements in the source `Vec`. - /// - /// # Examples - /// - /// ``` - /// #![feature(drain_filter)] - /// #![feature(drain_keep_rest)] - /// - /// let mut vec = vec!['a', 'b', 'c']; - /// let mut drain = vec.drain_filter(|_| true); - /// - /// assert_eq!(drain.next().unwrap(), 'a'); - /// - /// // This call keeps 'b' and 'c' in the vec. - /// drain.keep_rest(); - /// - /// // If we wouldn't call `keep_rest()`, - /// // `vec` would be empty. - /// assert_eq!(vec, ['b', 'c']); - /// ``` - #[unstable(feature = "drain_keep_rest", issue = "101122")] - pub fn keep_rest(self) { - // At this moment layout looks like this: - // - // _____________________/-- old_len - // / \ - // [kept] [yielded] [tail] - // \_______/ ^-- idx - // \-- del - // - // Normally `Drop` impl would drop [tail] (via .for_each(drop), ie still calling `pred`) - // - // 1. Move [tail] after [kept] - // 2. Update length of the original vec to `old_len - del` - // a. In case of ZST, this is the only thing we want to do - // 3. Do *not* drop self, as everything is put in a consistent state already, there is nothing to do - let mut this = ManuallyDrop::new(self); - - unsafe { - // ZSTs have no identity, so we don't need to move them around. - if !T::IS_ZST && this.idx < this.old_len && this.del > 0 { - let ptr = this.vec.as_mut_ptr(); - let src = ptr.add(this.idx); - let dst = src.sub(this.del); - let tail_len = this.old_len - this.idx; - src.copy_to(dst, tail_len); - } - - let new_len = this.old_len - this.del; - this.vec.set_len(new_len); - } - } -} - -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -impl Iterator for DrainFilter<'_, T, F, A> -where - F: FnMut(&mut T) -> bool, -{ - type Item = T; - - fn next(&mut self) -> Option { - unsafe { - while self.idx < self.old_len { - let i = self.idx; - let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len); - self.panic_flag = true; - let drained = (self.pred)(&mut v[i]); - self.panic_flag = false; - // Update the index *after* the predicate is called. If the index - // is updated prior and the predicate panics, the element at this - // index would be leaked. - self.idx += 1; - if drained { - self.del += 1; - return Some(ptr::read(&v[i])); - } else if self.del > 0 { - let del = self.del; - let src: *const T = &v[i]; - let dst: *mut T = &mut v[i - del]; - ptr::copy_nonoverlapping(src, dst, 1); - } - } - None - } - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(self.old_len - self.idx)) - } -} - -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -impl Drop for DrainFilter<'_, T, F, A> -where - F: FnMut(&mut T) -> bool, -{ - fn drop(&mut self) { - struct BackshiftOnDrop<'a, 'b, T, F, A: Allocator> - where - F: FnMut(&mut T) -> bool, - { - drain: &'b mut DrainFilter<'a, T, F, A>, - } - - impl<'a, 'b, T, F, A: Allocator> Drop for BackshiftOnDrop<'a, 'b, T, F, A> - where - F: FnMut(&mut T) -> bool, - { - fn drop(&mut self) { - unsafe { - if self.drain.idx < self.drain.old_len && self.drain.del > 0 { - // This is a pretty messed up state, and there isn't really an - // obviously right thing to do. We don't want to keep trying - // to execute `pred`, so we just backshift all the unprocessed - // elements and tell the vec that they still exist. The backshift - // is required to prevent a double-drop of the last successfully - // drained item prior to a panic in the predicate. - let ptr = self.drain.vec.as_mut_ptr(); - let src = ptr.add(self.drain.idx); - let dst = src.sub(self.drain.del); - let tail_len = self.drain.old_len - self.drain.idx; - src.copy_to(dst, tail_len); - } - self.drain.vec.set_len(self.drain.old_len - self.drain.del); - } - } - } - - let backshift = BackshiftOnDrop { drain: self }; - - // Attempt to consume any remaining elements if the filter predicate - // has not yet panicked. We'll backshift any remaining elements - // whether we've already panicked or if the consumption here panics. - if !backshift.drain.panic_flag { - backshift.drain.for_each(drop); - } - } -} diff --git a/library/alloc/src/vec/extract_if.rs b/library/alloc/src/vec/extract_if.rs new file mode 100644 index 0000000000000..118cfdb36b9c2 --- /dev/null +++ b/library/alloc/src/vec/extract_if.rs @@ -0,0 +1,113 @@ +use crate::alloc::{Allocator, Global}; +use core::ptr; +use core::slice; + +use super::Vec; + +/// An iterator which uses a closure to determine if an element should be removed. +/// +/// This struct is created by [`Vec::extract_if`]. +/// See its documentation for more. +/// +/// # Example +/// +/// ``` +/// #![feature(extract_if)] +/// +/// let mut v = vec![0, 1, 2]; +/// let iter: std::vec::ExtractIf<'_, _, _> = v.extract_if(|x| *x % 2 == 0); +/// ``` +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ExtractIf< + 'a, + T, + F, + #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, +> where + F: FnMut(&mut T) -> bool, +{ + pub(super) vec: &'a mut Vec, + /// The index of the item that will be inspected by the next call to `next`. + pub(super) idx: usize, + /// The number of items that have been drained (removed) thus far. + pub(super) del: usize, + /// The original length of `vec` prior to draining. + pub(super) old_len: usize, + /// The filter test predicate. + pub(super) pred: F, +} + +impl ExtractIf<'_, T, F, A> +where + F: FnMut(&mut T) -> bool, +{ + /// Returns a reference to the underlying allocator. + #[unstable(feature = "allocator_api", issue = "32838")] + #[inline] + pub fn allocator(&self) -> &A { + self.vec.allocator() + } +} + +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +impl Iterator for ExtractIf<'_, T, F, A> +where + F: FnMut(&mut T) -> bool, +{ + type Item = T; + + fn next(&mut self) -> Option { + unsafe { + while self.idx < self.old_len { + let i = self.idx; + let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len); + let drained = (self.pred)(&mut v[i]); + // Update the index *after* the predicate is called. If the index + // is updated prior and the predicate panics, the element at this + // index would be leaked. + self.idx += 1; + if drained { + self.del += 1; + return Some(ptr::read(&v[i])); + } else if self.del > 0 { + let del = self.del; + let src: *const T = &v[i]; + let dst: *mut T = &mut v[i - del]; + ptr::copy_nonoverlapping(src, dst, 1); + } + } + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.old_len - self.idx)) + } +} + +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +impl Drop for ExtractIf<'_, T, F, A> +where + F: FnMut(&mut T) -> bool, +{ + fn drop(&mut self) { + unsafe { + if self.idx < self.old_len && self.del > 0 { + // This is a pretty messed up state, and there isn't really an + // obviously right thing to do. We don't want to keep trying + // to execute `pred`, so we just backshift all the unprocessed + // elements and tell the vec that they still exist. The backshift + // is required to prevent a double-drop of the last successfully + // drained item prior to a panic in the predicate. + let ptr = self.vec.as_mut_ptr(); + let src = ptr.add(self.idx); + let dst = src.sub(self.del); + let tail_len = self.old_len - self.idx; + src.copy_to(dst, tail_len); + } + self.vec.set_len(self.old_len - self.del); + } + } +} diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 82f30a26d41c5..e45ddc7896beb 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -71,10 +71,10 @@ use crate::boxed::Box; use crate::collections::TryReserveError; use crate::raw_vec::RawVec; -#[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] -pub use self::drain_filter::DrainFilter; +#[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] +pub use self::extract_if::ExtractIf; -mod drain_filter; +mod extract_if; #[cfg(not(no_global_oom_handling))] #[stable(feature = "vec_splice", since = "1.21.0")] @@ -213,7 +213,7 @@ mod spec_extend; /// /// # Indexing /// -/// The `Vec` type allows to access values by index, because it implements the +/// The `Vec` type allows access to values by index, because it implements the /// [`Index`] trait. An example will be more explicit: /// /// ``` @@ -560,22 +560,20 @@ impl Vec { /// Using memory that was allocated elsewhere: /// /// ```rust - /// #![feature(allocator_api)] - /// - /// use std::alloc::{AllocError, Allocator, Global, Layout}; + /// use std::alloc::{alloc, Layout}; /// /// fn main() { /// let layout = Layout::array::(16).expect("overflow cannot happen"); /// /// let vec = unsafe { - /// let mem = match Global.allocate(layout) { - /// Ok(mem) => mem.cast::().as_ptr(), - /// Err(AllocError) => return, - /// }; + /// let mem = alloc(layout).cast::(); + /// if mem.is_null() { + /// return; + /// } /// /// mem.write(1_000_000); /// - /// Vec::from_raw_parts_in(mem, 1, 16, Global) + /// Vec::from_raw_parts(mem, 1, 16) /// }; /// /// assert_eq!(vec, &[1_000_000]); @@ -758,19 +756,22 @@ impl Vec { /// Using memory that was allocated elsewhere: /// /// ```rust - /// use std::alloc::{alloc, Layout}; + /// #![feature(allocator_api)] + /// + /// use std::alloc::{AllocError, Allocator, Global, Layout}; /// /// fn main() { /// let layout = Layout::array::(16).expect("overflow cannot happen"); + /// /// let vec = unsafe { - /// let mem = alloc(layout).cast::(); - /// if mem.is_null() { - /// return; - /// } + /// let mem = match Global.allocate(layout) { + /// Ok(mem) => mem.cast::().as_ptr(), + /// Err(AllocError) => return, + /// }; /// /// mem.write(1_000_000); /// - /// Vec::from_raw_parts(mem, 1, 16) + /// Vec::from_raw_parts_in(mem, 1, 16, Global) /// }; /// /// assert_eq!(vec, &[1_000_000]); @@ -2355,7 +2356,7 @@ impl Vec { let len = self.len(); if new_len > len { - self.extend_with(new_len - len, ExtendElement(value)) + self.extend_with(new_len - len, value) } else { self.truncate(new_len); } @@ -2469,26 +2470,10 @@ impl Vec<[T; N], A> { } } -// This code generalizes `extend_with_{element,default}`. -trait ExtendWith { - fn next(&mut self) -> T; - fn last(self) -> T; -} - -struct ExtendElement(T); -impl ExtendWith for ExtendElement { - fn next(&mut self) -> T { - self.0.clone() - } - fn last(self) -> T { - self.0 - } -} - -impl Vec { +impl Vec { #[cfg(not(no_global_oom_handling))] - /// Extend the vector by `n` values, using the given generator. - fn extend_with>(&mut self, n: usize, mut value: E) { + /// Extend the vector by `n` clones of value. + fn extend_with(&mut self, n: usize, value: T) { self.reserve(n); unsafe { @@ -2500,15 +2485,15 @@ impl Vec { // Write all elements except the last one for _ in 1..n { - ptr::write(ptr, value.next()); + ptr::write(ptr, value.clone()); ptr = ptr.add(1); - // Increment the length in every step in case next() panics + // Increment the length in every step in case clone() panics local_len.increment_len(1); } if n > 0 { // We can write the last element directly without cloning needlessly - ptr::write(ptr, value.last()); + ptr::write(ptr, value); local_len.increment_len(1); } @@ -2662,7 +2647,6 @@ impl Clone for Vec { /// as required by the `core::borrow::Borrow` implementation. /// /// ``` -/// #![feature(build_hasher_simple_hash_one)] /// use std::hash::BuildHasher; /// /// let b = std::collections::hash_map::RandomState::new(); @@ -2909,6 +2893,12 @@ impl Vec { /// If the closure returns false, the element will remain in the vector and will not be yielded /// by the iterator. /// + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// Use [`retain`] with a negated predicate if you do not need the returned iterator. + /// + /// [`retain`]: Vec::retain + /// /// Using this method is equivalent to the following code: /// /// ``` @@ -2927,10 +2917,10 @@ impl Vec { /// # assert_eq!(vec, vec![1, 4, 5]); /// ``` /// - /// But `drain_filter` is easier to use. `drain_filter` is also more efficient, + /// But `extract_if` is easier to use. `extract_if` is also more efficient, /// because it can backshift the elements of the array in bulk. /// - /// Note that `drain_filter` also lets you mutate every element in the filter closure, + /// Note that `extract_if` also lets you mutate every element in the filter closure, /// regardless of whether you choose to keep or remove it. /// /// # Examples @@ -2938,17 +2928,17 @@ impl Vec { /// Splitting an array into evens and odds, reusing the original allocation: /// /// ``` - /// #![feature(drain_filter)] + /// #![feature(extract_if)] /// let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15]; /// - /// let evens = numbers.drain_filter(|x| *x % 2 == 0).collect::>(); + /// let evens = numbers.extract_if(|x| *x % 2 == 0).collect::>(); /// let odds = numbers; /// /// assert_eq!(evens, vec![2, 4, 6, 8, 14]); /// assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]); /// ``` - #[unstable(feature = "drain_filter", reason = "recently added", issue = "43244")] - pub fn drain_filter(&mut self, filter: F) -> DrainFilter<'_, T, F, A> + #[unstable(feature = "extract_if", reason = "recently added", issue = "43244")] + pub fn extract_if(&mut self, filter: F) -> ExtractIf<'_, T, F, A> where F: FnMut(&mut T) -> bool, { @@ -2959,7 +2949,7 @@ impl Vec { self.set_len(0); } - DrainFilter { vec: self, idx: 0, del: 0, old_len, pred: filter, panic_flag: false } + ExtractIf { vec: self, idx: 0, del: 0, old_len, pred: filter } } } @@ -2971,7 +2961,7 @@ impl Vec { /// [`copy_from_slice`]: slice::copy_from_slice #[cfg(not(no_global_oom_handling))] #[stable(feature = "extend_ref", since = "1.2.0")] -impl<'a, T: Copy + 'a, A: Allocator + 'a> Extend<&'a T> for Vec { +impl<'a, T: Copy + 'a, A: Allocator> Extend<&'a T> for Vec { fn extend>(&mut self, iter: I) { self.spec_extend(iter.into_iter()) } @@ -2989,9 +2979,14 @@ impl<'a, T: Copy + 'a, A: Allocator + 'a> Extend<&'a T> for Vec { /// Implements comparison of vectors, [lexicographically](Ord#lexicographical-comparison). #[stable(feature = "rust1", since = "1.0.0")] -impl PartialOrd for Vec { +impl PartialOrd> for Vec +where + T: PartialOrd, + A1: Allocator, + A2: Allocator, +{ #[inline] - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Vec) -> Option { PartialOrd::partial_cmp(&**self, &**other) } } diff --git a/library/alloc/src/vec/spec_extend.rs b/library/alloc/src/vec/spec_extend.rs index 56065ce565bfc..e2f865d0f7167 100644 --- a/library/alloc/src/vec/spec_extend.rs +++ b/library/alloc/src/vec/spec_extend.rs @@ -36,7 +36,7 @@ impl SpecExtend> for Vec { } } -impl<'a, T: 'a, I, A: Allocator + 'a> SpecExtend<&'a T, I> for Vec +impl<'a, T: 'a, I, A: Allocator> SpecExtend<&'a T, I> for Vec where I: Iterator, T: Clone, @@ -46,7 +46,7 @@ where } } -impl<'a, T: 'a, A: Allocator + 'a> SpecExtend<&'a T, slice::Iter<'a, T>> for Vec +impl<'a, T: 'a, A: Allocator> SpecExtend<&'a T, slice::Iter<'a, T>> for Vec where T: Copy, { diff --git a/library/alloc/src/vec/spec_from_elem.rs b/library/alloc/src/vec/spec_from_elem.rs index ff364c033ee98..da43d17bf3624 100644 --- a/library/alloc/src/vec/spec_from_elem.rs +++ b/library/alloc/src/vec/spec_from_elem.rs @@ -3,7 +3,7 @@ use core::ptr; use crate::alloc::Allocator; use crate::raw_vec::RawVec; -use super::{ExtendElement, IsZero, Vec}; +use super::{IsZero, Vec}; // Specialization trait used for Vec::from_elem pub(super) trait SpecFromElem: Sized { @@ -13,7 +13,7 @@ pub(super) trait SpecFromElem: Sized { impl SpecFromElem for T { default fn from_elem(elem: Self, n: usize, alloc: A) -> Vec { let mut v = Vec::with_capacity_in(n, alloc); - v.extend_with(n, ExtendElement(elem)); + v.extend_with(n, elem); v } } @@ -25,7 +25,7 @@ impl SpecFromElem for T { return Vec { buf: RawVec::with_capacity_zeroed_in(n, alloc), len: n }; } let mut v = Vec::with_capacity_in(n, alloc); - v.extend_with(n, ExtendElement(elem)); + v.extend_with(n, elem); v } } diff --git a/library/alloc/tests/autotraits.rs b/library/alloc/tests/autotraits.rs index 879e32b3fa309..6a8e55bff23d9 100644 --- a/library/alloc/tests/autotraits.rs +++ b/library/alloc/tests/autotraits.rs @@ -55,7 +55,7 @@ fn test_btree_map() { require_send_sync(async { let _v = None::< - alloc::collections::btree_map::DrainFilter< + alloc::collections::btree_map::ExtractIf< '_, &u32, &u32, @@ -149,7 +149,7 @@ fn test_btree_set() { }); require_send_sync(async { - let _v = None:: bool>>; + let _v = None:: bool>>; async {}.await; }); @@ -238,7 +238,7 @@ fn test_linked_list() { /* require_send_sync(async { let _v = - None:: bool>>; + None:: bool>>; async {}.await; }); */ diff --git a/library/alloc/tests/lib.rs b/library/alloc/tests/lib.rs index 0eca4c9bb07aa..aa7a331b368be 100644 --- a/library/alloc/tests/lib.rs +++ b/library/alloc/tests/lib.rs @@ -1,7 +1,7 @@ #![feature(allocator_api)] #![feature(alloc_layout_extra)] #![feature(assert_matches)] -#![feature(btree_drain_filter)] +#![feature(btree_extract_if)] #![feature(cow_is_borrowed)] #![feature(const_cow_is_borrowed)] #![feature(const_heap)] @@ -10,7 +10,7 @@ #![feature(const_ptr_write)] #![feature(const_try)] #![feature(core_intrinsics)] -#![feature(drain_filter)] +#![feature(extract_if)] #![feature(exact_size_is_empty)] #![feature(linked_list_cursors)] #![feature(map_try_insert)] diff --git a/library/alloc/tests/slice.rs b/library/alloc/tests/slice.rs index 9aa5575ca938b..784839a3ffa42 100644 --- a/library/alloc/tests/slice.rs +++ b/library/alloc/tests/slice.rs @@ -1418,6 +1418,7 @@ fn test_box_slice_clone() { #[test] #[allow(unused_must_use)] // here, we care about the side effects of `.clone()` #[cfg_attr(target_os = "emscripten", ignore)] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_box_slice_clone_panics() { use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; diff --git a/library/alloc/tests/str.rs b/library/alloc/tests/str.rs index c1dbbde08b6b9..cb59a9d4ab2a6 100644 --- a/library/alloc/tests/str.rs +++ b/library/alloc/tests/str.rs @@ -1,3 +1,5 @@ +#![allow(invalid_from_utf8)] + use std::assert_matches::assert_matches; use std::borrow::Cow; use std::cmp::Ordering::{Equal, Greater, Less}; @@ -1736,6 +1738,28 @@ fn test_utf16_code_units() { assert_eq!("é\u{1F4A9}".encode_utf16().collect::>(), [0xE9, 0xD83D, 0xDCA9]) } +#[test] +fn test_utf16_size_hint() { + assert_eq!("".encode_utf16().size_hint(), (0, Some(0))); + assert_eq!("123".encode_utf16().size_hint(), (1, Some(3))); + assert_eq!("1234".encode_utf16().size_hint(), (2, Some(4))); + assert_eq!("12345678".encode_utf16().size_hint(), (3, Some(8))); + + fn hint_vec(src: &str) -> Vec<(usize, Option)> { + let mut it = src.encode_utf16(); + let mut result = Vec::new(); + result.push(it.size_hint()); + while it.next().is_some() { + result.push(it.size_hint()) + } + result + } + + assert_eq!(hint_vec("12"), [(1, Some(2)), (1, Some(1)), (0, Some(0))]); + assert_eq!(hint_vec("\u{101234}"), [(2, Some(4)), (1, Some(1)), (0, Some(0))]); + assert_eq!(hint_vec("\u{101234}a"), [(2, Some(5)), (2, Some(2)), (1, Some(1)), (0, Some(0))]); +} + #[test] fn starts_with_in_unicode() { assert!(!"├── Cargo.toml".starts_with("# ")); @@ -2414,10 +2438,7 @@ fn ceil_char_boundary() { check_many("🇯🇵", 0..=0, 0); check_many("🇯🇵", 1..=4, 4); check_many("🇯🇵", 5..=8, 8); -} -#[test] -#[should_panic] -fn ceil_char_boundary_above_len_panic() { - let _ = "x".ceil_char_boundary(2); + // above len + check_many("hello", 5..=10, 5); } diff --git a/library/alloc/tests/string.rs b/library/alloc/tests/string.rs index 99d1296a4c925..711e4eef2e724 100644 --- a/library/alloc/tests/string.rs +++ b/library/alloc/tests/string.rs @@ -368,32 +368,77 @@ fn remove_bad() { #[test] fn test_remove_matches() { + // test_single_pattern_occurrence let mut s = "abc".to_string(); - s.remove_matches('b'); assert_eq!(s, "ac"); + // repeat_test_single_pattern_occurrence s.remove_matches('b'); assert_eq!(s, "ac"); + // test_single_character_pattern let mut s = "abcb".to_string(); - s.remove_matches('b'); assert_eq!(s, "ac"); + // test_pattern_with_special_characters let mut s = "ศไทย中华Việt Nam; foobarศ".to_string(); s.remove_matches('ศ'); assert_eq!(s, "ไทย中华Việt Nam; foobar"); + // test_pattern_empty_text_and_pattern let mut s = "".to_string(); s.remove_matches(""); assert_eq!(s, ""); + // test_pattern_empty_text + let mut s = "".to_string(); + s.remove_matches("something"); + assert_eq!(s, ""); + + // test_empty_pattern + let mut s = "Testing with empty pattern.".to_string(); + s.remove_matches(""); + assert_eq!(s, "Testing with empty pattern."); + + // test_multiple_consecutive_patterns_1 let mut s = "aaaaa".to_string(); s.remove_matches('a'); assert_eq!(s, ""); + + // test_multiple_consecutive_patterns_2 + let mut s = "Hello **world****today!**".to_string(); + s.remove_matches("**"); + assert_eq!(s, "Hello worldtoday!"); + + // test_case_insensitive_pattern + let mut s = "CASE ** SeNsItIvE ** PaTtErN.".to_string(); + s.remove_matches("sEnSiTiVe"); + assert_eq!(s, "CASE ** SeNsItIvE ** PaTtErN."); + + // test_pattern_with_digits + let mut s = "123 ** 456 ** 789".to_string(); + s.remove_matches("**"); + assert_eq!(s, "123 456 789"); + + // test_pattern_occurs_after_empty_string + let mut s = "abc X defXghi".to_string(); + s.remove_matches("X"); + assert_eq!(s, "abc defghi"); + + // test_large_pattern + let mut s = "aaaXbbbXcccXdddXeee".to_string(); + s.remove_matches("X"); + assert_eq!(s, "aaabbbcccdddeee"); + + // test_pattern_at_multiple_positions + let mut s = "Pattern ** found ** multiple ** times ** in ** text.".to_string(); + s.remove_matches("**"); + assert_eq!(s, "Pattern found multiple times in text."); } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_retain() { let mut s = String::from("α_β_γ"); diff --git a/library/alloc/tests/vec.rs b/library/alloc/tests/vec.rs index cc4c1f1272865..183dd8e6e5932 100644 --- a/library/alloc/tests/vec.rs +++ b/library/alloc/tests/vec.rs @@ -314,6 +314,7 @@ fn test_retain_predicate_order() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_retain_pred_panic_with_hole() { let v = (0..5).map(Rc::new).collect::>(); catch_unwind(AssertUnwindSafe(|| { @@ -331,6 +332,7 @@ fn test_retain_pred_panic_with_hole() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_retain_pred_panic_no_hole() { let v = (0..5).map(Rc::new).collect::>(); catch_unwind(AssertUnwindSafe(|| { @@ -346,6 +348,7 @@ fn test_retain_pred_panic_no_hole() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_retain_drop_panic() { struct Wrap(Rc); @@ -806,6 +809,7 @@ fn test_drain_end_overflow() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_drain_leak() { static mut DROPS: i32 = 0; @@ -1038,6 +1042,7 @@ fn test_into_iter_clone() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_into_iter_leak() { static mut DROPS: i32 = 0; @@ -1195,6 +1200,7 @@ fn test_from_iter_specialization_head_tail_drop() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_from_iter_specialization_panic_during_iteration_drops() { let drop_count: Vec<_> = (0..=2).map(|_| Rc::new(())).collect(); let src: Vec<_> = drop_count.iter().cloned().collect(); @@ -1219,6 +1225,7 @@ fn test_from_iter_specialization_panic_during_iteration_drops() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_from_iter_specialization_panic_during_drop_doesnt_leak() { static mut DROP_COUNTER_OLD: [usize; 5] = [0; 5]; static mut DROP_COUNTER_NEW: [usize; 2] = [0; 2]; @@ -1340,11 +1347,11 @@ fn overaligned_allocations() { } #[test] -fn drain_filter_empty() { +fn extract_if_empty() { let mut vec: Vec = vec![]; { - let mut iter = vec.drain_filter(|_| true); + let mut iter = vec.extract_if(|_| true); assert_eq!(iter.size_hint(), (0, Some(0))); assert_eq!(iter.next(), None); assert_eq!(iter.size_hint(), (0, Some(0))); @@ -1356,12 +1363,12 @@ fn drain_filter_empty() { } #[test] -fn drain_filter_zst() { +fn extract_if_zst() { let mut vec = vec![(), (), (), (), ()]; let initial_len = vec.len(); let mut count = 0; { - let mut iter = vec.drain_filter(|_| true); + let mut iter = vec.extract_if(|_| true); assert_eq!(iter.size_hint(), (0, Some(initial_len))); while let Some(_) = iter.next() { count += 1; @@ -1378,13 +1385,13 @@ fn drain_filter_zst() { } #[test] -fn drain_filter_false() { +fn extract_if_false() { let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let initial_len = vec.len(); let mut count = 0; { - let mut iter = vec.drain_filter(|_| false); + let mut iter = vec.extract_if(|_| false); assert_eq!(iter.size_hint(), (0, Some(initial_len))); for _ in iter.by_ref() { count += 1; @@ -1400,13 +1407,13 @@ fn drain_filter_false() { } #[test] -fn drain_filter_true() { +fn extract_if_true() { let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let initial_len = vec.len(); let mut count = 0; { - let mut iter = vec.drain_filter(|_| true); + let mut iter = vec.extract_if(|_| true); assert_eq!(iter.size_hint(), (0, Some(initial_len))); while let Some(_) = iter.next() { count += 1; @@ -1423,7 +1430,7 @@ fn drain_filter_true() { } #[test] -fn drain_filter_complex() { +fn extract_if_complex() { { // [+xxx++++++xxxxx++++x+x++] let mut vec = vec![ @@ -1431,7 +1438,7 @@ fn drain_filter_complex() { 39, ]; - let removed = vec.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); @@ -1445,7 +1452,7 @@ fn drain_filter_complex() { 2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36, 37, 39, ]; - let removed = vec.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); @@ -1458,7 +1465,7 @@ fn drain_filter_complex() { let mut vec = vec![2, 4, 6, 7, 9, 11, 13, 15, 17, 18, 20, 22, 24, 26, 27, 29, 31, 33, 34, 35, 36]; - let removed = vec.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); @@ -1470,7 +1477,7 @@ fn drain_filter_complex() { // [xxxxxxxxxx+++++++++++] let mut vec = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; - let removed = vec.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]); @@ -1482,7 +1489,7 @@ fn drain_filter_complex() { // [+++++++++++xxxxxxxxxx] let mut vec = vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; - let removed = vec.drain_filter(|x| *x % 2 == 0).collect::>(); + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); assert_eq!(removed.len(), 10); assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]); @@ -1494,7 +1501,8 @@ fn drain_filter_complex() { // FIXME: re-enable emscripten once it can unwind again #[test] #[cfg(not(target_os = "emscripten"))] -fn drain_filter_consumed_panic() { +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn extract_if_consumed_panic() { use std::rc::Rc; use std::sync::Mutex; @@ -1529,9 +1537,9 @@ fn drain_filter_consumed_panic() { } c.index < 6 }; - let drain = data.drain_filter(filter); + let drain = data.extract_if(filter); - // NOTE: The DrainFilter is explicitly consumed + // NOTE: The ExtractIf is explicitly consumed drain.for_each(drop); }); @@ -1546,7 +1554,8 @@ fn drain_filter_consumed_panic() { // FIXME: Re-enable emscripten once it can catch panics #[test] #[cfg(not(target_os = "emscripten"))] -fn drain_filter_unconsumed_panic() { +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn extract_if_unconsumed_panic() { use std::rc::Rc; use std::sync::Mutex; @@ -1581,9 +1590,9 @@ fn drain_filter_unconsumed_panic() { } c.index < 6 }; - let _drain = data.drain_filter(filter); + let _drain = data.extract_if(filter); - // NOTE: The DrainFilter is dropped without being consumed + // NOTE: The ExtractIf is dropped without being consumed }); let drop_counts = drop_counts.lock().unwrap(); @@ -1595,40 +1604,11 @@ fn drain_filter_unconsumed_panic() { } #[test] -fn drain_filter_unconsumed() { +fn extract_if_unconsumed() { let mut vec = vec![1, 2, 3, 4]; - let drain = vec.drain_filter(|&mut x| x % 2 != 0); + let drain = vec.extract_if(|&mut x| x % 2 != 0); drop(drain); - assert_eq!(vec, [2, 4]); -} - -#[test] -fn test_drain_filter_keep_rest() { - let mut v = vec![0, 1, 2, 3, 4, 5, 6]; - let mut drain = v.drain_filter(|&mut x| x % 2 == 0); - assert_eq!(drain.next(), Some(0)); - assert_eq!(drain.next(), Some(2)); - - drain.keep_rest(); - assert_eq!(v, &[1, 3, 4, 5, 6]); -} - -#[test] -fn test_drain_filter_keep_rest_all() { - let mut v = vec![0, 1, 2, 3, 4, 5, 6]; - v.drain_filter(|_| true).keep_rest(); - assert_eq!(v, &[0, 1, 2, 3, 4, 5, 6]); -} - -#[test] -fn test_drain_filter_keep_rest_none() { - let mut v = vec![0, 1, 2, 3, 4, 5, 6]; - let mut drain = v.drain_filter(|_| true); - - drain.by_ref().for_each(drop); - - drain.keep_rest(); - assert_eq!(v, &[]); + assert_eq!(vec, [1, 2, 3, 4]); } #[test] @@ -1926,6 +1906,7 @@ fn test_stable_pointers() { assert_eq!(*v0, 13); // Smoke test that would fire even outside Miri if an actual relocation happened. + // Also ensures the pointer is still writeable after all this. *v0 -= 13; assert_eq!(v[0], 0); } @@ -2414,6 +2395,7 @@ fn test_vec_dedup() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_vec_dedup_panicking() { #[derive(Debug)] struct Panic<'a> { @@ -2470,6 +2452,7 @@ fn test_vec_dedup_panicking() { // Regression test for issue #82533 #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_extend_from_within_panicking_clone() { struct Panic<'dc> { drop_count: &'dc AtomicU32, @@ -2515,3 +2498,68 @@ fn test_into_flattened_size_overflow() { let v = vec![[(); usize::MAX]; 2]; let _ = v.into_flattened(); } + +#[cfg(not(bootstrap))] +#[test] +fn test_box_zero_allocator() { + use core::{alloc::AllocError, cell::RefCell}; + use std::collections::HashSet; + + // Track ZST allocations and ensure that they all have a matching free. + struct ZstTracker { + state: RefCell<(HashSet, usize)>, + } + unsafe impl Allocator for ZstTracker { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let ptr = if layout.size() == 0 { + let mut state = self.state.borrow_mut(); + let addr = state.1; + assert!(state.0.insert(addr)); + state.1 += 1; + std::println!("allocating {addr}"); + std::ptr::invalid_mut(addr) + } else { + unsafe { std::alloc::alloc(layout) } + }; + Ok(NonNull::slice_from_raw_parts(NonNull::new(ptr).ok_or(AllocError)?, layout.size())) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + if layout.size() == 0 { + let addr = ptr.as_ptr() as usize; + let mut state = self.state.borrow_mut(); + std::println!("freeing {addr}"); + assert!(state.0.remove(&addr), "ZST free that wasn't allocated"); + } else { + unsafe { std::alloc::dealloc(ptr.as_ptr(), layout) } + } + } + } + + // Start the state at 100 to avoid returning null pointers. + let alloc = ZstTracker { state: RefCell::new((HashSet::new(), 100)) }; + + // Ensure that unsizing retains the same behavior. + { + let b1: Box<[u8; 0], &ZstTracker> = Box::new_in([], &alloc); + let b2: Box<[u8], &ZstTracker> = b1.clone(); + let _b3: Box<[u8], &ZstTracker> = b2.clone(); + } + + // Ensure that shrinking doesn't leak a ZST allocation. + { + let mut v1: Vec = Vec::with_capacity_in(100, &alloc); + v1.shrink_to_fit(); + } + + // Ensure that conversion to/from vec works. + { + let v1: Vec<(), &ZstTracker> = Vec::with_capacity_in(100, &alloc); + let _b1: Box<[()], &ZstTracker> = v1.into_boxed_slice(); + let b2: Box<[()], &ZstTracker> = Box::new_in([(), (), ()], &alloc); + let _v2: Vec<(), &ZstTracker> = b2.into(); + } + + // Ensure all ZSTs have been freed. + assert!(alloc.state.borrow().0.is_empty()); +} diff --git a/library/alloc/tests/vec_deque.rs b/library/alloc/tests/vec_deque.rs index ddc27e34ed994..f6fb1f73e5cf9 100644 --- a/library/alloc/tests/vec_deque.rs +++ b/library/alloc/tests/vec_deque.rs @@ -747,6 +747,7 @@ fn test_drop_clear() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_drop_panic() { static mut DROPS: i32 = 0; @@ -1601,6 +1602,7 @@ fn test_try_rfold_moves_iter() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn truncate_leak() { static mut DROPS: i32 = 0; @@ -1634,6 +1636,7 @@ fn truncate_leak() { } #[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] fn test_drain_leak() { static mut DROPS: i32 = 0; diff --git a/library/backtrace b/library/backtrace index 4245978ca8169..e1c49fbd6124a 160000 --- a/library/backtrace +++ b/library/backtrace @@ -1 +1 @@ -Subproject commit 4245978ca8169c40c088ff733825e4527f7b914c +Subproject commit e1c49fbd6124a1b626cdf19871aff68c362bdf07 diff --git a/library/core/benches/iter.rs b/library/core/benches/iter.rs index 60ef83223d104..05fec0c4b9d26 100644 --- a/library/core/benches/iter.rs +++ b/library/core/benches/iter.rs @@ -2,6 +2,7 @@ use core::borrow::Borrow; use core::iter::*; use core::mem; use core::num::Wrapping; +use core::ops::Range; use test::{black_box, Bencher}; #[bench] @@ -69,6 +70,57 @@ fn bench_max(b: &mut Bencher) { }) } +#[bench] +fn bench_range_step_by_sum_reducible(b: &mut Bencher) { + let r = 0u32..1024; + b.iter(|| { + let r = black_box(r.clone()).step_by(8); + + let mut sum: u32 = 0; + for i in r { + sum += i; + } + + sum + }) +} + +#[bench] +fn bench_range_step_by_loop_u32(b: &mut Bencher) { + let r = 0..(u16::MAX as u32); + b.iter(|| { + let r = black_box(r.clone()).step_by(64); + + let mut sum: u32 = 0; + for i in r { + let i = i ^ i.wrapping_sub(1); + sum = sum.wrapping_add(i); + } + + sum + }) +} + +#[bench] +fn bench_range_step_by_fold_usize(b: &mut Bencher) { + let r: Range = 0..(u16::MAX as usize); + b.iter(|| { + let r = black_box(r.clone()); + r.step_by(64) + .map(|x: usize| x ^ (x.wrapping_sub(1))) + .fold(0usize, |acc, i| acc.wrapping_add(i)) + }) +} + +#[bench] +fn bench_range_step_by_fold_u16(b: &mut Bencher) { + let r: Range = 0..u16::MAX; + b.iter(|| { + let r = black_box(r.clone()); + r.step_by(64).map(|x: u16| x ^ (x.wrapping_sub(1))).fold(0u16, |acc, i| acc.wrapping_add(i)) + }) +} + pub fn copy_zip(xs: &[u8], ys: &mut [u8]) { for (a, b) in ys.iter_mut().zip(xs) { *a = *b; @@ -421,6 +473,7 @@ fn bench_next_chunk_copied(b: &mut Bencher) { /// Exercises the TrustedRandomAccess specialization in ArrayChunks #[bench] +#[allow(noop_method_call)] fn bench_next_chunk_trusted_random_access(b: &mut Bencher) { let v = vec![1u8; 1024]; diff --git a/library/core/benches/lib.rs b/library/core/benches/lib.rs index 74ef0949b8af2..289b2f1fba3eb 100644 --- a/library/core/benches/lib.rs +++ b/library/core/benches/lib.rs @@ -1,5 +1,5 @@ // wasm32 does not support benches (no time). -#![cfg(not(target_arch = "wasm32"))] +#![cfg(not(target_family = "wasm"))] #![feature(flt2dec)] #![feature(test)] #![feature(trusted_random_access)] diff --git a/library/core/benches/slice.rs b/library/core/benches/slice.rs index 9b86a0ca97c09..3bfb35e684ea1 100644 --- a/library/core/benches/slice.rs +++ b/library/core/benches/slice.rs @@ -1,3 +1,4 @@ +use core::ptr::NonNull; use test::black_box; use test::Bencher; @@ -162,3 +163,11 @@ fn fill_byte_sized(b: &mut Bencher) { black_box(slice.fill(black_box(NewType(42)))); }); } + +// Tests the ability of the compiler to recognize that only the last slice item is needed +// based on issue #106288 +#[bench] +fn fold_to_last(b: &mut Bencher) { + let slice: &[i32] = &[0; 1024]; + b.iter(|| black_box(slice).iter().fold(None, |_, r| Some(NonNull::from(r)))); +} diff --git a/library/core/src/alloc/global.rs b/library/core/src/alloc/global.rs index 18da70451f299..c582111701a99 100644 --- a/library/core/src/alloc/global.rs +++ b/library/core/src/alloc/global.rs @@ -235,7 +235,8 @@ pub unsafe trait GlobalAlloc { /// * `new_size` must be greater than zero. /// /// * `new_size`, when rounded up to the nearest multiple of `layout.align()`, - /// must not overflow (i.e., the rounded value must be less than `usize::MAX`). + /// must not overflow isize (i.e., the rounded value must be less than or + /// equal to `isize::MAX`). /// /// (Extension subtraits might provide more specific bounds on /// behavior, e.g., guarantee a sentinel address or a null pointer diff --git a/library/core/src/alloc/mod.rs b/library/core/src/alloc/mod.rs index d6ae2b8213f56..78091c0172955 100644 --- a/library/core/src/alloc/mod.rs +++ b/library/core/src/alloc/mod.rs @@ -94,8 +94,9 @@ impl fmt::Display for AllocError { /// /// # Safety /// -/// * Memory blocks returned from an allocator must point to valid memory and retain their validity -/// until the instance and all of its copies and clones are dropped, +/// * Memory blocks returned from an allocator that are [*currently allocated*] must point to +/// valid memory and retain their validity while they are [*currently allocated*] and at +/// least one of the instance and all of its clones has not been dropped. /// /// * copying, cloning, or moving the allocator must not invalidate memory blocks returned from this /// allocator. A copied or cloned allocator must behave like the same allocator, and diff --git a/library/core/src/any.rs b/library/core/src/any.rs index d1c1ae6526b6e..8f5404d9713dd 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -83,76 +83,11 @@ //! } //! ``` //! -//! # `Provider` and `Demand` -//! -//! `Provider` and the associated APIs support generic, type-driven access to data, and a mechanism -//! for implementers to provide such data. The key parts of the interface are the `Provider` -//! trait for objects which can provide data, and the [`request_value`] and [`request_ref`] -//! functions for requesting data from an object which implements `Provider`. Generally, end users -//! should not call `request_*` directly, they are helper functions for intermediate implementers -//! to use to implement a user-facing interface. This is purely for the sake of ergonomics, there is -//! no safety concern here; intermediate implementers can typically support methods rather than -//! free functions and use more specific names. -//! -//! Typically, a data provider is a trait object of a trait which extends `Provider`. A user will -//! request data from a trait object by specifying the type of the data. -//! -//! ## Data flow -//! -//! * A user requests an object of a specific type, which is delegated to `request_value` or -//! `request_ref` -//! * `request_*` creates a `Demand` object and passes it to `Provider::provide` -//! * The data provider's implementation of `Provider::provide` tries providing values of -//! different types using `Demand::provide_*`. If the type matches the type requested by -//! the user, the value will be stored in the `Demand` object. -//! * `request_*` unpacks the `Demand` object and returns any stored value to the user. -//! -//! ## Examples -//! -//! ``` -//! # #![feature(provide_any)] -//! use std::any::{Provider, Demand, request_ref}; -//! -//! // Definition of MyTrait, a data provider. -//! trait MyTrait: Provider { -//! // ... -//! } -//! -//! // Methods on `MyTrait` trait objects. -//! impl dyn MyTrait + '_ { -//! /// Get a reference to a field of the implementing struct. -//! pub fn get_context_by_ref(&self) -> Option<&T> { -//! request_ref::(self) -//! } -//! } -//! -//! // Downstream implementation of `MyTrait` and `Provider`. -//! # struct SomeConcreteType { some_string: String } -//! impl MyTrait for SomeConcreteType { -//! // ... -//! } -//! -//! impl Provider for SomeConcreteType { -//! fn provide<'a>(&'a self, demand: &mut Demand<'a>) { -//! // Provide a string reference. We could provide multiple values with -//! // different types here. -//! demand.provide_ref::(&self.some_string); -//! } -//! } -//! -//! // Downstream usage of `MyTrait`. -//! fn use_my_trait(obj: &dyn MyTrait) { -//! // Request a &String from obj. -//! let _ = obj.get_context_by_ref::().unwrap(); -//! } -//! ``` -//! -//! In this example, if the concrete type of `obj` in `use_my_trait` is `SomeConcreteType`, then -//! the `get_context_by_ref` call will return a reference to `obj.some_string` with type `&String`. #![stable(feature = "rust1", since = "1.0.0")] use crate::fmt; +use crate::hash; use crate::intrinsics; /////////////////////////////////////////////////////////////////////////////// @@ -662,10 +597,18 @@ impl dyn Any + Send + Sync { /// While `TypeId` implements `Hash`, `PartialOrd`, and `Ord`, it is worth /// noting that the hashes and ordering will vary between Rust releases. Beware /// of relying on them inside of your code! -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, Eq, PartialOrd, Ord)] #[stable(feature = "rust1", since = "1.0.0")] pub struct TypeId { - t: u64, + t: u128, +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl PartialEq for TypeId { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.t == other.t + } } impl TypeId { @@ -688,7 +631,28 @@ impl TypeId { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_type_id", issue = "77125")] pub const fn of() -> TypeId { - TypeId { t: intrinsics::type_id::() } + let t: u128 = intrinsics::type_id::(); + TypeId { t } + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl hash::Hash for TypeId { + #[inline] + fn hash(&self, state: &mut H) { + // We only hash the lower 64 bits of our (128 bit) internal numeric ID, + // because: + // - The hashing algorithm which backs `TypeId` is expected to be + // unbiased and high quality, meaning further mixing would be somewhat + // redundant compared to choosing (the lower) 64 bits arbitrarily. + // - `Hasher::finish` returns a u64 anyway, so the extra entropy we'd + // get from hashing the full value would probably not be useful + // (especially given the previous point about the lower 64 bits being + // high quality on their own). + // - It is correct to do so -- only hashing a subset of `self` is still + // with an `Eq` implementation that considers the entire value, as + // ours does. + (self.t as u64).hash(state); } } @@ -768,524 +732,3 @@ pub const fn type_name() -> &'static str { pub const fn type_name_of_val(_val: &T) -> &'static str { type_name::() } - -/////////////////////////////////////////////////////////////////////////////// -// Provider trait -/////////////////////////////////////////////////////////////////////////////// - -/// Trait implemented by a type which can dynamically provide values based on type. -#[unstable(feature = "provide_any", issue = "96024")] -pub trait Provider { - /// Data providers should implement this method to provide *all* values they are able to - /// provide by using `demand`. - /// - /// Note that the `provide_*` methods on `Demand` have short-circuit semantics: if an earlier - /// method has successfully provided a value, then later methods will not get an opportunity to - /// provide. - /// - /// # Examples - /// - /// Provides a reference to a field with type `String` as a `&str`, and a value of - /// type `i32`. - /// - /// ```rust - /// # #![feature(provide_any)] - /// use std::any::{Provider, Demand}; - /// # struct SomeConcreteType { field: String, num_field: i32 } - /// - /// impl Provider for SomeConcreteType { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand.provide_ref::(&self.field) - /// .provide_value::(self.num_field); - /// } - /// } - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - fn provide<'a>(&'a self, demand: &mut Demand<'a>); -} - -/// Request a value from the `Provider`. -/// -/// # Examples -/// -/// Get a string value from a provider. -/// -/// ```rust -/// # #![feature(provide_any)] -/// use std::any::{Provider, request_value}; -/// -/// fn get_string(provider: &impl Provider) -> String { -/// request_value::(provider).unwrap() -/// } -/// ``` -#[unstable(feature = "provide_any", issue = "96024")] -pub fn request_value<'a, T>(provider: &'a (impl Provider + ?Sized)) -> Option -where - T: 'static, -{ - request_by_type_tag::<'a, tags::Value>(provider) -} - -/// Request a reference from the `Provider`. -/// -/// # Examples -/// -/// Get a string reference from a provider. -/// -/// ```rust -/// # #![feature(provide_any)] -/// use std::any::{Provider, request_ref}; -/// -/// fn get_str(provider: &impl Provider) -> &str { -/// request_ref::(provider).unwrap() -/// } -/// ``` -#[unstable(feature = "provide_any", issue = "96024")] -pub fn request_ref<'a, T>(provider: &'a (impl Provider + ?Sized)) -> Option<&'a T> -where - T: 'static + ?Sized, -{ - request_by_type_tag::<'a, tags::Ref>>(provider) -} - -/// Request a specific value by tag from the `Provider`. -fn request_by_type_tag<'a, I>(provider: &'a (impl Provider + ?Sized)) -> Option -where - I: tags::Type<'a>, -{ - let mut tagged = TaggedOption::<'a, I>(None); - provider.provide(tagged.as_demand()); - tagged.0 -} - -/////////////////////////////////////////////////////////////////////////////// -// Demand and its methods -/////////////////////////////////////////////////////////////////////////////// - -/// A helper object for providing data by type. -/// -/// A data provider provides values by calling this type's provide methods. -#[unstable(feature = "provide_any", issue = "96024")] -#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 -pub struct Demand<'a>(dyn Erased<'a> + 'a); - -impl<'a> Demand<'a> { - /// Create a new `&mut Demand` from a `&mut dyn Erased` trait object. - fn new<'b>(erased: &'b mut (dyn Erased<'a> + 'a)) -> &'b mut Demand<'a> { - // SAFETY: transmuting `&mut (dyn Erased<'a> + 'a)` to `&mut Demand<'a>` is safe since - // `Demand` is repr(transparent). - unsafe { &mut *(erased as *mut dyn Erased<'a> as *mut Demand<'a>) } - } - - /// Provide a value or other type with only static lifetimes. - /// - /// # Examples - /// - /// Provides an `u8`. - /// - /// ```rust - /// #![feature(provide_any)] - /// - /// use std::any::{Provider, Demand}; - /// # struct SomeConcreteType { field: u8 } - /// - /// impl Provider for SomeConcreteType { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand.provide_value::(self.field); - /// } - /// } - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - pub fn provide_value(&mut self, value: T) -> &mut Self - where - T: 'static, - { - self.provide::>(value) - } - - /// Provide a value or other type with only static lifetimes computed using a closure. - /// - /// # Examples - /// - /// Provides a `String` by cloning. - /// - /// ```rust - /// #![feature(provide_any)] - /// - /// use std::any::{Provider, Demand}; - /// # struct SomeConcreteType { field: String } - /// - /// impl Provider for SomeConcreteType { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand.provide_value_with::(|| self.field.clone()); - /// } - /// } - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - pub fn provide_value_with(&mut self, fulfil: impl FnOnce() -> T) -> &mut Self - where - T: 'static, - { - self.provide_with::>(fulfil) - } - - /// Provide a reference. The referee type must be bounded by `'static`, - /// but may be unsized. - /// - /// # Examples - /// - /// Provides a reference to a field as a `&str`. - /// - /// ```rust - /// #![feature(provide_any)] - /// - /// use std::any::{Provider, Demand}; - /// # struct SomeConcreteType { field: String } - /// - /// impl Provider for SomeConcreteType { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand.provide_ref::(&self.field); - /// } - /// } - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - pub fn provide_ref(&mut self, value: &'a T) -> &mut Self { - self.provide::>>(value) - } - - /// Provide a reference computed using a closure. The referee type - /// must be bounded by `'static`, but may be unsized. - /// - /// # Examples - /// - /// Provides a reference to a field as a `&str`. - /// - /// ```rust - /// #![feature(provide_any)] - /// - /// use std::any::{Provider, Demand}; - /// # struct SomeConcreteType { business: String, party: String } - /// # fn today_is_a_weekday() -> bool { true } - /// - /// impl Provider for SomeConcreteType { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand.provide_ref_with::(|| { - /// if today_is_a_weekday() { - /// &self.business - /// } else { - /// &self.party - /// } - /// }); - /// } - /// } - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - pub fn provide_ref_with( - &mut self, - fulfil: impl FnOnce() -> &'a T, - ) -> &mut Self { - self.provide_with::>>(fulfil) - } - - /// Provide a value with the given `Type` tag. - fn provide(&mut self, value: I::Reified) -> &mut Self - where - I: tags::Type<'a>, - { - if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { - res.0 = Some(value); - } - self - } - - /// Provide a value with the given `Type` tag, using a closure to prevent unnecessary work. - fn provide_with(&mut self, fulfil: impl FnOnce() -> I::Reified) -> &mut Self - where - I: tags::Type<'a>, - { - if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { - res.0 = Some(fulfil()); - } - self - } - - /// Check if the `Demand` would be satisfied if provided with a - /// value of the specified type. If the type does not match or has - /// already been provided, returns false. - /// - /// # Examples - /// - /// Check if an `u8` still needs to be provided and then provides - /// it. - /// - /// ```rust - /// #![feature(provide_any)] - /// - /// use std::any::{Provider, Demand}; - /// - /// struct Parent(Option); - /// - /// impl Provider for Parent { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// if let Some(v) = self.0 { - /// demand.provide_value::(v); - /// } - /// } - /// } - /// - /// struct Child { - /// parent: Parent, - /// } - /// - /// impl Child { - /// // Pretend that this takes a lot of resources to evaluate. - /// fn an_expensive_computation(&self) -> Option { - /// Some(99) - /// } - /// } - /// - /// impl Provider for Child { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// // In general, we don't know if this call will provide - /// // an `u8` value or not... - /// self.parent.provide(demand); - /// - /// // ...so we check to see if the `u8` is needed before - /// // we run our expensive computation. - /// if demand.would_be_satisfied_by_value_of::() { - /// if let Some(v) = self.an_expensive_computation() { - /// demand.provide_value::(v); - /// } - /// } - /// - /// // The demand will be satisfied now, regardless of if - /// // the parent provided the value or we did. - /// assert!(!demand.would_be_satisfied_by_value_of::()); - /// } - /// } - /// - /// let parent = Parent(Some(42)); - /// let child = Child { parent }; - /// assert_eq!(Some(42), std::any::request_value::(&child)); - /// - /// let parent = Parent(None); - /// let child = Child { parent }; - /// assert_eq!(Some(99), std::any::request_value::(&child)); - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - pub fn would_be_satisfied_by_value_of(&self) -> bool - where - T: 'static, - { - self.would_be_satisfied_by::>() - } - - /// Check if the `Demand` would be satisfied if provided with a - /// reference to a value of the specified type. If the type does - /// not match or has already been provided, returns false. - /// - /// # Examples - /// - /// Check if a `&str` still needs to be provided and then provides - /// it. - /// - /// ```rust - /// #![feature(provide_any)] - /// - /// use std::any::{Provider, Demand}; - /// - /// struct Parent(Option); - /// - /// impl Provider for Parent { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// if let Some(v) = &self.0 { - /// demand.provide_ref::(v); - /// } - /// } - /// } - /// - /// struct Child { - /// parent: Parent, - /// name: String, - /// } - /// - /// impl Child { - /// // Pretend that this takes a lot of resources to evaluate. - /// fn an_expensive_computation(&self) -> Option<&str> { - /// Some(&self.name) - /// } - /// } - /// - /// impl Provider for Child { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// // In general, we don't know if this call will provide - /// // a `str` reference or not... - /// self.parent.provide(demand); - /// - /// // ...so we check to see if the `&str` is needed before - /// // we run our expensive computation. - /// if demand.would_be_satisfied_by_ref_of::() { - /// if let Some(v) = self.an_expensive_computation() { - /// demand.provide_ref::(v); - /// } - /// } - /// - /// // The demand will be satisfied now, regardless of if - /// // the parent provided the reference or we did. - /// assert!(!demand.would_be_satisfied_by_ref_of::()); - /// } - /// } - /// - /// let parent = Parent(Some("parent".into())); - /// let child = Child { parent, name: "child".into() }; - /// assert_eq!(Some("parent"), std::any::request_ref::(&child)); - /// - /// let parent = Parent(None); - /// let child = Child { parent, name: "child".into() }; - /// assert_eq!(Some("child"), std::any::request_ref::(&child)); - /// ``` - #[unstable(feature = "provide_any", issue = "96024")] - pub fn would_be_satisfied_by_ref_of(&self) -> bool - where - T: ?Sized + 'static, - { - self.would_be_satisfied_by::>>() - } - - fn would_be_satisfied_by(&self) -> bool - where - I: tags::Type<'a>, - { - matches!(self.0.downcast::(), Some(TaggedOption(None))) - } -} - -#[unstable(feature = "provide_any", issue = "96024")] -impl<'a> fmt::Debug for Demand<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Demand").finish_non_exhaustive() - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Type tags -/////////////////////////////////////////////////////////////////////////////// - -mod tags { - //! Type tags are used to identify a type using a separate value. This module includes type tags - //! for some very common types. - //! - //! Currently type tags are not exposed to the user. But in the future, if you want to use the - //! Provider API with more complex types (typically those including lifetime parameters), you - //! will need to write your own tags. - - use crate::marker::PhantomData; - - /// This trait is implemented by specific tag types in order to allow - /// describing a type which can be requested for a given lifetime `'a`. - /// - /// A few example implementations for type-driven tags can be found in this - /// module, although crates may also implement their own tags for more - /// complex types with internal lifetimes. - pub trait Type<'a>: Sized + 'static { - /// The type of values which may be tagged by this tag for the given - /// lifetime. - type Reified: 'a; - } - - /// Similar to the [`Type`] trait, but represents a type which may be unsized (i.e., has a - /// `?Sized` bound). E.g., `str`. - pub trait MaybeSizedType<'a>: Sized + 'static { - type Reified: 'a + ?Sized; - } - - impl<'a, T: Type<'a>> MaybeSizedType<'a> for T { - type Reified = T::Reified; - } - - /// Type-based tag for types bounded by `'static`, i.e., with no borrowed elements. - #[derive(Debug)] - pub struct Value(PhantomData); - - impl<'a, T: 'static> Type<'a> for Value { - type Reified = T; - } - - /// Type-based tag similar to [`Value`] but which may be unsized (i.e., has a `?Sized` bound). - #[derive(Debug)] - pub struct MaybeSizedValue(PhantomData); - - impl<'a, T: ?Sized + 'static> MaybeSizedType<'a> for MaybeSizedValue { - type Reified = T; - } - - /// Type-based tag for reference types (`&'a T`, where T is represented by - /// `>::Reified`. - #[derive(Debug)] - pub struct Ref(PhantomData); - - impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref { - type Reified = &'a I::Reified; - } -} - -/// An `Option` with a type tag `I`. -/// -/// Since this struct implements `Erased`, the type can be erased to make a dynamically typed -/// option. The type can be checked dynamically using `Erased::tag_id` and since this is statically -/// checked for the concrete type, there is some degree of type safety. -#[repr(transparent)] -struct TaggedOption<'a, I: tags::Type<'a>>(Option); - -impl<'a, I: tags::Type<'a>> TaggedOption<'a, I> { - fn as_demand(&mut self) -> &mut Demand<'a> { - Demand::new(self as &mut (dyn Erased<'a> + 'a)) - } -} - -/// Represents a type-erased but identifiable object. -/// -/// This trait is exclusively implemented by the `TaggedOption` type. -unsafe trait Erased<'a>: 'a { - /// The `TypeId` of the erased type. - fn tag_id(&self) -> TypeId; -} - -unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> { - fn tag_id(&self) -> TypeId { - TypeId::of::() - } -} - -#[unstable(feature = "provide_any", issue = "96024")] -impl<'a> dyn Erased<'a> + 'a { - /// Returns some reference to the dynamic value if it is tagged with `I`, - /// or `None` otherwise. - #[inline] - fn downcast(&self) -> Option<&TaggedOption<'a, I>> - where - I: tags::Type<'a>, - { - if self.tag_id() == TypeId::of::() { - // SAFETY: Just checked whether we're pointing to an I. - Some(unsafe { &*(self as *const Self).cast::>() }) - } else { - None - } - } - - /// Returns some mutable reference to the dynamic value if it is tagged with `I`, - /// or `None` otherwise. - #[inline] - fn downcast_mut(&mut self) -> Option<&mut TaggedOption<'a, I>> - where - I: tags::Type<'a>, - { - if self.tag_id() == TypeId::of::() { - // SAFETY: Just checked whether we're pointing to an I. - Some(unsafe { &mut *(self as *mut Self).cast::>() }) - } else { - None - } - } -} diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs index bdb4c975909e0..ebd4a8c05fe30 100644 --- a/library/core/src/array/mod.rs +++ b/library/core/src/array/mod.rs @@ -204,6 +204,7 @@ where { type Error = TryFromSliceError; + #[inline] fn try_from(slice: &[T]) -> Result<[T; N], TryFromSliceError> { <&Self>::try_from(slice).map(|r| *r) } @@ -228,6 +229,7 @@ where { type Error = TryFromSliceError; + #[inline] fn try_from(slice: &mut [T]) -> Result<[T; N], TryFromSliceError> { ::try_from(&*slice) } @@ -249,6 +251,7 @@ where impl<'a, T, const N: usize> TryFrom<&'a [T]> for &'a [T; N] { type Error = TryFromSliceError; + #[inline] fn try_from(slice: &'a [T]) -> Result<&'a [T; N], TryFromSliceError> { if slice.len() == N { let ptr = slice.as_ptr() as *const [T; N]; @@ -276,6 +279,7 @@ impl<'a, T, const N: usize> TryFrom<&'a [T]> for &'a [T; N] { impl<'a, T, const N: usize> TryFrom<&'a mut [T]> for &'a mut [T; N] { type Error = TryFromSliceError; + #[inline] fn try_from(slice: &'a mut [T]) -> Result<&'a mut [T; N], TryFromSliceError> { if slice.len() == N { let ptr = slice.as_mut_ptr() as *mut [T; N]; @@ -291,7 +295,6 @@ impl<'a, T, const N: usize> TryFrom<&'a mut [T]> for &'a mut [T; N] { /// as required by the `Borrow` implementation. /// /// ``` -/// #![feature(build_hasher_simple_hash_one)] /// use std::hash::BuildHasher; /// /// let b = std::collections::hash_map::RandomState::new(); @@ -535,29 +538,6 @@ impl [T; N] { drain_array_with(self, |iter| try_from_trusted_iterator(iter.map(f))) } - /// 'Zips up' two arrays into a single array of pairs. - /// - /// `zip()` returns a new array where every element is a tuple where the - /// first element comes from the first array, and the second element comes - /// from the second array. In other words, it zips two arrays together, - /// into a single one. - /// - /// # Examples - /// - /// ``` - /// #![feature(array_zip)] - /// let x = [1, 2, 3]; - /// let y = [4, 5, 6]; - /// let z = x.zip(y); - /// assert_eq!(z, [(1, 4), (2, 5), (3, 6)]); - /// ``` - #[unstable(feature = "array_zip", issue = "80094")] - pub fn zip(self, rhs: [U; N]) -> [(T, U); N] { - drain_array_with(self, |lhs| { - drain_array_with(rhs, |rhs| from_trusted_iterator(crate::iter::zip(lhs, rhs))) - }) - } - /// Returns a slice containing the entire array. Equivalent to `&s[..]`. #[stable(feature = "array_as_slice", since = "1.57.0")] #[rustc_const_stable(feature = "array_as_slice", since = "1.57.0")] @@ -945,7 +925,7 @@ fn iter_next_chunk_erased( // so we need to defuse the guard instead of using `?`. let initialized = guard.initialized; mem::forget(guard); - return Err(initialized) + return Err(initialized); }; // SAFETY: The loop condition ensures we have space to push the item diff --git a/library/core/src/ascii/ascii_char.rs b/library/core/src/ascii/ascii_char.rs index f093a0990d1a9..5378b210e6733 100644 --- a/library/core/src/ascii/ascii_char.rs +++ b/library/core/src/ascii/ascii_char.rs @@ -518,14 +518,14 @@ impl AsciiChar { /// Gets this ASCII character as a byte. #[unstable(feature = "ascii_char", issue = "110998")] #[inline] - pub const fn as_u8(self) -> u8 { + pub const fn to_u8(self) -> u8 { self as u8 } /// Gets this ASCII character as a `char` Unicode Scalar Value. #[unstable(feature = "ascii_char", issue = "110998")] #[inline] - pub const fn as_char(self) -> char { + pub const fn to_char(self) -> char { self as u8 as char } diff --git a/library/core/src/borrow.rs b/library/core/src/borrow.rs index efc9ada3891a0..bc026d0a44634 100644 --- a/library/core/src/borrow.rs +++ b/library/core/src/borrow.rs @@ -22,7 +22,7 @@ /// Types express that they can be borrowed as some type `T` by implementing /// `Borrow`, providing a reference to a `T` in the trait’s /// [`borrow`] method. A type is free to borrow as several different types. -/// If it wishes to mutably borrow as the type – allowing the underlying data +/// If it wishes to mutably borrow as the type, allowing the underlying data /// to be modified, it can additionally implement [`BorrowMut`]. /// /// Further, when providing implementations for additional traits, it needs diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 744767aae44ca..bf4c682d33e5a 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -59,7 +59,7 @@ //! [`borrow`](`RefCell::borrow`), and a mutable borrow (`&mut T`) can be obtained with //! [`borrow_mut`](`RefCell::borrow_mut`). When these functions are called, they first verify that //! Rust's borrow rules will be satisfied: any number of immutable borrows are allowed or a -//! single immutable borrow is allowed, but never both. If a borrow is attempted that would violate +//! single mutable borrow is allowed, but never both. If a borrow is attempted that would violate //! these rules, the thread will panic. //! //! The corresponding [`Sync`] version of `RefCell` is [`RwLock`]. @@ -1374,7 +1374,7 @@ impl Clone for BorrowRef<'_> { debug_assert!(is_reading(borrow)); // Prevent the borrow counter from overflowing into // a writing borrow. - assert!(borrow != isize::MAX); + assert!(borrow != BorrowFlag::MAX); self.borrow.set(borrow + 1); BorrowRef { borrow: self.borrow } } @@ -1756,7 +1756,7 @@ impl<'b> BorrowRefMut<'b> { let borrow = self.borrow.get(); debug_assert!(is_writing(borrow)); // Prevent the borrow counter from underflowing. - assert!(borrow != isize::MIN); + assert!(borrow != BorrowFlag::MIN); self.borrow.set(borrow - 1); BorrowRefMut { borrow: self.borrow } } @@ -1893,7 +1893,8 @@ impl fmt::Display for RefMut<'_, T> { /// on an _exclusive_ `UnsafeCell`. Even though `T` and `UnsafeCell` have the /// same memory layout, the following is not allowed and undefined behavior: /// -/// ```rust,no_run +#[cfg_attr(bootstrap, doc = "```rust,no_run")] +#[cfg_attr(not(bootstrap), doc = "```rust,compile_fail")] /// # use std::cell::UnsafeCell; /// unsafe fn not_allowed(ptr: &UnsafeCell) -> &mut T { /// let t = ptr as *const UnsafeCell as *mut T; diff --git a/library/core/src/cell/once.rs b/library/core/src/cell/once.rs index 5f06a7b07953d..2e8534f651a40 100644 --- a/library/core/src/cell/once.rs +++ b/library/core/src/cell/once.rs @@ -250,10 +250,12 @@ impl Default for OnceCell { #[stable(feature = "once_cell", since = "1.70.0")] impl fmt::Debug for OnceCell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_tuple("OnceCell"); match self.get() { - Some(v) => f.debug_tuple("OnceCell").field(v).finish(), - None => f.write_str("OnceCell(Uninit)"), - } + Some(v) => d.field(v), + None => d.field(&format_args!("")), + }; + d.finish() } } diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index a6d6230d3a62b..d7ca9c22dada8 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -86,6 +86,46 @@ /// } /// ``` /// +/// If we `derive`: +/// +/// ``` +/// #[derive(Copy, Clone)] +/// struct Generate(fn() -> T); +/// ``` +/// +/// the auto-derived implementations will have unnecessary `T: Copy` and `T: Clone` bounds: +/// +/// ``` +/// # struct Generate(fn() -> T); +/// +/// // Automatically derived +/// impl Copy for Generate { } +/// +/// // Automatically derived +/// impl Clone for Generate { +/// fn clone(&self) -> Generate { +/// Generate(Clone::clone(&self.0)) +/// } +/// } +/// ``` +/// +/// The bounds are unnecessary because clearly the function itself should be +/// copy- and cloneable even if its return type is not: +/// +/// ```compile_fail,E0599 +/// #[derive(Copy, Clone)] +/// struct Generate(fn() -> T); +/// +/// struct NotCloneable; +/// +/// fn generate_not_cloneable() -> NotCloneable { +/// NotCloneable +/// } +/// +/// Generate(generate_not_cloneable).clone(); // error: trait bounds were not satisfied +/// // Note: With the manual implementations the above line will compile. +/// ``` +/// /// ## Additional implementors /// /// In addition to the [implementors listed below][impls], diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs index faf48ae570fdd..3c127efb390ae 100644 --- a/library/core/src/cmp.rs +++ b/library/core/src/cmp.rs @@ -1406,6 +1406,22 @@ mod impls { _ => unsafe { unreachable_unchecked() }, } } + + #[inline] + fn min(self, other: bool) -> bool { + self & other + } + + #[inline] + fn max(self, other: bool) -> bool { + self | other + } + + #[inline] + fn clamp(self, min: bool, max: bool) -> bool { + assert!(min <= max); + self.max(min).min(max) + } } ord_impl! { char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 } diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index 38a6d1ccdb552..ff5a4c913b7ed 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -495,8 +495,7 @@ pub trait Into: Sized { /// By converting underlying error types to our own custom error type that encapsulates the /// underlying error type, we can return a single error type without losing information on the /// underlying cause. The '?' operator automatically converts the underlying error type to our -/// custom error type by calling `Into::into` which is automatically provided when -/// implementing `From`. The compiler then infers which implementation of `Into` should be used. +/// custom error type with `From::from`. /// /// ``` /// use std::fs; @@ -533,7 +532,7 @@ pub trait Into: Sized { #[rustc_diagnostic_item = "From"] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_on_unimplemented(on( - all(_Self = "&str", T = "std::string::String"), + all(_Self = "&str", any(T = "alloc::string::String", T = "std::string::String")), note = "to coerce a `{T}` into a `{Self}`, use `&*` as a prefix", ))] pub trait From: Sized { diff --git a/library/core/src/default.rs b/library/core/src/default.rs index 09dbc95810f51..5242e97eb9aed 100644 --- a/library/core/src/default.rs +++ b/library/core/src/default.rs @@ -133,51 +133,6 @@ pub trait Default: Sized { fn default() -> Self; } -/// Return the default value of a type according to the `Default` trait. -/// -/// The type to return is inferred from context; this is equivalent to -/// `Default::default()` but shorter to type. -/// -/// For example: -/// ``` -/// #![feature(default_free_fn)] -/// -/// use std::default::default; -/// -/// #[derive(Default)] -/// struct AppConfig { -/// foo: FooConfig, -/// bar: BarConfig, -/// } -/// -/// #[derive(Default)] -/// struct FooConfig { -/// foo: i32, -/// } -/// -/// #[derive(Default)] -/// struct BarConfig { -/// bar: f32, -/// baz: u8, -/// } -/// -/// fn main() { -/// let options = AppConfig { -/// foo: default(), -/// bar: BarConfig { -/// bar: 10.1, -/// ..default() -/// }, -/// }; -/// } -/// ``` -#[unstable(feature = "default_free_fn", issue = "73014")] -#[must_use] -#[inline] -pub fn default() -> T { - Default::default() -} - /// Derive macro generating an impl of the trait `Default`. #[rustc_builtin_macro(Default, attributes(default))] #[stable(feature = "builtin_macro_prelude", since = "1.38.0")] @@ -190,7 +145,7 @@ macro_rules! default_impl { ($t:ty, $v:expr, $doc:tt) => { #[stable(feature = "rust1", since = "1.0.0")] impl Default for $t { - #[inline] + #[inline(always)] #[doc = $doc] fn default() -> $t { $v diff --git a/library/core/src/error.md b/library/core/src/error.md index 78808d489b25a..7771b8adc920b 100644 --- a/library/core/src/error.md +++ b/library/core/src/error.md @@ -93,7 +93,8 @@ information that is already communicated by the source error being unwrapped: ```text -thread 'main' panicked at 'env variable `IMPORTANT_PATH` is not set: NotPresent', src/main.rs:4:6 +thread 'main' panicked at src/main.rs:4:6: +env variable `IMPORTANT_PATH` is not set: NotPresent ``` In this example we end up mentioning that an env variable is not set, @@ -109,7 +110,8 @@ prevent the source error, we end up introducing new information that is independent from our source error. ```text -thread 'main' panicked at 'env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`: NotPresent', src/main.rs:4:6 +thread 'main' panicked at src/main.rs:4:6: +env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`: NotPresent ``` In this example we are communicating not only the name of the diff --git a/library/core/src/error.rs b/library/core/src/error.rs index 11cb082757864..1170221c10c43 100644 --- a/library/core/src/error.rs +++ b/library/core/src/error.rs @@ -4,8 +4,8 @@ #[cfg(test)] mod tests; -use crate::any::{Demand, Provider, TypeId}; -use crate::fmt::{Debug, Display}; +use crate::any::TypeId; +use crate::fmt::{Debug, Display, Formatter, Result}; /// `Error` is a trait representing the basic expectations for error values, /// i.e., values of type `E` in [`Result`]. @@ -123,16 +123,21 @@ pub trait Error: Debug + Display { /// Provides type based access to context intended for error reports. /// - /// Used in conjunction with [`Demand::provide_value`] and [`Demand::provide_ref`] to extract + /// Used in conjunction with [`Request::provide_value`] and [`Request::provide_ref`] to extract /// references to member variables from `dyn Error` trait objects. /// /// # Example /// /// ```rust - /// #![feature(provide_any)] /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] /// use core::fmt; - /// use core::any::Demand; + /// use core::error::{request_ref, Request}; + /// + /// #[derive(Debug)] + /// enum MyLittleTeaPot { + /// Empty, + /// } /// /// #[derive(Debug)] /// struct MyBacktrace { @@ -147,21 +152,7 @@ pub trait Error: Debug + Display { /// } /// /// #[derive(Debug)] - /// struct SourceError { - /// // ... - /// } - /// - /// impl fmt::Display for SourceError { - /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - /// write!(f, "Example Source Error") - /// } - /// } - /// - /// impl std::error::Error for SourceError {} - /// - /// #[derive(Debug)] /// struct Error { - /// source: SourceError, /// backtrace: MyBacktrace, /// } /// @@ -172,38 +163,26 @@ pub trait Error: Debug + Display { /// } /// /// impl std::error::Error for Error { - /// fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - /// demand - /// .provide_ref::(&self.backtrace) - /// .provide_ref::(&self.source); + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request + /// .provide_ref::(&self.backtrace); /// } /// } /// /// fn main() { /// let backtrace = MyBacktrace::new(); - /// let source = SourceError {}; - /// let error = Error { source, backtrace }; + /// let error = Error { backtrace }; /// let dyn_error = &error as &dyn std::error::Error; - /// let backtrace_ref = dyn_error.request_ref::().unwrap(); + /// let backtrace_ref = request_ref::(dyn_error).unwrap(); /// /// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); + /// assert!(request_ref::(dyn_error).is_none()); /// } /// ``` #[unstable(feature = "error_generic_member_access", issue = "99301")] #[allow(unused_variables)] - fn provide<'a>(&'a self, demand: &mut Demand<'a>) {} -} - -#[unstable(feature = "error_generic_member_access", issue = "99301")] -impl Provider for E -where - E: Error + ?Sized, -{ - fn provide<'a>(&'a self, demand: &mut Demand<'a>) { - self.provide(demand) - } + fn provide<'a>(&'a self, request: &mut Request<'a>) {} } - mod private { // This is a hack to prevent `type_id` from being overridden by `Error` // implementations, since that can enable unsound downcasting. @@ -215,20 +194,6 @@ mod private { #[unstable(feature = "never_type", issue = "35121")] impl Error for ! {} -impl<'a> dyn Error + 'a { - /// Request a reference of type `T` as context about this error. - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn request_ref(&'a self) -> Option<&'a T> { - core::any::request_ref(self) - } - - /// Request a value of type `T` as context about this error. - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn request_value(&'a self) -> Option { - core::any::request_value(self) - } -} - // Copied from `any.rs`. impl dyn Error + 'static { /// Returns `true` if the inner type is the same as `T`. @@ -293,18 +258,6 @@ impl dyn Error + 'static + Send { pub fn downcast_mut(&mut self) -> Option<&mut T> { ::downcast_mut::(self) } - - /// Request a reference of type `T` as context about this error. - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn request_ref(&self) -> Option<&T> { - ::request_ref(self) - } - - /// Request a value of type `T` as context about this error. - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn request_value(&self) -> Option { - ::request_value(self) - } } impl dyn Error + 'static + Send + Sync { @@ -328,18 +281,6 @@ impl dyn Error + 'static + Send + Sync { pub fn downcast_mut(&mut self) -> Option<&mut T> { ::downcast_mut::(self) } - - /// Request a reference of type `T` as context about this error. - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn request_ref(&self) -> Option<&T> { - ::request_ref(self) - } - - /// Request a value of type `T` as context about this error. - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn request_value(&self) -> Option { - ::request_value(self) - } } impl dyn Error { @@ -412,6 +353,654 @@ impl dyn Error { } } +/// Request a value of type `T` from the given `impl Error`. +/// +/// # Examples +/// +/// Get a string value from an error. +/// +/// ```rust +/// # #![feature(error_generic_member_access)] +/// # #![feature(error_in_core)] +/// use std::error::Error; +/// use core::error::request_value; +/// +/// fn get_string(err: &impl Error) -> String { +/// request_value::(err).unwrap() +/// } +/// ``` +#[unstable(feature = "error_generic_member_access", issue = "99301")] +pub fn request_value<'a, T>(err: &'a (impl Error + ?Sized)) -> Option +where + T: 'static, +{ + request_by_type_tag::<'a, tags::Value>(err) +} + +/// Request a reference of type `T` from the given `impl Error`. +/// +/// # Examples +/// +/// Get a string reference from an error. +/// +/// ```rust +/// # #![feature(error_generic_member_access)] +/// # #![feature(error_in_core)] +/// use core::error::Error; +/// use core::error::request_ref; +/// +/// fn get_str(err: &impl Error) -> &str { +/// request_ref::(err).unwrap() +/// } +/// ``` +#[unstable(feature = "error_generic_member_access", issue = "99301")] +pub fn request_ref<'a, T>(err: &'a (impl Error + ?Sized)) -> Option<&'a T> +where + T: 'static + ?Sized, +{ + request_by_type_tag::<'a, tags::Ref>>(err) +} + +/// Request a specific value by tag from the `Error`. +fn request_by_type_tag<'a, I>(err: &'a (impl Error + ?Sized)) -> Option +where + I: tags::Type<'a>, +{ + let mut tagged = TaggedOption::<'a, I>(None); + err.provide(tagged.as_request()); + tagged.0 +} + +/////////////////////////////////////////////////////////////////////////////// +// Request and its methods +/////////////////////////////////////////////////////////////////////////////// + +/// `Request` supports generic, type-driven access to data. It's use is currently restricted to the +/// standard library in cases where trait authors wish to allow trait implementors to share generic +/// information across trait boundaries. The motivating and prototypical use case is +/// `core::error::Error` which would otherwise require a method per concrete type (eg. +/// `std::backtrace::Backtrace` instance that implementors want to expose to users). +/// +/// # Data flow +/// +/// To describe the intended data flow for Request objects, let's consider two conceptual users +/// separated by API boundaries: +/// +/// * Consumer - the consumer requests objects using a Request instance; eg a crate that offers +/// fancy `Error`/`Result` reporting to users wants to request a Backtrace from a given `dyn Error`. +/// +/// * Producer - the producer provides objects when requested via Request; eg. a library with an +/// an `Error` implementation that automatically captures backtraces at the time instances are +/// created. +/// +/// The consumer only needs to know where to submit their request and are expected to handle the +/// request not being fulfilled by the use of `Option` in the responses offered by the producer. +/// +/// * A Producer initializes the value of one of its fields of a specific type. (or is otherwise +/// prepared to generate a value requested). eg, `backtrace::Backtrace` or +/// `std::backtrace::Backtrace` +/// * A Consumer requests an object of a specific type (say `std::backtrace::Backtrace). In the case +/// of a `dyn Error` trait object (the Producer), there are methods called `request_ref` and +/// `request_value` are available to simplify obtaining an ``Option`` for a given type. * The +/// Producer, when requested, populates the given Request object which is given as a mutable +/// reference. +/// * The Consumer extracts a value or reference to the requested type from the `Request` object +/// wrapped in an `Option`; in the case of `dyn Error` the aforementioned `request_ref` and ` +/// request_value` methods mean that `dyn Error` users don't have to deal with the `Request` type at +/// all (but `Error` implementors do). The `None` case of the `Option` suggests only that the +/// Producer cannot currently offer an instance of the requested type, not it can't or never will. +/// +/// # Examples +/// +/// The best way to demonstrate this is using an example implementation of `Error`'s `provide` trait +/// method: +/// +/// ``` +/// #![feature(error_generic_member_access)] +/// #![feature(error_in_core)] +/// use core::fmt; +/// use core::error::Request; +/// use core::error::request_ref; +/// +/// #[derive(Debug)] +/// enum MyLittleTeaPot { +/// Empty, +/// } +/// +/// #[derive(Debug)] +/// struct MyBacktrace { +/// // ... +/// } +/// +/// impl MyBacktrace { +/// fn new() -> MyBacktrace { +/// // ... +/// # MyBacktrace {} +/// } +/// } +/// +/// #[derive(Debug)] +/// struct Error { +/// backtrace: MyBacktrace, +/// } +/// +/// impl fmt::Display for Error { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "Example Error") +/// } +/// } +/// +/// impl std::error::Error for Error { +/// fn provide<'a>(&'a self, request: &mut Request<'a>) { +/// request +/// .provide_ref::(&self.backtrace); +/// } +/// } +/// +/// fn main() { +/// let backtrace = MyBacktrace::new(); +/// let error = Error { backtrace }; +/// let dyn_error = &error as &dyn std::error::Error; +/// let backtrace_ref = request_ref::(dyn_error).unwrap(); +/// +/// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); +/// assert!(request_ref::(dyn_error).is_none()); +/// } +/// ``` +/// +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 +pub struct Request<'a>(dyn Erased<'a> + 'a); + +impl<'a> Request<'a> { + /// Create a new `&mut Request` from a `&mut dyn Erased` trait object. + fn new<'b>(erased: &'b mut (dyn Erased<'a> + 'a)) -> &'b mut Request<'a> { + // SAFETY: transmuting `&mut (dyn Erased<'a> + 'a)` to `&mut Request<'a>` is safe since + // `Request` is repr(transparent). + unsafe { &mut *(erased as *mut dyn Erased<'a> as *mut Request<'a>) } + } + + /// Provide a value or other type with only static lifetimes. + /// + /// # Examples + /// + /// Provides an `u8`. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { field: u8 } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.field) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_value::(self.field); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_value(&mut self, value: T) -> &mut Self + where + T: 'static, + { + self.provide::>(value) + } + + /// Provide a value or other type with only static lifetimes computed using a closure. + /// + /// # Examples + /// + /// Provides a `String` by cloning. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { field: String } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.field) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_value_with::(|| self.field.clone()); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_value_with(&mut self, fulfil: impl FnOnce() -> T) -> &mut Self + where + T: 'static, + { + self.provide_with::>(fulfil) + } + + /// Provide a reference. The referee type must be bounded by `'static`, + /// but may be unsized. + /// + /// # Examples + /// + /// Provides a reference to a field as a `&str`. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { field: String } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.field) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_ref::(&self.field); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_ref(&mut self, value: &'a T) -> &mut Self { + self.provide::>>(value) + } + + /// Provide a reference computed using a closure. The referee type + /// must be bounded by `'static`, but may be unsized. + /// + /// # Examples + /// + /// Provides a reference to a field as a `&str`. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { business: String, party: String } + /// fn today_is_a_weekday() -> bool { true } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.business) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_ref_with::(|| { + /// if today_is_a_weekday() { + /// &self.business + /// } else { + /// &self.party + /// } + /// }); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_ref_with( + &mut self, + fulfil: impl FnOnce() -> &'a T, + ) -> &mut Self { + self.provide_with::>>(fulfil) + } + + /// Provide a value with the given `Type` tag. + fn provide(&mut self, value: I::Reified) -> &mut Self + where + I: tags::Type<'a>, + { + if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { + res.0 = Some(value); + } + self + } + + /// Provide a value with the given `Type` tag, using a closure to prevent unnecessary work. + fn provide_with(&mut self, fulfil: impl FnOnce() -> I::Reified) -> &mut Self + where + I: tags::Type<'a>, + { + if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { + res.0 = Some(fulfil()); + } + self + } + + /// Check if the `Request` would be satisfied if provided with a + /// value of the specified type. If the type does not match or has + /// already been provided, returns false. + /// + /// # Examples + /// + /// Check if an `u8` still needs to be provided and then provides + /// it. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] + /// + /// use core::error::Request; + /// use core::error::request_value; + /// + /// #[derive(Debug)] + /// struct Parent(Option); + /// + /// impl std::fmt::Display for Parent { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "a parent failed") + /// } + /// } + /// + /// impl std::error::Error for Parent { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// if let Some(v) = self.0 { + /// request.provide_value::(v); + /// } + /// } + /// } + /// + /// #[derive(Debug)] + /// struct Child { + /// parent: Parent, + /// } + /// + /// impl Child { + /// // Pretend that this takes a lot of resources to evaluate. + /// fn an_expensive_computation(&self) -> Option { + /// Some(99) + /// } + /// } + /// + /// impl std::fmt::Display for Child { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "child failed: \n because of parent: {}", self.parent) + /// } + /// } + /// + /// impl std::error::Error for Child { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// // In general, we don't know if this call will provide + /// // an `u8` value or not... + /// self.parent.provide(request); + /// + /// // ...so we check to see if the `u8` is needed before + /// // we run our expensive computation. + /// if request.would_be_satisfied_by_value_of::() { + /// if let Some(v) = self.an_expensive_computation() { + /// request.provide_value::(v); + /// } + /// } + /// + /// // The request will be satisfied now, regardless of if + /// // the parent provided the value or we did. + /// assert!(!request.would_be_satisfied_by_value_of::()); + /// } + /// } + /// + /// let parent = Parent(Some(42)); + /// let child = Child { parent }; + /// assert_eq!(Some(42), request_value::(&child)); + /// + /// let parent = Parent(None); + /// let child = Child { parent }; + /// assert_eq!(Some(99), request_value::(&child)); + /// + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn would_be_satisfied_by_value_of(&self) -> bool + where + T: 'static, + { + self.would_be_satisfied_by::>() + } + + /// Check if the `Request` would be satisfied if provided with a + /// reference to a value of the specified type. If the type does + /// not match or has already been provided, returns false. + /// + /// # Examples + /// + /// Check if a `&str` still needs to be provided and then provides + /// it. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// #![feature(error_in_core)] + /// + /// use core::error::Request; + /// use core::error::request_ref; + /// + /// #[derive(Debug)] + /// struct Parent(Option); + /// + /// impl std::fmt::Display for Parent { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "a parent failed") + /// } + /// } + /// + /// impl std::error::Error for Parent { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// if let Some(v) = &self.0 { + /// request.provide_ref::(v); + /// } + /// } + /// } + /// + /// #[derive(Debug)] + /// struct Child { + /// parent: Parent, + /// name: String, + /// } + /// + /// impl Child { + /// // Pretend that this takes a lot of resources to evaluate. + /// fn an_expensive_computation(&self) -> Option<&str> { + /// Some(&self.name) + /// } + /// } + /// + /// impl std::fmt::Display for Child { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed: \n {}", self.name, self.parent) + /// } + /// } + /// + /// impl std::error::Error for Child { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// // In general, we don't know if this call will provide + /// // a `str` reference or not... + /// self.parent.provide(request); + /// + /// // ...so we check to see if the `&str` is needed before + /// // we run our expensive computation. + /// if request.would_be_satisfied_by_ref_of::() { + /// if let Some(v) = self.an_expensive_computation() { + /// request.provide_ref::(v); + /// } + /// } + /// + /// // The request will be satisfied now, regardless of if + /// // the parent provided the reference or we did. + /// assert!(!request.would_be_satisfied_by_ref_of::()); + /// } + /// } + /// + /// let parent = Parent(Some("parent".into())); + /// let child = Child { parent, name: "child".into() }; + /// assert_eq!(Some("parent"), request_ref::(&child)); + /// + /// let parent = Parent(None); + /// let child = Child { parent, name: "child".into() }; + /// assert_eq!(Some("child"), request_ref::(&child)); + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn would_be_satisfied_by_ref_of(&self) -> bool + where + T: ?Sized + 'static, + { + self.would_be_satisfied_by::>>() + } + + fn would_be_satisfied_by(&self) -> bool + where + I: tags::Type<'a>, + { + matches!(self.0.downcast::(), Some(TaggedOption(None))) + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a> Debug for Request<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("Request").finish_non_exhaustive() + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Type tags +/////////////////////////////////////////////////////////////////////////////// + +pub(crate) mod tags { + //! Type tags are used to identify a type using a separate value. This module includes type tags + //! for some very common types. + //! + //! Currently type tags are not exposed to the user. But in the future, if you want to use the + //! Request API with more complex types (typically those including lifetime parameters), you + //! will need to write your own tags. + + use crate::marker::PhantomData; + + /// This trait is implemented by specific tag types in order to allow + /// describing a type which can be requested for a given lifetime `'a`. + /// + /// A few example implementations for type-driven tags can be found in this + /// module, although crates may also implement their own tags for more + /// complex types with internal lifetimes. + pub(crate) trait Type<'a>: Sized + 'static { + /// The type of values which may be tagged by this tag for the given + /// lifetime. + type Reified: 'a; + } + + /// Similar to the [`Type`] trait, but represents a type which may be unsized (i.e., has a + /// `?Sized` bound). E.g., `str`. + pub(crate) trait MaybeSizedType<'a>: Sized + 'static { + type Reified: 'a + ?Sized; + } + + impl<'a, T: Type<'a>> MaybeSizedType<'a> for T { + type Reified = T::Reified; + } + + /// Type-based tag for types bounded by `'static`, i.e., with no borrowed elements. + #[derive(Debug)] + pub(crate) struct Value(PhantomData); + + impl<'a, T: 'static> Type<'a> for Value { + type Reified = T; + } + + /// Type-based tag similar to [`Value`] but which may be unsized (i.e., has a `?Sized` bound). + #[derive(Debug)] + pub(crate) struct MaybeSizedValue(PhantomData); + + impl<'a, T: ?Sized + 'static> MaybeSizedType<'a> for MaybeSizedValue { + type Reified = T; + } + + /// Type-based tag for reference types (`&'a T`, where T is represented by + /// `>::Reified`. + #[derive(Debug)] + pub(crate) struct Ref(PhantomData); + + impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref { + type Reified = &'a I::Reified; + } +} + +/// An `Option` with a type tag `I`. +/// +/// Since this struct implements `Erased`, the type can be erased to make a dynamically typed +/// option. The type can be checked dynamically using `Erased::tag_id` and since this is statically +/// checked for the concrete type, there is some degree of type safety. +#[repr(transparent)] +pub(crate) struct TaggedOption<'a, I: tags::Type<'a>>(pub Option); + +impl<'a, I: tags::Type<'a>> TaggedOption<'a, I> { + pub(crate) fn as_request(&mut self) -> &mut Request<'a> { + Request::new(self as &mut (dyn Erased<'a> + 'a)) + } +} + +/// Represents a type-erased but identifiable object. +/// +/// This trait is exclusively implemented by the `TaggedOption` type. +unsafe trait Erased<'a>: 'a { + /// The `TypeId` of the erased type. + fn tag_id(&self) -> TypeId; +} + +unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> { + fn tag_id(&self) -> TypeId { + TypeId::of::() + } +} + +impl<'a> dyn Erased<'a> + 'a { + /// Returns some reference to the dynamic value if it is tagged with `I`, + /// or `None` otherwise. + #[inline] + fn downcast(&self) -> Option<&TaggedOption<'a, I>> + where + I: tags::Type<'a>, + { + if self.tag_id() == TypeId::of::() { + // SAFETY: Just checked whether we're pointing to an I. + Some(unsafe { &*(self as *const Self).cast::>() }) + } else { + None + } + } + + /// Returns some mutable reference to the dynamic value if it is tagged with `I`, + /// or `None` otherwise. + #[inline] + fn downcast_mut(&mut self) -> Option<&mut TaggedOption<'a, I>> + where + I: tags::Type<'a>, + { + if self.tag_id() == TypeId::of::() { + // SAFETY: Just checked whether we're pointing to an I. + Some(unsafe { &mut *(self as *mut Self).cast::>() }) + } else { + None + } + } +} + /// An iterator over an [`Error`] and its sources. /// /// If you want to omit the initial error and only process @@ -449,8 +1038,8 @@ impl<'a, T: Error + ?Sized> Error for &'a T { Error::source(&**self) } - fn provide<'b>(&'b self, demand: &mut Demand<'b>) { - Error::provide(&**self, demand); + fn provide<'b>(&'b self, request: &mut Request<'b>) { + Error::provide(&**self, request); } } diff --git a/library/core/src/escape.rs b/library/core/src/escape.rs index 3d471419bb8f1..24bb9ad1ad1a2 100644 --- a/library/core/src/escape.rs +++ b/library/core/src/escape.rs @@ -95,11 +95,11 @@ impl EscapeIterInner { } pub fn next(&mut self) -> Option { - self.alive.next().map(|i| self.data[usize::from(i)].as_u8()) + self.alive.next().map(|i| self.data[usize::from(i)].to_u8()) } pub fn next_back(&mut self) -> Option { - self.alive.next_back().map(|i| self.data[usize::from(i)].as_u8()) + self.alive.next_back().map(|i| self.data[usize::from(i)].to_u8()) } pub fn advance_by(&mut self, n: usize) -> Result<(), NonZeroUsize> { diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs index 201bacb28c7ee..163a65c909e45 100644 --- a/library/core/src/ffi/c_str.rs +++ b/library/core/src/ffi/c_str.rs @@ -20,10 +20,10 @@ use crate::str; /// in each pair are borrowed references; the latter are owned /// strings. /// -/// Note that this structure is **not** `repr(C)` and is not recommended to be -/// placed in the signatures of FFI functions. Instead, safe wrappers of FFI -/// functions may leverage the unsafe [`CStr::from_ptr`] constructor to provide -/// a safe interface to other consumers. +/// Note that this structure does **not** have a guaranteed layout (the `repr(transparent)` +/// notwithstanding) and is not recommended to be placed in the signatures of FFI functions. +/// Instead, safe wrappers of FFI functions may leverage the unsafe [`CStr::from_ptr`] constructor +/// to provide a safe interface to other consumers. /// /// [`CString`]: ../../std/ffi/struct.CString.html /// [`String`]: ../../std/string/struct.String.html @@ -81,13 +81,13 @@ use crate::str; #[derive(Hash)] #[stable(feature = "core_c_str", since = "1.64.0")] #[rustc_has_incoherent_inherent_impls] -#[cfg_attr(not(bootstrap), lang = "CStr")] -// FIXME: +#[lang = "CStr"] // `fn from` in `impl From<&CStr> for Box` current implementation relies // on `CStr` being layout-compatible with `[u8]`. -// When attribute privacy is implemented, `CStr` should be annotated as `#[repr(transparent)]`. -// Anyway, `CStr` representation and layout are considered implementation detail, are -// not documented and must not be relied upon. +// However, `CStr` layout is considered an implementation detail and must not be relied upon. We +// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under +// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. +#[cfg_attr(not(doc), repr(transparent))] pub struct CStr { // FIXME: this should not be represented with a DST slice but rather with // just a raw `c_char` along with some form of marker to make @@ -197,8 +197,8 @@ impl CStr { /// /// This function will wrap the provided `ptr` with a `CStr` wrapper, which /// allows inspection and interoperation of non-owned C strings. The total - /// size of the raw C string must be smaller than `isize::MAX` **bytes** - /// in memory due to calling the `slice::from_raw_parts` function. + /// size of the terminated buffer must be smaller than [`isize::MAX`] **bytes** + /// in memory (a restriction from [`slice::from_raw_parts`]). /// /// # Safety /// @@ -241,7 +241,7 @@ impl CStr { /// ``` /// /// ``` - /// #![feature(const_cstr_methods)] + /// #![feature(const_cstr_from_ptr)] /// /// use std::ffi::{c_char, CStr}; /// @@ -253,10 +253,10 @@ impl CStr { /// ``` /// /// [valid]: core::ptr#safety - #[inline] + #[inline] // inline is necessary for codegen to see strlen. #[must_use] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")] + #[rustc_const_unstable(feature = "const_cstr_from_ptr", issue = "113219")] pub const unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr { // SAFETY: The caller has provided a pointer that points to a valid C // string with a NUL terminator of size less than `isize::MAX`, whose @@ -280,6 +280,8 @@ impl CStr { len } + // `inline` is necessary for codegen to see strlen. + #[inline] fn strlen_rt(s: *const c_char) -> usize { extern "C" { /// Provided by libc or compiler_builtins. @@ -295,11 +297,11 @@ impl CStr { } } - /// Creates a C string wrapper from a byte slice. + /// Creates a C string wrapper from a byte slice with any number of nuls. /// /// This method will create a `CStr` from any byte slice that contains at - /// least one nul byte. The caller does not need to know or specify where - /// the nul byte is located. + /// least one nul byte. Unlike with [`CStr::from_bytes_with_nul`], the caller + /// does not need to know where the nul byte is located. /// /// If the first byte is a nul character, this method will return an /// empty `CStr`. If multiple nul characters are present, the `CStr` will @@ -341,7 +343,8 @@ impl CStr { } } - /// Creates a C string wrapper from a byte slice. + /// Creates a C string wrapper from a byte slice with exactly one nul + /// terminator. /// /// This function will cast the provided `bytes` to a `CStr` /// wrapper after ensuring that the byte slice is nul-terminated @@ -377,7 +380,7 @@ impl CStr { /// assert!(cstr.is_err()); /// ``` #[stable(feature = "cstr_from_bytes", since = "1.10.0")] - #[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")] + #[rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0")] pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, FromBytesWithNulError> { let nul_pos = memchr::memchr(0, bytes); match nul_pos { @@ -531,8 +534,8 @@ impl CStr { /// # } /// ``` #[inline] - #[stable(feature = "cstr_is_empty", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "cstr_is_empty", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "cstr_is_empty", since = "1.71.0")] + #[rustc_const_stable(feature = "cstr_is_empty", since = "1.71.0")] pub const fn is_empty(&self) -> bool { // SAFETY: We know there is at least one byte; for empty strings it // is the NUL terminator. @@ -561,10 +564,12 @@ impl CStr { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] - pub fn to_bytes(&self) -> &[u8] { + #[rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0")] + pub const fn to_bytes(&self) -> &[u8] { let bytes = self.to_bytes_with_nul(); + // FIXME(const-hack) replace with range index // SAFETY: to_bytes_with_nul returns slice with length at least 1 - unsafe { bytes.get_unchecked(..bytes.len() - 1) } + unsafe { slice::from_raw_parts(bytes.as_ptr(), bytes.len() - 1) } } /// Converts this C string to a byte slice containing the trailing 0 byte. @@ -588,7 +593,7 @@ impl CStr { #[must_use = "this returns the result of the operation, \ without modifying the original"] #[stable(feature = "rust1", since = "1.0.0")] - #[rustc_const_unstable(feature = "const_cstr_methods", issue = "101719")] + #[rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0")] pub const fn to_bytes_with_nul(&self) -> &[u8] { // SAFETY: Transmuting a slice of `c_char`s to a slice of `u8`s // is safe on all supported targets. @@ -612,7 +617,8 @@ impl CStr { /// assert_eq!(cstr.to_str(), Ok("foo")); /// ``` #[stable(feature = "cstr_to_str", since = "1.4.0")] - pub fn to_str(&self) -> Result<&str, str::Utf8Error> { + #[rustc_const_stable(feature = "const_cstr_methods", since = "1.72.0")] + pub const fn to_str(&self) -> Result<&str, str::Utf8Error> { // N.B., when `CStr` is changed to perform the length check in `.to_bytes()` // instead of in `from_ptr()`, it may be worth considering if this should // be rewritten to do the UTF-8 check inline with the length calculation diff --git a/library/core/src/ffi/mod.rs b/library/core/src/ffi/mod.rs index b73abbbaca7ad..b2c9a0800c91b 100644 --- a/library/core/src/ffi/mod.rs +++ b/library/core/src/ffi/mod.rs @@ -52,11 +52,6 @@ macro_rules! type_alias { } type_alias! { "c_char.md", c_char = c_char_definition::c_char, NonZero_c_char = c_char_definition::NonZero_c_char; -// Make this type alias appear cfg-dependent so that Clippy does not suggest -// replacing `0 as c_char` with `0_i8`/`0_u8`. This #[cfg(all())] can be removed -// after the false positive in https://github.com/rust-lang/rust-clippy/issues/8093 -// is fixed. -#[cfg(all())] #[doc(cfg(all()))] } type_alias! { "c_schar.md", c_schar = i8, NonZero_c_schar = NonZeroI8; } @@ -115,7 +110,8 @@ mod c_char_definition { target_arch = "powerpc64", target_arch = "s390x", target_arch = "riscv64", - target_arch = "riscv32" + target_arch = "riscv32", + target_arch = "csky" ) ), all(target_os = "android", any(target_arch = "aarch64", target_arch = "arm")), @@ -132,7 +128,12 @@ mod c_char_definition { ), all( target_os = "netbsd", - any(target_arch = "aarch64", target_arch = "arm", target_arch = "powerpc") + any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "powerpc", + target_arch = "riscv64" + ) ), all( target_os = "vxworks", @@ -202,7 +203,7 @@ mod c_long_definition { // would be uninhabited and at least dereferencing such pointers would // be UB. #[doc = include_str!("c_void.md")] -#[cfg_attr(not(bootstrap), lang = "c_void")] +#[lang = "c_void"] #[cfg_attr(not(doc), repr(u8))] // work around https://github.com/rust-lang/rust/issues/90435 #[stable(feature = "core_c_void", since = "1.30.0")] pub enum c_void { @@ -238,7 +239,7 @@ impl fmt::Debug for c_void { not(target_arch = "s390x"), not(target_arch = "x86_64") ), - all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios")), + all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios", target_os = "tvos")), target_family = "wasm", target_arch = "asmjs", target_os = "uefi", @@ -267,7 +268,7 @@ pub struct VaListImpl<'f> { not(target_arch = "s390x"), not(target_arch = "x86_64") ), - all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios")), + all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios", target_os = "tvos")), target_family = "wasm", target_arch = "asmjs", target_os = "uefi", @@ -292,7 +293,7 @@ impl<'f> fmt::Debug for VaListImpl<'f> { /// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf #[cfg(all( target_arch = "aarch64", - not(any(target_os = "macos", target_os = "ios")), + not(any(target_os = "macos", target_os = "ios", target_os = "tvos")), not(target_os = "uefi"), not(windows), ))] @@ -389,7 +390,10 @@ pub struct VaList<'a, 'f: 'a> { not(target_arch = "s390x"), not(target_arch = "x86_64") ), - all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios")), + all( + target_arch = "aarch64", + any(target_os = "macos", target_os = "ios", target_os = "tvos") + ), target_family = "wasm", target_arch = "asmjs", target_os = "uefi", @@ -404,7 +408,10 @@ pub struct VaList<'a, 'f: 'a> { target_arch = "s390x", target_arch = "x86_64" ), - any(not(target_arch = "aarch64"), not(any(target_os = "macos", target_os = "ios"))), + any( + not(target_arch = "aarch64"), + not(any(target_os = "macos", target_os = "ios", target_os = "tvos")) + ), not(target_family = "wasm"), not(target_arch = "asmjs"), not(target_os = "uefi"), @@ -422,7 +429,7 @@ pub struct VaList<'a, 'f: 'a> { not(target_arch = "s390x"), not(target_arch = "x86_64") ), - all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios")), + all(target_arch = "aarch64", any(target_os = "macos", target_os = "ios", target_os = "tvos")), target_family = "wasm", target_arch = "asmjs", target_os = "uefi", @@ -449,7 +456,10 @@ impl<'f> VaListImpl<'f> { target_arch = "s390x", target_arch = "x86_64" ), - any(not(target_arch = "aarch64"), not(any(target_os = "macos", target_os = "ios"))), + any( + not(target_arch = "aarch64"), + not(any(target_os = "macos", target_os = "ios", target_os = "tvos")) + ), not(target_family = "wasm"), not(target_arch = "asmjs"), not(target_os = "uefi"), diff --git a/library/core/src/fmt/builders.rs b/library/core/src/fmt/builders.rs index 36f49d51ca6d3..d2c9f98004206 100644 --- a/library/core/src/fmt/builders.rs +++ b/library/core/src/fmt/builders.rs @@ -518,7 +518,7 @@ impl<'a, 'b: 'a> DebugSet<'a, 'b> { /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt.debug_set() /// .entries(self.0.iter()) - /// .finish() // Ends the struct formatting. + /// .finish() // Ends the set formatting. /// } /// } /// @@ -648,7 +648,7 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> { /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt.debug_list() /// .entries(self.0.iter()) - /// .finish() // Ends the struct formatting. + /// .finish() // Ends the list formatting. /// } /// } /// @@ -905,7 +905,7 @@ impl<'a, 'b: 'a> DebugMap<'a, 'b> { /// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { /// fmt.debug_map() /// .entries(self.0.iter().map(|&(ref k, ref v)| (k, v))) - /// .finish() // Ends the struct formatting. + /// .finish() // Ends the map formatting. /// } /// } /// diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 1786b309c5bd3..9ce6093f1d1f3 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -2521,22 +2521,12 @@ impl Debug for Cell { #[stable(feature = "rust1", since = "1.0.0")] impl Debug for RefCell { fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let mut d = f.debug_struct("RefCell"); match self.try_borrow() { - Ok(borrow) => f.debug_struct("RefCell").field("value", &borrow).finish(), - Err(_) => { - // The RefCell is mutably borrowed so we can't look at its value - // here. Show a placeholder instead. - struct BorrowedPlaceholder; - - impl Debug for BorrowedPlaceholder { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.write_str("") - } - } - - f.debug_struct("RefCell").field("value", &BorrowedPlaceholder).finish() - } - } + Ok(borrow) => d.field("value", &borrow), + Err(_) => d.field("value", &format_args!("")), + }; + d.finish() } } diff --git a/library/core/src/future/poll_fn.rs b/library/core/src/future/poll_fn.rs index 90cb797391a08..d27a9dfc176e3 100644 --- a/library/core/src/future/poll_fn.rs +++ b/library/core/src/future/poll_fn.rs @@ -24,6 +24,93 @@ use crate::task::{Context, Poll}; /// assert_eq!(read_future.await, "Hello, World!".to_owned()); /// # } /// ``` +/// +/// ## Capturing a pinned state +/// +/// Example of a closure wrapping inner futures: +/// +/// ``` +/// # async fn run() { +/// use core::future::{self, Future}; +/// use core::task::Poll; +/// +/// /// Resolves to the first future that completes. In the event of a tie, `a` wins. +/// fn naive_select( +/// a: impl Future, +/// b: impl Future, +/// ) -> impl Future +/// { +/// let (mut a, mut b) = (Box::pin(a), Box::pin(b)); +/// future::poll_fn(move |cx| { +/// if let Poll::Ready(r) = a.as_mut().poll(cx) { +/// Poll::Ready(r) +/// } else if let Poll::Ready(r) = b.as_mut().poll(cx) { +/// Poll::Ready(r) +/// } else { +/// Poll::Pending +/// } +/// }) +/// } +/// +/// let a = async { 42 }; +/// let b = future::pending(); +/// let v = naive_select(a, b).await; +/// assert_eq!(v, 42); +/// +/// let a = future::pending(); +/// let b = async { 27 }; +/// let v = naive_select(a, b).await; +/// assert_eq!(v, 27); +/// +/// let a = async { 42 }; +/// let b = async { 27 }; +/// let v = naive_select(a, b).await; +/// assert_eq!(v, 42); // biased towards `a` in case of tie! +/// # } +/// ``` +/// +/// This time without [`Box::pin`]ning: +/// +/// [`Box::pin`]: ../../std/boxed/struct.Box.html#method.pin +/// +/// ``` +/// # async fn run() { +/// use core::future::{self, Future}; +/// use core::pin::pin; +/// use core::task::Poll; +/// +/// /// Resolves to the first future that completes. In the event of a tie, `a` wins. +/// fn naive_select( +/// a: impl Future, +/// b: impl Future, +/// ) -> impl Future +/// { +/// async { +/// let (mut a, mut b) = (pin!(a), pin!(b)); +/// future::poll_fn(move |cx| { +/// if let Poll::Ready(r) = a.as_mut().poll(cx) { +/// Poll::Ready(r) +/// } else if let Poll::Ready(r) = b.as_mut().poll(cx) { +/// Poll::Ready(r) +/// } else { +/// Poll::Pending +/// } +/// }).await +/// } +/// } +/// +/// let a = async { 42 }; +/// let b = future::pending(); +/// let v = naive_select(a, b).await; +/// assert_eq!(v, 42); +/// # } +/// ``` +/// +/// - Notice how, by virtue of being in an `async` context, we have been able to make the [`pin!`] +/// macro work, thereby avoiding any need for the `unsafe` +/// [Pin::new_unchecked](&mut fut) constructor. +/// +/// [`pin!`]: crate::pin::pin! #[stable(feature = "future_poll_fn", since = "1.64.0")] pub fn poll_fn(f: F) -> PollFn where diff --git a/library/core/src/hash/mod.rs b/library/core/src/hash/mod.rs index a73b5b610a4ad..794a57f09226c 100644 --- a/library/core/src/hash/mod.rs +++ b/library/core/src/hash/mod.rs @@ -674,8 +674,6 @@ pub trait BuildHasher { /// # Example /// /// ``` - /// #![feature(build_hasher_simple_hash_one)] - /// /// use std::cmp::{max, min}; /// use std::hash::{BuildHasher, Hash, Hasher}; /// struct OrderAmbivalentPair(T, T); @@ -697,7 +695,7 @@ pub trait BuildHasher { /// bh.hash_one(&OrderAmbivalentPair(2, 10)) /// ); /// ``` - #[unstable(feature = "build_hasher_simple_hash_one", issue = "86161")] + #[stable(feature = "build_hasher_simple_hash_one", since = "1.71.0")] fn hash_one(&self, x: T) -> u64 where Self: Sized, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 23ded42fa6679..676d4f2f38ca3 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -9,7 +9,7 @@ //! This includes changes in the stability of the constness. //! //! In order to make an intrinsic usable at compile-time, one needs to copy the implementation -//! from to +//! from to //! and add a //! `#[rustc_const_unstable(feature = "const_such_and_such", issue = "01234")]` to the intrinsic declaration. //! @@ -1057,7 +1057,7 @@ extern "rust-intrinsic" { #[rustc_const_unstable(feature = "const_type_id", issue = "77125")] #[rustc_safe_intrinsic] #[rustc_nounwind] - pub fn type_id() -> u64; + pub fn type_id() -> u128; /// A guard for unsafe functions that cannot ever be executed if `T` is uninhabited: /// This will statically either panic, or do nothing. @@ -1385,7 +1385,6 @@ extern "rust-intrinsic" { /// /// This is not expected to ever be exposed directly to users, rather it /// may eventually be exposed through some more-constrained API. - #[cfg(not(bootstrap))] #[rustc_const_stable(feature = "const_transmute", since = "1.56.0")] #[rustc_nounwind] pub fn transmute_unchecked(src: Src) -> Dst; @@ -1425,19 +1424,11 @@ extern "rust-intrinsic" { /// returned value will result in undefined behavior. /// /// The stabilized version of this intrinsic is [`pointer::offset`]. - #[cfg(not(bootstrap))] #[must_use = "returns a new pointer rather than modifying its argument"] #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] #[rustc_nounwind] pub fn offset(dst: Ptr, offset: Delta) -> Ptr; - /// The bootstrap version of this is more restricted. - #[cfg(bootstrap)] - #[must_use = "returns a new pointer rather than modifying its argument"] - #[rustc_const_stable(feature = "const_ptr_offset", since = "1.61.0")] - #[rustc_nounwind] - pub fn offset(dst: *const T, offset: isize) -> *const T; - /// Calculates the offset from a pointer, potentially wrapping. /// /// This is implemented as an intrinsic to avoid converting to and from an @@ -2260,7 +2251,7 @@ extern "rust-intrinsic" { /// This intrinsic can *only* be called where the pointer is a local without /// projections (`read_via_copy(ptr)`, not `read_via_copy(*ptr)`) so that it /// trivially obeys runtime-MIR rules about derefs in operands. - #[rustc_const_stable(feature = "const_ptr_read", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_stable(feature = "const_ptr_read", since = "1.71.0")] #[rustc_nounwind] pub fn read_via_copy(ptr: *const T) -> T; @@ -2270,7 +2261,6 @@ extern "rust-intrinsic" { /// This intrinsic can *only* be called where the pointer is a local without /// projections (`write_via_move(ptr, x)`, not `write_via_move(*ptr, x)`) so /// that it trivially obeys runtime-MIR rules about derefs in operands. - #[cfg(not(bootstrap))] #[rustc_const_unstable(feature = "const_ptr_write", issue = "86302")] #[rustc_nounwind] pub fn write_via_move(ptr: *mut T, value: T); @@ -2395,6 +2385,25 @@ extern "rust-intrinsic" { #[rustc_nounwind] pub fn raw_eq(a: &T, b: &T) -> bool; + /// Lexicographically compare `[left, left + bytes)` and `[right, right + bytes)` + /// as unsigned bytes, returning negative if `left` is less, zero if all the + /// bytes match, or positive if `right` is greater. + /// + /// This underlies things like `<[u8]>::cmp`, and will usually lower to `memcmp`. + /// + /// # Safety + /// + /// `left` and `right` must each be [valid] for reads of `bytes` bytes. + /// + /// Note that this applies to the whole range, not just until the first byte + /// that differs. That allows optimizations that can read in large chunks. + /// + /// [valid]: crate::ptr#safety + #[cfg(not(bootstrap))] + #[rustc_const_unstable(feature = "const_intrinsic_compare_bytes", issue = "none")] + #[rustc_nounwind] + pub fn compare_bytes(left: *const u8, right: *const u8, bytes: usize) -> i32; + /// See documentation of [`std::hint::black_box`] for details. /// /// [`std::hint::black_box`]: crate::hint::black_box @@ -2534,12 +2543,14 @@ pub(crate) use assert_unsafe_precondition; /// Checks whether `ptr` is properly aligned with respect to /// `align_of::()`. +#[inline] pub(crate) fn is_aligned_and_not_null(ptr: *const T) -> bool { !ptr.is_null() && ptr.is_aligned() } /// Checks whether an allocation of `len` instances of `T` exceeds /// the maximum allowed allocation size. +#[inline] pub(crate) fn is_valid_allocation_size(len: usize) -> bool { let max_len = const { let size = crate::mem::size_of::(); @@ -2550,6 +2561,7 @@ pub(crate) fn is_valid_allocation_size(len: usize) -> bool { /// Checks whether the regions of memory starting at `src` and `dst` of size /// `count * size_of::()` do *not* overlap. +#[inline] pub(crate) fn is_nonoverlapping(src: *const T, dst: *const T, count: usize) -> bool { let src_usize = src.addr(); let dst_usize = dst.addr(); @@ -2650,7 +2662,7 @@ pub(crate) fn is_nonoverlapping(src: *const T, dst: *const T, count: usize) - #[stable(feature = "rust1", since = "1.0.0")] #[rustc_allowed_through_unstable_modules] #[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")] -#[inline] +#[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize) { extern "rust-intrinsic" { @@ -2741,7 +2753,7 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us #[stable(feature = "rust1", since = "1.0.0")] #[rustc_allowed_through_unstable_modules] #[rustc_const_stable(feature = "const_intrinsic_copy", since = "1.63.0")] -#[inline] +#[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn copy(src: *const T, dst: *mut T, count: usize) { extern "rust-intrinsic" { @@ -2814,7 +2826,7 @@ pub const unsafe fn copy(src: *const T, dst: *mut T, count: usize) { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_allowed_through_unstable_modules] #[rustc_const_unstable(feature = "const_ptr_write", issue = "86302")] -#[inline] +#[inline(always)] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) { extern "rust-intrinsic" { @@ -2833,23 +2845,17 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) { } } -/// Polyfill for bootstrap +/// Backfill for bootstrap #[cfg(bootstrap)] -pub const unsafe fn transmute_unchecked(src: Src) -> Dst { - use crate::mem::*; - // SAFETY: It's a transmute -- the caller promised it's fine. - unsafe { transmute_copy(&ManuallyDrop::new(src)) } -} +pub unsafe fn compare_bytes(left: *const u8, right: *const u8, bytes: usize) -> i32 { + extern "C" { + fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> crate::ffi::c_int; + } -/// Polyfill for bootstrap -#[cfg(bootstrap)] -pub const unsafe fn write_via_move(ptr: *mut T, value: T) { - use crate::mem::*; - // SAFETY: the caller must guarantee that `dst` is valid for writes. - // `dst` cannot overlap `src` because the caller has mutable access - // to `dst` while `src` is owned by this function. - unsafe { - copy_nonoverlapping::(&value, ptr, 1); - forget(value); + if bytes != 0 { + // SAFETY: Since bytes is non-zero, the caller has met `memcmp`'s requirements. + unsafe { memcmp(left, right, bytes).into() } + } else { + 0 } } diff --git a/library/core/src/intrinsics/mir.rs b/library/core/src/intrinsics/mir.rs index 5944a0de1a4b2..ef0a2fd4ec4b3 100644 --- a/library/core/src/intrinsics/mir.rs +++ b/library/core/src/intrinsics/mir.rs @@ -14,6 +14,7 @@ //! //! ```rust //! #![feature(core_intrinsics, custom_mir)] +#![cfg_attr(not(bootstrap), doc = "#![allow(internal_features)]")] //! //! use core::intrinsics::mir::*; //! @@ -63,6 +64,7 @@ //! //! ```rust //! #![feature(core_intrinsics, custom_mir)] +#![cfg_attr(not(bootstrap), doc = "#![allow(internal_features)]")] //! //! use core::intrinsics::mir::*; //! @@ -102,17 +104,18 @@ //! } //! //! #[custom_mir(dialect = "runtime", phase = "optimized")] +#![cfg_attr(bootstrap, doc = "#[cfg(any())]")] // disable the following function in doctests when `bootstrap` is set //! fn push_and_pop(v: &mut Vec, value: T) { //! mir!( -//! let unused; +//! let _unused; //! let popped; //! //! { -//! Call(unused, pop, Vec::push(v, value)) +//! Call(_unused = Vec::push(v, value), pop) //! } //! //! pop = { -//! Call(popped, drop, Vec::pop(v)) +//! Call(popped = Vec::pop(v), drop) //! } //! //! drop = { @@ -273,7 +276,7 @@ define!("mir_return", fn Return() -> BasicBlock); define!("mir_goto", fn Goto(destination: BasicBlock) -> BasicBlock); define!("mir_unreachable", fn Unreachable() -> BasicBlock); define!("mir_drop", fn Drop(place: T, goto: BasicBlock)); -define!("mir_call", fn Call(place: T, goto: BasicBlock, call: T)); +define!("mir_call", fn Call(call: (), goto: BasicBlock)); define!("mir_storage_live", fn StorageLive(local: T)); define!("mir_storage_dead", fn StorageDead(local: T)); define!("mir_deinit", fn Deinit(place: T)); @@ -315,6 +318,7 @@ define!( /// # Examples /// /// ```rust + #[cfg_attr(not(bootstrap), doc = "#![allow(internal_features)]")] /// #![feature(custom_mir, core_intrinsics)] /// /// use core::intrinsics::mir::*; diff --git a/library/core/src/iter/adapters/flatten.rs b/library/core/src/iter/adapters/flatten.rs index 2568aaf34f3fb..eee6e5bccecc7 100644 --- a/library/core/src/iter/adapters/flatten.rs +++ b/library/core/src/iter/adapters/flatten.rs @@ -464,7 +464,6 @@ where } } -#[unstable(feature = "trusted_len", issue = "37572")] impl Iterator for FlattenCompat where I: Iterator>, @@ -579,7 +578,6 @@ where } } -#[unstable(feature = "trusted_len", issue = "37572")] impl DoubleEndedIterator for FlattenCompat where I: DoubleEndedIterator>, @@ -649,7 +647,6 @@ where } } -#[unstable(feature = "trusted_len", issue = "37572")] unsafe impl TrustedLen for FlattenCompat::IntoIter> where @@ -657,7 +654,6 @@ where { } -#[unstable(feature = "trusted_len", issue = "37572")] unsafe impl<'a, const N: usize, I, T> TrustedLen for FlattenCompat::IntoIter> where @@ -665,7 +661,6 @@ where { } -#[unstable(feature = "trusted_len", issue = "37572")] unsafe impl<'a, const N: usize, I, T> TrustedLen for FlattenCompat::IntoIter> where diff --git a/library/core/src/iter/adapters/map_windows.rs b/library/core/src/iter/adapters/map_windows.rs new file mode 100644 index 0000000000000..3c0e80b2559db --- /dev/null +++ b/library/core/src/iter/adapters/map_windows.rs @@ -0,0 +1,293 @@ +use crate::{ + fmt, + iter::{ExactSizeIterator, FusedIterator}, + mem::{self, MaybeUninit}, + ptr, +}; + +/// An iterator over the mapped windows of another iterator. +/// +/// This `struct` is created by the [`Iterator::map_windows`]. See its +/// documentation for more information. +#[must_use = "iterators are lazy and do nothing unless consumed"] +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +pub struct MapWindows { + f: F, + inner: MapWindowsInner, +} + +struct MapWindowsInner { + // We fuse the inner iterator because there shouldn't be "holes" in + // the sliding window. Once the iterator returns a `None`, we make + // our `MapWindows` iterator return `None` forever. + iter: Option, + // Since iterators are assumed lazy, i.e. it only yields an item when + // `Iterator::next()` is called, and `MapWindows` is not an exception. + // + // Before the first iteration, we keep the buffer `None`. When the user + // first call `next` or other methods that makes the iterator advance, + // we collect the first `N` items yielded from the inner iterator and + // put it into the buffer. + // + // When the inner iterator has returned a `None` (i.e. fused), we take + // away this `buffer` and leave it `None` to reclaim its resources. + // + // FIXME: should we shrink the size of `buffer` using niche optimization? + buffer: Option>, +} + +// `Buffer` uses two times of space to reduce moves among the iterations. +// `Buffer` is semantically `[MaybeUninit; 2 * N]`. However, due +// to limitations of const generics, we use this different type. Note that +// it has the same underlying memory layout. +struct Buffer { + // Invariant: `self.buffer[self.start..self.start + N]` is initialized, + // with all other elements being uninitialized. This also + // implies that `self.start <= N`. + buffer: [[MaybeUninit; N]; 2], + start: usize, +} + +impl MapWindows { + pub(in crate::iter) fn new(iter: I, f: F) -> Self { + assert!(N != 0, "array in `Iterator::map_windows` must contain more than 0 elements"); + + // Only ZST arrays' length can be so large. + if mem::size_of::() == 0 { + assert!( + N.checked_mul(2).is_some(), + "array size of `Iterator::map_windows` is too large" + ); + } + + Self { inner: MapWindowsInner::new(iter), f } + } +} + +impl MapWindowsInner { + #[inline] + fn new(iter: I) -> Self { + Self { iter: Some(iter), buffer: None } + } + + fn next_window(&mut self) -> Option<&[I::Item; N]> { + let iter = self.iter.as_mut()?; + match self.buffer { + // It is the first time to advance. We collect + // the first `N` items from `self.iter` to initialize `self.buffer`. + None => self.buffer = Buffer::try_from_iter(iter), + Some(ref mut buffer) => match iter.next() { + None => { + // Fuse the inner iterator since it yields a `None`. + self.iter.take(); + self.buffer.take(); + } + // Advance the iterator. We first call `next` before changing our buffer + // at all. This means that if `next` panics, our invariant is upheld and + // our `Drop` impl drops the correct elements. + Some(item) => buffer.push(item), + }, + } + self.buffer.as_ref().map(Buffer::as_array_ref) + } + + fn size_hint(&self) -> (usize, Option) { + let Some(ref iter) = self.iter else { return (0, Some(0)) }; + let (lo, hi) = iter.size_hint(); + if self.buffer.is_some() { + // If the first `N` items are already yielded by the inner iterator, + // the size hint is then equal to the that of the inner iterator's. + (lo, hi) + } else { + // If the first `N` items are not yet yielded by the inner iterator, + // the first `N` elements should be counted as one window, so both bounds + // should subtract `N - 1`. + (lo.saturating_sub(N - 1), hi.map(|hi| hi.saturating_sub(N - 1))) + } + } +} + +impl Buffer { + fn try_from_iter(iter: &mut impl Iterator) -> Option { + let first_half = crate::array::iter_next_chunk(iter).ok()?; + let buffer = [MaybeUninit::new(first_half).transpose(), MaybeUninit::uninit_array()]; + Some(Self { buffer, start: 0 }) + } + + #[inline] + fn buffer_ptr(&self) -> *const MaybeUninit { + self.buffer.as_ptr().cast() + } + + #[inline] + fn buffer_mut_ptr(&mut self) -> *mut MaybeUninit { + self.buffer.as_mut_ptr().cast() + } + + #[inline] + fn as_array_ref(&self) -> &[T; N] { + debug_assert!(self.start + N <= 2 * N); + + // SAFETY: our invariant guarantees these elements are initialized. + unsafe { &*self.buffer_ptr().add(self.start).cast() } + } + + #[inline] + fn as_uninit_array_mut(&mut self) -> &mut MaybeUninit<[T; N]> { + debug_assert!(self.start + N <= 2 * N); + + // SAFETY: our invariant guarantees these elements are in bounds. + unsafe { &mut *self.buffer_mut_ptr().add(self.start).cast() } + } + + /// Pushes a new item `next` to the back, and pops the front-most one. + /// + /// All the elements will be shifted to the front end when pushing reaches + /// the back end. + fn push(&mut self, next: T) { + let buffer_mut_ptr = self.buffer_mut_ptr(); + debug_assert!(self.start + N <= 2 * N); + + let to_drop = if self.start == N { + // We have reached the end of our buffer and have to copy + // everything to the start. Example layout for N = 3. + // + // 0 1 2 3 4 5 0 1 2 3 4 5 + // ┌───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐ + // │ - │ - │ - │ a │ b │ c │ -> │ b │ c │ n │ - │ - │ - │ + // └───┴───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘ + // ↑ ↑ + // start start + + // SAFETY: the two pointers are valid for reads/writes of N -1 + // elements because our array's size is semantically 2 * N. The + // regions also don't overlap for the same reason. + // + // We leave the old elements in place. As soon as `start` is set + // to 0, we treat them as uninitialized and treat their copies + // as initialized. + let to_drop = unsafe { + ptr::copy_nonoverlapping(buffer_mut_ptr.add(self.start + 1), buffer_mut_ptr, N - 1); + (*buffer_mut_ptr.add(N - 1)).write(next); + buffer_mut_ptr.add(self.start) + }; + self.start = 0; + to_drop + } else { + // SAFETY: `self.start` is < N as guaranteed by the invariant + // plus the check above. Even if the drop at the end panics, + // the invariant is upheld. + // + // Example layout for N = 3: + // + // 0 1 2 3 4 5 0 1 2 3 4 5 + // ┌───┬───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┬───┐ + // │ - │ a │ b │ c │ - │ - │ -> │ - │ - │ b │ c │ n │ - │ + // └───┴───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┴───┘ + // ↑ ↑ + // start start + // + let to_drop = unsafe { + (*buffer_mut_ptr.add(self.start + N)).write(next); + buffer_mut_ptr.add(self.start) + }; + self.start += 1; + to_drop + }; + + // SAFETY: the index is valid and this is element `a` in the + // diagram above and has not been dropped yet. + unsafe { ptr::drop_in_place(to_drop.cast::()) }; + } +} + +impl Clone for Buffer { + fn clone(&self) -> Self { + let mut buffer = Buffer { + buffer: [MaybeUninit::uninit_array(), MaybeUninit::uninit_array()], + start: self.start, + }; + buffer.as_uninit_array_mut().write(self.as_array_ref().clone()); + buffer + } +} + +impl Clone for MapWindowsInner +where + I: Iterator + Clone, + I::Item: Clone, +{ + fn clone(&self) -> Self { + Self { iter: self.iter.clone(), buffer: self.buffer.clone() } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + // SAFETY: our invariant guarantees that N elements starting from + // `self.start` are initialized. We drop them here. + unsafe { + let initialized_part: *mut [T] = crate::ptr::slice_from_raw_parts_mut( + self.buffer_mut_ptr().add(self.start).cast(), + N, + ); + ptr::drop_in_place(initialized_part); + } + } +} + +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +impl Iterator for MapWindows +where + I: Iterator, + F: FnMut(&[I::Item; N]) -> R, +{ + type Item = R; + + fn next(&mut self) -> Option { + let window = self.inner.next_window()?; + let out = (self.f)(window); + Some(out) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +// Note that even if the inner iterator not fused, the `MapWindows` is still fused, +// because we don't allow "holes" in the mapping window. +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +impl FusedIterator for MapWindows +where + I: Iterator, + F: FnMut(&[I::Item; N]) -> R, +{ +} + +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +impl ExactSizeIterator for MapWindows +where + I: ExactSizeIterator, + F: FnMut(&[I::Item; N]) -> R, +{ +} + +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +impl fmt::Debug for MapWindows { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MapWindows").field("iter", &self.inner.iter).finish() + } +} + +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +impl Clone for MapWindows +where + I: Iterator + Clone, + F: Clone, + I::Item: Clone, +{ + fn clone(&self) -> Self { + Self { f: self.f.clone(), inner: self.inner.clone() } + } +} diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 8cc2b7cec4165..6f4fa7010f446 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -16,6 +16,7 @@ mod inspect; mod intersperse; mod map; mod map_while; +mod map_windows; mod peekable; mod rev; mod scan; @@ -57,6 +58,9 @@ pub use self::intersperse::{Intersperse, IntersperseWith}; #[stable(feature = "iter_map_while", since = "1.57.0")] pub use self::map_while::MapWhile; +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +pub use self::map_windows::MapWindows; + #[unstable(feature = "trusted_random_access", issue = "none")] pub use self::zip::TrustedRandomAccess; diff --git a/library/core/src/iter/adapters/step_by.rs b/library/core/src/iter/adapters/step_by.rs index 4252c34a0e0fc..7f58f7d17758d 100644 --- a/library/core/src/iter/adapters/step_by.rs +++ b/library/core/src/iter/adapters/step_by.rs @@ -1,4 +1,9 @@ -use crate::{intrinsics, iter::from_fn, ops::Try}; +use crate::convert::TryFrom; +use crate::{ + intrinsics, + iter::{from_fn, TrustedLen}, + ops::{Range, Try}, +}; /// An iterator for stepping iterators by a custom amount. /// @@ -11,14 +16,22 @@ use crate::{intrinsics, iter::from_fn, ops::Try}; #[stable(feature = "iterator_step_by", since = "1.28.0")] #[derive(Clone, Debug)] pub struct StepBy { + /// This field is guaranteed to be preprocessed by the specialized `SpecRangeSetup::setup` + /// in the constructor. + /// For most iterators that processing is a no-op, but for Range<{integer}> types it is lossy + /// which means the inner iterator cannot be returned to user code. + /// Additionally this type-dependent preprocessing means specialized implementations + /// cannot be used interchangeably. iter: I, step: usize, first_take: bool, } impl StepBy { + #[inline] pub(in crate::iter) fn new(iter: I, step: usize) -> StepBy { assert!(step != 0); + let iter = >::setup(iter, step); StepBy { iter, step: step - 1, first_take: true } } } @@ -32,16 +45,174 @@ where #[inline] fn next(&mut self) -> Option { + self.spec_next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.spec_size_hint() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + self.spec_nth(n) + } + + fn try_fold(&mut self, acc: Acc, f: F) -> R + where + F: FnMut(Acc, Self::Item) -> R, + R: Try, + { + self.spec_try_fold(acc, f) + } + + #[inline] + fn fold(self, acc: Acc, f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc, + { + self.spec_fold(acc, f) + } +} + +impl StepBy +where + I: ExactSizeIterator, +{ + // The zero-based index starting from the end of the iterator of the + // last element. Used in the `DoubleEndedIterator` implementation. + fn next_back_index(&self) -> usize { + let rem = self.iter.len() % (self.step + 1); if self.first_take { - self.first_take = false; - self.iter.next() + if rem == 0 { self.step } else { rem - 1 } } else { - self.iter.nth(self.step) + rem } } +} +#[stable(feature = "double_ended_step_by_iterator", since = "1.38.0")] +impl DoubleEndedIterator for StepBy +where + I: DoubleEndedIterator + ExactSizeIterator, +{ #[inline] - fn size_hint(&self) -> (usize, Option) { + fn next_back(&mut self) -> Option { + self.spec_next_back() + } + + #[inline] + fn nth_back(&mut self, n: usize) -> Option { + self.spec_nth_back(n) + } + + fn try_rfold(&mut self, init: Acc, f: F) -> R + where + F: FnMut(Acc, Self::Item) -> R, + R: Try, + { + self.spec_try_rfold(init, f) + } + + #[inline] + fn rfold(self, init: Acc, f: F) -> Acc + where + Self: Sized, + F: FnMut(Acc, Self::Item) -> Acc, + { + self.spec_rfold(init, f) + } +} + +// StepBy can only make the iterator shorter, so the len will still fit. +#[stable(feature = "iterator_step_by", since = "1.28.0")] +impl ExactSizeIterator for StepBy where I: ExactSizeIterator {} + +trait SpecRangeSetup { + fn setup(inner: T, step: usize) -> T; +} + +impl SpecRangeSetup for T { + #[inline] + default fn setup(inner: T, _step: usize) -> T { + inner + } +} + +/// Specialization trait to optimize `StepBy>` iteration. +/// +/// # Safety +/// +/// Technically this is safe to implement (look ma, no unsafe!), but in reality +/// a lot of unsafe code relies on ranges over integers being correct. +/// +/// For correctness *all* public StepBy methods must be specialized +/// because `setup` drastically alters the meaning of the struct fields so that mixing +/// different implementations would lead to incorrect results. +unsafe trait StepByImpl { + type Item; + + fn spec_next(&mut self) -> Option; + + fn spec_size_hint(&self) -> (usize, Option); + + fn spec_nth(&mut self, n: usize) -> Option; + + fn spec_try_fold(&mut self, acc: Acc, f: F) -> R + where + F: FnMut(Acc, Self::Item) -> R, + R: Try; + + fn spec_fold(self, acc: Acc, f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc; +} + +/// Specialization trait for double-ended iteration. +/// +/// See also: `StepByImpl` +/// +/// # Safety +/// +/// The specializations must be implemented together with `StepByImpl` +/// where applicable. I.e. if `StepBy` does support backwards iteration +/// for a given iterator and that is specialized for forward iteration then +/// it must also be specialized for backwards iteration. +unsafe trait StepByBackImpl { + type Item; + + fn spec_next_back(&mut self) -> Option + where + I: DoubleEndedIterator + ExactSizeIterator; + + fn spec_nth_back(&mut self, n: usize) -> Option + where + I: DoubleEndedIterator + ExactSizeIterator; + + fn spec_try_rfold(&mut self, init: Acc, f: F) -> R + where + I: DoubleEndedIterator + ExactSizeIterator, + F: FnMut(Acc, Self::Item) -> R, + R: Try; + + fn spec_rfold(self, init: Acc, f: F) -> Acc + where + I: DoubleEndedIterator + ExactSizeIterator, + F: FnMut(Acc, Self::Item) -> Acc; +} + +unsafe impl StepByImpl for StepBy { + type Item = I::Item; + + #[inline] + default fn spec_next(&mut self) -> Option { + let step_size = if self.first_take { 0 } else { self.step }; + self.first_take = false; + self.iter.nth(step_size) + } + + #[inline] + default fn spec_size_hint(&self) -> (usize, Option) { #[inline] fn first_size(step: usize) -> impl Fn(usize) -> usize { move |n| if n == 0 { 0 } else { 1 + (n - 1) / (step + 1) } @@ -64,7 +235,7 @@ where } #[inline] - fn nth(&mut self, mut n: usize) -> Option { + default fn spec_nth(&mut self, mut n: usize) -> Option { if self.first_take { self.first_take = false; let first = self.iter.next(); @@ -108,7 +279,7 @@ where } } - fn try_fold(&mut self, mut acc: Acc, mut f: F) -> R + default fn spec_try_fold(&mut self, mut acc: Acc, mut f: F) -> R where F: FnMut(Acc, Self::Item) -> R, R: Try, @@ -128,7 +299,7 @@ where from_fn(nth(&mut self.iter, self.step)).try_fold(acc, f) } - fn fold(mut self, mut acc: Acc, mut f: F) -> Acc + default fn spec_fold(mut self, mut acc: Acc, mut f: F) -> Acc where F: FnMut(Acc, Self::Item) -> Acc, { @@ -148,34 +319,16 @@ where } } -impl StepBy -where - I: ExactSizeIterator, -{ - // The zero-based index starting from the end of the iterator of the - // last element. Used in the `DoubleEndedIterator` implementation. - fn next_back_index(&self) -> usize { - let rem = self.iter.len() % (self.step + 1); - if self.first_take { - if rem == 0 { self.step } else { rem - 1 } - } else { - rem - } - } -} +unsafe impl StepByBackImpl for StepBy { + type Item = I::Item; -#[stable(feature = "double_ended_step_by_iterator", since = "1.38.0")] -impl DoubleEndedIterator for StepBy -where - I: DoubleEndedIterator + ExactSizeIterator, -{ #[inline] - fn next_back(&mut self) -> Option { + default fn spec_next_back(&mut self) -> Option { self.iter.nth_back(self.next_back_index()) } #[inline] - fn nth_back(&mut self, n: usize) -> Option { + default fn spec_nth_back(&mut self, n: usize) -> Option { // `self.iter.nth_back(usize::MAX)` does the right thing here when `n` // is out of bounds because the length of `self.iter` does not exceed // `usize::MAX` (because `I: ExactSizeIterator`) and `nth_back` is @@ -184,7 +337,7 @@ where self.iter.nth_back(n) } - fn try_rfold(&mut self, init: Acc, mut f: F) -> R + default fn spec_try_rfold(&mut self, init: Acc, mut f: F) -> R where F: FnMut(Acc, Self::Item) -> R, R: Try, @@ -207,10 +360,10 @@ where } #[inline] - fn rfold(mut self, init: Acc, mut f: F) -> Acc + default fn spec_rfold(mut self, init: Acc, mut f: F) -> Acc where Self: Sized, - F: FnMut(Acc, Self::Item) -> Acc, + F: FnMut(Acc, I::Item) -> Acc, { #[inline] fn nth_back( @@ -230,6 +383,192 @@ where } } -// StepBy can only make the iterator shorter, so the len will still fit. -#[stable(feature = "iterator_step_by", since = "1.28.0")] -impl ExactSizeIterator for StepBy where I: ExactSizeIterator {} +/// For these implementations, `SpecRangeSetup` calculates the number +/// of iterations that will be needed and stores that in `iter.end`. +/// +/// The various iterator implementations then rely on that to not need +/// overflow checking, letting loops just be counted instead. +/// +/// These only work for unsigned types, and will need to be reworked +/// if you want to use it to specialize on signed types. +/// +/// Currently these are only implemented for integers up to usize due to +/// correctness issues around ExactSizeIterator impls on 16bit platforms. +/// And since ExactSizeIterator is a prerequisite for backwards iteration +/// and we must consistently specialize backwards and forwards iteration +/// that makes the situation complicated enough that it's not covered +/// for now. +macro_rules! spec_int_ranges { + ($($t:ty)*) => ($( + + const _: () = assert!(usize::BITS >= <$t>::BITS); + + impl SpecRangeSetup> for Range<$t> { + #[inline] + fn setup(mut r: Range<$t>, step: usize) -> Range<$t> { + let inner_len = r.size_hint().0; + // If step exceeds $t::MAX, then the count will be at most 1 and + // thus always fit into $t. + let yield_count = inner_len.div_ceil(step); + // Turn the range end into an iteration counter + r.end = yield_count as $t; + r + } + } + + unsafe impl StepByImpl> for StepBy> { + #[inline] + fn spec_next(&mut self) -> Option<$t> { + // if a step size larger than the type has been specified fall back to + // t::MAX, in which case remaining will be at most 1. + // The `+ 1` can't overflow since the constructor substracted 1 from the original value. + let step = <$t>::try_from(self.step + 1).unwrap_or(<$t>::MAX); + let remaining = self.iter.end; + if remaining > 0 { + let val = self.iter.start; + // this can only overflow during the last step, after which the value + // will not be used + self.iter.start = val.wrapping_add(step); + self.iter.end = remaining - 1; + Some(val) + } else { + None + } + } + + #[inline] + fn spec_size_hint(&self) -> (usize, Option) { + let remaining = self.iter.end as usize; + (remaining, Some(remaining)) + } + + // The methods below are all copied from the Iterator trait default impls. + // We have to repeat them here so that the specialization overrides the StepByImpl defaults + + #[inline] + fn spec_nth(&mut self, n: usize) -> Option { + self.advance_by(n).ok()?; + self.next() + } + + #[inline] + fn spec_try_fold(&mut self, init: Acc, mut f: F) -> R + where + F: FnMut(Acc, Self::Item) -> R, + R: Try + { + let mut accum = init; + while let Some(x) = self.next() { + accum = f(accum, x)?; + } + try { accum } + } + + #[inline] + fn spec_fold(self, init: Acc, mut f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc + { + // if a step size larger than the type has been specified fall back to + // t::MAX, in which case remaining will be at most 1. + let step = <$t>::try_from(self.step + 1).unwrap_or(<$t>::MAX); + let remaining = self.iter.end; + let mut acc = init; + let mut val = self.iter.start; + for _ in 0..remaining { + acc = f(acc, val); + // this can only overflow during the last step, after which the value + // will no longer be used + val = val.wrapping_add(step); + } + acc + } + } + + /// Safety: This macro is only applied to ranges over types <= usize + /// which means the inner length is guaranteed to fit into a usize and so + /// the outer length calculation won't encounter clamped values + #[unstable(feature = "trusted_len", issue = "37572")] + unsafe impl TrustedLen for StepBy> {} + )*) +} + +macro_rules! spec_int_ranges_r { + ($($t:ty)*) => ($( + const _: () = assert!(usize::BITS >= <$t>::BITS); + + unsafe impl StepByBackImpl> for StepBy> { + + #[inline] + fn spec_next_back(&mut self) -> Option + where Range<$t>: DoubleEndedIterator + ExactSizeIterator, + { + let step = (self.step + 1) as $t; + let remaining = self.iter.end; + if remaining > 0 { + let start = self.iter.start; + self.iter.end = remaining - 1; + Some(start + step * (remaining - 1)) + } else { + None + } + } + + // The methods below are all copied from the Iterator trait default impls. + // We have to repeat them here so that the specialization overrides the StepByImplBack defaults + + #[inline] + fn spec_nth_back(&mut self, n: usize) -> Option + where Self: DoubleEndedIterator, + { + if self.advance_back_by(n).is_err() { + return None; + } + self.next_back() + } + + #[inline] + fn spec_try_rfold(&mut self, init: Acc, mut f: F) -> R + where + Self: DoubleEndedIterator, + F: FnMut(Acc, Self::Item) -> R, + R: Try + { + let mut accum = init; + while let Some(x) = self.next_back() { + accum = f(accum, x)?; + } + try { accum } + } + + #[inline] + fn spec_rfold(mut self, init: Acc, mut f: F) -> Acc + where + Self: DoubleEndedIterator, + F: FnMut(Acc, Self::Item) -> Acc + { + let mut accum = init; + while let Some(x) = self.next_back() { + accum = f(accum, x); + } + accum + } + } + )*) +} + +#[cfg(target_pointer_width = "64")] +spec_int_ranges!(u8 u16 u32 u64 usize); +// DoubleEndedIterator requires ExactSizeIterator, which isn't implemented for Range +#[cfg(target_pointer_width = "64")] +spec_int_ranges_r!(u8 u16 u32 usize); + +#[cfg(target_pointer_width = "32")] +spec_int_ranges!(u8 u16 u32 usize); +#[cfg(target_pointer_width = "32")] +spec_int_ranges_r!(u8 u16 u32 usize); + +#[cfg(target_pointer_width = "16")] +spec_int_ranges!(u8 u16 usize); +#[cfg(target_pointer_width = "16")] +spec_int_ranges_r!(u8 u16 usize); diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs index de638552fa378..ca977d1ef8240 100644 --- a/library/core/src/iter/mod.rs +++ b/library/core/src/iter/mod.rs @@ -361,6 +361,12 @@ macro_rules! impl_fold_via_try_fold { (rfold -> try_rfold) => { impl_fold_via_try_fold! { @internal rfold -> try_rfold } }; + (spec_fold -> spec_try_fold) => { + impl_fold_via_try_fold! { @internal spec_fold -> spec_try_fold } + }; + (spec_rfold -> spec_try_rfold) => { + impl_fold_via_try_fold! { @internal spec_rfold -> spec_try_rfold } + }; (@internal $fold:ident -> $try_fold:ident) => { #[inline] fn $fold(mut self, init: AAA, fold: FFF) -> AAA @@ -434,6 +440,8 @@ pub use self::adapters::Copied; pub use self::adapters::Flatten; #[stable(feature = "iter_map_while", since = "1.57.0")] pub use self::adapters::MapWhile; +#[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] +pub use self::adapters::MapWindows; #[unstable(feature = "inplace_iteration", issue = "none")] pub use self::adapters::SourceIter; #[stable(feature = "iterator_step_by", since = "1.28.0")] diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index 0171d89812feb..462f7170a55b3 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -619,9 +619,10 @@ impl RangeIteratorImpl for ops::Range { #[inline] fn spec_next(&mut self) -> Option { if self.start < self.end { + let old = self.start; // SAFETY: just checked precondition - let n = unsafe { Step::forward_unchecked(self.start.clone(), 1) }; - Some(mem::replace(&mut self.start, n)) + self.start = unsafe { Step::forward_unchecked(old, 1) }; + Some(old) } else { None } @@ -629,15 +630,15 @@ impl RangeIteratorImpl for ops::Range { #[inline] fn spec_nth(&mut self, n: usize) -> Option { - if let Some(plus_n) = Step::forward_checked(self.start.clone(), n) { + if let Some(plus_n) = Step::forward_checked(self.start, n) { if plus_n < self.end { // SAFETY: just checked precondition - self.start = unsafe { Step::forward_unchecked(plus_n.clone(), 1) }; + self.start = unsafe { Step::forward_unchecked(plus_n, 1) }; return Some(plus_n); } } - self.start = self.end.clone(); + self.start = self.end; None } @@ -655,7 +656,7 @@ impl RangeIteratorImpl for ops::Range { // then steps_between either returns a bound to which we clamp or returns None which // together with the initial inequality implies more than usize::MAX steps. // Otherwise 0 is returned which always safe to use. - self.start = unsafe { Step::forward_unchecked(self.start.clone(), taken) }; + self.start = unsafe { Step::forward_unchecked(self.start, taken) }; NonZeroUsize::new(n - taken).map_or(Ok(()), Err) } @@ -664,8 +665,8 @@ impl RangeIteratorImpl for ops::Range { fn spec_next_back(&mut self) -> Option { if self.start < self.end { // SAFETY: just checked precondition - self.end = unsafe { Step::backward_unchecked(self.end.clone(), 1) }; - Some(self.end.clone()) + self.end = unsafe { Step::backward_unchecked(self.end, 1) }; + Some(self.end) } else { None } @@ -673,15 +674,15 @@ impl RangeIteratorImpl for ops::Range { #[inline] fn spec_nth_back(&mut self, n: usize) -> Option { - if let Some(minus_n) = Step::backward_checked(self.end.clone(), n) { + if let Some(minus_n) = Step::backward_checked(self.end, n) { if minus_n > self.start { // SAFETY: just checked precondition self.end = unsafe { Step::backward_unchecked(minus_n, 1) }; - return Some(self.end.clone()); + return Some(self.end); } } - self.end = self.start.clone(); + self.end = self.start; None } @@ -696,7 +697,7 @@ impl RangeIteratorImpl for ops::Range { let taken = available.min(n); // SAFETY: same as the spec_advance_by() implementation - self.end = unsafe { Step::backward_unchecked(self.end.clone(), taken) }; + self.end = unsafe { Step::backward_unchecked(self.end, taken) }; NonZeroUsize::new(n - taken).map_or(Ok(()), Err) } diff --git a/library/core/src/iter/sources/successors.rs b/library/core/src/iter/sources/successors.rs index 99f058a901a31..6a6cbe905e464 100644 --- a/library/core/src/iter/sources/successors.rs +++ b/library/core/src/iter/sources/successors.rs @@ -22,7 +22,7 @@ where Successors { next: first, succ } } -/// An new iterator where each successive item is computed based on the preceding one. +/// A new iterator where each successive item is computed based on the preceding one. /// /// This `struct` is created by the [`iter::successors()`] function. /// See its documentation for more. diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs index 0675e56358f9b..e0ef5071c408a 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -138,8 +138,6 @@ pub trait FromIterator: Sized { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let five_fives = std::iter::repeat(5).take(5); /// @@ -255,8 +253,6 @@ pub trait IntoIterator { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// let v = [1, 2, 3]; /// let mut iter = v.into_iter(); @@ -363,8 +359,6 @@ pub trait Extend { /// /// # Examples /// - /// Basic usage: - /// /// ``` /// // You can extend a String with some chars: /// let mut message = String::from("abc"); diff --git a/library/core/src/iter/traits/double_ended.rs b/library/core/src/iter/traits/double_ended.rs index 182d9f758adc4..4c8af4eba7889 100644 --- a/library/core/src/iter/traits/double_ended.rs +++ b/library/core/src/iter/traits/double_ended.rs @@ -379,4 +379,66 @@ impl<'a, I: DoubleEndedIterator + ?Sized> DoubleEndedIterator for &'a mut I { fn nth_back(&mut self, n: usize) -> Option { (**self).nth_back(n) } + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.spec_rfold(init, f) + } + fn try_rfold(&mut self, init: B, f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try, + { + self.spec_try_rfold(init, f) + } +} + +/// Helper trait to specialize `rfold` and `rtry_fold` for `&mut I where I: Sized` +trait DoubleEndedIteratorRefSpec: DoubleEndedIterator { + fn spec_rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B; + + fn spec_try_rfold(&mut self, init: B, f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try; +} + +impl DoubleEndedIteratorRefSpec for &mut I { + default fn spec_rfold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + while let Some(x) = self.next_back() { + accum = f(accum, x); + } + accum + } + + default fn spec_try_rfold(&mut self, init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try, + { + let mut accum = init; + while let Some(x) = self.next_back() { + accum = f(accum, x)?; + } + try { accum } + } +} + +impl DoubleEndedIteratorRefSpec for &mut I { + impl_fold_via_try_fold! { spec_rfold -> spec_try_rfold } + + fn spec_try_rfold(&mut self, init: B, f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try, + { + (**self).try_rfold(init, f) + } } diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index dabfce1447483..ac1fc26a1efac 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -10,7 +10,8 @@ use super::super::{ArrayChunks, Chain, Cloned, Copied, Cycle, Enumerate, Filter, use super::super::{FlatMap, Flatten}; use super::super::{FromIterator, Intersperse, IntersperseWith, Product, Sum, Zip}; use super::super::{ - Inspect, Map, MapWhile, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, TakeWhile, + Inspect, Map, MapWhile, MapWindows, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, + TakeWhile, }; fn _assert_is_object_safe(_: &dyn Iterator) {} @@ -26,13 +27,13 @@ fn _assert_is_object_safe(_: &dyn Iterator) {} #[stable(feature = "rust1", since = "1.0.0")] #[rustc_on_unimplemented( on( - _Self = "std::ops::RangeTo", + any(_Self = "core::ops::RangeTo", _Self = "std::ops::RangeTo"), label = "if you meant to iterate until a value, add a starting value", note = "`..end` is a `RangeTo`, which cannot be iterated on; you might have meant to have a \ bounded `Range`: `0..end`" ), on( - _Self = "std::ops::RangeToInclusive", + any(_Self = "core::ops::RangeToInclusive", _Self = "std::ops::RangeToInclusive"), label = "if you meant to iterate until a value (including it), add a starting value", note = "`..=end` is a `RangeToInclusive`, which cannot be iterated on; you might have meant \ to have a bounded `RangeInclusive`: `0..=end`" @@ -43,7 +44,7 @@ fn _assert_is_object_safe(_: &dyn Iterator) {} ), on(_Self = "&[]", label = "`{Self}` is not an iterator; try calling `.iter()`"), on( - _Self = "std::vec::Vec", + any(_Self = "alloc::vec::Vec", _Self = "std::vec::Vec"), label = "`{Self}` is not an iterator; try calling `.into_iter()` or `.iter()`" ), on( @@ -51,7 +52,7 @@ fn _assert_is_object_safe(_: &dyn Iterator) {} label = "`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`" ), on( - _Self = "std::string::String", + any(_Self = "alloc::string::String", _Self = "std::string::String"), label = "`{Self}` is not an iterator; try calling `.chars()` or `.bytes()`" ), on( @@ -1591,6 +1592,163 @@ pub trait Iterator { Flatten::new(self) } + /// Calls the given function `f` for each contiguous window of size `N` over + /// `self` and returns an iterator over the outputs of `f`. Like [`slice::windows()`], + /// the windows during mapping overlap as well. + /// + /// In the following example, the closure is called three times with the + /// arguments `&['a', 'b']`, `&['b', 'c']` and `&['c', 'd']` respectively. + /// + /// ``` + /// #![feature(iter_map_windows)] + /// + /// let strings = "abcd".chars() + /// .map_windows(|[x, y]| format!("{}+{}", x, y)) + /// .collect::>(); + /// + /// assert_eq!(strings, vec!["a+b", "b+c", "c+d"]); + /// ``` + /// + /// Note that the const parameter `N` is usually inferred by the + /// destructured argument in the closure. + /// + /// The returned iterator yields 𝑘 − `N` + 1 items (where 𝑘 is the number of + /// items yielded by `self`). If 𝑘 is less than `N`, this method yields an + /// empty iterator. + /// + /// The returned iterator implements [`FusedIterator`], because once `self` + /// returns `None`, even if it returns a `Some(T)` again in the next iterations, + /// we cannot put it into a contigious array buffer, and thus the returned iterator + /// should be fused. + /// + /// [`slice::windows()`]: slice::windows + /// [`FusedIterator`]: crate::iter::FusedIterator + /// + /// # Panics + /// + /// Panics if `N` is 0. This check will most probably get changed to a + /// compile time error before this method gets stabilized. + /// + /// ```should_panic + /// #![feature(iter_map_windows)] + /// + /// let iter = std::iter::repeat(0).map_windows(|&[]| ()); + /// ``` + /// + /// # Examples + /// + /// Building the sums of neighboring numbers. + /// + /// ``` + /// #![feature(iter_map_windows)] + /// + /// let mut it = [1, 3, 8, 1].iter().map_windows(|&[a, b]| a + b); + /// assert_eq!(it.next(), Some(4)); // 1 + 3 + /// assert_eq!(it.next(), Some(11)); // 3 + 8 + /// assert_eq!(it.next(), Some(9)); // 8 + 1 + /// assert_eq!(it.next(), None); + /// ``` + /// + /// Since the elements in the following example implement `Copy`, we can + /// just copy the array and get an iterator over the windows. + /// + /// ``` + /// #![feature(iter_map_windows)] + /// + /// let mut it = "ferris".chars().map_windows(|w: &[_; 3]| *w); + /// assert_eq!(it.next(), Some(['f', 'e', 'r'])); + /// assert_eq!(it.next(), Some(['e', 'r', 'r'])); + /// assert_eq!(it.next(), Some(['r', 'r', 'i'])); + /// assert_eq!(it.next(), Some(['r', 'i', 's'])); + /// assert_eq!(it.next(), None); + /// ``` + /// + /// You can also use this function to check the sortedness of an iterator. + /// For the simple case, rather use [`Iterator::is_sorted`]. + /// + /// ``` + /// #![feature(iter_map_windows)] + /// + /// let mut it = [0.5, 1.0, 3.5, 3.0, 8.5, 8.5, f32::NAN].iter() + /// .map_windows(|[a, b]| a <= b); + /// + /// assert_eq!(it.next(), Some(true)); // 0.5 <= 1.0 + /// assert_eq!(it.next(), Some(true)); // 1.0 <= 3.5 + /// assert_eq!(it.next(), Some(false)); // 3.5 <= 3.0 + /// assert_eq!(it.next(), Some(true)); // 3.0 <= 8.5 + /// assert_eq!(it.next(), Some(true)); // 8.5 <= 8.5 + /// assert_eq!(it.next(), Some(false)); // 8.5 <= NAN + /// assert_eq!(it.next(), None); + /// ``` + /// + /// For non-fused iterators, they are fused after `map_windows`. + /// + /// ``` + /// #![feature(iter_map_windows)] + /// + /// #[derive(Default)] + /// struct NonFusedIterator { + /// state: i32, + /// } + /// + /// impl Iterator for NonFusedIterator { + /// type Item = i32; + /// + /// fn next(&mut self) -> Option { + /// let val = self.state; + /// self.state = self.state + 1; + /// + /// // yields `0..5` first, then only even numbers since `6..`. + /// if val < 5 || val % 2 == 0 { + /// Some(val) + /// } else { + /// None + /// } + /// } + /// } + /// + /// + /// let mut iter = NonFusedIterator::default(); + /// + /// // yields 0..5 first. + /// assert_eq!(iter.next(), Some(0)); + /// assert_eq!(iter.next(), Some(1)); + /// assert_eq!(iter.next(), Some(2)); + /// assert_eq!(iter.next(), Some(3)); + /// assert_eq!(iter.next(), Some(4)); + /// // then we can see our iterator going back and forth + /// assert_eq!(iter.next(), None); + /// assert_eq!(iter.next(), Some(6)); + /// assert_eq!(iter.next(), None); + /// assert_eq!(iter.next(), Some(8)); + /// assert_eq!(iter.next(), None); + /// + /// // however, with `.map_windows()`, it is fused. + /// let mut iter = NonFusedIterator::default() + /// .map_windows(|arr: &[_; 2]| *arr); + /// + /// assert_eq!(iter.next(), Some([0, 1])); + /// assert_eq!(iter.next(), Some([1, 2])); + /// assert_eq!(iter.next(), Some([2, 3])); + /// assert_eq!(iter.next(), Some([3, 4])); + /// assert_eq!(iter.next(), None); + /// + /// // it will always return `None` after the first time. + /// assert_eq!(iter.next(), None); + /// assert_eq!(iter.next(), None); + /// assert_eq!(iter.next(), None); + /// ``` + #[inline] + #[unstable(feature = "iter_map_windows", reason = "recently added", issue = "87155")] + #[rustc_do_not_const_check] + fn map_windows(self, f: F) -> MapWindows + where + Self: Sized, + F: FnMut(&[Self::Item; N]) -> R, + { + MapWindows::new(self, f) + } + /// Creates an iterator which ends after the first [`None`]. /// /// After an iterator returns [`None`], future calls may or may not yield @@ -4018,4 +4176,66 @@ impl Iterator for &mut I { fn nth(&mut self, n: usize) -> Option { (**self).nth(n) } + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.spec_fold(init, f) + } + fn try_fold(&mut self, init: B, f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try, + { + self.spec_try_fold(init, f) + } +} + +/// Helper trait to specialize `fold` and `try_fold` for `&mut I where I: Sized` +trait IteratorRefSpec: Iterator { + fn spec_fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B; + + fn spec_try_fold(&mut self, init: B, f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try; +} + +impl IteratorRefSpec for &mut I { + default fn spec_fold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let mut accum = init; + while let Some(x) = self.next() { + accum = f(accum, x); + } + accum + } + + default fn spec_try_fold(&mut self, init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try, + { + let mut accum = init; + while let Some(x) = self.next() { + accum = f(accum, x)?; + } + try { accum } + } +} + +impl IteratorRefSpec for &mut I { + impl_fold_via_try_fold! { spec_fold -> spec_try_fold } + + fn spec_try_fold(&mut self, init: B, f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: Try, + { + (**self).try_fold(init, f) + } } diff --git a/library/core/src/iter/traits/marker.rs b/library/core/src/iter/traits/marker.rs index af02848233d99..c21a2aac1c9a6 100644 --- a/library/core/src/iter/traits/marker.rs +++ b/library/core/src/iter/traits/marker.rs @@ -86,4 +86,4 @@ pub unsafe trait InPlaceIterable: Iterator {} /// for details. Consumers are free to rely on the invariants in unsafe code. #[unstable(feature = "trusted_step", issue = "85731")] #[rustc_specialization_trait] -pub unsafe trait TrustedStep: Step {} +pub unsafe trait TrustedStep: Step + Copy {} diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 6c419eb16f3b9..a2729b3743cc2 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -51,7 +51,7 @@ #![cfg(not(test))] // To run core tests without x.py without ending up with two copies of core, Miri needs to be // able to "empty" this crate. See . -// rustc itself never sets the feature, so this line has no affect there. +// rustc itself never sets the feature, so this line has no effect there. #![cfg(any(not(feature = "miri-test-libstd"), test, doctest))] #![stable(feature = "core", since = "1.6.0")] #![doc( @@ -96,6 +96,9 @@ #![allow(explicit_outlives_requirements)] #![allow(incomplete_features)] #![warn(multiple_supertrait_upcastable)] +#![cfg_attr(not(bootstrap), allow(internal_features))] +// Do not check link redundancy on bootstraping phase +#![cfg_attr(not(bootstrap), allow(rustdoc::redundant_explicit_links))] // // Library features: // tidy-alphabetical-start @@ -113,7 +116,6 @@ #![feature(const_caller_location)] #![feature(const_cell_into_inner)] #![feature(const_char_from_u32_unchecked)] -#![feature(const_cstr_methods)] #![feature(const_discriminant)] #![feature(const_eval_select)] #![feature(const_exact_div)] @@ -166,6 +168,7 @@ #![feature(duration_consts_float)] #![feature(internal_impls_macro)] #![feature(ip)] +#![feature(ip_bits)] #![feature(is_ascii_octdigit)] #![feature(maybe_uninit_uninit_array)] #![feature(ptr_alignment_type)] @@ -399,7 +402,8 @@ pub mod primitive; missing_debug_implementations, dead_code, unused_imports, - unsafe_op_in_unsafe_fn + unsafe_op_in_unsafe_fn, + ambiguous_glob_reexports )] #[allow(rustdoc::bare_urls)] // FIXME: This annotation should be moved into rust-lang/stdarch after clashing_extern_declarations is @@ -418,7 +422,7 @@ pub mod arch; // set up in such a way that directly pulling it here works such that the // crate uses this crate as its core. #[path = "../../portable-simd/crates/core_simd/src/mod.rs"] -#[allow(missing_debug_implementations, dead_code, unsafe_op_in_unsafe_fn, unused_unsafe)] +#[allow(missing_debug_implementations, dead_code, unsafe_op_in_unsafe_fn)] #[allow(rustdoc::bare_urls)] #[unstable(feature = "portable_simd", issue = "86656")] mod core_simd; diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index b24882ddb179f..14cc523b0c185 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -312,7 +312,6 @@ macro_rules! debug_assert_ne { /// let c = Ok("abc".to_string()); /// debug_assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); /// ``` -#[macro_export] #[unstable(feature = "assert_matches", issue = "82775")] #[allow_internal_unstable(assert_matches)] #[rustc_macro_transparency = "semitransparent"] @@ -960,6 +959,8 @@ pub(crate) mod builtin { /// /// A compile time error is never emitted when using this macro regardless /// of whether the environment variable is present or not. + /// To emit a compile error if the environment variable is not present, + /// use the [`env!`] macro instead. /// /// # Examples /// @@ -1427,7 +1428,7 @@ pub(crate) mod builtin { #[rustc_builtin_macro] #[macro_export] #[rustc_diagnostic_item = "assert_macro"] - #[allow_internal_unstable(core_panic, edition_panic)] + #[allow_internal_unstable(core_panic, edition_panic, generic_assert_internals)] macro_rules! assert { ($cond:expr $(,)?) => {{ /* compiler built-in */ }}; ($cond:expr, $($arg:tt)+) => {{ /* compiler built-in */ }}; diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index 8dab8d1a6923f..5ec751e5168bf 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -140,7 +140,7 @@ unsafe impl Send for &T {} )] #[fundamental] // for Default, for example, which requires that `[T]: !Default` be evaluatable #[rustc_specialization_trait] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] #[rustc_coinductive] pub trait Sized { // Empty. @@ -173,7 +173,7 @@ pub trait Sized { /// [nomicon-coerce]: ../../nomicon/coercions.html #[unstable(feature = "unsize", issue = "18598")] #[lang = "unsize"] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] pub trait Unsize { // Empty. } @@ -205,6 +205,20 @@ pub trait StructuralPartialEq { // Empty. } +marker_impls! { + #[unstable(feature = "structural_match", issue = "31434")] + StructuralPartialEq for + usize, u8, u16, u32, u64, u128, + isize, i8, i16, i32, i64, i128, + bool, + char, + str /* Technically requires `[u8]: StructuralEq` */, + (), + {T, const N: usize} [T; N], + {T} [T], + {T: ?Sized} &T, +} + /// Required trait for constants used in pattern matches. /// /// Any type that derives `Eq` automatically implements this trait, *regardless* @@ -267,6 +281,7 @@ marker_impls! { bool, char, str /* Technically requires `[u8]: StructuralEq` */, + (), {T, const N: usize} [T; N], {T} [T], {T: ?Sized} &T, @@ -558,59 +573,59 @@ impl Copy for &T {} #[lang = "sync"] #[rustc_on_unimplemented( on( - _Self = "std::cell::OnceCell", + any(_Self = "core::cell:OnceCell", _Self = "std::cell::OnceCell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::OnceLock` instead" ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicU8` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicU16` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicU32` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicU64` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicUsize` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI8` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI16` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI64` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicIsize` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicBool` instead", ), on( - _Self = "std::cell::Cell", + any(_Self = "core::cell::Cell", _Self = "std::cell::Cell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock`", ), on( - _Self = "std::cell::RefCell", + any(_Self = "core::cell::RefCell", _Self = "std::cell::RefCell"), note = "if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` instead", ), message = "`{Self}` cannot be shared between threads safely", @@ -839,7 +854,7 @@ impl StructuralEq for PhantomData {} reason = "this trait is unlikely to ever be stabilized, use `mem::discriminant` instead" )] #[lang = "discriminant_kind"] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] pub trait DiscriminantKind { /// The type of the discriminant, which must satisfy the trait /// bounds required by `mem::Discriminant`. @@ -944,7 +959,7 @@ marker_impls! { #[unstable(feature = "const_trait_impl", issue = "67792")] #[lang = "destruct"] #[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] #[const_trait] pub trait Destruct {} @@ -955,7 +970,7 @@ pub trait Destruct {} #[unstable(feature = "tuple_trait", issue = "none")] #[lang = "tuple_trait"] #[rustc_on_unimplemented(message = "`{Self}` is not a tuple")] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] pub trait Tuple {} /// A marker for pointer-like types. @@ -971,21 +986,20 @@ pub trait Tuple {} pub trait PointerLike {} /// A marker for types which can be used as types of `const` generic parameters. -#[cfg_attr(not(bootstrap), lang = "const_param_ty")] +#[lang = "const_param_ty"] #[unstable(feature = "adt_const_params", issue = "95174")] #[rustc_on_unimplemented(message = "`{Self}` can't be used as a const parameter type")] -pub trait ConstParamTy: StructuralEq {} +#[allow(multiple_supertrait_upcastable)] +pub trait ConstParamTy: StructuralEq + StructuralPartialEq {} /// Derive macro generating an impl of the trait `ConstParamTy`. #[rustc_builtin_macro] #[unstable(feature = "adt_const_params", issue = "95174")] -#[cfg(not(bootstrap))] pub macro ConstParamTy($item:item) { /* compiler built-in */ } -// FIXME(generic_const_parameter_types): handle `ty::FnDef`/`ty::Closure` -// FIXME(generic_const_parameter_types): handle `ty::Tuple` +// FIXME(adt_const_params): handle `ty::FnDef`/`ty::Closure` marker_impls! { #[unstable(feature = "adt_const_params", issue = "95174")] ConstParamTy for @@ -999,6 +1013,10 @@ marker_impls! { {T: ?Sized + ConstParamTy} &T, } +// FIXME(adt_const_params): Add to marker_impls call above once not in bootstrap +#[unstable(feature = "adt_const_params", issue = "95174")] +impl ConstParamTy for () {} + /// A common trait implemented by all function pointers. #[unstable( feature = "fn_ptr_trait", @@ -1006,7 +1024,7 @@ marker_impls! { reason = "internal trait for implementing various traits for all function pointers" )] #[lang = "fn_ptr_trait"] -#[rustc_deny_explicit_impl] +#[rustc_deny_explicit_impl(implement_via_object = false)] pub trait FnPtr: Copy + Clone { /// Returns the address of the function pointer. #[lang = "fn_ptr_addr"] diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index afbfd6d362dc9..2fff3f0efd73a 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -968,7 +968,7 @@ pub const fn replace(dest: &mut T, src: T) -> T { /// Integers and other types implementing [`Copy`] are unaffected by `drop`. /// /// ``` -/// # #![cfg_attr(not(bootstrap), allow(dropping_copy_types))] +/// # #![allow(dropping_copy_types)] /// #[derive(Copy, Clone)] /// struct Foo(u8); /// @@ -1316,9 +1316,9 @@ impl SizedTypeProperties for T {} /// /// assert_eq!(mem::offset_of!(NestedA, b.0), 0); /// ``` -#[cfg(not(bootstrap))] #[unstable(feature = "offset_of", issue = "106655")] -#[allow_internal_unstable(builtin_syntax)] +#[allow_internal_unstable(builtin_syntax, hint_must_use)] pub macro offset_of($Container:ty, $($fields:tt).+ $(,)?) { - builtin # offset_of($Container, $($fields).+) + // The `{}` is for better error messages + crate::hint::must_use({builtin # offset_of($Container, $($fields).+)}) } diff --git a/library/core/src/mem/transmutability.rs b/library/core/src/mem/transmutability.rs index 87ae30619c63b..f5cc86e7767e8 100644 --- a/library/core/src/mem/transmutability.rs +++ b/library/core/src/mem/transmutability.rs @@ -1,3 +1,5 @@ +use crate::marker::ConstParamTy; + /// Are values of a type transmutable into values of another type? /// /// This trait is implemented on-the-fly by the compiler for types `Src` and `Self` when the bits of @@ -5,6 +7,8 @@ /// notwithstanding whatever safety checks you have asked the compiler to [`Assume`] are satisfied. #[unstable(feature = "transmutability", issue = "99571")] #[lang = "transmute_trait"] +#[rustc_deny_explicit_impl(implement_via_object = false)] +#[rustc_coinductive] pub unsafe trait BikeshedIntrinsicFrom where Src: ?Sized, @@ -33,6 +37,9 @@ pub struct Assume { pub validity: bool, } +#[unstable(feature = "transmutability", issue = "99571")] +impl ConstParamTy for Assume {} + impl Assume { /// Do not assume that *you* have ensured any safety properties are met. #[unstable(feature = "transmutability", issue = "99571")] diff --git a/library/core/src/net/ip_addr.rs b/library/core/src/net/ip_addr.rs index 954d88d548e82..56460c75eba9d 100644 --- a/library/core/src/net/ip_addr.rs +++ b/library/core/src/net/ip_addr.rs @@ -450,6 +450,57 @@ impl Ipv4Addr { Ipv4Addr { octets: [a, b, c, d] } } + /// The size of an IPv4 address in bits. + /// + /// # Examples + /// + /// ``` + /// #![feature(ip_bits)] + /// use std::net::Ipv4Addr; + /// + /// assert_eq!(Ipv4Addr::BITS, 32); + /// ``` + #[unstable(feature = "ip_bits", issue = "113744")] + pub const BITS: u32 = 32; + + /// Converts an IPv4 address into host byte order `u32`. + /// + /// # Examples + /// + /// ``` + /// #![feature(ip_bits)] + /// use std::net::Ipv4Addr; + /// + /// let addr = Ipv4Addr::new(0x12, 0x34, 0x56, 0x78); + /// assert_eq!(0x12345678, addr.to_bits()); + /// ``` + #[rustc_const_unstable(feature = "ip_bits", issue = "113744")] + #[unstable(feature = "ip_bits", issue = "113744")] + #[must_use] + #[inline] + pub const fn to_bits(self) -> u32 { + u32::from_be_bytes(self.octets) + } + + /// Converts a host byte order `u32` into an IPv4 address. + /// + /// # Examples + /// + /// ``` + /// #![feature(ip_bits)] + /// use std::net::Ipv4Addr; + /// + /// let addr = Ipv4Addr::from(0x12345678); + /// assert_eq!(Ipv4Addr::new(0x12, 0x34, 0x56, 0x78), addr); + /// ``` + #[rustc_const_unstable(feature = "ip_bits", issue = "113744")] + #[unstable(feature = "ip_bits", issue = "113744")] + #[must_use] + #[inline] + pub const fn from_bits(bits: u32) -> Ipv4Addr { + Ipv4Addr { octets: bits.to_be_bytes() } + } + /// An IPv4 address with the address pointing to localhost: `127.0.0.1` /// /// # Examples @@ -1069,37 +1120,19 @@ impl Ord for Ipv4Addr { #[stable(feature = "ip_u32", since = "1.1.0")] impl From for u32 { - /// Converts an `Ipv4Addr` into a host byte order `u32`. - /// - /// # Examples - /// - /// ``` - /// use std::net::Ipv4Addr; - /// - /// let addr = Ipv4Addr::new(0x12, 0x34, 0x56, 0x78); - /// assert_eq!(0x12345678, u32::from(addr)); - /// ``` + /// Uses [`Ipv4Addr::to_bits`] to convert an IPv4 address to a host byte order `u32`. #[inline] fn from(ip: Ipv4Addr) -> u32 { - u32::from_be_bytes(ip.octets) + ip.to_bits() } } #[stable(feature = "ip_u32", since = "1.1.0")] impl From for Ipv4Addr { - /// Converts a host byte order `u32` into an `Ipv4Addr`. - /// - /// # Examples - /// - /// ``` - /// use std::net::Ipv4Addr; - /// - /// let addr = Ipv4Addr::from(0x12345678); - /// assert_eq!(Ipv4Addr::new(0x12, 0x34, 0x56, 0x78), addr); - /// ``` + /// Uses [`Ipv4Addr::from_bits`] to convert a host byte order `u32` into an IPv4 address. #[inline] fn from(ip: u32) -> Ipv4Addr { - Ipv4Addr { octets: ip.to_be_bytes() } + Ipv4Addr::from_bits(ip) } } @@ -1173,6 +1206,65 @@ impl Ipv6Addr { } } + /// The size of an IPv6 address in bits. + /// + /// # Examples + /// + /// ``` + /// #![feature(ip_bits)] + /// use std::net::Ipv6Addr; + /// + /// assert_eq!(Ipv6Addr::BITS, 128); + /// ``` + #[unstable(feature = "ip_bits", issue = "113744")] + pub const BITS: u32 = 128; + + /// Converts an IPv6 address into host byte order `u128`. + /// + /// # Examples + /// + /// ``` + /// #![feature(ip_bits)] + /// use std::net::Ipv6Addr; + /// + /// let addr = Ipv6Addr::new( + /// 0x1020, 0x3040, 0x5060, 0x7080, + /// 0x90A0, 0xB0C0, 0xD0E0, 0xF00D, + /// ); + /// assert_eq!(0x102030405060708090A0B0C0D0E0F00D_u128, u128::from(addr)); + /// ``` + #[rustc_const_unstable(feature = "ip_bits", issue = "113744")] + #[unstable(feature = "ip_bits", issue = "113744")] + #[must_use] + #[inline] + pub const fn to_bits(self) -> u128 { + u128::from_be_bytes(self.octets) + } + + /// Converts a host byte order `u128` into an IPv6 address. + /// + /// # Examples + /// + /// ``` + /// #![feature(ip_bits)] + /// use std::net::Ipv6Addr; + /// + /// let addr = Ipv6Addr::from(0x102030405060708090A0B0C0D0E0F00D_u128); + /// assert_eq!( + /// Ipv6Addr::new( + /// 0x1020, 0x3040, 0x5060, 0x7080, + /// 0x90A0, 0xB0C0, 0xD0E0, 0xF00D, + /// ), + /// addr); + /// ``` + #[rustc_const_unstable(feature = "ip_bits", issue = "113744")] + #[unstable(feature = "ip_bits", issue = "113744")] + #[must_use] + #[inline] + pub const fn from_bits(bits: u128) -> Ipv6Addr { + Ipv6Addr { octets: bits.to_be_bytes() } + } + /// An IPv6 address representing localhost: `::1`. /// /// This corresponds to constant `IN6ADDR_LOOPBACK_INIT` or `in6addr_loopback` in other @@ -1770,14 +1862,8 @@ impl fmt::Display for Ipv6Addr { f.write_str("::") } else if self.is_loopback() { f.write_str("::1") - } else if let Some(ipv4) = self.to_ipv4() { - match segments[5] { - // IPv4 Compatible address - 0 => write!(f, "::{}", ipv4), - // IPv4 Mapped address - 0xffff => write!(f, "::ffff:{}", ipv4), - _ => unreachable!(), - } + } else if let Some(ipv4) = self.to_ipv4_mapped() { + write!(f, "::ffff:{}", ipv4) } else { #[derive(Copy, Clone, Default)] struct Span { @@ -1911,44 +1997,18 @@ impl Ord for Ipv6Addr { #[stable(feature = "i128", since = "1.26.0")] impl From for u128 { - /// Convert an `Ipv6Addr` into a host byte order `u128`. - /// - /// # Examples - /// - /// ``` - /// use std::net::Ipv6Addr; - /// - /// let addr = Ipv6Addr::new( - /// 0x1020, 0x3040, 0x5060, 0x7080, - /// 0x90A0, 0xB0C0, 0xD0E0, 0xF00D, - /// ); - /// assert_eq!(0x102030405060708090A0B0C0D0E0F00D_u128, u128::from(addr)); - /// ``` + /// Uses [`Ipv6Addr::to_bits`] to convert an IPv6 address to a host byte order `u128`. #[inline] fn from(ip: Ipv6Addr) -> u128 { - u128::from_be_bytes(ip.octets) + ip.to_bits() } } #[stable(feature = "i128", since = "1.26.0")] impl From for Ipv6Addr { - /// Convert a host byte order `u128` into an `Ipv6Addr`. - /// - /// # Examples - /// - /// ``` - /// use std::net::Ipv6Addr; - /// - /// let addr = Ipv6Addr::from(0x102030405060708090A0B0C0D0E0F00D_u128); - /// assert_eq!( - /// Ipv6Addr::new( - /// 0x1020, 0x3040, 0x5060, 0x7080, - /// 0x90A0, 0xB0C0, 0xD0E0, 0xF00D, - /// ), - /// addr); - /// ``` + /// Uses [`Ipv6Addr::from_bits`] to convert a host byte order `u128` to an IPv6 address. #[inline] fn from(ip: u128) -> Ipv6Addr { - Ipv6Addr::from(ip.to_be_bytes()) + Ipv6Addr::from_bits(ip) } } diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 4a035ad61e107..d050d21c8c575 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -403,6 +403,7 @@ impl f32 { /// and the stability of its representation over Rust versions /// and target platforms isn't guaranteed. #[stable(feature = "assoc_int_consts", since = "1.43.0")] + #[rustc_diagnostic_item = "f32_nan"] pub const NAN: f32 = 0.0_f32 / 0.0_f32; /// Infinity (∞). #[stable(feature = "assoc_int_consts", since = "1.43.0")] diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 3aafc435f1e17..d9a738191f7f1 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -401,6 +401,7 @@ impl f64 { /// This constant isn't guaranteed to equal to any specific NaN bitpattern, /// and the stability of its representation over Rust versions /// and target platforms isn't guaranteed. + #[rustc_diagnostic_item = "f64_nan"] #[stable(feature = "assoc_int_consts", since = "1.43.0")] pub const NAN: f64 = 0.0_f64 / 0.0_f64; /// Infinity (∞). diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 1199d09b563d6..1f43520e1b30a 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2126,6 +2126,7 @@ macro_rules! int_impl { /// assert_eq!(a.rem_euclid(-b), 3); /// assert_eq!((-a).rem_euclid(-b), 1); /// ``` + #[doc(alias = "modulo", alias = "mod")] #[stable(feature = "euclidean_division", since = "1.38.0")] #[rustc_const_stable(feature = "const_euclidean_int_methods", since = "1.52.0")] #[must_use = "this returns the result of the operation, \ diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index c9baa09f4074a..95dcaf5dd7397 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -3,7 +3,6 @@ #![stable(feature = "rust1", since = "1.0.0")] use crate::ascii; -use crate::convert::TryInto; use crate::intrinsics; use crate::mem; use crate::ops::{Add, Mul, Sub}; @@ -278,18 +277,12 @@ macro_rules! widening_impl { macro_rules! conv_rhs_for_unchecked_shift { ($SelfT:ty, $x:expr) => {{ - #[inline] - fn conv(x: u32) -> $SelfT { - // FIXME(const-hack) replace with `.try_into().ok().unwrap_unchecked()`. - // SAFETY: Any legal shift amount must be losslessly representable in the self type. - unsafe { x.try_into().ok().unwrap_unchecked() } - } - #[inline] - const fn const_conv(x: u32) -> $SelfT { - x as _ + // If the `as` cast will truncate, ensure we still tell the backend + // that the pre-truncation value was also small. + if <$SelfT>::BITS < 32 { + intrinsics::assume($x <= (<$SelfT>::MAX as u32)); } - - intrinsics::const_eval_select(($x,), const_conv, conv) + $x as $SelfT }}; } diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 38a1c42d9e825..5939dedbd1db0 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -348,7 +348,7 @@ macro_rules! nonzero_unsigned_operations { } /// Adds an unsigned integer to a non-zero value. - #[doc = concat!("Return [`", stringify!($Int), "::MAX`] on overflow.")] + #[doc = concat!("Return [`", stringify!($Ty), "::MAX`] on overflow.")] /// /// # Examples /// @@ -579,7 +579,7 @@ macro_rules! nonzero_signed_operations { /// Checked absolute value. /// Checks for overflow and returns [`None`] if - #[doc = concat!("`self == ", stringify!($Int), "::MIN`.")] + #[doc = concat!("`self == ", stringify!($Ty), "::MIN`.")] /// The result cannot be zero. /// /// # Example @@ -769,8 +769,8 @@ macro_rules! nonzero_signed_operations { /// ``` #[must_use] #[inline] - #[stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] + #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn is_positive(self) -> bool { self.get().is_positive() } @@ -794,13 +794,14 @@ macro_rules! nonzero_signed_operations { /// ``` #[must_use] #[inline] - #[stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] + #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn is_negative(self) -> bool { self.get().is_negative() } - /// Checked negation. Computes `-self`, returning `None` if `self == i32::MIN`. + /// Checked negation. Computes `-self`, + #[doc = concat!("returning `None` if `self == ", stringify!($Ty), "::MIN`.")] /// /// # Example /// @@ -819,8 +820,8 @@ macro_rules! nonzero_signed_operations { /// # } /// ``` #[inline] - #[stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] + #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn checked_neg(self) -> Option<$Ty> { if let Some(result) = self.get().checked_neg() { // SAFETY: negation of nonzero cannot yield zero values. @@ -851,16 +852,18 @@ macro_rules! nonzero_signed_operations { /// # } /// ``` #[inline] - #[stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] + #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn overflowing_neg(self) -> ($Ty, bool) { let (result, overflow) = self.get().overflowing_neg(); // SAFETY: negation of nonzero cannot yield zero values. ((unsafe { $Ty::new_unchecked(result) }), overflow) } - /// Saturating negation. Computes `-self`, returning `MAX` if - /// `self == i32::MIN` instead of overflowing. + /// Saturating negation. Computes `-self`, + #[doc = concat!("returning [`", stringify!($Ty), "::MAX`]")] + #[doc = concat!("if `self == ", stringify!($Ty), "::MIN`")] + /// instead of overflowing. /// /// # Example /// @@ -884,8 +887,8 @@ macro_rules! nonzero_signed_operations { /// # } /// ``` #[inline] - #[stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] + #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn saturating_neg(self) -> $Ty { if let Some(result) = self.checked_neg() { return result; @@ -916,8 +919,8 @@ macro_rules! nonzero_signed_operations { /// # } /// ``` #[inline] - #[stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] - #[rustc_const_stable(feature = "nonzero_negation_ops", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "nonzero_negation_ops", since = "1.71.0")] + #[rustc_const_stable(feature = "nonzero_negation_ops", since = "1.71.0")] pub const fn wrapping_neg(self) -> $Ty { let result = self.get().wrapping_neg(); // SAFETY: negation of nonzero cannot yield zero values. @@ -925,7 +928,7 @@ macro_rules! nonzero_signed_operations { } } - #[stable(feature = "signed_nonzero_neg", since = "CURRENT_RUSTC_VERSION")] + #[stable(feature = "signed_nonzero_neg", since = "1.71.0")] impl Neg for $Ty { type Output = $Ty; @@ -937,7 +940,7 @@ macro_rules! nonzero_signed_operations { } forward_ref_unop! { impl Neg, neg for $Ty, - #[stable(feature = "signed_nonzero_neg", since = "CURRENT_RUSTC_VERSION")] } + #[stable(feature = "signed_nonzero_neg", since = "1.71.0")] } )+ } } @@ -993,7 +996,7 @@ macro_rules! nonzero_unsigned_signed_operations { } /// Multiplies two non-zero integers together. - #[doc = concat!("Return [`", stringify!($Int), "::MAX`] on overflow.")] + #[doc = concat!("Return [`", stringify!($Ty), "::MAX`] on overflow.")] /// /// # Examples /// @@ -1102,11 +1105,11 @@ macro_rules! nonzero_unsigned_signed_operations { #[doc = sign_dependent_expr!{ $signedness ? if signed { - concat!("Return [`", stringify!($Int), "::MIN`] ", - "or [`", stringify!($Int), "::MAX`] on overflow.") + concat!("Return [`", stringify!($Ty), "::MIN`] ", + "or [`", stringify!($Ty), "::MAX`] on overflow.") } if unsigned { - concat!("Return [`", stringify!($Int), "::MAX`] on overflow.") + concat!("Return [`", stringify!($Ty), "::MAX`] on overflow.") } }] /// diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 6f6b6dbb80b3f..23ca37817d4fd 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2024,6 +2024,7 @@ macro_rules! uint_impl { /// ``` #[doc = concat!("assert_eq!(7", stringify!($SelfT), ".rem_euclid(4), 3); // or any other integer type")] /// ``` + #[doc(alias = "modulo", alias = "mod")] #[stable(feature = "euclidean_division", since = "1.38.0")] #[rustc_const_stable(feature = "const_euclidean_int_methods", since = "1.52.0")] #[must_use = "this returns the result of the operation, \ @@ -2074,10 +2075,10 @@ macro_rules! uint_impl { /// Basic usage: /// /// ``` - /// #![feature(int_roundings)] #[doc = concat!("assert_eq!(7_", stringify!($SelfT), ".div_ceil(4), 2);")] /// ``` - #[unstable(feature = "int_roundings", issue = "88581")] + #[stable(feature = "int_roundings1", since = "1.73.0")] + #[rustc_const_stable(feature = "int_roundings1", since = "1.73.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2109,11 +2110,11 @@ macro_rules! uint_impl { /// Basic usage: /// /// ``` - /// #![feature(int_roundings)] #[doc = concat!("assert_eq!(16_", stringify!($SelfT), ".next_multiple_of(8), 16);")] #[doc = concat!("assert_eq!(23_", stringify!($SelfT), ".next_multiple_of(8), 24);")] /// ``` - #[unstable(feature = "int_roundings", issue = "88581")] + #[stable(feature = "int_roundings1", since = "1.73.0")] + #[rustc_const_stable(feature = "int_roundings1", since = "1.73.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2134,13 +2135,13 @@ macro_rules! uint_impl { /// Basic usage: /// /// ``` - /// #![feature(int_roundings)] #[doc = concat!("assert_eq!(16_", stringify!($SelfT), ".checked_next_multiple_of(8), Some(16));")] #[doc = concat!("assert_eq!(23_", stringify!($SelfT), ".checked_next_multiple_of(8), Some(24));")] #[doc = concat!("assert_eq!(1_", stringify!($SelfT), ".checked_next_multiple_of(0), None);")] #[doc = concat!("assert_eq!(", stringify!($SelfT), "::MAX.checked_next_multiple_of(2), None);")] /// ``` - #[unstable(feature = "int_roundings", issue = "88581")] + #[stable(feature = "int_roundings1", since = "1.73.0")] + #[rustc_const_stable(feature = "int_roundings1", since = "1.73.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/ops/index.rs b/library/core/src/ops/index.rs index 1f1784ec9b255..f4649be54d56f 100644 --- a/library/core/src/ops/index.rs +++ b/library/core/src/ops/index.rs @@ -153,7 +153,7 @@ see chapter in The Book " ), on( - _Self = "std::string::String", + any(_Self = "alloc::string::String", _Self = "std::string::String"), note = "you can use `.chars().nth()` or `.bytes().nth()` see chapter in The Book " ), diff --git a/library/core/src/ops/try_trait.rs b/library/core/src/ops/try_trait.rs index b4f69d0b21309..17625daccbcf9 100644 --- a/library/core/src/ops/try_trait.rs +++ b/library/core/src/ops/try_trait.rs @@ -226,8 +226,14 @@ pub trait Try: FromResidual { on( all( from_desugaring = "QuestionMark", - _Self = "std::result::Result", - R = "std::option::Option" + any( + _Self = "core::result::Result", + _Self = "std::result::Result", + ), + any( + R = "core::option::Option", + R = "std::option::Option", + ) ), message = "the `?` operator can only be used on `Result`s, not `Option`s, \ in {ItemContext} that returns `Result`", @@ -237,7 +243,10 @@ pub trait Try: FromResidual { on( all( from_desugaring = "QuestionMark", - _Self = "std::result::Result", + any( + _Self = "core::result::Result", + _Self = "std::result::Result", + ) ), // There's a special error message in the trait selection code for // `From` in `?`, so this is not shown for result-in-result errors, @@ -250,8 +259,14 @@ pub trait Try: FromResidual { on( all( from_desugaring = "QuestionMark", - _Self = "std::option::Option", - R = "std::result::Result", + any( + _Self = "core::option::Option", + _Self = "std::option::Option", + ), + any( + R = "core::result::Result", + R = "std::result::Result", + ) ), message = "the `?` operator can only be used on `Option`s, not `Result`s, \ in {ItemContext} that returns `Option`", @@ -261,7 +276,10 @@ pub trait Try: FromResidual { on( all( from_desugaring = "QuestionMark", - _Self = "std::option::Option", + any( + _Self = "core::option::Option", + _Self = "std::option::Option", + ) ), // `Option`-in-`Option` always works, as there's only one possible // residual, so this can also be phrased strongly. @@ -273,8 +291,14 @@ pub trait Try: FromResidual { on( all( from_desugaring = "QuestionMark", - _Self = "std::ops::ControlFlow", - R = "std::ops::ControlFlow", + any( + _Self = "core::ops::ControlFlow", + _Self = "std::ops::ControlFlow", + ), + any( + R = "core::ops::ControlFlow", + R = "std::ops::ControlFlow", + ) ), message = "the `?` operator in {ItemContext} that returns `ControlFlow` \ can only be used on other `ControlFlow`s (with the same Break type)", @@ -285,7 +309,10 @@ pub trait Try: FromResidual { on( all( from_desugaring = "QuestionMark", - _Self = "std::ops::ControlFlow", + any( + _Self = "core::ops::ControlFlow", + _Self = "std::ops::ControlFlow", + ) // `R` is not a `ControlFlow`, as that case was matched previously ), message = "the `?` operator can only be used on `ControlFlow`s \ diff --git a/library/core/src/option.rs b/library/core/src/option.rs index ec1ef3cf43d12..becb6330997d8 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -1125,6 +1125,7 @@ impl Option { /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] + #[must_use = "if you don't need the returned value, use `if let` instead"] pub fn map_or(self, default: U, f: F) -> U where F: FnOnce(T) -> U, @@ -1138,7 +1139,7 @@ impl Option { /// Computes a default function result (if none), or /// applies a different function to the contained value (if any). /// - /// # Examples + /// # Basic examples /// /// ``` /// let k = 21; @@ -1149,6 +1150,25 @@ impl Option { /// let x: Option<&str> = None; /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42); /// ``` + /// + /// # Handling a Result-based fallback + /// + /// A somewhat common occurrence when dealing with optional values + /// in combination with [`Result`] is the case where one wants to invoke + /// a fallible fallback if the option is not present. This example + /// parses a command line argument (if present), or the contents of a file to + /// an integer. However, unlike accessing the command line argument, reading + /// the file is fallible, so it must be wrapped with `Ok`. + /// + /// ```no_run + /// # fn main() -> Result<(), Box> { + /// let v: u64 = std::env::args() + /// .nth(1) + /// .map_or_else(|| std::fs::read_to_string("/etc/someconfig.conf"), Ok)? + /// .parse()?; + /// # Ok(()) + /// # } + /// ``` #[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn map_or_else(self, default: D, f: F) -> U @@ -1383,6 +1403,7 @@ impl Option { /// let item_2_0 = arr_2d.get(2).and_then(|row| row.get(0)); /// assert_eq!(item_2_0, None); /// ``` + #[doc(alias = "flatmap")] #[inline] #[stable(feature = "rust1", since = "1.0.0")] pub fn and_then(self, f: F) -> Option @@ -1677,6 +1698,41 @@ impl Option { mem::replace(self, None) } + /// Takes the value out of the option, but only if the predicate evaluates to + /// `true` on a mutable reference to the value. + /// + /// In other words, replaces `self` with `None` if the predicate returns `true`. + /// This method operates similar to [`Option::take`] but conditional. + /// + /// # Examples + /// + /// ``` + /// #![feature(option_take_if)] + /// + /// let mut x = Some(42); + /// + /// let prev = x.take_if(|v| if *v == 42 { + /// *v += 1; + /// false + /// } else { + /// false + /// }); + /// assert_eq!(x, Some(43)); + /// assert_eq!(prev, None); + /// + /// let prev = x.take_if(|v| *v == 43); + /// assert_eq!(x, None); + /// assert_eq!(prev, Some(43)); + /// ``` + #[inline] + #[unstable(feature = "option_take_if", issue = "98934")] + pub fn take_if